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")]
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")]
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")]
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")]
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")]
132pub fn extract_witness_version(script: &ByteString) -> Option<WitnessVersion> {
133    if script.is_empty() {
134        return None;
135    }
136
137    match script[0] {
138        OP_1 => Some(WitnessVersion::TaprootV1),
139        OP_0 => Some(WitnessVersion::SegWitV0),
140        _ => None,
141    }
142}
143
144/// Extract witness program from scriptPubKey
145///
146/// For SegWit v0: Returns bytes after OP_0 and push opcode
147/// For Taproot v1: Returns bytes after OP_1 and push opcode
148///
149/// Format: [version_opcode, push_opcode, program_bytes]
150/// Returns: program_bytes (without push opcode)
151#[spec_locked("11.1.3")]
152pub fn extract_witness_program(
153    script: &ByteString,
154    _version: WitnessVersion,
155) -> Option<ByteString> {
156    if script.len() < 3 {
157        return None;
158    }
159
160    // Skip version opcode (1 byte) and push opcode (1 byte)
161    // The push opcode tells us how many bytes follow
162    let push_opcode = script[1];
163    let program_start = 2;
164
165    // For P2WPKH: push_opcode is PUSH_20_BYTES (push 20 bytes)
166    // For P2WSH: push_opcode is PUSH_32_BYTES (push 32 bytes)
167    // For P2TR: push_opcode is PUSH_32_BYTES (push 32 bytes)
168    // Return the program bytes (after the push opcode)
169    if script.len() < program_start + (push_opcode as usize) {
170        return None;
171    }
172
173    Some(script[program_start..program_start + (push_opcode as usize)].to_vec())
174}
175
176/// Validate witness program length
177///
178/// BIP141: SegWit v0 programs are 20 or 32 bytes (P2WPKH or P2WSH)
179/// BIP341: Taproot v1 programs are 32 bytes (P2TR)
180#[spec_locked("11.1.3")]
181pub fn validate_witness_program_length(program: &ByteString, version: WitnessVersion) -> bool {
182    use crate::constants::{SEGWIT_P2WPKH_LENGTH, SEGWIT_P2WSH_LENGTH, TAPROOT_PROGRAM_LENGTH};
183
184    match version {
185        WitnessVersion::SegWitV0 => {
186            // P2WPKH: 20 bytes, P2WSH: 32 bytes
187            program.len() == SEGWIT_P2WPKH_LENGTH || program.len() == SEGWIT_P2WSH_LENGTH
188        }
189        WitnessVersion::TaprootV1 => {
190            // P2TR: 32 bytes
191            program.len() == TAPROOT_PROGRAM_LENGTH
192        }
193    }
194}
195
196/// Check if witness is empty (non-witness transaction)
197#[spec_locked("11.1.2")]
198pub fn is_witness_empty(witness: &Witness) -> bool {
199    witness.is_empty() || witness.iter().all(|elem| elem.is_empty())
200}
201
202#[cfg(test)]
203mod tests {
204    use super::*;
205
206    #[test]
207    fn test_validate_segwit_witness_structure() {
208        let witness = vec![
209            vec![0x01; 20], // P2WPKH witness
210            vec![0x02; 72], // Signature
211        ];
212        assert!(validate_segwit_witness_structure(&witness).unwrap());
213
214        // Too large element
215        let invalid_witness = vec![vec![0x01; crate::constants::MAX_SCRIPT_ELEMENT_SIZE + 1]];
216        assert!(!validate_segwit_witness_structure(&invalid_witness).unwrap());
217    }
218
219    #[test]
220    fn test_validate_taproot_witness_structure_key_path() {
221        // Key path: single 64-byte signature
222        let witness = vec![vec![0x01; 64]];
223        assert!(validate_taproot_witness_structure(&witness, false).unwrap());
224
225        // Invalid: wrong length
226        let invalid = vec![vec![0x01; 63]];
227        assert!(!validate_taproot_witness_structure(&invalid, false).unwrap());
228
229        // Invalid: multiple elements
230        let invalid2 = vec![vec![0x01; 64], vec![0x02; 32]];
231        assert!(!validate_taproot_witness_structure(&invalid2, false).unwrap());
232    }
233
234    #[test]
235    fn test_validate_taproot_witness_structure_script_path() {
236        // Script path: script + control block (33 bytes minimum)
237        let witness = vec![
238            vec![OP_1],    // Script
239            vec![0u8; 33], // Control block (internal key + leaf version + parity)
240        ];
241        assert!(validate_taproot_witness_structure(&witness, true).unwrap());
242
243        // Invalid: control block too small
244        let invalid = vec![vec![OP_1], vec![0u8; 32]];
245        assert!(!validate_taproot_witness_structure(&invalid, true).unwrap());
246
247        // Invalid: only one element
248        let invalid2 = vec![vec![OP_1]];
249        assert!(!validate_taproot_witness_structure(&invalid2, true).unwrap());
250    }
251
252    #[test]
253    fn test_calculate_transaction_weight_segwit() {
254        let base_size = 100;
255        let total_size = 150;
256        let weight = calculate_transaction_weight_segwit(base_size, total_size);
257        assert_eq!(weight, 3 * base_size + total_size); // BIP141
258    }
259
260    #[test]
261    fn test_weight_to_vsize() {
262        assert_eq!(weight_to_vsize(400), 100); // Exact division
263        assert_eq!(weight_to_vsize(401), 101); // Ceiling
264        assert_eq!(weight_to_vsize(403), 101); // Ceiling
265        assert_eq!(weight_to_vsize(404), 101); // Ceiling
266    }
267
268    #[test]
269    fn test_extract_witness_version() {
270        let segwit_script = vec![OP_0, PUSH_20_BYTES, 0x01, 0x02, 0x03]; // OP_0 <20-byte-program>
271        assert_eq!(
272            extract_witness_version(&segwit_script),
273            Some(WitnessVersion::SegWitV0)
274        );
275
276        let taproot_script = vec![OP_1, PUSH_32_BYTES]; // OP_1 <32-byte-program>
277        assert_eq!(
278            extract_witness_version(&taproot_script),
279            Some(WitnessVersion::TaprootV1)
280        );
281
282        let non_witness_script = vec![OP_DUP, OP_HASH160]; // OP_DUP OP_HASH160
283        assert_eq!(extract_witness_version(&non_witness_script), None);
284    }
285
286    #[test]
287    fn test_extract_witness_program() {
288        // P2WPKH format: [OP_0, PUSH_20_BYTES, <20-byte-hash>]
289        // Where OP_0 is OP_0 (witness version), PUSH_20_BYTES is push 20 bytes, then 20 bytes of hash
290        // extract_witness_program should return just the program bytes (after push opcode)
291        // Note: 0x01 to PUSH_20_BYTES is 20 bytes (1, 2, 3, ..., 20)
292        let segwit_script = vec![
293            OP_0,
294            PUSH_20_BYTES,
295            0x01,
296            0x02,
297            0x03,
298            0x04,
299            0x05,
300            0x06,
301            0x07,
302            0x08,
303            0x09,
304            0x0a,
305            0x0b,
306            0x0c,
307            0x0d,
308            0x0e,
309            0x0f,
310            0x10,
311            0x11,
312            0x12,
313            0x13,
314            PUSH_20_BYTES,
315        ];
316        let program = extract_witness_program(&segwit_script, WitnessVersion::SegWitV0);
317        // Should return the 20 bytes after the push opcode (PUSH_20_BYTES)
318        assert_eq!(
319            program,
320            Some(vec![
321                0x01,
322                0x02,
323                0x03,
324                0x04,
325                0x05,
326                0x06,
327                0x07,
328                0x08,
329                0x09,
330                0x0a,
331                0x0b,
332                0x0c,
333                0x0d,
334                0x0e,
335                0x0f,
336                0x10,
337                0x11,
338                0x12,
339                0x13,
340                PUSH_20_BYTES
341            ])
342        );
343    }
344
345    #[test]
346    fn test_validate_witness_program_length() {
347        let p2wpkh = vec![0u8; 20]; // 20 bytes
348        assert!(validate_witness_program_length(
349            &p2wpkh,
350            WitnessVersion::SegWitV0
351        ));
352
353        let p2wsh = vec![0u8; 32]; // 32 bytes
354        assert!(validate_witness_program_length(
355            &p2wsh,
356            WitnessVersion::SegWitV0
357        ));
358
359        let p2tr = vec![0u8; 32]; // 32 bytes
360        assert!(validate_witness_program_length(
361            &p2tr,
362            WitnessVersion::TaprootV1
363        ));
364
365        let invalid = vec![0u8; 33];
366        assert!(!validate_witness_program_length(
367            &invalid,
368            WitnessVersion::SegWitV0
369        ));
370        assert!(!validate_witness_program_length(
371            &invalid,
372            WitnessVersion::TaprootV1
373        ));
374    }
375
376    #[test]
377    fn test_is_witness_empty() {
378        assert!(is_witness_empty(&vec![]));
379        assert!(is_witness_empty(&vec![vec![]]));
380        assert!(!is_witness_empty(&vec![vec![0x01]]));
381    }
382}