Skip to main content

blvm_consensus/script/
mod.rs

1//! Script execution engine from Orange Paper Section 5.2
2//!
3//! Performance optimizations (VM):
4#![allow(
5    clippy::declare_interior_mutable_const,  // const vs static for OnceLock in array init
6    clippy::type_complexity,
7    clippy::too_many_arguments,
8    clippy::needless_return,  // Many branches; mechanical fix error-prone
9)]
10//! - Secp256k1 context reuse (thread-local, zero-cost abstraction)
11//! - Script result caching (production feature only, maintains correctness)
12//! - Hash operation result caching (OP_HASH160, OP_HASH256)
13//! - Stack pooling (thread-local pool of pre-allocated `Vec<StackElement>`)
14//! - Memory allocation optimizations
15//!
16//! **Test vs production graph:** fast paths, LRU script cache, and batch verification are
17//! behind `feature = "production"`. That feature is **on by default** in `Cargo.toml`
18//! (`default = ["production", "blvm-secp256k1"]`), so normal `cargo test` matches the release
19//! node interpreter. Use `cargo test --no-default-features` only when intentionally exercising
20//! the minimal non-production graph.
21
22mod arithmetic;
23mod context;
24mod control_flow;
25mod crypto_ops;
26pub mod flags;
27mod signature;
28mod stack;
29pub mod templates;
30
31#[cfg(feature = "production")]
32pub use signature::{batch_verify_signatures, verify_pre_extracted_ecdsa};
33pub use stack::{StackElement, cast_to_bool, to_stack_element};
34
35use crate::constants::*;
36use crate::crypto::OptimizedSha256;
37use crate::error::{ConsensusError, Result, ScriptErrorCode};
38use crate::opcodes::*;
39use crate::types::*;
40#[cfg(feature = "spec-lock")]
41use blvm_spec_lock::spec_locked;
42use digest::Digest;
43use ripemd::Ripemd160;
44
45// LLVM-like optimizations
46#[cfg(feature = "production")]
47use crate::optimizations::{precomputed_constants, prefetch};
48
49// Cold error construction helpers - these paths are rarely taken
50#[cold]
51#[allow(dead_code)]
52fn make_operation_limit_error() -> ConsensusError {
53    ConsensusError::ScriptErrorWithCode {
54        code: ScriptErrorCode::OpCount,
55        message: "Operation limit exceeded".into(),
56    }
57}
58
59/// CHECKMULTISIG counts each executed pubkey toward the 201-opcode limit.
60///
61/// The opcode itself is already counted. `n` is added only when it decodes and
62/// is in `0..=20`, and only on an executing non-tapscript path. A count the
63/// handler will reject is left unchanged.
64#[inline]
65fn charge_checkmultisig_pubkeys(stack: &[StackElement], op_count: usize) -> Result<usize> {
66    let Some(n_bytes) = stack.last() else {
67        return Ok(op_count);
68    };
69    let Ok(n_raw) = script_num_decode(n_bytes, 4) else {
70        return Ok(op_count);
71    };
72    if !(0..=20).contains(&n_raw) {
73        return Ok(op_count);
74    }
75    let next = op_count.saturating_add(n_raw as usize);
76    if next > MAX_SCRIPT_OPS {
77        return Err(make_operation_limit_error());
78    }
79    Ok(next)
80}
81
82#[inline]
83fn script_bool_bytes(value: bool) -> &'static [u8] {
84    if value { &[1] } else { &[] }
85}
86
87#[cold]
88fn make_stack_overflow_error() -> ConsensusError {
89    ConsensusError::ScriptErrorWithCode {
90        code: ScriptErrorCode::StackSize,
91        message: "Stack overflow".into(),
92    }
93}
94
95#[inline]
96fn bip143_p2wpkh_script_code(pubkey_hash: &[u8]) -> [u8; 25] {
97    let mut hash = [0u8; 20];
98    hash.copy_from_slice(pubkey_hash);
99    crate::transaction_hash::derive_bip143_script_code_p2wpkh(&hash)
100}
101
102#[cfg(feature = "production")]
103use std::collections::VecDeque;
104#[cfg(feature = "production")]
105use std::sync::{
106    OnceLock, RwLock,
107    atomic::{AtomicBool, AtomicU64, Ordering},
108};
109#[cfg(feature = "production")]
110use std::thread_local;
111
112/// Script verification result cache (production feature only)
113///
114/// Caches scriptPubKey verification results to avoid re-execution of identical scripts.
115/// Cache is bounded (LRU) and invalidated on consensus changes.
116/// Reference: Orange Paper Section 13.1 explicitly mentions script caching.
117#[cfg(feature = "production")]
118static SCRIPT_CACHE: OnceLock<RwLock<lru::LruCache<u64, bool>>> = OnceLock::new();
119
120/// Thread-local buffers for verify_soa_batch; reused to avoid per-batch allocs.
121#[cfg(feature = "production")]
122thread_local! {
123    static SOA_BATCH_BUF: std::cell::RefCell<(
124        Vec<[u8; 64]>,
125        Vec<[u8; 32]>,
126        Vec<[u8; 33]>,
127        Vec<usize>,
128        Vec<[u8; 32]>,
129    )> = const { std::cell::RefCell::new((
130        Vec::new(),
131        Vec::new(),
132        Vec::new(),
133        Vec::new(),
134        Vec::new(),
135    )) };
136}
137
138/// Fast-path hit counters (production): verify that P2PK/P2PKH/P2SH/P2WPKH/P2WSH fast-paths are used.
139/// Logged periodically from block validation; interpreter = scripts that fell through to full interpreter.
140#[cfg(feature = "production")]
141static FAST_PATH_P2PK: AtomicU64 = AtomicU64::new(0);
142#[cfg(feature = "production")]
143static FAST_PATH_P2PKH: AtomicU64 = AtomicU64::new(0);
144#[cfg(feature = "production")]
145static FAST_PATH_P2SH: AtomicU64 = AtomicU64::new(0);
146#[cfg(feature = "production")]
147static FAST_PATH_P2WPKH: AtomicU64 = AtomicU64::new(0);
148#[cfg(feature = "production")]
149static FAST_PATH_P2WSH: AtomicU64 = AtomicU64::new(0);
150#[cfg(feature = "production")]
151static FAST_PATH_P2TR: AtomicU64 = AtomicU64::new(0);
152#[cfg(feature = "production")]
153static FAST_PATH_UNEXEC_IF: AtomicU64 = AtomicU64::new(0);
154#[cfg(feature = "production")]
155static FAST_PATH_BARE_MULTISIG: AtomicU64 = AtomicU64::new(0);
156#[cfg(feature = "production")]
157static FAST_PATH_INTERPRETER: AtomicU64 = AtomicU64::new(0);
158
159#[cfg(feature = "production")]
160fn get_script_cache() -> &'static RwLock<lru::LruCache<u64, bool>> {
161    SCRIPT_CACHE.get_or_init(|| {
162        // Bounded cache: 100,000 entries (optimized for production workloads)
163        // LRU eviction policy prevents unbounded memory growth
164        // Increased from 50k to 100k for better hit rates in large mempools
165        use lru::LruCache;
166        use std::num::NonZeroUsize;
167        RwLock::new(LruCache::new(NonZeroUsize::new(100_000).unwrap()))
168    })
169}
170
171/// Stack pool for VM optimization (production feature only)
172///
173/// Thread-local pool of pre-allocated stacks to avoid allocation overhead.
174/// Stacks are reused across script executions, significantly reducing memory allocations.
175#[cfg(feature = "production")]
176thread_local! {
177    static STACK_POOL: std::cell::RefCell<VecDeque<Vec<StackElement>>> =
178        std::cell::RefCell::new(VecDeque::with_capacity(10));
179}
180
181/// Get a stack from the pool, or create a new one if pool is empty
182#[cfg(feature = "production")]
183fn get_pooled_stack() -> Vec<StackElement> {
184    STACK_POOL.with(|pool| {
185        let mut pool = pool.borrow_mut();
186        if let Some(mut stack) = pool.pop_front() {
187            stack.clear();
188            if stack.capacity() < 20 {
189                stack.reserve(20);
190            }
191            stack
192        } else {
193            Vec::with_capacity(20)
194        }
195    })
196}
197
198/// RAII guard that returns stack to pool on drop. Use for interpreter fallback path.
199#[cfg(feature = "production")]
200struct PooledStackGuard(Vec<StackElement>);
201#[cfg(feature = "production")]
202impl Drop for PooledStackGuard {
203    fn drop(&mut self) {
204        return_pooled_stack(std::mem::take(&mut self.0));
205    }
206}
207
208/// Return a stack to the pool for reuse
209#[cfg(feature = "production")]
210fn return_pooled_stack(mut stack: Vec<StackElement>) {
211    // Clear stack but preserve capacity
212    stack.clear();
213
214    STACK_POOL.with(|pool| {
215        let mut pool = pool.borrow_mut();
216        // Limit pool size to prevent unbounded growth
217        if pool.len() < 10 {
218            pool.push_back(stack);
219        }
220        // If pool is full, stack is dropped (deallocated)
221    });
222}
223
224/// Flag to disable caching for benchmarking (production feature only)
225///
226/// When set to true, caches are bypassed entirely, allowing reproducible benchmarks
227/// without cache state pollution between runs.
228#[cfg(feature = "production")]
229static CACHE_DISABLED: AtomicBool = AtomicBool::new(false);
230
231/// When true, `verify_script_with_context_full` skips fast paths (P4.2 differential harness).
232#[cfg(feature = "production")]
233static FAST_PATHS_DISABLED: AtomicBool = AtomicBool::new(false);
234
235/// Disable caching for benchmarking
236///
237/// When disabled, all cache lookups are bypassed, ensuring consistent performance
238/// measurements without cache state affecting results.
239///
240/// # Example
241///
242/// ```rust
243/// use blvm_consensus::script::disable_caching;
244///
245/// // Disable caches for benchmarking
246/// disable_caching(true);
247/// // Run benchmarks...
248/// disable_caching(false); // Re-enable for production
249/// ```
250#[cfg(feature = "production")]
251pub fn disable_caching(disabled: bool) {
252    CACHE_DISABLED.store(disabled, Ordering::Relaxed);
253}
254
255/// Skip script fast paths so results come from the full interpreter (P4.2 equivalence tests).
256#[cfg(feature = "production")]
257pub fn disable_fast_paths(disabled: bool) {
258    FAST_PATHS_DISABLED.store(disabled, Ordering::Relaxed);
259}
260
261/// Check if caching is disabled
262#[cfg(feature = "production")]
263fn is_caching_disabled() -> bool {
264    CACHE_DISABLED.load(Ordering::Relaxed)
265}
266
267#[cfg(feature = "production")]
268fn fast_paths_disabled() -> bool {
269    FAST_PATHS_DISABLED.load(Ordering::Relaxed)
270}
271
272/// Compute cache key for script verification (SHA256 prefix — not `DefaultHasher`).
273#[cfg(feature = "production")]
274fn compute_script_cache_key(
275    script_sig: &ByteString,
276    script_pubkey: &[u8],
277    witness: Option<&ByteString>,
278    flags: u32,
279) -> u64 {
280    use sha2::{Digest, Sha256};
281
282    let mut hasher = Sha256::new();
283    hasher.update((script_sig.len() as u64).to_le_bytes());
284    hasher.update(&script_sig[..]);
285    hasher.update((script_pubkey.len() as u64).to_le_bytes());
286    hasher.update(script_pubkey);
287    if let Some(w) = witness {
288        hasher.update((w.len() as u64).to_le_bytes());
289        hasher.update(&w[..]);
290    } else {
291        hasher.update(0u64.to_le_bytes());
292    }
293    hasher.update(flags.to_le_bytes());
294    let digest = hasher.finalize();
295    u64::from_le_bytes(digest[0..8].try_into().expect("sha256 prefix"))
296}
297
298/// Script version for policy/consensus behavior (BIP141/BIP341 SigVersion)
299#[derive(Debug, Clone, Copy, PartialEq, Eq)]
300pub enum SigVersion {
301    /// Legacy and P2SH scripts
302    Base,
303    /// Witness v0 (P2WPKH/P2WSH)
304    WitnessV0,
305    /// Taproot script path (witness v1 Tapscript)
306    Tapscript,
307}
308
309/// EvalScript: 𝒮𝒞 × 𝒮𝒯 × ℕ × SigVersion → {true, false}
310///
311/// Script execution follows a stack-based virtual machine:
312/// 1. Initialize stack S = ∅
313/// 2. For each opcode op in script:
314///    - If |S| > L_stack: return false (stack overflow)
315///    - If operation count > L_ops: return false (operation limit exceeded)
316///    - Execute op with current stack state
317///    - If execution fails: return false
318/// 3. Return true if execution completed without error (final stack shape is
319///    checked by [`verify_script`] / [`verify_script_with_context_full`], not here).
320///
321/// Performance: Pre-allocates stack with capacity hint to reduce allocations
322///
323/// In production mode, stacks should be obtained from pool using get_pooled_stack()
324/// for optimal performance. This function works with any Vec<StackElement>.
325#[cfg_attr(feature = "spec-lock", spec_locked("5.2", "EvalScript"))]
326#[cfg_attr(feature = "production", inline(always))]
327#[cfg_attr(not(feature = "production"), inline)]
328pub fn eval_script(
329    script: &[u8],
330    stack: &mut Vec<StackElement>,
331    flags: u32,
332    sigversion: SigVersion,
333) -> Result<bool> {
334    // Pre-allocate stack capacity to reduce allocations during execution
335    // Most scripts don't exceed 20 stack items in practice
336    if stack.capacity() < 20 {
337        stack.reserve(20);
338    }
339    #[cfg(feature = "production")]
340    {
341        eval_script_impl(script, stack, flags, sigversion)
342    }
343    #[cfg(not(feature = "production"))]
344    {
345        eval_script_inner(script, stack, flags, sigversion)
346    }
347}
348#[cfg(feature = "production")]
349fn eval_script_impl(
350    script: &[u8],
351    stack: &mut Vec<StackElement>,
352    flags: u32,
353    sigversion: SigVersion,
354) -> Result<bool> {
355    eval_script_inner(script, stack, flags, sigversion)
356}
357
358/// Push opcodes: any opcode <= OP_16 (0x60). Used by both production and non-production paths.
359#[inline(always)]
360pub(crate) fn is_push_opcode(opcode: u8) -> bool {
361    opcode <= 0x60
362}
363
364/// BIP342: opcodes that cause immediate script success in Tapscript (OP_SUCCESSx).
365/// Reference: BIP342 successful witness opcodes (IsOpSuccess).
366#[inline]
367pub(crate) fn is_op_success(opcode: u8) -> bool {
368    matches!(
369        opcode,
370        80 | 98
371            | 126..=129
372            | 131..=134
373            | 137..=138
374            | 141..=142
375            | 149..=153
376            | 187..=254
377    )
378}
379
380/// Opcodes that fail even when the current branch does not execute.
381///
382/// The numeric disabled set is not `OP_2MUL..=OP_RSHIFT`: that span includes
383/// `OP_NEGATE`, `OP_ABS`, `OP_NOT`, `OP_0NOTEQUAL`, `OP_ADD`, and `OP_SUB`.
384fn dead_branch_opcode_error(opcode: u8) -> Option<ScriptErrorCode> {
385    let disabled = matches!(
386        opcode,
387        OP_CAT..=OP_RIGHT
388            | OP_INVERT..=OP_XOR
389            | OP_2MUL
390            | OP_2DIV
391            | OP_MUL
392            | OP_DIV
393            | OP_MOD
394            | OP_LSHIFT
395            | OP_RSHIFT
396    );
397    if disabled {
398        return Some(ScriptErrorCode::DisabledOpcode);
399    }
400    if opcode == OP_VERIF || opcode == OP_VERNOTIF {
401        return Some(ScriptErrorCode::BadOpcode);
402    }
403    None
404}
405
406/// Advance past one opcode + its push data in a script byte slice.
407/// Returns the number of bytes to skip (always >= 1).
408#[inline]
409pub(crate) fn op_advance(script: &[u8], pc: usize) -> usize {
410    let opcode = script[pc];
411    match opcode {
412        // direct push: opcode byte is the data length (1–75 bytes)
413        0x01..=0x4b => 1 + opcode as usize,
414        // OP_PUSHDATA1: 1 length byte follows
415        0x4c => {
416            if pc + 1 < script.len() {
417                2 + script[pc + 1] as usize
418            } else {
419                1
420            }
421        }
422        // OP_PUSHDATA2: 2 length bytes follow (LE)
423        0x4d => {
424            if pc + 2 < script.len() {
425                3 + u16::from_le_bytes([script[pc + 1], script[pc + 2]]) as usize
426            } else {
427                1
428            }
429        }
430        // OP_PUSHDATA4: 4 length bytes follow (LE)
431        0x4e => {
432            if pc + 4 < script.len() {
433                5 + u32::from_le_bytes([
434                    script[pc + 1],
435                    script[pc + 2],
436                    script[pc + 3],
437                    script[pc + 4],
438                ]) as usize
439            } else {
440                1
441            }
442        }
443        _ => 1,
444    }
445}
446
447/// End offset of the opcode at `pc`, or `None` when a push does not fit.
448///
449/// A short `OP_PUSHDATA*` header is not an opcode. The tapscript success scan
450/// must stop there instead of reading the leftover length bytes as opcodes.
451fn tapscript_opcode_end(script: &[u8], pc: usize) -> Option<usize> {
452    let opcode = *script.get(pc)?;
453    let end = if opcode == 0 {
454        pc.checked_add(1)?
455    } else if opcode <= 0x4b {
456        pc.checked_add(1)?.checked_add(opcode as usize)?
457    } else if opcode == OP_PUSHDATA1 {
458        let len = *script.get(pc + 1)? as usize;
459        pc.checked_add(2)?.checked_add(len)?
460    } else if opcode == OP_PUSHDATA2 {
461        if pc + 2 >= script.len() {
462            return None;
463        }
464        let len = u16::from_le_bytes([script[pc + 1], script[pc + 2]]) as usize;
465        pc.checked_add(3)?.checked_add(len)?
466    } else if opcode == OP_PUSHDATA4 {
467        if pc + 4 >= script.len() {
468            return None;
469        }
470        let len = u32::from_le_bytes([
471            script[pc + 1],
472            script[pc + 2],
473            script[pc + 3],
474            script[pc + 4],
475        ]) as usize;
476        pc.checked_add(5)?.checked_add(len)?
477    } else {
478        pc.checked_add(1)?
479    };
480    (end <= script.len()).then_some(end)
481}
482
483/// A push has one shortest encoding. `SCRIPT_VERIFY_MINIMALDATA` rejects the longer ones.
484fn push_encoding_is_minimal(opcode: u8, data: &[u8]) -> bool {
485    if data.is_empty() {
486        return opcode == OP_0;
487    }
488    if data.len() == 1 {
489        let byte = data[0];
490        if (1..=16).contains(&byte) {
491            return opcode == OP_1 + (byte - 1);
492        }
493        if byte == 0x81 {
494            return opcode == OP_1NEGATE;
495        }
496    }
497    if data.len() <= 75 {
498        return opcode == data.len() as u8;
499    }
500    if data.len() <= 255 {
501        return opcode == OP_PUSHDATA1;
502    }
503    if data.len() <= 65535 {
504        return opcode == OP_PUSHDATA2;
505    }
506    true
507}
508
509/// Pop an executing `OP_IF` / `OP_NOTIF` condition and enforce MINIMALIF.
510///
511/// The caller builds the `ControlBlock` and, in the context interpreter, advances `i`.
512fn pop_if_condition(
513    stack: &mut Vec<StackElement>,
514    flags: u32,
515    sigversion: SigVersion,
516    minimal_message: &'static str,
517) -> Result<bool> {
518    let condition_bytes = stack.pop().unwrap();
519    let condition = cast_to_bool(&condition_bytes);
520    let enforce_minimal_if = sigversion == SigVersion::Tapscript
521        || (sigversion == SigVersion::WitnessV0 && flags & flags::SCRIPT_VERIFY_MINIMALIF != 0);
522    if enforce_minimal_if && !control_flow::is_minimal_if_condition(&condition_bytes) {
523        return Err(ConsensusError::ScriptErrorWithCode {
524            code: ScriptErrorCode::MinimalIf,
525            message: minimal_message.into(),
526        });
527    }
528    Ok(condition)
529}
530
531/// Decode one data push and, unless `in_false_branch`, put it on `stack`.
532///
533/// `Ok(None)` is a failed script (truncated push or non-minimal encoding).
534/// A push larger than [`MAX_SCRIPT_ELEMENT_SIZE`] is an error.
535fn apply_data_push(
536    script: &[u8],
537    i: usize,
538    opcode: u8,
539    stack: &mut Vec<StackElement>,
540    flags: u32,
541    in_false_branch: bool,
542) -> Result<Option<usize>> {
543    let (data, advance) = if opcode <= 0x4b {
544        let len = opcode as usize;
545        if i + 1 + len > script.len() {
546            return Ok(None);
547        }
548        (&script[i + 1..i + 1 + len], 1 + len)
549    } else if opcode == OP_PUSHDATA1 {
550        if i + 1 >= script.len() {
551            return Ok(None);
552        }
553        let len = script[i + 1] as usize;
554        if i + 2 + len > script.len() {
555            return Ok(None);
556        }
557        (&script[i + 2..i + 2 + len], 2 + len)
558    } else if opcode == OP_PUSHDATA2 {
559        if i + 2 >= script.len() {
560            return Ok(None);
561        }
562        let len = u16::from_le_bytes([script[i + 1], script[i + 2]]) as usize;
563        if i + 3 + len > script.len() {
564            return Ok(None);
565        }
566        (&script[i + 3..i + 3 + len], 3 + len)
567    } else {
568        if i + 4 >= script.len() {
569            return Ok(None);
570        }
571        let len = u32::from_le_bytes([script[i + 1], script[i + 2], script[i + 3], script[i + 4]])
572            as usize;
573        let data_start = i.saturating_add(5);
574        let data_end = data_start.saturating_add(len);
575        let advance = 5usize.saturating_add(len);
576        if advance < 5 || data_end > script.len() || data_end < data_start {
577            return Ok(None);
578        }
579        (&script[data_start..data_end], advance)
580    };
581
582    if data.len() > MAX_SCRIPT_ELEMENT_SIZE {
583        return Err(ConsensusError::ScriptErrorWithCode {
584            code: ScriptErrorCode::PushSize,
585            message: format!(
586                "Push data size {} exceeds maximum {}",
587                data.len(),
588                MAX_SCRIPT_ELEMENT_SIZE
589            )
590            .into(),
591        });
592    }
593    if !in_false_branch
594        && (flags & crate::script::flags::SCRIPT_VERIFY_MINIMALDATA) != 0
595        && !push_encoding_is_minimal(opcode, data)
596    {
597        return Ok(None);
598    }
599    if !in_false_branch {
600        stack.push(to_stack_element(data));
601    }
602    Ok(Some(advance))
603}
604
605fn eval_script_inner(
606    script: &[u8],
607    stack: &mut Vec<StackElement>,
608    flags: u32,
609    sigversion: SigVersion,
610) -> Result<bool> {
611    use crate::constants::MAX_SCRIPT_SIZE;
612    use crate::error::{ConsensusError, ScriptErrorCode};
613
614    if (sigversion == SigVersion::Base || sigversion == SigVersion::WitnessV0)
615        && script.len() > MAX_SCRIPT_SIZE
616    {
617        return Err(ConsensusError::ScriptErrorWithCode {
618            code: ScriptErrorCode::ScriptSize,
619            message: "Script size exceeds maximum".into(),
620        });
621    }
622
623    let mut op_count = 0;
624    let mut control_stack: Vec<control_flow::ControlBlock> = Vec::new();
625    let mut altstack: Vec<StackElement> = Vec::new();
626
627    let mut i = 0;
628    // The combined stack limit is applied after an opcode finishes, not before it runs.
629    let mut finished_opcode = false;
630    while i < script.len() {
631        if finished_opcode && stack.len() + altstack.len() > MAX_STACK_SIZE {
632            return Err(make_stack_overflow_error());
633        }
634        finished_opcode = true;
635        let opcode = script[i];
636        let in_false_branch = control_flow::in_false_branch(&control_stack);
637
638        // Count non-push opcodes toward op limit (Base/WitnessV0 only; BIP342 Tapscript has no 201-op cap).
639        // nOpCount only for BASE || WITNESS_V0 (BIP342 Tapscript exempt).
640        if sigversion != SigVersion::Tapscript && !is_push_opcode(opcode) {
641            op_count += 1;
642            if op_count > MAX_SCRIPT_OPS {
643                return Err(make_operation_limit_error());
644            }
645            debug_assert!(
646                op_count <= MAX_SCRIPT_OPS,
647                "Operation count ({op_count}) must not exceed MAX_SCRIPT_OPS ({MAX_SCRIPT_OPS})"
648            );
649        }
650
651        // Handle push opcodes with embedded data (0x01-0x4b direct, OP_PUSHDATA1/2/4).
652        if (0x01..=OP_PUSHDATA4).contains(&opcode) {
653            let Some(advance) = apply_data_push(script, i, opcode, stack, flags, in_false_branch)?
654            else {
655                return Ok(false);
656            };
657            i += advance;
658            continue;
659        }
660
661        match opcode {
662            // OP_IF
663            OP_IF => {
664                if in_false_branch {
665                    control_stack.push(control_flow::ControlBlock::If { executing: false });
666                } else if stack.is_empty() {
667                    return Err(ConsensusError::ScriptErrorWithCode {
668                        code: ScriptErrorCode::InvalidStackOperation,
669                        message: "OP_IF: empty stack".into(),
670                    });
671                } else {
672                    let condition = pop_if_condition(
673                        stack,
674                        flags,
675                        sigversion,
676                        "OP_IF condition must be minimally encoded",
677                    )?;
678                    control_stack.push(control_flow::ControlBlock::If {
679                        executing: condition,
680                    });
681                }
682            }
683            // OP_NOTIF
684            OP_NOTIF => {
685                if in_false_branch {
686                    control_stack.push(control_flow::ControlBlock::NotIf { executing: false });
687                } else if stack.is_empty() {
688                    return Err(ConsensusError::ScriptErrorWithCode {
689                        code: ScriptErrorCode::InvalidStackOperation,
690                        message: "OP_NOTIF: empty stack".into(),
691                    });
692                } else {
693                    let condition = pop_if_condition(
694                        stack,
695                        flags,
696                        sigversion,
697                        "OP_NOTIF condition must be minimally encoded",
698                    )?;
699                    control_stack.push(control_flow::ControlBlock::NotIf {
700                        executing: !condition,
701                    });
702                }
703            }
704            // OP_ELSE
705            OP_ELSE => {
706                if let Some(block) = control_stack.last_mut() {
707                    match block {
708                        control_flow::ControlBlock::If { executing }
709                        | control_flow::ControlBlock::NotIf { executing } => {
710                            *executing = !*executing;
711                        }
712                    }
713                } else {
714                    return Err(ConsensusError::ScriptErrorWithCode {
715                        code: ScriptErrorCode::UnbalancedConditional,
716                        message: "OP_ELSE without matching IF/NOTIF".into(),
717                    });
718                }
719            }
720            // OP_ENDIF
721            OP_ENDIF => {
722                if control_stack.pop().is_none() {
723                    return Err(ConsensusError::ScriptErrorWithCode {
724                        code: ScriptErrorCode::UnbalancedConditional,
725                        message: "OP_ENDIF without matching IF/NOTIF".into(),
726                    });
727                }
728            }
729            // OP_TOALTSTACK - move top stack item to altstack
730            OP_TOALTSTACK => {
731                if !in_false_branch {
732                    if stack.is_empty() {
733                        return Err(ConsensusError::ScriptErrorWithCode {
734                            code: ScriptErrorCode::InvalidStackOperation,
735                            message: "OP_TOALTSTACK: empty stack".into(),
736                        });
737                    }
738                    altstack.push(stack.pop().unwrap());
739                }
740            }
741            // OP_FROMALTSTACK - move top altstack item to stack
742            OP_FROMALTSTACK => {
743                if !in_false_branch {
744                    if altstack.is_empty() {
745                        return Err(ConsensusError::ScriptErrorWithCode {
746                            code: ScriptErrorCode::InvalidAltstackOperation,
747                            message: "OP_FROMALTSTACK: empty altstack".into(),
748                        });
749                    }
750                    stack.push(altstack.pop().unwrap());
751                }
752            }
753            _ => {
754                if in_false_branch {
755                    if let Some(code) = dead_branch_opcode_error(opcode) {
756                        return Err(ConsensusError::ScriptErrorWithCode {
757                            code,
758                            message: format!(
759                                "opcode 0x{opcode:02x} is invalid in an unexecuted branch"
760                            )
761                            .into(),
762                        });
763                    }
764                    i += 1;
765                    continue;
766                }
767
768                if sigversion != SigVersion::Tapscript
769                    && (opcode == OP_CHECKMULTISIG || opcode == OP_CHECKMULTISIGVERIFY)
770                {
771                    op_count = charge_checkmultisig_pubkeys(stack, op_count)?;
772                }
773                if !execute_opcode(opcode, stack, flags, sigversion)? {
774                    return Ok(false);
775                }
776            }
777        }
778        i += 1;
779    }
780
781    if finished_opcode && stack.len() + altstack.len() > MAX_STACK_SIZE {
782        return Err(make_stack_overflow_error());
783    }
784
785    if !control_stack.is_empty() {
786        return Err(ConsensusError::ScriptErrorWithCode {
787            code: ScriptErrorCode::UnbalancedConditional,
788            message: "Unclosed IF/NOTIF block".into(),
789        });
790    }
791
792    Ok(true)
793}
794
795/// VerifyScript: 𝒮𝒞 × 𝒮𝒞 × 𝒲 × ℕ → {true, false}
796///
797/// For scriptSig ss, scriptPubKey spk, witness w, and flags f:
798/// 1. Execute ss on empty stack
799/// 2. Execute spk on resulting stack
800/// 3. If witness present: execute w on stack
801/// 4. Return final stack has exactly one true value
802///
803/// After scriptSig and scriptPubKey, a P2SH output still has to run the redeem
804/// script. The pubkey script only checked HASH160. `OP_RESERVED` in the redeem
805/// script fails. The redeem runs only when `SCRIPT_VERIFY_P2SH` is set.
806fn apply_p2sh_redeem(
807    script_sig: &[u8],
808    script_pubkey: &[u8],
809    flags: u32,
810    sigversion: SigVersion,
811    stack: &mut Vec<StackElement>,
812) -> Result<bool> {
813    let is_p2sh = (flags & flags::SCRIPT_VERIFY_P2SH) != 0
814        && script_pubkey.len() == 23
815        && script_pubkey[0] == OP_HASH160
816        && script_pubkey[1] == PUSH_20_BYTES
817        && script_pubkey[22] == OP_EQUAL;
818    if !is_p2sh {
819        return Ok(true);
820    }
821    if stack.is_empty() || !cast_to_bool(&stack[stack.len() - 1]) {
822        return Ok(false);
823    }
824    stack.pop();
825    let Some(pushes) = parse_borrowed_pushes(script_sig) else {
826        return Ok(false);
827    };
828    let Some(redeem) = pushes.last() else {
829        return Ok(false);
830    };
831    eval_script(redeem.as_ref(), stack, flags, sigversion)
832}
833
834/// Legacy script verification (Base sigversion, at most one witness push).
835///
836/// **Deprecated**: accepts `Option<&ByteString>` not a full witness stack. SegWit and Taproot
837/// inputs require [`verify_script_with_context`] or [`verify_script_with_context_full`].
838#[deprecated(
839    since = "0.1.33",
840    note = "Use verify_script_with_context for witness-aware script verification"
841)]
842#[cfg_attr(feature = "spec-lock", spec_locked("5.2", "VerifyScript"))]
843#[cfg_attr(feature = "production", inline(always))]
844#[cfg_attr(not(feature = "production"), inline)]
845pub fn verify_script(
846    script_sig: &ByteString,
847    script_pubkey: &[u8],
848    witness: Option<&ByteString>,
849    flags: u32,
850) -> Result<bool> {
851    // P2SH: a non-push scriptSig is invalid before either script runs.
852    if flags & flags::SCRIPT_VERIFY_P2SH != 0
853        && script_pubkey.len() == 23
854        && script_pubkey[0] == OP_HASH160
855        && script_pubkey[1] == PUSH_20_BYTES
856        && script_pubkey[22] == OP_EQUAL
857        && !p2sh_push_only_check(script_sig)
858    {
859        return Ok(false);
860    }
861
862    // SigVersion is always Base for this API (no tx/witness context)
863    let sigversion = SigVersion::Base;
864
865    #[cfg(feature = "production")]
866    {
867        // Check cache first (unless disabled for benchmarking)
868        if !is_caching_disabled() {
869            let cache_key = compute_script_cache_key(script_sig, script_pubkey, witness, flags);
870            {
871                let cache = get_script_cache().read().unwrap_or_else(|e| e.into_inner());
872                if let Some(&cached_result) = cache.peek(&cache_key) {
873                    return Ok(cached_result);
874                }
875            }
876        }
877
878        // Execute script (cache miss)
879        // Use pooled stack to avoid allocation
880        let mut stack = get_pooled_stack();
881        let cache_key = compute_script_cache_key(script_sig, script_pubkey, witness, flags);
882        let result = {
883            if !eval_script(script_sig, &mut stack, flags, sigversion)? {
884                // Cache negative result (unless disabled)
885                if !is_caching_disabled() {
886                    let mut cache = get_script_cache()
887                        .write()
888                        .unwrap_or_else(|e| e.into_inner());
889                    cache.put(cache_key, false);
890                }
891                false
892            } else if !eval_script(script_pubkey, &mut stack, flags, sigversion)? {
893                if !is_caching_disabled() {
894                    let mut cache = get_script_cache()
895                        .write()
896                        .unwrap_or_else(|e| e.into_inner());
897                    cache.put(cache_key, false);
898                }
899                false
900            } else if !apply_p2sh_redeem(script_sig, script_pubkey, flags, sigversion, &mut stack)?
901            {
902                if !is_caching_disabled() {
903                    let mut cache = get_script_cache()
904                        .write()
905                        .unwrap_or_else(|e| e.into_inner());
906                    cache.put(cache_key, false);
907                }
908                false
909            } else if let Some(w) = witness {
910                if !eval_script(w, &mut stack, flags, sigversion)? {
911                    if !is_caching_disabled() {
912                        let mut cache = get_script_cache()
913                            .write()
914                            .unwrap_or_else(|e| e.into_inner());
915                        cache.put(cache_key, false);
916                    }
917                    false
918                } else {
919                    let res = !stack.is_empty() && cast_to_bool(&stack[stack.len() - 1]);
920                    if !is_caching_disabled() {
921                        let mut cache = get_script_cache()
922                            .write()
923                            .unwrap_or_else(|e| e.into_inner());
924                        cache.put(cache_key, res);
925                    }
926                    res
927                }
928            } else {
929                let res = !stack.is_empty() && cast_to_bool(&stack[stack.len() - 1]);
930                if !is_caching_disabled() {
931                    let mut cache = get_script_cache()
932                        .write()
933                        .unwrap_or_else(|e| e.into_inner());
934                    cache.put(cache_key, res);
935                }
936                res
937            }
938        };
939
940        // Return stack to pool
941        return_pooled_stack(stack);
942
943        Ok(result)
944    }
945
946    #[cfg(not(feature = "production"))]
947    {
948        // Pre-allocate stack with capacity hint (most scripts use <20 items)
949        let mut stack = Vec::with_capacity(20);
950
951        // Execute scriptSig
952        if !eval_script(script_sig, &mut stack, flags, sigversion)? {
953            return Ok(false);
954        }
955
956        // Execute scriptPubkey
957        if !eval_script(script_pubkey, &mut stack, flags, sigversion)? {
958            return Ok(false);
959        }
960        if !apply_p2sh_redeem(script_sig, script_pubkey, flags, sigversion, &mut stack)? {
961            return Ok(false);
962        }
963
964        // Execute witness if present
965        if let Some(w) = witness {
966            if !eval_script(w, &mut stack, flags, sigversion)? {
967                return Ok(false);
968            }
969        }
970
971        // Final validation: top of stack must be truthy (CastToBool on stack.back()).
972        Ok(!stack.is_empty() && cast_to_bool(&stack[stack.len() - 1]))
973    }
974}
975
976/// VerifyScript with transaction context for signature verification
977///
978/// This version includes the full transaction context needed for proper
979/// Script verification with transaction context and optional block height.
980///
981/// Pass `block_height` whenever it is known; height-dependent fork rules (CLTV, CSV,
982/// SegWit, Taproot) rely on it to select the correct evaluation path.  Passing `None`
983/// falls back to height `0` inside the engine, which means pre-activation rules are
984/// used — valid only for regtest / test-vector scenarios.
985///
986/// For callers that also need to supply a pre-computed sighash or a specific
987/// `SigVersion`, use [`verify_script_with_context_full`] directly.
988#[cfg_attr(feature = "spec-lock", spec_locked("5.2", "VerifyScript"))]
989#[cfg_attr(feature = "production", inline(always))]
990#[cfg_attr(not(feature = "production"), inline)]
991#[allow(clippy::too_many_arguments)]
992pub fn verify_script_with_context(
993    script_sig: &ByteString,
994    script_pubkey: &[u8],
995    witness: Option<&crate::witness::Witness>,
996    flags: u32,
997    tx: &Transaction,
998    input_index: usize,
999    prevouts: &[TransactionOutput],
1000    block_height: Option<u64>,
1001    network: crate::types::Network,
1002) -> Result<bool> {
1003    // Convert prevouts to parallel slices for the optimized API
1004    let prevout_values: Vec<i64> = prevouts.iter().map(|p| p.value).collect();
1005    let prevout_script_pubkeys: Vec<&[u8]> =
1006        prevouts.iter().map(|p| p.script_pubkey.as_ref()).collect();
1007
1008    verify_script_with_context_full(
1009        script_sig,
1010        script_pubkey,
1011        witness,
1012        flags,
1013        tx,
1014        input_index,
1015        &prevout_values,
1016        &prevout_script_pubkeys,
1017        block_height,
1018        None, // median_time_past
1019        network,
1020        SigVersion::Base, // _sigversion is ignored inside full; SigVersion is inferred from script
1021        #[cfg(feature = "production")]
1022        None, // schnorr_collector
1023        None,             // precomputed_bip143
1024        #[cfg(feature = "production")]
1025        None, // precomputed_sighash_all
1026        #[cfg(feature = "production")]
1027        None, // sighash_cache
1028        #[cfg(feature = "production")]
1029        None, // precomputed_p2pkh_hash
1030        #[cfg(all(feature = "production", feature = "blvm-secp256k1"))]
1031        None, // ecdsa_collect
1032    )
1033}
1034
1035/// Bare pay-to-pubkey whose push opcode is the key length, then `OP_CHECKSIG`.
1036///
1037/// `PUSH_33_BYTES` is a 33-byte key. `PUSH_65_BYTES` is a 65-byte key. A push
1038/// that does not match the script length is not this template.
1039pub(crate) fn canonical_p2pk_pubkey(script: &[u8]) -> Option<&[u8]> {
1040    let key_len = match script.first().copied()? {
1041        PUSH_33_BYTES => PUSH_33_BYTES as usize,
1042        PUSH_65_BYTES => PUSH_65_BYTES as usize,
1043        _ => return None,
1044    };
1045    if script.len() != key_len + 2 || script[script.len() - 1] != OP_CHECKSIG {
1046        return None;
1047    }
1048    Some(&script[1..1 + key_len])
1049}
1050
1051/// P2PK fast-path. Bare pay-to-pubkey: scriptPubKey is `pubkey` + OP_CHECKSIG, scriptSig is `sig`.
1052/// Common in early blocks (coinbase outputs). Returns Some(Ok(bool)) if handled; None to fall back.
1053/// Uses full transaction context (height, network) for BIP66 / signature validation.
1054///
1055/// For the Orange Paper **`VerifyScript`** contract (scriptSig + scriptPubKey + witness + flags),
1056/// see [`verify_script`], [`verify_script_with_context`], and [`verify_script_with_context_full`].
1057#[cfg(feature = "production")]
1058#[allow(clippy::too_many_arguments)]
1059pub fn try_verify_p2pk_fast_path(
1060    script_sig: &ByteString,
1061    script_pubkey: &[u8],
1062    flags: u32,
1063    tx: &Transaction,
1064    input_index: usize,
1065    prevout_values: &[i64],
1066    prevout_script_pubkeys: &[&[u8]],
1067    block_height: Option<u64>,
1068    network: crate::types::Network,
1069    #[cfg(feature = "production")] sighash_cache: Option<
1070        &crate::transaction_hash::SighashMidstateCache,
1071    >,
1072) -> Option<Result<bool>> {
1073    let pubkey_bytes = canonical_p2pk_pubkey(script_pubkey)?;
1074
1075    let signature_bytes = parse_p2pk_script_sig(script_sig.as_ref())?;
1076    if signature_bytes.is_empty() {
1077        return Some(Ok(false));
1078    }
1079
1080    // Fast-path: P2PK script_pubkey is 35 or 67 bytes; signature push ≥71. Skip serialize+find_and_delete.
1081    use crate::transaction_hash::{SighashType, calculate_transaction_sighash_single_input};
1082    let sighash_byte = signature_bytes[signature_bytes.len() - 1];
1083    let sighash_type = SighashType::from_byte(sighash_byte);
1084    let deleted_storage;
1085    let script_code: &[u8] = if script_pubkey.len() < 71 {
1086        script_pubkey
1087    } else {
1088        deleted_storage = with_push_pattern(signature_bytes, |pattern| {
1089            find_and_delete(script_pubkey, pattern)
1090        });
1091        deleted_storage.as_ref()
1092    };
1093    let sighash = match calculate_transaction_sighash_single_input(
1094        tx,
1095        input_index,
1096        script_code,
1097        prevout_values[input_index],
1098        sighash_type,
1099        #[cfg(feature = "production")]
1100        sighash_cache,
1101    ) {
1102        Ok(h) => h,
1103        Err(e) => return Some(Err(e)),
1104    };
1105
1106    let height = block_height.unwrap_or(0);
1107    let is_valid = signature::with_secp_context(|secp| {
1108        signature::verify_signature(
1109            secp,
1110            pubkey_bytes,
1111            signature_bytes,
1112            &sighash,
1113            flags,
1114            height,
1115            network,
1116            SigVersion::Base,
1117        )
1118    });
1119    Some(is_valid)
1120}
1121
1122/// P2PKH fast-path. Returns Some(Ok(bool)) if script is P2PKH and we handled it;
1123/// Returns None to fall back to full interpreter.
1124#[cfg(feature = "production")]
1125#[allow(clippy::too_many_arguments)]
1126pub fn try_verify_p2pkh_fast_path(
1127    script_sig: &ByteString,
1128    script_pubkey: &[u8],
1129    flags: u32,
1130    tx: &Transaction,
1131    input_index: usize,
1132    prevout_values: &[i64],
1133    prevout_script_pubkeys: &[&[u8]],
1134    block_height: Option<u64>,
1135    network: crate::types::Network,
1136    #[cfg(feature = "production")] precomputed_sighash_all: Option<[u8; 32]>,
1137    #[cfg(feature = "production")] sighash_cache: Option<
1138        &crate::transaction_hash::SighashMidstateCache,
1139    >,
1140    #[cfg(feature = "production")] precomputed_p2pkh_hash: Option<[u8; 20]>,
1141) -> Option<Result<bool>> {
1142    #[cfg(all(feature = "production", feature = "profile"))]
1143    let _t_entry = std::time::Instant::now();
1144    // P2PKH scriptPubKey: 25 bytes = OP_DUP OP_HASH160 PUSH_20_BYTES <20 bytes> OP_EQUALVERIFY OP_CHECKSIG
1145    if script_pubkey.len() != 25 {
1146        return None;
1147    }
1148    if script_pubkey[0] != OP_DUP
1149        || script_pubkey[1] != OP_HASH160
1150        || script_pubkey[2] != PUSH_20_BYTES
1151        || script_pubkey[23] != OP_EQUALVERIFY
1152        || script_pubkey[24] != OP_CHECKSIG
1153    {
1154        return None;
1155    }
1156    let expected_hash = &script_pubkey[3..23];
1157
1158    #[cfg(all(feature = "production", feature = "profile"))]
1159    crate::script_profile::add_p2pkh_fast_path_entry_ns(_t_entry.elapsed().as_nanos() as u64);
1160    #[cfg(all(feature = "production", feature = "profile"))]
1161    let _t_parse = std::time::Instant::now();
1162    let (signature_bytes, pubkey_bytes) = parse_p2pkh_script_sig(script_sig.as_ref())?;
1163    #[cfg(all(feature = "production", feature = "profile"))]
1164    crate::script_profile::add_p2pkh_parse_ns(_t_parse.elapsed().as_nanos() as u64);
1165
1166    // Pubkey must be 33 (compressed) or 65 (uncompressed) bytes
1167    if pubkey_bytes.len() != 33 && pubkey_bytes.len() != 65 {
1168        return Some(Ok(false));
1169    }
1170    // Empty signature is valid script but leaves 0 on stack -> verification false
1171    if signature_bytes.is_empty() {
1172        return Some(Ok(false));
1173    }
1174
1175    // HASH160(pubkey) == expected hash (or use precomputed when provided by batch path)
1176    let pubkey_hash: [u8; 20] = match precomputed_p2pkh_hash {
1177        Some(h) => h,
1178        None => {
1179            #[cfg(all(feature = "production", feature = "profile"))]
1180            let _t_hash = std::time::Instant::now();
1181            let sha256_hash = OptimizedSha256::new().hash(pubkey_bytes);
1182            let h = Ripemd160::digest(sha256_hash);
1183            #[cfg(all(feature = "production", feature = "profile"))]
1184            crate::script_profile::add_p2pkh_hash160_ns(_t_hash.elapsed().as_nanos() as u64);
1185            h.into()
1186        }
1187    };
1188    if &pubkey_hash[..] != expected_hash {
1189        return Some(Ok(false));
1190    }
1191
1192    // Legacy sighash: scriptCode = FindAndDelete(script_pubkey, serialize(signature))
1193    // Roadmap #12: use precomputed when available (batch sighash for P2PKH).
1194    use crate::transaction_hash::SighashType;
1195    let sighash_byte = signature_bytes[signature_bytes.len() - 1];
1196    let sighash_type = SighashType::from_byte(sighash_byte);
1197    let deleted_storage;
1198    let script_code: &[u8] = if script_pubkey.len() < 71 {
1199        script_pubkey
1200    } else {
1201        deleted_storage = with_push_pattern(signature_bytes, |pattern| {
1202            find_and_delete(script_pubkey, pattern)
1203        });
1204        deleted_storage.as_ref()
1205    };
1206    let sighash = {
1207        #[cfg(feature = "production")]
1208        {
1209            if let Some(precomp) = precomputed_sighash_all {
1210                precomp
1211            } else {
1212                crate::transaction_hash::compute_legacy_sighash_nocache(
1213                    tx,
1214                    input_index,
1215                    script_code,
1216                    sighash_byte,
1217                )
1218            }
1219        }
1220        #[cfg(not(feature = "production"))]
1221        {
1222            match calculate_transaction_sighash_single_input(
1223                tx,
1224                input_index,
1225                script_code,
1226                prevout_values[input_index],
1227                sighash_type,
1228            ) {
1229                Ok(h) => h,
1230                Err(e) => return Some(Err(e)),
1231            }
1232        }
1233    };
1234
1235    #[cfg(all(feature = "production", feature = "profile"))]
1236    let _t_secp = std::time::Instant::now();
1237    let height = block_height.unwrap_or(0);
1238    let is_valid: Result<bool> = {
1239        let der_sig = &signature_bytes[..signature_bytes.len() - 1];
1240        if flags & 0x04 != 0
1241            && !crate::bip_validation::check_bip66_network(signature_bytes, height, network)
1242                .unwrap_or(false)
1243        {
1244            Ok(false)
1245        } else if flags & 0x02 != 0 {
1246            let base_sighash = sighash_byte & !0x80;
1247            if !(0x01..=0x03).contains(&base_sighash) {
1248                Ok(false)
1249            } else if pubkey_bytes.len() == 33 {
1250                if pubkey_bytes[0] != 0x02 && pubkey_bytes[0] != 0x03 {
1251                    Ok(false)
1252                } else {
1253                    let strict_der = flags & 0x04 != 0;
1254                    let enforce_low_s = flags & 0x08 != 0;
1255                    Ok(crate::secp256k1_backend::verify_ecdsa_direct(
1256                        der_sig,
1257                        pubkey_bytes,
1258                        &sighash,
1259                        strict_der,
1260                        enforce_low_s,
1261                    )
1262                    .unwrap_or(false))
1263                }
1264            } else if pubkey_bytes.len() == 65 && pubkey_bytes[0] == 0x04 {
1265                let strict_der = flags & 0x04 != 0;
1266                let enforce_low_s = flags & 0x08 != 0;
1267                Ok(crate::secp256k1_backend::verify_ecdsa_direct(
1268                    der_sig,
1269                    pubkey_bytes,
1270                    &sighash,
1271                    strict_der,
1272                    enforce_low_s,
1273                )
1274                .unwrap_or(false))
1275            } else {
1276                Ok(false)
1277            }
1278        } else {
1279            let strict_der = flags & 0x04 != 0;
1280            let enforce_low_s = flags & 0x08 != 0;
1281            Ok(crate::secp256k1_backend::verify_ecdsa_direct(
1282                der_sig,
1283                pubkey_bytes,
1284                &sighash,
1285                strict_der,
1286                enforce_low_s,
1287            )
1288            .unwrap_or(false))
1289        }
1290    };
1291    #[cfg(all(feature = "production", feature = "profile"))]
1292    {
1293        let ns = _t_secp.elapsed().as_nanos() as u64;
1294        crate::script_profile::add_p2pkh_collect_ns(ns);
1295        crate::script_profile::add_p2pkh_secp_context_ns(ns);
1296    }
1297    Some(is_valid)
1298}
1299
1300/// Fully inlined P2PKH verification for the rayon fast path.
1301/// Caller MUST have already verified script_pubkey is a valid P2PKH (25 bytes, correct opcodes).
1302/// Eliminates: redundant pattern check, Option unwrapping for precomputed values (always None),
1303/// assumevalid lookup, sighash cache overhead.
1304/// Returns Ok(true/false) directly — no Option wrapping.
1305#[cfg(feature = "production")]
1306#[inline]
1307pub fn verify_p2pkh_inline(
1308    script_sig: &[u8],
1309    script_pubkey: &[u8],
1310    flags: u32,
1311    tx: &Transaction,
1312    input_index: usize,
1313    height: u64,
1314    network: crate::types::Network,
1315    precomputed_sighash_all: Option<[u8; 32]>,
1316    precomputed_p2pkh_hash: Option<[u8; 20]>,
1317    #[cfg(all(feature = "production", feature = "blvm-secp256k1"))] ecdsa_collect: Option<(
1318        &crate::ecdsa_batch::EcdsaSignatureCollector,
1319        usize,
1320    )>,
1321) -> Result<bool> {
1322    let (signature_bytes, pubkey_bytes) = match parse_p2pkh_script_sig(script_sig) {
1323        Some(pair) => pair,
1324        None => return Ok(false),
1325    };
1326    verify_p2pkh_inline_parsed(
1327        signature_bytes,
1328        pubkey_bytes,
1329        script_pubkey,
1330        flags,
1331        tx,
1332        input_index,
1333        height,
1334        network,
1335        precomputed_sighash_all,
1336        precomputed_p2pkh_hash,
1337        #[cfg(all(feature = "production", feature = "blvm-secp256k1"))]
1338        ecdsa_collect,
1339    )
1340}
1341
1342/// Like [`verify_p2pkh_inline`] but skips a second `parse_p2pkh_script_sig` when the
1343/// caller already parsed (IBD serial / collect hot path).
1344#[cfg(feature = "production")]
1345#[inline]
1346pub fn verify_p2pkh_inline_parsed(
1347    signature_bytes: &[u8],
1348    pubkey_bytes: &[u8],
1349    script_pubkey: &[u8],
1350    flags: u32,
1351    tx: &Transaction,
1352    input_index: usize,
1353    height: u64,
1354    network: crate::types::Network,
1355    precomputed_sighash_all: Option<[u8; 32]>,
1356    precomputed_p2pkh_hash: Option<[u8; 20]>,
1357    #[cfg(all(feature = "production", feature = "blvm-secp256k1"))] ecdsa_collect: Option<(
1358        &crate::ecdsa_batch::EcdsaSignatureCollector,
1359        usize,
1360    )>,
1361) -> Result<bool> {
1362    #[cfg(feature = "profile")]
1363    let _t0 = std::time::Instant::now();
1364
1365    let expected_hash = &script_pubkey[3..23];
1366
1367    if (pubkey_bytes.len() != 33 && pubkey_bytes.len() != 65) || signature_bytes.is_empty() {
1368        return Ok(false);
1369    }
1370
1371    #[cfg(feature = "profile")]
1372    let _t_hash = std::time::Instant::now();
1373
1374    let pubkey_hash: [u8; 20] = match precomputed_p2pkh_hash {
1375        Some(h) => h,
1376        None => {
1377            let sha256_hash = OptimizedSha256::new().hash(pubkey_bytes);
1378            Ripemd160::digest(sha256_hash).into()
1379        }
1380    };
1381    if &pubkey_hash[..] != expected_hash {
1382        return Ok(false);
1383    }
1384
1385    #[cfg(feature = "profile")]
1386    crate::script_profile::add_p2pkh_hash160_ns(_t_hash.elapsed().as_nanos() as u64);
1387
1388    #[cfg(feature = "profile")]
1389    let _t_sighash = std::time::Instant::now();
1390
1391    let sighash_byte = signature_bytes[signature_bytes.len() - 1];
1392    let sighash = if let Some(precomp) = precomputed_sighash_all {
1393        precomp
1394    } else {
1395        // Incremental hasher (REVERT calculate-path: S10 189.4 vs nocache 195.6).
1396        crate::transaction_hash::compute_legacy_sighash_nocache(
1397            tx,
1398            input_index,
1399            script_pubkey,
1400            sighash_byte,
1401        )
1402    };
1403
1404    #[cfg(feature = "profile")]
1405    crate::script_profile::add_sighash_ns(_t_sighash.elapsed().as_nanos() as u64);
1406
1407    let der_sig = &signature_bytes[..signature_bytes.len() - 1];
1408    let strict_der = flags & 0x04 != 0;
1409    let enforce_low_s = flags & 0x08 != 0;
1410
1411    if strict_der
1412        && !crate::bip_validation::check_bip66_network(signature_bytes, height, network)
1413            .unwrap_or(false)
1414    {
1415        return Ok(false);
1416    }
1417
1418    crate::script::signature::reject_strictenc(flags, sighash_byte, pubkey_bytes)?;
1419
1420    #[cfg(feature = "profile")]
1421    let _t_secp = std::time::Instant::now();
1422
1423    #[cfg(all(feature = "production", feature = "blvm-secp256k1"))]
1424    if let Some((collector, global_idx)) = ecdsa_collect {
1425        // Defer secp to block-end batch (CPU / optional GPU). Need compressed pk + compact sig.
1426        let compact =
1427            match blvm_secp256k1::ecdsa::ecdsa_der_to_compact(der_sig, strict_der, enforce_low_s) {
1428                Some(c) => c,
1429                None => return Ok(false),
1430            };
1431        let pk33 = match pubkey_bytes.len() {
1432            33 => {
1433                let mut a = [0u8; 33];
1434                a.copy_from_slice(pubkey_bytes);
1435                a
1436            }
1437            65 => match blvm_secp256k1::ecdsa::ge_from_pubkey_bytes(pubkey_bytes) {
1438                Some(ge) => blvm_secp256k1::ecdsa::ge_to_compressed(&ge),
1439                None => return Ok(false),
1440            },
1441            _ => return Ok(false),
1442        };
1443        #[cfg(feature = "profile")]
1444        {
1445            crate::script_profile::add_p2pkh_collect_ns(_t_secp.elapsed().as_nanos() as u64);
1446            crate::script_profile::add_p2pkh_fast_path_entry_ns(_t0.elapsed().as_nanos() as u64);
1447        }
1448        collector.collect_with_index(global_idx, &sighash, &pk33, &compact);
1449        return Ok(true);
1450    }
1451
1452    let result = crate::secp256k1_backend::verify_ecdsa_direct(
1453        der_sig,
1454        pubkey_bytes,
1455        &sighash,
1456        strict_der,
1457        enforce_low_s,
1458    )
1459    .unwrap_or(false);
1460
1461    #[cfg(feature = "profile")]
1462    crate::script_profile::add_p2pkh_secp_context_ns(_t_secp.elapsed().as_nanos() as u64);
1463
1464    #[cfg(feature = "profile")]
1465    crate::script_profile::add_p2pkh_fast_path_entry_ns(_t0.elapsed().as_nanos() as u64);
1466
1467    Ok(result)
1468}
1469
1470/// P2PK (Pay-to-Public-Key) inline verify.
1471#[cfg(feature = "production")]
1472#[inline]
1473pub fn verify_p2pk_inline(
1474    script_sig: &[u8],
1475    script_pubkey: &[u8],
1476    flags: u32,
1477    tx: &Transaction,
1478    input_index: usize,
1479    height: u64,
1480    network: crate::types::Network,
1481    #[cfg(all(feature = "production", feature = "blvm-secp256k1"))] ecdsa_collect: Option<(
1482        &crate::ecdsa_batch::EcdsaSignatureCollector,
1483        usize,
1484    )>,
1485) -> Result<bool> {
1486    let Some(pubkey_bytes) = canonical_p2pk_pubkey(script_pubkey) else {
1487        return Ok(false);
1488    };
1489
1490    let signature_bytes = match parse_p2pk_script_sig(script_sig) {
1491        Some(s) => s,
1492        None => return Ok(false),
1493    };
1494    if signature_bytes.is_empty() {
1495        return Ok(false);
1496    }
1497
1498    let sighash_byte = signature_bytes[signature_bytes.len() - 1];
1499    let script_code: &[u8] = script_pubkey; // P2PK scriptPubKey < 71 bytes, no FindAndDelete
1500
1501    let sighash = crate::transaction_hash::compute_legacy_sighash_nocache(
1502        tx,
1503        input_index,
1504        script_code,
1505        sighash_byte,
1506    );
1507
1508    let der_sig = &signature_bytes[..signature_bytes.len() - 1];
1509    let strict_der = flags & flags::SCRIPT_VERIFY_DERSIG != 0;
1510    let enforce_low_s = flags & flags::SCRIPT_VERIFY_LOW_S != 0;
1511
1512    if strict_der
1513        && !crate::bip_validation::check_bip66_network(signature_bytes, height, network)
1514            .unwrap_or(false)
1515    {
1516        return Ok(false);
1517    }
1518
1519    crate::script::signature::reject_strictenc(flags, sighash_byte, pubkey_bytes)?;
1520
1521    #[cfg(all(feature = "production", feature = "blvm-secp256k1"))]
1522    if let Some((collector, global_idx)) = ecdsa_collect {
1523        let compact =
1524            match blvm_secp256k1::ecdsa::ecdsa_der_to_compact(der_sig, strict_der, enforce_low_s) {
1525                Some(c) => c,
1526                None => return Ok(false),
1527            };
1528        let pk33 = match pubkey_bytes.len() {
1529            33 => {
1530                let mut a = [0u8; 33];
1531                a.copy_from_slice(pubkey_bytes);
1532                a
1533            }
1534            65 => match blvm_secp256k1::ecdsa::ge_from_pubkey_bytes(pubkey_bytes) {
1535                Some(ge) => blvm_secp256k1::ecdsa::ge_to_compressed(&ge),
1536                None => return Ok(false),
1537            },
1538            _ => return Ok(false),
1539        };
1540        collector.collect_with_index(global_idx, &sighash, &pk33, &compact);
1541        return Ok(true);
1542    }
1543
1544    Ok(crate::secp256k1_backend::verify_ecdsa_direct(
1545        der_sig,
1546        pubkey_bytes,
1547        &sighash,
1548        strict_der,
1549        enforce_low_s,
1550    )
1551    .unwrap_or(false))
1552}
1553
1554/// Null-fail applies after CHECKMULTISIG has failed, and then only if a signature is non-empty.
1555fn failed_multisig_nullfail(flags: u32, success: bool, any_nonempty_signature: bool) -> Result<()> {
1556    if !success && flags & flags::SCRIPT_VERIFY_NULLFAIL != 0 && any_nonempty_signature {
1557        return Err(ConsensusError::ScriptErrorWithCode {
1558            code: ScriptErrorCode::SigNullFail,
1559            message: "OP_CHECKMULTISIG: non-null signature must not fail under NULLFAIL".into(),
1560        });
1561    }
1562    Ok(())
1563}
1564
1565/// P2SH-multisig fast path: when redeem script matches `OP_m <pubkeys> OP_n OP_CHECKMULTISIG`,
1566/// verify inline or defer cartesian trials to [`EcdsaSignatureCollector`] when provided.
1567/// Returns Some(Ok(true/false)) if we handled it, None to fall through to interpreter.
1568#[allow(clippy::too_many_arguments)]
1569fn try_verify_p2sh_multisig_fast_path(
1570    script_sig: &ByteString,
1571    script_pubkey: &[u8],
1572    flags: u32,
1573    tx: &Transaction,
1574    input_index: usize,
1575    prevout_values: &[i64],
1576    prevout_script_pubkeys: &[&[u8]],
1577    block_height: Option<u64>,
1578    network: crate::types::Network,
1579    #[cfg(feature = "production")] sighash_cache: Option<
1580        &crate::transaction_hash::SighashMidstateCache,
1581    >,
1582    #[cfg(all(feature = "production", feature = "blvm-secp256k1"))] ecdsa_collector: Option<
1583        &crate::ecdsa_batch::EcdsaSignatureCollector,
1584    >,
1585) -> Option<Result<bool>> {
1586    let _ = prevout_script_pubkeys;
1587    // P2SH scriptPubKey: OP_HASH160 PUSH_20 <20> OP_EQUAL
1588    if script_pubkey.len() != 23
1589        || script_pubkey[0] != OP_HASH160
1590        || script_pubkey[1] != PUSH_20_BYTES
1591        || script_pubkey[22] != OP_EQUAL
1592    {
1593        return None;
1594    }
1595    let pushes = parse_borrowed_pushes(script_sig.as_ref())?;
1596    if pushes.len() < 2 {
1597        return None;
1598    }
1599    let redeem = pushes.last().expect("at least 2 pushes").as_ref();
1600    let expected_hash = &script_pubkey[2..22];
1601    let sha256_hash = OptimizedSha256::new().hash(redeem);
1602    let redeem_hash = Ripemd160::digest(sha256_hash);
1603    if &redeem_hash[..] != expected_hash {
1604        return Some(Ok(false));
1605    }
1606    let (m, _n, pubkeys) = parse_redeem_multisig(redeem)?;
1607    // The redeem is the last push. Items under it are the stack, bottom to top.
1608    let stack = &pushes[..pushes.len() - 1];
1609    let Some((dummy, signatures)) = checkmultisig_window(stack, m, false) else {
1610        return Some(Ok(false));
1611    };
1612
1613    let height = block_height.unwrap_or(0);
1614    if (flags & flags::SCRIPT_VERIFY_NULLDUMMY) != 0 && !dummy.is_empty() {
1615        return Some(Ok(false));
1616    }
1617
1618    let cleaned = multisig_script_code(redeem, &signatures);
1619
1620    use crate::transaction_hash::{SighashType, calculate_transaction_sighash_single_input};
1621
1622    // IBD deferral: cartesian oracle → SoA/GPU; Core match + NULLFAIL at block end.
1623    #[cfg(all(feature = "production", feature = "blvm-secp256k1"))]
1624    if let Some(collector) = ecdsa_collector {
1625        let nullfail = (flags & flags::SCRIPT_VERIFY_NULLFAIL) != 0;
1626        let enforce_low_s = (flags & flags::SCRIPT_VERIFY_LOW_S) != 0;
1627        let strict_der = (flags & flags::SCRIPT_VERIFY_DERSIG) != 0
1628            || (flags & flags::SCRIPT_VERIFY_STRICTENC) != 0
1629            || (flags & flags::SCRIPT_VERIFY_LOW_S) != 0;
1630        let mut trial_indices = Vec::new();
1631        let mut sig_empty = Vec::with_capacity(signatures.len());
1632        // Reuse sighash when multiple sigs share the same sighash byte (common m-of-n).
1633        let mut sighash_by_type: Vec<(u8, [u8; 32])> = Vec::new();
1634        for sig_bytes in &signatures {
1635            if sig_bytes.is_empty() {
1636                sig_empty.push(true);
1637                continue;
1638            }
1639            sig_empty.push(false);
1640            let sighash_byte = sig_bytes[sig_bytes.len() - 1];
1641            let sighash =
1642                if let Some((_, h)) = sighash_by_type.iter().find(|(b, _)| *b == sighash_byte) {
1643                    *h
1644                } else {
1645                    let sighash_type = SighashType::from_byte(sighash_byte);
1646                    let h = match calculate_transaction_sighash_single_input(
1647                        tx,
1648                        input_index,
1649                        &cleaned,
1650                        prevout_values[input_index],
1651                        sighash_type,
1652                        sighash_cache,
1653                    ) {
1654                        Ok(h) => h,
1655                        Err(e) => return Some(Err(e)),
1656                    };
1657                    sighash_by_type.push((sighash_byte, h));
1658                    h
1659                };
1660            let der_sig = &sig_bytes[..sig_bytes.len() - 1];
1661            // Invalid DER / high-S: Core treats the sig as non-matching, not hard-fail
1662            // (NULLFAIL applied at resolve when the overall match fails).
1663            let compact =
1664                blvm_secp256k1::ecdsa::ecdsa_der_to_compact(der_sig, strict_der, enforce_low_s);
1665            for pubkey_bytes in &pubkeys {
1666                let gidx = collector.alloc_multisig_trial_index();
1667                trial_indices.push(gidx);
1668                let Some(compact) = compact.as_ref() else {
1669                    // No SoA row → resolve sees false (unwrap_or(false)).
1670                    continue;
1671                };
1672                // Core skips invalid pubkeys (no match). Do not Ok(false) the spend —
1673                // mainnet 442715/tx1351 is 2-of-3 with one off-curve PUB65.
1674                let pk33 = match pubkey_bytes.len() {
1675                    33 => {
1676                        let mut a = [0u8; 33];
1677                        a.copy_from_slice(pubkey_bytes);
1678                        a
1679                    }
1680                    65 => match blvm_secp256k1::ecdsa::ge_from_pubkey_bytes(pubkey_bytes) {
1681                        Some(ge) => blvm_secp256k1::ecdsa::ge_to_compressed(&ge),
1682                        None => continue,
1683                    },
1684                    _ => continue,
1685                };
1686                collector.collect_with_index(gidx, &sighash, &pk33, compact);
1687            }
1688        }
1689        collector.push_multisig_pending(crate::ecdsa_batch::MultisigPending {
1690            m,
1691            n_pubs: pubkeys.len(),
1692            trial_indices,
1693            sig_empty,
1694            nullfail,
1695        });
1696        #[cfg(feature = "profile")]
1697        crate::script_profile::note_arm_p2sh_multisig(0);
1698        return Some(Ok(true));
1699    }
1700
1701    let mut sig_index = 0;
1702    let mut valid_sigs = 0u8;
1703
1704    for pubkey_bytes in pubkeys {
1705        if sig_index >= signatures.len() {
1706            break;
1707        }
1708        if signatures[sig_index].is_empty() {
1709            continue;
1710        }
1711        let signature_bytes = &signatures[sig_index];
1712        let sighash_byte = signature_bytes[signature_bytes.len() - 1];
1713        let sighash_type = SighashType::from_byte(sighash_byte);
1714        let sighash = match calculate_transaction_sighash_single_input(
1715            tx,
1716            input_index,
1717            &cleaned,
1718            prevout_values[input_index],
1719            sighash_type,
1720            #[cfg(feature = "production")]
1721            sighash_cache,
1722        ) {
1723            Ok(h) => h,
1724            Err(e) => return Some(Err(e)),
1725        };
1726
1727        #[cfg(feature = "production")]
1728        let is_valid = signature::with_secp_context(|secp| {
1729            signature::verify_signature(
1730                secp,
1731                pubkey_bytes,
1732                signature_bytes,
1733                &sighash,
1734                flags,
1735                height,
1736                network,
1737                SigVersion::Base,
1738            )
1739        });
1740
1741        #[cfg(not(feature = "production"))]
1742        let is_valid = {
1743            let secp = signature::new_secp();
1744            signature::verify_signature(
1745                &secp,
1746                pubkey_bytes,
1747                signature_bytes,
1748                &sighash,
1749                flags,
1750                height,
1751                network,
1752                SigVersion::Base,
1753            )
1754        };
1755
1756        let is_valid = match is_valid {
1757            Ok(v) => v,
1758            Err(e) => return Some(Err(e)),
1759        };
1760
1761        if is_valid {
1762            valid_sigs += 1;
1763            sig_index += 1;
1764        }
1765    }
1766
1767    let success = signatures.len() == usize::from(m) && valid_sigs == m;
1768    if let Err(e) =
1769        failed_multisig_nullfail(flags, success, signatures.iter().any(|sig| !sig.is_empty()))
1770    {
1771        return Some(Err(e));
1772    }
1773    Some(Ok(success))
1774}
1775
1776/// IBD entry: P2SH-multisig with optional SoA deferral (see [`try_verify_p2sh_multisig_fast_path`]).
1777#[cfg(all(feature = "production", feature = "blvm-secp256k1"))]
1778#[allow(clippy::too_many_arguments)]
1779pub(crate) fn try_verify_p2sh_multisig_ibd(
1780    script_sig: &ByteString,
1781    script_pubkey: &[u8],
1782    flags: u32,
1783    tx: &Transaction,
1784    input_index: usize,
1785    prevout_values: &[i64],
1786    prevout_script_pubkeys: &[&[u8]],
1787    block_height: Option<u64>,
1788    network: crate::types::Network,
1789    sighash_cache: Option<&crate::transaction_hash::SighashMidstateCache>,
1790    ecdsa_collector: Option<&crate::ecdsa_batch::EcdsaSignatureCollector>,
1791) -> Option<Result<bool>> {
1792    try_verify_p2sh_multisig_fast_path(
1793        script_sig,
1794        script_pubkey,
1795        flags,
1796        tx,
1797        input_index,
1798        prevout_values,
1799        prevout_script_pubkeys,
1800        block_height,
1801        network,
1802        sighash_cache,
1803        ecdsa_collector,
1804    )
1805}
1806
1807/// Bare multisig fast path: scriptPubKey is `OP_m <pubkeys> OP_n OP_CHECKMULTISIG`.
1808/// scriptSig is bottom-to-top. The top `m` pushes are the signatures.
1809#[allow(clippy::too_many_arguments)]
1810fn try_verify_bare_multisig_fast_path(
1811    script_sig: &ByteString,
1812    script_pubkey: &[u8],
1813    flags: u32,
1814    tx: &Transaction,
1815    input_index: usize,
1816    prevout_values: &[i64],
1817    prevout_script_pubkeys: &[&[u8]],
1818    block_height: Option<u64>,
1819    network: crate::types::Network,
1820    #[cfg(feature = "production")] sighash_cache: Option<
1821        &crate::transaction_hash::SighashMidstateCache,
1822    >,
1823) -> Option<Result<bool>> {
1824    let (m, _n, pubkeys) = parse_redeem_multisig(script_pubkey)?;
1825    let pushes = parse_borrowed_pushes(script_sig.as_ref())?;
1826    let Some((dummy, signatures)) = checkmultisig_window(&pushes, m, false) else {
1827        return Some(Ok(false));
1828    };
1829
1830    let height = block_height.unwrap_or(0);
1831    if (flags & flags::SCRIPT_VERIFY_NULLDUMMY) != 0 && !dummy.is_empty() {
1832        return Some(Ok(false));
1833    }
1834
1835    let cleaned = multisig_script_code(script_pubkey, &signatures);
1836
1837    use crate::transaction_hash::{SighashType, calculate_transaction_sighash_single_input};
1838
1839    let mut sig_index = 0;
1840    let mut valid_sigs = 0u8;
1841
1842    for pubkey_bytes in pubkeys {
1843        if sig_index >= signatures.len() {
1844            break;
1845        }
1846        if signatures[sig_index].is_empty() {
1847            continue;
1848        }
1849        let signature_bytes = &signatures[sig_index];
1850        let sighash_byte = signature_bytes[signature_bytes.len() - 1];
1851        let sighash_type = SighashType::from_byte(sighash_byte);
1852        let sighash = match calculate_transaction_sighash_single_input(
1853            tx,
1854            input_index,
1855            &cleaned,
1856            prevout_values[input_index],
1857            sighash_type,
1858            #[cfg(feature = "production")]
1859            sighash_cache,
1860        ) {
1861            Ok(h) => h,
1862            Err(e) => return Some(Err(e)),
1863        };
1864
1865        #[cfg(feature = "production")]
1866        let is_valid = signature::with_secp_context(|secp| {
1867            signature::verify_signature(
1868                secp,
1869                pubkey_bytes,
1870                signature_bytes,
1871                &sighash,
1872                flags,
1873                height,
1874                network,
1875                SigVersion::Base,
1876            )
1877        });
1878
1879        #[cfg(not(feature = "production"))]
1880        let is_valid = {
1881            let secp = signature::new_secp();
1882            signature::verify_signature(
1883                &secp,
1884                pubkey_bytes,
1885                signature_bytes,
1886                &sighash,
1887                flags,
1888                height,
1889                network,
1890                SigVersion::Base,
1891            )
1892        };
1893
1894        let is_valid = match is_valid {
1895            Ok(v) => v,
1896            Err(e) => return Some(Err(e)),
1897        };
1898
1899        if is_valid {
1900            valid_sigs += 1;
1901            sig_index += 1;
1902        }
1903    }
1904
1905    let success = signatures.len() == usize::from(m) && valid_sigs == m;
1906    if let Err(e) =
1907        failed_multisig_nullfail(flags, success, signatures.iter().any(|sig| !sig.is_empty()))
1908    {
1909        return Some(Err(e));
1910    }
1911    Some(Ok(success))
1912}
1913
1914/// Verify a P2SH-P2PKH redeem from borrowed signature, pubkey, and redeem slices.
1915#[cfg(feature = "production")]
1916#[allow(clippy::too_many_arguments)]
1917fn verify_p2sh_p2pkh_redeem(
1918    signature_bytes: &[u8],
1919    pubkey_bytes: &[u8],
1920    redeem: &[u8],
1921    flags: u32,
1922    tx: &Transaction,
1923    input_index: usize,
1924    prevout_values: &[i64],
1925    block_height: Option<u64>,
1926    network: crate::types::Network,
1927    #[cfg(feature = "production")] sighash_cache: Option<
1928        &crate::transaction_hash::SighashMidstateCache,
1929    >,
1930    #[cfg(feature = "production")] precomputed_sighash_all: Option<[u8; 32]>,
1931    #[cfg(all(feature = "production", feature = "blvm-secp256k1"))] ecdsa_collect: Option<(
1932        &crate::ecdsa_batch::EcdsaSignatureCollector,
1933        usize,
1934    )>,
1935) -> Result<bool> {
1936    #[cfg(feature = "production")]
1937    let sighash = if let Some(precomp) = precomputed_sighash_all {
1938        precomp
1939    } else {
1940        use crate::transaction_hash::{SighashType, calculate_transaction_sighash_single_input};
1941        let sighash_byte = signature_bytes[signature_bytes.len() - 1];
1942        let sighash_type = SighashType::from_byte(sighash_byte);
1943        let deleted_storage;
1944        let script_code: &[u8] = if redeem.len() < 71 {
1945            redeem
1946        } else {
1947            deleted_storage =
1948                with_push_pattern(signature_bytes, |pattern| find_and_delete(redeem, pattern));
1949            deleted_storage.as_ref()
1950        };
1951        calculate_transaction_sighash_single_input(
1952            tx,
1953            input_index,
1954            script_code,
1955            prevout_values[input_index],
1956            sighash_type,
1957            sighash_cache,
1958        )?
1959    };
1960    #[cfg(not(feature = "production"))]
1961    let sighash = {
1962        use crate::transaction_hash::{SighashType, calculate_transaction_sighash_single_input};
1963        let sighash_byte = signature_bytes[signature_bytes.len() - 1];
1964        let sighash_type = SighashType::from_byte(sighash_byte);
1965        let deleted_storage;
1966        let script_code: &[u8] = if redeem.len() < 71 {
1967            redeem
1968        } else {
1969            deleted_storage =
1970                with_push_pattern(signature_bytes, |pattern| find_and_delete(redeem, pattern));
1971            deleted_storage.as_ref()
1972        };
1973        calculate_transaction_sighash_single_input(
1974            tx,
1975            input_index,
1976            script_code,
1977            prevout_values[input_index],
1978            sighash_type,
1979        )?
1980    };
1981    let height = block_height.unwrap_or(0);
1982    let der_sig = &signature_bytes[..signature_bytes.len() - 1];
1983    let strict_der = flags & 0x04 != 0;
1984    let enforce_low_s = flags & 0x08 != 0;
1985    if strict_der
1986        && !crate::bip_validation::check_bip66_network(signature_bytes, height, network)
1987            .unwrap_or(false)
1988    {
1989        return Ok(false);
1990    }
1991    crate::script::signature::reject_strictenc(
1992        flags,
1993        signature_bytes[signature_bytes.len() - 1],
1994        pubkey_bytes,
1995    )?;
1996    #[cfg(all(feature = "production", feature = "blvm-secp256k1"))]
1997    if let Some((collector, global_idx)) = ecdsa_collect {
1998        // Default-on; set BLVM_P2SH_P2PKH_SOA=0 to disable.
1999        if std::env::var_os("BLVM_P2SH_P2PKH_SOA").is_none_or(|v| v != "0") {
2000            let compact = match blvm_secp256k1::ecdsa::ecdsa_der_to_compact(
2001                der_sig,
2002                strict_der,
2003                enforce_low_s,
2004            ) {
2005                Some(c) => c,
2006                None => return Ok(false),
2007            };
2008            let pk33 = match pubkey_bytes.len() {
2009                33 => {
2010                    let mut a = [0u8; 33];
2011                    a.copy_from_slice(pubkey_bytes);
2012                    a
2013                }
2014                65 => match blvm_secp256k1::ecdsa::ge_from_pubkey_bytes(pubkey_bytes) {
2015                    Some(ge) => blvm_secp256k1::ecdsa::ge_to_compressed(&ge),
2016                    None => return Ok(false),
2017                },
2018                _ => return Ok(false),
2019            };
2020            collector.collect_with_index(global_idx, &sighash, &pk33, &compact);
2021            return Ok(true);
2022        }
2023    }
2024    signature::with_secp_context(|secp| {
2025        signature::verify_signature(
2026            secp,
2027            pubkey_bytes,
2028            signature_bytes,
2029            &sighash,
2030            flags,
2031            height,
2032            network,
2033            SigVersion::Base,
2034        )
2035    })
2036}
2037
2038/// Verify a P2SH-P2PK redeem from a borrowed signature and redeem script.
2039#[cfg(feature = "production")]
2040#[allow(clippy::too_many_arguments)]
2041fn verify_p2sh_p2pk_redeem(
2042    signature_bytes: &[u8],
2043    pubkey_bytes: &[u8],
2044    redeem: &[u8],
2045    flags: u32,
2046    tx: &Transaction,
2047    input_index: usize,
2048    prevout_values: &[i64],
2049    block_height: Option<u64>,
2050    network: crate::types::Network,
2051    #[cfg(feature = "production")] sighash_cache: Option<
2052        &crate::transaction_hash::SighashMidstateCache,
2053    >,
2054) -> Result<bool> {
2055    use crate::transaction_hash::{SighashType, calculate_transaction_sighash_single_input};
2056    let sighash_byte = signature_bytes[signature_bytes.len() - 1];
2057    let sighash_type = SighashType::from_byte(sighash_byte);
2058    // Redeem is 35 or 67 bytes. A signature push is at least 71, so FindAndDelete cannot match.
2059    let deleted_storage;
2060    let script_code: &[u8] = if redeem.len() < 71 {
2061        redeem
2062    } else {
2063        deleted_storage =
2064            with_push_pattern(signature_bytes, |pattern| find_and_delete(redeem, pattern));
2065        deleted_storage.as_ref()
2066    };
2067    let sighash = calculate_transaction_sighash_single_input(
2068        tx,
2069        input_index,
2070        script_code,
2071        prevout_values[input_index],
2072        sighash_type,
2073        #[cfg(feature = "production")]
2074        sighash_cache,
2075    )?;
2076    let height = block_height.unwrap_or(0);
2077    signature::with_secp_context(|secp| {
2078        signature::verify_signature(
2079            secp,
2080            pubkey_bytes,
2081            signature_bytes,
2082            &sighash,
2083            flags,
2084            height,
2085            network,
2086            SigVersion::Base,
2087        )
2088    })
2089}
2090
2091/// P2SH fast-path for regular P2SH (redeem script is not a witness program).
2092/// Skips scriptSig + scriptPubKey interpreter run; verifies hash then runs redeem script only.
2093/// Returns None to fall back to full interpreter (e.g. witness programs, non-P2SH).
2094#[cfg(feature = "production")]
2095#[allow(clippy::too_many_arguments)]
2096fn try_verify_p2sh_fast_path(
2097    script_sig: &ByteString,
2098    script_pubkey: &[u8],
2099    flags: u32,
2100    tx: &Transaction,
2101    input_index: usize,
2102    prevout_values: &[i64],
2103    prevout_script_pubkeys: &[&[u8]],
2104    block_height: Option<u64>,
2105    median_time_past: Option<u64>,
2106    network: crate::types::Network,
2107    #[cfg(feature = "production")] sighash_cache: Option<
2108        &crate::transaction_hash::SighashMidstateCache,
2109    >,
2110    #[cfg(feature = "production")] precomputed_sighash_all: Option<[u8; 32]>,
2111    #[cfg(all(feature = "production", feature = "blvm-secp256k1"))] ecdsa_collect: Option<(
2112        &crate::ecdsa_batch::EcdsaSignatureCollector,
2113        usize,
2114    )>,
2115) -> Option<Result<bool>> {
2116    if (flags & flags::SCRIPT_VERIFY_P2SH) == 0 {
2117        return None;
2118    }
2119    // P2SH scriptPubKey: 23 bytes = OP_HASH160 PUSH_20_BYTES <20 bytes> OP_EQUAL
2120    if script_pubkey.len() != 23
2121        || script_pubkey[0] != OP_HASH160
2122        || script_pubkey[1] != PUSH_20_BYTES
2123        || script_pubkey[22] != OP_EQUAL
2124    {
2125        return None;
2126    }
2127    let expected_hash = &script_pubkey[2..22];
2128
2129    // P2SH-P2PKH is three data pushes. Borrow them. The owned parse below is for
2130    // numeric pushes and redeems that still run the interpreter.
2131    if let Some((signature_bytes, pubkey_bytes, redeem)) =
2132        parse_p2sh_p2pkh_pushes(script_sig.as_ref())
2133    {
2134        let redeem_sha = OptimizedSha256::new().hash(redeem);
2135        let redeem_hash = Ripemd160::digest(redeem_sha);
2136        if &redeem_hash[..] != expected_hash {
2137            return Some(Ok(false));
2138        }
2139        if pubkey_bytes.len() == 33 || pubkey_bytes.len() == 65 {
2140            let pubkey_sha = OptimizedSha256::new().hash(pubkey_bytes);
2141            let pubkey_hash = Ripemd160::digest(pubkey_sha);
2142            if pubkey_hash[..] == redeem[3..23] {
2143                return Some(verify_p2sh_p2pkh_redeem(
2144                    signature_bytes,
2145                    pubkey_bytes,
2146                    redeem,
2147                    flags,
2148                    tx,
2149                    input_index,
2150                    prevout_values,
2151                    block_height,
2152                    network,
2153                    #[cfg(feature = "production")]
2154                    sighash_cache,
2155                    #[cfg(feature = "production")]
2156                    precomputed_sighash_all,
2157                    #[cfg(all(feature = "production", feature = "blvm-secp256k1"))]
2158                    ecdsa_collect,
2159                ));
2160            }
2161        }
2162    }
2163
2164    // P2SH-P2PK is a signature push plus a 35-byte or 67-byte redeem.
2165    if let Some((signature_bytes, pubkey_bytes, redeem)) =
2166        parse_p2sh_p2pk_pushes(script_sig.as_ref())
2167    {
2168        let redeem_sha = OptimizedSha256::new().hash(redeem);
2169        let redeem_hash = Ripemd160::digest(redeem_sha);
2170        if &redeem_hash[..] != expected_hash {
2171            return Some(Ok(false));
2172        }
2173        return Some(verify_p2sh_p2pk_redeem(
2174            signature_bytes,
2175            pubkey_bytes,
2176            redeem,
2177            flags,
2178            tx,
2179            input_index,
2180            prevout_values,
2181            block_height,
2182            network,
2183            #[cfg(feature = "production")]
2184            sighash_cache,
2185        ));
2186    }
2187
2188    // The redeem is the last push and is only used as script bytes. The items
2189    // under it are copied because the interpreter pops them.
2190    let pushes = parse_borrowed_pushes(script_sig.as_ref())?;
2191    if pushes.is_empty() {
2192        return None;
2193    }
2194    let redeem = pushes.last().expect("at least one push").as_ref();
2195    let mut stack: Vec<StackElement> = pushes[..pushes.len() - 1]
2196        .iter()
2197        .map(|push| to_stack_element(push.as_ref()))
2198        .collect();
2199
2200    // Any witness-program redeem, including a future version. The interpreter
2201    // checks the hash, the canonical push, and that the program bytes are a
2202    // true stack value. Executing the redeem here would report success for a
2203    // base script without that check.
2204    if crate::witness::is_any_witness_program(redeem) {
2205        return None;
2206    }
2207
2208    // HASH160(redeem) == expected hash
2209    let sha256_hash = OptimizedSha256::new().hash(redeem.as_ref());
2210    let redeem_hash = Ripemd160::digest(sha256_hash);
2211    if &redeem_hash[..] != expected_hash {
2212        return Some(Ok(false));
2213    }
2214
2215    // P2SH-with-P2PKH-redeem fast-path: skip interpreter when redeem is P2PKH
2216    if redeem.len() == 25
2217        && redeem[0] == OP_DUP
2218        && redeem[1] == OP_HASH160
2219        && redeem[2] == PUSH_20_BYTES
2220        && redeem[23] == OP_EQUALVERIFY
2221        && redeem[24] == OP_CHECKSIG
2222        && stack.len() == 2
2223    {
2224        let signature_bytes = &stack[0];
2225        let pubkey_bytes = &stack[1];
2226        if (pubkey_bytes.len() == 33 || pubkey_bytes.len() == 65) && !signature_bytes.is_empty() {
2227            let expected_pubkey_hash = &redeem[3..23];
2228            let sha256_hash = OptimizedSha256::new().hash(pubkey_bytes);
2229            let pubkey_hash = Ripemd160::digest(sha256_hash);
2230            if &pubkey_hash[..] == expected_pubkey_hash {
2231                return Some(verify_p2sh_p2pkh_redeem(
2232                    signature_bytes,
2233                    pubkey_bytes,
2234                    redeem,
2235                    flags,
2236                    tx,
2237                    input_index,
2238                    prevout_values,
2239                    block_height,
2240                    network,
2241                    #[cfg(feature = "production")]
2242                    sighash_cache,
2243                    #[cfg(feature = "production")]
2244                    precomputed_sighash_all,
2245                    #[cfg(all(feature = "production", feature = "blvm-secp256k1"))]
2246                    ecdsa_collect,
2247                ));
2248            }
2249        }
2250    }
2251
2252    // P2SH-with-P2PK-redeem fast-path: redeem = push(pubkey) OP_CHECKSIG, stack = [sig]
2253    if stack.len() == 1 {
2254        if let Some(pubkey_bytes) = canonical_p2pk_pubkey(redeem) {
2255            let signature_bytes = &stack[0];
2256            if !signature_bytes.is_empty() {
2257                return Some(verify_p2sh_p2pk_redeem(
2258                    signature_bytes,
2259                    pubkey_bytes,
2260                    redeem,
2261                    flags,
2262                    tx,
2263                    input_index,
2264                    prevout_values,
2265                    block_height,
2266                    network,
2267                    #[cfg(feature = "production")]
2268                    sighash_cache,
2269                ));
2270            }
2271        }
2272    }
2273
2274    // Execute redeem script with remaining stack (same as regular P2SH path: no batch collector)
2275    let result = eval_script_with_context_full_inner(
2276        redeem,
2277        &mut stack,
2278        flags,
2279        tx,
2280        input_index,
2281        prevout_values,
2282        prevout_script_pubkeys,
2283        block_height,
2284        median_time_past,
2285        network,
2286        SigVersion::Base,
2287        Some(redeem),
2288        Some(script_sig), // FindAndDelete uses the same scriptSig as the slow path
2289        None,             // taproot_annex_hash
2290        None,             // tapscript_validation_weight
2291        #[cfg(feature = "production")]
2292        None, // schnorr_collector
2293        None,             // precomputed_bip143 - Base sigversion
2294        #[cfg(feature = "production")]
2295        sighash_cache,
2296    );
2297    // Base eval returns Ok(true) without looking at the stack. VerifyScript
2298    // still requires a truthy top, so a redeem of OP_0 fails. A clean stack
2299    // is one truthy item, and only when that policy flag is set.
2300    let result = match result {
2301        Ok(true) => {
2302            let top_true = !stack.is_empty() && cast_to_bool(stack.last().expect("redeem stack"));
2303            let ok = if flags & flags::SCRIPT_VERIFY_CLEANSTACK != 0 {
2304                stack.len() == 1 && top_true
2305            } else {
2306                top_true
2307            };
2308            Ok(ok)
2309        }
2310        other => other,
2311    };
2312    Some(result)
2313}
2314
2315/// DER, BIP66, strict encoding, optional SoA collect, then witness-v0 ECDSA.
2316///
2317/// Default-on SoA collect; set `BLVM_P2WPKH_SOA=0` to disable. A deferred GPU
2318/// path false-rejected mainnet block 497110 tx 330, so the env gate stays.
2319#[cfg(feature = "production")]
2320fn finish_p2wpkh_ecdsa(
2321    signature_bytes: &[u8],
2322    pubkey_bytes: &[u8],
2323    sighash: &[u8; 32],
2324    flags: u32,
2325    height: u64,
2326    network: crate::types::Network,
2327    #[cfg(all(feature = "production", feature = "blvm-secp256k1"))] ecdsa_collect: Option<(
2328        &crate::ecdsa_batch::EcdsaSignatureCollector,
2329        usize,
2330    )>,
2331) -> Result<bool> {
2332    let der_sig = &signature_bytes[..signature_bytes.len() - 1];
2333    let strict_der = flags & 0x04 != 0;
2334    let enforce_low_s = flags & 0x08 != 0;
2335    let sighash_byte = signature_bytes[signature_bytes.len() - 1];
2336
2337    if strict_der
2338        && !crate::bip_validation::check_bip66_network(signature_bytes, height, network)
2339            .unwrap_or(false)
2340    {
2341        return Ok(false);
2342    }
2343
2344    crate::script::signature::reject_strictenc(flags, sighash_byte, pubkey_bytes)?;
2345
2346    #[cfg(all(feature = "production", feature = "blvm-secp256k1"))]
2347    if let Some((collector, global_idx)) = ecdsa_collect {
2348        if std::env::var_os("BLVM_P2WPKH_SOA").is_none_or(|v| v != "0") {
2349            let compact = match blvm_secp256k1::ecdsa::ecdsa_der_to_compact(
2350                der_sig,
2351                strict_der,
2352                enforce_low_s,
2353            ) {
2354                Some(c) => c,
2355                None => return Ok(false),
2356            };
2357            let pk33 = match pubkey_bytes.len() {
2358                33 => {
2359                    let mut a = [0u8; 33];
2360                    a.copy_from_slice(pubkey_bytes);
2361                    a
2362                }
2363                65 => match blvm_secp256k1::ecdsa::ge_from_pubkey_bytes(pubkey_bytes) {
2364                    Some(ge) => blvm_secp256k1::ecdsa::ge_to_compressed(&ge),
2365                    None => return Ok(false),
2366                },
2367                _ => return Ok(false),
2368            };
2369            collector.collect_with_index(global_idx, sighash, &pk33, &compact);
2370            return Ok(true);
2371        }
2372    }
2373
2374    Ok(signature::with_secp_context(|secp| {
2375        signature::verify_signature(
2376            secp,
2377            pubkey_bytes,
2378            signature_bytes,
2379            sighash,
2380            flags,
2381            height,
2382            network,
2383            SigVersion::WitnessV0,
2384        )
2385    })
2386    .unwrap_or(false))
2387}
2388
2389/// Native P2WPKH inline verify (BIP143). Caller must gate scriptPubKey / witness shape.
2390#[cfg(feature = "production")]
2391#[allow(clippy::too_many_arguments)]
2392#[inline]
2393pub fn verify_p2wpkh_inline(
2394    witness: &crate::witness::Witness,
2395    script_pubkey: &[u8],
2396    flags: u32,
2397    tx: &Transaction,
2398    input_index: usize,
2399    prevout_value: i64,
2400    height: u64,
2401    network: crate::types::Network,
2402    precomputed_bip143: Option<&crate::transaction_hash::Bip143PrecomputedHashes>,
2403    precomputed_sighash_all: Option<[u8; 32]>,
2404    #[cfg(all(feature = "production", feature = "blvm-secp256k1"))] ecdsa_collect: Option<(
2405        &crate::ecdsa_batch::EcdsaSignatureCollector,
2406        usize,
2407    )>,
2408) -> Result<bool> {
2409    let signature_bytes = &witness[0];
2410    let pubkey_bytes = &witness[1];
2411    if (pubkey_bytes.len() != 33 && pubkey_bytes.len() != 65) || signature_bytes.is_empty() {
2412        return Ok(false);
2413    }
2414
2415    let expected_hash = &script_pubkey[2..22];
2416    let sha256_hash = OptimizedSha256::new().hash(pubkey_bytes);
2417    let pubkey_hash = Ripemd160::digest(sha256_hash);
2418    if &pubkey_hash[..] != expected_hash {
2419        return Ok(false);
2420    }
2421
2422    let p2pkh_script_code = bip143_p2wpkh_script_code(expected_hash);
2423    let sighash_byte = signature_bytes[signature_bytes.len() - 1];
2424    let sighash = if sighash_byte == 0x01 {
2425        if let Some(precomp) = precomputed_sighash_all {
2426            precomp
2427        } else {
2428            crate::transaction_hash::calculate_bip143_sighash(
2429                tx,
2430                input_index,
2431                &p2pkh_script_code,
2432                prevout_value,
2433                sighash_byte,
2434                precomputed_bip143,
2435            )?
2436        }
2437    } else {
2438        crate::transaction_hash::calculate_bip143_sighash(
2439            tx,
2440            input_index,
2441            &p2pkh_script_code,
2442            prevout_value,
2443            sighash_byte,
2444            precomputed_bip143,
2445        )?
2446    };
2447
2448    finish_p2wpkh_ecdsa(
2449        signature_bytes,
2450        pubkey_bytes,
2451        &sighash,
2452        flags,
2453        height,
2454        network,
2455        #[cfg(all(feature = "production", feature = "blvm-secp256k1"))]
2456        ecdsa_collect,
2457    )
2458}
2459
2460/// The script is one data push. Numeric opcodes are not this pattern.
2461#[cfg(feature = "production")]
2462#[inline(always)]
2463fn single_data_push(script: &[u8]) -> Option<&[u8]> {
2464    let (advance, start, end) = parse_one_data_push(script, 0)?;
2465    if advance != script.len() {
2466        return None;
2467    }
2468    Some(&script[start..end])
2469}
2470
2471/// P2WPKH-in-P2SH inline verify (BIP143). Caller must gate scriptPubKey / scriptSig / witness shape.
2472#[cfg(feature = "production")]
2473#[allow(clippy::too_many_arguments)]
2474#[inline]
2475pub fn verify_p2wpkh_in_p2sh_inline(
2476    script_sig: &[u8],
2477    witness: &crate::witness::Witness,
2478    script_pubkey: &[u8],
2479    flags: u32,
2480    tx: &Transaction,
2481    input_index: usize,
2482    prevout_value: i64,
2483    height: u64,
2484    network: crate::types::Network,
2485    precomputed_bip143: Option<&crate::transaction_hash::Bip143PrecomputedHashes>,
2486    #[cfg(all(feature = "production", feature = "blvm-secp256k1"))] ecdsa_collect: Option<(
2487        &crate::ecdsa_batch::EcdsaSignatureCollector,
2488        usize,
2489    )>,
2490) -> Result<bool> {
2491    let expected_hash = &script_pubkey[2..22];
2492    let Some(redeem) = single_data_push(script_sig) else {
2493        return Ok(false);
2494    };
2495    if redeem.len() != 22 || redeem[0] != OP_0 || redeem[1] != PUSH_20_BYTES {
2496        return Ok(false);
2497    }
2498    if !redeem_push_is_canonical(script_sig, redeem) {
2499        return Ok(false);
2500    }
2501    let sha256_hash = OptimizedSha256::new().hash(redeem.as_ref());
2502    let redeem_hash = Ripemd160::digest(sha256_hash);
2503    if &redeem_hash[..] != expected_hash {
2504        return Ok(false);
2505    }
2506
2507    if witness.len() != 2 {
2508        return Ok(false);
2509    }
2510    let signature_bytes = &witness[0];
2511    let pubkey_bytes = &witness[1];
2512    if (pubkey_bytes.len() != 33 && pubkey_bytes.len() != 65) || signature_bytes.is_empty() {
2513        return Ok(false);
2514    }
2515    let expected_pubkey_hash = &redeem[2..22];
2516    let pubkey_sha256 = OptimizedSha256::new().hash(pubkey_bytes);
2517    let pubkey_hash = Ripemd160::digest(pubkey_sha256);
2518    if &pubkey_hash[..] != expected_pubkey_hash {
2519        return Ok(false);
2520    }
2521
2522    let p2pkh_script_code = bip143_p2wpkh_script_code(expected_pubkey_hash);
2523    let sighash_byte = signature_bytes[signature_bytes.len() - 1];
2524    let sighash = crate::transaction_hash::calculate_bip143_sighash(
2525        tx,
2526        input_index,
2527        &p2pkh_script_code,
2528        prevout_value,
2529        sighash_byte,
2530        precomputed_bip143,
2531    )?;
2532
2533    finish_p2wpkh_ecdsa(
2534        signature_bytes,
2535        pubkey_bytes,
2536        &sighash,
2537        flags,
2538        height,
2539        network,
2540        #[cfg(all(feature = "production", feature = "blvm-secp256k1"))]
2541        ecdsa_collect,
2542    )
2543}
2544
2545/// P2WPKH fast-path (SegWit P2PKH). ScriptPubKey OP_0 <20-byte-hash>, witness [sig, pubkey].
2546/// Uses BIP143 sighash; skips interpreter. Returns None to fall back to full path.
2547#[cfg(feature = "production")]
2548#[allow(clippy::too_many_arguments)]
2549fn try_verify_p2wpkh_fast_path(
2550    script_sig: &ByteString,
2551    script_pubkey: &[u8],
2552    witness: &crate::witness::Witness,
2553    flags: u32,
2554    tx: &Transaction,
2555    input_index: usize,
2556    prevout_values: &[i64],
2557    prevout_script_pubkeys: &[&[u8]],
2558    block_height: Option<u64>,
2559    network: crate::types::Network,
2560    precomputed_bip143: Option<&crate::transaction_hash::Bip143PrecomputedHashes>,
2561    #[cfg(feature = "production")] precomputed_sighash_all: Option<[u8; 32]>,
2562    #[cfg(all(feature = "production", feature = "blvm-secp256k1"))] ecdsa_collect: Option<(
2563        &crate::ecdsa_batch::EcdsaSignatureCollector,
2564        usize,
2565    )>,
2566) -> Option<Result<bool>> {
2567    let _ = prevout_script_pubkeys;
2568    if flags & crate::script::flags::SCRIPT_VERIFY_WITNESS == 0 {
2569        return None;
2570    }
2571    // P2WPKH: 22 bytes = OP_0 PUSH_20_BYTES <20-byte-hash>
2572    if script_pubkey.len() != 22 || script_pubkey[0] != OP_0 || script_pubkey[1] != PUSH_20_BYTES {
2573        return None;
2574    }
2575    // Native SegWit: scriptSig must be empty
2576    if !script_sig.is_empty() {
2577        return None;
2578    }
2579    if witness.len() != 2 {
2580        return None;
2581    }
2582    let height = block_height.unwrap_or(0);
2583    let prevout_value = prevout_values.get(input_index).copied().unwrap_or(0);
2584    Some(verify_p2wpkh_inline(
2585        witness,
2586        script_pubkey,
2587        flags,
2588        tx,
2589        input_index,
2590        prevout_value,
2591        height,
2592        network,
2593        precomputed_bip143,
2594        precomputed_sighash_all,
2595        #[cfg(all(feature = "production", feature = "blvm-secp256k1"))]
2596        ecdsa_collect,
2597    ))
2598}
2599
2600/// P2WPKH-in-P2SH (nested SegWit). ScriptPubKey P2SH, scriptSig = \[redeem\], redeem = OP_0 + 20-byte pubkey hash, witness = \[sig, pubkey\].
2601#[cfg(feature = "production")]
2602#[allow(clippy::too_many_arguments)]
2603fn try_verify_p2wpkh_in_p2sh_fast_path(
2604    script_sig: &ByteString,
2605    script_pubkey: &[u8],
2606    witness: &crate::witness::Witness,
2607    flags: u32,
2608    tx: &Transaction,
2609    input_index: usize,
2610    prevout_values: &[i64],
2611    prevout_script_pubkeys: &[&[u8]],
2612    block_height: Option<u64>,
2613    network: crate::types::Network,
2614    precomputed_bip143: Option<&crate::transaction_hash::Bip143PrecomputedHashes>,
2615    #[cfg(all(feature = "production", feature = "blvm-secp256k1"))] ecdsa_collect: Option<(
2616        &crate::ecdsa_batch::EcdsaSignatureCollector,
2617        usize,
2618    )>,
2619) -> Option<Result<bool>> {
2620    let _ = prevout_script_pubkeys;
2621    if flags & crate::script::flags::SCRIPT_VERIFY_WITNESS == 0 {
2622        return None;
2623    }
2624    if (flags & flags::SCRIPT_VERIFY_P2SH) == 0 {
2625        return None;
2626    }
2627    if script_pubkey.len() != 23
2628        || script_pubkey[0] != OP_HASH160
2629        || script_pubkey[1] != PUSH_20_BYTES
2630        || script_pubkey[22] != OP_EQUAL
2631    {
2632        return None;
2633    }
2634    // P2WSH-in-P2SH also commonly has witness.len()==2 ([sig…, witnessScript]).
2635    // Only claim nested P2WPKH when redeem is the 22-byte v0 program; otherwise None
2636    // so the full P2SH/witness path runs (mainnet fail @497110 tx330 without this gate).
2637    if witness.len() != 2 {
2638        return None;
2639    }
2640    let redeem = single_data_push(script_sig.as_ref())?;
2641    if redeem.len() != 22 || redeem[0] != OP_0 || redeem[1] != PUSH_20_BYTES {
2642        return None;
2643    }
2644    if !redeem_push_is_canonical(script_sig.as_ref(), redeem) {
2645        return Some(Ok(false));
2646    }
2647    let height = block_height.unwrap_or(0);
2648    let prevout_value = prevout_values.get(input_index).copied().unwrap_or(0);
2649    Some(verify_p2wpkh_in_p2sh_inline(
2650        script_sig.as_ref(),
2651        witness,
2652        script_pubkey,
2653        flags,
2654        tx,
2655        input_index,
2656        prevout_value,
2657        height,
2658        network,
2659        precomputed_bip143,
2660        #[cfg(all(feature = "production", feature = "blvm-secp256k1"))]
2661        ecdsa_collect,
2662    ))
2663}
2664
2665/// One CHECKSIG in a witness script joins the block batch at `global_idx`.
2666/// `None` means the caller should verify inline (`BLVM_P2WPKH_SOA=0`).
2667#[cfg(all(feature = "production", feature = "blvm-secp256k1"))]
2668fn try_collect_witness_ecdsa(
2669    collector: &crate::ecdsa_batch::EcdsaSignatureCollector,
2670    global_idx: usize,
2671    signature_bytes: &[u8],
2672    pubkey_bytes: &[u8],
2673    sighash: &[u8; 32],
2674    flags: u32,
2675    height: u64,
2676    network: crate::types::Network,
2677) -> Option<Result<bool>> {
2678    if std::env::var_os("BLVM_P2WPKH_SOA").is_some_and(|v| v == "0") {
2679        return None;
2680    }
2681    let der_sig = &signature_bytes[..signature_bytes.len() - 1];
2682    let strict_der = flags & 0x04 != 0;
2683    let enforce_low_s = flags & 0x08 != 0;
2684    if strict_der
2685        && !crate::bip_validation::check_bip66_network(signature_bytes, height, network)
2686            .unwrap_or(false)
2687    {
2688        return Some(Ok(false));
2689    }
2690    if let Err(err) = crate::script::signature::reject_strictenc(
2691        flags,
2692        signature_bytes[signature_bytes.len() - 1],
2693        pubkey_bytes,
2694    ) {
2695        return Some(Err(err));
2696    }
2697    let compact =
2698        match blvm_secp256k1::ecdsa::ecdsa_der_to_compact(der_sig, strict_der, enforce_low_s) {
2699            Some(c) => c,
2700            None => return Some(Ok(false)),
2701        };
2702    let pk33 = match pubkey_bytes.len() {
2703        33 => {
2704            let mut a = [0u8; 33];
2705            a.copy_from_slice(pubkey_bytes);
2706            a
2707        }
2708        65 => match blvm_secp256k1::ecdsa::ge_from_pubkey_bytes(pubkey_bytes) {
2709            Some(ge) => blvm_secp256k1::ecdsa::ge_to_compressed(&ge),
2710            None => return Some(Ok(false)),
2711        },
2712        _ => return Some(Ok(false)),
2713    };
2714    collector.collect_with_index(global_idx, sighash, &pk33, &compact);
2715    Some(Ok(true))
2716}
2717
2718/// P2WSH-in-P2SH (nested). scriptSig = \[redeem\], redeem = OP_0 + 32-byte program; then same as native P2WSH.
2719/// Must run after nested-P2WPKH gate so 22-byte redeems are not claimed here.
2720#[cfg(feature = "production")]
2721#[allow(clippy::too_many_arguments)]
2722pub(crate) fn try_verify_p2wsh_in_p2sh_fast_path(
2723    script_sig: &ByteString,
2724    script_pubkey: &[u8],
2725    witness: &crate::witness::Witness,
2726    flags: u32,
2727    tx: &Transaction,
2728    input_index: usize,
2729    prevout_values: &[i64],
2730    prevout_script_pubkeys: &[&[u8]],
2731    block_height: Option<u64>,
2732    median_time_past: Option<u64>,
2733    network: crate::types::Network,
2734    schnorr_collector: Option<&crate::bip348::SchnorrSignatureCollector>,
2735    precomputed_bip143: Option<&crate::transaction_hash::Bip143PrecomputedHashes>,
2736    #[cfg(feature = "production")] sighash_cache: Option<
2737        &crate::transaction_hash::SighashMidstateCache,
2738    >,
2739    #[cfg(all(feature = "production", feature = "blvm-secp256k1"))] ecdsa_collect: Option<(
2740        &crate::ecdsa_batch::EcdsaSignatureCollector,
2741        usize,
2742    )>,
2743) -> Option<Result<bool>> {
2744    if flags & crate::script::flags::SCRIPT_VERIFY_WITNESS == 0 {
2745        return None;
2746    }
2747    if (flags & flags::SCRIPT_VERIFY_P2SH) == 0 {
2748        return None;
2749    }
2750    if script_pubkey.len() != 23
2751        || script_pubkey[0] != OP_HASH160
2752        || script_pubkey[1] != PUSH_20_BYTES
2753        || script_pubkey[22] != OP_EQUAL
2754    {
2755        return None;
2756    }
2757    let redeem = single_data_push(script_sig.as_ref())?;
2758    if redeem.len() != 34 || redeem[0] != OP_0 || redeem[1] != PUSH_32_BYTES {
2759        return None;
2760    }
2761    if !redeem_push_is_canonical(script_sig.as_ref(), redeem) {
2762        return Some(Ok(false));
2763    }
2764    let expected_hash = &script_pubkey[2..22];
2765    let sha256_hash = OptimizedSha256::new().hash(redeem.as_ref());
2766    let redeem_hash = Ripemd160::digest(sha256_hash);
2767    if &redeem_hash[..] != expected_hash {
2768        return Some(Ok(false));
2769    }
2770    // Native P2WSH path with an empty scriptSig and the redeem script as the v0 program.
2771    try_verify_p2wsh_fast_path(
2772        &[],
2773        redeem,
2774        witness,
2775        flags,
2776        tx,
2777        input_index,
2778        prevout_values,
2779        prevout_script_pubkeys,
2780        block_height,
2781        median_time_past,
2782        network,
2783        schnorr_collector,
2784        precomputed_bip143,
2785        #[cfg(feature = "production")]
2786        sighash_cache,
2787        #[cfg(all(feature = "production", feature = "blvm-secp256k1"))]
2788        ecdsa_collect,
2789    )
2790}
2791
2792/// P2WSH fast-path. ScriptPubKey OP_0 <32-byte-SHA256(witness_script)>; witness = [..., witness_script].
2793/// Verifies hash then executes witness script only. Returns None to fall back to full path.
2794#[cfg(feature = "production")]
2795#[allow(clippy::too_many_arguments)]
2796pub(crate) fn try_verify_p2wsh_fast_path(
2797    script_sig: &[u8],
2798    script_pubkey: &[u8],
2799    witness: &crate::witness::Witness,
2800    flags: u32,
2801    tx: &Transaction,
2802    input_index: usize,
2803    prevout_values: &[i64],
2804    prevout_script_pubkeys: &[&[u8]],
2805    block_height: Option<u64>,
2806    median_time_past: Option<u64>,
2807    network: crate::types::Network,
2808    schnorr_collector: Option<&crate::bip348::SchnorrSignatureCollector>,
2809    precomputed_bip143: Option<&crate::transaction_hash::Bip143PrecomputedHashes>,
2810    #[cfg(feature = "production")] sighash_cache: Option<
2811        &crate::transaction_hash::SighashMidstateCache,
2812    >,
2813    #[cfg(all(feature = "production", feature = "blvm-secp256k1"))] ecdsa_collect: Option<(
2814        &crate::ecdsa_batch::EcdsaSignatureCollector,
2815        usize,
2816    )>,
2817) -> Option<Result<bool>> {
2818    if flags & crate::script::flags::SCRIPT_VERIFY_WITNESS == 0 {
2819        return None;
2820    }
2821    // P2WSH: 34 bytes = OP_0 PUSH_32_BYTES <32-byte-hash>
2822    if script_pubkey.len() != 34 || script_pubkey[0] != OP_0 || script_pubkey[1] != PUSH_32_BYTES {
2823        return None;
2824    }
2825    if !script_sig.is_empty() {
2826        return None;
2827    }
2828    if witness.is_empty() {
2829        return None;
2830    }
2831    let max_witness_elem = MAX_SCRIPT_ELEMENT_SIZE;
2832    for element in witness.iter().take(witness.len().saturating_sub(1)) {
2833        if element.len() > max_witness_elem {
2834            return Some(Ok(false));
2835        }
2836    }
2837    // Borrow the witness script. A hash mismatch rejects before any stack copy.
2838    let witness_script: &[u8] = witness.last().expect("witness not empty").as_ref();
2839    let program_hash = &script_pubkey[2..34];
2840    let witness_script_hash = OptimizedSha256::new().hash(witness_script);
2841    if &witness_script_hash[..] != program_hash {
2842        return Some(Ok(false));
2843    }
2844
2845    // P2WSH always uses WitnessV0 sighash semantics (BIP143).
2846    // 0x8000 is SCRIPT_VERIFY_WITNESS_PUBKEYTYPE — a key-type strictness flag, not a
2847    // sigversion selector. Tapscript only applies inside Taproot script-path spends.
2848    let witness_sigversion = SigVersion::WitnessV0;
2849
2850    // P2WSH-with-P2PKH fast-path: witness_script = P2PKH, stack = [sig, pubkey]. BIP143 inline verify.
2851    if witness_sigversion == SigVersion::WitnessV0
2852        && witness_script.len() == 25
2853        && witness_script[0] == OP_DUP
2854        && witness_script[1] == OP_HASH160
2855        && witness_script[2] == PUSH_20_BYTES
2856        && witness_script[23] == OP_EQUALVERIFY
2857        && witness_script[24] == OP_CHECKSIG
2858        && witness.len() == 3
2859    {
2860        let signature_bytes: &[u8] = witness[0].as_ref();
2861        let pubkey_bytes: &[u8] = witness[1].as_ref();
2862        if (pubkey_bytes.len() == 33 || pubkey_bytes.len() == 65) && !signature_bytes.is_empty() {
2863            let expected_pubkey_hash = &witness_script[3..23];
2864            let pubkey_sha256 = OptimizedSha256::new().hash(pubkey_bytes);
2865            let pubkey_hash = Ripemd160::digest(pubkey_sha256);
2866            if &pubkey_hash[..] == expected_pubkey_hash {
2867                let sighash_byte = signature_bytes[signature_bytes.len() - 1];
2868                let amount = prevout_values.get(input_index).copied().unwrap_or(0);
2869                match crate::transaction_hash::calculate_bip143_sighash(
2870                    tx,
2871                    input_index,
2872                    witness_script,
2873                    amount,
2874                    sighash_byte,
2875                    precomputed_bip143,
2876                ) {
2877                    Ok(sighash) => {
2878                        let height = block_height.unwrap_or(0);
2879                        #[cfg(all(feature = "production", feature = "blvm-secp256k1"))]
2880                        if let Some((collector, global_idx)) = ecdsa_collect {
2881                            if let Some(verdict) = try_collect_witness_ecdsa(
2882                                collector,
2883                                global_idx,
2884                                signature_bytes,
2885                                pubkey_bytes,
2886                                &sighash,
2887                                flags,
2888                                height,
2889                                network,
2890                            ) {
2891                                return Some(verdict);
2892                            }
2893                        }
2894                        let is_valid = signature::with_secp_context(|secp| {
2895                            signature::verify_signature(
2896                                secp,
2897                                pubkey_bytes,
2898                                signature_bytes,
2899                                &sighash,
2900                                flags,
2901                                height,
2902                                network,
2903                                SigVersion::WitnessV0,
2904                            )
2905                        });
2906                        return Some(is_valid);
2907                    }
2908                    Err(e) => return Some(Err(e)),
2909                }
2910            }
2911        }
2912    }
2913
2914    // P2WSH-with-P2PK fast-path: witness_script = push(pubkey) OP_CHECKSIG, stack = [sig]. BIP143 inline verify.
2915    if witness_sigversion == SigVersion::WitnessV0 && witness.len() == 2 {
2916        if let Some(pubkey_bytes) = canonical_p2pk_pubkey(witness_script) {
2917            let signature_bytes: &[u8] = witness[0].as_ref();
2918            if !signature_bytes.is_empty() {
2919                let sighash_byte = signature_bytes[signature_bytes.len() - 1];
2920                let amount = prevout_values.get(input_index).copied().unwrap_or(0);
2921                match crate::transaction_hash::calculate_bip143_sighash(
2922                    tx,
2923                    input_index,
2924                    witness_script,
2925                    amount,
2926                    sighash_byte,
2927                    precomputed_bip143,
2928                ) {
2929                    Ok(sighash) => {
2930                        let height = block_height.unwrap_or(0);
2931                        #[cfg(all(feature = "production", feature = "blvm-secp256k1"))]
2932                        if let Some((collector, global_idx)) = ecdsa_collect {
2933                            if let Some(verdict) = try_collect_witness_ecdsa(
2934                                collector,
2935                                global_idx,
2936                                signature_bytes,
2937                                pubkey_bytes,
2938                                &sighash,
2939                                flags,
2940                                height,
2941                                network,
2942                            ) {
2943                                return Some(verdict);
2944                            }
2945                        }
2946                        let is_valid = signature::with_secp_context(|secp| {
2947                            signature::verify_signature(
2948                                secp,
2949                                pubkey_bytes,
2950                                signature_bytes,
2951                                &sighash,
2952                                flags,
2953                                height,
2954                                network,
2955                                SigVersion::WitnessV0,
2956                            )
2957                        });
2958                        return Some(is_valid);
2959                    }
2960                    Err(e) => return Some(Err(e)),
2961                }
2962            }
2963        }
2964    }
2965
2966    // P2WSH multisig: witness script is `OP_m <pubkeys> OP_n OP_CHECKMULTISIG`.
2967    // The stack under the script must be exactly the dummy plus `m` signatures.
2968    // Those items are borrowed from the witness. The interpreter copy is below.
2969    if witness_sigversion == SigVersion::WitnessV0 {
2970        if let Some((m, _n, pubkeys)) = parse_redeem_multisig(witness_script) {
2971            let items = &witness[..witness.len() - 1];
2972            let Some((dummy, signatures)) = checkmultisig_window(items, m, true) else {
2973                return Some(Ok(false));
2974            };
2975
2976            let height = block_height.unwrap_or(0);
2977            if (flags & flags::SCRIPT_VERIFY_NULLDUMMY) != 0 && !dummy.is_empty() {
2978                return Some(Ok(false));
2979            }
2980
2981            // WitnessV0 (BIP143): scriptCode is the witness script; no FindAndDelete.
2982            let script_code = witness_script;
2983            let amount = prevout_values.get(input_index).copied().unwrap_or(0);
2984
2985            // IBD deferral: cartesian oracle → SoA/GPU; Core match + NULLFAIL at block end.
2986            #[cfg(all(feature = "production", feature = "blvm-secp256k1"))]
2987            if let Some((collector, _)) = ecdsa_collect {
2988                let nullfail = (flags & flags::SCRIPT_VERIFY_NULLFAIL) != 0;
2989                let enforce_low_s = (flags & flags::SCRIPT_VERIFY_LOW_S) != 0;
2990                let strict_der = (flags & flags::SCRIPT_VERIFY_DERSIG) != 0
2991                    || (flags & flags::SCRIPT_VERIFY_STRICTENC) != 0
2992                    || (flags & flags::SCRIPT_VERIFY_LOW_S) != 0;
2993                let mut trial_indices = Vec::new();
2994                let mut sig_empty = Vec::with_capacity(signatures.len());
2995                // Reuse BIP143 sighash when multiple sigs share sighash byte (common m-of-n).
2996                let mut sighash_by_type: Vec<(u8, [u8; 32])> = Vec::new();
2997                for sig_bytes in &signatures {
2998                    if sig_bytes.is_empty() {
2999                        sig_empty.push(true);
3000                        continue;
3001                    }
3002                    sig_empty.push(false);
3003                    let sighash_byte = sig_bytes[sig_bytes.len() - 1];
3004                    let sighash = if let Some((_, h)) =
3005                        sighash_by_type.iter().find(|(b, _)| *b == sighash_byte)
3006                    {
3007                        *h
3008                    } else {
3009                        let h = match crate::transaction_hash::calculate_bip143_sighash(
3010                            tx,
3011                            input_index,
3012                            script_code,
3013                            amount,
3014                            sighash_byte,
3015                            precomputed_bip143,
3016                        ) {
3017                            Ok(h) => h,
3018                            Err(e) => return Some(Err(e)),
3019                        };
3020                        sighash_by_type.push((sighash_byte, h));
3021                        h
3022                    };
3023                    let der_sig = &sig_bytes[..sig_bytes.len() - 1];
3024                    let compact = blvm_secp256k1::ecdsa::ecdsa_der_to_compact(
3025                        der_sig,
3026                        strict_der,
3027                        enforce_low_s,
3028                    );
3029                    for pubkey_bytes in &pubkeys {
3030                        let gidx = collector.alloc_multisig_trial_index();
3031                        trial_indices.push(gidx);
3032                        let Some(compact) = compact.as_ref() else {
3033                            continue;
3034                        };
3035                        let pk33 = match pubkey_bytes.len() {
3036                            33 => {
3037                                let mut a = [0u8; 33];
3038                                a.copy_from_slice(pubkey_bytes);
3039                                a
3040                            }
3041                            65 => match blvm_secp256k1::ecdsa::ge_from_pubkey_bytes(pubkey_bytes) {
3042                                Some(ge) => blvm_secp256k1::ecdsa::ge_to_compressed(&ge),
3043                                None => continue,
3044                            },
3045                            _ => continue,
3046                        };
3047                        collector.collect_with_index(gidx, &sighash, &pk33, compact);
3048                    }
3049                }
3050                collector.push_multisig_pending(crate::ecdsa_batch::MultisigPending {
3051                    m,
3052                    n_pubs: pubkeys.len(),
3053                    trial_indices,
3054                    sig_empty,
3055                    nullfail,
3056                });
3057                #[cfg(feature = "profile")]
3058                crate::script_profile::note_arm_p2sh_multisig(0);
3059                return Some(Ok(true));
3060            }
3061
3062            let mut sig_index = 0;
3063            let mut valid_sigs = 0u8;
3064
3065            // Pubkeys are script order and signatures are bottom-to-top.
3066            // CHECKMULTISIG pops the top signature against the last pubkey first.
3067            for pubkey_bytes in pubkeys.iter().rev() {
3068                if sig_index >= signatures.len() {
3069                    break;
3070                }
3071                let signature_bytes = &signatures[signatures.len() - 1 - sig_index];
3072                if signature_bytes.is_empty() {
3073                    continue;
3074                }
3075                if flags & crate::script::flags::SCRIPT_VERIFY_WITNESS_PUBKEYTYPE != 0
3076                    && !(pubkey_bytes.len() == 33
3077                        && (pubkey_bytes[0] == 0x02 || pubkey_bytes[0] == 0x03))
3078                {
3079                    return Some(Err(ConsensusError::ScriptErrorWithCode {
3080                        code: ScriptErrorCode::WitnessPubkeyType,
3081                        message: "witness v0 public key must be compressed".into(),
3082                    }));
3083                }
3084                let sighash_byte = signature_bytes[signature_bytes.len() - 1];
3085                match crate::transaction_hash::calculate_bip143_sighash(
3086                    tx,
3087                    input_index,
3088                    script_code,
3089                    amount,
3090                    sighash_byte,
3091                    precomputed_bip143,
3092                ) {
3093                    Ok(sighash) => {
3094                        let is_valid = signature::with_secp_context(|secp| {
3095                            signature::verify_signature(
3096                                secp,
3097                                pubkey_bytes,
3098                                signature_bytes,
3099                                &sighash,
3100                                flags,
3101                                height,
3102                                network,
3103                                SigVersion::WitnessV0,
3104                            )
3105                        });
3106                        match is_valid {
3107                            Ok(v) if v => {
3108                                valid_sigs += 1;
3109                                sig_index += 1;
3110                            }
3111                            Ok(_) => {}
3112                            Err(e) => return Some(Err(e)),
3113                        }
3114                    }
3115                    Err(e) => return Some(Err(e)),
3116                }
3117            }
3118
3119            let success = signatures.len() == usize::from(m) && valid_sigs == m;
3120            if let Err(e) = failed_multisig_nullfail(
3121                flags,
3122                success,
3123                signatures.iter().any(|sig| !sig.is_empty()),
3124            ) {
3125                return Some(Err(e));
3126            }
3127            return Some(Ok(success));
3128        }
3129    }
3130
3131    // Witness script uses interpreter path (no batch collection).
3132    // CHECKMULTISIG in witness script can produce invalid sig/pubkey pairings for batch.
3133    let mut stack: Vec<StackElement> = witness
3134        .iter()
3135        .take(witness.len() - 1)
3136        .map(|w| to_stack_element(w))
3137        .collect();
3138    let result = eval_script_with_context_full_inner(
3139        witness_script,
3140        &mut stack,
3141        flags,
3142        tx,
3143        input_index,
3144        prevout_values,
3145        prevout_script_pubkeys,
3146        block_height,
3147        median_time_past,
3148        network,
3149        witness_sigversion,
3150        Some(witness_script), // BIP143 scriptCode for CHECKSIG inside P2WSH
3151        None,                 // script_sig_for_sighash (witness script context)
3152        None,                 // taproot_annex_hash
3153        None,                 // tapscript_validation_weight
3154        schnorr_collector,
3155        precomputed_bip143,
3156        #[cfg(feature = "production")]
3157        sighash_cache,
3158    );
3159    Some(result)
3160}
3161
3162/// P2TR script-path tapscript P2PK fast path. Tapscript PUSH_32_BYTES <32-byte-pubkey> OP_CHECKSIG, witness [sig, script, control_block].
3163#[cfg(feature = "production")]
3164#[allow(clippy::too_many_arguments)]
3165fn try_verify_p2tr_scriptpath_p2pk_fast_path(
3166    script_sig: &ByteString,
3167    script_pubkey: &[u8],
3168    witness: &crate::witness::Witness,
3169    _flags: u32,
3170    tx: &Transaction,
3171    input_index: usize,
3172    prevout_values: &[i64],
3173    prevout_script_pubkeys: &[&[u8]],
3174    block_height: Option<u64>,
3175    network: crate::types::Network,
3176    schnorr_collector: Option<&crate::bip348::SchnorrSignatureCollector>,
3177) -> Option<Result<bool>> {
3178    use crate::activation::taproot_active_at_height;
3179    use crate::taproot::parse_taproot_script_path_witness;
3180
3181    if !taproot_active_at_height(block_height, network) {
3182        return None;
3183    }
3184    if script_pubkey.len() != 34 || script_pubkey[0] != OP_1 || script_pubkey[1] != PUSH_32_BYTES {
3185        return None;
3186    }
3187    if !script_sig.is_empty() {
3188        return None;
3189    }
3190    if witness.len() < 2 {
3191        return None;
3192    }
3193    let mut output_key = [0u8; 32];
3194    output_key.copy_from_slice(&script_pubkey[2..34]);
3195    let (witness_body, annex_hash) = crate::taproot::strip_taproot_annex(witness);
3196    let parsed = match parse_taproot_script_path_witness(&witness_body, &output_key) {
3197        Ok(Some(p)) => p,
3198        Ok(None) | Err(_) => return None,
3199    };
3200    let (tapscript, stack_items, control_block) = parsed;
3201    if control_block.leaf_version != crate::taproot::TAPROOT_LEAF_VERSION_TAPSCRIPT {
3202        return None;
3203    }
3204    if tapscript.len() != 34 || tapscript[0] != PUSH_32_BYTES || tapscript[33] != OP_CHECKSIG {
3205        return None;
3206    }
3207    if stack_items.len() != 1 {
3208        return None;
3209    }
3210    let (sig_bytes, sighash_type) =
3211        crate::bip348::try_parse_taproot_schnorr_witness_sig(stack_items[0].as_ref())?;
3212    let pubkey_32 = &tapscript[1..33];
3213    let sighash = crate::taproot::compute_tapscript_signature_hash(
3214        tx,
3215        input_index,
3216        prevout_values,
3217        prevout_script_pubkeys,
3218        &tapscript,
3219        control_block.leaf_version,
3220        0xffff_ffff,
3221        sighash_type,
3222        annex_hash.as_ref(),
3223    )
3224    .ok()?;
3225    let result = crate::bip348::verify_tapscript_schnorr_signature(
3226        &sighash,
3227        pubkey_32,
3228        &sig_bytes,
3229        schnorr_collector,
3230    );
3231    Some(result)
3232}
3233
3234/// 64-byte Schnorr signatures use sighash `0x00`. A 65th byte of `0x00` is invalid.
3235fn parse_taproot_keypath_sig(sig_bytes: &[u8]) -> Option<([u8; 64], u8)> {
3236    match sig_bytes.len() {
3237        64 => {
3238            let mut sig = [0u8; 64];
3239            sig.copy_from_slice(sig_bytes);
3240            Some((sig, 0x00))
3241        }
3242        65 => {
3243            let hashtype = sig_bytes[64];
3244            if hashtype == 0x00 {
3245                return None;
3246            }
3247            let mut sig = [0u8; 64];
3248            sig.copy_from_slice(&sig_bytes[..64]);
3249            Some((sig, hashtype))
3250        }
3251        _ => None,
3252    }
3253}
3254
3255/// Taproot (P2TR) key-path fast-path. ScriptPubKey OP_1 <32-byte output key>, witness [64-byte sig].
3256/// Skips interpreter; verifies Schnorr directly. Returns None for script-path or pre-activation.
3257#[cfg(feature = "production")]
3258#[allow(clippy::too_many_arguments)]
3259fn verify_p2tr_keypath_witness(
3260    script_pubkey: &[u8],
3261    witness: &crate::witness::Witness,
3262    tx: &Transaction,
3263    input_index: usize,
3264    prevout_values: &[i64],
3265    prevout_script_pubkeys: &[&[u8]],
3266    schnorr_collector: Option<&crate::bip348::SchnorrSignatureCollector>,
3267) -> Result<bool> {
3268    use crate::bip348::{
3269        try_parse_taproot_schnorr_witness_sig, verify_tapscript_schnorr_signature,
3270    };
3271
3272    let (witness_body, annex_hash) = crate::taproot::strip_taproot_annex(witness);
3273    if witness_body.len() != 1 {
3274        return Ok(false);
3275    }
3276    let Some((sig_bytes, sighash_type)) = try_parse_taproot_schnorr_witness_sig(&witness_body[0])
3277    else {
3278        return Ok(false);
3279    };
3280    let output_key = &script_pubkey[2..34];
3281    let sighash = crate::taproot::compute_taproot_signature_hash(
3282        tx,
3283        input_index,
3284        prevout_values,
3285        prevout_script_pubkeys,
3286        sighash_type,
3287        annex_hash.as_ref(),
3288    )?;
3289    verify_tapscript_schnorr_signature(&sighash, output_key, &sig_bytes, schnorr_collector)
3290}
3291
3292#[cfg(feature = "production")]
3293#[allow(clippy::too_many_arguments)]
3294fn try_verify_p2tr_keypath_fast_path(
3295    script_sig: &ByteString,
3296    script_pubkey: &[u8],
3297    witness: &crate::witness::Witness,
3298    _flags: u32,
3299    tx: &Transaction,
3300    input_index: usize,
3301    prevout_values: &[i64],
3302    prevout_script_pubkeys: &[&[u8]],
3303    block_height: Option<u64>,
3304    network: crate::types::Network,
3305    schnorr_collector: Option<&crate::bip348::SchnorrSignatureCollector>,
3306) -> Option<Result<bool>> {
3307    use crate::activation::taproot_active_at_height;
3308    if !taproot_active_at_height(block_height, network) {
3309        return None;
3310    }
3311    // P2TR: 34 bytes = OP_1 PUSH_32_BYTES <32-byte output key>
3312    if script_pubkey.len() != 34 || script_pubkey[0] != OP_1 || script_pubkey[1] != PUSH_32_BYTES {
3313        return None;
3314    }
3315    if !script_sig.is_empty() {
3316        return None;
3317    }
3318    // Key-path: one stack element after optional annex strip (BIP340 Schnorr check).
3319    let (witness_body, _) = crate::taproot::strip_taproot_annex(witness);
3320    if witness_body.len() != 1 {
3321        return None;
3322    }
3323    crate::bip348::try_parse_taproot_schnorr_witness_sig(&witness_body[0])?;
3324    Some(verify_p2tr_keypath_witness(
3325        script_pubkey,
3326        witness,
3327        tx,
3328        input_index,
3329        prevout_values,
3330        prevout_script_pubkeys,
3331        schnorr_collector,
3332    ))
3333}
3334
3335#[cfg_attr(feature = "spec-lock", spec_locked("5.2", "VerifyScript"))]
3336pub fn verify_script_with_context_full(
3337    script_sig: &ByteString,
3338    script_pubkey: &[u8],
3339    witness: Option<&crate::witness::Witness>,
3340    flags: u32,
3341    tx: &Transaction,
3342    input_index: usize,
3343    prevout_values: &[i64],
3344    prevout_script_pubkeys: &[&[u8]],
3345    block_height: Option<u64>,
3346    median_time_past: Option<u64>,
3347    network: crate::types::Network,
3348    _sigversion: SigVersion,
3349    #[cfg(feature = "production")] schnorr_collector: Option<
3350        &crate::bip348::SchnorrSignatureCollector,
3351    >,
3352    precomputed_bip143: Option<&crate::transaction_hash::Bip143PrecomputedHashes>,
3353    #[cfg(feature = "production")] precomputed_sighash_all: Option<[u8; 32]>,
3354    #[cfg(feature = "production")] sighash_cache: Option<
3355        &crate::transaction_hash::SighashMidstateCache,
3356    >,
3357    #[cfg(feature = "production")] precomputed_p2pkh_hash: Option<[u8; 20]>,
3358    #[cfg(all(feature = "production", feature = "blvm-secp256k1"))] ecdsa_collect: Option<(
3359        &crate::ecdsa_batch::EcdsaSignatureCollector,
3360        usize,
3361    )>,
3362) -> Result<bool> {
3363    // libbitcoin-consensus check (multi-input verify_script): prevouts length must match vin size
3364    if prevout_values.len() != tx.inputs.len() || prevout_script_pubkeys.len() != tx.inputs.len() {
3365        return Err(ConsensusError::ScriptErrorWithCode {
3366            code: ScriptErrorCode::TxInputInvalid,
3367            message: format!(
3368                "Prevout slices: values={}, script_pubkeys={}, input_count={} (input_idx={})",
3369                prevout_values.len(),
3370                prevout_script_pubkeys.len(),
3371                tx.inputs.len(),
3372                input_index,
3373            )
3374            .into(),
3375        });
3376    }
3377
3378    // libbitcoin-consensus check: prevout.value must not exceed i64::MAX
3379    // In libbitcoin-consensus: if (prevout.value > std::numeric_limits<int64_t>::max())
3380    // This prevents value overflow in TransactionSignatureChecker
3381    // Note: Our value is already i64, so it can't exceed i64::MAX by definition
3382    // But we validate it's non-negative and within MAX_MONEY bounds for safety
3383    if input_index < prevout_values.len() {
3384        let prevout_value = prevout_values[input_index];
3385        if prevout_value < 0 {
3386            return Err(ConsensusError::ScriptErrorWithCode {
3387                code: ScriptErrorCode::ValueOverflow,
3388                message: "Prevout value cannot be negative".into(),
3389            });
3390        }
3391        #[cfg(feature = "production")]
3392        {
3393            use precomputed_constants::MAX_MONEY_U64;
3394            if (prevout_value as u64) > MAX_MONEY_U64 {
3395                return Err(ConsensusError::ScriptErrorWithCode {
3396                    code: ScriptErrorCode::ValueOverflow,
3397                    message: format!("Prevout value {prevout_value} exceeds MAX_MONEY").into(),
3398                });
3399            }
3400        }
3401        #[cfg(not(feature = "production"))]
3402        {
3403            use crate::constants::MAX_MONEY;
3404            if prevout_value > MAX_MONEY {
3405                return Err(ConsensusError::ScriptErrorWithCode {
3406                    code: ScriptErrorCode::ValueOverflow,
3407                    message: format!("Prevout value {prevout_value} exceeds MAX_MONEY").into(),
3408                });
3409            }
3410        }
3411    }
3412
3413    // libbitcoin-consensus check: input_index must be valid
3414    if input_index >= tx.inputs.len() {
3415        return Err(ConsensusError::ScriptErrorWithCode {
3416            code: ScriptErrorCode::TxInputInvalid,
3417            message: format!(
3418                "Input index {} out of bounds (tx has {} inputs)",
3419                input_index,
3420                tx.inputs.len()
3421            )
3422            .into(),
3423        });
3424    }
3425
3426    // P2PK / P2PKH / P2SH fast-paths — skip interpreter for common legacy scripts
3427    #[cfg(feature = "production")]
3428    if !fast_paths_disabled() && witness.is_none() {
3429        if let Some(result) = try_verify_p2pk_fast_path(
3430            script_sig,
3431            script_pubkey,
3432            flags,
3433            tx,
3434            input_index,
3435            prevout_values,
3436            prevout_script_pubkeys,
3437            block_height,
3438            network,
3439            #[cfg(feature = "production")]
3440            sighash_cache,
3441        ) {
3442            FAST_PATH_P2PK.fetch_add(1, Ordering::Relaxed);
3443            return result;
3444        }
3445        if let Some(result) = try_verify_p2pkh_fast_path(
3446            script_sig,
3447            script_pubkey,
3448            flags,
3449            tx,
3450            input_index,
3451            prevout_values,
3452            prevout_script_pubkeys,
3453            block_height,
3454            network,
3455            #[cfg(feature = "production")]
3456            precomputed_sighash_all,
3457            #[cfg(feature = "production")]
3458            sighash_cache,
3459            #[cfg(feature = "production")]
3460            precomputed_p2pkh_hash,
3461        ) {
3462            FAST_PATH_P2PKH.fetch_add(1, Ordering::Relaxed);
3463            return result;
3464        }
3465        if let Some(result) = try_verify_p2sh_fast_path(
3466            script_sig,
3467            script_pubkey,
3468            flags,
3469            tx,
3470            input_index,
3471            prevout_values,
3472            prevout_script_pubkeys,
3473            block_height,
3474            median_time_past,
3475            network,
3476            #[cfg(feature = "production")]
3477            sighash_cache,
3478            #[cfg(feature = "production")]
3479            precomputed_sighash_all,
3480            #[cfg(all(feature = "production", feature = "blvm-secp256k1"))]
3481            ecdsa_collect,
3482        ) {
3483            FAST_PATH_P2SH.fetch_add(1, Ordering::Relaxed);
3484            return result;
3485        }
3486        if let Some(result) = try_verify_bare_multisig_fast_path(
3487            script_sig,
3488            script_pubkey,
3489            flags,
3490            tx,
3491            input_index,
3492            prevout_values,
3493            prevout_script_pubkeys,
3494            block_height,
3495            network,
3496            #[cfg(feature = "production")]
3497            sighash_cache,
3498        ) {
3499            FAST_PATH_BARE_MULTISIG.fetch_add(1, Ordering::Relaxed);
3500            return result;
3501        }
3502    }
3503    // P2WPKH / P2WSH / P2WPKH-in-P2SH fast-paths when witness present
3504    #[cfg(feature = "production")]
3505    if !fast_paths_disabled() {
3506        if let Some(wit) = witness {
3507            if let Some(result) = try_verify_p2wpkh_in_p2sh_fast_path(
3508                script_sig,
3509                script_pubkey,
3510                wit,
3511                flags,
3512                tx,
3513                input_index,
3514                prevout_values,
3515                prevout_script_pubkeys,
3516                block_height,
3517                network,
3518                precomputed_bip143,
3519                #[cfg(all(feature = "production", feature = "blvm-secp256k1"))]
3520                ecdsa_collect,
3521            ) {
3522                FAST_PATH_P2WPKH.fetch_add(1, Ordering::Relaxed);
3523                return result;
3524            }
3525            if let Some(result) = try_verify_p2wsh_in_p2sh_fast_path(
3526                script_sig,
3527                script_pubkey,
3528                wit,
3529                flags,
3530                tx,
3531                input_index,
3532                prevout_values,
3533                prevout_script_pubkeys,
3534                block_height,
3535                median_time_past,
3536                network,
3537                schnorr_collector,
3538                precomputed_bip143,
3539                #[cfg(feature = "production")]
3540                sighash_cache,
3541                #[cfg(all(feature = "production", feature = "blvm-secp256k1"))]
3542                ecdsa_collect,
3543            ) {
3544                FAST_PATH_P2WSH.fetch_add(1, Ordering::Relaxed);
3545                return result;
3546            }
3547            if let Some(result) = try_verify_p2wpkh_fast_path(
3548                script_sig,
3549                script_pubkey,
3550                wit,
3551                flags,
3552                tx,
3553                input_index,
3554                prevout_values,
3555                prevout_script_pubkeys,
3556                block_height,
3557                network,
3558                precomputed_bip143,
3559                precomputed_sighash_all,
3560                #[cfg(all(feature = "production", feature = "blvm-secp256k1"))]
3561                ecdsa_collect,
3562            ) {
3563                FAST_PATH_P2WPKH.fetch_add(1, Ordering::Relaxed);
3564                return result;
3565            }
3566            if let Some(result) = try_verify_p2wsh_fast_path(
3567                script_sig,
3568                script_pubkey,
3569                wit,
3570                flags,
3571                tx,
3572                input_index,
3573                prevout_values,
3574                prevout_script_pubkeys,
3575                block_height,
3576                median_time_past,
3577                network,
3578                schnorr_collector,
3579                precomputed_bip143,
3580                #[cfg(feature = "production")]
3581                sighash_cache,
3582                #[cfg(all(feature = "production", feature = "blvm-secp256k1"))]
3583                ecdsa_collect,
3584            ) {
3585                FAST_PATH_P2WSH.fetch_add(1, Ordering::Relaxed);
3586                return result;
3587            }
3588            if let Some(result) = try_verify_p2tr_scriptpath_p2pk_fast_path(
3589                script_sig,
3590                script_pubkey,
3591                wit,
3592                flags,
3593                tx,
3594                input_index,
3595                prevout_values,
3596                prevout_script_pubkeys,
3597                block_height,
3598                network,
3599                schnorr_collector,
3600            ) {
3601                FAST_PATH_P2TR.fetch_add(1, Ordering::Relaxed);
3602                return result;
3603            }
3604            if let Some(result) = crate::script::templates::try_verify_p2tr_unexec_if_fast_path(
3605                script_sig,
3606                script_pubkey,
3607                wit,
3608                flags,
3609                tx,
3610                input_index,
3611                prevout_values,
3612                prevout_script_pubkeys,
3613                block_height,
3614                network,
3615                schnorr_collector,
3616            ) {
3617                FAST_PATH_UNEXEC_IF.fetch_add(1, Ordering::Relaxed);
3618                return result;
3619            }
3620            if let Some(result) = try_verify_p2tr_keypath_fast_path(
3621                script_sig,
3622                script_pubkey,
3623                wit,
3624                flags,
3625                tx,
3626                input_index,
3627                prevout_values,
3628                prevout_script_pubkeys,
3629                block_height,
3630                network,
3631                schnorr_collector,
3632            ) {
3633                FAST_PATH_P2TR.fetch_add(1, Ordering::Relaxed);
3634                return result;
3635            }
3636        }
3637    }
3638    #[cfg(feature = "production")]
3639    FAST_PATH_INTERPRETER.fetch_add(1, Ordering::Relaxed);
3640
3641    // P2SH handling: If SCRIPT_VERIFY_P2SH flag is set and scriptPubkey is P2SH format,
3642    // we need to check scriptSig push-only BEFORE executing it
3643    // P2SH scriptPubkey format: OP_HASH160 <20-byte-hash> OP_EQUAL
3644    let is_p2sh = (flags & flags::SCRIPT_VERIFY_P2SH) != 0
3645        && script_pubkey.len() == 23  // OP_HASH160 (1) + push 20 (1) + 20 bytes + OP_EQUAL (1) = 23
3646        && script_pubkey[0] == OP_HASH160   // OP_HASH160
3647        && script_pubkey[1] == PUSH_20_BYTES   // push 20 bytes
3648        && script_pubkey[22] == OP_EQUAL; // OP_EQUAL
3649
3650    // Where the last scriptSig push lives. Data pushes are already in `script_sig`.
3651    enum RedeemSource {
3652        Absent,
3653        Empty,
3654        Slice(usize, usize),
3655        Number(i64),
3656        StackCopy,
3657    }
3658    let mut redeem_source = RedeemSource::Absent;
3659
3660    // CRITICAL: For P2SH, scriptSig MUST only contain push operations (data pushes only)
3661    // This prevents script injection attacks. If scriptSig contains non-push opcodes, fail immediately.
3662    // This check MUST happen BEFORE executing scriptSig
3663    // Note: We validate push-only by attempting to parse scriptSig as push-only
3664    // If we encounter any non-push opcode OR invalid push encoding, we fail
3665    if is_p2sh {
3666        let mut i = 0;
3667        while i < script_sig.len() {
3668            let opcode = script_sig[i];
3669            if !is_push_opcode(opcode) {
3670                // Non-push opcode found in P2SH scriptSig - this is invalid
3671                return Ok(false);
3672            }
3673            // Advance past the push opcode and data. Remember the last push: that is the redeem script.
3674            if opcode == OP_0 {
3675                // OP_0 - push empty array, no data to skip
3676                redeem_source = RedeemSource::Empty;
3677                i += 1;
3678            } else if opcode <= 0x4b {
3679                // Direct push: opcode is the length (1-75 bytes)
3680                let len = opcode as usize;
3681                if i + 1 + len > script_sig.len() {
3682                    return Ok(false); // Invalid push length
3683                }
3684                redeem_source = RedeemSource::Slice(i + 1, i + 1 + len);
3685                i += 1 + len;
3686            } else if opcode == OP_PUSHDATA1 {
3687                // OP_PUSHDATA1
3688                if i + 1 >= script_sig.len() {
3689                    return Ok(false);
3690                }
3691                let len = script_sig[i + 1] as usize;
3692                if i + 2 + len > script_sig.len() {
3693                    return Ok(false);
3694                }
3695                redeem_source = RedeemSource::Slice(i + 2, i + 2 + len);
3696                i += 2 + len;
3697            } else if opcode == OP_PUSHDATA2 {
3698                // OP_PUSHDATA2
3699                if i + 2 >= script_sig.len() {
3700                    return Ok(false);
3701                }
3702                let len = u16::from_le_bytes([script_sig[i + 1], script_sig[i + 2]]) as usize;
3703                if i + 3 + len > script_sig.len() {
3704                    return Ok(false);
3705                }
3706                redeem_source = RedeemSource::Slice(i + 3, i + 3 + len);
3707                i += 3 + len;
3708            } else if opcode == OP_PUSHDATA4 {
3709                // OP_PUSHDATA4
3710                if i + 4 >= script_sig.len() {
3711                    return Ok(false);
3712                }
3713                let len = u32::from_le_bytes([
3714                    script_sig[i + 1],
3715                    script_sig[i + 2],
3716                    script_sig[i + 3],
3717                    script_sig[i + 4],
3718                ]) as usize;
3719                if i + 5 + len > script_sig.len() {
3720                    return Ok(false);
3721                }
3722                redeem_source = RedeemSource::Slice(i + 5, i + 5 + len);
3723                i += 5 + len;
3724            } else if opcode == OP_1NEGATE || (OP_1..=OP_16).contains(&opcode) {
3725                // Numeric pushes. The stack item is the CScriptNum, not a slice of scriptSig.
3726                redeem_source = RedeemSource::Number(script_num_from_opcode(opcode));
3727                i += 1;
3728            } else if (OP_1NEGATE..=OP_16).contains(&opcode) {
3729                // OP_RESERVED. Not a data push. Execution fails before the redeem is used.
3730                redeem_source = RedeemSource::StackCopy;
3731                i += 1;
3732            } else {
3733                // Should not reach here if is_push_opcode is correct, but fail anyway
3734                return Ok(false);
3735            }
3736        }
3737        #[cfg(feature = "production")]
3738        let run_fast_paths = !fast_paths_disabled();
3739        #[cfg(not(feature = "production"))]
3740        let run_fast_paths = true;
3741        if run_fast_paths {
3742            if let Some(result) = try_verify_p2sh_multisig_fast_path(
3743                script_sig,
3744                script_pubkey,
3745                flags,
3746                tx,
3747                input_index,
3748                prevout_values,
3749                prevout_script_pubkeys,
3750                block_height,
3751                network,
3752                #[cfg(feature = "production")]
3753                sighash_cache,
3754                #[cfg(all(feature = "production", feature = "blvm-secp256k1"))]
3755                ecdsa_collect.map(|(c, _)| c),
3756            ) {
3757                return result;
3758            }
3759        }
3760    }
3761
3762    #[cfg(feature = "production")]
3763    let mut _stack_guard = PooledStackGuard(get_pooled_stack());
3764    #[cfg(feature = "production")]
3765    let stack = &mut _stack_guard.0;
3766    #[cfg(not(feature = "production"))]
3767    let mut stack_buf: Vec<StackElement> = Vec::with_capacity(20);
3768    #[cfg(not(feature = "production"))]
3769    let stack = &mut stack_buf;
3770
3771    // Execute scriptSig (always Base sigversion)
3772    // FIX: scriptSig can contain CHECKSIG/CHECKMULTISIG (non-standard); collecting produces invalid
3773    // (sig, pubkey) pairings. Only fast paths (P2PKH, P2WPKH, P2WSH) have correct 1:1 pairing.
3774    let script_sig_result = eval_script_with_context_full(
3775        script_sig,
3776        stack,
3777        flags,
3778        tx,
3779        input_index,
3780        prevout_values,
3781        prevout_script_pubkeys,
3782        block_height,
3783        median_time_past,
3784        network,
3785        SigVersion::Base,
3786        None, // redeem_script_for_sighash
3787        None, // script_sig not needed when executing scriptSig
3788        None, // taproot_annex_hash
3789        None, // tapscript_validation_weight
3790        #[cfg(feature = "production")]
3791        schnorr_collector,
3792        None, // precomputed_bip143 - Base sigversion
3793        #[cfg(feature = "production")]
3794        sighash_cache,
3795    )?;
3796    if !script_sig_result {
3797        return Ok(false);
3798    }
3799
3800    // The redeem script is the last scriptSig push. Data pushes borrow `script_sig`.
3801    // scriptPubkey pops that stack item, so the borrow cannot point at the stack.
3802    let mut numeric_redeem = script_num_encode(0);
3803    let mut copied_redeem: Option<ByteString> = None;
3804    if is_p2sh && !stack.is_empty() {
3805        match redeem_source {
3806            RedeemSource::Number(n) => numeric_redeem = script_num_encode(n),
3807            RedeemSource::StackCopy | RedeemSource::Absent => {
3808                copied_redeem = Some(
3809                    stack
3810                        .last()
3811                        .expect("Stack is not empty")
3812                        .as_slice()
3813                        .to_vec(),
3814                );
3815            }
3816            RedeemSource::Empty | RedeemSource::Slice(_, _) => {}
3817        }
3818    }
3819    let redeem_script: Option<&[u8]> = if is_p2sh && !stack.is_empty() {
3820        match redeem_source {
3821            RedeemSource::Slice(start, end) => Some(&script_sig[start..end]),
3822            RedeemSource::Empty => Some(&[]),
3823            RedeemSource::Number(_) => Some(&numeric_redeem[..]),
3824            RedeemSource::StackCopy | RedeemSource::Absent => copied_redeem.as_deref(),
3825        }
3826    } else {
3827        None
3828    };
3829    let nested_witness_program = redeem_script.as_ref().is_some_and(|redeem| {
3830        redeem.len() >= 3
3831            && redeem[0] == OP_0
3832            && ((redeem[1] == PUSH_20_BYTES && redeem.len() == 22)
3833                || (redeem[1] == PUSH_32_BYTES && redeem.len() == 34))
3834    });
3835    // Nested v0 scriptSig must be the canonical push of the redeem script.
3836    if nested_witness_program
3837        && !redeem_push_is_canonical(
3838            script_sig.as_ref(),
3839            redeem_script.as_ref().expect("nested redeem"),
3840        )
3841    {
3842        return Ok(false);
3843    }
3844
3845    // Taproot format: [OP_1, PUSH_32_BYTES, <32 bytes>] = 34 bytes total.
3846    // The byte check comes first so a non-matching output skips the activation lookup.
3847    use crate::activation::taproot_active_at_height;
3848    use crate::script::flags::{
3849        SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM, SCRIPT_VERIFY_WITNESS,
3850    };
3851    let witness_flag = flags & SCRIPT_VERIFY_WITNESS != 0;
3852    let v1_32 =
3853        script_pubkey.len() == 34 && script_pubkey[0] == OP_1 && script_pubkey[1] == PUSH_32_BYTES;
3854    let is_taproot =
3855        redeem_script.is_none() && v1_32 && taproot_active_at_height(block_height, network);
3856
3857    // If Taproot, scriptSig must be empty
3858    if is_taproot && !script_sig.is_empty() {
3859        return Ok(false); // Taproot requires empty scriptSig
3860    }
3861
3862    // Future witness programs, and a v1 32-byte program before Taproot activation.
3863    // Empty scriptSig, witness ignored. After activation `is_taproot` handles v1-32.
3864    let untaproot_v1 = witness_flag && !is_taproot && v1_32;
3865    if redeem_script.is_none()
3866        && !is_taproot
3867        && (untaproot_v1 || crate::witness::is_upgradable_witness_program(script_pubkey))
3868    {
3869        if !script_sig.is_empty() {
3870            return Ok(false);
3871        }
3872        if !witness_flag {
3873            return Ok(false);
3874        }
3875        // Native pay-to-anchor is standard with an empty witness. A wrapped
3876        // anchor stays on the discourage path below.
3877        if crate::witness::is_pay_to_anchor(script_pubkey) {
3878            return Ok(true);
3879        }
3880        if (flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM) != 0 {
3881            return Ok(false);
3882        }
3883        // The program is the stack top after scriptPubKey execution. A false
3884        // top fails before the future-version success path.
3885        if !cast_to_bool(&script_pubkey[2..]) {
3886            return Ok(false);
3887        }
3888        return Ok(true);
3889    }
3890
3891    // Native v0 witness programs (BIP141). Only when SCRIPT_VERIFY_WITNESS is set.
3892    // With the flag clear, OP_0 <20>/<32> is a bare script.
3893    // P2WPKH: [OP_0, PUSH_20_BYTES, <20 bytes>] = 22 bytes
3894    // P2WSH: [OP_0, PUSH_32_BYTES, <32 bytes>] = 34 bytes
3895    // v0 programs must be 20 or 32 bytes. Other lengths are not future witness versions.
3896    if witness_flag
3897        && redeem_script.is_none()
3898        && crate::witness::is_invalid_v0_witness_program(script_pubkey)
3899    {
3900        return Ok(false);
3901    }
3902    let is_direct_witness_program = witness_flag
3903        && redeem_script.is_none()
3904        && !is_taproot
3905        && crate::witness::is_standard_witness_program(script_pubkey)
3906        && script_pubkey[0] == OP_0;
3907
3908    if is_direct_witness_program {
3909        // scriptPubKey execution leaves the program on top. A false program
3910        // fails before witness verification.
3911        if !cast_to_bool(&script_pubkey[2..]) {
3912            return Ok(false);
3913        }
3914        // Non-empty scriptSig is WITNESS_MALLEATED.
3915        if !script_sig.is_empty() {
3916            return Ok(false);
3917        }
3918        let Some(witness_stack) = witness else {
3919            return Ok(false);
3920        };
3921        if script_pubkey[1] == PUSH_32_BYTES {
3922            if witness_stack.is_empty() {
3923                return Ok(false);
3924            }
3925            let witness_script = witness_stack.last().expect("Witness stack is not empty");
3926            let program_bytes = &script_pubkey[2..];
3927            if program_bytes.len() != 32 {
3928                return Ok(false);
3929            }
3930            let witness_script_hash = OptimizedSha256::new().hash(witness_script.as_ref());
3931            if &witness_script_hash[..] != program_bytes {
3932                return Ok(false);
3933            }
3934            let max_witness_elem = MAX_SCRIPT_ELEMENT_SIZE;
3935            for element in witness_stack.iter().take(witness_stack.len() - 1) {
3936                if element.len() > max_witness_elem {
3937                    return Ok(false);
3938                }
3939            }
3940            // Fresh stack: scriptSig leftovers must not sit under the witness items.
3941            stack.clear();
3942            for element in witness_stack.iter().take(witness_stack.len() - 1) {
3943                stack.push(to_stack_element(element));
3944            }
3945            return eval_script_with_context_full(
3946                witness_script,
3947                stack,
3948                flags,
3949                tx,
3950                input_index,
3951                prevout_values,
3952                prevout_script_pubkeys,
3953                block_height,
3954                median_time_past,
3955                network,
3956                SigVersion::WitnessV0,
3957                Some(witness_script.as_ref()),
3958                None,
3959                None,
3960                None, // tapscript_validation_weight
3961                #[cfg(feature = "production")]
3962                schnorr_collector,
3963                precomputed_bip143,
3964                #[cfg(feature = "production")]
3965                sighash_cache,
3966            );
3967        } else if script_pubkey[1] == PUSH_20_BYTES {
3968            if witness_stack.len() != 2 {
3969                return Ok(false);
3970            }
3971            let signature_bytes = &witness_stack[0];
3972            let pubkey_bytes = &witness_stack[1];
3973            if signature_bytes.is_empty() {
3974                return Ok(false);
3975            }
3976            if pubkey_bytes.len() != 33 && pubkey_bytes.len() != 65 {
3977                return Ok(false);
3978            }
3979            let max_witness_elem = MAX_SCRIPT_ELEMENT_SIZE;
3980            if signature_bytes.len() > max_witness_elem || pubkey_bytes.len() > max_witness_elem {
3981                return Ok(false);
3982            }
3983            let pubkey_hash = &script_pubkey[2..22];
3984            let pubkey_sha256 = OptimizedSha256::new().hash(pubkey_bytes);
3985            let computed_hash = Ripemd160::digest(pubkey_sha256);
3986            if &computed_hash[..] != pubkey_hash {
3987                return Ok(false);
3988            }
3989            let sighash_byte = signature_bytes[signature_bytes.len() - 1];
3990            let amount = prevout_values.get(input_index).copied().unwrap_or(0);
3991            let p2pkh_script_code = bip143_p2wpkh_script_code(pubkey_hash);
3992            let sighash = crate::transaction_hash::calculate_bip143_sighash(
3993                tx,
3994                input_index,
3995                &p2pkh_script_code,
3996                amount,
3997                sighash_byte,
3998                precomputed_bip143,
3999            )?;
4000            let height = block_height.unwrap_or(0);
4001            return signature::with_secp_context(|secp| {
4002                signature::verify_signature(
4003                    secp,
4004                    pubkey_bytes,
4005                    signature_bytes,
4006                    &sighash,
4007                    flags,
4008                    height,
4009                    network,
4010                    SigVersion::WitnessV0,
4011                )
4012            });
4013        } else {
4014            return Ok(false);
4015        }
4016    }
4017
4018    if is_taproot {
4019        let Some(witness_stack) = witness else {
4020            return Ok(false);
4021        };
4022        if witness_stack.is_empty() {
4023            return Ok(false);
4024        }
4025        // After optional annex strip: 1 element = key-path; 2+ = script-path (BIP341).
4026        let (witness_body, annex_hash) = crate::taproot::strip_taproot_annex(witness_stack);
4027        if witness_body.is_empty() {
4028            return Ok(false);
4029        }
4030        if witness_body.len() == 1 {
4031            #[cfg(feature = "production")]
4032            {
4033                return verify_p2tr_keypath_witness(
4034                    script_pubkey,
4035                    witness_stack,
4036                    tx,
4037                    input_index,
4038                    prevout_values,
4039                    prevout_script_pubkeys,
4040                    schnorr_collector,
4041                );
4042            }
4043            #[cfg(not(feature = "production"))]
4044            {
4045                if witness_body.len() != 1 {
4046                    return Ok(false);
4047                }
4048                let Some((sig_bytes, sighash_type)) =
4049                    parse_taproot_keypath_sig(witness_body[0].as_ref())
4050                else {
4051                    return Ok(false);
4052                };
4053                let output_key = &script_pubkey[2..34];
4054                let sighash = crate::taproot::compute_taproot_signature_hash(
4055                    tx,
4056                    input_index,
4057                    prevout_values,
4058                    prevout_script_pubkeys,
4059                    sighash_type,
4060                    annex_hash.as_ref(),
4061                )?;
4062                let mut pubkey = [0u8; 32];
4063                pubkey.copy_from_slice(output_key);
4064                return crate::secp256k1_backend::verify_schnorr(&sig_bytes, &sighash, &pubkey);
4065            }
4066        }
4067        if witness_body.len() < 2 {
4068            return Ok(false);
4069        }
4070        let mut output_key = [0u8; 32];
4071        output_key.copy_from_slice(&script_pubkey[2..34]);
4072        match crate::taproot::parse_taproot_script_path_witness(&witness_body, &output_key)? {
4073            None => return Ok(false),
4074            Some((tapscript, stack_items, control_block)) => {
4075                if control_block.leaf_version != crate::taproot::TAPROOT_LEAF_VERSION_TAPSCRIPT {
4076                    use crate::script::flags::SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION;
4077                    if flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION != 0 {
4078                        return Ok(false);
4079                    }
4080                    return Ok(true);
4081                }
4082                for item in &stack_items {
4083                    stack.push(to_stack_element(item));
4084                }
4085                if !eval_script_with_context_full(
4086                    &tapscript,
4087                    stack,
4088                    flags,
4089                    tx,
4090                    input_index,
4091                    prevout_values,
4092                    prevout_script_pubkeys,
4093                    block_height,
4094                    median_time_past,
4095                    network,
4096                    SigVersion::Tapscript,
4097                    None, // redeem_script_for_sighash
4098                    None, // script_sig_for_sighash
4099                    annex_hash.as_ref(),
4100                    Some(crate::taproot::witness_stack_serialize_size(witness_stack) + 50),
4101                    #[cfg(feature = "production")]
4102                    schnorr_collector,
4103                    None,
4104                    #[cfg(feature = "production")]
4105                    sighash_cache,
4106                )? {
4107                    return Ok(false);
4108                }
4109                return Ok(true);
4110            }
4111        }
4112    }
4113
4114    // Execute scriptPubkey (always Base sigversion)
4115    // For P2WPKH/P2WSH, witness stack elements are already on the stack
4116    // Pass script_sig so legacy sighash uses same signature bytes as fast path (FindAndDelete pattern).
4117    // Interpreter path: verify in-place only. Interpreter sighash can diverge from
4118    // fast path (e.g. CHECKMULTISIG), causing batch to store invalid triples. Verify in-place only.
4119    // Thread-local guard ensures we never collect even if a collector is accidentally threaded through.
4120    let script_pubkey_result = eval_script_with_context_full(
4121        script_pubkey,
4122        stack,
4123        flags,
4124        tx,
4125        input_index,
4126        prevout_values,
4127        prevout_script_pubkeys,
4128        block_height,
4129        median_time_past,
4130        network,
4131        SigVersion::Base,
4132        None, // redeem_script_for_sighash
4133        Some(script_sig),
4134        None, // taproot_annex_hash
4135        None, // tapscript_validation_weight
4136        #[cfg(feature = "production")]
4137        schnorr_collector,
4138        None, // precomputed_bip143 - Base sigversion
4139        #[cfg(feature = "production")]
4140        sighash_cache,
4141    )?;
4142    if !script_pubkey_result {
4143        return Ok(false);
4144    }
4145
4146    // P2SH: If scriptPubkey verified the hash, we need to execute the redeem script
4147    // The scriptPubkey (OP_HASH160 <hash> OP_EQUAL) pops the redeem script, hashes it, compares
4148    // After scriptPubkey execution, if successful, stack should have [sig1, sig2, ..., 1]
4149    // where 1 is the OP_EQUAL result (true)
4150    if let Some(redeem) = redeem_script {
4151        // Verify stack has at least one element (the OP_EQUAL result)
4152        if stack.is_empty() {
4153            return Ok(false); // scriptPubkey execution failed
4154        }
4155
4156        // Verify top element is non-zero (OP_EQUAL returned 1 = hash matched)
4157        let top = stack.last().expect("Stack is not empty");
4158        if !cast_to_bool(top) {
4159            return Ok(false); // Hash didn't match or scriptPubkey failed
4160        }
4161
4162        // Pop the OP_EQUAL result (1) - this was pushed by OP_EQUAL when hashes matched
4163        stack.pop();
4164
4165        // Check if redeem script is a witness program (P2WSH-in-P2SH or P2WPKH-in-P2SH)
4166        // Witness program format: OP_0 (0x00) + push opcode + program bytes
4167        // P2WPKH: [OP_0, PUSH_20_BYTES, <20 bytes>] = 22 bytes total
4168        // P2WSH: [OP_0, PUSH_32_BYTES, <32 bytes>] = 34 bytes total
4169        let is_witness_program =
4170            crate::witness::is_standard_witness_program(redeem) && redeem[0] == OP_0;
4171
4172        if witness_flag && crate::witness::is_invalid_v0_witness_program(redeem) {
4173            return Ok(false);
4174        }
4175
4176        if witness_flag && is_witness_program {
4177            if !cast_to_bool(&redeem[2..]) {
4178                return Ok(false);
4179            }
4180            // For P2WSH-in-P2SH or P2WPKH-in-P2SH:
4181            // - We've already verified the redeem script hash matches (scriptPubkey check passed)
4182            // - We should NOT execute the redeem script as a normal script
4183            // - Extract the witness program from redeem script (program bytes after OP_0 and push opcode)
4184            // - For P2WPKH-in-P2SH: witness script is pubkey hash (20 bytes), witness contains signature + pubkey
4185            // - For P2WSH-in-P2SH: witness script is the last witness element, hash must match program (32 bytes)
4186
4187            // Extract program from redeem script: skip OP_0 (1 byte) + push opcode (1 byte), get program bytes
4188            let program_bytes = &redeem[2..];
4189
4190            if redeem[1] == PUSH_32_BYTES {
4191                // P2WSH-in-P2SH: program is 32 bytes, witness should contain the witness script as last element
4192                // The witness script's SHA256 hash must match the program
4193                if program_bytes.len() != 32 {
4194                    return Ok(false); // Invalid P2WSH program length
4195                }
4196
4197                // CRITICAL FIX: For P2WSH-in-P2SH, witness is now the full Witness stack
4198                // Structure: [sig1, sig2, ..., witness_script]
4199                // The last element is the witness script, which we verify the hash of
4200                // Then we execute the witness script with the remaining elements (signatures) on the stack
4201                if let Some(witness_stack) = witness {
4202                    if witness_stack.is_empty() {
4203                        return Ok(false); // P2WSH requires witness
4204                    }
4205
4206                    // Get the witness script (last element) - it's a ByteString (Vec<u8>)
4207                    let witness_script = witness_stack.last().expect("Witness stack is not empty");
4208                    let witness_script_hash = OptimizedSha256::new().hash(witness_script.as_ref());
4209                    if &witness_script_hash[..] != program_bytes {
4210                        return Ok(false); // Witness script hash doesn't match program
4211                    }
4212
4213                    // Hash matches - now push witness stack elements (except the last one, which is the script)
4214                    // onto the stack, then execute the witness script
4215                    stack.clear();
4216
4217                    // Push all witness stack elements except the last one (witness script) onto the stack
4218                    // These are the signatures and other data needed for witness script execution
4219                    let max_witness_elem = MAX_SCRIPT_ELEMENT_SIZE;
4220                    for element in witness_stack.iter().take(witness_stack.len() - 1) {
4221                        if element.len() > max_witness_elem {
4222                            return Ok(false);
4223                        }
4224                        stack.push(to_stack_element(element));
4225                    }
4226
4227                    // Execute the witness script with witness stack elements on the stack
4228                    // P2WSH-in-P2SH always uses WitnessV0 (BIP143). 0x8000 = WITNESS_PUBKEYTYPE
4229                    // is a key-type strictness flag and does not select Tapscript semantics.
4230                    let witness_sigversion = SigVersion::WitnessV0;
4231
4232                    // Interpreter path: no collection (P2WSH-in-P2SH).
4233                    if !eval_script_with_context_full(
4234                        witness_script,
4235                        stack,
4236                        flags,
4237                        tx,
4238                        input_index,
4239                        prevout_values,
4240                        prevout_script_pubkeys,
4241                        block_height,
4242                        median_time_past,
4243                        network,
4244                        witness_sigversion,
4245                        Some(witness_script.as_ref()), // BIP143 scriptCode for CHECKSIG inside P2WSH-in-P2SH
4246                        None,                          // script_sig_for_sighash
4247                        None,                          // taproot_annex_hash
4248                        None,                          // tapscript_validation_weight
4249                        #[cfg(feature = "production")]
4250                        schnorr_collector,
4251                        precomputed_bip143, // WitnessV0 uses BIP143
4252                        #[cfg(feature = "production")]
4253                        sighash_cache,
4254                    )? {
4255                        return Ok(false);
4256                    }
4257                } else {
4258                    return Ok(false); // P2WSH requires witness
4259                }
4260            } else if redeem[1] == PUSH_20_BYTES {
4261                // P2WPKH-in-P2SH (BIP141): the redeem script is OP_0 <20-byte-pubkey-hash>.
4262                // Witness must be exactly [signature, pubkey].
4263                // Verify pubkey hashes to the program, then check BIP143 signature.
4264                let pubkey_hash = &redeem[2..22]; // 20-byte hash from redeem script
4265
4266                let witness_stack = match witness {
4267                    Some(w) => w,
4268                    None => return Ok(false), // P2WPKH-in-P2SH requires witness
4269                };
4270                if witness_stack.len() != 2 {
4271                    return Ok(false); // Must be exactly [sig, pubkey]
4272                }
4273                let signature_bytes = &witness_stack[0];
4274                let pubkey_bytes = &witness_stack[1];
4275
4276                if signature_bytes.is_empty() {
4277                    return Ok(false);
4278                }
4279                if pubkey_bytes.len() != 33 && pubkey_bytes.len() != 65 {
4280                    return Ok(false);
4281                }
4282
4283                // Verify pubkey hash matches program
4284                let pubkey_sha256 = OptimizedSha256::new().hash(pubkey_bytes);
4285                let computed_hash = Ripemd160::digest(pubkey_sha256);
4286                if &computed_hash[..] != pubkey_hash {
4287                    return Ok(false);
4288                }
4289
4290                // BIP143 §4.3: scriptCode for P2WPKH is the P2PKH expansion, not the witness program.
4291                let sighash_byte = signature_bytes[signature_bytes.len() - 1];
4292                let amount = prevout_values.get(input_index).copied().unwrap_or(0);
4293                let p2pkh_script_code = bip143_p2wpkh_script_code(pubkey_hash);
4294                let sighash = crate::transaction_hash::calculate_bip143_sighash(
4295                    tx,
4296                    input_index,
4297                    &p2pkh_script_code,
4298                    amount,
4299                    sighash_byte,
4300                    precomputed_bip143,
4301                )?;
4302
4303                let height = block_height.unwrap_or(0);
4304                return signature::with_secp_context(|secp| {
4305                    signature::verify_signature(
4306                        secp,
4307                        pubkey_bytes,
4308                        signature_bytes,
4309                        &sighash,
4310                        flags,
4311                        height,
4312                        network,
4313                        SigVersion::WitnessV0,
4314                    )
4315                });
4316            } else {
4317                return Ok(false); // Invalid witness program format
4318            }
4319            // P2WSH-in-P2SH path falls through to final stack check above
4320        } else if witness_flag && crate::witness::is_any_witness_program(redeem) {
4321            // A P2SH-wrapped program that is not v0 P2WPKH/P2WSH is not taproot.
4322            // It succeeds when the redeem push is canonical, unless the
4323            // discourage-upgradable flag is set. Witness bytes are ignored.
4324            if !redeem_push_is_canonical(script_sig.as_ref(), redeem) {
4325                return Ok(false);
4326            }
4327            if flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM != 0 {
4328                return Ok(false);
4329            }
4330            if !cast_to_bool(&redeem[2..]) {
4331                return Ok(false);
4332            }
4333            return Ok(true);
4334        } else {
4335            // Regular P2SH: execute the redeem script with the remaining stack (signatures pushed by scriptSig)
4336            // The redeem script will consume the signatures and should leave exactly one true value
4337            // CRITICAL FIX: Pass redeem script for P2SH sighash calculation
4338            let redeem_result = eval_script_with_context_full_inner(
4339                redeem,
4340                stack,
4341                flags,
4342                tx,
4343                input_index,
4344                prevout_values,
4345                prevout_script_pubkeys,
4346                block_height,
4347                median_time_past,
4348                network,
4349                SigVersion::Base,
4350                Some(redeem),     // Pass redeem script for sighash
4351                Some(script_sig), // Use same script_sig for legacy sighash pattern (e.g. P2PKH inside P2SH)
4352                None,             // taproot_annex_hash
4353                None,             // tapscript_validation_weight
4354                #[cfg(feature = "production")]
4355                None, // schnorr_collector
4356                None,             // precomputed_bip143 - Base sigversion
4357                #[cfg(feature = "production")]
4358                sighash_cache,
4359            )?;
4360            if !redeem_result {
4361                return Ok(false);
4362            }
4363        }
4364    }
4365
4366    // Stack size must not exceed consensus limit after scriptPubkey execution.
4367    if stack.len() > MAX_STACK_SIZE {
4368        return Ok(false);
4369    }
4370
4371    // Execute witness if present
4372    // CRITICAL:
4373    // - Direct P2WPKH/P2WSH: Already handled above (witness stack pushed before scriptPubkey, witness script executed after)
4374    // - P2WSH-in-P2SH: Handled in P2SH section above (witness script executed with witness stack elements)
4375    // - P2WPKH-in-P2SH: Handled in P2SH section above (BIP143 sig/pubkey check via implicit P2PKH scriptCode)
4376    // - Regular scripts: No witness execution needed
4377    //
4378    // Fail closed when witness data is present but this spend is not a witness program path.
4379    // Do not require the witness parameter to be cleared: P2WSH-in-P2SH and similar paths
4380    // leave the original witness stack intact (see p2wsh_scriptcode_regression).
4381    let accepts_witness = is_direct_witness_program || is_taproot || nested_witness_program;
4382    if witness_flag {
4383        if let Some(witness_stack) = witness {
4384            if !crate::witness::witness_stack_is_null(witness_stack) && !accepts_witness {
4385                return Ok(false);
4386            }
4387        }
4388    }
4389
4390    // Final validation
4391    // SCRIPT_VERIFY_CLEANSTACK (0x100): requires exactly 1 element on the stack
4392    // This is only a consensus rule for witness scripts (handled above in witness paths).
4393    // For legacy scripts in block validation, only the top stack element is checked
4394    // to be truthy (non-empty and non-zero). CLEANSTACK for legacy is mempool policy only.
4395    let final_result = if (flags & flags::SCRIPT_VERIFY_CLEANSTACK) != 0 {
4396        // CLEANSTACK: exactly one element, must be truthy
4397        stack.len() == 1 && cast_to_bool(&stack[0])
4398    } else {
4399        // Legacy: stack non-empty, top element is truthy
4400        !stack.is_empty() && cast_to_bool(stack.last().expect("Stack is not empty"))
4401    };
4402    Ok(final_result)
4403}
4404
4405/// EvalScript with full context including block height, median time-past, and network
4406#[allow(clippy::too_many_arguments)]
4407fn eval_script_with_context_full(
4408    script: &[u8],
4409    stack: &mut Vec<StackElement>,
4410    flags: u32,
4411    tx: &Transaction,
4412    input_index: usize,
4413    prevout_values: &[i64],
4414    prevout_script_pubkeys: &[&[u8]],
4415    block_height: Option<u64>,
4416    median_time_past: Option<u64>,
4417    network: crate::types::Network,
4418    sigversion: SigVersion,
4419    redeem_script_for_sighash: Option<&[u8]>,
4420    script_sig_for_sighash: Option<&ByteString>,
4421    taproot_annex_hash: Option<&Hash>,
4422    tapscript_validation_weight: Option<i64>,
4423    #[cfg(feature = "production")] schnorr_collector: Option<
4424        &crate::bip348::SchnorrSignatureCollector,
4425    >,
4426    precomputed_bip143: Option<&crate::transaction_hash::Bip143PrecomputedHashes>,
4427    #[cfg(feature = "production")] sighash_cache: Option<
4428        &crate::transaction_hash::SighashMidstateCache,
4429    >,
4430) -> Result<bool> {
4431    #[cfg(all(feature = "production", feature = "profile"))]
4432    let _t0 = std::time::Instant::now();
4433    let r = eval_script_with_context_full_inner(
4434        script,
4435        stack,
4436        flags,
4437        tx,
4438        input_index,
4439        prevout_values,
4440        prevout_script_pubkeys,
4441        block_height,
4442        median_time_past,
4443        network,
4444        sigversion,
4445        redeem_script_for_sighash,
4446        script_sig_for_sighash,
4447        taproot_annex_hash,
4448        tapscript_validation_weight,
4449        #[cfg(feature = "production")]
4450        schnorr_collector,
4451        precomputed_bip143,
4452        #[cfg(feature = "production")]
4453        sighash_cache,
4454    );
4455    #[cfg(all(feature = "production", feature = "profile"))]
4456    crate::script_profile::add_interpreter_ns(_t0.elapsed().as_nanos() as u64);
4457    r
4458}
4459
4460/// Internal function with redeem script support for P2SH sighash
4461fn eval_script_with_context_full_inner(
4462    script: &[u8],
4463    stack: &mut Vec<StackElement>,
4464    flags: u32,
4465    tx: &Transaction,
4466    input_index: usize,
4467    prevout_values: &[i64],
4468    prevout_script_pubkeys: &[&[u8]],
4469    block_height: Option<u64>,
4470    median_time_past: Option<u64>,
4471    network: crate::types::Network,
4472    sigversion: SigVersion,
4473    redeem_script_for_sighash: Option<&[u8]>,
4474    script_sig_for_sighash: Option<&ByteString>,
4475    taproot_annex_hash: Option<&Hash>,
4476    tapscript_validation_weight: Option<i64>,
4477    #[cfg(feature = "production")] schnorr_collector: Option<
4478        &crate::bip348::SchnorrSignatureCollector,
4479    >,
4480    precomputed_bip143: Option<&crate::transaction_hash::Bip143PrecomputedHashes>,
4481    #[cfg(feature = "production")] sighash_cache: Option<
4482        &crate::transaction_hash::SighashMidstateCache,
4483    >,
4484) -> Result<bool> {
4485    // Precondition assertions: input_index and prevout lengths validated by caller (verify_script_with_context_full).
4486    // 6d: Removed redundant assert! for input_index and prevout lengths — caller returns error on mismatch.
4487    use crate::constants::{MAX_SCRIPT_ELEMENT_SIZE, MAX_SCRIPT_SIZE, MAX_STACK_SIZE};
4488    use crate::error::{ConsensusError, ScriptErrorCode};
4489    let weight_cell = tapscript_validation_weight.map(std::cell::Cell::new);
4490
4491    // MAX_SCRIPT_SIZE applies to Base and WitnessV0 only; Tapscript may exceed 10k.
4492    if (sigversion == SigVersion::Base || sigversion == SigVersion::WitnessV0)
4493        && script.len() > MAX_SCRIPT_SIZE
4494    {
4495        return Err(ConsensusError::ScriptErrorWithCode {
4496            code: ScriptErrorCode::ScriptSize,
4497            message: "Script size exceeds maximum".into(),
4498        });
4499    }
4500    // BIP342: In Tapscript, pre-scan the entire script for OP_SUCCESSx opcodes.
4501    // If any OP_SUCCESSx is encountered (even in unexecuted branches), the whole
4502    // script succeeds immediately. This must happen before any opcode is executed.
4503    // Reference: BIP342 Tapscript witness execution (ExecuteWitnessScript).
4504    if sigversion == SigVersion::Tapscript {
4505        use crate::activation::IsForkActive;
4506        use crate::script::flags::SCRIPT_VERIFY_DISCOURAGE_OP_SUCCESS;
4507        let csfs_active = cfg!(feature = "csfs")
4508            && block_height.is_some_and(|height| {
4509                crate::activation::ForkActivationTable::from_network(network)
4510                    .is_fork_active(crate::types::ForkId::Csfs, height)
4511            });
4512        let mut pc = 0usize;
4513        while pc < script.len() {
4514            let opcode = script[pc];
4515            // 0xcc stays OP_SUCCESS until the activation table says CSFS is on.
4516            if is_op_success(opcode)
4517                && !(csfs_active && opcode == crate::opcodes::OP_CHECKSIGFROMSTACK)
4518            {
4519                if flags & SCRIPT_VERIFY_DISCOURAGE_OP_SUCCESS != 0 {
4520                    return Err(ConsensusError::ScriptErrorWithCode {
4521                        code: ScriptErrorCode::BadOpcode,
4522                        message: format!("OP_SUCCESSx opcode 0x{opcode:02x} is discouraged").into(),
4523                    });
4524                }
4525                return Ok(true);
4526            }
4527            // A push that does not fit ends the scan. Its leftover bytes are not opcodes.
4528            let Some(next) = tapscript_opcode_end(script, pc) else {
4529                break;
4530            };
4531            pc = next;
4532        }
4533        if stack.len() > MAX_STACK_SIZE {
4534            return Err(make_stack_overflow_error());
4535        }
4536        if stack
4537            .iter()
4538            .any(|item| item.len() > MAX_SCRIPT_ELEMENT_SIZE)
4539        {
4540            return Ok(false);
4541        }
4542    }
4543
4544    // Pre-allocate stack capacity if needed
4545    if stack.capacity() < 20 {
4546        stack.reserve(20);
4547    }
4548    let mut op_count = 0;
4549
4550    // Pre-allocate control_stack and altstack to avoid realloc in hot path
4551    #[cfg(feature = "production")]
4552    let mut control_stack: Vec<control_flow::ControlBlock> = Vec::with_capacity(4);
4553    #[cfg(not(feature = "production"))]
4554    let mut control_stack: Vec<control_flow::ControlBlock> = Vec::new();
4555
4556    #[cfg(feature = "production")]
4557    let mut altstack: Vec<StackElement> = Vec::with_capacity(8);
4558    #[cfg(not(feature = "production"))]
4559    let mut altstack: Vec<StackElement> = Vec::new();
4560
4561    // Track OP_CODESEPARATOR position for sighash calculation.
4562    // pbegincodehash: the script code used for sighash starts
4563    // from after the last OP_CODESEPARATOR (or from the beginning if none).
4564    let mut code_separator_pos: usize = 0;
4565    let mut last_codesep_opcode_pos: u32 = 0xffff_ffff;
4566
4567    // Use index-based iteration to properly handle push opcodes.
4568    // The combined stack limit is applied after an opcode finishes, not before it runs.
4569    let mut i = 0;
4570    let mut finished_opcode = false;
4571    while i < script.len() {
4572        if finished_opcode && stack.len() + altstack.len() > MAX_STACK_SIZE {
4573            return Err(make_stack_overflow_error());
4574        }
4575        finished_opcode = true;
4576        #[cfg(feature = "production")]
4577        {
4578            // Prefetch next cache line(s) ahead for sequential script access
4579            prefetch::prefetch_ahead(script, i, 64); // Prefetch 64 bytes ahead
4580        }
4581        // Use optimized bounds access after length check
4582        let opcode = {
4583            #[cfg(feature = "production")]
4584            {
4585                unsafe { *script.get_unchecked(i) }
4586            }
4587            #[cfg(not(feature = "production"))]
4588            {
4589                script[i]
4590            }
4591        };
4592
4593        // Cache in_false_branch state - only recompute when control stack changes
4594        let in_false_branch = control_flow::in_false_branch(&control_stack);
4595
4596        // Count non-push opcodes toward op limit (Base/WitnessV0 only; BIP342 Tapscript has no 201-op cap).
4597        if sigversion != SigVersion::Tapscript && !is_push_opcode(opcode) {
4598            op_count += 1;
4599            if op_count > MAX_SCRIPT_OPS {
4600                return Err(make_operation_limit_error());
4601            }
4602        }
4603
4604        // Handle push opcodes (0x01-0x4b: direct push, OP_PUSHDATA1/2/4)
4605        if (0x01..=OP_PUSHDATA4).contains(&opcode) {
4606            let Some(advance) = apply_data_push(script, i, opcode, stack, flags, in_false_branch)?
4607            else {
4608                return Ok(false);
4609            };
4610            i += advance;
4611            continue;
4612        }
4613
4614        // Check hottest opcodes BEFORE match statement
4615        // This eliminates dispatch overhead for the most common opcodes (OP_DUP, OP_EQUALVERIFY, OP_HASH160)
4616        // These opcodes are executed millions of times per block, so avoiding match overhead is critical
4617
4618        // OP_DUP - duplicate top stack item (VERY HOT - every P2PKH script)
4619        if opcode == OP_DUP {
4620            if !in_false_branch {
4621                if stack.is_empty() {
4622                    return Err(ConsensusError::ScriptErrorWithCode {
4623                        code: ScriptErrorCode::InvalidStackOperation,
4624                        message: "OP_DUP: empty stack".into(),
4625                    });
4626                }
4627                // Optimize OP_DUP - avoid double lookup
4628                // Use unsafe bounds access after length check (already verified above)
4629                let len = stack.len();
4630                #[cfg(feature = "production")]
4631                {
4632                    // OPTIMIZATION: Reserve capacity before push to avoid reallocation
4633                    if stack.capacity() == stack.len() {
4634                        stack.reserve(1);
4635                    }
4636                    // Clone the item using unsafe bounds access (we already checked len > 0)
4637                    let item = unsafe { stack.get_unchecked(len - 1).clone() };
4638                    stack.push(item);
4639                }
4640                #[cfg(not(feature = "production"))]
4641                {
4642                    let item = stack.last().unwrap();
4643                    stack.push(item.clone());
4644                }
4645            }
4646            i += 1;
4647            continue;
4648        }
4649
4650        // OP_EQUALVERIFY - verify top two stack items are equal (VERY HOT - every P2PKH script)
4651        if opcode == OP_EQUALVERIFY {
4652            if !in_false_branch {
4653                if stack.len() < 2 {
4654                    return Err(ConsensusError::ScriptErrorWithCode {
4655                        code: ScriptErrorCode::InvalidStackOperation,
4656                        message: "OP_EQUALVERIFY: insufficient stack items".into(),
4657                    });
4658                }
4659                let a = stack.pop().unwrap();
4660                let b = stack.pop().unwrap();
4661                if a != b {
4662                    return Ok(false);
4663                }
4664            }
4665            i += 1;
4666            continue;
4667        }
4668
4669        // OP_HASH160 - RIPEMD160(SHA256(x)) (VERY HOT - every P2PKH script)
4670        if opcode == OP_HASH160 {
4671            if !in_false_branch && !crypto_ops::op_hash160(stack)? {
4672                return Ok(false);
4673            }
4674            i += 1;
4675            continue;
4676        }
4677
4678        // OP_VERIFY - check if top stack item is non-zero (HOT - many scripts)
4679        if opcode == OP_VERIFY {
4680            if !in_false_branch {
4681                if let Some(item) = stack.pop() {
4682                    if !cast_to_bool(&item) {
4683                        return Ok(false);
4684                    }
4685                } else {
4686                    return Ok(false);
4687                }
4688            }
4689            i += 1;
4690            continue;
4691        }
4692
4693        // OP_EQUAL - check if top two stack items are equal (HOT - P2SH scripts)
4694        if opcode == OP_EQUAL {
4695            if !in_false_branch {
4696                if stack.len() < 2 {
4697                    return Err(ConsensusError::ScriptErrorWithCode {
4698                        code: ScriptErrorCode::InvalidStackOperation,
4699                        message: "OP_EQUAL: insufficient stack items".into(),
4700                    });
4701                }
4702                let a = stack.pop().unwrap();
4703                let b = stack.pop().unwrap();
4704                stack.push(to_stack_element(script_bool_bytes(a == b)));
4705            }
4706            i += 1;
4707            continue;
4708        }
4709
4710        // OP_CHECKSIG / OP_CHECKSIGVERIFY - hot in multisig/P2SH (skip match dispatch)
4711        // OP_CHECKSIGADD (BIP 342) - Tapscript only; in Base/WitnessV0, 0xba falls through to match
4712        if opcode == OP_CHECKSIG
4713            || opcode == OP_CHECKSIGVERIFY
4714            || (opcode == OP_CHECKSIGADD && sigversion == SigVersion::Tapscript)
4715        {
4716            if !in_false_branch {
4717                let effective_script_code = Some(&script[code_separator_pos..]);
4718                let (tapscript, codesep) = if sigversion == SigVersion::Tapscript {
4719                    (Some(script), Some(last_codesep_opcode_pos))
4720                } else {
4721                    (None, None)
4722                };
4723                let ctx = context::ScriptContext {
4724                    tx,
4725                    input_index,
4726                    prevout_values,
4727                    prevout_script_pubkeys,
4728                    block_height,
4729                    median_time_past,
4730                    network,
4731                    sigversion,
4732                    redeem_script_for_sighash,
4733                    script_sig_for_sighash,
4734                    tapscript_for_sighash: tapscript,
4735                    tapscript_codesep_pos: codesep,
4736                    taproot_annex_hash,
4737                    tapscript_validation_weight: weight_cell.as_ref(),
4738                    #[cfg(feature = "production")]
4739                    schnorr_collector,
4740                    precomputed_bip143,
4741                    #[cfg(feature = "production")]
4742                    sighash_cache,
4743                };
4744                if !execute_opcode_with_context_full(
4745                    opcode,
4746                    stack,
4747                    flags,
4748                    &ctx,
4749                    effective_script_code,
4750                )? {
4751                    return Ok(false);
4752                }
4753            }
4754            i += 1;
4755            continue;
4756        }
4757
4758        match opcode {
4759            // OP_0 - push empty array
4760            OP_0 => {
4761                if !in_false_branch {
4762                    stack.push(to_stack_element(&[]));
4763                }
4764            }
4765
4766            // OP_1 to OP_16 - push numbers 1-16
4767            OP_1_RANGE_START..=OP_1_RANGE_END => {
4768                if !in_false_branch {
4769                    let num = opcode - OP_N_BASE;
4770                    stack.push(to_stack_element(&[num]));
4771                }
4772            }
4773
4774            // OP_1NEGATE - push -1
4775            OP_1NEGATE => {
4776                if !in_false_branch {
4777                    stack.push(to_stack_element(&[0x81])); // -1 in script number encoding
4778                }
4779            }
4780
4781            // OP_NOP - do nothing, execution continues
4782            OP_NOP => {
4783                // No operation - this is valid and execution continues
4784            }
4785
4786            // OP_VER - causes failure only when executing
4787            // OP_VER is inside the conditional-execution check,
4788            // so it only fails in executing branches. Non-executing branches skip it.
4789            // This differs from truly disabled opcodes (OP_CAT, etc.) which always fail.
4790            OP_VER => {
4791                if !in_false_branch {
4792                    return Err(ConsensusError::ScriptErrorWithCode {
4793                        code: ScriptErrorCode::DisabledOpcode,
4794                        message: "OP_VER is disabled".into(),
4795                    });
4796                }
4797            }
4798
4799            OP_IF => {
4800                // OP_IF
4801                if in_false_branch {
4802                    control_stack.push(control_flow::ControlBlock::If { executing: false });
4803                    i += 1;
4804                    continue;
4805                }
4806
4807                if stack.is_empty() {
4808                    return Err(ConsensusError::ScriptErrorWithCode {
4809                        code: ScriptErrorCode::InvalidStackOperation,
4810                        message: "OP_IF: empty stack".into(),
4811                    });
4812                }
4813                let condition = pop_if_condition(
4814                    stack,
4815                    flags,
4816                    sigversion,
4817                    "OP_IF condition must be minimally encoded",
4818                )?;
4819                control_stack.push(control_flow::ControlBlock::If {
4820                    executing: condition,
4821                });
4822            }
4823            OP_NOTIF => {
4824                // OP_NOTIF
4825                if in_false_branch {
4826                    control_stack.push(control_flow::ControlBlock::NotIf { executing: false });
4827                    i += 1;
4828                    continue;
4829                }
4830
4831                if stack.is_empty() {
4832                    return Err(ConsensusError::ScriptErrorWithCode {
4833                        code: ScriptErrorCode::InvalidStackOperation,
4834                        message: "OP_NOTIF: empty stack".into(),
4835                    });
4836                }
4837                let condition = pop_if_condition(
4838                    stack,
4839                    flags,
4840                    sigversion,
4841                    "OP_NOTIF condition must be minimally encoded",
4842                )?;
4843                control_stack.push(control_flow::ControlBlock::NotIf {
4844                    executing: !condition,
4845                });
4846            }
4847            OP_ELSE => {
4848                // OP_ELSE
4849                if let Some(block) = control_stack.last_mut() {
4850                    match block {
4851                        control_flow::ControlBlock::If { executing }
4852                        | control_flow::ControlBlock::NotIf { executing } => {
4853                            *executing = !*executing;
4854                        }
4855                    }
4856                } else {
4857                    return Err(ConsensusError::ScriptErrorWithCode {
4858                        code: ScriptErrorCode::UnbalancedConditional,
4859                        message: "OP_ELSE without matching IF/NOTIF".into(),
4860                    });
4861                }
4862            }
4863            OP_ENDIF => {
4864                // OP_ENDIF
4865                if control_stack.pop().is_none() {
4866                    return Err(ConsensusError::ScriptErrorWithCode {
4867                        code: ScriptErrorCode::UnbalancedConditional,
4868                        message: "OP_ENDIF without matching IF/NOTIF".into(),
4869                    });
4870                }
4871            }
4872
4873            // OP_DUP, OP_EQUALVERIFY, OP_HASH160 are handled BEFORE match statement for performance
4874            // (moved to avoid dispatch overhead)
4875
4876            // OP_TOALTSTACK - move top stack item to altstack
4877            OP_TOALTSTACK => {
4878                if in_false_branch {
4879                    i += 1;
4880                    continue;
4881                }
4882                if stack.is_empty() {
4883                    return Err(ConsensusError::ScriptErrorWithCode {
4884                        code: ScriptErrorCode::InvalidStackOperation,
4885                        message: "OP_TOALTSTACK: empty stack".into(),
4886                    });
4887                }
4888                altstack.push(stack.pop().unwrap());
4889            }
4890            // OP_FROMALTSTACK - move top altstack item to stack
4891            OP_FROMALTSTACK => {
4892                if in_false_branch {
4893                    i += 1;
4894                    continue;
4895                }
4896                if altstack.is_empty() {
4897                    return Err(ConsensusError::ScriptErrorWithCode {
4898                        code: ScriptErrorCode::InvalidAltstackOperation,
4899                        message: "OP_FROMALTSTACK: empty altstack".into(),
4900                    });
4901                }
4902                stack.push(altstack.pop().unwrap());
4903            }
4904            // OP_CODESEPARATOR - update sighash script code start position.
4905            // Standardness rejects it in a non-segwit script, including an unexecuted branch.
4906            OP_CODESEPARATOR => {
4907                if sigversion == SigVersion::Base
4908                    && flags & crate::script::flags::SCRIPT_VERIFY_CONST_SCRIPTCODE != 0
4909                {
4910                    return Err(ConsensusError::ScriptErrorWithCode {
4911                        code: ScriptErrorCode::BadOpcode,
4912                        message: "OP_CODESEPARATOR is non-standard in a non-segwit script".into(),
4913                    });
4914                }
4915                if in_false_branch {
4916                    i += 1;
4917                    continue;
4918                }
4919                code_separator_pos = i + 1;
4920                last_codesep_opcode_pos = opcode_position_at_byte(script, i);
4921            }
4922            _ => {
4923                if in_false_branch {
4924                    if let Some(code) = dead_branch_opcode_error(opcode) {
4925                        return Err(ConsensusError::ScriptErrorWithCode {
4926                            code,
4927                            message: format!(
4928                                "opcode 0x{opcode:02x} is invalid in an unexecuted branch"
4929                            )
4930                            .into(),
4931                        });
4932                    }
4933                    i += 1;
4934                    continue;
4935                }
4936
4937                // For signature opcodes, compute the effective script code for sighash:
4938                // From the last OP_CODESEPARATOR position to the end of the script.
4939                // scriptCode = slice from pbegincodehash to pend
4940                // Only allocate for opcodes that actually use the script code.
4941                if sigversion != SigVersion::Tapscript
4942                    && (opcode == OP_CHECKMULTISIG || opcode == OP_CHECKMULTISIGVERIFY)
4943                {
4944                    op_count = charge_checkmultisig_pubkeys(stack, op_count)?;
4945                }
4946                let subscript_for_sighash = if matches!(
4947                    opcode,
4948                    OP_CHECKSIG
4949                        | OP_CHECKSIGVERIFY
4950                        | OP_CHECKSIGADD
4951                        | OP_CHECKMULTISIG
4952                        | OP_CHECKMULTISIGVERIFY
4953                ) {
4954                    Some(&script[code_separator_pos..])
4955                } else {
4956                    None
4957                };
4958                let effective_script_code = subscript_for_sighash.or(redeem_script_for_sighash);
4959                let (tapscript, codesep) = if sigversion == SigVersion::Tapscript {
4960                    (Some(script), Some(last_codesep_opcode_pos))
4961                } else {
4962                    (None, None)
4963                };
4964                let ctx = context::ScriptContext {
4965                    tx,
4966                    input_index,
4967                    prevout_values,
4968                    prevout_script_pubkeys,
4969                    block_height,
4970                    median_time_past,
4971                    network,
4972                    sigversion,
4973                    redeem_script_for_sighash,
4974                    script_sig_for_sighash,
4975                    tapscript_for_sighash: tapscript,
4976                    tapscript_codesep_pos: codesep,
4977                    taproot_annex_hash,
4978                    tapscript_validation_weight: weight_cell.as_ref(),
4979                    #[cfg(feature = "production")]
4980                    schnorr_collector,
4981                    precomputed_bip143,
4982                    #[cfg(feature = "production")]
4983                    sighash_cache,
4984                };
4985                if !execute_opcode_with_context_full(
4986                    opcode,
4987                    stack,
4988                    flags,
4989                    &ctx,
4990                    effective_script_code,
4991                )? {
4992                    return Ok(false);
4993                }
4994            }
4995        }
4996        i += 1;
4997    }
4998
4999    if finished_opcode && stack.len() + altstack.len() > MAX_STACK_SIZE {
5000        return Err(make_stack_overflow_error());
5001    }
5002
5003    // Invariant: control stack must be empty — return Err if not (unclosed IF/NOTIF)
5004    if !control_stack.is_empty() {
5005        return Err(ConsensusError::ScriptErrorWithCode {
5006            code: ScriptErrorCode::UnbalancedConditional,
5007            message: "Unclosed IF/NOTIF block".into(),
5008        });
5009    }
5010
5011    // Witness v0 and tapscript require exactly one truthy stack element.
5012    // OP_SUCCESS returns above this. Base scripts leave the check to VerifyScript.
5013    if sigversion == SigVersion::WitnessV0 || sigversion == SigVersion::Tapscript {
5014        let clean = stack.len() == 1 && cast_to_bool(&stack[0]);
5015        return Ok(clean);
5016    }
5017    Ok(true)
5018}
5019
5020/// A script number whose final byte is `0x00` or `0x80` is minimal only when the
5021/// previous byte's high bit requires that sign byte. `0x80` is negative zero.
5022pub(crate) fn script_num_is_minimally_encoded(data: &[u8]) -> bool {
5023    if let Some(&last) = data.last() {
5024        if last & 0x7f == 0 && (data.len() <= 1 || data[data.len() - 2] & 0x80 == 0) {
5025            return false;
5026        }
5027    }
5028    true
5029}
5030
5031/// Decode a CScriptNum from byte representation.
5032/// Bitcoin's variable-length signed integer encoding (little-endian, sign bit in MSB of last byte).
5033/// CScriptNum::set_vch() — BIP62 numeric encoding.
5034#[cfg_attr(feature = "spec-lock", spec_locked("5.4.5", "DecodeCScriptNum"))]
5035#[cfg_attr(feature = "spec-lock", blvm_spec_lock::axiom(result >= -549755813887))]
5036#[cfg_attr(feature = "spec-lock", blvm_spec_lock::ensures(result >= -549755813887))]
5037#[cfg(feature = "production")]
5038#[inline(always)]
5039pub(crate) fn script_num_decode(data: &[u8], max_num_size: usize) -> Result<i64> {
5040    if data.len() > max_num_size {
5041        return Err(ConsensusError::ScriptErrorWithCode {
5042            code: ScriptErrorCode::InvalidStackOperation,
5043            message: format!(
5044                "Script number overflow: {} > {} bytes",
5045                data.len(),
5046                max_num_size
5047            )
5048            .into(),
5049        });
5050    }
5051    if data.is_empty() {
5052        return Ok(0);
5053    }
5054
5055    // Fast paths for common sizes (most script numbers are 1-2 bytes)
5056    let len = data.len();
5057    let result = match len {
5058        1 => {
5059            let byte = data[0];
5060            if byte & 0x80 != 0 {
5061                // Negative: clear sign bit and negate
5062                -((byte & 0x7f) as i64)
5063            } else {
5064                byte as i64
5065            }
5066        }
5067        2 => {
5068            let byte0 = data[0] as i64;
5069            let byte1 = data[1] as i64;
5070            let value = byte0 | (byte1 << 8);
5071            if byte1 & 0x80 != 0 {
5072                // Negative: clear sign bit and negate
5073                -(value & !(0x80i64 << 8))
5074            } else {
5075                value
5076            }
5077        }
5078        _ => {
5079            // General case for 3+ bytes
5080            let mut result: i64 = 0;
5081            for (i, &byte) in data.iter().enumerate() {
5082                result |= (byte as i64) << (8 * i);
5083            }
5084            // Check sign bit (MSB of last byte) - safe because len > 0
5085            let last_idx = len - 1;
5086            if data[last_idx] & 0x80 != 0 {
5087                // Negative: clear sign bit and negate
5088                result &= !(0x80i64 << (8 * last_idx));
5089                result = -result;
5090            }
5091            result
5092        }
5093    };
5094
5095    Ok(result)
5096}
5097
5098#[cfg(not(feature = "production"))]
5099#[inline]
5100pub(crate) fn script_num_decode(data: &[u8], max_num_size: usize) -> Result<i64> {
5101    if data.len() > max_num_size {
5102        return Err(ConsensusError::ScriptErrorWithCode {
5103            code: ScriptErrorCode::InvalidStackOperation,
5104            message: format!(
5105                "Script number overflow: {} > {} bytes",
5106                data.len(),
5107                max_num_size
5108            )
5109            .into(),
5110        });
5111    }
5112    if data.is_empty() {
5113        return Ok(0);
5114    }
5115    // Little-endian decode
5116    let mut result: i64 = 0;
5117    for (i, &byte) in data.iter().enumerate() {
5118        result |= (byte as i64) << (8 * i);
5119    }
5120    // Check sign bit (MSB of last byte)
5121    if data.last().expect("Data is not empty") & 0x80 != 0 {
5122        // Negative: clear sign bit and negate
5123        result &= !(0x80i64 << (8 * (data.len() - 1)));
5124        result = -result;
5125    }
5126    Ok(result)
5127}
5128
5129/// Encoded CScriptNum. At most 9 bytes, kept off the heap.
5130pub(crate) struct ScriptNumBytes {
5131    buf: [u8; 9],
5132    len: u8,
5133}
5134
5135impl ScriptNumBytes {
5136    fn new(buf: [u8; 9], len: u8) -> Self {
5137        Self { buf, len }
5138    }
5139}
5140
5141impl std::ops::Deref for ScriptNumBytes {
5142    type Target = [u8];
5143    fn deref(&self) -> &[u8] {
5144        &self.buf[..self.len as usize]
5145    }
5146}
5147
5148/// Encode an i64 as CScriptNum byte representation.
5149/// CScriptNum::serialize() — BIP62 numeric encoding.
5150pub(crate) fn script_num_encode(value: i64) -> ScriptNumBytes {
5151    match value {
5152        0 => return ScriptNumBytes::new([0; 9], 0),
5153        1 => {
5154            let mut buf = [0u8; 9];
5155            buf[0] = 1;
5156            return ScriptNumBytes::new(buf, 1);
5157        }
5158        -1 => {
5159            let mut buf = [0u8; 9];
5160            buf[0] = 0x81;
5161            return ScriptNumBytes::new(buf, 1);
5162        }
5163        _ => {}
5164    }
5165
5166    let neg = value < 0;
5167    let mut absvalue = if neg {
5168        (-(value as i128)) as u64
5169    } else {
5170        value as u64
5171    };
5172    let mut buf = [0u8; 9];
5173    let mut len = 0u8;
5174    while absvalue > 0 {
5175        buf[len as usize] = (absvalue & 0xff) as u8;
5176        len += 1;
5177        absvalue >>= 8;
5178    }
5179    // If MSB is set, add extra byte for sign
5180    if buf[len as usize - 1] & 0x80 != 0 {
5181        buf[len as usize] = if neg { 0x80 } else { 0x00 };
5182        len += 1;
5183    } else if neg {
5184        buf[len as usize - 1] |= 0x80;
5185    }
5186    ScriptNumBytes::new(buf, len)
5187}
5188
5189/// NOP1 and NOP4–NOP10 stay successful until the upgradable-NOP flag is set.
5190fn upgradable_nop(flags: u32) -> Result<bool> {
5191    if flags & flags::SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS != 0 {
5192        return Err(ConsensusError::ScriptErrorWithCode {
5193            code: ScriptErrorCode::DiscourageUpgradableNops,
5194            message: "reserved NOP is discouraged".into(),
5195        });
5196    }
5197    Ok(true)
5198}
5199
5200/// Encoding is checked before the no-context multisig pushes a false result.
5201/// `OP_NOT` must not turn a STRICTENC failure into success.
5202/// A 0-of-n result is success and Core does not encoding-check those keys.
5203fn strictenc_nocheck_multisig(stack: &[StackElement], flags: u32) -> Result<()> {
5204    if flags & 0x02 == 0 || stack.is_empty() {
5205        return Ok(());
5206    }
5207    let n = match script_num_decode(stack.last().unwrap(), 4) {
5208        Ok(v) if (0..=20).contains(&v) => v as usize,
5209        _ => return Ok(()),
5210    };
5211    if stack.len() < n + 2 {
5212        return Ok(());
5213    }
5214    let n_at = stack.len() - 1;
5215    let m_at = n_at - n - 1;
5216    let m = match script_num_decode(&stack[m_at], 4) {
5217        Ok(v) if v >= 0 && v as usize <= n => v as usize,
5218        _ => return Ok(()),
5219    };
5220    // Core checks encoding only for keys a signature is tested against.
5221    // A 0-of-n multisig tests none, so an unused pubkey stays valid.
5222    if m == 0 {
5223        return Ok(());
5224    }
5225    for pubkey in &stack[n_at - n..n_at] {
5226        crate::script::signature::reject_strictenc_pubkey(flags, pubkey)?;
5227    }
5228    if m_at < m {
5229        return Ok(());
5230    }
5231    for signature in &stack[m_at - m..m_at] {
5232        crate::script::signature::reject_strictenc_signature(flags, signature)?;
5233    }
5234    Ok(())
5235}
5236
5237/// Pop the CHECKMULTISIG stack (n pubkeys, m signatures, dummy) without verifying.
5238/// `None` is a script failure. The caller decides the 0-of-n result.
5239fn pop_nocheck_multisig(stack: &mut Vec<StackElement>) -> Option<usize> {
5240    if stack.is_empty() {
5241        return None;
5242    }
5243    let n_bytes = stack.pop().unwrap();
5244    let n = match script_num_decode(&n_bytes, 4) {
5245        Ok(v) if (0..=20).contains(&v) => v as usize,
5246        _ => return None,
5247    };
5248    if stack.len() < n {
5249        return None;
5250    }
5251    for _ in 0..n {
5252        stack.pop();
5253    }
5254    if stack.is_empty() {
5255        return None;
5256    }
5257    let m_bytes = stack.pop().unwrap();
5258    let m = match script_num_decode(&m_bytes, 4) {
5259        Ok(v) if v >= 0 && v as usize <= n => v as usize,
5260        _ => return None,
5261    };
5262    if stack.len() < m {
5263        return None;
5264    }
5265    for _ in 0..m {
5266        stack.pop();
5267    }
5268    if stack.is_empty() {
5269        return None;
5270    }
5271    stack.pop();
5272    Some(m)
5273}
5274
5275/// Execute a single opcode (currently ignores sigversion; accepts it for future compatibility)
5276#[cfg_attr(feature = "production", inline(always))]
5277fn execute_opcode(
5278    opcode: u8,
5279    stack: &mut Vec<StackElement>,
5280    flags: u32,
5281    _sigversion: SigVersion,
5282) -> Result<bool> {
5283    match opcode {
5284        // OP_0 - push empty array
5285        OP_0 => {
5286            stack.push(to_stack_element(&[]));
5287            Ok(true)
5288        }
5289
5290        // OP_1 to OP_16 - push numbers 1-16
5291        OP_1..=OP_16 => {
5292            let num = opcode - OP_N_BASE;
5293            stack.push(to_stack_element(&[num]));
5294            Ok(true)
5295        }
5296
5297        // OP_NOP - do nothing, execution continues
5298        OP_NOP => Ok(true),
5299
5300        // OP_VER - disabled opcode, always fails
5301        OP_VER => Ok(false),
5302
5303        // OP_DEPTH - push stack size
5304        OP_DEPTH => {
5305            let depth = stack.len() as i64;
5306            stack.push(to_stack_element(&script_num_encode(depth)));
5307            Ok(true)
5308        }
5309
5310        // OP_DUP - duplicate top stack item
5311        OP_DUP => {
5312            if let Some(item) = stack.last().cloned() {
5313                stack.push(item);
5314                Ok(true)
5315            } else {
5316                Ok(false)
5317            }
5318        }
5319
5320        // OP_RIPEMD160 - RIPEMD160(x)
5321        OP_RIPEMD160 => crypto_ops::op_ripemd160(stack),
5322
5323        // OP_SHA1 - SHA1(x)
5324        OP_SHA1 => crypto_ops::op_sha1(stack),
5325
5326        // OP_SHA256 - SHA256(x)
5327        OP_SHA256 => crypto_ops::op_sha256(stack),
5328
5329        // OP_HASH160 - RIPEMD160(SHA256(x))
5330        OP_HASH160 => crypto_ops::op_hash160(stack),
5331
5332        // OP_HASH256 - SHA256(SHA256(x))
5333        OP_HASH256 => crypto_ops::op_hash256(stack),
5334
5335        // OP_EQUAL - check if top two stack items are equal.
5336        // Pushes [] (empty = false) or [0x01] (true).
5337        OP_EQUAL => {
5338            if stack.len() < 2 {
5339                return Err(ConsensusError::ScriptErrorWithCode {
5340                    code: ScriptErrorCode::InvalidStackOperation,
5341                    message: "OP_EQUAL: insufficient stack items".into(),
5342                });
5343            }
5344            let a = stack.pop().unwrap();
5345            let b = stack.pop().unwrap();
5346            stack.push(to_stack_element(if a == b { &[1u8] } else { &[] }));
5347            Ok(true)
5348        }
5349
5350        // OP_EQUALVERIFY - verify top two stack items are equal
5351        // OP_EQUAL followed by pop if equal
5352        OP_EQUALVERIFY => {
5353            if stack.len() < 2 {
5354                return Err(ConsensusError::ScriptErrorWithCode {
5355                    code: ScriptErrorCode::InvalidStackOperation,
5356                    message: "OP_EQUALVERIFY: insufficient stack items".into(),
5357                });
5358            }
5359            let a = stack.pop().unwrap();
5360            let b = stack.pop().unwrap();
5361            let f_equal = a == b;
5362            // Push result (like OP_EQUAL does)
5363            stack.push(to_stack_element(script_bool_bytes(f_equal)));
5364            if f_equal {
5365                // Pop the true value
5366                stack.pop();
5367                Ok(true)
5368            } else {
5369                Err(ConsensusError::ScriptErrorWithCode {
5370                    code: ScriptErrorCode::EqualVerify,
5371                    message: "OP_EQUALVERIFY: stack items not equal".into(),
5372                })
5373            }
5374        }
5375
5376        // OP_CHECKSIG - verify ECDSA signature (simple path, no tx context)
5377        OP_CHECKSIG => crypto_ops::op_checksig_simple(stack, flags),
5378
5379        // OP_CHECKSIGVERIFY - verify ECDSA signature and fail if invalid (simple path)
5380        OP_CHECKSIGVERIFY => crypto_ops::op_checksigverify_simple(stack, flags),
5381
5382        // OP_RETURN - always fail (unspendable output)
5383        OP_RETURN => Ok(false),
5384
5385        // OP_VERIFY - check if top stack item is non-zero
5386        OP_VERIFY => {
5387            if let Some(item) = stack.pop() {
5388                Ok(cast_to_bool(&item))
5389            } else {
5390                Ok(false)
5391            }
5392        }
5393
5394        // OP_CHECKLOCKTIMEVERIFY (BIP65)
5395        // When SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY flag is not set, behaves as NOP (BIP65 rule).
5396        // With the flag set but no tx context, fails. Use verify_script_with_context for full CLTV.
5397        OP_CHECKLOCKTIMEVERIFY => {
5398            if (flags & flags::SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY) == 0 {
5399                Ok(true)
5400            } else {
5401                Ok(false)
5402            }
5403        }
5404
5405        // OP_CHECKSEQUENCEVERIFY (BIP112)
5406        // When SCRIPT_VERIFY_CHECKSEQUENCEVERIFY flag is not set, behaves as NOP (BIP112 rule).
5407        // With the flag set but no tx context, fails. Use verify_script_with_context for full CSV.
5408        OP_CHECKSEQUENCEVERIFY => {
5409            if (flags & flags::SCRIPT_VERIFY_CHECKSEQUENCEVERIFY) == 0 {
5410                Ok(true)
5411            } else {
5412                Ok(false)
5413            }
5414        }
5415
5416        // OP_IFDUP - duplicate top stack item if it's non-zero
5417        OP_IFDUP => {
5418            if let Some(item) = stack.last().cloned() {
5419                if cast_to_bool(&item) {
5420                    stack.push(item);
5421                }
5422                Ok(true)
5423            } else {
5424                Ok(false)
5425            }
5426        }
5427
5428        // OP_DEPTH - push stack size (duplicate handler removed, using single implementation)
5429        // OP_DROP - remove top stack item
5430        OP_DROP => {
5431            if stack.pop().is_some() {
5432                Ok(true)
5433            } else {
5434                Ok(false)
5435            }
5436        }
5437
5438        // OP_NIP - remove second-to-top stack item
5439        OP_NIP => {
5440            if stack.len() >= 2 {
5441                let top = stack.pop().unwrap();
5442                stack.pop(); // Remove second-to-top
5443                stack.push(top);
5444                Ok(true)
5445            } else {
5446                Ok(false)
5447            }
5448        }
5449
5450        // OP_OVER - copy second-to-top stack item to top
5451        OP_OVER => {
5452            if stack.len() >= 2 {
5453                let len = stack.len();
5454                #[cfg(feature = "production")]
5455                {
5456                    // Use proven bounds after length check
5457                    unsafe {
5458                        let second = stack.get_unchecked(len - 2);
5459                        stack.push(second.clone());
5460                    }
5461                }
5462                #[cfg(not(feature = "production"))]
5463                {
5464                    let second = stack[stack.len() - 2].clone();
5465                    stack.push(second);
5466                }
5467                Ok(true)
5468            } else {
5469                Ok(false)
5470            }
5471        }
5472
5473        // OP_PICK - copy nth stack item to top
5474        OP_PICK => {
5475            if let Some(n_bytes) = stack.pop() {
5476                if (flags & crate::script::flags::SCRIPT_VERIFY_MINIMALDATA) != 0
5477                    && !script_num_is_minimally_encoded(&n_bytes)
5478                {
5479                    return Err(ConsensusError::ScriptErrorWithCode {
5480                        code: ScriptErrorCode::MinimalData,
5481                        message: "OP_PICK: non-minimal script number".into(),
5482                    });
5483                }
5484                // Use script_num_decode to properly handle CScriptNum encoding
5485                // (empty [] = 0, [0x00] = 0, [0x01] = 1, etc.)
5486                let n_val = script_num_decode(&n_bytes, 4)?;
5487                if n_val < 0 || n_val as usize >= stack.len() {
5488                    return Ok(false);
5489                }
5490                let n = n_val as usize;
5491                let len = stack.len();
5492                #[cfg(feature = "production")]
5493                {
5494                    // Use proven bounds after length check (n_val < stack.len() already checked)
5495                    unsafe {
5496                        let item = stack.get_unchecked(len - 1 - n);
5497                        stack.push(item.clone());
5498                    }
5499                }
5500                #[cfg(not(feature = "production"))]
5501                {
5502                    let item = stack[stack.len() - 1 - n].clone();
5503                    stack.push(item);
5504                }
5505                Ok(true)
5506            } else {
5507                Ok(false)
5508            }
5509        }
5510
5511        // OP_ROLL - move nth stack item to top
5512        OP_ROLL => {
5513            if let Some(n_bytes) = stack.pop() {
5514                if (flags & crate::script::flags::SCRIPT_VERIFY_MINIMALDATA) != 0
5515                    && !script_num_is_minimally_encoded(&n_bytes)
5516                {
5517                    return Err(ConsensusError::ScriptErrorWithCode {
5518                        code: ScriptErrorCode::MinimalData,
5519                        message: "OP_ROLL: non-minimal script number".into(),
5520                    });
5521                }
5522                // Use script_num_decode to properly handle CScriptNum encoding
5523                // (empty [] = 0, which is a valid no-op roll)
5524                let n_val = script_num_decode(&n_bytes, 4)?;
5525                if n_val < 0 || n_val as usize >= stack.len() {
5526                    return Ok(false);
5527                }
5528                let n = n_val as usize;
5529                let len = stack.len();
5530                #[cfg(feature = "production")]
5531                {
5532                    // Use proven bounds after length check (n_val < stack.len() already checked)
5533                    let idx = len - 1 - n;
5534                    let item = stack.remove(idx);
5535                    stack.push(item);
5536                }
5537                #[cfg(not(feature = "production"))]
5538                {
5539                    let item = stack.remove(stack.len() - 1 - n);
5540                    stack.push(item);
5541                }
5542                Ok(true)
5543            } else {
5544                Ok(false)
5545            }
5546        }
5547
5548        // OP_ROT - rotate top 3 stack items
5549        OP_ROT => {
5550            if stack.len() >= 3 {
5551                let top = stack.pop().unwrap();
5552                let second = stack.pop().unwrap();
5553                let third = stack.pop().unwrap();
5554                stack.push(second);
5555                stack.push(top);
5556                stack.push(third);
5557                Ok(true)
5558            } else {
5559                Ok(false)
5560            }
5561        }
5562
5563        // OP_SWAP - swap top 2 stack items
5564        OP_SWAP => {
5565            if stack.len() >= 2 {
5566                let top = stack.pop().unwrap();
5567                let second = stack.pop().unwrap();
5568                stack.push(top);
5569                stack.push(second);
5570                Ok(true)
5571            } else {
5572                Ok(false)
5573            }
5574        }
5575
5576        // OP_TUCK - copy top stack item to before second-to-top
5577        OP_TUCK => {
5578            if stack.len() >= 2 {
5579                let top = stack.pop().unwrap();
5580                let second = stack.pop().unwrap();
5581                stack.push(top.clone());
5582                stack.push(second);
5583                stack.push(top);
5584                Ok(true)
5585            } else {
5586                Ok(false)
5587            }
5588        }
5589
5590        // OP_2DROP - remove top 2 stack items
5591        OP_2DROP => {
5592            if stack.len() >= 2 {
5593                stack.pop();
5594                stack.pop();
5595                Ok(true)
5596            } else {
5597                Ok(false)
5598            }
5599        }
5600
5601        // OP_2DUP - duplicate top 2 stack items
5602        OP_2DUP => {
5603            if stack.len() >= 2 {
5604                let top = stack[stack.len() - 1].clone();
5605                let second = stack[stack.len() - 2].clone();
5606                stack.push(second);
5607                stack.push(top);
5608                Ok(true)
5609            } else {
5610                Ok(false)
5611            }
5612        }
5613
5614        // OP_3DUP - duplicate top 3 stack items
5615        OP_3DUP => {
5616            if stack.len() >= 3 {
5617                let top = stack[stack.len() - 1].clone();
5618                let second = stack[stack.len() - 2].clone();
5619                let third = stack[stack.len() - 3].clone();
5620                stack.push(third);
5621                stack.push(second);
5622                stack.push(top);
5623                Ok(true)
5624            } else {
5625                Ok(false)
5626            }
5627        }
5628
5629        // OP_2OVER - copy second pair of stack items to top
5630        OP_2OVER => {
5631            if stack.len() >= 4 {
5632                let fourth = stack[stack.len() - 4].clone();
5633                let third = stack[stack.len() - 3].clone();
5634                stack.push(fourth);
5635                stack.push(third);
5636                Ok(true)
5637            } else {
5638                Ok(false)
5639            }
5640        }
5641
5642        // OP_2ROT - move the pair at positions 5 and 6 from top to top.
5643        // Before: [... a b c d e f] (f=top). After: [... c d e f a b] (b=top).
5644        OP_2ROT => {
5645            if stack.len() >= 6 {
5646                // Remove from bottom-most of the pair first (index 0 = 6th from top).
5647                let sixth = stack.remove(stack.len() - 6); // a
5648                // After first remove the stack shrank; 5th from original top is now at index 0.
5649                let fifth = stack.remove(stack.len() - 5); // b
5650                // Push in original order: sixth (a) below, fifth (b) on top.
5651                stack.push(sixth);
5652                stack.push(fifth);
5653                Ok(true)
5654            } else {
5655                Ok(false)
5656            }
5657        }
5658
5659        // OP_2SWAP - swap second pair of stack items
5660        OP_2SWAP => {
5661            if stack.len() >= 4 {
5662                let top = stack.pop().unwrap();
5663                let second = stack.pop().unwrap();
5664                let third = stack.pop().unwrap();
5665                let fourth = stack.pop().unwrap();
5666                stack.push(second);
5667                stack.push(top);
5668                stack.push(fourth);
5669                stack.push(third);
5670                Ok(true)
5671            } else {
5672                Ok(false)
5673            }
5674        }
5675
5676        // OP_SIZE - push size of top stack item
5677        // OP_SIZE - push the byte length of top stack item (does NOT pop)
5678        OP_SIZE => {
5679            if let Some(item) = stack.last() {
5680                let size = item.len() as i64;
5681                stack.push(to_stack_element(&script_num_encode(size)));
5682                Ok(true)
5683            } else {
5684                Ok(false)
5685            }
5686        }
5687
5688        // --- Arithmetic opcodes ---
5689        // All use CScriptNum encoding (max 4 bytes by default)
5690
5691        // OP_1ADD - increment top by 1
5692        OP_1ADD => {
5693            if let Some(item) = stack.pop() {
5694                if (flags & crate::script::flags::SCRIPT_VERIFY_MINIMALDATA) != 0
5695                    && !script_num_is_minimally_encoded(&item)
5696                {
5697                    return Err(ConsensusError::ScriptErrorWithCode {
5698                        code: ScriptErrorCode::MinimalData,
5699                        message: "OP_1ADD: non-minimal script number".into(),
5700                    });
5701                }
5702                let a = script_num_decode(&item, 4)?;
5703                stack.push(to_stack_element(&script_num_encode(a + 1)));
5704                Ok(true)
5705            } else {
5706                Ok(false)
5707            }
5708        }
5709        // OP_1SUB - decrement top by 1
5710        OP_1SUB => {
5711            if let Some(item) = stack.pop() {
5712                if (flags & crate::script::flags::SCRIPT_VERIFY_MINIMALDATA) != 0
5713                    && !script_num_is_minimally_encoded(&item)
5714                {
5715                    return Err(ConsensusError::ScriptErrorWithCode {
5716                        code: ScriptErrorCode::MinimalData,
5717                        message: "OP_1SUB: non-minimal script number".into(),
5718                    });
5719                }
5720                let a = script_num_decode(&item, 4)?;
5721                stack.push(to_stack_element(&script_num_encode(a - 1)));
5722                Ok(true)
5723            } else {
5724                Ok(false)
5725            }
5726        }
5727        // OP_2MUL - DISABLED
5728        OP_2MUL => Err(ConsensusError::ScriptErrorWithCode {
5729            code: ScriptErrorCode::DisabledOpcode,
5730            message: "OP_2MUL is disabled".into(),
5731        }),
5732        // OP_2DIV - DISABLED
5733        OP_2DIV => Err(ConsensusError::ScriptErrorWithCode {
5734            code: ScriptErrorCode::DisabledOpcode,
5735            message: "OP_2DIV is disabled".into(),
5736        }),
5737        // OP_NEGATE - negate top
5738        OP_NEGATE => {
5739            if let Some(item) = stack.pop() {
5740                if (flags & crate::script::flags::SCRIPT_VERIFY_MINIMALDATA) != 0
5741                    && !script_num_is_minimally_encoded(&item)
5742                {
5743                    return Err(ConsensusError::ScriptErrorWithCode {
5744                        code: ScriptErrorCode::MinimalData,
5745                        message: "OP_NEGATE: non-minimal script number".into(),
5746                    });
5747                }
5748                let a = script_num_decode(&item, 4)?;
5749                stack.push(to_stack_element(&script_num_encode(-a)));
5750                Ok(true)
5751            } else {
5752                Ok(false)
5753            }
5754        }
5755        // OP_ABS - absolute value
5756        OP_ABS => {
5757            if let Some(item) = stack.pop() {
5758                if (flags & crate::script::flags::SCRIPT_VERIFY_MINIMALDATA) != 0
5759                    && !script_num_is_minimally_encoded(&item)
5760                {
5761                    return Err(ConsensusError::ScriptErrorWithCode {
5762                        code: ScriptErrorCode::MinimalData,
5763                        message: "OP_ABS: non-minimal script number".into(),
5764                    });
5765                }
5766                let a = script_num_decode(&item, 4)?;
5767                stack.push(to_stack_element(&script_num_encode(a.abs())));
5768                Ok(true)
5769            } else {
5770                Ok(false)
5771            }
5772        }
5773        // OP_NOT - logical NOT: 0 → 1, nonzero → 0
5774        OP_NOT => {
5775            if let Some(item) = stack.pop() {
5776                if (flags & crate::script::flags::SCRIPT_VERIFY_MINIMALDATA) != 0
5777                    && !script_num_is_minimally_encoded(&item)
5778                {
5779                    return Err(ConsensusError::ScriptErrorWithCode {
5780                        code: ScriptErrorCode::MinimalData,
5781                        message: "OP_NOT: non-minimal script number".into(),
5782                    });
5783                }
5784                let a = script_num_decode(&item, 4)?;
5785                stack.push(to_stack_element(&script_num_encode(if a == 0 {
5786                    1
5787                } else {
5788                    0
5789                })));
5790                Ok(true)
5791            } else {
5792                Ok(false)
5793            }
5794        }
5795        // OP_0NOTEQUAL - 0 → 0, nonzero → 1
5796        OP_0NOTEQUAL => {
5797            if let Some(item) = stack.pop() {
5798                if (flags & crate::script::flags::SCRIPT_VERIFY_MINIMALDATA) != 0
5799                    && !script_num_is_minimally_encoded(&item)
5800                {
5801                    return Err(ConsensusError::ScriptErrorWithCode {
5802                        code: ScriptErrorCode::MinimalData,
5803                        message: "OP_0NOTEQUAL: non-minimal script number".into(),
5804                    });
5805                }
5806                let a = script_num_decode(&item, 4)?;
5807                stack.push(to_stack_element(&script_num_encode(if a != 0 {
5808                    1
5809                } else {
5810                    0
5811                })));
5812                Ok(true)
5813            } else {
5814                Ok(false)
5815            }
5816        }
5817        OP_ADD => {
5818            if (flags & crate::script::flags::SCRIPT_VERIFY_MINIMALDATA) != 0 && stack.len() >= 2 {
5819                let len = stack.len();
5820                if !script_num_is_minimally_encoded(&stack[len - 1])
5821                    || !script_num_is_minimally_encoded(&stack[len - 2])
5822                {
5823                    return Err(ConsensusError::ScriptErrorWithCode {
5824                        code: ScriptErrorCode::MinimalData,
5825                        message: "OP_ADD: non-minimal script number".into(),
5826                    });
5827                }
5828            }
5829            arithmetic::op_add(stack)
5830        }
5831        OP_SUB => {
5832            if (flags & crate::script::flags::SCRIPT_VERIFY_MINIMALDATA) != 0 && stack.len() >= 2 {
5833                let len = stack.len();
5834                if !script_num_is_minimally_encoded(&stack[len - 1])
5835                    || !script_num_is_minimally_encoded(&stack[len - 2])
5836                {
5837                    return Err(ConsensusError::ScriptErrorWithCode {
5838                        code: ScriptErrorCode::MinimalData,
5839                        message: "OP_SUB: non-minimal script number".into(),
5840                    });
5841                }
5842            }
5843            arithmetic::op_sub(stack)
5844        }
5845        OP_MUL => arithmetic::op_mul_disabled(),
5846        OP_DIV => arithmetic::op_div_disabled(),
5847        OP_MOD => arithmetic::op_mod_disabled(),
5848        OP_LSHIFT => arithmetic::op_lshift_disabled(),
5849        OP_RSHIFT => arithmetic::op_rshift_disabled(),
5850        OP_BOOLAND => {
5851            if (flags & crate::script::flags::SCRIPT_VERIFY_MINIMALDATA) != 0 && stack.len() >= 2 {
5852                let len = stack.len();
5853                if !script_num_is_minimally_encoded(&stack[len - 1])
5854                    || !script_num_is_minimally_encoded(&stack[len - 2])
5855                {
5856                    return Err(ConsensusError::ScriptErrorWithCode {
5857                        code: ScriptErrorCode::MinimalData,
5858                        message: "OP_BOOLAND: non-minimal script number".into(),
5859                    });
5860                }
5861            }
5862            arithmetic::op_booland(stack)
5863        }
5864        OP_BOOLOR => {
5865            if (flags & crate::script::flags::SCRIPT_VERIFY_MINIMALDATA) != 0 && stack.len() >= 2 {
5866                let len = stack.len();
5867                if !script_num_is_minimally_encoded(&stack[len - 1])
5868                    || !script_num_is_minimally_encoded(&stack[len - 2])
5869                {
5870                    return Err(ConsensusError::ScriptErrorWithCode {
5871                        code: ScriptErrorCode::MinimalData,
5872                        message: "OP_BOOLOR: non-minimal script number".into(),
5873                    });
5874                }
5875            }
5876            arithmetic::op_boolor(stack)
5877        }
5878        OP_NUMEQUAL => {
5879            if (flags & crate::script::flags::SCRIPT_VERIFY_MINIMALDATA) != 0 && stack.len() >= 2 {
5880                let len = stack.len();
5881                if !script_num_is_minimally_encoded(&stack[len - 1])
5882                    || !script_num_is_minimally_encoded(&stack[len - 2])
5883                {
5884                    return Err(ConsensusError::ScriptErrorWithCode {
5885                        code: ScriptErrorCode::MinimalData,
5886                        message: "OP_NUMEQUAL: non-minimal script number".into(),
5887                    });
5888                }
5889            }
5890            arithmetic::op_numequal(stack)
5891        }
5892        OP_NUMEQUALVERIFY => {
5893            if (flags & crate::script::flags::SCRIPT_VERIFY_MINIMALDATA) != 0 && stack.len() >= 2 {
5894                let len = stack.len();
5895                if !script_num_is_minimally_encoded(&stack[len - 1])
5896                    || !script_num_is_minimally_encoded(&stack[len - 2])
5897                {
5898                    return Err(ConsensusError::ScriptErrorWithCode {
5899                        code: ScriptErrorCode::MinimalData,
5900                        message: "OP_NUMEQUALVERIFY: non-minimal script number".into(),
5901                    });
5902                }
5903            }
5904            arithmetic::op_numequalverify(stack)
5905        }
5906        OP_NUMNOTEQUAL => {
5907            if (flags & crate::script::flags::SCRIPT_VERIFY_MINIMALDATA) != 0 && stack.len() >= 2 {
5908                let len = stack.len();
5909                if !script_num_is_minimally_encoded(&stack[len - 1])
5910                    || !script_num_is_minimally_encoded(&stack[len - 2])
5911                {
5912                    return Err(ConsensusError::ScriptErrorWithCode {
5913                        code: ScriptErrorCode::MinimalData,
5914                        message: "OP_NUMNOTEQUAL: non-minimal script number".into(),
5915                    });
5916                }
5917            }
5918            arithmetic::op_numnotequal(stack)
5919        }
5920        OP_LESSTHAN => {
5921            if (flags & crate::script::flags::SCRIPT_VERIFY_MINIMALDATA) != 0 && stack.len() >= 2 {
5922                let len = stack.len();
5923                if !script_num_is_minimally_encoded(&stack[len - 1])
5924                    || !script_num_is_minimally_encoded(&stack[len - 2])
5925                {
5926                    return Err(ConsensusError::ScriptErrorWithCode {
5927                        code: ScriptErrorCode::MinimalData,
5928                        message: "OP_LESSTHAN: non-minimal script number".into(),
5929                    });
5930                }
5931            }
5932            arithmetic::op_lessthan(stack)
5933        }
5934        OP_GREATERTHAN => {
5935            if (flags & crate::script::flags::SCRIPT_VERIFY_MINIMALDATA) != 0 && stack.len() >= 2 {
5936                let len = stack.len();
5937                if !script_num_is_minimally_encoded(&stack[len - 1])
5938                    || !script_num_is_minimally_encoded(&stack[len - 2])
5939                {
5940                    return Err(ConsensusError::ScriptErrorWithCode {
5941                        code: ScriptErrorCode::MinimalData,
5942                        message: "OP_GREATERTHAN: non-minimal script number".into(),
5943                    });
5944                }
5945            }
5946            arithmetic::op_greaterthan(stack)
5947        }
5948        OP_LESSTHANOREQUAL => {
5949            if (flags & crate::script::flags::SCRIPT_VERIFY_MINIMALDATA) != 0 && stack.len() >= 2 {
5950                let len = stack.len();
5951                if !script_num_is_minimally_encoded(&stack[len - 1])
5952                    || !script_num_is_minimally_encoded(&stack[len - 2])
5953                {
5954                    return Err(ConsensusError::ScriptErrorWithCode {
5955                        code: ScriptErrorCode::MinimalData,
5956                        message: "OP_LESSTHANOREQUAL: non-minimal script number".into(),
5957                    });
5958                }
5959            }
5960            arithmetic::op_lessthanorequal(stack)
5961        }
5962        OP_GREATERTHANOREQUAL => {
5963            if (flags & crate::script::flags::SCRIPT_VERIFY_MINIMALDATA) != 0 && stack.len() >= 2 {
5964                let len = stack.len();
5965                if !script_num_is_minimally_encoded(&stack[len - 1])
5966                    || !script_num_is_minimally_encoded(&stack[len - 2])
5967                {
5968                    return Err(ConsensusError::ScriptErrorWithCode {
5969                        code: ScriptErrorCode::MinimalData,
5970                        message: "OP_GREATERTHANOREQUAL: non-minimal script number".into(),
5971                    });
5972                }
5973            }
5974            arithmetic::op_greaterthanorequal(stack)
5975        }
5976        OP_MIN => {
5977            if (flags & crate::script::flags::SCRIPT_VERIFY_MINIMALDATA) != 0 && stack.len() >= 2 {
5978                let len = stack.len();
5979                if !script_num_is_minimally_encoded(&stack[len - 1])
5980                    || !script_num_is_minimally_encoded(&stack[len - 2])
5981                {
5982                    return Err(ConsensusError::ScriptErrorWithCode {
5983                        code: ScriptErrorCode::MinimalData,
5984                        message: "OP_MIN: non-minimal script number".into(),
5985                    });
5986                }
5987            }
5988            arithmetic::op_min(stack)
5989        }
5990        OP_MAX => {
5991            if (flags & crate::script::flags::SCRIPT_VERIFY_MINIMALDATA) != 0 && stack.len() >= 2 {
5992                let len = stack.len();
5993                if !script_num_is_minimally_encoded(&stack[len - 1])
5994                    || !script_num_is_minimally_encoded(&stack[len - 2])
5995                {
5996                    return Err(ConsensusError::ScriptErrorWithCode {
5997                        code: ScriptErrorCode::MinimalData,
5998                        message: "OP_MAX: non-minimal script number".into(),
5999                    });
6000                }
6001            }
6002            arithmetic::op_max(stack)
6003        }
6004        OP_WITHIN => {
6005            if (flags & crate::script::flags::SCRIPT_VERIFY_MINIMALDATA) != 0 && stack.len() >= 3 {
6006                let len = stack.len();
6007                if !script_num_is_minimally_encoded(&stack[len - 1])
6008                    || !script_num_is_minimally_encoded(&stack[len - 2])
6009                    || !script_num_is_minimally_encoded(&stack[len - 3])
6010                {
6011                    return Err(ConsensusError::ScriptErrorWithCode {
6012                        code: ScriptErrorCode::MinimalData,
6013                        message: "OP_WITHIN: non-minimal script number".into(),
6014                    });
6015                }
6016            }
6017            arithmetic::op_within(stack)
6018        }
6019
6020        // OP_CODESEPARATOR - marks position for sighash (no-op in execute_opcode)
6021        OP_CODESEPARATOR => Ok(true),
6022
6023        // OP_NOP1 and OP_NOP5-OP_NOP10. OP_NOP4 is OP_CHECKTEMPLATEVERIFY.
6024        OP_NOP1 | OP_NOP5..=OP_NOP10 => upgradable_nop(flags),
6025
6026        // OP_CHECKTEMPLATEVERIFY (NOP4 / BIP 119)
6027        // Treated as a reserved NOP unless the CTV feature is on and its flag is set.
6028        OP_CHECKTEMPLATEVERIFY => {
6029            #[cfg(feature = "ctv")]
6030            if flags & flags::SCRIPT_VERIFY_DEFAULT_CHECK_TEMPLATE_VERIFY_HASH != 0 {
6031                return Err(ConsensusError::ScriptErrorWithCode {
6032                    code: ScriptErrorCode::TxInvalid,
6033                    message: "OP_CHECKTEMPLATEVERIFY requires transaction context".into(),
6034                });
6035            }
6036            upgradable_nop(flags)
6037        }
6038
6039        // Disabled string opcodes - must return error per consensus
6040        OP_DISABLED_STRING_RANGE_START..=OP_DISABLED_STRING_RANGE_END
6041        | OP_DISABLED_BITWISE_RANGE_START..=OP_DISABLED_BITWISE_RANGE_END => {
6042            Err(ConsensusError::ScriptErrorWithCode {
6043                code: ScriptErrorCode::DisabledOpcode,
6044                message: format!("Disabled opcode 0x{opcode:02x}").into(),
6045            })
6046        }
6047
6048        // OP_1NEGATE - push -1 onto stack
6049        OP_1NEGATE => {
6050            stack.push(to_stack_element(&[0x81]));
6051            Ok(true)
6052        }
6053
6054        // OP_CHECKMULTISIG - verify m-of-n multisig without transaction context.
6055        // Handles stack mechanics correctly for all cases; succeeds only when m=0
6056        // (no signatures to verify). Vectors requiring real ECDSA verification need
6057        // verify_script_with_context_full.
6058        OP_CHECKMULTISIG => {
6059            strictenc_nocheck_multisig(stack, flags)?;
6060            let Some(m) = pop_nocheck_multisig(stack) else {
6061                return Ok(false);
6062            };
6063            // Without tx context only 0-sig multisig can succeed
6064            stack.push(to_stack_element(script_bool_bytes(m == 0)));
6065            Ok(true)
6066        }
6067
6068        // OP_CHECKMULTISIGVERIFY - CHECKMULTISIG then VERIFY
6069        OP_CHECKMULTISIGVERIFY => {
6070            strictenc_nocheck_multisig(stack, flags)?;
6071            let Some(m) = pop_nocheck_multisig(stack) else {
6072                return Ok(false);
6073            };
6074            Ok(m == 0)
6075        }
6076
6077        // Unknown opcode
6078        _ => Ok(false),
6079    }
6080}
6081
6082/// Three data pushes: signature, pubkey, and a 25-byte P2PKH redeem.
6083/// Borrows `script_sig`. Numeric opcodes are not this pattern.
6084#[inline(always)]
6085fn parse_p2sh_p2pkh_pushes(script_sig: &[u8]) -> Option<(&[u8], &[u8], &[u8])> {
6086    let mut i = 0;
6087    let (adv1, s_start, s_end) = parse_one_data_push(script_sig, i)?;
6088    i += adv1;
6089    if i >= script_sig.len() {
6090        return None;
6091    }
6092    let (adv2, p_start, p_end) = parse_one_data_push(script_sig, i)?;
6093    i += adv2;
6094    if i >= script_sig.len() {
6095        return None;
6096    }
6097    let (adv3, r_start, r_end) = parse_one_data_push(script_sig, i)?;
6098    i += adv3;
6099    if i != script_sig.len() {
6100        return None;
6101    }
6102    let sig = &script_sig[s_start..s_end];
6103    let pubkey = &script_sig[p_start..p_end];
6104    let redeem = &script_sig[r_start..r_end];
6105    if sig.is_empty() || redeem.len() != 25 {
6106        return None;
6107    }
6108    if redeem[0] != OP_DUP
6109        || redeem[1] != OP_HASH160
6110        || redeem[2] != PUSH_20_BYTES
6111        || redeem[23] != OP_EQUALVERIFY
6112        || redeem[24] != OP_CHECKSIG
6113    {
6114        return None;
6115    }
6116    Some((sig, pubkey, redeem))
6117}
6118
6119/// Two data pushes: a non-empty signature and a canonical P2PK redeem.
6120/// Borrows `script_sig`. Numeric opcodes are not this pattern.
6121#[inline(always)]
6122fn parse_p2sh_p2pk_pushes(script_sig: &[u8]) -> Option<(&[u8], &[u8], &[u8])> {
6123    let mut i = 0;
6124    let (adv1, s_start, s_end) = parse_one_data_push(script_sig, i)?;
6125    i += adv1;
6126    if i >= script_sig.len() {
6127        return None;
6128    }
6129    let (adv2, r_start, r_end) = parse_one_data_push(script_sig, i)?;
6130    i += adv2;
6131    if i != script_sig.len() {
6132        return None;
6133    }
6134    let sig = &script_sig[s_start..s_end];
6135    let redeem = &script_sig[r_start..r_end];
6136    if sig.is_empty() {
6137        return None;
6138    }
6139    let pubkey = canonical_p2pk_pubkey(redeem)?;
6140    Some((sig, pubkey, redeem))
6141}
6142
6143/// Parse P2SH-P2PKH scriptSig for batch sighash precompute. Zero-allocation.
6144/// script_sig = [sig, pubkey, redeem] where redeem is P2PKH (25 bytes).
6145/// Returns (sighash_byte, redeem_slice) or None. Pub(crate) for block.rs.
6146#[cfg(feature = "production")]
6147#[inline(always)]
6148pub(crate) fn parse_p2sh_p2pkh_for_precompute(script_sig: &[u8]) -> Option<(u8, &[u8])> {
6149    let (sig, _pubkey, redeem) = parse_p2sh_p2pkh_pushes(script_sig)?;
6150    Some((sig[sig.len() - 1], redeem))
6151}
6152
6153/// Zero-allocation parser for P2PKH scriptSig: exactly two data pushes (`signature`, then `pubkey`).
6154/// Returns (sig_slice, pubkey_slice) borrowing into script_sig, or None if invalid.
6155/// Pub(crate) for batch sighash precompute in block.rs.
6156#[inline(always)]
6157pub(crate) fn parse_p2pkh_script_sig(script_sig: &[u8]) -> Option<(&[u8], &[u8])> {
6158    let mut i = 0;
6159    let (adv1, s_start, s_end) = parse_one_data_push(script_sig, i)?;
6160    i += adv1;
6161    if i >= script_sig.len() {
6162        return None;
6163    }
6164    let (adv2, p_start, p_end) = parse_one_data_push(script_sig, i)?;
6165    i += adv2;
6166    if i != script_sig.len() {
6167        return None;
6168    }
6169    Some((&script_sig[s_start..s_end], &script_sig[p_start..p_end]))
6170}
6171
6172/// Parse P2PK scriptSig as single push (signature). Returns sig slice or None.
6173/// Used for P2PK pre-extraction in producer.
6174pub(crate) fn parse_p2pk_script_sig(script_sig: &[u8]) -> Option<&[u8]> {
6175    let (advance, data_start, data_end) = parse_one_data_push(script_sig, 0)?;
6176    if advance != script_sig.len() {
6177        return None;
6178    }
6179    Some(&script_sig[data_start..data_end])
6180}
6181
6182/// Parse a single push opcode at `i`, return (advance, data_start, data_end). Rejects OP_0 and numerics.
6183fn parse_one_data_push(script: &[u8], i: usize) -> Option<(usize, usize, usize)> {
6184    if i >= script.len() {
6185        return None;
6186    }
6187    let opcode = script[i];
6188    let (advance, data_start, data_end) = if opcode == OP_0 {
6189        return None;
6190    } else if opcode <= 0x4b {
6191        let len = opcode as usize;
6192        if i + 1 + len > script.len() {
6193            return None;
6194        }
6195        (1 + len, i + 1, i + 1 + len)
6196    } else if opcode == OP_PUSHDATA1 {
6197        if i + 1 >= script.len() {
6198            return None;
6199        }
6200        let len = script[i + 1] as usize;
6201        if i + 2 + len > script.len() {
6202            return None;
6203        }
6204        (2 + len, i + 2, i + 2 + len)
6205    } else if opcode == OP_PUSHDATA2 {
6206        if i + 2 >= script.len() {
6207            return None;
6208        }
6209        let len = u16::from_le_bytes([script[i + 1], script[i + 2]]) as usize;
6210        if i + 3 + len > script.len() {
6211            return None;
6212        }
6213        (3 + len, i + 3, i + 3 + len)
6214    } else if opcode == OP_PUSHDATA4 {
6215        if i + 4 >= script.len() {
6216            return None;
6217        }
6218        let len = u32::from_le_bytes([script[i + 1], script[i + 2], script[i + 3], script[i + 4]])
6219            as usize;
6220        if i + 5 + len > script.len() {
6221            return None;
6222        }
6223        (5 + len, i + 5, i + 5 + len)
6224    } else {
6225        return None;
6226    };
6227    Some((advance, data_start, data_end))
6228}
6229
6230/// P2SH Push-Only Validation (Orange Paper 5.2.1).
6231/// Returns true if script_sig contains only push opcodes (valid), false otherwise (invalid).
6232#[cfg_attr(feature = "spec-lock", spec_locked("5.2.1", "P2SHPushOnlyCheck"))]
6233pub fn p2sh_push_only_check(script_sig: &[u8]) -> bool {
6234    let mut i = 0;
6235    while i < script_sig.len() {
6236        let opcode = script_sig[i];
6237        if !is_push_opcode(opcode) {
6238            return false;
6239        }
6240        if opcode == OP_0 || opcode == OP_1NEGATE || (OP_1..=OP_16).contains(&opcode) {
6241            i += 1;
6242            continue;
6243        }
6244        let Some((advance, _, _)) = parse_one_data_push(script_sig, i) else {
6245            return false;
6246        };
6247        i += advance;
6248    }
6249    true
6250}
6251
6252/// Copies each push. Spec-lock mutation coverage extracts this body; the live check does not call it.
6253#[allow(dead_code)]
6254fn parse_script_sig_push_only(script_sig: &[u8]) -> Option<Vec<StackElement>> {
6255    let mut out = Vec::new();
6256    let mut i = 0;
6257    while i < script_sig.len() {
6258        let opcode = script_sig[i];
6259        if !is_push_opcode(opcode) {
6260            return None;
6261        }
6262        if opcode == OP_0 {
6263            out.push(to_stack_element(&[]));
6264            i += 1;
6265            continue;
6266        }
6267        if opcode == OP_1NEGATE || (OP_1..=OP_16).contains(&opcode) {
6268            // Numeric pushes. OP_RESERVED sits between OP_1NEGATE and OP_1 and is not one of them.
6269            out.push(to_stack_element(&script_num_encode(
6270                script_num_from_opcode(opcode),
6271            )));
6272            i += 1;
6273            continue;
6274        }
6275        let (advance, data_start, data_end) = parse_one_data_push(script_sig, i)?;
6276        out.push(to_stack_element(&script_sig[data_start..data_end]));
6277        i += advance;
6278    }
6279    Some(out)
6280}
6281
6282/// One scriptSig push. Data and `OP_0` borrow the script. A numeric opcode keeps its
6283/// one-byte encoding beside the slice, because that encoding is not in the script.
6284enum BorrowedPush<'a> {
6285    Borrowed(&'a [u8]),
6286    Number(ScriptNumBytes),
6287}
6288
6289impl AsRef<[u8]> for BorrowedPush<'_> {
6290    fn as_ref(&self) -> &[u8] {
6291        match self {
6292            BorrowedPush::Borrowed(bytes) => bytes,
6293            BorrowedPush::Number(encoded) => &encoded[..],
6294        }
6295    }
6296}
6297
6298/// Push-only script as borrowed values. `OP_0` is an empty slice. `OP_RESERVED` is not a push.
6299fn parse_borrowed_pushes(script_sig: &[u8]) -> Option<Vec<BorrowedPush<'_>>> {
6300    let mut out = Vec::new();
6301    let mut i = 0;
6302    while i < script_sig.len() {
6303        let opcode = script_sig[i];
6304        if !is_push_opcode(opcode) {
6305            return None;
6306        }
6307        if opcode == OP_0 {
6308            out.push(BorrowedPush::Borrowed(&[]));
6309            i += 1;
6310            continue;
6311        }
6312        if opcode == OP_1NEGATE || (OP_1..=OP_16).contains(&opcode) {
6313            out.push(BorrowedPush::Number(script_num_encode(
6314                script_num_from_opcode(opcode),
6315            )));
6316            i += 1;
6317            continue;
6318        }
6319        let (advance, data_start, data_end) = parse_one_data_push(script_sig, i)?;
6320        out.push(BorrowedPush::Borrowed(&script_sig[data_start..data_end]));
6321        i += advance;
6322    }
6323    Some(out)
6324}
6325
6326/// Stack items are bottom-to-top, before the script pushes `m`, the keys, and `n`.
6327///
6328/// `OP_CHECKMULTISIG` pops exactly `m` signatures from the top and the item
6329/// under them as the dummy. `exact` is the witness rule: the stack must be
6330/// that dummy plus those signatures and nothing underneath.
6331fn checkmultisig_window<T: AsRef<[u8]>>(
6332    items: &[T],
6333    m: u8,
6334    exact: bool,
6335) -> Option<(&[u8], Vec<&[u8]>)> {
6336    let m = m as usize;
6337    let need = m + 1;
6338    if m == 0 || items.len() < need {
6339        return None;
6340    }
6341    if exact && items.len() != need {
6342        return None;
6343    }
6344    let sig_at = items.len() - m;
6345    let dummy = items[sig_at - 1].as_ref();
6346    let signatures = items[sig_at..].iter().map(|item| item.as_ref()).collect();
6347    Some((dummy, signatures))
6348}
6349
6350/// Parse redeem script as `OP_m <pubkeys> OP_n OP_CHECKMULTISIG`.
6351///
6352/// `m` is the required signature count. `n` is the pubkey count. Each key is a
6353/// `PUSH_33_BYTES` or `PUSH_65_BYTES` push. A direct push of 2, 3, or 4 bytes
6354/// is not a key.
6355///
6356/// Returns `(m, n, pubkey_slices)` or `None` if the format doesn't match.
6357fn parse_redeem_multisig(redeem: &[u8]) -> Option<(u8, u8, Vec<&[u8]>)> {
6358    if redeem.len() < 4 {
6359        return None;
6360    }
6361    // Trailer: OP_n OP_CHECKMULTISIG
6362    if redeem[redeem.len() - 1] != OP_CHECKMULTISIG {
6363        return None;
6364    }
6365    let n_op = redeem[redeem.len() - 2];
6366    if !(OP_1..=OP_16).contains(&n_op) {
6367        return None;
6368    }
6369    let n = (n_op - OP_1 + 1) as usize;
6370    let m_op = redeem[0];
6371    if !(OP_1..=OP_16).contains(&m_op) {
6372        return None;
6373    }
6374    let m = m_op - OP_1 + 1;
6375    if (m as usize) > n || m == 0 {
6376        return None;
6377    }
6378    let mut i = 1;
6379    let mut pubkeys = Vec::with_capacity(n);
6380    for _ in 0..n {
6381        if i >= redeem.len().saturating_sub(2) {
6382            return None;
6383        }
6384        let first = redeem[i];
6385        let (pk_start, pk_len) = if first == PUSH_33_BYTES {
6386            (i + 1, PUSH_33_BYTES as usize)
6387        } else if first == PUSH_65_BYTES {
6388            (i + 1, PUSH_65_BYTES as usize)
6389        } else {
6390            return None;
6391        };
6392        if pk_start + pk_len > redeem.len().saturating_sub(2) {
6393            return None;
6394        }
6395        let pk = &redeem[pk_start..pk_start + pk_len];
6396        let hdr = pk[0];
6397        if !(hdr == 0x02 || hdr == 0x03 || hdr == 0x04) {
6398            return None;
6399        }
6400        if (hdr == 0x02 || hdr == 0x03) && pk_len != 33 {
6401            return None;
6402        }
6403        if hdr == 0x04 && pk_len != 65 {
6404            return None;
6405        }
6406        pubkeys.push(pk);
6407        i = pk_start + pk_len;
6408    }
6409    // Must land exactly on OP_n OP_CHECKMULTISIG.
6410    if i != redeem.len() - 2 {
6411        return None;
6412    }
6413    Some((m, n as u8, pubkeys))
6414}
6415
6416#[cfg(test)]
6417mod parse_redeem_multisig_tests {
6418    use super::*;
6419
6420    #[test]
6421    fn accepts_push_encoded_compressed_2of3() {
6422        // Standard: OP_m <pks> OP_n OP_CHECKMULTISIG  →  2-of-3
6423        let mut redeem = vec![OP_2];
6424        for i in 0..3u8 {
6425            redeem.push(PUSH_33_BYTES);
6426            let mut pk = [0u8; 33];
6427            pk[0] = 0x02;
6428            pk[1] = i + 1;
6429            redeem.extend_from_slice(&pk);
6430        }
6431        redeem.push(OP_3);
6432        redeem.push(OP_CHECKMULTISIG);
6433        let (m, n, pks) = parse_redeem_multisig(&redeem).expect("push-encoded redeem");
6434        assert_eq!(m, 2);
6435        assert_eq!(n, 3);
6436        assert_eq!(pks.len(), 3);
6437        assert_eq!(pks[0][0], 0x02);
6438    }
6439
6440    #[test]
6441    fn rejects_raw_key_without_a_push_opcode() {
6442        let mut compressed = vec![OP_1];
6443        let mut pk33 = [0u8; PUSH_33_BYTES as usize];
6444        pk33[0] = 3;
6445        compressed.extend_from_slice(&pk33);
6446        compressed.push(OP_1);
6447        compressed.push(OP_CHECKMULTISIG);
6448        assert!(parse_redeem_multisig(&compressed).is_none());
6449
6450        let mut uncompressed = vec![OP_1];
6451        let pk65 = [4u8; PUSH_65_BYTES as usize];
6452        uncompressed.extend_from_slice(&pk65);
6453        uncompressed.push(OP_1);
6454        uncompressed.push(OP_CHECKMULTISIG);
6455        assert!(parse_redeem_multisig(&uncompressed).is_none());
6456    }
6457
6458    #[test]
6459    fn rejects_trailing_opcodes() {
6460        let mut redeem = vec![OP_1, PUSH_33_BYTES];
6461        let mut pk = [0u8; 33];
6462        pk[0] = 0x02;
6463        redeem.extend_from_slice(&pk);
6464        redeem.push(OP_1);
6465        redeem.push(OP_CHECKMULTISIG);
6466        redeem.push(OP_DROP);
6467        assert!(parse_redeem_multisig(&redeem).is_none());
6468    }
6469
6470    #[test]
6471    fn checkmultisig_window_is_the_top_m_signatures() {
6472        let items = vec![
6473            to_stack_element(&[]),
6474            to_stack_element(&[0x11]),
6475            to_stack_element(&[0x22]),
6476        ];
6477        let (dummy, sigs) = checkmultisig_window(&items, 1, false).expect("legacy window");
6478        assert_eq!(dummy, &[0x11]);
6479        assert_eq!(sigs, vec![&[0x22][..]]);
6480        assert!(checkmultisig_window(&items, 1, true).is_none());
6481
6482        let exact = vec![to_stack_element(&[]), to_stack_element(&[0x22])];
6483        let (dummy, sigs) = checkmultisig_window(&exact, 1, true).expect("witness window");
6484        assert!(dummy.is_empty());
6485        assert_eq!(sigs, vec![&[0x22][..]]);
6486        assert!(checkmultisig_window(&exact, 2, false).is_none());
6487    }
6488
6489    #[cfg(all(feature = "production", feature = "blvm-secp256k1"))]
6490    #[test]
6491    fn p2sh_multisig_fast_path_rejects_a_signature_above_the_window() {
6492        use crate::script::flags::{
6493            SCRIPT_VERIFY_DERSIG, SCRIPT_VERIFY_P2SH, SCRIPT_VERIFY_WITNESS,
6494        };
6495        use crate::transaction_hash::compute_legacy_sighash_nocache;
6496        use crate::types::{Network, OutPoint, Transaction, TransactionInput, TransactionOutput};
6497        use secp256k1::{Message, PublicKey, Secp256k1, SecretKey};
6498
6499        let secp = Secp256k1::new();
6500        let sk = SecretKey::from_slice(&[0x41; 32]).expect("secret");
6501        let pk1 = PublicKey::from_secret_key(&secp, &sk).serialize();
6502        let pk2 =
6503            PublicKey::from_secret_key(&secp, &SecretKey::from_slice(&[0x42; 32]).expect("secret"))
6504                .serialize();
6505        let mut redeem = vec![OP_1, PUSH_33_BYTES];
6506        redeem.extend_from_slice(&pk1);
6507        redeem.push(PUSH_33_BYTES);
6508        redeem.extend_from_slice(&pk2);
6509        redeem.push(OP_2);
6510        redeem.push(OP_CHECKMULTISIG);
6511
6512        let hash160 = Ripemd160::digest(OptimizedSha256::new().hash(&redeem));
6513        let mut script_pubkey = vec![OP_HASH160, PUSH_20_BYTES];
6514        script_pubkey.extend_from_slice(&hash160);
6515        script_pubkey.push(OP_EQUAL);
6516
6517        let mut tx = Transaction {
6518            version: 1,
6519            inputs: vec![TransactionInput {
6520                prevout: OutPoint {
6521                    hash: [0x11; 32],
6522                    index: 0,
6523                },
6524                script_sig: Vec::new().into(),
6525                sequence: 0xffff_ffff,
6526            }]
6527            .into(),
6528            outputs: vec![TransactionOutput {
6529                value: 1_000,
6530                script_pubkey: vec![OP_1].into(),
6531            }]
6532            .into(),
6533            lock_time: 0,
6534        };
6535        let sighash = compute_legacy_sighash_nocache(&tx, 0, &redeem, 0x01);
6536        let mut sig = secp
6537            .sign_ecdsa(&Message::from_digest_slice(&sighash).expect("digest"), &sk)
6538            .serialize_der()
6539            .to_vec();
6540        sig.push(0x01);
6541
6542        let mut exact = vec![OP_0];
6543        exact.push(sig.len() as u8);
6544        exact.extend_from_slice(&sig);
6545        exact.push(redeem.len() as u8);
6546        exact.extend_from_slice(&redeem);
6547        tx.inputs[0].script_sig = exact.into();
6548
6549        // No NULLDUMMY: rejection has to come from the signature window itself.
6550        let flags = SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_DERSIG;
6551        let values = [50_000_000i64];
6552        let prevouts: [&[u8]; 1] = [&script_pubkey];
6553        let height = Some(crate::constants::BIP147_ACTIVATION_MAINNET);
6554        let verdict = |tx: &Transaction| {
6555            try_verify_p2sh_multisig_fast_path(
6556                &tx.inputs[0].script_sig,
6557                &script_pubkey,
6558                flags,
6559                tx,
6560                0,
6561                &values,
6562                &prevouts,
6563                height,
6564                Network::Mainnet,
6565                None,
6566                None,
6567            )
6568        };
6569        assert_eq!(
6570            verdict(&tx).expect("handled").expect("no script error"),
6571            true,
6572            "dummy plus one signature"
6573        );
6574
6575        let garbage = [0x22u8; 9];
6576        let mut extra = vec![OP_0];
6577        extra.push(sig.len() as u8);
6578        extra.extend_from_slice(&sig);
6579        extra.push(garbage.len() as u8);
6580        extra.extend_from_slice(&garbage);
6581        extra.push(redeem.len() as u8);
6582        extra.extend_from_slice(&redeem);
6583        tx.inputs[0].script_sig = extra.into();
6584        assert!(
6585            !matches!(verdict(&tx), Some(Ok(true))),
6586            "a non-matching push above the real signature is not in the window"
6587        );
6588    }
6589
6590    /// Regression: P2WSH-in-P2SH (wit.len()==2) must not be claimed as nested P2WPKH.
6591    /// Mainnet height 497110 tx330 failed when the dens path returned Ok(false) instead of None.
6592    #[cfg(feature = "production")]
6593    #[test]
6594    fn nested_p2wsh_not_claimed_as_p2wpkh_in_p2sh() {
6595        use crate::types::{OutPoint, Transaction, TransactionInput};
6596        // scriptSig = push(34) ‖ OP_0 ‖ PUSH_32 ‖ <32-byte program>  (nested P2WSH)
6597        let mut program = [0u8; 32];
6598        program[0] = 0x50;
6599        let mut script_sig = vec![34u8, OP_0, PUSH_32_BYTES];
6600        script_sig.extend_from_slice(&program);
6601        let script_pubkey = {
6602            let h = Ripemd160::digest(OptimizedSha256::new().hash(&script_sig[1..]));
6603            let mut spk = vec![OP_HASH160, PUSH_20_BYTES];
6604            spk.extend_from_slice(&h);
6605            spk.push(OP_EQUAL);
6606            spk
6607        };
6608        // Minimal fake witness [sig, witnessScript] — shape only; fast path must return None
6609        // before verifying (redeem is not 22-byte P2WPKH).
6610        let witness: crate::witness::Witness = vec![vec![0u8; 72], vec![OP_1, OP_1]];
6611        let tx = Transaction {
6612            version: 1,
6613            inputs: vec![TransactionInput {
6614                prevout: OutPoint {
6615                    hash: [0u8; 32],
6616                    index: 0,
6617                },
6618                sequence: 0xffff_ffff,
6619                script_sig: script_sig.clone(),
6620            }]
6621            .into(),
6622            outputs: vec![].into(),
6623            lock_time: 0,
6624        };
6625        let result = try_verify_p2wpkh_in_p2sh_fast_path(
6626            &script_sig,
6627            &script_pubkey,
6628            &witness,
6629            0x01, // P2SH
6630            &tx,
6631            0,
6632            &[0i64],
6633            &[],
6634            Some(497_110),
6635            crate::types::Network::Mainnet,
6636            None,
6637            #[cfg(all(feature = "production", feature = "blvm-secp256k1"))]
6638            None,
6639        );
6640        assert!(
6641            result.is_none(),
6642            "nested P2WSH must fall through, got {:?}",
6643            result
6644        );
6645    }
6646}
6647
6648/// Map OP_1NEGATE..OP_16 to numeric value for script_num_to_bytes
6649fn script_num_from_opcode(opcode: u8) -> i64 {
6650    match opcode {
6651        OP_1NEGATE => -1,
6652        OP_1 => 1,
6653        OP_2 => 2,
6654        OP_3 => 3,
6655        OP_4 => 4,
6656        OP_5 => 5,
6657        OP_6 => 6,
6658        OP_7 => 7,
6659        OP_8 => 8,
6660        OP_9 => 9,
6661        OP_10 => 10,
6662        OP_11 => 11,
6663        OP_12 => 12,
6664        OP_13 => 13,
6665        OP_14 => 14,
6666        OP_15 => 15,
6667        OP_16 => 16,
6668        _ => 0,
6669    }
6670}
6671
6672/// Serialize data as a Bitcoin push operation: `push_opcode` followed by `data` bytes.
6673/// This creates the byte pattern that FindAndDelete searches for.
6674/// Push data to script (BIP62 encoding rules).
6675#[inline]
6676fn redeem_push_is_canonical(script_sig: &[u8], redeem: &[u8]) -> bool {
6677    let len = redeem.len();
6678    if len < 76 {
6679        return script_sig.len() == len + 1
6680            && script_sig[0] as usize == len
6681            && &script_sig[1..] == redeem;
6682    }
6683    script_sig == serialize_push_data(redeem).as_slice()
6684}
6685
6686pub(crate) fn serialize_push_data(data: &[u8]) -> Vec<u8> {
6687    let len = data.len();
6688    let mut result = Vec::with_capacity(len + 5);
6689    if len < 76 {
6690        result.push(len as u8);
6691    } else if len < 256 {
6692        result.push(OP_PUSHDATA1);
6693        result.push(len as u8);
6694    } else if len < 65536 {
6695        result.push(OP_PUSHDATA2);
6696        result.push((len & 0xff) as u8);
6697        result.push(((len >> 8) & 0xff) as u8);
6698    } else {
6699        result.push(OP_PUSHDATA4);
6700        result.push((len & 0xff) as u8);
6701        result.push(((len >> 8) & 0xff) as u8);
6702        result.push(((len >> 16) & 0xff) as u8);
6703        result.push(((len >> 24) & 0xff) as u8);
6704    }
6705    result.extend_from_slice(data);
6706    result
6707}
6708
6709/// Push encoding for a FindAndDelete probe. Direct pushes stay on the stack.
6710fn with_push_pattern<R>(data: &[u8], f: impl FnOnce(&[u8]) -> R) -> R {
6711    let len = data.len();
6712    if len < 76 {
6713        let mut buf = [0u8; 76];
6714        buf[0] = len as u8;
6715        buf[1..1 + len].copy_from_slice(data);
6716        f(&buf[..1 + len])
6717    } else {
6718        f(serialize_push_data(data).as_slice())
6719    }
6720}
6721
6722/// Advance `pc` past one opcode (Bitcoin `CScript::GetOp`-compatible sizing).
6723#[inline]
6724fn script_get_op_advance(script: &[u8], pc: usize) -> Option<usize> {
6725    if pc >= script.len() {
6726        return None;
6727    }
6728    let opcode = script[pc];
6729    let advance = if opcode <= 0x4b {
6730        1 + opcode as usize
6731    } else if opcode == OP_PUSHDATA1 && pc + 1 < script.len() {
6732        2 + script[pc + 1] as usize
6733    } else if opcode == OP_PUSHDATA2 && pc + 2 < script.len() {
6734        3 + ((script[pc + 1] as usize) | ((script[pc + 2] as usize) << 8))
6735    } else if opcode == OP_PUSHDATA4 && pc + 4 < script.len() {
6736        5 + ((script[pc + 1] as usize)
6737            | ((script[pc + 2] as usize) << 8)
6738            | ((script[pc + 3] as usize) << 16)
6739            | ((script[pc + 4] as usize) << 24))
6740    } else {
6741        1
6742    };
6743    let next = pc + advance;
6744    if next > script.len() {
6745        None
6746    } else {
6747        Some(next)
6748    }
6749}
6750
6751/// Legacy CHECKMULTISIG scriptCode. Stays borrowed until a signature push is removed.
6752fn multisig_script_code<'a, S: AsRef<[u8]>>(
6753    script: &'a [u8],
6754    signatures: &[S],
6755) -> std::borrow::Cow<'a, [u8]> {
6756    let mut cleaned = std::borrow::Cow::Borrowed(script);
6757    for sig in signatures {
6758        let sig = sig.as_ref();
6759        if sig.is_empty() {
6760            continue;
6761        }
6762        // Push encoding is ≥ sig.len()+1; FindAndDelete is a no-op when pattern can't fit.
6763        if sig.len().saturating_add(1) > cleaned.len() {
6764            continue;
6765        }
6766        let next = with_push_pattern(sig, |pattern| {
6767            match find_and_delete(cleaned.as_ref(), pattern) {
6768                std::borrow::Cow::Owned(next) => Some(next),
6769                std::borrow::Cow::Borrowed(_) => None,
6770            }
6771        });
6772        if let Some(next) = next {
6773            cleaned = std::borrow::Cow::Owned(next);
6774        }
6775    }
6776    cleaned
6777}
6778
6779/// FindAndDelete — consensus match to the reference FindAndDelete algorithm.
6780///
6781/// Do/while loop: flush `[pc2, pc)`, delete consecutive raw occurrences of `pattern` from `pc`,
6782/// then advance `pc` one opcode via GetOp-style sizing. The inner delete loop can leave `pc`
6783/// misaligned; the next "opcode" is parsed from that byte (same behavior as the reference impl).
6784#[cfg_attr(feature = "spec-lock", spec_locked("5.1.1", "FindAndDelete"))]
6785#[inline]
6786pub(crate) fn find_and_delete<'a>(script: &'a [u8], pattern: &[u8]) -> std::borrow::Cow<'a, [u8]> {
6787    if pattern.is_empty() {
6788        return std::borrow::Cow::Borrowed(script);
6789    }
6790    if pattern.len() > script.len() {
6791        return std::borrow::Cow::Borrowed(script);
6792    }
6793    // Fast pre-scan: if pattern doesn't appear anywhere in the raw bytes, it can't appear
6794    // at any opcode boundary either — skip the full walk and the Vec allocation entirely.
6795    // `windows()` is O(N) and avoids ~100B-4KB Vec allocation + full script copy on the
6796    // overwhelmingly common case (CHECKSIG before codeseparator = never matches).
6797    if !script.windows(pattern.len()).any(|w| w == pattern) {
6798        return std::borrow::Cow::Borrowed(script);
6799    }
6800    let end = script.len();
6801    let mut n_found = 0usize;
6802    let mut result = Vec::new();
6803    let mut pc = 0usize;
6804    let mut pc2 = 0usize;
6805
6806    loop {
6807        result.extend_from_slice(&script[pc2..pc]);
6808        while end - pc >= pattern.len() && script[pc..pc + pattern.len()] == *pattern {
6809            pc += pattern.len();
6810            n_found += 1;
6811        }
6812        pc2 = pc;
6813        if pc >= end {
6814            break;
6815        }
6816        let Some(next_pc) = script_get_op_advance(script, pc) else {
6817            break;
6818        };
6819        pc = next_pc;
6820    }
6821
6822    if n_found > 0 {
6823        result.extend_from_slice(&script[pc2..end]);
6824        std::borrow::Cow::Owned(result)
6825    } else {
6826        std::borrow::Cow::Borrowed(script)
6827    }
6828}
6829
6830/// Return opcode position (0-indexed) of the opcode at byte_index in script. BIP 342 codesep_pos.
6831fn opcode_position_at_byte(script: &[u8], byte_index: usize) -> u32 {
6832    let mut pos = 0u32;
6833    let mut i = 0usize;
6834    while i < script.len() && i <= byte_index {
6835        let opcode = script[i];
6836        let advance = if opcode <= 0x4b {
6837            1 + opcode as usize
6838        } else if opcode == OP_PUSHDATA1 && i + 1 < script.len() {
6839            2 + script[i + 1] as usize
6840        } else if opcode == OP_PUSHDATA2 && i + 2 < script.len() {
6841            3 + ((script[i + 1] as usize) | ((script[i + 2] as usize) << 8))
6842        } else if opcode == OP_PUSHDATA4 && i + 4 < script.len() {
6843            5 + ((script[i + 1] as usize)
6844                | ((script[i + 2] as usize) << 8)
6845                | ((script[i + 3] as usize) << 16)
6846                | ((script[i + 4] as usize) << 24))
6847        } else {
6848            1
6849        };
6850        if i == byte_index {
6851            return pos;
6852        }
6853        pos += 1;
6854        i = std::cmp::min(i + advance, script.len());
6855    }
6856    0xffff_ffff
6857}
6858
6859/// BIP342 `EvalChecksigTapscript`.
6860/// `Ok(None)` fails the script. `Ok(Some(success))` is the signature success bit.
6861fn eval_checksig_tapscript(
6862    ctx: &context::ScriptContext<'_>,
6863    flags: u32,
6864    signature_bytes: &[u8],
6865    pubkey_bytes: &[u8],
6866) -> Result<Option<bool>> {
6867    const VALIDATION_WEIGHT_PER_SIGOP: i64 = 50;
6868    let success = !signature_bytes.is_empty();
6869    if success {
6870        if let Some(weight) = ctx.tapscript_validation_weight {
6871            let left = weight.get() - VALIDATION_WEIGHT_PER_SIGOP;
6872            weight.set(left);
6873            if left < 0 {
6874                return Ok(None);
6875            }
6876        }
6877    }
6878    if pubkey_bytes.is_empty() {
6879        return Ok(None);
6880    }
6881    if pubkey_bytes.len() == 32 {
6882        if !success {
6883            return Ok(Some(false));
6884        }
6885        let Some((sig_bytes, sighash_byte)) = parse_taproot_keypath_sig(signature_bytes) else {
6886            return Ok(None);
6887        };
6888        let (tapscript, codesep_pos) = ctx
6889            .tapscript_for_sighash
6890            .map(|s| (s, ctx.tapscript_codesep_pos.unwrap_or(0xffff_ffff)))
6891            .unwrap_or((&[] as &[u8], 0xffff_ffff));
6892        let sighash = if tapscript.is_empty() {
6893            crate::taproot::compute_taproot_signature_hash(
6894                ctx.tx,
6895                ctx.input_index,
6896                ctx.prevout_values,
6897                ctx.prevout_script_pubkeys,
6898                sighash_byte,
6899                None,
6900            )?
6901        } else {
6902            crate::taproot::compute_tapscript_signature_hash(
6903                ctx.tx,
6904                ctx.input_index,
6905                ctx.prevout_values,
6906                ctx.prevout_script_pubkeys,
6907                tapscript,
6908                crate::taproot::TAPROOT_LEAF_VERSION_TAPSCRIPT,
6909                codesep_pos,
6910                sighash_byte,
6911                ctx.taproot_annex_hash,
6912            )?
6913        };
6914        #[cfg(feature = "production")]
6915        let is_valid = {
6916            use crate::bip348::verify_tapscript_schnorr_signature;
6917            verify_tapscript_schnorr_signature(
6918                &sighash,
6919                pubkey_bytes,
6920                &sig_bytes,
6921                ctx.schnorr_collector,
6922            )
6923            .unwrap_or(false)
6924        };
6925        #[cfg(not(feature = "production"))]
6926        let is_valid = {
6927            let mut pk = [0u8; 32];
6928            pk.copy_from_slice(pubkey_bytes);
6929            crate::secp256k1_backend::verify_schnorr(&sig_bytes, &sighash, &pk).unwrap_or(false)
6930        };
6931        if !is_valid {
6932            return Ok(None);
6933        }
6934        return Ok(Some(true));
6935    }
6936    if flags & crate::script::flags::SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE != 0 {
6937        return Ok(None);
6938    }
6939    Ok(Some(success))
6940}
6941
6942/// ECDSA CHECKSIG shared by `OP_CHECKSIG` and `OP_CHECKSIGVERIFY`.
6943///
6944/// Returns whether the signature is valid. The caller applies the empty-signature
6945/// policy, NULLFAIL, and the stack result. `redeem_script_for_sighash` is the
6946/// effective script code for this opcode, which can differ from `ctx`.
6947#[cfg_attr(feature = "production", inline(always))]
6948fn eval_checksig_ecdsa(
6949    ctx: &context::ScriptContext<'_>,
6950    flags: u32,
6951    signature_bytes: &[u8],
6952    pubkey_bytes: &[u8],
6953    redeem_script_for_sighash: Option<&[u8]>,
6954) -> Result<bool> {
6955    let tx = ctx.tx;
6956    let input_index = ctx.input_index;
6957    let prevout_values = ctx.prevout_values;
6958    let prevout_script_pubkeys = ctx.prevout_script_pubkeys;
6959    let block_height = ctx.block_height;
6960    let network = ctx.network;
6961    let sigversion = ctx.sigversion;
6962    let precomputed_bip143 = ctx.precomputed_bip143;
6963    #[cfg(feature = "production")]
6964    let sighash_cache = ctx.sighash_cache;
6965
6966    let sig_len = signature_bytes.len();
6967    let sighash_byte = signature_bytes[sig_len - 1];
6968    let _der_sig = &signature_bytes[..sig_len - 1];
6969
6970    let sighash = if sigversion == SigVersion::WitnessV0 {
6971        let amount = prevout_values.get(input_index).copied().unwrap_or(0);
6972        let script_code = redeem_script_for_sighash.unwrap_or_else(|| {
6973            prevout_script_pubkeys
6974                .get(input_index)
6975                .copied()
6976                .unwrap_or(&[])
6977        });
6978        crate::transaction_hash::calculate_bip143_sighash(
6979            tx,
6980            input_index,
6981            script_code,
6982            amount,
6983            sighash_byte,
6984            precomputed_bip143,
6985        )?
6986    } else {
6987        use crate::transaction_hash::{SighashType, calculate_transaction_sighash_single_input};
6988        let sighash_type = SighashType::from_byte(sighash_byte);
6989        let sig = signature_bytes;
6990        let base_script = match (
6991            redeem_script_for_sighash,
6992            prevout_script_pubkeys.get(input_index),
6993        ) {
6994            (Some(redeem), Some(prevout)) if redeem == *prevout => *prevout,
6995            (Some(redeem), _) => redeem,
6996            (None, Some(prevout)) => *prevout,
6997            (None, None) => &[],
6998        };
6999        let cleaned = if sig.len().saturating_add(1) > base_script.len() {
7000            std::borrow::Cow::Borrowed(base_script)
7001        } else {
7002            with_push_pattern(sig, |pattern| find_and_delete(base_script, pattern))
7003        };
7004        // Standardness only. Block connect does not set this flag. A witness
7005        // script may still contain the signature; this branch is non-segwit.
7006        if sigversion == SigVersion::Base
7007            && flags & crate::script::flags::SCRIPT_VERIFY_CONST_SCRIPTCODE != 0
7008            && matches!(cleaned, std::borrow::Cow::Owned(_))
7009        {
7010            return Err(ConsensusError::ScriptErrorWithCode {
7011                code: ScriptErrorCode::SigFindAndDelete,
7012                message: "signature is in the non-segwit script code".into(),
7013            });
7014        }
7015        calculate_transaction_sighash_single_input(
7016            tx,
7017            input_index,
7018            cleaned.as_ref(),
7019            prevout_values[input_index],
7020            sighash_type,
7021            #[cfg(feature = "production")]
7022            sighash_cache,
7023        )?
7024    };
7025
7026    let height = block_height.unwrap_or(0);
7027    #[cfg(feature = "production")]
7028    let is_valid = signature::with_secp_context(|secp| {
7029        signature::verify_signature(
7030            secp,
7031            pubkey_bytes,
7032            signature_bytes,
7033            &sighash,
7034            flags,
7035            height,
7036            network,
7037            sigversion,
7038        )
7039    })?;
7040    #[cfg(not(feature = "production"))]
7041    let is_valid = {
7042        let secp = signature::new_secp();
7043        signature::verify_signature(
7044            &secp,
7045            pubkey_bytes,
7046            signature_bytes,
7047            &sighash,
7048            flags,
7049            height,
7050            network,
7051            sigversion,
7052        )?
7053    };
7054    Ok(is_valid)
7055}
7056
7057/// Execute a single opcode with full context including block height, median time-past, and network
7058#[cfg_attr(feature = "production", inline(always))]
7059fn execute_opcode_with_context_full(
7060    opcode: u8,
7061    stack: &mut Vec<StackElement>,
7062    flags: u32,
7063    ctx: &context::ScriptContext<'_>,
7064    effective_script_code: Option<&[u8]>,
7065) -> Result<bool> {
7066    let tx = ctx.tx;
7067    let input_index = ctx.input_index;
7068    let prevout_values = ctx.prevout_values;
7069    let prevout_script_pubkeys = ctx.prevout_script_pubkeys;
7070    let block_height = ctx.block_height;
7071    let median_time_past = ctx.median_time_past;
7072    let network = ctx.network;
7073    let sigversion = ctx.sigversion;
7074    let script_sig_for_sighash = ctx.script_sig_for_sighash;
7075    let tapscript_for_sighash = ctx.tapscript_for_sighash;
7076    let tapscript_codesep_pos = ctx.tapscript_codesep_pos;
7077    let redeem_script_for_sighash = effective_script_code;
7078    #[cfg(feature = "production")]
7079    let schnorr_collector = ctx.schnorr_collector;
7080    let precomputed_bip143 = ctx.precomputed_bip143;
7081    #[cfg(feature = "production")]
7082    let sighash_cache = ctx.sighash_cache;
7083
7084    // match ordered by frequency (hot opcodes first for better branch prediction)
7085    match opcode {
7086        // OP_CHECKSIG - verify ECDSA signature
7087        OP_CHECKSIG => {
7088            if stack.len() >= 2 {
7089                let pubkey_bytes = stack.pop().unwrap();
7090                let signature_bytes = stack.pop().unwrap();
7091
7092                if sigversion == SigVersion::Tapscript {
7093                    return match eval_checksig_tapscript(
7094                        ctx,
7095                        flags,
7096                        &signature_bytes,
7097                        &pubkey_bytes,
7098                    )? {
7099                        None => Ok(false),
7100                        Some(ok) => {
7101                            stack.push(to_stack_element(script_bool_bytes(ok)));
7102                            Ok(true)
7103                        }
7104                    };
7105                }
7106
7107                // Empty signature always fails but is valid script execution.
7108                // The public key is still checked: a hybrid key, and an uncompressed
7109                // witness v0 key, fail the script before the false result can be dropped.
7110                if signature_bytes.is_empty() {
7111                    let compressed_or_uncompressed = matches!(
7112                        (pubkey_bytes.first(), pubkey_bytes.len()),
7113                        (Some(0x04), 65) | (Some(0x02 | 0x03), 33)
7114                    );
7115                    if flags & flags::SCRIPT_VERIFY_STRICTENC != 0 && !compressed_or_uncompressed {
7116                        return Err(ConsensusError::ScriptErrorWithCode {
7117                            code: ScriptErrorCode::PubkeyType,
7118                            message: "public key must be compressed or uncompressed".into(),
7119                        });
7120                    }
7121                    if sigversion == SigVersion::WitnessV0
7122                        && flags & flags::SCRIPT_VERIFY_WITNESS_PUBKEYTYPE != 0
7123                        && !(pubkey_bytes.len() == 33
7124                            && (pubkey_bytes[0] == 0x02 || pubkey_bytes[0] == 0x03))
7125                    {
7126                        return Err(ConsensusError::ScriptErrorWithCode {
7127                            code: ScriptErrorCode::WitnessPubkeyType,
7128                            message: "witness v0 pubkey must be compressed".into(),
7129                        });
7130                    }
7131                    stack.push(to_stack_element(&[]));
7132                    return Ok(true);
7133                }
7134
7135                let is_valid = eval_checksig_ecdsa(
7136                    ctx,
7137                    flags,
7138                    &signature_bytes,
7139                    &pubkey_bytes,
7140                    redeem_script_for_sighash,
7141                )?;
7142
7143                if !is_valid && flags & flags::SCRIPT_VERIFY_NULLFAIL != 0 {
7144                    return Err(ConsensusError::ScriptErrorWithCode {
7145                        code: ScriptErrorCode::SigNullFail,
7146                        message: "OP_CHECKSIG: non-null signature must not fail under NULLFAIL"
7147                            .into(),
7148                    });
7149                }
7150                stack.push(to_stack_element(script_bool_bytes(is_valid)));
7151                Ok(true)
7152            } else {
7153                Ok(false)
7154            }
7155        }
7156
7157        // OP_CHECKSIGVERIFY - verify signature (Schnorr in Tapscript, ECDSA otherwise)
7158        OP_CHECKSIGVERIFY => {
7159            if stack.len() >= 2 {
7160                let pubkey_bytes = stack.pop().unwrap();
7161                let signature_bytes = stack.pop().unwrap();
7162
7163                if sigversion == SigVersion::Tapscript {
7164                    return match eval_checksig_tapscript(
7165                        ctx,
7166                        flags,
7167                        &signature_bytes,
7168                        &pubkey_bytes,
7169                    )? {
7170                        Some(true) => Ok(true),
7171                        _ => Ok(false),
7172                    };
7173                }
7174
7175                // Empty signature always fails
7176                if signature_bytes.is_empty() {
7177                    return Ok(false);
7178                }
7179
7180                let is_valid = eval_checksig_ecdsa(
7181                    ctx,
7182                    flags,
7183                    &signature_bytes,
7184                    &pubkey_bytes,
7185                    redeem_script_for_sighash,
7186                )?;
7187                Ok(is_valid)
7188            } else {
7189                Ok(false)
7190            }
7191        }
7192
7193        // OP_CHECKSIGADD (BIP 342) - Tapscript only. Pops pubkey, n, sig. Verifies Schnorr; if valid push n+1 else fail.
7194        OP_CHECKSIGADD => {
7195            if sigversion != SigVersion::Tapscript {
7196                return Err(ConsensusError::ScriptErrorWithCode {
7197                    code: ScriptErrorCode::DisabledOpcode,
7198                    message: "OP_CHECKSIGADD is only available in Tapscript".into(),
7199                });
7200            }
7201            if stack.len() < 3 {
7202                return Err(ConsensusError::ScriptErrorWithCode {
7203                    code: ScriptErrorCode::InvalidStackOperation,
7204                    message: "OP_CHECKSIGADD: insufficient stack items (need 3)".into(),
7205                });
7206            }
7207            // BIP 342: pubkey (top), n (second), sig (third)
7208            let pubkey_bytes = stack.pop().unwrap();
7209            let n_bytes = stack.pop().unwrap();
7210            let signature_bytes = stack.pop().unwrap();
7211            if (flags & crate::script::flags::SCRIPT_VERIFY_MINIMALDATA) != 0
7212                && !script_num_is_minimally_encoded(&n_bytes)
7213            {
7214                return Err(ConsensusError::ScriptErrorWithCode {
7215                    code: ScriptErrorCode::MinimalData,
7216                    message: "OP_CHECKSIGADD: non-minimal script number".into(),
7217                });
7218            }
7219            let n = script_num_decode(&n_bytes, 4)?;
7220
7221            match eval_checksig_tapscript(ctx, flags, &signature_bytes, &pubkey_bytes)? {
7222                None => return Ok(false),
7223                Some(ok) => {
7224                    let pushed = if ok { n + 1 } else { n };
7225                    stack.push(to_stack_element(&script_num_encode(pushed)));
7226                    return Ok(true);
7227                }
7228            }
7229        }
7230
7231        // OP_CHECKMULTISIG - verify m-of-n multisig (hot path)
7232        OP_CHECKMULTISIG => {
7233            if sigversion == SigVersion::Tapscript {
7234                return Ok(false);
7235            }
7236            // OP_CHECKMULTISIG implementation
7237            // Stack layout: [dummy] [sig1] ... [sigm] [m] [pubkey1] ... [pubkeyn] [n]
7238            if stack.len() < 2 {
7239                return Ok(false);
7240            }
7241
7242            // Pop n (number of public keys) — must be decoded as CScriptNum (BIP62)
7243            let n_bytes = stack.pop().unwrap();
7244            if (flags & crate::script::flags::SCRIPT_VERIFY_MINIMALDATA) != 0
7245                && !script_num_is_minimally_encoded(&n_bytes)
7246            {
7247                return Err(ConsensusError::ScriptErrorWithCode {
7248                    code: ScriptErrorCode::MinimalData,
7249                    message: "OP_CHECKMULTISIG: non-minimal key count".into(),
7250                });
7251            }
7252            let n_raw = script_num_decode(&n_bytes, 4).map_err(|_| {
7253                ConsensusError::ScriptErrorWithCode {
7254                    code: ScriptErrorCode::InvalidStackOperation,
7255                    message: "OP_CHECKMULTISIG: invalid n encoding".into(),
7256                }
7257            })?;
7258            if !(0..=20).contains(&n_raw) {
7259                return Ok(false);
7260            }
7261            let n = n_raw as usize;
7262            if stack.len() < n + 1 {
7263                return Ok(false);
7264            }
7265
7266            // Pop n public keys
7267            let mut pubkeys = Vec::with_capacity(n);
7268            for _ in 0..n {
7269                pubkeys.push(stack.pop().unwrap());
7270            }
7271
7272            // Pop m (number of required signatures) — must be decoded as CScriptNum (BIP62)
7273            let m_bytes = stack.pop().unwrap();
7274            if (flags & crate::script::flags::SCRIPT_VERIFY_MINIMALDATA) != 0
7275                && !script_num_is_minimally_encoded(&m_bytes)
7276            {
7277                return Err(ConsensusError::ScriptErrorWithCode {
7278                    code: ScriptErrorCode::MinimalData,
7279                    message: "OP_CHECKMULTISIG: non-minimal script number".into(),
7280                });
7281            }
7282            let m_raw = script_num_decode(&m_bytes, 4).map_err(|_| {
7283                ConsensusError::ScriptErrorWithCode {
7284                    code: ScriptErrorCode::InvalidStackOperation,
7285                    message: "OP_CHECKMULTISIG: invalid m encoding".into(),
7286                }
7287            })?;
7288            if m_raw < 0 || m_raw as usize > n || m_raw > 20 {
7289                return Ok(false);
7290            }
7291            let m = m_raw as usize;
7292            if stack.len() < m + 1 {
7293                return Ok(false);
7294            }
7295
7296            // Pop m signatures
7297            let mut signatures = Vec::with_capacity(m);
7298            for _ in 0..m {
7299                signatures.push(stack.pop().unwrap());
7300            }
7301
7302            // The extra element must be zero-length when NULLDUMMY is set.
7303            // OP_0 pushes that. A one-byte 0x00 has length 1 and is rejected.
7304            // The flag is the whole rule.
7305            let dummy = stack.pop().unwrap();
7306            if (flags & flags::SCRIPT_VERIFY_NULLDUMMY) != 0 && !dummy.is_empty() {
7307                return Err(ConsensusError::ScriptErrorWithCode {
7308                    code: ScriptErrorCode::SigNullDummy,
7309                    message: format!(
7310                        "OP_CHECKMULTISIG: dummy element {dummy:?} violates BIP147 NULLDUMMY (must be zero-length)"
7311                    )
7312                    .into(),
7313                });
7314            }
7315
7316            // Verify signatures against public keys
7317            // CHECKMULTISIG algorithm: iterate pubkeys, try to match sigs in order
7318            let height = block_height.unwrap_or(0);
7319            if flags & 0x02 != 0 {
7320                for pubkey in &pubkeys {
7321                    crate::script::signature::reject_strictenc_pubkey(flags, pubkey)?;
7322                }
7323                for sig_bytes in &signatures {
7324                    crate::script::signature::reject_strictenc_signature(flags, sig_bytes)?;
7325                }
7326            }
7327
7328            // FindAndDelete: Remove ALL signatures from scriptCode BEFORE any sighash computation
7329            // Consensus rule for OP_CHECKMULTISIG (legacy only, not SegWit)
7330            let cleaned_script_for_multisig: std::borrow::Cow<[u8]> =
7331                if sigversion == SigVersion::Base {
7332                    let base_script = match (
7333                        redeem_script_for_sighash,
7334                        prevout_script_pubkeys.get(input_index),
7335                    ) {
7336                        (Some(redeem), Some(prevout)) if redeem == *prevout => *prevout,
7337                        (Some(redeem), _) => redeem,
7338                        (None, Some(prevout)) => *prevout,
7339                        (None, None) => &[],
7340                    };
7341                    multisig_script_code(base_script, &signatures)
7342                } else {
7343                    // For SegWit, no FindAndDelete needed
7344                    std::borrow::Cow::Borrowed(redeem_script_for_sighash.unwrap_or_else(|| {
7345                        prevout_script_pubkeys
7346                            .get(input_index)
7347                            .copied()
7348                            .unwrap_or(&[])
7349                    }))
7350                };
7351            // Standardness only. Block connect does not set this flag. A witness
7352            // script may still contain the signature; Owned means a non-segwit removal.
7353            if sigversion == SigVersion::Base
7354                && flags & crate::script::flags::SCRIPT_VERIFY_CONST_SCRIPTCODE != 0
7355                && matches!(cleaned_script_for_multisig, std::borrow::Cow::Owned(_))
7356            {
7357                return Err(ConsensusError::ScriptErrorWithCode {
7358                    code: ScriptErrorCode::SigFindAndDelete,
7359                    message: "signature is in the non-segwit script code".into(),
7360                });
7361            }
7362
7363            use crate::transaction_hash::{
7364                SighashType, calculate_transaction_sighash_single_input,
7365            };
7366
7367            // Batch path: when n*m >= 4, precompute sighashes once per sig and batch-verify all (pubkey, sig) pairs.
7368            #[cfg(feature = "production")]
7369            let use_batch = pubkeys.len() * signatures.len() >= 4;
7370
7371            #[cfg(feature = "production")]
7372            let (valid_sigs, _) = if use_batch {
7373                // Phase 3: Batch sighash for multisig — use batch_compute_legacy_sighashes when Base
7374                let sighashes: Vec<[u8; 32]> = if sigversion == SigVersion::Base {
7375                    let non_empty: Vec<_> = signatures.iter().filter(|s| !s.is_empty()).collect();
7376                    if non_empty.is_empty() {
7377                        vec![]
7378                    } else {
7379                        let specs: Vec<(usize, u8, &[u8])> = non_empty
7380                            .iter()
7381                            .map(|s| {
7382                                (
7383                                    input_index,
7384                                    s.as_ref()[s.as_ref().len() - 1],
7385                                    cleaned_script_for_multisig.as_ref(),
7386                                )
7387                            })
7388                            .collect();
7389                        crate::transaction_hash::batch_compute_legacy_sighashes(
7390                            tx,
7391                            prevout_values,
7392                            prevout_script_pubkeys,
7393                            &specs,
7394                        )?
7395                    }
7396                } else {
7397                    // WitnessV0 (P2WSH): use BIP143 sighash, not legacy.
7398                    let amount = prevout_values.get(input_index).copied().unwrap_or(0);
7399                    signatures
7400                        .iter()
7401                        .filter(|s| !s.is_empty())
7402                        .map(|sig_bytes| {
7403                            let sighash_byte = sig_bytes[sig_bytes.len() - 1];
7404                            crate::transaction_hash::calculate_bip143_sighash(
7405                                tx,
7406                                input_index,
7407                                &cleaned_script_for_multisig,
7408                                amount,
7409                                sighash_byte,
7410                                precomputed_bip143,
7411                            )
7412                        })
7413                        .collect::<Result<Vec<_>>>()?
7414                };
7415
7416                // Build verification tasks: (pubkey_i, sig_j, sighash_j) for all i,j. Order: j then i (sig_index, pubkey_index)
7417                let mut tasks: Vec<(&[u8], &[u8], [u8; 32])> =
7418                    Vec::with_capacity(pubkeys.len() * signatures.len());
7419                let mut sig_idx_to_sighash_idx = Vec::with_capacity(signatures.len());
7420                let mut sighash_idx = 0usize;
7421                for (j, sig_bytes) in signatures.iter().enumerate() {
7422                    if sig_bytes.is_empty() {
7423                        sig_idx_to_sighash_idx.push(usize::MAX);
7424                    } else {
7425                        sig_idx_to_sighash_idx.push(sighash_idx);
7426                        let sh = sighashes[sighash_idx];
7427                        sighash_idx += 1;
7428                        for pubkey_bytes in &pubkeys {
7429                            tasks.push((pubkey_bytes.as_ref(), sig_bytes.as_ref(), sh));
7430                        }
7431                    }
7432                }
7433
7434                let results = if tasks.is_empty() {
7435                    vec![]
7436                } else {
7437                    batch_verify_signatures(&tasks, flags, height, network, sigversion)?
7438                };
7439
7440                // Matching: for each pubkey in order, if current sig verifies with this pubkey, advance
7441                let mut sig_index = 0;
7442                let mut valid_sigs = 0usize;
7443                for (i, pubkey_bytes) in pubkeys.iter().enumerate() {
7444                    if sig_index >= signatures.len() {
7445                        break;
7446                    }
7447                    if sigversion == SigVersion::WitnessV0
7448                        && flags & crate::script::flags::SCRIPT_VERIFY_WITNESS_PUBKEYTYPE != 0
7449                        && !(pubkey_bytes.len() == 33
7450                            && (pubkey_bytes[0] == 0x02 || pubkey_bytes[0] == 0x03))
7451                    {
7452                        return Err(ConsensusError::ScriptErrorWithCode {
7453                            code: ScriptErrorCode::WitnessPubkeyType,
7454                            message: "witness v0 public key must be compressed".into(),
7455                        });
7456                    }
7457                    // Skip empty sigs without advancing (same as original)
7458                    while sig_index < signatures.len() && signatures[sig_index].is_empty() {
7459                        sig_index += 1;
7460                    }
7461                    if sig_index >= signatures.len() {
7462                        break;
7463                    }
7464                    let sh_idx = sig_idx_to_sighash_idx[sig_index];
7465                    if sh_idx == usize::MAX {
7466                        continue;
7467                    }
7468                    let task_idx = sh_idx * pubkeys.len() + i;
7469                    if task_idx < results.len() && results[task_idx] {
7470                        valid_sigs += 1;
7471                        sig_index += 1;
7472                    }
7473                }
7474
7475                (valid_sigs, ())
7476            } else {
7477                let mut sig_index = 0;
7478                let mut valid_sigs = 0;
7479                let amount = prevout_values.get(input_index).copied().unwrap_or(0);
7480
7481                for pubkey_bytes in &pubkeys {
7482                    if sig_index >= signatures.len() {
7483                        break;
7484                    }
7485
7486                    let signature_bytes = &signatures[sig_index];
7487
7488                    if signature_bytes.is_empty() {
7489                        continue;
7490                    }
7491                    // Encoding failure aborts the script. It is not "try the next key".
7492                    if sigversion == SigVersion::WitnessV0
7493                        && flags & crate::script::flags::SCRIPT_VERIFY_WITNESS_PUBKEYTYPE != 0
7494                        && !(pubkey_bytes.len() == 33
7495                            && (pubkey_bytes[0] == 0x02 || pubkey_bytes[0] == 0x03))
7496                    {
7497                        return Err(ConsensusError::ScriptErrorWithCode {
7498                            code: ScriptErrorCode::WitnessPubkeyType,
7499                            message: "witness v0 public key must be compressed".into(),
7500                        });
7501                    }
7502
7503                    let sig_len = signature_bytes.len();
7504                    let sighash_byte = signature_bytes[sig_len - 1];
7505
7506                    // WitnessV0 (P2WSH): BIP143 sighash; Base (P2SH legacy): legacy sighash.
7507                    let sighash = if sigversion == SigVersion::WitnessV0 {
7508                        crate::transaction_hash::calculate_bip143_sighash(
7509                            tx,
7510                            input_index,
7511                            &cleaned_script_for_multisig,
7512                            amount,
7513                            sighash_byte,
7514                            #[cfg(feature = "production")]
7515                            precomputed_bip143,
7516                            #[cfg(not(feature = "production"))]
7517                            None,
7518                        )?
7519                    } else {
7520                        let sighash_type = SighashType::from_byte(sighash_byte);
7521                        calculate_transaction_sighash_single_input(
7522                            tx,
7523                            input_index,
7524                            &cleaned_script_for_multisig,
7525                            prevout_values[input_index],
7526                            sighash_type,
7527                            #[cfg(feature = "production")]
7528                            sighash_cache,
7529                        )?
7530                    };
7531
7532                    #[cfg(feature = "production")]
7533                    let is_valid = signature::with_secp_context(|secp| {
7534                        signature::verify_signature(
7535                            secp,
7536                            pubkey_bytes,
7537                            signature_bytes,
7538                            &sighash,
7539                            flags,
7540                            height,
7541                            network,
7542                            sigversion,
7543                        )
7544                    })?;
7545
7546                    #[cfg(not(feature = "production"))]
7547                    let is_valid = {
7548                        let secp = signature::new_secp();
7549                        signature::verify_signature(
7550                            &secp,
7551                            pubkey_bytes,
7552                            signature_bytes,
7553                            &sighash,
7554                            flags,
7555                            height,
7556                            network,
7557                            sigversion,
7558                        )?
7559                    };
7560
7561                    if is_valid {
7562                        valid_sigs += 1;
7563                        sig_index += 1;
7564                    }
7565                }
7566                (valid_sigs, ())
7567            };
7568
7569            #[cfg(not(feature = "production"))]
7570            let (valid_sigs, _) = {
7571                let mut sig_index = 0;
7572                let mut valid_sigs = 0;
7573                let amount = prevout_values.get(input_index).copied().unwrap_or(0);
7574
7575                for pubkey_bytes in &pubkeys {
7576                    if sig_index >= signatures.len() {
7577                        break;
7578                    }
7579                    let signature_bytes = &signatures[sig_index];
7580                    if signature_bytes.is_empty() {
7581                        continue;
7582                    }
7583                    let sig_len = signature_bytes.len();
7584                    let sighash_byte = signature_bytes[sig_len - 1];
7585
7586                    // WitnessV0 (P2WSH): BIP143 sighash; Base: legacy sighash.
7587                    let sighash = if sigversion == SigVersion::WitnessV0 {
7588                        crate::transaction_hash::calculate_bip143_sighash(
7589                            tx,
7590                            input_index,
7591                            &cleaned_script_for_multisig,
7592                            amount,
7593                            sighash_byte,
7594                            None,
7595                        )?
7596                    } else {
7597                        let sighash_type = SighashType::from_byte(sighash_byte);
7598                        calculate_transaction_sighash_single_input(
7599                            tx,
7600                            input_index,
7601                            &cleaned_script_for_multisig,
7602                            prevout_values[input_index],
7603                            sighash_type,
7604                        )?
7605                    };
7606                    let secp = signature::new_secp();
7607                    let is_valid = signature::verify_signature(
7608                        &secp,
7609                        pubkey_bytes,
7610                        signature_bytes,
7611                        &sighash,
7612                        flags,
7613                        height,
7614                        network,
7615                        sigversion,
7616                    )?;
7617                    if is_valid {
7618                        valid_sigs += 1;
7619                        sig_index += 1;
7620                    }
7621                }
7622                (valid_sigs, ())
7623            };
7624
7625            let success = valid_sigs >= m;
7626            failed_multisig_nullfail(flags, success, signatures.iter().any(|sig| !sig.is_empty()))?;
7627            stack.push(to_stack_element(script_bool_bytes(success)));
7628            Ok(true)
7629        }
7630
7631        // OP_CHECKMULTISIGVERIFY - CHECKMULTISIG + VERIFY (hot path)
7632        OP_CHECKMULTISIGVERIFY => {
7633            // Execute CHECKMULTISIG first
7634            let ctx_checkmultisig = context::ScriptContext {
7635                tx,
7636                input_index,
7637                prevout_values,
7638                prevout_script_pubkeys,
7639                block_height,
7640                median_time_past,
7641                network,
7642                sigversion,
7643                redeem_script_for_sighash,
7644                script_sig_for_sighash,
7645                tapscript_for_sighash,
7646                tapscript_codesep_pos,
7647                taproot_annex_hash: None,
7648                tapscript_validation_weight: ctx.tapscript_validation_weight,
7649                #[cfg(feature = "production")]
7650                schnorr_collector: None,
7651                precomputed_bip143,
7652                #[cfg(feature = "production")]
7653                sighash_cache,
7654            };
7655            let result = execute_opcode_with_context_full(
7656                OP_CHECKMULTISIG,
7657                stack,
7658                flags,
7659                &ctx_checkmultisig,
7660                redeem_script_for_sighash,
7661            )?;
7662            if !result {
7663                return Ok(false);
7664            }
7665            // VERIFY: check top of stack is truthy, then pop it
7666            if let Some(top) = stack.pop() {
7667                if !cast_to_bool(&top) {
7668                    return Ok(false);
7669                }
7670                Ok(true)
7671            } else {
7672                Ok(false)
7673            }
7674        }
7675
7676        // OP_CHECKLOCKTIMEVERIFY (BIP65)
7677        // Validates that transaction locktime is >= top stack item
7678        // If SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY flag is not set, treat as NOP2
7679        // CLTV does NOT pop the stack — it only reads the top element (NOP-type opcode)
7680        OP_CHECKLOCKTIMEVERIFY => {
7681            // If CLTV flag is not enabled, behave as NOP (treat as NOP2)
7682            if (flags & flags::SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY) == 0 {
7683                return Ok(true);
7684            }
7685
7686            use crate::locktime::{check_bip65, decode_locktime_value};
7687
7688            if stack.is_empty() {
7689                return Err(ConsensusError::ScriptErrorWithCode {
7690                    code: ScriptErrorCode::InvalidStackOperation,
7691                    message: "OP_CHECKLOCKTIMEVERIFY: empty stack".into(),
7692                });
7693            }
7694
7695            // Script number, at most 5 bytes. The sign bit is on the last byte.
7696            let locktime_bytes = stack.last().expect("Stack is not empty");
7697            if (flags & crate::script::flags::SCRIPT_VERIFY_MINIMALDATA) != 0
7698                && !script_num_is_minimally_encoded(locktime_bytes.as_ref())
7699            {
7700                return Err(ConsensusError::ScriptErrorWithCode {
7701                    code: ScriptErrorCode::MinimalData,
7702                    message: "OP_CHECKLOCKTIMEVERIFY: non-minimal script number".into(),
7703                });
7704            }
7705            let locktime_number = match decode_locktime_value(locktime_bytes.as_ref()) {
7706                Some(v) => v,
7707                None => {
7708                    return Err(ConsensusError::ScriptErrorWithCode {
7709                        code: ScriptErrorCode::MinimalData,
7710                        message: "OP_CHECKLOCKTIMEVERIFY: invalid locktime encoding".into(),
7711                    });
7712                }
7713            };
7714            if locktime_number < 0 {
7715                return Err(ConsensusError::ScriptErrorWithCode {
7716                    code: ScriptErrorCode::NegativeLocktime,
7717                    message: "OP_CHECKLOCKTIMEVERIFY: negative locktime".into(),
7718                });
7719            }
7720            // A value above u32::MAX cannot be <= the transaction locktime.
7721            if locktime_number > u32::MAX as i64 {
7722                return Ok(false);
7723            }
7724            let locktime_value = locktime_number as u32;
7725
7726            let tx_locktime = tx.lock_time as u32;
7727
7728            // CheckLockTime order (BIP65): locktime check via check_bip65
7729            if !check_bip65(tx_locktime, locktime_value) {
7730                return Ok(false);
7731            }
7732
7733            // Input sequence must NOT be SEQUENCE_FINAL (0xffffffff)
7734            let input_seq = if input_index < tx.inputs.len() {
7735                tx.inputs[input_index].sequence
7736            } else {
7737                0xffffffff
7738            };
7739            if input_seq == 0xffffffff {
7740                return Ok(false);
7741            }
7742
7743            // CLTV does NOT pop the stack (NOP-type opcode)
7744            Ok(true)
7745        }
7746
7747        // OP_CHECKSEQUENCEVERIFY (BIP112)
7748        // Validates that transaction input sequence number meets relative locktime requirement.
7749        // Implements BIP68: Relative Lock-Time Using Consensus-Enforced Sequence Numbers.
7750        //
7751        // Behavior must match consensus (BIP65/112):
7752        // - If SCRIPT_VERIFY_CHECKSEQUENCEVERIFY flag is not set, behaves as a NOP (no-op)
7753        // - If the *stack* sequence has the disable flag set (0x80000000), behaves as a NOP
7754        // - Otherwise the transaction version must be at least 2
7755        // - If the *input* nSequence disable flag is set (and stack is not disabled), fail
7756        // - Does NOT remove the top stack item on success (non-consuming)
7757        OP_CHECKSEQUENCEVERIFY => {
7758            use crate::locktime::{check_bip112, decode_locktime_value, is_sequence_disabled};
7759
7760            // If CSV flag is not enabled, behave as NOP (treat as NOP3)
7761            if (flags & flags::SCRIPT_VERIFY_CHECKSEQUENCEVERIFY) == 0 {
7762                return Ok(true);
7763            }
7764
7765            if stack.is_empty() {
7766                return Ok(false);
7767            }
7768
7769            // Decode sequence value from stack using shared locktime logic.
7770            // Interpret the top stack element as a sequence value (BIP112).
7771            let sequence_bytes = stack.last().expect("Stack is not empty");
7772            if (flags & crate::script::flags::SCRIPT_VERIFY_MINIMALDATA) != 0
7773                && !script_num_is_minimally_encoded(sequence_bytes.as_ref())
7774            {
7775                return Err(ConsensusError::ScriptErrorWithCode {
7776                    code: ScriptErrorCode::MinimalData,
7777                    message: "OP_CHECKSEQUENCEVERIFY: non-minimal script number".into(),
7778                });
7779            }
7780            let sequence_number = match decode_locktime_value(sequence_bytes.as_ref()) {
7781                Some(v) => v,
7782                None => return Ok(false), // Invalid encoding
7783            };
7784            if sequence_number < 0 {
7785                return Err(ConsensusError::ScriptErrorWithCode {
7786                    code: ScriptErrorCode::NegativeLocktime,
7787                    message: "OP_CHECKSEQUENCEVERIFY: negative locktime".into(),
7788                });
7789            }
7790            // Disable flag and BIP68 masks use the low 32 bits of a non-negative number.
7791            let sequence_value = sequence_number as u32;
7792
7793            // Get input sequence number
7794            if input_index >= tx.inputs.len() {
7795                return Ok(false);
7796            }
7797            let input_sequence = tx.inputs[input_index].sequence as u32;
7798
7799            // Stack disable flag is a NOP on every version. A real relative lock
7800            // requires transaction version 2 or greater.
7801            if is_sequence_disabled(sequence_value) {
7802                return Ok(true);
7803            }
7804            if !crate::sequence_locks::version_enforces_relative_lock(tx.version) {
7805                return Ok(false);
7806            }
7807            if is_sequence_disabled(input_sequence) {
7808                return Ok(false);
7809            }
7810
7811            // Type bit matches and the input's low 16 bits are at least the operand's.
7812            if !check_bip112(sequence_value, input_sequence) {
7813                return Ok(false);
7814            }
7815
7816            // Validation passed - behave as NOP (do NOT pop the sequence value)
7817            Ok(true)
7818        }
7819
7820        // OP_CHECKTEMPLATEVERIFY (BIP119) - OP_NOP4
7821        // Verifies that the transaction matches a template hash.
7822        // Implements BIP119: CHECKTEMPLATEVERIFY.
7823        //
7824        // Behavior must match consensus:
7825        // - If SCRIPT_VERIFY_DEFAULT_CHECK_TEMPLATE_VERIFY_HASH flag is not set, behaves as NOP4
7826        // - Requires exactly 32 bytes on stack (template hash)
7827        // - Fails if template hash doesn't match transaction
7828        OP_CHECKTEMPLATEVERIFY => {
7829            #[cfg(not(feature = "ctv"))]
7830            {
7831                upgradable_nop(flags)
7832            }
7833
7834            #[cfg(feature = "ctv")]
7835            {
7836                use crate::constants::{
7837                    CTV_ACTIVATION_MAINNET, CTV_ACTIVATION_REGTEST, CTV_ACTIVATION_TESTNET,
7838                };
7839
7840                // Check activation
7841                let ctv_activation = match network {
7842                    crate::types::Network::Mainnet => CTV_ACTIVATION_MAINNET,
7843                    crate::types::Network::Testnet => CTV_ACTIVATION_TESTNET,
7844                    crate::types::Network::Regtest | crate::types::Network::Signet => {
7845                        CTV_ACTIVATION_REGTEST
7846                    }
7847                    // Same sentinel as ForkActivationTable: not active on testnet4.
7848                    crate::types::Network::Testnet4 => u64::MAX,
7849                };
7850
7851                let ctv_active = block_height.map(|h| h >= ctv_activation).unwrap_or(false);
7852                if !ctv_active {
7853                    return upgradable_nop(flags);
7854                }
7855
7856                if flags & flags::SCRIPT_VERIFY_DEFAULT_CHECK_TEMPLATE_VERIFY_HASH == 0 {
7857                    return upgradable_nop(flags);
7858                }
7859
7860                use crate::bip119::calculate_template_hash;
7861
7862                // CTV requires exactly 32 bytes (template hash) on stack
7863                if stack.is_empty() {
7864                    return Err(ConsensusError::ScriptErrorWithCode {
7865                        code: ScriptErrorCode::InvalidStackOperation,
7866                        message: "OP_CHECKTEMPLATEVERIFY: insufficient stack items".into(),
7867                    });
7868                }
7869
7870                let template_hash_bytes = stack.pop().unwrap();
7871
7872                // Template hash must be exactly 32 bytes
7873                if template_hash_bytes.len() != 32 {
7874                    return upgradable_nop(flags);
7875                }
7876
7877                // Calculate actual template hash for this transaction
7878                let mut expected_hash = [0u8; 32];
7879                expected_hash.copy_from_slice(&template_hash_bytes);
7880
7881                let actual_hash = calculate_template_hash(tx, input_index).map_err(|e| {
7882                    ConsensusError::ScriptErrorWithCode {
7883                        code: ScriptErrorCode::TxInvalid,
7884                        message: format!("CTV hash calculation failed: {e}").into(),
7885                    }
7886                })?;
7887
7888                // Constant-time comparison (use hash_eq from crypto module)
7889                use crate::crypto::hash_compare::hash_eq;
7890                let matches = hash_eq(&expected_hash, &actual_hash);
7891
7892                if !matches {
7893                    return Ok(false); // Script fails if template doesn't match
7894                }
7895
7896                // CTV succeeds - script continues (NOP-type opcode, doesn't push anything)
7897                Ok(true)
7898            }
7899        }
7900
7901        // OP_CHECKSIGFROMSTACK (BIP348) - replaces OP_SUCCESS204
7902        // Verifies a BIP 340 Schnorr signature against an arbitrary message.
7903        // Implements BIP348: CHECKSIGFROMSTACK.
7904        //
7905        // Behavior must match BIP341 tapscript verification:
7906        // - Only available in Tapscript (leaf version 0xc0)
7907        // - Pops 3 items: pubkey (top), message (second), signature (third)
7908        // - If signature is empty, pushes empty vector and continues
7909        // - If signature is valid, pushes 0x01 (single byte)
7910        // - If signature is invalid, script fails
7911        OP_CHECKSIGFROMSTACK => {
7912            #[cfg(not(feature = "csfs"))]
7913            {
7914                // OP_CHECKSIGFROMSTACK is an immediate success only while scanning a tapscript.
7915                // Executing it in a legacy script or a version-0 witness script
7916                // is an undefined opcode and fails the script.
7917                if sigversion == SigVersion::Tapscript {
7918                    return Ok(true);
7919                }
7920                Err(ConsensusError::ScriptErrorWithCode {
7921                    code: ScriptErrorCode::BadOpcode,
7922                    message: "OP_CHECKSIGFROMSTACK is undefined outside tapscript".into(),
7923                })
7924            }
7925
7926            #[cfg(feature = "csfs")]
7927            {
7928                // BIP-348: Only available in Tapscript (leaf version 0xc0)
7929                if sigversion != SigVersion::Tapscript {
7930                    return Err(ConsensusError::ScriptErrorWithCode {
7931                        code: ScriptErrorCode::BadOpcode,
7932                        message: "OP_CHECKSIGFROMSTACK only available in Tapscript".into(),
7933                    });
7934                }
7935
7936                let csfs_active = {
7937                    use crate::activation::IsForkActive;
7938                    block_height.is_some_and(|height| {
7939                        crate::activation::ForkActivationTable::from_network(network)
7940                            .is_fork_active(crate::types::ForkId::Csfs, height)
7941                    })
7942                };
7943                if !csfs_active {
7944                    if flags & flags::SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS != 0 {
7945                        return Err(ConsensusError::ScriptErrorWithCode {
7946                            code: ScriptErrorCode::DiscourageUpgradableNops,
7947                            message: "OP_CHECKSIGFROMSTACK not yet activated".into(),
7948                        });
7949                    }
7950                    return Ok(true);
7951                }
7952
7953                use crate::bip348::verify_signature_from_stack;
7954
7955                // BIP-348: If fewer than 3 elements, script MUST fail
7956                if stack.len() < 3 {
7957                    return Err(ConsensusError::ScriptErrorWithCode {
7958                        code: ScriptErrorCode::InvalidStackOperation,
7959                        message: "OP_CHECKSIGFROMSTACK: insufficient stack items (need 3)".into(),
7960                    });
7961                }
7962
7963                // BIP-348: Pop in order: pubkey (top), message (second), signature (third)
7964                let pubkey_bytes = stack.pop().unwrap(); // Top
7965                let message_bytes = stack.pop().unwrap(); // Second
7966                let signature_bytes = stack.pop().unwrap(); // Third
7967
7968                // BIP-348: If pubkey size is zero, script MUST fail
7969                if pubkey_bytes.is_empty() {
7970                    return Err(ConsensusError::ScriptErrorWithCode {
7971                        code: ScriptErrorCode::PubkeyType,
7972                        message: "OP_CHECKSIGFROMSTACK: pubkey size is zero".into(),
7973                    });
7974                }
7975
7976                // BIP-348: If signature is empty, push empty vector and continue
7977                if signature_bytes.is_empty() {
7978                    stack.push(to_stack_element(&[])); // Empty vector, not 0
7979                    return Ok(true);
7980                }
7981
7982                // BIP-348: Verify signature (only for 32-byte pubkeys)
7983                // OPTIMIZATION: Use collector for batch verification if available
7984                #[cfg(feature = "production")]
7985                let is_valid = {
7986                    verify_signature_from_stack(
7987                        &message_bytes,    // Message (NOT hashed by BIP 340 spec)
7988                        &pubkey_bytes,     // Pubkey (32 bytes for BIP 340)
7989                        &signature_bytes,  // Signature (64-byte BIP 340 Schnorr)
7990                        schnorr_collector, // Pass collector for batch verification
7991                    )
7992                    .unwrap_or(false)
7993                };
7994                #[cfg(not(feature = "production"))]
7995                let is_valid = verify_signature_from_stack(
7996                    &message_bytes,   // Message (NOT hashed by BIP 340 spec)
7997                    &pubkey_bytes,    // Pubkey (32 bytes for BIP 340)
7998                    &signature_bytes, // Signature (64-byte BIP 340 Schnorr)
7999                )
8000                .unwrap_or(false);
8001
8002                if !is_valid {
8003                    // BIP-348: Validation failure immediately terminates script execution
8004                    return Ok(false);
8005                }
8006
8007                // BIP-342/348: per-tapscript validation weight enforces signature-related limits during
8008                // execution. They are not added to `MAX_BLOCK_SIGOPS_COST` (witness v1 returns 0).
8009
8010                // BIP-348: Push 0x01 (single byte) if valid
8011                stack.push(to_stack_element(&[0x01])); // Single byte 0x01, not 1
8012                Ok(true)
8013            }
8014        }
8015
8016        // cold path for all other opcodes (branch prediction hint)
8017        _ => execute_opcode_cold(opcode, stack, flags),
8018    }
8019}
8020
8021/// Rare opcode dispatch (`#[cold]` so hot path stays compact).
8022#[cold]
8023fn execute_opcode_cold(opcode: u8, stack: &mut Vec<StackElement>, flags: u32) -> Result<bool> {
8024    execute_opcode(opcode, stack, flags, SigVersion::Base)
8025}
8026
8027// ============================================================================
8028// Benchmarking Utilities
8029// ============================================================================
8030
8031/// Clear script verification cache
8032///
8033/// Useful for benchmarking to ensure consistent results without cache state
8034/// pollution between runs.
8035///
8036/// # Example
8037///
8038/// Get and reset fast-path hit counters (production). Used by block validation to log
8039/// whether P2PK/P2PKH/P2SH/P2WPKH/P2WSH fast-paths are taken vs interpreter fallback.
8040/// Returns (p2pk, p2pkh, p2sh, p2wpkh, p2wsh, p2tr, unexec_if, bare_multisig, interpreter).
8041#[cfg(all(feature = "production", feature = "profile"))]
8042pub(crate) fn get_and_reset_fast_path_counts() -> (u64, u64, u64, u64, u64, u64, u64, u64, u64) {
8043    (
8044        FAST_PATH_P2PK.swap(0, Ordering::Relaxed),
8045        FAST_PATH_P2PKH.swap(0, Ordering::Relaxed),
8046        FAST_PATH_P2SH.swap(0, Ordering::Relaxed),
8047        FAST_PATH_P2WPKH.swap(0, Ordering::Relaxed),
8048        FAST_PATH_P2WSH.swap(0, Ordering::Relaxed),
8049        FAST_PATH_P2TR.swap(0, Ordering::Relaxed),
8050        FAST_PATH_UNEXEC_IF.swap(0, Ordering::Relaxed),
8051        FAST_PATH_BARE_MULTISIG.swap(0, Ordering::Relaxed),
8052        FAST_PATH_INTERPRETER.swap(0, Ordering::Relaxed),
8053    )
8054}
8055
8056/// Reset and return the UnexecIf fast-path hit count (production tests / 859672 gate).
8057#[cfg(feature = "production")]
8058pub fn take_fast_path_unexec_if() -> u64 {
8059    FAST_PATH_UNEXEC_IF.swap(0, Ordering::Relaxed)
8060}
8061
8062/// ```rust
8063/// use blvm_consensus::script::clear_script_cache;
8064///
8065/// // Clear cache before benchmark run
8066/// clear_script_cache();
8067/// ```
8068#[cfg(all(feature = "production", feature = "benchmarking"))]
8069pub fn clear_script_cache() {
8070    if let Some(cache) = SCRIPT_CACHE.get() {
8071        let mut cache = cache.write().unwrap_or_else(|e| e.into_inner());
8072        cache.clear();
8073    }
8074}
8075
8076/// Clear hash operation cache
8077///
8078/// Useful for benchmarking to ensure consistent results without cache state
8079/// pollution between runs.
8080///
8081/// # Example
8082///
8083/// ```rust
8084/// use blvm_consensus::script::clear_hash_cache;
8085///
8086/// // Clear cache before benchmark run
8087/// clear_hash_cache();
8088/// ```
8089#[cfg(all(feature = "production", feature = "benchmarking"))]
8090pub fn clear_hash_cache() {
8091    crypto_ops::clear_hash_cache();
8092}
8093
8094/// Clear all caches
8095///
8096/// Convenience function to clear both script and hash caches.
8097///
8098/// # Example
8099///
8100/// ```rust
8101/// use blvm_consensus::script::clear_all_caches;
8102///
8103/// // Clear all caches before benchmark run
8104/// clear_all_caches();
8105/// ```
8106#[cfg(all(feature = "production", feature = "benchmarking"))]
8107pub fn clear_all_caches() {
8108    clear_script_cache();
8109    clear_hash_cache();
8110}
8111
8112/// Clear thread-local stack pool
8113///
8114/// Clears the thread-local stack pool to reset allocation state for benchmarking.
8115/// This ensures consistent memory allocation patterns across benchmark runs.
8116///
8117/// # Example
8118///
8119/// ```rust
8120/// use blvm_consensus::script::clear_stack_pool;
8121///
8122/// // Clear pool before benchmark run
8123/// clear_stack_pool();
8124/// ```
8125#[cfg(all(feature = "production", feature = "benchmarking"))]
8126pub fn clear_stack_pool() {
8127    STACK_POOL.with(|pool| {
8128        let mut pool = pool.borrow_mut();
8129        pool.clear();
8130    });
8131}
8132
8133/// Reset all benchmarking state
8134///
8135/// Convenience function to reset all caches and thread-local state for
8136/// reproducible benchmarks. Also clears sighash templates cache.
8137///
8138/// # Example
8139///
8140/// ```rust
8141/// use blvm_consensus::script::reset_benchmarking_state;
8142///
8143/// // Reset all state before benchmark run
8144/// reset_benchmarking_state();
8145/// ```
8146#[cfg(all(feature = "production", feature = "benchmarking"))]
8147pub fn reset_benchmarking_state() {
8148    clear_all_caches();
8149    clear_stack_pool();
8150    disable_caching(false); // Re-enable caching by default
8151    // Also clear sighash templates (currently no-op as templates aren't populated yet)
8152    #[cfg(feature = "benchmarking")]
8153    crate::transaction_hash::clear_sighash_templates();
8154}
8155
8156#[cfg(test)]
8157mod tests {
8158    use super::*;
8159
8160    #[test]
8161    fn tapscript_truncated_push_is_not_op_success() {
8162        use crate::types::{Network, OutPoint, Transaction, TransactionInput, TransactionOutput};
8163
8164        let tx = Transaction {
8165            version: 2,
8166            inputs: vec![TransactionInput {
8167                prevout: OutPoint {
8168                    hash: [0u8; 32],
8169                    index: 0,
8170                },
8171                sequence: 0xffff_ffff,
8172                script_sig: vec![],
8173            }]
8174            .into(),
8175            outputs: vec![TransactionOutput {
8176                value: 0,
8177                script_pubkey: vec![],
8178            }]
8179            .into(),
8180            lock_time: 0,
8181        };
8182        let eval = |script: &[u8]| {
8183            let mut stack = Vec::new();
8184            eval_script_with_context_full(
8185                script,
8186                &mut stack,
8187                0,
8188                &tx,
8189                0,
8190                &[0],
8191                &[&[]],
8192                None,
8193                None,
8194                Network::Mainnet,
8195                SigVersion::Tapscript,
8196                None,
8197                None,
8198                None,
8199                Some(0),
8200                #[cfg(feature = "production")]
8201                None,
8202                None,
8203                #[cfg(feature = "production")]
8204                None,
8205            )
8206        };
8207
8208        // Opcode 80 is an immediate success. A bare one succeeds.
8209        assert!(eval(&[0x50]).unwrap());
8210        // OP_PUSHDATA2 with a one-byte header. 0x50 is leftover length, not an opcode.
8211        assert!(!eval(&[0x4d, 0x50]).unwrap());
8212        // OP_PUSHDATA4 with a one-byte header. 0x7e is opcode 126, also an immediate success.
8213        assert!(!eval(&[0x4e, 0x7e]).unwrap());
8214        // 0x50 inside a real push is data. The following OP_0 leaves two stack items.
8215        assert!(!eval(&[0x01, 0x50, 0x00]).unwrap());
8216
8217        // A push whose payload does not fit is not an immediate-success opcode,
8218        // whatever byte is left over. One supplied byte is short of every
8219        // direct push except a one-byte push, which is truncated by itself.
8220        assert!(!eval(&[0x01]).unwrap(), "direct push 1");
8221        for opcode in 2u8..=0x4b {
8222            assert!(!eval(&[opcode, 0x50]).unwrap(), "direct push {opcode}");
8223        }
8224        for (header, leftover) in [(0x4cu8, 0x50u8), (0x4d, 0x50), (0x4e, 0x50), (0x4e, 0x7e)] {
8225            assert!(!eval(&[header, leftover]).unwrap(), "header {header:#x}");
8226        }
8227    }
8228
8229    fn eval_tapscript(
8230        script: &[u8],
8231        network: crate::types::Network,
8232        block_height: Option<u64>,
8233    ) -> Result<bool> {
8234        use crate::types::{OutPoint, Transaction, TransactionInput, TransactionOutput};
8235        let tx = Transaction {
8236            version: 2,
8237            inputs: vec![TransactionInput {
8238                prevout: OutPoint {
8239                    hash: [0u8; 32],
8240                    index: 0,
8241                },
8242                sequence: 0xffff_ffff,
8243                script_sig: vec![],
8244            }]
8245            .into(),
8246            outputs: vec![TransactionOutput {
8247                value: 0,
8248                script_pubkey: vec![],
8249            }]
8250            .into(),
8251            lock_time: 0,
8252        };
8253        let mut stack = Vec::new();
8254        eval_script_with_context_full(
8255            script,
8256            &mut stack,
8257            0,
8258            &tx,
8259            0,
8260            &[0],
8261            &[&[]],
8262            block_height,
8263            None,
8264            network,
8265            SigVersion::Tapscript,
8266            None,
8267            None,
8268            None,
8269            Some(0),
8270            #[cfg(feature = "production")]
8271            None,
8272            None,
8273            #[cfg(feature = "production")]
8274            None,
8275        )
8276    }
8277
8278    #[test]
8279    fn mainnet_checksigfromstack_is_op_success() {
8280        let result = eval_tapscript(&[OP_CHECKSIGFROMSTACK], Network::Mainnet, Some(800_000));
8281        assert_eq!(result.unwrap(), true);
8282    }
8283
8284    #[cfg(feature = "csfs")]
8285    #[test]
8286    fn active_csfs_is_not_an_immediate_success() {
8287        let mainnet = eval_tapscript(&[OP_CHECKSIGFROMSTACK], Network::Mainnet, Some(0));
8288        assert_eq!(mainnet.unwrap(), true);
8289        let regtest = eval_tapscript(&[OP_CHECKSIGFROMSTACK], Network::Regtest, Some(0));
8290        assert!(
8291            !matches!(regtest, Ok(true)),
8292            "regtest CSFS must run the opcode, got {regtest:?}"
8293        );
8294    }
8295
8296    #[cfg(not(feature = "production"))]
8297    #[test]
8298    fn nonproduction_tapscript_schnorr_spend_is_accepted() {
8299        use crate::taproot::{
8300            TAPROOT_LEAF_VERSION_TAPSCRIPT, compute_script_merkle_root,
8301            compute_tapscript_signature_hash,
8302        };
8303        use crate::types::{OutPoint, Transaction, TransactionInput, TransactionOutput};
8304        use secp256k1::{Keypair, Message, Secp256k1, SecretKey};
8305
8306        let secp = Secp256k1::new();
8307        let secret = SecretKey::from_slice(&[0x42; 32]).expect("secret");
8308        let keypair = Keypair::from_secret_key(&secp, &secret);
8309        let leaf_pk = keypair.x_only_public_key().0.serialize();
8310        let mut tapscript = vec![0x20];
8311        tapscript.extend_from_slice(&leaf_pk);
8312        tapscript.push(OP_CHECKSIG);
8313        let internal = [
8314            0x79, 0xbe, 0x66, 0x7e, 0xf9, 0xdc, 0xbb, 0xac, 0x55, 0xa0, 0x62, 0x95, 0xce, 0x87,
8315            0x0b, 0x07, 0x02, 0x9b, 0xfc, 0xdb, 0x2d, 0xce, 0x28, 0xd9, 0x59, 0xf2, 0x81, 0x5b,
8316            0x16, 0xf8, 0x17, 0x98,
8317        ];
8318        let root = compute_script_merkle_root(&tapscript, &[], TAPROOT_LEAF_VERSION_TAPSCRIPT)
8319            .expect("root");
8320        let (output_key, parity) =
8321            crate::secp256k1_backend::taproot_output_key_with_parity(&internal, &root)
8322                .expect("tweak");
8323        let mut script_pubkey = vec![0x51, 0x20];
8324        script_pubkey.extend_from_slice(&output_key);
8325        let mut control = vec![TAPROOT_LEAF_VERSION_TAPSCRIPT | parity];
8326        control.extend_from_slice(&internal);
8327        let prevout_value = 10_000i64;
8328        let tx = Transaction {
8329            version: 2,
8330            inputs: vec![TransactionInput {
8331                prevout: OutPoint {
8332                    hash: [0x71; 32],
8333                    index: 0,
8334                },
8335                script_sig: vec![].into(),
8336                sequence: 0xffff_ffff,
8337            }]
8338            .into(),
8339            outputs: vec![TransactionOutput {
8340                value: 9_000,
8341                script_pubkey: vec![0x51].into(),
8342            }]
8343            .into(),
8344            lock_time: 0,
8345        };
8346        let sighash = compute_tapscript_signature_hash(
8347            &tx,
8348            0,
8349            &[prevout_value],
8350            &[script_pubkey.as_slice()],
8351            &tapscript,
8352            TAPROOT_LEAF_VERSION_TAPSCRIPT,
8353            0xffff_ffff,
8354            0x00,
8355            None,
8356        )
8357        .expect("sighash");
8358        let msg = Message::from_digest_slice(&sighash).expect("digest");
8359        let sig = secp.sign_schnorr_no_aux_rand(&msg, &keypair);
8360        let witness = vec![sig.as_ref().to_vec(), tapscript, control];
8361        let accepted = verify_script_with_context_full(
8362            &tx.inputs[0].script_sig,
8363            &script_pubkey,
8364            Some(&witness),
8365            flags::SCRIPT_VERIFY_WITNESS | flags::SCRIPT_VERIFY_TAPROOT,
8366            &tx,
8367            0,
8368            &[prevout_value],
8369            &[script_pubkey.as_slice()],
8370            Some(crate::constants::TAPROOT_ACTIVATION_MAINNET),
8371            None,
8372            Network::Mainnet,
8373            SigVersion::Base,
8374            None,
8375        );
8376        assert_eq!(accepted.unwrap(), true);
8377    }
8378
8379    #[test]
8380    fn checkmultisig_pubkeys_count_toward_opcode_limit() {
8381        let mut within = vec![0x61; 160];
8382        within.push(0x00);
8383        within.push(0x00);
8384        within.extend(std::iter::repeat(0x00).take(20));
8385        within.extend_from_slice(&[0x01, 20, 0xae]);
8386        let mut stack = Vec::new();
8387        assert!(eval_script(&within, &mut stack, 0, SigVersion::Base).unwrap());
8388
8389        let mut over = vec![0x61; 181];
8390        over.push(0x00);
8391        over.push(0x00);
8392        over.extend(std::iter::repeat(0x00).take(20));
8393        over.extend_from_slice(&[0x01, 20, 0xae]);
8394        let mut stack = Vec::new();
8395        assert!(eval_script(&over, &mut stack, 0, SigVersion::Base).is_err());
8396
8397        // n outside 0..=20 is not added. 181 NOPs plus the opcode stay within 201.
8398        let mut ignored = vec![0x61; 181];
8399        ignored.extend_from_slice(&[0x00, 0x00, 0x01, 21, 0xae]);
8400        let mut stack = Vec::new();
8401        assert!(!eval_script(&ignored, &mut stack, 0, SigVersion::Base).unwrap());
8402    }
8403
8404    #[test]
8405    fn false_script_result_is_an_empty_vector() {
8406        let mut stack = Vec::new();
8407        assert!(eval_script(&[0x51, 0x52, 0x87], &mut stack, 0, SigVersion::Base).unwrap());
8408        assert!(stack.last().unwrap().is_empty());
8409
8410        let mut stack = Vec::new();
8411        assert!(eval_script(&[0x51, 0x51, 0x87], &mut stack, 0, SigVersion::Base).unwrap());
8412        assert_eq!(stack[0].as_slice(), &[1]);
8413
8414        let mut stack = Vec::new();
8415        assert!(
8416            eval_script(
8417                &[0x00, 0x01, 0x02, 0xac, 0x82],
8418                &mut stack,
8419                0,
8420                SigVersion::Base
8421            )
8422            .unwrap()
8423        );
8424        assert!(stack.last().unwrap().is_empty());
8425
8426        let mut stack = Vec::new();
8427        assert!(
8428            eval_script(
8429                &[0x00, 0x00, 0x51, 0x00, 0x51, 0xae, 0x82],
8430                &mut stack,
8431                0,
8432                SigVersion::Base
8433            )
8434            .unwrap()
8435        );
8436        assert!(stack.last().unwrap().is_empty());
8437    }
8438
8439    #[test]
8440    fn non_der_signature_aborts_the_script() {
8441        use crate::constants::BIP66_ACTIVATION_MAINNET;
8442        use crate::types::{Network, OutPoint, Transaction, TransactionInput, TransactionOutput};
8443
8444        let tx = Transaction {
8445            version: 1,
8446            inputs: vec![TransactionInput {
8447                prevout: OutPoint {
8448                    hash: [0u8; 32],
8449                    index: 0,
8450                },
8451                sequence: 0xffff_ffff,
8452                script_sig: vec![],
8453            }]
8454            .into(),
8455            outputs: vec![TransactionOutput {
8456                value: 0,
8457                script_pubkey: vec![],
8458            }]
8459            .into(),
8460            lock_time: 0,
8461        };
8462        let eval = |script: &[u8], flags: u32| {
8463            let mut stack = Vec::new();
8464            eval_script_with_context_full(
8465                script,
8466                &mut stack,
8467                flags,
8468                &tx,
8469                0,
8470                &[0],
8471                &[&[]],
8472                Some(BIP66_ACTIVATION_MAINNET),
8473                None,
8474                Network::Mainnet,
8475                SigVersion::Base,
8476                None,
8477                None,
8478                None,
8479                None,
8480                #[cfg(feature = "production")]
8481                None,
8482                None,
8483                #[cfg(feature = "production")]
8484                None,
8485            )
8486        };
8487
8488        let bad = [0x03, 0x30, 0x01, 0x01, 0x01, 0x02, 0xac, 0x75, 0x51];
8489        assert!(eval(&bad, 0x04).is_err());
8490        let empty = [0x00, 0x01, 0x02, 0xac, 0x75, 0x51];
8491        assert!(eval(&empty, 0x04).unwrap());
8492        assert!(eval(&bad, 0).unwrap());
8493    }
8494
8495    #[test]
8496    fn nested_v0_redeem_push_must_be_canonical() {
8497        let mut redeem = vec![0x00, 0x14];
8498        redeem.extend_from_slice(&[0xab; 20]);
8499        let canonical = serialize_push_data(&redeem);
8500        let mut pushdata1 = vec![0x4c, redeem.len() as u8];
8501        pushdata1.extend_from_slice(&redeem);
8502        assert!(redeem_push_is_canonical(&canonical, &redeem));
8503        assert!(!redeem_push_is_canonical(&pushdata1, &redeem));
8504        assert_eq!(canonical[0], 22);
8505    }
8506
8507    #[test]
8508    fn keypath_spend_reaches_the_sighash_check() {
8509        use crate::script::flags::SCRIPT_VERIFY_WITNESS;
8510        use crate::types::{Network, OutPoint, Transaction, TransactionInput, TransactionOutput};
8511
8512        let mut script_pubkey = vec![OP_1, PUSH_32_BYTES];
8513        script_pubkey.extend_from_slice(&[0x11u8; 32]);
8514        let tx = Transaction {
8515            version: 2,
8516            inputs: vec![TransactionInput {
8517                prevout: OutPoint {
8518                    hash: [0x22; 32],
8519                    index: 0,
8520                },
8521                script_sig: vec![].into(),
8522                sequence: 0xffff_ffff,
8523            }]
8524            .into(),
8525            outputs: vec![TransactionOutput {
8526                value: 1_000,
8527                script_pubkey: vec![OP_1].into(),
8528            }]
8529            .into(),
8530            lock_time: 0,
8531        };
8532        let witness = vec![vec![0x40u8; 64]];
8533        let result = verify_script_with_context_full(
8534            &vec![].into(),
8535            &script_pubkey,
8536            Some(&witness),
8537            SCRIPT_VERIFY_WITNESS,
8538            &tx,
8539            0,
8540            &[],
8541            &[],
8542            Some(1),
8543            None,
8544            Network::Regtest,
8545            SigVersion::Tapscript,
8546            #[cfg(feature = "production")]
8547            None,
8548            None,
8549            #[cfg(feature = "production")]
8550            None,
8551            #[cfg(feature = "production")]
8552            None,
8553            #[cfg(feature = "production")]
8554            None,
8555            #[cfg(all(feature = "production", feature = "blvm-secp256k1"))]
8556            None,
8557        );
8558        assert!(
8559            result.is_err(),
8560            "a key-path witness with no prevouts must reach the sighash check, got {result:?}"
8561        );
8562    }
8563
8564    #[test]
8565    fn p2sh_wrapped_unknown_witness_program_succeeds() {
8566        use digest::Digest;
8567
8568        let redeem = vec![0x52, 0x02, 0x11, 0x22];
8569        let hash = ripemd::Ripemd160::digest(sha2::Sha256::digest(&redeem));
8570        let mut script_pubkey = vec![OP_HASH160, 0x14];
8571        script_pubkey.extend_from_slice(&hash);
8572        script_pubkey.push(OP_EQUAL);
8573        let script_sig = serialize_push_data(&redeem);
8574        let witness = vec![vec![1u8]];
8575        let (tx, pv, psp) = minimal_tx_and_prevouts(&script_sig, &script_pubkey);
8576        let psp_refs: Vec<&[u8]> = psp.iter().map(|b| b.as_ref()).collect();
8577        let verify = |flags: u32, wit: Option<&Vec<Vec<u8>>>| {
8578            verify_script_with_context_full(
8579                &script_sig,
8580                &script_pubkey,
8581                wit,
8582                flags,
8583                &tx,
8584                0,
8585                &pv,
8586                &psp_refs,
8587                Some(500_000),
8588                None,
8589                crate::types::Network::Mainnet,
8590                SigVersion::Base,
8591                #[cfg(feature = "production")]
8592                None,
8593                None,
8594                #[cfg(feature = "production")]
8595                None,
8596                #[cfg(feature = "production")]
8597                None,
8598                #[cfg(feature = "production")]
8599                None,
8600                #[cfg(all(feature = "production", feature = "blvm-secp256k1"))]
8601                None,
8602            )
8603        };
8604        assert!(verify(0x801, Some(&witness)).unwrap());
8605        assert!(!verify(0x801 | 0x1000, Some(&witness)).unwrap());
8606        let mut pushdata1 = vec![0x4c, redeem.len() as u8];
8607        pushdata1.extend_from_slice(&redeem);
8608        let (tx_pd, pv_pd, psp_pd) = minimal_tx_and_prevouts(&pushdata1, &script_pubkey);
8609        let psp_pd_refs: Vec<&[u8]> = psp_pd.iter().map(|b| b.as_ref()).collect();
8610        let noncanonical = verify_script_with_context_full(
8611            &pushdata1,
8612            &script_pubkey,
8613            Some(&witness),
8614            0x801,
8615            &tx_pd,
8616            0,
8617            &pv_pd,
8618            &psp_pd_refs,
8619            Some(500_000),
8620            None,
8621            crate::types::Network::Mainnet,
8622            SigVersion::Base,
8623            #[cfg(feature = "production")]
8624            None,
8625            None,
8626            #[cfg(feature = "production")]
8627            None,
8628            #[cfg(feature = "production")]
8629            None,
8630            #[cfg(feature = "production")]
8631            None,
8632            #[cfg(all(feature = "production", feature = "blvm-secp256k1"))]
8633            None,
8634        );
8635        assert!(!noncanonical.unwrap());
8636
8637        let invalid_v0 = vec![0x00, 0x02, 0x11, 0x22];
8638        let hash = ripemd::Ripemd160::digest(sha2::Sha256::digest(&invalid_v0));
8639        let mut script_pubkey = vec![OP_HASH160, 0x14];
8640        script_pubkey.extend_from_slice(&hash);
8641        script_pubkey.push(OP_EQUAL);
8642        let script_sig = serialize_push_data(&invalid_v0);
8643        let (tx, pv, psp) = minimal_tx_and_prevouts(&script_sig, &script_pubkey);
8644        let psp_refs: Vec<&[u8]> = psp.iter().map(|b| b.as_ref()).collect();
8645        let result = verify_script_with_context_full(
8646            &script_sig,
8647            &script_pubkey,
8648            Some(&witness),
8649            0x801,
8650            &tx,
8651            0,
8652            &pv,
8653            &psp_refs,
8654            Some(500_000),
8655            None,
8656            crate::types::Network::Mainnet,
8657            SigVersion::Base,
8658            #[cfg(feature = "production")]
8659            None,
8660            None,
8661            #[cfg(feature = "production")]
8662            None,
8663            #[cfg(feature = "production")]
8664            None,
8665            #[cfg(feature = "production")]
8666            None,
8667            #[cfg(all(feature = "production", feature = "blvm-secp256k1"))]
8668            None,
8669        );
8670        assert!(!result.unwrap());
8671    }
8672
8673    /// scriptPubKey is executed before the witness-program shortcut. An
8674    /// all-zero or negative-zero program leaves a false stack and fails. A
8675    /// nonzero program of a future version succeeds.
8676    #[test]
8677    fn witness_program_false_payload_is_rejected() {
8678        use crate::constants::TAPROOT_ACTIVATION_MAINNET;
8679        use digest::Digest;
8680
8681        let verify = |script_pubkey: &[u8], script_sig: &[u8], height: u64| {
8682            let script_sig = script_sig.to_vec();
8683            let (tx, pv, psp) = minimal_tx_and_prevouts(&script_sig, script_pubkey);
8684            let refs: Vec<&[u8]> = psp.iter().map(|b| b.as_ref()).collect();
8685            verify_script_with_context_full(
8686                &script_sig,
8687                script_pubkey,
8688                None,
8689                0x801,
8690                &tx,
8691                0,
8692                &pv,
8693                &refs,
8694                Some(height),
8695                None,
8696                crate::types::Network::Mainnet,
8697                SigVersion::Base,
8698                #[cfg(feature = "production")]
8699                None,
8700                None,
8701                #[cfg(feature = "production")]
8702                None,
8703                #[cfg(feature = "production")]
8704                None,
8705                #[cfg(feature = "production")]
8706                None,
8707                #[cfg(all(feature = "production", feature = "blvm-secp256k1"))]
8708                None,
8709            )
8710        };
8711
8712        let payloads: &[(&[u8], bool)] = &[
8713            (&[0x00, 0x00], false),
8714            (&[0x00, 0x80], false),
8715            (&[0x01, 0x00], true),
8716        ];
8717        for version in 0x51u8..=0x60 {
8718            for &(payload, truthy) in payloads {
8719                let mut script = vec![version, payload.len() as u8];
8720                script.extend_from_slice(payload);
8721                let got = verify(&script, &[], 400_000).unwrap();
8722                let taproot_v1 = version == 0x51 && payload.len() == 32;
8723                let expect = truthy && !taproot_v1;
8724                assert_eq!(got, expect, "native {script:02x?}");
8725            }
8726        }
8727
8728        let zeros = vec![0u8; 32];
8729        let mut neg = vec![0u8; 32];
8730        neg[31] = 0x80;
8731        let mut nonzero = vec![0u8; 32];
8732        nonzero[0] = 1;
8733        for (payload, truthy) in [(&zeros[..], false), (&neg[..], false), (&nonzero[..], true)] {
8734            let mut script = vec![0x51, 0x20];
8735            script.extend_from_slice(payload);
8736            assert!(!verify(&script, &[], TAPROOT_ACTIVATION_MAINNET).unwrap());
8737            let before = verify(&script, &[], 400_000).unwrap();
8738            assert_eq!(before, truthy, "v1-32 before taproot {payload:02x?}");
8739        }
8740
8741        let redeem = vec![0x52, 0x02, 0x00, 0x00];
8742        let hash = ripemd::Ripemd160::digest(sha2::Sha256::digest(&redeem));
8743        let mut p2sh = vec![OP_HASH160, 0x14];
8744        p2sh.extend_from_slice(&hash);
8745        p2sh.push(OP_EQUAL);
8746        let push = serialize_push_data(&redeem);
8747        assert!(!verify(&p2sh, &push, 400_000).unwrap());
8748
8749        let redeem_ok = vec![0x52, 0x02, 0x01, 0x00];
8750        let hash_ok = ripemd::Ripemd160::digest(sha2::Sha256::digest(&redeem_ok));
8751        let mut p2sh_ok = vec![OP_HASH160, 0x14];
8752        p2sh_ok.extend_from_slice(&hash_ok);
8753        p2sh_ok.push(OP_EQUAL);
8754        assert!(verify(&p2sh_ok, &serialize_push_data(&redeem_ok), 400_000).unwrap());
8755    }
8756
8757    /// BIP341 taproot is the native `OP_1 PUSH_32_BYTES` program. The same
8758    /// program inside pay-to-script-hash stays an upgradable witness program
8759    /// after activation: a truthy program succeeds, and an all-zero program fails.
8760    #[test]
8761    fn p2sh_wrapped_v1_32_stays_upgradable_after_taproot() {
8762        use crate::constants::TAPROOT_ACTIVATION_MAINNET;
8763        use crate::script::flags::{SCRIPT_VERIFY_P2SH, SCRIPT_VERIFY_WITNESS};
8764        use digest::Digest;
8765
8766        let flags = SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS;
8767        let verify = |script_pubkey: &[u8], script_sig: &[u8]| {
8768            let script_sig = script_sig.to_vec();
8769            let (tx, pv, psp) = minimal_tx_and_prevouts(&script_sig, script_pubkey);
8770            let refs: Vec<&[u8]> = psp.iter().map(|b| b.as_ref()).collect();
8771            verify_script_with_context_full(
8772                &script_sig,
8773                script_pubkey,
8774                None,
8775                flags,
8776                &tx,
8777                0,
8778                &pv,
8779                &refs,
8780                Some(TAPROOT_ACTIVATION_MAINNET),
8781                None,
8782                crate::types::Network::Mainnet,
8783                SigVersion::Base,
8784                #[cfg(feature = "production")]
8785                None,
8786                None,
8787                #[cfg(feature = "production")]
8788                None,
8789                #[cfg(feature = "production")]
8790                None,
8791                #[cfg(feature = "production")]
8792                None,
8793                #[cfg(all(feature = "production", feature = "blvm-secp256k1"))]
8794                None,
8795            )
8796        };
8797
8798        let program_len = PUSH_32_BYTES as usize;
8799        let mut truthy_redeem = vec![OP_1, PUSH_32_BYTES];
8800        truthy_redeem.extend(std::iter::repeat(1u8).take(program_len));
8801        let truthy_hash = ripemd::Ripemd160::digest(sha2::Sha256::digest(&truthy_redeem));
8802        let mut truthy_p2sh = vec![OP_HASH160, PUSH_20_BYTES];
8803        truthy_p2sh.extend_from_slice(&truthy_hash);
8804        truthy_p2sh.push(OP_EQUAL);
8805        assert!(verify(&truthy_p2sh, &serialize_push_data(&truthy_redeem)).unwrap());
8806        assert!(!verify(&truthy_redeem, &[]).unwrap());
8807
8808        let mut zero_redeem = vec![OP_1, PUSH_32_BYTES];
8809        zero_redeem.extend(std::iter::repeat(0u8).take(program_len));
8810        let zero_hash = ripemd::Ripemd160::digest(sha2::Sha256::digest(&zero_redeem));
8811        let mut zero_p2sh = vec![OP_HASH160, PUSH_20_BYTES];
8812        zero_p2sh.extend_from_slice(&zero_hash);
8813        zero_p2sh.push(OP_EQUAL);
8814        assert!(!verify(&zero_p2sh, &serialize_push_data(&zero_redeem)).unwrap());
8815    }
8816
8817    #[test]
8818    fn test_eval_script_simple() {
8819        let script = vec![OP_1]; // OP_1
8820        let mut stack = Vec::new();
8821
8822        assert!(eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap());
8823        assert_eq!(stack.len(), 1);
8824        assert_eq!(stack[0].as_slice(), &[1]);
8825    }
8826
8827    #[test]
8828    fn test_eval_script_overflow() {
8829        let script = vec![0x51; MAX_STACK_SIZE + 1]; // Too many pushes
8830        let mut stack = Vec::new();
8831
8832        assert!(eval_script(&script, &mut stack, 0, SigVersion::Base).is_err());
8833    }
8834
8835    #[test]
8836    fn test_verify_script_simple() {
8837        let _script_sig = [0x51]; // OP_1
8838        let _script_pubkey = [0x51]; // OP_1
8839
8840        // This should work: OP_1 pushes 1, then OP_1 pushes another 1
8841        // Final stack has [1, 1], which is not exactly one non-zero value
8842        // Let's use a script that results in exactly one value on stack
8843        let script_sig = vec![0x51]; // OP_1
8844        let script_pubkey = vec![0x76, 0x88]; // OP_DUP, OP_EQUALVERIFY
8845
8846        // This should fail because OP_EQUALVERIFY removes both values
8847        assert!(!verify_script(&script_sig, &script_pubkey, None, 0).unwrap());
8848    }
8849
8850    // ============================================================================
8851    // COMPREHENSIVE OPCODE TESTS
8852    // ============================================================================
8853
8854    #[test]
8855    fn test_op_0() {
8856        let script = vec![OP_0]; // OP_0
8857        let mut stack = Vec::new();
8858        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
8859        assert!(result); // eval_script always succeeds; stack has empty item (falsy but not an error)
8860        assert_eq!(stack.len(), 1);
8861        assert!(stack[0].is_empty());
8862    }
8863
8864    #[test]
8865    fn test_op_1_to_op_16() {
8866        // Test OP_1 through OP_16
8867        for i in 1..=16 {
8868            let opcode = 0x50 + i;
8869            let script = vec![opcode];
8870            let mut stack = Vec::new();
8871            let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
8872            assert!(result);
8873            assert_eq!(stack.len(), 1);
8874            assert_eq!(stack[0].as_slice(), &[i]);
8875        }
8876    }
8877
8878    #[test]
8879    fn test_op_dup() {
8880        let script = vec![0x51, 0x76]; // OP_1, OP_DUP
8881        let mut stack = Vec::new();
8882        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
8883        assert!(result); // eval_script succeeds; stack has 2 items (EvalScript doesn't check count)
8884        assert_eq!(stack.len(), 2);
8885        assert_eq!(stack[0].as_slice(), &[1]);
8886        assert_eq!(stack[1].as_slice(), &[1]);
8887    }
8888
8889    #[test]
8890    fn test_op_dup_empty_stack() {
8891        let script = vec![OP_DUP]; // OP_DUP on empty stack
8892        let mut stack = Vec::new();
8893        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
8894        assert!(!result);
8895    }
8896
8897    #[test]
8898    fn test_op_hash160() {
8899        let script = vec![OP_1, OP_HASH160]; // OP_1, OP_HASH160
8900        let mut stack = Vec::new();
8901        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
8902        assert!(result);
8903        assert_eq!(stack.len(), 1);
8904        assert_eq!(stack[0].len(), 20); // RIPEMD160 output is 20 bytes
8905    }
8906
8907    #[test]
8908    fn test_op_hash160_empty_stack() {
8909        let script = vec![OP_HASH160]; // OP_HASH160 on empty stack
8910        let mut stack = Vec::new();
8911        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
8912        assert!(!result);
8913    }
8914
8915    #[test]
8916    fn test_op_hash256() {
8917        let script = vec![OP_1, OP_HASH256]; // OP_1, OP_HASH256
8918        let mut stack = Vec::new();
8919        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
8920        assert!(result);
8921        assert_eq!(stack.len(), 1);
8922        assert_eq!(stack[0].len(), 32); // SHA256 output is 32 bytes
8923    }
8924
8925    #[test]
8926    fn test_op_hash256_empty_stack() {
8927        let script = vec![OP_HASH256]; // OP_HASH256 on empty stack
8928        let mut stack = Vec::new();
8929        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
8930        assert!(!result);
8931    }
8932
8933    #[test]
8934    fn test_op_equal() {
8935        let script = vec![0x51, 0x51, 0x87]; // OP_1, OP_1, OP_EQUAL
8936        let mut stack = Vec::new();
8937        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
8938        assert!(result);
8939        assert_eq!(stack.len(), 1);
8940        assert_eq!(stack[0].as_slice(), &[1]); // True
8941    }
8942
8943    #[test]
8944    fn test_op_equal_false() {
8945        let script = vec![0x51, 0x52, 0x87]; // OP_1, OP_2, OP_EQUAL
8946        let mut stack = Vec::new();
8947        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
8948        assert!(result); // eval_script succeeds; OP_EQUAL pushes [] (empty = falsy)
8949        assert_eq!(stack.len(), 1);
8950        assert!(stack[0].is_empty()); // False is [] (empty), not [0x00]
8951    }
8952
8953    #[test]
8954    fn test_op_equal_insufficient_stack() {
8955        let script = vec![0x51, 0x87]; // OP_1, OP_EQUAL (need 2 items)
8956        let mut stack = Vec::new();
8957        let result = eval_script(&script, &mut stack, 0, SigVersion::Base);
8958        assert!(
8959            result.is_err(),
8960            "OP_EQUAL with insufficient stack should return error"
8961        );
8962        if let Err(crate::error::ConsensusError::ScriptErrorWithCode { code, .. }) = result {
8963            assert_eq!(
8964                code,
8965                crate::error::ScriptErrorCode::InvalidStackOperation,
8966                "Should return InvalidStackOperation"
8967            );
8968        }
8969    }
8970
8971    #[test]
8972    fn test_op_verify() {
8973        let script = vec![0x51, 0x69]; // OP_1, OP_VERIFY
8974        let mut stack = Vec::new();
8975        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
8976        assert!(result); // eval_script succeeds; OP_VERIFY consumed the truthy item
8977        assert_eq!(stack.len(), 0); // OP_VERIFY consumes the top item
8978    }
8979
8980    #[test]
8981    fn test_op_verify_false() {
8982        let script = vec![0x00, 0x69]; // OP_0, OP_VERIFY (false)
8983        let mut stack = Vec::new();
8984        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
8985        assert!(!result);
8986    }
8987
8988    #[test]
8989    fn test_op_verify_empty_stack() {
8990        let script = vec![OP_VERIFY]; // OP_VERIFY on empty stack
8991        let mut stack = Vec::new();
8992        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
8993        assert!(!result);
8994    }
8995
8996    #[test]
8997    fn test_op_equalverify() {
8998        let script = vec![0x51, 0x51, 0x88]; // OP_1, OP_1, OP_EQUALVERIFY
8999        let mut stack = Vec::new();
9000        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
9001        assert!(result); // eval_script succeeds; OP_EQUALVERIFY consumed both equal items
9002        assert_eq!(stack.len(), 0); // OP_EQUALVERIFY consumes both items
9003    }
9004
9005    #[test]
9006    fn test_op_equalverify_false() {
9007        let script = vec![0x51, 0x52, 0x88]; // OP_1, OP_2, OP_EQUALVERIFY (false)
9008        let mut stack = Vec::new();
9009        let result = eval_script(&script, &mut stack, 0, SigVersion::Base);
9010        assert!(
9011            result.is_err(),
9012            "OP_EQUALVERIFY with false condition should return error"
9013        );
9014        if let Err(crate::error::ConsensusError::ScriptErrorWithCode { code, .. }) = result {
9015            assert_eq!(
9016                code,
9017                crate::error::ScriptErrorCode::EqualVerify,
9018                "Should return EqualVerify"
9019            );
9020        }
9021    }
9022
9023    #[test]
9024    fn test_op_checksig() {
9025        // Note: This test uses simplified inputs. Production code performs full signature verification.
9026        // The test verifies that OP_CHECKSIG executes without panicking, not that signatures are valid.
9027        let script = vec![OP_1, OP_1, OP_CHECKSIG]; // OP_1, OP_1, OP_CHECKSIG
9028        let mut stack = Vec::new();
9029        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
9030        assert!(result); // eval_script succeeds; CHECKSIG pushes [] (invalid sig = falsy) but no error
9031        assert_eq!(stack.len(), 1);
9032        // Production code validates signatures using secp256k1; test uses simplified inputs
9033    }
9034
9035    #[test]
9036    fn test_op_checksig_insufficient_stack() {
9037        let script = vec![OP_1, OP_CHECKSIG]; // OP_1, OP_CHECKSIG (need 2 items)
9038        let mut stack = Vec::new();
9039        let result = eval_script(&script, &mut stack, 0, SigVersion::Base);
9040        assert!(
9041            result.is_err(),
9042            "OP_CHECKSIG with insufficient stack should return error"
9043        );
9044        if let Err(crate::error::ConsensusError::ScriptErrorWithCode { code, .. }) = result {
9045            assert_eq!(
9046                code,
9047                crate::error::ScriptErrorCode::InvalidStackOperation,
9048                "Should return InvalidStackOperation"
9049            );
9050        }
9051    }
9052
9053    #[test]
9054    fn test_unknown_opcode() {
9055        let script = vec![0xff]; // Unknown opcode (0xff is not a defined opcode constant)
9056        let mut stack = Vec::new();
9057        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
9058        assert!(!result);
9059    }
9060
9061    #[test]
9062    fn test_script_size_limit() {
9063        let script = vec![0x51; MAX_SCRIPT_SIZE + 1]; // Exceed size limit
9064        let mut stack = Vec::new();
9065        let result = eval_script(&script, &mut stack, 0, SigVersion::Base);
9066        assert!(result.is_err());
9067    }
9068
9069    #[test]
9070    fn test_operation_count_limit() {
9071        // Use OP_NOP (0x61) - non-push opcodes count toward limit
9072        let script = vec![0x61; MAX_SCRIPT_OPS + 1]; // Exceed operation limit
9073        let mut stack = Vec::new();
9074        let result = eval_script(&script, &mut stack, 0, SigVersion::Base);
9075        assert!(result.is_err());
9076    }
9077
9078    #[test]
9079    fn test_stack_underflow_multiple_ops() {
9080        let script = vec![0x51, 0x87, 0x87]; // OP_1, OP_EQUAL, OP_EQUAL (second OP_EQUAL will underflow)
9081        let mut stack = Vec::new();
9082        let result = eval_script(&script, &mut stack, 0, SigVersion::Base);
9083        assert!(result.is_err(), "Stack underflow should return error");
9084        if let Err(crate::error::ConsensusError::ScriptErrorWithCode { code, .. }) = result {
9085            assert_eq!(
9086                code,
9087                crate::error::ScriptErrorCode::InvalidStackOperation,
9088                "Should return InvalidStackOperation"
9089            );
9090        }
9091    }
9092
9093    #[test]
9094    fn test_final_stack_empty() {
9095        // eval_script (EvalScript) does not check the final stack — VerifyScript does.
9096        let script = vec![0x51, 0x52]; // OP_1, OP_2 (two items on final stack)
9097        let mut stack = Vec::new();
9098        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
9099        assert!(result); // eval_script succeeds; final stack check is VerifyScript's responsibility
9100        assert_eq!(stack.len(), 2);
9101    }
9102
9103    #[test]
9104    fn test_final_stack_false() {
9105        // eval_script (EvalScript) does not check top-of-stack truthiness — VerifyScript does.
9106        let script = vec![OP_0]; // OP_0 (false on final stack)
9107        let mut stack = Vec::new();
9108        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
9109        assert!(result); // eval_script succeeds; verify_script would return false here
9110    }
9111
9112    #[test]
9113    fn test_verify_script_with_witness() {
9114        // OP_1 sig + OP_1 pubkey + OP_1 witness leaves 3 truthy items; top is truthy → success.
9115        let script_sig = vec![OP_1]; // OP_1
9116        let script_pubkey = vec![OP_1]; // OP_1
9117        let witness = vec![OP_1]; // OP_1
9118        let flags = 0;
9119
9120        let result = verify_script(&script_sig, &script_pubkey, Some(&witness), flags).unwrap();
9121        assert!(result); // VerifyScript: top of stack is truthy regardless of stack size
9122    }
9123
9124    #[test]
9125    fn test_verify_script_failure() {
9126        // OP_0 pubkey pushes empty (falsy) item on top; verify_script returns false.
9127        let script_sig = vec![OP_1]; // OP_1 (truthy)
9128        let script_pubkey = vec![OP_0]; // OP_0 (pushes empty = falsy)
9129        let witness = None;
9130        let flags = 0;
9131
9132        let result = verify_script(&script_sig, &script_pubkey, witness, flags).unwrap();
9133        assert!(!result); // VerifyScript: top of stack is [] (falsy) → fail
9134    }
9135
9136    // ============================================================================
9137    // COMPREHENSIVE SCRIPT TESTS
9138    // ============================================================================
9139
9140    #[test]
9141    fn test_op_ifdup_true() {
9142        let script = vec![OP_1, OP_IFDUP]; // OP_1, OP_IFDUP
9143        let mut stack = Vec::new();
9144        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
9145        assert!(result); // eval_script succeeds; stack has 2 items after dup
9146        assert_eq!(stack.len(), 2);
9147        assert_eq!(stack[0].as_slice(), &[1]);
9148        assert_eq!(stack[1].as_slice(), &[1]);
9149    }
9150
9151    #[test]
9152    fn test_op_ifdup_false() {
9153        let script = vec![OP_0, OP_IFDUP]; // OP_0, OP_IFDUP
9154        let mut stack = Vec::new();
9155        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
9156        assert!(result); // eval_script succeeds; IFDUP doesn't dup falsy items
9157        assert_eq!(stack.len(), 1);
9158        assert_eq!(stack[0].as_slice(), &[] as &[u8]);
9159    }
9160
9161    #[test]
9162    fn test_op_depth() {
9163        let script = vec![OP_1, OP_1, OP_DEPTH]; // OP_1, OP_1, OP_DEPTH
9164        let mut stack = Vec::new();
9165        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
9166        assert!(result); // eval_script succeeds; stack has 3 items
9167        assert_eq!(stack.len(), 3);
9168        assert_eq!(stack[2].as_slice(), &[2]); // Depth should be 2 (before OP_DEPTH)
9169    }
9170
9171    #[test]
9172    fn test_op_drop() {
9173        let script = vec![OP_1, OP_2, OP_DROP]; // OP_1, OP_2, OP_DROP
9174        let mut stack = Vec::new();
9175        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
9176        assert!(result); // Final stack has 1 item [1]
9177        assert_eq!(stack.len(), 1);
9178        assert_eq!(stack[0].as_slice(), &[1]);
9179    }
9180
9181    #[test]
9182    fn test_op_drop_empty_stack() {
9183        let script = vec![OP_DROP]; // OP_DROP on empty stack
9184        let mut stack = Vec::new();
9185        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
9186        assert!(!result);
9187        assert_eq!(stack.len(), 0);
9188    }
9189
9190    #[test]
9191    fn test_op_nip() {
9192        let script = vec![OP_1, OP_2, OP_NIP]; // OP_1, OP_2, OP_NIP
9193        let mut stack = Vec::new();
9194        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
9195        assert!(result); // Final stack has 1 item [2]
9196        assert_eq!(stack.len(), 1);
9197        assert_eq!(stack[0].as_slice(), &[2]);
9198    }
9199
9200    #[test]
9201    fn test_op_nip_insufficient_stack() {
9202        let script = vec![OP_1, OP_NIP]; // OP_1, OP_NIP (only 1 item)
9203        let mut stack = Vec::new();
9204        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
9205        assert!(!result);
9206        assert_eq!(stack.len(), 1);
9207    }
9208
9209    #[test]
9210    fn test_op_over() {
9211        let script = vec![OP_1, OP_2, OP_OVER]; // OP_1, OP_2, OP_OVER
9212        let mut stack = Vec::new();
9213        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
9214        assert!(result); // eval_script succeeds; stack has 3 items
9215        assert_eq!(stack.len(), 3);
9216        assert_eq!(stack[0].as_slice(), &[1]);
9217        assert_eq!(stack[1].as_slice(), &[2]);
9218        assert_eq!(stack[2].as_slice(), &[1]);
9219    }
9220
9221    #[test]
9222    fn test_op_over_insufficient_stack() {
9223        let script = vec![OP_1, OP_OVER]; // OP_1, OP_OVER (only 1 item)
9224        let mut stack = Vec::new();
9225        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
9226        assert!(!result);
9227        assert_eq!(stack.len(), 1);
9228    }
9229
9230    #[test]
9231    fn test_op_pick() {
9232        let script = vec![OP_1, OP_2, OP_3, OP_1, OP_PICK]; // OP_1, OP_2, OP_3, OP_1, OP_PICK
9233        let mut stack = Vec::new();
9234        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
9235        assert!(result); // eval_script succeeds; stack has 4 items
9236        assert_eq!(stack.len(), 4);
9237        assert_eq!(stack[3].as_slice(), &[2]); // Should pick index 1 (OP_2)
9238    }
9239
9240    #[test]
9241    fn test_op_pick_empty_n() {
9242        // OP_1, OP_0, OP_PICK: n=0 picks top item (duplicates it), stack [1,1]
9243        let script = vec![OP_1, OP_0, OP_PICK];
9244        let mut stack = Vec::new();
9245        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
9246        assert!(result); // eval_script succeeds; stack has 2 items
9247        assert_eq!(stack.len(), 2);
9248        assert_eq!(stack[1].as_slice(), &[1]); // Picked the top (OP_1 value)
9249    }
9250
9251    #[test]
9252    fn test_op_pick_invalid_index() {
9253        let script = vec![OP_1, OP_2, OP_PICK]; // OP_1, OP_2, OP_PICK (n=2, but only 1 item)
9254        let mut stack = Vec::new();
9255        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
9256        assert!(!result);
9257        assert_eq!(stack.len(), 1);
9258    }
9259
9260    #[test]
9261    fn test_op_roll() {
9262        let script = vec![OP_1, OP_2, OP_3, OP_1, OP_ROLL]; // OP_1, OP_2, OP_3, OP_1, OP_ROLL
9263        let mut stack = Vec::new();
9264        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
9265        assert!(result); // eval_script succeeds; stack has 3 items
9266        assert_eq!(stack.len(), 3);
9267        assert_eq!(stack[0].as_slice(), &[1]);
9268        assert_eq!(stack[1].as_slice(), &[3]);
9269        assert_eq!(stack[2].as_slice(), &[2]); // Should roll index 1 (OP_2) to top
9270    }
9271
9272    #[test]
9273    fn test_op_roll_zero_n() {
9274        // OP_0 pushes empty bytes (CScriptNum 0), OP_ROLL(0) is a valid no-op
9275        let script = vec![OP_1, OP_0, OP_ROLL]; // OP_1, OP_0, OP_ROLL (n=0, no-op)
9276        let mut stack = Vec::new();
9277        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
9278        assert!(result); // Stack has [1], which is truthy
9279        assert_eq!(stack.len(), 1);
9280        assert_eq!(stack[0].as_slice(), &[1]);
9281    }
9282
9283    #[test]
9284    fn test_op_roll_invalid_index() {
9285        let script = vec![OP_1, OP_2, OP_ROLL]; // OP_1, OP_2, OP_ROLL (n=2, but only 1 item)
9286        let mut stack = Vec::new();
9287        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
9288        assert!(!result);
9289        assert_eq!(stack.len(), 1);
9290    }
9291
9292    #[test]
9293    fn test_op_rot() {
9294        let script = vec![OP_1, OP_2, OP_3, OP_ROT]; // OP_1, OP_2, OP_3, OP_ROT
9295        let mut stack = Vec::new();
9296        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
9297        assert!(result); // eval_script succeeds; stack has 3 items
9298        assert_eq!(stack.len(), 3);
9299        assert_eq!(stack[0].as_slice(), &[2]);
9300        assert_eq!(stack[1].as_slice(), &[3]);
9301        assert_eq!(stack[2].as_slice(), &[1]);
9302    }
9303
9304    #[test]
9305    fn test_op_rot_insufficient_stack() {
9306        let script = vec![OP_1, OP_2, OP_ROT]; // OP_1, OP_2, OP_ROT (only 2 items)
9307        let mut stack = Vec::new();
9308        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
9309        assert!(!result);
9310        assert_eq!(stack.len(), 2);
9311    }
9312
9313    #[test]
9314    fn test_op_swap() {
9315        let script = vec![OP_1, OP_2, OP_SWAP]; // OP_1, OP_2, OP_SWAP
9316        let mut stack = Vec::new();
9317        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
9318        assert!(result); // eval_script succeeds; stack has 2 items
9319        assert_eq!(stack.len(), 2);
9320        assert_eq!(stack[0].as_slice(), &[2]);
9321        assert_eq!(stack[1].as_slice(), &[1]);
9322    }
9323
9324    #[test]
9325    fn test_op_swap_insufficient_stack() {
9326        let script = vec![OP_1, OP_SWAP]; // OP_1, OP_SWAP (only 1 item)
9327        let mut stack = Vec::new();
9328        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
9329        assert!(!result);
9330        assert_eq!(stack.len(), 1);
9331    }
9332
9333    #[test]
9334    fn test_op_tuck() {
9335        let script = vec![OP_1, OP_2, OP_TUCK]; // OP_1, OP_2, OP_TUCK
9336        let mut stack = Vec::new();
9337        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
9338        assert!(result); // eval_script succeeds; stack has 3 items
9339        assert_eq!(stack.len(), 3);
9340        assert_eq!(stack[0].as_slice(), &[2]);
9341        assert_eq!(stack[1].as_slice(), &[1]);
9342        assert_eq!(stack[2].as_slice(), &[2]);
9343    }
9344
9345    #[test]
9346    fn test_op_tuck_insufficient_stack() {
9347        let script = vec![OP_1, OP_TUCK]; // OP_1, OP_TUCK (only 1 item)
9348        let mut stack = Vec::new();
9349        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
9350        assert!(!result);
9351        assert_eq!(stack.len(), 1);
9352    }
9353
9354    #[test]
9355    fn test_op_2drop() {
9356        let script = vec![OP_1, OP_2, OP_3, OP_2DROP]; // OP_1, OP_2, OP_3, OP_2DROP
9357        let mut stack = Vec::new();
9358        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
9359        assert!(result); // Final stack has 1 item [1]
9360        assert_eq!(stack.len(), 1);
9361        assert_eq!(stack[0].as_slice(), &[1]);
9362    }
9363
9364    #[test]
9365    fn test_op_2drop_insufficient_stack() {
9366        let script = vec![OP_1, OP_2DROP]; // OP_1, OP_2DROP (only 1 item)
9367        let mut stack = Vec::new();
9368        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
9369        assert!(!result);
9370        assert_eq!(stack.len(), 1);
9371    }
9372
9373    #[test]
9374    fn test_op_2dup() {
9375        let script = vec![OP_1, OP_2, OP_2DUP]; // OP_1, OP_2, OP_2DUP
9376        let mut stack = Vec::new();
9377        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
9378        assert!(result); // eval_script succeeds; stack has 4 items
9379        assert_eq!(stack.len(), 4);
9380        assert_eq!(stack[0].as_slice(), &[1]);
9381        assert_eq!(stack[1].as_slice(), &[2]);
9382        assert_eq!(stack[2].as_slice(), &[1]);
9383        assert_eq!(stack[3].as_slice(), &[2]);
9384    }
9385
9386    #[test]
9387    fn test_op_2dup_insufficient_stack() {
9388        let script = vec![OP_1, OP_2DUP]; // OP_1, OP_2DUP (only 1 item)
9389        let mut stack = Vec::new();
9390        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
9391        assert!(!result);
9392        assert_eq!(stack.len(), 1);
9393    }
9394
9395    #[test]
9396    fn test_op_3dup() {
9397        let script = vec![OP_1, OP_2, OP_3, OP_3DUP]; // OP_1, OP_2, OP_3, OP_3DUP
9398        let mut stack = Vec::new();
9399        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
9400        assert!(result); // eval_script succeeds; stack has 6 items
9401        assert_eq!(stack.len(), 6);
9402        assert_eq!(stack[0].as_slice(), &[1]);
9403        assert_eq!(stack[1].as_slice(), &[2]);
9404        assert_eq!(stack[2].as_slice(), &[3]);
9405        assert_eq!(stack[3].as_slice(), &[1]);
9406        assert_eq!(stack[4].as_slice(), &[2]);
9407        assert_eq!(stack[5].as_slice(), &[3]);
9408    }
9409
9410    #[test]
9411    fn test_op_3dup_insufficient_stack() {
9412        let script = vec![OP_1, OP_2, OP_3DUP]; // OP_1, OP_2, OP_3DUP (only 2 items)
9413        let mut stack = Vec::new();
9414        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
9415        assert!(!result);
9416        assert_eq!(stack.len(), 2);
9417    }
9418
9419    #[test]
9420    fn test_op_2over() {
9421        let script = vec![OP_1, OP_2, OP_3, OP_4, OP_2OVER]; // OP_1, OP_2, OP_3, OP_4, OP_2OVER
9422        let mut stack = Vec::new();
9423        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
9424        assert!(result); // eval_script succeeds; stack has 6 items
9425        assert_eq!(stack.len(), 6);
9426        assert_eq!(stack[4].as_slice(), &[1]); // Should copy second pair
9427        assert_eq!(stack[5].as_slice(), &[2]);
9428    }
9429
9430    #[test]
9431    fn test_op_2over_insufficient_stack() {
9432        let script = vec![OP_1, OP_2, OP_3, OP_2OVER]; // OP_1, OP_2, OP_3, OP_2OVER (only 3 items)
9433        let mut stack = Vec::new();
9434        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
9435        assert!(!result);
9436        assert_eq!(stack.len(), 3);
9437    }
9438
9439    #[test]
9440    fn test_op_2rot() {
9441        // Stack before: [1,2,3,4,5,6] (1=bottom, 6=top)
9442        // 2ROT moves 6th and 5th from top (=1 and 2) to top preserving order.
9443        // Stack after:  [3,4,5,6,1,2] (2=top, 1=second)
9444        let script = vec![OP_1, OP_2, OP_3, OP_4, OP_5, OP_6, OP_2ROT]; // 6 items, OP_2ROT
9445        let mut stack = Vec::new();
9446        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
9447        assert!(result); // eval_script succeeds; stack has 6 items
9448        assert_eq!(stack.len(), 6);
9449        assert_eq!(stack[4].as_slice(), &[1]); // OP_1 moved to second-from-top
9450        assert_eq!(stack[5].as_slice(), &[2]); // OP_2 moved to top
9451    }
9452
9453    #[test]
9454    fn test_op_2rot_insufficient_stack() {
9455        let script = vec![OP_1, OP_2, OP_3, OP_4, OP_2ROT]; // OP_1, OP_2, OP_3, OP_4, OP_2ROT (only 4 items)
9456        let mut stack = Vec::new();
9457        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
9458        assert!(!result);
9459        assert_eq!(stack.len(), 4);
9460    }
9461
9462    #[test]
9463    fn test_op_2swap() {
9464        let script = vec![OP_1, OP_2, OP_3, OP_4, OP_2SWAP]; // OP_1, OP_2, OP_3, OP_4, OP_2SWAP
9465        let mut stack = Vec::new();
9466        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
9467        assert!(result); // eval_script succeeds; stack has 4 items
9468        assert_eq!(stack.len(), 4);
9469        assert_eq!(stack[0].as_slice(), &[3]); // Should swap second pair
9470        assert_eq!(stack[1].as_slice(), &[4]);
9471        assert_eq!(stack[2].as_slice(), &[1]);
9472        assert_eq!(stack[3].as_slice(), &[2]);
9473    }
9474
9475    #[test]
9476    fn test_op_2swap_insufficient_stack() {
9477        let script = vec![OP_1, OP_2, OP_3, OP_2SWAP]; // OP_1, OP_2, OP_3, OP_2SWAP (only 3 items)
9478        let mut stack = Vec::new();
9479        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
9480        assert!(!result);
9481        assert_eq!(stack.len(), 3);
9482    }
9483
9484    #[test]
9485    fn test_op_size() {
9486        let script = vec![OP_1, OP_SIZE]; // OP_1, OP_SIZE
9487        let mut stack = Vec::new();
9488        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
9489        assert!(result); // eval_script succeeds; stack has 2 items (original + size)
9490        assert_eq!(stack.len(), 2);
9491        assert_eq!(stack[0].as_slice(), &[1]);
9492        assert_eq!(stack[1].as_slice(), &[1]); // Size of [1] is 1
9493    }
9494
9495    #[test]
9496    fn test_op_size_empty_stack() {
9497        let script = vec![OP_SIZE]; // OP_SIZE on empty stack
9498        let mut stack = Vec::new();
9499        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
9500        assert!(!result);
9501        assert_eq!(stack.len(), 0);
9502    }
9503
9504    #[test]
9505    fn test_op_return() {
9506        let script = vec![OP_1, OP_RETURN]; // OP_1, OP_RETURN
9507        let mut stack = Vec::new();
9508        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
9509        assert!(!result); // OP_RETURN always fails
9510        assert_eq!(stack.len(), 1);
9511    }
9512
9513    #[test]
9514    fn test_op_checksigverify() {
9515        let script = vec![OP_1, OP_2, OP_CHECKSIGVERIFY]; // OP_1, OP_2, OP_CHECKSIGVERIFY
9516        let mut stack = Vec::new();
9517        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
9518        assert!(!result); // Should fail due to invalid signature
9519        assert_eq!(stack.len(), 0);
9520    }
9521
9522    #[test]
9523    fn test_op_checksigverify_insufficient_stack() {
9524        let script = vec![OP_1, OP_CHECKSIGVERIFY]; // OP_1, OP_CHECKSIGVERIFY (only 1 item)
9525        let mut stack = Vec::new();
9526        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
9527        assert!(!result);
9528        assert_eq!(stack.len(), 1);
9529    }
9530
9531    #[test]
9532    fn test_unknown_opcode_comprehensive() {
9533        let script = vec![OP_1, 0xff]; // OP_1, unknown opcode (0xff is not a defined opcode constant)
9534        let mut stack = Vec::new();
9535        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
9536        assert!(!result); // Unknown opcode should fail
9537        assert_eq!(stack.len(), 1);
9538    }
9539
9540    #[test]
9541    fn test_verify_signature_invalid_pubkey() {
9542        let secp = signature::new_secp();
9543        let invalid_pubkey = vec![0x00]; // Invalid pubkey
9544        let signature = vec![0x30, 0x06, 0x02, 0x01, 0x00, 0x02, 0x01, 0x00]; // Valid DER signature
9545        let dummy_hash = [0u8; 32];
9546        let result = signature::verify_signature(
9547            &secp,
9548            &invalid_pubkey,
9549            &signature,
9550            &dummy_hash,
9551            0,
9552            0,
9553            crate::types::Network::Regtest,
9554            SigVersion::Base,
9555        );
9556        assert!(!result.unwrap_or(false));
9557    }
9558
9559    #[test]
9560    fn test_verify_signature_invalid_signature() {
9561        let secp = signature::new_secp();
9562        let pubkey = vec![
9563            0x02, 0x79, 0xbe, 0x66, 0x7e, 0xf9, 0xdc, 0xbb, 0xac, 0x55, 0xa0, 0x62, 0x95, 0xce,
9564            0x87, 0x0b, 0x07, 0x02, 0x9b, 0xfc, 0xdb, 0x2d, 0xce, 0x28, 0xd9, 0x59, 0xf2, 0x81,
9565            0x5b, 0x16, 0xf8, 0x17, 0x98,
9566        ]; // Valid pubkey
9567        let invalid_signature = vec![0x00]; // Invalid signature
9568        let dummy_hash = [0u8; 32];
9569        let result = signature::verify_signature(
9570            &secp,
9571            &pubkey,
9572            &invalid_signature,
9573            &dummy_hash,
9574            0,
9575            0,
9576            crate::types::Network::Regtest,
9577            SigVersion::Base,
9578        );
9579        assert!(!result.unwrap_or(false));
9580    }
9581
9582    // ============================================================================
9583    // Fast-path and verify_script_with_context_full tests
9584    // ============================================================================
9585
9586    /// Build a minimal transaction and prevout slices for verify_script_with_context_full.
9587    fn minimal_tx_and_prevouts(
9588        script_sig: &[u8],
9589        script_pubkey: &[u8],
9590    ) -> (
9591        crate::types::Transaction,
9592        Vec<i64>,
9593        Vec<crate::types::ByteString>,
9594    ) {
9595        use crate::types::{OutPoint, Transaction, TransactionInput, TransactionOutput};
9596        let tx = Transaction {
9597            version: 1,
9598            inputs: vec![TransactionInput {
9599                prevout: OutPoint {
9600                    hash: [0u8; 32],
9601                    index: 0,
9602                },
9603                sequence: 0xffff_ffff,
9604                script_sig: script_sig.to_vec(),
9605            }]
9606            .into(),
9607            outputs: vec![TransactionOutput {
9608                value: 0,
9609                script_pubkey: script_pubkey.to_vec(),
9610            }]
9611            .into(),
9612            lock_time: 0,
9613        };
9614        let prevout_values = vec![0i64];
9615        let prevout_script_pubkeys_vec = vec![script_pubkey.to_vec()];
9616        let prevout_script_pubkeys: Vec<&ByteString> = prevout_script_pubkeys_vec.iter().collect();
9617        (tx, prevout_values, prevout_script_pubkeys_vec)
9618    }
9619
9620    #[test]
9621    fn canonical_p2pk_requires_the_push_opcode_to_match_the_key_length() {
9622        let compressed = [2u8; PUSH_33_BYTES as usize];
9623        let mut matched = vec![PUSH_33_BYTES];
9624        matched.extend_from_slice(&compressed);
9625        matched.push(OP_CHECKSIG);
9626        assert_eq!(canonical_p2pk_pubkey(&matched), Some(compressed.as_slice()));
9627
9628        let mut truncated = vec![PUSH_65_BYTES];
9629        truncated.extend_from_slice(&compressed);
9630        truncated.push(OP_CHECKSIG);
9631        assert!(canonical_p2pk_pubkey(&truncated).is_none());
9632
9633        let uncompressed = [4u8; PUSH_65_BYTES as usize];
9634        let mut matched_long = vec![PUSH_65_BYTES];
9635        matched_long.extend_from_slice(&uncompressed);
9636        matched_long.push(OP_CHECKSIG);
9637        assert_eq!(
9638            canonical_p2pk_pubkey(&matched_long),
9639            Some(uncompressed.as_slice())
9640        );
9641
9642        let mut short_push_on_long_script = vec![PUSH_33_BYTES];
9643        short_push_on_long_script.extend_from_slice(&uncompressed);
9644        short_push_on_long_script.push(OP_CHECKSIG);
9645        assert!(canonical_p2pk_pubkey(&short_push_on_long_script).is_none());
9646    }
9647
9648    #[cfg(all(feature = "production", feature = "blvm-secp256k1"))]
9649    #[test]
9650    fn p2pk_fast_path_does_not_accept_a_push_that_does_not_fit() {
9651        use crate::transaction_hash::{SighashType, calculate_transaction_sighash_single_input};
9652        use secp256k1::{Message, PublicKey, Secp256k1, SecretKey};
9653
9654        let secp = Secp256k1::new();
9655        let secret = SecretKey::from_slice(&[52u8; 32]).expect("secret");
9656        let pubkey = PublicKey::from_secret_key(&secp, &secret).serialize();
9657
9658        let spend = |script_pubkey: &[u8]| {
9659            let (mut tx, prevout_values, prevout_scripts) =
9660                minimal_tx_and_prevouts(&[], script_pubkey);
9661            let sighash = calculate_transaction_sighash_single_input(
9662                &tx,
9663                0,
9664                script_pubkey,
9665                prevout_values[0],
9666                SighashType::ALL,
9667                None,
9668            )
9669            .expect("sighash");
9670            let msg = Message::from_digest_slice(&sighash).expect("digest");
9671            let mut der = secp.sign_ecdsa(&msg, &secret).serialize_der().to_vec();
9672            der.push(SighashType::ALL.0);
9673            let script_sig = serialize_push_data(&der);
9674            tx.inputs[0].script_sig = script_sig.clone();
9675            let refs: Vec<&[u8]> = prevout_scripts.iter().map(|s| s.as_slice()).collect();
9676            verify_script_with_context_full(
9677                &script_sig,
9678                script_pubkey,
9679                None,
9680                0,
9681                &tx,
9682                0,
9683                &prevout_values,
9684                &refs,
9685                None,
9686                None,
9687                crate::types::Network::Mainnet,
9688                SigVersion::Base,
9689                None,
9690                None,
9691                None,
9692                None,
9693                None,
9694                None,
9695            )
9696            .expect("verify")
9697        };
9698
9699        let mut mismatched = vec![PUSH_65_BYTES];
9700        mismatched.extend_from_slice(&pubkey);
9701        mismatched.push(OP_CHECKSIG);
9702        assert!(!spend(&mismatched));
9703
9704        let mut matched = vec![PUSH_33_BYTES];
9705        matched.extend_from_slice(&pubkey);
9706        matched.push(OP_CHECKSIG);
9707        assert!(spend(&matched));
9708    }
9709
9710    #[cfg(all(feature = "production", feature = "blvm-secp256k1"))]
9711    #[test]
9712    fn raw_key_multisig_is_not_accepted_by_the_fast_path() {
9713        use crate::transaction_hash::{SighashType, calculate_transaction_sighash_single_input};
9714        use secp256k1::{Message, PublicKey, Secp256k1, SecretKey};
9715
9716        let secp = Secp256k1::new();
9717        let secret = SecretKey::from_slice(&[52u8; 32]).expect("secret");
9718        let pubkey = PublicKey::from_secret_key(&secp, &secret).serialize();
9719
9720        let spend = |script_pubkey: &[u8]| {
9721            let (mut tx, prevout_values, prevout_scripts) =
9722                minimal_tx_and_prevouts(&[], script_pubkey);
9723            let sighash = calculate_transaction_sighash_single_input(
9724                &tx,
9725                0,
9726                script_pubkey,
9727                prevout_values[0],
9728                SighashType::ALL,
9729                None,
9730            )
9731            .expect("sighash");
9732            let msg = Message::from_digest_slice(&sighash).expect("digest");
9733            let mut der = secp.sign_ecdsa(&msg, &secret).serialize_der().to_vec();
9734            der.push(SighashType::ALL.0);
9735            let mut script_sig = vec![OP_0];
9736            script_sig.extend(serialize_push_data(&der));
9737            tx.inputs[0].script_sig = script_sig.clone();
9738            let refs: Vec<&[u8]> = prevout_scripts.iter().map(|s| s.as_slice()).collect();
9739            verify_script_with_context_full(
9740                &script_sig,
9741                script_pubkey,
9742                None,
9743                0,
9744                &tx,
9745                0,
9746                &prevout_values,
9747                &refs,
9748                None,
9749                None,
9750                crate::types::Network::Mainnet,
9751                SigVersion::Base,
9752                None,
9753                None,
9754                None,
9755                None,
9756                None,
9757                None,
9758            )
9759        };
9760
9761        let mut pushed = vec![OP_1, PUSH_33_BYTES];
9762        pushed.extend_from_slice(&pubkey);
9763        pushed.push(OP_1);
9764        pushed.push(OP_CHECKMULTISIG);
9765        assert!(matches!(spend(&pushed), Ok(true)));
9766
9767        let mut raw = vec![OP_1];
9768        raw.extend_from_slice(&pubkey);
9769        raw.push(OP_1);
9770        raw.push(OP_CHECKMULTISIG);
9771        assert!(!matches!(spend(&raw), Ok(true)));
9772    }
9773
9774    #[cfg(all(feature = "production", feature = "blvm-secp256k1"))]
9775    #[test]
9776    fn reserved_opcode_multisig_is_not_accepted_by_the_fast_path() {
9777        use crate::transaction_hash::{SighashType, calculate_transaction_sighash_single_input};
9778        use secp256k1::{Message, PublicKey, Secp256k1, SecretKey};
9779
9780        let secp = Secp256k1::new();
9781        let secret = SecretKey::from_slice(&[52u8; 32]).expect("secret");
9782        let pubkey = PublicKey::from_secret_key(&secp, &secret).serialize();
9783
9784        let mut redeem = vec![OP_1, PUSH_33_BYTES];
9785        redeem.extend_from_slice(&pubkey);
9786        redeem.push(OP_1);
9787        redeem.push(OP_CHECKMULTISIG);
9788
9789        let redeem_hash = Ripemd160::digest(OptimizedSha256::new().hash(&redeem));
9790        let mut p2sh = vec![OP_HASH160, PUSH_20_BYTES];
9791        p2sh.extend_from_slice(&redeem_hash);
9792        p2sh.push(OP_EQUAL);
9793
9794        let spend = |script_pubkey: &[u8], script_code: &[u8], prefix: &[u8], flags: u32| {
9795            let (mut tx, prevout_values, prevout_scripts) =
9796                minimal_tx_and_prevouts(&[], script_pubkey);
9797            let sighash = calculate_transaction_sighash_single_input(
9798                &tx,
9799                0,
9800                script_code,
9801                prevout_values[0],
9802                SighashType::ALL,
9803                None,
9804            )
9805            .expect("sighash");
9806            let msg = Message::from_digest_slice(&sighash).expect("digest");
9807            let mut der = secp.sign_ecdsa(&msg, &secret).serialize_der().to_vec();
9808            der.push(SighashType::ALL.0);
9809            let mut script_sig = prefix.to_vec();
9810            script_sig.extend(serialize_push_data(&der));
9811            if script_pubkey.len() == 23 {
9812                script_sig.extend(serialize_push_data(script_code));
9813            }
9814            tx.inputs[0].script_sig = script_sig.clone();
9815            let refs: Vec<&[u8]> = prevout_scripts.iter().map(|s| s.as_slice()).collect();
9816            verify_script_with_context_full(
9817                &script_sig,
9818                script_pubkey,
9819                None,
9820                flags,
9821                &tx,
9822                0,
9823                &prevout_values,
9824                &refs,
9825                None,
9826                None,
9827                crate::types::Network::Mainnet,
9828                SigVersion::Base,
9829                None,
9830                None,
9831                None,
9832                None,
9833                None,
9834                None,
9835            )
9836        };
9837
9838        assert!(matches!(spend(&redeem, &redeem, &[OP_0], 0), Ok(true)));
9839        assert!(!matches!(
9840            spend(&redeem, &redeem, &[OP_RESERVED], 0),
9841            Ok(true)
9842        ));
9843        assert!(matches!(
9844            spend(&p2sh, &redeem, &[OP_0], flags::SCRIPT_VERIFY_P2SH),
9845            Ok(true)
9846        ));
9847        assert!(!matches!(
9848            spend(&p2sh, &redeem, &[OP_RESERVED], flags::SCRIPT_VERIFY_P2SH),
9849            Ok(true)
9850        ));
9851    }
9852
9853    #[test]
9854    #[allow(deprecated)]
9855    fn verify_script_runs_the_p2sh_redeem_script() {
9856        let bad = vec![OP_RESERVED];
9857        let bad_hash = Ripemd160::digest(OptimizedSha256::new().hash(&bad));
9858        let mut bad_pubkey = vec![OP_HASH160, PUSH_20_BYTES];
9859        bad_pubkey.extend_from_slice(&bad_hash);
9860        bad_pubkey.push(OP_EQUAL);
9861        assert_eq!(
9862            verify_script(
9863                &serialize_push_data(&bad),
9864                &bad_pubkey,
9865                None,
9866                flags::SCRIPT_VERIFY_P2SH
9867            )
9868            .unwrap(),
9869            false
9870        );
9871
9872        let ok = vec![OP_1];
9873        let ok_hash = Ripemd160::digest(OptimizedSha256::new().hash(&ok));
9874        let mut ok_pubkey = vec![OP_HASH160, PUSH_20_BYTES];
9875        ok_pubkey.extend_from_slice(&ok_hash);
9876        ok_pubkey.push(OP_EQUAL);
9877        assert_eq!(
9878            verify_script(
9879                &serialize_push_data(&ok),
9880                &ok_pubkey,
9881                None,
9882                flags::SCRIPT_VERIFY_P2SH
9883            )
9884            .unwrap(),
9885            true
9886        );
9887    }
9888
9889    #[cfg(all(feature = "production", feature = "blvm-secp256k1"))]
9890    #[test]
9891    fn hybrid_pubkey_is_accepted_without_strictenc() {
9892        use crate::transaction_hash::{SighashType, calculate_transaction_sighash_single_input};
9893        use secp256k1::{Message, PublicKey, Secp256k1, SecretKey};
9894
9895        const PUBKEY_UNCOMPRESSED: u8 = 0x04;
9896        const PUBKEY_HYBRID_EVEN: u8 = 0x06;
9897        const PUBKEY_HYBRID_ODD: u8 = 0x07;
9898
9899        let secp = Secp256k1::new();
9900        let secret = SecretKey::from_slice(&[53u8; 32]).expect("secret");
9901        let mut uncompressed = PublicKey::from_secret_key(&secp, &secret).serialize_uncompressed();
9902        assert_eq!(uncompressed[0], PUBKEY_UNCOMPRESSED);
9903        let y_odd = uncompressed[64] & 1 == 1;
9904        let matching = if y_odd {
9905            PUBKEY_HYBRID_ODD
9906        } else {
9907            PUBKEY_HYBRID_EVEN
9908        };
9909        let mismatched = if y_odd {
9910            PUBKEY_HYBRID_EVEN
9911        } else {
9912            PUBKEY_HYBRID_ODD
9913        };
9914
9915        let spend = |pubkey: &[u8], flags: u32| {
9916            let mut script_pubkey = vec![PUSH_65_BYTES];
9917            script_pubkey.extend_from_slice(pubkey);
9918            script_pubkey.push(OP_CHECKSIG);
9919            let (mut tx, prevout_values, prevout_scripts) =
9920                minimal_tx_and_prevouts(&[], &script_pubkey);
9921            let sighash = calculate_transaction_sighash_single_input(
9922                &tx,
9923                0,
9924                &script_pubkey,
9925                prevout_values[0],
9926                SighashType::ALL,
9927                None,
9928            )
9929            .expect("sighash");
9930            let msg = Message::from_digest_slice(&sighash).expect("digest");
9931            let mut der = secp.sign_ecdsa(&msg, &secret).serialize_der().to_vec();
9932            der.push(SighashType::ALL.0);
9933            let script_sig = serialize_push_data(&der);
9934            tx.inputs[0].script_sig = script_sig.clone();
9935            let refs: Vec<&[u8]> = prevout_scripts.iter().map(|s| s.as_slice()).collect();
9936            verify_script_with_context_full(
9937                &script_sig,
9938                &script_pubkey,
9939                None,
9940                flags,
9941                &tx,
9942                0,
9943                &prevout_values,
9944                &refs,
9945                None,
9946                None,
9947                crate::types::Network::Mainnet,
9948                SigVersion::Base,
9949                None,
9950                None,
9951                None,
9952                None,
9953                None,
9954                None,
9955            )
9956        };
9957
9958        let block_flags = flags::SCRIPT_VERIFY_P2SH
9959            | flags::SCRIPT_VERIFY_WITNESS
9960            | flags::SCRIPT_VERIFY_TAPROOT
9961            | flags::SCRIPT_VERIFY_DERSIG;
9962        uncompressed[0] = matching;
9963        assert!(matches!(spend(&uncompressed, 0), Ok(true)));
9964        assert!(matches!(spend(&uncompressed, block_flags), Ok(true)));
9965        assert!(!matches!(
9966            spend(&uncompressed, flags::SCRIPT_VERIFY_STRICTENC),
9967            Ok(true)
9968        ));
9969        uncompressed[0] = mismatched;
9970        assert!(!matches!(spend(&uncompressed, 0), Ok(true)));
9971        uncompressed[0] = PUBKEY_UNCOMPRESSED;
9972        assert!(matches!(spend(&uncompressed, 0), Ok(true)));
9973    }
9974
9975    #[test]
9976    fn discouraged_nop_uses_the_upgradable_nop_flag() {
9977        let discourage = flags::SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS;
9978        let reserved = [OP_NOP1, OP_NOP5, OP_NOP10, OP_CHECKTEMPLATEVERIFY];
9979
9980        let eval = |script: &[u8], flags: u32| {
9981            let mut stack = Vec::new();
9982            eval_script(script, &mut stack, flags, SigVersion::Base)
9983        };
9984        let with_context = |opcode: u8, flags: u32| {
9985            let script_pubkey = vec![OP_1, opcode];
9986            let script_sig = Vec::new();
9987            let (tx, pv, psp) = minimal_tx_and_prevouts(&script_sig, &script_pubkey);
9988            let refs: Vec<&[u8]> = psp.iter().map(|b| b.as_ref()).collect();
9989            verify_script_with_context_full(
9990                &script_sig,
9991                &script_pubkey,
9992                None,
9993                flags,
9994                &tx,
9995                0,
9996                &pv,
9997                &refs,
9998                None,
9999                None,
10000                crate::types::Network::Mainnet,
10001                SigVersion::Base,
10002                #[cfg(feature = "production")]
10003                None,
10004                None,
10005                #[cfg(feature = "production")]
10006                None,
10007                #[cfg(feature = "production")]
10008                None,
10009                #[cfg(feature = "production")]
10010                None,
10011                #[cfg(all(feature = "production", feature = "blvm-secp256k1"))]
10012                None,
10013            )
10014        };
10015        let is_discouraged = |result: Result<bool>| match result {
10016            Err(ConsensusError::ScriptErrorWithCode { code, .. }) => {
10017                code == ScriptErrorCode::DiscourageUpgradableNops
10018            }
10019            _ => false,
10020        };
10021
10022        for opcode in reserved {
10023            let script = [OP_1, opcode];
10024            assert!(eval(&script, 0).unwrap());
10025            assert!(is_discouraged(eval(&script, discourage)));
10026            assert!(eval(&script, flags::SCRIPT_VERIFY_CONST_SCRIPTCODE).unwrap());
10027            assert!(eval(&script, flags::SCRIPT_VERIFY_WITNESS_PUBKEYTYPE).unwrap());
10028            assert!(with_context(opcode, 0).unwrap());
10029            assert!(is_discouraged(with_context(opcode, discourage)));
10030            assert!(with_context(opcode, flags::SCRIPT_VERIFY_CONST_SCRIPTCODE).unwrap());
10031        }
10032
10033        assert!(eval(&[OP_1, OP_NOP], discourage).unwrap());
10034        assert!(eval(&[OP_1, OP_CHECKLOCKTIMEVERIFY], discourage).unwrap());
10035        assert!(eval(&[OP_0, OP_IF, OP_NOP1, OP_ENDIF, OP_1], discourage,).unwrap());
10036        assert!(with_context(OP_NOP, discourage).unwrap());
10037        assert!(with_context(OP_CHECKLOCKTIMEVERIFY, discourage).unwrap());
10038    }
10039
10040    #[cfg(all(feature = "production", feature = "blvm-secp256k1"))]
10041    #[test]
10042    fn nullfail_waits_until_the_multisig_fails() {
10043        use crate::transaction_hash::{SighashType, calculate_transaction_sighash_single_input};
10044        use secp256k1::{Message, PublicKey, Secp256k1, SecretKey};
10045
10046        let secp = Secp256k1::new();
10047        let signing = SecretKey::from_slice(&[53u8; 32]).expect("secret");
10048        let other = SecretKey::from_slice(&[54u8; 32]).expect("secret");
10049        let outsider = SecretKey::from_slice(&[55u8; 32]).expect("secret");
10050        let signing_pk = PublicKey::from_secret_key(&secp, &signing).serialize();
10051        let other_pk = PublicKey::from_secret_key(&secp, &other).serialize();
10052
10053        let multisig = |prefix: &[u8], first: &[u8], second: &[u8]| {
10054            let mut script = prefix.to_vec();
10055            script.push(OP_1);
10056            script.push(PUSH_33_BYTES);
10057            script.extend_from_slice(first);
10058            script.push(PUSH_33_BYTES);
10059            script.extend_from_slice(second);
10060            script.push(OP_2);
10061            script.push(OP_CHECKMULTISIG);
10062            script
10063        };
10064        let spend = |script_pubkey: &[u8], secret: &SecretKey, flags: u32| {
10065            let (mut tx, prevout_values, prevout_scripts) =
10066                minimal_tx_and_prevouts(&[], script_pubkey);
10067            let sighash = calculate_transaction_sighash_single_input(
10068                &tx,
10069                0,
10070                script_pubkey,
10071                prevout_values[0],
10072                SighashType::ALL,
10073                None,
10074            )
10075            .expect("sighash");
10076            let msg = Message::from_digest_slice(&sighash).expect("digest");
10077            let mut der = secp.sign_ecdsa(&msg, secret).serialize_der().to_vec();
10078            der.push(SighashType::ALL.0);
10079            let mut script_sig = vec![OP_0];
10080            script_sig.extend(serialize_push_data(&der));
10081            tx.inputs[0].script_sig = script_sig.clone();
10082            let refs: Vec<&[u8]> = prevout_scripts.iter().map(|s| s.as_slice()).collect();
10083            verify_script_with_context_full(
10084                &script_sig,
10085                script_pubkey,
10086                None,
10087                flags,
10088                &tx,
10089                0,
10090                &prevout_values,
10091                &refs,
10092                None,
10093                None,
10094                crate::types::Network::Mainnet,
10095                SigVersion::Base,
10096                None,
10097                None,
10098                None,
10099                None,
10100                None,
10101                None,
10102            )
10103        };
10104        let nullfail_error = |result: Result<bool>| {
10105            matches!(
10106                result,
10107                Err(ConsensusError::ScriptErrorWithCode {
10108                    code: ScriptErrorCode::SigNullFail,
10109                    ..
10110                })
10111            )
10112        };
10113
10114        let nullfail = flags::SCRIPT_VERIFY_NULLFAIL;
10115        // Leading OP_NOP skips the bare-multisig fast path. The interpreter tries the
10116        // last key first, so this signature misses once and then matches.
10117        let interpreter = multisig(&[OP_NOP], &signing_pk, &other_pk);
10118        assert!(matches!(spend(&interpreter, &signing, nullfail), Ok(true)));
10119        assert!(matches!(spend(&interpreter, &signing, 0), Ok(true)));
10120        assert!(nullfail_error(spend(&interpreter, &outsider, nullfail)));
10121        assert!(matches!(spend(&interpreter, &outsider, 0), Ok(false)));
10122
10123        // Fast path tries keys in script order. The signature misses the first key.
10124        let fast_path = multisig(&[], &other_pk, &signing_pk);
10125        assert!(matches!(spend(&fast_path, &signing, nullfail), Ok(true)));
10126    }
10127
10128    #[test]
10129    fn test_verify_with_context_p2pkh_hash_mismatch() {
10130        // P2PKH pattern but pubkey hash does not match script_pubkey -> false (fast-path or interpreter).
10131        let pubkey = vec![0x02u8; 33]; // dummy compressed pubkey
10132        let sig = vec![0x30u8; 70]; // dummy sig (with sighash byte)
10133        let mut script_sig = Vec::new();
10134        script_sig.push(sig.len() as u8);
10135        script_sig.extend(&sig);
10136        script_sig.push(pubkey.len() as u8);
10137        script_sig.extend(&pubkey);
10138
10139        let mut script_pubkey = vec![OP_DUP, OP_HASH160, PUSH_20_BYTES];
10140        script_pubkey.extend(&[0u8; 20]); // wrong hash (not HASH160(pubkey))
10141        script_pubkey.push(OP_EQUALVERIFY);
10142        script_pubkey.push(OP_CHECKSIG);
10143
10144        let (tx, pv, psp) = minimal_tx_and_prevouts(&script_sig, &script_pubkey);
10145        let psp_refs: Vec<&[u8]> = psp.iter().map(|b| b.as_ref()).collect();
10146        let result = verify_script_with_context_full(
10147            &script_sig,
10148            &script_pubkey,
10149            None,
10150            0,
10151            &tx,
10152            0,
10153            &pv,
10154            &psp_refs,
10155            Some(500_000),
10156            None,
10157            crate::types::Network::Mainnet,
10158            SigVersion::Base,
10159            #[cfg(feature = "production")]
10160            None,
10161            None, // precomputed_bip143
10162            #[cfg(feature = "production")]
10163            None,
10164            #[cfg(feature = "production")]
10165            None,
10166            #[cfg(feature = "production")]
10167            None,
10168            #[cfg(all(feature = "production", feature = "blvm-secp256k1"))]
10169            None,
10170        );
10171        assert!(result.is_ok());
10172        assert!(!result.unwrap());
10173    }
10174
10175    #[test]
10176    fn test_verify_with_context_p2sh_hash_mismatch() {
10177        // P2SH pattern but redeem script hash does not match -> false.
10178        let redeem = vec![OP_1, OP_1, OP_ADD]; // minimal redeem
10179        let mut script_sig = Vec::new();
10180        script_sig.push(redeem.len() as u8);
10181        script_sig.extend(&redeem);
10182
10183        let mut script_pubkey = vec![OP_HASH160, PUSH_20_BYTES];
10184        script_pubkey.extend(&[0u8; 20]); // wrong hash (not HASH160(redeem))
10185        script_pubkey.push(OP_EQUAL);
10186
10187        let (tx, pv, psp) = minimal_tx_and_prevouts(&script_sig, &script_pubkey);
10188        let psp_refs: Vec<&[u8]> = psp.iter().map(|b| b.as_ref()).collect();
10189        let result = verify_script_with_context_full(
10190            &script_sig,
10191            &script_pubkey,
10192            None,
10193            0x01, // P2SH
10194            &tx,
10195            0,
10196            &pv,
10197            &psp_refs,
10198            Some(500_000),
10199            None,
10200            crate::types::Network::Mainnet,
10201            SigVersion::Base,
10202            #[cfg(feature = "production")]
10203            None,
10204            None, // precomputed_bip143
10205            #[cfg(feature = "production")]
10206            None,
10207            #[cfg(feature = "production")]
10208            None,
10209            #[cfg(feature = "production")]
10210            None,
10211            #[cfg(all(feature = "production", feature = "blvm-secp256k1"))]
10212            None,
10213        );
10214        assert!(result.is_ok());
10215        assert!(!result.unwrap());
10216    }
10217
10218    #[test]
10219    fn test_verify_with_context_p2wpkh_wrong_witness_size() {
10220        // P2WPKH script_pubkey but witness has 1 element (need 2) -> false.
10221        let mut script_pubkey = vec![OP_0, PUSH_20_BYTES];
10222        script_pubkey.extend(&[0u8; 20]);
10223        let witness: Vec<Vec<u8>> = vec![vec![0x30; 70]]; // only sig, no pubkey
10224        let (tx, pv, psp) = minimal_tx_and_prevouts(&[], &script_pubkey);
10225        let psp_refs: Vec<&[u8]> = psp.iter().map(|b| b.as_ref()).collect();
10226        let empty: Vec<u8> = vec![];
10227        let result = verify_script_with_context_full(
10228            &empty,
10229            &script_pubkey,
10230            Some(&witness),
10231            0,
10232            &tx,
10233            0,
10234            &pv,
10235            &psp_refs,
10236            Some(500_000),
10237            None,
10238            crate::types::Network::Mainnet,
10239            SigVersion::Base,
10240            #[cfg(feature = "production")]
10241            None,
10242            None, // precomputed_bip143
10243            #[cfg(feature = "production")]
10244            None,
10245            #[cfg(feature = "production")]
10246            None,
10247            #[cfg(feature = "production")]
10248            None,
10249            #[cfg(all(feature = "production", feature = "blvm-secp256k1"))]
10250            None,
10251        );
10252        assert!(result.is_ok());
10253        assert!(!result.unwrap());
10254    }
10255
10256    #[test]
10257    fn test_verify_with_context_p2wsh_wrong_witness_script_hash() {
10258        // P2WSH script_pubkey but SHA256(witness_script) != program -> false.
10259        let witness_script = vec![OP_1];
10260        let mut script_pubkey = vec![OP_0, PUSH_32_BYTES];
10261        script_pubkey.extend(&[0u8; 32]); // wrong hash (not SHA256(witness_script))
10262        let witness: Vec<Vec<u8>> = vec![witness_script];
10263        let (tx, pv, psp) = minimal_tx_and_prevouts(&[], &script_pubkey);
10264        let psp_refs: Vec<&[u8]> = psp.iter().map(|b| b.as_ref()).collect();
10265        let empty: Vec<u8> = vec![];
10266        let result = verify_script_with_context_full(
10267            &empty,
10268            &script_pubkey,
10269            Some(&witness),
10270            0,
10271            &tx,
10272            0,
10273            &pv,
10274            &psp_refs,
10275            Some(500_000),
10276            None,
10277            crate::types::Network::Mainnet,
10278            SigVersion::Base,
10279            #[cfg(feature = "production")]
10280            None,
10281            None, // precomputed_bip143
10282            #[cfg(feature = "production")]
10283            None,
10284            #[cfg(feature = "production")]
10285            None,
10286            #[cfg(feature = "production")]
10287            None,
10288            #[cfg(all(feature = "production", feature = "blvm-secp256k1"))]
10289            None,
10290        );
10291        assert!(result.is_ok());
10292        assert!(!result.unwrap());
10293    }
10294
10295    #[test]
10296    #[cfg(feature = "production")]
10297    fn test_p2wsh_multisig_fast_path() {
10298        // P2WSH 2-of-2 multisig: fast path parses and validates; placeholder sigs fail -> Ok(false).
10299        use crate::constants::BIP147_ACTIVATION_MAINNET;
10300        use crate::crypto::OptimizedSha256;
10301
10302        let pk1 = [0x02u8; PUSH_33_BYTES as usize];
10303        let pk2 = [0x03u8; PUSH_33_BYTES as usize];
10304        let mut witness_script = vec![OP_2, PUSH_33_BYTES];
10305        witness_script.extend_from_slice(&pk1);
10306        witness_script.push(PUSH_33_BYTES);
10307        witness_script.extend_from_slice(&pk2);
10308        witness_script.push(OP_2);
10309        witness_script.push(OP_CHECKMULTISIG);
10310
10311        let wsh_hash = OptimizedSha256::new().hash(&witness_script);
10312        let mut script_pubkey = vec![OP_0, PUSH_32_BYTES];
10313        script_pubkey.extend_from_slice(&wsh_hash);
10314
10315        let witness: Vec<Vec<u8>> = vec![
10316            vec![],           // zero-length NULLDUMMY
10317            vec![0x30u8; 72], // placeholder sig 1
10318            vec![0x30u8; 72], // placeholder sig 2
10319            witness_script.clone(),
10320        ];
10321
10322        let (tx, pv, psp) = minimal_tx_and_prevouts(&[], &script_pubkey);
10323        let psp_refs: Vec<&[u8]> = psp.iter().map(|b| b.as_ref()).collect();
10324        let empty: Vec<u8> = vec![];
10325        let result = verify_script_with_context_full(
10326            &empty,
10327            &script_pubkey,
10328            Some(&witness),
10329            0x810, // SIGHASH_ALL | VERIFY_NULLDUMMY | VERIFY_NULLFAIL
10330            &tx,
10331            0,
10332            &pv,
10333            &psp_refs,
10334            Some(BIP147_ACTIVATION_MAINNET + 1),
10335            None,
10336            crate::types::Network::Mainnet,
10337            SigVersion::Base,
10338            #[cfg(feature = "production")]
10339            None,
10340            None, // precomputed_bip143
10341            #[cfg(feature = "production")]
10342            None,
10343            #[cfg(feature = "production")]
10344            None,
10345            #[cfg(feature = "production")]
10346            None,
10347            #[cfg(all(feature = "production", feature = "blvm-secp256k1"))]
10348            None,
10349        );
10350        assert!(result.is_ok());
10351        assert!(!result.unwrap());
10352    }
10353}
10354
10355#[cfg(test)]
10356#[allow(unused_doc_comments)]
10357mod property_tests {
10358    use super::*;
10359    use proptest::prelude::*;
10360
10361    /// Property test: eval_script respects operation limits
10362    ///
10363    /// Mathematical specification:
10364    /// ∀ script ∈ ByteString: |script| > MAX_SCRIPT_OPS ⟹ eval_script fails
10365    proptest! {
10366        #[test]
10367        fn prop_eval_script_operation_limit(extra in 0usize..=8usize) {
10368            let mut stack = Vec::new();
10369            let over = vec![OP_NOP; MAX_SCRIPT_OPS as usize + 1 + extra];
10370            let rejected = eval_script(&over, &mut stack, 0, SigVersion::Base);
10371            prop_assert!(
10372                rejected.is_err(),
10373                "more than {MAX_SCRIPT_OPS} non-push opcodes is rejected"
10374            );
10375
10376            let mut stack = Vec::new();
10377            let under = vec![OP_NOP; MAX_SCRIPT_OPS as usize];
10378            prop_assert!(eval_script(&under, &mut stack, 0, SigVersion::Base).is_ok());
10379        }
10380    }
10381
10382    /// Property test: verify_script is deterministic
10383    ///
10384    /// Mathematical specification:
10385    /// ∀ inputs: verify_script(inputs) = verify_script(inputs)
10386    proptest! {
10387        #[test]
10388        fn prop_verify_script_deterministic(
10389            script_sig in prop::collection::vec(any::<u8>(), 0..20),
10390            script_pubkey in prop::collection::vec(any::<u8>(), 0..20),
10391            witness in prop::option::of(prop::collection::vec(any::<u8>(), 0..10)),
10392            flags in any::<u32>()
10393        ) {
10394            let result1 = verify_script(&script_sig, &script_pubkey, witness.as_ref(), flags);
10395            let result2 = verify_script(&script_sig, &script_pubkey, witness.as_ref(), flags);
10396
10397            assert_eq!(result1.is_ok(), result2.is_ok());
10398            if result1.is_ok() && result2.is_ok() {
10399                assert_eq!(result1.unwrap(), result2.unwrap());
10400            }
10401        }
10402    }
10403
10404    /// Property test: execute_opcode handles all opcodes without panicking
10405    ///
10406    /// Mathematical specification:
10407    /// ∀ opcode ∈ {0..255}, stack ∈ Vec<StackElement>: execute_opcode(opcode, stack) ∈ {true, false}
10408    proptest! {
10409        #[test]
10410        fn prop_execute_opcode_no_panic(
10411            opcode in any::<u8>(),
10412            stack_items in prop::collection::vec(
10413                prop::collection::vec(any::<u8>(), 0..5),
10414                0..10
10415            ),
10416            flags in any::<u32>()
10417        ) {
10418            let mut stack: Vec<StackElement> = stack_items.into_iter().map(|v| to_stack_element(&v)).collect();
10419            let result = execute_opcode(opcode, &mut stack, flags, SigVersion::Base);
10420
10421            // Some opcodes may return errors (invalid opcodes, insufficient stack, etc.)
10422            // The important thing is that it doesn't panic
10423            match result {
10424                Ok(success) => {
10425                    // Just test it returns a boolean (success is either true or false)
10426                    let _ = success;
10427                },
10428                Err(_) => {
10429                    // Errors are acceptable - invalid opcodes, insufficient stack, etc.
10430                    // The test is about not panicking, not about always succeeding
10431                }
10432            }
10433
10434            // Stack should remain within bounds
10435            assert!(stack.len() <= MAX_STACK_SIZE);
10436        }
10437    }
10438
10439    /// Property test: stack operations preserve bounds
10440    ///
10441    /// Mathematical specification:
10442    /// ∀ opcode ∈ {0..255}, stack ∈ Vec<StackElement>:
10443    /// - |stack| ≤ MAX_STACK_SIZE before and after execute_opcode
10444    /// - Stack operations are well-defined
10445    proptest! {
10446        #[test]
10447        fn prop_stack_operations_bounds(
10448            opcode in any::<u8>(),
10449            stack_items in prop::collection::vec(
10450                prop::collection::vec(any::<u8>(), 0..3),
10451                0..5
10452            ),
10453            flags in any::<u32>()
10454        ) {
10455            let mut stack: Vec<StackElement> = stack_items.into_iter().map(|v| to_stack_element(&v)).collect();
10456            let initial_len = stack.len();
10457
10458            let result = execute_opcode(opcode, &mut stack, flags, SigVersion::Base);
10459
10460            // Stack should never exceed MAX_STACK_SIZE
10461            assert!(stack.len() <= MAX_STACK_SIZE);
10462
10463            // If operation succeeded, stack should be in valid state
10464            if result.is_ok() && result.unwrap() {
10465                // For opcodes that modify stack size, verify reasonable bounds
10466                match opcode {
10467                    OP_0 | OP_1..=OP_16 => {
10468                        // Push opcodes - increase by 1
10469                        assert!(stack.len() == initial_len + 1);
10470                    },
10471                    OP_DUP => {
10472                        // OP_DUP - increase by 1
10473                        if initial_len > 0 {
10474                            assert!(stack.len() == initial_len + 1);
10475                        }
10476                    },
10477                    OP_3DUP => {
10478                        // OP_3DUP - increases by 3 if stack has >= 3 items
10479                        if initial_len >= 3 {
10480                            assert!(stack.len() == initial_len + 3);
10481                        }
10482                    },
10483                    OP_2OVER => {
10484                        // OP_2OVER - increases by 2 if stack has >= 4 items
10485                        if initial_len >= 4 {
10486                            assert!(stack.len() == initial_len + 2);
10487                        }
10488                    },
10489                    OP_DROP | OP_NIP | OP_2DROP => {
10490                        // These opcodes decrease stack size
10491                        assert!(stack.len() <= initial_len);
10492                    },
10493                    _ => {
10494                        // Other opcodes maintain or modify stack size reasonably
10495                        // Some opcodes can push multiple items, so allow up to +3
10496                        assert!(stack.len() <= initial_len + 3, "Stack size should be reasonable");
10497                    }
10498                }
10499            }
10500        }
10501    }
10502
10503    /// Property test: hash operations are deterministic
10504    ///
10505    /// Mathematical specification:
10506    /// ∀ input ∈ ByteString: OP_HASH160(input) = OP_HASH160(input)
10507    proptest! {
10508        #[test]
10509        fn prop_hash_operations_deterministic(
10510            input in prop::collection::vec(any::<u8>(), 0..10)
10511        ) {
10512            let elem = to_stack_element(&input);
10513            let mut stack1 = vec![elem.clone()];
10514            let mut stack2 = vec![elem];
10515
10516            let result1 = execute_opcode(0xa9, &mut stack1, 0, SigVersion::Base); // OP_HASH160
10517            let result2 = execute_opcode(0xa9, &mut stack2, 0, SigVersion::Base); // OP_HASH160
10518
10519            assert_eq!(result1.is_ok(), result2.is_ok());
10520            if let (Ok(val1), Ok(val2)) = (result1, result2) {
10521                assert_eq!(val1, val2);
10522                if val1 {
10523                    assert_eq!(stack1, stack2);
10524                }
10525            }
10526        }
10527    }
10528
10529    /// Property test: equality operations are symmetric
10530    ///
10531    /// Mathematical specification:
10532    /// ∀ a, b ∈ ByteString: OP_EQUAL(a, b) = OP_EQUAL(b, a)
10533    proptest! {
10534        #[test]
10535        fn prop_equality_operations_symmetric(
10536            a in prop::collection::vec(any::<u8>(), 0..5),
10537            b in prop::collection::vec(any::<u8>(), 0..5)
10538        ) {
10539            let mut stack1 = vec![to_stack_element(&a), to_stack_element(&b)];
10540            let mut stack2 = vec![to_stack_element(&b), to_stack_element(&a)];
10541
10542            let result1 = execute_opcode(0x87, &mut stack1, 0, SigVersion::Base); // OP_EQUAL
10543            let result2 = execute_opcode(0x87, &mut stack2, 0, SigVersion::Base); // OP_EQUAL
10544
10545            assert_eq!(result1.is_ok(), result2.is_ok());
10546            if let (Ok(val1), Ok(val2)) = (result1, result2) {
10547                assert_eq!(val1, val2);
10548                if val1 {
10549                    // Results should be identical (both true or both false)
10550                    assert_eq!(stack1.len(), stack2.len());
10551                    if !stack1.is_empty() && !stack2.is_empty() {
10552                        assert_eq!(stack1[0], stack2[0]);
10553                    }
10554                }
10555            }
10556        }
10557    }
10558
10559    /// Property test: script execution terminates
10560    ///
10561    /// Mathematical specification:
10562    /// ∀ script ∈ ByteString: eval_script(script) terminates (no infinite loops)
10563    proptest! {
10564        #[test]
10565        fn prop_script_execution_terminates(
10566            script in prop::collection::vec(any::<u8>(), 0..50)
10567        ) {
10568            let mut stack = Vec::new();
10569            let flags = 0u32;
10570
10571            let _ = eval_script(&script, &mut stack, flags, SigVersion::Base);
10572            assert!(stack.len() <= MAX_STACK_SIZE);
10573        }
10574    }
10575}