#![allow(
clippy::declare_interior_mutable_const, // const vs static for OnceLock in array init
clippy::type_complexity,
clippy::too_many_arguments,
clippy::needless_return, // Many branches; mechanical fix error-prone
)]
mod arithmetic;
mod context;
mod control_flow;
mod crypto_ops;
mod signature;
mod stack;
pub use signature::{batch_verify_signatures, verify_pre_extracted_ecdsa};
pub use stack::{cast_to_bool, to_stack_element, StackElement};
use crate::constants::*;
use crate::crypto::OptimizedSha256;
use crate::error::{ConsensusError, Result, ScriptErrorCode};
use crate::opcodes::*;
use crate::types::*;
use blvm_spec_lock::spec_locked;
use digest::Digest;
use ripemd::Ripemd160;
#[cfg(any(not(feature = "production"), test))]
use secp256k1::Secp256k1;
#[cfg(feature = "production")]
use crate::optimizations::{precomputed_constants, prefetch};
#[cold]
#[allow(dead_code)]
fn make_operation_limit_error() -> ConsensusError {
ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::OpCount,
message: "Operation limit exceeded".into(),
}
}
#[cold]
fn make_stack_overflow_error() -> ConsensusError {
ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::StackSize,
message: "Stack overflow".into(),
}
}
#[cfg(feature = "production")]
use std::collections::VecDeque;
#[cfg(feature = "production")]
use std::sync::{
atomic::{AtomicBool, AtomicU64, Ordering},
OnceLock, RwLock,
};
#[cfg(feature = "production")]
use std::thread_local;
#[cfg(feature = "production")]
static SCRIPT_CACHE: OnceLock<RwLock<lru::LruCache<u64, bool>>> = OnceLock::new();
#[cfg(feature = "production")]
const SIG_CACHE_SHARDS: usize = 32;
#[cfg(feature = "production")]
const SIG_CACHE_SHARD: OnceLock<RwLock<lru::LruCache<[u8; 32], bool>>> = OnceLock::new();
#[cfg(feature = "production")]
static SIG_CACHE: [OnceLock<RwLock<lru::LruCache<[u8; 32], bool>>>; SIG_CACHE_SHARDS] =
[SIG_CACHE_SHARD; SIG_CACHE_SHARDS];
#[cfg(feature = "production")]
fn sig_cache_size() -> usize {
std::env::var("BLVM_SIG_CACHE_ENTRIES")
.ok()
.and_then(|s| s.parse().ok())
.filter(|&n: &usize| n > 0 && n <= 1_000_000)
.unwrap_or(500_000)
}
#[cfg(feature = "production")]
fn sig_cache_shard_index(key: &[u8; 32]) -> usize {
let h = (key[0] as usize) | ((key[1] as usize) << 8) | ((key[2] as usize) << 16);
h % SIG_CACHE_SHARDS
}
#[cfg(feature = "production")]
fn get_sig_cache_shard(key: &[u8; 32]) -> &'static RwLock<lru::LruCache<[u8; 32], bool>> {
let idx = sig_cache_shard_index(key);
SIG_CACHE[idx].get_or_init(|| {
use lru::LruCache;
use std::num::NonZeroUsize;
let cap = (sig_cache_size() / SIG_CACHE_SHARDS).max(1);
RwLock::new(LruCache::new(NonZeroUsize::new(cap).unwrap()))
})
}
#[cfg(feature = "production")]
thread_local! {
static BATCH_PUT_SIG_CACHE_BY_SHARD: std::cell::RefCell<[Vec<([u8; 32], bool)>; SIG_CACHE_SHARDS]> =
std::cell::RefCell::new(std::array::from_fn(|_| Vec::new()));
}
#[cfg(feature = "production")]
thread_local! {
static SOA_BATCH_BUF: std::cell::RefCell<(
Vec<[u8; 64]>,
Vec<[u8; 32]>,
Vec<[u8; 33]>,
Vec<usize>,
Vec<[u8; 32]>,
)> = const { std::cell::RefCell::new((
Vec::new(),
Vec::new(),
Vec::new(),
Vec::new(),
Vec::new(),
)) };
}
#[cfg(feature = "production")]
fn batch_put_sig_cache(keys: &[[u8; 32]], results: &[bool]) {
BATCH_PUT_SIG_CACHE_BY_SHARD.with(|cell| {
let mut by_shard = cell.borrow_mut();
for v in by_shard.iter_mut() {
v.clear();
}
for (i, key) in keys.iter().enumerate() {
let result = results.get(i).copied().unwrap_or(false);
let idx = sig_cache_shard_index(key);
by_shard[idx].push((*key, result));
}
for shard_entries in by_shard.iter() {
if shard_entries.is_empty() {
continue;
}
let first_key = &shard_entries[0].0;
if let Ok(mut guard) = get_sig_cache_shard(first_key).write() {
for (k, v) in shard_entries.iter() {
guard.put(*k, *v);
}
}
}
});
}
#[cfg(feature = "production")]
fn sig_cache_at_collect_enabled() -> bool {
use std::sync::OnceLock;
static CACHE: OnceLock<bool> = OnceLock::new();
*CACHE.get_or_init(|| {
std::env::var("BLVM_SIG_CACHE_AT_COLLECT")
.map(|s| s == "1" || s.eq_ignore_ascii_case("true"))
.unwrap_or(false)
})
}
#[cfg(feature = "production")]
#[inline(always)]
fn ecdsa_cache_key(msg: &[u8; 32], pk: &[u8; 33], sig_compact: &[u8; 64], flags: u32) -> [u8; 32] {
use siphasher::sip::SipHasher24;
use std::hash::{Hash, Hasher};
let mut key_input = [0u8; 133];
key_input[..32].copy_from_slice(msg);
key_input[32..65].copy_from_slice(pk);
key_input[65..129].copy_from_slice(sig_compact);
key_input[129..133].copy_from_slice(&flags.to_le_bytes());
let mut hasher = SipHasher24::new();
key_input.hash(&mut hasher);
let h = hasher.finish();
let mut out = [0u8; 32];
out[..8].copy_from_slice(&h.to_le_bytes());
out
}
#[cfg(feature = "production")]
static FAST_PATH_P2PK: AtomicU64 = AtomicU64::new(0);
#[cfg(feature = "production")]
static FAST_PATH_P2PKH: AtomicU64 = AtomicU64::new(0);
#[cfg(feature = "production")]
static FAST_PATH_P2SH: AtomicU64 = AtomicU64::new(0);
#[cfg(feature = "production")]
static FAST_PATH_P2WPKH: AtomicU64 = AtomicU64::new(0);
#[cfg(feature = "production")]
static FAST_PATH_P2WSH: AtomicU64 = AtomicU64::new(0);
static FAST_PATH_P2TR: AtomicU64 = AtomicU64::new(0);
#[cfg(feature = "production")]
static FAST_PATH_BARE_MULTISIG: AtomicU64 = AtomicU64::new(0);
#[cfg(feature = "production")]
static FAST_PATH_INTERPRETER: AtomicU64 = AtomicU64::new(0);
#[cfg(feature = "production")]
fn get_script_cache() -> &'static RwLock<lru::LruCache<u64, bool>> {
SCRIPT_CACHE.get_or_init(|| {
use lru::LruCache;
use std::num::NonZeroUsize;
RwLock::new(LruCache::new(NonZeroUsize::new(100_000).unwrap()))
})
}
#[cfg(feature = "production")]
thread_local! {
static STACK_POOL: std::cell::RefCell<VecDeque<Vec<StackElement>>> =
std::cell::RefCell::new(VecDeque::with_capacity(10));
}
#[cfg(feature = "production")]
fn get_pooled_stack() -> Vec<StackElement> {
STACK_POOL.with(|pool| {
let mut pool = pool.borrow_mut();
if let Some(mut stack) = pool.pop_front() {
stack.clear();
if stack.capacity() < 20 {
stack.reserve(20);
}
stack
} else {
Vec::with_capacity(20)
}
})
}
#[cfg(feature = "production")]
struct PooledStackGuard(Vec<StackElement>);
#[cfg(feature = "production")]
impl Drop for PooledStackGuard {
fn drop(&mut self) {
return_pooled_stack(std::mem::take(&mut self.0));
}
}
#[cfg(feature = "production")]
fn return_pooled_stack(mut stack: Vec<StackElement>) {
stack.clear();
STACK_POOL.with(|pool| {
let mut pool = pool.borrow_mut();
if pool.len() < 10 {
pool.push_back(stack);
}
});
}
#[cfg(feature = "production")]
static CACHE_DISABLED: AtomicBool = AtomicBool::new(false);
#[cfg(feature = "production")]
pub fn disable_caching(disabled: bool) {
CACHE_DISABLED.store(disabled, Ordering::Relaxed);
}
#[cfg(feature = "production")]
fn is_caching_disabled() -> bool {
CACHE_DISABLED.load(Ordering::Relaxed)
}
#[cfg(feature = "production")]
fn compute_script_cache_key(
script_sig: &ByteString,
script_pubkey: &[u8],
witness: Option<&ByteString>,
flags: u32,
) -> u64 {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
script_sig.hash(&mut hasher);
script_pubkey.hash(&mut hasher);
if let Some(w) = witness {
w.hash(&mut hasher);
}
flags.hash(&mut hasher);
hasher.finish()
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SigVersion {
Base,
WitnessV0,
Tapscript,
}
#[spec_locked("5.2")]
#[cfg_attr(feature = "production", inline(always))]
#[cfg_attr(not(feature = "production"), inline)]
pub fn eval_script(
script: &[u8],
stack: &mut Vec<StackElement>,
flags: u32,
sigversion: SigVersion,
) -> Result<bool> {
if stack.capacity() < 20 {
stack.reserve(20);
}
#[cfg(feature = "production")]
{
eval_script_impl(script, stack, flags, sigversion)
}
#[cfg(not(feature = "production"))]
{
eval_script_inner(script, stack, flags, sigversion)
}
}
#[cfg(feature = "production")]
fn eval_script_impl(
script: &[u8],
stack: &mut Vec<StackElement>,
flags: u32,
sigversion: SigVersion,
) -> Result<bool> {
eval_script_inner(script, stack, flags, sigversion)
}
#[cfg(not(feature = "production"))]
#[allow(dead_code)]
fn eval_script_impl(
script: &[u8],
stack: &mut Vec<StackElement>,
flags: u32,
sigversion: SigVersion,
) -> Result<bool> {
eval_script_inner(script, stack, flags, sigversion)
}
#[inline(always)]
fn is_push_opcode(opcode: u8) -> bool {
opcode <= 0x60
}
fn eval_script_inner(
script: &[u8],
stack: &mut Vec<StackElement>,
flags: u32,
sigversion: SigVersion,
) -> Result<bool> {
use crate::constants::MAX_SCRIPT_SIZE;
use crate::error::{ConsensusError, ScriptErrorCode};
if script.len() > MAX_SCRIPT_SIZE {
return Err(ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::ScriptSize,
message: "Script size exceeds maximum".into(),
});
}
let mut op_count = 0;
let mut control_stack: Vec<control_flow::ControlBlock> = Vec::new();
let mut altstack: Vec<StackElement> = Vec::new();
for opcode in script {
let opcode = *opcode;
let in_false_branch = control_flow::in_false_branch(&control_stack);
if !is_push_opcode(opcode) {
op_count += 1;
if op_count > MAX_SCRIPT_OPS {
return Err(make_operation_limit_error());
}
debug_assert!(
op_count <= MAX_SCRIPT_OPS,
"Operation count ({op_count}) must not exceed MAX_SCRIPT_OPS ({MAX_SCRIPT_OPS})"
);
}
if stack.len() + altstack.len() >= MAX_STACK_SIZE {
return Err(make_stack_overflow_error());
}
match opcode {
OP_IF => {
if in_false_branch {
control_stack.push(control_flow::ControlBlock::If { executing: false });
continue;
}
if stack.is_empty() {
return Err(ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::InvalidStackOperation,
message: "OP_IF: empty stack".into(),
});
}
let condition_bytes = stack.pop().unwrap();
let condition = cast_to_bool(&condition_bytes);
const SCRIPT_VERIFY_MINIMALIF: u32 = 0x2000;
if (flags & SCRIPT_VERIFY_MINIMALIF) != 0
&& (sigversion == SigVersion::WitnessV0 || sigversion == SigVersion::Tapscript)
&& !control_flow::is_minimal_if_condition(&condition_bytes)
{
return Err(ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::MinimalIf,
message: "OP_IF condition must be minimally encoded".into(),
});
}
control_stack.push(control_flow::ControlBlock::If {
executing: condition,
});
}
OP_NOTIF => {
if in_false_branch {
control_stack.push(control_flow::ControlBlock::NotIf { executing: false });
continue;
}
if stack.is_empty() {
return Err(ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::InvalidStackOperation,
message: "OP_NOTIF: empty stack".into(),
});
}
let condition_bytes = stack.pop().unwrap();
let condition = cast_to_bool(&condition_bytes);
const SCRIPT_VERIFY_MINIMALIF: u32 = 0x2000;
if (flags & SCRIPT_VERIFY_MINIMALIF) != 0
&& (sigversion == SigVersion::WitnessV0 || sigversion == SigVersion::Tapscript)
&& !control_flow::is_minimal_if_condition(&condition_bytes)
{
return Err(ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::MinimalIf,
message: "OP_NOTIF condition must be minimally encoded".into(),
});
}
control_stack.push(control_flow::ControlBlock::NotIf {
executing: !condition,
});
}
OP_ELSE => {
if let Some(block) = control_stack.last_mut() {
match block {
control_flow::ControlBlock::If { executing }
| control_flow::ControlBlock::NotIf { executing } => {
*executing = !*executing;
}
}
} else {
return Err(ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::UnbalancedConditional,
message: "OP_ELSE without matching IF/NOTIF".into(),
});
}
}
OP_ENDIF => {
if control_stack.pop().is_none() {
return Err(ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::UnbalancedConditional,
message: "OP_ENDIF without matching IF/NOTIF".into(),
});
}
}
OP_TOALTSTACK => {
if in_false_branch {
continue;
}
if stack.is_empty() {
return Err(ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::InvalidStackOperation,
message: "OP_TOALTSTACK: empty stack".into(),
});
}
altstack.push(stack.pop().unwrap());
}
OP_FROMALTSTACK => {
if in_false_branch {
continue;
}
if altstack.is_empty() {
return Err(ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::InvalidAltstackOperation,
message: "OP_FROMALTSTACK: empty altstack".into(),
});
}
stack.push(altstack.pop().unwrap());
}
_ => {
if in_false_branch {
continue;
}
if !execute_opcode(opcode, stack, flags, sigversion)? {
return Ok(false);
}
debug_assert!(
stack.len() + altstack.len() <= MAX_STACK_SIZE,
"Combined stack size ({}) must not exceed MAX_STACK_SIZE ({}) after opcode execution",
stack.len() + altstack.len(),
MAX_STACK_SIZE
);
}
}
}
if !control_stack.is_empty() {
return Err(ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::UnbalancedConditional,
message: "Unclosed IF/NOTIF block".into(),
});
}
#[cfg(feature = "production")]
{
if stack.len() == 1 {
Ok(cast_to_bool(&stack[0]))
} else {
Ok(false)
}
}
#[cfg(not(feature = "production"))]
{
Ok(stack.len() == 1 && cast_to_bool(&stack[0]))
}
}
#[spec_locked("5.2")]
#[cfg_attr(feature = "production", inline(always))]
#[cfg_attr(not(feature = "production"), inline)]
pub fn verify_script(
script_sig: &ByteString,
script_pubkey: &[u8],
witness: Option<&ByteString>,
flags: u32,
) -> Result<bool> {
let sigversion = SigVersion::Base;
#[cfg(feature = "production")]
{
if !is_caching_disabled() {
let cache_key = compute_script_cache_key(script_sig, script_pubkey, witness, flags);
{
let cache = get_script_cache().read().unwrap();
if let Some(&cached_result) = cache.peek(&cache_key) {
return Ok(cached_result);
}
}
}
let mut stack = get_pooled_stack();
let cache_key = compute_script_cache_key(script_sig, script_pubkey, witness, flags);
let result = {
if !eval_script(script_sig, &mut stack, flags, sigversion)? {
if !is_caching_disabled() {
let mut cache = get_script_cache().write().unwrap();
cache.put(cache_key, false);
}
false
} else if !eval_script(script_pubkey, &mut stack, flags, sigversion)? {
if !is_caching_disabled() {
let mut cache = get_script_cache().write().unwrap();
cache.put(cache_key, false);
}
false
} else if let Some(w) = witness {
if !eval_script(w, &mut stack, flags, sigversion)? {
if !is_caching_disabled() {
let mut cache = get_script_cache().write().unwrap();
cache.put(cache_key, false);
}
false
} else {
let res = stack.len() == 1 && cast_to_bool(&stack[0]);
if !is_caching_disabled() {
let mut cache = get_script_cache().write().unwrap();
cache.put(cache_key, res);
}
res
}
} else {
let res = stack.len() == 1 && cast_to_bool(&stack[0]);
if !is_caching_disabled() {
let mut cache = get_script_cache().write().unwrap();
cache.put(cache_key, res);
}
res
}
};
return_pooled_stack(stack);
Ok(result)
}
#[cfg(not(feature = "production"))]
{
let mut stack = Vec::with_capacity(20);
if !eval_script(script_sig, &mut stack, flags, sigversion)? {
return Ok(false);
}
if !eval_script(script_pubkey, &mut stack, flags, sigversion)? {
return Ok(false);
}
if let Some(w) = witness {
if !eval_script(w, &mut stack, flags, sigversion)? {
return Ok(false);
}
}
Ok(stack.len() == 1 && cast_to_bool(&stack[0]))
}
}
#[spec_locked("5.2")]
#[cfg_attr(feature = "production", inline(always))]
#[cfg_attr(not(feature = "production"), inline)]
#[allow(clippy::too_many_arguments)]
pub fn verify_script_with_context(
script_sig: &ByteString,
script_pubkey: &[u8],
witness: Option<&crate::witness::Witness>,
flags: u32,
tx: &Transaction,
input_index: usize,
prevouts: &[TransactionOutput],
network: crate::types::Network,
) -> Result<bool> {
let prevout_values: Vec<i64> = prevouts.iter().map(|p| p.value).collect();
let prevout_script_pubkeys: Vec<&[u8]> =
prevouts.iter().map(|p| p.script_pubkey.as_ref()).collect();
let sigversion = SigVersion::Base;
verify_script_with_context_full(
script_sig,
script_pubkey,
witness,
flags,
tx,
input_index,
&prevout_values,
&prevout_script_pubkeys,
None, None, network,
sigversion,
#[cfg(feature = "production")]
None, None, #[cfg(feature = "production")]
None, #[cfg(feature = "production")]
None, #[cfg(feature = "production")]
None, )
}
#[spec_locked("5.2")]
#[allow(clippy::too_many_arguments)]
#[cfg_attr(feature = "production", inline(always))]
#[cfg_attr(not(feature = "production"), inline)]
#[cfg(feature = "production")]
#[allow(clippy::too_many_arguments)]
pub fn try_verify_p2pk_fast_path(
script_sig: &ByteString,
script_pubkey: &[u8],
flags: u32,
tx: &Transaction,
input_index: usize,
prevout_values: &[i64],
prevout_script_pubkeys: &[&[u8]],
block_height: Option<u64>,
network: crate::types::Network,
#[cfg(feature = "production")] sighash_cache: Option<
&crate::transaction_hash::SighashMidstateCache,
>,
) -> Option<Result<bool>> {
let len = script_pubkey.len();
if len != 35 && len != 67 {
return None;
}
if script_pubkey[len - 1] != OP_CHECKSIG {
return None;
}
let pubkey_len = len - 2; if pubkey_len != 33 && pubkey_len != 65 {
return None;
}
if script_pubkey[0] != 0x21 && script_pubkey[0] != 0x41 {
return None; }
let pubkey_bytes = &script_pubkey[1..(len - 1)];
let signature_bytes = parse_p2pk_script_sig(script_sig.as_ref())?;
if signature_bytes.is_empty() {
return Some(Ok(false));
}
use crate::transaction_hash::{calculate_transaction_sighash_single_input, SighashType};
let sighash_byte = signature_bytes[signature_bytes.len() - 1];
let sighash_type = SighashType::from_byte(sighash_byte);
let deleted_storage;
let script_code: &[u8] = if script_pubkey.len() < 71 {
script_pubkey
} else {
let pattern = serialize_push_data(signature_bytes);
deleted_storage = find_and_delete(script_pubkey, &pattern);
deleted_storage.as_ref()
};
let sighash = match calculate_transaction_sighash_single_input(
tx,
input_index,
script_code,
prevout_values[input_index],
sighash_type,
#[cfg(feature = "production")]
sighash_cache,
) {
Ok(h) => h,
Err(e) => return Some(Err(e)),
};
let height = block_height.unwrap_or(0);
let is_valid = signature::with_secp_context(|secp| {
signature::verify_signature(
secp,
pubkey_bytes,
signature_bytes,
&sighash,
flags,
height,
network,
SigVersion::Base,
)
});
Some(is_valid)
}
#[cfg(feature = "production")]
#[allow(clippy::too_many_arguments)]
pub fn try_verify_p2pkh_fast_path(
script_sig: &ByteString,
script_pubkey: &[u8],
flags: u32,
tx: &Transaction,
input_index: usize,
prevout_values: &[i64],
prevout_script_pubkeys: &[&[u8]],
block_height: Option<u64>,
network: crate::types::Network,
#[cfg(feature = "production")] precomputed_sighash_all: Option<[u8; 32]>,
#[cfg(feature = "production")] sighash_cache: Option<
&crate::transaction_hash::SighashMidstateCache,
>,
#[cfg(feature = "production")] precomputed_p2pkh_hash: Option<[u8; 20]>,
) -> Option<Result<bool>> {
#[cfg(all(feature = "production", feature = "profile"))]
let _t_entry = std::time::Instant::now();
if script_pubkey.len() != 25 {
return None;
}
if script_pubkey[0] != OP_DUP
|| script_pubkey[1] != OP_HASH160
|| script_pubkey[2] != PUSH_20_BYTES
|| script_pubkey[23] != OP_EQUALVERIFY
|| script_pubkey[24] != OP_CHECKSIG
{
return None;
}
let expected_hash = &script_pubkey[3..23];
#[cfg(all(feature = "production", feature = "profile"))]
crate::script_profile::add_p2pkh_fast_path_entry_ns(_t_entry.elapsed().as_nanos() as u64);
#[cfg(all(feature = "production", feature = "profile"))]
let _t_parse = std::time::Instant::now();
let (signature_bytes, pubkey_bytes) = parse_p2pkh_script_sig(script_sig.as_ref())?;
#[cfg(all(feature = "production", feature = "profile"))]
crate::script_profile::add_p2pkh_parse_ns(_t_parse.elapsed().as_nanos() as u64);
if pubkey_bytes.len() != 33 && pubkey_bytes.len() != 65 {
return Some(Ok(false));
}
if signature_bytes.is_empty() {
return Some(Ok(false));
}
let pubkey_hash: [u8; 20] = match precomputed_p2pkh_hash {
Some(h) => h,
None => {
#[cfg(all(feature = "production", feature = "profile"))]
let _t_hash = std::time::Instant::now();
let sha256_hash = OptimizedSha256::new().hash(pubkey_bytes);
let h = Ripemd160::digest(sha256_hash);
#[cfg(all(feature = "production", feature = "profile"))]
crate::script_profile::add_p2pkh_hash160_ns(_t_hash.elapsed().as_nanos() as u64);
h.into()
}
};
if &pubkey_hash[..] != expected_hash {
return Some(Ok(false));
}
use crate::transaction_hash::SighashType;
let sighash_byte = signature_bytes[signature_bytes.len() - 1];
let sighash_type = SighashType::from_byte(sighash_byte);
let deleted_storage;
let script_code: &[u8] = if script_pubkey.len() < 71 {
script_pubkey
} else {
let pattern = serialize_push_data(signature_bytes);
deleted_storage = find_and_delete(script_pubkey, &pattern);
deleted_storage.as_ref()
};
let sighash = {
#[cfg(feature = "production")]
{
if let Some(precomp) = precomputed_sighash_all {
precomp
} else {
crate::transaction_hash::compute_legacy_sighash_nocache(
tx,
input_index,
script_code,
sighash_byte,
)
}
}
#[cfg(not(feature = "production"))]
{
match calculate_transaction_sighash_single_input(
tx,
input_index,
script_code,
prevout_values[input_index],
sighash_type,
) {
Ok(h) => h,
Err(e) => return Some(Err(e)),
}
}
};
#[cfg(all(feature = "production", feature = "profile"))]
let _t_secp = std::time::Instant::now();
let height = block_height.unwrap_or(0);
let is_valid: Result<bool> = {
let der_sig = &signature_bytes[..signature_bytes.len() - 1];
if flags & 0x04 != 0
&& !crate::bip_validation::check_bip66_network(signature_bytes, height, network)
.unwrap_or(false)
{
Ok(false)
} else if flags & 0x02 != 0 {
let base_sighash = sighash_byte & !0x80;
if !(0x01..=0x03).contains(&base_sighash) {
Ok(false)
} else if pubkey_bytes.len() == 33 {
if pubkey_bytes[0] != 0x02 && pubkey_bytes[0] != 0x03 {
Ok(false)
} else {
let strict_der = flags & 0x04 != 0;
let enforce_low_s = flags & 0x08 != 0;
Ok(crate::secp256k1_backend::verify_ecdsa_direct(
der_sig,
pubkey_bytes,
&sighash,
strict_der,
enforce_low_s,
)
.unwrap_or(false))
}
} else if pubkey_bytes.len() == 65 && pubkey_bytes[0] == 0x04 {
let strict_der = flags & 0x04 != 0;
let enforce_low_s = flags & 0x08 != 0;
Ok(crate::secp256k1_backend::verify_ecdsa_direct(
der_sig,
pubkey_bytes,
&sighash,
strict_der,
enforce_low_s,
)
.unwrap_or(false))
} else {
Ok(false)
}
} else {
let strict_der = flags & 0x04 != 0;
let enforce_low_s = flags & 0x08 != 0;
Ok(crate::secp256k1_backend::verify_ecdsa_direct(
der_sig,
pubkey_bytes,
&sighash,
strict_der,
enforce_low_s,
)
.unwrap_or(false))
}
};
#[cfg(all(feature = "production", feature = "profile"))]
{
let ns = _t_secp.elapsed().as_nanos() as u64;
crate::script_profile::add_p2pkh_collect_ns(ns);
crate::script_profile::add_p2pkh_secp_context_ns(ns);
}
Some(is_valid)
}
#[cfg(feature = "production")]
#[inline]
pub fn verify_p2pkh_inline(
script_sig: &[u8],
script_pubkey: &[u8],
flags: u32,
tx: &Transaction,
input_index: usize,
height: u64,
network: crate::types::Network,
precomputed_sighash_all: Option<[u8; 32]>,
) -> Result<bool> {
#[cfg(feature = "profile")]
let _t0 = std::time::Instant::now();
let expected_hash = &script_pubkey[3..23];
let (signature_bytes, pubkey_bytes) = match parse_p2pkh_script_sig(script_sig) {
Some(pair) => pair,
None => return Ok(false),
};
if (pubkey_bytes.len() != 33 && pubkey_bytes.len() != 65) || signature_bytes.is_empty() {
return Ok(false);
}
#[cfg(feature = "profile")]
let _t_hash = std::time::Instant::now();
let sha256_hash = OptimizedSha256::new().hash(pubkey_bytes);
let pubkey_hash: [u8; 20] = Ripemd160::digest(sha256_hash).into();
if &pubkey_hash[..] != expected_hash {
return Ok(false);
}
#[cfg(feature = "profile")]
crate::script_profile::add_p2pkh_hash160_ns(_t_hash.elapsed().as_nanos() as u64);
#[cfg(feature = "profile")]
let _t_sighash = std::time::Instant::now();
let sighash_byte = signature_bytes[signature_bytes.len() - 1];
let sighash = if let Some(precomp) = precomputed_sighash_all {
precomp
} else {
crate::transaction_hash::compute_legacy_sighash_buffered(
tx,
input_index,
script_pubkey,
sighash_byte,
)
};
#[cfg(feature = "profile")]
crate::script_profile::add_sighash_ns(_t_sighash.elapsed().as_nanos() as u64);
let der_sig = &signature_bytes[..signature_bytes.len() - 1];
let strict_der = flags & 0x04 != 0;
let enforce_low_s = flags & 0x08 != 0;
if strict_der
&& !crate::bip_validation::check_bip66_network(signature_bytes, height, network)
.unwrap_or(false)
{
return Ok(false);
}
if flags & 0x02 != 0 {
let sighash_base = sighash_byte & !0x80;
if !(0x01..=0x03).contains(&sighash_base) {
return Ok(false);
}
match pubkey_bytes.len() {
33 if pubkey_bytes[0] != 0x02 && pubkey_bytes[0] != 0x03 => return Ok(false),
65 if pubkey_bytes[0] != 0x04 => return Ok(false),
33 | 65 => {}
_ => return Ok(false),
}
}
#[cfg(feature = "profile")]
let _t_secp = std::time::Instant::now();
let result = crate::secp256k1_backend::verify_ecdsa_direct(
der_sig,
pubkey_bytes,
&sighash,
strict_der,
enforce_low_s,
)
.unwrap_or(false);
#[cfg(feature = "profile")]
crate::script_profile::add_p2pkh_secp_context_ns(_t_secp.elapsed().as_nanos() as u64);
#[cfg(feature = "profile")]
crate::script_profile::add_p2pkh_fast_path_entry_ns(_t0.elapsed().as_nanos() as u64);
Ok(result)
}
#[cfg(feature = "production")]
#[inline]
pub fn verify_p2pk_inline(
script_sig: &[u8],
script_pubkey: &[u8],
flags: u32,
tx: &Transaction,
input_index: usize,
height: u64,
network: crate::types::Network,
) -> Result<bool> {
let pk_len = script_pubkey.len() - 2; let pubkey_bytes = &script_pubkey[1..1 + pk_len];
let signature_bytes = match parse_p2pk_script_sig(script_sig) {
Some(s) => s,
None => return Ok(false),
};
if signature_bytes.is_empty() {
return Ok(false);
}
let sighash_byte = signature_bytes[signature_bytes.len() - 1];
let script_code: &[u8] = script_pubkey;
let sighash = crate::transaction_hash::compute_legacy_sighash_buffered(
tx,
input_index,
script_code,
sighash_byte,
);
let der_sig = &signature_bytes[..signature_bytes.len() - 1];
let strict_der = flags & 0x04 != 0;
let enforce_low_s = flags & 0x08 != 0;
if strict_der
&& !crate::bip_validation::check_bip66_network(signature_bytes, height, network)
.unwrap_or(false)
{
return Ok(false);
}
if flags & 0x02 != 0 {
let sighash_base = sighash_byte & !0x80;
if !(0x01..=0x03).contains(&sighash_base) {
return Ok(false);
}
match pubkey_bytes.len() {
33 if pubkey_bytes[0] != 0x02 && pubkey_bytes[0] != 0x03 => return Ok(false),
65 if pubkey_bytes[0] != 0x04 => return Ok(false),
33 | 65 => {}
_ => return Ok(false),
}
}
Ok(crate::secp256k1_backend::verify_ecdsa_direct(
der_sig,
pubkey_bytes,
&sighash,
strict_der,
enforce_low_s,
)
.unwrap_or(false))
}
#[allow(clippy::too_many_arguments)]
fn try_verify_p2sh_multisig_fast_path(
script_sig: &ByteString,
script_pubkey: &[u8],
flags: u32,
tx: &Transaction,
input_index: usize,
prevout_values: &[i64],
prevout_script_pubkeys: &[&[u8]],
block_height: Option<u64>,
network: crate::types::Network,
#[cfg(feature = "production")] sighash_cache: Option<
&crate::transaction_hash::SighashMidstateCache,
>,
) -> Option<Result<bool>> {
let pushes = parse_p2sh_script_sig_pushes(script_sig.as_ref())?;
if pushes.len() < 2 {
return None;
}
let redeem = pushes.last().expect("at least 2 pushes").as_ref();
let expected_hash = &script_pubkey[2..22];
let sha256_hash = OptimizedSha256::new().hash(redeem);
let redeem_hash = Ripemd160::digest(sha256_hash);
if &redeem_hash[..] != expected_hash {
return Some(Ok(false));
}
let (m, _n, pubkeys) = parse_redeem_multisig(redeem)?;
let signatures: Vec<&[u8]> = pushes
.iter()
.take(pushes.len() - 1)
.skip(1)
.map(|e| e.as_ref())
.collect();
let dummy = pushes.first().expect("at least 2 pushes").as_ref();
const SCRIPT_VERIFY_NULLDUMMY: u32 = 0x10;
const SCRIPT_VERIFY_NULLFAIL: u32 = 0x4000;
let height = block_height.unwrap_or(0);
if (flags & SCRIPT_VERIFY_NULLDUMMY) != 0 {
let activation = match network {
crate::types::Network::Mainnet => crate::constants::BIP147_ACTIVATION_MAINNET,
crate::types::Network::Testnet => crate::constants::BIP147_ACTIVATION_TESTNET,
crate::types::Network::Regtest => 0,
};
if height >= activation && !dummy.is_empty() && dummy != [0x00] {
return Some(Ok(false));
}
}
let mut cleaned = redeem.to_vec();
for sig in &signatures {
if !sig.is_empty() {
let pattern = serialize_push_data(sig);
cleaned = find_and_delete(&cleaned, &pattern).into_owned();
}
}
use crate::transaction_hash::{calculate_transaction_sighash_single_input, SighashType};
let mut sig_index = 0;
let mut valid_sigs = 0u8;
for pubkey_bytes in pubkeys {
if sig_index >= signatures.len() {
break;
}
while sig_index < signatures.len() && signatures[sig_index].is_empty() {
sig_index += 1;
}
if sig_index >= signatures.len() {
break;
}
let signature_bytes = &signatures[sig_index];
let sighash_byte = signature_bytes[signature_bytes.len() - 1];
let sighash_type = SighashType::from_byte(sighash_byte);
let sighash = match calculate_transaction_sighash_single_input(
tx,
input_index,
&cleaned,
prevout_values[input_index],
sighash_type,
#[cfg(feature = "production")]
sighash_cache,
) {
Ok(h) => h,
Err(e) => return Some(Err(e)),
};
#[cfg(feature = "production")]
let is_valid = signature::with_secp_context(|secp| {
signature::verify_signature(
secp,
pubkey_bytes,
signature_bytes,
&sighash,
flags,
height,
network,
SigVersion::Base,
)
});
#[cfg(not(feature = "production"))]
let is_valid = {
let secp = Secp256k1::new();
signature::verify_signature(
&secp,
pubkey_bytes,
signature_bytes,
&sighash,
flags,
height,
network,
SigVersion::Base,
)
};
let is_valid = match is_valid {
Ok(v) => v,
Err(e) => return Some(Err(e)),
};
if is_valid {
valid_sigs += 1;
sig_index += 1;
}
}
if (flags & SCRIPT_VERIFY_NULLFAIL) != 0 {
for sig_bytes in &signatures[sig_index..] {
if !sig_bytes.is_empty() {
return Some(Err(ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::SigNullFail,
message: "OP_CHECKMULTISIG: non-null signature must not fail under NULLFAIL"
.into(),
}));
}
}
}
Some(Ok(valid_sigs >= m))
}
#[allow(clippy::too_many_arguments)]
fn try_verify_bare_multisig_fast_path(
script_sig: &ByteString,
script_pubkey: &[u8],
flags: u32,
tx: &Transaction,
input_index: usize,
prevout_values: &[i64],
prevout_script_pubkeys: &[&[u8]],
block_height: Option<u64>,
network: crate::types::Network,
#[cfg(feature = "production")] sighash_cache: Option<
&crate::transaction_hash::SighashMidstateCache,
>,
) -> Option<Result<bool>> {
let (m, _n, pubkeys) = parse_redeem_multisig(script_pubkey)?;
let pushes = parse_p2sh_script_sig_pushes(script_sig.as_ref())?;
if pushes.len() < 2 {
return None;
}
let dummy = pushes.first().expect("at least 2 pushes").as_ref();
let signatures: Vec<&[u8]> = pushes[1..].iter().map(|e| e.as_ref()).collect();
const SCRIPT_VERIFY_NULLDUMMY: u32 = 0x10;
const SCRIPT_VERIFY_NULLFAIL: u32 = 0x4000;
let height = block_height.unwrap_or(0);
if (flags & SCRIPT_VERIFY_NULLDUMMY) != 0 {
let activation = match network {
crate::types::Network::Mainnet => crate::constants::BIP147_ACTIVATION_MAINNET,
crate::types::Network::Testnet => crate::constants::BIP147_ACTIVATION_TESTNET,
crate::types::Network::Regtest => 0,
};
if height >= activation && !dummy.is_empty() && dummy != [0x00] {
return Some(Ok(false));
}
}
let mut cleaned = script_pubkey.to_vec();
for sig in &signatures {
if !sig.is_empty() {
let pattern = serialize_push_data(sig);
cleaned = find_and_delete(&cleaned, &pattern).into_owned();
}
}
use crate::transaction_hash::{calculate_transaction_sighash_single_input, SighashType};
let mut sig_index = 0;
let mut valid_sigs = 0u8;
for pubkey_bytes in pubkeys {
if sig_index >= signatures.len() {
break;
}
while sig_index < signatures.len() && signatures[sig_index].is_empty() {
sig_index += 1;
}
if sig_index >= signatures.len() {
break;
}
let signature_bytes = &signatures[sig_index];
let sighash_byte = signature_bytes[signature_bytes.len() - 1];
let sighash_type = SighashType::from_byte(sighash_byte);
let sighash = match calculate_transaction_sighash_single_input(
tx,
input_index,
&cleaned,
prevout_values[input_index],
sighash_type,
#[cfg(feature = "production")]
sighash_cache,
) {
Ok(h) => h,
Err(e) => return Some(Err(e)),
};
#[cfg(feature = "production")]
let is_valid = signature::with_secp_context(|secp| {
signature::verify_signature(
secp,
pubkey_bytes,
signature_bytes,
&sighash,
flags,
height,
network,
SigVersion::Base,
)
});
#[cfg(not(feature = "production"))]
let is_valid = {
let secp = Secp256k1::new();
signature::verify_signature(
&secp,
pubkey_bytes,
signature_bytes,
&sighash,
flags,
height,
network,
SigVersion::Base,
)
};
let is_valid = match is_valid {
Ok(v) => v,
Err(e) => return Some(Err(e)),
};
if is_valid {
valid_sigs += 1;
sig_index += 1;
}
}
if (flags & SCRIPT_VERIFY_NULLFAIL) != 0 {
for sig_bytes in &signatures[sig_index..] {
if !sig_bytes.is_empty() {
return Some(Err(ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::SigNullFail,
message: "OP_CHECKMULTISIG: non-null signature must not fail under NULLFAIL"
.into(),
}));
}
}
}
Some(Ok(valid_sigs >= m))
}
#[cfg(feature = "production")]
#[allow(clippy::too_many_arguments)]
fn try_verify_p2sh_fast_path(
script_sig: &ByteString,
script_pubkey: &[u8],
flags: u32,
tx: &Transaction,
input_index: usize,
prevout_values: &[i64],
prevout_script_pubkeys: &[&[u8]],
block_height: Option<u64>,
median_time_past: Option<u64>,
network: crate::types::Network,
#[cfg(feature = "production")] sighash_cache: Option<
&crate::transaction_hash::SighashMidstateCache,
>,
#[cfg(feature = "production")] precomputed_sighash_all: Option<[u8; 32]>,
) -> Option<Result<bool>> {
const SCRIPT_VERIFY_P2SH: u32 = 0x01;
if (flags & SCRIPT_VERIFY_P2SH) == 0 {
return None;
}
if script_pubkey.len() != 23
|| script_pubkey[0] != OP_HASH160
|| script_pubkey[1] != PUSH_20_BYTES
|| script_pubkey[22] != OP_EQUAL
{
return None;
}
let expected_hash = &script_pubkey[2..22];
let mut pushes = parse_script_sig_push_only(script_sig.as_ref())?;
if pushes.is_empty() {
return None;
}
let redeem = pushes.pop().expect("at least one push");
let mut stack = pushes;
if redeem.len() >= 3
&& redeem[0] == OP_0
&& ((redeem[1] == PUSH_20_BYTES && redeem.len() == 22)
|| (redeem[1] == PUSH_32_BYTES && redeem.len() == 34))
{
return None;
}
let sha256_hash = OptimizedSha256::new().hash(redeem.as_ref());
let redeem_hash = Ripemd160::digest(sha256_hash);
if &redeem_hash[..] != expected_hash {
return Some(Ok(false));
}
if redeem.len() == 25
&& redeem[0] == OP_DUP
&& redeem[1] == OP_HASH160
&& redeem[2] == PUSH_20_BYTES
&& redeem[23] == OP_EQUALVERIFY
&& redeem[24] == OP_CHECKSIG
&& stack.len() == 2
{
let signature_bytes = &stack[0];
let pubkey_bytes = &stack[1];
if (pubkey_bytes.len() == 33 || pubkey_bytes.len() == 65) && !signature_bytes.is_empty() {
let expected_pubkey_hash = &redeem[3..23];
let sha256_hash = OptimizedSha256::new().hash(pubkey_bytes);
let pubkey_hash = Ripemd160::digest(sha256_hash);
if &pubkey_hash[..] == expected_pubkey_hash {
#[cfg(feature = "production")]
let sighash = if let Some(precomp) = precomputed_sighash_all {
precomp
} else {
use crate::transaction_hash::{
calculate_transaction_sighash_single_input, SighashType,
};
let sighash_byte = signature_bytes[signature_bytes.len() - 1];
let sighash_type = SighashType::from_byte(sighash_byte);
let deleted_storage;
let script_code: &[u8] = if redeem.len() < 71 {
redeem.as_ref()
} else {
let pattern = serialize_push_data(signature_bytes);
deleted_storage = find_and_delete(redeem.as_ref(), &pattern);
deleted_storage.as_ref()
};
match calculate_transaction_sighash_single_input(
tx,
input_index,
script_code,
prevout_values[input_index],
sighash_type,
sighash_cache,
) {
Ok(h) => h,
Err(e) => return Some(Err(e)),
}
};
#[cfg(not(feature = "production"))]
let sighash = {
use crate::transaction_hash::{
calculate_transaction_sighash_single_input, SighashType,
};
let sighash_byte = signature_bytes[signature_bytes.len() - 1];
let sighash_type = SighashType::from_byte(sighash_byte);
let deleted_storage;
let script_code: &[u8] = if redeem.len() < 71 {
redeem.as_ref()
} else {
let pattern = serialize_push_data(signature_bytes);
deleted_storage = find_and_delete(redeem.as_ref(), &pattern);
deleted_storage.as_ref()
};
match calculate_transaction_sighash_single_input(
tx,
input_index,
script_code,
prevout_values[input_index],
sighash_type,
) {
Ok(h) => h,
Err(e) => return Some(Err(e)),
}
};
let height = block_height.unwrap_or(0);
let is_valid = signature::with_secp_context(|secp| {
signature::verify_signature(
secp,
pubkey_bytes,
signature_bytes,
&sighash,
flags,
height,
network,
SigVersion::Base,
)
});
return Some(is_valid);
}
}
}
if (redeem.len() == 35 || redeem.len() == 67)
&& redeem[redeem.len() - 1] == OP_CHECKSIG
&& (redeem[0] == 0x21 || redeem[0] == 0x41)
&& stack.len() == 1
{
let pubkey_len = redeem.len() - 2;
if pubkey_len == 33 || pubkey_len == 65 {
let pubkey_bytes = &redeem.as_ref()[1..(redeem.len() - 1)];
let signature_bytes = &stack[0];
if !signature_bytes.is_empty() {
use crate::transaction_hash::{
calculate_transaction_sighash_single_input, SighashType,
};
let sighash_byte = signature_bytes[signature_bytes.len() - 1];
let sighash_type = SighashType::from_byte(sighash_byte);
let deleted_storage;
let script_code: &[u8] = if redeem.len() < 71 {
redeem.as_ref()
} else {
let pattern = serialize_push_data(signature_bytes);
deleted_storage = find_and_delete(redeem.as_ref(), &pattern);
deleted_storage.as_ref()
};
match calculate_transaction_sighash_single_input(
tx,
input_index,
script_code,
prevout_values[input_index],
sighash_type,
#[cfg(feature = "production")]
sighash_cache,
) {
Ok(sighash) => {
let height = block_height.unwrap_or(0);
let is_valid = signature::with_secp_context(|secp| {
signature::verify_signature(
secp,
pubkey_bytes,
signature_bytes,
&sighash,
flags,
height,
network,
SigVersion::Base,
)
});
return Some(is_valid);
}
Err(e) => return Some(Err(e)),
}
}
}
}
let result = eval_script_with_context_full_inner(
&redeem,
&mut stack,
flags,
tx,
input_index,
prevout_values,
prevout_script_pubkeys,
block_height,
median_time_past,
network,
SigVersion::Base,
Some(redeem.as_ref()),
None, #[cfg(feature = "production")]
None, None, #[cfg(feature = "production")]
sighash_cache,
);
Some(result)
}
#[cfg(feature = "production")]
#[allow(clippy::too_many_arguments)]
fn try_verify_p2wpkh_fast_path(
script_sig: &ByteString,
script_pubkey: &[u8],
witness: &crate::witness::Witness,
flags: u32,
tx: &Transaction,
input_index: usize,
prevout_values: &[i64],
prevout_script_pubkeys: &[&[u8]],
block_height: Option<u64>,
network: crate::types::Network,
precomputed_bip143: Option<&crate::transaction_hash::Bip143PrecomputedHashes>,
#[cfg(feature = "production")] precomputed_sighash_all: Option<[u8; 32]>,
) -> Option<Result<bool>> {
if script_pubkey.len() != 22 || script_pubkey[0] != OP_0 || script_pubkey[1] != PUSH_20_BYTES {
return None;
}
if !script_sig.is_empty() {
return None;
}
if witness.len() != 2 {
return None;
}
let signature_bytes = &witness[0];
let pubkey_bytes = &witness[1];
if pubkey_bytes.len() != 33 && pubkey_bytes.len() != 65 {
return Some(Ok(false));
}
if signature_bytes.is_empty() {
return Some(Ok(false));
}
let expected_hash = &script_pubkey[2..22];
let sha256_hash = OptimizedSha256::new().hash(pubkey_bytes);
let pubkey_hash = Ripemd160::digest(sha256_hash);
if &pubkey_hash[..] != expected_hash {
return Some(Ok(false));
}
let sighash_byte = signature_bytes[signature_bytes.len() - 1];
let sighash = if sighash_byte == 0x01 {
#[cfg(feature = "production")]
if let Some(precomp) = precomputed_sighash_all {
precomp
} else {
let amount = prevout_values.get(input_index).copied().unwrap_or(0);
match crate::transaction_hash::calculate_bip143_sighash(
tx,
input_index,
script_pubkey,
amount,
sighash_byte,
precomputed_bip143,
) {
Ok(h) => h,
Err(e) => return Some(Err(e)),
}
}
#[cfg(not(feature = "production"))]
{
let amount = prevout_values.get(input_index).copied().unwrap_or(0);
match crate::transaction_hash::calculate_bip143_sighash(
tx,
input_index,
script_pubkey,
amount,
sighash_byte,
precomputed_bip143,
) {
Ok(h) => h,
Err(e) => return Some(Err(e)),
}
}
} else {
let amount = prevout_values.get(input_index).copied().unwrap_or(0);
match crate::transaction_hash::calculate_bip143_sighash(
tx,
input_index,
script_pubkey,
amount,
sighash_byte,
precomputed_bip143,
) {
Ok(h) => h,
Err(e) => return Some(Err(e)),
}
};
let height = block_height.unwrap_or(0);
let is_valid = signature::with_secp_context(|secp| {
signature::verify_signature(
secp,
pubkey_bytes,
signature_bytes,
&sighash,
flags,
height,
network,
SigVersion::WitnessV0,
)
});
Some(is_valid)
}
#[cfg(feature = "production")]
#[allow(clippy::too_many_arguments)]
fn try_verify_p2wpkh_in_p2sh_fast_path(
script_sig: &ByteString,
script_pubkey: &[u8],
witness: &crate::witness::Witness,
flags: u32,
tx: &Transaction,
input_index: usize,
prevout_values: &[i64],
prevout_script_pubkeys: &[&[u8]],
block_height: Option<u64>,
network: crate::types::Network,
precomputed_bip143: Option<&crate::transaction_hash::Bip143PrecomputedHashes>,
) -> Option<Result<bool>> {
const SCRIPT_VERIFY_P2SH: u32 = 0x01;
if (flags & SCRIPT_VERIFY_P2SH) == 0 {
return None;
}
if script_pubkey.len() != 23
|| script_pubkey[0] != OP_HASH160
|| script_pubkey[1] != PUSH_20_BYTES
|| script_pubkey[22] != OP_EQUAL
{
return None;
}
let expected_hash = &script_pubkey[2..22];
let pushes = parse_script_sig_push_only(script_sig.as_ref())?;
if pushes.len() != 1 {
return None;
}
let redeem = &pushes[0];
if redeem.len() != 22 || redeem[0] != OP_0 || redeem[1] != PUSH_20_BYTES {
return None;
}
let sha256_hash = OptimizedSha256::new().hash(redeem.as_ref());
let redeem_hash = Ripemd160::digest(sha256_hash);
if &redeem_hash[..] != expected_hash {
return Some(Ok(false));
}
if witness.len() != 2 {
return None;
}
let signature_bytes = &witness[0];
let pubkey_bytes = &witness[1];
if (pubkey_bytes.len() != 33 && pubkey_bytes.len() != 65) || signature_bytes.is_empty() {
return Some(Ok(false));
}
let expected_pubkey_hash = &redeem[2..22];
let pubkey_sha256 = OptimizedSha256::new().hash(pubkey_bytes);
let pubkey_hash = Ripemd160::digest(pubkey_sha256);
if &pubkey_hash[..] != expected_pubkey_hash {
return Some(Ok(false));
}
let sighash_byte = signature_bytes[signature_bytes.len() - 1];
let amount = prevout_values.get(input_index).copied().unwrap_or(0);
let sighash = match crate::transaction_hash::calculate_bip143_sighash(
tx,
input_index,
redeem.as_ref(),
amount,
sighash_byte,
precomputed_bip143,
) {
Ok(h) => h,
Err(e) => return Some(Err(e)),
};
let height = block_height.unwrap_or(0);
let is_valid = signature::with_secp_context(|secp| {
signature::verify_signature(
secp,
pubkey_bytes,
signature_bytes,
&sighash,
flags,
height,
network,
SigVersion::WitnessV0,
)
});
Some(is_valid)
}
#[cfg(feature = "production")]
#[allow(clippy::too_many_arguments)]
fn try_verify_p2wsh_fast_path(
script_sig: &ByteString,
script_pubkey: &[u8],
witness: &crate::witness::Witness,
flags: u32,
tx: &Transaction,
input_index: usize,
prevout_values: &[i64],
prevout_script_pubkeys: &[&[u8]],
block_height: Option<u64>,
median_time_past: Option<u64>,
network: crate::types::Network,
schnorr_collector: Option<&crate::bip348::SchnorrSignatureCollector>,
precomputed_bip143: Option<&crate::transaction_hash::Bip143PrecomputedHashes>,
#[cfg(feature = "production")] sighash_cache: Option<
&crate::transaction_hash::SighashMidstateCache,
>,
) -> Option<Result<bool>> {
if script_pubkey.len() != 34 || script_pubkey[0] != OP_0 || script_pubkey[1] != PUSH_32_BYTES {
return None;
}
if !script_sig.is_empty() {
return None;
}
if witness.is_empty() {
return None;
}
let witness_script = witness.last().expect("witness not empty").clone();
let mut stack: Vec<StackElement> = witness
.iter()
.take(witness.len() - 1)
.map(|w| to_stack_element(w))
.collect();
let program_hash = &script_pubkey[2..34];
if program_hash.len() != 32 {
return None;
}
let witness_script_hash = OptimizedSha256::new().hash(witness_script.as_ref());
if &witness_script_hash[..] != program_hash {
return Some(Ok(false));
}
let witness_sigversion = if flags & 0x8000 != 0 {
SigVersion::Tapscript
} else {
SigVersion::WitnessV0
};
if witness_sigversion == SigVersion::WitnessV0
&& witness_script.len() == 25
&& witness_script[0] == OP_DUP
&& witness_script[1] == OP_HASH160
&& witness_script[2] == PUSH_20_BYTES
&& witness_script[23] == OP_EQUALVERIFY
&& witness_script[24] == OP_CHECKSIG
&& stack.len() == 2
{
let signature_bytes = &stack[0];
let pubkey_bytes = &stack[1];
if (pubkey_bytes.len() == 33 || pubkey_bytes.len() == 65) && !signature_bytes.is_empty() {
let expected_pubkey_hash = &witness_script[3..23];
let pubkey_sha256 = OptimizedSha256::new().hash(pubkey_bytes);
let pubkey_hash = Ripemd160::digest(pubkey_sha256);
if &pubkey_hash[..] == expected_pubkey_hash {
let sighash_byte = signature_bytes[signature_bytes.len() - 1];
let amount = prevout_values.get(input_index).copied().unwrap_or(0);
match crate::transaction_hash::calculate_bip143_sighash(
tx,
input_index,
witness_script.as_ref(),
amount,
sighash_byte,
precomputed_bip143,
) {
Ok(sighash) => {
let height = block_height.unwrap_or(0);
let is_valid = signature::with_secp_context(|secp| {
signature::verify_signature(
secp,
pubkey_bytes,
signature_bytes,
&sighash,
flags,
height,
network,
SigVersion::WitnessV0,
)
});
return Some(is_valid);
}
Err(e) => return Some(Err(e)),
}
}
}
}
if witness_sigversion == SigVersion::WitnessV0
&& (witness_script.len() == 35 || witness_script.len() == 67)
&& witness_script[witness_script.len() - 1] == OP_CHECKSIG
&& (witness_script[0] == 0x21 || witness_script[0] == 0x41)
&& stack.len() == 1
{
let pubkey_len = witness_script.len() - 2;
if (pubkey_len == 33 || pubkey_len == 65) && !stack[0].is_empty() {
let pubkey_bytes = &witness_script[1..(witness_script.len() - 1)];
let signature_bytes = &stack[0];
let sighash_byte = signature_bytes[signature_bytes.len() - 1];
let amount = prevout_values.get(input_index).copied().unwrap_or(0);
match crate::transaction_hash::calculate_bip143_sighash(
tx,
input_index,
witness_script.as_ref(),
amount,
sighash_byte,
precomputed_bip143,
) {
Ok(sighash) => {
let height = block_height.unwrap_or(0);
let is_valid = signature::with_secp_context(|secp| {
signature::verify_signature(
secp,
pubkey_bytes,
signature_bytes,
&sighash,
flags,
height,
network,
SigVersion::WitnessV0,
)
});
return Some(is_valid);
}
Err(e) => return Some(Err(e)),
}
}
}
if witness_sigversion == SigVersion::WitnessV0 {
if let Some((m, _n, pubkeys)) = parse_redeem_multisig(witness_script.as_ref()) {
if stack.len() < 2 {
return Some(Ok(false));
}
let dummy = stack[0].as_ref();
let signatures: Vec<&[u8]> = stack[1..].iter().map(|e| e.as_ref()).collect();
const SCRIPT_VERIFY_NULLDUMMY: u32 = 0x10;
const SCRIPT_VERIFY_NULLFAIL: u32 = 0x4000;
let height = block_height.unwrap_or(0);
if (flags & SCRIPT_VERIFY_NULLDUMMY) != 0 {
let activation = match network {
crate::types::Network::Mainnet => crate::constants::BIP147_ACTIVATION_MAINNET,
crate::types::Network::Testnet => crate::constants::BIP147_ACTIVATION_TESTNET,
crate::types::Network::Regtest => 0,
};
if height >= activation && !dummy.is_empty() && dummy != [0x00] {
return Some(Ok(false));
}
}
let mut cleaned = witness_script.to_vec();
for sig in &signatures {
if !sig.is_empty() {
let pattern = serialize_push_data(sig);
cleaned = find_and_delete(&cleaned, &pattern).into_owned();
}
}
let amount = prevout_values.get(input_index).copied().unwrap_or(0);
let mut sig_index = 0;
let mut valid_sigs = 0u8;
for pubkey_bytes in pubkeys {
if sig_index >= signatures.len() {
break;
}
while sig_index < signatures.len() && signatures[sig_index].is_empty() {
sig_index += 1;
}
if sig_index >= signatures.len() {
break;
}
let signature_bytes = &signatures[sig_index];
let sighash_byte = signature_bytes[signature_bytes.len() - 1];
match crate::transaction_hash::calculate_bip143_sighash(
tx,
input_index,
&cleaned,
amount,
sighash_byte,
precomputed_bip143,
) {
Ok(sighash) => {
let is_valid = signature::with_secp_context(|secp| {
signature::verify_signature(
secp,
pubkey_bytes,
signature_bytes,
&sighash,
flags,
height,
network,
SigVersion::WitnessV0,
)
});
match is_valid {
Ok(v) if v => {
valid_sigs += 1;
sig_index += 1;
}
Ok(_) => {}
Err(e) => return Some(Err(e)),
}
}
Err(e) => return Some(Err(e)),
}
}
if (flags & SCRIPT_VERIFY_NULLFAIL) != 0 {
for sig_bytes in &signatures[sig_index..] {
if !sig_bytes.is_empty() {
return Some(Err(ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::SigNullFail,
message:
"OP_CHECKMULTISIG: non-null signature must not fail under NULLFAIL"
.into(),
}));
}
}
}
return Some(Ok(valid_sigs >= m));
}
}
let result = eval_script_with_context_full_inner(
&witness_script,
&mut stack,
flags,
tx,
input_index,
prevout_values,
prevout_script_pubkeys,
block_height,
median_time_past,
network,
witness_sigversion,
None, None, schnorr_collector,
precomputed_bip143,
#[cfg(feature = "production")]
sighash_cache,
);
Some(result)
}
#[cfg(feature = "production")]
#[allow(clippy::too_many_arguments)]
fn try_verify_p2tr_scriptpath_p2pk_fast_path(
script_sig: &ByteString,
script_pubkey: &[u8],
witness: &crate::witness::Witness,
_flags: u32,
tx: &Transaction,
input_index: usize,
prevout_values: &[i64],
prevout_script_pubkeys: &[&[u8]],
block_height: Option<u64>,
network: crate::types::Network,
schnorr_collector: Option<&crate::bip348::SchnorrSignatureCollector>,
) -> Option<Result<bool>> {
use crate::activation::taproot_activation_height;
use crate::taproot::parse_taproot_script_path_witness;
let tap_h = taproot_activation_height(network);
if block_height.map(|h| h < tap_h).unwrap_or(true) {
return None;
}
if script_pubkey.len() != 34 || script_pubkey[0] != OP_1 || script_pubkey[1] != PUSH_32_BYTES {
return None;
}
if !script_sig.is_empty() {
return None;
}
if witness.len() < 2 {
return None;
}
let mut output_key = [0u8; 32];
output_key.copy_from_slice(&script_pubkey[2..34]);
let parsed = match parse_taproot_script_path_witness(witness, &output_key) {
Ok(Some(p)) => p,
Ok(None) | Err(_) => return None,
};
let (tapscript, stack_items, control_block) = parsed;
if tapscript.len() != 34 || tapscript[0] != PUSH_32_BYTES || tapscript[33] != OP_CHECKSIG {
return None;
}
if stack_items.len() != 1 || stack_items[0].len() != 64 {
return None;
}
let sig = stack_items[0].as_ref();
let pubkey_32 = &tapscript[1..33];
let sighash = crate::taproot::compute_tapscript_signature_hash(
tx,
input_index,
prevout_values,
prevout_script_pubkeys,
&tapscript,
control_block.leaf_version,
0xffff_ffff,
0x00,
)
.ok()?;
let result = crate::bip348::verify_tapscript_schnorr_signature(
&sighash,
pubkey_32,
sig,
schnorr_collector,
);
Some(result)
}
#[cfg(feature = "production")]
#[allow(clippy::too_many_arguments)]
fn try_verify_p2tr_keypath_fast_path(
script_sig: &ByteString,
script_pubkey: &[u8],
witness: &crate::witness::Witness,
_flags: u32,
tx: &Transaction,
input_index: usize,
prevout_values: &[i64],
prevout_script_pubkeys: &[&[u8]],
block_height: Option<u64>,
network: crate::types::Network,
schnorr_collector: Option<&crate::bip348::SchnorrSignatureCollector>,
) -> Option<Result<bool>> {
use crate::activation::taproot_activation_height;
let tap_h = taproot_activation_height(network);
if block_height.map(|h| h < tap_h).unwrap_or(true) {
return None;
}
if script_pubkey.len() != 34 || script_pubkey[0] != OP_1 || script_pubkey[1] != PUSH_32_BYTES {
return None;
}
if !script_sig.is_empty() {
return None;
}
if witness.len() != 1 || witness[0].len() != 64 {
return None;
}
let output_key = &script_pubkey[2..34];
let sig = &witness[0];
let sighash = crate::taproot::compute_taproot_signature_hash(
tx,
input_index,
prevout_values,
prevout_script_pubkeys,
0x00, )
.ok()?;
let result = crate::bip348::verify_tapscript_schnorr_signature(
&sighash,
output_key,
sig,
schnorr_collector,
);
Some(result)
}
#[spec_locked("5.2")]
pub fn verify_script_with_context_full(
script_sig: &ByteString,
script_pubkey: &[u8],
witness: Option<&crate::witness::Witness>,
flags: u32,
tx: &Transaction,
input_index: usize,
prevout_values: &[i64],
prevout_script_pubkeys: &[&[u8]],
block_height: Option<u64>,
median_time_past: Option<u64>,
network: crate::types::Network,
_sigversion: SigVersion,
#[cfg(feature = "production")] schnorr_collector: Option<
&crate::bip348::SchnorrSignatureCollector,
>,
precomputed_bip143: Option<&crate::transaction_hash::Bip143PrecomputedHashes>,
#[cfg(feature = "production")] precomputed_sighash_all: Option<[u8; 32]>,
#[cfg(feature = "production")] sighash_cache: Option<
&crate::transaction_hash::SighashMidstateCache,
>,
#[cfg(feature = "production")] precomputed_p2pkh_hash: Option<[u8; 20]>,
) -> Result<bool> {
if prevout_values.len() != tx.inputs.len() || prevout_script_pubkeys.len() != tx.inputs.len() {
return Err(ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::TxInputInvalid,
message: format!(
"Prevout slices: values={}, script_pubkeys={}, input_count={} (input_idx={})",
prevout_values.len(),
prevout_script_pubkeys.len(),
tx.inputs.len(),
input_index,
)
.into(),
});
}
if input_index < prevout_values.len() {
let prevout_value = prevout_values[input_index];
if prevout_value < 0 {
return Err(ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::ValueOverflow,
message: "Prevout value cannot be negative".into(),
});
}
#[cfg(feature = "production")]
{
use precomputed_constants::MAX_MONEY_U64;
if (prevout_value as u64) > MAX_MONEY_U64 {
return Err(ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::ValueOverflow,
message: format!("Prevout value {prevout_value} exceeds MAX_MONEY").into(),
});
}
}
#[cfg(not(feature = "production"))]
{
use crate::constants::MAX_MONEY;
if prevout_value > MAX_MONEY {
return Err(ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::ValueOverflow,
message: format!("Prevout value {prevout_value} exceeds MAX_MONEY").into(),
});
}
}
}
if input_index >= tx.inputs.len() {
return Err(ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::TxInputInvalid,
message: format!(
"Input index {} out of bounds (tx has {} inputs)",
input_index,
tx.inputs.len()
)
.into(),
});
}
#[cfg(feature = "production")]
if witness.is_none() {
if let Some(result) = try_verify_p2pk_fast_path(
script_sig,
script_pubkey,
flags,
tx,
input_index,
prevout_values,
prevout_script_pubkeys,
block_height,
network,
#[cfg(feature = "production")]
sighash_cache,
) {
FAST_PATH_P2PK.fetch_add(1, Ordering::Relaxed);
return result;
}
if let Some(result) = try_verify_p2pkh_fast_path(
script_sig,
script_pubkey,
flags,
tx,
input_index,
prevout_values,
prevout_script_pubkeys,
block_height,
network,
#[cfg(feature = "production")]
precomputed_sighash_all,
#[cfg(feature = "production")]
sighash_cache,
#[cfg(feature = "production")]
precomputed_p2pkh_hash,
) {
FAST_PATH_P2PKH.fetch_add(1, Ordering::Relaxed);
return result;
}
if let Some(result) = try_verify_p2sh_fast_path(
script_sig,
script_pubkey,
flags,
tx,
input_index,
prevout_values,
prevout_script_pubkeys,
block_height,
median_time_past,
network,
#[cfg(feature = "production")]
sighash_cache,
#[cfg(feature = "production")]
precomputed_sighash_all,
) {
FAST_PATH_P2SH.fetch_add(1, Ordering::Relaxed);
return result;
}
if let Some(result) = try_verify_bare_multisig_fast_path(
script_sig,
script_pubkey,
flags,
tx,
input_index,
prevout_values,
prevout_script_pubkeys,
block_height,
network,
#[cfg(feature = "production")]
sighash_cache,
) {
FAST_PATH_BARE_MULTISIG.fetch_add(1, Ordering::Relaxed);
return result;
}
}
#[cfg(feature = "production")]
if let Some(wit) = witness {
if let Some(result) = try_verify_p2wpkh_in_p2sh_fast_path(
script_sig,
script_pubkey,
wit,
flags,
tx,
input_index,
prevout_values,
prevout_script_pubkeys,
block_height,
network,
precomputed_bip143,
) {
FAST_PATH_P2WPKH.fetch_add(1, Ordering::Relaxed);
return result;
}
if let Some(result) = try_verify_p2wpkh_fast_path(
script_sig,
script_pubkey,
wit,
flags,
tx,
input_index,
prevout_values,
prevout_script_pubkeys,
block_height,
network,
precomputed_bip143,
precomputed_sighash_all,
) {
FAST_PATH_P2WPKH.fetch_add(1, Ordering::Relaxed);
return result;
}
if let Some(result) = try_verify_p2wsh_fast_path(
script_sig,
script_pubkey,
wit,
flags,
tx,
input_index,
prevout_values,
prevout_script_pubkeys,
block_height,
median_time_past,
network,
schnorr_collector,
precomputed_bip143,
#[cfg(feature = "production")]
sighash_cache,
) {
FAST_PATH_P2WSH.fetch_add(1, Ordering::Relaxed);
return result;
}
if let Some(result) = try_verify_p2tr_scriptpath_p2pk_fast_path(
script_sig,
script_pubkey,
wit,
flags,
tx,
input_index,
prevout_values,
prevout_script_pubkeys,
block_height,
network,
schnorr_collector,
) {
FAST_PATH_P2TR.fetch_add(1, Ordering::Relaxed);
return result;
}
if let Some(result) = try_verify_p2tr_keypath_fast_path(
script_sig,
script_pubkey,
wit,
flags,
tx,
input_index,
prevout_values,
prevout_script_pubkeys,
block_height,
network,
schnorr_collector,
) {
FAST_PATH_P2TR.fetch_add(1, Ordering::Relaxed);
return result;
}
}
#[cfg(feature = "production")]
FAST_PATH_INTERPRETER.fetch_add(1, Ordering::Relaxed);
const SCRIPT_VERIFY_P2SH: u32 = 0x01;
let is_p2sh = (flags & SCRIPT_VERIFY_P2SH) != 0
&& script_pubkey.len() == 23 && script_pubkey[0] == OP_HASH160 && script_pubkey[1] == PUSH_20_BYTES && script_pubkey[22] == OP_EQUAL;
if is_p2sh {
let mut i = 0;
while i < script_sig.len() {
let opcode = script_sig[i];
if !is_push_opcode(opcode) {
return Ok(false);
}
if opcode == OP_0 {
i += 1;
} else if opcode <= 0x4b {
let len = opcode as usize;
if i + 1 + len > script_sig.len() {
return Ok(false); }
i += 1 + len;
} else if opcode == OP_PUSHDATA1 {
if i + 1 >= script_sig.len() {
return Ok(false);
}
let len = script_sig[i + 1] as usize;
if i + 2 + len > script_sig.len() {
return Ok(false);
}
i += 2 + len;
} else if opcode == OP_PUSHDATA2 {
if i + 2 >= script_sig.len() {
return Ok(false);
}
let len = u16::from_le_bytes([script_sig[i + 1], script_sig[i + 2]]) as usize;
if i + 3 + len > script_sig.len() {
return Ok(false);
}
i += 3 + len;
} else if opcode == OP_PUSHDATA4 {
if i + 4 >= script_sig.len() {
return Ok(false);
}
let len = u32::from_le_bytes([
script_sig[i + 1],
script_sig[i + 2],
script_sig[i + 3],
script_sig[i + 4],
]) as usize;
if i + 5 + len > script_sig.len() {
return Ok(false);
}
i += 5 + len;
} else if (OP_1NEGATE..=OP_16).contains(&opcode) {
i += 1;
} else {
return Ok(false);
}
}
if let Some(result) = try_verify_p2sh_multisig_fast_path(
script_sig,
script_pubkey,
flags,
tx,
input_index,
prevout_values,
prevout_script_pubkeys,
block_height,
network,
#[cfg(feature = "production")]
sighash_cache,
) {
return result;
}
}
#[cfg(feature = "production")]
let mut _stack_guard = PooledStackGuard(get_pooled_stack());
#[cfg(feature = "production")]
let stack = &mut _stack_guard.0;
#[cfg(not(feature = "production"))]
let mut stack = Vec::with_capacity(20);
let script_sig_result = eval_script_with_context_full(
script_sig,
stack,
flags,
tx,
input_index,
prevout_values,
prevout_script_pubkeys,
block_height,
median_time_past,
network,
SigVersion::Base,
None, #[cfg(feature = "production")]
schnorr_collector,
None, #[cfg(feature = "production")]
sighash_cache,
)?;
if !script_sig_result {
return Ok(false);
}
let redeem_script: Option<ByteString> = if is_p2sh && !stack.is_empty() {
Some(stack.last().expect("Stack is not empty").as_ref().to_vec())
} else {
None
};
use crate::activation::taproot_activation_height;
let tap_h = taproot_activation_height(network);
let is_taproot = redeem_script.is_none() && block_height.is_some() && block_height.unwrap() >= tap_h
&& script_pubkey.len() == 34
&& script_pubkey[0] == OP_1 && script_pubkey[1] == PUSH_32_BYTES;
if is_taproot && !script_sig.is_empty() {
return Ok(false); }
let is_direct_witness_program = redeem_script.is_none() && !is_taproot && script_pubkey.len() >= 3
&& script_pubkey[0] == OP_0 && ((script_pubkey[1] == PUSH_20_BYTES && script_pubkey.len() == 22) || (script_pubkey[1] == PUSH_32_BYTES && script_pubkey.len() == 34));
let mut witness_script_to_execute: Option<ByteString> = None;
if is_direct_witness_program {
if let Some(witness_stack) = witness {
if script_pubkey[1] == PUSH_32_BYTES {
if witness_stack.is_empty() {
return Ok(false); }
let witness_script = witness_stack.last().expect("Witness stack is not empty");
let program_bytes = &script_pubkey[2..];
if program_bytes.len() != 32 {
return Ok(false); }
let witness_script_hash = OptimizedSha256::new().hash(witness_script.as_ref());
if &witness_script_hash[..] != program_bytes {
return Ok(false); }
for element in witness_stack.iter().take(witness_stack.len() - 1) {
stack.push(to_stack_element(element));
}
witness_script_to_execute = Some(witness_script.clone());
} else if script_pubkey[1] == PUSH_20_BYTES {
if witness_stack.len() != 2 {
return Ok(false); }
for element in witness_stack.iter() {
stack.push(to_stack_element(element));
}
} else {
return Ok(false); }
} else {
return Ok(false); }
}
if is_taproot {
let Some(witness_stack) = witness else {
return Ok(false);
};
if witness_stack.len() < 2 {
return Ok(false);
}
let mut output_key = [0u8; 32];
output_key.copy_from_slice(&script_pubkey[2..34]);
match crate::taproot::parse_taproot_script_path_witness(witness_stack, &output_key)? {
None => return Ok(false),
Some((tapscript, stack_items, _control_block)) => {
for item in &stack_items {
stack.push(to_stack_element(item));
}
let tapscript_flags = flags | 0x8000;
if !eval_script_with_context_full(
&tapscript,
stack,
tapscript_flags,
tx,
input_index,
prevout_values,
prevout_script_pubkeys,
block_height,
median_time_past,
network,
SigVersion::Tapscript,
None,
#[cfg(feature = "production")]
schnorr_collector,
None,
#[cfg(feature = "production")]
sighash_cache,
)? {
return Ok(false);
}
return Ok(true);
}
}
}
let script_pubkey_result = eval_script_with_context_full(
script_pubkey,
stack,
flags,
tx,
input_index,
prevout_values,
prevout_script_pubkeys,
block_height,
median_time_past,
network,
SigVersion::Base,
Some(script_sig),
#[cfg(feature = "production")]
schnorr_collector,
None, #[cfg(feature = "production")]
sighash_cache,
)?;
if !script_pubkey_result {
return Ok(false);
}
if let Some(witness_script) = witness_script_to_execute {
let witness_sigversion = if flags & 0x8000 != 0 {
SigVersion::Tapscript
} else {
SigVersion::WitnessV0 };
if !eval_script_with_context_full(
&witness_script,
stack,
flags,
tx,
input_index,
prevout_values,
prevout_script_pubkeys,
block_height,
median_time_past,
network,
witness_sigversion,
None, #[cfg(feature = "production")]
schnorr_collector,
precomputed_bip143, #[cfg(feature = "production")]
sighash_cache,
)? {
return Ok(false);
}
}
if let Some(redeem) = redeem_script {
if stack.is_empty() {
return Ok(false); }
let top = stack.last().expect("Stack is not empty");
if !cast_to_bool(top) {
return Ok(false); }
stack.pop();
let is_witness_program = redeem.len() >= 3
&& redeem[0] == OP_0 && ((redeem[1] == PUSH_20_BYTES && redeem.len() == 22) || (redeem[1] == PUSH_32_BYTES && redeem.len() == 34));
if is_witness_program && witness.is_some() {
let program_bytes = &redeem[2..];
if redeem[1] == PUSH_32_BYTES {
if program_bytes.len() != 32 {
return Ok(false); }
if let Some(witness_stack) = witness {
if witness_stack.is_empty() {
return Ok(false); }
let witness_script = witness_stack.last().expect("Witness stack is not empty");
let witness_script_hash = OptimizedSha256::new().hash(witness_script.as_ref());
if &witness_script_hash[..] != program_bytes {
return Ok(false); }
stack.clear();
for element in witness_stack.iter().take(witness_stack.len() - 1) {
stack.push(to_stack_element(element));
}
let witness_sigversion = if flags & 0x8000 != 0 {
SigVersion::Tapscript
} else {
SigVersion::WitnessV0 };
if !eval_script_with_context_full(
witness_script,
stack,
flags,
tx,
input_index,
prevout_values,
prevout_script_pubkeys,
block_height,
median_time_past,
network,
witness_sigversion,
None, #[cfg(feature = "production")]
schnorr_collector,
precomputed_bip143, #[cfg(feature = "production")]
sighash_cache,
)? {
return Ok(false);
}
} else {
return Ok(false); }
} else if redeem[1] == PUSH_20_BYTES {
stack.clear();
} else {
return Ok(false); }
} else {
let redeem_result = eval_script_with_context_full_inner(
&redeem,
stack,
flags,
tx,
input_index,
prevout_values,
prevout_script_pubkeys,
block_height,
median_time_past,
network,
SigVersion::Base,
Some(redeem.as_ref()), Some(script_sig), #[cfg(feature = "production")]
None, None, #[cfg(feature = "production")]
sighash_cache,
)?;
if !redeem_result {
return Ok(false);
}
}
}
assert!(
stack.len() <= 1000,
"Stack size {} exceeds reasonable maximum after scriptPubkey",
stack.len()
);
if let Some(_witness_stack) = witness {
}
const SCRIPT_VERIFY_CLEANSTACK: u32 = 0x100;
let final_result = if (flags & SCRIPT_VERIFY_CLEANSTACK) != 0 {
stack.len() == 1 && cast_to_bool(&stack[0])
} else {
!stack.is_empty() && cast_to_bool(stack.last().expect("Stack is not empty"))
};
Ok(final_result)
}
#[allow(dead_code)]
fn eval_script_with_context(
script: &ByteString,
stack: &mut Vec<StackElement>,
flags: u32,
tx: &Transaction,
input_index: usize,
prevouts: &[TransactionOutput],
network: crate::types::Network,
) -> Result<bool> {
let prevout_values: Vec<i64> = prevouts.iter().map(|p| p.value).collect();
let prevout_script_pubkeys: Vec<&[u8]> =
prevouts.iter().map(|p| p.script_pubkey.as_ref()).collect();
eval_script_with_context_full(
script,
stack,
flags,
tx,
input_index,
&prevout_values,
&prevout_script_pubkeys,
None, None, network,
SigVersion::Base,
None, #[cfg(feature = "production")]
None, None, #[cfg(feature = "production")]
None, )
}
#[allow(clippy::too_many_arguments)]
fn eval_script_with_context_full(
script: &[u8],
stack: &mut Vec<StackElement>,
flags: u32,
tx: &Transaction,
input_index: usize,
prevout_values: &[i64],
prevout_script_pubkeys: &[&[u8]],
block_height: Option<u64>,
median_time_past: Option<u64>,
network: crate::types::Network,
sigversion: SigVersion,
script_sig_for_sighash: Option<&ByteString>,
#[cfg(feature = "production")] schnorr_collector: Option<
&crate::bip348::SchnorrSignatureCollector,
>,
precomputed_bip143: Option<&crate::transaction_hash::Bip143PrecomputedHashes>,
#[cfg(feature = "production")] sighash_cache: Option<
&crate::transaction_hash::SighashMidstateCache,
>,
) -> Result<bool> {
#[cfg(all(feature = "production", feature = "profile"))]
let _t0 = std::time::Instant::now();
let r = eval_script_with_context_full_inner(
script,
stack,
flags,
tx,
input_index,
prevout_values,
prevout_script_pubkeys,
block_height,
median_time_past,
network,
sigversion,
None,
script_sig_for_sighash,
#[cfg(feature = "production")]
schnorr_collector,
precomputed_bip143,
#[cfg(feature = "production")]
sighash_cache,
);
#[cfg(all(feature = "production", feature = "profile"))]
crate::script_profile::add_interpreter_ns(_t0.elapsed().as_nanos() as u64);
r
}
fn eval_script_with_context_full_inner(
script: &[u8],
stack: &mut Vec<StackElement>,
flags: u32,
tx: &Transaction,
input_index: usize,
prevout_values: &[i64],
prevout_script_pubkeys: &[&[u8]],
block_height: Option<u64>,
median_time_past: Option<u64>,
network: crate::types::Network,
sigversion: SigVersion,
redeem_script_for_sighash: Option<&[u8]>,
script_sig_for_sighash: Option<&ByteString>,
#[cfg(feature = "production")] schnorr_collector: Option<
&crate::bip348::SchnorrSignatureCollector,
>,
precomputed_bip143: Option<&crate::transaction_hash::Bip143PrecomputedHashes>,
#[cfg(feature = "production")] sighash_cache: Option<
&crate::transaction_hash::SighashMidstateCache,
>,
) -> Result<bool> {
assert!(
script.len() <= 10000,
"Script length {} exceeds reasonable maximum",
script.len()
);
assert!(
stack.len() <= 1000,
"Stack size {} exceeds reasonable maximum at start",
stack.len()
);
use crate::error::{ConsensusError, ScriptErrorCode};
if stack.capacity() < 20 {
stack.reserve(20);
}
let mut op_count = 0;
assert!(op_count == 0, "Op count must start at zero");
#[cfg(feature = "production")]
let mut control_stack: Vec<control_flow::ControlBlock> = Vec::with_capacity(4);
#[cfg(not(feature = "production"))]
let mut control_stack: Vec<control_flow::ControlBlock> = Vec::new();
assert!(control_stack.is_empty(), "Control stack must start empty");
#[cfg(feature = "production")]
let mut altstack: Vec<StackElement> = Vec::with_capacity(8);
#[cfg(not(feature = "production"))]
let mut altstack: Vec<StackElement> = Vec::new();
let mut code_separator_pos: usize = 0;
let mut last_codesep_opcode_pos: u32 = 0xffff_ffff;
let mut i = 0;
while i < script.len() {
#[cfg(feature = "production")]
{
prefetch::prefetch_ahead(script, i, 64); }
let opcode = {
#[cfg(feature = "production")]
{
unsafe { *script.get_unchecked(i) }
}
#[cfg(not(feature = "production"))]
{
script[i]
}
};
let in_false_branch = control_flow::in_false_branch(&control_stack);
if !is_push_opcode(opcode) {
op_count += 1;
assert!(
op_count <= MAX_SCRIPT_OPS + 1,
"Op count {op_count} must not exceed MAX_SCRIPT_OPS + 1"
);
if op_count > MAX_SCRIPT_OPS {
return Err(make_operation_limit_error());
}
}
if stack.len() + altstack.len() > MAX_STACK_SIZE {
return Err(make_stack_overflow_error());
}
if (0x01..=OP_PUSHDATA4).contains(&opcode) {
let (data, advance) = if opcode <= 0x4b {
let len = opcode as usize;
if i + 1 + len > script.len() {
return Ok(false); }
(&script[i + 1..i + 1 + len], 1 + len)
} else if opcode == OP_PUSHDATA1 {
if i + 1 >= script.len() {
return Ok(false);
}
let len = script[i + 1] as usize;
if i + 2 + len > script.len() {
return Ok(false);
}
(&script[i + 2..i + 2 + len], 2 + len)
} else if opcode == OP_PUSHDATA2 {
if i + 2 >= script.len() {
return Ok(false);
}
let len = u16::from_le_bytes([script[i + 1], script[i + 2]]) as usize;
if i + 3 + len > script.len() {
return Ok(false);
}
(&script[i + 3..i + 3 + len], 3 + len)
} else {
if i + 4 >= script.len() {
return Ok(false);
}
let len = u32::from_le_bytes([
script[i + 1],
script[i + 2],
script[i + 3],
script[i + 4],
]) as usize;
let data_start = i.saturating_add(5);
let data_end = data_start.saturating_add(len);
let advance = 5usize.saturating_add(len);
if advance < 5 || data_end > script.len() || data_end < data_start {
return Ok(false); }
(&script[data_start..data_end], advance)
};
if !in_false_branch {
stack.push(to_stack_element(data));
}
i += advance;
continue;
}
if opcode == OP_DUP {
if !in_false_branch {
if stack.is_empty() {
return Err(ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::InvalidStackOperation,
message: "OP_DUP: empty stack".into(),
});
}
let len = stack.len();
#[cfg(feature = "production")]
{
if stack.capacity() == stack.len() {
stack.reserve(1);
}
let item = unsafe { stack.get_unchecked(len - 1).clone() };
stack.push(item);
}
#[cfg(not(feature = "production"))]
{
let item = stack.last().unwrap();
stack.push(item.clone());
}
}
i += 1;
continue;
}
if opcode == OP_EQUALVERIFY {
if !in_false_branch {
if stack.len() < 2 {
return Err(ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::InvalidStackOperation,
message: "OP_EQUALVERIFY: insufficient stack items".into(),
});
}
let a = stack.pop().unwrap();
let b = stack.pop().unwrap();
if a != b {
return Ok(false);
}
}
i += 1;
continue;
}
if opcode == OP_HASH160 {
if !in_false_branch && !crypto_ops::op_hash160(stack)? {
return Ok(false);
}
i += 1;
continue;
}
if opcode == OP_VERIFY {
if !in_false_branch {
if let Some(item) = stack.pop() {
if !cast_to_bool(&item) {
return Ok(false);
}
} else {
return Ok(false);
}
}
i += 1;
continue;
}
if opcode == OP_EQUAL {
if !in_false_branch {
if stack.len() < 2 {
return Err(ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::InvalidStackOperation,
message: "OP_EQUAL: insufficient stack items".into(),
});
}
let a = stack.pop().unwrap();
let b = stack.pop().unwrap();
stack.push(to_stack_element(&[if a == b { 1 } else { 0 }]));
}
i += 1;
continue;
}
if opcode == OP_CHECKSIG
|| opcode == OP_CHECKSIGVERIFY
|| (opcode == OP_CHECKSIGADD && sigversion == SigVersion::Tapscript)
{
if !in_false_branch {
let effective_script_code = Some(&script[code_separator_pos..]);
let (tapscript, codesep) = if sigversion == SigVersion::Tapscript {
(Some(script), Some(last_codesep_opcode_pos))
} else {
(None, None)
};
let ctx = context::ScriptContext {
tx,
input_index,
prevout_values,
prevout_script_pubkeys,
block_height,
median_time_past,
network,
sigversion,
redeem_script_for_sighash,
script_sig_for_sighash,
tapscript_for_sighash: tapscript,
tapscript_codesep_pos: codesep,
#[cfg(feature = "production")]
schnorr_collector,
#[cfg(feature = "production")]
precomputed_bip143,
#[cfg(feature = "production")]
sighash_cache,
};
if !execute_opcode_with_context_full(
opcode,
stack,
flags,
&ctx,
effective_script_code,
)? {
return Ok(false);
}
}
i += 1;
continue;
}
match opcode {
OP_0 => {
if !in_false_branch {
stack.push(to_stack_element(&[]));
}
}
OP_1_RANGE_START..=OP_1_RANGE_END => {
if !in_false_branch {
let num = opcode - OP_N_BASE;
stack.push(to_stack_element(&[num]));
}
}
OP_1NEGATE => {
if !in_false_branch {
stack.push(to_stack_element(&[0x81])); }
}
OP_NOP => {
}
OP_VER => {
if !in_false_branch {
return Err(ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::DisabledOpcode,
message: "OP_VER is disabled".into(),
});
}
}
OP_IF => {
if in_false_branch {
control_stack.push(control_flow::ControlBlock::If { executing: false });
i += 1;
continue;
}
if stack.is_empty() {
return Err(ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::InvalidStackOperation,
message: "OP_IF: empty stack".into(),
});
}
let condition_bytes = stack.pop().unwrap();
let condition = cast_to_bool(&condition_bytes);
const SCRIPT_VERIFY_MINIMALIF: u32 = 0x2000;
if (flags & SCRIPT_VERIFY_MINIMALIF) != 0
&& (sigversion == SigVersion::WitnessV0 || sigversion == SigVersion::Tapscript)
&& !control_flow::is_minimal_if_condition(&condition_bytes)
{
return Err(ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::MinimalIf,
message: "OP_IF condition must be minimally encoded".into(),
});
}
control_stack.push(control_flow::ControlBlock::If {
executing: condition,
});
}
OP_NOTIF => {
if in_false_branch {
control_stack.push(control_flow::ControlBlock::NotIf { executing: false });
i += 1;
continue;
}
if stack.is_empty() {
return Err(ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::InvalidStackOperation,
message: "OP_NOTIF: empty stack".into(),
});
}
let condition_bytes = stack.pop().unwrap();
let condition = cast_to_bool(&condition_bytes);
const SCRIPT_VERIFY_MINIMALIF: u32 = 0x2000;
if (flags & SCRIPT_VERIFY_MINIMALIF) != 0
&& (sigversion == SigVersion::WitnessV0 || sigversion == SigVersion::Tapscript)
&& !control_flow::is_minimal_if_condition(&condition_bytes)
{
return Err(ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::MinimalIf,
message: "OP_NOTIF condition must be minimally encoded".into(),
});
}
control_stack.push(control_flow::ControlBlock::NotIf {
executing: !condition,
});
}
OP_ELSE => {
if let Some(block) = control_stack.last_mut() {
match block {
control_flow::ControlBlock::If { executing }
| control_flow::ControlBlock::NotIf { executing } => {
*executing = !*executing;
}
}
} else {
return Err(ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::UnbalancedConditional,
message: "OP_ELSE without matching IF/NOTIF".into(),
});
}
}
OP_ENDIF => {
if control_stack.pop().is_none() {
return Err(ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::UnbalancedConditional,
message: "OP_ENDIF without matching IF/NOTIF".into(),
});
}
}
OP_TOALTSTACK => {
if in_false_branch {
i += 1;
continue;
}
if stack.is_empty() {
return Err(ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::InvalidStackOperation,
message: "OP_TOALTSTACK: empty stack".into(),
});
}
altstack.push(stack.pop().unwrap());
}
OP_FROMALTSTACK => {
if in_false_branch {
i += 1;
continue;
}
if altstack.is_empty() {
return Err(ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::InvalidAltstackOperation,
message: "OP_FROMALTSTACK: empty altstack".into(),
});
}
stack.push(altstack.pop().unwrap());
}
OP_CODESEPARATOR => {
if in_false_branch {
i += 1;
continue;
}
code_separator_pos = i + 1;
last_codesep_opcode_pos = opcode_position_at_byte(script, i);
}
_ => {
if in_false_branch {
i += 1;
continue;
}
let subscript_for_sighash = if matches!(
opcode,
OP_CHECKSIG
| OP_CHECKSIGVERIFY
| OP_CHECKSIGADD
| OP_CHECKMULTISIG
| OP_CHECKMULTISIGVERIFY
) {
Some(&script[code_separator_pos..])
} else {
None
};
let effective_script_code = subscript_for_sighash.or(redeem_script_for_sighash);
let (tapscript, codesep) = if sigversion == SigVersion::Tapscript {
(Some(script), Some(last_codesep_opcode_pos))
} else {
(None, None)
};
let ctx = context::ScriptContext {
tx,
input_index,
prevout_values,
prevout_script_pubkeys,
block_height,
median_time_past,
network,
sigversion,
redeem_script_for_sighash,
script_sig_for_sighash,
tapscript_for_sighash: tapscript,
tapscript_codesep_pos: codesep,
#[cfg(feature = "production")]
schnorr_collector,
#[cfg(feature = "production")]
precomputed_bip143,
#[cfg(feature = "production")]
sighash_cache,
};
if !execute_opcode_with_context_full(
opcode,
stack,
flags,
&ctx,
effective_script_code,
)? {
return Ok(false);
}
}
}
i += 1;
}
if !control_stack.is_empty() {
return Err(ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::UnbalancedConditional,
message: "Unclosed IF/NOTIF block".into(),
});
}
Ok(true)
}
#[spec_locked("5.4.5")]
#[cfg(feature = "production")]
#[inline(always)]
pub(crate) fn script_num_decode(data: &[u8], max_num_size: usize) -> Result<i64> {
if data.len() > max_num_size {
return Err(ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::InvalidStackOperation,
message: format!(
"Script number overflow: {} > {} bytes",
data.len(),
max_num_size
)
.into(),
});
}
if data.is_empty() {
return Ok(0);
}
let len = data.len();
let result = match len {
1 => {
let byte = data[0];
if byte & 0x80 != 0 {
-((byte & 0x7f) as i64)
} else {
byte as i64
}
}
2 => {
let byte0 = data[0] as i64;
let byte1 = data[1] as i64;
let value = byte0 | (byte1 << 8);
if byte1 & 0x80 != 0 {
-(value & !(0x80i64 << 8))
} else {
value
}
}
_ => {
let mut result: i64 = 0;
for (i, &byte) in data.iter().enumerate() {
result |= (byte as i64) << (8 * i);
}
let last_idx = len - 1;
if data[last_idx] & 0x80 != 0 {
result &= !(0x80i64 << (8 * last_idx));
result = -result;
}
result
}
};
Ok(result)
}
#[cfg(not(feature = "production"))]
#[inline]
pub(crate) fn script_num_decode(data: &[u8], max_num_size: usize) -> Result<i64> {
if data.len() > max_num_size {
return Err(ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::InvalidStackOperation,
message: format!(
"Script number overflow: {} > {} bytes",
data.len(),
max_num_size
)
.into(),
});
}
if data.is_empty() {
return Ok(0);
}
let mut result: i64 = 0;
for (i, &byte) in data.iter().enumerate() {
result |= (byte as i64) << (8 * i);
}
if data.last().expect("Data is not empty") & 0x80 != 0 {
result &= !(0x80i64 << (8 * (data.len() - 1)));
result = -result;
}
Ok(result)
}
#[cfg(feature = "production")]
pub(crate) fn script_num_encode(value: i64) -> Vec<u8> {
match value {
0 => return vec![],
1 => return vec![1],
-1 => return vec![0x81],
_ => {}
}
let neg = value < 0;
let mut absvalue = if neg {
(-(value as i128)) as u64
} else {
value as u64
};
let mut result = Vec::with_capacity(4);
while absvalue > 0 {
result.push((absvalue & 0xff) as u8);
absvalue >>= 8;
}
if result.last().expect("Result is not empty (absvalue > 0)") & 0x80 != 0 {
result.push(if neg { 0x80 } else { 0x00 });
} else if neg {
*result.last_mut().unwrap() |= 0x80;
}
result
}
#[cfg(not(feature = "production"))]
pub(crate) fn script_num_encode(value: i64) -> Vec<u8> {
if value == 0 {
return vec![];
}
let neg = value < 0;
let mut absvalue = if neg {
(-(value as i128)) as u64
} else {
value as u64
};
let mut result = Vec::new();
while absvalue > 0 {
result.push((absvalue & 0xff) as u8);
absvalue >>= 8;
}
if result.last().expect("Result is not empty (absvalue > 0)") & 0x80 != 0 {
result.push(if neg { 0x80 } else { 0x00 });
} else if neg {
*result.last_mut().unwrap() |= 0x80;
}
result
}
#[cfg(feature = "production")]
#[inline(always)]
fn execute_opcode(
opcode: u8,
stack: &mut Vec<StackElement>,
flags: u32,
_sigversion: SigVersion,
) -> Result<bool> {
match opcode {
OP_0 => {
stack.push(to_stack_element(&[]));
Ok(true)
}
OP_1..=OP_16 => {
let num = opcode - OP_N_BASE;
stack.push(to_stack_element(&[num]));
Ok(true)
}
OP_NOP => Ok(true),
OP_VER => Ok(false),
OP_DEPTH => {
let depth = stack.len() as i64;
stack.push(to_stack_element(&script_num_encode(depth)));
Ok(true)
}
OP_DUP => {
if let Some(item) = stack.last().cloned() {
stack.push(item);
Ok(true)
} else {
Ok(false)
}
}
OP_RIPEMD160 => crypto_ops::op_ripemd160(stack),
OP_SHA1 => crypto_ops::op_sha1(stack),
OP_SHA256 => crypto_ops::op_sha256(stack),
OP_HASH160 => crypto_ops::op_hash160(stack),
OP_HASH256 => crypto_ops::op_hash256(stack),
OP_EQUAL => {
if stack.len() < 2 {
return Err(ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::InvalidStackOperation,
message: "OP_EQUAL: insufficient stack items".into(),
});
}
let a = stack.pop().unwrap();
let b = stack.pop().unwrap();
stack.push(to_stack_element(&[if a == b { 1 } else { 0 }]));
Ok(true)
}
OP_EQUALVERIFY => {
if stack.len() < 2 {
return Err(ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::InvalidStackOperation,
message: "OP_EQUALVERIFY: insufficient stack items".into(),
});
}
let a = stack.pop().unwrap();
let b = stack.pop().unwrap();
let f_equal = a == b;
stack.push(to_stack_element(&[if f_equal { 1 } else { 0 }]));
if f_equal {
stack.pop();
Ok(true)
} else {
Err(ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::EqualVerify,
message: "OP_EQUALVERIFY: stack items not equal".into(),
})
}
}
OP_CHECKSIG => crypto_ops::op_checksig_simple(stack, flags),
OP_CHECKSIGVERIFY => crypto_ops::op_checksigverify_simple(stack, flags),
OP_RETURN => Ok(false),
OP_VERIFY => {
if let Some(item) = stack.pop() {
Ok(cast_to_bool(&item))
} else {
Ok(false)
}
}
OP_CHECKLOCKTIMEVERIFY => {
Ok(false)
}
OP_CHECKSEQUENCEVERIFY => {
Ok(false)
}
OP_IFDUP => {
if let Some(item) = stack.last().cloned() {
if cast_to_bool(&item) {
stack.push(item);
}
Ok(true)
} else {
Ok(false)
}
}
OP_DROP => {
if stack.pop().is_some() {
Ok(true)
} else {
Ok(false)
}
}
OP_NIP => {
if stack.len() >= 2 {
let top = stack.pop().unwrap();
stack.pop(); stack.push(top);
Ok(true)
} else {
Ok(false)
}
}
OP_OVER => {
if stack.len() >= 2 {
let len = stack.len();
#[cfg(feature = "production")]
{
unsafe {
let second = stack.get_unchecked(len - 2);
stack.push(second.clone());
}
}
#[cfg(not(feature = "production"))]
{
let second = stack[stack.len() - 2].clone();
stack.push(second);
}
Ok(true)
} else {
Ok(false)
}
}
OP_PICK => {
if let Some(n_bytes) = stack.pop() {
let n_val = script_num_decode(&n_bytes, 4)?;
if n_val < 0 || n_val as usize >= stack.len() {
return Ok(false);
}
let n = n_val as usize;
let len = stack.len();
#[cfg(feature = "production")]
{
unsafe {
let item = stack.get_unchecked(len - 1 - n);
stack.push(item.clone());
}
}
#[cfg(not(feature = "production"))]
{
let item = stack[stack.len() - 1 - n].clone();
stack.push(item);
}
Ok(true)
} else {
Ok(false)
}
}
OP_ROLL => {
if let Some(n_bytes) = stack.pop() {
let n_val = script_num_decode(&n_bytes, 4)?;
if n_val < 0 || n_val as usize >= stack.len() {
return Ok(false);
}
let n = n_val as usize;
let len = stack.len();
#[cfg(feature = "production")]
{
let idx = len - 1 - n;
let item = stack.remove(idx);
stack.push(item);
}
#[cfg(not(feature = "production"))]
{
let item = stack.remove(stack.len() - 1 - n);
stack.push(item);
}
Ok(true)
} else {
Ok(false)
}
}
OP_ROT => {
if stack.len() >= 3 {
let top = stack.pop().unwrap();
let second = stack.pop().unwrap();
let third = stack.pop().unwrap();
stack.push(second);
stack.push(top);
stack.push(third);
Ok(true)
} else {
Ok(false)
}
}
OP_SWAP => {
if stack.len() >= 2 {
let top = stack.pop().unwrap();
let second = stack.pop().unwrap();
stack.push(top);
stack.push(second);
Ok(true)
} else {
Ok(false)
}
}
OP_TUCK => {
if stack.len() >= 2 {
let top = stack.pop().unwrap();
let second = stack.pop().unwrap();
stack.push(top.clone());
stack.push(second);
stack.push(top);
Ok(true)
} else {
Ok(false)
}
}
OP_2DROP => {
if stack.len() >= 2 {
stack.pop();
stack.pop();
Ok(true)
} else {
Ok(false)
}
}
OP_2DUP => {
if stack.len() >= 2 {
let top = stack[stack.len() - 1].clone();
let second = stack[stack.len() - 2].clone();
stack.push(second);
stack.push(top);
Ok(true)
} else {
Ok(false)
}
}
OP_3DUP => {
if stack.len() >= 3 {
let top = stack[stack.len() - 1].clone();
let second = stack[stack.len() - 2].clone();
let third = stack[stack.len() - 3].clone();
stack.push(third);
stack.push(second);
stack.push(top);
Ok(true)
} else {
Ok(false)
}
}
OP_2OVER => {
if stack.len() >= 4 {
let fourth = stack[stack.len() - 4].clone();
let third = stack[stack.len() - 3].clone();
stack.push(fourth);
stack.push(third);
Ok(true)
} else {
Ok(false)
}
}
OP_2ROT => {
if stack.len() >= 6 {
let sixth = stack.remove(stack.len() - 6);
let fifth = stack.remove(stack.len() - 5);
stack.push(fifth);
stack.push(sixth);
Ok(true)
} else {
Ok(false)
}
}
OP_2SWAP => {
if stack.len() >= 4 {
let top = stack.pop().unwrap();
let second = stack.pop().unwrap();
let third = stack.pop().unwrap();
let fourth = stack.pop().unwrap();
stack.push(second);
stack.push(top);
stack.push(fourth);
stack.push(third);
Ok(true)
} else {
Ok(false)
}
}
OP_SIZE => {
if let Some(item) = stack.last() {
let size = item.len() as i64;
stack.push(to_stack_element(&script_num_encode(size)));
Ok(true)
} else {
Ok(false)
}
}
OP_1ADD => {
if let Some(item) = stack.pop() {
let a = script_num_decode(&item, 4)?;
stack.push(to_stack_element(&script_num_encode(a + 1)));
Ok(true)
} else {
Ok(false)
}
}
OP_1SUB => {
if let Some(item) = stack.pop() {
let a = script_num_decode(&item, 4)?;
stack.push(to_stack_element(&script_num_encode(a - 1)));
Ok(true)
} else {
Ok(false)
}
}
OP_2MUL => Err(ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::DisabledOpcode,
message: "OP_2MUL is disabled".into(),
}),
OP_2DIV => Err(ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::DisabledOpcode,
message: "OP_2DIV is disabled".into(),
}),
OP_NEGATE => {
if let Some(item) = stack.pop() {
let a = script_num_decode(&item, 4)?;
stack.push(to_stack_element(&script_num_encode(-a)));
Ok(true)
} else {
Ok(false)
}
}
OP_ABS => {
if let Some(item) = stack.pop() {
let a = script_num_decode(&item, 4)?;
stack.push(to_stack_element(&script_num_encode(a.abs())));
Ok(true)
} else {
Ok(false)
}
}
OP_NOT => {
if let Some(item) = stack.pop() {
let a = script_num_decode(&item, 4)?;
stack.push(to_stack_element(&script_num_encode(if a == 0 {
1
} else {
0
})));
Ok(true)
} else {
Ok(false)
}
}
OP_0NOTEQUAL => {
if let Some(item) = stack.pop() {
let a = script_num_decode(&item, 4)?;
stack.push(to_stack_element(&script_num_encode(if a != 0 {
1
} else {
0
})));
Ok(true)
} else {
Ok(false)
}
}
OP_ADD => arithmetic::op_add(stack),
OP_SUB => arithmetic::op_sub(stack),
OP_MUL => arithmetic::op_mul_disabled(),
OP_DIV => arithmetic::op_div_disabled(),
OP_MOD => arithmetic::op_mod_disabled(),
OP_LSHIFT => arithmetic::op_lshift_disabled(),
OP_RSHIFT => arithmetic::op_rshift_disabled(),
OP_BOOLAND => arithmetic::op_booland(stack),
OP_BOOLOR => arithmetic::op_boolor(stack),
OP_NUMEQUAL => arithmetic::op_numequal(stack),
OP_NUMEQUALVERIFY => arithmetic::op_numequalverify(stack),
OP_NUMNOTEQUAL => arithmetic::op_numnotequal(stack),
OP_LESSTHAN => arithmetic::op_lessthan(stack),
OP_GREATERTHAN => arithmetic::op_greaterthan(stack),
OP_LESSTHANOREQUAL => arithmetic::op_lessthanorequal(stack),
OP_GREATERTHANOREQUAL => arithmetic::op_greaterthanorequal(stack),
OP_MIN => arithmetic::op_min(stack),
OP_MAX => arithmetic::op_max(stack),
OP_WITHIN => arithmetic::op_within(stack),
OP_CODESEPARATOR => Ok(true),
OP_NOP1 | OP_NOP5..=OP_NOP10 => Ok(true),
OP_CHECKTEMPLATEVERIFY => {
#[cfg(not(feature = "ctv"))]
{
Ok(true)
}
#[cfg(feature = "ctv")]
{
return Err(ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::TxInvalid,
message: "OP_CHECKTEMPLATEVERIFY requires transaction context".into(),
});
}
}
OP_DISABLED_STRING_RANGE_START..=OP_DISABLED_STRING_RANGE_END
| OP_DISABLED_BITWISE_RANGE_START..=OP_DISABLED_BITWISE_RANGE_END => {
Err(ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::DisabledOpcode,
message: format!("Disabled opcode 0x{opcode:02x}").into(),
})
}
_ => Ok(false),
}
}
#[allow(dead_code)]
fn execute_opcode_with_context(
opcode: u8,
stack: &mut Vec<StackElement>,
flags: u32,
tx: &Transaction,
input_index: usize,
prevouts: &[TransactionOutput],
network: crate::types::Network,
) -> Result<bool> {
let prevout_values: Vec<i64> = prevouts.iter().map(|p| p.value).collect();
let prevout_script_pubkeys: Vec<&[u8]> =
prevouts.iter().map(|p| p.script_pubkey.as_ref()).collect();
let ctx = context::ScriptContext {
tx,
input_index,
prevout_values: &prevout_values,
prevout_script_pubkeys: &prevout_script_pubkeys,
block_height: None,
median_time_past: None,
network,
sigversion: SigVersion::Base,
redeem_script_for_sighash: None,
script_sig_for_sighash: None,
tapscript_for_sighash: None,
tapscript_codesep_pos: None,
#[cfg(feature = "production")]
schnorr_collector: None,
#[cfg(feature = "production")]
precomputed_bip143: None,
#[cfg(feature = "production")]
sighash_cache: None,
};
execute_opcode_with_context_full(opcode, stack, flags, &ctx, None)
}
#[cfg(feature = "production")]
#[inline(always)]
pub(crate) fn parse_p2sh_p2pkh_for_precompute(script_sig: &[u8]) -> Option<(u8, &[u8])> {
let mut i = 0;
let (adv1, s_start, s_end) = parse_one_data_push(script_sig, i)?;
i += adv1;
if i >= script_sig.len() {
return None;
}
let (adv2, _p_start, _p_end) = parse_one_data_push(script_sig, i)?;
i += adv2;
if i >= script_sig.len() {
return None;
}
let (adv3, r_start, r_end) = parse_one_data_push(script_sig, i)?;
i += adv3;
if i != script_sig.len() {
return None;
}
let sig = &script_sig[s_start..s_end];
let redeem = &script_sig[r_start..r_end];
if sig.is_empty() || redeem.len() != 25 {
return None;
}
if redeem[0] != OP_DUP
|| redeem[1] != OP_HASH160
|| redeem[2] != PUSH_20_BYTES
|| redeem[23] != OP_EQUALVERIFY
|| redeem[24] != OP_CHECKSIG
{
return None;
}
Some((sig[sig.len() - 1], redeem))
}
#[inline(always)]
pub(crate) fn parse_p2pkh_script_sig(script_sig: &[u8]) -> Option<(&[u8], &[u8])> {
let mut i = 0;
let (adv1, s_start, s_end) = parse_one_data_push(script_sig, i)?;
i += adv1;
if i >= script_sig.len() {
return None;
}
let (adv2, p_start, p_end) = parse_one_data_push(script_sig, i)?;
i += adv2;
if i != script_sig.len() {
return None;
}
Some((&script_sig[s_start..s_end], &script_sig[p_start..p_end]))
}
pub(crate) fn parse_p2pk_script_sig(script_sig: &[u8]) -> Option<&[u8]> {
let (advance, data_start, data_end) = parse_one_data_push(script_sig, 0)?;
if advance != script_sig.len() {
return None;
}
Some(&script_sig[data_start..data_end])
}
fn parse_one_data_push(script: &[u8], i: usize) -> Option<(usize, usize, usize)> {
if i >= script.len() {
return None;
}
let opcode = script[i];
let (advance, data_start, data_end) = if opcode == OP_0 {
return None;
} else if opcode <= 0x4b {
let len = opcode as usize;
if i + 1 + len > script.len() {
return None;
}
(1 + len, i + 1, i + 1 + len)
} else if opcode == OP_PUSHDATA1 {
if i + 1 >= script.len() {
return None;
}
let len = script[i + 1] as usize;
if i + 2 + len > script.len() {
return None;
}
(2 + len, i + 2, i + 2 + len)
} else if opcode == OP_PUSHDATA2 {
if i + 2 >= script.len() {
return None;
}
let len = u16::from_le_bytes([script[i + 1], script[i + 2]]) as usize;
if i + 3 + len > script.len() {
return None;
}
(3 + len, i + 3, i + 3 + len)
} else if opcode == OP_PUSHDATA4 {
if i + 4 >= script.len() {
return None;
}
let len = u32::from_le_bytes([script[i + 1], script[i + 2], script[i + 3], script[i + 4]])
as usize;
if i + 5 + len > script.len() {
return None;
}
(5 + len, i + 5, i + 5 + len)
} else {
return None;
};
Some((advance, data_start, data_end))
}
#[spec_locked("5.2.1")]
pub fn p2sh_push_only_check(script_sig: &[u8]) -> bool {
parse_script_sig_push_only(script_sig).is_some()
}
fn parse_script_sig_push_only(script_sig: &[u8]) -> Option<Vec<StackElement>> {
let mut out = Vec::new();
let mut i = 0;
while i < script_sig.len() {
let opcode = script_sig[i];
if !is_push_opcode(opcode) {
return None;
}
let (advance, data) = if opcode == OP_0 {
(1, vec![])
} else if opcode <= 0x4b {
let len = opcode as usize;
if i + 1 + len > script_sig.len() {
return None;
}
(1 + len, script_sig[i + 1..i + 1 + len].to_vec())
} else if opcode == OP_PUSHDATA1 {
if i + 1 >= script_sig.len() {
return None;
}
let len = script_sig[i + 1] as usize;
if i + 2 + len > script_sig.len() {
return None;
}
(2 + len, script_sig[i + 2..i + 2 + len].to_vec())
} else if opcode == OP_PUSHDATA2 {
if i + 2 >= script_sig.len() {
return None;
}
let len = u16::from_le_bytes([script_sig[i + 1], script_sig[i + 2]]) as usize;
if i + 3 + len > script_sig.len() {
return None;
}
(3 + len, script_sig[i + 3..i + 3 + len].to_vec())
} else if opcode == OP_PUSHDATA4 {
if i + 4 >= script_sig.len() {
return None;
}
let len = u32::from_le_bytes([
script_sig[i + 1],
script_sig[i + 2],
script_sig[i + 3],
script_sig[i + 4],
]) as usize;
if i + 5 + len > script_sig.len() {
return None;
}
(5 + len, script_sig[i + 5..i + 5 + len].to_vec())
} else if (OP_1NEGATE..=OP_16).contains(&opcode) {
let n = script_num_from_opcode(opcode);
(1, script_num_encode(n))
} else {
return None;
};
out.push(to_stack_element(&data));
i += advance;
}
Some(out)
}
fn parse_p2sh_script_sig_pushes(script_sig: &[u8]) -> Option<Vec<StackElement>> {
parse_script_sig_push_only(script_sig)
}
fn parse_redeem_multisig(redeem: &[u8]) -> Option<(u8, u8, Vec<&[u8]>)> {
if redeem.len() < 4 {
return None;
}
let n_op = redeem[0];
if !(OP_1..=OP_16).contains(&n_op) {
return None;
}
let n = (n_op - OP_1 + 1) as usize;
let mut i = 1;
let mut pubkeys = Vec::with_capacity(n);
for _ in 0..n {
if i >= redeem.len() {
return None;
}
let first = redeem[i];
let pk_len = if first == 0x02 || first == 0x03 {
33
} else if first == 0x04 {
65
} else {
return None;
};
if i + pk_len > redeem.len() {
return None;
}
pubkeys.push(&redeem[i..i + pk_len]);
i += pk_len;
}
if i + 2 > redeem.len() {
return None;
}
let m_op = redeem[i];
if !(OP_1..=OP_16).contains(&m_op) {
return None;
}
let m = m_op - OP_1 + 1;
if redeem[i + 1] != OP_CHECKMULTISIG {
return None;
}
Some((m, n as u8, pubkeys))
}
fn script_num_from_opcode(opcode: u8) -> i64 {
match opcode {
OP_1NEGATE => -1,
OP_1 => 1,
OP_2 => 2,
OP_3 => 3,
OP_4 => 4,
OP_5 => 5,
OP_6 => 6,
OP_7 => 7,
OP_8 => 8,
OP_9 => 9,
OP_10 => 10,
OP_11 => 11,
OP_12 => 12,
OP_13 => 13,
OP_14 => 14,
OP_15 => 15,
OP_16 => 16,
_ => 0,
}
}
pub(crate) fn serialize_push_data(data: &[u8]) -> Vec<u8> {
let len = data.len();
let mut result = Vec::with_capacity(len + 5);
if len < 76 {
result.push(len as u8);
} else if len < 256 {
result.push(OP_PUSHDATA1);
result.push(len as u8);
} else if len < 65536 {
result.push(OP_PUSHDATA2);
result.push((len & 0xff) as u8);
result.push(((len >> 8) & 0xff) as u8);
} else {
result.push(OP_PUSHDATA4);
result.push((len & 0xff) as u8);
result.push(((len >> 8) & 0xff) as u8);
result.push(((len >> 16) & 0xff) as u8);
result.push(((len >> 24) & 0xff) as u8);
}
result.extend_from_slice(data);
result
}
#[inline]
fn script_get_op_advance(script: &[u8], pc: usize) -> Option<usize> {
if pc >= script.len() {
return None;
}
let opcode = script[pc];
let advance = if opcode <= 0x4b {
1 + opcode as usize
} else if opcode == OP_PUSHDATA1 && pc + 1 < script.len() {
2 + script[pc + 1] as usize
} else if opcode == OP_PUSHDATA2 && pc + 2 < script.len() {
3 + ((script[pc + 1] as usize) | ((script[pc + 2] as usize) << 8))
} else if opcode == OP_PUSHDATA4 && pc + 4 < script.len() {
5 + ((script[pc + 1] as usize)
| ((script[pc + 2] as usize) << 8)
| ((script[pc + 3] as usize) << 16)
| ((script[pc + 4] as usize) << 24))
} else {
1
};
let next = pc + advance;
if next > script.len() {
None
} else {
Some(next)
}
}
#[spec_locked("5.1.1")]
#[inline]
pub(crate) fn find_and_delete<'a>(script: &'a [u8], pattern: &[u8]) -> std::borrow::Cow<'a, [u8]> {
if pattern.is_empty() {
return std::borrow::Cow::Borrowed(script);
}
let end = script.len();
let mut n_found = 0usize;
let mut result = Vec::new();
let mut pc = 0usize;
let mut pc2 = 0usize;
loop {
result.extend_from_slice(&script[pc2..pc]);
while end - pc >= pattern.len() && script[pc..pc + pattern.len()] == *pattern {
pc += pattern.len();
n_found += 1;
}
pc2 = pc;
if pc >= end {
break;
}
let Some(next_pc) = script_get_op_advance(script, pc) else {
break;
};
pc = next_pc;
}
if n_found > 0 {
result.extend_from_slice(&script[pc2..end]);
std::borrow::Cow::Owned(result)
} else {
std::borrow::Cow::Borrowed(script)
}
}
fn opcode_position_at_byte(script: &[u8], byte_index: usize) -> u32 {
let mut pos = 0u32;
let mut i = 0usize;
while i < script.len() && i <= byte_index {
let opcode = script[i];
let advance = if opcode <= 0x4b {
1 + opcode as usize
} else if opcode == OP_PUSHDATA1 && i + 1 < script.len() {
2 + script[i + 1] as usize
} else if opcode == OP_PUSHDATA2 && i + 2 < script.len() {
3 + ((script[i + 1] as usize) | ((script[i + 2] as usize) << 8))
} else if opcode == OP_PUSHDATA4 && i + 4 < script.len() {
5 + ((script[i + 1] as usize)
| ((script[i + 2] as usize) << 8)
| ((script[i + 3] as usize) << 16)
| ((script[i + 4] as usize) << 24))
} else {
1
};
if i == byte_index {
return pos;
}
pos += 1;
i = std::cmp::min(i + advance, script.len());
}
0xffff_ffff
}
#[cfg_attr(feature = "production", inline(always))]
fn execute_opcode_with_context_full(
opcode: u8,
stack: &mut Vec<StackElement>,
flags: u32,
ctx: &context::ScriptContext<'_>,
effective_script_code: Option<&[u8]>,
) -> Result<bool> {
let tx = ctx.tx;
let input_index = ctx.input_index;
let prevout_values = ctx.prevout_values;
let prevout_script_pubkeys = ctx.prevout_script_pubkeys;
let block_height = ctx.block_height;
let median_time_past = ctx.median_time_past;
let network = ctx.network;
let sigversion = ctx.sigversion;
let script_sig_for_sighash = ctx.script_sig_for_sighash;
let tapscript_for_sighash = ctx.tapscript_for_sighash;
let tapscript_codesep_pos = ctx.tapscript_codesep_pos;
let redeem_script_for_sighash = effective_script_code;
#[cfg(feature = "production")]
let schnorr_collector = ctx.schnorr_collector;
#[cfg(feature = "production")]
let precomputed_bip143 = ctx.precomputed_bip143;
#[cfg(feature = "production")]
let sighash_cache = ctx.sighash_cache;
match opcode {
OP_CHECKSIG => {
if stack.len() >= 2 {
let pubkey_bytes = stack.pop().unwrap();
let signature_bytes = stack.pop().unwrap();
if signature_bytes.is_empty() {
stack.push(to_stack_element(&[0]));
return Ok(true);
}
if sigversion == SigVersion::Tapscript {
if signature_bytes.len() == 64 && pubkey_bytes.len() == 32 {
let sighash_byte = 0x00;
let (tapscript, codesep_pos) = tapscript_for_sighash
.map(|s| (s, tapscript_codesep_pos.unwrap_or(0xffff_ffff)))
.unwrap_or((&[] as &[u8], 0xffff_ffff));
let sighash = if tapscript.is_empty() {
crate::taproot::compute_taproot_signature_hash(
tx,
input_index,
prevout_values,
prevout_script_pubkeys,
sighash_byte,
)?
} else {
crate::taproot::compute_tapscript_signature_hash(
tx,
input_index,
prevout_values,
prevout_script_pubkeys,
tapscript,
crate::taproot::TAPROOT_LEAF_VERSION_TAPSCRIPT,
codesep_pos,
sighash_byte,
)?
};
#[cfg(feature = "production")]
let is_valid = {
use crate::bip348::verify_tapscript_schnorr_signature;
verify_tapscript_schnorr_signature(
&sighash,
&pubkey_bytes,
&signature_bytes,
schnorr_collector,
)
.unwrap_or(false)
};
#[cfg(not(feature = "production"))]
let is_valid = {
#[cfg(feature = "csfs")]
let x = {
use crate::bip348::verify_tapscript_schnorr_signature;
verify_tapscript_schnorr_signature(
&sighash,
&pubkey_bytes,
&signature_bytes,
None,
)
.unwrap_or(false)
};
#[cfg(not(feature = "csfs"))]
let x = false;
x
};
stack.push(to_stack_element(&[if is_valid { 1 } else { 0 }]));
return Ok(true);
}
}
let sig_len = signature_bytes.len();
let sighash_byte = signature_bytes[sig_len - 1];
let _der_sig = &signature_bytes[..sig_len - 1];
let sighash = if sigversion == SigVersion::WitnessV0 {
let amount = prevout_values.get(input_index).copied().unwrap_or(0);
let script_code = redeem_script_for_sighash.unwrap_or_else(|| {
prevout_script_pubkeys
.get(input_index)
.copied()
.unwrap_or(&[])
});
crate::transaction_hash::calculate_bip143_sighash(
tx,
input_index,
script_code,
amount,
sighash_byte,
precomputed_bip143,
)?
} else {
use crate::transaction_hash::{
calculate_transaction_sighash_single_input, SighashType,
};
let sighash_type = SighashType::from_byte(sighash_byte);
let pattern = serialize_push_data(signature_bytes.as_ref());
let base_script = match (
redeem_script_for_sighash,
prevout_script_pubkeys.get(input_index),
) {
(Some(redeem), Some(prevout)) if redeem == *prevout => *prevout,
(Some(redeem), _) => redeem,
(None, Some(prevout)) => *prevout,
(None, None) => &[],
};
let cleaned = find_and_delete(base_script, &pattern);
calculate_transaction_sighash_single_input(
tx,
input_index,
cleaned.as_ref(),
prevout_values[input_index],
sighash_type,
#[cfg(feature = "production")]
sighash_cache,
)?
};
let height = block_height.unwrap_or(0);
#[cfg(feature = "production")]
let is_valid = signature::with_secp_context(|secp| {
signature::verify_signature(
secp,
&pubkey_bytes,
&signature_bytes, &sighash,
flags,
height,
network,
sigversion,
)
})?;
#[cfg(not(feature = "production"))]
let is_valid = {
let secp = Secp256k1::new();
signature::verify_signature(
&secp,
&pubkey_bytes,
&signature_bytes, &sighash,
flags,
height,
network,
sigversion,
)?
};
stack.push(to_stack_element(&[if is_valid { 1 } else { 0 }]));
Ok(true)
} else {
Ok(false)
}
}
OP_CHECKSIGVERIFY => {
if stack.len() >= 2 {
let pubkey_bytes = stack.pop().unwrap();
let signature_bytes = stack.pop().unwrap();
if signature_bytes.is_empty() {
return Ok(false);
}
let sig_len = signature_bytes.len();
let sighash_byte = signature_bytes[sig_len - 1];
let _der_sig = &signature_bytes[..sig_len - 1];
let sighash = if sigversion == SigVersion::WitnessV0 {
let amount = prevout_values.get(input_index).copied().unwrap_or(0);
let script_code = redeem_script_for_sighash.unwrap_or_else(|| {
prevout_script_pubkeys
.get(input_index)
.copied()
.unwrap_or(&[])
});
crate::transaction_hash::calculate_bip143_sighash(
tx,
input_index,
script_code,
amount,
sighash_byte,
precomputed_bip143,
)?
} else {
use crate::transaction_hash::{
calculate_transaction_sighash_single_input, SighashType,
};
let sighash_type = SighashType::from_byte(sighash_byte);
let pattern = serialize_push_data(signature_bytes.as_ref());
let base_script = match (
redeem_script_for_sighash,
prevout_script_pubkeys.get(input_index),
) {
(Some(redeem), Some(prevout)) if redeem == *prevout => *prevout,
(Some(redeem), _) => redeem,
(None, Some(prevout)) => *prevout,
(None, None) => &[],
};
let cleaned = find_and_delete(base_script, &pattern);
calculate_transaction_sighash_single_input(
tx,
input_index,
cleaned.as_ref(),
prevout_values[input_index],
sighash_type,
#[cfg(feature = "production")]
sighash_cache,
)?
};
let height = block_height.unwrap_or(0);
#[cfg(feature = "production")]
let is_valid = signature::with_secp_context(|secp| {
signature::verify_signature(
secp,
&pubkey_bytes,
&signature_bytes, &sighash,
flags,
height,
network,
sigversion,
)
})?;
#[cfg(not(feature = "production"))]
let is_valid = {
let secp = Secp256k1::new();
signature::verify_signature(
&secp,
&pubkey_bytes,
&signature_bytes, &sighash,
flags,
height,
network,
sigversion,
)?
};
if is_valid {
Ok(true)
} else {
Ok(false)
}
} else {
Ok(false)
}
}
OP_CHECKSIGADD => {
if sigversion != SigVersion::Tapscript {
return Err(ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::DisabledOpcode,
message: "OP_CHECKSIGADD is only available in Tapscript".into(),
});
}
if stack.len() < 3 {
return Err(ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::InvalidStackOperation,
message: "OP_CHECKSIGADD: insufficient stack items (need 3)".into(),
});
}
let pubkey_bytes = stack.pop().unwrap();
let n_bytes = stack.pop().unwrap();
let signature_bytes = stack.pop().unwrap();
let n = script_num_decode(&n_bytes, 4)?;
if signature_bytes.is_empty() {
stack.push(to_stack_element(&script_num_encode(n)));
return Ok(true);
}
if pubkey_bytes.len() == 32 && signature_bytes.len() == 64 {
let sighash_byte = 0x00;
let (tapscript, codesep_pos) = tapscript_for_sighash
.map(|s| (s, tapscript_codesep_pos.unwrap_or(0xffff_ffff)))
.unwrap_or((&[] as &[u8], 0xffff_ffff));
let sighash = if tapscript.is_empty() {
crate::taproot::compute_taproot_signature_hash(
tx,
input_index,
prevout_values,
prevout_script_pubkeys,
sighash_byte,
)?
} else {
crate::taproot::compute_tapscript_signature_hash(
tx,
input_index,
prevout_values,
prevout_script_pubkeys,
tapscript,
crate::taproot::TAPROOT_LEAF_VERSION_TAPSCRIPT,
codesep_pos,
sighash_byte,
)?
};
#[cfg(feature = "production")]
let is_valid = {
use crate::bip348::verify_tapscript_schnorr_signature;
verify_tapscript_schnorr_signature(
&sighash,
&pubkey_bytes,
&signature_bytes,
schnorr_collector,
)
.unwrap_or(false)
};
#[cfg(not(feature = "production"))]
let is_valid = {
#[cfg(feature = "csfs")]
let x = {
use crate::bip348::verify_tapscript_schnorr_signature;
verify_tapscript_schnorr_signature(
&sighash,
&pubkey_bytes,
&signature_bytes,
None,
)
.unwrap_or(false)
};
#[cfg(not(feature = "csfs"))]
let x = false;
x
};
if !is_valid {
return Ok(false); }
stack.push(to_stack_element(&script_num_encode(n + 1)));
return Ok(true);
}
stack.push(to_stack_element(&script_num_encode(n + 1)));
Ok(true)
}
OP_CHECKMULTISIG => {
if stack.len() < 2 {
return Ok(false);
}
let n_bytes = stack.pop().unwrap();
let n = if n_bytes.is_empty() {
0
} else {
n_bytes[0] as usize
};
if n > 20 || stack.len() < n + 1 {
return Ok(false);
}
let mut pubkeys = Vec::with_capacity(n);
for _ in 0..n {
pubkeys.push(stack.pop().unwrap());
}
let m_bytes = stack.pop().unwrap();
let m = if m_bytes.is_empty() {
0
} else {
m_bytes[0] as usize
};
if m > n || m > 20 || stack.len() < m + 1 {
return Ok(false);
}
let mut signatures = Vec::with_capacity(m);
for _ in 0..m {
signatures.push(stack.pop().unwrap());
}
let dummy = stack.pop().unwrap();
if flags & 0x10 != 0 {
let height = block_height.unwrap_or(0);
use crate::bip_validation::Bip147Network;
let bip147_network = match network {
crate::types::Network::Mainnet => Bip147Network::Mainnet,
crate::types::Network::Testnet => Bip147Network::Testnet,
crate::types::Network::Regtest => Bip147Network::Regtest,
};
use crate::constants::{BIP147_ACTIVATION_MAINNET, BIP147_ACTIVATION_TESTNET};
let bip147_active = height
>= match bip147_network {
Bip147Network::Mainnet => BIP147_ACTIVATION_MAINNET,
Bip147Network::Testnet => BIP147_ACTIVATION_TESTNET,
Bip147Network::Regtest => 0,
};
if bip147_active {
let is_empty = dummy.is_empty() || dummy.as_ref() == [0x00];
if !is_empty {
return Err(ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::SigNullDummy,
message: format!(
"OP_CHECKMULTISIG: dummy element {dummy:?} violates BIP147 NULLDUMMY (must be empty: [] or [0x00])"
)
.into(),
});
}
}
}
let height = block_height.unwrap_or(0);
let cleaned_script_for_multisig: Vec<u8> = if sigversion == SigVersion::Base {
let base_script = match (
redeem_script_for_sighash,
prevout_script_pubkeys.get(input_index),
) {
(Some(redeem), Some(prevout)) if redeem == *prevout => *prevout,
(Some(redeem), _) => redeem,
(None, Some(prevout)) => *prevout,
(None, None) => &[],
};
let mut cleaned = base_script.to_vec();
for sig in &signatures {
if !sig.is_empty() {
let pattern = serialize_push_data(sig.as_ref());
cleaned = find_and_delete(&cleaned, &pattern).into_owned();
}
}
cleaned
} else {
redeem_script_for_sighash
.map(|s| s.to_vec())
.unwrap_or_else(|| {
prevout_script_pubkeys
.get(input_index)
.map(|p| p.to_vec())
.unwrap_or_default()
})
};
use crate::transaction_hash::{
calculate_transaction_sighash_single_input, SighashType,
};
#[cfg(feature = "production")]
let use_batch = pubkeys.len() * signatures.len() >= 4;
#[cfg(feature = "production")]
let (valid_sigs, _) = if use_batch {
let sighashes: Vec<[u8; 32]> = if sigversion == SigVersion::Base {
let non_empty: Vec<_> = signatures.iter().filter(|s| !s.is_empty()).collect();
if non_empty.is_empty() {
vec![]
} else {
let specs: Vec<(usize, u8, &[u8])> = non_empty
.iter()
.map(|s| {
(
input_index,
s.as_ref()[s.as_ref().len() - 1],
cleaned_script_for_multisig.as_ref(),
)
})
.collect();
crate::transaction_hash::batch_compute_legacy_sighashes(
tx,
prevout_values,
prevout_script_pubkeys,
&specs,
)?
}
} else {
signatures
.iter()
.filter(|s| !s.is_empty())
.map(|sig_bytes| {
let sighash_type =
SighashType::from_byte(sig_bytes[sig_bytes.len() - 1]);
calculate_transaction_sighash_single_input(
tx,
input_index,
&cleaned_script_for_multisig,
prevout_values[input_index],
sighash_type,
sighash_cache,
)
})
.collect::<Result<Vec<_>>>()?
};
let mut tasks: Vec<(&[u8], &[u8], [u8; 32])> =
Vec::with_capacity(pubkeys.len() * signatures.len());
let mut sig_idx_to_sighash_idx = Vec::with_capacity(signatures.len());
let mut sighash_idx = 0usize;
for (j, sig_bytes) in signatures.iter().enumerate() {
if sig_bytes.is_empty() {
sig_idx_to_sighash_idx.push(usize::MAX);
} else {
sig_idx_to_sighash_idx.push(sighash_idx);
let sh = sighashes[sighash_idx];
sighash_idx += 1;
for pubkey_bytes in &pubkeys {
tasks.push((pubkey_bytes.as_ref(), sig_bytes.as_ref(), sh));
}
}
}
let results = if tasks.is_empty() {
vec![]
} else {
batch_verify_signatures(&tasks, flags, height, network)?
};
let mut sig_index = 0;
let mut valid_sigs = 0usize;
for (i, _pubkey_bytes) in pubkeys.iter().enumerate() {
if sig_index >= signatures.len() {
break;
}
while sig_index < signatures.len() && signatures[sig_index].is_empty() {
sig_index += 1;
}
if sig_index >= signatures.len() {
break;
}
let sh_idx = sig_idx_to_sighash_idx[sig_index];
if sh_idx == usize::MAX {
continue;
}
let task_idx = sh_idx * pubkeys.len() + i;
if task_idx < results.len() && results[task_idx] {
valid_sigs += 1;
sig_index += 1;
}
}
const SCRIPT_VERIFY_NULLFAIL: u32 = 0x4000;
if (flags & SCRIPT_VERIFY_NULLFAIL) != 0 {
for (j, sig_bytes) in signatures.iter().enumerate() {
if sig_bytes.is_empty() {
continue;
}
let sh_idx = sig_idx_to_sighash_idx[j];
if sh_idx == usize::MAX {
continue;
}
let sig_start = sh_idx * pubkeys.len();
let sig_end = (sig_start + pubkeys.len()).min(results.len());
let matched = results[sig_start..sig_end].iter().any(|&r| r);
if !matched {
return Err(ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::SigNullFail,
message: "OP_CHECKMULTISIG: non-null signature must not fail under NULLFAIL".into(),
});
}
}
}
(valid_sigs, ())
} else {
let mut sig_index = 0;
let mut valid_sigs = 0;
for pubkey_bytes in &pubkeys {
if sig_index >= signatures.len() {
break;
}
let signature_bytes = &signatures[sig_index];
if signature_bytes.is_empty() {
continue;
}
let sig_len = signature_bytes.len();
let sighash_byte = signature_bytes[sig_len - 1];
let sighash_type = SighashType::from_byte(sighash_byte);
let sighash = calculate_transaction_sighash_single_input(
tx,
input_index,
&cleaned_script_for_multisig,
prevout_values[input_index],
sighash_type,
#[cfg(feature = "production")]
sighash_cache,
)?;
#[cfg(feature = "production")]
let is_valid = signature::with_secp_context(|secp| {
signature::verify_signature(
secp,
pubkey_bytes,
signature_bytes,
&sighash,
flags,
height,
network,
sigversion,
)
})?;
#[cfg(not(feature = "production"))]
let is_valid = {
let secp = Secp256k1::new();
signature::verify_signature(
&secp,
pubkey_bytes,
signature_bytes,
&sighash,
flags,
height,
network,
sigversion,
)?
};
const SCRIPT_VERIFY_NULLFAIL: u32 = 0x4000;
if !is_valid
&& (flags & SCRIPT_VERIFY_NULLFAIL) != 0
&& !signature_bytes.is_empty()
{
return Err(ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::SigNullFail,
message:
"OP_CHECKMULTISIG: non-null signature must not fail under NULLFAIL"
.into(),
});
}
if is_valid {
valid_sigs += 1;
sig_index += 1;
}
}
(valid_sigs, ())
};
#[cfg(not(feature = "production"))]
let (valid_sigs, _) = {
let mut sig_index = 0;
let mut valid_sigs = 0;
for pubkey_bytes in &pubkeys {
if sig_index >= signatures.len() {
break;
}
let signature_bytes = &signatures[sig_index];
if signature_bytes.is_empty() {
continue;
}
let sig_len = signature_bytes.len();
let sighash_type = SighashType::from_byte(signature_bytes[sig_len - 1]);
let sighash = calculate_transaction_sighash_single_input(
tx,
input_index,
&cleaned_script_for_multisig,
prevout_values[input_index],
sighash_type,
#[cfg(feature = "production")]
sighash_cache,
)?;
let secp = Secp256k1::new();
let is_valid = signature::verify_signature(
&secp,
pubkey_bytes,
signature_bytes,
&sighash,
flags,
height,
network,
sigversion,
)?;
const SCRIPT_VERIFY_NULLFAIL: u32 = 0x4000;
if !is_valid
&& (flags & SCRIPT_VERIFY_NULLFAIL) != 0
&& !signature_bytes.is_empty()
{
return Err(ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::SigNullFail,
message:
"OP_CHECKMULTISIG: non-null signature must not fail under NULLFAIL"
.into(),
});
}
if is_valid {
valid_sigs += 1;
sig_index += 1;
}
}
(valid_sigs, ())
};
stack.push(to_stack_element(&[if valid_sigs >= m { 1 } else { 0 }]));
Ok(true)
}
OP_CHECKMULTISIGVERIFY => {
let ctx_checkmultisig = context::ScriptContext {
tx,
input_index,
prevout_values,
prevout_script_pubkeys,
block_height,
median_time_past,
network,
sigversion,
redeem_script_for_sighash,
script_sig_for_sighash,
tapscript_for_sighash,
tapscript_codesep_pos,
#[cfg(feature = "production")]
schnorr_collector: None,
#[cfg(feature = "production")]
precomputed_bip143,
#[cfg(feature = "production")]
sighash_cache,
};
let result = execute_opcode_with_context_full(
OP_CHECKMULTISIG,
stack,
flags,
&ctx_checkmultisig,
redeem_script_for_sighash,
)?;
if !result {
return Ok(false);
}
if let Some(top) = stack.pop() {
if !cast_to_bool(&top) {
return Ok(false);
}
Ok(true)
} else {
Ok(false)
}
}
OP_CHECKLOCKTIMEVERIFY => {
const SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY: u32 = 0x200;
if (flags & SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY) == 0 {
return Ok(true);
}
use crate::locktime::{check_bip65, decode_locktime_value};
if stack.is_empty() {
return Err(ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::InvalidStackOperation,
message: "OP_CHECKLOCKTIMEVERIFY: empty stack".into(),
});
}
let locktime_bytes = stack.last().expect("Stack is not empty");
let locktime_value = match decode_locktime_value(locktime_bytes.as_ref()) {
Some(v) => v,
None => {
return Err(ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::MinimalData,
message: "OP_CHECKLOCKTIMEVERIFY: invalid locktime encoding".into(),
})
}
};
let tx_locktime = tx.lock_time as u32;
if !check_bip65(tx_locktime, locktime_value) {
return Ok(false);
}
let input_seq = if input_index < tx.inputs.len() {
tx.inputs[input_index].sequence
} else {
0xffffffff
};
if input_seq == 0xffffffff {
return Ok(false);
}
Ok(true)
}
OP_CHECKSEQUENCEVERIFY => {
use crate::locktime::{
decode_locktime_value, extract_sequence_locktime_value, extract_sequence_type_flag,
is_sequence_disabled,
};
const SCRIPT_VERIFY_CHECKSEQUENCEVERIFY: u32 = 0x400;
if (flags & SCRIPT_VERIFY_CHECKSEQUENCEVERIFY) == 0 {
return Ok(true);
}
if stack.is_empty() {
return Ok(false);
}
let sequence_bytes = stack.last().expect("Stack is not empty");
let sequence_value = match decode_locktime_value(sequence_bytes.as_ref()) {
Some(v) => v,
None => return Ok(false), };
if input_index >= tx.inputs.len() {
return Ok(false);
}
let input_sequence = tx.inputs[input_index].sequence as u32;
if is_sequence_disabled(input_sequence) {
return Ok(true);
}
let type_flag = extract_sequence_type_flag(sequence_value);
let locktime_mask = extract_sequence_locktime_value(sequence_value) as u32;
let input_type_flag = extract_sequence_type_flag(input_sequence);
let input_locktime = extract_sequence_locktime_value(input_sequence) as u32;
if type_flag != input_type_flag {
return Ok(false);
}
if input_locktime < locktime_mask {
return Ok(false);
}
Ok(true)
}
OP_CHECKTEMPLATEVERIFY => {
#[cfg(not(feature = "ctv"))]
{
const SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS: u32 = 0x10000;
if (flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS) != 0 {
return Err(ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::BadOpcode,
message: "OP_CHECKTEMPLATEVERIFY requires --features ctv".into(),
});
}
Ok(true) }
#[cfg(feature = "ctv")]
{
use crate::constants::{
CTV_ACTIVATION_MAINNET, CTV_ACTIVATION_REGTEST, CTV_ACTIVATION_TESTNET,
};
let ctv_activation = match network {
crate::types::Network::Mainnet => CTV_ACTIVATION_MAINNET,
crate::types::Network::Testnet => CTV_ACTIVATION_TESTNET,
crate::types::Network::Regtest => CTV_ACTIVATION_REGTEST,
};
let ctv_active = block_height.map(|h| h >= ctv_activation).unwrap_or(false);
if !ctv_active {
const SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS: u32 = 0x10000;
if (flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS) != 0 {
return Err(ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::BadOpcode,
message: "OP_CHECKTEMPLATEVERIFY not yet activated".into(),
});
}
return Ok(true); }
const SCRIPT_VERIFY_DEFAULT_CHECK_TEMPLATE_VERIFY_HASH: u32 = 0x80000000;
if (flags & SCRIPT_VERIFY_DEFAULT_CHECK_TEMPLATE_VERIFY_HASH) == 0 {
return Ok(true);
}
use crate::bip119::calculate_template_hash;
if stack.is_empty() {
return Err(ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::InvalidStackOperation,
message: "OP_CHECKTEMPLATEVERIFY: insufficient stack items".into(),
});
}
let template_hash_bytes = stack.pop().unwrap();
if template_hash_bytes.len() != 32 {
const SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS: u32 = 0x10000;
if (flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS) != 0 {
return Err(ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::InvalidStackOperation,
message: "OP_CHECKTEMPLATEVERIFY: template hash must be 32 bytes"
.into(),
});
}
return Ok(true); }
let mut expected_hash = [0u8; 32];
expected_hash.copy_from_slice(&template_hash_bytes);
let actual_hash = calculate_template_hash(tx, input_index).map_err(|e| {
ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::TxInvalid,
message: format!("CTV hash calculation failed: {e}").into(),
}
})?;
use crate::crypto::hash_compare::hash_eq;
let matches = hash_eq(&expected_hash, &actual_hash);
if !matches {
return Ok(false); }
Ok(true)
}
}
OP_CHECKSIGFROMSTACK => {
#[cfg(not(feature = "csfs"))]
{
const SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS: u32 = 0x10000;
if (flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS) != 0 {
return Err(ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::BadOpcode,
message: "OP_CHECKSIGFROMSTACK requires --features csfs".into(),
});
}
Ok(true) }
#[cfg(feature = "csfs")]
{
use crate::constants::{
CSFS_ACTIVATION_MAINNET, CSFS_ACTIVATION_REGTEST, CSFS_ACTIVATION_TESTNET,
};
if sigversion != SigVersion::Tapscript {
return Err(ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::BadOpcode,
message: "OP_CHECKSIGFROMSTACK only available in Tapscript".into(),
});
}
let csfs_activation = match network {
crate::types::Network::Mainnet => CSFS_ACTIVATION_MAINNET,
crate::types::Network::Testnet => CSFS_ACTIVATION_TESTNET,
crate::types::Network::Regtest => CSFS_ACTIVATION_REGTEST,
};
let csfs_active = block_height.map(|h| h >= csfs_activation).unwrap_or(false);
if !csfs_active {
const SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS: u32 = 0x10000;
if (flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS) != 0 {
return Err(ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::BadOpcode,
message: "OP_CHECKSIGFROMSTACK not yet activated".into(),
});
}
return Ok(true); }
use crate::bip348::verify_signature_from_stack;
if stack.len() < 3 {
return Err(ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::InvalidStackOperation,
message: "OP_CHECKSIGFROMSTACK: insufficient stack items (need 3)".into(),
});
}
let pubkey_bytes = stack.pop().unwrap(); let message_bytes = stack.pop().unwrap(); let signature_bytes = stack.pop().unwrap();
if pubkey_bytes.is_empty() {
return Err(ConsensusError::ScriptErrorWithCode {
code: ScriptErrorCode::PubkeyType,
message: "OP_CHECKSIGFROMSTACK: pubkey size is zero".into(),
});
}
if signature_bytes.is_empty() {
stack.push(to_stack_element(&[])); return Ok(true);
}
#[cfg(feature = "production")]
let is_valid = {
verify_signature_from_stack(
&message_bytes, &pubkey_bytes, &signature_bytes, schnorr_collector, )
.unwrap_or(false)
};
#[cfg(not(feature = "production"))]
let is_valid = verify_signature_from_stack(
&message_bytes, &pubkey_bytes, &signature_bytes, )
.unwrap_or(false);
if !is_valid {
return Ok(false);
}
stack.push(to_stack_element(&[0x01])); Ok(true)
}
}
_ => execute_opcode_cold(opcode, stack, flags),
}
}
#[cold]
fn execute_opcode_cold(opcode: u8, stack: &mut Vec<StackElement>, flags: u32) -> Result<bool> {
execute_opcode(opcode, stack, flags, SigVersion::Base)
}
#[cfg(feature = "production")]
pub(crate) fn get_and_reset_fast_path_counts() -> (u64, u64, u64, u64, u64, u64, u64, u64) {
(
FAST_PATH_P2PK.swap(0, Ordering::Relaxed),
FAST_PATH_P2PKH.swap(0, Ordering::Relaxed),
FAST_PATH_P2SH.swap(0, Ordering::Relaxed),
FAST_PATH_P2WPKH.swap(0, Ordering::Relaxed),
FAST_PATH_P2WSH.swap(0, Ordering::Relaxed),
FAST_PATH_P2TR.swap(0, Ordering::Relaxed),
FAST_PATH_BARE_MULTISIG.swap(0, Ordering::Relaxed),
FAST_PATH_INTERPRETER.swap(0, Ordering::Relaxed),
)
}
#[cfg(all(feature = "production", feature = "benchmarking"))]
pub fn clear_script_cache() {
if let Some(cache) = SCRIPT_CACHE.get() {
let mut cache = cache.write().unwrap();
cache.clear();
}
}
#[cfg(all(feature = "production", feature = "benchmarking"))]
pub fn clear_hash_cache() {
crypto_ops::clear_hash_cache();
}
#[cfg(all(feature = "production", feature = "benchmarking"))]
pub fn clear_all_caches() {
clear_script_cache();
clear_hash_cache();
}
#[cfg(all(feature = "production", feature = "benchmarking"))]
pub fn clear_stack_pool() {
STACK_POOL.with(|pool| {
let mut pool = pool.borrow_mut();
pool.clear();
});
}
#[cfg(all(feature = "production", feature = "benchmarking"))]
pub fn reset_benchmarking_state() {
clear_all_caches();
clear_stack_pool();
disable_caching(false); #[cfg(feature = "benchmarking")]
crate::transaction_hash::clear_sighash_templates();
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_eval_script_simple() {
let script = vec![OP_1]; let mut stack = Vec::new();
assert!(eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap());
assert_eq!(stack.len(), 1);
assert_eq!(stack[0].as_ref(), &[1]);
}
#[test]
fn test_eval_script_overflow() {
let script = vec![0x51; MAX_STACK_SIZE + 1]; let mut stack = Vec::new();
assert!(eval_script(&script, &mut stack, 0, SigVersion::Base).is_err());
}
#[test]
fn test_verify_script_simple() {
let _script_sig = [0x51]; let _script_pubkey = [0x51];
let script_sig = vec![0x51]; let script_pubkey = vec![0x76, 0x88];
assert!(!verify_script(&script_sig, &script_pubkey, None, 0).unwrap());
}
#[test]
fn test_op_0() {
let script = vec![OP_0]; let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
assert!(!result); assert_eq!(stack.len(), 1);
assert!(stack[0].is_empty());
}
#[test]
fn test_op_1_to_op_16() {
for i in 1..=16 {
let opcode = 0x50 + i;
let script = vec![opcode];
let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
assert!(result);
assert_eq!(stack.len(), 1);
assert_eq!(stack[0].as_ref(), &[i]);
}
}
#[test]
fn test_op_dup() {
let script = vec![0x51, 0x76]; let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
assert!(!result); assert_eq!(stack.len(), 2);
assert_eq!(stack[0].as_ref(), &[1]);
assert_eq!(stack[1].as_ref(), &[1]);
}
#[test]
fn test_op_dup_empty_stack() {
let script = vec![OP_DUP]; let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
assert!(!result);
}
#[test]
fn test_op_hash160() {
let script = vec![OP_1, OP_HASH160]; let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
assert!(result);
assert_eq!(stack.len(), 1);
assert_eq!(stack[0].len(), 20); }
#[test]
fn test_op_hash160_empty_stack() {
let script = vec![OP_HASH160]; let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
assert!(!result);
}
#[test]
fn test_op_hash256() {
let script = vec![OP_1, OP_HASH256]; let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
assert!(result);
assert_eq!(stack.len(), 1);
assert_eq!(stack[0].len(), 32); }
#[test]
fn test_op_hash256_empty_stack() {
let script = vec![OP_HASH256]; let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
assert!(!result);
}
#[test]
fn test_op_equal() {
let script = vec![0x51, 0x51, 0x87]; let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
assert!(result);
assert_eq!(stack.len(), 1);
assert_eq!(stack[0].as_ref(), &[1]); }
#[test]
fn test_op_equal_false() {
let script = vec![0x51, 0x52, 0x87]; let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
assert!(!result); assert_eq!(stack.len(), 1);
assert_eq!(stack[0].as_ref(), &[0]); }
#[test]
fn test_op_equal_insufficient_stack() {
let script = vec![0x51, 0x87]; let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base);
assert!(
result.is_err(),
"OP_EQUAL with insufficient stack should return error"
);
if let Err(crate::error::ConsensusError::ScriptErrorWithCode { code, .. }) = result {
assert_eq!(
code,
crate::error::ScriptErrorCode::InvalidStackOperation,
"Should return InvalidStackOperation"
);
}
}
#[test]
fn test_op_verify() {
let script = vec![0x51, 0x69]; let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
assert!(!result); assert_eq!(stack.len(), 0); }
#[test]
fn test_op_verify_false() {
let script = vec![0x00, 0x69]; let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
assert!(!result);
}
#[test]
fn test_op_verify_empty_stack() {
let script = vec![OP_VERIFY]; let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
assert!(!result);
}
#[test]
fn test_op_equalverify() {
let script = vec![0x51, 0x51, 0x88]; let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
assert!(!result); assert_eq!(stack.len(), 0); }
#[test]
fn test_op_equalverify_false() {
let script = vec![0x51, 0x52, 0x88]; let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base);
assert!(
result.is_err(),
"OP_EQUALVERIFY with false condition should return error"
);
if let Err(crate::error::ConsensusError::ScriptErrorWithCode { code, .. }) = result {
assert_eq!(
code,
crate::error::ScriptErrorCode::EqualVerify,
"Should return EqualVerify"
);
}
}
#[test]
fn test_op_checksig() {
let script = vec![OP_1, OP_1, OP_CHECKSIG]; let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
assert!(!result); assert_eq!(stack.len(), 1);
}
#[test]
fn test_op_checksig_insufficient_stack() {
let script = vec![OP_1, OP_CHECKSIG]; let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base);
assert!(
result.is_err(),
"OP_CHECKSIG with insufficient stack should return error"
);
if let Err(crate::error::ConsensusError::ScriptErrorWithCode { code, .. }) = result {
assert_eq!(
code,
crate::error::ScriptErrorCode::InvalidStackOperation,
"Should return InvalidStackOperation"
);
}
}
#[test]
fn test_unknown_opcode() {
let script = vec![0xff]; let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
assert!(!result);
}
#[test]
fn test_script_size_limit() {
let script = vec![0x51; MAX_SCRIPT_SIZE + 1]; let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base);
assert!(result.is_err());
}
#[test]
fn test_operation_count_limit() {
let script = vec![0x61; MAX_SCRIPT_OPS + 1]; let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base);
assert!(result.is_err());
}
#[test]
fn test_stack_underflow_multiple_ops() {
let script = vec![0x51, 0x87, 0x87]; let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base);
assert!(result.is_err(), "Stack underflow should return error");
if let Err(crate::error::ConsensusError::ScriptErrorWithCode { code, .. }) = result {
assert_eq!(
code,
crate::error::ScriptErrorCode::InvalidStackOperation,
"Should return InvalidStackOperation"
);
}
}
#[test]
fn test_final_stack_empty() {
let script = vec![0x51, 0x52]; let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
assert!(!result);
}
#[test]
fn test_final_stack_false() {
let script = vec![OP_0]; let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
assert!(!result);
}
#[test]
fn test_verify_script_with_witness() {
let script_sig = vec![OP_1]; let script_pubkey = vec![OP_1]; let witness = vec![OP_1]; let flags = 0;
let result = verify_script(&script_sig, &script_pubkey, Some(&witness), flags).unwrap();
assert!(!result); }
#[test]
fn test_verify_script_failure() {
let script_sig = vec![OP_1]; let script_pubkey = vec![OP_2]; let witness = None;
let flags = 0;
let result = verify_script(&script_sig, &script_pubkey, witness, flags).unwrap();
assert!(!result);
}
#[test]
fn test_op_ifdup_true() {
let script = vec![OP_1, OP_IFDUP]; let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
assert!(!result); assert_eq!(stack.len(), 2);
assert_eq!(stack[0].as_ref(), &[1]);
assert_eq!(stack[1].as_ref(), &[1]);
}
#[test]
fn test_op_ifdup_false() {
let script = vec![OP_0, OP_IFDUP]; let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
assert!(!result); assert_eq!(stack.len(), 1);
assert_eq!(stack[0].as_ref(), &[] as &[u8]);
}
#[test]
fn test_op_depth() {
let script = vec![OP_1, OP_1, OP_DEPTH]; let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
assert!(!result); assert_eq!(stack.len(), 3);
assert_eq!(stack[2].as_ref(), &[2]); }
#[test]
fn test_op_drop() {
let script = vec![OP_1, OP_2, OP_DROP]; let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
assert!(result); assert_eq!(stack.len(), 1);
assert_eq!(stack[0].as_ref(), &[1]);
}
#[test]
fn test_op_drop_empty_stack() {
let script = vec![OP_DROP]; let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
assert!(!result);
assert_eq!(stack.len(), 0);
}
#[test]
fn test_op_nip() {
let script = vec![OP_1, OP_2, OP_NIP]; let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
assert!(result); assert_eq!(stack.len(), 1);
assert_eq!(stack[0].as_ref(), &[2]);
}
#[test]
fn test_op_nip_insufficient_stack() {
let script = vec![OP_1, OP_NIP]; let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
assert!(!result);
assert_eq!(stack.len(), 1);
}
#[test]
fn test_op_over() {
let script = vec![OP_1, OP_2, OP_OVER]; let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
assert!(!result); assert_eq!(stack.len(), 3);
assert_eq!(stack[0].as_ref(), &[1]);
assert_eq!(stack[1].as_ref(), &[2]);
assert_eq!(stack[2].as_ref(), &[1]);
}
#[test]
fn test_op_over_insufficient_stack() {
let script = vec![OP_1, OP_OVER]; let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
assert!(!result);
assert_eq!(stack.len(), 1);
}
#[test]
fn test_op_pick() {
let script = vec![OP_1, OP_2, OP_3, OP_1, OP_PICK]; let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
assert!(!result); assert_eq!(stack.len(), 4);
assert_eq!(stack[3].as_ref(), &[2]); }
#[test]
fn test_op_pick_empty_n() {
let script = vec![OP_1, OP_0, OP_PICK];
let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
assert!(!result); assert_eq!(stack.len(), 2);
assert_eq!(stack[1].as_ref(), &[1]); }
#[test]
fn test_op_pick_invalid_index() {
let script = vec![OP_1, OP_2, OP_PICK]; let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
assert!(!result);
assert_eq!(stack.len(), 1);
}
#[test]
fn test_op_roll() {
let script = vec![OP_1, OP_2, OP_3, OP_1, OP_ROLL]; let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
assert!(!result); assert_eq!(stack.len(), 3);
assert_eq!(stack[0].as_ref(), &[1]);
assert_eq!(stack[1].as_ref(), &[3]);
assert_eq!(stack[2].as_ref(), &[2]); }
#[test]
fn test_op_roll_zero_n() {
let script = vec![OP_1, OP_0, OP_ROLL]; let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
assert!(result); assert_eq!(stack.len(), 1);
assert_eq!(stack[0].as_ref(), &[1]);
}
#[test]
fn test_op_roll_invalid_index() {
let script = vec![OP_1, OP_2, OP_ROLL]; let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
assert!(!result);
assert_eq!(stack.len(), 1);
}
#[test]
fn test_op_rot() {
let script = vec![OP_1, OP_2, OP_3, OP_ROT]; let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
assert!(!result); assert_eq!(stack.len(), 3);
assert_eq!(stack[0].as_ref(), &[2]);
assert_eq!(stack[1].as_ref(), &[3]);
assert_eq!(stack[2].as_ref(), &[1]);
}
#[test]
fn test_op_rot_insufficient_stack() {
let script = vec![OP_1, OP_2, OP_ROT]; let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
assert!(!result);
assert_eq!(stack.len(), 2);
}
#[test]
fn test_op_swap() {
let script = vec![OP_1, OP_2, OP_SWAP]; let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
assert!(!result); assert_eq!(stack.len(), 2);
assert_eq!(stack[0].as_ref(), &[2]);
assert_eq!(stack[1].as_ref(), &[1]);
}
#[test]
fn test_op_swap_insufficient_stack() {
let script = vec![OP_1, OP_SWAP]; let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
assert!(!result);
assert_eq!(stack.len(), 1);
}
#[test]
fn test_op_tuck() {
let script = vec![OP_1, OP_2, OP_TUCK]; let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
assert!(!result); assert_eq!(stack.len(), 3);
assert_eq!(stack[0].as_ref(), &[2]);
assert_eq!(stack[1].as_ref(), &[1]);
assert_eq!(stack[2].as_ref(), &[2]);
}
#[test]
fn test_op_tuck_insufficient_stack() {
let script = vec![OP_1, OP_TUCK]; let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
assert!(!result);
assert_eq!(stack.len(), 1);
}
#[test]
fn test_op_2drop() {
let script = vec![OP_1, OP_2, OP_3, OP_2DROP]; let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
assert!(result); assert_eq!(stack.len(), 1);
assert_eq!(stack[0].as_ref(), &[1]);
}
#[test]
fn test_op_2drop_insufficient_stack() {
let script = vec![OP_1, OP_2DROP]; let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
assert!(!result);
assert_eq!(stack.len(), 1);
}
#[test]
fn test_op_2dup() {
let script = vec![OP_1, OP_2, OP_2DUP]; let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
assert!(!result); assert_eq!(stack.len(), 4);
assert_eq!(stack[0].as_ref(), &[1]);
assert_eq!(stack[1].as_ref(), &[2]);
assert_eq!(stack[2].as_ref(), &[1]);
assert_eq!(stack[3].as_ref(), &[2]);
}
#[test]
fn test_op_2dup_insufficient_stack() {
let script = vec![OP_1, OP_2DUP]; let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
assert!(!result);
assert_eq!(stack.len(), 1);
}
#[test]
fn test_op_3dup() {
let script = vec![OP_1, OP_2, OP_3, OP_3DUP]; let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
assert!(!result); assert_eq!(stack.len(), 6);
assert_eq!(stack[0].as_ref(), &[1]);
assert_eq!(stack[1].as_ref(), &[2]);
assert_eq!(stack[2].as_ref(), &[3]);
assert_eq!(stack[3].as_ref(), &[1]);
assert_eq!(stack[4].as_ref(), &[2]);
assert_eq!(stack[5].as_ref(), &[3]);
}
#[test]
fn test_op_3dup_insufficient_stack() {
let script = vec![OP_1, OP_2, OP_3DUP]; let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
assert!(!result);
assert_eq!(stack.len(), 2);
}
#[test]
fn test_op_2over() {
let script = vec![OP_1, OP_2, OP_3, OP_4, OP_2OVER]; let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
assert!(!result); assert_eq!(stack.len(), 6);
assert_eq!(stack[4].as_ref(), &[1]); assert_eq!(stack[5].as_ref(), &[2]);
}
#[test]
fn test_op_2over_insufficient_stack() {
let script = vec![OP_1, OP_2, OP_3, OP_2OVER]; let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
assert!(!result);
assert_eq!(stack.len(), 3);
}
#[test]
fn test_op_2rot() {
let script = vec![OP_1, OP_2, OP_3, OP_4, OP_5, OP_6, OP_2ROT]; let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
assert!(!result); assert_eq!(stack.len(), 6);
assert_eq!(stack[4].as_ref(), &[2]); assert_eq!(stack[5].as_ref(), &[1]);
}
#[test]
fn test_op_2rot_insufficient_stack() {
let script = vec![OP_1, OP_2, OP_3, OP_4, OP_2ROT]; let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
assert!(!result);
assert_eq!(stack.len(), 4);
}
#[test]
fn test_op_2swap() {
let script = vec![OP_1, OP_2, OP_3, OP_4, OP_2SWAP]; let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
assert!(!result); assert_eq!(stack.len(), 4);
assert_eq!(stack[0].as_ref(), &[3]); assert_eq!(stack[1].as_ref(), &[4]);
assert_eq!(stack[2].as_ref(), &[1]);
assert_eq!(stack[3].as_ref(), &[2]);
}
#[test]
fn test_op_2swap_insufficient_stack() {
let script = vec![OP_1, OP_2, OP_3, OP_2SWAP]; let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
assert!(!result);
assert_eq!(stack.len(), 3);
}
#[test]
fn test_op_size() {
let script = vec![OP_1, OP_SIZE]; let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
assert!(!result); assert_eq!(stack.len(), 2);
assert_eq!(stack[0].as_ref(), &[1]);
assert_eq!(stack[1].as_ref(), &[1]); }
#[test]
fn test_op_size_empty_stack() {
let script = vec![OP_SIZE]; let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
assert!(!result);
assert_eq!(stack.len(), 0);
}
#[test]
fn test_op_return() {
let script = vec![OP_1, OP_RETURN]; let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
assert!(!result); assert_eq!(stack.len(), 1);
}
#[test]
fn test_op_checksigverify() {
let script = vec![OP_1, OP_2, OP_CHECKSIGVERIFY]; let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
assert!(!result); assert_eq!(stack.len(), 0);
}
#[test]
fn test_op_checksigverify_insufficient_stack() {
let script = vec![OP_1, OP_CHECKSIGVERIFY]; let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
assert!(!result);
assert_eq!(stack.len(), 1);
}
#[test]
fn test_unknown_opcode_comprehensive() {
let script = vec![OP_1, 0xff]; let mut stack = Vec::new();
let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
assert!(!result); assert_eq!(stack.len(), 1);
}
#[test]
fn test_verify_signature_invalid_pubkey() {
let secp = Secp256k1::new();
let invalid_pubkey = vec![0x00]; let signature = vec![0x30, 0x06, 0x02, 0x01, 0x00, 0x02, 0x01, 0x00]; let dummy_hash = [0u8; 32];
let result = signature::verify_signature(
&secp,
&invalid_pubkey,
&signature,
&dummy_hash,
0,
0,
crate::types::Network::Regtest,
SigVersion::Base,
);
assert!(!result.unwrap_or(false));
}
#[test]
fn test_verify_signature_invalid_signature() {
let secp = Secp256k1::new();
let pubkey = vec![
0x02, 0x79, 0xbe, 0x66, 0x7e, 0xf9, 0xdc, 0xbb, 0xac, 0x55, 0xa0, 0x62, 0x95, 0xce,
0x87, 0x0b, 0x07, 0x02, 0x9b, 0xfc, 0xdb, 0x2d, 0xce, 0x28, 0xd9, 0x59, 0xf2, 0x81,
0x5b, 0x16, 0xf8, 0x17, 0x98,
]; let invalid_signature = vec![0x00]; let dummy_hash = [0u8; 32];
let result = signature::verify_signature(
&secp,
&pubkey,
&invalid_signature,
&dummy_hash,
0,
0,
crate::types::Network::Regtest,
SigVersion::Base,
);
assert!(!result.unwrap_or(false));
}
fn minimal_tx_and_prevouts(
script_sig: &[u8],
script_pubkey: &[u8],
) -> (
crate::types::Transaction,
Vec<i64>,
Vec<crate::types::ByteString>,
) {
use crate::types::{OutPoint, Transaction, TransactionInput, TransactionOutput};
let tx = Transaction {
version: 1,
inputs: vec![TransactionInput {
prevout: OutPoint {
hash: [0u8; 32],
index: 0,
},
sequence: 0xffff_ffff,
script_sig: script_sig.to_vec(),
}]
.into(),
outputs: vec![TransactionOutput {
value: 0,
script_pubkey: script_pubkey.to_vec(),
}]
.into(),
lock_time: 0,
};
let prevout_values = vec![0i64];
let prevout_script_pubkeys_vec = vec![script_pubkey.to_vec()];
let prevout_script_pubkeys: Vec<&ByteString> = prevout_script_pubkeys_vec.iter().collect();
(tx, prevout_values, prevout_script_pubkeys_vec)
}
#[test]
fn test_verify_with_context_p2pkh_hash_mismatch() {
let pubkey = vec![0x02u8; 33]; let sig = vec![0x30u8; 70]; let mut script_sig = Vec::new();
script_sig.push(sig.len() as u8);
script_sig.extend(&sig);
script_sig.push(pubkey.len() as u8);
script_sig.extend(&pubkey);
let mut script_pubkey = vec![OP_DUP, OP_HASH160, PUSH_20_BYTES];
script_pubkey.extend(&[0u8; 20]); script_pubkey.push(OP_EQUALVERIFY);
script_pubkey.push(OP_CHECKSIG);
let (tx, pv, psp) = minimal_tx_and_prevouts(&script_sig, &script_pubkey);
let psp_refs: Vec<&[u8]> = psp.iter().map(|b| b.as_ref()).collect();
let result = verify_script_with_context_full(
&script_sig,
&script_pubkey,
None,
0,
&tx,
0,
&pv,
&psp_refs,
Some(500_000),
None,
crate::types::Network::Mainnet,
SigVersion::Base,
#[cfg(feature = "production")]
None,
None, #[cfg(feature = "production")]
None,
#[cfg(feature = "production")]
None,
#[cfg(feature = "production")]
None,
);
assert!(result.is_ok());
assert!(!result.unwrap());
}
#[test]
fn test_verify_with_context_p2sh_hash_mismatch() {
let redeem = vec![OP_1, OP_1, OP_ADD]; let mut script_sig = Vec::new();
script_sig.push(redeem.len() as u8);
script_sig.extend(&redeem);
let mut script_pubkey = vec![OP_HASH160, PUSH_20_BYTES];
script_pubkey.extend(&[0u8; 20]); script_pubkey.push(OP_EQUAL);
let (tx, pv, psp) = minimal_tx_and_prevouts(&script_sig, &script_pubkey);
let psp_refs: Vec<&[u8]> = psp.iter().map(|b| b.as_ref()).collect();
let result = verify_script_with_context_full(
&script_sig,
&script_pubkey,
None,
0x01, &tx,
0,
&pv,
&psp_refs,
Some(500_000),
None,
crate::types::Network::Mainnet,
SigVersion::Base,
#[cfg(feature = "production")]
None,
None, #[cfg(feature = "production")]
None,
#[cfg(feature = "production")]
None,
#[cfg(feature = "production")]
None,
);
assert!(result.is_ok());
assert!(!result.unwrap());
}
#[test]
fn test_verify_with_context_p2wpkh_wrong_witness_size() {
let mut script_pubkey = vec![OP_0, PUSH_20_BYTES];
script_pubkey.extend(&[0u8; 20]);
let witness: Vec<Vec<u8>> = vec![vec![0x30; 70]]; let (tx, pv, psp) = minimal_tx_and_prevouts(&[], &script_pubkey);
let psp_refs: Vec<&[u8]> = psp.iter().map(|b| b.as_ref()).collect();
let empty: Vec<u8> = vec![];
let result = verify_script_with_context_full(
&empty,
&script_pubkey,
Some(&witness),
0,
&tx,
0,
&pv,
&psp_refs,
Some(500_000),
None,
crate::types::Network::Mainnet,
SigVersion::Base,
#[cfg(feature = "production")]
None,
None, #[cfg(feature = "production")]
None,
#[cfg(feature = "production")]
None,
#[cfg(feature = "production")]
None,
);
assert!(result.is_ok());
assert!(!result.unwrap());
}
#[test]
fn test_verify_with_context_p2wsh_wrong_witness_script_hash() {
let witness_script = vec![OP_1];
let mut script_pubkey = vec![OP_0, PUSH_32_BYTES];
script_pubkey.extend(&[0u8; 32]); let witness: Vec<Vec<u8>> = vec![witness_script];
let (tx, pv, psp) = minimal_tx_and_prevouts(&[], &script_pubkey);
let psp_refs: Vec<&[u8]> = psp.iter().map(|b| b.as_ref()).collect();
let empty: Vec<u8> = vec![];
let result = verify_script_with_context_full(
&empty,
&script_pubkey,
Some(&witness),
0,
&tx,
0,
&pv,
&psp_refs,
Some(500_000),
None,
crate::types::Network::Mainnet,
SigVersion::Base,
#[cfg(feature = "production")]
None,
None, #[cfg(feature = "production")]
None,
#[cfg(feature = "production")]
None,
#[cfg(feature = "production")]
None,
);
assert!(result.is_ok());
assert!(!result.unwrap());
}
#[test]
#[cfg(feature = "production")]
fn test_p2wsh_multisig_fast_path() {
use crate::constants::BIP147_ACTIVATION_MAINNET;
use crate::crypto::OptimizedSha256;
let pk1 = [0x02u8; 33];
let pk2 = [0x03u8; 33];
let mut witness_script = vec![0x52]; witness_script.extend_from_slice(&pk1);
witness_script.extend_from_slice(&pk2);
witness_script.push(0x52); witness_script.push(0xae);
let wsh_hash = OptimizedSha256::new().hash(&witness_script);
let mut script_pubkey = vec![OP_0, PUSH_32_BYTES];
script_pubkey.extend_from_slice(&wsh_hash);
let witness: Vec<Vec<u8>> = vec![
vec![0x00], vec![0x30u8; 72], vec![0x30u8; 72], witness_script.clone(),
];
let (tx, pv, psp) = minimal_tx_and_prevouts(&[], &script_pubkey);
let psp_refs: Vec<&[u8]> = psp.iter().map(|b| b.as_ref()).collect();
let empty: Vec<u8> = vec![];
let result = verify_script_with_context_full(
&empty,
&script_pubkey,
Some(&witness),
0x810, &tx,
0,
&pv,
&psp_refs,
Some(BIP147_ACTIVATION_MAINNET + 1),
None,
crate::types::Network::Mainnet,
SigVersion::Base,
#[cfg(feature = "production")]
None,
None, #[cfg(feature = "production")]
None,
#[cfg(feature = "production")]
None,
#[cfg(feature = "production")]
None,
);
assert!(result.is_ok());
assert!(!result.unwrap());
}
}
#[cfg(test)]
#[allow(unused_doc_comments)]
mod property_tests {
use super::*;
use proptest::prelude::*;
proptest! {
#[test]
fn prop_eval_script_operation_limit(script in prop::collection::vec(any::<u8>(), 0..300)) {
let mut stack = Vec::new();
let flags = 0u32;
let result = eval_script(&script, &mut stack, flags, SigVersion::Base);
if script.len() > MAX_SCRIPT_OPS * 2 {
prop_assert!(result.is_err() || !result.unwrap(),
"Very long scripts should fail or return false");
}
}
}
proptest! {
#[test]
fn prop_verify_script_deterministic(
script_sig in prop::collection::vec(any::<u8>(), 0..20),
script_pubkey in prop::collection::vec(any::<u8>(), 0..20),
witness in prop::option::of(prop::collection::vec(any::<u8>(), 0..10)),
flags in any::<u32>()
) {
let result1 = verify_script(&script_sig, &script_pubkey, witness.as_ref(), flags);
let result2 = verify_script(&script_sig, &script_pubkey, witness.as_ref(), flags);
assert_eq!(result1.is_ok(), result2.is_ok());
if result1.is_ok() && result2.is_ok() {
assert_eq!(result1.unwrap(), result2.unwrap());
}
}
}
proptest! {
#[test]
fn prop_execute_opcode_no_panic(
opcode in any::<u8>(),
stack_items in prop::collection::vec(
prop::collection::vec(any::<u8>(), 0..5),
0..10
),
flags in any::<u32>()
) {
let mut stack: Vec<StackElement> = stack_items.into_iter().map(|v| to_stack_element(&v)).collect();
let result = execute_opcode(opcode, &mut stack, flags, SigVersion::Base);
match result {
Ok(success) => {
let _ = success;
},
Err(_) => {
}
}
assert!(stack.len() <= MAX_STACK_SIZE);
}
}
proptest! {
#[test]
fn prop_stack_operations_bounds(
opcode in any::<u8>(),
stack_items in prop::collection::vec(
prop::collection::vec(any::<u8>(), 0..3),
0..5
),
flags in any::<u32>()
) {
let mut stack: Vec<StackElement> = stack_items.into_iter().map(|v| to_stack_element(&v)).collect();
let initial_len = stack.len();
let result = execute_opcode(opcode, &mut stack, flags, SigVersion::Base);
assert!(stack.len() <= MAX_STACK_SIZE);
if result.is_ok() && result.unwrap() {
match opcode {
OP_0 | OP_1..=OP_16 => {
assert!(stack.len() == initial_len + 1);
},
OP_DUP => {
if initial_len > 0 {
assert!(stack.len() == initial_len + 1);
}
},
OP_3DUP => {
if initial_len >= 3 {
assert!(stack.len() == initial_len + 3);
}
},
OP_2OVER => {
if initial_len >= 4 {
assert!(stack.len() == initial_len + 2);
}
},
OP_DROP | OP_NIP | OP_2DROP => {
assert!(stack.len() <= initial_len);
},
_ => {
assert!(stack.len() <= initial_len + 3, "Stack size should be reasonable");
}
}
}
}
}
proptest! {
#[test]
fn prop_hash_operations_deterministic(
input in prop::collection::vec(any::<u8>(), 0..10)
) {
let elem = to_stack_element(&input);
let mut stack1 = vec![elem.clone()];
let mut stack2 = vec![elem];
let result1 = execute_opcode(0xa9, &mut stack1, 0, SigVersion::Base); let result2 = execute_opcode(0xa9, &mut stack2, 0, SigVersion::Base);
assert_eq!(result1.is_ok(), result2.is_ok());
if let (Ok(val1), Ok(val2)) = (result1, result2) {
assert_eq!(val1, val2);
if val1 {
assert_eq!(stack1, stack2);
}
}
}
}
proptest! {
#[test]
fn prop_equality_operations_symmetric(
a in prop::collection::vec(any::<u8>(), 0..5),
b in prop::collection::vec(any::<u8>(), 0..5)
) {
let mut stack1 = vec![to_stack_element(&a), to_stack_element(&b)];
let mut stack2 = vec![to_stack_element(&b), to_stack_element(&a)];
let result1 = execute_opcode(0x87, &mut stack1, 0, SigVersion::Base); let result2 = execute_opcode(0x87, &mut stack2, 0, SigVersion::Base);
assert_eq!(result1.is_ok(), result2.is_ok());
if let (Ok(val1), Ok(val2)) = (result1, result2) {
assert_eq!(val1, val2);
if val1 {
assert_eq!(stack1.len(), stack2.len());
if !stack1.is_empty() && !stack2.is_empty() {
assert_eq!(stack1[0], stack2[0]);
}
}
}
}
}
proptest! {
#[test]
fn prop_script_execution_terminates(
script in prop::collection::vec(any::<u8>(), 0..50)
) {
let mut stack = Vec::new();
let flags = 0u32;
let result = eval_script(&script, &mut stack, flags, SigVersion::Base);
assert!(result.is_ok() || result.is_err());
assert!(stack.len() <= MAX_STACK_SIZE);
}
}
}