diff --git a/bitcoin/Cargo.toml b/bitcoin/Cargo.toml index 1f1926a4..c5493710 100644 --- a/bitcoin/Cargo.toml +++ b/bitcoin/Cargo.toml @@ -70,6 +70,14 @@ required-features = ["std", "rand-std"] name = "ecdsa-psbt" required-features = ["std", "bitcoinconsensus"] +[[example]] +name = "sign-tx-segwit-v0" +required-features = ["std", "rand-std"] + +[[example]] +name = "sign-tx-taproot" +required-features = ["std", "rand-std"] + [[example]] name = "taproot-psbt" required-features = ["std", "rand-std", "bitcoinconsensus"] diff --git a/bitcoin/contrib/test.sh b/bitcoin/contrib/test.sh index fa87a21d..b46ab679 100755 --- a/bitcoin/contrib/test.sh +++ b/bitcoin/contrib/test.sh @@ -29,6 +29,8 @@ then cargo clippy --locked --example bip32 -- -D warnings cargo clippy --locked --example handshake --features=rand-std -- -D warnings cargo clippy --locked --example ecdsa-psbt --features=bitcoinconsensus -- -D warnings + cargo clippy --locked --example sign-tx-segwit-v0 --features=rand-std -- -D warnings + cargo clippy --locked --example sign-tx-taproot --features=rand-std -- -D warnings cargo clippy --locked --example taproot-psbt --features=rand-std,bitcoinconsensus -- -D warnings # We should not have any duplicate dependencies. This catches mistakes made upgrading dependencies @@ -92,6 +94,8 @@ do done cargo run --locked --example ecdsa-psbt --features=bitcoinconsensus +cargo run --locked --example sign-tx-segwit-v0 --features=rand-std -- -D warnings +cargo run --locked --example sign-tx-taproot --features=rand-std -- -D warnings cargo run --locked --example taproot-psbt --features=rand-std,bitcoinconsensus # Build the docs if told to (this only works with the nightly toolchain) diff --git a/bitcoin/examples/sign-tx-segwit-v0.rs b/bitcoin/examples/sign-tx-segwit-v0.rs new file mode 100644 index 00000000..ef7f969c --- /dev/null +++ b/bitcoin/examples/sign-tx-segwit-v0.rs @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: CC0-1.0 + +//! Demonstrate creating a transaction that spends to and from p2wpkh outputs. + +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::{ + transaction, Address, Amount, Network, OutPoint, ScriptBuf, Sequence, Transaction, TxIn, TxOut, + Txid, WPubkeyHash, Witness, +}; + +const DUMMY_UTXO_AMOUNT: Amount = Amount::from_sat(20_000_000); +const SPEND_AMOUNT: Amount = Amount::from_sat(5_000_000); +const CHANGE_AMOUNT: Amount = Amount::from_sat(14_999_000); // 1000 sat fee. + +fn main() { + let secp = Secp256k1::new(); + + // Get a secret key we control and the pubkeyhash of the associated pubkey. + // In a real application these would come from a stored secret. + let (sk, wpkh) = senders_keys(&secp); + + // Get an address to send to. + let address = receivers_address(); + + // Get an unspent output that is locked to the key above that we control. + // In a real application these would come from the chain. + let (dummy_out_point, dummy_utxo) = dummy_unspent_transaction_output(&wpkh); + + // The input for the transaction we are constructing. + let input = TxIn { + previous_output: dummy_out_point, // The dummy output we are spending. + script_sig: ScriptBuf::default(), // For a p2wpkh script_sig is empty. + sequence: Sequence::ENABLE_RBF_NO_LOCKTIME, + witness: Witness::default(), // Filled in after signing. + }; + + // 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_p2wpkh(&wpkh), // Change comes back to us. + }; + + // The transaction we want to sign and broadcast. + let mut unsigned_tx = Transaction { + version: transaction::Version::TWO, // Post BIP-68. + lock_time: absolute::LockTime::ZERO, // Ignore the locktime. + input: vec![input], // Input goes into index 0. + output: vec![spend, change], // Outputs, order does not matter. + }; + let input_index = 0; + + // Get the sighash to sign. + let sighash_type = EcdsaSighashType::All; + let mut sighasher = SighashCache::new(&mut unsigned_tx); + let sighash = sighasher + .p2wpkh_signature_hash(input_index, &dummy_utxo.script_pubkey, SPEND_AMOUNT, sighash_type) + .expect("failed to create sighash"); + + // Sign the sighash using the secp256k1 library (exported by rust-bitcoin). + let msg = Message::from(sighash); + let sig = secp.sign_ecdsa(&msg, &sk); + + // Update the witness stack. + let signature = bitcoin::ecdsa::Signature { sig, hash_ty: EcdsaSighashType::All }; + let pk = sk.public_key(&secp); + *sighasher.witness_mut(input_index).unwrap() = Witness::p2wpkh(&signature, &pk); + + // Get the signed transaction. + let tx = sighasher.into_transaction(); + + // BOOM! Transaction signed and ready to broadcast. + println!("{:#?}", tx); +} + +/// An example of keys controlled by the transaction sender. +/// +/// In a real application these would be actual secrets. +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) +} + +/// A dummy address for the receiver. +/// +/// We lock the spend output to the key associated with this address. +/// +/// (FWIW this is a random mainnet address from block 80219.) +fn receivers_address() -> Address { + Address::from_str("bc1q7cyrfmck2ffu2ud3rn5l5a8yv6f0chkp0zpemf") + .expect("a valid address") + .require_network(Network::Bitcoin) + .expect("valid address for mainnet") +} + +/// Creates a p2wpkh output locked to the key associated with `wpkh`. +/// +/// An utxo is described by the `OutPoint` (txid and index within the transaction that it was +/// created). Using the out point one can get the transaction by `txid` and using the `vout` get the +/// transaction value and script pubkey (`TxOut`) of the utxo. +/// +/// This output is locked to keys that we control, in a real application this would be a valid +/// output taken from a transaction that appears in the chain. +fn dummy_unspent_transaction_output(wpkh: &WPubkeyHash) -> (OutPoint, TxOut) { + let script_pubkey = ScriptBuf::new_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) +} diff --git a/bitcoin/examples/sign-tx-taproot.rs b/bitcoin/examples/sign-tx-taproot.rs new file mode 100644 index 00000000..c9025df2 --- /dev/null +++ b/bitcoin/examples/sign-tx-taproot.rs @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: CC0-1.0 + +//! Demonstrate creating a transaction that spends to and from p2tr outputs. + +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::{ + transaction, Address, Amount, Network, OutPoint, ScriptBuf, Sequence, Transaction, TxIn, TxOut, + Txid, Witness, +}; + +const DUMMY_UTXO_AMOUNT: Amount = Amount::from_sat(20_000_000); +const SPEND_AMOUNT: Amount = Amount::from_sat(5_000_000); +const CHANGE_AMOUNT: Amount = Amount::from_sat(14_999_000); // 1000 sat fee. + +fn main() { + let secp = Secp256k1::new(); + + // Get a keypair we control. In a real application these would come from a stored secret. + let keypair = senders_keys(&secp); + let (internal_key, _parity) = keypair.x_only_public_key(); + + // Get an unspent output that is locked to the key above that we control. + // In a real application these would come from the chain. + let (dummy_out_point, dummy_utxo) = dummy_unspent_transaction_output(&secp, internal_key); + + // Get an address to send to. + let address = receivers_address(); + + // The input for the transaction we are constructing. + let input = TxIn { + previous_output: dummy_out_point, // The dummy output we are spending. + script_sig: ScriptBuf::default(), // For a p2tr script_sig is empty. + sequence: Sequence::ENABLE_RBF_NO_LOCKTIME, + witness: Witness::default(), // Filled in after signing. + }; + + // 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_p2tr(&secp, internal_key, None), // Change comes back to us. + }; + + // The transaction we want to sign and broadcast. + let mut unsigned_tx = Transaction { + version: transaction::Version::TWO, // Post BIP-68. + lock_time: absolute::LockTime::ZERO, // Ignore the locktime. + input: vec![input], // Input goes into index 0. + output: vec![spend, change], // Outputs, order does not matter. + }; + let input_index = 0; + + // Get the sighash to sign. + + let sighash_type = TapSighashType::Default; + let prevouts = vec![dummy_utxo]; + let prevouts = Prevouts::All(&prevouts); + + let mut sighasher = SighashCache::new(&mut unsigned_tx); + let sighash = sighasher + .taproot_key_spend_signature_hash(input_index, &prevouts, sighash_type) + .expect("failed to construct sighash"); + + // Sign the sighash using the secp256k1 library (exported by rust-bitcoin). + let tweaked: TweakedKeypair = keypair.tap_tweak(&secp, None); + let msg = Message::from_digest(sighash.to_byte_array()); + let sig = secp.sign_schnorr(&msg, &tweaked.to_inner()); + + // Update the witness stack. + let signature = bitcoin::taproot::Signature { sig, hash_ty: sighash_type }; + sighasher.witness_mut(input_index).unwrap().push(&signature.to_vec()); + + // Get the signed transaction. + let tx = sighasher.into_transaction(); + + // BOOM! Transaction signed and ready to broadcast. + println!("{:#?}", tx); +} + +/// An example of keys controlled by the transaction sender. +/// +/// In a real application these would be actual secrets. +fn senders_keys(secp: &Secp256k1) -> Keypair { + let sk = SecretKey::new(&mut rand::thread_rng()); + Keypair::from_secret_key(secp, &sk) +} + +/// A dummy address for the receiver. +/// +/// We lock the spend output to the key associated with this address. +/// +/// (FWIW this is an arbitrary mainnet address from block 805222.) +fn receivers_address() -> Address { + Address::from_str("bc1p0dq0tzg2r780hldthn5mrznmpxsxc0jux5f20fwj0z3wqxxk6fpqm7q0va") + .expect("a valid address") + .require_network(Network::Bitcoin) + .expect("valid address for mainnet") +} + +/// Creates a p2wpkh output locked to the key associated with `wpkh`. +/// +/// An utxo is described by the `OutPoint` (txid and index within the transaction that it was +/// created). Using the out point one can get the transaction by `txid` and using the `vout` get the +/// transaction value and script pubkey (`TxOut`) of the utxo. +/// +/// This output is locked to keys that we control, in a real application this would be a valid +/// output taken from a transaction that appears in the chain. +fn dummy_unspent_transaction_output( + secp: &Secp256k1, + internal_key: UntweakedPublicKey, +) -> (OutPoint, TxOut) { + let script_pubkey = ScriptBuf::new_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) +}