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