ironfish_primitives/transaction/
mod.rs

1//! Structs and methods for handling Zcash transactions.
2pub mod builder;
3pub mod components;
4pub mod sighash;
5pub mod sighash_v4;
6pub mod sighash_v5;
7pub mod txid;
8pub mod util;
9
10#[cfg(test)]
11mod tests;
12
13use blake2b_simd::Hash as Blake2bHash;
14use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
15use ff::PrimeField;
16use std::convert::TryFrom;
17use std::fmt;
18use std::fmt::Debug;
19use std::io::{self, Read, Write};
20use std::ops::Deref;
21use zcash_encoding::{Array, CompactSize, Vector};
22
23use crate::{
24    consensus::{BlockHeight, BranchId},
25    sapling::redjubjub,
26};
27
28use self::{
29    components::{
30        amount::Amount,
31        orchard as orchard_serialization,
32        sapling::{
33            self, OutputDescription, OutputDescriptionV5, SpendDescription, SpendDescriptionV5,
34        },
35        sprout::{self, JsDescription},
36        transparent::{self, TxIn, TxOut},
37    },
38    txid::{to_txid, BlockTxCommitmentDigester, TxIdDigester},
39    util::sha256d::{HashReader, HashWriter},
40};
41
42#[cfg(feature = "zfuture")]
43use self::components::tze::{self, TzeIn, TzeOut};
44
45const OVERWINTER_VERSION_GROUP_ID: u32 = 0x03C48270;
46const OVERWINTER_TX_VERSION: u32 = 3;
47const SAPLING_VERSION_GROUP_ID: u32 = 0x892F2085;
48const SAPLING_TX_VERSION: u32 = 4;
49
50const V5_TX_VERSION: u32 = 5;
51const V5_VERSION_GROUP_ID: u32 = 0x26A7270A;
52
53/// These versions are used exclusively for in-development transaction
54/// serialization, and will never be active under the consensus rules.
55/// When new consensus transaction versions are added, all call sites
56/// using these constants should be inspected, and use of these constants
57/// should be removed as appropriate in favor of the new consensus
58/// transaction version and group.
59#[cfg(feature = "zfuture")]
60const ZFUTURE_VERSION_GROUP_ID: u32 = 0xFFFFFFFF;
61#[cfg(feature = "zfuture")]
62const ZFUTURE_TX_VERSION: u32 = 0x0000FFFF;
63
64#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]
65pub struct TxId([u8; 32]);
66
67impl fmt::Debug for TxId {
68    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69        // The (byte-flipped) hex string is more useful than the raw bytes, because we can
70        // look that up in RPC methods and block explorers.
71        let txid_str = self.to_string();
72        f.debug_tuple("TxId").field(&txid_str).finish()
73    }
74}
75
76impl fmt::Display for TxId {
77    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
78        let mut data = self.0;
79        data.reverse();
80        formatter.write_str(&hex::encode(data))
81    }
82}
83
84impl AsRef<[u8; 32]> for TxId {
85    fn as_ref(&self) -> &[u8; 32] {
86        &self.0
87    }
88}
89
90impl TxId {
91    pub fn from_bytes(bytes: [u8; 32]) -> Self {
92        TxId(bytes)
93    }
94
95    pub fn read<R: Read>(mut reader: R) -> io::Result<Self> {
96        let mut hash = [0u8; 32];
97        reader.read_exact(&mut hash)?;
98        Ok(TxId::from_bytes(hash))
99    }
100
101    pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
102        writer.write_all(&self.0)?;
103        Ok(())
104    }
105}
106
107/// The set of defined transaction format versions.
108///
109/// This is serialized in the first four or eight bytes of the transaction format, and
110/// represents valid combinations of the `(overwintered, version, version_group_id)`
111/// transaction fields. Note that this is not dependent on epoch, only on transaction encoding.
112/// For example, if a particular epoch defines a new transaction version but also allows the
113/// previous version, then only the new version would be added to this enum.
114#[derive(Clone, Copy, Debug, PartialEq, Eq)]
115pub enum TxVersion {
116    Sprout(u32),
117    Overwinter,
118    Sapling,
119    Zip225,
120    #[cfg(feature = "zfuture")]
121    ZFuture,
122}
123
124impl TxVersion {
125    pub fn read<R: Read>(mut reader: R) -> io::Result<Self> {
126        let header = reader.read_u32::<LittleEndian>()?;
127        let overwintered = (header >> 31) == 1;
128        let version = header & 0x7FFFFFFF;
129
130        if overwintered {
131            match (version, reader.read_u32::<LittleEndian>()?) {
132                (OVERWINTER_TX_VERSION, OVERWINTER_VERSION_GROUP_ID) => Ok(TxVersion::Overwinter),
133                (SAPLING_TX_VERSION, SAPLING_VERSION_GROUP_ID) => Ok(TxVersion::Sapling),
134                (V5_TX_VERSION, V5_VERSION_GROUP_ID) => Ok(TxVersion::Zip225),
135                #[cfg(feature = "zfuture")]
136                (ZFUTURE_TX_VERSION, ZFUTURE_VERSION_GROUP_ID) => Ok(TxVersion::ZFuture),
137                _ => Err(io::Error::new(
138                    io::ErrorKind::InvalidInput,
139                    "Unknown transaction format",
140                )),
141            }
142        } else if version >= 1 {
143            Ok(TxVersion::Sprout(version))
144        } else {
145            Err(io::Error::new(
146                io::ErrorKind::InvalidInput,
147                "Unknown transaction format",
148            ))
149        }
150    }
151
152    pub fn header(&self) -> u32 {
153        // After Sprout, the overwintered bit is always set.
154        let overwintered = match self {
155            TxVersion::Sprout(_) => 0,
156            _ => 1 << 31,
157        };
158
159        overwintered
160            | match self {
161                TxVersion::Sprout(v) => *v,
162                TxVersion::Overwinter => OVERWINTER_TX_VERSION,
163                TxVersion::Sapling => SAPLING_TX_VERSION,
164                TxVersion::Zip225 => V5_TX_VERSION,
165                #[cfg(feature = "zfuture")]
166                TxVersion::ZFuture => ZFUTURE_TX_VERSION,
167            }
168    }
169
170    pub fn version_group_id(&self) -> u32 {
171        match self {
172            TxVersion::Sprout(_) => 0,
173            TxVersion::Overwinter => OVERWINTER_VERSION_GROUP_ID,
174            TxVersion::Sapling => SAPLING_VERSION_GROUP_ID,
175            TxVersion::Zip225 => V5_VERSION_GROUP_ID,
176            #[cfg(feature = "zfuture")]
177            TxVersion::ZFuture => ZFUTURE_VERSION_GROUP_ID,
178        }
179    }
180
181    pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
182        writer.write_u32::<LittleEndian>(self.header())?;
183        match self {
184            TxVersion::Sprout(_) => Ok(()),
185            _ => writer.write_u32::<LittleEndian>(self.version_group_id()),
186        }
187    }
188
189    pub fn has_sprout(&self) -> bool {
190        match self {
191            TxVersion::Sprout(v) => *v >= 2u32,
192            TxVersion::Overwinter | TxVersion::Sapling => true,
193            TxVersion::Zip225 => false,
194            #[cfg(feature = "zfuture")]
195            TxVersion::ZFuture => true,
196        }
197    }
198
199    pub fn has_overwinter(&self) -> bool {
200        !matches!(self, TxVersion::Sprout(_))
201    }
202
203    pub fn has_sapling(&self) -> bool {
204        match self {
205            TxVersion::Sprout(_) | TxVersion::Overwinter => false,
206            TxVersion::Sapling => true,
207            TxVersion::Zip225 => true,
208            #[cfg(feature = "zfuture")]
209            TxVersion::ZFuture => true,
210        }
211    }
212
213    pub fn has_orchard(&self) -> bool {
214        match self {
215            TxVersion::Sprout(_) | TxVersion::Overwinter | TxVersion::Sapling => false,
216            TxVersion::Zip225 => true,
217            #[cfg(feature = "zfuture")]
218            TxVersion::ZFuture => true,
219        }
220    }
221
222    #[cfg(feature = "zfuture")]
223    pub fn has_tze(&self) -> bool {
224        matches!(self, TxVersion::ZFuture)
225    }
226
227    pub fn suggested_for_branch(consensus_branch_id: BranchId) -> Self {
228        match consensus_branch_id {
229            BranchId::Sprout => TxVersion::Sprout(2),
230            BranchId::Overwinter => TxVersion::Overwinter,
231            BranchId::Sapling | BranchId::Blossom | BranchId::Heartwood | BranchId::Canopy => {
232                TxVersion::Sapling
233            }
234            BranchId::Nu5 => TxVersion::Zip225,
235            #[cfg(feature = "zfuture")]
236            BranchId::ZFuture => TxVersion::ZFuture,
237        }
238    }
239}
240
241/// Authorization state for a bundle of transaction data.
242pub trait Authorization {
243    type TransparentAuth: transparent::Authorization;
244    type SaplingAuth: sapling::Authorization;
245    type OrchardAuth: orchard::bundle::Authorization;
246
247    #[cfg(feature = "zfuture")]
248    type TzeAuth: tze::Authorization;
249}
250
251#[derive(Debug)]
252pub struct Authorized;
253
254impl Authorization for Authorized {
255    type TransparentAuth = transparent::Authorized;
256    type SaplingAuth = sapling::Authorized;
257    type OrchardAuth = orchard::bundle::Authorized;
258
259    #[cfg(feature = "zfuture")]
260    type TzeAuth = tze::Authorized;
261}
262
263pub struct Unauthorized;
264
265impl Authorization for Unauthorized {
266    type TransparentAuth = transparent::builder::Unauthorized;
267    type SaplingAuth = sapling::builder::Unauthorized;
268    type OrchardAuth = orchard_serialization::Unauthorized;
269
270    #[cfg(feature = "zfuture")]
271    type TzeAuth = tze::builder::Unauthorized;
272}
273
274/// A Zcash transaction.
275#[derive(Debug)]
276pub struct Transaction {
277    txid: TxId,
278    data: TransactionData<Authorized>,
279}
280
281impl Deref for Transaction {
282    type Target = TransactionData<Authorized>;
283
284    fn deref(&self) -> &TransactionData<Authorized> {
285        &self.data
286    }
287}
288
289impl PartialEq for Transaction {
290    fn eq(&self, other: &Transaction) -> bool {
291        self.txid == other.txid
292    }
293}
294
295#[derive(Debug)]
296pub struct TransactionData<A: Authorization> {
297    version: TxVersion,
298    consensus_branch_id: BranchId,
299    lock_time: u32,
300    expiry_height: BlockHeight,
301    transparent_bundle: Option<transparent::Bundle<A::TransparentAuth>>,
302    sprout_bundle: Option<sprout::Bundle>,
303    sapling_bundle: Option<sapling::Bundle<A::SaplingAuth>>,
304    orchard_bundle: Option<orchard::bundle::Bundle<A::OrchardAuth, Amount>>,
305    #[cfg(feature = "zfuture")]
306    tze_bundle: Option<tze::Bundle<A::TzeAuth>>,
307}
308
309impl<A: Authorization> TransactionData<A> {
310    #[allow(clippy::too_many_arguments)]
311    pub fn from_parts(
312        version: TxVersion,
313        consensus_branch_id: BranchId,
314        lock_time: u32,
315        expiry_height: BlockHeight,
316        transparent_bundle: Option<transparent::Bundle<A::TransparentAuth>>,
317        sprout_bundle: Option<sprout::Bundle>,
318        sapling_bundle: Option<sapling::Bundle<A::SaplingAuth>>,
319        orchard_bundle: Option<orchard::Bundle<A::OrchardAuth, Amount>>,
320        #[cfg(feature = "zfuture")] tze_bundle: Option<tze::Bundle<A::TzeAuth>>,
321    ) -> Self {
322        TransactionData {
323            version,
324            consensus_branch_id,
325            lock_time,
326            expiry_height,
327            transparent_bundle,
328            sprout_bundle,
329            sapling_bundle,
330            orchard_bundle,
331            #[cfg(feature = "zfuture")]
332            tze_bundle,
333        }
334    }
335
336    pub fn version(&self) -> TxVersion {
337        self.version
338    }
339
340    pub fn consensus_branch_id(&self) -> BranchId {
341        self.consensus_branch_id
342    }
343
344    pub fn lock_time(&self) -> u32 {
345        self.lock_time
346    }
347
348    pub fn expiry_height(&self) -> BlockHeight {
349        self.expiry_height
350    }
351
352    pub fn transparent_bundle(&self) -> Option<&transparent::Bundle<A::TransparentAuth>> {
353        self.transparent_bundle.as_ref()
354    }
355
356    pub fn sprout_bundle(&self) -> Option<&sprout::Bundle> {
357        self.sprout_bundle.as_ref()
358    }
359
360    pub fn sapling_bundle(&self) -> Option<&sapling::Bundle<A::SaplingAuth>> {
361        self.sapling_bundle.as_ref()
362    }
363
364    pub fn orchard_bundle(&self) -> Option<&orchard::Bundle<A::OrchardAuth, Amount>> {
365        self.orchard_bundle.as_ref()
366    }
367
368    #[cfg(feature = "zfuture")]
369    pub fn tze_bundle(&self) -> Option<&tze::Bundle<A::TzeAuth>> {
370        self.tze_bundle.as_ref()
371    }
372
373    pub fn digest<D: TransactionDigest<A>>(&self, digester: D) -> D::Digest {
374        digester.combine(
375            digester.digest_header(
376                self.version,
377                self.consensus_branch_id,
378                self.lock_time,
379                self.expiry_height,
380            ),
381            digester.digest_transparent(self.transparent_bundle.as_ref()),
382            digester.digest_sapling(self.sapling_bundle.as_ref()),
383            digester.digest_orchard(self.orchard_bundle.as_ref()),
384            #[cfg(feature = "zfuture")]
385            digester.digest_tze(self.tze_bundle.as_ref()),
386        )
387    }
388
389    /// Maps the bundles from one type to another.
390    ///
391    /// This shouldn't be necessary for most use cases; it is provided for handling the
392    /// cross-FFI builder logic in `zcashd`.
393    pub fn map_bundles<B: Authorization>(
394        self,
395        f_transparent: impl FnOnce(
396            Option<transparent::Bundle<A::TransparentAuth>>,
397        ) -> Option<transparent::Bundle<B::TransparentAuth>>,
398        f_sapling: impl FnOnce(
399            Option<sapling::Bundle<A::SaplingAuth>>,
400        ) -> Option<sapling::Bundle<B::SaplingAuth>>,
401        f_orchard: impl FnOnce(
402            Option<orchard::bundle::Bundle<A::OrchardAuth, Amount>>,
403        ) -> Option<orchard::bundle::Bundle<B::OrchardAuth, Amount>>,
404        #[cfg(feature = "zfuture")] f_tze: impl FnOnce(
405            Option<tze::Bundle<A::TzeAuth>>,
406        ) -> Option<tze::Bundle<B::TzeAuth>>,
407    ) -> TransactionData<B> {
408        TransactionData {
409            version: self.version,
410            consensus_branch_id: self.consensus_branch_id,
411            lock_time: self.lock_time,
412            expiry_height: self.expiry_height,
413            transparent_bundle: f_transparent(self.transparent_bundle),
414            sprout_bundle: self.sprout_bundle,
415            sapling_bundle: f_sapling(self.sapling_bundle),
416            orchard_bundle: f_orchard(self.orchard_bundle),
417            #[cfg(feature = "zfuture")]
418            tze_bundle: f_tze(self.tze_bundle),
419        }
420    }
421
422    pub fn map_authorization<B: Authorization>(
423        self,
424        f_transparent: impl transparent::MapAuth<A::TransparentAuth, B::TransparentAuth>,
425        f_sapling: impl sapling::MapAuth<A::SaplingAuth, B::SaplingAuth>,
426        mut f_orchard: impl orchard_serialization::MapAuth<A::OrchardAuth, B::OrchardAuth>,
427        #[cfg(feature = "zfuture")] f_tze: impl tze::MapAuth<A::TzeAuth, B::TzeAuth>,
428    ) -> TransactionData<B> {
429        TransactionData {
430            version: self.version,
431            consensus_branch_id: self.consensus_branch_id,
432            lock_time: self.lock_time,
433            expiry_height: self.expiry_height,
434            transparent_bundle: self
435                .transparent_bundle
436                .map(|b| b.map_authorization(f_transparent)),
437            sprout_bundle: self.sprout_bundle,
438            sapling_bundle: self.sapling_bundle.map(|b| b.map_authorization(f_sapling)),
439            orchard_bundle: self.orchard_bundle.map(|b| {
440                b.map_authorization(
441                    &mut f_orchard,
442                    |f, _, s| f.map_spend_auth(s),
443                    |f, a| f.map_authorization(a),
444                )
445            }),
446            #[cfg(feature = "zfuture")]
447            tze_bundle: self.tze_bundle.map(|b| b.map_authorization(f_tze)),
448        }
449    }
450}
451
452impl<A: Authorization> TransactionData<A> {
453    pub fn sapling_value_balance(&self) -> Amount {
454        self.sapling_bundle
455            .as_ref()
456            .map_or(Amount::zero(), |b| b.value_balance)
457    }
458}
459
460impl TransactionData<Authorized> {
461    pub fn freeze(self) -> io::Result<Transaction> {
462        Transaction::from_data(self)
463    }
464}
465
466impl Transaction {
467    fn from_data(data: TransactionData<Authorized>) -> io::Result<Self> {
468        match data.version {
469            TxVersion::Sprout(_) | TxVersion::Overwinter | TxVersion::Sapling => {
470                Self::from_data_v4(data)
471            }
472            TxVersion::Zip225 => Ok(Self::from_data_v5(data)),
473            #[cfg(feature = "zfuture")]
474            TxVersion::ZFuture => Ok(Self::from_data_v5(data)),
475        }
476    }
477
478    fn from_data_v4(data: TransactionData<Authorized>) -> io::Result<Self> {
479        let mut tx = Transaction {
480            txid: TxId([0; 32]),
481            data,
482        };
483        let mut writer = HashWriter::default();
484        tx.write(&mut writer)?;
485        tx.txid.0.copy_from_slice(&writer.into_hash());
486        Ok(tx)
487    }
488
489    fn from_data_v5(data: TransactionData<Authorized>) -> Self {
490        let txid = to_txid(
491            data.version,
492            data.consensus_branch_id,
493            &data.digest(TxIdDigester),
494        );
495
496        Transaction { txid, data }
497    }
498
499    pub fn into_data(self) -> TransactionData<Authorized> {
500        self.data
501    }
502
503    pub fn txid(&self) -> TxId {
504        self.txid
505    }
506
507    pub fn read<R: Read>(reader: R, consensus_branch_id: BranchId) -> io::Result<Self> {
508        let mut reader = HashReader::new(reader);
509
510        let version = TxVersion::read(&mut reader)?;
511        match version {
512            TxVersion::Sprout(_) | TxVersion::Overwinter | TxVersion::Sapling => {
513                Self::read_v4(reader, version, consensus_branch_id)
514            }
515            TxVersion::Zip225 => Self::read_v5(reader.into_base_reader(), version),
516            #[cfg(feature = "zfuture")]
517            TxVersion::ZFuture => Self::read_v5(reader.into_base_reader(), version),
518        }
519    }
520
521    #[allow(clippy::redundant_closure)]
522    fn read_v4<R: Read>(
523        mut reader: HashReader<R>,
524        version: TxVersion,
525        consensus_branch_id: BranchId,
526    ) -> io::Result<Self> {
527        let transparent_bundle = Self::read_transparent(&mut reader)?;
528
529        let lock_time = reader.read_u32::<LittleEndian>()?;
530        let expiry_height: BlockHeight = if version.has_overwinter() {
531            reader.read_u32::<LittleEndian>()?.into()
532        } else {
533            0u32.into()
534        };
535
536        let (value_balance, shielded_spends, shielded_outputs) = if version.has_sapling() {
537            let vb = Self::read_amount(&mut reader)?;
538            #[allow(clippy::redundant_closure)]
539            let ss: Vec<SpendDescription<sapling::Authorized>> =
540                Vector::read(&mut reader, |r| SpendDescription::read(r))?;
541            #[allow(clippy::redundant_closure)]
542            let so: Vec<OutputDescription<sapling::GrothProofBytes>> =
543                Vector::read(&mut reader, |r| OutputDescription::read(r))?;
544            (vb, ss, so)
545        } else {
546            (Amount::zero(), vec![], vec![])
547        };
548
549        let sprout_bundle = if version.has_sprout() {
550            let joinsplits = Vector::read(&mut reader, |r| {
551                JsDescription::read(r, version.has_sapling())
552            })?;
553
554            if !joinsplits.is_empty() {
555                let mut bundle = sprout::Bundle {
556                    joinsplits,
557                    joinsplit_pubkey: [0; 32],
558                    joinsplit_sig: [0; 64],
559                };
560                reader.read_exact(&mut bundle.joinsplit_pubkey)?;
561                reader.read_exact(&mut bundle.joinsplit_sig)?;
562                Some(bundle)
563            } else {
564                None
565            }
566        } else {
567            None
568        };
569
570        let binding_sig = if version.has_sapling()
571            && !(shielded_spends.is_empty() && shielded_outputs.is_empty())
572        {
573            Some(redjubjub::Signature::read(&mut reader)?)
574        } else {
575            None
576        };
577
578        let mut txid = [0; 32];
579        let hash_bytes = reader.into_hash();
580        txid.copy_from_slice(&hash_bytes);
581
582        Ok(Transaction {
583            txid: TxId(txid),
584            data: TransactionData {
585                version,
586                consensus_branch_id,
587                lock_time,
588                expiry_height,
589                transparent_bundle,
590                sprout_bundle,
591                sapling_bundle: binding_sig.map(|binding_sig| sapling::Bundle {
592                    value_balance,
593                    shielded_spends,
594                    shielded_outputs,
595                    authorization: sapling::Authorized { binding_sig },
596                }),
597                orchard_bundle: None,
598                #[cfg(feature = "zfuture")]
599                tze_bundle: None,
600            },
601        })
602    }
603
604    fn read_transparent<R: Read>(
605        mut reader: R,
606    ) -> io::Result<Option<transparent::Bundle<transparent::Authorized>>> {
607        let vin = Vector::read(&mut reader, TxIn::read)?;
608        let vout = Vector::read(&mut reader, TxOut::read)?;
609        Ok(if vin.is_empty() && vout.is_empty() {
610            None
611        } else {
612            Some(transparent::Bundle {
613                vin,
614                vout,
615                authorization: transparent::Authorized,
616            })
617        })
618    }
619
620    fn read_amount<R: Read>(mut reader: R) -> io::Result<Amount> {
621        let mut tmp = [0; 8];
622        reader.read_exact(&mut tmp)?;
623        Amount::from_i64_le_bytes(tmp)
624            .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "valueBalance out of range"))
625    }
626
627    fn read_v5<R: Read>(mut reader: R, version: TxVersion) -> io::Result<Self> {
628        let (consensus_branch_id, lock_time, expiry_height) =
629            Self::read_v5_header_fragment(&mut reader)?;
630        let transparent_bundle = Self::read_transparent(&mut reader)?;
631        let sapling_bundle = Self::read_v5_sapling(&mut reader)?;
632        let orchard_bundle = orchard_serialization::read_v5_bundle(&mut reader)?;
633
634        #[cfg(feature = "zfuture")]
635        let tze_bundle = if version.has_tze() {
636            Self::read_tze(&mut reader)?
637        } else {
638            None
639        };
640
641        let data = TransactionData {
642            version,
643            consensus_branch_id,
644            lock_time,
645            expiry_height,
646            transparent_bundle,
647            sprout_bundle: None,
648            sapling_bundle,
649            orchard_bundle,
650            #[cfg(feature = "zfuture")]
651            tze_bundle,
652        };
653
654        Ok(Self::from_data_v5(data))
655    }
656
657    fn read_v5_header_fragment<R: Read>(mut reader: R) -> io::Result<(BranchId, u32, BlockHeight)> {
658        let consensus_branch_id = reader.read_u32::<LittleEndian>().and_then(|value| {
659            BranchId::try_from(value).map_err(|e| {
660                io::Error::new(
661                    io::ErrorKind::InvalidInput,
662                    "invalid consensus branch id: ".to_owned() + e,
663                )
664            })
665        })?;
666        let lock_time = reader.read_u32::<LittleEndian>()?;
667        let expiry_height: BlockHeight = reader.read_u32::<LittleEndian>()?.into();
668        Ok((consensus_branch_id, lock_time, expiry_height))
669    }
670
671    #[allow(clippy::redundant_closure)]
672    fn read_v5_sapling<R: Read>(
673        mut reader: R,
674    ) -> io::Result<Option<sapling::Bundle<sapling::Authorized>>> {
675        let sd_v5s = Vector::read(&mut reader, SpendDescriptionV5::read)?;
676        let od_v5s = Vector::read(&mut reader, OutputDescriptionV5::read)?;
677        let n_spends = sd_v5s.len();
678        let n_outputs = od_v5s.len();
679        let value_balance = if n_spends > 0 || n_outputs > 0 {
680            Self::read_amount(&mut reader)?
681        } else {
682            Amount::zero()
683        };
684
685        let anchor = if n_spends > 0 {
686            Some(sapling::read_base(&mut reader, "anchor")?)
687        } else {
688            None
689        };
690
691        let v_spend_proofs = Array::read(&mut reader, n_spends, |r| sapling::read_zkproof(r))?;
692        let v_spend_auth_sigs = Array::read(&mut reader, n_spends, |r| {
693            SpendDescription::read_spend_auth_sig(r)
694        })?;
695        let v_output_proofs = Array::read(&mut reader, n_outputs, |r| sapling::read_zkproof(r))?;
696
697        let binding_sig = if n_spends > 0 || n_outputs > 0 {
698            Some(redjubjub::Signature::read(&mut reader)?)
699        } else {
700            None
701        };
702
703        let shielded_spends = sd_v5s
704            .into_iter()
705            .zip(
706                v_spend_proofs
707                    .into_iter()
708                    .zip(v_spend_auth_sigs.into_iter()),
709            )
710            .map(|(sd_5, (zkproof, spend_auth_sig))| {
711                // the following `unwrap` is safe because we know n_spends > 0.
712                sd_5.into_spend_description(anchor.unwrap(), zkproof, spend_auth_sig)
713            })
714            .collect();
715
716        let shielded_outputs = od_v5s
717            .into_iter()
718            .zip(v_output_proofs.into_iter())
719            .map(|(od_5, zkproof)| od_5.into_output_description(zkproof))
720            .collect();
721
722        Ok(binding_sig.map(|binding_sig| sapling::Bundle {
723            value_balance,
724            shielded_spends,
725            shielded_outputs,
726            authorization: sapling::Authorized { binding_sig },
727        }))
728    }
729
730    #[cfg(feature = "zfuture")]
731    fn read_tze<R: Read>(mut reader: &mut R) -> io::Result<Option<tze::Bundle<tze::Authorized>>> {
732        let vin = Vector::read(&mut reader, TzeIn::read)?;
733        let vout = Vector::read(&mut reader, TzeOut::read)?;
734        Ok(if vin.is_empty() && vout.is_empty() {
735            None
736        } else {
737            Some(tze::Bundle {
738                vin,
739                vout,
740                authorization: tze::Authorized,
741            })
742        })
743    }
744
745    pub fn write<W: Write>(&self, writer: W) -> io::Result<()> {
746        match self.version {
747            TxVersion::Sprout(_) | TxVersion::Overwinter | TxVersion::Sapling => {
748                self.write_v4(writer)
749            }
750            TxVersion::Zip225 => self.write_v5(writer),
751            #[cfg(feature = "zfuture")]
752            TxVersion::ZFuture => self.write_v5(writer),
753        }
754    }
755
756    pub fn write_v4<W: Write>(&self, mut writer: W) -> io::Result<()> {
757        self.version.write(&mut writer)?;
758
759        self.write_transparent(&mut writer)?;
760        writer.write_u32::<LittleEndian>(self.lock_time)?;
761        if self.version.has_overwinter() {
762            writer.write_u32::<LittleEndian>(u32::from(self.expiry_height))?;
763        }
764
765        if self.version.has_sapling() {
766            writer.write_all(
767                &self
768                    .sapling_bundle
769                    .as_ref()
770                    .map_or(Amount::zero(), |b| b.value_balance)
771                    .to_i64_le_bytes(),
772            )?;
773            Vector::write(
774                &mut writer,
775                self.sapling_bundle
776                    .as_ref()
777                    .map_or(&[], |b| &b.shielded_spends),
778                |w, e| e.write_v4(w),
779            )?;
780            Vector::write(
781                &mut writer,
782                self.sapling_bundle
783                    .as_ref()
784                    .map_or(&[], |b| &b.shielded_outputs),
785                |w, e| e.write_v4(w),
786            )?;
787        } else if self.sapling_bundle.is_some() {
788            return Err(io::Error::new(
789                io::ErrorKind::InvalidInput,
790                "Sapling components may not be present if Sapling is not active.",
791            ));
792        }
793
794        if self.version.has_sprout() {
795            if let Some(bundle) = self.sprout_bundle.as_ref() {
796                Vector::write(&mut writer, &bundle.joinsplits, |w, e| e.write(w))?;
797                writer.write_all(&bundle.joinsplit_pubkey)?;
798                writer.write_all(&bundle.joinsplit_sig)?;
799            } else {
800                CompactSize::write(&mut writer, 0)?;
801            }
802        }
803
804        if self.version.has_sapling() {
805            if let Some(bundle) = self.sapling_bundle.as_ref() {
806                bundle.authorization.binding_sig.write(&mut writer)?;
807            }
808        }
809
810        if self.orchard_bundle.is_some() {
811            return Err(io::Error::new(
812                io::ErrorKind::InvalidInput,
813                "Orchard components cannot be present when serializing to the V4 transaction format."
814            ));
815        }
816
817        Ok(())
818    }
819
820    pub fn write_transparent<W: Write>(&self, mut writer: W) -> io::Result<()> {
821        if let Some(bundle) = &self.transparent_bundle {
822            Vector::write(&mut writer, &bundle.vin, |w, e| e.write(w))?;
823            Vector::write(&mut writer, &bundle.vout, |w, e| e.write(w))?;
824        } else {
825            CompactSize::write(&mut writer, 0)?;
826            CompactSize::write(&mut writer, 0)?;
827        }
828
829        Ok(())
830    }
831
832    pub fn write_v5<W: Write>(&self, mut writer: W) -> io::Result<()> {
833        if self.sprout_bundle.is_some() {
834            return Err(io::Error::new(
835                io::ErrorKind::InvalidInput,
836                "Sprout components cannot be present when serializing to the V5 transaction format.",
837            ));
838        }
839        self.write_v5_header(&mut writer)?;
840        self.write_transparent(&mut writer)?;
841        self.write_v5_sapling(&mut writer)?;
842        orchard_serialization::write_v5_bundle(self.orchard_bundle.as_ref(), &mut writer)?;
843        #[cfg(feature = "zfuture")]
844        self.write_tze(&mut writer)?;
845        Ok(())
846    }
847
848    pub fn write_v5_header<W: Write>(&self, mut writer: W) -> io::Result<()> {
849        self.version.write(&mut writer)?;
850        writer.write_u32::<LittleEndian>(u32::from(self.consensus_branch_id))?;
851        writer.write_u32::<LittleEndian>(self.lock_time)?;
852        writer.write_u32::<LittleEndian>(u32::from(self.expiry_height))?;
853        Ok(())
854    }
855
856    pub fn write_v5_sapling<W: Write>(&self, mut writer: W) -> io::Result<()> {
857        if let Some(bundle) = &self.sapling_bundle {
858            Vector::write(&mut writer, &bundle.shielded_spends, |w, e| {
859                e.write_v5_without_witness_data(w)
860            })?;
861
862            Vector::write(&mut writer, &bundle.shielded_outputs, |w, e| {
863                e.write_v5_without_proof(w)
864            })?;
865
866            if !(bundle.shielded_spends.is_empty() && bundle.shielded_outputs.is_empty()) {
867                writer.write_all(&bundle.value_balance.to_i64_le_bytes())?;
868            }
869            if !bundle.shielded_spends.is_empty() {
870                writer.write_all(bundle.shielded_spends[0].anchor.to_repr().as_ref())?;
871            }
872
873            Array::write(
874                &mut writer,
875                bundle.shielded_spends.iter().map(|s| s.zkproof),
876                |w, e| w.write_all(e),
877            )?;
878            Array::write(
879                &mut writer,
880                bundle.shielded_spends.iter().map(|s| s.spend_auth_sig),
881                |w, e| e.write(w),
882            )?;
883
884            Array::write(
885                &mut writer,
886                bundle.shielded_outputs.iter().map(|s| s.zkproof),
887                |w, e| w.write_all(e),
888            )?;
889
890            if !(bundle.shielded_spends.is_empty() && bundle.shielded_outputs.is_empty()) {
891                bundle.authorization.binding_sig.write(&mut writer)?;
892            }
893        } else {
894            CompactSize::write(&mut writer, 0)?;
895            CompactSize::write(&mut writer, 0)?;
896        }
897
898        Ok(())
899    }
900
901    #[cfg(feature = "zfuture")]
902    pub fn write_tze<W: Write>(&self, mut writer: W) -> io::Result<()> {
903        if let Some(bundle) = &self.tze_bundle {
904            Vector::write(&mut writer, &bundle.vin, |w, e| e.write(w))?;
905            Vector::write(&mut writer, &bundle.vout, |w, e| e.write(w))?;
906        } else {
907            CompactSize::write(&mut writer, 0)?;
908            CompactSize::write(&mut writer, 0)?;
909        }
910
911        Ok(())
912    }
913
914    // TODO: should this be moved to `from_data` and stored?
915    pub fn auth_commitment(&self) -> Blake2bHash {
916        self.data.digest(BlockTxCommitmentDigester)
917    }
918}
919
920#[derive(Clone, Debug)]
921pub struct TransparentDigests<A> {
922    pub prevouts_digest: A,
923    pub sequence_digest: A,
924    pub outputs_digest: A,
925}
926
927#[derive(Clone, Debug)]
928pub struct TzeDigests<A> {
929    pub inputs_digest: A,
930    pub outputs_digest: A,
931    pub per_input_digest: Option<A>,
932}
933
934#[derive(Clone, Debug)]
935pub struct TxDigests<A> {
936    pub header_digest: A,
937    pub transparent_digests: Option<TransparentDigests<A>>,
938    pub sapling_digest: Option<A>,
939    pub orchard_digest: Option<A>,
940    #[cfg(feature = "zfuture")]
941    pub tze_digests: Option<TzeDigests<A>>,
942}
943
944pub trait TransactionDigest<A: Authorization> {
945    type HeaderDigest;
946    type TransparentDigest;
947    type SaplingDigest;
948    type OrchardDigest;
949
950    #[cfg(feature = "zfuture")]
951    type TzeDigest;
952
953    type Digest;
954
955    fn digest_header(
956        &self,
957        version: TxVersion,
958        consensus_branch_id: BranchId,
959        lock_time: u32,
960        expiry_height: BlockHeight,
961    ) -> Self::HeaderDigest;
962
963    fn digest_transparent(
964        &self,
965        transparent_bundle: Option<&transparent::Bundle<A::TransparentAuth>>,
966    ) -> Self::TransparentDigest;
967
968    fn digest_sapling(
969        &self,
970        sapling_bundle: Option<&sapling::Bundle<A::SaplingAuth>>,
971    ) -> Self::SaplingDigest;
972
973    fn digest_orchard(
974        &self,
975        orchard_bundle: Option<&orchard::Bundle<A::OrchardAuth, Amount>>,
976    ) -> Self::OrchardDigest;
977
978    #[cfg(feature = "zfuture")]
979    fn digest_tze(&self, tze_bundle: Option<&tze::Bundle<A::TzeAuth>>) -> Self::TzeDigest;
980
981    fn combine(
982        &self,
983        header_digest: Self::HeaderDigest,
984        transparent_digest: Self::TransparentDigest,
985        sapling_digest: Self::SaplingDigest,
986        orchard_digest: Self::OrchardDigest,
987        #[cfg(feature = "zfuture")] tze_digest: Self::TzeDigest,
988    ) -> Self::Digest;
989}
990
991pub enum DigestError {
992    NotSigned,
993}
994
995#[cfg(any(test, feature = "test-dependencies"))]
996pub mod testing {
997    use proptest::prelude::*;
998
999    use crate::consensus::BranchId;
1000
1001    use super::{
1002        components::{
1003            orchard::testing::{self as orchard},
1004            sapling::testing::{self as sapling},
1005            transparent::testing::{self as transparent},
1006        },
1007        Authorized, Transaction, TransactionData, TxId, TxVersion,
1008    };
1009
1010    #[cfg(feature = "zfuture")]
1011    use super::components::tze::testing::{self as tze};
1012
1013    pub fn arb_txid() -> impl Strategy<Value = TxId> {
1014        prop::array::uniform32(any::<u8>()).prop_map(TxId::from_bytes)
1015    }
1016
1017    pub fn arb_tx_version(branch_id: BranchId) -> impl Strategy<Value = TxVersion> {
1018        match branch_id {
1019            BranchId::Sprout => (1..=2u32).prop_map(TxVersion::Sprout).boxed(),
1020            BranchId::Overwinter => Just(TxVersion::Overwinter).boxed(),
1021            BranchId::Sapling | BranchId::Blossom | BranchId::Heartwood | BranchId::Canopy => {
1022                Just(TxVersion::Sapling).boxed()
1023            }
1024            BranchId::Nu5 => Just(TxVersion::Zip225).boxed(),
1025            #[cfg(feature = "zfuture")]
1026            BranchId::ZFuture => Just(TxVersion::ZFuture).boxed(),
1027        }
1028    }
1029
1030    #[cfg(not(feature = "zfuture"))]
1031    prop_compose! {
1032        pub fn arb_txdata(consensus_branch_id: BranchId)(
1033            version in arb_tx_version(consensus_branch_id),
1034        )(
1035            lock_time in any::<u32>(),
1036            expiry_height in any::<u32>(),
1037            transparent_bundle in transparent::arb_bundle(),
1038            sapling_bundle in sapling::arb_bundle_for_version(version),
1039            orchard_bundle in orchard::arb_bundle_for_version(version),
1040            version in Just(version)
1041        ) -> TransactionData<Authorized> {
1042            TransactionData {
1043                version,
1044                consensus_branch_id,
1045                lock_time,
1046                expiry_height: expiry_height.into(),
1047                transparent_bundle,
1048                sprout_bundle: None,
1049                sapling_bundle,
1050                orchard_bundle
1051            }
1052        }
1053    }
1054
1055    #[cfg(feature = "zfuture")]
1056    prop_compose! {
1057        pub fn arb_txdata(consensus_branch_id: BranchId)(
1058            version in arb_tx_version(consensus_branch_id),
1059        )(
1060            lock_time in any::<u32>(),
1061            expiry_height in any::<u32>(),
1062            transparent_bundle in transparent::arb_bundle(),
1063            sapling_bundle in sapling::arb_bundle_for_version(version),
1064            orchard_bundle in orchard::arb_bundle_for_version(version),
1065            tze_bundle in tze::arb_bundle(consensus_branch_id),
1066            version in Just(version)
1067        ) -> TransactionData<Authorized> {
1068            TransactionData {
1069                version,
1070                consensus_branch_id,
1071                lock_time,
1072                expiry_height: expiry_height.into(),
1073                transparent_bundle,
1074                sprout_bundle: None,
1075                sapling_bundle,
1076                orchard_bundle,
1077                tze_bundle
1078            }
1079        }
1080    }
1081
1082    prop_compose! {
1083        pub fn arb_tx(branch_id: BranchId)(tx_data in arb_txdata(branch_id)) -> Transaction {
1084            Transaction::from_data(tx_data).unwrap()
1085        }
1086    }
1087}