Skip to main content

blvm_consensus/script/
mod.rs

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