Skip to main content

zingolabs_zewif/
script.rs

1use super::Data;
2use anyhow::{Context, Result};
3use bc_envelope::prelude::*;
4use std::ops::{
5    Index, IndexMut, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive,
6};
7
8/// A Bitcoin-style script for spending or encumbering coins in transparent transactions.
9///
10/// `Script` represents a serialized Bitcoin script, which is a sequence of operations used to
11/// specify conditions for spending Zcash coins in transparent transactions. In ZeWIF,
12/// scripts appear in two contexts:
13///
14/// - `script_pubkey` in outputs: Defines spending conditions (e.g., P2PKH, P2SH)
15/// - `script_sig` in inputs: Contains signatures and data to satisfy spending conditions
16///
17/// Internally, `Script` is a wrapper around [`Data`](crate::Data), providing a
18/// type-safe representation for script handling.
19///
20/// # Zcash Concept Relation
21/// Zcash inherits the Bitcoin script system for its transparent UTXO model. Unlike
22/// shielded transactions, which use zero-knowledge proofs, transparent transactions
23/// use explicit scripts to validate spending conditions.
24///
25/// Common script patterns in Zcash transparent addresses include:
26/// - Pay to Public Key Hash (P2PKH): Sends to a standard transparent address
27/// - Pay to Script Hash (P2SH): Sends to a script hash, enabling more complex conditions
28///
29/// # Data Preservation
30/// The `Script` type preserves the exact binary representation of transaction scripts
31/// from wallet data, ensuring cryptographic integrity during wallet migrations.
32///
33/// # Examples
34/// ```
35/// # use zewif::{Script, Data};
36/// // Create a script from binary data (this would typically be from a transaction)
37/// let script_bytes = vec![0x76, 0xa9, 0x14, /* more script bytes */];
38/// let script = Script::from(Data::from_vec(script_bytes.clone()));
39///
40/// // Check script properties
41/// assert_eq!(script.len(), script_bytes.len());
42/// assert!(!script.is_empty());
43/// ```
44#[derive(Clone, PartialEq, Eq, Hash)]
45pub struct Script(Data);
46
47impl Script {
48    pub fn len(&self) -> usize {
49        self.0.len()
50    }
51
52    pub fn is_empty(&self) -> bool {
53        self.len() == 0
54    }
55}
56
57/// Debug formatting that includes script length and hex representation
58impl std::fmt::Debug for Script {
59    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
60        write!(f, "Script<{}>({})", self.0.len(), hex::encode(self))
61    }
62}
63
64/// Allows treating a Script as a byte slice
65impl AsRef<[u8]> for Script {
66    fn as_ref(&self) -> &[u8] {
67        self.0.as_ref()
68    }
69}
70
71/// Converts a Script to a Data value, allowing manipulation as variable-length bytes
72impl From<Script> for Data {
73    fn from(script: Script) -> Self {
74        script.0
75    }
76}
77
78/// Creates a Script from Data, allowing conversion from variable-length bytes
79impl From<Data> for Script {
80    fn from(data: Data) -> Self {
81        Script(data)
82    }
83}
84
85/// Allows accessing individual bytes in the script by index
86impl Index<usize> for Script {
87    type Output = u8;
88
89    fn index(&self, index: usize) -> &Self::Output {
90        &self.0[index]
91    }
92}
93
94/// Allows modifying individual bytes in the script by index
95impl IndexMut<usize> for Script {
96    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
97        &mut self.0[index]
98    }
99}
100
101impl Index<Range<usize>> for Script {
102    type Output = [u8];
103
104    fn index(&self, index: Range<usize>) -> &Self::Output {
105        &self.0[index]
106    }
107}
108
109impl IndexMut<Range<usize>> for Script {
110    fn index_mut(&mut self, index: Range<usize>) -> &mut Self::Output {
111        &mut self.0[index]
112    }
113}
114
115impl Index<RangeTo<usize>> for Script {
116    type Output = [u8];
117
118    fn index(&self, index: RangeTo<usize>) -> &Self::Output {
119        &self.0[index]
120    }
121}
122
123impl IndexMut<RangeTo<usize>> for Script {
124    fn index_mut(&mut self, index: RangeTo<usize>) -> &mut Self::Output {
125        &mut self.0[index]
126    }
127}
128
129impl Index<RangeFrom<usize>> for Script {
130    type Output = [u8];
131
132    fn index(&self, index: RangeFrom<usize>) -> &Self::Output {
133        &self.0[index]
134    }
135}
136
137impl IndexMut<RangeFrom<usize>> for Script {
138    fn index_mut(&mut self, index: RangeFrom<usize>) -> &mut Self::Output {
139        &mut self.0[index]
140    }
141}
142
143impl Index<RangeFull> for Script {
144    type Output = [u8];
145
146    fn index(&self, index: RangeFull) -> &Self::Output {
147        &self.0[index]
148    }
149}
150
151impl IndexMut<RangeFull> for Script {
152    fn index_mut(&mut self, index: RangeFull) -> &mut Self::Output {
153        &mut self.0[index]
154    }
155}
156
157impl Index<RangeInclusive<usize>> for Script {
158    type Output = [u8];
159
160    fn index(&self, index: RangeInclusive<usize>) -> &Self::Output {
161        &self.0[index]
162    }
163}
164
165impl IndexMut<RangeInclusive<usize>> for Script {
166    fn index_mut(&mut self, index: RangeInclusive<usize>) -> &mut Self::Output {
167        &mut self.0[index]
168    }
169}
170
171impl Index<RangeToInclusive<usize>> for Script {
172    type Output = [u8];
173
174    fn index(&self, index: RangeToInclusive<usize>) -> &Self::Output {
175        &self.0[index]
176    }
177}
178
179impl IndexMut<RangeToInclusive<usize>> for Script {
180    fn index_mut(&mut self, index: RangeToInclusive<usize>) -> &mut Self::Output {
181        &mut self.0[index]
182    }
183}
184
185impl From<Script> for CBOR {
186    fn from(value: Script) -> Self {
187        CBOR::to_byte_string(value.0)
188    }
189}
190
191impl From<&Script> for CBOR {
192    fn from(value: &Script) -> Self {
193        CBOR::to_byte_string(value.0.clone())
194    }
195}
196
197impl TryFrom<CBOR> for Script {
198    type Error = dcbor::Error;
199
200    fn try_from(cbor: CBOR) -> dcbor::Result<Self> {
201        let bytes = cbor.try_into_byte_string()?;
202        if bytes.len() > 0xffff {
203            return Err("Script length exceeds maximum size of 65535 bytes".into());
204        }
205        Ok(Script(Data::from_vec(bytes)))
206    }
207}
208
209impl From<Script> for Envelope {
210    fn from(value: Script) -> Self {
211        Envelope::new(CBOR::from(value))
212    }
213}
214
215impl TryFrom<Envelope> for Script {
216    type Error = anyhow::Error;
217
218    fn try_from(envelope: Envelope) -> Result<Self, Self::Error> {
219        envelope.extract_subject().context("Script")
220    }
221}
222
223#[cfg(test)]
224mod tests {
225    use crate::{Data, test_cbor_roundtrip, test_envelope_roundtrip};
226
227    use super::Script;
228
229    impl crate::RandomInstance for Script {
230        fn random_with_size(size: usize) -> Self {
231            Self(Data::random_with_size(size))
232        }
233
234        fn random() -> Self {
235            Self(Data::random())
236        }
237    }
238
239    test_cbor_roundtrip!(Script);
240    test_envelope_roundtrip!(Script);
241}