1use crate::error::Result;
7use crate::opcodes::*;
8use crate::types::*;
9use blvm_spec_lock::spec_locked;
10
11pub use crate::types::Witness;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum WitnessVersion {
20 SegWitV0 = 0,
22 TaprootV1 = 1,
24}
25
26#[spec_locked("11.1.2", "ValidateSegWitWitnessStructure")]
31pub fn validate_segwit_witness_structure(witness: &Witness) -> Result<bool> {
32 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#[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 if witness.len() < 2 {
58 return Ok(false);
59 }
60
61 let control_block = &witness[witness.len() - 1];
63 if control_block.len() < 33 {
64 return Ok(false);
65 }
66
67 if (control_block.len() - 33) % 32 != 0 {
70 return Ok(false);
71 }
72 } else {
73 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#[spec_locked("11.1.1", "CalculateTransactionWeight")]
94#[blvm_spec_lock::requires(total_size >= base_size)]
95#[blvm_spec_lock::ensures(result >= total_size)]
96#[blvm_spec_lock::ensures(result >= 4 * base_size)]
97pub fn calculate_transaction_weight_segwit(base_size: Natural, total_size: Natural) -> Natural {
98 3 * base_size + total_size
99}
100
101#[spec_locked("11.1.1", "WeightToVSize")]
111#[blvm_spec_lock::ensures(result * 4 >= weight)]
112#[blvm_spec_lock::ensures(result <= weight)]
113pub fn weight_to_vsize(weight: Natural) -> Natural {
114 let result = weight.div_ceil(4);
115
116 let weight_div_4 = weight / 4;
119 debug_assert!(
120 result >= weight_div_4,
121 "Vsize ({result}) must be >= weight / 4 ({weight_div_4})"
122 );
123
124 let weight_div_4_plus_1 = weight_div_4 + 1;
127 debug_assert!(
128 result <= weight_div_4_plus_1,
129 "Vsize ({result}) must be <= (weight / 4) + 1 ({weight_div_4_plus_1})"
130 );
131
132 result
135}
136
137#[spec_locked("11.1.3", "ExtractWitnessVersion")]
142pub fn extract_witness_version(script: &ByteString) -> Option<WitnessVersion> {
143 if script.len() < 4 {
146 return None;
147 }
148
149 let push_opcode = script[1];
151 let program_len = push_opcode as usize; if !(2..=40).contains(&program_len) {
153 return None;
154 }
155 if script.len() != 2 + program_len {
157 return None;
158 }
159
160 match script[0] {
161 OP_1 => Some(WitnessVersion::TaprootV1),
162 OP_0 => Some(WitnessVersion::SegWitV0),
163 _ => None,
164 }
165}
166
167#[spec_locked("11.1.3", "ExtractWitnessProgram")]
175pub fn extract_witness_program(
176 script: &ByteString,
177 _version: WitnessVersion,
178) -> Option<ByteString> {
179 if script.len() < 3 {
180 return None;
181 }
182
183 let push_opcode = script[1];
186 let program_start = 2;
187
188 if script.len() < program_start + (push_opcode as usize) {
193 return None;
194 }
195
196 Some(script[program_start..program_start + (push_opcode as usize)].to_vec())
197}
198
199#[spec_locked("11.1.3", "ValidateWitnessProgramLength")]
207#[blvm_spec_lock::ensures(result == false || program.len() == 20 || program.len() == 32)]
208pub fn validate_witness_program_length(program: &ByteString, version: WitnessVersion) -> bool {
209 use crate::constants::{SEGWIT_P2WPKH_LENGTH, SEGWIT_P2WSH_LENGTH, TAPROOT_PROGRAM_LENGTH};
210
211 match version {
212 WitnessVersion::SegWitV0 => {
213 program.len() == SEGWIT_P2WPKH_LENGTH || program.len() == SEGWIT_P2WSH_LENGTH
215 }
216 WitnessVersion::TaprootV1 => {
217 program.len() == TAPROOT_PROGRAM_LENGTH
219 }
220 }
221}
222
223#[spec_locked("11.1.2", "IsWitnessEmpty")]
228#[blvm_spec_lock::ensures(result == true || witness.len() > 0)]
229pub fn is_witness_empty(witness: &Witness) -> bool {
230 witness.is_empty() || witness.iter().all(|elem| elem.is_empty())
231}
232
233#[cfg(test)]
234mod tests {
235 use super::*;
236
237 #[test]
238 fn test_validate_segwit_witness_structure() {
239 let witness = vec![
240 vec![0x01; 20], vec![0x02; 72], ];
243 assert!(validate_segwit_witness_structure(&witness).unwrap());
244
245 let invalid_witness = vec![vec![0x01; crate::constants::MAX_SCRIPT_ELEMENT_SIZE + 1]];
247 assert!(!validate_segwit_witness_structure(&invalid_witness).unwrap());
248 }
249
250 #[test]
251 fn test_validate_taproot_witness_structure_key_path() {
252 let witness = vec![vec![0x01; 64]];
254 assert!(validate_taproot_witness_structure(&witness, false).unwrap());
255
256 let invalid = vec![vec![0x01; 63]];
258 assert!(!validate_taproot_witness_structure(&invalid, false).unwrap());
259
260 let invalid2 = vec![vec![0x01; 64], vec![0x02; 32]];
262 assert!(!validate_taproot_witness_structure(&invalid2, false).unwrap());
263 }
264
265 #[test]
266 fn test_validate_taproot_witness_structure_script_path() {
267 let witness = vec![
269 vec![OP_1], vec![0u8; 33], ];
272 assert!(validate_taproot_witness_structure(&witness, true).unwrap());
273
274 let invalid = vec![vec![OP_1], vec![0u8; 32]];
276 assert!(!validate_taproot_witness_structure(&invalid, true).unwrap());
277
278 let invalid2 = vec![vec![OP_1]];
280 assert!(!validate_taproot_witness_structure(&invalid2, true).unwrap());
281 }
282
283 #[test]
284 fn test_calculate_transaction_weight_segwit() {
285 let base_size = 100;
286 let total_size = 150;
287 let weight = calculate_transaction_weight_segwit(base_size, total_size);
288 assert_eq!(weight, 3 * base_size + total_size); }
290
291 #[test]
292 fn test_weight_to_vsize() {
293 assert_eq!(weight_to_vsize(400), 100); assert_eq!(weight_to_vsize(401), 101); assert_eq!(weight_to_vsize(403), 101); assert_eq!(weight_to_vsize(404), 101); }
298
299 #[test]
300 fn test_extract_witness_version() {
301 let mut segwit_script = vec![OP_0, PUSH_20_BYTES];
303 segwit_script.extend([0x01u8; 20]);
304 assert_eq!(
305 extract_witness_version(&segwit_script),
306 Some(WitnessVersion::SegWitV0)
307 );
308
309 let mut taproot_script = vec![OP_1, PUSH_32_BYTES];
311 taproot_script.extend([0x02u8; 32]);
312 assert_eq!(
313 extract_witness_version(&taproot_script),
314 Some(WitnessVersion::TaprootV1)
315 );
316
317 let non_witness_script = vec![OP_DUP, OP_HASH160]; assert_eq!(extract_witness_version(&non_witness_script), None);
319 }
320
321 #[test]
322 fn test_extract_witness_program() {
323 let segwit_script = vec![
328 OP_0,
329 PUSH_20_BYTES,
330 0x01,
331 0x02,
332 0x03,
333 0x04,
334 0x05,
335 0x06,
336 0x07,
337 0x08,
338 0x09,
339 0x0a,
340 0x0b,
341 0x0c,
342 0x0d,
343 0x0e,
344 0x0f,
345 0x10,
346 0x11,
347 0x12,
348 0x13,
349 PUSH_20_BYTES,
350 ];
351 let program = extract_witness_program(&segwit_script, WitnessVersion::SegWitV0);
352 assert_eq!(
354 program,
355 Some(vec![
356 0x01,
357 0x02,
358 0x03,
359 0x04,
360 0x05,
361 0x06,
362 0x07,
363 0x08,
364 0x09,
365 0x0a,
366 0x0b,
367 0x0c,
368 0x0d,
369 0x0e,
370 0x0f,
371 0x10,
372 0x11,
373 0x12,
374 0x13,
375 PUSH_20_BYTES
376 ])
377 );
378 }
379
380 #[test]
381 fn test_validate_witness_program_length() {
382 let p2wpkh = vec![0u8; 20]; assert!(validate_witness_program_length(
384 &p2wpkh,
385 WitnessVersion::SegWitV0
386 ));
387
388 let p2wsh = vec![0u8; 32]; assert!(validate_witness_program_length(
390 &p2wsh,
391 WitnessVersion::SegWitV0
392 ));
393
394 let p2tr = vec![0u8; 32]; assert!(validate_witness_program_length(
396 &p2tr,
397 WitnessVersion::TaprootV1
398 ));
399
400 let invalid = vec![0u8; 33];
401 assert!(!validate_witness_program_length(
402 &invalid,
403 WitnessVersion::SegWitV0
404 ));
405 assert!(!validate_witness_program_length(
406 &invalid,
407 WitnessVersion::TaprootV1
408 ));
409 }
410
411 #[test]
412 fn test_is_witness_empty() {
413 assert!(is_witness_empty(&vec![]));
414 assert!(is_witness_empty(&vec![vec![]]));
415 assert!(!is_witness_empty(&vec![vec![0x01]]));
416 }
417}