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