Skip to main content

mini_bitcoin_script/
opcode.rs

1/// A Bitcoin Script opcode supported by this engine.
2///
3/// This is a fieldless enum that maps 1:1 to protocol-defined byte values.
4/// It derives `Copy` because it carries no heap data.
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum Opcode {
7    // Constants
8    Op0,
9    Op1Negate,
10    Op1,
11    Op2,
12    Op3,
13    Op4,
14    Op5,
15    Op6,
16    Op7,
17    Op8,
18    Op9,
19    Op10,
20    Op11,
21    Op12,
22    Op13,
23    Op14,
24    Op15,
25    Op16,
26
27    // Flow control
28    OpNop,
29    OpIf,
30    OpNotIf,
31    OpElse,
32    OpEndIf,
33    OpVerify,
34    OpReturn,
35
36    // Stack manipulation
37    Op2Drop,
38    Op2Dup,
39    OpDepth,
40    OpDrop,
41    OpDup,
42    OpNip,
43    OpOver,
44    OpSwap,
45    OpTuck,
46
47    // Splice
48    OpSize,
49
50    // Comparison
51    OpEqual,
52    OpEqualVerify,
53
54    // Logic
55    OpNot,
56
57    // Crypto
58    OpRipemd160,
59    OpSha256,
60    OpHash160,
61    OpHash256,
62    OpCheckSig,
63    OpCheckSigVerify,
64}
65
66impl Opcode {
67    /// Convert a byte to an `Opcode`, if it maps to a supported opcode.
68    ///
69    /// Returns `None` for push-data bytes (`0x01`-`0x4e`), reserved opcodes,
70    /// and any unimplemented opcode. These are handled by the tokenizer
71    /// (push-data) or rejected as unsupported.
72    pub fn from_byte(byte: u8) -> Option<Opcode> {
73        match byte {
74            0x00 => Some(Opcode::Op0),
75            0x4f => Some(Opcode::Op1Negate),
76            0x51 => Some(Opcode::Op1),
77            0x52 => Some(Opcode::Op2),
78            0x53 => Some(Opcode::Op3),
79            0x54 => Some(Opcode::Op4),
80            0x55 => Some(Opcode::Op5),
81            0x56 => Some(Opcode::Op6),
82            0x57 => Some(Opcode::Op7),
83            0x58 => Some(Opcode::Op8),
84            0x59 => Some(Opcode::Op9),
85            0x5a => Some(Opcode::Op10),
86            0x5b => Some(Opcode::Op11),
87            0x5c => Some(Opcode::Op12),
88            0x5d => Some(Opcode::Op13),
89            0x5e => Some(Opcode::Op14),
90            0x5f => Some(Opcode::Op15),
91            0x60 => Some(Opcode::Op16),
92            0x61 => Some(Opcode::OpNop),
93            0x63 => Some(Opcode::OpIf),
94            0x64 => Some(Opcode::OpNotIf),
95            0x67 => Some(Opcode::OpElse),
96            0x68 => Some(Opcode::OpEndIf),
97            0x69 => Some(Opcode::OpVerify),
98            0x6a => Some(Opcode::OpReturn),
99            0x6d => Some(Opcode::Op2Drop),
100            0x6e => Some(Opcode::Op2Dup),
101            0x74 => Some(Opcode::OpDepth),
102            0x75 => Some(Opcode::OpDrop),
103            0x76 => Some(Opcode::OpDup),
104            0x77 => Some(Opcode::OpNip),
105            0x78 => Some(Opcode::OpOver),
106            0x7c => Some(Opcode::OpSwap),
107            0x7d => Some(Opcode::OpTuck),
108            0x82 => Some(Opcode::OpSize),
109            0x87 => Some(Opcode::OpEqual),
110            0x88 => Some(Opcode::OpEqualVerify),
111            0x91 => Some(Opcode::OpNot),
112            0xa6 => Some(Opcode::OpRipemd160),
113            0xa8 => Some(Opcode::OpSha256),
114            0xa9 => Some(Opcode::OpHash160),
115            0xaa => Some(Opcode::OpHash256),
116            0xac => Some(Opcode::OpCheckSig),
117            0xad => Some(Opcode::OpCheckSigVerify),
118            _ => None,
119        }
120    }
121
122    /// Convert an `Opcode` back to its canonical byte value.
123    pub fn to_byte(self) -> u8 {
124        match self {
125            Opcode::Op0 => 0x00,
126            Opcode::Op1Negate => 0x4f,
127            Opcode::Op1 => 0x51,
128            Opcode::Op2 => 0x52,
129            Opcode::Op3 => 0x53,
130            Opcode::Op4 => 0x54,
131            Opcode::Op5 => 0x55,
132            Opcode::Op6 => 0x56,
133            Opcode::Op7 => 0x57,
134            Opcode::Op8 => 0x58,
135            Opcode::Op9 => 0x59,
136            Opcode::Op10 => 0x5a,
137            Opcode::Op11 => 0x5b,
138            Opcode::Op12 => 0x5c,
139            Opcode::Op13 => 0x5d,
140            Opcode::Op14 => 0x5e,
141            Opcode::Op15 => 0x5f,
142            Opcode::Op16 => 0x60,
143            Opcode::OpNop => 0x61,
144            Opcode::OpIf => 0x63,
145            Opcode::OpNotIf => 0x64,
146            Opcode::OpElse => 0x67,
147            Opcode::OpEndIf => 0x68,
148            Opcode::OpVerify => 0x69,
149            Opcode::OpReturn => 0x6a,
150            Opcode::Op2Drop => 0x6d,
151            Opcode::Op2Dup => 0x6e,
152            Opcode::OpDepth => 0x74,
153            Opcode::OpDrop => 0x75,
154            Opcode::OpDup => 0x76,
155            Opcode::OpNip => 0x77,
156            Opcode::OpOver => 0x78,
157            Opcode::OpSwap => 0x7c,
158            Opcode::OpTuck => 0x7d,
159            Opcode::OpSize => 0x82,
160            Opcode::OpEqual => 0x87,
161            Opcode::OpEqualVerify => 0x88,
162            Opcode::OpNot => 0x91,
163            Opcode::OpRipemd160 => 0xa6,
164            Opcode::OpSha256 => 0xa8,
165            Opcode::OpHash160 => 0xa9,
166            Opcode::OpHash256 => 0xaa,
167            Opcode::OpCheckSig => 0xac,
168            Opcode::OpCheckSigVerify => 0xad,
169        }
170    }
171}
172
173impl std::fmt::Display for Opcode {
174    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
175        let name = match self {
176            Opcode::Op0 => "OP_0",
177            Opcode::Op1Negate => "OP_1NEGATE",
178            Opcode::Op1 => "OP_1",
179            Opcode::Op2 => "OP_2",
180            Opcode::Op3 => "OP_3",
181            Opcode::Op4 => "OP_4",
182            Opcode::Op5 => "OP_5",
183            Opcode::Op6 => "OP_6",
184            Opcode::Op7 => "OP_7",
185            Opcode::Op8 => "OP_8",
186            Opcode::Op9 => "OP_9",
187            Opcode::Op10 => "OP_10",
188            Opcode::Op11 => "OP_11",
189            Opcode::Op12 => "OP_12",
190            Opcode::Op13 => "OP_13",
191            Opcode::Op14 => "OP_14",
192            Opcode::Op15 => "OP_15",
193            Opcode::Op16 => "OP_16",
194            Opcode::OpNop => "OP_NOP",
195            Opcode::OpIf => "OP_IF",
196            Opcode::OpNotIf => "OP_NOTIF",
197            Opcode::OpElse => "OP_ELSE",
198            Opcode::OpEndIf => "OP_ENDIF",
199            Opcode::OpVerify => "OP_VERIFY",
200            Opcode::OpReturn => "OP_RETURN",
201            Opcode::Op2Drop => "OP_2DROP",
202            Opcode::Op2Dup => "OP_2DUP",
203            Opcode::OpDepth => "OP_DEPTH",
204            Opcode::OpDrop => "OP_DROP",
205            Opcode::OpDup => "OP_DUP",
206            Opcode::OpNip => "OP_NIP",
207            Opcode::OpOver => "OP_OVER",
208            Opcode::OpSwap => "OP_SWAP",
209            Opcode::OpTuck => "OP_TUCK",
210            Opcode::OpSize => "OP_SIZE",
211            Opcode::OpEqual => "OP_EQUAL",
212            Opcode::OpEqualVerify => "OP_EQUALVERIFY",
213            Opcode::OpNot => "OP_NOT",
214            Opcode::OpRipemd160 => "OP_RIPEMD160",
215            Opcode::OpSha256 => "OP_SHA256",
216            Opcode::OpHash160 => "OP_HASH160",
217            Opcode::OpHash256 => "OP_HASH256",
218            Opcode::OpCheckSig => "OP_CHECKSIG",
219            Opcode::OpCheckSigVerify => "OP_CHECKSIGVERIFY",
220        };
221        write!(f, "{name}")
222    }
223}
224
225#[cfg(test)]
226mod tests {
227    use super::*;
228
229    #[test]
230    fn roundtrip_all_opcodes() {
231        let opcodes = [
232            Opcode::Op0,
233            Opcode::Op1Negate,
234            Opcode::Op1,
235            Opcode::Op2,
236            Opcode::Op3,
237            Opcode::Op4,
238            Opcode::Op5,
239            Opcode::Op6,
240            Opcode::Op7,
241            Opcode::Op8,
242            Opcode::Op9,
243            Opcode::Op10,
244            Opcode::Op11,
245            Opcode::Op12,
246            Opcode::Op13,
247            Opcode::Op14,
248            Opcode::Op15,
249            Opcode::Op16,
250            Opcode::OpNop,
251            Opcode::OpIf,
252            Opcode::OpNotIf,
253            Opcode::OpElse,
254            Opcode::OpEndIf,
255            Opcode::OpVerify,
256            Opcode::OpReturn,
257            Opcode::Op2Drop,
258            Opcode::Op2Dup,
259            Opcode::OpDepth,
260            Opcode::OpDrop,
261            Opcode::OpDup,
262            Opcode::OpNip,
263            Opcode::OpOver,
264            Opcode::OpSwap,
265            Opcode::OpTuck,
266            Opcode::OpSize,
267            Opcode::OpEqual,
268            Opcode::OpEqualVerify,
269            Opcode::OpNot,
270            Opcode::OpRipemd160,
271            Opcode::OpSha256,
272            Opcode::OpHash160,
273            Opcode::OpHash256,
274            Opcode::OpCheckSig,
275            Opcode::OpCheckSigVerify,
276        ];
277
278        for opcode in &opcodes {
279            let byte = opcode.to_byte();
280            let recovered = Opcode::from_byte(byte);
281            assert_eq!(recovered, Some(*opcode), "roundtrip failed for {opcode}");
282        }
283    }
284
285    #[test]
286    fn push_data_bytes_return_none() {
287        for byte in 0x01..=0x4bu8 {
288            assert_eq!(
289                Opcode::from_byte(byte),
290                None,
291                "byte 0x{byte:02x} should be None"
292            );
293        }
294        // OP_PUSHDATA1, OP_PUSHDATA2, OP_PUSHDATA4
295        assert_eq!(Opcode::from_byte(0x4c), None);
296        assert_eq!(Opcode::from_byte(0x4d), None);
297        assert_eq!(Opcode::from_byte(0x4e), None);
298    }
299
300    #[test]
301    fn unsupported_bytes_return_none() {
302        assert_eq!(Opcode::from_byte(0x50), None); // OP_RESERVED
303        assert_eq!(Opcode::from_byte(0xb0), None);
304        assert_eq!(Opcode::from_byte(0xff), None);
305    }
306
307    #[test]
308    fn display_formatting() {
309        assert_eq!(format!("{}", Opcode::OpDup), "OP_DUP");
310        assert_eq!(format!("{}", Opcode::OpHash160), "OP_HASH160");
311        assert_eq!(format!("{}", Opcode::Op0), "OP_0");
312        assert_eq!(format!("{}", Opcode::OpCheckSig), "OP_CHECKSIG");
313    }
314}