From 6c68c16f707c04df5d9100c670ebd42e56ec71e9 Mon Sep 17 00:00:00 2001 From: jbride Date: Mon, 16 Mar 2026 15:32:58 -0600 Subject: [PATCH] bip360: throw error when leaf version is non-standard ( 192 / c0 ) --- .../common/tests/data/p2mr_construction.json | 16 ++--------- bip-0360/ref-impl/rust/src/data_structures.rs | 3 +- bip-0360/ref-impl/rust/src/error.rs | 3 ++ .../ref-impl/rust/tests/p2mr_construction.rs | 28 +++++++++++++------ 4 files changed, 27 insertions(+), 23 deletions(-) diff --git a/bip-0360/ref-impl/common/tests/data/p2mr_construction.json b/bip-0360/ref-impl/common/tests/data/p2mr_construction.json index f6d76d07ca..061d76be8e 100644 --- a/bip-0360/ref-impl/common/tests/data/p2mr_construction.json +++ b/bip-0360/ref-impl/common/tests/data/p2mr_construction.json @@ -57,7 +57,7 @@ }, { "id": "p2mr_different_version_leaves", - "objective": "Tests P2MR with two script leaves of different versions. TO-DO: currently ignores given leaf version and over-rides. Probably better to throw error", + "objective": "Tests P2MR with two script leaves of different versions. BIP-360 requires all leaves to use leaf version 0xc0; a non-standard version must throw an error.", "given": { "scriptTree": [ { @@ -75,20 +75,8 @@ } ] }, - "intermediary": { - "leafHashes": [ - "8ad69ec7cf41c2a4001fd1f738bf1e505ce2277acdcaa63fe4765192497f47a7", - "f224a923cd0021ab202ab139cc56802ddb92dcfc172b9212261a539df79a112a" - ], - "merkleRoot": "6c2dc106ab816b73f9d07e3cd1ef2c8c1256f519748e0813e4edd2405d277bef" - }, "expected": { - "scriptPubKey": "52206c2dc106ab816b73f9d07e3cd1ef2c8c1256f519748e0813e4edd2405d277bef", - "bip350Address": "bc1zdskuzp4ts94h87ws0c7drmev3sf9dagewj8qsylyahfyqhf800hsam4d6e", - "scriptPathControlBlocks": [ - "c1f224a923cd0021ab202ab139cc56802ddb92dcfc172b9212261a539df79a112a", - "c18ad69ec7cf41c2a4001fd1f738bf1e505ce2277acdcaa63fe4765192497f47a7" - ] + "error": "BIP-360 requires leaf version 0xc0; leaf 1 has version 250" } }, { diff --git a/bip-0360/ref-impl/rust/src/data_structures.rs b/bip-0360/ref-impl/rust/src/data_structures.rs index 11f653aec7..e21b402e4d 100644 --- a/bip-0360/ref-impl/rust/src/data_structures.rs +++ b/bip-0360/ref-impl/rust/src/data_structures.rs @@ -111,6 +111,7 @@ pub struct TestVector { pub id: String, pub objective: String, pub given: TestVectorGiven, + #[serde(default)] pub intermediary: TestVectorIntermediary, pub expected: TestVectorExpected, } @@ -132,7 +133,7 @@ pub struct TestVectorGiven { pub control_block: Option, } -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Default, Serialize, Deserialize, Clone)] pub struct TestVectorIntermediary { #[serde(default)] diff --git a/bip-0360/ref-impl/rust/src/error.rs b/bip-0360/ref-impl/rust/src/error.rs index d2cb98b9e5..ceebf1a9c5 100644 --- a/bip-0360/ref-impl/rust/src/error.rs +++ b/bip-0360/ref-impl/rust/src/error.rs @@ -13,4 +13,7 @@ pub enum P2MRError { // We can add more specific error variants here as needed #[error("Invalid script tree structure: {0}")] InvalidScriptTree(String), + + #[error("BIP-360 requires leaf version 0xc0; leaf {0} has version {1}")] + InvalidLeafVersion(u8, u8), } \ No newline at end of file diff --git a/bip-0360/ref-impl/rust/tests/p2mr_construction.rs b/bip-0360/ref-impl/rust/tests/p2mr_construction.rs index 38d790d104..bd86ba21fd 100644 --- a/bip-0360/ref-impl/rust/tests/p2mr_construction.rs +++ b/bip-0360/ref-impl/rust/tests/p2mr_construction.rs @@ -1,7 +1,7 @@ use std::collections::HashSet; use bitcoin::{Network, ScriptBuf}; use bitcoin::taproot::{LeafVersion, TapTree, ScriptLeaves, TapLeafHash, TaprootMerkleBranch, TapNodeHash}; -use bitcoin::p2mr::{P2mrBuilder, P2mrControlBlock, P2mrSpendInfo}; +use bitcoin::p2mr::{P2mrBuilder, P2mrControlBlock, P2mrSpendInfo, P2MR_LEAF_VERSION}; use bitcoin::hashes::Hash; use hex; @@ -72,7 +72,9 @@ fn test_p2mr_different_version_leaves() { let test_vectors = &*TEST_VECTORS; let test_vector = test_vectors.test_vector_map.get(P2MR_DIFFERENT_VERSION_LEAVES_TEST).unwrap(); - process_test_vector_p2mr(test_vector).unwrap(); + let test_result = process_test_vector_p2mr(test_vector); + assert!(matches!(test_result.unwrap_err().downcast_ref::(), + Some(P2MRError::InvalidLeafVersion(_, _)))); } #[test] @@ -137,29 +139,37 @@ fn process_test_vector_p2mr(test_vector: &TestVector) -> anyhow::Result<()> { let mut p2mr_builder: P2mrBuilder = P2mrBuilder::new(); let mut control_block_data: Vec<(ScriptBuf, LeafVersion)> = Vec::new(); + let mut traversal_result: anyhow::Result<()> = Ok(()); // 1) traverse test vector script tree and add leaves to P2MR builder if let Some(script_tree) = tv_script_tree { script_tree.traverse_with_right_subtree_first(0, Direction::Root,&mut |node, depth, direction| { + if traversal_result.is_err() { return; } + if let TVScriptTree::Leaf(tv_leaf) = node { - + let tv_leaf_script_bytes = hex::decode(&tv_leaf.script).unwrap(); - + // NOTE: IOT to execute script_info.control_block(..), will add these to a vector let tv_leaf_script_buf = ScriptBuf::from_bytes(tv_leaf_script_bytes.clone()); let tv_leaf_version = LeafVersion::from_consensus(tv_leaf.leaf_version).unwrap(); + + if tv_leaf.leaf_version != P2MR_LEAF_VERSION { + traversal_result = Err(P2MRError::InvalidLeafVersion(tv_leaf.id, tv_leaf.leaf_version).into()); + return; + } + control_block_data.push((tv_leaf_script_buf.clone(), tv_leaf_version)); - + let mut modified_depth = depth + 1; if direction == Direction::Root { modified_depth = depth; } - debug!("traverse_with_depth: leaf_count: {}, depth: {}, modified_depth: {}, direction: {}, tv_leaf_script: {}", + debug!("traverse_with_depth: leaf_count: {}, depth: {}, modified_depth: {}, direction: {}, tv_leaf_script: {}", tv_leaf_count, depth, modified_depth, direction, tv_leaf.script); - - // NOTE: Some of the the test vectors in this project specify leaves with non-standard versions (ie: 250 / 0xfa) + p2mr_builder = p2mr_builder.clone().add_leaf_with_ver(depth, tv_leaf_script_buf.clone(), tv_leaf_version) .unwrap_or_else(|e| { panic!("Failed to add leaf: {:?}", e); @@ -177,6 +187,8 @@ fn process_test_vector_p2mr(test_vector: &TestVector) -> anyhow::Result<()> { return Err(P2MRError::MissingScriptTreeLeaf.into()); } + traversal_result?; + let spend_info: P2mrSpendInfo = p2mr_builder.clone() .finalize() .unwrap_or_else(|e| {