Skip to main content

blvm_consensus/
witness.rs

1//! Unified witness validation framework for SegWit (BIP141) and Taproot (BIP340/341/342)
2//!
3//! Provides shared functions for witness structure validation, weight calculation,
4//! and witness data handling that are common to both SegWit and Taproot.
5
6use crate::error::Result;
7use crate::opcodes::*;
8use crate::types::*;
9use blvm_spec_lock::spec_locked;
10
11/// Witness Data: 𝒲 = 𝕊* (stack of witness elements)
12///
13/// Re-export from primitives for backward compatibility.
14/// Witness validation logic stays in this module.
15pub use crate::types::Witness;
16
17/// Witness version for SegWit (v0) and Taproot (v1)
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum WitnessVersion {
20    /// SegWit version 0
21    SegWitV0 = 0,
22    /// Taproot version 1
23    TaprootV1 = 1,
24}
25
26/// Validate witness structure for SegWit
27///
28/// BIP141: Witness must be a vector of byte strings (stack elements).
29/// Each element can be up to MAX_SCRIPT_ELEMENT_SIZE bytes.
30#[spec_locked("11.1.2", "ValidateSegWitWitnessStructure")]
31pub fn validate_segwit_witness_structure(witness: &Witness) -> Result<bool> {
32    // Check each witness element size
33    // BIP141: Each witness element can be up to 520 bytes (MAX_SCRIPT_ELEMENT_SIZE)
34    // Using 520 as the limit per Bitcoin consensus rules
35    const MAX_WITNESS_ELEMENT_SIZE: usize = 520;
36    for element in witness {
37        if element.len() > MAX_WITNESS_ELEMENT_SIZE {
38            return Ok(false);
39        }
40    }
41    Ok(true)
42}
43
44/// Validate witness structure for Taproot
45///
46/// BIP341: Taproot witness structure depends on spending path:
47/// - Key path: single signature (64 bytes)
48/// - Script path: script, control block (33 + 32n bytes), and witness items
49#[spec_locked("11.2.4", "ValidateTaprootWitnessStructure")]
50pub fn validate_taproot_witness_structure(witness: &Witness, is_script_path: bool) -> Result<bool> {
51    if witness.is_empty() {
52        return Ok(false);
53    }
54
55    if is_script_path {
56        // Script path: at least script + control block
57        if witness.len() < 2 {
58            return Ok(false);
59        }
60
61        // Control block must be at least 33 bytes (internal key + leaf version + parity)
62        let control_block = &witness[witness.len() - 1];
63        if control_block.len() < 33 {
64            return Ok(false);
65        }
66
67        // Control block size: 33 + 32n (where n is number of merkle proof levels)
68        // Must be valid multiple
69        if (control_block.len() - 33) % 32 != 0 {
70            return Ok(false);
71        }
72    } else {
73        // Key path: single Schnorr signature (64 bytes)
74        if witness.len() != 1 {
75            return Ok(false);
76        }
77        if witness[0].len() != 64 {
78            return Ok(false);
79        }
80    }
81
82    Ok(true)
83}
84
85/// Calculate transaction weight using SegWit formula
86///
87/// BIP141: Weight(tx) = 3 × BaseSize(tx) + TotalSize(tx)
88/// BaseSize: Transaction size without witness data
89/// TotalSize: Transaction size with witness data
90#[spec_locked("11.1.1", "CalculateTransactionWeight")]
91pub fn calculate_transaction_weight_segwit(base_size: Natural, total_size: Natural) -> Natural {
92    3 * base_size + total_size
93}
94
95/// Calculate virtual size (vsize) from weight
96///
97/// BIP141: vsize = ceil(weight / 4)
98/// Used for fee calculation in SegWit transactions
99///
100/// Mathematical specification:
101/// - vsize = ⌈weight / 4⌉
102#[spec_locked("11.1.1", "WeightToVSize")]
103pub fn weight_to_vsize(weight: Natural) -> Natural {
104    let result = weight.div_ceil(4);
105
106    // Runtime assertion: Verify ceiling division property
107    // vsize must be >= weight / 4 (ceiling property)
108    let weight_div_4 = weight / 4;
109    debug_assert!(
110        result >= weight_div_4,
111        "Vsize ({result}) must be >= weight / 4 ({weight_div_4})"
112    );
113
114    // Runtime assertion: vsize must be <= (weight / 4) + 1 (ceiling property)
115    // Note: When weight % 4 == 0, result == weight/4, otherwise result == (weight/4) + 1
116    let weight_div_4_plus_1 = weight_div_4 + 1;
117    debug_assert!(
118        result <= weight_div_4_plus_1,
119        "Vsize ({result}) must be <= (weight / 4) + 1 ({weight_div_4_plus_1})"
120    );
121
122    // Natural is always non-negative - no assertion needed
123
124    result
125}
126
127/// Validate witness version in scriptPubKey
128///
129/// Shared function for extracting and validating witness version
130/// from SegWit v0 (OP_0 <witness-program>) or Taproot v1 (OP_1 <witness-program>)
131#[spec_locked("11.1.3", "ExtractWitnessVersion")]
132pub fn extract_witness_version(script: &ByteString) -> Option<WitnessVersion> {
133    // BIP141 §witness_program: scriptPubKey must be exactly [version, push, program_bytes]
134    // where program_bytes is 2–40 bytes. Minimum valid length is 4 bytes (version + push + 2).
135    if script.len() < 4 {
136        return None;
137    }
138
139    // Validate the push opcode encodes a 2–40 byte program.
140    let push_opcode = script[1];
141    let program_len = push_opcode as usize; // direct push opcodes (0x02..=0x28) encode their length
142    if !(2..=40).contains(&program_len) {
143        return None;
144    }
145    // The script must be exactly version(1) + push(1) + program_bytes
146    if script.len() != 2 + program_len {
147        return None;
148    }
149
150    match script[0] {
151        OP_1 => Some(WitnessVersion::TaprootV1),
152        OP_0 => Some(WitnessVersion::SegWitV0),
153        _ => None,
154    }
155}
156
157/// Extract witness program from scriptPubKey
158///
159/// For SegWit v0: Returns bytes after OP_0 and push opcode
160/// For Taproot v1: Returns bytes after OP_1 and push opcode
161///
162/// Format: [version_opcode, push_opcode, program_bytes]
163/// Returns: program_bytes (without push opcode)
164#[spec_locked("11.1.3", "ExtractWitnessProgram")]
165pub fn extract_witness_program(
166    script: &ByteString,
167    _version: WitnessVersion,
168) -> Option<ByteString> {
169    if script.len() < 3 {
170        return None;
171    }
172
173    // Skip version opcode (1 byte) and push opcode (1 byte)
174    // The push opcode tells us how many bytes follow
175    let push_opcode = script[1];
176    let program_start = 2;
177
178    // For P2WPKH: push_opcode is PUSH_20_BYTES (push 20 bytes)
179    // For P2WSH: push_opcode is PUSH_32_BYTES (push 32 bytes)
180    // For P2TR: push_opcode is PUSH_32_BYTES (push 32 bytes)
181    // Return the program bytes (after the push opcode)
182    if script.len() < program_start + (push_opcode as usize) {
183        return None;
184    }
185
186    Some(script[program_start..program_start + (push_opcode as usize)].to_vec())
187}
188
189/// Validate witness program length
190///
191/// BIP141: SegWit v0 programs are 20 or 32 bytes (P2WPKH or P2WSH)
192/// BIP341: Taproot v1 programs are 32 bytes (P2TR)
193#[spec_locked("11.1.3", "ValidateWitnessProgramLength")]
194pub fn validate_witness_program_length(program: &ByteString, version: WitnessVersion) -> bool {
195    use crate::constants::{SEGWIT_P2WPKH_LENGTH, SEGWIT_P2WSH_LENGTH, TAPROOT_PROGRAM_LENGTH};
196
197    match version {
198        WitnessVersion::SegWitV0 => {
199            // P2WPKH: 20 bytes, P2WSH: 32 bytes
200            program.len() == SEGWIT_P2WPKH_LENGTH || program.len() == SEGWIT_P2WSH_LENGTH
201        }
202        WitnessVersion::TaprootV1 => {
203            // P2TR: 32 bytes
204            program.len() == TAPROOT_PROGRAM_LENGTH
205        }
206    }
207}
208
209/// Check if witness is empty (non-witness transaction)
210#[spec_locked("11.1.2", "IsWitnessEmpty")]
211pub fn is_witness_empty(witness: &Witness) -> bool {
212    witness.is_empty() || witness.iter().all(|elem| elem.is_empty())
213}
214
215#[cfg(test)]
216mod tests {
217    use super::*;
218
219    #[test]
220    fn test_validate_segwit_witness_structure() {
221        let witness = vec![
222            vec![0x01; 20], // P2WPKH witness
223            vec![0x02; 72], // Signature
224        ];
225        assert!(validate_segwit_witness_structure(&witness).unwrap());
226
227        // Too large element
228        let invalid_witness = vec![vec![0x01; crate::constants::MAX_SCRIPT_ELEMENT_SIZE + 1]];
229        assert!(!validate_segwit_witness_structure(&invalid_witness).unwrap());
230    }
231
232    #[test]
233    fn test_validate_taproot_witness_structure_key_path() {
234        // Key path: single 64-byte signature
235        let witness = vec![vec![0x01; 64]];
236        assert!(validate_taproot_witness_structure(&witness, false).unwrap());
237
238        // Invalid: wrong length
239        let invalid = vec![vec![0x01; 63]];
240        assert!(!validate_taproot_witness_structure(&invalid, false).unwrap());
241
242        // Invalid: multiple elements
243        let invalid2 = vec![vec![0x01; 64], vec![0x02; 32]];
244        assert!(!validate_taproot_witness_structure(&invalid2, false).unwrap());
245    }
246
247    #[test]
248    fn test_validate_taproot_witness_structure_script_path() {
249        // Script path: script + control block (33 bytes minimum)
250        let witness = vec![
251            vec![OP_1],    // Script
252            vec![0u8; 33], // Control block (internal key + leaf version + parity)
253        ];
254        assert!(validate_taproot_witness_structure(&witness, true).unwrap());
255
256        // Invalid: control block too small
257        let invalid = vec![vec![OP_1], vec![0u8; 32]];
258        assert!(!validate_taproot_witness_structure(&invalid, true).unwrap());
259
260        // Invalid: only one element
261        let invalid2 = vec![vec![OP_1]];
262        assert!(!validate_taproot_witness_structure(&invalid2, true).unwrap());
263    }
264
265    #[test]
266    fn test_calculate_transaction_weight_segwit() {
267        let base_size = 100;
268        let total_size = 150;
269        let weight = calculate_transaction_weight_segwit(base_size, total_size);
270        assert_eq!(weight, 3 * base_size + total_size); // BIP141
271    }
272
273    #[test]
274    fn test_weight_to_vsize() {
275        assert_eq!(weight_to_vsize(400), 100); // Exact division
276        assert_eq!(weight_to_vsize(401), 101); // Ceiling
277        assert_eq!(weight_to_vsize(403), 101); // Ceiling
278        assert_eq!(weight_to_vsize(404), 101); // Ceiling
279    }
280
281    #[test]
282    fn test_extract_witness_version() {
283        // P2WPKH: OP_0 PUSH_20 <20 bytes> — must be exactly 22 bytes (BIP141).
284        let mut segwit_script = vec![OP_0, PUSH_20_BYTES];
285        segwit_script.extend([0x01u8; 20]);
286        assert_eq!(
287            extract_witness_version(&segwit_script),
288            Some(WitnessVersion::SegWitV0)
289        );
290
291        // P2TR: OP_1 PUSH_32 <32 bytes> — must be exactly 34 bytes (BIP341).
292        let mut taproot_script = vec![OP_1, PUSH_32_BYTES];
293        taproot_script.extend([0x02u8; 32]);
294        assert_eq!(
295            extract_witness_version(&taproot_script),
296            Some(WitnessVersion::TaprootV1)
297        );
298
299        let non_witness_script = vec![OP_DUP, OP_HASH160]; // OP_DUP OP_HASH160
300        assert_eq!(extract_witness_version(&non_witness_script), None);
301    }
302
303    #[test]
304    fn test_extract_witness_program() {
305        // P2WPKH format: [OP_0, PUSH_20_BYTES, <20-byte-hash>]
306        // Where OP_0 is OP_0 (witness version), PUSH_20_BYTES is push 20 bytes, then 20 bytes of hash
307        // extract_witness_program should return just the program bytes (after push opcode)
308        // Note: 0x01 to PUSH_20_BYTES is 20 bytes (1, 2, 3, ..., 20)
309        let segwit_script = vec![
310            OP_0,
311            PUSH_20_BYTES,
312            0x01,
313            0x02,
314            0x03,
315            0x04,
316            0x05,
317            0x06,
318            0x07,
319            0x08,
320            0x09,
321            0x0a,
322            0x0b,
323            0x0c,
324            0x0d,
325            0x0e,
326            0x0f,
327            0x10,
328            0x11,
329            0x12,
330            0x13,
331            PUSH_20_BYTES,
332        ];
333        let program = extract_witness_program(&segwit_script, WitnessVersion::SegWitV0);
334        // Should return the 20 bytes after the push opcode (PUSH_20_BYTES)
335        assert_eq!(
336            program,
337            Some(vec![
338                0x01,
339                0x02,
340                0x03,
341                0x04,
342                0x05,
343                0x06,
344                0x07,
345                0x08,
346                0x09,
347                0x0a,
348                0x0b,
349                0x0c,
350                0x0d,
351                0x0e,
352                0x0f,
353                0x10,
354                0x11,
355                0x12,
356                0x13,
357                PUSH_20_BYTES
358            ])
359        );
360    }
361
362    #[test]
363    fn test_validate_witness_program_length() {
364        let p2wpkh = vec![0u8; 20]; // 20 bytes
365        assert!(validate_witness_program_length(
366            &p2wpkh,
367            WitnessVersion::SegWitV0
368        ));
369
370        let p2wsh = vec![0u8; 32]; // 32 bytes
371        assert!(validate_witness_program_length(
372            &p2wsh,
373            WitnessVersion::SegWitV0
374        ));
375
376        let p2tr = vec![0u8; 32]; // 32 bytes
377        assert!(validate_witness_program_length(
378            &p2tr,
379            WitnessVersion::TaprootV1
380        ));
381
382        let invalid = vec![0u8; 33];
383        assert!(!validate_witness_program_length(
384            &invalid,
385            WitnessVersion::SegWitV0
386        ));
387        assert!(!validate_witness_program_length(
388            &invalid,
389            WitnessVersion::TaprootV1
390        ));
391    }
392
393    #[test]
394    fn test_is_witness_empty() {
395        assert!(is_witness_empty(&vec![]));
396        assert!(is_witness_empty(&vec![vec![]]));
397        assert!(!is_witness_empty(&vec![vec![0x01]]));
398    }
399}