mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-03-26 21:42:48 +01:00
Create v4 release for website (#2015)
This commit is contained in:
parent
c59b3baf01
commit
27b467a50d
45 changed files with 5810 additions and 0 deletions
|
@ -334,6 +334,135 @@
|
|||
},
|
||||
"version-0.3.0/wallet/version-0.3.0-wallet": {
|
||||
"title": "Wallet"
|
||||
},
|
||||
"version-v0.4/applications/version-v0.4-cli": {
|
||||
"title": "CLI"
|
||||
},
|
||||
"version-v0.4/applications/version-v0.4-configuration": {
|
||||
"title": "Application Configuration"
|
||||
},
|
||||
"version-v0.4/applications/version-v0.4-dlc": {
|
||||
"title": "Executing A DLC with Bitcoin-S"
|
||||
},
|
||||
"version-v0.4/applications/version-v0.4-gui": {
|
||||
"title": "GUI"
|
||||
},
|
||||
"version-v0.4/applications/version-v0.4-node": {
|
||||
"title": "Light Client"
|
||||
},
|
||||
"version-v0.4/applications/version-v0.4-server": {
|
||||
"title": "Application Server"
|
||||
},
|
||||
"version-v0.4/applications/version-v0.4-wallet": {
|
||||
"title": "Wallet"
|
||||
},
|
||||
"version-v0.4/chain/version-v0.4-chain-query-api": {
|
||||
"title": "Chain Query API"
|
||||
},
|
||||
"version-v0.4/chain/version-v0.4-chain": {
|
||||
"title": "Blockchain Verification"
|
||||
},
|
||||
"version-v0.4/chain/version-v0.4-filter-sync": {
|
||||
"title": "Syncing Blockfilters"
|
||||
},
|
||||
"version-v0.4/config/version-v0.4-configuration": {
|
||||
"title": "Application Configuration"
|
||||
},
|
||||
"version-v0.4/version-v0.4-contributing-website": {
|
||||
"title": "Contributing to the website"
|
||||
},
|
||||
"version-v0.4/version-v0.4-contributing": {
|
||||
"title": "Contributing"
|
||||
},
|
||||
"version-v0.4/core/version-v0.4-adding-spks": {
|
||||
"title": "Adding New Script Types"
|
||||
},
|
||||
"version-v0.4/core/version-v0.4-addresses": {
|
||||
"title": "Generating Addresses"
|
||||
},
|
||||
"version-v0.4/core/version-v0.4-core-intro": {
|
||||
"title": "Core Module"
|
||||
},
|
||||
"version-v0.4/core/version-v0.4-hd-keys": {
|
||||
"title": "HD Key Generation"
|
||||
},
|
||||
"version-v0.4/core/version-v0.4-lightning-network": {
|
||||
"title": "Lightning Network Data Types"
|
||||
},
|
||||
"version-v0.4/core/version-v0.4-psbts": {
|
||||
"title": "Partially Signed Bitcoin Transactions"
|
||||
},
|
||||
"version-v0.4/core/version-v0.4-sign": {
|
||||
"title": "Sign API"
|
||||
},
|
||||
"version-v0.4/core/version-v0.4-spending-info": {
|
||||
"title": "Signing Transactions"
|
||||
},
|
||||
"version-v0.4/core/version-v0.4-txbuilder": {
|
||||
"title": "TxBuilder Example"
|
||||
},
|
||||
"version-v0.4/crypto/version-v0.4-crypto-intro": {
|
||||
"title": "Crypto Module"
|
||||
},
|
||||
"version-v0.4/crypto/version-v0.4-sign": {
|
||||
"title": "Sign API"
|
||||
},
|
||||
"version-v0.4/fee-provider/version-v0.4-fee-provider": {
|
||||
"title": "Fee Provider"
|
||||
},
|
||||
"version-v0.4/version-v0.4-getting-setup": {
|
||||
"title": "Getting Bitcoin-S installed on your machine"
|
||||
},
|
||||
"version-v0.4/version-v0.4-getting-started": {
|
||||
"title": "Intro and Getting Started"
|
||||
},
|
||||
"version-v0.4/key-manager/version-v0.4-key-manager": {
|
||||
"title": "Key Manager"
|
||||
},
|
||||
"version-v0.4/node/version-v0.4-node-api": {
|
||||
"title": "Node API"
|
||||
},
|
||||
"version-v0.4/node/version-v0.4-node": {
|
||||
"title": "Light Client"
|
||||
},
|
||||
"version-v0.4/rpc/version-v0.4-rpc-eclair": {
|
||||
"title": "Eclair"
|
||||
},
|
||||
"version-v0.4/secp256k1/version-v0.4-jni-modify": {
|
||||
"title": "Adding to Secp256k1 JNI"
|
||||
},
|
||||
"version-v0.4/secp256k1/version-v0.4-secp256k1": {
|
||||
"title": "Secp256k1"
|
||||
},
|
||||
"version-v0.4/version-v0.4-security": {
|
||||
"title": "Security"
|
||||
},
|
||||
"version-v0.4/testkit/version-v0.4-testkit": {
|
||||
"title": "Testkit"
|
||||
},
|
||||
"version-v0.4/wallet/version-v0.4-address-tagging": {
|
||||
"title": "Address and UTXO tagging"
|
||||
},
|
||||
"version-v0.4/wallet/version-v0.4-chain-query-api": {
|
||||
"title": "Chain Query API"
|
||||
},
|
||||
"version-v0.4/wallet/version-v0.4-dlc": {
|
||||
"title": "Executing A DLC with Bitcoin-S"
|
||||
},
|
||||
"version-v0.4/wallet/version-v0.4-node-api": {
|
||||
"title": "Node API"
|
||||
},
|
||||
"version-v0.4/wallet/version-v0.4-wallet-callbacks": {
|
||||
"title": "Wallet Callbacks"
|
||||
},
|
||||
"version-v0.4/wallet/version-v0.4-wallet-get-address": {
|
||||
"title": "Wallet Get Address APIs"
|
||||
},
|
||||
"version-v0.4/wallet/version-v0.4-wallet-rescan": {
|
||||
"title": "Wallet Rescans"
|
||||
},
|
||||
"version-v0.4/wallet/version-v0.4-wallet": {
|
||||
"title": "Wallet"
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
|
|
39
website/versioned_docs/version-v0.4/applications/cli.md
Normal file
39
website/versioned_docs/version-v0.4/applications/cli.md
Normal file
|
@ -0,0 +1,39 @@
|
|||
---
|
||||
id: version-v0.4-cli
|
||||
title: CLI
|
||||
original_id: cli
|
||||
---
|
||||
|
||||
|
||||
## Bitcoin-s command line interface
|
||||
|
||||
The [cli](../../app/cli/) project is meant to be a bitcoin-s command line interface (cli).
|
||||
|
||||
### Building the command line interface
|
||||
|
||||
You must first have [bitcoin-s properly installed](../getting-setup) on your machine, after which you should be able to build the cli with
|
||||
```bashrc
|
||||
$ sbt cli/universal:stage
|
||||
```
|
||||
|
||||
After running that command you should find the executable here:
|
||||
|
||||
```bash
|
||||
bitcoin-s/app/cli/target/universal/stage/bin/bitcoin-s-cli
|
||||
```
|
||||
|
||||
### Executing commands
|
||||
You can ask the client for help with
|
||||
|
||||
```bash
|
||||
./app/cli/target/universal/stage/bin/bitcoin-s-cli --help
|
||||
Usage: bitcoin-s-cli [options] [<cmd>]
|
||||
|
||||
-n, --network <value> Select the active network.
|
||||
--debug Print debugging information
|
||||
-h, --help Display this help message and exit
|
||||
<cmd> The command and arguments to be executed. Try bitcoin-s-cli help for a list of all commands
|
||||
```
|
||||
|
||||
|
||||
Now you are are ready to start the server that the cli sends commands to. Take a look at our [server](server.md) documentation on how to build and start the server.
|
|
@ -0,0 +1,172 @@
|
|||
---
|
||||
id: version-v0.4-configuration
|
||||
title: Application Configuration
|
||||
original_id: 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`](../../db-commons/src/main/scala/org/bitcoins/db/AppConfig.scala).
|
||||
`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
|
||||
|
||||
// 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.
|
||||
|
||||
## Internal configuration
|
||||
|
||||
Database connections are also configured by using HOCON. This is done in
|
||||
[`db.conf`](../../db-commons/src/main/resources/db.conf). 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.initialize()`](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.initialize()`](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
|
||||
|
||||
logging {
|
||||
level = WARN # trace, debug, info, warn, error, off
|
||||
|
||||
# You can also tune specific module loggers.
|
||||
# They each take the same levels as above.
|
||||
# If they are commented out (as they are
|
||||
# by default), `logging.level` gets used
|
||||
# instead.
|
||||
# The available loggers are:
|
||||
|
||||
# incoming and outgoing P2P messages
|
||||
# p2p = info
|
||||
|
||||
# verification of block headers, merkle trees
|
||||
# chain-verification = info
|
||||
|
||||
# generation of addresses, signing of TXs
|
||||
# key-handling = info
|
||||
|
||||
# wallet operations not related to key management
|
||||
# wallet = info
|
||||
|
||||
# HTTP RPC server
|
||||
# http = info
|
||||
|
||||
# Database interactions
|
||||
# database = info
|
||||
|
||||
# whether or not to write to the log file
|
||||
disable-file = false
|
||||
|
||||
# whether or not to log to stdout
|
||||
disable-console = false
|
||||
}
|
||||
|
||||
node {
|
||||
mode = neutrino # neutrino, spv
|
||||
|
||||
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.
|
||||
}
|
||||
|
||||
chain {
|
||||
neutrino {
|
||||
filter-header-batch-size = 2000
|
||||
filter-batch-size = 100
|
||||
}
|
||||
}
|
||||
|
||||
# settings for wallet module
|
||||
wallet {
|
||||
defaultAccountType = legacy # legacy, segwit, nested-segwit
|
||||
|
||||
bloomFalsePositiveRate = 0.0001 # percentage
|
||||
|
||||
addressGapLimit = 20
|
||||
|
||||
discoveryBatchSize = 100
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
166
website/versioned_docs/version-v0.4/applications/dlc.md
Normal file
166
website/versioned_docs/version-v0.4/applications/dlc.md
Normal file
|
@ -0,0 +1,166 @@
|
|||
---
|
||||
id: version-v0.4-dlc
|
||||
title: Executing A DLC with Bitcoin-S
|
||||
original_id: dlc
|
||||
---
|
||||
|
||||
## Executing A Discreet Log Contract (DLC)
|
||||
|
||||
## Step 1: Get Bitcoin-S Setup
|
||||
|
||||
See the [setup document](../getting-setup).
|
||||
|
||||
Make sure to follow [Step 4](../getting-setup#step-4-optional-discreet-log-contract-branch) to checkout the `dlc` feature branch.
|
||||
|
||||
## Step 2: Agree On Contract Terms
|
||||
|
||||
Both parties must agree on all fields from the table below:
|
||||
|
||||
| Field Name | Format |
|
||||
| :------------: | :------------------------------------------------------: |
|
||||
| oracleInfo | OraclePubKeyHex ++ OracleRValueHex |
|
||||
| contractInfo | Hash1Hex ++ 8ByteValue1Hex ++ Hash2Hex ++ 8ByteValue2Hex |
|
||||
| collateral | NumInSatoshis |
|
||||
| locktime | LockTimeNum |
|
||||
| refundlocktime | LockTimeNum |
|
||||
| feerate | NumInSatoshisPerVByte |
|
||||
|
||||
Here is an example `oracleInfo` for public key `025acb434efb32bbf7ca7fd44b22e0f3f5570c6bc564e6059b03ba18c277054ac1` and R value `03f8758d7f03a65b67b90f62301a3554849bde6d00d50e965eb123398de9fd6ea7`:
|
||||
|
||||
```bashrc
|
||||
025acb434efb32bbf7ca7fd44b22e0f3f5570c6bc564e6059b03ba18c277054ac103f8758d7f03a65b67b90f62301a3554849bde6d00d50e965eb123398de9fd6ea7
|
||||
```
|
||||
|
||||
Here is an example `contractInfo` for hashes `c07803e32c12e100905e8d69fe38ae72f2e7a17eb7b8dc1a9bce134b0cbe920f` and `5c58e41254e7a117ee1db59874f2334facc1576c238c16d18767b47861f93f7c` with respective Satoshi denominated outcomes of `100000 sats` and `0 sats`:
|
||||
|
||||
```bashrc
|
||||
c07803e32c12e100905e8d69fe38ae72f2e7a17eb7b8dc1a9bce134b0cbe920fa0860100000000005c58e41254e7a117ee1db59874f2334facc1576c238c16d18767b47861f93f7c0000000000000000
|
||||
```
|
||||
|
||||
And finally, here are the oracle signatures for each hash in order in case you want to test with this contract:
|
||||
|
||||
```bashrc
|
||||
f8758d7f03a65b67b90f62301a3554849bde6d00d50e965eb123398de9fd6ea7fbbee821b7166028a6927282830c9452cfcf3c5716c57e43dd4069ca87625010
|
||||
```
|
||||
|
||||
```bashrc
|
||||
f8758d7f03a65b67b90f62301a3554849bde6d00d50e965eb123398de9fd6ea7af05f01f1ca852cf5454a7dc91cdad7903dc2e67ddb2b3bc9d61dabd8856aa6a
|
||||
```
|
||||
|
||||
Note: if you wish to setup your own oracle for testing, you can do so by pasting the following into the `sbt core/console`:
|
||||
|
||||
```scala
|
||||
import org.bitcoins.core.crypto._
|
||||
import org.bitcoins.core.util.CryptoUtil
|
||||
import scodec.bits.ByteVector
|
||||
import org.bitcoins.core.currency._
|
||||
|
||||
val privKey = ECPrivateKey.freshPrivateKey
|
||||
val pubKey = privKey.publicKey
|
||||
val nonce = SchnorrNonce.freshNonce
|
||||
val rValue = nonce.publicKey
|
||||
val winHash = CryptoUtil.sha256(ByteVector("WIN".getBytes)).flip
|
||||
val loseHash = CryptoUtil.sha256(ByteVector("LOSE".getBytes)).flip
|
||||
|
||||
(pubKey.bytes ++ rValue.bytes).toHex
|
||||
(winHash.bytes ++ Satoshis(100000).bytes ++ loseHash.bytes ++ Satoshis.zero.bytes).toHex
|
||||
Schnorr.signWithNonce(winHash.bytes, privKey, nonce).hex
|
||||
Schnorr.signWithNonce(loseHash.bytes, privKey, nonce).hex
|
||||
```
|
||||
|
||||
Where you can replace the messages `WIN` and `LOSE` to have the oracle sign any two messages, and replace `Satoshis(100000)` and `Satoshis.zero` to change the outcomes.
|
||||
|
||||
## Step 3: Setup The DLC
|
||||
|
||||
### Creating The Offer
|
||||
|
||||
Once these terms are agreed to, either party can call on `createdlcoffer` with flags for each of the fields in the table above. For example:
|
||||
|
||||
```bashrc
|
||||
./app/cli/target/graalvm-native-image/bitcoin-s-cli createdlcoffer --oracleInfo 025acb434efb32bbf7ca7fd44b22e0f3f5570c6bc564e6059b03ba18c277054ac103f8758d7f03a65b67b90f62301a3554849bde6d00d50e965eb123398de9fd6ea7 --contractInfo c07803e32c12e100905e8d69fe38ae72f2e7a17eb7b8dc1a9bce134b0cbe920fa0860100000000005c58e41254e7a117ee1db59874f2334facc1576c238c16d18767b47861f93f7c0000000000000000 --collateral 40000 --locktime 1666720 --refundlocktime 1666730 --feerate 3
|
||||
```
|
||||
|
||||
This will return a nice pretty-printed JSON offer. To get an offer that can be sent to the counter-party, add the `--escaped` flag to the end of this command.
|
||||
|
||||
### Accepting The Offer
|
||||
|
||||
Upon receiving a DLC Offer from your counter-party, the following command will create the serialized accept message:
|
||||
|
||||
```bashrc
|
||||
./app/cli/target/graalvm-native-image/bitcoin-s-cli acceptdlcoffer --offer [offer] --escaped
|
||||
```
|
||||
|
||||
### Signing The DLC
|
||||
|
||||
Upon receiving a DLC Accept message from your counter-party, the following command will generate all of your signatures for this DLC:
|
||||
|
||||
```bashrc
|
||||
./app/cli/target/graalvm-native-image/bitcoin-s-cli signdlc --accept [accept] --escaped
|
||||
```
|
||||
|
||||
### Adding DLC Signatures To Your Database
|
||||
|
||||
Upon receiving a DLC Sign message from your counter-party, add their signatures to your database by:
|
||||
|
||||
```bashrc
|
||||
./app/cli/target/graalvm-native-image/bitcoin-s-cli adddlcsigs --sigs [sign]
|
||||
```
|
||||
|
||||
You are now fully setup and can generate the fully signed funding transaction for broadcast using
|
||||
|
||||
```bashrc
|
||||
./app/cli/target/graalvm-native-image/bitcoin-s-cli getdlcfundingtx --eventid [eventid]
|
||||
```
|
||||
|
||||
where the `eventid` is in all but the messages other than the DLC Offer message, and is also returned by the `adddlcsigs` command.
|
||||
|
||||
## Step 4: Executing the DLC
|
||||
|
||||
### Mutual Close
|
||||
|
||||
Upon receiving an oracle signature, either party can initiate a mutual close with
|
||||
|
||||
```bashrc
|
||||
./app/cli/target/graalvm-native-image/bitcoin-s-cli initdlcmutualclose --eventid [eventid] --oraclesig [sig] --escaped
|
||||
```
|
||||
|
||||
And if you receive one of these CloseSig messages from your counter-party, you can generate the fully-signed mutual closing transaction with
|
||||
|
||||
```bashrc
|
||||
./app/cli/target/graalvm-native-image/bitcoin-s-cli acceptdlcmutualclose --closesig [closesig]
|
||||
```
|
||||
|
||||
### Unilateral Close
|
||||
|
||||
If your counter-party is unresponsive upon receiving an `initdlcmutualclose` message, or is unreachable, you can execute the DLC unilaterally with
|
||||
|
||||
```bashrc
|
||||
./app/cli/target/graalvm-native-image/bitcoin-s-cli executedlcforceclose --eventid [eventid] --oraclesig [sig]
|
||||
```
|
||||
|
||||
which will return two fully-signed transactions in the case that you are owed any funds, and one fully-signed transaction in the case that you aren't. The first transaction returned should be the fully signed Contract Execution Transaction, and the second transaction, if existing, should be the fully-signed sweep transaction which claims your funds on the CET.
|
||||
|
||||
#### Claiming Remote Funds When Counter-Party Unilaterally Closes
|
||||
|
||||
If your counter-party has broadcasted a CET to the network, you can claim the funds on the `ToRemoteOutput` using
|
||||
|
||||
```bashrc
|
||||
./app/cli/target/graalvm-native-image/bitcoin-s-cli claimdlcremotefunds --eventid [eventid] --forceclosetx [cet]
|
||||
```
|
||||
|
||||
#### Claiming Penalty Funds
|
||||
|
||||
If your counter-party has broadcasted a CET to the network, and does not sweep their ToLocal funds in `5` blocks, you can claim the funds on the `ToLocalOutput` using
|
||||
|
||||
```bashrc
|
||||
./app/cli/target/graalvm-native-image/bitcoin-s-cli claimdlcpenaltyfunds --eventid [eventid] --forceclosetx [cet]
|
||||
```
|
||||
|
||||
### Refund
|
||||
|
||||
If the `refundlocktime` for the DLC has been reached, you can get the fully-signed refund transaction with
|
||||
|
||||
```bashrc
|
||||
./app/cli/target/graalvm-native-image/bitcoin-s-cli executedlcrefund --eventid [eventid]
|
||||
```
|
||||
|
72
website/versioned_docs/version-v0.4/applications/gui.md
Normal file
72
website/versioned_docs/version-v0.4/applications/gui.md
Normal file
|
@ -0,0 +1,72 @@
|
|||
---
|
||||
id: version-v0.4-gui
|
||||
title: GUI
|
||||
original_id: gui
|
||||
---
|
||||
|
||||
|
||||
## Bitcoin-S Graphical User Interface
|
||||
There is now a GUI built using [scalafx](https://www.scalafx.org/) to allow users who do not wish to use the [command line interface](cli.md) to interact with a Bitcoin-S wallet.
|
||||
This GUI is currently very minimal and looks something like this:
|
||||
|
||||

|
||||
|
||||
At the time of writing this document, creating addresses and sending Bitcoin are the only supported wallet functionalities in the wallet's RPC API. The expansion of this interface will enable a more robust GUI and this issue is being tracked [here](https://github.com/bitcoin-s/bitcoin-s/issues/1284).
|
||||
|
||||
### Running the GUI
|
||||
|
||||
> It is required to use java11 to run the GUI
|
||||
|
||||
There are two ways to run the GUI
|
||||
|
||||
#### Bundled version (easy)
|
||||
|
||||
This command will start both the [bitcoin-s server](server.md) and the GUI at the same time
|
||||
|
||||
```bashrc
|
||||
sbt bundle/run
|
||||
```
|
||||
|
||||
#### Standalone version (advanced)
|
||||
|
||||
The GUI will only function properly if it can connect to the [bitcoin-s server](server.md) which must be
|
||||
running in the background before running the GUI.
|
||||
|
||||
To run the gui, simply execute
|
||||
|
||||
```bashrc
|
||||
sbt gui/run
|
||||
```
|
||||
|
||||
The gui can be built into an executable using the [sbt native packager](https://www.scala-sbt.org/sbt-native-packager/).
|
||||
|
||||
### ScalaFX
|
||||
|
||||
[ScalaFX](https://www.scalafx.org/) is a scala library wrapping [JavaFX](https://openjfx.io/) which is itself a java interface to [GTK](https://www.gtk.org/) (which is useful to know when googling for answers as it is easier to search for answers in GTK or sometimes javafx and then translate them to scalafx).
|
||||
|
||||
ScalaFX itself requires the dependency
|
||||
|
||||
```scala
|
||||
"org.scalafx" %% "scalafx"
|
||||
```
|
||||
|
||||
but full use of the library requires also adding these dependencies:
|
||||
|
||||
```scala
|
||||
"org.openjfx" % "javafx-base"
|
||||
"org.openjfx" % "javafx-controls"
|
||||
"org.openjfx" % "javafx-fxml"
|
||||
"org.openjfx" % "javafx-graphics"
|
||||
"org.openjfx" % "javafx-media"
|
||||
"org.openjfx" % "javafx-swing"
|
||||
"org.openjfx" % "javafx-web"
|
||||
```
|
||||
|
||||
which for some reason must all be classified by the OS of the machine they are running on.
|
||||
|
||||
A GUI can now be built by creating an object which extends `scalafx.application.JFXApp` and setting the `stage` var to a `new JFXApp.PrimaryStage` whose `scene` is your `Node` (where `Node` is the super-class in this framework for all GUI elements).
|
||||
|
||||
#### ScalaFX Examples
|
||||
|
||||
Aside from looking at how the Bitcoin-S GUI is implemented, for more useful examples of ScalaFX, see [these tutorials](https://github.com/scalafx/ScalaFX-Tutorials) and specifically [this one](https://github.com/scalafx/ScalaFX-Tutorials/tree/master/slick-table) which I think is most illustrative.
|
||||
|
14
website/versioned_docs/version-v0.4/applications/node.md
Normal file
14
website/versioned_docs/version-v0.4/applications/node.md
Normal file
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
id: version-v0.4-node
|
||||
title: Light Client
|
||||
original_id: node
|
||||
---
|
||||
|
||||
Bitcoin-S comes bundled with a light client Bitcoin node. this client
|
||||
is capable of doing verification of some parts of the blockchain,
|
||||
and can act as a starting point for building Bitcoin applications
|
||||
that need to connect to the P2P network.
|
||||
|
||||
This node 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.
|
127
website/versioned_docs/version-v0.4/applications/server.md
Normal file
127
website/versioned_docs/version-v0.4/applications/server.md
Normal file
|
@ -0,0 +1,127 @@
|
|||
---
|
||||
id: version-v0.4-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
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
### 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)
|
||||
|
||||
### 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
|
||||
|
||||
#### 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
|
||||
- `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
|
||||
- `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
|
||||
- `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
|
||||
|
||||
#### 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
|
||||
- `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
|
151
website/versioned_docs/version-v0.4/applications/wallet.md
Normal file
151
website/versioned_docs/version-v0.4/applications/wallet.md
Normal file
|
@ -0,0 +1,151 @@
|
|||
---
|
||||
title: Wallet
|
||||
id: version-v0.4-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.md) which is a minimal dependecy library to store and use key material.
|
||||
|
||||
By default, we store the encrypted root key in `$HOME/.bitcoin-s/encrypted-bitcoin-s-seed.json`. This is the seed that is used for each of the wallets on each bitcoin network.
|
||||
|
||||
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.initialize()
|
||||
_ <- chainConfig.initialize()
|
||||
} 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,
|
||||
blockchains = Vector.empty,
|
||||
blockFilterCheckpoints = Map.empty)
|
||||
|
||||
ChainSync.sync(chainHandler, getBlockHeaderFunc, getBestBlockHashFunc)
|
||||
}
|
||||
|
||||
//initialize our key manager, where we store our keys
|
||||
//you can add a password here if you want
|
||||
//val bip39PasswordOpt = Some("my-password-here")
|
||||
val bip39PasswordOpt = None
|
||||
val keyManager = BIP39KeyManager.initialize(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 downloadBlocks(blockHashes: Vector[DoubleSha256Digest]): Future[Unit] = Future.successful(())
|
||||
}, new ChainQueryApi {
|
||||
override def getBlockHeight(blockHash: DoubleSha256DigestBE): Future[Option[Int]] = Future.successful(None)
|
||||
override def getBestBlockHash(): Future[DoubleSha256DigestBE] = Future.successful(DoubleSha256DigestBE.empty)
|
||||
override def getNumberOfConfirmations(blockHashOpt: DoubleSha256DigestBE): Future[Option[Int]] = Future.successful(None)
|
||||
override def getFilterCount: Future[Int] = Future.successful(0)
|
||||
override def getHeightByBlockStamp(blockStamp: BlockStamp): Future[Int] = Future.successful(0)
|
||||
override def getFiltersBetweenHeights(startHeight: Int, endHeight: Int): Future[Vector[FilterResponse]] = Future.successful(Vector.empty)
|
||||
})
|
||||
val walletF: Future[LockedWalletApi] = 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")
|
||||
}
|
||||
```
|
180
website/versioned_docs/version-v0.4/chain/chain-query-api.md
Normal file
180
website/versioned_docs/version-v0.4/chain/chain-query-api.md
Normal file
|
@ -0,0 +1,180 @@
|
|||
---
|
||||
id: version-v0.4-chain-query-api
|
||||
title: Chain Query API
|
||||
original_id: chain-query-api
|
||||
---
|
||||
|
||||
|
||||
### ChainQueryAPI
|
||||
|
||||
The ChainQueryApi is how the wallet project stays aware of the current best chain.
|
||||
This allows the wallet for example to calculate the number of confirmations for a transaction,
|
||||
get the current chain tip, or even retrieve block filters for a given set of blocks.
|
||||
|
||||
Since this is an API it can be hooked up to the `chain` 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 ChainQueryApi supports are:
|
||||
|
||||
```scala
|
||||
trait ChainQueryApi {
|
||||
|
||||
/** Gets the height of the given block */
|
||||
def getBlockHeight(blockHash: DoubleSha256DigestBE): Future[Option[Int]]
|
||||
|
||||
/** Gets the hash of the block that is what we consider "best" */
|
||||
def getBestBlockHash(): Future[DoubleSha256DigestBE]
|
||||
|
||||
/** Gets number of confirmations for the given block hash*/
|
||||
def getNumberOfConfirmations(
|
||||
blockHashOpt: DoubleSha256DigestBE): Future[Option[Int]]
|
||||
|
||||
/** Gets the number of compact filters in the database */
|
||||
def getFilterCount: Future[Int]
|
||||
|
||||
/** Returns the block height of the given block stamp */
|
||||
def getHeightByBlockStamp(blockStamp: BlockStamp): Future[Int]
|
||||
|
||||
def getFiltersBetweenHeights(
|
||||
startHeight: Int,
|
||||
endHeight: Int): Future[Vector[FilterResponse]]
|
||||
}
|
||||
```
|
||||
|
||||
## Chain query with bitcoind
|
||||
|
||||
As an example, we will show you how to use the `ChainQueryApi` and bitcoind to query chain data.
|
||||
|
||||
```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 nodeApi = BitcoinSWalletTest.MockNodeApi
|
||||
|
||||
// Create our key manager
|
||||
val keyManagerE = BIP39KeyManager.initialize(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 chain api receives a transaction, block, or
|
||||
// a block filter, the returned NodeCallbacks will contain the necessary items to initialize the callbacks
|
||||
def createCallbacks(
|
||||
processTransaction: Transaction => Future[Unit],
|
||||
processCompactFilters: (Vector[(DoubleSha256Digest, GolombFilter)]) => Future[Unit],
|
||||
processBlock: Block => Future[Unit]): NodeCallbacks = {
|
||||
lazy val onTx: OnTxReceived = { tx =>
|
||||
processTransaction(tx)
|
||||
}
|
||||
lazy val onCompactFilters: OnCompactFiltersReceived = {
|
||||
blockFilters =>
|
||||
processCompactFilters(blockFilters)
|
||||
}
|
||||
lazy val onBlock: OnBlockReceived = { block =>
|
||||
processBlock(block)
|
||||
}
|
||||
NodeCallbacks(onTxReceived = Vector(onTx),
|
||||
onBlockReceived = Vector(onBlock),
|
||||
onCompactFiltersReceived = Vector(onCompactFilters))
|
||||
}
|
||||
|
||||
// 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 exampleProcessTx = (tx: Transaction) =>
|
||||
Future.successful(println(s"Received tx: ${tx.txIdBE}"))
|
||||
|
||||
val exampleProcessBlock = (block: Block) =>
|
||||
Future.successful(println(s"Received block: ${block.blockHeader.hashBE}"))
|
||||
|
||||
val exampleProcessFilters =
|
||||
(filters: Vector[(DoubleSha256Digest, GolombFilter)]) =>
|
||||
Future.successful(println(s"Received filter: ${filters.head._1.flip.hex} ${filters.head._2.hash.flip.hex}"))
|
||||
|
||||
val exampleCallbacks =
|
||||
createCallbacks(exampleProcessTx, exampleProcessFilters, exampleProcessBlock)
|
||||
|
||||
// Here is where we are defining our actual chain api, Ideally this could be it's own class
|
||||
// but for the examples sake we will keep it small.
|
||||
val chainApi = new ChainQueryApi {
|
||||
|
||||
override def epochSecondToBlockHeight(time: Long): Future[Int] =
|
||||
Future.successful(0)
|
||||
|
||||
/** Gets the height of the given block */
|
||||
override def getBlockHeight(
|
||||
blockHash: DoubleSha256DigestBE): Future[Option[Int]] = {
|
||||
bitcoind.getBlock(blockHash).map(block => Some(block.height))
|
||||
}
|
||||
|
||||
/** Gets the hash of the block that is what we consider "best" */
|
||||
override def getBestBlockHash(): Future[DoubleSha256DigestBE] = {
|
||||
bitcoind.getBestBlockHash
|
||||
}
|
||||
|
||||
/** Gets number of confirmations for the given block hash */
|
||||
override def getNumberOfConfirmations(
|
||||
blockHash: DoubleSha256DigestBE): Future[Option[Int]] = {
|
||||
for {
|
||||
tip <- bitcoind.getBlockCount
|
||||
block <- bitcoind.getBlock(blockHash)
|
||||
} yield {
|
||||
Some(tip - block.height + 1)
|
||||
}
|
||||
}
|
||||
|
||||
/** Gets the number of compact filters in the database */
|
||||
override def getFilterCount: Future[Int] = {
|
||||
// since bitcoind should have the filter for
|
||||
// every block we can just return the block height
|
||||
bitcoind.getBlockCount
|
||||
}
|
||||
|
||||
/** Returns the block height of the given block stamp */
|
||||
override def getHeightByBlockStamp(blockStamp: BlockStamp): Future[Int] = {
|
||||
blockStamp match {
|
||||
case blockHeight: BlockStamp.BlockHeight =>
|
||||
Future.successful(blockHeight.height)
|
||||
case blockHash: BlockStamp.BlockHash =>
|
||||
getBlockHeight(blockHash.hash).map(_.get)
|
||||
case blockTime: BlockStamp.BlockTime =>
|
||||
Future.failed(new RuntimeException(s"Not implemented: $blockTime"))
|
||||
}
|
||||
}
|
||||
|
||||
override def getFiltersBetweenHeights(
|
||||
startHeight: Int,
|
||||
endHeight: Int): Future[Vector[FilterResponse]] = {
|
||||
val filterFs = startHeight
|
||||
.until(endHeight)
|
||||
.map { height =>
|
||||
for {
|
||||
hash <- bitcoind.getBlockHash(height)
|
||||
filter <- bitcoind.getBlockFilter(hash, FilterType.Basic)
|
||||
} yield {
|
||||
FilterResponse(filter.filter, hash, height)
|
||||
}
|
||||
}
|
||||
.toVector
|
||||
|
||||
Future.sequence(filterFs)
|
||||
}
|
||||
}
|
||||
|
||||
// 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 one of the events we can run
|
||||
wallet.chainQueryApi.getFiltersBetweenHeights(100, 150)
|
||||
```
|
78
website/versioned_docs/version-v0.4/chain/chain.md
Normal file
78
website/versioned_docs/version-v0.4/chain/chain.md
Normal file
|
@ -0,0 +1,78 @@
|
|||
---
|
||||
title: Blockchain Verification
|
||||
id: version-v0.4-chain
|
||||
original_id: chain
|
||||
---
|
||||
|
||||
Bitcoin-S comes bundled with a rudimentary blockchain verification
|
||||
module. This module 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.
|
||||
|
||||
## Syncing and verifying block headers
|
||||
|
||||
Using the `chain` module of Bitcoin-S it's possible to
|
||||
sync and verify block headers from the Bitcoin blockchain. In this document
|
||||
we demonstrate how to do this, while persisting it to disk. We should be
|
||||
able to read this chain on subsequent runs, assuming we are connected
|
||||
to the same `bitcoind` instance.
|
||||
|
||||
|
||||
```scala
|
||||
implicit val ec = ExecutionContext.global
|
||||
|
||||
// We are assuming that a `bitcoind` regtest node is running the background.
|
||||
// You can see our `bitcoind` guides to see how to connect
|
||||
// to a local or remote `bitcoind` node.
|
||||
|
||||
val bitcoindInstance = BitcoindInstance.fromDatadir()
|
||||
val rpcCli = BitcoindRpcClient(bitcoindInstance)
|
||||
|
||||
// Next, we need to create a way to monitor the chain:
|
||||
|
||||
val getBestBlockHash = SyncUtil.getBestBlockHashFunc(rpcCli)
|
||||
|
||||
val getBlockHeader = SyncUtil.getBlockHeaderFunc(rpcCli)
|
||||
|
||||
// set a data directory
|
||||
val datadir = Files.createTempDirectory("bitcoin-s-test")
|
||||
|
||||
// set the current network to regtest
|
||||
import com.typesafe.config.ConfigFactory
|
||||
val config = ConfigFactory.parseString {
|
||||
"""
|
||||
| bitcoin-s {
|
||||
| network = regtest
|
||||
| }
|
||||
|""".stripMargin
|
||||
}
|
||||
|
||||
implicit val chainConfig = ChainAppConfig(datadir, config)
|
||||
|
||||
// Initialize the needed database tables if they don't exist:
|
||||
val chainProjectInitF = chainConfig.start()
|
||||
val blockHeaderDAO = BlockHeaderDAO()
|
||||
val compactFilterHeaderDAO = CompactFilterHeaderDAO()
|
||||
val compactFilterDAO = CompactFilterDAO()
|
||||
|
||||
|
||||
//initialize the chain handler from the database
|
||||
val chainHandlerF = ChainHandler.fromDatabase(blockHeaderDAO, compactFilterHeaderDAO, compactFilterDAO)
|
||||
|
||||
// Now, do the actual syncing:
|
||||
val syncedChainApiF = for {
|
||||
_ <- chainProjectInitF
|
||||
handler <- chainHandlerF
|
||||
synced <- ChainSync.sync(handler, getBlockHeader, getBestBlockHash)
|
||||
} yield synced
|
||||
|
||||
val syncResultF = syncedChainApiF.flatMap { chainApi =>
|
||||
chainApi.getBlockCount.map(count => println(s"chain api blockcount=${count}"))
|
||||
|
||||
rpcCli.getBlockCount.map(count => println(s"bitcoind blockcount=${count}"))
|
||||
}
|
||||
|
||||
syncResultF.onComplete { case result =>
|
||||
println(s"Sync result=${result}")
|
||||
}
|
||||
```
|
111
website/versioned_docs/version-v0.4/chain/filter-sync.md
Normal file
111
website/versioned_docs/version-v0.4/chain/filter-sync.md
Normal file
|
@ -0,0 +1,111 @@
|
|||
---
|
||||
title: Syncing Blockfilters
|
||||
id: version-v0.4-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.
|
||||
|
||||
#### 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(_.bitcoind)
|
||||
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.
|
280
website/versioned_docs/version-v0.4/config/configuration.md
Normal file
280
website/versioned_docs/version-v0.4/config/configuration.md
Normal file
|
@ -0,0 +1,280 @@
|
|||
---
|
||||
id: version-v0.4-configuration
|
||||
title: Application Configuration
|
||||
original_id: 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`
|
||||
|
||||
- `--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.
|
||||
|
||||
## Internal configuration
|
||||
|
||||
Database connections are also configured by using HOCON. This is done in
|
||||
[`db.conf`](https://github.com/bitcoin-s/bitcoin-s/blob/master/db-commons/src/main/resources/db.conf). 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
|
||||
|
||||
logging {
|
||||
# Ignore bitcoin-s logging config and use a logback config
|
||||
logback = false
|
||||
|
||||
level = WARN # trace, debug, info, warn, error, off
|
||||
|
||||
# You can also tune specific module loggers.
|
||||
# They each take the same levels as above.
|
||||
# If they are commented out (as they are
|
||||
# by default), `logging.level` gets used
|
||||
# instead.
|
||||
# The available loggers are:
|
||||
|
||||
# incoming and outgoing P2P messages
|
||||
# p2p = info
|
||||
|
||||
# verification of block headers, merkle trees
|
||||
# chain-verification = info
|
||||
|
||||
# generation of addresses, signing of TXs
|
||||
# key-handling = info
|
||||
|
||||
# wallet operations not related to key management
|
||||
# wallet = info
|
||||
|
||||
# HTTP RPC server
|
||||
# http = info
|
||||
|
||||
# Database interactions
|
||||
# database = info
|
||||
|
||||
# whether or not to write to the log file
|
||||
disable-file = false
|
||||
|
||||
# whether or not to log to stdout
|
||||
disable-console = false
|
||||
}
|
||||
|
||||
node {
|
||||
mode = neutrino # neutrino, spv
|
||||
|
||||
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.
|
||||
}
|
||||
|
||||
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 = 100
|
||||
}
|
||||
}
|
||||
|
||||
# settings for wallet module
|
||||
wallet {
|
||||
defaultAccountType = legacy # 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
|
||||
|
||||
}
|
||||
|
||||
server {
|
||||
# The port we bind our rpc server on
|
||||
rpcport = 9999
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
url = "jdbc:postgresql://localhost:5432/database"
|
||||
username = "user"
|
||||
password = "topsecret"
|
||||
numThreads = 5
|
||||
}
|
||||
}
|
||||
|
||||
chain.profile = ${bitcoin-s.common.profile}
|
||||
chain.db = ${bitcoin-s.common.db}
|
||||
|
||||
node.profile = ${bitcoin-s.common.profile}
|
||||
node.db = ${bitcoin-s.common.db}
|
||||
|
||||
wallet.profile = ${bitcoin-s.common.profile}
|
||||
wallet.db = ${bitcoin-s.common.db}
|
||||
}
|
||||
```
|
||||
|
||||
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
|
||||
url = "jdbc:postgresql://localhost:5432/chaindb"
|
||||
username = "user"
|
||||
password = "topsecret"
|
||||
}
|
||||
}
|
||||
wallet {
|
||||
profile = "slick.jdbc.PostgresProfile$"
|
||||
db {
|
||||
driver = org.postgresql.Driver
|
||||
url = "jdbc:postgresql://localhost:5432/walletdb"
|
||||
username = "user"
|
||||
password = "topsecret"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
106
website/versioned_docs/version-v0.4/contributing-website.md
Normal file
106
website/versioned_docs/version-v0.4/contributing-website.md
Normal file
|
@ -0,0 +1,106 @@
|
|||
---
|
||||
id: version-v0.4-contributing-website
|
||||
title: Contributing to the website
|
||||
original_id: contributing-website
|
||||
---
|
||||
|
||||
This website is built using [Docusaurus](https://docusaurus.io/).
|
||||
|
||||
For simple changes to the documentation, click on the `Edit` button at the top
|
||||
of each page and submit those changes directly on GitHub.
|
||||
|
||||
## Scaladoc
|
||||
|
||||
One of the goals of Bitcoin-S is having useful and well-formatted Scaladoc comments on classes,
|
||||
objects and functions. Here are some useful resources on how to properly format your Scaladoc comments:
|
||||
|
||||
- [Scaladoc for library authors](https://docs.scala-lang.org/overviews/scaladoc/for-library-authors.html)
|
||||
- [Guidelines](https://docs.scala-lang.org/style/scaladoc.html) used by the official Scala language Scaladoc
|
||||
- [Alvin Alexander guide](https://alvinalexander.com/scala/how-to-generate-scala-documentation-scaladoc-command-examples) on writing Scaladoc
|
||||
|
||||
To generate Scaladocs:
|
||||
|
||||
```bash
|
||||
$ sbt
|
||||
> unidoc
|
||||
> docs/mdoc
|
||||
```
|
||||
|
||||
This gets placed in `website/static/api`. When viewing the Docusaurus site the generated Scaladocs
|
||||
appear under the API tab in the header bar,
|
||||
or in the "API reference" link in the footer.
|
||||
|
||||
## Running the site locally
|
||||
|
||||
For running the website locally, you'll need:
|
||||
|
||||
- `yarn` (https://yarnpkg.com/lang/en/docs/install-ci/)
|
||||
- `sbt` (https://www.scala-sbt.org/1.0/docs/Setup.html)
|
||||
|
||||
> In case you want to contribute substantial structural changes to the website,
|
||||
> we suggest to read
|
||||
> [Docusaurus' documentation](https://docusaurus.io/docs/en/installation.html)
|
||||
> first.
|
||||
|
||||
You can now build and launch the website using
|
||||
these commands:
|
||||
|
||||
```sh
|
||||
cd website
|
||||
yarn install # only the first time, to install the dependencies
|
||||
yarn start
|
||||
```
|
||||
|
||||
In a separate shell:
|
||||
|
||||
```bash
|
||||
$ bloop run docs -- --watch
|
||||
```
|
||||
|
||||
The above commands compiles our Mdoc Markdown files every time you change
|
||||
them, and puts them in the correct directory for Docusaurus to find them.
|
||||
|
||||
Now visit http://localhost:3000/ and you should see a local version of
|
||||
the website.
|
||||
|
||||
## Adding a new page
|
||||
|
||||
Whenever you add a new markdown page to the documentation, you'll have to
|
||||
manually include it in the side menu.
|
||||
|
||||
You can do this by editing the `website/sidebars.json` file. The name to use is
|
||||
the `id` specified in the page metadata (see the existing pages for an example).
|
||||
|
||||
|
||||
## Creating a new version
|
||||
|
||||
You can create a new version of the site by running this in the `website/` directory.
|
||||
|
||||
```bashrc
|
||||
yarn run version [MY-VERSION-HERE]
|
||||
```
|
||||
|
||||
You also need to manually run
|
||||
```bashrc
|
||||
git add versioned_docs/version-[MY-VERSION-HERE]/ versioned_sidebars/version-[MY-VERSION-HERE]-sidebars.json
|
||||
```
|
||||
|
||||
## Publishing the site
|
||||
|
||||
```bash
|
||||
$ sbt
|
||||
> docs/publishWebsite
|
||||
```
|
||||
|
||||
This command first generates Scaladocs, then invokes
|
||||
`docs/docusaurusPublishGhPages`, which in turn compile our mdoc
|
||||
files, build the site and push them to GH pages.
|
||||
|
||||
Before running those commands, you might have to change a few constants in
|
||||
`siteConfig.js`. These are specifed in the comments of that file.
|
||||
|
||||
### CI
|
||||
|
||||
Bitcoin-S uses Travis to run tests and deploy library and website builds. Generally
|
||||
speaking CI has to pass for a PR to get merged. If you make documentation/website only
|
||||
changes, you can start your PR title with `Docs:`. This skips running tests on CI.
|
267
website/versioned_docs/version-v0.4/contributing.md
Normal file
267
website/versioned_docs/version-v0.4/contributing.md
Normal file
|
@ -0,0 +1,267 @@
|
|||
---
|
||||
id: version-v0.4-contributing
|
||||
title: Contributing
|
||||
original_id: contributing
|
||||
---
|
||||
|
||||
Bitcoin-S is an open source project where anyone is welcome to contribute. All contributions are encouraged and appreciated, whether that is code, testing, documentation or something else entirely.
|
||||
|
||||
## Communication Channels
|
||||
|
||||
It's possible to communicate with other developers through a variety of communication channels:
|
||||
|
||||
- [Suredbits Slack](https://join.slack.com/t/suredbits/shared_invite/enQtNDEyMjY3MTg1MTg3LTYyYjkwOGUzMDQ4NDAwZjE1M2I3MmQyNWNlZjNlYjg4OGRjYTRjNWUwNjRjNjg4Y2NjZjAxYjU1N2JjMTU1YWM) - Suredbits is a company monetizing APIs through the Lightning Network. Suredbits doesn't own Bitcoin-S, but the Suredbits CEO Chris Stewart is the maintainer of this library. There's a separate Bitcoin-S channel on their Slack, this is probably the easiest way of getting in touch with someone working on this project.
|
||||
- [Bitcoin-S Gitter](https://gitter.im/bitcoin-s-core/)
|
||||
- [#bitcoin-scala](https://webchat.freenode.net/?channels=bitcoin-scala) on IRC Freenode
|
||||
|
||||
## Working on Bitcoin-S applications
|
||||
|
||||
Bitcoin-S includes a couple of applications that can be run as standalone executables.
|
||||
This includes the node, wallet and (partial) blockchain verification modules, as well
|
||||
as the server that bundles these three together and the CLI used to communicate with
|
||||
the server. These applications are configured with HOCON files. The file
|
||||
[`reference.conf`](https://github.com/bitcoin-s/bitcoin-s/blob/master/testkit/src/main/resources/reference.conf)
|
||||
is the basis configuration file, and every option read by Bitcoin-S should be present in
|
||||
this file. This means that you can copy sections from this file and edit them, to tune
|
||||
how the application runs on your machine.
|
||||
|
||||
One example of things you can tune is logging levels. Let's say you wanted the logback config
|
||||
ignored, general logging to happen at the `WARN` level, and the P2P message handling to be logged at `DEBUG`.
|
||||
Your configuration file would then look like:
|
||||
|
||||
```conf
|
||||
bitcoins-s {
|
||||
logging {
|
||||
logback = false
|
||||
|
||||
level = warn
|
||||
|
||||
p2p = debug
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Running the applications
|
||||
|
||||
When running the application's configuration placed in `bitcoin-s.conf` in the current
|
||||
data directory gets picked up. For linux this is by default `$HOME/.bitcoin-s/`, so the
|
||||
file you should edit would be `$HOME/.bitcoin-s/bitcoin-s.conf`.
|
||||
|
||||
### Running tests for the applications
|
||||
|
||||
You can place a `logback-test.xml` file in the `src/test/resources/` directory in the same project that tests are being run in.
|
||||
|
||||
If the test suite depends on `testkit`, you can modify [`reference.conf`](https://github.com/bitcoin-s/bitcoin-s/blob/master/testkit/src/main/resources/reference.conf)
|
||||
that is built into the testkit to control logging.
|
||||
|
||||
## Logging when working on Bitcoin-S tests
|
||||
|
||||
When working on various parts of Bitcoin-S the need to log what's going on arises
|
||||
pretty quickly. There are two ways of doing this:
|
||||
|
||||
1. Using the way described in the section above, "Working on Bitcoin-S applications".
|
||||
You could either use traits (like `HTTPLogger` or `P2PLogger`) that exposes a
|
||||
field `logger`, or acquire the logger directly through the trait's companion
|
||||
object.
|
||||
2. Use the standard `BitcoinSLogger`, which is also available as both a trait and
|
||||
a companion object with a field you can access (`BitcoinSLogger.logger`). Note
|
||||
that by default all logging from this logger is turned off in tests, to make
|
||||
output less noisy. You can tune this by changing the level found in
|
||||
`core-test/src/test/resources/logback-test.xml`.
|
||||
|
||||
### Akka logging
|
||||
|
||||
The test logging for akka is controlled by the [`reference.conf`](https://github.com/bitcoin-s/bitcoin-s/blob/master/testkit/src/main/resources/reference.conf) file inside of testkit.
|
||||
|
||||
This allows you to debug what is happening in our actors inside of bitcoin-s easier. For examples of what you can enable for akka to log, please look at their [logging documentation](https://doc.akka.io/docs/akka/current/logging.html#auxiliary-logging-options)
|
||||
|
||||
The easiest thing to do to enable akka logging is to adjust the `loglevel` and `stdout-loglevel` from `OFF` to `DEBUG`.
|
||||
|
||||
If you want to enable this when you are running a bitcoin-s application, you will need to modify the [`reference.conf`](../app/server/src/main/resources/reference.conf) file
|
||||
|
||||
## Developer productivity
|
||||
|
||||
### sbt
|
||||
|
||||
The default scala build tool is [sbt](https://www.scala-sbt.org/).
|
||||
|
||||
For the basics of how sbt works see the [sbt guide](https://www.scala-sbt.org/1.x/docs/Getting-Started.html)
|
||||
|
||||
One helpful configuration is the env variable `SBT_OPTS` which allows you to pass jvm arguments for sbt.
|
||||
|
||||
### Bloop
|
||||
|
||||
If you're tired of waiting around for sbt all day, there's a new,
|
||||
cool kid on the block. It is called [Bloop](https://scalacenter.github.io/bloop/),
|
||||
and it makes compilations in general faster, and in particular
|
||||
incremental, small compilation units (which greatly help editor
|
||||
performance). Bloop is a server that runs in the background of
|
||||
your computer, and keeps several "hot" JVMs running at all
|
||||
times. These JVMs serve compilation requests. Because the JVMs
|
||||
are running in the background you avoid the startup lag, and you
|
||||
also get code that's already [JIT compiled](https://en.wikipedia.org/wiki/Just-in-time_compilation)
|
||||
for you.
|
||||
|
||||
The documentation on Bloops [site](https://scalacenter.github.io/bloop/) is good, but here is the highlights:
|
||||
|
||||
1. Install Bloop by doing step 1 & 2 in the [official guide](https://scalacenter.github.io/bloop/setup#universal)
|
||||
2. Enable the Bloop background daemon
|
||||
1. macOS:
|
||||
```bash
|
||||
$ brew services start bloop
|
||||
```
|
||||
2. Ubuntu:
|
||||
```bash
|
||||
$ systemctl --user enable $HOME/.bloop/systemd/bloop.service
|
||||
$ systemctl --user daemon-reload
|
||||
$ systemctl --user start bloop
|
||||
```
|
||||
3. Enable shell completion for the Bloop CLI
|
||||
1. Bash:
|
||||
```bash
|
||||
$ echo '. $HOME/.bloop/bash/bloop' >> $HOME/.bash_profile
|
||||
```
|
||||
2. Zsh:
|
||||
```bash
|
||||
$ echo 'autoload -U compinit' >> $HOME/.zshrc
|
||||
$ echo 'fpath=($HOME/.bloop/zsh $fpath)' >> $HOME/.bashrc
|
||||
$ echo 'compinit' >> $HOME/.bashrc
|
||||
```
|
||||
3. Fish:
|
||||
```bash
|
||||
$ ln -s $HOME/.bloop/fish/bloop.fish ~/.config/fish/completions/bloop.fish
|
||||
```
|
||||
4. Generate configuration files
|
||||
```bash
|
||||
$ sbt bloopInstall
|
||||
```
|
||||
5. Import Bitcoin-S into IntelliJ again, as a bsp (Build Server Protocol) project (instead of a sbt project). Make sure you're running on the most recent IntelliJ and Scala plugin. See [official docs](https://scalacenter.github.io/bloop/docs/ides/intellij) for details.
|
||||
6. _(Bonus step):_ Lightning fast recompilations on file save:
|
||||
```bash
|
||||
$ bloop compile --project <name of module your're working on> --watch
|
||||
```
|
||||
|
||||
Your editor should now be much faster and require less resources :tada:
|
||||
|
||||
## Testing
|
||||
|
||||
### Property based testing
|
||||
|
||||
This library aims to achieve high level of correctness via property based
|
||||
testing. At the simplest level, you can think of property based testing as
|
||||
specifying a invariant that must always hold true.
|
||||
[Here](https://github.com/bitcoin-s/bitcoin-s-core/blob/89fbf35d78046b7ed21fd93fec05bb57cba023bb/src/test/scala/org/bitcoins/core/protocol/transaction/TransactionSpec.scala#L13-L17)
|
||||
is an example of a property in the bitcoin-s-core test suite
|
||||
|
||||
```scala
|
||||
property("Serialization symmetry") =
|
||||
Prop.forAll(TransactionGenerators.transactions) { tx =>
|
||||
Transaction(tx.hex) == tx
|
||||
}
|
||||
```
|
||||
|
||||
What this property says is that for every transaction we can generate with
|
||||
[`TransactionGenerators.transactions`](/api/org/bitcoins/testkit/core/gen/TransactionGenerators$)
|
||||
we _must_ be able to serialize it to hex format, then deserialize it back
|
||||
to a transaction and get the original `tx` back.
|
||||
|
||||
A more complex example of property based testing is checking that a
|
||||
multisignature transaction was signed correctly (see
|
||||
[`TransactionSignatureCreatorSpec`](https://github.com/bitcoin-s/bitcoin-s/blob/master/core-test/src/test/scala/org/bitcoins/core/crypto/TransactionSignatureCreatorSpec.scala)
|
||||
line 29-34). First we generate a _supposedly_ validly signed multisig
|
||||
transaction with [`TransactionGenerators.signedMultiSigTransaction`](/api/org/bitcoins/testkit/core/gen/TransactionGenerators$)
|
||||
(line 102-108). These transactions have varying `m` of `n` requirements.
|
||||
An interesting corner case if when you have 0 of `n` signatures, which
|
||||
means no signature is required. Property based testing is really good at
|
||||
fleshing out these corner cases. We check to see if this transaction is
|
||||
valid by running it through our [`ScriptInterpreter`](/api/org/bitcoins/core/script/interpreter/ScriptInterpreter).
|
||||
If we have built our functionality correctly the `ScriptInterpreter` should
|
||||
always return [`ScriptOk`](/api/org/bitcoins/core/script/result/ScriptResult)
|
||||
indicating the script was valid.
|
||||
|
||||
```scala
|
||||
property("generate valid signatures for a multisignature transaction") =
|
||||
Prop.forAllNoShrink(TransactionGenerators.signedMultiSigTransaction) {
|
||||
case (txSignatureComponent: TxSigComponent, _) =>
|
||||
//run it through the interpreter
|
||||
val program = ScriptProgram(txSignatureComponent)
|
||||
val result = ScriptInterpreter.run(program)
|
||||
result == ScriptOk
|
||||
}
|
||||
```
|
||||
|
||||
### Running tests
|
||||
|
||||
To run the entire test suite all you need to do is run the following command:
|
||||
|
||||
> This takes a long time, and runs a lot of tests that require IO. It may hog your computer at times.
|
||||
|
||||
```scala
|
||||
$ sbt test
|
||||
[info] Elapsed time: 4 min 36.760 sec
|
||||
[info] ScalaCheck
|
||||
[info] Passed: Total 149, Failed 0, Errors 0, Passed 149
|
||||
[info] ScalaTest
|
||||
[info] Run completed in 4 minutes, 55 seconds.
|
||||
[info] Total number of tests run: 744
|
||||
[info] Suites: completed 97, aborted 0
|
||||
[info] Tests: succeeded 744, failed 0, canceled 0, ignored 0, pending 0
|
||||
[info] All tests passed.
|
||||
[info] Passed: Total 909, Failed 0, Errors 0, Passed 909
|
||||
[success] Total time: 297 s, completed Jul 20, 2017 10:34:16 AM
|
||||
```
|
||||
|
||||
To run a specific suite of tests you can specify the suite name in the following way
|
||||
|
||||
```scala
|
||||
$ sbt testOnly *ScriptInterpreterTest*
|
||||
[info] ScriptInterpreterTest:
|
||||
[info] ScriptInterpreter
|
||||
[info] - must evaluate all the scripts from the bitcoin core script_tests.json
|
||||
[info] Run completed in 8 seconds, 208 milliseconds.
|
||||
[info] Total number of tests run: 1
|
||||
[info] Suites: completed 1, aborted 0
|
||||
[info] Tests: succeeded 1, failed 0, canceled 0, ignored 0, pending 0
|
||||
[info] All tests passed.
|
||||
```
|
||||
|
||||
The command `sbt testQuick` can also be handy. It runs tests that either:
|
||||
|
||||
1. Failed previously
|
||||
2. Has not been run previously
|
||||
3. Either the test or one of its dependencies has been recompiled
|
||||
|
||||
For more information on `testQuick`, see the offical
|
||||
[sbt docs](https://www.scala-sbt.org/1.x/docs/Testing.html#testQuick).
|
||||
|
||||
### Coverage
|
||||
|
||||
To produce a report that quantifies how much of our code is covered by tests:
|
||||
|
||||
```bash
|
||||
sbt
|
||||
> coverage
|
||||
> coreTest/test
|
||||
> core/coverageReport
|
||||
```
|
||||
|
||||
This generates three different reports: Cobertura, XML and HTML formats.
|
||||
See the output of your sbt shell to find the location of them.
|
||||
Open up the HTML file in your browser. You'll now see code coverage
|
||||
of all files in `core` project.
|
||||
|
||||
### CI
|
||||
|
||||
Bitcoin-S uses Travis to run tests and deploy library and website builds. Generally
|
||||
speaking CI has to pass for a PR to get merged. If you make documentation/website only
|
||||
changes, you can start your PR title with `Docs:`. This skips running tests on CI.
|
||||
|
||||
#### My CI isn't running!
|
||||
Travis CI has a conservative policy when it comes to crypto projects. They do not
|
||||
allow new contributors to create new CI runs. They believe you are crypto mining.
|
||||
|
||||
You can detect if travis thinks you are abusing their resources by looking a [this link](https://travis-ci.org/bitcoin-s/bitcoin-s/requests).
|
||||
|
||||
If your PR says `Abuse Detected` next to it, that means you need to email travis saying you are just
|
||||
trying to contribute to bitcoin-s and are not attempting to mine cryptocurrency. For more information on travis users
|
||||
having this same problem see [this link](https://travis-ci.community/t/abuse-detected-for-my-prs-and-some-commits/6124).
|
608
website/versioned_docs/version-v0.4/core/adding-spks.md
Normal file
608
website/versioned_docs/version-v0.4/core/adding-spks.md
Normal file
|
@ -0,0 +1,608 @@
|
|||
---
|
||||
id: version-v0.4-adding-spks
|
||||
title: Adding New Script Types
|
||||
original_id: adding-spks
|
||||
---
|
||||
|
||||
|
||||
<!-- 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 ScriptPubKey Type](#adding-a-new-scriptpubkey-type)
|
||||
- [Step 0: Design Philosophy](#step-0-design-philosophy)
|
||||
- [Step 1: Create a New ScriptPubKey Trait](#step-1-create-a-new-scriptpubkey-trait)
|
||||
- [Step 2: Create Companion Object](#step-2-create-companion-object)
|
||||
- [Step 3: Add to Relevant fromAsm Methods](#step-3-add-to-relevant-fromasm-methods)
|
||||
- [Step 4: Create a ScriptSignature If Necessary](#step-4-create-a-scriptsignature-if-necessary)
|
||||
- [Step 5: Add to ScriptSignature.fromAsm If Applicable](#step-5-add-to-scriptsignaturefromasm-if-applicable)
|
||||
- [Step 6: Create Relevant BitcoinUTXOSpendingInfo](#step-6-create-relevant-bitcoinutxospendinginfo)
|
||||
- [Non-Nested Single-Key Spending Info](#non-nested-single-key-spending-info)
|
||||
- [Non-Nested Multi-Key Spending Info](#non-nested-multi-key-spending-info)
|
||||
- [Nested Spending Info](#nested-spending-info)
|
||||
- [Step 7: Add to Relevant Apply Methods](#step-7-add-to-relevant-apply-methods)
|
||||
- [Step 8: Create a Signer](#step-8-create-a-signer)
|
||||
- [Non-Nested Single-Key Spending Info](#non-nested-single-key-spending-info-1)
|
||||
- [Non-Nested Multi-Key Spending Info](#non-nested-multi-key-spending-info-1)
|
||||
- [Nested Spending Info](#nested-spending-info-1)
|
||||
- [Step 9: Add to BitcoinSigner.sign](#step-9-add-to-bitcoinsignersign)
|
||||
- [Step 10: Add to ScriptGenerators](#step-10-add-to-scriptgenerators)
|
||||
- [ScriptPubKey Generator](#scriptpubkey-generator)
|
||||
- [ScriptSignature Generator](#scriptsignature-generator)
|
||||
- [ScriptPubKey with Paired ScriptSignature Generator](#scriptpubkey-with-paired-scriptsignature-generator)
|
||||
- [Step 11: Add to CreditingTxGen](#step-11-add-to-creditingtxgen)
|
||||
- [Step 12: Fix all Non-Exhaustive Matches](#step-12-fix-all-non-exhaustive-matches)
|
||||
- [Step 13: Run tests and debug](#step-13-run-tests-and-debug)
|
||||
|
||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||
|
||||
# Adding a New ScriptPubKey Type
|
||||
|
||||
In this document, we will describe how to add new script implementations and types in Bitcoin-S. We will use the following script template example which we have called P2PK with Timeout to illustrate the process:
|
||||
|
||||
```
|
||||
OP_IF
|
||||
<Public Key>
|
||||
OP_ELSE
|
||||
<Timeout> OP_CHECKSEQUENCEVERIFY OP_DROP
|
||||
<Timeout Public Key>
|
||||
OP_ENDIF
|
||||
OP_CHECKSIG
|
||||
```
|
||||
|
||||
Here is [the actual pull request](https://github.com/bitcoin-s/bitcoin-s/pull/967) in which a very similar `ScriptPubKey` is implemented in Bitcoin-S.
|
||||
|
||||
Please note that this document only explains how to add new `RawScriptPubKey`s which are the subset of `ScriptPubKey`s which are fully described by their raw scripts. This is to say that this guide will not help in implementing a new segwit version, but should be helpful for most anything else.
|
||||
|
||||
It is also important to note that all new scripts should be implemented as if they are to appear on-chain without any P2SH or P2WSH. Bitcoin-S already supports conversions from raw on-chain scripts to these formats in the constructors for the script hash schemes which does not require extra support for new script types.
|
||||
|
||||
## Step 0: Design Philosophy
|
||||
|
||||
Bitcoin-S strives to have script types defined in such a way that they can be easily composed and reused. Before going through this guide and implementing a really large script template type, try to decompose your script into smaller re-usable pieces.
|
||||
|
||||
Also remember to consider what existing pieces you can use. For example, `LockTimeScriptPubKey`s are implemented in such a way that any other `RawScriptPubKey` can be given a time lock by nesting it within a `LockTimeScriptPubKey` subtype. Likewise, `ConditionalScriptPubKey`s are built to allow any other `RawScriptPubKey` type to populate both the `OP_IF/OP_NOTIF` case and the `OP_ELSE` case.
|
||||
|
||||
## Step 1: Create a New ScriptPubKey Trait
|
||||
|
||||
Go to `ScriptPubKey.scala` and add a new trait:
|
||||
|
||||
```scala
|
||||
sealed trait P2PKWithTimeoutScriptPubKey extends RawScriptPubKey
|
||||
```
|
||||
|
||||
You will then want to add all of the relevant accessor methods. For our case of P2PKWithTimeout, this will mean giving access to the public key, timeout, and timeout public key. Lastly, you will want to add a scaladoc. In total, we get the following result:
|
||||
|
||||
```scala
|
||||
/** The type for ScriptPubKeys of the form:
|
||||
* OP_IF
|
||||
* <Public Key>
|
||||
* OP_ELSE
|
||||
* <Timeout> OP_CHECKSEQUENCEVERIFY OP_DROP
|
||||
* <Timeout Public Key>
|
||||
* OP_ENDIF
|
||||
* OP_CHECKSIG
|
||||
*/
|
||||
sealed trait P2PKWithTimeoutScriptPubKey extends RawScriptPubKey {
|
||||
|
||||
lazy val pubKey: ECPublicKey =
|
||||
ECPublicKey.fromBytes(asm(2).bytes)
|
||||
|
||||
lazy val lockTime: ScriptNumber = ScriptNumber.fromBytes(asm(5).bytes)
|
||||
|
||||
lazy val timeoutPubKey: ECPublicKey =
|
||||
ECPublicKey.fromBytes(asm(9).bytes)
|
||||
}
|
||||
```
|
||||
|
||||
## Step 2: Create Companion Object
|
||||
|
||||
We now need a companion object which will fulfill four functionalities for us:
|
||||
|
||||
1. Contain a concrete `Impl` class for our SPK type
|
||||
- This simply means creating a `private case class` wrapping `asm: Vector[ScriptToken]`
|
||||
2. Create a `fromAsm` constructor
|
||||
- This should be a simple call to `buildScript` which is inherited from `ScriptFactory`
|
||||
3. Create a logical constructor from Bitcoin-S types
|
||||
- This means creating an `apply` method that takes in logical BItcoin-S types and constructs asm
|
||||
- Note that this may require the use of `BitcoinScriptUtil.calculatePushOp`
|
||||
4. Create an ASM filter which will detect if a given `Vector[ScriptToken]` corresponds to our type
|
||||
|
||||
This looks like the following:
|
||||
|
||||
```scala
|
||||
object P2PKWithTimeoutScriptPubKey
|
||||
extends ScriptFactory[P2PKWithTimeoutScriptPubKey] {
|
||||
private case class P2PKWithTimeoutScriptPubKeyImpl(asm: Vector[ScriptToken])
|
||||
extends P2PKWithTimeoutScriptPubKey
|
||||
|
||||
override def fromAsm(asm: Seq[ScriptToken]): P2PKWithTimeoutScriptPubKey = {
|
||||
buildScript(
|
||||
asm = asm.toVector,
|
||||
constructor = P2PKWithTimeoutScriptPubKeyImpl.apply,
|
||||
invariant = isP2PKWithTimeoutScriptPubKey,
|
||||
errorMsg = s"Given asm was not a P2PKWithTimeoutScriptPubKey, got $asm"
|
||||
)
|
||||
}
|
||||
|
||||
def apply(
|
||||
pubKey: ECPublicKey,
|
||||
lockTime: ScriptNumber,
|
||||
timeoutPubKey: ECPublicKey): P2PKWithTimeoutScriptPubKey = {
|
||||
val timeoutAsm = CSVScriptPubKey(lockTime, EmptyScriptPubKey).asm.toVector
|
||||
val pubKeyAsm = BitcoinScriptUtil
|
||||
.calculatePushOp(pubKey.bytes)
|
||||
.toVector ++ Vector(ScriptConstant(pubKey.bytes))
|
||||
val timeoutPubKeyAsm = BitcoinScriptUtil
|
||||
.calculatePushOp(timeoutPubKey.bytes)
|
||||
.toVector ++ Vector(ScriptConstant(timeoutPubKey.bytes))
|
||||
|
||||
P2PKWithTimeoutScriptPubKeyImpl(
|
||||
Vector(Vector(OP_IF),
|
||||
pubKeyAsm,
|
||||
Vector(OP_ELSE),
|
||||
timeoutAsm,
|
||||
timeoutPubKeyAsm,
|
||||
Vector(OP_ENDIF, OP_CHECKSIG)).flatten
|
||||
)
|
||||
}
|
||||
|
||||
def isP2PKWithTimeoutScriptPubKey(asm: Seq[ScriptToken]): Boolean = {
|
||||
if (asm.length == 12) {
|
||||
val pubKey = ECPublicKey.fromBytes(asm(2).bytes)
|
||||
val lockTimeTry = Try(ScriptNumber.fromBytes(asm(5).bytes))
|
||||
val timeoutPubKey = ECPublicKey.fromBytes(asm(9).bytes)
|
||||
|
||||
lockTimeTry match {
|
||||
case Success(lockTime) =>
|
||||
asm == P2PKWithTimeoutScriptPubKey(pubKey, lockTime, timeoutPubKey).asm
|
||||
case Failure(_) => false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Step 3: Add to Relevant fromAsm Methods
|
||||
|
||||
We now need to ensure that `ScriptPubKey.fromAsm(p2pkWithTimeoutSPK.asm)` returns our type. Since `P2PKWithTimeoutScriptPubKey extends RawScriptPubKey`, this means we must add to `RawScriptPubKey.fromAsm`. Note that order in this function's `match` can matter. Since our type is more specific than any other currently existing type, we put our new `case` at the top:
|
||||
|
||||
```scala
|
||||
asm match {
|
||||
case Nil => EmptyScriptPubKey
|
||||
case _ if P2PKWithTimeoutScriptPubKey.isP2PKWithTimeoutScriptPubKey(asm) =>
|
||||
P2PKWithTimeoutScriptPubKey.fromAsm(asm)
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
## Step 4: Create a ScriptSignature If Necessary
|
||||
|
||||
Often times a new `ScriptSignature` type will be necessary when introducing a new `ScriptPubKey` type. When this is the case, the procedure for adding a new `ScriptSignature` is more or less identical to steps 1 and 2 above. Here is what this looks like for `P2PKScriptPubKey` (note, this is not `P2PKWithTimeoutScriptPubKey`):
|
||||
|
||||
```scala
|
||||
/**
|
||||
* Represents a pay to public key script signature
|
||||
* https://bitcoin.org/en/developer-guide#pubkey
|
||||
* Signature script: <sig>
|
||||
*/
|
||||
sealed trait P2PKScriptSignature extends ScriptSignature {
|
||||
|
||||
/** PubKey scriptSignatures only have one signature */
|
||||
def signature: ECDigitalSignature = signatures.head
|
||||
|
||||
/** The digital signatures inside of the scriptSig */
|
||||
def signatures: Seq[ECDigitalSignature] = {
|
||||
Seq(ECDigitalSignature(BitcoinScriptUtil.filterPushOps(asm).head.hex))
|
||||
}
|
||||
|
||||
override def toString = s"P2PKScriptSignature($signature)"
|
||||
}
|
||||
|
||||
object P2PKScriptSignature extends ScriptFactory[P2PKScriptSignature] {
|
||||
private case class P2PKScriptSignatureImpl(
|
||||
override val asm: Vector[ScriptToken])
|
||||
extends P2PKScriptSignature
|
||||
|
||||
def fromAsm(asm: Seq[ScriptToken]): P2PKScriptSignature = {
|
||||
buildScript(asm.toVector,
|
||||
P2PKScriptSignatureImpl(_),
|
||||
isP2PKScriptSignature(_),
|
||||
"The given asm tokens were not a p2pk script sig: " + asm)
|
||||
}
|
||||
|
||||
def apply(signature: ECDigitalSignature): P2PKScriptSignature = {
|
||||
val pushOps = BitcoinScriptUtil.calculatePushOp(signature.bytes)
|
||||
val signatureConstant = ScriptConstant(signature.bytes)
|
||||
val asm = pushOps ++ Seq(signatureConstant)
|
||||
P2PKScriptSignature.fromAsm(asm)
|
||||
}
|
||||
|
||||
/** P2PK scriptSigs always have the pattern [pushop, digitalSignature] */
|
||||
def isP2PKScriptSignature(asm: Seq[ScriptToken]): Boolean = asm match {
|
||||
case Seq(_: BytesToPushOntoStack, _: ScriptConstant) => true
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
However, it is sometimes not necessary to create a new `ScriptSignature` type for every new `ScriptPubKey`. This is because we want to maintain unique representations for every `ScriptSignature`, and it turns out that in our case of `P2PKWithTimeoutScriptPubKey`, script signatures are of the form
|
||||
|
||||
```
|
||||
<boolean> <signautre>
|
||||
```
|
||||
|
||||
which is already represented by `ConditionalScriptSignature`. When this happens, you only need to create an `object` for your new type, and then follow step 2 above, skipping the first part (adding an Impl `case class`):
|
||||
|
||||
```scala
|
||||
object P2PKWithTimeoutScriptSignature
|
||||
extends ScriptFactory[ConditionalScriptSignature] {
|
||||
override def fromAsm(asm: Seq[ScriptToken]): ConditionalScriptSignature = {
|
||||
buildScript(
|
||||
asm.toVector,
|
||||
ConditionalScriptSignature.fromAsm,
|
||||
isP2PKWithTimeoutScriptSignature,
|
||||
s"The given asm tokens were not a P2PKWithTimeoutScriptSignature, got $asm"
|
||||
)
|
||||
}
|
||||
|
||||
def apply(
|
||||
beforeTimeout: Boolean,
|
||||
signature: ECDigitalSignature): ConditionalScriptSignature = {
|
||||
ConditionalScriptSignature(P2PKScriptSignature(signature), beforeTimeout)
|
||||
}
|
||||
|
||||
def isP2PKWithTimeoutScriptSignature(asm: Seq[ScriptToken]): Boolean = {
|
||||
P2PKScriptSignature.isP2PKScriptSignature(asm.dropRight(1)) && ConditionalScriptSignature
|
||||
.isValidConditionalScriptSig(asm)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Remember that in all of them above, `ScriptSignature`s are written as if they are to appear on-chain in transaction inputs rather than transaction witnesses, since Bitcoin-S supports turning any raw `ScriptSignature` into a `P2WSHWitness` without requiring explicit support for new script types.
|
||||
|
||||
## Step 5: Add to ScriptSignature.fromAsm If Applicable
|
||||
|
||||
If you added a new `ScriptSignature` type in the previous step, you must add a `case` to the `match` statement in `ScriptSignature.fromAsm` at the bottom of `ScriptSignature.scala`. For `P2PKScriptSignature` (note that this does not apply to `P2PKWithTimeoutScriptSignature` since there is no new unique type for this `ScriptSignature`), this looks like:
|
||||
|
||||
```scala
|
||||
tokens match {
|
||||
//...
|
||||
case _ if P2PKScriptSignature.isP2PKScriptSignature(tokens) =>
|
||||
P2PKScriptSignature.fromAsm(tokens)
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
## Step 6: Create Relevant InputInfo
|
||||
|
||||
`InputInfo` is the Bitcoin-S data structure for the information required to spend from a specific condition of a given `ScriptPubKey` other than private keys. Hence, when defining new script types, it is important to define how they are spent as well so that they can be useful.
|
||||
|
||||
There are two distinct kinds of scripts when it comes to signing in Bitcoin-S: scripts that have nesting (such as `ConditionalScriptPubKey`, `P2SHScriptPubKey`) and scripts without nesting (such as `MultiSignatureScriptPubKey`, `P2PKWithTimeoutScriptPubKey`, `P2PKHScriptPubKey`). We will cover each of these cases in turn, starting with the latter case as it applies to our example of `P2PKWithTimeout`. In both cases, please make sure to validate any parameter data using `require` statements when necessary, but make things correct by construction instead whenever possible. For example, if there is a redeem script and a `ScriptPubKey` which must wrap this redeem script, take as a parameter only the redeem script and construct the `ScriptPubKey` internally so that it is sure to be consistent.
|
||||
|
||||
### Non-Nesting Input Info
|
||||
|
||||
We create a new `case class` in `InputInfo.scala` which extends `RawInputInfo` and which contains in its parameters, all of the info required for spending a specific condition other than private keys and `HashType`. Here is what this looks like for `P2PKWithTimeout`:
|
||||
|
||||
```scala
|
||||
case class P2PKWithTimeoutInputInfo(
|
||||
outPoint: TransactionOutPoint,
|
||||
amount: CurrencyUnit,
|
||||
scriptPubKey: P2PKWithTimeoutScriptPubKey,
|
||||
isBeforeTimeout: Boolean)
|
||||
extends RawInputInfo {
|
||||
override def conditionalPath: ConditionalPath = {
|
||||
if (isBeforeTimeout) {
|
||||
ConditionalPath.nonNestedTrue
|
||||
} else {
|
||||
ConditionalPath.nonNestedFalse
|
||||
}
|
||||
}
|
||||
|
||||
override def pubKeys: Vector[ECPublicKey] =
|
||||
Vector(scriptPubKey.pubKey, scriptPubKey.timeoutPubKey)
|
||||
}
|
||||
```
|
||||
|
||||
### Nested Spending Info
|
||||
|
||||
The one new thing in the nested case is that we must create a `val nestedSpendingInfo: RawInputInfo` and make sure to pass on `hashPreImages: Vector[NetworkElement]` to the nested `InputInfo`, and pull public keys from the `nestedInputInfo`. For the case of spending `LockTimeScriptPubKey`s, this looks like the following:
|
||||
|
||||
```scala
|
||||
case class LockTimeInputInfo(
|
||||
outPoint: TransactionOutPoint,
|
||||
amount: CurrencyUnit,
|
||||
scriptPubKey: LockTimeScriptPubKey,
|
||||
conditionalPath: ConditionalPath,
|
||||
hashPreImages: Vector[NetworkElement] = Vector.empty
|
||||
) extends RawInputInfo {
|
||||
|
||||
val nestedInputInfo: RawInputInfo = RawInputInfo(
|
||||
outPoint,
|
||||
amount,
|
||||
scriptPubKey.nestedScriptPubKey,
|
||||
conditionalPath,
|
||||
hashPreImages)
|
||||
|
||||
override def pubKeys: Vector[ECPublicKey] = nestedInputInfo.pubKeys
|
||||
}
|
||||
```
|
||||
|
||||
## Step 7: Add to Relevant Apply Methods
|
||||
|
||||
Now that we have created our new `RawInputInfo`, we need to add them to the general-purpose input info constructors. This means adding a `case` to `RawInputInfo.apply` for your new `ScriptPubKey` type which constructs your relevant `RawInputInfo` from generic types (given as parameters in the `apply` methods). For `P2PKWithTimeout`, this looks like the following:
|
||||
|
||||
```scala
|
||||
scriptPubKey match {
|
||||
//...
|
||||
case p2pkWithTimeout: P2PKWithTimeoutScriptPubKey =>
|
||||
conditionalPath.headOption match {
|
||||
case None =>
|
||||
throw new IllegalArgumentException(
|
||||
"ConditionalPath must be specified for P2PKWithTimeout")
|
||||
case Some(beforeTimeout) =>
|
||||
P2PKWithTimeoutInputInfo(outPoint,
|
||||
amount,
|
||||
p2pkWithTimeout,
|
||||
beforeTimeout)
|
||||
}
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
## Step 8: Create a Signer
|
||||
|
||||
We must now add signing functionality for our new script type within `Signer.scala`. This time, we have three different cases depending on your new script type.
|
||||
|
||||
### Non-Nested Single-Key Spending Info
|
||||
|
||||
For the non-nested case where only a single key is required, all we must do is create a new class which extends `RawSingleKeyBitcoinSigner` and implements `keyAndSigToScriptSig`. For `P2PKWithTimeout` this looks like the following:
|
||||
|
||||
```scala
|
||||
sealed abstract class P2PKWithTimeoutSigner
|
||||
extends RawSingleKeyBitcoinSigner[P2PKWithTimeoutInputInfo] {
|
||||
|
||||
override def keyAndSigToScriptSig(
|
||||
key: ECPublicKey,
|
||||
sig: ECDigitalSignature,
|
||||
spendingInfo: UTXOInfo[P2PKWithTimeoutInputInfo]): ScriptSignature = {
|
||||
P2PKWithTimeoutScriptSignature(spendingInfo.inputInfo.isBeforeTimeout, sig)
|
||||
}
|
||||
}
|
||||
|
||||
object P2PKWithTimeoutSigner extends P2PKWithTimeoutSigner
|
||||
```
|
||||
|
||||
### Non-Nested Multi-Key Spending Info
|
||||
|
||||
In the non-nested case where multiple keys are required, we must create a new `Signer`, which requires implementing the `sign` function. For `MultiSignature` this looks like the following:
|
||||
|
||||
```scala
|
||||
sealed abstract class MultiSigSigner extends Signer[MultiSignatureInputInfo] {
|
||||
|
||||
override def sign(
|
||||
spendingInfo: UTXOSatisfyingInfo[InputInfo],
|
||||
unsignedTx: Transaction,
|
||||
isDummySignature: Boolean,
|
||||
spendingInfoToSatisfy: UTXOSatisfyingInfo[MultiSignatureInputInfo])(
|
||||
implicit ec: ExecutionContext): Future[TxSigComponent] = {
|
||||
val (_, output, inputIndex, _) =
|
||||
relevantInfo(spendingInfo, unsignedTx)
|
||||
|
||||
val keysAndSigsF = spendingInfo.toSingles.map { spendingInfoSingle =>
|
||||
signSingle(spendingInfoSingle, unsignedTx, isDummySignature)
|
||||
}
|
||||
|
||||
val signaturesF = Future.sequence(keysAndSigsF).map(_.map(_.signature))
|
||||
|
||||
val scriptSigF = signaturesF.map { sigs =>
|
||||
MultiSignatureScriptSignature(sigs)
|
||||
}
|
||||
|
||||
updateScriptSigInSigComponent(unsignedTx,
|
||||
inputIndex.toInt,
|
||||
output,
|
||||
scriptSigF)
|
||||
}
|
||||
}
|
||||
|
||||
object MultiSigSigner extends MultiSigSigner
|
||||
```
|
||||
|
||||
### Nested Spending Info
|
||||
|
||||
When signing for a nested script structure, we must create a new `Signer`. You will need to make a delegating call with the `nestedSpendingInfo` to `BitcoinSigner.sign`, but you may also need to do whatever else is needed with the nested result to construct a correct `ScriptSignature`. For `ConditionalScriptSignature`, this all looks like:
|
||||
|
||||
```scala
|
||||
/** Delegates to get a ScriptSignature for the case being
|
||||
* spent and then adds an OP_TRUE or OP_FALSE
|
||||
*/
|
||||
sealed abstract class ConditionalSigner extends Signer[ConditionalInputInfo] {
|
||||
|
||||
override def sign(
|
||||
spendingInfo: UTXOSatisfyingInfo[InputInfo],
|
||||
unsignedTx: Transaction,
|
||||
isDummySignature: Boolean,
|
||||
spendingInfoToSatisfy: UTXOSatisfyingInfo[ConditionalInputInfo])(
|
||||
implicit ec: ExecutionContext): Future[TxSigComponent] = {
|
||||
val (_, output, inputIndex, _) = relevantInfo(spendingInfo, unsignedTx)
|
||||
|
||||
val nestedSpendingInfo = spendingInfoToSatisfy.copy(
|
||||
inputInfo = spendingInfoToSatisfy.inputInfo.nestedInputInfo)
|
||||
|
||||
val missingOpSigComponentF = BitcoinSigner.sign(spendingInfo,
|
||||
unsignedTx,
|
||||
isDummySignature,
|
||||
nestedSpendingInfo)
|
||||
|
||||
val scriptSigF = missingOpSigComponentF.map { sigComponent =>
|
||||
ConditionalScriptSignature(sigComponent.scriptSignature,
|
||||
spendingInfoToSatisfy.inputInfo.condition)
|
||||
}
|
||||
|
||||
updateScriptSigInSigComponent(unsignedTx,
|
||||
inputIndex.toInt,
|
||||
output,
|
||||
scriptSigF)
|
||||
}
|
||||
}
|
||||
object ConditionalSigner extends ConditionalSigner
|
||||
```
|
||||
|
||||
## Step 9: Add to BitcoinSigner.sign
|
||||
|
||||
We must now add the new signing functionality from the previous step to the general-purpose signing functions by adding a new `case` for your new `InputInfo` type in the `match` within `BitcoinSigner.sign`. In the case of `P2PKWithTimeout`, this looks like:
|
||||
|
||||
```scala
|
||||
spendingInfoToSatisfy match {
|
||||
//...
|
||||
case p2pKWithTimeout: P2PKWithTimeoutInputInfo =>
|
||||
P2PKWithTimeoutSigner.sign(spendingInfo,
|
||||
unsignedTx,
|
||||
isDummySignature,
|
||||
spendingFrom(p2pKWithTimeout))
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
We have now fully implemented the new script type! But have we done it correctly? We must now add the new script type to the Bitcoin-S test framework so that our scripts get added to existing Bitcoin-S property-based tests.
|
||||
|
||||
## Step 10: Add to ScriptGenerators
|
||||
|
||||
The first step to adding our new script type to Bitcoin-S property-based tests is creating generators for our new `ScriptPubKey` and `ScriptSignature` types in `ScriptGenerators.scala`.
|
||||
|
||||
It is important to note that in the current Bitcoin-S generator framework for `ScriptPubKey`s, all conditionals always spend only their `OP_TRUE` cases.
|
||||
|
||||
### ScriptPubKey Generator
|
||||
|
||||
Let's start by creating a generator for our `ScriptPubKey`, this generator should also return the private keys that were used to create the `ScriptPubKey`. To construct this `Gen`, you will likely need to use other generators for the internal structures in your script such as keys and lock times. For `P2PKWithTimeout` this looks like:
|
||||
|
||||
```scala
|
||||
def p2pkWithTimeoutScriptPubKey: Gen[
|
||||
(P2PKWithTimeoutScriptPubKey, Seq[ECPrivateKey])] =
|
||||
for {
|
||||
privKey <- CryptoGenerators.privateKey
|
||||
timeoutPrivKey <- CryptoGenerators.privateKey
|
||||
lockTime <- NumberGenerator.timeLockScriptNumbers
|
||||
} yield {
|
||||
(P2PKWithTimeoutScriptPubKey(privKey.publicKey,
|
||||
lockTime,
|
||||
timeoutPrivKey.publicKey),
|
||||
Vector(privKey, timeoutPrivKey))
|
||||
}
|
||||
```
|
||||
|
||||
Note that the private key used in the `OP_TRUE` case is the `head` of the `Seq[ECPrivateKey]` returned. This makes it possible for tests that only spend the `OP_TRUE` case to find the correct key, as it is expected to be the first one.
|
||||
|
||||
We must now add this `Gen` to all of the following `def`s in `ScriptGenerators.scala`: `randomNonP2SHScriptPubKey, scriptPubKey, nonWitnessScriptPubKey, nonConditionalRawScriptPubKey, rawScriptPubKey`, and if your `ScriptPubKey` has no lock times, you must also add the above `Gen` to `nonConditionalNonLocktimeRawScriptPubKey, nonLocktimeRawScriptPubKey` as well.
|
||||
|
||||
### ScriptSignature Generator
|
||||
|
||||
We must also create a generator for our `ScriptSignature` type, even if we did not introduce a new `ScriptSignature` type (in our example of `P2PKWithTimeout` we use a specific form of `ConditionalScriptSignature`). Once again you will likely need to use other existing generators. For `P2PKWithTimeoutScriptSignature`, this looks like:
|
||||
|
||||
```scala
|
||||
def p2pkWithTimeoutScriptSignature: Gen[ConditionalScriptSignature] =
|
||||
for {
|
||||
privKey <- CryptoGenerators.privateKey
|
||||
hash <- CryptoGenerators.doubleSha256Digest
|
||||
hashType <- CryptoGenerators.hashType
|
||||
signature = ECDigitalSignature.fromBytes(
|
||||
privKey.sign(hash).bytes ++ ByteVector.fromByte(hashType.byte))
|
||||
beforeTimeout <- NumberGenerator.bool
|
||||
} yield P2PKWithTimeoutScriptSignature(beforeTimeout, signature)
|
||||
```
|
||||
|
||||
We now add this `Gen` to `scriptSignature: Gen[ScriptSignature]` as well as adding a case for our new `ScriptPubKey` type in `pickCorrespondingScriptSignature` which should return our new `ScriptSignature` generator. If our `ScriptPubKey` does not have any lock times, you should also add this script signature `Gen` to `nonLockTimeConditionalScriptSignature` and `randomNonLockTimeScriptSig`.
|
||||
|
||||
### ScriptPubKey with Paired ScriptSignature Generator
|
||||
|
||||
Lastly, we need to construct a generator that returns both a `ScriptPubKey` and a `ScriptSignature` signing that that `ScriptPubKey`. All keys used in signing should also be returned. This all should be done by using the above `ScriptPubKey` generator, then constructing an `ScriptSignature` for your type where all actual signatures are `EmptyDigitalSignature`s. A `UTXOSatisfyingInfo` should then be constructed for the generated `ScriptPubKey` (using the private keys generated in the same line). Finally, a `TxSignatureComponent` should be created by using the new `Signer` for our script type. From this `TxSignatureComponent`, a `ScriptSignature` is readily available. For `P2PKWithTimeout`, this generator looks like:
|
||||
|
||||
```scala
|
||||
def signedP2PKWithTimeoutScriptSignature: Gen[
|
||||
(ConditionalScriptSignature, P2PKWithTimeoutScriptPubKey, ECPrivateKey)] =
|
||||
for {
|
||||
(spk, privKeys) <- p2pkWithTimeoutScriptPubKey
|
||||
hashType <- CryptoGenerators.hashType
|
||||
} yield {
|
||||
val emptyScriptSig = P2PKWithTimeoutScriptSignature(beforeTimeout = true,
|
||||
EmptyDigitalSignature)
|
||||
val (creditingTx, outputIndex) =
|
||||
TransactionGenerators.buildCreditingTransaction(spk)
|
||||
val (spendingTx, inputIndex) = TransactionGenerators
|
||||
.buildSpendingTransaction(creditingTx, emptyScriptSig, outputIndex)
|
||||
val spendingInfo = UTXOSatisfyingInfo(
|
||||
P2PKWithTimeoutInputInfo(
|
||||
TransactionOutPoint(creditingTx.txIdBE, inputIndex),
|
||||
creditingTx.outputs(outputIndex.toInt).value,
|
||||
spk,
|
||||
isBeforeTimeout = true),
|
||||
privKeys.toVector,
|
||||
hashType
|
||||
)
|
||||
val txSigComponentF = P2PKWithTimeoutSigner.sign(spendingInfo,
|
||||
spendingTx,
|
||||
isDummySignature = false)
|
||||
val txSigComponent = Await.result(txSigComponentF, timeout)
|
||||
val signedScriptSig =
|
||||
txSigComponent.scriptSignature.asInstanceOf[ConditionalScriptSignature]
|
||||
|
||||
(signedScriptSig, spk, privKeys.head)
|
||||
}
|
||||
```
|
||||
|
||||
I strongly advise you also look at at least one other `Gen` of this kind before writing your own.
|
||||
|
||||
## Step 11: Add to CreditingTxGen
|
||||
|
||||
Now that we have generators constructed for `ScriptPubKey`s, `ScriptSignature`s and their pairings completed, we will create a generator for our type's `SpendingInfoFull`. This should usually be as simple as mapping on the `ScriptPubKey` generator in `ScriptGenerators` and calling `build` (within `CreditinTxGen.scala`). We then also create another generator which returns lists of `SpendingInfo`s generated by the previous `Gen`. For `P2PKWithTimeout`, this looks like:
|
||||
|
||||
```scala
|
||||
def p2pkWithTimeoutOutput: Gen[UTXOSatisfyingInfo[InputInfo]] = {
|
||||
ScriptGenerators.p2pkWithTimeoutScriptPubKey.flatMap { p2pkWithTimeout =>
|
||||
build(p2pkWithTimeout._1, Seq(p2pkWithTimeout._2.head), None, None)
|
||||
}
|
||||
}
|
||||
|
||||
def p2pkWithTimeoutOutputs: Gen[Seq[UTXOSatisfyingInfo[InputInfo]]] = {
|
||||
Gen.choose(min, max).flatMap(n => Gen.listOfN(n, p2pkWithTimeoutOutput))
|
||||
}
|
||||
```
|
||||
|
||||
We must then add our output `Gen` to one of `cltvOutputGens` or `nonCLTVOutputGens` depending on whether the `ScriptPubKey` type has CLTVs (absolute lock times) or not. We must also add our output `Gen` to `nonP2SHOutput`, and also to `nonSHOutput` and `nonP2WSHOutput` in the case that your `ScriptPubKey` type has no CLTVs.
|
||||
|
||||
## Step 12: Fix all Non-Exhaustive Matches
|
||||
|
||||
All we have left is to clean up our code and make sure that nothing has been missed. Within an `sbt` terminal, you should run the following sequence of commands:
|
||||
|
||||
```bashrc
|
||||
clean
|
||||
project coreTest
|
||||
test:compile
|
||||
```
|
||||
|
||||
This should have quite a lengthy output but we are only interested in any compiler errors there may be, as well as non-exhaustive match compiler warnings. You should first fix any compiler errors you encounter, and then you can get the warnings again by running `clean` and then running `test:compile` again.
|
||||
|
||||
The warnings we're interested in should look something like this:
|
||||
|
||||
```
|
||||
[warn] /home/nkohen/Desktop/SuredBits/bitcoin-s-core/testkit/src/main/scala/org/bitcoins/testkit/core/gen/ScriptGenerators.scala:524:59: match may not be exhaustive.
|
||||
[warn] It would fail on the following input: P2PKWithTimeoutScriptPubKeyImpl(_)
|
||||
[warn] scriptPubKey: ScriptPubKey): Gen[ScriptSignature] = scriptPubKey match {
|
||||
[warn] ^
|
||||
[warn] one warning found
|
||||
```
|
||||
|
||||
You may get these warnings for your new `ScriptSignature` type as well. These are places where the compiler expects there to be defined functionality in a pattern match where one of our new types is a possibility, but for which no functionality is defined. You must go to each of these warnings and add a `case` for the relevant new type, or add this new type to an existing case when applicable.
|
||||
|
||||
## Step 13: Run tests and debug
|
||||
|
||||
Lastly, once everything is compiling nicely, all that is left is to run tests and debug. While within an `sbt` terminal session, run the following two commands to run the relevant tests:
|
||||
|
||||
```
|
||||
project coreTest
|
||||
test
|
||||
```
|
||||
|
||||
If all tests pass we are all done! If you encounter any test failures, you can re-run individual tests using the `testOnly` command which must be given the full name of the test you wish to run (these names should be at the bottom of the testing output and look something like `org.bitcoins.core.script.interpreter.ScriptInterpreterTest`).
|
59
website/versioned_docs/version-v0.4/core/addresses.md
Normal file
59
website/versioned_docs/version-v0.4/core/addresses.md
Normal file
|
@ -0,0 +1,59 @@
|
|||
---
|
||||
id: version-v0.4-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(02f8800659b76e089952e4ad29fbfc2b2df71cc42f6e972b84ccfcfd473f731b9c)
|
||||
|
||||
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 = tb1q55pqkkeaugsd49jny8dvs3ca6jke0crg20htcm
|
||||
|
||||
println(segwitAddress.toString)
|
||||
// tb1q55pqkkeaugsd49jny8dvs3ca6jke0crg20htcm
|
||||
```
|
||||
|
||||
## 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 = mvZSEkViUNKk6oFibRruDzembG9LYeiKKS
|
||||
|
||||
println(legacyAddress.toString)
|
||||
// mvZSEkViUNKk6oFibRruDzembG9LYeiKKS
|
||||
```
|
108
website/versioned_docs/version-v0.4/core/core-intro.md
Normal file
108
website/versioned_docs/version-v0.4/core/core-intro.md
Normal file
|
@ -0,0 +1,108 @@
|
|||
---
|
||||
id: version-v0.4-core-intro
|
||||
title: Core Module
|
||||
original_id: core-intro
|
||||
---
|
||||
|
||||
The `core` module is the core (duh!) functionality of Bitcoin-S. The goal is to provide basic
|
||||
data structures that are found in the Bitcoin and Lightning protocols while
|
||||
minimizing external dependencies for security purposes. We aim to have an extremely
|
||||
high level of test coverage in this module to flesh out bugs. We use property based
|
||||
testing heavily in this library to ensure high quality of code.
|
||||
|
||||
## The basics
|
||||
|
||||
Every bitcoin protocol data structure (and some other data structures) extends [`NetworkElement`](/api/org/bitcoins/crypto/NetworkElement). `NetworkElement` provides methods to convert the data structure to hex or byte representation. When paired with [`Factory`](/api/org/bitcoins/crypto/Factory) we can easily serialize and deserialize data structures.
|
||||
|
||||
Most data structures have companion objects that extends `Factory` to be able to easily create protocol data structures. An example of this is the [`ScriptPubKey`](/api/org/bitcoins/core/protocol/script/ScriptPubKey) companion object. You can use this companion object to create a `ScriptPubKey` from a hex string or a byte array.
|
||||
|
||||
## Main modules in `core`
|
||||
|
||||
1. [`protocol`](/api/org/bitcoins/core/protocol) - basic protocol data structures. Useful for serializing/deserializing things
|
||||
1. [`crypto`](/api/org/bitcoins/core/crypto) - cryptograhic functionality used in Bitcoin and Lightning
|
||||
1. [`script`](/api/org/bitcoins/core/script) - an implementation of [Script](https://en.bitcoin.it/wiki/Script) - the programming language in Bitcoin
|
||||
1. [`wallet`](/api/org/bitcoins/core/wallet) - implements signing logic for Bitcoin transactions. This module is not named well as there is **NO** functionality to persist wallet state to disk as it stands. This will most likely be renamed in the future.
|
||||
1. [`config`](/api/org/bitcoins/core/config) - Contains information about a chain's genesis block and DNS seeds
|
||||
1. [`number`](/api/org/bitcoins/core/number) - Implements number types that are native in C, i.e. `UInt8`, `UInt32`, `UInt64`, etc.
|
||||
1. [`currency`](/api/org/bitcoins/core/currency) - Implements currency units in the Bitcoin protocol
|
||||
1. [`bloom`](/api/org/bitcoins/core/bloom) - Implements [Bloom filters](https://en.wikipedia.org/wiki/Bloom_filter) and [merkle blocks](https://bitcoin.org/en/glossary/merkle-block) needed for [BIP37](https://github.com/bitcoin/bips/blob/master/bip-0037.mediawiki)
|
||||
1. [`hd`](/api/org/bitcoins/core/hd) - Contains implementations of hierarchical deterministic (HD) paths, that when combined with `ExtPrivKey` and `ExtPubKey` in `crypto` can implement BIP32, BIP44, BIP49 and BIP84.
|
||||
|
||||
## Examples
|
||||
|
||||
### Serializing and deserializing a `Transaction`
|
||||
|
||||
Here is an example scala console session with bitcoins-core
|
||||
|
||||
```scala
|
||||
import org.bitcoins.core.protocol.transaction._
|
||||
|
||||
val hexTx = "0100000002d8c8df6a6fdd2addaf589a83d860f18b44872d13ee6ec3526b2b470d42a96d4d000000008b483045022100b31557e47191936cb14e013fb421b1860b5e4fd5d2bc5ec1938f4ffb1651dc8902202661c2920771fd29dd91cd4100cefb971269836da4914d970d333861819265ba014104c54f8ea9507f31a05ae325616e3024bd9878cb0a5dff780444002d731577be4e2e69c663ff2da922902a4454841aa1754c1b6292ad7d317150308d8cce0ad7abffffffff2ab3fa4f68a512266134085d3260b94d3b6cfd351450cff021c045a69ba120b2000000008b4830450220230110bc99ef311f1f8bda9d0d968bfe5dfa4af171adbef9ef71678d658823bf022100f956d4fcfa0995a578d84e7e913f9bb1cf5b5be1440bcede07bce9cd5b38115d014104c6ec27cffce0823c3fecb162dbd576c88dd7cda0b7b32b0961188a392b488c94ca174d833ee6a9b71c0996620ae71e799fc7c77901db147fa7d97732e49c8226ffffffff02c0175302000000001976a914a3d89c53bb956f08917b44d113c6b2bcbe0c29b788acc01c3d09000000001976a91408338e1d5e26db3fce21b011795b1c3c8a5a5d0788ac00000000"
|
||||
// hexTx: String = 0100000002d8c8df6a6fdd2addaf589a83d860f18b44872d13ee6ec3526b2b470d42a96d4d000000008b483045022100b31557e47191936cb14e013fb421b1860b5e4fd5d2bc5ec1938f4ffb1651dc8902202661c2920771fd29dd91cd4100cefb971269836da4914d970d333861819265ba014104c54f8ea9507f31a05ae325616e3024bd9878cb0a5dff780444002d731577be4e2e69c663ff2da922902a4454841aa1754c1b6292ad7d317150308d8cce0ad7abffffffff2ab3fa4f68a512266134085d3260b94d3b6cfd351450cff021c045a69ba120b2000000008b4830450220230110bc99ef311f1f8bda9d0d968bfe5dfa4af171adbef9ef71678d658823bf022100f956d4fcfa0995a578d84e7e913f9bb1cf5b5be1440bcede07bce9cd5b38115d014104c6ec27cffce0823c3fecb162dbd576c88dd7cda0b7b32b0961188a392b488c94ca174d833ee6a9b71c0996620ae71e799fc7c77901db147fa7d97732e49c8226ffffffff02c0175302000000001976a914a3d89c53bb956f08917b44d113c6b2bcbe0c29b788acc01c3d09000000001976a91408338e1d5e26db3fce21b011795b1c3c8a5a5d0788ac00000000
|
||||
|
||||
val tx = Transaction.fromHex(hexTx)
|
||||
// tx: Transaction = BaseTransaction(Int32Impl(1),Vector(TransactionInputImpl(TransactionOutPoint(4d6da9420d472b6b52c36eee132d87448bf160d8839a58afdd2add6f6adfc8d8:0),P2PKHScriptSignature(ECPublicKey(04c54f8ea9507f31a05ae325616e3024bd9878cb0a5dff780444002d731577be4e2e69c663ff2da922902a4454841aa1754c1b6292ad7d317150308d8cce0ad7ab), ECDigitalSignature(3045022100b31557e47191936cb14e013fb421b1860b5e4fd5d2bc5ec1938f4ffb1651dc8902202661c2920771fd29dd91cd4100cefb971269836da4914d970d333861819265ba01)),UInt32Impl(4294967295)), TransactionInputImpl(TransactionOutPoint(b220a19ba645c021f0cf501435fd6c3b4db960325d0834612612a5684ffab32a:0),P2PKHScriptSignature(ECPublicKey(04c6ec27cffce0823c3fecb162dbd576c88dd7cda0b7b32b0961188a392b488c94ca174d833ee6a9b71c0996620ae71e799fc7c77901db147fa7d97732e49c8226), ECDigitalSignature(30450220230110bc99ef311f1f8bda9d0d968bfe5dfa4af171adbef9ef71678d658823bf022100f956d4fcfa0995a578d84e7e913f9bb1cf5b5be1440bcede07bce9cd5b38115d01)),UInt32Impl(4294967295))),Vector(TransactionOutput(39000000 sats,pkh(a3d89c53bb956f08917b44d113c6b2bcbe0c29b7)), TransactionOutput(155000000 sats,pkh(08338e1d5e26db3fce21b011795b1c3c8a5a5d07))),UInt32Impl(0))
|
||||
|
||||
tx.hex == hexTx
|
||||
// res0: Boolean = true
|
||||
```
|
||||
|
||||
This gives us an example of a hex encoded Bitcoin transaction that is deserialized to a native Scala object called a [`Transaction`](/api/org/bitcoins/core/protocol/transaction/Transaction). You could also serialize the transaction to bytes using `tx.bytes` instead of `tx.hex`. These methods are available on every data structure that extends NetworkElement, like [`ECPrivateKey`](/api/org/bitcoins/crypto/ECPrivateKey), [`ScriptPubKey`](/api/org/bitcoins/core/protocol/script/ScriptPubKey), [`ScriptWitness`](/api/org/bitcoins/core/protocol/script/ScriptWitness), and [`Block`](/api/org/bitcoins/core/protocol/blockchain/Block).
|
||||
|
||||
#### Generating a BIP39 mnemonic phrase and an `xpriv`
|
||||
|
||||
See our [HD example](hd-keys)
|
||||
|
||||
### Building a signed transaction
|
||||
|
||||
Bitcoin Core supports building unsigned transactions and then signing them with a set of private keys. The first important thing to look at is [`UTXOSpendingInfo`](/api/org/bitcoins/core/wallet/utxo/UTXOSpendingInfo). This contains all of the information needed to create a validly signed [`ScriptSignature`](/api/org/bitcoins/core/protocol/script/ScriptSignature) that spends this output.
|
||||
|
||||
Our [`RawTxBuilder`](/api/org/bitcoins/core/wallet/builder/RawTxBuilder) class requires you to provide the following:
|
||||
|
||||
1. `destinations` - the places we are sending bitcoin to. These are [TransactionOutputs](/api/org/bitcoins/core/protocol/transaction/TransactionOutput) you are sending coins too
|
||||
2. `utxos` - these are the [InputSigningInfo](/api/org/bitcoins/core/wallet/utxo/InputSigningInfo) used to fund your transaction. These must exist in your wallet, and you must know how to spend them (i.e. have the private key)
|
||||
3. `feeRate` - the fee rate you want to pay for this transaction
|
||||
4. `changeSPK` - where the change (i.e. `creditingAmount - destinationAmount - fee`) from the transaction will be sent
|
||||
5. `network` - the [network](/api/org/bitcoins/core/config/NetworkParameters) we are transacting on
|
||||
|
||||
After providing this information, you can generate a validly signed bitcoin transaction by calling the `sign` method.
|
||||
|
||||
To see a complete example of this, see [our `TxBuilder` example](txbuilder.md)
|
||||
|
||||
### Verifying a transaction's script is valid (does not check if UTXO is valid)
|
||||
|
||||
Transactions are run through the interpreter to check their validity. These are packaged up into an object called `ScriptProgram`, which contains the following:
|
||||
|
||||
- The transaction that is being checked
|
||||
- The specific input index that it is checking
|
||||
- The `scriptPubKey` for the crediting transaction
|
||||
- The flags used to verify the script
|
||||
|
||||
Here is an example of a transaction spending a `scriptPubKey` which is correctly evaluated with our interpreter implementation:
|
||||
|
||||
|
||||
```scala
|
||||
val spendingTx = Transaction.fromHex("0100000001ccf318f0cbac588a680bbad075aebdda1f211c94ba28125b0f627f9248310db3000000006b4830450221008337ce3ce0c6ac0ab72509f889c1d52701817a2362d6357457b63e3bdedc0c0602202908963b9cf1a095ab3b34b95ce2bc0d67fb0f19be1cc5f7b3de0b3a325629bf01210241d746ca08da0a668735c3e01c1fa02045f2f399c5937079b6434b5a31dfe353ffffffff0210335d05000000001976a914b1d7591b69e9def0feb13254bace942923c7922d88ac48030000000000001976a9145e690c865c2f6f7a9710a474154ab1423abb5b9288ac00000000")
|
||||
// spendingTx: Transaction = BaseTransaction(Int32Impl(1),Vector(TransactionInputImpl(TransactionOutPoint(b30d3148927f620f5b1228ba941c211fdabdae75d0ba0b688a58accbf018f3cc:0),P2PKHScriptSignature(ECPublicKey(0241d746ca08da0a668735c3e01c1fa02045f2f399c5937079b6434b5a31dfe353), ECDigitalSignature(30450221008337ce3ce0c6ac0ab72509f889c1d52701817a2362d6357457b63e3bdedc0c0602202908963b9cf1a095ab3b34b95ce2bc0d67fb0f19be1cc5f7b3de0b3a325629bf01)),UInt32Impl(4294967295))),Vector(TransactionOutput(89994000 sats,pkh(b1d7591b69e9def0feb13254bace942923c7922d)), TransactionOutput(840 sats,pkh(5e690c865c2f6f7a9710a474154ab1423abb5b92))),UInt32Impl(0))
|
||||
|
||||
val scriptPubKey = ScriptPubKey.fromAsmHex("76a91431a420903c05a0a7de2de40c9f02ebedbacdc17288ac")
|
||||
// scriptPubKey: ScriptPubKey = pkh(31a420903c05a0a7de2de40c9f02ebedbacdc172)
|
||||
|
||||
val output = TransactionOutput(CurrencyUnits.zero, scriptPubKey)
|
||||
// output: TransactionOutput = TransactionOutput(0 sats,pkh(31a420903c05a0a7de2de40c9f02ebedbacdc172))
|
||||
|
||||
val inputIndex = UInt32.zero
|
||||
// inputIndex: UInt32 = UInt32Impl(0)
|
||||
|
||||
val btxsc = BaseTxSigComponent(spendingTx,inputIndex,output,Policy.standardScriptVerifyFlags)
|
||||
// btxsc: BaseTxSigComponent = BaseTxSigComponentImpl(BaseTransaction(Int32Impl(1),Vector(TransactionInputImpl(TransactionOutPoint(b30d3148927f620f5b1228ba941c211fdabdae75d0ba0b688a58accbf018f3cc:0),P2PKHScriptSignature(ECPublicKey(0241d746ca08da0a668735c3e01c1fa02045f2f399c5937079b6434b5a31dfe353), ECDigitalSignature(30450221008337ce3ce0c6ac0ab72509f889c1d52701817a2362d6357457b63e3bdedc0c0602202908963b9cf1a095ab3b34b95ce2bc0d67fb0f19be1cc5f7b3de0b3a325629bf01)),UInt32Impl(4294967295))),Vector(TransactionOutput(89994000 sats,pkh(b1d7591b69e9def0feb13254bace942923c7922d)), TransactionOutput(840 sats,pkh(5e690c865c2f6f7a9710a474154ab1423abb5b92))),UInt32Impl(0)),UInt32Impl(0),TransactionOutput(0 sats,pkh(31a420903c05a0a7de2de40c9f02ebedbacdc172)),List(ScriptVerifyP2SH, ScriptVerifyDerSig, ScriptVerifyStrictEnc, ScriptVerifyMinimalData, ScriptVerifyDiscourageUpgradableNOPs, ScriptVerifyCleanStack, ScriptVerifyCheckLocktimeVerify, ScriptVerifyCheckSequenceVerify, ScriptVerifyLowS, ScriptVerifyWitness, ScriptVerifyMinimalIf, ScriptVerifyNullFail, ScriptVerifyNullDummy, ScriptVerifyWitnessPubKeyType, ScriptVerifyDiscourageUpgradableWitnessProgram))
|
||||
|
||||
val preExecution = PreExecutionScriptProgram(btxsc)
|
||||
// preExecution: PreExecutionScriptProgram = PreExecutionScriptProgram(BaseTxSigComponentImpl(BaseTransaction(Int32Impl(1),Vector(TransactionInputImpl(TransactionOutPoint(b30d3148927f620f5b1228ba941c211fdabdae75d0ba0b688a58accbf018f3cc:0),P2PKHScriptSignature(ECPublicKey(0241d746ca08da0a668735c3e01c1fa02045f2f399c5937079b6434b5a31dfe353), ECDigitalSignature(30450221008337ce3ce0c6ac0ab72509f889c1d52701817a2362d6357457b63e3bdedc0c0602202908963b9cf1a095ab3b34b95ce2bc0d67fb0f19be1cc5f7b3de0b3a325629bf01)),UInt32Impl(4294967295))),Vector(TransactionOutput(89994000 sats,pkh(b1d7591b69e9def0feb13254bace942923c7922d)), TransactionOutput(840 sats,pkh(5e690c865c2f6f7a9710a474154ab1423abb5b92))),UInt32Impl(0)),UInt32Impl(0),TransactionOutput(0 sats,pkh(31a420903c05a0a7de2de40c9f02ebedbacdc172)),List(ScriptVerifyP2SH, ScriptVerifyDerSig, ScriptVerifyStrictEnc, ScriptVerifyMinimalData, ScriptVerifyDiscourageUpgradableNOPs, ScriptVerifyCleanStack, ScriptVerifyCheckLocktimeVerify, ScriptVerifyCheckSequenceVerify, ScriptVerifyLowS, ScriptVerifyWitness, ScriptVerifyMinimalIf, ScriptVerifyNullFail, ScriptVerifyNullDummy, ScriptVerifyWitnessPubKeyType, ScriptVerifyDiscourageUpgradableWitnessProgram)),List(),List(BytesToPushOntoStackImpl(72), ScriptConstantImpl(ByteVector(72 bytes, 0x30450221008337ce3ce0c6ac0ab72509f889c1d52701817a2362d6357457b63e3bdedc0c0602202908963b9cf1a095ab3b34b95ce2bc0d67fb0f19be1cc5f7b3de0b3a325629bf01)), BytesToPushOntoStackImpl(33), ScriptConstantImpl(ByteVector(33 bytes, 0x0241d746ca08da0a668735c3e01c1fa02045f2f399c5937079b6434b5a31dfe353))),List(BytesToPushOntoStackImpl(72), ScriptConstantImpl(ByteVector(72 bytes, 0x30450221008337ce3ce0c6ac0ab72509f889c1d52701817a2362d6357457b63e3bdedc0c0602202908963b9cf1a095ab3b34b95ce2bc0d67fb0f19be1cc5f7b3de0b3a325629bf01)), BytesToPushOntoStackImpl(33), ScriptConstantImpl(ByteVector(33 bytes, 0x0241d746ca08da0a668735c3e01c1fa02045f2f399c5937079b6434b5a31dfe353))),List(),List(ScriptVerifyP2SH, ScriptVerifyDerSig, ScriptVerifyStrictEnc, ScriptVerifyMinimalData, ScriptVerifyDiscourageUpgradableNOPs, ScriptVerifyCleanStack, ScriptVerifyCheckLocktimeVerify, ScriptVerifyCheckSequenceVerify, ScriptVerifyLowS, ScriptVerifyWitness, ScriptVerifyMinimalIf, ScriptVerifyNullFail, ScriptVerifyNullDummy, ScriptVerifyWitnessPubKeyType, ScriptVerifyDiscourageUpgradableWitnessProgram))
|
||||
|
||||
val result = ScriptInterpreter.run(preExecution)
|
||||
// result: org.bitcoins.core.script.result.ScriptResult = ScriptOk
|
||||
|
||||
println(s"Script execution result=${result}")
|
||||
// Script execution result=ScriptOk
|
||||
```
|
145
website/versioned_docs/version-v0.4/core/hd-keys.md
Normal file
145
website/versioned_docs/version-v0.4/core/hd-keys.md
Normal file
|
@ -0,0 +1,145 @@
|
|||
---
|
||||
id: version-v0.4-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, 0x8dda5842d7f2c365a0bf9de3a409f69c95c01b660ccf5e5fb1edbe15a5f6a3a4)
|
||||
|
||||
val mnemonicCode = MnemonicCode.fromEntropy(entropy)
|
||||
// mnemonicCode: MnemonicCode = Masked(MnemonicCodeImpl)
|
||||
|
||||
mnemonicCode.words // the phrase the user should write down
|
||||
// res0: Vector[String] = Vector(miss, spot, awful, quiz, club, ready, load, victory, together, cake, laundry, decrease, foster, asset, oblige, okay, furnace, work, kiwi, label, fog, win, model, lottery) // 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 = zpub6jftahH18ngZwuvkBz1bn2EpiEVTh2kPHoCXkj3zRg45pWz3vsTp8WvAvicZuaC2JeeMQiwZMEWcSjcgec6CWGUz65m27VYta2pWq6M848Y
|
||||
|
||||
// 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,
|
||||
HDChangeType.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 = zpub6qYKXoomENPW6viK8Fy9DzWTYCjXeh8j3PK3tYfgjovVg37P6uLNka11HAdemystRPaFBmqVkuB3B69EmJaEFVKNZmyC9qR8N1qoochQefA
|
||||
|
||||
// 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 = tb1qfy0tc064dy4zdxy6kvgllyfy5p67c9ua97p9ty
|
||||
|
||||
// tada! We just generated an address you can send money to,
|
||||
// without having access to the private key!
|
||||
firstAccountAddress.value
|
||||
// res2: String = tb1qfy0tc064dy4zdxy6kvgllyfy5p67c9ua97p9ty
|
||||
|
||||
// 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.
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
id: version-v0.4-lightning-network
|
||||
title: Lightning Network Data Types
|
||||
original_id: lightning-network
|
||||
---
|
||||
|
||||
Bitcoin-S offers support for many different Lightning Network data types.
|
||||
Here are some examples:
|
||||
|
||||
|
||||
```scala
|
||||
val lnInvoiceT: Try[LnInvoice] = LnInvoice.fromStringT(string = "lnbc1u1p0d0k6npp5qndcvujqf47244g6yhd3laqj8c3ckummaahrs375gsgau4upg32qdq623jhxapqv3jhxcmjd9c8g6t0dccqzpgxqrrssrzjqvgptfurj3528snx6e3dtwepafxw5fpzdymw9pj20jj09sunnqmwqz2uagqqjjgqqqqqqqlgqqqqqqgq9qsp5nyg950yy703ah6sazalytaksg0lvyxq3veze5lqswp6rh3jx9css9qy9qsq0d4vhtxkkvse0yx8cgcpemhzatw2wtyvy04ve7n0j3k78l40qpqpvg07vjldvn8zx6nana6glmnlwqvm4fz0pttw7k3rh7wzr48nccsqhnd8wv")
|
||||
// lnInvoiceT: Try[LnInvoice] = Success(lnbc1u1p0d0k6npp5qndcvujqf47244g6yhd3laqj8c3ckummaahrs375gsgau4upg32qdq623jhxapqv3jhxcmjd9c8g6t0dccqzpgxqrrssrzjqvgptfurj3528snx6e3dtwepafxw5fpzdymw9pj20jj09sunnqmwqz2uagqqjjgqqqqqqqlgqqqqqqgq9qsp5nyg950yy703ah6sazalytaksg0lvyxq3veze5lqswp6rh3jx9css9qy9qsq0d4vhtxkkvse0yx8cgcpemhzatw2wtyvy04ve7n0j3k78l40qpqpvg07vjldvn8zx6nana6glmnlwqvm4fz0pttw7k3rh7wzr48nccsqhnd8wv)
|
||||
|
||||
val paymentPreImage = PaymentPreimage(hex"a8c5260174cab81047ba14e8fd96b23074aec7407867343b2d270861e3f0ee66")
|
||||
// paymentPreImage: PaymentPreimage = PaymentPreimage(ByteVector(32 bytes, 0xa8c5260174cab81047ba14e8fd96b23074aec7407867343b2d270861e3f0ee66))
|
||||
|
||||
val paymentSecret = PaymentSecret(hex"f19d44f4d5fd6a25a7dba8cc84954f2156c2d48db4785b586fdea95561c295b8")
|
||||
// paymentSecret: PaymentSecret = PaymentSecret(ByteVector(32 bytes, 0xf19d44f4d5fd6a25a7dba8cc84954f2156c2d48db4785b586fdea95561c295b8))
|
||||
|
||||
val nodeId = NodeId(ECPublicKey("034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa"))
|
||||
// nodeId: NodeId = 034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa
|
||||
|
||||
val millsats100 = MilliSatoshis(BigInt(100))
|
||||
// millsats100: MilliSatoshis = 100 msats
|
||||
```
|
157
website/versioned_docs/version-v0.4/core/psbts.md
Normal file
157
website/versioned_docs/version-v0.4/core/psbts.md
Normal file
|
@ -0,0 +1,157 @@
|
|||
---
|
||||
id: version-v0.4-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 psbtFirstSigF =
|
||||
psbtWithSigHashFlags
|
||||
.sign(inputIndex = 0, signer = privKey0)
|
||||
.flatMap(_.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 signatureF = 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
|
||||
signatureF.map(sig => psbtWithSigHashFlags.addSignature(sig, inputIndex = 0))
|
||||
|
||||
// With our first input signed we can now move on to showing how another party could sign our second input
|
||||
val signedTransactionF = psbtFirstSigF.map { psbtFirstSig =>
|
||||
// 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
|
||||
}
|
||||
|
||||
Await.result(signedTransactionF, 30.seconds)
|
||||
```
|
65
website/versioned_docs/version-v0.4/core/sign.md
Normal file
65
website/versioned_docs/version-v0.4/core/sign.md
Normal file
|
@ -0,0 +1,65 @@
|
|||
---
|
||||
id: version-v0.4-sign
|
||||
title: Sign API
|
||||
original_id: sign
|
||||
---
|
||||
|
||||
### The [`Sign` API](org/bitcoins/core/crypto/Sign.scala)
|
||||
|
||||
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](../../core/src/main/scala/org/bitcoins/core/crypto/Sign.scala):
|
||||
|
||||
```scala
|
||||
import scodec.bits._
|
||||
import org.bitcoins.core.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 [`ECKey`](/api/org/bitcoins/core/crypto/ECKey) 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`](../../core/src/main/scala/org/bitcoins/core/crypto/Sign.scala) API.
|
||||
|
||||
An [ExtKey](org/bitcoins/core/crypto/ExtKey.scala) is a data structure that can be used to generate more keys from a parent key. For more information look at [hd-keys.md](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(3045022100cfeda1a9e7ae6fbd4a608b2da83ce84ff5a58d3ce44f32cf8e6db53b34d66426022047e0573ab74730f4776ca53d653b884918225b5a869cb20890067faf14319ee9)
|
||||
|
||||
val path = BIP32Path(Vector(BIP32Node(0,false)))
|
||||
// path: BIP32Path = m/0
|
||||
|
||||
extPrivKey.sign(DoubleSha256Digest.empty.bytes,path)
|
||||
// res1: ECDigitalSignature = ECDigitalSignature(3045022100c799248b456a4ae3512d143d75e03ca0c6f26d845b39353ca9de8b3b2dff7a1e0220563285830a5a43562a35c005ff996911535ad70c38930f163c26b1650e05610c)
|
||||
```
|
||||
|
||||
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
|
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
id: version-v0.4-spending-info
|
||||
title: Signing Transactions
|
||||
original_id: spending-info
|
||||
---
|
||||
|
||||
# Signing Transactions in Bitcoin-S
|
||||
|
||||
There are two kinds of transaction signing in Bitcoin-S, as defined in `Signer.scala`: Single Signing and Full Signing.
|
||||
|
||||
Single signing, which is implemented as a simple function in `BitcoinSigner`, consists of using a single `ECPrivateKey` (or other [Sign implementation](../crypto/sign.md)) to generate a `PartialSignature` of the given transaction. This consists of an `ECDigitalSignature` and a `ECPublicKey`. This is useful any time you have a single private key and wish to get a single `ECDigitalSignature` for that private key as is the case when using [Partially Signed Bitcoin Transactions](psbts.md). In order to call `signSingle`, you must have access to the unsigned transaction, as well as a `ECSignatureParams`, which encapsulates all the information needed to sign with a single key. Note that if the unsigned transaction has signatures on it, they will be ignored.
|
||||
|
||||
Full signing, which is implemented by the subtypes of `Signer`, consists of using any number of `ECPrivateKey`s (or other [Sign implementations](../crypto/sign.md)) to generate a fully signed, `TxSigComponent` for a given transaction. In order to call `sign` on any instance of `Signer`, you must have access to the unsigned transaction, as well as a `ScriptSignatureParams`, which encapsulates all the information needed to sign with any number of keys. Note that the unsigned transaction can have signatures on it, just not on the input being signed. Specifically, a fully signed `TxSigComponent` has a member called `input` which will contain the fully signed `TransactionInput` as well as `scriptSignature` which contains the `ScriptSignature` that was just generated. Additionally, `TxSigComponent` has a member called `transaction` which returns a transaction which has all inputs of the input unsigned transaction except for it uses the new input that was just generated by the call to `sign` instead of an `EmptyScriptSignature`.
|
162
website/versioned_docs/version-v0.4/core/txbuilder.md
Normal file
162
website/versioned_docs/version-v0.4/core/txbuilder.md
Normal file
|
@ -0,0 +1,162 @@
|
|||
---
|
||||
id: version-v0.4-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@68dbe23a[Running, parallelism = 12, size = 3, active = 0, running = 0, steals = 3, 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(037ad941b7ac16fba77a8f76b111e6254f4645116ed60d742e7641c41541231208)
|
||||
|
||||
// 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(6d52a3bf4776ae367dee519bda02a3f6b03fad7a)
|
||||
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(6d52a3bf4776ae367dee519bda02a3f6b03fad7a))
|
||||
|
||||
// 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(5223486f5cd3b5a8e7b210fd11ac6212f774f204)
|
||||
|
||||
// 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(5223486f5cd3b5a8e7b210fd11ac6212f774f204)))
|
||||
|
||||
// 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(6d52a3bf4776ae367dee519bda02a3f6b03fad7a))),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(76aca317ebbd73347d8c95c9af482c7670cb8e7a9efe383a2909e7eb7dd98ea1:0)
|
||||
val input = TransactionInput(
|
||||
outPoint,
|
||||
EmptyScriptSignature,
|
||||
sequenceNumber = UInt32.zero)
|
||||
// input: TransactionInput = TransactionInputImpl(TransactionOutPoint(76aca317ebbd73347d8c95c9af482c7670cb8e7a9efe383a2909e7eb7dd98ea1: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(76aca317ebbd73347d8c95c9af482c7670cb8e7a9efe383a2909e7eb7dd98ea1:0),EmptyScriptSignature,UInt32Impl(0))),Vector(TransactionOutput(5000 sats,pkh(5223486f5cd3b5a8e7b210fd11ac6212f774f204))),UInt32Impl(0))
|
||||
|
||||
// this contains the information needed to analyze our input during finalization
|
||||
val inputInfo = P2PKHInputInfo(outPoint, amount, privKey.publicKey)
|
||||
// inputInfo: P2PKHInputInfo = P2PKHInputInfo(TransactionOutPoint(76aca317ebbd73347d8c95c9af482c7670cb8e7a9efe383a2909e7eb7dd98ea1:0),10000 sats,ECPublicKey(037ad941b7ac16fba77a8f76b111e6254f4645116ed60d742e7641c41541231208))
|
||||
|
||||
// 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 = SatoshisPerByte(1 sat)
|
||||
|
||||
val changePrivKey = ECPrivateKey.freshPrivateKey
|
||||
// changePrivKey: ECPrivateKey = Masked(ECPrivateKeyImpl)
|
||||
val changeSPK = P2PKHScriptPubKey(pubKey = changePrivKey.publicKey)
|
||||
// changeSPK: P2PKHScriptPubKey = pkh(c3a4486c18cddd735615b246bc0071aee44da46c)
|
||||
|
||||
// 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(76aca317ebbd73347d8c95c9af482c7670cb8e7a9efe383a2909e7eb7dd98ea1:0),10000 sats,ECPublicKey(037ad941b7ac16fba77a8f76b111e6254f4645116ed60d742e7641c41541231208))),SatoshisPerByte(1 sat),pkh(c3a4486c18cddd735615b246bc0071aee44da46c))
|
||||
|
||||
// We can now finalize the tx builder result from earlier with this finalizer
|
||||
val unsignedTxF: Future[Transaction] = finalizer.buildTx(builderResult)
|
||||
// unsignedTxF: Future[Transaction] = Future(Success(BaseTransaction(Int32Impl(2),Vector(TransactionInputImpl(TransactionOutPoint(76aca317ebbd73347d8c95c9af482c7670cb8e7a9efe383a2909e7eb7dd98ea1:0),EmptyScriptSignature,UInt32Impl(0))),Vector(TransactionOutput(5000 sats,pkh(5223486f5cd3b5a8e7b210fd11ac6212f774f204)), TransactionOutput(4774 sats,pkh(c3a4486c18cddd735615b246bc0071aee44da46c))),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(76aca317ebbd73347d8c95c9af482c7670cb8e7a9efe383a2909e7eb7dd98ea1:0),10000 sats,ECPublicKey(037ad941b7ac16fba77a8f76b111e6254f4645116ed60d742e7641c41541231208)),BaseTransaction(Int32Impl(1),Vector(),Vector(TransactionOutput(10000 sats,pkh(6d52a3bf4776ae367dee519bda02a3f6b03fad7a))),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(76aca317ebbd73347d8c95c9af482c7670cb8e7a9efe383a2909e7eb7dd98ea1:0),10000 sats,ECPublicKey(037ad941b7ac16fba77a8f76b111e6254f4645116ed60d742e7641c41541231208)),BaseTransaction(Int32Impl(1),Vector(),Vector(TransactionOutput(10000 sats,pkh(6d52a3bf4776ae367dee519bda02a3f6b03fad7a))),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 = {
|
||||
val signedTxF = unsignedTxF.flatMap { unsignedTx =>
|
||||
RawTxSigner.sign(
|
||||
utx = unsignedTx,
|
||||
utxoInfos = utxoInfos,
|
||||
expectedFeeRate = feeRate
|
||||
)
|
||||
}
|
||||
Await.result(signedTxF, 30.seconds)
|
||||
}
|
||||
// signedTx: Transaction = BaseTransaction(Int32Impl(2),Vector(TransactionInputImpl(TransactionOutPoint(76aca317ebbd73347d8c95c9af482c7670cb8e7a9efe383a2909e7eb7dd98ea1:0),P2PKHScriptSignature(ECPublicKey(037ad941b7ac16fba77a8f76b111e6254f4645116ed60d742e7641c41541231208), ECDigitalSignature(3045022100fe3fb06da1aed7204688a08ddea3be5ac33cfebd9dbeb57e09273b180c4ecfff02202be564c2a51fe23406da1c2da4cfbd53caaf53ac01b6b5a7cd6826435fc1911501)),UInt32Impl(0))),Vector(TransactionOutput(5000 sats,pkh(5223486f5cd3b5a8e7b210fd11ac6212f774f204)), TransactionOutput(4774 sats,pkh(c3a4486c18cddd735615b246bc0071aee44da46c))),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 = 0200000001a18ed97debe709293a38fe9e7a8ecb70762c48afc9958c7d3473bdeb17a3ac76000000006b483045022100fe3fb06da1aed7204688a08ddea3be5ac33cfebd9dbeb57e09273b180c4ecfff02202be564c2a51fe23406da1c2da4cfbd53caaf53ac01b6b5a7cd6826435fc191150121037ad941b7ac16fba77a8f76b111e6254f4645116ed60d742e7641c41541231208000000000288130000000000001976a9145223486f5cd3b5a8e7b210fd11ac6212f774f20488aca6120000000000001976a914c3a4486c18cddd735615b246bc0071aee44da46c88ac00000000
|
||||
```
|
43
website/versioned_docs/version-v0.4/crypto/crypto-intro.md
Normal file
43
website/versioned_docs/version-v0.4/crypto/crypto-intro.md
Normal file
|
@ -0,0 +1,43 @@
|
|||
---
|
||||
id: version-v0.4-crypto-intro
|
||||
title: Crypto Module
|
||||
original_id: crypto-intro
|
||||
---
|
||||
|
||||
The `crypto` module contains the base cryptographic functionality for Bitcoin-s. Specifically, this includes the fundamental cryptographic data structures and functions such as keys, hashing encryption and signing. This module does not include secondary cryptographic types and functions such as those pertaining to BIP 32 or 39, nor does it include signature code specific to Bitcoin transactions. For all of these things, see the [core module](../core/core-intro.md). It is very important to keep this code well tested, and we also try to minimize the dependencies of this module.
|
||||
|
||||
## Dealing with Array[Byte]
|
||||
|
||||
The crypto module contains a trait called `NetworkElement` which is extended by all data structures with byte serializations. This allows for a uniform interface when dealing with serialization and deserialization which can be very important when constructing hashes. Additionally, the `Factory` trait can be extended by companion objects of `NetworkElement`s to allow for out-of-the-box constructors from String hex or bytes in little or big endian given one constructor. The `CryptoUtil` object provides easy access to hash functions and `HashDigest` wraps the results in nice types. Lastly, this module also contains a `BytesUtil` for dealing with raw bytes and raw hex.
|
||||
|
||||
## AES Encryption
|
||||
|
||||
The crypto module contains support for AES using the object `AesCrypt` and its companion data structures.
|
||||
|
||||
## Elliptic Curve Keys and Functions
|
||||
|
||||
`ECKey.scala` contains types `ECPublicKey` and `ECPrivateKey` which represent private and public keys for the secp256k1 curve (used by Bitcoin). These keys also implement functions for using them to create and verify `ECDigitalSignature`s and `ECPrivateKey` implements the [`Sign` interface](sign.md). Note that all sensitive information (such as private keys) extends the `MaskToString` interface which overrides the `toString` method to mask the actual result unless `toStringSensitive` is explicitly called. This is done to avoid accidentally logging keys.
|
||||
|
||||
Utility functions for signatures (such as checking for or flipping to low s) can be found in `DERSignatureUtil`.
|
||||
|
||||
Lastly, Bitcoin-S uses Java Native Interface (JNI) bindings to the Bitcoin library secp256k1 to execute cryptographic functions by default. See [this doc](../secp256k1/secp256k1.md) for more details. However, fall-back implementations of all cryptographic functions supported and used exist using the bouncy castle library in `BouncyCastleUtil`.
|
||||
|
||||
## Basic Examples
|
||||
|
||||
```scala
|
||||
// Randomly generate new key
|
||||
val privateKey = ECPrivateKey.freshPrivateKey
|
||||
|
||||
// Construct private key from hex
|
||||
val privateKeyFixed = ECPrivateKey("6846a082d76e7c34cd2deddc6ef3d4cb3220e6c72c7c9ec03408d60ed976837c")
|
||||
|
||||
// Compute public key from private key
|
||||
val publicKey = privateKey.publicKey
|
||||
|
||||
// Take SHA256 hash
|
||||
val hash = CryptoUtil.sha256(ByteVector("Hello".getBytes()))
|
||||
|
||||
// Sign and verify signature
|
||||
val sig = privateKey.sign(hash)
|
||||
val validSig = publicKey.verify(hash, sig)
|
||||
```
|
65
website/versioned_docs/version-v0.4/crypto/sign.md
Normal file
65
website/versioned_docs/version-v0.4/crypto/sign.md
Normal file
|
@ -0,0 +1,65 @@
|
|||
---
|
||||
id: version-v0.4-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 [`ECKey`](/api/org/bitcoins/crypto/ECKey) 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(3044022034008a40435a0bd15cf4b54630188d677bfb81283e704364c6fcb55addfcd6e4022026abe200f4d890ff4e9957764d0e267cad8622283e145a2a899bf29605b35e2c)
|
||||
|
||||
val path = BIP32Path(Vector(BIP32Node(0,false)))
|
||||
// path: BIP32Path = m/0
|
||||
|
||||
extPrivKey.sign(DoubleSha256Digest.empty.bytes,path)
|
||||
// res1: ECDigitalSignature = ECDigitalSignature(3044022006846c117ab4911901e370e5e7e308dd6dd4701e6f73d7a6188716132a0690e302206bb4cd3245c4cfbd157b9cb8ed0f89ff1a8768bb092014f363b77746bc80db60)
|
||||
```
|
||||
|
||||
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
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
id: version-v0.4-fee-provider
|
||||
title: Fee Provider
|
||||
original_id: fee-provider
|
||||
---
|
||||
|
||||
# Fee Provider
|
||||
|
||||
Bitcoin-S has a `FeeProvider` that is used to fetch fee rates.
|
||||
Currently, Bitcoin-s has a couple implemented, one being a `BitcoindRpcClient`, which will use `estimateSmartFee` to calculate a fee rate.
|
||||
Another uses [bitcoiner.live's api](https://bitcoiner.live/) to get a fee rate.
|
||||
|
||||
Any `FeeProvider` can be passed to a `Wallet` which will be used to calculate fees for transactions when one is not specified.
|
||||
|
||||
## HttpFeeRateProvider
|
||||
|
||||
A `HttpFeeRateProvider` is a `FeeProvider` that uses an outside API to get fee rates.
|
||||
These can be hooked up to any website's API as long as you can provide a `URI` and a function to convert the response to a `FeeUnit`.
|
||||
|
||||
There also exists `CachedHttpFeeRateProvider`, which will cache the response for the `cacheDuration` as to prevent hitting request limits and save on api calls.
|
||||
|
||||
Checkout [`BitcoinerLiveFeeRateProvider`'s implementation](https://github.com/bitcoin-s/bitcoin-s/blob/master/fee-provider/src/main/scala/org/bitcoins/feeprovider/BitcoinerLiveFeeRateProvider.scala) for an example.
|
164
website/versioned_docs/version-v0.4/getting-setup.md
Normal file
164
website/versioned_docs/version-v0.4/getting-setup.md
Normal file
|
@ -0,0 +1,164 @@
|
|||
---
|
||||
id: version-v0.4-getting-setup
|
||||
title: Getting Bitcoin-S installed on your machine
|
||||
original_id: getting-setup
|
||||
---
|
||||
|
||||
## 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-neutrino-node)
|
||||
- [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 --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 --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.
|
||||
|
||||
You should be able to test your secp256k1 installation by running `sbt core/console` in your bitcoin-s directory and then running
|
||||
|
||||
```scala
|
||||
scala> import org.bitcoin._
|
||||
import org.bitcoin._
|
||||
|
||||
scala> Secp256k1Context.isEnabled()
|
||||
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
|
||||
SLF4J: Defaulting to no-operation (NOP) logger implementation
|
||||
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
|
||||
res0: Boolean = true
|
||||
```
|
||||
|
||||
where the important thing is that the function returns `true`, and you can ignore SLF4J errors.
|
||||
|
||||
Note: To exit the `sbt console`, you can execute `:quit`, and for general help, run `:help`.
|
||||
|
||||
We will now download all of the bitcoind and eclair binaries needed with the following two commands
|
||||
|
||||
```bashrc
|
||||
sbt downloadBitcoind
|
||||
sbt downloadEclair
|
||||
```
|
||||
|
||||
Lastly, you can test that your bitcoin-s build is functional by running
|
||||
|
||||
```bashrc
|
||||
sbt test
|
||||
```
|
||||
|
||||
## 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](applications/configuration#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]
|
||||
rpcport=18332
|
||||
daemon=1
|
||||
deprecatedrpc=signrawtransaction
|
||||
blockfilterindex=1
|
||||
debug=1
|
||||
txindex=1
|
||||
zmqpubrawblock=tcp://127.0.0.1:29000
|
||||
zmqpubrawtx=tcp://127.0.0.1:29000
|
||||
```
|
||||
|
||||
## Step 4 (Optional): Discreet Log Contract Branch
|
||||
|
||||
In order to run the Bitcoin-S server with DLCs enabled, you will have to checkout the `dlc` feature branch:
|
||||
|
||||
```bashrc
|
||||
git fetch origin
|
||||
git checkout adaptor-dlc
|
||||
git submodule update
|
||||
```
|
||||
|
||||
and then finally test that `Secp256k1Context.isEnabled()` as in Step 2.
|
||||
|
||||
## Step 5: Setting Up A Bitcoin-S Server (Neutrino Node)
|
||||
|
||||
We are finally ready to start running some programs! Follow the [instructions here](applications/server#building-the-server) to build the server. Then, follow [these instructions](applications/cli) to setup the CLI.
|
||||
|
||||
If you wish to use the GUI instead of the CLI, the setup process is the same as building the server but for the project `gui`. That is, you must run
|
||||
|
||||
```bashrc
|
||||
sbt gui/universal:stage
|
||||
```
|
||||
|
||||
to build and then the following (once setup is complete) to run:
|
||||
|
||||
```bashrc
|
||||
./app/gui/target/universal/stage/bin/bitcoin-s-gui
|
||||
```
|
||||
|
||||
Now, you want to run your `bitcoind` in regtest by doing the following command:
|
||||
|
||||
```bashrc
|
||||
$HOME/.bitcoin-s/binaries/bitcoind/bitcoin-0.18.99/bin/bitcoind --datadir=[path/to/conf/directory] --rpcport=18332
|
||||
```
|
||||
|
||||
Once the node is running, you should be able to start your bitcoin-s 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
|
||||
```
|
||||
|
||||
To fund your wallet, you should use the CLI's `getnewaddress` command and then run
|
||||
|
||||
```bashrc
|
||||
$HOME/.bitcoin-s/binaries/bitcoind/bitcoin-0.18.99/bin/bitcoin-cli --datadir=[path/to/conf/directory] --rpcport=18332 generatetoaddress 200 [address]
|
||||
```
|
||||
|
||||
There is currently a bug on regtest where the server is unable to handle too many blocks at once, so when generating more than a couple blocks (like above), it is recommended you shut down your server and restart it after the blocks have been created.
|
||||
|
||||
## 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/testnet-chaindump-2-25-2020.zip), unzip it and put the file in your `$HOME/.bitcoin-s/testnet3` directory and then from there, run
|
||||
|
||||
```bashrc
|
||||
cat chaindump.sql | sqlite3 chaindb.sqlite
|
||||
```
|
||||
|
||||
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.
|
111
website/versioned_docs/version-v0.4/getting-started.md
Normal file
111
website/versioned_docs/version-v0.4/getting-started.md
Normal file
|
@ -0,0 +1,111 @@
|
|||
---
|
||||
id: version-v0.4-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/)!
|
||||
|
||||
## If you want to setup Bitcoin-S locally
|
||||
|
||||
Then go to [this document](getting-setup.md).
|
||||
|
||||
## REPL
|
||||
|
||||
You can try out Bitcoin-S in a REPL in a matter of seconds. Run the provided
|
||||
["try bitcoin-s"](https://github.com/bitcoin-s/bitcoin-s-core/blob/master/try-bitcoin-s.sh)
|
||||
script, which has no dependencies other than an installed *Java 8*. The script
|
||||
downloads and installs [Coursier](https://get-coursier.io/) and uses it to
|
||||
fetch the [Ammonite](https://ammonite.io) REPL and the latest version of
|
||||
Bitcoin-S. It then drops you into immediately into a REPL session.
|
||||
|
||||
```bash
|
||||
$ curl -s https://raw.githubusercontent.com/bitcoin-s/bitcoin-s/master/try-bitcoin-s.sh | bash
|
||||
Loading...
|
||||
Welcome the Bitcoin-S REPL, powered by Ammonite
|
||||
Check out our documentation and examples at
|
||||
https://bitcoin-s.org/docs/getting-started
|
||||
@ val priv = ECPrivateKey()
|
||||
@ val pub = priv.publicKey
|
||||
@ val spk = P2WPKHWitnessSPKV0(pub)
|
||||
@ val address = Bech32Address(spk, MainNet)
|
||||
@ address.value # Tada! You've just made a Bech32 address
|
||||
res4: String = "bc1q7ynsz7tamtnvlmts4snrl7e98jc9d8gqwsjsr5"
|
||||
```
|
||||
|
||||
## Getting prebuilt JARs
|
||||
|
||||
If you want to add Bitcoin-S to your project, follow the
|
||||
instructions for your build tool
|
||||
|
||||
### sbt
|
||||
|
||||
Add this to your `build.sbt`:
|
||||
|
||||
```scala
|
||||
libraryDependencies +="org.bitcoin-s" % "bitcoin-s-secp256k1jni" % "0.3.0"
|
||||
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-core" % "0.3.0"
|
||||
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-chain" % "0.3.0"
|
||||
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-bitcoind-rpc" % "0.3.0"
|
||||
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-eclair-rpc" % "0.3.0"
|
||||
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-key-manager" % "0.3.0"
|
||||
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-node" % "0.3.0"
|
||||
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-wallet" % "0.3.0"
|
||||
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-testkit" % "0.3.0"
|
||||
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-zmq" % "0.3.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.3.0+422-bd94ff15+20200901-0943-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/
|
||||
|
||||
### Mill
|
||||
|
||||
TODO
|
||||
|
||||
## Building JARs yourself
|
||||
|
||||
If you want to build Bitcoin-S JARs yourself, you need to use the
|
||||
[sbt](https://www.scala-sbt.org/) build tool. Once you have sbt
|
||||
installed, run `sbt publishLocal`. This places the required JAR
|
||||
files in your `.ivy2/local` folder. On Linux, this is located at
|
||||
`$HOME/.ivy2/local/` by default.
|
133
website/versioned_docs/version-v0.4/key-manager/key-manager.md
Normal file
133
website/versioned_docs/version-v0.4/key-manager/key-manager.md
Normal file
|
@ -0,0 +1,133 @@
|
|||
---
|
||||
id: version-v0.4-key-manager
|
||||
title: Key Manager
|
||||
original_id: key-manager
|
||||
---
|
||||
|
||||
|
||||
### Key Manager
|
||||
|
||||
The key manager module's goal is to encapusulate 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.
|
||||
|
||||
### Disclaimer
|
||||
|
||||
Currently bip39 password is supported at the library level, but is not supported for end users using the server project.
|
||||
[You can see that the bip39 password is hard coded to `None` here](https://github.com/bitcoin-s/bitcoin-s/blob/e387d075b0ff2e0a0fec15788fcb48e4ddc4d9d5/app/server/src/main/scala/org/bitcoins/server/Main.scala#L53).
|
||||
|
||||
There is a password that is used to encrypt your mnemonic seed on disk, but that password is hard coded to a default value.
|
||||
THIS MEANS THAT YOUR MNEMONIC SEED CAN TRIVIALLY BE STOLEN IF AN ATTACKER CAN ACCESS YOUR HARD DRIVE.
|
||||
TAKE PROPER OPSEC PRECAUTIONS.
|
||||
|
||||
Overall the key manager module should be considered insecure. For this release, it is more about setting up the module
|
||||
as a logical distinction for further development in subsequent releases.
|
||||
|
||||
#### 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, 0x11552db15c62846ffcefafb91be2dd5d15537e4f9ac8c65262d8801ffad1de6b)
|
||||
|
||||
val mnemonic = MnemonicCode.fromEntropy(entropy)
|
||||
// mnemonic: MnemonicCode = Masked(MnemonicCodeImpl)
|
||||
|
||||
//you can print that mnemonic seed with this
|
||||
println(mnemonic.words)
|
||||
// Vector(bacon, practice, hobby, rhythm, choose, dawn, victory, vote, ribbon, tenant, resemble, riot, festival, sand, dinosaur, flip, shock, narrow, renew, ability, zero, happy, keen, goddess)
|
||||
```
|
||||
|
||||
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-example7012968728506996023/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-example7012968728506996023/encrypted-bitcoin-s-seed.json,m/84',RegTest)
|
||||
|
||||
val km = BIP39KeyManager.initializeWithMnemonic(mnemonic, None, kmParams)
|
||||
// km: Either[KeyManagerInitializeError, BIP39KeyManager] = Right(BIP39KeyManager(Masked(MnemonicCodeImpl),KeyManagerParams(/tmp/key-manager-example7012968728506996023/encrypted-bitcoin-s-seed.json,m/84',RegTest),None,2020-09-01T14:59:55Z))
|
||||
|
||||
val rootXPub = km.right.get.getRootXPub
|
||||
// rootXPub: ExtPublicKey = vpub5SLqN2bLY4WeYYQGLhqfVQXHXAFzCUycX8Y3Z2JGy6R7DJDHyZbMcYafkNzZZzPNET7vznX74rGmKiBPV9CCyTJZec2ZYDdkZ3YKBodrSAe
|
||||
|
||||
println(rootXPub)
|
||||
// vpub5SLqN2bLY4WeYYQGLhqfVQXHXAFzCUycX8Y3Z2JGy6R7DJDHyZbMcYafkNzZZzPNET7vznX74rGmKiBPV9CCyTJZec2ZYDdkZ3YKBodrSAe
|
||||
```
|
||||
|
||||
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-example7012968728506996023/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(mnemonic, mainnetKmParams, None, Instant.now)
|
||||
// mainnetKeyManager: BIP39KeyManager = BIP39KeyManager(Masked(MnemonicCodeImpl),KeyManagerParams(/tmp/key-manager-example7012968728506996023/encrypted-bitcoin-s-seed.json,m/84',MainNet),None,2020-09-01T14:59:55.980707Z)
|
||||
|
||||
val mainnetXpub = mainnetKeyManager.getRootXPub
|
||||
// mainnetXpub: ExtPublicKey = zpub6jftahH18ngZwjAjg8zAKkuJD2qmxxwcBacvgbspV7vdRhUCzCFc6oDDqCpuZd13s1b9zguLuVgxrrdeMvrGAQ2y7xpFsruhdwntk6U7riN
|
||||
|
||||
println(mainnetXpub)
|
||||
// zpub6jftahH18ngZwjAjg8zAKkuJD2qmxxwcBacvgbspV7vdRhUCzCFc6oDDqCpuZd13s1b9zguLuVgxrrdeMvrGAQ2y7xpFsruhdwntk6U7riN
|
||||
```
|
||||
|
||||
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
|
92
website/versioned_docs/version-v0.4/node/node-api.md
Normal file
92
website/versioned_docs/version-v0.4/node/node-api.md
Normal file
|
@ -0,0 +1,92 @@
|
|||
---
|
||||
id: version-v0.4-node-api
|
||||
title: Node API
|
||||
original_id: 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
|
||||
|
||||
// Create our key manager
|
||||
val keyManagerE = BIP39KeyManager.initialize(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 broadcastTransaction(transaction: Transaction): Future[Unit] = {
|
||||
bitcoind.sendRawTransaction(transaction).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))
|
||||
```
|
135
website/versioned_docs/version-v0.4/node/node.md
Normal file
135
website/versioned_docs/version-v0.4/node/node.md
Normal file
|
@ -0,0 +1,135 @@
|
|||
---
|
||||
id: version-v0.4-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.
|
||||
|
||||
#### 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. Unforunately bitcoin core has not merged neutrino
|
||||
p2p network support yet ([pr here](https://github.com/bitcoin/bitcoin/pull/16442)) which means that we have built a custom binary and host it ourselves. You need
|
||||
to make sure to run `sbt downloadBitcoind` and then look for the `bitcoind` binary with neutrino support in
|
||||
`$HOME/.bitcoin-s/binaries/bitcoind/bitcoin-0.18.99/`. This binary is built from the open PR on bitcoin core.
|
||||
|
||||
|
||||
```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)
|
||||
|
||||
//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 {
|
||||
_ <- chainApiF
|
||||
peer <- peerF
|
||||
} yield {
|
||||
NeutrinoNode(nodePeer = peer,
|
||||
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)
|
||||
```
|
60
website/versioned_docs/version-v0.4/rpc/eclair.md
Normal file
60
website/versioned_docs/version-v0.4/rpc/eclair.md
Normal file
|
@ -0,0 +1,60 @@
|
|||
---
|
||||
id: version-v0.4-rpc-eclair
|
||||
title: Eclair
|
||||
original_id: rpc-eclair
|
||||
---
|
||||
|
||||
This is a RPC client for [Eclair](https://github.com/acinq/eclair). It assumes that a bitcoind instance is running.
|
||||
|
||||
Currently this RPC client is written for [v0.4.1](https://github.com/ACINQ/eclair/releases/tag/v0.4.1) version of Eclair.
|
||||
|
||||
## Configuration of Eclair
|
||||
|
||||
Please see the configuration secion of the
|
||||
[Eclair README](https://github.com/acinq/eclair#configuring-eclair).
|
||||
|
||||
You can find the configuration we use for our testing infrastrture for eclair [here](https://github.com/bitcoin-s/bitcoin-s/blob/a043d3858ef33da51229ee59c478d2a6c9d5a46f/testkit/src/main/scala/org/bitcoins/testkit/eclair/rpc/EclairRpcTestUtil.scala#L98).
|
||||
|
||||
## Starting Eclair
|
||||
|
||||
You need to download the jar from the [eclair's github](https://github.com/ACINQ/eclair/releases/tag/v0.4.1).
|
||||
|
||||
To run Eclair by unzipping the `eclair-node-0.4.1-e5fb281-bin.zip` and then running
|
||||
|
||||
```bash
|
||||
$ ./eclair-node-0.4-69c538e/bin/eclair-node.sh
|
||||
```
|
||||
|
||||
If you wish to start Eclair from the RPC client, you can do one of the following:
|
||||
|
||||
1. Construct a [`EclairRpcClient.binary`](https://github.com/bitcoin-s/bitcoin-s/blob/a043d3858ef33da51229ee59c478d2a6c9d5a46f/eclair-rpc/src/main/scala/org/bitcoins/eclair/rpc/client/EclairRpcClient.scala#L51) field set
|
||||
2. Set the [`ECLAIR_PATH`](https://github.com/bitcoin-s/bitcoin-s/blob/a043d3858ef33da51229ee59c478d2a6c9d5a46f/eclair-rpc/src/main/scala/org/bitcoins/eclair/rpc/client/EclairRpcClient.scala#L701) environment variable to the directory where the Eclair Jar is located.
|
||||
|
||||
We will default to using the `binary` field first when trying to start the jar, and the fallback to `ECLAIR_PATH`.
|
||||
|
||||
Here is an example of how to start eclair:
|
||||
|
||||
|
||||
```scala
|
||||
implicit val system = ActorSystem(s"eclair-rpc-${System.currentTimeMillis}")
|
||||
implicit val ec = system.dispatcher
|
||||
|
||||
val datadirPath = Paths.get("path", "to", "datadir")
|
||||
val binaryPath = Paths.get("path", "to", "eclair-node-0.3.3-12ac145.jar")
|
||||
val instance = EclairInstance.fromDatadir(datadirPath.toFile,None)
|
||||
val client = new EclairRpcClient(instance, Some(binaryPath.toFile))
|
||||
|
||||
val startedF = client.start()
|
||||
|
||||
for {
|
||||
eclair <- startedF
|
||||
info <- eclair.getInfo
|
||||
} yield {
|
||||
println(s"Eclair info: $info")
|
||||
}
|
||||
```
|
||||
|
||||
### Connecting to the websocket
|
||||
|
||||
As of `v0.3.3` eclair supports a websocket endpoint. This means you can receive updates of what is happening with eclair
|
||||
in real time. You can see an example of us testing this [here](https://github.com/bitcoin-s/bitcoin-s/blob/a043d3858ef33da51229ee59c478d2a6c9d5a46f/eclair-rpc-test/src/test/scala/org/bitcoins/eclair/rpc/EclairRpcClientTest.scala#L591)
|
441
website/versioned_docs/version-v0.4/secp256k1/add-to-jni.md
Normal file
441
website/versioned_docs/version-v0.4/secp256k1/add-to-jni.md
Normal file
|
@ -0,0 +1,441 @@
|
|||
---
|
||||
id: version-v0.4-jni-modify
|
||||
title: Adding to Secp256k1 JNI
|
||||
original_id: jni-modify
|
||||
---
|
||||
|
||||
Bitcoin-S uses a Java Native Interface (JNI) to execute functions in [secp256k1](https://github.com/bitcoin-core/secp256k1) 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/jonasnick/secp256k1/pull/14) 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 calls to `DatatypeConverter` to either `toByteArray` or `toHex` as well as changing the method to make it non-`static` as well as adding the `@Test` annotation above the 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
|
||||
make
|
||||
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-experimental --enable-module_ecdh --enable-jni && make clean && make CFLAGS="-std=c99"
|
||||
```
|
||||
|
||||
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
|
89
website/versioned_docs/version-v0.4/secp256k1/secp256k1.md
Normal file
89
website/versioned_docs/version-v0.4/secp256k1/secp256k1.md
Normal file
|
@ -0,0 +1,89 @@
|
|||
---
|
||||
id: version-v0.4-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)
|
||||
val publicKey = privKey.publicKeyWithBouncyCastle
|
||||
// publicKey: ECPublicKey = ECPublicKey(028d34d1927269c1b9756dfe24159984b33c7a083d78fad651512656e99d4bc2ab)
|
||||
val dataToSign = DoubleSha256Digest.empty
|
||||
// dataToSign: DoubleSha256Digest = DoubleSha256Digest(0000000000000000000000000000000000000000000000000000000000000000)
|
||||
|
||||
val signature = privKey.signWithBouncyCastle(dataToSign.bytes)
|
||||
// signature: ECDigitalSignature = ECDigitalSignature(3045022100b53eb423b311740cfc3bbdb3227d2bf9fb512c4f74a47ce839f5f29227fb16ba0220226edf38d1adb9f7b1d03a2119404566902aed0ebf42269f900ce14265e1732b)
|
||||
|
||||
val verified = publicKey.verifyWithBouncyCastle(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)
|
130
website/versioned_docs/version-v0.4/security.md
Normal file
130
website/versioned_docs/version-v0.4/security.md
Normal file
|
@ -0,0 +1,130 @@
|
|||
---
|
||||
id: version-v0.4-security
|
||||
title: Security
|
||||
original_id: security
|
||||
---
|
||||
|
||||
The Bitcoin-S developers take security very seriously. This library has
|
||||
very few dependencies (at least in the `core` module), which is for
|
||||
security reasons.
|
||||
|
||||
## Disclosure
|
||||
|
||||
If you have any security disclosures related to Bitcoin-S, please send an
|
||||
email to either [stewart.chris1234@gmail.com](mailto:stewart.chris1234@gmail.com?subject=Bitcoin-S%20Security%20Disclosure),
|
||||
[nadavk25@gmail.com](mailto:nadavk25@gmail.com?subject=Bitcoin-S%20Security%20Disclosure),
|
||||
or [benthecarman@live.com](mailto:benthecarman@live.com?subject=Bitcoin-S%20Security%20Disclosure).
|
||||
|
||||
If you want to encrypt said email (which you should), Ben's key is available on [his Keybase](https://keybase.io/benthecarman/pgp_keys.asc?fingerprint=0ad83877c1f0cd1ee9bd660ad7cc770b81fd22a8), and Chris and Nadav's
|
||||
are posted below:
|
||||
|
||||
Chris:
|
||||
```
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
mQINBF53d98BEAC6oxrqPIN2pzd6ScJ8N48rOQS0S7QP4owXAObkXIzH0I0XawOZ
|
||||
cPAAPnO1NQnTABL7XWuIYWxAeDGoe5CbZENGenKdCBDsNwB0mQuLkyUdFqADJkHQ
|
||||
dAeTeuFFyifSJPGCsOcuXr25AW1QDUlfzXsWfIPib3gzonoTI8yK4D5/n6fVrrjj
|
||||
6JCCQ6a0M74k85AsyJV+mBjRVbmt0vQ0egJTwLyTTCYKSOBofbv0AFPjp4b2lzrB
|
||||
c+O0JkmPas2FqNFVD08S63tGjI8k2ll6WPzen8kDdt+wvJI0R3jdJO02Z1BxphA/
|
||||
kGWeyfgay6LLOPy/DlSE5BPwoSaAb7R2VEEALC/JoP8pjeACCoIcekV0xWU6x2j1
|
||||
G4onlPN0+GI5ybLQR5iLFY86IkhaNMy6v+s0LgUs1ZIPx8me1m+YZaY6UpYIDL8I
|
||||
QA9c5nXfwZWh+htbn7dfE5UOVYr6b9uQllEaoJkms/DUa92CMaNCNvBMWRazEQqF
|
||||
g6YR2hV9eR/fF7MAwyWz0p0UopAkx1SvGrrtcPq9FUNpW+fTt06eKwICES8a055W
|
||||
3H8gBH2dFSWWv9GZSiQ0/6E1jfqIi42NlIdd2yYnTujIrKZt0hyxtS6eqAsokRfX
|
||||
5+2zUFJZYOurQcuEsqW/Bs8Qfn5bpaspbbcL9REuEXmEwAp3OxCBUks18QARAQAB
|
||||
tCtDaHJpcyBTdGV3YXJ0IDxzdGV3YXJ0LmNocmlzMTIzNEBnbWFpbC5jb20+iQJU
|
||||
BBMBCgA+FiEEM5pJIpV2BQgZCD6z+Zckhy+CKRAFAl53d98CGwMFCQPCZwAFCwkI
|
||||
BwIGFQoJCAsCBBYCAwECHgECF4AACgkQ+Zckhy+CKRD3sw//bcv5+kRhGXwaaQgR
|
||||
CYdJFf77ZjBdoS0oEVnbLz40kWu+z/Cy7FaYBEBecGPJwjnzDb1yHEDUq+t7funi
|
||||
6KBAo0HctnxoTDrpH7dIlaK7tIDxKaSM67pbZrP+AQWi6F3PsNBScTl5NrKsOTTC
|
||||
Q+q2TS+X9ZmzDDlNLGMQPhPRg2ak9lkmhX7wVxOYA13c8YhirtCXipzcqpC76qKz
|
||||
VjklfahNKYtFRlfiJ3ZlLdjyEXJoyIw1cDhmmNGI0hXarlUbS4P8Ux43B+x1nYv0
|
||||
s3d2rM49lNU2uQ5LCb24h9ViKlm3qcJ6iba5QDaRuWwpII7mAf1PcLYSHNGR7GRx
|
||||
m0iZqFzG/QuNb/mFzy2AaC8TZAQkQd0Rd7ZpKlTXoWs2yqRtM2vjjHEbwGFEZvH4
|
||||
dM9vBIOrV+fL/CkjMu5ZBXkLj2oH9kYRVxnD7H2a6ZGL63nXqVCVEaPLgDdmWkgy
|
||||
Wp6f1RumOjQE4nXpKfHP8xm8qKS4FAr6gB/7r1sJ4j0p7AXYA1YYP9zEFCZ3Itom
|
||||
yMTLg9S8iO2Nljd3Y2fkTJpCe35ksw9X5yOV+x4PjYFGcvcDYd44vI65LyJ5j71C
|
||||
YpxLUdI5a6k+YClvpT/G8PzllIR1B9m0SLYMNebvY82GzZGmDTX/3Qaxt+xZNqHz
|
||||
h+NhpoPpJwNWd4RJZf7ebH6FfBC5Ag0EXnd33wEQALGFwe+Wpy1NjHy82jJHEwiK
|
||||
0bJLZ/IDc6Vcn2nVYDEI4C3X+RiEQ+JZ7QZFgW8hiHFaQ40Cx3gAGPGysgPQ6pW+
|
||||
42w1BiDaq3AIqBBAyYJbcc2qRLb04/BAKerfYS7KPzusUGfLXHUFHIYu/p03wOrU
|
||||
VXTFA4tCOnDGQOq+R3D8JQzsGBVSbzkIkZDIgsS/e0Xpp8e/BFOcol2fQMGrUROF
|
||||
QMiprL0smLpv4qjuYsVWFZd6w+7nHExmf5OndMsNBUhcYcggED8oKtotfFSzJG9Y
|
||||
V0G+0yV/6dFzw5wiPkjyN80pDQdT80gEJC1JfWtTXXT+EZ6JpiRRfZyZsA/c8wXJ
|
||||
rI+FscvtLWTKzKdMHv7a7gqfFOj9XbxuriZFez3DhyEIOynbjCVGX+U4Nqp7b93B
|
||||
RrNPF8a9DIeVI3IdrK9NclAlHXPeZwVWPUmP3ibf9SE5Asf6dEMtZBx2MJXlHp6I
|
||||
y0/pz1a8XIqVrX1H6A8QtjlA+DlWqA/RtVRPa67E96u3d5n5SyQMua9BE8h55X35
|
||||
+nbIEvu2+PIjZAUAxMrJGzXzeDhHiYeZdGLQ+gu75h0x9jjdeHL3Wqf4IizTJs3R
|
||||
nvri0PvxJcyheBAvjrOgViFEdMF3MFD5TVxS7Y2w0pxg+aTV0Uve4xR7LBHAeyjO
|
||||
Q8A9wNXh3Pfy+M8wGA+zABEBAAGJAjwEGAEKACYWIQQzmkkilXYFCBkIPrP5lySH
|
||||
L4IpEAUCXnd33wIbDAUJA8JnAAAKCRD5lySHL4IpEJnKD/9ZBlJmaIUchSsZUvgz
|
||||
sas+fqHUPQWkTMHnWFfT3iQwjiQrvjENN3ys6B7sKics+ytfVGDuv1dBsuZ+oQNK
|
||||
jxvpauoCQAo3me7jOIwNghSdbDtOKhr6eC2Eap+6zYSyWhFhVexww76i4Pv0/+Rw
|
||||
JOU8yM9SODwWgE7GyLmcU2TPRCZPZDNUS95+3X/zWUA9cNdYR+JYGN+Vp2Ii1QVA
|
||||
DSUe3DCmskdED6EtnHxmF304H5X6Ec/b2XwnC7Ug1FyMGneO96W12I2jI0Zc8icb
|
||||
DD8Jra+uAk5mR4ysRv8YgE9vBfI2K2LWcN9Xs79MghZ5867Ifvgo6kmQTyd5dNCG
|
||||
wYg5yjOd2pAVtIP8YuSvYqphqrykMyC8AH0mqqRqQAMT8LWo69cJGU4kVrpArA84
|
||||
u9v0kAbM+LsnzSzCQxrQKeRPJNDrg28X47ANKSnqukqAQiS95uDXAEXslS8cIpgu
|
||||
fqbcQubec6ZEvB59MoxoRsVZ2p2VaWFPMakR4ssdiM2Yl8XZDqhUDBQ4v0ELolj9
|
||||
uqH5u5Z6JUL3NnPd4hK4LqYoIPYhgHEjriMB6cslk2dcn//8xjaH4W7Ri6Ud/Lpx
|
||||
URnYhu0yULf1X6/79i1cN8WfrDexB5X1WGipNphkuPSHElBnQ2koO45I+h+PIk8K
|
||||
+cOyekIHrOA3YqR2V6Ck6NjRxA==
|
||||
=e/f8
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
```
|
||||
|
||||
Nadav:
|
||||
```
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
mQINBFsSA9kBEACfC4nxfmQAw8bepv4uE3v9I9hOJ63bH/DKcrslkSmxEUW6e+A9
|
||||
LLqvejlRclXVcYz6D6GxHTeS1PVVqHK3++CG2QfeTIsRdG3gMt8LhamJ/m4hmPYu
|
||||
5JRVjMvY2ORvO4owQcT5YSNWPMEUdK4Y6NRXPK/9mVvTIsGaiqxAoeGRgZ5TXCVV
|
||||
bEPRBGY2PsVPCq4QPmw+jqQfh8tLtLj7r44kbjTGj8tO5+KDnZpM7Ed4A41LTmbR
|
||||
zYmxPApRfgEZrHy5GpnvYI/EcpEBrftda1KRVCX1uhPuvHKcFy0mJ/ctJX4ocIBc
|
||||
7sPbrf2WIU/FgrZgtdEjC4smRuIe/oeryuQDcHThImMLe/iehrVoYY/dgVJ05Kpq
|
||||
/No/mDGpN/7S+Nylj9FZaCzbjrg3xlm/gxmwxZBRBoxH0r7MQrY8XkYKw/j+804o
|
||||
fZmoWNW/aCrHpRHm+yd88AdlYEAKJv8l2AFgnXfszO0b5ax6ArZXI/zTVIFLebwn
|
||||
msqCkgeZptQpzV2moMAHQiUsshyhdu8SVx6f9alRyhLbUxYyrm82Rp+4XhZxNf0w
|
||||
rJUMAe7uq8c85RWhWml46jIIasx7oaG0VBRjaRlbqvfu2ST5RowKAGs36aF089MM
|
||||
DHH751MSPQlTR+lCP+nxHM7t2+kFJ4VfuIy6/aOyVGLlNqEeMr/fYlQdpwARAQAB
|
||||
tChOYWRhdiBLb2hlbiAoTGludXgpIDxuYWRhdmsyNUBnbWFpbC5jb20+iQJOBBMB
|
||||
CgA4FiEE2HuwjJv2Vb/j2Shhnqz5XGuw42IFAlsSA9kCGwMFCwkIBwIGFQoJCAsC
|
||||
BBYCAwECHgECF4AACgkQnqz5XGuw42JyUw/9HNAdcCx0ji02rBmrfVNc2EYaV5v6
|
||||
t+fcHxEC4NJB2ilmtE9bxxQgLof0Yb27DTxFvG3AHfds6Xwgzh0W8caZFguaGk8m
|
||||
kEIdi33lK1H3VGBzuJzIfNqd08iZppTZ+hV8gxdq5APrY3AFjQM3jNAdmVjPn6P5
|
||||
GkiOWaVdqS7OnrpZmqOblQGjyB8WrIBP17lrQRN487NtaCbRTSxzqnUAyaKKzY8u
|
||||
4ZHDTfogdgAaSqtJO2MLSqs/b/vwk22pThgCy0dt99eaXbrtYTP4S4OPFFPuV/23
|
||||
e6BJFfSqugDPcZxE4ms0QzfZDmFS5WZrvNa03pUa4Ch8rXH7tkdpMxdKNKLL6uqy
|
||||
9PA732gOxGPJLZ9ZiDcrTQX1fApnTEtUuu5pLX1U9y81+fv04wB0SRR5sggtwg2a
|
||||
x+6rwvdwtx/7rM4WbqATvo1MDkA8SkbWGur06tm4yQrLI7YhNrEC91H3l8SXnIQZ
|
||||
o7/LDhoW3FvvzpPfQMnNwyPV9VoKCnsS7w40rwiLTuS0xvYNZY8eSY3SgM8EtpG2
|
||||
iLgW8Fnavs8lyCTrl9NFnFw7W2OEan0xpc0/N9pEC6+O+zp3Td9Qr0yhk8g7kMU/
|
||||
FghEgDZay2LyiefHqQy1R/YV/GNjLJvOIADH3x7mLI/Wyk9TQvQHgnLO9ksDsp/Y
|
||||
eqeDoRHfy8a8IEe5Ag0EWxID2QEQAOpM8Jz7W1sdsO7nDw29skfGG/6YX+2n/hIr
|
||||
w2GgHoV5LrN3rCo/wYkfZBQK/cnHdlG7n1E1Ih5XnNIsvGb8riAeX0C6htm44QZW
|
||||
XcQs1pfaOSXMjuTzRDW37lQFJDZcTPouber6jWWLuwBLu6gKHT8ihBRQxUuPoanX
|
||||
NCQxGdim4MgW3PT+2BCmkoviuTDSrDlW7IY1+g5EuQTR7a5R54sEFE3evE8r92OI
|
||||
HnfekX0+be9w89jZqiLZZQfgmgTBWkb/yt26AQ1StST/JsoAUkLjT3EaAhXnd9gf
|
||||
bAPl5q62bqFBgis5n2GRvI+MMV6qBtscskZTp7eYHDvl4jCOebp7Yx9gXJuWiuKK
|
||||
g/fQQJuQgDA6eop1BJYJdNWUUy/fA4avegDg6ZTyYhU3gckyT/84jYvDRj82SerN
|
||||
3oKWjtJ03QtInlxJ/BvndibhrZKUKrGtX1BzkFGzuvehKoYdKcZcgF4ryeVY5TTB
|
||||
08NoAQQ/Iy5RKdbHJg/1jMxp2z0UrND51akGpxXbfV4UBZzmJzLb7RB9SvpUe0xb
|
||||
NqJT1lAiE6biybHG9O5MtpG9SjCRnXtM7Cvd1kn1o+cuF3vvxDvFmvCRJCV/s1tj
|
||||
sJU6bPTusT5HZ4VWQuXD5zEBA7bvPQR89S1WjadfFOL8/cVGiMJ0567HBxg1B0yo
|
||||
6XX36q59ABEBAAGJAjYEGAEKACAWIQTYe7CMm/ZVv+PZKGGerPlca7DjYgUCWxID
|
||||
2QIbDAAKCRCerPlca7DjYpFhD/41PIalkLWols5959nMAxbwLS3wnSbHg6n713Df
|
||||
2jdnXr7NdfJiI6Pb3aCphOcTQ8F1H9AbZlCBo9AwzzCrL9MBZNdeMF5RySYdNQS4
|
||||
nlpdgaqN53zWfjewz8e9Nr8SMVeQgDhE/+wgMvxGd2NxXgtEJHQvFOOicgR+osAR
|
||||
AJnQ/ajdxm57zzsTStXMt5DTAah2nCj42A5T6fcRXhOsTF3V8QVJ2T7xZLt8rjwD
|
||||
Iw03W3Z/0MefvQKPHk8ywPXwbdQ7CWDq5AK3JtZtQO+uHYOhObucoJJLXXHuehRb
|
||||
geJA1gW50d6iDqC7f0t+Wm2U6paNNwpIhhE8NHBHRUSIC1fFe3XYlzBieQessr7L
|
||||
ilfYEo7V+5Ez6Kgjvtb7zwgN6GNv0IsilJ0lJyZ6Dj02BP8qCBY5t+0Asuc6qUVM
|
||||
pIxM+DD9PAesSrzJPLwwTXRvgjfDtKjGTz5bOpF6mzMP7WcPRDbplRay+j2ta8ig
|
||||
szLZeLuBGc0i9X9vliEWKbJq3aubCboQDTpYXhNzi0LiHoeDdXlBtnQiRHTU80oT
|
||||
57EwGJOjD9VkyN0vhwi7fKBmpTZjR2mqVNRuQTtkW4VpUDQ74RvQTY5CmHbh9kli
|
||||
LyHFHMU7bblbIp7Jc9z6qzXEHd39fAbexXtRsWKM8GiylGOLS1xotfHAPxfWaze3
|
||||
yCLXAg==
|
||||
=fx/l
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
```
|
163
website/versioned_docs/version-v0.4/testkit/testkit.md
Normal file
163
website/versioned_docs/version-v0.4/testkit/testkit.md
Normal file
|
@ -0,0 +1,163 @@
|
|||
---
|
||||
id: version-v0.4-testkit
|
||||
title: Testkit
|
||||
original_id: testkit
|
||||
---
|
||||
|
||||
## Philosphy 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)
|
||||
|
||||
//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)
|
||||
|
||||
### 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)
|
||||
```
|
||||
|
||||
### Testkit for core
|
||||
|
||||
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 strucutre -- such as a `Transaction`.
|
||||
|
||||
There is also a robust set of generators available in the [org.bitcoins.testkit.gen](../../testkit/src/main/scala/org/bitcoins/testkit/core/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 in our [Private Key test cases](../../core-test/src/test/scala/org/bitcoins/core/crypto/ECPrivateKeyTest.scala)
|
||||
|
||||
### 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.
|
121
website/versioned_docs/version-v0.4/wallet/address-tagging.md
Normal file
121
website/versioned_docs/version-v0.4/wallet/address-tagging.md
Normal file
|
@ -0,0 +1,121 @@
|
|||
---
|
||||
id: version-v0.4-address-tagging
|
||||
title: Address and UTXO tagging
|
||||
original_id: address-tagging
|
||||
---
|
||||
|
||||
|
||||
### Using AddressTags
|
||||
|
||||
The Bitcoin-S wallet allows you to give addresses, and their associated utxos,
|
||||
a tag. These tags allow you to separate funds between utxos so you can query utxos,
|
||||
and spend from them, based off of an AddressTag. The system also allows you to create
|
||||
your own custom address tags, that will be enforced by the library.
|
||||
|
||||
An address tag consists of the tag name, and a tag type. We use a tag type so we can have
|
||||
tag with the same name without complications.
|
||||
|
||||
To create an address with a tag you can use `getNewAddress` but pass in a `Vector[AddressTag]`.
|
||||
It will add to the address tag database along with all the corresponding tags.
|
||||
|
||||
```scala
|
||||
wallet.getNewAddress(tags = Vector(ExampleAddressTag))
|
||||
```
|
||||
|
||||
When sending with `sendToAddress` you can also a `Vector` of new `AddressTag`s that will be applied to the
|
||||
resulting change outputs. Any tags of a different tag type not included in `newTag`s will also be applied to
|
||||
the change outputs.
|
||||
|
||||
```scala
|
||||
wallet.sendToAddress(exampleAddress, Bitcoins(2), SatoshisPerVirtualByte.one, account, Vector(ExampleAddressTag))
|
||||
```
|
||||
|
||||
Also, when sending you can use `fundRawTransaction` and use `fromTagOpt` to pass in an optional `AddressTag`,
|
||||
this will use only utxos associated with the `AddressTag`.
|
||||
|
||||
```scala
|
||||
wallet.fundRawTransaction(
|
||||
destinations = destinations,
|
||||
feeRate = SatoshisPerVirtualByte.one,
|
||||
fromTagOpt = Some(ExampleAddressTag),
|
||||
markAsReserved = false)
|
||||
```
|
||||
|
||||
### Creating your own AddressTags
|
||||
|
||||
You can create your own custom `AddressTag`s. This allows you to tag addresses and utxos in any way that your
|
||||
application needs. To do this you are going to need to use `ExternalAddressTag`. As an example we will create
|
||||
`AddressTag`s for user specific funds.
|
||||
|
||||
We will need to define the tag type, then define the tag name for each tag, as well as a way to go to and
|
||||
from a `String`. Then we define the actual tags, we are going to have a `Company`, `InsuranceFund`, and `UserId`
|
||||
tags. We are going to make the `UserId` tag special, and allow it to take in any user id so we can have a huge
|
||||
set of users but all with different ids.
|
||||
|
||||
```scala
|
||||
object UserIdTagType extends ExternalAddressTagType {
|
||||
override val typeName: String = "UserIdTag"
|
||||
}
|
||||
|
||||
/** Allows to assign funds in a specific address to a user */
|
||||
sealed trait UserIdTag extends ExternalAddressTag {
|
||||
override val tagType: AddressTagType = UserIdTagType
|
||||
}
|
||||
|
||||
object UserIdTags extends AddressTagFactory[UserIdTag] {
|
||||
|
||||
override val tagType: ExternalAddressTagType = UserIdTagType
|
||||
|
||||
case object CompanyTagName extends ExternalAddressTagName {
|
||||
override def name: String = "Company"
|
||||
}
|
||||
|
||||
case object InsuranceFundTagName extends ExternalAddressTagName {
|
||||
override def name: String = "InsuranceFund"
|
||||
}
|
||||
|
||||
/** Funds that do not belong to any user and instead belong to the company */
|
||||
case object Company extends ExternalAddressTag with UserIdTag {
|
||||
override val tagName: ExternalAddressTagName = CompanyTagName
|
||||
}
|
||||
|
||||
/** Funds in the company's insurance fund */
|
||||
case object InsuranceFund extends ExternalAddressTag with UserIdTag {
|
||||
override val tagName: ExternalAddressTagName = InsuranceFundTagName
|
||||
}
|
||||
|
||||
/** Funds that are specific to an individual user */
|
||||
case class UserId(id: String) extends ExternalAddressTag with UserIdTag {
|
||||
override val tagName: ExternalAddressTagName = new ExternalAddressTagName {
|
||||
override def name: String = id
|
||||
}
|
||||
|
||||
val uid = id.toLong
|
||||
}
|
||||
|
||||
override val all: Vector[UserIdTag] = Vector(Company, InsuranceFund)
|
||||
|
||||
override val tagNames = Vector(CompanyTagName, InsuranceFundTagName)
|
||||
|
||||
override def fromStringOpt(str: String): Option[UserIdTag] = {
|
||||
all.find(tag => str.toLowerCase() == tag.toString.toLowerCase) match {
|
||||
case Some(tag) =>
|
||||
Some(tag)
|
||||
case None =>
|
||||
Some(UserId(str))
|
||||
}
|
||||
}
|
||||
|
||||
override def fromString(str: String): UserIdTag = {
|
||||
fromStringOpt(str) match {
|
||||
case Some(tag) => tag
|
||||
case None => sys.error(s"Could not find tag=$str")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
def fromUID(uid: Long): UserIdTag = {
|
||||
UserId(uid.toString)
|
||||
}
|
||||
}
|
||||
```
|
180
website/versioned_docs/version-v0.4/wallet/chain-query-api.md
Normal file
180
website/versioned_docs/version-v0.4/wallet/chain-query-api.md
Normal file
|
@ -0,0 +1,180 @@
|
|||
---
|
||||
id: version-v0.4-chain-query-api
|
||||
title: Chain Query API
|
||||
original_id: chain-query-api
|
||||
---
|
||||
|
||||
|
||||
### ChainQueryAPI
|
||||
|
||||
The ChainQueryApi is how the wallet project stays aware of the current best chain.
|
||||
This allows the wallet for example to calculate the number of confirmations for a transaction,
|
||||
get the current chain tip, or even retrieve block filters for a given set of blocks.
|
||||
|
||||
Since this is an API it can be hooked up to the `chain` 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 ChainQueryApi supports are:
|
||||
|
||||
```scala
|
||||
trait ChainQueryApi {
|
||||
|
||||
/** Gets the height of the given block */
|
||||
def getBlockHeight(blockHash: DoubleSha256DigestBE): Future[Option[Int]]
|
||||
|
||||
/** Gets the hash of the block that is what we consider "best" */
|
||||
def getBestBlockHash(): Future[DoubleSha256DigestBE]
|
||||
|
||||
/** Gets number of confirmations for the given block hash*/
|
||||
def getNumberOfConfirmations(
|
||||
blockHashOpt: DoubleSha256DigestBE): Future[Option[Int]]
|
||||
|
||||
/** Gets the number of compact filters in the database */
|
||||
def getFilterCount: Future[Int]
|
||||
|
||||
/** Returns the block height of the given block stamp */
|
||||
def getHeightByBlockStamp(blockStamp: BlockStamp): Future[Int]
|
||||
|
||||
def getFiltersBetweenHeights(
|
||||
startHeight: Int,
|
||||
endHeight: Int): Future[Vector[FilterResponse]]
|
||||
}
|
||||
```
|
||||
|
||||
## Chain query with bitcoind
|
||||
|
||||
As an example, we will show you how to use the `ChainQueryApi` and bitcoind to query chain data.
|
||||
|
||||
```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 nodeApi = BitcoinSWalletTest.MockNodeApi
|
||||
|
||||
// Create our key manager
|
||||
val keyManagerE = BIP39KeyManager.initialize(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 chain api receives a transaction, block, or
|
||||
// a block filter, the returned NodeCallbacks will contain the necessary items to initialize the callbacks
|
||||
def createCallbacks(
|
||||
processTransaction: Transaction => Future[Unit],
|
||||
processCompactFilters: (Vector[(DoubleSha256Digest, GolombFilter)]) => Future[Unit],
|
||||
processBlock: Block => Future[Unit]): NodeCallbacks = {
|
||||
lazy val onTx: OnTxReceived = { tx =>
|
||||
processTransaction(tx)
|
||||
}
|
||||
lazy val onCompactFilters: OnCompactFiltersReceived = {
|
||||
blockFilters =>
|
||||
processCompactFilters(blockFilters)
|
||||
}
|
||||
lazy val onBlock: OnBlockReceived = { block =>
|
||||
processBlock(block)
|
||||
}
|
||||
NodeCallbacks(onTxReceived = Vector(onTx),
|
||||
onBlockReceived = Vector(onBlock),
|
||||
onCompactFiltersReceived = Vector(onCompactFilters))
|
||||
}
|
||||
|
||||
// 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 exampleProcessTx = (tx: Transaction) =>
|
||||
Future.successful(println(s"Received tx: ${tx.txIdBE}"))
|
||||
|
||||
val exampleProcessBlock = (block: Block) =>
|
||||
Future.successful(println(s"Received block: ${block.blockHeader.hashBE}"))
|
||||
|
||||
val exampleProcessFilters =
|
||||
(filters: Vector[(DoubleSha256Digest, GolombFilter)]) =>
|
||||
Future.successful(println(s"Received filter: ${filters.head._1.flip.hex} ${filters.head._2.hash.flip.hex}"))
|
||||
|
||||
val exampleCallbacks =
|
||||
createCallbacks(exampleProcessTx, exampleProcessFilters, exampleProcessBlock)
|
||||
|
||||
// Here is where we are defining our actual chain api, Ideally this could be it's own class
|
||||
// but for the examples sake we will keep it small.
|
||||
val chainApi = new ChainQueryApi {
|
||||
|
||||
override def epochSecondToBlockHeight(time: Long): Future[Int] =
|
||||
Future.successful(0)
|
||||
|
||||
/** Gets the height of the given block */
|
||||
override def getBlockHeight(
|
||||
blockHash: DoubleSha256DigestBE): Future[Option[Int]] = {
|
||||
bitcoind.getBlock(blockHash).map(block => Some(block.height))
|
||||
}
|
||||
|
||||
/** Gets the hash of the block that is what we consider "best" */
|
||||
override def getBestBlockHash(): Future[DoubleSha256DigestBE] = {
|
||||
bitcoind.getBestBlockHash
|
||||
}
|
||||
|
||||
/** Gets number of confirmations for the given block hash */
|
||||
override def getNumberOfConfirmations(
|
||||
blockHash: DoubleSha256DigestBE): Future[Option[Int]] = {
|
||||
for {
|
||||
tip <- bitcoind.getBlockCount
|
||||
block <- bitcoind.getBlock(blockHash)
|
||||
} yield {
|
||||
Some(tip - block.height + 1)
|
||||
}
|
||||
}
|
||||
|
||||
/** Gets the number of compact filters in the database */
|
||||
override def getFilterCount: Future[Int] = {
|
||||
// since bitcoind should have the filter for
|
||||
// every block we can just return the block height
|
||||
bitcoind.getBlockCount
|
||||
}
|
||||
|
||||
/** Returns the block height of the given block stamp */
|
||||
override def getHeightByBlockStamp(blockStamp: BlockStamp): Future[Int] = {
|
||||
blockStamp match {
|
||||
case blockHeight: BlockStamp.BlockHeight =>
|
||||
Future.successful(blockHeight.height)
|
||||
case blockHash: BlockStamp.BlockHash =>
|
||||
getBlockHeight(blockHash.hash).map(_.get)
|
||||
case blockTime: BlockStamp.BlockTime =>
|
||||
Future.failed(new RuntimeException(s"Not implemented: $blockTime"))
|
||||
}
|
||||
}
|
||||
|
||||
override def getFiltersBetweenHeights(
|
||||
startHeight: Int,
|
||||
endHeight: Int): Future[Vector[FilterResponse]] = {
|
||||
val filterFs = startHeight
|
||||
.until(endHeight)
|
||||
.map { height =>
|
||||
for {
|
||||
hash <- bitcoind.getBlockHash(height)
|
||||
filter <- bitcoind.getBlockFilter(hash, FilterType.Basic)
|
||||
} yield {
|
||||
FilterResponse(filter.filter, hash, height)
|
||||
}
|
||||
}
|
||||
.toVector
|
||||
|
||||
Future.sequence(filterFs)
|
||||
}
|
||||
}
|
||||
|
||||
// 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 one of the events we can run
|
||||
wallet.chainQueryApi.getFiltersBetweenHeights(100, 150)
|
||||
```
|
195
website/versioned_docs/version-v0.4/wallet/dlc.md
Normal file
195
website/versioned_docs/version-v0.4/wallet/dlc.md
Normal file
|
@ -0,0 +1,195 @@
|
|||
---
|
||||
id: version-v0.4-dlc
|
||||
title: Executing A DLC with Bitcoin-S
|
||||
original_id: dlc
|
||||
---
|
||||
|
||||
## Executing A Discreet Log Contract (DLC)
|
||||
|
||||
## Step 1: Get Bitcoin-S Setup
|
||||
|
||||
See the [setup document](../getting-setup).
|
||||
|
||||
Make sure to follow [Step 4](../getting-setup#step-4-optional-discreet-log-contract-branch) to checkout the `adaptor-dlc` feature branch.
|
||||
|
||||
## Step 2: Agree On Contract Terms
|
||||
|
||||
Both parties must agree on all fields from the table below:
|
||||
|
||||
| Field Name | Format |
|
||||
| :------------: | :------------------------------------------------------: |
|
||||
| oracleInfo | OraclePubKeyHex ++ OracleRValueHex |
|
||||
| contractInfo | Hash1Hex ++ 8ByteValue1Hex ++ Hash2Hex ++ 8ByteValue2Hex |
|
||||
| collateral | NumInSatoshis |
|
||||
| locktime | LockTimeNum |
|
||||
| refundlocktime | LockTimeNum |
|
||||
| feerate | NumInSatoshisPerVByte |
|
||||
|
||||
Here is an example `oracleInfo` for public key `02debeef17d7be7ced0bf346395a5c5c7177491953e91f0af2b098aac5d23cab` and R value `b1a63752e5a760f47252545b7cda933afeaf06dba3b6c6fd5356781f240c2750`:
|
||||
|
||||
```bashrc
|
||||
02debeef17d7be7ced0bf346395a5c5c7177491953e91f0af2b098aac5d23cabb1a63752e5a760f47252545b7cda933afeaf06dba3b6c6fd5356781f240c2750
|
||||
```
|
||||
|
||||
Here is an example `contractInfo` for hashes `c07803e32c12e100905e8d69fe38ae72f2e7a17eb7b8dc1a9bce134b0cbe920f` and `5c58e41254e7a117ee1db59874f2334facc1576c238c16d18767b47861f93f7c` with respective Satoshi denominated outcomes of `100000 sats` and `0 sats`:
|
||||
|
||||
```bashrc
|
||||
c07803e32c12e100905e8d69fe38ae72f2e7a17eb7b8dc1a9bce134b0cbe920fa0860100000000005c58e41254e7a117ee1db59874f2334facc1576c238c16d18767b47861f93f7c0000000000000000
|
||||
```
|
||||
|
||||
And finally, here are the oracle signatures for each hash in order in case you want to test with this contract:
|
||||
|
||||
```bashrc
|
||||
f8758d7f03a65b67b90f62301a3554849bde6d00d50e965eb123398de9fd6ea7fbbee821b7166028a6927282830c9452cfcf3c5716c57e43dd4069ca87625010
|
||||
```
|
||||
|
||||
```bashrc
|
||||
f8758d7f03a65b67b90f62301a3554849bde6d00d50e965eb123398de9fd6ea7af05f01f1ca852cf5454a7dc91cdad7903dc2e67ddb2b3bc9d61dabd8856aa6a
|
||||
```
|
||||
|
||||
Note: if you wish to setup your own oracle for testing, you can do so by pasting the following into the `sbt core/console`:
|
||||
|
||||
```scala
|
||||
import org.bitcoins.crypto._
|
||||
import org.bitcoins.core.currency._
|
||||
import scodec.bits._
|
||||
|
||||
val privKey = ECPrivateKey.freshPrivateKey
|
||||
val pubKey = privKey.schnorrPublicKey
|
||||
val kValue = ECPrivateKey.freshPrivateKey
|
||||
val rValue = kValue.schnorrNonce
|
||||
|
||||
//the hash the oracle will sign when the bitcoin price is over $9,000
|
||||
val winHash = CryptoUtil.sha256(ByteVector("BTC_OVER_9000".getBytes)).flip
|
||||
//the hash the oracle with sign when the bitcoin price is under $9,000
|
||||
val loseHash = CryptoUtil.sha256(ByteVector("BTC_UNDER_9000".getBytes)).flip
|
||||
|
||||
//the amounts received in the case the oracle signs hash of message "BTC_OVER_9000"
|
||||
val amtReceivedOnWin = Satoshis(100000)
|
||||
|
||||
//the amount received in the case the oracle signs hash of message "BTC_UNDER_9000"
|
||||
val amtReceivedOnLoss = Satoshis.zero
|
||||
|
||||
(pubKey.bytes ++ rValue.bytes).toHex
|
||||
(winHash.bytes ++ amtReceivedOnWin.bytes ++ loseHash.bytes ++ amtReceivedOnLoss.bytes).toHex
|
||||
privKey.schnorrSignWithNonce(winHash.bytes, kValue)
|
||||
privKey.schnorrSignWithNonce(loseHash.bytes, kValue)
|
||||
```
|
||||
|
||||
Where you can replace the messages `WIN` and `LOSE` to have the oracle sign any two messages, and replace `Satoshis(100000)` and `Satoshis.zero` to change the outcomes.
|
||||
|
||||
## Using the GUI
|
||||
|
||||
To first start up the GUI you first need to start your bitcoin-s server and gui with
|
||||
|
||||
```bashrc
|
||||
sbt bundle/run
|
||||
```
|
||||
|
||||
or if your bitcoin-s server is already running, you can run the standalone gui with
|
||||
|
||||
```bashrc
|
||||
sbt gui/run
|
||||
```
|
||||
|
||||
or by following the instructions for building and running the GUI [here](../getting-setup#step-5-setting-up-a-bitcoin-s-server-neutrino-node)
|
||||
|
||||
### Step 3: Setup The DLC
|
||||
|
||||
If you're a visual learner there is a [video demo](https://www.youtube.com/watch?v=zy1sL2ndcDg) that explains this process in detail. But do note that this demonstrates the old non-adaptor version of DLCs so that the Offer, Accept, Sign protocol is the same, but the contents will be different.
|
||||
|
||||
|
||||
#### Creating The Offer
|
||||
|
||||
Once the terms are agreed to, either party can use the `Offer` button and enter each of the fields from the table above.
|
||||
|
||||
#### Accepting The Offer
|
||||
|
||||
Upon receiving a DLC Offer from your counter-party, you can use the `Accept` button and paste in the DLC Offer.
|
||||
|
||||
#### Signing The DLC
|
||||
|
||||
Upon receiving a DLC Accept message from your counter-party, you can use the `Sign` button and paste in the DLC Accept.
|
||||
|
||||
#### Adding DLC Signatures To Your Database
|
||||
|
||||
Upon receiving a DLC Sign message from your counter-party, add their signatures to your database using the `Add Sigs` button and paste in the message.
|
||||
After doing so you can get the fully signed funding transaction using the `Get Funding Tx` button. This will return the fully signed serialized transaction.
|
||||
|
||||
### Step 4: Executing the DLC
|
||||
|
||||
#### Execute
|
||||
|
||||
You can execute the DLC unilaterally with the `Execute` button which will require the oracle signature.
|
||||
This will return a fully signed Contract Execution Transaction for the event signed by the oracle.
|
||||
|
||||
#### Refund
|
||||
|
||||
If the `refundlocktime` for the DLC has been reached, you can get the fully-signed refund transaction with the `Refund` button.
|
||||
|
||||
## Using the CLI
|
||||
|
||||
### Step 3: Setup The DLC
|
||||
|
||||
#### Creating The Offer
|
||||
|
||||
Once these terms are agreed to, either party can call on `createdlcoffer` with flags for each of the fields in the table above. For example:
|
||||
|
||||
```bashrc
|
||||
./app/cli/target/universal/stage/bitcoin-s-cli createdlcoffer --oracleInfo 02debeef17d7be7ced0bf346395a5c5c7177491953e91f0af2b098aac5d23cabb1a63752e5a760f47252545b7cda933afeaf06dba3b6c6fd5356781f240c2750 --contractInfo c07803e32c12e100905e8d69fe38ae72f2e7a17eb7b8dc1a9bce134b0cbe920fa0860100000000005c58e41254e7a117ee1db59874f2334facc1576c238c16d18767b47861f93f7c0000000000000000 --collateral 40000 --locktime 1666720 --refundlocktime 1666730 --feerate 3
|
||||
```
|
||||
|
||||
This will return a nice pretty-printed JSON offer. To get an offer that can be sent to the counter-party, add the `--escaped` flag to the end of this command.
|
||||
|
||||
#### Accepting The Offer
|
||||
|
||||
Upon receiving a DLC Offer from your counter-party, the following command will create the serialized accept message:
|
||||
|
||||
```bashrc
|
||||
./app/cli/target/universal/stage/bitcoin-s-cli acceptdlcoffer --offer [offer] --escaped
|
||||
```
|
||||
|
||||
#### Signing The DLC
|
||||
|
||||
Upon receiving a DLC Accept message from your counter-party, the following command will generate all of your signatures for this DLC:
|
||||
|
||||
```bashrc
|
||||
./app/cli/target/universal/stage/bitcoin-s-cli signdlc --accept [accept] --escaped
|
||||
```
|
||||
|
||||
#### Adding DLC Signatures To Your Database
|
||||
|
||||
Upon receiving a DLC Sign message from your counter-party, add their signatures to your database by:
|
||||
|
||||
```bashrc
|
||||
./app/cli/target/universal/stage/bitcoin-s-cli adddlcsigs --sigs [sign]
|
||||
```
|
||||
|
||||
You are now fully setup and can generate the fully signed funding transaction for broadcast using
|
||||
|
||||
```bashrc
|
||||
./app/cli/target/universal/stage/bitcoin-s-cli getdlcfundingtx --eventid [eventid]
|
||||
```
|
||||
|
||||
where the `eventid` is in all but the messages other than the DLC Offer message, and is also returned by the `adddlcsigs` command.
|
||||
|
||||
### Step 4: Executing the DLC
|
||||
|
||||
#### Execute
|
||||
|
||||
Upon receiving an oracle signature, you can execute the DLC unilaterally with
|
||||
|
||||
```bashrc
|
||||
./app/cli/target/universal/stage/bitcoin-s-cli executedlc --eventid [eventid] --oraclesig [sig]
|
||||
```
|
||||
|
||||
which will return fully signed Contract Execution Transaction for the event signed by the oracle.
|
||||
|
||||
#### Refund
|
||||
|
||||
If the `refundlocktime` for the DLC has been reached, you can get the fully-signed refund transaction with
|
||||
|
||||
```bashrc
|
||||
./app/cli/target/universal/stage/bitcoin-s-cli executedlcrefund --eventid [eventid]
|
||||
```
|
||||
|
92
website/versioned_docs/version-v0.4/wallet/node-api.md
Normal file
92
website/versioned_docs/version-v0.4/wallet/node-api.md
Normal file
|
@ -0,0 +1,92 @@
|
|||
---
|
||||
id: version-v0.4-node-api
|
||||
title: Node API
|
||||
original_id: 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
|
||||
|
||||
// Create our key manager
|
||||
val keyManagerE = BIP39KeyManager.initialize(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 broadcastTransaction(transaction: Transaction): Future[Unit] = {
|
||||
bitcoind.sendRawTransaction(transaction).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))
|
||||
```
|
|
@ -0,0 +1,71 @@
|
|||
---
|
||||
title: Wallet Callbacks
|
||||
id: version-v0.4-wallet-callbacks
|
||||
original_id: wallet-callbacks
|
||||
---
|
||||
|
||||
#### Callbacks
|
||||
|
||||
Bitcoin-S support call backs for the following events that happen in the wallet:
|
||||
|
||||
1. onTransactionProcessed
|
||||
2. onTransactionBroadcast
|
||||
3. onReservedUtxos
|
||||
4. onNewAddressGenerated
|
||||
|
||||
That means every time one of these events happens, 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 synchronously. 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 wallet and registering a callback, so you can be notified of an event.
|
||||
|
||||
|
||||
```scala
|
||||
implicit val system: ActorSystem = ActorSystem("example")
|
||||
implicit val ec: ExecutionContextExecutor = system.dispatcher
|
||||
implicit val walletConf: WalletAppConfig =
|
||||
BitcoinSTestAppConfig.getNeutrinoTestConfig().walletConf
|
||||
|
||||
// let's use a helper method to get a v19 bitcoind
|
||||
// and a ChainApi
|
||||
val bitcoind = BitcoindV19RpcClient(BitcoindInstance.fromConfigFile())
|
||||
|
||||
// Create our key manager
|
||||
val keyManagerE = BIP39KeyManager.initialize(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")
|
||||
}
|
||||
|
||||
// Here is a super simple example of a callback, this could be replaced with anything, from
|
||||
// relaying the transaction on the network, finding relevant wallet outputs, verifying the transaction,
|
||||
// or writing it to disk
|
||||
val exampleProcessTx: OnTransactionProcessed = (tx: Transaction) =>
|
||||
Future.successful(println(s"Processed Tx: ${tx.txIdBE}"))
|
||||
|
||||
// Create our WalletCallbacks that
|
||||
val exampleCallbacks = WalletCallbacks(
|
||||
onTransactionProcessed = Vector(exampleProcessTx))
|
||||
|
||||
// Now we can create a wallet
|
||||
val wallet =
|
||||
Wallet(keyManager = keyManager,
|
||||
nodeApi = bitcoind,
|
||||
chainQueryApi = bitcoind,
|
||||
feeRateApi = ConstantFeeRateProvider(SatoshisPerVirtualByte.one),
|
||||
creationTime = Instant.now)
|
||||
|
||||
// Finally, we can add the callbacks to our wallet config
|
||||
walletConf.addCallbacks(exampleCallbacks)
|
||||
|
||||
// Then to trigger the event we can run
|
||||
val exampleTx = Transaction(
|
||||
"0200000000010258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd7500000000da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752aeffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d01000000232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f000400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00000000")
|
||||
wallet.processTransaction(exampleTx, None)
|
||||
```
|
|
@ -0,0 +1,32 @@
|
|||
---
|
||||
title: Wallet Get Address APIs
|
||||
id: version-v0.4-wallet-get-address
|
||||
original_id: wallet-get-address
|
||||
---
|
||||
|
||||
The Bitcoin-S wallet has a few different functions for getting a wallet address
|
||||
that all server different purposes.
|
||||
|
||||
First, there is `getNewAddress`. This function will always return a new a address
|
||||
determined by the next address available address index.
|
||||
|
||||
Second, we have `getUnusedAddress`. This function will return an address that has
|
||||
not ever received funds.
|
||||
|
||||
Third, there is `getAddress`. This function takes in an `AccountDb`, `HDChainType`,
|
||||
and an `Int`. This will return the address associated with the pubkey at
|
||||
the resulting `BIP32Path`.
|
||||
|
||||
## Address Queue
|
||||
|
||||
The Bitcoin-S wallet uses a background thread meant to ensure safety when fetching addresses.
|
||||
This is to ensure independent calls to getNewAddress don't result in a race condition to the database that would generate the same address and cause an error.
|
||||
With this background thread, we poll the `addressRequestQueue` seeing if there are any elements in it, if there are, we process them and complete the Promise in the queue.
|
||||
|
||||
There are two ways to configure the wallet's address queue.
|
||||
|
||||
1. `addressQueueSize`: How big the address queue size is before we throw an overflow exception
|
||||
|
||||
2. `addressQueueTimeout`: How long we attempt to generate an address for before it times out
|
||||
|
||||
For an example configuration, checkout the [example config](../config/configuration.md#example-configuration-file).
|
82
website/versioned_docs/version-v0.4/wallet/wallet-rescan.md
Normal file
82
website/versioned_docs/version-v0.4/wallet/wallet-rescan.md
Normal file
|
@ -0,0 +1,82 @@
|
|||
---
|
||||
title: Wallet Rescans
|
||||
id: version-v0.4-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](../applications/chain.md) project gives us
|
||||
an API with the ability to query for filters.
|
||||
|
||||
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)
|
||||
|
||||
### Example
|
||||
|
||||
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()
|
||||
|
||||
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)
|
||||
```
|
153
website/versioned_docs/version-v0.4/wallet/wallet.md
Normal file
153
website/versioned_docs/version-v0.4/wallet/wallet.md
Normal file
|
@ -0,0 +1,153 @@
|
|||
---
|
||||
title: Wallet
|
||||
id: version-v0.4-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 dependecy library to store and use key material.
|
||||
|
||||
By default, we store the encrypted root key in `$HOME/.bitcoin-s/encrypted-bitcoin-s-seed.json`. This is the seed that is used for each of the wallets on each bitcoin network.
|
||||
|
||||
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,
|
||||
blockchains = Vector.empty,
|
||||
blockFilterCheckpoints = Map.empty)
|
||||
|
||||
ChainSync.sync(chainHandler, getBlockHeaderFunc, getBestBlockHashFunc)
|
||||
}
|
||||
|
||||
//initialize our key manager, where we store our keys
|
||||
//you can add a password here if you want
|
||||
//val bip39PasswordOpt = Some("my-password-here")
|
||||
val bip39PasswordOpt = None
|
||||
val keyManager = BIP39KeyManager.initialize(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 broadcastTransaction(tx: Transaction): Future[Unit] = Future.successful(())
|
||||
override def downloadBlocks(blockHashes: Vector[DoubleSha256Digest]): Future[Unit] = Future.successful(())
|
||||
}, new ChainQueryApi {
|
||||
override def epochSecondToBlockHeight(time: Long): Future[Int] = Future.successful(0)
|
||||
override def getBlockHeight(blockHash: DoubleSha256DigestBE): Future[Option[Int]] = Future.successful(None)
|
||||
override def getBestBlockHash(): Future[DoubleSha256DigestBE] = Future.successful(DoubleSha256DigestBE.empty)
|
||||
override def getNumberOfConfirmations(blockHashOpt: DoubleSha256DigestBE): Future[Option[Int]] = Future.successful(None)
|
||||
override def getFilterCount: Future[Int] = Future.successful(0)
|
||||
override def getHeightByBlockStamp(blockStamp: BlockStamp): Future[Int] = Future.successful(0)
|
||||
override def getFiltersBetweenHeights(startHeight: Int, endHeight: Int): Future[Vector[FilterResponse]] = Future.successful(Vector.empty)
|
||||
}, 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")
|
||||
}
|
||||
```
|
|
@ -1,4 +1,5 @@
|
|||
[
|
||||
"v0.4",
|
||||
"0.3.0",
|
||||
"0.2.0",
|
||||
"0.1.0"
|
||||
|
|
Loading…
Add table
Reference in a new issue