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