Skip to main content

blvm_consensus/
taproot.rs

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