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}