Skip to main content

mini_bitcoin_script/
engine.rs

1use crate::error::ScriptError;
2use crate::hash;
3use crate::opcode::Opcode;
4use crate::stack::{is_true, Stack};
5use crate::token::Token;
6
7/// Options for script execution.
8///
9/// Controls optional behavior such as real OP_CHECKSIG verification.
10#[derive(Debug, Clone, Default)]
11pub struct ExecuteOpts {
12    /// The sighash digest for OP_CHECKSIG verification.
13    ///
14    /// When `None`, OP_CHECKSIG always pushes true (stub mode).
15    /// When `Some` and the `secp256k1` feature is enabled,
16    /// real ECDSA signature verification is performed.
17    pub sighash: Option<[u8; 32]>,
18}
19
20/// Executes a sequence of tokens on a fresh stack.
21///
22/// Returns `Ok(true)` if the script succeeds (top stack element is truthy).
23/// Returns `Ok(false)` if the stack is empty or the top element is falsy.
24/// Returns `Err(ScriptError)` if any operation fails during execution.
25///
26/// OP_CHECKSIG uses stub mode (always succeeds). For real signature
27/// verification, use [`execute_with_opts`] with a sighash and the
28/// `secp256k1` feature enabled.
29pub fn execute(tokens: &[Token]) -> Result<bool, ScriptError> {
30    execute_with_opts(tokens, &ExecuteOpts::default())
31}
32
33/// Executes a sequence of tokens with configuration options.
34///
35/// See [`execute`] for return value semantics. The `opts` parameter
36/// controls OP_CHECKSIG behavior via [`ExecuteOpts::sighash`].
37pub fn execute_with_opts(tokens: &[Token], opts: &ExecuteOpts) -> Result<bool, ScriptError> {
38    let mut stack = Stack::new();
39    execute_on_stack(tokens, &mut stack, opts)?;
40
41    if stack.is_empty() {
42        return Ok(false);
43    }
44    let top = stack.pop()?;
45    Ok(is_true(&top))
46}
47
48/// Executes tokens on an existing stack.
49///
50/// Used internally by `script.rs` for two-phase P2PKH execution where
51/// the scriptSig runs first, then the scriptPubKey runs on the same stack.
52pub(crate) fn execute_on_stack(
53    tokens: &[Token],
54    stack: &mut Stack,
55    opts: &ExecuteOpts,
56) -> Result<(), ScriptError> {
57    let mut exec_stack: Vec<bool> = Vec::new();
58
59    for token in tokens {
60        let executing = is_executing(&exec_stack);
61
62        match token {
63            // ── Conditional flow control (always processed) ──────────
64            Token::Op(Opcode::OpIf) => {
65                if executing {
66                    let val = stack.pop()?;
67                    exec_stack.push(is_true(&val));
68                } else {
69                    exec_stack.push(false);
70                }
71            }
72            Token::Op(Opcode::OpNotIf) => {
73                if executing {
74                    let val = stack.pop()?;
75                    exec_stack.push(!is_true(&val));
76                } else {
77                    exec_stack.push(false);
78                }
79            }
80            Token::Op(Opcode::OpElse) => {
81                let top = exec_stack
82                    .last_mut()
83                    .ok_or(ScriptError::UnbalancedConditional)?;
84                *top = !*top;
85            }
86            Token::Op(Opcode::OpEndIf) => {
87                if exec_stack.pop().is_none() {
88                    return Err(ScriptError::UnbalancedConditional);
89                }
90            }
91
92            // ── Skip everything else when not executing ──────────────
93            _ if !executing => continue,
94
95            // ── PushData ─────────────────────────────────────────────
96            Token::PushData(data) => {
97                stack.push(data.clone());
98            }
99
100            // ── Constants ────────────────────────────────────────────
101            Token::Op(Opcode::Op0) => stack.push(vec![]),
102            Token::Op(Opcode::Op1Negate) => stack.push(vec![0x81]),
103            Token::Op(Opcode::Op1) => stack.push(vec![1]),
104            Token::Op(Opcode::Op2) => stack.push(vec![2]),
105            Token::Op(Opcode::Op3) => stack.push(vec![3]),
106            Token::Op(Opcode::Op4) => stack.push(vec![4]),
107            Token::Op(Opcode::Op5) => stack.push(vec![5]),
108            Token::Op(Opcode::Op6) => stack.push(vec![6]),
109            Token::Op(Opcode::Op7) => stack.push(vec![7]),
110            Token::Op(Opcode::Op8) => stack.push(vec![8]),
111            Token::Op(Opcode::Op9) => stack.push(vec![9]),
112            Token::Op(Opcode::Op10) => stack.push(vec![10]),
113            Token::Op(Opcode::Op11) => stack.push(vec![11]),
114            Token::Op(Opcode::Op12) => stack.push(vec![12]),
115            Token::Op(Opcode::Op13) => stack.push(vec![13]),
116            Token::Op(Opcode::Op14) => stack.push(vec![14]),
117            Token::Op(Opcode::Op15) => stack.push(vec![15]),
118            Token::Op(Opcode::Op16) => stack.push(vec![16]),
119
120            // ── Flow control ─────────────────────────────────────────
121            Token::Op(Opcode::OpNop) => {}
122            Token::Op(Opcode::OpVerify) => {
123                let val = stack.pop()?;
124                if !is_true(&val) {
125                    return Err(ScriptError::VerifyFailed);
126                }
127            }
128            Token::Op(Opcode::OpReturn) => {
129                return Err(ScriptError::OpReturnEncountered);
130            }
131
132            // ── Stack manipulation ───────────────────────────────────
133            Token::Op(Opcode::OpDup) => {
134                let top = stack.peek()?.to_vec();
135                stack.push(top);
136            }
137            Token::Op(Opcode::OpDrop) => {
138                stack.pop()?;
139            }
140            Token::Op(Opcode::Op2Dup) => {
141                let b = stack.pop()?;
142                let a = stack.pop()?;
143                stack.push(a.clone());
144                stack.push(b.clone());
145                stack.push(a);
146                stack.push(b);
147            }
148            Token::Op(Opcode::Op2Drop) => {
149                stack.pop()?;
150                stack.pop()?;
151            }
152            Token::Op(Opcode::OpNip) => {
153                if stack.len() < 2 {
154                    return Err(ScriptError::StackUnderflow);
155                }
156                stack.remove(stack.len() - 2)?;
157            }
158            Token::Op(Opcode::OpOver) => {
159                if stack.len() < 2 {
160                    return Err(ScriptError::StackUnderflow);
161                }
162                let second = stack.pop()?;
163                let first = stack.peek()?.to_vec();
164                stack.push(second);
165                stack.push(first);
166            }
167            Token::Op(Opcode::OpSwap) => {
168                let b = stack.pop()?;
169                let a = stack.pop()?;
170                stack.push(b);
171                stack.push(a);
172            }
173            Token::Op(Opcode::OpTuck) => {
174                let b = stack.pop()?;
175                let a = stack.pop()?;
176                stack.push(b.clone());
177                stack.push(a);
178                stack.push(b);
179            }
180            Token::Op(Opcode::OpDepth) => {
181                let depth = stack.len();
182                stack.push(encode_num(depth as i64));
183            }
184            Token::Op(Opcode::OpSize) => {
185                let top = stack.peek()?;
186                let size = top.len();
187                stack.push(encode_num(size as i64));
188            }
189
190            // ── Comparison ───────────────────────────────────────────
191            Token::Op(Opcode::OpEqual) => {
192                let b = stack.pop()?;
193                let a = stack.pop()?;
194                stack.push_bool(a == b);
195            }
196            Token::Op(Opcode::OpEqualVerify) => {
197                let b = stack.pop()?;
198                let a = stack.pop()?;
199                if a != b {
200                    return Err(ScriptError::VerifyFailed);
201                }
202            }
203
204            // ── Logic ────────────────────────────────────────────────
205            Token::Op(Opcode::OpNot) => {
206                let val = stack.pop()?;
207                // OP_NOT: 0 -> 1, 1 -> 0, anything else -> 0
208                if val.is_empty() || val == [0x00] {
209                    stack.push(vec![0x01]);
210                } else {
211                    stack.push(vec![]);
212                }
213            }
214
215            // ── Crypto ───────────────────────────────────────────────
216            Token::Op(Opcode::OpRipemd160) => {
217                let data = stack.pop()?;
218                stack.push(hash::ripemd160(&data).to_vec());
219            }
220            Token::Op(Opcode::OpSha256) => {
221                let data = stack.pop()?;
222                stack.push(hash::sha256(&data).to_vec());
223            }
224            Token::Op(Opcode::OpHash160) => {
225                let data = stack.pop()?;
226                stack.push(hash::hash160(&data).to_vec());
227            }
228            Token::Op(Opcode::OpHash256) => {
229                let data = stack.pop()?;
230                stack.push(hash::hash256(&data).to_vec());
231            }
232            Token::Op(Opcode::OpCheckSig) => {
233                checksig(stack, opts)?;
234            }
235            Token::Op(Opcode::OpCheckSigVerify) => {
236                checksig(stack, opts)?;
237                let val = stack.pop()?;
238                if !is_true(&val) {
239                    return Err(ScriptError::VerifyFailed);
240                }
241            }
242        }
243    }
244
245    if !exec_stack.is_empty() {
246        return Err(ScriptError::UnbalancedConditional);
247    }
248
249    Ok(())
250}
251
252// ── Helpers ──────────────────────────────────────────────────────────────
253
254/// Returns `true` if the execution stack indicates we are in an executing branch.
255fn is_executing(exec_stack: &[bool]) -> bool {
256    exec_stack.iter().all(|&v| v)
257}
258
259/// Encodes a non-negative integer as a minimal Bitcoin Script number.
260fn encode_num(n: i64) -> Vec<u8> {
261    if n == 0 {
262        return vec![];
263    }
264
265    let negative = n < 0;
266    let mut abs = if negative { (-n) as u64 } else { n as u64 };
267    let mut result = Vec::new();
268
269    while abs > 0 {
270        result.push((abs & 0xff) as u8);
271        abs >>= 8;
272    }
273
274    // If the most significant byte has bit 0x80 set, we need an extra byte
275    // for the sign bit.
276    if result.last().map_or(false, |&b| b & 0x80 != 0) {
277        result.push(if negative { 0x80 } else { 0x00 });
278    } else if negative {
279        let len = result.len();
280        result[len - 1] |= 0x80;
281    }
282
283    result
284}
285
286/// OP_CHECKSIG implementation.
287///
288/// Default: stub mode (always pushes true).
289/// With `secp256k1` feature + sighash: real ECDSA verification.
290fn checksig(stack: &mut Stack, opts: &ExecuteOpts) -> Result<(), ScriptError> {
291    let pubkey = stack.pop()?;
292    let sig = stack.pop()?;
293
294    #[cfg(feature = "secp256k1")]
295    {
296        if let Some(sighash) = opts.sighash {
297            let result = verify_ecdsa(&sig, &pubkey, &sighash);
298            stack.push_bool(result);
299            return Ok(());
300        }
301    }
302
303    // Stub mode: suppress unused warning when feature is off
304    let _ = (&pubkey, &sig, &opts);
305    stack.push(vec![0x01]);
306    Ok(())
307}
308
309/// Real ECDSA signature verification using secp256k1.
310#[cfg(feature = "secp256k1")]
311fn verify_ecdsa(sig_bytes: &[u8], pubkey_bytes: &[u8], sighash: &[u8; 32]) -> bool {
312    use secp256k1::{ecdsa::Signature, Message, PublicKey, Secp256k1};
313
314    // Signature must have at least 1 byte (the hash type byte)
315    if sig_bytes.is_empty() {
316        return false;
317    }
318
319    // Last byte is the hash type. We only support SIGHASH_ALL (0x01).
320    let hash_type = sig_bytes[sig_bytes.len() - 1];
321    if hash_type != 0x01 {
322        // Unsupported hash type — fall back to false
323        return false;
324    }
325
326    let der_sig = &sig_bytes[..sig_bytes.len() - 1];
327
328    let secp = Secp256k1::verification_only();
329
330    let signature = match Signature::from_der(der_sig) {
331        Ok(s) => s,
332        Err(_) => return false,
333    };
334
335    let public_key = match PublicKey::from_slice(pubkey_bytes) {
336        Ok(k) => k,
337        Err(_) => return false,
338    };
339
340    let message = match Message::from_digest(*sighash) {
341        msg => msg,
342    };
343
344    secp.verify_ecdsa(&message, &signature, &public_key).is_ok()
345}
346
347#[cfg(test)]
348mod tests {
349    use super::*;
350    use crate::opcode::Opcode;
351    use crate::token::Token;
352
353    // Helper to build Token::Op
354    fn op(o: Opcode) -> Token {
355        Token::Op(o)
356    }
357
358    fn push(data: &[u8]) -> Token {
359        Token::PushData(data.to_vec())
360    }
361
362    // ── Basic execution ──────────────────────────────────────────────
363
364    #[test]
365    fn empty_script_returns_false() {
366        assert_eq!(execute(&[]).unwrap(), false);
367    }
368
369    #[test]
370    fn op0_is_false() {
371        assert_eq!(execute(&[op(Opcode::Op0)]).unwrap(), false);
372    }
373
374    #[test]
375    fn op1_is_true() {
376        assert_eq!(execute(&[op(Opcode::Op1)]).unwrap(), true);
377    }
378
379    #[test]
380    fn push_data_true() {
381        assert_eq!(execute(&[push(&[0x42])]).unwrap(), true);
382    }
383
384    #[test]
385    fn push_data_empty_is_false() {
386        assert_eq!(execute(&[push(&[])]).unwrap(), false);
387    }
388
389    // ── Constants ────────────────────────────────────────────────────
390
391    #[test]
392    fn op1negate_pushes_0x81() {
393        let mut stack = Stack::new();
394        execute_on_stack(
395            &[op(Opcode::Op1Negate)],
396            &mut stack,
397            &ExecuteOpts::default(),
398        )
399        .unwrap();
400        assert_eq!(stack.pop().unwrap(), vec![0x81]);
401    }
402
403    #[test]
404    fn op_n_values() {
405        for n in 1u8..=16 {
406            let opcode = Opcode::from_byte(0x50 + n).unwrap();
407            let mut stack = Stack::new();
408            execute_on_stack(&[op(opcode)], &mut stack, &ExecuteOpts::default()).unwrap();
409            assert_eq!(stack.pop().unwrap(), vec![n]);
410        }
411    }
412
413    // ── Flow control ─────────────────────────────────────────────────
414
415    #[test]
416    fn op_nop() {
417        let tokens = [op(Opcode::Op1), op(Opcode::OpNop)];
418        assert_eq!(execute(&tokens).unwrap(), true);
419    }
420
421    #[test]
422    fn op_verify_true() {
423        let tokens = [op(Opcode::Op1), op(Opcode::OpVerify), op(Opcode::Op1)];
424        assert_eq!(execute(&tokens).unwrap(), true);
425    }
426
427    #[test]
428    fn op_verify_false() {
429        let tokens = [op(Opcode::Op0), op(Opcode::OpVerify)];
430        let err = execute(&tokens).unwrap_err();
431        assert!(matches!(err, ScriptError::VerifyFailed));
432    }
433
434    #[test]
435    fn op_return_error() {
436        let tokens = [op(Opcode::Op1), op(Opcode::OpReturn)];
437        let err = execute(&tokens).unwrap_err();
438        assert!(matches!(err, ScriptError::OpReturnEncountered));
439    }
440
441    // ── Conditionals ─────────────────────────────────────────────────
442
443    #[test]
444    fn if_true_branch() {
445        // OP_1 OP_IF OP_2 OP_ENDIF
446        let tokens = [
447            op(Opcode::Op1),
448            op(Opcode::OpIf),
449            op(Opcode::Op2),
450            op(Opcode::OpEndIf),
451        ];
452        let mut stack = Stack::new();
453        execute_on_stack(&tokens, &mut stack, &ExecuteOpts::default()).unwrap();
454        assert_eq!(stack.pop().unwrap(), vec![2]);
455    }
456
457    #[test]
458    fn if_false_branch() {
459        // OP_0 OP_IF OP_2 OP_ENDIF OP_3
460        let tokens = [
461            op(Opcode::Op0),
462            op(Opcode::OpIf),
463            op(Opcode::Op2),
464            op(Opcode::OpEndIf),
465            op(Opcode::Op3),
466        ];
467        let mut stack = Stack::new();
468        execute_on_stack(&tokens, &mut stack, &ExecuteOpts::default()).unwrap();
469        // OP_2 was skipped, only OP_3 remains
470        assert_eq!(stack.pop().unwrap(), vec![3]);
471        assert!(stack.is_empty());
472    }
473
474    #[test]
475    fn if_else_true() {
476        // OP_1 OP_IF OP_2 OP_ELSE OP_3 OP_ENDIF
477        let tokens = [
478            op(Opcode::Op1),
479            op(Opcode::OpIf),
480            op(Opcode::Op2),
481            op(Opcode::OpElse),
482            op(Opcode::Op3),
483            op(Opcode::OpEndIf),
484        ];
485        let mut stack = Stack::new();
486        execute_on_stack(&tokens, &mut stack, &ExecuteOpts::default()).unwrap();
487        assert_eq!(stack.pop().unwrap(), vec![2]);
488        assert!(stack.is_empty());
489    }
490
491    #[test]
492    fn if_else_false() {
493        // OP_0 OP_IF OP_2 OP_ELSE OP_3 OP_ENDIF
494        let tokens = [
495            op(Opcode::Op0),
496            op(Opcode::OpIf),
497            op(Opcode::Op2),
498            op(Opcode::OpElse),
499            op(Opcode::Op3),
500            op(Opcode::OpEndIf),
501        ];
502        let mut stack = Stack::new();
503        execute_on_stack(&tokens, &mut stack, &ExecuteOpts::default()).unwrap();
504        assert_eq!(stack.pop().unwrap(), vec![3]);
505        assert!(stack.is_empty());
506    }
507
508    #[test]
509    fn notif_true_skips() {
510        // OP_1 OP_NOTIF OP_2 OP_ENDIF OP_3
511        let tokens = [
512            op(Opcode::Op1),
513            op(Opcode::OpNotIf),
514            op(Opcode::Op2),
515            op(Opcode::OpEndIf),
516            op(Opcode::Op3),
517        ];
518        let mut stack = Stack::new();
519        execute_on_stack(&tokens, &mut stack, &ExecuteOpts::default()).unwrap();
520        assert_eq!(stack.pop().unwrap(), vec![3]);
521        assert!(stack.is_empty());
522    }
523
524    #[test]
525    fn unbalanced_if() {
526        let tokens = [op(Opcode::Op1), op(Opcode::OpIf)];
527        let err = execute(&tokens).unwrap_err();
528        assert!(matches!(err, ScriptError::UnbalancedConditional));
529    }
530
531    #[test]
532    fn unbalanced_else() {
533        let tokens = [op(Opcode::OpElse)];
534        let err = execute(&tokens).unwrap_err();
535        assert!(matches!(err, ScriptError::UnbalancedConditional));
536    }
537
538    #[test]
539    fn unbalanced_endif() {
540        let tokens = [op(Opcode::OpEndIf)];
541        let err = execute(&tokens).unwrap_err();
542        assert!(matches!(err, ScriptError::UnbalancedConditional));
543    }
544
545    // ── Stack manipulation ───────────────────────────────────────────
546
547    #[test]
548    fn op_dup() {
549        let tokens = [push(&[0xaa]), op(Opcode::OpDup)];
550        let mut stack = Stack::new();
551        execute_on_stack(&tokens, &mut stack, &ExecuteOpts::default()).unwrap();
552        assert_eq!(stack.pop().unwrap(), vec![0xaa]);
553        assert_eq!(stack.pop().unwrap(), vec![0xaa]);
554    }
555
556    #[test]
557    fn op_drop() {
558        let tokens = [op(Opcode::Op1), op(Opcode::Op2), op(Opcode::OpDrop)];
559        let mut stack = Stack::new();
560        execute_on_stack(&tokens, &mut stack, &ExecuteOpts::default()).unwrap();
561        assert_eq!(stack.pop().unwrap(), vec![1]);
562    }
563
564    #[test]
565    fn op_2dup() {
566        let tokens = [op(Opcode::Op1), op(Opcode::Op2), op(Opcode::Op2Dup)];
567        let mut stack = Stack::new();
568        execute_on_stack(&tokens, &mut stack, &ExecuteOpts::default()).unwrap();
569        assert_eq!(stack.len(), 4);
570        assert_eq!(stack.pop().unwrap(), vec![2]);
571        assert_eq!(stack.pop().unwrap(), vec![1]);
572        assert_eq!(stack.pop().unwrap(), vec![2]);
573        assert_eq!(stack.pop().unwrap(), vec![1]);
574    }
575
576    #[test]
577    fn op_2drop() {
578        let tokens = [
579            op(Opcode::Op1),
580            op(Opcode::Op2),
581            op(Opcode::Op3),
582            op(Opcode::Op2Drop),
583        ];
584        let mut stack = Stack::new();
585        execute_on_stack(&tokens, &mut stack, &ExecuteOpts::default()).unwrap();
586        assert_eq!(stack.pop().unwrap(), vec![1]);
587    }
588
589    #[test]
590    fn op_nip() {
591        let tokens = [op(Opcode::Op1), op(Opcode::Op2), op(Opcode::OpNip)];
592        let mut stack = Stack::new();
593        execute_on_stack(&tokens, &mut stack, &ExecuteOpts::default()).unwrap();
594        assert_eq!(stack.len(), 1);
595        assert_eq!(stack.pop().unwrap(), vec![2]);
596    }
597
598    #[test]
599    fn op_over() {
600        let tokens = [op(Opcode::Op1), op(Opcode::Op2), op(Opcode::OpOver)];
601        let mut stack = Stack::new();
602        execute_on_stack(&tokens, &mut stack, &ExecuteOpts::default()).unwrap();
603        assert_eq!(stack.len(), 3);
604        assert_eq!(stack.pop().unwrap(), vec![1]);
605        assert_eq!(stack.pop().unwrap(), vec![2]);
606        assert_eq!(stack.pop().unwrap(), vec![1]);
607    }
608
609    #[test]
610    fn op_swap() {
611        let tokens = [op(Opcode::Op1), op(Opcode::Op2), op(Opcode::OpSwap)];
612        let mut stack = Stack::new();
613        execute_on_stack(&tokens, &mut stack, &ExecuteOpts::default()).unwrap();
614        assert_eq!(stack.pop().unwrap(), vec![1]);
615        assert_eq!(stack.pop().unwrap(), vec![2]);
616    }
617
618    #[test]
619    fn op_tuck() {
620        // [1, 2] -> [2, 1, 2]
621        let tokens = [op(Opcode::Op1), op(Opcode::Op2), op(Opcode::OpTuck)];
622        let mut stack = Stack::new();
623        execute_on_stack(&tokens, &mut stack, &ExecuteOpts::default()).unwrap();
624        assert_eq!(stack.len(), 3);
625        assert_eq!(stack.pop().unwrap(), vec![2]);
626        assert_eq!(stack.pop().unwrap(), vec![1]);
627        assert_eq!(stack.pop().unwrap(), vec![2]);
628    }
629
630    #[test]
631    fn op_depth() {
632        let tokens = [op(Opcode::Op1), op(Opcode::Op2), op(Opcode::OpDepth)];
633        let mut stack = Stack::new();
634        execute_on_stack(&tokens, &mut stack, &ExecuteOpts::default()).unwrap();
635        assert_eq!(stack.pop().unwrap(), vec![2]); // depth was 2
636    }
637
638    #[test]
639    fn op_depth_empty() {
640        let tokens = [op(Opcode::OpDepth)];
641        let mut stack = Stack::new();
642        execute_on_stack(&tokens, &mut stack, &ExecuteOpts::default()).unwrap();
643        assert_eq!(stack.pop().unwrap(), vec![]); // depth 0 = empty vec
644    }
645
646    #[test]
647    fn op_size() {
648        let tokens = [push(&[0xaa, 0xbb, 0xcc]), op(Opcode::OpSize)];
649        let mut stack = Stack::new();
650        execute_on_stack(&tokens, &mut stack, &ExecuteOpts::default()).unwrap();
651        assert_eq!(stack.pop().unwrap(), vec![3]); // size = 3
652        assert_eq!(stack.pop().unwrap(), vec![0xaa, 0xbb, 0xcc]); // original remains
653    }
654
655    // ── Comparison ───────────────────────────────────────────────────
656
657    #[test]
658    fn op_equal_true() {
659        let tokens = [
660            push(&[0x01, 0x02]),
661            push(&[0x01, 0x02]),
662            op(Opcode::OpEqual),
663        ];
664        assert_eq!(execute(&tokens).unwrap(), true);
665    }
666
667    #[test]
668    fn op_equal_false() {
669        let tokens = [push(&[0x01]), push(&[0x02]), op(Opcode::OpEqual)];
670        assert_eq!(execute(&tokens).unwrap(), false);
671    }
672
673    #[test]
674    fn op_equalverify_pass() {
675        let tokens = [
676            push(&[0xaa]),
677            push(&[0xaa]),
678            op(Opcode::OpEqualVerify),
679            op(Opcode::Op1),
680        ];
681        assert_eq!(execute(&tokens).unwrap(), true);
682    }
683
684    #[test]
685    fn op_equalverify_fail() {
686        let tokens = [push(&[0xaa]), push(&[0xbb]), op(Opcode::OpEqualVerify)];
687        let err = execute(&tokens).unwrap_err();
688        assert!(matches!(err, ScriptError::VerifyFailed));
689    }
690
691    // ── Logic ────────────────────────────────────────────────────────
692
693    #[test]
694    fn op_not_zero_becomes_one() {
695        let tokens = [op(Opcode::Op0), op(Opcode::OpNot)];
696        assert_eq!(execute(&tokens).unwrap(), true);
697    }
698
699    #[test]
700    fn op_not_one_becomes_zero() {
701        let tokens = [op(Opcode::Op1), op(Opcode::OpNot)];
702        assert_eq!(execute(&tokens).unwrap(), false);
703    }
704
705    #[test]
706    fn op_not_other_becomes_zero() {
707        let tokens = [op(Opcode::Op2), op(Opcode::OpNot)];
708        assert_eq!(execute(&tokens).unwrap(), false);
709    }
710
711    // ── Crypto ───────────────────────────────────────────────────────
712
713    #[test]
714    fn op_sha256() {
715        let tokens = [push(b""), op(Opcode::OpSha256)];
716        let mut stack = Stack::new();
717        execute_on_stack(&tokens, &mut stack, &ExecuteOpts::default()).unwrap();
718        let result = stack.pop().unwrap();
719        assert_eq!(result.len(), 32);
720        assert_eq!(result, hash::sha256(b"").to_vec());
721    }
722
723    #[test]
724    fn op_hash160() {
725        let tokens = [push(b"test"), op(Opcode::OpHash160)];
726        let mut stack = Stack::new();
727        execute_on_stack(&tokens, &mut stack, &ExecuteOpts::default()).unwrap();
728        let result = stack.pop().unwrap();
729        assert_eq!(result.len(), 20);
730        assert_eq!(result, hash::hash160(b"test").to_vec());
731    }
732
733    // ── OP_CHECKSIG stub ─────────────────────────────────────────────
734
735    #[test]
736    fn checksig_stub_always_true() {
737        let tokens = [push(&[0x00]), push(&[0x00]), op(Opcode::OpCheckSig)];
738        assert_eq!(execute(&tokens).unwrap(), true);
739    }
740
741    #[test]
742    fn checksigverify_stub() {
743        let tokens = [
744            push(&[0x00]),
745            push(&[0x00]),
746            op(Opcode::OpCheckSigVerify),
747            op(Opcode::Op1),
748        ];
749        assert_eq!(execute(&tokens).unwrap(), true);
750    }
751
752    // ── encode_num ───────────────────────────────────────────────────
753
754    #[test]
755    fn encode_num_zero() {
756        assert_eq!(encode_num(0), vec![]);
757    }
758
759    #[test]
760    fn encode_num_positive() {
761        assert_eq!(encode_num(1), vec![0x01]);
762        assert_eq!(encode_num(127), vec![0x7f]);
763        assert_eq!(encode_num(128), vec![0x80, 0x00]); // needs sign byte
764        assert_eq!(encode_num(255), vec![0xff, 0x00]);
765        assert_eq!(encode_num(256), vec![0x00, 0x01]);
766    }
767
768    #[test]
769    fn encode_num_negative() {
770        assert_eq!(encode_num(-1), vec![0x81]);
771        assert_eq!(encode_num(-127), vec![0xff]);
772        assert_eq!(encode_num(-128), vec![0x80, 0x80]);
773    }
774
775    // ── Stack underflow ──────────────────────────────────────────────
776
777    #[test]
778    fn dup_empty_stack() {
779        let err = execute(&[op(Opcode::OpDup)]).unwrap_err();
780        assert!(matches!(err, ScriptError::StackUnderflow));
781    }
782
783    #[test]
784    fn equal_needs_two() {
785        let err = execute(&[op(Opcode::Op1), op(Opcode::OpEqual)]).unwrap_err();
786        assert!(matches!(err, ScriptError::StackUnderflow));
787    }
788}