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