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 some arbitrary data onto the stack.
240    ///
241    /// ## Panics
242    ///
243    /// The method panics if `data` length is greater or equal to
244    /// 0x100000000.
245    pub fn push_slice(&mut self, data: &[u8]) {
246        // Start with a PUSH opcode
247        match data.len() as u64 {
248            n if n < OP_PUSHDATA1 as u64 => {
249                self.push(n as u8);
250            }
251            n if n < 0x100 => {
252                self.push(OP_PUSHDATA1);
253                self.push(n as u8);
254            }
255            n if n < 0x10000 => {
256                self.push(OP_PUSHDATA2);
257                self.push((n % 0x100) as u8);
258                self.push((n / 0x100) as u8);
259            }
260            n if n < 0x100000000 => {
261                self.push(OP_PUSHDATA4);
262                self.push((n % 0x100) as u8);
263                self.push(((n / 0x100) % 0x100) as u8);
264                self.push(((n / 0x10000) % 0x100) as u8);
265                self.push((n / 0x1000000) as u8);
266            }
267            _ => panic!("tried to put a 4bn+ sized object into a script!"),
268        }
269        // Then push the raw bytes
270        self.extend(data);
271    }
272
273    #[inline]
274    pub(crate) fn push(&mut self, data: u8) { self.0.push(data).expect("script exceeds 4GB") }
275
276    #[inline]
277    pub(crate) fn extend(&mut self, data: &[u8]) {
278        self.0.extend(data.iter().copied()).expect("script exceeds 4GB")
279    }
280
281    /// Computes the sum of `len` and the length of an appropriate push
282    /// opcode.
283    pub fn len_for_slice(len: usize) -> usize {
284        len + match len {
285            0..=0x4b => 1,
286            0x4c..=0xff => 2,
287            0x100..=0xffff => 3,
288            // we don't care about oversized, the other fn will panic anyway
289            _ => 5,
290        }
291    }
292
293    pub fn len_var_int(&self) -> VarInt { VarInt(self.len() as u64) }
294
295    pub fn into_vec(self) -> Vec<u8> { self.0.release() }
296
297    pub(crate) fn as_var_int_bytes(&self) -> &VarIntBytes { &self.0 }
298}
299
300#[cfg(feature = "serde")]
301mod _serde {
302    use amplify::hex::{FromHex, ToHex};
303    use serde::de::Error;
304    use serde::{Deserialize, Deserializer, Serialize, Serializer};
305
306    use super::*;
307
308    impl Serialize for ScriptBytes {
309        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
310        where S: Serializer {
311            if serializer.is_human_readable() {
312                serializer.serialize_str(&self.to_hex())
313            } else {
314                serializer.serialize_bytes(self.as_slice())
315            }
316        }
317    }
318
319    impl<'de> Deserialize<'de> for ScriptBytes {
320        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
321        where D: Deserializer<'de> {
322            if deserializer.is_human_readable() {
323                String::deserialize(deserializer).and_then(|string| {
324                    Self::from_hex(&string).map_err(|_| D::Error::custom("wrong hex data"))
325                })
326            } else {
327                let bytes = Vec::<u8>::deserialize(deserializer)?;
328                Self::try_from(bytes)
329                    .map_err(|_| D::Error::custom("invalid script length exceeding 4GB"))
330            }
331        }
332    }
333}
334
335#[cfg(test)]
336mod test {
337    #![cfg_attr(coverage_nightly, coverage(off))]
338
339    use amplify::hex::ToHex;
340
341    use super::*;
342
343    #[test]
344    fn script_index() {
345        let mut script = ScriptPubkey::op_return(&[0u8; 40]);
346        assert_eq!(script[0], OP_RETURN);
347        assert_eq!(&script[..2], &[OP_RETURN, OP_PUSHBYTES_40]);
348        assert_eq!(&script[40..], &[0u8, 0u8]);
349        assert_eq!(&script[2..4], &[0u8, 0u8]);
350        assert_eq!(&script[2..=3], &[0u8, 0u8]);
351
352        script[0] = 0xFF;
353        script[..2].copy_from_slice(&[0xFF, 0xFF]);
354        script[40..].copy_from_slice(&[0xFF, 0xFF]);
355        script[2..4].copy_from_slice(&[0xFF, 0xFF]);
356        script[2..=3].copy_from_slice(&[0xFF, 0xFF]);
357
358        assert_eq!(script[0], 0xFF);
359        assert_eq!(&script[..2], &[0xFF, 0xFF]);
360        assert_eq!(&script[40..], &[0xFF, 0xFF]);
361        assert_eq!(&script[2..4], &[0xFF, 0xFF]);
362        assert_eq!(&script[2..=3], &[0xFF, 0xFF]);
363
364        assert_eq!(
365            &script.to_hex(),
366            "ffffffff000000000000000000000000000000000000000000000000000000000000000000000000ffff"
367        );
368    }
369}