Skip to main content

zebra_chain/transaction/
serialize.rs

1//! Contains impls of `ZcashSerialize`, `ZcashDeserialize` for all of the
2//! transaction types, so that all of the serialization logic is in one place.
3
4use std::{borrow::Borrow, io, sync::Arc};
5
6use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
7use halo2::pasta::group::ff::PrimeField;
8use hex::FromHex;
9use reddsa::{orchard::Binding, orchard::SpendAuth, Signature};
10
11use crate::{
12    amount,
13    block::MAX_BLOCK_BYTES,
14    parameters::{OVERWINTER_VERSION_GROUP_ID, SAPLING_VERSION_GROUP_ID, TX_V5_VERSION_GROUP_ID},
15    primitives::{Halo2Proof, ZkSnarkProof},
16    serialization::{
17        zcash_deserialize_external_count, zcash_serialize_empty_list,
18        zcash_serialize_external_count, AtLeastOne, CompactSizeMessage, ReadZcashExt,
19        SerializationError, TrustedPreallocate, ZcashDeserialize, ZcashDeserializeInto,
20        ZcashSerialize,
21    },
22};
23
24use crate::parameters::TX_V6_VERSION_GROUP_ID;
25
26use super::*;
27use crate::sapling;
28
29impl ZcashDeserialize for jubjub::Fq {
30    fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
31        let possible_scalar = jubjub::Fq::from_bytes(&reader.read_32_bytes()?);
32
33        if possible_scalar.is_some().into() {
34            Ok(possible_scalar.unwrap())
35        } else {
36            Err(SerializationError::Parse(
37                "Invalid jubjub::Fq, input not canonical",
38            ))
39        }
40    }
41}
42
43impl ZcashDeserialize for pallas::Scalar {
44    fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
45        let possible_scalar = pallas::Scalar::from_repr(reader.read_32_bytes()?);
46
47        if possible_scalar.is_some().into() {
48            Ok(possible_scalar.unwrap())
49        } else {
50            Err(SerializationError::Parse(
51                "Invalid pallas::Scalar, input not canonical",
52            ))
53        }
54    }
55}
56
57impl ZcashDeserialize for pallas::Base {
58    fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
59        let possible_field_element = pallas::Base::from_repr(reader.read_32_bytes()?);
60
61        if possible_field_element.is_some().into() {
62            Ok(possible_field_element.unwrap())
63        } else {
64            Err(SerializationError::Parse(
65                "Invalid pallas::Base, input not canonical",
66            ))
67        }
68    }
69}
70
71impl<P: ZkSnarkProof> ZcashSerialize for JoinSplitData<P> {
72    fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
73        // Denoted as `nJoinSplit` and `vJoinSplit` in the spec.
74        let joinsplits: Vec<_> = self.joinsplits().cloned().collect();
75        joinsplits.zcash_serialize(&mut writer)?;
76
77        // Denoted as `joinSplitPubKey` in the spec.
78        writer.write_all(&<[u8; 32]>::from(self.pub_key)[..])?;
79
80        // Denoted as `joinSplitSig` in the spec.
81        writer.write_all(&<[u8; 64]>::from(self.sig)[..])?;
82        Ok(())
83    }
84}
85
86impl<P> ZcashDeserialize for Option<JoinSplitData<P>>
87where
88    P: ZkSnarkProof,
89    sprout::JoinSplit<P>: TrustedPreallocate,
90{
91    fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
92        // Denoted as `nJoinSplit` and `vJoinSplit` in the spec.
93        let joinsplits: Vec<sprout::JoinSplit<P>> = (&mut reader).zcash_deserialize_into()?;
94        match joinsplits.split_first() {
95            None => Ok(None),
96            Some((first, rest)) => {
97                // Denoted as `joinSplitPubKey` in the spec.
98                let pub_key = reader.read_32_bytes()?.into();
99                // Denoted as `joinSplitSig` in the spec.
100                let sig = reader.read_64_bytes()?.into();
101                Ok(Some(JoinSplitData {
102                    first: first.clone(),
103                    rest: rest.to_vec(),
104                    pub_key,
105                    sig,
106                }))
107            }
108        }
109    }
110}
111
112// Transaction::V5 serializes sapling ShieldedData in a single continuous byte
113// range, so we can implement its serialization and deserialization separately.
114// (Unlike V4, where it must be serialized as part of the transaction.)
115
116impl ZcashSerialize for Option<sapling::ShieldedData<sapling::SharedAnchor>> {
117    fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
118        match self {
119            None => {
120                // Denoted as `nSpendsSapling` in the spec.
121                zcash_serialize_empty_list(&mut writer)?;
122                // Denoted as `nOutputsSapling` in the spec.
123                zcash_serialize_empty_list(&mut writer)?;
124            }
125            Some(sapling_shielded_data) => {
126                sapling_shielded_data.zcash_serialize(&mut writer)?;
127            }
128        }
129        Ok(())
130    }
131}
132
133impl ZcashSerialize for sapling::ShieldedData<sapling::SharedAnchor> {
134    fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
135        // Collect arrays for Spends
136        // There's no unzip3, so we have to unzip twice.
137        let (spend_prefixes, spend_proofs_sigs): (Vec<_>, Vec<_>) = self
138            .spends()
139            .cloned()
140            .map(sapling::Spend::<sapling::SharedAnchor>::into_v5_parts)
141            .map(|(prefix, proof, sig)| (prefix, (proof, sig)))
142            .unzip();
143        let (spend_proofs, spend_sigs) = spend_proofs_sigs.into_iter().unzip();
144
145        // Collect arrays for Outputs
146        let (output_prefixes, output_proofs): (Vec<_>, _) = self
147            .outputs()
148            .cloned()
149            .map(sapling::Output::into_v5_parts)
150            .unzip();
151
152        // Denoted as `nSpendsSapling` and `vSpendsSapling` in the spec.
153        spend_prefixes.zcash_serialize(&mut writer)?;
154        // Denoted as `nOutputsSapling` and `vOutputsSapling` in the spec.
155        output_prefixes.zcash_serialize(&mut writer)?;
156
157        // Denoted as `valueBalanceSapling` in the spec.
158        self.value_balance.zcash_serialize(&mut writer)?;
159
160        // Denoted as `anchorSapling` in the spec.
161        // `TransferData` ensures this field is only present when there is at
162        // least one spend.
163        if let Some(shared_anchor) = self.shared_anchor() {
164            writer.write_all(&<[u8; 32]>::from(shared_anchor)[..])?;
165        }
166
167        // Denoted as `vSpendProofsSapling` in the spec.
168        zcash_serialize_external_count(&spend_proofs, &mut writer)?;
169        // Denoted as `vSpendAuthSigsSapling` in the spec.
170        zcash_serialize_external_count(&spend_sigs, &mut writer)?;
171
172        // Denoted as `vOutputProofsSapling` in the spec.
173        zcash_serialize_external_count(&output_proofs, &mut writer)?;
174
175        // Denoted as `bindingSigSapling` in the spec.
176        writer.write_all(&<[u8; 64]>::from(self.binding_sig)[..])?;
177
178        Ok(())
179    }
180}
181
182// we can't split ShieldedData out of Option<ShieldedData> deserialization,
183// because the counts are read along with the arrays.
184impl ZcashDeserialize for Option<sapling::ShieldedData<sapling::SharedAnchor>> {
185    #[allow(clippy::unwrap_in_result)]
186    fn zcash_deserialize<R: io::Read>(reader: R) -> Result<Self, SerializationError> {
187        deserialize_v5_sapling_shielded_data(reader, false)
188    }
189}
190
191/// Deserialize V5/V6 Sapling shielded data with an optional early coinbase
192/// rejection.
193///
194/// When `is_coinbase` is true, a non-zero `nSpendsSapling` count is rejected
195/// **before** allocating the spend vector, closing the late-validation gap
196/// described in GHSA-rgwx-8r98-p34c.
197#[allow(clippy::unwrap_in_result)]
198fn deserialize_v5_sapling_shielded_data<R: io::Read>(
199    mut reader: R,
200    is_coinbase: bool,
201) -> Result<Option<sapling::ShieldedData<sapling::SharedAnchor>>, SerializationError> {
202    // Denoted as `nSpendsSapling` in the spec โ€” read count before allocating.
203    let spend_count: CompactSizeMessage = (&mut reader).zcash_deserialize_into()?;
204    let spend_count: usize = spend_count.into();
205
206    // # Consensus
207    //
208    // > A coinbase transaction MUST NOT have any Spend descriptions.
209    //
210    // <https://zips.z.cash/protocol/protocol.pdf#txnconsensus>
211    //
212    // Reject before allocating to prevent a peer from forcing thousands of
213    // spend-prefix allocations for a transaction that will always be invalid.
214    if is_coinbase && spend_count > 0 {
215        return Err(SerializationError::Parse(
216            "coinbase transaction must not have Sapling spends",
217        ));
218    }
219
220    // Denoted as `vSpendsSapling` in the spec.
221    let spend_prefixes: Vec<sapling::SpendPrefixInTransactionV5> =
222        zcash_deserialize_external_count(spend_count, &mut reader)?;
223
224    // Denoted as `nOutputsSapling` and `vOutputsSapling` in the spec.
225    let output_prefixes: Vec<_> = (&mut reader).zcash_deserialize_into()?;
226
227    // nSpendsSapling and nOutputsSapling as variables
228    let spends_count = spend_prefixes.len();
229    let outputs_count = output_prefixes.len();
230
231    // All the other fields depend on having spends or outputs
232    if spend_prefixes.is_empty() && output_prefixes.is_empty() {
233        return Ok(None);
234    }
235
236    // Denoted as `valueBalanceSapling` in the spec.
237    let value_balance = (&mut reader).zcash_deserialize_into()?;
238
239    // Denoted as `anchorSapling` in the spec.
240    //
241    // # Consensus
242    //
243    // > Elements of a Spend description MUST be valid encodings of the types given above.
244    //
245    // https://zips.z.cash/protocol/protocol.pdf#spenddesc
246    //
247    // Type is `B^{[โ„“_{Sapling}_{Merkle}]}`, i.e. 32 bytes
248    //
249    // > LEOS2IP_{256}(anchorSapling), if present, MUST be less than ๐‘ž_๐•.
250    //
251    // https://zips.z.cash/protocol/protocol.pdf#spendencodingandconsensus
252    //
253    // Validated in [`crate::sapling::tree::Root::zcash_deserialize`].
254    let shared_anchor = if spends_count > 0 {
255        Some((&mut reader).zcash_deserialize_into()?)
256    } else {
257        None
258    };
259
260    // Denoted as `vSpendProofsSapling` in the spec.
261    //
262    // # Consensus
263    //
264    // > Elements of a Spend description MUST be valid encodings of the types given above.
265    //
266    // https://zips.z.cash/protocol/protocol.pdf#spenddesc
267    //
268    // Type is `ZKSpend.Proof`, described in
269    // https://zips.z.cash/protocol/protocol.pdf#grothencoding
270    // It is not enforced here; this just reads 192 bytes.
271    // The type is validated when validating the proof, see
272    // [`groth16::Item::try_from`]. In #3179 we plan to validate here instead.
273    let spend_proofs = zcash_deserialize_external_count(spends_count, &mut reader)?;
274
275    // Denoted as `vSpendAuthSigsSapling` in the spec.
276    //
277    // # Consensus
278    //
279    // > Elements of a Spend description MUST be valid encodings of the types given above.
280    //
281    // https://zips.z.cash/protocol/protocol.pdf#spenddesc
282    //
283    // Type is SpendAuthSig^{Sapling}.Signature, i.e.
284    // B^Y^{[ceiling(โ„“_G/8) + ceiling(bitlength(๐‘Ÿ_G)/8)]} i.e. 64 bytes
285    // https://zips.z.cash/protocol/protocol.pdf#concretereddsa
286    // See [`redjubjub::Signature<SpendAuth>::zcash_deserialize`].
287    let spend_sigs = zcash_deserialize_external_count(spends_count, &mut reader)?;
288
289    // Denoted as `vOutputProofsSapling` in the spec.
290    //
291    // # Consensus
292    //
293    // > Elements of an Output description MUST be valid encodings of the types given above.
294    //
295    // https://zips.z.cash/protocol/protocol.pdf#outputdesc
296    //
297    // Type is `ZKOutput.Proof`, described in
298    // https://zips.z.cash/protocol/protocol.pdf#grothencoding
299    // It is not enforced here; this just reads 192 bytes.
300    // The type is validated when validating the proof, see
301    // [`groth16::Item::try_from`]. In #3179 we plan to validate here instead.
302    let output_proofs = zcash_deserialize_external_count(outputs_count, &mut reader)?;
303
304    // Denoted as `bindingSigSapling` in the spec.
305    let binding_sig = reader.read_64_bytes()?.into();
306
307    // Create shielded spends from deserialized parts
308    let spends: Vec<_> = spend_prefixes
309        .into_iter()
310        .zip(spend_proofs)
311        .zip(spend_sigs)
312        .map(|((prefix, proof), sig)| {
313            sapling::Spend::<sapling::SharedAnchor>::from_v5_parts(prefix, proof, sig)
314        })
315        .collect();
316
317    // Create shielded outputs from deserialized parts
318    let outputs = output_prefixes
319        .into_iter()
320        .zip(output_proofs)
321        .map(|(prefix, proof)| sapling::Output::from_v5_parts(prefix, proof))
322        .collect();
323
324    // Create transfers
325    //
326    // # Consensus
327    //
328    // > The anchor of each Spend description MUST refer to some earlier
329    // > blockโ€™s final Sapling treestate. The anchor is encoded separately
330    // > in each Spend description for v4 transactions, or encoded once and
331    // > shared between all Spend descriptions in a v5 transaction.
332    //
333    // <https://zips.z.cash/protocol/protocol.pdf#spendsandoutputs>
334    //
335    // This rule is also implemented in
336    // [`zebra_state::service::check::anchor`] and
337    // [`zebra_chain::sapling::spend`].
338    //
339    // The "anchor encoding for v5 transactions" is implemented here.
340    let transfers = match shared_anchor {
341        Some(shared_anchor) => sapling::TransferData::SpendsAndMaybeOutputs {
342            shared_anchor,
343            spends: spends
344                .try_into()
345                .expect("checked spends when parsing shared anchor"),
346            maybe_outputs: outputs,
347        },
348        None => sapling::TransferData::JustOutputs {
349            outputs: outputs
350                .try_into()
351                .expect("checked spends or outputs and returned early"),
352        },
353    };
354
355    Ok(Some(sapling::ShieldedData {
356        value_balance,
357        transfers,
358        binding_sig,
359    }))
360}
361
362/// Serializes an optional Orchard-protocol bundle (v5 Orchard, v6 Orchard, or Ironwood).
363///
364/// All three share the same wire encoding: an empty action list (`nActions = 0`) when the bundle is
365/// absent, otherwise the bundle's fields.
366///
367/// "The fields flagsOrchard, valueBalanceOrchard, anchorOrchard, sizeProofsOrchard, proofsOrchard,
368/// and bindingSigOrchard are present if and only if nActionsOrchard > 0." โ€” `ยง` note of the second
369/// table of <https://zips.z.cash/protocol/protocol.pdf#txnencoding>
370fn zcash_serialize_optional_orchard_bundle<W: io::Write>(
371    shielded_data: Option<&orchard::ShieldedData>,
372    mut writer: W,
373) -> Result<(), io::Error> {
374    match shielded_data {
375        // Denoted as `nActionsOrchard` in the spec.
376        None => zcash_serialize_empty_list(&mut writer),
377        Some(shielded_data) => shielded_data.zcash_serialize(&mut writer),
378    }
379}
380
381impl ZcashSerialize for Option<orchard::ShieldedData> {
382    fn zcash_serialize<W: io::Write>(&self, writer: W) -> Result<(), io::Error> {
383        zcash_serialize_optional_orchard_bundle(self.as_ref(), writer)
384    }
385}
386
387impl ZcashSerialize for orchard::ShieldedData {
388    fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
389        // Split the AuthorizedAction
390        let (actions, sigs): (Vec<orchard::Action>, Vec<Signature<SpendAuth>>) = self
391            .actions
392            .iter()
393            .cloned()
394            .map(orchard::AuthorizedAction::into_parts)
395            .unzip();
396
397        // Denoted as `nActionsOrchard` and `vActionsOrchard` in the spec.
398        actions.zcash_serialize(&mut writer)?;
399
400        // Denoted as `flagsOrchard` in the spec.
401        self.flags.zcash_serialize(&mut writer)?;
402
403        // Denoted as `valueBalanceOrchard` in the spec.
404        self.value_balance.zcash_serialize(&mut writer)?;
405
406        // Denoted as `anchorOrchard` in the spec.
407        self.shared_anchor.zcash_serialize(&mut writer)?;
408
409        // Denoted as `sizeProofsOrchard` and `proofsOrchard` in the spec.
410        self.proof.zcash_serialize(&mut writer)?;
411
412        // Denoted as `vSpendAuthSigsOrchard` in the spec.
413        zcash_serialize_external_count(&sigs, &mut writer)?;
414
415        // Denoted as `bindingSigOrchard` in the spec.
416        self.binding_sig.zcash_serialize(&mut writer)?;
417
418        Ok(())
419    }
420}
421
422/// The `flagsOrchard` codec a v6 Orchard-protocol bundle newtype uses on deserialization.
423///
424/// A v6 Orchard or Ironwood bundle encodes identically on the wire to a v5 Orchard bundle (the flag
425/// byte is written as-is); the pools differ only in the reserved-bit rule applied to `flagsOrchard`.
426/// The `enableCrossAddress` bit (bit 2) is permitted only for the Ironwood pool, and is reserved
427/// (MUST be 0) for the Orchard pool regardless of tx version โ€” matching
428/// `orchard::bundle::Flags::from_byte`, which rejects bit 2 for `ValuePool::Orchard`. Tying the
429/// codec to the bundle type lets the (de)serializers below imply it instead of naming it explicitly.
430trait V6FlagCodec {
431    /// The flag codec: `orchard::Flags` reserves bit 2, `orchard::FlagsV6` permits it.
432    type Codec: ZcashDeserialize + Into<orchard::Flags>;
433}
434
435impl V6FlagCodec for orchard::ShieldedDataV6 {
436    // The v6 Orchard bundle parses with the pre-NU6.3 codec, exactly like v5.
437    type Codec = orchard::Flags;
438}
439
440impl V6FlagCodec for ironwood::ShieldedData {
441    // Only the Ironwood bundle permits `enableCrossAddress`.
442    type Codec = orchard::FlagsV6;
443}
444
445/// Deserializes the shared Orchard-protocol bundle body of a v6 bundle newtype `T`, using the flag
446/// codec [implied by `T`](V6FlagCodec) rather than one named at the call site.
447fn deserialize_v6_orchard_shielded_data<R, T>(
448    reader: R,
449) -> Result<Option<orchard::ShieldedData>, SerializationError>
450where
451    R: io::Read,
452    T: V6FlagCodec,
453{
454    deserialize_orchard_shielded_data::<R, T::Codec>(reader)
455}
456
457impl ZcashDeserialize for Option<orchard::ShieldedDataV6> {
458    fn zcash_deserialize<R: io::Read>(reader: R) -> Result<Self, SerializationError> {
459        Ok(
460            deserialize_v6_orchard_shielded_data::<R, orchard::ShieldedDataV6>(reader)?
461                .map(orchard::ShieldedDataV6::new),
462        )
463    }
464}
465
466impl ZcashSerialize for Option<orchard::ShieldedDataV6> {
467    fn zcash_serialize<W: io::Write>(&self, writer: W) -> Result<(), io::Error> {
468        zcash_serialize_optional_orchard_bundle(self.as_ref().map(|data| data.data()), writer)
469    }
470}
471
472impl ZcashDeserialize for Option<ironwood::ShieldedData> {
473    fn zcash_deserialize<R: io::Read>(reader: R) -> Result<Self, SerializationError> {
474        Ok(
475            deserialize_v6_orchard_shielded_data::<R, ironwood::ShieldedData>(reader)?
476                .map(orchard::ShieldedDataV6::new)
477                .map(ironwood::ShieldedData::new),
478        )
479    }
480}
481
482impl ZcashSerialize for Option<ironwood::ShieldedData> {
483    fn zcash_serialize<W: io::Write>(&self, writer: W) -> Result<(), io::Error> {
484        zcash_serialize_optional_orchard_bundle(self.as_ref().map(|data| data.data()), writer)
485    }
486}
487
488// we can't split ShieldedData out of Option<ShieldedData> deserialization,
489// because the counts are read along with the arrays.
490impl ZcashDeserialize for Option<orchard::ShieldedData> {
491    fn zcash_deserialize<R: io::Read>(reader: R) -> Result<Self, SerializationError> {
492        // The bare `Option<orchard::ShieldedData>` codec is the v5 Orchard bundle, which uses the
493        // pre-NU6.3 flag-byte format (`orchard::Flags`). v6 Orchard and Ironwood bundles use the
494        // `orchard::FlagsV6` newtype.
495        deserialize_orchard_shielded_data::<R, orchard::Flags>(reader)
496    }
497}
498
499/// Deserializes an `Option<orchard::ShieldedData>`, parsing the flags byte via the flag type `F`.
500///
501/// v5 Orchard bundles pass `F = orchard::Flags` (pre-NU6.3 format); v6 Orchard and Ironwood bundles
502/// pass `F = orchard::FlagsV6` (the NU6.3 format, which permits the `enableCrossAddress` flag).
503pub(crate) fn deserialize_orchard_shielded_data<R, F>(
504    mut reader: R,
505) -> Result<Option<orchard::ShieldedData>, SerializationError>
506where
507    R: io::Read,
508    F: ZcashDeserialize + Into<orchard::Flags>,
509{
510    // Denoted as `nActionsOrchard` and `vActionsOrchard` in the spec.
511    let actions: Vec<orchard::Action> = (&mut reader).zcash_deserialize_into()?;
512
513    // "The fields flagsOrchard, valueBalanceOrchard, anchorOrchard, sizeProofsOrchard,
514    // proofsOrchard , and bindingSigOrchard are present if and only if nActionsOrchard > 0."
515    // `ยง` note of the second table of https://zips.z.cash/protocol/protocol.pdf#txnencoding
516    if actions.is_empty() {
517        return Ok(None);
518    }
519
520    // # Consensus
521    //
522    // > Elements of an Action description MUST be canonical encodings of the types given above.
523    //
524    // https://zips.z.cash/protocol/protocol.pdf#actiondesc
525    //
526    // Some Action elements are validated in this function; they are described below.
527
528    // Denoted as `flagsOrchard` in the spec. The flag type `F` selects the reserved-bit rule
529    // (pre-NU6.3 reserves bits 2..7; NU6.3 reserves bits 3..7).
530    let flags: orchard::Flags = F::zcash_deserialize(&mut reader)?.into();
531
532    // Denoted as `valueBalanceOrchard` in the spec.
533    let value_balance: amount::Amount = (&mut reader).zcash_deserialize_into()?;
534
535    // Denoted as `anchorOrchard` in the spec.
536    // Consensus: type is `{0 .. ๐‘ž_โ„™ โˆ’ 1}`. See [`orchard::tree::Root::zcash_deserialize`].
537    let shared_anchor: orchard::tree::Root = (&mut reader).zcash_deserialize_into()?;
538
539    // Denoted as `sizeProofsOrchard` and `proofsOrchard` in the spec.
540    // Consensus: type is `ZKAction.Proof`, i.e. a byte sequence.
541    // https://zips.z.cash/protocol/protocol.pdf#halo2encoding
542    let proof: Halo2Proof = (&mut reader).zcash_deserialize_into()?;
543
544    // Denoted as `vSpendAuthSigsOrchard` in the spec.
545    // Consensus: this validates the `spendAuthSig` elements, whose type is
546    // SpendAuthSig^{Orchard}.Signature, i.e.
547    // B^Y^{[ceiling(โ„“_G/8) + ceiling(bitlength(๐‘Ÿ_G)/8)]} i.e. 64 bytes
548    // See [`Signature::zcash_deserialize`].
549    let sigs: Vec<Signature<SpendAuth>> =
550        zcash_deserialize_external_count(actions.len(), &mut reader)?;
551
552    // Denoted as `bindingSigOrchard` in the spec.
553    let binding_sig: Signature<Binding> = (&mut reader).zcash_deserialize_into()?;
554
555    // Create the AuthorizedAction from deserialized parts
556    let authorized_actions: Vec<orchard::AuthorizedAction> = actions
557        .into_iter()
558        .zip(sigs)
559        .map(|(action, spend_auth_sig)| {
560            orchard::AuthorizedAction::from_parts(action, spend_auth_sig)
561        })
562        .collect();
563
564    let actions: AtLeastOne<orchard::AuthorizedAction> = authorized_actions.try_into()?;
565
566    Ok(Some(orchard::ShieldedData {
567        flags,
568        value_balance,
569        shared_anchor,
570        proof,
571        actions,
572        binding_sig,
573    }))
574}
575
576impl<T: reddsa::SigType> ZcashSerialize for reddsa::Signature<T> {
577    fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
578        writer.write_all(&<[u8; 64]>::from(*self)[..])?;
579        Ok(())
580    }
581}
582
583impl<T: reddsa::SigType> ZcashDeserialize for reddsa::Signature<T> {
584    fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
585        Ok(reader.read_64_bytes()?.into())
586    }
587}
588
589impl ZcashSerialize for Transaction {
590    #[allow(clippy::unwrap_in_result)]
591    fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
592        // Post-Sapling, transaction size is limited to MAX_BLOCK_BYTES.
593        // (Strictly, the maximum transaction size is about 1.5 kB less,
594        // because blocks also include a block header.)
595        //
596        // Currently, all transaction structs are parsed as part of a
597        // block. So we don't need to check transaction size here, until
598        // we start parsing mempool transactions, or generating our own
599        // transactions (see #483).
600        //
601        // Since we checkpoint on Canopy activation, we won't ever need
602        // to check the smaller pre-Sapling transaction size limit.
603
604        // Denoted as `header` in the spec, contains the `fOverwintered` flag and the `version` field.
605        // Write `version` and set the `fOverwintered` bit if necessary
606        let overwintered_flag = if self.is_overwintered() { 1 << 31 } else { 0 };
607        let version = overwintered_flag | self.version();
608
609        writer.write_u32::<LittleEndian>(version)?;
610
611        match self {
612            Transaction::V1 {
613                inputs,
614                outputs,
615                lock_time,
616            } => {
617                // Denoted as `tx_in_count` and `tx_in` in the spec.
618                inputs.zcash_serialize(&mut writer)?;
619
620                // Denoted as `tx_out_count` and `tx_out` in the spec.
621                outputs.zcash_serialize(&mut writer)?;
622
623                // Denoted as `lock_time` in the spec.
624                lock_time.zcash_serialize(&mut writer)?;
625            }
626            Transaction::V2 {
627                inputs,
628                outputs,
629                lock_time,
630                joinsplit_data,
631            } => {
632                // Denoted as `tx_in_count` and `tx_in` in the spec.
633                inputs.zcash_serialize(&mut writer)?;
634
635                // Denoted as `tx_out_count` and `tx_out` in the spec.
636                outputs.zcash_serialize(&mut writer)?;
637
638                // Denoted as `lock_time` in the spec.
639                lock_time.zcash_serialize(&mut writer)?;
640
641                // A bundle of fields denoted in the spec as `nJoinSplit`, `vJoinSplit`,
642                // `joinSplitPubKey` and `joinSplitSig`.
643                match joinsplit_data {
644                    // Write 0 for nJoinSplits to signal no JoinSplitData.
645                    None => zcash_serialize_empty_list(writer)?,
646                    Some(jsd) => jsd.zcash_serialize(&mut writer)?,
647                }
648            }
649            Transaction::V3 {
650                inputs,
651                outputs,
652                lock_time,
653                expiry_height,
654                joinsplit_data,
655            } => {
656                // Denoted as `nVersionGroupId` in the spec.
657                writer.write_u32::<LittleEndian>(OVERWINTER_VERSION_GROUP_ID)?;
658
659                // Denoted as `tx_in_count` and `tx_in` in the spec.
660                inputs.zcash_serialize(&mut writer)?;
661
662                // Denoted as `tx_out_count` and `tx_out` in the spec.
663                outputs.zcash_serialize(&mut writer)?;
664
665                // Denoted as `lock_time` in the spec.
666                lock_time.zcash_serialize(&mut writer)?;
667
668                writer.write_u32::<LittleEndian>(expiry_height.0)?;
669
670                // A bundle of fields denoted in the spec as `nJoinSplit`, `vJoinSplit`,
671                // `joinSplitPubKey` and `joinSplitSig`.
672                match joinsplit_data {
673                    // Write 0 for nJoinSplits to signal no JoinSplitData.
674                    None => zcash_serialize_empty_list(writer)?,
675                    Some(jsd) => jsd.zcash_serialize(&mut writer)?,
676                }
677            }
678            Transaction::V4 {
679                inputs,
680                outputs,
681                lock_time,
682                expiry_height,
683                sapling_shielded_data,
684                joinsplit_data,
685            } => {
686                // Transaction V4 spec:
687                // https://zips.z.cash/protocol/protocol.pdf#txnencoding
688
689                // Denoted as `nVersionGroupId` in the spec.
690                writer.write_u32::<LittleEndian>(SAPLING_VERSION_GROUP_ID)?;
691
692                // Denoted as `tx_in_count` and `tx_in` in the spec.
693                inputs.zcash_serialize(&mut writer)?;
694
695                // Denoted as `tx_out_count` and `tx_out` in the spec.
696                outputs.zcash_serialize(&mut writer)?;
697
698                // Denoted as `lock_time` in the spec.
699                lock_time.zcash_serialize(&mut writer)?;
700
701                // Denoted as `nExpiryHeight` in the spec.
702                writer.write_u32::<LittleEndian>(expiry_height.0)?;
703
704                // The previous match arms serialize in one go, because the
705                // internal structure happens to nicely line up with the
706                // serialized structure. However, this is not possible for
707                // version 4 transactions, as the binding_sig for the
708                // ShieldedData is placed at the end of the transaction. So
709                // instead we have to interleave serialization of the
710                // ShieldedData and the JoinSplitData.
711
712                match sapling_shielded_data {
713                    None => {
714                        // Signal no value balance.
715                        writer.write_i64::<LittleEndian>(0)?;
716                        // Signal no shielded spends and no shielded outputs.
717                        zcash_serialize_empty_list(&mut writer)?;
718                        zcash_serialize_empty_list(&mut writer)?;
719                    }
720                    Some(sapling_shielded_data) => {
721                        // Denoted as `valueBalanceSapling` in the spec.
722                        sapling_shielded_data
723                            .value_balance
724                            .zcash_serialize(&mut writer)?;
725
726                        // Denoted as `nSpendsSapling` and `vSpendsSapling` in the spec.
727                        let spends: Vec<_> = sapling_shielded_data.spends().cloned().collect();
728                        spends.zcash_serialize(&mut writer)?;
729
730                        // Denoted as `nOutputsSapling` and `vOutputsSapling` in the spec.
731                        let outputs: Vec<_> = sapling_shielded_data
732                            .outputs()
733                            .cloned()
734                            .map(sapling::OutputInTransactionV4)
735                            .collect();
736                        outputs.zcash_serialize(&mut writer)?;
737                    }
738                }
739
740                // A bundle of fields denoted in the spec as `nJoinSplit`, `vJoinSplit`,
741                // `joinSplitPubKey` and `joinSplitSig`.
742                match joinsplit_data {
743                    None => zcash_serialize_empty_list(&mut writer)?,
744                    Some(jsd) => jsd.zcash_serialize(&mut writer)?,
745                }
746
747                // Denoted as `bindingSigSapling` in the spec.
748                if let Some(shielded_data) = sapling_shielded_data {
749                    writer.write_all(&<[u8; 64]>::from(shielded_data.binding_sig)[..])?;
750                }
751            }
752
753            Transaction::V5 {
754                network_upgrade,
755                lock_time,
756                expiry_height,
757                inputs,
758                outputs,
759                sapling_shielded_data,
760                orchard_shielded_data,
761            } => {
762                // Transaction V5 spec:
763                // https://zips.z.cash/protocol/protocol.pdf#txnencoding
764
765                // Denoted as `nVersionGroupId` in the spec.
766                writer.write_u32::<LittleEndian>(TX_V5_VERSION_GROUP_ID)?;
767
768                // Denoted as `nConsensusBranchId` in the spec.
769                writer.write_u32::<LittleEndian>(u32::from(
770                    network_upgrade
771                        .branch_id()
772                        .expect("valid transactions must have a network upgrade with a branch id"),
773                ))?;
774
775                // Denoted as `lock_time` in the spec.
776                lock_time.zcash_serialize(&mut writer)?;
777
778                // Denoted as `nExpiryHeight` in the spec.
779                writer.write_u32::<LittleEndian>(expiry_height.0)?;
780
781                // Denoted as `tx_in_count` and `tx_in` in the spec.
782                inputs.zcash_serialize(&mut writer)?;
783
784                // Denoted as `tx_out_count` and `tx_out` in the spec.
785                outputs.zcash_serialize(&mut writer)?;
786
787                // A bundle of fields denoted in the spec as `nSpendsSapling`, `vSpendsSapling`,
788                // `nOutputsSapling`,`vOutputsSapling`, `valueBalanceSapling`, `anchorSapling`,
789                // `vSpendProofsSapling`, `vSpendAuthSigsSapling`, `vOutputProofsSapling` and
790                // `bindingSigSapling`.
791                sapling_shielded_data.zcash_serialize(&mut writer)?;
792
793                // A bundle of fields denoted in the spec as `nActionsOrchard`, `vActionsOrchard`,
794                // `flagsOrchard`,`valueBalanceOrchard`, `anchorOrchard`, `sizeProofsOrchard`,
795                // `proofsOrchard`, `vSpendAuthSigsOrchard`, and `bindingSigOrchard`.
796                orchard_shielded_data.zcash_serialize(&mut writer)?;
797            }
798
799            Transaction::V6 {
800                network_upgrade,
801                lock_time,
802                expiry_height,
803                inputs,
804                outputs,
805                sapling_shielded_data,
806                orchard_shielded_data,
807                ironwood_shielded_data,
808            } => {
809                // Transaction V6 (Ironwood / NU6.3) spec:
810                // https://github.com/zcash/zips/pull/1301
811
812                // Denoted as `nVersionGroupId` in the spec.
813                writer.write_u32::<LittleEndian>(TX_V6_VERSION_GROUP_ID)?;
814
815                // Denoted as `nConsensusBranchId` in the spec.
816                writer.write_u32::<LittleEndian>(u32::from(
817                    network_upgrade
818                        .branch_id()
819                        .expect("valid transactions must have a network upgrade with a branch id"),
820                ))?;
821
822                // Denoted as `lock_time` in the spec.
823                lock_time.zcash_serialize(&mut writer)?;
824
825                // Denoted as `nExpiryHeight` in the spec.
826                writer.write_u32::<LittleEndian>(expiry_height.0)?;
827
828                // Denoted as `tx_in_count` and `tx_in` in the spec.
829                inputs.zcash_serialize(&mut writer)?;
830
831                // Denoted as `tx_out_count` and `tx_out` in the spec.
832                outputs.zcash_serialize(&mut writer)?;
833
834                // A bundle of fields denoted in the spec as `nSpendsSapling`, `vSpendsSapling`,
835                // `nOutputsSapling`,`vOutputsSapling`, `valueBalanceSapling`, `anchorSapling`,
836                // `vSpendProofsSapling`, `vSpendAuthSigsSapling`, `vOutputProofsSapling` and
837                // `bindingSigSapling`.
838                sapling_shielded_data.zcash_serialize(&mut writer)?;
839
840                // A bundle of fields denoted in the spec as `nActionsOrchard`, `vActionsOrchard`,
841                // `flagsOrchard`,`valueBalanceOrchard`, `anchorOrchard`, `sizeProofsOrchard`,
842                // `proofsOrchard`, `vSpendAuthSigsOrchard`, and `bindingSigOrchard`.
843                orchard_shielded_data.zcash_serialize(&mut writer)?;
844
845                // The Ironwood bundle: the same field layout as the Orchard bundle above, denoted
846                // `nActionsIronwood`, `vActionsIronwood`, `flagsIronwood`, `valueBalanceIronwood`,
847                // `anchorIronwood`, `sizeProofsIronwood`, `proofsIronwood`, `vSpendAuthSigsIronwood`,
848                // and `bindingSigIronwood`.
849                ironwood_shielded_data.zcash_serialize(&mut writer)?;
850            }
851        }
852        Ok(())
853    }
854}
855
856impl ZcashDeserialize for Transaction {
857    #[allow(clippy::unwrap_in_result)]
858    fn zcash_deserialize<R: io::Read>(reader: R) -> Result<Self, SerializationError> {
859        // # Consensus
860        //
861        // > [Pre-Sapling] The encoded size of the transaction MUST be less than or
862        // > equal to 100000 bytes.
863        //
864        // https://zips.z.cash/protocol/protocol.pdf#txnconsensus
865        //
866        // Zebra does not verify this rule because we checkpoint up to Canopy blocks, but:
867        // Since transactions must get mined into a block to be useful,
868        // we reject transactions that are larger than blocks.
869        //
870        // If the limit is reached, we'll get an UnexpectedEof error.
871        let mut limited_reader = reader.take(MAX_BLOCK_BYTES);
872
873        let (version, overwintered) = {
874            const LOW_31_BITS: u32 = (1 << 31) - 1;
875            // Denoted as `header` in the spec, contains the `fOverwintered` flag and the `version` field.
876            let header = limited_reader.read_u32::<LittleEndian>()?;
877            (header & LOW_31_BITS, header >> 31 != 0)
878        };
879
880        // # Consensus
881        //
882        // The next rules apply for different transaction versions as follows:
883        //
884        // [Pre-Overwinter]: Transactions version 1 and 2.
885        // [Overwinter onward]: Transactions version 3 and above.
886        // [Overwinter only, pre-Sapling]: Transactions version 3.
887        // [Sapling to Canopy inclusive, pre-NU5]: Transactions version 4.
888        // [NU5 onward]: Transactions version 4 and above.
889        //
890        // > The transaction version number MUST be greater than or equal to 1.
891        //
892        // > [Pre-Overwinter] The fOverwintered fag MUST NOT be set.
893        //
894        // > [Overwinter onward] The version group ID MUST be recognized.
895        //
896        // > [Overwinter onward] The fOverwintered flag MUST be set.
897        //
898        // > [Overwinter only, pre-Sapling] The transaction version number MUST be 3,
899        // > and the version group ID MUST be 0x03C48270.
900        //
901        // > [Sapling to Canopy inclusive, pre-NU5] The transaction version number MUST be 4,
902        // > and the version group ID MUST be 0x892F2085.
903        //
904        // > [NU5 onward] The transaction version number MUST be 4 or 5.
905        // > If the transaction version number is 4 then the version group ID MUST be 0x892F2085.
906        // > If the transaction version number is 5 then the version group ID MUST be 0x26A7270A.
907        //
908        // Note: Zebra checkpoints until Canopy blocks, this means only transactions versions
909        // 4 and 5 get fully verified. This satisfies "The transaction version number MUST be 4"
910        // and "The transaction version number MUST be 4 or 5" from the last two rules above.
911        // This is done in the zebra-consensus crate, in the transactions checks.
912        //
913        // https://zips.z.cash/protocol/protocol.pdf#txnconsensus
914        match (version, overwintered) {
915            (1, false) => Ok(Transaction::V1 {
916                // Denoted as `tx_in_count` and `tx_in` in the spec.
917                inputs: Vec::zcash_deserialize(&mut limited_reader)?,
918                // Denoted as `tx_out_count` and `tx_out` in the spec.
919                outputs: Vec::zcash_deserialize(&mut limited_reader)?,
920                // Denoted as `lock_time` in the spec.
921                lock_time: LockTime::zcash_deserialize(&mut limited_reader)?,
922            }),
923            (2, false) => {
924                // Version 2 transactions use Sprout-on-BCTV14.
925                type OptV2Jsd = Option<JoinSplitData<Bctv14Proof>>;
926                Ok(Transaction::V2 {
927                    // Denoted as `tx_in_count` and `tx_in` in the spec.
928                    inputs: Vec::zcash_deserialize(&mut limited_reader)?,
929                    // Denoted as `tx_out_count` and `tx_out` in the spec.
930                    outputs: Vec::zcash_deserialize(&mut limited_reader)?,
931                    // Denoted as `lock_time` in the spec.
932                    lock_time: LockTime::zcash_deserialize(&mut limited_reader)?,
933                    // A bundle of fields denoted in the spec as `nJoinSplit`, `vJoinSplit`,
934                    // `joinSplitPubKey` and `joinSplitSig`.
935                    joinsplit_data: OptV2Jsd::zcash_deserialize(&mut limited_reader)?,
936                })
937            }
938            (3, true) => {
939                // Denoted as `nVersionGroupId` in the spec.
940                let id = limited_reader.read_u32::<LittleEndian>()?;
941                if id != OVERWINTER_VERSION_GROUP_ID {
942                    return Err(SerializationError::Parse(
943                        "expected OVERWINTER_VERSION_GROUP_ID",
944                    ));
945                }
946                // Version 3 transactions use Sprout-on-BCTV14.
947                type OptV3Jsd = Option<JoinSplitData<Bctv14Proof>>;
948                Ok(Transaction::V3 {
949                    // Denoted as `tx_in_count` and `tx_in` in the spec.
950                    inputs: Vec::zcash_deserialize(&mut limited_reader)?,
951                    // Denoted as `tx_out_count` and `tx_out` in the spec.
952                    outputs: Vec::zcash_deserialize(&mut limited_reader)?,
953                    // Denoted as `lock_time` in the spec.
954                    lock_time: LockTime::zcash_deserialize(&mut limited_reader)?,
955                    // Denoted as `nExpiryHeight` in the spec.
956                    expiry_height: block::Height(limited_reader.read_u32::<LittleEndian>()?),
957                    // A bundle of fields denoted in the spec as `nJoinSplit`, `vJoinSplit`,
958                    // `joinSplitPubKey` and `joinSplitSig`.
959                    joinsplit_data: OptV3Jsd::zcash_deserialize(&mut limited_reader)?,
960                })
961            }
962            (4, true) => {
963                // Transaction V4 spec:
964                // https://zips.z.cash/protocol/protocol.pdf#txnencoding
965
966                // Denoted as `nVersionGroupId` in the spec.
967                let id = limited_reader.read_u32::<LittleEndian>()?;
968                if id != SAPLING_VERSION_GROUP_ID {
969                    return Err(SerializationError::Parse(
970                        "expected SAPLING_VERSION_GROUP_ID",
971                    ));
972                }
973                // Version 4 transactions use Sprout-on-Groth16.
974                type OptV4Jsd = Option<JoinSplitData<Groth16Proof>>;
975
976                // The previous match arms deserialize in one go, because the
977                // internal structure happens to nicely line up with the
978                // serialized structure. However, this is not possible for
979                // version 4 transactions, as the binding_sig for the
980                // ShieldedData is placed at the end of the transaction. So
981                // instead we have to pull the component parts out manually and
982                // then assemble them.
983
984                // Denoted as `tx_in_count` and `tx_in` in the spec.
985                let inputs: Vec<transparent::Input> = Vec::zcash_deserialize(&mut limited_reader)?;
986
987                // Denoted as `tx_out_count` and `tx_out` in the spec.
988                let outputs = Vec::zcash_deserialize(&mut limited_reader)?;
989
990                let is_coinbase = inputs.len() == 1
991                    && matches!(inputs.first(), Some(transparent::Input::Coinbase { .. }));
992
993                // Denoted as `lock_time` in the spec.
994                let lock_time = LockTime::zcash_deserialize(&mut limited_reader)?;
995
996                // Denoted as `nExpiryHeight` in the spec.
997                let expiry_height = block::Height(limited_reader.read_u32::<LittleEndian>()?);
998
999                // Denoted as `valueBalanceSapling` in the spec.
1000                let value_balance = (&mut limited_reader).zcash_deserialize_into()?;
1001
1002                // Denoted as `nSpendsSapling` โ€” read count before allocating.
1003                let spend_count: CompactSizeMessage =
1004                    (&mut limited_reader).zcash_deserialize_into()?;
1005                let spend_count: usize = spend_count.into();
1006
1007                // # Consensus
1008                //
1009                // > A coinbase transaction MUST NOT have any Spend descriptions.
1010                //
1011                // <https://zips.z.cash/protocol/protocol.pdf#txnconsensus>
1012                if is_coinbase && spend_count > 0 {
1013                    return Err(SerializationError::Parse(
1014                        "coinbase transaction must not have Sapling spends",
1015                    ));
1016                }
1017
1018                // Denoted as `vSpendsSapling` in the spec.
1019                let shielded_spends: Vec<sapling::Spend<sapling::PerSpendAnchor>> =
1020                    zcash_deserialize_external_count(spend_count, &mut limited_reader)?;
1021
1022                // Denoted as `nOutputsSapling` and `vOutputsSapling` in the spec.
1023                let shielded_outputs =
1024                    Vec::<sapling::OutputInTransactionV4>::zcash_deserialize(&mut limited_reader)?
1025                        .into_iter()
1026                        .map(sapling::Output::from_v4)
1027                        .collect();
1028
1029                // A bundle of fields denoted in the spec as `nJoinSplit`, `vJoinSplit`,
1030                // `joinSplitPubKey` and `joinSplitSig`.
1031                let joinsplit_data = OptV4Jsd::zcash_deserialize(&mut limited_reader)?;
1032
1033                let sapling_transfers = if !shielded_spends.is_empty() {
1034                    Some(sapling::TransferData::SpendsAndMaybeOutputs {
1035                        shared_anchor: FieldNotPresent,
1036                        spends: shielded_spends.try_into().expect("checked for spends"),
1037                        maybe_outputs: shielded_outputs,
1038                    })
1039                } else if !shielded_outputs.is_empty() {
1040                    Some(sapling::TransferData::JustOutputs {
1041                        outputs: shielded_outputs.try_into().expect("checked for outputs"),
1042                    })
1043                } else {
1044                    // # Consensus
1045                    //
1046                    // > [Sapling onward] If effectiveVersion = 4 and there are no Spend
1047                    // > descriptions or Output descriptions, then valueBalanceSapling MUST be 0.
1048                    //
1049                    // https://zips.z.cash/protocol/protocol.pdf#txnconsensus
1050                    if value_balance != 0 {
1051                        return Err(SerializationError::BadTransactionBalance);
1052                    }
1053                    None
1054                };
1055
1056                let sapling_shielded_data = match sapling_transfers {
1057                    Some(transfers) => Some(sapling::ShieldedData {
1058                        value_balance,
1059                        transfers,
1060                        // Denoted as `bindingSigSapling` in the spec.
1061                        binding_sig: limited_reader.read_64_bytes()?.into(),
1062                    }),
1063                    None => None,
1064                };
1065
1066                Ok(Transaction::V4 {
1067                    inputs,
1068                    outputs,
1069                    lock_time,
1070                    expiry_height,
1071                    sapling_shielded_data,
1072                    joinsplit_data,
1073                })
1074            }
1075            (5, true) => {
1076                // Transaction V5 spec:
1077                // https://zips.z.cash/protocol/protocol.pdf#txnencoding
1078
1079                // Denoted as `nVersionGroupId` in the spec.
1080                let id = limited_reader.read_u32::<LittleEndian>()?;
1081                if id != TX_V5_VERSION_GROUP_ID {
1082                    return Err(SerializationError::Parse("expected TX_V5_VERSION_GROUP_ID"));
1083                }
1084                // Denoted as `nConsensusBranchId` in the spec.
1085                // Convert it to a NetworkUpgrade
1086                let network_upgrade =
1087                    NetworkUpgrade::try_from(limited_reader.read_u32::<LittleEndian>()?)?;
1088
1089                // # Consensus
1090                //
1091                // > [NU5 onward] The transaction version number MUST be 4 or 5.
1092                //
1093                // V5 transactions are only valid from NU5 onward, so reject
1094                // transactions with pre-NU5 consensus branch IDs.
1095                if network_upgrade < NetworkUpgrade::Nu5 {
1096                    return Err(SerializationError::Parse(
1097                        "v5 transaction must have NU5 or later consensus branch ID",
1098                    ));
1099                }
1100
1101                // Denoted as `lock_time` in the spec.
1102                let lock_time = LockTime::zcash_deserialize(&mut limited_reader)?;
1103
1104                // Denoted as `nExpiryHeight` in the spec.
1105                let expiry_height = block::Height(limited_reader.read_u32::<LittleEndian>()?);
1106
1107                // Denoted as `tx_in_count` and `tx_in` in the spec.
1108                let inputs: Vec<transparent::Input> = Vec::zcash_deserialize(&mut limited_reader)?;
1109
1110                // Denoted as `tx_out_count` and `tx_out` in the spec.
1111                let outputs = Vec::zcash_deserialize(&mut limited_reader)?;
1112
1113                let is_coinbase = inputs.len() == 1
1114                    && matches!(inputs.first(), Some(transparent::Input::Coinbase { .. }));
1115
1116                // A bundle of fields denoted in the spec as `nSpendsSapling`, `vSpendsSapling`,
1117                // `nOutputsSapling`,`vOutputsSapling`, `valueBalanceSapling`, `anchorSapling`,
1118                // `vSpendProofsSapling`, `vSpendAuthSigsSapling`, `vOutputProofsSapling` and
1119                // `bindingSigSapling`.
1120                let sapling_shielded_data =
1121                    deserialize_v5_sapling_shielded_data(&mut limited_reader, is_coinbase)?;
1122
1123                // A bundle of fields denoted in the spec as `nActionsOrchard`, `vActionsOrchard`,
1124                // `flagsOrchard`,`valueBalanceOrchard`, `anchorOrchard`, `sizeProofsOrchard`,
1125                // `proofsOrchard`, `vSpendAuthSigsOrchard`, and `bindingSigOrchard`.
1126                let orchard_shielded_data = (&mut limited_reader).zcash_deserialize_into()?;
1127
1128                let tx = Transaction::V5 {
1129                    network_upgrade,
1130                    lock_time,
1131                    expiry_height,
1132                    inputs,
1133                    outputs,
1134                    sapling_shielded_data,
1135                    orchard_shielded_data,
1136                };
1137
1138                tx.to_librustzcash(network_upgrade)?;
1139
1140                Ok(tx)
1141            }
1142            (6, true) => {
1143                // Denoted as `nVersionGroupId` in the spec.
1144                let id = limited_reader.read_u32::<LittleEndian>()?;
1145                if id != TX_V6_VERSION_GROUP_ID {
1146                    return Err(SerializationError::Parse("expected TX_V6_VERSION_GROUP_ID"));
1147                }
1148                // Denoted as `nConsensusBranchId` in the spec.
1149                // Convert it to a NetworkUpgrade
1150                let network_upgrade =
1151                    NetworkUpgrade::try_from(limited_reader.read_u32::<LittleEndian>()?)?;
1152                // v6 transactions are only valid from NU6.3 onward, so reject transactions with
1153                // pre-NU6.3 consensus branch IDs at the wire layer. (The exact tx-vs-block network
1154                // upgrade match is also re-checked during verification by `consensus_branch_id`.)
1155                if network_upgrade < NetworkUpgrade::Nu6_3 {
1156                    return Err(SerializationError::Parse(
1157                        "v6 transaction must have a NU6.3 or later consensus branch ID",
1158                    ));
1159                }
1160                // Denoted as `lock_time` in the spec.
1161                let lock_time = LockTime::zcash_deserialize(&mut limited_reader)?;
1162
1163                // Denoted as `nExpiryHeight` in the spec.
1164                let expiry_height = block::Height(limited_reader.read_u32::<LittleEndian>()?);
1165
1166                // Denoted as `tx_in_count` and `tx_in` in the spec.
1167                let inputs: Vec<transparent::Input> = Vec::zcash_deserialize(&mut limited_reader)?;
1168
1169                // Denoted as `tx_out_count` and `tx_out` in the spec.
1170                let outputs = Vec::zcash_deserialize(&mut limited_reader)?;
1171
1172                let is_coinbase = inputs.len() == 1
1173                    && matches!(inputs.first(), Some(transparent::Input::Coinbase { .. }));
1174
1175                // A bundle of fields denoted in the spec as `nSpendsSapling`, `vSpendsSapling`,
1176                // `nOutputsSapling`,`vOutputsSapling`, `valueBalanceSapling`, `anchorSapling`,
1177                // `vSpendProofsSapling`, `vSpendAuthSigsSapling`, `vOutputProofsSapling` and
1178                // `bindingSigSapling`.
1179                //
1180                // The v6 Sapling bundle has the same *wire* format as v5: the NU6.3 changes (moving
1181                // `anchorSapling` from the txid digest to the auth digest, and the `..._v6`
1182                // personalizations) only affect digest computation, which Zebra delegates to
1183                // librustzcash via `to_librustzcash`. So the v5 Sapling codec is reused here.
1184                let sapling_shielded_data =
1185                    deserialize_v5_sapling_shielded_data(&mut limited_reader, is_coinbase)?;
1186
1187                // A bundle of fields denoted in the spec as `nActionsOrchard`, `vActionsOrchard`,
1188                // `flagsOrchard`,`valueBalanceOrchard`, `anchorOrchard`, `sizeProofsOrchard`,
1189                // `proofsOrchard`, `vSpendAuthSigsOrchard`, and `bindingSigOrchard`. The v6 Orchard
1190                // bundle reserves the `enableCrossAddress` bit (like v5); only the Ironwood bundle
1191                // below permits it.
1192                let orchard_shielded_data = (&mut limited_reader)
1193                    .zcash_deserialize_into::<Option<orchard::ShieldedDataV6>>()?;
1194
1195                // The Ironwood bundle: the same field layout and NU6.3 flag format as the Orchard
1196                // bundle above (`nActionsIronwood` .. `bindingSigIronwood`).
1197                let ironwood_shielded_data = (&mut limited_reader)
1198                    .zcash_deserialize_into::<Option<ironwood::ShieldedData>>()?;
1199
1200                let tx = Transaction::V6 {
1201                    network_upgrade,
1202                    lock_time,
1203                    expiry_height,
1204                    inputs,
1205                    outputs,
1206                    sapling_shielded_data,
1207                    orchard_shielded_data,
1208                    ironwood_shielded_data,
1209                };
1210
1211                // Validate that the whole transaction round-trips to the librustzcash format, as the
1212                // v5 arm above does. Zebra's and librustzcash's parsers are not identical, so this
1213                // fails closed at the wire layer for any divergence, ensuring an incompatibility can
1214                // never reach the `expect(...)` in the txid/auth-digest path (`Hash::from`), which
1215                // would otherwise abort the node on attacker-supplied input.
1216                tx.to_librustzcash(network_upgrade)?;
1217
1218                Ok(tx)
1219            }
1220            (_, _) => Err(SerializationError::Parse("bad tx header")),
1221        }
1222    }
1223}
1224
1225impl<T> ZcashDeserialize for Arc<T>
1226where
1227    T: ZcashDeserialize,
1228{
1229    fn zcash_deserialize<R: io::Read>(reader: R) -> Result<Self, SerializationError> {
1230        Ok(Arc::new(T::zcash_deserialize(reader)?))
1231    }
1232}
1233
1234impl<T> ZcashSerialize for Arc<T>
1235where
1236    T: ZcashSerialize,
1237{
1238    fn zcash_serialize<W: io::Write>(&self, writer: W) -> Result<(), io::Error> {
1239        T::zcash_serialize(self, writer)
1240    }
1241}
1242
1243/// A Tx Input must have an Outpoint (32 byte hash + 4 byte index), a 4 byte sequence number,
1244/// and a signature script, which always takes a min of 1 byte (for a length 0 script).
1245pub(crate) const MIN_TRANSPARENT_INPUT_SIZE: u64 = 32 + 4 + 4 + 1;
1246
1247/// A Transparent output has an 8 byte value and script which takes a min of 1 byte.
1248pub(crate) const MIN_TRANSPARENT_OUTPUT_SIZE: u64 = 8 + 1;
1249
1250/// All txs must have at least one input, a 4 byte locktime, and at least one output.
1251///
1252/// Shielded transfers are much larger than transparent transfers,
1253/// so this is the minimum transaction size.
1254pub const MIN_TRANSPARENT_TX_SIZE: u64 =
1255    MIN_TRANSPARENT_INPUT_SIZE + 4 + MIN_TRANSPARENT_OUTPUT_SIZE;
1256
1257/// The minimum transaction size for v4 transactions.
1258///
1259/// v4 transactions also have an expiry height.
1260pub const MIN_TRANSPARENT_TX_V4_SIZE: u64 = MIN_TRANSPARENT_TX_SIZE + 4;
1261
1262/// The minimum transaction size for v5 transactions.
1263///
1264/// v5 transactions also have an expiry height and a consensus branch ID.
1265pub const MIN_TRANSPARENT_TX_V5_SIZE: u64 = MIN_TRANSPARENT_TX_SIZE + 4 + 4;
1266
1267/// No valid Zcash message contains more transactions than can fit in a single block
1268///
1269/// `tx` messages contain a single transaction, and `block` messages are limited to the maximum
1270/// block size.
1271impl TrustedPreallocate for Transaction {
1272    fn max_allocation() -> u64 {
1273        // A transparent transaction is the smallest transaction variant
1274        MAX_BLOCK_BYTES / MIN_TRANSPARENT_TX_SIZE
1275    }
1276}
1277
1278/// The maximum number of inputs in a valid Zcash on-chain transaction.
1279///
1280/// If a transaction contains more inputs than can fit in maximally large block, it might be
1281/// valid on the network and in the mempool, but it can never be mined into a block. So
1282/// rejecting these large edge-case transactions can never break consensus.
1283impl TrustedPreallocate for transparent::Input {
1284    fn max_allocation() -> u64 {
1285        MAX_BLOCK_BYTES / MIN_TRANSPARENT_INPUT_SIZE
1286    }
1287}
1288
1289/// The maximum number of outputs in a valid Zcash on-chain transaction.
1290///
1291/// If a transaction contains more outputs than can fit in maximally large block, it might be
1292/// valid on the network and in the mempool, but it can never be mined into a block. So
1293/// rejecting these large edge-case transactions can never break consensus.
1294impl TrustedPreallocate for transparent::Output {
1295    fn max_allocation() -> u64 {
1296        MAX_BLOCK_BYTES / MIN_TRANSPARENT_OUTPUT_SIZE
1297    }
1298}
1299
1300/// A serialized transaction.
1301///
1302/// Stores bytes that are guaranteed to be deserializable into a [`Transaction`].
1303///
1304/// Sorts in lexicographic order of the transaction's serialized data.
1305#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
1306pub struct SerializedTransaction {
1307    bytes: Vec<u8>,
1308}
1309
1310impl fmt::Display for SerializedTransaction {
1311    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1312        f.write_str(&hex::encode(&self.bytes))
1313    }
1314}
1315
1316impl fmt::Debug for SerializedTransaction {
1317    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1318        // A transaction with a lot of transfers can be extremely long in logs.
1319        let mut data_truncated = hex::encode(&self.bytes);
1320        if data_truncated.len() > 1003 {
1321            let end = data_truncated.len() - 500;
1322            // Replace the middle bytes with "...", but leave 500 bytes on either side.
1323            // The data is hex, so this replacement won't panic.
1324            data_truncated.replace_range(500..=end, "...");
1325        }
1326
1327        f.debug_tuple("SerializedTransaction")
1328            .field(&data_truncated)
1329            .finish()
1330    }
1331}
1332
1333/// Build a [`SerializedTransaction`] by serializing a block.
1334impl<B: Borrow<Transaction>> From<B> for SerializedTransaction {
1335    fn from(tx: B) -> Self {
1336        SerializedTransaction {
1337            bytes: tx
1338                .borrow()
1339                .zcash_serialize_to_vec()
1340                .expect("Writing to a `Vec` should never fail"),
1341        }
1342    }
1343}
1344
1345/// Access the serialized bytes of a [`SerializedTransaction`].
1346impl AsRef<[u8]> for SerializedTransaction {
1347    fn as_ref(&self) -> &[u8] {
1348        self.bytes.as_ref()
1349    }
1350}
1351
1352impl From<Vec<u8>> for SerializedTransaction {
1353    fn from(bytes: Vec<u8>) -> Self {
1354        Self { bytes }
1355    }
1356}
1357
1358impl FromHex for SerializedTransaction {
1359    type Error = <Vec<u8> as FromHex>::Error;
1360
1361    fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
1362        let bytes = <Vec<u8>>::from_hex(hex)?;
1363
1364        Ok(bytes.into())
1365    }
1366}