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