Skip to main content

bsv_script/interpreter/
mod.rs

1//! Full Bitcoin script interpreter.
2//!
3//! Executes locking and unlocking scripts to verify transaction inputs,
4//! supporting all standard opcodes and verification flags.
5//!
6//! # Architecture
7//!
8//! The interpreter does not depend on the transaction crate directly to avoid
9//! circular dependencies. Instead, callers provide a [`TxContext`](crate::interpreter::TxContext) trait
10//! implementation that handles signature hash computation and verification.
11//!
12//! # Example
13//!
14//! ```ignore
15//! use bsv_script::interpreter::{Engine, ScriptFlags};
16//!
17//! let engine = Engine::new();
18//! engine.execute(
19//!     &unlocking_script,
20//!     &locking_script,
21//!     ScriptFlags::ENABLE_SIGHASH_FORKID | ScriptFlags::UTXO_AFTER_GENESIS,
22//!     None, // no tx context needed for simple scripts
23//!     0,
24//! )?;
25//! ```
26
27/// Interpreter configuration with pre/post-genesis limits.
28pub mod config;
29/// Interpreter error types and error codes.
30pub mod error;
31/// Script verification flags (bitmask).
32pub mod flags;
33/// Parsed opcode representation and script parser.
34pub mod parsed_opcode;
35/// Script number arithmetic with Bitcoin consensus rules.
36pub mod scriptnum;
37/// Script execution stack.
38pub mod stack;
39/// Script execution thread — the core interpreter engine.
40pub mod thread;
41mod ops_arithmetic;
42mod ops_crypto;
43mod ops_data;
44mod ops_flow;
45mod ops_stack;
46
47pub use config::Config;
48pub use error::{InterpreterError, InterpreterErrorCode};
49pub use flags::ScriptFlags;
50pub use parsed_opcode::{ParsedOpcode, ParsedScript};
51pub use scriptnum::ScriptNumber;
52pub use stack::Stack;
53
54use crate::Script;
55use thread::Thread;
56
57/// Transaction context trait — provides signature verification without
58/// circular dependency on bsv-transaction.
59///
60/// Implementors provide the transaction data needed for OP_CHECKSIG,
61/// OP_CHECKMULTISIG, OP_CHECKLOCKTIMEVERIFY, and OP_CHECKSEQUENCEVERIFY.
62pub trait TxContext {
63    /// Verify a signature against a public key for the given input.
64    ///
65    /// `full_sig` includes the sighash flag byte at the end.
66    /// `pub_key` is the public key bytes.
67    /// `sub_script` is the relevant portion of the locking script.
68    /// `input_idx` is the input being verified.
69    /// `sighash_flag` is the sighash type.
70    ///
71    /// Returns Ok(true) if valid, Ok(false) if invalid, Err on failure.
72    fn verify_signature(
73        &self,
74        full_sig: &[u8],
75        pub_key: &[u8],
76        sub_script: &Script,
77        input_idx: usize,
78        sighash_flag: u32,
79    ) -> Result<bool, InterpreterError>;
80
81    /// Get the transaction lock time.
82    fn lock_time(&self) -> u32;
83
84    /// Get the transaction version.
85    fn tx_version(&self) -> u32;
86
87    /// Get the sequence number of the given input.
88    fn input_sequence(&self, input_idx: usize) -> u32;
89}
90
91/// The script execution engine.
92pub struct Engine;
93
94impl Engine {
95    /// Create a new script execution engine instance.
96    pub fn new() -> Self {
97        Engine
98    }
99
100    /// Execute unlocking + locking scripts.
101    ///
102    /// # Arguments
103    /// * `unlocking_script` - The input's unlocking (signature) script.
104    /// * `locking_script` - The output's locking (pubkey) script.
105    /// * `flags` - Verification flags.
106    /// * `tx_context` - Optional transaction context for checksig operations.
107    /// * `input_idx` - The input index being verified.
108    pub fn execute(
109        &self,
110        unlocking_script: &Script,
111        locking_script: &Script,
112        flags: ScriptFlags,
113        tx_context: Option<&dyn TxContext>,
114        input_idx: usize,
115    ) -> Result<(), InterpreterError> {
116        let mut thread = Thread::new(
117            unlocking_script,
118            locking_script,
119            flags,
120            tx_context,
121            input_idx,
122        )?;
123        thread.execute()
124    }
125}
126
127#[cfg(test)]
128mod tests {
129    use super::*;
130    use crate::opcodes::*;
131
132    #[test]
133    fn test_simple_true() {
134        // OP_TRUE should leave true on stack
135        let unlock = Script::from_bytes(&[OP_TRUE]);
136        let lock = Script::from_bytes(&[]);
137        // Empty locking script + OP_TRUE should fail (both empty check)
138        // Actually: unlock=OP_1, lock=empty → stack has [1], which is true
139        // But engine rejects both-empty. lock is empty but unlock isn't.
140        // Let's do: unlock pushes 1, lock = OP_1 (just returns true)
141    }
142
143    #[test]
144    fn test_op_1_op_1_op_equal() {
145        // unlocking: OP_1, locking: OP_1 OP_EQUAL
146        let unlock = Script::from_bytes(&[OP_1]);
147        let lock = Script::from_bytes(&[OP_1, OP_EQUAL]);
148        let engine = Engine::new();
149        let result = engine.execute(&unlock, &lock, ScriptFlags::NONE, None, 0);
150        assert!(result.is_ok(), "OP_1 OP_1 OP_EQUAL should succeed");
151    }
152
153    #[test]
154    fn test_op_1_op_2_op_equal_fails() {
155        let unlock = Script::from_bytes(&[OP_1]);
156        let lock = Script::from_bytes(&[OP_2, OP_EQUAL]);
157        let engine = Engine::new();
158        let result = engine.execute(&unlock, &lock, ScriptFlags::NONE, None, 0);
159        assert!(result.is_err(), "OP_1 OP_2 OP_EQUAL should fail");
160    }
161
162    #[test]
163    fn test_op_add() {
164        // 2 + 3 = 5, verify 5
165        let unlock = Script::from_bytes(&[OP_2, OP_3]);
166        let lock = Script::from_bytes(&[OP_ADD, OP_5, OP_EQUAL]);
167        let engine = Engine::new();
168        let result = engine.execute(&unlock, &lock, ScriptFlags::NONE, None, 0);
169        assert!(result.is_ok(), "2 + 3 should equal 5");
170    }
171
172    #[test]
173    fn test_op_sub() {
174        // 5 - 3 = 2
175        let unlock = Script::from_bytes(&[OP_5, OP_3]);
176        let lock = Script::from_bytes(&[OP_SUB, OP_2, OP_EQUAL]);
177        let engine = Engine::new();
178        let result = engine.execute(&unlock, &lock, ScriptFlags::NONE, None, 0);
179        assert!(result.is_ok(), "5 - 3 should equal 2");
180    }
181
182    #[test]
183    fn test_op_dup_hash160_equalverify() {
184        // Standard P2PKH pattern (without actual sig verification)
185        // Just test the hash path: push data, dup, hash160, push expected, equalverify, checksig would need tx
186        // Simplified: push some bytes, OP_DUP, OP_HASH160, push expected_hash, OP_EQUALVERIFY, OP_1
187        use sha2::{Sha256, Digest as D2};
188        use ripemd::{Ripemd160, Digest};
189
190        let pubkey = vec![0x04; 33]; // fake pubkey
191        let sha = Sha256::digest(&pubkey);
192        let hash160 = Ripemd160::digest(&sha);
193
194        let mut unlock_bytes = vec![pubkey.len() as u8];
195        unlock_bytes.extend_from_slice(&pubkey);
196
197        let mut lock_bytes = vec![OP_DUP, OP_HASH160];
198        lock_bytes.push(hash160.len() as u8);
199        lock_bytes.extend_from_slice(&hash160);
200        lock_bytes.push(OP_EQUALVERIFY);
201        // We can't do checksig without tx, so just push OP_1
202        lock_bytes.push(OP_1);
203
204        let unlock = Script::from_bytes(&unlock_bytes);
205        let lock = Script::from_bytes(&lock_bytes);
206        let engine = Engine::new();
207        let result = engine.execute(&unlock, &lock, ScriptFlags::NONE, None, 0);
208        assert!(result.is_ok(), "P2PKH-like hash verification should pass: {:?}", result.err());
209    }
210
211    #[test]
212    fn test_op_if_else_endif() {
213        // OP_1 OP_IF OP_2 OP_ELSE OP_3 OP_ENDIF -> stack: [2]
214        let unlock = Script::from_bytes(&[]);
215        let lock = Script::from_bytes(&[OP_1, OP_IF, OP_2, OP_ELSE, OP_3, OP_ENDIF]);
216        // Both empty check: unlock is empty, lock is not.
217        // But unlock empty means script_idx starts at 1 (locking script).
218        let engine = Engine::new();
219        let result = engine.execute(&unlock, &lock, ScriptFlags::NONE, None, 0);
220        // Stack should have [2], which is truthy → success
221        assert!(result.is_ok(), "IF/ELSE/ENDIF should work: {:?}", result.err());
222    }
223
224    #[test]
225    fn test_op_notif() {
226        let unlock = Script::from_bytes(&[]);
227        let lock = Script::from_bytes(&[OP_0, OP_NOTIF, OP_1, OP_ELSE, OP_0, OP_ENDIF]);
228        let engine = Engine::new();
229        let result = engine.execute(&unlock, &lock, ScriptFlags::NONE, None, 0);
230        assert!(result.is_ok(), "NOTIF with false should execute first branch");
231    }
232
233    #[test]
234    fn test_op_return_before_genesis() {
235        let unlock = Script::from_bytes(&[OP_1]);
236        let lock = Script::from_bytes(&[OP_RETURN]);
237        let engine = Engine::new();
238        let result = engine.execute(&unlock, &lock, ScriptFlags::NONE, None, 0);
239        assert!(result.is_err(), "OP_RETURN before genesis should fail");
240    }
241
242    #[test]
243    fn test_op_return_after_genesis() {
244        let unlock = Script::from_bytes(&[OP_1]);
245        let lock = Script::from_bytes(&[OP_1, OP_RETURN, 0x01, 0x02, 0x03]);
246        let engine = Engine::new();
247        let result = engine.execute(
248            &unlock,
249            &lock,
250            ScriptFlags::UTXO_AFTER_GENESIS,
251            None,
252            0,
253        );
254        assert!(result.is_ok(), "OP_RETURN after genesis with OP_1 before should succeed: {:?}", result.err());
255    }
256
257    #[test]
258    fn test_op_depth() {
259        // Push 3 items, then DEPTH should give 3
260        let unlock = Script::from_bytes(&[OP_1, OP_2, OP_3]);
261        let lock = Script::from_bytes(&[OP_DEPTH, OP_3, OP_EQUAL]);
262        let engine = Engine::new();
263        // After unlock: stack [1, 2, 3]. Lock: DEPTH pushes 3, then 3 EQUAL → true
264        // But then stack has [1, 2, true] → clean stack might complain
265        // Without clean stack flag, just needs true on top
266        let result = engine.execute(&unlock, &lock, ScriptFlags::NONE, None, 0);
267        assert!(result.is_ok(), "DEPTH should return 3: {:?}", result.err());
268    }
269
270    #[test]
271    fn test_op_size() {
272        // Push 3 bytes, SIZE should give 3
273        let unlock = Script::from_bytes(&[0x03, 0xaa, 0xbb, 0xcc]);
274        let lock = Script::from_bytes(&[OP_SIZE, OP_3, OP_EQUALVERIFY, OP_1]);
275        let engine = Engine::new();
276        let result = engine.execute(&unlock, &lock, ScriptFlags::NONE, None, 0);
277        assert!(result.is_ok(), "SIZE of 3-byte element should be 3: {:?}", result.err());
278    }
279
280    #[test]
281    fn test_op_cat() {
282        // After genesis: CAT two byte arrays
283        let unlock = Script::from_bytes(&[0x01, 0xaa, 0x01, 0xbb]);
284        let lock = Script::from_bytes(&[OP_CAT, 0x02, 0xaa, 0xbb, OP_EQUAL]);
285        let engine = Engine::new();
286        let result = engine.execute(
287            &unlock,
288            &lock,
289            ScriptFlags::UTXO_AFTER_GENESIS,
290            None,
291            0,
292        );
293        assert!(result.is_ok(), "CAT should concatenate: {:?}", result.err());
294    }
295
296    #[test]
297    fn test_op_split() {
298        // Split [aa, bb] at position 1
299        let unlock = Script::from_bytes(&[0x02, 0xaa, 0xbb, OP_1]);
300        let lock = Script::from_bytes(&[OP_SPLIT, 0x01, 0xbb, OP_EQUALVERIFY, 0x01, 0xaa, OP_EQUAL]);
301        let engine = Engine::new();
302        let result = engine.execute(
303            &unlock,
304            &lock,
305            ScriptFlags::UTXO_AFTER_GENESIS,
306            None,
307            0,
308        );
309        assert!(result.is_ok(), "SPLIT should work: {:?}", result.err());
310    }
311
312    #[test]
313    fn test_op_negate() {
314        // NEGATE(1) = -1, check with 1NEGATE
315        let unlock = Script::from_bytes(&[OP_1]);
316        let lock = Script::from_bytes(&[OP_NEGATE, OP_1NEGATE, OP_EQUAL]);
317        let engine = Engine::new();
318        let result = engine.execute(&unlock, &lock, ScriptFlags::NONE, None, 0);
319        assert!(result.is_ok(), "NEGATE(1) should equal -1: {:?}", result.err());
320    }
321
322    #[test]
323    fn test_op_abs() {
324        // ABS(-1) = 1
325        let unlock = Script::from_bytes(&[OP_1NEGATE]);
326        let lock = Script::from_bytes(&[OP_ABS, OP_1, OP_EQUAL]);
327        let engine = Engine::new();
328        let result = engine.execute(&unlock, &lock, ScriptFlags::NONE, None, 0);
329        assert!(result.is_ok(), "ABS(-1) should equal 1: {:?}", result.err());
330    }
331
332    #[test]
333    fn test_op_not() {
334        // NOT(0) = 1
335        let unlock = Script::from_bytes(&[OP_0]);
336        let lock = Script::from_bytes(&[OP_NOT]);
337        let engine = Engine::new();
338        let result = engine.execute(&unlock, &lock, ScriptFlags::NONE, None, 0);
339        assert!(result.is_ok(), "NOT(0) should be 1 (truthy): {:?}", result.err());
340    }
341
342    #[test]
343    fn test_op_within() {
344        // 3 is within [2, 5)
345        let unlock = Script::from_bytes(&[OP_3, OP_2, OP_5]);
346        let lock = Script::from_bytes(&[OP_WITHIN]);
347        let engine = Engine::new();
348        let result = engine.execute(&unlock, &lock, ScriptFlags::NONE, None, 0);
349        assert!(result.is_ok(), "3 WITHIN [2,5) should be true: {:?}", result.err());
350    }
351
352    #[test]
353    fn test_op_mul() {
354        // 3 * 4 = 12
355        let unlock = Script::from_bytes(&[OP_3, OP_4]);
356        let lock = Script::from_bytes(&[OP_MUL, OP_12, OP_EQUAL]);
357        let engine = Engine::new();
358        let result = engine.execute(
359            &unlock,
360            &lock,
361            ScriptFlags::UTXO_AFTER_GENESIS,
362            None,
363            0,
364        );
365        assert!(result.is_ok(), "3 * 4 should equal 12: {:?}", result.err());
366    }
367
368    #[test]
369    fn test_op_div() {
370        // 6 / 3 = 2
371        let unlock = Script::from_bytes(&[OP_6, OP_3]);
372        let lock = Script::from_bytes(&[OP_DIV, OP_2, OP_EQUAL]);
373        let engine = Engine::new();
374        let result = engine.execute(
375            &unlock,
376            &lock,
377            ScriptFlags::UTXO_AFTER_GENESIS,
378            None,
379            0,
380        );
381        assert!(result.is_ok(), "6 / 3 should equal 2: {:?}", result.err());
382    }
383
384    #[test]
385    fn test_op_div_by_zero() {
386        let unlock = Script::from_bytes(&[OP_6, OP_0]);
387        let lock = Script::from_bytes(&[OP_DIV]);
388        let engine = Engine::new();
389        let result = engine.execute(
390            &unlock,
391            &lock,
392            ScriptFlags::UTXO_AFTER_GENESIS,
393            None,
394            0,
395        );
396        assert!(result.is_err(), "Division by zero should fail");
397        let err = result.unwrap_err();
398        assert_eq!(err.code, InterpreterErrorCode::DivideByZero);
399    }
400
401    #[test]
402    fn test_op_mod() {
403        // 7 % 3 = 1
404        let unlock = Script::from_bytes(&[OP_7, OP_3]);
405        let lock = Script::from_bytes(&[OP_MOD, OP_1, OP_EQUAL]);
406        let engine = Engine::new();
407        let result = engine.execute(
408            &unlock,
409            &lock,
410            ScriptFlags::UTXO_AFTER_GENESIS,
411            None,
412            0,
413        );
414        assert!(result.is_ok(), "7 % 3 should equal 1: {:?}", result.err());
415    }
416
417    #[test]
418    fn test_op_booland() {
419        // 1 AND 1 = 1
420        let unlock = Script::from_bytes(&[OP_1, OP_1]);
421        let lock = Script::from_bytes(&[OP_BOOLAND]);
422        let engine = Engine::new();
423        let result = engine.execute(&unlock, &lock, ScriptFlags::NONE, None, 0);
424        assert!(result.is_ok());
425
426        // 1 AND 0 = 0
427        let unlock2 = Script::from_bytes(&[OP_1, OP_0]);
428        let lock2 = Script::from_bytes(&[OP_BOOLAND, OP_NOT]);
429        let result2 = engine.execute(&unlock2, &lock2, ScriptFlags::NONE, None, 0);
430        assert!(result2.is_ok());
431    }
432
433    #[test]
434    fn test_op_numequal() {
435        let unlock = Script::from_bytes(&[OP_5, OP_5]);
436        let lock = Script::from_bytes(&[OP_NUMEQUAL]);
437        let engine = Engine::new();
438        assert!(engine.execute(&unlock, &lock, ScriptFlags::NONE, None, 0).is_ok());
439    }
440
441    #[test]
442    fn test_op_lessthan() {
443        let unlock = Script::from_bytes(&[OP_3, OP_5]);
444        let lock = Script::from_bytes(&[OP_LESSTHAN]);
445        let engine = Engine::new();
446        assert!(engine.execute(&unlock, &lock, ScriptFlags::NONE, None, 0).is_ok());
447    }
448
449    #[test]
450    fn test_hash_ops() {
451        // SHA256 of empty
452        let unlock = Script::from_bytes(&[OP_0]);
453        let lock = Script::from_bytes(&[OP_SHA256, OP_SIZE, 0x01, 0x20, OP_EQUALVERIFY, OP_1]);
454        let engine = Engine::new();
455        let result = engine.execute(&unlock, &lock, ScriptFlags::NONE, None, 0);
456        assert!(result.is_ok(), "SHA256 should produce 32 bytes: {:?}", result.err());
457    }
458
459    #[test]
460    fn test_op_pick_roll() {
461        // PICK: [1, 2, 3], PICK(2) -> [1, 2, 3, 1]
462        let unlock = Script::from_bytes(&[OP_1, OP_2, OP_3, OP_2]);
463        let lock = Script::from_bytes(&[OP_PICK, OP_1, OP_EQUALVERIFY, OP_3, OP_EQUALVERIFY, OP_2, OP_EQUALVERIFY, OP_1]);
464        let engine = Engine::new();
465        let result = engine.execute(&unlock, &lock, ScriptFlags::NONE, None, 0);
466        assert!(result.is_ok(), "PICK should copy element: {:?}", result.err());
467    }
468
469    #[test]
470    fn test_op_toaltstack_fromaltstack() {
471        let unlock = Script::from_bytes(&[OP_5]);
472        let lock = Script::from_bytes(&[OP_TOALTSTACK, OP_FROMALTSTACK, OP_5, OP_EQUAL]);
473        let engine = Engine::new();
474        let result = engine.execute(&unlock, &lock, ScriptFlags::NONE, None, 0);
475        assert!(result.is_ok(), "TOALTSTACK/FROMALTSTACK: {:?}", result.err());
476    }
477
478    #[test]
479    fn test_disabled_opcodes() {
480        let unlock = Script::from_bytes(&[OP_1]);
481        let lock = Script::from_bytes(&[OP_2MUL]);
482        let engine = Engine::new();
483        let result = engine.execute(&unlock, &lock, ScriptFlags::NONE, None, 0);
484        assert!(result.is_err());
485        assert_eq!(result.unwrap_err().code, InterpreterErrorCode::DisabledOpcode);
486    }
487
488    #[test]
489    fn test_op_invert() {
490        // INVERT of 0x00 should give 0xff
491        let unlock = Script::from_bytes(&[0x01, 0x00]);
492        let lock = Script::from_bytes(&[OP_INVERT, 0x01, 0xff, OP_EQUAL]);
493        let engine = Engine::new();
494        let result = engine.execute(
495            &unlock,
496            &lock,
497            ScriptFlags::UTXO_AFTER_GENESIS,
498            None,
499            0,
500        );
501        assert!(result.is_ok(), "INVERT should flip bits: {:?}", result.err());
502    }
503
504    #[test]
505    fn test_op_and_or_xor() {
506        // AND: 0xff AND 0x0f = 0x0f
507        let unlock = Script::from_bytes(&[0x01, 0xff, 0x01, 0x0f]);
508        let lock = Script::from_bytes(&[OP_AND, 0x01, 0x0f, OP_EQUAL]);
509        let engine = Engine::new();
510        assert!(engine.execute(
511            &unlock,
512            &lock,
513            ScriptFlags::UTXO_AFTER_GENESIS,
514            None,
515            0,
516        ).is_ok());
517
518        // OR: 0xf0 OR 0x0f = 0xff
519        let unlock2 = Script::from_bytes(&[0x01, 0xf0, 0x01, 0x0f]);
520        let lock2 = Script::from_bytes(&[OP_OR, 0x01, 0xff, OP_EQUAL]);
521        assert!(engine.execute(
522            &unlock2,
523            &lock2,
524            ScriptFlags::UTXO_AFTER_GENESIS,
525            None,
526            0,
527        ).is_ok());
528
529        // XOR: 0xff XOR 0xff = 0x00
530        let unlock3 = Script::from_bytes(&[0x01, 0xff, 0x01, 0xff]);
531        let lock3 = Script::from_bytes(&[OP_XOR, 0x01, 0x00, OP_EQUAL]);
532        assert!(engine.execute(
533            &unlock3,
534            &lock3,
535            ScriptFlags::UTXO_AFTER_GENESIS,
536            None,
537            0,
538        ).is_ok());
539    }
540
541    #[test]
542    fn test_op_rot() {
543        // [1 2 3] ROT -> [2 3 1]
544        let unlock = Script::from_bytes(&[OP_1, OP_2, OP_3]);
545        let lock = Script::from_bytes(&[
546            OP_ROT,
547            OP_1, OP_EQUALVERIFY,   // top was 3, after ROT top is 1
548            OP_3, OP_EQUALVERIFY,
549            OP_2,OP_EQUAL,
550        ]);
551        let engine = Engine::new();
552        let result = engine.execute(&unlock, &lock, ScriptFlags::NONE, None, 0);
553        assert!(result.is_ok(), "ROT should rotate: {:?}", result.err());
554    }
555
556    #[test]
557    fn test_op_tuck() {
558        // [1 2] TUCK -> [2 1 2]
559        let unlock = Script::from_bytes(&[OP_1, OP_2]);
560        let lock = Script::from_bytes(&[
561            OP_TUCK,
562            OP_2, OP_EQUALVERIFY,
563            OP_1, OP_EQUALVERIFY,
564            OP_2, OP_EQUAL,
565        ]);
566        let engine = Engine::new();
567        let result = engine.execute(&unlock, &lock, ScriptFlags::NONE, None, 0);
568        assert!(result.is_ok(), "TUCK should work: {:?}", result.err());
569    }
570
571    #[test]
572    fn test_op_2dup() {
573        let unlock = Script::from_bytes(&[OP_1, OP_2]);
574        let lock = Script::from_bytes(&[
575            OP_2DUP,
576            OP_2, OP_EQUALVERIFY,
577            OP_1, OP_EQUALVERIFY,
578            OP_2, OP_EQUALVERIFY,
579            OP_1, OP_EQUAL,
580        ]);
581        let engine = Engine::new();
582        assert!(engine.execute(&unlock, &lock, ScriptFlags::NONE, None, 0).is_ok());
583    }
584
585    #[test]
586    fn test_empty_both_scripts() {
587        let engine = Engine::new();
588        let result = engine.execute(
589            &Script::new(),
590            &Script::new(),
591            ScriptFlags::NONE,
592            None,
593            0,
594        );
595        assert!(result.is_err());
596        assert_eq!(result.unwrap_err().code, InterpreterErrorCode::EvalFalse);
597    }
598
599    #[test]
600    fn test_op_greaterthan() {
601        let unlock = Script::from_bytes(&[OP_5, OP_3]);
602        let lock = Script::from_bytes(&[OP_GREATERTHAN]);
603        let engine = Engine::new();
604        assert!(engine.execute(&unlock, &lock, ScriptFlags::NONE, None, 0).is_ok());
605    }
606
607    #[test]
608    fn test_op_min_max() {
609        // MIN(3, 5) = 3
610        let unlock = Script::from_bytes(&[OP_3, OP_5]);
611        let lock = Script::from_bytes(&[OP_MIN, OP_3, OP_EQUAL]);
612        let engine = Engine::new();
613        assert!(engine.execute(&unlock, &lock, ScriptFlags::NONE, None, 0).is_ok());
614
615        // MAX(3, 5) = 5
616        let unlock2 = Script::from_bytes(&[OP_3, OP_5]);
617        let lock2 = Script::from_bytes(&[OP_MAX, OP_5, OP_EQUAL]);
618        assert!(engine.execute(&unlock2, &lock2, ScriptFlags::NONE, None, 0).is_ok());
619    }
620
621    #[test]
622    fn test_op_verify_fail() {
623        let unlock = Script::from_bytes(&[OP_0]);
624        let lock = Script::from_bytes(&[OP_VERIFY]);
625        let engine = Engine::new();
626        let result = engine.execute(&unlock, &lock, ScriptFlags::NONE, None, 0);
627        assert!(result.is_err());
628        assert_eq!(result.unwrap_err().code, InterpreterErrorCode::Verify);
629    }
630
631    #[test]
632    fn test_nested_if() {
633        // OP_1 OP_IF OP_1 OP_IF OP_2 OP_ENDIF OP_ENDIF
634        let unlock = Script::from_bytes(&[]);
635        let lock = Script::from_bytes(&[OP_1, OP_IF, OP_1, OP_IF, OP_2, OP_ENDIF, OP_ENDIF]);
636        let engine = Engine::new();
637        let result = engine.execute(&unlock, &lock, ScriptFlags::NONE, None, 0);
638        assert!(result.is_ok(), "Nested IF should work: {:?}", result.err());
639    }
640
641    #[test]
642    fn test_unbalanced_if() {
643        let unlock = Script::from_bytes(&[OP_1]);
644        let lock = Script::from_bytes(&[OP_IF]);
645        let engine = Engine::new();
646        let result = engine.execute(&unlock, &lock, ScriptFlags::NONE, None, 0);
647        assert!(result.is_err());
648        assert_eq!(
649            result.unwrap_err().code,
650            InterpreterErrorCode::UnbalancedConditional
651        );
652    }
653
654    #[test]
655    fn test_op_ifdup() {
656        // OP_1 OP_IFDUP → stack [1, 1]
657        let unlock = Script::from_bytes(&[OP_1]);
658        let lock = Script::from_bytes(&[OP_IFDUP, OP_EQUAL]);
659        let engine = Engine::new();
660        assert!(engine.execute(&unlock, &lock, ScriptFlags::NONE, None, 0).is_ok());
661    }
662
663    #[test]
664    fn test_clean_stack_without_bip16() {
665        let engine = Engine::new();
666        let result = engine.execute(
667            &Script::from_bytes(&[OP_1]),
668            &Script::from_bytes(&[OP_1]),
669            ScriptFlags::VERIFY_CLEAN_STACK,
670            None,
671            0,
672        );
673        assert!(result.is_err());
674        assert_eq!(result.unwrap_err().code, InterpreterErrorCode::InvalidFlags);
675    }
676
677    #[test]
678    fn test_lshift_rshift() {
679        // [0x80] << 1 = [0x00, 0x01] (but with same-length result, [0x00])
680        // Actually Go impl: result is same length as input
681        // [0x01] << 1 = [0x02]
682        let unlock = Script::from_bytes(&[0x01, 0x01, OP_1]);
683        let lock = Script::from_bytes(&[OP_LSHIFT, 0x01, 0x02, OP_EQUAL]);
684        let engine = Engine::new();
685        let result = engine.execute(
686            &unlock,
687            &lock,
688            ScriptFlags::UTXO_AFTER_GENESIS,
689            None,
690            0,
691        );
692        assert!(result.is_ok(), "LSHIFT should work: {:?}", result.err());
693    }
694}