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