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 let len = witness[0].len();
78 if len != 64 && len != 65 {
79 return Ok(false);
80 }
81 if len == 65 && witness[0][64] == 0x00 {
82 return Ok(false);
83 }
84 }
85
86 Ok(true)
87}
88
89#[spec_locked("11.1.1", "CalculateTransactionWeight")]
98#[blvm_spec_lock::requires(total_size >= base_size)]
99#[blvm_spec_lock::ensures(result >= total_size)]
100#[blvm_spec_lock::ensures(result >= 4 * base_size)]
101pub fn calculate_transaction_weight_segwit(base_size: Natural, total_size: Natural) -> Natural {
102 3 * base_size + total_size
103}
104
105#[spec_locked("11.1.1", "WeightToVSize")]
115#[blvm_spec_lock::ensures(result * 4 >= weight)]
116#[blvm_spec_lock::ensures(result <= weight)]
117pub fn weight_to_vsize(weight: Natural) -> Natural {
118 let result = weight.div_ceil(4);
119
120 let weight_div_4 = weight / 4;
123 debug_assert!(
124 result >= weight_div_4,
125 "Vsize ({result}) must be >= weight / 4 ({weight_div_4})"
126 );
127
128 let weight_div_4_plus_1 = weight_div_4 + 1;
131 debug_assert!(
132 result <= weight_div_4_plus_1,
133 "Vsize ({result}) must be <= (weight / 4) + 1 ({weight_div_4_plus_1})"
134 );
135
136 result
139}
140
141#[spec_locked("11.1.3", "ExtractWitnessVersion")]
146pub fn extract_witness_version(script: &ByteString) -> Option<WitnessVersion> {
147 if script.len() < 4 {
150 return None;
151 }
152
153 let push_opcode = script[1];
155 let program_len = push_opcode as usize; if !(2..=40).contains(&program_len) {
157 return None;
158 }
159 if script.len() != 2 + program_len {
161 return None;
162 }
163
164 match script[0] {
165 OP_1 => Some(WitnessVersion::TaprootV1),
166 OP_0 => Some(WitnessVersion::SegWitV0),
167 _ => None,
168 }
169}
170
171#[spec_locked("11.1.3", "ExtractWitnessProgram")]
179pub fn extract_witness_program(
180 script: &ByteString,
181 _version: WitnessVersion,
182) -> Option<ByteString> {
183 if script.len() < 4 {
184 return None;
185 }
186
187 let push_opcode = script[1];
188 if !(0x02..=0x28).contains(&push_opcode) {
190 return None;
191 }
192 let program_len = push_opcode as usize;
193 if script.len() != 2 + program_len {
194 return None;
195 }
196
197 Some(script[2..2 + program_len].to_vec())
198}
199
200#[spec_locked("11.1.3", "ValidateWitnessProgramLength")]
208#[blvm_spec_lock::ensures(result == false || program.len() == 20 || program.len() == 32)]
209pub fn validate_witness_program_length(program: &ByteString, version: WitnessVersion) -> bool {
210 use crate::constants::{SEGWIT_P2WPKH_LENGTH, SEGWIT_P2WSH_LENGTH, TAPROOT_PROGRAM_LENGTH};
211
212 match version {
213 WitnessVersion::SegWitV0 => {
214 program.len() == SEGWIT_P2WPKH_LENGTH || program.len() == SEGWIT_P2WSH_LENGTH
216 }
217 WitnessVersion::TaprootV1 => {
218 program.len() == TAPROOT_PROGRAM_LENGTH
220 }
221 }
222}
223
224#[spec_locked("11.1.2", "IsWitnessEmpty")]
229#[blvm_spec_lock::ensures(result == true || witness.len() > 0)]
230pub fn is_witness_empty(witness: &Witness) -> bool {
231 witness.is_empty() || witness.iter().all(|elem| elem.is_empty())
232}
233
234#[cfg(test)]
235mod tests {
236 use super::*;
237
238 #[test]
239 fn test_validate_segwit_witness_structure() {
240 let witness = vec![
241 vec![0x01; 20], vec![0x02; 72], ];
244 assert!(validate_segwit_witness_structure(&witness).unwrap());
245
246 let invalid_witness = vec![vec![0x01; crate::constants::MAX_SCRIPT_ELEMENT_SIZE + 1]];
248 assert!(!validate_segwit_witness_structure(&invalid_witness).unwrap());
249 }
250
251 #[test]
252 fn test_validate_taproot_witness_structure_key_path() {
253 let witness = vec![vec![0x01; 64]];
255 assert!(validate_taproot_witness_structure(&witness, false).unwrap());
256
257 let invalid = vec![vec![0x01; 63]];
259 assert!(!validate_taproot_witness_structure(&invalid, false).unwrap());
260
261 let with_hashtype = vec![
263 vec![0x01; 64]
264 .into_iter()
265 .chain([0x01u8])
266 .collect::<Vec<_>>(),
267 ];
268 assert!(validate_taproot_witness_structure(&with_hashtype, false).unwrap());
269
270 let invalid_hashtype = vec![
272 vec![0x01; 64]
273 .into_iter()
274 .chain([0x00u8])
275 .collect::<Vec<_>>(),
276 ];
277 assert!(!validate_taproot_witness_structure(&invalid_hashtype, false).unwrap());
278
279 let invalid2 = vec![vec![0x01; 64], vec![0x02; 32]];
281 assert!(!validate_taproot_witness_structure(&invalid2, false).unwrap());
282 }
283
284 #[test]
285 fn test_validate_taproot_witness_structure_script_path() {
286 let witness = vec![
288 vec![OP_1], vec![0u8; 33], ];
291 assert!(validate_taproot_witness_structure(&witness, true).unwrap());
292
293 let invalid = vec![vec![OP_1], vec![0u8; 32]];
295 assert!(!validate_taproot_witness_structure(&invalid, true).unwrap());
296
297 let invalid2 = vec![vec![OP_1]];
299 assert!(!validate_taproot_witness_structure(&invalid2, true).unwrap());
300 }
301
302 #[test]
303 fn test_calculate_transaction_weight_segwit() {
304 let base_size = 100;
305 let total_size = 150;
306 let weight = calculate_transaction_weight_segwit(base_size, total_size);
307 assert_eq!(weight, 3 * base_size + total_size); }
309
310 #[test]
311 fn test_weight_to_vsize() {
312 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); }
317
318 #[test]
319 fn test_extract_witness_version() {
320 let mut segwit_script = vec![OP_0, PUSH_20_BYTES];
322 segwit_script.extend([0x01u8; 20]);
323 assert_eq!(
324 extract_witness_version(&segwit_script),
325 Some(WitnessVersion::SegWitV0)
326 );
327
328 let mut taproot_script = vec![OP_1, PUSH_32_BYTES];
330 taproot_script.extend([0x02u8; 32]);
331 assert_eq!(
332 extract_witness_version(&taproot_script),
333 Some(WitnessVersion::TaprootV1)
334 );
335
336 let non_witness_script = vec![OP_DUP, OP_HASH160]; assert_eq!(extract_witness_version(&non_witness_script), None);
338 }
339
340 #[test]
341 fn test_extract_witness_program() {
342 let segwit_script = vec![
347 OP_0,
348 PUSH_20_BYTES,
349 0x01,
350 0x02,
351 0x03,
352 0x04,
353 0x05,
354 0x06,
355 0x07,
356 0x08,
357 0x09,
358 0x0a,
359 0x0b,
360 0x0c,
361 0x0d,
362 0x0e,
363 0x0f,
364 0x10,
365 0x11,
366 0x12,
367 0x13,
368 PUSH_20_BYTES,
369 ];
370 let program = extract_witness_program(&segwit_script, WitnessVersion::SegWitV0);
371 assert_eq!(
373 program,
374 Some(vec![
375 0x01,
376 0x02,
377 0x03,
378 0x04,
379 0x05,
380 0x06,
381 0x07,
382 0x08,
383 0x09,
384 0x0a,
385 0x0b,
386 0x0c,
387 0x0d,
388 0x0e,
389 0x0f,
390 0x10,
391 0x11,
392 0x12,
393 0x13,
394 PUSH_20_BYTES
395 ])
396 );
397 }
398
399 #[test]
400 fn test_extract_witness_program_rejects_non_direct_push() {
401 use crate::opcodes::OP_PUSHDATA1;
402 let bad = vec![OP_0, OP_PUSHDATA1, 0x14u8];
404 assert_eq!(
405 extract_witness_program(&bad, WitnessVersion::SegWitV0),
406 None
407 );
408 }
409
410 #[test]
411 fn test_validate_witness_program_length() {
412 let p2wpkh = vec![0u8; 20]; assert!(validate_witness_program_length(
414 &p2wpkh,
415 WitnessVersion::SegWitV0
416 ));
417
418 let p2wsh = vec![0u8; 32]; assert!(validate_witness_program_length(
420 &p2wsh,
421 WitnessVersion::SegWitV0
422 ));
423
424 let p2tr = vec![0u8; 32]; assert!(validate_witness_program_length(
426 &p2tr,
427 WitnessVersion::TaprootV1
428 ));
429
430 let invalid = vec![0u8; 33];
431 assert!(!validate_witness_program_length(
432 &invalid,
433 WitnessVersion::SegWitV0
434 ));
435 assert!(!validate_witness_program_length(
436 &invalid,
437 WitnessVersion::TaprootV1
438 ));
439 }
440
441 #[test]
442 fn test_is_witness_empty() {
443 assert!(is_witness_empty(&vec![]));
444 assert!(is_witness_empty(&vec![vec![]]));
445 assert!(!is_witness_empty(&vec![vec![0x01]]));
446 }
447}