Skip to content

Commit 820f9b8

Browse files
committed
tr: add a whole bunch of fixed vector unit tests for TrSpendInfo
1 parent aacb5d9 commit 820f9b8

File tree

1 file changed

+321
-0
lines changed

1 file changed

+321
-0
lines changed

src/descriptor/tr/spend_info.rs

+321
Original file line numberDiff line numberDiff line change
@@ -345,3 +345,324 @@ impl<'sp, Pk: MiniscriptKey> TrSpendInfoIterItem<'sp, Pk> {
345345
#[inline]
346346
pub fn control_block(&self) -> &ControlBlock { &self.control_block }
347347
}
348+
349+
#[cfg(test)]
350+
mod tests {
351+
use super::*;
352+
353+
#[derive(PartialEq, Eq, Debug)]
354+
struct ExpectedTree {
355+
internal_key: UntweakedPublicKey,
356+
output_key: TweakedPublicKey,
357+
output_key_parity: Parity,
358+
merkle_root: Option<TapNodeHash>,
359+
}
360+
361+
#[derive(PartialEq, Eq, Debug)]
362+
struct ExpectedLeaf {
363+
leaf_hash: TapLeafHash,
364+
branch: TaprootMerkleBranch,
365+
}
366+
367+
fn test_cases() -> Vec<(String, ExpectedTree, Vec<ExpectedLeaf>)> {
368+
let secp = Secp256k1::verification_only();
369+
let pk = "03cc8a4bc64d897bddc5fbc2f670f7a8ba0b386779106cf1223c6fc5d7cd6fc115"
370+
.parse::<bitcoin::PublicKey>()
371+
.unwrap();
372+
373+
// Hash of the FALSE script
374+
let zero_hash = "e7e4d593fcb72926eedbe0d1e311f41acd6f6ef161dcba081a75168ec4dcd379"
375+
.parse::<TapLeafHash>()
376+
.unwrap();
377+
// Hash of the TRUE script
378+
let one_hash = "a85b2107f791b26a84e7586c28cec7cb61202ed3d01944d832500f363782d675"
379+
.parse::<TapLeafHash>()
380+
.unwrap();
381+
382+
let mut ret = vec![];
383+
384+
// Empty tree
385+
let merkle_root = None;
386+
let internal_key = pk.to_x_only_pubkey();
387+
let (output_key, output_key_parity) = internal_key.tap_tweak(&secp, merkle_root);
388+
ret.push((
389+
format!("tr({pk})"),
390+
ExpectedTree { internal_key, output_key, output_key_parity, merkle_root },
391+
vec![],
392+
));
393+
394+
// Single-leaf tree
395+
let merkle_root = Some(TapNodeHash::from(zero_hash));
396+
let internal_key = pk.to_x_only_pubkey();
397+
let (output_key, output_key_parity) = internal_key.tap_tweak(&secp, merkle_root);
398+
ret.push((
399+
format!("tr({pk},0)"),
400+
ExpectedTree { internal_key, output_key, output_key_parity, merkle_root },
401+
vec![ExpectedLeaf {
402+
leaf_hash: zero_hash,
403+
branch: TaprootMerkleBranch::try_from(vec![]).unwrap(),
404+
}],
405+
));
406+
407+
// Two-leaf tree, repeated leaf
408+
let merkle_root = Some(
409+
"e3208df58f4fae78044357451c8830698300cd7da47cf41957d82ac4ce1dd170"
410+
.parse()
411+
.unwrap(),
412+
);
413+
let internal_key = pk.to_x_only_pubkey();
414+
let (output_key, output_key_parity) = internal_key.tap_tweak(&secp, merkle_root);
415+
ret.push((
416+
format!("tr({pk},{{0,0}})"),
417+
ExpectedTree { internal_key, output_key, output_key_parity, merkle_root },
418+
vec![
419+
ExpectedLeaf {
420+
leaf_hash: zero_hash,
421+
branch: TaprootMerkleBranch::try_from(vec![TapNodeHash::from(zero_hash)])
422+
.unwrap(),
423+
},
424+
ExpectedLeaf {
425+
leaf_hash: zero_hash,
426+
branch: TaprootMerkleBranch::try_from(vec![TapNodeHash::from(zero_hash)])
427+
.unwrap(),
428+
},
429+
],
430+
));
431+
432+
// Two-leaf tree, non-repeated leaf
433+
let merkle_root = Some(
434+
"15526cd6108b4765640abe555e75f4bd11d9b1453b9db4cd36cf4189577a6f63"
435+
.parse()
436+
.unwrap(),
437+
);
438+
let internal_key = pk.to_x_only_pubkey();
439+
let (output_key, output_key_parity) = internal_key.tap_tweak(&secp, merkle_root);
440+
ret.push((
441+
format!("tr({pk},{{0,1}})"),
442+
ExpectedTree { internal_key, output_key, output_key_parity, merkle_root },
443+
vec![
444+
ExpectedLeaf {
445+
leaf_hash: zero_hash,
446+
branch: TaprootMerkleBranch::try_from(vec![TapNodeHash::from(one_hash)])
447+
.unwrap(),
448+
},
449+
ExpectedLeaf {
450+
leaf_hash: one_hash,
451+
branch: TaprootMerkleBranch::try_from(vec![TapNodeHash::from(zero_hash)])
452+
.unwrap(),
453+
},
454+
],
455+
));
456+
457+
// Fuzz test vector 1
458+
let merkle_root = Some(
459+
"d281962c67932b82e19b0da5ea437af316213e24509be0ef1bd7c5ee2b460d79"
460+
.parse()
461+
.unwrap(),
462+
);
463+
let internal_key = pk.to_x_only_pubkey();
464+
let (output_key, output_key_parity) = internal_key.tap_tweak(&secp, merkle_root);
465+
466+
ret.push((
467+
format!("tr({pk},{{0,{{0,tv:0}}}})"),
468+
ExpectedTree { internal_key, output_key, output_key_parity, merkle_root },
469+
vec![
470+
ExpectedLeaf {
471+
leaf_hash: zero_hash,
472+
branch: TaprootMerkleBranch::try_from(vec![
473+
"573d619569d58a36b52187e56f168650ac17f66a9a3afaf054900a04001019b3"
474+
.parse::<TapNodeHash>()
475+
.unwrap(),
476+
])
477+
.unwrap(),
478+
},
479+
ExpectedLeaf {
480+
leaf_hash: zero_hash,
481+
branch: TaprootMerkleBranch::try_from(vec![
482+
"64ac241466a5e7032586718ff7465716f77a88d89946ce472daa4c3d0b81148f"
483+
.parse::<TapNodeHash>()
484+
.unwrap(),
485+
TapNodeHash::from(zero_hash),
486+
])
487+
.unwrap(),
488+
},
489+
ExpectedLeaf {
490+
leaf_hash: "64ac241466a5e7032586718ff7465716f77a88d89946ce472daa4c3d0b81148f"
491+
.parse()
492+
.unwrap(),
493+
branch: TaprootMerkleBranch::try_from(vec![
494+
TapNodeHash::from(zero_hash),
495+
TapNodeHash::from(zero_hash),
496+
])
497+
.unwrap(),
498+
},
499+
],
500+
));
501+
502+
// Fuzz test vector 2
503+
let merkle_root = Some(
504+
"2534e94c6ad06281b61fff86bad38a3911fb13436fb27fed6f5c057e4a71a911"
505+
.parse()
506+
.unwrap(),
507+
);
508+
let internal_key = pk.to_x_only_pubkey();
509+
let (output_key, output_key_parity) = internal_key.tap_tweak(&secp, merkle_root);
510+
511+
ret.push((
512+
format!("tr({pk},{{uuu:0,{{0,uu:0}}}})"),
513+
ExpectedTree { internal_key, output_key, output_key_parity, merkle_root },
514+
vec![
515+
ExpectedLeaf {
516+
leaf_hash: "6498e1d56640a272493d1d87549f3347dc448ca674556a2110cdfe100e3c238b"
517+
.parse()
518+
.unwrap(),
519+
branch: TaprootMerkleBranch::try_from(vec![
520+
"7e3e98bab404812c8eebd21c5d825527676b8e9f261f7ad479f3a08a83a43fb4"
521+
.parse::<TapNodeHash>()
522+
.unwrap(),
523+
])
524+
.unwrap(),
525+
},
526+
ExpectedLeaf {
527+
leaf_hash: zero_hash,
528+
branch: TaprootMerkleBranch::try_from(vec![
529+
"19417c32bc6ca7e0f6e65b006ac305107c6add73c8bef31181037e6faaa55e7f"
530+
.parse::<TapNodeHash>()
531+
.unwrap(),
532+
"6498e1d56640a272493d1d87549f3347dc448ca674556a2110cdfe100e3c238b"
533+
.parse::<TapNodeHash>()
534+
.unwrap(),
535+
])
536+
.unwrap(),
537+
},
538+
ExpectedLeaf {
539+
leaf_hash: "19417c32bc6ca7e0f6e65b006ac305107c6add73c8bef31181037e6faaa55e7f"
540+
.parse()
541+
.unwrap(),
542+
branch: TaprootMerkleBranch::try_from(vec![
543+
TapNodeHash::from(zero_hash),
544+
"6498e1d56640a272493d1d87549f3347dc448ca674556a2110cdfe100e3c238b"
545+
.parse::<TapNodeHash>()
546+
.unwrap(),
547+
])
548+
.unwrap(),
549+
},
550+
],
551+
));
552+
553+
// Fuzz test vector 3
554+
let merkle_root = Some(
555+
"9f4bc03c65a88ffbbb3a8d4fe5e01be608109d9f875f35685d8865e181def26e"
556+
.parse()
557+
.unwrap(),
558+
);
559+
let internal_key = pk.to_x_only_pubkey();
560+
let (output_key, output_key_parity) = internal_key.tap_tweak(&secp, merkle_root);
561+
562+
ret.push((
563+
format!("tr({pk},{{{{0,{{uuu:0,0}}}},{{0,uu:0}}}})"),
564+
ExpectedTree { internal_key, output_key, output_key_parity, merkle_root },
565+
vec![
566+
ExpectedLeaf {
567+
leaf_hash: zero_hash,
568+
branch: TaprootMerkleBranch::try_from(vec![
569+
"57e3b7d414075ff4864deec9efa99db4462c038706306e02c58e02e957c8a51e"
570+
.parse::<TapNodeHash>()
571+
.unwrap(),
572+
"7e3e98bab404812c8eebd21c5d825527676b8e9f261f7ad479f3a08a83a43fb4"
573+
.parse::<TapNodeHash>()
574+
.unwrap(),
575+
])
576+
.unwrap(),
577+
},
578+
ExpectedLeaf {
579+
leaf_hash: "6498e1d56640a272493d1d87549f3347dc448ca674556a2110cdfe100e3c238b"
580+
.parse()
581+
.unwrap(),
582+
branch: TaprootMerkleBranch::try_from(vec![
583+
TapNodeHash::from(zero_hash),
584+
TapNodeHash::from(zero_hash),
585+
"7e3e98bab404812c8eebd21c5d825527676b8e9f261f7ad479f3a08a83a43fb4"
586+
.parse::<TapNodeHash>()
587+
.unwrap(),
588+
])
589+
.unwrap(),
590+
},
591+
ExpectedLeaf {
592+
leaf_hash: zero_hash,
593+
branch: TaprootMerkleBranch::try_from(vec![
594+
"6498e1d56640a272493d1d87549f3347dc448ca674556a2110cdfe100e3c238b"
595+
.parse::<TapNodeHash>()
596+
.unwrap(),
597+
TapNodeHash::from(zero_hash),
598+
"7e3e98bab404812c8eebd21c5d825527676b8e9f261f7ad479f3a08a83a43fb4"
599+
.parse::<TapNodeHash>()
600+
.unwrap(),
601+
])
602+
.unwrap(),
603+
},
604+
ExpectedLeaf {
605+
leaf_hash: zero_hash,
606+
branch: TaprootMerkleBranch::try_from(vec![
607+
"19417c32bc6ca7e0f6e65b006ac305107c6add73c8bef31181037e6faaa55e7f"
608+
.parse::<TapNodeHash>()
609+
.unwrap(),
610+
"e034d7d8b221034861bf3893c63cb0ff60d28a7a00090d0dc57c26fec91983cb"
611+
.parse::<TapNodeHash>()
612+
.unwrap(),
613+
])
614+
.unwrap(),
615+
},
616+
ExpectedLeaf {
617+
leaf_hash: "19417c32bc6ca7e0f6e65b006ac305107c6add73c8bef31181037e6faaa55e7f"
618+
.parse()
619+
.unwrap(),
620+
branch: TaprootMerkleBranch::try_from(vec![
621+
TapNodeHash::from(zero_hash),
622+
"e034d7d8b221034861bf3893c63cb0ff60d28a7a00090d0dc57c26fec91983cb"
623+
.parse::<TapNodeHash>()
624+
.unwrap(),
625+
])
626+
.unwrap(),
627+
},
628+
],
629+
));
630+
631+
ret
632+
}
633+
634+
#[test]
635+
fn spend_info_fixed_vectors() {
636+
for (s, tree, leaves) in test_cases() {
637+
let tr = s
638+
.parse::<crate::descriptor::Tr<bitcoin::PublicKey>>()
639+
.unwrap();
640+
let spend_info = tr.spend_info();
641+
642+
assert_eq!(
643+
spend_info.internal_key(),
644+
tree.internal_key,
645+
"internal key mismatch (left: computed, right: expected)",
646+
);
647+
assert_eq!(
648+
spend_info.merkle_root(),
649+
tree.merkle_root,
650+
"merkle root mismatch (left: computed, right: expected)",
651+
);
652+
assert_eq!(
653+
spend_info.output_key(),
654+
tree.output_key,
655+
"output key mismatch (left: computed, right: expected)",
656+
);
657+
658+
let got_leaves: Vec<_> = spend_info
659+
.leaves()
660+
.map(|leaf| ExpectedLeaf {
661+
leaf_hash: leaf.leaf_hash(),
662+
branch: leaf.control_block().merkle_branch.clone(),
663+
})
664+
.collect();
665+
assert_eq!(got_leaves, leaves, "leaves mismatch (left: computed, right: expected)",);
666+
}
667+
}
668+
}

0 commit comments

Comments
 (0)