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 if conditional_depth > 0 => conditional_depth -= 1,
247            OP_RETURN if conditional_depth == 0 => {
248                // OP_RETURN outside conditionals: consume remaining data
249                if i + 1 < scr.len() {
250                    parsed_op.data = scr[i + 1..].to_vec();
251                }
252                parsed_ops.push(parsed_op);
253                return Ok(parsed_ops);
254            }
255            _ => {}
256        }
257
258        // Extract data for this opcode
259        match instruction {
260            OP_PUSHDATA1 => {
261                if i + 1 >= scr.len() {
262                    return Err(InterpreterError::new(
263                        InterpreterErrorCode::MalformedPush,
264                        "script truncated".to_string(),
265                    ));
266                }
267                let data_len = scr[i + 1] as usize;
268                if i + 2 + data_len > scr.len() {
269                    return Err(InterpreterError::new(
270                        InterpreterErrorCode::MalformedPush,
271                        "push data exceeds script length".to_string(),
272                    ));
273                }
274                parsed_op.data = scr[i + 2..i + 2 + data_len].to_vec();
275                i += 2 + data_len;
276            }
277            OP_PUSHDATA2 => {
278                if i + 2 >= scr.len() {
279                    return Err(InterpreterError::new(
280                        InterpreterErrorCode::MalformedPush,
281                        "script truncated".to_string(),
282                    ));
283                }
284                let data_len = u16::from_le_bytes([scr[i + 1], scr[i + 2]]) as usize;
285                if i + 3 + data_len > scr.len() {
286                    return Err(InterpreterError::new(
287                        InterpreterErrorCode::MalformedPush,
288                        "push data exceeds script length".to_string(),
289                    ));
290                }
291                parsed_op.data = scr[i + 3..i + 3 + data_len].to_vec();
292                i += 3 + data_len;
293            }
294            OP_PUSHDATA4 => {
295                if i + 4 >= scr.len() {
296                    return Err(InterpreterError::new(
297                        InterpreterErrorCode::MalformedPush,
298                        "script truncated".to_string(),
299                    ));
300                }
301                let data_len =
302                    u32::from_le_bytes([scr[i + 1], scr[i + 2], scr[i + 3], scr[i + 4]]) as usize;
303                if i + 5 + data_len > scr.len() {
304                    return Err(InterpreterError::new(
305                        InterpreterErrorCode::MalformedPush,
306                        "push data exceeds script length".to_string(),
307                    ));
308                }
309                parsed_op.data = scr[i + 5..i + 5 + data_len].to_vec();
310                i += 5 + data_len;
311            }
312            op if op >= OP_DATA_1 && op <= OP_DATA_75 => {
313                let data_len = op as usize;
314                if i + 1 + data_len > scr.len() {
315                    return Err(InterpreterError::new(
316                        InterpreterErrorCode::MalformedPush,
317                        "script truncated".to_string(),
318                    ));
319                }
320                parsed_op.data = scr[i + 1..i + 1 + data_len].to_vec();
321                i += 1 + data_len;
322            }
323            _ => {
324                // Single-byte opcode
325                i += 1;
326            }
327        }
328
329        parsed_ops.push(parsed_op);
330    }
331
332    Ok(parsed_ops)
333}