zcash_transparent/
bundle.rs

1//! Structs representing the components within Zcash transactions.
2
3use alloc::vec::Vec;
4use core::fmt::Debug;
5use core2::io::{self, Read, Write};
6
7use zcash_protocol::{
8    value::{BalanceError, ZatBalance as Amount, Zatoshis as NonNegativeAmount},
9    TxId,
10};
11
12use crate::{
13    address::{Script, TransparentAddress},
14    sighash::TransparentAuthorizingContext,
15};
16
17pub trait Authorization: Debug {
18    type ScriptSig: Debug + Clone + PartialEq;
19}
20
21/// Marker type for a bundle that contains no authorizing data, and the necessary input
22/// information for creating sighashes.
23#[derive(Debug)]
24pub struct EffectsOnly {
25    pub(crate) inputs: Vec<TxOut>,
26}
27
28impl Authorization for EffectsOnly {
29    type ScriptSig = ();
30}
31
32impl TransparentAuthorizingContext for EffectsOnly {
33    fn input_amounts(&self) -> Vec<NonNegativeAmount> {
34        self.inputs.iter().map(|input| input.value).collect()
35    }
36
37    fn input_scriptpubkeys(&self) -> Vec<Script> {
38        self.inputs
39            .iter()
40            .map(|input| input.script_pubkey.clone())
41            .collect()
42    }
43}
44
45#[derive(Debug, Copy, Clone, PartialEq, Eq)]
46pub struct Authorized;
47
48impl Authorization for Authorized {
49    type ScriptSig = Script;
50}
51
52pub trait MapAuth<A: Authorization, B: Authorization> {
53    fn map_script_sig(&self, s: A::ScriptSig) -> B::ScriptSig;
54    fn map_authorization(&self, s: A) -> B;
55}
56
57/// The identity map.
58impl MapAuth<Authorized, Authorized> for () {
59    fn map_script_sig(
60        &self,
61        s: <Authorized as Authorization>::ScriptSig,
62    ) -> <Authorized as Authorization>::ScriptSig {
63        s
64    }
65
66    fn map_authorization(&self, s: Authorized) -> Authorized {
67        s
68    }
69}
70
71#[derive(Debug, Clone, PartialEq)]
72pub struct Bundle<A: Authorization> {
73    pub vin: Vec<TxIn<A>>,
74    pub vout: Vec<TxOut>,
75    pub authorization: A,
76}
77
78impl<A: Authorization> Bundle<A> {
79    /// Returns `true` if this bundle matches the definition of a coinbase transaction.
80    ///
81    /// Note that this is defined purely in terms of the transparent transaction part. The
82    /// consensus rules enforce additional rules on the shielded parts (namely, that they
83    /// don't have any inputs) of transactions with a transparent part that matches this
84    /// definition.
85    pub fn is_coinbase(&self) -> bool {
86        // From `CTransaction::IsCoinBase()` in zcashd:
87        //   return (vin.size() == 1 && vin[0].prevout.IsNull());
88        matches!(&self.vin[..], [input] if input.prevout.is_null())
89    }
90
91    pub fn map_authorization<B: Authorization, F: MapAuth<A, B>>(self, f: F) -> Bundle<B> {
92        Bundle {
93            vin: self
94                .vin
95                .into_iter()
96                .map(|txin| TxIn {
97                    prevout: txin.prevout,
98                    script_sig: f.map_script_sig(txin.script_sig),
99                    sequence: txin.sequence,
100                })
101                .collect(),
102            vout: self.vout,
103            authorization: f.map_authorization(self.authorization),
104        }
105    }
106
107    /// The amount of value added to or removed from the transparent pool by the action of this
108    /// bundle. A positive value represents that the containing transaction has funds being
109    /// transferred out of the transparent pool into shielded pools or to fees; a negative value
110    /// means that the containing transaction has funds being transferred into the transparent pool
111    /// from the shielded pools.
112    pub fn value_balance<E, F>(&self, mut get_prevout_value: F) -> Result<Amount, E>
113    where
114        E: From<BalanceError>,
115        F: FnMut(&OutPoint) -> Result<Amount, E>,
116    {
117        let input_sum = self.vin.iter().try_fold(Amount::zero(), |total, txin| {
118            get_prevout_value(&txin.prevout)
119                .and_then(|v| (total + v).ok_or_else(|| BalanceError::Overflow.into()))
120        })?;
121
122        let output_sum = self
123            .vout
124            .iter()
125            .map(|p| Amount::from(p.value))
126            .sum::<Option<Amount>>()
127            .ok_or(BalanceError::Overflow)?;
128
129        (input_sum - output_sum).ok_or_else(|| BalanceError::Underflow.into())
130    }
131}
132
133#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
134pub struct OutPoint {
135    pub(crate) hash: TxId,
136    pub(crate) n: u32,
137}
138
139impl OutPoint {
140    /// Constructs an `OutPoint` for the output at index `n` in the transaction
141    /// with txid `hash`.
142    pub fn new(hash: [u8; 32], n: u32) -> Self {
143        OutPoint {
144            hash: TxId::from_bytes(hash),
145            n,
146        }
147    }
148
149    /// Constructs a fake `OutPoint` for use in tests.
150    #[cfg(any(test, feature = "test-dependencies"))]
151    pub const fn fake() -> Self {
152        OutPoint {
153            hash: TxId::from_bytes([1u8; 32]),
154            n: 1,
155        }
156    }
157
158    pub fn read<R: Read>(mut reader: R) -> io::Result<Self> {
159        let mut hash = [0u8; 32];
160        reader.read_exact(&mut hash)?;
161        let mut n = [0; 4];
162        reader.read_exact(&mut n)?;
163        Ok(OutPoint::new(hash, u32::from_le_bytes(n)))
164    }
165
166    pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
167        writer.write_all(self.hash.as_ref())?;
168        writer.write_all(&self.n.to_le_bytes())
169    }
170
171    /// Returns `true` if this `OutPoint` is "null" in the Bitcoin sense: it has txid set to
172    /// all-zeroes and output index set to `u32::MAX`.
173    fn is_null(&self) -> bool {
174        // From `BaseOutPoint::IsNull()` in zcashd:
175        //   return (hash.IsNull() && n == (uint32_t) -1);
176        self.hash.as_ref() == &[0; 32] && self.n == u32::MAX
177    }
178
179    /// Returns the output index of this `OutPoint`.
180    pub fn n(&self) -> u32 {
181        self.n
182    }
183
184    /// Returns the byte representation of the txid of the transaction containing this `OutPoint`.
185    pub fn hash(&self) -> &[u8; 32] {
186        self.hash.as_ref()
187    }
188
189    /// Returns the txid of the transaction containing this `OutPoint`.
190    pub fn txid(&self) -> &TxId {
191        &self.hash
192    }
193}
194
195#[derive(Debug, Clone, PartialEq)]
196pub struct TxIn<A: Authorization> {
197    pub prevout: OutPoint,
198    pub script_sig: A::ScriptSig,
199    pub sequence: u32,
200}
201
202impl TxIn<Authorized> {
203    pub fn read<R: Read>(mut reader: &mut R) -> io::Result<Self> {
204        let prevout = OutPoint::read(&mut reader)?;
205        let script_sig = Script::read(&mut reader)?;
206        let sequence = {
207            let mut sequence = [0; 4];
208            reader.read_exact(&mut sequence)?;
209            u32::from_le_bytes(sequence)
210        };
211
212        Ok(TxIn {
213            prevout,
214            script_sig,
215            sequence,
216        })
217    }
218
219    pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
220        self.prevout.write(&mut writer)?;
221        self.script_sig.write(&mut writer)?;
222        writer.write_all(&self.sequence.to_le_bytes())
223    }
224}
225
226#[derive(Clone, Debug, PartialEq, Eq)]
227pub struct TxOut {
228    pub value: NonNegativeAmount,
229    pub script_pubkey: Script,
230}
231
232impl TxOut {
233    pub fn read<R: Read>(mut reader: &mut R) -> io::Result<Self> {
234        let value = {
235            let mut tmp = [0u8; 8];
236            reader.read_exact(&mut tmp)?;
237            NonNegativeAmount::from_nonnegative_i64_le_bytes(tmp)
238        }
239        .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "value out of range"))?;
240        let script_pubkey = Script::read(&mut reader)?;
241
242        Ok(TxOut {
243            value,
244            script_pubkey,
245        })
246    }
247
248    pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
249        writer.write_all(&self.value.to_i64_le_bytes())?;
250        self.script_pubkey.write(&mut writer)
251    }
252
253    /// Returns the address to which the TxOut was sent, if this is a valid P2SH or P2PKH output.
254    pub fn recipient_address(&self) -> Option<TransparentAddress> {
255        self.script_pubkey.address()
256    }
257}
258
259#[cfg(any(test, feature = "test-dependencies"))]
260pub mod testing {
261    use proptest::collection::vec;
262    use proptest::prelude::*;
263    use proptest::sample::select;
264    use zcash_protocol::value::testing::arb_zatoshis;
265
266    use crate::address::Script;
267
268    use super::{Authorized, Bundle, OutPoint, TxIn, TxOut};
269
270    pub const VALID_OPCODES: [u8; 8] = [
271        0x00, // OP_FALSE,
272        0x51, // OP_1,
273        0x52, // OP_2,
274        0x53, // OP_3,
275        0xac, // OP_CHECKSIG,
276        0x63, // OP_IF,
277        0x65, // OP_VERIF,
278        0x6a, // OP_RETURN,
279    ];
280
281    prop_compose! {
282        pub fn arb_outpoint()(hash in prop::array::uniform32(0u8..), n in 0..100u32) -> OutPoint {
283            OutPoint::new(hash, n)
284        }
285    }
286
287    prop_compose! {
288        pub fn arb_script()(v in vec(select(&VALID_OPCODES[..]), 1..256)) -> Script {
289            Script(v)
290        }
291    }
292
293    prop_compose! {
294        pub fn arb_txin()(
295            prevout in arb_outpoint(),
296            script_sig in arb_script(),
297            sequence in any::<u32>()
298        ) -> TxIn<Authorized> {
299            TxIn { prevout, script_sig, sequence }
300        }
301    }
302
303    prop_compose! {
304        pub fn arb_txout()(value in arb_zatoshis(), script_pubkey in arb_script()) -> TxOut {
305            TxOut { value, script_pubkey }
306        }
307    }
308
309    prop_compose! {
310        pub fn arb_bundle()(
311            vin in vec(arb_txin(), 0..10),
312            vout in vec(arb_txout(), 0..10),
313        ) -> Option<Bundle<Authorized>> {
314            if vin.is_empty() && vout.is_empty() {
315                None
316            } else {
317                Some(Bundle { vin, vout, authorization: Authorized })
318            }
319        }
320    }
321}