bitcoin_script_analyzer/script/
mod.rs

1pub mod convert;
2pub mod stack;
3
4use crate::opcode::Opcode;
5use core::fmt;
6
7#[derive(Debug, Clone, Copy)]
8pub enum ScriptElem<'a> {
9    Op(Opcode),
10    Bytes(&'a [u8]),
11}
12
13impl<'a> fmt::Display for ScriptElem<'a> {
14    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
15        match self {
16            Self::Op(opcode) => write!(f, "{}", opcode.name().unwrap_or("UNKNOWN")),
17            Self::Bytes(bytes) => {
18                write!(f, "<")?;
19                for byte in *bytes {
20                    write!(f, "{:02x}", byte)?;
21                }
22                write!(f, ">")
23            }
24        }
25    }
26}
27
28pub type Script<'a> = Vec<ScriptElem<'a>>;
29pub type ScriptSlice<'a> = &'a [ScriptElem<'a>];
30
31pub fn parse_script(bytes: &[u8]) -> Result<Script<'_>, ParseScriptError> {
32    let mut a = Vec::new();
33
34    let mut offset = 0;
35    while offset < bytes.len() {
36        let b = bytes[offset];
37        offset += 1;
38        let opcode = Opcode { opcode: b };
39        if opcode.name().is_some() {
40            if let Some(n) = opcode.pushdata_length() {
41                let n = n;
42                let Some(push_size) = bytes.get(offset..offset + n) else {
43                    return Err(ParseScriptError::UnexpectedEndPushdataLength(opcode));
44                };
45                let l = u32::from_le_bytes({
46                    let mut buf = [0u8; 4];
47                    buf[0..push_size.len()].copy_from_slice(push_size);
48                    buf
49                }) as usize;
50                offset += n;
51                let Some(data) = bytes.get(offset..offset + l) else {
52                    return Err(ParseScriptError::UnexpectedEnd(l, bytes.len() - offset));
53                };
54                offset += l;
55                a.push(ScriptElem::Bytes(data));
56            } else {
57                a.push(ScriptElem::Op(opcode));
58            }
59        } else if b <= 75 {
60            let Some(data) = bytes.get(offset..offset + b as usize) else {
61                return Err(ParseScriptError::UnexpectedEnd(b as usize, bytes.len() - offset));
62            };
63            offset += b as usize;
64            a.push(ScriptElem::Bytes(data));
65        } else {
66            return Err(ParseScriptError::Invalid(b));
67        }
68    }
69
70    Ok(a)
71}
72
73#[derive(Debug)]
74pub enum ParseScriptError {
75    Invalid(u8),
76    UnexpectedEndPushdataLength(Opcode),
77    UnexpectedEnd(usize, usize),
78}
79
80impl fmt::Display for ParseScriptError {
81    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82        match self {
83            Self::Invalid(opcode) => write!(f, "Invalid opcode 0x{opcode:02x}"),
84            Self::UnexpectedEndPushdataLength(opcode) => write!(
85                f,
86                "{opcode} with incomplete push length (SCRIPT_ERR_BAD_OPCODE)"
87            ),
88            Self::UnexpectedEnd(expected, actual) => write!(
89                f,
90                "Invalid length, expected {expected} but got {actual} (SCRIPT_ERR_BAD_OPCODE)"
91            ),
92        }
93    }
94}
95
96// TODO serialization
97
98/*
99
100TODO maybe flags from bitcoin core
101
102MANDATORY_SCRIPT_VERIFY_FLAGS = SCRIPT_VERIFY_P2SH
103
104consensus:
105MANDATORY_SCRIPT_VERIFY_FLAGS
106
107relay:
108MANDATORY_SCRIPT_VERIFY_FLAGS
109SCRIPT_VERIFY_DERSIG
110SCRIPT_VERIFY_STRICTENC
111SCRIPT_VERIFY_MINIMALDATA
112SCRIPT_VERIFY_NULLDUMMY
113SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS
114SCRIPT_VERIFY_CLEANSTACK
115SCRIPT_VERIFY_MINIMALIF
116SCRIPT_VERIFY_NULLFAIL
117SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY
118SCRIPT_VERIFY_CHECKSEQUENCEVERIFY
119SCRIPT_VERIFY_LOW_S
120SCRIPT_VERIFY_WITNESS
121SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM
122SCRIPT_VERIFY_WITNESS_PUBKEYTYPE
123SCRIPT_VERIFY_CONST_SCRIPTCODE
124SCRIPT_VERIFY_TAPROOT
125SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION
126SCRIPT_VERIFY_DISCOURAGE_OP_SUCCESS
127SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE
128
129*/