1use crate::{
4 cursor::Cursor,
5 error::{ParseError, ParseResult},
6};
7
8pub const MAX_SCRIPT_SIZE: usize = 10_000;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub struct Script<'a> {
16 pub bytes: &'a [u8],
18}
19
20impl<'a> Script<'a> {
21 #[inline]
23 pub fn parse(cursor: &mut Cursor<'a>) -> ParseResult<Self> {
24 let bytes = cursor.read_var_bytes(MAX_SCRIPT_SIZE)?;
25 Ok(Self { bytes })
26 }
27
28 #[inline]
30 pub fn script_type(&self) -> ScriptType<'a> {
31 ScriptType::classify(self.bytes)
32 }
33
34 #[inline]
36 pub fn len(&self) -> usize {
37 self.bytes.len()
38 }
39
40 #[inline]
42 pub fn is_empty(&self) -> bool {
43 self.bytes.is_empty()
44 }
45
46 pub fn instructions(&self) -> Instructions<'a> {
48 Instructions {
49 data: self.bytes,
50 pos: 0,
51 errored: false,
52 }
53 }
54}
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
62pub enum ScriptType<'a> {
63 P2PKH {
65 pubkey_hash: &'a [u8; 20],
67 },
68 P2SH {
70 script_hash: &'a [u8; 20],
72 },
73 P2WPKH {
75 pubkey_hash: &'a [u8; 20],
77 },
78 P2WSH {
80 script_hash: &'a [u8; 32],
82 },
83 P2TR {
85 x_only_pubkey: &'a [u8; 32],
87 },
88 P2PK {
90 pubkey: &'a [u8],
92 },
93 OpReturn {
95 data: &'a [u8],
97 },
98 Multisig {
100 required: u8,
102 total: u8,
104 },
105 Unknown,
107}
108
109impl<'a> ScriptType<'a> {
110 fn classify(b: &'a [u8]) -> Self {
111 match b {
112 [0x76, 0xa9, 0x14, hash @ .., 0x88, 0xac] if hash.len() == 20 => ScriptType::P2PKH {
114 pubkey_hash: hash.try_into().unwrap(),
115 },
116 [0xa9, 0x14, hash @ .., 0x87] if hash.len() == 20 => ScriptType::P2SH {
118 script_hash: hash.try_into().unwrap(),
119 },
120 [0x00, 0x14, hash @ ..] if hash.len() == 20 => ScriptType::P2WPKH {
122 pubkey_hash: hash.try_into().unwrap(),
123 },
124 [0x00, 0x20, hash @ ..] if hash.len() == 32 => ScriptType::P2WSH {
126 script_hash: hash.try_into().unwrap(),
127 },
128 [0x51, 0x20, pk @ ..] if pk.len() == 32 => ScriptType::P2TR {
130 x_only_pubkey: pk.try_into().unwrap(),
131 },
132 [0x21, pk @ .., 0xac] if pk.len() == 33 => ScriptType::P2PK { pubkey: pk },
134 [0x41, pk @ .., 0xac] if pk.len() == 65 => ScriptType::P2PK { pubkey: pk },
136 [0x6a, rest @ ..] => ScriptType::OpReturn { data: rest },
138 _ => ScriptType::Unknown,
139 }
140 }
141}
142
143#[derive(Debug, Clone, Copy, PartialEq, Eq)]
152pub enum Instruction<'a> {
153 PushBytes(&'a [u8]),
155 Op(u8),
157}
158
159pub struct Instructions<'a> {
164 data: &'a [u8],
165 pos: usize,
166 errored: bool,
168}
169
170impl<'a> Iterator for Instructions<'a> {
171 type Item = ParseResult<Instruction<'a>>;
172
173 fn next(&mut self) -> Option<Self::Item> {
174 if self.errored || self.pos >= self.data.len() {
175 return None;
176 }
177
178 let op = self.data[self.pos];
179 self.pos += 1;
180
181 let push_len: Option<Result<usize, ParseError>> = match op {
184 0x00 => Some(Ok(0)),
185 n @ 0x01..=0x4b => Some(Ok(n as usize)),
186 0x4c => {
187 let avail = self.data.len() - self.pos;
189 if avail < 1 {
190 Some(Err(ParseError::UnexpectedEof {
191 needed: 1,
192 available: avail,
193 }))
194 } else {
195 let n = self.data[self.pos] as usize;
196 self.pos += 1;
197 Some(Ok(n))
198 }
199 }
200 0x4d => {
201 let avail = self.data.len() - self.pos;
203 if avail < 2 {
204 Some(Err(ParseError::UnexpectedEof {
205 needed: 2,
206 available: avail,
207 }))
208 } else {
209 let n =
210 u16::from_le_bytes([self.data[self.pos], self.data[self.pos + 1]]) as usize;
211 self.pos += 2;
212 Some(Ok(n))
213 }
214 }
215 0x4e => {
216 let avail = self.data.len() - self.pos;
218 if avail < 4 {
219 Some(Err(ParseError::UnexpectedEof {
220 needed: 4,
221 available: avail,
222 }))
223 } else {
224 let n = u32::from_le_bytes([
225 self.data[self.pos],
226 self.data[self.pos + 1],
227 self.data[self.pos + 2],
228 self.data[self.pos + 3],
229 ]) as usize;
230 self.pos += 4;
231 Some(Ok(n))
232 }
233 }
234 _ => None, };
236
237 match push_len {
238 None => Some(Ok(Instruction::Op(op))),
239 Some(Err(e)) => {
240 self.errored = true;
241 Some(Err(e))
242 }
243 Some(Ok(n)) => {
244 let avail = self.data.len() - self.pos;
245 if n > avail {
246 self.errored = true;
247 Some(Err(ParseError::UnexpectedEof {
248 needed: n,
249 available: avail,
250 }))
251 } else {
252 let bytes = &self.data[self.pos..self.pos + n];
253 self.pos += n;
254 Some(Ok(Instruction::PushBytes(bytes)))
255 }
256 }
257 }
258 }
259}
260
261#[cfg(test)]
262mod tests {
263 extern crate std;
264 use super::*;
265 use crate::cursor::Cursor;
266 use std::vec::Vec;
267
268 #[test]
269 fn p2pkh_classification() {
270 let script_bytes: &[u8] = &[
272 0x76, 0xa9, 0x14, 0x89, 0xab, 0xcd, 0xef, 0xab, 0xba, 0xab, 0xba, 0xab, 0xba, 0xab,
273 0xba, 0xab, 0xba, 0xab, 0xba, 0xab, 0xba, 0xab, 0xba, 0x88, 0xac,
274 ];
275 let mut raw = Vec::new();
276 raw.push(script_bytes.len() as u8);
278 raw.extend_from_slice(script_bytes);
279
280 let mut cursor = Cursor::new(&raw);
281 let script = Script::parse(&mut cursor).unwrap();
282 assert!(matches!(script.script_type(), ScriptType::P2PKH { .. }));
283 }
284
285 #[test]
286 fn op_return_classification() {
287 let script_bytes: &[u8] = &[0x6a, 0x04, 0xde, 0xad, 0xbe, 0xef];
288 let mut raw = Vec::new();
289 raw.push(script_bytes.len() as u8);
290 raw.extend_from_slice(script_bytes);
291 let mut cursor = Cursor::new(&raw);
292 let script = Script::parse(&mut cursor).unwrap();
293 assert!(matches!(script.script_type(), ScriptType::OpReturn { .. }));
294 }
295}