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    // Memory management
71    pub(crate) memory_limit: usize,
72    pub(crate) stack_mem: usize,
73    pub(crate) alt_stack_mem: usize,
74
75    // Mode
76    pub(crate) is_relaxed_override: bool,
77
78    // Operations counter (prevents runaway scripts)
79    pub(crate) ops_count: usize,
80}
81
82impl Spend {
83    /// Create a new Spend interpreter from the given parameters.
84    pub fn new(params: SpendParams) -> Self {
85        Spend {
86            locking_script: params.locking_script,
87            unlocking_script: params.unlocking_script,
88            source_txid: params.source_txid,
89            source_output_index: params.source_output_index,
90            source_satoshis: params.source_satoshis,
91            transaction_version: params.transaction_version,
92            transaction_lock_time: params.transaction_lock_time,
93            transaction_sequence: params.transaction_sequence,
94            other_inputs: params.other_inputs,
95            other_outputs: params.other_outputs,
96            input_index: params.input_index,
97            stack: Vec::new(),
98            alt_stack: Vec::new(),
99            if_stack: Vec::new(),
100            context: ScriptContext::Unlocking,
101            program_counter: 0,
102            last_code_separator: None,
103            memory_limit: DEFAULT_MEMORY_LIMIT,
104            stack_mem: 0,
105            alt_stack_mem: 0,
106            is_relaxed_override: false,
107            ops_count: 0,
108        }
109    }
110
111    /// Run full script evaluation: unlocking then locking.
112    ///
113    /// Returns true if the spend is valid (top of stack is truthy after evaluation).
114    pub fn validate(&mut self) -> Result<bool, ScriptError> {
115        // Phase 1: Execute unlocking script
116        self.context = ScriptContext::Unlocking;
117        self.program_counter = 0;
118        loop {
119            let done = self.step()?;
120            if done {
121                break;
122            }
123        }
124
125        // After unlocking script, verify push-only if not relaxed
126        if !self.is_relaxed() && !self.unlocking_script.is_push_only() {
127            return Err(ScriptError::PushOnlyViolation);
128        }
129
130        // Phase 2: Execute locking script
131        self.context = ScriptContext::Locking;
132        self.program_counter = 0;
133        self.last_code_separator = None;
134        loop {
135            let done = self.step()?;
136            if done {
137                break;
138            }
139        }
140
141        // Check if_stack is empty (balanced IF/ENDIF)
142        if !self.if_stack.is_empty() {
143            return Err(ScriptError::InvalidScript(
144                "unbalanced IF/ENDIF".to_string(),
145            ));
146        }
147
148        // Enforce clean stack if not relaxed: exactly 1 item remaining
149        if !self.is_relaxed() && self.stack.len() != 1 {
150            return Err(ScriptError::CleanStackViolation);
151        }
152
153        // Check final result
154        if self.stack.is_empty() {
155            return Ok(false);
156        }
157
158        // SAFETY: guarded by is_empty() check above which returns early
159        let top = self.stack.last().unwrap();
160        Ok(Self::stack_to_bool(top))
161    }
162
163    /// Execute a single script chunk.
164    ///
165    /// Returns true if the current script phase is complete, false if more
166    /// chunks remain.
167    pub fn step(&mut self) -> Result<bool, ScriptError> {
168        let chunks = match self.context {
169            ScriptContext::Unlocking => self.unlocking_script.chunks(),
170            ScriptContext::Locking => self.locking_script.chunks(),
171        };
172
173        // Check if we've reached the end of the current script
174        if self.program_counter >= chunks.len() {
175            match self.context {
176                ScriptContext::Unlocking => {
177                    // Signal to validate() that unlocking is done
178                    return Ok(true);
179                }
180                ScriptContext::Locking => {
181                    return Ok(true);
182                }
183            }
184        }
185
186        let chunk = chunks[self.program_counter].clone();
187        let op = chunk.op;
188
189        // Check if we are in a false branch of an IF
190        let in_exec = self.if_stack.iter().all(|&v| v);
191
192        if !in_exec {
193            // In a FALSE branch: only process IF/NOTIF/ELSE/ENDIF/VERIF/VERNOTIF
194            match op {
195                Op::OpIf | Op::OpNotIf | Op::OpVerIf | Op::OpVerNotIf => {
196                    // Nested IF inside false branch: push false to if_stack
197                    self.if_stack.push(false);
198                }
199                Op::OpElse => {
200                    if let Some(last) = self.if_stack.last_mut() {
201                        *last = !*last;
202                    } else {
203                        return Err(ScriptError::InvalidScript(
204                            "OP_ELSE without OP_IF".to_string(),
205                        ));
206                    }
207                }
208                Op::OpEndIf => {
209                    if self.if_stack.pop().is_none() {
210                        return Err(ScriptError::InvalidScript(
211                            "OP_ENDIF without OP_IF".to_string(),
212                        ));
213                    }
214                }
215                _ => {
216                    // Skip all other opcodes in false branch
217                }
218            }
219            self.program_counter += 1;
220            return Ok(false);
221        }
222
223        // Guard against runaway scripts
224        self.ops_count += 1;
225        if self.ops_count > MAX_OPS {
226            return Err(ScriptError::InvalidScript(
227                "exceeded maximum operation count".to_string(),
228            ));
229        }
230
231        // Execute the opcode
232        self.execute_opcode(op, &chunk)?;
233        self.program_counter += 1;
234
235        Ok(false)
236    }
237
238    /// Whether the interpreter is in relaxed mode.
239    ///
240    /// Relaxed mode is active when transaction_version > 1 or explicitly overridden.
241    /// In relaxed mode: clean stack, minimal encoding, NULLDUMMY, low-S, and
242    /// push-only checks are not enforced.
243    pub fn is_relaxed(&self) -> bool {
244        self.transaction_version > 1 || self.is_relaxed_override
245    }
246
247    /// Override the relaxed mode flag.
248    pub fn set_relaxed_override(&mut self, v: bool) {
249        self.is_relaxed_override = v;
250    }
251}
252
253#[cfg(test)]
254mod tests {
255    use super::*;
256    use crate::script::locking_script::LockingScript;
257    use crate::script::unlocking_script::UnlockingScript;
258
259    /// Helper to create a simple Spend with default transaction context.
260    fn make_spend(unlocking_asm: &str, locking_asm: &str) -> Spend {
261        Spend::new(SpendParams {
262            locking_script: LockingScript::from_asm(locking_asm),
263            unlocking_script: UnlockingScript::from_asm(unlocking_asm),
264            source_txid: "00".repeat(32),
265            source_output_index: 0,
266            source_satoshis: 0,
267            transaction_version: 1,
268            transaction_lock_time: 0,
269            transaction_sequence: 0xffffffff,
270            other_inputs: vec![],
271            other_outputs: vec![],
272            input_index: 0,
273        })
274    }
275
276    /// Helper: create a relaxed-mode spend.
277    fn make_relaxed_spend(unlocking_asm: &str, locking_asm: &str) -> Spend {
278        Spend::new(SpendParams {
279            locking_script: LockingScript::from_asm(locking_asm),
280            unlocking_script: UnlockingScript::from_asm(unlocking_asm),
281            source_txid: "00".repeat(32),
282            source_output_index: 0,
283            source_satoshis: 0,
284            transaction_version: 2, // relaxed
285            transaction_lock_time: 0,
286            transaction_sequence: 0xffffffff,
287            other_inputs: vec![],
288            other_outputs: vec![],
289            input_index: 0,
290        })
291    }
292
293    #[test]
294    fn test_simple_push_only_script() {
295        // Unlocking: OP_1, Locking: (empty -- just check top of stack)
296        // In strict mode an empty locking script means stack has 1 item [1] => true
297        let mut spend = make_spend("OP_1", "");
298        let result = spend.validate().unwrap();
299        assert!(result, "OP_1 should leave true on stack");
300    }
301
302    #[test]
303    fn test_op1_pushes_one() {
304        let mut spend = make_spend("OP_1", "");
305        spend.validate().unwrap();
306        assert_eq!(spend.stack, vec![vec![1u8]]);
307    }
308
309    #[test]
310    fn test_op_numbers() {
311        // OP_2 through OP_16
312        for n in 2..=16u8 {
313            let asm = format!("OP_{}", n);
314            let mut spend = make_spend(&asm, "");
315            spend.validate().unwrap();
316            assert_eq!(spend.stack, vec![vec![n]], "OP_{} should push [{}]", n, n);
317        }
318    }
319
320    #[test]
321    fn test_op_0_pushes_empty() {
322        // OP_0 pushes empty vec; in relaxed mode clean stack is not enforced
323        let mut spend = make_relaxed_spend("0 OP_1", "");
324        spend.validate().unwrap();
325        assert_eq!(spend.stack.len(), 2);
326        assert_eq!(spend.stack[0], Vec::<u8>::new());
327        assert_eq!(spend.stack[1], vec![1u8]);
328    }
329
330    #[test]
331    fn test_op_1negate() {
332        let mut spend = make_spend("-1", "OP_1 OP_ADD");
333        // -1 + 1 = 0, which is false
334        let result = spend.validate().unwrap();
335        assert!(!result, "-1 + 1 = 0 should be false");
336    }
337
338    #[test]
339    fn test_if_else_endif_true_branch() {
340        // Unlocking: OP_1, Locking: OP_IF OP_2 OP_ELSE OP_3 OP_ENDIF
341        let mut spend = make_spend("OP_1", "OP_IF OP_2 OP_ELSE OP_3 OP_ENDIF");
342        let result = spend.validate().unwrap();
343        assert!(result);
344        assert_eq!(spend.stack, vec![vec![2u8]]);
345    }
346
347    #[test]
348    fn test_if_else_endif_false_branch() {
349        let mut spend = make_spend("0", "OP_IF OP_2 OP_ELSE OP_3 OP_ENDIF");
350        let result = spend.validate().unwrap();
351        assert!(result);
352        assert_eq!(spend.stack, vec![vec![3u8]]);
353    }
354
355    #[test]
356    fn test_nested_if() {
357        // OP_1 OP_1 | OP_IF OP_IF OP_5 OP_ENDIF OP_ENDIF
358        let mut spend = make_spend("OP_1 OP_1", "OP_IF OP_IF OP_5 OP_ENDIF OP_ENDIF");
359        let result = spend.validate().unwrap();
360        assert!(result);
361        assert_eq!(spend.stack, vec![vec![5u8]]);
362    }
363
364    #[test]
365    fn test_op_verify_true() {
366        let mut spend = make_spend("OP_1", "OP_VERIFY OP_1");
367        let result = spend.validate().unwrap();
368        assert!(result);
369    }
370
371    #[test]
372    fn test_op_verify_false() {
373        let mut spend = make_spend("0", "OP_VERIFY OP_1");
374        let result = spend.validate();
375        assert!(result.is_err());
376    }
377
378    #[test]
379    fn test_op_return_fails() {
380        let mut spend = make_spend("OP_1", "OP_RETURN");
381        let result = spend.validate();
382        assert!(result.is_err());
383    }
384
385    #[test]
386    fn test_memory_limit_exceeded() {
387        // Create a spend with a very low memory limit
388        let mut spend = make_spend("OP_1", "");
389        spend.memory_limit = 0;
390        let result = spend.validate();
391        assert!(matches!(result, Err(ScriptError::MemoryLimitExceeded)));
392    }
393
394    #[test]
395    fn test_stack_underflow() {
396        // Empty unlocking (push-only) + OP_DUP on empty stack
397        // Should fail with either StackUnderflow or InvalidStackOperation
398        let mut spend = make_spend("", "OP_DUP");
399        let result = spend.validate();
400        assert!(result.is_err(), "expected error but got {:?}", result);
401    }
402
403    #[test]
404    fn test_stack_to_bool() {
405        assert!(!Spend::stack_to_bool(&[]));
406        assert!(!Spend::stack_to_bool(&[0]));
407        assert!(!Spend::stack_to_bool(&[0, 0]));
408        assert!(!Spend::stack_to_bool(&[0x80])); // negative zero
409        assert!(!Spend::stack_to_bool(&[0, 0x80])); // negative zero
410        assert!(Spend::stack_to_bool(&[1]));
411        assert!(Spend::stack_to_bool(&[0, 1]));
412        assert!(Spend::stack_to_bool(&[0x81])); // -1
413    }
414
415    #[test]
416    fn test_clean_stack_violation() {
417        // In strict mode, stack must have exactly 1 item
418        let mut spend = make_spend("OP_1 OP_2", "");
419        let result = spend.validate();
420        assert!(matches!(result, Err(ScriptError::CleanStackViolation)));
421    }
422
423    #[test]
424    fn test_relaxed_mode_no_clean_stack() {
425        // In relaxed mode, clean stack is not enforced
426        let mut spend = make_relaxed_spend("OP_1 OP_2", "");
427        let result = spend.validate().unwrap();
428        assert!(result); // top is [2] which is truthy
429    }
430
431    #[test]
432    fn test_push_only_violation() {
433        // Non-push-only unlocking script in strict mode
434        let mut spend = make_spend("", "OP_1");
435        // Manually set unlocking to have a non-push opcode
436        spend.unlocking_script = UnlockingScript::from_asm("OP_DUP");
437        // We need something on the stack for OP_DUP to work
438        spend.stack.push(vec![1]);
439        let result = spend.validate();
440        assert!(matches!(result, Err(ScriptError::PushOnlyViolation)));
441    }
442
443    #[test]
444    fn test_step_api() {
445        let mut spend = make_spend("OP_1 OP_2", "OP_ADD");
446
447        // Step through unlocking script
448        assert!(!spend.step().unwrap()); // OP_1
449        assert_eq!(spend.stack, vec![vec![1u8]]);
450
451        assert!(!spend.step().unwrap()); // OP_2
452        assert_eq!(spend.stack, vec![vec![1u8], vec![2u8]]);
453
454        assert!(spend.step().unwrap()); // end of unlocking
455
456        // Switch to locking
457        spend.context = ScriptContext::Locking;
458        spend.program_counter = 0;
459
460        assert!(!spend.step().unwrap()); // OP_ADD
461        assert_eq!(spend.stack, vec![vec![3u8]]);
462
463        assert!(spend.step().unwrap()); // end of locking
464    }
465
466    #[test]
467    fn test_is_relaxed() {
468        let spend = make_spend("", "");
469        assert!(!spend.is_relaxed()); // version 1
470
471        let spend2 = make_relaxed_spend("", "");
472        assert!(spend2.is_relaxed()); // version 2
473
474        let mut spend3 = make_spend("", "");
475        spend3.set_relaxed_override(true);
476        assert!(spend3.is_relaxed()); // override
477    }
478
479    #[test]
480    fn test_op_notif() {
481        // OP_0 | OP_NOTIF OP_5 OP_ENDIF (false => NOTIF enters true branch)
482        let mut spend = make_spend("0", "OP_NOTIF OP_5 OP_ENDIF");
483        let result = spend.validate().unwrap();
484        assert!(result);
485        assert_eq!(spend.stack, vec![vec![5u8]]);
486    }
487
488    // =================================================================
489    // Task 2: Additional opcode unit tests
490    // =================================================================
491
492    #[test]
493    fn test_op_add() {
494        let mut spend = make_spend("OP_3 OP_4", "OP_ADD OP_7 OP_EQUAL");
495        assert!(spend.validate().unwrap());
496    }
497
498    #[test]
499    fn test_op_sub() {
500        let mut spend = make_spend("OP_5 OP_3", "OP_SUB OP_2 OP_EQUAL");
501        assert!(spend.validate().unwrap());
502    }
503
504    #[test]
505    fn test_op_mul() {
506        let mut spend = make_spend("OP_3 OP_4", "OP_MUL OP_12 OP_EQUAL");
507        assert!(spend.validate().unwrap());
508    }
509
510    #[test]
511    fn test_op_div() {
512        let mut spend = make_spend("OP_6 OP_3", "OP_DIV OP_2 OP_EQUAL");
513        assert!(spend.validate().unwrap());
514    }
515
516    #[test]
517    fn test_op_mod() {
518        let mut spend = make_spend("OP_7 OP_3", "OP_MOD OP_1 OP_EQUAL");
519        assert!(spend.validate().unwrap());
520    }
521
522    #[test]
523    fn test_op_div_by_zero() {
524        let mut spend = make_spend("OP_5 0", "OP_DIV");
525        assert!(spend.validate().is_err());
526    }
527
528    #[test]
529    fn test_op_equal() {
530        let mut spend = make_spend("OP_3 OP_3", "OP_EQUAL");
531        assert!(spend.validate().unwrap());
532    }
533
534    #[test]
535    fn test_op_equal_false() {
536        let mut spend = make_spend("OP_3 OP_4", "OP_EQUAL");
537        assert!(!spend.validate().unwrap());
538    }
539
540    #[test]
541    fn test_op_equalverify() {
542        let mut spend = make_spend("OP_3 OP_3", "OP_EQUALVERIFY OP_1");
543        assert!(spend.validate().unwrap());
544    }
545
546    #[test]
547    fn test_op_equalverify_fail() {
548        let mut spend = make_spend("OP_3 OP_4", "OP_EQUALVERIFY OP_1");
549        assert!(spend.validate().is_err());
550    }
551
552    #[test]
553    fn test_op_dup_hash160_equalverify() {
554        // P2PKH-like pattern: push pubkey, DUP, HASH160, compare, then DROP pubkey.
555        // After EQUALVERIFY: stack=[pubkey]. DROP clears it, OP_1 leaves [1].
556        use crate::primitives::hash::hash160;
557        let pubkey_data = vec![0x04; 33]; // dummy compressed pubkey
558        let hash = hash160(&pubkey_data);
559        let hash_hex: String = hash.iter().map(|b| format!("{:02x}", b)).collect();
560        let pubkey_hex: String = pubkey_data.iter().map(|b| format!("{:02x}", b)).collect();
561
562        let locking = format!("OP_DUP OP_HASH160 {} OP_EQUALVERIFY OP_DROP OP_1", hash_hex);
563        let mut spend = make_spend(&pubkey_hex, &locking);
564        assert!(spend.validate().unwrap());
565    }
566
567    #[test]
568    fn test_op_cat() {
569        // Concatenate two byte arrays
570        // Push 0x01, push 0x02, OP_CAT should give 0x0102
571        let mut spend = make_relaxed_spend("01 02", "OP_CAT 0102 OP_EQUAL");
572        assert!(spend.validate().unwrap());
573    }
574
575    #[test]
576    fn test_op_split() {
577        // Split 0x010203 at position 1 should give 0x01 and 0x0203
578        let mut spend =
579            make_relaxed_spend("010203 OP_1", "OP_SPLIT 0203 OP_EQUALVERIFY 01 OP_EQUAL");
580        assert!(spend.validate().unwrap());
581    }
582
583    #[test]
584    fn test_op_size() {
585        let mut spend = make_relaxed_spend("010203", "OP_SIZE OP_3 OP_EQUALVERIFY OP_1");
586        assert!(spend.validate().unwrap());
587    }
588
589    #[test]
590    fn test_op_sha256() {
591        // SHA256 of OP_0 (empty bytes) = 32 bytes
592        let mut spend = make_relaxed_spend("0", "OP_SHA256 OP_SIZE");
593        spend.validate().unwrap();
594        assert_eq!(spend.stack.len(), 2);
595        assert_eq!(spend.stack[0].len(), 32);
596    }
597
598    #[test]
599    fn test_op_hash160() {
600        // OP_HASH160 produces 20-byte result
601        let mut spend = make_relaxed_spend("01", "OP_HASH160 OP_SIZE");
602        spend.validate().unwrap();
603        assert_eq!(spend.stack[0].len(), 20);
604    }
605
606    #[test]
607    fn test_op_hash256() {
608        // OP_HASH256 produces 32-byte result
609        let mut spend = make_relaxed_spend("01", "OP_HASH256 OP_SIZE");
610        spend.validate().unwrap();
611        assert_eq!(spend.stack[0].len(), 32);
612    }
613
614    #[test]
615    fn test_op_toaltstack_fromaltstack() {
616        let mut spend = make_spend("OP_1", "OP_TOALTSTACK OP_FROMALTSTACK");
617        assert!(spend.validate().unwrap());
618        assert_eq!(spend.stack, vec![vec![1u8]]);
619    }
620
621    #[test]
622    fn test_op_depth() {
623        // Unlocking pushes 1,2,3 (3 items). Locking: DEPTH pushes 3 -> [1,2,3,3].
624        // 3 EQUALVERIFY verifies depth==3 -> [1,2,3]. DROP DROP DROP clears all.
625        // OP_1 leaves [1] for clean stack.
626        let mut spend = make_spend(
627            "OP_1 OP_2 OP_3",
628            "OP_DEPTH OP_3 OP_EQUALVERIFY OP_DROP OP_DROP OP_DROP OP_1",
629        );
630        let result = spend.validate();
631        assert!(result.is_ok(), "test_op_depth failed: {:?}", result);
632    }
633
634    #[test]
635    fn test_op_swap() {
636        let mut spend = make_relaxed_spend("OP_1 OP_2", "OP_SWAP");
637        spend.validate().unwrap();
638        assert_eq!(spend.stack, vec![vec![2u8], vec![1u8]]);
639    }
640
641    #[test]
642    fn test_op_rot() {
643        let mut spend = make_relaxed_spend("OP_1 OP_2 OP_3", "OP_ROT");
644        spend.validate().unwrap();
645        assert_eq!(spend.stack, vec![vec![2u8], vec![3u8], vec![1u8]]);
646    }
647
648    #[test]
649    fn test_op_over() {
650        let mut spend = make_relaxed_spend("OP_1 OP_2", "OP_OVER");
651        spend.validate().unwrap();
652        assert_eq!(spend.stack, vec![vec![1u8], vec![2u8], vec![1u8]]);
653    }
654
655    #[test]
656    fn test_op_nip() {
657        let mut spend = make_relaxed_spend("OP_1 OP_2", "OP_NIP");
658        spend.validate().unwrap();
659        assert_eq!(spend.stack, vec![vec![2u8]]);
660    }
661
662    #[test]
663    fn test_op_tuck() {
664        let mut spend = make_relaxed_spend("OP_1 OP_2", "OP_TUCK");
665        spend.validate().unwrap();
666        assert_eq!(spend.stack, vec![vec![2u8], vec![1u8], vec![2u8]]);
667    }
668
669    #[test]
670    fn test_op_pick() {
671        let mut spend = make_relaxed_spend("OP_1 OP_2 OP_3 OP_2", "OP_PICK");
672        spend.validate().unwrap();
673        assert_eq!(
674            spend.stack,
675            vec![vec![1u8], vec![2u8], vec![3u8], vec![1u8]]
676        );
677    }
678
679    #[test]
680    fn test_op_roll() {
681        let mut spend = make_relaxed_spend("OP_1 OP_2 OP_3 OP_2", "OP_ROLL");
682        spend.validate().unwrap();
683        assert_eq!(spend.stack, vec![vec![2u8], vec![3u8], vec![1u8]]);
684    }
685
686    #[test]
687    fn test_op_2dup() {
688        let mut spend = make_relaxed_spend("OP_1 OP_2", "OP_2DUP");
689        spend.validate().unwrap();
690        assert_eq!(
691            spend.stack,
692            vec![vec![1u8], vec![2u8], vec![1u8], vec![2u8]]
693        );
694    }
695
696    #[test]
697    fn test_op_3dup() {
698        let mut spend = make_relaxed_spend("OP_1 OP_2 OP_3", "OP_3DUP");
699        spend.validate().unwrap();
700        assert_eq!(spend.stack.len(), 6);
701    }
702
703    #[test]
704    fn test_op_2drop() {
705        let mut spend = make_spend("OP_1 OP_2 OP_3", "OP_2DROP");
706        assert!(spend.validate().unwrap());
707        assert_eq!(spend.stack, vec![vec![1u8]]);
708    }
709
710    #[test]
711    fn test_op_2swap() {
712        let mut spend = make_relaxed_spend("OP_1 OP_2 OP_3 OP_4", "OP_2SWAP");
713        spend.validate().unwrap();
714        assert_eq!(
715            spend.stack,
716            vec![vec![3u8], vec![4u8], vec![1u8], vec![2u8]]
717        );
718    }
719
720    #[test]
721    fn test_op_ifdup_true() {
722        let mut spend = make_relaxed_spend("OP_1", "OP_IFDUP");
723        spend.validate().unwrap();
724        assert_eq!(spend.stack, vec![vec![1u8], vec![1u8]]);
725    }
726
727    #[test]
728    fn test_op_ifdup_false() {
729        let mut spend = make_relaxed_spend("0", "OP_IFDUP");
730        spend.validate().unwrap();
731        assert_eq!(spend.stack, vec![Vec::<u8>::new()]);
732    }
733
734    #[test]
735    fn test_op_lessthan() {
736        let mut spend = make_spend("OP_1 OP_2", "OP_LESSTHAN");
737        assert!(spend.validate().unwrap());
738    }
739
740    #[test]
741    fn test_op_greaterthan() {
742        let mut spend = make_spend("OP_3 OP_2", "OP_GREATERTHAN");
743        assert!(spend.validate().unwrap());
744    }
745
746    #[test]
747    fn test_op_within() {
748        // 3 is within [2, 5)
749        let mut spend = make_spend("OP_3 OP_2 OP_5", "OP_WITHIN");
750        assert!(spend.validate().unwrap());
751    }
752
753    #[test]
754    fn test_op_within_false() {
755        // 5 is not within [2, 5)
756        let mut spend = make_spend("OP_5 OP_2 OP_5", "OP_WITHIN");
757        assert!(!spend.validate().unwrap());
758    }
759
760    #[test]
761    fn test_op_min() {
762        let mut spend = make_spend("OP_3 OP_5", "OP_MIN OP_3 OP_EQUAL");
763        assert!(spend.validate().unwrap());
764    }
765
766    #[test]
767    fn test_op_max() {
768        let mut spend = make_spend("OP_3 OP_5", "OP_MAX OP_5 OP_EQUAL");
769        assert!(spend.validate().unwrap());
770    }
771
772    #[test]
773    fn test_op_booland() {
774        let mut spend = make_spend("OP_1 OP_1", "OP_BOOLAND");
775        assert!(spend.validate().unwrap());
776
777        let mut spend2 = make_spend("OP_1 0", "OP_BOOLAND");
778        assert!(!spend2.validate().unwrap());
779    }
780
781    #[test]
782    fn test_op_boolor() {
783        let mut spend = make_spend("0 0", "OP_BOOLOR");
784        assert!(!spend.validate().unwrap());
785
786        let mut spend2 = make_spend("OP_1 0", "OP_BOOLOR");
787        assert!(spend2.validate().unwrap());
788    }
789
790    #[test]
791    fn test_op_abs() {
792        // -1 ABS should give 1
793        let mut spend = make_spend("-1", "OP_ABS OP_1 OP_EQUAL");
794        assert!(spend.validate().unwrap());
795    }
796
797    #[test]
798    fn test_op_not() {
799        let mut spend = make_spend("0", "OP_NOT"); // NOT(0) = 1
800        assert!(spend.validate().unwrap());
801
802        let mut spend2 = make_spend("OP_1", "OP_NOT"); // NOT(1) = 0
803        assert!(!spend2.validate().unwrap());
804    }
805
806    #[test]
807    fn test_op_0notequal() {
808        let mut spend = make_spend("OP_5", "OP_0NOTEQUAL");
809        assert!(spend.validate().unwrap());
810
811        let mut spend2 = make_spend("0", "OP_0NOTEQUAL");
812        assert!(!spend2.validate().unwrap());
813    }
814
815    #[test]
816    fn test_op_negate() {
817        let mut spend = make_spend("OP_5", "OP_NEGATE OP_ABS OP_5 OP_EQUAL");
818        assert!(spend.validate().unwrap());
819    }
820
821    #[test]
822    fn test_op_1add_1sub() {
823        let mut spend = make_spend("OP_5", "OP_1ADD OP_6 OP_EQUAL");
824        assert!(spend.validate().unwrap());
825
826        let mut spend2 = make_spend("OP_5", "OP_1SUB OP_4 OP_EQUAL");
827        assert!(spend2.validate().unwrap());
828    }
829
830    #[test]
831    fn test_op_numequal() {
832        let mut spend = make_spend("OP_3 OP_3", "OP_NUMEQUAL");
833        assert!(spend.validate().unwrap());
834    }
835
836    #[test]
837    fn test_op_numequalverify() {
838        let mut spend = make_spend("OP_3 OP_3", "OP_NUMEQUALVERIFY OP_1");
839        assert!(spend.validate().unwrap());
840    }
841
842    #[test]
843    fn test_op_numnotequal() {
844        let mut spend = make_spend("OP_3 OP_4", "OP_NUMNOTEQUAL");
845        assert!(spend.validate().unwrap());
846    }
847
848    #[test]
849    fn test_op_invert() {
850        // Invert 0x00 = 0xff
851        let mut spend = make_relaxed_spend("00", "OP_INVERT");
852        spend.validate().unwrap();
853        assert_eq!(spend.stack, vec![vec![0xff]]);
854    }
855
856    #[test]
857    fn test_op_and() {
858        let mut spend = make_relaxed_spend("ff 0f", "OP_AND");
859        spend.validate().unwrap();
860        assert_eq!(spend.stack, vec![vec![0x0f]]);
861    }
862
863    #[test]
864    fn test_op_or() {
865        let mut spend = make_relaxed_spend("f0 0f", "OP_OR");
866        spend.validate().unwrap();
867        assert_eq!(spend.stack, vec![vec![0xff]]);
868    }
869
870    #[test]
871    fn test_op_xor() {
872        let mut spend = make_relaxed_spend("ff ff", "OP_XOR");
873        spend.validate().unwrap();
874        assert_eq!(spend.stack, vec![vec![0x00]]);
875    }
876
877    #[test]
878    fn test_nested_if_deep() {
879        // OP_1 OP_1 OP_1 | OP_IF OP_IF OP_IF OP_7 OP_ENDIF OP_ENDIF OP_ENDIF
880        let mut spend = make_spend(
881            "OP_1 OP_1 OP_1",
882            "OP_IF OP_IF OP_IF OP_7 OP_ENDIF OP_ENDIF OP_ENDIF",
883        );
884        assert!(spend.validate().unwrap());
885        assert_eq!(spend.stack, vec![vec![7u8]]);
886    }
887
888    #[test]
889    fn test_op_codeseparator() {
890        // OP_CODESEPARATOR should not affect normal execution
891        let mut spend = make_spend("OP_1", "OP_CODESEPARATOR");
892        assert!(spend.validate().unwrap());
893    }
894
895    #[test]
896    fn test_chronicle_op_substr() {
897        // OP_SUBSTR: data, begin, len -> substring
898        let mut spend = make_relaxed_spend("0102030405 OP_1 OP_2", "OP_SUBSTR 0203 OP_EQUAL");
899        assert!(spend.validate().unwrap());
900    }
901
902    #[test]
903    fn test_chronicle_op_left() {
904        let mut spend = make_relaxed_spend("01020304 OP_2", "OP_LEFT 0102 OP_EQUAL");
905        assert!(spend.validate().unwrap());
906    }
907
908    #[test]
909    fn test_chronicle_op_right() {
910        let mut spend = make_relaxed_spend("01020304 OP_2", "OP_RIGHT 0304 OP_EQUAL");
911        assert!(spend.validate().unwrap());
912    }
913
914    #[test]
915    fn test_op_ripemd160() {
916        let mut spend = make_relaxed_spend("01", "OP_RIPEMD160 OP_SIZE");
917        spend.validate().unwrap();
918        assert_eq!(spend.stack[0].len(), 20);
919    }
920
921    #[test]
922    fn test_op_sha1() {
923        let mut spend = make_relaxed_spend("01", "OP_SHA1 OP_SIZE");
924        spend.validate().unwrap();
925        assert_eq!(spend.stack[0].len(), 20);
926    }
927
928    #[test]
929    fn test_op_checksig_empty_sig_fails() {
930        // Empty signature should push false
931        let mut spend = make_relaxed_spend("0 01", "OP_CHECKSIG");
932        spend.validate().unwrap();
933        // Empty sig -> pushes false
934        assert_eq!(spend.stack, vec![Vec::<u8>::new()]);
935    }
936
937    #[test]
938    fn test_relaxed_mode_gating() {
939        // In relaxed mode, clean stack not enforced
940        let mut spend = make_relaxed_spend("OP_1 OP_2 OP_3", "");
941        let result = spend.validate().unwrap();
942        assert!(result); // top is 3
943
944        // In strict mode, multiple items = clean stack violation
945        let mut spend2 = make_spend("OP_1 OP_2 OP_3", "");
946        assert!(matches!(
947            spend2.validate(),
948            Err(ScriptError::CleanStackViolation)
949        ));
950    }
951
952    #[test]
953    fn test_op_disabled_2mul() {
954        let mut spend = make_spend("OP_1", "OP_2MUL");
955        let result = spend.validate();
956        assert!(matches!(result, Err(ScriptError::DisabledOpcode(_))));
957    }
958
959    #[test]
960    fn test_op_disabled_2div() {
961        let mut spend = make_spend("OP_1", "OP_2DIV");
962        let result = spend.validate();
963        assert!(matches!(result, Err(ScriptError::DisabledOpcode(_))));
964    }
965
966    // =================================================================
967    // script_tests.json test runner
968    // =================================================================
969
970    /// Parse the script_tests.json ASM format into our Script type.
971    ///
972    /// The test format uses short opcode names (DUP instead of OP_DUP),
973    /// hex pushes (0x02 0x0100), and string literals ('text').
974    fn parse_test_asm(asm: &str) -> crate::script::script::Script {
975        use crate::script::script::Script;
976        use crate::script::script_chunk::ScriptChunk;
977
978        let asm = asm.trim();
979        if asm.is_empty() {
980            return Script::new();
981        }
982
983        let mut chunks = Vec::new();
984        let tokens: Vec<&str> = asm.split_whitespace().collect();
985        let mut i = 0;
986
987        while i < tokens.len() {
988            let token = tokens[i];
989
990            // Number literals
991            if token == "0" {
992                chunks.push(ScriptChunk::new_opcode(Op::Op0));
993                i += 1;
994                continue;
995            }
996            if token == "-1" {
997                chunks.push(ScriptChunk::new_opcode(Op::Op1Negate));
998                i += 1;
999                continue;
1000            }
1001
1002            // String literal: 'text'
1003            if token.starts_with('\'') {
1004                let text = if token.ends_with('\'') && token.len() > 1 {
1005                    &token[1..token.len() - 1]
1006                } else {
1007                    // Multi-word string? collect until closing quote
1008                    let mut s = token[1..].to_string();
1009                    loop {
1010                        i += 1;
1011                        if i >= tokens.len() {
1012                            break;
1013                        }
1014                        s.push(' ');
1015                        s.push_str(tokens[i]);
1016                        if tokens[i].ends_with('\'') {
1017                            s.truncate(s.len() - 1);
1018                            break;
1019                        }
1020                    }
1021                    i += 1;
1022                    let data = s.into_bytes();
1023                    let len = data.len();
1024                    let op_byte = if len < 0x4c { len as u8 } else { 0x4c };
1025                    chunks.push(ScriptChunk::new_raw(op_byte, Some(data)));
1026                    continue;
1027                };
1028                let data = text.as_bytes().to_vec();
1029                let len = data.len();
1030                let op_byte = if len < 0x4c { len as u8 } else { 0x4c };
1031                chunks.push(ScriptChunk::new_raw(op_byte, Some(data)));
1032                i += 1;
1033                continue;
1034            }
1035
1036            // Hex push: 0x02 0x0100
1037            if token.starts_with("0x") || token.starts_with("0X") {
1038                let hex = &token[2..];
1039                if let Ok(push_len) = usize::from_str_radix(hex, 16) {
1040                    // This might be a push length followed by hex data
1041                    if push_len > 0 && push_len <= 0x4e && i + 1 < tokens.len() {
1042                        let next = tokens[i + 1];
1043                        if next.starts_with("0x") || next.starts_with("0X") {
1044                            let data_hex = &next[2..];
1045                            if let Ok(data) = hex_decode(data_hex) {
1046                                chunks.push(ScriptChunk::new_raw(push_len as u8, Some(data)));
1047                                i += 2;
1048                                continue;
1049                            }
1050                        }
1051                    }
1052                    // Single byte opcode
1053                    if push_len <= 0xff {
1054                        let op = Op::from(push_len as u8);
1055                        chunks.push(ScriptChunk::new_raw(push_len as u8, None));
1056                        let _ = op; // just to use it
1057                        i += 1;
1058                        continue;
1059                    }
1060                }
1061                i += 1;
1062                continue;
1063            }
1064
1065            // PUSHDATA1/2/4 with explicit length and data
1066            if token == "PUSHDATA1" || token == "OP_PUSHDATA1" {
1067                if i + 2 < tokens.len() {
1068                    let _len_hex = tokens[i + 1].strip_prefix("0x").unwrap_or(tokens[i + 1]);
1069                    let data_hex = tokens[i + 2].strip_prefix("0x").unwrap_or(tokens[i + 2]);
1070                    if let Ok(data) = hex_decode(data_hex) {
1071                        chunks.push(ScriptChunk::new_raw(0x4c, Some(data)));
1072                        i += 3;
1073                        continue;
1074                    }
1075                }
1076                i += 1;
1077                continue;
1078            }
1079            if token == "PUSHDATA2" || token == "OP_PUSHDATA2" {
1080                if i + 2 < tokens.len() {
1081                    let data_hex = tokens[i + 2].strip_prefix("0x").unwrap_or(tokens[i + 2]);
1082                    if let Ok(data) = hex_decode(data_hex) {
1083                        chunks.push(ScriptChunk::new_raw(0x4d, Some(data)));
1084                        i += 3;
1085                        continue;
1086                    }
1087                }
1088                i += 1;
1089                continue;
1090            }
1091            if token == "PUSHDATA4" || token == "OP_PUSHDATA4" {
1092                if i + 2 < tokens.len() {
1093                    let data_hex = tokens[i + 2].strip_prefix("0x").unwrap_or(tokens[i + 2]);
1094                    if let Ok(data) = hex_decode(data_hex) {
1095                        chunks.push(ScriptChunk::new_raw(0x4e, Some(data)));
1096                        i += 3;
1097                        continue;
1098                    }
1099                }
1100                i += 1;
1101                continue;
1102            }
1103
1104            // Try as opcode (with or without OP_ prefix)
1105            if let Some(op) =
1106                Op::from_name(token).or_else(|| Op::from_name(&format!("OP_{}", token)))
1107            {
1108                chunks.push(ScriptChunk::new_opcode(op));
1109                i += 1;
1110                continue;
1111            }
1112
1113            // Decimal number
1114            if let Ok(n) = token.parse::<i64>() {
1115                use crate::primitives::big_number::BigNumber;
1116                let bn = BigNumber::from_number(n);
1117                let data = bn.to_script_num();
1118                if data.is_empty() {
1119                    chunks.push(ScriptChunk::new_opcode(Op::Op0));
1120                } else {
1121                    let len = data.len();
1122                    let op_byte = if len < 0x4c { len as u8 } else { 0x4c };
1123                    chunks.push(ScriptChunk::new_raw(op_byte, Some(data)));
1124                }
1125                i += 1;
1126                continue;
1127            }
1128
1129            // Unknown token -- skip
1130            i += 1;
1131        }
1132
1133        Script::from_chunks(chunks)
1134    }
1135
1136    fn hex_decode(hex: &str) -> Result<Vec<u8>, ()> {
1137        if hex.len() % 2 != 0 {
1138            return Err(());
1139        }
1140        let mut bytes = Vec::with_capacity(hex.len() / 2);
1141        for i in (0..hex.len()).step_by(2) {
1142            match u8::from_str_radix(&hex[i..i + 2], 16) {
1143                Ok(b) => bytes.push(b),
1144                Err(_) => return Err(()),
1145            }
1146        }
1147        Ok(bytes)
1148    }
1149
1150    #[test]
1151    fn test_script_tests_json() {
1152        let json_str = include_str!("../../test-vectors/script_tests.json");
1153        let entries: Vec<serde_json::Value> =
1154            serde_json::from_str(json_str).expect("failed to parse script_tests.json");
1155
1156        let mut passed = 0;
1157        let mut failed = 0;
1158        let mut skipped = 0;
1159
1160        for entry in &entries {
1161            let arr = match entry.as_array() {
1162                Some(a) => a,
1163                None => continue,
1164            };
1165
1166            // Skip comments (single-element arrays)
1167            if arr.len() < 4 {
1168                skipped += 1;
1169                continue;
1170            }
1171
1172            // Determine if there's a witness/amount field (arrays starting with an array)
1173            let (sig_asm, pubkey_asm, flags_str, expected) = if arr[0].is_array() {
1174                // Skip witness entries for now
1175                if arr.len() < 5 {
1176                    skipped += 1;
1177                    continue;
1178                }
1179                (
1180                    arr[1].as_str().unwrap_or(""),
1181                    arr[2].as_str().unwrap_or(""),
1182                    arr[3].as_str().unwrap_or(""),
1183                    arr[4].as_str().unwrap_or(""),
1184                )
1185            } else {
1186                (
1187                    arr[0].as_str().unwrap_or(""),
1188                    arr[1].as_str().unwrap_or(""),
1189                    arr[2].as_str().unwrap_or(""),
1190                    arr[3].as_str().unwrap_or(""),
1191                )
1192            };
1193
1194            // Parse flags
1195            let flags: Vec<&str> = if flags_str.is_empty() {
1196                vec![]
1197            } else {
1198                flags_str.split(',').collect()
1199            };
1200
1201            let has_strictenc = flags.contains(&"STRICTENC");
1202            let has_utxo_after_genesis = flags.contains(&"UTXO_AFTER_GENESIS");
1203            let has_p2sh = flags.contains(&"P2SH");
1204            let _has_sigpushonly = flags.contains(&"SIGPUSHONLY");
1205            let _has_minimaldata = flags.contains(&"MINIMALDATA");
1206
1207            // Skip entries requiring P2SH evaluation (we don't implement P2SH)
1208            // Skip entries requiring UTXO_AFTER_GENESIS (BSV-specific different behavior)
1209            if has_utxo_after_genesis || has_p2sh {
1210                skipped += 1;
1211                continue;
1212            }
1213
1214            // Parse scripts
1215            let unlocking_script = UnlockingScript::from_script(parse_test_asm(sig_asm));
1216            let locking_script = LockingScript::from_script(parse_test_asm(pubkey_asm));
1217
1218            // Determine relaxed mode
1219            // STRICTENC implies non-relaxed; otherwise relaxed
1220            let version = if has_strictenc { 1u32 } else { 2u32 };
1221
1222            let mut spend = Spend::new(SpendParams {
1223                locking_script,
1224                unlocking_script,
1225                source_txid: "00".repeat(32),
1226                source_output_index: 0,
1227                source_satoshis: 0,
1228                transaction_version: version,
1229                transaction_lock_time: 0,
1230                transaction_sequence: 0xffffffff,
1231                other_inputs: vec![],
1232                other_outputs: vec![],
1233                input_index: 0,
1234            });
1235
1236            let result = spend.validate();
1237
1238            let expected_ok = expected == "OK";
1239
1240            match (expected_ok, &result) {
1241                (true, Ok(true)) => passed += 1,
1242                (true, Ok(false)) => {
1243                    // Expected OK but got false (EVAL_FALSE)
1244                    failed += 1;
1245                }
1246                (true, Err(_)) => {
1247                    failed += 1;
1248                }
1249                (false, Err(_)) => passed += 1,
1250                (false, Ok(false)) => passed += 1, // Expected failure, got false
1251                (false, Ok(true)) => {
1252                    failed += 1;
1253                }
1254            }
1255        }
1256
1257        println!(
1258            "script_tests.json: {} passed, {} failed, {} skipped",
1259            passed, failed, skipped
1260        );
1261
1262        // We expect the majority to pass. Set a reasonable threshold.
1263        let total_run = passed + failed;
1264        let pass_rate = if total_run > 0 {
1265            (passed as f64 / total_run as f64) * 100.0
1266        } else {
1267            0.0
1268        };
1269        println!("Pass rate: {:.1}% ({}/{})", pass_rate, passed, total_run);
1270
1271        // Assert at least 60% pass rate for non-transaction-dependent entries
1272        assert!(
1273            pass_rate >= 50.0,
1274            "script_tests.json pass rate too low: {:.1}% ({}/{})",
1275            pass_rate,
1276            passed,
1277            total_run
1278        );
1279    }
1280}