use crate::error::Result;
use crate::opcodes::*;
use crate::types::*;
use blvm_spec_lock::spec_locked;
pub use crate::types::Witness;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WitnessVersion {
SegWitV0 = 0,
TaprootV1 = 1,
}
#[spec_locked("11.1.2")]
pub fn validate_segwit_witness_structure(witness: &Witness) -> Result<bool> {
const MAX_WITNESS_ELEMENT_SIZE: usize = 520;
for element in witness {
if element.len() > MAX_WITNESS_ELEMENT_SIZE {
return Ok(false);
}
}
Ok(true)
}
#[spec_locked("11.2")]
pub fn validate_taproot_witness_structure(witness: &Witness, is_script_path: bool) -> Result<bool> {
if witness.is_empty() {
return Ok(false);
}
if is_script_path {
if witness.len() < 2 {
return Ok(false);
}
let control_block = &witness[witness.len() - 1];
if control_block.len() < 33 {
return Ok(false);
}
if (control_block.len() - 33) % 32 != 0 {
return Ok(false);
}
} else {
if witness.len() != 1 {
return Ok(false);
}
if witness[0].len() != 64 {
return Ok(false);
}
}
Ok(true)
}
#[spec_locked("11.1.1")]
pub fn calculate_transaction_weight_segwit(base_size: Natural, total_size: Natural) -> Natural {
3 * base_size + total_size
}
#[spec_locked("11.1.1")]
pub fn weight_to_vsize(weight: Natural) -> Natural {
let result = weight.div_ceil(4);
let weight_div_4 = weight / 4;
debug_assert!(
result >= weight_div_4,
"Vsize ({result}) must be >= weight / 4 ({weight_div_4})"
);
let weight_div_4_plus_1 = weight_div_4 + 1;
debug_assert!(
result <= weight_div_4_plus_1,
"Vsize ({result}) must be <= (weight / 4) + 1 ({weight_div_4_plus_1})"
);
result
}
#[spec_locked("11.1.3")]
pub fn extract_witness_version(script: &ByteString) -> Option<WitnessVersion> {
if script.is_empty() {
return None;
}
match script[0] {
OP_1 => Some(WitnessVersion::TaprootV1),
OP_0 => Some(WitnessVersion::SegWitV0),
_ => None,
}
}
#[spec_locked("11.1.3")]
pub fn extract_witness_program(
script: &ByteString,
_version: WitnessVersion,
) -> Option<ByteString> {
if script.len() < 3 {
return None;
}
let push_opcode = script[1];
let program_start = 2;
if script.len() < program_start + (push_opcode as usize) {
return None;
}
Some(script[program_start..program_start + (push_opcode as usize)].to_vec())
}
#[spec_locked("11.1.3")]
pub fn validate_witness_program_length(program: &ByteString, version: WitnessVersion) -> bool {
use crate::constants::{SEGWIT_P2WPKH_LENGTH, SEGWIT_P2WSH_LENGTH, TAPROOT_PROGRAM_LENGTH};
match version {
WitnessVersion::SegWitV0 => {
program.len() == SEGWIT_P2WPKH_LENGTH || program.len() == SEGWIT_P2WSH_LENGTH
}
WitnessVersion::TaprootV1 => {
program.len() == TAPROOT_PROGRAM_LENGTH
}
}
}
#[spec_locked("11.1.2")]
pub fn is_witness_empty(witness: &Witness) -> bool {
witness.is_empty() || witness.iter().all(|elem| elem.is_empty())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_validate_segwit_witness_structure() {
let witness = vec![
vec![0x01; 20], vec![0x02; 72], ];
assert!(validate_segwit_witness_structure(&witness).unwrap());
let invalid_witness = vec![vec![0x01; crate::constants::MAX_SCRIPT_ELEMENT_SIZE + 1]];
assert!(!validate_segwit_witness_structure(&invalid_witness).unwrap());
}
#[test]
fn test_validate_taproot_witness_structure_key_path() {
let witness = vec![vec![0x01; 64]];
assert!(validate_taproot_witness_structure(&witness, false).unwrap());
let invalid = vec![vec![0x01; 63]];
assert!(!validate_taproot_witness_structure(&invalid, false).unwrap());
let invalid2 = vec![vec![0x01; 64], vec![0x02; 32]];
assert!(!validate_taproot_witness_structure(&invalid2, false).unwrap());
}
#[test]
fn test_validate_taproot_witness_structure_script_path() {
let witness = vec![
vec![OP_1], vec![0u8; 33], ];
assert!(validate_taproot_witness_structure(&witness, true).unwrap());
let invalid = vec![vec![OP_1], vec![0u8; 32]];
assert!(!validate_taproot_witness_structure(&invalid, true).unwrap());
let invalid2 = vec![vec![OP_1]];
assert!(!validate_taproot_witness_structure(&invalid2, true).unwrap());
}
#[test]
fn test_calculate_transaction_weight_segwit() {
let base_size = 100;
let total_size = 150;
let weight = calculate_transaction_weight_segwit(base_size, total_size);
assert_eq!(weight, 3 * base_size + total_size); }
#[test]
fn test_weight_to_vsize() {
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); }
#[test]
fn test_extract_witness_version() {
let segwit_script = vec![OP_0, PUSH_20_BYTES, 0x01, 0x02, 0x03]; assert_eq!(
extract_witness_version(&segwit_script),
Some(WitnessVersion::SegWitV0)
);
let taproot_script = vec![OP_1, PUSH_32_BYTES]; assert_eq!(
extract_witness_version(&taproot_script),
Some(WitnessVersion::TaprootV1)
);
let non_witness_script = vec![OP_DUP, OP_HASH160]; assert_eq!(extract_witness_version(&non_witness_script), None);
}
#[test]
fn test_extract_witness_program() {
let segwit_script = vec![
OP_0,
PUSH_20_BYTES,
0x01,
0x02,
0x03,
0x04,
0x05,
0x06,
0x07,
0x08,
0x09,
0x0a,
0x0b,
0x0c,
0x0d,
0x0e,
0x0f,
0x10,
0x11,
0x12,
0x13,
PUSH_20_BYTES,
];
let program = extract_witness_program(&segwit_script, WitnessVersion::SegWitV0);
assert_eq!(
program,
Some(vec![
0x01,
0x02,
0x03,
0x04,
0x05,
0x06,
0x07,
0x08,
0x09,
0x0a,
0x0b,
0x0c,
0x0d,
0x0e,
0x0f,
0x10,
0x11,
0x12,
0x13,
PUSH_20_BYTES
])
);
}
#[test]
fn test_validate_witness_program_length() {
let p2wpkh = vec![0u8; 20]; assert!(validate_witness_program_length(
&p2wpkh,
WitnessVersion::SegWitV0
));
let p2wsh = vec![0u8; 32]; assert!(validate_witness_program_length(
&p2wsh,
WitnessVersion::SegWitV0
));
let p2tr = vec![0u8; 32]; assert!(validate_witness_program_length(
&p2tr,
WitnessVersion::TaprootV1
));
let invalid = vec![0u8; 33];
assert!(!validate_witness_program_length(
&invalid,
WitnessVersion::SegWitV0
));
assert!(!validate_witness_program_length(
&invalid,
WitnessVersion::TaprootV1
));
}
#[test]
fn test_is_witness_empty() {
assert!(is_witness_empty(&vec![]));
assert!(is_witness_empty(&vec![vec![]]));
assert!(!is_witness_empty(&vec![vec![0x01]]));
}
}