Skip to main content

blvm_consensus/script/
mod.rs

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