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