bitcoin: Add signing examples
Add two signing examples to showcase signing a simple one input two output transaction using both segwit v0 outputs and taproot outputs. This patch is the result of the recent rust-bitcoin TABConf workshop, wit bug fix by Sanket, updated to use APIs from tip of master branch. This code, depending on v0.30.0 is what is being introduced to the cookbook at the moment.
This commit is contained in:
parent
60318c4c71
commit
fa104aefa5
|
@ -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"]
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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<C: Signing>(secp: &Secp256k1<C>) -> (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)
|
||||
}
|
|
@ -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<C: Signing>(secp: &Secp256k1<C>) -> 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<C: Verification>(
|
||||
secp: &Secp256k1<C>,
|
||||
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)
|
||||
}
|
Loading…
Reference in New Issue