Skip to main content

blvm_consensus/
taproot.rs

1//! Taproot functions from Orange Paper Section 11.2
2
3use std::borrow::Cow;
4
5use crate::error::Result;
6use crate::types::*;
7use crate::types::{ByteString, Hash};
8use crate::witness;
9use blvm_spec_lock::spec_locked;
10
11/// BIP 341 default tapscript leaf version.
12pub const TAPROOT_LEAF_VERSION_TAPSCRIPT: u8 = 0xc0;
13
14/// Witness Data: 𝒲 = π•Š* (stack of witness elements)
15///
16/// Uses unified witness type from witness module for consistency with SegWit
17pub use crate::witness::Witness;
18
19use crate::opcodes::{OP_1, PUSH_32_BYTES};
20
21/// Taproot output script: OP_1 <32-byte-hash>
22pub const TAPROOT_SCRIPT_PREFIX: u8 = OP_1;
23
24/// Validate Taproot output script
25#[spec_locked("11.2.1", "ValidateTaprootScript")]
26pub fn validate_taproot_script(script: &ByteString) -> Result<bool> {
27    use crate::constants::TAPROOT_SCRIPT_LENGTH;
28
29    // Check if script is P2TR: OP_1 <32-byte-program>
30    if script.len() != TAPROOT_SCRIPT_LENGTH {
31        return Ok(false);
32    }
33
34    if script[0] != TAPROOT_SCRIPT_PREFIX {
35        return Ok(false);
36    }
37
38    // BIP341 P2TR: OP_1 PUSH_32 <32-byte x-only output key>
39    if script[1] != PUSH_32_BYTES {
40        return Ok(false);
41    }
42
43    Ok(true)
44}
45
46/// Extract Taproot output key from script
47#[spec_locked("11.2.1", "ExtractTaprootOutputKey")]
48pub fn extract_taproot_output_key(script: &ByteString) -> Result<Option<[u8; 32]>> {
49    if !validate_taproot_script(script)? {
50        return Ok(None);
51    }
52
53    let mut output_key = [0u8; 32];
54    output_key.copy_from_slice(&script[2..34]);
55    Ok(Some(output_key))
56}
57
58/// Compute Taproot tweak using proper cryptographic operations
59/// OutputKey = InternalPubKey + TaprootTweak(MerkleRoot) Γ— G
60///
61/// With `blvm-secp256k1` feature: uses BIP 341 tagged hash (correct).
62/// Without: uses libsecp256k1 with plain SHA256 (legacy, non-BIP341).
63#[spec_locked("11.2.2", "ComputeTaprootTweak")]
64pub fn compute_taproot_tweak(internal_pubkey: &[u8; 32], merkle_root: &Hash) -> Result<[u8; 32]> {
65    crate::secp256k1_backend::taproot_output_key(internal_pubkey, merkle_root)
66}
67
68/// Validate Taproot key aggregation, including the parity commitment from the control block.
69///
70/// Per BIP341: verifies that `output_key.x == tweaked_internal_key.x` AND that
71/// `tweaked_internal_key.y_parity == expected_parity` (from `control_block[0] & 0x01`).
72#[spec_locked("11.2.2", "ValidateTaprootKeyAggregation")]
73pub fn validate_taproot_key_aggregation(
74    internal_pubkey: &[u8; 32],
75    merkle_root: &Hash,
76    output_key: &[u8; 32],
77    expected_parity: u8,
78) -> Result<bool> {
79    let (computed_key, actual_parity) =
80        crate::secp256k1_backend::taproot_output_key_with_parity(internal_pubkey, merkle_root)?;
81    Ok(computed_key == *output_key && actual_parity == expected_parity)
82}
83
84/// Validate Taproot script path spending
85#[spec_locked("11.2.3", "ValidateTaprootScriptPath")]
86pub fn validate_taproot_script_path(
87    script: &ByteString,
88    merkle_proof: &[Hash],
89    merkle_root: &Hash,
90) -> Result<bool> {
91    validate_taproot_script_path_with_leaf_version(
92        script,
93        merkle_proof,
94        merkle_root,
95        TAPROOT_LEAF_VERSION_TAPSCRIPT,
96    )
97}
98
99/// Validate Taproot script path spending with explicit leaf version.
100#[spec_locked("11.2.3", "ValidateTaprootScriptPath")]
101pub fn validate_taproot_script_path_with_leaf_version(
102    script: &ByteString,
103    merkle_proof: &[Hash],
104    merkle_root: &Hash,
105    leaf_version: u8,
106) -> Result<bool> {
107    let computed_root = compute_script_merkle_root(script, merkle_proof, leaf_version)?;
108    Ok(computed_root == *merkle_root)
109}
110
111/// Compute merkle root for script path using BIP 341 TapLeaf/TapBranch tagged hashes.
112#[spec_locked("11.2.3", "ComputeScriptMerkleRoot")]
113pub fn compute_script_merkle_root(
114    script: &ByteString,
115    proof: &[Hash],
116    leaf_version: u8,
117) -> Result<Hash> {
118    let mut current_hash = crate::secp256k1_backend::tap_leaf_hash(leaf_version, script);
119
120    for proof_hash in proof {
121        let (left, right) = if current_hash < *proof_hash {
122            (current_hash, *proof_hash)
123        } else {
124            (*proof_hash, current_hash)
125        };
126        current_hash = crate::secp256k1_backend::tap_branch_hash(&left, &right);
127    }
128
129    Ok(current_hash)
130}
131
132/// BIP341 annex tag (first byte of optional annex witness element).
133pub const TAPROOT_ANNEX_TAG: u8 = 0x50;
134
135/// Strip optional BIP341 annex from the end of a Taproot witness stack.
136///
137/// BIP341 `VerifyWitnessProgram` (v1): if there are at least two stack elements and the
138/// last begins with `0x50`, it is an annex and removed before key/script path dispatch.
139/// Returns the stripped witness and the annex sighash hash when present.
140/// Borrows the stack when no annex is present (avoids cloning the full witness).
141#[spec_locked("11.2.4", "StripTaprootAnnex")]
142pub fn strip_taproot_annex(witness: &Witness) -> (Cow<'_, Witness>, Option<Hash>) {
143    if witness.len() >= 2
144        && witness
145            .last()
146            .and_then(|a| a.first())
147            .is_some_and(|&b| b == TAPROOT_ANNEX_TAG)
148    {
149        let annex = &witness[witness.len() - 1];
150        let annex_hash = compute_taproot_annex_sighash_hash(annex);
151        let stripped = witness[..witness.len() - 1].to_vec();
152        return (Cow::Owned(stripped), Some(annex_hash));
153    }
154    (Cow::Borrowed(witness), None)
155}
156
157/// SHA256 of the annex element for Taproot sighash (BIP341 annex hash step).
158fn compute_taproot_annex_sighash_hash(annex: &[u8]) -> Hash {
159    use sha2::{Digest, Sha256};
160    let mut buf = encode_varint(annex.len() as u64);
161    buf.extend_from_slice(annex);
162    let digest = Sha256::digest(&buf);
163    let mut out = [0u8; 32];
164    out.copy_from_slice(&digest);
165    out
166}
167
168/// Parsed control block from Taproot script-path witness.
169#[derive(Debug)]
170pub struct TaprootControlBlock {
171    pub leaf_version: u8,
172    pub internal_pubkey: [u8; 32],
173    pub merkle_proof: Vec<Hash>,
174}
175
176/// Parse and validate Taproot script-path witness.
177/// Returns (tapscript, stack_items) if valid, Err otherwise.
178/// Witness format: [stack_items..., script, annex?, control_block]
179/// Annex: optional, last element before control block, must start with 0x50.
180/// Control block: leaf_version (1) + internal_pubkey (32) + merkle_proof (32*n).
181#[spec_locked("11.2.4", "ParseTaprootScriptPathWitness")]
182pub fn parse_taproot_script_path_witness(
183    witness: &Witness,
184    output_key: &[u8; 32],
185) -> Result<Option<(ByteString, Vec<ByteString>, TaprootControlBlock)>> {
186    if witness.len() < 2 {
187        return Ok(None);
188    }
189
190    let Some(control_block) = witness.last() else {
191        return Ok(None);
192    };
193    if control_block.len() < 33 || (control_block.len() - 33) % 32 != 0 {
194        return Ok(None);
195    }
196
197    // BIP341: leaf_version = control_block[0] & 0xfe (strip parity bit).
198    // parity = control_block[0] & 0x01 (which y-coordinate the tweaked key uses).
199    let leaf_version = control_block[0] & 0xfe;
200    let parity = control_block[0] & 0x01;
201    let mut internal_pubkey = [0u8; 32];
202    internal_pubkey.copy_from_slice(&control_block[1..33]);
203    let merkle_proof: Vec<Hash> = control_block[33..]
204        .chunks_exact(32)
205        .map(|c| {
206            let mut h = [0u8; 32];
207            h.copy_from_slice(c);
208            h
209        })
210        .collect();
211
212    let script_idx = if witness.len() >= 3 {
213        let maybe_annex = &witness[witness.len() - 2];
214        if maybe_annex.first() == Some(&0x50) {
215            witness.len() - 3
216        } else {
217            witness.len() - 2
218        }
219    } else {
220        witness.len() - 2
221    };
222
223    let tapscript = witness[script_idx].clone();
224    let stack_items: Vec<ByteString> = witness[..script_idx].to_vec();
225
226    let merkle_root = match compute_script_merkle_root(&tapscript, &merkle_proof, leaf_version) {
227        Ok(root) => root,
228        Err(_) => return Ok(None),
229    };
230    // Invalid tweak / aggregation mismatch β†’ script invalid (Ok(false)), not consensus Err.
231    let valid = match validate_taproot_key_aggregation(
232        &internal_pubkey,
233        &merkle_root,
234        output_key,
235        parity,
236    ) {
237        Ok(v) => v,
238        Err(_) => return Ok(None),
239    };
240    if !valid {
241        return Ok(None);
242    }
243
244    Ok(Some((
245        tapscript,
246        stack_items,
247        TaprootControlBlock {
248            leaf_version, // already masked (0xfe) β€” parity bit stripped
249            internal_pubkey,
250            merkle_proof,
251        },
252    )))
253}
254
255/// Check if transaction output is Taproot
256/// BIP341 invariant: a Taproot output has a P2TR scriptPubKey of exactly 34 bytes
257/// (OP_1 + 0x20 push + 32-byte x-only pubkey).  If this returns true, the length is 34.
258#[spec_locked("11.2.1", "IsTaprootOutput")]
259#[blvm_spec_lock::ensures(result == false || output.script_pubkey.len() == 34)]
260pub fn is_taproot_output(output: &TransactionOutput) -> bool {
261    validate_taproot_script(&output.script_pubkey).unwrap_or(false)
262}
263
264/// Validate Taproot transaction
265#[spec_locked("11.2.5", "ValidateTaprootTransaction")]
266pub fn validate_taproot_transaction(tx: &Transaction, witness: Option<&Witness>) -> Result<bool> {
267    // Check if any output is Taproot
268    for output in &tx.outputs {
269        if is_taproot_output(output) {
270            // Validate Taproot output
271            if !validate_taproot_script(&output.script_pubkey)? {
272                return Ok(false);
273            }
274        }
275    }
276
277    // Validate Taproot witness structure using unified framework.
278    // Strip optional annex before key/script path classification (BIP341).
279    if let Some(w) = witness {
280        let (body, _) = strip_taproot_annex(w);
281        let is_script_path = body.len() >= 2;
282        if !witness::validate_taproot_witness_structure(&body, is_script_path)? {
283            return Ok(false);
284        }
285    }
286
287    Ok(true)
288}
289
290/// Compute BIP341 SigMsg hash commitments from all inputs and outputs.
291///
292/// Returns (sha_prevouts, sha_amounts, sha_scriptpubkeys, sha_sequences, sha_outputs).
293/// Each is SHA256 of the concatenation of the respective field across all inputs/outputs.
294fn bip341_precompute(
295    tx: &Transaction,
296    prevout_values: &[i64],
297    prevout_script_pubkeys: &[&[u8]],
298) -> ([u8; 32], [u8; 32], [u8; 32], [u8; 32], [u8; 32]) {
299    use sha2::{Digest, Sha256};
300
301    let mut prevouts_data = Vec::new();
302    let mut amounts_data = Vec::new();
303    let mut scriptpubkeys_data = Vec::new();
304    let mut sequences_data = Vec::new();
305    for (i, input) in tx.inputs.iter().enumerate() {
306        prevouts_data.extend_from_slice(&input.prevout.hash);
307        prevouts_data.extend_from_slice(&input.prevout.index.to_le_bytes());
308        // Both callers validate that prevout slices are at least as long as tx.inputs before
309        // calling this function.  Direct indexing here makes the invariant explicit; a panic
310        // would signal a programming error, not a consensus-invalid input.
311        amounts_data.extend_from_slice(&(prevout_values[i] as u64).to_le_bytes());
312        let spk = prevout_script_pubkeys[i];
313        scriptpubkeys_data.extend_from_slice(&encode_varint(spk.len() as u64));
314        scriptpubkeys_data.extend_from_slice(spk);
315        sequences_data.extend_from_slice(&(input.sequence as u32).to_le_bytes());
316    }
317    let mut outputs_data = Vec::new();
318    for output in &tx.outputs {
319        outputs_data.extend_from_slice(&(output.value as u64).to_le_bytes());
320        outputs_data.extend_from_slice(&encode_varint(output.script_pubkey.len() as u64));
321        outputs_data.extend_from_slice(&output.script_pubkey);
322    }
323
324    (
325        Sha256::digest(&prevouts_data).into(),
326        Sha256::digest(&amounts_data).into(),
327        Sha256::digest(&scriptpubkeys_data).into(),
328        Sha256::digest(&sequences_data).into(),
329        Sha256::digest(&outputs_data).into(),
330    )
331}
332
333/// Compute Taproot key-path signature hash following BIP341.
334///
335/// SigMsg = epoch(1) || hash_type(1) || nVersion(4) || nLockTime(4)
336///        || sha_prevouts(32) || sha_amounts(32) || sha_scriptpubkeys(32) || sha_sequences(32)
337///        || sha_outputs(32)   [if SIGHASH_ALL]
338///        || spend_type(1)     [0x00 = key-path, no annex]
339///        || input_index(4)
340///
341/// Only SIGHASH_DEFAULT (0x00 = treated as ALL) and SIGHASH_ALL (0x01) are handled here;
342/// ANYONECANPAY and SIGHASH_SINGLE paths use the same base but are not yet exercised.
343#[spec_locked("11.2.6", "ComputeTaprootSignatureHash")]
344pub fn compute_taproot_signature_hash(
345    tx: &Transaction,
346    input_index: usize,
347    prevout_values: &[i64],
348    prevout_script_pubkeys: &[&[u8]],
349    sighash_type: u8,
350    annex_hash: Option<&Hash>,
351) -> Result<Hash> {
352    use sha2::{Digest, Sha256};
353
354    // Reject invalid sighash types (BIP341 Β§Common signature message)
355    // Valid: 0x00 (DEFAULT/ALL), 0x01–0x03, 0x81–0x83.
356    if !(sighash_type <= 0x03 || (0x81..=0x83).contains(&sighash_type)) {
357        return Err(crate::error::ConsensusError::InvalidSignature(
358            "Invalid Taproot sighash type".into(),
359        ));
360    }
361
362    // Validate prevout slices: one entry per input is required for a correct sighash.
363    // A short slice would silently produce wrong amounts/scriptpubkeys (zeroed),
364    // which is a consensus error.
365    if prevout_values.len() < tx.inputs.len() || prevout_script_pubkeys.len() < tx.inputs.len() {
366        return Err(crate::error::ConsensusError::InvalidSignature(
367            "prevout_values/prevout_script_pubkeys shorter than tx.inputs β€” cannot compute sighash"
368                .into(),
369        ));
370    }
371
372    let input_type = sighash_type & 0x80; // ANYONECANPAY bit
373    let output_type = if sighash_type == 0x00 {
374        0x01
375    } else {
376        sighash_type & 0x03
377    }; // DEFAULT β†’ ALL
378
379    let (sha_prevouts, sha_amounts, sha_scriptpubkeys, sha_sequences, sha_outputs) =
380        bip341_precompute(tx, prevout_values, prevout_script_pubkeys);
381
382    let mut sigmsg = Vec::with_capacity(180);
383
384    // epoch
385    sigmsg.push(0x00u8);
386    // hash_type
387    sigmsg.push(sighash_type);
388    // nVersion
389    sigmsg.extend_from_slice(&(tx.version as u32).to_le_bytes());
390    // nLockTime
391    sigmsg.extend_from_slice(&(tx.lock_time as u32).to_le_bytes());
392
393    if input_type != 0x80 {
394        // not ANYONECANPAY: include all-input hash commitments
395        sigmsg.extend_from_slice(&sha_prevouts);
396        sigmsg.extend_from_slice(&sha_amounts);
397        sigmsg.extend_from_slice(&sha_scriptpubkeys);
398        sigmsg.extend_from_slice(&sha_sequences);
399    }
400    if output_type == 0x01 {
401        // SIGHASH_ALL: include all-output hash
402        sigmsg.extend_from_slice(&sha_outputs);
403    }
404
405    // spend_type: ext_flag=0 (key path), annex_present in low bit (BIP341)
406    let spend_type = if annex_hash.is_some() { 0x01u8 } else { 0x00u8 };
407    sigmsg.push(spend_type);
408
409    if input_type == 0x80 {
410        // ANYONECANPAY: include this input's outpoint + amount + scriptpubkey + sequence
411        let input = tx.inputs.get(input_index).ok_or_else(|| {
412            crate::error::ConsensusError::InvalidSignature("input_index out of range".into())
413        })?;
414        sigmsg.extend_from_slice(&input.prevout.hash);
415        sigmsg.extend_from_slice(&input.prevout.index.to_le_bytes());
416        // prevout slice length was validated at function entry; indexing is safe here.
417        let amount = *prevout_values.get(input_index).ok_or_else(|| {
418            crate::error::ConsensusError::InvalidSignature(
419                "prevout_values index out of range for ANYONECANPAY".into(),
420            )
421        })? as u64;
422        sigmsg.extend_from_slice(&amount.to_le_bytes());
423        let spk = *prevout_script_pubkeys.get(input_index).ok_or_else(|| {
424            crate::error::ConsensusError::InvalidSignature(
425                "prevout_script_pubkeys index out of range for ANYONECANPAY".into(),
426            )
427        })?;
428        sigmsg.extend_from_slice(&encode_varint(spk.len() as u64));
429        sigmsg.extend_from_slice(spk);
430        sigmsg.extend_from_slice(&(input.sequence as u32).to_le_bytes());
431    } else {
432        // include input_index
433        sigmsg.extend_from_slice(&(input_index as u32).to_le_bytes());
434    }
435
436    if let Some(h) = annex_hash {
437        sigmsg.extend_from_slice(h);
438    }
439
440    if output_type == 0x03 {
441        // SIGHASH_SINGLE: hash of this input's corresponding output
442        if let Some(output) = tx.outputs.get(input_index) {
443            let mut out_data = Vec::new();
444            out_data.extend_from_slice(&(output.value as u64).to_le_bytes());
445            out_data.extend_from_slice(&encode_varint(output.script_pubkey.len() as u64));
446            out_data.extend_from_slice(&output.script_pubkey);
447            sigmsg.extend_from_slice(&Sha256::digest(&out_data));
448        }
449    }
450
451    Ok(crate::secp256k1_backend::tap_sighash_hash(&sigmsg))
452}
453
454/// Compute Tapscript (BIP342) signature hash.
455///
456/// Same base SigMsg as key-path (spend_type bit 0 set = 0x01 or 0x03 depending on annex),
457/// with extension: tapleaf_hash(32) || key_version(1=0x00) || codesep_pos(4).
458#[spec_locked("11.2.7", "ComputeTapscriptSignatureHash")]
459pub fn compute_tapscript_signature_hash(
460    tx: &Transaction,
461    input_index: usize,
462    prevout_values: &[i64],
463    prevout_script_pubkeys: &[&[u8]],
464    tapscript: &[u8],
465    leaf_version: u8,
466    codesep_pos: u32,
467    sighash_type: u8,
468    annex_hash: Option<&Hash>,
469) -> Result<Hash> {
470    use sha2::{Digest, Sha256};
471
472    if !(sighash_type <= 0x03 || (0x81..=0x83).contains(&sighash_type)) {
473        return Err(crate::error::ConsensusError::InvalidSignature(
474            "Invalid Tapscript sighash type".into(),
475        ));
476    }
477
478    let input_type = sighash_type & 0x80;
479    let output_type = if sighash_type == 0x00 {
480        0x01
481    } else {
482        sighash_type & 0x03
483    };
484
485    let (sha_prevouts, sha_amounts, sha_scriptpubkeys, sha_sequences, sha_outputs) =
486        bip341_precompute(tx, prevout_values, prevout_script_pubkeys);
487
488    let mut sigmsg = Vec::with_capacity(250);
489
490    // epoch
491    sigmsg.push(0x00u8);
492    // hash_type
493    sigmsg.push(sighash_type);
494    // nVersion
495    sigmsg.extend_from_slice(&(tx.version as u32).to_le_bytes());
496    // nLockTime
497    sigmsg.extend_from_slice(&(tx.lock_time as u32).to_le_bytes());
498
499    if input_type != 0x80 {
500        sigmsg.extend_from_slice(&sha_prevouts);
501        sigmsg.extend_from_slice(&sha_amounts);
502        sigmsg.extend_from_slice(&sha_scriptpubkeys);
503        sigmsg.extend_from_slice(&sha_sequences);
504    }
505    if output_type == 0x01 {
506        sigmsg.extend_from_slice(&sha_outputs);
507    }
508
509    // spend_type: ext_flag=1 (script path); annex_present in low bit (BIP341)
510    let spend_type = if annex_hash.is_some() { 0x03u8 } else { 0x02u8 };
511    sigmsg.push(spend_type);
512
513    if input_type == 0x80 {
514        let input = tx.inputs.get(input_index).ok_or_else(|| {
515            crate::error::ConsensusError::InvalidSignature("input_index out of range".into())
516        })?;
517        sigmsg.extend_from_slice(&input.prevout.hash);
518        sigmsg.extend_from_slice(&input.prevout.index.to_le_bytes());
519        let amount = *prevout_values.get(input_index).ok_or_else(|| {
520            crate::error::ConsensusError::InvalidSignature(
521                "prevout_values index out of range for script-path ANYONECANPAY".into(),
522            )
523        })? as u64;
524        sigmsg.extend_from_slice(&amount.to_le_bytes());
525        let spk = *prevout_script_pubkeys.get(input_index).ok_or_else(|| {
526            crate::error::ConsensusError::InvalidSignature(
527                "prevout_script_pubkeys index out of range for script-path ANYONECANPAY".into(),
528            )
529        })?;
530        sigmsg.extend_from_slice(&encode_varint(spk.len() as u64));
531        sigmsg.extend_from_slice(spk);
532        sigmsg.extend_from_slice(&(input.sequence as u32).to_le_bytes());
533    } else {
534        sigmsg.extend_from_slice(&(input_index as u32).to_le_bytes());
535    }
536
537    if let Some(h) = annex_hash {
538        sigmsg.extend_from_slice(h);
539    }
540
541    if output_type == 0x03 {
542        if let Some(output) = tx.outputs.get(input_index) {
543            let mut out_data = Vec::new();
544            out_data.extend_from_slice(&(output.value as u64).to_le_bytes());
545            out_data.extend_from_slice(&encode_varint(output.script_pubkey.len() as u64));
546            out_data.extend_from_slice(&output.script_pubkey);
547            sigmsg.extend_from_slice(&Sha256::digest(&out_data));
548        }
549    }
550
551    // BIP342 extension: tapleaf_hash || key_version(0x00) || codesep_pos
552    let tapleaf_hash = crate::secp256k1_backend::tap_leaf_hash(leaf_version, tapscript);
553    sigmsg.extend_from_slice(&tapleaf_hash);
554    sigmsg.push(0x00u8); // key_version
555    sigmsg.extend_from_slice(&codesep_pos.to_le_bytes());
556
557    Ok(crate::secp256k1_backend::tap_sighash_hash(&sigmsg))
558}
559
560/// Encode a number as a Bitcoin varint
561fn encode_varint(value: u64) -> Vec<u8> {
562    if value < 0xfd {
563        vec![value as u8]
564    } else if value <= 0xffff {
565        let mut result = vec![0xfd];
566        result.extend_from_slice(&(value as u16).to_le_bytes());
567        result
568    } else if value <= 0xffffffff {
569        let mut result = vec![0xfe];
570        result.extend_from_slice(&(value as u32).to_le_bytes());
571        result
572    } else {
573        let mut result = vec![0xff];
574        result.extend_from_slice(&value.to_le_bytes());
575        result
576    }
577}
578
579#[cfg(test)]
580mod tests {
581    use super::*;
582
583    #[test]
584    fn test_validate_taproot_script_valid() {
585        let script = create_taproot_script(&[1u8; 32]);
586        assert!(validate_taproot_script(&script).unwrap());
587    }
588
589    #[test]
590    fn test_validate_taproot_script_invalid_length() {
591        let script = vec![0x51, 0x20]; // Too short
592        assert!(!validate_taproot_script(&script).unwrap());
593    }
594
595    #[test]
596    fn test_validate_taproot_script_invalid_prefix() {
597        let mut script = vec![0x52]; // Wrong prefix (OP_2 instead of OP_1)
598        script.extend_from_slice(&[1u8; 32]);
599        assert!(!validate_taproot_script(&script).unwrap());
600    }
601
602    #[test]
603    fn test_extract_taproot_output_key() {
604        let expected_key = [1u8; 32];
605        let script = create_taproot_script(&expected_key);
606
607        let extracted_key = extract_taproot_output_key(&script).unwrap();
608        assert_eq!(extracted_key, Some(expected_key));
609    }
610
611    #[test]
612    fn test_compute_taproot_tweak() {
613        // Use a valid secp256k1 public key (x-coordinate only for Taproot)
614        let internal_pubkey = [
615            0x79, 0xbe, 0x66, 0x7e, 0xf9, 0xdc, 0xbb, 0xac, 0x55, 0xa0, 0x62, 0x95, 0xce, 0x87,
616            0x0b, 0x07, 0x02, 0x9b, 0xfc, 0xdb, 0x2d, 0xce, 0x28, 0xd9, 0x59, 0xf2, 0x81, 0x5b,
617            0x16, 0xf8, 0x17, 0x98,
618        ];
619        let merkle_root = [2u8; 32];
620
621        let tweak = compute_taproot_tweak(&internal_pubkey, &merkle_root).unwrap();
622        assert_eq!(tweak.len(), 32);
623    }
624
625    #[test]
626    fn test_validate_taproot_key_aggregation() {
627        // Use a valid secp256k1 public key (x-coordinate only for Taproot)
628        let internal_pubkey = [
629            0x79, 0xbe, 0x66, 0x7e, 0xf9, 0xdc, 0xbb, 0xac, 0x55, 0xa0, 0x62, 0x95, 0xce, 0x87,
630            0x0b, 0x07, 0x02, 0x9b, 0xfc, 0xdb, 0x2d, 0xce, 0x28, 0xd9, 0x59, 0xf2, 0x81, 0x5b,
631            0x16, 0xf8, 0x17, 0x98,
632        ];
633        let merkle_root = [2u8; 32];
634        let (output_key, parity) = crate::secp256k1_backend::taproot_output_key_with_parity(
635            &internal_pubkey,
636            &merkle_root,
637        )
638        .unwrap();
639
640        assert!(
641            validate_taproot_key_aggregation(&internal_pubkey, &merkle_root, &output_key, parity)
642                .unwrap()
643        );
644    }
645
646    #[test]
647    fn test_validate_taproot_script_path() {
648        let script = vec![0x51, 0x52]; // OP_1, OP_2
649        let merkle_proof = vec![[3u8; 32], [4u8; 32]];
650        let merkle_root =
651            compute_script_merkle_root(&script, &merkle_proof, TAPROOT_LEAF_VERSION_TAPSCRIPT)
652                .unwrap();
653
654        assert!(validate_taproot_script_path(&script, &merkle_proof, &merkle_root).unwrap());
655    }
656
657    #[test]
658    fn test_validate_taproot_script_requires_push_opcode() {
659        // 34 bytes but missing PUSH_32 (0x20) before output key
660        let mut bad = vec![TAPROOT_SCRIPT_PREFIX];
661        bad.extend_from_slice(&[0u8; 33]);
662        assert!(!validate_taproot_script(&bad).unwrap());
663
664        assert!(validate_taproot_script(&create_taproot_script(&[1u8; 32])).unwrap());
665    }
666
667    #[test]
668    fn test_is_taproot_output() {
669        let output = TransactionOutput {
670            value: 1000,
671            script_pubkey: create_taproot_script(&[1u8; 32]),
672        };
673
674        assert!(is_taproot_output(&output));
675    }
676
677    #[test]
678    fn test_validate_taproot_transaction() {
679        let tx = Transaction {
680            version: 1,
681            inputs: vec![TransactionInput {
682                prevout: OutPoint {
683                    hash: [0; 32],
684                    index: 0,
685                },
686                script_sig: vec![],
687                sequence: 0xffffffff,
688            }]
689            .into(),
690            outputs: vec![TransactionOutput {
691                value: 1000,
692                script_pubkey: create_taproot_script(&[1u8; 32]),
693            }]
694            .into(),
695            lock_time: 0,
696        };
697
698        // Key path spend: single signature
699        let witness = Some(vec![vec![0u8; 64]]);
700        assert!(validate_taproot_transaction(&tx, witness.as_ref()).unwrap());
701    }
702
703    #[test]
704    fn test_compute_taproot_signature_hash() {
705        let tx = Transaction {
706            version: 1,
707            inputs: vec![TransactionInput {
708                prevout: OutPoint {
709                    hash: [0; 32],
710                    index: 0,
711                },
712                script_sig: vec![],
713                sequence: 0xffffffff,
714            }]
715            .into(),
716            outputs: vec![TransactionOutput {
717                value: 1000,
718                script_pubkey: vec![0x51],
719            }]
720            .into(),
721            lock_time: 0,
722        };
723
724        let prevouts = [TransactionOutput {
725            value: 2000,
726            script_pubkey: create_taproot_script(&[1u8; 32]),
727        }];
728        let pv: Vec<i64> = prevouts.iter().map(|p| p.value).collect();
729        let psp: Vec<&[u8]> = prevouts
730            .iter()
731            .map(|p| p.script_pubkey.as_slice())
732            .collect();
733        let sig_hash = compute_taproot_signature_hash(&tx, 0, &pv, &psp, 0x01, None).unwrap();
734        assert_eq!(sig_hash.len(), 32);
735    }
736
737    #[test]
738    fn test_compute_taproot_signature_hash_invalid_input_index() {
739        let tx = Transaction {
740            version: 1,
741            inputs: vec![TransactionInput {
742                prevout: OutPoint {
743                    hash: [0; 32],
744                    index: 0,
745                },
746                script_sig: vec![],
747                sequence: 0xffffffff,
748            }]
749            .into(),
750            outputs: vec![TransactionOutput {
751                value: 1000,
752                script_pubkey: vec![0x51],
753            }]
754            .into(),
755            lock_time: 0,
756        };
757
758        let prevouts = [TransactionOutput {
759            value: 2000,
760            script_pubkey: create_taproot_script(&[1u8; 32]),
761        }];
762        let pv: Vec<i64> = prevouts.iter().map(|p| p.value).collect();
763        let psp: Vec<&[u8]> = prevouts
764            .iter()
765            .map(|p| p.script_pubkey.as_slice())
766            .collect();
767        // Use invalid input index (out of bounds)
768        let sig_hash = compute_taproot_signature_hash(&tx, 1, &pv, &psp, 0x01, None).unwrap();
769        assert_eq!(sig_hash.len(), 32);
770    }
771
772    #[test]
773    fn test_compute_taproot_signature_hash_empty_prevouts() {
774        let tx = Transaction {
775            version: 1,
776            inputs: vec![TransactionInput {
777                prevout: OutPoint {
778                    hash: [0; 32],
779                    index: 0,
780                },
781                script_sig: vec![],
782                sequence: 0xffffffff,
783            }]
784            .into(),
785            outputs: vec![TransactionOutput {
786                value: 1000,
787                script_pubkey: vec![0x51],
788            }]
789            .into(),
790            lock_time: 0,
791        };
792
793        // Empty prevout slices with a non-empty input list must return Err (fail-closed).
794        // Previously this silently used zeros; the audit fix made it explicit.
795        let prevouts: Vec<TransactionOutput> = vec![];
796        let pv: Vec<i64> = prevouts.iter().map(|p| p.value).collect();
797        let psp: Vec<&[u8]> = prevouts
798            .iter()
799            .map(|p| p.script_pubkey.as_slice())
800            .collect();
801        let result = compute_taproot_signature_hash(&tx, 0, &pv, &psp, 0x01, None);
802        assert!(
803            result.is_err(),
804            "empty prevouts shorter than tx.inputs must return Err, not silently use zeros"
805        );
806    }
807
808    #[test]
809    fn test_compute_taproot_tweak_invalid_pubkey() {
810        let invalid_pubkey = [0u8; 32]; // Invalid public key
811        let merkle_root = [2u8; 32];
812
813        let result = compute_taproot_tweak(&invalid_pubkey, &merkle_root);
814        assert!(result.is_err());
815    }
816
817    #[test]
818    fn test_validate_taproot_key_aggregation_invalid() {
819        let internal_pubkey = [
820            0x79, 0xbe, 0x66, 0x7e, 0xf9, 0xdc, 0xbb, 0xac, 0x55, 0xa0, 0x62, 0x95, 0xce, 0x87,
821            0x0b, 0x07, 0x02, 0x9b, 0xfc, 0xdb, 0x2d, 0xce, 0x28, 0xd9, 0x59, 0xf2, 0x81, 0x5b,
822            0x16, 0xf8, 0x17, 0x98,
823        ];
824        let merkle_root = [2u8; 32];
825        let wrong_output_key = [3u8; 32]; // Wrong output key
826
827        assert!(
828            !validate_taproot_key_aggregation(
829                &internal_pubkey,
830                &merkle_root,
831                &wrong_output_key,
832                0, // parity β€” irrelevant since key mismatch will fail first
833            )
834            .unwrap()
835        );
836    }
837
838    #[test]
839    fn test_validate_taproot_script_path_invalid() {
840        let script = vec![0x51, 0x52]; // OP_1, OP_2
841        let merkle_proof = vec![[3u8; 32], [4u8; 32]];
842        let wrong_merkle_root = [5u8; 32]; // Wrong merkle root
843
844        assert!(!validate_taproot_script_path(&script, &merkle_proof, &wrong_merkle_root).unwrap());
845    }
846
847    #[test]
848    fn test_validate_taproot_script_path_empty_proof() {
849        let script = vec![0x51, 0x52]; // OP_1, OP_2
850        let merkle_proof = vec![];
851        let merkle_root =
852            compute_script_merkle_root(&script, &merkle_proof, TAPROOT_LEAF_VERSION_TAPSCRIPT)
853                .unwrap();
854
855        assert!(validate_taproot_script_path(&script, &merkle_proof, &merkle_root).unwrap());
856    }
857
858    #[test]
859    fn test_tap_leaf_hash() {
860        let script = vec![0x51, 0x52];
861        let hash = crate::secp256k1_backend::tap_leaf_hash(TAPROOT_LEAF_VERSION_TAPSCRIPT, &script);
862
863        assert_eq!(hash.len(), 32);
864
865        let script2 = vec![0x53, 0x54];
866        let hash2 =
867            crate::secp256k1_backend::tap_leaf_hash(TAPROOT_LEAF_VERSION_TAPSCRIPT, &script2);
868        assert_ne!(hash, hash2);
869    }
870
871    #[test]
872    fn test_tap_branch_hash() {
873        let left = [1u8; 32];
874        let right = [2u8; 32];
875        let hash = crate::secp256k1_backend::tap_branch_hash(&left, &right);
876
877        assert_eq!(hash.len(), 32);
878
879        let hash2 = crate::secp256k1_backend::tap_branch_hash(&right, &left);
880        assert_ne!(hash, hash2);
881    }
882
883    #[test]
884    fn test_encode_varint_small() {
885        let encoded = encode_varint(0xfc);
886        assert_eq!(encoded, vec![0xfc]);
887    }
888
889    #[test]
890    fn test_encode_varint_medium() {
891        let encoded = encode_varint(0x1000);
892        assert_eq!(encoded.len(), 3);
893        assert_eq!(encoded[0], 0xfd);
894    }
895
896    #[test]
897    fn test_encode_varint_large() {
898        let encoded = encode_varint(0x1000000);
899        assert_eq!(encoded.len(), 5);
900        assert_eq!(encoded[0], 0xfe);
901    }
902
903    #[test]
904    fn test_encode_varint_huge() {
905        let encoded = encode_varint(0x1000000000000000);
906        assert_eq!(encoded.len(), 9);
907        assert_eq!(encoded[0], 0xff);
908    }
909
910    #[test]
911    fn test_extract_taproot_output_key_invalid_script() {
912        let script = vec![0x52, 0x20]; // Invalid script
913        let result = extract_taproot_output_key(&script).unwrap();
914        assert!(result.is_none());
915    }
916
917    #[test]
918    fn test_is_taproot_output_false() {
919        let output = TransactionOutput {
920            value: 1000,
921            script_pubkey: vec![0x52, 0x20], // Not a Taproot script
922        };
923
924        assert!(!is_taproot_output(&output));
925    }
926
927    #[test]
928    fn test_validate_taproot_transaction_no_taproot_outputs() {
929        let tx = Transaction {
930            version: 1,
931            inputs: vec![TransactionInput {
932                prevout: OutPoint {
933                    hash: [0; 32],
934                    index: 0,
935                },
936                script_sig: vec![],
937                sequence: 0xffffffff,
938            }]
939            .into(),
940            outputs: vec![TransactionOutput {
941                value: 1000,
942                script_pubkey: vec![0x52], // Not Taproot
943            }]
944            .into(),
945            lock_time: 0,
946        };
947
948        // No witness needed for non-Taproot transaction
949        assert!(validate_taproot_transaction(&tx, None).unwrap());
950    }
951
952    #[test]
953    fn test_validate_taproot_transaction_invalid_taproot_output() {
954        // Create a transaction with a valid Taproot script
955        let tx = Transaction {
956            version: 1,
957            inputs: vec![TransactionInput {
958                prevout: OutPoint {
959                    hash: [0; 32],
960                    index: 0,
961                },
962                script_sig: vec![],
963                sequence: 0xffffffff,
964            }]
965            .into(),
966            outputs: vec![TransactionOutput {
967                value: 1000,
968                script_pubkey: create_taproot_script(&[1u8; 32]),
969            }]
970            .into(),
971            lock_time: 0,
972        };
973
974        // Key path spend: single signature
975        let witness = Some(vec![vec![0u8; 64]]);
976        assert!(validate_taproot_transaction(&tx, witness.as_ref()).unwrap());
977    }
978
979    // Helper function
980    fn create_taproot_script(output_key: &[u8; 32]) -> ByteString {
981        let mut script = vec![TAPROOT_SCRIPT_PREFIX, PUSH_32_BYTES];
982        script.extend_from_slice(output_key);
983        script
984    }
985}
986
987#[cfg(test)]
988mod property_tests {
989    use super::*;
990    use proptest::prelude::*;
991
992    /// Property test: Taproot script validation is deterministic
993    ///
994    /// Mathematical specification:
995    /// βˆ€ script ∈ ByteString: validate_taproot_script(script) is deterministic
996    proptest! {
997        #[test]
998        fn prop_validate_taproot_script_deterministic(
999            script in prop::collection::vec(any::<u8>(), 0..50)
1000        ) {
1001            let result1 = validate_taproot_script(&script).unwrap();
1002            let result2 = validate_taproot_script(&script).unwrap();
1003
1004            assert_eq!(result1, result2);
1005        }
1006    }
1007
1008    /// Property test: Taproot output key extraction is correct
1009    ///
1010    /// Mathematical specification:
1011    /// βˆ€ script ∈ ByteString: if validate_taproot_script(script) = true
1012    /// then extract_taproot_output_key(script) returns Some(key)
1013    proptest! {
1014        #[test]
1015        fn prop_extract_taproot_output_key_correct(
1016            script in prop::collection::vec(any::<u8>(), 0..50)
1017        ) {
1018            let extracted_key = extract_taproot_output_key(&script).unwrap();
1019            let is_valid = validate_taproot_script(&script).unwrap();
1020
1021            if is_valid {
1022                assert!(extracted_key.is_some());
1023                let key = extracted_key.unwrap();
1024                assert_eq!(key.len(), 32);
1025            } else {
1026                assert!(extracted_key.is_none());
1027            }
1028        }
1029    }
1030
1031    /// Property test: Taproot key aggregation is deterministic
1032    ///
1033    /// Mathematical specification:
1034    /// βˆ€ internal_pubkey ∈ [u8; 32], merkle_root ∈ Hash:
1035    /// compute_taproot_tweak(internal_pubkey, merkle_root) is deterministic
1036    proptest! {
1037        #[test]
1038        fn prop_taproot_key_aggregation_deterministic(
1039            internal_pubkey in create_pubkey_strategy(),
1040            merkle_root in create_hash_strategy()
1041        ) {
1042            let result1 = compute_taproot_tweak(&internal_pubkey, &merkle_root);
1043            let result2 = compute_taproot_tweak(&internal_pubkey, &merkle_root);
1044
1045            assert_eq!(result1.is_ok(), result2.is_ok());
1046            if result1.is_ok() && result2.is_ok() {
1047                assert_eq!(result1.unwrap(), result2.unwrap());
1048            }
1049        }
1050    }
1051
1052    /// Property test: Taproot script path validation is deterministic
1053    ///
1054    /// Mathematical specification:
1055    /// βˆ€ script ∈ ByteString, merkle_proof ∈ [Hash], merkle_root ∈ Hash:
1056    /// validate_taproot_script_path(script, merkle_proof, merkle_root) is deterministic
1057    proptest! {
1058        #[test]
1059        fn prop_validate_taproot_script_path_deterministic(
1060            script in prop::collection::vec(any::<u8>(), 0..20),
1061            merkle_proof in prop::collection::vec(create_hash_strategy(), 0..5),
1062            merkle_root in create_hash_strategy()
1063        ) {
1064            let result1 = validate_taproot_script_path(&script, &merkle_proof, &merkle_root);
1065            let result2 = validate_taproot_script_path(&script, &merkle_proof, &merkle_root);
1066
1067            assert_eq!(result1.is_ok(), result2.is_ok());
1068            if result1.is_ok() && result2.is_ok() {
1069                assert_eq!(result1.unwrap(), result2.unwrap());
1070            }
1071        }
1072    }
1073
1074    /// Property test: Taproot signature hash computation is deterministic
1075    ///
1076    /// Mathematical specification:
1077    /// βˆ€ tx ∈ Transaction, input_index ∈ β„•, prevouts ∈ [TransactionOutput], sighash_type ∈ β„•:
1078    /// compute_taproot_signature_hash(tx, input_index, prevouts, sighash_type) is deterministic
1079    proptest! {
1080        #[test]
1081        fn prop_compute_taproot_signature_hash_deterministic(
1082            tx in create_transaction_strategy(),
1083            input_index in 0..10usize,
1084            prevouts in prop::collection::vec(create_output_strategy(), 0..5),
1085            sighash_type in any::<u8>()
1086        ) {
1087            let prevout_values: Vec<i64> = prevouts.iter().map(|p| p.value).collect();
1088            let prevout_script_pubkeys: Vec<&[u8]> = prevouts.iter().map(|p| p.script_pubkey.as_slice()).collect();
1089            let result1 = compute_taproot_signature_hash(&tx, input_index, &prevout_values, &prevout_script_pubkeys, sighash_type, None);
1090            let result2 = compute_taproot_signature_hash(&tx, input_index, &prevout_values, &prevout_script_pubkeys, sighash_type, None);
1091
1092            assert_eq!(result1.is_ok(), result2.is_ok());
1093            if let (Ok(hash1), Ok(hash2)) = (&result1, &result2) {
1094                assert_eq!(hash1, hash2);
1095                assert_eq!(hash1.len(), 32);
1096            }
1097
1098            // Hash should be 32 bytes if result is Ok
1099            if let Ok(ref hash) = result1 {
1100                assert_eq!(hash.len(), 32);
1101            }
1102        }
1103    }
1104
1105    /// Property test: Taproot output detection is consistent
1106    ///
1107    /// Mathematical specification:
1108    /// βˆ€ output ∈ TransactionOutput: is_taproot_output(output) ∈ {true, false}
1109    proptest! {
1110        #[test]
1111        fn prop_is_taproot_output_consistent(
1112            output in create_output_strategy()
1113        ) {
1114            let is_taproot = is_taproot_output(&output);
1115            // Just test it returns a boolean (is_taproot is either true or false)
1116            let _ = is_taproot;
1117        }
1118    }
1119
1120    /// Property test: Taproot transaction validation is deterministic
1121    ///
1122    /// Mathematical specification:
1123    /// βˆ€ tx ∈ Transaction: validate_taproot_transaction(tx) is deterministic
1124    proptest! {
1125        #[test]
1126        fn prop_validate_taproot_transaction_deterministic(
1127            tx in create_transaction_strategy()
1128        ) {
1129            let result1 = validate_taproot_transaction(&tx, None).unwrap();
1130            let result2 = validate_taproot_transaction(&tx, None).unwrap();
1131
1132            assert_eq!(result1, result2);
1133        }
1134    }
1135
1136    /// Property test: TapLeaf hashing is deterministic
1137    proptest! {
1138        #[test]
1139        fn prop_tap_leaf_hash_deterministic(
1140            script in prop::collection::vec(any::<u8>(), 0..20)
1141        ) {
1142            let hash1 = crate::secp256k1_backend::tap_leaf_hash(TAPROOT_LEAF_VERSION_TAPSCRIPT, &script);
1143            let hash2 = crate::secp256k1_backend::tap_leaf_hash(TAPROOT_LEAF_VERSION_TAPSCRIPT, &script);
1144
1145            assert_eq!(hash1, hash2);
1146            assert_eq!(hash1.len(), 32);
1147        }
1148    }
1149
1150    /// Property test: TapBranch hashing is deterministic
1151    proptest! {
1152        #[test]
1153        fn prop_tap_branch_hash_deterministic(
1154            left in create_hash_strategy(),
1155            right in create_hash_strategy()
1156        ) {
1157            let hash1 = crate::secp256k1_backend::tap_branch_hash(&left, &right);
1158            let hash2 = crate::secp256k1_backend::tap_branch_hash(&left, &right);
1159
1160            assert_eq!(hash1, hash2);
1161            assert_eq!(hash1.len(), 32);
1162        }
1163    }
1164
1165    /// Property test: Varint encoding is deterministic
1166    ///
1167    /// Mathematical specification:
1168    /// βˆ€ value ∈ β„•: encode_varint(value) is deterministic
1169    proptest! {
1170        #[test]
1171        fn prop_encode_varint_deterministic(
1172            value in 0..u64::MAX
1173        ) {
1174            let encoded1 = encode_varint(value);
1175            let encoded2 = encode_varint(value);
1176
1177            assert_eq!(encoded1, encoded2);
1178
1179            // Encoded length should be reasonable
1180            assert!(!encoded1.is_empty());
1181            assert!(encoded1.len() <= 9);
1182        }
1183    }
1184
1185    /// Property test: Varint encoding preserves value
1186    ///
1187    /// Mathematical specification:
1188    /// βˆ€ value ∈ β„•: decode_varint(encode_varint(value)) = value
1189    proptest! {
1190        #[test]
1191        fn prop_encode_varint_preserves_value(
1192            value in 0..1000000u64  // Smaller range for tractability
1193        ) {
1194            let encoded = encode_varint(value);
1195
1196            // Basic validation of encoding format
1197            match encoded.len() {
1198                1 => {
1199                    // Single byte encoding
1200                    assert!(value < 0xfd);
1201                    assert_eq!(encoded[0], value as u8);
1202                },
1203                3 => {
1204                    // 2-byte encoding
1205                    assert!((0xfd..=0xffff).contains(&value));
1206                    assert_eq!(encoded[0], 0xfd);
1207                },
1208                5 => {
1209                    // 4-byte encoding
1210                    assert!(value > 0xffff && value <= 0xffffffff);
1211                    assert_eq!(encoded[0], 0xfe);
1212                },
1213                9 => {
1214                    // 8-byte encoding
1215                    assert!(value > 0xffffffff);
1216                    assert_eq!(encoded[0], 0xff);
1217                },
1218                _ => panic!("Invalid varint encoding length"),
1219            }
1220        }
1221    }
1222
1223    /// Property test: Taproot script path validation with correct proof
1224    ///
1225    /// Mathematical specification:
1226    /// βˆ€ script ∈ ByteString, merkle_proof ∈ [Hash]:
1227    /// If computed_root = compute_script_merkle_root(script, merkle_proof)
1228    /// then validate_taproot_script_path(script, merkle_proof, computed_root) = true
1229    proptest! {
1230        #[test]
1231        fn prop_validate_taproot_script_path_correct_proof(
1232            script in prop::collection::vec(any::<u8>(), 0..20),
1233            merkle_proof in prop::collection::vec(create_hash_strategy(), 0..5)
1234        ) {
1235            let computed_root = compute_script_merkle_root(&script, &merkle_proof, TAPROOT_LEAF_VERSION_TAPSCRIPT).unwrap();
1236            let is_valid = validate_taproot_script_path(&script, &merkle_proof, &computed_root).unwrap();
1237
1238            assert!(is_valid);
1239        }
1240    }
1241
1242    // Property test strategies
1243    fn create_transaction_strategy() -> impl Strategy<Value = Transaction> {
1244        (
1245            prop::collection::vec(any::<u8>(), 0..10), // inputs
1246            prop::collection::vec(any::<u8>(), 0..10), // outputs
1247        )
1248            .prop_map(|(input_data, output_data)| {
1249                let mut inputs = Vec::new();
1250                for (i, _) in input_data.iter().enumerate() {
1251                    inputs.push(TransactionInput {
1252                        prevout: OutPoint {
1253                            hash: [0; 32],
1254                            index: i as u32,
1255                        },
1256                        script_sig: vec![],
1257                        sequence: 0xffffffff,
1258                    });
1259                }
1260
1261                let mut outputs = Vec::new();
1262                for _ in output_data {
1263                    outputs.push(TransactionOutput {
1264                        value: 1000,
1265                        script_pubkey: vec![0x51],
1266                    });
1267                }
1268
1269                Transaction {
1270                    version: 1,
1271                    inputs: inputs.into(),
1272                    outputs: outputs.into(),
1273                    lock_time: 0,
1274                }
1275            })
1276    }
1277
1278    fn create_output_strategy() -> impl Strategy<Value = TransactionOutput> {
1279        (any::<i64>(), prop::collection::vec(any::<u8>(), 0..50)).prop_map(|(value, script)| {
1280            TransactionOutput {
1281                value,
1282                script_pubkey: script,
1283            }
1284        })
1285    }
1286
1287    fn create_hash_strategy() -> impl Strategy<Value = Hash> {
1288        prop::collection::vec(any::<u8>(), 32..=32).prop_map(|bytes| {
1289            let mut hash = [0u8; 32];
1290            hash.copy_from_slice(&bytes);
1291            hash
1292        })
1293    }
1294
1295    fn create_pubkey_strategy() -> impl Strategy<Value = [u8; 32]> {
1296        prop::collection::vec(any::<u8>(), 32..=32).prop_map(|bytes| {
1297            let mut pubkey = [0u8; 32];
1298            pubkey.copy_from_slice(&bytes);
1299            pubkey
1300        })
1301    }
1302}