tidecoin_primitives/script/
instruction.rs1use internals::script::{self, PushDataLenLen};
4
5use super::{Error, PushBytes, Script, ScriptBuf};
6use crate::opcodes::{all, Opcode};
7
8#[derive(Debug, PartialEq, Eq, Copy, Clone)]
10pub enum Instruction<'a> {
11 PushBytes(&'a PushBytes),
13 Op(Opcode),
15}
16
17impl Instruction<'_> {
18 pub fn opcode(&self) -> Option<Opcode> {
20 match self {
21 Self::Op(op) => Some(*op),
22 Self::PushBytes(_) => None,
23 }
24 }
25
26 pub fn push_bytes(&self) -> Option<&PushBytes> {
28 match self {
29 Self::Op(_) => None,
30 Self::PushBytes(bytes) => Some(bytes),
31 }
32 }
33
34 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 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#[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 pub fn new<T>(script: &'a Script<T>, enforce_minimal: bool) -> Self {
84 Self { data: script.as_bytes().iter(), enforce_minimal }
85 }
86
87 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#[derive(Debug, Clone)]
183pub struct InstructionIndices<'a> {
184 instructions: Instructions<'a>,
185 pos: usize,
186}
187
188impl<'a> InstructionIndices<'a> {
189 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 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<'_> {}