Skip to main content

bitcoin_primitives/script/
builder.rs

1// SPDX-License-Identifier: CC0-1.0
2
3use core::fmt;
4
5use super::{PushBytes, Script, ScriptBuf};
6use crate::opcodes::Opcode;
7use crate::prelude::Vec;
8
9/// An Object which can be used to construct a script piece by piece.
10///
11/// # Panics
12///
13/// `Builder` is backed by [`ScriptBuf`] and inherits its panic behavior. This means that
14/// attempting to construct scripts larger than `isize::MAX` bytes will panic.
15#[derive(PartialEq, Eq, Clone)]
16pub struct Builder<T>(ScriptBuf<T>);
17
18impl<T> Builder<T> {
19    /// Constructs a new empty script.
20    #[inline]
21    pub const fn new() -> Self { Self(ScriptBuf::new()) }
22
23    /// Adds instructions to push some arbitrary data onto the stack.
24    ///
25    /// If the data can be exactly produced by a numeric opcode, that opcode
26    /// will be used, since its behavior is equivalent but will not violate minimality
27    /// rules. To avoid this, use [`Builder::push_slice_non_minimal`] which will always
28    /// use a push opcode.
29    ///
30    /// However, this method does *not* enforce any numeric minimality rules.
31    /// If your pushes should be interpreted as numbers, ensure your input does
32    /// not have any leading zeros. In particular, the number 0 should be encoded
33    /// as an empty string rather than as a single 0 byte.
34    #[must_use]
35    pub fn push_slice<D: AsRef<PushBytes>>(mut self, data: D) -> Self {
36        self.0.push_slice(data);
37        self
38    }
39
40    /// Adds instructions to push some arbitrary data onto the stack without minimality.
41    ///
42    /// Standardness rules require push minimality according to [CheckMinimalPush] of core.
43    ///
44    /// [CheckMinimalPush]: <https://github.com/bitcoin/bitcoin/blob/99a4ddf5ab1b3e514d08b90ad8565827fda7b63b/src/script/script.cpp#L366>
45    #[must_use]
46    pub fn push_slice_non_minimal<D: AsRef<PushBytes>>(mut self, data: D) -> Self {
47        self.0.push_slice_non_minimal(data);
48        self
49    }
50
51    /// Adds a single opcode to the script.
52    #[must_use]
53    pub fn push_opcode(mut self, data: Opcode) -> Self {
54        self.0.push_opcode(data);
55        self
56    }
57
58    /// Converts the `Builder` into `ScriptBuf`.
59    pub fn into_script(self) -> ScriptBuf<T> { self.0 }
60
61    /// Returns the internal script
62    pub fn as_script(&self) -> &Script<T> { &self.0 }
63}
64
65impl<T> Default for Builder<T> {
66    fn default() -> Self { Self::new() }
67}
68
69/// Constructs a new builder from an existing vector.
70impl<T> From<Vec<u8>> for Builder<T> {
71    fn from(v: Vec<u8>) -> Self {
72        let script = ScriptBuf::from(v);
73        Self(script)
74    }
75}
76
77impl<T> fmt::Display for Builder<T> {
78    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) }
79}
80
81impl<T> fmt::Debug for Builder<T> {
82    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
83        f.debug_tuple("Builder").field(&self.0).finish()
84    }
85}
86
87#[cfg(test)]
88mod tests {
89    use alloc::{format, vec};
90
91    use super::Builder;
92    use crate::script::{PushBytes, ScriptSigTag as Tag};
93
94    #[test]
95    fn push_slice_minimal() {
96        let script = Builder::<Tag>::new().push_slice([0x81]).into_script();
97        assert_eq!(script.as_bytes(), &[0x4f]);
98
99        for n in 1u8..=16 {
100            let script = Builder::<Tag>::new().push_slice([n]).into_script();
101            assert_eq!(script.as_bytes(), &[0x50 + n]);
102        }
103
104        let script = Builder::<Tag>::new().push_slice([0u8]).into_script();
105        assert_eq!(script.as_bytes(), &[1, 0]);
106        let script = Builder::<Tag>::new().push_slice([17u8]).into_script();
107        assert_eq!(script.as_bytes(), &[1, 17]);
108        let script = Builder::<Tag>::new().push_slice(b"NRA4VR").into_script();
109        assert_eq!(script.as_bytes(), &[6, b'N', b'R', b'A', b'4', b'V', b'R']);
110    }
111
112    #[test]
113    fn push_slice_non_minimal() {
114        let script = Builder::<Tag>::new().push_slice_non_minimal([0x81]).into_script();
115        assert_eq!(script.as_bytes(), &[1, 0x81]);
116
117        let script = Builder::<Tag>::new().push_slice_non_minimal([1u8]).into_script();
118        assert_eq!(script.as_bytes(), &[1, 1]);
119    }
120
121    #[test]
122    fn push_slice_pushdata1_and_pushdata2() {
123        let script = Builder::<Tag>::new()
124            .push_slice(<&PushBytes>::try_from([0xab; 0x4b].as_slice()).unwrap())
125            .into_script();
126        assert_eq!(script.as_bytes()[0], 0x4b);
127        assert_eq!(script.len(), 1 + 0x4b);
128
129        let script = Builder::<Tag>::new()
130            .push_slice(<&PushBytes>::try_from([0xab; 0x4c].as_slice()).unwrap())
131            .into_script();
132        assert_eq!(&script.as_bytes()[..2], &[0x4c, 0x4c]);
133        assert_eq!(script.len(), 2 + 0x4c);
134
135        let script = Builder::<Tag>::new()
136            .push_slice(<&PushBytes>::try_from([0xab; 0xff].as_slice()).unwrap())
137            .into_script();
138        assert_eq!(&script.as_bytes()[..2], &[0x4c, 0xff]);
139        assert_eq!(script.len(), 2 + 0xff);
140
141        let script = Builder::<Tag>::new()
142            .push_slice(<&PushBytes>::try_from([0xab; 0x100].as_slice()).unwrap())
143            .into_script();
144        assert_eq!(&script.as_bytes()[..3], &[0x4d, 0x00, 0x01]);
145        assert_eq!(script.len(), 3 + 0x100);
146
147        let script = Builder::<Tag>::new()
148            .push_slice(<&PushBytes>::try_from([0xab; 0x102].as_slice()).unwrap())
149            .into_script();
150        assert_eq!(&script.as_bytes()[..3], &[0x4d, 0x02, 0x01]);
151        assert_eq!(script.len(), 3 + 0x102);
152
153        let script = Builder::<Tag>::new()
154            .push_slice(<&PushBytes>::try_from(vec![0xab; 0xffff].as_slice()).unwrap())
155            .into_script();
156        assert_eq!(&script.as_bytes()[..3], &[0x4d, 0xff, 0xff]);
157        assert_eq!(script.len(), 3 + 0xffff);
158    }
159
160    #[test]
161    #[cfg_attr(miri, ignore)]
162    fn push_slice_pushdata4_boundary() {
163        let script = Builder::<Tag>::new()
164            .push_slice(<&PushBytes>::try_from(vec![0u8; 0x10000].as_slice()).unwrap())
165            .into_script();
166        assert_eq!(&script.as_bytes()[..5], &[0x4e, 0x00, 0x00, 0x01, 0x00]);
167        assert_eq!(script.len(), 5 + 0x10000);
168    }
169
170    #[test]
171    #[cfg(target_pointer_width = "64")]
172    #[cfg_attr(miri, ignore)]
173    fn push_slice_pushdata4_length_bytes() {
174        let len = 0x0102_0304;
175        let script = Builder::<Tag>::new()
176            .push_slice(<&PushBytes>::try_from(vec![0u8; len].as_slice()).unwrap())
177            .into_script();
178        assert_eq!(&script.as_bytes()[..5], &[0x4e, 0x04, 0x03, 0x02, 0x01]);
179        assert_eq!(script.len(), 5 + len);
180    }
181
182    #[test]
183    fn from_vec() {
184        let script = Builder::<Tag>::from(vec![0xac, 0x51]).into_script();
185        assert_eq!(script.as_bytes(), &[0xac, 0x51]);
186    }
187
188    #[test]
189    fn display_delegates_to_script() {
190        let builder = Builder::<Tag>::from(vec![0x51, 0x52]);
191        let displayed = format!("{}", builder);
192        assert!(!displayed.is_empty());
193        assert_eq!(displayed, format!("{}", builder.as_script()));
194    }
195}