Skip to main content

bsv_rs/script/
spend.rs

1//! Bitcoin Script interpreter for spend validation.
2//!
3// Allow large error type - ScriptEvaluationError intentionally captures full
4// execution state for debugging failed script executions.
5#![allow(clippy::result_large_err)]
6//!
7//! This module implements the full Bitcoin Script interpreter for BSV, enabling
8//! validation of transaction spends by executing unlocking and locking scripts.
9//!
10//! # Example
11//!
12//! ```rust,ignore
13//! use bsv_rs::script::{Spend, LockingScript, UnlockingScript};
14//!
15//! let spend = Spend::new(SpendParams {
16//!     source_txid: [0u8; 32],
17//!     source_output_index: 0,
18//!     source_satoshis: 100_000,
19//!     locking_script: LockingScript::from_asm("OP_DUP OP_HASH160 ... OP_CHECKSIG")?,
20//!     transaction_version: 1,
21//!     other_inputs: vec![],
22//!     outputs: vec![],
23//!     input_index: 0,
24//!     unlocking_script: UnlockingScript::from_asm("<sig> <pubkey>")?,
25//!     input_sequence: 0xffffffff,
26//!     lock_time: 0,
27//!     memory_limit: None,
28//! });
29//!
30//! let valid = spend.validate()?;
31//! ```
32
33use super::evaluation_error::{
34    ExecutionContext, ScriptEvaluationError, ScriptResource, ScriptResourceLimit,
35};
36use super::op::*;
37use super::script_num::ScriptNum;
38use super::{LockingScript, Script, ScriptChunk, UnlockingScript};
39use crate::primitives::bsv::sighash::{
40    compute_sighash_for_signing, SighashParams, TxInput, TxOutput, SIGHASH_FORKID,
41};
42use crate::primitives::bsv::tx_signature::TransactionSignature;
43use crate::primitives::ec::PublicKey;
44use crate::primitives::{hash160, ripemd160, sha1, sha256, sha256d, to_hex, BigNumber};
45
46// ============================================================================
47// Configuration Constants
48// ============================================================================
49
50/// Maximum size of a single script element (1GB for BSV unlimited)
51const MAX_SCRIPT_ELEMENT_SIZE: usize = 1024 * 1024 * 1024;
52
53/// Default memory limit for stack usage (32MB)
54const DEFAULT_MEMORY_LIMIT: usize = 32_000_000;
55
56/// Maximum number of keys in a multisig (i32::MAX for BSV)
57const MAX_MULTISIG_KEY_COUNT: i64 = i32::MAX as i64;
58
59/// Require minimal push encoding
60const REQUIRE_MINIMAL_PUSH: bool = true;
61
62/// Require push-only unlocking scripts
63const REQUIRE_PUSH_ONLY_UNLOCKING: bool = true;
64
65/// Require low-S signatures
66const REQUIRE_LOW_S_SIGNATURES: bool = true;
67
68/// Require clean stack after execution
69const REQUIRE_CLEAN_STACK: bool = true;
70
71// ============================================================================
72// Pre-computed Script Numbers
73// ============================================================================
74
75lazy_static::lazy_static! {
76    /// Pre-computed script number for -1
77    static ref SCRIPTNUM_NEG_1: Vec<u8> = ScriptNum::to_bytes(&BigNumber::from_i64(-1));
78
79    /// Pre-computed script numbers for 0-16
80    static ref SCRIPTNUMS_0_TO_16: Vec<Vec<u8>> = (0..=16)
81        .map(|i| ScriptNum::to_bytes(&BigNumber::from_i64(i)))
82        .collect();
83}
84
85// ============================================================================
86// Spend Parameters
87// ============================================================================
88
89/// Parameters for constructing a Spend validator.
90pub struct SpendParams {
91    /// The transaction ID of the source UTXO (32 bytes, internal byte order).
92    pub source_txid: [u8; 32],
93    /// The index of the output in the source transaction.
94    pub source_output_index: u32,
95    /// The satoshi value of the source UTXO.
96    pub source_satoshis: u64,
97    /// The locking script of the source UTXO.
98    pub locking_script: LockingScript,
99    /// The version of the spending transaction.
100    pub transaction_version: i32,
101    /// Other inputs in the spending transaction (excluding this one).
102    pub other_inputs: Vec<TxInput>,
103    /// Outputs of the spending transaction.
104    pub outputs: Vec<TxOutput>,
105    /// The index of this input in the spending transaction.
106    pub input_index: usize,
107    /// The unlocking script for this spend.
108    pub unlocking_script: UnlockingScript,
109    /// The sequence number of this input.
110    pub input_sequence: u32,
111    /// The lock time of the spending transaction.
112    pub lock_time: u32,
113    /// Optional memory limit in bytes (default: 32MB).
114    pub memory_limit: Option<usize>,
115}
116
117// ============================================================================
118// Spend Struct
119// ============================================================================
120
121/// The Spend struct represents a spend action and validates it by executing
122/// the unlocking and locking scripts.
123pub struct Spend {
124    // Transaction context
125    source_txid: [u8; 32],
126    source_output_index: u32,
127    source_satoshis: u64,
128    locking_script: LockingScript,
129    transaction_version: i32,
130    other_inputs: Vec<TxInput>,
131    outputs: Vec<TxOutput>,
132    input_index: usize,
133    unlocking_script: UnlockingScript,
134    input_sequence: u32,
135    lock_time: u32,
136
137    // Execution state
138    context: ExecutionContext,
139    program_counter: usize,
140    last_code_separator: Option<usize>,
141    stack: Vec<Vec<u8>>,
142    alt_stack: Vec<Vec<u8>>,
143    if_stack: Vec<bool>,
144    memory_limit: usize,
145    stack_mem: usize,
146    alt_stack_mem: usize,
147    require_push_only: bool,
148    require_minimal: bool,
149    require_low_s: bool,
150    require_clean_stack: bool,
151
152    // Parsed-chunk caches. `Script::chunks()` deep-clones the whole chunk
153    // vector; calling it from `step()` made execution O(N²) in script size,
154    // which is prohibitive for large covenant scripts (a ~500 KB script
155    // would take hours). Cached once here; `step()` indexes the cache.
156    unlocking_chunks: Vec<crate::script::chunk::ScriptChunk>,
157    locking_chunks: Vec<crate::script::chunk::ScriptChunk>,
158}
159
160impl Spend {
161    /// Creates a new Spend validator from the given parameters.
162    pub fn new(params: SpendParams) -> Self {
163        let mut spend = Self {
164            source_txid: params.source_txid,
165            source_output_index: params.source_output_index,
166            source_satoshis: params.source_satoshis,
167            locking_script: params.locking_script,
168            transaction_version: params.transaction_version,
169            other_inputs: params.other_inputs,
170            outputs: params.outputs,
171            input_index: params.input_index,
172            unlocking_script: params.unlocking_script,
173            input_sequence: params.input_sequence,
174            lock_time: params.lock_time,
175            context: ExecutionContext::UnlockingScript,
176            program_counter: 0,
177            unlocking_chunks: Vec::new(),
178            locking_chunks: Vec::new(),
179            last_code_separator: None,
180            stack: Vec::new(),
181            alt_stack: Vec::new(),
182            if_stack: Vec::new(),
183            memory_limit: params.memory_limit.unwrap_or(DEFAULT_MEMORY_LIMIT),
184            stack_mem: 0,
185            alt_stack_mem: 0,
186            require_push_only: REQUIRE_PUSH_ONLY_UNLOCKING,
187            // ts-sdk parity: transactions with version > 1 run "relaxed"
188            // (post-Genesis semantics) — MINIMALDATA, LOW_S and CLEANSTACK are
189            // not enforced (mirrors ts-sdk Spend.isRelaxed()).
190            require_minimal: REQUIRE_MINIMAL_PUSH && params.transaction_version <= 1,
191            require_low_s: REQUIRE_LOW_S_SIGNATURES && params.transaction_version <= 1,
192            require_clean_stack: REQUIRE_CLEAN_STACK && params.transaction_version <= 1,
193        };
194        spend.unlocking_chunks = spend.unlocking_script.chunks();
195        spend.locking_chunks = spend.locking_script.chunks();
196        spend.reset();
197        spend
198    }
199
200    /// Overrides MINIMALDATA enforcement (script-number and push minimality).
201    ///
202    /// Default follows ts-sdk: enforced for version <= 1 transactions, relaxed
203    /// for version > 1 (post-Genesis semantics).
204    pub fn set_require_minimal(&mut self, require: bool) {
205        self.require_minimal = require;
206    }
207
208    /// Allows opting out of the push-only unlocking-script check.
209    ///
210    /// The TypeScript `@bsv/sdk` Spend engine does not enforce push-only
211    /// unlocking scripts; some OP_PUSH_TX-style covenant designs place
212    /// executable code in the unlocking script and verify under that engine.
213    /// Default remains `true` (enforced).
214    pub fn set_require_push_only(&mut self, require: bool) {
215        self.require_push_only = require;
216    }
217
218    /// Resets the interpreter state for re-execution.
219    pub fn reset(&mut self) {
220        self.context = ExecutionContext::UnlockingScript;
221        self.program_counter = 0;
222        self.last_code_separator = None;
223        self.stack.clear();
224        self.alt_stack.clear();
225        self.if_stack.clear();
226        self.stack_mem = 0;
227        self.alt_stack_mem = 0;
228    }
229
230    /// Validates the spend by executing both scripts.
231    ///
232    /// # Returns
233    ///
234    /// `Ok(true)` if the spend is valid, or an error describing why validation failed.
235    pub fn validate(&mut self) -> Result<bool, ScriptEvaluationError> {
236        // Check that unlocking script is push-only
237        if self.require_push_only && !self.unlocking_script.is_push_only() {
238            return Err(self.error(
239                "Unlocking scripts can only contain push operations, and no other opcodes.",
240            ));
241        }
242
243        // Execute both scripts
244        while self.step()? {
245            // Continue until script ends
246            if self.context == ExecutionContext::LockingScript
247                && self.program_counter >= self.locking_chunks.len()
248            {
249                break;
250            }
251        }
252
253        // Verify if_stack is empty (all conditionals closed)
254        if !self.if_stack.is_empty() {
255            return Err(self.error(
256                "Every OP_IF, OP_NOTIF, or OP_ELSE must be terminated with OP_ENDIF prior to the end of the script.",
257            ));
258        }
259
260        // Clean stack rule
261        if self.require_clean_stack && self.stack.len() != 1 {
262            return Err(self.error(&format!(
263                "The clean stack rule requires exactly one item to be on the stack after script execution, found {}.",
264                self.stack.len()
265            )));
266        }
267
268        // Top value must be truthy
269        if self.stack.is_empty() {
270            return Err(self.error(
271                "The top stack element must be truthy after script evaluation (stack is empty).",
272            ));
273        }
274
275        if !ScriptNum::cast_to_bool(&self.stack[self.stack.len() - 1]) {
276            return Err(self.error("The top stack element must be truthy after script evaluation."));
277        }
278
279        Ok(true)
280    }
281
282    /// Executes a single instruction (step).
283    ///
284    /// # Returns
285    ///
286    /// `Ok(true)` if execution should continue, `Ok(false)` if the script is complete.
287    pub fn step(&mut self) -> Result<bool, ScriptEvaluationError> {
288        // Check memory limits — a LOCAL budget, reported as a resource limit
289        // (the reference's `ScriptResourceLimitError`), never as a verdict on
290        // the script.
291        if self.stack_mem > self.memory_limit {
292            return Err(self.resource_error(ScriptResource::Stack, self.stack_mem));
293        }
294        if self.alt_stack_mem > self.memory_limit {
295            return Err(self.resource_error(ScriptResource::AltStack, self.alt_stack_mem));
296        }
297
298        // Switch from unlocking to locking script when unlocking is complete.
299        // ts-sdk parity: conditionals must be terminated, the alt stack is
300        // cleared, and the last code separator does not carry across scripts.
301        if self.context == ExecutionContext::UnlockingScript
302            && self.program_counter >= self.unlocking_chunks.len()
303        {
304            if !self.if_stack.is_empty() {
305                return Err(self.error(
306                    "Every OP_IF, OP_NOTIF, or OP_ELSE must be terminated with OP_ENDIF prior to the end of the unlocking script.",
307                ));
308            }
309            self.alt_stack.clear();
310            self.alt_stack_mem = 0;
311            self.last_code_separator = None;
312            self.context = ExecutionContext::LockingScript;
313            self.program_counter = 0;
314        }
315
316        // Get current script and check if we're done (cached chunks: the
317        // per-step deep clone of the whole script was O(N²) — see field docs)
318        let current_len = match self.context {
319            ExecutionContext::UnlockingScript => self.unlocking_chunks.len(),
320            ExecutionContext::LockingScript => self.locking_chunks.len(),
321        };
322
323        if self.program_counter >= current_len {
324            return Ok(false);
325        }
326
327        let op_owned = match self.context {
328            ExecutionContext::UnlockingScript => {
329                self.unlocking_chunks[self.program_counter].clone()
330            }
331            ExecutionContext::LockingScript => self.locking_chunks[self.program_counter].clone(),
332        };
333        let operation = &op_owned;
334        let current_opcode = operation.op;
335
336        // Check for oversized data push
337        if let Some(ref data) = operation.data {
338            if data.len() > MAX_SCRIPT_ELEMENT_SIZE {
339                return Err(self.error(&format!(
340                    "Data push > {} bytes (pc={})",
341                    MAX_SCRIPT_ELEMENT_SIZE, self.program_counter
342                )));
343            }
344        }
345
346        // Determine if we're currently executing (not in a false conditional branch)
347        let is_executing = !self.if_stack.contains(&false);
348
349        // Check for disabled opcodes when executing
350        if is_executing && is_opcode_disabled(current_opcode) {
351            return Err(self.error(&format!(
352                "This opcode is currently disabled. (Opcode: {}, PC: {})",
353                opcode_to_name(current_opcode).unwrap_or("UNKNOWN"),
354                self.program_counter
355            )));
356        }
357
358        // Execute opcode
359        if is_executing && current_opcode <= OP_PUSHDATA4 {
360            // Push data operations
361            if self.require_minimal && !is_chunk_minimal_push(operation) {
362                return Err(self.error(&format!(
363                    "This data is not minimally-encoded. (PC: {})",
364                    self.program_counter
365                )));
366            }
367            let data = operation.data.clone().unwrap_or_default();
368            self.push_stack(data)?;
369        } else if is_executing || (OP_IF..=OP_ENDIF).contains(&current_opcode) {
370            // Execute the opcode
371            self.execute_opcode(current_opcode, operation)?;
372        }
373
374        self.program_counter += 1;
375        Ok(true)
376    }
377
378    // ========================================================================
379    // Opcode Execution
380    // ========================================================================
381
382    fn execute_opcode(
383        &mut self,
384        opcode: u8,
385        chunk: &ScriptChunk,
386    ) -> Result<(), ScriptEvaluationError> {
387        let is_executing = !self.if_stack.contains(&false);
388
389        match opcode {
390            // ================================================================
391            // Push Operations (0x00-0x60)
392            // ================================================================
393            OP_1NEGATE => {
394                self.push_stack_copy(&SCRIPTNUM_NEG_1)?;
395            }
396            OP_0 => {
397                self.push_stack_copy(&SCRIPTNUMS_0_TO_16[0])?;
398            }
399            OP_1..=OP_16 => {
400                let n = (opcode - OP_1 + 1) as usize;
401                self.push_stack_copy(&SCRIPTNUMS_0_TO_16[n])?;
402            }
403
404            // ================================================================
405            // NOPs (do nothing)
406            // ================================================================
407            OP_NOP | OP_NOP1 | OP_NOP2 | OP_NOP3 | OP_NOP4 | OP_NOP5 | OP_NOP6 | OP_NOP7
408            | OP_NOP8 | OP_NOP9 | OP_NOP10 => {}
409            // Extended NOPs (0xba-0xff)
410            0xba..=0xff => {}
411
412            // ================================================================
413            // Flow Control (0x63-0x6a)
414            // ================================================================
415            OP_IF | OP_NOTIF => {
416                let mut f_value = false;
417                if is_executing {
418                    if self.stack.is_empty() {
419                        return Err(self.error(
420                            "OP_IF and OP_NOTIF require at least one item on the stack when they are used!",
421                        ));
422                    }
423                    let buf = self.pop_stack()?;
424                    f_value = ScriptNum::cast_to_bool(&buf);
425                    if opcode == OP_NOTIF {
426                        f_value = !f_value;
427                    }
428                }
429                self.if_stack.push(f_value);
430            }
431            OP_ELSE => {
432                if self.if_stack.is_empty() {
433                    return Err(self.error("OP_ELSE requires a preceeding OP_IF."));
434                }
435                let last = self.if_stack.len() - 1;
436                self.if_stack[last] = !self.if_stack[last];
437            }
438            OP_ENDIF => {
439                if self.if_stack.is_empty() {
440                    return Err(self.error("OP_ENDIF requires a preceeding OP_IF."));
441                }
442                self.if_stack.pop();
443            }
444            OP_VERIFY => {
445                if self.stack.is_empty() {
446                    return Err(
447                        self.error("OP_VERIFY requires at least one item to be on the stack.")
448                    );
449                }
450                let f_value = ScriptNum::cast_to_bool(self.stack_top()?);
451                if !f_value {
452                    return Err(self.error("OP_VERIFY requires the top stack value to be truthy."));
453                }
454                self.pop_stack()?;
455            }
456            OP_RETURN => {
457                // Jump to end of current script
458                let end = match self.context {
459                    ExecutionContext::UnlockingScript => self.unlocking_chunks.len(),
460                    ExecutionContext::LockingScript => self.locking_chunks.len(),
461                };
462                self.program_counter = end;
463                self.if_stack.clear();
464                // Counteract the final increment
465                if self.program_counter > 0 {
466                    self.program_counter -= 1;
467                }
468            }
469
470            // ================================================================
471            // Stack Operations (0x6b-0x7d)
472            // ================================================================
473            OP_TOALTSTACK => {
474                if self.stack.is_empty() {
475                    return Err(
476                        self.error("OP_TOALTSTACK requires at least one item to be on the stack.")
477                    );
478                }
479                let item = self.pop_stack()?;
480                self.push_alt_stack(item)?;
481            }
482            OP_FROMALTSTACK => {
483                if self.alt_stack.is_empty() {
484                    return Err(self.error(
485                        "OP_FROMALTSTACK requires at least one item to be on the alt stack.",
486                    ));
487                }
488                let item = self.pop_alt_stack()?;
489                self.push_stack(item)?;
490            }
491            OP_2DROP => {
492                if self.stack.len() < 2 {
493                    return Err(
494                        self.error("OP_2DROP requires at least two items to be on the stack.")
495                    );
496                }
497                self.pop_stack()?;
498                self.pop_stack()?;
499            }
500            OP_2DUP => {
501                if self.stack.len() < 2 {
502                    return Err(
503                        self.error("OP_2DUP requires at least two items to be on the stack.")
504                    );
505                }
506                let buf1 = self.stack_top_n(2)?.to_vec();
507                let buf2 = self.stack_top()?.to_vec();
508                self.push_stack(buf1)?;
509                self.push_stack(buf2)?;
510            }
511            OP_3DUP => {
512                if self.stack.len() < 3 {
513                    return Err(
514                        self.error("OP_3DUP requires at least three items to be on the stack.")
515                    );
516                }
517                let buf1 = self.stack_top_n(3)?.to_vec();
518                let buf2 = self.stack_top_n(2)?.to_vec();
519                let buf3 = self.stack_top()?.to_vec();
520                self.push_stack(buf1)?;
521                self.push_stack(buf2)?;
522                self.push_stack(buf3)?;
523            }
524            OP_2OVER => {
525                if self.stack.len() < 4 {
526                    return Err(
527                        self.error("OP_2OVER requires at least four items to be on the stack.")
528                    );
529                }
530                let buf1 = self.stack_top_n(4)?.to_vec();
531                let buf2 = self.stack_top_n(3)?.to_vec();
532                self.push_stack(buf1)?;
533                self.push_stack(buf2)?;
534            }
535            OP_2ROT => {
536                if self.stack.len() < 6 {
537                    return Err(
538                        self.error("OP_2ROT requires at least six items to be on the stack.")
539                    );
540                }
541                let x6 = self.pop_stack()?;
542                let x5 = self.pop_stack()?;
543                let x4 = self.pop_stack()?;
544                let x3 = self.pop_stack()?;
545                let x2 = self.pop_stack()?;
546                let x1 = self.pop_stack()?;
547                self.push_stack(x3)?;
548                self.push_stack(x4)?;
549                self.push_stack(x5)?;
550                self.push_stack(x6)?;
551                self.push_stack(x1)?;
552                self.push_stack(x2)?;
553            }
554            OP_2SWAP => {
555                if self.stack.len() < 4 {
556                    return Err(
557                        self.error("OP_2SWAP requires at least four items to be on the stack.")
558                    );
559                }
560                let x4 = self.pop_stack()?;
561                let x3 = self.pop_stack()?;
562                let x2 = self.pop_stack()?;
563                let x1 = self.pop_stack()?;
564                self.push_stack(x3)?;
565                self.push_stack(x4)?;
566                self.push_stack(x1)?;
567                self.push_stack(x2)?;
568            }
569            OP_IFDUP => {
570                if self.stack.is_empty() {
571                    return Err(
572                        self.error("OP_IFDUP requires at least one item to be on the stack.")
573                    );
574                }
575                let top = self.stack_top()?.to_vec();
576                if ScriptNum::cast_to_bool(&top) {
577                    self.push_stack(top)?;
578                }
579            }
580            OP_DEPTH => {
581                let depth = BigNumber::from_u64(self.stack.len() as u64);
582                self.push_stack(ScriptNum::to_bytes(&depth))?;
583            }
584            OP_DROP => {
585                if self.stack.is_empty() {
586                    return Err(
587                        self.error("OP_DROP requires at least one item to be on the stack.")
588                    );
589                }
590                self.pop_stack()?;
591            }
592            OP_DUP => {
593                if self.stack.is_empty() {
594                    return Err(self.error("OP_DUP requires at least one item to be on the stack."));
595                }
596                let top = self.stack_top()?.to_vec();
597                self.push_stack(top)?;
598            }
599            OP_NIP => {
600                if self.stack.len() < 2 {
601                    return Err(
602                        self.error("OP_NIP requires at least two items to be on the stack.")
603                    );
604                }
605                let top = self.pop_stack()?;
606                self.pop_stack()?;
607                self.push_stack(top)?;
608            }
609            OP_OVER => {
610                if self.stack.len() < 2 {
611                    return Err(
612                        self.error("OP_OVER requires at least two items to be on the stack.")
613                    );
614                }
615                let second = self.stack_top_n(2)?.to_vec();
616                self.push_stack(second)?;
617            }
618            OP_PICK | OP_ROLL => {
619                if self.stack.len() < 2 {
620                    return Err(self.error(&format!(
621                        "{} requires at least two items to be on the stack.",
622                        opcode_to_name(opcode).unwrap_or("OP_PICK/ROLL")
623                    )));
624                }
625                let n_bytes = self.pop_stack()?;
626                let bn = ScriptNum::from_bytes(&n_bytes, self.require_minimal)
627                    .map_err(|e| self.error(&format!("Invalid script number: {}", e)))?;
628
629                let n = bn.to_i64().unwrap_or(i64::MAX);
630                if n < 0 || n >= self.stack.len() as i64 {
631                    return Err(self.error(&format!(
632                        "{} requires the top stack element to be 0 or a positive number less than the current size of the stack.",
633                        opcode_to_name(opcode).unwrap_or("OP_PICK/ROLL")
634                    )));
635                }
636
637                let n_idx = n as usize;
638                let item = self.stack[self.stack.len() - 1 - n_idx].clone();
639
640                if opcode == OP_ROLL {
641                    let remove_idx = self.stack.len() - 1 - n_idx;
642                    let removed = self.stack.remove(remove_idx);
643                    self.stack_mem -= removed.len();
644                    self.push_stack(item)?;
645                } else {
646                    // OP_PICK
647                    self.push_stack(item)?;
648                }
649            }
650            OP_ROT => {
651                if self.stack.len() < 3 {
652                    return Err(
653                        self.error("OP_ROT requires at least three items to be on the stack.")
654                    );
655                }
656                let x3 = self.pop_stack()?;
657                let x2 = self.pop_stack()?;
658                let x1 = self.pop_stack()?;
659                self.push_stack(x2)?;
660                self.push_stack(x3)?;
661                self.push_stack(x1)?;
662            }
663            OP_SWAP => {
664                if self.stack.len() < 2 {
665                    return Err(
666                        self.error("OP_SWAP requires at least two items to be on the stack.")
667                    );
668                }
669                let x2 = self.pop_stack()?;
670                let x1 = self.pop_stack()?;
671                self.push_stack(x2)?;
672                self.push_stack(x1)?;
673            }
674            OP_TUCK => {
675                if self.stack.len() < 2 {
676                    return Err(
677                        self.error("OP_TUCK requires at least two items to be on the stack.")
678                    );
679                }
680                let top = self.stack_top()?.to_vec();
681                self.ensure_stack_mem(top.len())?;
682                let insert_idx = self.stack.len() - 2;
683                self.stack.insert(insert_idx, top.clone());
684                self.stack_mem += top.len();
685            }
686            OP_SIZE => {
687                if self.stack.is_empty() {
688                    return Err(
689                        self.error("OP_SIZE requires at least one item to be on the stack.")
690                    );
691                }
692                let size = self.stack_top()?.len();
693                let bn = BigNumber::from_u64(size as u64);
694                self.push_stack(ScriptNum::to_bytes(&bn))?;
695            }
696
697            // ================================================================
698            // Splice Operations (BSV re-enabled)
699            // ================================================================
700            OP_CAT => {
701                if self.stack.len() < 2 {
702                    return Err(
703                        self.error("OP_CAT requires at least two items to be on the stack.")
704                    );
705                }
706                let buf2 = self.pop_stack()?;
707                let buf1 = self.pop_stack()?;
708                let mut result = buf1;
709                result.extend(buf2);
710                if result.len() > MAX_SCRIPT_ELEMENT_SIZE {
711                    return Err(self.error(&format!(
712                        "It's not currently possible to push data larger than {} bytes.",
713                        MAX_SCRIPT_ELEMENT_SIZE
714                    )));
715                }
716                self.push_stack(result)?;
717            }
718            OP_SPLIT => {
719                if self.stack.len() < 2 {
720                    return Err(
721                        self.error("OP_SPLIT requires at least two items to be on the stack.")
722                    );
723                }
724                let pos_bytes = self.pop_stack()?;
725                let data = self.pop_stack()?;
726
727                let pos_bn = ScriptNum::from_bytes(&pos_bytes, self.require_minimal)
728                    .map_err(|e| self.error(&format!("Invalid script number: {}", e)))?;
729                let pos = pos_bn.to_i64().unwrap_or(-1);
730
731                if pos < 0 || pos > data.len() as i64 {
732                    return Err(self.error(
733                        "OP_SPLIT requires the first stack item to be a non-negative number less than or equal to the size of the second-from-top stack item.",
734                    ));
735                }
736
737                let split_idx = pos as usize;
738                let left = data[..split_idx].to_vec();
739                let right = data[split_idx..].to_vec();
740                self.push_stack(left)?;
741                self.push_stack(right)?;
742            }
743            OP_NUM2BIN => {
744                if self.stack.len() < 2 {
745                    return Err(
746                        self.error("OP_NUM2BIN requires at least two items to be on the stack.")
747                    );
748                }
749                let size_bytes = self.pop_stack()?;
750                let size_bn = ScriptNum::from_bytes(&size_bytes, self.require_minimal)
751                    .map_err(|e| self.error(&format!("Invalid script number: {}", e)))?;
752                let size = size_bn.to_i64().unwrap_or(-1);
753
754                if size < 0 || size > MAX_SCRIPT_ELEMENT_SIZE as i64 {
755                    return Err(self.error(&format!(
756                        "It's not currently possible to push data larger than {} bytes or negative size.",
757                        MAX_SCRIPT_ELEMENT_SIZE
758                    )));
759                }
760                let size = size as usize;
761                // Reference parity (0.3.23): the element the script asks for is
762                // refused BEFORE it is allocated when it alone exceeds the
763                // local memory budget — the TypeScript SDK's `element-size`
764                // resource check. Without this a 9-byte script could make the
765                // evaluator allocate up to MAX_SCRIPT_ELEMENT_SIZE (1 GB) and
766                // only then trip the stack budget on the push.
767                if size > self.memory_limit {
768                    return Err(self.resource_error(ScriptResource::ElementSize, size));
769                }
770
771                let rawnum = self.pop_stack()?;
772                let minimal = ScriptNum::minimally_encode(&rawnum);
773
774                if minimal.len() > size {
775                    return Err(self.error(
776                        "OP_NUM2BIN requires that the size expressed in the top stack item is large enough to hold the value expressed in the second-from-top stack item.",
777                    ));
778                }
779
780                if minimal.len() == size {
781                    self.push_stack(minimal)?;
782                } else {
783                    // Pad to size, preserving sign
784                    let mut result = vec![0u8; size];
785                    let mut signbit = 0u8;
786
787                    if !minimal.is_empty() {
788                        signbit = minimal[minimal.len() - 1] & 0x80;
789                        let mut minimal_copy = minimal.clone();
790                        if let Some(last) = minimal_copy.last_mut() {
791                            *last &= 0x7f;
792                        }
793                        result[..minimal_copy.len()].copy_from_slice(&minimal_copy);
794                    }
795
796                    if signbit != 0 {
797                        result[size - 1] |= 0x80;
798                    }
799                    self.push_stack(result)?;
800                }
801            }
802            OP_BIN2NUM => {
803                if self.stack.is_empty() {
804                    return Err(
805                        self.error("OP_BIN2NUM requires at least one item to be on the stack.")
806                    );
807                }
808                let buf = self.pop_stack()?;
809                let result = ScriptNum::minimally_encode(&buf);
810                if !ScriptNum::is_minimally_encoded(&result) {
811                    return Err(
812                        self.error("OP_BIN2NUM requires that the resulting number is valid.")
813                    );
814                }
815                self.push_stack(result)?;
816            }
817
818            // ================================================================
819            // Bitwise Operations
820            // ================================================================
821            OP_INVERT => {
822                if self.stack.is_empty() {
823                    return Err(
824                        self.error("OP_INVERT requires at least one item to be on the stack.")
825                    );
826                }
827                let buf = self.pop_stack()?;
828                let result: Vec<u8> = buf.iter().map(|&b| !b).collect();
829                self.push_stack(result)?;
830            }
831            OP_AND | OP_OR | OP_XOR => {
832                if self.stack.len() < 2 {
833                    return Err(self.error(&format!(
834                        "{} requires at least two items on the stack.",
835                        opcode_to_name(opcode).unwrap_or("OP")
836                    )));
837                }
838                let buf2 = self.pop_stack()?;
839                let buf1 = self.pop_stack()?;
840                if buf1.len() != buf2.len() {
841                    return Err(self.error(&format!(
842                        "{} requires the top two stack items to be the same size.",
843                        opcode_to_name(opcode).unwrap_or("OP")
844                    )));
845                }
846                let result: Vec<u8> = buf1
847                    .iter()
848                    .zip(buf2.iter())
849                    .map(|(&a, &b)| match opcode {
850                        OP_AND => a & b,
851                        OP_OR => a | b,
852                        _ => a ^ b, // OP_XOR
853                    })
854                    .collect();
855                self.push_stack(result)?;
856            }
857            OP_EQUAL | OP_EQUALVERIFY => {
858                if self.stack.len() < 2 {
859                    return Err(self.error(&format!(
860                        "{} requires at least two items to be on the stack.",
861                        opcode_to_name(opcode).unwrap_or("OP_EQUAL")
862                    )));
863                }
864                let buf2 = self.pop_stack()?;
865                let buf1 = self.pop_stack()?;
866                let equal = buf1 == buf2;
867                self.push_stack(if equal { vec![1] } else { vec![] })?;
868
869                if opcode == OP_EQUALVERIFY {
870                    if !equal {
871                        return Err(self.error(
872                            "OP_EQUALVERIFY requires the top two stack items to be equal.",
873                        ));
874                    }
875                    self.pop_stack()?;
876                }
877            }
878            OP_LSHIFT | OP_RSHIFT => {
879                if self.stack.len() < 2 {
880                    return Err(self.error(&format!(
881                        "{} requires at least two items to be on the stack.",
882                        opcode_to_name(opcode).unwrap_or("OP")
883                    )));
884                }
885                let n_bytes = self.pop_stack()?;
886                let buf = self.pop_stack()?;
887
888                let n_bn = ScriptNum::from_bytes(&n_bytes, self.require_minimal)
889                    .map_err(|e| self.error(&format!("Invalid script number: {}", e)))?;
890                let n = n_bn.to_i64().unwrap_or(-1);
891
892                if n < 0 {
893                    return Err(self.error(&format!(
894                        "{} requires the top item on the stack not to be negative.",
895                        opcode_to_name(opcode).unwrap_or("OP")
896                    )));
897                }
898
899                if buf.is_empty() {
900                    self.push_stack(vec![])?;
901                } else {
902                    // Node semantics (and ts-sdk post-#493): LSHIFT/RSHIFT are
903                    // WIDTH-PRESERVING bitwise shifts on the raw byte buffer —
904                    // bits shifted past the end are discarded, the result is
905                    // exactly buf.len() bytes. The previous BigNumber
906                    // mul/to_bytes_be(buf.len()) implementation PANICKED on
907                    // overflow ("BigNumber requires N bytes") and clamped the
908                    // shift count to 63 bits (conformance vectors
909                    // lshift-truncation.0001/.0003).
910                    let len = buf.len();
911                    let result: Vec<u8> = if (n as u128) >= (len as u128) * 8 {
912                        vec![0u8; len]
913                    } else {
914                        let byte_shift = (n as usize) / 8;
915                        let bit_shift = (n as usize) % 8;
916                        let mut out = vec![0u8; len];
917                        #[allow(clippy::needless_range_loop)]
918                        for i in 0..len {
919                            if opcode == OP_LSHIFT {
920                                let src = i + byte_shift;
921                                let hi = if src < len { buf[src] } else { 0 };
922                                let lo = if bit_shift > 0 && src + 1 < len {
923                                    buf[src + 1]
924                                } else {
925                                    0
926                                };
927                                out[i] = if bit_shift == 0 {
928                                    hi
929                                } else {
930                                    (hi << bit_shift) | (lo >> (8 - bit_shift))
931                                };
932                            } else {
933                                // OP_RSHIFT
934                                if i >= byte_shift {
935                                    let src = i - byte_shift;
936                                    let hi = buf[src];
937                                    let carry = if bit_shift > 0 && src >= 1 {
938                                        buf[src - 1]
939                                    } else {
940                                        0
941                                    };
942                                    out[i] = if bit_shift == 0 {
943                                        hi
944                                    } else {
945                                        (hi >> bit_shift) | (carry << (8 - bit_shift))
946                                    };
947                                }
948                            }
949                        }
950                        out
951                    };
952                    self.push_stack(result)?;
953                }
954            }
955
956            // ================================================================
957            // Arithmetic Operations
958            // ================================================================
959            OP_1ADD | OP_1SUB | OP_NEGATE | OP_ABS | OP_NOT | OP_0NOTEQUAL => {
960                if self.stack.is_empty() {
961                    return Err(self.error(&format!(
962                        "{} requires at least one item to be on the stack.",
963                        opcode_to_name(opcode).unwrap_or("OP")
964                    )));
965                }
966                let buf = self.pop_stack()?;
967                let mut bn = ScriptNum::from_bytes(&buf, self.require_minimal)
968                    .map_err(|e| self.error(&format!("Invalid script number: {}", e)))?;
969
970                bn = match opcode {
971                    OP_1ADD => bn.add(&BigNumber::one()),
972                    OP_1SUB => bn.sub(&BigNumber::one()),
973                    OP_NEGATE => bn.neg(),
974                    OP_ABS => bn.abs(),
975                    OP_NOT => {
976                        if bn.is_zero() {
977                            BigNumber::one()
978                        } else {
979                            BigNumber::zero()
980                        }
981                    }
982                    OP_0NOTEQUAL => {
983                        if bn.is_zero() {
984                            BigNumber::zero()
985                        } else {
986                            BigNumber::one()
987                        }
988                    }
989                    _ => bn,
990                };
991                self.push_stack(ScriptNum::to_bytes(&bn))?;
992            }
993            OP_ADD
994            | OP_SUB
995            | OP_MUL
996            | OP_DIV
997            | OP_MOD
998            | OP_BOOLAND
999            | OP_BOOLOR
1000            | OP_NUMEQUAL
1001            | OP_NUMEQUALVERIFY
1002            | OP_NUMNOTEQUAL
1003            | OP_LESSTHAN
1004            | OP_GREATERTHAN
1005            | OP_LESSTHANOREQUAL
1006            | OP_GREATERTHANOREQUAL
1007            | OP_MIN
1008            | OP_MAX => {
1009                if self.stack.len() < 2 {
1010                    return Err(self.error(&format!(
1011                        "{} requires at least two items to be on the stack.",
1012                        opcode_to_name(opcode).unwrap_or("OP")
1013                    )));
1014                }
1015                let buf2 = self.pop_stack()?;
1016                let buf1 = self.pop_stack()?;
1017                let bn1 = ScriptNum::from_bytes(&buf1, self.require_minimal)
1018                    .map_err(|e| self.error(&format!("Invalid script number: {}", e)))?;
1019                let bn2 = ScriptNum::from_bytes(&buf2, self.require_minimal)
1020                    .map_err(|e| self.error(&format!("Invalid script number: {}", e)))?;
1021
1022                let result = match opcode {
1023                    OP_ADD => bn1.add(&bn2),
1024                    OP_SUB => bn1.sub(&bn2),
1025                    OP_MUL => bn1.mul(&bn2),
1026                    OP_DIV => {
1027                        if bn2.is_zero() {
1028                            return Err(self.error("OP_DIV cannot divide by zero!"));
1029                        }
1030                        bn1.div(&bn2)
1031                    }
1032                    OP_MOD => {
1033                        if bn2.is_zero() {
1034                            return Err(self.error("OP_MOD cannot divide by zero!"));
1035                        }
1036                        bn1.mod_floor(&bn2)
1037                    }
1038                    OP_BOOLAND => {
1039                        if !bn1.is_zero() && !bn2.is_zero() {
1040                            BigNumber::one()
1041                        } else {
1042                            BigNumber::zero()
1043                        }
1044                    }
1045                    OP_BOOLOR => {
1046                        if !bn1.is_zero() || !bn2.is_zero() {
1047                            BigNumber::one()
1048                        } else {
1049                            BigNumber::zero()
1050                        }
1051                    }
1052                    OP_NUMEQUAL | OP_NUMEQUALVERIFY => {
1053                        if bn1 == bn2 {
1054                            BigNumber::one()
1055                        } else {
1056                            BigNumber::zero()
1057                        }
1058                    }
1059                    OP_NUMNOTEQUAL => {
1060                        if bn1 != bn2 {
1061                            BigNumber::one()
1062                        } else {
1063                            BigNumber::zero()
1064                        }
1065                    }
1066                    OP_LESSTHAN => {
1067                        if bn1 < bn2 {
1068                            BigNumber::one()
1069                        } else {
1070                            BigNumber::zero()
1071                        }
1072                    }
1073                    OP_GREATERTHAN => {
1074                        if bn1 > bn2 {
1075                            BigNumber::one()
1076                        } else {
1077                            BigNumber::zero()
1078                        }
1079                    }
1080                    OP_LESSTHANOREQUAL => {
1081                        if bn1 <= bn2 {
1082                            BigNumber::one()
1083                        } else {
1084                            BigNumber::zero()
1085                        }
1086                    }
1087                    OP_GREATERTHANOREQUAL => {
1088                        if bn1 >= bn2 {
1089                            BigNumber::one()
1090                        } else {
1091                            BigNumber::zero()
1092                        }
1093                    }
1094                    OP_MIN => {
1095                        if bn1 < bn2 {
1096                            bn1
1097                        } else {
1098                            bn2
1099                        }
1100                    }
1101                    OP_MAX => {
1102                        if bn1 > bn2 {
1103                            bn1
1104                        } else {
1105                            bn2
1106                        }
1107                    }
1108                    _ => BigNumber::zero(),
1109                };
1110
1111                self.push_stack(ScriptNum::to_bytes(&result))?;
1112
1113                if opcode == OP_NUMEQUALVERIFY {
1114                    if !ScriptNum::cast_to_bool(self.stack_top()?) {
1115                        return Err(self
1116                            .error("OP_NUMEQUALVERIFY requires the top stack item to be truthy."));
1117                    }
1118                    self.pop_stack()?;
1119                }
1120            }
1121            OP_WITHIN => {
1122                if self.stack.len() < 3 {
1123                    return Err(
1124                        self.error("OP_WITHIN requires at least three items to be on the stack.")
1125                    );
1126                }
1127                let max_bytes = self.pop_stack()?;
1128                let min_bytes = self.pop_stack()?;
1129                let x_bytes = self.pop_stack()?;
1130                let max_bn = ScriptNum::from_bytes(&max_bytes, self.require_minimal)
1131                    .map_err(|e| self.error(&format!("Invalid script number: {}", e)))?;
1132                let min_bn = ScriptNum::from_bytes(&min_bytes, self.require_minimal)
1133                    .map_err(|e| self.error(&format!("Invalid script number: {}", e)))?;
1134                let x_bn = ScriptNum::from_bytes(&x_bytes, self.require_minimal)
1135                    .map_err(|e| self.error(&format!("Invalid script number: {}", e)))?;
1136
1137                let in_range = x_bn >= min_bn && x_bn < max_bn;
1138                self.push_stack(if in_range { vec![1] } else { vec![] })?;
1139            }
1140
1141            // ================================================================
1142            // Crypto Operations
1143            // ================================================================
1144            OP_RIPEMD160 => {
1145                if self.stack.is_empty() {
1146                    return Err(
1147                        self.error("OP_RIPEMD160 requires at least one item to be on the stack.")
1148                    );
1149                }
1150                let buf = self.pop_stack()?;
1151                let hash = ripemd160(&buf);
1152                self.push_stack(hash.to_vec())?;
1153            }
1154            OP_SHA1 => {
1155                if self.stack.is_empty() {
1156                    return Err(
1157                        self.error("OP_SHA1 requires at least one item to be on the stack.")
1158                    );
1159                }
1160                let buf = self.pop_stack()?;
1161                let hash = sha1(&buf);
1162                self.push_stack(hash.to_vec())?;
1163            }
1164            OP_SHA256 => {
1165                if self.stack.is_empty() {
1166                    return Err(
1167                        self.error("OP_SHA256 requires at least one item to be on the stack.")
1168                    );
1169                }
1170                let buf = self.pop_stack()?;
1171                let hash = sha256(&buf);
1172                self.push_stack(hash.to_vec())?;
1173            }
1174            OP_HASH160 => {
1175                if self.stack.is_empty() {
1176                    return Err(
1177                        self.error("OP_HASH160 requires at least one item to be on the stack.")
1178                    );
1179                }
1180                let buf = self.pop_stack()?;
1181                let hash = hash160(&buf);
1182                self.push_stack(hash.to_vec())?;
1183            }
1184            OP_HASH256 => {
1185                if self.stack.is_empty() {
1186                    return Err(
1187                        self.error("OP_HASH256 requires at least one item to be on the stack.")
1188                    );
1189                }
1190                let buf = self.pop_stack()?;
1191                let hash = sha256d(&buf);
1192                self.push_stack(hash.to_vec())?;
1193            }
1194            OP_CODESEPARATOR => {
1195                self.last_code_separator = Some(self.program_counter);
1196            }
1197            OP_CHECKSIG | OP_CHECKSIGVERIFY => {
1198                if self.stack.len() < 2 {
1199                    return Err(self.error(&format!(
1200                        "{} requires at least two items to be on the stack.",
1201                        opcode_to_name(opcode).unwrap_or("OP_CHECKSIG")
1202                    )));
1203                }
1204                let pubkey_bytes = self.pop_stack()?;
1205                let sig_bytes = self.pop_stack()?;
1206
1207                // Validate encodings
1208                self.check_signature_encoding(&sig_bytes)?;
1209                self.check_public_key_encoding(&pubkey_bytes)?;
1210
1211                // Build subscript
1212                let subscript = self.build_subscript(&sig_bytes)?;
1213
1214                // Verify signature
1215                let success = if sig_bytes.is_empty() {
1216                    false
1217                } else {
1218                    self.verify_signature(&sig_bytes, &pubkey_bytes, &subscript)?
1219                };
1220
1221                self.push_stack(if success { vec![1] } else { vec![] })?;
1222
1223                if opcode == OP_CHECKSIGVERIFY {
1224                    if !success {
1225                        return Err(self.error(
1226                            "OP_CHECKSIGVERIFY requires that a valid signature is provided.",
1227                        ));
1228                    }
1229                    self.pop_stack()?;
1230                }
1231            }
1232            OP_CHECKMULTISIG | OP_CHECKMULTISIGVERIFY => {
1233                self.op_checkmultisig(opcode)?;
1234            }
1235
1236            // ================================================================
1237            // Data Push (handled above, but catch any missed)
1238            // ================================================================
1239            0x01..=0x4b => {
1240                // Direct push opcodes - should have data
1241                let data = chunk.data.clone().unwrap_or_default();
1242                self.push_stack(data)?;
1243            }
1244            OP_PUSHDATA1 | OP_PUSHDATA2 | OP_PUSHDATA4 => {
1245                let data = chunk.data.clone().unwrap_or_default();
1246                self.push_stack(data)?;
1247            }
1248
1249            // ================================================================
1250            // Unknown/Invalid Opcode
1251            // ================================================================
1252            _ => {
1253                return Err(self.error(&format!(
1254                    "Invalid opcode {} (pc={}).",
1255                    opcode, self.program_counter
1256                )));
1257            }
1258        }
1259
1260        Ok(())
1261    }
1262
1263    // ========================================================================
1264    // OP_CHECKMULTISIG Implementation
1265    // ========================================================================
1266
1267    fn op_checkmultisig(&mut self, opcode: u8) -> Result<(), ScriptEvaluationError> {
1268        // Get number of public keys
1269        if self.stack.is_empty() {
1270            return Err(self.error(&format!(
1271                "{} requires at least 1 item for nKeys.",
1272                opcode_to_name(opcode).unwrap_or("OP_CHECKMULTISIG")
1273            )));
1274        }
1275
1276        let n_keys_bytes = self.pop_stack()?;
1277        let n_keys_bn = ScriptNum::from_bytes(&n_keys_bytes, self.require_minimal)
1278            .map_err(|e| self.error(&format!("Invalid script number: {}", e)))?;
1279        let n_keys = n_keys_bn.to_i64().unwrap_or(-1);
1280
1281        if !(0..=MAX_MULTISIG_KEY_COUNT).contains(&n_keys) {
1282            return Err(self.error(&format!(
1283                "{} requires a key count between 0 and {}.",
1284                opcode_to_name(opcode).unwrap_or("OP_CHECKMULTISIG"),
1285                MAX_MULTISIG_KEY_COUNT
1286            )));
1287        }
1288        let n_keys = n_keys as usize;
1289
1290        // Get public keys
1291        if self.stack.len() < n_keys {
1292            return Err(self.error(&format!(
1293                "{} stack too small for keys. Need {}, have {}.",
1294                opcode_to_name(opcode).unwrap_or("OP_CHECKMULTISIG"),
1295                n_keys,
1296                self.stack.len()
1297            )));
1298        }
1299
1300        let mut pubkeys = Vec::with_capacity(n_keys);
1301        for _ in 0..n_keys {
1302            pubkeys.push(self.pop_stack()?);
1303        }
1304
1305        // Get number of signatures
1306        if self.stack.is_empty() {
1307            return Err(self.error(&format!(
1308                "{} requires item for nSigs.",
1309                opcode_to_name(opcode).unwrap_or("OP_CHECKMULTISIG")
1310            )));
1311        }
1312
1313        let n_sigs_bytes = self.pop_stack()?;
1314        let n_sigs_bn = ScriptNum::from_bytes(&n_sigs_bytes, self.require_minimal)
1315            .map_err(|e| self.error(&format!("Invalid script number: {}", e)))?;
1316        let n_sigs = n_sigs_bn.to_i64().unwrap_or(-1);
1317
1318        if n_sigs < 0 || n_sigs as usize > n_keys {
1319            return Err(self.error(&format!(
1320                "{} requires the number of signatures to be no greater than the number of keys.",
1321                opcode_to_name(opcode).unwrap_or("OP_CHECKMULTISIG")
1322            )));
1323        }
1324        let n_sigs = n_sigs as usize;
1325
1326        // Get signatures
1327        if self.stack.len() < n_sigs {
1328            return Err(self.error(&format!(
1329                "{} stack too small for sigs. Need {}, have {}.",
1330                opcode_to_name(opcode).unwrap_or("OP_CHECKMULTISIG"),
1331                n_sigs,
1332                self.stack.len()
1333            )));
1334        }
1335
1336        let mut sigs = Vec::with_capacity(n_sigs);
1337        for _ in 0..n_sigs {
1338            sigs.push(self.pop_stack()?);
1339        }
1340
1341        // Build subscript and remove all signatures
1342        let base_script = match self.context {
1343            ExecutionContext::UnlockingScript => self.unlocking_script.as_script().clone(),
1344            ExecutionContext::LockingScript => self.locking_script.as_script().clone(),
1345        };
1346        let start_idx = self.last_code_separator.map(|i| i + 1).unwrap_or(0);
1347        let chunks = base_script.chunks();
1348        let mut subscript_chunks: Vec<ScriptChunk> = chunks.into_iter().skip(start_idx).collect();
1349        // See build_subscript: unlock-context subscripts continue into the
1350        // full locking script (combined-script semantics, ts-sdk parity).
1351        if self.context == ExecutionContext::UnlockingScript {
1352            subscript_chunks.extend(self.locking_script.as_script().chunks());
1353        }
1354        let mut subscript = Script::from_chunks(subscript_chunks);
1355
1356        for sig in &sigs {
1357            let sig_script = Script::new();
1358            let mut sig_script = sig_script;
1359            sig_script.write_bin(sig);
1360            subscript.find_and_delete(&sig_script);
1361        }
1362
1363        // Verify signatures
1364        let mut success = true;
1365        let mut sig_idx = 0;
1366        let mut key_idx = 0;
1367
1368        while success && sig_idx < n_sigs {
1369            if key_idx >= n_keys {
1370                success = false;
1371                break;
1372            }
1373
1374            let sig_bytes = &sigs[sig_idx];
1375            let pubkey_bytes = &pubkeys[key_idx];
1376
1377            // Validate encodings
1378            if self.check_signature_encoding(sig_bytes).is_err()
1379                || self.check_public_key_encoding(pubkey_bytes).is_err()
1380            {
1381                return Err(self.error(&format!(
1382                    "{} requires correct encoding for the public key and signature.",
1383                    opcode_to_name(opcode).unwrap_or("OP_CHECKMULTISIG")
1384                )));
1385            }
1386
1387            let sig_valid = if sig_bytes.is_empty() {
1388                false
1389            } else {
1390                self.verify_signature(sig_bytes, pubkey_bytes, &subscript)
1391                    .unwrap_or(false)
1392            };
1393
1394            if sig_valid {
1395                sig_idx += 1;
1396            }
1397            key_idx += 1;
1398
1399            if n_sigs - sig_idx > n_keys - key_idx {
1400                success = false;
1401            }
1402        }
1403
1404        // Pop dummy element (NULLDUMMY)
1405        if self.stack.is_empty() {
1406            return Err(self.error(&format!(
1407                "{} requires an extra item (dummy) to be on the stack.",
1408                opcode_to_name(opcode).unwrap_or("OP_CHECKMULTISIG")
1409            )));
1410        }
1411        let dummy = self.pop_stack()?;
1412        if !dummy.is_empty() {
1413            return Err(self.error(&format!(
1414                "{} requires the extra stack item (dummy) to be empty.",
1415                opcode_to_name(opcode).unwrap_or("OP_CHECKMULTISIG")
1416            )));
1417        }
1418
1419        self.push_stack(if success { vec![1] } else { vec![] })?;
1420
1421        if opcode == OP_CHECKMULTISIGVERIFY {
1422            if !success {
1423                return Err(self.error(
1424                    "OP_CHECKMULTISIGVERIFY requires that a sufficient number of valid signatures are provided.",
1425                ));
1426            }
1427            self.pop_stack()?;
1428        }
1429
1430        Ok(())
1431    }
1432
1433    // ========================================================================
1434    // Signature Verification Helpers
1435    // ========================================================================
1436
1437    fn check_signature_encoding(&self, sig: &[u8]) -> Result<(), ScriptEvaluationError> {
1438        if sig.is_empty() {
1439            return Ok(());
1440        }
1441
1442        // Check basic DER format
1443        if !is_valid_signature_encoding(sig) {
1444            return Err(self.error("The signature format is invalid."));
1445        }
1446
1447        // Parse and check additional requirements
1448        let tx_sig = TransactionSignature::from_checksig_format(sig)
1449            .map_err(|_| self.error("The signature format is invalid."))?;
1450
1451        if self.require_low_s && !tx_sig.has_low_s() {
1452            return Err(self.error("The signature must have a low S value."));
1453        }
1454
1455        if (tx_sig.scope() & SIGHASH_FORKID) == 0 {
1456            return Err(self.error("The signature must use SIGHASH_FORKID."));
1457        }
1458
1459        Ok(())
1460    }
1461
1462    fn check_public_key_encoding(&self, pubkey: &[u8]) -> Result<(), ScriptEvaluationError> {
1463        if pubkey.is_empty() {
1464            return Err(self.error("Public key is empty."));
1465        }
1466
1467        if pubkey.len() < 33 {
1468            return Err(self.error("The public key is too short, it must be at least 33 bytes."));
1469        }
1470
1471        if pubkey[0] == 0x04 {
1472            if pubkey.len() != 65 {
1473                return Err(self.error("The non-compressed public key must be 65 bytes."));
1474            }
1475        } else if pubkey[0] == 0x02 || pubkey[0] == 0x03 {
1476            if pubkey.len() != 33 {
1477                return Err(self.error("The compressed public key must be 33 bytes."));
1478            }
1479        } else {
1480            return Err(self.error("The public key is in an unknown format."));
1481        }
1482
1483        // Try to parse it
1484        PublicKey::from_bytes(pubkey)
1485            .map_err(|_| self.error("The public key is in an unknown format."))?;
1486
1487        Ok(())
1488    }
1489
1490    fn build_subscript(&self, sig_bytes: &[u8]) -> Result<Script, ScriptEvaluationError> {
1491        let base_script = match self.context {
1492            ExecutionContext::UnlockingScript => self.unlocking_script.as_script().clone(),
1493            ExecutionContext::LockingScript => self.locking_script.as_script().clone(),
1494        };
1495
1496        let start_idx = self.last_code_separator.map(|i| i + 1).unwrap_or(0);
1497        let chunks = base_script.chunks();
1498        let mut subscript_chunks: Vec<ScriptChunk> = chunks.into_iter().skip(start_idx).collect();
1499        // When a CHECKSIG executes in the unlocking script, the subscript
1500        // continues across the unlock/lock boundary into the full locking
1501        // script (legacy combined-script semantics; matches BSV node consensus
1502        // and ts-sdk). Without this, signatures taken over such a subscript
1503        // (e.g. OP_PUSH_TX-style contracts) are wrongly rejected.
1504        if self.context == ExecutionContext::UnlockingScript {
1505            subscript_chunks.extend(self.locking_script.as_script().chunks());
1506        }
1507        let mut subscript = Script::from_chunks(subscript_chunks);
1508
1509        // Remove the signature from the subscript
1510        let mut sig_script = Script::new();
1511        sig_script.write_bin(sig_bytes);
1512        subscript.find_and_delete(&sig_script);
1513
1514        Ok(subscript)
1515    }
1516
1517    fn verify_signature(
1518        &self,
1519        sig_bytes: &[u8],
1520        pubkey_bytes: &[u8],
1521        subscript: &Script,
1522    ) -> Result<bool, ScriptEvaluationError> {
1523        // Parse signature and public key
1524        let tx_sig = match TransactionSignature::from_checksig_format(sig_bytes) {
1525            Ok(s) => s,
1526            Err(_) => return Ok(false),
1527        };
1528
1529        let pubkey = match PublicKey::from_bytes(pubkey_bytes) {
1530            Ok(p) => p,
1531            Err(_) => return Ok(false),
1532        };
1533
1534        // Build inputs array for sighash
1535        let inputs = self.build_inputs_array();
1536
1537        // Compute sighash
1538        let sighash = compute_sighash_for_signing(&SighashParams {
1539            version: self.transaction_version,
1540            inputs: &inputs,
1541            outputs: &self.outputs,
1542            locktime: self.lock_time,
1543            input_index: self.input_index,
1544            subscript: &subscript.to_binary(),
1545            satoshis: self.source_satoshis,
1546            scope: tx_sig.scope(),
1547        });
1548
1549        // Verify
1550        Ok(pubkey.verify(&sighash, tx_sig.signature()))
1551    }
1552
1553    fn build_inputs_array(&self) -> Vec<TxInput> {
1554        let mut inputs = Vec::with_capacity(self.other_inputs.len() + 1);
1555
1556        // Add other inputs
1557        for (i, other) in self.other_inputs.iter().enumerate() {
1558            if i == self.input_index {
1559                // Insert our input at the correct position
1560                inputs.push(TxInput {
1561                    txid: self.source_txid,
1562                    output_index: self.source_output_index,
1563                    script: self.unlocking_script.to_binary(),
1564                    sequence: self.input_sequence,
1565                });
1566            }
1567            inputs.push(other.clone());
1568        }
1569
1570        // Handle case where our input is at the end or other_inputs is empty
1571        if self.input_index >= self.other_inputs.len() {
1572            inputs.push(TxInput {
1573                txid: self.source_txid,
1574                output_index: self.source_output_index,
1575                script: self.unlocking_script.to_binary(),
1576                sequence: self.input_sequence,
1577            });
1578        }
1579
1580        inputs
1581    }
1582
1583    // ========================================================================
1584    // Stack Helpers
1585    // ========================================================================
1586
1587    fn push_stack(&mut self, item: Vec<u8>) -> Result<(), ScriptEvaluationError> {
1588        self.ensure_stack_mem(item.len())?;
1589        self.stack_mem += item.len();
1590        self.stack.push(item);
1591        Ok(())
1592    }
1593
1594    fn push_stack_copy(&mut self, item: &[u8]) -> Result<(), ScriptEvaluationError> {
1595        self.push_stack(item.to_vec())
1596    }
1597
1598    fn pop_stack(&mut self) -> Result<Vec<u8>, ScriptEvaluationError> {
1599        if self.stack.is_empty() {
1600            return Err(self.error("Attempted to pop from an empty stack."));
1601        }
1602        let item = self.stack.pop().unwrap();
1603        self.stack_mem -= item.len();
1604        Ok(item)
1605    }
1606
1607    fn stack_top(&self) -> Result<&Vec<u8>, ScriptEvaluationError> {
1608        if self.stack.is_empty() {
1609            return Err(self.error("Stack is empty."));
1610        }
1611        Ok(&self.stack[self.stack.len() - 1])
1612    }
1613
1614    fn stack_top_n(&self, n: usize) -> Result<&Vec<u8>, ScriptEvaluationError> {
1615        if self.stack.len() < n {
1616            return Err(self.error(&format!(
1617                "Stack underflow accessing element at index {}. Stack length is {}.",
1618                n,
1619                self.stack.len()
1620            )));
1621        }
1622        Ok(&self.stack[self.stack.len() - n])
1623    }
1624
1625    fn push_alt_stack(&mut self, item: Vec<u8>) -> Result<(), ScriptEvaluationError> {
1626        self.ensure_alt_stack_mem(item.len())?;
1627        self.alt_stack_mem += item.len();
1628        self.alt_stack.push(item);
1629        Ok(())
1630    }
1631
1632    fn pop_alt_stack(&mut self) -> Result<Vec<u8>, ScriptEvaluationError> {
1633        if self.alt_stack.is_empty() {
1634            return Err(self.error("Attempted to pop from an empty alt stack."));
1635        }
1636        let item = self.alt_stack.pop().unwrap();
1637        self.alt_stack_mem -= item.len();
1638        Ok(item)
1639    }
1640
1641    fn ensure_stack_mem(&self, additional: usize) -> Result<(), ScriptEvaluationError> {
1642        if self.stack_mem + additional > self.memory_limit {
1643            return Err(self.resource_error(ScriptResource::Stack, self.stack_mem + additional));
1644        }
1645        Ok(())
1646    }
1647
1648    fn ensure_alt_stack_mem(&self, additional: usize) -> Result<(), ScriptEvaluationError> {
1649        if self.alt_stack_mem + additional > self.memory_limit {
1650            return Err(
1651                self.resource_error(ScriptResource::AltStack, self.alt_stack_mem + additional)
1652            );
1653        }
1654        Ok(())
1655    }
1656
1657    /// A LOCAL resource-limit error (the reference's `ScriptResourceLimitError`):
1658    /// the same message shape (`<label> has exceeded <limit> bytes`) plus the
1659    /// structured `resource_limit` a caller can branch on.
1660    fn resource_error(&self, resource: ScriptResource, attempted: usize) -> ScriptEvaluationError {
1661        let label = match resource {
1662            ScriptResource::Stack => "Stack memory usage",
1663            ScriptResource::AltStack => "Alt stack memory usage",
1664            ScriptResource::ElementSize => "Script element allocation",
1665        };
1666        self.error(&format!("{label} has exceeded {} bytes", self.memory_limit))
1667            .with_resource_limit(ScriptResourceLimit {
1668                resource,
1669                limit: self.memory_limit,
1670                attempted,
1671            })
1672    }
1673
1674    // ========================================================================
1675    // Error Helpers
1676    // ========================================================================
1677
1678    fn error(&self, message: &str) -> ScriptEvaluationError {
1679        ScriptEvaluationError::new(
1680            message,
1681            to_hex(&self.source_txid),
1682            self.source_output_index,
1683            self.context,
1684            self.program_counter,
1685            self.stack.clone(),
1686            self.alt_stack.clone(),
1687            self.if_stack.clone(),
1688            self.stack_mem,
1689            self.alt_stack_mem,
1690        )
1691    }
1692}
1693
1694// ============================================================================
1695// Helper Functions
1696// ============================================================================
1697
1698/// Checks if an opcode is disabled.
1699fn is_opcode_disabled(op: u8) -> bool {
1700    matches!(op, OP_2MUL | OP_2DIV | OP_VER | OP_VERIF | OP_VERNOTIF)
1701}
1702
1703/// Checks if a chunk uses minimal push encoding.
1704fn is_chunk_minimal_push(chunk: &ScriptChunk) -> bool {
1705    let data = match &chunk.data {
1706        Some(d) => d,
1707        None => return true,
1708    };
1709    let op = chunk.op;
1710
1711    if data.is_empty() {
1712        return op == OP_0;
1713    }
1714
1715    if data.len() == 1 && data[0] >= 1 && data[0] <= 16 {
1716        return op == OP_1 + (data[0] - 1);
1717    }
1718
1719    if data.len() == 1 && data[0] == 0x81 {
1720        return op == OP_1NEGATE;
1721    }
1722
1723    if data.len() <= 75 {
1724        return op as usize == data.len();
1725    }
1726
1727    if data.len() <= 255 {
1728        return op == OP_PUSHDATA1;
1729    }
1730
1731    if data.len() <= 65535 {
1732        return op == OP_PUSHDATA2;
1733    }
1734
1735    true
1736}
1737
1738/// Validates DER signature encoding (simplified check).
1739fn is_valid_signature_encoding(sig: &[u8]) -> bool {
1740    if sig.len() < 9 || sig.len() > 73 {
1741        return false;
1742    }
1743
1744    // Sequence tag
1745    if sig[0] != 0x30 {
1746        return false;
1747    }
1748
1749    // Length check
1750    if sig[1] as usize != sig.len() - 3 {
1751        return false;
1752    }
1753
1754    // R value
1755    if sig[2] != 0x02 {
1756        return false;
1757    }
1758
1759    let r_len = sig[3] as usize;
1760    if r_len == 0 || 5 + r_len >= sig.len() {
1761        return false;
1762    }
1763
1764    // S value
1765    let s_offset = 4 + r_len;
1766    if sig[s_offset] != 0x02 {
1767        return false;
1768    }
1769
1770    let s_len = sig[s_offset + 1] as usize;
1771    if s_len == 0 {
1772        return false;
1773    }
1774
1775    // Check total length
1776    if r_len + s_len + 7 != sig.len() {
1777        return false;
1778    }
1779
1780    // Check R not negative
1781    if (sig[4] & 0x80) != 0 {
1782        return false;
1783    }
1784
1785    // Check R not excessively padded
1786    if r_len > 1 && sig[4] == 0x00 && (sig[5] & 0x80) == 0 {
1787        return false;
1788    }
1789
1790    // Check S not negative
1791    let s_value_offset = s_offset + 2;
1792    if (sig[s_value_offset] & 0x80) != 0 {
1793        return false;
1794    }
1795
1796    // Check S not excessively padded
1797    if s_len > 1 && sig[s_value_offset] == 0x00 && (sig[s_value_offset + 1] & 0x80) == 0 {
1798        return false;
1799    }
1800
1801    true
1802}
1803
1804#[cfg(test)]
1805mod tests {
1806    use super::*;
1807
1808    #[test]
1809    fn test_is_opcode_disabled() {
1810        assert!(is_opcode_disabled(OP_2MUL));
1811        assert!(is_opcode_disabled(OP_2DIV));
1812        assert!(is_opcode_disabled(OP_VER));
1813        assert!(is_opcode_disabled(OP_VERIF));
1814        assert!(is_opcode_disabled(OP_VERNOTIF));
1815
1816        assert!(!is_opcode_disabled(OP_DUP));
1817        assert!(!is_opcode_disabled(OP_MUL));
1818        assert!(!is_opcode_disabled(OP_CAT));
1819    }
1820
1821    #[test]
1822    fn test_is_chunk_minimal_push() {
1823        // OP_0 for empty data
1824        let chunk = ScriptChunk::new(OP_0, Some(vec![]));
1825        assert!(is_chunk_minimal_push(&chunk));
1826
1827        // Direct push for small data
1828        let chunk = ScriptChunk::new(3, Some(vec![1, 2, 3]));
1829        assert!(is_chunk_minimal_push(&chunk));
1830
1831        // OP_1 for [1]
1832        let chunk = ScriptChunk::new(OP_1, Some(vec![1]));
1833        assert!(is_chunk_minimal_push(&chunk));
1834
1835        // Non-minimal: using push opcode for [1] instead of OP_1
1836        let chunk = ScriptChunk::new(1, Some(vec![1]));
1837        assert!(!is_chunk_minimal_push(&chunk));
1838    }
1839
1840    #[test]
1841    fn test_simple_stack_script() {
1842        // Test: OP_1 OP_2 OP_ADD OP_3 OP_EQUAL
1843        // Should leave [1] on stack (true)
1844        let locking = LockingScript::from_asm("OP_ADD OP_3 OP_EQUAL").unwrap();
1845        let unlocking = UnlockingScript::from_asm("OP_1 OP_2").unwrap();
1846
1847        let mut spend = Spend::new(SpendParams {
1848            source_txid: [0u8; 32],
1849            source_output_index: 0,
1850            source_satoshis: 0,
1851            locking_script: locking,
1852            transaction_version: 1,
1853            other_inputs: vec![],
1854            outputs: vec![],
1855            input_index: 0,
1856            unlocking_script: unlocking,
1857            input_sequence: 0xffffffff,
1858            lock_time: 0,
1859            memory_limit: None,
1860        });
1861
1862        let result = spend.validate();
1863        assert!(result.is_ok(), "Expected valid spend, got {:?}", result);
1864    }
1865
1866    #[test]
1867    fn test_if_else_endif() {
1868        // Test: OP_1 OP_IF OP_2 OP_ELSE OP_3 OP_ENDIF
1869        // Should push 2 (true branch)
1870        let locking = LockingScript::from_asm("OP_IF OP_2 OP_ELSE OP_3 OP_ENDIF").unwrap();
1871        let unlocking = UnlockingScript::from_asm("OP_1").unwrap();
1872
1873        let mut spend = Spend::new(SpendParams {
1874            source_txid: [0u8; 32],
1875            source_output_index: 0,
1876            source_satoshis: 0,
1877            locking_script: locking,
1878            transaction_version: 1,
1879            other_inputs: vec![],
1880            outputs: vec![],
1881            input_index: 0,
1882            unlocking_script: unlocking,
1883            input_sequence: 0xffffffff,
1884            lock_time: 0,
1885            memory_limit: None,
1886        });
1887
1888        let result = spend.validate();
1889        assert!(result.is_ok(), "Expected valid spend, got {:?}", result);
1890    }
1891
1892    #[test]
1893    fn test_hash_operations() {
1894        // Test that hash operations work
1895        // SHA256 produces 32 bytes, we check the size is 32 (0x20)
1896        // Use NIP to remove the hash after SIZE, leaving just the size to compare
1897        let locking = LockingScript::from_asm("OP_SHA256 OP_SIZE OP_NIP 20 OP_EQUAL").unwrap();
1898        let unlocking = UnlockingScript::from_asm("00").unwrap();
1899
1900        let mut spend = Spend::new(SpendParams {
1901            source_txid: [0u8; 32],
1902            source_output_index: 0,
1903            source_satoshis: 0,
1904            locking_script: locking,
1905            transaction_version: 1,
1906            other_inputs: vec![],
1907            outputs: vec![],
1908            input_index: 0,
1909            unlocking_script: unlocking,
1910            input_sequence: 0xffffffff,
1911            lock_time: 0,
1912            memory_limit: None,
1913        });
1914
1915        let result = spend.validate();
1916        assert!(result.is_ok(), "Expected valid spend, got {:?}", result);
1917    }
1918
1919    #[test]
1920    fn test_failing_script() {
1921        // Test: just OP_0 should fail (stack has falsy value)
1922        let locking = LockingScript::from_asm("OP_0").unwrap();
1923        let unlocking = UnlockingScript::new();
1924
1925        let mut spend = Spend::new(SpendParams {
1926            source_txid: [0u8; 32],
1927            source_output_index: 0,
1928            source_satoshis: 0,
1929            locking_script: locking,
1930            transaction_version: 1,
1931            other_inputs: vec![],
1932            outputs: vec![],
1933            input_index: 0,
1934            unlocking_script: unlocking,
1935            input_sequence: 0xffffffff,
1936            lock_time: 0,
1937            memory_limit: None,
1938        });
1939
1940        let result = spend.validate();
1941        assert!(result.is_err(), "Expected failed validation");
1942    }
1943}