Skip to main content

bsv/script/
script_chunk.rs

1//! ScriptChunk: a parsed element of a Bitcoin script.
2//!
3//! Each chunk is either a bare opcode or a data push (opcode + data bytes).
4
5use crate::script::op::Op;
6
7/// A single parsed element of a script.
8#[derive(Debug, Clone, PartialEq, Eq)]
9pub struct ScriptChunk {
10    /// The opcode. For direct pushes (0x01..=0x4b) this is the raw byte
11    /// cast via `Op::from`, which gives `OpInvalidOpcode`. The parser
12    /// stores the raw byte value as `op_byte` instead.
13    pub op: Op,
14    /// For push operations this carries the raw opcode byte (which may
15    /// be a direct-push length 0x01..=0x4b that has no named Op variant).
16    pub op_byte: u8,
17    /// The pushed data, if any.
18    pub data: Option<Vec<u8>>,
19}
20
21impl ScriptChunk {
22    /// Create a new chunk with just an opcode (no data).
23    pub fn new_opcode(op: Op) -> Self {
24        ScriptChunk {
25            op,
26            op_byte: op.to_byte(),
27            data: None,
28        }
29    }
30
31    /// Create a new chunk from a raw opcode byte and optional data.
32    pub fn new_raw(op_byte: u8, data: Option<Vec<u8>>) -> Self {
33        ScriptChunk {
34            op: Op::from(op_byte),
35            op_byte,
36            data,
37        }
38    }
39
40    /// Serialized byte length of this chunk.
41    pub fn len(&self) -> usize {
42        let data_len = self.data.as_ref().map_or(0, |d| d.len());
43        if data_len == 0 && self.data.is_none() {
44            // Pure opcode, no data
45            return 1;
46        }
47
48        // OP_RETURN with data: opcode + raw data (no length prefix)
49        if self.op == Op::OpReturn && self.data.is_some() {
50            return 1 + data_len;
51        }
52
53        // Direct push (opcode IS the length: 0x01..=0x4b)
54        if self.op_byte >= 0x01 && self.op_byte <= 0x4b {
55            return 1 + data_len;
56        }
57
58        match self.op {
59            Op::OpPushData1 => 1 + 1 + data_len,
60            Op::OpPushData2 => 1 + 2 + data_len,
61            Op::OpPushData4 => 1 + 4 + data_len,
62            _ => 1 + data_len,
63        }
64    }
65
66    /// Whether the chunk has a non-zero serialized length (always true).
67    pub fn is_empty(&self) -> bool {
68        false
69    }
70
71    /// ASM string representation.
72    ///
73    /// Data pushes render as lowercase hex. Opcodes render as their name.
74    pub fn to_asm(&self) -> String {
75        match &self.data {
76            Some(data) => {
77                let hex: String = data.iter().map(|b| format!("{:02x}", b)).collect();
78                hex
79            }
80            None => {
81                // OP_0 renders as "0" in ASM, OP_1NEGATE as "-1"
82                if self.op == Op::Op0 {
83                    "0".to_string()
84                } else if self.op == Op::Op1Negate {
85                    "-1".to_string()
86                } else {
87                    self.op.to_name().to_string()
88                }
89            }
90        }
91    }
92
93    /// Serialize this chunk to binary bytes.
94    pub fn serialize(&self) -> Vec<u8> {
95        let mut out = Vec::with_capacity(self.len());
96        out.push(self.op_byte);
97
98        if let Some(data) = &self.data {
99            if self.op == Op::OpReturn {
100                // OP_RETURN data chunk: opcode + raw data
101                out.extend_from_slice(data);
102                return out;
103            }
104
105            // Direct push (0x01..=0x4b): opcode is the length
106            if self.op_byte >= 0x01 && self.op_byte <= 0x4b {
107                out.extend_from_slice(data);
108                return out;
109            }
110
111            match self.op {
112                Op::OpPushData1 => {
113                    out.push(data.len() as u8);
114                    out.extend_from_slice(data);
115                }
116                Op::OpPushData2 => {
117                    let len = data.len() as u16;
118                    out.push((len & 0xff) as u8);
119                    out.push(((len >> 8) & 0xff) as u8);
120                    out.extend_from_slice(data);
121                }
122                Op::OpPushData4 => {
123                    let len = data.len() as u32;
124                    out.push((len & 0xff) as u8);
125                    out.push(((len >> 8) & 0xff) as u8);
126                    out.push(((len >> 16) & 0xff) as u8);
127                    out.push(((len >> 24) & 0xff) as u8);
128                    out.extend_from_slice(data);
129                }
130                _ => {
131                    out.extend_from_slice(data);
132                }
133            }
134        }
135
136        out
137    }
138}