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