#![allow(clippy::result_large_err)]
use super::evaluation_error::{
ExecutionContext, ScriptEvaluationError, ScriptResource, ScriptResourceLimit,
};
use super::flags::{
Gates, ScriptFlags, DEFAULT_SCRIPT_NUM_LENGTH_POLICY, DEFAULT_STACK_MEMORY_USAGE_POLICY,
STACK_ELEMENT_OVERHEAD,
};
use super::op::*;
use super::script_num::ScriptNum;
use super::{LockingScript, Script, ScriptChunk, UnlockingScript};
use crate::primitives::bsv::sighash::{
compute_sighash_dispatched_for_signing, SighashParams, TxInput, TxOutput, SIGHASH_ALL,
SIGHASH_ANYONECANPAY, SIGHASH_CHRONICLE, SIGHASH_FORKID, SIGHASH_SINGLE,
};
use crate::primitives::bsv::tx_signature::TransactionSignature;
use crate::primitives::ec::PublicKey;
use crate::primitives::{hash160, ripemd160, sha1, sha256, sha256d, to_hex, BigNumber};
const MAX_SCRIPT_ELEMENT_SIZE: usize = 1024 * 1024 * 1024;
const DEFAULT_MEMORY_LIMIT: usize = 32_000_000;
const WORD_MEMORY_LIMIT: usize = DEFAULT_STACK_MEMORY_USAGE_POLICY;
const MAX_MULTISIG_KEY_COUNT: i64 = i32::MAX as i64;
const REQUIRE_MINIMAL_PUSH: bool = true;
const REQUIRE_PUSH_ONLY_UNLOCKING: bool = true;
const REQUIRE_LOW_S_SIGNATURES: bool = true;
const REQUIRE_CLEAN_STACK: bool = true;
lazy_static::lazy_static! {
static ref SCRIPTNUM_NEG_1: Vec<u8> = ScriptNum::to_bytes(&BigNumber::from_i64(-1));
static ref SCRIPTNUMS_0_TO_16: Vec<Vec<u8>> = (0..=16)
.map(|i| ScriptNum::to_bytes(&BigNumber::from_i64(i)))
.collect();
}
pub struct SpendParams {
pub source_txid: [u8; 32],
pub source_output_index: u32,
pub source_satoshis: u64,
pub locking_script: LockingScript,
pub transaction_version: i32,
pub other_inputs: Vec<TxInput>,
pub outputs: Vec<TxOutput>,
pub input_index: usize,
pub unlocking_script: UnlockingScript,
pub input_sequence: u32,
pub lock_time: u32,
pub memory_limit: Option<usize>,
}
pub struct Spend {
source_txid: [u8; 32],
source_output_index: u32,
source_satoshis: u64,
locking_script: LockingScript,
transaction_version: i32,
other_inputs: Vec<TxInput>,
outputs: Vec<TxOutput>,
input_index: usize,
unlocking_script: UnlockingScript,
input_sequence: u32,
lock_time: u32,
context: ExecutionContext,
program_counter: usize,
last_code_separator: Option<usize>,
stack: Vec<Vec<u8>>,
alt_stack: Vec<Vec<u8>>,
if_stack: Vec<bool>,
memory_limit: Option<usize>,
stack_mem: usize,
alt_stack_mem: usize,
node_mem: u64,
require_push_only: bool,
require_minimal: bool,
require_low_s: bool,
require_clean_stack: bool,
require_null_dummy: bool,
require_null_fail: bool,
require_minimal_if: bool,
require_compressed_pubkey: bool,
discourage_upgradable_nops: bool,
flags: Option<ScriptFlags>,
utxo_after_chronicle: bool,
script_num_length_policy: usize,
stack_memory_policy: usize,
else_stack: Vec<bool>,
returning: bool,
unlocking_truncated: Option<usize>,
locking_truncated: Option<usize>,
unlocking_chunks: Vec<crate::script::chunk::ScriptChunk>,
locking_chunks: Vec<crate::script::chunk::ScriptChunk>,
}
impl Spend {
pub fn new(params: SpendParams) -> Self {
let mut spend = Self {
source_txid: params.source_txid,
source_output_index: params.source_output_index,
source_satoshis: params.source_satoshis,
locking_script: params.locking_script,
transaction_version: params.transaction_version,
other_inputs: params.other_inputs,
outputs: params.outputs,
input_index: params.input_index,
unlocking_script: params.unlocking_script,
input_sequence: params.input_sequence,
lock_time: params.lock_time,
context: ExecutionContext::UnlockingScript,
program_counter: 0,
unlocking_chunks: Vec::new(),
locking_chunks: Vec::new(),
last_code_separator: None,
stack: Vec::new(),
alt_stack: Vec::new(),
if_stack: Vec::new(),
memory_limit: params.memory_limit,
stack_mem: 0,
alt_stack_mem: 0,
node_mem: 0,
require_push_only: REQUIRE_PUSH_ONLY_UNLOCKING,
require_minimal: REQUIRE_MINIMAL_PUSH && params.transaction_version <= 1,
require_low_s: REQUIRE_LOW_S_SIGNATURES && params.transaction_version <= 1,
require_clean_stack: REQUIRE_CLEAN_STACK && params.transaction_version <= 1,
require_null_dummy: params.transaction_version <= 1,
require_null_fail: false,
require_minimal_if: false,
require_compressed_pubkey: false,
discourage_upgradable_nops: false,
flags: None,
utxo_after_chronicle: params.transaction_version > 1,
script_num_length_policy: DEFAULT_SCRIPT_NUM_LENGTH_POLICY,
stack_memory_policy: DEFAULT_STACK_MEMORY_USAGE_POLICY,
else_stack: Vec::new(),
returning: false,
unlocking_truncated: None,
locking_truncated: None,
};
spend.unlocking_chunks = spend.unlocking_script.chunks();
spend.locking_chunks = spend.locking_script.chunks();
spend.unlocking_truncated = spend.unlocking_script.as_script().truncated_push();
spend.locking_truncated = spend.locking_script.as_script().truncated_push();
spend.reset();
spend
}
pub fn set_require_minimal(&mut self, require: bool) {
self.require_minimal = require;
}
pub fn set_require_push_only(&mut self, require: bool) {
self.require_push_only = require;
}
pub fn set_flags(&mut self, flags: ScriptFlags) {
let Gates {
push_only,
minimal,
low_s,
clean_stack,
null_dummy,
null_fail,
minimal_if,
discourage_upgradable_nops,
compressed_pubkey,
utxo_after_chronicle,
} = flags.gates(self.transaction_version);
self.require_push_only = push_only;
self.require_minimal = minimal;
self.require_low_s = low_s;
self.require_clean_stack = clean_stack;
self.require_null_dummy = null_dummy;
self.require_null_fail = null_fail;
self.require_minimal_if = minimal_if;
self.discourage_upgradable_nops = discourage_upgradable_nops;
self.require_compressed_pubkey = compressed_pubkey;
self.utxo_after_chronicle = utxo_after_chronicle;
self.flags = Some(flags);
}
pub fn set_utxo_after_chronicle(&mut self, after: bool) {
self.utxo_after_chronicle = after;
}
pub fn flags(&self) -> Option<ScriptFlags> {
self.flags
}
pub fn max_script_num_length(&self) -> Option<usize> {
self.flags.map(|word| {
word.max_script_num_length(self.utxo_after_chronicle, self.script_num_length_policy)
})
}
pub fn set_script_num_length_policy(&mut self, bytes: usize) {
self.script_num_length_policy = bytes;
}
pub fn memory_limit(&self) -> usize {
self.memory_limit.unwrap_or(if self.flags.is_some() {
WORD_MEMORY_LIMIT
} else {
DEFAULT_MEMORY_LIMIT
})
}
pub fn stack_memory_policy(&self) -> Option<usize> {
self.flags
.and_then(|word| word.max_stack_memory_usage(self.stack_memory_policy))
}
pub fn set_stack_memory_policy(&mut self, bytes: usize) {
self.stack_memory_policy = bytes;
}
pub fn reset(&mut self) {
self.context = ExecutionContext::UnlockingScript;
self.program_counter = 0;
self.last_code_separator = None;
self.stack.clear();
self.alt_stack.clear();
self.if_stack.clear();
self.else_stack.clear();
self.returning = false;
self.stack_mem = 0;
self.alt_stack_mem = 0;
self.node_mem = 0;
}
pub fn validate(&mut self) -> Result<bool, ScriptEvaluationError> {
if let Some(flags) = self.flags {
if let Err(e) = flags.check() {
return Err(self.error(&format!("Invalid verification flags: {e}.")));
}
}
if self.require_push_only && !self.unlocking_script.is_push_only() {
return Err(self.error(
"Unlocking scripts can only contain push operations, and no other opcodes.",
));
}
while self.step()? {
if self.context == ExecutionContext::LockingScript
&& self.program_counter >= self.locking_chunks.len()
{
break;
}
}
if !self.if_stack.is_empty() {
return Err(self.error(
"Every OP_IF, OP_NOTIF, or OP_ELSE must be terminated with OP_ENDIF prior to the end of the script.",
));
}
if self.require_clean_stack && self.stack.len() != 1 {
return Err(self.error(&format!(
"The clean stack rule requires exactly one item to be on the stack after script execution, found {}.",
self.stack.len()
)));
}
if self.stack.is_empty() {
return Err(self.error(
"The top stack element must be truthy after script evaluation (stack is empty).",
));
}
if !ScriptNum::cast_to_bool(&self.stack[self.stack.len() - 1]) {
return Err(self.error("The top stack element must be truthy after script evaluation."));
}
Ok(true)
}
pub fn step(&mut self) -> Result<bool, ScriptEvaluationError> {
if self.flags.is_none() {
let limit = self.memory_limit();
if self.stack_mem > limit {
return Err(self.resource_error(ScriptResource::Stack, self.stack_mem));
}
if self.alt_stack_mem > limit {
return Err(self.resource_error(ScriptResource::AltStack, self.alt_stack_mem));
}
}
if self.context == ExecutionContext::UnlockingScript
&& self.program_counter >= self.unlocking_chunks.len()
{
if !self.if_stack.is_empty() {
return Err(self.error(
"Every OP_IF, OP_NOTIF, or OP_ELSE must be terminated with OP_ENDIF prior to the end of the unlocking script.",
));
}
self.alt_stack.clear();
self.alt_stack_mem = 0;
self.last_code_separator = None;
self.else_stack.clear();
self.returning = false;
self.context = ExecutionContext::LockingScript;
self.program_counter = 0;
}
let current_len = match self.context {
ExecutionContext::UnlockingScript => self.unlocking_chunks.len(),
ExecutionContext::LockingScript => self.locking_chunks.len(),
};
if self.program_counter >= current_len {
return Ok(false);
}
let op_owned = match self.context {
ExecutionContext::UnlockingScript => {
self.unlocking_chunks[self.program_counter].clone()
}
ExecutionContext::LockingScript => self.locking_chunks[self.program_counter].clone(),
};
let operation = &op_owned;
let current_opcode = operation.op;
let truncated = match self.context {
ExecutionContext::UnlockingScript => self.unlocking_truncated,
ExecutionContext::LockingScript => self.locking_truncated,
};
if truncated == Some(self.program_counter) {
return Err(self.error(&format!(
"A push declares more bytes than the script holds; the script cannot be parsed past it (pc={}).",
self.program_counter
)));
}
if let Some(ref data) = operation.data {
if data.len() > MAX_SCRIPT_ELEMENT_SIZE {
return Err(self.error(&format!(
"Data push > {} bytes (pc={})",
MAX_SCRIPT_ELEMENT_SIZE, self.program_counter
)));
}
}
let is_executing = !self.returning && !self.if_stack.contains(&false);
if is_executing && is_opcode_disabled(current_opcode, self.utxo_after_chronicle) {
return Err(self.error(&format!(
"This opcode is currently disabled. (Opcode: {}, PC: {})",
opcode_to_name(current_opcode).unwrap_or("UNKNOWN"),
self.program_counter
)));
}
if is_executing && current_opcode <= OP_PUSHDATA4 {
if self.require_minimal && !is_chunk_minimal_push(operation) {
return Err(self.error(&format!(
"This data is not minimally-encoded. (PC: {})",
self.program_counter
)));
}
let data = operation.data.clone().unwrap_or_default();
self.push_stack(data)?;
} else if is_executing || (OP_IF..=OP_ENDIF).contains(¤t_opcode) {
self.execute_opcode(current_opcode, operation)?;
}
self.program_counter += 1;
Ok(true)
}
fn execute_opcode(
&mut self,
opcode: u8,
chunk: &ScriptChunk,
) -> Result<(), ScriptEvaluationError> {
let is_executing = !self.returning && !self.if_stack.contains(&false);
match opcode {
OP_1NEGATE => {
self.push_stack_copy(&SCRIPTNUM_NEG_1)?;
}
OP_0 => {
self.push_stack_copy(&SCRIPTNUMS_0_TO_16[0])?;
}
OP_1..=OP_16 => {
let n = (opcode - OP_1 + 1) as usize;
self.push_stack_copy(&SCRIPTNUMS_0_TO_16[n])?;
}
OP_NOP => {}
OP_NOP1 | OP_NOP2 | OP_NOP3 | OP_NOP9 | OP_NOP10 => {
if self.discourage_upgradable_nops {
return Err(self.error(&format!(
"{} is discouraged by verification flags.",
opcode_to_name(opcode).unwrap_or("OP_NOP")
)));
}
}
OP_NOP4 | OP_NOP5 | OP_NOP6 | OP_NOP7 | OP_NOP8 => {
if !self.utxo_after_chronicle {
if self.discourage_upgradable_nops {
return Err(self.error(&format!(
"{} is discouraged by verification flags.",
opcode_to_name(opcode).unwrap_or("OP_NOP")
)));
}
} else {
self.op_chronicle_splice(opcode)?;
}
}
OP_VER => {
if !self.utxo_after_chronicle {
return Err(self.error("OP_VER is disabled until Chronicle."));
}
self.push_stack(self.transaction_version.to_le_bytes().to_vec())?;
}
OP_IF | OP_NOTIF => {
let mut f_value = false;
if is_executing {
if self.stack.is_empty() {
return Err(self.error(
"OP_IF and OP_NOTIF require at least one item on the stack when they are used!",
));
}
let buf = self.pop_stack()?;
if self.require_minimal_if && !(buf.is_empty() || buf == [1]) {
return Err(self.error("OP_IF and OP_NOTIF require minimal truth values."));
}
f_value = ScriptNum::cast_to_bool(&buf);
if opcode == OP_NOTIF {
f_value = !f_value;
}
}
self.if_stack.push(f_value);
self.else_stack.push(false);
}
OP_VERIF | OP_VERNOTIF => {
if !self.utxo_after_chronicle {
if !is_executing {
return Ok(());
}
return Err(self.error(&format!(
"{} is disabled until Chronicle.",
opcode_to_name(opcode).unwrap_or("OP_VERIF")
)));
}
let mut f_value = false;
if is_executing {
if self.stack.is_empty() {
return Err(self.error(
"OP_VERIF and OP_VERNOTIF require at least one item on the stack when they are used!",
));
}
let buf = self.pop_stack()?;
if buf.len() == 4 {
f_value = buf == self.transaction_version.to_le_bytes();
}
if opcode == OP_VERNOTIF {
f_value = !f_value;
}
}
self.if_stack.push(f_value);
self.else_stack.push(false);
}
OP_ELSE => {
if self.if_stack.is_empty() {
return Err(self.error("OP_ELSE requires a preceeding OP_IF."));
}
if self.else_stack.last() == Some(&true) {
return Err(self.error(
"OP_ELSE may only be used once for each OP_IF or OP_NOTIF after Genesis.",
));
}
if let Some(seen) = self.else_stack.last_mut() {
*seen = true;
}
let last = self.if_stack.len() - 1;
self.if_stack[last] = !self.if_stack[last];
}
OP_ENDIF => {
if self.if_stack.is_empty() {
return Err(self.error("OP_ENDIF requires a preceeding OP_IF."));
}
self.if_stack.pop();
self.else_stack.pop();
}
OP_VERIFY => {
if self.stack.is_empty() {
return Err(
self.error("OP_VERIFY requires at least one item to be on the stack.")
);
}
let f_value = ScriptNum::cast_to_bool(self.stack_top()?);
if !f_value {
return Err(self.error("OP_VERIFY requires the top stack value to be truthy."));
}
self.pop_stack()?;
}
OP_RETURN => {
if self.if_stack.is_empty() {
let end = match self.context {
ExecutionContext::UnlockingScript => self.unlocking_chunks.len(),
ExecutionContext::LockingScript => self.locking_chunks.len(),
};
self.program_counter = end;
if self.program_counter > 0 {
self.program_counter -= 1;
}
} else {
self.returning = true;
}
}
OP_TOALTSTACK => {
if self.stack.is_empty() {
return Err(
self.error("OP_TOALTSTACK requires at least one item to be on the stack.")
);
}
let item = self.pop_stack()?;
self.push_alt_stack(item)?;
}
OP_FROMALTSTACK => {
if self.alt_stack.is_empty() {
return Err(self.error(
"OP_FROMALTSTACK requires at least one item to be on the alt stack.",
));
}
let item = self.pop_alt_stack()?;
self.push_stack(item)?;
}
OP_2DROP => {
if self.stack.len() < 2 {
return Err(
self.error("OP_2DROP requires at least two items to be on the stack.")
);
}
self.pop_stack()?;
self.pop_stack()?;
}
OP_2DUP => {
if self.stack.len() < 2 {
return Err(
self.error("OP_2DUP requires at least two items to be on the stack.")
);
}
let buf1 = self.stack_top_n(2)?.to_vec();
let buf2 = self.stack_top()?.to_vec();
self.push_stack(buf1)?;
self.push_stack(buf2)?;
}
OP_3DUP => {
if self.stack.len() < 3 {
return Err(
self.error("OP_3DUP requires at least three items to be on the stack.")
);
}
let buf1 = self.stack_top_n(3)?.to_vec();
let buf2 = self.stack_top_n(2)?.to_vec();
let buf3 = self.stack_top()?.to_vec();
self.push_stack(buf1)?;
self.push_stack(buf2)?;
self.push_stack(buf3)?;
}
OP_2OVER => {
if self.stack.len() < 4 {
return Err(
self.error("OP_2OVER requires at least four items to be on the stack.")
);
}
let buf1 = self.stack_top_n(4)?.to_vec();
let buf2 = self.stack_top_n(3)?.to_vec();
self.push_stack(buf1)?;
self.push_stack(buf2)?;
}
OP_2ROT => {
if self.stack.len() < 6 {
return Err(
self.error("OP_2ROT requires at least six items to be on the stack.")
);
}
let x6 = self.pop_stack()?;
let x5 = self.pop_stack()?;
let x4 = self.pop_stack()?;
let x3 = self.pop_stack()?;
let x2 = self.pop_stack()?;
let x1 = self.pop_stack()?;
self.push_stack(x3)?;
self.push_stack(x4)?;
self.push_stack(x5)?;
self.push_stack(x6)?;
self.push_stack(x1)?;
self.push_stack(x2)?;
}
OP_2SWAP => {
if self.stack.len() < 4 {
return Err(
self.error("OP_2SWAP requires at least four items to be on the stack.")
);
}
let x4 = self.pop_stack()?;
let x3 = self.pop_stack()?;
let x2 = self.pop_stack()?;
let x1 = self.pop_stack()?;
self.push_stack(x3)?;
self.push_stack(x4)?;
self.push_stack(x1)?;
self.push_stack(x2)?;
}
OP_IFDUP => {
if self.stack.is_empty() {
return Err(
self.error("OP_IFDUP requires at least one item to be on the stack.")
);
}
let top = self.stack_top()?.to_vec();
if ScriptNum::cast_to_bool(&top) {
self.push_stack(top)?;
}
}
OP_DEPTH => {
let depth = BigNumber::from_u64(self.stack.len() as u64);
self.push_stack(ScriptNum::to_bytes(&depth))?;
}
OP_DROP => {
if self.stack.is_empty() {
return Err(
self.error("OP_DROP requires at least one item to be on the stack.")
);
}
self.pop_stack()?;
}
OP_DUP => {
if self.stack.is_empty() {
return Err(self.error("OP_DUP requires at least one item to be on the stack."));
}
let top = self.stack_top()?.to_vec();
self.push_stack(top)?;
}
OP_NIP => {
if self.stack.len() < 2 {
return Err(
self.error("OP_NIP requires at least two items to be on the stack.")
);
}
let top = self.pop_stack()?;
self.pop_stack()?;
self.push_stack(top)?;
}
OP_OVER => {
if self.stack.len() < 2 {
return Err(
self.error("OP_OVER requires at least two items to be on the stack.")
);
}
let second = self.stack_top_n(2)?.to_vec();
self.push_stack(second)?;
}
OP_PICK | OP_ROLL => {
if self.stack.len() < 2 {
return Err(self.error(&format!(
"{} requires at least two items to be on the stack.",
opcode_to_name(opcode).unwrap_or("OP_PICK/ROLL")
)));
}
let n_bytes = self.pop_stack()?;
let bn = self.read_number(&n_bytes)?;
let n = bn.to_i64().unwrap_or(i64::MAX);
if n < 0 || n >= self.stack.len() as i64 {
return Err(self.error(&format!(
"{} requires the top stack element to be 0 or a positive number less than the current size of the stack.",
opcode_to_name(opcode).unwrap_or("OP_PICK/ROLL")
)));
}
let n_idx = n as usize;
let item = self.stack[self.stack.len() - 1 - n_idx].clone();
if opcode == OP_ROLL {
let remove_idx = self.stack.len() - 1 - n_idx;
let removed = self.stack.remove(remove_idx);
self.stack_mem -= removed.len();
self.release(removed.len());
self.push_stack(item)?;
} else {
self.push_stack(item)?;
}
}
OP_ROT => {
if self.stack.len() < 3 {
return Err(
self.error("OP_ROT requires at least three items to be on the stack.")
);
}
let x3 = self.pop_stack()?;
let x2 = self.pop_stack()?;
let x1 = self.pop_stack()?;
self.push_stack(x2)?;
self.push_stack(x3)?;
self.push_stack(x1)?;
}
OP_SWAP => {
if self.stack.len() < 2 {
return Err(
self.error("OP_SWAP requires at least two items to be on the stack.")
);
}
let x2 = self.pop_stack()?;
let x1 = self.pop_stack()?;
self.push_stack(x2)?;
self.push_stack(x1)?;
}
OP_TUCK => {
if self.stack.len() < 2 {
return Err(
self.error("OP_TUCK requires at least two items to be on the stack.")
);
}
let top = self.stack_top()?.to_vec();
self.reserve_stack(top.len())?;
let insert_idx = self.stack.len() - 2;
self.stack.insert(insert_idx, top.clone());
self.stack_mem += top.len();
}
OP_SIZE => {
if self.stack.is_empty() {
return Err(
self.error("OP_SIZE requires at least one item to be on the stack.")
);
}
let size = self.stack_top()?.len();
let bn = BigNumber::from_u64(size as u64);
self.push_stack(ScriptNum::to_bytes(&bn))?;
}
OP_CAT => {
if self.stack.len() < 2 {
return Err(
self.error("OP_CAT requires at least two items to be on the stack.")
);
}
let buf2 = self.pop_stack()?;
let buf1 = self.pop_stack()?;
let mut result = buf1;
result.extend(buf2);
if self.flags.is_none() && result.len() > MAX_SCRIPT_ELEMENT_SIZE {
return Err(self.error(&format!(
"It's not currently possible to push data larger than {} bytes.",
MAX_SCRIPT_ELEMENT_SIZE
)));
}
self.push_stack(result)?;
}
OP_SPLIT => {
if self.stack.len() < 2 {
return Err(
self.error("OP_SPLIT requires at least two items to be on the stack.")
);
}
let pos_bytes = self.pop_stack()?;
let data = self.pop_stack()?;
let pos_bn = self.read_number(&pos_bytes)?;
let pos = pos_bn.to_i64().unwrap_or(-1);
if pos < 0 || pos > data.len() as i64 {
return Err(self.error(
"OP_SPLIT requires the first stack item to be a non-negative number less than or equal to the size of the second-from-top stack item.",
));
}
let split_idx = pos as usize;
let left = data[..split_idx].to_vec();
let right = data[split_idx..].to_vec();
self.push_stack(left)?;
self.push_stack(right)?;
}
OP_NUM2BIN => {
if self.stack.len() < 2 {
return Err(
self.error("OP_NUM2BIN requires at least two items to be on the stack.")
);
}
let size_bytes = self.pop_stack()?;
let size_bn = self.read_number(&size_bytes)?;
let size = size_bn.to_i64().unwrap_or(-1);
let size = if self.flags.is_some() {
if size < 0 || size > i64::from(i32::MAX) {
return Err(self.error(&format!(
"OP_NUM2BIN requires a size from 0 to 2147483647, found {}.",
size_bn.to_dec_string()
)));
}
size as usize
} else {
if size < 0 || size > MAX_SCRIPT_ELEMENT_SIZE as i64 {
return Err(self.error(&format!(
"It's not currently possible to push data larger than {} bytes or negative size.",
MAX_SCRIPT_ELEMENT_SIZE
)));
}
let size = size as usize;
if size > self.memory_limit() {
return Err(self.resource_error(ScriptResource::ElementSize, size));
}
size
};
let rawnum = self.pop_stack()?;
let minimal = ScriptNum::minimally_encode(&rawnum);
if minimal.len() > size {
return Err(self.error(
"OP_NUM2BIN requires that the size expressed in the top stack item is large enough to hold the value expressed in the second-from-top stack item.",
));
}
if self.flags.is_some() && minimal.len() < size {
self.check_node_budget(
self.node_mem + size as u64 + STACK_ELEMENT_OVERHEAD as u64,
)?;
}
if minimal.len() == size {
self.push_stack(minimal)?;
} else {
let mut result = vec![0u8; size];
let mut signbit = 0u8;
if !minimal.is_empty() {
signbit = minimal[minimal.len() - 1] & 0x80;
let mut minimal_copy = minimal.clone();
if let Some(last) = minimal_copy.last_mut() {
*last &= 0x7f;
}
result[..minimal_copy.len()].copy_from_slice(&minimal_copy);
}
if signbit != 0 {
result[size - 1] |= 0x80;
}
self.push_stack(result)?;
}
}
OP_BIN2NUM => {
if self.stack.is_empty() {
return Err(
self.error("OP_BIN2NUM requires at least one item to be on the stack.")
);
}
let buf = self.pop_stack()?;
let result = ScriptNum::minimally_encode(&buf);
let over = matches!(self.max_script_num_length(), Some(max) if result.len() > max);
if over || !ScriptNum::is_minimally_encoded(&result) {
return Err(
self.error("OP_BIN2NUM requires that the resulting number is valid.")
);
}
self.push_stack(result)?;
}
OP_INVERT => {
if self.stack.is_empty() {
return Err(
self.error("OP_INVERT requires at least one item to be on the stack.")
);
}
let buf = self.pop_stack()?;
let result: Vec<u8> = buf.iter().map(|&b| !b).collect();
self.push_stack(result)?;
}
OP_AND | OP_OR | OP_XOR => {
if self.stack.len() < 2 {
return Err(self.error(&format!(
"{} requires at least two items on the stack.",
opcode_to_name(opcode).unwrap_or("OP")
)));
}
let buf2 = self.pop_stack()?;
let buf1 = self.pop_stack()?;
if buf1.len() != buf2.len() {
return Err(self.error(&format!(
"{} requires the top two stack items to be the same size.",
opcode_to_name(opcode).unwrap_or("OP")
)));
}
let result: Vec<u8> = buf1
.iter()
.zip(buf2.iter())
.map(|(&a, &b)| match opcode {
OP_AND => a & b,
OP_OR => a | b,
_ => a ^ b, })
.collect();
self.push_stack(result)?;
}
OP_EQUAL | OP_EQUALVERIFY => {
if self.stack.len() < 2 {
return Err(self.error(&format!(
"{} requires at least two items to be on the stack.",
opcode_to_name(opcode).unwrap_or("OP_EQUAL")
)));
}
let buf2 = self.pop_stack()?;
let buf1 = self.pop_stack()?;
let equal = buf1 == buf2;
self.push_stack(if equal { vec![1] } else { vec![] })?;
if opcode == OP_EQUALVERIFY {
if !equal {
return Err(self.error(
"OP_EQUALVERIFY requires the top two stack items to be equal.",
));
}
self.pop_stack()?;
}
}
OP_LSHIFT | OP_RSHIFT => {
if self.stack.len() < 2 {
return Err(self.error(&format!(
"{} requires at least two items to be on the stack.",
opcode_to_name(opcode).unwrap_or("OP")
)));
}
let n_bytes = self.pop_stack()?;
let buf = self.pop_stack()?;
let n_bn = self.read_number(&n_bytes)?;
let n = n_bn.to_i64().unwrap_or(-1);
if n < 0 {
return Err(self.error(&format!(
"{} requires the top item on the stack not to be negative.",
opcode_to_name(opcode).unwrap_or("OP")
)));
}
if buf.is_empty() {
self.push_stack(vec![])?;
} else {
let len = buf.len();
let result: Vec<u8> = if (n as u128) >= (len as u128) * 8 {
vec![0u8; len]
} else {
let byte_shift = (n as usize) / 8;
let bit_shift = (n as usize) % 8;
let mut out = vec![0u8; len];
#[allow(clippy::needless_range_loop)]
for i in 0..len {
if opcode == OP_LSHIFT {
let src = i + byte_shift;
let hi = if src < len { buf[src] } else { 0 };
let lo = if bit_shift > 0 && src + 1 < len {
buf[src + 1]
} else {
0
};
out[i] = if bit_shift == 0 {
hi
} else {
(hi << bit_shift) | (lo >> (8 - bit_shift))
};
} else {
if i >= byte_shift {
let src = i - byte_shift;
let hi = buf[src];
let carry = if bit_shift > 0 && src >= 1 {
buf[src - 1]
} else {
0
};
out[i] = if bit_shift == 0 {
hi
} else {
(hi >> bit_shift) | (carry << (8 - bit_shift))
};
}
}
}
out
};
self.push_stack(result)?;
}
}
OP_1ADD | OP_1SUB | OP_2MUL | OP_2DIV | OP_NEGATE | OP_ABS | OP_NOT | OP_0NOTEQUAL => {
if self.stack.is_empty() {
return Err(self.error(&format!(
"{} requires at least one item to be on the stack.",
opcode_to_name(opcode).unwrap_or("OP")
)));
}
let buf = self.pop_stack()?;
let mut bn = self.read_number(&buf)?;
bn = match opcode {
OP_1ADD => bn.add(&BigNumber::one()),
OP_1SUB => bn.sub(&BigNumber::one()),
OP_2MUL => bn.add(&bn),
OP_2DIV => bn.div(&BigNumber::from_i64(2)),
OP_NEGATE => bn.neg(),
OP_ABS => bn.abs(),
OP_NOT => {
if bn.is_zero() {
BigNumber::one()
} else {
BigNumber::zero()
}
}
OP_0NOTEQUAL => {
if bn.is_zero() {
BigNumber::zero()
} else {
BigNumber::one()
}
}
_ => bn,
};
self.push_number(&bn)?;
}
OP_ADD
| OP_SUB
| OP_MUL
| OP_DIV
| OP_MOD
| OP_BOOLAND
| OP_BOOLOR
| OP_NUMEQUAL
| OP_NUMEQUALVERIFY
| OP_NUMNOTEQUAL
| OP_LESSTHAN
| OP_GREATERTHAN
| OP_LESSTHANOREQUAL
| OP_GREATERTHANOREQUAL
| OP_MIN
| OP_MAX => {
if self.stack.len() < 2 {
return Err(self.error(&format!(
"{} requires at least two items to be on the stack.",
opcode_to_name(opcode).unwrap_or("OP")
)));
}
let buf2 = self.pop_stack()?;
let buf1 = self.pop_stack()?;
let bn1 = self.read_number(&buf1)?;
let bn2 = self.read_number(&buf2)?;
let result = match opcode {
OP_ADD => bn1.add(&bn2),
OP_SUB => bn1.sub(&bn2),
OP_MUL => bn1.mul(&bn2),
OP_DIV => {
if bn2.is_zero() {
return Err(self.error("OP_DIV cannot divide by zero!"));
}
bn1.div(&bn2)
}
OP_MOD => {
if bn2.is_zero() {
return Err(self.error("OP_MOD cannot divide by zero!"));
}
bn1.mod_floor(&bn2)
}
OP_BOOLAND => {
if !bn1.is_zero() && !bn2.is_zero() {
BigNumber::one()
} else {
BigNumber::zero()
}
}
OP_BOOLOR => {
if !bn1.is_zero() || !bn2.is_zero() {
BigNumber::one()
} else {
BigNumber::zero()
}
}
OP_NUMEQUAL | OP_NUMEQUALVERIFY => {
if bn1 == bn2 {
BigNumber::one()
} else {
BigNumber::zero()
}
}
OP_NUMNOTEQUAL => {
if bn1 != bn2 {
BigNumber::one()
} else {
BigNumber::zero()
}
}
OP_LESSTHAN => {
if bn1 < bn2 {
BigNumber::one()
} else {
BigNumber::zero()
}
}
OP_GREATERTHAN => {
if bn1 > bn2 {
BigNumber::one()
} else {
BigNumber::zero()
}
}
OP_LESSTHANOREQUAL => {
if bn1 <= bn2 {
BigNumber::one()
} else {
BigNumber::zero()
}
}
OP_GREATERTHANOREQUAL => {
if bn1 >= bn2 {
BigNumber::one()
} else {
BigNumber::zero()
}
}
OP_MIN => {
if bn1 < bn2 {
bn1
} else {
bn2
}
}
OP_MAX => {
if bn1 > bn2 {
bn1
} else {
bn2
}
}
_ => BigNumber::zero(),
};
self.push_number(&result)?;
if opcode == OP_NUMEQUALVERIFY {
if !ScriptNum::cast_to_bool(self.stack_top()?) {
return Err(self
.error("OP_NUMEQUALVERIFY requires the top stack item to be truthy."));
}
self.pop_stack()?;
}
}
OP_WITHIN => {
if self.stack.len() < 3 {
return Err(
self.error("OP_WITHIN requires at least three items to be on the stack.")
);
}
let max_bytes = self.pop_stack()?;
let min_bytes = self.pop_stack()?;
let x_bytes = self.pop_stack()?;
let max_bn = self.read_number(&max_bytes)?;
let min_bn = self.read_number(&min_bytes)?;
let x_bn = self.read_number(&x_bytes)?;
let in_range = x_bn >= min_bn && x_bn < max_bn;
self.push_stack(if in_range { vec![1] } else { vec![] })?;
}
OP_RIPEMD160 => {
if self.stack.is_empty() {
return Err(
self.error("OP_RIPEMD160 requires at least one item to be on the stack.")
);
}
let buf = self.pop_stack()?;
let hash = ripemd160(&buf);
self.push_stack(hash.to_vec())?;
}
OP_SHA1 => {
if self.stack.is_empty() {
return Err(
self.error("OP_SHA1 requires at least one item to be on the stack.")
);
}
let buf = self.pop_stack()?;
let hash = sha1(&buf);
self.push_stack(hash.to_vec())?;
}
OP_SHA256 => {
if self.stack.is_empty() {
return Err(
self.error("OP_SHA256 requires at least one item to be on the stack.")
);
}
let buf = self.pop_stack()?;
let hash = sha256(&buf);
self.push_stack(hash.to_vec())?;
}
OP_HASH160 => {
if self.stack.is_empty() {
return Err(
self.error("OP_HASH160 requires at least one item to be on the stack.")
);
}
let buf = self.pop_stack()?;
let hash = hash160(&buf);
self.push_stack(hash.to_vec())?;
}
OP_HASH256 => {
if self.stack.is_empty() {
return Err(
self.error("OP_HASH256 requires at least one item to be on the stack.")
);
}
let buf = self.pop_stack()?;
let hash = sha256d(&buf);
self.push_stack(hash.to_vec())?;
}
OP_CODESEPARATOR => {
self.last_code_separator = Some(self.program_counter);
}
OP_CHECKSIG | OP_CHECKSIGVERIFY => {
if self.stack.len() < 2 {
return Err(self.error(&format!(
"{} requires at least two items to be on the stack.",
opcode_to_name(opcode).unwrap_or("OP_CHECKSIG")
)));
}
let pubkey_bytes = self.pop_stack()?;
let sig_bytes = self.pop_stack()?;
self.check_signature_encoding(&sig_bytes)?;
self.check_public_key_encoding(&pubkey_bytes)?;
let subscript = self.build_subscript(&sig_bytes)?;
let success = if sig_bytes.is_empty() {
false
} else {
self.verify_signature(&sig_bytes, &pubkey_bytes, &subscript)?
};
if !success && self.require_null_fail && !sig_bytes.is_empty() {
return Err(self.error(&format!(
"{} requires failing signatures to be empty.",
opcode_to_name(opcode).unwrap_or("OP_CHECKSIG")
)));
}
self.push_stack(if success { vec![1] } else { vec![] })?;
if opcode == OP_CHECKSIGVERIFY {
if !success {
return Err(self.error(
"OP_CHECKSIGVERIFY requires that a valid signature is provided.",
));
}
self.pop_stack()?;
}
}
OP_CHECKMULTISIG | OP_CHECKMULTISIGVERIFY => {
self.op_checkmultisig(opcode)?;
}
0x01..=0x4b => {
let data = chunk.data.clone().unwrap_or_default();
self.push_stack(data)?;
}
OP_PUSHDATA1 | OP_PUSHDATA2 | OP_PUSHDATA4 => {
let data = chunk.data.clone().unwrap_or_default();
self.push_stack(data)?;
}
_ => {
return Err(self.error(&format!(
"Invalid opcode {} (pc={}).",
opcode, self.program_counter
)));
}
}
Ok(())
}
fn op_checkmultisig(&mut self, opcode: u8) -> Result<(), ScriptEvaluationError> {
if self.stack.is_empty() {
return Err(self.error(&format!(
"{} requires at least 1 item for nKeys.",
opcode_to_name(opcode).unwrap_or("OP_CHECKMULTISIG")
)));
}
let n_keys_bytes = self.pop_stack()?;
let n_keys_bn = self.read_count(&n_keys_bytes)?;
let n_keys = n_keys_bn.to_i64().unwrap_or(-1);
if !(0..=MAX_MULTISIG_KEY_COUNT).contains(&n_keys) {
return Err(self.error(&format!(
"{} requires a key count between 0 and {}.",
opcode_to_name(opcode).unwrap_or("OP_CHECKMULTISIG"),
MAX_MULTISIG_KEY_COUNT
)));
}
let n_keys = n_keys as usize;
if self.stack.len() < n_keys {
return Err(self.error(&format!(
"{} stack too small for keys. Need {}, have {}.",
opcode_to_name(opcode).unwrap_or("OP_CHECKMULTISIG"),
n_keys,
self.stack.len()
)));
}
let mut pubkeys = Vec::with_capacity(n_keys);
for _ in 0..n_keys {
pubkeys.push(self.pop_stack()?);
}
if self.stack.is_empty() {
return Err(self.error(&format!(
"{} requires item for nSigs.",
opcode_to_name(opcode).unwrap_or("OP_CHECKMULTISIG")
)));
}
let n_sigs_bytes = self.pop_stack()?;
let n_sigs_bn = self.read_count(&n_sigs_bytes)?;
let n_sigs = n_sigs_bn.to_i64().unwrap_or(-1);
if n_sigs < 0 || n_sigs as usize > n_keys {
return Err(self.error(&format!(
"{} requires the number of signatures to be no greater than the number of keys.",
opcode_to_name(opcode).unwrap_or("OP_CHECKMULTISIG")
)));
}
let n_sigs = n_sigs as usize;
if self.stack.len() < n_sigs {
return Err(self.error(&format!(
"{} stack too small for sigs. Need {}, have {}.",
opcode_to_name(opcode).unwrap_or("OP_CHECKMULTISIG"),
n_sigs,
self.stack.len()
)));
}
let mut sigs = Vec::with_capacity(n_sigs);
for _ in 0..n_sigs {
sigs.push(self.pop_stack()?);
}
let base_script = match self.context {
ExecutionContext::UnlockingScript => self.unlocking_script.as_script().clone(),
ExecutionContext::LockingScript => self.locking_script.as_script().clone(),
};
let start_idx = self.last_code_separator.map(|i| i + 1).unwrap_or(0);
let chunks = base_script.chunks();
let mut subscript_chunks: Vec<ScriptChunk> = chunks.into_iter().skip(start_idx).collect();
if self.context == ExecutionContext::UnlockingScript {
subscript_chunks.extend(self.locking_script.as_script().chunks());
}
let mut subscript = Script::from_chunks(subscript_chunks);
for sig in &sigs {
if !has_forkid_bit(sig) {
let mut sig_script = Script::new();
sig_script.write_bin(sig);
subscript.find_and_delete(&sig_script);
}
}
let mut success = true;
let mut sig_idx = 0;
let mut key_idx = 0;
while success && sig_idx < n_sigs {
if key_idx >= n_keys {
success = false;
break;
}
let sig_bytes = &sigs[sig_idx];
let pubkey_bytes = &pubkeys[key_idx];
if self.check_signature_encoding(sig_bytes).is_err()
|| self.check_public_key_encoding(pubkey_bytes).is_err()
{
return Err(self.error(&format!(
"{} requires correct encoding for the public key and signature.",
opcode_to_name(opcode).unwrap_or("OP_CHECKMULTISIG")
)));
}
let sig_valid = if sig_bytes.is_empty() {
false
} else {
self.verify_signature(sig_bytes, pubkey_bytes, &subscript)
.unwrap_or(false)
};
if sig_valid {
sig_idx += 1;
}
key_idx += 1;
if n_sigs - sig_idx > n_keys - key_idx {
success = false;
}
}
if !success && self.require_null_fail && sigs.iter().any(|s| !s.is_empty()) {
return Err(self.error(&format!(
"{} requires failing signatures to be empty.",
opcode_to_name(opcode).unwrap_or("OP_CHECKMULTISIG")
)));
}
if self.stack.is_empty() {
return Err(self.error(&format!(
"{} requires an extra item (dummy) to be on the stack.",
opcode_to_name(opcode).unwrap_or("OP_CHECKMULTISIG")
)));
}
let dummy = self.pop_stack()?;
if self.require_null_dummy && !dummy.is_empty() {
return Err(self.error(&format!(
"{} requires the extra stack item (dummy) to be empty.",
opcode_to_name(opcode).unwrap_or("OP_CHECKMULTISIG")
)));
}
self.push_stack(if success { vec![1] } else { vec![] })?;
if opcode == OP_CHECKMULTISIGVERIFY {
if !success {
return Err(self.error(
"OP_CHECKMULTISIGVERIFY requires that a sufficient number of valid signatures are provided.",
));
}
self.pop_stack()?;
}
Ok(())
}
fn check_signature_encoding(&self, sig: &[u8]) -> Result<(), ScriptEvaluationError> {
if sig.is_empty() {
return Ok(());
}
if !is_valid_signature_encoding(sig) {
return Err(self.error("The signature format is invalid."));
}
let tx_sig = TransactionSignature::from_checksig_format(sig)
.map_err(|_| self.error("The signature format is invalid."))?;
if self.require_low_s && !tx_sig.has_low_s() && !signature_overflows_the_order(&tx_sig) {
return Err(self.error("The signature must have a low S value."));
}
if self
.flags
.is_none_or(|word| word.contains(ScriptFlags::STRICTENC))
{
let base =
tx_sig.scope() & !(SIGHASH_CHRONICLE | SIGHASH_FORKID | SIGHASH_ANYONECANPAY);
if !(SIGHASH_ALL..=SIGHASH_SINGLE).contains(&base) {
return Err(self.error(&format!(
"The signature's hash type (0x{:02x}) has no defined base type.",
tx_sig.scope()
)));
}
}
if let Some(word) = self.flags {
if word.contains(ScriptFlags::STRICTENC)
&& !word.contains(ScriptFlags::CHRONICLE)
&& (tx_sig.scope() & SIGHASH_CHRONICLE) != 0
{
return Err(
self.error("The signature must not use SIGHASH_CHRONICLE before Chronicle.")
);
}
}
if (tx_sig.scope() & SIGHASH_FORKID) == 0 {
return Err(self.error("The signature must use SIGHASH_FORKID."));
}
Ok(())
}
fn check_public_key_encoding(&self, pubkey: &[u8]) -> Result<(), ScriptEvaluationError> {
if pubkey.is_empty() {
return Err(self.error("Public key is empty."));
}
if pubkey.len() < 33 {
return Err(self.error("The public key is too short, it must be at least 33 bytes."));
}
if pubkey[0] == 0x04 {
if pubkey.len() != 65 {
return Err(self.error("The non-compressed public key must be 65 bytes."));
}
} else if pubkey[0] == 0x02 || pubkey[0] == 0x03 {
if pubkey.len() != 33 {
return Err(self.error("The compressed public key must be 33 bytes."));
}
} else {
return Err(self.error("The public key is in an unknown format."));
}
if self.require_compressed_pubkey && pubkey[0] == 0x04 {
return Err(self.error("The public key must be compressed."));
}
PublicKey::from_bytes(pubkey)
.map_err(|_| self.error("The public key is in an unknown format."))?;
Ok(())
}
fn build_subscript(&self, sig_bytes: &[u8]) -> Result<Script, ScriptEvaluationError> {
let base_script = match self.context {
ExecutionContext::UnlockingScript => self.unlocking_script.as_script().clone(),
ExecutionContext::LockingScript => self.locking_script.as_script().clone(),
};
let start_idx = self.last_code_separator.map(|i| i + 1).unwrap_or(0);
let chunks = base_script.chunks();
let mut subscript_chunks: Vec<ScriptChunk> = chunks.into_iter().skip(start_idx).collect();
if self.context == ExecutionContext::UnlockingScript {
subscript_chunks.extend(self.locking_script.as_script().chunks());
}
let mut subscript = Script::from_chunks(subscript_chunks);
if !has_forkid_bit(sig_bytes) {
let mut sig_script = Script::new();
sig_script.write_bin(sig_bytes);
subscript.find_and_delete(&sig_script);
}
Ok(subscript)
}
fn op_chronicle_splice(&mut self, opcode: u8) -> Result<(), ScriptEvaluationError> {
let name = match opcode {
OP_NOP4 => "OP_SUBSTR",
OP_NOP5 => "OP_LEFT",
OP_NOP6 => "OP_RIGHT",
OP_NOP7 => "OP_LSHIFTNUM",
_ => "OP_RSHIFTNUM",
};
let need = if opcode == OP_NOP4 { 3 } else { 2 };
if self.stack.len() < need {
return Err(self.error(&format!(
"{name} requires at least {} items to be on the stack.",
if need == 3 { "three" } else { "two" }
)));
}
let num = |s: &Self, bytes: &[u8]| -> Result<BigNumber, ScriptEvaluationError> {
s.read_number(bytes)
};
match opcode {
OP_NOP4 => {
let len_bytes = self.pop_stack()?;
let off_bytes = self.pop_stack()?;
let len = self.read_splice_operand(&len_bytes)?;
let offset = self.read_splice_operand(&off_bytes)?;
let data = self.pop_stack()?;
let size = data.len() as i64;
if offset < 0 || offset >= size || len < 0 || len > size - offset {
return Err(self.error(&format!(
"OP_SUBSTR offset ({offset}) must be in range [0, {size}) and length ({len}) must be in range [0, {}]",
size - offset
)));
}
let (o, l) = (offset as usize, len as usize);
self.push_stack(data[o..o + l].to_vec())?;
}
OP_NOP5 | OP_NOP6 => {
let len_bytes = self.pop_stack()?;
let len = self.read_splice_operand(&len_bytes)?;
let data = self.pop_stack()?;
let size = data.len() as i64;
if len < 0 || len > size {
return Err(self.error(&format!(
"{name} length ({len}) must be in range [0, {size}]"
)));
}
let l = len as usize;
let out = if opcode == OP_NOP5 {
data[..l].to_vec()
} else {
data[data.len() - l..].to_vec()
};
self.push_stack(out)?;
}
_ => {
let n_bytes = self.pop_stack()?;
let n_bn = num(self, &n_bytes)?;
if n_bn.is_negative() {
return Err(self.error(&format!("{name} bits to shift must not be negative.")));
}
let x_bytes = self.pop_stack()?;
let x = num(self, &x_bytes)?;
if let Some(max) = self.max_script_num_length() {
if opcode == OP_NOP7 {
let current = ScriptNum::to_bytes(&x).len() as u64;
match n_bn.to_u64().and_then(|n| current.checked_add(n / 8)) {
Some(size) if size <= max as u64 => {}
Some(size) => return Err(self.overflow_error(size as usize, max)),
None => {
return Err(self.error(&format!(
"Script number overflow: a shift by {} bits, the limit is {max} bytes.",
n_bn.to_dec_string()
)))
}
}
}
if n_bn.to_i64().is_none_or(|n| n > i64::from(i32::MAX)) {
return Err(self.error(&format!(
"{name} count ({}) does not fit an int.",
n_bn.to_dec_string()
)));
}
}
let n = n_bn.to_i64().map(|v| v as u64).unwrap_or(u64::MAX);
let out = if opcode == OP_NOP7 {
if x.is_zero() {
x
} else if let Some(max) = self.max_script_num_length() {
let bits = (x.bit_length() as u64).saturating_add(n);
let len = bits / 8 + 1;
if len > max as u64 {
return Err(self.overflow_error(len as usize, max));
}
self.check_node_budget(
self.node_mem + len + STACK_ELEMENT_OVERHEAD as u64,
)?;
x.shl_bits(n)
} else {
let bits = (x.bit_length() as u64).saturating_add(n);
let bytes = (bits / 8 + 2) as usize;
if bytes > self.memory_limit() {
return Err(self.resource_error(ScriptResource::ElementSize, bytes));
}
x.shl_bits(n)
}
} else if n >= x.bit_length() as u64 {
BigNumber::zero()
} else {
x.shr_bits_toward_zero(n)
};
self.push_number(&out)?;
}
}
Ok(())
}
fn read_splice_operand(&self, bytes: &[u8]) -> Result<i64, ScriptEvaluationError> {
if self.flags.is_none() {
return Ok(self.read_number(bytes)?.to_i64().unwrap_or(-1));
}
let as_number = self.read_number(bytes)?;
let value = if bytes.len() <= 8 {
as_number.to_i64().unwrap_or(-1)
} else {
let mut first = [0u8; 8];
first.copy_from_slice(&bytes[..8]);
i64::from_le_bytes(first)
};
Ok(value.clamp(i64::from(i32::MIN), i64::from(i32::MAX)))
}
fn verify_signature(
&self,
sig_bytes: &[u8],
pubkey_bytes: &[u8],
subscript: &Script,
) -> Result<bool, ScriptEvaluationError> {
let tx_sig = match TransactionSignature::from_checksig_format(sig_bytes) {
Ok(s) => s,
Err(_) => return Ok(false),
};
let pubkey = match PublicKey::from_bytes(pubkey_bytes) {
Ok(p) => p,
Err(_) => return Ok(false),
};
let inputs = self.build_inputs_array();
let sighash = compute_sighash_dispatched_for_signing(&SighashParams {
version: self.transaction_version,
inputs: &inputs,
outputs: &self.outputs,
locktime: self.lock_time,
input_index: self.input_index,
subscript: &subscript.to_binary(),
satoshis: self.source_satoshis,
scope: tx_sig.scope(),
});
Ok(pubkey.verify(&sighash, tx_sig.signature()))
}
fn build_inputs_array(&self) -> Vec<TxInput> {
let mut inputs = Vec::with_capacity(self.other_inputs.len() + 1);
for (i, other) in self.other_inputs.iter().enumerate() {
if i == self.input_index {
inputs.push(TxInput {
txid: self.source_txid,
output_index: self.source_output_index,
script: self.unlocking_script.to_binary(),
sequence: self.input_sequence,
});
}
inputs.push(other.clone());
}
if self.input_index >= self.other_inputs.len() {
inputs.push(TxInput {
txid: self.source_txid,
output_index: self.source_output_index,
script: self.unlocking_script.to_binary(),
sequence: self.input_sequence,
});
}
inputs
}
fn push_stack(&mut self, item: Vec<u8>) -> Result<(), ScriptEvaluationError> {
self.reserve_stack(item.len())?;
self.stack_mem += item.len();
self.stack.push(item);
Ok(())
}
fn push_stack_copy(&mut self, item: &[u8]) -> Result<(), ScriptEvaluationError> {
self.push_stack(item.to_vec())
}
fn pop_stack(&mut self) -> Result<Vec<u8>, ScriptEvaluationError> {
if self.stack.is_empty() {
return Err(self.error("Attempted to pop from an empty stack."));
}
let item = self.stack.pop().unwrap();
self.stack_mem -= item.len();
self.release(item.len());
Ok(item)
}
fn stack_top(&self) -> Result<&Vec<u8>, ScriptEvaluationError> {
if self.stack.is_empty() {
return Err(self.error("Stack is empty."));
}
Ok(&self.stack[self.stack.len() - 1])
}
fn stack_top_n(&self, n: usize) -> Result<&Vec<u8>, ScriptEvaluationError> {
if self.stack.len() < n {
return Err(self.error(&format!(
"Stack underflow accessing element at index {}. Stack length is {}.",
n,
self.stack.len()
)));
}
Ok(&self.stack[self.stack.len() - n])
}
fn push_alt_stack(&mut self, item: Vec<u8>) -> Result<(), ScriptEvaluationError> {
self.reserve_alt_stack(item.len())?;
self.alt_stack_mem += item.len();
self.alt_stack.push(item);
Ok(())
}
fn pop_alt_stack(&mut self) -> Result<Vec<u8>, ScriptEvaluationError> {
if self.alt_stack.is_empty() {
return Err(self.error("Attempted to pop from an empty alt stack."));
}
let item = self.alt_stack.pop().unwrap();
self.alt_stack_mem -= item.len();
self.release(item.len());
Ok(item)
}
fn reserve_stack(&mut self, len: usize) -> Result<(), ScriptEvaluationError> {
if self.flags.is_some() {
self.charge(len)
} else {
self.ensure_stack_mem(len)
}
}
fn reserve_alt_stack(&mut self, len: usize) -> Result<(), ScriptEvaluationError> {
if self.flags.is_some() {
self.charge(len)
} else {
self.ensure_alt_stack_mem(len)
}
}
fn charge(&mut self, len: usize) -> Result<(), ScriptEvaluationError> {
let attempted = self
.node_mem
.saturating_add(len as u64 + STACK_ELEMENT_OVERHEAD as u64);
self.check_node_budget(attempted)?;
self.node_mem = attempted;
Ok(())
}
fn release(&mut self, len: usize) {
if self.flags.is_some() {
self.node_mem = self
.node_mem
.saturating_sub(len as u64 + STACK_ELEMENT_OVERHEAD as u64);
}
}
fn check_node_budget(&self, attempted: u64) -> Result<(), ScriptEvaluationError> {
if let Some(policy) = self.stack_memory_policy() {
if attempted > policy as u64 {
return Err(self.error(&format!(
"Stack size limit exceeded: {attempted} bytes, the stack memory policy is {policy} bytes."
)));
}
}
if attempted > self.memory_limit() as u64 {
return Err(self.resource_error(
ScriptResource::Stack,
usize::try_from(attempted).unwrap_or(usize::MAX),
));
}
Ok(())
}
fn ensure_stack_mem(&self, additional: usize) -> Result<(), ScriptEvaluationError> {
if self.stack_mem + additional > self.memory_limit() {
return Err(self.resource_error(ScriptResource::Stack, self.stack_mem + additional));
}
Ok(())
}
fn ensure_alt_stack_mem(&self, additional: usize) -> Result<(), ScriptEvaluationError> {
if self.alt_stack_mem + additional > self.memory_limit() {
return Err(
self.resource_error(ScriptResource::AltStack, self.alt_stack_mem + additional)
);
}
Ok(())
}
fn resource_error(&self, resource: ScriptResource, attempted: usize) -> ScriptEvaluationError {
let label = match resource {
ScriptResource::Stack => "Stack memory usage",
ScriptResource::AltStack => "Alt stack memory usage",
ScriptResource::ElementSize => "Script element allocation",
};
let limit = self.memory_limit();
self.error(&format!("{label} has exceeded {limit} bytes"))
.with_resource_limit(ScriptResourceLimit {
resource,
limit,
attempted,
})
}
fn read_number_within(
&self,
bytes: &[u8],
limit: Option<usize>,
) -> Result<BigNumber, ScriptEvaluationError> {
if let Some(max) = limit {
if bytes.len() > max {
return Err(self.overflow_error(bytes.len(), max));
}
}
ScriptNum::from_bytes(bytes, self.require_minimal)
.map_err(|e| self.error(&format!("Invalid script number: {}", e)))
}
fn read_number(&self, bytes: &[u8]) -> Result<BigNumber, ScriptEvaluationError> {
self.read_number_within(bytes, self.max_script_num_length())
}
fn read_count(&self, bytes: &[u8]) -> Result<BigNumber, ScriptEvaluationError> {
self.read_number_within(bytes, self.flags.map(|_| 4))
}
fn push_number(&mut self, value: &BigNumber) -> Result<(), ScriptEvaluationError> {
let bytes = ScriptNum::to_bytes(value);
if let Some(max) = self.max_script_num_length() {
if bytes.len() > max {
return Err(self.overflow_error(bytes.len(), max));
}
}
self.push_stack(bytes)
}
fn overflow_error(&self, len: usize, max: usize) -> ScriptEvaluationError {
self.error(&format!(
"Script number overflow: {len} bytes, the limit is {max} bytes."
))
}
fn error(&self, message: &str) -> ScriptEvaluationError {
ScriptEvaluationError::new(
message,
to_hex(&self.source_txid),
self.source_output_index,
self.context,
self.program_counter,
self.stack.clone(),
self.alt_stack.clone(),
self.if_stack.clone(),
self.stack_mem,
self.alt_stack_mem,
)
}
}
fn is_opcode_disabled(op: u8, utxo_after_chronicle: bool) -> bool {
!utxo_after_chronicle && matches!(op, OP_2MUL | OP_2DIV)
}
fn signature_overflows_the_order(sig: &TransactionSignature) -> bool {
let n = BigNumber::secp256k1_order();
BigNumber::from_bytes_be(sig.r()) >= n || BigNumber::from_bytes_be(sig.s()) >= n
}
fn has_forkid_bit(sig: &[u8]) -> bool {
sig.last().is_some_and(|t| t & (SIGHASH_FORKID as u8) != 0)
}
fn is_chunk_minimal_push(chunk: &ScriptChunk) -> bool {
let data = match &chunk.data {
Some(d) => d,
None => return true,
};
let op = chunk.op;
if data.is_empty() {
return op == OP_0;
}
if data.len() == 1 && data[0] >= 1 && data[0] <= 16 {
return op == OP_1 + (data[0] - 1);
}
if data.len() == 1 && data[0] == 0x81 {
return op == OP_1NEGATE;
}
if data.len() <= 75 {
return op as usize == data.len();
}
if data.len() <= 255 {
return op == OP_PUSHDATA1;
}
if data.len() <= 65535 {
return op == OP_PUSHDATA2;
}
true
}
fn is_valid_signature_encoding(sig: &[u8]) -> bool {
if sig.len() < 9 || sig.len() > 73 {
return false;
}
if sig[0] != 0x30 {
return false;
}
if sig[1] as usize != sig.len() - 3 {
return false;
}
if sig[2] != 0x02 {
return false;
}
let r_len = sig[3] as usize;
if r_len == 0 || 5 + r_len >= sig.len() {
return false;
}
let s_offset = 4 + r_len;
if sig[s_offset] != 0x02 {
return false;
}
let s_len = sig[s_offset + 1] as usize;
if s_len == 0 {
return false;
}
if r_len + s_len + 7 != sig.len() {
return false;
}
if (sig[4] & 0x80) != 0 {
return false;
}
if r_len > 1 && sig[4] == 0x00 && (sig[5] & 0x80) == 0 {
return false;
}
let s_value_offset = s_offset + 2;
if (sig[s_value_offset] & 0x80) != 0 {
return false;
}
if s_len > 1 && sig[s_value_offset] == 0x00 && (sig[s_value_offset + 1] & 0x80) == 0 {
return false;
}
true
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_is_opcode_disabled() {
assert!(is_opcode_disabled(OP_2MUL, false));
assert!(is_opcode_disabled(OP_2DIV, false));
assert!(!is_opcode_disabled(OP_2MUL, true));
assert!(!is_opcode_disabled(OP_2DIV, true));
assert!(!is_opcode_disabled(OP_VER, false));
assert!(!is_opcode_disabled(OP_VERIF, false));
assert!(!is_opcode_disabled(OP_VERNOTIF, false));
assert!(!is_opcode_disabled(OP_DUP, false));
assert!(!is_opcode_disabled(OP_MUL, false));
assert!(!is_opcode_disabled(OP_CAT, false));
}
#[test]
fn test_is_chunk_minimal_push() {
let chunk = ScriptChunk::new(OP_0, Some(vec![]));
assert!(is_chunk_minimal_push(&chunk));
let chunk = ScriptChunk::new(3, Some(vec![1, 2, 3]));
assert!(is_chunk_minimal_push(&chunk));
let chunk = ScriptChunk::new(OP_1, Some(vec![1]));
assert!(is_chunk_minimal_push(&chunk));
let chunk = ScriptChunk::new(1, Some(vec![1]));
assert!(!is_chunk_minimal_push(&chunk));
}
#[test]
fn test_simple_stack_script() {
let locking = LockingScript::from_asm("OP_ADD OP_3 OP_EQUAL").unwrap();
let unlocking = UnlockingScript::from_asm("OP_1 OP_2").unwrap();
let mut spend = Spend::new(SpendParams {
source_txid: [0u8; 32],
source_output_index: 0,
source_satoshis: 0,
locking_script: locking,
transaction_version: 1,
other_inputs: vec![],
outputs: vec![],
input_index: 0,
unlocking_script: unlocking,
input_sequence: 0xffffffff,
lock_time: 0,
memory_limit: None,
});
let result = spend.validate();
assert!(result.is_ok(), "Expected valid spend, got {:?}", result);
}
#[test]
fn test_if_else_endif() {
let locking = LockingScript::from_asm("OP_IF OP_2 OP_ELSE OP_3 OP_ENDIF").unwrap();
let unlocking = UnlockingScript::from_asm("OP_1").unwrap();
let mut spend = Spend::new(SpendParams {
source_txid: [0u8; 32],
source_output_index: 0,
source_satoshis: 0,
locking_script: locking,
transaction_version: 1,
other_inputs: vec![],
outputs: vec![],
input_index: 0,
unlocking_script: unlocking,
input_sequence: 0xffffffff,
lock_time: 0,
memory_limit: None,
});
let result = spend.validate();
assert!(result.is_ok(), "Expected valid spend, got {:?}", result);
}
#[test]
fn test_hash_operations() {
let locking = LockingScript::from_asm("OP_SHA256 OP_SIZE OP_NIP 20 OP_EQUAL").unwrap();
let unlocking = UnlockingScript::from_asm("00").unwrap();
let mut spend = Spend::new(SpendParams {
source_txid: [0u8; 32],
source_output_index: 0,
source_satoshis: 0,
locking_script: locking,
transaction_version: 1,
other_inputs: vec![],
outputs: vec![],
input_index: 0,
unlocking_script: unlocking,
input_sequence: 0xffffffff,
lock_time: 0,
memory_limit: None,
});
let result = spend.validate();
assert!(result.is_ok(), "Expected valid spend, got {:?}", result);
}
#[test]
fn test_failing_script() {
let locking = LockingScript::from_asm("OP_0").unwrap();
let unlocking = UnlockingScript::new();
let mut spend = Spend::new(SpendParams {
source_txid: [0u8; 32],
source_output_index: 0,
source_satoshis: 0,
locking_script: locking,
transaction_version: 1,
other_inputs: vec![],
outputs: vec![],
input_index: 0,
unlocking_script: unlocking,
input_sequence: 0xffffffff,
lock_time: 0,
memory_limit: None,
});
let result = spend.validate();
assert!(result.is_err(), "Expected failed validation");
}
}
#[cfg(test)]
mod flag_tests {
use super::*;
use crate::primitives::from_hex;
use crate::script::flags::ProtocolEra;
const PUBKEY: &str = "035935f55855afd8c999bdb5a8d08ae8e73b7618e200d4ef7687cd55d3c2e4c9d7";
const WRONG_SIG: &str = "304402204bbb723c10080132ef81641e0e9963eb77782bf50c149f0f15c6d7b8e263464e02207f18ea8fdff74fb4d4d2dc677555fb1c94cf6219fe4bb4c40162e1e270f8924941";
fn spend(lock_asm: &str, unlock_asm: &str, version: i32) -> Spend {
Spend::new(SpendParams {
source_txid: [0u8; 32],
source_output_index: 0,
source_satoshis: 1000,
locking_script: LockingScript::from_asm(lock_asm).unwrap(),
transaction_version: version,
other_inputs: vec![],
outputs: vec![],
input_index: 0,
unlocking_script: UnlockingScript::from_asm(unlock_asm).unwrap(),
input_sequence: 0xffff_ffff,
lock_time: 0,
memory_limit: None,
})
}
fn with_flags(lock_asm: &str, unlock_asm: &str, version: i32, flags: ScriptFlags) -> Spend {
let mut s = spend(lock_asm, unlock_asm, version);
s.set_flags(flags);
s
}
fn message(r: Result<bool, ScriptEvaluationError>) -> String {
r.expect_err("expected a refusal").message
}
fn valid(r: Result<bool, ScriptEvaluationError>) -> bool {
matches!(r, Ok(true))
}
fn block() -> ScriptFlags {
ScriptFlags::block(ProtocolEra::PostChronicle)
}
fn standard() -> ScriptFlags {
ScriptFlags::standard(ProtocolEra::PostChronicle)
}
#[test]
fn nullfail_refuses_a_failing_non_empty_checksig_signature_at_version_1_under_the_block_word() {
let lock = format!("{PUBKEY} OP_CHECKSIG OP_NOT");
let msg = message(with_flags(&lock, WRONG_SIG, 1, block()).validate());
assert_eq!(msg, "OP_CHECKSIG requires failing signatures to be empty.");
assert!(valid(with_flags(&lock, WRONG_SIG, 2, block()).validate()));
assert!(with_flags(
&lock,
WRONG_SIG,
2,
ScriptFlags::block(ProtocolEra::PostGenesis)
)
.validate()
.is_err());
assert!(valid(with_flags(&lock, "0", 1, block()).validate()));
assert!(valid(spend(&lock, WRONG_SIG, 1).validate()));
}
#[test]
fn nullfail_refuses_a_failing_checkmultisig_with_any_non_empty_signature_at_version_1() {
let lock = format!("OP_1 {PUBKEY} OP_1 OP_CHECKMULTISIG OP_NOT");
let msg = message(with_flags(&lock, &format!("0 {WRONG_SIG}"), 1, block()).validate());
assert_eq!(
msg,
"OP_CHECKMULTISIG requires failing signatures to be empty."
);
assert!(valid(with_flags(&lock, "0 0", 1, block()).validate()));
assert!(valid(
with_flags(&lock, &format!("0 {WRONG_SIG}"), 2, block()).validate()
));
assert!(valid(spend(&lock, &format!("0 {WRONG_SIG}"), 1).validate()));
}
#[test]
fn nulldummy_is_a_standard_only_rule_gated_on_the_version() {
let lock = "OP_0 OP_0 OP_CHECKMULTISIG";
assert!(valid(with_flags(lock, "OP_1", 1, block()).validate()));
let msg = message(with_flags(lock, "OP_1", 1, standard()).validate());
assert_eq!(
msg,
"OP_CHECKMULTISIG requires the extra stack item (dummy) to be empty."
);
assert!(valid(with_flags(lock, "OP_1", 2, standard()).validate()));
assert!(with_flags(
lock,
"OP_1",
2,
ScriptFlags::standard(ProtocolEra::PostGenesis)
)
.validate()
.is_err());
assert!(spend(lock, "OP_1", 1).validate().is_err());
assert!(valid(spend(lock, "OP_1", 2).validate()));
assert!(valid(with_flags(lock, "0", 1, standard()).validate()));
}
#[test]
fn minimaldata_low_s_and_cleanstack_follow_the_word_and_the_gate() {
let non_minimal = UnlockingScript::from_binary(&[0x01, 0x01]).unwrap();
let mut s = spend("OP_1 OP_EQUAL", "", 1);
s = Spend::new(SpendParams {
unlocking_script: non_minimal.clone(),
..params_of(&s)
});
assert!(
s.validate().is_err(),
"the default mode enforces MINIMALDATA at version 1"
);
let mut s = Spend::new(SpendParams {
unlocking_script: non_minimal.clone(),
..params_of(&spend("OP_1 OP_EQUAL", "", 1))
});
s.set_flags(block());
assert!(
valid(s.validate()),
"the block word never carries MINIMALDATA"
);
let mut s = Spend::new(SpendParams {
unlocking_script: non_minimal,
..params_of(&spend("OP_1 OP_EQUAL", "", 1))
});
s.set_flags(standard());
assert!(
s.validate().is_err(),
"the standard word carries it, and version 1 is gated on"
);
assert!(valid(with_flags("OP_1 OP_1", "", 1, block()).validate()));
assert!(with_flags("OP_1 OP_1", "", 1, standard())
.validate()
.is_err());
assert!(valid(with_flags("OP_1 OP_1", "", 2, standard()).validate()));
}
fn params_of(s: &Spend) -> SpendParams {
SpendParams {
source_txid: s.source_txid,
source_output_index: s.source_output_index,
source_satoshis: s.source_satoshis,
locking_script: s.locking_script.clone(),
transaction_version: s.transaction_version,
other_inputs: s.other_inputs.clone(),
outputs: s.outputs.clone(),
input_index: s.input_index,
unlocking_script: s.unlocking_script.clone(),
input_sequence: s.input_sequence,
lock_time: s.lock_time,
memory_limit: s.memory_limit,
}
}
#[test]
fn push_only_is_derived_from_the_word_and_the_version() {
let lock = "OP_1 OP_EQUAL";
let unlock = "OP_0 OP_1ADD";
assert!(
spend(lock, unlock, 2).validate().is_err(),
"the default mode requires push-only at every version"
);
assert!(
valid(with_flags(lock, unlock, 2, block()).validate()),
"post-Chronicle, version 2: not required"
);
assert!(
with_flags(lock, unlock, 1, block()).validate().is_err(),
"post-Chronicle, version 1: required"
);
assert!(
with_flags(
lock,
unlock,
2,
ScriptFlags::block(ProtocolEra::PostGenesis)
)
.validate()
.is_err(),
"pre-Chronicle: required at every version"
);
let mut s = with_flags(lock, unlock, 1, block());
s.set_require_push_only(false);
assert!(valid(s.validate()));
}
#[test]
fn discourage_upgradable_nops_refuses_an_executed_nop1_to_nop10_under_the_standard_word_only() {
for (nop, era) in [
("OP_NOP1", ProtocolEra::PostChronicle),
("OP_NOP2", ProtocolEra::PostChronicle),
("OP_NOP3", ProtocolEra::PostChronicle),
("OP_NOP4", ProtocolEra::PostGenesis),
("OP_NOP8", ProtocolEra::PostGenesis),
("OP_NOP9", ProtocolEra::PostChronicle),
("OP_NOP10", ProtocolEra::PostChronicle),
] {
let lock = format!("{nop} OP_1");
assert!(
valid(with_flags(&lock, "", 2, ScriptFlags::block(era)).validate()),
"{nop}: a NOP in a block"
);
let msg = message(with_flags(&lock, "", 2, ScriptFlags::standard(era)).validate());
assert_eq!(msg, format!("{nop} is discouraged by verification flags."));
assert!(
valid(spend(&lock, "", 1).validate()),
"{nop}: the default mode has no such rule"
);
}
assert!(valid(
with_flags("OP_0 OP_IF OP_NOP1 OP_ENDIF OP_1", "", 2, standard()).validate()
));
assert!(valid(
with_flags("OP_NOP OP_1", "", 2, standard()).validate()
));
}
#[test]
fn minimalif_requires_an_empty_or_0x01_argument_under_the_flag_and_the_gate() {
let word = block() | ScriptFlags::MINIMALIF;
let lock = "OP_IF OP_1 OP_ELSE OP_0 OP_ENDIF";
assert!(valid(with_flags(lock, "OP_1", 1, word).validate()));
let msg = message(with_flags(lock, "OP_2", 1, word).validate());
assert_eq!(msg, "OP_IF and OP_NOTIF require minimal truth values.");
assert!(
valid(with_flags(lock, "OP_2", 2, word).validate()),
"version 2 post-Chronicle: gated off"
);
assert!(
valid(with_flags(lock, "OP_2", 1, block()).validate()),
"not in the block word"
);
assert!(
valid(spend(lock, "OP_2", 1).validate()),
"not in the default mode"
);
}
#[test]
fn compressed_pubkeytype_refuses_an_uncompressed_key_under_the_flag() {
let g = "0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8";
let lock = format!("{g} OP_CHECKSIG OP_NOT");
assert!(valid(with_flags(&lock, "0", 1, block()).validate()));
let msg = message(
with_flags(&lock, "0", 1, block() | ScriptFlags::COMPRESSED_PUBKEYTYPE).validate(),
);
assert_eq!(msg, "The public key must be compressed.");
let compressed = format!("{PUBKEY} OP_CHECKSIG OP_NOT");
assert!(valid(
with_flags(
&compressed,
"0",
1,
block() | ScriptFlags::COMPRESSED_PUBKEYTYPE
)
.validate()
));
}
#[test]
fn a_word_the_interpreter_cannot_honor_is_refused_by_validate_as_invalid_flags() {
let word = block().without(ScriptFlags::SIGHASH_FORKID);
let msg = message(with_flags("OP_1", "", 1, word).validate());
assert!(
msg.starts_with("Invalid verification flags: SIGHASH_FORKID is not set"),
"{msg}"
);
let word = (standard() | ScriptFlags::CLEANSTACK).without(ScriptFlags::P2SH);
let msg = message(with_flags("OP_1", "", 1, word).validate());
assert!(msg.contains("CLEANSTACK without P2SH"), "{msg}");
let s = with_flags("OP_1", "", 1, block());
assert_eq!(s.flags(), Some(block()));
assert_eq!(spend("OP_1", "", 1).flags(), None);
}
#[test]
fn set_flags_replaces_the_default_switches_and_the_setters_override_afterwards() {
let non_minimal = UnlockingScript::from_binary(&[0x01, 0x01]).unwrap();
let mut s = Spend::new(SpendParams {
unlocking_script: non_minimal,
..params_of(&spend("OP_1 OP_EQUAL", "", 1))
});
s.set_flags(block());
s.set_require_minimal(true);
assert!(s.validate().is_err());
let _ = from_hex; }
}
#[cfg(test)]
mod chronicle_tests {
use super::*;
use crate::script::flags::ProtocolEra;
fn spend(lock_asm: &str, unlock_asm: &str, version: i32) -> Spend {
Spend::new(SpendParams {
source_txid: [0u8; 32],
source_output_index: 0,
source_satoshis: 1000,
locking_script: LockingScript::from_asm(lock_asm).unwrap(),
transaction_version: version,
other_inputs: vec![],
outputs: vec![],
input_index: 0,
unlocking_script: UnlockingScript::from_asm(unlock_asm).unwrap(),
input_sequence: 0xffff_ffff,
lock_time: 0,
memory_limit: None,
})
}
fn valid(r: Result<bool, ScriptEvaluationError>) -> bool {
matches!(r, Ok(true))
}
fn message(r: Result<bool, ScriptEvaluationError>) -> String {
r.expect_err("expected a refusal").message
}
#[test]
fn substr_left_and_right_take_ranges_after_chronicle_and_refuse_out_of_range_ones() {
assert!(valid(
spend("OP_1 OP_2 OP_NOP4 bbcc OP_EQUAL", "aabbccdd", 2).validate()
));
assert!(valid(
spend("OP_2 OP_NOP5 aabb OP_EQUAL", "aabbccdd", 2).validate()
));
assert!(valid(
spend("OP_2 OP_NOP6 ccdd OP_EQUAL", "aabbccdd", 2).validate()
));
assert!(valid(
spend("OP_0 OP_NOP5 OP_0 OP_EQUAL", "aabbccdd", 2).validate()
));
assert_eq!(
message(spend("OP_5 OP_NOP5", "aabbccdd", 2).validate()),
"OP_LEFT length (5) must be in range [0, 4]"
);
assert_eq!(
message(spend("OP_4 OP_0 OP_NOP4", "aabbccdd", 2).validate()),
"OP_SUBSTR offset (4) must be in range [0, 4) and length (0) must be in range [0, 0]"
);
assert!(
message(spend("OP_1 OP_4 OP_NOP4", "aabbccdd", 2).validate())
.starts_with("OP_SUBSTR offset (1)")
);
assert!(
message(spend("OP_1NEGATE OP_NOP6", "aabbccdd", 2).validate())
.contains("OP_RIGHT length (-1)")
);
}
#[test]
fn lshiftnum_and_rshiftnum_shift_script_numbers_toward_zero() {
assert!(valid(
spend("OP_1 OP_NOP7 OP_14 OP_EQUAL", "OP_7", 2).validate()
));
assert!(valid(
spend("OP_10 OP_NOP7 0004 OP_EQUAL", "OP_1", 2).validate()
)); assert!(valid(spend("OP_1 OP_NOP8 83 OP_EQUAL", "87", 2).validate()));
assert!(valid(
spend("OP_1 OP_NOP8 OP_3 OP_EQUAL", "OP_7", 2).validate()
));
assert!(valid(
spend("OP_16 OP_NOP8 OP_0 OP_EQUAL", "OP_7", 2).validate()
));
assert!(valid(
spend("OP_16 OP_NOP8 OP_0 OP_EQUAL", "87", 2).validate()
));
assert!(valid(
spend("OP_16 OP_NOP7 OP_0 OP_EQUAL", "OP_0", 2).validate()
));
assert_eq!(
message(spend("OP_1NEGATE OP_NOP7", "OP_7", 2).validate()),
"OP_LSHIFTNUM bits to shift must not be negative."
);
assert_eq!(
message(spend("OP_1NEGATE OP_NOP8", "OP_7", 2).validate()),
"OP_RSHIFTNUM bits to shift must not be negative."
);
}
#[test]
fn lshiftnum_refuses_a_result_beyond_the_memory_budget_before_computing_it() {
let mut s = Spend::new(SpendParams {
source_txid: [0u8; 32],
source_output_index: 0,
source_satoshis: 1000,
locking_script: LockingScript::from_asm("2823 OP_NOP7").unwrap(), transaction_version: 2,
other_inputs: vec![],
outputs: vec![],
input_index: 0,
unlocking_script: UnlockingScript::from_asm("OP_1").unwrap(),
input_sequence: 0xffff_ffff,
lock_time: 0,
memory_limit: Some(1000),
});
let err = s.validate().unwrap_err();
assert!(err.is_resource_limit(), "{}", err.message);
assert_eq!(
err.resource_limit.unwrap().resource,
ScriptResource::ElementSize
);
}
#[test]
fn before_chronicle_0xb3_to_0xb7_are_nops_and_discouraged_under_the_standard_word() {
assert!(valid(
spend("OP_NOP4 OP_NOP5 OP_NOP6 OP_NOP7 OP_NOP8", "OP_1", 1).validate()
));
let mut s = spend("OP_NOP7 OP_1", "", 2);
s.set_flags(ScriptFlags::standard(ProtocolEra::PostGenesis));
assert_eq!(
message(s.validate()),
"OP_NOP7 is discouraged by verification flags."
);
let mut s = spend("OP_1 OP_NOP7 OP_14 OP_EQUAL", "OP_7", 1);
s.set_flags(ScriptFlags::block(ProtocolEra::PostChronicle));
assert!(valid(s.validate()));
let mut s = spend("OP_1 OP_NOP7 OP_14 OP_EQUAL", "OP_7", 1);
s.set_utxo_after_chronicle(true);
assert!(valid(s.validate()));
}
#[test]
fn op_ver_pushes_the_version_after_chronicle_and_is_refused_before() {
assert!(valid(spend("OP_VER 02000000 OP_EQUAL", "", 2).validate()));
assert_eq!(
message(spend("OP_VER", "", 1).validate()),
"OP_VER is disabled until Chronicle."
);
let mut s = spend("OP_VER 01000000 OP_EQUAL", "", 1);
s.set_flags(ScriptFlags::block(ProtocolEra::PostChronicle));
assert!(valid(s.validate()));
assert!(valid(
spend("OP_0 OP_IF OP_VER OP_ENDIF OP_1", "", 1).validate()
));
}
#[test]
fn op_verif_compares_the_top_with_the_version_and_is_skipped_or_refused_before_chronicle() {
assert!(valid(
spend("OP_VERIF OP_1 OP_ELSE OP_0 OP_ENDIF", "02000000", 2).validate()
));
assert!(!valid(
spend("OP_VERIF OP_1 OP_ELSE OP_0 OP_ENDIF", "01000000", 2).validate()
));
assert!(valid(
spend("OP_VERIF OP_0 OP_ELSE OP_1 OP_ENDIF", "OP_2", 2).validate()
));
assert!(valid(
spend("OP_VERNOTIF OP_1 OP_ELSE OP_0 OP_ENDIF", "01000000", 2).validate()
));
assert!(valid(
spend("OP_VERNOTIF OP_0 OP_ELSE OP_1 OP_ENDIF", "02000000", 2).validate()
));
assert_eq!(
message(spend("OP_VERIF OP_1 OP_ENDIF", "02000000", 1).validate()),
"OP_VERIF is disabled until Chronicle."
);
assert!(valid(
spend("OP_0 OP_IF OP_VERIF OP_ENDIF OP_1", "", 1).validate()
));
assert!(valid(
spend("OP_0 OP_IF OP_VERNOTIF OP_ENDIF OP_1", "", 1).validate()
));
}
#[test]
fn two_mul_and_two_div_are_disabled_before_chronicle_and_compute_after() {
assert!(message(spend("OP_2MUL", "OP_7", 1).validate()).contains("currently disabled"));
assert!(message(spend("OP_2DIV", "OP_7", 1).validate()).contains("currently disabled"));
assert!(valid(spend("OP_2MUL OP_14 OP_EQUAL", "OP_7", 2).validate()));
assert!(valid(spend("OP_2DIV OP_3 OP_EQUAL", "OP_7", 2).validate()));
assert!(valid(spend("OP_2DIV 83 OP_EQUAL", "87", 2).validate())); assert!(valid(spend("OP_2MUL 8e OP_EQUAL", "87", 2).validate())); assert!(valid(
spend("OP_0 OP_IF OP_2MUL OP_ENDIF OP_1", "", 1).validate()
));
}
#[test]
fn one_op_else_per_op_if_after_genesis() {
assert!(valid(
spend("OP_IF OP_1 OP_ELSE OP_0 OP_ENDIF", "OP_1", 2).validate()
));
assert_eq!(
message(spend("OP_IF OP_1 OP_ELSE OP_1 OP_ELSE OP_1 OP_ENDIF", "OP_1", 2).validate()),
"OP_ELSE may only be used once for each OP_IF or OP_NOTIF after Genesis."
);
assert!(valid(
spend(
"OP_IF OP_0 OP_IF OP_ELSE OP_ENDIF OP_ELSE OP_ENDIF OP_1",
"OP_1",
2
)
.validate()
));
assert!(
message(spend("OP_IF OP_ELSE OP_ELSE OP_ENDIF OP_1", "OP_1", 1).validate())
.contains("only be used once")
);
}
#[test]
fn a_return_inside_a_conditional_stops_execution_but_the_balance_and_the_parse_still_hold() {
assert!(valid(
spend("OP_1 OP_IF OP_RETURN OP_0 OP_ENDIF", "OP_1", 2).validate()
));
assert!(message(spend("OP_1 OP_IF OP_RETURN", "OP_1", 2).validate())
.contains("terminated with OP_ENDIF"));
let lock = LockingScript::from_binary(&[0x51, 0x63, 0x6a, 0x68, 0xba]).unwrap();
let mut s = spend("OP_1", "OP_1", 2);
s = Spend::new(SpendParams {
locking_script: lock,
..params_of(&s)
});
assert!(valid(s.validate()));
let lock = LockingScript::from_binary(&[0x51, 0x63, 0x6a, 0x68, 0x03, 0x01]).unwrap();
let mut s = Spend::new(SpendParams {
locking_script: lock,
..params_of(&spend("OP_1", "OP_1", 2))
});
assert!(message(s.validate()).starts_with("A push declares more bytes"));
let lock = LockingScript::from_binary(&[0x51, 0x6a, 0xba, 0x03, 0x01]).unwrap();
let mut s = Spend::new(SpendParams {
locking_script: lock,
..params_of(&spend("OP_1", "", 2))
});
assert!(valid(s.validate()));
}
#[test]
fn an_undefined_opcode_is_refused_only_when_executed() {
assert!(message(spend("OP_1 OP_NOP77", "", 2).validate()).starts_with("Invalid opcode 252"));
assert!(valid(
spend("OP_0 OP_IF OP_NOP77 OP_ENDIF OP_1", "", 2).validate()
));
let lock = LockingScript::from_binary(&[0x51, 0xba]).unwrap();
let mut s = Spend::new(SpendParams {
locking_script: lock,
..params_of(&spend("OP_1", "", 1))
});
assert!(message(s.validate()).starts_with("Invalid opcode 186"));
}
#[test]
fn a_truncated_push_is_refused_where_the_walk_reaches_it_even_unexecuted() {
let lock = LockingScript::from_binary(&[0x00, 0x63, 0x03, 0x01]).unwrap();
assert_eq!(lock.as_script().truncated_push(), Some(2));
let mut s = Spend::new(SpendParams {
locking_script: lock,
..params_of(&spend("OP_1", "", 2))
});
assert!(message(s.validate()).contains("(pc=2)"));
assert_eq!(
Script::from_binary(&[0x51, 0x03, 0x01, 0x02, 0x03])
.unwrap()
.truncated_push(),
None
);
assert_eq!(
Script::from_binary(&[0x6a, 0x03, 0x01])
.unwrap()
.truncated_push(),
None
);
assert_eq!(
Script::from_binary(&[0x4c]).unwrap().truncated_push(),
Some(0)
);
assert_eq!(
Script::from_binary(&[0x51, 0x4d, 0x01])
.unwrap()
.truncated_push(),
Some(1)
);
assert_eq!(
Script::from_binary(&[0x4e, 0x01, 0x00, 0x00, 0x00])
.unwrap()
.truncated_push(),
Some(0)
);
assert_eq!(
Script::from_binary(&[0x4e, 0x01, 0x00, 0x00, 0x00, 0xaa])
.unwrap()
.truncated_push(),
None
);
let lock = LockingScript::from_binary(&[0x69, 0x03, 0x01]).unwrap(); let mut s = Spend::new(SpendParams {
locking_script: lock,
..params_of(&spend("OP_1", "", 2))
});
assert!(message(s.validate()).contains("OP_VERIFY requires"));
}
#[test]
fn the_scriptcode_keeps_a_forkid_signatures_push_and_deletes_an_empty_ones_op_0() {
let sig = "304402204c9195e05dc41a9119b4cf65f43e450a057818d74c12265faee6a21ae2e0ab87022051c94bb55d9c54d68efaa16785c6ba6a7958757745528974a92d8cddcb993c1241";
let lock_asm = format!("{sig} OP_DROP OP_1");
let mut s = spend(&lock_asm, "", 2);
s.context = ExecutionContext::LockingScript;
let sig_bytes = crate::primitives::from_hex(sig).unwrap();
assert!(has_forkid_bit(&sig_bytes));
let sub = s.build_subscript(&sig_bytes).unwrap();
assert_eq!(
sub.to_binary(),
s.locking_script.to_binary(),
"a FORKID signature's push stays"
);
let mut s = spend("OP_0 OP_1 OP_0", "", 2);
s.context = ExecutionContext::LockingScript;
assert!(!has_forkid_bit(&[]));
let sub = s.build_subscript(&[]).unwrap();
assert_eq!(sub.to_asm(), "OP_1");
assert!(!has_forkid_bit(&[
0x30, 0x06, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01
]));
}
fn params_of(s: &Spend) -> SpendParams {
SpendParams {
source_txid: s.source_txid,
source_output_index: s.source_output_index,
source_satoshis: s.source_satoshis,
locking_script: s.locking_script.clone(),
transaction_version: s.transaction_version,
other_inputs: s.other_inputs.clone(),
outputs: s.outputs.clone(),
input_index: s.input_index,
unlocking_script: s.unlocking_script.clone(),
input_sequence: s.input_sequence,
lock_time: s.lock_time,
memory_limit: s.memory_limit,
}
}
}
#[cfg(test)]
mod low_s_order_tests {
use super::*;
const N: &str = "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141";
const HALF: &str = "7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0";
fn der(r: &str, s: &str) -> Vec<u8> {
fn int(hex: &str) -> Vec<u8> {
let mut v = crate::primitives::from_hex(hex).unwrap();
while v.len() > 1 && v[0] == 0 && v[1] & 0x80 == 0 {
v.remove(0);
}
if v[0] & 0x80 != 0 {
v.insert(0, 0);
}
let mut out = vec![0x02, v.len() as u8];
out.extend(v);
out
}
let body = [int(r), int(s)].concat();
let mut out = vec![0x30, body.len() as u8];
out.extend(body);
out.push(0x41);
out
}
fn checker() -> Spend {
Spend::new(SpendParams {
source_txid: [0u8; 32],
source_output_index: 0,
source_satoshis: 1000,
locking_script: LockingScript::from_asm("OP_1").unwrap(),
transaction_version: 1, other_inputs: vec![],
outputs: vec![],
input_index: 0,
unlocking_script: UnlockingScript::new(),
input_sequence: 0xffff_ffff,
lock_time: 0,
memory_limit: None,
})
}
fn s_plus(hex: &str, k: u64) -> String {
let v = BigNumber::from_bytes_be(&crate::primitives::from_hex(hex).unwrap())
.add(&BigNumber::from_i64(k as i64));
crate::primitives::to_hex(&v.to_bytes_be(32))
}
#[test]
fn s_at_or_above_the_order_passes_the_low_s_check_as_the_zero_signature() {
let one = "0000000000000000000000000000000000000000000000000000000000000001";
let c = checker();
assert!(c.check_signature_encoding(&der(one, N)).is_ok(), "s = n");
assert!(
c.check_signature_encoding(&der(one, &s_plus(N, 1))).is_ok(),
"s = n + 1"
);
assert!(c.check_signature_encoding(&der(N, one)).is_ok(), "r = n");
assert!(
c.check_signature_encoding(&der(one, HALF)).is_ok(),
"s = n/2"
);
let high = c
.check_signature_encoding(&der(one, &s_plus(HALF, 1)))
.unwrap_err();
assert_eq!(high.message, "The signature must have a low S value.");
let n_minus_1 = "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140";
assert_eq!(
c.check_signature_encoding(&der(one, n_minus_1))
.unwrap_err()
.message,
"The signature must have a low S value."
);
let pk = "035935f55855afd8c999bdb5a8d08ae8e73b7618e200d4ef7687cd55d3c2e4c9d7";
let sig_hex = crate::primitives::to_hex(&der(one, N));
let mut spend = Spend::new(SpendParams {
source_txid: [0u8; 32],
source_output_index: 0,
source_satoshis: 1000,
locking_script: LockingScript::from_asm(&format!("{pk} OP_CHECKSIG")).unwrap(),
transaction_version: 1,
other_inputs: vec![],
outputs: vec![],
input_index: 0,
unlocking_script: UnlockingScript::from_asm(&sig_hex).unwrap(),
input_sequence: 0xffff_ffff,
lock_time: 0,
memory_limit: None,
});
spend.set_flags(ScriptFlags::block(
crate::script::flags::ProtocolEra::PostChronicle,
));
assert_eq!(
spend.validate().unwrap_err().message,
"OP_CHECKSIG requires failing signatures to be empty."
);
}
}
#[cfg(test)]
mod script_num_length_tests {
use super::*;
use crate::primitives::to_hex;
use crate::script::flags::ProtocolEra;
const PUBKEY: &str = "035935f55855afd8c999bdb5a8d08ae8e73b7618e200d4ef7687cd55d3c2e4c9d7";
fn push_num(lock: &mut Vec<u8>, n: u64) {
let bytes = ScriptNum::to_bytes(&BigNumber::from_u64(n));
lock.push(bytes.len() as u8);
lock.extend_from_slice(&bytes);
}
fn read_n_bytes(n: u64) -> Vec<u8> {
let mut lock = vec![OP_1];
push_num(&mut lock, n);
lock.extend_from_slice(&[OP_NUM2BIN, OP_1ADD, OP_DROP, OP_1]);
lock
}
fn square_of_n_plus_one_bytes(n: u64) -> Vec<u8> {
let mut lock = vec![OP_1];
push_num(&mut lock, n);
lock.extend_from_slice(&[
OP_NUM2BIN, 0x01, 0x40, OP_CAT, OP_DUP, OP_MUL, OP_DROP, OP_1,
]);
lock
}
fn bin2num_of_n_plus_one_bytes(n: u64) -> Vec<u8> {
let mut lock = vec![OP_1];
push_num(&mut lock, n);
lock.extend_from_slice(&[OP_NUM2BIN, 0x01, 0x40, OP_CAT, OP_BIN2NUM, OP_DROP, OP_1]);
lock
}
fn checkmultisig_with_a_count_of(len: u8) -> Vec<u8> {
let mut lock = vec![OP_0, OP_0, 0x21];
lock.extend_from_slice(&crate::primitives::from_hex(PUBKEY).unwrap());
lock.push(len);
lock.push(0x01);
lock.extend_from_slice(&vec![0u8; len as usize - 1]);
lock.push(OP_CHECKMULTISIG);
lock
}
fn spend(lock: &[u8], version: i32) -> Spend {
Spend::new(SpendParams {
source_txid: [0u8; 32],
source_output_index: 0,
source_satoshis: 1000,
locking_script: LockingScript::from_hex(&to_hex(lock)).unwrap(),
transaction_version: version,
other_inputs: vec![],
outputs: vec![],
input_index: 0,
unlocking_script: UnlockingScript::from_binary(&[]).unwrap(),
input_sequence: 0xffff_ffff,
lock_time: 0,
memory_limit: Some(200_000_000),
})
}
fn block() -> ScriptFlags {
ScriptFlags::block(ProtocolEra::PostChronicle)
}
fn standard() -> ScriptFlags {
ScriptFlags::standard(ProtocolEra::PostChronicle)
}
fn run(
lock: &[u8],
version: i32,
word: Option<ScriptFlags>,
utxo_after_chronicle: Option<bool>,
policy: Option<usize>,
) -> String {
let mut s = spend(lock, version);
if let Some(w) = word {
s.set_flags(w);
}
if let Some(after) = utxo_after_chronicle {
s.set_utxo_after_chronicle(after);
}
if let Some(bytes) = policy {
s.set_script_num_length_policy(bytes);
}
match s.validate() {
Ok(v) => format!("Ok({v})"),
Err(e) => e.message,
}
}
fn overflow(len: usize, max: usize) -> String {
format!("Script number overflow: {len} bytes, the limit is {max} bytes.")
}
#[test]
fn the_word_and_the_coins_era_decide_the_limit() {
assert!(!block().is_mempool_word());
assert!(standard().is_mempool_word());
assert_eq!(block().max_script_num_length(false, 10_000), 750_000);
assert_eq!(block().max_script_num_length(true, 10_000), 32_000_000);
assert_eq!(standard().max_script_num_length(false, 10_000), 10_000);
assert_eq!(standard().max_script_num_length(true, 10_000), 10_000);
assert_eq!(standard().max_script_num_length(false, 0), 750_000);
assert_eq!(standard().max_script_num_length(true, 0), 32_000_000);
let lock = read_n_bytes(1);
assert_eq!(spend(&lock, 2).max_script_num_length(), None);
let mut s = spend(&lock, 2);
s.set_flags(block());
assert_eq!(s.max_script_num_length(), Some(32_000_000));
s.set_utxo_after_chronicle(false);
assert_eq!(s.max_script_num_length(), Some(750_000));
s.set_flags(standard());
assert_eq!(s.max_script_num_length(), Some(10_000));
s.set_script_num_length_policy(0);
assert_eq!(s.max_script_num_length(), Some(32_000_000));
}
#[test]
fn a_coin_created_after_genesis_reads_750_000_bytes_and_refuses_750_001_under_the_block_word() {
assert_eq!(
run(&read_n_bytes(750_000), 2, Some(block()), Some(false), None),
"Ok(true)"
);
assert_eq!(
run(&read_n_bytes(750_001), 2, Some(block()), Some(false), None),
overflow(750_001, 750_000)
);
}
#[test]
fn a_coin_created_after_chronicle_reads_32_000_000_bytes_and_refuses_32_000_001() {
assert_eq!(
run(
&read_n_bytes(32_000_000),
2,
Some(block()),
Some(true),
None
),
"Ok(true)"
);
assert_eq!(
run(
&read_n_bytes(32_000_001),
2,
Some(block()),
Some(true),
None
),
overflow(32_000_001, 32_000_000)
);
}
#[test]
fn the_standard_word_applies_the_policy_default_in_both_eras_and_zero_selects_the_consensus_limit(
) {
for after in [false, true] {
assert_eq!(
run(
&read_n_bytes(10_000),
2,
Some(standard()),
Some(after),
None
),
"Ok(true)"
);
assert_eq!(
run(
&read_n_bytes(10_001),
2,
Some(standard()),
Some(after),
None
),
overflow(10_001, 10_000)
);
}
assert_eq!(
run(
&read_n_bytes(750_000),
2,
Some(standard()),
Some(false),
Some(0)
),
"Ok(true)"
);
assert_eq!(
run(
&read_n_bytes(750_001),
2,
Some(standard()),
Some(false),
Some(0)
),
overflow(750_001, 750_000)
);
assert_eq!(
run(
&read_n_bytes(20_000),
2,
Some(standard()),
Some(false),
Some(20_000)
),
"Ok(true)"
);
assert_eq!(
run(
&read_n_bytes(20_001),
2,
Some(standard()),
Some(false),
Some(20_000)
),
overflow(20_001, 20_000)
);
}
#[test]
fn the_length_test_precedes_the_minimal_encoding_test() {
assert_eq!(
run(
&read_n_bytes(10_000),
1,
Some(standard()),
Some(false),
None
),
"Invalid script number: script execution error: Non-minimally encoded script number"
);
assert_eq!(
run(
&read_n_bytes(10_001),
1,
Some(standard()),
Some(false),
None
),
overflow(10_001, 10_000)
);
}
#[test]
fn a_product_longer_than_the_limit_is_refused_before_it_is_pushed() {
assert_eq!(
run(
&square_of_n_plus_one_bytes(369_999),
2,
Some(block()),
Some(false),
None
),
"Ok(true)"
);
let refused = run(
&square_of_n_plus_one_bytes(379_999),
2,
Some(block()),
Some(false),
None,
);
assert!(
refused.starts_with("Script number overflow: 7")
&& refused.ends_with("the limit is 750000 bytes."),
"{refused}"
);
}
#[test]
fn bin2num_refuses_a_result_longer_than_the_limit() {
assert_eq!(
run(
&bin2num_of_n_plus_one_bytes(9_999),
2,
Some(standard()),
Some(false),
None
),
"Ok(true)"
);
assert_eq!(
run(
&bin2num_of_n_plus_one_bytes(10_000),
2,
Some(standard()),
Some(false),
None
),
"OP_BIN2NUM requires that the resulting number is valid."
);
}
#[test]
fn a_five_byte_checkmultisig_count_is_an_overflow_under_a_word() {
assert_eq!(
run(
&checkmultisig_with_a_count_of(4),
2,
Some(block()),
Some(false),
None
),
"Ok(true)"
);
assert_eq!(
run(
&checkmultisig_with_a_count_of(5),
2,
Some(block()),
Some(false),
None
),
overflow(5, 4)
);
assert_eq!(
run(&checkmultisig_with_a_count_of(5), 2, None, None, None),
"Ok(true)"
);
}
#[test]
fn the_default_mode_reads_a_number_of_any_length() {
assert_eq!(
run(&read_n_bytes(1_048_577), 2, None, None, None),
"Ok(true)"
);
}
#[test]
fn a_left_shifts_exact_length_is_its_serialized_length() {
for x in [
1i64,
2,
127,
128,
255,
256,
32_767,
32_768,
-1,
-127,
-128,
-255,
i64::MAX,
i64::MIN + 1,
] {
let x = BigNumber::from_i64(x);
for n in 0..40u64 {
let bits = x.bit_length() as u64 + n;
assert_eq!(
(bits / 8 + 1) as usize,
ScriptNum::to_bytes(&x.shl_bits(n)).len(),
"{} << {n}",
x.to_dec_string()
);
}
}
}
}