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
16mod arithmetic;
17mod context;
18mod control_flow;
19mod crypto_ops;
20pub mod flags;
21mod signature;
22mod stack;
23
24pub use signature::{batch_verify_signatures, verify_pre_extracted_ecdsa};
25pub use stack::{StackElement, cast_to_bool, to_stack_element};
26
27use crate::constants::*;
28use crate::crypto::OptimizedSha256;
29use crate::error::{ConsensusError, Result, ScriptErrorCode};
30use crate::opcodes::*;
31use crate::types::*;
32use blvm_spec_lock::spec_locked;
33use digest::Digest;
34use ripemd::Ripemd160;
35
36// LLVM-like optimizations
37#[cfg(feature = "production")]
38use crate::optimizations::{precomputed_constants, prefetch};
39
40// Cold error construction helpers - these paths are rarely taken
41#[cold]
42#[allow(dead_code)]
43fn make_operation_limit_error() -> ConsensusError {
44    ConsensusError::ScriptErrorWithCode {
45        code: ScriptErrorCode::OpCount,
46        message: "Operation limit exceeded".into(),
47    }
48}
49
50#[cold]
51fn make_stack_overflow_error() -> ConsensusError {
52    ConsensusError::ScriptErrorWithCode {
53        code: ScriptErrorCode::StackSize,
54        message: "Stack overflow".into(),
55    }
56}
57
58#[inline]
59fn bip143_p2wpkh_script_code(pubkey_hash: &[u8]) -> [u8; 25] {
60    let mut hash = [0u8; 20];
61    hash.copy_from_slice(pubkey_hash);
62    crate::transaction_hash::derive_bip143_script_code_p2wpkh(&hash)
63}
64
65#[cfg(feature = "production")]
66use std::collections::VecDeque;
67#[cfg(feature = "production")]
68use std::sync::{
69    OnceLock, RwLock,
70    atomic::{AtomicBool, AtomicU64, Ordering},
71};
72#[cfg(feature = "production")]
73use std::thread_local;
74
75/// Script verification result cache (production feature only)
76///
77/// Caches scriptPubKey verification results to avoid re-execution of identical scripts.
78/// Cache is bounded (LRU) and invalidated on consensus changes.
79/// Reference: Orange Paper Section 13.1 explicitly mentions script caching.
80#[cfg(feature = "production")]
81static SCRIPT_CACHE: OnceLock<RwLock<lru::LruCache<u64, bool>>> = OnceLock::new();
82
83/// Signature verification cache (sighash, pubkey, sig, flags) -> valid
84/// Sharded by key hash to reduce RwLock contention across parallel workers.
85#[cfg(feature = "production")]
86const SIG_CACHE_SHARDS: usize = 32;
87
88#[cfg(feature = "production")]
89const SIG_CACHE_SHARD: OnceLock<RwLock<lru::LruCache<[u8; 32], bool>>> = OnceLock::new();
90
91#[cfg(feature = "production")]
92static SIG_CACHE: [OnceLock<RwLock<lru::LruCache<[u8; 32], bool>>>; SIG_CACHE_SHARDS] =
93    [SIG_CACHE_SHARD; SIG_CACHE_SHARDS];
94
95/// Signature cache size. Default 500k; env BLVM_SIG_CACHE_ENTRIES overrides (up to 1M).
96#[cfg(feature = "production")]
97fn sig_cache_size() -> usize {
98    std::env::var("BLVM_SIG_CACHE_ENTRIES")
99        .ok()
100        .and_then(|s| s.parse().ok())
101        .filter(|&n: &usize| n > 0 && n <= 1_000_000)
102        .unwrap_or(500_000)
103}
104
105#[cfg(feature = "production")]
106fn sig_cache_shard_index(key: &[u8; 32]) -> usize {
107    let h = (key[0] as usize) | ((key[1] as usize) << 8) | ((key[2] as usize) << 16);
108    h % SIG_CACHE_SHARDS
109}
110
111#[cfg(feature = "production")]
112fn get_sig_cache_shard(key: &[u8; 32]) -> &'static RwLock<lru::LruCache<[u8; 32], bool>> {
113    let idx = sig_cache_shard_index(key);
114    SIG_CACHE[idx].get_or_init(|| {
115        use lru::LruCache;
116        use std::num::NonZeroUsize;
117        let cap = (sig_cache_size() / SIG_CACHE_SHARDS).max(1);
118        RwLock::new(LruCache::new(NonZeroUsize::new(cap).unwrap()))
119    })
120}
121
122#[cfg(feature = "production")]
123thread_local! {
124    static BATCH_PUT_SIG_CACHE_BY_SHARD: std::cell::RefCell<[Vec<([u8; 32], bool)>; SIG_CACHE_SHARDS]> =
125        std::cell::RefCell::new(std::array::from_fn(|_| Vec::new()));
126}
127
128/// Thread-local buffers for verify_soa_batch; reused to avoid per-batch allocs.
129#[cfg(feature = "production")]
130thread_local! {
131    static SOA_BATCH_BUF: std::cell::RefCell<(
132        Vec<[u8; 64]>,
133        Vec<[u8; 32]>,
134        Vec<[u8; 33]>,
135        Vec<usize>,
136        Vec<[u8; 32]>,
137    )> = const { std::cell::RefCell::new((
138        Vec::new(),
139        Vec::new(),
140        Vec::new(),
141        Vec::new(),
142        Vec::new(),
143    )) };
144}
145
146#[cfg(feature = "production")]
147fn batch_put_sig_cache(keys: &[[u8; 32]], results: &[bool]) {
148    BATCH_PUT_SIG_CACHE_BY_SHARD.with(|cell| {
149        let mut by_shard = cell.borrow_mut();
150        for v in by_shard.iter_mut() {
151            v.clear();
152        }
153        for (i, key) in keys.iter().enumerate() {
154            let result = results.get(i).copied().unwrap_or(false);
155            let idx = sig_cache_shard_index(key);
156            by_shard[idx].push((*key, result));
157        }
158        for shard_entries in by_shard.iter() {
159            if shard_entries.is_empty() {
160                continue;
161            }
162            let first_key = &shard_entries[0].0;
163            if let Ok(mut guard) = get_sig_cache_shard(first_key).write() {
164                for (k, v) in shard_entries.iter() {
165                    guard.put(*k, *v);
166                }
167            }
168        }
169    });
170}
171
172/// #4: Skip collect-time sig cache check during IBD (cache is cold, 100% miss = wasted DER parse).
173/// Set BLVM_SIG_CACHE_AT_COLLECT=1 for mempool/reorg where cache may be warm.
174///
175/// **IBD:** Do NOT set BLVM_SIG_CACHE_AT_COLLECT=1 during initial block download. Cache has 0% hit
176/// rate; serialization + hash + lock per sig is pure overhead. Default off is correct for IBD.
177#[cfg(feature = "production")]
178fn sig_cache_at_collect_enabled() -> bool {
179    use std::sync::OnceLock;
180    static CACHE: OnceLock<bool> = OnceLock::new();
181    *CACHE.get_or_init(|| {
182        std::env::var("BLVM_SIG_CACHE_AT_COLLECT")
183            .map(|s| s == "1" || s.eq_ignore_ascii_case("true"))
184            .unwrap_or(false)
185    })
186}
187
188/// Compute ECDSA sig cache key from msg(32)+pk(33)+sig(64)+flags(4)=133 bytes.
189/// Uses SipHash (Bitcoin Core style) instead of SHA-256 for ~10x faster hashing.
190#[cfg(feature = "production")]
191#[inline(always)]
192fn ecdsa_cache_key(msg: &[u8; 32], pk: &[u8; 33], sig_compact: &[u8; 64], flags: u32) -> [u8; 32] {
193    use siphasher::sip::SipHasher24;
194    use std::hash::{Hash, Hasher};
195    let mut key_input = [0u8; 133];
196    key_input[..32].copy_from_slice(msg);
197    key_input[32..65].copy_from_slice(pk);
198    key_input[65..129].copy_from_slice(sig_compact);
199    key_input[129..133].copy_from_slice(&flags.to_le_bytes());
200    let mut hasher = SipHasher24::new();
201    key_input.hash(&mut hasher);
202    let h = hasher.finish();
203    let mut out = [0u8; 32];
204    out[..8].copy_from_slice(&h.to_le_bytes());
205    out
206}
207
208/// Fast-path hit counters (production): verify that P2PK/P2PKH/P2SH/P2WPKH/P2WSH fast-paths are used.
209/// Logged periodically from block validation; interpreter = scripts that fell through to full interpreter.
210#[cfg(feature = "production")]
211static FAST_PATH_P2PK: AtomicU64 = AtomicU64::new(0);
212#[cfg(feature = "production")]
213static FAST_PATH_P2PKH: AtomicU64 = AtomicU64::new(0);
214#[cfg(feature = "production")]
215static FAST_PATH_P2SH: AtomicU64 = AtomicU64::new(0);
216#[cfg(feature = "production")]
217static FAST_PATH_P2WPKH: AtomicU64 = AtomicU64::new(0);
218#[cfg(feature = "production")]
219static FAST_PATH_P2WSH: AtomicU64 = AtomicU64::new(0);
220static FAST_PATH_P2TR: AtomicU64 = AtomicU64::new(0);
221#[cfg(feature = "production")]
222static FAST_PATH_BARE_MULTISIG: AtomicU64 = AtomicU64::new(0);
223#[cfg(feature = "production")]
224static FAST_PATH_INTERPRETER: AtomicU64 = AtomicU64::new(0);
225
226#[cfg(feature = "production")]
227fn get_script_cache() -> &'static RwLock<lru::LruCache<u64, bool>> {
228    SCRIPT_CACHE.get_or_init(|| {
229        // Bounded cache: 100,000 entries (optimized for production workloads)
230        // LRU eviction policy prevents unbounded memory growth
231        // Increased from 50k to 100k for better hit rates in large mempools
232        use lru::LruCache;
233        use std::num::NonZeroUsize;
234        RwLock::new(LruCache::new(NonZeroUsize::new(100_000).unwrap()))
235    })
236}
237
238/// Stack pool for VM optimization (production feature only)
239///
240/// Thread-local pool of pre-allocated stacks to avoid allocation overhead.
241/// Stacks are reused across script executions, significantly reducing memory allocations.
242#[cfg(feature = "production")]
243thread_local! {
244    static STACK_POOL: std::cell::RefCell<VecDeque<Vec<StackElement>>> =
245        std::cell::RefCell::new(VecDeque::with_capacity(10));
246}
247
248/// Get a stack from the pool, or create a new one if pool is empty
249#[cfg(feature = "production")]
250fn get_pooled_stack() -> Vec<StackElement> {
251    STACK_POOL.with(|pool| {
252        let mut pool = pool.borrow_mut();
253        if let Some(mut stack) = pool.pop_front() {
254            stack.clear();
255            if stack.capacity() < 20 {
256                stack.reserve(20);
257            }
258            stack
259        } else {
260            Vec::with_capacity(20)
261        }
262    })
263}
264
265/// RAII guard that returns stack to pool on drop. Use for interpreter fallback path.
266#[cfg(feature = "production")]
267struct PooledStackGuard(Vec<StackElement>);
268#[cfg(feature = "production")]
269impl Drop for PooledStackGuard {
270    fn drop(&mut self) {
271        return_pooled_stack(std::mem::take(&mut self.0));
272    }
273}
274
275/// Return a stack to the pool for reuse
276#[cfg(feature = "production")]
277fn return_pooled_stack(mut stack: Vec<StackElement>) {
278    // Clear stack but preserve capacity
279    stack.clear();
280
281    STACK_POOL.with(|pool| {
282        let mut pool = pool.borrow_mut();
283        // Limit pool size to prevent unbounded growth
284        if pool.len() < 10 {
285            pool.push_back(stack);
286        }
287        // If pool is full, stack is dropped (deallocated)
288    });
289}
290
291/// Flag to disable caching for benchmarking (production feature only)
292///
293/// When set to true, caches are bypassed entirely, allowing reproducible benchmarks
294/// without cache state pollution between runs.
295#[cfg(feature = "production")]
296static CACHE_DISABLED: AtomicBool = AtomicBool::new(false);
297
298/// When true, `verify_script_with_context_full` skips fast paths (P4.2 differential harness).
299#[cfg(feature = "production")]
300static FAST_PATHS_DISABLED: AtomicBool = AtomicBool::new(false);
301
302/// Disable caching for benchmarking
303///
304/// When disabled, all cache lookups are bypassed, ensuring consistent performance
305/// measurements without cache state affecting results.
306///
307/// # Example
308///
309/// ```rust
310/// use blvm_consensus::script::disable_caching;
311///
312/// // Disable caches for benchmarking
313/// disable_caching(true);
314/// // Run benchmarks...
315/// disable_caching(false); // Re-enable for production
316/// ```
317#[cfg(feature = "production")]
318pub fn disable_caching(disabled: bool) {
319    CACHE_DISABLED.store(disabled, Ordering::Relaxed);
320}
321
322/// Skip script fast paths so results come from the full interpreter (P4.2 equivalence tests).
323#[cfg(feature = "production")]
324pub fn disable_fast_paths(disabled: bool) {
325    FAST_PATHS_DISABLED.store(disabled, Ordering::Relaxed);
326}
327
328/// Check if caching is disabled
329#[cfg(feature = "production")]
330fn is_caching_disabled() -> bool {
331    CACHE_DISABLED.load(Ordering::Relaxed)
332}
333
334#[cfg(feature = "production")]
335fn fast_paths_disabled() -> bool {
336    FAST_PATHS_DISABLED.load(Ordering::Relaxed)
337}
338
339/// Compute cache key for script verification
340///
341/// Uses a simple hash of script_sig + script_pubkey + witness + flags to create cache key.
342/// Note: This is a simplified key - full implementation would use proper cryptographic hash.
343#[cfg(feature = "production")]
344fn compute_script_cache_key(
345    script_sig: &ByteString,
346    script_pubkey: &[u8],
347    witness: Option<&ByteString>,
348    flags: u32,
349) -> u64 {
350    use std::collections::hash_map::DefaultHasher;
351    use std::hash::{Hash, Hasher};
352
353    let mut hasher = DefaultHasher::new();
354    script_sig.hash(&mut hasher);
355    script_pubkey.hash(&mut hasher);
356    if let Some(w) = witness {
357        w.hash(&mut hasher);
358    }
359    flags.hash(&mut hasher);
360    hasher.finish()
361}
362
363/// Script version for policy/consensus behavior (BIP141/BIP341 SigVersion)
364#[derive(Debug, Clone, Copy, PartialEq, Eq)]
365pub enum SigVersion {
366    /// Legacy and P2SH scripts
367    Base,
368    /// Witness v0 (P2WPKH/P2WSH)
369    WitnessV0,
370    /// Taproot script path (witness v1 Tapscript)
371    Tapscript,
372}
373
374/// EvalScript: 𝒮𝒞 × 𝒮𝒯 × ℕ × SigVersion → {true, false}
375///
376/// Script execution follows a stack-based virtual machine:
377/// 1. Initialize stack S = ∅
378/// 2. For each opcode op in script:
379///    - If |S| > L_stack: return false (stack overflow)
380///    - If operation count > L_ops: return false (operation limit exceeded)
381///    - Execute op with current stack state
382///    - If execution fails: return false
383/// 3. Return true if execution completed without error (final stack shape is
384///    checked by [`verify_script`] / [`verify_script_with_context_full`], not here).
385///
386/// Performance: Pre-allocates stack with capacity hint to reduce allocations
387///
388/// In production mode, stacks should be obtained from pool using get_pooled_stack()
389/// for optimal performance. This function works with any Vec<StackElement>.
390#[spec_locked("5.2", "EvalScript")]
391#[cfg_attr(feature = "production", inline(always))]
392#[cfg_attr(not(feature = "production"), inline)]
393pub fn eval_script(
394    script: &[u8],
395    stack: &mut Vec<StackElement>,
396    flags: u32,
397    sigversion: SigVersion,
398) -> Result<bool> {
399    // Pre-allocate stack capacity to reduce allocations during execution
400    // Most scripts don't exceed 20 stack items in practice
401    if stack.capacity() < 20 {
402        stack.reserve(20);
403    }
404    #[cfg(feature = "production")]
405    {
406        eval_script_impl(script, stack, flags, sigversion)
407    }
408    #[cfg(not(feature = "production"))]
409    {
410        eval_script_inner(script, stack, flags, sigversion)
411    }
412}
413#[cfg(feature = "production")]
414fn eval_script_impl(
415    script: &[u8],
416    stack: &mut Vec<StackElement>,
417    flags: u32,
418    sigversion: SigVersion,
419) -> Result<bool> {
420    eval_script_inner(script, stack, flags, sigversion)
421}
422
423#[cfg(not(feature = "production"))]
424#[allow(dead_code)]
425fn eval_script_impl(
426    script: &[u8],
427    stack: &mut Vec<StackElement>,
428    flags: u32,
429    sigversion: SigVersion,
430) -> Result<bool> {
431    eval_script_inner(script, stack, flags, sigversion)
432}
433
434/// Push opcodes: any opcode <= OP_16 (0x60). Used by both production and non-production paths.
435#[inline(always)]
436fn is_push_opcode(opcode: u8) -> bool {
437    opcode <= 0x60
438}
439
440/// BIP342: opcodes that cause immediate script success in Tapscript (OP_SUCCESSx).
441/// Reference: Bitcoin Core IsOpSuccess (script.cpp:364–370).
442#[inline]
443fn is_op_success(opcode: u8) -> bool {
444    matches!(
445        opcode,
446        80 | 98
447            | 126..=129
448            | 131..=134
449            | 137..=138
450            | 141..=142
451            | 149..=153
452            | 187..=254
453    )
454}
455
456/// Advance past one opcode + its push data in a script byte slice.
457/// Returns the number of bytes to skip (always >= 1).
458#[inline]
459fn op_advance(script: &[u8], pc: usize) -> usize {
460    let opcode = script[pc];
461    match opcode {
462        // direct push: opcode byte is the data length (1–75 bytes)
463        0x01..=0x4b => 1 + opcode as usize,
464        // OP_PUSHDATA1: 1 length byte follows
465        0x4c => {
466            if pc + 1 < script.len() {
467                2 + script[pc + 1] as usize
468            } else {
469                1
470            }
471        }
472        // OP_PUSHDATA2: 2 length bytes follow (LE)
473        0x4d => {
474            if pc + 2 < script.len() {
475                3 + u16::from_le_bytes([script[pc + 1], script[pc + 2]]) as usize
476            } else {
477                1
478            }
479        }
480        // OP_PUSHDATA4: 4 length bytes follow (LE)
481        0x4e => {
482            if pc + 4 < script.len() {
483                5 + u32::from_le_bytes([
484                    script[pc + 1],
485                    script[pc + 2],
486                    script[pc + 3],
487                    script[pc + 4],
488                ]) as usize
489            } else {
490                1
491            }
492        }
493        _ => 1,
494    }
495}
496
497fn eval_script_inner(
498    script: &[u8],
499    stack: &mut Vec<StackElement>,
500    flags: u32,
501    sigversion: SigVersion,
502) -> Result<bool> {
503    use crate::constants::MAX_SCRIPT_SIZE;
504    use crate::error::{ConsensusError, ScriptErrorCode};
505
506    if (sigversion == SigVersion::Base || sigversion == SigVersion::WitnessV0)
507        && script.len() > MAX_SCRIPT_SIZE
508    {
509        return Err(ConsensusError::ScriptErrorWithCode {
510            code: ScriptErrorCode::ScriptSize,
511            message: "Script size exceeds maximum".into(),
512        });
513    }
514
515    let mut op_count = 0;
516    let mut control_stack: Vec<control_flow::ControlBlock> = Vec::new();
517    let mut altstack: Vec<StackElement> = Vec::new();
518
519    let mut i = 0;
520    while i < script.len() {
521        let opcode = script[i];
522        let in_false_branch = control_flow::in_false_branch(&control_stack);
523
524        // Count non-push opcodes toward op limit (Base/WitnessV0 only; BIP342 Tapscript has no 201-op cap).
525        // Bitcoin Core: interpreter.cpp EvalScript — nOpCount only for BASE || WITNESS_V0.
526        if sigversion != SigVersion::Tapscript && !is_push_opcode(opcode) {
527            op_count += 1;
528            if op_count > MAX_SCRIPT_OPS {
529                return Err(make_operation_limit_error());
530            }
531            debug_assert!(
532                op_count <= MAX_SCRIPT_OPS,
533                "Operation count ({op_count}) must not exceed MAX_SCRIPT_OPS ({MAX_SCRIPT_OPS})"
534            );
535        }
536
537        // Check combined stack + altstack size (BIP62/consensus).
538        // Bitcoin Core uses > MAX_STACK_SIZE (allows exactly 1000 items).
539        if stack.len() + altstack.len() > MAX_STACK_SIZE {
540            return Err(make_stack_overflow_error());
541        }
542
543        // Handle push opcodes with embedded data (0x01-0x4b direct, OP_PUSHDATA1/2/4).
544        if (0x01..=OP_PUSHDATA4).contains(&opcode) {
545            let (data, advance) = if opcode <= 0x4b {
546                let len = opcode as usize;
547                if i + 1 + len > script.len() {
548                    return Ok(false);
549                }
550                (&script[i + 1..i + 1 + len], 1 + len)
551            } else if opcode == OP_PUSHDATA1 {
552                if i + 1 >= script.len() {
553                    return Ok(false);
554                }
555                let len = script[i + 1] as usize;
556                if i + 2 + len > script.len() {
557                    return Ok(false);
558                }
559                (&script[i + 2..i + 2 + len], 2 + len)
560            } else if opcode == OP_PUSHDATA2 {
561                if i + 2 >= script.len() {
562                    return Ok(false);
563                }
564                let len = u16::from_le_bytes([script[i + 1], script[i + 2]]) as usize;
565                if i + 3 + len > script.len() {
566                    return Ok(false);
567                }
568                (&script[i + 3..i + 3 + len], 3 + len)
569            } else {
570                if i + 4 >= script.len() {
571                    return Ok(false);
572                }
573                let len = u32::from_le_bytes([
574                    script[i + 1],
575                    script[i + 2],
576                    script[i + 3],
577                    script[i + 4],
578                ]) as usize;
579                let data_start = i.saturating_add(5);
580                let data_end = data_start.saturating_add(len);
581                let advance = 5usize.saturating_add(len);
582                if advance < 5 || data_end > script.len() || data_end < data_start {
583                    return Ok(false);
584                }
585                (&script[data_start..data_end], advance)
586            };
587
588            if !in_false_branch {
589                stack.push(to_stack_element(data));
590            }
591            i += advance;
592            continue;
593        }
594
595        match opcode {
596            // OP_IF
597            OP_IF => {
598                if in_false_branch {
599                    control_stack.push(control_flow::ControlBlock::If { executing: false });
600                } else if stack.is_empty() {
601                    return Err(ConsensusError::ScriptErrorWithCode {
602                        code: ScriptErrorCode::InvalidStackOperation,
603                        message: "OP_IF: empty stack".into(),
604                    });
605                } else {
606                    let condition_bytes = stack.pop().unwrap();
607                    let condition = cast_to_bool(&condition_bytes);
608
609                    const SCRIPT_VERIFY_MINIMALIF: u32 = 0x2000;
610                    if (flags & SCRIPT_VERIFY_MINIMALIF) != 0
611                        && (sigversion == SigVersion::WitnessV0
612                            || sigversion == SigVersion::Tapscript)
613                        && !control_flow::is_minimal_if_condition(&condition_bytes)
614                    {
615                        return Err(ConsensusError::ScriptErrorWithCode {
616                            code: ScriptErrorCode::MinimalIf,
617                            message: "OP_IF condition must be minimally encoded".into(),
618                        });
619                    }
620
621                    control_stack.push(control_flow::ControlBlock::If {
622                        executing: condition,
623                    });
624                }
625            }
626            // OP_NOTIF
627            OP_NOTIF => {
628                if in_false_branch {
629                    control_stack.push(control_flow::ControlBlock::NotIf { executing: false });
630                } else if stack.is_empty() {
631                    return Err(ConsensusError::ScriptErrorWithCode {
632                        code: ScriptErrorCode::InvalidStackOperation,
633                        message: "OP_NOTIF: empty stack".into(),
634                    });
635                } else {
636                    let condition_bytes = stack.pop().unwrap();
637                    let condition = cast_to_bool(&condition_bytes);
638
639                    const SCRIPT_VERIFY_MINIMALIF: u32 = 0x2000;
640                    if (flags & SCRIPT_VERIFY_MINIMALIF) != 0
641                        && (sigversion == SigVersion::WitnessV0
642                            || sigversion == SigVersion::Tapscript)
643                        && !control_flow::is_minimal_if_condition(&condition_bytes)
644                    {
645                        return Err(ConsensusError::ScriptErrorWithCode {
646                            code: ScriptErrorCode::MinimalIf,
647                            message: "OP_NOTIF condition must be minimally encoded".into(),
648                        });
649                    }
650
651                    control_stack.push(control_flow::ControlBlock::NotIf {
652                        executing: !condition,
653                    });
654                }
655            }
656            // OP_ELSE
657            OP_ELSE => {
658                if let Some(block) = control_stack.last_mut() {
659                    match block {
660                        control_flow::ControlBlock::If { executing }
661                        | control_flow::ControlBlock::NotIf { executing } => {
662                            *executing = !*executing;
663                        }
664                    }
665                } else {
666                    return Err(ConsensusError::ScriptErrorWithCode {
667                        code: ScriptErrorCode::UnbalancedConditional,
668                        message: "OP_ELSE without matching IF/NOTIF".into(),
669                    });
670                }
671            }
672            // OP_ENDIF
673            OP_ENDIF => {
674                if control_stack.pop().is_none() {
675                    return Err(ConsensusError::ScriptErrorWithCode {
676                        code: ScriptErrorCode::UnbalancedConditional,
677                        message: "OP_ENDIF without matching IF/NOTIF".into(),
678                    });
679                }
680            }
681            // OP_TOALTSTACK - move top stack item to altstack
682            OP_TOALTSTACK => {
683                if !in_false_branch {
684                    if stack.is_empty() {
685                        return Err(ConsensusError::ScriptErrorWithCode {
686                            code: ScriptErrorCode::InvalidStackOperation,
687                            message: "OP_TOALTSTACK: empty stack".into(),
688                        });
689                    }
690                    altstack.push(stack.pop().unwrap());
691                }
692            }
693            // OP_FROMALTSTACK - move top altstack item to stack
694            OP_FROMALTSTACK => {
695                if !in_false_branch {
696                    if altstack.is_empty() {
697                        return Err(ConsensusError::ScriptErrorWithCode {
698                            code: ScriptErrorCode::InvalidAltstackOperation,
699                            message: "OP_FROMALTSTACK: empty altstack".into(),
700                        });
701                    }
702                    stack.push(altstack.pop().unwrap());
703                }
704            }
705            _ => {
706                if in_false_branch {
707                    i += 1;
708                    continue;
709                }
710
711                if !execute_opcode(opcode, stack, flags, sigversion)? {
712                    return Ok(false);
713                }
714
715                if stack.len() + altstack.len() > MAX_STACK_SIZE {
716                    return Err(make_stack_overflow_error());
717                }
718            }
719        }
720        i += 1;
721    }
722
723    if !control_stack.is_empty() {
724        return Err(ConsensusError::ScriptErrorWithCode {
725            code: ScriptErrorCode::UnbalancedConditional,
726            message: "Unclosed IF/NOTIF block".into(),
727        });
728    }
729
730    // No final stack check — EvalScript behavior (VerifyScript checks stack afterward).
731    Ok(true)
732}
733
734/// VerifyScript: 𝒮𝒞 × 𝒮𝒞 × 𝒲 × ℕ → {true, false}
735///
736/// For scriptSig ss, scriptPubKey spk, witness w, and flags f:
737/// 1. Execute ss on empty stack
738/// 2. Execute spk on resulting stack
739/// 3. If witness present: execute w on stack
740/// 4. Return final stack has exactly one true value
741///
742/// Performance: Pre-allocates stack capacity, caches verification results in production mode
743#[spec_locked("5.2", "VerifyScript")]
744#[cfg_attr(feature = "production", inline(always))]
745#[cfg_attr(not(feature = "production"), inline)]
746pub fn verify_script(
747    script_sig: &ByteString,
748    script_pubkey: &[u8],
749    witness: Option<&ByteString>,
750    flags: u32,
751) -> Result<bool> {
752    // SigVersion is always Base for this API (no tx/witness context)
753    let sigversion = SigVersion::Base;
754
755    #[cfg(feature = "production")]
756    {
757        // Check cache first (unless disabled for benchmarking)
758        if !is_caching_disabled() {
759            let cache_key = compute_script_cache_key(script_sig, script_pubkey, witness, flags);
760            {
761                let cache = get_script_cache().read().unwrap_or_else(|e| e.into_inner());
762                if let Some(&cached_result) = cache.peek(&cache_key) {
763                    return Ok(cached_result);
764                }
765            }
766        }
767
768        // Execute script (cache miss)
769        // Use pooled stack to avoid allocation
770        let mut stack = get_pooled_stack();
771        let cache_key = compute_script_cache_key(script_sig, script_pubkey, witness, flags);
772        let result = {
773            if !eval_script(script_sig, &mut stack, flags, sigversion)? {
774                // Cache negative result (unless disabled)
775                if !is_caching_disabled() {
776                    let mut cache = get_script_cache()
777                        .write()
778                        .unwrap_or_else(|e| e.into_inner());
779                    cache.put(cache_key, false);
780                }
781                false
782            } else if !eval_script(script_pubkey, &mut stack, flags, sigversion)? {
783                if !is_caching_disabled() {
784                    let mut cache = get_script_cache()
785                        .write()
786                        .unwrap_or_else(|e| e.into_inner());
787                    cache.put(cache_key, false);
788                }
789                false
790            } else if let Some(w) = witness {
791                if !eval_script(w, &mut stack, flags, sigversion)? {
792                    if !is_caching_disabled() {
793                        let mut cache = get_script_cache()
794                            .write()
795                            .unwrap_or_else(|e| e.into_inner());
796                        cache.put(cache_key, false);
797                    }
798                    false
799                } else {
800                    let res = !stack.is_empty() && cast_to_bool(&stack[stack.len() - 1]);
801                    if !is_caching_disabled() {
802                        let mut cache = get_script_cache()
803                            .write()
804                            .unwrap_or_else(|e| e.into_inner());
805                        cache.put(cache_key, res);
806                    }
807                    res
808                }
809            } else {
810                let res = !stack.is_empty() && cast_to_bool(&stack[stack.len() - 1]);
811                if !is_caching_disabled() {
812                    let mut cache = get_script_cache()
813                        .write()
814                        .unwrap_or_else(|e| e.into_inner());
815                    cache.put(cache_key, res);
816                }
817                res
818            }
819        };
820
821        // Return stack to pool
822        return_pooled_stack(stack);
823
824        Ok(result)
825    }
826
827    #[cfg(not(feature = "production"))]
828    {
829        // Pre-allocate stack with capacity hint (most scripts use <20 items)
830        let mut stack = Vec::with_capacity(20);
831
832        // Execute scriptSig
833        if !eval_script(script_sig, &mut stack, flags, sigversion)? {
834            return Ok(false);
835        }
836
837        // Execute scriptPubkey
838        if !eval_script(script_pubkey, &mut stack, flags, sigversion)? {
839            return Ok(false);
840        }
841
842        // Execute witness if present
843        if let Some(w) = witness {
844            if !eval_script(w, &mut stack, flags, sigversion)? {
845                return Ok(false);
846            }
847        }
848
849        // Final validation: top of stack must be truthy (Core VerifyScript: CastToBool(stack.back()))
850        Ok(!stack.is_empty() && cast_to_bool(&stack[stack.len() - 1]))
851    }
852}
853
854/// VerifyScript with transaction context for signature verification
855///
856/// This version includes the full transaction context needed for proper
857/// Script verification with transaction context and optional block height.
858///
859/// Pass `block_height` whenever it is known; height-dependent fork rules (CLTV, CSV,
860/// SegWit, Taproot) rely on it to select the correct evaluation path.  Passing `None`
861/// falls back to height `0` inside the engine, which means pre-activation rules are
862/// used — valid only for regtest / test-vector scenarios.
863///
864/// For callers that also need to supply a pre-computed sighash or a specific
865/// `SigVersion`, use [`verify_script_with_context_full`] directly.
866#[spec_locked("5.2", "VerifyScript")]
867#[cfg_attr(feature = "production", inline(always))]
868#[cfg_attr(not(feature = "production"), inline)]
869#[allow(clippy::too_many_arguments)]
870pub fn verify_script_with_context(
871    script_sig: &ByteString,
872    script_pubkey: &[u8],
873    witness: Option<&crate::witness::Witness>,
874    flags: u32,
875    tx: &Transaction,
876    input_index: usize,
877    prevouts: &[TransactionOutput],
878    block_height: Option<u64>,
879    network: crate::types::Network,
880) -> Result<bool> {
881    // Convert prevouts to parallel slices for the optimized API
882    let prevout_values: Vec<i64> = prevouts.iter().map(|p| p.value).collect();
883    let prevout_script_pubkeys: Vec<&[u8]> =
884        prevouts.iter().map(|p| p.script_pubkey.as_ref()).collect();
885
886    verify_script_with_context_full(
887        script_sig,
888        script_pubkey,
889        witness,
890        flags,
891        tx,
892        input_index,
893        &prevout_values,
894        &prevout_script_pubkeys,
895        block_height,
896        None, // median_time_past
897        network,
898        SigVersion::Base, // _sigversion is ignored inside full; SigVersion is inferred from script
899        #[cfg(feature = "production")]
900        None, // schnorr_collector
901        None,             // precomputed_bip143
902        #[cfg(feature = "production")]
903        None, // precomputed_sighash_all
904        #[cfg(feature = "production")]
905        None, // sighash_cache
906        #[cfg(feature = "production")]
907        None, // precomputed_p2pkh_hash
908    )
909}
910
911/// P2PK fast-path. Bare pay-to-pubkey: scriptPubKey is `pubkey` + OP_CHECKSIG, scriptSig is `sig`.
912/// Common in early blocks (coinbase outputs). Returns Some(Ok(bool)) if handled; None to fall back.
913/// Uses full transaction context (height, network) for BIP66 / signature validation.
914///
915/// For the Orange Paper **`VerifyScript`** contract (scriptSig + scriptPubKey + witness + flags),
916/// see [`verify_script`], [`verify_script_with_context`], and [`verify_script_with_context_full`].
917#[cfg(feature = "production")]
918#[allow(clippy::too_many_arguments)]
919pub fn try_verify_p2pk_fast_path(
920    script_sig: &ByteString,
921    script_pubkey: &[u8],
922    flags: u32,
923    tx: &Transaction,
924    input_index: usize,
925    prevout_values: &[i64],
926    prevout_script_pubkeys: &[&[u8]],
927    block_height: Option<u64>,
928    network: crate::types::Network,
929    #[cfg(feature = "production")] sighash_cache: Option<
930        &crate::transaction_hash::SighashMidstateCache,
931    >,
932) -> Option<Result<bool>> {
933    // P2PK scriptPubKey: OP_PUSHBYTES_N + pubkey + OP_CHECKSIG
934    // 35 bytes (compressed: 0x21 + 33) or 67 bytes (uncompressed: 0x41 + 65)
935    let len = script_pubkey.len();
936    if len != 35 && len != 67 {
937        return None;
938    }
939    if script_pubkey[len - 1] != OP_CHECKSIG {
940        return None;
941    }
942    let pubkey_len = len - 2; // exclude push opcode and OP_CHECKSIG
943    if pubkey_len != 33 && pubkey_len != 65 {
944        return None;
945    }
946    if script_pubkey[0] != 0x21 && script_pubkey[0] != 0x41 {
947        return None; // OP_PUSHBYTES_33 or OP_PUSHBYTES_65
948    }
949    let pubkey_bytes = &script_pubkey[1..(len - 1)];
950
951    let signature_bytes = parse_p2pk_script_sig(script_sig.as_ref())?;
952    if signature_bytes.is_empty() {
953        return Some(Ok(false));
954    }
955
956    // Fast-path: P2PK script_pubkey is 35 or 67 bytes; signature push ≥71. Skip serialize+find_and_delete.
957    use crate::transaction_hash::{SighashType, calculate_transaction_sighash_single_input};
958    let sighash_byte = signature_bytes[signature_bytes.len() - 1];
959    let sighash_type = SighashType::from_byte(sighash_byte);
960    let deleted_storage;
961    let script_code: &[u8] = if script_pubkey.len() < 71 {
962        script_pubkey
963    } else {
964        let pattern = serialize_push_data(signature_bytes);
965        deleted_storage = find_and_delete(script_pubkey, &pattern);
966        deleted_storage.as_ref()
967    };
968    let sighash = match calculate_transaction_sighash_single_input(
969        tx,
970        input_index,
971        script_code,
972        prevout_values[input_index],
973        sighash_type,
974        #[cfg(feature = "production")]
975        sighash_cache,
976    ) {
977        Ok(h) => h,
978        Err(e) => return Some(Err(e)),
979    };
980
981    let height = block_height.unwrap_or(0);
982    let is_valid = signature::with_secp_context(|secp| {
983        signature::verify_signature(
984            secp,
985            pubkey_bytes,
986            signature_bytes,
987            &sighash,
988            flags,
989            height,
990            network,
991            SigVersion::Base,
992        )
993    });
994    Some(is_valid)
995}
996
997/// P2PKH fast-path. Returns Some(Ok(bool)) if script is P2PKH and we handled it;
998/// Returns None to fall back to full interpreter.
999#[cfg(feature = "production")]
1000#[allow(clippy::too_many_arguments)]
1001pub fn try_verify_p2pkh_fast_path(
1002    script_sig: &ByteString,
1003    script_pubkey: &[u8],
1004    flags: u32,
1005    tx: &Transaction,
1006    input_index: usize,
1007    prevout_values: &[i64],
1008    prevout_script_pubkeys: &[&[u8]],
1009    block_height: Option<u64>,
1010    network: crate::types::Network,
1011    #[cfg(feature = "production")] precomputed_sighash_all: Option<[u8; 32]>,
1012    #[cfg(feature = "production")] sighash_cache: Option<
1013        &crate::transaction_hash::SighashMidstateCache,
1014    >,
1015    #[cfg(feature = "production")] precomputed_p2pkh_hash: Option<[u8; 20]>,
1016) -> Option<Result<bool>> {
1017    #[cfg(all(feature = "production", feature = "profile"))]
1018    let _t_entry = std::time::Instant::now();
1019    // P2PKH scriptPubKey: 25 bytes = OP_DUP OP_HASH160 PUSH_20_BYTES <20 bytes> OP_EQUALVERIFY OP_CHECKSIG
1020    if script_pubkey.len() != 25 {
1021        return None;
1022    }
1023    if script_pubkey[0] != OP_DUP
1024        || script_pubkey[1] != OP_HASH160
1025        || script_pubkey[2] != PUSH_20_BYTES
1026        || script_pubkey[23] != OP_EQUALVERIFY
1027        || script_pubkey[24] != OP_CHECKSIG
1028    {
1029        return None;
1030    }
1031    let expected_hash = &script_pubkey[3..23];
1032
1033    #[cfg(all(feature = "production", feature = "profile"))]
1034    crate::script_profile::add_p2pkh_fast_path_entry_ns(_t_entry.elapsed().as_nanos() as u64);
1035    #[cfg(all(feature = "production", feature = "profile"))]
1036    let _t_parse = std::time::Instant::now();
1037    let (signature_bytes, pubkey_bytes) = parse_p2pkh_script_sig(script_sig.as_ref())?;
1038    #[cfg(all(feature = "production", feature = "profile"))]
1039    crate::script_profile::add_p2pkh_parse_ns(_t_parse.elapsed().as_nanos() as u64);
1040
1041    // Pubkey must be 33 (compressed) or 65 (uncompressed) bytes
1042    if pubkey_bytes.len() != 33 && pubkey_bytes.len() != 65 {
1043        return Some(Ok(false));
1044    }
1045    // Empty signature is valid script but leaves 0 on stack -> verification false
1046    if signature_bytes.is_empty() {
1047        return Some(Ok(false));
1048    }
1049
1050    // HASH160(pubkey) == expected hash (or use precomputed when provided by batch path)
1051    let pubkey_hash: [u8; 20] = match precomputed_p2pkh_hash {
1052        Some(h) => h,
1053        None => {
1054            #[cfg(all(feature = "production", feature = "profile"))]
1055            let _t_hash = std::time::Instant::now();
1056            let sha256_hash = OptimizedSha256::new().hash(pubkey_bytes);
1057            let h = Ripemd160::digest(sha256_hash);
1058            #[cfg(all(feature = "production", feature = "profile"))]
1059            crate::script_profile::add_p2pkh_hash160_ns(_t_hash.elapsed().as_nanos() as u64);
1060            h.into()
1061        }
1062    };
1063    if &pubkey_hash[..] != expected_hash {
1064        return Some(Ok(false));
1065    }
1066
1067    // Legacy sighash: scriptCode = FindAndDelete(script_pubkey, serialize(signature))
1068    // Roadmap #12: use precomputed when available (batch sighash for P2PKH).
1069    use crate::transaction_hash::SighashType;
1070    let sighash_byte = signature_bytes[signature_bytes.len() - 1];
1071    let sighash_type = SighashType::from_byte(sighash_byte);
1072    let deleted_storage;
1073    let script_code: &[u8] = if script_pubkey.len() < 71 {
1074        script_pubkey
1075    } else {
1076        let pattern = serialize_push_data(signature_bytes);
1077        deleted_storage = find_and_delete(script_pubkey, &pattern);
1078        deleted_storage.as_ref()
1079    };
1080    let sighash = {
1081        #[cfg(feature = "production")]
1082        {
1083            if let Some(precomp) = precomputed_sighash_all {
1084                precomp
1085            } else {
1086                crate::transaction_hash::compute_legacy_sighash_nocache(
1087                    tx,
1088                    input_index,
1089                    script_code,
1090                    sighash_byte,
1091                )
1092            }
1093        }
1094        #[cfg(not(feature = "production"))]
1095        {
1096            match calculate_transaction_sighash_single_input(
1097                tx,
1098                input_index,
1099                script_code,
1100                prevout_values[input_index],
1101                sighash_type,
1102            ) {
1103                Ok(h) => h,
1104                Err(e) => return Some(Err(e)),
1105            }
1106        }
1107    };
1108
1109    #[cfg(all(feature = "production", feature = "profile"))]
1110    let _t_secp = std::time::Instant::now();
1111    let height = block_height.unwrap_or(0);
1112    let is_valid: Result<bool> = {
1113        let der_sig = &signature_bytes[..signature_bytes.len() - 1];
1114        if flags & 0x04 != 0
1115            && !crate::bip_validation::check_bip66_network(signature_bytes, height, network)
1116                .unwrap_or(false)
1117        {
1118            Ok(false)
1119        } else if flags & 0x02 != 0 {
1120            let base_sighash = sighash_byte & !0x80;
1121            if !(0x01..=0x03).contains(&base_sighash) {
1122                Ok(false)
1123            } else if pubkey_bytes.len() == 33 {
1124                if pubkey_bytes[0] != 0x02 && pubkey_bytes[0] != 0x03 {
1125                    Ok(false)
1126                } else {
1127                    let strict_der = flags & 0x04 != 0;
1128                    let enforce_low_s = flags & 0x08 != 0;
1129                    Ok(crate::secp256k1_backend::verify_ecdsa_direct(
1130                        der_sig,
1131                        pubkey_bytes,
1132                        &sighash,
1133                        strict_der,
1134                        enforce_low_s,
1135                    )
1136                    .unwrap_or(false))
1137                }
1138            } else if pubkey_bytes.len() == 65 && pubkey_bytes[0] == 0x04 {
1139                let strict_der = flags & 0x04 != 0;
1140                let enforce_low_s = flags & 0x08 != 0;
1141                Ok(crate::secp256k1_backend::verify_ecdsa_direct(
1142                    der_sig,
1143                    pubkey_bytes,
1144                    &sighash,
1145                    strict_der,
1146                    enforce_low_s,
1147                )
1148                .unwrap_or(false))
1149            } else {
1150                Ok(false)
1151            }
1152        } else {
1153            let strict_der = flags & 0x04 != 0;
1154            let enforce_low_s = flags & 0x08 != 0;
1155            Ok(crate::secp256k1_backend::verify_ecdsa_direct(
1156                der_sig,
1157                pubkey_bytes,
1158                &sighash,
1159                strict_der,
1160                enforce_low_s,
1161            )
1162            .unwrap_or(false))
1163        }
1164    };
1165    #[cfg(all(feature = "production", feature = "profile"))]
1166    {
1167        let ns = _t_secp.elapsed().as_nanos() as u64;
1168        crate::script_profile::add_p2pkh_collect_ns(ns);
1169        crate::script_profile::add_p2pkh_secp_context_ns(ns);
1170    }
1171    Some(is_valid)
1172}
1173
1174/// Fully inlined P2PKH verification for the rayon fast path.
1175/// Caller MUST have already verified script_pubkey is a valid P2PKH (25 bytes, correct opcodes).
1176/// Eliminates: redundant pattern check, Option unwrapping for precomputed values (always None),
1177/// assumevalid lookup, sighash cache overhead.
1178/// Returns Ok(true/false) directly — no Option wrapping.
1179#[cfg(feature = "production")]
1180#[inline]
1181pub fn verify_p2pkh_inline(
1182    script_sig: &[u8],
1183    script_pubkey: &[u8],
1184    flags: u32,
1185    tx: &Transaction,
1186    input_index: usize,
1187    height: u64,
1188    network: crate::types::Network,
1189    precomputed_sighash_all: Option<[u8; 32]>,
1190) -> Result<bool> {
1191    #[cfg(feature = "profile")]
1192    let _t0 = std::time::Instant::now();
1193
1194    let expected_hash = &script_pubkey[3..23];
1195
1196    let (signature_bytes, pubkey_bytes) = match parse_p2pkh_script_sig(script_sig) {
1197        Some(pair) => pair,
1198        None => return Ok(false),
1199    };
1200
1201    if (pubkey_bytes.len() != 33 && pubkey_bytes.len() != 65) || signature_bytes.is_empty() {
1202        return Ok(false);
1203    }
1204
1205    #[cfg(feature = "profile")]
1206    let _t_hash = std::time::Instant::now();
1207
1208    let sha256_hash = OptimizedSha256::new().hash(pubkey_bytes);
1209    let pubkey_hash: [u8; 20] = Ripemd160::digest(sha256_hash).into();
1210    if &pubkey_hash[..] != expected_hash {
1211        return Ok(false);
1212    }
1213
1214    #[cfg(feature = "profile")]
1215    crate::script_profile::add_p2pkh_hash160_ns(_t_hash.elapsed().as_nanos() as u64);
1216
1217    #[cfg(feature = "profile")]
1218    let _t_sighash = std::time::Instant::now();
1219
1220    let sighash_byte = signature_bytes[signature_bytes.len() - 1];
1221    let sighash = if let Some(precomp) = precomputed_sighash_all {
1222        precomp
1223    } else {
1224        crate::transaction_hash::compute_legacy_sighash_buffered(
1225            tx,
1226            input_index,
1227            script_pubkey,
1228            sighash_byte,
1229        )
1230    };
1231
1232    #[cfg(feature = "profile")]
1233    crate::script_profile::add_sighash_ns(_t_sighash.elapsed().as_nanos() as u64);
1234
1235    let der_sig = &signature_bytes[..signature_bytes.len() - 1];
1236    let strict_der = flags & 0x04 != 0;
1237    let enforce_low_s = flags & 0x08 != 0;
1238
1239    if strict_der
1240        && !crate::bip_validation::check_bip66_network(signature_bytes, height, network)
1241            .unwrap_or(false)
1242    {
1243        return Ok(false);
1244    }
1245
1246    if flags & 0x02 != 0 {
1247        let sighash_base = sighash_byte & !0x80;
1248        if !(0x01..=0x03).contains(&sighash_base) {
1249            return Ok(false);
1250        }
1251        match pubkey_bytes.len() {
1252            33 if pubkey_bytes[0] != 0x02 && pubkey_bytes[0] != 0x03 => return Ok(false),
1253            65 if pubkey_bytes[0] != 0x04 => return Ok(false),
1254            33 | 65 => {}
1255            _ => return Ok(false),
1256        }
1257    }
1258
1259    #[cfg(feature = "profile")]
1260    let _t_secp = std::time::Instant::now();
1261
1262    let result = crate::secp256k1_backend::verify_ecdsa_direct(
1263        der_sig,
1264        pubkey_bytes,
1265        &sighash,
1266        strict_der,
1267        enforce_low_s,
1268    )
1269    .unwrap_or(false);
1270
1271    #[cfg(feature = "profile")]
1272    crate::script_profile::add_p2pkh_secp_context_ns(_t_secp.elapsed().as_nanos() as u64);
1273
1274    #[cfg(feature = "profile")]
1275    crate::script_profile::add_p2pkh_fast_path_entry_ns(_t0.elapsed().as_nanos() as u64);
1276
1277    Ok(result)
1278}
1279
1280/// P2PK (Pay-to-Public-Key) inline verify.
1281#[cfg(feature = "production")]
1282#[inline]
1283pub fn verify_p2pk_inline(
1284    script_sig: &[u8],
1285    script_pubkey: &[u8],
1286    flags: u32,
1287    tx: &Transaction,
1288    input_index: usize,
1289    height: u64,
1290    network: crate::types::Network,
1291) -> Result<bool> {
1292    let pk_len = script_pubkey.len() - 2; // 33 or 65
1293    let pubkey_bytes = &script_pubkey[1..1 + pk_len];
1294
1295    let signature_bytes = match parse_p2pk_script_sig(script_sig) {
1296        Some(s) => s,
1297        None => return Ok(false),
1298    };
1299    if signature_bytes.is_empty() {
1300        return Ok(false);
1301    }
1302
1303    let sighash_byte = signature_bytes[signature_bytes.len() - 1];
1304    let script_code: &[u8] = script_pubkey; // P2PK scriptPubKey < 71 bytes, no FindAndDelete
1305
1306    let sighash = crate::transaction_hash::compute_legacy_sighash_buffered(
1307        tx,
1308        input_index,
1309        script_code,
1310        sighash_byte,
1311    );
1312
1313    let der_sig = &signature_bytes[..signature_bytes.len() - 1];
1314    let strict_der = flags & 0x04 != 0;
1315    let enforce_low_s = flags & 0x08 != 0;
1316
1317    if strict_der
1318        && !crate::bip_validation::check_bip66_network(signature_bytes, height, network)
1319            .unwrap_or(false)
1320    {
1321        return Ok(false);
1322    }
1323
1324    if flags & 0x02 != 0 {
1325        let sighash_base = sighash_byte & !0x80;
1326        if !(0x01..=0x03).contains(&sighash_base) {
1327            return Ok(false);
1328        }
1329        match pubkey_bytes.len() {
1330            33 if pubkey_bytes[0] != 0x02 && pubkey_bytes[0] != 0x03 => return Ok(false),
1331            65 if pubkey_bytes[0] != 0x04 => return Ok(false),
1332            33 | 65 => {}
1333            _ => return Ok(false),
1334        }
1335    }
1336
1337    Ok(crate::secp256k1_backend::verify_ecdsa_direct(
1338        der_sig,
1339        pubkey_bytes,
1340        &sighash,
1341        strict_der,
1342        enforce_low_s,
1343    )
1344    .unwrap_or(false))
1345}
1346
1347/// P2SH-multisig fast path: when redeem script matches `OP_m <pubkeys> OP_n OP_CHECKMULTISIG`,
1348/// verify each (sig, pubkey, sighash) inline via ecdsa::verify(), avoiding the interpreter.
1349/// Returns Some(Ok(true/false)) if we handled it, None to fall through to interpreter.
1350#[allow(clippy::too_many_arguments)]
1351fn try_verify_p2sh_multisig_fast_path(
1352    script_sig: &ByteString,
1353    script_pubkey: &[u8],
1354    flags: u32,
1355    tx: &Transaction,
1356    input_index: usize,
1357    prevout_values: &[i64],
1358    prevout_script_pubkeys: &[&[u8]],
1359    block_height: Option<u64>,
1360    network: crate::types::Network,
1361    #[cfg(feature = "production")] sighash_cache: Option<
1362        &crate::transaction_hash::SighashMidstateCache,
1363    >,
1364) -> Option<Result<bool>> {
1365    let pushes = parse_p2sh_script_sig_pushes(script_sig.as_ref())?;
1366    if pushes.len() < 2 {
1367        return None;
1368    }
1369    let redeem = pushes.last().expect("at least 2 pushes").as_ref();
1370    let expected_hash = &script_pubkey[2..22];
1371    let sha256_hash = OptimizedSha256::new().hash(redeem);
1372    let redeem_hash = Ripemd160::digest(sha256_hash);
1373    if &redeem_hash[..] != expected_hash {
1374        return Some(Ok(false));
1375    }
1376    let (m, _n, pubkeys) = parse_redeem_multisig(redeem)?;
1377    let signatures: Vec<&[u8]> = pushes
1378        .iter()
1379        .take(pushes.len() - 1)
1380        .skip(1)
1381        .map(|e| e.as_ref())
1382        .collect();
1383    let dummy = pushes.first().expect("at least 2 pushes").as_ref();
1384
1385    const SCRIPT_VERIFY_NULLDUMMY: u32 = 0x10;
1386    const SCRIPT_VERIFY_NULLFAIL: u32 = 0x4000;
1387    let height = block_height.unwrap_or(0);
1388    if (flags & SCRIPT_VERIFY_NULLDUMMY) != 0 {
1389        let activation = match network {
1390            crate::types::Network::Mainnet => crate::constants::BIP147_ACTIVATION_MAINNET,
1391            crate::types::Network::Testnet => crate::constants::BIP147_ACTIVATION_TESTNET,
1392            crate::types::Network::Regtest | crate::types::Network::Signet => 0,
1393        };
1394        if height >= activation && !dummy.is_empty() && dummy != [0x00] {
1395            return Some(Ok(false));
1396        }
1397    }
1398
1399    let mut cleaned = redeem.to_vec();
1400    for sig in &signatures {
1401        if !sig.is_empty() {
1402            let pattern = serialize_push_data(sig);
1403            cleaned = find_and_delete(&cleaned, &pattern).into_owned();
1404        }
1405    }
1406
1407    use crate::transaction_hash::{SighashType, calculate_transaction_sighash_single_input};
1408
1409    let mut sig_index = 0;
1410    let mut valid_sigs = 0u8;
1411
1412    for pubkey_bytes in pubkeys {
1413        if sig_index >= signatures.len() {
1414            break;
1415        }
1416        while sig_index < signatures.len() && signatures[sig_index].is_empty() {
1417            sig_index += 1;
1418        }
1419        if sig_index >= signatures.len() {
1420            break;
1421        }
1422        let signature_bytes = &signatures[sig_index];
1423        let sighash_byte = signature_bytes[signature_bytes.len() - 1];
1424        let sighash_type = SighashType::from_byte(sighash_byte);
1425        let sighash = match calculate_transaction_sighash_single_input(
1426            tx,
1427            input_index,
1428            &cleaned,
1429            prevout_values[input_index],
1430            sighash_type,
1431            #[cfg(feature = "production")]
1432            sighash_cache,
1433        ) {
1434            Ok(h) => h,
1435            Err(e) => return Some(Err(e)),
1436        };
1437
1438        #[cfg(feature = "production")]
1439        let is_valid = signature::with_secp_context(|secp| {
1440            signature::verify_signature(
1441                secp,
1442                pubkey_bytes,
1443                signature_bytes,
1444                &sighash,
1445                flags,
1446                height,
1447                network,
1448                SigVersion::Base,
1449            )
1450        });
1451
1452        #[cfg(not(feature = "production"))]
1453        let is_valid = {
1454            let secp = signature::new_secp();
1455            signature::verify_signature(
1456                &secp,
1457                pubkey_bytes,
1458                signature_bytes,
1459                &sighash,
1460                flags,
1461                height,
1462                network,
1463                SigVersion::Base,
1464            )
1465        };
1466
1467        let is_valid = match is_valid {
1468            Ok(v) => v,
1469            Err(e) => return Some(Err(e)),
1470        };
1471
1472        if is_valid {
1473            valid_sigs += 1;
1474            sig_index += 1;
1475        }
1476    }
1477
1478    if (flags & SCRIPT_VERIFY_NULLFAIL) != 0 {
1479        for sig_bytes in &signatures[sig_index..] {
1480            if !sig_bytes.is_empty() {
1481                return Some(Err(ConsensusError::ScriptErrorWithCode {
1482                    code: ScriptErrorCode::SigNullFail,
1483                    message: "OP_CHECKMULTISIG: non-null signature must not fail under NULLFAIL"
1484                        .into(),
1485                }));
1486            }
1487        }
1488    }
1489
1490    Some(Ok(valid_sigs >= m))
1491}
1492
1493/// Bare multisig fast path: scriptPubKey is `OP_n <pubkeys> OP_m OP_CHECKMULTISIG` directly.
1494/// No P2SH wrapper; scriptSig is \[dummy, sig_1, ..., sig_m\]. Same verification as P2SH multisig.
1495#[allow(clippy::too_many_arguments)]
1496fn try_verify_bare_multisig_fast_path(
1497    script_sig: &ByteString,
1498    script_pubkey: &[u8],
1499    flags: u32,
1500    tx: &Transaction,
1501    input_index: usize,
1502    prevout_values: &[i64],
1503    prevout_script_pubkeys: &[&[u8]],
1504    block_height: Option<u64>,
1505    network: crate::types::Network,
1506    #[cfg(feature = "production")] sighash_cache: Option<
1507        &crate::transaction_hash::SighashMidstateCache,
1508    >,
1509) -> Option<Result<bool>> {
1510    let (m, _n, pubkeys) = parse_redeem_multisig(script_pubkey)?;
1511    let pushes = parse_p2sh_script_sig_pushes(script_sig.as_ref())?;
1512    if pushes.len() < 2 {
1513        return None;
1514    }
1515    let dummy = pushes.first().expect("at least 2 pushes").as_ref();
1516    let signatures: Vec<&[u8]> = pushes[1..].iter().map(|e| e.as_ref()).collect();
1517
1518    const SCRIPT_VERIFY_NULLDUMMY: u32 = 0x10;
1519    const SCRIPT_VERIFY_NULLFAIL: u32 = 0x4000;
1520    let height = block_height.unwrap_or(0);
1521    if (flags & SCRIPT_VERIFY_NULLDUMMY) != 0 {
1522        let activation = match network {
1523            crate::types::Network::Mainnet => crate::constants::BIP147_ACTIVATION_MAINNET,
1524            crate::types::Network::Testnet => crate::constants::BIP147_ACTIVATION_TESTNET,
1525            crate::types::Network::Regtest | crate::types::Network::Signet => 0,
1526        };
1527        if height >= activation && !dummy.is_empty() && dummy != [0x00] {
1528            return Some(Ok(false));
1529        }
1530    }
1531
1532    let mut cleaned = script_pubkey.to_vec();
1533    for sig in &signatures {
1534        if !sig.is_empty() {
1535            let pattern = serialize_push_data(sig);
1536            cleaned = find_and_delete(&cleaned, &pattern).into_owned();
1537        }
1538    }
1539
1540    use crate::transaction_hash::{SighashType, calculate_transaction_sighash_single_input};
1541
1542    let mut sig_index = 0;
1543    let mut valid_sigs = 0u8;
1544
1545    for pubkey_bytes in pubkeys {
1546        if sig_index >= signatures.len() {
1547            break;
1548        }
1549        while sig_index < signatures.len() && signatures[sig_index].is_empty() {
1550            sig_index += 1;
1551        }
1552        if sig_index >= signatures.len() {
1553            break;
1554        }
1555        let signature_bytes = &signatures[sig_index];
1556        let sighash_byte = signature_bytes[signature_bytes.len() - 1];
1557        let sighash_type = SighashType::from_byte(sighash_byte);
1558        let sighash = match calculate_transaction_sighash_single_input(
1559            tx,
1560            input_index,
1561            &cleaned,
1562            prevout_values[input_index],
1563            sighash_type,
1564            #[cfg(feature = "production")]
1565            sighash_cache,
1566        ) {
1567            Ok(h) => h,
1568            Err(e) => return Some(Err(e)),
1569        };
1570
1571        #[cfg(feature = "production")]
1572        let is_valid = signature::with_secp_context(|secp| {
1573            signature::verify_signature(
1574                secp,
1575                pubkey_bytes,
1576                signature_bytes,
1577                &sighash,
1578                flags,
1579                height,
1580                network,
1581                SigVersion::Base,
1582            )
1583        });
1584
1585        #[cfg(not(feature = "production"))]
1586        let is_valid = {
1587            let secp = signature::new_secp();
1588            signature::verify_signature(
1589                &secp,
1590                pubkey_bytes,
1591                signature_bytes,
1592                &sighash,
1593                flags,
1594                height,
1595                network,
1596                SigVersion::Base,
1597            )
1598        };
1599
1600        let is_valid = match is_valid {
1601            Ok(v) => v,
1602            Err(e) => return Some(Err(e)),
1603        };
1604
1605        if is_valid {
1606            valid_sigs += 1;
1607            sig_index += 1;
1608        }
1609    }
1610
1611    if (flags & SCRIPT_VERIFY_NULLFAIL) != 0 {
1612        for sig_bytes in &signatures[sig_index..] {
1613            if !sig_bytes.is_empty() {
1614                return Some(Err(ConsensusError::ScriptErrorWithCode {
1615                    code: ScriptErrorCode::SigNullFail,
1616                    message: "OP_CHECKMULTISIG: non-null signature must not fail under NULLFAIL"
1617                        .into(),
1618                }));
1619            }
1620        }
1621    }
1622
1623    Some(Ok(valid_sigs >= m))
1624}
1625
1626/// P2SH fast-path for regular P2SH (redeem script is not a witness program).
1627/// Skips scriptSig + scriptPubKey interpreter run; verifies hash then runs redeem script only.
1628/// Returns None to fall back to full interpreter (e.g. witness programs, non-P2SH).
1629#[cfg(feature = "production")]
1630#[allow(clippy::too_many_arguments)]
1631fn try_verify_p2sh_fast_path(
1632    script_sig: &ByteString,
1633    script_pubkey: &[u8],
1634    flags: u32,
1635    tx: &Transaction,
1636    input_index: usize,
1637    prevout_values: &[i64],
1638    prevout_script_pubkeys: &[&[u8]],
1639    block_height: Option<u64>,
1640    median_time_past: Option<u64>,
1641    network: crate::types::Network,
1642    #[cfg(feature = "production")] sighash_cache: Option<
1643        &crate::transaction_hash::SighashMidstateCache,
1644    >,
1645    #[cfg(feature = "production")] precomputed_sighash_all: Option<[u8; 32]>,
1646) -> Option<Result<bool>> {
1647    const SCRIPT_VERIFY_P2SH: u32 = 0x01;
1648    if (flags & SCRIPT_VERIFY_P2SH) == 0 {
1649        return None;
1650    }
1651    // P2SH scriptPubKey: 23 bytes = OP_HASH160 PUSH_20_BYTES <20 bytes> OP_EQUAL
1652    if script_pubkey.len() != 23
1653        || script_pubkey[0] != OP_HASH160
1654        || script_pubkey[1] != PUSH_20_BYTES
1655        || script_pubkey[22] != OP_EQUAL
1656    {
1657        return None;
1658    }
1659    let expected_hash = &script_pubkey[2..22];
1660
1661    let mut pushes = parse_script_sig_push_only(script_sig.as_ref())?;
1662    if pushes.is_empty() {
1663        return None;
1664    }
1665    let redeem = pushes.pop().expect("at least one push");
1666    let mut stack = pushes;
1667
1668    // Redeem must not be a witness program (P2WPKH-in-P2SH / P2WSH-in-P2SH use witness; we don't handle here)
1669    if redeem.len() >= 3
1670        && redeem[0] == OP_0
1671        && ((redeem[1] == PUSH_20_BYTES && redeem.len() == 22)
1672            || (redeem[1] == PUSH_32_BYTES && redeem.len() == 34))
1673    {
1674        return None;
1675    }
1676
1677    // HASH160(redeem) == expected hash
1678    let sha256_hash = OptimizedSha256::new().hash(redeem.as_ref());
1679    let redeem_hash = Ripemd160::digest(sha256_hash);
1680    if &redeem_hash[..] != expected_hash {
1681        return Some(Ok(false));
1682    }
1683
1684    // P2SH-with-P2PKH-redeem fast-path: skip interpreter when redeem is P2PKH
1685    if redeem.len() == 25
1686        && redeem[0] == OP_DUP
1687        && redeem[1] == OP_HASH160
1688        && redeem[2] == PUSH_20_BYTES
1689        && redeem[23] == OP_EQUALVERIFY
1690        && redeem[24] == OP_CHECKSIG
1691        && stack.len() == 2
1692    {
1693        let signature_bytes = &stack[0];
1694        let pubkey_bytes = &stack[1];
1695        if (pubkey_bytes.len() == 33 || pubkey_bytes.len() == 65) && !signature_bytes.is_empty() {
1696            let expected_pubkey_hash = &redeem[3..23];
1697            let sha256_hash = OptimizedSha256::new().hash(pubkey_bytes);
1698            let pubkey_hash = Ripemd160::digest(sha256_hash);
1699            if &pubkey_hash[..] == expected_pubkey_hash {
1700                #[cfg(feature = "production")]
1701                let sighash = if let Some(precomp) = precomputed_sighash_all {
1702                    precomp
1703                } else {
1704                    use crate::transaction_hash::{
1705                        SighashType, calculate_transaction_sighash_single_input,
1706                    };
1707                    let sighash_byte = signature_bytes[signature_bytes.len() - 1];
1708                    let sighash_type = SighashType::from_byte(sighash_byte);
1709                    let deleted_storage;
1710                    let script_code: &[u8] = if redeem.len() < 71 {
1711                        redeem.as_ref()
1712                    } else {
1713                        let pattern = serialize_push_data(signature_bytes);
1714                        deleted_storage = find_and_delete(redeem.as_ref(), &pattern);
1715                        deleted_storage.as_ref()
1716                    };
1717                    match calculate_transaction_sighash_single_input(
1718                        tx,
1719                        input_index,
1720                        script_code,
1721                        prevout_values[input_index],
1722                        sighash_type,
1723                        sighash_cache,
1724                    ) {
1725                        Ok(h) => h,
1726                        Err(e) => return Some(Err(e)),
1727                    }
1728                };
1729                #[cfg(not(feature = "production"))]
1730                let sighash = {
1731                    use crate::transaction_hash::{
1732                        SighashType, calculate_transaction_sighash_single_input,
1733                    };
1734                    let sighash_byte = signature_bytes[signature_bytes.len() - 1];
1735                    let sighash_type = SighashType::from_byte(sighash_byte);
1736                    let deleted_storage;
1737                    let script_code: &[u8] = if redeem.len() < 71 {
1738                        redeem.as_ref()
1739                    } else {
1740                        let pattern = serialize_push_data(signature_bytes);
1741                        deleted_storage = find_and_delete(redeem.as_ref(), &pattern);
1742                        deleted_storage.as_ref()
1743                    };
1744                    match calculate_transaction_sighash_single_input(
1745                        tx,
1746                        input_index,
1747                        script_code,
1748                        prevout_values[input_index],
1749                        sighash_type,
1750                    ) {
1751                        Ok(h) => h,
1752                        Err(e) => return Some(Err(e)),
1753                    }
1754                };
1755                let height = block_height.unwrap_or(0);
1756                let is_valid = signature::with_secp_context(|secp| {
1757                    signature::verify_signature(
1758                        secp,
1759                        pubkey_bytes,
1760                        signature_bytes,
1761                        &sighash,
1762                        flags,
1763                        height,
1764                        network,
1765                        SigVersion::Base,
1766                    )
1767                });
1768                return Some(is_valid);
1769            }
1770        }
1771    }
1772
1773    // P2SH-with-P2PK-redeem fast-path: redeem = OP_PUSHBYTES_N + pubkey + OP_CHECKSIG, stack = [sig]
1774    if (redeem.len() == 35 || redeem.len() == 67)
1775        && redeem[redeem.len() - 1] == OP_CHECKSIG
1776        && (redeem[0] == 0x21 || redeem[0] == 0x41)
1777        && stack.len() == 1
1778    {
1779        let pubkey_len = redeem.len() - 2;
1780        if pubkey_len == 33 || pubkey_len == 65 {
1781            let pubkey_bytes = &redeem.as_ref()[1..(redeem.len() - 1)];
1782            let signature_bytes = &stack[0];
1783            if !signature_bytes.is_empty() {
1784                use crate::transaction_hash::{
1785                    SighashType, calculate_transaction_sighash_single_input,
1786                };
1787                let sighash_byte = signature_bytes[signature_bytes.len() - 1];
1788                let sighash_type = SighashType::from_byte(sighash_byte);
1789                // Fast-path: redeem is 35 or 67 bytes; signature push ≥71. FindAndDelete no-op.
1790                let deleted_storage;
1791                let script_code: &[u8] = if redeem.len() < 71 {
1792                    redeem.as_ref()
1793                } else {
1794                    let pattern = serialize_push_data(signature_bytes);
1795                    deleted_storage = find_and_delete(redeem.as_ref(), &pattern);
1796                    deleted_storage.as_ref()
1797                };
1798                match calculate_transaction_sighash_single_input(
1799                    tx,
1800                    input_index,
1801                    script_code,
1802                    prevout_values[input_index],
1803                    sighash_type,
1804                    #[cfg(feature = "production")]
1805                    sighash_cache,
1806                ) {
1807                    Ok(sighash) => {
1808                        let height = block_height.unwrap_or(0);
1809                        let is_valid = signature::with_secp_context(|secp| {
1810                            signature::verify_signature(
1811                                secp,
1812                                pubkey_bytes,
1813                                signature_bytes,
1814                                &sighash,
1815                                flags,
1816                                height,
1817                                network,
1818                                SigVersion::Base,
1819                            )
1820                        });
1821                        return Some(is_valid);
1822                    }
1823                    Err(e) => return Some(Err(e)),
1824                }
1825            }
1826        }
1827    }
1828
1829    // Execute redeem script with remaining stack (same as regular P2SH path: no batch collector)
1830    let result = eval_script_with_context_full_inner(
1831        &redeem,
1832        &mut stack,
1833        flags,
1834        tx,
1835        input_index,
1836        prevout_values,
1837        prevout_script_pubkeys,
1838        block_height,
1839        median_time_past,
1840        network,
1841        SigVersion::Base,
1842        Some(redeem.as_ref()),
1843        None, // script_sig_for_sighash (P2SH redeem context)
1844        None, // taproot_annex_hash
1845        #[cfg(feature = "production")]
1846        None, // schnorr_collector
1847        None, // precomputed_bip143 - Base sigversion
1848        #[cfg(feature = "production")]
1849        sighash_cache,
1850    );
1851    Some(result)
1852}
1853
1854/// P2WPKH fast-path (SegWit P2PKH). ScriptPubKey OP_0 <20-byte-hash>, witness [sig, pubkey].
1855/// Uses BIP143 sighash; skips interpreter. Returns None to fall back to full path.
1856#[cfg(feature = "production")]
1857#[allow(clippy::too_many_arguments)]
1858fn try_verify_p2wpkh_fast_path(
1859    script_sig: &ByteString,
1860    script_pubkey: &[u8],
1861    witness: &crate::witness::Witness,
1862    flags: u32,
1863    tx: &Transaction,
1864    input_index: usize,
1865    prevout_values: &[i64],
1866    prevout_script_pubkeys: &[&[u8]],
1867    block_height: Option<u64>,
1868    network: crate::types::Network,
1869    precomputed_bip143: Option<&crate::transaction_hash::Bip143PrecomputedHashes>,
1870    #[cfg(feature = "production")] precomputed_sighash_all: Option<[u8; 32]>,
1871) -> Option<Result<bool>> {
1872    // P2WPKH: 22 bytes = OP_0 PUSH_20_BYTES <20-byte-hash>
1873    if script_pubkey.len() != 22 || script_pubkey[0] != OP_0 || script_pubkey[1] != PUSH_20_BYTES {
1874        return None;
1875    }
1876    // Native SegWit: scriptSig must be empty
1877    if !script_sig.is_empty() {
1878        return None;
1879    }
1880    if witness.len() != 2 {
1881        return None;
1882    }
1883    let signature_bytes = &witness[0];
1884    let pubkey_bytes = &witness[1];
1885
1886    if pubkey_bytes.len() != 33 && pubkey_bytes.len() != 65 {
1887        return Some(Ok(false));
1888    }
1889    if signature_bytes.is_empty() {
1890        return Some(Ok(false));
1891    }
1892
1893    let expected_hash = &script_pubkey[2..22];
1894    let sha256_hash = OptimizedSha256::new().hash(pubkey_bytes);
1895    let pubkey_hash = Ripemd160::digest(sha256_hash);
1896    if &pubkey_hash[..] != expected_hash {
1897        return Some(Ok(false));
1898    }
1899
1900    // BIP143 §4.3: for P2WPKH the scriptCode is the P2PKH expansion, not the witness program.
1901    let p2pkh_script_code = bip143_p2wpkh_script_code(expected_hash);
1902
1903    let sighash_byte = signature_bytes[signature_bytes.len() - 1];
1904    let sighash = if sighash_byte == 0x01 {
1905        // Roadmap #12: use precomputed SIGHASH_ALL when available
1906        #[cfg(feature = "production")]
1907        if let Some(precomp) = precomputed_sighash_all {
1908            precomp
1909        } else {
1910            let amount = prevout_values.get(input_index).copied().unwrap_or(0);
1911            match crate::transaction_hash::calculate_bip143_sighash(
1912                tx,
1913                input_index,
1914                &p2pkh_script_code,
1915                amount,
1916                sighash_byte,
1917                precomputed_bip143,
1918            ) {
1919                Ok(h) => h,
1920                Err(e) => return Some(Err(e)),
1921            }
1922        }
1923        #[cfg(not(feature = "production"))]
1924        {
1925            let amount = prevout_values.get(input_index).copied().unwrap_or(0);
1926            match crate::transaction_hash::calculate_bip143_sighash(
1927                tx,
1928                input_index,
1929                &p2pkh_script_code,
1930                amount,
1931                sighash_byte,
1932                precomputed_bip143,
1933            ) {
1934                Ok(h) => h,
1935                Err(e) => return Some(Err(e)),
1936            }
1937        }
1938    } else {
1939        let amount = prevout_values.get(input_index).copied().unwrap_or(0);
1940        match crate::transaction_hash::calculate_bip143_sighash(
1941            tx,
1942            input_index,
1943            &p2pkh_script_code,
1944            amount,
1945            sighash_byte,
1946            precomputed_bip143,
1947        ) {
1948            Ok(h) => h,
1949            Err(e) => return Some(Err(e)),
1950        }
1951    };
1952
1953    let height = block_height.unwrap_or(0);
1954    let is_valid = signature::with_secp_context(|secp| {
1955        signature::verify_signature(
1956            secp,
1957            pubkey_bytes,
1958            signature_bytes,
1959            &sighash,
1960            flags,
1961            height,
1962            network,
1963            SigVersion::WitnessV0,
1964        )
1965    });
1966    Some(is_valid)
1967}
1968
1969/// P2WPKH-in-P2SH (nested SegWit). ScriptPubKey P2SH, scriptSig = \[redeem\], redeem = OP_0 + 20-byte pubkey hash, witness = \[sig, pubkey\].
1970#[cfg(feature = "production")]
1971#[allow(clippy::too_many_arguments)]
1972fn try_verify_p2wpkh_in_p2sh_fast_path(
1973    script_sig: &ByteString,
1974    script_pubkey: &[u8],
1975    witness: &crate::witness::Witness,
1976    flags: u32,
1977    tx: &Transaction,
1978    input_index: usize,
1979    prevout_values: &[i64],
1980    prevout_script_pubkeys: &[&[u8]],
1981    block_height: Option<u64>,
1982    network: crate::types::Network,
1983    precomputed_bip143: Option<&crate::transaction_hash::Bip143PrecomputedHashes>,
1984) -> Option<Result<bool>> {
1985    const SCRIPT_VERIFY_P2SH: u32 = 0x01;
1986    if (flags & SCRIPT_VERIFY_P2SH) == 0 {
1987        return None;
1988    }
1989    if script_pubkey.len() != 23
1990        || script_pubkey[0] != OP_HASH160
1991        || script_pubkey[1] != PUSH_20_BYTES
1992        || script_pubkey[22] != OP_EQUAL
1993    {
1994        return None;
1995    }
1996    let expected_hash = &script_pubkey[2..22];
1997
1998    let pushes = parse_script_sig_push_only(script_sig.as_ref())?;
1999    if pushes.len() != 1 {
2000        return None;
2001    }
2002    let redeem = &pushes[0];
2003    if redeem.len() != 22 || redeem[0] != OP_0 || redeem[1] != PUSH_20_BYTES {
2004        return None;
2005    }
2006    let sha256_hash = OptimizedSha256::new().hash(redeem.as_ref());
2007    let redeem_hash = Ripemd160::digest(sha256_hash);
2008    if &redeem_hash[..] != expected_hash {
2009        return Some(Ok(false));
2010    }
2011
2012    if witness.len() != 2 {
2013        return None;
2014    }
2015    let signature_bytes = &witness[0];
2016    let pubkey_bytes = &witness[1];
2017    if (pubkey_bytes.len() != 33 && pubkey_bytes.len() != 65) || signature_bytes.is_empty() {
2018        return Some(Ok(false));
2019    }
2020    let expected_pubkey_hash = &redeem[2..22];
2021    let pubkey_sha256 = OptimizedSha256::new().hash(pubkey_bytes);
2022    let pubkey_hash = Ripemd160::digest(pubkey_sha256);
2023    if &pubkey_hash[..] != expected_pubkey_hash {
2024        return Some(Ok(false));
2025    }
2026
2027    // BIP143 §4.3: P2WPKH-in-P2SH scriptCode is the P2PKH expansion of the 20-byte program.
2028    let p2pkh_script_code = bip143_p2wpkh_script_code(expected_pubkey_hash);
2029
2030    let sighash_byte = signature_bytes[signature_bytes.len() - 1];
2031    let amount = prevout_values.get(input_index).copied().unwrap_or(0);
2032    let sighash = match crate::transaction_hash::calculate_bip143_sighash(
2033        tx,
2034        input_index,
2035        &p2pkh_script_code,
2036        amount,
2037        sighash_byte,
2038        precomputed_bip143,
2039    ) {
2040        Ok(h) => h,
2041        Err(e) => return Some(Err(e)),
2042    };
2043
2044    let height = block_height.unwrap_or(0);
2045    let is_valid = signature::with_secp_context(|secp| {
2046        signature::verify_signature(
2047            secp,
2048            pubkey_bytes,
2049            signature_bytes,
2050            &sighash,
2051            flags,
2052            height,
2053            network,
2054            SigVersion::WitnessV0,
2055        )
2056    });
2057    Some(is_valid)
2058}
2059
2060/// P2WSH fast-path. ScriptPubKey OP_0 <32-byte-SHA256(witness_script)>; witness = [..., witness_script].
2061/// Verifies hash then executes witness script only. Returns None to fall back to full path.
2062#[cfg(feature = "production")]
2063#[allow(clippy::too_many_arguments)]
2064fn try_verify_p2wsh_fast_path(
2065    script_sig: &ByteString,
2066    script_pubkey: &[u8],
2067    witness: &crate::witness::Witness,
2068    flags: u32,
2069    tx: &Transaction,
2070    input_index: usize,
2071    prevout_values: &[i64],
2072    prevout_script_pubkeys: &[&[u8]],
2073    block_height: Option<u64>,
2074    median_time_past: Option<u64>,
2075    network: crate::types::Network,
2076    schnorr_collector: Option<&crate::bip348::SchnorrSignatureCollector>,
2077    precomputed_bip143: Option<&crate::transaction_hash::Bip143PrecomputedHashes>,
2078    #[cfg(feature = "production")] sighash_cache: Option<
2079        &crate::transaction_hash::SighashMidstateCache,
2080    >,
2081) -> Option<Result<bool>> {
2082    // P2WSH: 34 bytes = OP_0 PUSH_32_BYTES <32-byte-hash>
2083    if script_pubkey.len() != 34 || script_pubkey[0] != OP_0 || script_pubkey[1] != PUSH_32_BYTES {
2084        return None;
2085    }
2086    if !script_sig.is_empty() {
2087        return None;
2088    }
2089    if witness.is_empty() {
2090        return None;
2091    }
2092    let witness_script = witness.last().expect("witness not empty").clone();
2093    let mut stack: Vec<StackElement> = witness
2094        .iter()
2095        .take(witness.len() - 1)
2096        .map(|w| to_stack_element(w))
2097        .collect();
2098
2099    let program_hash = &script_pubkey[2..34];
2100    if program_hash.len() != 32 {
2101        return None;
2102    }
2103    let witness_script_hash = OptimizedSha256::new().hash(witness_script.as_ref());
2104    if &witness_script_hash[..] != program_hash {
2105        return Some(Ok(false));
2106    }
2107
2108    // P2WSH always uses WitnessV0 sighash semantics (BIP143).
2109    // 0x8000 is SCRIPT_VERIFY_WITNESS_PUBKEYTYPE — a key-type strictness flag, not a
2110    // sigversion selector. Tapscript only applies inside Taproot script-path spends.
2111    let witness_sigversion = SigVersion::WitnessV0;
2112
2113    // P2WSH-with-P2PKH fast-path: witness_script = P2PKH, stack = [sig, pubkey]. BIP143, batch collect.
2114    if witness_sigversion == SigVersion::WitnessV0
2115        && witness_script.len() == 25
2116        && witness_script[0] == OP_DUP
2117        && witness_script[1] == OP_HASH160
2118        && witness_script[2] == PUSH_20_BYTES
2119        && witness_script[23] == OP_EQUALVERIFY
2120        && witness_script[24] == OP_CHECKSIG
2121        && stack.len() == 2
2122    {
2123        let signature_bytes = &stack[0];
2124        let pubkey_bytes = &stack[1];
2125        if (pubkey_bytes.len() == 33 || pubkey_bytes.len() == 65) && !signature_bytes.is_empty() {
2126            let expected_pubkey_hash = &witness_script[3..23];
2127            let pubkey_sha256 = OptimizedSha256::new().hash(pubkey_bytes);
2128            let pubkey_hash = Ripemd160::digest(pubkey_sha256);
2129            if &pubkey_hash[..] == expected_pubkey_hash {
2130                let sighash_byte = signature_bytes[signature_bytes.len() - 1];
2131                let amount = prevout_values.get(input_index).copied().unwrap_or(0);
2132                match crate::transaction_hash::calculate_bip143_sighash(
2133                    tx,
2134                    input_index,
2135                    witness_script.as_ref(),
2136                    amount,
2137                    sighash_byte,
2138                    precomputed_bip143,
2139                ) {
2140                    Ok(sighash) => {
2141                        let height = block_height.unwrap_or(0);
2142                        let is_valid = signature::with_secp_context(|secp| {
2143                            signature::verify_signature(
2144                                secp,
2145                                pubkey_bytes,
2146                                signature_bytes,
2147                                &sighash,
2148                                flags,
2149                                height,
2150                                network,
2151                                SigVersion::WitnessV0,
2152                            )
2153                        });
2154                        return Some(is_valid);
2155                    }
2156                    Err(e) => return Some(Err(e)),
2157                }
2158            }
2159        }
2160    }
2161
2162    // P2WSH-with-P2PK fast-path: witness_script = OP_PUSHBYTES_N + pubkey + OP_CHECKSIG, stack = [sig]. BIP143, batch collect.
2163    if witness_sigversion == SigVersion::WitnessV0
2164        && (witness_script.len() == 35 || witness_script.len() == 67)
2165        && witness_script[witness_script.len() - 1] == OP_CHECKSIG
2166        && (witness_script[0] == 0x21 || witness_script[0] == 0x41)
2167        && stack.len() == 1
2168    {
2169        let pubkey_len = witness_script.len() - 2;
2170        if (pubkey_len == 33 || pubkey_len == 65) && !stack[0].is_empty() {
2171            let pubkey_bytes = &witness_script[1..(witness_script.len() - 1)];
2172            let signature_bytes = &stack[0];
2173            let sighash_byte = signature_bytes[signature_bytes.len() - 1];
2174            let amount = prevout_values.get(input_index).copied().unwrap_or(0);
2175            match crate::transaction_hash::calculate_bip143_sighash(
2176                tx,
2177                input_index,
2178                witness_script.as_ref(),
2179                amount,
2180                sighash_byte,
2181                precomputed_bip143,
2182            ) {
2183                Ok(sighash) => {
2184                    let height = block_height.unwrap_or(0);
2185                    let is_valid = signature::with_secp_context(|secp| {
2186                        signature::verify_signature(
2187                            secp,
2188                            pubkey_bytes,
2189                            signature_bytes,
2190                            &sighash,
2191                            flags,
2192                            height,
2193                            network,
2194                            SigVersion::WitnessV0,
2195                        )
2196                    });
2197                    return Some(is_valid);
2198                }
2199                Err(e) => return Some(Err(e)),
2200            }
2201        }
2202    }
2203
2204    // P2WSH-with-multisig fast-path: witness_script = OP_n <pubkeys> OP_m OP_CHECKMULTISIG, stack = [dummy, sig_1, ..., sig_m]. BIP143, BIP147 NULLDUMMY.
2205    if witness_sigversion == SigVersion::WitnessV0 {
2206        if let Some((m, _n, pubkeys)) = parse_redeem_multisig(witness_script.as_ref()) {
2207            if stack.len() < 2 {
2208                return Some(Ok(false));
2209            }
2210            let dummy = stack[0].as_ref();
2211            let signatures: Vec<&[u8]> = stack[1..].iter().map(|e| e.as_ref()).collect();
2212
2213            const SCRIPT_VERIFY_NULLDUMMY: u32 = 0x10;
2214            const SCRIPT_VERIFY_NULLFAIL: u32 = 0x4000;
2215            let height = block_height.unwrap_or(0);
2216            if (flags & SCRIPT_VERIFY_NULLDUMMY) != 0 {
2217                let activation = match network {
2218                    crate::types::Network::Mainnet => crate::constants::BIP147_ACTIVATION_MAINNET,
2219                    crate::types::Network::Testnet => crate::constants::BIP147_ACTIVATION_TESTNET,
2220                    crate::types::Network::Regtest | crate::types::Network::Signet => 0,
2221                };
2222                if height >= activation && !dummy.is_empty() && dummy != [0x00] {
2223                    return Some(Ok(false));
2224                }
2225            }
2226
2227            let mut cleaned = witness_script.to_vec();
2228            for sig in &signatures {
2229                if !sig.is_empty() {
2230                    let pattern = serialize_push_data(sig);
2231                    cleaned = find_and_delete(&cleaned, &pattern).into_owned();
2232                }
2233            }
2234
2235            let amount = prevout_values.get(input_index).copied().unwrap_or(0);
2236            let mut sig_index = 0;
2237            let mut valid_sigs = 0u8;
2238
2239            for pubkey_bytes in pubkeys {
2240                if sig_index >= signatures.len() {
2241                    break;
2242                }
2243                while sig_index < signatures.len() && signatures[sig_index].is_empty() {
2244                    sig_index += 1;
2245                }
2246                if sig_index >= signatures.len() {
2247                    break;
2248                }
2249                let signature_bytes = &signatures[sig_index];
2250                let sighash_byte = signature_bytes[signature_bytes.len() - 1];
2251                match crate::transaction_hash::calculate_bip143_sighash(
2252                    tx,
2253                    input_index,
2254                    &cleaned,
2255                    amount,
2256                    sighash_byte,
2257                    precomputed_bip143,
2258                ) {
2259                    Ok(sighash) => {
2260                        let is_valid = signature::with_secp_context(|secp| {
2261                            signature::verify_signature(
2262                                secp,
2263                                pubkey_bytes,
2264                                signature_bytes,
2265                                &sighash,
2266                                flags,
2267                                height,
2268                                network,
2269                                SigVersion::WitnessV0,
2270                            )
2271                        });
2272                        match is_valid {
2273                            Ok(v) if v => {
2274                                valid_sigs += 1;
2275                                sig_index += 1;
2276                            }
2277                            Ok(_) => {}
2278                            Err(e) => return Some(Err(e)),
2279                        }
2280                    }
2281                    Err(e) => return Some(Err(e)),
2282                }
2283            }
2284
2285            if (flags & SCRIPT_VERIFY_NULLFAIL) != 0 {
2286                for sig_bytes in &signatures[sig_index..] {
2287                    if !sig_bytes.is_empty() {
2288                        return Some(Err(ConsensusError::ScriptErrorWithCode {
2289                            code: ScriptErrorCode::SigNullFail,
2290                            message:
2291                                "OP_CHECKMULTISIG: non-null signature must not fail under NULLFAIL"
2292                                    .into(),
2293                        }));
2294                    }
2295                }
2296            }
2297
2298            return Some(Ok(valid_sigs >= m));
2299        }
2300    }
2301
2302    // Witness script uses interpreter path (no batch collection).
2303    // CHECKMULTISIG in witness script can produce invalid sig/pubkey pairings for batch.
2304    let result = eval_script_with_context_full_inner(
2305        &witness_script,
2306        &mut stack,
2307        flags,
2308        tx,
2309        input_index,
2310        prevout_values,
2311        prevout_script_pubkeys,
2312        block_height,
2313        median_time_past,
2314        network,
2315        witness_sigversion,
2316        None, // redeem_script_for_sighash
2317        None, // script_sig_for_sighash (witness script context)
2318        None, // taproot_annex_hash
2319        schnorr_collector,
2320        precomputed_bip143,
2321        #[cfg(feature = "production")]
2322        sighash_cache,
2323    );
2324    Some(result)
2325}
2326
2327/// P2TR script-path tapscript P2PK fast path. Tapscript PUSH_32_BYTES <32-byte-pubkey> OP_CHECKSIG, witness [sig, script, control_block].
2328#[cfg(feature = "production")]
2329#[allow(clippy::too_many_arguments)]
2330fn try_verify_p2tr_scriptpath_p2pk_fast_path(
2331    script_sig: &ByteString,
2332    script_pubkey: &[u8],
2333    witness: &crate::witness::Witness,
2334    _flags: u32,
2335    tx: &Transaction,
2336    input_index: usize,
2337    prevout_values: &[i64],
2338    prevout_script_pubkeys: &[&[u8]],
2339    block_height: Option<u64>,
2340    network: crate::types::Network,
2341    schnorr_collector: Option<&crate::bip348::SchnorrSignatureCollector>,
2342) -> Option<Result<bool>> {
2343    use crate::activation::taproot_activation_height;
2344    use crate::taproot::parse_taproot_script_path_witness;
2345
2346    let tap_h = taproot_activation_height(network);
2347    if block_height.map(|h| h < tap_h).unwrap_or(true) {
2348        return None;
2349    }
2350    if script_pubkey.len() != 34 || script_pubkey[0] != OP_1 || script_pubkey[1] != PUSH_32_BYTES {
2351        return None;
2352    }
2353    if !script_sig.is_empty() {
2354        return None;
2355    }
2356    if witness.len() < 2 {
2357        return None;
2358    }
2359    let mut output_key = [0u8; 32];
2360    output_key.copy_from_slice(&script_pubkey[2..34]);
2361    let (witness_body, annex_hash) = crate::taproot::strip_taproot_annex(witness);
2362    let parsed = match parse_taproot_script_path_witness(&witness_body, &output_key) {
2363        Ok(Some(p)) => p,
2364        Ok(None) | Err(_) => return None,
2365    };
2366    let (tapscript, stack_items, control_block) = parsed;
2367    if tapscript.len() != 34 || tapscript[0] != PUSH_32_BYTES || tapscript[33] != OP_CHECKSIG {
2368        return None;
2369    }
2370    if stack_items.len() != 1 {
2371        return None;
2372    }
2373    let (sig_bytes, sighash_type) =
2374        crate::bip348::try_parse_taproot_schnorr_witness_sig(stack_items[0].as_ref())?;
2375    let pubkey_32 = &tapscript[1..33];
2376    let sighash = crate::taproot::compute_tapscript_signature_hash(
2377        tx,
2378        input_index,
2379        prevout_values,
2380        prevout_script_pubkeys,
2381        &tapscript,
2382        control_block.leaf_version,
2383        0xffff_ffff,
2384        sighash_type,
2385        annex_hash.as_ref(),
2386    )
2387    .ok()?;
2388    let result = crate::bip348::verify_tapscript_schnorr_signature(
2389        &sighash,
2390        pubkey_32,
2391        &sig_bytes,
2392        schnorr_collector,
2393    );
2394    Some(result)
2395}
2396
2397/// Taproot (P2TR) key-path fast-path. ScriptPubKey OP_1 <32-byte output key>, witness [64-byte sig].
2398/// Skips interpreter; verifies Schnorr directly. Returns None for script-path or pre-activation.
2399#[cfg(feature = "production")]
2400#[allow(clippy::too_many_arguments)]
2401fn verify_p2tr_keypath_witness(
2402    script_pubkey: &[u8],
2403    witness: &crate::witness::Witness,
2404    tx: &Transaction,
2405    input_index: usize,
2406    prevout_values: &[i64],
2407    prevout_script_pubkeys: &[&[u8]],
2408    schnorr_collector: Option<&crate::bip348::SchnorrSignatureCollector>,
2409) -> Result<bool> {
2410    use crate::bip348::{
2411        try_parse_taproot_schnorr_witness_sig, verify_tapscript_schnorr_signature,
2412    };
2413
2414    let (witness_body, annex_hash) = crate::taproot::strip_taproot_annex(witness);
2415    if witness_body.len() != 1 {
2416        return Ok(false);
2417    }
2418    let Some((sig_bytes, sighash_type)) = try_parse_taproot_schnorr_witness_sig(&witness_body[0])
2419    else {
2420        return Ok(false);
2421    };
2422    let output_key = &script_pubkey[2..34];
2423    let sighash = crate::taproot::compute_taproot_signature_hash(
2424        tx,
2425        input_index,
2426        prevout_values,
2427        prevout_script_pubkeys,
2428        sighash_type,
2429        annex_hash.as_ref(),
2430    )?;
2431    verify_tapscript_schnorr_signature(&sighash, output_key, &sig_bytes, schnorr_collector)
2432}
2433
2434#[cfg(feature = "production")]
2435#[allow(clippy::too_many_arguments)]
2436fn try_verify_p2tr_keypath_fast_path(
2437    script_sig: &ByteString,
2438    script_pubkey: &[u8],
2439    witness: &crate::witness::Witness,
2440    _flags: u32,
2441    tx: &Transaction,
2442    input_index: usize,
2443    prevout_values: &[i64],
2444    prevout_script_pubkeys: &[&[u8]],
2445    block_height: Option<u64>,
2446    network: crate::types::Network,
2447    schnorr_collector: Option<&crate::bip348::SchnorrSignatureCollector>,
2448) -> Option<Result<bool>> {
2449    use crate::activation::taproot_activation_height;
2450    let tap_h = taproot_activation_height(network);
2451    if block_height.map(|h| h < tap_h).unwrap_or(true) {
2452        return None;
2453    }
2454    // P2TR: 34 bytes = OP_1 PUSH_32_BYTES <32-byte output key>
2455    if script_pubkey.len() != 34 || script_pubkey[0] != OP_1 || script_pubkey[1] != PUSH_32_BYTES {
2456        return None;
2457    }
2458    if !script_sig.is_empty() {
2459        return None;
2460    }
2461    // Key-path: one stack element after optional annex strip (Core CheckSchnorrSignature).
2462    let (witness_body, _) = crate::taproot::strip_taproot_annex(witness);
2463    if witness_body.len() != 1 {
2464        return None;
2465    }
2466    crate::bip348::try_parse_taproot_schnorr_witness_sig(&witness_body[0])?;
2467    Some(verify_p2tr_keypath_witness(
2468        script_pubkey,
2469        witness,
2470        tx,
2471        input_index,
2472        prevout_values,
2473        prevout_script_pubkeys,
2474        schnorr_collector,
2475    ))
2476}
2477
2478#[spec_locked("5.2", "VerifyScript")]
2479pub fn verify_script_with_context_full(
2480    script_sig: &ByteString,
2481    script_pubkey: &[u8],
2482    witness: Option<&crate::witness::Witness>,
2483    flags: u32,
2484    tx: &Transaction,
2485    input_index: usize,
2486    prevout_values: &[i64],
2487    prevout_script_pubkeys: &[&[u8]],
2488    block_height: Option<u64>,
2489    median_time_past: Option<u64>,
2490    network: crate::types::Network,
2491    _sigversion: SigVersion,
2492    #[cfg(feature = "production")] schnorr_collector: Option<
2493        &crate::bip348::SchnorrSignatureCollector,
2494    >,
2495    precomputed_bip143: Option<&crate::transaction_hash::Bip143PrecomputedHashes>,
2496    #[cfg(feature = "production")] precomputed_sighash_all: Option<[u8; 32]>,
2497    #[cfg(feature = "production")] sighash_cache: Option<
2498        &crate::transaction_hash::SighashMidstateCache,
2499    >,
2500    #[cfg(feature = "production")] precomputed_p2pkh_hash: Option<[u8; 20]>,
2501) -> Result<bool> {
2502    // libbitcoin-consensus check (multi-input verify_script): prevouts length must match vin size
2503    if prevout_values.len() != tx.inputs.len() || prevout_script_pubkeys.len() != tx.inputs.len() {
2504        return Err(ConsensusError::ScriptErrorWithCode {
2505            code: ScriptErrorCode::TxInputInvalid,
2506            message: format!(
2507                "Prevout slices: values={}, script_pubkeys={}, input_count={} (input_idx={})",
2508                prevout_values.len(),
2509                prevout_script_pubkeys.len(),
2510                tx.inputs.len(),
2511                input_index,
2512            )
2513            .into(),
2514        });
2515    }
2516
2517    // libbitcoin-consensus check: prevout.value must not exceed i64::MAX
2518    // In libbitcoin-consensus: if (prevout.value > std::numeric_limits<int64_t>::max())
2519    // This prevents value overflow in TransactionSignatureChecker
2520    // Note: Our value is already i64, so it can't exceed i64::MAX by definition
2521    // But we validate it's non-negative and within MAX_MONEY bounds for safety
2522    if input_index < prevout_values.len() {
2523        let prevout_value = prevout_values[input_index];
2524        if prevout_value < 0 {
2525            return Err(ConsensusError::ScriptErrorWithCode {
2526                code: ScriptErrorCode::ValueOverflow,
2527                message: "Prevout value cannot be negative".into(),
2528            });
2529        }
2530        #[cfg(feature = "production")]
2531        {
2532            use precomputed_constants::MAX_MONEY_U64;
2533            if (prevout_value as u64) > MAX_MONEY_U64 {
2534                return Err(ConsensusError::ScriptErrorWithCode {
2535                    code: ScriptErrorCode::ValueOverflow,
2536                    message: format!("Prevout value {prevout_value} exceeds MAX_MONEY").into(),
2537                });
2538            }
2539        }
2540        #[cfg(not(feature = "production"))]
2541        {
2542            use crate::constants::MAX_MONEY;
2543            if prevout_value > MAX_MONEY {
2544                return Err(ConsensusError::ScriptErrorWithCode {
2545                    code: ScriptErrorCode::ValueOverflow,
2546                    message: format!("Prevout value {prevout_value} exceeds MAX_MONEY").into(),
2547                });
2548            }
2549        }
2550    }
2551
2552    // libbitcoin-consensus check: input_index must be valid
2553    if input_index >= tx.inputs.len() {
2554        return Err(ConsensusError::ScriptErrorWithCode {
2555            code: ScriptErrorCode::TxInputInvalid,
2556            message: format!(
2557                "Input index {} out of bounds (tx has {} inputs)",
2558                input_index,
2559                tx.inputs.len()
2560            )
2561            .into(),
2562        });
2563    }
2564
2565    // P2PK / P2PKH / P2SH fast-paths — skip interpreter for common legacy scripts
2566    #[cfg(feature = "production")]
2567    if !fast_paths_disabled() && witness.is_none() {
2568        if let Some(result) = try_verify_p2pk_fast_path(
2569            script_sig,
2570            script_pubkey,
2571            flags,
2572            tx,
2573            input_index,
2574            prevout_values,
2575            prevout_script_pubkeys,
2576            block_height,
2577            network,
2578            #[cfg(feature = "production")]
2579            sighash_cache,
2580        ) {
2581            FAST_PATH_P2PK.fetch_add(1, Ordering::Relaxed);
2582            return result;
2583        }
2584        if let Some(result) = try_verify_p2pkh_fast_path(
2585            script_sig,
2586            script_pubkey,
2587            flags,
2588            tx,
2589            input_index,
2590            prevout_values,
2591            prevout_script_pubkeys,
2592            block_height,
2593            network,
2594            #[cfg(feature = "production")]
2595            precomputed_sighash_all,
2596            #[cfg(feature = "production")]
2597            sighash_cache,
2598            #[cfg(feature = "production")]
2599            precomputed_p2pkh_hash,
2600        ) {
2601            FAST_PATH_P2PKH.fetch_add(1, Ordering::Relaxed);
2602            return result;
2603        }
2604        if let Some(result) = try_verify_p2sh_fast_path(
2605            script_sig,
2606            script_pubkey,
2607            flags,
2608            tx,
2609            input_index,
2610            prevout_values,
2611            prevout_script_pubkeys,
2612            block_height,
2613            median_time_past,
2614            network,
2615            #[cfg(feature = "production")]
2616            sighash_cache,
2617            #[cfg(feature = "production")]
2618            precomputed_sighash_all,
2619        ) {
2620            FAST_PATH_P2SH.fetch_add(1, Ordering::Relaxed);
2621            return result;
2622        }
2623        if let Some(result) = try_verify_bare_multisig_fast_path(
2624            script_sig,
2625            script_pubkey,
2626            flags,
2627            tx,
2628            input_index,
2629            prevout_values,
2630            prevout_script_pubkeys,
2631            block_height,
2632            network,
2633            #[cfg(feature = "production")]
2634            sighash_cache,
2635        ) {
2636            FAST_PATH_BARE_MULTISIG.fetch_add(1, Ordering::Relaxed);
2637            return result;
2638        }
2639    }
2640    // P2WPKH / P2WSH / P2WPKH-in-P2SH fast-paths when witness present
2641    #[cfg(feature = "production")]
2642    if !fast_paths_disabled() {
2643        if let Some(wit) = witness {
2644            if let Some(result) = try_verify_p2wpkh_in_p2sh_fast_path(
2645                script_sig,
2646                script_pubkey,
2647                wit,
2648                flags,
2649                tx,
2650                input_index,
2651                prevout_values,
2652                prevout_script_pubkeys,
2653                block_height,
2654                network,
2655                precomputed_bip143,
2656            ) {
2657                FAST_PATH_P2WPKH.fetch_add(1, Ordering::Relaxed);
2658                return result;
2659            }
2660            if let Some(result) = try_verify_p2wpkh_fast_path(
2661                script_sig,
2662                script_pubkey,
2663                wit,
2664                flags,
2665                tx,
2666                input_index,
2667                prevout_values,
2668                prevout_script_pubkeys,
2669                block_height,
2670                network,
2671                precomputed_bip143,
2672                precomputed_sighash_all,
2673            ) {
2674                FAST_PATH_P2WPKH.fetch_add(1, Ordering::Relaxed);
2675                return result;
2676            }
2677            if let Some(result) = try_verify_p2wsh_fast_path(
2678                script_sig,
2679                script_pubkey,
2680                wit,
2681                flags,
2682                tx,
2683                input_index,
2684                prevout_values,
2685                prevout_script_pubkeys,
2686                block_height,
2687                median_time_past,
2688                network,
2689                schnorr_collector,
2690                precomputed_bip143,
2691                #[cfg(feature = "production")]
2692                sighash_cache,
2693            ) {
2694                FAST_PATH_P2WSH.fetch_add(1, Ordering::Relaxed);
2695                return result;
2696            }
2697            if let Some(result) = try_verify_p2tr_scriptpath_p2pk_fast_path(
2698                script_sig,
2699                script_pubkey,
2700                wit,
2701                flags,
2702                tx,
2703                input_index,
2704                prevout_values,
2705                prevout_script_pubkeys,
2706                block_height,
2707                network,
2708                schnorr_collector,
2709            ) {
2710                FAST_PATH_P2TR.fetch_add(1, Ordering::Relaxed);
2711                return result;
2712            }
2713            if let Some(result) = try_verify_p2tr_keypath_fast_path(
2714                script_sig,
2715                script_pubkey,
2716                wit,
2717                flags,
2718                tx,
2719                input_index,
2720                prevout_values,
2721                prevout_script_pubkeys,
2722                block_height,
2723                network,
2724                schnorr_collector,
2725            ) {
2726                FAST_PATH_P2TR.fetch_add(1, Ordering::Relaxed);
2727                return result;
2728            }
2729        }
2730    }
2731    #[cfg(feature = "production")]
2732    FAST_PATH_INTERPRETER.fetch_add(1, Ordering::Relaxed);
2733
2734    // P2SH handling: If SCRIPT_VERIFY_P2SH flag is set and scriptPubkey is P2SH format,
2735    // we need to check scriptSig push-only BEFORE executing it
2736    // P2SH scriptPubkey format: OP_HASH160 <20-byte-hash> OP_EQUAL
2737    const SCRIPT_VERIFY_P2SH: u32 = 0x01;
2738    let is_p2sh = (flags & SCRIPT_VERIFY_P2SH) != 0
2739        && script_pubkey.len() == 23  // OP_HASH160 (1) + push 20 (1) + 20 bytes + OP_EQUAL (1) = 23
2740        && script_pubkey[0] == OP_HASH160   // OP_HASH160
2741        && script_pubkey[1] == PUSH_20_BYTES   // push 20 bytes
2742        && script_pubkey[22] == OP_EQUAL; // OP_EQUAL
2743
2744    // CRITICAL: For P2SH, scriptSig MUST only contain push operations (data pushes only)
2745    // This prevents script injection attacks. If scriptSig contains non-push opcodes, fail immediately.
2746    // This check MUST happen BEFORE executing scriptSig
2747    // Note: We validate push-only by attempting to parse scriptSig as push-only
2748    // If we encounter any non-push opcode OR invalid push encoding, we fail
2749    if is_p2sh {
2750        let mut i = 0;
2751        while i < script_sig.len() {
2752            let opcode = script_sig[i];
2753            if !is_push_opcode(opcode) {
2754                // Non-push opcode found in P2SH scriptSig - this is invalid
2755                return Ok(false);
2756            }
2757            // Advance past the push opcode and data
2758            if opcode == OP_0 {
2759                // OP_0 - push empty array, no data to skip
2760                i += 1;
2761            } else if opcode <= 0x4b {
2762                // Direct push: opcode is the length (1-75 bytes)
2763                let len = opcode as usize;
2764                if i + 1 + len > script_sig.len() {
2765                    return Ok(false); // Invalid push length
2766                }
2767                i += 1 + len;
2768            } else if opcode == OP_PUSHDATA1 {
2769                // OP_PUSHDATA1
2770                if i + 1 >= script_sig.len() {
2771                    return Ok(false);
2772                }
2773                let len = script_sig[i + 1] as usize;
2774                if i + 2 + len > script_sig.len() {
2775                    return Ok(false);
2776                }
2777                i += 2 + len;
2778            } else if opcode == OP_PUSHDATA2 {
2779                // OP_PUSHDATA2
2780                if i + 2 >= script_sig.len() {
2781                    return Ok(false);
2782                }
2783                let len = u16::from_le_bytes([script_sig[i + 1], script_sig[i + 2]]) as usize;
2784                if i + 3 + len > script_sig.len() {
2785                    return Ok(false);
2786                }
2787                i += 3 + len;
2788            } else if opcode == OP_PUSHDATA4 {
2789                // OP_PUSHDATA4
2790                if i + 4 >= script_sig.len() {
2791                    return Ok(false);
2792                }
2793                let len = u32::from_le_bytes([
2794                    script_sig[i + 1],
2795                    script_sig[i + 2],
2796                    script_sig[i + 3],
2797                    script_sig[i + 4],
2798                ]) as usize;
2799                if i + 5 + len > script_sig.len() {
2800                    return Ok(false);
2801                }
2802                i += 5 + len;
2803            } else if (OP_1NEGATE..=OP_16).contains(&opcode) {
2804                // OP_1NEGATE, OP_RESERVED, OP_1-OP_16
2805                // These are single-byte push opcodes with no data payload
2806                i += 1;
2807            } else {
2808                // Should not reach here if is_push_opcode is correct, but fail anyway
2809                return Ok(false);
2810            }
2811        }
2812        if !fast_paths_disabled() {
2813            if let Some(result) = try_verify_p2sh_multisig_fast_path(
2814                script_sig,
2815                script_pubkey,
2816                flags,
2817                tx,
2818                input_index,
2819                prevout_values,
2820                prevout_script_pubkeys,
2821                block_height,
2822                network,
2823                #[cfg(feature = "production")]
2824                sighash_cache,
2825            ) {
2826                return result;
2827            }
2828        }
2829    }
2830
2831    #[cfg(feature = "production")]
2832    let mut _stack_guard = PooledStackGuard(get_pooled_stack());
2833    #[cfg(feature = "production")]
2834    let stack = &mut _stack_guard.0;
2835    #[cfg(not(feature = "production"))]
2836    let mut stack = Vec::with_capacity(20);
2837
2838    // Execute scriptSig (always Base sigversion)
2839    // FIX: scriptSig can contain CHECKSIG/CHECKMULTISIG (non-standard); collecting produces invalid
2840    // (sig, pubkey) pairings. Only fast paths (P2PKH, P2WPKH, P2WSH) have correct 1:1 pairing.
2841    let script_sig_result = eval_script_with_context_full(
2842        script_sig,
2843        stack,
2844        flags,
2845        tx,
2846        input_index,
2847        prevout_values,
2848        prevout_script_pubkeys,
2849        block_height,
2850        median_time_past,
2851        network,
2852        SigVersion::Base,
2853        None, // script_sig not needed when executing scriptSig
2854        None, // taproot_annex_hash
2855        #[cfg(feature = "production")]
2856        schnorr_collector,
2857        None, // precomputed_bip143 - Base sigversion
2858        #[cfg(feature = "production")]
2859        sighash_cache,
2860    )?;
2861    if !script_sig_result {
2862        return Ok(false);
2863    }
2864
2865    // Save redeem script if P2SH (it's the last item on stack after scriptSig)
2866    let redeem_script: Option<ByteString> = if is_p2sh && !stack.is_empty() {
2867        Some(stack.last().expect("Stack is not empty").as_ref().to_vec())
2868    } else {
2869        None
2870    };
2871
2872    // CRITICAL FIX: Check if scriptPubkey is Taproot (P2TR) - OP_1 <32-byte-hash>
2873    // Taproot format: [OP_1, PUSH_32_BYTES, <32 bytes>] = 34 bytes total
2874    // For Taproot, scriptSig must be empty and validation happens via witness using Taproot-specific logic
2875    use crate::activation::taproot_activation_height;
2876    let tap_h = taproot_activation_height(network);
2877    let is_taproot = redeem_script.is_none()  // Not P2SH
2878        && block_height.is_some() && block_height.unwrap() >= tap_h
2879        && script_pubkey.len() == 34
2880        && script_pubkey[0] == OP_1  // OP_1 (witness version 1)
2881        && script_pubkey[1] == PUSH_32_BYTES; // push 32 bytes
2882
2883    // If Taproot, scriptSig must be empty
2884    if is_taproot && !script_sig.is_empty() {
2885        return Ok(false); // Taproot requires empty scriptSig
2886    }
2887
2888    // CRITICAL FIX: Check if scriptPubkey is a direct witness program (P2WPKH or P2WSH, not nested in P2SH)
2889    // Witness program format: OP_0 (0x00) + push opcode + program bytes
2890    // P2WPKH: [OP_0, PUSH_20_BYTES, <20 bytes>] = 22 bytes total
2891    // P2WSH: [OP_0, PUSH_32_BYTES, <32 bytes>] = 34 bytes total
2892    let is_direct_witness_program = redeem_script.is_none()  // Not P2SH
2893        && !is_taproot  // Not Taproot
2894        && script_pubkey.len() >= 3
2895        && script_pubkey[0] == OP_0  // OP_0 (witness version 0)
2896        && ((script_pubkey[1] == PUSH_20_BYTES && script_pubkey.len() == 22)  // P2WPKH: push 20 bytes, total 22
2897            || (script_pubkey[1] == PUSH_32_BYTES && script_pubkey.len() == 34)); // P2WSH: push 32 bytes, total 34
2898
2899    // For direct P2WPKH/P2WSH, push witness stack elements BEFORE executing scriptPubkey
2900    let mut witness_script_to_execute: Option<ByteString> = None;
2901    if is_direct_witness_program {
2902        if let Some(witness_stack) = witness {
2903            if script_pubkey[1] == PUSH_32_BYTES {
2904                // P2WSH: witness_stack = [sig1, sig2, ..., witness_script]
2905                // Push all elements except last onto stack, save witness_script for later execution
2906                if witness_stack.is_empty() {
2907                    return Ok(false); // P2WSH requires witness
2908                }
2909
2910                // Get witness script (last element)
2911                let witness_script = witness_stack.last().expect("Witness stack is not empty");
2912
2913                // Verify witness script hash matches program
2914                let program_bytes = &script_pubkey[2..];
2915                if program_bytes.len() != 32 {
2916                    return Ok(false); // Invalid P2WSH program length
2917                }
2918
2919                let witness_script_hash = OptimizedSha256::new().hash(witness_script.as_ref());
2920                if &witness_script_hash[..] != program_bytes {
2921                    return Ok(false); // Witness script hash doesn't match program
2922                }
2923
2924                // Hash matches - push witness stack elements (except last) onto stack
2925                for element in witness_stack.iter().take(witness_stack.len() - 1) {
2926                    stack.push(to_stack_element(element));
2927                }
2928
2929                // Save witness script for execution after scriptPubkey
2930                witness_script_to_execute = Some(witness_script.clone());
2931            } else if script_pubkey[1] == PUSH_20_BYTES {
2932                // P2WPKH: witness_stack = [signature, pubkey]
2933                // Push both elements onto stack
2934                if witness_stack.len() != 2 {
2935                    return Ok(false); // P2WPKH requires exactly 2 witness elements
2936                }
2937
2938                for element in witness_stack.iter() {
2939                    stack.push(to_stack_element(element));
2940                }
2941            } else {
2942                return Ok(false); // Invalid witness program format
2943            }
2944        } else {
2945            return Ok(false); // Witness program requires witness
2946        }
2947    }
2948
2949    if is_taproot {
2950        let Some(witness_stack) = witness else {
2951            return Ok(false);
2952        };
2953        if witness_stack.is_empty() {
2954            return Ok(false);
2955        }
2956        // After optional annex strip: 1 element = key-path; 2+ = script-path (Core VerifyWitnessProgram).
2957        let (witness_body, annex_hash) = crate::taproot::strip_taproot_annex(witness_stack);
2958        if witness_body.is_empty() {
2959            return Ok(false);
2960        }
2961        if witness_body.len() == 1 {
2962            #[cfg(feature = "production")]
2963            {
2964                return verify_p2tr_keypath_witness(
2965                    script_pubkey,
2966                    witness_stack,
2967                    tx,
2968                    input_index,
2969                    prevout_values,
2970                    prevout_script_pubkeys,
2971                    schnorr_collector,
2972                );
2973            }
2974            #[cfg(not(feature = "production"))]
2975            {
2976                return Ok(false);
2977            }
2978        }
2979        if witness_body.len() < 2 {
2980            return Ok(false);
2981        }
2982        let mut output_key = [0u8; 32];
2983        output_key.copy_from_slice(&script_pubkey[2..34]);
2984        match crate::taproot::parse_taproot_script_path_witness(&witness_body, &output_key)? {
2985            None => return Ok(false),
2986            Some((tapscript, stack_items, _control_block)) => {
2987                for item in &stack_items {
2988                    stack.push(to_stack_element(item));
2989                }
2990                let tapscript_flags = flags | 0x8000;
2991                if !eval_script_with_context_full(
2992                    &tapscript,
2993                    stack,
2994                    tapscript_flags,
2995                    tx,
2996                    input_index,
2997                    prevout_values,
2998                    prevout_script_pubkeys,
2999                    block_height,
3000                    median_time_past,
3001                    network,
3002                    SigVersion::Tapscript,
3003                    None,
3004                    annex_hash.as_ref(),
3005                    #[cfg(feature = "production")]
3006                    schnorr_collector,
3007                    None,
3008                    #[cfg(feature = "production")]
3009                    sighash_cache,
3010                )? {
3011                    return Ok(false);
3012                }
3013                return Ok(true);
3014            }
3015        }
3016    }
3017
3018    // Execute scriptPubkey (always Base sigversion)
3019    // For P2WPKH/P2WSH, witness stack elements are already on the stack
3020    // Pass script_sig so legacy sighash uses same signature bytes as fast path (FindAndDelete pattern).
3021    // Interpreter path: verify in-place only. Interpreter sighash can diverge from
3022    // fast path (e.g. CHECKMULTISIG), causing batch to store invalid triples. Verify in-place only.
3023    // Thread-local guard ensures we never collect even if a collector is accidentally threaded through.
3024    let script_pubkey_result = eval_script_with_context_full(
3025        script_pubkey,
3026        stack,
3027        flags,
3028        tx,
3029        input_index,
3030        prevout_values,
3031        prevout_script_pubkeys,
3032        block_height,
3033        median_time_past,
3034        network,
3035        SigVersion::Base,
3036        Some(script_sig),
3037        None, // taproot_annex_hash
3038        #[cfg(feature = "production")]
3039        schnorr_collector,
3040        None, // precomputed_bip143 - Base sigversion
3041        #[cfg(feature = "production")]
3042        sighash_cache,
3043    )?;
3044    if !script_pubkey_result {
3045        return Ok(false);
3046    }
3047
3048    // For P2WSH, execute the witness script after scriptPubkey verification
3049    if let Some(witness_script) = witness_script_to_execute {
3050        // P2WSH always uses WitnessV0 (BIP143). 0x8000 = SCRIPT_VERIFY_WITNESS_PUBKEYTYPE
3051        // is a key-type strictness flag and does not select Tapscript semantics.
3052        let witness_sigversion = SigVersion::WitnessV0;
3053
3054        // Execute witness script with witness stack elements on the stack
3055        // Interpreter path: no collection (same invalid pairing issue as bare multisig).
3056        if !eval_script_with_context_full(
3057            &witness_script,
3058            stack,
3059            flags,
3060            tx,
3061            input_index,
3062            prevout_values,
3063            prevout_script_pubkeys,
3064            block_height,
3065            median_time_past,
3066            network,
3067            witness_sigversion,
3068            None, // witness script, no script_sig for sighash
3069            None, // taproot_annex_hash
3070            #[cfg(feature = "production")]
3071            schnorr_collector,
3072            precomputed_bip143, // WitnessV0 uses BIP143
3073            #[cfg(feature = "production")]
3074            sighash_cache,
3075        )? {
3076            return Ok(false);
3077        }
3078    }
3079
3080    // P2SH: If scriptPubkey verified the hash, we need to execute the redeem script
3081    // The scriptPubkey (OP_HASH160 <hash> OP_EQUAL) pops the redeem script, hashes it, compares
3082    // After scriptPubkey execution, if successful, stack should have [sig1, sig2, ..., 1]
3083    // where 1 is the OP_EQUAL result (true)
3084    if let Some(redeem) = redeem_script {
3085        // Verify stack has at least one element (the OP_EQUAL result)
3086        if stack.is_empty() {
3087            return Ok(false); // scriptPubkey execution failed
3088        }
3089
3090        // Verify top element is non-zero (OP_EQUAL returned 1 = hash matched)
3091        let top = stack.last().expect("Stack is not empty");
3092        if !cast_to_bool(top) {
3093            return Ok(false); // Hash didn't match or scriptPubkey failed
3094        }
3095
3096        // Pop the OP_EQUAL result (1) - this was pushed by OP_EQUAL when hashes matched
3097        stack.pop();
3098
3099        // Check if redeem script is a witness program (P2WSH-in-P2SH or P2WPKH-in-P2SH)
3100        // Witness program format: OP_0 (0x00) + push opcode + program bytes
3101        // P2WPKH: [OP_0, PUSH_20_BYTES, <20 bytes>] = 22 bytes total
3102        // P2WSH: [OP_0, PUSH_32_BYTES, <32 bytes>] = 34 bytes total
3103        let is_witness_program = redeem.len() >= 3
3104            && redeem[0] == OP_0  // OP_0 (witness version 0)
3105            && ((redeem[1] == PUSH_20_BYTES && redeem.len() == 22)  // P2WPKH: push 20 bytes, total 22
3106                || (redeem[1] == PUSH_32_BYTES && redeem.len() == 34)); // P2WSH: push 32 bytes, total 34
3107
3108        if is_witness_program && witness.is_some() {
3109            // For P2WSH-in-P2SH or P2WPKH-in-P2SH:
3110            // - We've already verified the redeem script hash matches (scriptPubkey check passed)
3111            // - We should NOT execute the redeem script as a normal script
3112            // - Extract the witness program from redeem script (program bytes after OP_0 and push opcode)
3113            // - For P2WPKH-in-P2SH: witness script is pubkey hash (20 bytes), witness contains signature + pubkey
3114            // - For P2WSH-in-P2SH: witness script is the last witness element, hash must match program (32 bytes)
3115
3116            // Extract program from redeem script: skip OP_0 (1 byte) + push opcode (1 byte), get program bytes
3117            let program_bytes = &redeem[2..];
3118
3119            if redeem[1] == PUSH_32_BYTES {
3120                // P2WSH-in-P2SH: program is 32 bytes, witness should contain the witness script as last element
3121                // The witness script's SHA256 hash must match the program
3122                if program_bytes.len() != 32 {
3123                    return Ok(false); // Invalid P2WSH program length
3124                }
3125
3126                // CRITICAL FIX: For P2WSH-in-P2SH, witness is now the full Witness stack
3127                // Structure: [sig1, sig2, ..., witness_script]
3128                // The last element is the witness script, which we verify the hash of
3129                // Then we execute the witness script with the remaining elements (signatures) on the stack
3130                if let Some(witness_stack) = witness {
3131                    if witness_stack.is_empty() {
3132                        return Ok(false); // P2WSH requires witness
3133                    }
3134
3135                    // Get the witness script (last element) - it's a ByteString (Vec<u8>)
3136                    let witness_script = witness_stack.last().expect("Witness stack is not empty");
3137                    let witness_script_hash = OptimizedSha256::new().hash(witness_script.as_ref());
3138                    if &witness_script_hash[..] != program_bytes {
3139                        return Ok(false); // Witness script hash doesn't match program
3140                    }
3141
3142                    // Hash matches - now push witness stack elements (except the last one, which is the script)
3143                    // onto the stack, then execute the witness script
3144                    stack.clear();
3145
3146                    // Push all witness stack elements except the last one (witness script) onto the stack
3147                    // These are the signatures and other data needed for witness script execution
3148                    for element in witness_stack.iter().take(witness_stack.len() - 1) {
3149                        stack.push(to_stack_element(element));
3150                    }
3151
3152                    // Execute the witness script with witness stack elements on the stack
3153                    // P2WSH-in-P2SH always uses WitnessV0 (BIP143). 0x8000 = WITNESS_PUBKEYTYPE
3154                    // is a key-type strictness flag and does not select Tapscript semantics.
3155                    let witness_sigversion = SigVersion::WitnessV0;
3156
3157                    // Interpreter path: no collection (P2WSH-in-P2SH).
3158                    if !eval_script_with_context_full(
3159                        witness_script,
3160                        stack,
3161                        flags,
3162                        tx,
3163                        input_index,
3164                        prevout_values,
3165                        prevout_script_pubkeys,
3166                        block_height,
3167                        median_time_past,
3168                        network,
3169                        witness_sigversion,
3170                        None, // witness script
3171                        None, // taproot_annex_hash
3172                        #[cfg(feature = "production")]
3173                        schnorr_collector,
3174                        precomputed_bip143, // WitnessV0 uses BIP143
3175                        #[cfg(feature = "production")]
3176                        sighash_cache,
3177                    )? {
3178                        return Ok(false);
3179                    }
3180                } else {
3181                    return Ok(false); // P2WSH requires witness
3182                }
3183            } else if redeem[1] == PUSH_20_BYTES {
3184                // P2WPKH-in-P2SH (BIP141): the redeem script is OP_0 <20-byte-pubkey-hash>.
3185                // Witness must be exactly [signature, pubkey].
3186                // Verify pubkey hashes to the program, then check BIP143 signature.
3187                let pubkey_hash = &redeem[2..22]; // 20-byte hash from redeem script
3188
3189                let witness_stack = match witness {
3190                    Some(w) => w,
3191                    None => return Ok(false), // P2WPKH-in-P2SH requires witness
3192                };
3193                if witness_stack.len() != 2 {
3194                    return Ok(false); // Must be exactly [sig, pubkey]
3195                }
3196                let signature_bytes = &witness_stack[0];
3197                let pubkey_bytes = &witness_stack[1];
3198
3199                if signature_bytes.is_empty() {
3200                    return Ok(false);
3201                }
3202                if pubkey_bytes.len() != 33 && pubkey_bytes.len() != 65 {
3203                    return Ok(false);
3204                }
3205
3206                // Verify pubkey hash matches program
3207                let pubkey_sha256 = OptimizedSha256::new().hash(pubkey_bytes);
3208                let computed_hash = Ripemd160::digest(pubkey_sha256);
3209                if &computed_hash[..] != pubkey_hash {
3210                    return Ok(false);
3211                }
3212
3213                // BIP143 §4.3: scriptCode for P2WPKH is the P2PKH expansion, not the witness program.
3214                let sighash_byte = signature_bytes[signature_bytes.len() - 1];
3215                let amount = prevout_values.get(input_index).copied().unwrap_or(0);
3216                let p2pkh_script_code = bip143_p2wpkh_script_code(pubkey_hash);
3217                let sighash = crate::transaction_hash::calculate_bip143_sighash(
3218                    tx,
3219                    input_index,
3220                    &p2pkh_script_code,
3221                    amount,
3222                    sighash_byte,
3223                    precomputed_bip143,
3224                )?;
3225
3226                let height = block_height.unwrap_or(0);
3227                return signature::with_secp_context(|secp| {
3228                    signature::verify_signature(
3229                        secp,
3230                        pubkey_bytes,
3231                        signature_bytes,
3232                        &sighash,
3233                        flags,
3234                        height,
3235                        network,
3236                        SigVersion::WitnessV0,
3237                    )
3238                });
3239            } else {
3240                return Ok(false); // Invalid witness program format
3241            }
3242            // P2WSH-in-P2SH path falls through to final stack check above
3243        } else {
3244            // Regular P2SH: execute the redeem script with the remaining stack (signatures pushed by scriptSig)
3245            // The redeem script will consume the signatures and should leave exactly one true value
3246            // CRITICAL FIX: Pass redeem script for P2SH sighash calculation
3247            let redeem_result = eval_script_with_context_full_inner(
3248                &redeem,
3249                stack,
3250                flags,
3251                tx,
3252                input_index,
3253                prevout_values,
3254                prevout_script_pubkeys,
3255                block_height,
3256                median_time_past,
3257                network,
3258                SigVersion::Base,
3259                Some(redeem.as_ref()), // Pass redeem script for sighash
3260                Some(script_sig), // Use same script_sig for legacy sighash pattern (e.g. P2PKH inside P2SH)
3261                None,             // taproot_annex_hash
3262                #[cfg(feature = "production")]
3263                None, // schnorr_collector
3264                None,             // precomputed_bip143 - Base sigversion
3265                #[cfg(feature = "production")]
3266                sighash_cache,
3267            )?;
3268            if !redeem_result {
3269                return Ok(false);
3270            }
3271        }
3272    }
3273
3274    // Invariant assertion: Stack size must be reasonable after scriptPubkey execution
3275    assert!(
3276        stack.len() <= 1000,
3277        "Stack size {} exceeds reasonable maximum after scriptPubkey",
3278        stack.len()
3279    );
3280
3281    // Execute witness if present
3282    // CRITICAL:
3283    // - Direct P2WPKH/P2WSH: Already handled above (witness stack pushed before scriptPubkey, witness script executed after)
3284    // - P2WSH-in-P2SH: Handled in P2SH section above (witness script executed with witness stack elements)
3285    // - P2WPKH-in-P2SH: Handled in P2SH section above (BIP143 sig/pubkey check via implicit P2PKH scriptCode)
3286    // - Regular scripts: No witness execution needed
3287    //
3288    // All witness execution should be complete by this point
3289    if let Some(_witness_stack) = witness {
3290        // All witness cases should have been handled above
3291        // If we reach here with a witness, it means we missed a case
3292        // For now, skip to avoid double execution
3293    }
3294
3295    // Final validation
3296    // SCRIPT_VERIFY_CLEANSTACK (0x100): requires exactly 1 element on the stack
3297    // This is only a consensus rule for witness scripts (handled above in witness paths).
3298    // For legacy scripts in block validation, only the top stack element is checked
3299    // to be truthy (non-empty and non-zero). CLEANSTACK for legacy is mempool policy only.
3300    const SCRIPT_VERIFY_CLEANSTACK: u32 = 0x100;
3301
3302    let final_result = if (flags & SCRIPT_VERIFY_CLEANSTACK) != 0 {
3303        // CLEANSTACK: exactly one element, must be truthy
3304        stack.len() == 1 && cast_to_bool(&stack[0])
3305    } else {
3306        // Legacy: stack non-empty, top element is truthy
3307        !stack.is_empty() && cast_to_bool(stack.last().expect("Stack is not empty"))
3308    };
3309    Ok(final_result)
3310}
3311
3312/// EvalScript with transaction context for signature verification
3313#[allow(dead_code)]
3314fn eval_script_with_context(
3315    script: &ByteString,
3316    stack: &mut Vec<StackElement>,
3317    flags: u32,
3318    tx: &Transaction,
3319    input_index: usize,
3320    prevouts: &[TransactionOutput],
3321    network: crate::types::Network,
3322) -> Result<bool> {
3323    // Convert prevouts to parallel slices for the optimized API
3324    let prevout_values: Vec<i64> = prevouts.iter().map(|p| p.value).collect();
3325    let prevout_script_pubkeys: Vec<&[u8]> =
3326        prevouts.iter().map(|p| p.script_pubkey.as_ref()).collect();
3327    eval_script_with_context_full(
3328        script,
3329        stack,
3330        flags,
3331        tx,
3332        input_index,
3333        &prevout_values,
3334        &prevout_script_pubkeys,
3335        None, // block_height
3336        None, // median_time_past
3337        network,
3338        SigVersion::Base,
3339        None, // script_sig_for_sighash
3340        None, // taproot_annex_hash
3341        #[cfg(feature = "production")]
3342        None, // schnorr_collector - No collector in this context
3343        None, // precomputed_bip143 - Base sigversion
3344        #[cfg(feature = "production")]
3345        None, // sighash_cache - no context
3346    )
3347}
3348
3349/// EvalScript with full context including block height, median time-past, and network
3350#[allow(clippy::too_many_arguments)]
3351fn eval_script_with_context_full(
3352    script: &[u8],
3353    stack: &mut Vec<StackElement>,
3354    flags: u32,
3355    tx: &Transaction,
3356    input_index: usize,
3357    prevout_values: &[i64],
3358    prevout_script_pubkeys: &[&[u8]],
3359    block_height: Option<u64>,
3360    median_time_past: Option<u64>,
3361    network: crate::types::Network,
3362    sigversion: SigVersion,
3363    script_sig_for_sighash: Option<&ByteString>,
3364    taproot_annex_hash: Option<&Hash>,
3365    #[cfg(feature = "production")] schnorr_collector: Option<
3366        &crate::bip348::SchnorrSignatureCollector,
3367    >,
3368    precomputed_bip143: Option<&crate::transaction_hash::Bip143PrecomputedHashes>,
3369    #[cfg(feature = "production")] sighash_cache: Option<
3370        &crate::transaction_hash::SighashMidstateCache,
3371    >,
3372) -> Result<bool> {
3373    #[cfg(all(feature = "production", feature = "profile"))]
3374    let _t0 = std::time::Instant::now();
3375    let r = eval_script_with_context_full_inner(
3376        script,
3377        stack,
3378        flags,
3379        tx,
3380        input_index,
3381        prevout_values,
3382        prevout_script_pubkeys,
3383        block_height,
3384        median_time_past,
3385        network,
3386        sigversion,
3387        None,
3388        script_sig_for_sighash,
3389        taproot_annex_hash,
3390        #[cfg(feature = "production")]
3391        schnorr_collector,
3392        precomputed_bip143,
3393        #[cfg(feature = "production")]
3394        sighash_cache,
3395    );
3396    #[cfg(all(feature = "production", feature = "profile"))]
3397    crate::script_profile::add_interpreter_ns(_t0.elapsed().as_nanos() as u64);
3398    r
3399}
3400
3401/// Internal function with redeem script support for P2SH sighash
3402fn eval_script_with_context_full_inner(
3403    script: &[u8],
3404    stack: &mut Vec<StackElement>,
3405    flags: u32,
3406    tx: &Transaction,
3407    input_index: usize,
3408    prevout_values: &[i64],
3409    prevout_script_pubkeys: &[&[u8]],
3410    block_height: Option<u64>,
3411    median_time_past: Option<u64>,
3412    network: crate::types::Network,
3413    sigversion: SigVersion,
3414    redeem_script_for_sighash: Option<&[u8]>,
3415    script_sig_for_sighash: Option<&ByteString>,
3416    taproot_annex_hash: Option<&Hash>,
3417    #[cfg(feature = "production")] schnorr_collector: Option<
3418        &crate::bip348::SchnorrSignatureCollector,
3419    >,
3420    precomputed_bip143: Option<&crate::transaction_hash::Bip143PrecomputedHashes>,
3421    #[cfg(feature = "production")] sighash_cache: Option<
3422        &crate::transaction_hash::SighashMidstateCache,
3423    >,
3424) -> Result<bool> {
3425    // Precondition assertions: input_index and prevout lengths validated by caller (verify_script_with_context_full).
3426    // 6d: Removed redundant assert! for input_index and prevout lengths — caller returns error on mismatch.
3427    use crate::constants::MAX_SCRIPT_SIZE;
3428    use crate::error::{ConsensusError, ScriptErrorCode};
3429
3430    // Core: MAX_SCRIPT_SIZE applies to Base and WitnessV0 only; Tapscript may exceed 10k.
3431    if (sigversion == SigVersion::Base || sigversion == SigVersion::WitnessV0)
3432        && script.len() > MAX_SCRIPT_SIZE
3433    {
3434        return Err(ConsensusError::ScriptErrorWithCode {
3435            code: ScriptErrorCode::ScriptSize,
3436            message: "Script size exceeds maximum".into(),
3437        });
3438    }
3439    assert!(
3440        stack.len() <= 1000,
3441        "Stack size {} exceeds reasonable maximum at start",
3442        stack.len()
3443    );
3444
3445    // BIP342: In Tapscript, pre-scan the entire script for OP_SUCCESSx opcodes.
3446    // If any OP_SUCCESSx is encountered (even in unexecuted branches), the whole
3447    // script succeeds immediately. This must happen before any opcode is executed.
3448    // Reference: Bitcoin Core ExecuteWitnessScript (interpreter.cpp:1836–1851).
3449    if sigversion == SigVersion::Tapscript {
3450        use crate::script::flags::SCRIPT_VERIFY_DISCOURAGE_OP_SUCCESS;
3451        let mut pc = 0usize;
3452        while pc < script.len() {
3453            let opcode = script[pc];
3454            if is_op_success(opcode) {
3455                if flags & SCRIPT_VERIFY_DISCOURAGE_OP_SUCCESS != 0 {
3456                    return Err(ConsensusError::ScriptErrorWithCode {
3457                        code: ScriptErrorCode::BadOpcode,
3458                        message: format!("OP_SUCCESSx opcode 0x{opcode:02x} is discouraged").into(),
3459                    });
3460                }
3461                return Ok(true);
3462            }
3463            // Advance past push data so we inspect opcodes, not push payloads.
3464            pc += op_advance(script, pc);
3465        }
3466    }
3467
3468    // Pre-allocate stack capacity if needed
3469    if stack.capacity() < 20 {
3470        stack.reserve(20);
3471    }
3472    let mut op_count = 0;
3473    // Invariant assertion: Op count must start at zero
3474    assert!(op_count == 0, "Op count must start at zero");
3475
3476    // Pre-allocate control_stack and altstack to avoid realloc in hot path
3477    #[cfg(feature = "production")]
3478    let mut control_stack: Vec<control_flow::ControlBlock> = Vec::with_capacity(4);
3479    #[cfg(not(feature = "production"))]
3480    let mut control_stack: Vec<control_flow::ControlBlock> = Vec::new();
3481    // Invariant assertion: Control stack must start empty
3482    assert!(control_stack.is_empty(), "Control stack must start empty");
3483
3484    #[cfg(feature = "production")]
3485    let mut altstack: Vec<StackElement> = Vec::with_capacity(8);
3486    #[cfg(not(feature = "production"))]
3487    let mut altstack: Vec<StackElement> = Vec::new();
3488
3489    // Track OP_CODESEPARATOR position for sighash calculation.
3490    // pbegincodehash: the script code used for sighash starts
3491    // from after the last OP_CODESEPARATOR (or from the beginning if none).
3492    let mut code_separator_pos: usize = 0;
3493    let mut last_codesep_opcode_pos: u32 = 0xffff_ffff;
3494
3495    // Use index-based iteration to properly handle push opcodes
3496    let mut i = 0;
3497    while i < script.len() {
3498        #[cfg(feature = "production")]
3499        {
3500            // Prefetch next cache line(s) ahead for sequential script access
3501            prefetch::prefetch_ahead(script, i, 64); // Prefetch 64 bytes ahead
3502        }
3503        // Use optimized bounds access after length check
3504        let opcode = {
3505            #[cfg(feature = "production")]
3506            {
3507                unsafe { *script.get_unchecked(i) }
3508            }
3509            #[cfg(not(feature = "production"))]
3510            {
3511                script[i]
3512            }
3513        };
3514
3515        // Cache in_false_branch state - only recompute when control stack changes
3516        let in_false_branch = control_flow::in_false_branch(&control_stack);
3517
3518        // Count non-push opcodes toward op limit (Base/WitnessV0 only; BIP342 Tapscript has no 201-op cap).
3519        if sigversion != SigVersion::Tapscript && !is_push_opcode(opcode) {
3520            op_count += 1;
3521            // Invariant assertion: Op count must not exceed limit
3522            assert!(
3523                op_count <= MAX_SCRIPT_OPS + 1,
3524                "Op count {op_count} must not exceed MAX_SCRIPT_OPS + 1"
3525            );
3526            if op_count > MAX_SCRIPT_OPS {
3527                return Err(make_operation_limit_error());
3528            }
3529        }
3530
3531        // Check combined stack + altstack size (BIP62/consensus)
3532        if stack.len() + altstack.len() > MAX_STACK_SIZE {
3533            return Err(make_stack_overflow_error());
3534        }
3535
3536        // Handle push opcodes (0x01-0x4b: direct push, OP_PUSHDATA1/2/4)
3537        if (0x01..=OP_PUSHDATA4).contains(&opcode) {
3538            let (data, advance) = if opcode <= 0x4b {
3539                // Direct push: opcode is the length (1-75 bytes)
3540                let len = opcode as usize;
3541                if i + 1 + len > script.len() {
3542                    return Ok(false); // Script truncated
3543                }
3544                (&script[i + 1..i + 1 + len], 1 + len)
3545            } else if opcode == OP_PUSHDATA1 {
3546                // OP_PUSHDATA1: next byte is length
3547                if i + 1 >= script.len() {
3548                    return Ok(false);
3549                }
3550                let len = script[i + 1] as usize;
3551                if i + 2 + len > script.len() {
3552                    return Ok(false);
3553                }
3554                (&script[i + 2..i + 2 + len], 2 + len)
3555            } else if opcode == OP_PUSHDATA2 {
3556                // OP_PUSHDATA2: next 2 bytes (little-endian) are length
3557                if i + 2 >= script.len() {
3558                    return Ok(false);
3559                }
3560                let len = u16::from_le_bytes([script[i + 1], script[i + 2]]) as usize;
3561                if i + 3 + len > script.len() {
3562                    return Ok(false);
3563                }
3564                (&script[i + 3..i + 3 + len], 3 + len)
3565            } else {
3566                // OP_PUSHDATA4: next 4 bytes (little-endian) are length
3567                // Use saturating arithmetic to avoid overflow on 32-bit platforms
3568                if i + 4 >= script.len() {
3569                    return Ok(false);
3570                }
3571                let len = u32::from_le_bytes([
3572                    script[i + 1],
3573                    script[i + 2],
3574                    script[i + 3],
3575                    script[i + 4],
3576                ]) as usize;
3577                let data_start = i.saturating_add(5);
3578                let data_end = data_start.saturating_add(len);
3579                let advance = 5usize.saturating_add(len);
3580                if advance < 5 || data_end > script.len() || data_end < data_start {
3581                    return Ok(false); // Overflow or out-of-bounds
3582                }
3583                (&script[data_start..data_end], advance)
3584            };
3585
3586            // Only push data if not in a non-executing branch
3587            if !in_false_branch {
3588                stack.push(to_stack_element(data));
3589            }
3590            i += advance;
3591            continue;
3592        }
3593
3594        // Check hottest opcodes BEFORE match statement
3595        // This eliminates dispatch overhead for the most common opcodes (OP_DUP, OP_EQUALVERIFY, OP_HASH160)
3596        // These opcodes are executed millions of times per block, so avoiding match overhead is critical
3597
3598        // OP_DUP - duplicate top stack item (VERY HOT - every P2PKH script)
3599        if opcode == OP_DUP {
3600            if !in_false_branch {
3601                if stack.is_empty() {
3602                    return Err(ConsensusError::ScriptErrorWithCode {
3603                        code: ScriptErrorCode::InvalidStackOperation,
3604                        message: "OP_DUP: empty stack".into(),
3605                    });
3606                }
3607                // Optimize OP_DUP - avoid double lookup
3608                // Use unsafe bounds access after length check (already verified above)
3609                let len = stack.len();
3610                #[cfg(feature = "production")]
3611                {
3612                    // OPTIMIZATION: Reserve capacity before push to avoid reallocation
3613                    if stack.capacity() == stack.len() {
3614                        stack.reserve(1);
3615                    }
3616                    // Clone the item using unsafe bounds access (we already checked len > 0)
3617                    let item = unsafe { stack.get_unchecked(len - 1).clone() };
3618                    stack.push(item);
3619                }
3620                #[cfg(not(feature = "production"))]
3621                {
3622                    let item = stack.last().unwrap();
3623                    stack.push(item.clone());
3624                }
3625            }
3626            i += 1;
3627            continue;
3628        }
3629
3630        // OP_EQUALVERIFY - verify top two stack items are equal (VERY HOT - every P2PKH script)
3631        if opcode == OP_EQUALVERIFY {
3632            if !in_false_branch {
3633                if stack.len() < 2 {
3634                    return Err(ConsensusError::ScriptErrorWithCode {
3635                        code: ScriptErrorCode::InvalidStackOperation,
3636                        message: "OP_EQUALVERIFY: insufficient stack items".into(),
3637                    });
3638                }
3639                let a = stack.pop().unwrap();
3640                let b = stack.pop().unwrap();
3641                if a != b {
3642                    return Ok(false);
3643                }
3644            }
3645            i += 1;
3646            continue;
3647        }
3648
3649        // OP_HASH160 - RIPEMD160(SHA256(x)) (VERY HOT - every P2PKH script)
3650        if opcode == OP_HASH160 {
3651            if !in_false_branch && !crypto_ops::op_hash160(stack)? {
3652                return Ok(false);
3653            }
3654            i += 1;
3655            continue;
3656        }
3657
3658        // OP_VERIFY - check if top stack item is non-zero (HOT - many scripts)
3659        if opcode == OP_VERIFY {
3660            if !in_false_branch {
3661                if let Some(item) = stack.pop() {
3662                    if !cast_to_bool(&item) {
3663                        return Ok(false);
3664                    }
3665                } else {
3666                    return Ok(false);
3667                }
3668            }
3669            i += 1;
3670            continue;
3671        }
3672
3673        // OP_EQUAL - check if top two stack items are equal (HOT - P2SH scripts)
3674        if opcode == OP_EQUAL {
3675            if !in_false_branch {
3676                if stack.len() < 2 {
3677                    return Err(ConsensusError::ScriptErrorWithCode {
3678                        code: ScriptErrorCode::InvalidStackOperation,
3679                        message: "OP_EQUAL: insufficient stack items".into(),
3680                    });
3681                }
3682                let a = stack.pop().unwrap();
3683                let b = stack.pop().unwrap();
3684                stack.push(to_stack_element(&[if a == b { 1 } else { 0 }]));
3685            }
3686            i += 1;
3687            continue;
3688        }
3689
3690        // OP_CHECKSIG / OP_CHECKSIGVERIFY - hot in multisig/P2SH (skip match dispatch)
3691        // OP_CHECKSIGADD (BIP 342) - Tapscript only; in Base/WitnessV0, 0xba falls through to match
3692        if opcode == OP_CHECKSIG
3693            || opcode == OP_CHECKSIGVERIFY
3694            || (opcode == OP_CHECKSIGADD && sigversion == SigVersion::Tapscript)
3695        {
3696            if !in_false_branch {
3697                let effective_script_code = Some(&script[code_separator_pos..]);
3698                let (tapscript, codesep) = if sigversion == SigVersion::Tapscript {
3699                    (Some(script), Some(last_codesep_opcode_pos))
3700                } else {
3701                    (None, None)
3702                };
3703                let ctx = context::ScriptContext {
3704                    tx,
3705                    input_index,
3706                    prevout_values,
3707                    prevout_script_pubkeys,
3708                    block_height,
3709                    median_time_past,
3710                    network,
3711                    sigversion,
3712                    redeem_script_for_sighash,
3713                    script_sig_for_sighash,
3714                    tapscript_for_sighash: tapscript,
3715                    tapscript_codesep_pos: codesep,
3716                    taproot_annex_hash,
3717                    #[cfg(feature = "production")]
3718                    schnorr_collector,
3719                    #[cfg(feature = "production")]
3720                    precomputed_bip143,
3721                    #[cfg(feature = "production")]
3722                    sighash_cache,
3723                };
3724                if !execute_opcode_with_context_full(
3725                    opcode,
3726                    stack,
3727                    flags,
3728                    &ctx,
3729                    effective_script_code,
3730                )? {
3731                    return Ok(false);
3732                }
3733            }
3734            i += 1;
3735            continue;
3736        }
3737
3738        match opcode {
3739            // OP_0 - push empty array
3740            OP_0 => {
3741                if !in_false_branch {
3742                    stack.push(to_stack_element(&[]));
3743                }
3744            }
3745
3746            // OP_1 to OP_16 - push numbers 1-16
3747            OP_1_RANGE_START..=OP_1_RANGE_END => {
3748                if !in_false_branch {
3749                    let num = opcode - OP_N_BASE;
3750                    stack.push(to_stack_element(&[num]));
3751                }
3752            }
3753
3754            // OP_1NEGATE - push -1
3755            OP_1NEGATE => {
3756                if !in_false_branch {
3757                    stack.push(to_stack_element(&[0x81])); // -1 in script number encoding
3758                }
3759            }
3760
3761            // OP_NOP - do nothing, execution continues
3762            OP_NOP => {
3763                // No operation - this is valid and execution continues
3764            }
3765
3766            // OP_VER - causes failure only when executing
3767            // OP_VER is inside the conditional-execution check,
3768            // so it only fails in executing branches. Non-executing branches skip it.
3769            // This differs from truly disabled opcodes (OP_CAT, etc.) which always fail.
3770            OP_VER => {
3771                if !in_false_branch {
3772                    return Err(ConsensusError::ScriptErrorWithCode {
3773                        code: ScriptErrorCode::DisabledOpcode,
3774                        message: "OP_VER is disabled".into(),
3775                    });
3776                }
3777            }
3778
3779            OP_IF => {
3780                // OP_IF
3781                if in_false_branch {
3782                    control_stack.push(control_flow::ControlBlock::If { executing: false });
3783                    i += 1;
3784                    continue;
3785                }
3786
3787                if stack.is_empty() {
3788                    return Err(ConsensusError::ScriptErrorWithCode {
3789                        code: ScriptErrorCode::InvalidStackOperation,
3790                        message: "OP_IF: empty stack".into(),
3791                    });
3792                }
3793                let condition_bytes = stack.pop().unwrap();
3794                let condition = cast_to_bool(&condition_bytes);
3795
3796                const SCRIPT_VERIFY_MINIMALIF: u32 = 0x2000;
3797                if (flags & SCRIPT_VERIFY_MINIMALIF) != 0
3798                    && (sigversion == SigVersion::WitnessV0 || sigversion == SigVersion::Tapscript)
3799                    && !control_flow::is_minimal_if_condition(&condition_bytes)
3800                {
3801                    return Err(ConsensusError::ScriptErrorWithCode {
3802                        code: ScriptErrorCode::MinimalIf,
3803                        message: "OP_IF condition must be minimally encoded".into(),
3804                    });
3805                }
3806
3807                control_stack.push(control_flow::ControlBlock::If {
3808                    executing: condition,
3809                });
3810            }
3811            OP_NOTIF => {
3812                // OP_NOTIF
3813                if in_false_branch {
3814                    control_stack.push(control_flow::ControlBlock::NotIf { executing: false });
3815                    i += 1;
3816                    continue;
3817                }
3818
3819                if stack.is_empty() {
3820                    return Err(ConsensusError::ScriptErrorWithCode {
3821                        code: ScriptErrorCode::InvalidStackOperation,
3822                        message: "OP_NOTIF: empty stack".into(),
3823                    });
3824                }
3825                let condition_bytes = stack.pop().unwrap();
3826                let condition = cast_to_bool(&condition_bytes);
3827
3828                const SCRIPT_VERIFY_MINIMALIF: u32 = 0x2000;
3829                if (flags & SCRIPT_VERIFY_MINIMALIF) != 0
3830                    && (sigversion == SigVersion::WitnessV0 || sigversion == SigVersion::Tapscript)
3831                    && !control_flow::is_minimal_if_condition(&condition_bytes)
3832                {
3833                    return Err(ConsensusError::ScriptErrorWithCode {
3834                        code: ScriptErrorCode::MinimalIf,
3835                        message: "OP_NOTIF condition must be minimally encoded".into(),
3836                    });
3837                }
3838
3839                control_stack.push(control_flow::ControlBlock::NotIf {
3840                    executing: !condition,
3841                });
3842            }
3843            OP_ELSE => {
3844                // OP_ELSE
3845                if let Some(block) = control_stack.last_mut() {
3846                    match block {
3847                        control_flow::ControlBlock::If { executing }
3848                        | control_flow::ControlBlock::NotIf { executing } => {
3849                            *executing = !*executing;
3850                        }
3851                    }
3852                } else {
3853                    return Err(ConsensusError::ScriptErrorWithCode {
3854                        code: ScriptErrorCode::UnbalancedConditional,
3855                        message: "OP_ELSE without matching IF/NOTIF".into(),
3856                    });
3857                }
3858            }
3859            OP_ENDIF => {
3860                // OP_ENDIF
3861                if control_stack.pop().is_none() {
3862                    return Err(ConsensusError::ScriptErrorWithCode {
3863                        code: ScriptErrorCode::UnbalancedConditional,
3864                        message: "OP_ENDIF without matching IF/NOTIF".into(),
3865                    });
3866                }
3867            }
3868
3869            // OP_DUP, OP_EQUALVERIFY, OP_HASH160 are handled BEFORE match statement for performance
3870            // (moved to avoid dispatch overhead)
3871
3872            // OP_TOALTSTACK - move top stack item to altstack
3873            OP_TOALTSTACK => {
3874                if in_false_branch {
3875                    i += 1;
3876                    continue;
3877                }
3878                if stack.is_empty() {
3879                    return Err(ConsensusError::ScriptErrorWithCode {
3880                        code: ScriptErrorCode::InvalidStackOperation,
3881                        message: "OP_TOALTSTACK: empty stack".into(),
3882                    });
3883                }
3884                altstack.push(stack.pop().unwrap());
3885            }
3886            // OP_FROMALTSTACK - move top altstack item to stack
3887            OP_FROMALTSTACK => {
3888                if in_false_branch {
3889                    i += 1;
3890                    continue;
3891                }
3892                if altstack.is_empty() {
3893                    return Err(ConsensusError::ScriptErrorWithCode {
3894                        code: ScriptErrorCode::InvalidAltstackOperation,
3895                        message: "OP_FROMALTSTACK: empty altstack".into(),
3896                    });
3897                }
3898                stack.push(altstack.pop().unwrap());
3899            }
3900            // OP_CODESEPARATOR - update sighash script code start position
3901            OP_CODESEPARATOR => {
3902                if in_false_branch {
3903                    i += 1;
3904                    continue;
3905                }
3906                code_separator_pos = i + 1;
3907                last_codesep_opcode_pos = opcode_position_at_byte(script, i);
3908            }
3909            _ => {
3910                if in_false_branch {
3911                    i += 1;
3912                    continue;
3913                }
3914
3915                // For signature opcodes, compute the effective script code for sighash:
3916                // From the last OP_CODESEPARATOR position to the end of the script.
3917                // scriptCode = slice from pbegincodehash to pend
3918                // Only allocate for opcodes that actually use the script code.
3919                let subscript_for_sighash = if matches!(
3920                    opcode,
3921                    OP_CHECKSIG
3922                        | OP_CHECKSIGVERIFY
3923                        | OP_CHECKSIGADD
3924                        | OP_CHECKMULTISIG
3925                        | OP_CHECKMULTISIGVERIFY
3926                ) {
3927                    Some(&script[code_separator_pos..])
3928                } else {
3929                    None
3930                };
3931                let effective_script_code = subscript_for_sighash.or(redeem_script_for_sighash);
3932                let (tapscript, codesep) = if sigversion == SigVersion::Tapscript {
3933                    (Some(script), Some(last_codesep_opcode_pos))
3934                } else {
3935                    (None, None)
3936                };
3937                let ctx = context::ScriptContext {
3938                    tx,
3939                    input_index,
3940                    prevout_values,
3941                    prevout_script_pubkeys,
3942                    block_height,
3943                    median_time_past,
3944                    network,
3945                    sigversion,
3946                    redeem_script_for_sighash,
3947                    script_sig_for_sighash,
3948                    tapscript_for_sighash: tapscript,
3949                    tapscript_codesep_pos: codesep,
3950                    taproot_annex_hash,
3951                    #[cfg(feature = "production")]
3952                    schnorr_collector,
3953                    #[cfg(feature = "production")]
3954                    precomputed_bip143,
3955                    #[cfg(feature = "production")]
3956                    sighash_cache,
3957                };
3958                if !execute_opcode_with_context_full(
3959                    opcode,
3960                    stack,
3961                    flags,
3962                    &ctx,
3963                    effective_script_code,
3964                )? {
3965                    return Ok(false);
3966                }
3967            }
3968        }
3969        i += 1;
3970    }
3971
3972    // Invariant: control stack must be empty — return Err if not (unclosed IF/NOTIF)
3973    if !control_stack.is_empty() {
3974        return Err(ConsensusError::ScriptErrorWithCode {
3975            code: ScriptErrorCode::UnbalancedConditional,
3976            message: "Unclosed IF/NOTIF block".into(),
3977        });
3978    }
3979
3980    // No final stack check here — EvalScript behavior.
3981    // Stack evaluation happens in verify_script_with_context_full (the VerifyScript equivalent)
3982    // after BOTH scriptSig and scriptPubKey have been executed.
3983    Ok(true)
3984}
3985
3986/// Decode a CScriptNum from byte representation.
3987/// Bitcoin's variable-length signed integer encoding (little-endian, sign bit in MSB of last byte).
3988/// CScriptNum::set_vch() — BIP62 numeric encoding.
3989#[spec_locked("5.4.5", "DecodeCScriptNum")]
3990#[blvm_spec_lock::axiom(result >= -549755813887)]
3991#[blvm_spec_lock::ensures(result >= -549755813887)]
3992#[cfg(feature = "production")]
3993#[inline(always)]
3994pub(crate) fn script_num_decode(data: &[u8], max_num_size: usize) -> Result<i64> {
3995    if data.len() > max_num_size {
3996        return Err(ConsensusError::ScriptErrorWithCode {
3997            code: ScriptErrorCode::InvalidStackOperation,
3998            message: format!(
3999                "Script number overflow: {} > {} bytes",
4000                data.len(),
4001                max_num_size
4002            )
4003            .into(),
4004        });
4005    }
4006    if data.is_empty() {
4007        return Ok(0);
4008    }
4009
4010    // Fast paths for common sizes (most script numbers are 1-2 bytes)
4011    let len = data.len();
4012    let result = match len {
4013        1 => {
4014            let byte = data[0];
4015            if byte & 0x80 != 0 {
4016                // Negative: clear sign bit and negate
4017                -((byte & 0x7f) as i64)
4018            } else {
4019                byte as i64
4020            }
4021        }
4022        2 => {
4023            let byte0 = data[0] as i64;
4024            let byte1 = data[1] as i64;
4025            let value = byte0 | (byte1 << 8);
4026            if byte1 & 0x80 != 0 {
4027                // Negative: clear sign bit and negate
4028                -(value & !(0x80i64 << 8))
4029            } else {
4030                value
4031            }
4032        }
4033        _ => {
4034            // General case for 3+ bytes
4035            let mut result: i64 = 0;
4036            for (i, &byte) in data.iter().enumerate() {
4037                result |= (byte as i64) << (8 * i);
4038            }
4039            // Check sign bit (MSB of last byte) - safe because len > 0
4040            let last_idx = len - 1;
4041            if data[last_idx] & 0x80 != 0 {
4042                // Negative: clear sign bit and negate
4043                result &= !(0x80i64 << (8 * last_idx));
4044                result = -result;
4045            }
4046            result
4047        }
4048    };
4049
4050    Ok(result)
4051}
4052
4053#[cfg(not(feature = "production"))]
4054#[inline]
4055pub(crate) fn script_num_decode(data: &[u8], max_num_size: usize) -> Result<i64> {
4056    if data.len() > max_num_size {
4057        return Err(ConsensusError::ScriptErrorWithCode {
4058            code: ScriptErrorCode::InvalidStackOperation,
4059            message: format!(
4060                "Script number overflow: {} > {} bytes",
4061                data.len(),
4062                max_num_size
4063            )
4064            .into(),
4065        });
4066    }
4067    if data.is_empty() {
4068        return Ok(0);
4069    }
4070    // Little-endian decode
4071    let mut result: i64 = 0;
4072    for (i, &byte) in data.iter().enumerate() {
4073        result |= (byte as i64) << (8 * i);
4074    }
4075    // Check sign bit (MSB of last byte)
4076    if data.last().expect("Data is not empty") & 0x80 != 0 {
4077        // Negative: clear sign bit and negate
4078        result &= !(0x80i64 << (8 * (data.len() - 1)));
4079        result = -result;
4080    }
4081    Ok(result)
4082}
4083
4084/// Encode an i64 as CScriptNum byte representation.
4085/// CScriptNum::serialize() — BIP62 numeric encoding.
4086#[cfg(feature = "production")]
4087pub(crate) fn script_num_encode(value: i64) -> Vec<u8> {
4088    // Fast paths for common values
4089    match value {
4090        0 => return vec![],
4091        1 => return vec![1],
4092        -1 => return vec![0x81],
4093        _ => {}
4094    }
4095
4096    let neg = value < 0;
4097    let mut absvalue = if neg {
4098        (-(value as i128)) as u64
4099    } else {
4100        value as u64
4101    };
4102    // Pre-allocate Vec: most script numbers are 1-4 bytes
4103    let mut result = Vec::with_capacity(4);
4104    while absvalue > 0 {
4105        result.push((absvalue & 0xff) as u8);
4106        absvalue >>= 8;
4107    }
4108    // If MSB is set, add extra byte for sign
4109    if result.last().expect("Result is not empty (absvalue > 0)") & 0x80 != 0 {
4110        result.push(if neg { 0x80 } else { 0x00 });
4111    } else if neg {
4112        *result.last_mut().unwrap() |= 0x80;
4113    }
4114    result
4115}
4116
4117#[cfg(not(feature = "production"))]
4118pub(crate) fn script_num_encode(value: i64) -> Vec<u8> {
4119    if value == 0 {
4120        return vec![];
4121    }
4122    let neg = value < 0;
4123    let mut absvalue = if neg {
4124        (-(value as i128)) as u64
4125    } else {
4126        value as u64
4127    };
4128    let mut result = Vec::new();
4129    while absvalue > 0 {
4130        result.push((absvalue & 0xff) as u8);
4131        absvalue >>= 8;
4132    }
4133    // If MSB is set, add extra byte for sign
4134    if result.last().expect("Result is not empty (absvalue > 0)") & 0x80 != 0 {
4135        result.push(if neg { 0x80 } else { 0x00 });
4136    } else if neg {
4137        *result.last_mut().unwrap() |= 0x80;
4138    }
4139    result
4140}
4141
4142/// Execute a single opcode (currently ignores sigversion; accepts it for future compatibility)
4143#[cfg(feature = "production")]
4144#[inline(always)]
4145fn execute_opcode(
4146    opcode: u8,
4147    stack: &mut Vec<StackElement>,
4148    flags: u32,
4149    _sigversion: SigVersion,
4150) -> Result<bool> {
4151    match opcode {
4152        // OP_0 - push empty array
4153        OP_0 => {
4154            stack.push(to_stack_element(&[]));
4155            Ok(true)
4156        }
4157
4158        // OP_1 to OP_16 - push numbers 1-16
4159        OP_1..=OP_16 => {
4160            let num = opcode - OP_N_BASE;
4161            stack.push(to_stack_element(&[num]));
4162            Ok(true)
4163        }
4164
4165        // OP_NOP - do nothing, execution continues
4166        OP_NOP => Ok(true),
4167
4168        // OP_VER - disabled opcode, always fails
4169        OP_VER => Ok(false),
4170
4171        // OP_DEPTH - push stack size
4172        OP_DEPTH => {
4173            let depth = stack.len() as i64;
4174            stack.push(to_stack_element(&script_num_encode(depth)));
4175            Ok(true)
4176        }
4177
4178        // OP_DUP - duplicate top stack item
4179        OP_DUP => {
4180            if let Some(item) = stack.last().cloned() {
4181                stack.push(item);
4182                Ok(true)
4183            } else {
4184                Ok(false)
4185            }
4186        }
4187
4188        // OP_RIPEMD160 - RIPEMD160(x)
4189        OP_RIPEMD160 => crypto_ops::op_ripemd160(stack),
4190
4191        // OP_SHA1 - SHA1(x)
4192        OP_SHA1 => crypto_ops::op_sha1(stack),
4193
4194        // OP_SHA256 - SHA256(x)
4195        OP_SHA256 => crypto_ops::op_sha256(stack),
4196
4197        // OP_HASH160 - RIPEMD160(SHA256(x))
4198        OP_HASH160 => crypto_ops::op_hash160(stack),
4199
4200        // OP_HASH256 - SHA256(SHA256(x))
4201        OP_HASH256 => crypto_ops::op_hash256(stack),
4202
4203        // OP_EQUAL - check if top two stack items are equal.
4204        // Pushes [] (empty = false) or [0x01] (true), matching Core's vchFalse/vchTrue.
4205        OP_EQUAL => {
4206            if stack.len() < 2 {
4207                return Err(ConsensusError::ScriptErrorWithCode {
4208                    code: ScriptErrorCode::InvalidStackOperation,
4209                    message: "OP_EQUAL: insufficient stack items".into(),
4210                });
4211            }
4212            let a = stack.pop().unwrap();
4213            let b = stack.pop().unwrap();
4214            stack.push(to_stack_element(if a == b { &[1u8] } else { &[] }));
4215            Ok(true)
4216        }
4217
4218        // OP_EQUALVERIFY - verify top two stack items are equal
4219        // OP_EQUAL followed by pop if equal
4220        OP_EQUALVERIFY => {
4221            if stack.len() < 2 {
4222                return Err(ConsensusError::ScriptErrorWithCode {
4223                    code: ScriptErrorCode::InvalidStackOperation,
4224                    message: "OP_EQUALVERIFY: insufficient stack items".into(),
4225                });
4226            }
4227            let a = stack.pop().unwrap();
4228            let b = stack.pop().unwrap();
4229            let f_equal = a == b;
4230            // Push result (like OP_EQUAL does)
4231            stack.push(to_stack_element(&[if f_equal { 1 } else { 0 }]));
4232            if f_equal {
4233                // Pop the true value
4234                stack.pop();
4235                Ok(true)
4236            } else {
4237                Err(ConsensusError::ScriptErrorWithCode {
4238                    code: ScriptErrorCode::EqualVerify,
4239                    message: "OP_EQUALVERIFY: stack items not equal".into(),
4240                })
4241            }
4242        }
4243
4244        // OP_CHECKSIG - verify ECDSA signature (simple path, no tx context)
4245        OP_CHECKSIG => crypto_ops::op_checksig_simple(stack, flags),
4246
4247        // OP_CHECKSIGVERIFY - verify ECDSA signature and fail if invalid (simple path)
4248        OP_CHECKSIGVERIFY => crypto_ops::op_checksigverify_simple(stack, flags),
4249
4250        // OP_RETURN - always fail (unspendable output)
4251        OP_RETURN => Ok(false),
4252
4253        // OP_VERIFY - check if top stack item is non-zero
4254        OP_VERIFY => {
4255            if let Some(item) = stack.pop() {
4256                Ok(cast_to_bool(&item))
4257            } else {
4258                Ok(false)
4259            }
4260        }
4261
4262        // OP_CHECKLOCKTIMEVERIFY (BIP65)
4263        // When SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY flag is not set, behaves as NOP (BIP65 rule).
4264        // With the flag set but no tx context, fails. Use verify_script_with_context for full CLTV.
4265        OP_CHECKLOCKTIMEVERIFY => {
4266            const SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY: u32 = 0x200;
4267            if (flags & SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY) == 0 {
4268                Ok(true)
4269            } else {
4270                Ok(false)
4271            }
4272        }
4273
4274        // OP_CHECKSEQUENCEVERIFY (BIP112)
4275        // When SCRIPT_VERIFY_CHECKSEQUENCEVERIFY flag is not set, behaves as NOP (BIP112 rule).
4276        // With the flag set but no tx context, fails. Use verify_script_with_context for full CSV.
4277        OP_CHECKSEQUENCEVERIFY => {
4278            const SCRIPT_VERIFY_CHECKSEQUENCEVERIFY: u32 = 0x400;
4279            if (flags & SCRIPT_VERIFY_CHECKSEQUENCEVERIFY) == 0 {
4280                Ok(true)
4281            } else {
4282                Ok(false)
4283            }
4284        }
4285
4286        // OP_IFDUP - duplicate top stack item if it's non-zero
4287        OP_IFDUP => {
4288            if let Some(item) = stack.last().cloned() {
4289                if cast_to_bool(&item) {
4290                    stack.push(item);
4291                }
4292                Ok(true)
4293            } else {
4294                Ok(false)
4295            }
4296        }
4297
4298        // OP_DEPTH - push stack size (duplicate handler removed, using single implementation)
4299        // OP_DROP - remove top stack item
4300        OP_DROP => {
4301            if stack.pop().is_some() {
4302                Ok(true)
4303            } else {
4304                Ok(false)
4305            }
4306        }
4307
4308        // OP_NIP - remove second-to-top stack item
4309        OP_NIP => {
4310            if stack.len() >= 2 {
4311                let top = stack.pop().unwrap();
4312                stack.pop(); // Remove second-to-top
4313                stack.push(top);
4314                Ok(true)
4315            } else {
4316                Ok(false)
4317            }
4318        }
4319
4320        // OP_OVER - copy second-to-top stack item to top
4321        OP_OVER => {
4322            if stack.len() >= 2 {
4323                let len = stack.len();
4324                #[cfg(feature = "production")]
4325                {
4326                    // Use proven bounds after length check
4327                    unsafe {
4328                        let second = stack.get_unchecked(len - 2);
4329                        stack.push(second.clone());
4330                    }
4331                }
4332                #[cfg(not(feature = "production"))]
4333                {
4334                    let second = stack[stack.len() - 2].clone();
4335                    stack.push(second);
4336                }
4337                Ok(true)
4338            } else {
4339                Ok(false)
4340            }
4341        }
4342
4343        // OP_PICK - copy nth stack item to top
4344        OP_PICK => {
4345            if let Some(n_bytes) = stack.pop() {
4346                // Use script_num_decode to properly handle CScriptNum encoding
4347                // (empty [] = 0, [0x00] = 0, [0x01] = 1, etc.)
4348                let n_val = script_num_decode(&n_bytes, 4)?;
4349                if n_val < 0 || n_val as usize >= stack.len() {
4350                    return Ok(false);
4351                }
4352                let n = n_val as usize;
4353                let len = stack.len();
4354                #[cfg(feature = "production")]
4355                {
4356                    // Use proven bounds after length check (n_val < stack.len() already checked)
4357                    unsafe {
4358                        let item = stack.get_unchecked(len - 1 - n);
4359                        stack.push(item.clone());
4360                    }
4361                }
4362                #[cfg(not(feature = "production"))]
4363                {
4364                    let item = stack[stack.len() - 1 - n].clone();
4365                    stack.push(item);
4366                }
4367                Ok(true)
4368            } else {
4369                Ok(false)
4370            }
4371        }
4372
4373        // OP_ROLL - move nth stack item to top
4374        OP_ROLL => {
4375            if let Some(n_bytes) = stack.pop() {
4376                // Use script_num_decode to properly handle CScriptNum encoding
4377                // (empty [] = 0, which is a valid no-op roll)
4378                let n_val = script_num_decode(&n_bytes, 4)?;
4379                if n_val < 0 || n_val as usize >= stack.len() {
4380                    return Ok(false);
4381                }
4382                let n = n_val as usize;
4383                let len = stack.len();
4384                #[cfg(feature = "production")]
4385                {
4386                    // Use proven bounds after length check (n_val < stack.len() already checked)
4387                    let idx = len - 1 - n;
4388                    let item = stack.remove(idx);
4389                    stack.push(item);
4390                }
4391                #[cfg(not(feature = "production"))]
4392                {
4393                    let item = stack.remove(stack.len() - 1 - n);
4394                    stack.push(item);
4395                }
4396                Ok(true)
4397            } else {
4398                Ok(false)
4399            }
4400        }
4401
4402        // OP_ROT - rotate top 3 stack items
4403        OP_ROT => {
4404            if stack.len() >= 3 {
4405                let top = stack.pop().unwrap();
4406                let second = stack.pop().unwrap();
4407                let third = stack.pop().unwrap();
4408                stack.push(second);
4409                stack.push(top);
4410                stack.push(third);
4411                Ok(true)
4412            } else {
4413                Ok(false)
4414            }
4415        }
4416
4417        // OP_SWAP - swap top 2 stack items
4418        OP_SWAP => {
4419            if stack.len() >= 2 {
4420                let top = stack.pop().unwrap();
4421                let second = stack.pop().unwrap();
4422                stack.push(top);
4423                stack.push(second);
4424                Ok(true)
4425            } else {
4426                Ok(false)
4427            }
4428        }
4429
4430        // OP_TUCK - copy top stack item to before second-to-top
4431        OP_TUCK => {
4432            if stack.len() >= 2 {
4433                let top = stack.pop().unwrap();
4434                let second = stack.pop().unwrap();
4435                stack.push(top.clone());
4436                stack.push(second);
4437                stack.push(top);
4438                Ok(true)
4439            } else {
4440                Ok(false)
4441            }
4442        }
4443
4444        // OP_2DROP - remove top 2 stack items
4445        OP_2DROP => {
4446            if stack.len() >= 2 {
4447                stack.pop();
4448                stack.pop();
4449                Ok(true)
4450            } else {
4451                Ok(false)
4452            }
4453        }
4454
4455        // OP_2DUP - duplicate top 2 stack items
4456        OP_2DUP => {
4457            if stack.len() >= 2 {
4458                let top = stack[stack.len() - 1].clone();
4459                let second = stack[stack.len() - 2].clone();
4460                stack.push(second);
4461                stack.push(top);
4462                Ok(true)
4463            } else {
4464                Ok(false)
4465            }
4466        }
4467
4468        // OP_3DUP - duplicate top 3 stack items
4469        OP_3DUP => {
4470            if stack.len() >= 3 {
4471                let top = stack[stack.len() - 1].clone();
4472                let second = stack[stack.len() - 2].clone();
4473                let third = stack[stack.len() - 3].clone();
4474                stack.push(third);
4475                stack.push(second);
4476                stack.push(top);
4477                Ok(true)
4478            } else {
4479                Ok(false)
4480            }
4481        }
4482
4483        // OP_2OVER - copy second pair of stack items to top
4484        OP_2OVER => {
4485            if stack.len() >= 4 {
4486                let fourth = stack[stack.len() - 4].clone();
4487                let third = stack[stack.len() - 3].clone();
4488                stack.push(fourth);
4489                stack.push(third);
4490                Ok(true)
4491            } else {
4492                Ok(false)
4493            }
4494        }
4495
4496        // OP_2ROT - move the pair at positions 5 and 6 from top to top.
4497        // Before: [... a b c d e f] (f=top). After: [... c d e f a b] (b=top).
4498        OP_2ROT => {
4499            if stack.len() >= 6 {
4500                // Remove from bottom-most of the pair first (index 0 = 6th from top).
4501                let sixth = stack.remove(stack.len() - 6); // a
4502                // After first remove the stack shrank; 5th from original top is now at index 0.
4503                let fifth = stack.remove(stack.len() - 5); // b
4504                // Push in original order: sixth (a) below, fifth (b) on top.
4505                stack.push(sixth);
4506                stack.push(fifth);
4507                Ok(true)
4508            } else {
4509                Ok(false)
4510            }
4511        }
4512
4513        // OP_2SWAP - swap second pair of stack items
4514        OP_2SWAP => {
4515            if stack.len() >= 4 {
4516                let top = stack.pop().unwrap();
4517                let second = stack.pop().unwrap();
4518                let third = stack.pop().unwrap();
4519                let fourth = stack.pop().unwrap();
4520                stack.push(second);
4521                stack.push(top);
4522                stack.push(fourth);
4523                stack.push(third);
4524                Ok(true)
4525            } else {
4526                Ok(false)
4527            }
4528        }
4529
4530        // OP_SIZE - push size of top stack item
4531        // OP_SIZE - push the byte length of top stack item (does NOT pop)
4532        OP_SIZE => {
4533            if let Some(item) = stack.last() {
4534                let size = item.len() as i64;
4535                stack.push(to_stack_element(&script_num_encode(size)));
4536                Ok(true)
4537            } else {
4538                Ok(false)
4539            }
4540        }
4541
4542        // --- Arithmetic opcodes ---
4543        // All use CScriptNum encoding (max 4 bytes by default)
4544
4545        // OP_1ADD - increment top by 1
4546        OP_1ADD => {
4547            if let Some(item) = stack.pop() {
4548                let a = script_num_decode(&item, 4)?;
4549                stack.push(to_stack_element(&script_num_encode(a + 1)));
4550                Ok(true)
4551            } else {
4552                Ok(false)
4553            }
4554        }
4555        // OP_1SUB - decrement top by 1
4556        OP_1SUB => {
4557            if let Some(item) = stack.pop() {
4558                let a = script_num_decode(&item, 4)?;
4559                stack.push(to_stack_element(&script_num_encode(a - 1)));
4560                Ok(true)
4561            } else {
4562                Ok(false)
4563            }
4564        }
4565        // OP_2MUL - DISABLED
4566        OP_2MUL => Err(ConsensusError::ScriptErrorWithCode {
4567            code: ScriptErrorCode::DisabledOpcode,
4568            message: "OP_2MUL is disabled".into(),
4569        }),
4570        // OP_2DIV - DISABLED
4571        OP_2DIV => Err(ConsensusError::ScriptErrorWithCode {
4572            code: ScriptErrorCode::DisabledOpcode,
4573            message: "OP_2DIV is disabled".into(),
4574        }),
4575        // OP_NEGATE - negate top
4576        OP_NEGATE => {
4577            if let Some(item) = stack.pop() {
4578                let a = script_num_decode(&item, 4)?;
4579                stack.push(to_stack_element(&script_num_encode(-a)));
4580                Ok(true)
4581            } else {
4582                Ok(false)
4583            }
4584        }
4585        // OP_ABS - absolute value
4586        OP_ABS => {
4587            if let Some(item) = stack.pop() {
4588                let a = script_num_decode(&item, 4)?;
4589                stack.push(to_stack_element(&script_num_encode(a.abs())));
4590                Ok(true)
4591            } else {
4592                Ok(false)
4593            }
4594        }
4595        // OP_NOT - logical NOT: 0 → 1, nonzero → 0
4596        OP_NOT => {
4597            if let Some(item) = stack.pop() {
4598                let a = script_num_decode(&item, 4)?;
4599                stack.push(to_stack_element(&script_num_encode(if a == 0 {
4600                    1
4601                } else {
4602                    0
4603                })));
4604                Ok(true)
4605            } else {
4606                Ok(false)
4607            }
4608        }
4609        // OP_0NOTEQUAL - 0 → 0, nonzero → 1
4610        OP_0NOTEQUAL => {
4611            if let Some(item) = stack.pop() {
4612                let a = script_num_decode(&item, 4)?;
4613                stack.push(to_stack_element(&script_num_encode(if a != 0 {
4614                    1
4615                } else {
4616                    0
4617                })));
4618                Ok(true)
4619            } else {
4620                Ok(false)
4621            }
4622        }
4623        OP_ADD => arithmetic::op_add(stack),
4624        OP_SUB => arithmetic::op_sub(stack),
4625        OP_MUL => arithmetic::op_mul_disabled(),
4626        OP_DIV => arithmetic::op_div_disabled(),
4627        OP_MOD => arithmetic::op_mod_disabled(),
4628        OP_LSHIFT => arithmetic::op_lshift_disabled(),
4629        OP_RSHIFT => arithmetic::op_rshift_disabled(),
4630        OP_BOOLAND => arithmetic::op_booland(stack),
4631        OP_BOOLOR => arithmetic::op_boolor(stack),
4632        OP_NUMEQUAL => arithmetic::op_numequal(stack),
4633        OP_NUMEQUALVERIFY => arithmetic::op_numequalverify(stack),
4634        OP_NUMNOTEQUAL => arithmetic::op_numnotequal(stack),
4635        OP_LESSTHAN => arithmetic::op_lessthan(stack),
4636        OP_GREATERTHAN => arithmetic::op_greaterthan(stack),
4637        OP_LESSTHANOREQUAL => arithmetic::op_lessthanorequal(stack),
4638        OP_GREATERTHANOREQUAL => arithmetic::op_greaterthanorequal(stack),
4639        OP_MIN => arithmetic::op_min(stack),
4640        OP_MAX => arithmetic::op_max(stack),
4641        OP_WITHIN => arithmetic::op_within(stack),
4642
4643        // OP_CODESEPARATOR - marks position for sighash (no-op in execute_opcode)
4644        OP_CODESEPARATOR => Ok(true),
4645
4646        // OP_NOP1 and OP_NOP5-OP_NOP10 - no-ops
4647        // Note: OP_NOP4 (0xb3) is used for OP_CHECKTEMPLATEVERIFY (BIP119)
4648        OP_NOP1 | OP_NOP5..=OP_NOP10 => Ok(true),
4649
4650        // OP_CHECKTEMPLATEVERIFY (NOP4 / BIP 119)
4651        // Treated as NOP when the CTV flag is not in the verification flags.
4652        // Full validation requires tx context and the SCRIPT_VERIFY_CHECKTEMPLATEVERIFY flag.
4653        OP_CHECKTEMPLATEVERIFY => {
4654            const SCRIPT_VERIFY_CHECKTEMPLATEVERIFY: u32 = 0x8000;
4655            #[cfg(feature = "ctv")]
4656            if (flags & SCRIPT_VERIFY_CHECKTEMPLATEVERIFY) != 0 {
4657                return Err(ConsensusError::ScriptErrorWithCode {
4658                    code: ScriptErrorCode::TxInvalid,
4659                    message: "OP_CHECKTEMPLATEVERIFY requires transaction context".into(),
4660                });
4661            }
4662            Ok(true)
4663        }
4664
4665        // Disabled string opcodes - must return error per consensus
4666        OP_DISABLED_STRING_RANGE_START..=OP_DISABLED_STRING_RANGE_END
4667        | OP_DISABLED_BITWISE_RANGE_START..=OP_DISABLED_BITWISE_RANGE_END => {
4668            Err(ConsensusError::ScriptErrorWithCode {
4669                code: ScriptErrorCode::DisabledOpcode,
4670                message: format!("Disabled opcode 0x{opcode:02x}").into(),
4671            })
4672        }
4673
4674        // OP_1NEGATE - push -1 onto stack
4675        OP_1NEGATE => {
4676            stack.push(to_stack_element(&[0x81]));
4677            Ok(true)
4678        }
4679
4680        // OP_CHECKMULTISIG - verify m-of-n multisig without transaction context.
4681        // Handles stack mechanics correctly for all cases; succeeds only when m=0
4682        // (no signatures to verify). Vectors requiring real ECDSA verification need
4683        // verify_script_with_context_full.
4684        OP_CHECKMULTISIG => {
4685            if stack.is_empty() {
4686                return Ok(false);
4687            }
4688            let n_bytes = stack.pop().unwrap();
4689            let n = match script_num_decode(&n_bytes, 4) {
4690                Ok(v) if (0..=20).contains(&v) => v as usize,
4691                _ => return Ok(false),
4692            };
4693            if stack.len() < n {
4694                return Ok(false);
4695            }
4696            for _ in 0..n {
4697                stack.pop();
4698            }
4699            if stack.is_empty() {
4700                return Ok(false);
4701            }
4702            let m_bytes = stack.pop().unwrap();
4703            let m = match script_num_decode(&m_bytes, 4) {
4704                Ok(v) if v >= 0 && v as usize <= n => v as usize,
4705                _ => return Ok(false),
4706            };
4707            if stack.len() < m {
4708                return Ok(false);
4709            }
4710            for _ in 0..m {
4711                stack.pop();
4712            }
4713            // Pop the mandatory dummy element (Bitcoin off-by-one quirk)
4714            if stack.is_empty() {
4715                return Ok(false);
4716            }
4717            stack.pop();
4718            // Without tx context only 0-sig multisig can succeed
4719            stack.push(to_stack_element(&[if m == 0 { 1 } else { 0 }]));
4720            Ok(true)
4721        }
4722
4723        // OP_CHECKMULTISIGVERIFY - CHECKMULTISIG then VERIFY
4724        OP_CHECKMULTISIGVERIFY => {
4725            if stack.is_empty() {
4726                return Ok(false);
4727            }
4728            let n_bytes = stack.pop().unwrap();
4729            let n = match script_num_decode(&n_bytes, 4) {
4730                Ok(v) if (0..=20).contains(&v) => v as usize,
4731                _ => return Ok(false),
4732            };
4733            if stack.len() < n {
4734                return Ok(false);
4735            }
4736            for _ in 0..n {
4737                stack.pop();
4738            }
4739            if stack.is_empty() {
4740                return Ok(false);
4741            }
4742            let m_bytes = stack.pop().unwrap();
4743            let m = match script_num_decode(&m_bytes, 4) {
4744                Ok(v) if v >= 0 && v as usize <= n => v as usize,
4745                _ => return Ok(false),
4746            };
4747            if stack.len() < m {
4748                return Ok(false);
4749            }
4750            for _ in 0..m {
4751                stack.pop();
4752            }
4753            if stack.is_empty() {
4754                return Ok(false);
4755            }
4756            stack.pop();
4757            // Verify: succeed only if m == 0
4758            Ok(m == 0)
4759        }
4760
4761        // Unknown opcode
4762        _ => Ok(false),
4763    }
4764}
4765
4766/// Execute a single opcode with transaction context for signature verification
4767#[allow(dead_code)]
4768fn execute_opcode_with_context(
4769    opcode: u8,
4770    stack: &mut Vec<StackElement>,
4771    flags: u32,
4772    tx: &Transaction,
4773    input_index: usize,
4774    prevouts: &[TransactionOutput],
4775    network: crate::types::Network,
4776) -> Result<bool> {
4777    // Convert prevouts to parallel slices for the optimized API
4778    let prevout_values: Vec<i64> = prevouts.iter().map(|p| p.value).collect();
4779    let prevout_script_pubkeys: Vec<&[u8]> =
4780        prevouts.iter().map(|p| p.script_pubkey.as_ref()).collect();
4781    let ctx = context::ScriptContext {
4782        tx,
4783        input_index,
4784        prevout_values: &prevout_values,
4785        prevout_script_pubkeys: &prevout_script_pubkeys,
4786        block_height: None,
4787        median_time_past: None,
4788        network,
4789        sigversion: SigVersion::Base,
4790        redeem_script_for_sighash: None,
4791        script_sig_for_sighash: None,
4792        tapscript_for_sighash: None,
4793        tapscript_codesep_pos: None,
4794        taproot_annex_hash: None,
4795        #[cfg(feature = "production")]
4796        schnorr_collector: None,
4797        #[cfg(feature = "production")]
4798        precomputed_bip143: None,
4799        #[cfg(feature = "production")]
4800        sighash_cache: None,
4801    };
4802    execute_opcode_with_context_full(opcode, stack, flags, &ctx, None)
4803}
4804
4805/// Parse P2SH-P2PKH scriptSig for batch sighash precompute. Zero-allocation.
4806/// script_sig = [sig, pubkey, redeem] where redeem is P2PKH (25 bytes).
4807/// Returns (sighash_byte, redeem_slice) or None. Pub(crate) for block.rs.
4808#[cfg(feature = "production")]
4809#[inline(always)]
4810pub(crate) fn parse_p2sh_p2pkh_for_precompute(script_sig: &[u8]) -> Option<(u8, &[u8])> {
4811    let mut i = 0;
4812    let (adv1, s_start, s_end) = parse_one_data_push(script_sig, i)?;
4813    i += adv1;
4814    if i >= script_sig.len() {
4815        return None;
4816    }
4817    let (adv2, _p_start, _p_end) = parse_one_data_push(script_sig, i)?;
4818    i += adv2;
4819    if i >= script_sig.len() {
4820        return None;
4821    }
4822    let (adv3, r_start, r_end) = parse_one_data_push(script_sig, i)?;
4823    i += adv3;
4824    if i != script_sig.len() {
4825        return None;
4826    }
4827    let sig = &script_sig[s_start..s_end];
4828    let redeem = &script_sig[r_start..r_end];
4829    if sig.is_empty() || redeem.len() != 25 {
4830        return None;
4831    }
4832    if redeem[0] != OP_DUP
4833        || redeem[1] != OP_HASH160
4834        || redeem[2] != PUSH_20_BYTES
4835        || redeem[23] != OP_EQUALVERIFY
4836        || redeem[24] != OP_CHECKSIG
4837    {
4838        return None;
4839    }
4840    Some((sig[sig.len() - 1], redeem))
4841}
4842
4843/// Zero-allocation parser for P2PKH scriptSig: exactly two data pushes (`signature`, then `pubkey`).
4844/// Returns (sig_slice, pubkey_slice) borrowing into script_sig, or None if invalid.
4845/// Pub(crate) for batch sighash precompute in block.rs.
4846#[inline(always)]
4847pub(crate) fn parse_p2pkh_script_sig(script_sig: &[u8]) -> Option<(&[u8], &[u8])> {
4848    let mut i = 0;
4849    let (adv1, s_start, s_end) = parse_one_data_push(script_sig, i)?;
4850    i += adv1;
4851    if i >= script_sig.len() {
4852        return None;
4853    }
4854    let (adv2, p_start, p_end) = parse_one_data_push(script_sig, i)?;
4855    i += adv2;
4856    if i != script_sig.len() {
4857        return None;
4858    }
4859    Some((&script_sig[s_start..s_end], &script_sig[p_start..p_end]))
4860}
4861
4862/// Parse P2PK scriptSig as single push (signature). Returns sig slice or None.
4863/// Used for P2PK pre-extraction in producer.
4864pub(crate) fn parse_p2pk_script_sig(script_sig: &[u8]) -> Option<&[u8]> {
4865    let (advance, data_start, data_end) = parse_one_data_push(script_sig, 0)?;
4866    if advance != script_sig.len() {
4867        return None;
4868    }
4869    Some(&script_sig[data_start..data_end])
4870}
4871
4872/// Parse a single push opcode at `i`, return (advance, data_start, data_end). Rejects OP_0 and numerics.
4873fn parse_one_data_push(script: &[u8], i: usize) -> Option<(usize, usize, usize)> {
4874    if i >= script.len() {
4875        return None;
4876    }
4877    let opcode = script[i];
4878    let (advance, data_start, data_end) = if opcode == OP_0 {
4879        return None;
4880    } else if opcode <= 0x4b {
4881        let len = opcode as usize;
4882        if i + 1 + len > script.len() {
4883            return None;
4884        }
4885        (1 + len, i + 1, i + 1 + len)
4886    } else if opcode == OP_PUSHDATA1 {
4887        if i + 1 >= script.len() {
4888            return None;
4889        }
4890        let len = script[i + 1] as usize;
4891        if i + 2 + len > script.len() {
4892            return None;
4893        }
4894        (2 + len, i + 2, i + 2 + len)
4895    } else if opcode == OP_PUSHDATA2 {
4896        if i + 2 >= script.len() {
4897            return None;
4898        }
4899        let len = u16::from_le_bytes([script[i + 1], script[i + 2]]) as usize;
4900        if i + 3 + len > script.len() {
4901            return None;
4902        }
4903        (3 + len, i + 3, i + 3 + len)
4904    } else if opcode == OP_PUSHDATA4 {
4905        if i + 4 >= script.len() {
4906            return None;
4907        }
4908        let len = u32::from_le_bytes([script[i + 1], script[i + 2], script[i + 3], script[i + 4]])
4909            as usize;
4910        if i + 5 + len > script.len() {
4911            return None;
4912        }
4913        (5 + len, i + 5, i + 5 + len)
4914    } else {
4915        return None;
4916    };
4917    Some((advance, data_start, data_end))
4918}
4919
4920/// P2SH Push-Only Validation (Orange Paper 5.2.1).
4921/// Returns true if script_sig contains only push opcodes (valid), false otherwise (invalid).
4922#[spec_locked("5.2.1", "P2SHPushOnlyCheck")]
4923pub fn p2sh_push_only_check(script_sig: &[u8]) -> bool {
4924    parse_script_sig_push_only(script_sig).is_some()
4925}
4926
4927/// Parse script_sig as push-only and return pushed items in order.
4928/// Returns None if script contains non-push opcodes or invalid push encoding.
4929/// Used by P2PKH fast-path to get [signature, pubkey] without running the interpreter.
4930fn parse_script_sig_push_only(script_sig: &[u8]) -> Option<Vec<StackElement>> {
4931    let mut out = Vec::new();
4932    let mut i = 0;
4933    while i < script_sig.len() {
4934        let opcode = script_sig[i];
4935        if !is_push_opcode(opcode) {
4936            return None;
4937        }
4938        let (advance, data) = if opcode == OP_0 {
4939            (1, vec![])
4940        } else if opcode <= 0x4b {
4941            let len = opcode as usize;
4942            if i + 1 + len > script_sig.len() {
4943                return None;
4944            }
4945            (1 + len, script_sig[i + 1..i + 1 + len].to_vec())
4946        } else if opcode == OP_PUSHDATA1 {
4947            if i + 1 >= script_sig.len() {
4948                return None;
4949            }
4950            let len = script_sig[i + 1] as usize;
4951            if i + 2 + len > script_sig.len() {
4952                return None;
4953            }
4954            (2 + len, script_sig[i + 2..i + 2 + len].to_vec())
4955        } else if opcode == OP_PUSHDATA2 {
4956            if i + 2 >= script_sig.len() {
4957                return None;
4958            }
4959            let len = u16::from_le_bytes([script_sig[i + 1], script_sig[i + 2]]) as usize;
4960            if i + 3 + len > script_sig.len() {
4961                return None;
4962            }
4963            (3 + len, script_sig[i + 3..i + 3 + len].to_vec())
4964        } else if opcode == OP_PUSHDATA4 {
4965            if i + 4 >= script_sig.len() {
4966                return None;
4967            }
4968            let len = u32::from_le_bytes([
4969                script_sig[i + 1],
4970                script_sig[i + 2],
4971                script_sig[i + 3],
4972                script_sig[i + 4],
4973            ]) as usize;
4974            if i + 5 + len > script_sig.len() {
4975                return None;
4976            }
4977            (5 + len, script_sig[i + 5..i + 5 + len].to_vec())
4978        } else if (OP_1NEGATE..=OP_16).contains(&opcode) {
4979            // Single-byte push: push the numeric value as minimal bytes
4980            let n = script_num_from_opcode(opcode);
4981            (1, script_num_encode(n))
4982        } else {
4983            return None;
4984        };
4985        out.push(to_stack_element(&data));
4986        i += advance;
4987    }
4988    Some(out)
4989}
4990
4991/// Parse all pushes from P2SH scriptSig (including OP_0/dummy).
4992/// Returns pushed data in order; last push = redeem script.
4993/// Uses parse_script_sig_push_only; caller uses .as_ref() for &[u8].
4994fn parse_p2sh_script_sig_pushes(script_sig: &[u8]) -> Option<Vec<StackElement>> {
4995    parse_script_sig_push_only(script_sig)
4996}
4997
4998/// Parse redeem script as `OP_n <pubkeys> OP_m OP_CHECKMULTISIG`.
4999/// Format: first byte OP_1..OP_16 = n, then n pubkeys (33 or 65 bytes each),
5000/// then OP_1..OP_16 = m, then 0xae (OP_CHECKMULTISIG).
5001/// Returns (m, n, pubkey_slices) or None if format doesn't match.
5002fn parse_redeem_multisig(redeem: &[u8]) -> Option<(u8, u8, Vec<&[u8]>)> {
5003    if redeem.len() < 4 {
5004        return None;
5005    }
5006    let n_op = redeem[0];
5007    if !(OP_1..=OP_16).contains(&n_op) {
5008        return None;
5009    }
5010    let n = (n_op - OP_1 + 1) as usize;
5011    let mut i = 1;
5012    let mut pubkeys = Vec::with_capacity(n);
5013    for _ in 0..n {
5014        if i >= redeem.len() {
5015            return None;
5016        }
5017        let first = redeem[i];
5018        let pk_len = if first == 0x02 || first == 0x03 {
5019            33
5020        } else if first == 0x04 {
5021            65
5022        } else {
5023            return None;
5024        };
5025        if i + pk_len > redeem.len() {
5026            return None;
5027        }
5028        pubkeys.push(&redeem[i..i + pk_len]);
5029        i += pk_len;
5030    }
5031    if i + 2 > redeem.len() {
5032        return None;
5033    }
5034    let m_op = redeem[i];
5035    if !(OP_1..=OP_16).contains(&m_op) {
5036        return None;
5037    }
5038    let m = m_op - OP_1 + 1;
5039    if redeem[i + 1] != OP_CHECKMULTISIG {
5040        return None;
5041    }
5042    Some((m, n as u8, pubkeys))
5043}
5044
5045/// Map OP_1NEGATE..OP_16 to numeric value for script_num_to_bytes
5046fn script_num_from_opcode(opcode: u8) -> i64 {
5047    match opcode {
5048        OP_1NEGATE => -1,
5049        OP_1 => 1,
5050        OP_2 => 2,
5051        OP_3 => 3,
5052        OP_4 => 4,
5053        OP_5 => 5,
5054        OP_6 => 6,
5055        OP_7 => 7,
5056        OP_8 => 8,
5057        OP_9 => 9,
5058        OP_10 => 10,
5059        OP_11 => 11,
5060        OP_12 => 12,
5061        OP_13 => 13,
5062        OP_14 => 14,
5063        OP_15 => 15,
5064        OP_16 => 16,
5065        _ => 0,
5066    }
5067}
5068
5069/// Serialize data as a Bitcoin push operation: `push_opcode` followed by `data` bytes.
5070/// This creates the byte pattern that FindAndDelete searches for.
5071/// Push data to script (BIP62 encoding rules).
5072pub(crate) fn serialize_push_data(data: &[u8]) -> Vec<u8> {
5073    let len = data.len();
5074    let mut result = Vec::with_capacity(len + 5);
5075    if len < 76 {
5076        result.push(len as u8);
5077    } else if len < 256 {
5078        result.push(OP_PUSHDATA1);
5079        result.push(len as u8);
5080    } else if len < 65536 {
5081        result.push(OP_PUSHDATA2);
5082        result.push((len & 0xff) as u8);
5083        result.push(((len >> 8) & 0xff) as u8);
5084    } else {
5085        result.push(OP_PUSHDATA4);
5086        result.push((len & 0xff) as u8);
5087        result.push(((len >> 8) & 0xff) as u8);
5088        result.push(((len >> 16) & 0xff) as u8);
5089        result.push(((len >> 24) & 0xff) as u8);
5090    }
5091    result.extend_from_slice(data);
5092    result
5093}
5094
5095/// Advance `pc` past one opcode (Bitcoin `CScript::GetOp`-compatible sizing).
5096#[inline]
5097fn script_get_op_advance(script: &[u8], pc: usize) -> Option<usize> {
5098    if pc >= script.len() {
5099        return None;
5100    }
5101    let opcode = script[pc];
5102    let advance = if opcode <= 0x4b {
5103        1 + opcode as usize
5104    } else if opcode == OP_PUSHDATA1 && pc + 1 < script.len() {
5105        2 + script[pc + 1] as usize
5106    } else if opcode == OP_PUSHDATA2 && pc + 2 < script.len() {
5107        3 + ((script[pc + 1] as usize) | ((script[pc + 2] as usize) << 8))
5108    } else if opcode == OP_PUSHDATA4 && pc + 4 < script.len() {
5109        5 + ((script[pc + 1] as usize)
5110            | ((script[pc + 2] as usize) << 8)
5111            | ((script[pc + 3] as usize) << 16)
5112            | ((script[pc + 4] as usize) << 24))
5113    } else {
5114        1
5115    };
5116    let next = pc + advance;
5117    if next > script.len() {
5118        None
5119    } else {
5120        Some(next)
5121    }
5122}
5123
5124/// FindAndDelete — consensus match to Bitcoin Core `FindAndDelete` in `interpreter.cpp`.
5125///
5126/// Do/while loop: flush `[pc2, pc)`, delete consecutive raw occurrences of `pattern` from `pc`,
5127/// then advance `pc` one opcode via GetOp-style sizing. The inner delete loop can leave `pc`
5128/// misaligned; the next "opcode" is parsed from that byte (same as Core).
5129#[spec_locked("5.1.1", "FindAndDelete")]
5130#[inline]
5131pub(crate) fn find_and_delete<'a>(script: &'a [u8], pattern: &[u8]) -> std::borrow::Cow<'a, [u8]> {
5132    if pattern.is_empty() {
5133        return std::borrow::Cow::Borrowed(script);
5134    }
5135    if pattern.len() > script.len() {
5136        return std::borrow::Cow::Borrowed(script);
5137    }
5138    // Fast pre-scan: if pattern doesn't appear anywhere in the raw bytes, it can't appear
5139    // at any opcode boundary either — skip the full walk and the Vec allocation entirely.
5140    // `windows()` is O(N) and avoids ~100B-4KB Vec allocation + full script copy on the
5141    // overwhelmingly common case (CHECKSIG before codeseparator = never matches).
5142    if !script.windows(pattern.len()).any(|w| w == pattern) {
5143        return std::borrow::Cow::Borrowed(script);
5144    }
5145    let end = script.len();
5146    let mut n_found = 0usize;
5147    let mut result = Vec::new();
5148    let mut pc = 0usize;
5149    let mut pc2 = 0usize;
5150
5151    loop {
5152        result.extend_from_slice(&script[pc2..pc]);
5153        while end - pc >= pattern.len() && script[pc..pc + pattern.len()] == *pattern {
5154            pc += pattern.len();
5155            n_found += 1;
5156        }
5157        pc2 = pc;
5158        if pc >= end {
5159            break;
5160        }
5161        let Some(next_pc) = script_get_op_advance(script, pc) else {
5162            break;
5163        };
5164        pc = next_pc;
5165    }
5166
5167    if n_found > 0 {
5168        result.extend_from_slice(&script[pc2..end]);
5169        std::borrow::Cow::Owned(result)
5170    } else {
5171        std::borrow::Cow::Borrowed(script)
5172    }
5173}
5174
5175/// Return opcode position (0-indexed) of the opcode at byte_index in script. BIP 342 codesep_pos.
5176fn opcode_position_at_byte(script: &[u8], byte_index: usize) -> u32 {
5177    let mut pos = 0u32;
5178    let mut i = 0usize;
5179    while i < script.len() && i <= byte_index {
5180        let opcode = script[i];
5181        let advance = if opcode <= 0x4b {
5182            1 + opcode as usize
5183        } else if opcode == OP_PUSHDATA1 && i + 1 < script.len() {
5184            2 + script[i + 1] as usize
5185        } else if opcode == OP_PUSHDATA2 && i + 2 < script.len() {
5186            3 + ((script[i + 1] as usize) | ((script[i + 2] as usize) << 8))
5187        } else if opcode == OP_PUSHDATA4 && i + 4 < script.len() {
5188            5 + ((script[i + 1] as usize)
5189                | ((script[i + 2] as usize) << 8)
5190                | ((script[i + 3] as usize) << 16)
5191                | ((script[i + 4] as usize) << 24))
5192        } else {
5193            1
5194        };
5195        if i == byte_index {
5196            return pos;
5197        }
5198        pos += 1;
5199        i = std::cmp::min(i + advance, script.len());
5200    }
5201    0xffff_ffff
5202}
5203
5204/// Execute a single opcode with full context including block height, median time-past, and network
5205#[cfg_attr(feature = "production", inline(always))]
5206fn execute_opcode_with_context_full(
5207    opcode: u8,
5208    stack: &mut Vec<StackElement>,
5209    flags: u32,
5210    ctx: &context::ScriptContext<'_>,
5211    effective_script_code: Option<&[u8]>,
5212) -> Result<bool> {
5213    let tx = ctx.tx;
5214    let input_index = ctx.input_index;
5215    let prevout_values = ctx.prevout_values;
5216    let prevout_script_pubkeys = ctx.prevout_script_pubkeys;
5217    let block_height = ctx.block_height;
5218    let median_time_past = ctx.median_time_past;
5219    let network = ctx.network;
5220    let sigversion = ctx.sigversion;
5221    let script_sig_for_sighash = ctx.script_sig_for_sighash;
5222    let tapscript_for_sighash = ctx.tapscript_for_sighash;
5223    let tapscript_codesep_pos = ctx.tapscript_codesep_pos;
5224    let redeem_script_for_sighash = effective_script_code;
5225    #[cfg(feature = "production")]
5226    let schnorr_collector = ctx.schnorr_collector;
5227    #[cfg(feature = "production")]
5228    let precomputed_bip143 = ctx.precomputed_bip143;
5229    #[cfg(feature = "production")]
5230    let sighash_cache = ctx.sighash_cache;
5231
5232    // match ordered by frequency (hot opcodes first for better branch prediction)
5233    match opcode {
5234        // OP_CHECKSIG - verify ECDSA signature
5235        OP_CHECKSIG => {
5236            if stack.len() >= 2 {
5237                let pubkey_bytes = stack.pop().unwrap();
5238                let signature_bytes = stack.pop().unwrap();
5239
5240                // Empty signature always fails but is valid script execution
5241                if signature_bytes.is_empty() {
5242                    stack.push(to_stack_element(&[0]));
5243                    return Ok(true);
5244                }
5245
5246                // Tapscript (BIP 342): BIP 340 Schnorr (64 bytes, or 65 with explicit sighash byte).
5247                if sigversion == SigVersion::Tapscript && pubkey_bytes.len() == 32 {
5248                    use crate::bip348::try_parse_taproot_schnorr_witness_sig;
5249                    if let Some((sig_bytes, sighash_byte)) =
5250                        try_parse_taproot_schnorr_witness_sig(&signature_bytes)
5251                    {
5252                        let (tapscript, codesep_pos) = tapscript_for_sighash
5253                            .map(|s| (s, tapscript_codesep_pos.unwrap_or(0xffff_ffff)))
5254                            .unwrap_or((&[] as &[u8], 0xffff_ffff));
5255                        let sighash = if tapscript.is_empty() {
5256                            crate::taproot::compute_taproot_signature_hash(
5257                                tx,
5258                                input_index,
5259                                prevout_values,
5260                                prevout_script_pubkeys,
5261                                sighash_byte,
5262                                None,
5263                            )?
5264                        } else {
5265                            crate::taproot::compute_tapscript_signature_hash(
5266                                tx,
5267                                input_index,
5268                                prevout_values,
5269                                prevout_script_pubkeys,
5270                                tapscript,
5271                                crate::taproot::TAPROOT_LEAF_VERSION_TAPSCRIPT,
5272                                codesep_pos,
5273                                sighash_byte,
5274                                ctx.taproot_annex_hash,
5275                            )?
5276                        };
5277
5278                        #[cfg(feature = "production")]
5279                        let is_valid = {
5280                            use crate::bip348::verify_tapscript_schnorr_signature;
5281                            verify_tapscript_schnorr_signature(
5282                                &sighash,
5283                                &pubkey_bytes,
5284                                &sig_bytes,
5285                                schnorr_collector,
5286                            )
5287                            .unwrap_or(false)
5288                        };
5289
5290                        #[cfg(not(feature = "production"))]
5291                        let is_valid = {
5292                            #[cfg(feature = "csfs")]
5293                            let x = {
5294                                use crate::bip348::verify_tapscript_schnorr_signature;
5295                                verify_tapscript_schnorr_signature(
5296                                    &sighash,
5297                                    &pubkey_bytes,
5298                                    &sig_bytes,
5299                                    None,
5300                                )
5301                                .unwrap_or(false)
5302                            };
5303                            #[cfg(not(feature = "csfs"))]
5304                            let x = false;
5305                            x
5306                        };
5307
5308                        stack.push(to_stack_element(&[if is_valid { 1 } else { 0 }]));
5309                        return Ok(true);
5310                    }
5311                    // Invalid Schnorr encoding for 32-byte x-only pubkey → failed check.
5312                    stack.push(to_stack_element(&[0]));
5313                    return Ok(true);
5314                }
5315                if sigversion == SigVersion::Tapscript {
5316                    // Non-32-byte pubkeys succeed without verification (BIP 342).
5317                    stack.push(to_stack_element(&[1]));
5318                    return Ok(true);
5319                }
5320
5321                // Extract sighash type from last byte of signature
5322                // Bitcoin signature format: <DER signature><sighash_type>
5323                // OPTIMIZATION: Cache length to avoid repeated computation
5324                let sig_len = signature_bytes.len();
5325                let sighash_byte = signature_bytes[sig_len - 1];
5326                let _der_sig = &signature_bytes[..sig_len - 1];
5327
5328                // Calculate sighash - use BIP143 for SegWit, legacy for others
5329                // BIP143 OPTIMIZATION: For SegWit, hashPrevouts/hashSequence/hashOutputs
5330                // are computed once per transaction, not once per input.
5331                let sighash = if sigversion == SigVersion::WitnessV0 {
5332                    // BIP143 sighash for SegWit v0 (P2WPKH, P2WSH)
5333                    let amount = prevout_values.get(input_index).copied().unwrap_or(0);
5334
5335                    // scriptCode for BIP143: the witnessScript or P2PKH equivalent
5336                    let script_code = redeem_script_for_sighash.unwrap_or_else(|| {
5337                        prevout_script_pubkeys
5338                            .get(input_index)
5339                            .copied()
5340                            .unwrap_or(&[])
5341                    });
5342
5343                    crate::transaction_hash::calculate_bip143_sighash(
5344                        tx,
5345                        input_index,
5346                        script_code,
5347                        amount,
5348                        sighash_byte,
5349                        precomputed_bip143,
5350                    )?
5351                } else {
5352                    // Legacy sighash for non-SegWit transactions
5353                    use crate::transaction_hash::{
5354                        SighashType, calculate_transaction_sighash_single_input,
5355                    };
5356                    let sighash_type = SighashType::from_byte(sighash_byte);
5357
5358                    // FindAndDelete: remove the *current* signature's push encoding from scriptCode.
5359                    // Must not use only the first scriptSig push — P2SH inputs may contain multiple
5360                    // signatures before the redeem script, each checked with its own sighash.
5361                    let pattern = serialize_push_data(signature_bytes.as_ref());
5362
5363                    let base_script = match (
5364                        redeem_script_for_sighash,
5365                        prevout_script_pubkeys.get(input_index),
5366                    ) {
5367                        (Some(redeem), Some(prevout)) if redeem == *prevout => *prevout,
5368                        (Some(redeem), _) => redeem,
5369                        (None, Some(prevout)) => *prevout,
5370                        (None, None) => &[],
5371                    };
5372                    let cleaned = find_and_delete(base_script, &pattern);
5373
5374                    calculate_transaction_sighash_single_input(
5375                        tx,
5376                        input_index,
5377                        cleaned.as_ref(),
5378                        prevout_values[input_index],
5379                        sighash_type,
5380                        #[cfg(feature = "production")]
5381                        sighash_cache,
5382                    )?
5383                };
5384
5385                // Verify signature with real transaction hash
5386                // CRITICAL FIX: Pass full signature (with sighash byte) to verify_signature
5387                // IsValidSignatureEncoding expects signature WITH sighash byte
5388                let height = block_height.unwrap_or(0);
5389                #[cfg(feature = "production")]
5390                let is_valid = signature::with_secp_context(|secp| {
5391                    signature::verify_signature(
5392                        secp,
5393                        &pubkey_bytes,
5394                        &signature_bytes, // Pass full signature WITH sighash byte
5395                        &sighash,
5396                        flags,
5397                        height,
5398                        network,
5399                        sigversion,
5400                    )
5401                })?;
5402
5403                #[cfg(not(feature = "production"))]
5404                let is_valid = {
5405                    let secp = signature::new_secp();
5406                    signature::verify_signature(
5407                        &secp,
5408                        &pubkey_bytes,
5409                        &signature_bytes, // Pass full signature WITH sighash byte
5410                        &sighash,
5411                        flags,
5412                        height,
5413                        network,
5414                        sigversion,
5415                    )?
5416                };
5417
5418                stack.push(to_stack_element(&[if is_valid { 1 } else { 0 }]));
5419                Ok(true)
5420            } else {
5421                Ok(false)
5422            }
5423        }
5424
5425        // OP_CHECKSIGVERIFY - verify signature (Schnorr in Tapscript, ECDSA otherwise)
5426        OP_CHECKSIGVERIFY => {
5427            if stack.len() >= 2 {
5428                let pubkey_bytes = stack.pop().unwrap();
5429                let signature_bytes = stack.pop().unwrap();
5430
5431                // Empty signature always fails
5432                if signature_bytes.is_empty() {
5433                    return Ok(false);
5434                }
5435
5436                // BIP342 Tapscript: Schnorr path (same as OP_CHECKSIG Tapscript branch).
5437                if sigversion == SigVersion::Tapscript {
5438                    if pubkey_bytes.len() == 32 {
5439                        use crate::bip348::try_parse_taproot_schnorr_witness_sig;
5440                        if let Some((sig_bytes, sighash_byte)) =
5441                            try_parse_taproot_schnorr_witness_sig(&signature_bytes)
5442                        {
5443                            let (tapscript, codesep_pos) = tapscript_for_sighash
5444                                .map(|s| (s, tapscript_codesep_pos.unwrap_or(0xffff_ffff)))
5445                                .unwrap_or((&[] as &[u8], 0xffff_ffff));
5446                            let sighash = if tapscript.is_empty() {
5447                                crate::taproot::compute_taproot_signature_hash(
5448                                    tx,
5449                                    input_index,
5450                                    prevout_values,
5451                                    prevout_script_pubkeys,
5452                                    sighash_byte,
5453                                    None,
5454                                )?
5455                            } else {
5456                                crate::taproot::compute_tapscript_signature_hash(
5457                                    tx,
5458                                    input_index,
5459                                    prevout_values,
5460                                    prevout_script_pubkeys,
5461                                    tapscript,
5462                                    crate::taproot::TAPROOT_LEAF_VERSION_TAPSCRIPT,
5463                                    codesep_pos,
5464                                    sighash_byte,
5465                                    ctx.taproot_annex_hash,
5466                                )?
5467                            };
5468                            #[cfg(feature = "production")]
5469                            let is_valid = {
5470                                use crate::bip348::verify_tapscript_schnorr_signature;
5471                                verify_tapscript_schnorr_signature(
5472                                    &sighash,
5473                                    &pubkey_bytes,
5474                                    &sig_bytes,
5475                                    schnorr_collector,
5476                                )
5477                                .unwrap_or(false)
5478                            };
5479                            #[cfg(not(feature = "production"))]
5480                            let is_valid = {
5481                                #[cfg(feature = "csfs")]
5482                                let x = {
5483                                    use crate::bip348::verify_tapscript_schnorr_signature;
5484                                    verify_tapscript_schnorr_signature(
5485                                        &sighash,
5486                                        &pubkey_bytes,
5487                                        &sig_bytes,
5488                                        None,
5489                                    )
5490                                    .unwrap_or(false)
5491                                };
5492                                #[cfg(not(feature = "csfs"))]
5493                                let x = false;
5494                                x
5495                            };
5496                            if !is_valid {
5497                                return Ok(false); // OP_CHECKSIGVERIFY: fail script on invalid sig
5498                            }
5499                            return Ok(true);
5500                        }
5501                        // Invalid Schnorr encoding for 32-byte x-only pubkey → failed check.
5502                        return Ok(false);
5503                    }
5504                    // Non-32-byte pubkeys succeed without verification (BIP 342).
5505                    return Ok(true);
5506                }
5507
5508                // Legacy / SegWit v0: ECDSA path
5509                // Extract sighash type from last byte of signature
5510                let sig_len = signature_bytes.len();
5511                let sighash_byte = signature_bytes[sig_len - 1];
5512                let _der_sig = &signature_bytes[..sig_len - 1];
5513
5514                // Calculate sighash - use BIP143 for SegWit, legacy for others
5515                // BIP143 OPTIMIZATION: For SegWit, hashPrevouts/hashSequence/hashOutputs
5516                // are computed once per transaction, not once per input.
5517                let sighash = if sigversion == SigVersion::WitnessV0 {
5518                    // BIP143 sighash for SegWit v0 (P2WPKH, P2WSH)
5519                    let amount = prevout_values.get(input_index).copied().unwrap_or(0);
5520
5521                    let script_code = redeem_script_for_sighash.unwrap_or_else(|| {
5522                        prevout_script_pubkeys
5523                            .get(input_index)
5524                            .copied()
5525                            .unwrap_or(&[])
5526                    });
5527
5528                    crate::transaction_hash::calculate_bip143_sighash(
5529                        tx,
5530                        input_index,
5531                        script_code,
5532                        amount,
5533                        sighash_byte,
5534                        precomputed_bip143,
5535                    )?
5536                } else {
5537                    // Legacy sighash for non-SegWit transactions
5538                    use crate::transaction_hash::{
5539                        SighashType, calculate_transaction_sighash_single_input,
5540                    };
5541                    let sighash_type = SighashType::from_byte(sighash_byte);
5542
5543                    // FindAndDelete: remove the signature under verification (not first scriptSig push).
5544                    let pattern = serialize_push_data(signature_bytes.as_ref());
5545
5546                    let base_script = match (
5547                        redeem_script_for_sighash,
5548                        prevout_script_pubkeys.get(input_index),
5549                    ) {
5550                        (Some(redeem), Some(prevout)) if redeem == *prevout => *prevout,
5551                        (Some(redeem), _) => redeem,
5552                        (None, Some(prevout)) => *prevout,
5553                        (None, None) => &[],
5554                    };
5555                    let cleaned = find_and_delete(base_script, &pattern);
5556
5557                    calculate_transaction_sighash_single_input(
5558                        tx,
5559                        input_index,
5560                        cleaned.as_ref(),
5561                        prevout_values[input_index],
5562                        sighash_type,
5563                        #[cfg(feature = "production")]
5564                        sighash_cache,
5565                    )?
5566                };
5567
5568                // Verify signature with real transaction hash
5569                // CRITICAL FIX: Pass full signature (with sighash byte) to verify_signature
5570                // IsValidSignatureEncoding expects signature WITH sighash byte
5571                let height = block_height.unwrap_or(0);
5572                #[cfg(feature = "production")]
5573                let is_valid = signature::with_secp_context(|secp| {
5574                    signature::verify_signature(
5575                        secp,
5576                        &pubkey_bytes,
5577                        &signature_bytes, // Pass full signature WITH sighash byte
5578                        &sighash,
5579                        flags,
5580                        height,
5581                        network,
5582                        sigversion,
5583                    )
5584                })?;
5585
5586                #[cfg(not(feature = "production"))]
5587                let is_valid = {
5588                    let secp = signature::new_secp();
5589                    signature::verify_signature(
5590                        &secp,
5591                        &pubkey_bytes,
5592                        &signature_bytes, // Pass full signature WITH sighash byte
5593                        &sighash,
5594                        flags,
5595                        height,
5596                        network,
5597                        sigversion,
5598                    )?
5599                };
5600
5601                if is_valid { Ok(true) } else { Ok(false) }
5602            } else {
5603                Ok(false)
5604            }
5605        }
5606
5607        // OP_CHECKSIGADD (BIP 342) - Tapscript only. Pops pubkey, n, sig. Verifies Schnorr; if valid push n+1 else fail.
5608        OP_CHECKSIGADD => {
5609            if sigversion != SigVersion::Tapscript {
5610                return Err(ConsensusError::ScriptErrorWithCode {
5611                    code: ScriptErrorCode::DisabledOpcode,
5612                    message: "OP_CHECKSIGADD is only available in Tapscript".into(),
5613                });
5614            }
5615            if stack.len() < 3 {
5616                return Err(ConsensusError::ScriptErrorWithCode {
5617                    code: ScriptErrorCode::InvalidStackOperation,
5618                    message: "OP_CHECKSIGADD: insufficient stack items (need 3)".into(),
5619                });
5620            }
5621            // BIP 342: pubkey (top), n (second), sig (third)
5622            let pubkey_bytes = stack.pop().unwrap();
5623            let n_bytes = stack.pop().unwrap();
5624            let signature_bytes = stack.pop().unwrap();
5625            let n = script_num_decode(&n_bytes, 4)?;
5626
5627            // Empty signature: push n unchanged (BIP 342)
5628            if signature_bytes.is_empty() {
5629                stack.push(to_stack_element(&script_num_encode(n)));
5630                return Ok(true);
5631            }
5632
5633            // 32-byte pubkey + non-empty sig: validate. BIP 342: validation failure terminates script.
5634            if pubkey_bytes.len() == 32 {
5635                use crate::bip348::try_parse_taproot_schnorr_witness_sig;
5636                if let Some((sig_bytes, sighash_byte)) =
5637                    try_parse_taproot_schnorr_witness_sig(&signature_bytes)
5638                {
5639                    let (tapscript, codesep_pos) = tapscript_for_sighash
5640                        .map(|s| (s, tapscript_codesep_pos.unwrap_or(0xffff_ffff)))
5641                        .unwrap_or((&[] as &[u8], 0xffff_ffff));
5642                    let sighash = if tapscript.is_empty() {
5643                        crate::taproot::compute_taproot_signature_hash(
5644                            tx,
5645                            input_index,
5646                            prevout_values,
5647                            prevout_script_pubkeys,
5648                            sighash_byte,
5649                            None,
5650                        )?
5651                    } else {
5652                        crate::taproot::compute_tapscript_signature_hash(
5653                            tx,
5654                            input_index,
5655                            prevout_values,
5656                            prevout_script_pubkeys,
5657                            tapscript,
5658                            crate::taproot::TAPROOT_LEAF_VERSION_TAPSCRIPT,
5659                            codesep_pos,
5660                            sighash_byte,
5661                            ctx.taproot_annex_hash,
5662                        )?
5663                    };
5664
5665                    #[cfg(feature = "production")]
5666                    let is_valid = {
5667                        use crate::bip348::verify_tapscript_schnorr_signature;
5668                        verify_tapscript_schnorr_signature(
5669                            &sighash,
5670                            &pubkey_bytes,
5671                            &sig_bytes,
5672                            schnorr_collector,
5673                        )
5674                        .unwrap_or(false)
5675                    };
5676
5677                    #[cfg(not(feature = "production"))]
5678                    let is_valid = {
5679                        #[cfg(feature = "csfs")]
5680                        let x = {
5681                            use crate::bip348::verify_tapscript_schnorr_signature;
5682                            verify_tapscript_schnorr_signature(
5683                                &sighash,
5684                                &pubkey_bytes,
5685                                &sig_bytes,
5686                                None,
5687                            )
5688                            .unwrap_or(false)
5689                        };
5690                        #[cfg(not(feature = "csfs"))]
5691                        let x = false;
5692                        x
5693                    };
5694
5695                    if !is_valid {
5696                        return Ok(false); // BIP 342: validation failure terminates script
5697                    }
5698                    stack.push(to_stack_element(&script_num_encode(n + 1)));
5699                    return Ok(true);
5700                }
5701                return Ok(false); // invalid Schnorr encoding for 32-byte pubkey
5702            }
5703
5704            // Unknown pubkey type (not 32 bytes): BIP 342 treats as always-valid, push n+1
5705            stack.push(to_stack_element(&script_num_encode(n + 1)));
5706            Ok(true)
5707        }
5708
5709        // OP_CHECKMULTISIG - verify m-of-n multisig (hot path)
5710        OP_CHECKMULTISIG => {
5711            // OP_CHECKMULTISIG implementation
5712            // Stack layout: [dummy] [sig1] ... [sigm] [m] [pubkey1] ... [pubkeyn] [n]
5713            if stack.len() < 2 {
5714                return Ok(false);
5715            }
5716
5717            // Pop n (number of public keys) — must be decoded as CScriptNum (BIP62)
5718            let n_bytes = stack.pop().unwrap();
5719            let n_raw = script_num_decode(&n_bytes, 4).map_err(|_| {
5720                ConsensusError::ScriptErrorWithCode {
5721                    code: ScriptErrorCode::InvalidStackOperation,
5722                    message: "OP_CHECKMULTISIG: invalid n encoding".into(),
5723                }
5724            })?;
5725            if !(0..=20).contains(&n_raw) {
5726                return Ok(false);
5727            }
5728            let n = n_raw as usize;
5729            if stack.len() < n + 1 {
5730                return Ok(false);
5731            }
5732
5733            // Pop n public keys
5734            let mut pubkeys = Vec::with_capacity(n);
5735            for _ in 0..n {
5736                pubkeys.push(stack.pop().unwrap());
5737            }
5738
5739            // Pop m (number of required signatures) — must be decoded as CScriptNum (BIP62)
5740            let m_bytes = stack.pop().unwrap();
5741            let m_raw = script_num_decode(&m_bytes, 4).map_err(|_| {
5742                ConsensusError::ScriptErrorWithCode {
5743                    code: ScriptErrorCode::InvalidStackOperation,
5744                    message: "OP_CHECKMULTISIG: invalid m encoding".into(),
5745                }
5746            })?;
5747            if m_raw < 0 || m_raw as usize > n || m_raw > 20 {
5748                return Ok(false);
5749            }
5750            let m = m_raw as usize;
5751            if stack.len() < m + 1 {
5752                return Ok(false);
5753            }
5754
5755            // Pop m signatures
5756            let mut signatures = Vec::with_capacity(m);
5757            for _ in 0..m {
5758                signatures.push(stack.pop().unwrap());
5759            }
5760
5761            // Pop dummy element - this is the FIRST element consumed (last remaining on stack)
5762            // BIP147: Check NULLDUMMY if flag is set (SCRIPT_VERIFY_NULLDUMMY = 0x10)
5763            let dummy = stack.pop().unwrap();
5764            if flags & 0x10 != 0 {
5765                let height = block_height.unwrap_or(0);
5766                // Convert network type for BIP147
5767                use crate::bip_validation::Bip147Network;
5768                let bip147_network = match network {
5769                    crate::types::Network::Mainnet => Bip147Network::Mainnet,
5770                    crate::types::Network::Testnet => Bip147Network::Testnet,
5771                    crate::types::Network::Regtest | crate::types::Network::Signet => {
5772                        Bip147Network::Regtest
5773                    }
5774                };
5775
5776                // For BIP147, the dummy element must be exactly [0x00] (OP_0) after activation
5777                // BIP147 requires the dummy to be exactly one byte: 0x00
5778                // Not empty [], not multi-byte [0x00, ...], not non-zero [0x01, ...]
5779                use crate::constants::{BIP147_ACTIVATION_MAINNET, BIP147_ACTIVATION_TESTNET};
5780
5781                let bip147_active = height
5782                    >= match bip147_network {
5783                        Bip147Network::Mainnet => BIP147_ACTIVATION_MAINNET,
5784                        Bip147Network::Testnet => BIP147_ACTIVATION_TESTNET,
5785                        Bip147Network::Regtest => 0,
5786                    };
5787
5788                if bip147_active {
5789                    // BIP147: Dummy must be empty (either [] or [0x00])
5790                    // In Bitcoin script, both empty [] and [0x00] (OP_0) are considered "empty"
5791                    // Both accepted as valid NULLDUMMY (BIP147)
5792                    let is_empty = dummy.is_empty() || dummy.as_ref() == [0x00];
5793                    if !is_empty {
5794                        return Err(ConsensusError::ScriptErrorWithCode {
5795                            code: ScriptErrorCode::SigNullDummy,
5796                message: format!(
5797                    "OP_CHECKMULTISIG: dummy element {dummy:?} violates BIP147 NULLDUMMY (must be empty: [] or [0x00])"
5798                )
5799                            .into(),
5800                        });
5801                    }
5802                }
5803            }
5804
5805            // Verify signatures against public keys
5806            // CHECKMULTISIG algorithm: iterate pubkeys, try to match sigs in order
5807            let height = block_height.unwrap_or(0);
5808
5809            // FindAndDelete: Remove ALL signatures from scriptCode BEFORE any sighash computation
5810            // Consensus rule for OP_CHECKMULTISIG (legacy only, not SegWit)
5811            let cleaned_script_for_multisig: Vec<u8> = if sigversion == SigVersion::Base {
5812                let base_script = match (
5813                    redeem_script_for_sighash,
5814                    prevout_script_pubkeys.get(input_index),
5815                ) {
5816                    (Some(redeem), Some(prevout)) if redeem == *prevout => *prevout,
5817                    (Some(redeem), _) => redeem,
5818                    (None, Some(prevout)) => *prevout,
5819                    (None, None) => &[],
5820                };
5821                let mut cleaned = base_script.to_vec();
5822                for sig in &signatures {
5823                    if !sig.is_empty() {
5824                        let pattern = serialize_push_data(sig.as_ref());
5825                        cleaned = find_and_delete(&cleaned, &pattern).into_owned();
5826                    }
5827                }
5828                cleaned
5829            } else {
5830                // For SegWit, no FindAndDelete needed
5831                redeem_script_for_sighash
5832                    .map(|s| s.to_vec())
5833                    .unwrap_or_else(|| {
5834                        prevout_script_pubkeys
5835                            .get(input_index)
5836                            .map(|p| p.to_vec())
5837                            .unwrap_or_default()
5838                    })
5839            };
5840
5841            use crate::transaction_hash::{
5842                SighashType, calculate_transaction_sighash_single_input,
5843            };
5844
5845            // Batch path: when n*m >= 4, precompute sighashes once per sig and batch-verify all (pubkey, sig) pairs.
5846            #[cfg(feature = "production")]
5847            let use_batch = pubkeys.len() * signatures.len() >= 4;
5848
5849            #[cfg(feature = "production")]
5850            let (valid_sigs, _) = if use_batch {
5851                // Phase 3: Batch sighash for multisig — use batch_compute_legacy_sighashes when Base
5852                let sighashes: Vec<[u8; 32]> = if sigversion == SigVersion::Base {
5853                    let non_empty: Vec<_> = signatures.iter().filter(|s| !s.is_empty()).collect();
5854                    if non_empty.is_empty() {
5855                        vec![]
5856                    } else {
5857                        let specs: Vec<(usize, u8, &[u8])> = non_empty
5858                            .iter()
5859                            .map(|s| {
5860                                (
5861                                    input_index,
5862                                    s.as_ref()[s.as_ref().len() - 1],
5863                                    cleaned_script_for_multisig.as_ref(),
5864                                )
5865                            })
5866                            .collect();
5867                        crate::transaction_hash::batch_compute_legacy_sighashes(
5868                            tx,
5869                            prevout_values,
5870                            prevout_script_pubkeys,
5871                            &specs,
5872                        )?
5873                    }
5874                } else {
5875                    // WitnessV0 (P2WSH): use BIP143 sighash, not legacy.
5876                    let amount = prevout_values.get(input_index).copied().unwrap_or(0);
5877                    signatures
5878                        .iter()
5879                        .filter(|s| !s.is_empty())
5880                        .map(|sig_bytes| {
5881                            let sighash_byte = sig_bytes[sig_bytes.len() - 1];
5882                            crate::transaction_hash::calculate_bip143_sighash(
5883                                tx,
5884                                input_index,
5885                                &cleaned_script_for_multisig,
5886                                amount,
5887                                sighash_byte,
5888                                precomputed_bip143,
5889                            )
5890                        })
5891                        .collect::<Result<Vec<_>>>()?
5892                };
5893
5894                // Build verification tasks: (pubkey_i, sig_j, sighash_j) for all i,j. Order: j then i (sig_index, pubkey_index)
5895                let mut tasks: Vec<(&[u8], &[u8], [u8; 32])> =
5896                    Vec::with_capacity(pubkeys.len() * signatures.len());
5897                let mut sig_idx_to_sighash_idx = Vec::with_capacity(signatures.len());
5898                let mut sighash_idx = 0usize;
5899                for (j, sig_bytes) in signatures.iter().enumerate() {
5900                    if sig_bytes.is_empty() {
5901                        sig_idx_to_sighash_idx.push(usize::MAX);
5902                    } else {
5903                        sig_idx_to_sighash_idx.push(sighash_idx);
5904                        let sh = sighashes[sighash_idx];
5905                        sighash_idx += 1;
5906                        for pubkey_bytes in &pubkeys {
5907                            tasks.push((pubkey_bytes.as_ref(), sig_bytes.as_ref(), sh));
5908                        }
5909                    }
5910                }
5911
5912                let results = if tasks.is_empty() {
5913                    vec![]
5914                } else {
5915                    batch_verify_signatures(&tasks, flags, height, network, sigversion)?
5916                };
5917
5918                // Matching: for each pubkey in order, if current sig verifies with this pubkey, advance
5919                let mut sig_index = 0;
5920                let mut valid_sigs = 0usize;
5921                for (i, _pubkey_bytes) in pubkeys.iter().enumerate() {
5922                    if sig_index >= signatures.len() {
5923                        break;
5924                    }
5925                    // Skip empty sigs without advancing (same as original)
5926                    while sig_index < signatures.len() && signatures[sig_index].is_empty() {
5927                        sig_index += 1;
5928                    }
5929                    if sig_index >= signatures.len() {
5930                        break;
5931                    }
5932                    let sh_idx = sig_idx_to_sighash_idx[sig_index];
5933                    if sh_idx == usize::MAX {
5934                        continue;
5935                    }
5936                    let task_idx = sh_idx * pubkeys.len() + i;
5937                    if task_idx < results.len() && results[task_idx] {
5938                        valid_sigs += 1;
5939                        sig_index += 1;
5940                    }
5941                }
5942
5943                // NULLFAIL: any non-empty sig that didn't match any pubkey must cause failure
5944                const SCRIPT_VERIFY_NULLFAIL: u32 = 0x4000;
5945                if (flags & SCRIPT_VERIFY_NULLFAIL) != 0 {
5946                    for (j, sig_bytes) in signatures.iter().enumerate() {
5947                        if sig_bytes.is_empty() {
5948                            continue;
5949                        }
5950                        let sh_idx = sig_idx_to_sighash_idx[j];
5951                        if sh_idx == usize::MAX {
5952                            continue;
5953                        }
5954                        let sig_start = sh_idx * pubkeys.len();
5955                        let sig_end = (sig_start + pubkeys.len()).min(results.len());
5956                        let matched = results[sig_start..sig_end].iter().any(|&r| r);
5957                        if !matched {
5958                            return Err(ConsensusError::ScriptErrorWithCode {
5959                                code: ScriptErrorCode::SigNullFail,
5960                                message: "OP_CHECKMULTISIG: non-null signature must not fail under NULLFAIL".into(),
5961                            });
5962                        }
5963                    }
5964                }
5965                (valid_sigs, ())
5966            } else {
5967                let mut sig_index = 0;
5968                let mut valid_sigs = 0;
5969                let amount = prevout_values.get(input_index).copied().unwrap_or(0);
5970
5971                for pubkey_bytes in &pubkeys {
5972                    if sig_index >= signatures.len() {
5973                        break;
5974                    }
5975
5976                    let signature_bytes = &signatures[sig_index];
5977
5978                    if signature_bytes.is_empty() {
5979                        continue;
5980                    }
5981
5982                    let sig_len = signature_bytes.len();
5983                    let sighash_byte = signature_bytes[sig_len - 1];
5984
5985                    // WitnessV0 (P2WSH): BIP143 sighash; Base (P2SH legacy): legacy sighash.
5986                    let sighash = if sigversion == SigVersion::WitnessV0 {
5987                        crate::transaction_hash::calculate_bip143_sighash(
5988                            tx,
5989                            input_index,
5990                            &cleaned_script_for_multisig,
5991                            amount,
5992                            sighash_byte,
5993                            #[cfg(feature = "production")]
5994                            precomputed_bip143,
5995                            #[cfg(not(feature = "production"))]
5996                            None,
5997                        )?
5998                    } else {
5999                        let sighash_type = SighashType::from_byte(sighash_byte);
6000                        calculate_transaction_sighash_single_input(
6001                            tx,
6002                            input_index,
6003                            &cleaned_script_for_multisig,
6004                            prevout_values[input_index],
6005                            sighash_type,
6006                            #[cfg(feature = "production")]
6007                            sighash_cache,
6008                        )?
6009                    };
6010
6011                    #[cfg(feature = "production")]
6012                    let is_valid = signature::with_secp_context(|secp| {
6013                        signature::verify_signature(
6014                            secp,
6015                            pubkey_bytes,
6016                            signature_bytes,
6017                            &sighash,
6018                            flags,
6019                            height,
6020                            network,
6021                            sigversion,
6022                        )
6023                    })?;
6024
6025                    #[cfg(not(feature = "production"))]
6026                    let is_valid = {
6027                        let secp = signature::new_secp();
6028                        signature::verify_signature(
6029                            &secp,
6030                            pubkey_bytes,
6031                            signature_bytes,
6032                            &sighash,
6033                            flags,
6034                            height,
6035                            network,
6036                            sigversion,
6037                        )?
6038                    };
6039
6040                    const SCRIPT_VERIFY_NULLFAIL: u32 = 0x4000;
6041                    if !is_valid
6042                        && (flags & SCRIPT_VERIFY_NULLFAIL) != 0
6043                        && !signature_bytes.is_empty()
6044                    {
6045                        return Err(ConsensusError::ScriptErrorWithCode {
6046                            code: ScriptErrorCode::SigNullFail,
6047                            message:
6048                                "OP_CHECKMULTISIG: non-null signature must not fail under NULLFAIL"
6049                                    .into(),
6050                        });
6051                    }
6052
6053                    if is_valid {
6054                        valid_sigs += 1;
6055                        sig_index += 1;
6056                    }
6057                }
6058                (valid_sigs, ())
6059            };
6060
6061            #[cfg(not(feature = "production"))]
6062            let (valid_sigs, _) = {
6063                let mut sig_index = 0;
6064                let mut valid_sigs = 0;
6065                let amount = prevout_values.get(input_index).copied().unwrap_or(0);
6066
6067                for pubkey_bytes in &pubkeys {
6068                    if sig_index >= signatures.len() {
6069                        break;
6070                    }
6071                    let signature_bytes = &signatures[sig_index];
6072                    if signature_bytes.is_empty() {
6073                        continue;
6074                    }
6075                    let sig_len = signature_bytes.len();
6076                    let sighash_byte = signature_bytes[sig_len - 1];
6077
6078                    // WitnessV0 (P2WSH): BIP143 sighash; Base: legacy sighash.
6079                    let sighash = if sigversion == SigVersion::WitnessV0 {
6080                        crate::transaction_hash::calculate_bip143_sighash(
6081                            tx,
6082                            input_index,
6083                            &cleaned_script_for_multisig,
6084                            amount,
6085                            sighash_byte,
6086                            None,
6087                        )?
6088                    } else {
6089                        let sighash_type = SighashType::from_byte(sighash_byte);
6090                        calculate_transaction_sighash_single_input(
6091                            tx,
6092                            input_index,
6093                            &cleaned_script_for_multisig,
6094                            prevout_values[input_index],
6095                            sighash_type,
6096                        )?
6097                    };
6098                    let secp = signature::new_secp();
6099                    let is_valid = signature::verify_signature(
6100                        &secp,
6101                        pubkey_bytes,
6102                        signature_bytes,
6103                        &sighash,
6104                        flags,
6105                        height,
6106                        network,
6107                        sigversion,
6108                    )?;
6109                    const SCRIPT_VERIFY_NULLFAIL: u32 = 0x4000;
6110                    if !is_valid
6111                        && (flags & SCRIPT_VERIFY_NULLFAIL) != 0
6112                        && !signature_bytes.is_empty()
6113                    {
6114                        return Err(ConsensusError::ScriptErrorWithCode {
6115                            code: ScriptErrorCode::SigNullFail,
6116                            message:
6117                                "OP_CHECKMULTISIG: non-null signature must not fail under NULLFAIL"
6118                                    .into(),
6119                        });
6120                    }
6121                    if is_valid {
6122                        valid_sigs += 1;
6123                        sig_index += 1;
6124                    }
6125                }
6126                (valid_sigs, ())
6127            };
6128
6129            // Push result: 1 if valid_sigs >= m, 0 otherwise
6130            stack.push(to_stack_element(&[if valid_sigs >= m { 1 } else { 0 }]));
6131            Ok(true)
6132        }
6133
6134        // OP_CHECKMULTISIGVERIFY - CHECKMULTISIG + VERIFY (hot path)
6135        OP_CHECKMULTISIGVERIFY => {
6136            // Execute CHECKMULTISIG first
6137            let ctx_checkmultisig = context::ScriptContext {
6138                tx,
6139                input_index,
6140                prevout_values,
6141                prevout_script_pubkeys,
6142                block_height,
6143                median_time_past,
6144                network,
6145                sigversion,
6146                redeem_script_for_sighash,
6147                script_sig_for_sighash,
6148                tapscript_for_sighash,
6149                tapscript_codesep_pos,
6150                taproot_annex_hash: None,
6151                #[cfg(feature = "production")]
6152                schnorr_collector: None,
6153                #[cfg(feature = "production")]
6154                precomputed_bip143,
6155                #[cfg(feature = "production")]
6156                sighash_cache,
6157            };
6158            let result = execute_opcode_with_context_full(
6159                OP_CHECKMULTISIG,
6160                stack,
6161                flags,
6162                &ctx_checkmultisig,
6163                redeem_script_for_sighash,
6164            )?;
6165            if !result {
6166                return Ok(false);
6167            }
6168            // VERIFY: check top of stack is truthy, then pop it
6169            if let Some(top) = stack.pop() {
6170                if !cast_to_bool(&top) {
6171                    return Ok(false);
6172                }
6173                Ok(true)
6174            } else {
6175                Ok(false)
6176            }
6177        }
6178
6179        // OP_CHECKLOCKTIMEVERIFY (BIP65)
6180        // Validates that transaction locktime is >= top stack item
6181        // If SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY flag is not set, treat as NOP2
6182        // CLTV does NOT pop the stack — it only reads the top element (NOP-type opcode)
6183        OP_CHECKLOCKTIMEVERIFY => {
6184            // If CLTV flag is not enabled, behave as NOP (treat as NOP2)
6185            const SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY: u32 = 0x200;
6186            if (flags & SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY) == 0 {
6187                return Ok(true);
6188            }
6189
6190            use crate::locktime::{check_bip65, decode_locktime_value};
6191
6192            if stack.is_empty() {
6193                return Err(ConsensusError::ScriptErrorWithCode {
6194                    code: ScriptErrorCode::InvalidStackOperation,
6195                    message: "OP_CHECKLOCKTIMEVERIFY: empty stack".into(),
6196                });
6197            }
6198
6199            // Decode locktime value from stack using CScriptNum rules (max 5 bytes)
6200            let locktime_bytes = stack.last().expect("Stack is not empty");
6201            let locktime_value = match decode_locktime_value(locktime_bytes.as_ref()) {
6202                Some(v) => v,
6203                None => {
6204                    return Err(ConsensusError::ScriptErrorWithCode {
6205                        code: ScriptErrorCode::MinimalData,
6206                        message: "OP_CHECKLOCKTIMEVERIFY: invalid locktime encoding".into(),
6207                    });
6208                }
6209            };
6210
6211            let tx_locktime = tx.lock_time as u32;
6212
6213            // CheckLockTime order (BIP65): locktime check via check_bip65
6214            if !check_bip65(tx_locktime, locktime_value) {
6215                return Ok(false);
6216            }
6217
6218            // Input sequence must NOT be SEQUENCE_FINAL (0xffffffff)
6219            let input_seq = if input_index < tx.inputs.len() {
6220                tx.inputs[input_index].sequence
6221            } else {
6222                0xffffffff
6223            };
6224            if input_seq == 0xffffffff {
6225                return Ok(false);
6226            }
6227
6228            // CLTV does NOT pop the stack (NOP-type opcode)
6229            Ok(true)
6230        }
6231
6232        // OP_CHECKSEQUENCEVERIFY (BIP112)
6233        // Validates that transaction input sequence number meets relative locktime requirement.
6234        // Implements BIP68: Relative Lock-Time Using Consensus-Enforced Sequence Numbers.
6235        //
6236        // Behavior must match consensus (BIP65/112):
6237        // - If SCRIPT_VERIFY_CHECKSEQUENCEVERIFY flag is not set, behaves as a NOP (no-op)
6238        // - If sequence has the disable flag set (0x80000000), behaves as a NOP
6239        // - Does NOT remove the top stack item on success (non-consuming)
6240        OP_CHECKSEQUENCEVERIFY => {
6241            use crate::locktime::{
6242                decode_locktime_value, extract_sequence_locktime_value, extract_sequence_type_flag,
6243                is_sequence_disabled,
6244            };
6245
6246            // If CSV flag is not enabled, behave as NOP (treat as NOP3)
6247            const SCRIPT_VERIFY_CHECKSEQUENCEVERIFY: u32 = 0x400;
6248            if (flags & SCRIPT_VERIFY_CHECKSEQUENCEVERIFY) == 0 {
6249                return Ok(true);
6250            }
6251
6252            if stack.is_empty() {
6253                return Ok(false);
6254            }
6255
6256            // Decode sequence value from stack using shared locktime logic.
6257            // Interpret the top stack element as a sequence value (BIP112).
6258            let sequence_bytes = stack.last().expect("Stack is not empty");
6259            let sequence_value = match decode_locktime_value(sequence_bytes.as_ref()) {
6260                Some(v) => v,
6261                None => return Ok(false), // Invalid encoding
6262            };
6263
6264            // Get input sequence number
6265            if input_index >= tx.inputs.len() {
6266                return Ok(false);
6267            }
6268            let input_sequence = tx.inputs[input_index].sequence as u32;
6269
6270            // BIP112/BIP68: If sequence has the disable flag set, CSV behaves as a NOP
6271            if is_sequence_disabled(input_sequence) {
6272                return Ok(true);
6273            }
6274
6275            // BIP68: Extract relative locktime type and value using shared logic
6276            let type_flag = extract_sequence_type_flag(sequence_value);
6277            let locktime_mask = extract_sequence_locktime_value(sequence_value) as u32;
6278
6279            // Extract input sequence flags and value
6280            let input_type_flag = extract_sequence_type_flag(input_sequence);
6281            let input_locktime = extract_sequence_locktime_value(input_sequence) as u32;
6282
6283            // BIP112: CSV fails if type_flag doesn't match input type
6284            if type_flag != input_type_flag {
6285                return Ok(false);
6286            }
6287
6288            // BIP112: CSV fails if input locktime < required locktime
6289            if input_locktime < locktime_mask {
6290                return Ok(false);
6291            }
6292
6293            // Validation passed - behave as NOP (do NOT pop the sequence value)
6294            Ok(true)
6295        }
6296
6297        // OP_CHECKTEMPLATEVERIFY (BIP119) - OP_NOP4
6298        // Verifies that the transaction matches a template hash.
6299        // Implements BIP119: CHECKTEMPLATEVERIFY.
6300        //
6301        // Behavior must match consensus:
6302        // - If SCRIPT_VERIFY_DEFAULT_CHECK_TEMPLATE_VERIFY_HASH flag is not set, behaves as NOP4
6303        // - Requires exactly 32 bytes on stack (template hash)
6304        // - Fails if template hash doesn't match transaction
6305        OP_CHECKTEMPLATEVERIFY => {
6306            #[cfg(not(feature = "ctv"))]
6307            {
6308                // Without feature flag, treat as NOP4 (or discourage if flag set)
6309                const SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS: u32 = 0x10000;
6310                if (flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS) != 0 {
6311                    return Err(ConsensusError::ScriptErrorWithCode {
6312                        code: ScriptErrorCode::BadOpcode,
6313                        message: "OP_CHECKTEMPLATEVERIFY requires --features ctv".into(),
6314                    });
6315                }
6316                Ok(true) // NOP4
6317            }
6318
6319            #[cfg(feature = "ctv")]
6320            {
6321                use crate::constants::{
6322                    CTV_ACTIVATION_MAINNET, CTV_ACTIVATION_REGTEST, CTV_ACTIVATION_TESTNET,
6323                };
6324
6325                // Check activation
6326                let ctv_activation = match network {
6327                    crate::types::Network::Mainnet => CTV_ACTIVATION_MAINNET,
6328                    crate::types::Network::Testnet => CTV_ACTIVATION_TESTNET,
6329                    crate::types::Network::Regtest | crate::types::Network::Signet => {
6330                        CTV_ACTIVATION_REGTEST
6331                    }
6332                };
6333
6334                let ctv_active = block_height.map(|h| h >= ctv_activation).unwrap_or(false);
6335                if !ctv_active {
6336                    // Before activation: treat as NOP4
6337                    const SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS: u32 = 0x10000;
6338                    if (flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS) != 0 {
6339                        return Err(ConsensusError::ScriptErrorWithCode {
6340                            code: ScriptErrorCode::BadOpcode,
6341                            message: "OP_CHECKTEMPLATEVERIFY not yet activated".into(),
6342                        });
6343                    }
6344                    return Ok(true); // NOP4
6345                }
6346
6347                // Check if CTV flag is enabled
6348                const SCRIPT_VERIFY_DEFAULT_CHECK_TEMPLATE_VERIFY_HASH: u32 = 0x80000000;
6349                if (flags & SCRIPT_VERIFY_DEFAULT_CHECK_TEMPLATE_VERIFY_HASH) == 0 {
6350                    // Flag not set, treat as NOP4
6351                    return Ok(true);
6352                }
6353
6354                use crate::bip119::calculate_template_hash;
6355
6356                // CTV requires exactly 32 bytes (template hash) on stack
6357                if stack.is_empty() {
6358                    return Err(ConsensusError::ScriptErrorWithCode {
6359                        code: ScriptErrorCode::InvalidStackOperation,
6360                        message: "OP_CHECKTEMPLATEVERIFY: insufficient stack items".into(),
6361                    });
6362                }
6363
6364                let template_hash_bytes = stack.pop().unwrap();
6365
6366                // Template hash must be exactly 32 bytes
6367                if template_hash_bytes.len() != 32 {
6368                    // Non-32-byte argument: NOP (per BIP-119)
6369                    // But discourage if flag is set
6370                    const SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS: u32 = 0x10000;
6371                    if (flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS) != 0 {
6372                        return Err(ConsensusError::ScriptErrorWithCode {
6373                            code: ScriptErrorCode::InvalidStackOperation,
6374                            message: "OP_CHECKTEMPLATEVERIFY: template hash must be 32 bytes"
6375                                .into(),
6376                        });
6377                    }
6378                    return Ok(true); // NOP
6379                }
6380
6381                // Calculate actual template hash for this transaction
6382                let mut expected_hash = [0u8; 32];
6383                expected_hash.copy_from_slice(&template_hash_bytes);
6384
6385                let actual_hash = calculate_template_hash(tx, input_index).map_err(|e| {
6386                    ConsensusError::ScriptErrorWithCode {
6387                        code: ScriptErrorCode::TxInvalid,
6388                        message: format!("CTV hash calculation failed: {e}").into(),
6389                    }
6390                })?;
6391
6392                // Constant-time comparison (use hash_eq from crypto module)
6393                use crate::crypto::hash_compare::hash_eq;
6394                let matches = hash_eq(&expected_hash, &actual_hash);
6395
6396                if !matches {
6397                    return Ok(false); // Script fails if template doesn't match
6398                }
6399
6400                // CTV succeeds - script continues (NOP-type opcode, doesn't push anything)
6401                Ok(true)
6402            }
6403        }
6404
6405        // OP_CHECKSIGFROMSTACK (BIP348) - replaces OP_SUCCESS204
6406        // Verifies a BIP 340 Schnorr signature against an arbitrary message.
6407        // Implements BIP348: CHECKSIGFROMSTACK.
6408        //
6409        // Behavior must match BIP341 tapscript verification:
6410        // - Only available in Tapscript (leaf version 0xc0)
6411        // - Pops 3 items: pubkey (top), message (second), signature (third)
6412        // - If signature is empty, pushes empty vector and continues
6413        // - If signature is valid, pushes 0x01 (single byte)
6414        // - If signature is invalid, script fails
6415        OP_CHECKSIGFROMSTACK => {
6416            #[cfg(not(feature = "csfs"))]
6417            {
6418                // Without feature flag, OP_SUCCESS204 behavior (succeeds)
6419                // But discourage if flag is set
6420                const SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS: u32 = 0x10000;
6421                if (flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS) != 0 {
6422                    return Err(ConsensusError::ScriptErrorWithCode {
6423                        code: ScriptErrorCode::BadOpcode,
6424                        message: "OP_CHECKSIGFROMSTACK requires --features csfs".into(),
6425                    });
6426                }
6427                Ok(true) // OP_SUCCESS204 succeeds
6428            }
6429
6430            #[cfg(feature = "csfs")]
6431            {
6432                use crate::constants::{
6433                    CSFS_ACTIVATION_MAINNET, CSFS_ACTIVATION_REGTEST, CSFS_ACTIVATION_TESTNET,
6434                };
6435
6436                // BIP-348: Only available in Tapscript (leaf version 0xc0)
6437                if sigversion != SigVersion::Tapscript {
6438                    return Err(ConsensusError::ScriptErrorWithCode {
6439                        code: ScriptErrorCode::BadOpcode,
6440                        message: "OP_CHECKSIGFROMSTACK only available in Tapscript".into(),
6441                    });
6442                }
6443
6444                // Check activation
6445                let csfs_activation = match network {
6446                    crate::types::Network::Mainnet => CSFS_ACTIVATION_MAINNET,
6447                    crate::types::Network::Testnet => CSFS_ACTIVATION_TESTNET,
6448                    crate::types::Network::Regtest | crate::types::Network::Signet => {
6449                        CSFS_ACTIVATION_REGTEST
6450                    }
6451                };
6452
6453                let csfs_active = block_height.map(|h| h >= csfs_activation).unwrap_or(false);
6454                if !csfs_active {
6455                    // Before activation: OP_SUCCESS204 behavior (succeeds)
6456                    const SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS: u32 = 0x10000;
6457                    if (flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS) != 0 {
6458                        return Err(ConsensusError::ScriptErrorWithCode {
6459                            code: ScriptErrorCode::BadOpcode,
6460                            message: "OP_CHECKSIGFROMSTACK not yet activated".into(),
6461                        });
6462                    }
6463                    return Ok(true); // OP_SUCCESS204 succeeds
6464                }
6465
6466                use crate::bip348::verify_signature_from_stack;
6467
6468                // BIP-348: If fewer than 3 elements, script MUST fail
6469                if stack.len() < 3 {
6470                    return Err(ConsensusError::ScriptErrorWithCode {
6471                        code: ScriptErrorCode::InvalidStackOperation,
6472                        message: "OP_CHECKSIGFROMSTACK: insufficient stack items (need 3)".into(),
6473                    });
6474                }
6475
6476                // BIP-348: Pop in order: pubkey (top), message (second), signature (third)
6477                let pubkey_bytes = stack.pop().unwrap(); // Top
6478                let message_bytes = stack.pop().unwrap(); // Second
6479                let signature_bytes = stack.pop().unwrap(); // Third
6480
6481                // BIP-348: If pubkey size is zero, script MUST fail
6482                if pubkey_bytes.is_empty() {
6483                    return Err(ConsensusError::ScriptErrorWithCode {
6484                        code: ScriptErrorCode::PubkeyType,
6485                        message: "OP_CHECKSIGFROMSTACK: pubkey size is zero".into(),
6486                    });
6487                }
6488
6489                // BIP-348: If signature is empty, push empty vector and continue
6490                if signature_bytes.is_empty() {
6491                    stack.push(to_stack_element(&[])); // Empty vector, not 0
6492                    return Ok(true);
6493                }
6494
6495                // BIP-348: Verify signature (only for 32-byte pubkeys)
6496                // OPTIMIZATION: Use collector for batch verification if available
6497                #[cfg(feature = "production")]
6498                let is_valid = {
6499                    verify_signature_from_stack(
6500                        &message_bytes,    // Message (NOT hashed by BIP 340 spec)
6501                        &pubkey_bytes,     // Pubkey (32 bytes for BIP 340)
6502                        &signature_bytes,  // Signature (64-byte BIP 340 Schnorr)
6503                        schnorr_collector, // Pass collector for batch verification
6504                    )
6505                    .unwrap_or(false)
6506                };
6507                #[cfg(not(feature = "production"))]
6508                let is_valid = verify_signature_from_stack(
6509                    &message_bytes,   // Message (NOT hashed by BIP 340 spec)
6510                    &pubkey_bytes,    // Pubkey (32 bytes for BIP 340)
6511                    &signature_bytes, // Signature (64-byte BIP 340 Schnorr)
6512                )
6513                .unwrap_or(false);
6514
6515                if !is_valid {
6516                    // BIP-348: Validation failure immediately terminates script execution
6517                    return Ok(false);
6518                }
6519
6520                // BIP-342/348: per-tapscript validation weight enforces signature-related limits during
6521                // execution. They are not added to `MAX_BLOCK_SIGOPS_COST` (witness v1 returns 0 in Core's
6522                // WitnessSigOps).
6523
6524                // BIP-348: Push 0x01 (single byte) if valid
6525                stack.push(to_stack_element(&[0x01])); // Single byte 0x01, not 1
6526                Ok(true)
6527            }
6528        }
6529
6530        // cold path for all other opcodes (branch prediction hint)
6531        _ => execute_opcode_cold(opcode, stack, flags),
6532    }
6533}
6534
6535/// Rare opcode dispatch (`#[cold]` so hot path stays compact).
6536#[cold]
6537fn execute_opcode_cold(opcode: u8, stack: &mut Vec<StackElement>, flags: u32) -> Result<bool> {
6538    execute_opcode(opcode, stack, flags, SigVersion::Base)
6539}
6540
6541// ============================================================================
6542// Benchmarking Utilities
6543// ============================================================================
6544
6545/// Clear script verification cache
6546///
6547/// Useful for benchmarking to ensure consistent results without cache state
6548/// pollution between runs.
6549///
6550/// # Example
6551///
6552/// Get and reset fast-path hit counters (production). Used by block validation to log
6553/// whether P2PK/P2PKH/P2SH/P2WPKH/P2WSH fast-paths are taken vs interpreter fallback.
6554/// Returns (p2pk, p2pkh, p2sh, p2wpkh, p2wsh, p2tr, bare_multisig, interpreter).
6555#[cfg(feature = "production")]
6556pub(crate) fn get_and_reset_fast_path_counts() -> (u64, u64, u64, u64, u64, u64, u64, u64) {
6557    (
6558        FAST_PATH_P2PK.swap(0, Ordering::Relaxed),
6559        FAST_PATH_P2PKH.swap(0, Ordering::Relaxed),
6560        FAST_PATH_P2SH.swap(0, Ordering::Relaxed),
6561        FAST_PATH_P2WPKH.swap(0, Ordering::Relaxed),
6562        FAST_PATH_P2WSH.swap(0, Ordering::Relaxed),
6563        FAST_PATH_P2TR.swap(0, Ordering::Relaxed),
6564        FAST_PATH_BARE_MULTISIG.swap(0, Ordering::Relaxed),
6565        FAST_PATH_INTERPRETER.swap(0, Ordering::Relaxed),
6566    )
6567}
6568
6569/// ```rust
6570/// use blvm_consensus::script::clear_script_cache;
6571///
6572/// // Clear cache before benchmark run
6573/// clear_script_cache();
6574/// ```
6575#[cfg(all(feature = "production", feature = "benchmarking"))]
6576pub fn clear_script_cache() {
6577    if let Some(cache) = SCRIPT_CACHE.get() {
6578        let mut cache = cache.write().unwrap();
6579        cache.clear();
6580    }
6581}
6582
6583/// Clear hash operation cache
6584///
6585/// Useful for benchmarking to ensure consistent results without cache state
6586/// pollution between runs.
6587///
6588/// # Example
6589///
6590/// ```rust
6591/// use blvm_consensus::script::clear_hash_cache;
6592///
6593/// // Clear cache before benchmark run
6594/// clear_hash_cache();
6595/// ```
6596#[cfg(all(feature = "production", feature = "benchmarking"))]
6597pub fn clear_hash_cache() {
6598    crypto_ops::clear_hash_cache();
6599}
6600
6601/// Clear all caches
6602///
6603/// Convenience function to clear both script and hash caches.
6604///
6605/// # Example
6606///
6607/// ```rust
6608/// use blvm_consensus::script::clear_all_caches;
6609///
6610/// // Clear all caches before benchmark run
6611/// clear_all_caches();
6612/// ```
6613#[cfg(all(feature = "production", feature = "benchmarking"))]
6614pub fn clear_all_caches() {
6615    clear_script_cache();
6616    clear_hash_cache();
6617}
6618
6619/// Clear thread-local stack pool
6620///
6621/// Clears the thread-local stack pool to reset allocation state for benchmarking.
6622/// This ensures consistent memory allocation patterns across benchmark runs.
6623///
6624/// # Example
6625///
6626/// ```rust
6627/// use blvm_consensus::script::clear_stack_pool;
6628///
6629/// // Clear pool before benchmark run
6630/// clear_stack_pool();
6631/// ```
6632#[cfg(all(feature = "production", feature = "benchmarking"))]
6633pub fn clear_stack_pool() {
6634    STACK_POOL.with(|pool| {
6635        let mut pool = pool.borrow_mut();
6636        pool.clear();
6637    });
6638}
6639
6640/// Reset all benchmarking state
6641///
6642/// Convenience function to reset all caches and thread-local state for
6643/// reproducible benchmarks. Also clears sighash templates cache.
6644///
6645/// # Example
6646///
6647/// ```rust
6648/// use blvm_consensus::script::reset_benchmarking_state;
6649///
6650/// // Reset all state before benchmark run
6651/// reset_benchmarking_state();
6652/// ```
6653#[cfg(all(feature = "production", feature = "benchmarking"))]
6654pub fn reset_benchmarking_state() {
6655    clear_all_caches();
6656    clear_stack_pool();
6657    disable_caching(false); // Re-enable caching by default
6658    // Also clear sighash templates (currently no-op as templates aren't populated yet)
6659    #[cfg(feature = "benchmarking")]
6660    crate::transaction_hash::clear_sighash_templates();
6661}
6662
6663#[cfg(test)]
6664mod tests {
6665    use super::*;
6666
6667    #[test]
6668    fn test_eval_script_simple() {
6669        let script = vec![OP_1]; // OP_1
6670        let mut stack = Vec::new();
6671
6672        assert!(eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap());
6673        assert_eq!(stack.len(), 1);
6674        assert_eq!(stack[0].as_ref(), &[1]);
6675    }
6676
6677    #[test]
6678    fn test_eval_script_overflow() {
6679        let script = vec![0x51; MAX_STACK_SIZE + 1]; // Too many pushes
6680        let mut stack = Vec::new();
6681
6682        assert!(eval_script(&script, &mut stack, 0, SigVersion::Base).is_err());
6683    }
6684
6685    #[test]
6686    fn test_verify_script_simple() {
6687        let _script_sig = [0x51]; // OP_1
6688        let _script_pubkey = [0x51]; // OP_1
6689
6690        // This should work: OP_1 pushes 1, then OP_1 pushes another 1
6691        // Final stack has [1, 1], which is not exactly one non-zero value
6692        // Let's use a script that results in exactly one value on stack
6693        let script_sig = vec![0x51]; // OP_1
6694        let script_pubkey = vec![0x76, 0x88]; // OP_DUP, OP_EQUALVERIFY
6695
6696        // This should fail because OP_EQUALVERIFY removes both values
6697        assert!(!verify_script(&script_sig, &script_pubkey, None, 0).unwrap());
6698    }
6699
6700    // ============================================================================
6701    // COMPREHENSIVE OPCODE TESTS
6702    // ============================================================================
6703
6704    #[test]
6705    fn test_op_0() {
6706        let script = vec![OP_0]; // OP_0
6707        let mut stack = Vec::new();
6708        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6709        assert!(result); // eval_script always succeeds; stack has empty item (falsy but not an error)
6710        assert_eq!(stack.len(), 1);
6711        assert!(stack[0].is_empty());
6712    }
6713
6714    #[test]
6715    fn test_op_1_to_op_16() {
6716        // Test OP_1 through OP_16
6717        for i in 1..=16 {
6718            let opcode = 0x50 + i;
6719            let script = vec![opcode];
6720            let mut stack = Vec::new();
6721            let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6722            assert!(result);
6723            assert_eq!(stack.len(), 1);
6724            assert_eq!(stack[0].as_ref(), &[i]);
6725        }
6726    }
6727
6728    #[test]
6729    fn test_op_dup() {
6730        let script = vec![0x51, 0x76]; // OP_1, OP_DUP
6731        let mut stack = Vec::new();
6732        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6733        assert!(result); // eval_script succeeds; stack has 2 items (EvalScript doesn't check count)
6734        assert_eq!(stack.len(), 2);
6735        assert_eq!(stack[0].as_ref(), &[1]);
6736        assert_eq!(stack[1].as_ref(), &[1]);
6737    }
6738
6739    #[test]
6740    fn test_op_dup_empty_stack() {
6741        let script = vec![OP_DUP]; // OP_DUP on empty stack
6742        let mut stack = Vec::new();
6743        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6744        assert!(!result);
6745    }
6746
6747    #[test]
6748    fn test_op_hash160() {
6749        let script = vec![OP_1, OP_HASH160]; // OP_1, OP_HASH160
6750        let mut stack = Vec::new();
6751        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6752        assert!(result);
6753        assert_eq!(stack.len(), 1);
6754        assert_eq!(stack[0].len(), 20); // RIPEMD160 output is 20 bytes
6755    }
6756
6757    #[test]
6758    fn test_op_hash160_empty_stack() {
6759        let script = vec![OP_HASH160]; // OP_HASH160 on empty stack
6760        let mut stack = Vec::new();
6761        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6762        assert!(!result);
6763    }
6764
6765    #[test]
6766    fn test_op_hash256() {
6767        let script = vec![OP_1, OP_HASH256]; // OP_1, OP_HASH256
6768        let mut stack = Vec::new();
6769        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6770        assert!(result);
6771        assert_eq!(stack.len(), 1);
6772        assert_eq!(stack[0].len(), 32); // SHA256 output is 32 bytes
6773    }
6774
6775    #[test]
6776    fn test_op_hash256_empty_stack() {
6777        let script = vec![OP_HASH256]; // OP_HASH256 on empty stack
6778        let mut stack = Vec::new();
6779        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6780        assert!(!result);
6781    }
6782
6783    #[test]
6784    fn test_op_equal() {
6785        let script = vec![0x51, 0x51, 0x87]; // OP_1, OP_1, OP_EQUAL
6786        let mut stack = Vec::new();
6787        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6788        assert!(result);
6789        assert_eq!(stack.len(), 1);
6790        assert_eq!(stack[0].as_ref(), &[1]); // True
6791    }
6792
6793    #[test]
6794    fn test_op_equal_false() {
6795        let script = vec![0x51, 0x52, 0x87]; // OP_1, OP_2, OP_EQUAL
6796        let mut stack = Vec::new();
6797        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6798        assert!(result); // eval_script succeeds; OP_EQUAL pushes [] (empty = falsy, like Core's vchFalse)
6799        assert_eq!(stack.len(), 1);
6800        assert!(stack[0].is_empty()); // False is [] (empty), not [0x00]
6801    }
6802
6803    #[test]
6804    fn test_op_equal_insufficient_stack() {
6805        let script = vec![0x51, 0x87]; // OP_1, OP_EQUAL (need 2 items)
6806        let mut stack = Vec::new();
6807        let result = eval_script(&script, &mut stack, 0, SigVersion::Base);
6808        assert!(
6809            result.is_err(),
6810            "OP_EQUAL with insufficient stack should return error"
6811        );
6812        if let Err(crate::error::ConsensusError::ScriptErrorWithCode { code, .. }) = result {
6813            assert_eq!(
6814                code,
6815                crate::error::ScriptErrorCode::InvalidStackOperation,
6816                "Should return InvalidStackOperation"
6817            );
6818        }
6819    }
6820
6821    #[test]
6822    fn test_op_verify() {
6823        let script = vec![0x51, 0x69]; // OP_1, OP_VERIFY
6824        let mut stack = Vec::new();
6825        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6826        assert!(result); // eval_script succeeds; OP_VERIFY consumed the truthy item
6827        assert_eq!(stack.len(), 0); // OP_VERIFY consumes the top item
6828    }
6829
6830    #[test]
6831    fn test_op_verify_false() {
6832        let script = vec![0x00, 0x69]; // OP_0, OP_VERIFY (false)
6833        let mut stack = Vec::new();
6834        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6835        assert!(!result);
6836    }
6837
6838    #[test]
6839    fn test_op_verify_empty_stack() {
6840        let script = vec![OP_VERIFY]; // OP_VERIFY on empty stack
6841        let mut stack = Vec::new();
6842        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6843        assert!(!result);
6844    }
6845
6846    #[test]
6847    fn test_op_equalverify() {
6848        let script = vec![0x51, 0x51, 0x88]; // OP_1, OP_1, OP_EQUALVERIFY
6849        let mut stack = Vec::new();
6850        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6851        assert!(result); // eval_script succeeds; OP_EQUALVERIFY consumed both equal items
6852        assert_eq!(stack.len(), 0); // OP_EQUALVERIFY consumes both items
6853    }
6854
6855    #[test]
6856    fn test_op_equalverify_false() {
6857        let script = vec![0x51, 0x52, 0x88]; // OP_1, OP_2, OP_EQUALVERIFY (false)
6858        let mut stack = Vec::new();
6859        let result = eval_script(&script, &mut stack, 0, SigVersion::Base);
6860        assert!(
6861            result.is_err(),
6862            "OP_EQUALVERIFY with false condition should return error"
6863        );
6864        if let Err(crate::error::ConsensusError::ScriptErrorWithCode { code, .. }) = result {
6865            assert_eq!(
6866                code,
6867                crate::error::ScriptErrorCode::EqualVerify,
6868                "Should return EqualVerify"
6869            );
6870        }
6871    }
6872
6873    #[test]
6874    fn test_op_checksig() {
6875        // Note: This test uses simplified inputs. Production code performs full signature verification.
6876        // The test verifies that OP_CHECKSIG executes without panicking, not that signatures are valid.
6877        let script = vec![OP_1, OP_1, OP_CHECKSIG]; // OP_1, OP_1, OP_CHECKSIG
6878        let mut stack = Vec::new();
6879        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6880        assert!(result); // eval_script succeeds; CHECKSIG pushes [] (invalid sig = falsy) but no error
6881        assert_eq!(stack.len(), 1);
6882        // Production code validates signatures using secp256k1; test uses simplified inputs
6883    }
6884
6885    #[test]
6886    fn test_op_checksig_insufficient_stack() {
6887        let script = vec![OP_1, OP_CHECKSIG]; // OP_1, OP_CHECKSIG (need 2 items)
6888        let mut stack = Vec::new();
6889        let result = eval_script(&script, &mut stack, 0, SigVersion::Base);
6890        assert!(
6891            result.is_err(),
6892            "OP_CHECKSIG with insufficient stack should return error"
6893        );
6894        if let Err(crate::error::ConsensusError::ScriptErrorWithCode { code, .. }) = result {
6895            assert_eq!(
6896                code,
6897                crate::error::ScriptErrorCode::InvalidStackOperation,
6898                "Should return InvalidStackOperation"
6899            );
6900        }
6901    }
6902
6903    #[test]
6904    fn test_unknown_opcode() {
6905        let script = vec![0xff]; // Unknown opcode (0xff is not a defined opcode constant)
6906        let mut stack = Vec::new();
6907        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6908        assert!(!result);
6909    }
6910
6911    #[test]
6912    fn test_script_size_limit() {
6913        let script = vec![0x51; MAX_SCRIPT_SIZE + 1]; // Exceed size limit
6914        let mut stack = Vec::new();
6915        let result = eval_script(&script, &mut stack, 0, SigVersion::Base);
6916        assert!(result.is_err());
6917    }
6918
6919    #[test]
6920    fn test_operation_count_limit() {
6921        // Use OP_NOP (0x61) - non-push opcodes count toward limit
6922        let script = vec![0x61; MAX_SCRIPT_OPS + 1]; // Exceed operation limit
6923        let mut stack = Vec::new();
6924        let result = eval_script(&script, &mut stack, 0, SigVersion::Base);
6925        assert!(result.is_err());
6926    }
6927
6928    #[test]
6929    fn test_stack_underflow_multiple_ops() {
6930        let script = vec![0x51, 0x87, 0x87]; // OP_1, OP_EQUAL, OP_EQUAL (second OP_EQUAL will underflow)
6931        let mut stack = Vec::new();
6932        let result = eval_script(&script, &mut stack, 0, SigVersion::Base);
6933        assert!(result.is_err(), "Stack underflow should return error");
6934        if let Err(crate::error::ConsensusError::ScriptErrorWithCode { code, .. }) = result {
6935            assert_eq!(
6936                code,
6937                crate::error::ScriptErrorCode::InvalidStackOperation,
6938                "Should return InvalidStackOperation"
6939            );
6940        }
6941    }
6942
6943    #[test]
6944    fn test_final_stack_empty() {
6945        // eval_script (EvalScript) does not check the final stack — VerifyScript does.
6946        let script = vec![0x51, 0x52]; // OP_1, OP_2 (two items on final stack)
6947        let mut stack = Vec::new();
6948        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6949        assert!(result); // eval_script succeeds; final stack check is VerifyScript's responsibility
6950        assert_eq!(stack.len(), 2);
6951    }
6952
6953    #[test]
6954    fn test_final_stack_false() {
6955        // eval_script (EvalScript) does not check top-of-stack truthiness — VerifyScript does.
6956        let script = vec![OP_0]; // OP_0 (false on final stack)
6957        let mut stack = Vec::new();
6958        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6959        assert!(result); // eval_script succeeds; verify_script would return false here
6960    }
6961
6962    #[test]
6963    fn test_verify_script_with_witness() {
6964        // OP_1 sig + OP_1 pubkey + OP_1 witness leaves 3 truthy items; top is truthy → success.
6965        let script_sig = vec![OP_1]; // OP_1
6966        let script_pubkey = vec![OP_1]; // OP_1
6967        let witness = vec![OP_1]; // OP_1
6968        let flags = 0;
6969
6970        let result = verify_script(&script_sig, &script_pubkey, Some(&witness), flags).unwrap();
6971        assert!(result); // VerifyScript: top of stack is truthy regardless of stack size
6972    }
6973
6974    #[test]
6975    fn test_verify_script_failure() {
6976        // OP_0 pubkey pushes empty (falsy) item on top; verify_script returns false.
6977        let script_sig = vec![OP_1]; // OP_1 (truthy)
6978        let script_pubkey = vec![OP_0]; // OP_0 (pushes empty = falsy)
6979        let witness = None;
6980        let flags = 0;
6981
6982        let result = verify_script(&script_sig, &script_pubkey, witness, flags).unwrap();
6983        assert!(!result); // VerifyScript: top of stack is [] (falsy) → fail
6984    }
6985
6986    // ============================================================================
6987    // COMPREHENSIVE SCRIPT TESTS
6988    // ============================================================================
6989
6990    #[test]
6991    fn test_op_ifdup_true() {
6992        let script = vec![OP_1, OP_IFDUP]; // OP_1, OP_IFDUP
6993        let mut stack = Vec::new();
6994        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6995        assert!(result); // eval_script succeeds; stack has 2 items after dup
6996        assert_eq!(stack.len(), 2);
6997        assert_eq!(stack[0].as_ref(), &[1]);
6998        assert_eq!(stack[1].as_ref(), &[1]);
6999    }
7000
7001    #[test]
7002    fn test_op_ifdup_false() {
7003        let script = vec![OP_0, OP_IFDUP]; // OP_0, OP_IFDUP
7004        let mut stack = Vec::new();
7005        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
7006        assert!(result); // eval_script succeeds; IFDUP doesn't dup falsy items
7007        assert_eq!(stack.len(), 1);
7008        assert_eq!(stack[0].as_ref(), &[] as &[u8]);
7009    }
7010
7011    #[test]
7012    fn test_op_depth() {
7013        let script = vec![OP_1, OP_1, OP_DEPTH]; // OP_1, OP_1, OP_DEPTH
7014        let mut stack = Vec::new();
7015        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
7016        assert!(result); // eval_script succeeds; stack has 3 items
7017        assert_eq!(stack.len(), 3);
7018        assert_eq!(stack[2].as_ref(), &[2]); // Depth should be 2 (before OP_DEPTH)
7019    }
7020
7021    #[test]
7022    fn test_op_drop() {
7023        let script = vec![OP_1, OP_2, OP_DROP]; // OP_1, OP_2, OP_DROP
7024        let mut stack = Vec::new();
7025        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
7026        assert!(result); // Final stack has 1 item [1]
7027        assert_eq!(stack.len(), 1);
7028        assert_eq!(stack[0].as_ref(), &[1]);
7029    }
7030
7031    #[test]
7032    fn test_op_drop_empty_stack() {
7033        let script = vec![OP_DROP]; // OP_DROP on empty stack
7034        let mut stack = Vec::new();
7035        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
7036        assert!(!result);
7037        assert_eq!(stack.len(), 0);
7038    }
7039
7040    #[test]
7041    fn test_op_nip() {
7042        let script = vec![OP_1, OP_2, OP_NIP]; // OP_1, OP_2, OP_NIP
7043        let mut stack = Vec::new();
7044        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
7045        assert!(result); // Final stack has 1 item [2]
7046        assert_eq!(stack.len(), 1);
7047        assert_eq!(stack[0].as_ref(), &[2]);
7048    }
7049
7050    #[test]
7051    fn test_op_nip_insufficient_stack() {
7052        let script = vec![OP_1, OP_NIP]; // OP_1, OP_NIP (only 1 item)
7053        let mut stack = Vec::new();
7054        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
7055        assert!(!result);
7056        assert_eq!(stack.len(), 1);
7057    }
7058
7059    #[test]
7060    fn test_op_over() {
7061        let script = vec![OP_1, OP_2, OP_OVER]; // OP_1, OP_2, OP_OVER
7062        let mut stack = Vec::new();
7063        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
7064        assert!(result); // eval_script succeeds; stack has 3 items
7065        assert_eq!(stack.len(), 3);
7066        assert_eq!(stack[0].as_ref(), &[1]);
7067        assert_eq!(stack[1].as_ref(), &[2]);
7068        assert_eq!(stack[2].as_ref(), &[1]);
7069    }
7070
7071    #[test]
7072    fn test_op_over_insufficient_stack() {
7073        let script = vec![OP_1, OP_OVER]; // OP_1, OP_OVER (only 1 item)
7074        let mut stack = Vec::new();
7075        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
7076        assert!(!result);
7077        assert_eq!(stack.len(), 1);
7078    }
7079
7080    #[test]
7081    fn test_op_pick() {
7082        let script = vec![OP_1, OP_2, OP_3, OP_1, OP_PICK]; // OP_1, OP_2, OP_3, OP_1, OP_PICK
7083        let mut stack = Vec::new();
7084        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
7085        assert!(result); // eval_script succeeds; stack has 4 items
7086        assert_eq!(stack.len(), 4);
7087        assert_eq!(stack[3].as_ref(), &[2]); // Should pick index 1 (OP_2)
7088    }
7089
7090    #[test]
7091    fn test_op_pick_empty_n() {
7092        // OP_1, OP_0, OP_PICK: n=0 picks top item (duplicates it), stack [1,1]
7093        let script = vec![OP_1, OP_0, OP_PICK];
7094        let mut stack = Vec::new();
7095        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
7096        assert!(result); // eval_script succeeds; stack has 2 items
7097        assert_eq!(stack.len(), 2);
7098        assert_eq!(stack[1].as_ref(), &[1]); // Picked the top (OP_1 value)
7099    }
7100
7101    #[test]
7102    fn test_op_pick_invalid_index() {
7103        let script = vec![OP_1, OP_2, OP_PICK]; // OP_1, OP_2, OP_PICK (n=2, but only 1 item)
7104        let mut stack = Vec::new();
7105        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
7106        assert!(!result);
7107        assert_eq!(stack.len(), 1);
7108    }
7109
7110    #[test]
7111    fn test_op_roll() {
7112        let script = vec![OP_1, OP_2, OP_3, OP_1, OP_ROLL]; // OP_1, OP_2, OP_3, OP_1, OP_ROLL
7113        let mut stack = Vec::new();
7114        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
7115        assert!(result); // eval_script succeeds; stack has 3 items
7116        assert_eq!(stack.len(), 3);
7117        assert_eq!(stack[0].as_ref(), &[1]);
7118        assert_eq!(stack[1].as_ref(), &[3]);
7119        assert_eq!(stack[2].as_ref(), &[2]); // Should roll index 1 (OP_2) to top
7120    }
7121
7122    #[test]
7123    fn test_op_roll_zero_n() {
7124        // OP_0 pushes empty bytes (CScriptNum 0), OP_ROLL(0) is a valid no-op
7125        let script = vec![OP_1, OP_0, OP_ROLL]; // OP_1, OP_0, OP_ROLL (n=0, no-op)
7126        let mut stack = Vec::new();
7127        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
7128        assert!(result); // Stack has [1], which is truthy
7129        assert_eq!(stack.len(), 1);
7130        assert_eq!(stack[0].as_ref(), &[1]);
7131    }
7132
7133    #[test]
7134    fn test_op_roll_invalid_index() {
7135        let script = vec![OP_1, OP_2, OP_ROLL]; // OP_1, OP_2, OP_ROLL (n=2, but only 1 item)
7136        let mut stack = Vec::new();
7137        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
7138        assert!(!result);
7139        assert_eq!(stack.len(), 1);
7140    }
7141
7142    #[test]
7143    fn test_op_rot() {
7144        let script = vec![OP_1, OP_2, OP_3, OP_ROT]; // OP_1, OP_2, OP_3, OP_ROT
7145        let mut stack = Vec::new();
7146        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
7147        assert!(result); // eval_script succeeds; stack has 3 items
7148        assert_eq!(stack.len(), 3);
7149        assert_eq!(stack[0].as_ref(), &[2]);
7150        assert_eq!(stack[1].as_ref(), &[3]);
7151        assert_eq!(stack[2].as_ref(), &[1]);
7152    }
7153
7154    #[test]
7155    fn test_op_rot_insufficient_stack() {
7156        let script = vec![OP_1, OP_2, OP_ROT]; // OP_1, OP_2, OP_ROT (only 2 items)
7157        let mut stack = Vec::new();
7158        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
7159        assert!(!result);
7160        assert_eq!(stack.len(), 2);
7161    }
7162
7163    #[test]
7164    fn test_op_swap() {
7165        let script = vec![OP_1, OP_2, OP_SWAP]; // OP_1, OP_2, OP_SWAP
7166        let mut stack = Vec::new();
7167        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
7168        assert!(result); // eval_script succeeds; stack has 2 items
7169        assert_eq!(stack.len(), 2);
7170        assert_eq!(stack[0].as_ref(), &[2]);
7171        assert_eq!(stack[1].as_ref(), &[1]);
7172    }
7173
7174    #[test]
7175    fn test_op_swap_insufficient_stack() {
7176        let script = vec![OP_1, OP_SWAP]; // OP_1, OP_SWAP (only 1 item)
7177        let mut stack = Vec::new();
7178        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
7179        assert!(!result);
7180        assert_eq!(stack.len(), 1);
7181    }
7182
7183    #[test]
7184    fn test_op_tuck() {
7185        let script = vec![OP_1, OP_2, OP_TUCK]; // OP_1, OP_2, OP_TUCK
7186        let mut stack = Vec::new();
7187        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
7188        assert!(result); // eval_script succeeds; stack has 3 items
7189        assert_eq!(stack.len(), 3);
7190        assert_eq!(stack[0].as_ref(), &[2]);
7191        assert_eq!(stack[1].as_ref(), &[1]);
7192        assert_eq!(stack[2].as_ref(), &[2]);
7193    }
7194
7195    #[test]
7196    fn test_op_tuck_insufficient_stack() {
7197        let script = vec![OP_1, OP_TUCK]; // OP_1, OP_TUCK (only 1 item)
7198        let mut stack = Vec::new();
7199        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
7200        assert!(!result);
7201        assert_eq!(stack.len(), 1);
7202    }
7203
7204    #[test]
7205    fn test_op_2drop() {
7206        let script = vec![OP_1, OP_2, OP_3, OP_2DROP]; // OP_1, OP_2, OP_3, OP_2DROP
7207        let mut stack = Vec::new();
7208        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
7209        assert!(result); // Final stack has 1 item [1]
7210        assert_eq!(stack.len(), 1);
7211        assert_eq!(stack[0].as_ref(), &[1]);
7212    }
7213
7214    #[test]
7215    fn test_op_2drop_insufficient_stack() {
7216        let script = vec![OP_1, OP_2DROP]; // OP_1, OP_2DROP (only 1 item)
7217        let mut stack = Vec::new();
7218        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
7219        assert!(!result);
7220        assert_eq!(stack.len(), 1);
7221    }
7222
7223    #[test]
7224    fn test_op_2dup() {
7225        let script = vec![OP_1, OP_2, OP_2DUP]; // OP_1, OP_2, OP_2DUP
7226        let mut stack = Vec::new();
7227        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
7228        assert!(result); // eval_script succeeds; stack has 4 items
7229        assert_eq!(stack.len(), 4);
7230        assert_eq!(stack[0].as_ref(), &[1]);
7231        assert_eq!(stack[1].as_ref(), &[2]);
7232        assert_eq!(stack[2].as_ref(), &[1]);
7233        assert_eq!(stack[3].as_ref(), &[2]);
7234    }
7235
7236    #[test]
7237    fn test_op_2dup_insufficient_stack() {
7238        let script = vec![OP_1, OP_2DUP]; // OP_1, OP_2DUP (only 1 item)
7239        let mut stack = Vec::new();
7240        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
7241        assert!(!result);
7242        assert_eq!(stack.len(), 1);
7243    }
7244
7245    #[test]
7246    fn test_op_3dup() {
7247        let script = vec![OP_1, OP_2, OP_3, OP_3DUP]; // OP_1, OP_2, OP_3, OP_3DUP
7248        let mut stack = Vec::new();
7249        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
7250        assert!(result); // eval_script succeeds; stack has 6 items
7251        assert_eq!(stack.len(), 6);
7252        assert_eq!(stack[0].as_ref(), &[1]);
7253        assert_eq!(stack[1].as_ref(), &[2]);
7254        assert_eq!(stack[2].as_ref(), &[3]);
7255        assert_eq!(stack[3].as_ref(), &[1]);
7256        assert_eq!(stack[4].as_ref(), &[2]);
7257        assert_eq!(stack[5].as_ref(), &[3]);
7258    }
7259
7260    #[test]
7261    fn test_op_3dup_insufficient_stack() {
7262        let script = vec![OP_1, OP_2, OP_3DUP]; // OP_1, OP_2, OP_3DUP (only 2 items)
7263        let mut stack = Vec::new();
7264        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
7265        assert!(!result);
7266        assert_eq!(stack.len(), 2);
7267    }
7268
7269    #[test]
7270    fn test_op_2over() {
7271        let script = vec![OP_1, OP_2, OP_3, OP_4, OP_2OVER]; // OP_1, OP_2, OP_3, OP_4, OP_2OVER
7272        let mut stack = Vec::new();
7273        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
7274        assert!(result); // eval_script succeeds; stack has 6 items
7275        assert_eq!(stack.len(), 6);
7276        assert_eq!(stack[4].as_ref(), &[1]); // Should copy second pair
7277        assert_eq!(stack[5].as_ref(), &[2]);
7278    }
7279
7280    #[test]
7281    fn test_op_2over_insufficient_stack() {
7282        let script = vec![OP_1, OP_2, OP_3, OP_2OVER]; // OP_1, OP_2, OP_3, OP_2OVER (only 3 items)
7283        let mut stack = Vec::new();
7284        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
7285        assert!(!result);
7286        assert_eq!(stack.len(), 3);
7287    }
7288
7289    #[test]
7290    fn test_op_2rot() {
7291        // Stack before: [1,2,3,4,5,6] (1=bottom, 6=top)
7292        // 2ROT moves 6th and 5th from top (=1 and 2) to top preserving order.
7293        // Stack after:  [3,4,5,6,1,2] (2=top, 1=second)
7294        let script = vec![OP_1, OP_2, OP_3, OP_4, OP_5, OP_6, OP_2ROT]; // 6 items, OP_2ROT
7295        let mut stack = Vec::new();
7296        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
7297        assert!(result); // eval_script succeeds; stack has 6 items
7298        assert_eq!(stack.len(), 6);
7299        assert_eq!(stack[4].as_ref(), &[1]); // OP_1 moved to second-from-top
7300        assert_eq!(stack[5].as_ref(), &[2]); // OP_2 moved to top
7301    }
7302
7303    #[test]
7304    fn test_op_2rot_insufficient_stack() {
7305        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)
7306        let mut stack = Vec::new();
7307        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
7308        assert!(!result);
7309        assert_eq!(stack.len(), 4);
7310    }
7311
7312    #[test]
7313    fn test_op_2swap() {
7314        let script = vec![OP_1, OP_2, OP_3, OP_4, OP_2SWAP]; // OP_1, OP_2, OP_3, OP_4, OP_2SWAP
7315        let mut stack = Vec::new();
7316        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
7317        assert!(result); // eval_script succeeds; stack has 4 items
7318        assert_eq!(stack.len(), 4);
7319        assert_eq!(stack[0].as_ref(), &[3]); // Should swap second pair
7320        assert_eq!(stack[1].as_ref(), &[4]);
7321        assert_eq!(stack[2].as_ref(), &[1]);
7322        assert_eq!(stack[3].as_ref(), &[2]);
7323    }
7324
7325    #[test]
7326    fn test_op_2swap_insufficient_stack() {
7327        let script = vec![OP_1, OP_2, OP_3, OP_2SWAP]; // OP_1, OP_2, OP_3, OP_2SWAP (only 3 items)
7328        let mut stack = Vec::new();
7329        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
7330        assert!(!result);
7331        assert_eq!(stack.len(), 3);
7332    }
7333
7334    #[test]
7335    fn test_op_size() {
7336        let script = vec![OP_1, OP_SIZE]; // OP_1, OP_SIZE
7337        let mut stack = Vec::new();
7338        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
7339        assert!(result); // eval_script succeeds; stack has 2 items (original + size)
7340        assert_eq!(stack.len(), 2);
7341        assert_eq!(stack[0].as_ref(), &[1]);
7342        assert_eq!(stack[1].as_ref(), &[1]); // Size of [1] is 1
7343    }
7344
7345    #[test]
7346    fn test_op_size_empty_stack() {
7347        let script = vec![OP_SIZE]; // OP_SIZE on empty stack
7348        let mut stack = Vec::new();
7349        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
7350        assert!(!result);
7351        assert_eq!(stack.len(), 0);
7352    }
7353
7354    #[test]
7355    fn test_op_return() {
7356        let script = vec![OP_1, OP_RETURN]; // OP_1, OP_RETURN
7357        let mut stack = Vec::new();
7358        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
7359        assert!(!result); // OP_RETURN always fails
7360        assert_eq!(stack.len(), 1);
7361    }
7362
7363    #[test]
7364    fn test_op_checksigverify() {
7365        let script = vec![OP_1, OP_2, OP_CHECKSIGVERIFY]; // OP_1, OP_2, OP_CHECKSIGVERIFY
7366        let mut stack = Vec::new();
7367        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
7368        assert!(!result); // Should fail due to invalid signature
7369        assert_eq!(stack.len(), 0);
7370    }
7371
7372    #[test]
7373    fn test_op_checksigverify_insufficient_stack() {
7374        let script = vec![OP_1, OP_CHECKSIGVERIFY]; // OP_1, OP_CHECKSIGVERIFY (only 1 item)
7375        let mut stack = Vec::new();
7376        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
7377        assert!(!result);
7378        assert_eq!(stack.len(), 1);
7379    }
7380
7381    #[test]
7382    fn test_unknown_opcode_comprehensive() {
7383        let script = vec![OP_1, 0xff]; // OP_1, unknown opcode (0xff is not a defined opcode constant)
7384        let mut stack = Vec::new();
7385        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
7386        assert!(!result); // Unknown opcode should fail
7387        assert_eq!(stack.len(), 1);
7388    }
7389
7390    #[test]
7391    fn test_verify_signature_invalid_pubkey() {
7392        let secp = signature::new_secp();
7393        let invalid_pubkey = vec![0x00]; // Invalid pubkey
7394        let signature = vec![0x30, 0x06, 0x02, 0x01, 0x00, 0x02, 0x01, 0x00]; // Valid DER signature
7395        let dummy_hash = [0u8; 32];
7396        let result = signature::verify_signature(
7397            &secp,
7398            &invalid_pubkey,
7399            &signature,
7400            &dummy_hash,
7401            0,
7402            0,
7403            crate::types::Network::Regtest,
7404            SigVersion::Base,
7405        );
7406        assert!(!result.unwrap_or(false));
7407    }
7408
7409    #[test]
7410    fn test_verify_signature_invalid_signature() {
7411        let secp = signature::new_secp();
7412        let pubkey = vec![
7413            0x02, 0x79, 0xbe, 0x66, 0x7e, 0xf9, 0xdc, 0xbb, 0xac, 0x55, 0xa0, 0x62, 0x95, 0xce,
7414            0x87, 0x0b, 0x07, 0x02, 0x9b, 0xfc, 0xdb, 0x2d, 0xce, 0x28, 0xd9, 0x59, 0xf2, 0x81,
7415            0x5b, 0x16, 0xf8, 0x17, 0x98,
7416        ]; // Valid pubkey
7417        let invalid_signature = vec![0x00]; // Invalid signature
7418        let dummy_hash = [0u8; 32];
7419        let result = signature::verify_signature(
7420            &secp,
7421            &pubkey,
7422            &invalid_signature,
7423            &dummy_hash,
7424            0,
7425            0,
7426            crate::types::Network::Regtest,
7427            SigVersion::Base,
7428        );
7429        assert!(!result.unwrap_or(false));
7430    }
7431
7432    // ============================================================================
7433    // Fast-path and verify_script_with_context_full tests
7434    // ============================================================================
7435
7436    /// Build a minimal transaction and prevout slices for verify_script_with_context_full.
7437    fn minimal_tx_and_prevouts(
7438        script_sig: &[u8],
7439        script_pubkey: &[u8],
7440    ) -> (
7441        crate::types::Transaction,
7442        Vec<i64>,
7443        Vec<crate::types::ByteString>,
7444    ) {
7445        use crate::types::{OutPoint, Transaction, TransactionInput, TransactionOutput};
7446        let tx = Transaction {
7447            version: 1,
7448            inputs: vec![TransactionInput {
7449                prevout: OutPoint {
7450                    hash: [0u8; 32],
7451                    index: 0,
7452                },
7453                sequence: 0xffff_ffff,
7454                script_sig: script_sig.to_vec(),
7455            }]
7456            .into(),
7457            outputs: vec![TransactionOutput {
7458                value: 0,
7459                script_pubkey: script_pubkey.to_vec(),
7460            }]
7461            .into(),
7462            lock_time: 0,
7463        };
7464        let prevout_values = vec![0i64];
7465        let prevout_script_pubkeys_vec = vec![script_pubkey.to_vec()];
7466        let prevout_script_pubkeys: Vec<&ByteString> = prevout_script_pubkeys_vec.iter().collect();
7467        (tx, prevout_values, prevout_script_pubkeys_vec)
7468    }
7469
7470    #[test]
7471    fn test_verify_with_context_p2pkh_hash_mismatch() {
7472        // P2PKH pattern but pubkey hash does not match script_pubkey -> false (fast-path or interpreter).
7473        let pubkey = vec![0x02u8; 33]; // dummy compressed pubkey
7474        let sig = vec![0x30u8; 70]; // dummy sig (with sighash byte)
7475        let mut script_sig = Vec::new();
7476        script_sig.push(sig.len() as u8);
7477        script_sig.extend(&sig);
7478        script_sig.push(pubkey.len() as u8);
7479        script_sig.extend(&pubkey);
7480
7481        let mut script_pubkey = vec![OP_DUP, OP_HASH160, PUSH_20_BYTES];
7482        script_pubkey.extend(&[0u8; 20]); // wrong hash (not HASH160(pubkey))
7483        script_pubkey.push(OP_EQUALVERIFY);
7484        script_pubkey.push(OP_CHECKSIG);
7485
7486        let (tx, pv, psp) = minimal_tx_and_prevouts(&script_sig, &script_pubkey);
7487        let psp_refs: Vec<&[u8]> = psp.iter().map(|b| b.as_ref()).collect();
7488        let result = verify_script_with_context_full(
7489            &script_sig,
7490            &script_pubkey,
7491            None,
7492            0,
7493            &tx,
7494            0,
7495            &pv,
7496            &psp_refs,
7497            Some(500_000),
7498            None,
7499            crate::types::Network::Mainnet,
7500            SigVersion::Base,
7501            #[cfg(feature = "production")]
7502            None,
7503            None, // precomputed_bip143
7504            #[cfg(feature = "production")]
7505            None,
7506            #[cfg(feature = "production")]
7507            None,
7508            #[cfg(feature = "production")]
7509            None,
7510        );
7511        assert!(result.is_ok());
7512        assert!(!result.unwrap());
7513    }
7514
7515    #[test]
7516    fn test_verify_with_context_p2sh_hash_mismatch() {
7517        // P2SH pattern but redeem script hash does not match -> false.
7518        let redeem = vec![OP_1, OP_1, OP_ADD]; // minimal redeem
7519        let mut script_sig = Vec::new();
7520        script_sig.push(redeem.len() as u8);
7521        script_sig.extend(&redeem);
7522
7523        let mut script_pubkey = vec![OP_HASH160, PUSH_20_BYTES];
7524        script_pubkey.extend(&[0u8; 20]); // wrong hash (not HASH160(redeem))
7525        script_pubkey.push(OP_EQUAL);
7526
7527        let (tx, pv, psp) = minimal_tx_and_prevouts(&script_sig, &script_pubkey);
7528        let psp_refs: Vec<&[u8]> = psp.iter().map(|b| b.as_ref()).collect();
7529        let result = verify_script_with_context_full(
7530            &script_sig,
7531            &script_pubkey,
7532            None,
7533            0x01, // P2SH
7534            &tx,
7535            0,
7536            &pv,
7537            &psp_refs,
7538            Some(500_000),
7539            None,
7540            crate::types::Network::Mainnet,
7541            SigVersion::Base,
7542            #[cfg(feature = "production")]
7543            None,
7544            None, // precomputed_bip143
7545            #[cfg(feature = "production")]
7546            None,
7547            #[cfg(feature = "production")]
7548            None,
7549            #[cfg(feature = "production")]
7550            None,
7551        );
7552        assert!(result.is_ok());
7553        assert!(!result.unwrap());
7554    }
7555
7556    #[test]
7557    fn test_verify_with_context_p2wpkh_wrong_witness_size() {
7558        // P2WPKH script_pubkey but witness has 1 element (need 2) -> false.
7559        let mut script_pubkey = vec![OP_0, PUSH_20_BYTES];
7560        script_pubkey.extend(&[0u8; 20]);
7561        let witness: Vec<Vec<u8>> = vec![vec![0x30; 70]]; // only sig, no pubkey
7562        let (tx, pv, psp) = minimal_tx_and_prevouts(&[], &script_pubkey);
7563        let psp_refs: Vec<&[u8]> = psp.iter().map(|b| b.as_ref()).collect();
7564        let empty: Vec<u8> = vec![];
7565        let result = verify_script_with_context_full(
7566            &empty,
7567            &script_pubkey,
7568            Some(&witness),
7569            0,
7570            &tx,
7571            0,
7572            &pv,
7573            &psp_refs,
7574            Some(500_000),
7575            None,
7576            crate::types::Network::Mainnet,
7577            SigVersion::Base,
7578            #[cfg(feature = "production")]
7579            None,
7580            None, // precomputed_bip143
7581            #[cfg(feature = "production")]
7582            None,
7583            #[cfg(feature = "production")]
7584            None,
7585            #[cfg(feature = "production")]
7586            None,
7587        );
7588        assert!(result.is_ok());
7589        assert!(!result.unwrap());
7590    }
7591
7592    #[test]
7593    fn test_verify_with_context_p2wsh_wrong_witness_script_hash() {
7594        // P2WSH script_pubkey but SHA256(witness_script) != program -> false.
7595        let witness_script = vec![OP_1];
7596        let mut script_pubkey = vec![OP_0, PUSH_32_BYTES];
7597        script_pubkey.extend(&[0u8; 32]); // wrong hash (not SHA256(witness_script))
7598        let witness: Vec<Vec<u8>> = vec![witness_script];
7599        let (tx, pv, psp) = minimal_tx_and_prevouts(&[], &script_pubkey);
7600        let psp_refs: Vec<&[u8]> = psp.iter().map(|b| b.as_ref()).collect();
7601        let empty: Vec<u8> = vec![];
7602        let result = verify_script_with_context_full(
7603            &empty,
7604            &script_pubkey,
7605            Some(&witness),
7606            0,
7607            &tx,
7608            0,
7609            &pv,
7610            &psp_refs,
7611            Some(500_000),
7612            None,
7613            crate::types::Network::Mainnet,
7614            SigVersion::Base,
7615            #[cfg(feature = "production")]
7616            None,
7617            None, // precomputed_bip143
7618            #[cfg(feature = "production")]
7619            None,
7620            #[cfg(feature = "production")]
7621            None,
7622            #[cfg(feature = "production")]
7623            None,
7624        );
7625        assert!(result.is_ok());
7626        assert!(!result.unwrap());
7627    }
7628
7629    #[test]
7630    #[cfg(feature = "production")]
7631    fn test_p2wsh_multisig_fast_path() {
7632        // P2WSH 2-of-2 multisig: fast path parses and validates; placeholder sigs fail -> Ok(false).
7633        use crate::constants::BIP147_ACTIVATION_MAINNET;
7634        use crate::crypto::OptimizedSha256;
7635
7636        let pk1 = [0x02u8; 33];
7637        let pk2 = [0x03u8; 33];
7638        let mut witness_script = vec![0x52]; // OP_2
7639        witness_script.extend_from_slice(&pk1);
7640        witness_script.extend_from_slice(&pk2);
7641        witness_script.push(0x52); // OP_2
7642        witness_script.push(0xae); // OP_CHECKMULTISIG
7643
7644        let wsh_hash = OptimizedSha256::new().hash(&witness_script);
7645        let mut script_pubkey = vec![OP_0, PUSH_32_BYTES];
7646        script_pubkey.extend_from_slice(&wsh_hash);
7647
7648        let witness: Vec<Vec<u8>> = vec![
7649            vec![0x00],       // NULLDUMMY
7650            vec![0x30u8; 72], // placeholder sig 1
7651            vec![0x30u8; 72], // placeholder sig 2
7652            witness_script.clone(),
7653        ];
7654
7655        let (tx, pv, psp) = minimal_tx_and_prevouts(&[], &script_pubkey);
7656        let psp_refs: Vec<&[u8]> = psp.iter().map(|b| b.as_ref()).collect();
7657        let empty: Vec<u8> = vec![];
7658        let result = verify_script_with_context_full(
7659            &empty,
7660            &script_pubkey,
7661            Some(&witness),
7662            0x810, // SIGHASH_ALL | VERIFY_NULLDUMMY | VERIFY_NULLFAIL
7663            &tx,
7664            0,
7665            &pv,
7666            &psp_refs,
7667            Some(BIP147_ACTIVATION_MAINNET + 1),
7668            None,
7669            crate::types::Network::Mainnet,
7670            SigVersion::Base,
7671            #[cfg(feature = "production")]
7672            None,
7673            None, // precomputed_bip143
7674            #[cfg(feature = "production")]
7675            None,
7676            #[cfg(feature = "production")]
7677            None,
7678            #[cfg(feature = "production")]
7679            None,
7680        );
7681        assert!(result.is_ok());
7682        assert!(!result.unwrap());
7683    }
7684}
7685
7686#[cfg(test)]
7687#[allow(unused_doc_comments)]
7688mod property_tests {
7689    use super::*;
7690    use proptest::prelude::*;
7691
7692    /// Property test: eval_script respects operation limits
7693    ///
7694    /// Mathematical specification:
7695    /// ∀ script ∈ ByteString: |script| > MAX_SCRIPT_OPS ⟹ eval_script fails
7696    proptest! {
7697        #[test]
7698        fn prop_eval_script_operation_limit(script in prop::collection::vec(any::<u8>(), 0..300)) {
7699            let mut stack = Vec::new();
7700            let flags = 0u32;
7701
7702            let result = eval_script(&script, &mut stack, flags, SigVersion::Base);
7703
7704            // Note: Production code tracks op_count precisely (number of non-push opcodes executed).
7705            // Script length can be larger than op_count if there are data pushes.
7706            // For a script with only opcodes (no data pushes), length = op_count.
7707            // So scripts with length > MAX_SCRIPT_OPS that are all opcodes will fail.
7708            // But scripts with data pushes might have length > MAX_SCRIPT_OPS but op_count <= MAX_SCRIPT_OPS.
7709            // This property test verifies that very long scripts (> MAX_SCRIPT_OPS * 2) eventually fail
7710            // or that the operation limit is respected.
7711            if script.len() > MAX_SCRIPT_OPS * 2 {
7712                // Very long scripts should fail (either op limit or other reasons)
7713                // This is a weak check but acceptable for property testing
7714                prop_assert!(result.is_err() || !result.unwrap(),
7715                    "Very long scripts should fail or return false");
7716            }
7717            // Otherwise, scripts may succeed or fail - both are acceptable
7718        }
7719    }
7720
7721    /// Property test: verify_script is deterministic
7722    ///
7723    /// Mathematical specification:
7724    /// ∀ inputs: verify_script(inputs) = verify_script(inputs)
7725    proptest! {
7726        #[test]
7727        fn prop_verify_script_deterministic(
7728            script_sig in prop::collection::vec(any::<u8>(), 0..20),
7729            script_pubkey in prop::collection::vec(any::<u8>(), 0..20),
7730            witness in prop::option::of(prop::collection::vec(any::<u8>(), 0..10)),
7731            flags in any::<u32>()
7732        ) {
7733            let result1 = verify_script(&script_sig, &script_pubkey, witness.as_ref(), flags);
7734            let result2 = verify_script(&script_sig, &script_pubkey, witness.as_ref(), flags);
7735
7736            assert_eq!(result1.is_ok(), result2.is_ok());
7737            if result1.is_ok() && result2.is_ok() {
7738                assert_eq!(result1.unwrap(), result2.unwrap());
7739            }
7740        }
7741    }
7742
7743    /// Property test: execute_opcode handles all opcodes without panicking
7744    ///
7745    /// Mathematical specification:
7746    /// ∀ opcode ∈ {0..255}, stack ∈ Vec<StackElement>: execute_opcode(opcode, stack) ∈ {true, false}
7747    proptest! {
7748        #[test]
7749        fn prop_execute_opcode_no_panic(
7750            opcode in any::<u8>(),
7751            stack_items in prop::collection::vec(
7752                prop::collection::vec(any::<u8>(), 0..5),
7753                0..10
7754            ),
7755            flags in any::<u32>()
7756        ) {
7757            let mut stack: Vec<StackElement> = stack_items.into_iter().map(|v| to_stack_element(&v)).collect();
7758            let result = execute_opcode(opcode, &mut stack, flags, SigVersion::Base);
7759
7760            // Some opcodes may return errors (invalid opcodes, insufficient stack, etc.)
7761            // The important thing is that it doesn't panic
7762            match result {
7763                Ok(success) => {
7764                    // Just test it returns a boolean (success is either true or false)
7765                    let _ = success;
7766                },
7767                Err(_) => {
7768                    // Errors are acceptable - invalid opcodes, insufficient stack, etc.
7769                    // The test is about not panicking, not about always succeeding
7770                }
7771            }
7772
7773            // Stack should remain within bounds
7774            assert!(stack.len() <= MAX_STACK_SIZE);
7775        }
7776    }
7777
7778    /// Property test: stack operations preserve bounds
7779    ///
7780    /// Mathematical specification:
7781    /// ∀ opcode ∈ {0..255}, stack ∈ Vec<StackElement>:
7782    /// - |stack| ≤ MAX_STACK_SIZE before and after execute_opcode
7783    /// - Stack operations are well-defined
7784    proptest! {
7785        #[test]
7786        fn prop_stack_operations_bounds(
7787            opcode in any::<u8>(),
7788            stack_items in prop::collection::vec(
7789                prop::collection::vec(any::<u8>(), 0..3),
7790                0..5
7791            ),
7792            flags in any::<u32>()
7793        ) {
7794            let mut stack: Vec<StackElement> = stack_items.into_iter().map(|v| to_stack_element(&v)).collect();
7795            let initial_len = stack.len();
7796
7797            let result = execute_opcode(opcode, &mut stack, flags, SigVersion::Base);
7798
7799            // Stack should never exceed MAX_STACK_SIZE
7800            assert!(stack.len() <= MAX_STACK_SIZE);
7801
7802            // If operation succeeded, stack should be in valid state
7803            if result.is_ok() && result.unwrap() {
7804                // For opcodes that modify stack size, verify reasonable bounds
7805                match opcode {
7806                    OP_0 | OP_1..=OP_16 => {
7807                        // Push opcodes - increase by 1
7808                        assert!(stack.len() == initial_len + 1);
7809                    },
7810                    OP_DUP => {
7811                        // OP_DUP - increase by 1
7812                        if initial_len > 0 {
7813                            assert!(stack.len() == initial_len + 1);
7814                        }
7815                    },
7816                    OP_3DUP => {
7817                        // OP_3DUP - increases by 3 if stack has >= 3 items
7818                        if initial_len >= 3 {
7819                            assert!(stack.len() == initial_len + 3);
7820                        }
7821                    },
7822                    OP_2OVER => {
7823                        // OP_2OVER - increases by 2 if stack has >= 4 items
7824                        if initial_len >= 4 {
7825                            assert!(stack.len() == initial_len + 2);
7826                        }
7827                    },
7828                    OP_DROP | OP_NIP | OP_2DROP => {
7829                        // These opcodes decrease stack size
7830                        assert!(stack.len() <= initial_len);
7831                    },
7832                    _ => {
7833                        // Other opcodes maintain or modify stack size reasonably
7834                        // Some opcodes can push multiple items, so allow up to +3
7835                        assert!(stack.len() <= initial_len + 3, "Stack size should be reasonable");
7836                    }
7837                }
7838            }
7839        }
7840    }
7841
7842    /// Property test: hash operations are deterministic
7843    ///
7844    /// Mathematical specification:
7845    /// ∀ input ∈ ByteString: OP_HASH160(input) = OP_HASH160(input)
7846    proptest! {
7847        #[test]
7848        fn prop_hash_operations_deterministic(
7849            input in prop::collection::vec(any::<u8>(), 0..10)
7850        ) {
7851            let elem = to_stack_element(&input);
7852            let mut stack1 = vec![elem.clone()];
7853            let mut stack2 = vec![elem];
7854
7855            let result1 = execute_opcode(0xa9, &mut stack1, 0, SigVersion::Base); // OP_HASH160
7856            let result2 = execute_opcode(0xa9, &mut stack2, 0, SigVersion::Base); // OP_HASH160
7857
7858            assert_eq!(result1.is_ok(), result2.is_ok());
7859            if let (Ok(val1), Ok(val2)) = (result1, result2) {
7860                assert_eq!(val1, val2);
7861                if val1 {
7862                    assert_eq!(stack1, stack2);
7863                }
7864            }
7865        }
7866    }
7867
7868    /// Property test: equality operations are symmetric
7869    ///
7870    /// Mathematical specification:
7871    /// ∀ a, b ∈ ByteString: OP_EQUAL(a, b) = OP_EQUAL(b, a)
7872    proptest! {
7873        #[test]
7874        fn prop_equality_operations_symmetric(
7875            a in prop::collection::vec(any::<u8>(), 0..5),
7876            b in prop::collection::vec(any::<u8>(), 0..5)
7877        ) {
7878            let mut stack1 = vec![to_stack_element(&a), to_stack_element(&b)];
7879            let mut stack2 = vec![to_stack_element(&b), to_stack_element(&a)];
7880
7881            let result1 = execute_opcode(0x87, &mut stack1, 0, SigVersion::Base); // OP_EQUAL
7882            let result2 = execute_opcode(0x87, &mut stack2, 0, SigVersion::Base); // OP_EQUAL
7883
7884            assert_eq!(result1.is_ok(), result2.is_ok());
7885            if let (Ok(val1), Ok(val2)) = (result1, result2) {
7886                assert_eq!(val1, val2);
7887                if val1 {
7888                    // Results should be identical (both true or both false)
7889                    assert_eq!(stack1.len(), stack2.len());
7890                    if !stack1.is_empty() && !stack2.is_empty() {
7891                        assert_eq!(stack1[0], stack2[0]);
7892                    }
7893                }
7894            }
7895        }
7896    }
7897
7898    /// Property test: script execution terminates
7899    ///
7900    /// Mathematical specification:
7901    /// ∀ script ∈ ByteString: eval_script(script) terminates (no infinite loops)
7902    proptest! {
7903        #[test]
7904        fn prop_script_execution_terminates(
7905            script in prop::collection::vec(any::<u8>(), 0..50)
7906        ) {
7907            let mut stack = Vec::new();
7908            let flags = 0u32;
7909
7910            // This should complete without hanging
7911            let result = eval_script(&script, &mut stack, flags, SigVersion::Base);
7912
7913            // Should return a result (success or failure)
7914            assert!(result.is_ok() || result.is_err());
7915
7916            // Stack should be in valid state
7917            assert!(stack.len() <= MAX_STACK_SIZE);
7918        }
7919    }
7920}