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 1fb08f07..dc5b0b85 100644 --- a/bip-0360/ref-impl/common/tests/data/p2mr_construction.json +++ b/bip-0360/ref-impl/common/tests/data/p2mr_construction.json @@ -19,7 +19,7 @@ } }, { - "id": "p2mr_missing_leaf_script_tree_error", + "id": "p2mr_null_or_missing_script_tree_error", "objective": "Tests P2MR with null or missing script tree", "given": { "script_tree": "" diff --git a/bip-0360/ref-impl/rust/examples/p2mr_construction.rs b/bip-0360/ref-impl/rust/examples/p2mr_construction.rs index 517ea619..7bcf9f49 100644 --- a/bip-0360/ref-impl/rust/examples/p2mr_construction.rs +++ b/bip-0360/ref-impl/rust/examples/p2mr_construction.rs @@ -1,7 +1,8 @@ +use log::{info}; + use p2mr_ref::{create_p2mr_utxo, create_p2mr_multi_leaf_taptree, tap_tree_lock_type}; use p2mr_ref::data_structures::{UtxoReturn, TaptreeReturn, ConstructionReturn, LeafScriptType}; -use std::env; -use log::{info, error}; + // Inspired by: https://learnmeabitcoin.com/technical/upgrades/taproot/#example-3-script-path-spend-signature fn main() -> ConstructionReturn { diff --git a/bip-0360/ref-impl/rust/examples/schnorr_example.rs b/bip-0360/ref-impl/rust/examples/schnorr_example.rs index 4723d99b..12762d69 100644 --- a/bip-0360/ref-impl/rust/examples/schnorr_example.rs +++ b/bip-0360/ref-impl/rust/examples/schnorr_example.rs @@ -1,4 +1,3 @@ -use std::env; use log::info; use once_cell::sync::Lazy; use bitcoin::key::{Secp256k1}; @@ -22,20 +21,20 @@ fn main() { let keypair = acquire_schnorr_keypair(); let (secret_key, public_key) = keypair.as_schnorr().unwrap(); let message_bytes = b"hello"; - + // secp256k1 operates on a 256-bit (32-byte) field, so inputs must be exactly this size // subsequently, Schnorr signatures on secp256k1 require exactly a 32-byte input (the curve's scalar field size) let message_hash: Hash = Hash::hash(message_bytes); let message: Message = Message::from_digest_slice(&message_hash.to_byte_array()).unwrap(); - - + + /* The secp256k1 library internally generates a random scalar value (aka: nonce or k-value) for each signature * Every signature is unique - even if you sign the same message with the same private key multiple times * The randomness is handled automatically by the secp256k1 implementation * You get different signatures each time for the same inputs * The nonce is only needed during signing, not during verification - + Schnorr signatures require randomness for security reasons: * Prevents private key recovery - If the same nonce is used twice, an attacker could potentially derive your private key * Ensures signature uniqueness - Each signature should be cryptographically distinct @@ -43,7 +42,7 @@ fn main() { */ let signature: bitcoin::secp256k1::schnorr::Signature = SECP.sign_schnorr(&message, &secret_key.keypair(&SECP)); info!("Signature created successfully, size: {}", signature.serialize().len()); - + //let pubkey = public_key; diff --git a/bip-0360/ref-impl/rust/examples/slh_dsa_verification_example.rs b/bip-0360/ref-impl/rust/examples/slh_dsa_verification_example.rs index c6c965e7..026de390 100644 --- a/bip-0360/ref-impl/rust/examples/slh_dsa_verification_example.rs +++ b/bip-0360/ref-impl/rust/examples/slh_dsa_verification_example.rs @@ -1,12 +1,6 @@ -use std::env; -use log::info; -use once_cell::sync::Lazy; -use bitcoin::hashes::{sha256::Hash, Hash as HashTrait}; use rand::{rng, RngCore}; -use bitcoinpqc::{ - generate_keypair, public_key_size, secret_key_size, sign, signature_size, verify, Algorithm, KeyPair, -}; +use bitcoinpqc::{generate_keypair, sign, verify, Algorithm, KeyPair}; fn main() { let _ = env_logger::try_init(); diff --git a/bip-0360/ref-impl/rust/src/bin/slh_dsa_key_gen.rs b/bip-0360/ref-impl/rust/src/bin/slh_dsa_key_gen.rs index 999d487d..fb184357 100644 --- a/bip-0360/ref-impl/rust/src/bin/slh_dsa_key_gen.rs +++ b/bip-0360/ref-impl/rust/src/bin/slh_dsa_key_gen.rs @@ -1,4 +1,3 @@ -use std::env; use log::info; use rand::{rng, RngCore}; diff --git a/bip-0360/ref-impl/rust/src/data_structures.rs b/bip-0360/ref-impl/rust/src/data_structures.rs index 11f653ae..fe08abd3 100644 --- a/bip-0360/ref-impl/rust/src/data_structures.rs +++ b/bip-0360/ref-impl/rust/src/data_structures.rs @@ -4,14 +4,14 @@ use log::debug; // Add imports for the unified keypair use bitcoin::secp256k1::{SecretKey, XOnlyPublicKey}; -use bitcoinpqc::{KeyPair, Algorithm}; +use bitcoinpqc::{KeyPair}; /// Enum representing the type of leaf script to create #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum LeafScriptType { /// Script requires only SLH-DSA signature SlhDsaOnly, - /// Script requires only Schnorr signature + /// Script requires only Schnorr signature SchnorrOnly, /// Script requires both Schnorr and SLH-DSA signatures (in that order) ConcatenatedSchnorrAndSlhDsaSameLeaf, @@ -92,7 +92,7 @@ impl<'de> Deserialize<'de> for TestVectors { } let helper = Helper::deserialize(deserializer)?; - + let mut test_vector_map = HashMap::new(); for test in helper.test_vectors.iter() { test_vector_map.insert(test.id.clone(), test.clone()); diff --git a/bip-0360/ref-impl/rust/src/lib.rs b/bip-0360/ref-impl/rust/src/lib.rs index e9c9816d..d3476b6a 100644 --- a/bip-0360/ref-impl/rust/src/lib.rs +++ b/bip-0360/ref-impl/rust/src/lib.rs @@ -13,18 +13,16 @@ use bitcoin::{ Amount, TxOut, WPubkeyHash, Address, Network, OutPoint, blockdata::witness::Witness, Script, ScriptBuf, XOnlyPublicKey, PublicKey, - sighash::{SighashCache, TapSighashType, Prevouts, TapSighash}, + sighash::{SighashCache, TapSighashType, Prevouts, TapSighash}, taproot::{LeafVersion, NodeInfo, TapLeafHash, TapNodeHash, TapTree, ScriptLeaves, TaprootMerkleBranch, TaprootBuilder, TaprootSpendInfo, ControlBlock}, transaction::{Transaction, Sequence} }; use bitcoin::p2mr::{P2mrScriptBuf, P2mrBuilder, P2mrSpendInfo, P2mrControlBlock, P2MR_LEAF_VERSION}; -use bitcoinpqc::{ - generate_keypair, public_key_size, secret_key_size, Algorithm, KeyPair, sign, verify, -}; +use bitcoinpqc::{generate_keypair, Algorithm, KeyPair, sign, verify}; -use data_structures::{SpendDetails, UtxoReturn, TaptreeReturn, UnifiedKeypair, MultiKeypair, LeafScriptType, MixedLeafInfo}; +use data_structures::{SpendDetails, UtxoReturn, TaptreeReturn, UnifiedKeypair, MultiKeypair, LeafScriptType}; /* Secp256k1 implements the Signing trait when it's initialized in signing mode. It's important to note that Secp256k1 has different capabilities depending on how it's constructed: @@ -325,7 +323,7 @@ pub fn get_bitcoin_network() -> Network { } }; } - + bitcoin_network } @@ -333,7 +331,7 @@ pub fn create_p2mr_utxo(merkle_root_hex: String) -> UtxoReturn { let merkle_root_bytes= hex::decode(merkle_root_hex.clone()).unwrap(); let merkle_root: TapNodeHash = TapNodeHash::from_byte_array(merkle_root_bytes.try_into().unwrap()); - + /* commit (in scriptPubKey) to the merkle root of all the script path leaves. ie: This output key is what gets committed to in the final P2MR address (ie: scriptPubKey) */ @@ -342,7 +340,7 @@ pub fn create_p2mr_utxo(merkle_root_hex: String) -> UtxoReturn { let script_pubkey = script.to_hex_string(); let bitcoin_network = get_bitcoin_network(); - + // derive bech32m address and verify against test vector // p2mr address is comprised of network HRP + WitnessProgram (version + program) let bech32m_address = Address::p2mr(Some(merkle_root), bitcoin_network); @@ -452,10 +450,10 @@ pub fn pay_to_p2wpkh_tx( } // assumes bytes are in big endian format let secret_key = SecretKey::from_slice(&leaf_script_priv_keys_bytes[0]).unwrap(); - + // Spending a p2tr UTXO thus using Schnorr signature // The aux_rand parameter ensures that signing the same message with the same key produces the same signature - // Otherwise (without providing aux_rand), the secp256k1 library internally generates a random nonce for each signature + // Otherwise (without providing aux_rand), the secp256k1 library internally generates a random nonce for each signature let signature: bitcoin::secp256k1::schnorr::Signature = SECP.sign_schnorr_with_aux_rand( &spend_msg, &secret_key.keypair(&SECP), @@ -471,7 +469,7 @@ pub fn pay_to_p2wpkh_tx( if leaf_script_priv_keys_bytes.len() != 2 { panic!("SchnorrAndSlhDsa requires exactly two private keys (Schnorr first, then SLH-DSA)"); } - + // Generate Schnorr signature (first key) let schnorr_secret_key = SecretKey::from_slice(&leaf_script_priv_keys_bytes[0]).unwrap(); let schnorr_signature: bitcoin::secp256k1::schnorr::Signature = SECP.sign_schnorr_with_aux_rand( @@ -482,21 +480,21 @@ pub fn pay_to_p2wpkh_tx( // Build combined signature for return value (without sighash bytes) let mut combined_sig_bytes = schnorr_signature.serialize().to_vec(); debug!("SchnorrAndSlhDsa schnorr_sig_bytes: {:?}", combined_sig_bytes.len()); - + // Generate SLH-DSA signature (second key) let slh_dsa_secret_key: bitcoinpqc::SecretKey = bitcoinpqc::SecretKey::try_from_slice( Algorithm::SLH_DSA_128S, &leaf_script_priv_keys_bytes[1]).unwrap(); - + // Debug: Print the private key being used for signature creation info!("SLH-DSA DEBUG: Using private key for signature creation: {}", hex::encode(&leaf_script_priv_keys_bytes[1])); - + let slh_dsa_signature = sign(&slh_dsa_secret_key, spend_msg.as_ref()).expect("Failed to sign with SLH-DSA-128S"); debug!("SchnorrAndSlhDsa slh_dsa_signature.bytes: {:?}", slh_dsa_signature.bytes.len()); - + // Add SLH-DSA signature to combined signature for return value combined_sig_bytes.extend_from_slice(&slh_dsa_signature.bytes); sig_bytes = combined_sig_bytes; - + // Build witness with sighash bytes let mut witness_sig_bytes = schnorr_signature.serialize().to_vec(); witness_sig_bytes.push(TapSighashType::All as u8); @@ -543,7 +541,7 @@ pub fn create_p2tr_utxo(merkle_root_hex: String, internal_pubkey_hex: String) -> let pub_key_string = format!("02{}", internal_pubkey_hex); let internal_pubkey: PublicKey = pub_key_string.parse::().unwrap(); let internal_xonly_pubkey: XOnlyPublicKey = internal_pubkey.inner.into(); - + let script_buf: ScriptBuf = ScriptBuf::new_p2tr(&SECP, internal_xonly_pubkey, Option::Some(merkle_root)); let script: &Script = script_buf.as_script(); @@ -629,7 +627,7 @@ pub fn acquire_schnorr_keypair() -> UnifiedKeypair { * Interrupt timing */ let keypair = Keypair::new(&SECP, &mut OsRng); - + let privkey: SecretKey = keypair.secret_key(); let pubkey: (XOnlyPublicKey, Parity) = XOnlyPublicKey::from_keypair(&keypair); UnifiedKeypair::new_schnorr(privkey, pubkey.0) @@ -649,16 +647,16 @@ pub fn verify_schnorr_signature_via_bytes(signature: &[u8], message: &[u8], pubk } pub fn verify_slh_dsa_via_bytes(signature: &[u8], message: &[u8], pubkey_bytes: &[u8]) -> bool { - + // Remove possible trailing Sighash Type byte if present (SLH-DSA-128S is 7856 bytes, so 7857 would indicate SIGHASH byte) let mut sig_bytes = signature.to_vec(); if sig_bytes.len() == 7857 { sig_bytes.pop(); // Remove the last byte } - - info!("verify_slh_dsa_via_bytes: signature length: {:?}, message: {:?}, pubkey_bytes: {:?}", - sig_bytes.len(), - hex::encode(message), + + info!("verify_slh_dsa_via_bytes: signature length: {:?}, message: {:?}, pubkey_bytes: {:?}", + sig_bytes.len(), + hex::encode(message), hex::encode(pubkey_bytes)); let signature = bitcoinpqc::Signature::try_from_slice(Algorithm::SLH_DSA_128S, &sig_bytes).unwrap(); @@ -676,9 +674,9 @@ pub fn verify_schnorr_signature(mut signature: Signature, message: Message, pubk } let is_valid: bool = SECP.verify_schnorr(&signature, &message, &pubkey).is_ok(); if !is_valid { - error!("verify schnorr failed:\n\tsignature: {:?}\n\tmessage: {:?}\n\tpubkey: {:?}", - signature, - message, + error!("verify schnorr failed:\n\tsignature: {:?}\n\tmessage: {:?}\n\tpubkey: {:?}", + signature, + message, hex::encode(pubkey.serialize())); } is_valid diff --git a/bip-0360/ref-impl/rust/tests/p2mr_construction.rs b/bip-0360/ref-impl/rust/tests/p2mr_construction.rs index d0afc21b..fabf858a 100644 --- a/bip-0360/ref-impl/rust/tests/p2mr_construction.rs +++ b/bip-0360/ref-impl/rust/tests/p2mr_construction.rs @@ -1,16 +1,15 @@ -use std::collections::HashMap; -use bitcoin::{Network, ScriptBuf}; -use bitcoin::taproot::{LeafVersion, TapTree, ScriptLeaves, TapLeafHash, TaprootMerkleBranch, TapNodeHash}; -use bitcoin::p2mr::{P2mrBuilder, P2mrControlBlock, P2mrSpendInfo}; -use bitcoin::hashes::Hash; - use hex; use log::debug; use once_cell::sync::Lazy; +use bitcoin::{Network, ScriptBuf}; +use bitcoin::hashes::Hash; +use bitcoin::p2mr::{P2mrBuilder, P2mrSpendInfo}; +use bitcoin::taproot::{LeafVersion, TapTree, ScriptLeaves, TapLeafHash, TaprootMerkleBranch, TapNodeHash}; + use p2mr_ref::data_structures::{TVScriptTree, TestVector, Direction, TestVectors, UtxoReturn}; use p2mr_ref::error::P2MRError; -use p2mr_ref::{create_p2mr_utxo, tagged_hash}; +use p2mr_ref::{create_p2mr_utxo}; // This file contains tests that execute against the BIP360 script-path-only test vectors. @@ -21,48 +20,49 @@ static TEST_VECTORS: Lazy = Lazy::new(|| { test_vectors }); -static P2TR_USING_V2_WITNESS_VERSION_ERROR: &str = "p2tr_using_v2_witness_version_error"; -static P2MR_MISSING_LEAF_SCRIPT_TREE_ERROR_TEST: &str = "p2mr_missing_leaf_script_tree_error"; -static P2MR_SINGLE_LEAF_SCRIPT_TREE_TEST: &str = "p2mr_single_leaf_script_tree"; -static P2MR_DIFFERENT_VERSION_LEAVES_TEST: &str = "p2mr_different_version_leaves"; -static P2MR_TWO_LEAF_SAME_VERSION_TEST: &str = "p2mr_two_leaf_same_version"; -static P2MR_THREE_LEAF_COMPLEX_TEST: &str = "p2mr_three_leaf_complex"; -static P2MR_THREE_LEAF_ALTERNATIVE_TEST: &str = "p2mr_three_leaf_alternative"; -static P2MR_SIMPLE_LIGHTNING_CONTRACT_TEST: &str = "p2mr_simple_lightning_contract"; +// Helper to run a P2MR error test vector +fn assert_p2mr_error(id: &str, process: F, expected: P2MRError) +where + F: FnOnce(&TestVector) -> anyhow::Result<()>, +{ + let _ = env_logger::try_init(); + let tv = TEST_VECTORS.test_vector_map.get(id).unwrap(); + let err = process(tv).unwrap_err(); + assert!(matches!(err.downcast_ref::(), Some(e) if std::mem::discriminant(e) == std::mem::discriminant(&expected))); +} +// Helper to run a P2MR positive test vector by its ID. +fn run_p2mr_test(id: &str) { + let _ = env_logger::try_init(); // Use try_init to avoid reinitialization error + let test_vector = TEST_VECTORS.test_vector_map.get(id).unwrap(); + process_test_vector_p2mr(test_vector).unwrap(); +} + +// Error Tests #[test] fn test_p2tr_using_v2_witness_version_error() { - - let _ = env_logger::try_init(); // Use try_init to avoid reinitialization error - - let test_vectors = &*TEST_VECTORS; - let test_vector = test_vectors.test_vector_map.get(P2TR_USING_V2_WITNESS_VERSION_ERROR).unwrap(); - let test_result: anyhow::Result<()> = process_test_vector_p2tr(test_vector); - assert!(matches!(test_result.unwrap_err().downcast_ref::(), - Some(P2MRError::P2trRequiresWitnessVersion1))); + assert_p2mr_error( + "p2mr_misuse_v2_witness_version_with_pubkey_error", + process_test_vector_p2tr, + P2MRError::P2trRequiresWitnessVersion1, + ); } // https://learnmeabitcoin.com/technical/upgrades/taproot/#example-2-script-path-spend-simple #[test] fn test_p2mr_missing_leaf_script_tree_error() { - - let _ = env_logger::try_init(); // Use try_init to avoid reinitialization error - - let test_vectors = &*TEST_VECTORS; - let test_vector = test_vectors.test_vector_map.get(P2MR_MISSING_LEAF_SCRIPT_TREE_ERROR_TEST).unwrap(); - let test_result: anyhow::Result<()> = process_test_vector_p2mr(test_vector); - assert!(matches!(test_result.unwrap_err().downcast_ref::(), - Some(P2MRError::MissingScriptTreeLeaf))); + assert_p2mr_error( + "p2mr_null_or_missing_script_tree_error", + process_test_vector_p2mr, + P2MRError::MissingScriptTreeLeaf, + ); } +// Positive Tests // https://learnmeabitcoin.com/technical/upgrades/taproot/#example-2-script-path-spend-simple #[test] fn test_p2mr_single_leaf_script_tree() { - let _ = env_logger::try_init(); // Use try_init to avoid reinitialization error - - let test_vectors = &*TEST_VECTORS; - let test_vector = test_vectors.test_vector_map.get(P2MR_SINGLE_LEAF_SCRIPT_TREE_TEST).unwrap(); - process_test_vector_p2mr(test_vector).unwrap(); + run_p2mr_test("p2mr_single_leaf_script_tree"); } /// Verifies that P2MR construction succeeds when leaves carry non-standard leaf versions (e.g. 0xfa). @@ -70,52 +70,32 @@ fn test_p2mr_single_leaf_script_tree() { /// and the resulting merkle root and control blocks are valid. #[test] fn test_p2mr_different_version_leaves() { - - let _ = env_logger::try_init(); // Use try_init to avoid reinitialization error - - 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(); + run_p2mr_test("p2mr_different_version_leaves"); } #[test] fn test_p2mr_simple_lightning_contract() { - - let _ = env_logger::try_init(); // Use try_init to avoid reinitialization error - - let test_vectors = &*TEST_VECTORS; - let test_vector = test_vectors.test_vector_map.get(P2MR_SIMPLE_LIGHTNING_CONTRACT_TEST).unwrap(); - process_test_vector_p2mr(test_vector).unwrap(); + run_p2mr_test("p2mr_simple_lightning_contract") } #[test] fn test_p2mr_two_leaf_same_version() { - - let _ = env_logger::try_init(); // Use try_init to avoid reinitialization error - - let test_vectors = &*TEST_VECTORS; - let test_vector = test_vectors.test_vector_map.get(P2MR_TWO_LEAF_SAME_VERSION_TEST).unwrap(); - process_test_vector_p2mr(test_vector).unwrap(); + run_p2mr_test("p2mr_two_leaf_same_version"); } #[test] fn test_p2mr_three_leaf_complex() { - - let _ = env_logger::try_init(); // Use try_init to avoid reinitialization error - - let test_vectors = &*TEST_VECTORS; - let test_vector = test_vectors.test_vector_map.get(P2MR_THREE_LEAF_COMPLEX_TEST).unwrap(); - process_test_vector_p2mr(test_vector).unwrap(); + run_p2mr_test("p2mr_three_leaf_complex"); } #[test] fn test_p2mr_three_leaf_alternative() { + run_p2mr_test("p2mr_three_leaf_alternative"); +} - let _ = env_logger::try_init(); // Use try_init to avoid reinitialization error - - let test_vectors = &*TEST_VECTORS; - let test_vector = test_vectors.test_vector_map.get(P2MR_THREE_LEAF_ALTERNATIVE_TEST).unwrap(); - process_test_vector_p2mr(test_vector).unwrap(); +#[test] +fn test_p2mr_duplicate_leaves() { + run_p2mr_test("p2mr_duplicate_leaves"); } fn process_test_vector_p2tr(test_vector: &TestVector) -> anyhow::Result<()> { @@ -139,43 +119,37 @@ fn process_test_vector_p2mr(test_vector: &TestVector) -> anyhow::Result<()> { // Use of TaprootBuilder avoids user error in constructing branches manually and ensures Merkle tree correctness and determinism let mut p2mr_builder: P2mrBuilder = P2mrBuilder::new(); - let mut script_to_id: HashMap = HashMap::new(); - // 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| { + script_tree.traverse_with_right_subtree_first(0, Direction::Root, &mut |node, depth, direction| { if let TVScriptTree::Leaf(tv_leaf) = node { let tv_leaf_script_bytes = hex::decode(&tv_leaf.script).unwrap(); - 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(); - script_to_id.insert(tv_leaf_script_buf.clone(), tv_leaf.id); - + 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) + + // NOTE: Some of 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); }); - + tv_leaf_count += 1; } else if let TVScriptTree::Branch { left, right } = node { - // No need to calculate branch hash. - // TaprootBuilder does this for us. debug!("branch_count: {}, depth: {}, direction: {}", current_branch_id, depth, direction); current_branch_id += 1; } }); - }else { + } else { return Err(P2MRError::MissingScriptTreeLeaf.into()); } @@ -189,9 +163,9 @@ fn process_test_vector_p2mr(test_vector: &TestVector) -> anyhow::Result<()> { // 2) verify derived merkle root against test vector let test_vector_merkle_root = test_vector.intermediary.merkle_root.as_ref().unwrap(); - assert_eq!( + assert_eq!( derived_merkle_root.to_string(), - *test_vector_merkle_root, + *test_vector_merkle_root, "Merkle root mismatch" ); debug!("just passed merkle root validation: {}", test_vector_merkle_root); @@ -200,7 +174,7 @@ fn process_test_vector_p2mr(test_vector: &TestVector) -> anyhow::Result<()> { let tap_tree: TapTree = p2mr_builder.clone().into_inner().try_into_taptree().unwrap(); let script_leaves: ScriptLeaves = tap_tree.script_leaves(); - // 3) Iterate through leaves of derived script tree and verify both script leaf hashes and control blocks + // 3) Iterate through leaves of derived script tree and verify control blocks for derived_leaf in script_leaves { let version = derived_leaf.version(); @@ -210,9 +184,6 @@ fn process_test_vector_p2mr(test_vector: &TestVector) -> anyhow::Result<()> { let derived_leaf_hash: TapLeafHash = TapLeafHash::from_script(script, version); let leaf_hash = hex::encode(derived_leaf_hash.as_raw_hash().to_byte_array()); - let leaf_id = script_to_id.get(script) - .unwrap_or_else(|| panic!("leaf script not found in script_to_id map: {}", hex::encode(script.as_bytes()))); - // BIP341 control byte layout: bits 7..1 = leaf_version, bit 0 = parity. // `& 0xfe` (11111110) masks off bit 0, isolating the leaf version in the upper 7 bits. // `| 0x01` sets bit 0 to 1: P2MR has no key-spend path, so parity is always 1. @@ -223,18 +194,17 @@ fn process_test_vector_p2mr(test_vector: &TestVector) -> anyhow::Result<()> { .expect("encode should not fail"); let derived_serialized_control_block = hex::encode(&cb_buf); - let expected_cb = &expected_control_blocks[*leaf_id as usize]; - assert_eq!( - derived_serialized_control_block, *expected_cb, - "Control block mismatch for leaf id {}: derived {}, expected {}", leaf_id, derived_serialized_control_block, expected_cb + assert!( + expected_control_blocks.contains(&derived_serialized_control_block), + "Unexpected control block: {}", derived_serialized_control_block ); - debug!("leaf_id: {}, leaf_hash: {}, derived_serialized_control_block: {}", leaf_id, leaf_hash, derived_serialized_control_block); + debug!("leaf_hash: {}, derived_serialized_control_block: {}", leaf_hash, derived_serialized_control_block); } let p2mr_utxo_return: UtxoReturn = create_p2mr_utxo(derived_merkle_root.to_string()); - assert_eq!( + assert_eq!( p2mr_utxo_return.script_pubkey_hex, *test_vector.expected.script_pubkey.as_ref().unwrap(), "Script pubkey mismatch" diff --git a/bip-0360/ref-impl/rust/tests/p2mr_pqc_construction.rs b/bip-0360/ref-impl/rust/tests/p2mr_pqc_construction.rs index 22f13ae4..fbc5ad85 100644 --- a/bip-0360/ref-impl/rust/tests/p2mr_pqc_construction.rs +++ b/bip-0360/ref-impl/rust/tests/p2mr_pqc_construction.rs @@ -10,7 +10,7 @@ use once_cell::sync::Lazy; use p2mr_ref::data_structures::{TVScriptTree, TestVector, Direction, TestVectors, UtxoReturn}; use p2mr_ref::error::P2MRError; -use p2mr_ref::{create_p2mr_utxo, tagged_hash}; +use p2mr_ref::{create_p2mr_utxo}; // This file contains tests that execute against the BIP360 script-path-only test vectors. @@ -131,20 +131,20 @@ fn process_test_vector_p2mr(test_vector: &TestVector) -> anyhow::Result<()> { 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(); script_to_id.insert(tv_leaf_script_buf.clone(), tv_leaf.id); - + 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); }); - + tv_leaf_count += 1; } else if let TVScriptTree::Branch { left, right } = node { // No need to calculate branch hash. @@ -167,9 +167,9 @@ fn process_test_vector_p2mr(test_vector: &TestVector) -> anyhow::Result<()> { // 2) verify derived merkle root against test vector let test_vector_merkle_root = test_vector.intermediary.merkle_root.as_ref().unwrap(); - assert_eq!( + assert_eq!( derived_merkle_root.to_string(), - *test_vector_merkle_root, + *test_vector_merkle_root, "Merkle root mismatch" ); debug!("just passed merkle root validation: {}", test_vector_merkle_root); @@ -207,7 +207,7 @@ fn process_test_vector_p2mr(test_vector: &TestVector) -> anyhow::Result<()> { let p2mr_utxo_return: UtxoReturn = create_p2mr_utxo(derived_merkle_root.to_string()); - assert_eq!( + assert_eq!( p2mr_utxo_return.script_pubkey_hex, *test_vector.expected.script_pubkey.as_ref().unwrap(), "Script pubkey mismatch"