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);