Skip to main content

bitcoin_embed/
envelope.rs

1//! # Script Envelope Encoding
2
3// Based on ordinals/inscriptions/envelope.rs
4
5use {
6    bitcoin::{
7        Script,
8        blockdata::{
9            constants::MAX_SCRIPT_ELEMENT_SIZE,
10            opcodes,
11            script::{
12                Instruction::{self, Op, PushBytes},
13                Instructions,
14            },
15        },
16        script::{Builder, Error, PushBytes as ScriptPushBytes},
17    },
18    std::iter::Peekable,
19};
20
21type Result<T> = std::result::Result<T, Error>;
22
23/// An Envelope represents a series of data pushes
24pub type Envelope = Vec<Vec<u8>>;
25
26/// Adds envelope to a Bitcoin script using the envelope pattern (OP_FALSE OP_IF ... OP_ENDIF)
27pub fn append_to_builder(envelope: Envelope, mut builder: Builder) -> Builder {
28    builder = builder
29        .push_opcode(opcodes::OP_FALSE)
30        .push_opcode(opcodes::all::OP_IF);
31
32    for bytes in envelope {
33        for chunk in bytes.chunks(MAX_SCRIPT_ELEMENT_SIZE) {
34            builder = builder.push_slice::<&ScriptPushBytes>(chunk.try_into().unwrap());
35        }
36    }
37
38    builder.push_opcode(opcodes::all::OP_ENDIF)
39}
40
41/// Adds bytes to a Bitcoin script using the envelope pattern (OP_FALSE OP_IF ... OP_ENDIF)
42pub fn append_bytes_to_builder(bytes: &[u8], builder: Builder) -> Builder {
43    append_to_builder(vec![bytes.to_vec()], builder)
44}
45
46/// Extracts envelopes from Bitcoin script
47pub fn from_script(script: &Script) -> Vec<Envelope> {
48    let mut envelopes = Vec::new();
49
50    let mut instructions = script.instructions().peekable();
51
52    while let Ok(Some(instruction)) = instructions.next().transpose() {
53        if instruction == PushBytes((&[]).into()) {
54            if let Ok(Some(envelope)) = from_instructions(&mut instructions) {
55                envelopes.push(envelope);
56            }
57        }
58    }
59
60    envelopes
61}
62
63fn accept(instructions: &mut Peekable<Instructions>, instruction: Instruction) -> Result<bool> {
64    if instructions.peek() == Some(&Ok(instruction)) {
65        instructions.next().transpose()?;
66        Ok(true)
67    } else {
68        Ok(false)
69    }
70}
71
72fn from_instructions(instructions: &mut Peekable<Instructions>) -> Result<Option<Envelope>> {
73    if !accept(instructions, Op(opcodes::all::OP_IF))? {
74        return Ok(None);
75    }
76
77    let mut payload = Vec::new();
78
79    loop {
80        match instructions.next().transpose()? {
81            None => return Ok(None),
82            Some(Op(opcodes::all::OP_ENDIF)) => {
83                return Ok(Some(payload));
84            }
85            Some(Op(opcodes::all::OP_PUSHNUM_NEG1)) => {
86                payload.push(vec![0x81]);
87            }
88            Some(Op(opcodes::all::OP_PUSHNUM_1)) => {
89                payload.push(vec![1]);
90            }
91            Some(Op(opcodes::all::OP_PUSHNUM_2)) => {
92                payload.push(vec![2]);
93            }
94            Some(Op(opcodes::all::OP_PUSHNUM_3)) => {
95                payload.push(vec![3]);
96            }
97            Some(Op(opcodes::all::OP_PUSHNUM_4)) => {
98                payload.push(vec![4]);
99            }
100            Some(Op(opcodes::all::OP_PUSHNUM_5)) => {
101                payload.push(vec![5]);
102            }
103            Some(Op(opcodes::all::OP_PUSHNUM_6)) => {
104                payload.push(vec![6]);
105            }
106            Some(Op(opcodes::all::OP_PUSHNUM_7)) => {
107                payload.push(vec![7]);
108            }
109            Some(Op(opcodes::all::OP_PUSHNUM_8)) => {
110                payload.push(vec![8]);
111            }
112            Some(Op(opcodes::all::OP_PUSHNUM_9)) => {
113                payload.push(vec![9]);
114            }
115            Some(Op(opcodes::all::OP_PUSHNUM_10)) => {
116                payload.push(vec![10]);
117            }
118            Some(Op(opcodes::all::OP_PUSHNUM_11)) => {
119                payload.push(vec![11]);
120            }
121            Some(Op(opcodes::all::OP_PUSHNUM_12)) => {
122                payload.push(vec![12]);
123            }
124            Some(Op(opcodes::all::OP_PUSHNUM_13)) => {
125                payload.push(vec![13]);
126            }
127            Some(Op(opcodes::all::OP_PUSHNUM_14)) => {
128                payload.push(vec![14]);
129            }
130            Some(Op(opcodes::all::OP_PUSHNUM_15)) => {
131                payload.push(vec![15]);
132            }
133            Some(Op(opcodes::all::OP_PUSHNUM_16)) => {
134                payload.push(vec![16]);
135            }
136            Some(PushBytes(push)) => {
137                payload.push(push.as_bytes().to_vec());
138            }
139            Some(_) => return Ok(None),
140        }
141    }
142}
143
144#[cfg(test)]
145mod tests {
146    use super::*;
147
148    #[test]
149    fn test_empty_script() {
150        let script = Script::new();
151        assert_eq!(from_script(script), Vec::<Envelope>::new());
152    }
153
154    #[test]
155    fn test_script_without_envelopes() {
156        let script = Builder::new()
157            .push_opcode(opcodes::OP_FALSE)
158            .push_opcode(opcodes::all::OP_CHECKSIG)
159            .into_script();
160
161        assert_eq!(from_script(&script), Vec::<Envelope>::new());
162    }
163
164    #[test]
165    fn test_single_empty_envelope() {
166        let script = Builder::new()
167            .push_opcode(opcodes::OP_FALSE)
168            .push_opcode(opcodes::all::OP_IF)
169            .push_opcode(opcodes::all::OP_ENDIF)
170            .into_script();
171
172        assert_eq!(from_script(&script), vec![Vec::<Vec<u8>>::new()]);
173    }
174
175    #[test]
176    fn test_envelope_with_single_push() {
177        let data = b"test data";
178
179        let script = Builder::new()
180            .push_opcode(opcodes::OP_FALSE)
181            .push_opcode(opcodes::all::OP_IF)
182            .push_slice(data)
183            .push_opcode(opcodes::all::OP_ENDIF)
184            .into_script();
185
186        assert_eq!(from_script(&script), vec![vec![data.to_vec()]]);
187    }
188
189    #[test]
190    fn test_envelope_with_multiple_pushes() {
191        let data1 = b"first";
192        let data2 = b"second";
193
194        let script = Builder::new()
195            .push_opcode(opcodes::OP_FALSE)
196            .push_opcode(opcodes::all::OP_IF)
197            .push_slice(data1)
198            .push_slice(data2)
199            .push_opcode(opcodes::all::OP_ENDIF)
200            .into_script();
201
202        assert_eq!(
203            from_script(&script),
204            vec![vec![data1.to_vec(), data2.to_vec()]]
205        );
206    }
207
208    #[test]
209    fn test_multiple_envelopes() {
210        let data1 = b"envelope1";
211        let data2 = b"envelope2";
212
213        let mut builder = Builder::new();
214
215        builder = builder
216            // First envelope
217            .push_opcode(opcodes::OP_FALSE)
218            .push_opcode(opcodes::all::OP_IF)
219            .push_slice(data1)
220            .push_opcode(opcodes::all::OP_ENDIF)
221            // Second envelope
222            .push_opcode(opcodes::OP_FALSE)
223            .push_opcode(opcodes::all::OP_IF)
224            .push_slice(data2)
225            .push_opcode(opcodes::all::OP_ENDIF);
226
227        let script = builder.into_script();
228
229        assert_eq!(
230            from_script(&script),
231            vec![vec![data1.to_vec()], vec![data2.to_vec()]]
232        );
233    }
234
235    #[test]
236    fn test_pushnum_opcodes() {
237        let pushnums = [
238            (opcodes::all::OP_PUSHNUM_NEG1, vec![0x81]),
239            (opcodes::all::OP_PUSHNUM_1, vec![1]),
240            (opcodes::all::OP_PUSHNUM_2, vec![2]),
241            (opcodes::all::OP_PUSHNUM_16, vec![16]),
242        ];
243
244        for (opcode, expected) in pushnums {
245            let script = Builder::new()
246                .push_opcode(opcodes::OP_FALSE)
247                .push_opcode(opcodes::all::OP_IF)
248                .push_opcode(opcode)
249                .push_opcode(opcodes::all::OP_ENDIF)
250                .into_script();
251
252            assert_eq!(from_script(&script), vec![vec![expected]]);
253        }
254    }
255
256    #[test]
257    fn test_large_data_chunking() {
258        let large_data = vec![0xaa; 100_000];
259
260        let builder = Builder::new();
261        let builder = append_bytes_to_builder(&large_data, builder);
262        let script = builder.into_script();
263
264        let extracted = from_script(&script);
265        assert_eq!(extracted.len(), 1);
266
267        let flattened: Vec<u8> = extracted[0].iter().flatten().cloned().collect();
268        assert_eq!(flattened, large_data);
269    }
270
271    #[test]
272    fn test_append_to_builder() {
273        let envelope = vec![vec![1, 2, 3], vec![4, 5, 6]];
274
275        let builder = Builder::new();
276        let builder = append_to_builder(envelope.clone(), builder);
277        let script = builder.into_script();
278
279        assert_eq!(from_script(&script), vec![envelope]);
280    }
281
282    #[test]
283    fn test_append_bytes_to_builder() {
284        let data = b"test data";
285
286        let builder = Builder::new();
287        let builder = append_bytes_to_builder(data, builder);
288        let script = builder.into_script();
289
290        assert_eq!(from_script(&script), vec![vec![data.to_vec()]]);
291    }
292
293    #[test]
294    fn test_nested_invalid_instructions() {
295        let script = Builder::new()
296            .push_opcode(opcodes::OP_FALSE)
297            .push_opcode(opcodes::all::OP_IF)
298            .push_slice([0x01]) // Valid push
299            .push_opcode(opcodes::all::OP_IF) // Invalid opcode
300            .push_opcode(opcodes::all::OP_ENDIF)
301            .into_script();
302
303        assert_eq!(from_script(&script), Vec::<Envelope>::new());
304    }
305
306    #[test]
307    fn test_incomplete_envelope() {
308        let script = Builder::new()
309            .push_opcode(opcodes::OP_FALSE)
310            .push_opcode(opcodes::all::OP_IF)
311            .push_slice(b"data")
312            // Missing OP_ENDIF
313            .into_script();
314
315        assert_eq!(from_script(&script), Vec::<Envelope>::new());
316    }
317
318    #[test]
319    fn test_surrounding_opcodes() {
320        let data = b"test data";
321
322        let script = Builder::new()
323            .push_opcode(opcodes::all::OP_DUP)
324            .push_opcode(opcodes::OP_FALSE)
325            .push_opcode(opcodes::all::OP_IF)
326            .push_slice(data)
327            .push_opcode(opcodes::all::OP_ENDIF)
328            .push_opcode(opcodes::all::OP_EQUALVERIFY)
329            .into_script();
330
331        assert_eq!(from_script(&script), vec![vec![data.to_vec()]]);
332    }
333
334    #[test]
335    fn test_roundtrip() {
336        let original_data = vec![vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 9]];
337
338        let builder = Builder::new();
339        let builder = append_to_builder(original_data.clone(), builder);
340        let script = builder.into_script();
341
342        let extracted = from_script(&script);
343        assert_eq!(extracted, vec![original_data]);
344    }
345}