Skip to main content

tidecoin_primitives/script/
instruction.rs

1// SPDX-License-Identifier: CC0-1.0
2
3use internals::script::{self, PushDataLenLen};
4
5use super::{Error, PushBytes, Script, ScriptBuf};
6use crate::opcodes::{all, Opcode};
7
8/// Parsed script instruction.
9#[derive(Debug, PartialEq, Eq, Copy, Clone)]
10pub enum Instruction<'a> {
11    /// Pushed bytes.
12    PushBytes(&'a PushBytes),
13    /// Opcode.
14    Op(Opcode),
15}
16
17impl Instruction<'_> {
18    /// Returns opcode if this is an opcode instruction.
19    pub fn opcode(&self) -> Option<Opcode> {
20        match self {
21            Self::Op(op) => Some(*op),
22            Self::PushBytes(_) => None,
23        }
24    }
25
26    /// Returns the pushed bytes if this is a push instruction.
27    pub fn push_bytes(&self) -> Option<&PushBytes> {
28        match self {
29            Self::Op(_) => None,
30            Self::PushBytes(bytes) => Some(bytes),
31        }
32    }
33
34    /// Returns the numeric value represented by the instruction, if any.
35    pub fn script_num(&self) -> Option<i64> {
36        match self {
37            Self::Op(op) => {
38                let v = op.to_u8();
39                match v {
40                    0x51..=0x60 => Some(i64::from(v) - 0x50),
41                    0x4f => Some(-1),
42                    _ => None,
43                }
44            }
45            Self::PushBytes(bytes) => {
46                super::read_scriptint_non_minimal(bytes.as_bytes()).ok().map(i64::from)
47            }
48        }
49    }
50
51    /// Returns the numeric value interpreted with CLTV-compatible bounds, if any.
52    pub fn read_int(&self) -> Option<i64> {
53        match self {
54            Self::Op(op) => {
55                let v = op.to_u8();
56                match v {
57                    0x51..=0x60 => Some(i64::from(v) - 0x50),
58                    0x4f => Some(-1),
59                    _ => None,
60                }
61            }
62            Self::PushBytes(bytes) => bytes.read_cltv_scriptint().ok(),
63        }
64    }
65
66    pub(crate) fn script_serialized_len(&self) -> usize {
67        match self {
68            Self::Op(_) => 1,
69            Self::PushBytes(bytes) => ScriptBuf::<()>::reserved_len_for_slice(bytes.len()),
70        }
71    }
72}
73
74/// Iterator over parsed script instructions.
75#[derive(Debug, Clone)]
76pub struct Instructions<'a> {
77    pub(crate) data: core::slice::Iter<'a, u8>,
78    pub(crate) enforce_minimal: bool,
79}
80
81impl<'a> Instructions<'a> {
82    /// Creates a new instruction iterator.
83    pub fn new<T>(script: &'a Script<T>, enforce_minimal: bool) -> Self {
84        Self { data: script.as_bytes().iter(), enforce_minimal }
85    }
86
87    /// Views the remaining bytes as a script.
88    pub fn as_script<T>(&self) -> &'a Script<T> {
89        Script::from_bytes(self.data.as_slice())
90    }
91
92    fn remaining_bytes(&self) -> usize {
93        self.data.as_slice().len()
94    }
95
96    fn kill(&mut self) {
97        let len = self.data.len();
98        self.data.nth(len.max(1) - 1);
99    }
100
101    fn take_slice_or_kill(&mut self, len: u32) -> Result<&'a PushBytes, Error> {
102        let len = len as usize;
103        if self.data.len() >= len {
104            let slice = &self.data.as_slice()[..len];
105            if len > 0 {
106                self.data.nth(len - 1);
107            }
108            Ok(slice.try_into().expect("u32-sized slice length always fits PushBytes"))
109        } else {
110            self.kill();
111            Err(Error::EarlyEndOfScript)
112        }
113    }
114
115    fn next_push_data_len(
116        &mut self,
117        len: PushDataLenLen,
118        min_push_len: usize,
119    ) -> Result<Instruction<'a>, Error> {
120        let Ok(n) = script::read_push_data_len(&mut self.data, len) else {
121            self.kill();
122            return Err(Error::EarlyEndOfScript);
123        };
124        if self.enforce_minimal && n < min_push_len {
125            self.kill();
126            return Err(Error::NonMinimalPush);
127        }
128        n.try_into()
129            .map_err(|_| Error::NumericOverflow)
130            .and_then(|n| self.take_slice_or_kill(n))
131            .map(Instruction::PushBytes)
132    }
133}
134
135impl<'a> Iterator for Instructions<'a> {
136    type Item = Result<Instruction<'a>, Error>;
137
138    fn next(&mut self) -> Option<Self::Item> {
139        let &byte = self.data.next()?;
140
141        match byte {
142            0x00..=0x4b => {
143                let n = u32::from(byte);
144                let op_byte = self.data.as_slice().first();
145                match (self.enforce_minimal, op_byte, n) {
146                    (true, Some(&op_byte), 1)
147                        if op_byte == 0x81 || (op_byte > 0 && op_byte <= 16) =>
148                    {
149                        self.kill();
150                        Some(Err(Error::NonMinimalPush))
151                    }
152                    (_, None, 0) => Some(Ok(Instruction::PushBytes(PushBytes::empty()))),
153                    _ => Some(self.take_slice_or_kill(n).map(Instruction::PushBytes)),
154                }
155            }
156            x if x == all::OP_PUSHDATA1.to_u8() => {
157                Some(self.next_push_data_len(PushDataLenLen::One, 76))
158            }
159            x if x == all::OP_PUSHDATA2.to_u8() => {
160                Some(self.next_push_data_len(PushDataLenLen::Two, 0x100))
161            }
162            x if x == all::OP_PUSHDATA4.to_u8() => {
163                Some(self.next_push_data_len(PushDataLenLen::Four, 0x10000))
164            }
165            _ => Some(Ok(Instruction::Op(Opcode::from(byte)))),
166        }
167    }
168
169    #[inline]
170    fn size_hint(&self) -> (usize, Option<usize>) {
171        if self.data.as_slice().is_empty() {
172            (0, Some(0))
173        } else {
174            (1, Some(self.data.len()))
175        }
176    }
177}
178
179impl core::iter::FusedIterator for Instructions<'_> {}
180
181/// Iterator over script instructions with byte offsets.
182#[derive(Debug, Clone)]
183pub struct InstructionIndices<'a> {
184    instructions: Instructions<'a>,
185    pos: usize,
186}
187
188impl<'a> InstructionIndices<'a> {
189    /// Creates a new iterator from a script.
190    pub fn new<T>(script: &'a Script<T>, enforce_minimal: bool) -> Self {
191        Self { instructions: Instructions::new(script, enforce_minimal), pos: 0 }
192    }
193
194    /// Views the remaining bytes as a script.
195    pub fn as_script<T>(&self) -> &'a Script<T> {
196        self.instructions.as_script()
197    }
198
199    fn remaining_bytes(&self) -> usize {
200        self.instructions.remaining_bytes()
201    }
202
203    fn next_with<F: FnOnce(&mut Self) -> Option<Result<Instruction<'a>, Error>>>(
204        &mut self,
205        next_fn: F,
206    ) -> Option<<Self as Iterator>::Item> {
207        let prev_remaining = self.remaining_bytes();
208        let prev_pos = self.pos;
209        let instruction = next_fn(self)?;
210        let consumed = prev_remaining - self.remaining_bytes();
211        self.pos += consumed;
212        Some(instruction.map(move |instruction| (prev_pos, instruction)))
213    }
214}
215
216impl<'a> Iterator for InstructionIndices<'a> {
217    type Item = Result<(usize, Instruction<'a>), Error>;
218
219    fn next(&mut self) -> Option<Self::Item> {
220        self.next_with(|this| this.instructions.next())
221    }
222
223    #[inline]
224    fn size_hint(&self) -> (usize, Option<usize>) {
225        self.instructions.size_hint()
226    }
227
228    fn nth(&mut self, n: usize) -> Option<Self::Item> {
229        self.next_with(|this| this.instructions.nth(n))
230    }
231}
232
233impl core::iter::FusedIterator for InstructionIndices<'_> {}