use crate::script::error::ScriptError;
use crate::script::locking_script::LockingScript;
use crate::script::op::Op;
use crate::script::unlocking_script::UnlockingScript;
use crate::transaction::transaction_input::TransactionInput;
use crate::transaction::transaction_output::TransactionOutput;
const DEFAULT_MEMORY_LIMIT: usize = 32 * 1024 * 1024;
const MAX_OPS: usize = 100_000;
pub struct SpendParams {
pub locking_script: LockingScript,
pub unlocking_script: UnlockingScript,
pub source_txid: String,
pub source_output_index: usize,
pub source_satoshis: u64,
pub transaction_version: u32,
pub transaction_lock_time: u32,
pub transaction_sequence: u32,
pub other_inputs: Vec<TransactionInput>,
pub other_outputs: Vec<TransactionOutput>,
pub input_index: usize,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub(crate) enum ScriptContext {
Unlocking,
Locking,
}
pub struct Spend {
pub(crate) locking_script: LockingScript,
pub(crate) unlocking_script: UnlockingScript,
pub(crate) source_txid: String,
pub(crate) source_output_index: usize,
pub(crate) source_satoshis: u64,
pub(crate) transaction_version: u32,
pub(crate) transaction_lock_time: u32,
pub(crate) transaction_sequence: u32,
pub(crate) other_inputs: Vec<TransactionInput>,
pub(crate) other_outputs: Vec<TransactionOutput>,
pub(crate) input_index: usize,
pub(crate) stack: Vec<Vec<u8>>,
pub(crate) alt_stack: Vec<Vec<u8>>,
pub(crate) if_stack: Vec<bool>,
pub(crate) context: ScriptContext,
pub(crate) program_counter: usize,
pub(crate) last_code_separator: Option<usize>,
pub(crate) memory_limit: usize,
pub(crate) stack_mem: usize,
pub(crate) alt_stack_mem: usize,
pub(crate) is_relaxed_override: bool,
pub(crate) ops_count: usize,
}
impl Spend {
pub fn new(params: SpendParams) -> Self {
Spend {
locking_script: params.locking_script,
unlocking_script: params.unlocking_script,
source_txid: params.source_txid,
source_output_index: params.source_output_index,
source_satoshis: params.source_satoshis,
transaction_version: params.transaction_version,
transaction_lock_time: params.transaction_lock_time,
transaction_sequence: params.transaction_sequence,
other_inputs: params.other_inputs,
other_outputs: params.other_outputs,
input_index: params.input_index,
stack: Vec::new(),
alt_stack: Vec::new(),
if_stack: Vec::new(),
context: ScriptContext::Unlocking,
program_counter: 0,
last_code_separator: None,
memory_limit: DEFAULT_MEMORY_LIMIT,
stack_mem: 0,
alt_stack_mem: 0,
is_relaxed_override: false,
ops_count: 0,
}
}
pub fn validate(&mut self) -> Result<bool, ScriptError> {
self.context = ScriptContext::Unlocking;
self.program_counter = 0;
loop {
let done = self.step()?;
if done {
break;
}
}
if !self.is_relaxed() && !self.unlocking_script.is_push_only() {
return Err(ScriptError::PushOnlyViolation);
}
self.context = ScriptContext::Locking;
self.program_counter = 0;
self.last_code_separator = None;
loop {
let done = self.step()?;
if done {
break;
}
}
if !self.if_stack.is_empty() {
return Err(ScriptError::InvalidScript(
"unbalanced IF/ENDIF".to_string(),
));
}
if !self.is_relaxed() && self.stack.len() != 1 {
return Err(ScriptError::CleanStackViolation);
}
if self.stack.is_empty() {
return Ok(false);
}
let top = self.stack.last().unwrap();
Ok(Self::stack_to_bool(top))
}
pub fn step(&mut self) -> Result<bool, ScriptError> {
let chunks = match self.context {
ScriptContext::Unlocking => self.unlocking_script.chunks(),
ScriptContext::Locking => self.locking_script.chunks(),
};
if self.program_counter >= chunks.len() {
match self.context {
ScriptContext::Unlocking => {
return Ok(true);
}
ScriptContext::Locking => {
return Ok(true);
}
}
}
let chunk = chunks[self.program_counter].clone();
let op = chunk.op;
let in_exec = self.if_stack.iter().all(|&v| v);
if !in_exec {
match op {
Op::OpIf | Op::OpNotIf | Op::OpVerIf | Op::OpVerNotIf => {
self.if_stack.push(false);
}
Op::OpElse => {
if let Some(last) = self.if_stack.last_mut() {
*last = !*last;
} else {
return Err(ScriptError::InvalidScript(
"OP_ELSE without OP_IF".to_string(),
));
}
}
Op::OpEndIf => {
self.if_stack.pop().ok_or_else(|| {
ScriptError::InvalidScript("OP_ENDIF without OP_IF".to_string())
})?;
}
_ => {
}
}
self.program_counter += 1;
return Ok(false);
}
self.ops_count += 1;
if self.ops_count > MAX_OPS {
return Err(ScriptError::InvalidScript(
"exceeded maximum operation count".to_string(),
));
}
self.execute_opcode(op, &chunk)?;
self.program_counter += 1;
Ok(false)
}
pub fn is_relaxed(&self) -> bool {
self.transaction_version > 1 || self.is_relaxed_override
}
pub fn set_relaxed_override(&mut self, v: bool) {
self.is_relaxed_override = v;
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::script::locking_script::LockingScript;
use crate::script::unlocking_script::UnlockingScript;
fn make_spend(unlocking_asm: &str, locking_asm: &str) -> Spend {
Spend::new(SpendParams {
locking_script: LockingScript::from_asm(locking_asm),
unlocking_script: UnlockingScript::from_asm(unlocking_asm),
source_txid: "00".repeat(32),
source_output_index: 0,
source_satoshis: 0,
transaction_version: 1,
transaction_lock_time: 0,
transaction_sequence: 0xffffffff,
other_inputs: vec![],
other_outputs: vec![],
input_index: 0,
})
}
fn make_relaxed_spend(unlocking_asm: &str, locking_asm: &str) -> Spend {
Spend::new(SpendParams {
locking_script: LockingScript::from_asm(locking_asm),
unlocking_script: UnlockingScript::from_asm(unlocking_asm),
source_txid: "00".repeat(32),
source_output_index: 0,
source_satoshis: 0,
transaction_version: 2, transaction_lock_time: 0,
transaction_sequence: 0xffffffff,
other_inputs: vec![],
other_outputs: vec![],
input_index: 0,
})
}
#[test]
fn test_simple_push_only_script() {
let mut spend = make_spend("OP_1", "");
let result = spend.validate().unwrap();
assert!(result, "OP_1 should leave true on stack");
}
#[test]
fn test_op1_pushes_one() {
let mut spend = make_spend("OP_1", "");
spend.validate().unwrap();
assert_eq!(spend.stack, vec![vec![1u8]]);
}
#[test]
fn test_op_numbers() {
for n in 2..=16u8 {
let asm = format!("OP_{}", n);
let mut spend = make_spend(&asm, "");
spend.validate().unwrap();
assert_eq!(spend.stack, vec![vec![n]], "OP_{} should push [{}]", n, n);
}
}
#[test]
fn test_op_0_pushes_empty() {
let mut spend = make_relaxed_spend("0 OP_1", "");
spend.validate().unwrap();
assert_eq!(spend.stack.len(), 2);
assert_eq!(spend.stack[0], Vec::<u8>::new());
assert_eq!(spend.stack[1], vec![1u8]);
}
#[test]
fn test_op_1negate() {
let mut spend = make_spend("-1", "OP_1 OP_ADD");
let result = spend.validate().unwrap();
assert!(!result, "-1 + 1 = 0 should be false");
}
#[test]
fn test_if_else_endif_true_branch() {
let mut spend = make_spend("OP_1", "OP_IF OP_2 OP_ELSE OP_3 OP_ENDIF");
let result = spend.validate().unwrap();
assert!(result);
assert_eq!(spend.stack, vec![vec![2u8]]);
}
#[test]
fn test_if_else_endif_false_branch() {
let mut spend = make_spend("0", "OP_IF OP_2 OP_ELSE OP_3 OP_ENDIF");
let result = spend.validate().unwrap();
assert!(result);
assert_eq!(spend.stack, vec![vec![3u8]]);
}
#[test]
fn test_nested_if() {
let mut spend = make_spend("OP_1 OP_1", "OP_IF OP_IF OP_5 OP_ENDIF OP_ENDIF");
let result = spend.validate().unwrap();
assert!(result);
assert_eq!(spend.stack, vec![vec![5u8]]);
}
#[test]
fn test_op_verify_true() {
let mut spend = make_spend("OP_1", "OP_VERIFY OP_1");
let result = spend.validate().unwrap();
assert!(result);
}
#[test]
fn test_op_verify_false() {
let mut spend = make_spend("0", "OP_VERIFY OP_1");
let result = spend.validate();
assert!(result.is_err());
}
#[test]
fn test_op_return_fails() {
let mut spend = make_spend("OP_1", "OP_RETURN");
let result = spend.validate();
assert!(result.is_err());
}
#[test]
fn test_memory_limit_exceeded() {
let mut spend = make_spend("OP_1", "");
spend.memory_limit = 0;
let result = spend.validate();
assert!(matches!(result, Err(ScriptError::MemoryLimitExceeded)));
}
#[test]
fn test_stack_underflow() {
let mut spend = make_spend("", "OP_DUP");
let result = spend.validate();
assert!(result.is_err(), "expected error but got {:?}", result);
}
#[test]
fn test_stack_to_bool() {
assert!(!Spend::stack_to_bool(&[]));
assert!(!Spend::stack_to_bool(&[0]));
assert!(!Spend::stack_to_bool(&[0, 0]));
assert!(!Spend::stack_to_bool(&[0x80])); assert!(!Spend::stack_to_bool(&[0, 0x80])); assert!(Spend::stack_to_bool(&[1]));
assert!(Spend::stack_to_bool(&[0, 1]));
assert!(Spend::stack_to_bool(&[0x81])); }
#[test]
fn test_clean_stack_violation() {
let mut spend = make_spend("OP_1 OP_2", "");
let result = spend.validate();
assert!(matches!(result, Err(ScriptError::CleanStackViolation)));
}
#[test]
fn test_relaxed_mode_no_clean_stack() {
let mut spend = make_relaxed_spend("OP_1 OP_2", "");
let result = spend.validate().unwrap();
assert!(result); }
#[test]
fn test_push_only_violation() {
let mut spend = make_spend("", "OP_1");
spend.unlocking_script = UnlockingScript::from_asm("OP_DUP");
spend.stack.push(vec![1]);
let result = spend.validate();
assert!(matches!(result, Err(ScriptError::PushOnlyViolation)));
}
#[test]
fn test_step_api() {
let mut spend = make_spend("OP_1 OP_2", "OP_ADD");
assert!(!spend.step().unwrap()); assert_eq!(spend.stack, vec![vec![1u8]]);
assert!(!spend.step().unwrap()); assert_eq!(spend.stack, vec![vec![1u8], vec![2u8]]);
assert!(spend.step().unwrap());
spend.context = ScriptContext::Locking;
spend.program_counter = 0;
assert!(!spend.step().unwrap()); assert_eq!(spend.stack, vec![vec![3u8]]);
assert!(spend.step().unwrap()); }
#[test]
fn test_is_relaxed() {
let spend = make_spend("", "");
assert!(!spend.is_relaxed());
let spend2 = make_relaxed_spend("", "");
assert!(spend2.is_relaxed());
let mut spend3 = make_spend("", "");
spend3.set_relaxed_override(true);
assert!(spend3.is_relaxed()); }
#[test]
fn test_op_notif() {
let mut spend = make_spend("0", "OP_NOTIF OP_5 OP_ENDIF");
let result = spend.validate().unwrap();
assert!(result);
assert_eq!(spend.stack, vec![vec![5u8]]);
}
#[test]
fn test_op_add() {
let mut spend = make_spend("OP_3 OP_4", "OP_ADD OP_7 OP_EQUAL");
assert!(spend.validate().unwrap());
}
#[test]
fn test_op_sub() {
let mut spend = make_spend("OP_5 OP_3", "OP_SUB OP_2 OP_EQUAL");
assert!(spend.validate().unwrap());
}
#[test]
fn test_op_mul() {
let mut spend = make_spend("OP_3 OP_4", "OP_MUL OP_12 OP_EQUAL");
assert!(spend.validate().unwrap());
}
#[test]
fn test_op_div() {
let mut spend = make_spend("OP_6 OP_3", "OP_DIV OP_2 OP_EQUAL");
assert!(spend.validate().unwrap());
}
#[test]
fn test_op_mod() {
let mut spend = make_spend("OP_7 OP_3", "OP_MOD OP_1 OP_EQUAL");
assert!(spend.validate().unwrap());
}
#[test]
fn test_op_div_by_zero() {
let mut spend = make_spend("OP_5 0", "OP_DIV");
assert!(spend.validate().is_err());
}
#[test]
fn test_op_equal() {
let mut spend = make_spend("OP_3 OP_3", "OP_EQUAL");
assert!(spend.validate().unwrap());
}
#[test]
fn test_op_equal_false() {
let mut spend = make_spend("OP_3 OP_4", "OP_EQUAL");
assert!(!spend.validate().unwrap());
}
#[test]
fn test_op_equalverify() {
let mut spend = make_spend("OP_3 OP_3", "OP_EQUALVERIFY OP_1");
assert!(spend.validate().unwrap());
}
#[test]
fn test_op_equalverify_fail() {
let mut spend = make_spend("OP_3 OP_4", "OP_EQUALVERIFY OP_1");
assert!(spend.validate().is_err());
}
#[test]
fn test_op_dup_hash160_equalverify() {
use crate::primitives::hash::hash160;
let pubkey_data = vec![0x04; 33]; let hash = hash160(&pubkey_data);
let hash_hex: String = hash.iter().map(|b| format!("{:02x}", b)).collect();
let pubkey_hex: String = pubkey_data.iter().map(|b| format!("{:02x}", b)).collect();
let locking = format!("OP_DUP OP_HASH160 {} OP_EQUALVERIFY OP_DROP OP_1", hash_hex);
let mut spend = make_spend(&pubkey_hex, &locking);
assert!(spend.validate().unwrap());
}
#[test]
fn test_op_cat() {
let mut spend = make_relaxed_spend("01 02", "OP_CAT 0102 OP_EQUAL");
assert!(spend.validate().unwrap());
}
#[test]
fn test_op_split() {
let mut spend =
make_relaxed_spend("010203 OP_1", "OP_SPLIT 0203 OP_EQUALVERIFY 01 OP_EQUAL");
assert!(spend.validate().unwrap());
}
#[test]
fn test_op_size() {
let mut spend = make_relaxed_spend("010203", "OP_SIZE OP_3 OP_EQUALVERIFY OP_1");
assert!(spend.validate().unwrap());
}
#[test]
fn test_op_sha256() {
let mut spend = make_relaxed_spend("0", "OP_SHA256 OP_SIZE");
spend.validate().unwrap();
assert_eq!(spend.stack.len(), 2);
assert_eq!(spend.stack[0].len(), 32);
}
#[test]
fn test_op_hash160() {
let mut spend = make_relaxed_spend("01", "OP_HASH160 OP_SIZE");
spend.validate().unwrap();
assert_eq!(spend.stack[0].len(), 20);
}
#[test]
fn test_op_hash256() {
let mut spend = make_relaxed_spend("01", "OP_HASH256 OP_SIZE");
spend.validate().unwrap();
assert_eq!(spend.stack[0].len(), 32);
}
#[test]
fn test_op_toaltstack_fromaltstack() {
let mut spend = make_spend("OP_1", "OP_TOALTSTACK OP_FROMALTSTACK");
assert!(spend.validate().unwrap());
assert_eq!(spend.stack, vec![vec![1u8]]);
}
#[test]
fn test_op_depth() {
let mut spend = make_spend(
"OP_1 OP_2 OP_3",
"OP_DEPTH OP_3 OP_EQUALVERIFY OP_DROP OP_DROP OP_DROP OP_1",
);
let result = spend.validate();
assert!(result.is_ok(), "test_op_depth failed: {:?}", result);
}
#[test]
fn test_op_swap() {
let mut spend = make_relaxed_spend("OP_1 OP_2", "OP_SWAP");
spend.validate().unwrap();
assert_eq!(spend.stack, vec![vec![2u8], vec![1u8]]);
}
#[test]
fn test_op_rot() {
let mut spend = make_relaxed_spend("OP_1 OP_2 OP_3", "OP_ROT");
spend.validate().unwrap();
assert_eq!(spend.stack, vec![vec![2u8], vec![3u8], vec![1u8]]);
}
#[test]
fn test_op_over() {
let mut spend = make_relaxed_spend("OP_1 OP_2", "OP_OVER");
spend.validate().unwrap();
assert_eq!(spend.stack, vec![vec![1u8], vec![2u8], vec![1u8]]);
}
#[test]
fn test_op_nip() {
let mut spend = make_relaxed_spend("OP_1 OP_2", "OP_NIP");
spend.validate().unwrap();
assert_eq!(spend.stack, vec![vec![2u8]]);
}
#[test]
fn test_op_tuck() {
let mut spend = make_relaxed_spend("OP_1 OP_2", "OP_TUCK");
spend.validate().unwrap();
assert_eq!(spend.stack, vec![vec![2u8], vec![1u8], vec![2u8]]);
}
#[test]
fn test_op_pick() {
let mut spend = make_relaxed_spend("OP_1 OP_2 OP_3 OP_2", "OP_PICK");
spend.validate().unwrap();
assert_eq!(
spend.stack,
vec![vec![1u8], vec![2u8], vec![3u8], vec![1u8]]
);
}
#[test]
fn test_op_roll() {
let mut spend = make_relaxed_spend("OP_1 OP_2 OP_3 OP_2", "OP_ROLL");
spend.validate().unwrap();
assert_eq!(spend.stack, vec![vec![2u8], vec![3u8], vec![1u8]]);
}
#[test]
fn test_op_2dup() {
let mut spend = make_relaxed_spend("OP_1 OP_2", "OP_2DUP");
spend.validate().unwrap();
assert_eq!(
spend.stack,
vec![vec![1u8], vec![2u8], vec![1u8], vec![2u8]]
);
}
#[test]
fn test_op_3dup() {
let mut spend = make_relaxed_spend("OP_1 OP_2 OP_3", "OP_3DUP");
spend.validate().unwrap();
assert_eq!(spend.stack.len(), 6);
}
#[test]
fn test_op_2drop() {
let mut spend = make_spend("OP_1 OP_2 OP_3", "OP_2DROP");
assert!(spend.validate().unwrap());
assert_eq!(spend.stack, vec![vec![1u8]]);
}
#[test]
fn test_op_2swap() {
let mut spend = make_relaxed_spend("OP_1 OP_2 OP_3 OP_4", "OP_2SWAP");
spend.validate().unwrap();
assert_eq!(
spend.stack,
vec![vec![3u8], vec![4u8], vec![1u8], vec![2u8]]
);
}
#[test]
fn test_op_ifdup_true() {
let mut spend = make_relaxed_spend("OP_1", "OP_IFDUP");
spend.validate().unwrap();
assert_eq!(spend.stack, vec![vec![1u8], vec![1u8]]);
}
#[test]
fn test_op_ifdup_false() {
let mut spend = make_relaxed_spend("0", "OP_IFDUP");
spend.validate().unwrap();
assert_eq!(spend.stack, vec![Vec::<u8>::new()]);
}
#[test]
fn test_op_lessthan() {
let mut spend = make_spend("OP_1 OP_2", "OP_LESSTHAN");
assert!(spend.validate().unwrap());
}
#[test]
fn test_op_greaterthan() {
let mut spend = make_spend("OP_3 OP_2", "OP_GREATERTHAN");
assert!(spend.validate().unwrap());
}
#[test]
fn test_op_within() {
let mut spend = make_spend("OP_3 OP_2 OP_5", "OP_WITHIN");
assert!(spend.validate().unwrap());
}
#[test]
fn test_op_within_false() {
let mut spend = make_spend("OP_5 OP_2 OP_5", "OP_WITHIN");
assert!(!spend.validate().unwrap());
}
#[test]
fn test_op_min() {
let mut spend = make_spend("OP_3 OP_5", "OP_MIN OP_3 OP_EQUAL");
assert!(spend.validate().unwrap());
}
#[test]
fn test_op_max() {
let mut spend = make_spend("OP_3 OP_5", "OP_MAX OP_5 OP_EQUAL");
assert!(spend.validate().unwrap());
}
#[test]
fn test_op_booland() {
let mut spend = make_spend("OP_1 OP_1", "OP_BOOLAND");
assert!(spend.validate().unwrap());
let mut spend2 = make_spend("OP_1 0", "OP_BOOLAND");
assert!(!spend2.validate().unwrap());
}
#[test]
fn test_op_boolor() {
let mut spend = make_spend("0 0", "OP_BOOLOR");
assert!(!spend.validate().unwrap());
let mut spend2 = make_spend("OP_1 0", "OP_BOOLOR");
assert!(spend2.validate().unwrap());
}
#[test]
fn test_op_abs() {
let mut spend = make_spend("-1", "OP_ABS OP_1 OP_EQUAL");
assert!(spend.validate().unwrap());
}
#[test]
fn test_op_not() {
let mut spend = make_spend("0", "OP_NOT"); assert!(spend.validate().unwrap());
let mut spend2 = make_spend("OP_1", "OP_NOT"); assert!(!spend2.validate().unwrap());
}
#[test]
fn test_op_0notequal() {
let mut spend = make_spend("OP_5", "OP_0NOTEQUAL");
assert!(spend.validate().unwrap());
let mut spend2 = make_spend("0", "OP_0NOTEQUAL");
assert!(!spend2.validate().unwrap());
}
#[test]
fn test_op_negate() {
let mut spend = make_spend("OP_5", "OP_NEGATE OP_ABS OP_5 OP_EQUAL");
assert!(spend.validate().unwrap());
}
#[test]
fn test_op_1add_1sub() {
let mut spend = make_spend("OP_5", "OP_1ADD OP_6 OP_EQUAL");
assert!(spend.validate().unwrap());
let mut spend2 = make_spend("OP_5", "OP_1SUB OP_4 OP_EQUAL");
assert!(spend2.validate().unwrap());
}
#[test]
fn test_op_numequal() {
let mut spend = make_spend("OP_3 OP_3", "OP_NUMEQUAL");
assert!(spend.validate().unwrap());
}
#[test]
fn test_op_numequalverify() {
let mut spend = make_spend("OP_3 OP_3", "OP_NUMEQUALVERIFY OP_1");
assert!(spend.validate().unwrap());
}
#[test]
fn test_op_numnotequal() {
let mut spend = make_spend("OP_3 OP_4", "OP_NUMNOTEQUAL");
assert!(spend.validate().unwrap());
}
#[test]
fn test_op_invert() {
let mut spend = make_relaxed_spend("00", "OP_INVERT");
spend.validate().unwrap();
assert_eq!(spend.stack, vec![vec![0xff]]);
}
#[test]
fn test_op_and() {
let mut spend = make_relaxed_spend("ff 0f", "OP_AND");
spend.validate().unwrap();
assert_eq!(spend.stack, vec![vec![0x0f]]);
}
#[test]
fn test_op_or() {
let mut spend = make_relaxed_spend("f0 0f", "OP_OR");
spend.validate().unwrap();
assert_eq!(spend.stack, vec![vec![0xff]]);
}
#[test]
fn test_op_xor() {
let mut spend = make_relaxed_spend("ff ff", "OP_XOR");
spend.validate().unwrap();
assert_eq!(spend.stack, vec![vec![0x00]]);
}
#[test]
fn test_nested_if_deep() {
let mut spend = make_spend(
"OP_1 OP_1 OP_1",
"OP_IF OP_IF OP_IF OP_7 OP_ENDIF OP_ENDIF OP_ENDIF",
);
assert!(spend.validate().unwrap());
assert_eq!(spend.stack, vec![vec![7u8]]);
}
#[test]
fn test_op_codeseparator() {
let mut spend = make_spend("OP_1", "OP_CODESEPARATOR");
assert!(spend.validate().unwrap());
}
#[test]
fn test_chronicle_op_substr() {
let mut spend = make_relaxed_spend("0102030405 OP_1 OP_2", "OP_SUBSTR 0203 OP_EQUAL");
assert!(spend.validate().unwrap());
}
#[test]
fn test_chronicle_op_left() {
let mut spend = make_relaxed_spend("01020304 OP_2", "OP_LEFT 0102 OP_EQUAL");
assert!(spend.validate().unwrap());
}
#[test]
fn test_chronicle_op_right() {
let mut spend = make_relaxed_spend("01020304 OP_2", "OP_RIGHT 0304 OP_EQUAL");
assert!(spend.validate().unwrap());
}
#[test]
fn test_op_ripemd160() {
let mut spend = make_relaxed_spend("01", "OP_RIPEMD160 OP_SIZE");
spend.validate().unwrap();
assert_eq!(spend.stack[0].len(), 20);
}
#[test]
fn test_op_sha1() {
let mut spend = make_relaxed_spend("01", "OP_SHA1 OP_SIZE");
spend.validate().unwrap();
assert_eq!(spend.stack[0].len(), 20);
}
#[test]
fn test_op_checksig_empty_sig_fails() {
let mut spend = make_relaxed_spend("0 01", "OP_CHECKSIG");
spend.validate().unwrap();
assert_eq!(spend.stack, vec![Vec::<u8>::new()]);
}
#[test]
fn test_relaxed_mode_gating() {
let mut spend = make_relaxed_spend("OP_1 OP_2 OP_3", "");
let result = spend.validate().unwrap();
assert!(result);
let mut spend2 = make_spend("OP_1 OP_2 OP_3", "");
assert!(matches!(
spend2.validate(),
Err(ScriptError::CleanStackViolation)
));
}
#[test]
fn test_op_disabled_2mul() {
let mut spend = make_spend("OP_1", "OP_2MUL");
let result = spend.validate();
assert!(matches!(result, Err(ScriptError::DisabledOpcode(_))));
}
#[test]
fn test_op_disabled_2div() {
let mut spend = make_spend("OP_1", "OP_2DIV");
let result = spend.validate();
assert!(matches!(result, Err(ScriptError::DisabledOpcode(_))));
}
fn parse_test_asm(asm: &str) -> crate::script::script::Script {
use crate::script::script::Script;
use crate::script::script_chunk::ScriptChunk;
let asm = asm.trim();
if asm.is_empty() {
return Script::new();
}
let mut chunks = Vec::new();
let tokens: Vec<&str> = asm.split_whitespace().collect();
let mut i = 0;
while i < tokens.len() {
let token = tokens[i];
if token == "0" {
chunks.push(ScriptChunk::new_opcode(Op::Op0));
i += 1;
continue;
}
if token == "-1" {
chunks.push(ScriptChunk::new_opcode(Op::Op1Negate));
i += 1;
continue;
}
if token.starts_with('\'') {
let text = if token.ends_with('\'') && token.len() > 1 {
&token[1..token.len() - 1]
} else {
let mut s = token[1..].to_string();
loop {
i += 1;
if i >= tokens.len() {
break;
}
s.push(' ');
s.push_str(tokens[i]);
if tokens[i].ends_with('\'') {
s.truncate(s.len() - 1);
break;
}
}
i += 1;
let data = s.into_bytes();
let len = data.len();
let op_byte = if len < 0x4c { len as u8 } else { 0x4c };
chunks.push(ScriptChunk::new_raw(op_byte, Some(data)));
continue;
};
let data = text.as_bytes().to_vec();
let len = data.len();
let op_byte = if len < 0x4c { len as u8 } else { 0x4c };
chunks.push(ScriptChunk::new_raw(op_byte, Some(data)));
i += 1;
continue;
}
if token.starts_with("0x") || token.starts_with("0X") {
let hex = &token[2..];
if let Ok(push_len) = usize::from_str_radix(hex, 16) {
if push_len > 0 && push_len <= 0x4e && i + 1 < tokens.len() {
let next = tokens[i + 1];
if next.starts_with("0x") || next.starts_with("0X") {
let data_hex = &next[2..];
if let Ok(data) = hex_decode(data_hex) {
chunks.push(ScriptChunk::new_raw(push_len as u8, Some(data)));
i += 2;
continue;
}
}
}
if push_len <= 0xff {
let op = Op::from(push_len as u8);
chunks.push(ScriptChunk::new_raw(push_len as u8, None));
let _ = op; i += 1;
continue;
}
}
i += 1;
continue;
}
if token == "PUSHDATA1" || token == "OP_PUSHDATA1" {
if i + 2 < tokens.len() {
let _len_hex = tokens[i + 1].strip_prefix("0x").unwrap_or(tokens[i + 1]);
let data_hex = tokens[i + 2].strip_prefix("0x").unwrap_or(tokens[i + 2]);
if let Ok(data) = hex_decode(data_hex) {
chunks.push(ScriptChunk::new_raw(0x4c, Some(data)));
i += 3;
continue;
}
}
i += 1;
continue;
}
if token == "PUSHDATA2" || token == "OP_PUSHDATA2" {
if i + 2 < tokens.len() {
let data_hex = tokens[i + 2].strip_prefix("0x").unwrap_or(tokens[i + 2]);
if let Ok(data) = hex_decode(data_hex) {
chunks.push(ScriptChunk::new_raw(0x4d, Some(data)));
i += 3;
continue;
}
}
i += 1;
continue;
}
if token == "PUSHDATA4" || token == "OP_PUSHDATA4" {
if i + 2 < tokens.len() {
let data_hex = tokens[i + 2].strip_prefix("0x").unwrap_or(tokens[i + 2]);
if let Ok(data) = hex_decode(data_hex) {
chunks.push(ScriptChunk::new_raw(0x4e, Some(data)));
i += 3;
continue;
}
}
i += 1;
continue;
}
if let Some(op) =
Op::from_name(token).or_else(|| Op::from_name(&format!("OP_{}", token)))
{
chunks.push(ScriptChunk::new_opcode(op));
i += 1;
continue;
}
if let Ok(n) = token.parse::<i64>() {
use crate::primitives::big_number::BigNumber;
let bn = BigNumber::from_number(n);
let data = bn.to_script_num();
if data.is_empty() {
chunks.push(ScriptChunk::new_opcode(Op::Op0));
} else {
let len = data.len();
let op_byte = if len < 0x4c { len as u8 } else { 0x4c };
chunks.push(ScriptChunk::new_raw(op_byte, Some(data)));
}
i += 1;
continue;
}
i += 1;
}
Script::from_chunks(chunks)
}
fn hex_decode(hex: &str) -> Result<Vec<u8>, ()> {
if hex.len() % 2 != 0 {
return Err(());
}
let mut bytes = Vec::with_capacity(hex.len() / 2);
for i in (0..hex.len()).step_by(2) {
match u8::from_str_radix(&hex[i..i + 2], 16) {
Ok(b) => bytes.push(b),
Err(_) => return Err(()),
}
}
Ok(bytes)
}
#[test]
fn test_script_tests_json() {
let json_str = include_str!("../../test-vectors/script_tests.json");
let entries: Vec<serde_json::Value> =
serde_json::from_str(json_str).expect("failed to parse script_tests.json");
let mut passed = 0;
let mut failed = 0;
let mut skipped = 0;
for entry in &entries {
let arr = match entry.as_array() {
Some(a) => a,
None => continue,
};
if arr.len() < 4 {
skipped += 1;
continue;
}
let (sig_asm, pubkey_asm, flags_str, expected) = if arr[0].is_array() {
if arr.len() < 5 {
skipped += 1;
continue;
}
(
arr[1].as_str().unwrap_or(""),
arr[2].as_str().unwrap_or(""),
arr[3].as_str().unwrap_or(""),
arr[4].as_str().unwrap_or(""),
)
} else {
(
arr[0].as_str().unwrap_or(""),
arr[1].as_str().unwrap_or(""),
arr[2].as_str().unwrap_or(""),
arr[3].as_str().unwrap_or(""),
)
};
let flags: Vec<&str> = if flags_str.is_empty() {
vec![]
} else {
flags_str.split(',').collect()
};
let has_strictenc = flags.contains(&"STRICTENC");
let has_utxo_after_genesis = flags.contains(&"UTXO_AFTER_GENESIS");
let has_p2sh = flags.contains(&"P2SH");
let _has_sigpushonly = flags.contains(&"SIGPUSHONLY");
let _has_minimaldata = flags.contains(&"MINIMALDATA");
if has_utxo_after_genesis || has_p2sh {
skipped += 1;
continue;
}
let unlocking_script = UnlockingScript::from_script(parse_test_asm(sig_asm));
let locking_script = LockingScript::from_script(parse_test_asm(pubkey_asm));
let version = if has_strictenc { 1u32 } else { 2u32 };
let mut spend = Spend::new(SpendParams {
locking_script,
unlocking_script,
source_txid: "00".repeat(32),
source_output_index: 0,
source_satoshis: 0,
transaction_version: version,
transaction_lock_time: 0,
transaction_sequence: 0xffffffff,
other_inputs: vec![],
other_outputs: vec![],
input_index: 0,
});
let result = spend.validate();
let expected_ok = expected == "OK";
match (expected_ok, &result) {
(true, Ok(true)) => passed += 1,
(true, Ok(false)) => {
failed += 1;
}
(true, Err(_)) => {
failed += 1;
}
(false, Err(_)) => passed += 1,
(false, Ok(false)) => passed += 1, (false, Ok(true)) => {
failed += 1;
}
}
}
println!(
"script_tests.json: {} passed, {} failed, {} skipped",
passed, failed, skipped
);
let total_run = passed + failed;
let pass_rate = if total_run > 0 {
(passed as f64 / total_run as f64) * 100.0
} else {
0.0
};
println!("Pass rate: {:.1}% ({}/{})", pass_rate, passed, total_run);
assert!(
pass_rate >= 50.0,
"script_tests.json pass rate too low: {:.1}% ({}/{})",
pass_rate,
passed,
total_run
);
}
}