Skip to content

Commit 32cf0bd

Browse files
committed
p2qrh: pure p2tr script path spend test vector
1 parent 25a0f56 commit 32cf0bd

File tree

2 files changed

+182
-21
lines changed

2 files changed

+182
-21
lines changed

bip-0360/ref-impl/rust/tests/p2qrh_construction.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ fn process_test_vector_p2qrh(test_vector: &TestVector) -> anyhow::Result<()> {
136136
debug!("traverse_with_depth: leaf_count: {}, depth: {}, modified_depth: {}, direction: {}, tv_leaf_script: {}",
137137
tv_leaf_count, depth, modified_depth, direction, tv_leaf.script);
138138

139-
// NOTE: Some of the the test vectors in this project specify leaves with non-standardversions (ie: 250 / 0xfa)
139+
// NOTE: Some of the the test vectors in this project specify leaves with non-standard versions (ie: 250 / 0xfa)
140140
p2qrh_builder = p2qrh_builder.clone().add_leaf_with_ver(depth, tv_leaf_script_buf.clone(), tv_leaf_version)
141141
.unwrap_or_else(|e| {
142142
panic!("Failed to add leaf: {:?}", e);

bip-0360/ref-impl/rust/tests/p2qrh_spend.rs

Lines changed: 181 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,22 @@
1-
use log::debug;
1+
use log::{debug, info};
22
use once_cell::sync::Lazy;
33

4-
use bitcoin::blockdata::witness::Witness;
5-
use bitcoin::ScriptBuf;
6-
7-
use p2qrh_ref::data_structures::{TestVector, TestVectors};
4+
use bitcoin::sighash::{EcdsaSighashType, Prevouts, TapSighash};
5+
use bitcoin::hashes::Hash;
6+
use bitcoin::secp256k1::{Message, Secp256k1, SecretKey};
7+
use bitcoin::ecdsa::Signature;
8+
use bitcoin::{ Amount, TxOut, sighash::TapSighashType, transaction, ScriptBuf, WPubkeyHash,
9+
OutPoint,
10+
blockdata::witness::Witness,
11+
sighash::SighashCache,
12+
taproot::{LeafVersion, TapLeafHash},
13+
transaction::Transaction,
14+
};
15+
16+
use p2qrh_ref::{
17+
data_structures::{TestVector, TestVectors},
18+
serialize_script,
19+
};
820

921
static TEST_VECTORS: Lazy<TestVectors> = Lazy::new(|| {
1022
let bip360_test_vectors = include_str!("../tests/data/p2qrh_spend.json");
@@ -16,24 +28,177 @@ static TEST_VECTORS: Lazy<TestVectors> = Lazy::new(|| {
1628
static P2QRH_SINGLE_LEAF_SCRIPT_TREE_NO_SIGS_TEST: &str = "p2qrh_single_leaf_script_tree_no_sigs";
1729

1830
/* The rust-bitcoin crate does not provide a single high-level API that builds the full Taproot script-path witness stack for you.
19-
It does expose all the necessary types and primitives to build it manually and correctly.
20-
*/
31+
It does expose all the necessary types and primitives to build it manually and correctly.
32+
*/
2133

34+
// https://learnmeabitcoin.com/technical/upgrades/taproot/#example-2-script-path-spend-simple
2235
#[test]
23-
fn test_p2qrh_single_leaf_script_tree_no_sigs() {
36+
fn test_script_path_spend_simple() {
2437
let _ = env_logger::try_init(); // Use try_init to avoid reinitialization error
2538

26-
let test_vectors: &TestVectors = &*TEST_VECTORS;
27-
let test_vector: &TestVector = test_vectors.test_vector_map.get(P2QRH_SINGLE_LEAF_SCRIPT_TREE_NO_SIGS_TEST).unwrap();
39+
let script_inputs_count = hex::decode("03").unwrap();
40+
let script_inputs_bytes: Vec<u8> = hex::decode("08").unwrap();
41+
let leaf_script_bytes: Vec<u8> = hex::decode("5887").unwrap();
42+
let control_block_bytes: Vec<u8> =
43+
hex::decode("c1924c163b385af7093440184af6fd6244936d1288cbb41cc3812286d3f83a3329").unwrap();
44+
let test_witness_bytes: Vec<u8> = hex::decode(
45+
"03010802588721c1924c163b385af7093440184af6fd6244936d1288cbb41cc3812286d3f83a3329",
46+
)
47+
.unwrap();
48+
49+
let mut derived_witness: Witness = Witness::new();
50+
derived_witness.push(script_inputs_count);
51+
derived_witness.push(serialize_script(&script_inputs_bytes));
52+
derived_witness.push(serialize_script(&leaf_script_bytes));
53+
derived_witness.push(serialize_script(&control_block_bytes));
54+
55+
info!("witness: {:?}", derived_witness);
56+
57+
let derived_witness_vec: Vec<u8> = derived_witness.iter().flatten().cloned().collect();
58+
59+
assert_eq!(derived_witness_vec, test_witness_bytes);
60+
}
61+
62+
63+
// https://learnmeabitcoin.com/technical/upgrades/taproot/#example-3-script-path-spend-signature
64+
#[test]
65+
fn test_script_path_spend_signatures() {
66+
let _ = env_logger::try_init(); // Use try_init to avoid reinitialization error
67+
68+
let input_tx_id_bytes =
69+
hex::decode("d1c40446c65456a9b11a9dddede31ee34b8d3df83788d98f690225d2958bfe3c").unwrap();
70+
71+
let input_leaf_script_bytes: Vec<u8> =
72+
hex::decode("206d4ddc0e47d2e8f82cbe2fc2d0d749e7bd3338112cecdc76d8f831ae6620dbe0ac").unwrap();
73+
let input_control_block_bytes: Vec<u8> =
74+
hex::decode("c0924c163b385af7093440184af6fd6244936d1288cbb41cc3812286d3f83a3329").unwrap();
75+
let input_script_pubkey_bytes: Vec<u8> =
76+
hex::decode("5120f3778defe5173a9bf7169575116224f961c03c725c0e98b8da8f15df29194b80")
77+
.unwrap();
78+
let input_script_priv_key_bytes: Vec<u8> = hex::decode("9b8de5d7f20a8ebb026a82babac3aa47a008debbfde5348962b2c46520bd5189").unwrap();
2879

29-
let mut witness: Witness= Witness::new();
80+
let spend_pubkey_hash_bytes: Vec<u8> = hex::decode("0de745dc58d8e62e6f47bde30cd5804a82016f9e").unwrap();
81+
82+
let test_sighash_bytes: Vec<u8> = hex::decode("752453d473e511a0da2097d664d69fe5eb89d8d9d00eab924b42fc0801a980c9").unwrap();
83+
let test_p2wpkh_signature_bytes: Vec<u8> = hex::decode("01769105cbcbdcaaee5e58cd201ba3152477fda31410df8b91b4aee2c4864c7700615efb425e002f146a39ca0a4f2924566762d9213bd33f825fad83977fba7f01").unwrap();
84+
let test_witness_bytes: Vec<u8> = hex::decode("034101769105cbcbdcaaee5e58cd201ba3152477fda31410df8b91b4aee2c4864c7700615efb425e002f146a39ca0a4f2924566762d9213bd33f825fad83977fba7f0122206d4ddc0e47d2e8f82cbe2fc2d0d749e7bd3338112cecdc76d8f831ae6620dbe0ac21c0924c163b385af7093440184af6fd6244936d1288cbb41cc3812286d3f83a3329").unwrap();
85+
86+
let mut txid_little_endian = input_tx_id_bytes.clone();
87+
txid_little_endian.reverse();
3088

3189

32-
test_vector.given.script_inputs.as_ref().unwrap().iter().for_each(|tv_script_input| {
33-
let script_input_bytes = hex::decode(tv_script_input).unwrap();
34-
witness.push(script_input_bytes);
35-
36-
});
90+
// vin: Create TxIn from the input utxo
91+
// Details of this input tx are not known at this point
92+
let input_tx_in = bitcoin::TxIn {
93+
previous_output: OutPoint {
94+
txid: bitcoin::Txid::from_slice(&txid_little_endian).unwrap(), // bitcoin::Txid expects the bytes in little-endian format
95+
vout: 0,
96+
},
97+
script_sig: ScriptBuf::new(), // Empty for segwit transactions - script goes in witness
98+
sequence: transaction::Sequence::MAX, // Default sequence, allows immediate spending (no RBF or timelock)
99+
witness: bitcoin::Witness::new(), // Empty for now, will be filled with signature and pubkey after signing
100+
};
101+
102+
let spend_wpubkey_hash = WPubkeyHash::from_byte_array(spend_pubkey_hash_bytes.try_into().unwrap());
103+
let spend_output: TxOut = TxOut {
104+
value: Amount::from_sat(15000),
105+
script_pubkey: ScriptBuf::new_p2wpkh(&spend_wpubkey_hash),
106+
};
107+
108+
// The spend tx to eventually be signed and broadcast
109+
let mut unsigned_spend_tx = Transaction {
110+
version: bitcoin::transaction::Version::TWO,
111+
lock_time: bitcoin::locktime::absolute::LockTime::ZERO,
112+
input: vec![input_tx_in],
113+
output: vec![spend_output],
114+
};
115+
116+
// Create SighashCache
117+
// At this point, sighash_cache does not know the values and type of input UTXO
118+
let mut tapscript_sighash_cache = SighashCache::new(&mut unsigned_spend_tx);
119+
120+
// Create the leaf hash
121+
let leaf_version = LeafVersion::TapScript;
122+
let leaf_script = ScriptBuf::from_bytes(input_leaf_script_bytes.clone());
123+
let leaf_hash: TapLeafHash = TapLeafHash::from_script(&leaf_script, leaf_version);
124+
125+
/* prevouts parameter tells the sighash algorithm:
126+
1. The value of each input being spent (needed for fee calculation and sighash computation)
127+
2. The scriptPubKey of each input being spent (ie: type of output & how to validate the spend)
128+
*/
129+
let prevouts = vec![TxOut {
130+
value: Amount::from_sat(20000),
131+
script_pubkey: ScriptBuf::from_bytes(input_script_pubkey_bytes.clone()),
132+
}];
133+
info!("prevouts: {:?}", prevouts);
134+
135+
// Compute the sighash
136+
let tapscript_sighash: TapSighash = tapscript_sighash_cache.taproot_script_spend_signature_hash(
137+
0, // input_index
138+
&Prevouts::All(&prevouts),
139+
leaf_hash,
140+
TapSighashType::All
141+
).unwrap();
142+
143+
assert_eq!(tapscript_sighash.as_byte_array().as_slice(), test_sighash_bytes.as_slice(), "sighash mismatch");
144+
info!("sighash: {:?}", tapscript_sighash);
145+
146+
let spend_msg = Message::from(tapscript_sighash);
147+
148+
// Signing: Sign the sighash using the secp256k1 library (re-exported by rust-bitcoin).
149+
let secp = Secp256k1::new();
150+
let secret_key = SecretKey::from_slice(&input_script_priv_key_bytes).unwrap();
151+
152+
// Spending a p2tr UTXO thus using Schnorr signature
153+
// The aux_rand parameter ensures that signing the same message with the same key produces the same signature
154+
let p2wpkh_signature: bitcoin::secp256k1::schnorr::Signature = secp.sign_schnorr_with_aux_rand(
155+
&spend_msg,
156+
&secret_key.keypair(&secp),
157+
&[0u8; 32] // 32 zero bytes of auxiliary random data
158+
);
159+
let mut p2wpkh_sig_bytes: Vec<u8> = p2wpkh_signature.serialize().to_vec();
160+
p2wpkh_sig_bytes.push(EcdsaSighashType::All as u8);
161+
162+
assert_eq!(p2wpkh_sig_bytes, test_p2wpkh_signature_bytes, "p2wpkh_signature mismatch");
163+
let p2wpkh_sig_hex = hex::encode(p2wpkh_sig_bytes.clone());
164+
info!("p2wpkh_signature: {:?}", p2wpkh_sig_hex);
165+
166+
let mut derived_witness: Witness = Witness::new();
167+
derived_witness.push(hex::decode("03").unwrap());
168+
derived_witness.push(serialize_script(&p2wpkh_sig_bytes));
169+
derived_witness.push(serialize_script(&input_leaf_script_bytes));
170+
derived_witness.push(serialize_script(&input_control_block_bytes));
171+
172+
let derived_witness_vec: Vec<u8> = derived_witness.iter().flatten().cloned().collect();
173+
174+
assert_eq!(derived_witness_vec, test_witness_bytes, "derived_witness mismatch");
175+
176+
let derived_witness_hex = hex::encode(derived_witness_vec);
177+
info!("derived_witness_hex: {:?}", derived_witness_hex);
178+
}
179+
180+
#[test]
181+
fn test_p2qrh_single_leaf_script_tree_no_sigs() {
182+
let _ = env_logger::try_init(); // Use try_init to avoid reinitialization error
183+
184+
let test_vectors: &TestVectors = &*TEST_VECTORS;
185+
let test_vector: &TestVector = test_vectors
186+
.test_vector_map
187+
.get(P2QRH_SINGLE_LEAF_SCRIPT_TREE_NO_SIGS_TEST)
188+
.unwrap();
189+
190+
let mut witness: Witness = Witness::new();
191+
192+
test_vector
193+
.given
194+
.script_inputs
195+
.as_ref()
196+
.unwrap()
197+
.iter()
198+
.for_each(|tv_script_input| {
199+
let script_input_bytes = hex::decode(tv_script_input).unwrap();
200+
witness.push(script_input_bytes);
201+
});
37202

38203
// Hint: use https://learnmeabitcoin.com/technical/script/
39204
let tv_script_hex = test_vector.given.script_hex.as_ref().unwrap();
@@ -56,8 +221,4 @@ fn test_p2qrh_single_leaf_script_tree_no_sigs() {
56221

57222
let expected_witness = test_vector.expected.witness.as_ref().unwrap();
58223
assert_eq!(&witness_hex_string, expected_witness);
59-
60224
}
61-
62-
63-

0 commit comments

Comments
 (0)