Skip to main content

bsv/script/
spend.rs

1//! Bitcoin script interpreter (Spend).
2//!
3//! Evaluates unlocking + locking script pairs to determine whether a
4//! spending transaction satisfies its locking conditions. Implements the
5//! full BSV opcode set including restored and Chronicle 2026 opcodes.
6
7use crate::script::error::ScriptError;
8use crate::script::locking_script::LockingScript;
9use crate::script::op::Op;
10use crate::script::unlocking_script::UnlockingScript;
11use crate::transaction::transaction_input::TransactionInput;
12use crate::transaction::transaction_output::TransactionOutput;
13
14/// Default memory limit: 32 MB.
15const DEFAULT_MEMORY_LIMIT: usize = 32 * 1024 * 1024;
16
17/// Maximum number of opcodes that can be executed before aborting.
18const MAX_OPS: usize = 100_000;
19
20/// Parameters for constructing a Spend interpreter instance.
21pub struct SpendParams {
22    pub locking_script: LockingScript,
23    pub unlocking_script: UnlockingScript,
24    pub source_txid: String,
25    pub source_output_index: usize,
26    pub source_satoshis: u64,
27    pub transaction_version: u32,
28    pub transaction_lock_time: u32,
29    pub transaction_sequence: u32,
30    pub other_inputs: Vec<TransactionInput>,
31    pub other_outputs: Vec<TransactionOutput>,
32    pub input_index: usize,
33}
34
35/// Tracks which script is currently being executed.
36#[derive(Debug, Clone, Copy, PartialEq)]
37pub(crate) enum ScriptContext {
38    Unlocking,
39    Locking,
40}
41
42/// The Bitcoin script interpreter.
43///
44/// Evaluates an unlocking script followed by a locking script,
45/// producing a boolean result indicating whether the spend is valid.
46pub struct Spend {
47    // Script data
48    pub(crate) locking_script: LockingScript,
49    pub(crate) unlocking_script: UnlockingScript,
50
51    // Transaction context (for CHECKSIG)
52    pub(crate) source_txid: String,
53    pub(crate) source_output_index: usize,
54    pub(crate) source_satoshis: u64,
55    pub(crate) transaction_version: u32,
56    pub(crate) transaction_lock_time: u32,
57    pub(crate) transaction_sequence: u32,
58    pub(crate) other_inputs: Vec<TransactionInput>,
59    pub(crate) other_outputs: Vec<TransactionOutput>,
60    pub(crate) input_index: usize,
61
62    // Execution state
63    pub(crate) stack: Vec<Vec<u8>>,
64    pub(crate) alt_stack: Vec<Vec<u8>>,
65    pub(crate) if_stack: Vec<bool>,
66    pub(crate) context: ScriptContext,
67    pub(crate) program_counter: usize,
68    pub(crate) last_code_separator: Option<usize>,
69
70    // OP_RETURN encountered inside an open OP_IF/OP_NOTIF: defer termination
71    // until the matching OP_ENDIF closes the last open conditional. Until
72    // then, only IF/NOTIF/ELSE/ENDIF execute; all other opcodes are skipped.
73    // Mirrors `returningFromConditional` in ts-sdk Spend.ts and
74    // `earlyReturnAfterGenesis` in go-sdk operations.go.
75    pub(crate) returning_from_conditional: bool,
76
77    // Memory management
78    pub(crate) memory_limit: usize,
79    pub(crate) stack_mem: usize,
80    pub(crate) alt_stack_mem: usize,
81
82    // Mode
83    pub(crate) is_relaxed_override: bool,
84
85    // Operations counter (prevents runaway scripts)
86    pub(crate) ops_count: usize,
87}
88
89impl Spend {
90    /// Create a new Spend interpreter from the given parameters.
91    pub fn new(params: SpendParams) -> Self {
92        Spend {
93            locking_script: params.locking_script,
94            unlocking_script: params.unlocking_script,
95            source_txid: params.source_txid,
96            source_output_index: params.source_output_index,
97            source_satoshis: params.source_satoshis,
98            transaction_version: params.transaction_version,
99            transaction_lock_time: params.transaction_lock_time,
100            transaction_sequence: params.transaction_sequence,
101            other_inputs: params.other_inputs,
102            other_outputs: params.other_outputs,
103            input_index: params.input_index,
104            stack: Vec::new(),
105            alt_stack: Vec::new(),
106            if_stack: Vec::new(),
107            context: ScriptContext::Unlocking,
108            program_counter: 0,
109            last_code_separator: None,
110            returning_from_conditional: false,
111            memory_limit: DEFAULT_MEMORY_LIMIT,
112            stack_mem: 0,
113            alt_stack_mem: 0,
114            is_relaxed_override: false,
115            ops_count: 0,
116        }
117    }
118
119    /// Run full script evaluation: unlocking then locking.
120    ///
121    /// Returns true if the spend is valid (top of stack is truthy after evaluation).
122    pub fn validate(&mut self) -> Result<bool, ScriptError> {
123        // Phase 1: Execute unlocking script
124        self.context = ScriptContext::Unlocking;
125        self.program_counter = 0;
126        loop {
127            let done = self.step()?;
128            if done {
129                break;
130            }
131        }
132
133        // After unlocking script, verify push-only if not relaxed
134        if !self.is_relaxed() && !self.unlocking_script.is_push_only() {
135            return Err(ScriptError::PushOnlyViolation);
136        }
137
138        // Phase 2: Execute locking script
139        self.context = ScriptContext::Locking;
140        self.program_counter = 0;
141        self.last_code_separator = None;
142        // Reset deferred-termination flag at script-context boundary (matches
143        // ts-sdk Spend.ts:618 and go-sdk runScript reset semantics).
144        self.returning_from_conditional = false;
145        loop {
146            let done = self.step()?;
147            if done {
148                break;
149            }
150        }
151
152        // Check if_stack is empty (balanced IF/ENDIF)
153        if !self.if_stack.is_empty() {
154            return Err(ScriptError::InvalidScript(
155                "unbalanced IF/ENDIF".to_string(),
156            ));
157        }
158
159        // Enforce clean stack if not relaxed: exactly 1 item remaining
160        if !self.is_relaxed() && self.stack.len() != 1 {
161            return Err(ScriptError::CleanStackViolation);
162        }
163
164        // Check final result
165        if self.stack.is_empty() {
166            return Ok(false);
167        }
168
169        // SAFETY: guarded by is_empty() check above which returns early
170        let top = self.stack.last().unwrap();
171        Ok(Self::stack_to_bool(top))
172    }
173
174    /// Execute a single script chunk.
175    ///
176    /// Returns true if the current script phase is complete, false if more
177    /// chunks remain.
178    pub fn step(&mut self) -> Result<bool, ScriptError> {
179        let chunks = match self.context {
180            ScriptContext::Unlocking => self.unlocking_script.chunks(),
181            ScriptContext::Locking => self.locking_script.chunks(),
182        };
183
184        // Check if we've reached the end of the current script
185        if self.program_counter >= chunks.len() {
186            match self.context {
187                ScriptContext::Unlocking => {
188                    // Signal to validate() that unlocking is done
189                    return Ok(true);
190                }
191                ScriptContext::Locking => {
192                    return Ok(true);
193                }
194            }
195        }
196
197        let chunk = chunks[self.program_counter].clone();
198        let op = chunk.op;
199
200        // Script execution is suspended when either (a) we're in a FALSE
201        // branch of an open IF, or (b) an OP_RETURN inside an open IF set
202        // the `returning_from_conditional` flag. In both cases, only the
203        // structural ops (IF/NOTIF/ELSE/ENDIF/VERIF/VERNOTIF) execute; all
204        // other opcodes are skipped. Mirrors ts-sdk Spend.ts:641
205        // `isScriptExecuting = !returningFromConditional && !ifStack.includes(false)`.
206        let in_exec = self.if_stack.iter().all(|&v| v) && !self.returning_from_conditional;
207
208        if !in_exec {
209            // In a FALSE branch or post-OP_RETURN: only process structural ops.
210            match op {
211                Op::OpIf | Op::OpNotIf | Op::OpVerIf | Op::OpVerNotIf => {
212                    // Nested IF inside false branch: push false to if_stack
213                    self.if_stack.push(false);
214                }
215                Op::OpElse => {
216                    if let Some(last) = self.if_stack.last_mut() {
217                        *last = !*last;
218                    } else {
219                        return Err(ScriptError::InvalidScript(
220                            "OP_ELSE without OP_IF".to_string(),
221                        ));
222                    }
223                }
224                Op::OpEndIf => {
225                    self.if_stack.pop().ok_or_else(|| {
226                        ScriptError::InvalidScript("OP_ENDIF without OP_IF".to_string())
227                    })?;
228                }
229                _ => {
230                    // Skip all other opcodes in false branch
231                }
232            }
233            self.program_counter += 1;
234            // Once OP_ENDIF has closed the last open IF after a deferred
235            // OP_RETURN, jump to end-of-script (TS Spend.ts:1435-1436).
236            if self.returning_from_conditional && self.if_stack.is_empty() {
237                self.program_counter = chunks.len();
238                self.returning_from_conditional = false;
239            }
240            return Ok(false);
241        }
242
243        // Guard against runaway scripts
244        self.ops_count += 1;
245        if self.ops_count > MAX_OPS {
246            return Err(ScriptError::InvalidScript(
247                "exceeded maximum operation count".to_string(),
248            ));
249        }
250
251        // Execute the opcode
252        self.execute_opcode(op, &chunk)?;
253        self.program_counter += 1;
254
255        Ok(false)
256    }
257
258    /// Whether the interpreter is in relaxed mode.
259    ///
260    /// Relaxed mode is active when transaction_version > 1 or explicitly overridden.
261    /// In relaxed mode: clean stack, minimal encoding, NULLDUMMY, low-S, and
262    /// push-only checks are not enforced.
263    pub fn is_relaxed(&self) -> bool {
264        self.transaction_version > 1 || self.is_relaxed_override
265    }
266
267    /// Override the relaxed mode flag.
268    pub fn set_relaxed_override(&mut self, v: bool) {
269        self.is_relaxed_override = v;
270    }
271}
272
273#[cfg(test)]
274mod tests {
275    use super::*;
276    use crate::script::locking_script::LockingScript;
277    use crate::script::unlocking_script::UnlockingScript;
278
279    /// Helper to create a simple Spend with default transaction context.
280    fn make_spend(unlocking_asm: &str, locking_asm: &str) -> Spend {
281        Spend::new(SpendParams {
282            locking_script: LockingScript::from_asm(locking_asm),
283            unlocking_script: UnlockingScript::from_asm(unlocking_asm),
284            source_txid: "00".repeat(32),
285            source_output_index: 0,
286            source_satoshis: 0,
287            transaction_version: 1,
288            transaction_lock_time: 0,
289            transaction_sequence: 0xffffffff,
290            other_inputs: vec![],
291            other_outputs: vec![],
292            input_index: 0,
293        })
294    }
295
296    /// Helper: create a relaxed-mode spend.
297    fn make_relaxed_spend(unlocking_asm: &str, locking_asm: &str) -> Spend {
298        Spend::new(SpendParams {
299            locking_script: LockingScript::from_asm(locking_asm),
300            unlocking_script: UnlockingScript::from_asm(unlocking_asm),
301            source_txid: "00".repeat(32),
302            source_output_index: 0,
303            source_satoshis: 0,
304            transaction_version: 2, // relaxed
305            transaction_lock_time: 0,
306            transaction_sequence: 0xffffffff,
307            other_inputs: vec![],
308            other_outputs: vec![],
309            input_index: 0,
310        })
311    }
312
313    #[test]
314    fn test_simple_push_only_script() {
315        // Unlocking: OP_1, Locking: (empty -- just check top of stack)
316        // In strict mode an empty locking script means stack has 1 item [1] => true
317        let mut spend = make_spend("OP_1", "");
318        let result = spend.validate().unwrap();
319        assert!(result, "OP_1 should leave true on stack");
320    }
321
322    #[test]
323    fn test_op1_pushes_one() {
324        let mut spend = make_spend("OP_1", "");
325        spend.validate().unwrap();
326        assert_eq!(spend.stack, vec![vec![1u8]]);
327    }
328
329    #[test]
330    fn test_op_numbers() {
331        // OP_2 through OP_16
332        for n in 2..=16u8 {
333            let asm = format!("OP_{}", n);
334            let mut spend = make_spend(&asm, "");
335            spend.validate().unwrap();
336            assert_eq!(spend.stack, vec![vec![n]], "OP_{} should push [{}]", n, n);
337        }
338    }
339
340    #[test]
341    fn test_op_0_pushes_empty() {
342        // OP_0 pushes empty vec; in relaxed mode clean stack is not enforced
343        let mut spend = make_relaxed_spend("0 OP_1", "");
344        spend.validate().unwrap();
345        assert_eq!(spend.stack.len(), 2);
346        assert_eq!(spend.stack[0], Vec::<u8>::new());
347        assert_eq!(spend.stack[1], vec![1u8]);
348    }
349
350    #[test]
351    fn test_op_1negate() {
352        let mut spend = make_spend("-1", "OP_1 OP_ADD");
353        // -1 + 1 = 0, which is false
354        let result = spend.validate().unwrap();
355        assert!(!result, "-1 + 1 = 0 should be false");
356    }
357
358    #[test]
359    fn test_if_else_endif_true_branch() {
360        // Unlocking: OP_1, Locking: OP_IF OP_2 OP_ELSE OP_3 OP_ENDIF
361        let mut spend = make_spend("OP_1", "OP_IF OP_2 OP_ELSE OP_3 OP_ENDIF");
362        let result = spend.validate().unwrap();
363        assert!(result);
364        assert_eq!(spend.stack, vec![vec![2u8]]);
365    }
366
367    #[test]
368    fn test_if_else_endif_false_branch() {
369        let mut spend = make_spend("0", "OP_IF OP_2 OP_ELSE OP_3 OP_ENDIF");
370        let result = spend.validate().unwrap();
371        assert!(result);
372        assert_eq!(spend.stack, vec![vec![3u8]]);
373    }
374
375    #[test]
376    fn test_nested_if() {
377        // OP_1 OP_1 | OP_IF OP_IF OP_5 OP_ENDIF OP_ENDIF
378        let mut spend = make_spend("OP_1 OP_1", "OP_IF OP_IF OP_5 OP_ENDIF OP_ENDIF");
379        let result = spend.validate().unwrap();
380        assert!(result);
381        assert_eq!(spend.stack, vec![vec![5u8]]);
382    }
383
384    #[test]
385    fn test_op_verify_true() {
386        let mut spend = make_spend("OP_1", "OP_VERIFY OP_1");
387        let result = spend.validate().unwrap();
388        assert!(result);
389    }
390
391    #[test]
392    fn test_op_verify_false() {
393        let mut spend = make_spend("0", "OP_VERIFY OP_1");
394        let result = spend.validate();
395        assert!(result.is_err());
396    }
397
398    #[test]
399    fn test_op_return_terminates_successfully() {
400        // Post-Genesis BSV semantics (matching ts-sdk Spend.ts:902-913 and
401        // go-sdk operations.go:585-598): top-level OP_RETURN is a successful
402        // early termination. The script's stack at that moment determines
403        // validity — here the unlocking pushed OP_1 (truthy), so validate
404        // returns Ok(true). Required by covenant scripts (e.g. STAS-3)
405        // that end with OP_RETURN as a success marker.
406        let mut spend = make_spend("OP_1", "OP_RETURN");
407        let result = spend.validate();
408        assert!(matches!(result, Ok(true)));
409    }
410
411    #[test]
412    fn test_op_return_with_falsy_stack_returns_false() {
413        // Top-level OP_RETURN is a *successful early termination*: the script
414        // exits cleanly, but validity is still decided by what's on the stack
415        // at that moment. Empty/zero top-of-stack means validate returns
416        // Ok(false), not Err. Mirrors ts-sdk Spend.ts:902-913.
417        let mut spend = make_relaxed_spend("OP_0", "OP_RETURN");
418        let result = spend.validate();
419        assert!(matches!(result, Ok(false)));
420    }
421
422    #[test]
423    fn test_op_return_data_carrier_idiom() {
424        // The canonical BSV NullData pattern is a locking script of
425        // `OP_FALSE OP_RETURN <data>`. The parser absorbs the trailing data
426        // into the OP_RETURN chunk so only two chunks execute. Stack-top is
427        // OP_FALSE (falsy) at OP_RETURN time, so validate returns Ok(false).
428        let mut spend = make_relaxed_spend("", "OP_FALSE OP_RETURN 6c6f6c");
429        let result = spend.validate();
430        assert!(matches!(result, Ok(false)));
431    }
432
433    #[test]
434    fn test_op_return_in_unexecuted_branch_does_not_terminate() {
435        // OP_RETURN inside the FALSE branch of an OP_IF must not fire — the
436        // existing `in_exec` gate skips it. Script continues past ENDIF and
437        // the trailing OP_1 leaves a truthy top. Mirrors ts-sdk
438        // Spend.ts:641 isScriptExecuting gate.
439        let mut spend = make_spend("OP_0", "OP_IF OP_RETURN OP_ENDIF OP_1");
440        let result = spend.validate();
441        assert!(matches!(result, Ok(true)));
442    }
443
444    #[test]
445    fn test_op_return_in_executing_if_branch_defers_termination() {
446        // OP_RETURN inside an *executing* OP_IF branch sets
447        // returningFromConditional and defers termination to the matching
448        // OP_ENDIF. Without this, the script returns ErrUnbalancedConditional
449        // because the unconditional jump to chunks_len skips the ENDIF.
450        // After ENDIF closes the IF, the script jumps to end with the
451        // current stack — `OP_1` was pushed before the IF, so Ok(true).
452        // Mirrors ts-sdk Spend.ts:902-913 + 1435-1436.
453        let mut spend = make_spend("OP_1", "OP_1 OP_IF OP_RETURN OP_ENDIF");
454        let result = spend.validate();
455        assert!(matches!(result, Ok(true)));
456    }
457
458    #[test]
459    fn test_op_return_in_nested_if_defers_until_outermost_endif() {
460        // Deferred termination must wait for the LAST open IF to close —
461        // not just the innermost. Sets returningFromConditional inside the
462        // inner IF; only after both ENDIFs is the if_stack empty and the
463        // jump-to-end fires. Stack at OP_RETURN time is [OP_1] (truthy).
464        let mut spend = make_spend("OP_1", "OP_1 OP_IF OP_1 OP_IF OP_RETURN OP_ENDIF OP_ENDIF");
465        let result = spend.validate();
466        assert!(matches!(result, Ok(true)));
467    }
468
469    #[test]
470    fn test_memory_limit_exceeded() {
471        // Create a spend with a very low memory limit
472        let mut spend = make_spend("OP_1", "");
473        spend.memory_limit = 0;
474        let result = spend.validate();
475        assert!(matches!(result, Err(ScriptError::MemoryLimitExceeded)));
476    }
477
478    #[test]
479    fn test_stack_underflow() {
480        // Empty unlocking (push-only) + OP_DUP on empty stack
481        // Should fail with either StackUnderflow or InvalidStackOperation
482        let mut spend = make_spend("", "OP_DUP");
483        let result = spend.validate();
484        assert!(result.is_err(), "expected error but got {:?}", result);
485    }
486
487    #[test]
488    fn test_stack_to_bool() {
489        assert!(!Spend::stack_to_bool(&[]));
490        assert!(!Spend::stack_to_bool(&[0]));
491        assert!(!Spend::stack_to_bool(&[0, 0]));
492        assert!(!Spend::stack_to_bool(&[0x80])); // negative zero
493        assert!(!Spend::stack_to_bool(&[0, 0x80])); // negative zero
494        assert!(Spend::stack_to_bool(&[1]));
495        assert!(Spend::stack_to_bool(&[0, 1]));
496        assert!(Spend::stack_to_bool(&[0x81])); // -1
497    }
498
499    #[test]
500    fn test_clean_stack_violation() {
501        // In strict mode, stack must have exactly 1 item
502        let mut spend = make_spend("OP_1 OP_2", "");
503        let result = spend.validate();
504        assert!(matches!(result, Err(ScriptError::CleanStackViolation)));
505    }
506
507    #[test]
508    fn test_relaxed_mode_no_clean_stack() {
509        // In relaxed mode, clean stack is not enforced
510        let mut spend = make_relaxed_spend("OP_1 OP_2", "");
511        let result = spend.validate().unwrap();
512        assert!(result); // top is [2] which is truthy
513    }
514
515    #[test]
516    fn test_push_only_violation() {
517        // Non-push-only unlocking script in strict mode
518        let mut spend = make_spend("", "OP_1");
519        // Manually set unlocking to have a non-push opcode
520        spend.unlocking_script = UnlockingScript::from_asm("OP_DUP");
521        // We need something on the stack for OP_DUP to work
522        spend.stack.push(vec![1]);
523        let result = spend.validate();
524        assert!(matches!(result, Err(ScriptError::PushOnlyViolation)));
525    }
526
527    #[test]
528    fn test_step_api() {
529        let mut spend = make_spend("OP_1 OP_2", "OP_ADD");
530
531        // Step through unlocking script
532        assert!(!spend.step().unwrap()); // OP_1
533        assert_eq!(spend.stack, vec![vec![1u8]]);
534
535        assert!(!spend.step().unwrap()); // OP_2
536        assert_eq!(spend.stack, vec![vec![1u8], vec![2u8]]);
537
538        assert!(spend.step().unwrap()); // end of unlocking
539
540        // Switch to locking
541        spend.context = ScriptContext::Locking;
542        spend.program_counter = 0;
543
544        assert!(!spend.step().unwrap()); // OP_ADD
545        assert_eq!(spend.stack, vec![vec![3u8]]);
546
547        assert!(spend.step().unwrap()); // end of locking
548    }
549
550    #[test]
551    fn test_is_relaxed() {
552        let spend = make_spend("", "");
553        assert!(!spend.is_relaxed()); // version 1
554
555        let spend2 = make_relaxed_spend("", "");
556        assert!(spend2.is_relaxed()); // version 2
557
558        let mut spend3 = make_spend("", "");
559        spend3.set_relaxed_override(true);
560        assert!(spend3.is_relaxed()); // override
561    }
562
563    #[test]
564    fn test_op_notif() {
565        // OP_0 | OP_NOTIF OP_5 OP_ENDIF (false => NOTIF enters true branch)
566        let mut spend = make_spend("0", "OP_NOTIF OP_5 OP_ENDIF");
567        let result = spend.validate().unwrap();
568        assert!(result);
569        assert_eq!(spend.stack, vec![vec![5u8]]);
570    }
571
572    // =================================================================
573    // Task 2: Additional opcode unit tests
574    // =================================================================
575
576    #[test]
577    fn test_op_add() {
578        let mut spend = make_spend("OP_3 OP_4", "OP_ADD OP_7 OP_EQUAL");
579        assert!(spend.validate().unwrap());
580    }
581
582    #[test]
583    fn test_op_sub() {
584        let mut spend = make_spend("OP_5 OP_3", "OP_SUB OP_2 OP_EQUAL");
585        assert!(spend.validate().unwrap());
586    }
587
588    #[test]
589    fn test_op_mul() {
590        let mut spend = make_spend("OP_3 OP_4", "OP_MUL OP_12 OP_EQUAL");
591        assert!(spend.validate().unwrap());
592    }
593
594    #[test]
595    fn test_op_div() {
596        let mut spend = make_spend("OP_6 OP_3", "OP_DIV OP_2 OP_EQUAL");
597        assert!(spend.validate().unwrap());
598    }
599
600    #[test]
601    fn test_op_mod() {
602        let mut spend = make_spend("OP_7 OP_3", "OP_MOD OP_1 OP_EQUAL");
603        assert!(spend.validate().unwrap());
604    }
605
606    #[test]
607    fn test_op_div_by_zero() {
608        let mut spend = make_spend("OP_5 0", "OP_DIV");
609        assert!(spend.validate().is_err());
610    }
611
612    #[test]
613    fn test_op_equal() {
614        let mut spend = make_spend("OP_3 OP_3", "OP_EQUAL");
615        assert!(spend.validate().unwrap());
616    }
617
618    #[test]
619    fn test_op_equal_false() {
620        let mut spend = make_spend("OP_3 OP_4", "OP_EQUAL");
621        assert!(!spend.validate().unwrap());
622    }
623
624    #[test]
625    fn test_op_equalverify() {
626        let mut spend = make_spend("OP_3 OP_3", "OP_EQUALVERIFY OP_1");
627        assert!(spend.validate().unwrap());
628    }
629
630    #[test]
631    fn test_op_equalverify_fail() {
632        let mut spend = make_spend("OP_3 OP_4", "OP_EQUALVERIFY OP_1");
633        assert!(spend.validate().is_err());
634    }
635
636    #[test]
637    fn test_op_dup_hash160_equalverify() {
638        // P2PKH-like pattern: push pubkey, DUP, HASH160, compare, then DROP pubkey.
639        // After EQUALVERIFY: stack=[pubkey]. DROP clears it, OP_1 leaves [1].
640        use crate::primitives::hash::hash160;
641        let pubkey_data = vec![0x04; 33]; // dummy compressed pubkey
642        let hash = hash160(&pubkey_data);
643        let hash_hex: String = hash.iter().map(|b| format!("{:02x}", b)).collect();
644        let pubkey_hex: String = pubkey_data.iter().map(|b| format!("{:02x}", b)).collect();
645
646        let locking = format!("OP_DUP OP_HASH160 {} OP_EQUALVERIFY OP_DROP OP_1", hash_hex);
647        let mut spend = make_spend(&pubkey_hex, &locking);
648        assert!(spend.validate().unwrap());
649    }
650
651    #[test]
652    fn test_op_cat() {
653        // Concatenate two byte arrays
654        // Push 0x01, push 0x02, OP_CAT should give 0x0102
655        let mut spend = make_relaxed_spend("01 02", "OP_CAT 0102 OP_EQUAL");
656        assert!(spend.validate().unwrap());
657    }
658
659    #[test]
660    fn test_op_split() {
661        // Split 0x010203 at position 1 should give 0x01 and 0x0203
662        let mut spend =
663            make_relaxed_spend("010203 OP_1", "OP_SPLIT 0203 OP_EQUALVERIFY 01 OP_EQUAL");
664        assert!(spend.validate().unwrap());
665    }
666
667    #[test]
668    fn test_op_size() {
669        let mut spend = make_relaxed_spend("010203", "OP_SIZE OP_3 OP_EQUALVERIFY OP_1");
670        assert!(spend.validate().unwrap());
671    }
672
673    #[test]
674    fn test_op_sha256() {
675        // SHA256 of OP_0 (empty bytes) = 32 bytes
676        let mut spend = make_relaxed_spend("0", "OP_SHA256 OP_SIZE");
677        spend.validate().unwrap();
678        assert_eq!(spend.stack.len(), 2);
679        assert_eq!(spend.stack[0].len(), 32);
680    }
681
682    #[test]
683    fn test_op_hash160() {
684        // OP_HASH160 produces 20-byte result
685        let mut spend = make_relaxed_spend("01", "OP_HASH160 OP_SIZE");
686        spend.validate().unwrap();
687        assert_eq!(spend.stack[0].len(), 20);
688    }
689
690    #[test]
691    fn test_op_hash256() {
692        // OP_HASH256 produces 32-byte result
693        let mut spend = make_relaxed_spend("01", "OP_HASH256 OP_SIZE");
694        spend.validate().unwrap();
695        assert_eq!(spend.stack[0].len(), 32);
696    }
697
698    #[test]
699    fn test_op_toaltstack_fromaltstack() {
700        let mut spend = make_spend("OP_1", "OP_TOALTSTACK OP_FROMALTSTACK");
701        assert!(spend.validate().unwrap());
702        assert_eq!(spend.stack, vec![vec![1u8]]);
703    }
704
705    #[test]
706    fn test_op_depth() {
707        // Unlocking pushes 1,2,3 (3 items). Locking: DEPTH pushes 3 -> [1,2,3,3].
708        // 3 EQUALVERIFY verifies depth==3 -> [1,2,3]. DROP DROP DROP clears all.
709        // OP_1 leaves [1] for clean stack.
710        let mut spend = make_spend(
711            "OP_1 OP_2 OP_3",
712            "OP_DEPTH OP_3 OP_EQUALVERIFY OP_DROP OP_DROP OP_DROP OP_1",
713        );
714        let result = spend.validate();
715        assert!(result.is_ok(), "test_op_depth failed: {:?}", result);
716    }
717
718    #[test]
719    fn test_op_swap() {
720        let mut spend = make_relaxed_spend("OP_1 OP_2", "OP_SWAP");
721        spend.validate().unwrap();
722        assert_eq!(spend.stack, vec![vec![2u8], vec![1u8]]);
723    }
724
725    #[test]
726    fn test_op_rot() {
727        let mut spend = make_relaxed_spend("OP_1 OP_2 OP_3", "OP_ROT");
728        spend.validate().unwrap();
729        assert_eq!(spend.stack, vec![vec![2u8], vec![3u8], vec![1u8]]);
730    }
731
732    #[test]
733    fn test_op_over() {
734        let mut spend = make_relaxed_spend("OP_1 OP_2", "OP_OVER");
735        spend.validate().unwrap();
736        assert_eq!(spend.stack, vec![vec![1u8], vec![2u8], vec![1u8]]);
737    }
738
739    #[test]
740    fn test_op_nip() {
741        let mut spend = make_relaxed_spend("OP_1 OP_2", "OP_NIP");
742        spend.validate().unwrap();
743        assert_eq!(spend.stack, vec![vec![2u8]]);
744    }
745
746    #[test]
747    fn test_op_tuck() {
748        let mut spend = make_relaxed_spend("OP_1 OP_2", "OP_TUCK");
749        spend.validate().unwrap();
750        assert_eq!(spend.stack, vec![vec![2u8], vec![1u8], vec![2u8]]);
751    }
752
753    #[test]
754    fn test_op_pick() {
755        let mut spend = make_relaxed_spend("OP_1 OP_2 OP_3 OP_2", "OP_PICK");
756        spend.validate().unwrap();
757        assert_eq!(
758            spend.stack,
759            vec![vec![1u8], vec![2u8], vec![3u8], vec![1u8]]
760        );
761    }
762
763    #[test]
764    fn test_op_roll() {
765        let mut spend = make_relaxed_spend("OP_1 OP_2 OP_3 OP_2", "OP_ROLL");
766        spend.validate().unwrap();
767        assert_eq!(spend.stack, vec![vec![2u8], vec![3u8], vec![1u8]]);
768    }
769
770    #[test]
771    fn test_op_2dup() {
772        let mut spend = make_relaxed_spend("OP_1 OP_2", "OP_2DUP");
773        spend.validate().unwrap();
774        assert_eq!(
775            spend.stack,
776            vec![vec![1u8], vec![2u8], vec![1u8], vec![2u8]]
777        );
778    }
779
780    #[test]
781    fn test_op_3dup() {
782        let mut spend = make_relaxed_spend("OP_1 OP_2 OP_3", "OP_3DUP");
783        spend.validate().unwrap();
784        assert_eq!(spend.stack.len(), 6);
785    }
786
787    #[test]
788    fn test_op_2drop() {
789        let mut spend = make_spend("OP_1 OP_2 OP_3", "OP_2DROP");
790        assert!(spend.validate().unwrap());
791        assert_eq!(spend.stack, vec![vec![1u8]]);
792    }
793
794    #[test]
795    fn test_op_2swap() {
796        let mut spend = make_relaxed_spend("OP_1 OP_2 OP_3 OP_4", "OP_2SWAP");
797        spend.validate().unwrap();
798        assert_eq!(
799            spend.stack,
800            vec![vec![3u8], vec![4u8], vec![1u8], vec![2u8]]
801        );
802    }
803
804    #[test]
805    fn test_op_ifdup_true() {
806        let mut spend = make_relaxed_spend("OP_1", "OP_IFDUP");
807        spend.validate().unwrap();
808        assert_eq!(spend.stack, vec![vec![1u8], vec![1u8]]);
809    }
810
811    #[test]
812    fn test_op_ifdup_false() {
813        let mut spend = make_relaxed_spend("0", "OP_IFDUP");
814        spend.validate().unwrap();
815        assert_eq!(spend.stack, vec![Vec::<u8>::new()]);
816    }
817
818    #[test]
819    fn test_op_lessthan() {
820        let mut spend = make_spend("OP_1 OP_2", "OP_LESSTHAN");
821        assert!(spend.validate().unwrap());
822    }
823
824    #[test]
825    fn test_op_greaterthan() {
826        let mut spend = make_spend("OP_3 OP_2", "OP_GREATERTHAN");
827        assert!(spend.validate().unwrap());
828    }
829
830    #[test]
831    fn test_op_within() {
832        // 3 is within [2, 5)
833        let mut spend = make_spend("OP_3 OP_2 OP_5", "OP_WITHIN");
834        assert!(spend.validate().unwrap());
835    }
836
837    #[test]
838    fn test_op_within_false() {
839        // 5 is not within [2, 5)
840        let mut spend = make_spend("OP_5 OP_2 OP_5", "OP_WITHIN");
841        assert!(!spend.validate().unwrap());
842    }
843
844    #[test]
845    fn test_op_min() {
846        let mut spend = make_spend("OP_3 OP_5", "OP_MIN OP_3 OP_EQUAL");
847        assert!(spend.validate().unwrap());
848    }
849
850    #[test]
851    fn test_op_max() {
852        let mut spend = make_spend("OP_3 OP_5", "OP_MAX OP_5 OP_EQUAL");
853        assert!(spend.validate().unwrap());
854    }
855
856    #[test]
857    fn test_op_booland() {
858        let mut spend = make_spend("OP_1 OP_1", "OP_BOOLAND");
859        assert!(spend.validate().unwrap());
860
861        let mut spend2 = make_spend("OP_1 0", "OP_BOOLAND");
862        assert!(!spend2.validate().unwrap());
863    }
864
865    #[test]
866    fn test_op_boolor() {
867        let mut spend = make_spend("0 0", "OP_BOOLOR");
868        assert!(!spend.validate().unwrap());
869
870        let mut spend2 = make_spend("OP_1 0", "OP_BOOLOR");
871        assert!(spend2.validate().unwrap());
872    }
873
874    #[test]
875    fn test_op_abs() {
876        // -1 ABS should give 1
877        let mut spend = make_spend("-1", "OP_ABS OP_1 OP_EQUAL");
878        assert!(spend.validate().unwrap());
879    }
880
881    #[test]
882    fn test_op_not() {
883        let mut spend = make_spend("0", "OP_NOT"); // NOT(0) = 1
884        assert!(spend.validate().unwrap());
885
886        let mut spend2 = make_spend("OP_1", "OP_NOT"); // NOT(1) = 0
887        assert!(!spend2.validate().unwrap());
888    }
889
890    #[test]
891    fn test_op_0notequal() {
892        let mut spend = make_spend("OP_5", "OP_0NOTEQUAL");
893        assert!(spend.validate().unwrap());
894
895        let mut spend2 = make_spend("0", "OP_0NOTEQUAL");
896        assert!(!spend2.validate().unwrap());
897    }
898
899    #[test]
900    fn test_op_negate() {
901        let mut spend = make_spend("OP_5", "OP_NEGATE OP_ABS OP_5 OP_EQUAL");
902        assert!(spend.validate().unwrap());
903    }
904
905    #[test]
906    fn test_op_1add_1sub() {
907        let mut spend = make_spend("OP_5", "OP_1ADD OP_6 OP_EQUAL");
908        assert!(spend.validate().unwrap());
909
910        let mut spend2 = make_spend("OP_5", "OP_1SUB OP_4 OP_EQUAL");
911        assert!(spend2.validate().unwrap());
912    }
913
914    #[test]
915    fn test_op_numequal() {
916        let mut spend = make_spend("OP_3 OP_3", "OP_NUMEQUAL");
917        assert!(spend.validate().unwrap());
918    }
919
920    #[test]
921    fn test_op_numequalverify() {
922        let mut spend = make_spend("OP_3 OP_3", "OP_NUMEQUALVERIFY OP_1");
923        assert!(spend.validate().unwrap());
924    }
925
926    #[test]
927    fn test_op_numnotequal() {
928        let mut spend = make_spend("OP_3 OP_4", "OP_NUMNOTEQUAL");
929        assert!(spend.validate().unwrap());
930    }
931
932    #[test]
933    fn test_op_invert() {
934        // Invert 0x00 = 0xff
935        let mut spend = make_relaxed_spend("00", "OP_INVERT");
936        spend.validate().unwrap();
937        assert_eq!(spend.stack, vec![vec![0xff]]);
938    }
939
940    #[test]
941    fn test_op_and() {
942        let mut spend = make_relaxed_spend("ff 0f", "OP_AND");
943        spend.validate().unwrap();
944        assert_eq!(spend.stack, vec![vec![0x0f]]);
945    }
946
947    #[test]
948    fn test_op_or() {
949        let mut spend = make_relaxed_spend("f0 0f", "OP_OR");
950        spend.validate().unwrap();
951        assert_eq!(spend.stack, vec![vec![0xff]]);
952    }
953
954    #[test]
955    fn test_op_xor() {
956        let mut spend = make_relaxed_spend("ff ff", "OP_XOR");
957        spend.validate().unwrap();
958        assert_eq!(spend.stack, vec![vec![0x00]]);
959    }
960
961    #[test]
962    fn test_nested_if_deep() {
963        // OP_1 OP_1 OP_1 | OP_IF OP_IF OP_IF OP_7 OP_ENDIF OP_ENDIF OP_ENDIF
964        let mut spend = make_spend(
965            "OP_1 OP_1 OP_1",
966            "OP_IF OP_IF OP_IF OP_7 OP_ENDIF OP_ENDIF OP_ENDIF",
967        );
968        assert!(spend.validate().unwrap());
969        assert_eq!(spend.stack, vec![vec![7u8]]);
970    }
971
972    #[test]
973    fn test_op_codeseparator() {
974        // OP_CODESEPARATOR should not affect normal execution
975        let mut spend = make_spend("OP_1", "OP_CODESEPARATOR");
976        assert!(spend.validate().unwrap());
977    }
978
979    #[test]
980    fn test_chronicle_op_substr() {
981        // OP_SUBSTR: data, begin, len -> substring
982        let mut spend = make_relaxed_spend("0102030405 OP_1 OP_2", "OP_SUBSTR 0203 OP_EQUAL");
983        assert!(spend.validate().unwrap());
984    }
985
986    #[test]
987    fn test_chronicle_op_left() {
988        let mut spend = make_relaxed_spend("01020304 OP_2", "OP_LEFT 0102 OP_EQUAL");
989        assert!(spend.validate().unwrap());
990    }
991
992    #[test]
993    fn test_chronicle_op_right() {
994        let mut spend = make_relaxed_spend("01020304 OP_2", "OP_RIGHT 0304 OP_EQUAL");
995        assert!(spend.validate().unwrap());
996    }
997
998    #[test]
999    fn test_op_ripemd160() {
1000        let mut spend = make_relaxed_spend("01", "OP_RIPEMD160 OP_SIZE");
1001        spend.validate().unwrap();
1002        assert_eq!(spend.stack[0].len(), 20);
1003    }
1004
1005    #[test]
1006    fn test_op_sha1() {
1007        let mut spend = make_relaxed_spend("01", "OP_SHA1 OP_SIZE");
1008        spend.validate().unwrap();
1009        assert_eq!(spend.stack[0].len(), 20);
1010    }
1011
1012    #[test]
1013    fn test_op_checksig_empty_sig_fails() {
1014        // Empty signature should push false
1015        let mut spend = make_relaxed_spend("0 01", "OP_CHECKSIG");
1016        spend.validate().unwrap();
1017        // Empty sig -> pushes false
1018        assert_eq!(spend.stack, vec![Vec::<u8>::new()]);
1019    }
1020
1021    #[test]
1022    fn test_relaxed_mode_gating() {
1023        // In relaxed mode, clean stack not enforced
1024        let mut spend = make_relaxed_spend("OP_1 OP_2 OP_3", "");
1025        let result = spend.validate().unwrap();
1026        assert!(result); // top is 3
1027
1028        // In strict mode, multiple items = clean stack violation
1029        let mut spend2 = make_spend("OP_1 OP_2 OP_3", "");
1030        assert!(matches!(
1031            spend2.validate(),
1032            Err(ScriptError::CleanStackViolation)
1033        ));
1034    }
1035
1036    #[test]
1037    fn test_op_disabled_2mul() {
1038        let mut spend = make_spend("OP_1", "OP_2MUL");
1039        let result = spend.validate();
1040        assert!(matches!(result, Err(ScriptError::DisabledOpcode(_))));
1041    }
1042
1043    #[test]
1044    fn test_op_disabled_2div() {
1045        let mut spend = make_spend("OP_1", "OP_2DIV");
1046        let result = spend.validate();
1047        assert!(matches!(result, Err(ScriptError::DisabledOpcode(_))));
1048    }
1049
1050    // =================================================================
1051    // script_tests.json test runner
1052    // =================================================================
1053
1054    /// Parse the script_tests.json ASM format into our Script type.
1055    ///
1056    /// The test format uses short opcode names (DUP instead of OP_DUP),
1057    /// hex pushes (0x02 0x0100), and string literals ('text').
1058    fn parse_test_asm(asm: &str) -> crate::script::script::Script {
1059        use crate::script::script::Script;
1060        use crate::script::script_chunk::ScriptChunk;
1061
1062        let asm = asm.trim();
1063        if asm.is_empty() {
1064            return Script::new();
1065        }
1066
1067        let mut chunks = Vec::new();
1068        let tokens: Vec<&str> = asm.split_whitespace().collect();
1069        let mut i = 0;
1070
1071        while i < tokens.len() {
1072            let token = tokens[i];
1073
1074            // Number literals
1075            if token == "0" {
1076                chunks.push(ScriptChunk::new_opcode(Op::Op0));
1077                i += 1;
1078                continue;
1079            }
1080            if token == "-1" {
1081                chunks.push(ScriptChunk::new_opcode(Op::Op1Negate));
1082                i += 1;
1083                continue;
1084            }
1085
1086            // String literal: 'text'
1087            if token.starts_with('\'') {
1088                let text = if token.ends_with('\'') && token.len() > 1 {
1089                    &token[1..token.len() - 1]
1090                } else {
1091                    // Multi-word string? collect until closing quote
1092                    let mut s = token[1..].to_string();
1093                    loop {
1094                        i += 1;
1095                        if i >= tokens.len() {
1096                            break;
1097                        }
1098                        s.push(' ');
1099                        s.push_str(tokens[i]);
1100                        if tokens[i].ends_with('\'') {
1101                            s.truncate(s.len() - 1);
1102                            break;
1103                        }
1104                    }
1105                    i += 1;
1106                    let data = s.into_bytes();
1107                    let len = data.len();
1108                    let op_byte = if len < 0x4c { len as u8 } else { 0x4c };
1109                    chunks.push(ScriptChunk::new_raw(op_byte, Some(data)));
1110                    continue;
1111                };
1112                let data = text.as_bytes().to_vec();
1113                let len = data.len();
1114                let op_byte = if len < 0x4c { len as u8 } else { 0x4c };
1115                chunks.push(ScriptChunk::new_raw(op_byte, Some(data)));
1116                i += 1;
1117                continue;
1118            }
1119
1120            // Hex push: 0x02 0x0100
1121            if token.starts_with("0x") || token.starts_with("0X") {
1122                let hex = &token[2..];
1123                if let Ok(push_len) = usize::from_str_radix(hex, 16) {
1124                    // This might be a push length followed by hex data
1125                    if push_len > 0 && push_len <= 0x4e && i + 1 < tokens.len() {
1126                        let next = tokens[i + 1];
1127                        if next.starts_with("0x") || next.starts_with("0X") {
1128                            let data_hex = &next[2..];
1129                            if let Ok(data) = hex_decode(data_hex) {
1130                                chunks.push(ScriptChunk::new_raw(push_len as u8, Some(data)));
1131                                i += 2;
1132                                continue;
1133                            }
1134                        }
1135                    }
1136                    // Single byte opcode
1137                    if push_len <= 0xff {
1138                        let op = Op::from(push_len as u8);
1139                        chunks.push(ScriptChunk::new_raw(push_len as u8, None));
1140                        let _ = op; // just to use it
1141                        i += 1;
1142                        continue;
1143                    }
1144                }
1145                i += 1;
1146                continue;
1147            }
1148
1149            // PUSHDATA1/2/4 with explicit length and data
1150            if token == "PUSHDATA1" || token == "OP_PUSHDATA1" {
1151                if i + 2 < tokens.len() {
1152                    let _len_hex = tokens[i + 1].strip_prefix("0x").unwrap_or(tokens[i + 1]);
1153                    let data_hex = tokens[i + 2].strip_prefix("0x").unwrap_or(tokens[i + 2]);
1154                    if let Ok(data) = hex_decode(data_hex) {
1155                        chunks.push(ScriptChunk::new_raw(0x4c, Some(data)));
1156                        i += 3;
1157                        continue;
1158                    }
1159                }
1160                i += 1;
1161                continue;
1162            }
1163            if token == "PUSHDATA2" || token == "OP_PUSHDATA2" {
1164                if i + 2 < tokens.len() {
1165                    let data_hex = tokens[i + 2].strip_prefix("0x").unwrap_or(tokens[i + 2]);
1166                    if let Ok(data) = hex_decode(data_hex) {
1167                        chunks.push(ScriptChunk::new_raw(0x4d, Some(data)));
1168                        i += 3;
1169                        continue;
1170                    }
1171                }
1172                i += 1;
1173                continue;
1174            }
1175            if token == "PUSHDATA4" || token == "OP_PUSHDATA4" {
1176                if i + 2 < tokens.len() {
1177                    let data_hex = tokens[i + 2].strip_prefix("0x").unwrap_or(tokens[i + 2]);
1178                    if let Ok(data) = hex_decode(data_hex) {
1179                        chunks.push(ScriptChunk::new_raw(0x4e, Some(data)));
1180                        i += 3;
1181                        continue;
1182                    }
1183                }
1184                i += 1;
1185                continue;
1186            }
1187
1188            // Try as opcode (with or without OP_ prefix)
1189            if let Some(op) =
1190                Op::from_name(token).or_else(|| Op::from_name(&format!("OP_{}", token)))
1191            {
1192                chunks.push(ScriptChunk::new_opcode(op));
1193                i += 1;
1194                continue;
1195            }
1196
1197            // Decimal number
1198            if let Ok(n) = token.parse::<i64>() {
1199                use crate::primitives::big_number::BigNumber;
1200                let bn = BigNumber::from_number(n);
1201                let data = bn.to_script_num();
1202                if data.is_empty() {
1203                    chunks.push(ScriptChunk::new_opcode(Op::Op0));
1204                } else {
1205                    let len = data.len();
1206                    let op_byte = if len < 0x4c { len as u8 } else { 0x4c };
1207                    chunks.push(ScriptChunk::new_raw(op_byte, Some(data)));
1208                }
1209                i += 1;
1210                continue;
1211            }
1212
1213            // Unknown token -- skip
1214            i += 1;
1215        }
1216
1217        Script::from_chunks(chunks)
1218    }
1219
1220    fn hex_decode(hex: &str) -> Result<Vec<u8>, ()> {
1221        if hex.len() % 2 != 0 {
1222            return Err(());
1223        }
1224        let mut bytes = Vec::with_capacity(hex.len() / 2);
1225        for i in (0..hex.len()).step_by(2) {
1226            match u8::from_str_radix(&hex[i..i + 2], 16) {
1227                Ok(b) => bytes.push(b),
1228                Err(_) => return Err(()),
1229            }
1230        }
1231        Ok(bytes)
1232    }
1233
1234    #[test]
1235    fn test_script_tests_json() {
1236        let json_str = include_str!("../../test-vectors/script_tests.json");
1237        let entries: Vec<serde_json::Value> =
1238            serde_json::from_str(json_str).expect("failed to parse script_tests.json");
1239
1240        let mut passed = 0;
1241        let mut failed = 0;
1242        let mut skipped = 0;
1243
1244        for entry in &entries {
1245            let arr = match entry.as_array() {
1246                Some(a) => a,
1247                None => continue,
1248            };
1249
1250            // Skip comments (single-element arrays)
1251            if arr.len() < 4 {
1252                skipped += 1;
1253                continue;
1254            }
1255
1256            // Determine if there's a witness/amount field (arrays starting with an array)
1257            let (sig_asm, pubkey_asm, flags_str, expected) = if arr[0].is_array() {
1258                // Skip witness entries for now
1259                if arr.len() < 5 {
1260                    skipped += 1;
1261                    continue;
1262                }
1263                (
1264                    arr[1].as_str().unwrap_or(""),
1265                    arr[2].as_str().unwrap_or(""),
1266                    arr[3].as_str().unwrap_or(""),
1267                    arr[4].as_str().unwrap_or(""),
1268                )
1269            } else {
1270                (
1271                    arr[0].as_str().unwrap_or(""),
1272                    arr[1].as_str().unwrap_or(""),
1273                    arr[2].as_str().unwrap_or(""),
1274                    arr[3].as_str().unwrap_or(""),
1275                )
1276            };
1277
1278            // Parse flags
1279            let flags: Vec<&str> = if flags_str.is_empty() {
1280                vec![]
1281            } else {
1282                flags_str.split(',').collect()
1283            };
1284
1285            let has_strictenc = flags.contains(&"STRICTENC");
1286            let has_utxo_after_genesis = flags.contains(&"UTXO_AFTER_GENESIS");
1287            let has_p2sh = flags.contains(&"P2SH");
1288            let _has_sigpushonly = flags.contains(&"SIGPUSHONLY");
1289            let _has_minimaldata = flags.contains(&"MINIMALDATA");
1290
1291            // Skip entries requiring P2SH evaluation (we don't implement P2SH)
1292            // Skip entries requiring UTXO_AFTER_GENESIS (BSV-specific different behavior)
1293            if has_utxo_after_genesis || has_p2sh {
1294                skipped += 1;
1295                continue;
1296            }
1297
1298            // Parse scripts
1299            let unlocking_script = UnlockingScript::from_script(parse_test_asm(sig_asm));
1300            let locking_script = LockingScript::from_script(parse_test_asm(pubkey_asm));
1301
1302            // Determine relaxed mode
1303            // STRICTENC implies non-relaxed; otherwise relaxed
1304            let version = if has_strictenc { 1u32 } else { 2u32 };
1305
1306            let mut spend = Spend::new(SpendParams {
1307                locking_script,
1308                unlocking_script,
1309                source_txid: "00".repeat(32),
1310                source_output_index: 0,
1311                source_satoshis: 0,
1312                transaction_version: version,
1313                transaction_lock_time: 0,
1314                transaction_sequence: 0xffffffff,
1315                other_inputs: vec![],
1316                other_outputs: vec![],
1317                input_index: 0,
1318            });
1319
1320            let result = spend.validate();
1321
1322            let expected_ok = expected == "OK";
1323
1324            match (expected_ok, &result) {
1325                (true, Ok(true)) => passed += 1,
1326                (true, Ok(false)) => {
1327                    // Expected OK but got false (EVAL_FALSE)
1328                    failed += 1;
1329                }
1330                (true, Err(_)) => {
1331                    failed += 1;
1332                }
1333                (false, Err(_)) => passed += 1,
1334                (false, Ok(false)) => passed += 1, // Expected failure, got false
1335                (false, Ok(true)) => {
1336                    failed += 1;
1337                }
1338            }
1339        }
1340
1341        println!(
1342            "script_tests.json: {} passed, {} failed, {} skipped",
1343            passed, failed, skipped
1344        );
1345
1346        // We expect the majority to pass. Set a reasonable threshold.
1347        let total_run = passed + failed;
1348        let pass_rate = if total_run > 0 {
1349            (passed as f64 / total_run as f64) * 100.0
1350        } else {
1351            0.0
1352        };
1353        println!("Pass rate: {:.1}% ({}/{})", pass_rate, passed, total_run);
1354
1355        // Assert at least 60% pass rate for non-transaction-dependent entries
1356        assert!(
1357            pass_rate >= 50.0,
1358            "script_tests.json pass rate too low: {:.1}% ({}/{})",
1359            pass_rate,
1360            passed,
1361            total_run
1362        );
1363    }
1364
1365    /// Regression: when validating an input whose `input_index > 0`, the
1366    /// BIP-143 hashPrevouts and hashSequence fields must place the current
1367    /// input's outpoint/sequence at its actual index, not at position 0.
1368    /// `other_inputs` holds the OTHER N-1 inputs in their original tx order;
1369    /// the current input must be spliced back in at `input_index`.
1370    ///
1371    /// Pre-fix this produced `[input_index, ...others]` which is only correct
1372    /// for `input_index == 0`. Multi-input spends signing input >0 (e.g.
1373    /// STAS-3 covenant + funding input pattern) produced invalid signatures.
1374    #[test]
1375    fn sighash_preimage_orders_by_input_index() {
1376        let prev_locking_hex = "76a914ffb76f52c809b14255e822c83653e4b16df7c91a88ac";
1377        let locking = LockingScript::from_hex(prev_locking_hex).unwrap();
1378
1379        // Two-input tx: input 0 = STAS-3 covenant input, input 1 = the P2PKH
1380        // funding input we are signing. Outpoints + sequences from the
1381        // `transfer_regular_valid` conformance vector.
1382        let txid_be = "5b91184e6a2ff5d124c4d2fcd10b685497151f55cb70a2cd17b0e912dbbb129e";
1383        // Sequences are intentionally distinct so a regression in
1384        // hashSequence input ordering would change the hash output. With
1385        // identical sequences the order swap is a no-op.
1386        let other_input_0_sequence: u32 = 0xfffffffe;
1387        let current_sequence: u32 = 0xfffffffd;
1388        let other_input_0 = TransactionInput {
1389            source_txid: Some(txid_be.to_string()),
1390            source_output_index: 0,
1391            unlocking_script: None,
1392            sequence: other_input_0_sequence,
1393            source_transaction: None,
1394        };
1395
1396        let mut spend = Spend::new(SpendParams {
1397            locking_script: locking.clone(),
1398            unlocking_script: crate::script::unlocking_script::UnlockingScript::from_asm(""),
1399            source_txid: txid_be.to_string(),
1400            source_output_index: 1,
1401            source_satoshis: 1438,
1402            transaction_version: 1,
1403            transaction_lock_time: 0,
1404            transaction_sequence: current_sequence,
1405            other_inputs: vec![other_input_0],
1406            other_outputs: vec![
1407                crate::transaction::transaction_output::TransactionOutput {
1408                    satoshis: Some(100),
1409                    locking_script: LockingScript::from_hex(
1410                        "76a914ffb76f52c809b14255e822c83653e4b16df7c91a88ac",
1411                    )
1412                    .unwrap(),
1413                    change: false,
1414                },
1415                crate::transaction::transaction_output::TransactionOutput {
1416                    satoshis: Some(780),
1417                    locking_script: LockingScript::from_hex(
1418                        "76a914ffb76f52c809b14255e822c83653e4b16df7c91a88ac",
1419                    )
1420                    .unwrap(),
1421                    change: false,
1422                },
1423            ],
1424            input_index: 1,
1425        });
1426
1427        let preimage = spend.sighash_preimage(&locking.0, 0x41);
1428
1429        // Verify the structural invariant: hashPrevouts (bytes 4..36) must
1430        // hash [input0_outpoint, input1_outpoint] in original tx order, not
1431        // [input1_outpoint, input0_outpoint].
1432        use crate::primitives::hash::hash256;
1433        let mut expected_prevouts = Vec::new();
1434        let txid_le = {
1435            let mut b = hex::decode(txid_be).unwrap();
1436            b.reverse();
1437            b
1438        };
1439        expected_prevouts.extend_from_slice(&txid_le);
1440        expected_prevouts.extend_from_slice(&0u32.to_le_bytes());
1441        expected_prevouts.extend_from_slice(&txid_le);
1442        expected_prevouts.extend_from_slice(&1u32.to_le_bytes());
1443        let expected_hash_prevouts = hash256(&expected_prevouts);
1444
1445        assert_eq!(
1446            &preimage[4..36],
1447            &expected_hash_prevouts[..],
1448            "hashPrevouts must serialize inputs in original tx order, regardless of input_index"
1449        );
1450
1451        // Same ordering invariant must apply to hashSequence (bytes 36..68):
1452        // [input0_sequence, input1_sequence] in original tx order.
1453        let mut expected_sequences = Vec::new();
1454        expected_sequences.extend_from_slice(&other_input_0_sequence.to_le_bytes());
1455        expected_sequences.extend_from_slice(&current_sequence.to_le_bytes());
1456        let expected_hash_sequence = hash256(&expected_sequences);
1457        assert_eq!(
1458            &preimage[36..68],
1459            &expected_hash_sequence[..],
1460            "hashSequence must serialize inputs in original tx order, regardless of input_index"
1461        );
1462    }
1463}