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;
33mod ops_arithmetic;
34mod ops_crypto;
35mod ops_data;
36mod ops_flow;
37mod ops_stack;
38/// Parsed opcode representation and script parser.
39pub mod parsed_opcode;
40/// Script number arithmetic with Bitcoin consensus rules.
41pub mod scriptnum;
42/// Script execution stack.
43pub mod stack;
44/// Script execution thread — the core interpreter engine.
45pub mod thread;
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 ripemd::{Digest, Ripemd160};
188        use sha2::{Digest as D2, Sha256};
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!(
209            result.is_ok(),
210            "P2PKH-like hash verification should pass: {:?}",
211            result.err()
212        );
213    }
214
215    #[test]
216    fn test_op_if_else_endif() {
217        // OP_1 OP_IF OP_2 OP_ELSE OP_3 OP_ENDIF -> stack: [2]
218        let unlock = Script::from_bytes(&[]);
219        let lock = Script::from_bytes(&[OP_1, OP_IF, OP_2, OP_ELSE, OP_3, OP_ENDIF]);
220        // Both empty check: unlock is empty, lock is not.
221        // But unlock empty means script_idx starts at 1 (locking script).
222        let engine = Engine::new();
223        let result = engine.execute(&unlock, &lock, ScriptFlags::NONE, None, 0);
224        // Stack should have [2], which is truthy → success
225        assert!(
226            result.is_ok(),
227            "IF/ELSE/ENDIF should work: {:?}",
228            result.err()
229        );
230    }
231
232    #[test]
233    fn test_op_notif() {
234        let unlock = Script::from_bytes(&[]);
235        let lock = Script::from_bytes(&[OP_0, OP_NOTIF, OP_1, OP_ELSE, OP_0, OP_ENDIF]);
236        let engine = Engine::new();
237        let result = engine.execute(&unlock, &lock, ScriptFlags::NONE, None, 0);
238        assert!(
239            result.is_ok(),
240            "NOTIF with false should execute first branch"
241        );
242    }
243
244    #[test]
245    fn test_op_return_before_genesis() {
246        let unlock = Script::from_bytes(&[OP_1]);
247        let lock = Script::from_bytes(&[OP_RETURN]);
248        let engine = Engine::new();
249        let result = engine.execute(&unlock, &lock, ScriptFlags::NONE, None, 0);
250        assert!(result.is_err(), "OP_RETURN before genesis should fail");
251    }
252
253    #[test]
254    fn test_op_return_after_genesis() {
255        let unlock = Script::from_bytes(&[OP_1]);
256        let lock = Script::from_bytes(&[OP_1, OP_RETURN, 0x01, 0x02, 0x03]);
257        let engine = Engine::new();
258        let result = engine.execute(&unlock, &lock, ScriptFlags::UTXO_AFTER_GENESIS, None, 0);
259        assert!(
260            result.is_ok(),
261            "OP_RETURN after genesis with OP_1 before should succeed: {:?}",
262            result.err()
263        );
264    }
265
266    #[test]
267    fn test_op_depth() {
268        // Push 3 items, then DEPTH should give 3
269        let unlock = Script::from_bytes(&[OP_1, OP_2, OP_3]);
270        let lock = Script::from_bytes(&[OP_DEPTH, OP_3, OP_EQUAL]);
271        let engine = Engine::new();
272        // After unlock: stack [1, 2, 3]. Lock: DEPTH pushes 3, then 3 EQUAL → true
273        // But then stack has [1, 2, true] → clean stack might complain
274        // Without clean stack flag, just needs true on top
275        let result = engine.execute(&unlock, &lock, ScriptFlags::NONE, None, 0);
276        assert!(result.is_ok(), "DEPTH should return 3: {:?}", result.err());
277    }
278
279    #[test]
280    fn test_op_size() {
281        // Push 3 bytes, SIZE should give 3
282        let unlock = Script::from_bytes(&[0x03, 0xaa, 0xbb, 0xcc]);
283        let lock = Script::from_bytes(&[OP_SIZE, OP_3, OP_EQUALVERIFY, OP_1]);
284        let engine = Engine::new();
285        let result = engine.execute(&unlock, &lock, ScriptFlags::NONE, None, 0);
286        assert!(
287            result.is_ok(),
288            "SIZE of 3-byte element should be 3: {:?}",
289            result.err()
290        );
291    }
292
293    #[test]
294    fn test_op_cat() {
295        // After genesis: CAT two byte arrays
296        let unlock = Script::from_bytes(&[0x01, 0xaa, 0x01, 0xbb]);
297        let lock = Script::from_bytes(&[OP_CAT, 0x02, 0xaa, 0xbb, OP_EQUAL]);
298        let engine = Engine::new();
299        let result = engine.execute(&unlock, &lock, ScriptFlags::UTXO_AFTER_GENESIS, None, 0);
300        assert!(result.is_ok(), "CAT should concatenate: {:?}", result.err());
301    }
302
303    #[test]
304    fn test_op_split() {
305        // Split [aa, bb] at position 1
306        let unlock = Script::from_bytes(&[0x02, 0xaa, 0xbb, OP_1]);
307        let lock =
308            Script::from_bytes(&[OP_SPLIT, 0x01, 0xbb, OP_EQUALVERIFY, 0x01, 0xaa, OP_EQUAL]);
309        let engine = Engine::new();
310        let result = engine.execute(&unlock, &lock, ScriptFlags::UTXO_AFTER_GENESIS, None, 0);
311        assert!(result.is_ok(), "SPLIT should work: {:?}", result.err());
312    }
313
314    #[test]
315    fn test_op_negate() {
316        // NEGATE(1) = -1, check with 1NEGATE
317        let unlock = Script::from_bytes(&[OP_1]);
318        let lock = Script::from_bytes(&[OP_NEGATE, OP_1NEGATE, OP_EQUAL]);
319        let engine = Engine::new();
320        let result = engine.execute(&unlock, &lock, ScriptFlags::NONE, None, 0);
321        assert!(
322            result.is_ok(),
323            "NEGATE(1) should equal -1: {:?}",
324            result.err()
325        );
326    }
327
328    #[test]
329    fn test_op_abs() {
330        // ABS(-1) = 1
331        let unlock = Script::from_bytes(&[OP_1NEGATE]);
332        let lock = Script::from_bytes(&[OP_ABS, OP_1, OP_EQUAL]);
333        let engine = Engine::new();
334        let result = engine.execute(&unlock, &lock, ScriptFlags::NONE, None, 0);
335        assert!(result.is_ok(), "ABS(-1) should equal 1: {:?}", result.err());
336    }
337
338    #[test]
339    fn test_op_not() {
340        // NOT(0) = 1
341        let unlock = Script::from_bytes(&[OP_0]);
342        let lock = Script::from_bytes(&[OP_NOT]);
343        let engine = Engine::new();
344        let result = engine.execute(&unlock, &lock, ScriptFlags::NONE, None, 0);
345        assert!(
346            result.is_ok(),
347            "NOT(0) should be 1 (truthy): {:?}",
348            result.err()
349        );
350    }
351
352    #[test]
353    fn test_op_within() {
354        // 3 is within [2, 5)
355        let unlock = Script::from_bytes(&[OP_3, OP_2, OP_5]);
356        let lock = Script::from_bytes(&[OP_WITHIN]);
357        let engine = Engine::new();
358        let result = engine.execute(&unlock, &lock, ScriptFlags::NONE, None, 0);
359        assert!(
360            result.is_ok(),
361            "3 WITHIN [2,5) should be true: {:?}",
362            result.err()
363        );
364    }
365
366    #[test]
367    fn test_op_mul() {
368        // 3 * 4 = 12
369        let unlock = Script::from_bytes(&[OP_3, OP_4]);
370        let lock = Script::from_bytes(&[OP_MUL, OP_12, OP_EQUAL]);
371        let engine = Engine::new();
372        let result = engine.execute(&unlock, &lock, ScriptFlags::UTXO_AFTER_GENESIS, None, 0);
373        assert!(result.is_ok(), "3 * 4 should equal 12: {:?}", result.err());
374    }
375
376    #[test]
377    fn test_op_div() {
378        // 6 / 3 = 2
379        let unlock = Script::from_bytes(&[OP_6, OP_3]);
380        let lock = Script::from_bytes(&[OP_DIV, OP_2, OP_EQUAL]);
381        let engine = Engine::new();
382        let result = engine.execute(&unlock, &lock, ScriptFlags::UTXO_AFTER_GENESIS, None, 0);
383        assert!(result.is_ok(), "6 / 3 should equal 2: {:?}", result.err());
384    }
385
386    #[test]
387    fn test_op_div_by_zero() {
388        let unlock = Script::from_bytes(&[OP_6, OP_0]);
389        let lock = Script::from_bytes(&[OP_DIV]);
390        let engine = Engine::new();
391        let result = engine.execute(&unlock, &lock, ScriptFlags::UTXO_AFTER_GENESIS, None, 0);
392        assert!(result.is_err(), "Division by zero should fail");
393        let err = result.unwrap_err();
394        assert_eq!(err.code, InterpreterErrorCode::DivideByZero);
395    }
396
397    #[test]
398    fn test_op_mod() {
399        // 7 % 3 = 1
400        let unlock = Script::from_bytes(&[OP_7, OP_3]);
401        let lock = Script::from_bytes(&[OP_MOD, OP_1, OP_EQUAL]);
402        let engine = Engine::new();
403        let result = engine.execute(&unlock, &lock, ScriptFlags::UTXO_AFTER_GENESIS, None, 0);
404        assert!(result.is_ok(), "7 % 3 should equal 1: {:?}", result.err());
405    }
406
407    #[test]
408    fn test_op_booland() {
409        // 1 AND 1 = 1
410        let unlock = Script::from_bytes(&[OP_1, OP_1]);
411        let lock = Script::from_bytes(&[OP_BOOLAND]);
412        let engine = Engine::new();
413        let result = engine.execute(&unlock, &lock, ScriptFlags::NONE, None, 0);
414        assert!(result.is_ok());
415
416        // 1 AND 0 = 0
417        let unlock2 = Script::from_bytes(&[OP_1, OP_0]);
418        let lock2 = Script::from_bytes(&[OP_BOOLAND, OP_NOT]);
419        let result2 = engine.execute(&unlock2, &lock2, ScriptFlags::NONE, None, 0);
420        assert!(result2.is_ok());
421    }
422
423    #[test]
424    fn test_op_numequal() {
425        let unlock = Script::from_bytes(&[OP_5, OP_5]);
426        let lock = Script::from_bytes(&[OP_NUMEQUAL]);
427        let engine = Engine::new();
428        assert!(engine
429            .execute(&unlock, &lock, ScriptFlags::NONE, None, 0)
430            .is_ok());
431    }
432
433    #[test]
434    fn test_op_lessthan() {
435        let unlock = Script::from_bytes(&[OP_3, OP_5]);
436        let lock = Script::from_bytes(&[OP_LESSTHAN]);
437        let engine = Engine::new();
438        assert!(engine
439            .execute(&unlock, &lock, ScriptFlags::NONE, None, 0)
440            .is_ok());
441    }
442
443    #[test]
444    fn test_hash_ops() {
445        // SHA256 of empty
446        let unlock = Script::from_bytes(&[OP_0]);
447        let lock = Script::from_bytes(&[OP_SHA256, OP_SIZE, 0x01, 0x20, OP_EQUALVERIFY, OP_1]);
448        let engine = Engine::new();
449        let result = engine.execute(&unlock, &lock, ScriptFlags::NONE, None, 0);
450        assert!(
451            result.is_ok(),
452            "SHA256 should produce 32 bytes: {:?}",
453            result.err()
454        );
455    }
456
457    #[test]
458    fn test_op_pick_roll() {
459        // PICK: [1, 2, 3], PICK(2) -> [1, 2, 3, 1]
460        let unlock = Script::from_bytes(&[OP_1, OP_2, OP_3, OP_2]);
461        let lock = Script::from_bytes(&[
462            OP_PICK,
463            OP_1,
464            OP_EQUALVERIFY,
465            OP_3,
466            OP_EQUALVERIFY,
467            OP_2,
468            OP_EQUALVERIFY,
469            OP_1,
470        ]);
471        let engine = Engine::new();
472        let result = engine.execute(&unlock, &lock, ScriptFlags::NONE, None, 0);
473        assert!(
474            result.is_ok(),
475            "PICK should copy element: {:?}",
476            result.err()
477        );
478    }
479
480    #[test]
481    fn test_op_toaltstack_fromaltstack() {
482        let unlock = Script::from_bytes(&[OP_5]);
483        let lock = Script::from_bytes(&[OP_TOALTSTACK, OP_FROMALTSTACK, OP_5, OP_EQUAL]);
484        let engine = Engine::new();
485        let result = engine.execute(&unlock, &lock, ScriptFlags::NONE, None, 0);
486        assert!(
487            result.is_ok(),
488            "TOALTSTACK/FROMALTSTACK: {:?}",
489            result.err()
490        );
491    }
492
493    #[test]
494    fn test_disabled_opcodes() {
495        let unlock = Script::from_bytes(&[OP_1]);
496        let lock = Script::from_bytes(&[OP_2MUL]);
497        let engine = Engine::new();
498        let result = engine.execute(&unlock, &lock, ScriptFlags::NONE, None, 0);
499        assert!(result.is_err());
500        assert_eq!(
501            result.unwrap_err().code,
502            InterpreterErrorCode::DisabledOpcode
503        );
504    }
505
506    #[test]
507    fn test_op_invert() {
508        // INVERT of 0x00 should give 0xff
509        let unlock = Script::from_bytes(&[0x01, 0x00]);
510        let lock = Script::from_bytes(&[OP_INVERT, 0x01, 0xff, OP_EQUAL]);
511        let engine = Engine::new();
512        let result = engine.execute(&unlock, &lock, ScriptFlags::UTXO_AFTER_GENESIS, None, 0);
513        assert!(
514            result.is_ok(),
515            "INVERT should flip bits: {:?}",
516            result.err()
517        );
518    }
519
520    #[test]
521    fn test_op_and_or_xor() {
522        // AND: 0xff AND 0x0f = 0x0f
523        let unlock = Script::from_bytes(&[0x01, 0xff, 0x01, 0x0f]);
524        let lock = Script::from_bytes(&[OP_AND, 0x01, 0x0f, OP_EQUAL]);
525        let engine = Engine::new();
526        assert!(engine
527            .execute(&unlock, &lock, ScriptFlags::UTXO_AFTER_GENESIS, None, 0,)
528            .is_ok());
529
530        // OR: 0xf0 OR 0x0f = 0xff
531        let unlock2 = Script::from_bytes(&[0x01, 0xf0, 0x01, 0x0f]);
532        let lock2 = Script::from_bytes(&[OP_OR, 0x01, 0xff, OP_EQUAL]);
533        assert!(engine
534            .execute(&unlock2, &lock2, ScriptFlags::UTXO_AFTER_GENESIS, None, 0,)
535            .is_ok());
536
537        // XOR: 0xff XOR 0xff = 0x00
538        let unlock3 = Script::from_bytes(&[0x01, 0xff, 0x01, 0xff]);
539        let lock3 = Script::from_bytes(&[OP_XOR, 0x01, 0x00, OP_EQUAL]);
540        assert!(engine
541            .execute(&unlock3, &lock3, ScriptFlags::UTXO_AFTER_GENESIS, None, 0,)
542            .is_ok());
543    }
544
545    #[test]
546    fn test_op_rot() {
547        // [1 2 3] ROT -> [2 3 1]
548        let unlock = Script::from_bytes(&[OP_1, OP_2, OP_3]);
549        let lock = Script::from_bytes(&[
550            OP_ROT,
551            OP_1,
552            OP_EQUALVERIFY, // top was 3, after ROT top is 1
553            OP_3,
554            OP_EQUALVERIFY,
555            OP_2,
556            OP_EQUAL,
557        ]);
558        let engine = Engine::new();
559        let result = engine.execute(&unlock, &lock, ScriptFlags::NONE, None, 0);
560        assert!(result.is_ok(), "ROT should rotate: {:?}", result.err());
561    }
562
563    #[test]
564    fn test_op_tuck() {
565        // [1 2] TUCK -> [2 1 2]
566        let unlock = Script::from_bytes(&[OP_1, OP_2]);
567        let lock = Script::from_bytes(&[
568            OP_TUCK,
569            OP_2,
570            OP_EQUALVERIFY,
571            OP_1,
572            OP_EQUALVERIFY,
573            OP_2,
574            OP_EQUAL,
575        ]);
576        let engine = Engine::new();
577        let result = engine.execute(&unlock, &lock, ScriptFlags::NONE, None, 0);
578        assert!(result.is_ok(), "TUCK should work: {:?}", result.err());
579    }
580
581    #[test]
582    fn test_op_2dup() {
583        let unlock = Script::from_bytes(&[OP_1, OP_2]);
584        let lock = Script::from_bytes(&[
585            OP_2DUP,
586            OP_2,
587            OP_EQUALVERIFY,
588            OP_1,
589            OP_EQUALVERIFY,
590            OP_2,
591            OP_EQUALVERIFY,
592            OP_1,
593            OP_EQUAL,
594        ]);
595        let engine = Engine::new();
596        assert!(engine
597            .execute(&unlock, &lock, ScriptFlags::NONE, None, 0)
598            .is_ok());
599    }
600
601    #[test]
602    fn test_empty_both_scripts() {
603        let engine = Engine::new();
604        let result = engine.execute(&Script::new(), &Script::new(), ScriptFlags::NONE, None, 0);
605        assert!(result.is_err());
606        assert_eq!(result.unwrap_err().code, InterpreterErrorCode::EvalFalse);
607    }
608
609    #[test]
610    fn test_op_greaterthan() {
611        let unlock = Script::from_bytes(&[OP_5, OP_3]);
612        let lock = Script::from_bytes(&[OP_GREATERTHAN]);
613        let engine = Engine::new();
614        assert!(engine
615            .execute(&unlock, &lock, ScriptFlags::NONE, None, 0)
616            .is_ok());
617    }
618
619    #[test]
620    fn test_op_min_max() {
621        // MIN(3, 5) = 3
622        let unlock = Script::from_bytes(&[OP_3, OP_5]);
623        let lock = Script::from_bytes(&[OP_MIN, OP_3, OP_EQUAL]);
624        let engine = Engine::new();
625        assert!(engine
626            .execute(&unlock, &lock, ScriptFlags::NONE, None, 0)
627            .is_ok());
628
629        // MAX(3, 5) = 5
630        let unlock2 = Script::from_bytes(&[OP_3, OP_5]);
631        let lock2 = Script::from_bytes(&[OP_MAX, OP_5, OP_EQUAL]);
632        assert!(engine
633            .execute(&unlock2, &lock2, ScriptFlags::NONE, None, 0)
634            .is_ok());
635    }
636
637    #[test]
638    fn test_op_verify_fail() {
639        let unlock = Script::from_bytes(&[OP_0]);
640        let lock = Script::from_bytes(&[OP_VERIFY]);
641        let engine = Engine::new();
642        let result = engine.execute(&unlock, &lock, ScriptFlags::NONE, None, 0);
643        assert!(result.is_err());
644        assert_eq!(result.unwrap_err().code, InterpreterErrorCode::Verify);
645    }
646
647    #[test]
648    fn test_nested_if() {
649        // OP_1 OP_IF OP_1 OP_IF OP_2 OP_ENDIF OP_ENDIF
650        let unlock = Script::from_bytes(&[]);
651        let lock = Script::from_bytes(&[OP_1, OP_IF, OP_1, OP_IF, OP_2, OP_ENDIF, OP_ENDIF]);
652        let engine = Engine::new();
653        let result = engine.execute(&unlock, &lock, ScriptFlags::NONE, None, 0);
654        assert!(result.is_ok(), "Nested IF should work: {:?}", result.err());
655    }
656
657    #[test]
658    fn test_unbalanced_if() {
659        let unlock = Script::from_bytes(&[OP_1]);
660        let lock = Script::from_bytes(&[OP_IF]);
661        let engine = Engine::new();
662        let result = engine.execute(&unlock, &lock, ScriptFlags::NONE, None, 0);
663        assert!(result.is_err());
664        assert_eq!(
665            result.unwrap_err().code,
666            InterpreterErrorCode::UnbalancedConditional
667        );
668    }
669
670    #[test]
671    fn test_op_ifdup() {
672        // OP_1 OP_IFDUP → stack [1, 1]
673        let unlock = Script::from_bytes(&[OP_1]);
674        let lock = Script::from_bytes(&[OP_IFDUP, OP_EQUAL]);
675        let engine = Engine::new();
676        assert!(engine
677            .execute(&unlock, &lock, ScriptFlags::NONE, None, 0)
678            .is_ok());
679    }
680
681    #[test]
682    fn test_clean_stack_without_bip16() {
683        let engine = Engine::new();
684        let result = engine.execute(
685            &Script::from_bytes(&[OP_1]),
686            &Script::from_bytes(&[OP_1]),
687            ScriptFlags::VERIFY_CLEAN_STACK,
688            None,
689            0,
690        );
691        assert!(result.is_err());
692        assert_eq!(result.unwrap_err().code, InterpreterErrorCode::InvalidFlags);
693    }
694
695    #[test]
696    fn test_lshift_rshift() {
697        // [0x80] << 1 = [0x00, 0x01] (but with same-length result, [0x00])
698        // Actually Go impl: result is same length as input
699        // [0x01] << 1 = [0x02]
700        let unlock = Script::from_bytes(&[0x01, 0x01, OP_1]);
701        let lock = Script::from_bytes(&[OP_LSHIFT, 0x01, 0x02, OP_EQUAL]);
702        let engine = Engine::new();
703        let result = engine.execute(&unlock, &lock, ScriptFlags::UTXO_AFTER_GENESIS, None, 0);
704        assert!(result.is_ok(), "LSHIFT should work: {:?}", result.err());
705    }
706}