masp_primitives/transaction/components/
transparent.rs

1//! Structs representing the components within Zcash transactions.
2
3use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
4use std::fmt::{self, Debug};
5use std::io::{self, Read, Write};
6
7use crate::asset_type::AssetType;
8use crate::transaction::TransparentAddress;
9use crate::MaybeArbitrary;
10use borsh::schema::add_definition;
11use borsh::schema::Declaration;
12use borsh::schema::Definition;
13use borsh::schema::Fields;
14use std::collections::BTreeMap;
15
16use super::amount::{BalanceError, I128Sum, ValueSum, MAX_MONEY};
17
18pub mod builder;
19pub mod fees;
20
21pub trait Authorization: fmt::Debug {
22    type TransparentSig: fmt::Debug + Clone + PartialEq + for<'a> MaybeArbitrary<'a>;
23}
24
25#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
26#[derive(Debug, Copy, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
27pub struct Authorized;
28
29impl Authorization for Authorized {
30    type TransparentSig = ();
31}
32
33pub trait MapAuth<A: Authorization, B: Authorization> {
34    fn map_script_sig(&self, s: A::TransparentSig, pos: usize) -> B::TransparentSig;
35    fn map_authorization(&self, s: A) -> B;
36}
37
38/// The identity map.
39///
40/// This can be used with [`TransactionData::map_authorization`] when you want to map the
41/// authorization of a subset of the transaction's bundles.
42///
43/// [`TransactionData::map_authorization`]: crate::transaction::TransactionData::map_authorization
44impl MapAuth<Authorized, Authorized> for () {
45    fn map_script_sig(
46        &self,
47        s: <Authorized as Authorization>::TransparentSig,
48        _pos: usize,
49    ) -> <Authorized as Authorization>::TransparentSig {
50        s
51    }
52
53    fn map_authorization(&self, a: Authorized) -> Authorized {
54        a
55    }
56}
57
58#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
59#[derive(Debug, Clone, PartialEq)]
60pub struct Bundle<A: Authorization> {
61    pub vin: Vec<TxIn<A>>,
62    pub vout: Vec<TxOut>,
63    pub authorization: A,
64}
65
66impl<A: Authorization> Bundle<A> {
67    pub fn map_authorization<B: Authorization, F: MapAuth<A, B>>(self, f: F) -> Bundle<B> {
68        Bundle {
69            vin: self
70                .vin
71                .into_iter()
72                .enumerate()
73                .map(|(pos, txin)| TxIn {
74                    asset_type: txin.asset_type,
75                    address: txin.address,
76                    transparent_sig: f.map_script_sig(txin.transparent_sig, pos),
77                    value: txin.value,
78                })
79                .collect(),
80            vout: self.vout,
81            authorization: f.map_authorization(self.authorization),
82        }
83    }
84
85    /// The amount of value added to or removed from the transparent pool by the action of this
86    /// bundle. A positive value represents that the containing transaction has funds being
87    /// transferred out of the transparent pool into shielded pools or to fees; a negative value
88    /// means that the containing transaction has funds being transferred into the transparent pool
89    /// from the shielded pools.
90    pub fn value_balance<E, F>(&self) -> I128Sum
91    where
92        E: From<BalanceError>,
93    {
94        let input_sum = self
95            .vin
96            .iter()
97            .map(|p| ValueSum::from_pair(p.asset_type, p.value as i128))
98            .sum::<I128Sum>();
99
100        let output_sum = self
101            .vout
102            .iter()
103            .map(|p| ValueSum::from_pair(p.asset_type, p.value as i128))
104            .sum::<I128Sum>();
105
106        // Cannot panic when subtracting two positive i64
107        input_sum - output_sum
108    }
109}
110
111#[derive(Debug, Clone, PartialEq, Eq)]
112#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
113pub struct TxIn<A: Authorization> {
114    pub asset_type: AssetType,
115    pub value: u64,
116    pub address: TransparentAddress,
117    pub transparent_sig: A::TransparentSig,
118}
119
120impl TxIn<Authorized> {
121    pub fn read<R: Read>(reader: &mut R) -> io::Result<Self> {
122        let asset_type = AssetType::read(reader)?;
123        let value = {
124            let mut tmp = [0u8; 8];
125            reader.read_exact(&mut tmp)?;
126            u64::from_le_bytes(tmp)
127        };
128        if value > MAX_MONEY {
129            return Err(io::Error::new(
130                io::ErrorKind::InvalidData,
131                "value out of range",
132            ));
133        }
134        let address = {
135            let mut tmp = [0u8; 20];
136            reader.read_exact(&mut tmp)?;
137            TransparentAddress(tmp)
138        };
139
140        Ok(TxIn {
141            asset_type,
142            value,
143            address,
144            transparent_sig: (),
145        })
146    }
147
148    pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
149        writer.write_all(self.asset_type.get_identifier())?;
150        writer.write_all(&self.value.to_le_bytes())?;
151        writer.write_all(&self.address.0)
152    }
153}
154
155impl BorshSerialize for TxIn<Authorized> {
156    fn serialize<W: Write>(&self, writer: &mut W) -> io::Result<()> {
157        self.write(writer)
158    }
159}
160
161impl BorshDeserialize for TxIn<Authorized> {
162    fn deserialize_reader<R: Read>(reader: &mut R) -> io::Result<Self> {
163        Self::read(reader)
164    }
165}
166
167impl BorshSchema for TxIn<Authorized> {
168    fn add_definitions_recursively(
169        definitions: &mut BTreeMap<borsh::schema::Declaration, borsh::schema::Definition>,
170    ) {
171        let definition = Definition::Struct {
172            fields: Fields::NamedFields(vec![
173                ("asset_type".into(), AssetType::declaration()),
174                ("value".into(), u64::declaration()),
175                ("address".into(), TransparentAddress::declaration()),
176            ]),
177        };
178        add_definition(Self::declaration(), definition, definitions);
179        AssetType::add_definitions_recursively(definitions);
180        u64::add_definitions_recursively(definitions);
181        TransparentAddress::add_definitions_recursively(definitions);
182    }
183
184    fn declaration() -> borsh::schema::Declaration {
185        "TxIn<Authorized>".into()
186    }
187}
188
189#[derive(Clone, Debug, Hash, PartialOrd, PartialEq, Ord, Eq)]
190#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
191pub struct TxOut {
192    pub asset_type: AssetType,
193    pub value: u64,
194    pub address: TransparentAddress,
195}
196
197impl TxOut {
198    pub fn read<R: Read>(reader: &mut R) -> io::Result<Self> {
199        let asset_type = AssetType::read(reader)?;
200        let value = {
201            let mut tmp = [0u8; 8];
202            reader.read_exact(&mut tmp)?;
203            u64::from_le_bytes(tmp)
204        };
205        if value > MAX_MONEY {
206            return Err(io::Error::new(
207                io::ErrorKind::InvalidData,
208                "value out of range",
209            ));
210        }
211
212        let address = {
213            let mut tmp = [0u8; 20];
214            reader.read_exact(&mut tmp)?;
215            TransparentAddress(tmp)
216        };
217
218        Ok(TxOut {
219            asset_type,
220            value,
221            address,
222        })
223    }
224
225    pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
226        writer.write_all(self.asset_type.get_identifier())?;
227        writer.write_all(&self.value.to_le_bytes())?;
228        writer.write_all(&self.address.0)
229    }
230
231    /// Returns the address to which the TxOut was sent, if this is a valid P2SH or P2PKH output.
232    pub fn recipient_address(&self) -> TransparentAddress {
233        self.address
234    }
235}
236
237impl BorshDeserialize for TxOut {
238    fn deserialize_reader<R: Read>(reader: &mut R) -> io::Result<Self> {
239        Self::read(reader)
240    }
241}
242
243impl BorshSerialize for TxOut {
244    fn serialize<W: Write>(&self, writer: &mut W) -> io::Result<()> {
245        self.write(writer)
246    }
247}
248
249impl BorshSchema for TxOut {
250    fn add_definitions_recursively(definitions: &mut BTreeMap<Declaration, Definition>) {
251        let definition = Definition::Struct {
252            fields: Fields::NamedFields(vec![
253                ("asset_type".into(), AssetType::declaration()),
254                ("value".into(), u64::declaration()),
255                ("address".into(), TransparentAddress::declaration()),
256            ]),
257        };
258        add_definition(Self::declaration(), definition, definitions);
259        AssetType::add_definitions_recursively(definitions);
260        u64::add_definitions_recursively(definitions);
261        TransparentAddress::add_definitions_recursively(definitions);
262    }
263
264    fn declaration() -> Declaration {
265        "TxOut".into()
266    }
267}
268
269#[cfg(any(test, feature = "test-dependencies"))]
270pub mod testing {
271    use proptest::collection::vec;
272    use proptest::prelude::*;
273
274    use crate::transaction::components::amount::testing::arb_nonnegative_amount;
275    use crate::transaction::TransparentAddress;
276
277    use super::{Authorized, Bundle, TxIn, TxOut};
278
279    prop_compose! {
280        pub fn arb_transparent_address()(value in prop::array::uniform20(prop::num::u8::ANY)) -> TransparentAddress {
281            TransparentAddress(value)
282        }
283    }
284
285    prop_compose! {
286        pub fn arb_txin()(amt in arb_nonnegative_amount(), addr in arb_transparent_address()) -> TxIn<Authorized> {
287            let (asset_type, value) = amt.components().next().unwrap();
288            TxIn { asset_type: *asset_type, value: *value, address: addr, transparent_sig: () }
289        }
290    }
291
292    prop_compose! {
293        pub fn arb_txout()(amt in arb_nonnegative_amount(), addr in arb_transparent_address()) -> TxOut {
294            let (asset_type, value) = amt.components().next().unwrap();
295
296            TxOut { asset_type: *asset_type, value: *value, address : addr }
297        }
298    }
299
300    prop_compose! {
301        pub fn arb_bundle()(
302            vin in vec(arb_txin(), 0..10),
303            vout in vec(arb_txout(), 0..10),
304        ) -> Option<Bundle<Authorized>> {
305            if vout.is_empty() {
306                None
307            } else {
308                Some(Bundle {vin, vout, authorization: Authorized })
309            }
310        }
311    }
312}
313
314#[cfg(test)]
315mod test_serialization {
316    use super::*;
317
318    /// Simple test that a serialization round trip is the identity
319    #[test]
320    fn test_roundtrip_txin() {
321        let asset_type = AssetType::new_with_nonce(&[1, 2, 3, 4], 1).expect("Test failed");
322        let txin = TxIn::<Authorized> {
323            asset_type,
324            value: MAX_MONEY - 1,
325            address: TransparentAddress([12u8; 20]),
326            transparent_sig: (),
327        };
328
329        let mut buf = vec![];
330        txin.write(&mut buf).expect("Test failed");
331        let deserialized = TxIn::read::<&[u8]>(&mut buf.as_ref()).expect("Test failed");
332        assert_eq!(deserialized, txin);
333    }
334
335    /// Simple test that a serialization round trip is the identity
336    #[test]
337    fn test_roundtrip_txout() {
338        let asset_type = AssetType::new_with_nonce(&[1, 2, 3, 4], 1).expect("Test failed");
339        let txout = TxOut {
340            asset_type,
341            value: MAX_MONEY - 1,
342            address: TransparentAddress([12u8; 20]),
343        };
344
345        let mut buf = vec![];
346        txout.write(&mut buf).expect("Test failed");
347        let deserialized = TxOut::read::<&[u8]>(&mut buf.as_ref()).expect("Test failed");
348        assert_eq!(deserialized, txout);
349    }
350}