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