Skip to main content

tidecoin_primitives/script/
borrowed.rs

1// SPDX-License-Identifier: CC0-1.0
2
3#[cfg(all(feature = "hex", feature = "alloc"))]
4use alloc::string::String;
5use core::marker::PhantomData;
6use core::ops::{
7    Bound, Index, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive,
8};
9
10#[cfg(feature = "arbitrary")]
11use arbitrary::{Arbitrary, Unstructured};
12use encoding::{BytesEncoder, CompactSizeEncoder, Encodable, Encoder2};
13
14use super::{InstructionIndices, Instructions, ScriptBuf};
15use crate::opcodes::all::{
16    OP_CHECKMULTISIG, OP_CHECKMULTISIGVERIFY, OP_CHECKSIG, OP_CHECKSIGVERIFY,
17};
18use crate::prelude::{Box, ToOwned, Vec};
19
20internals::transparent_newtype! {
21    /// Tidecoin script slice.
22    ///
23    /// *[See also the `script` module](super).*
24    ///
25    /// `Script` is a script slice, the most primitive script type. It's usually seen in its borrowed
26    /// form `&Script`. It is always encoded as a series of bytes representing the opcodes and data
27    /// pushes.
28    ///
29    /// # Validity
30    ///
31    /// `Script` does not have any validity invariants - it's essentially just a marked slice of
32    /// bytes. This is similar to [`Path`](std::path::Path) vs [`OsStr`](std::ffi::OsStr) where they
33    /// are trivially cast-able to each-other and `Path` doesn't guarantee being a usable FS path but
34    /// having a newtype still has value because of added methods, readability and basic type checking.
35    ///
36    /// Although at least data pushes could be checked not to overflow the script, bad scripts are
37    /// allowed to be in a transaction (outputs just become unspendable) and there even are such
38    /// transactions in the chain. Thus we must allow such scripts to be placed in the transaction.
39    ///
40    /// # Slicing safety
41    ///
42    /// Slicing is similar to how `str` works: some ranges may be incorrect and indexing by
43    /// `usize` is not supported. However, as opposed to `std`, we have no way of checking
44    /// correctness without causing linear complexity so there are **no panics on invalid
45    /// ranges!** If you supply an invalid range, you'll get a garbled script.
46    ///
47    /// The range is considered valid if it's at a boundary of instruction. Care must be taken
48    /// especially with push operations because you could get a reference to arbitrary
49    /// attacker-supplied bytes that look like a valid script.
50    ///
51    /// It is recommended to use `.instructions()` method to get an iterator over script
52    /// instructions and work with that instead.
53    ///
54    /// # Memory safety
55    ///
56    /// The type is `#[repr(transparent)]` for internal purposes only!
57    /// No consumer crate may rely on the representation of the struct!
58    ///
59    /// # Hexadecimal strings
60    ///
61    /// Scripts are consensus encoded with a length prefix and as a result of this in some places in
62    /// the ecosystem one will encounter hex strings that include the prefix while in other places
63    /// the prefix is excluded. To support parsing and formatting scripts as hex we provide a bunch
64    /// of different APIs and trait implementations. Please see [`examples/script.rs`] for a
65    /// thorough example of all the APIs.
66    ///
67    #[derive(PartialOrd, Ord, PartialEq, Eq, Hash)]
68    pub struct Script<T>(PhantomData<T>, [u8]);
69
70    impl<T> Script<T> {
71        /// Treat byte slice as `Script`
72        pub const fn from_bytes(bytes: &_) -> &Self;
73
74        /// Treat mutable byte slice as `Script`
75        pub fn from_bytes_mut(bytes: &mut _) -> &mut Self;
76
77        pub(crate) fn from_boxed_bytes(bytes: Box<_>) -> Box<Self>;
78        pub(crate) fn from_rc_bytes(bytes: Rc<_>) -> Rc<Self>;
79        pub(crate) fn from_arc_bytes(bytes: Arc<_>) -> Arc<Self>;
80    }
81}
82
83impl<T: 'static> Default for &Script<T> {
84    #[inline]
85    fn default() -> Self {
86        Script::new()
87    }
88}
89
90impl<T> ToOwned for Script<T> {
91    type Owned = ScriptBuf<T>;
92
93    #[inline]
94    fn to_owned(&self) -> Self::Owned {
95        ScriptBuf::from_bytes(self.to_vec())
96    }
97}
98
99impl<T> Script<T> {
100    /// Constructs a new empty script.
101    #[inline]
102    pub const fn new() -> &'static Self {
103        Self::from_bytes(&[])
104    }
105
106    /// Returns the script data as a byte slice.
107    ///
108    /// This is just the script bytes **not** consensus encoding (which includes a length prefix).
109    #[inline]
110    pub const fn as_bytes(&self) -> &[u8] {
111        &self.1
112    }
113
114    /// Returns the script data as a mutable byte slice.
115    ///
116    /// This is just the script bytes **not** consensus encoding (which includes a length prefix).
117    #[inline]
118    pub fn as_mut_bytes(&mut self) -> &mut [u8] {
119        &mut self.1
120    }
121
122    /// Returns a copy of the script data.
123    ///
124    /// This is just the script bytes **not** consensus encoding (which includes a length prefix).
125    #[inline]
126    pub fn to_vec(&self) -> Vec<u8> {
127        self.as_bytes().to_owned()
128    }
129
130    /// Consensus encodes the script as lower-case hex.
131    ///
132    /// Consensus encoding includes a length prefix. To hex encode without the length prefix use
133    /// `to_hex_string_no_length_prefix`.
134    #[cfg(all(feature = "hex", feature = "alloc"))]
135    pub fn to_hex_string_prefixed(&self) -> String {
136        use internals::hex::{BytesToHexIter, Case};
137
138        let iter = encoding::EncodableByteIter::new(self);
139        BytesToHexIter::new(iter, Case::Lower).collect()
140    }
141
142    /// Encodes the script as lower-case hex.
143    ///
144    /// This is **not** consensus encoding. The returned hex string will not include the length
145    /// prefix. See `to_hex_string_prefixed`.
146    #[cfg(all(feature = "hex", feature = "alloc"))]
147    pub fn to_hex_string_no_length_prefix(&self) -> String {
148        use internals::hex::DisplayHex as _;
149
150        self.as_bytes().to_lower_hex_string()
151    }
152
153    /// Returns the length in bytes of the script.
154    #[inline]
155    pub const fn len(&self) -> usize {
156        self.as_bytes().len()
157    }
158
159    /// Returns whether the script is the empty script.
160    #[inline]
161    pub const fn is_empty(&self) -> bool {
162        self.as_bytes().is_empty()
163    }
164
165    /// Converts a [`Box<Script>`](Box) into a [`ScriptBuf`] without copying or allocating.
166    #[must_use]
167    #[inline]
168    pub fn into_script_buf(self: Box<Self>) -> ScriptBuf<T> {
169        let rw = Box::into_raw(self) as *mut [u8];
170        // SAFETY: copied from `std`
171        // The pointer was just created from a box without deallocating
172        // Casting a transparent struct wrapping a slice to the slice pointer is sound (same
173        // layout).
174        let inner = unsafe { Box::from_raw(rw) };
175        ScriptBuf::from_bytes(Vec::from(inner))
176    }
177
178    /// Iterates over decoded instructions.
179    #[inline]
180    pub fn instructions(&self) -> Instructions<'_> {
181        Instructions::new(self, false)
182    }
183
184    /// Iterates over decoded instructions while enforcing minimal pushes.
185    #[inline]
186    pub fn instructions_minimal(&self) -> Instructions<'_> {
187        Instructions::new(self, true)
188    }
189
190    /// Counts signature-check operations using accurate multisig counting.
191    ///
192    /// This is the counting mode used by the node for redeem scripts and
193    /// witness scripts. `OP_CHECKSIGADD` is not counted by Tidecoin consensus.
194    pub fn count_sigops(&self) -> usize {
195        self.count_sigops_internal(true)
196    }
197
198    /// Counts signature-check operations using legacy multisig counting.
199    ///
200    /// This is the counting mode used by the node for scriptSigs and
201    /// scriptPubKeys in context-free block sanity checks.
202    pub fn count_sigops_legacy(&self) -> usize {
203        self.count_sigops_internal(false)
204    }
205
206    fn count_sigops_internal(&self, accurate: bool) -> usize {
207        let mut count = 0;
208        let mut pushnum_cache = None;
209        for inst in self.instructions() {
210            match inst {
211                Ok(super::Instruction::Op(opcode)) => match opcode.to_u8() {
212                    x if x == OP_CHECKSIG.to_u8() || x == OP_CHECKSIGVERIFY.to_u8() => {
213                        count += 1;
214                    }
215                    x if x == OP_CHECKMULTISIG.to_u8() || x == OP_CHECKMULTISIGVERIFY.to_u8() => {
216                        if accurate {
217                            count += pushnum_cache.map_or(20, usize::from);
218                        } else {
219                            count += 20;
220                        }
221                    }
222                    _ => {
223                        pushnum_cache = opcode.decode_pushnum();
224                    }
225                },
226                Ok(super::Instruction::PushBytes(_)) => {
227                    pushnum_cache = None;
228                }
229                Err(_) => break,
230            }
231        }
232        count
233    }
234
235    /// Iterates over decoded instructions together with their byte indices.
236    #[inline]
237    pub fn instruction_indices(&self) -> InstructionIndices<'_> {
238        InstructionIndices::new(self, false)
239    }
240
241    /// Iterates over decoded instructions and indices while enforcing minimal pushes.
242    #[inline]
243    pub fn instruction_indices_minimal(&self) -> InstructionIndices<'_> {
244        InstructionIndices::new(self, true)
245    }
246
247    /// Returns the last opcode if the final instruction is an opcode.
248    pub fn last_opcode(&self) -> Option<crate::opcodes::Opcode> {
249        match self.instructions().last() {
250            Some(Ok(super::Instruction::Op(op))) => Some(op),
251            _ => None,
252        }
253    }
254
255    /// Returns the last pushed byte slice if the final instruction is a data push.
256    pub fn last_pushdata(&self) -> Option<&super::PushBytes> {
257        match self.instructions().last() {
258            Some(Ok(super::Instruction::PushBytes(bytes))) => Some(bytes),
259            _ => None,
260        }
261    }
262}
263
264encoding::encoder_newtype_exact! {
265    /// The encoder for the [`Script<T>`] type.
266    pub struct ScriptEncoder<'e>(Encoder2<CompactSizeEncoder, BytesEncoder<'e>>);
267}
268
269impl<T> Encodable for Script<T> {
270    type Encoder<'e>
271        = ScriptEncoder<'e>
272    where
273        Self: 'e;
274
275    fn encoder(&self) -> Self::Encoder<'_> {
276        ScriptEncoder::new(Encoder2::new(
277            CompactSizeEncoder::new(self.as_bytes().len()),
278            BytesEncoder::without_length_prefix(self.as_bytes()),
279        ))
280    }
281}
282
283#[cfg(feature = "arbitrary")]
284impl<'a, T> Arbitrary<'a> for &'a Script<T> {
285    #[inline]
286    fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
287        let v = <&'a [u8]>::arbitrary(u)?;
288        Ok(Script::from_bytes(v))
289    }
290}
291
292macro_rules! delegate_index {
293    ($($type:ty),* $(,)?) => {
294        $(
295            /// Script subslicing operation - read [slicing safety](#slicing-safety)!
296            impl<T> Index<$type> for Script<T> {
297                type Output = Self;
298
299                #[inline]
300                fn index(&self, index: $type) -> &Self::Output {
301                    Self::from_bytes(&self.as_bytes()[index])
302                }
303            }
304        )*
305    }
306}
307
308delegate_index!(
309    Range<usize>,
310    RangeFrom<usize>,
311    RangeTo<usize>,
312    RangeFull,
313    RangeInclusive<usize>,
314    RangeToInclusive<usize>,
315    (Bound<usize>, Bound<usize>)
316);