bc/
script.rs

1// Bitcoin protocol consensus library.
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Written in 2019-2024 by
6//     Dr Maxim Orlovsky <orlovsky@lnp-bp.org>
7//
8// Copyright (C) 2019-2024 LNP/BP Standards Association. All rights reserved.
9//
10// Licensed under the Apache License, Version 2.0 (the "License");
11// you may not use this file except in compliance with the License.
12// You may obtain a copy of the License at
13//
14//     http://www.apache.org/licenses/LICENSE-2.0
15//
16// Unless required by applicable law or agreed to in writing, software
17// distributed under the License is distributed on an "AS IS" BASIS,
18// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19// See the License for the specific language governing permissions and
20// limitations under the License.
21
22use amplify::confinement;
23use amplify::confinement::Confined;
24
25use crate::opcodes::*;
26use crate::{ScriptHash, VarInt, VarIntBytes, WitnessVer, LIB_NAME_BITCOIN};
27
28#[derive(Wrapper, WrapperMut, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From, Default)]
29#[wrapper(Deref, AsSlice, Hex)]
30#[wrapper_mut(DerefMut, AsSliceMut)]
31#[derive(StrictType, StrictEncode, StrictDecode)]
32#[strict_type(lib = LIB_NAME_BITCOIN)]
33#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(transparent))]
34pub struct SigScript(ScriptBytes);
35
36impl TryFrom<Vec<u8>> for SigScript {
37    type Error = confinement::Error;
38    fn try_from(script_bytes: Vec<u8>) -> Result<Self, Self::Error> {
39        ScriptBytes::try_from(script_bytes).map(Self)
40    }
41}
42
43impl SigScript {
44    #[inline]
45    pub fn empty() -> Self { SigScript::default() }
46
47    #[inline]
48    pub fn new() -> Self { Self::default() }
49
50    #[inline]
51    pub fn with_capacity(capacity: usize) -> Self {
52        Self(ScriptBytes::from(Confined::with_capacity(capacity)))
53    }
54
55    /// Constructs script object assuming the script length is less than 4GB.
56    /// Panics otherwise.
57    #[inline]
58    pub fn from_checked(script_bytes: Vec<u8>) -> Self {
59        Self(ScriptBytes::from_checked(script_bytes))
60    }
61
62    #[inline]
63    pub fn as_script_bytes(&self) -> &ScriptBytes { &self.0 }
64}
65
66#[derive(Wrapper, WrapperMut, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From, Default)]
67#[wrapper(Deref, AsSlice, Hex)]
68#[wrapper_mut(DerefMut, AsSliceMut)]
69#[derive(StrictType, StrictEncode, StrictDecode)]
70#[strict_type(lib = LIB_NAME_BITCOIN)]
71#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(transparent))]
72pub struct ScriptPubkey(ScriptBytes);
73
74impl TryFrom<Vec<u8>> for ScriptPubkey {
75    type Error = confinement::Error;
76    fn try_from(script_bytes: Vec<u8>) -> Result<Self, Self::Error> {
77        ScriptBytes::try_from(script_bytes).map(Self)
78    }
79}
80
81impl ScriptPubkey {
82    #[inline]
83    pub fn new() -> Self { Self::default() }
84
85    #[inline]
86    pub fn with_capacity(capacity: usize) -> Self {
87        Self(ScriptBytes::from(Confined::with_capacity(capacity)))
88    }
89
90    /// Constructs script object assuming the script length is less than 4GB.
91    /// Panics otherwise.
92    #[inline]
93    pub fn from_checked(script_bytes: Vec<u8>) -> Self {
94        Self(ScriptBytes::from_checked(script_bytes))
95    }
96
97    pub fn p2pkh(hash: impl Into<[u8; 20]>) -> Self {
98        let mut script = Self::with_capacity(25);
99        script.push_opcode(OpCode::Dup);
100        script.push_opcode(OpCode::Hash160);
101        script.push_slice(&hash.into());
102        script.push_opcode(OpCode::EqualVerify);
103        script.push_opcode(OpCode::CheckSig);
104        script
105    }
106
107    pub fn p2sh(hash: impl Into<[u8; 20]>) -> Self {
108        let mut script = Self::with_capacity(23);
109        script.push_opcode(OpCode::Hash160);
110        script.push_slice(&hash.into());
111        script.push_opcode(OpCode::Equal);
112        script
113    }
114
115    pub fn op_return(data: &[u8]) -> Self {
116        let mut script = Self::with_capacity(ScriptBytes::len_for_slice(data.len()) + 1);
117        script.push_opcode(OpCode::Return);
118        script.push_slice(data);
119        script
120    }
121
122    /// Checks whether a script pubkey is a P2PKH output.
123    #[inline]
124    pub fn is_p2pkh(&self) -> bool {
125        self.0.len() == 25
126            && self.0[0] == OP_DUP
127            && self.0[1] == OP_HASH160
128            && self.0[2] == OP_PUSHBYTES_20
129            && self.0[23] == OP_EQUALVERIFY
130            && self.0[24] == OP_CHECKSIG
131    }
132
133    /// Checks whether a script pubkey is a P2SH output.
134    #[inline]
135    pub fn is_p2sh(&self) -> bool {
136        self.0.len() == 23
137            && self.0[0] == OP_HASH160
138            && self.0[1] == OP_PUSHBYTES_20
139            && self.0[22] == OP_EQUAL
140    }
141
142    #[inline]
143    pub fn is_op_return(&self) -> bool { !self.is_empty() && self[0] == OpCode::Return as u8 }
144
145    /// Adds a single opcode to the script.
146    #[inline]
147    pub fn push_opcode(&mut self, op_code: OpCode) { self.0.push(op_code as u8) }
148
149    #[inline]
150    pub fn as_script_bytes(&self) -> &ScriptBytes { &self.0 }
151}
152
153#[derive(Wrapper, WrapperMut, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From, Default)]
154#[wrapper(Deref, AsSlice, Hex)]
155#[wrapper_mut(DerefMut, AsSliceMut)]
156#[derive(StrictType, StrictEncode, StrictDecode)]
157#[strict_type(lib = LIB_NAME_BITCOIN)]
158#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(transparent))]
159pub struct RedeemScript(ScriptBytes);
160
161impl TryFrom<Vec<u8>> for RedeemScript {
162    type Error = confinement::Error;
163    fn try_from(script_bytes: Vec<u8>) -> Result<Self, Self::Error> {
164        ScriptBytes::try_from(script_bytes).map(Self)
165    }
166}
167
168impl RedeemScript {
169    #[inline]
170    pub fn new() -> Self { Self::default() }
171
172    #[inline]
173    pub fn with_capacity(capacity: usize) -> Self {
174        Self(ScriptBytes::from(Confined::with_capacity(capacity)))
175    }
176
177    /// Constructs script object assuming the script length is less than 4GB.
178    /// Panics otherwise.
179    #[inline]
180    pub fn from_checked(script_bytes: Vec<u8>) -> Self {
181        Self(ScriptBytes::from_checked(script_bytes))
182    }
183
184    pub fn p2sh_wpkh(hash: impl Into<[u8; 20]>) -> Self {
185        Self::with_witness_program_unchecked(WitnessVer::V0, &hash.into())
186    }
187
188    pub fn p2sh_wsh(hash: impl Into<[u8; 32]>) -> Self {
189        Self::with_witness_program_unchecked(WitnessVer::V0, &hash.into())
190    }
191
192    fn with_witness_program_unchecked(ver: WitnessVer, prog: &[u8]) -> Self {
193        let mut script = Self::with_capacity(ScriptBytes::len_for_slice(prog.len()) + 2);
194        script.push_opcode(ver.op_code());
195        script.push_slice(prog);
196        script
197    }
198
199    pub fn is_p2sh_wpkh(&self) -> bool {
200        self.len() == 22 && self[0] == WitnessVer::V0.op_code() as u8 && self[1] == OP_PUSHBYTES_20
201    }
202
203    pub fn is_p2sh_wsh(&self) -> bool {
204        self.len() == 34 && self[0] == WitnessVer::V0.op_code() as u8 && self[1] == OP_PUSHBYTES_32
205    }
206
207    /// Adds a single opcode to the script.
208    #[inline]
209    pub fn push_opcode(&mut self, op_code: OpCode) { self.0.push(op_code as u8); }
210
211    pub fn to_script_pubkey(&self) -> ScriptPubkey { ScriptPubkey::p2sh(ScriptHash::from(self)) }
212
213    #[inline]
214    pub fn as_script_bytes(&self) -> &ScriptBytes { &self.0 }
215}
216
217#[derive(Wrapper, WrapperMut, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Default, Debug, From)]
218#[wrapper(Deref, AsSlice, Hex)]
219#[wrapper_mut(DerefMut, AsSliceMut)]
220#[derive(StrictType, StrictEncode, StrictDecode)]
221#[strict_type(lib = LIB_NAME_BITCOIN)]
222pub struct ScriptBytes(VarIntBytes);
223
224impl TryFrom<Vec<u8>> for ScriptBytes {
225    type Error = confinement::Error;
226    fn try_from(script_bytes: Vec<u8>) -> Result<Self, Self::Error> {
227        Confined::try_from(script_bytes).map(Self)
228    }
229}
230
231impl ScriptBytes {
232    /// Constructs script object assuming the script length is less than 4GB.
233    /// Panics otherwise.
234    #[inline]
235    pub fn from_checked(script_bytes: Vec<u8>) -> Self {
236        Self(Confined::try_from(script_bytes).expect("script exceeding 4GB"))
237    }
238
239    /// Adds instructions to push a number from 0 to 16 to the stack.
240    ///
241    /// # Panics
242    ///
243    /// If the number is greater than 16
244    pub fn push_num(&mut self, num: i64) {
245        // Taken from rust-bitcoin
246        //
247        // Encodes an integer in script(minimal CScriptNum) format.
248        //
249        // Writes bytes into the buffer and returns the number of bytes written.
250        //
251        // Note that `write_scriptint`/`read_scriptint` do not roundtrip if the value written
252        // requires more than 4 bytes, this is in line with Bitcoin Core (see
253        // [`CScriptNum::serialize`]).
254        //
255        // [`CScriptNum::serialize`]: <https://github.com/bitcoin/bitcoin/blob/8ae2808a4354e8dcc697f76bacc5e2f2befe9220/src/script/script.h#L345>
256        pub fn write_scriptint(out: &mut [u8; 8], n: i64) -> usize {
257            let mut len = 0;
258            if n == 0 {
259                return len;
260            }
261
262            let neg = n < 0;
263
264            let mut abs = n.unsigned_abs();
265            while abs > 0xFF {
266                out[len] = (abs & 0xFF) as u8;
267                len += 1;
268                abs >>= 8;
269            }
270            // If the number's value causes the sign bit to be set, we need an extra
271            // byte to get the correct value and correct sign bit
272            if abs & 0x80 != 0 {
273                out[len] = abs as u8;
274                len += 1;
275                out[len] = if neg { 0x80u8 } else { 0u8 };
276                len += 1;
277            }
278            // Otherwise we just set the sign bit ourselves
279            else {
280                abs |= if neg { 0x80 } else { 0 };
281                out[len] = abs as u8;
282                len += 1;
283            }
284            len
285        }
286
287        match num {
288            -1 => self.push(OP_PUSHNUM_NEG1),
289            0 => self.push(OP_PUSHBYTES_0),
290            1..=16 => self.push(num as u8 + (OP_PUSHNUM_1 - 1)),
291            _ => {
292                let mut buf = [0u8; 8];
293                let len = write_scriptint(&mut buf, num);
294                self.push_slice(&buf[..len]);
295            }
296        };
297    }
298
299    /// Adds instructions to push some arbitrary data onto the stack.
300    ///
301    /// # Panics
302    ///
303    /// The method panics if `data` length is greater or equal to 0x100000000.
304    pub fn push_slice(&mut self, data: &[u8]) {
305        // Start with a PUSH opcode
306        match data.len() as u64 {
307            n if n < OP_PUSHDATA1 as u64 => {
308                self.push(n as u8);
309            }
310            n if n < 0x100 => {
311                self.push(OP_PUSHDATA1);
312                self.push(n as u8);
313            }
314            n if n < 0x10000 => {
315                self.push(OP_PUSHDATA2);
316                self.push((n % 0x100) as u8);
317                self.push((n / 0x100) as u8);
318            }
319            n if n < 0x100000000 => {
320                self.push(OP_PUSHDATA4);
321                self.push((n % 0x100) as u8);
322                self.push(((n / 0x100) % 0x100) as u8);
323                self.push(((n / 0x10000) % 0x100) as u8);
324                self.push((n / 0x1000000) as u8);
325            }
326            _ => panic!("tried to put a 4bn+ sized object into a script!"),
327        }
328        // Then push the raw bytes
329        self.extend(data);
330    }
331
332    #[inline]
333    pub(crate) fn push(&mut self, data: u8) { self.0.push(data).expect("script exceeds 4GB") }
334
335    #[inline]
336    pub(crate) fn extend(&mut self, data: &[u8]) {
337        self.0.extend(data.iter().copied()).expect("script exceeds 4GB")
338    }
339
340    /// Computes the sum of `len` and the length of an appropriate push
341    /// opcode.
342    pub fn len_for_slice(len: usize) -> usize {
343        len + match len {
344            0..=0x4b => 1,
345            0x4c..=0xff => 2,
346            0x100..=0xffff => 3,
347            // we don't care about oversized, the other fn will panic anyway
348            _ => 5,
349        }
350    }
351
352    pub fn len_var_int(&self) -> VarInt { VarInt(self.len() as u64) }
353
354    pub fn into_vec(self) -> Vec<u8> { self.0.release() }
355
356    pub(crate) fn as_var_int_bytes(&self) -> &VarIntBytes { &self.0 }
357}
358
359#[cfg(feature = "serde")]
360mod _serde {
361    use amplify::hex::{FromHex, ToHex};
362    use serde::de::Error;
363    use serde::{Deserialize, Deserializer, Serialize, Serializer};
364
365    use super::*;
366
367    impl Serialize for ScriptBytes {
368        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
369        where S: Serializer {
370            if serializer.is_human_readable() {
371                serializer.serialize_str(&self.to_hex())
372            } else {
373                serializer.serialize_bytes(self.as_slice())
374            }
375        }
376    }
377
378    impl<'de> Deserialize<'de> for ScriptBytes {
379        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
380        where D: Deserializer<'de> {
381            if deserializer.is_human_readable() {
382                String::deserialize(deserializer).and_then(|string| {
383                    Self::from_hex(&string).map_err(|_| D::Error::custom("wrong hex data"))
384                })
385            } else {
386                let bytes = Vec::<u8>::deserialize(deserializer)?;
387                Self::try_from(bytes)
388                    .map_err(|_| D::Error::custom("invalid script length exceeding 4GB"))
389            }
390        }
391    }
392}
393
394#[cfg(test)]
395mod test {
396    #![cfg_attr(coverage_nightly, coverage(off))]
397
398    use amplify::hex::ToHex;
399
400    use super::*;
401
402    #[test]
403    fn script_index() {
404        let mut script = ScriptPubkey::op_return(&[0u8; 40]);
405        assert_eq!(script[0], OP_RETURN);
406        assert_eq!(&script[..2], &[OP_RETURN, OP_PUSHBYTES_40]);
407        assert_eq!(&script[40..], &[0u8, 0u8]);
408        assert_eq!(&script[2..4], &[0u8, 0u8]);
409        assert_eq!(&script[2..=3], &[0u8, 0u8]);
410
411        script[0] = 0xFF;
412        script[..2].copy_from_slice(&[0xFF, 0xFF]);
413        script[40..].copy_from_slice(&[0xFF, 0xFF]);
414        script[2..4].copy_from_slice(&[0xFF, 0xFF]);
415        script[2..=3].copy_from_slice(&[0xFF, 0xFF]);
416
417        assert_eq!(script[0], 0xFF);
418        assert_eq!(&script[..2], &[0xFF, 0xFF]);
419        assert_eq!(&script[40..], &[0xFF, 0xFF]);
420        assert_eq!(&script[2..4], &[0xFF, 0xFF]);
421        assert_eq!(&script[2..=3], &[0xFF, 0xFF]);
422
423        assert_eq!(
424            &script.to_hex(),
425            "ffffffff000000000000000000000000000000000000000000000000000000000000000000000000ffff"
426        );
427    }
428}