Add 0.6.0 website (#3020)

This commit is contained in:
Chris Stewart 2021-05-03 12:17:16 -05:00 committed by GitHub
parent ff39945777
commit d25dff14b4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 4251 additions and 0 deletions

View file

@ -0,0 +1,263 @@
---
id: version-0.6.0-server
title: Application Server
original_id: server
---
## App server
The server project is the aggregation of these three sub projects
1. [Wallet](../wallet/wallet.md)
2. [Chain](../chain/chain.md)
3. [Node](../node/node.md)
The server project provides a away to access information from these three projects via a JSON RPC.
## Building the server
### Java binary
You can build the server with the [sbt native packager](https://github.com/sbt/sbt-native-packager).
The native packager offers [numerous ways to package the project](https://github.com/sbt/sbt-native-packager#examples).
In this example we are going to use `stage` which will produce bash scripts we can easily execute. You can stage the server with the following command.
```bash
sbt appServer/universal:stage
```
This will produce a script to execute bitcoin-s which you can start with
```bash
./app/server/target/universal/stage/bin/bitcoin-s-server
```
### Docker
The oracle server also has docker support. You can build a docker image with the following commands
#### Using an existing docker image
We publish docker images on every PR that is merged to bitcoin-s.
You can find the docker repo for the app server [here](https://hub.docker.com/r/bitcoinscala/bitcoin-s-server/tags?page=1&ordering=last_updated)
#### Building a docker image
```
sbt "appServer/docker:stage"
```
This will build a `Dockerfile` that is located in `app/server/target/docker/stage`
You can now build the docker image with
```
docker build app/server/target/docker/stage/ -t bitcoin-s-server:latest
```
Finally, let's run the image! It's important that you correctly configure port forwarding with the docker container so
you can interact with the running container with `bitcoin-s-cli` or `curl`. By default, our oracle
server listens for requests on port `9999`.
This means we need to forward requests on the host machine to the docker container correctly.
This can be done with the following command
```
docker run -d -p 9999:9999 bitcoin-s-server:latest:latest
```
Now you can send requests with `bitcoin-s-cli` or `curl`.
Here is an example with `bitcoin-s-cli`
```
./bitcoin-s-cli getblockcount
10000
```
For more information on build configuration options with `sbt` please see the [sbt native packager docs](https://sbt-native-packager.readthedocs.io/en/latest/formats/docker.html#tasks)
## Configuration
### Java binary configuration
If you would like to pass in a custom datadir for your server, you can do
```bash
./app/server/target/universal/stage/bin/bitcoin-s-server --datadir /path/to/datadir/
```
To use a config file that is not the `bitcoin-s.conf` file in your datadir, you can do
```bash
./app/server/target/universal/stage/bin/bitcoin-s-server --conf /path/to/file.conf
```
You can also pass in a custom `rpcport` to bind to
```bash
./app/server/target/universal/stage/bin/bitcoin-s-server --rpcport 12345
```
For more information on configuring the server please see our [configuration](../config/configuration.md) document
For more information on how to use our built in `cli` to interact with the server please see [cli.md](cli.md)
### Docker configuration
In this example, we are using the latest docker image published to our [docker hub](https://hub.docker.com/repository/docker/bitcoinscala/bitcoin-s-oracle-server/tags?page=1&ordering=last_updated)
which is referenced by `bitcoinscala/bitcoin-s-server:latest`
You can use bitcoin-s with docker volumes. You can also pass in a custom configuration at container runtime.
#### Using a docker volume
```basrc
docker volume create bitcoin-s
docker run -p 9999:9999 \
--mount source=bitcoin-s,target=/home/bitcoin-s/ bitcoinscala/bitcoin-s-server:latest
```
Now you can re-use this volume across container runs. It will keep the same oracle database
and seeds directory located at `/home/bitcoin-s/.bitcoin-s/seeds` in the volume.
#### Using a custom bitcoin-s configuration with docker
You can also specify a custom bitcoin-s configuration at container runtime.
You can mount the configuration file on the docker container and that
configuration will be used in the docker container runtime rather than
the default one we provide [here](https://github.com/bitcoin-s/bitcoin-s/blob/master/app/oracle-server/src/universal/docker-application.conf)
You can do this with the following command
```bashrc
docker run -p 9999:9999 \
--mount type=bind,source=/my/new/config/,target=/home/bitcoin-s/.bitcoin-s/ \
bitcoinscala/bitcoin-s-server:latest --conf /home/bitcoin-s/.bitcoin-s/bitcoin-s.conf
```
Note: If you adjust the `bitcoin-s.server.rpcport` setting you will need to adjust
the `-p 9999:9999` port mapping on the docker container to adjust for this.
## Server Endpoints
### Blockchain
- `getblockcount` - Get the current block height
- `getfiltercount` - Get the number of filters
- `getfilterheadercount` - Get the number of filter headers
- `getbestblockhash` - Get the best block hash
- `getblockheader` - Returns information about block header <hash>
- `hash` - The block hash
- `decoderawtransaction` `tx` - `Decode the given raw hex transaction`
- `tx` - Transaction encoded in hex to decode
### Wallet
- `rescan` `[options]` - Rescan for wallet UTXOs
- `--force` - Clears existing wallet records. Warning! Use with caution!
- `--batch-size <value>` - Number of filters that can be matched in one batch
- `--start <value>` - Start height
- `--end <value>` - End height
- `--ignorecreationtime` - Ignores the wallet creation date and will instead do a full rescan
- `isempty` - Checks if the wallet contains any data
- `walletinfo` - Returns data about the current wallet being used
- `getbalance` `[options]` - Get the wallet balance
- `--sats ` - Display balance in satoshis
- `getconfirmedbalance` `[options]` - Get the wallet balance of confirmed utxos
- `--sats ` - Display balance in satoshis
- `getunconfirmedbalance` `[options]` - Get the wallet balance of unconfirmed utxos
- `--sats ` - Display balance in satoshis
- `getutxos` - Returns list of all wallet utxos
- `getaddresses` - Returns list of all wallet addresses currently being watched
- `getspentaddresses` - Returns list of all wallet addresses that have received funds and been spent
- `getfundedaddresses` - Returns list of all wallet addresses that are holding funds
- `getunusedaddresses` - Returns list of all wallet addresses that have not been used
- `getaccounts` - Returns list of all wallet accounts
- `walletinfo` - Returns meta information about the wallet
- `createnewaccount` - Creates a new wallet account
- `getaddressinfo` `address` - Returns list of all wallet accounts
- `address` - Address to get information about
- `getnewaddress` - Get a new address
- `sendtoaddress` `address` `amount` `[options]` - Send money to the given address
- `address` - Address to send to
- `amount` - Amount to send in BTC
- `--feerate <value>` - Fee rate in sats per virtual byte
- `sendfromoutpoints` `outpoints` `address` `amount` `[options]` - Send money to the given address
- `outpoints` - Out Points to send from
- `address` - Address to send to
- `amount` - Amount to send in BTC
- `--feerate <value>` - Fee rate in sats per virtual byte
- `sendwithalgo` `address` `amount` `algo` `[options]` - Send money to the given address using a specific coin selection algo
- `address` - Address to send to
- `amount` - Amount to send in BTC
- `algo` - Coin selection algo
- `--feerate <value>` - Fee rate in sats per virtual byte
- `signpsbt` `psbt` - Signs the PSBT's inputs with keys that are associated with the wallet
- `psbt` - PSBT to sign
- `opreturncommit` `message` `[options]` - Creates OP_RETURN commitment transaction
- `message` - message to put into OP_RETURN commitment
- `--hashMessage` - should the message be hashed before commitment
- `--feerate <value>` - Fee rate in sats per virtual byte
- `bumpfeecpfp` `txid` `feerate` - Bump the fee of the given transaction id with a child tx using the given fee rate
- `txid` - Id of transaction to bump fee
- `feerate` - Fee rate in sats per virtual byte of the child transaction
- `bumpfeerbf` `txid` `feerate` - Replace given transaction with one with the new fee rate
- `txid` - Id of transaction to bump fee
- `feerate` - New fee rate in sats per virtual byte
- `gettransaction` `txid` - Get detailed information about in-wallet transaction <txid>
- `txid` - The transaction id
- `lockunspent` `unlock` `transactions` - Temporarily lock (unlock=false) or unlock (unlock=true) specified transaction outputs.
- `unlock` - Whether to unlock (true) or lock (false) the specified transactions
- `transactions` - The transaction outpoints to unlock/lock
- `importseed` `walletname` `words` `passphrase` - Imports a mnemonic seed as a new seed file
- `walletname` - Name to associate with this seed
- `words` - Mnemonic seed words, space separated
- `passphrase` - Passphrase to encrypt this seed with
- `importxprv` `walletname` `xprv` `passphrase` - Imports a mnemonic seed as a new seed file
- `walletname` - Name to associate with this seed
- `xprv` - base58 encoded extended private key
- `passphrase` - Passphrase to encrypt this seed with
- `keymanagerpassphrasechange` `oldpassphrase` `newpassphrase` - Changes the wallet passphrase
- `oldpassphrase` - The current passphrase
- `newpassphrase` - The new passphrase
- `keymanagerpassphraseset` `passphrase` - Encrypts the wallet with the given passphrase
- `passphrase` - The passphrase to encrypt the wallet with
### Network
- `getpeers` - List the connected peers
- `stop` - Request a graceful shutdown of Bitcoin-S
- `sendrawtransaction` `tx` `Broadcasts the raw transaction`
- `tx` - Transaction serialized in hex
### PSBT
- `decodepsbt` `psbt` - Return a JSON object representing the serialized, base64-encoded partially signed Bitcoin transaction.
- `psbt` - PSBT serialized in hex or base64 format
- `combinepsbts` `psbts` - Combines all the given PSBTs
- `psbts` - PSBTs serialized in hex or base64 format
- `joinpsbts` `psbts` - Combines all the given PSBTs
- `psbts` - PSBTs serialized in hex or base64 format
- `finalizepsbt` `psbt` - Finalizes the given PSBT if it can
- `psbt` - PSBT serialized in hex or base64 format
- `extractfrompsbt` `psbt` - Extracts a transaction from the given PSBT if it can
- `psbt` - PSBT serialized in hex or base64 format
- `converttopsbt` `unsignedTx` - Creates an empty psbt from the given transaction
- `unsignedTx` - serialized unsigned transaction in hex
### Util
- `createmultisig` `nrequired` `keys` `[address_type]` - Creates a multi-signature address with n signature of m keys required.
- `nrequired` - The number of required signatures out of the n keys.
- `keys` - The hex-encoded public keys.
- `address_type` -The address type to use. Options are "legacy", "p2sh-segwit", and "bech32"
## Sign PSBT with Wallet Example
Bitcoin-S CLI:
```bash
$ bitcoin-s-cli signpsbt cHNidP8BAP0FAQIAAAABWUWxYiPKgdGfXcIxJ6MRDxEpUecw59Gk4NpROI5oukoBAAAAAAAAAAAEPttkvdwAAAAXqRSOVAp6Qe/u2hq74e/ThB8foBKn7IfZYMgGCAAAAADbmaQ2nwAAAEdRIQLpfVqyaL9Jb/IkveatNyVeONE8Q/6TzXAWosxLo9e21SECc5G3XiK7xKLlkBG7prMx7p0fMeQwMH5e9H10mBon39JSrtgtgjjLAQAAUGMhAn2YaZnv25I6d6vbb1kw6Xp5IToDrEzl/0VBIW21gHrTZwXg5jGdALJ1IQKyNpDNiOiN6lWpYethib04+XC9bpFXrdpec+xO3U5IM2is9ckf5AABAD0CAAAAAALuiOL0rRcAABYAFPnpLByQq1Gg3vwiP6qR8FmOOjwxvVllM08DAAALBfXJH+QAsXUAAK4AAAAAAQcBAAAAAAAA
cHNidP8BAP0FAQIAAAABWUWxYiPKgdGfXcIxJ6MRDxEpUecw59Gk4NpROI5oukoBAAAAAAAAAAAEPttkvdwAAAAXqRSOVAp6Qe/u2hq74e/ThB8foBKn7IfZYMgGCAAAAADbmaQ2nwAAAEdRIQLpfVqyaL9Jb/IkveatNyVeONE8Q/6TzXAWosxLo9e21SECc5G3XiK7xKLlkBG7prMx7p0fMeQwMH5e9H10mBon39JSrtgtgjjLAQAAUGMhAn2YaZnv25I6d6vbb1kw6Xp5IToDrEzl/0VBIW21gHrTZwXg5jGdALJ1IQKyNpDNiOiN6lWpYethib04+XC9bpFXrdpec+xO3U5IM2is9ckf5AABAD0CAAAAAALuiOL0rRcAABYAFPnpLByQq1Gg3vwiP6qR8FmOOjwxvVllM08DAAALBfXJH+QAsXUAAK4AAAAAAQcBAAAAAAAA
```
CURL:
```bash
$ curl --data-binary '{"jsonrpc": "1.0", "id": "curltest", "method": "signpsbt", "params": ["cHNidP8BAP0FAQIAAAABWUWxYiPKgdGfXcIxJ6MRDxEpUecw59Gk4NpROI5oukoBAAAAAAAAAAAEPttkvdwAAAAXqRSOVAp6Qe/u2hq74e/ThB8foBKn7IfZYMgGCAAAAADbmaQ2nwAAAEdRIQLpfVqyaL9Jb/IkveatNyVeONE8Q/6TzXAWosxLo9e21SECc5G3XiK7xKLlkBG7prMx7p0fMeQwMH5e9H10mBon39JSrtgtgjjLAQAAUGMhAn2YaZnv25I6d6vbb1kw6Xp5IToDrEzl/0VBIW21gHrTZwXg5jGdALJ1IQKyNpDNiOiN6lWpYethib04+XC9bpFXrdpec+xO3U5IM2is9ckf5AABAD0CAAAAAALuiOL0rRcAABYAFPnpLByQq1Gg3vwiP6qR8FmOOjwxvVllM08DAAALBfXJH+QAsXUAAK4AAAAAAQcBAAAAAAAA"]}' -H "Content-Type: application/json" http://127.0.0.1:9999/
{"result":"cHNidP8BAP0FAQIAAAABWUWxYiPKgdGfXcIxJ6MRDxEpUecw59Gk4NpROI5oukoBAAAAAAAAAAAEPttkvdwAAAAXqRSOVAp6Qe/u2hq74e/ThB8foBKn7IfZYMgGCAAAAADbmaQ2nwAAAEdRIQLpfVqyaL9Jb/IkveatNyVeONE8Q/6TzXAWosxLo9e21SECc5G3XiK7xKLlkBG7prMx7p0fMeQwMH5e9H10mBon39JSrtgtgjjLAQAAUGMhAn2YaZnv25I6d6vbb1kw6Xp5IToDrEzl/0VBIW21gHrTZwXg5jGdALJ1IQKyNpDNiOiN6lWpYethib04+XC9bpFXrdpec+xO3U5IM2is9ckf5AABAD0CAAAAAALuiOL0rRcAABYAFPnpLByQq1Gg3vwiP6qR8FmOOjwxvVllM08DAAALBfXJH+QAsXUAAK4AAAAAAQcBAAAAAAAA","error":null}
```

View file

@ -0,0 +1,115 @@
---
title: Syncing Blockfilters
id: version-0.6.0-filter-sync
original_id: filter-sync
---
The `chain` module has the ability to store [BIP157](https://github.com/bitcoin/bips/blob/master/bip-0157.mediawiki) block filters locally. Generally these filters are useful
for doing wallet rescans. The idea is you can generate a list of script pubkeys you are interested in and see if
the block filter matches the scriptPubKey.
As we demonstrated in [the chain docs](chain.md) with block headers, you can sync block filters from an external data source
as well. We are going to use bitcoind as an example of an external data source to sync filters against. It is important
that the bitcoind version you are using is >= `v19` as the [`getblockfilter`](https://github.com/bitcoin/bitcoin/blob/master/doc/release-notes/release-notes-0.19.0.1.md#new-rpcs)
rpc is implemented there. You need to make sure bitcoind is started with the `-blockfilterindex` flag. This makes it
so we can query filters.
> It is important to remember that you need fully synced block headers before you can sync filter headers and filters. Please see [the chain docs](chain.md) for syncing block headers.
#### Abstract idea of syncing filters.
Our internal infrastructure depends on one function to be implemented to be able to sync filters.
```scala
val getFilterFunc: BlockHeader => Future[FilterWithHeaderHash] = ???
```
With `getFilterFunc` given a `BlockHeader` we can find it's associated `GolombFilter` -- which is our internal repesentation
of a BIP157 block filter.
The basic idea for `FilterSync.syncFilters()` is to look at our current best block header inside of our `ChainApi.getBestBlockHeader()`
and then check what our best block filter's block hash is with `ChainApi.getBestFilterHeader()`. If the blockfilter returned from our internal
data store is NOT associated with our best block header, we attempt to sync our filter headers to catch up to our best block header.
### Syncing block filters against bitcoind
We are going to implement `getFilterFunc` with bitcoind and then sync a few filter headers.
```scala
implicit val system = ActorSystem(s"filter-sync-example")
implicit val ec = system.dispatcher
implicit val chainAppConfig = BitcoinSTestAppConfig.getNeutrinoTestConfig().chainConf
//let's use a helper method to get a v19 bitcoind
//instance and a chainApi
val bitcoindWithChainApiF: Future[BitcoindV19ChainHandler] = {
ChainUnitTest.createBitcoindV19ChainHandler()
}
val bitcoindF = bitcoindWithChainApiF.map(_.bitcoindRpc)
val chainApiF = bitcoindWithChainApiF.map(_.chainHandler)
val filterType = FilterType.Basic
val addressF = bitcoindF.flatMap(_.getNewAddress)
//this is the function that we are going to use to sync
//our internal filters against. We use this function to query
//for each block filter associated with a blockheader
val getFilterFunc: BlockHeader => Future[FilterWithHeaderHash] = { blockHeader =>
val prevFilterResultF =
bitcoindF.flatMap(_.getBlockFilter(blockHeader.hashBE, filterType))
prevFilterResultF.map { filterResult =>
FilterWithHeaderHash(filterResult.filter, filterResult.header)
}
}
//ok enough setup, let's generate a block that we need to sync the filter for in bitcoind
val block1F = for {
bitcoind <- bitcoindF
address <- addressF
hashes <- bitcoind.generateToAddress(1,address)
} yield hashes
//to be able to sync filters, we need to make sure our block headers are synced first
//so let's sync our block headers to our internal chainstate
val chainApiSyncedHeadersF = for {
bitcoind <- bitcoindF
handler <- chainApiF
getBestBlockHash = SyncUtil.getBestBlockHashFunc(bitcoind)
getBlockHeader = SyncUtil.getBlockHeaderFunc(bitcoind)
syncedChainApiHeaders <- ChainSync.sync(handler, getBlockHeader, getBestBlockHash)
} yield syncedChainApiHeaders
//now that we have synced our 1 block header, we can now sync the 1 block filter
//associated with that header.
val chainApiSyncedFiltersF = for {
syncedHeadersChainApi <- chainApiSyncedHeadersF
syncedFilters <- FilterSync.syncFilters(syncedHeadersChainApi,getFilterFunc)
} yield syncedFilters
//now we should have synced our one filter, let's make sure we have it
val resultF = for {
chainApi <- chainApiSyncedFiltersF
filterHeaderCount <- chainApi.getFilterHeaderCount()
filterCount <- chainApi.getFilterCount()
} yield {
println(s"filterHeaderCount=$filterHeaderCount filterCount=$filterCount")
}
//cleanup
resultF.onComplete { _ =>
for {
c <- bitcoindWithChainApiF
_ <- ChainUnitTest.destroyBitcoindV19ChainApi(c)
_ <- system.terminate()
} yield ()
}
```
Yay! Now we have synced block filters from an external data source. If you want to repeatedly sync you can just call
`FilterSync.syncFilters(syncedFiltersChainApi,getFilterFunc)` every time you would like to sync.
Again, you need to ensure
your headers are synced before you can sync filters, so make sure that you are calling `ChainSync.sync()` before syncing
filters.

View file

@ -0,0 +1,401 @@
---
id: version-0.6.0-configuration title: Application Configuration
title: configuration title: Application Configuration
original_id: configuration title: Application Configuration
---
Bitcoin-S uses [HOCON](https://github.com/lightbend/config/blob/master/HOCON.md)
to configure various parts of the application the library offers. HOCON is a superset of JSON, that is, all valid JSON
is valid HOCON.
All configuration for Bitcoin-S is under the `bitcoin-s` key.
If you have a file `application.conf` anywhere on your classpath when using bitcoin-s, the values there take precedence
over the ones found in our
`reference.conf`. We also look for the file `bitcoin-s.conf` in the current Bitcoin-S data directory.
The resolved configuration gets parsed by
[`AppConfig`](/api/org/bitcoins/db/AppConfig).
`AppConfig` is an abstract class that's implemented by corresponding case classes in the `wallet`, `chain` and `node`
projects. Here's some examples of how to construct a wallet configuration:
```scala
import org.bitcoins.wallet.config.WalletAppConfig
import com.typesafe.config.ConfigFactory
import java.nio.file.Paths
import scala.util.Properties
import scala.concurrent.ExecutionContext.Implicits.global
// reads $HOME/.bitcoin-s/
val defaultConfig = WalletAppConfig.fromDefaultDatadir()
// reads a custom data directory
val customDirectory = Paths.get(Properties.userHome, "custom-bitcoin-s-directory")
val configFromCustomDatadir = WalletAppConfig(customDirectory)
// reads a custom data directory and overrides the network to be testnet3
val customOverride = ConfigFactory.parseString("bitcoin-s.network = testnet3")
val configFromCustomDirAndOverride = WalletAppConfig(customDirectory, customOverride)
```
You can pass as many `com.typesafe.config.Config`s as you'd like. If any keys appear multiple times the last one
encountered takes precedence.
## Command Line Options
There are a few command line options available that take precedence over configuration file.
- `--datadir <directory>`
`datadir` sets the data directory instead of using the default `$HOME/.bitcoin-s`
- `--rpcbind <ip>`
`rpcbind` sets the interface the rpc server binds to instead of using the default `127.0.0.1`
- `--rpcport <port>`
`rpcport` sets the port the rpc server binds to instead of using the default `9999`
- `--force-recalc-chainwork`
`force-recalc-chainwork` will force a recalculation of the entire chain's chain work, this can be useful if there is
an incompatible migration or if it got out of sync.
- `-Dlogback.configurationFile=/path/to/config.xml`
You can set a custom logback configuration. If you need help creating a custom logback file you can
read [the logback configuration documentation](http://logback.qos.ch/manual/configuration.html).
## Internal configuration
Database connections are also configured by using HOCON. This is done
in [`reference.conf`](https://github.com/bitcoin-s/bitcoin-s/blob/master/db-commons/src/main/resources/reference.conf)
inside the `db-commons` module. The options exposed here are **not** intended to be used by users of Bitcoin-S, and are
internal only.
## Database Migrations
All of our modules that require databases now have database migrations. The tool we use for these migrations is
called [flyway](https://flywaydb.org/). To find your projects migraitons, you need to look inside of the
`[project-name]/src/main/resources/[database-name]/migration/`. For example, the chain projects migrations live under
the path `chain/src/main/resources/chaindb/migration/V1__chain_db_baseline.sql`.
Migrations can be executed by calling
the [`DbManagement.migrate()`](https://github.com/bitcoin-s/bitcoin-s/blob/e387d075b0ff2e0a0fec15788fcb48e4ddc4d9d5/db-commons/src/main/scala/org/bitcoins/db/DbManagement.scala#L92)
method. Migrations are applied by default on server startup, via
the [`AppConfig.start()`](https://github.com/bitcoin-s/bitcoin-s/blob/master/db-commons/src/main/scala/org/bitcoins/db/AppConfig.scala#L49)
method.
These migrations are setup so that project's databases and migrations are independent of each other. Therefore if you
want to use the `bitcoin-s-chain` project, but not the `bitcoin-s-wallet` project, wallet migrations are not applied. It
should be noted if you are using a module as a library, you are responsible for configuring the database via
[slick's configuration](https://scala-slick.org/doc/3.3.1/database.html#using-typesafe-config) and calling
[`AppConfig.start()`](https://github.com/bitcoin-s/bitcoin-s/blob/master/db-commons/src/main/scala/org/bitcoins/db/AppConfig.scala#L49)
to ensure the entire module is initialized correctly.
## Example Configuration File
```$xslt
bitcoin-s {
datadir = ${HOME}/.bitcoin-s
network = regtest # regtest, testnet3, mainnet, signet
dbDefault = {
dataSourceClass = slick.jdbc.DatabaseUrlDataSource
profile = "slick.jdbc.SQLiteProfile$"
db {
# for information on parameters available here see
# https://scala-slick.org/doc/3.3.1/api/index.html#slick.jdbc.JdbcBackend$DatabaseFactoryDef@forConfig(String,Config,Driver,ClassLoader):Database
path = ${bitcoin-s.datadir}/${bitcoin-s.network}/
driver = org.sqlite.JDBC
user = ""
password = ""
host = localhost
port = 5432
# this needs to be set to 1 for SQLITE as it does not support concurrent database operations
# see: https://github.com/bitcoin-s/bitcoin-s/pull/1840
numThreads = 1
queueSize=5000
connectionPool = "HikariCP"
registerMbeans = true
}
hikari-logging = false
hikari-logging-interval = 10 minute
}
bitcoind-rpc {
# bitcoind rpc username
rpcuser = user
# bitcoind rpc password
rpcpassword = password
# Binary location of bitcoind
binary = ${HOME}/.bitcoin-s/binaries/bitcoind/bitcoin-0.20.1/bin/bitcoind
# bitcoind datadir
datadir = ${HOME}/.bitcoin
# bitcoind network binding
bind = localhost
# bitcoind p2p port
port = 8333
# bitcoind rpc binding
rpcbind = localhost
# bitcoind rpc port
rpcport = 8332
# bitcoind zmq port for all services
zmqport = 29000
# bitcoind zmq raw tx
zmqpubrawtx = "tcp://127.0.0.1:28332"
# bitcoind zmq raw block
zmqpubrawblock = "tcp://127.0.0.1:28333"
# bitcoind zmq hash tx
zmqpubhashtx = "tcp://127.0.0.1:28330"
# bitcoind zmq raw block
zmqpubhashblock = "tcp://127.0.0.1:28331"
}
node {
mode = neutrino # neutrino, spv, bitcoind
peers = [] # a list of peer addresses in form "hostname:portnumber"
# (e.g. "neutrino.testnet3.suredbits.com:18333")
# Port number is optional, the default value is 8333 for mainnet,
# 18333 for testnet and 18444 for regtest.
hikari-logging = true
hikari-logging-interval = 10 minute
}
chain {
force-recalc-chainwork = false
neutrino {
filter-header-batch-size.default = 2000
filter-header-batch-size.regtest = 10
# You can set a network specific filter-header-batch-size
# by adding a trailing `.networkId` (main, test, regtest)
# It is recommended to keep the main and test batch size high
# to keep the sync time fast, however, for regtest it should be small
# so it does not exceed the chain size.
filter-batch-size = 1000
}
hikari-logging = true
hikari-logging-interval = 10 minute
}
# settings for wallet module
wallet {
# You can have multiple wallets by setting a different
# wallet name for each of them. They will each have
# their own unique seed and database or schema,
# depending on the database driver.
# The wallet name can contain letters, numbers, and underscores '_'.
# walletName = MyWallet0
defaultAccountType = segwit # legacy, segwit, nested-segwit
bloomFalsePositiveRate = 0.0001 # percentage
addressGapLimit = 20
discoveryBatchSize = 100
requiredConfirmations = 6
# How big the address queue size is before we throw an exception
# because of an overflow
addressQueueSize = 10
# How long we attempt to generate an address for
# before we timeout
addressQueueTimeout = 5 seconds
# How often the wallet will rebroadcast unconfirmed transactions
rebroadcastFrequency = 4 hours
hikari-logging = true
hikari-logging-interval = 10 minute
}
keymanager {
# You can optionally set a BIP 39 password
# bip39password = "changeMe"
# Password that your seed is encrypted with
aesPassword = changeMe
}
# Bitcoin-S provides manny different fee providers
# You can configure your server to use any of them
# Below is some examples of different options
fee-provider {
# name = mempoolspace # Uses mempool.space's api
# The target is optional for mempool.space
# It refers to the expected number of blocks until confirmation
# target = 6
# name = bitcoinerlive # Uses bitcoiner.live's api
# The target is optional for Bitcoiner Live
# It refers to the expected number of blocks until confirmation
# target = 6
# name = bitgo # Uses BitGo's api
# The target is optional for BitGo
# It refers to the expected number of blocks until confirmation
# target = 6
# name = constant # A constant fee rate in sats/vbyte
# target = 1 # Will always use 1 sat/vbyte
}
server {
# The port we bind our rpc server on
rpcport = 9999
# The ip address we bind our server too
rpcbind = "127.0.0.1"
}
oracle {
# The port we bind our rpc server on
rpcport = 9998
# The ip address we bind our server too
rpcbind = "127.0.0.1"
hikari-logging = true
hikari-logging-interval = 10 minute
db {
path = ${bitcoin-s.datadir}/oracle/
}
}
}
akka {
loglevel = "OFF"
stdout-loglevel = "OFF"
http {
client {
# The time after which an idle connection will be automatically closed.
# Set to `infinite` to completely disable idle connection timeouts.
# some requests potentially take a long time, like generate and prune
idle-timeout = 5 minutes
}
server {
# The amount of time until a request times out on the server
# If you have a large payload this may need to be bumped
# https://doc.akka.io/docs/akka-http/current/common/timeouts.html#request-timeout
request-timeout = 10s
}
}
actor {
debug {
# enable DEBUG logging of all AutoReceiveMessages (Kill, PoisonPill etc.)
autoreceive= off
# enable function of LoggingReceive, which is to log any received message at
# DEBUG level
receive = on
# enable DEBUG logging of unhandled messages
unhandled = off
# enable DEBUG logging of actor lifecycle changes
lifecycle = off
event-stream=off
}
}
}
```
## Database configuration
By default, bitcoin-s uses Sqlite to store its data. It creates three Sqlite databases
in `~/.bitcoin-s/${network}`: `chain.sqlite` for `chain` project,
`node.sqlite` for `node` project and `wallet.sqlite` the wallet. This is the default configuration, it doesn't require
additional changes in the config file.
`bitcoin-s` also supports PostgreSQL as a database backend. In order to use a PostgreSQL database for all project you
need to add following into your config file:
```$xslt
bitcoin-s {
common {
profile = "slick.jdbc.PostgresProfile$"
db {
driver = org.postgresql.Driver
# these 3 options will result into a jdbc url of
# "jdbc:postgresql://localhost:5432/database"
name = database
host = localhost
port = 5432
user = "user"
password = "topsecret"
numThreads = 5
# http://scala-slick.org/doc/3.3.3/database.html
connectionPool = "HikariCP"
registerMbeans = true
}
}
chain.profile = ${bitcoin-s.common.profile}
chain.db = ${bitcoin-s.common.db}
chain.db.poolName = "chain-connection-pool"
node.profile = ${bitcoin-s.common.profile}
node.db = ${bitcoin-s.common.db}
node.db.poolName = "node-connection-pool"
wallet.profile = ${bitcoin-s.common.profile}
wallet.db = ${bitcoin-s.common.db}
wallet.db.poolName = "wallet-connection-pool"
oracle.profile = ${bitcoin-s.common.profile}
oracle.db = ${bitcoin-s.common.db}
oracle.db.poolName = "oracle-connection-pool"
}
```
The database driver will create a separate SQL namespace for each sub-project: `chain`, `node` and `wallet`.
Also you can use mix databases and drivers in one configuration. For example, This configuration file enables Sqlite
for `node` project (it's default, so its configuration is omitted), and `walletdb` and `chaindb` PostgreSQL databases
for `wallet` and `chain` projects:
```$xslt
bitcoin-s {
chain {
profile = "slick.jdbc.PostgresProfile$"
db {
driver = org.postgresql.Driver
name = chaindb
host = localhost
port = 5432
user = "user"
password = "topsecret"
}
}
wallet {
profile = "slick.jdbc.PostgresProfile$"
db {
driver = org.postgresql.Driver
name = walletdb
host = localhost
port = 5432
user = "user"
password = "topsecret"
}
}
}
```

View file

@ -0,0 +1,59 @@
---
id: version-0.6.0-addresses
title: Generating Addresses
original_id: addresses
---
Almost all Bitcoin applications need to generate addresses
for their users somehow. There's a lot going on in getting
a correct bitcoin address, but our APIs make it possible to
to get started with all types of addresses in a matter of
minutes.
## Generating SegWit (bech32) addresses
Generating native SegWit addresses in the bech32 format
is something that all Bitcoin applications should enable,
as it makes the transaction fees less expensive, and also
makes the addresses more readable by humans. However, it
has seen slower than necessary adoption. With Bitcoin-S
you can generate bech32 addresses in four(!) lines of code
(not counting comments and imports), so now there's no
reason to keep using legacy transaction formats.
```scala
// this generates a random private key
val privkey = ECPrivateKey()
// privkey: ECPrivateKey = Masked(ECPrivateKeyImpl)
val pubkey = privkey.publicKey
// pubkey: org.bitcoins.crypto.ECPublicKey = ECPublicKey(02da06ea00abc1766e22267c3de60f8b878bee151640020e55b25e562364ee5d6e)
val segwitAddress = {
// see https://bitcoin.org/en/glossary/pubkey-script
// for reading resources on the details of scriptPubKeys
// pay-to-witness-pubkey-hash scriptPubKey V0
val scriptPubKey = P2WPKHWitnessSPKV0(pubkey)
Bech32Address(scriptPubKey, TestNet3)
}
// segwitAddress: Bech32Address = tb1qaa3rcp5yzpr8x0w6zusxh5aer6qztftljesshn
println(segwitAddress.toString)
// tb1qaa3rcp5yzpr8x0w6zusxh5aer6qztftljesshn
```
## Generating legacy (base58) addresses
If you need to generate legacy addresses for backwards
compatability reasons, that's also a walk in the park.
Take a look:
```scala
// we're reusing the same private/public key pair
// from before. don't do this in an actual application!
val legacyAddress = P2PKHAddress(pubkey, TestNet3)
// legacyAddress: P2PKHAddress = n3LhUMPqUSjX1NPxLn7abU5WSmhJBiFHia
println(legacyAddress.toString)
// n3LhUMPqUSjX1NPxLn7abU5WSmhJBiFHia
```

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,145 @@
---
id: version-0.6.0-hd-keys
title: HD Key Generation
original_id: hd-keys
---
In modern Bitcoin wallets, users only need to write down
a sequence of words, and that sequence is a complete backup
of their wallet. This is thanks to what's called Hierarchical
Deterministic key generation. In short, every wallet using HD
key generation has a root seed for each wallet, and this
seed can be used to generate an arbitrary amount of later
private and public keys. This is done in a standardized manner,
so different wallets can operate with the same standard.
> If you want to jump into the details of how this work,
> you should check out
> [BIP 32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki).
Bitcoin-S supports generating keys in this fashion. Here's a
full example of how to obtain a wallet seed, and then
use that to generate further private and public keys:
```scala
import scodec.bits._
import org.bitcoins.core.crypto._
import org.bitcoins.core.hd._
// the length of the entropy bit vector determine
// how long our phrase ends up being
// 256 bits of entropy results in 24 words
val entropy: BitVector = MnemonicCode.getEntropy256Bits
// entropy: BitVector = BitVector(256 bits, 0x62eb2a698d651e1ce87cb4dde7444a6cfcb4ac152deb1fec7876def416566f81)
val mnemonicCode = MnemonicCode.fromEntropy(entropy)
// mnemonicCode: MnemonicCode = Masked(MnemonicCodeImpl)
mnemonicCode.words // the phrase the user should write down
// res0: Vector[String] = Vector(glass, flock, omit, bracket, faint, attack, peanut, notable, target, demand, barely, supreme, slender, figure, feed, runway, cable, glow, buffalo, sadness, door, clinic, safe, ceiling) // the phrase the user should write down
// the password argument is an optional, extra security
// measure. all MnemonicCode instances will give you a
// valid BIP39 seed, but different passwords will give
// you different seeds. So you could have as many wallets
// from the same seed as you'd like, by simply giving them
// different passwords.
val bip39Seed = BIP39Seed.fromMnemonic(mnemonicCode,
password = "secret password")
// bip39Seed: BIP39Seed = Masked(BIP39SeedImpl)
val xpriv = ExtPrivateKey.fromBIP39Seed(ExtKeyVersion.SegWitMainNetPriv,
bip39Seed)
// xpriv: ExtPrivateKey = Masked(ExtPrivateKeyImpl)
val xpub = xpriv.extPublicKey
// xpub: ExtPublicKey = zpub6jftahH18ngZyJVkgvBx1MQEHkof9MxQ3y5baEPJDN2XzaxHXjuc5bRqzBn4jtaVnWBTeGSNAg3N5U3nqgtpCFcRrCNo9KaAsHZY2WEJUVG
// you can now use the generated xpriv to derive further
// private or public keys
// this can be done with BIP89 paths (called SegWitHDPath in bitcoin-s)
val segwitPath = SegWitHDPath.fromString("m/84'/0'/0'/0/0")
// segwitPath: SegWitHDPath = m/84'/0'/0'/0/0
// alternatively:
val otherSegwitPath =
SegWitHDPath(HDCoinType.Bitcoin,
accountIndex = 0,
HDChainType.External,
addressIndex = 0)
// otherSegwitPath: SegWitHDPath = m/84'/0'/0'/0/0
segwitPath == otherSegwitPath
// res1: Boolean = true
```
## Generating new addresses without having access to the private key
One the coolest features of HD wallets is that it's possible
to generate addresses offline, without having access to the
private keys. This feature is commonly called watch-only
wallets, where a wallet can import information about all
your past and future transactions, without being able to
spend or steal any of your money.
Let's see an example of this:
```scala
import scala.util.Success
import org.bitcoins.core.protocol.script._
import org.bitcoins.core.protocol.Bech32Address
import org.bitcoins.core.config.TestNet3
// first account -------┐
// bitcoin ----------┐ |
// segwit --------┐ | |
val accountPath = BIP32Path.fromString("m/84'/0'/0'")
// accountPath: BIP32Path = m/84'/0'/0'
val accountXpub = {
// this key is sensitive, keep away from prying eyes!
val accountXpriv = xpriv.deriveChildPrivKey(accountPath)
// this key is not sufficient to spend from, but we
// can generate addresses with it!
accountXpriv.extPublicKey
}
// accountXpub: ExtPublicKey = zpub6qrqaWTHWbosf4trAMe9sWVu5hE8a1Fr6nnewT1o7Vr5ca8WGWaVXfeo8b3EvGwk8Fap1wUx6pu2Zu9MTWjTZWh3ESsZ61j64LHekVGobrh
// address no. 0 ---------------┐
// external address ----------┐ |
val firstAddressPath = SegWitHDPath.fromString("m/84'/0'/0'/0/0")
// firstAddressPath: SegWitHDPath = m/84'/0'/0'/0/0
val firstAccountAddress = {
// this is a bit quirky, but we're not interesting in
// deriving the complete path from our account xpub
// instead, we're only interested in the part after
// the account level (3rd level). the .diff() method
// achieves that
val Some(pathDiff) = accountPath.diff(firstAddressPath)
// deriving public keys from hardened extended keys
// is not possible, that's why .deriveChildPubKey()
// returns a Try[ExtPublicKey]. A hardened key is marked
// by a ' after the number in the notation we use above.
val Success(extPubKey) = accountXpub.deriveChildPubKey(pathDiff)
val pubkey = extPubKey.key
val scriptPubKey = P2WPKHWitnessSPKV0(pubkey)
Bech32Address(scriptPubKey, TestNet3)
}
// firstAccountAddress: Bech32Address = tb1qw8khnel5e070tpk9500um5t7qw7mesqhcwsu7y
// tada! We just generated an address you can send money to,
// without having access to the private key!
firstAccountAddress.value
// res2: String = tb1qw8khnel5e070tpk9500um5t7qw7mesqhcwsu7y
// you can now continue deriving addresses from the same public
// key, by imitating what we did above. To get the next
// HD path to generate an address at:
val nextAddressPath: SegWitHDPath = firstAddressPath.next
// nextAddressPath: SegWitHDPath = m/84'/0'/0'/0/1
```
### Signing things with HD keys
Please see [sign.md](../crypto/sign.md) for information on how to sign things with HD keys.

View file

@ -0,0 +1,153 @@
---
id: version-0.6.0-psbts
title: Partially Signed Bitcoin Transactions
original_id: psbts
---
Creating unsigned or partially signed transactions to be passed
around to other signers can be a useful for many applications.
PSBTs offer a standardized format to serialize the necessary data
for signing the transaction, as well as, validating that you in fact
want to sign this transaction.
> If you want to jump into the details of the specification,
> you should checkout
> [BIP 174](https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki).
Bitcoin-S fully supports PSBTs with functionality for
creation, updating, combining, signing, finalizing,
and transaction extraction.
An example on a typical PSBT workflow:
```scala
implicit val ec: ExecutionContextExecutor = ExecutionContext.global
// First you need an unsigned transaction,
// here we have a standard 2 input, 2 output transaction
// This transaction must be of type BaseTransaction
// and have empty ScriptSignatures for all of it's inputs
val unsignedTransaction = BaseTransaction(
"020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000")
// To create the initial PSBT all we need to do is
val emptyPSBT = PSBT.fromUnsignedTx(unsignedTransaction)
// Now that we have an empty PSBT we can start updating it with data we know
// First, we want to fill the UTXO fields that we will need for signing and extraction
// The transactions we add are the fully serialized transaction that we are spending from
val utxo0 = Transaction(
"0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f618765000000")
val utxo1 = Transaction(
"0200000000010158e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd7501000000171600145f275f436b09a8cc9a2eb2a2f528485c68a56323feffffff02d8231f1b0100000017a914aed962d6654f9a2b36608eb9d64d2b260db4f1118700c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e88702483045022100a22edcc6e5bc511af4cc4ae0de0fcd75c7e04d8c1c3a8aa9d820ed4b967384ec02200642963597b9b1bc22c75e9f3e117284a962188bf5e8a74c895089046a20ad770121035509a48eb623e10aace8bfd0212fdb8a8e5af3c94b0b133b95e114cab89e4f7965000000")
val psbtWithUTXOs = emptyPSBT.addUTXOToInput(utxo0, index = 0).addUTXOToInput(utxo1, index = 1)
// After we have the relevant UTXOs we can add the
// redeem scripts, witness scripts, and BIP 32 derivation paths if needed
// In this transaction the first input is a P2SH 2-of-2 multisig
// so we need to add its corresponding redeem script.
// Here we are just using a deserialized version of the redeem script but
// you may generate your ScriptPubKey another way in practice
val redeemScript0 = ScriptPubKey.fromAsmBytes(
hex"5221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae")
val psbtWithUpdatedFirstInput =
psbtWithUTXOs.addRedeemOrWitnessScriptToInput(redeemScript0, index = 0)
// The second input in this transaction is a P2SH(P2WSH) 2-of-2 multisig
// so we need to add its corresponding redeem script and witness script.
// Here we add them both using the same function, the PSBT updater will
// be able to figure out, based on the available data, where to correctly
val redeemScript1 = ScriptPubKey.fromAsmBytes(
hex"00208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903")
val witnessScript = ScriptPubKey.fromAsmBytes(
hex"522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae")
// put the data in the PSBT
val psbtWithUpdatedSecondInput = psbtWithUpdatedFirstInput
.addRedeemOrWitnessScriptToInput(redeemScript1, index = 1)
.addRedeemOrWitnessScriptToInput(witnessScript, index = 1)
// Before signing we need to add the needed SigHash flags so we know how to sign the transaction
// If one is not provided it will be assumed to be SigHashAll
val psbtWithSigHashFlags = psbtWithUpdatedSecondInput
.addSigHashTypeToInput(HashType.sigHashAll, index = 0)
.addSigHashTypeToInput(HashType.sigHashAll, index = 1)
// Next, we can now sign the PSBT
// Signing a PSBT will return a Future[PSBT] so this will need to be handled
// correctly in an application
// Here we use the relevant private keys to sign the first input
val privKey0 = ECPrivateKeyUtil.fromWIFToPrivateKey(
"cP53pDbR5WtAD8dYAW9hhTjuvvTVaEiQBdrz9XPrgLBeRFiyCbQr")
val privKey1 = ECPrivateKeyUtil.fromWIFToPrivateKey(
"cR6SXDoyfQrcp4piaiHE97Rsgta9mNhGTen9XeonVgwsh4iSgw6d")
val psbtFirstSig =
psbtWithSigHashFlags
.sign(inputIndex = 0, signer = privKey0)
.sign(inputIndex = 0, signer = privKey1)
// Alternatively, you can use produce a signature with a ECSignatureParams
// using the BitcoinSigner will return a PartialSignature that can be added to a PSBT
// First we need to declare out spendingInfoSingle
val outPoint = unsignedTransaction.inputs.head.previousOutput
val output = utxo0.outputs(outPoint.vout.toInt)
val spendingInfoSingle = ECSignatureParams(
InputInfo(outPoint = outPoint,
output = output,
redeemScriptOpt = Some(redeemScript0),
scriptWitnessOpt = None,
conditionalPath = ConditionalPath.NoCondition),
prevTransaction = utxo0,
signer = privKey0,
hashType = HashType.sigHashAll
)
// Then we can sign the transaction
val signature = BitcoinSigner.signSingle(
spendingInfo = spendingInfoSingle,
unsignedTx = unsignedTransaction,
isDummySignature = false)
// We can then add the signature to the PSBT
// Note: this signature could be produced by us or another party
psbtWithSigHashFlags.addSignature(signature, inputIndex = 0)
// With our first input signed we can now move on to showing how another party could sign our second input
// In this scenario, let's say that the second input does not belong to us and we need
// another party to sign it. In this case we would need to send the PSBT to the other party.
// The two standard formats for this are in byte form or in base64 you can access these easily.
val bytes = psbtFirstSig.bytes
val base64 = psbtFirstSig.base64
// After the other party has signed their input they can send us back the PSBT with the signatures
// To import we can use any of these functions
val fromBytes = PSBT.fromBytes(
hex"70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f618765000000220202dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d7483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01010304010000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f0000008000000080010000800001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8872202023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e73473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d2010103040100000001042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f00000080000000800200008000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000")
val fromBase64 = PSBT.fromBase64(
"cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD")
// After we've imported the PSBT we can combine it with our own signed PSBT so we can
// have one PSBT with all of the necessary data
val combinedPSBT = fromBase64.combinePSBT(psbtFirstSig)
// Now that the PSBT has all the necessary data, we can finalize it and extract the transaction
// This will return a Try[PSBT] and will fail if you do not have all the required fields filled
val finalizedPSBT = combinedPSBT.finalizePSBT
// After it has been finalized we can extract the fully signed transaction that is ready
// to be broadcast to the network.
// You can also use extractTransactionAndValidate that will validate if the transaction is valid
finalizedPSBT.get.extractTransaction
```

View file

@ -0,0 +1,158 @@
---
id: version-0.6.0-txbuilder
title: TxBuilder Example
original_id: txbuilder
---
Bitcoin-S features a transaction building API that allows you to construct and sign Bitcoin transactions. Here's an example of how to use it
```scala
implicit val ec: ExecutionContext = ExecutionContext.Implicits.global
// ec: ExecutionContext = scala.concurrent.impl.ExecutionContextImpl$$anon$3@6be04fea[Running, parallelism = 16, size = 1, active = 0, running = 0, steals = 5740, tasks = 0, submissions = 0]
// Initialize a transaction builder
val builder = RawTxBuilder()
// builder: RawTxBuilder = RawTxBuilder()
// generate a fresh private key that we are going to use in the scriptpubkey
val privKey = ECPrivateKey.freshPrivateKey
// privKey: ECPrivateKey = Masked(ECPrivateKeyImpl)
val pubKey = privKey.publicKey
// pubKey: ECPublicKey = ECPublicKey(0221a0372e832444037fae5a633b1011f977de38d8dbf15ee86f9792f128bbf69f)
// this is the script that the TxBuilder is going to create a
// script signature that validly spends this scriptPubKey
val creditingSpk = P2PKHScriptPubKey(pubKey = privKey.publicKey)
// creditingSpk: P2PKHScriptPubKey = pkh(406d36ed4b1da94e4b97f25312ca620df99672fd)
val amount = 10000.satoshis
// amount: Satoshis = 10000 sats
// this is the UTXO we are going to be spending
val utxo =
TransactionOutput(value = amount, scriptPubKey = creditingSpk)
// utxo: TransactionOutput = TransactionOutput(10000 sats,pkh(406d36ed4b1da94e4b97f25312ca620df99672fd))
// the private key that locks the funds for the script we are spending too
val destinationPrivKey = ECPrivateKey.freshPrivateKey
// destinationPrivKey: ECPrivateKey = Masked(ECPrivateKeyImpl)
// the amount we are sending -- 5000 satoshis -- to the destinationSPK
val destinationAmount = 5000.satoshis
// destinationAmount: Satoshis = 5000 sats
// the script that corresponds to destination private key, this is what is receiving the money
val destinationSPK =
P2PKHScriptPubKey(pubKey = destinationPrivKey.publicKey)
// destinationSPK: P2PKHScriptPubKey = pkh(3ebbbb7b65ea2e7b7383c4902e8e81ed74d3a5f6)
// this is where we are sending money too
// we could add more destinations here if we
// wanted to batch transactions
val destinations = {
val destination0 = TransactionOutput(value = destinationAmount,
scriptPubKey = destinationSPK)
Vector(destination0)
}
// destinations: Vector[TransactionOutput] = Vector(TransactionOutput(5000 sats,pkh(3ebbbb7b65ea2e7b7383c4902e8e81ed74d3a5f6)))
// Add the destinations to the tx builder
builder ++= destinations
// res0: RawTxBuilder = RawTxBuilder()
// we have to fabricate a transaction that contains the
// UTXO we are trying to spend. If this were a real blockchain
// we would need to reference the UTXO set
val creditingTx = BaseTransaction(version = Int32.one,
inputs = Vector.empty,
outputs = Vector(utxo),
lockTime = UInt32.zero)
// creditingTx: BaseTransaction = BaseTransaction(Int32Impl(1),Vector(),Vector(TransactionOutput(10000 sats,pkh(406d36ed4b1da94e4b97f25312ca620df99672fd))),UInt32Impl(0))
// this is the information we need from the crediting TX
// to properly "link" it in the transaction we are creating
val outPoint = TransactionOutPoint(creditingTx.txId, UInt32.zero)
// outPoint: TransactionOutPoint = TransactionOutPoint(e42c4919ec52597b2a8bca5f691bb551febc4ed5d5185ea56405c4e423077cf6:0)
val input = TransactionInput(
outPoint,
EmptyScriptSignature,
sequenceNumber = UInt32.zero)
// input: TransactionInput = TransactionInputImpl(TransactionOutPoint(e42c4919ec52597b2a8bca5f691bb551febc4ed5d5185ea56405c4e423077cf6:0),EmptyScriptSignature,UInt32Impl(0))
// Add a new input to our builder
builder += input
// res1: RawTxBuilder = RawTxBuilder()
// We can now generate a RawTxBuilderResult ready to be finalized
val builderResult = builder.result()
// builderResult: RawTxBuilderResult = RawTxBuilderResult(Int32Impl(2),Vector(TransactionInputImpl(TransactionOutPoint(e42c4919ec52597b2a8bca5f691bb551febc4ed5d5185ea56405c4e423077cf6:0),EmptyScriptSignature,UInt32Impl(0))),Vector(TransactionOutput(5000 sats,pkh(3ebbbb7b65ea2e7b7383c4902e8e81ed74d3a5f6))),UInt32Impl(0))
// this contains the information needed to analyze our input during finalization
val inputInfo = P2PKHInputInfo(outPoint, amount, privKey.publicKey)
// inputInfo: P2PKHInputInfo = P2PKHInputInfo(TransactionOutPoint(e42c4919ec52597b2a8bca5f691bb551febc4ed5d5185ea56405c4e423077cf6:0),10000 sats,ECPublicKey(0221a0372e832444037fae5a633b1011f977de38d8dbf15ee86f9792f128bbf69f))
// this is how much we are going to pay as a fee to the network
// for this example, we are going to pay 1 satoshi per byte
val feeRate = SatoshisPerByte(1.satoshi)
// feeRate: SatoshisPerByte = 1 sats/byte
val changePrivKey = ECPrivateKey.freshPrivateKey
// changePrivKey: ECPrivateKey = Masked(ECPrivateKeyImpl)
val changeSPK = P2PKHScriptPubKey(pubKey = changePrivKey.publicKey)
// changeSPK: P2PKHScriptPubKey = pkh(19b08251ccd72e7d17f52c1a2006e177ff74cae4)
// We chose a finalizer that adds a change output to our tx based on a fee rate
val finalizer = StandardNonInteractiveFinalizer(
Vector(inputInfo),
feeRate,
changeSPK)
// finalizer: StandardNonInteractiveFinalizer = StandardNonInteractiveFinalizer(Vector(P2PKHInputInfo(TransactionOutPoint(e42c4919ec52597b2a8bca5f691bb551febc4ed5d5185ea56405c4e423077cf6:0),10000 sats,ECPublicKey(0221a0372e832444037fae5a633b1011f977de38d8dbf15ee86f9792f128bbf69f))),1 sats/byte,pkh(19b08251ccd72e7d17f52c1a2006e177ff74cae4))
// We can now finalize the tx builder result from earlier with this finalizer
val unsignedTx: Transaction = finalizer.buildTx(builderResult)
// unsignedTx: Transaction = BaseTransaction(Int32Impl(2),Vector(TransactionInputImpl(TransactionOutPoint(e42c4919ec52597b2a8bca5f691bb551febc4ed5d5185ea56405c4e423077cf6:0),EmptyScriptSignature,UInt32Impl(0))),Vector(TransactionOutput(5000 sats,pkh(3ebbbb7b65ea2e7b7383c4902e8e81ed74d3a5f6)), TransactionOutput(4775 sats,pkh(19b08251ccd72e7d17f52c1a2006e177ff74cae4))),UInt32Impl(0))
// We now turn to signing the unsigned transaction
// this contains all the information we need to
// validly sign the UTXO above
val utxoInfo = ScriptSignatureParams(inputInfo = inputInfo,
prevTransaction = creditingTx,
signers = Vector(privKey),
hashType =
HashType.sigHashAll)
// utxoInfo: ScriptSignatureParams[P2PKHInputInfo] = ScriptSignatureParams(P2PKHInputInfo(TransactionOutPoint(e42c4919ec52597b2a8bca5f691bb551febc4ed5d5185ea56405c4e423077cf6:0),10000 sats,ECPublicKey(0221a0372e832444037fae5a633b1011f977de38d8dbf15ee86f9792f128bbf69f)),BaseTransaction(Int32Impl(1),Vector(),Vector(TransactionOutput(10000 sats,pkh(406d36ed4b1da94e4b97f25312ca620df99672fd))),UInt32Impl(0)),Vector(Masked(ECPrivateKeyImpl)),SIGHASH_ALL(Int32Impl(1)))
// all of the UTXO spending information, since we only have
// one input, this is just one element
val utxoInfos: Vector[ScriptSignatureParams[InputInfo]] = Vector(utxoInfo)
// utxoInfos: Vector[ScriptSignatureParams[InputInfo]] = Vector(ScriptSignatureParams(P2PKHInputInfo(TransactionOutPoint(e42c4919ec52597b2a8bca5f691bb551febc4ed5d5185ea56405c4e423077cf6:0),10000 sats,ECPublicKey(0221a0372e832444037fae5a633b1011f977de38d8dbf15ee86f9792f128bbf69f)),BaseTransaction(Int32Impl(1),Vector(),Vector(TransactionOutput(10000 sats,pkh(406d36ed4b1da94e4b97f25312ca620df99672fd))),UInt32Impl(0)),Vector(Masked(ECPrivateKeyImpl)),SIGHASH_ALL(Int32Impl(1))))
// Yay! Now we use the RawTxSigner object to sign the tx.
// The 'sign' method is going produce a validly signed transaction
// This is going to iterate through each of the UTXOs and use
// the corresponding ScriptSignatureParams to produce a validly
// signed input. This UTXO has:
// 1: one input
// 2: outputs (destination and change outputs)
// 3: a fee rate of 1 satoshi/byte
val signedTx: Transaction =
RawTxSigner.sign(
utx = unsignedTx,
utxoInfos = utxoInfos,
expectedFeeRate = feeRate
)
// signedTx: Transaction = BaseTransaction(Int32Impl(2),Vector(TransactionInputImpl(TransactionOutPoint(e42c4919ec52597b2a8bca5f691bb551febc4ed5d5185ea56405c4e423077cf6:0),P2PKHScriptSignature(ECPublicKey(0221a0372e832444037fae5a633b1011f977de38d8dbf15ee86f9792f128bbf69f), ECDigitalSignature(3044022019a959074d916270112980a8ab6d1835d6ff04121a4b1a82bac4a5e4121c720e022027ed31d51f4713bc8901f0d39d70ef4c85712726e522005f232b70d5eabe170d01)),UInt32Impl(0))),Vector(TransactionOutput(5000 sats,pkh(3ebbbb7b65ea2e7b7383c4902e8e81ed74d3a5f6)), TransactionOutput(4775 sats,pkh(19b08251ccd72e7d17f52c1a2006e177ff74cae4))),UInt32Impl(0))
```
```scala
signedTx.inputs.length
// res2: Int = 1
signedTx.outputs.length
// res3: Int = 2
//remember, you can call .hex on any bitcoin-s data structure to get the hex representation!
signedTx.hex
// res4: String = 0200000001f67c0723e4c40564a55e18d5d54ebcfe51b51b695fca8b2a7b5952ec19492ce4000000006a473044022019a959074d916270112980a8ab6d1835d6ff04121a4b1a82bac4a5e4121c720e022027ed31d51f4713bc8901f0d39d70ef4c85712726e522005f232b70d5eabe170d01210221a0372e832444037fae5a633b1011f977de38d8dbf15ee86f9792f128bbf69f000000000288130000000000001976a9143ebbbb7b65ea2e7b7383c4902e8e81ed74d3a5f688aca7120000000000001976a91419b08251ccd72e7d17f52c1a2006e177ff74cae488ac00000000
```

View file

@ -0,0 +1,40 @@
---
id: version-0.6.0-adaptor-signatures
title: Adaptor Signatures
original_id: adaptor-signatures
---
Bitcoin-S now has support for [ECDSA Adaptor Signatures](https://github.com/discreetlogcontracts/dlcspecs/blob/03bf7095c2016e1ce9c9fb612920872d4456f179/ECDSA-adaptor.md).
There are four relevant functions to adaptor signatures:
* `sign` (aka encrypt)
* This function belongs to `ECPrivateKey` and creates an adaptor signature given a message (`ByteVector`) and an adaptor point (`ECPublicKey`).
* `verify`
* Verifies an adaptor signature given the signing public key, the message and the adaptor point.
* `complete` (aka decrypt)
* This function belongs to `ECPrivateKey` and computes a valid ECDSA signature given a valid adaptor signature whose adaptor point is this private key's public key.
* `extract` (aka recover)
* This function belongs to `ECPublicKey` and computes the adaptor secret (private key to this public key) given a valid adaptor signature for this adaptor point, and the valid ECDSA signature computed using `complete`.
The following code shows each function to do with adaptor signature usage:
```scala
// Alice generages an adaptor signature using her private key and the adaptor point
val adaptorSig = privKey.adaptorSign(adaptorPoint, msg)
// Bob verifies this adaptor signature using Alice's public key and the adaptor point
require(pubKey.adaptorVerify(msg, adaptorPoint, adaptorSig))
// Bob computes a valid ECDSA signature using the adaptorSignature, which he knows
val sig = adaptorSecret.completeAdaptorSignature(adaptorSig)
// Anyone can validate this signature
require(pubKey.verify(msg, sig))
// Alice can compute the adaptor secret from the signatures
val secret = adaptorPoint.extractAdaptorSecret(adaptorSig, sig)
require(secret == adaptorSecret)
```

View file

@ -0,0 +1,65 @@
---
id: version-0.6.0-sign
title: Sign API
original_id: sign
---
### The [`Sign` API](/api/org/bitcoins/crypto/Sign)
This is the API we define to sign things with. It takes in an arbitrary byte vector and returns a `Future[ECDigitalSignature]`. The reason we incorporate `Future`s here is for extensibility of this API. We would like to provide implementations of this API for hardware devices, which need to be asynchrnous since they may require user input.
From [Sign.scala](/api/org/bitcoins/crypto/Sign):
```scala
import scodec.bits._
import org.bitcoins.crypto._
import scala.concurrent._
import scala.concurrent.duration._
trait Sign {
def signFunction: ByteVector => Future[ECDigitalSignature]
def signFuture(bytes: ByteVector): Future[ECDigitalSignature] =
signFunction(bytes)
def sign(bytes: ByteVector): ECDigitalSignature = {
Await.result(signFuture(bytes), 30.seconds)
}
def publicKey: ECPublicKey
}
```
The `ByteVector` that is input to the `signFunction` should be the hash that is output from [`TransactionSignatureSerializer`](/api/org/bitcoins/core/crypto/TransactionSignatureSerializer)'s `hashForSignature` method. Our in-memory [`BaseECKey`](/api/org/bitcoins/crypto/BaseECKey) types implement the `Sign` API.
If you wanted to implement a new `Sign` api for a hardware wallet, you can easily pass it into the `TxBuilder`/`Signer` classes to allow for you to use those devices to sign with Bitcoin-S.
This API is currently used to sign ordinary transactions with our [`Signer`](/api/org/bitcoins/core/wallet/signer/Signer)s. The `Signer` subtypes (i.e. `P2PKHSigner`) implement the specific functionality needed to produce a valid digital signature for their corresponding script type.
### The [`ExtSign`](/api/org/bitcoins/crypto/Sign) API.
An [ExtKey](/api/org/bitcoins/core/crypto/ExtKey) is a data structure that can be used to generate more keys from a parent key. For more information look at [hd-keys.md](../core/hd-keys.md)
You can sign with `ExtPrivateKey` the same way you could with a normal `ECPrivateKey`.
```scala
import org.bitcoins.core.hd._
import org.bitcoins.core.crypto._
val extPrivKey = ExtPrivateKey(ExtKeyVersion.SegWitMainNetPriv)
// extPrivKey: ExtPrivateKey = Masked(ExtPrivateKeyImpl)
extPrivKey.sign(DoubleSha256Digest.empty.bytes)
// res0: ECDigitalSignature = ECDigitalSignature(30440220544800a9006f156b21db20dc177fba4e087ed699ff8d388e23223d5821e3490102205321f3059b3edd7fbb97952f1776e7d779686c89ada0b134be5d581c8f736a75)
val path = BIP32Path(Vector(BIP32Node(0,false)))
// path: BIP32Path = m/0
extPrivKey.sign(DoubleSha256Digest.empty.bytes,path)
// res1: ECDigitalSignature = ECDigitalSignature(304402207c09b230d45a9afe8d4dc0a0ee3aa5a74c31a6d3e32f7b32dab99dee6762071d02207ff2ec0ed643f31b86902adfd66c2aa61c8fecf36271c6d6df6cc17264cbb603)
```
With `ExtSign`, you can use `ExtPrivateKey` to sign transactions inside of `TxBuilder` since `UTXOSpendingInfo` takes in `Sign` as a parameter.
You can also provide a `path` to use to derive a child `ExtPrivateKey`, and then sign with that child private key

View file

@ -0,0 +1,181 @@
---
id: version-0.6.0-getting-setup
title: Getting Bitcoin-S installed on your machine
original_id: getting-setup
---
> This documentation is intended for setting up development of bitcoin-s.
> If you want to just install bitcoin-s rather than develop,
> see [getting-started](getting-started.md)
## Getting Setup With Bitcoin-S
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
<!-- END doctoc -->
- [Step 1: Java and Scala](#step-1-java-and-scala)
- [Step 2: Bitcoin-S Repository](#step-2-bitcoin-s-repository)
- [Step 3: Configuration](#step-3-configuration)
- [Step 4 (Optional): Discreet Log Contract Branch](#step-4-optional-discreet-log-contract-branch)
- [Step 5: Setting Up A Bitcoin-S Server (Neutrino Node)](#step-5-setting-up-a-bitcoin-s-server)
- [Step 6 (Optional): Moving To Testnet](#step-6-optional-moving-to-testnet)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
## Step 1: Java and Scala
To get started you will need Java, Scala, and some other nice tools installed, luckily the Scala team has an easy setup process!
Simply follow the instructions in [this short blog](https://www.scala-lang.org/2020/06/29/one-click-install.html) to get started.
## Step 2: Bitcoin-S Repository
Now, it is time to clone the [Bitcoin-S repository](https://github.com/bitcoin-s/bitcoin-s/) by running
```bashrc
git clone --depth 100 --recursive git@github.com:bitcoin-s/bitcoin-s.git
```
or alternatively, if you do not have ssh setup with github, you can run
```bashrc
git clone --depth 100 --recursive https://github.com/bitcoin-s/bitcoin-s.git
```
#### Optional: Running full test suite
<details>
> WARNING: This should not be done on low resource machines. Running the entire test suite requires at minimum of 4GB
> of RAM on the machine you are running this on.
To run the entire test suite, you need to download all bitcoind instances and eclair instances. This is needed for unit tests
or binding bitcoin-s to a bitcoind instance if you do not have locally running instances.
```bashrc
sbt downloadBitcoind
sbt downloadEclair
```
If you want to run the entire test suite you can run the following command after you download bitcoind
and eclair.
```bashrc
sbt test
```
</details>
## Step 3: Configuration
Now that we have the bitcoin-s repo setup, we want to create our application configurations. This is done by creating a `bitcoin-s.conf` file at `$HOME/.bitcoin-s`. [Here is an example configuration file](config/configuration.md#example-configuration-file). The only thing that you will _need_ to change is the `peers` list to which you will want to add `"localhost:18444"` if you want to run in regtest.
Once the bitcoin-s configuration is all done, I recommend creating a directory someplace in which to run your `bitcoind` node. Once you have this directory created, add the following `bitcoin.conf` file to it
```
regtest=1
server=1
rpcuser=[your username here]
rpcpassword=[your password here]
daemon=1
blockfilterindex=1
peerblockfilters=1
debug=1
txindex=1
zmqpubrawblock=tcp://127.0.0.1:29000
zmqpubrawtx=tcp://127.0.0.1:29000
```
## Step 4: Setting Up A Bitcoin-S Node
We are finally ready to start running some programs! Follow the [instructions here](applications/server.md#building-the-server) to build the server. Then, follow [these instructions](applications/cli.md) to setup the CLI.
There are 2 ways to use the bitcoin-s server. It can either be as a neutrino node or use bitcoind as a backend.
This can be configured by the configuration option `bitcoin-s.node.mode` choosing either `neutrino` or `bitcoind`.
### Neutrino Node
<details>
To use a neutrino server you need to be paired with a bitcoin node that can serve compact filters.
[Suredbits](https://suredbits.com/) runs a mainnet and testnet node you can connect to them by setting your `peers` config option to:
Mainnet:
`bitcoin-s.node.peers = ["neutrino.suredbits.com:8333"]`
Testnet:
`bitcoin-s.node.peers = ["neutrino.testnet3.suredbits.com:18333"]`
If you would like to use your own node you can either use the bitcoind backend option or connect to your own compatible node.
There is no released version of bitcoind that is neutrino compatible, so you will either have to compile the latest `master` yourself, or use the experimental version provided by running `sbt downloadBitcoind`.
After building your bitcoin-s server, properly configuring it to be in `neutrino` mode you can start your server with:
```bashrc
./app/server/target/universal/stage/bin/bitcoin-s-server
```
and once this is done, you should be able to communicate with the server using
```bashrc
./app/cli/target/universal/stage/bitcoin-s-cli getnewaddress
```
</details>
### Bitcoind Backend
<details>
If you already have a bitcoind node running and would like to connect your bitcoin-s server to it you can set your node's mode to `bitcoind`.
You will need to configure bitcoin-s to be able to find your bitcoind.
If you would only like bitcoin-s to connect to bitcoind and start it itself then you only need to properly set the `rpcuser`, and `rpcpassword` options.
If you would like bitcoin-s to launch bitcoind on start up you will need to set the other configuration options.
These options should default to use the latest bitcoind downloaded from `sbt downloadBitcoind`.
```$xslt
bitcoin-s {
bitcoind-rpc {
# bitcoind rpc username
rpcuser = user
# bitcoind rpc password
rpcpassword = password
# Binary location of bitcoind
binary = ${HOME}/.bitcoin-s/binaries/bitcoind/bitcoin-0.20.1/bin/bitcoind
# bitcoind datadir
datadir = ${HOME}/.bitcoin
# bitcoind network binding
bind = localhost
# bitcoind p2p port
port = 8333
# bitcoind rpc binding
rpcbind = localhost
# bitcoind rpc port
rpcport = 8332
# bitcoind zmq raw tx
zmqpubrawtx = "tcp://127.0.0.1:28332"
# bitcoind zmq raw block
zmqpubrawblock = "tcp://127.0.0.1:28333"
}
```
</details>
## Step 6 (Optional): Moving To Testnet
To run your Bitcoin-S Server on testnet, simply change `network = testnet3` and change
your `peers = ["neutrino.testnet3.suredbits.com:18333"] ` in your `.bitcoin-s/bitcoin-s.conf` file.
This will allow you to connect to Suredbits' neutrino-enabled `bitcoind` node.
Keep in mind then when you restart your server, it will begin initial sync which will take
many hours as all block filters for all testnet blocks will be downloaded.
If you wish to speed this process up,
download [this snapshot](https://s3-us-west-2.amazonaws.com/www.suredbits.com/chaindb-testnet-2021-02-03.zip), unzip it and put the file in your `$HOME/.bitcoin-s/testnet3` directory and then from there, run
```bashrc
$ unzip chaindb-testnet-2021-02-03.zip
$ mv chaindb.sqlite ~/.bitcoin-s/testnet/
```
This should take a couple minutes to execute, but once it is done, you will only have a short while left to sync once you start your server.

View file

@ -0,0 +1,114 @@
---
id: version-0.6.0-getting-started
title: Intro and Getting Started
original_id: getting-started
---
## Philosophy
Bitcoin-S is a loosely coupled set of cryptocurrency libraries for the JVM. They work well together, but also can be used
independently. This project's goal is NOT to be a full node implementation, rather a set of scalable cryptocurrency libraries
that use industry standard tools (rather than esoteric tech often found in cryptocurrency) where possible to make the lives of professional
software engineers, security engineers, devops engineers and accountants easier.
We are rapidly iterating on development with the goal of getting to a set of stable APIs that only change when the underlying bitcoin protocol changes.
If you are a professional working a cryptocurrency business and
have feedback on how to make your lives easier, please reach out on [slack](https://join.slack.com/t/suredbits/shared_invite/zt-eavycu0x-WQL7XOakzQo8tAy7jHHZUw),
[gitter](https://gitter.im/bitcoin-s-core/) or [twitter](https://twitter.com/Chris_Stewart_5/)!
## Getting prebuilt artifacts
### Java binaries
<details>
Please download these from our latest [release on github](https://github.com/bitcoin-s/bitcoin-s/releases/tag/v0.5.0)
</details>
### Docker
<details>
We publish docker images to docker hub on every PR merge and tag on github.
You can obtain the images for both the app server and oracle server on these
docker hub repos
[bitcoin-s-server docker hub repo](https://hub.docker.com/r/bitcoinscala/bitcoin-s-server/tags?page=1&ordering=last_updated)
[bitcoin-s-oracle-server docker hub repo](https://hub.docker.com/r/bitcoinscala/bitcoin-s-oracle-server/tags?page=1&ordering=last_updated)
</details>
### Library jars
<details>
Add this to your `build.sbt`:
```scala
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-bitcoind-rpc" % "0.5.0"
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-core" % "0.5.0"
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-chain" % "0.5.0"
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-dlc-oracle" % "0.5.0"
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-eclair-rpc" % "0.5.0"
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-fee-provider" % "0.5.0"
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-key-manager" % "0.5.0"
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-lnd-rpc" % "0.5.0"
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-node" % "0.5.0"
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-oracle-explorer-client" % "0.5.0"
libraryDependencies +="org.bitcoin-s" % "bitcoin-s-secp256k1jni" % "0.5.0"
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-testkit-core" % "0.5.0"
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-testkit" % "0.5.0"
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-wallet" % "0.5.0"
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-zmq" % "0.5.0"
```
### Nightly builds
You can also run on the bleeding edge of Bitcoin-S, by
adding a snapshot build to your `build.sbt`. The most
recent snapshot published is `0.5.0-190-ca06b80e-20210428-0903-SNAPSHOT`.
To fetch snapshots, you will need to add the correct
resolver in your `build.sbt`:
```sbt
resolvers += Resolver.sonatypeRepo("snapshots")
```
The official maven repo for releases is
https://repo1.maven.org/maven2/org/bitcoin-s/
The repo for snapshots, which are published after everytime something is merged to master:
https://oss.sonatype.org/content/repositories/snapshots/org/bitcoin-s/
</details>
## Building JARs yourself
Please see [getting-setup.md](getting-setup.md)
## If you want to setup Bitcoin-S locally for development
Please see [getting-setup.md](getting-setup.md)

View file

@ -0,0 +1,124 @@
---
id: version-0.6.0-key-manager
title: Key Manager
original_id: key-manager
---
### Key Manager
The key manager module's goal is to encapsulate all private key interactions with the [wallet](../wallet/wallet.md) project.
As of this writing, there is only one type of `KeyManager` - [`BIP39KeyManager`](/api/org/bitcoins/keymanager/bip39/BIP39KeyManager).
The [`BIP39KeyManager`](/api/org/bitcoins/keymanager/bip39/BIP39KeyManager) stores a [`MnemonicCode`](/api/org/bitcoins/core/crypto/MnemonicCode) on disk which can be decrypted and used as a hot wallet.
Over the long run, we want to make it so that the wallet project needs to communicate with the key-manager to access private keys.
This means that ALL SIGNING should be done inside of the key-manager, and private keys should not leave the key manager.
This makes it easier to reason about the security characteristics of our private keys, and a way to provide a uniform interface for alternative key storage systems (hsm, cloud based key storage, etc) to be plugged into the bitcoin-s library.
#### Creating a key manager
The first thing you need create a key manager is some entropy.
A popular way for bitcoin wallet's to represent entropy is [BIP39](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki) which you [can use in bitcoin-s](/api/org/bitcoins/core/crypto/BIP39Seed)
You can generate a `MnemonicCode` in bitcoin-s with the following code
```scala
import org.bitcoins.core.crypto._
//get 256 bits of random entropy
val entropy = MnemonicCode.getEntropy256Bits
// entropy: scodec.bits.BitVector = BitVector(256 bits, 0x6d0fa037c7072ead6caae2079da51b1fffa9d32132bb7d2291bd41e76f416226)
val mnemonic = MnemonicCode.fromEntropy(entropy)
// mnemonic: MnemonicCode = Masked(MnemonicCodeImpl)
//you can print that mnemonic seed with this
println(mnemonic.words)
// Vector(home, laptop, assume, mix, indicate, find, razor, fox, always, unfold, egg, divide, whisper, trumpet, luggage, first, large, behind, hungry, long, ivory, pass, mass, scan)
```
Now that we have a `MnemonicCode` that was securely generated, we need to now create `KeyManagerParams` which tells us how to generate
generate specific kinds of addresses for wallets.
`KeyManagerParams` takes 3 parameters:
1. `seedPath` there is where we store the `MnemonicCode` on your file system
2. [`purpose`](/api/org/bitcoins/core/hd/HDPurpose) which represents what type of utxo this `KeyManager` is associated with. The specification for this is in [BIP43](https://github.com/bitcoin/bips/blob/master/bip-0043.mediawiki)
3. [`network`](/api/org/bitcoins/core/config/NetworkParameters) what cryptocurrency network this key manager is associated with
This controls how the root key is defined. The combination of `purpose` and `network` determine how the root `ExtKey` is serialized. For more information on how this works please see [hd-keys](../core/hd-keys.md)
Now we can construct a native segwit key manager for the regtest network!
```scala
//this will create a temp directory with the prefix 'key-manager-example` that will
//have a file in it called "encrypted-bitcoin-s-seed.json"
val seedPath = Files.createTempDirectory("key-manager-example").resolve(WalletStorage.ENCRYPTED_SEED_FILE_NAME)
// seedPath: Path = /tmp/key-manager-example1457373026665946247/encrypted-bitcoin-s-seed.json
//let's create a native segwit key manager
val purpose = HDPurposes.SegWit
// purpose: HDPurpose = m/84'
//let's choose regtest as our network
val network = RegTest
// network: RegTest.type = RegTest
val kmParams = KeyManagerParams(seedPath, purpose, network)
// kmParams: KeyManagerParams = KeyManagerParams(/tmp/key-manager-example1457373026665946247/encrypted-bitcoin-s-seed.json,m/84',RegTest)
val aesPasswordOpt = Some(AesPassword.fromString("password"))
// aesPasswordOpt: Some[AesPassword] = Some(Masked(AesPassword))
val km = BIP39KeyManager.initializeWithMnemonic(aesPasswordOpt, mnemonic, None, kmParams)
// km: Either[KeyManagerInitializeError, BIP39KeyManager] = Right(org.bitcoins.keymanager.bip39.BIP39KeyManager@4e5027f)
val rootXPub = km.right.get.getRootXPub
// rootXPub: ExtPublicKey = vpub5SLqN2bLY4WeZbx48czziTwoGPgTy15QtbiWN74GPP7VnFLciSuw8bikepNv7dvvEaoHbPtBh9r4a13t7Mv81Af3YD4QKh6Whyiow4LDkjd
println(rootXPub)
// vpub5SLqN2bLY4WeZbx48czziTwoGPgTy15QtbiWN74GPP7VnFLciSuw8bikepNv7dvvEaoHbPtBh9r4a13t7Mv81Af3YD4QKh6Whyiow4LDkjd
```
Which should print something that looks like this
`vpub5SLqN2bLY4WeXxMqwJHJFBEwxSscGB2uDUnsTS3edVjZEwTrQDFDNqoR2xLqARQPabGaXsHSTenTRcqm2EnB9MpuC4vSk3LqSgNmGGZtuq7`
which is a native segwit `ExtPubKey` for the regtest network!
You can always change the `network` or `purpose` to support different things. You do _not_ need to initialize the key manager
again after initializing it once. You can use the same `mnemonic` for different networks, which you control `KeyManagerParams`.
```scala
//let's create a nested segwit key manager for mainnet
val mainnetKmParams = KeyManagerParams(seedPath, HDPurposes.SegWit, MainNet)
// mainnetKmParams: KeyManagerParams = KeyManagerParams(/tmp/key-manager-example1457373026665946247/encrypted-bitcoin-s-seed.json,m/84',MainNet)
//we do not need to all `initializeWithMnemonic()` again as we have saved the seed to dis
val mainnetKeyManager = BIP39KeyManager.fromMnemonic(mnemonic, mainnetKmParams, None, Instant.now)
// mainnetKeyManager: BIP39KeyManager = org.bitcoins.keymanager.bip39.BIP39KeyManager@29fa05a
val mainnetXpub = mainnetKeyManager.getRootXPub
// mainnetXpub: ExtPublicKey = zpub6jftahH18ngZxniXU49VYpKoxGGFjV3QZ3oPVgdouQd1zebXj5aBcrMJjeDG7GYbs9GWbJGRXoGG79W8z9aBC7PT1Zr6fLNTnsyPVMtiBuF
println(mainnetXpub)
// zpub6jftahH18ngZxniXU49VYpKoxGGFjV3QZ3oPVgdouQd1zebXj5aBcrMJjeDG7GYbs9GWbJGRXoGG79W8z9aBC7PT1Zr6fLNTnsyPVMtiBuF
```
Which gives us something that looks like this
`zpub6jftahH18ngZw98KGjRo5XcxeKTQ2eztsvskb1dC9XF5TLimQquTs6Ry7nBBA425D9joXmfgJJCexmJ1u2SELJZJfRi95gcnXadLpZzYb5c`
which is a p2sh wrapped segwit `ExtPubKey` for the bitcoin main network!
#### Creating a key manager from existing mnemonic
To create a `KeyManager` from existing mnemonic you need to specify the `seedPath` and then construct the `KeyManagerParams` that you would like.
Finally you call `KeyManager.fromParams()` that reads the mnemonic from disk and create's the key manager

View file

@ -0,0 +1,93 @@
---
id: version-0.6.0-node-api title: Node API
title: node-api title: Node API
original_id: node-api title: Node API
---
### NodeAPI
The NodeApi is how the wallet project retrieves relevant node data like blocks. This allows the wallet for example to
retrieve blocks for finding its relevant transactions.
Since this is an API it can be hooked up to the `node` module of bitcoin-s but it can also be linked to any other
implementation of your choosing. This allows you to use the bitcoin-s wallet in any schema that you want.
The functions that the NodeApi supports are:
```scala
trait NodeApi {
/** Request the underlying node to download the given blocks from its peers and feed the blocks to [[org.bitcoins.node.NodeCallbacks]] */
def downloadBlocks(blockHashes: Vector[DoubleSha256Digest]): Future[Unit]
}
```
## Downloading blocks with bitcoind
As an example, we will show you how to use the `NodeApi` and bitcoind to download blocks for a wallet.
```scala
implicit val system: ActorSystem = ActorSystem(s"node-api-example")
implicit val ec: ExecutionContextExecutor = system.dispatcher
implicit val walletConf: WalletAppConfig =
BitcoinSTestAppConfig.getSpvTestConfig().walletConf
// let's use a helper method to get a v19 bitcoind
// and a ChainApi
val bitcoind = BitcoindV19RpcClient(BitcoindInstance.fromConfigFile())
val chainApi = BitcoinSWalletTest.MockChainQueryApi
val aesPasswordOpt = Some(AesPassword.fromString("password"))
// Create our key manager
val keyManagerE = BIP39KeyManager.initialize(aesPasswordOpt = aesPasswordOpt,
kmParams = walletConf.kmParams,
bip39PasswordOpt = None)
val keyManager = keyManagerE match {
case Right(keyManager) => keyManager
case Left(err) =>
throw new RuntimeException(s"Cannot initialize key manager err=$err")
}
// This function can be used to create a callback for when our node api calls downloadBlocks,
// more specifically it will call the function every time we receive a block, the returned
// NodeCallbacks will contain the necessary items to initialize the callbacks
def createCallback(processBlock: Block => Future[Unit]): NodeCallbacks = {
lazy val onBlock: OnBlockReceived = { block =>
processBlock(block)
}
NodeCallbacks(onBlockReceived = Vector(onBlock))
}
// Here is a super simple example of a callback, this could be replaced with anything, from
// relaying the block on the network, finding relevant wallet transactions, verifying the block,
// or writing it to disk
val exampleProcessBlock = (block: Block) =>
Future.successful(println(s"Received block: ${block.blockHeader.hashBE}"))
val exampleCallback = createCallback(exampleProcessBlock)
// Here is where we are defining our actual node api, Ideally this could be it's own class
// but for the examples sake we will keep it small.
val nodeApi = new NodeApi {
override def broadcastTransactions(transactions: Vector[Transaction]): Future[Unit] = {
FutureUtil.sequentially(transactions)(bitcoind.sendRawTransaction(_)).map(_ => ())
}
override def downloadBlocks(
blockHashes: Vector[DoubleSha256Digest]): Future[Unit] = {
val blockFs = blockHashes.map(hash => bitcoind.getBlockRaw(hash))
Future.sequence(blockFs).map(_ => ())
}
}
// Finally, we can initialize our wallet with our own node api
val wallet =
Wallet(keyManager = keyManager, nodeApi = nodeApi, chainQueryApi = chainApi, feeRateApi = ConstantFeeRateProvider(SatoshisPerVirtualByte.one), creationTime = Instant.now)
// Then to trigger the event we can run
val exampleBlock = DoubleSha256Digest(
"000000000010dc23dc0d5acad64667a7a2b3010b6e02da4868bf392c90b6431d")
wallet.nodeApi.downloadBlocks(Vector(exampleBlock))
```

View file

@ -0,0 +1,143 @@
---
id: version-0.6.0-node
title: Light Client
original_id: node
---
Bitcoin-s has node module that allows you to connect to the p2p network.
### Neutrino Node
Bitcoin-s has experimental support for neutrino which is a new lite client proposal on the bitcoin p2p network. You can
read more about how neutrino works [here](https://suredbits.com/neutrino-what-is-it-and-why-we-need-it/). At this time,
bitcoin-s only supports connecting to one trusted peer.
#### Limitations
Currently, the node does not have an active mempool.
It is only aware of transactions it broadcasts and ones confirmed in blocks.
#### Callbacks
Bitcoin-S support call backs for the following events that happen on the bitcoin p2p network:
1. onTxReceived
2. onBlockReceived
3. onMerkleBlockReceived
4. onCompactFilterReceived
That means every time one of these events happens on the p2p network, we will call your callback
so that you can be notified of the event. These callbacks will be run after the message has been
recieved and will execute sequentially. If any of them fail an error log will be output and the remainder of the callbacks will continue.
Let's make an easy one
#### Example
Here is an example of constructing a neutrino node and registering a callback so you can be notified of an event.
To run the example, we need a bitcoind binary that has neutrino support.
Bitcoin Core only has p2p neutrino support as of version 0.21.0.
You will need to use a version of Bitcoin Core at least as old as 0.21.0.
For your node to be able to service these filters you will need set
`blockfilterindex=1` and `peerblockfilters=1` in your `bitcoin.conf` file.
```scala
implicit val system = ActorSystem(s"node-example")
implicit val ec = system.dispatcher
//we also require a bitcoind instance to connect to
//so let's start one (make sure you ran 'sbt downloadBitcoind')
val instance = BitcoindRpcTestUtil.instance(versionOpt = Some(BitcoindVersion.Experimental))
val p2pPort = instance.p2pPort
val bitcoindF = BitcoindRpcTestUtil.startedBitcoindRpcClient(instance, Vector.newBuilder)
//contains information on how to connect to bitcoin's p2p info
val peerF = bitcoindF.map(b => NodeUnitTest.createPeer(b))
// set a data directory
val prefix = s"node-example-${System.currentTimeMillis()}"
val datadir = Files.createTempDirectory(prefix)
val tmpDir = BitcoinSTestAppConfig.tmpDir()
// set the current network to regtest
val config = ConfigFactory.parseString {
s"""
| bitcoin-s {
| network = regtest
| node {
| mode = neutrino # neutrino, spv
|
| peers = ["127.0.0.1:$p2pPort"] # a list of peer addresses in form "hostname:portnumber"
| # (e.g. "neutrino.testnet3.suredbits.com:18333")
| # Port number is optional, the default value is 8333 for mainnet,
| # 18333 for testnet and 18444 for regtest.
| }
| }
|""".stripMargin
}
implicit val appConfig = BitcoinSAppConfig(datadir, config)
implicit val chainConfig = appConfig.chainConf
implicit val nodeConfig = appConfig.nodeConf
val initNodeF = nodeConfig.start()
//the node requires a chainHandler to store block information
//use a helper method in our testkit to create the chain project
val chainApiF = for {
chainHandler <- ChainUnitTest.createChainHandler()
} yield chainHandler
//yay! All setup done, let's create a node and then start it!
val nodeF = for {
chainApi <- chainApiF
peer <- peerF
} yield {
val dataMessageHandler = DataMessageHandler(chainApi)
NeutrinoNode(nodePeer = peer,
dataMessageHandler = dataMessageHandler,
nodeConfig = nodeConfig,
chainConfig = chainConfig,
actorSystem = system)
}
//let's start it
val startedNodeF = nodeF.flatMap(_.start())
//let's make a simple callback that print's the
//blockhash everytime we receive a block on the network
val blockReceivedFunc: OnBlockReceived = { block: Block =>
Future.successful(
println(s"Received blockhash=${block.blockHeader.hashBE}"))
}
// Create callback
val nodeCallbacks = NodeCallbacks.onBlockReceived(blockReceivedFunc)
// Add call to our node's config
nodeConfig.addCallbacks(nodeCallbacks)
//let's test it out by generating a block with bitcoind!
val genBlockF = for {
bitcoind <- bitcoindF
addr <- bitcoind.getNewAddress
hashes <- bitcoind.generateToAddress(1,addr)
} yield ()
//you should see our callback print a block hash
//when running this code
//cleanup
val cleanupF = for {
_ <- genBlockF
bitcoind <- bitcoindF
node <- startedNodeF
x = NeutrinoNodeConnectedWithBitcoind(node.asInstanceOf[NeutrinoNode],bitcoind)
_ <- NodeUnitTest.destroyNodeConnectedWithBitcoind(x)
} yield ()
Await.result(cleanupF, 60.seconds)
```

View file

@ -0,0 +1,64 @@
---
id: version-0.6.0-oracle-explorer-client
title: Oracle Explorer Client
original_id: oracle-explorer-client
---
[Suredbits offers a tool called an Oracle Explorer](https://oracle.suredbits.com) for oracles to post their
announcements and attestments at a later time.
Bitcoin-s provides an open source oracle explorer client for
interacting with the oracle explorer.
### Environments
There are 2 live environments that can be used with the explorer client
1. ExplorerEnv.Production
2. ExplorerEnv.Test
As the names indicate, one references the [production oracle explorer](https://oracle.suredbits.com)
while the other references the [test environment](https://test.oracle.suredbits.com)
```scala
implicit val system = ActorSystem("explorer-client-actor-system")
//use test environment for this little example
val env = ExplorerEnv.Test
val explorerClient = SbExplorerClient(env)
//list all announcemnts on the explorer
val announcementsF: Future[Vector[SbAnnouncementEvent]] = explorerClient.listAnnouncements()
//example announcement taken from
//https://oracle.suredbits.com/event/e0a5624edbc854120982165b0eef53f0777a49febd79a0c21bf75e5582021e33
val announcementHex = "fdd824c8bf634b2d76f6d8c6499aa977a7b0ae2b84bc206d800f8448e46d63d6ca31778ddb023e39df098c7e109b3d6ee7273d18be62e10f8481dae6531dbe3e0647f6e95d1bcfab252c6dd9edd7aea4c5eeeef138f7ff7346061ea40143a9f5ae80baa9fdd82264000190ef605e3450e16b47745e7a33e26ac9437f6ec2ed660d829a064dceee3699c8605fc700fdd806270005076e67616e6e6f75066d696f63696304647261770a6e6f2d636f6e74657374056f74686572124d696f6369632076204e67616e6e6f752032"
val announcement = OracleAnnouncementV0TLV.fromHex(announcementHex)
//you can query by announcement
val announcementF: Future[SbAnnouncementEvent] = explorerClient.getAnnouncement(announcement)
//or query by hash
val sameAnnouncementF: Future[SbAnnouncementEvent] = explorerClient.getAnnouncement(announcement.sha256)
//you can post an announcement to the oracle explorer
val oracleName = "Chris_Stewart_5"
val description = "2021-03-24-sunny-in-chicago"
val uriOpt = Some("https://twitter.com/Chris_Stewart_5")
val sbAnnouncement = CreateAnnouncementExplorer(announcement, oracleName, description, uriOpt)
val createdF = explorerClient.createAnnouncement(sbAnnouncement)
//and then you can follow up and post the attestations
val attestationsHex =
"fdd868821b323032312d30332d32342d73756e6e792d696e2d6368696361676f1d5dcdba2e64cb116cc0c375a0856298f0058b778f46bfe625ac6576204889e40001efdf735567ae0a00a515e313d20029de5d7525da7b8367bc843d28b672d4db4db5de4dbff689f3b742be634a9c92c615dbcf2eadbdd470f514b1ac250a30db6d03594553"
val attestations = OracleAttestmentV0TLV.fromHex(attestationsHex)
val announcementHash = announcement.sha256
val sbAttestations = CreateAttestations(announcementHash, attestations)
val createdAttestationsF = explorerClient.createAttestations(sbAttestations)
```

View file

@ -0,0 +1,172 @@
---
id: version-0.6.0-build-oracle-server
title: Building the Oracle Server
original_id: build-oracle-server
---
The Oracle Server is a DLC Oracle with functionality for creating events and attesting to them.
You can interact with the oracle server with `bitcoin-s-cli` or `curl`
The following a guide is for how to build the oracle server.
If you are looking for the documentation on how to use the oracle server,
checkout [this page](oracle-server.md).
## Step 1: Java and Scala
To get started you will need Java, Scala, and some other nice tools installed, luckily the Scala team has an easy setup process!
Simply follow the instructions in [this short blog](https://www.scala-lang.org/2020/06/29/one-click-install.html) to get started.
## Step 2: Bitcoin-S Repository
Now, it is time to clone the [Bitcoin-S repository](https://github.com/bitcoin-s/bitcoin-s/) by running
```bashrc
git clone --depth 100 --recursive git@github.com:bitcoin-s/bitcoin-s.git
```
or alternatively, if you do not have ssh setup with github, you can run
```bashrc
git clone --depth 100 --recursive https://github.com/bitcoin-s/bitcoin-s.git
```
Next, you will want to execute the commands
```bashrc
cd bitcoin-s
git submodule update
```
to download the secp256k1 submodule, this is so cryptographic functions like signing will be faster.
## Step 3: Building the Oracle Server
### Java Binary
You can build the oracle server with the [sbt native packager](https://github.com/sbt/sbt-native-packager).
The native packager offers [numerous ways to package the project](https://github.com/sbt/sbt-native-packager#examples).
In this example we are going to use `universal:stage` which will produce bash scripts we can easily execute. You can stage the server with the following command.
```bash
sbt oracleServer/universal:stage
```
This will produce a script to execute bitcoin-s which you can start with
```bash
./app/oracle-server/target/universal/stage/bin/bitcoin-s-oracle-server
```
Alternatively you can run the server by just using:
```bash
sbt oracleServer/run
```
### Docker
#### Using an existing docker image
We publish docker images on every PR that is merged to bitcoin-s.
You can find the docker repo for the oracle server [here](https://hub.docker.com/r/bitcoinscala/bitcoin-s-oracle-server/tags?page=1&ordering=last_updated)
#### Building our own docker image
You can build a docker image with the following commands
```
sbt "oracleServer/docker:stage"
```
This will build a `Dockerfile` that is located in `app/oracle-server/target/docker/stage`
You can publish to your local docker repository by using `docker:publishLocal` instead of `docker:stage`
You can now build the docker image with
```
docker build app/oracle-server/target/docker/stage/ -t bitcoin-s-oracle-server:latest
```
Finally, let's run the image! It's important that you correctly configure port forwarding with the docker container so
you can interact with the running container with `bitcoin-s-cli` or `curl`. By default, our oracle
server listens for requests on port `9998`.
This means we need to forward requests on the host machine to the docker container correctly.
This can be done with the following command
```
docker run -d -p 9998:9998 bitcoin-s-oracle-server:latest
```
Now you can send requests with `bitcoin-s-cli` or `curl`.
Here is an example with `bitcoin-s-cli`
```
./bitcoin-s-cli getpublickey
c9c9fe2772330b0d61a2efbfacabf5cab1137710a69f0e12f1eb3dbb74f7ea54
```
For more information on build configuration options with `sbt` please see the [sbt native packager docs](https://sbt-native-packager.readthedocs.io/en/latest/formats/docker.html#tasks)
## Step 4: Configuration
### Java binary configuration
If you would like to pass in a custom datadir for your server, you can do
```bash
./app/oracle-server/target/universal/stage/bin/bitcoin-s-oracle-server --datadir /path/to/datadir/
```
To use a config file that is not the `bitcoin-s.conf` file in your datadir, you can do
```bash
./app/oracle-server/target/universal/stage/bin/bitcoin-s-oracle-server --conf /path/to/file.conf
```
You can also pass in a custom `rpcport` to bind to
```bash
./app/oracle-server/target/universal/stage/bin/bitcoin-s-oracle-server --rpcport 12345
```
For more information on configuring the server please see our [configuration](../config/configuration.md) document.
For more information on how to use our built in `cli` to interact with the server please see the [cli docs](../applications/cli.md).
### Docker configuration
In this example, we are using the latest docker image published to our [docker hub](https://hub.docker.com/repository/docker/bitcoinscala/bitcoin-s-oracle-server/tags?page=1&ordering=last_updated)
which is referenced by `bitcoinscala/bitcoin-s-oracle-server:latest`
You can use bitcoin-s with docker volumes. You can also pass in a custom configuration at container runtime.
#### Using a docker volume
```basrc
docker volume create bitcoin-s
docker run -p 9998:9998 \
--mount source=bitcoin-s,target=/home/bitcoin-s/ bitcoinscala/bitcoin-s-oracle-server:latest
```
Now you can re-use this volume across container runs. It will keep the same oracle database
and seeds directory located at `/home/bitcoin-s/.bitcoin-s/seeds` in the volume.
#### Using a custom bitcoin-s configuration with docker
You can also specify a custom bitcoin-s configuration at container runtime.
You can mount the configuration file on the docker container and that
configuration will be used in the docker container runtime rather than
the default one we provide [here](https://github.com/bitcoin-s/bitcoin-s/blob/master/app/oracle-server/src/universal/docker-application.conf)
You can do this with the following command
```bashrc
docker run -p 9998:9998 \
--mount type=bind,source=/my/new/config/,target=/home/bitcoin-s/.bitcoin-s/ \
bitcoinscala/bitcoin-s-oracle-server:latest --conf /home/bitcoin-s/.bitcoin-s/bitcoin-s.conf
```
Note: If you adjust the `bitcoin-s.oracle.rpcport` setting you will need to adjust
the `-p 9998:9998` port mapping on the docker container to adjust for this.

View file

@ -0,0 +1,113 @@
---
id: version-0.6.0-oracle-election-example
title: Election Example
original_id: oracle-election-example
---
## Requirements for example
You need to have a fully built oracle server. You can follow [this guide](build-oracle-server.md) to do this.
You will also need a the `bitcoin-s-cli` command line tool to interact with the server.
You can find how to build this [here](../applications/cli.md)
After building the oracle server, you will need to start it with
```
./app/oracle-server/target/universal/stage/bin/bitcoin-s-oracle-server
```
## US 2020 Election
In 2020, the United States held a presidential election.
People want to do a DLC based on the outcome, so you decide to be
their oracle and attest to the winner of the election.
1. Setting up an oracle that can attest to the outcome of the election
2. Complete the event by attesting to the winner of the election
### Setting up the election bet
The election bet can be represented by an enumerated event. What we mean by this is the outcomes
for this event can be simply enumerated in a list. There were three possible outcomes for the election
1. "Republican_win"
2. "Democrat_win"
3. "other"
The next step is to create an oracle announcement that can be shared with others. This announcement
contains cryptographic information that is used by people that want to enter into DLCs to setup
their bitcoin transactions.
To do this, we need to use the `createenumevent` rpc. This RPC takes 3 parameters
1. The label for the event
we will use `2020-us-election`
2. the maturation time for the event in ISO 8061 format
For our example we will use `"2021-01-20T00:00:00Z"`
3. The outcomes, which we listed above "Republican_win,Democrat_win,other"
With all of this information, we can create the event with `bitcoin-s-cli`!
```
./bitcoin-s-cli createenumevent 2020-us-election "2021-01-20T00:00:00Z" "Republican_win,Democrat_win,other"
fdd824c3988fabec9820690f366271c9ceac00fbec1412075f9b319bb0db1f86460519dd9c61478949f2c00c35aeb8e53a1507616072cb802891e2c189a9fa65a0493de5d3b04a6d7b90c9c43c09ebe5250d583e1c3fc423219b26f6a02ec394a130000afdd8225f0001ae3e30df5a203ad10ee89a909df0c8ccea4836e94e0a5d34c3cdab758fcaee1460189600fdd8062400030e52657075626c6963616e5f77696e0c44656d6f637261745f77696e056f7468657210323032302d75732d656c656374696f6e
```
Yay! The hex string returned is an oracle announcement.
You can submit this on a tool like the [Suredbits oracle explorer](https://oracle.suredbits.com)
so others can find your oracle.
If you are building infrastructure to automatically sign events, it is important to store two things
1. The oracle announcement above (`fdd824c...`)
2. The timestamp that the event matures at (`"2021-01-20T00:00:00Z"`)
Now you can schedule jobs to sign the event when the maturation time passes.
### Signing the outcome for the election bet
In the real world, you would want to wait for the maturation time to pass for your event.
For the purposes of the demo, we can skip this wait. The winner of the US election was Joe Biden.
Let's sign the event.
```
./bitcoin-s-cli signevent 2020-us-election Democrat_win
fdd8688010323032302d75732d656c656374696f6ed3b04a6d7b90c9c43c09ebe5250d583e1c3fc423219b26f6a02ec394a130000a0001ae3e30df5a203ad10ee89a909df0c8ccea4836e94e0a5d34c3cdab758fcaee1447a59ba58797e55b967aa79c89ffec67023578009c4dc1e3dee2fd75277993590c44656d6f637261745f77696e
```
Yay! Now bitcoin-s gives us an attestation that is represented by the hex string `fdd868...`
If you submitted your event to the [suredbits oracle explorer](https://oracle.suredbits.com) above
you will also want to submit the attestation for your event so others can find it and settle their DLCs.
If you use the `getevent` rpc along the oracle announcement, you can see the event is now completed!
```
./bitcoin-s-cli getevent 2020-us-election
{
"nonces": [
"ae3e30df5a203ad10ee89a909df0c8ccea4836e94e0a5d34c3cdab758fcaee14"
],
"eventName": "2020-us-election",
"signingVersion": "DLCOracleV0SigningVersion",
"maturationTime": "2021-01-20T00:00:00Z",
"announcementSignature": "988fabec9820690f366271c9ceac00fbec1412075f9b319bb0db1f86460519dd9c61478949f2c00c35aeb8e53a1507616072cb802891e2c189a9fa65a0493de5",
"eventDescriptorTLV": "fdd8062400030e52657075626c6963616e5f77696e0c44656d6f637261745f77696e056f74686572",
"eventTLV": "fdd8225f0001ae3e30df5a203ad10ee89a909df0c8ccea4836e94e0a5d34c3cdab758fcaee1460189600fdd8062400030e52657075626c6963616e5f77696e0c44656d6f637261745f77696e056f7468657210323032302d75732d656c656374696f6e",
"announcementTLV": "fdd824c3988fabec9820690f366271c9ceac00fbec1412075f9b319bb0db1f86460519dd9c61478949f2c00c35aeb8e53a1507616072cb802891e2c189a9fa65a0493de5d3b04a6d7b90c9c43c09ebe5250d583e1c3fc423219b26f6a02ec394a130000afdd8225f0001ae3e30df5a203ad10ee89a909df0c8ccea4836e94e0a5d34c3cdab758fcaee1460189600fdd8062400030e52657075626c6963616e5f77696e0c44656d6f637261745f77696e056f7468657210323032302d75732d656c656374696f6e",
"attestations": [
"47a59ba58797e55b967aa79c89ffec67023578009c4dc1e3dee2fd7527799359"
],
"signatures": [
"ae3e30df5a203ad10ee89a909df0c8ccea4836e94e0a5d34c3cdab758fcaee1447a59ba58797e55b967aa79c89ffec67023578009c4dc1e3dee2fd7527799359"
],
"outcomes": [
"Republican_win",
"Democrat_win",
"other"
],
"signedOutcome": "Democrat_win"
}
```

View file

@ -0,0 +1,264 @@
---
id: version-0.6.0-oracle-price-example
title: Price Example
original_id: oracle-price-example
---
## Requirements for example
You need to have a fully built oracle server. You can follow [this guide](build-oracle-server.md) to do this.
You will also need a the `bitcoin-s-cli` command line tool to interact with the server.
You can find how to build this [here](../applications/cli.md)
After building the oracle server, you will need to start it with
```
./app/oracle-server/target/universal/stage/bin/bitcoin-s-oracle-server
```
## Signing BTC/USD price
BTC/USD markets trade 24/7/365 around the world. Exchanges publish the market price
for the trading pair everytime a trade is matched in their matching engine.
In this example, we will
1. Explain the `createnumericevent` and `createdigitdecompevent` rpc
2. Set up an oracle that can sign the BTC/USD price at a maturation time
3. Completing the event by signing the observed market price
For this example our maturation time will be
> 2021-02-04 00:00:00 UTC
## 2 RPC Options
When signing numbers, we need to use a [digit decomposition event](https://github.com/discreetlogcontracts/dlcspecs/blob/master/Oracle.md#version-0-digit_decomposition_event_descriptor) that can sign
each digit of a number.
There are 2 different RPC options for making a digit decomposition event: `createnumericevent` and `createdigitdecompevent`.
`createnumericevent` is meant to be user-friendly with an easy to use api,
where `createdigitdecompevent` is meant to give to be a more advanced api that gives more expressivity.
Here we will give examples of each that result in the same event/announcement.
### createnumericevent rpc
The `createnumericevent` takes 6 arguments
1. the name for the event (`bitcoin-s-price-example`)
2. maturation time in ISO 8601 format (`"2021-02-04T00:00:00Z"`)
3. minimum value (`0`)
4. maximum value (`131071`)
5. units (`BTC/USD`)
6. precision (`base^precision * base^numdigits` to get the actual outcome. This is useful for very small or large values)
### createdigitdecompevent rpc
It takes 6 arguments
1. the name for the event (`bitcoin-s-price-example`)
2. maturation time in seconds since the epoch (`1612396800`)
3. base (`2`)
4. number of digits (`17`)
5. units (`BTC/USD`)
6. precision (`base^precision * base^numdigits` to get the actual outcome. This is useful for very small or large values)
### Understanding createdigitdecompevent parameters
Most of these fields are self-explanatory, but one that might confuse new users
is the usage of `base` and `number of digits`. You need to set these two parameters
in such a way that your domain is contained within `base^numdigits`.
Our domain is BTC/USD price at `2020-02-04 00:00:00 UTC`. That means we need to
pick numDigits such that `2^numDigits` can contain the BTC/USD price at `2020-02-04 00:00:00 UTC`
For this example, we will pick `numDigits=17`. This means we can sign a BTC/USD price
in between `[0,2^17-1]` or `[$0-$131,071]`. If the BTC/USD price is outside of your
predetermined interval, you need to sign the min (`0`) or the max (`$131,071`).
## Setting up the BTC/USD oracle
Given the parameters we specified above, we are ready to create our digit decompisition event!
```
./bitcoin-s-cli createdigitdecompevent "bitcoin-s-price-example" 1612396800 2 17 "BTC/USD" 0
fdd824fd02b9659e890eef1b223ba45c9993f88c7997859302fd5510ac23f4cac0d4ee8232a77ecbdf50c07f093794370e6a506a836f6b0fb54b45f1fb662e1307166d2e57030574f77305826939fa9124d19bfa8a8b2f00f000586b8c58c79ee8b77969a949fdd822fd025300114762c188048a953803f0edeeeb68c69e6cdc1d371ba8d517003accfe05afc4d6588c3ea326512bc66c26a841adffa68330b8c723da442792e731fb19fda94274a7766bb48e520f118c100bbe62dc3806a8d05a63d92e23683a04b0b8c24148cd166585a6b33b995b3d6c083523a8435b156c05100d88f449f4754310d5574d5e88aad09af1b8ba942cfd305e728044ec6360d847254453ec05b1b518a36660e2238360e02f3a004663a7f3a3534973d8b66a2646c1386779aa820672b6361b88a8696395c0add87840b460dfd8a8c0d520017efc6bf58267d4c9d2a225c5d0e5719068a7dda5d630d7432239b6c9d921d5f3842b584503460ca52612ac2e64337d299513690372e8f4770eb8a28080e8d7c29920ca32af470d65d6f916ee81e3ac15ce02684ba6d2522a9ffea1de7e202b4b699ef7ec4f089dda07f3de5b7d1f853b2c56471999be4efca82674a651c80f047ba3a2b9e6f9999f0cd4062c533d1ae29cab2a5e33cbe98728b7b4271c67f7c5cd6e12e39128b9971e08496cbd84cfa99c77c88867d33e73acef37022ba4422a5221776991d45416db71fb54bc6c104f6a8e50e8905161709215104a7e7b97e866f32cf43233ffd615cab66699832ec607cf59c85a7f56fa957aa5f5d7ec9f46d84d5d4b777122d41ad76c6f4968aeedca243f2030d4f502e58f4181130e9afb75309ac21637bcfd0717528bfb82ffe1b6c9fadee6ba70357210990539184bcc913a0ec65837a736733a2fb6172d601b3900fdd80a11000200074254432f55534400000000001117626974636f696e2d732d70726963652d6578616d706c65
```
Yay! The hex string returned is an oracle announcement.
You can submit this on a tool like the [suredbits oracle explorer](https://oracle.suredbits.com)
so others can find your oracle.
If you are building infrastructure to automatically sign events, it is important to store two things
1. The oracle announcement above (`fdd824fd02b9659...`)
2. The timestamp that the event matures at (`1612396800`)
Now you can schedule jobs to sign the event when the maturation time passes.
## Signing the BTC/USD price for the oracle
At the maturation time (`2020-02-04 00:00:00 UTC`) you need to check the BTC/USD price.
For the purposes of this example, we are going to assume the BTC/USD price was `$42,069` BTC/USD.
Now let's sign the event. To do this, we use the `signdigits` rpc. It takes two parameters
1. The oracle announcement (`fdd824fd02b9659...`)
2. The outcome (`$42,069`)
```
./bitcoin-s-cli signdigits "bitcoin-s-price-example" 42069
fdd868fd049c17626974636f696e2d732d70726963652d6578616d706c650574f77305826939fa9124d19bfa8a8b2f00f000586b8c58c79ee8b77969a94900114762c188048a953803f0edeeeb68c69e6cdc1d371ba8d517003accfe05afc4d6c787b447aef7494301823b1570453b6a84bdadb4822cc215b057b3ef4969688f588c3ea326512bc66c26a841adffa68330b8c723da442792e731fb19fda94274f8ce423054eaccb0e3eadd81395efd9a1fe6c8c70cdb6ce5ade2515593c0306ba7766bb48e520f118c100bbe62dc3806a8d05a63d92e23683a04b0b8c24148cd2d8d787a6280d88188b85f8c1a8c2b4dc859b408e391037b27c30dd3c2fa1cbf166585a6b33b995b3d6c083523a8435b156c05100d88f449f4754310d5574d5ea403f06f5785e54e43ed84c4ecd73dcda752e0864d61bc406bd4f84495a1ce5988aad09af1b8ba942cfd305e728044ec6360d847254453ec05b1b518a36660e21e1114f3c577d8b7b954505038a9a7372b1546e04ea5c6315d3729c389faa022238360e02f3a004663a7f3a3534973d8b66a2646c1386779aa820672b6361b88b3b3e6babdf51b87edab5c22a7a017085488cb3abdabe09e780db34a8860f88ea8696395c0add87840b460dfd8a8c0d520017efc6bf58267d4c9d2a225c5d0e5ddab4419d6859eae05150160029de1065e895e434bddc6a4e0f547572dc661bb719068a7dda5d630d7432239b6c9d921d5f3842b584503460ca52612ac2e6433a694633a7914f3f155148cceb901ffb81abcb6bc244ce6d28ad6c61c8c9fdd2f7d299513690372e8f4770eb8a28080e8d7c29920ca32af470d65d6f916ee81e3bac60fa715ca02a3a801c71afd4d947c7436e05c558194ad7fdd1aeedb3e83eeac15ce02684ba6d2522a9ffea1de7e202b4b699ef7ec4f089dda07f3de5b7d1fb515a7eedb33e4d1ad111c5e2c47b36eae320d5be505293026d4c237e7eac3bf853b2c56471999be4efca82674a651c80f047ba3a2b9e6f9999f0cd4062c533dccce9f582e55e7125bb202820fa4e4ec5ffc2da14c03c3b60d53a0a990d9766b1ae29cab2a5e33cbe98728b7b4271c67f7c5cd6e12e39128b9971e08496cbd847530ec3fee05a8eeb4919569fa966b7da6960c9f2abf8d884f61c1f5f0895871cfa99c77c88867d33e73acef37022ba4422a5221776991d45416db71fb54bc6cc339855ca09a01b9a52ef520f7f328ead330803ab37155d619d8499190ed435c104f6a8e50e8905161709215104a7e7b97e866f32cf43233ffd615cab666998360d49fdec41fd2165c1c9fa8e880203034fab805b141cf22817d23ace14a0bd22ec607cf59c85a7f56fa957aa5f5d7ec9f46d84d5d4b777122d41ad76c6f49689493aba5ad6c0cb16c7d862b7cd9747418e0ffec7d3a4ec1ce394d23114bf765aeedca243f2030d4f502e58f4181130e9afb75309ac21637bcfd0717528bfb828dc8a3114da0cbb7578f57635fcd9daaf1e47cf9214b6605b9a018c1337790ccffe1b6c9fadee6ba70357210990539184bcc913a0ec65837a736733a2fb6172d581ddc2f08d27b7fd895c7fb305dddba5f3bdb923b83c3fdc3560bad5b6044a801300131013001310130013001310130013001300131013001310130013101300131
```
Yay! Now bitcoin-s gives us an attestation that is represented by the hex string `fdd868fd049...`
If you submitted your event to the [suredbits oracle explorer](https://oracle.suredbits.com) above
you will also want to submit the attestation for your event so others can find it and settle their DLCs.
If you use the `getevent` rpc along the oracle announcement, you can see the event is now completed!
```
./bitcoin-s-cli getevent "bitcoin-s-price-example"
{
"nonces": [
"4762c188048a953803f0edeeeb68c69e6cdc1d371ba8d517003accfe05afc4d6",
"588c3ea326512bc66c26a841adffa68330b8c723da442792e731fb19fda94274",
"a7766bb48e520f118c100bbe62dc3806a8d05a63d92e23683a04b0b8c24148cd",
"166585a6b33b995b3d6c083523a8435b156c05100d88f449f4754310d5574d5e",
"88aad09af1b8ba942cfd305e728044ec6360d847254453ec05b1b518a36660e2",
"238360e02f3a004663a7f3a3534973d8b66a2646c1386779aa820672b6361b88",
"a8696395c0add87840b460dfd8a8c0d520017efc6bf58267d4c9d2a225c5d0e5",
"719068a7dda5d630d7432239b6c9d921d5f3842b584503460ca52612ac2e6433",
"7d299513690372e8f4770eb8a28080e8d7c29920ca32af470d65d6f916ee81e3",
"ac15ce02684ba6d2522a9ffea1de7e202b4b699ef7ec4f089dda07f3de5b7d1f",
"853b2c56471999be4efca82674a651c80f047ba3a2b9e6f9999f0cd4062c533d",
"1ae29cab2a5e33cbe98728b7b4271c67f7c5cd6e12e39128b9971e08496cbd84",
"cfa99c77c88867d33e73acef37022ba4422a5221776991d45416db71fb54bc6c",
"104f6a8e50e8905161709215104a7e7b97e866f32cf43233ffd615cab6669983",
"2ec607cf59c85a7f56fa957aa5f5d7ec9f46d84d5d4b777122d41ad76c6f4968",
"aeedca243f2030d4f502e58f4181130e9afb75309ac21637bcfd0717528bfb82",
"ffe1b6c9fadee6ba70357210990539184bcc913a0ec65837a736733a2fb6172d"
],
"eventName": "bitcoin-s-price-example",
"signingVersion": "DLCOracleV0SigningVersion",
"maturationTime": "2021-02-04T00:00:00Z",
"announcementSignature": "659e890eef1b223ba45c9993f88c7997859302fd5510ac23f4cac0d4ee8232a77ecbdf50c07f093794370e6a506a836f6b0fb54b45f1fb662e1307166d2e5703",
"eventDescriptorTLV": "fdd80a11000200074254432f555344000000000011",
"eventTLV": "fdd822fd025300114762c188048a953803f0edeeeb68c69e6cdc1d371ba8d517003accfe05afc4d6588c3ea326512bc66c26a841adffa68330b8c723da442792e731fb19fda94274a7766bb48e520f118c100bbe62dc3806a8d05a63d92e23683a04b0b8c24148cd166585a6b33b995b3d6c083523a8435b156c05100d88f449f4754310d5574d5e88aad09af1b8ba942cfd305e728044ec6360d847254453ec05b1b518a36660e2238360e02f3a004663a7f3a3534973d8b66a2646c1386779aa820672b6361b88a8696395c0add87840b460dfd8a8c0d520017efc6bf58267d4c9d2a225c5d0e5719068a7dda5d630d7432239b6c9d921d5f3842b584503460ca52612ac2e64337d299513690372e8f4770eb8a28080e8d7c29920ca32af470d65d6f916ee81e3ac15ce02684ba6d2522a9ffea1de7e202b4b699ef7ec4f089dda07f3de5b7d1f853b2c56471999be4efca82674a651c80f047ba3a2b9e6f9999f0cd4062c533d1ae29cab2a5e33cbe98728b7b4271c67f7c5cd6e12e39128b9971e08496cbd84cfa99c77c88867d33e73acef37022ba4422a5221776991d45416db71fb54bc6c104f6a8e50e8905161709215104a7e7b97e866f32cf43233ffd615cab66699832ec607cf59c85a7f56fa957aa5f5d7ec9f46d84d5d4b777122d41ad76c6f4968aeedca243f2030d4f502e58f4181130e9afb75309ac21637bcfd0717528bfb82ffe1b6c9fadee6ba70357210990539184bcc913a0ec65837a736733a2fb6172d601b3900fdd80a11000200074254432f55534400000000001117626974636f696e2d732d70726963652d6578616d706c65",
"announcementTLV": "fdd824fd02b9659e890eef1b223ba45c9993f88c7997859302fd5510ac23f4cac0d4ee8232a77ecbdf50c07f093794370e6a506a836f6b0fb54b45f1fb662e1307166d2e57030574f77305826939fa9124d19bfa8a8b2f00f000586b8c58c79ee8b77969a949fdd822fd025300114762c188048a953803f0edeeeb68c69e6cdc1d371ba8d517003accfe05afc4d6588c3ea326512bc66c26a841adffa68330b8c723da442792e731fb19fda94274a7766bb48e520f118c100bbe62dc3806a8d05a63d92e23683a04b0b8c24148cd166585a6b33b995b3d6c083523a8435b156c05100d88f449f4754310d5574d5e88aad09af1b8ba942cfd305e728044ec6360d847254453ec05b1b518a36660e2238360e02f3a004663a7f3a3534973d8b66a2646c1386779aa820672b6361b88a8696395c0add87840b460dfd8a8c0d520017efc6bf58267d4c9d2a225c5d0e5719068a7dda5d630d7432239b6c9d921d5f3842b584503460ca52612ac2e64337d299513690372e8f4770eb8a28080e8d7c29920ca32af470d65d6f916ee81e3ac15ce02684ba6d2522a9ffea1de7e202b4b699ef7ec4f089dda07f3de5b7d1f853b2c56471999be4efca82674a651c80f047ba3a2b9e6f9999f0cd4062c533d1ae29cab2a5e33cbe98728b7b4271c67f7c5cd6e12e39128b9971e08496cbd84cfa99c77c88867d33e73acef37022ba4422a5221776991d45416db71fb54bc6c104f6a8e50e8905161709215104a7e7b97e866f32cf43233ffd615cab66699832ec607cf59c85a7f56fa957aa5f5d7ec9f46d84d5d4b777122d41ad76c6f4968aeedca243f2030d4f502e58f4181130e9afb75309ac21637bcfd0717528bfb82ffe1b6c9fadee6ba70357210990539184bcc913a0ec65837a736733a2fb6172d601b3900fdd80a11000200074254432f55534400000000001117626974636f696e2d732d70726963652d6578616d706c65",
"attestations": [
"c787b447aef7494301823b1570453b6a84bdadb4822cc215b057b3ef4969688f",
"f8ce423054eaccb0e3eadd81395efd9a1fe6c8c70cdb6ce5ade2515593c0306b",
"2d8d787a6280d88188b85f8c1a8c2b4dc859b408e391037b27c30dd3c2fa1cbf",
"a403f06f5785e54e43ed84c4ecd73dcda752e0864d61bc406bd4f84495a1ce59",
"1e1114f3c577d8b7b954505038a9a7372b1546e04ea5c6315d3729c389faa022",
"b3b3e6babdf51b87edab5c22a7a017085488cb3abdabe09e780db34a8860f88e",
"ddab4419d6859eae05150160029de1065e895e434bddc6a4e0f547572dc661bb",
"a694633a7914f3f155148cceb901ffb81abcb6bc244ce6d28ad6c61c8c9fdd2f",
"bac60fa715ca02a3a801c71afd4d947c7436e05c558194ad7fdd1aeedb3e83ee",
"b515a7eedb33e4d1ad111c5e2c47b36eae320d5be505293026d4c237e7eac3bf",
"ccce9f582e55e7125bb202820fa4e4ec5ffc2da14c03c3b60d53a0a990d9766b",
"7530ec3fee05a8eeb4919569fa966b7da6960c9f2abf8d884f61c1f5f0895871",
"c339855ca09a01b9a52ef520f7f328ead330803ab37155d619d8499190ed435c",
"60d49fdec41fd2165c1c9fa8e880203034fab805b141cf22817d23ace14a0bd2",
"9493aba5ad6c0cb16c7d862b7cd9747418e0ffec7d3a4ec1ce394d23114bf765",
"8dc8a3114da0cbb7578f57635fcd9daaf1e47cf9214b6605b9a018c1337790cc",
"581ddc2f08d27b7fd895c7fb305dddba5f3bdb923b83c3fdc3560bad5b6044a8"
],
"signatures": [
"4762c188048a953803f0edeeeb68c69e6cdc1d371ba8d517003accfe05afc4d6c787b447aef7494301823b1570453b6a84bdadb4822cc215b057b3ef4969688f",
"588c3ea326512bc66c26a841adffa68330b8c723da442792e731fb19fda94274f8ce423054eaccb0e3eadd81395efd9a1fe6c8c70cdb6ce5ade2515593c0306b",
"a7766bb48e520f118c100bbe62dc3806a8d05a63d92e23683a04b0b8c24148cd2d8d787a6280d88188b85f8c1a8c2b4dc859b408e391037b27c30dd3c2fa1cbf",
"166585a6b33b995b3d6c083523a8435b156c05100d88f449f4754310d5574d5ea403f06f5785e54e43ed84c4ecd73dcda752e0864d61bc406bd4f84495a1ce59",
"88aad09af1b8ba942cfd305e728044ec6360d847254453ec05b1b518a36660e21e1114f3c577d8b7b954505038a9a7372b1546e04ea5c6315d3729c389faa022",
"238360e02f3a004663a7f3a3534973d8b66a2646c1386779aa820672b6361b88b3b3e6babdf51b87edab5c22a7a017085488cb3abdabe09e780db34a8860f88e",
"a8696395c0add87840b460dfd8a8c0d520017efc6bf58267d4c9d2a225c5d0e5ddab4419d6859eae05150160029de1065e895e434bddc6a4e0f547572dc661bb",
"719068a7dda5d630d7432239b6c9d921d5f3842b584503460ca52612ac2e6433a694633a7914f3f155148cceb901ffb81abcb6bc244ce6d28ad6c61c8c9fdd2f",
"7d299513690372e8f4770eb8a28080e8d7c29920ca32af470d65d6f916ee81e3bac60fa715ca02a3a801c71afd4d947c7436e05c558194ad7fdd1aeedb3e83ee",
"ac15ce02684ba6d2522a9ffea1de7e202b4b699ef7ec4f089dda07f3de5b7d1fb515a7eedb33e4d1ad111c5e2c47b36eae320d5be505293026d4c237e7eac3bf",
"853b2c56471999be4efca82674a651c80f047ba3a2b9e6f9999f0cd4062c533dccce9f582e55e7125bb202820fa4e4ec5ffc2da14c03c3b60d53a0a990d9766b",
"1ae29cab2a5e33cbe98728b7b4271c67f7c5cd6e12e39128b9971e08496cbd847530ec3fee05a8eeb4919569fa966b7da6960c9f2abf8d884f61c1f5f0895871",
"cfa99c77c88867d33e73acef37022ba4422a5221776991d45416db71fb54bc6cc339855ca09a01b9a52ef520f7f328ead330803ab37155d619d8499190ed435c",
"104f6a8e50e8905161709215104a7e7b97e866f32cf43233ffd615cab666998360d49fdec41fd2165c1c9fa8e880203034fab805b141cf22817d23ace14a0bd2",
"2ec607cf59c85a7f56fa957aa5f5d7ec9f46d84d5d4b777122d41ad76c6f49689493aba5ad6c0cb16c7d862b7cd9747418e0ffec7d3a4ec1ce394d23114bf765",
"aeedca243f2030d4f502e58f4181130e9afb75309ac21637bcfd0717528bfb828dc8a3114da0cbb7578f57635fcd9daaf1e47cf9214b6605b9a018c1337790cc",
"ffe1b6c9fadee6ba70357210990539184bcc913a0ec65837a736733a2fb6172d581ddc2f08d27b7fd895c7fb305dddba5f3bdb923b83c3fdc3560bad5b6044a8"
],
"outcomes": [
[
"0",
"1"
],
[
"0",
"1"
],
[
"0",
"1"
],
[
"0",
"1"
],
[
"0",
"1"
],
[
"0",
"1"
],
[
"0",
"1"
],
[
"0",
"1"
],
[
"0",
"1"
],
[
"0",
"1"
],
[
"0",
"1"
],
[
"0",
"1"
],
[
"0",
"1"
],
[
"0",
"1"
],
[
"0",
"1"
],
[
"0",
"1"
],
[
"0",
"1"
],
"signedOutcome": 42069
]
}
```

View file

@ -0,0 +1,187 @@
---
id: version-0.6.0-oracle-server
title: Oracle Server
original_id: oracle-server
---
The Oracle Server is a DLC Oracle with functionality for creating events and attesting to them.
You can interact with the oracle server with `bitcoin-s-cli` or `curl`
The following a guide is for interacting with the oracle
If you are looking for the documentation on how to build the oracle server,
checkout [this page](build-oracle-server.md).
## Server Endpoints
- `getpublickey` - Get oracle's public key
- `getstakingaddress` - Get oracle's staking address
- `listevents` - Lists all event names
- `createenumevent` `label` `maturationtime` `outcomes` - Registers an oracle enum event
- `label` - Label for this event
- `maturationtime` - The earliest expected time an outcome will be signed, given in ISO 8601 format
- `outcomes` - Possible outcomes for this event
- `createnumericevent` `name` `maturationtime` `minvalue` `maxvalue` `unit` `precision` - Registers an oracle event that uses digit decomposition when signing the number
- `name`- Name for this event
- `maturationtime` - The earliest expected time an outcome will be signed, given in ISO 8601 format
- `minvalue` - Minimum value of this event
- `maxvalue` - Maximum value of this event
- `unit` - The unit denomination of the outcome value
- `precision` - The precision of the outcome representing the base exponent by which to multiply the number represented by the composition of the digits to obtain the actual outcome value.
- `createdigitdecompevent` `name` `maturationtime` `base` `numdigits` `unit` `precision` `[signed]` - Registers an oracle event that uses digit decomposition when signing the number
- `name`- Name for this event
- `maturationtime` - The earliest expected time an outcome will be signed, given in epoch second
- `base` - The base in which the outcome value is decomposed
- `numdigits` - The max number of digits the outcome can have
- `unit` - The unit denomination of the outcome value
- `precision` - The precision of the outcome representing the base exponent by which to multiply the number represented by the composition of the digits to obtain the actual outcome value.
- `--signed`- Whether the outcomes can be negative
- `getevent` `event` - Get an event's details
- `eventName` - The event's name
- `signevent` `event` `outcome` - Signs an event
- `eventName` - The event's name
- `outcome`- Outcome to sign for this event
- `signdigits` `event` `outcome` - Signs an event
- `eventName` - The event's name
- `outcome` - Number to sign for this event
- `getsignatures` `event` - Get the signatures from a signed event
- `eventName` - The event's name
### Create Event Example
Bitcoin-S CLI:
```bash
$ bitcoin-s-cli createenumevent test "2030-01-03T00:30:00.000Z" "outcome1,outcome2,outcome3"
fdd824b0ba0f08e9becbf77019e246ca8a80c027585634dc1aed4b7f67442ada394b40dcb242d8a8c84893a752b93f30ff07525b0604382255ec7392fcc6f230140feb905f6f49e116de8cb57856bacdd9997d8dfb73877f64a4ec8d45fc0e73a0e52115fdd8224c000180e550759cb6275f6db3fad2b616ed51bdcccc204d0d978cd921cafae9fc1d6f657131d1fdd8061d0003086f7574636f6d6531086f7574636f6d6532086f7574636f6d65330474657374
$ bitcoin-s-cli getevent test
{
"nonces": [
"80e550759cb6275f6db3fad2b616ed51bdcccc204d0d978cd921cafae9fc1d6f"
],
"eventName": "test",
"signingVersion": "DLCOracleV0SigningVersion",
"maturationTime": "2030-01-03T00:30:00.000Z",
"announcementSignature": "ba0f08e9becbf77019e246ca8a80c027585634dc1aed4b7f67442ada394b40dcb242d8a8c84893a752b93f30ff07525b0604382255ec7392fcc6f230140feb90",
"eventDescriptorTLV": "fdd8061d0003086f7574636f6d6531086f7574636f6d6532086f7574636f6d6533",
"eventTLV": "fdd8224c000180e550759cb6275f6db3fad2b616ed51bdcccc204d0d978cd921cafae9fc1d6f657131d1fdd8061d0003086f7574636f6d6531086f7574636f6d6532086f7574636f6d65330474657374",
"announcementTLV": "fdd824b0ba0f08e9becbf77019e246ca8a80c027585634dc1aed4b7f67442ada394b40dcb242d8a8c84893a752b93f30ff07525b0604382255ec7392fcc6f230140feb905f6f49e116de8cb57856bacdd9997d8dfb73877f64a4ec8d45fc0e73a0e52115fdd8224c000180e550759cb6275f6db3fad2b616ed51bdcccc204d0d978cd921cafae9fc1d6f657131d1fdd8061d0003086f7574636f6d6531086f7574636f6d6532086f7574636f6d65330474657374",
"attestations": null,
"signatures": null,
"outcomes": [
"outcome1",
"outcome2",
"outcome3"
],
"signedOutcome": null
}
$ bitcoin-s-cli signevent test "outcome1"
fdd8687004746573745f6f49e116de8cb57856bacdd9997d8dfb73877f64a4ec8d45fc0e73a0e52115000180e550759cb6275f6db3fad2b616ed51bdcccc204d0d978cd921cafae9fc1d6f33fd84ba8eea0a75f1568149f42e8466e1bc3422ea449532d4eeffad8586d14e086f7574636f6d6531
$ bitcoin-s-cli getsignatures test
fdd8687004746573745f6f49e116de8cb57856bacdd9997d8dfb73877f64a4ec8d45fc0e73a0e52115000180e550759cb6275f6db3fad2b616ed51bdcccc204d0d978cd921cafae9fc1d6f33fd84ba8eea0a75f1568149f42e8466e1bc3422ea449532d4eeffad8586d14e086f7574636f6d6531
```
CURL:
```bash
$ curl --data-binary '{"jsonrpc": "1.0", "id": "curltest", "method": "createenumevent", "params": ["testEvent", ""2030-01-03T00:30:00.000Z"", ["outcome1", "outcome2", "outcome3"]]}' -H "Content-Type: application/json" http://127.0.0.1:9998/
{"result":"fdd824b0ba0f08e9becbf77019e246ca8a80c027585634dc1aed4b7f67442ada394b40dcb242d8a8c84893a752b93f30ff07525b0604382255ec7392fcc6f230140feb905f6f49e116de8cb57856bacdd9997d8dfb73877f64a4ec8d45fc0e73a0e52115fdd8224c000180e550759cb6275f6db3fad2b616ed51bdcccc204d0d978cd921cafae9fc1d6f657131d1fdd8061d0003086f7574636f6d6531086f7574636f6d6532086f7574636f6d65330474657374","error":null}
$ curl --data-binary '{"jsonrpc": "1.0", "id": "curltest", "method": "getevent", "params": ["testEvent"]}' -H "Content-Type: application/json" http://127.0.0.1:9998/
{"result":{"nonces":["80e550759cb6275f6db3fad2b616ed51bdcccc204d0d978cd921cafae9fc1d6f"],"eventName":"test","signingVersion":"DLCOracleV0SigningVersion","maturationTime":"2030-01-03T00:30:00.000Z","announcementSignature":"ba0f08e9becbf77019e246ca8a80c027585634dc1aed4b7f67442ada394b40dcb242d8a8c84893a752b93f30ff07525b0604382255ec7392fcc6f230140feb90","eventDescriptorTLV":"fdd8061d0003086f7574636f6d6531086f7574636f6d6532086f7574636f6d6533","eventTLV":"fdd8224c000180e550759cb6275f6db3fad2b616ed51bdcccc204d0d978cd921cafae9fc1d6f657131d1fdd8061d0003086f7574636f6d6531086f7574636f6d6532086f7574636f6d65330474657374","announcementTLV":"fdd824b0ba0f08e9becbf77019e246ca8a80c027585634dc1aed4b7f67442ada394b40dcb242d8a8c84893a752b93f30ff07525b0604382255ec7392fcc6f230140feb905f6f49e116de8cb57856bacdd9997d8dfb73877f64a4ec8d45fc0e73a0e52115fdd8224c000180e550759cb6275f6db3fad2b616ed51bdcccc204d0d978cd921cafae9fc1d6f657131d1fdd8061d0003086f7574636f6d6531086f7574636f6d6532086f7574636f6d65330474657374","attestations":["33fd84ba8eea0a75f1568149f42e8466e1bc3422ea449532d4eeffad8586d14e"],"signatures":["80e550759cb6275f6db3fad2b616ed51bdcccc204d0d978cd921cafae9fc1d6f33fd84ba8eea0a75f1568149f42e8466e1bc3422ea449532d4eeffad8586d14e"],"outcomes":["outcome1","outcome2","outcome3",],"signedOutcome": null},"error":null}
$ curl --data-binary '{"jsonrpc": "1.0", "id": "curltest", "method": "signevent", "params": ["testEvent", "outcome1"]}' -H "Content-Type: application/json" http://127.0.0.1:9998/
{"result":"fdd8687004746573745f6f49e116de8cb57856bacdd9997d8dfb73877f64a4ec8d45fc0e73a0e52115000180e550759cb6275f6db3fad2b616ed51bdcccc204d0d978cd921cafae9fc1d6f33fd84ba8eea0a75f1568149f42e8466e1bc3422ea449532d4eeffad8586d14e086f7574636f6d6531","error":null}
$ curl --data-binary '{"jsonrpc": "1.0", "id": "curltest", "method": "getsignatures", "params": ["testEvent"]}' -H "Content-Type: application/json" http://127.0.0.1:9998/
{"result":["fdd8687004746573745f6f49e116de8cb57856bacdd9997d8dfb73877f64a4ec8d45fc0e73a0e52115000180e550759cb6275f6db3fad2b616ed51bdcccc204d0d978cd921cafae9fc1d6f33fd84ba8eea0a75f1568149f42e8466e1bc3422ea449532d4eeffad8586d14e086f7574636f6d6531"],"error":null}
```
### Numeric Example
Bitcoin-S CLI:
```bash
$ bitcoin-s-cli createnumericevent exampleNumeric "2030-01-03T00:30:00.000Z" -1000 1000 "units" 0
fdd824fd010bfc52dab25169eef25815c795128f38ef3b89bc7f53d1d788b4a1c544e5bebfbf6799975b62a1888e2d77445b6d002672f52f8626b1ea6cc6cd974a8039d28a9f5f6f49e116de8cb57856bacdd9997d8dfb73877f64a4ec8d45fc0e73a0e52115fdd822a70004012d73a453bb630fe355830a81727cf2fb10c41ccfee040c529a4dec21ca5071f5aff60ac9ef8425ae438e84a6f067952d60b947e9e44bfc6e8fd89b781492057b1db5da37f1c10bfcaf7a4fb0e9f6dbb8d25dfed7a25241bbec3c0a60a40d2949305ff92f679598a11e7a857beef901903fc83624413831a06513da577cdd66657131d1fdd80a0f000a0105756e6974730000000000030d6578616d706c654465636f6d70
$ bitcoins-cli getevent exampleNumeric
{
"nonces": [
"012d73a453bb630fe355830a81727cf2fb10c41ccfee040c529a4dec21ca5071",
"49305ff92f679598a11e7a857beef901903fc83624413831a06513da577cdd66",
"7b1db5da37f1c10bfcaf7a4fb0e9f6dbb8d25dfed7a25241bbec3c0a60a40d29",
"f5aff60ac9ef8425ae438e84a6f067952d60b947e9e44bfc6e8fd89b78149205"
],
"eventName": "exampleNumeric",
"signingVersion": "DLCOracleV0SigningVersion",
"maturationTime": "2030-01-03T00:30:00.000Z",
"announcementSignature": "fc52dab25169eef25815c795128f38ef3b89bc7f53d1d788b4a1c544e5bebfbf6799975b62a1888e2d77445b6d002672f52f8626b1ea6cc6cd974a8039d28a9f",
"eventDescriptorTLV": "fdd80a0f000a0105756e697473000000000003",
"eventTLV": "fdd822a70004012d73a453bb630fe355830a81727cf2fb10c41ccfee040c529a4dec21ca507149305ff92f679598a11e7a857beef901903fc83624413831a06513da577cdd667b1db5da37f1c10bfcaf7a4fb0e9f6dbb8d25dfed7a25241bbec3c0a60a40d29f5aff60ac9ef8425ae438e84a6f067952d60b947e9e44bfc6e8fd89b78149205657131d1fdd80a0f000a0105756e6974730000000000030d6578616d706c654465636f6d70",
"announcementTLV": "fdd824fd010bfc52dab25169eef25815c795128f38ef3b89bc7f53d1d788b4a1c544e5bebfbf6799975b62a1888e2d77445b6d002672f52f8626b1ea6cc6cd974a8039d28a9f5f6f49e116de8cb57856bacdd9997d8dfb73877f64a4ec8d45fc0e73a0e52115fdd822a70004012d73a453bb630fe355830a81727cf2fb10c41ccfee040c529a4dec21ca507149305ff92f679598a11e7a857beef901903fc83624413831a06513da577cdd667b1db5da37f1c10bfcaf7a4fb0e9f6dbb8d25dfed7a25241bbec3c0a60a40d29f5aff60ac9ef8425ae438e84a6f067952d60b947e9e44bfc6e8fd89b78149205657131d1fdd80a0f000a0105756e6974730000000000030d6578616d706c654465636f6d70",
"attestations": null,
"signatures": null,
"outcomes": [
[
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9"
],
[
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9"
],
[
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9"
],
[
"+",
"-"
]
],
"signedOutcome": null
}
$ bitcoin-s-cli signdigits exampleNumeric 123
fdd868fd01380d6578616d706c654465636f6d705f6f49e116de8cb57856bacdd9997d8dfb73877f64a4ec8d45fc0e73a0e521150004012d73a453bb630fe355830a81727cf2fb10c41ccfee040c529a4dec21ca5071a853a189b9acffa2488542c4998261866ce392dbf38031509ceff34077431e65f5aff60ac9ef8425ae438e84a6f067952d60b947e9e44bfc6e8fd89b78149205773713008d316640b74d04f180b6c3c09b8de11b29cd7474681a7ad869857cd57b1db5da37f1c10bfcaf7a4fb0e9f6dbb8d25dfed7a25241bbec3c0a60a40d294f2222871b23a823acbfa552478ae3d526377a8918b346d6e206156c3e5a2c8549305ff92f679598a11e7a857beef901903fc83624413831a06513da577cdd66c03ed28ef6f7b0f48f974b61811a571652ea2eafda5fd5b244674420deb294e8012b013101320133
```
CURL:
```bash
$ curl --data-binary '{"jsonrpc": "1.0", "id": "curltest", "method": "createnumericevent", "params": ["numericExample", "2030-01-03T00:30:00.000Z", -1000, 1000, "units", 0]}' -H "Content-Type: application/json" http://127.0.0.1:9998/
{"result":"fdd824fd0110647c85d333aa6fc0d7808201da9d1010b815710dc25c3d73e9cc7a7f372a7342c99144ba74d70be72920f4515daa6565bce12aedfc5a828ee37b5453454c1b575f6f49e116de8cb57856bacdd9997d8dfb73877f64a4ec8d45fc0e73a0e52115fdd822ac0004d72282a2e9532924dc8cd79685a501202332ad0d118166328cb76138414fccf37051e50fd1ab30df4717da8905e400a32c5f4d793a4ac5433ed416165dd286c47446ab1d71a550a0d604c3e86c40a3c9b12de8f08a86639068707822cd4756217139d7cabd19d6b0b9e827cdf84a4fc18c88d1882e4e096d8dfeff58759504d2657131d1fdd80a0f000a0105756e697473000000000003126578616d706c6544696769744465636f6d70","error":null}
$ curl --data-binary '{"jsonrpc": "1.0", "id": "curltest", "method": "getevent", "params": ["numericExample"]}' -H "Content-Type: application/json" http://127.0.0.1:9998/
{"result":{"nonces":["7051e50fd1ab30df4717da8905e400a32c5f4d793a4ac5433ed416165dd286c4","7139d7cabd19d6b0b9e827cdf84a4fc18c88d1882e4e096d8dfeff58759504d2","7446ab1d71a550a0d604c3e86c40a3c9b12de8f08a86639068707822cd475621","d72282a2e9532924dc8cd79685a501202332ad0d118166328cb76138414fccf3"],"eventName":"numericExample","signingVersion":"DLCOracleV0SigningVersion","maturationTime":"2030-01-03T00:30:00.000Z","announcementSignature":"647c85d333aa6fc0d7808201da9d1010b815710dc25c3d73e9cc7a7f372a7342c99144ba74d70be72920f4515daa6565bce12aedfc5a828ee37b5453454c1b57","eventDescriptorTLV":"fdd80a0f000a0105756e697473000000000003","eventTLV":"fdd822ac00047051e50fd1ab30df4717da8905e400a32c5f4d793a4ac5433ed416165dd286c47139d7cabd19d6b0b9e827cdf84a4fc18c88d1882e4e096d8dfeff58759504d27446ab1d71a550a0d604c3e86c40a3c9b12de8f08a86639068707822cd475621d72282a2e9532924dc8cd79685a501202332ad0d118166328cb76138414fccf3657131d1fdd80a0f000a0105756e697473000000000003126578616d706c6544696769744465636f6d70","announcementTLV":"fdd824fd0110647c85d333aa6fc0d7808201da9d1010b815710dc25c3d73e9cc7a7f372a7342c99144ba74d70be72920f4515daa6565bce12aedfc5a828ee37b5453454c1b575f6f49e116de8cb57856bacdd9997d8dfb73877f64a4ec8d45fc0e73a0e52115fdd822ac00047051e50fd1ab30df4717da8905e400a32c5f4d793a4ac5433ed416165dd286c47139d7cabd19d6b0b9e827cdf84a4fc18c88d1882e4e096d8dfeff58759504d27446ab1d71a550a0d604c3e86c40a3c9b12de8f08a86639068707822cd475621d72282a2e9532924dc8cd79685a501202332ad0d118166328cb76138414fccf3657131d1fdd80a0f000a0105756e697473000000000003126578616d706c6544696769744465636f6d70","attestations":null,"signatures":null,"outcomes":[["0","1","2","3","4","5","6","7","8","9"],["0","1","2","3","4","5","6","7","8","9"],["0","1","2","3","4","5","6","7","8","9"],["+","-"]],"signedOutcome": null},"error":null}
$ curl --data-binary '{"jsonrpc": "1.0", "id": "curltest", "method": "signdigits", "params": ["numericExample", 123]}' -H "Content-Type: application/json" http://127.0.0.1:9998/
{"result":"fdd868fd013d126578616d706c6544696769744465636f6d705f6f49e116de8cb57856bacdd9997d8dfb73877f64a4ec8d45fc0e73a0e521150004d72282a2e9532924dc8cd79685a501202332ad0d118166328cb76138414fccf3d0646c9efd9523274014841ba24bf63219d5650d1682209d7e48af009d58e6d87051e50fd1ab30df4717da8905e400a32c5f4d793a4ac5433ed416165dd286c4c025dfd1e39de77e0418fa7d39abf2e9daf55d7fe34f8e312368cb4d45b4d4b97446ab1d71a550a0d604c3e86c40a3c9b12de8f08a86639068707822cd475621700347c52af088eda9a0245385094518134e73bb997102e11f6de0aeb36af7237139d7cabd19d6b0b9e827cdf84a4fc18c88d1882e4e096d8dfeff58759504d2f9e7a9e183b0836ad58dd646d9ab123132397109e4f51c5842958932a81bacd1012b013101320133","error":null}
```

View file

@ -0,0 +1,60 @@
---
id: version-0.6.0-lnd-rpc
title: LND
original_id: lnd-rpc
---
This is an RPC client for [LND](https://github.com/LightningNetwork/lnd). It assumes that a bitcoind instance is running.
Currently, this RPC client is written for [v0.12.1](https://github.com/lightningnetwork/lnd/releases/tag/v0.12.1-beta) version of LND.
## Configuration of LND
Please see the [sample configuration for LND](https://github.com/lightningnetwork/lnd/blob/v0.12.1-beta/sample-lnd.conf).
You can find the configuration we use for our testing infrastructure for lnd [here](https://github.com/bitcoin-s/bitcoin-s/blob/656e0928bf1bf4f511f60dec625699b454f29a1f/testkit/src/main/scala/org/bitcoins/testkit/lnd/LndRpcTestUtil.scala#L90).
## Starting LND
You need to download the binaries from the [LND's github](https://github.com/lightningnetwork/lnd/releases/tag/v0.12.1-beta).
To run lnd by unzipping the `lnd-linux-amd64-v0.12.1-beta.tar.gz` (or whichever platform you are on) and then running
```bash
$ ./lnd-linux-amd64-v0.12.1-beta/lnd
```
If you wish to start lnd from the RPC client, you can construct a [`LndRpcClient.binary`](https://github.com/bitcoin-s/bitcoin-s/blob/656e0928bf1bf4f511f60dec625699b454f29a1f/lnd-rpc/src/main/scala/org/bitcoins/lnd/rpc/LndRpcClient.scala#L35) field set
We will default to using the `binary` field first when trying to start the jar, and the fallback to the default datadir (`~/.lnd`).
Here is an example of how to start lnd:
```scala
implicit val system = ActorSystem(s"lnd-rpc-${System.currentTimeMillis}")
implicit val ec = system.dispatcher
val datadirPath = Paths.get("path", "to", "datadir")
val binaryPath = Paths.get("path", "to", "lnd-linux-amd64-v0.12.1-beta", "lnd")
val instance = LndInstance.fromDataDir(datadirPath.toFile)
val client = new LndRpcClient(instance, Some(binaryPath.toFile))
val startedF = client.start()
for {
lnd <- startedF
info <- lnd.getInfo
} yield {
println(s"Lnd info: $info")
}
```
### Updating to a new LND version
The lnd rpc module uses lnd's gRPC. This means when updating to the latest version, the `.proto` files will need to be updated.
Bitcoin-S stores them in [lnd-rpc/src/main/protobuf](https://github.com/bitcoin-s/bitcoin-s/tree/master/lnd-rpc/src/main/protobuf).
You can find the files to copy from LND [here](https://github.com/lightningnetwork/lnd/tree/master/lnrpc).
After updating the `proto` files you can run `sbt compile` and this will generate the corresponding class files, this should then give
compile warnings for changed rpc functions.

View file

@ -0,0 +1,32 @@
---
id: version-0.6.0-rpc-clients-intro
title: Introduction
original_id: rpc-clients-intro
---
When working with Bitcoin applications, a common task to
accomplish is connecting to a service like Bitcoin Core,
and use that for tasks like generating addresses,
verifying payments and
monitoring the blockchain. This typically happens through
tools like `bitcoin-cli`, or the Bitcoin Core HTTP RPC
server interface. One big drawback to this, is that you
lose all type-safety in your application. Even if you
have a custom type that represents a Bitcoin transaction,
how do you get that to play nicely with the result that
Bitcoin Core gives you after signing a transaction? A
random hexadecimal string in a HTTP response could be
anything from a public key, a transaction or a block
header.
We've done all the mundane work of wiring requests and
responses from Bitcoin Core to the powerful and safe types
found in Bitcoin-S. We've also written a bunch of tests,
that verify that all of this actually work.
You'll know for sure that you're sending
a valid public key to `importmulti`, and you when doing
RPC calls like `getblockheader` we'll even parse the
hexadecimal string into a complete header that you can
interact with without goofing around with bits and bytes.
We currently have RPC clients for Bitcoin Core, Eclair, Lnd.

View file

@ -0,0 +1,445 @@
---
id: version-0.6.0-jni-modify
title: Adding to Secp256k1 JNI
original_id: jni-modify
---
Bitcoin-S uses a Java Native Interface (JNI) to execute functions in [secp256k1-zkp](https://github.com/ElementsProject/secp256k1-zkp) from java/scala. The native java bindings used to be a part of the secp256k1 library that was maintained by bitcoin-core, but it was [removed in October 2019](https://github.com/bitcoin-core/secp256k1/pull/682). We maintain a [fork of secp256k1](https://github.com/bitcoin-s/secp256k1) which forks off of bitcoin-core's `master` but re-introduces the jni. This is also the easiest way to add functionality from new projects such as [Schnorr signatures](https://github.com/bitcoin-core/secp256k1/pull/558) and [ECDSA adaptor signatures](https://github.com/ElementsProject/secp256k1-zkp/pull/117) by rebasing the bitcoin-s branch with the JNI on top of these experimental branches. That said, it is quite tricky to hook up new functionality in secp256k1 into bitcoin-s and specifically `NativeSecp256k1.java`. The following is a description of this process.
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
<!-- END doctoc -->
- [Adding a new function to NativeSecp256k1.java](#adding-a-new-function-to-nativesecp256k1java)
- [Adding to `src/java/org_bitcoin_NativeSecp256k1.c`](#adding-to-srcjavaorg_bitcoin_nativesecp256k1c)
- [Adding to `src/java/org_bitcoin_NativeSecp256k1.h`](#adding-to-srcjavaorg_bitcoin_nativesecp256k1h)
- [Adding to `src/java/org/bitcoin/NativeSecp256k1.java`](#adding-to-srcjavaorgbitcoinnativesecp256k1java)
- [Adding to `src/java/org/bitcoin/NativeSecp256k1Test.java`](#adding-to-srcjavaorgbitcoinnativesecp256k1testjava)
- [Adding to Bitcoin-S](#adding-to-bitcoin-s)
- [Further Work to Enable Typed Invocations and Nice Tests](#further-work-to-enable-typed-invocations-and-nice-tests)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
## Adding a new function to NativeSecp256k1.java
### Adding to `src/java/org_bitcoin_NativeSecp256k1.c`
1. Add an `#include` import at the top (if applicable)
If your secp256k1 functions are not already included, you will need to `#include` the header file (should be in the `secp256k1/include` directory).
2. Function signature
Your new function signature should begin with
```c
SECP256K1_API jint JNICALL Java_org_bitcoin_NativeSecp256k1_
```
followed by the secp256k1 function name where `_` are replaced with `_1` (it's a weird jni thing). Finally, you add a parameter list that begins with
```c
(JNIEnv* env, jclass classObject, jobject byteBufferObject, jlong ctx_l
```
and ends with any `jint`s in the case that any of the secp256k1 function inputs have variable length (such as public keys which can be either `33` or `65` bytes, or ECDSA signatures), and lastly any `jboolean`s in case there is some flag like `compressed` passed in.
As an example that includes everything, if you are making a call to `secp256k1_pubkey_tweak_add` which takes in public keys that could be `33` or `65` bytes and outputs a public key that will either be compressed or decompressed based on an input flag, then the function signature would be
```c
SECP256K1_API jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1pubkey_1tweak_1add
(JNIEnv* env, jclass classObject, jobject byteBufferObject, jlong ctx_l, jint publen, jboolean compressed)
```
3. Reading `unsigned char*` inputs
It is now time to create pointers for each of the secp256k1 function inputs that where passed in via the `byteBufferObject`. We must first read in the `Secp256k1Context` with the line
```c
secp256k1_context *ctx = (secp256k1_context*)(uintptr_t)ctx_l;
```
and we can then initialize the first pointer to be the beginning of the `byteBufferObject` with the line
```c
unsigned char* firstArg = (*env)->GetDirectBufferAddress(env, byteBufferObject);
```
and subsequent arguments' pointers where the previous argument's length is known (say `32` bytes for example) can be instantiated using
```c
unsigned char* prevArg = ...
unsigned char* nextArg = (unsigned char*) (prevArg + 32);
```
and in the case that a previous argument has variable length, then a `jint` has been provided as an input and can be used instead, such as in the example
```c
unsigned char* prevArg = ...
unsigned char* nextArg = (unsigned char*) (prevArg + publen);
```
where `publen` is a `jint` passed to this C function.
As an example that includes everything, consider the function `secp256k1_ecdsa_verify` which takes as input a `32` byte message, a variable length signature and a public key (of length `33` or `65` bytes). Our function will begin with
```c
{
secp256k1_context *ctx = (secp256k1_context*)(uintptr_t)ctx_l;
unsigned char* data = (unsigned char*) (*env)->GetDirectBufferAddress(env, byteBufferObject);
const unsigned char* sigdata = (unsigned char*) (data + 32);
const unsigned char* pubdata = (unsigned char*) (sigdata + siglen);
```
where `siglen` is a `jint` passed into the C function.
4. Initialize variables
Next we must declare all variables. We put all decelerations here as it is required by the C framework used by `libsecp256k1` that definitions and assignments/function calls cannot be interleaved.
Specifically you will need to declare any secp256k1 specific structs here as well as all outputs (such as `jobjectArrays` and `jByteArrays`). Generally speaking this will include all inputs which are not raw data (public keys, signatures, etc). Lastly, you will also have an `int ret` which will store `0` if an error occurred and `1` otherwise.
As an example that includes everything, consider again the function `secp256k1_pubkey_tweak_add` has the following declarations
```c
jobjectArray retArray;
jbyteArray pubArray, intsByteArray;
unsigned char intsarray[2];
unsigned char outputSer[65];
size_t outputLen = 65;
secp256k1_pubkey pubkey;
int ret;
```
Where `retArray` is eventually going to be the data returned, which will contain the `jbyteArray`s `pubArray` and `intsByteArray`, which will contain `outputSer` and `intsarray` respectively. Lastly `pubkey` will store a deserialized `secp256k1_pubkey` corresponding to the input `unsigned char*` public key.
5. Parse inputs when applicable
In the case where there are `unsigned char*` inputs which need to be deserialized into secp256k1 structs, this is done now. As an example, `secp256k1_pubkey_tweak_add` takes a public key as input:
```c
unsigned char* pkey = (*env)->GetDirectBufferAddress(env, byteBufferObject);
```
where a `jint publen` is passed in as a function parameter. This function already has a declaration for `secp256k1_pubkey pubkey;`. The first call made after the above declarations is
```c
ret = secp256k1_ec_pubkey_parse(ctx, &pubkey, pkey, publen)
```
and if further parsing is necessary, it is put inside of `if (ret) { ret = [further parsing here] }`.
6. Make calls to secp256k1 functions to instantiate outputs
It is finally time to actually call the secp256k1 function we are binding to the jni! This is done by simply calling `ret = [call to secp function here];` or `if (ret) { ret = [secp function call] };` if there were any inputs that needed to be parsed. Note that some secp256k1 functions return outputs by populating variables you should have declared and for which pointers are passed as inputs, while other functions will mutate their inputs rather than returning outputs.
7. Serialize variable length outputs if applicable
When dealing with variable length outputs such as signatures, you will likely need to serialize these outputs. This is done by having already instantiated such a variable as
```c
unsigned char outputSer[72];
size_t outputLen = 72;
```
where in this case `72` is an upper bound on signature length. With these variables existing (as well as a `secp256k1_ecdsa_signature sig` which has been populated), we call a secp256k1 serialization function to populate `outputSer` and `outputLen` from `sig`
```c
if(ret) {
int ret2 = secp256k1_ecdsa_signature_serialize_der(ctx, outputSer, &outputLen, &sig);
(void)ret2;
}
```
As you can see, in this case we do not which to alter the value returned in `ret` if serialization fails. If we did then `ret2` would not be introduced and we would instead do `ret = [serialize]`.
8. Populate return array when applicable
We now begin translating our serialized results back into Java entities. If you are returning any `int`s containing meta-data (usually `ret` is included here, as are the variable lengths of outputs when applicable), you will want an `unsigned char intsarray[n]` to be already declared where `n` is the number of pieces of meta-data. For example, in `secp256k1_ecdsa_sign`, we wish to return whether there were any errors (stored in `int ret`) and the output signature's length, `size_t outputLen`. Hence we have an `unsigned char intsarray[2]` and we populate it as follows
```c
intsarray[0] = outputLen;
intsarray[1] = ret;
```
Next we populate the `jobjectArray` we wish to return, this will always begin with a call
```c
retArray = (*env)->NewObjectArray(env, 2,
(*env)->FindClass(env, "[B"),
(*env)->NewByteArray(env, 1));
```
to instantiate an empty return array. Next we instantiate our `jbyteArray`s with calls to
```c
myByteArray = (*env)->NewByteArray(env, len);
```
where `myByteArray` is replaced with a real name (such as `intsByteArray`) and `len` is replaced with the length of this array (either a constant or a populated `size_t` variable). Next we populate this array with our data by calling
```c
(*env)->SetByteArrayRegion(env, myByteArray, 0, len, (jbyte*)myData);
```
where `myData` is a C array of `unsigned char` (of length `len`). Lastly, we place `myByteArray` into its place in `retArray` with
```c
(*env)->SetObjectArrayElement(env, retArray, index, myByteArray);
```
where `index` is a constant (`0`, `1`, `2`, etc.) for the index of `myByteArray` within `retArray`. Note that you should follow our conventions and have index `0` contain the actual data to be returned and index `1` (and onward) contain any meta-data.
Please note that if you wish not to return such meta-data (such as if you wish to return only a `boolean`), then none of the code in this subsection is required
9. void `classObject`
Once we are ready to return, we first void the input `classObject` by making the call
```c
(void)classObject;
```
10. Return array when applicable, `ret` when applicable
Lastly, we return `retArray` in the case where we wish to return a `byte[]`, or `ret` in the case that we wish to return a `boolean`.
### Adding to `src/java/org_bitcoin_NativeSecp256k1.h`
Once your function is defined in `src/java/org_bitcoin_NativeSecp256k1.c`, you must define them in the corresponding header files by simply copying the function signature but without parameter names. For example, if `secp256k1_pubkey_tweak_add` has the function signature
```c
SECP256K1_API jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1pubkey_1tweak_1add
(JNIEnv* env, jclass classObject, jobject byteBufferObject, jlong ctx_l, jint publen, jboolean compressed)
```
then in the header file we include
```c
/*
* Class: org_bitcoin_NativeSecp256k1
* Method: secp256k1_pubkey_tweak_add
* Signature: (Ljava/nio/ByteBuffer;JI)[[B
*/
SECP256K1_API jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1pubkey_1tweak_1add
(JNIEnv *, jclass, jobject, jlong, jint, jboolean);
```
### Adding to `src/java/org/bitcoin/NativeSecp256k1.java`
We are now done writing C code! We have completed an interface in C for the JNI to hook up to. However, we must now write the corresponding Java code which hides the Java to C (and back) conversions from other Java code. We accomplish this with a `class` of `static` methods called `NativeSecp256k1`.
1. Add `private static native` secp256k1 function
We begin by adding a `private static native` method at the bottom of the file corresponding to our secp256k1 function. Notice that the syntax for `native` methods is similar to that of Java abstract interface methods where instead of providing an implementation we simply end with a semi-colon.
For functions returning `boolean`s, we have their `native` methods return `int` (will be `0` or `1`). Otherwise, for functions returning `byte[]`s, we have their `native` methods return `byte[][]` (two dimensional array to allow for meta-data).
2. Method signature
Next we add a method to the `NativeSecp256k1` class
```java
public static byte[] myFunc(byte[] input1, byte[] input2, boolean input3) throws AssertFailException
```
where `boolean` could also be the return type instead of `byte[]`.
3. `checkArgument`s
We begin implementing this function by checking the input argument lengths using the `checkArument` function
```java
checkArgument(input1.length == 32 && (input2.length == 33 || input2.length == 65));
```
4. Initialize `ByteBuffer`
We now initialize the `ByteBuffer` which we will be passing through the JNI as an input. This is done with a call to
```java
ByteBuffer byteBuff = nativeECDSABuffer.get();
```
followed by allocation when necessary as follows
```java
if (byteBuff == null || byteBuff.capacity() < input1.length + input2.length) {
byteBuff = ByteBuffer.allocateDirect(input1.length + input2.length);
byteBuff.order(ByteOrder.nativeOrder());
nativeECDSABuffer.set(byteBuff);
}
```
where `input1.length + input2.length` is replaced by whatever the total `ByteBuffer` length needed.
5. Fill `ByteBuffer`
We now populate the `ByteBuffer` as follows
```java
byteBuff.rewind();
byteBuff.put(input1);
byteBuff.put(input2);
```
where generally, you will `rewind()` and then `put()` all inputs (in order).
6. Make `native` call
It is now time to make a call to our `native` C function.
In the case where we are returning a `byte[]`, this is done by first declaring a `byte[][]` to store the output and then locking the read lock, `r`. Then we call the `native` function within a `try` clause which releases the lock in the `finally` clause.
```java
byte[][] retByteArray;
r.lock();
try {
retByteArray = secp256k1_my_call(byteBuff, Secp256k1Context.getContext(), input3);
} finally {
r.unlock();
}
```
In the case where we are returning a `boolean`, simply make the call in the `try` and compare the output to `1` like so
```java
r.lock();
try {
return secp256k1_my_bool_call(byteBuff, Secp256k1Context.getContext()) == 1;
} finally {
r.unlock();
}
```
If this is the case, you are now done and can ignore the following steps.
7. Parse outputs
`retByteArray` should now be populated and we want to read its two parts (data and meta-data). Getting the data should be as easy as
```java
byte[] resultArr = retByteArr[0];
```
while for each piece of meta-data, you can read the corresponding `int` as follows
```java
int metaVal = new BigInteger(new byte[] { retByteArray[1][index] }).intValue();
```
where `index` is replaced with the index in the meta-data array.
8. Validate outputs
In the case where we now have meta-data, we validate it with calls to `assertEquals`.
9. Return output
Finally, we return `resultArr`.
### Adding to `src/java/org/bitcoin/NativeSecp256k1Test.java`
I normally first build the C binaries and add to Bitcoin-S before coming back to this section because I use `sbt core/console` to generate values and make calls below, but this is not a requirement.
1. Generate values and make calls to `org.bitcoin.NativeSecp256k1` to generate inputs and their expected outputs
2. Create regression unit tests with these values in NativeSecp256k1Test
Note that you can use `DatatypeConverter.parseHexBinary` to convert `String` hex to a `byte[]`, and you can use `DatatypeConverter.printHexBinary` to convert a `byte[]` to its `String` hex. Lastly you will make assertions with calls to `assertEquals`.
3. Add test to `main`
### Adding to Bitcoin-S
1. Translate `NativeSecp256k1` and `NativeSecp256k1Test` to jni project
By translate I mean to say that you must copy the functions from those files to the corresponding files in the `bitcoin-s/secp256k1jni` project. For tests this will require changing the methods to be non-`static` as well as adding the `@Test` annotation above each method (rather than adding to a `main` method).
2. Configure and build `secp256k1`
You will need to go to the `bitcoin-s/secp256k1` directory in a terminal and running the following where you may need to add to the `./configure` command if you are introducing a new module.
__For Linux or OSx (64-bit)__
You will have to make sure `JAVA_HOME` is set, and build tools are installed, for Linux this requires:
```bashrc
echo JAVA_HOME
sudo apt install build-essential autotools-dev libtool automake
```
and for Mac this requires:
```bashrc
brew install automake libtool
```
You should then be able to build `libsecp256k1` with the following:
```bashrc
./autogen.sh
./configure --enable-jni --enable-experimental --enable-module-ecdh --enable-module-schnorrsig --enable-module-ecdsa-adaptor
make CFLAGS="-std=c99"
make check
make check-java
```
__For Windows (64-bit)__
Windows bindings are cross-built on Linux. You need to install the `mingw` toolchain and have `JAVA_HOME` point to a Windows JDK:
```bashrc
sudo apt install g++-mingw-w64-x86-64
sudo update-alternatives --config x86_64-w64-mingw32-g++
```
You should then be able to build `libsecp256k1` with the following:
```bashrc
echo "LDFLAGS = -no-undefined" >> Makefile.am
./configure --host=x86_64-w64-mingw32 --enable-jni --enable-experimental --enable-module-ecdh --enable-module-schnorrsig --enable-module-ecdsa-adaptor && make clean && make CFLAGS="-std=c99"
```
There may be some errors that can be ignored:
- `Could not determine the host path corresponding to`
- `redeclared without dllimport attribute: previous dllimport ignored`
3. Copy binaries into bitcoin-s natives for your system
You have now built the C binaries for your JNI bindings for your operating system and you should now find your operating system's directory in `bitcoin-s/secp256k1jni/natives` and replace its contents with the contents of `secp256k1/.libs` (which contains the compiled binaries).
4. Run `secp256k1jni` tests
If you have not yet implemented tests, you should now be able to go back and do so as calls to `NativeSecp256k1` should now succeed.
Once you have tests implemented, and assuming you've copied them correctly to the `bitcoin-s/secp256k1jni` project, you should be able to run them using
```bashrc
sbt secp256k1jni/test
```
### Further Work to Enable Typed Invocations and Nice Tests
1. Add new `NetworkElement`s where applicable
In the case where you are dealing in new kinds of data that are not yet defined in Bitcoin-S, you should add these as `case class`es extending the `NetworkElement` trait, and give them companion objects extending `Factory` for easy serialization and deserialization.
This step is not necessary if you are only dealing in raw data, `ECPrivateKey`s, `ECPublicKey`s, etc.
2. Add new typed functions to relevant data types where applicable
In the case where your new function should be a static method, find a good `object` (or introduce one) and give it a `def` which takes in typed arguments and outputs typed arguments (using `ByteVector` in all places dealing with raw data rather than using `Array[Byte]`). You will then implement these methods using calls to `NativeSecp256k1` methods and getting the inputs into `Array[Byte]` form by getting their `ByteVector`s (usually through a call to `_.bytes`) and then calling `_.toArray`.
You will then need to take the data returned and deserialize it.
In the case where your new function belongs naturally as an action performed by some existing or newly introduced type, you can implement your new function as a call made by that class as described for the previous case but where the class will pass a serialized version of itself into the `NativeSecp256k1` call.
It is often acceptable to implement the call in an `object` and then also add the call (via a call to the object, passing `this`) to the interface of relevant types.
3. Implement Bouncy Castle fallback in `BouncyCastleUtil.scala` if you can.
4. Add unit and property-based tests.
5. If you implemented Bouncy Castle fallback, add tests to `BouncyCastleSecp256k1Test` to compare implementations

View file

@ -0,0 +1,92 @@
---
id: version-0.6.0-secp256k1
title: Secp256k1
original_id: secp256k1
---
[Libsecp256k1](https://github.com/bitcoin-core/secp256k1) is used to preform cryptographic operations on the secp256k1 curve.
This is the curve that bitcoin uses. There is a _signficant_ speedup when using this library compared to java crypto libraries
like bouncy castle.
In bitcoin-s, we support native binaries for libsecp256k1
1. [linux 32 bit](../../secp256k1jni/natives/linux_32)
2. [linux 64 bit](../../secp256k1jni/natives/linux_64)
3. [mac osx 64 bit](../../secp256k1jni/natives/osx_64)
4. [windows 64 bit](../../secp256k1jni/natives/windows_64)
Bitcoin-s uses a zero dependency library called [`native-lib-loader`](https://github.com/scijava/native-lib-loader).
That does the appropriate loading of the library onto your classpath to be accessed.
### Using libsecp256k1
To tell if you have access to libsecp256k1 you can do the following
```scala
val isEnabled = org.bitcoin.Secp256k1Context.isEnabled()
println(s"Secp256k1Context.isEnabled=${isEnabled}")
```
If libsecp256k1 is enabled, you can use [NativeSecp256k1](/api/org/bitcoin/NativeSecp256k1)
with static method defined in the class.
```scala
val privKey = ECPrivateKey.freshPrivateKey
val pubKey = privKey.publicKey
val dataToSign = DoubleSha256Digest.empty
val signature = NativeSecp256k1.sign(dataToSign.bytes.toArray, privKey.bytes.toArray)
val verify = NativeSecp256k1.verify(dataToSign.bytes.toArray, signature, pubKey.bytes.toArray)
println(s"Verified with NativeSecp256k1 signature=${verify}")
//you can also just directly sign with the ECKey interface:
val signature2 = privKey.sign(dataToSign)
val verified2 = pubKey.verify(dataToSign, signature2)
println(s"Verified with NativeSecp256k1 again=${verified2}")
```
### When libsecp256k1 isn't available, or you want to turn it off
There are two reasons you wouldn't want to use libsecp256k1
1. You don't trust the pre-compiled binaries we are using
2. Your OS/arch is not supported
There are two ways you can circumvent libsecp256k1
1. Set `DISABLE_SECP256K1=true` in your environment variables. This will force `CryptoContext.default` to return false which will make Bitcoin-S act like `Secp256k1Context.isEnabled()` has returned false.
2. Call Bouncy castle methods in `ECKey`.
Here is an example of calling bouncy castle methods in `ECKey`
```scala
val privKey = ECPrivateKey.freshPrivateKey
// privKey: ECPrivateKey = Masked(ECPrivateKeyImpl)
// calls bouncy castle indirectly via CryptoContext
val publicKey = privKey.publicKey
// publicKey: ECPublicKey = ECPublicKey(0393d4b6b67065c1b536f0015c729df1fc97fbe1f25115e2426ad220a4268dac39)
val dataToSign = DoubleSha256Digest.empty
// dataToSign: DoubleSha256Digest = DoubleSha256Digest(0000000000000000000000000000000000000000000000000000000000000000)
// calls bouncy castle indirectly via CryptoContext
val signature = privKey.sign(dataToSign.bytes)
// signature: ECDigitalSignature = ECDigitalSignature(30440220445169f62426fed42ed12a5e9861dad034e86da1e71f1a069ff7709a69603d1e0220651b2a4937673d3e908e690e90a458e46dffaefcc899ae32aaacd76133ffec2d)
// calls bouncy castle indirectly via CryptoContext
val verified = publicKey.verify(dataToSign.bytes, signature)
// verified: Boolean = true
println(s"Verified with bouncy castle=${verified}")
// Verified with bouncy castle=true
```
### Building libsecp256k1
[See instructions here](add-to-jni.md#adding-to-bitcoin-s)

View file

@ -0,0 +1,22 @@
---
id: version-0.6.0-testkit-core
title: Testkit Core
original_id: testkit-core
---
`testkit-core` is a new module that is introduced in the bitcoin-s 0.6 release.
The purpose of this module is to be scalajs compatible so that it can be used
to test the cryptoJS and coreJS modules. This means you can use this dependency
to test both your JVM and JS application that uses bitcoin-s.
If you only need to basic data structures for testing your application, you likely want
to use this module rather than `testkit`.
The testkit functionality for our core module primary consists of generators for property based tests.
A generator is a piece of code that generates a random object for a data structure -- such as a `Transaction`.
There is also a robust set of generators available in the [org.bitcoins.testkitcore.gen](../../testkit-core/src/main/scala/org/bitcoins/testkitcore/gen) package.
This allows you to integrate property based testing into your library and feel confident about implementing your application specific logic correctly.
You can see examples of us using these generators inside of testkit-core in our [Private Key test cases](../../crypto-test/src/test/scala/org/bitcoins/crypto/ECPrivateKeyTest.scala)

View file

@ -0,0 +1,173 @@
---
id: version-0.6.0-testkit
title: Testkit
original_id: testkit
---
## Philosophy of Testkit
The high level of of the bitcoin-s testkit is to mimic and provide functionality to test 3rd party applications.
There are other examples of these in the Scala ecosystem like the `akka-testkit` and `slick-testkit`.
We use this testkit to test bitcoin-s it self.
### Testkit for bitcoind
This gives the ability to create and destroy `bitcoind` on the underlying operating system to test against.
Make sure you have run `sbt downloadBitcoind` before running this example, as you need access to the bitcoind binaries.
Our [BitcoindRpcClient](/api/org/bitcoins/rpc/client/common/BitcoindRpcClient) is tested with the functionality provided in the testkit.
A quick example of a useful utility method is [BitcoindRpcTestUtil.startedBitcoindRpcClient()](/api/org/bitcoins/testkit/rpc/BitcoindRpcTestUtil).
This spins up a bitcoind regtest instance on machine and generates 101 blocks on that node.
This gives you the ability to start spending money immediately with that bitcoind node.
```scala
implicit val system = ActorSystem("bitcoind-testkit-example")
implicit val ec = system.dispatcher
//pick our bitcoind version we want to spin up
//you can pick older versions if you want
//we support versions 16-19
val bitcoindV = BitcoindVersion.V19
//create an instance
val instance = BitcoindRpcTestUtil.instance(versionOpt = Some(bitcoindV))
//now let's create an rpc client off of that instance
val bitcoindRpcClientF = BitcoindRpcTestUtil.startedBitcoindRpcClient(instance, Vector.newBuilder)
//yay! it's started. Now you can run tests against this.
//let's just grab the block count for an example
val blockCountF = for {
bitcoind <- bitcoindRpcClientF
count <- bitcoind.getBlockCount
} yield {
//run a test against the block count
assert(count > 0, s"Block count was not more than zero!")
}
//when you are done, don't forget to destroy it! Otherwise it will keep running on the underlying os
val stoppedF = for {
rpc <- bitcoindRpcClientF
_ <- blockCountF
stopped <- BitcoindRpcTestUtil.stopServers(Vector(rpc))
} yield stopped
```
For more information on how the bitcoind rpc client works, see our [bitcoind rpc docs](../rpc/bitcoind.md)
#### Caching bitcoind in test cases
When doing integration tests with bitcoind, you likely do not want to spin up a
new bitcoind for _every_ test that is run.
Not to fear, when using `testkit` you can use our bitcoind fixtures for your unit tests!
These will only spin up on bitcoind per test suite, rather than one bitcoind per test.
We currently have two types of fixtures available to users of this dependency
1. [Connected pairs of bitcoind nodes](https://github.com/bitcoin-s/bitcoin-s/blob/eaac9c154c25f3bd76615ea2151092f06df6bdb4/testkit/src/main/scala/org/bitcoins/testkit/rpc/BitcoindFixtures.scala#L282)
2. [Bitcoind nodes with funded wallets](https://github.com/bitcoin-s/bitcoin-s/blob/eaac9c154c25f3bd76615ea2151092f06df6bdb4/testkit/src/main/scala/org/bitcoins/testkit/rpc/BitcoindFixtures.scala#L161)
If you mixin either of those traits for your test, you will now have access to the corresponding fixture.
You can find an examples of how to use these two test fixtures
1. [Example of using a connected pair of nodes in test suite](https://github.com/bitcoin-s/bitcoin-s/blob/32a6db930bdf849a94d92cd1de160b87845ab168/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/common/WalletRpcTest.scala#L37)
2. [Example of using a bitcoind with funded wallet in test suite](https://github.com/bitcoin-s/bitcoin-s/blob/eaac9c154c25f3bd76615ea2151092f06df6bdb4/testkit/src/main/scala/org/bitcoins/testkit/rpc/BitcoindFixtures.scala#L161)
### Testkit for eclair
We have similar utility methods for eclair. Eclair's testkit requires a bitcoind running (which we can spin up thanks to our bitcoind testkit).
Here is an example of spinning up an eclair lightning node, that is connected to a bitcoind and testing your lightning application.
Make sure to run `sbt downloadBitcoind downloadEclair` before running this so you have access to the underlying eclair binares
```scala
//Steps:
//1. Open and confirm channel on the underlying blockchain (regtest)
//2. pay an invoice
//3. Await until the payment is processed
//4. assert the node has received the payment
//5. cleanup
implicit val system = ActorSystem("eclair-testkit-example")
implicit val ec = system.dispatcher
//we need a bitcoind to connect eclair nodes to
lazy val bitcoindRpcClientF: Future[BitcoindRpcClient] = {
for {
cli <- EclairRpcTestUtil.startedBitcoindRpcClient()
// make sure we have enough money to open channels
address <- cli.getNewAddress
_ <- cli.generateToAddress(200, address)
} yield cli
}
//let's create two eclair nodes now
val clientF = for {
bitcoind <- bitcoindRpcClientF
e <- EclairRpcTestUtil.randomEclairClient(Some(bitcoind))
} yield e
val otherClientF = for {
bitcoind <- bitcoindRpcClientF
e <- EclairRpcTestUtil.randomEclairClient(Some(bitcoind))
} yield e
//great, setup done! Let's run the test
//to verify we can send a payment over the channel
for {
client <- clientF
otherClient <- otherClientF
_ <- EclairRpcTestUtil.openAndConfirmChannel(clientF, otherClientF)
invoice <- otherClient.createInvoice("abc", 50.msats)
info <- otherClient.getInfo
_ = assert(info.nodeId == invoice.nodeId)
infos <- client.getSentInfo(invoice.lnTags.paymentHash.hash)
_ = assert(infos.isEmpty)
paymentId <- client.payInvoice(invoice)
_ <- EclairRpcTestUtil.awaitUntilPaymentSucceeded(client, paymentId)
sentInfo <- client.getSentInfo(invoice.lnTags.paymentHash.hash)
} yield {
assert(sentInfo.head.amount == 50.msats)
}
//don't forget to shutdown everything!
val stop1F = clientF.map(c => EclairRpcTestUtil.shutdown(c))
val stop2F = otherClientF.map(o => EclairRpcTestUtil.shutdown(o))
val stoppedBitcoindF = for {
bitcoind <- bitcoindRpcClientF
_ <- BitcoindRpcTestUtil.stopServers(Vector(bitcoind))
} yield ()
val resultF = for {
_ <- stop1F
_ <- stop2F
_ <- stoppedBitcoindF
_ <- system.terminate()
} yield ()
Await.result(resultF, 180.seconds)
```
### Other modules
You may find useful testkit functionality for other modules here
1. [Chain](/api/org/bitcoins/testkit/chain/ChainUnitTest)
2. [Key Manager](/api/org/bitcoins/testkit/keymanager/KeyManagerApiUnitTest)
3. [Wallet](/api/org/bitcoins/testkit/wallet/BitcoinSWalletTest)
4. [Node](/api/org/bitcoins/testkit/node/NodeUnitTest)
In general, you will find constructors and destructors of fixtures that can be useful when testing your applications
if yo uare using any of those modules.

View file

@ -0,0 +1,108 @@
---
title: Wallet Rescans
id: version-0.6.0-wallet-rescan
original_id: wallet-rescan
---
With [BIP157](https://github.com/bitcoin/bips/blob/master/bip-0157.mediawiki) you can cache block filters locally to use
later for rescans in the case you need to restore your wallets. Our [chain](../chain/chain.md) project gives us
an API with the ability to query for filters.
### Rescan from CLI
To execute a rescan from the cli because you are restoring a wallet or it has gotten out of sync is fairly simple.
If you have an empty wallet it can be done by simply calling rescan
```bash
./bitcoin-s-cli rescan
```
If your wallet is not empty then you will need to call it with the force command
```bash
./bitcoin-s-cli rescan --force
```
You can also specify start and stop heights
```bash
./bitcoin-s-cli rescan --start <start height> --stop <stop height>
```
By default, if you do not set the start height, the rescan will begin at your wallet's creation time.
If you wish to ignore this and start from genesis use the `ignorecreationtime` flag
```bash
./bitcoin-s-cli rescan --ignorecreationtime
```
### Code Example
You can rescan your wallet with filters with [`WalletApi.rescanNeutrinoWallet()`](https://github.com/bitcoin-s/bitcoin-s/blob/master/core/src/main/scala/org/bitcoins/core/api/wallet/NeutrinoWalletApi.scala#L77)
To run this example you need to make sure you have access to a bitcoind binary.
You can download this with bitcoin-s by doing `sbt downloadBitcoind`
```scala
//we need an actor system and app config to power this
implicit val system: ActorSystem = ActorSystem(s"wallet-rescan-example")
implicit val ec: ExecutionContext = system.dispatcher
implicit val appConfig: BitcoinSAppConfig = BitcoinSTestAppConfig.getNeutrinoTestConfig()
implicit val walletAppConfig: WalletAppConfig = appConfig.walletConf
val bip39PasswordOpt = None
//ok now let's spin up a bitcoind and a bitcoin-s wallet with funds in it
val walletWithBitcoindF = for {
bitcoind <- BitcoinSFixture.createBitcoindWithFunds()
walletWithBitcoind <- BitcoinSWalletTest.createWalletWithBitcoindCallbacks(bitcoind, bip39PasswordOpt)
} yield walletWithBitcoind
val walletF = walletWithBitcoindF.map(_.wallet)
val bitcoindF = walletWithBitcoindF.map(_.bitcoind)
//let's see what our initial wallet balance is
val initBalanceF = for {
w <- walletF
balance <- w.getBalance()
} yield {
println(s"Initial wallet balance=${balance}")
balance
}
//ok great! We have money in the wallet to start,
//now let's delete our internal tables that hold our utxos
//and addresses so that we end up with a 0 balance
val clearedWalletF = for {
w <- walletF
_ <- initBalanceF
clearedWallet <- w.clearAllUtxosAndAddresses()
zeroBalance <- clearedWallet.getBalance()
} yield {
println(s"Balance after clearing utxos: ${zeroBalance}")
clearedWallet
}
//we need to pick how many addresses we want to generate off of our keychain
//when doing a rescan, this means we are generating 100 addrsses
//and then looking for matches. If we find a match, we generate _another_
//100 fresh addresses and search those. We keep doing this until we find
//100 addresses that do not contain a match.
val addrBatchSize = 100
//ok now that we have a cleared wallet, we need to rescan and find our fudns again!
val rescannedBalanceF = for {
w <- clearedWalletF
_ <- w.fullRescanNeutrinoWallet(addrBatchSize)
balanceAfterRescan <- w.getBalance()
} yield {
println(s"Wallet balance after rescan: ${balanceAfterRescan}")
()
}
//cleanup
val cleanupF = for {
_ <- rescannedBalanceF
walletWithBitcoind <- walletWithBitcoindF
_ <- BitcoinSWalletTest.destroyWalletWithBitcoind(walletWithBitcoind)
} yield ()
Await.result(cleanupF, 60.seconds)
```

View file

@ -0,0 +1,147 @@
---
title: Wallet
id: version-0.6.0-wallet
original_id: wallet
---
## Bitcoin-s wallet
Bitcoin-s comes bundled with a rudimentary Bitcoin wallet. This wallet
is capable of managing private keys, generating addresses, constructing
and signing transactions, among other things. It is BIP32/BIP44/BIP49/BIP84
compatible.
This wallet is currently only released as a library, and not as a binary.
This is because it (nor the documentation) is not deemed production
ready. Use at your own risk, and without too much money depending on it.
### How is the bitcoin-s wallet implemented
The bitcoin-s wallet is a scalable way for individuals up to large bitcoin exchanges to safely and securely store their bitcoin in a scalable way.
All key interactions are delegated to the [key-manager](../key-manager/key-manager.md) which is a minimal dependency library to store and use key material.
By default, we store the encrypted root key in `$HOME/.bitcoin-s/seeds/encrypted-bitcoin-s-seed.json`. This is the seed that is used for each of the wallets on each bitcoin network.
Multiple wallet seeds can be saved using the `bitcoin-s.wallet.walletName` config option.
You can read more in the [key manager docs](../key-manager/server-key-manager.md).
The wallet itself is used to manage the utxo life cycle, create transactions, and update wallet balances to show how much money you have the on a bitcoin network.
We use [slick](https://scala-slick.org/doc/3.3.1/) as middleware to support different database types. Depending on your use case, you can use something as simple as sqlite, or something much more scalable like postgres.
### Example
This guide shows how to create a Bitcoin-s wallet and then
peer it with a `bitcoind` instance that relays
information about what is happening on the blockchain
through the P2P network.
This is useful if you want more flexible signing procedures in
the JVM ecosystem and more granular control over your
UTXOs with popular database like Postgres, SQLite, etc.
This code snippet you have a running `bitcoind` instance, locally
on regtest.
```scala
implicit val ec = scala.concurrent.ExecutionContext.global
val config = ConfigFactory.parseString {
"""
| bitcoin-s {
| network = regtest
| }
""".stripMargin
}
val datadir = Files.createTempDirectory("bitcoin-s-wallet")
implicit val walletConfig = WalletAppConfig(datadir, config)
// we also need to store chain state for syncing purposes
implicit val chainConfig = ChainAppConfig(datadir, config)
// when this future completes, we have
// created the necessary directories and
// databases for managing both chain state
// and wallet state
val configF: Future[Unit] = for {
_ <- walletConfig.start()
_ <- chainConfig.start()
} yield ()
val bitcoindInstance = BitcoindInstance.fromDatadir()
val bitcoind = BitcoindRpcClient(bitcoindInstance)
// when this future completes, we have
// synced our chain handler to our bitcoind
// peer
val syncF: Future[ChainApi] = configF.flatMap { _ =>
val getBestBlockHashFunc = { () =>
bitcoind.getBestBlockHash
}
val getBlockHeaderFunc = { hash: DoubleSha256DigestBE =>
bitcoind.getBlockHeader(hash).map(_.blockHeader)
}
val blockHeaderDAO = BlockHeaderDAO()
val compactFilterHeaderDAO = CompactFilterHeaderDAO()
val compactFilterDAO = CompactFilterDAO()
val chainHandler = ChainHandler(
blockHeaderDAO,
compactFilterHeaderDAO,
compactFilterDAO,
blockFilterCheckpoints = Map.empty)
ChainSync.sync(chainHandler, getBlockHeaderFunc, getBestBlockHashFunc)
}
//initialize our key manager, where we store our keys
val aesPasswordOpt = Some(AesPassword.fromString("password"))
//you can add a password here if you want
//val bip39PasswordOpt = Some("my-password-here")
val bip39PasswordOpt = None
val keyManager = BIP39KeyManager.initialize(aesPasswordOpt, walletConfig.kmParams, bip39PasswordOpt).getOrElse {
throw new RuntimeException(s"Failed to initalize key manager")
}
// once this future completes, we have a initialized
// wallet
val wallet = Wallet(keyManager, new NodeApi {
override def broadcastTransactions(txs: Vector[Transaction]): Future[Unit] = Future.successful(())
override def downloadBlocks(blockHashes: Vector[DoubleSha256Digest]): Future[Unit] = Future.successful(())
}, chainApi, ConstantFeeRateProvider(SatoshisPerVirtualByte.one), creationTime = Instant.now)
val walletF: Future[WalletApi] = configF.flatMap { _ =>
Wallet.initialize(wallet,bip39PasswordOpt)
}
// when this future completes, ww have sent a transaction
// from bitcoind to the Bitcoin-S wallet
val transactionF: Future[(Transaction, Option[DoubleSha256DigestBE])] = for {
wallet <- walletF
address <- wallet.getNewAddress()
txid <- bitcoind.sendToAddress(address, 3.bitcoin)
transaction <- bitcoind.getRawTransaction(txid)
} yield (transaction.hex, transaction.blockhash)
// when this future completes, we have processed
// the transaction from bitcoind, and we have
// queried our balance for the current balance
val balanceF: Future[CurrencyUnit] = for {
wallet <- walletF
(tx, blockhash) <- transactionF
_ <- wallet.processTransaction(tx, blockhash)
balance <- wallet.getBalance()
} yield balance
balanceF.foreach { balance =>
println(s"Bitcoin-S wallet balance: $balance")
}
```

View file

@ -0,0 +1,92 @@
{
"version-0.6.0-docs": {
"Getting Started": [
"version-0.6.0-getting-started",
"version-0.6.0-bips"
],
"Getting Setup": [
"version-0.6.0-getting-setup"
],
"Applications": [
"version-0.6.0-applications/cli",
"version-0.6.0-applications/server",
"version-0.6.0-applications/gui",
"version-0.6.0-applications/server-systemd"
],
"Chain": [
"version-0.6.0-chain/chain",
"version-0.6.0-chain/filter-sync",
"version-0.6.0-chain/chain-query-api"
],
"Configuration": [
"version-0.6.0-config/configuration"
],
"Core Module": [
"version-0.6.0-core/core-intro",
"version-0.6.0-core/addresses",
"version-0.6.0-core/hd-keys",
"version-0.6.0-core/adding-spks",
"version-0.6.0-core/spending-info",
"version-0.6.0-core/psbts",
"version-0.6.0-core/dlc",
"version-0.6.0-core/txbuilder",
"version-0.6.0-core/lightning-network"
],
"Crypto Module": [
"version-0.6.0-crypto/crypto-intro",
"version-0.6.0-crypto/sign",
"version-0.6.0-crypto/adaptor-signatures"
],
"Fee Provider": [
"version-0.6.0-fee-provider/fee-provider"
],
"Key Manager": [
"version-0.6.0-key-manager/server-key-manager",
"version-0.6.0-key-manager/key-manager"
],
"Node": [
"version-0.6.0-node/node",
"version-0.6.0-node/node-api"
],
"Wallet": [
"version-0.6.0-wallet/wallet",
"version-0.6.0-wallet/wallet-callbacks",
"version-0.6.0-wallet/wallet-get-address",
"version-0.6.0-wallet/address-tagging",
"version-0.6.0-wallet/dlc",
"version-0.6.0-wallet/wallet-rescan",
"version-0.6.0-wallet/wallet-sync",
"version-0.6.0-wallet/wallet-rpc"
],
"RPC Clients": [
"version-0.6.0-rpc/rpc-clients-intro",
"version-0.6.0-rpc/rpc-eclair",
"version-0.6.0-rpc/rpc-bitcoind",
"version-0.6.0-rpc/lnd-rpc"
],
"Secp256k1": [
"version-0.6.0-secp256k1/secp256k1",
"version-0.6.0-secp256k1/jni-modify"
],
"Testkit": [
"version-0.6.0-testkit/testkit",
"version-0.6.0-testkit/testkit-core"
],
"DLC Oracle": [
"version-0.6.0-oracle/build-oracle-server",
"version-0.6.0-oracle/oracle-server",
"version-0.6.0-oracle/oracle-election-example",
"version-0.6.0-oracle/oracle-price-example"
],
"Oracle Explorer Client": [
"version-0.6.0-oracle-explorer-client/oracle-explorer-client"
],
"Contributing": [
"version-0.6.0-contributing",
"version-0.6.0-contributing-website"
],
"Security": [
"version-0.6.0-security"
]
}
}

View file

@ -1,4 +1,5 @@
[
"0.6.0",
"0.5.0",
"0.4.0",
"0.3.0",