Skip to main content

blvm_consensus/script/
mod.rs

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