1
0
mirror of https://github.com/bitcoin/bips.git synced 2026-08-24 18:47:19 +00:00

bip360: cleanup rust tests a bit and remove unused imports

This commit is contained in:
notmike
2026-06-28 20:33:31 -06:00
parent 75167ec077
commit 3e3e4347e2
9 changed files with 105 additions and 144 deletions

View File

@@ -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", "objective": "Tests P2MR with null or missing script tree",
"given": { "given": {
"script_tree": "" "script_tree": ""

View File

@@ -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::{create_p2mr_utxo, create_p2mr_multi_leaf_taptree, tap_tree_lock_type};
use p2mr_ref::data_structures::{UtxoReturn, TaptreeReturn, ConstructionReturn, LeafScriptType}; 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 // Inspired by: https://learnmeabitcoin.com/technical/upgrades/taproot/#example-3-script-path-spend-signature
fn main() -> ConstructionReturn { fn main() -> ConstructionReturn {

View File

@@ -1,4 +1,3 @@
use std::env;
use log::info; use log::info;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use bitcoin::key::{Secp256k1}; use bitcoin::key::{Secp256k1};
@@ -22,20 +21,20 @@ fn main() {
let keypair = acquire_schnorr_keypair(); let keypair = acquire_schnorr_keypair();
let (secret_key, public_key) = keypair.as_schnorr().unwrap(); let (secret_key, public_key) = keypair.as_schnorr().unwrap();
let message_bytes = b"hello"; let message_bytes = b"hello";
// secp256k1 operates on a 256-bit (32-byte) field, so inputs must be exactly this size // 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) // 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_hash: Hash = Hash::hash(message_bytes);
let message: Message = Message::from_digest_slice(&message_hash.to_byte_array()).unwrap(); 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 /* 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 * 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 * The randomness is handled automatically by the secp256k1 implementation
* You get different signatures each time for the same inputs * You get different signatures each time for the same inputs
* The nonce is only needed during signing, not during verification * The nonce is only needed during signing, not during verification
Schnorr signatures require randomness for security reasons: 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 * 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 * 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)); let signature: bitcoin::secp256k1::schnorr::Signature = SECP.sign_schnorr(&message, &secret_key.keypair(&SECP));
info!("Signature created successfully, size: {}", signature.serialize().len()); info!("Signature created successfully, size: {}", signature.serialize().len());
//let pubkey = public_key; //let pubkey = public_key;

View File

@@ -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 rand::{rng, RngCore};
use bitcoinpqc::{ use bitcoinpqc::{generate_keypair, sign, verify, Algorithm, KeyPair};
generate_keypair, public_key_size, secret_key_size, sign, signature_size, verify, Algorithm, KeyPair,
};
fn main() { fn main() {
let _ = env_logger::try_init(); let _ = env_logger::try_init();

View File

@@ -1,4 +1,3 @@
use std::env;
use log::info; use log::info;
use rand::{rng, RngCore}; use rand::{rng, RngCore};

View File

@@ -4,14 +4,14 @@ use log::debug;
// Add imports for the unified keypair // Add imports for the unified keypair
use bitcoin::secp256k1::{SecretKey, XOnlyPublicKey}; use bitcoin::secp256k1::{SecretKey, XOnlyPublicKey};
use bitcoinpqc::{KeyPair, Algorithm}; use bitcoinpqc::{KeyPair};
/// Enum representing the type of leaf script to create /// Enum representing the type of leaf script to create
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LeafScriptType { pub enum LeafScriptType {
/// Script requires only SLH-DSA signature /// Script requires only SLH-DSA signature
SlhDsaOnly, SlhDsaOnly,
/// Script requires only Schnorr signature /// Script requires only Schnorr signature
SchnorrOnly, SchnorrOnly,
/// Script requires both Schnorr and SLH-DSA signatures (in that order) /// Script requires both Schnorr and SLH-DSA signatures (in that order)
ConcatenatedSchnorrAndSlhDsaSameLeaf, ConcatenatedSchnorrAndSlhDsaSameLeaf,
@@ -92,7 +92,7 @@ impl<'de> Deserialize<'de> for TestVectors {
} }
let helper = Helper::deserialize(deserializer)?; let helper = Helper::deserialize(deserializer)?;
let mut test_vector_map = HashMap::new(); let mut test_vector_map = HashMap::new();
for test in helper.test_vectors.iter() { for test in helper.test_vectors.iter() {
test_vector_map.insert(test.id.clone(), test.clone()); test_vector_map.insert(test.id.clone(), test.clone());

View File

@@ -13,18 +13,16 @@ use bitcoin::{ Amount, TxOut, WPubkeyHash,
Address, Network, OutPoint, Address, Network, OutPoint,
blockdata::witness::Witness, blockdata::witness::Witness,
Script, ScriptBuf, XOnlyPublicKey, PublicKey, 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}, taproot::{LeafVersion, NodeInfo, TapLeafHash, TapNodeHash, TapTree, ScriptLeaves, TaprootMerkleBranch, TaprootBuilder, TaprootSpendInfo, ControlBlock},
transaction::{Transaction, Sequence} transaction::{Transaction, Sequence}
}; };
use bitcoin::p2mr::{P2mrScriptBuf, P2mrBuilder, P2mrSpendInfo, P2mrControlBlock, P2MR_LEAF_VERSION}; use bitcoin::p2mr::{P2mrScriptBuf, P2mrBuilder, P2mrSpendInfo, P2mrControlBlock, P2MR_LEAF_VERSION};
use bitcoinpqc::{ use bitcoinpqc::{generate_keypair, Algorithm, KeyPair, sign, verify};
generate_keypair, public_key_size, secret_key_size, 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. /* 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: 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 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_bytes= hex::decode(merkle_root_hex.clone()).unwrap();
let merkle_root: TapNodeHash = TapNodeHash::from_byte_array(merkle_root_bytes.try_into().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: /* 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) 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 script_pubkey = script.to_hex_string();
let bitcoin_network = get_bitcoin_network(); let bitcoin_network = get_bitcoin_network();
// derive bech32m address and verify against test vector // derive bech32m address and verify against test vector
// p2mr address is comprised of network HRP + WitnessProgram (version + program) // p2mr address is comprised of network HRP + WitnessProgram (version + program)
let bech32m_address = Address::p2mr(Some(merkle_root), bitcoin_network); 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 // assumes bytes are in big endian format
let secret_key = SecretKey::from_slice(&leaf_script_priv_keys_bytes[0]).unwrap(); let secret_key = SecretKey::from_slice(&leaf_script_priv_keys_bytes[0]).unwrap();
// Spending a p2tr UTXO thus using Schnorr signature // 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 // 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( let signature: bitcoin::secp256k1::schnorr::Signature = SECP.sign_schnorr_with_aux_rand(
&spend_msg, &spend_msg,
&secret_key.keypair(&SECP), &secret_key.keypair(&SECP),
@@ -471,7 +469,7 @@ pub fn pay_to_p2wpkh_tx(
if leaf_script_priv_keys_bytes.len() != 2 { if leaf_script_priv_keys_bytes.len() != 2 {
panic!("SchnorrAndSlhDsa requires exactly two private keys (Schnorr first, then SLH-DSA)"); panic!("SchnorrAndSlhDsa requires exactly two private keys (Schnorr first, then SLH-DSA)");
} }
// Generate Schnorr signature (first key) // Generate Schnorr signature (first key)
let schnorr_secret_key = SecretKey::from_slice(&leaf_script_priv_keys_bytes[0]).unwrap(); 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( 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) // Build combined signature for return value (without sighash bytes)
let mut combined_sig_bytes = schnorr_signature.serialize().to_vec(); let mut combined_sig_bytes = schnorr_signature.serialize().to_vec();
debug!("SchnorrAndSlhDsa schnorr_sig_bytes: {:?}", combined_sig_bytes.len()); debug!("SchnorrAndSlhDsa schnorr_sig_bytes: {:?}", combined_sig_bytes.len());
// Generate SLH-DSA signature (second key) // Generate SLH-DSA signature (second key)
let slh_dsa_secret_key: bitcoinpqc::SecretKey = bitcoinpqc::SecretKey::try_from_slice( let slh_dsa_secret_key: bitcoinpqc::SecretKey = bitcoinpqc::SecretKey::try_from_slice(
Algorithm::SLH_DSA_128S, &leaf_script_priv_keys_bytes[1]).unwrap(); Algorithm::SLH_DSA_128S, &leaf_script_priv_keys_bytes[1]).unwrap();
// Debug: Print the private key being used for signature creation // 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])); 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"); 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()); debug!("SchnorrAndSlhDsa slh_dsa_signature.bytes: {:?}", slh_dsa_signature.bytes.len());
// Add SLH-DSA signature to combined signature for return value // Add SLH-DSA signature to combined signature for return value
combined_sig_bytes.extend_from_slice(&slh_dsa_signature.bytes); combined_sig_bytes.extend_from_slice(&slh_dsa_signature.bytes);
sig_bytes = combined_sig_bytes; sig_bytes = combined_sig_bytes;
// Build witness with sighash bytes // Build witness with sighash bytes
let mut witness_sig_bytes = schnorr_signature.serialize().to_vec(); let mut witness_sig_bytes = schnorr_signature.serialize().to_vec();
witness_sig_bytes.push(TapSighashType::All as u8); 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 pub_key_string = format!("02{}", internal_pubkey_hex);
let internal_pubkey: PublicKey = pub_key_string.parse::<PublicKey>().unwrap(); let internal_pubkey: PublicKey = pub_key_string.parse::<PublicKey>().unwrap();
let internal_xonly_pubkey: XOnlyPublicKey = internal_pubkey.inner.into(); 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_buf: ScriptBuf = ScriptBuf::new_p2tr(&SECP, internal_xonly_pubkey, Option::Some(merkle_root));
let script: &Script = script_buf.as_script(); let script: &Script = script_buf.as_script();
@@ -629,7 +627,7 @@ pub fn acquire_schnorr_keypair() -> UnifiedKeypair {
* Interrupt timing * Interrupt timing
*/ */
let keypair = Keypair::new(&SECP, &mut OsRng); let keypair = Keypair::new(&SECP, &mut OsRng);
let privkey: SecretKey = keypair.secret_key(); let privkey: SecretKey = keypair.secret_key();
let pubkey: (XOnlyPublicKey, Parity) = XOnlyPublicKey::from_keypair(&keypair); let pubkey: (XOnlyPublicKey, Parity) = XOnlyPublicKey::from_keypair(&keypair);
UnifiedKeypair::new_schnorr(privkey, pubkey.0) 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 { 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) // 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(); let mut sig_bytes = signature.to_vec();
if sig_bytes.len() == 7857 { if sig_bytes.len() == 7857 {
sig_bytes.pop(); // Remove the last byte sig_bytes.pop(); // Remove the last byte
} }
info!("verify_slh_dsa_via_bytes: signature length: {:?}, message: {:?}, pubkey_bytes: {:?}", info!("verify_slh_dsa_via_bytes: signature length: {:?}, message: {:?}, pubkey_bytes: {:?}",
sig_bytes.len(), sig_bytes.len(),
hex::encode(message), hex::encode(message),
hex::encode(pubkey_bytes)); hex::encode(pubkey_bytes));
let signature = bitcoinpqc::Signature::try_from_slice(Algorithm::SLH_DSA_128S, &sig_bytes).unwrap(); 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(); let is_valid: bool = SECP.verify_schnorr(&signature, &message, &pubkey).is_ok();
if !is_valid { if !is_valid {
error!("verify schnorr failed:\n\tsignature: {:?}\n\tmessage: {:?}\n\tpubkey: {:?}", error!("verify schnorr failed:\n\tsignature: {:?}\n\tmessage: {:?}\n\tpubkey: {:?}",
signature, signature,
message, message,
hex::encode(pubkey.serialize())); hex::encode(pubkey.serialize()));
} }
is_valid is_valid

View File

@@ -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 hex;
use log::debug; use log::debug;
use once_cell::sync::Lazy; 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::data_structures::{TVScriptTree, TestVector, Direction, TestVectors, UtxoReturn};
use p2mr_ref::error::P2MRError; 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. // This file contains tests that execute against the BIP360 script-path-only test vectors.
@@ -21,48 +20,49 @@ static TEST_VECTORS: Lazy<TestVectors> = Lazy::new(|| {
test_vectors test_vectors
}); });
static P2TR_USING_V2_WITNESS_VERSION_ERROR: &str = "p2tr_using_v2_witness_version_error"; // Helper to run a P2MR error test vector
static P2MR_MISSING_LEAF_SCRIPT_TREE_ERROR_TEST: &str = "p2mr_missing_leaf_script_tree_error"; fn assert_p2mr_error<F>(id: &str, process: F, expected: P2MRError)
static P2MR_SINGLE_LEAF_SCRIPT_TREE_TEST: &str = "p2mr_single_leaf_script_tree"; where
static P2MR_DIFFERENT_VERSION_LEAVES_TEST: &str = "p2mr_different_version_leaves"; F: FnOnce(&TestVector) -> anyhow::Result<()>,
static P2MR_TWO_LEAF_SAME_VERSION_TEST: &str = "p2mr_two_leaf_same_version"; {
static P2MR_THREE_LEAF_COMPLEX_TEST: &str = "p2mr_three_leaf_complex"; let _ = env_logger::try_init();
static P2MR_THREE_LEAF_ALTERNATIVE_TEST: &str = "p2mr_three_leaf_alternative"; let tv = TEST_VECTORS.test_vector_map.get(id).unwrap();
static P2MR_SIMPLE_LIGHTNING_CONTRACT_TEST: &str = "p2mr_simple_lightning_contract"; let err = process(tv).unwrap_err();
assert!(matches!(err.downcast_ref::<P2MRError>(), 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] #[test]
fn test_p2tr_using_v2_witness_version_error() { fn test_p2tr_using_v2_witness_version_error() {
assert_p2mr_error(
let _ = env_logger::try_init(); // Use try_init to avoid reinitialization error "p2mr_misuse_v2_witness_version_with_pubkey_error",
process_test_vector_p2tr,
let test_vectors = &*TEST_VECTORS; P2MRError::P2trRequiresWitnessVersion1,
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::<P2MRError>(),
Some(P2MRError::P2trRequiresWitnessVersion1)));
} }
// https://learnmeabitcoin.com/technical/upgrades/taproot/#example-2-script-path-spend-simple // https://learnmeabitcoin.com/technical/upgrades/taproot/#example-2-script-path-spend-simple
#[test] #[test]
fn test_p2mr_missing_leaf_script_tree_error() { fn test_p2mr_missing_leaf_script_tree_error() {
assert_p2mr_error(
let _ = env_logger::try_init(); // Use try_init to avoid reinitialization error "p2mr_null_or_missing_script_tree_error",
process_test_vector_p2mr,
let test_vectors = &*TEST_VECTORS; P2MRError::MissingScriptTreeLeaf,
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::<P2MRError>(),
Some(P2MRError::MissingScriptTreeLeaf)));
} }
// Positive Tests
// https://learnmeabitcoin.com/technical/upgrades/taproot/#example-2-script-path-spend-simple // https://learnmeabitcoin.com/technical/upgrades/taproot/#example-2-script-path-spend-simple
#[test] #[test]
fn test_p2mr_single_leaf_script_tree() { fn test_p2mr_single_leaf_script_tree() {
let _ = env_logger::try_init(); // Use try_init to avoid reinitialization error run_p2mr_test("p2mr_single_leaf_script_tree");
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();
} }
/// Verifies that P2MR construction succeeds when leaves carry non-standard leaf versions (e.g. 0xfa). /// 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. /// and the resulting merkle root and control blocks are valid.
#[test] #[test]
fn test_p2mr_different_version_leaves() { fn test_p2mr_different_version_leaves() {
run_p2mr_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();
} }
#[test] #[test]
fn test_p2mr_simple_lightning_contract() { fn test_p2mr_simple_lightning_contract() {
run_p2mr_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();
} }
#[test] #[test]
fn test_p2mr_two_leaf_same_version() { fn test_p2mr_two_leaf_same_version() {
run_p2mr_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();
} }
#[test] #[test]
fn test_p2mr_three_leaf_complex() { fn test_p2mr_three_leaf_complex() {
run_p2mr_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();
} }
#[test] #[test]
fn test_p2mr_three_leaf_alternative() { 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 #[test]
fn test_p2mr_duplicate_leaves() {
let test_vectors = &*TEST_VECTORS; run_p2mr_test("p2mr_duplicate_leaves");
let test_vector = test_vectors.test_vector_map.get(P2MR_THREE_LEAF_ALTERNATIVE_TEST).unwrap();
process_test_vector_p2mr(test_vector).unwrap();
} }
fn process_test_vector_p2tr(test_vector: &TestVector) -> anyhow::Result<()> { 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 // 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 p2mr_builder: P2mrBuilder = P2mrBuilder::new();
let mut script_to_id: HashMap<ScriptBuf, u8> = HashMap::new();
// 1) traverse test vector script tree and add leaves to P2MR builder // 1) traverse test vector script tree and add leaves to P2MR builder
if let Some(script_tree) = tv_script_tree { 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 { if let TVScriptTree::Leaf(tv_leaf) = node {
let tv_leaf_script_bytes = hex::decode(&tv_leaf.script).unwrap(); 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_script_buf = ScriptBuf::from_bytes(tv_leaf_script_bytes.clone());
let tv_leaf_version = LeafVersion::from_consensus(tv_leaf.leaf_version).unwrap(); 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; let mut modified_depth = depth + 1;
if direction == Direction::Root { if direction == Direction::Root {
modified_depth = depth; 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); 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) p2mr_builder = p2mr_builder.clone().add_leaf_with_ver(depth, tv_leaf_script_buf.clone(), tv_leaf_version)
.unwrap_or_else(|e| { .unwrap_or_else(|e| {
panic!("Failed to add leaf: {:?}", e); panic!("Failed to add leaf: {:?}", e);
}); });
tv_leaf_count += 1; tv_leaf_count += 1;
} else if let TVScriptTree::Branch { left, right } = node { } 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); debug!("branch_count: {}, depth: {}, direction: {}", current_branch_id, depth, direction);
current_branch_id += 1; current_branch_id += 1;
} }
}); });
}else { } else {
return Err(P2MRError::MissingScriptTreeLeaf.into()); 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 // 2) verify derived merkle root against test vector
let test_vector_merkle_root = test_vector.intermediary.merkle_root.as_ref().unwrap(); let test_vector_merkle_root = test_vector.intermediary.merkle_root.as_ref().unwrap();
assert_eq!( assert_eq!(
derived_merkle_root.to_string(), derived_merkle_root.to_string(),
*test_vector_merkle_root, *test_vector_merkle_root,
"Merkle root mismatch" "Merkle root mismatch"
); );
debug!("just passed merkle root validation: {}", test_vector_merkle_root); 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 tap_tree: TapTree = p2mr_builder.clone().into_inner().try_into_taptree().unwrap();
let script_leaves: ScriptLeaves = tap_tree.script_leaves(); 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 { for derived_leaf in script_leaves {
let version = derived_leaf.version(); 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 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_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. // 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. // `& 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. // `| 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"); .expect("encode should not fail");
let derived_serialized_control_block = hex::encode(&cb_buf); let derived_serialized_control_block = hex::encode(&cb_buf);
let expected_cb = &expected_control_blocks[*leaf_id as usize]; assert!(
assert_eq!( expected_control_blocks.contains(&derived_serialized_control_block),
derived_serialized_control_block, *expected_cb, "Unexpected control block: {}", derived_serialized_control_block
"Control block mismatch for leaf id {}: derived {}, expected {}", leaf_id, derived_serialized_control_block, expected_cb
); );
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()); let p2mr_utxo_return: UtxoReturn = create_p2mr_utxo(derived_merkle_root.to_string());
assert_eq!( assert_eq!(
p2mr_utxo_return.script_pubkey_hex, p2mr_utxo_return.script_pubkey_hex,
*test_vector.expected.script_pubkey.as_ref().unwrap(), *test_vector.expected.script_pubkey.as_ref().unwrap(),
"Script pubkey mismatch" "Script pubkey mismatch"

View File

@@ -10,7 +10,7 @@ use once_cell::sync::Lazy;
use p2mr_ref::data_structures::{TVScriptTree, TestVector, Direction, TestVectors, UtxoReturn}; use p2mr_ref::data_structures::{TVScriptTree, TestVector, Direction, TestVectors, UtxoReturn};
use p2mr_ref::error::P2MRError; 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. // 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_script_buf = ScriptBuf::from_bytes(tv_leaf_script_bytes.clone());
let tv_leaf_version = LeafVersion::from_consensus(tv_leaf.leaf_version).unwrap(); let tv_leaf_version = LeafVersion::from_consensus(tv_leaf.leaf_version).unwrap();
script_to_id.insert(tv_leaf_script_buf.clone(), tv_leaf.id); script_to_id.insert(tv_leaf_script_buf.clone(), tv_leaf.id);
let mut modified_depth = depth + 1; let mut modified_depth = depth + 1;
if direction == Direction::Root { if direction == Direction::Root {
modified_depth = depth; 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); 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 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) p2mr_builder = p2mr_builder.clone().add_leaf_with_ver(depth, tv_leaf_script_buf.clone(), tv_leaf_version)
.unwrap_or_else(|e| { .unwrap_or_else(|e| {
panic!("Failed to add leaf: {:?}", e); panic!("Failed to add leaf: {:?}", e);
}); });
tv_leaf_count += 1; tv_leaf_count += 1;
} else if let TVScriptTree::Branch { left, right } = node { } else if let TVScriptTree::Branch { left, right } = node {
// No need to calculate branch hash. // 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 // 2) verify derived merkle root against test vector
let test_vector_merkle_root = test_vector.intermediary.merkle_root.as_ref().unwrap(); let test_vector_merkle_root = test_vector.intermediary.merkle_root.as_ref().unwrap();
assert_eq!( assert_eq!(
derived_merkle_root.to_string(), derived_merkle_root.to_string(),
*test_vector_merkle_root, *test_vector_merkle_root,
"Merkle root mismatch" "Merkle root mismatch"
); );
debug!("just passed merkle root validation: {}", test_vector_merkle_root); 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()); let p2mr_utxo_return: UtxoReturn = create_p2mr_utxo(derived_merkle_root.to_string());
assert_eq!( assert_eq!(
p2mr_utxo_return.script_pubkey_hex, p2mr_utxo_return.script_pubkey_hex,
*test_vector.expected.script_pubkey.as_ref().unwrap(), *test_vector.expected.script_pubkey.as_ref().unwrap(),
"Script pubkey mismatch" "Script pubkey mismatch"