Actually add all files for 0.3.0 on the website so they show up (#1256)

This commit is contained in:
Chris Stewart 2020-03-19 07:32:02 -05:00 committed by GitHub
parent 436b8d7733
commit de46e74f1a
43 changed files with 4799 additions and 325 deletions

View file

@ -1,8 +1,33 @@
![Bitcoin-S logo](website/static/img/bitcoin-s-dark-logo.png)
[![Build Status](https://travis-ci.org/bitcoin-s/bitcoin-s.svg?branch=master)](https://travis-ci.org/bitcoin-s/bitcoin-s) [![Coverage Status](https://coveralls.io/repos/github/bitcoin-s/bitcoin-s/badge.svg?branch=master)](https://coveralls.io/github/bitcoin-s/bitcoin-s?branch=master) [![Maven Central](https://img.shields.io/badge/Maven%20Central-0.2.0-brightgreen.svg)](https://mvnrepository.com/artifact/org.bitcoin-s) [![Gitter chat](https://badges.gitter.im/gitterHQ/gitter.png)](https://gitter.im/bitcoin-s-core)
[![Build Status](https://travis-ci.org/bitcoin-s/bitcoin-s.svg?branch=master)](https://travis-ci.org/bitcoin-s/bitcoin-s) [![Coverage Status](https://coveralls.io/repos/github/bitcoin-s/bitcoin-s/badge.svg?branch=master)](https://coveralls.io/github/bitcoin-s/bitcoin-s?branch=master) [![Maven Central](https://img.shields.io/badge/Maven%20Central-0.3.0-brightgreen.svg)](https://mvnrepository.com/artifact/org.bitcoin-s) [![Gitter chat](https://badges.gitter.im/gitterHQ/gitter.png)](https://gitter.im/bitcoin-s-core)
Feature rich toolkit for making Bitcoin and Lightning applications
on the JVM.
For a complete guide on how to get started with Bitcoin-S, see our website at
[Bitcoin-S.org](https://bitcoin-s.org)
The latest release of bitcoin-s is `v0.3.0`, here is how you can use the dependencies in your projects:
```
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"
```

View file

@ -1,17 +1,3 @@
### chain
### Chain
This is meant to be a stand alone project that process a new block / transaction and stores it.
It also provides a interface to query information related to a blockchain.
The design goal with this project is to be agnostic of how the project is receiving
the blockchain data, just that it processes and stores it. For instance
you could provide the blockchain data via
- rpc
- zmq
- p2p
- sattelite
This project just stores relevant [`block`](../core/src/main/scala/org/bitcoins/core/protocol/blockchain/Block.scala)
and [`transaction`](../core/src/main/scala/org/bitcoins/core/protocol/transaction/Transaction.scala) information and allows
for it to be queried via a api.
Please see the website https://bitcoin-s.org/docs/next/chain/chain

View file

@ -1,21 +1,3 @@
### db-commons
This is a project that is meant to contain re-usable database related infrastructure for bitcoin-s. This project is a dependency of [`wallet`](../wallet/) and [`node`](../node).
The library that bitcoin-s currently uses for database related things is called [`Slick`](http://slick.lightbend.com/doc/3.3.0/).
The most important file in this project is [`DbConfig`](src/main/scala/org/bitcoins/db/DbConfig.scala). This provides a
common way for databases to be accessed from configuration files. For more information on how Slick configuration files
work please see this [reference](http://slick.lightbend.com/doc/3.3.0/gettingstarted.html#database-configuration).
This project expects the following keys for databases
- mainnetDb
- testnet3Db
- regtestDb
- unittestDb
This will be read by [`DbConfig`](src/main/scala/org/bitcoins/db/DbConfig.scala) to specify database information related
to a specific project. You can look at the database configuration for the [`node`](../node/src/main/resources/application.conf) project for an example
of how this works.
Please see our website https://bitcoin-s.org/docs/next/config/configuration#database-migrations

View file

@ -76,7 +76,12 @@ the `id` specified in the page metadata (see the existing pages for an example).
You can create a new version of the site by running this in the `website/` directory.
```bashrc
yarn run version [MY-VERSION-HERE]
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

View file

@ -1,60 +1,3 @@
# Bitcoin-S SPV node
### Node
This module is a Bitcoin SPV (simplified payment verification) node that peers
with a Bitcoin Core node over the P2P network. It syncs block headers and does
as much verification as possible with the data it has available.
The node supports bloom filters, and provides optional callbacks that notify
consumers on events such as new blocks, filtered merkle blocks and transactions.
## Caveats:
1. This is a **heavy** work in progress, and should not be used for anything serious
yet
2. The node can only peer with one node on the P2P network right now, and that
node must be passed in on startup. Eventually we want to support peer discovery
through DNS seeds, as well as supporting multiple peers at the same time.
3. The majority of the P2P code was written in late 2017, and as a consequence does
not handle some of the newer P2P messages and functionality (including SegWit
related messages).
## Interesting files
Currently this project is a _heavy_ WIP. The most important files are
- [`Client`](src/main/scala/org/bitcoins/node/networking/Client.scala) - this handles
all of the networking code. Currently this uses Akka but the plan is to move away
from Akka in the future and use a networking library with a smaller classpath footprint.
- [`PeerMessageReceiver`](src/main/scala/org/bitcoins/node/networking/peer/PeerMessageReceiver.scala) - this handles messages we receive on the P2P network.
All messages are algebraic data types, so we can easily pattern match on them and
implement features in `PeerMessageReceiver.handleControlPayload` and
`PeerMessageReceiver.handleDataPayload`
- [`PeerMessageReceiverState`](src/main/scala/org/bitcoins/node/networking/peer/PeerMessageReceiverState.scala) - the states that our peer message receiver can be in.
It transitions through these states during the connect/disconnect process with our peer.
- [`PeerMessageSender`](src/main/scala/org/bitcoins/node/networking/peer/PeerMessageSender.scala) - this handles sending messages to our peer on the P2P network.
Since we are a light client, we probably won't be sending a lot of messages to peers
so this isn't that interesting.
- [`PeerHandler`](src/main/scala/org/bitcoins/node/networking/peer/PeerHandler.scala) - this combines a `PeerMessageReceiver` and a `PeerMessageSender` into a pair.
- [`Peer`](src/main/scala/org/bitcoins/node/models/Peer.scala) - The low level socket
details need to connect to a peer
## Interesting tests
There is still a lot of code commented out on the project, but the tests should
pass for the ones that are not. Interesting tests are
- [`ClientTest`](../node-test/src/test/scala/org/bitcoins/node/networking/ClientTest.scala) - currently tests that we can connect with peers
- [`PeerMessageHandlerTest`](../node-test/src/test/scala/org/bitcoins/node/networking/peer/PeerMessageHandlerTest.scala) - tests that we can get our node into the
[`PeerMessageReceiverState.Normal`](src/main/scala/org/bitcoins/node/networking/peer/PeerMessageReceiverState.scala)
state. This means we can send/receive arbitrary messages from our peer.
- [`SpvNodeTest`] - tests that we can peer with a `bitcoind` and sync a block header
## Main method
There's a main method available in
[`SpvNodeMain.scala`](src/main/scala/org/bitcoins/node/SpvNodeMain.scala). Currently
(June 17th, 2019) the node peers with a locally running `bitcoind`. It does not do
much interesting beyond that, although you can make it more interesting if you
modify the logging levels (look in
[common-logback.xml](../core/src/test/resources/common-logback.xml)) and pass in
some callbacks to the node on startup.
Please see the bitcoin-s website for more information: https://bitcoin-s.org/docs/next/node/node

View file

@ -1,150 +1 @@
[ ![Download](https://api.bintray.com/packages/bitcoin-s/bitcoin-s-core/bitcoin-s-secp256k1jni/images/download.svg) ](https://bintray.com/bitcoin-s/bitcoin-s-core/bitcoin-s-secp256k1jni/_latestVersion)
## Secp256k1jni
This project gives people a convenient way to use [libsecp256k1](https://github.com/bitcoin-core/secp256k1) on the JVM without compiling natives themselves.
Currently we have support for natives on
1. [linux 32 bit](natives/linux_32)
2. [linux 64 bit](natives/linux_64)
3. [mac osx 64 bit](natives/osx_64)
This uses a zero depdency library called [`native-lib-loader`](https://github.com/scijava/native-lib-loader). The does the appropriate loading of the library onto your classpath to be accessed. To tell if you have access to libsecp256k1 you can do the following
```scala
sbt:root> project secp256k1jni
[info] Set current project to bitcoin-s-secp256k1jni (in build file:/home/chris/dev/bitcoin-s-core/)
sbt:bitcoin-s-secp256k1jni> console
[info] Starting scala interpreter...
Welcome to Scala 2.12.7 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_191).
Type in expressions for evaluation. Or try :help.
scala> import org.bitcoin.Secp256k1Context;
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
```
## Adding secp256k1jni to your project
To add `bitcoin-s-secp256k1jni` to your project you add it like this
```
"org.bitcoins" % "bitcoin-s-secp256k1jni" % "0.0.1"
```
or with maven
```
<dependency>
<groupId>org.bitcoins</groupId>
<artifactId>bitcoin-s-secp256k1jni</artifactId>
<version>0.0.1</version>
</dependency>
```
## Using secp256k1
The file [NativeSecp256k1.java](src/main/java/org/bitcoin/NativeSecp256k1.java) contains basic functionality for
1. Verifying digital signatures
2. Producing digital signatures
3. Computing a public key from a private key
4. tweaking keys
5. Checking public key validity
## Binaries
The binaries provided here are PGP signed, to give at least some assurances that
nothing funny has happened to them.
### macOS 64 bit
```bash
bitcoin-s/secp256k1jni/natives/osx_64 $ find -type f -exec sha256sum {} \; | \
sort -k 2 | sha256sum | cut --fields 1 --delimiter=" " | \
gpg --clearsign --output hashed-dir.sig --detach-sig
bitcoin-s/secp256k1jni/natives/osx_64 $ cat hashed-dir.sig
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512
341d768b915d989db82c1fb94b29faec9509ad45e30a27c217feb121747789ce
-----BEGIN PGP SIGNATURE-----
iQEzBAEBCgAdFiEEcQE9FyWyvvLjrwybHk0b2DvgrM0FAlxPHuQACgkQHk0b2Dvg
rM07EwgApXWPlVKMDP7Z8RQhhDmlnycTJxFaw315TH2IKDMEtCA0mBGY8UOy7jis
ZtQEyUQdspBnLW6RHeEBhkDlQzjMS3G4K2T2D1r2eL4vXf7dnZ2CUSy7N9Gd8Lsy
Tcdi9a/GSyFu1RCtmklCZ75/oECPlmNctdOY30+5FDIfWIcTHqKebfDstTqeYGbd
0+U5N2xDI/07g5jCF1EsgiqvFkXZEjI/44xlxWon6GtbiYR/n+MHzgwBi5XqNs2p
0t3Tvx5NbJiVVn7K2ThQFaW4ap/bqggZh1ddT/v2Wq0BH+UX6b6Abrq0tD5+ZPst
5+tz19oEa9XK+X/DDkFrT4WEMDpyIw==
=CauS
-----END PGP SIGNATURE-----
bitcoin-s/secp256k1jni/natives/osx_64 $ gpg --verify hashed-dir.sig
gpg: Signature made Mon 28 Jan 2019 04:25:24 PM CET
gpg: using RSA key 71013D1725B2BEF2E3AF0C9B1E4D1BD83BE0ACCD
gpg: Good signature from "Torkel Rogstad <torkel@suredbits.com>" [ultimate]
```
### Linux 32 bit
TODO
### Linux 64 bit
TODO
### Windows 64 bit
To build binaries for windows, you need to cross compile them from a linux machine. This can be done with the following commands. The origin of these commands [comes from ACINQ](https://github.com/ACINQ/bitcoin-lib/blob/bf115a442e17e1522eba98a362473fddd9b1ffe6/BUILDING.md#for-windows-64-bits)
```
$ sudo apt install g++-mingw-w64-x86-64
$ sudo update-alternatives --config x86_64-w64-mingw32-g++ # Set the default mingw32 g++ compiler option to posix.
```
```
# this is needed to compile shared libraries, otherwise you'll get:
# libtool: warning: undefined symbols not allowed in x86_64-w64-mingw32 shared libraries; building static only
$ 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"
cp ./.libs/libsecp256k1-0.dll ../src/main/resources/fr/acinq/native/Windows/x86_64/secp256k1.dll
```
#### Windows 64 bit signature
```
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512
510f864a2d5d5ec238aa8e3653d01e3c027caef810797c494fc22e8f6fd8fe84 libsecp256k1-0.dll
-----BEGIN PGP SIGNATURE-----
iQIzBAEBCgAdFiEEf7s2jgtmCoShWAhamEbs778HiL4FAl0afpAACgkQmEbs778H
iL5Ojw/+Krr+rtUPfW4DIYgOSD9G9QUUEop6LbEEcFZew1kn8VhKy/2fgJzHiktv
fQy0efyo5dxh36qj+L5MjIMPVYxtxaORTEelDRBixmNGXpI9OrOlV0miaPjTDT2B
suBoqy0kM5/aRWtqU9Pdvd0LBoLYhC6dlqmUerDZ3XccYiXGNTyj881HwBZMn2QS
l+OhKyEGOgD+80px2z8Ix/yO+65ldL1ejbajSrBuJyjqaOLlliF6GrIFqxwT11WF
unOLMloNRvdkIQcoGChfRUarM+ntkyHn4jwI76d1PAjZ2baSEhSqtfCgBZBQWy5R
TwceBHvfxoIoffKehgHY3stszjGOIChY5GS5eGskc3EcDXvdZQQP/lJq91AKuzqb
Lj7lf7MPMyqxQqRkcXv8k08d7yBu3+QOxdduRPD4V8dYLqG53982yWGQEA3Eedsu
70feaOkvslSbXBUR3YZMemI6GBuQTDzkK54L4TID7/QSkraYrp/6k45OGyujrjnB
L7wMjO/+v4zK5zq3kxDlboRbvpwrxwxlpGq+UFIoFBggJbQV5qN1gcodCoePZ54O
7eVKD+AIWk7CNg70qe1/EVGrQ0SnPnXKiI+j5SmMvhJADuuRv58+KT7Y3eJ2kP4t
jZzBmirG1m8UPYnHA50SvrQAScDqZaTDoNwD2vLnIymtr3rJMIw=
=2mZ8
-----END PGP SIGNATURE-----
```
Please see our website for more information on secp256k1jni https://bitcoin-s.org/docs/next/secp256k1/secp256k1

View file

@ -1,16 +1 @@
[ ![Download](https://api.bintray.com/packages/bitcoin-s/bitcoin-s-core/bitcoin-s-testkit/images/download.svg) ](https://bintray.com/bitcoin-s/bitcoin-s-core/bitcoin-s-testkit/_latestVersion)
### Philosphy of testkit
The high level of of the bitcoin-s testkit is to mimic 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. For instance, our [BitcoindRpcClient](../bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/BitcoindRpcClient.scala) is tested with the functionality provided in the testkit. A quick example of a useful utility method is [BitcoindRpcTestUtil.startedBitcoindRpcClient()](src/main/scala/org/bitcoins/testkit/rpc/BitcoindRpcTestUtil.scala). This spins up a bitcoind regtest instance on machine and generates 101 blocks on that node. This gives you the abililty to start spending money immediately with that bitcoind node.
We have similar utility methods for eclair.
### Property based testing
There is also a robust set of generators available in the [org.bitcoins.testkit.gen](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/ECPrivateKeySpec.scala)
Please see our website for more information on the testkit https://bitcoin-s.org/docs/next/testkit/testkit

View file

@ -1,63 +1 @@
### wallet
This is meant to be a stand alone project that can be used as a cold storage wallet _and_ hot wallet.
#### Features
- utxo storage
- key storage
- key generation
- coin selection
- transaction building
- fee calculation
#### Design choices
- Private key material is just stored once, as the mnemonic code used to initialize the
wallet
- Addresses we hand out to users are stored with their
[BIP44/BIP49/BIP84 paths](../core/src/main/scala/org/bitcoins/core/hd/HDPath.scala)
and script types, so that everything we need for spending the money sent to an address
is derivable.
- **The wallet is a "dumb" wallet that acts mostly as a database of UTXOs, transactions and
addresses, with associated operations on these.**
The wallet module does very little verification of incoming data about transactions,
UTXOs and reorgs. We're aiming to write small, self contained modules, that can be
composed together into more fully fledged systems. That means the `chain` and `node`
modules does the actual verification of data we receive, and `wallet` just blindly
acts on this. This results in a design where you can swap out `node` for a Bitcoin Core
full node, use it with hardware wallets, or something else entirely. However, that also
means that users of `wallet` that doesn't want to use the other modules we provide have
to make sure that the data they are feeding the wallet is correct.
#### Database structure
We store information in the following tables:
- TXOs - Contains both the information needed to spent it as well as information related
to wallet state (confirmations, spent/unspent etc)
- Addresses - must reference the account it belongs to
- Accounts
#### Mnemonic encryption
The mnemonic seed to the Bitcoin-S wallet is written to disk, encrypted. The file name is
`$HOME/.bitcoin-s/$NETWORK/encrypted_bitcoin-s_seed.json`. We store it in a JSON object
that looks like this:
```json
{
"iv": "initializationVector",
"cipherText": "encryptedCipherText",
"salt": "saltUsedInEncryption"
}
```
The parts that's relevant to this part of the wallet is `WalletStorage.scala` (where we handle
the actual reading from and writing to disk), `EncryptedMnemonic.scala` (where we convert an
encrypted mnemonic to a cleartext mnemonic) and `AesCrypt.scala` (where do the actual
encryption/decryption).
We use AES encryption for this, block cipher mode and PKCS5 padding. The wallet password is fed
into the PBKDF2 key stretching function, using SHA512 as the HMAC function. This happens in
`PBKDF2.scala`.
Please see our website for more information on the wallet https://bitcoin-s.org/docs/next/wallet/wallet

View file

@ -0,0 +1,78 @@
---
title: Blockchain Verification
id: version-0.3.0-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.initialize()
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}")
}
```

View file

@ -0,0 +1,172 @@
---
id: version-0.3.0-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
}
}
}
```

View file

@ -0,0 +1,166 @@
---
id: version-0.3.0-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]
```

View file

@ -0,0 +1,111 @@
---
title: Syncing Blockfilters
id: version-0.3.0-filter-sync
original_id: filter-sync
---
The `chain` module has the ability to store [BIP157](https://github.com/bitcoin/bips/blob/master/bip-0157.mediawiki) block filters locally. Generally these filters are useful
for doing wallet rescans. The idea is you can generate a list of script pubkeys you are interested in and see if
the block filter matches the scriptPubKey.
As we demonstrated in [chain.md](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.

View file

@ -0,0 +1,133 @@
---
id: version-0.3.0-node
title: Light Client
original_id: node
---
Bitcoin-s has node module that allows you to connect to the p2p network.
### Neutrino Node
Bitcoin-s has experimental support for neutrino which is a new lite client proposal on the bitcoin p2p network. You can
read more about how neutrino works [here](https://suredbits.com/neutrino-what-is-it-and-why-we-need-it/).
#### 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. Let's make a 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.initialize()
//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 = { block: Block =>
println(s"Received blockhash=${block.blockHeader.hashBE}")
}
val nodeCallbacks = NodeCallbacks.onBlockReceived(blockReceivedFunc)
//ok, now we need to add this allback to our running node
val nodeWithCallbackF = for {
node <- startedNodeF
withCallback = node.addCallbacks(nodeCallbacks)
} yield withCallback
//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 <- nodeWithCallbackF
x = NeutrinoNodeConnectedWithBitcoind(node.asInstanceOf[NeutrinoNode],bitcoind)
_ <- NodeUnitTest.destroyNodeConnectedWithBitcoind(x)
} yield ()
Await.result(cleanupF, 60.seconds)
```

View file

@ -0,0 +1,42 @@
---
id: version-0.3.0-server
title: Application Server
original_id: server
---
### App server
The [server](../../app/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
```
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/
```
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)

View file

@ -0,0 +1,151 @@
---
title: Wallet
id: version-0.3.0-wallet
original_id: wallet
---
## Bitcoin-s wallet
Bitcoin-s comes bundled with a rudimentary Bitcoin wallet. This wallet
is capable of managing private keys, generating addresses, constructing
and signing transactions, among other things. It is BIP32/BIP44/BIP49/BIP84
compatible.
This wallet is currently only released as a library, and not as a binary.
This is because it (nor the documentation) is not deemed production
ready. Use at your own risk, and without too much money depending on it.
### How is the bitcoin-s wallet implemented
The bitcoin-s wallet is a scalable way for individuals up to large bitcoin exchanges to safely and securely store their bitcoin in a scalable way.
All key interactions are delegated to the [key-manager](key-manager.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")
}
```

View file

@ -0,0 +1,78 @@
---
title: Blockchain Verification
id: version-0.3.0-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.initialize()
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}")
}
```

View file

@ -0,0 +1,111 @@
---
title: Syncing Blockfilters
id: version-0.3.0-filter-sync
original_id: filter-sync
---
The `chain` module has the ability to store [BIP157](https://github.com/bitcoin/bips/blob/master/bip-0157.mediawiki) block filters locally. Generally these filters are useful
for doing wallet rescans. The idea is you can generate a list of script pubkeys you are interested in and see if
the block filter matches the scriptPubKey.
As we demonstrated in [chain.md](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.

View file

@ -0,0 +1,172 @@
---
id: version-0.3.0-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
}
}
}
```

View file

@ -0,0 +1,101 @@
---
id: version-0.3.0-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]
```
## 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.

View file

@ -0,0 +1,265 @@
---
id: version-0.3.0-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`](../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. Lets say you wanted general logging
to happen at the `WARN` level, but the P2P message handling to be logged at `DEBUG`. Your
configuration file would then look like:
```conf
bitcoins-s {
logging {
level = warn
p2p = debug
}
}
```
### Running the applications
When running the applications 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`](../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's two way 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 traits 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`](../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/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`](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).

View file

@ -0,0 +1,699 @@
---
id: version-0.3.0-adding-spks
title: Adding New Script Types
original_id: adding-spks
---
# 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 BitcoinUTXOSpendingInfo
`BitcoinUTXOSpendingInfo` is the Bitcoin-S data structure for the information required to spend from a given `ScriptPubKey`. 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 three distinct kinds of scripts when it comes to signing in Bitcoin-S: scripts that have nesting (such as `ConditionalScriptPubKey`, `LockTimeScriptPubKey`), scripts without nesting but which involve multiple signing keys (such as `MultiSignatureScriptPubKey`), and scripts without nesting and which require only one key for signing (such as `P2PKWithTimeoutScriptPubKey`, `P2PKHScriptPubKey`). We will cover each of these cases in turn, starting with the last case as it applies to our example of `P2PKWithTimeout`.
### Non-Nested Single-Key Spending Info
This is the easiest case and only requires creating a new `case class` in `UTXOSpendingInfo.scala` which extends `RawScriptUTXOSpendingInfoFull with RawScriptUTXOSpendingInfoSingle` and which contains in its parameters, all of the info required for spending. Make sure to also validate any data in these parameters using `require` statements. Make sure to `override` both `requiredSigs: Int = 1` and `conditionalPath: ConditionalPath` to be either whatever is needed based on your parameters, or `ConditionalPath.NoConditionsLeft` if your script does not use any conditionals. Here is what this looks like for `P2PKWithTimeout`:
```scala
case class P2PKWithTimeoutSpendingInfo(
outPoint: TransactionOutPoint,
amount: CurrencyUnit,
scriptPubKey: P2PKWithTimeoutScriptPubKey,
override val signer: Sign,
hashType: HashType,
isBeforeTimeout: Boolean)
extends RawScriptUTXOSpendingInfoFull
with RawScriptUTXOSpendingInfoSingle {
require(
scriptPubKey.pubKey == signer.publicKey || scriptPubKey.timeoutPubKey == signer.publicKey,
"Signer pubkey must match ScriptPubKey")
override val requiredSigs: Int = 1
override def conditionalPath: ConditionalPath =
if (isBeforeTimeout) {
ConditionalPath.nonNestedTrue
} else {
ConditionalPath.nonNestedFalse
}
}
```
### Non-Nested Multi-Key Spending Info
For new script types which require multiple keys to spend, we must make two separate `case class`es, one for normal spending (which extends `RawScriptUTXOSpendingInfoFull`) and one for signing a transaction with a single key (which extends `RawScriptUTXOSpendingInfoSingle`). We then make a `sealed trait` for the both of them to extend. In this `trait`, you want to make sure to `override def scriptPubKey` to have your new `ScriptPubKey` type as well as overriding any other members of `RawScriptUTXOSpendingInfo` which are common to both classes. In total, this all works out to the following for `MutliSignatureScriptPubKey`:
```scala
sealed trait MultiSignatureSpendingInfo extends RawScriptUTXOSpendingInfo {
override def conditionalPath: ConditionalPath =
ConditionalPath.NoConditionsLeft
override def scriptPubKey: MultiSignatureScriptPubKey
}
case class MultiSignatureSpendingInfoSingle(
outPoint: TransactionOutPoint,
amount: CurrencyUnit,
scriptPubKey: MultiSignatureScriptPubKey,
signer: Sign,
hashType: HashType
) extends MultiSignatureSpendingInfo
with RawScriptUTXOSpendingInfoSingle
case class MultiSignatureSpendingInfoFull(
outPoint: TransactionOutPoint,
amount: CurrencyUnit,
scriptPubKey: MultiSignatureScriptPubKey,
private val signersWithPossibleExtra: Vector[Sign],
hashType: HashType
) extends RawScriptUTXOSpendingInfoFull
with MultiSignatureSpendingInfo {
require(signersWithPossibleExtra.length >= scriptPubKey.requiredSigs,
s"Not enough signers!: $this")
override val requiredSigs: Int = scriptPubKey.requiredSigs
override val signers: Vector[Sign] =
signersWithPossibleExtra.take(requiredSigs)
override def toSingle(signerIndex: Int): MultiSignatureSpendingInfoSingle = {
MultiSignatureSpendingInfoSingle(outPoint,
amount,
scriptPubKey,
signers(signerIndex),
hashType)
}
/** @inheritdoc */
override def toSingles: Vector[MultiSignatureSpendingInfoSingle] = {
signers.map { signer =>
MultiSignatureSpendingInfoSingle(outPoint,
amount,
scriptPubKey,
signer,
hashType)
}
}
}
```
### Nested Spending Info
This case is very similar to the above where we need to create two case classes one for normal spending (which extends `RawScriptUTXOSpendingInfoFull`) and one for signing a transaction with a single key (which extends `RawScriptUTXOSpendingInfoSingle`). As well as needing to create a `sealed trait` for them both to extend which overrides `scriptPubKey` as well as any other commonalities from the sub-classes. The one new thing in the nested case is that we must create a `def nestedSpendingInfo: RawScriptUTXOSpendingInfo` in our `trait` and make sure to override `signers` and `requiredSigs` from this `nestedSpendingInfo` in the subclass extending `RawScriptUTXOSpendingInfoFull`. For the case of spending `LockTimeScriptPubKey`s, this looks like the following:
```scala
sealed trait LockTimeSpendingInfo extends RawScriptUTXOSpendingInfo {
override def scriptPubKey: LockTimeScriptPubKey
def nestedSpendingInfo: RawScriptUTXOSpendingInfo
}
case class LockTimeSpendingInfoSingle(
outPoint: TransactionOutPoint,
amount: CurrencyUnit,
scriptPubKey: LockTimeScriptPubKey,
signer: Sign,
hashType: HashType,
conditionalPath: ConditionalPath
) extends LockTimeSpendingInfo
with RawScriptUTXOSpendingInfoSingle {
override val nestedSpendingInfo: RawScriptUTXOSpendingInfoSingle = {
RawScriptUTXOSpendingInfoSingle(outPoint,
amount,
scriptPubKey.nestedScriptPubKey,
signer,
hashType,
conditionalPath)
}
}
case class LockTimeSpendingInfoFull(
outPoint: TransactionOutPoint,
amount: CurrencyUnit,
scriptPubKey: LockTimeScriptPubKey,
private val signersWithPossibleExtra: Vector[Sign],
hashType: HashType,
conditionalPath: ConditionalPath
) extends RawScriptUTXOSpendingInfoFull
with LockTimeSpendingInfo {
override val nestedSpendingInfo: RawScriptUTXOSpendingInfoFull = {
RawScriptUTXOSpendingInfoFull(outPoint,
amount,
scriptPubKey.nestedScriptPubKey,
signersWithPossibleExtra,
hashType,
conditionalPath)
}
override val signers: Vector[Sign] = nestedSpendingInfo.signers
override val requiredSigs: Int = nestedSpendingInfo.requiredSigs
}
```
## Step 7: Add to Relevant Apply Methods
Now that we have created our new `RawScriptUTXOSpendingInfoFull` and `RawScriptUTXOSpendingInfoSingle`, possibly in the same class, we need to add them to the general-purpose spending info constructors. This means adding a `case` to both `RawScriptUTXOSpendingInfoSingle.apply` and `RawScriptUTXOSpendingInfoFull.apply` for your new `ScriptPubKey` type which constructs your relevant `RawScriptUTXOSpendingInfoSingle` and `RawScriptUTXOSpendingInfoFull` from generic types (given as parameters in the `apply` methods). For `P2PKWithTimeout`, both of these cases look 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) =>
P2PKWithTimeoutSpendingInfo(outPoint,
amount,
p2pkWithTimeout,
signer,
hashType,
beforeTimeout)
}
//...
}
```
## Step 8: Create a Signer
We must now add signing functionality for our new script type within `Signer.scala`. Once again, we have three different cases depending on your new script type.
### Non-Nested Single-Key Spending Info
For this case, 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[P2PKWithTimeoutSpendingInfo] {
override def keyAndSigToScriptSig(
key: ECPublicKey,
sig: ECDigitalSignature,
spendingInfo: P2PKWithTimeoutSpendingInfo): ScriptSignature = {
P2PKWithTimeoutScriptSignature(spendingInfo.isBeforeTimeout, sig)
}
}
object P2PKWithTimeoutSigner extends P2PKWithTimeoutSigner
```
### Non-Nested Multi-Key Spending Info
In this case we must create a new `BitcoinSignerSingle` and a normal `BitcoinSignerFull`, the latter of which requires implementing the `sign` function. For `MultiSignature` this looks like the following:
```scala
sealed abstract class MultiSigSignerSingle
extends BitcoinSignerSingle[MultiSignatureSpendingInfoSingle]
object MultiSigSignerSingle extends MultiSigSignerSingle
sealed abstract class MultiSigSigner
extends BitcoinSignerFull[MultiSignatureSpendingInfoFull] {
override def sign(
spendingInfo: UTXOSpendingInfoFull,
unsignedTx: Transaction,
isDummySignature: Boolean,
spendingInfoToSatisfy: MultiSignatureSpendingInfoFull)(
implicit ec: ExecutionContext): Future[TxSigComponent] = {
val (_, output, inputIndex, _) =
relevantInfo(spendingInfo, unsignedTx)
val keysAndSigsF: Seq[Future[PartialSignature]] = spendingInfoToSatisfy.toSingles.zipWithIndex.map {
case (infoSingle, index) =>
MultiSigSignerSingle
.signSingle(spendingInfo.toSingle(index),
unsignedTx,
isDummySignature,
infoSingle)
}
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 `BitcoinSignerSingle` and a normal `BitcoinSignerFull`. For the single signer, you will likely only need to delegate with a call to `BitcoinSignerSingle.signSingle` because we only need a signature which will not likely require anything other than doing the signing on `spendingInfoToSatisfy.nestedSpendingInfo`. For the full signer, you will also 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
sealed abstract class ConditionalSignerSingle
extends BitcoinSignerSingle[ConditionalSpendingInfoSingle] {
override def signSingle(
spendingInfo: UTXOSpendingInfoSingle,
unsignedTx: Transaction,
isDummySignature: Boolean,
spendingInfoToSatisfy: ConditionalSpendingInfoSingle)(
implicit ec: ExecutionContext): Future[PartialSignature] = {
BitcoinSignerSingle.signSingle(spendingInfo,
unsignedTx,
isDummySignature,
spendingInfoToSatisfy.nestedSpendingInfo)
}
}
object ConditionalSignerSingle extends ConditionalSignerSingle
/** Delegates to get a ScriptSignature for the case being
* spent and then adds an OP_TRUE or OP_FALSE
*/
sealed abstract class ConditionalSigner
extends BitcoinSignerFull[ConditionalSpendingInfoFull] {
override def sign(
spendingInfo: UTXOSpendingInfoFull,
unsignedTx: Transaction,
isDummySignature: Boolean,
spendingInfoToSatisfy: ConditionalSpendingInfoFull)(
implicit ec: ExecutionContext): Future[TxSigComponent] = {
val (_, output, inputIndex, _) = relevantInfo(spendingInfo, unsignedTx)
val missingOpSigComponentF = BitcoinSigner.sign(
spendingInfo,
unsignedTx,
isDummySignature,
spendingInfoToSatisfy.nestedSpendingInfo)
val scriptSigF = missingOpSigComponentF.map { sigComponent =>
ConditionalScriptSignature(sigComponent.scriptSignature,
spendingInfoToSatisfy.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 `ScriptPubKey` type in the `match` within `BitcoinSigner.sign`. In the case of `P2PKWithTimeout`, this looks like:
```scala
spendingInfoToSatisfy match {
//...
case p2pKWithTimeout: P2PKWithTimeoutSpendingInfo =>
P2PKWithTimeoutSigner.sign(spendingInfo,
unsignedTx,
isDummySignature,
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 `SpendingInfoFull` 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 privKey = privKeys.head
val emptyScriptSig = P2PKWithTimeoutScriptSignature(beforeTimeout = true,
EmptyDigitalSignature)
val (creditingTx, outputIndex) =
TransactionGenerators.buildCreditingTransaction(spk)
val (spendingTx, inputIndex) = TransactionGenerators
.buildSpendingTransaction(creditingTx, emptyScriptSig, outputIndex)
val spendingInfo = P2PKWithTimeoutSpendingInfo(
TransactionOutPoint(creditingTx.txIdBE, inputIndex),
creditingTx.outputs(outputIndex.toInt).value,
spk,
privKey,
hashType,
isBeforeTimeout = true)
val txSigComponentF = P2PKWithTimeoutSigner.sign(spendingInfo,
spendingTx,
isDummySignature = false)
val txSigComponent = Await.result(txSigComponentF, timeout)
val signedScriptSig =
txSigComponent.scriptSignature.asInstanceOf[ConditionalScriptSignature]
(signedScriptSig, spk, privKey)
}
```
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[BitcoinUTXOSpendingInfoFull] = {
ScriptGenerators.p2pkWithTimeoutScriptPubKey.flatMap { p2pkWithTimeout =>
build(p2pkWithTimeout._1, Seq(p2pkWithTimeout._2.head), None, None)
}
}
def p2pkWithTimeoutOutputs: Gen[Seq[BitcoinUTXOSpendingInfoFull]] = {
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`).

View file

@ -0,0 +1,59 @@
---
id: version-0.3.0-addresses
title: Generating Addresses
original_id: addresses
---
Almost all Bitcoin applications need to generate addresses
for their users somehow. There's a lot going on in getting
a correct bitcoin address, but our APIs make it possible to
to get started with all types of addresses in a matter of
minutes.
## Generating SegWit (bech32) addresses
Generating native SegWit addresses in the bech32 format
is something that all Bitcoin applications should enable,
as it makes the transaction fees less expensive, and also
makes the addresses more readable by humans. However, it
has seen slower than necessary adoption. With Bitcoin-S
you can generate bech32 addresses in four(!) lines of code
(not counting comments and imports), so now there's no
reason to keep using legacy transaction formats.
```scala
// this generates a random private key
val privkey = ECPrivateKey()
// privkey: ECPrivateKey = Masked(ECPrivateKeyImpl)
val pubkey = privkey.publicKey
// pubkey: crypto.ECPublicKey = ECPublicKey(037448f4f246a6e89479348e0e43471b542f76184e1f51547d2471390d417e04e7)
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 = Bech32Address(tb1qa854lhph9pyer2u7eau9hs6cyyud4g08jqkput)
println(segwitAddress.toString)
// Bech32Address(tb1qa854lhph9pyer2u7eau9hs6cyyud4g08jqkput)
```
## 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 = n2qmJtgQbBheZwW48yrZdApMAeGbnLHi3W
println(legacyAddress.toString)
// n2qmJtgQbBheZwW48yrZdApMAeGbnLHi3W
```

View file

@ -0,0 +1,108 @@
---
id: version-0.3.0-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 depedencies 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/core/protocol/NetworkElement). `NetworkElement` provides methods to convert the data structure to hex or byte representation. When paired with [`Factory`](/api/org/bitcoins/core/util/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 = BaseTransactionImpl(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/core/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 [`TxBuilder`](/api/org/bitcoins/core/wallet/builder/TxBuilder) 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 [UTXOs](/api/org/bitcoins/core/wallet/utxo/UTXOSpendingInfo) 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 = BaseTransactionImpl(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(BaseTransactionImpl(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(BaseTransactionImpl(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
```

View file

@ -0,0 +1,145 @@
---
id: version-0.3.0-hd-keys
title: HD Key Generation
original_id: hd-keys
---
In modern Bitcoin wallets, users only need to write down
a sequence of words, and that sequence is a complete backup
of their wallet. This is thanks to what's called Hierarchical
Deterministic key generation. In short, every wallet using HD
key generation has a root seed for each wallet, and this
seed can be used to generate an arbitrary amount of later
private and public keys. This is done in a standardized manner,
so different wallets can operate with the same standard.
> If you want to jump into the details of how this work,
> you should check out
> [BIP 32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki).
Bitcoin-S supports generating keys in this fashion. Here's a
full example of how to obtain a wallet seed, and then
use that to generate further private and public keys:
```scala
import scodec.bits._
import org.bitcoins.core.crypto._
import org.bitcoins.core.hd._
// the length of the entropy bit vector determine
// how long our phrase ends up being
// 256 bits of entropy results in 24 words
val entropy: BitVector = MnemonicCode.getEntropy256Bits
// entropy: BitVector = BitVector(256 bits, 0xce5268fd5c3797ddc4b028a2b1303e32aa231b81b4851ad07dfb962cdfa8f94f)
val mnemonicCode = MnemonicCode.fromEntropy(entropy)
// mnemonicCode: MnemonicCode = Masked(MnemonicCodeImpl)
mnemonicCode.words // the phrase the user should write down
// res0: Vector[String] = Vector(soft, nation, dismiss, reveal, just, upon, barrel, agree, pencil, maze, auto, grab, peasant, miss, almost, mouse, bracket, loop, leave, clump, social, stamp, topple, will) // 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 = zpub6jftahH18ngZxvQSvE3zGBJr1hihdd1oUkb16jQFgihtKLkgswnPAaYVZmoA7HNHS4DHhG9r7py8vvuCaw4QLNAwkBND2ZvomYGqJZXoPhc
// you can now use the generated xpriv to derive further
// private or public keys
// this can be done with BIP89 paths (called SegWitHDPath in bitcoin-s)
val segwitPath = SegWitHDPath.fromString("m/84'/0'/0'/0/0")
// segwitPath: SegWitHDPath = m/84'/0'/0'/0/0
// alternatively:
val otherSegwitPath =
SegWitHDPath(HDCoinType.Bitcoin,
accountIndex = 0,
HDChainType.External,
addressIndex = 0)
// otherSegwitPath: SegWitHDPath = m/84'/0'/0'/0/0
segwitPath == otherSegwitPath
// res1: Boolean = true
```
## Generating new addresses without having access to the private key
One the coolest features of HD wallets is that it's possible
to generate addresses offline, without having access to the
private keys. This feature is commonly called watch-only
wallets, where a wallet can import information about all
your past and future transactions, without being able to
spend or steal any of your money.
Let's see an example of this:
```scala
import scala.util.Success
import org.bitcoins.core.protocol.script._
import org.bitcoins.core.protocol.Bech32Address
import org.bitcoins.core.config.TestNet3
// first account -------┐
// bitcoin ----------┐ |
// segwit --------┐ | |
val accountPath = BIP32Path.fromString("m/84'/0'/0'")
// accountPath: BIP32Path = m/84'/0'/0'
val accountXpub = {
// this key is sensitive, keep away from prying eyes!
val accountXpriv = xpriv.deriveChildPrivKey(accountPath)
// this key is not sufficient to spend from, but we
// can generate addresses with it!
accountXpriv.extPublicKey
}
// accountXpub: ExtPublicKey = zpub6rtRJ6MNGa4pCdKffqgu83b6G8u1heXZTSmiUMHG2BuMFMAaAznpQ6M9xJJCQno4TeinBEQ5tVze78w96fEPymSSbpXMVXWtGMMVCmgkZmS
// 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 = Bech32Address(tb1qf04r2ukj279qqagy9al9mdpqcsp8v9lrkr8g38)
// tada! We just generated an address you can send money to,
// without having access to the private key!
firstAccountAddress.value
// res2: String = tb1qf04r2ukj279qqagy9al9mdpqcsp8v9lrkr8g38
// 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](sign.md) for information on how to sign things with HD keys.

View file

@ -0,0 +1,156 @@
---
id: version-0.3.0-psbts
title: Partially Signed Bitcoin Transactions
original_id: psbts
---
Creating unsigned or partially signed transactions to be passed
around to other signers can be a useful for many applications.
PSBTs offer a standardized format to serialize the necessary data
for signing the transaction, as well as, validating that you in fact
want to sign this transaction.
> If you want to jump into the details of the specification,
> you should checkout
> [BIP 174](https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki).
Bitcoin-S fully supports PSBTs with functionality for
creation, updating, combining, signing, finalizing,
and transaction extraction.
An example on a typical PSBT workflow:
```scala
implicit val ec: ExecutionContextExecutor = ExecutionContext.global
// First you need an unsigned transaction,
// here we have a standard 2 input, 2 output transaction
// This transaction must be of type BaseTransaction
// and have empty ScriptSignatures for all of it's inputs
val unsignedTransaction = BaseTransaction(
"020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000")
// To create the initial PSBT all we need to do is
val emptyPSBT = PSBT.fromUnsignedTx(unsignedTransaction)
// Now that we have an empty PSBT we can start updating it with data we know
// First, we want to fill the UTXO fields that we will need for signing and extraction
// The transactions we add are the fully serialized transaction that we are spending from
val utxo0 = Transaction(
"0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f618765000000")
val utxo1 = Transaction(
"0200000000010158e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd7501000000171600145f275f436b09a8cc9a2eb2a2f528485c68a56323feffffff02d8231f1b0100000017a914aed962d6654f9a2b36608eb9d64d2b260db4f1118700c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e88702483045022100a22edcc6e5bc511af4cc4ae0de0fcd75c7e04d8c1c3a8aa9d820ed4b967384ec02200642963597b9b1bc22c75e9f3e117284a962188bf5e8a74c895089046a20ad770121035509a48eb623e10aace8bfd0212fdb8a8e5af3c94b0b133b95e114cab89e4f7965000000")
val psbtWithUTXOs = emptyPSBT.addUTXOToInput(utxo0, index = 0).addUTXOToInput(utxo1, index = 1)
// After we have the relevant UTXOs we can add the
// redeem scripts, witness scripts, and BIP 32 derivation paths if needed
// In this transaction the first input is a P2SH 2-of-2 multisig
// so we need to add its corresponding redeem script.
// Here we are just using a deserialized version of the redeem script but
// you may generate your ScriptPubKey another way in practice
val redeemScript0 = ScriptPubKey.fromAsmBytes(
hex"5221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae")
val psbtWithUpdatedFirstInput =
psbtWithUTXOs.addRedeemOrWitnessScriptToInput(redeemScript0, index = 0)
// The second input in this transaction is a P2SH(P2WSH) 2-of-2 multisig
// so we need to add its corresponding redeem script and witness script.
// Here we add them both using the same function, the PSBT updater will
// be able to figure out, based on the available data, where to correctly
val redeemScript1 = ScriptPubKey.fromAsmBytes(
hex"00208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903")
val witnessScript = ScriptPubKey.fromAsmBytes(
hex"522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae")
// put the data in the PSBT
val psbtWithUpdatedSecondInput = psbtWithUpdatedFirstInput
.addRedeemOrWitnessScriptToInput(redeemScript1, index = 1)
.addRedeemOrWitnessScriptToInput(witnessScript, index = 1)
// Before signing we need to add the needed SigHash flags so we know how to sign the transaction
// If one is not provided it will be assumed to be SigHashAll
val psbtWithSigHashFlags = psbtWithUpdatedSecondInput
.addSigHashTypeToInput(HashType.sigHashAll, index = 0)
.addSigHashTypeToInput(HashType.sigHashAll, index = 1)
// Next, we can now sign the PSBT
// Signing a PSBT will return a Future[PSBT] so this will need to be handled
// correctly in an application
// Here we use the relevant private keys to sign the first input
val privKey0 = ECPrivateKey.fromWIFToPrivateKey(
"cP53pDbR5WtAD8dYAW9hhTjuvvTVaEiQBdrz9XPrgLBeRFiyCbQr")
val privKey1 = ECPrivateKey.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 BitcoinUTXOSpendingInfoSingle
// using the BitcoinSingleSigner 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 = BitcoinUTXOSpendingInfoSingle(
outPoint = outPoint,
output = output,
signer = privKey0,
redeemScriptOpt = Some(redeemScript0),
scriptWitnessOpt = None,
hashType = HashType.sigHashAll,
conditionalPath = ConditionalPath.NoConditionsLeft
)
// Then we can sign the transaction
val signatureF = BitcoinSignerSingle.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)
```

View file

@ -0,0 +1,65 @@
---
id: version-0.3.0-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(3044022002ef6ab03b3f0bc86bfea1f149c2730aa9cec84a456c6517ea9815878fa7e42302207159fe6378a1b47a4479aa534eb5c09f2ff78993d28fa6f10f5d9dc9946dcd74)
val path = BIP32Path(Vector(BIP32Node(0,false)))
// path: BIP32Path = m/0
extPrivKey.sign(DoubleSha256Digest.empty.bytes,path)
// res1: ECDigitalSignature = ECDigitalSignature(304502210083e44bafdee66ebf6bae54e8b19c5fb5b6457a72bcb255d67b7ffec5654afa9b0220061981821b0a538e5ec3b033322778cb25190306d4ed054bcba66098c771cafb)
```
With `ExtSign`, you can use `ExtPrivateKey` to sign transactions inside of `TxBuilder` since `UTXOSpendingInfo` takes in `Sign` as a parameter.
You can also provide a `path` to use to derive a child `ExtPrivateKey`, and then sign with that child private key

View file

@ -0,0 +1,13 @@
---
id: version-0.3.0-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 by the subtypes of `SingleSigner`, consists of using a single `ECPrivateKey` (or other [Sign implementation](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` on any instance of `SingleSigner`, you must have access to the unsigned transaction, as well as a `BitcoinUTXOSpendingInfoSingle`, 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 `FullSigner`, consists of using any number of `ECPrivateKey`s (or other [Sign implementations](sign.md)) to generate a fully signed, `TxSigComponent` for a given transaction. In order to call `sign` on any instance of `FullSigner`, you must have access to the unsigned transaction, as well as a `BitcoinUTXOSpendingInfo`, 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`.

View file

@ -0,0 +1,143 @@
---
id: version-0.3.0-txbuilder
title: TxBuilder Example
original_id: txbuilder
---
Bitcoin-S features a transaction builder that constructs and signs 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@259cf018[Running, parallelism = 8, size = 2, active = 0, running = 0, steals = 20, tasks = 0, submissions = 0]
// 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(0383604b72577aebd92ab56ba6141a3e942fb86941e37b2e978b11257f8b9a6400)
// 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(be30549d4ac6b3391370321ffbc630aef84272e6)
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(be30549d4ac6b3391370321ffbc630aef84272e6))
// 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 protecting the money
val destinationSPK =
P2PKHScriptPubKey(pubKey = destinationPrivKey.publicKey)
// destinationSPK: P2PKHScriptPubKey = pkh(fab4c315dedacdf530c6b560c146514749533e83)
// this is where we are sending money too
// we could add more destinations here if we
// wanted to batch transactions
val destinations = {
val destination1 = TransactionOutput(value = destinationAmount,
scriptPubKey = destinationSPK)
Vector(destination1)
}
// destinations: Vector[TransactionOutput] = Vector(TransactionOutput(5000 sats,pkh(fab4c315dedacdf530c6b560c146514749533e83)))
// 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 = BaseTransactionImpl(Int32Impl(1),Vector(),Vector(TransactionOutput(10000 sats,pkh(be30549d4ac6b3391370321ffbc630aef84272e6))),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(0f9997fb129243db98adaf0fb4882c14cfbda0a076b954b4bd0a7f500a41eeff:0)
// this contains all the information we need to
// validly sign the UTXO above
val utxoSpendingInfo = BitcoinUTXOSpendingInfoFull(outPoint = outPoint,
output = utxo,
signers = Vector(privKey),
redeemScriptOpt = None,
scriptWitnessOpt = None,
hashType =
HashType.sigHashAll,
conditionalPath =
ConditionalPath.NoConditionsLeft)
// utxoSpendingInfo: BitcoinUTXOSpendingInfoFull = P2PKHSpendingInfo(TransactionOutPoint(0f9997fb129243db98adaf0fb4882c14cfbda0a076b954b4bd0a7f500a41eeff:0),10000 sats,pkh(be30549d4ac6b3391370321ffbc630aef84272e6),Masked(ECPrivateKeyImpl),SIGHASH_ALL(Int32Impl(1)))
// all of the UTXO spending information, since we are only
//spending one UTXO, this is just one element
val utxos: Vector[BitcoinUTXOSpendingInfoFull] = Vector(utxoSpendingInfo)
// utxos: Vector[BitcoinUTXOSpendingInfoFull] = Vector(P2PKHSpendingInfo(TransactionOutPoint(0f9997fb129243db98adaf0fb4882c14cfbda0a076b954b4bd0a7f500a41eeff:0),10000 sats,pkh(be30549d4ac6b3391370321ffbc630aef84272e6),Masked(ECPrivateKeyImpl),SIGHASH_ALL(Int32Impl(1))))
// 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(79c2ad65b2805f0b56ee83b160a0fc6e9eeec5ff)
// the network we are on, for this example we are using
// the regression test network. This is a network you control
// on your own machine
val networkParams = RegTest
// networkParams: RegTest.type = RegTest
// Yay! Now we have a TxBuilder object that we can use
// to sign the TX.
val txBuilder: BitcoinTxBuilder = {
val builderF = BitcoinTxBuilder(
destinations = destinations,
utxos = utxos,
feeRate = feeRate,
changeSPK = changeSPK,
network = networkParams)
Await.result(builderF, 30.seconds)
}
// txBuilder: BitcoinTxBuilder = BitcoinTxBuilderImpl(Vector(TransactionOutput(5000 sats,pkh(fab4c315dedacdf530c6b560c146514749533e83))),Map(TransactionOutPoint(0f9997fb129243db98adaf0fb4882c14cfbda0a076b954b4bd0a7f500a41eeff:0) -> P2PKHSpendingInfo(TransactionOutPoint(0f9997fb129243db98adaf0fb4882c14cfbda0a076b954b4bd0a7f500a41eeff:0),10000 sats,pkh(be30549d4ac6b3391370321ffbc630aef84272e6),Masked(ECPrivateKeyImpl),SIGHASH_ALL(Int32Impl(1)))),SatoshisPerByte(1 sat),pkh(79c2ad65b2805f0b56ee83b160a0fc6e9eeec5ff),RegTest)
// Let's finally produce a validly signed 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 UTXOSpendingInfo 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 signF = txBuilder.sign
Await.result(signF, 30.seconds)
}
// signedTx: Transaction = BaseTransactionImpl(Int32Impl(2),List(TransactionInputImpl(TransactionOutPoint(0f9997fb129243db98adaf0fb4882c14cfbda0a076b954b4bd0a7f500a41eeff:0),P2PKHScriptSignature(ECPublicKey(0383604b72577aebd92ab56ba6141a3e942fb86941e37b2e978b11257f8b9a6400), ECDigitalSignature(3045022100f8138b17824e063daa266388d28117e122bcaca623b5cd5410959b96692d7e5f02201110005441e352ea076ec27c21524ad51ac1b7315209e9760ae07aaf726978ac01)),UInt32Impl(0))),Vector(TransactionOutput(5000 sats,pkh(fab4c315dedacdf530c6b560c146514749533e83)), TransactionOutput(4774 sats,pkh(79c2ad65b2805f0b56ee83b160a0fc6e9eeec5ff))),UInt32Impl(0))
```
```scala
signedTx.inputs.length
// res0: Int = 1
signedTx.outputs.length
// res1: Int = 2
//remember, you can call .hex on any bitcoin-s data structure to get the hex representation!
signedTx.hex
// res2: String = 0200000001ffee410a507f0abdb454b976a0a0bdcf142c88b40fafad98db439212fb97990f000000006b483045022100f8138b17824e063daa266388d28117e122bcaca623b5cd5410959b96692d7e5f02201110005441e352ea076ec27c21524ad51ac1b7315209e9760ae07aaf726978ac01210383604b72577aebd92ab56ba6141a3e942fb86941e37b2e978b11257f8b9a6400000000000288130000000000001976a914fab4c315dedacdf530c6b560c146514749533e8388aca6120000000000001976a91479c2ad65b2805f0b56ee83b160a0fc6e9eeec5ff88ac00000000
```

View file

@ -0,0 +1,136 @@
---
id: version-0.3.0-getting-setup
title: Getting Bitcoin-S installed on your machine
original_id: getting-setup
---
## Getting Setup With Bitcoin-S
## Step 1: Java and Scala
The first step in getting setup will be getting the [Java Development Kit](https://www.oracle.com/java/technologies/javase-downloads.html) (JDK) installed on your machine. Bitcoin-S works best with Java 8 but _should_ also work with Java 11 and Java 13.
Once java is setup on your machine (try running `javac -version`), you are ready to download and install the [Scala Build Tool](https://www.scala-sbt.org/download.html) (sbt). Note that running `sbt` for the first time will take a while.
## 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
```
## 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 dlc
git submodule update
```
and then 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 (note that this will require you install some graalvm things as detailed in the instructions).
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/graalvm-native-image/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 sink 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.

View file

@ -0,0 +1,111 @@
---
id: version-0.3.0-getting-started
title: Intro and Getting Started
original_id: getting-started
---
## Philosophy
Bitcoin-S is a loosely coupled set of cryptocurrency libraries for the JVM. They work well together, but also can be used
independently. This project's goal is NOT to be a full node implementation, rather a set of scalable cryptocurrency libraries
that use industry standard tools (rather than esoteric tech often found in cryptocurrency) where possible to make the lives of professional
software engineers, security engineers, devops engineers and accountants easier.
We are rapidly iterating on development with the goal of getting to a set of stable APIs that only change when the underlying bitcoin protocol changes.
If you are a professional working a cryptocurrency business and
have feedback on how to make your lives easier, please reach out on [slack](https://join.slack.com/t/suredbits/shared_invite/enQtNDEyMjY3MTg1MTg3LTYyYjkwOGUzMDQ4NDAwZjE1M2I3MmQyNWNlZjNlYjg4OGRjYTRjNWUwNjRjNjg4Y2NjZjAxYjU1N2JjMTU1YWM),
[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.2.0"
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-core" % "0.2.0"
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-chain" % "0.2.0"
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-bitcoind-rpc" % "0.2.0"
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-eclair-rpc" % "0.2.0"
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-key-manager" % "0.2.0"
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-node" % "0.2.0"
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-wallet" % "0.2.0"
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-testkit" % "0.2.0"
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-zmq" % "0.2.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.2.0+273-67a4955b+20200318-1412-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.

View file

@ -0,0 +1,133 @@
---
id: version-0.3.0-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`](../../key-manager/src/main/scala/org/bitcoins/keymanager/bip39/BIP39KeyManager.scala).
The [`BIP39KeyManager`](../../key-manager/src/main/scala/org/bitcoins/keymanager/bip39/BIP39KeyManager.scala) stores a [`MnemonicCode`](../../core/src/main/scala/org/bitcoins/core/crypto/MnemonicCode.scala) 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](../../core/src/main/scala/org/bitcoins/core/crypto/BIP39Seed.scala)
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, 0xbf2968260f174937863a69ac8e2e8f25813b5437246077b6c3f88f95ef932d76)
val mnemonic = MnemonicCode.fromEntropy(entropy)
// mnemonic: MnemonicCode = Masked(MnemonicCodeImpl)
//you can print that mnemonic seed with this
println(mnemonic.words)
// Vector(sand, ensure, another, bullet, innocent, orange, body, essay, prosper, imitate, phrase, enrich, beauty, present, symptom, metal, jeans, render, wreck, busy, galaxy, sister, remind, sort)
```
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`](../../core/src/main/scala/org/bitcoins/core/hd/HDPurpose.scala) 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`](../../core/src/main/scala/org/bitcoins/core/config/NetworkParameters.scala) 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-example6695794117403645345/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-example6695794117403645345/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-example6695794117403645345/encrypted-bitcoin-s-seed.json,m/84',RegTest),None))
val rootXPub = km.right.get.getRootXPub
// rootXPub: ExtPublicKey = vpub5SLqN2bLY4WeZwnivVWfghDdWLN1osw3cJagfGXncZhnZ4T1r9m2D6Be4h6ceRY6YQR2hDLcvExdAq36cuKTKbEDDBn8CAUAjTxn5Y1o9JH
println(rootXPub)
// vpub5SLqN2bLY4WeZwnivVWfghDdWLN1osw3cJagfGXncZhnZ4T1r9m2D6Be4h6ceRY6YQR2hDLcvExdAq36cuKTKbEDDBn8CAUAjTxn5Y1o9JH
```
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-example6695794117403645345/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)
// mainnetKeyManager: BIP39KeyManager = BIP39KeyManager(Masked(MnemonicCodeImpl),KeyManagerParams(/tmp/key-manager-example6695794117403645345/encrypted-bitcoin-s-seed.json,m/84',MainNet),None)
val mainnetXpub = mainnetKeyManager.getRootXPub
// mainnetXpub: ExtPublicKey = zpub6jftahH18ngZy8ZCFvfAX3beCCwoaMu3GkfZnr7L8bDJmThvrnRGhLpC9Wvxe49nAxtFh7irktNphyVMVgyWWXxcgYZpXok7pNDMdoTT1K6
println(mainnetXpub)
// zpub6jftahH18ngZy8ZCFvfAX3beCCwoaMu3GkfZnr7L8bDJmThvrnRGhLpC9Wvxe49nAxtFh7irktNphyVMVgyWWXxcgYZpXok7pNDMdoTT1K6
```
Which gives us something that looks like this
`zpub6jftahH18ngZw98KGjRo5XcxeKTQ2eztsvskb1dC9XF5TLimQquTs6Ry7nBBA425D9joXmfgJJCexmJ1u2SELJZJfRi95gcnXadLpZzYb5c`
which is a p2sh wrapped segwit `ExtPubKey` for the bitcoin main network!
#### Creating a key manager from existing mnemonic
To create a `KeyManager` from existing mnemonic you need to specify the `seedPath` and then construct the `KeyManagerParams` that you would like.
Finally you call `KeyManager.fromParams()` that reads the mnemonic from disk and create's the key manager

View file

@ -0,0 +1,134 @@
---
id: version-0.3.0-node
title: Light Client
original_id: node
---
Bitcoin-s has node module that allows you to connect to the p2p network.
### Neutrino Node
Bitcoin-s has experimental support for neutrino which is a new lite client proposal on the bitcoin p2p network. You can
read more about how neutrino works [here](https://suredbits.com/neutrino-what-is-it-and-why-we-need-it/). At this time,
bitcoin-s only supports connecting to one trusted peer.
#### 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. Let's make a 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.initialize()
//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 = { block: Block =>
println(s"Received blockhash=${block.blockHeader.hashBE}")
}
val nodeCallbacks = NodeCallbacks.onBlockReceived(blockReceivedFunc)
//ok, now we need to add this allback to our running node
val nodeWithCallbackF = for {
node <- startedNodeF
withCallback = node.addCallbacks(nodeCallbacks)
} yield withCallback
//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 <- nodeWithCallbackF
x = NeutrinoNodeConnectedWithBitcoind(node.asInstanceOf[NeutrinoNode],bitcoind)
_ <- NodeUnitTest.destroyNodeConnectedWithBitcoind(x)
} yield ()
Await.result(cleanupF, 60.seconds)
```

View file

@ -0,0 +1,108 @@
---
id: version-0.3.0-rpc-bitcoind
title: bitcoind/Bitcoin Core
original_id: rpc-bitcoind
---
> Note: `bitcoin-s-bitcoind-rpc` requires you to have `bitcoind` (Bitcoin Core daemon) installed. Grab this at [bitcoincore.org](https://bitcoincore.org/en/download/)
The Bitcoin Core RPC client in Bitcoin-S currently supports the Bitcoin Core 0.16, 0.17, 0.18, and 0.19
version lines. It can be set up to work with both local and remote Bitcoin Core servers.
You can fetch them using bitcoin-s by running the following sbt command
```bash
sbt downloadBitcoind
```
The binaries will be stored in `$HOME/.bitcoin-s/binaries/bitcoind/`
## Connecting to a local `bitcoind` instance
### Getting started quickly, with default options:
```scala
implicit val ec: ExecutionContext = ExecutionContext.global
// this reads authentication credentials and
// connection details from the default data
// directory on your platform
val client = BitcoindRpcClient.fromDatadir(binary=new File("/path/to/bitcoind"), datadir=new File("/path/to/bitcoind-datadir"))
val balance: Future[Bitcoins] = for {
_ <- client.start()
balance <- client.getBalance
} yield balance
```
## Connecting to a remote `bitcoind`
First, we create a secure connection to our `bitcoind` instance by setting
up a SSH tunnel:
```bash
$ ssh -L 8332:localhost:8332 \
my-cool-user@my-cool-website.com
```
> Note: the port number '8332' is the default for mainnet. If you want to
> connect to a testnet `bitcoind`, the default port is '18332'
Now that we have a secure connection between our remote `bitcoind`, we're
ready to create the connection with our RPC client
```scala
implicit val ec: ExecutionContext = ExecutionContext.global
val username = "FILL_ME_IN" //this username comes from 'rpcuser' in your bitcoin.conf file //this username comes from 'rpcuser' in your bitcoin.conf file
val password = "FILL_ME_IN" //this password comes from your 'rpcpassword' in your bitcoin.conf file //this password comes from your 'rpcpassword' in your bitcoin.conf file
val authCredentials = BitcoindAuthCredentials.PasswordBased(
username = username,
password = password
)
val bitcoindInstance = {
BitcoindInstance (
network = MainNet,
uri = new URI(s"http://localhost:${MainNet.port}"),
rpcUri = new URI(s"http://localhost:${MainNet.rpcPort}"),
authCredentials = authCredentials
)
}
val rpcCli = BitcoindRpcClient(bitcoindInstance)
rpcCli.getBalance.onComplete { case balance =>
println(s"Wallet balance=${balance}")
}
```
## Error handling
All errors returned by Bitcoin Core are mapped to a corresponding
[`BitcoindException`](https://github.com/bitcoin-s/bitcoin-s/blob/master/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/BitcoindException.scala).
These exceptions contain an error code and a message. `BitcoindException` is a sealed
trait, which means you can easily pattern match exhaustively. Of course, other errors
could also happen: network errors, stack overflows or out-of-memory errors. The provided
class is only intended to cover errors returned by Bitcoin Core. An example of how error
handling could look:
```scala
implicit val ec = ExecutionContext.global
// let's assume you have an already running client,
// so there's no need to start this one
val cli = BitcoindRpcClient.fromDatadir(binary=new File("/path/to/bitcoind"), datadir=new File("/path/to/bitcoind-datadir"))
// let's also assume you have a bitcoin address
val address: BitcoinAddress = ???
val txid: Future[DoubleSha256DigestBE] =
cli.sendToAddress(address, 3.bitcoins).recoverWith {
case BitcoindWalletException.UnlockNeeded(_) =>
cli.walletPassphrase("my_passphrase", 60).flatMap { _ =>
cli.sendToAddress(address, 3.bitcoins)
}
}
```

View file

@ -0,0 +1,60 @@
---
id: version-0.3.0-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.3.3](https://github.com/ACINQ/eclair/releases/tag/v0.3.3) 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 the jar
You need to download the jar from the [eclair's github](https://github.com/ACINQ/eclair/releases/tag/v0.3.3).
To run Eclair you can use this command:
```bash
$ java -jar eclair-node-0.3.3-12ac145.jar &
```
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)

View file

@ -0,0 +1,86 @@
---
id: version-0.3.0-secp256k1
title: Secp256k1
original_id: secp256k1
---
[Libsecp256k1](https://github.com/bitcoin-core/secp256k1) is used to preform cryptographic operations on the secp256k1 curve.
This is the curve that bitcoin uses. There is a _signficant_ speedup when using this library compared to java crypto libraries
like bouncy castle.
In bitcoin-s, we support native binaries for libsecp256k1
1. [linux 32 bit](../../secp256k1jni/natives/linux_32)
2. [linux 64 bit](../../secp256k1jni/natives/linux_64)
3. [mac osx 64 bit](../../secp256k1jni/natives/osx_64)
4. [windows 64 bit](../../secp256k1jni/natives/windows_64)
Bitcoin-s uses a zero dependency library called [`native-lib-loader`](https://github.com/scijava/native-lib-loader).
That does the appropriate loading of the library onto your classpath to be accessed.
#### Using libsecp256k1
To tell if you have access to libsecp256k1 you can do the following
```scala
val isEnabled = org.bitcoin.Secp256k1Context.isEnabled()
println(s"Secp256k1Context.isEnabled=${isEnabled}")
```
If libsecp256k1 is enabled, you can use [NativeSecp256k1](../../secp256k1jni/src/main/java/org/bitcoin/NativeSecp256k1.java)
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 `Secp256k1Context.isEnabled()` to return 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(03b12dbe31d79acc699af9ea0c741ab1e98d821a885ad8690cfc2f9921decc771b)
val dataToSign = DoubleSha256Digest.empty
// dataToSign: DoubleSha256Digest = DoubleSha256Digest(0000000000000000000000000000000000000000000000000000000000000000)
val signature = privKey.signWithBouncyCastle(dataToSign.bytes)
// signature: ECDigitalSignature = ECDigitalSignature(30440220042bb6ce44f919a759e341f4f87e6ccd074253161e50d1d84668da68fbfb3f2a022036c759496f425b24446c79240dcc8fc1fe3c5c4ae2619fcb960efae98e37df26)
val verified = publicKey.verifyWithBouncyCastle(dataToSign.bytes, signature)
// verified: Boolean = true
println(s"Verified with bouncy castle=${verified}")
// Verified with bouncy castle=true
```

View file

@ -0,0 +1,130 @@
---
id: version-0.3.0-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-----
Version: GnuPG v1
mQINBFku8rYBEACub6cz6nAy/MHK2TlTztkSJbNpc2B3tZoVCKRz987yLX578JiO
EDYosAsD4b4nIjY1O6kD5FUjoJttQO+7IR8RwkmBFRsPqXaryrGr69FqaPlGSMLi
uNVlawe0qMY79DPNw03Pu2tdgiq94sWplmOnuyQqcg2lkoK1+8/DXX3801s164wq
4ZCCmkAAxJDhoLTyXB6LNI586vJhQ5+SVh8z/kAtp6NVXU5v2LcnkrqlX4oaakbt
nY2FYpAZgEI8T+Fg+btGbt3muajhM9ooq0LBtaIMwgcTC4JPyFg6acWaluMGCOtW
k1j8eu2/J4ly27YGCtWoDwrDen5qzHF6mm1HI4ko9fG2L4SxxXkl7DpGgtR71D1R
mSA9yot8oPwWW1NRncIct188uRuhy4CJIylNU88iqgpUZfYRqwaDl2KCrEbetGMT
yZSBVyilXdNjzFUJFtOPAxKmYPztUcEL1hEyGOu03YFt3FQr7QHhNR8+v688DdH4
QHpwcrXkB0x/wq18tCO6gNTbnt94VR4PJUvz3BjP7XrHGn3ljKt5PPHO7cmPkDH5
/iP928VC1U5FjAJh6dpsGgFb7k51CKx9bUJRDmL3GUMxFvmo+4YZpTKS5G+Ighs3
vLIzO1X0aV5b4mltn4Fz4o/Cp+CSVqK//YnetezkmrvwQ2u/iOGeqRKhXwARAQAB
tCtDaHJpcyBTdGV3YXJ0IDxzdGV3YXJ0LmNocmlzMTIzNEBnbWFpbC5jb20+iQI+
BBMBAgAoAhsDBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAUCWxW1AAUJBakpQwAK
CRCYRuzvvweIvijkD/0RQzQAWcJY5XJ503vj0CidzKym4ueykxhgISiZ+hmjr+u5
/Poceo5z3nXj1zI8/yMIb//7WRiX61+JGFKwN/OsM07KHa/J8Rf3NCASO2hYCGjq
ev0UOWUeSqHwN8zr4kAY9hKZJVR7byKmYV/iqmuAFTB4Nrs8567CzqKaPezB1mKv
JMTxx4XiL4M6cFiBSbCBi0qGYS6M0zBETJvFAmSh3H3PtUUdGz0kAyE2Ju11qhPh
yZVCjILnzHbsguqPSPsj3Cba6+WcbGzm7HIBPdZDjIvcnuw3Mq8kjtcpOWTMoGP1
q9xHioP97Gh+E+z7iFo8g2NszZ1wEzL4k3TnO0keknOmfOVn5zSINp6M8CHCTbzr
ls+qqHjcbAfs5BbNRMXxapO78kvSQSDenRjpC7JCl3FIwWqoMMi7T3PIYVFQfMG+
EyC3UMVOGmIDavw6yDCCdILcGeMdafEuzZmmV3Qvtt3LsqQjjm+TyBhWazRR+9sJ
kRgSdvBkF8XgqyOpaLffWPoKxnaQtJwylQnYuUM8kLP52aH2C9ctKxLChu1BDmZm
aWZNpm8h9jySeybQSBlMeDd8TrscuGJ8qBEOfREuv7fqYxsIi+G74+9h5kjiOe+y
hkIkSI440mAHRIoCyAkBx4EFZN5lRdYShRGRQrCWD/RrFssABmXhBik707V0nLkC
DQRZLvK2ARAA1gn4P9fm9KJpYuwYfxwrl0VfDPMNct5dDY6zbdQGV3djBTcuyeVU
EL6w4rRfF1aPFFEXGhT2zn2tPY410fswTNANLKuP/sFEiGEfaMjmYu0DucCMyznK
KMbE5FGD/ue0SHAN2K+A13OkPVv6kabu7XiK2QzYBrw1Lk7kGhSqMb8t3hVs0A9B
iAqWFhSIBfmnd5SKyJKNksv7DuBcgK9Xjszlbr2gV0v1kc0aMHGdZH4omB5qmq8I
aFkgZcY3MbA1zmetd1YauwzhZCFyTFcebm9trTn4D5htHGgXSehP3DMc4y9HVaLL
rANAi0Ykk7Bi+BmYHkjsvt7FZj2/V/KWkwgiPjEBvnKX3iWpbJdqU2os9VZM3ctB
WaE4pwsXt1kpiEgc/qUJ2xQL3po0dTO8q5heE6iZUNlD+xBLwpUxGjS8rN+V0LCt
muqtZ8N8lSZD5U1XVVEpEdfe8lPKQ0wvjZZUTjix1j18mhiwE+cC66JNyM3mlMUx
GKHg6M8nnXxTSbv2ogj7wXmqPF5dq5uGzCeDhdnR5tRmNQCpW/QUq8AJzV7VXRJl
+qZXar7DgbGUeRBY4M3LJptYUYqJ3LdxYPAO0AeKdlO0e843BaeG1ASGlhCZRGOn
Z0xYL7wBiVgwtx57y7D1zpQE1a9Mh7+jnUnH4tplYv70TsQ9wF5yuRsAEQEAAYkC
JQQYAQIADwIbDAUCWxW0LAUJBakobgAKCRCYRuzvvweIvhSvD/0RlNWzls+OlLqc
6NT/ZGGpECntrfTpQqA0MakuuIkf6/RgBU4OfHqqcBrG8onV1vuu0hH0KnYvv658
01PHRthIZN1K/+jcaIdNkOLbl9k9rWaDxKkHzH+IcIF61b6oCW86ffZ1yzZE2nxY
zjO0MVM4IlUxtHbrItVj5Itfj7QxPf1YdmLxO/xH2NRbmVwwTSKe4/xK3v1LSxlH
R5vknUWIT69VJoINB7jye/aSpBYpJgxXXiGbdYyt6fLyLxds7HXLJ6v6SfBKLuvm
qXsDWX5Jyv7EWzzHgl7iCIkCe9tr8ic9IyyWiRn5bjoo5vVFVMYWU/ZtGpYerAh/
sp8M09q7OeJbtbYkh9Nqz78ALt7ldT+PeAmqBVqb188F3tG8yG9lQNy43bk90Q8N
o7ZpjxlpKFYoSq/Ow7TZgMCdMPkGbcCaSkGDv/McGsaOa7IYf+7n/yE0xMSb0x5A
wyaXSLZjScTPiKGAP4fAf6hb0H1Je/69DOJynaODONAYfmlM+9WQjuVlQ3J0rlcK
Cgvon6XyxrZ4q1tKheAxOmyM0OSuPYyeXSqCYu5bt7MzNEDUChydZM3v1QvEB9iG
6eLX7fq2ipGGDoVl8y1yVRe4BWa3cJfCPC7jT+9VtD/qdlpHSwotYTWj5metYgqB
LTIbdd7r9XCGoKIxMJRqNFXc8kylUg==
=J0NH
-----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-----
```

View file

@ -0,0 +1,163 @@
---
id: version-0.3.0-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](../../bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/BitcoindRpcClient.scala) is tested with the functionality provided in the testkit.
A quick example of a useful utility method is [BitcoindRpcTestUtil.startedBitcoindRpcClient()](../../bitcoind-rpc/src/main/scala/org/bitcoins/testkit/rpc/BitcoindRpcTestUtil.scala).
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](../../testkit/src/main/scala/org/bitcoins/testkit/chain/ChainUnitTest.scala)
2. [Key Manager](../../testkit/src/main/scala/org/bitcoins/testkit/keymanager/KeyManagerUnitTest.scala)
3. [Wallet](../../testkit/src/main/scala/org/bitcoins/testkit/wallet/BitcoinSWalletTest.scala)
4. [Node](../../testkit/src/main/scala/org/bitcoins/testkit/node/NodeUnitTest.scala)
In general, you will find constructors and destructors of fixtures that can be useful when testing your applications
if yo uare using any of those modules.

View file

@ -0,0 +1,179 @@
---
id: version-0.3.0-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 => Unit,
processCompactFilter: (DoubleSha256Digest, GolombFilter) => Unit,
processBlock: Block => Unit): NodeCallbacks = {
lazy val onTx: OnTxReceived = { tx =>
processTransaction(tx)
()
}
lazy val onCompactFilter: OnCompactFilterReceived = {
(blockHash, blockFilter) =>
processCompactFilter(blockHash, blockFilter)
()
}
lazy val onBlock: OnBlockReceived = { block =>
processBlock(block)
}
NodeCallbacks(onTxReceived = Seq(onTx),
onBlockReceived = Seq(onBlock),
onCompactFilterReceived = Seq(onCompactFilter))
}
// 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) =>
println(s"Received tx: ${tx.txIdBE}")
val exampleProcessBlock = (block: Block) =>
println(s"Received block: ${block.blockHeader.hashBE}")
val exampleProcessFilter =
(blockHash: DoubleSha256Digest, filter: GolombFilter) =>
println(s"Received filter: ${blockHash.flip.hex} ${filter.hash.flip.hex}")
val exampleCallbacks =
createCallbacks(exampleProcessTx, exampleProcessFilter, 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 {
/** 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)
// Then to trigger one of the events we can run
wallet.chainQueryApi.getFiltersBetweenHeights(100, 150)
```

View file

@ -0,0 +1,166 @@
---
id: version-0.3.0-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]
```

View file

@ -0,0 +1,87 @@
---
id: version-0.3.0-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 => Unit): NodeCallbacks = {
lazy val onBlock: OnBlockReceived = { block =>
processBlock(block)
}
NodeCallbacks(onBlockReceived = Seq(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) =>
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 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)
// Then to trigger the event we can run
val exampleBlock = DoubleSha256Digest(
"000000000010dc23dc0d5acad64667a7a2b3010b6e02da4868bf392c90b6431d")
wallet.nodeApi.downloadBlocks(Vector(exampleBlock))
```

View file

@ -0,0 +1,81 @@
---
title: Wallet Rescans
id: version-0.3.0-wallet-rescan
original_id: wallet-rescan
---
With [BIP157](https://github.com/bitcoin/bips/blob/master/bip-0157.mediawiki) you can cache block filters locally to use
later for rescans in the case you need to restore your wallets. Our [chain](../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/1a3b6b5b1e4eb8442dfab8b1a9faeff74418bdb0/wallet/src/main/scala/org/bitcoins/wallet/api/WalletApi.scala#L399)
### 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()
//ok now let's spin up a bitcoind and a bitcoin-s wallet with funds in it
val walletWithBitcoindF = for {
w <- BitcoinSWalletTest.createWalletBitcoindNodeChainQueryApi()
} yield w
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.clearUtxosAndAddresses()
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.fullRescanNeurinoWallet(addrBatchSize)
balanceAfterRescan <- w.getBalance()
} yield {
println(s"Wallet balance after rescan: ${balanceAfterRescan}")
()
}
//cleanup
val cleanupF = for {
_ <- rescannedBalanceF
walletWithBitcoind <- walletWithBitcoindF
_ <- BitcoinSWalletTest.destroyWalletWithBitcoind(walletWithBitcoind)
} yield ()
Await.result(cleanupF, 60.seconds)
```

View file

@ -0,0 +1,155 @@
---
title: Wallet
id: version-0.3.0-wallet
original_id: wallet
---
## Bitcoin-s wallet
Bitcoin-s comes bundled with a rudimentary Bitcoin wallet. This wallet
is capable of managing private keys, generating addresses, constructing
and signing transactions, among other things. It is BIP32/BIP44/BIP49/BIP84
compatible.
This wallet is currently only released as a library, and not as a binary.
This is because it (nor the documentation) is not deemed production
ready. Use at your own risk, and without too much money depending on it.
### Disclaimer
The wallet api will changing significantly in the next release of bitcoin-s. EXPECT API BREAKING CHANGES and
surprising behavior from the current wallet..
### 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")
}
```

View file

@ -0,0 +1,62 @@
{
"version-0.3.0-docs": {
"Getting Started": [
"version-0.3.0-getting-started"
],
"Getting Setup": [
"version-0.3.0-getting-setup"
],
"Applications": [
"version-0.3.0-applications/cli",
"version-0.3.0-applications/server"
],
"Chain": [
"version-0.3.0-chain/chain",
"version-0.3.0-chain/filter-sync"
],
"Configuration": [
"version-0.3.0-config/configuration"
],
"Core Module": [
"version-0.3.0-core/core-intro",
"version-0.3.0-core/addresses",
"version-0.3.0-core/hd-keys",
"version-0.3.0-core/adding-spks",
"version-0.3.0-core/spending-info",
"version-0.3.0-core/sign",
"version-0.3.0-core/psbts",
"version-0.3.0-core/txbuilder"
],
"Key Manager": [
"version-0.3.0-key-manager/key-manager"
],
"Node": [
"version-0.3.0-node/node"
],
"Wallet": [
"version-0.3.0-wallet/wallet",
"version-0.3.0-wallet/chain-query-api",
"version-0.3.0-wallet/node-api",
"version-0.3.0-wallet/dlc",
"version-0.3.0-wallet/wallet-rescan"
],
"RPC Clients": [
"version-0.3.0-rpc/rpc-clients-intro",
"version-0.3.0-rpc/rpc-eclair",
"version-0.3.0-rpc/rpc-bitcoind"
],
"Secp256k1": [
"version-0.3.0-secp256k1/secp256k1"
],
"Testkit": [
"version-0.3.0-testkit/testkit"
],
"Contributing": [
"version-0.3.0-contributing",
"version-0.3.0-contributing-website"
],
"Security": [
"version-0.3.0-security"
]
}
}