Skip to main content

bsv_script/interpreter/
parsed_opcode.rs

1//! Parsed opcode representation and script parser.
2
3use super::error::{InterpreterError, InterpreterErrorCode};
4use crate::opcodes::*;
5use crate::Script;
6
7/// A parsed opcode with its data payload.
8#[derive(Debug, Clone)]
9pub struct ParsedOpcode {
10    /// The opcode byte value.
11    pub opcode: u8,
12    /// The data payload associated with push opcodes (empty for non-push opcodes).
13    pub data: Vec<u8>,
14}
15
16impl ParsedOpcode {
17    /// Return the human-readable name of this opcode.
18    pub fn name(&self) -> &'static str {
19        crate::opcodes::opcode_to_string(self.opcode)
20    }
21
22    /// Return true if this opcode is disabled (OP_2MUL, OP_2DIV).
23    pub fn is_disabled(&self) -> bool {
24        matches!(self.opcode, OP_2MUL | OP_2DIV)
25    }
26
27    /// Return true if this opcode is always illegal (OP_VERIF, OP_VERNOTIF).
28    pub fn always_illegal(&self) -> bool {
29        matches!(self.opcode, OP_VERIF | OP_VERNOTIF)
30    }
31
32    /// Return true if this opcode is a conditional flow control opcode.
33    pub fn is_conditional(&self) -> bool {
34        matches!(
35            self.opcode,
36            OP_IF | OP_NOTIF | OP_ELSE | OP_ENDIF | OP_VERIF | OP_VERNOTIF
37        )
38    }
39
40    /// Return true if this opcode requires a transaction context to execute.
41    pub fn requires_tx(&self) -> bool {
42        matches!(
43            self.opcode,
44            OP_CHECKSIG
45                | OP_CHECKSIGVERIFY
46                | OP_CHECKMULTISIG
47                | OP_CHECKMULTISIGVERIFY
48                | OP_CHECKSEQUENCEVERIFY
49        )
50    }
51
52    /// Check that push uses minimal encoding.
53    pub fn enforce_minimum_data_push(&self) -> Result<(), InterpreterError> {
54        let data_len = self.data.len();
55        if data_len == 0 && self.opcode != OP_0 {
56            return Err(InterpreterError::new(
57                InterpreterErrorCode::MinimalData,
58                format!(
59                    "zero length data push is encoded with opcode {} instead of OP_0",
60                    self.name()
61                ),
62            ));
63        }
64        if data_len == 1
65            && (1..=16).contains(&self.data[0])
66            && self.opcode != OP_1 + self.data[0] - 1
67        {
68            return Err(InterpreterError::new(
69                InterpreterErrorCode::MinimalData,
70                format!(
71                    "data push of the value {} encoded with opcode {} instead of OP_{}",
72                    self.data[0],
73                    self.name(),
74                    self.data[0]
75                ),
76            ));
77        }
78        if data_len == 1 && self.data[0] == 0x81 && self.opcode != OP_1NEGATE {
79            return Err(InterpreterError::new(
80                InterpreterErrorCode::MinimalData,
81                format!(
82                    "data push of the value -1 encoded with opcode {} instead of OP_1NEGATE",
83                    self.name()
84                ),
85            ));
86        }
87        if data_len <= 75 {
88            if self.opcode as usize != data_len {
89                return Err(InterpreterError::new(
90                    InterpreterErrorCode::MinimalData,
91                    format!(
92                        "data push of {} bytes encoded with opcode {} instead of OP_DATA_{}",
93                        data_len,
94                        self.name(),
95                        data_len
96                    ),
97                ));
98            }
99        } else if data_len <= 255 {
100            if self.opcode != OP_PUSHDATA1 {
101                return Err(InterpreterError::new(
102                    InterpreterErrorCode::MinimalData,
103                    format!(
104                        "data push of {} bytes encoded with opcode {} instead of OP_PUSHDATA1",
105                        data_len,
106                        self.name()
107                    ),
108                ));
109            }
110        } else if data_len <= 65535 && self.opcode != OP_PUSHDATA2 {
111            return Err(InterpreterError::new(
112                InterpreterErrorCode::MinimalData,
113                format!(
114                    "data push of {} bytes encoded with opcode {} instead of OP_PUSHDATA2",
115                    data_len,
116                    self.name()
117                ),
118            ));
119        }
120        Ok(())
121    }
122
123    /// Check if this is a canonical push (matches the smallest push opcode).
124    pub fn canonical_push(&self) -> bool {
125        let opcode = self.opcode;
126        let data = &self.data;
127        let data_len = data.len();
128        if opcode > OP_16 {
129            return true;
130        }
131        if opcode < OP_PUSHDATA1 && opcode > OP_0 && data_len == 1 && data[0] <= 16 {
132            return false;
133        }
134        if opcode == OP_PUSHDATA1 && data_len < OP_PUSHDATA1 as usize {
135            return false;
136        }
137        if opcode == OP_PUSHDATA2 && data_len <= 0xff {
138            return false;
139        }
140        if opcode == OP_PUSHDATA4 && data_len <= 0xffff {
141            return false;
142        }
143        true
144    }
145
146    /// Serialize back to script bytes.
147    pub fn to_bytes(&self) -> Vec<u8> {
148        let mut out = vec![self.opcode];
149        if self.opcode == 0
150            || (self.opcode >= OP_1NEGATE && self.opcode <= OP_16)
151            || self.opcode > OP_PUSHDATA4
152        {
153            // No data for these opcodes (except OP_RETURN which has special handling)
154            if self.opcode == OP_RETURN && !self.data.is_empty() {
155                out.extend_from_slice(&self.data);
156            }
157            return out;
158        }
159        // Push data opcodes
160        match self.opcode {
161            OP_PUSHDATA1 => {
162                out.push(self.data.len() as u8);
163                out.extend_from_slice(&self.data);
164            }
165            OP_PUSHDATA2 => {
166                out.extend_from_slice(&(self.data.len() as u16).to_le_bytes());
167                out.extend_from_slice(&self.data);
168            }
169            OP_PUSHDATA4 => {
170                out.extend_from_slice(&(self.data.len() as u32).to_le_bytes());
171                out.extend_from_slice(&self.data);
172            }
173            _ => {
174                // OP_DATA_1..OP_DATA_75
175                out.extend_from_slice(&self.data);
176            }
177        }
178        out
179    }
180}
181
182/// A parsed script is a sequence of parsed opcodes.
183pub type ParsedScript = Vec<ParsedOpcode>;
184
185/// Check if a parsed script is push-only.
186pub fn is_push_only(script: &ParsedScript) -> bool {
187    script.iter().all(|op| op.opcode <= OP_16)
188}
189
190/// Remove opcodes that push the given data.
191pub fn remove_opcode_by_data(script: &ParsedScript, data: &[u8]) -> ParsedScript {
192    script
193        .iter()
194        .filter(|pop| !pop.canonical_push() || !pop.data.windows(data.len()).any(|w| w == data))
195        .cloned()
196        .collect()
197}
198
199/// Remove all occurrences of a specific opcode.
200pub fn remove_opcode(script: &ParsedScript, opcode: u8) -> ParsedScript {
201    script
202        .iter()
203        .filter(|pop| pop.opcode != opcode)
204        .cloned()
205        .collect()
206}
207
208/// Unparse a ParsedScript back to a Script.
209pub fn unparse(pscript: &ParsedScript) -> Script {
210    let mut bytes = Vec::new();
211    for pop in pscript {
212        bytes.extend_from_slice(&pop.to_bytes());
213    }
214    Script::from_bytes(&bytes)
215}
216
217/// Parse a Script into a ParsedScript.
218///
219/// `error_on_checksig` - if true, returns error for checksig ops (when no tx available)
220pub fn parse_script(
221    script: &Script,
222    error_on_checksig: bool,
223) -> Result<ParsedScript, InterpreterError> {
224    let scr = script.to_bytes();
225    let mut parsed_ops = Vec::new();
226    let mut conditional_depth = 0i32;
227    let mut i = 0;
228
229    while i < scr.len() {
230        let instruction = scr[i];
231        let mut parsed_op = ParsedOpcode {
232            opcode: instruction,
233            data: Vec::new(),
234        };
235
236        if error_on_checksig && parsed_op.requires_tx() {
237            return Err(InterpreterError::new(
238                InterpreterErrorCode::InvalidParams,
239                "tx and previous output must be supplied for checksig".to_string(),
240            ));
241        }
242
243        // Track conditionals and check for OP_RETURN
244        match instruction {
245            OP_IF | OP_NOTIF | OP_VERIF | OP_VERNOTIF => conditional_depth += 1,
246            OP_ENDIF => {
247                if conditional_depth > 0 {
248                    conditional_depth -= 1;
249                }
250            }
251            OP_RETURN if conditional_depth == 0 => {
252                // OP_RETURN outside conditionals: consume remaining data
253                if i + 1 < scr.len() {
254                    parsed_op.data = scr[i + 1..].to_vec();
255                }
256                parsed_ops.push(parsed_op);
257                return Ok(parsed_ops);
258            }
259            _ => {}
260        }
261
262        // Extract data for this opcode
263        match instruction {
264            OP_PUSHDATA1 => {
265                if i + 1 >= scr.len() {
266                    return Err(InterpreterError::new(
267                        InterpreterErrorCode::MalformedPush,
268                        "script truncated".to_string(),
269                    ));
270                }
271                let data_len = scr[i + 1] as usize;
272                if i + 2 + data_len > scr.len() {
273                    return Err(InterpreterError::new(
274                        InterpreterErrorCode::MalformedPush,
275                        "push data exceeds script length".to_string(),
276                    ));
277                }
278                parsed_op.data = scr[i + 2..i + 2 + data_len].to_vec();
279                i += 2 + data_len;
280            }
281            OP_PUSHDATA2 => {
282                if i + 2 >= scr.len() {
283                    return Err(InterpreterError::new(
284                        InterpreterErrorCode::MalformedPush,
285                        "script truncated".to_string(),
286                    ));
287                }
288                let data_len = u16::from_le_bytes([scr[i + 1], scr[i + 2]]) as usize;
289                if i + 3 + data_len > scr.len() {
290                    return Err(InterpreterError::new(
291                        InterpreterErrorCode::MalformedPush,
292                        "push data exceeds script length".to_string(),
293                    ));
294                }
295                parsed_op.data = scr[i + 3..i + 3 + data_len].to_vec();
296                i += 3 + data_len;
297            }
298            OP_PUSHDATA4 => {
299                if i + 4 >= scr.len() {
300                    return Err(InterpreterError::new(
301                        InterpreterErrorCode::MalformedPush,
302                        "script truncated".to_string(),
303                    ));
304                }
305                let data_len =
306                    u32::from_le_bytes([scr[i + 1], scr[i + 2], scr[i + 3], scr[i + 4]]) as usize;
307                if i + 5 + data_len > scr.len() {
308                    return Err(InterpreterError::new(
309                        InterpreterErrorCode::MalformedPush,
310                        "push data exceeds script length".to_string(),
311                    ));
312                }
313                parsed_op.data = scr[i + 5..i + 5 + data_len].to_vec();
314                i += 5 + data_len;
315            }
316            op if op >= OP_DATA_1 && op <= OP_DATA_75 => {
317                let data_len = op as usize;
318                if i + 1 + data_len > scr.len() {
319                    return Err(InterpreterError::new(
320                        InterpreterErrorCode::MalformedPush,
321                        "script truncated".to_string(),
322                    ));
323                }
324                parsed_op.data = scr[i + 1..i + 1 + data_len].to_vec();
325                i += 1 + data_len;
326            }
327            _ => {
328                // Single-byte opcode
329                i += 1;
330            }
331        }
332
333        parsed_ops.push(parsed_op);
334    }
335
336    Ok(parsed_ops)
337}