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")]
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")]
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")]
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
63#[spec_locked("11.2.2")]
64pub fn validate_taproot_key_aggregation(
65    internal_pubkey: &[u8; 32],
66    merkle_root: &Hash,
67    output_key: &[u8; 32],
68) -> Result<bool> {
69    let expected_output_key = compute_taproot_tweak(internal_pubkey, merkle_root)?;
70    Ok(expected_output_key == *output_key)
71}
72
73/// Validate Taproot script path spending
74#[spec_locked("11.2.3")]
75pub fn validate_taproot_script_path(
76    script: &ByteString,
77    merkle_proof: &[Hash],
78    merkle_root: &Hash,
79) -> Result<bool> {
80    validate_taproot_script_path_with_leaf_version(
81        script,
82        merkle_proof,
83        merkle_root,
84        TAPROOT_LEAF_VERSION_TAPSCRIPT,
85    )
86}
87
88/// Validate Taproot script path spending with explicit leaf version.
89#[spec_locked("11.2.3")]
90pub fn validate_taproot_script_path_with_leaf_version(
91    script: &ByteString,
92    merkle_proof: &[Hash],
93    merkle_root: &Hash,
94    leaf_version: u8,
95) -> Result<bool> {
96    let computed_root = compute_script_merkle_root(script, merkle_proof, leaf_version)?;
97    Ok(computed_root == *merkle_root)
98}
99
100/// Compute merkle root for script path using BIP 341 TapLeaf/TapBranch tagged hashes.
101#[spec_locked("11.2.3")]
102pub fn compute_script_merkle_root(
103    script: &ByteString,
104    proof: &[Hash],
105    leaf_version: u8,
106) -> Result<Hash> {
107    let mut current_hash = crate::secp256k1_backend::tap_leaf_hash(leaf_version, script);
108
109    for proof_hash in proof {
110        let (left, right) = if current_hash < *proof_hash {
111            (current_hash, *proof_hash)
112        } else {
113            (*proof_hash, current_hash)
114        };
115        current_hash = crate::secp256k1_backend::tap_branch_hash(&left, &right);
116    }
117
118    Ok(current_hash)
119}
120
121/// Parsed control block from Taproot script-path witness.
122#[derive(Debug)]
123pub struct TaprootControlBlock {
124    pub leaf_version: u8,
125    pub internal_pubkey: [u8; 32],
126    pub merkle_proof: Vec<Hash>,
127}
128
129/// Parse and validate Taproot script-path witness.
130/// Returns (tapscript, stack_items) if valid, Err otherwise.
131/// Witness format: [stack_items..., script, annex?, control_block]
132/// Annex: optional, last element before control block, must start with 0x50.
133/// Control block: leaf_version (1) + internal_pubkey (32) + merkle_proof (32*n).
134#[spec_locked("11.2.4")]
135pub fn parse_taproot_script_path_witness(
136    witness: &Witness,
137    output_key: &[u8; 32],
138) -> Result<Option<(ByteString, Vec<ByteString>, TaprootControlBlock)>> {
139    if witness.len() < 2 {
140        return Ok(None);
141    }
142
143    let control_block = witness.last().expect("len >= 2");
144    if control_block.len() < 33 || (control_block.len() - 33) % 32 != 0 {
145        return Ok(None);
146    }
147
148    let leaf_version = control_block[0];
149    let mut internal_pubkey = [0u8; 32];
150    internal_pubkey.copy_from_slice(&control_block[1..33]);
151    let merkle_proof: Vec<Hash> = control_block[33..]
152        .chunks_exact(32)
153        .map(|c| {
154            let mut h = [0u8; 32];
155            h.copy_from_slice(c);
156            h
157        })
158        .collect();
159
160    let script_idx = if witness.len() >= 3 {
161        let maybe_annex = &witness[witness.len() - 2];
162        if maybe_annex.first() == Some(&0x50) {
163            witness.len() - 3
164        } else {
165            witness.len() - 2
166        }
167    } else {
168        witness.len() - 2
169    };
170
171    let tapscript = witness[script_idx].clone();
172    let stack_items: Vec<ByteString> = witness[..script_idx].to_vec();
173
174    let merkle_root = compute_script_merkle_root(&tapscript, &merkle_proof, leaf_version)?;
175    if !validate_taproot_key_aggregation(&internal_pubkey, &merkle_root, output_key)? {
176        return Ok(None);
177    }
178
179    Ok(Some((
180        tapscript,
181        stack_items,
182        TaprootControlBlock {
183            leaf_version,
184            internal_pubkey,
185            merkle_proof,
186        },
187    )))
188}
189
190/// Check if transaction output is Taproot
191#[spec_locked("11.2.1")]
192pub fn is_taproot_output(output: &TransactionOutput) -> bool {
193    validate_taproot_script(&output.script_pubkey).unwrap_or(false)
194}
195
196/// Validate Taproot transaction
197#[spec_locked("11.2.5")]
198pub fn validate_taproot_transaction(tx: &Transaction, witness: Option<&Witness>) -> Result<bool> {
199    // Check if any output is Taproot
200    for output in &tx.outputs {
201        if is_taproot_output(output) {
202            // Validate Taproot output
203            if !validate_taproot_script(&output.script_pubkey)? {
204                return Ok(false);
205            }
206        }
207    }
208
209    // Validate Taproot witness structure using unified framework
210    // Determine if this is a script path spend based on witness structure
211    // Script path has at least 2 elements (script + control block), key path has 1 element (signature)
212    if let Some(w) = witness {
213        let is_script_path = w.len() >= 2;
214        if !witness::validate_taproot_witness_structure(w, is_script_path)? {
215            return Ok(false);
216        }
217    }
218
219    Ok(true)
220}
221
222/// Compute Taproot signature hash following BIP 341 specification.
223/// Uses TaggedHash("TapSighash", 0x00 || SigMsg(...)) per BIP 341.
224#[spec_locked("11.2.6")]
225pub fn compute_taproot_signature_hash(
226    tx: &Transaction,
227    input_index: usize,
228    prevout_values: &[i64],
229    prevout_script_pubkeys: &[&[u8]],
230    sighash_type: u8,
231) -> Result<Hash> {
232    let mut sigmsg = Vec::new();
233
234    sigmsg.extend((tx.version as u32).to_le_bytes());
235    sigmsg.extend(encode_varint(tx.inputs.len() as u64));
236    for input in &tx.inputs {
237        sigmsg.extend(input.prevout.hash);
238        sigmsg.extend(input.prevout.index.to_le_bytes());
239        sigmsg.push(0);
240        sigmsg.extend((input.sequence as u32).to_le_bytes());
241    }
242    sigmsg.extend(encode_varint(tx.outputs.len() as u64));
243    for output in &tx.outputs {
244        sigmsg.extend((output.value as u64).to_le_bytes());
245        sigmsg.extend(encode_varint(output.script_pubkey.len() as u64));
246        sigmsg.extend(&output.script_pubkey);
247    }
248    sigmsg.extend((tx.lock_time as u32).to_le_bytes());
249    sigmsg.extend((sighash_type as u32).to_le_bytes());
250    sigmsg.extend((input_index as u32).to_le_bytes());
251    if input_index < prevout_values.len() {
252        sigmsg.extend((prevout_values[input_index] as u64).to_le_bytes());
253    } else {
254        sigmsg.extend([0u8; 8]);
255    }
256    if input_index < prevout_script_pubkeys.len() {
257        sigmsg.extend(encode_varint(
258            prevout_script_pubkeys[input_index].len() as u64
259        ));
260        sigmsg.extend(prevout_script_pubkeys[input_index]);
261    } else {
262        sigmsg.push(0);
263    }
264
265    let mut tagged_input = Vec::with_capacity(1 + sigmsg.len());
266    tagged_input.push(0x00);
267    tagged_input.extend(sigmsg);
268    Ok(crate::secp256k1_backend::tap_sighash_hash(&tagged_input))
269}
270
271/// Compute Tapscript signature hash per BIP 342.
272/// Same base SigMsg as key-path, with ext = codesep_pos (4) || key_version (1) || tapleaf_hash (32).
273#[spec_locked("11.2.7")]
274pub fn compute_tapscript_signature_hash(
275    tx: &Transaction,
276    input_index: usize,
277    prevout_values: &[i64],
278    prevout_script_pubkeys: &[&[u8]],
279    tapscript: &[u8],
280    leaf_version: u8,
281    codesep_pos: u32,
282    sighash_type: u8,
283) -> Result<Hash> {
284    let mut sigmsg = Vec::new();
285    sigmsg.extend((tx.version as u32).to_le_bytes());
286    sigmsg.extend(encode_varint(tx.inputs.len() as u64));
287    for input in &tx.inputs {
288        sigmsg.extend(input.prevout.hash);
289        sigmsg.extend(input.prevout.index.to_le_bytes());
290        sigmsg.push(0);
291        sigmsg.extend((input.sequence as u32).to_le_bytes());
292    }
293    sigmsg.extend(encode_varint(tx.outputs.len() as u64));
294    for output in &tx.outputs {
295        sigmsg.extend((output.value as u64).to_le_bytes());
296        sigmsg.extend(encode_varint(output.script_pubkey.len() as u64));
297        sigmsg.extend(&output.script_pubkey);
298    }
299    sigmsg.extend((tx.lock_time as u32).to_le_bytes());
300    sigmsg.extend((sighash_type as u32).to_le_bytes());
301    sigmsg.extend((input_index as u32).to_le_bytes());
302    if input_index < prevout_values.len() {
303        sigmsg.extend((prevout_values[input_index] as u64).to_le_bytes());
304    } else {
305        sigmsg.extend([0u8; 8]);
306    }
307    if input_index < prevout_script_pubkeys.len() {
308        sigmsg.extend(encode_varint(
309            prevout_script_pubkeys[input_index].len() as u64
310        ));
311        sigmsg.extend(prevout_script_pubkeys[input_index]);
312    } else {
313        sigmsg.push(0);
314    }
315    let tapleaf_hash = crate::secp256k1_backend::tap_leaf_hash(leaf_version, tapscript);
316    sigmsg.extend(codesep_pos.to_le_bytes());
317    sigmsg.push(0x00);
318    sigmsg.extend(tapleaf_hash);
319    let mut tagged_input = Vec::with_capacity(1 + sigmsg.len());
320    tagged_input.push(0x00);
321    tagged_input.extend(sigmsg);
322    Ok(crate::secp256k1_backend::tap_sighash_hash(&tagged_input))
323}
324
325/// Encode a number as a Bitcoin varint
326fn encode_varint(value: u64) -> Vec<u8> {
327    if value < 0xfd {
328        vec![value as u8]
329    } else if value <= 0xffff {
330        let mut result = vec![0xfd];
331        result.extend_from_slice(&(value as u16).to_le_bytes());
332        result
333    } else if value <= 0xffffffff {
334        let mut result = vec![0xfe];
335        result.extend_from_slice(&(value as u32).to_le_bytes());
336        result
337    } else {
338        let mut result = vec![0xff];
339        result.extend_from_slice(&value.to_le_bytes());
340        result
341    }
342}
343
344#[cfg(test)]
345mod tests {
346    use super::*;
347
348    #[test]
349    fn test_validate_taproot_script_valid() {
350        let script = create_taproot_script(&[1u8; 32]);
351        assert!(validate_taproot_script(&script).unwrap());
352    }
353
354    #[test]
355    fn test_validate_taproot_script_invalid_length() {
356        let script = vec![0x51, 0x20]; // Too short
357        assert!(!validate_taproot_script(&script).unwrap());
358    }
359
360    #[test]
361    fn test_validate_taproot_script_invalid_prefix() {
362        let mut script = vec![0x52]; // Wrong prefix (OP_2 instead of OP_1)
363        script.extend_from_slice(&[1u8; 32]);
364        assert!(!validate_taproot_script(&script).unwrap());
365    }
366
367    #[test]
368    fn test_extract_taproot_output_key() {
369        let expected_key = [1u8; 32];
370        let script = create_taproot_script(&expected_key);
371
372        let extracted_key = extract_taproot_output_key(&script).unwrap();
373        assert_eq!(extracted_key, Some(expected_key));
374    }
375
376    #[test]
377    fn test_compute_taproot_tweak() {
378        // Use a valid secp256k1 public key (x-coordinate only for Taproot)
379        let internal_pubkey = [
380            0x79, 0xbe, 0x66, 0x7e, 0xf9, 0xdc, 0xbb, 0xac, 0x55, 0xa0, 0x62, 0x95, 0xce, 0x87,
381            0x0b, 0x07, 0x02, 0x9b, 0xfc, 0xdb, 0x2d, 0xce, 0x28, 0xd9, 0x59, 0xf2, 0x81, 0x5b,
382            0x16, 0xf8, 0x17, 0x98,
383        ];
384        let merkle_root = [2u8; 32];
385
386        let tweak = compute_taproot_tweak(&internal_pubkey, &merkle_root).unwrap();
387        assert_eq!(tweak.len(), 32);
388    }
389
390    #[test]
391    fn test_validate_taproot_key_aggregation() {
392        // Use a valid secp256k1 public key (x-coordinate only for Taproot)
393        let internal_pubkey = [
394            0x79, 0xbe, 0x66, 0x7e, 0xf9, 0xdc, 0xbb, 0xac, 0x55, 0xa0, 0x62, 0x95, 0xce, 0x87,
395            0x0b, 0x07, 0x02, 0x9b, 0xfc, 0xdb, 0x2d, 0xce, 0x28, 0xd9, 0x59, 0xf2, 0x81, 0x5b,
396            0x16, 0xf8, 0x17, 0x98,
397        ];
398        let merkle_root = [2u8; 32];
399        let output_key = compute_taproot_tweak(&internal_pubkey, &merkle_root).unwrap();
400
401        assert!(
402            validate_taproot_key_aggregation(&internal_pubkey, &merkle_root, &output_key).unwrap()
403        );
404    }
405
406    #[test]
407    fn test_validate_taproot_script_path() {
408        let script = vec![0x51, 0x52]; // OP_1, OP_2
409        let merkle_proof = vec![[3u8; 32], [4u8; 32]];
410        let merkle_root =
411            compute_script_merkle_root(&script, &merkle_proof, TAPROOT_LEAF_VERSION_TAPSCRIPT)
412                .unwrap();
413
414        assert!(validate_taproot_script_path(&script, &merkle_proof, &merkle_root).unwrap());
415    }
416
417    #[test]
418    fn test_is_taproot_output() {
419        let output = TransactionOutput {
420            value: 1000,
421            script_pubkey: create_taproot_script(&[1u8; 32]),
422        };
423
424        assert!(is_taproot_output(&output));
425    }
426
427    #[test]
428    fn test_validate_taproot_transaction() {
429        let tx = Transaction {
430            version: 1,
431            inputs: vec![TransactionInput {
432                prevout: OutPoint {
433                    hash: [0; 32].into(),
434                    index: 0,
435                },
436                script_sig: vec![],
437                sequence: 0xffffffff,
438            }]
439            .into(),
440            outputs: vec![TransactionOutput {
441                value: 1000,
442                script_pubkey: create_taproot_script(&[1u8; 32].into()),
443            }]
444            .into(),
445            lock_time: 0,
446        };
447
448        // Key path spend: single signature
449        let witness = Some(vec![vec![0u8; 64]]);
450        assert!(validate_taproot_transaction(&tx, witness.as_ref()).unwrap());
451    }
452
453    #[test]
454    fn test_compute_taproot_signature_hash() {
455        let tx = Transaction {
456            version: 1,
457            inputs: vec![TransactionInput {
458                prevout: OutPoint {
459                    hash: [0; 32].into(),
460                    index: 0,
461                },
462                script_sig: vec![],
463                sequence: 0xffffffff,
464            }]
465            .into(),
466            outputs: vec![TransactionOutput {
467                value: 1000,
468                script_pubkey: vec![0x51].into(),
469            }]
470            .into(),
471            lock_time: 0,
472        };
473
474        let prevouts = vec![TransactionOutput {
475            value: 2000,
476            script_pubkey: create_taproot_script(&[1u8; 32]),
477        }];
478        let pv: Vec<i64> = prevouts.iter().map(|p| p.value).collect();
479        let psp: Vec<&[u8]> = prevouts
480            .iter()
481            .map(|p| p.script_pubkey.as_slice())
482            .collect();
483        let sig_hash = compute_taproot_signature_hash(&tx, 0, &pv, &psp, 0x01).unwrap();
484        assert_eq!(sig_hash.len(), 32);
485    }
486
487    #[test]
488    fn test_compute_taproot_signature_hash_invalid_input_index() {
489        let tx = Transaction {
490            version: 1,
491            inputs: vec![TransactionInput {
492                prevout: OutPoint {
493                    hash: [0; 32].into(),
494                    index: 0,
495                },
496                script_sig: vec![],
497                sequence: 0xffffffff,
498            }]
499            .into(),
500            outputs: vec![TransactionOutput {
501                value: 1000,
502                script_pubkey: vec![0x51].into(),
503            }]
504            .into(),
505            lock_time: 0,
506        };
507
508        let prevouts = vec![TransactionOutput {
509            value: 2000,
510            script_pubkey: create_taproot_script(&[1u8; 32]),
511        }];
512        let pv: Vec<i64> = prevouts.iter().map(|p| p.value).collect();
513        let psp: Vec<&[u8]> = prevouts
514            .iter()
515            .map(|p| p.script_pubkey.as_slice())
516            .collect();
517        // Use invalid input index (out of bounds)
518        let sig_hash = compute_taproot_signature_hash(&tx, 1, &pv, &psp, 0x01).unwrap();
519        assert_eq!(sig_hash.len(), 32);
520    }
521
522    #[test]
523    fn test_compute_taproot_signature_hash_empty_prevouts() {
524        let tx = Transaction {
525            version: 1,
526            inputs: vec![TransactionInput {
527                prevout: OutPoint {
528                    hash: [0; 32].into(),
529                    index: 0,
530                },
531                script_sig: vec![],
532                sequence: 0xffffffff,
533            }]
534            .into(),
535            outputs: vec![TransactionOutput {
536                value: 1000,
537                script_pubkey: vec![0x51].into(),
538            }]
539            .into(),
540            lock_time: 0,
541        };
542
543        let prevouts: Vec<TransactionOutput> = vec![];
544        let pv: Vec<i64> = prevouts.iter().map(|p| p.value).collect();
545        let psp: Vec<&[u8]> = prevouts
546            .iter()
547            .map(|p| p.script_pubkey.as_slice())
548            .collect();
549        let sig_hash = compute_taproot_signature_hash(&tx, 0, &pv, &psp, 0x01).unwrap();
550        assert_eq!(sig_hash.len(), 32);
551    }
552
553    #[test]
554    fn test_compute_taproot_tweak_invalid_pubkey() {
555        let invalid_pubkey = [0u8; 32]; // Invalid public key
556        let merkle_root = [2u8; 32];
557
558        let result = compute_taproot_tweak(&invalid_pubkey, &merkle_root);
559        assert!(result.is_err());
560    }
561
562    #[test]
563    fn test_validate_taproot_key_aggregation_invalid() {
564        let internal_pubkey = [
565            0x79, 0xbe, 0x66, 0x7e, 0xf9, 0xdc, 0xbb, 0xac, 0x55, 0xa0, 0x62, 0x95, 0xce, 0x87,
566            0x0b, 0x07, 0x02, 0x9b, 0xfc, 0xdb, 0x2d, 0xce, 0x28, 0xd9, 0x59, 0xf2, 0x81, 0x5b,
567            0x16, 0xf8, 0x17, 0x98,
568        ];
569        let merkle_root = [2u8; 32];
570        let wrong_output_key = [3u8; 32]; // Wrong output key
571
572        assert!(!validate_taproot_key_aggregation(
573            &internal_pubkey,
574            &merkle_root,
575            &wrong_output_key
576        )
577        .unwrap());
578    }
579
580    #[test]
581    fn test_validate_taproot_script_path_invalid() {
582        let script = vec![0x51, 0x52]; // OP_1, OP_2
583        let merkle_proof = vec![[3u8; 32], [4u8; 32]];
584        let wrong_merkle_root = [5u8; 32]; // Wrong merkle root
585
586        assert!(!validate_taproot_script_path(&script, &merkle_proof, &wrong_merkle_root).unwrap());
587    }
588
589    #[test]
590    fn test_validate_taproot_script_path_empty_proof() {
591        let script = vec![0x51, 0x52]; // OP_1, OP_2
592        let merkle_proof = vec![];
593        let merkle_root =
594            compute_script_merkle_root(&script, &merkle_proof, TAPROOT_LEAF_VERSION_TAPSCRIPT)
595                .unwrap();
596
597        assert!(validate_taproot_script_path(&script, &merkle_proof, &merkle_root).unwrap());
598    }
599
600    #[test]
601    fn test_tap_leaf_hash() {
602        let script = vec![0x51, 0x52];
603        let hash = crate::secp256k1_backend::tap_leaf_hash(TAPROOT_LEAF_VERSION_TAPSCRIPT, &script);
604
605        assert_eq!(hash.len(), 32);
606
607        let script2 = vec![0x53, 0x54];
608        let hash2 =
609            crate::secp256k1_backend::tap_leaf_hash(TAPROOT_LEAF_VERSION_TAPSCRIPT, &script2);
610        assert_ne!(hash, hash2);
611    }
612
613    #[test]
614    fn test_tap_branch_hash() {
615        let left = [1u8; 32];
616        let right = [2u8; 32];
617        let hash = crate::secp256k1_backend::tap_branch_hash(&left, &right);
618
619        assert_eq!(hash.len(), 32);
620
621        let hash2 = crate::secp256k1_backend::tap_branch_hash(&right, &left);
622        assert_ne!(hash, hash2);
623    }
624
625    #[test]
626    fn test_encode_varint_small() {
627        let encoded = encode_varint(0xfc);
628        assert_eq!(encoded, vec![0xfc]);
629    }
630
631    #[test]
632    fn test_encode_varint_medium() {
633        let encoded = encode_varint(0x1000);
634        assert_eq!(encoded.len(), 3);
635        assert_eq!(encoded[0], 0xfd);
636    }
637
638    #[test]
639    fn test_encode_varint_large() {
640        let encoded = encode_varint(0x1000000);
641        assert_eq!(encoded.len(), 5);
642        assert_eq!(encoded[0], 0xfe);
643    }
644
645    #[test]
646    fn test_encode_varint_huge() {
647        let encoded = encode_varint(0x1000000000000000);
648        assert_eq!(encoded.len(), 9);
649        assert_eq!(encoded[0], 0xff);
650    }
651
652    #[test]
653    fn test_extract_taproot_output_key_invalid_script() {
654        let script = vec![0x52, 0x20]; // Invalid script
655        let result = extract_taproot_output_key(&script).unwrap();
656        assert!(result.is_none());
657    }
658
659    #[test]
660    fn test_is_taproot_output_false() {
661        let output = TransactionOutput {
662            value: 1000,
663            script_pubkey: vec![0x52, 0x20], // Not a Taproot script
664        };
665
666        assert!(!is_taproot_output(&output));
667    }
668
669    #[test]
670    fn test_validate_taproot_transaction_no_taproot_outputs() {
671        let tx = Transaction {
672            version: 1,
673            inputs: vec![TransactionInput {
674                prevout: OutPoint {
675                    hash: [0; 32].into(),
676                    index: 0,
677                },
678                script_sig: vec![],
679                sequence: 0xffffffff,
680            }]
681            .into(),
682            outputs: vec![TransactionOutput {
683                value: 1000,
684                script_pubkey: vec![0x52].into(), // Not Taproot
685            }]
686            .into(),
687            lock_time: 0,
688        };
689
690        // No witness needed for non-Taproot transaction
691        assert!(validate_taproot_transaction(&tx, None).unwrap());
692    }
693
694    #[test]
695    fn test_validate_taproot_transaction_invalid_taproot_output() {
696        // Create a transaction with a valid Taproot script
697        let tx = Transaction {
698            version: 1,
699            inputs: vec![TransactionInput {
700                prevout: OutPoint {
701                    hash: [0; 32].into(),
702                    index: 0,
703                },
704                script_sig: vec![],
705                sequence: 0xffffffff,
706            }]
707            .into(),
708            outputs: vec![TransactionOutput {
709                value: 1000,
710                script_pubkey: create_taproot_script(&[1u8; 32].into()),
711            }]
712            .into(),
713            lock_time: 0,
714        };
715
716        // Key path spend: single signature
717        let witness = Some(vec![vec![0u8; 64]]);
718        assert!(validate_taproot_transaction(&tx, witness.as_ref()).unwrap());
719    }
720
721    // Helper function
722    fn create_taproot_script(output_key: &[u8; 32]) -> ByteString {
723        let mut script = vec![TAPROOT_SCRIPT_PREFIX];
724        script.extend_from_slice(output_key);
725        script.push(0x00); // Add extra byte to make it 34 bytes total
726        script
727    }
728}
729
730#[cfg(test)]
731mod property_tests {
732    use super::*;
733    use proptest::prelude::*;
734
735    /// Property test: Taproot script validation is deterministic
736    ///
737    /// Mathematical specification:
738    /// ∀ script ∈ ByteString: validate_taproot_script(script) is deterministic
739    proptest! {
740        #[test]
741        fn prop_validate_taproot_script_deterministic(
742            script in prop::collection::vec(any::<u8>(), 0..50)
743        ) {
744            let result1 = validate_taproot_script(&script).unwrap();
745            let result2 = validate_taproot_script(&script).unwrap();
746
747            assert_eq!(result1, result2);
748        }
749    }
750
751    /// Property test: Taproot output key extraction is correct
752    ///
753    /// Mathematical specification:
754    /// ∀ script ∈ ByteString: if validate_taproot_script(script) = true
755    /// then extract_taproot_output_key(script) returns Some(key)
756    proptest! {
757        #[test]
758        fn prop_extract_taproot_output_key_correct(
759            script in prop::collection::vec(any::<u8>(), 0..50)
760        ) {
761            let extracted_key = extract_taproot_output_key(&script).unwrap();
762            let is_valid = validate_taproot_script(&script).unwrap();
763
764            if is_valid {
765                assert!(extracted_key.is_some());
766                let key = extracted_key.unwrap();
767                assert_eq!(key.len(), 32);
768            } else {
769                assert!(extracted_key.is_none());
770            }
771        }
772    }
773
774    /// Property test: Taproot key aggregation is deterministic
775    ///
776    /// Mathematical specification:
777    /// ∀ internal_pubkey ∈ [u8; 32], merkle_root ∈ Hash:
778    /// compute_taproot_tweak(internal_pubkey, merkle_root) is deterministic
779    proptest! {
780        #[test]
781        fn prop_taproot_key_aggregation_deterministic(
782            internal_pubkey in create_pubkey_strategy(),
783            merkle_root in create_hash_strategy()
784        ) {
785            let result1 = compute_taproot_tweak(&internal_pubkey, &merkle_root);
786            let result2 = compute_taproot_tweak(&internal_pubkey, &merkle_root);
787
788            assert_eq!(result1.is_ok(), result2.is_ok());
789            if result1.is_ok() && result2.is_ok() {
790                assert_eq!(result1.unwrap(), result2.unwrap());
791            }
792        }
793    }
794
795    /// Property test: Taproot script path validation is deterministic
796    ///
797    /// Mathematical specification:
798    /// ∀ script ∈ ByteString, merkle_proof ∈ [Hash], merkle_root ∈ Hash:
799    /// validate_taproot_script_path(script, merkle_proof, merkle_root) is deterministic
800    proptest! {
801        #[test]
802        fn prop_validate_taproot_script_path_deterministic(
803            script in prop::collection::vec(any::<u8>(), 0..20),
804            merkle_proof in prop::collection::vec(create_hash_strategy(), 0..5),
805            merkle_root in create_hash_strategy()
806        ) {
807            let result1 = validate_taproot_script_path(&script, &merkle_proof, &merkle_root);
808            let result2 = validate_taproot_script_path(&script, &merkle_proof, &merkle_root);
809
810            assert_eq!(result1.is_ok(), result2.is_ok());
811            if result1.is_ok() && result2.is_ok() {
812                assert_eq!(result1.unwrap(), result2.unwrap());
813            }
814        }
815    }
816
817    /// Property test: Taproot signature hash computation is deterministic
818    ///
819    /// Mathematical specification:
820    /// ∀ tx ∈ Transaction, input_index ∈ ℕ, prevouts ∈ [TransactionOutput], sighash_type ∈ ℕ:
821    /// compute_taproot_signature_hash(tx, input_index, prevouts, sighash_type) is deterministic
822    proptest! {
823        #[test]
824        fn prop_compute_taproot_signature_hash_deterministic(
825            tx in create_transaction_strategy(),
826            input_index in 0..10usize,
827            prevouts in prop::collection::vec(create_output_strategy(), 0..5),
828            sighash_type in any::<u8>()
829        ) {
830            let prevout_values: Vec<i64> = prevouts.iter().map(|p| p.value).collect();
831            let prevout_script_pubkeys: Vec<&[u8]> = prevouts.iter().map(|p| p.script_pubkey.as_slice()).collect();
832            let result1 = compute_taproot_signature_hash(&tx, input_index, &prevout_values, &prevout_script_pubkeys, sighash_type);
833            let result2 = compute_taproot_signature_hash(&tx, input_index, &prevout_values, &prevout_script_pubkeys, sighash_type);
834
835            assert_eq!(result1.is_ok(), result2.is_ok());
836            if let (Ok(hash1), Ok(hash2)) = (&result1, &result2) {
837                assert_eq!(hash1, hash2);
838                assert_eq!(hash1.len(), 32);
839            }
840
841            // Hash should be 32 bytes if result is Ok
842            if let Ok(ref hash) = result1 {
843                assert_eq!(hash.len(), 32);
844            }
845        }
846    }
847
848    /// Property test: Taproot output detection is consistent
849    ///
850    /// Mathematical specification:
851    /// ∀ output ∈ TransactionOutput: is_taproot_output(output) ∈ {true, false}
852    proptest! {
853        #[test]
854        fn prop_is_taproot_output_consistent(
855            output in create_output_strategy()
856        ) {
857            let is_taproot = is_taproot_output(&output);
858            // Just test it returns a boolean (is_taproot is either true or false)
859            let _ = is_taproot;
860        }
861    }
862
863    /// Property test: Taproot transaction validation is deterministic
864    ///
865    /// Mathematical specification:
866    /// ∀ tx ∈ Transaction: validate_taproot_transaction(tx) is deterministic
867    proptest! {
868        #[test]
869        fn prop_validate_taproot_transaction_deterministic(
870            tx in create_transaction_strategy()
871        ) {
872            let result1 = validate_taproot_transaction(&tx, None).unwrap();
873            let result2 = validate_taproot_transaction(&tx, None).unwrap();
874
875            assert_eq!(result1, result2);
876        }
877    }
878
879    /// Property test: TapLeaf hashing is deterministic
880    proptest! {
881        #[test]
882        fn prop_tap_leaf_hash_deterministic(
883            script in prop::collection::vec(any::<u8>(), 0..20)
884        ) {
885            let hash1 = crate::secp256k1_backend::tap_leaf_hash(TAPROOT_LEAF_VERSION_TAPSCRIPT, &script);
886            let hash2 = crate::secp256k1_backend::tap_leaf_hash(TAPROOT_LEAF_VERSION_TAPSCRIPT, &script);
887
888            assert_eq!(hash1, hash2);
889            assert_eq!(hash1.len(), 32);
890        }
891    }
892
893    /// Property test: TapBranch hashing is deterministic
894    proptest! {
895        #[test]
896        fn prop_tap_branch_hash_deterministic(
897            left in create_hash_strategy(),
898            right in create_hash_strategy()
899        ) {
900            let hash1 = crate::secp256k1_backend::tap_branch_hash(&left, &right);
901            let hash2 = crate::secp256k1_backend::tap_branch_hash(&left, &right);
902
903            assert_eq!(hash1, hash2);
904            assert_eq!(hash1.len(), 32);
905        }
906    }
907
908    /// Property test: Varint encoding is deterministic
909    ///
910    /// Mathematical specification:
911    /// ∀ value ∈ ℕ: encode_varint(value) is deterministic
912    proptest! {
913        #[test]
914        fn prop_encode_varint_deterministic(
915            value in 0..u64::MAX
916        ) {
917            let encoded1 = encode_varint(value);
918            let encoded2 = encode_varint(value);
919
920            assert_eq!(encoded1, encoded2);
921
922            // Encoded length should be reasonable
923            assert!(!encoded1.is_empty());
924            assert!(encoded1.len() <= 9);
925        }
926    }
927
928    /// Property test: Varint encoding preserves value
929    ///
930    /// Mathematical specification:
931    /// ∀ value ∈ ℕ: decode_varint(encode_varint(value)) = value
932    proptest! {
933        #[test]
934        fn prop_encode_varint_preserves_value(
935            value in 0..1000000u64  // Smaller range for tractability
936        ) {
937            let encoded = encode_varint(value);
938
939            // Basic validation of encoding format
940            match encoded.len() {
941                1 => {
942                    // Single byte encoding
943                    assert!(value < 0xfd);
944                    assert_eq!(encoded[0], value as u8);
945                },
946                3 => {
947                    // 2-byte encoding
948                    assert!((0xfd..=0xffff).contains(&value));
949                    assert_eq!(encoded[0], 0xfd);
950                },
951                5 => {
952                    // 4-byte encoding
953                    assert!(value > 0xffff && value <= 0xffffffff);
954                    assert_eq!(encoded[0], 0xfe);
955                },
956                9 => {
957                    // 8-byte encoding
958                    assert!(value > 0xffffffff);
959                    assert_eq!(encoded[0], 0xff);
960                },
961                _ => panic!("Invalid varint encoding length"),
962            }
963        }
964    }
965
966    /// Property test: Taproot script path validation with correct proof
967    ///
968    /// Mathematical specification:
969    /// ∀ script ∈ ByteString, merkle_proof ∈ [Hash]:
970    /// If computed_root = compute_script_merkle_root(script, merkle_proof)
971    /// then validate_taproot_script_path(script, merkle_proof, computed_root) = true
972    proptest! {
973        #[test]
974        fn prop_validate_taproot_script_path_correct_proof(
975            script in prop::collection::vec(any::<u8>(), 0..20),
976            merkle_proof in prop::collection::vec(create_hash_strategy(), 0..5)
977        ) {
978            let computed_root = compute_script_merkle_root(&script, &merkle_proof, TAPROOT_LEAF_VERSION_TAPSCRIPT).unwrap();
979            let is_valid = validate_taproot_script_path(&script, &merkle_proof, &computed_root).unwrap();
980
981            assert!(is_valid);
982        }
983    }
984
985    // Property test strategies
986    fn create_transaction_strategy() -> impl Strategy<Value = Transaction> {
987        (
988            prop::collection::vec(any::<u8>(), 0..10), // inputs
989            prop::collection::vec(any::<u8>(), 0..10), // outputs
990        )
991            .prop_map(|(input_data, output_data)| {
992                let mut inputs = Vec::new();
993                for (i, _) in input_data.iter().enumerate() {
994                    inputs.push(TransactionInput {
995                        prevout: OutPoint {
996                            hash: [0; 32],
997                            index: i as u32,
998                        },
999                        script_sig: vec![],
1000                        sequence: 0xffffffff,
1001                    });
1002                }
1003
1004                let mut outputs = Vec::new();
1005                for _ in output_data {
1006                    outputs.push(TransactionOutput {
1007                        value: 1000,
1008                        script_pubkey: vec![0x51],
1009                    });
1010                }
1011
1012                Transaction {
1013                    version: 1,
1014                    inputs: inputs.into(),
1015                    outputs: outputs.into(),
1016                    lock_time: 0,
1017                }
1018            })
1019    }
1020
1021    fn create_output_strategy() -> impl Strategy<Value = TransactionOutput> {
1022        (any::<i64>(), prop::collection::vec(any::<u8>(), 0..50)).prop_map(|(value, script)| {
1023            TransactionOutput {
1024                value,
1025                script_pubkey: script,
1026            }
1027        })
1028    }
1029
1030    fn create_hash_strategy() -> impl Strategy<Value = Hash> {
1031        prop::collection::vec(any::<u8>(), 32..=32).prop_map(|bytes| {
1032            let mut hash = [0u8; 32];
1033            hash.copy_from_slice(&bytes);
1034            hash
1035        })
1036    }
1037
1038    fn create_pubkey_strategy() -> impl Strategy<Value = [u8; 32]> {
1039        prop::collection::vec(any::<u8>(), 32..=32).prop_map(|bytes| {
1040            let mut pubkey = [0u8; 32];
1041            pubkey.copy_from_slice(&bytes);
1042            pubkey
1043        })
1044    }
1045}