1- use log:: debug;
1+ use log:: { debug, info } ;
22use 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
921static 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(|| {
1628static 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