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#[blvm_spec_lock::axiom(result >= -549755813887)]
3793#[blvm_spec_lock::ensures(result >= -549755813887)]
3794#[cfg(feature = "production")]
3795#[inline(always)]
3796pub(crate) fn script_num_decode(data: &[u8], max_num_size: usize) -> Result<i64> {
3797    if data.len() > max_num_size {
3798        return Err(ConsensusError::ScriptErrorWithCode {
3799            code: ScriptErrorCode::InvalidStackOperation,
3800            message: format!(
3801                "Script number overflow: {} > {} bytes",
3802                data.len(),
3803                max_num_size
3804            )
3805            .into(),
3806        });
3807    }
3808    if data.is_empty() {
3809        return Ok(0);
3810    }
3811
3812    // Fast paths for common sizes (most script numbers are 1-2 bytes)
3813    let len = data.len();
3814    let result = match len {
3815        1 => {
3816            let byte = data[0];
3817            if byte & 0x80 != 0 {
3818                // Negative: clear sign bit and negate
3819                -((byte & 0x7f) as i64)
3820            } else {
3821                byte as i64
3822            }
3823        }
3824        2 => {
3825            let byte0 = data[0] as i64;
3826            let byte1 = data[1] as i64;
3827            let value = byte0 | (byte1 << 8);
3828            if byte1 & 0x80 != 0 {
3829                // Negative: clear sign bit and negate
3830                -(value & !(0x80i64 << 8))
3831            } else {
3832                value
3833            }
3834        }
3835        _ => {
3836            // General case for 3+ bytes
3837            let mut result: i64 = 0;
3838            for (i, &byte) in data.iter().enumerate() {
3839                result |= (byte as i64) << (8 * i);
3840            }
3841            // Check sign bit (MSB of last byte) - safe because len > 0
3842            let last_idx = len - 1;
3843            if data[last_idx] & 0x80 != 0 {
3844                // Negative: clear sign bit and negate
3845                result &= !(0x80i64 << (8 * last_idx));
3846                result = -result;
3847            }
3848            result
3849        }
3850    };
3851
3852    Ok(result)
3853}
3854
3855#[cfg(not(feature = "production"))]
3856#[inline]
3857pub(crate) fn script_num_decode(data: &[u8], max_num_size: usize) -> Result<i64> {
3858    if data.len() > max_num_size {
3859        return Err(ConsensusError::ScriptErrorWithCode {
3860            code: ScriptErrorCode::InvalidStackOperation,
3861            message: format!(
3862                "Script number overflow: {} > {} bytes",
3863                data.len(),
3864                max_num_size
3865            )
3866            .into(),
3867        });
3868    }
3869    if data.is_empty() {
3870        return Ok(0);
3871    }
3872    // Little-endian decode
3873    let mut result: i64 = 0;
3874    for (i, &byte) in data.iter().enumerate() {
3875        result |= (byte as i64) << (8 * i);
3876    }
3877    // Check sign bit (MSB of last byte)
3878    if data.last().expect("Data is not empty") & 0x80 != 0 {
3879        // Negative: clear sign bit and negate
3880        result &= !(0x80i64 << (8 * (data.len() - 1)));
3881        result = -result;
3882    }
3883    Ok(result)
3884}
3885
3886/// Encode an i64 as CScriptNum byte representation.
3887/// CScriptNum::serialize() — BIP62 numeric encoding.
3888#[cfg(feature = "production")]
3889pub(crate) fn script_num_encode(value: i64) -> Vec<u8> {
3890    // Fast paths for common values
3891    match value {
3892        0 => return vec![],
3893        1 => return vec![1],
3894        -1 => return vec![0x81],
3895        _ => {}
3896    }
3897
3898    let neg = value < 0;
3899    let mut absvalue = if neg {
3900        (-(value as i128)) as u64
3901    } else {
3902        value as u64
3903    };
3904    // Pre-allocate Vec: most script numbers are 1-4 bytes
3905    let mut result = Vec::with_capacity(4);
3906    while absvalue > 0 {
3907        result.push((absvalue & 0xff) as u8);
3908        absvalue >>= 8;
3909    }
3910    // If MSB is set, add extra byte for sign
3911    if result.last().expect("Result is not empty (absvalue > 0)") & 0x80 != 0 {
3912        result.push(if neg { 0x80 } else { 0x00 });
3913    } else if neg {
3914        *result.last_mut().unwrap() |= 0x80;
3915    }
3916    result
3917}
3918
3919#[cfg(not(feature = "production"))]
3920pub(crate) fn script_num_encode(value: i64) -> Vec<u8> {
3921    if value == 0 {
3922        return vec![];
3923    }
3924    let neg = value < 0;
3925    let mut absvalue = if neg {
3926        (-(value as i128)) as u64
3927    } else {
3928        value as u64
3929    };
3930    let mut result = Vec::new();
3931    while absvalue > 0 {
3932        result.push((absvalue & 0xff) as u8);
3933        absvalue >>= 8;
3934    }
3935    // If MSB is set, add extra byte for sign
3936    if result.last().expect("Result is not empty (absvalue > 0)") & 0x80 != 0 {
3937        result.push(if neg { 0x80 } else { 0x00 });
3938    } else if neg {
3939        *result.last_mut().unwrap() |= 0x80;
3940    }
3941    result
3942}
3943
3944/// Execute a single opcode (currently ignores sigversion; accepts it for future compatibility)
3945#[cfg(feature = "production")]
3946#[inline(always)]
3947fn execute_opcode(
3948    opcode: u8,
3949    stack: &mut Vec<StackElement>,
3950    flags: u32,
3951    _sigversion: SigVersion,
3952) -> Result<bool> {
3953    match opcode {
3954        // OP_0 - push empty array
3955        OP_0 => {
3956            stack.push(to_stack_element(&[]));
3957            Ok(true)
3958        }
3959
3960        // OP_1 to OP_16 - push numbers 1-16
3961        OP_1..=OP_16 => {
3962            let num = opcode - OP_N_BASE;
3963            stack.push(to_stack_element(&[num]));
3964            Ok(true)
3965        }
3966
3967        // OP_NOP - do nothing, execution continues
3968        OP_NOP => Ok(true),
3969
3970        // OP_VER - disabled opcode, always fails
3971        OP_VER => Ok(false),
3972
3973        // OP_DEPTH - push stack size
3974        OP_DEPTH => {
3975            let depth = stack.len() as i64;
3976            stack.push(to_stack_element(&script_num_encode(depth)));
3977            Ok(true)
3978        }
3979
3980        // OP_DUP - duplicate top stack item
3981        OP_DUP => {
3982            if let Some(item) = stack.last().cloned() {
3983                stack.push(item);
3984                Ok(true)
3985            } else {
3986                Ok(false)
3987            }
3988        }
3989
3990        // OP_RIPEMD160 - RIPEMD160(x)
3991        OP_RIPEMD160 => crypto_ops::op_ripemd160(stack),
3992
3993        // OP_SHA1 - SHA1(x)
3994        OP_SHA1 => crypto_ops::op_sha1(stack),
3995
3996        // OP_SHA256 - SHA256(x)
3997        OP_SHA256 => crypto_ops::op_sha256(stack),
3998
3999        // OP_HASH160 - RIPEMD160(SHA256(x))
4000        OP_HASH160 => crypto_ops::op_hash160(stack),
4001
4002        // OP_HASH256 - SHA256(SHA256(x))
4003        OP_HASH256 => crypto_ops::op_hash256(stack),
4004
4005        // OP_EQUAL - check if top two stack items are equal
4006        OP_EQUAL => {
4007            if stack.len() < 2 {
4008                return Err(ConsensusError::ScriptErrorWithCode {
4009                    code: ScriptErrorCode::InvalidStackOperation,
4010                    message: "OP_EQUAL: insufficient stack items".into(),
4011                });
4012            }
4013            let a = stack.pop().unwrap();
4014            let b = stack.pop().unwrap();
4015            stack.push(to_stack_element(&[if a == b { 1 } else { 0 }]));
4016            Ok(true)
4017        }
4018
4019        // OP_EQUALVERIFY - verify top two stack items are equal
4020        // OP_EQUAL followed by pop if equal
4021        OP_EQUALVERIFY => {
4022            if stack.len() < 2 {
4023                return Err(ConsensusError::ScriptErrorWithCode {
4024                    code: ScriptErrorCode::InvalidStackOperation,
4025                    message: "OP_EQUALVERIFY: insufficient stack items".into(),
4026                });
4027            }
4028            let a = stack.pop().unwrap();
4029            let b = stack.pop().unwrap();
4030            let f_equal = a == b;
4031            // Push result (like OP_EQUAL does)
4032            stack.push(to_stack_element(&[if f_equal { 1 } else { 0 }]));
4033            if f_equal {
4034                // Pop the true value
4035                stack.pop();
4036                Ok(true)
4037            } else {
4038                Err(ConsensusError::ScriptErrorWithCode {
4039                    code: ScriptErrorCode::EqualVerify,
4040                    message: "OP_EQUALVERIFY: stack items not equal".into(),
4041                })
4042            }
4043        }
4044
4045        // OP_CHECKSIG - verify ECDSA signature (simple path, no tx context)
4046        OP_CHECKSIG => crypto_ops::op_checksig_simple(stack, flags),
4047
4048        // OP_CHECKSIGVERIFY - verify ECDSA signature and fail if invalid (simple path)
4049        OP_CHECKSIGVERIFY => crypto_ops::op_checksigverify_simple(stack, flags),
4050
4051        // OP_RETURN - always fail (unspendable output)
4052        OP_RETURN => Ok(false),
4053
4054        // OP_VERIFY - check if top stack item is non-zero
4055        OP_VERIFY => {
4056            if let Some(item) = stack.pop() {
4057                Ok(cast_to_bool(&item))
4058            } else {
4059                Ok(false)
4060            }
4061        }
4062
4063        // OP_CHECKLOCKTIMEVERIFY (BIP65)
4064        // Note: Requires transaction context for proper validation.
4065        // This basic implementation will fail - use verify_script_with_context for proper CLTV validation.
4066        OP_CHECKLOCKTIMEVERIFY => {
4067            // CLTV requires transaction locktime and block context, so it always fails here
4068            // Proper implementation is in execute_opcode_with_context
4069            Ok(false)
4070        }
4071
4072        // OP_CHECKSEQUENCEVERIFY (BIP112)
4073        // Note: Requires transaction context for proper validation.
4074        // This basic implementation will fail - use verify_script_with_context for proper CSV validation.
4075        OP_CHECKSEQUENCEVERIFY => {
4076            // CSV requires transaction sequence and block context, so it always fails here
4077            // Proper implementation is in execute_opcode_with_context
4078            Ok(false)
4079        }
4080
4081        // OP_IFDUP - duplicate top stack item if it's non-zero
4082        OP_IFDUP => {
4083            if let Some(item) = stack.last().cloned() {
4084                if cast_to_bool(&item) {
4085                    stack.push(item);
4086                }
4087                Ok(true)
4088            } else {
4089                Ok(false)
4090            }
4091        }
4092
4093        // OP_DEPTH - push stack size (duplicate handler removed, using single implementation)
4094        // OP_DROP - remove top stack item
4095        OP_DROP => {
4096            if stack.pop().is_some() {
4097                Ok(true)
4098            } else {
4099                Ok(false)
4100            }
4101        }
4102
4103        // OP_NIP - remove second-to-top stack item
4104        OP_NIP => {
4105            if stack.len() >= 2 {
4106                let top = stack.pop().unwrap();
4107                stack.pop(); // Remove second-to-top
4108                stack.push(top);
4109                Ok(true)
4110            } else {
4111                Ok(false)
4112            }
4113        }
4114
4115        // OP_OVER - copy second-to-top stack item to top
4116        OP_OVER => {
4117            if stack.len() >= 2 {
4118                let len = stack.len();
4119                #[cfg(feature = "production")]
4120                {
4121                    // Use proven bounds after length check
4122                    unsafe {
4123                        let second = stack.get_unchecked(len - 2);
4124                        stack.push(second.clone());
4125                    }
4126                }
4127                #[cfg(not(feature = "production"))]
4128                {
4129                    let second = stack[stack.len() - 2].clone();
4130                    stack.push(second);
4131                }
4132                Ok(true)
4133            } else {
4134                Ok(false)
4135            }
4136        }
4137
4138        // OP_PICK - copy nth stack item to top
4139        OP_PICK => {
4140            if let Some(n_bytes) = stack.pop() {
4141                // Use script_num_decode to properly handle CScriptNum encoding
4142                // (empty [] = 0, [0x00] = 0, [0x01] = 1, etc.)
4143                let n_val = script_num_decode(&n_bytes, 4)?;
4144                if n_val < 0 || n_val as usize >= stack.len() {
4145                    return Ok(false);
4146                }
4147                let n = n_val as usize;
4148                let len = stack.len();
4149                #[cfg(feature = "production")]
4150                {
4151                    // Use proven bounds after length check (n_val < stack.len() already checked)
4152                    unsafe {
4153                        let item = stack.get_unchecked(len - 1 - n);
4154                        stack.push(item.clone());
4155                    }
4156                }
4157                #[cfg(not(feature = "production"))]
4158                {
4159                    let item = stack[stack.len() - 1 - n].clone();
4160                    stack.push(item);
4161                }
4162                Ok(true)
4163            } else {
4164                Ok(false)
4165            }
4166        }
4167
4168        // OP_ROLL - move nth stack item to top
4169        OP_ROLL => {
4170            if let Some(n_bytes) = stack.pop() {
4171                // Use script_num_decode to properly handle CScriptNum encoding
4172                // (empty [] = 0, which is a valid no-op roll)
4173                let n_val = script_num_decode(&n_bytes, 4)?;
4174                if n_val < 0 || n_val as usize >= stack.len() {
4175                    return Ok(false);
4176                }
4177                let n = n_val as usize;
4178                let len = stack.len();
4179                #[cfg(feature = "production")]
4180                {
4181                    // Use proven bounds after length check (n_val < stack.len() already checked)
4182                    let idx = len - 1 - n;
4183                    let item = stack.remove(idx);
4184                    stack.push(item);
4185                }
4186                #[cfg(not(feature = "production"))]
4187                {
4188                    let item = stack.remove(stack.len() - 1 - n);
4189                    stack.push(item);
4190                }
4191                Ok(true)
4192            } else {
4193                Ok(false)
4194            }
4195        }
4196
4197        // OP_ROT - rotate top 3 stack items
4198        OP_ROT => {
4199            if stack.len() >= 3 {
4200                let top = stack.pop().unwrap();
4201                let second = stack.pop().unwrap();
4202                let third = stack.pop().unwrap();
4203                stack.push(second);
4204                stack.push(top);
4205                stack.push(third);
4206                Ok(true)
4207            } else {
4208                Ok(false)
4209            }
4210        }
4211
4212        // OP_SWAP - swap top 2 stack items
4213        OP_SWAP => {
4214            if stack.len() >= 2 {
4215                let top = stack.pop().unwrap();
4216                let second = stack.pop().unwrap();
4217                stack.push(top);
4218                stack.push(second);
4219                Ok(true)
4220            } else {
4221                Ok(false)
4222            }
4223        }
4224
4225        // OP_TUCK - copy top stack item to before second-to-top
4226        OP_TUCK => {
4227            if stack.len() >= 2 {
4228                let top = stack.pop().unwrap();
4229                let second = stack.pop().unwrap();
4230                stack.push(top.clone());
4231                stack.push(second);
4232                stack.push(top);
4233                Ok(true)
4234            } else {
4235                Ok(false)
4236            }
4237        }
4238
4239        // OP_2DROP - remove top 2 stack items
4240        OP_2DROP => {
4241            if stack.len() >= 2 {
4242                stack.pop();
4243                stack.pop();
4244                Ok(true)
4245            } else {
4246                Ok(false)
4247            }
4248        }
4249
4250        // OP_2DUP - duplicate top 2 stack items
4251        OP_2DUP => {
4252            if stack.len() >= 2 {
4253                let top = stack[stack.len() - 1].clone();
4254                let second = stack[stack.len() - 2].clone();
4255                stack.push(second);
4256                stack.push(top);
4257                Ok(true)
4258            } else {
4259                Ok(false)
4260            }
4261        }
4262
4263        // OP_3DUP - duplicate top 3 stack items
4264        OP_3DUP => {
4265            if stack.len() >= 3 {
4266                let top = stack[stack.len() - 1].clone();
4267                let second = stack[stack.len() - 2].clone();
4268                let third = stack[stack.len() - 3].clone();
4269                stack.push(third);
4270                stack.push(second);
4271                stack.push(top);
4272                Ok(true)
4273            } else {
4274                Ok(false)
4275            }
4276        }
4277
4278        // OP_2OVER - copy second pair of stack items to top
4279        OP_2OVER => {
4280            if stack.len() >= 4 {
4281                let fourth = stack[stack.len() - 4].clone();
4282                let third = stack[stack.len() - 3].clone();
4283                stack.push(fourth);
4284                stack.push(third);
4285                Ok(true)
4286            } else {
4287                Ok(false)
4288            }
4289        }
4290
4291        // OP_2ROT - rotate second pair of stack items to top
4292        OP_2ROT => {
4293            if stack.len() >= 6 {
4294                let sixth = stack.remove(stack.len() - 6);
4295                let fifth = stack.remove(stack.len() - 5);
4296                stack.push(fifth);
4297                stack.push(sixth);
4298                Ok(true)
4299            } else {
4300                Ok(false)
4301            }
4302        }
4303
4304        // OP_2SWAP - swap second pair of stack items
4305        OP_2SWAP => {
4306            if stack.len() >= 4 {
4307                let top = stack.pop().unwrap();
4308                let second = stack.pop().unwrap();
4309                let third = stack.pop().unwrap();
4310                let fourth = stack.pop().unwrap();
4311                stack.push(second);
4312                stack.push(top);
4313                stack.push(fourth);
4314                stack.push(third);
4315                Ok(true)
4316            } else {
4317                Ok(false)
4318            }
4319        }
4320
4321        // OP_SIZE - push size of top stack item
4322        // OP_SIZE - push the byte length of top stack item (does NOT pop)
4323        OP_SIZE => {
4324            if let Some(item) = stack.last() {
4325                let size = item.len() as i64;
4326                stack.push(to_stack_element(&script_num_encode(size)));
4327                Ok(true)
4328            } else {
4329                Ok(false)
4330            }
4331        }
4332
4333        // --- Arithmetic opcodes ---
4334        // All use CScriptNum encoding (max 4 bytes by default)
4335
4336        // OP_1ADD - increment top by 1
4337        OP_1ADD => {
4338            if let Some(item) = stack.pop() {
4339                let a = script_num_decode(&item, 4)?;
4340                stack.push(to_stack_element(&script_num_encode(a + 1)));
4341                Ok(true)
4342            } else {
4343                Ok(false)
4344            }
4345        }
4346        // OP_1SUB - decrement top by 1
4347        OP_1SUB => {
4348            if let Some(item) = stack.pop() {
4349                let a = script_num_decode(&item, 4)?;
4350                stack.push(to_stack_element(&script_num_encode(a - 1)));
4351                Ok(true)
4352            } else {
4353                Ok(false)
4354            }
4355        }
4356        // OP_2MUL - DISABLED
4357        OP_2MUL => Err(ConsensusError::ScriptErrorWithCode {
4358            code: ScriptErrorCode::DisabledOpcode,
4359            message: "OP_2MUL is disabled".into(),
4360        }),
4361        // OP_2DIV - DISABLED
4362        OP_2DIV => Err(ConsensusError::ScriptErrorWithCode {
4363            code: ScriptErrorCode::DisabledOpcode,
4364            message: "OP_2DIV is disabled".into(),
4365        }),
4366        // OP_NEGATE - negate top
4367        OP_NEGATE => {
4368            if let Some(item) = stack.pop() {
4369                let a = script_num_decode(&item, 4)?;
4370                stack.push(to_stack_element(&script_num_encode(-a)));
4371                Ok(true)
4372            } else {
4373                Ok(false)
4374            }
4375        }
4376        // OP_ABS - absolute value
4377        OP_ABS => {
4378            if let Some(item) = stack.pop() {
4379                let a = script_num_decode(&item, 4)?;
4380                stack.push(to_stack_element(&script_num_encode(a.abs())));
4381                Ok(true)
4382            } else {
4383                Ok(false)
4384            }
4385        }
4386        // OP_NOT - logical NOT: 0 → 1, nonzero → 0
4387        OP_NOT => {
4388            if let Some(item) = stack.pop() {
4389                let a = script_num_decode(&item, 4)?;
4390                stack.push(to_stack_element(&script_num_encode(if a == 0 {
4391                    1
4392                } else {
4393                    0
4394                })));
4395                Ok(true)
4396            } else {
4397                Ok(false)
4398            }
4399        }
4400        // OP_0NOTEQUAL - 0 → 0, nonzero → 1
4401        OP_0NOTEQUAL => {
4402            if let Some(item) = stack.pop() {
4403                let a = script_num_decode(&item, 4)?;
4404                stack.push(to_stack_element(&script_num_encode(if a != 0 {
4405                    1
4406                } else {
4407                    0
4408                })));
4409                Ok(true)
4410            } else {
4411                Ok(false)
4412            }
4413        }
4414        OP_ADD => arithmetic::op_add(stack),
4415        OP_SUB => arithmetic::op_sub(stack),
4416        OP_MUL => arithmetic::op_mul_disabled(),
4417        OP_DIV => arithmetic::op_div_disabled(),
4418        OP_MOD => arithmetic::op_mod_disabled(),
4419        OP_LSHIFT => arithmetic::op_lshift_disabled(),
4420        OP_RSHIFT => arithmetic::op_rshift_disabled(),
4421        OP_BOOLAND => arithmetic::op_booland(stack),
4422        OP_BOOLOR => arithmetic::op_boolor(stack),
4423        OP_NUMEQUAL => arithmetic::op_numequal(stack),
4424        OP_NUMEQUALVERIFY => arithmetic::op_numequalverify(stack),
4425        OP_NUMNOTEQUAL => arithmetic::op_numnotequal(stack),
4426        OP_LESSTHAN => arithmetic::op_lessthan(stack),
4427        OP_GREATERTHAN => arithmetic::op_greaterthan(stack),
4428        OP_LESSTHANOREQUAL => arithmetic::op_lessthanorequal(stack),
4429        OP_GREATERTHANOREQUAL => arithmetic::op_greaterthanorequal(stack),
4430        OP_MIN => arithmetic::op_min(stack),
4431        OP_MAX => arithmetic::op_max(stack),
4432        OP_WITHIN => arithmetic::op_within(stack),
4433
4434        // OP_CODESEPARATOR - marks position for sighash (no-op in execute_opcode)
4435        OP_CODESEPARATOR => Ok(true),
4436
4437        // OP_NOP1 and OP_NOP5-OP_NOP10 - no-ops
4438        // Note: OP_NOP4 (0xb3) is used for OP_CHECKTEMPLATEVERIFY (BIP119)
4439        OP_NOP1 | OP_NOP5..=OP_NOP10 => Ok(true),
4440
4441        // OP_CHECKTEMPLATEVERIFY - requires transaction context
4442        OP_CHECKTEMPLATEVERIFY => {
4443            #[cfg(not(feature = "ctv"))]
4444            {
4445                // Without feature flag, treat as NOP4
4446                Ok(true)
4447            }
4448
4449            #[cfg(feature = "ctv")]
4450            {
4451                // CTV requires transaction context - cannot execute without it
4452                return Err(ConsensusError::ScriptErrorWithCode {
4453                    code: ScriptErrorCode::TxInvalid,
4454                    message: "OP_CHECKTEMPLATEVERIFY requires transaction context".into(),
4455                });
4456            }
4457        }
4458
4459        // Disabled string opcodes - must return error per consensus
4460        OP_DISABLED_STRING_RANGE_START..=OP_DISABLED_STRING_RANGE_END
4461        | OP_DISABLED_BITWISE_RANGE_START..=OP_DISABLED_BITWISE_RANGE_END => {
4462            Err(ConsensusError::ScriptErrorWithCode {
4463                code: ScriptErrorCode::DisabledOpcode,
4464                message: format!("Disabled opcode 0x{opcode:02x}").into(),
4465            })
4466        }
4467
4468        // Unknown opcode
4469        _ => Ok(false),
4470    }
4471}
4472
4473/// Execute a single opcode with transaction context for signature verification
4474#[allow(dead_code)]
4475fn execute_opcode_with_context(
4476    opcode: u8,
4477    stack: &mut Vec<StackElement>,
4478    flags: u32,
4479    tx: &Transaction,
4480    input_index: usize,
4481    prevouts: &[TransactionOutput],
4482    network: crate::types::Network,
4483) -> Result<bool> {
4484    // Convert prevouts to parallel slices for the optimized API
4485    let prevout_values: Vec<i64> = prevouts.iter().map(|p| p.value).collect();
4486    let prevout_script_pubkeys: Vec<&[u8]> =
4487        prevouts.iter().map(|p| p.script_pubkey.as_ref()).collect();
4488    let ctx = context::ScriptContext {
4489        tx,
4490        input_index,
4491        prevout_values: &prevout_values,
4492        prevout_script_pubkeys: &prevout_script_pubkeys,
4493        block_height: None,
4494        median_time_past: None,
4495        network,
4496        sigversion: SigVersion::Base,
4497        redeem_script_for_sighash: None,
4498        script_sig_for_sighash: None,
4499        tapscript_for_sighash: None,
4500        tapscript_codesep_pos: None,
4501        #[cfg(feature = "production")]
4502        schnorr_collector: None,
4503        #[cfg(feature = "production")]
4504        precomputed_bip143: None,
4505        #[cfg(feature = "production")]
4506        sighash_cache: None,
4507    };
4508    execute_opcode_with_context_full(opcode, stack, flags, &ctx, None)
4509}
4510
4511/// Parse P2SH-P2PKH scriptSig for batch sighash precompute. Zero-allocation.
4512/// script_sig = [sig, pubkey, redeem] where redeem is P2PKH (25 bytes).
4513/// Returns (sighash_byte, redeem_slice) or None. Pub(crate) for block.rs.
4514#[cfg(feature = "production")]
4515#[inline(always)]
4516pub(crate) fn parse_p2sh_p2pkh_for_precompute(script_sig: &[u8]) -> Option<(u8, &[u8])> {
4517    let mut i = 0;
4518    let (adv1, s_start, s_end) = parse_one_data_push(script_sig, i)?;
4519    i += adv1;
4520    if i >= script_sig.len() {
4521        return None;
4522    }
4523    let (adv2, _p_start, _p_end) = parse_one_data_push(script_sig, i)?;
4524    i += adv2;
4525    if i >= script_sig.len() {
4526        return None;
4527    }
4528    let (adv3, r_start, r_end) = parse_one_data_push(script_sig, i)?;
4529    i += adv3;
4530    if i != script_sig.len() {
4531        return None;
4532    }
4533    let sig = &script_sig[s_start..s_end];
4534    let redeem = &script_sig[r_start..r_end];
4535    if sig.is_empty() || redeem.len() != 25 {
4536        return None;
4537    }
4538    if redeem[0] != OP_DUP
4539        || redeem[1] != OP_HASH160
4540        || redeem[2] != PUSH_20_BYTES
4541        || redeem[23] != OP_EQUALVERIFY
4542        || redeem[24] != OP_CHECKSIG
4543    {
4544        return None;
4545    }
4546    Some((sig[sig.len() - 1], redeem))
4547}
4548
4549/// Zero-allocation parser for P2PKH scriptSig: exactly two data pushes (`signature`, then `pubkey`).
4550/// Returns (sig_slice, pubkey_slice) borrowing into script_sig, or None if invalid.
4551/// Pub(crate) for batch sighash precompute in block.rs.
4552#[inline(always)]
4553pub(crate) fn parse_p2pkh_script_sig(script_sig: &[u8]) -> Option<(&[u8], &[u8])> {
4554    let mut i = 0;
4555    let (adv1, s_start, s_end) = parse_one_data_push(script_sig, i)?;
4556    i += adv1;
4557    if i >= script_sig.len() {
4558        return None;
4559    }
4560    let (adv2, p_start, p_end) = parse_one_data_push(script_sig, i)?;
4561    i += adv2;
4562    if i != script_sig.len() {
4563        return None;
4564    }
4565    Some((&script_sig[s_start..s_end], &script_sig[p_start..p_end]))
4566}
4567
4568/// Parse P2PK scriptSig as single push (signature). Returns sig slice or None.
4569/// Used for P2PK pre-extraction in producer.
4570pub(crate) fn parse_p2pk_script_sig(script_sig: &[u8]) -> Option<&[u8]> {
4571    let (advance, data_start, data_end) = parse_one_data_push(script_sig, 0)?;
4572    if advance != script_sig.len() {
4573        return None;
4574    }
4575    Some(&script_sig[data_start..data_end])
4576}
4577
4578/// Parse a single push opcode at `i`, return (advance, data_start, data_end). Rejects OP_0 and numerics.
4579fn parse_one_data_push(script: &[u8], i: usize) -> Option<(usize, usize, usize)> {
4580    if i >= script.len() {
4581        return None;
4582    }
4583    let opcode = script[i];
4584    let (advance, data_start, data_end) = if opcode == OP_0 {
4585        return None;
4586    } else if opcode <= 0x4b {
4587        let len = opcode as usize;
4588        if i + 1 + len > script.len() {
4589            return None;
4590        }
4591        (1 + len, i + 1, i + 1 + len)
4592    } else if opcode == OP_PUSHDATA1 {
4593        if i + 1 >= script.len() {
4594            return None;
4595        }
4596        let len = script[i + 1] as usize;
4597        if i + 2 + len > script.len() {
4598            return None;
4599        }
4600        (2 + len, i + 2, i + 2 + len)
4601    } else if opcode == OP_PUSHDATA2 {
4602        if i + 2 >= script.len() {
4603            return None;
4604        }
4605        let len = u16::from_le_bytes([script[i + 1], script[i + 2]]) as usize;
4606        if i + 3 + len > script.len() {
4607            return None;
4608        }
4609        (3 + len, i + 3, i + 3 + len)
4610    } else if opcode == OP_PUSHDATA4 {
4611        if i + 4 >= script.len() {
4612            return None;
4613        }
4614        let len = u32::from_le_bytes([script[i + 1], script[i + 2], script[i + 3], script[i + 4]])
4615            as usize;
4616        if i + 5 + len > script.len() {
4617            return None;
4618        }
4619        (5 + len, i + 5, i + 5 + len)
4620    } else {
4621        return None;
4622    };
4623    Some((advance, data_start, data_end))
4624}
4625
4626/// P2SH Push-Only Validation (Orange Paper 5.2.1).
4627/// Returns true if script_sig contains only push opcodes (valid), false otherwise (invalid).
4628#[spec_locked("5.2.1", "P2SHPushOnlyCheck")]
4629pub fn p2sh_push_only_check(script_sig: &[u8]) -> bool {
4630    parse_script_sig_push_only(script_sig).is_some()
4631}
4632
4633/// Parse script_sig as push-only and return pushed items in order.
4634/// Returns None if script contains non-push opcodes or invalid push encoding.
4635/// Used by P2PKH fast-path to get [signature, pubkey] without running the interpreter.
4636fn parse_script_sig_push_only(script_sig: &[u8]) -> Option<Vec<StackElement>> {
4637    let mut out = Vec::new();
4638    let mut i = 0;
4639    while i < script_sig.len() {
4640        let opcode = script_sig[i];
4641        if !is_push_opcode(opcode) {
4642            return None;
4643        }
4644        let (advance, data) = if opcode == OP_0 {
4645            (1, vec![])
4646        } else if opcode <= 0x4b {
4647            let len = opcode as usize;
4648            if i + 1 + len > script_sig.len() {
4649                return None;
4650            }
4651            (1 + len, script_sig[i + 1..i + 1 + len].to_vec())
4652        } else if opcode == OP_PUSHDATA1 {
4653            if i + 1 >= script_sig.len() {
4654                return None;
4655            }
4656            let len = script_sig[i + 1] as usize;
4657            if i + 2 + len > script_sig.len() {
4658                return None;
4659            }
4660            (2 + len, script_sig[i + 2..i + 2 + len].to_vec())
4661        } else if opcode == OP_PUSHDATA2 {
4662            if i + 2 >= script_sig.len() {
4663                return None;
4664            }
4665            let len = u16::from_le_bytes([script_sig[i + 1], script_sig[i + 2]]) as usize;
4666            if i + 3 + len > script_sig.len() {
4667                return None;
4668            }
4669            (3 + len, script_sig[i + 3..i + 3 + len].to_vec())
4670        } else if opcode == OP_PUSHDATA4 {
4671            if i + 4 >= script_sig.len() {
4672                return None;
4673            }
4674            let len = u32::from_le_bytes([
4675                script_sig[i + 1],
4676                script_sig[i + 2],
4677                script_sig[i + 3],
4678                script_sig[i + 4],
4679            ]) as usize;
4680            if i + 5 + len > script_sig.len() {
4681                return None;
4682            }
4683            (5 + len, script_sig[i + 5..i + 5 + len].to_vec())
4684        } else if (OP_1NEGATE..=OP_16).contains(&opcode) {
4685            // Single-byte push: push the numeric value as minimal bytes
4686            let n = script_num_from_opcode(opcode);
4687            (1, script_num_encode(n))
4688        } else {
4689            return None;
4690        };
4691        out.push(to_stack_element(&data));
4692        i += advance;
4693    }
4694    Some(out)
4695}
4696
4697/// Parse all pushes from P2SH scriptSig (including OP_0/dummy).
4698/// Returns pushed data in order; last push = redeem script.
4699/// Uses parse_script_sig_push_only; caller uses .as_ref() for &[u8].
4700fn parse_p2sh_script_sig_pushes(script_sig: &[u8]) -> Option<Vec<StackElement>> {
4701    parse_script_sig_push_only(script_sig)
4702}
4703
4704/// Parse redeem script as `OP_n <pubkeys> OP_m OP_CHECKMULTISIG`.
4705/// Format: first byte OP_1..OP_16 = n, then n pubkeys (33 or 65 bytes each),
4706/// then OP_1..OP_16 = m, then 0xae (OP_CHECKMULTISIG).
4707/// Returns (m, n, pubkey_slices) or None if format doesn't match.
4708fn parse_redeem_multisig(redeem: &[u8]) -> Option<(u8, u8, Vec<&[u8]>)> {
4709    if redeem.len() < 4 {
4710        return None;
4711    }
4712    let n_op = redeem[0];
4713    if !(OP_1..=OP_16).contains(&n_op) {
4714        return None;
4715    }
4716    let n = (n_op - OP_1 + 1) as usize;
4717    let mut i = 1;
4718    let mut pubkeys = Vec::with_capacity(n);
4719    for _ in 0..n {
4720        if i >= redeem.len() {
4721            return None;
4722        }
4723        let first = redeem[i];
4724        let pk_len = if first == 0x02 || first == 0x03 {
4725            33
4726        } else if first == 0x04 {
4727            65
4728        } else {
4729            return None;
4730        };
4731        if i + pk_len > redeem.len() {
4732            return None;
4733        }
4734        pubkeys.push(&redeem[i..i + pk_len]);
4735        i += pk_len;
4736    }
4737    if i + 2 > redeem.len() {
4738        return None;
4739    }
4740    let m_op = redeem[i];
4741    if !(OP_1..=OP_16).contains(&m_op) {
4742        return None;
4743    }
4744    let m = m_op - OP_1 + 1;
4745    if redeem[i + 1] != OP_CHECKMULTISIG {
4746        return None;
4747    }
4748    Some((m, n as u8, pubkeys))
4749}
4750
4751/// Map OP_1NEGATE..OP_16 to numeric value for script_num_to_bytes
4752fn script_num_from_opcode(opcode: u8) -> i64 {
4753    match opcode {
4754        OP_1NEGATE => -1,
4755        OP_1 => 1,
4756        OP_2 => 2,
4757        OP_3 => 3,
4758        OP_4 => 4,
4759        OP_5 => 5,
4760        OP_6 => 6,
4761        OP_7 => 7,
4762        OP_8 => 8,
4763        OP_9 => 9,
4764        OP_10 => 10,
4765        OP_11 => 11,
4766        OP_12 => 12,
4767        OP_13 => 13,
4768        OP_14 => 14,
4769        OP_15 => 15,
4770        OP_16 => 16,
4771        _ => 0,
4772    }
4773}
4774
4775/// Serialize data as a Bitcoin push operation: `push_opcode` followed by `data` bytes.
4776/// This creates the byte pattern that FindAndDelete searches for.
4777/// Push data to script (BIP62 encoding rules).
4778pub(crate) fn serialize_push_data(data: &[u8]) -> Vec<u8> {
4779    let len = data.len();
4780    let mut result = Vec::with_capacity(len + 5);
4781    if len < 76 {
4782        result.push(len as u8);
4783    } else if len < 256 {
4784        result.push(OP_PUSHDATA1);
4785        result.push(len as u8);
4786    } else if len < 65536 {
4787        result.push(OP_PUSHDATA2);
4788        result.push((len & 0xff) as u8);
4789        result.push(((len >> 8) & 0xff) as u8);
4790    } else {
4791        result.push(OP_PUSHDATA4);
4792        result.push((len & 0xff) as u8);
4793        result.push(((len >> 8) & 0xff) as u8);
4794        result.push(((len >> 16) & 0xff) as u8);
4795        result.push(((len >> 24) & 0xff) as u8);
4796    }
4797    result.extend_from_slice(data);
4798    result
4799}
4800
4801/// Advance `pc` past one opcode (Bitcoin `CScript::GetOp`-compatible sizing).
4802#[inline]
4803fn script_get_op_advance(script: &[u8], pc: usize) -> Option<usize> {
4804    if pc >= script.len() {
4805        return None;
4806    }
4807    let opcode = script[pc];
4808    let advance = if opcode <= 0x4b {
4809        1 + opcode as usize
4810    } else if opcode == OP_PUSHDATA1 && pc + 1 < script.len() {
4811        2 + script[pc + 1] as usize
4812    } else if opcode == OP_PUSHDATA2 && pc + 2 < script.len() {
4813        3 + ((script[pc + 1] as usize) | ((script[pc + 2] as usize) << 8))
4814    } else if opcode == OP_PUSHDATA4 && pc + 4 < script.len() {
4815        5 + ((script[pc + 1] as usize)
4816            | ((script[pc + 2] as usize) << 8)
4817            | ((script[pc + 3] as usize) << 16)
4818            | ((script[pc + 4] as usize) << 24))
4819    } else {
4820        1
4821    };
4822    let next = pc + advance;
4823    if next > script.len() {
4824        None
4825    } else {
4826        Some(next)
4827    }
4828}
4829
4830/// FindAndDelete — consensus match to Bitcoin Core `FindAndDelete` in `interpreter.cpp`.
4831///
4832/// Do/while loop: flush `[pc2, pc)`, delete consecutive raw occurrences of `pattern` from `pc`,
4833/// then advance `pc` one opcode via GetOp-style sizing. The inner delete loop can leave `pc`
4834/// misaligned; the next "opcode" is parsed from that byte (same as Core).
4835#[spec_locked("5.1.1", "FindAndDelete")]
4836#[inline]
4837pub(crate) fn find_and_delete<'a>(script: &'a [u8], pattern: &[u8]) -> std::borrow::Cow<'a, [u8]> {
4838    if pattern.is_empty() {
4839        return std::borrow::Cow::Borrowed(script);
4840    }
4841    if pattern.len() > script.len() {
4842        return std::borrow::Cow::Borrowed(script);
4843    }
4844    // Fast pre-scan: if pattern doesn't appear anywhere in the raw bytes, it can't appear
4845    // at any opcode boundary either — skip the full walk and the Vec allocation entirely.
4846    // `windows()` is O(N) and avoids ~100B-4KB Vec allocation + full script copy on the
4847    // overwhelmingly common case (CHECKSIG before codeseparator = never matches).
4848    if !script.windows(pattern.len()).any(|w| w == pattern) {
4849        return std::borrow::Cow::Borrowed(script);
4850    }
4851    let end = script.len();
4852    let mut n_found = 0usize;
4853    let mut result = Vec::new();
4854    let mut pc = 0usize;
4855    let mut pc2 = 0usize;
4856
4857    loop {
4858        result.extend_from_slice(&script[pc2..pc]);
4859        while end - pc >= pattern.len() && script[pc..pc + pattern.len()] == *pattern {
4860            pc += pattern.len();
4861            n_found += 1;
4862        }
4863        pc2 = pc;
4864        if pc >= end {
4865            break;
4866        }
4867        let Some(next_pc) = script_get_op_advance(script, pc) else {
4868            break;
4869        };
4870        pc = next_pc;
4871    }
4872
4873    if n_found > 0 {
4874        result.extend_from_slice(&script[pc2..end]);
4875        std::borrow::Cow::Owned(result)
4876    } else {
4877        std::borrow::Cow::Borrowed(script)
4878    }
4879}
4880
4881/// Return opcode position (0-indexed) of the opcode at byte_index in script. BIP 342 codesep_pos.
4882fn opcode_position_at_byte(script: &[u8], byte_index: usize) -> u32 {
4883    let mut pos = 0u32;
4884    let mut i = 0usize;
4885    while i < script.len() && i <= byte_index {
4886        let opcode = script[i];
4887        let advance = if opcode <= 0x4b {
4888            1 + opcode as usize
4889        } else if opcode == OP_PUSHDATA1 && i + 1 < script.len() {
4890            2 + script[i + 1] as usize
4891        } else if opcode == OP_PUSHDATA2 && i + 2 < script.len() {
4892            3 + ((script[i + 1] as usize) | ((script[i + 2] as usize) << 8))
4893        } else if opcode == OP_PUSHDATA4 && i + 4 < script.len() {
4894            5 + ((script[i + 1] as usize)
4895                | ((script[i + 2] as usize) << 8)
4896                | ((script[i + 3] as usize) << 16)
4897                | ((script[i + 4] as usize) << 24))
4898        } else {
4899            1
4900        };
4901        if i == byte_index {
4902            return pos;
4903        }
4904        pos += 1;
4905        i = std::cmp::min(i + advance, script.len());
4906    }
4907    0xffff_ffff
4908}
4909
4910/// Execute a single opcode with full context including block height, median time-past, and network
4911#[cfg_attr(feature = "production", inline(always))]
4912fn execute_opcode_with_context_full(
4913    opcode: u8,
4914    stack: &mut Vec<StackElement>,
4915    flags: u32,
4916    ctx: &context::ScriptContext<'_>,
4917    effective_script_code: Option<&[u8]>,
4918) -> Result<bool> {
4919    let tx = ctx.tx;
4920    let input_index = ctx.input_index;
4921    let prevout_values = ctx.prevout_values;
4922    let prevout_script_pubkeys = ctx.prevout_script_pubkeys;
4923    let block_height = ctx.block_height;
4924    let median_time_past = ctx.median_time_past;
4925    let network = ctx.network;
4926    let sigversion = ctx.sigversion;
4927    let script_sig_for_sighash = ctx.script_sig_for_sighash;
4928    let tapscript_for_sighash = ctx.tapscript_for_sighash;
4929    let tapscript_codesep_pos = ctx.tapscript_codesep_pos;
4930    let redeem_script_for_sighash = effective_script_code;
4931    #[cfg(feature = "production")]
4932    let schnorr_collector = ctx.schnorr_collector;
4933    #[cfg(feature = "production")]
4934    let precomputed_bip143 = ctx.precomputed_bip143;
4935    #[cfg(feature = "production")]
4936    let sighash_cache = ctx.sighash_cache;
4937
4938    // match ordered by frequency (hot opcodes first for better branch prediction)
4939    match opcode {
4940        // OP_CHECKSIG - verify ECDSA signature
4941        OP_CHECKSIG => {
4942            if stack.len() >= 2 {
4943                let pubkey_bytes = stack.pop().unwrap();
4944                let signature_bytes = stack.pop().unwrap();
4945
4946                // Empty signature always fails but is valid script execution
4947                if signature_bytes.is_empty() {
4948                    stack.push(to_stack_element(&[0]));
4949                    return Ok(true);
4950                }
4951
4952                // Tapscript (BIP 342): Uses BIP 340 Schnorr signatures (64 bytes, not DER)
4953                // and 32-byte x-only pubkeys. Signature format is just 64 bytes (no sighash byte).
4954                if sigversion == SigVersion::Tapscript {
4955                    // Tapscript: signature is 64-byte BIP 340 Schnorr, pubkey is 32-byte x-only
4956                    if signature_bytes.len() == 64 && pubkey_bytes.len() == 32 {
4957                        let sighash_byte = 0x00;
4958                        let (tapscript, codesep_pos) = tapscript_for_sighash
4959                            .map(|s| (s, tapscript_codesep_pos.unwrap_or(0xffff_ffff)))
4960                            .unwrap_or((&[] as &[u8], 0xffff_ffff));
4961                        let sighash = if tapscript.is_empty() {
4962                            crate::taproot::compute_taproot_signature_hash(
4963                                tx,
4964                                input_index,
4965                                prevout_values,
4966                                prevout_script_pubkeys,
4967                                sighash_byte,
4968                            )?
4969                        } else {
4970                            crate::taproot::compute_tapscript_signature_hash(
4971                                tx,
4972                                input_index,
4973                                prevout_values,
4974                                prevout_script_pubkeys,
4975                                tapscript,
4976                                crate::taproot::TAPROOT_LEAF_VERSION_TAPSCRIPT,
4977                                codesep_pos,
4978                                sighash_byte,
4979                            )?
4980                        };
4981
4982                        // OPTIMIZATION: Use collector for batch verification if available
4983                        #[cfg(feature = "production")]
4984                        let is_valid = {
4985                            use crate::bip348::verify_tapscript_schnorr_signature;
4986                            verify_tapscript_schnorr_signature(
4987                                &sighash,
4988                                &pubkey_bytes,
4989                                &signature_bytes,
4990                                schnorr_collector,
4991                            )
4992                            .unwrap_or(false)
4993                        };
4994
4995                        #[cfg(not(feature = "production"))]
4996                        let is_valid = {
4997                            #[cfg(feature = "csfs")]
4998                            let x = {
4999                                use crate::bip348::verify_tapscript_schnorr_signature;
5000                                verify_tapscript_schnorr_signature(
5001                                    &sighash,
5002                                    &pubkey_bytes,
5003                                    &signature_bytes,
5004                                    None,
5005                                )
5006                                .unwrap_or(false)
5007                            };
5008                            #[cfg(not(feature = "csfs"))]
5009                            let x = false;
5010                            x
5011                        };
5012
5013                        stack.push(to_stack_element(&[if is_valid { 1 } else { 0 }]));
5014                        return Ok(true);
5015                    }
5016                    // Fall through to ECDSA path for non-Tapscript signatures
5017                }
5018
5019                // Extract sighash type from last byte of signature
5020                // Bitcoin signature format: <DER signature><sighash_type>
5021                // OPTIMIZATION: Cache length to avoid repeated computation
5022                let sig_len = signature_bytes.len();
5023                let sighash_byte = signature_bytes[sig_len - 1];
5024                let _der_sig = &signature_bytes[..sig_len - 1];
5025
5026                // Calculate sighash - use BIP143 for SegWit, legacy for others
5027                // BIP143 OPTIMIZATION: For SegWit, hashPrevouts/hashSequence/hashOutputs
5028                // are computed once per transaction, not once per input.
5029                let sighash = if sigversion == SigVersion::WitnessV0 {
5030                    // BIP143 sighash for SegWit v0 (P2WPKH, P2WSH)
5031                    let amount = prevout_values.get(input_index).copied().unwrap_or(0);
5032
5033                    // scriptCode for BIP143: the witnessScript or P2PKH equivalent
5034                    let script_code = redeem_script_for_sighash.unwrap_or_else(|| {
5035                        prevout_script_pubkeys
5036                            .get(input_index)
5037                            .copied()
5038                            .unwrap_or(&[])
5039                    });
5040
5041                    crate::transaction_hash::calculate_bip143_sighash(
5042                        tx,
5043                        input_index,
5044                        script_code,
5045                        amount,
5046                        sighash_byte,
5047                        precomputed_bip143,
5048                    )?
5049                } else {
5050                    // Legacy sighash for non-SegWit transactions
5051                    use crate::transaction_hash::{
5052                        calculate_transaction_sighash_single_input, SighashType,
5053                    };
5054                    let sighash_type = SighashType::from_byte(sighash_byte);
5055
5056                    // FindAndDelete: remove the *current* signature's push encoding from scriptCode.
5057                    // Must not use only the first scriptSig push — P2SH inputs may contain multiple
5058                    // signatures before the redeem script, each checked with its own sighash.
5059                    let pattern = serialize_push_data(signature_bytes.as_ref());
5060
5061                    let base_script = match (
5062                        redeem_script_for_sighash,
5063                        prevout_script_pubkeys.get(input_index),
5064                    ) {
5065                        (Some(redeem), Some(prevout)) if redeem == *prevout => *prevout,
5066                        (Some(redeem), _) => redeem,
5067                        (None, Some(prevout)) => *prevout,
5068                        (None, None) => &[],
5069                    };
5070                    let cleaned = find_and_delete(base_script, &pattern);
5071
5072                    calculate_transaction_sighash_single_input(
5073                        tx,
5074                        input_index,
5075                        cleaned.as_ref(),
5076                        prevout_values[input_index],
5077                        sighash_type,
5078                        #[cfg(feature = "production")]
5079                        sighash_cache,
5080                    )?
5081                };
5082
5083                // Verify signature with real transaction hash
5084                // CRITICAL FIX: Pass full signature (with sighash byte) to verify_signature
5085                // IsValidSignatureEncoding expects signature WITH sighash byte
5086                let height = block_height.unwrap_or(0);
5087                #[cfg(feature = "production")]
5088                let is_valid = signature::with_secp_context(|secp| {
5089                    signature::verify_signature(
5090                        secp,
5091                        &pubkey_bytes,
5092                        &signature_bytes, // Pass full signature WITH sighash byte
5093                        &sighash,
5094                        flags,
5095                        height,
5096                        network,
5097                        sigversion,
5098                    )
5099                })?;
5100
5101                #[cfg(not(feature = "production"))]
5102                let is_valid = {
5103                    let secp = signature::new_secp();
5104                    signature::verify_signature(
5105                        &secp,
5106                        &pubkey_bytes,
5107                        &signature_bytes, // Pass full signature WITH sighash byte
5108                        &sighash,
5109                        flags,
5110                        height,
5111                        network,
5112                        sigversion,
5113                    )?
5114                };
5115
5116                stack.push(to_stack_element(&[if is_valid { 1 } else { 0 }]));
5117                Ok(true)
5118            } else {
5119                Ok(false)
5120            }
5121        }
5122
5123        // OP_CHECKSIGVERIFY - verify signature (Schnorr in Tapscript, ECDSA otherwise)
5124        OP_CHECKSIGVERIFY => {
5125            if stack.len() >= 2 {
5126                let pubkey_bytes = stack.pop().unwrap();
5127                let signature_bytes = stack.pop().unwrap();
5128
5129                // Empty signature always fails
5130                if signature_bytes.is_empty() {
5131                    return Ok(false);
5132                }
5133
5134                // BIP342 Tapscript: Schnorr path (same as OP_CHECKSIG Tapscript branch).
5135                if sigversion == SigVersion::Tapscript {
5136                    if signature_bytes.len() == 64 && pubkey_bytes.len() == 32 {
5137                        let sighash_byte = 0x00u8;
5138                        let (tapscript, codesep_pos) = tapscript_for_sighash
5139                            .map(|s| (s, tapscript_codesep_pos.unwrap_or(0xffff_ffff)))
5140                            .unwrap_or((&[] as &[u8], 0xffff_ffff));
5141                        let sighash = if tapscript.is_empty() {
5142                            crate::taproot::compute_taproot_signature_hash(
5143                                tx,
5144                                input_index,
5145                                prevout_values,
5146                                prevout_script_pubkeys,
5147                                sighash_byte,
5148                            )?
5149                        } else {
5150                            crate::taproot::compute_tapscript_signature_hash(
5151                                tx,
5152                                input_index,
5153                                prevout_values,
5154                                prevout_script_pubkeys,
5155                                tapscript,
5156                                crate::taproot::TAPROOT_LEAF_VERSION_TAPSCRIPT,
5157                                codesep_pos,
5158                                sighash_byte,
5159                            )?
5160                        };
5161                        #[cfg(feature = "production")]
5162                        let is_valid = {
5163                            use crate::bip348::verify_tapscript_schnorr_signature;
5164                            verify_tapscript_schnorr_signature(
5165                                &sighash,
5166                                &pubkey_bytes,
5167                                &signature_bytes,
5168                                schnorr_collector,
5169                            )
5170                            .unwrap_or(false)
5171                        };
5172                        #[cfg(not(feature = "production"))]
5173                        let is_valid = {
5174                            #[cfg(feature = "csfs")]
5175                            let x = {
5176                                use crate::bip348::verify_tapscript_schnorr_signature;
5177                                verify_tapscript_schnorr_signature(
5178                                    &sighash,
5179                                    &pubkey_bytes,
5180                                    &signature_bytes,
5181                                    None,
5182                                )
5183                                .unwrap_or(false)
5184                            };
5185                            #[cfg(not(feature = "csfs"))]
5186                            let x = false;
5187                            x
5188                        };
5189                        if !is_valid {
5190                            return Ok(false); // OP_CHECKSIGVERIFY: fail script on invalid sig
5191                        }
5192                        return Ok(true);
5193                    }
5194                    // Non-standard pubkey size in Tapscript: fail per BIP342
5195                    return Ok(false);
5196                }
5197
5198                // Legacy / SegWit v0: ECDSA path
5199                // Extract sighash type from last byte of signature
5200                let sig_len = signature_bytes.len();
5201                let sighash_byte = signature_bytes[sig_len - 1];
5202                let _der_sig = &signature_bytes[..sig_len - 1];
5203
5204                // Calculate sighash - use BIP143 for SegWit, legacy for others
5205                // BIP143 OPTIMIZATION: For SegWit, hashPrevouts/hashSequence/hashOutputs
5206                // are computed once per transaction, not once per input.
5207                let sighash = if sigversion == SigVersion::WitnessV0 {
5208                    // BIP143 sighash for SegWit v0 (P2WPKH, P2WSH)
5209                    let amount = prevout_values.get(input_index).copied().unwrap_or(0);
5210
5211                    let script_code = redeem_script_for_sighash.unwrap_or_else(|| {
5212                        prevout_script_pubkeys
5213                            .get(input_index)
5214                            .copied()
5215                            .unwrap_or(&[])
5216                    });
5217
5218                    crate::transaction_hash::calculate_bip143_sighash(
5219                        tx,
5220                        input_index,
5221                        script_code,
5222                        amount,
5223                        sighash_byte,
5224                        precomputed_bip143,
5225                    )?
5226                } else {
5227                    // Legacy sighash for non-SegWit transactions
5228                    use crate::transaction_hash::{
5229                        calculate_transaction_sighash_single_input, SighashType,
5230                    };
5231                    let sighash_type = SighashType::from_byte(sighash_byte);
5232
5233                    // FindAndDelete: remove the signature under verification (not first scriptSig push).
5234                    let pattern = serialize_push_data(signature_bytes.as_ref());
5235
5236                    let base_script = match (
5237                        redeem_script_for_sighash,
5238                        prevout_script_pubkeys.get(input_index),
5239                    ) {
5240                        (Some(redeem), Some(prevout)) if redeem == *prevout => *prevout,
5241                        (Some(redeem), _) => redeem,
5242                        (None, Some(prevout)) => *prevout,
5243                        (None, None) => &[],
5244                    };
5245                    let cleaned = find_and_delete(base_script, &pattern);
5246
5247                    calculate_transaction_sighash_single_input(
5248                        tx,
5249                        input_index,
5250                        cleaned.as_ref(),
5251                        prevout_values[input_index],
5252                        sighash_type,
5253                        #[cfg(feature = "production")]
5254                        sighash_cache,
5255                    )?
5256                };
5257
5258                // Verify signature with real transaction hash
5259                // CRITICAL FIX: Pass full signature (with sighash byte) to verify_signature
5260                // IsValidSignatureEncoding expects signature WITH sighash byte
5261                let height = block_height.unwrap_or(0);
5262                #[cfg(feature = "production")]
5263                let is_valid = signature::with_secp_context(|secp| {
5264                    signature::verify_signature(
5265                        secp,
5266                        &pubkey_bytes,
5267                        &signature_bytes, // Pass full signature WITH sighash byte
5268                        &sighash,
5269                        flags,
5270                        height,
5271                        network,
5272                        sigversion,
5273                    )
5274                })?;
5275
5276                #[cfg(not(feature = "production"))]
5277                let is_valid = {
5278                    let secp = signature::new_secp();
5279                    signature::verify_signature(
5280                        &secp,
5281                        &pubkey_bytes,
5282                        &signature_bytes, // Pass full signature WITH sighash byte
5283                        &sighash,
5284                        flags,
5285                        height,
5286                        network,
5287                        sigversion,
5288                    )?
5289                };
5290
5291                if is_valid {
5292                    Ok(true)
5293                } else {
5294                    Ok(false)
5295                }
5296            } else {
5297                Ok(false)
5298            }
5299        }
5300
5301        // OP_CHECKSIGADD (BIP 342) - Tapscript only. Pops pubkey, n, sig. Verifies Schnorr; if valid push n+1 else fail.
5302        OP_CHECKSIGADD => {
5303            if sigversion != SigVersion::Tapscript {
5304                return Err(ConsensusError::ScriptErrorWithCode {
5305                    code: ScriptErrorCode::DisabledOpcode,
5306                    message: "OP_CHECKSIGADD is only available in Tapscript".into(),
5307                });
5308            }
5309            if stack.len() < 3 {
5310                return Err(ConsensusError::ScriptErrorWithCode {
5311                    code: ScriptErrorCode::InvalidStackOperation,
5312                    message: "OP_CHECKSIGADD: insufficient stack items (need 3)".into(),
5313                });
5314            }
5315            // BIP 342: pubkey (top), n (second), sig (third)
5316            let pubkey_bytes = stack.pop().unwrap();
5317            let n_bytes = stack.pop().unwrap();
5318            let signature_bytes = stack.pop().unwrap();
5319            let n = script_num_decode(&n_bytes, 4)?;
5320
5321            // Empty signature: push n unchanged (BIP 342)
5322            if signature_bytes.is_empty() {
5323                stack.push(to_stack_element(&script_num_encode(n)));
5324                return Ok(true);
5325            }
5326
5327            // 32-byte pubkey + non-empty sig: validate. BIP 342: validation failure terminates script.
5328            if pubkey_bytes.len() == 32 && signature_bytes.len() == 64 {
5329                let sighash_byte = 0x00;
5330                let (tapscript, codesep_pos) = tapscript_for_sighash
5331                    .map(|s| (s, tapscript_codesep_pos.unwrap_or(0xffff_ffff)))
5332                    .unwrap_or((&[] as &[u8], 0xffff_ffff));
5333                let sighash = if tapscript.is_empty() {
5334                    crate::taproot::compute_taproot_signature_hash(
5335                        tx,
5336                        input_index,
5337                        prevout_values,
5338                        prevout_script_pubkeys,
5339                        sighash_byte,
5340                    )?
5341                } else {
5342                    crate::taproot::compute_tapscript_signature_hash(
5343                        tx,
5344                        input_index,
5345                        prevout_values,
5346                        prevout_script_pubkeys,
5347                        tapscript,
5348                        crate::taproot::TAPROOT_LEAF_VERSION_TAPSCRIPT,
5349                        codesep_pos,
5350                        sighash_byte,
5351                    )?
5352                };
5353
5354                #[cfg(feature = "production")]
5355                let is_valid = {
5356                    use crate::bip348::verify_tapscript_schnorr_signature;
5357                    verify_tapscript_schnorr_signature(
5358                        &sighash,
5359                        &pubkey_bytes,
5360                        &signature_bytes,
5361                        schnorr_collector,
5362                    )
5363                    .unwrap_or(false)
5364                };
5365
5366                #[cfg(not(feature = "production"))]
5367                let is_valid = {
5368                    #[cfg(feature = "csfs")]
5369                    let x = {
5370                        use crate::bip348::verify_tapscript_schnorr_signature;
5371                        verify_tapscript_schnorr_signature(
5372                            &sighash,
5373                            &pubkey_bytes,
5374                            &signature_bytes,
5375                            None,
5376                        )
5377                        .unwrap_or(false)
5378                    };
5379                    #[cfg(not(feature = "csfs"))]
5380                    let x = false;
5381                    x
5382                };
5383
5384                if !is_valid {
5385                    return Ok(false); // BIP 342: validation failure terminates script
5386                }
5387                stack.push(to_stack_element(&script_num_encode(n + 1)));
5388                return Ok(true);
5389            }
5390
5391            // Unknown pubkey type (not 32 bytes): BIP 342 treats as always-valid, push n+1
5392            stack.push(to_stack_element(&script_num_encode(n + 1)));
5393            Ok(true)
5394        }
5395
5396        // OP_CHECKMULTISIG - verify m-of-n multisig (hot path)
5397        OP_CHECKMULTISIG => {
5398            // OP_CHECKMULTISIG implementation
5399            // Stack layout: [dummy] [sig1] ... [sigm] [m] [pubkey1] ... [pubkeyn] [n]
5400            if stack.len() < 2 {
5401                return Ok(false);
5402            }
5403
5404            // Pop n (number of public keys) — must be decoded as CScriptNum (BIP62)
5405            let n_bytes = stack.pop().unwrap();
5406            let n_raw = script_num_decode(&n_bytes, 4).map_err(|_| {
5407                ConsensusError::ScriptErrorWithCode {
5408                    code: ScriptErrorCode::InvalidStackOperation,
5409                    message: "OP_CHECKMULTISIG: invalid n encoding".into(),
5410                }
5411            })?;
5412            if !(0..=20).contains(&n_raw) {
5413                return Ok(false);
5414            }
5415            let n = n_raw as usize;
5416            if stack.len() < n + 1 {
5417                return Ok(false);
5418            }
5419
5420            // Pop n public keys
5421            let mut pubkeys = Vec::with_capacity(n);
5422            for _ in 0..n {
5423                pubkeys.push(stack.pop().unwrap());
5424            }
5425
5426            // Pop m (number of required signatures) — must be decoded as CScriptNum (BIP62)
5427            let m_bytes = stack.pop().unwrap();
5428            let m_raw = script_num_decode(&m_bytes, 4).map_err(|_| {
5429                ConsensusError::ScriptErrorWithCode {
5430                    code: ScriptErrorCode::InvalidStackOperation,
5431                    message: "OP_CHECKMULTISIG: invalid m encoding".into(),
5432                }
5433            })?;
5434            if m_raw < 0 || m_raw as usize > n || m_raw > 20 {
5435                return Ok(false);
5436            }
5437            let m = m_raw as usize;
5438            if stack.len() < m + 1 {
5439                return Ok(false);
5440            }
5441
5442            // Pop m signatures
5443            let mut signatures = Vec::with_capacity(m);
5444            for _ in 0..m {
5445                signatures.push(stack.pop().unwrap());
5446            }
5447
5448            // Pop dummy element - this is the FIRST element consumed (last remaining on stack)
5449            // BIP147: Check NULLDUMMY if flag is set (SCRIPT_VERIFY_NULLDUMMY = 0x10)
5450            let dummy = stack.pop().unwrap();
5451            if flags & 0x10 != 0 {
5452                let height = block_height.unwrap_or(0);
5453                // Convert network type for BIP147
5454                use crate::bip_validation::Bip147Network;
5455                let bip147_network = match network {
5456                    crate::types::Network::Mainnet => Bip147Network::Mainnet,
5457                    crate::types::Network::Testnet => Bip147Network::Testnet,
5458                    crate::types::Network::Regtest => Bip147Network::Regtest,
5459                };
5460
5461                // For BIP147, the dummy element must be exactly [0x00] (OP_0) after activation
5462                // BIP147 requires the dummy to be exactly one byte: 0x00
5463                // Not empty [], not multi-byte [0x00, ...], not non-zero [0x01, ...]
5464                use crate::constants::{BIP147_ACTIVATION_MAINNET, BIP147_ACTIVATION_TESTNET};
5465
5466                let bip147_active = height
5467                    >= match bip147_network {
5468                        Bip147Network::Mainnet => BIP147_ACTIVATION_MAINNET,
5469                        Bip147Network::Testnet => BIP147_ACTIVATION_TESTNET,
5470                        Bip147Network::Regtest => 0,
5471                    };
5472
5473                if bip147_active {
5474                    // BIP147: Dummy must be empty (either [] or [0x00])
5475                    // In Bitcoin script, both empty [] and [0x00] (OP_0) are considered "empty"
5476                    // Both accepted as valid NULLDUMMY (BIP147)
5477                    let is_empty = dummy.is_empty() || dummy.as_ref() == [0x00];
5478                    if !is_empty {
5479                        return Err(ConsensusError::ScriptErrorWithCode {
5480                            code: ScriptErrorCode::SigNullDummy,
5481                message: format!(
5482                    "OP_CHECKMULTISIG: dummy element {dummy:?} violates BIP147 NULLDUMMY (must be empty: [] or [0x00])"
5483                )
5484                            .into(),
5485                        });
5486                    }
5487                }
5488            }
5489
5490            // Verify signatures against public keys
5491            // CHECKMULTISIG algorithm: iterate pubkeys, try to match sigs in order
5492            let height = block_height.unwrap_or(0);
5493
5494            // FindAndDelete: Remove ALL signatures from scriptCode BEFORE any sighash computation
5495            // Consensus rule for OP_CHECKMULTISIG (legacy only, not SegWit)
5496            let cleaned_script_for_multisig: Vec<u8> = if sigversion == SigVersion::Base {
5497                let base_script = match (
5498                    redeem_script_for_sighash,
5499                    prevout_script_pubkeys.get(input_index),
5500                ) {
5501                    (Some(redeem), Some(prevout)) if redeem == *prevout => *prevout,
5502                    (Some(redeem), _) => redeem,
5503                    (None, Some(prevout)) => *prevout,
5504                    (None, None) => &[],
5505                };
5506                let mut cleaned = base_script.to_vec();
5507                for sig in &signatures {
5508                    if !sig.is_empty() {
5509                        let pattern = serialize_push_data(sig.as_ref());
5510                        cleaned = find_and_delete(&cleaned, &pattern).into_owned();
5511                    }
5512                }
5513                cleaned
5514            } else {
5515                // For SegWit, no FindAndDelete needed
5516                redeem_script_for_sighash
5517                    .map(|s| s.to_vec())
5518                    .unwrap_or_else(|| {
5519                        prevout_script_pubkeys
5520                            .get(input_index)
5521                            .map(|p| p.to_vec())
5522                            .unwrap_or_default()
5523                    })
5524            };
5525
5526            use crate::transaction_hash::{
5527                calculate_transaction_sighash_single_input, SighashType,
5528            };
5529
5530            // Batch path: when n*m >= 4, precompute sighashes once per sig and batch-verify all (pubkey, sig) pairs.
5531            #[cfg(feature = "production")]
5532            let use_batch = pubkeys.len() * signatures.len() >= 4;
5533
5534            #[cfg(feature = "production")]
5535            let (valid_sigs, _) = if use_batch {
5536                // Phase 3: Batch sighash for multisig — use batch_compute_legacy_sighashes when Base
5537                let sighashes: Vec<[u8; 32]> = if sigversion == SigVersion::Base {
5538                    let non_empty: Vec<_> = signatures.iter().filter(|s| !s.is_empty()).collect();
5539                    if non_empty.is_empty() {
5540                        vec![]
5541                    } else {
5542                        let specs: Vec<(usize, u8, &[u8])> = non_empty
5543                            .iter()
5544                            .map(|s| {
5545                                (
5546                                    input_index,
5547                                    s.as_ref()[s.as_ref().len() - 1],
5548                                    cleaned_script_for_multisig.as_ref(),
5549                                )
5550                            })
5551                            .collect();
5552                        crate::transaction_hash::batch_compute_legacy_sighashes(
5553                            tx,
5554                            prevout_values,
5555                            prevout_script_pubkeys,
5556                            &specs,
5557                        )?
5558                    }
5559                } else {
5560                    signatures
5561                        .iter()
5562                        .filter(|s| !s.is_empty())
5563                        .map(|sig_bytes| {
5564                            let sighash_type =
5565                                SighashType::from_byte(sig_bytes[sig_bytes.len() - 1]);
5566                            calculate_transaction_sighash_single_input(
5567                                tx,
5568                                input_index,
5569                                &cleaned_script_for_multisig,
5570                                prevout_values[input_index],
5571                                sighash_type,
5572                                sighash_cache,
5573                            )
5574                        })
5575                        .collect::<Result<Vec<_>>>()?
5576                };
5577
5578                // Build verification tasks: (pubkey_i, sig_j, sighash_j) for all i,j. Order: j then i (sig_index, pubkey_index)
5579                let mut tasks: Vec<(&[u8], &[u8], [u8; 32])> =
5580                    Vec::with_capacity(pubkeys.len() * signatures.len());
5581                let mut sig_idx_to_sighash_idx = Vec::with_capacity(signatures.len());
5582                let mut sighash_idx = 0usize;
5583                for (j, sig_bytes) in signatures.iter().enumerate() {
5584                    if sig_bytes.is_empty() {
5585                        sig_idx_to_sighash_idx.push(usize::MAX);
5586                    } else {
5587                        sig_idx_to_sighash_idx.push(sighash_idx);
5588                        let sh = sighashes[sighash_idx];
5589                        sighash_idx += 1;
5590                        for pubkey_bytes in &pubkeys {
5591                            tasks.push((pubkey_bytes.as_ref(), sig_bytes.as_ref(), sh));
5592                        }
5593                    }
5594                }
5595
5596                let results = if tasks.is_empty() {
5597                    vec![]
5598                } else {
5599                    batch_verify_signatures(&tasks, flags, height, network, sigversion)?
5600                };
5601
5602                // Matching: for each pubkey in order, if current sig verifies with this pubkey, advance
5603                let mut sig_index = 0;
5604                let mut valid_sigs = 0usize;
5605                for (i, _pubkey_bytes) in pubkeys.iter().enumerate() {
5606                    if sig_index >= signatures.len() {
5607                        break;
5608                    }
5609                    // Skip empty sigs without advancing (same as original)
5610                    while sig_index < signatures.len() && signatures[sig_index].is_empty() {
5611                        sig_index += 1;
5612                    }
5613                    if sig_index >= signatures.len() {
5614                        break;
5615                    }
5616                    let sh_idx = sig_idx_to_sighash_idx[sig_index];
5617                    if sh_idx == usize::MAX {
5618                        continue;
5619                    }
5620                    let task_idx = sh_idx * pubkeys.len() + i;
5621                    if task_idx < results.len() && results[task_idx] {
5622                        valid_sigs += 1;
5623                        sig_index += 1;
5624                    }
5625                }
5626
5627                // NULLFAIL: any non-empty sig that didn't match any pubkey must cause failure
5628                const SCRIPT_VERIFY_NULLFAIL: u32 = 0x4000;
5629                if (flags & SCRIPT_VERIFY_NULLFAIL) != 0 {
5630                    for (j, sig_bytes) in signatures.iter().enumerate() {
5631                        if sig_bytes.is_empty() {
5632                            continue;
5633                        }
5634                        let sh_idx = sig_idx_to_sighash_idx[j];
5635                        if sh_idx == usize::MAX {
5636                            continue;
5637                        }
5638                        let sig_start = sh_idx * pubkeys.len();
5639                        let sig_end = (sig_start + pubkeys.len()).min(results.len());
5640                        let matched = results[sig_start..sig_end].iter().any(|&r| r);
5641                        if !matched {
5642                            return Err(ConsensusError::ScriptErrorWithCode {
5643                                code: ScriptErrorCode::SigNullFail,
5644                                message: "OP_CHECKMULTISIG: non-null signature must not fail under NULLFAIL".into(),
5645                            });
5646                        }
5647                    }
5648                }
5649                (valid_sigs, ())
5650            } else {
5651                let mut sig_index = 0;
5652                let mut valid_sigs = 0;
5653
5654                for pubkey_bytes in &pubkeys {
5655                    if sig_index >= signatures.len() {
5656                        break;
5657                    }
5658
5659                    let signature_bytes = &signatures[sig_index];
5660
5661                    if signature_bytes.is_empty() {
5662                        continue;
5663                    }
5664
5665                    let sig_len = signature_bytes.len();
5666                    let sighash_byte = signature_bytes[sig_len - 1];
5667                    let sighash_type = SighashType::from_byte(sighash_byte);
5668
5669                    let sighash = calculate_transaction_sighash_single_input(
5670                        tx,
5671                        input_index,
5672                        &cleaned_script_for_multisig,
5673                        prevout_values[input_index],
5674                        sighash_type,
5675                        #[cfg(feature = "production")]
5676                        sighash_cache,
5677                    )?;
5678
5679                    #[cfg(feature = "production")]
5680                    let is_valid = signature::with_secp_context(|secp| {
5681                        signature::verify_signature(
5682                            secp,
5683                            pubkey_bytes,
5684                            signature_bytes,
5685                            &sighash,
5686                            flags,
5687                            height,
5688                            network,
5689                            sigversion,
5690                        )
5691                    })?;
5692
5693                    #[cfg(not(feature = "production"))]
5694                    let is_valid = {
5695                        let secp = signature::new_secp();
5696                        signature::verify_signature(
5697                            &secp,
5698                            pubkey_bytes,
5699                            signature_bytes,
5700                            &sighash,
5701                            flags,
5702                            height,
5703                            network,
5704                            sigversion,
5705                        )?
5706                    };
5707
5708                    const SCRIPT_VERIFY_NULLFAIL: u32 = 0x4000;
5709                    if !is_valid
5710                        && (flags & SCRIPT_VERIFY_NULLFAIL) != 0
5711                        && !signature_bytes.is_empty()
5712                    {
5713                        return Err(ConsensusError::ScriptErrorWithCode {
5714                            code: ScriptErrorCode::SigNullFail,
5715                            message:
5716                                "OP_CHECKMULTISIG: non-null signature must not fail under NULLFAIL"
5717                                    .into(),
5718                        });
5719                    }
5720
5721                    if is_valid {
5722                        valid_sigs += 1;
5723                        sig_index += 1;
5724                    }
5725                }
5726                (valid_sigs, ())
5727            };
5728
5729            #[cfg(not(feature = "production"))]
5730            let (valid_sigs, _) = {
5731                let mut sig_index = 0;
5732                let mut valid_sigs = 0;
5733
5734                for pubkey_bytes in &pubkeys {
5735                    if sig_index >= signatures.len() {
5736                        break;
5737                    }
5738                    let signature_bytes = &signatures[sig_index];
5739                    if signature_bytes.is_empty() {
5740                        continue;
5741                    }
5742                    let sig_len = signature_bytes.len();
5743                    let sighash_type = SighashType::from_byte(signature_bytes[sig_len - 1]);
5744                    let sighash = calculate_transaction_sighash_single_input(
5745                        tx,
5746                        input_index,
5747                        &cleaned_script_for_multisig,
5748                        prevout_values[input_index],
5749                        sighash_type,
5750                        #[cfg(feature = "production")]
5751                        sighash_cache,
5752                    )?;
5753                    let secp = signature::new_secp();
5754                    let is_valid = signature::verify_signature(
5755                        &secp,
5756                        pubkey_bytes,
5757                        signature_bytes,
5758                        &sighash,
5759                        flags,
5760                        height,
5761                        network,
5762                        sigversion,
5763                    )?;
5764                    const SCRIPT_VERIFY_NULLFAIL: u32 = 0x4000;
5765                    if !is_valid
5766                        && (flags & SCRIPT_VERIFY_NULLFAIL) != 0
5767                        && !signature_bytes.is_empty()
5768                    {
5769                        return Err(ConsensusError::ScriptErrorWithCode {
5770                            code: ScriptErrorCode::SigNullFail,
5771                            message:
5772                                "OP_CHECKMULTISIG: non-null signature must not fail under NULLFAIL"
5773                                    .into(),
5774                        });
5775                    }
5776                    if is_valid {
5777                        valid_sigs += 1;
5778                        sig_index += 1;
5779                    }
5780                }
5781                (valid_sigs, ())
5782            };
5783
5784            // Push result: 1 if valid_sigs >= m, 0 otherwise
5785            stack.push(to_stack_element(&[if valid_sigs >= m { 1 } else { 0 }]));
5786            Ok(true)
5787        }
5788
5789        // OP_CHECKMULTISIGVERIFY - CHECKMULTISIG + VERIFY (hot path)
5790        OP_CHECKMULTISIGVERIFY => {
5791            // Execute CHECKMULTISIG first
5792            let ctx_checkmultisig = context::ScriptContext {
5793                tx,
5794                input_index,
5795                prevout_values,
5796                prevout_script_pubkeys,
5797                block_height,
5798                median_time_past,
5799                network,
5800                sigversion,
5801                redeem_script_for_sighash,
5802                script_sig_for_sighash,
5803                tapscript_for_sighash,
5804                tapscript_codesep_pos,
5805                #[cfg(feature = "production")]
5806                schnorr_collector: None,
5807                #[cfg(feature = "production")]
5808                precomputed_bip143,
5809                #[cfg(feature = "production")]
5810                sighash_cache,
5811            };
5812            let result = execute_opcode_with_context_full(
5813                OP_CHECKMULTISIG,
5814                stack,
5815                flags,
5816                &ctx_checkmultisig,
5817                redeem_script_for_sighash,
5818            )?;
5819            if !result {
5820                return Ok(false);
5821            }
5822            // VERIFY: check top of stack is truthy, then pop it
5823            if let Some(top) = stack.pop() {
5824                if !cast_to_bool(&top) {
5825                    return Ok(false);
5826                }
5827                Ok(true)
5828            } else {
5829                Ok(false)
5830            }
5831        }
5832
5833        // OP_CHECKLOCKTIMEVERIFY (BIP65)
5834        // Validates that transaction locktime is >= top stack item
5835        // If SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY flag is not set, treat as NOP2
5836        // CLTV does NOT pop the stack — it only reads the top element (NOP-type opcode)
5837        OP_CHECKLOCKTIMEVERIFY => {
5838            // If CLTV flag is not enabled, behave as NOP (treat as NOP2)
5839            const SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY: u32 = 0x200;
5840            if (flags & SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY) == 0 {
5841                return Ok(true);
5842            }
5843
5844            use crate::locktime::{check_bip65, decode_locktime_value};
5845
5846            if stack.is_empty() {
5847                return Err(ConsensusError::ScriptErrorWithCode {
5848                    code: ScriptErrorCode::InvalidStackOperation,
5849                    message: "OP_CHECKLOCKTIMEVERIFY: empty stack".into(),
5850                });
5851            }
5852
5853            // Decode locktime value from stack using CScriptNum rules (max 5 bytes)
5854            let locktime_bytes = stack.last().expect("Stack is not empty");
5855            let locktime_value = match decode_locktime_value(locktime_bytes.as_ref()) {
5856                Some(v) => v,
5857                None => {
5858                    return Err(ConsensusError::ScriptErrorWithCode {
5859                        code: ScriptErrorCode::MinimalData,
5860                        message: "OP_CHECKLOCKTIMEVERIFY: invalid locktime encoding".into(),
5861                    })
5862                }
5863            };
5864
5865            let tx_locktime = tx.lock_time as u32;
5866
5867            // CheckLockTime order (BIP65): locktime check via check_bip65
5868            if !check_bip65(tx_locktime, locktime_value) {
5869                return Ok(false);
5870            }
5871
5872            // Input sequence must NOT be SEQUENCE_FINAL (0xffffffff)
5873            let input_seq = if input_index < tx.inputs.len() {
5874                tx.inputs[input_index].sequence
5875            } else {
5876                0xffffffff
5877            };
5878            if input_seq == 0xffffffff {
5879                return Ok(false);
5880            }
5881
5882            // CLTV does NOT pop the stack (NOP-type opcode)
5883            Ok(true)
5884        }
5885
5886        // OP_CHECKSEQUENCEVERIFY (BIP112)
5887        // Validates that transaction input sequence number meets relative locktime requirement.
5888        // Implements BIP68: Relative Lock-Time Using Consensus-Enforced Sequence Numbers.
5889        //
5890        // Behavior must match consensus (BIP65/112):
5891        // - If SCRIPT_VERIFY_CHECKSEQUENCEVERIFY flag is not set, behaves as a NOP (no-op)
5892        // - If sequence has the disable flag set (0x80000000), behaves as a NOP
5893        // - Does NOT remove the top stack item on success (non-consuming)
5894        OP_CHECKSEQUENCEVERIFY => {
5895            use crate::locktime::{
5896                decode_locktime_value, extract_sequence_locktime_value, extract_sequence_type_flag,
5897                is_sequence_disabled,
5898            };
5899
5900            // If CSV flag is not enabled, behave as NOP (treat as NOP3)
5901            const SCRIPT_VERIFY_CHECKSEQUENCEVERIFY: u32 = 0x400;
5902            if (flags & SCRIPT_VERIFY_CHECKSEQUENCEVERIFY) == 0 {
5903                return Ok(true);
5904            }
5905
5906            if stack.is_empty() {
5907                return Ok(false);
5908            }
5909
5910            // Decode sequence value from stack using shared locktime logic.
5911            // Interpret the top stack element as a sequence value (BIP112).
5912            let sequence_bytes = stack.last().expect("Stack is not empty");
5913            let sequence_value = match decode_locktime_value(sequence_bytes.as_ref()) {
5914                Some(v) => v,
5915                None => return Ok(false), // Invalid encoding
5916            };
5917
5918            // Get input sequence number
5919            if input_index >= tx.inputs.len() {
5920                return Ok(false);
5921            }
5922            let input_sequence = tx.inputs[input_index].sequence as u32;
5923
5924            // BIP112/BIP68: If sequence has the disable flag set, CSV behaves as a NOP
5925            if is_sequence_disabled(input_sequence) {
5926                return Ok(true);
5927            }
5928
5929            // BIP68: Extract relative locktime type and value using shared logic
5930            let type_flag = extract_sequence_type_flag(sequence_value);
5931            let locktime_mask = extract_sequence_locktime_value(sequence_value) as u32;
5932
5933            // Extract input sequence flags and value
5934            let input_type_flag = extract_sequence_type_flag(input_sequence);
5935            let input_locktime = extract_sequence_locktime_value(input_sequence) as u32;
5936
5937            // BIP112: CSV fails if type_flag doesn't match input type
5938            if type_flag != input_type_flag {
5939                return Ok(false);
5940            }
5941
5942            // BIP112: CSV fails if input locktime < required locktime
5943            if input_locktime < locktime_mask {
5944                return Ok(false);
5945            }
5946
5947            // Validation passed - behave as NOP (do NOT pop the sequence value)
5948            Ok(true)
5949        }
5950
5951        // OP_CHECKTEMPLATEVERIFY (BIP119) - OP_NOP4
5952        // Verifies that the transaction matches a template hash.
5953        // Implements BIP119: CHECKTEMPLATEVERIFY.
5954        //
5955        // Behavior must match consensus:
5956        // - If SCRIPT_VERIFY_DEFAULT_CHECK_TEMPLATE_VERIFY_HASH flag is not set, behaves as NOP4
5957        // - Requires exactly 32 bytes on stack (template hash)
5958        // - Fails if template hash doesn't match transaction
5959        OP_CHECKTEMPLATEVERIFY => {
5960            #[cfg(not(feature = "ctv"))]
5961            {
5962                // Without feature flag, treat as NOP4 (or discourage if flag set)
5963                const SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS: u32 = 0x10000;
5964                if (flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS) != 0 {
5965                    return Err(ConsensusError::ScriptErrorWithCode {
5966                        code: ScriptErrorCode::BadOpcode,
5967                        message: "OP_CHECKTEMPLATEVERIFY requires --features ctv".into(),
5968                    });
5969                }
5970                Ok(true) // NOP4
5971            }
5972
5973            #[cfg(feature = "ctv")]
5974            {
5975                use crate::constants::{
5976                    CTV_ACTIVATION_MAINNET, CTV_ACTIVATION_REGTEST, CTV_ACTIVATION_TESTNET,
5977                };
5978
5979                // Check activation
5980                let ctv_activation = match network {
5981                    crate::types::Network::Mainnet => CTV_ACTIVATION_MAINNET,
5982                    crate::types::Network::Testnet => CTV_ACTIVATION_TESTNET,
5983                    crate::types::Network::Regtest => CTV_ACTIVATION_REGTEST,
5984                };
5985
5986                let ctv_active = block_height.map(|h| h >= ctv_activation).unwrap_or(false);
5987                if !ctv_active {
5988                    // Before activation: treat as NOP4
5989                    const SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS: u32 = 0x10000;
5990                    if (flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS) != 0 {
5991                        return Err(ConsensusError::ScriptErrorWithCode {
5992                            code: ScriptErrorCode::BadOpcode,
5993                            message: "OP_CHECKTEMPLATEVERIFY not yet activated".into(),
5994                        });
5995                    }
5996                    return Ok(true); // NOP4
5997                }
5998
5999                // Check if CTV flag is enabled
6000                const SCRIPT_VERIFY_DEFAULT_CHECK_TEMPLATE_VERIFY_HASH: u32 = 0x80000000;
6001                if (flags & SCRIPT_VERIFY_DEFAULT_CHECK_TEMPLATE_VERIFY_HASH) == 0 {
6002                    // Flag not set, treat as NOP4
6003                    return Ok(true);
6004                }
6005
6006                use crate::bip119::calculate_template_hash;
6007
6008                // CTV requires exactly 32 bytes (template hash) on stack
6009                if stack.is_empty() {
6010                    return Err(ConsensusError::ScriptErrorWithCode {
6011                        code: ScriptErrorCode::InvalidStackOperation,
6012                        message: "OP_CHECKTEMPLATEVERIFY: insufficient stack items".into(),
6013                    });
6014                }
6015
6016                let template_hash_bytes = stack.pop().unwrap();
6017
6018                // Template hash must be exactly 32 bytes
6019                if template_hash_bytes.len() != 32 {
6020                    // Non-32-byte argument: NOP (per BIP-119)
6021                    // But discourage if flag is set
6022                    const SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS: u32 = 0x10000;
6023                    if (flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS) != 0 {
6024                        return Err(ConsensusError::ScriptErrorWithCode {
6025                            code: ScriptErrorCode::InvalidStackOperation,
6026                            message: "OP_CHECKTEMPLATEVERIFY: template hash must be 32 bytes"
6027                                .into(),
6028                        });
6029                    }
6030                    return Ok(true); // NOP
6031                }
6032
6033                // Calculate actual template hash for this transaction
6034                let mut expected_hash = [0u8; 32];
6035                expected_hash.copy_from_slice(&template_hash_bytes);
6036
6037                let actual_hash = calculate_template_hash(tx, input_index).map_err(|e| {
6038                    ConsensusError::ScriptErrorWithCode {
6039                        code: ScriptErrorCode::TxInvalid,
6040                        message: format!("CTV hash calculation failed: {e}").into(),
6041                    }
6042                })?;
6043
6044                // Constant-time comparison (use hash_eq from crypto module)
6045                use crate::crypto::hash_compare::hash_eq;
6046                let matches = hash_eq(&expected_hash, &actual_hash);
6047
6048                if !matches {
6049                    return Ok(false); // Script fails if template doesn't match
6050                }
6051
6052                // CTV succeeds - script continues (NOP-type opcode, doesn't push anything)
6053                Ok(true)
6054            }
6055        }
6056
6057        // OP_CHECKSIGFROMSTACK (BIP348) - replaces OP_SUCCESS204
6058        // Verifies a BIP 340 Schnorr signature against an arbitrary message.
6059        // Implements BIP348: CHECKSIGFROMSTACK.
6060        //
6061        // Behavior must match BIP341 tapscript verification:
6062        // - Only available in Tapscript (leaf version 0xc0)
6063        // - Pops 3 items: pubkey (top), message (second), signature (third)
6064        // - If signature is empty, pushes empty vector and continues
6065        // - If signature is valid, pushes 0x01 (single byte)
6066        // - If signature is invalid, script fails
6067        OP_CHECKSIGFROMSTACK => {
6068            #[cfg(not(feature = "csfs"))]
6069            {
6070                // Without feature flag, OP_SUCCESS204 behavior (succeeds)
6071                // But discourage if flag is set
6072                const SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS: u32 = 0x10000;
6073                if (flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS) != 0 {
6074                    return Err(ConsensusError::ScriptErrorWithCode {
6075                        code: ScriptErrorCode::BadOpcode,
6076                        message: "OP_CHECKSIGFROMSTACK requires --features csfs".into(),
6077                    });
6078                }
6079                Ok(true) // OP_SUCCESS204 succeeds
6080            }
6081
6082            #[cfg(feature = "csfs")]
6083            {
6084                use crate::constants::{
6085                    CSFS_ACTIVATION_MAINNET, CSFS_ACTIVATION_REGTEST, CSFS_ACTIVATION_TESTNET,
6086                };
6087
6088                // BIP-348: Only available in Tapscript (leaf version 0xc0)
6089                if sigversion != SigVersion::Tapscript {
6090                    return Err(ConsensusError::ScriptErrorWithCode {
6091                        code: ScriptErrorCode::BadOpcode,
6092                        message: "OP_CHECKSIGFROMSTACK only available in Tapscript".into(),
6093                    });
6094                }
6095
6096                // Check activation
6097                let csfs_activation = match network {
6098                    crate::types::Network::Mainnet => CSFS_ACTIVATION_MAINNET,
6099                    crate::types::Network::Testnet => CSFS_ACTIVATION_TESTNET,
6100                    crate::types::Network::Regtest => CSFS_ACTIVATION_REGTEST,
6101                };
6102
6103                let csfs_active = block_height.map(|h| h >= csfs_activation).unwrap_or(false);
6104                if !csfs_active {
6105                    // Before activation: OP_SUCCESS204 behavior (succeeds)
6106                    const SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS: u32 = 0x10000;
6107                    if (flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS) != 0 {
6108                        return Err(ConsensusError::ScriptErrorWithCode {
6109                            code: ScriptErrorCode::BadOpcode,
6110                            message: "OP_CHECKSIGFROMSTACK not yet activated".into(),
6111                        });
6112                    }
6113                    return Ok(true); // OP_SUCCESS204 succeeds
6114                }
6115
6116                use crate::bip348::verify_signature_from_stack;
6117
6118                // BIP-348: If fewer than 3 elements, script MUST fail
6119                if stack.len() < 3 {
6120                    return Err(ConsensusError::ScriptErrorWithCode {
6121                        code: ScriptErrorCode::InvalidStackOperation,
6122                        message: "OP_CHECKSIGFROMSTACK: insufficient stack items (need 3)".into(),
6123                    });
6124                }
6125
6126                // BIP-348: Pop in order: pubkey (top), message (second), signature (third)
6127                let pubkey_bytes = stack.pop().unwrap(); // Top
6128                let message_bytes = stack.pop().unwrap(); // Second
6129                let signature_bytes = stack.pop().unwrap(); // Third
6130
6131                // BIP-348: If pubkey size is zero, script MUST fail
6132                if pubkey_bytes.is_empty() {
6133                    return Err(ConsensusError::ScriptErrorWithCode {
6134                        code: ScriptErrorCode::PubkeyType,
6135                        message: "OP_CHECKSIGFROMSTACK: pubkey size is zero".into(),
6136                    });
6137                }
6138
6139                // BIP-348: If signature is empty, push empty vector and continue
6140                if signature_bytes.is_empty() {
6141                    stack.push(to_stack_element(&[])); // Empty vector, not 0
6142                    return Ok(true);
6143                }
6144
6145                // BIP-348: Verify signature (only for 32-byte pubkeys)
6146                // OPTIMIZATION: Use collector for batch verification if available
6147                #[cfg(feature = "production")]
6148                let is_valid = {
6149                    verify_signature_from_stack(
6150                        &message_bytes,    // Message (NOT hashed by BIP 340 spec)
6151                        &pubkey_bytes,     // Pubkey (32 bytes for BIP 340)
6152                        &signature_bytes,  // Signature (64-byte BIP 340 Schnorr)
6153                        schnorr_collector, // Pass collector for batch verification
6154                    )
6155                    .unwrap_or(false)
6156                };
6157                #[cfg(not(feature = "production"))]
6158                let is_valid = verify_signature_from_stack(
6159                    &message_bytes,   // Message (NOT hashed by BIP 340 spec)
6160                    &pubkey_bytes,    // Pubkey (32 bytes for BIP 340)
6161                    &signature_bytes, // Signature (64-byte BIP 340 Schnorr)
6162                )
6163                .unwrap_or(false);
6164
6165                if !is_valid {
6166                    // BIP-348: Validation failure immediately terminates script execution
6167                    return Ok(false);
6168                }
6169
6170                // BIP-342/348: per-tapscript validation weight enforces signature-related limits during
6171                // execution. They are not added to `MAX_BLOCK_SIGOPS_COST` (witness v1 returns 0 in Core's
6172                // WitnessSigOps).
6173
6174                // BIP-348: Push 0x01 (single byte) if valid
6175                stack.push(to_stack_element(&[0x01])); // Single byte 0x01, not 1
6176                Ok(true)
6177            }
6178        }
6179
6180        // cold path for all other opcodes (branch prediction hint)
6181        _ => execute_opcode_cold(opcode, stack, flags),
6182    }
6183}
6184
6185/// Rare opcode dispatch (`#[cold]` so hot path stays compact).
6186#[cold]
6187fn execute_opcode_cold(opcode: u8, stack: &mut Vec<StackElement>, flags: u32) -> Result<bool> {
6188    execute_opcode(opcode, stack, flags, SigVersion::Base)
6189}
6190
6191// ============================================================================
6192// Benchmarking Utilities
6193// ============================================================================
6194
6195/// Clear script verification cache
6196///
6197/// Useful for benchmarking to ensure consistent results without cache state
6198/// pollution between runs.
6199///
6200/// # Example
6201///
6202/// Get and reset fast-path hit counters (production). Used by block validation to log
6203/// whether P2PK/P2PKH/P2SH/P2WPKH/P2WSH fast-paths are taken vs interpreter fallback.
6204/// Returns (p2pk, p2pkh, p2sh, p2wpkh, p2wsh, p2tr, bare_multisig, interpreter).
6205#[cfg(feature = "production")]
6206pub(crate) fn get_and_reset_fast_path_counts() -> (u64, u64, u64, u64, u64, u64, u64, u64) {
6207    (
6208        FAST_PATH_P2PK.swap(0, Ordering::Relaxed),
6209        FAST_PATH_P2PKH.swap(0, Ordering::Relaxed),
6210        FAST_PATH_P2SH.swap(0, Ordering::Relaxed),
6211        FAST_PATH_P2WPKH.swap(0, Ordering::Relaxed),
6212        FAST_PATH_P2WSH.swap(0, Ordering::Relaxed),
6213        FAST_PATH_P2TR.swap(0, Ordering::Relaxed),
6214        FAST_PATH_BARE_MULTISIG.swap(0, Ordering::Relaxed),
6215        FAST_PATH_INTERPRETER.swap(0, Ordering::Relaxed),
6216    )
6217}
6218
6219/// ```rust
6220/// use blvm_consensus::script::clear_script_cache;
6221///
6222/// // Clear cache before benchmark run
6223/// clear_script_cache();
6224/// ```
6225#[cfg(all(feature = "production", feature = "benchmarking"))]
6226pub fn clear_script_cache() {
6227    if let Some(cache) = SCRIPT_CACHE.get() {
6228        let mut cache = cache.write().unwrap();
6229        cache.clear();
6230    }
6231}
6232
6233/// Clear hash operation cache
6234///
6235/// Useful for benchmarking to ensure consistent results without cache state
6236/// pollution between runs.
6237///
6238/// # Example
6239///
6240/// ```rust
6241/// use blvm_consensus::script::clear_hash_cache;
6242///
6243/// // Clear cache before benchmark run
6244/// clear_hash_cache();
6245/// ```
6246#[cfg(all(feature = "production", feature = "benchmarking"))]
6247pub fn clear_hash_cache() {
6248    crypto_ops::clear_hash_cache();
6249}
6250
6251/// Clear all caches
6252///
6253/// Convenience function to clear both script and hash caches.
6254///
6255/// # Example
6256///
6257/// ```rust
6258/// use blvm_consensus::script::clear_all_caches;
6259///
6260/// // Clear all caches before benchmark run
6261/// clear_all_caches();
6262/// ```
6263#[cfg(all(feature = "production", feature = "benchmarking"))]
6264pub fn clear_all_caches() {
6265    clear_script_cache();
6266    clear_hash_cache();
6267}
6268
6269/// Clear thread-local stack pool
6270///
6271/// Clears the thread-local stack pool to reset allocation state for benchmarking.
6272/// This ensures consistent memory allocation patterns across benchmark runs.
6273///
6274/// # Example
6275///
6276/// ```rust
6277/// use blvm_consensus::script::clear_stack_pool;
6278///
6279/// // Clear pool before benchmark run
6280/// clear_stack_pool();
6281/// ```
6282#[cfg(all(feature = "production", feature = "benchmarking"))]
6283pub fn clear_stack_pool() {
6284    STACK_POOL.with(|pool| {
6285        let mut pool = pool.borrow_mut();
6286        pool.clear();
6287    });
6288}
6289
6290/// Reset all benchmarking state
6291///
6292/// Convenience function to reset all caches and thread-local state for
6293/// reproducible benchmarks. Also clears sighash templates cache.
6294///
6295/// # Example
6296///
6297/// ```rust
6298/// use blvm_consensus::script::reset_benchmarking_state;
6299///
6300/// // Reset all state before benchmark run
6301/// reset_benchmarking_state();
6302/// ```
6303#[cfg(all(feature = "production", feature = "benchmarking"))]
6304pub fn reset_benchmarking_state() {
6305    clear_all_caches();
6306    clear_stack_pool();
6307    disable_caching(false); // Re-enable caching by default
6308                            // Also clear sighash templates (currently no-op as templates aren't populated yet)
6309    #[cfg(feature = "benchmarking")]
6310    crate::transaction_hash::clear_sighash_templates();
6311}
6312
6313#[cfg(test)]
6314mod tests {
6315    use super::*;
6316
6317    #[test]
6318    fn test_eval_script_simple() {
6319        let script = vec![OP_1]; // OP_1
6320        let mut stack = Vec::new();
6321
6322        assert!(eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap());
6323        assert_eq!(stack.len(), 1);
6324        assert_eq!(stack[0].as_ref(), &[1]);
6325    }
6326
6327    #[test]
6328    fn test_eval_script_overflow() {
6329        let script = vec![0x51; MAX_STACK_SIZE + 1]; // Too many pushes
6330        let mut stack = Vec::new();
6331
6332        assert!(eval_script(&script, &mut stack, 0, SigVersion::Base).is_err());
6333    }
6334
6335    #[test]
6336    fn test_verify_script_simple() {
6337        let _script_sig = [0x51]; // OP_1
6338        let _script_pubkey = [0x51]; // OP_1
6339
6340        // This should work: OP_1 pushes 1, then OP_1 pushes another 1
6341        // Final stack has [1, 1], which is not exactly one non-zero value
6342        // Let's use a script that results in exactly one value on stack
6343        let script_sig = vec![0x51]; // OP_1
6344        let script_pubkey = vec![0x76, 0x88]; // OP_DUP, OP_EQUALVERIFY
6345
6346        // This should fail because OP_EQUALVERIFY removes both values
6347        assert!(!verify_script(&script_sig, &script_pubkey, None, 0).unwrap());
6348    }
6349
6350    // ============================================================================
6351    // COMPREHENSIVE OPCODE TESTS
6352    // ============================================================================
6353
6354    #[test]
6355    fn test_op_0() {
6356        let script = vec![OP_0]; // OP_0
6357        let mut stack = Vec::new();
6358        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6359        assert!(!result); // OP_0 pushes empty array, which is "false"
6360        assert_eq!(stack.len(), 1);
6361        assert!(stack[0].is_empty());
6362    }
6363
6364    #[test]
6365    fn test_op_1_to_op_16() {
6366        // Test OP_1 through OP_16
6367        for i in 1..=16 {
6368            let opcode = 0x50 + i;
6369            let script = vec![opcode];
6370            let mut stack = Vec::new();
6371            let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6372            assert!(result);
6373            assert_eq!(stack.len(), 1);
6374            assert_eq!(stack[0].as_ref(), &[i]);
6375        }
6376    }
6377
6378    #[test]
6379    fn test_op_dup() {
6380        let script = vec![0x51, 0x76]; // OP_1, OP_DUP
6381        let mut stack = Vec::new();
6382        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6383        assert!(!result); // Final stack has 2 items [1, 1], not exactly 1
6384        assert_eq!(stack.len(), 2);
6385        assert_eq!(stack[0].as_ref(), &[1]);
6386        assert_eq!(stack[1].as_ref(), &[1]);
6387    }
6388
6389    #[test]
6390    fn test_op_dup_empty_stack() {
6391        let script = vec![OP_DUP]; // OP_DUP on empty stack
6392        let mut stack = Vec::new();
6393        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6394        assert!(!result);
6395    }
6396
6397    #[test]
6398    fn test_op_hash160() {
6399        let script = vec![OP_1, OP_HASH160]; // OP_1, OP_HASH160
6400        let mut stack = Vec::new();
6401        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6402        assert!(result);
6403        assert_eq!(stack.len(), 1);
6404        assert_eq!(stack[0].len(), 20); // RIPEMD160 output is 20 bytes
6405    }
6406
6407    #[test]
6408    fn test_op_hash160_empty_stack() {
6409        let script = vec![OP_HASH160]; // OP_HASH160 on empty stack
6410        let mut stack = Vec::new();
6411        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6412        assert!(!result);
6413    }
6414
6415    #[test]
6416    fn test_op_hash256() {
6417        let script = vec![OP_1, OP_HASH256]; // OP_1, OP_HASH256
6418        let mut stack = Vec::new();
6419        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6420        assert!(result);
6421        assert_eq!(stack.len(), 1);
6422        assert_eq!(stack[0].len(), 32); // SHA256 output is 32 bytes
6423    }
6424
6425    #[test]
6426    fn test_op_hash256_empty_stack() {
6427        let script = vec![OP_HASH256]; // OP_HASH256 on empty stack
6428        let mut stack = Vec::new();
6429        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6430        assert!(!result);
6431    }
6432
6433    #[test]
6434    fn test_op_equal() {
6435        let script = vec![0x51, 0x51, 0x87]; // OP_1, OP_1, OP_EQUAL
6436        let mut stack = Vec::new();
6437        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6438        assert!(result);
6439        assert_eq!(stack.len(), 1);
6440        assert_eq!(stack[0].as_ref(), &[1]); // True
6441    }
6442
6443    #[test]
6444    fn test_op_equal_false() {
6445        let script = vec![0x51, 0x52, 0x87]; // OP_1, OP_2, OP_EQUAL
6446        let mut stack = Vec::new();
6447        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6448        assert!(!result); // False value (0) is not considered "true"
6449        assert_eq!(stack.len(), 1);
6450        assert_eq!(stack[0].as_ref(), &[0]); // False
6451    }
6452
6453    #[test]
6454    fn test_op_equal_insufficient_stack() {
6455        let script = vec![0x51, 0x87]; // OP_1, OP_EQUAL (need 2 items)
6456        let mut stack = Vec::new();
6457        let result = eval_script(&script, &mut stack, 0, SigVersion::Base);
6458        assert!(
6459            result.is_err(),
6460            "OP_EQUAL with insufficient stack should return error"
6461        );
6462        if let Err(crate::error::ConsensusError::ScriptErrorWithCode { code, .. }) = result {
6463            assert_eq!(
6464                code,
6465                crate::error::ScriptErrorCode::InvalidStackOperation,
6466                "Should return InvalidStackOperation"
6467            );
6468        }
6469    }
6470
6471    #[test]
6472    fn test_op_verify() {
6473        let script = vec![0x51, 0x69]; // OP_1, OP_VERIFY
6474        let mut stack = Vec::new();
6475        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6476        assert!(!result); // Final stack is empty, not exactly 1 item
6477        assert_eq!(stack.len(), 0); // OP_VERIFY consumes the top item
6478    }
6479
6480    #[test]
6481    fn test_op_verify_false() {
6482        let script = vec![0x00, 0x69]; // OP_0, OP_VERIFY (false)
6483        let mut stack = Vec::new();
6484        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6485        assert!(!result);
6486    }
6487
6488    #[test]
6489    fn test_op_verify_empty_stack() {
6490        let script = vec![OP_VERIFY]; // OP_VERIFY on empty stack
6491        let mut stack = Vec::new();
6492        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6493        assert!(!result);
6494    }
6495
6496    #[test]
6497    fn test_op_equalverify() {
6498        let script = vec![0x51, 0x51, 0x88]; // OP_1, OP_1, OP_EQUALVERIFY
6499        let mut stack = Vec::new();
6500        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6501        assert!(!result); // Final stack is empty, not exactly 1 item
6502        assert_eq!(stack.len(), 0); // OP_EQUALVERIFY consumes both items
6503    }
6504
6505    #[test]
6506    fn test_op_equalverify_false() {
6507        let script = vec![0x51, 0x52, 0x88]; // OP_1, OP_2, OP_EQUALVERIFY (false)
6508        let mut stack = Vec::new();
6509        let result = eval_script(&script, &mut stack, 0, SigVersion::Base);
6510        assert!(
6511            result.is_err(),
6512            "OP_EQUALVERIFY with false condition should return error"
6513        );
6514        if let Err(crate::error::ConsensusError::ScriptErrorWithCode { code, .. }) = result {
6515            assert_eq!(
6516                code,
6517                crate::error::ScriptErrorCode::EqualVerify,
6518                "Should return EqualVerify"
6519            );
6520        }
6521    }
6522
6523    #[test]
6524    fn test_op_checksig() {
6525        // Note: This test uses simplified inputs. Production code performs full signature verification.
6526        // The test verifies that OP_CHECKSIG executes without panicking, not that signatures are valid.
6527        let script = vec![OP_1, OP_1, OP_CHECKSIG]; // OP_1, OP_1, OP_CHECKSIG
6528        let mut stack = Vec::new();
6529        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6530        assert!(!result); // OP_CHECKSIG returns false for invalid signatures (expected in test)
6531        assert_eq!(stack.len(), 1);
6532        // Production code validates signatures using secp256k1; test uses simplified inputs
6533    }
6534
6535    #[test]
6536    fn test_op_checksig_insufficient_stack() {
6537        let script = vec![OP_1, OP_CHECKSIG]; // OP_1, OP_CHECKSIG (need 2 items)
6538        let mut stack = Vec::new();
6539        let result = eval_script(&script, &mut stack, 0, SigVersion::Base);
6540        assert!(
6541            result.is_err(),
6542            "OP_CHECKSIG with insufficient stack should return error"
6543        );
6544        if let Err(crate::error::ConsensusError::ScriptErrorWithCode { code, .. }) = result {
6545            assert_eq!(
6546                code,
6547                crate::error::ScriptErrorCode::InvalidStackOperation,
6548                "Should return InvalidStackOperation"
6549            );
6550        }
6551    }
6552
6553    #[test]
6554    fn test_unknown_opcode() {
6555        let script = vec![0xff]; // Unknown opcode (0xff is not a defined opcode constant)
6556        let mut stack = Vec::new();
6557        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6558        assert!(!result);
6559    }
6560
6561    #[test]
6562    fn test_script_size_limit() {
6563        let script = vec![0x51; MAX_SCRIPT_SIZE + 1]; // Exceed size limit
6564        let mut stack = Vec::new();
6565        let result = eval_script(&script, &mut stack, 0, SigVersion::Base);
6566        assert!(result.is_err());
6567    }
6568
6569    #[test]
6570    fn test_operation_count_limit() {
6571        // Use OP_NOP (0x61) - non-push opcodes count toward limit
6572        let script = vec![0x61; MAX_SCRIPT_OPS + 1]; // Exceed operation limit
6573        let mut stack = Vec::new();
6574        let result = eval_script(&script, &mut stack, 0, SigVersion::Base);
6575        assert!(result.is_err());
6576    }
6577
6578    #[test]
6579    fn test_stack_underflow_multiple_ops() {
6580        let script = vec![0x51, 0x87, 0x87]; // OP_1, OP_EQUAL, OP_EQUAL (second OP_EQUAL will underflow)
6581        let mut stack = Vec::new();
6582        let result = eval_script(&script, &mut stack, 0, SigVersion::Base);
6583        assert!(result.is_err(), "Stack underflow should return error");
6584        if let Err(crate::error::ConsensusError::ScriptErrorWithCode { code, .. }) = result {
6585            assert_eq!(
6586                code,
6587                crate::error::ScriptErrorCode::InvalidStackOperation,
6588                "Should return InvalidStackOperation"
6589            );
6590        }
6591    }
6592
6593    #[test]
6594    fn test_final_stack_empty() {
6595        let script = vec![0x51, 0x52]; // OP_1, OP_2 (two items on final stack)
6596        let mut stack = Vec::new();
6597        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6598        assert!(!result);
6599    }
6600
6601    #[test]
6602    fn test_final_stack_false() {
6603        let script = vec![OP_0]; // OP_0 (false on final stack)
6604        let mut stack = Vec::new();
6605        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6606        assert!(!result);
6607    }
6608
6609    #[test]
6610    fn test_verify_script_with_witness() {
6611        let script_sig = vec![OP_1]; // OP_1
6612        let script_pubkey = vec![OP_1]; // OP_1
6613        let witness = vec![OP_1]; // OP_1
6614        let flags = 0;
6615
6616        let result = verify_script(&script_sig, &script_pubkey, Some(&witness), flags).unwrap();
6617        assert!(!result); // Final stack has 2 items [1, 1], not exactly 1
6618    }
6619
6620    #[test]
6621    fn test_verify_script_failure() {
6622        let script_sig = vec![OP_1]; // OP_1
6623        let script_pubkey = vec![OP_2]; // OP_2
6624        let witness = None;
6625        let flags = 0;
6626
6627        let result = verify_script(&script_sig, &script_pubkey, witness, flags).unwrap();
6628        assert!(!result);
6629    }
6630
6631    // ============================================================================
6632    // COMPREHENSIVE SCRIPT TESTS
6633    // ============================================================================
6634
6635    #[test]
6636    fn test_op_ifdup_true() {
6637        let script = vec![OP_1, OP_IFDUP]; // OP_1, OP_IFDUP
6638        let mut stack = Vec::new();
6639        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6640        assert!(!result); // Final stack has 2 items [1, 1], not exactly 1
6641        assert_eq!(stack.len(), 2);
6642        assert_eq!(stack[0].as_ref(), &[1]);
6643        assert_eq!(stack[1].as_ref(), &[1]);
6644    }
6645
6646    #[test]
6647    fn test_op_ifdup_false() {
6648        let script = vec![OP_0, OP_IFDUP]; // OP_0, OP_IFDUP
6649        let mut stack = Vec::new();
6650        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6651        assert!(!result); // Final stack has 1 item [0], which is false
6652        assert_eq!(stack.len(), 1);
6653        assert_eq!(stack[0].as_ref(), &[] as &[u8]);
6654    }
6655
6656    #[test]
6657    fn test_op_depth() {
6658        let script = vec![OP_1, OP_1, OP_DEPTH]; // OP_1, OP_1, OP_DEPTH
6659        let mut stack = Vec::new();
6660        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6661        assert!(!result); // Final stack has 3 items, not exactly 1
6662        assert_eq!(stack.len(), 3);
6663        assert_eq!(stack[2].as_ref(), &[2]); // Depth should be 2 (before OP_DEPTH)
6664    }
6665
6666    #[test]
6667    fn test_op_drop() {
6668        let script = vec![OP_1, OP_2, OP_DROP]; // OP_1, OP_2, OP_DROP
6669        let mut stack = Vec::new();
6670        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6671        assert!(result); // Final stack has 1 item [1]
6672        assert_eq!(stack.len(), 1);
6673        assert_eq!(stack[0].as_ref(), &[1]);
6674    }
6675
6676    #[test]
6677    fn test_op_drop_empty_stack() {
6678        let script = vec![OP_DROP]; // OP_DROP on empty stack
6679        let mut stack = Vec::new();
6680        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6681        assert!(!result);
6682        assert_eq!(stack.len(), 0);
6683    }
6684
6685    #[test]
6686    fn test_op_nip() {
6687        let script = vec![OP_1, OP_2, OP_NIP]; // OP_1, OP_2, OP_NIP
6688        let mut stack = Vec::new();
6689        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6690        assert!(result); // Final stack has 1 item [2]
6691        assert_eq!(stack.len(), 1);
6692        assert_eq!(stack[0].as_ref(), &[2]);
6693    }
6694
6695    #[test]
6696    fn test_op_nip_insufficient_stack() {
6697        let script = vec![OP_1, OP_NIP]; // OP_1, OP_NIP (only 1 item)
6698        let mut stack = Vec::new();
6699        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6700        assert!(!result);
6701        assert_eq!(stack.len(), 1);
6702    }
6703
6704    #[test]
6705    fn test_op_over() {
6706        let script = vec![OP_1, OP_2, OP_OVER]; // OP_1, OP_2, OP_OVER
6707        let mut stack = Vec::new();
6708        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6709        assert!(!result); // Final stack has 3 items [1, 2, 1], not exactly 1
6710        assert_eq!(stack.len(), 3);
6711        assert_eq!(stack[0].as_ref(), &[1]);
6712        assert_eq!(stack[1].as_ref(), &[2]);
6713        assert_eq!(stack[2].as_ref(), &[1]);
6714    }
6715
6716    #[test]
6717    fn test_op_over_insufficient_stack() {
6718        let script = vec![OP_1, OP_OVER]; // OP_1, OP_OVER (only 1 item)
6719        let mut stack = Vec::new();
6720        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6721        assert!(!result);
6722        assert_eq!(stack.len(), 1);
6723    }
6724
6725    #[test]
6726    fn test_op_pick() {
6727        let script = vec![OP_1, OP_2, OP_3, OP_1, OP_PICK]; // OP_1, OP_2, OP_3, OP_1, OP_PICK
6728        let mut stack = Vec::new();
6729        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6730        assert!(!result); // Final stack has 4 items [1, 2, 3, 2], not exactly 1
6731        assert_eq!(stack.len(), 4);
6732        assert_eq!(stack[3].as_ref(), &[2]); // Should pick index 1 (OP_2)
6733    }
6734
6735    #[test]
6736    fn test_op_pick_empty_n() {
6737        // OP_1, OP_0, OP_PICK: n=0 picks top item (duplicates it), stack [1,1]
6738        let script = vec![OP_1, OP_0, OP_PICK];
6739        let mut stack = Vec::new();
6740        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6741        assert!(!result); // Final stack has 2 items, not exactly 1
6742        assert_eq!(stack.len(), 2);
6743        assert_eq!(stack[1].as_ref(), &[1]); // Picked the top (OP_1 value)
6744    }
6745
6746    #[test]
6747    fn test_op_pick_invalid_index() {
6748        let script = vec![OP_1, OP_2, OP_PICK]; // OP_1, OP_2, OP_PICK (n=2, but only 1 item)
6749        let mut stack = Vec::new();
6750        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6751        assert!(!result);
6752        assert_eq!(stack.len(), 1);
6753    }
6754
6755    #[test]
6756    fn test_op_roll() {
6757        let script = vec![OP_1, OP_2, OP_3, OP_1, OP_ROLL]; // OP_1, OP_2, OP_3, OP_1, OP_ROLL
6758        let mut stack = Vec::new();
6759        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6760        assert!(!result); // Final stack has 3 items [1, 3, 2], not exactly 1
6761        assert_eq!(stack.len(), 3);
6762        assert_eq!(stack[0].as_ref(), &[1]);
6763        assert_eq!(stack[1].as_ref(), &[3]);
6764        assert_eq!(stack[2].as_ref(), &[2]); // Should roll index 1 (OP_2) to top
6765    }
6766
6767    #[test]
6768    fn test_op_roll_zero_n() {
6769        // OP_0 pushes empty bytes (CScriptNum 0), OP_ROLL(0) is a valid no-op
6770        let script = vec![OP_1, OP_0, OP_ROLL]; // OP_1, OP_0, OP_ROLL (n=0, no-op)
6771        let mut stack = Vec::new();
6772        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6773        assert!(result); // Stack has [1], which is truthy
6774        assert_eq!(stack.len(), 1);
6775        assert_eq!(stack[0].as_ref(), &[1]);
6776    }
6777
6778    #[test]
6779    fn test_op_roll_invalid_index() {
6780        let script = vec![OP_1, OP_2, OP_ROLL]; // OP_1, OP_2, OP_ROLL (n=2, but only 1 item)
6781        let mut stack = Vec::new();
6782        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6783        assert!(!result);
6784        assert_eq!(stack.len(), 1);
6785    }
6786
6787    #[test]
6788    fn test_op_rot() {
6789        let script = vec![OP_1, OP_2, OP_3, OP_ROT]; // OP_1, OP_2, OP_3, OP_ROT
6790        let mut stack = Vec::new();
6791        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6792        assert!(!result); // Final stack has 3 items [2, 3, 1], not exactly 1
6793        assert_eq!(stack.len(), 3);
6794        assert_eq!(stack[0].as_ref(), &[2]);
6795        assert_eq!(stack[1].as_ref(), &[3]);
6796        assert_eq!(stack[2].as_ref(), &[1]);
6797    }
6798
6799    #[test]
6800    fn test_op_rot_insufficient_stack() {
6801        let script = vec![OP_1, OP_2, OP_ROT]; // OP_1, OP_2, OP_ROT (only 2 items)
6802        let mut stack = Vec::new();
6803        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6804        assert!(!result);
6805        assert_eq!(stack.len(), 2);
6806    }
6807
6808    #[test]
6809    fn test_op_swap() {
6810        let script = vec![OP_1, OP_2, OP_SWAP]; // OP_1, OP_2, OP_SWAP
6811        let mut stack = Vec::new();
6812        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6813        assert!(!result); // Final stack has 2 items [2, 1], not exactly 1
6814        assert_eq!(stack.len(), 2);
6815        assert_eq!(stack[0].as_ref(), &[2]);
6816        assert_eq!(stack[1].as_ref(), &[1]);
6817    }
6818
6819    #[test]
6820    fn test_op_swap_insufficient_stack() {
6821        let script = vec![OP_1, OP_SWAP]; // OP_1, OP_SWAP (only 1 item)
6822        let mut stack = Vec::new();
6823        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6824        assert!(!result);
6825        assert_eq!(stack.len(), 1);
6826    }
6827
6828    #[test]
6829    fn test_op_tuck() {
6830        let script = vec![OP_1, OP_2, OP_TUCK]; // OP_1, OP_2, OP_TUCK
6831        let mut stack = Vec::new();
6832        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6833        assert!(!result); // Final stack has 3 items [2, 1, 2], not exactly 1
6834        assert_eq!(stack.len(), 3);
6835        assert_eq!(stack[0].as_ref(), &[2]);
6836        assert_eq!(stack[1].as_ref(), &[1]);
6837        assert_eq!(stack[2].as_ref(), &[2]);
6838    }
6839
6840    #[test]
6841    fn test_op_tuck_insufficient_stack() {
6842        let script = vec![OP_1, OP_TUCK]; // OP_1, OP_TUCK (only 1 item)
6843        let mut stack = Vec::new();
6844        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6845        assert!(!result);
6846        assert_eq!(stack.len(), 1);
6847    }
6848
6849    #[test]
6850    fn test_op_2drop() {
6851        let script = vec![OP_1, OP_2, OP_3, OP_2DROP]; // OP_1, OP_2, OP_3, OP_2DROP
6852        let mut stack = Vec::new();
6853        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6854        assert!(result); // Final stack has 1 item [1]
6855        assert_eq!(stack.len(), 1);
6856        assert_eq!(stack[0].as_ref(), &[1]);
6857    }
6858
6859    #[test]
6860    fn test_op_2drop_insufficient_stack() {
6861        let script = vec![OP_1, OP_2DROP]; // OP_1, OP_2DROP (only 1 item)
6862        let mut stack = Vec::new();
6863        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6864        assert!(!result);
6865        assert_eq!(stack.len(), 1);
6866    }
6867
6868    #[test]
6869    fn test_op_2dup() {
6870        let script = vec![OP_1, OP_2, OP_2DUP]; // OP_1, OP_2, OP_2DUP
6871        let mut stack = Vec::new();
6872        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6873        assert!(!result); // Final stack has 4 items [1, 2, 1, 2], not exactly 1
6874        assert_eq!(stack.len(), 4);
6875        assert_eq!(stack[0].as_ref(), &[1]);
6876        assert_eq!(stack[1].as_ref(), &[2]);
6877        assert_eq!(stack[2].as_ref(), &[1]);
6878        assert_eq!(stack[3].as_ref(), &[2]);
6879    }
6880
6881    #[test]
6882    fn test_op_2dup_insufficient_stack() {
6883        let script = vec![OP_1, OP_2DUP]; // OP_1, OP_2DUP (only 1 item)
6884        let mut stack = Vec::new();
6885        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6886        assert!(!result);
6887        assert_eq!(stack.len(), 1);
6888    }
6889
6890    #[test]
6891    fn test_op_3dup() {
6892        let script = vec![OP_1, OP_2, OP_3, OP_3DUP]; // OP_1, OP_2, OP_3, OP_3DUP
6893        let mut stack = Vec::new();
6894        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6895        assert!(!result); // Final stack has 6 items, not exactly 1
6896        assert_eq!(stack.len(), 6);
6897        assert_eq!(stack[0].as_ref(), &[1]);
6898        assert_eq!(stack[1].as_ref(), &[2]);
6899        assert_eq!(stack[2].as_ref(), &[3]);
6900        assert_eq!(stack[3].as_ref(), &[1]);
6901        assert_eq!(stack[4].as_ref(), &[2]);
6902        assert_eq!(stack[5].as_ref(), &[3]);
6903    }
6904
6905    #[test]
6906    fn test_op_3dup_insufficient_stack() {
6907        let script = vec![OP_1, OP_2, OP_3DUP]; // OP_1, OP_2, OP_3DUP (only 2 items)
6908        let mut stack = Vec::new();
6909        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6910        assert!(!result);
6911        assert_eq!(stack.len(), 2);
6912    }
6913
6914    #[test]
6915    fn test_op_2over() {
6916        let script = vec![OP_1, OP_2, OP_3, OP_4, OP_2OVER]; // OP_1, OP_2, OP_3, OP_4, OP_2OVER
6917        let mut stack = Vec::new();
6918        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6919        assert!(!result); // Final stack has 6 items, not exactly 1
6920        assert_eq!(stack.len(), 6);
6921        assert_eq!(stack[4].as_ref(), &[1]); // Should copy second pair
6922        assert_eq!(stack[5].as_ref(), &[2]);
6923    }
6924
6925    #[test]
6926    fn test_op_2over_insufficient_stack() {
6927        let script = vec![OP_1, OP_2, OP_3, OP_2OVER]; // OP_1, OP_2, OP_3, OP_2OVER (only 3 items)
6928        let mut stack = Vec::new();
6929        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6930        assert!(!result);
6931        assert_eq!(stack.len(), 3);
6932    }
6933
6934    #[test]
6935    fn test_op_2rot() {
6936        let script = vec![OP_1, OP_2, OP_3, OP_4, OP_5, OP_6, OP_2ROT]; // 6 items, OP_2ROT
6937        let mut stack = Vec::new();
6938        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6939        assert!(!result); // Final stack has 6 items, not exactly 1
6940        assert_eq!(stack.len(), 6);
6941        assert_eq!(stack[4].as_ref(), &[2]); // Should rotate second pair to top
6942        assert_eq!(stack[5].as_ref(), &[1]);
6943    }
6944
6945    #[test]
6946    fn test_op_2rot_insufficient_stack() {
6947        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)
6948        let mut stack = Vec::new();
6949        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6950        assert!(!result);
6951        assert_eq!(stack.len(), 4);
6952    }
6953
6954    #[test]
6955    fn test_op_2swap() {
6956        let script = vec![OP_1, OP_2, OP_3, OP_4, OP_2SWAP]; // OP_1, OP_2, OP_3, OP_4, OP_2SWAP
6957        let mut stack = Vec::new();
6958        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6959        assert!(!result); // Final stack has 4 items, not exactly 1
6960        assert_eq!(stack.len(), 4);
6961        assert_eq!(stack[0].as_ref(), &[3]); // Should swap second pair
6962        assert_eq!(stack[1].as_ref(), &[4]);
6963        assert_eq!(stack[2].as_ref(), &[1]);
6964        assert_eq!(stack[3].as_ref(), &[2]);
6965    }
6966
6967    #[test]
6968    fn test_op_2swap_insufficient_stack() {
6969        let script = vec![OP_1, OP_2, OP_3, OP_2SWAP]; // OP_1, OP_2, OP_3, OP_2SWAP (only 3 items)
6970        let mut stack = Vec::new();
6971        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6972        assert!(!result);
6973        assert_eq!(stack.len(), 3);
6974    }
6975
6976    #[test]
6977    fn test_op_size() {
6978        let script = vec![OP_1, OP_SIZE]; // OP_1, OP_SIZE
6979        let mut stack = Vec::new();
6980        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6981        assert!(!result); // Final stack has 2 items [1, 1], not exactly 1
6982        assert_eq!(stack.len(), 2);
6983        assert_eq!(stack[0].as_ref(), &[1]);
6984        assert_eq!(stack[1].as_ref(), &[1]); // Size of [1] is 1
6985    }
6986
6987    #[test]
6988    fn test_op_size_empty_stack() {
6989        let script = vec![OP_SIZE]; // OP_SIZE on empty stack
6990        let mut stack = Vec::new();
6991        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
6992        assert!(!result);
6993        assert_eq!(stack.len(), 0);
6994    }
6995
6996    #[test]
6997    fn test_op_return() {
6998        let script = vec![OP_1, OP_RETURN]; // OP_1, OP_RETURN
6999        let mut stack = Vec::new();
7000        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
7001        assert!(!result); // OP_RETURN always fails
7002        assert_eq!(stack.len(), 1);
7003    }
7004
7005    #[test]
7006    fn test_op_checksigverify() {
7007        let script = vec![OP_1, OP_2, OP_CHECKSIGVERIFY]; // OP_1, OP_2, OP_CHECKSIGVERIFY
7008        let mut stack = Vec::new();
7009        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
7010        assert!(!result); // Should fail due to invalid signature
7011        assert_eq!(stack.len(), 0);
7012    }
7013
7014    #[test]
7015    fn test_op_checksigverify_insufficient_stack() {
7016        let script = vec![OP_1, OP_CHECKSIGVERIFY]; // OP_1, OP_CHECKSIGVERIFY (only 1 item)
7017        let mut stack = Vec::new();
7018        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
7019        assert!(!result);
7020        assert_eq!(stack.len(), 1);
7021    }
7022
7023    #[test]
7024    fn test_unknown_opcode_comprehensive() {
7025        let script = vec![OP_1, 0xff]; // OP_1, unknown opcode (0xff is not a defined opcode constant)
7026        let mut stack = Vec::new();
7027        let result = eval_script(&script, &mut stack, 0, SigVersion::Base).unwrap();
7028        assert!(!result); // Unknown opcode should fail
7029        assert_eq!(stack.len(), 1);
7030    }
7031
7032    #[test]
7033    fn test_verify_signature_invalid_pubkey() {
7034        let secp = signature::new_secp();
7035        let invalid_pubkey = vec![0x00]; // Invalid pubkey
7036        let signature = vec![0x30, 0x06, 0x02, 0x01, 0x00, 0x02, 0x01, 0x00]; // Valid DER signature
7037        let dummy_hash = [0u8; 32];
7038        let result = signature::verify_signature(
7039            &secp,
7040            &invalid_pubkey,
7041            &signature,
7042            &dummy_hash,
7043            0,
7044            0,
7045            crate::types::Network::Regtest,
7046            SigVersion::Base,
7047        );
7048        assert!(!result.unwrap_or(false));
7049    }
7050
7051    #[test]
7052    fn test_verify_signature_invalid_signature() {
7053        let secp = signature::new_secp();
7054        let pubkey = vec![
7055            0x02, 0x79, 0xbe, 0x66, 0x7e, 0xf9, 0xdc, 0xbb, 0xac, 0x55, 0xa0, 0x62, 0x95, 0xce,
7056            0x87, 0x0b, 0x07, 0x02, 0x9b, 0xfc, 0xdb, 0x2d, 0xce, 0x28, 0xd9, 0x59, 0xf2, 0x81,
7057            0x5b, 0x16, 0xf8, 0x17, 0x98,
7058        ]; // Valid pubkey
7059        let invalid_signature = vec![0x00]; // Invalid signature
7060        let dummy_hash = [0u8; 32];
7061        let result = signature::verify_signature(
7062            &secp,
7063            &pubkey,
7064            &invalid_signature,
7065            &dummy_hash,
7066            0,
7067            0,
7068            crate::types::Network::Regtest,
7069            SigVersion::Base,
7070        );
7071        assert!(!result.unwrap_or(false));
7072    }
7073
7074    // ============================================================================
7075    // Fast-path and verify_script_with_context_full tests
7076    // ============================================================================
7077
7078    /// Build a minimal transaction and prevout slices for verify_script_with_context_full.
7079    fn minimal_tx_and_prevouts(
7080        script_sig: &[u8],
7081        script_pubkey: &[u8],
7082    ) -> (
7083        crate::types::Transaction,
7084        Vec<i64>,
7085        Vec<crate::types::ByteString>,
7086    ) {
7087        use crate::types::{OutPoint, Transaction, TransactionInput, TransactionOutput};
7088        let tx = Transaction {
7089            version: 1,
7090            inputs: vec![TransactionInput {
7091                prevout: OutPoint {
7092                    hash: [0u8; 32],
7093                    index: 0,
7094                },
7095                sequence: 0xffff_ffff,
7096                script_sig: script_sig.to_vec(),
7097            }]
7098            .into(),
7099            outputs: vec![TransactionOutput {
7100                value: 0,
7101                script_pubkey: script_pubkey.to_vec(),
7102            }]
7103            .into(),
7104            lock_time: 0,
7105        };
7106        let prevout_values = vec![0i64];
7107        let prevout_script_pubkeys_vec = vec![script_pubkey.to_vec()];
7108        let prevout_script_pubkeys: Vec<&ByteString> = prevout_script_pubkeys_vec.iter().collect();
7109        (tx, prevout_values, prevout_script_pubkeys_vec)
7110    }
7111
7112    #[test]
7113    fn test_verify_with_context_p2pkh_hash_mismatch() {
7114        // P2PKH pattern but pubkey hash does not match script_pubkey -> false (fast-path or interpreter).
7115        let pubkey = vec![0x02u8; 33]; // dummy compressed pubkey
7116        let sig = vec![0x30u8; 70]; // dummy sig (with sighash byte)
7117        let mut script_sig = Vec::new();
7118        script_sig.push(sig.len() as u8);
7119        script_sig.extend(&sig);
7120        script_sig.push(pubkey.len() as u8);
7121        script_sig.extend(&pubkey);
7122
7123        let mut script_pubkey = vec![OP_DUP, OP_HASH160, PUSH_20_BYTES];
7124        script_pubkey.extend(&[0u8; 20]); // wrong hash (not HASH160(pubkey))
7125        script_pubkey.push(OP_EQUALVERIFY);
7126        script_pubkey.push(OP_CHECKSIG);
7127
7128        let (tx, pv, psp) = minimal_tx_and_prevouts(&script_sig, &script_pubkey);
7129        let psp_refs: Vec<&[u8]> = psp.iter().map(|b| b.as_ref()).collect();
7130        let result = verify_script_with_context_full(
7131            &script_sig,
7132            &script_pubkey,
7133            None,
7134            0,
7135            &tx,
7136            0,
7137            &pv,
7138            &psp_refs,
7139            Some(500_000),
7140            None,
7141            crate::types::Network::Mainnet,
7142            SigVersion::Base,
7143            #[cfg(feature = "production")]
7144            None,
7145            None, // precomputed_bip143
7146            #[cfg(feature = "production")]
7147            None,
7148            #[cfg(feature = "production")]
7149            None,
7150            #[cfg(feature = "production")]
7151            None,
7152        );
7153        assert!(result.is_ok());
7154        assert!(!result.unwrap());
7155    }
7156
7157    #[test]
7158    fn test_verify_with_context_p2sh_hash_mismatch() {
7159        // P2SH pattern but redeem script hash does not match -> false.
7160        let redeem = vec![OP_1, OP_1, OP_ADD]; // minimal redeem
7161        let mut script_sig = Vec::new();
7162        script_sig.push(redeem.len() as u8);
7163        script_sig.extend(&redeem);
7164
7165        let mut script_pubkey = vec![OP_HASH160, PUSH_20_BYTES];
7166        script_pubkey.extend(&[0u8; 20]); // wrong hash (not HASH160(redeem))
7167        script_pubkey.push(OP_EQUAL);
7168
7169        let (tx, pv, psp) = minimal_tx_and_prevouts(&script_sig, &script_pubkey);
7170        let psp_refs: Vec<&[u8]> = psp.iter().map(|b| b.as_ref()).collect();
7171        let result = verify_script_with_context_full(
7172            &script_sig,
7173            &script_pubkey,
7174            None,
7175            0x01, // P2SH
7176            &tx,
7177            0,
7178            &pv,
7179            &psp_refs,
7180            Some(500_000),
7181            None,
7182            crate::types::Network::Mainnet,
7183            SigVersion::Base,
7184            #[cfg(feature = "production")]
7185            None,
7186            None, // precomputed_bip143
7187            #[cfg(feature = "production")]
7188            None,
7189            #[cfg(feature = "production")]
7190            None,
7191            #[cfg(feature = "production")]
7192            None,
7193        );
7194        assert!(result.is_ok());
7195        assert!(!result.unwrap());
7196    }
7197
7198    #[test]
7199    fn test_verify_with_context_p2wpkh_wrong_witness_size() {
7200        // P2WPKH script_pubkey but witness has 1 element (need 2) -> false.
7201        let mut script_pubkey = vec![OP_0, PUSH_20_BYTES];
7202        script_pubkey.extend(&[0u8; 20]);
7203        let witness: Vec<Vec<u8>> = vec![vec![0x30; 70]]; // only sig, no pubkey
7204        let (tx, pv, psp) = minimal_tx_and_prevouts(&[], &script_pubkey);
7205        let psp_refs: Vec<&[u8]> = psp.iter().map(|b| b.as_ref()).collect();
7206        let empty: Vec<u8> = vec![];
7207        let result = verify_script_with_context_full(
7208            &empty,
7209            &script_pubkey,
7210            Some(&witness),
7211            0,
7212            &tx,
7213            0,
7214            &pv,
7215            &psp_refs,
7216            Some(500_000),
7217            None,
7218            crate::types::Network::Mainnet,
7219            SigVersion::Base,
7220            #[cfg(feature = "production")]
7221            None,
7222            None, // precomputed_bip143
7223            #[cfg(feature = "production")]
7224            None,
7225            #[cfg(feature = "production")]
7226            None,
7227            #[cfg(feature = "production")]
7228            None,
7229        );
7230        assert!(result.is_ok());
7231        assert!(!result.unwrap());
7232    }
7233
7234    #[test]
7235    fn test_verify_with_context_p2wsh_wrong_witness_script_hash() {
7236        // P2WSH script_pubkey but SHA256(witness_script) != program -> false.
7237        let witness_script = vec![OP_1];
7238        let mut script_pubkey = vec![OP_0, PUSH_32_BYTES];
7239        script_pubkey.extend(&[0u8; 32]); // wrong hash (not SHA256(witness_script))
7240        let witness: Vec<Vec<u8>> = vec![witness_script];
7241        let (tx, pv, psp) = minimal_tx_and_prevouts(&[], &script_pubkey);
7242        let psp_refs: Vec<&[u8]> = psp.iter().map(|b| b.as_ref()).collect();
7243        let empty: Vec<u8> = vec![];
7244        let result = verify_script_with_context_full(
7245            &empty,
7246            &script_pubkey,
7247            Some(&witness),
7248            0,
7249            &tx,
7250            0,
7251            &pv,
7252            &psp_refs,
7253            Some(500_000),
7254            None,
7255            crate::types::Network::Mainnet,
7256            SigVersion::Base,
7257            #[cfg(feature = "production")]
7258            None,
7259            None, // precomputed_bip143
7260            #[cfg(feature = "production")]
7261            None,
7262            #[cfg(feature = "production")]
7263            None,
7264            #[cfg(feature = "production")]
7265            None,
7266        );
7267        assert!(result.is_ok());
7268        assert!(!result.unwrap());
7269    }
7270
7271    #[test]
7272    #[cfg(feature = "production")]
7273    fn test_p2wsh_multisig_fast_path() {
7274        // P2WSH 2-of-2 multisig: fast path parses and validates; placeholder sigs fail -> Ok(false).
7275        use crate::constants::BIP147_ACTIVATION_MAINNET;
7276        use crate::crypto::OptimizedSha256;
7277
7278        let pk1 = [0x02u8; 33];
7279        let pk2 = [0x03u8; 33];
7280        let mut witness_script = vec![0x52]; // OP_2
7281        witness_script.extend_from_slice(&pk1);
7282        witness_script.extend_from_slice(&pk2);
7283        witness_script.push(0x52); // OP_2
7284        witness_script.push(0xae); // OP_CHECKMULTISIG
7285
7286        let wsh_hash = OptimizedSha256::new().hash(&witness_script);
7287        let mut script_pubkey = vec![OP_0, PUSH_32_BYTES];
7288        script_pubkey.extend_from_slice(&wsh_hash);
7289
7290        let witness: Vec<Vec<u8>> = vec![
7291            vec![0x00],       // NULLDUMMY
7292            vec![0x30u8; 72], // placeholder sig 1
7293            vec![0x30u8; 72], // placeholder sig 2
7294            witness_script.clone(),
7295        ];
7296
7297        let (tx, pv, psp) = minimal_tx_and_prevouts(&[], &script_pubkey);
7298        let psp_refs: Vec<&[u8]> = psp.iter().map(|b| b.as_ref()).collect();
7299        let empty: Vec<u8> = vec![];
7300        let result = verify_script_with_context_full(
7301            &empty,
7302            &script_pubkey,
7303            Some(&witness),
7304            0x810, // SIGHASH_ALL | VERIFY_NULLDUMMY | VERIFY_NULLFAIL
7305            &tx,
7306            0,
7307            &pv,
7308            &psp_refs,
7309            Some(BIP147_ACTIVATION_MAINNET + 1),
7310            None,
7311            crate::types::Network::Mainnet,
7312            SigVersion::Base,
7313            #[cfg(feature = "production")]
7314            None,
7315            None, // precomputed_bip143
7316            #[cfg(feature = "production")]
7317            None,
7318            #[cfg(feature = "production")]
7319            None,
7320            #[cfg(feature = "production")]
7321            None,
7322        );
7323        assert!(result.is_ok());
7324        assert!(!result.unwrap());
7325    }
7326}
7327
7328#[cfg(test)]
7329#[allow(unused_doc_comments)]
7330mod property_tests {
7331    use super::*;
7332    use proptest::prelude::*;
7333
7334    /// Property test: eval_script respects operation limits
7335    ///
7336    /// Mathematical specification:
7337    /// ∀ script ∈ ByteString: |script| > MAX_SCRIPT_OPS ⟹ eval_script fails
7338    proptest! {
7339        #[test]
7340        fn prop_eval_script_operation_limit(script in prop::collection::vec(any::<u8>(), 0..300)) {
7341            let mut stack = Vec::new();
7342            let flags = 0u32;
7343
7344            let result = eval_script(&script, &mut stack, flags, SigVersion::Base);
7345
7346            // Note: Production code tracks op_count precisely (number of non-push opcodes executed).
7347            // Script length can be larger than op_count if there are data pushes.
7348            // For a script with only opcodes (no data pushes), length = op_count.
7349            // So scripts with length > MAX_SCRIPT_OPS that are all opcodes will fail.
7350            // But scripts with data pushes might have length > MAX_SCRIPT_OPS but op_count <= MAX_SCRIPT_OPS.
7351            // This property test verifies that very long scripts (> MAX_SCRIPT_OPS * 2) eventually fail
7352            // or that the operation limit is respected.
7353            if script.len() > MAX_SCRIPT_OPS * 2 {
7354                // Very long scripts should fail (either op limit or other reasons)
7355                // This is a weak check but acceptable for property testing
7356                prop_assert!(result.is_err() || !result.unwrap(),
7357                    "Very long scripts should fail or return false");
7358            }
7359            // Otherwise, scripts may succeed or fail - both are acceptable
7360        }
7361    }
7362
7363    /// Property test: verify_script is deterministic
7364    ///
7365    /// Mathematical specification:
7366    /// ∀ inputs: verify_script(inputs) = verify_script(inputs)
7367    proptest! {
7368        #[test]
7369        fn prop_verify_script_deterministic(
7370            script_sig in prop::collection::vec(any::<u8>(), 0..20),
7371            script_pubkey in prop::collection::vec(any::<u8>(), 0..20),
7372            witness in prop::option::of(prop::collection::vec(any::<u8>(), 0..10)),
7373            flags in any::<u32>()
7374        ) {
7375            let result1 = verify_script(&script_sig, &script_pubkey, witness.as_ref(), flags);
7376            let result2 = verify_script(&script_sig, &script_pubkey, witness.as_ref(), flags);
7377
7378            assert_eq!(result1.is_ok(), result2.is_ok());
7379            if result1.is_ok() && result2.is_ok() {
7380                assert_eq!(result1.unwrap(), result2.unwrap());
7381            }
7382        }
7383    }
7384
7385    /// Property test: execute_opcode handles all opcodes without panicking
7386    ///
7387    /// Mathematical specification:
7388    /// ∀ opcode ∈ {0..255}, stack ∈ Vec<StackElement>: execute_opcode(opcode, stack) ∈ {true, false}
7389    proptest! {
7390        #[test]
7391        fn prop_execute_opcode_no_panic(
7392            opcode in any::<u8>(),
7393            stack_items in prop::collection::vec(
7394                prop::collection::vec(any::<u8>(), 0..5),
7395                0..10
7396            ),
7397            flags in any::<u32>()
7398        ) {
7399            let mut stack: Vec<StackElement> = stack_items.into_iter().map(|v| to_stack_element(&v)).collect();
7400            let result = execute_opcode(opcode, &mut stack, flags, SigVersion::Base);
7401
7402            // Some opcodes may return errors (invalid opcodes, insufficient stack, etc.)
7403            // The important thing is that it doesn't panic
7404            match result {
7405                Ok(success) => {
7406                    // Just test it returns a boolean (success is either true or false)
7407                    let _ = success;
7408                },
7409                Err(_) => {
7410                    // Errors are acceptable - invalid opcodes, insufficient stack, etc.
7411                    // The test is about not panicking, not about always succeeding
7412                }
7413            }
7414
7415            // Stack should remain within bounds
7416            assert!(stack.len() <= MAX_STACK_SIZE);
7417        }
7418    }
7419
7420    /// Property test: stack operations preserve bounds
7421    ///
7422    /// Mathematical specification:
7423    /// ∀ opcode ∈ {0..255}, stack ∈ Vec<StackElement>:
7424    /// - |stack| ≤ MAX_STACK_SIZE before and after execute_opcode
7425    /// - Stack operations are well-defined
7426    proptest! {
7427        #[test]
7428        fn prop_stack_operations_bounds(
7429            opcode in any::<u8>(),
7430            stack_items in prop::collection::vec(
7431                prop::collection::vec(any::<u8>(), 0..3),
7432                0..5
7433            ),
7434            flags in any::<u32>()
7435        ) {
7436            let mut stack: Vec<StackElement> = stack_items.into_iter().map(|v| to_stack_element(&v)).collect();
7437            let initial_len = stack.len();
7438
7439            let result = execute_opcode(opcode, &mut stack, flags, SigVersion::Base);
7440
7441            // Stack should never exceed MAX_STACK_SIZE
7442            assert!(stack.len() <= MAX_STACK_SIZE);
7443
7444            // If operation succeeded, stack should be in valid state
7445            if result.is_ok() && result.unwrap() {
7446                // For opcodes that modify stack size, verify reasonable bounds
7447                match opcode {
7448                    OP_0 | OP_1..=OP_16 => {
7449                        // Push opcodes - increase by 1
7450                        assert!(stack.len() == initial_len + 1);
7451                    },
7452                    OP_DUP => {
7453                        // OP_DUP - increase by 1
7454                        if initial_len > 0 {
7455                            assert!(stack.len() == initial_len + 1);
7456                        }
7457                    },
7458                    OP_3DUP => {
7459                        // OP_3DUP - increases by 3 if stack has >= 3 items
7460                        if initial_len >= 3 {
7461                            assert!(stack.len() == initial_len + 3);
7462                        }
7463                    },
7464                    OP_2OVER => {
7465                        // OP_2OVER - increases by 2 if stack has >= 4 items
7466                        if initial_len >= 4 {
7467                            assert!(stack.len() == initial_len + 2);
7468                        }
7469                    },
7470                    OP_DROP | OP_NIP | OP_2DROP => {
7471                        // These opcodes decrease stack size
7472                        assert!(stack.len() <= initial_len);
7473                    },
7474                    _ => {
7475                        // Other opcodes maintain or modify stack size reasonably
7476                        // Some opcodes can push multiple items, so allow up to +3
7477                        assert!(stack.len() <= initial_len + 3, "Stack size should be reasonable");
7478                    }
7479                }
7480            }
7481        }
7482    }
7483
7484    /// Property test: hash operations are deterministic
7485    ///
7486    /// Mathematical specification:
7487    /// ∀ input ∈ ByteString: OP_HASH160(input) = OP_HASH160(input)
7488    proptest! {
7489        #[test]
7490        fn prop_hash_operations_deterministic(
7491            input in prop::collection::vec(any::<u8>(), 0..10)
7492        ) {
7493            let elem = to_stack_element(&input);
7494            let mut stack1 = vec![elem.clone()];
7495            let mut stack2 = vec![elem];
7496
7497            let result1 = execute_opcode(0xa9, &mut stack1, 0, SigVersion::Base); // OP_HASH160
7498            let result2 = execute_opcode(0xa9, &mut stack2, 0, SigVersion::Base); // OP_HASH160
7499
7500            assert_eq!(result1.is_ok(), result2.is_ok());
7501            if let (Ok(val1), Ok(val2)) = (result1, result2) {
7502                assert_eq!(val1, val2);
7503                if val1 {
7504                    assert_eq!(stack1, stack2);
7505                }
7506            }
7507        }
7508    }
7509
7510    /// Property test: equality operations are symmetric
7511    ///
7512    /// Mathematical specification:
7513    /// ∀ a, b ∈ ByteString: OP_EQUAL(a, b) = OP_EQUAL(b, a)
7514    proptest! {
7515        #[test]
7516        fn prop_equality_operations_symmetric(
7517            a in prop::collection::vec(any::<u8>(), 0..5),
7518            b in prop::collection::vec(any::<u8>(), 0..5)
7519        ) {
7520            let mut stack1 = vec![to_stack_element(&a), to_stack_element(&b)];
7521            let mut stack2 = vec![to_stack_element(&b), to_stack_element(&a)];
7522
7523            let result1 = execute_opcode(0x87, &mut stack1, 0, SigVersion::Base); // OP_EQUAL
7524            let result2 = execute_opcode(0x87, &mut stack2, 0, SigVersion::Base); // OP_EQUAL
7525
7526            assert_eq!(result1.is_ok(), result2.is_ok());
7527            if let (Ok(val1), Ok(val2)) = (result1, result2) {
7528                assert_eq!(val1, val2);
7529                if val1 {
7530                    // Results should be identical (both true or both false)
7531                    assert_eq!(stack1.len(), stack2.len());
7532                    if !stack1.is_empty() && !stack2.is_empty() {
7533                        assert_eq!(stack1[0], stack2[0]);
7534                    }
7535                }
7536            }
7537        }
7538    }
7539
7540    /// Property test: script execution terminates
7541    ///
7542    /// Mathematical specification:
7543    /// ∀ script ∈ ByteString: eval_script(script) terminates (no infinite loops)
7544    proptest! {
7545        #[test]
7546        fn prop_script_execution_terminates(
7547            script in prop::collection::vec(any::<u8>(), 0..50)
7548        ) {
7549            let mut stack = Vec::new();
7550            let flags = 0u32;
7551
7552            // This should complete without hanging
7553            let result = eval_script(&script, &mut stack, flags, SigVersion::Base);
7554
7555            // Should return a result (success or failure)
7556            assert!(result.is_ok() || result.is_err());
7557
7558            // Stack should be in valid state
7559            assert!(stack.len() <= MAX_STACK_SIZE);
7560        }
7561    }
7562}