bitcoin/blockdata/script/
instruction.rs

1// SPDX-License-Identifier: CC0-1.0
2
3use crate::blockdata::opcodes::{self, Opcode};
4use crate::blockdata::script::{read_uint_iter, Error, PushBytes, Script, ScriptBuf, UintError};
5
6/// A "parsed opcode" which allows iterating over a [`Script`] in a more sensible way.
7#[derive(Debug, PartialEq, Eq, Copy, Clone)]
8pub enum Instruction<'a> {
9    /// Push a bunch of data.
10    PushBytes(&'a PushBytes),
11    /// Some non-push opcode.
12    Op(Opcode),
13}
14
15impl<'a> Instruction<'a> {
16    /// Returns the opcode if the instruction is not a data push.
17    pub fn opcode(&self) -> Option<Opcode> {
18        match self {
19            Instruction::Op(op) => Some(*op),
20            Instruction::PushBytes(_) => None,
21        }
22    }
23
24    /// Returns the pushed bytes if the instruction is a data push.
25    pub fn push_bytes(&self) -> Option<&PushBytes> {
26        match self {
27            Instruction::Op(_) => None,
28            Instruction::PushBytes(bytes) => Some(bytes),
29        }
30    }
31
32    /// Returns the number interpretted by the script parser
33    /// if it can be coerced into a number.
34    ///
35    /// This does not require the script num to be minimal.
36    pub fn script_num(&self) -> Option<i64> {
37        match self {
38            Instruction::Op(op) => {
39                let v = op.to_u8();
40                match v {
41                    // OP_PUSHNUM_1 ..= OP_PUSHNUM_16
42                    0x51..=0x60 => Some(v as i64 - 0x50),
43                    // OP_PUSHNUM_NEG1
44                    0x4f => Some(-1),
45                    _ => None,
46                }
47            }
48            Instruction::PushBytes(bytes) => {
49                match super::read_scriptint_non_minimal(bytes.as_bytes()) {
50                    Ok(v) => Some(v),
51                    _ => None,
52                }
53            }
54        }
55    }
56
57    /// Returns the number of bytes required to encode the instruction in script.
58    pub(super) fn script_serialized_len(&self) -> usize {
59        match self {
60            Instruction::Op(_) => 1,
61            Instruction::PushBytes(bytes) => ScriptBuf::reserved_len_for_slice(bytes.len()),
62        }
63    }
64}
65
66/// Iterator over a script returning parsed opcodes.
67#[derive(Debug, Clone)]
68pub struct Instructions<'a> {
69    pub(crate) data: core::slice::Iter<'a, u8>,
70    pub(crate) enforce_minimal: bool,
71}
72
73impl<'a> Instructions<'a> {
74    /// Views the remaining script as a slice.
75    ///
76    /// This is analogous to what [`core::str::Chars::as_str`] does.
77    pub fn as_script(&self) -> &'a Script { Script::from_bytes(self.data.as_slice()) }
78
79    /// Sets the iterator to end so that it won't iterate any longer.
80    pub(super) fn kill(&mut self) {
81        let len = self.data.len();
82        self.data.nth(len.max(1) - 1);
83    }
84
85    /// Takes a `len` bytes long slice from iterator and returns it, advancing the iterator.
86    ///
87    /// If the iterator is not long enough [`Error::EarlyEndOfScript`] is returned and the iterator
88    /// is killed to avoid returning an infinite stream of errors.
89    pub(super) fn take_slice_or_kill(&mut self, len: u32) -> Result<&'a PushBytes, Error> {
90        let len = len as usize;
91        if self.data.len() >= len {
92            let slice = &self.data.as_slice()[..len];
93            if len > 0 {
94                self.data.nth(len - 1);
95            }
96
97            Ok(slice.try_into().expect("len was created from u32, so can't happen"))
98        } else {
99            self.kill();
100            Err(Error::EarlyEndOfScript)
101        }
102    }
103
104    pub(super) fn next_push_data_len(
105        &mut self,
106        len: PushDataLenLen,
107        min_push_len: usize,
108    ) -> Option<Result<Instruction<'a>, Error>> {
109        let n = match read_uint_iter(&mut self.data, len as usize) {
110            Ok(n) => n,
111            // We do exhaustive matching to not forget to handle new variants if we extend
112            // `UintError` type.
113            // Overflow actually means early end of script (script is definitely shorter
114            // than `usize::MAX`)
115            Err(UintError::EarlyEndOfScript) | Err(UintError::NumericOverflow) => {
116                self.kill();
117                return Some(Err(Error::EarlyEndOfScript));
118            }
119        };
120        if self.enforce_minimal && n < min_push_len {
121            self.kill();
122            return Some(Err(Error::NonMinimalPush));
123        }
124        let result = n
125            .try_into()
126            .map_err(|_| Error::NumericOverflow)
127            .and_then(|n| self.take_slice_or_kill(n))
128            .map(Instruction::PushBytes);
129        Some(result)
130    }
131}
132
133/// Allowed length of push data length.
134///
135/// This makes it easier to prove correctness of `next_push_data_len`.
136pub(super) enum PushDataLenLen {
137    One = 1,
138    Two = 2,
139    Four = 4,
140}
141
142impl<'a> Iterator for Instructions<'a> {
143    type Item = Result<Instruction<'a>, Error>;
144
145    fn next(&mut self) -> Option<Result<Instruction<'a>, Error>> {
146        let &byte = self.data.next()?;
147
148        // classify parameter does not really matter here since we are only using
149        // it for pushes and nums
150        match Opcode::from(byte).classify(opcodes::ClassifyContext::Legacy) {
151            opcodes::Class::PushBytes(n) => {
152                // make sure safety argument holds across refactorings
153                let n: u32 = n;
154
155                let op_byte = self.data.as_slice().first();
156                match (self.enforce_minimal, op_byte, n) {
157                    (true, Some(&op_byte), 1)
158                        if op_byte == 0x81 || (op_byte > 0 && op_byte <= 16) =>
159                    {
160                        self.kill();
161                        Some(Err(Error::NonMinimalPush))
162                    }
163                    (_, None, 0) => {
164                        // the iterator is already empty, may as well use this information to avoid
165                        // whole take_slice_or_kill function
166                        Some(Ok(Instruction::PushBytes(PushBytes::empty())))
167                    }
168                    _ => Some(self.take_slice_or_kill(n).map(Instruction::PushBytes)),
169                }
170            }
171            opcodes::Class::Ordinary(opcodes::Ordinary::OP_PUSHDATA1) =>
172                self.next_push_data_len(PushDataLenLen::One, 76),
173            opcodes::Class::Ordinary(opcodes::Ordinary::OP_PUSHDATA2) =>
174                self.next_push_data_len(PushDataLenLen::Two, 0x100),
175            opcodes::Class::Ordinary(opcodes::Ordinary::OP_PUSHDATA4) =>
176                self.next_push_data_len(PushDataLenLen::Four, 0x10000),
177            // Everything else we can push right through
178            _ => Some(Ok(Instruction::Op(Opcode::from(byte)))),
179        }
180    }
181
182    #[inline]
183    fn size_hint(&self) -> (usize, Option<usize>) {
184        if self.data.len() == 0 {
185            (0, Some(0))
186        } else {
187            // There will not be more instructions than bytes
188            (1, Some(self.data.len()))
189        }
190    }
191}
192
193impl<'a> core::iter::FusedIterator for Instructions<'a> {}
194
195/// Iterator over script instructions with their positions.
196///
197/// The returned indices can be used for slicing [`Script`] [safely](Script#slicing-safety).
198///
199/// This is analogous to [`core::str::CharIndices`].
200#[derive(Debug, Clone)]
201pub struct InstructionIndices<'a> {
202    instructions: Instructions<'a>,
203    pos: usize,
204}
205
206impl<'a> InstructionIndices<'a> {
207    /// Views the remaining script as a slice.
208    ///
209    /// This is analogous to what [`core::str::Chars::as_str`] does.
210    #[inline]
211    pub fn as_script(&self) -> &'a Script { self.instructions.as_script() }
212
213    /// Creates `Self` setting `pos` to 0.
214    pub(super) fn from_instructions(instructions: Instructions<'a>) -> Self {
215        InstructionIndices { instructions, pos: 0 }
216    }
217
218    pub(super) fn remaining_bytes(&self) -> usize { self.instructions.as_script().len() }
219
220    /// Modifies the iterator using `next_fn` returning the next item.
221    ///
222    /// This generically computes the new position and maps the value to be returned from iterator
223    /// method.
224    pub(super) fn next_with<F: FnOnce(&mut Self) -> Option<Result<Instruction<'a>, Error>>>(
225        &mut self,
226        next_fn: F,
227    ) -> Option<<Self as Iterator>::Item> {
228        let prev_remaining = self.remaining_bytes();
229        let prev_pos = self.pos;
230        let instruction = next_fn(self)?;
231        // No underflow: there must be less remaining bytes now than previously
232        let consumed = prev_remaining - self.remaining_bytes();
233        // No overflow: sum will never exceed slice length which itself can't exceed `usize`
234        self.pos += consumed;
235        Some(instruction.map(move |instruction| (prev_pos, instruction)))
236    }
237}
238
239impl<'a> Iterator for InstructionIndices<'a> {
240    /// The `usize` in the tuple represents index at which the returned `Instruction` is located.
241    type Item = Result<(usize, Instruction<'a>), Error>;
242
243    fn next(&mut self) -> Option<Self::Item> { self.next_with(|this| this.instructions.next()) }
244
245    #[inline]
246    fn size_hint(&self) -> (usize, Option<usize>) { self.instructions.size_hint() }
247
248    // the override avoids computing pos multiple times
249    fn nth(&mut self, n: usize) -> Option<Self::Item> {
250        self.next_with(|this| this.instructions.nth(n))
251    }
252}
253
254impl core::iter::FusedIterator for InstructionIndices<'_> {}