kaspa_txscript/
script_builder.rs

1use std::iter::once;
2
3use crate::{
4    data_stack::OpcodeData,
5    opcodes::{codes::*, OP_1_NEGATE_VAL, OP_DATA_MAX_VAL, OP_DATA_MIN_VAL, OP_SMALL_INT_MAX_VAL},
6    MAX_SCRIPTS_SIZE, MAX_SCRIPT_ELEMENT_SIZE,
7};
8use hexplay::{HexView, HexViewBuilder};
9use thiserror::Error;
10
11/// DEFAULT_SCRIPT_ALLOC is the default size used for the backing array
12/// for a script being built by the ScriptBuilder. The array will
13/// dynamically grow as needed, but this figure is intended to provide
14/// enough space for vast majority of scripts without needing to grow the
15/// backing array multiple times.
16const DEFAULT_SCRIPT_ALLOC: usize = 512;
17
18#[derive(Error, PartialEq, Eq, Debug, Clone, Copy)]
19pub enum ScriptBuilderError {
20    #[error("adding opcode {0} would exceed the maximum allowed canonical script length of {MAX_SCRIPTS_SIZE}")]
21    OpCodeRejected(u8),
22
23    #[error("adding {0} opcodes would exceed the maximum allowed canonical script length of {MAX_SCRIPTS_SIZE}")]
24    OpCodesRejected(usize),
25
26    #[error("adding {0} bytes of data would exceed the maximum allowed canonical script length of {MAX_SCRIPTS_SIZE}")]
27    DataRejected(usize),
28
29    #[error("adding a data element of {0} bytes exceed the maximum allowed script element size of {MAX_SCRIPT_ELEMENT_SIZE}")]
30    ElementExceedsMaxSize(usize),
31
32    #[error("adding integer {0} would exceed the maximum allowed canonical script length of {MAX_SCRIPTS_SIZE}")]
33    IntegerRejected(i64),
34}
35pub type ScriptBuilderResult<T> = std::result::Result<T, ScriptBuilderError>;
36
37/// ScriptBuilder provides a facility for building custom scripts. It allows
38/// you to push opcodes, ints, and data while respecting canonical encoding. In
39/// general it does not ensure the script will execute correctly, however any
40/// data pushes which would exceed the maximum allowed script engine limits and
41/// are therefore guaranteed not to execute will not be pushed and will result in
42/// the Script function returning an error.
43///
44/// For example, the following would build a 2-of-3 multisig script for usage in
45/// a pay-to-script-hash (although in this situation MultiSigScript() would be a
46/// better choice to generate the script):
47///
48/// ```
49/// use kaspa_txscript::opcodes::codes::*;
50/// use kaspa_txscript::script_builder::{ScriptBuilderResult, ScriptBuilder};
51/// fn build_multisig_script(pub_key1: &[u8], pub_key2: &[u8], pub_key3: &[u8]) -> ScriptBuilderResult<Vec<u8>> {
52///     Ok(ScriptBuilder::new()
53///         .add_op(Op2)?
54///         .add_data(pub_key1)?.add_data(pub_key2)?.add_data(pub_key3)?
55///         .add_op(Op3)?
56///         .add_op(OpCheckMultiSig)?
57///         .drain())
58/// }
59/// ```
60pub struct ScriptBuilder {
61    script: Vec<u8>,
62}
63
64impl ScriptBuilder {
65    pub fn new() -> Self {
66        Self { script: Vec::with_capacity(DEFAULT_SCRIPT_ALLOC) }
67    }
68
69    pub fn script(&self) -> &[u8] {
70        &self.script
71    }
72
73    #[cfg(any(test, target_arch = "wasm32"))]
74    pub fn extend(&mut self, data: &[u8]) {
75        self.script.extend(data);
76    }
77
78    pub fn drain(&mut self) -> Vec<u8> {
79        // Note that the internal script, when taken, is replaced by
80        // vector with no predefined capacity because the script
81        // builder is not supposed to be reused after a call
82        // to drain.
83        std::mem::take(&mut self.script)
84    }
85
86    /// Pushes the passed opcode to the end of the script. The script will not
87    /// be modified if pushing the opcode would cause the script to exceed the
88    /// maximum allowed script engine size.
89    pub fn add_op(&mut self, opcode: u8) -> ScriptBuilderResult<&mut Self> {
90        // Pushes that would cause the script to exceed the largest allowed
91        // script size would result in a non-canonical script.
92        if self.script.len() >= MAX_SCRIPTS_SIZE {
93            return Err(ScriptBuilderError::OpCodeRejected(opcode));
94        }
95
96        self.script.push(opcode);
97        Ok(self)
98    }
99
100    pub fn add_ops(&mut self, opcodes: &[u8]) -> ScriptBuilderResult<&mut Self> {
101        // Pushes that would cause the script to exceed the largest allowed
102        // script size would result in a non-canonical script.
103        if self.script.len() + opcodes.len() > MAX_SCRIPTS_SIZE {
104            return Err(ScriptBuilderError::OpCodesRejected(opcodes.len()));
105        }
106
107        self.script.extend_from_slice(opcodes);
108        Ok(self)
109    }
110
111    /// Returns the number of bytes the canonical encoding of the data will take.
112    pub fn canonical_data_size(data: &[u8]) -> usize {
113        let data_len = data.len();
114
115        // When the data consists of a single number that can be represented
116        // by one of the "small integer" opcodes, that opcode will used be instead
117        // of a data push opcode followed by the number.
118        if data_len == 0 || (data_len == 1 && (data[0] <= OP_SMALL_INT_MAX_VAL || data[0] == OP_1_NEGATE_VAL)) {
119            return 1;
120        }
121
122        data_len
123            + if data_len <= OP_DATA_MAX_VAL as usize {
124                1 // length encoded as OpData#
125            } else if data_len <= u8::MAX as usize {
126                2 // length encoded as OpPushData1 + 1 byte for value
127            } else if data_len <= u16::MAX as usize {
128                3 // length encoded as OpPushData2 + 2 bytes for value
129            } else {
130                5 // length encoded as OpPushData4 + 4 bytes for value
131            }
132    }
133
134    /// Internal function that actually pushes the passed data to the
135    /// end of the script. It automatically chooses canonical opcodes depending on
136    /// the length of the data. A zero length buffer will lead to a push of empty
137    /// data onto the stack (OP_0). No data limits are enforced with this function.
138    fn add_raw_data(&mut self, data: &[u8]) -> &mut Self {
139        let data_len = data.len();
140
141        // When the data consists of a single number that can be represented
142        // by one of the "small integer" opcodes, use that opcode instead of
143        // a data push opcode followed by the number.
144        if data_len == 0 || (data_len == 1 && data[0] == 0) {
145            self.script.push(Op0);
146            return self;
147        } else if data_len == 1 && data[0] <= OP_SMALL_INT_MAX_VAL {
148            self.script.push((Op1 - 1) + data[0]);
149            return self;
150        } else if data_len == 1 && data[0] == OP_1_NEGATE_VAL {
151            self.script.push(Op1Negate);
152            return self;
153        }
154
155        // Use one of the OpData# opcodes if the length of the data is small
156        // enough so the data push instruction is only a single byte.
157        // Otherwise, choose the smallest possible OpPushData# opcode that
158        // can represent the length of the data.
159        if data_len <= OP_DATA_MAX_VAL as usize {
160            self.script.push((OP_DATA_MIN_VAL - 1) + data_len as u8);
161        } else if data_len <= u8::MAX as usize {
162            self.script.extend(once(OpPushData1).chain(once(data_len as u8)));
163        } else if data_len <= u16::MAX as usize {
164            self.script.extend(once(OpPushData2).chain((data_len as u16).to_le_bytes()));
165        } else {
166            self.script.extend(once(OpPushData4).chain((data_len as u32).to_le_bytes()));
167        }
168
169        // Append the actual data.
170        self.script.extend(data);
171        self
172    }
173
174    /// This function should not typically be used by ordinary users as it does not
175    /// include the checks which prevent data pushes larger than the maximum allowed
176    /// sizes which leads to scripts that can't be executed. This is provided for
177    /// testing purposes such as tests where sizes are intentionally made larger
178    /// than allowed.
179    ///
180    /// Use add_data instead.
181    #[cfg(test)]
182    pub fn add_data_unchecked(&mut self, data: &[u8]) -> &mut Self {
183        self.add_raw_data(data)
184    }
185
186    /// AddData pushes the passed data to the end of the script. It automatically
187    /// chooses canonical opcodes depending on the length of the data.
188    ///
189    /// A zero length buffer will lead to a push of empty data onto the stack (Op0 = OpFalse)
190    /// and any push of data greater than [`MAX_SCRIPT_ELEMENT_SIZE`] will not modify
191    /// the script since that is not allowed by the script engine.
192    ///
193    /// Also, the script will not be modified if pushing the data would cause the script to
194    /// exceed the maximum allowed script engine size [`MAX_SCRIPTS_SIZE`].
195    pub fn add_data(&mut self, data: &[u8]) -> ScriptBuilderResult<&mut Self> {
196        // Pushes that would cause the script to exceed the largest allowed
197        // script size would result in a non-canonical script.
198        let data_size = Self::canonical_data_size(data);
199
200        if self.script.len() + data_size > MAX_SCRIPTS_SIZE {
201            return Err(ScriptBuilderError::DataRejected(data_size));
202        }
203
204        // Pushes larger than the max script element size would result in a
205        // script that is not canonical.
206        let data_len = data.len();
207        if data_len > MAX_SCRIPT_ELEMENT_SIZE {
208            return Err(ScriptBuilderError::ElementExceedsMaxSize(data_len));
209        }
210
211        Ok(self.add_raw_data(data))
212    }
213
214    pub fn add_i64(&mut self, val: i64) -> ScriptBuilderResult<&mut Self> {
215        // Pushes that would cause the script to exceed the largest allowed
216        // script size would result in a non-canonical script.
217        if self.script.len() + 1 > MAX_SCRIPTS_SIZE {
218            return Err(ScriptBuilderError::IntegerRejected(val));
219        }
220
221        // Fast path for small integers and Op1Negate.
222        if val == 0 {
223            self.script.push(Op0);
224            return Ok(self);
225        }
226        if val == -1 || (1..=16).contains(&val) {
227            self.script.push(((Op1 as i64 - 1) + val) as u8);
228            return Ok(self);
229        }
230
231        let bytes: Vec<_> = OpcodeData::serialize(&val);
232        self.add_data(&bytes)
233    }
234
235    /// Gets a u64 lock time, converts it to byte array in little-endian, and then used the add_data function.
236    pub fn add_lock_time(&mut self, lock_time: u64) -> ScriptBuilderResult<&mut Self> {
237        self.add_u64(lock_time)
238    }
239
240    /// Gets a u64 sequence, converts it to byte array in little-endian, and then used the add_data function.
241    pub fn add_sequence(&mut self, sequence: u64) -> ScriptBuilderResult<&mut Self> {
242        self.add_u64(sequence)
243    }
244
245    /// Gets a u64 lock time or sequence, converts it to byte array in little-endian, and then used the add_data function.
246    fn add_u64(&mut self, val: u64) -> ScriptBuilderResult<&mut Self> {
247        let buffer: [u8; 8] = val.to_le_bytes();
248        let trimmed_size = 8 - buffer.iter().rev().position(|x| *x != 0u8).unwrap_or(8);
249        let trimmed = &buffer[0..trimmed_size];
250        self.add_data(trimmed)
251    }
252
253    /// Return [`HexViewBuilder`] for the script
254    pub fn hex_view_builder(&self) -> HexViewBuilder<'_> {
255        HexViewBuilder::new(&self.script)
256    }
257
258    /// Return ready to use [`HexView`] for the script
259    pub fn hex_view(&self, offset: usize, width: usize) -> HexView<'_> {
260        HexViewBuilder::new(&self.script).address_offset(offset).row_width(width).finish()
261    }
262}
263
264impl Default for ScriptBuilder {
265    fn default() -> Self {
266        Self::new()
267    }
268}
269
270#[cfg(test)]
271mod tests {
272    use super::*;
273    use std::iter::{once, repeat};
274
275    // Tests that pushing opcodes to a script via the ScriptBuilder API works as expected.
276    #[test]
277    fn test_add_op() {
278        struct Test {
279            name: &'static str,
280            opcodes: Vec<u8>,
281            expected: Vec<u8>,
282        }
283
284        let tests = vec![
285            Test { name: "push OP_FALSE", opcodes: vec![OpFalse], expected: vec![OpFalse] },
286            Test { name: "push OP_TRUE", opcodes: vec![OpTrue], expected: vec![OpTrue] },
287            Test { name: "push OP_0", opcodes: vec![Op0], expected: vec![Op0] },
288            Test { name: "push OP_1 OP_2", opcodes: vec![Op1, Op2], expected: vec![Op1, Op2] },
289            Test { name: "push OP_BLAKE2B OP_EQUAL", opcodes: vec![OpBlake2b, OpEqual], expected: vec![OpBlake2b, OpEqual] },
290        ];
291
292        // Run tests and individually add each op via AddOp.
293        for test in tests.iter() {
294            let mut builder = ScriptBuilder::new();
295            test.opcodes.iter().for_each(|opcode| {
296                builder.add_op(*opcode).expect("the script is canonical");
297            });
298            let result = builder.script();
299            assert_eq!(result, &test.expected, "{} wrong result using add_op", test.name);
300        }
301
302        // Run tests and bulk add ops via AddOps.
303        for test in tests.iter() {
304            let mut builder = ScriptBuilder::new();
305            let result = builder.add_ops(&test.opcodes).expect("the script is canonical").script();
306            assert_eq!(result, &test.expected, "{} wrong result using add_ops", test.name);
307        }
308    }
309
310    /// Tests that pushing signed integers to a script via the ScriptBuilder API works as expected.
311    #[test]
312    fn test_add_i64() {
313        struct Test {
314            name: &'static str,
315            val: i64,
316            expected: Vec<u8>,
317        }
318
319        let tests = vec![
320            Test { name: "push -1", val: -1, expected: vec![Op1Negate] },
321            Test { name: "push small int 0", val: 0, expected: vec![Op0] },
322            Test { name: "push small int 1", val: 1, expected: vec![Op1] },
323            Test { name: "push small int 2", val: 2, expected: vec![Op2] },
324            Test { name: "push small int 3", val: 3, expected: vec![Op3] },
325            Test { name: "push small int 4", val: 4, expected: vec![Op4] },
326            Test { name: "push small int 5", val: 5, expected: vec![Op5] },
327            Test { name: "push small int 6", val: 6, expected: vec![Op6] },
328            Test { name: "push small int 7", val: 7, expected: vec![Op7] },
329            Test { name: "push small int 8", val: 8, expected: vec![Op8] },
330            Test { name: "push small int 9", val: 9, expected: vec![Op9] },
331            Test { name: "push small int 10", val: 10, expected: vec![Op10] },
332            Test { name: "push small int 11", val: 11, expected: vec![Op11] },
333            Test { name: "push small int 12", val: 12, expected: vec![Op12] },
334            Test { name: "push small int 13", val: 13, expected: vec![Op13] },
335            Test { name: "push small int 14", val: 14, expected: vec![Op14] },
336            Test { name: "push small int 15", val: 15, expected: vec![Op15] },
337            Test { name: "push small int 16", val: 16, expected: vec![Op16] },
338            Test { name: "push 17", val: 17, expected: vec![OpData1, 0x11] },
339            Test { name: "push 65", val: 65, expected: vec![OpData1, 0x41] },
340            Test { name: "push 127", val: 127, expected: vec![OpData1, 0x7f] },
341            Test { name: "push 128", val: 128, expected: vec![OpData2, 0x80, 0] },
342            Test { name: "push 255", val: 255, expected: vec![OpData2, 0xff, 0] },
343            Test { name: "push 256", val: 256, expected: vec![OpData2, 0, 0x01] },
344            Test { name: "push 32767", val: 32767, expected: vec![OpData2, 0xff, 0x7f] },
345            Test { name: "push 32768", val: 32768, expected: vec![OpData3, 0, 0x80, 0] },
346            Test { name: "push -2", val: -2, expected: vec![OpData1, 0x82] },
347            Test { name: "push -3", val: -3, expected: vec![OpData1, 0x83] },
348            Test { name: "push -4", val: -4, expected: vec![OpData1, 0x84] },
349            Test { name: "push -5", val: -5, expected: vec![OpData1, 0x85] },
350            Test { name: "push -17", val: -17, expected: vec![OpData1, 0x91] },
351            Test { name: "push -65", val: -65, expected: vec![OpData1, 0xc1] },
352            Test { name: "push -127", val: -127, expected: vec![OpData1, 0xff] },
353            Test { name: "push -128", val: -128, expected: vec![OpData2, 0x80, 0x80] },
354            Test { name: "push -255", val: -255, expected: vec![OpData2, 0xff, 0x80] },
355            Test { name: "push -256", val: -256, expected: vec![OpData2, 0x00, 0x81] },
356            Test { name: "push -32767", val: -32767, expected: vec![OpData2, 0xff, 0xff] },
357            Test { name: "push -32768", val: -32768, expected: vec![OpData3, 0x00, 0x80, 0x80] },
358        ];
359
360        for test in tests {
361            let mut builder = ScriptBuilder::new();
362            let result = builder.add_i64(test.val).expect("the script is canonical").script();
363            assert_eq!(result, test.expected, "{} wrong result", test.name);
364        }
365    }
366
367    /// Tests that pushing data to a script via the ScriptBuilder API works as expected and conforms to BIP0062.
368    #[test]
369    fn test_add_data() {
370        struct Test {
371            name: &'static str,
372            data: Vec<u8>,
373            expected: ScriptBuilderResult<Vec<u8>>,
374            /// use add_data_unchecked instead of add_data
375            unchecked: bool,
376        }
377
378        let tests = vec![
379            // BIP0062: Pushing an empty byte sequence must use OP_0.
380            Test { name: "push empty byte sequence", data: vec![], expected: Ok(vec![Op0]), unchecked: false },
381            Test { name: "push 1 byte 0x00", data: vec![0x00], expected: Ok(vec![Op0]), unchecked: false },
382            // BIP0062: Pushing a 1-byte sequence of byte 0x01 through 0x10 must use OP_n.
383            Test { name: "push 1 byte 0x01", data: vec![0x01], expected: Ok(vec![Op1]), unchecked: false },
384            Test { name: "push 1 byte 0x02", data: vec![0x02], expected: Ok(vec![Op2]), unchecked: false },
385            Test { name: "push 1 byte 0x03", data: vec![0x03], expected: Ok(vec![Op3]), unchecked: false },
386            Test { name: "push 1 byte 0x04", data: vec![0x04], expected: Ok(vec![Op4]), unchecked: false },
387            Test { name: "push 1 byte 0x05", data: vec![0x05], expected: Ok(vec![Op5]), unchecked: false },
388            Test { name: "push 1 byte 0x06", data: vec![0x06], expected: Ok(vec![Op6]), unchecked: false },
389            Test { name: "push 1 byte 0x07", data: vec![0x07], expected: Ok(vec![Op7]), unchecked: false },
390            Test { name: "push 1 byte 0x08", data: vec![0x08], expected: Ok(vec![Op8]), unchecked: false },
391            Test { name: "push 1 byte 0x09", data: vec![0x09], expected: Ok(vec![Op9]), unchecked: false },
392            Test { name: "push 1 byte 0x0a", data: vec![0x0a], expected: Ok(vec![Op10]), unchecked: false },
393            Test { name: "push 1 byte 0x0b", data: vec![0x0b], expected: Ok(vec![Op11]), unchecked: false },
394            Test { name: "push 1 byte 0x0c", data: vec![0x0c], expected: Ok(vec![Op12]), unchecked: false },
395            Test { name: "push 1 byte 0x0d", data: vec![0x0d], expected: Ok(vec![Op13]), unchecked: false },
396            Test { name: "push 1 byte 0x0e", data: vec![0x0e], expected: Ok(vec![Op14]), unchecked: false },
397            Test { name: "push 1 byte 0x0f", data: vec![0x0f], expected: Ok(vec![Op15]), unchecked: false },
398            Test { name: "push 1 byte 0x10", data: vec![0x10], expected: Ok(vec![Op16]), unchecked: false },
399            // BIP0062: Pushing the byte 0x81 must use OP_1NEGATE.
400            Test { name: "push 1 byte 0x81", data: vec![0x81], expected: Ok(vec![Op1Negate]), unchecked: false },
401            // BIP0062: Pushing any other byte sequence up to 75 bytes must
402            // use the normal data push (opcode byte n, with n the number of
403            // bytes, followed n bytes of data being pushed).
404            Test { name: "push 1 byte 0x11", data: vec![0x11], expected: Ok(vec![OpData1, 0x11]), unchecked: false },
405            Test { name: "push 1 byte 0x80", data: vec![0x80], expected: Ok(vec![OpData1, 0x80]), unchecked: false },
406            Test { name: "push 1 byte 0x82", data: vec![0x82], expected: Ok(vec![OpData1, 0x82]), unchecked: false },
407            Test { name: "push 1 byte 0xff", data: vec![0xff], expected: Ok(vec![OpData1, 0xff]), unchecked: false },
408            Test {
409                name: "push data len 17",
410                data: vec![0x49; 17],
411                expected: Ok(once(OpData17).chain(repeat(0x49).take(17)).collect()),
412                unchecked: false,
413            },
414            Test {
415                name: "push data len 75",
416                data: vec![0x49; 75],
417                expected: Ok(once(OpData75).chain(repeat(0x49).take(75)).collect()),
418                unchecked: false,
419            },
420            // BIP0062: Pushing 76 to 255 bytes must use OP_PUSHDATA1.
421            Test {
422                name: "push data len 76",
423                data: vec![0x49; 76],
424                expected: Ok(once(OpPushData1).chain(once(76)).chain(repeat(0x49).take(76)).collect()),
425                unchecked: false,
426            },
427            Test {
428                name: "push data len 255",
429                data: vec![0x49; 255],
430                expected: Ok(once(OpPushData1).chain(once(255)).chain(repeat(0x49).take(255)).collect()),
431                unchecked: false,
432            },
433            // // BIP0062: Pushing 256 to 520 bytes must use OP_PUSHDATA2.
434            Test {
435                name: "push data len 256",
436                data: vec![0x49; 256],
437                expected: Ok(once(OpPushData2).chain([0, 1]).chain(repeat(0x49).take(256)).collect()),
438                unchecked: false,
439            },
440            Test {
441                name: "push data len 520",
442                data: vec![0x49; 520],
443                expected: Ok(once(OpPushData2).chain([8, 2]).chain(repeat(0x49).take(520)).collect()),
444                unchecked: false,
445            },
446            // BIP0062: OP_PUSHDATA4 can never be used, as pushes over 520
447            // bytes are not allowed, and those below can be done using
448            // other operators.
449            Test {
450                name: "push data len 521",
451                data: vec![0x49; 521],
452                expected: Err(ScriptBuilderError::ElementExceedsMaxSize(521)),
453                unchecked: false,
454            },
455            Test {
456                name: "push data len 32767 (canonical)",
457                data: vec![0x49; 32767],
458                expected: Err(ScriptBuilderError::DataRejected(32770)),
459                unchecked: false,
460            },
461            Test {
462                name: "push data len 65536 (canonical)",
463                data: vec![0x49; 65536],
464                expected: Err(ScriptBuilderError::DataRejected(65541)),
465                unchecked: false,
466            },
467            // // Additional tests for the add_data_unchecked function that
468            // // intentionally allows data pushes to exceed the limit for
469            // // testing purposes.
470
471            // 3-byte data push via OP_PUSHDATA_2.
472            Test {
473                name: "push data len 32767 (non-canonical)",
474                data: vec![0x49; 32767],
475                expected: Ok(once(OpPushData2).chain([255, 127]).chain(repeat(0x49).take(32767)).collect()),
476                unchecked: true,
477            },
478            // 5-byte data push via OP_PUSHDATA_4.
479            Test {
480                name: "push data len 65536 (non-canonical)",
481                data: vec![0x49; 65536],
482                expected: Ok(once(OpPushData4).chain([0, 0, 1, 0]).chain(repeat(0x49).take(65536)).collect()),
483                unchecked: true,
484            },
485        ];
486
487        for test in tests {
488            let mut builder = ScriptBuilder::new();
489            let result = match test.unchecked {
490                false => builder.add_data(&test.data).map(|x| x.drain()),
491                true => {
492                    builder.add_data_unchecked(&test.data);
493                    Ok(builder.drain())
494                }
495            };
496            assert_eq!(result, test.expected, "{} wrong result", test.name);
497        }
498    }
499
500    #[test]
501    fn test_u64() {
502        struct Test {
503            name: &'static str,
504            value: u64,
505            expected: Vec<u8>,
506        }
507
508        let tests = vec![
509            Test { name: "0x00", value: 0x00, expected: vec![Op0] },
510            Test { name: "0x01", value: 0x01, expected: vec![Op1] },
511            Test { name: "0xff", value: 0xff, expected: vec![OpData1, 0xff] },
512            Test { name: "0xffee", value: 0xffee, expected: vec![OpData2, 0xee, 0xff] },
513            Test { name: "0xffeedd", value: 0xffeedd, expected: vec![OpData3, 0xdd, 0xee, 0xff] },
514            Test { name: "0xffeeddcc", value: 0xffeeddcc, expected: vec![OpData4, 0xcc, 0xdd, 0xee, 0xff] },
515            Test { name: "0xffeeddccbb", value: 0xffeeddccbb, expected: vec![OpData5, 0xbb, 0xcc, 0xdd, 0xee, 0xff] },
516            Test { name: "0xffeeddccbbaa", value: 0xffeeddccbbaa, expected: vec![OpData6, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff] },
517            Test {
518                name: "0xffeeddccbbaa99",
519                value: 0xffeeddccbbaa99,
520                expected: vec![OpData7, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff],
521            },
522            Test {
523                name: "0xffeeddccbbaa9988",
524                value: 0xffeeddccbbaa9988,
525                expected: vec![OpData8, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff],
526            },
527            Test { name: "0xffffffffffffffff", value: u64::MAX, expected: once(OpData8).chain(repeat(0xff).take(8)).collect() },
528        ];
529
530        for test in tests {
531            let result = ScriptBuilder::new().add_u64(test.value).expect("the script is canonical").drain();
532            assert_eq!(result, test.expected, "{} wrong result", test.name);
533            let result = ScriptBuilder::new().add_lock_time(test.value).expect("the script is canonical").drain();
534            assert_eq!(result, test.expected, "{} wrong lock time result", test.name);
535            let result = ScriptBuilder::new().add_sequence(test.value).expect("the script is canonical").drain();
536            assert_eq!(result, test.expected, "{} wrong sequence result", test.name);
537        }
538    }
539
540    /// Ensures that all of the functions that can be used to add data to a script don't allow
541    /// the script to exceed the max allowed size.
542    #[test]
543    fn test_exceed_max_script_size() {
544        fn full_builder() -> ScriptBuilder {
545            let mut builder = ScriptBuilder::new();
546            builder.add_data_unchecked(&[0u8; MAX_SCRIPTS_SIZE - 3]);
547            builder
548        }
549        // Start off by constructing a max size script.
550        let mut builder = full_builder();
551        let original_result: Vec<u8> = Vec::from(builder.script());
552
553        // Ensure adding data that would exceed the maximum size of the script
554        // does not add the data.
555        let result = builder.add_data(&[0u8]).map(|_| ());
556        assert_eq!(
557            result,
558            Err(ScriptBuilderError::DataRejected(1)),
559            "adding data that would exceed the maximum size of the script must fail"
560        );
561        assert_eq!(builder.script(), &original_result, "unexpected modified script");
562
563        // Ensure adding an opcode that would exceed the maximum size of the
564        // script does not add the data.
565        let result = builder.add_op(Op0).map(|_| ());
566        assert_eq!(
567            result,
568            Err(ScriptBuilderError::OpCodeRejected(Op0)),
569            "adding an opcode that would exceed the maximum size of the script must fail"
570        );
571        assert_eq!(builder.script(), &original_result, "unexpected modified script");
572
573        // Ensure adding an opcode array that would exceed the maximum size of the
574        // script does not add the data.
575        let result = builder.add_ops(&[OpCheckSig]).map(|_| ());
576        assert_eq!(
577            result,
578            Err(ScriptBuilderError::OpCodesRejected(1)),
579            "adding an opcode array that would exceed the maximum size of the script must fail"
580        );
581        assert_eq!(builder.script(), &original_result, "unexpected modified script");
582
583        // Ensure adding an integer that would exceed the maximum size of the
584        // script does not add the data.
585        let result = builder.add_i64(0).map(|_| ());
586        assert_eq!(
587            result,
588            Err(ScriptBuilderError::IntegerRejected(0)),
589            "adding an integer that would exceed the maximum size of the script must fail"
590        );
591        assert_eq!(builder.script(), &original_result, "unexpected modified script");
592
593        // Ensure adding a lock time that would exceed the maximum size of the
594        // script does not add the data.
595        let result = builder.add_lock_time(0).map(|_| ());
596        assert_eq!(
597            result,
598            Err(ScriptBuilderError::DataRejected(1)),
599            "adding a lock time that would exceed the maximum size of the script must fail"
600        );
601        assert_eq!(builder.script(), &original_result, "unexpected modified script");
602
603        // Ensure adding a sequence that would exceed the maximum size of the
604        // script does not add the data.
605        let result = builder.add_sequence(0).map(|_| ());
606        assert_eq!(
607            result,
608            Err(ScriptBuilderError::DataRejected(1)),
609            "adding a sequence that would exceed the maximum size of the script must fail"
610        );
611        assert_eq!(builder.script(), &original_result, "unexpected modified script");
612    }
613}