From cee495382e34859db4e7db0f800a113a07901bf0 Mon Sep 17 00:00:00 2001 From: Einherjar Date: Sat, 23 Sep 2023 17:20:28 -0300 Subject: [PATCH] feat: cookbook Cookbook on how to sign segwit and taproot transactions Additionally: - Convert `build.sh` to a GitHub Action - Automated CI in GitHub Actions that test all the code in the cookbook - Automated scheduled CI in GitHub Actions that check all markdown files for broken links - Dependabot to update (weekly) - GitHub Actions - Test dependencies - `justfile` to easy run the build and test with `just` --- .github/dependabot.yml | 13 ++ .github/workflows/check-links.yml | 25 ++ .github/workflows/gh-pages.yml | 44 ++++ .github/workflows/test.yml | 28 +++ .gitignore | 3 + README.md | 10 + build.sh | 77 ------- cookbook/README.md | 59 ++++- cookbook/book.toml | 13 +- cookbook/src/SUMMARY.md | 8 +- cookbook/src/about.md | 8 - cookbook/src/chapter_1.md | 1 - cookbook/src/intro.md | 46 ++++ cookbook/src/table_of_contents.md | 1 - cookbook/src/tx.md | 6 + cookbook/src/tx_segwit-v0.md | 353 +++++++++++++++++++++++++++++ cookbook/src/tx_taproot.md | 365 ++++++++++++++++++++++++++++++ cookbook/tests/Cargo.toml | 9 + cookbook/tests/generate.sh | 17 ++ justfile | 14 ++ site/static/CNAME | 1 + 21 files changed, 1005 insertions(+), 96 deletions(-) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/check-links.yml create mode 100644 .github/workflows/gh-pages.yml create mode 100644 .github/workflows/test.yml delete mode 100755 build.sh delete mode 100644 cookbook/src/about.md delete mode 100644 cookbook/src/chapter_1.md create mode 100644 cookbook/src/intro.md delete mode 100644 cookbook/src/table_of_contents.md create mode 100644 cookbook/src/tx.md create mode 100644 cookbook/src/tx_segwit-v0.md create mode 100644 cookbook/src/tx_taproot.md create mode 100644 cookbook/tests/Cargo.toml create mode 100755 cookbook/tests/generate.sh create mode 100644 justfile create mode 100644 site/static/CNAME diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..b294f7f --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,13 @@ +# Set update schedule for GitHub Actions +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + # Check for updates to GitHub Actions every week + interval: "weekly" + - package-ecosystem: "cargo" + directory: "/cookbook/tests" # point this towards a Cargo.toml directory + schedule: + # Check for updates to Rust dependencies every week + interval: "weekly" \ No newline at end of file diff --git a/.github/workflows/check-links.yml b/.github/workflows/check-links.yml new file mode 100644 index 0000000..618746e --- /dev/null +++ b/.github/workflows/check-links.yml @@ -0,0 +1,25 @@ +name: Markdown Links Check +# runs every monday at 9 am +on: + schedule: + - cron: "0 9 * * 1" + workflow_dispatch: null + +jobs: + check-links: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: gaurav-nelson/github-action-markdown-link-check@v1 + # checks all markdown files from /cookbook including all subfolders + with: + use-quiet-mode: 'yes' + use-verbose-mode: 'yes' + folder-path: 'cookbook/' + - uses: actions/checkout@v4 + - uses: gaurav-nelson/github-action-markdown-link-check@v1 + # checks all markdown files from root but ignores subfolders + with: + use-quiet-mode: 'yes' + use-verbose-mode: 'yes' + max-depth: 0 diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml new file mode 100644 index 0000000..d12a811 --- /dev/null +++ b/.github/workflows/gh-pages.yml @@ -0,0 +1,44 @@ +# This needs the following setting: +# Setttings > Actions > General > Workflow permissions +# change to "Read and write permissions" +name: github pages + +on: + push: + branches: + - master + workflow_dispatch: null + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: true # could be useful in the future + fetch-depth: 0 # we just need the latest commit + + - name: Setup mdBook + uses: peaceiris/actions-mdbook@v1 + with: + mdbook-version: 'latest' + + - name: Build mdbook + working-directory: ./cookbook + run: mdbook build --dest-dir "../site/static/book" + + - name: Setup Hugo + uses: peaceiris/actions-hugo@v2 + with: + hugo-version: "latest" + + - name: Build Hugo + working-directory: ./site + run: hugo --minify + + - name: Deploy + uses: peaceiris/actions-gh-pages@v3 + if: github.ref == 'refs/heads/master' + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./site/public diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..de1e409 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,28 @@ +name: Tests +on: + push: + branches: [ master, '[0-9]+.[0-9]+.[0-9]+', '[0-9]+.[0-9]+.[0-9]+-[A-Za-z0-9]+' ] + pull_request: + branches: [ master, '[0-9]+.[0-9]+.[0-9]+', '[0-9]+.[0-9]+.[0-9]+-[A-Za-z0-9]+' ] +# runs every monday at 9 am + schedule: + - cron: "0 9 * * 1" + workflow_dispatch: null + +jobs: + code-samples: + name: Test code samples + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + - name: Generate harness + working-directory: ./cookbook/tests + run: ./generate.sh + - name: Test code samples + working-directory: ./cookbook/tests + run: cargo test \ No newline at end of file diff --git a/.gitignore b/.gitignore index 8d6779a..f1ca7e7 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,7 @@ site/.hugo_build.lock # mdbook build cookbook/book +cookbook/tests/Cargo.lock +cookbook/tests/target +cookbook/tests/src site/static/book diff --git a/README.md b/README.md index b7ea8cc..afb6bb3 100644 --- a/README.md +++ b/README.md @@ -7,3 +7,13 @@ Website for the rust-bitcoin ecosystem. The site is built using [hugo](https://g [nightfall](https://themes.gohugo.io/themes/hugo-theme-nightfall/) theme. Includes a cookbook built with [mdbook](https://rust-lang.github.io/mdBook/). + +## License + +This website is licensed under [CC0 1.0 Universal (CC0 1.0) Public Domain Dedication][cc]. + +[![CC BY-SA 4.0][cc-image]][cc] + +[cc]: https://creativecommons.org/publicdomain/zero/1.0/ +[cc-image]: https://licensebuttons.net/l/by-sa/4.0/88x31.png +[cc-shield]: https://img.shields.io/badge/License-CC0%201.0-lightgrey.svg diff --git a/build.sh b/build.sh deleted file mode 100755 index f2646b7..0000000 --- a/build.sh +++ /dev/null @@ -1,77 +0,0 @@ -#!/bin/bash -# -# Bulid/deployment script for www.rust-bitcoin.org - -set -e - -root=$(pwd) - -main() { - local _deploy=false - - if [ "$#" -eq 1 ]; then - if [ "$1" = "--deploy" ]; then - _deploy=true - fi - fi - - if [ $_deploy = true ]; then - deploy - else - build - echo "" - echo -e "\033[0;32m Site built, you can serve locally by cd'ing into site/ and running 'hugo serve'...\033[0m" - fi -} - -build() { - # Build the cookbook - cd $root - rm -rf "site/static/book" - cd cookbook - mdbook build --dest-dir "../site/static/book" - cd $root - - # Build the hugo project. - cd site - hugo - cd $root -} - -# Deploy the site to https://github.com/rust-bitcoin/rust-bitcoin.github.io -deploy() { - local _date=$(date --utc) - echo -e "\033[0;32m Deploying site to GitHub...\033[0m" - cd $root - - local branch=$(git rev-parse --abbrev-ref HEAD) - - if [ $branch != "master" ]; then - echo "Not on master branch, must be on master to deploy" - return 1 - fi - - build - - # Commit changes. - cd site/public - git add -A - - msg="Build site `date`" - if [ $# -eq 1 ] - then msg="$1" - fi - git commit -m "$msg" - - # Push html to GitHub pages (see public/.git/config) - git push - - cd $root - echo -e "\033[0;32m Deployment successful\033[0m" -} - -# -# Main -# -main $@ -exit 0 diff --git a/cookbook/README.md b/cookbook/README.md index f4173f1..94db190 100644 --- a/cookbook/README.md +++ b/cookbook/README.md @@ -1,9 +1,58 @@ -# Rust Bitcoin CookBook +# Rust Bitcoin Cookbook -Welcome to the Rust-Bitcoin documentation! This documentation is designed to provide readers with a -comprehensive understanding of Rust-Bitcoin and its key features. +[![CC0 1.0][cc-shield]][cc] +Welcome to the `rust-bitcoin` cookbook! +This cookbook is designed to provide readers with a +comprehensive understanding of `rust-bitcoin` and its key features. +Don't forget to check [`rust-bitcoin`'s documentation](https://docs.rs/bitcoin) -## Build +## How to contribute -The book can be built with `mdbook`: https://rust-lang.github.io/mdBook/ +For the cookbook we use [`mdbook`](https://rust-lang.github.io/mdBook). +Please check how to install it on your system. + +To build the cookbook locally, run: + +```bash +mdbook build +``` + +If you want to preview the cookbook locally, run: + +```bash +mdbook serve +``` + +### Testing the code snippets + +Since [`mdbook` does not support external crates](https://github.com/rust-lang/mdBook/issues/706), +we use [a solution provided by `doc-comment`](https://github.com/rust-lang/mdBook/issues/706#issuecomment-1139423009) +to test the code snippets in the cookbook. +First, go to the `tests/` directory: + +```bash +cd tests +``` + +Then, run the `generate.sh` to automatically generate all tests files: + +```bash +./generate.sh +``` + +Finally run the tests: + +```bash +cargo test +``` + +## License + +This website is licensed under [CC0 1.0 Universal (CC0 1.0) Public Domain Dedication][cc]. + +[![CC BY-SA 4.0][cc-image]][cc] + +[cc]: https://creativecommons.org/publicdomain/zero/1.0/ +[cc-image]: https://licensebuttons.net/l/by-sa/4.0/88x31.png +[cc-shield]: https://img.shields.io/badge/License-CC0%201.0-lightgrey.svg diff --git a/cookbook/book.toml b/cookbook/book.toml index aef5f5e..c4ffa35 100644 --- a/cookbook/book.toml +++ b/cookbook/book.toml @@ -1,6 +1,17 @@ [book] -authors = ["Harshil Jani"] +authors = [ + "Harshil Jani ", + "Tobin C. Harding", + "Einherjar ", +] language = "en" multilingual = false src = "src" title = "Rust Bitcoin Cookbook" +description = "A cookbook for Rust Bitcoin development" + +[rust] +edition = "2021" + +[output.html.playground] +runnable = false # playground is not runnable by default diff --git a/cookbook/src/SUMMARY.md b/cookbook/src/SUMMARY.md index d5c7b7c..3ec9e4e 100644 --- a/cookbook/src/SUMMARY.md +++ b/cookbook/src/SUMMARY.md @@ -1,5 +1,7 @@ # Summary -[Table of Contents](./table_of_contents.md) -[About](./about.md) -- [Chapter 1](./chapter_1.md) +[Introduction](./intro.md) + +- [Constructing and Signing Transactions](tx.md) + - [SegWit V0](tx_segwit-v0.md) + - [Taproot](tx_taproot.md) diff --git a/cookbook/src/about.md b/cookbook/src/about.md deleted file mode 100644 index 240faca..0000000 --- a/cookbook/src/about.md +++ /dev/null @@ -1,8 +0,0 @@ -# About - -The [rust-bitcoin](https://github.com/rust-bitcoin/rust-bitcoin) cookbook provides straightforward -examples that showcase effective approaches for accomplishing typical Bitcoin-related programming -tasks, utilizing the Rust ecosystem's crates. - -The book covers various topics, including receiving data over P2P, parsing blocks and transactions, -and constructing and signing transactions. diff --git a/cookbook/src/chapter_1.md b/cookbook/src/chapter_1.md deleted file mode 100644 index b743fda..0000000 --- a/cookbook/src/chapter_1.md +++ /dev/null @@ -1 +0,0 @@ -# Chapter 1 diff --git a/cookbook/src/intro.md b/cookbook/src/intro.md new file mode 100644 index 0000000..300bb8c --- /dev/null +++ b/cookbook/src/intro.md @@ -0,0 +1,46 @@ +# Rust Bitcoin + +[![CC0 1.0][cc-shield]][cc] + +[`rust-bitcoin`](https://github.com/rust-bitcoin/rust-bitcoin) is a library for working with Bitcoin in Rust. +It contains Bitcoin network protocol and associated primitives. +You can find more by reading the [documentation](https://docs.rs/bitcoin). + +To add `rust-bitcoin` to your project, run: + +```bash +cargo add bitcoin +``` + +Additionally, you can add flags to enable features. +Here's an example: + +```bash +cargo add bitcoin --features=rand-std +``` + +This cookbook provides straightforward examples that showcase effective approaches +for accomplishing typical Bitcoin-related programming tasks, +and utilizing the Rust ecosystem's crates. + +The book covers various topics, including receiving data over P2P, +parsing blocks and transactions, +and constructing and signing transactions. + +## Table of Contents + +This book contains: + +1. [Constructing and Signing Transactions](tx.md) + 1. [SegWit V0](tx_segwit-v0.md) + 1. [Taproot](tx_taproot.md) + +## License + +This website is licensed under [CC0 1.0 Universal (CC0 1.0) Public Domain Dedication][cc]. + +[![CC BY-SA 4.0][cc-image]][cc] + +[cc]: https://creativecommons.org/publicdomain/zero/1.0/ +[cc-image]: https://licensebuttons.net/l/by-sa/4.0/88x31.png +[cc-shield]: https://img.shields.io/badge/License-CC0%201.0-lightgrey.svg diff --git a/cookbook/src/table_of_contents.md b/cookbook/src/table_of_contents.md deleted file mode 100644 index f886a96..0000000 --- a/cookbook/src/table_of_contents.md +++ /dev/null @@ -1 +0,0 @@ -# Table of Contents diff --git a/cookbook/src/tx.md b/cookbook/src/tx.md new file mode 100644 index 0000000..bbb60f4 --- /dev/null +++ b/cookbook/src/tx.md @@ -0,0 +1,6 @@ +# Constructing and Signing Transactions + +We provide the following examples: + +- [Constructing and Signing Transactions - SegWit V0](tx_segwit-v0.md) +- [Constructing and Signing Transactions - Taproot](tx_taproot.md) diff --git a/cookbook/src/tx_segwit-v0.md b/cookbook/src/tx_segwit-v0.md new file mode 100644 index 0000000..5497f0b --- /dev/null +++ b/cookbook/src/tx_segwit-v0.md @@ -0,0 +1,353 @@ +# Constructing and Signing Transactions - SegWit V0 + +In this section, we will construct a [SegWit V0 transaction](https://bitcoinops.org/en/topics/segregated-witness/). +This is the most common type of transaction on the Bitcoin network today[^today]. + +This is the `cargo` commands that you need to run this example: + +```bash +cargo add bitcoin --features "std, rand-std" +``` + +First we'll need to import the following: + +```rust +use std::str::FromStr; + +use bitcoin::hashes::Hash; +use bitcoin::locktime::absolute; +use bitcoin::secp256k1::{rand, Message, Secp256k1, SecretKey, Signing}; +use bitcoin::sighash::{EcdsaSighashType, SighashCache}; +use bitcoin::{ + Address, Network, OutPoint, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Txid, WPubkeyHash, + Witness, +}; +``` + +Here is the logic behind these imports: + +- `std::str::FromStr` is used to parse strings into Bitcoin primitives +- `bitcoin::hashes::Hash` is used to hash data +- `bitcoin::locktime::absolute` is used to create a locktime +- `bitcoin::secp256k1::{rand, Message, Secp256k1, SecretKey, Signing}` is used to sign transactions +- `bitcoin::sighash::{EcdsaSighashType, SighashCache}` is used to create sighashes +- `bitcoin::{Address, Network, OutPoint, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Txid, WPubkeyHash, Witness}` is used to construct transactions + +Next, we define the following constants: + +```rust +const DUMMY_UTXO_AMOUNT: u64 = 20_000_000; +const SPEND_AMOUNT: u64 = 5_000_000; +const CHANGE_AMOUNT: u64 = 14_999_000; // 1000 sat fee. +``` + +- `DUMMY_UTXO_AMOUNT` is the amount of the dummy UTXO we will be spending +- `SPEND_AMOUNT` is the amount we will be spending from the dummy UTXO +- `CHANGE_AMOUNT`[^change] is the amount we will be sending back to ourselves as change + +Before we can construct the transaction, we need to define some helper functions[^expect]: + +```rust +# use bitcoin::secp256k1::{rand, Secp256k1, SecretKey, Signing}; +# use bitcoin::WPubkeyHash; +fn senders_keys(secp: &Secp256k1) -> (SecretKey, WPubkeyHash) { + let sk = SecretKey::new(&mut rand::thread_rng()); + let pk = bitcoin::PublicKey::new(sk.public_key(secp)); + let wpkh = pk.wpubkey_hash().expect("key is compressed"); + + (sk, wpkh) +} +``` + +`senders_keys` generates a random private key and derives the corresponding public key hash. +This will be useful to mock a sender. +In a real application these would be actual secrets[^secp]. +We use the `SecretKey::new` method to generate a random private key `sk`. +We then use the `PublicKey::new` method to derive the corresponding public key `pk`. +Finally, we use the `PublicKey::wpubkey_hash` method to derive the corresponding public key hash `wpkh`. +Note that `senders_keys` is generic over the [`Signing`](https://docs.rs/secp256k1/latest/secp256k1/trait.Signing.html) trait. +This is used to indicate that is an instance of `Secp256k1` and can be used for signing. +We conclude returning the private key `sk` and the public key hash `wpkh` as a tuple. + +```rust +# use std::str::FromStr; +# use bitcoin::{Address, Network}; +fn receivers_address() -> Address { + Address::from_str("bc1q7cyrfmck2ffu2ud3rn5l5a8yv6f0chkp0zpemf") + .expect("a valid address") + .require_network(Network::Bitcoin) + .expect("valid address for mainnet") +} +``` + +`receivers_address` generates a receiver address. +In a real application this would be the address of the receiver. +We use the method `Address::from_str` to parse the string `"bc1q7cyrfmck2ffu2ud3rn5l5a8yv6f0chkp0zpemf"` into an address. +Hence, it is necessary to import the `std::str::FromStr` trait. +Note that `bc1q7cyrfmck2ffu2ud3rn5l5a8yv6f0chkp0zpemf` is a [Bech32](https://bitcoinops.org/en/topics/bech32/) address. +This is an arbitrary, however valid, Bitcoin mainnet address. +Hence we use the `require_network` method to ensure that the address is valid for mainnet. + +```rust +# use bitcoin::{OutPoint, ScriptBuf, TxOut, Txid, WPubkeyHash}; +# use bitcoin::hashes::Hash; +# const DUMMY_UTXO_AMOUNT: u64 = 20_000_000; +fn dummy_unspent_transaction_output(wpkh: &WPubkeyHash) -> (OutPoint, TxOut) { + let script_pubkey = ScriptBuf::new_v0_p2wpkh(wpkh); + + let out_point = OutPoint { + txid: Txid::all_zeros(), // Obviously invalid. + vout: 0, + }; + + let utxo = TxOut { + value: DUMMY_UTXO_AMOUNT, + script_pubkey, + }; + + (out_point, utxo) +} +``` + +`dummy_unspent_transaction_output` generates a dummy unspent transaction output (UTXO). +This is a SegWit V0 P2WPKH (`ScriptBuf::new_v0_p2wpkh`) UTXO with a dummy invalid transaction ID (`txid: Txid::all_zeros()`), +and a value of the `const DUMMY_UTXO_AMOUNT` that we defined earlier. +We are using the [`OutPoint`](https://docs.rs/bitcoin/latest/bitcoin/blockdata/transaction/struct.OutPoint.html) struct to represent the transaction output. +Finally, we return the tuple `(out_point, utxo)`. + +Now we are ready for our main function that will sign a transaction that spends a `p2wpkh` unspent output: + +```rust +# use std::str::FromStr; +# +# use bitcoin::hashes::Hash; +# use bitcoin::locktime::absolute; +# use bitcoin::secp256k1::{rand, Message, Secp256k1, SecretKey, Signing}; +# use bitcoin::sighash::{EcdsaSighashType, SighashCache}; +# use bitcoin::{ +# Address, Network, OutPoint, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Txid, WPubkeyHash, +# Witness, +# }; +# +# const DUMMY_UTXO_AMOUNT: u64 = 20_000_000; +# const SPEND_AMOUNT: u64 = 5_000_000; +# const CHANGE_AMOUNT: u64 = 14_999_000; // 1000 sat fee. +# +# fn senders_keys(secp: &Secp256k1) -> (SecretKey, WPubkeyHash) { +# let sk = SecretKey::new(&mut rand::thread_rng()); +# let pk = bitcoin::PublicKey::new(sk.public_key(secp)); +# let wpkh = pk.wpubkey_hash().expect("key is compressed"); +# +# (sk, wpkh) +# } +# +# +# fn receivers_address() -> Address { +# Address::from_str("bc1q7cyrfmck2ffu2ud3rn5l5a8yv6f0chkp0zpemf") +# .expect("a valid address") +# .require_network(Network::Bitcoin) +# .expect("valid address for mainnet") +# } +# +# fn dummy_unspent_transaction_output(wpkh: &WPubkeyHash) -> (OutPoint, TxOut) { +# let script_pubkey = ScriptBuf::new_v0_p2wpkh(wpkh); +# +# let out_point = OutPoint { +# txid: Txid::all_zeros(), // Obviously invalid. +# vout: 0, +# }; +# +# let utxo = TxOut { +# value: DUMMY_UTXO_AMOUNT, +# script_pubkey, +# }; +# +# (out_point, utxo) +# } + +fn main() { + let secp = Secp256k1::new(); + let (sk, wpkh) = senders_keys(&secp); + let address = receivers_address(); + let (dummy_out_point, dummy_utxo) = dummy_unspent_transaction_output(&wpkh); + + // The script code required to spend a p2wpkh output. + let script_code = dummy_utxo + .script_pubkey + .p2wpkh_script_code() + .expect("valid script"); + + // The input for the transaction we are constructing. + let input = TxIn { + previous_output: dummy_out_point, + script_sig: ScriptBuf::new(), + sequence: Sequence::ENABLE_RBF_NO_LOCKTIME, + witness: Witness::default(), + }; + + // The spend output is locked to a key controlled by the receiver. + let spend = TxOut { + value: SPEND_AMOUNT, + script_pubkey: address.script_pubkey(), + }; + + // The change output is locked to a key controlled by us. + let change = TxOut { + value: CHANGE_AMOUNT, + script_pubkey: ScriptBuf::new_v0_p2wpkh(&wpkh), + }; + + // The transaction we want to sign and broadcast. + let unsigned_tx = Transaction { + version: 2, + lock_time: absolute::LockTime::ZERO, + input: vec![input], + output: vec![spend, change], + }; + + // Sign the unsigned transaction. + let mut sighash_cache = SighashCache::new(unsigned_tx); + let sighash = sighash_cache + .segwit_signature_hash(0, &script_code, DUMMY_UTXO_AMOUNT, EcdsaSighashType::All) + .expect("valid sighash"); + let msg = Message::from(sighash); + let sig = secp.sign_ecdsa(&msg, &sk); + + // Convert into a transaction + let mut tx = sighash_cache.into_transaction(); + + // Update the witness stack + let pk = sk.public_key(&secp); + let mut witness = &mut tx.input[0].witness; + witness.push_bitcoin_signature( + &sig.serialize_der(), + EcdsaSighashType::All + ); + witness.push(&pk.serialize()); + + // Print the transaction ready to broadcast + println!("tx: {tx:?}"); +} +``` + +Let's go over the main function code block by block. + +`let secp = Secp256k1::new();` creates a new `Secp256k1` context with all capabilities. +Since we added the `rand-std` feature to our `Cargo.toml`, +we can use the [`SecretKey::new`](https://docs.rs/secp256k1/latest/secp256k1/struct.Secp256k1.html#method.new) method to generate a random private key `sk`. + +`let (sk, wpkh) = senders_keys(&secp);` generates a random private key `sk` and derives the corresponding public key hash `wpkh`. +`let address = receivers_address();` generates a receiver's address `address`. +`let (dummy_out_point, dummy_utxo) = dummy_unspent_transaction_output(&wpkh);` generates a dummy unspent transaction output `dummy_utxo` and its corresponding outpoint `dummy_out_point`. +All of these are helper functions that we defined earlier. + +`let script_code = dummy_utxo.script_pubkey.p2wpkh_script_code().expect("valid script");` +creates the script code required to spend a P2WPKH output. +Since `dummy_utxo` is a [`TxOut`](https://docs.rs/bitcoin/latest/bitcoin/blockdata/transaction/struct.TxOut.html) type, +we can access the underlying public field `script_pubkey` which, in turn is a [`Script`](https://docs.rs/bitcoin/latest/bitcoin/blockdata/script/struct.Script.html) type. +We then use the [`p2wpkh_script_code`](https://docs.rs/bitcoin/latest/bitcoin/blockdata/script/struct.ScriptBuf.html#method.p2wpkh_script_code) method to generate the script code. + +In `let input = TxIn {...}` we are instantiating the input for the transaction we are constructing +Inside the [`TxIn`](https://docs.rs/bitcoin/latest/bitcoin/blockdata/transaction/struct.TxIn.html) struct we are setting the following fields: + +- `previous_output` is the outpoint of the dummy UTXO we are spending; it is a [`OutPoint`](https://docs.rs/bitcoin/latest/bitcoin/blockdata/transaction/struct.OutPoint.html) type. +- `script_sig` is the script code required to spend a P2WPKH output; it is a [`ScriptBuf`](https://docs.rs/bitcoin/latest/bitcoin/blockdata/script/struct.ScriptBuf.html) type. + It should be empty. That's why the `ScriptBuf::new()`. +- `sequence` is the sequence number; it is a [`Sequence`](https://docs.rs/bitcoin/latest/bitcoin/blockdata/transaction/struct.Sequence.html) type. + We are using the [`ENABLE_RBF_NO_LOCKTIME`](https://docs.rs/bitcoin/latest/bitcoin/blockdata/transaction/struct.Sequence.html#associatedconstant.ENABLE_RBF_NO_LOCKTIME) constant. +- `witness` is the witness stack; it is a [`Witness`](https://docs.rs/bitcoin/latest/bitcoin/blockdata/transaction/struct.Witness.html) type. + We are using the [`default`](https://docs.rs/bitcoin/latest/bitcoin/blockdata/transaction/struct.Witness.html#impl-Default) method to create an empty witness that will be filled in later after signing. + This is possible because `Witness` implements the [`Default`](https://doc.rust-lang.org/std/default/trait.Default.html) trait. + +In `let spend = TxOut {...}` we are instantiating the spend output. +Inside the [`TxOut`](https://docs.rs/bitcoin/latest/bitcoin/blockdata/transaction/struct.TxOut.html) struct we are setting the following fields: + +- `value` is the amount we are spending; it is a [`u64`](https://doc.rust-lang.org/std/primitive.u64.html) type. + We are using the `const SPEND_AMOUNT` that we defined earlier. +- `script_pubkey` is the script code required to spend a P2WPKH output; it is a [`ScriptBuf`](https://docs.rs/bitcoin/latest/bitcoin/blockdata/script/struct.ScriptBuf.html) type. + We are using the [`script_pubkey`](https://docs.rs/bitcoin/latest/bitcoin/blockdata/address/struct.Address.html#method.script_pubkey) method to generate the script pubkey from the receivers address. + This will lock the output to the receiver's address. + +In `let change = TxOut {...}` we are instantiating the change output. +It is very similar to the `spend` output, but we are now using the `const CHANGE_AMOUNT` that we defined earlier[^spend]. +This is done by setting the `script_pubkey` field to [`ScriptBuf::new_v0_p2wpkh(&wpkh)`](https://docs.rs/bitcoin/latest/bitcoin/blockdata/script/struct.ScriptBuf.html#method.new_v0_p2wpkh), +which generates P2WPKH-type of script pubkey. + +In `let unsigned_tx = Transaction {...}` we are instantiating the transaction we want to sign and broadcast using the [`Transaction`](https://docs.rs/bitcoin/latest/bitcoin/blockdata/transaction/struct.Transaction.html) struct. +We set the following fields: + +- `version` is the transaction version; it is a [`i32`](https://doc.rust-lang.org/std/primitive.u32.html) type. + We are using version `2` which means that [BIP68](https://github.com/bitcoin/bips/blob/master/bip-0068.mediawiki) applies. +- `lock_time` is the transaction lock time; + it is a [`LockTime`](https://docs.rs/bitcoin/latest/bitcoin/blockdata/locktime/absolute/enum.LockTime.html) enum. + We are using the constant [`ZERO`](https://docs.rs/bitcoin/latest/bitcoin/blockdata/locktime/absolute/enum.LockTime.html#associatedconstant.ZERO) + This will make the transaction valid immediately. +- `input` is the input vector; it is a [`Vec`](https://doc.rust-lang.org/std/vec/struct.Vec.html) type. + We are using the `input` variable that we defined earlier wrapped in the [`vec!`](https://doc.rust-lang.org/std/macro.vec.html) macro for convenient initialization. +- `output` is the output vector; it is a [`Vec`](https://doc.rust-lang.org/std/vec/struct.Vec.html) type. + We are using the `spend` and `change` variables that we defined earlier wrapped in the [`vec!`](https://doc.rust-lang.org/std/macro.vec.html) macro for convenient initialization. + +In `let mut sighash_cache = SighashCache::new(unsigned_tx);` we are instantiating a [`SighashCache`](https://docs.rs/bitcoin/latest/bitcoin/sighash/struct.SighashCache.html) struct. +This is a type that efficiently calculates [signature hash message](https://developer.bitcoin.org/devguide/transactions.html?highlight=sighash_all#signature-hash-types) for legacy, segwit and taproot inputs. +We are using the `new` method to instantiate the struct with the `unsigned_tx` that we defined earlier. +`new` takes any `Borrow` as an argument. +[`Borrow`](https://doc.rust-lang.org/std/borrow/trait.Borrow.html) is a trait that allows us to pass either a reference to a `T` or a `T` itself. +Hence, you can pass a `Transaction` or a `&Transaction` to `new`. + +`sighash_cache` is instantiated as mutable because we require a mutable reference when creating the sighash to sign using [`segwit_signature_hash`](https://docs.rs/bitcoin/latest/bitcoin/sighash/struct.SighashCache.html#method.segwit_signature_hash). +This computes the [BIP143](https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki) sighash for any flag type. +It takes the following arguments: + +- `input_index` is the index of the input we are signing; it is a [`usize`](https://doc.rust-lang.org/std/primitive.usize.html) type. + We are using `0` since we only have one input. +- `script_code` is the script code required to spend a P2WPKH output; it is a reference to [`Script`](https://docs.rs/bitcoin/latest/bitcoin/blockdata/script/struct.Script.html) type. + We are using the `script_code` variable that we defined earlier. +- `value` is the amount of the UTXO we are spending; it is a [`u64`](https://doc.rust-lang.org/std/primitive.u64.html) type. + We are using the `const DUMMY_UTXO_AMOUNT` that we defined earlier. +- `sighash_type` is the type of sighash; it is a [`EcdsaSighashType`](https://docs.rs/bitcoin/latest/bitcoin/sighash/enum.EcdsaSighashType.html) enum. + We are using the [`All`](https://docs.rs/bitcoin/latest/bitcoin/sighash/enum.EcdsaSighashType.html#variant.All) variant, + which indicates that the sighash will include all the inputs and outputs. + +We create the message `msg` by converting the `sighash` to a [`Message`](https://docs.rs/secp256k1/latest/secp256k1/struct.Message.html) type. +This is the message that we will sign. +The [Message::from](https://docs.rs/secp256k1/latest/secp256k1/struct.Message.html#impl-From%3C%26%27_%20bitcoin%3A%3Ahashes%3A%3Asha256d%3A%3AHash%3E) method takes anything that implements the promises to be a thirty two byte hash i.e., 32 bytes that came from a cryptographically secure hashing algorithm. + +We compute the signature `sig` by using the [`sign_ecdsa`](https://docs.rs/secp256k1/latest/secp256k1/struct.Secp256k1.html#method.sign_ecdsa) method. +It takes a refence to a [`Message`](https://docs.rs/secp256k1/latest/secp256k1/struct.Message.html) and a reference to a [`SecretKey`](https://docs.rs/secp256k1/latest/secp256k1/struct.SecretKey.html) as arguments, +and returns a [`Signature`](https://docs.rs/secp256k1/latest/secp256k1/ecdsa/struct.Signature.html) type. + +In the next step, we update the witness stack for the input we just signed by first converting the `sighash_cache` into a [`Transaction`](https://docs.rs/bitcoin/latest/bitcoin/blockdata/transaction/struct.Transaction.html) +by using the [`into_transaction`](https://docs.rs/bitcoin/latest/bitcoin/sighash/struct.SighashCache.html#method.into_transaction) method. +We access the witness field of the first input with `tx.input[0].witness`. +It is a [`Witness`](https://docs.rs/bitcoin/latest/bitcoin/blockdata/witness/struct.Witness.html) type. +We use the [`push_bitcoin_signature`](https://docs.rs/bitcoin/latest/bitcoin/blockdata/witness/struct.Witness.html#method.push_bitcoin_signature) method. +It expects two arguments: + +1. A reference to a [`SerializedSignature`](https://docs.rs/secp256k1/0.27.0/secp256k1/ecdsa/serialized_signature/struct.SerializedSignature.html) type. + This is accomplished by calling the [`serialize_der`](https://docs.rs/secp256k1/latest/secp256k1/ecdsa/struct.Signature.html#method.serialize_der) method on the `Signature` `sig`, + which returns a `SerializedSignature` type. +1. A [`EcdsaSighashType`](https://docs.rs/bitcoin/latest/bitcoin/sighash/enum.EcdsaSighashType.html) enum. + Again we are using the same [`All`](https://docs.rs/bitcoin/latest/bitcoin/sighash/enum.EcdsaSighashType.html#variant.All) variant that we used earlier. + +We repeat the same step as above, but now using the [`push`](https://docs.rs/bitcoin/latest/bitcoin/blockdata/witness/struct.Witness.html#method.push) method +to push the serialized public key to the witness stack. +It expects a single argument of type `AsRef<[u8]>` which is a reference to a byte slice. + +As the last step we print this to terminal using the [`println!`](https://doc.rust-lang.org/std/macro.println.html) macro. +This transaction is now ready to be broadcast to the Bitcoin network. + +[^today]: mid-2023. + +[^change]: Please note that the `CHANGE_AMOUNT` is not the same as the `DUMMY_UTXO_AMOUNT` minus the `SPEND_AMOUNT`. + This is due to the fact that we need to pay a fee for the transaction. + +[^expect]: We will be unwraping any [`Option`](https://doc.rust-lang.org/std/option)/[`Result`](https://doc.rust-lang.org/std/result) + with the `expect` method. + +[^secp]: Under the hood we are using the [`secp256k1`](https://github.com/rust-bitcoin/rust-secp256k1/) crate to generate the key pair. + `rust-secp256k1` is a wrapper around [libsecp256k1](https://github.com/bitcoin-core/secp256k1), a C + library implementing various cryptographic functions using the [SECG](https://www.secg.org/) curve + [secp256k1](https://en.bitcoin.it/wiki/Secp256k1). + +[^spend]: And also we are locking the output to an address that we control: + the `wpkh` public key hash that we generated earlier. \ No newline at end of file diff --git a/cookbook/src/tx_taproot.md b/cookbook/src/tx_taproot.md new file mode 100644 index 0000000..7bbb7a3 --- /dev/null +++ b/cookbook/src/tx_taproot.md @@ -0,0 +1,365 @@ +# Constructing and Signing Transactions - Taproot + +In this section, we will construct a [Taproot transaction](https://bitcoinops.org/en/topics/taproot/). + +This is the `cargo` commands that you need to run this example: + +```bash +cargo add bitcoin --features "std, rand-std" +``` + +First we'll need to import the following: + +```rust +use std::str::FromStr; + +use bitcoin::hashes::Hash; +use bitcoin::key::{KeyPair, TapTweak, TweakedKeyPair, UntweakedPublicKey}; +use bitcoin::locktime::absolute; +use bitcoin::secp256k1::{rand, Message, Secp256k1, SecretKey, Signing, Verification}; +use bitcoin::sighash::{Prevouts, SighashCache, TapSighashType}; +use bitcoin::{ + Address, Network, OutPoint, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Txid, Witness, +}; +``` + +Here is the logic behind these imports: + +- `std::str::FromStr` is used to parse strings into Bitcoin primitives +- `bitcoin::key` is used to tweak keys according to [BIP340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki) +- `bitcoin::hashes::Hash` is used to hash data +- `bitcoin::locktime::absolute` is used to create a locktime +- `bitcoin::secp256k1::{rand, Message, Secp256k1, SecretKey, Signing, Verification}` is used to sign transactions +- `use bitcoin::sighash::{Prevouts, SighashCache, TapSighashType}` is used to create and tweak taproot sighashes +- `bitcoin::{Address, Network, OutPoint, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Txid, Witness}` is used to construct transactions + +Next, we define the following constants: + +```rust +const DUMMY_UTXO_AMOUNT: u64 = 20_000_000; +const SPEND_AMOUNT: u64 = 5_000_000; +const CHANGE_AMOUNT: u64 = 14_999_000; // 1000 sat fee. +``` + +- `DUMMY_UTXO_AMOUNT` is the amount of the dummy UTXO we will be spending +- `SPEND_AMOUNT` is the amount we will be spending from the dummy UTXO +- `CHANGE_AMOUNT`[^change] is the amount we will be sending back to ourselves as change + +Before we can construct the transaction, we need to define some helper functions[^expect]: + +```rust +# use bitcoin::secp256k1::{rand, Secp256k1, SecretKey, Signing}; +# use bitcoin::key::KeyPair; +fn senders_keys(secp: &Secp256k1) -> KeyPair { + let sk = SecretKey::new(&mut rand::thread_rng()); + KeyPair::from_secret_key(secp, &sk) +} +``` + +`senders_keys` generates a random private key and derives the corresponding public key hash. +This will be useful to mock a sender. +In a real application these would be actual secrets[^secp]. +We use the `SecretKey::new` method to generate a random private key `sk`. +We then use the [`KeyPair::from_secret_key`](https://docs.rs/bitcoin/latest/bitcoin/key/struct.KeyPair.html#method.from_secret_key) method to instatiate a [`KeyPair`](https://docs.rs/bitcoin/latest/bitcoin/key/struct.KeyPair.html) type, +which is a data structure that holds a keypair consisting of a secret and a public key. +Note that `senders_keys` is generic over the [`Signing`](https://docs.rs/secp256k1/latest/secp256k1/trait.Signing.html) trait. +This is used to indicate that is an instance of `Secp256k1` and can be used for signing. + +```rust +# use std::str::FromStr; +# use bitcoin::{Address, Network}; +fn receivers_address() -> Address { + Address::from_str("bc1p0dq0tzg2r780hldthn5mrznmpxsxc0jux5f20fwj0z3wqxxk6fpqm7q0va") + .expect("a valid address") + .require_network(Network::Bitcoin) + .expect("valid address for mainnet") +} +``` + +`receivers_address` generates a receiver address. +In a real application this would be the address of the receiver. +We use the method `Address::from_str` to parse the string `"bc1p0dq0tzg2r780hldthn5mrznmpxsxc0jux5f20fwj0z3wqxxk6fpqm7q0va"`[^arbitrary_address] into an address. +Hence, it is necessary to import the `std::str::FromStr` trait. +Note that `bc1p0dq0tzg2r780hldthn5mrznmpxsxc0jux5f20fwj0z3wqxxk6fpqm7q0va` is a [Bech32](https://bitcoinops.org/en/topics/bech32/) address. +This is an arbitrary, however valid, Bitcoin mainnet address. +Hence we use the `require_network` method to ensure that the address is valid for mainnet. + +```rust +# use bitcoin::{OutPoint, ScriptBuf, TxOut, Txid}; +# use bitcoin::hashes::Hash; +# use bitcoin::key::UntweakedPublicKey; +# use bitcoin::locktime::absolute; +# use bitcoin::secp256k1::{Secp256k1, Verification}; +# const DUMMY_UTXO_AMOUNT: u64 = 20_000_000; +fn dummy_unspent_transaction_output( + secp: &Secp256k1, + internal_key: UntweakedPublicKey, +) -> (OutPoint, TxOut) { + let script_pubkey = ScriptBuf::new_v1_p2tr(secp, internal_key, None); + + let out_point = OutPoint { + txid: Txid::all_zeros(), // Obviously invalid. + vout: 0, + }; + + let utxo = TxOut { + value: DUMMY_UTXO_AMOUNT, + script_pubkey, + }; + + (out_point, utxo) +} +``` + +`dummy_unspent_transaction_output` generates a dummy unspent transaction output (UTXO). +This is a P2TR (`ScriptBuf::new_v1_p2tr`) UTXO. +It takes the following arguments: + +- `secp` is a reference to a [`Secp256k1`](https://docs.rs/secp256k1/latest/secp256k1/struct.Secp256k1.html) type. + This is used to verify the internal key. +- `internal_key` is a [`UntweakedPublicKey`](https://docs.rs/bitcoin/latest/bitcoin/key/type.UntweakedPublicKey.html) type. + This is the internal key that is used to generate the script pubkey. + It is untweaked, since we are not going to tweak the key. +- `merkle_root` is an optional [`TapNodeHash`](https://docs.rs/bitcoin/latest/bitcoin/taproot/struct.TapNodeHash.html) type. + This is the merkle root of the taproot tree. + Since we are not using a merkle tree, we are passing `None`. + +[`Verification`](https://docs.rs/bitcoin/latest/bitcoin/key/trait.Verification.html) is a trait that indicates that an instance of `Secp256k1` can be used for verification. +The UTXO has a dummy invalid transaction ID (`txid: Txid::all_zeros()`), +and a value of the `const DUMMY_UTXO_AMOUNT` that we defined earlier. +P2TR UTXOs could be tweaked ([`TweakedPublicKey`](https://docs.rs/bitcoin/latest/bitcoin/key/struct.TweakedPublicKey.html)) +or untweaked ([`UntweakedPublicKey`](https://docs.rs/bitcoin/latest/bitcoin/key/type.UntweakedPublicKey.html)). +We are using the latter, since we are not going to tweak the key. +We are using the [`OutPoint`](https://docs.rs/bitcoin/latest/bitcoin/blockdata/transaction/struct.OutPoint.html) struct to represent the transaction output. +Finally, we return the tuple `(out_point, utxo)`. + +Now we are ready for our main function that will sign a transaction that spends a `p2wpkh` unspent output: + +```rust +# use std::str::FromStr; +# +# use bitcoin::hashes::Hash; +# use bitcoin::key::{KeyPair, TapTweak, TweakedKeyPair, UntweakedPublicKey}; +# use bitcoin::locktime::absolute; +# use bitcoin::secp256k1::{rand, Message, Secp256k1, SecretKey, Signing, Verification}; +# use bitcoin::sighash::{Prevouts, SighashCache, TapSighashType}; +# use bitcoin::{ +# Address, Network, OutPoint, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Txid, Witness, +# }; +# +# const DUMMY_UTXO_AMOUNT: u64 = 20_000_000; +# const SPEND_AMOUNT: u64 = 5_000_000; +# const CHANGE_AMOUNT: u64 = 14_999_000; // 1000 sat fee. +# +# fn senders_keys(secp: &Secp256k1) -> KeyPair { +# let sk = SecretKey::new(&mut rand::thread_rng()); +# KeyPair::from_secret_key(secp, &sk) +# } +# +# fn receivers_address() -> Address { +# Address::from_str("bc1p0dq0tzg2r780hldthn5mrznmpxsxc0jux5f20fwj0z3wqxxk6fpqm7q0va") +# .expect("a valid address") +# .require_network(Network::Bitcoin) +# .expect("valid address for mainnet") +# } +# +# fn dummy_unspent_transaction_output( +# secp: &Secp256k1, +# internal_key: UntweakedPublicKey, +# ) -> (OutPoint, TxOut) { +# let script_pubkey = ScriptBuf::new_v1_p2tr(secp, internal_key, None); +# +# let out_point = OutPoint { +# txid: Txid::all_zeros(), // Obviously invalid. +# vout: 0, +# }; +# +# let utxo = TxOut { +# value: DUMMY_UTXO_AMOUNT, +# script_pubkey, +# }; +# +# (out_point, utxo) +# } + +fn main() { + let secp = Secp256k1::new(); + let keypair = senders_keys(&secp); + let (internal_key, _parity) = keypair.x_only_public_key(); + let address = receivers_address(); + let (dummy_out_point, dummy_utxo) = dummy_unspent_transaction_output(&secp, internal_key); + + // The input for the transaction we are constructing. + let input = TxIn { + previous_output: dummy_out_point, + script_sig: ScriptBuf::new(), + sequence: Sequence::ENABLE_RBF_NO_LOCKTIME, + witness: Witness::default(), + }; + + // The spend output is locked to a key controlled by the receiver. + let spend = TxOut { + value: SPEND_AMOUNT, + script_pubkey: address.script_pubkey(), + }; + + // The change output is locked to a key controlled by us. + let change = TxOut { + value: CHANGE_AMOUNT, + script_pubkey: ScriptBuf::new_v1_p2tr(&secp, internal_key, None), + }; + + // The transaction we want to sign and broadcast. + let unsigned_tx = Transaction { + version: 2, + lock_time: absolute::LockTime::ZERO, + input: vec![input], + output: vec![spend, change], + }; + + // Create the prevouts + let prevouts = vec![dummy_utxo]; + let prevouts = Prevouts::All(&prevouts); + + // Sign the unsigned transaction. + let mut sighash_cache = SighashCache::new(unsigned_tx); + let sighash = sighash_cache + .taproot_signature_hash(0, &prevouts, None, None, TapSighashType::Default) + .expect("valid sighash"); + let tweaked: TweakedKeyPair = keypair.tap_tweak(&secp, None); + let msg = Message::from(sighash); + let sig = secp.sign_schnorr(&msg, &tweaked.to_inner()); + + // Convert into a transaction + let mut tx = sighash_cache.into_transaction(); + + // Update the witness stack + let mut witness = &mut tx.input[0].witness; + witness.push(sig.as_ref()); + + // Print the transaction ready to broadcast + println!("tx: {tx:?}"); +} +``` + +Let's go over the main function code block by block. + +`let secp = Secp256k1::new();` creates a new `Secp256k1` context with all capabilities. +Since we added the `rand-std` feature to our `Cargo.toml`, +we can use the [`SecretKey::new`](https://docs.rs/secp256k1/latest/secp256k1/struct.Secp256k1.html#method.new) method to generate a random private key `sk`. + +`let keypair = senders_keys(&secp);` generates a keypair that we control, +and `let (internal_key, _parity) = keypair.x_only_public_key();` generates a [`XOnlyPublicKey`](https://docs.rs/bitcoin/latest/bitcoin/key/struct.XOnlyPublicKey.html) that represent an X-only public key, used for verification of Schnorr signatures according to [BIP340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki). +We won't be using second element from the returned tuple, the parity, so we are ignoring it by using the `_` underscore. +`let address = receivers_address();` generates a receiver's address `address`. +`let (dummy_out_point, dummy_utxo) = dummy_unspent_transaction_output(&wpkh);` generates a dummy unspent transaction output `dummy_utxo` and its corresponding outpoint `dummy_out_point`. +All of these are helper functions that we defined earlier. + +In `let input = TxIn {...}` we are instantiating the input for the transaction we are constructing +Inside the [`TxIn`](https://docs.rs/bitcoin/latest/bitcoin/blockdata/transaction/struct.TxIn.html) struct we are setting the following fields: + +- `previous_output` is the outpoint of the dummy UTXO we are spending; it is a [`OutPoint`](https://docs.rs/bitcoin/latest/bitcoin/blockdata/transaction/struct.OutPoint.html) type. +- `script_sig` is the script code required to spend an output; it is a [`ScriptBuf`](https://docs.rs/bitcoin/latest/bitcoin/blockdata/script/struct.ScriptBuf.html) type. + We are instantiating a new empty script with [`ScriptBuf::new()`](https://docs.rs/bitcoin/latest/bitcoin/blockdata/script/struct.ScriptBuf.html#method.new). +- `sequence` is the sequence number; it is a [`Sequence`](https://docs.rs/bitcoin/latest/bitcoin/blockdata/transaction/struct.Sequence.html) type. + We are using the [`ENABLE_RBF_NO_LOCKTIME`](https://docs.rs/bitcoin/latest/bitcoin/blockdata/transaction/struct.Sequence.html#associatedconstant.ENABLE_RBF_NO_LOCKTIME) constant. +- `witness` is the witness stack; it is a [`Witness`](https://docs.rs/bitcoin/latest/bitcoin/blockdata/transaction/struct.Witness.html) type. + We are using the [`default`](https://docs.rs/bitcoin/latest/bitcoin/blockdata/transaction/struct.Witness.html#impl-Default) method to create an empty witness that will be filled in later after signing. + This is possible because `Witness` implements the [`Default`](https://doc.rust-lang.org/std/default/trait.Default.html) trait. + +In `let spend = TxOut {...}` we are instantiating the spend output. +Inside the [`TxOut`](https://docs.rs/bitcoin/latest/bitcoin/blockdata/transaction/struct.TxOut.html) struct we are setting the following fields: + +- `value` is the amount we are spending; it is a [`u64`](https://doc.rust-lang.org/std/primitive.u64.html) type. + We are using the `const SPEND_AMOUNT` that we defined earlier. +- `script_pubkey` is the script code required to spend a P2TR output; it is a [`ScriptBuf`](https://docs.rs/bitcoin/latest/bitcoin/blockdata/script/struct.ScriptBuf.html) type. + We are using the [`script_pubkey`](https://docs.rs/bitcoin/latest/bitcoin/blockdata/address/struct.Address.html#method.script_pubkey) method to generate the script pubkey from the receivers address. + This will lock the output to the receiver's address. + +In `let change = TxOut {...}` we are instantiating the change output. +It is very similar to the `spend` output, but we are now using the `const CHANGE_AMOUNT` that we defined earlier[^spend]. +This is done by setting the `script_pubkey` field to [`ScriptBuf::new_v1_p2tr(...)`](https://docs.rs/bitcoin/latest/bitcoin/blockdata/script/struct.ScriptBuf.html#method.new_v1_p2tr), +which generates P2TR-type of script pubkey. + +In `let unsigned_tx = Transaction {...}` we are instantiating the transaction we want to sign and broadcast using the [`Transaction`](https://docs.rs/bitcoin/latest/bitcoin/blockdata/transaction/struct.Transaction.html) struct. +We set the following fields: + +- `version` is the transaction version; it is a [`i32`](https://doc.rust-lang.org/std/primitive.u32.html) type. + We are using version `2` which means that [BIP68](https://github.com/bitcoin/bips/blob/master/bip-0068.mediawiki) applies. +- `lock_time` is the transaction lock time; + it is a [`LockTime`](https://docs.rs/bitcoin/latest/bitcoin/blockdata/locktime/absolute/enum.LockTime.html) enum. + We are using the constant [`ZERO`](https://docs.rs/bitcoin/latest/bitcoin/blockdata/locktime/absolute/enum.LockTime.html#associatedconstant.ZERO) + This will make the transaction valid immediately. +- `input` is the input vector; it is a [`Vec`](https://doc.rust-lang.org/std/vec/struct.Vec.html) type. + We are using the `input` variable that we defined earlier wrapped in the [`vec!`](https://doc.rust-lang.org/std/macro.vec.html) macro for convenient initialization. +- `output` is the output vector; it is a [`Vec`](https://doc.rust-lang.org/std/vec/struct.Vec.html) type. + We are using the `spend` and `change` variables that we defined earlier wrapped in the [`vec!`](https://doc.rust-lang.org/std/macro.vec.html) macro for convenient initialization. + +We need to reference the outputs of previous transactions in our transaction. +We accomplish this with the [`Prevouts`](https://docs.rs/bitcoin/latest/bitcoin/sighash/enum.Prevouts.html) enum. +In `let prevouts = vec![dummy_utxo];`, +we create a vector of [`TxOut`](https://docs.rs/bitcoin/latest/bitcoin/blockdata/transaction/struct.TxOut.html) types that we want to reference. +In our case, we only have one output, the `dummy_utxo` that we defined earlier. +With `let prevouts = Prevouts::All(&prevouts);` we create a [`Prevouts::All`](https://docs.rs/bitcoin/latest/bitcoin/sighash/enum.Prevouts.html#variant.All) variant that takes a reference to a vector of [`TxOut`](https://docs.rs/bitcoin/latest/bitcoin/blockdata/transaction/struct.TxOut.html) types. + +In `let mut sighash_cache = SighashCache::new(unsigned_tx);` we are instantiating a [`SighashCache`](https://docs.rs/bitcoin/latest/bitcoin/sighash/struct.SighashCache.html) struct. +This is a type that efficiently calculates [signature hash message](https://developer.bitcoin.org/devguide/transactions.html?highlight=sighash_all#signature-hash-types) for legacy, segwit and taproot inputs. +We are using the `new` method to instantiate the struct with the `unsigned_tx` that we defined earlier. +`new` takes any `Borrow` as an argument. +[`Borrow`](https://doc.rust-lang.org/std/borrow/trait.Borrow.html) is a trait that allows us to pass either a reference to a `T` or a `T` itself. +Hence, you can pass a `Transaction` or a `&Transaction` to `new`. + +`sighash_cache` is instantiated as mutable because we require a mutable reference when creating the sighash to sign using [`taproot_signature_hash`](https://docs.rs/bitcoin/latest/bitcoin/sighash/struct.SighashCache.html#method.taproot_signature_hash) to it. +This computes the [BIP341](https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki) sighash for any flag type. +It takes the following arguments: + +- `input_index` is the index of the input we are signing; it is a [`usize`](https://doc.rust-lang.org/std/primitive.usize.html) type. + We are using `0` since we only have one input. +- `&prevouts` is a refence to the [`Prevouts`](https://docs.rs/bitcoin/latest/bitcoin/sighash/enum.Prevouts.html) enum that we defined earlier. + This is used to reference the outputs of previous transactions and also used to calculate our transaction value. +- `annex` is an optional argument that is used to pass the annex data. + We are not using it, so we are passing `None`. +- `leaf_hash_code_separator` is an optional argument that is used to pass the leaf hash code separator. + We are not using it, so we are passing `None`. +- `sighash_type` is the type of sighash; it is a [`TapSighashType`](https://docs.rs/bitcoin/latest/bitcoin/sighash/enum.TapSighashType.html) enum. + We are using the [`All`](https://docs.rs/bitcoin/latest/bitcoin/sighash/enum.TapSighashType.html#variant.All) variant, + which indicates that the sighash will include all the inputs and outputs. + +Taproot signatures are generated by tweaking the private (and public) key(s). +`let tweaked: TweakedKeyPair = keypair.tap_tweak(&secp, None);` accomplishes this. + +We create the message `msg` by converting the `sighash` to a [`Message`](https://docs.rs/secp256k1/latest/secp256k1/struct.Message.html) type. +This is a the message that we will sign. +The [Message::from](https://docs.rs/secp256k1/latest/secp256k1/struct.Message.html#impl-From%3C%26%27_%20bitcoin%3A%3Ahashes%3A%3Asha256d%3A%3AHash%3E) method takes anything that implements the promises to be a thirty two byte hash i.e., 32 bytes that came from a cryptographically secure hashing algorithm. + +We compute the signature `sig` by using the [`sign_schnorr`](https://docs.rs/secp256k1/latest/secp256k1/struct.Secp256k1.html#method.sign_schnorr) method. +It takes a refence to a [`Message`](https://docs.rs/secp256k1/latest/secp256k1/struct.Message.html) and a reference to a [`KeyPair`](https://docs.rs/secp256k1/latest/secp256k1/struct.KeyPair.html) as arguments, +and returns a [`Signature`](https://docs.rs/secp256k1/latest/secp256k1/ecdsa/struct.Signature.html) type. + +In the next step, we update the witness stack for the input we just signed by first converting the `sighash_cache` into a [`Transaction`](https://docs.rs/bitcoin/latest/bitcoin/blockdata/transaction/struct.Transaction.html) +by using the [`into_transaction`](https://docs.rs/bitcoin/latest/bitcoin/sighash/struct.SighashCache.html#method.into_transaction) method. +We access the witness field of the first input with `tx.input[0].witness`. +It is a [`Witness`](https://docs.rs/bitcoin/latest/bitcoin/blockdata/witness/struct.Witness.html) type. +We use the [`push`](https://docs.rs/bitcoin/latest/bitcoin/blockdata/witness/struct.Witness.html#method.push) method +to push the serialized public and private Taproot keys. +It expects a single argument of type `AsRef<[u8]>` which is a reference to a byte slice. +We are using the [`as_ref`](https://doc.rust-lang.org/std/convert/trait.AsRef.html) method to convert the signature `sig` to a byte slice. + +As the last step we print this to terminal using the [`println!`](https://doc.rust-lang.org/std/macro.println.html) macro. +This transaction is now ready to be broadcast to the Bitcoin network. + +[^change]: Please note that the `CHANGE_AMOUNT` is not the same as the `DUMMY_UTXO_AMOUNT` minus the `SPEND_AMOUNT`. + This is due to the fact that we need to pay a fee for the transaction. + +[^expect]: We will be unwraping any [`Option`](https://doc.rust-lang.org/std/option)/[`Result`](https://doc.rust-lang.org/std/result) + with the `expect` method. + +[^secp]: Under the hood we are using the [`secp256k1`](https://github.com/rust-bitcoin/rust-secp256k1/) crate to generate the key pair. + `rust-secp256k1` is a wrapper around [libsecp256k1](https://github.com/bitcoin-core/secp256k1), a C + library implementing various cryptographic functions using the [SECG](https://www.secg.org/) curve + [secp256k1](https://en.bitcoin.it/wiki/Secp256k1). + +[^arbitrary_address]: this is an arbitrary mainnet address from block 805222. + +[^spend]: And also we are locking the output to an address that we control: + the `internal_key` public key hash that we generated earlier. diff --git a/cookbook/tests/Cargo.toml b/cookbook/tests/Cargo.toml new file mode 100644 index 0000000..5997bfe --- /dev/null +++ b/cookbook/tests/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "tests" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +doc-comment = "0.3" +bitcoin = { version = "0.30", features = ["std", "rand-std"] } diff --git a/cookbook/tests/generate.sh b/cookbook/tests/generate.sh new file mode 100755 index 0000000..5fc1eff --- /dev/null +++ b/cookbook/tests/generate.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +mkdir -p src +cat << EOF > src/lib.rs +#![allow(non_snake_case)] +#[macro_use] +extern crate doc_comment; +EOF + +for doc in ../src/*.md +do + NAME=$(basename $doc .md) + NAME=${NAME//./_} + NAME=${NAME//-/_} + echo -e "doctest\041(\"../$doc\");" > src/$NAME.rs + echo "mod $NAME;" >> src/lib.rs +done \ No newline at end of file diff --git a/justfile b/justfile new file mode 100644 index 0000000..ff9f909 --- /dev/null +++ b/justfile @@ -0,0 +1,14 @@ +default: + just --list + +alias b := build +alias t := test + +build: + cd cookbook && mdbook build + +serve: + cd cookbook && mdbook serve + +test: + cd cookbook/tests && ./generate.sh && cargo test \ No newline at end of file diff --git a/site/static/CNAME b/site/static/CNAME new file mode 100644 index 0000000..20b65e5 --- /dev/null +++ b/site/static/CNAME @@ -0,0 +1 @@ +rust-bitcoin.org