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