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(
34    feature = "serde",
35    derive(Serialize, Deserialize),
36    serde(crate = "serde_crate", transparent)
37)]
38pub struct SigScript(ScriptBytes);
39
40impl TryFrom<Vec<u8>> for SigScript {
41    type Error = confinement::Error;
42    fn try_from(script_bytes: Vec<u8>) -> Result<Self, Self::Error> {
43        ScriptBytes::try_from(script_bytes).map(Self)
44    }
45}
46
47impl SigScript {
48    #[inline]
49    pub fn empty() -> Self { SigScript::default() }
50
51    #[inline]
52    pub fn new() -> Self { Self::default() }
53
54    #[inline]
55    pub fn with_capacity(capacity: usize) -> Self {
56        Self(ScriptBytes::from(Confined::with_capacity(capacity)))
57    }
58
59    /// Constructs script object assuming the script length is less than 4GB.
60    /// Panics otherwise.
61    #[inline]
62    pub fn from_unsafe(script_bytes: Vec<u8>) -> Self {
63        Self(ScriptBytes::from_unsafe(script_bytes))
64    }
65
66    #[inline]
67    pub fn as_script_bytes(&self) -> &ScriptBytes { &self.0 }
68}
69
70#[derive(Wrapper, WrapperMut, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From, Default)]
71#[wrapper(Deref, AsSlice, Hex)]
72#[wrapper_mut(DerefMut, AsSliceMut)]
73#[derive(StrictType, StrictEncode, StrictDecode)]
74#[strict_type(lib = LIB_NAME_BITCOIN)]
75#[cfg_attr(
76    feature = "serde",
77    derive(Serialize, Deserialize),
78    serde(crate = "serde_crate", transparent)
79)]
80pub struct ScriptPubkey(ScriptBytes);
81
82impl TryFrom<Vec<u8>> for ScriptPubkey {
83    type Error = confinement::Error;
84    fn try_from(script_bytes: Vec<u8>) -> Result<Self, Self::Error> {
85        ScriptBytes::try_from(script_bytes).map(Self)
86    }
87}
88
89impl ScriptPubkey {
90    #[inline]
91    pub fn new() -> Self { Self::default() }
92
93    #[inline]
94    pub fn with_capacity(capacity: usize) -> Self {
95        Self(ScriptBytes::from(Confined::with_capacity(capacity)))
96    }
97
98    /// Constructs script object assuming the script length is less than 4GB.
99    /// Panics otherwise.
100    #[inline]
101    pub fn from_unsafe(script_bytes: Vec<u8>) -> Self {
102        Self(ScriptBytes::from_unsafe(script_bytes))
103    }
104
105    pub fn p2pkh(hash: impl Into<[u8; 20]>) -> Self {
106        let mut script = Self::with_capacity(25);
107        script.push_opcode(OpCode::Dup);
108        script.push_opcode(OpCode::Hash160);
109        script.push_slice(&hash.into());
110        script.push_opcode(OpCode::EqualVerify);
111        script.push_opcode(OpCode::CheckSig);
112        script
113    }
114
115    pub fn p2sh(hash: impl Into<[u8; 20]>) -> Self {
116        let mut script = Self::with_capacity(23);
117        script.push_opcode(OpCode::Hash160);
118        script.push_slice(&hash.into());
119        script.push_opcode(OpCode::Equal);
120        script
121    }
122
123    pub fn op_return(data: &[u8]) -> Self {
124        let mut script = Self::with_capacity(ScriptBytes::len_for_slice(data.len()) + 1);
125        script.push_opcode(OpCode::Return);
126        script.push_slice(data);
127        script
128    }
129
130    /// Checks whether a script pubkey is a P2PKH output.
131    #[inline]
132    pub fn is_p2pkh(&self) -> bool {
133        self.0.len() == 25
134            && self.0[0] == OP_DUP
135            && self.0[1] == OP_HASH160
136            && self.0[2] == OP_PUSHBYTES_20
137            && self.0[23] == OP_EQUALVERIFY
138            && self.0[24] == OP_CHECKSIG
139    }
140
141    /// Checks whether a script pubkey is a P2SH output.
142    #[inline]
143    pub fn is_p2sh(&self) -> bool {
144        self.0.len() == 23
145            && self.0[0] == OP_HASH160
146            && self.0[1] == OP_PUSHBYTES_20
147            && self.0[22] == OP_EQUAL
148    }
149
150    #[inline]
151    pub fn is_op_return(&self) -> bool { !self.is_empty() && self[0] == OpCode::Return as u8 }
152
153    /// Adds a single opcode to the script.
154    #[inline]
155    pub fn push_opcode(&mut self, op_code: OpCode) { self.0.push(op_code as u8) }
156
157    #[inline]
158    pub fn as_script_bytes(&self) -> &ScriptBytes { &self.0 }
159}
160
161#[derive(Wrapper, WrapperMut, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From, Default)]
162#[wrapper(Deref, AsSlice, Hex)]
163#[wrapper_mut(DerefMut, AsSliceMut)]
164#[derive(StrictType, StrictEncode, StrictDecode)]
165#[strict_type(lib = LIB_NAME_BITCOIN)]
166#[cfg_attr(
167    feature = "serde",
168    derive(Serialize, Deserialize),
169    serde(crate = "serde_crate", transparent)
170)]
171pub struct RedeemScript(ScriptBytes);
172
173impl TryFrom<Vec<u8>> for RedeemScript {
174    type Error = confinement::Error;
175    fn try_from(script_bytes: Vec<u8>) -> Result<Self, Self::Error> {
176        ScriptBytes::try_from(script_bytes).map(Self)
177    }
178}
179
180impl RedeemScript {
181    #[inline]
182    pub fn new() -> Self { Self::default() }
183
184    #[inline]
185    pub fn with_capacity(capacity: usize) -> Self {
186        Self(ScriptBytes::from(Confined::with_capacity(capacity)))
187    }
188
189    /// Constructs script object assuming the script length is less than 4GB.
190    /// Panics otherwise.
191    #[inline]
192    pub fn from_unsafe(script_bytes: Vec<u8>) -> Self {
193        Self(ScriptBytes::from_unsafe(script_bytes))
194    }
195
196    pub fn p2sh_wpkh(hash: impl Into<[u8; 20]>) -> Self {
197        Self::with_witness_program_unchecked(WitnessVer::V0, &hash.into())
198    }
199
200    pub fn p2sh_wsh(hash: impl Into<[u8; 32]>) -> Self {
201        Self::with_witness_program_unchecked(WitnessVer::V0, &hash.into())
202    }
203
204    fn with_witness_program_unchecked(ver: WitnessVer, prog: &[u8]) -> Self {
205        let mut script = Self::with_capacity(ScriptBytes::len_for_slice(prog.len()) + 2);
206        script.push_opcode(ver.op_code());
207        script.push_slice(prog);
208        script
209    }
210
211    pub fn is_p2sh_wpkh(&self) -> bool {
212        self.len() == 22 && self[0] == WitnessVer::V0.op_code() as u8 && self[1] == OP_PUSHBYTES_20
213    }
214
215    pub fn is_p2sh_wsh(&self) -> bool {
216        self.len() == 34 && self[0] == WitnessVer::V0.op_code() as u8 && self[1] == OP_PUSHBYTES_32
217    }
218
219    /// Adds a single opcode to the script.
220    #[inline]
221    pub fn push_opcode(&mut self, op_code: OpCode) { self.0.push(op_code as u8); }
222
223    pub fn to_script_pubkey(&self) -> ScriptPubkey { ScriptPubkey::p2sh(ScriptHash::from(self)) }
224
225    #[inline]
226    pub fn as_script_bytes(&self) -> &ScriptBytes { &self.0 }
227}
228
229#[derive(Wrapper, WrapperMut, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Default, Debug, From)]
230#[wrapper(Deref, AsSlice, Hex)]
231#[wrapper_mut(DerefMut, AsSliceMut)]
232#[derive(StrictType, StrictEncode, StrictDecode)]
233#[strict_type(lib = LIB_NAME_BITCOIN)]
234pub struct ScriptBytes(VarIntBytes);
235
236impl TryFrom<Vec<u8>> for ScriptBytes {
237    type Error = confinement::Error;
238    fn try_from(script_bytes: Vec<u8>) -> Result<Self, Self::Error> {
239        Confined::try_from(script_bytes).map(Self)
240    }
241}
242
243impl ScriptBytes {
244    /// Constructs script object assuming the script length is less than 4GB.
245    /// Panics otherwise.
246    #[inline]
247    pub fn from_unsafe(script_bytes: Vec<u8>) -> Self {
248        Self(Confined::try_from(script_bytes).expect("script exceeding 4GB"))
249    }
250
251    /// Adds instructions to push some arbitrary data onto the stack.
252    ///
253    /// ## Panics
254    ///
255    /// The method panics if `data` length is greater or equal to
256    /// 0x100000000.
257    pub fn push_slice(&mut self, data: &[u8]) {
258        // Start with a PUSH opcode
259        match data.len() as u64 {
260            n if n < OP_PUSHDATA1 as u64 => {
261                self.push(n as u8);
262            }
263            n if n < 0x100 => {
264                self.push(OP_PUSHDATA1);
265                self.push(n as u8);
266            }
267            n if n < 0x10000 => {
268                self.push(OP_PUSHDATA2);
269                self.push((n % 0x100) as u8);
270                self.push((n / 0x100) as u8);
271            }
272            n if n < 0x100000000 => {
273                self.push(OP_PUSHDATA4);
274                self.push((n % 0x100) as u8);
275                self.push(((n / 0x100) % 0x100) as u8);
276                self.push(((n / 0x10000) % 0x100) as u8);
277                self.push((n / 0x1000000) as u8);
278            }
279            _ => panic!("tried to put a 4bn+ sized object into a script!"),
280        }
281        // Then push the raw bytes
282        self.extend(data);
283    }
284
285    #[inline]
286    pub(crate) fn push(&mut self, data: u8) { self.0.push(data).expect("script exceeds 4GB") }
287
288    #[inline]
289    pub(crate) fn extend(&mut self, data: &[u8]) {
290        self.0.extend(data.iter().copied()).expect("script exceeds 4GB")
291    }
292
293    /// Computes the sum of `len` and the length of an appropriate push
294    /// opcode.
295    pub fn len_for_slice(len: usize) -> usize {
296        len + match len {
297            0..=0x4b => 1,
298            0x4c..=0xff => 2,
299            0x100..=0xffff => 3,
300            // we don't care about oversized, the other fn will panic anyway
301            _ => 5,
302        }
303    }
304
305    pub fn len_var_int(&self) -> VarInt { VarInt(self.len() as u64) }
306
307    pub fn into_vec(self) -> Vec<u8> { self.0.release() }
308
309    pub(crate) fn as_var_int_bytes(&self) -> &VarIntBytes { &self.0 }
310}
311
312#[cfg(feature = "serde")]
313mod _serde {
314    use amplify::hex::{FromHex, ToHex};
315    use serde::{Deserialize, Serialize};
316    use serde_crate::de::Error;
317    use serde_crate::{Deserializer, Serializer};
318
319    use super::*;
320
321    impl Serialize for ScriptBytes {
322        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
323        where S: Serializer {
324            if serializer.is_human_readable() {
325                serializer.serialize_str(&self.to_hex())
326            } else {
327                serializer.serialize_bytes(self.as_slice())
328            }
329        }
330    }
331
332    impl<'de> Deserialize<'de> for ScriptBytes {
333        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
334        where D: Deserializer<'de> {
335            if deserializer.is_human_readable() {
336                String::deserialize(deserializer).and_then(|string| {
337                    Self::from_hex(&string).map_err(|_| D::Error::custom("wrong hex data"))
338                })
339            } else {
340                let bytes = Vec::<u8>::deserialize(deserializer)?;
341                Self::try_from(bytes)
342                    .map_err(|_| D::Error::custom("invalid script length exceeding 4GB"))
343            }
344        }
345    }
346}
347
348#[cfg(test)]
349mod test {
350    use amplify::hex::ToHex;
351
352    use super::*;
353
354    #[test]
355    fn script_index() {
356        let mut script = ScriptPubkey::op_return(&[0u8; 40]);
357        assert_eq!(script[0], OP_RETURN);
358        assert_eq!(&script[..2], &[OP_RETURN, OP_PUSHBYTES_40]);
359        assert_eq!(&script[40..], &[0u8, 0u8]);
360        assert_eq!(&script[2..4], &[0u8, 0u8]);
361        assert_eq!(&script[2..=3], &[0u8, 0u8]);
362
363        script[0] = 0xFF;
364        script[..2].copy_from_slice(&[0xFF, 0xFF]);
365        script[40..].copy_from_slice(&[0xFF, 0xFF]);
366        script[2..4].copy_from_slice(&[0xFF, 0xFF]);
367        script[2..=3].copy_from_slice(&[0xFF, 0xFF]);
368
369        assert_eq!(script[0], 0xFF);
370        assert_eq!(&script[..2], &[0xFF, 0xFF]);
371        assert_eq!(&script[40..], &[0xFF, 0xFF]);
372        assert_eq!(&script[2..4], &[0xFF, 0xFF]);
373        assert_eq!(&script[2..=3], &[0xFF, 0xFF]);
374
375        assert_eq!(
376            &script.to_hex(),
377            "ffffffff000000000000000000000000000000000000000000000000000000000000000000000000ffff"
378        );
379    }
380}