@@ -108,23 +108,20 @@ pub struct P2mrTreeInfo {
108108 pub leaves : Vec < P2mrLeafInfo > ,
109109}
110110
111+ /// Intermediate leaf data collected during tree traversal: (leaf_hash, leaf_version, merkle_path).
112+ type LeafCollector = Vec < ( [ u8 ; 32 ] , u8 , Vec < [ u8 ; 32 ] > ) > ;
113+
111114/// Build a P2MR Merkle tree from a script tree definition.
112115///
113116/// Returns the Merkle root and per-leaf spending info (leaf hash + control block).
114117/// Leaves are returned in left-to-right DFS order matching the input tree structure.
115118pub fn build_p2mr_tree ( tree : & ScriptTreeNode ) -> P2mrTreeInfo {
116- // Compute the merkle root and collect leaf hashes with their merkle proof paths.
117- // Leaves are output in sorted DFS order (matching tap_branch_hash lex sorting).
118- let mut leaves: Vec < ( [ u8 ; 32 ] , Vec < [ u8 ; 32 ] > ) > = Vec :: new ( ) ;
119+ let mut leaves: LeafCollector = Vec :: new ( ) ;
119120 let merkle_root = compute_node ( tree, & mut leaves) ;
120121
121122 let leaf_infos = leaves
122123 . into_iter ( )
123- . map ( |( leaf_hash, path) | {
124- // Determine leaf version from the leaf hash by looking up the tree.
125- // We need the leaf version for the control byte. Extract it from the tree.
126- let leaf_version =
127- find_leaf_version ( tree, & leaf_hash) . unwrap_or ( TAPSCRIPT_LEAF_VERSION ) ;
124+ . map ( |( leaf_hash, leaf_version, path) | {
128125 let mut control_block = vec ! [ p2mr_control_byte( leaf_version) ] ;
129126 for sibling in & path {
130127 control_block. extend_from_slice ( sibling) ;
@@ -144,16 +141,16 @@ pub fn build_p2mr_tree(tree: &ScriptTreeNode) -> P2mrTreeInfo {
144141
145142/// Recursively compute the hash of a tree node, collecting leaf info along the way.
146143///
147- /// Leaves are output in input DFS order (left subtree before right subtree,
148- /// preserving the tree structure as given ).
149- fn compute_node ( node : & ScriptTreeNode , leaves : & mut Vec < ( [ u8 ; 32 ] , Vec < [ u8 ; 32 ] > ) > ) -> [ u8 ; 32 ] {
144+ /// Each leaf entry stores (leaf_hash, leaf_version, merkle_path_siblings).
145+ /// Leaves are output in input DFS order (left subtree before right subtree ).
146+ fn compute_node ( node : & ScriptTreeNode , leaves : & mut LeafCollector ) -> [ u8 ; 32 ] {
150147 match node {
151148 ScriptTreeNode :: Leaf {
152149 script,
153150 leaf_version,
154151 } => {
155152 let hash = tap_leaf_hash ( script, * leaf_version) ;
156- leaves. push ( ( hash, Vec :: new ( ) ) ) ;
153+ leaves. push ( ( hash, * leaf_version , Vec :: new ( ) ) ) ;
157154 hash
158155 }
159156 ScriptTreeNode :: Branch ( left, right) => {
@@ -165,36 +162,17 @@ fn compute_node(node: &ScriptTreeNode, leaves: &mut Vec<([u8; 32], Vec<[u8; 32]>
165162 // Add sibling hashes to the merkle proof paths.
166163 // Left subtree leaves need right_hash as sibling, and vice versa.
167164 for leaf in leaves[ left_start..right_start] . iter_mut ( ) {
168- leaf. 1 . push ( right_hash) ;
165+ leaf. 2 . push ( right_hash) ;
169166 }
170167 for leaf in leaves[ right_start..] . iter_mut ( ) {
171- leaf. 1 . push ( left_hash) ;
168+ leaf. 2 . push ( left_hash) ;
172169 }
173170
174171 tap_branch_hash ( & left_hash, & right_hash)
175172 }
176173 }
177174}
178175
179- /// Find the leaf version for a leaf with the given hash (DFS search).
180- fn find_leaf_version ( node : & ScriptTreeNode , target_hash : & [ u8 ; 32 ] ) -> Option < u8 > {
181- match node {
182- ScriptTreeNode :: Leaf {
183- script,
184- leaf_version,
185- } => {
186- if tap_leaf_hash ( script, * leaf_version) == * target_hash {
187- Some ( * leaf_version)
188- } else {
189- None
190- }
191- }
192- ScriptTreeNode :: Branch ( left, right) => {
193- find_leaf_version ( left, target_hash) . or_else ( || find_leaf_version ( right, target_hash) )
194- }
195- }
196- }
197-
198176/// Verify a P2MR control block against a leaf hash and expected merkle root.
199177///
200178/// Walks the merkle path in the control block, combining with `tap_branch_hash`
@@ -389,6 +367,133 @@ mod tests {
389367 ) ;
390368 }
391369
370+ /// Test against BIP-360 spec vector: p2mr_simple_lightning_contract
371+ #[ test]
372+ fn test_simple_lightning_contract ( ) {
373+ let script0 = hex:: decode (
374+ "029000b275209997a497d964fc1a62885b05a51166a65a90df00492c8d7cf61d6accf54803beac" ,
375+ )
376+ . unwrap ( ) ;
377+ let script1 = hex:: decode (
378+ "a8206c60f404f8167a38fc70eaf8aa17ac351023bef86bcb9d1086a19afe95bd533388204edfcf9dfe6c0b5c83d1ab3f78d1b39a46ebac6798e08e19761f5ed89ec83c10ac" ,
379+ )
380+ . unwrap ( ) ;
381+
382+ let tree = ScriptTreeNode :: Branch (
383+ Box :: new ( ScriptTreeNode :: Leaf {
384+ script : script0,
385+ leaf_version : 192 ,
386+ } ) ,
387+ Box :: new ( ScriptTreeNode :: Leaf {
388+ script : script1,
389+ leaf_version : 192 ,
390+ } ) ,
391+ ) ;
392+ let info = build_p2mr_tree ( & tree) ;
393+
394+ assert_eq ! (
395+ hex:: encode( info. merkle_root) ,
396+ "41646f8c1fe2a96ddad7f5471bc4fee7da98794ef8c45a4f4fc6a559d60c9f6b"
397+ ) ;
398+
399+ // Verify leaf hashes
400+ let leaf_hashes: Vec < String > = info
401+ . leaves
402+ . iter ( )
403+ . map ( |l| hex:: encode ( l. leaf_hash ) )
404+ . collect ( ) ;
405+ assert ! ( leaf_hashes. contains(
406+ & "c81451874bd9ebd4b6fd4bba1f84cdfb533c532365d22a0a702205ff658b17c9" . to_string( )
407+ ) ) ;
408+ assert ! ( leaf_hashes. contains(
409+ & "632c8632b4f29c6291416e23135cf78ecb82e525788ea5ed6483e3c6ce943b42" . to_string( )
410+ ) ) ;
411+
412+ assert_control_blocks_match (
413+ & info,
414+ & [
415+ "c1c81451874bd9ebd4b6fd4bba1f84cdfb533c532365d22a0a702205ff658b17c9" ,
416+ "c1632c8632b4f29c6291416e23135cf78ecb82e525788ea5ed6483e3c6ce943b42" ,
417+ ] ,
418+ ) ;
419+
420+ // Verify scriptPubKey and address
421+ let spk = build_p2mr_script_pubkey ( & info. merkle_root ) ;
422+ assert_eq ! (
423+ hex:: encode( spk. as_bytes( ) ) ,
424+ "522041646f8c1fe2a96ddad7f5471bc4fee7da98794ef8c45a4f4fc6a559d60c9f6b"
425+ ) ;
426+ }
427+
428+ /// Test against BIP-360 spec vector: p2mr_three_leaf_alternative
429+ #[ test]
430+ fn test_three_leaf_alternative ( ) {
431+ let script_a =
432+ hex:: decode ( "2071981521ad9fc9036687364118fb6ccd2035b96a423c59c5430e98310a11abe2ac" )
433+ . unwrap ( ) ;
434+ let script_b =
435+ hex:: decode ( "20d5094d2dbe9b76e2c245a2b89b6006888952e2faa6a149ae318d69e520617748ac" )
436+ . unwrap ( ) ;
437+ let script_c =
438+ hex:: decode ( "20c440b462ad48c7a77f94cd4532d8f2119dcebbd7c9764557e62726419b08ad4cac" )
439+ . unwrap ( ) ;
440+
441+ let tree = ScriptTreeNode :: Branch (
442+ Box :: new ( ScriptTreeNode :: Leaf {
443+ script : script_a,
444+ leaf_version : 192 ,
445+ } ) ,
446+ Box :: new ( ScriptTreeNode :: Branch (
447+ Box :: new ( ScriptTreeNode :: Leaf {
448+ script : script_b,
449+ leaf_version : 192 ,
450+ } ) ,
451+ Box :: new ( ScriptTreeNode :: Leaf {
452+ script : script_c,
453+ leaf_version : 192 ,
454+ } ) ,
455+ ) ) ,
456+ ) ;
457+ let info = build_p2mr_tree ( & tree) ;
458+
459+ assert_eq ! (
460+ hex:: encode( info. merkle_root) ,
461+ "2f6b2c5397b6d68ca18e09a3f05161668ffe93a988582d55c6f07bd5b3329def"
462+ ) ;
463+
464+ // Verify leaf hashes
465+ let leaf_hashes: Vec < String > = info
466+ . leaves
467+ . iter ( )
468+ . map ( |l| hex:: encode ( l. leaf_hash ) )
469+ . collect ( ) ;
470+ assert ! ( leaf_hashes. contains(
471+ & "f154e8e8e17c31d3462d7132589ed29353c6fafdb884c5a6e04ea938834f0d9d" . to_string( )
472+ ) ) ;
473+ assert ! ( leaf_hashes. contains(
474+ & "737ed1fe30bc42b8022d717b44f0d93516617af64a64753b7a06bf16b26cd711" . to_string( )
475+ ) ) ;
476+ assert ! ( leaf_hashes. contains(
477+ & "d7485025fceb78b9ed667db36ed8b8dc7b1f0b307ac167fa516fe4352b9f4ef7" . to_string( )
478+ ) ) ;
479+
480+ assert_control_blocks_match (
481+ & info,
482+ & [
483+ "c13cd369a528b326bc9d2133cbd2ac21451acb31681a410434672c8e34fe757e91" ,
484+ "c1737ed1fe30bc42b8022d717b44f0d93516617af64a64753b7a06bf16b26cd711f154e8e8e17c31d3462d7132589ed29353c6fafdb884c5a6e04ea938834f0d9d" ,
485+ "c1d7485025fceb78b9ed667db36ed8b8dc7b1f0b307ac167fa516fe4352b9f4ef7f154e8e8e17c31d3462d7132589ed29353c6fafdb884c5a6e04ea938834f0d9d" ,
486+ ] ,
487+ ) ;
488+
489+ // Verify scriptPubKey
490+ let spk = build_p2mr_script_pubkey ( & info. merkle_root ) ;
491+ assert_eq ! (
492+ hex:: encode( spk. as_bytes( ) ) ,
493+ "52202f6b2c5397b6d68ca18e09a3f05161668ffe93a988582d55c6f07bd5b3329def"
494+ ) ;
495+ }
496+
392497 /// Test against BIP-360 spec vector: p2mr_three_leaf_complex
393498 /// Tree structure: [A, [B, C]]
394499 #[ test]
0 commit comments