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 halo2::pasta::{group::ff::PrimeField, pallas};
7use hex::FromHex;
8
9use crate::{
10    block::MAX_BLOCK_BYTES,
11    primitives::ZkSnarkProof,
12    serialization::{
13        ReadZcashExt, SerializationError, TrustedPreallocate, ZcashDeserialize,
14        ZcashDeserializeInto, ZcashSerialize,
15    },
16};
17
18use super::*;
19use crate::sprout;
20
21// Only the test-gated shielded-data codecs below need these.
22#[cfg(any(test, feature = "proptest-impl"))]
23use crate::{
24    amount, ironwood, orchard,
25    primitives::Halo2Proof,
26    sapling,
27    serialization::{
28        zcash_deserialize_external_count, zcash_serialize_empty_list,
29        zcash_serialize_external_count, AtLeastOne, CompactSizeMessage,
30    },
31};
32#[cfg(any(test, feature = "proptest-impl"))]
33use reddsa::{orchard::Binding, orchard::SpendAuth, Signature};
34
35impl ZcashDeserialize for jubjub::Fq {
36    fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
37        let possible_scalar = jubjub::Fq::from_bytes(&reader.read_32_bytes()?);
38
39        if possible_scalar.is_some().into() {
40            Ok(possible_scalar.unwrap())
41        } else {
42            Err(SerializationError::Parse(
43                "Invalid jubjub::Fq, input not canonical",
44            ))
45        }
46    }
47}
48
49impl ZcashDeserialize for pallas::Scalar {
50    fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
51        let possible_scalar = pallas::Scalar::from_repr(reader.read_32_bytes()?);
52
53        if possible_scalar.is_some().into() {
54            Ok(possible_scalar.unwrap())
55        } else {
56            Err(SerializationError::Parse(
57                "Invalid pallas::Scalar, input not canonical",
58            ))
59        }
60    }
61}
62
63impl ZcashDeserialize for pallas::Base {
64    fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
65        let possible_field_element = pallas::Base::from_repr(reader.read_32_bytes()?);
66
67        if possible_field_element.is_some().into() {
68            Ok(possible_field_element.unwrap())
69        } else {
70            Err(SerializationError::Parse(
71                "Invalid pallas::Base, input not canonical",
72            ))
73        }
74    }
75}
76
77impl<P: ZkSnarkProof> ZcashSerialize for JoinSplitData<P> {
78    fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
79        // Denoted as `nJoinSplit` and `vJoinSplit` in the spec.
80        let joinsplits: Vec<_> = self.joinsplits().cloned().collect();
81        joinsplits.zcash_serialize(&mut writer)?;
82
83        // Denoted as `joinSplitPubKey` in the spec.
84        writer.write_all(&<[u8; 32]>::from(self.pub_key)[..])?;
85
86        // Denoted as `joinSplitSig` in the spec.
87        writer.write_all(&<[u8; 64]>::from(self.sig)[..])?;
88        Ok(())
89    }
90}
91
92impl<P> ZcashDeserialize for Option<JoinSplitData<P>>
93where
94    P: ZkSnarkProof,
95    sprout::JoinSplit<P>: TrustedPreallocate,
96{
97    fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
98        // Denoted as `nJoinSplit` and `vJoinSplit` in the spec.
99        let joinsplits: Vec<sprout::JoinSplit<P>> = (&mut reader).zcash_deserialize_into()?;
100        match joinsplits.split_first() {
101            None => Ok(None),
102            Some((first, rest)) => {
103                // Denoted as `joinSplitPubKey` in the spec.
104                let pub_key = reader.read_32_bytes()?.into();
105                // Denoted as `joinSplitSig` in the spec.
106                let sig = reader.read_64_bytes()?.into();
107                Ok(Some(JoinSplitData {
108                    first: first.clone(),
109                    rest: rest.to_vec(),
110                    pub_key,
111                    sig,
112                }))
113            }
114        }
115    }
116}
117
118// Serialization for Zebra's own shielded-data types.
119//
120// Production transaction serialization goes through `zcash_primitives`, which owns the wire
121// format for every shielded pool (see the `ZcashSerialize`/`ZcashDeserialize` impls for
122// `Transaction`). The codecs below operate on Zebra's `sapling`/`orchard`/`ironwood`
123// shielded-data structs, which no production path serializes, so they are compiled only for
124// tests. Gating them keeps a second copy of the wire format out of release builds and makes it
125// unambiguous which encoder consensus depends on.
126
127#[cfg(any(test, feature = "proptest-impl"))]
128impl ZcashSerialize for Option<sapling::ShieldedData<sapling::PerSpendAnchor>> {
129    fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
130        match self {
131            None => {
132                zcash_serialize_empty_list(&mut writer)?;
133                zcash_serialize_empty_list(&mut writer)?;
134            }
135            Some(sd) => {
136                sd.zcash_serialize(&mut writer)?;
137            }
138        }
139        Ok(())
140    }
141}
142
143#[cfg(any(test, feature = "proptest-impl"))]
144impl ZcashSerialize for sapling::ShieldedData<sapling::PerSpendAnchor> {
145    fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
146        let spends: Vec<_> = self.spends().cloned().collect();
147        let outputs: Vec<_> = self
148            .outputs()
149            .cloned()
150            .map(sapling::Output::into_v4)
151            .collect();
152
153        spends.zcash_serialize(&mut writer)?;
154        outputs.zcash_serialize(&mut writer)?;
155        self.value_balance.zcash_serialize(&mut writer)?;
156        writer.write_all(&<[u8; 64]>::from(self.binding_sig)[..])?;
157        Ok(())
158    }
159}
160
161#[cfg(any(test, feature = "proptest-impl"))]
162impl ZcashDeserialize for Option<sapling::ShieldedData<sapling::PerSpendAnchor>> {
163    fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
164        let spends: Vec<sapling::Spend<sapling::PerSpendAnchor>> =
165            (&mut reader).zcash_deserialize_into()?;
166        let outputs: Vec<sapling::OutputInTransactionV4> =
167            (&mut reader).zcash_deserialize_into()?;
168
169        if spends.is_empty() && outputs.is_empty() {
170            return Ok(None);
171        }
172
173        let value_balance = (&mut reader).zcash_deserialize_into()?;
174        let binding_sig = reader.read_64_bytes()?.into();
175
176        let outputs: Vec<sapling::Output> = outputs
177            .into_iter()
178            .map(sapling::OutputInTransactionV4::into_output)
179            .collect();
180
181        let transfers = match spends.split_first() {
182            None => sapling::TransferData::JustOutputs {
183                outputs: outputs.try_into().map_err(|_| {
184                    SerializationError::Parse(
185                        "ShieldedData<PerSpendAnchor> with no spends or outputs",
186                    )
187                })?,
188            },
189            Some((first, rest)) => sapling::TransferData::SpendsAndMaybeOutputs {
190                shared_anchor: sapling::FieldNotPresent,
191                spends: std::iter::once(first.clone())
192                    .chain(rest.iter().cloned())
193                    .collect::<Vec<_>>()
194                    .try_into()
195                    .map_err(|_| {
196                        SerializationError::Parse("ShieldedData<PerSpendAnchor> spend list empty")
197                    })?,
198                maybe_outputs: outputs,
199            },
200        };
201
202        Ok(Some(sapling::ShieldedData {
203            value_balance,
204            transfers,
205            binding_sig,
206        }))
207    }
208}
209
210// Transaction::V5 serializes sapling ShieldedData in a single continuous byte
211// range, so we can implement its serialization and deserialization separately.
212// (Unlike V4, where it must be serialized as part of the transaction.)
213
214#[cfg(any(test, feature = "proptest-impl"))]
215impl ZcashSerialize for Option<sapling::ShieldedData<sapling::SharedAnchor>> {
216    fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
217        match self {
218            None => {
219                // Denoted as `nSpendsSapling` in the spec.
220                zcash_serialize_empty_list(&mut writer)?;
221                // Denoted as `nOutputsSapling` in the spec.
222                zcash_serialize_empty_list(&mut writer)?;
223            }
224            Some(sapling_shielded_data) => {
225                sapling_shielded_data.zcash_serialize(&mut writer)?;
226            }
227        }
228        Ok(())
229    }
230}
231
232#[cfg(any(test, feature = "proptest-impl"))]
233impl ZcashSerialize for sapling::ShieldedData<sapling::SharedAnchor> {
234    fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
235        // Collect arrays for Spends
236        // There's no unzip3, so we have to unzip twice.
237        let (spend_prefixes, spend_proofs_sigs): (Vec<_>, Vec<_>) = self
238            .spends()
239            .cloned()
240            .map(sapling::Spend::<sapling::SharedAnchor>::into_v5_parts)
241            .map(|(prefix, proof, sig)| (prefix, (proof, sig)))
242            .unzip();
243        let (spend_proofs, spend_sigs) = spend_proofs_sigs.into_iter().unzip();
244
245        // Collect arrays for Outputs
246        let (output_prefixes, output_proofs): (Vec<_>, _) = self
247            .outputs()
248            .cloned()
249            .map(sapling::Output::into_v5_parts)
250            .unzip();
251
252        // Denoted as `nSpendsSapling` and `vSpendsSapling` in the spec.
253        spend_prefixes.zcash_serialize(&mut writer)?;
254        // Denoted as `nOutputsSapling` and `vOutputsSapling` in the spec.
255        output_prefixes.zcash_serialize(&mut writer)?;
256
257        // Denoted as `valueBalanceSapling` in the spec.
258        self.value_balance.zcash_serialize(&mut writer)?;
259
260        // Denoted as `anchorSapling` in the spec.
261        // `TransferData` ensures this field is only present when there is at
262        // least one spend.
263        if let Some(shared_anchor) = self.shared_anchor() {
264            writer.write_all(&<[u8; 32]>::from(shared_anchor)[..])?;
265        }
266
267        // Denoted as `vSpendProofsSapling` in the spec.
268        zcash_serialize_external_count(&spend_proofs, &mut writer)?;
269        // Denoted as `vSpendAuthSigsSapling` in the spec.
270        zcash_serialize_external_count(&spend_sigs, &mut writer)?;
271
272        // Denoted as `vOutputProofsSapling` in the spec.
273        zcash_serialize_external_count(&output_proofs, &mut writer)?;
274
275        // Denoted as `bindingSigSapling` in the spec.
276        writer.write_all(&<[u8; 64]>::from(self.binding_sig)[..])?;
277
278        Ok(())
279    }
280}
281
282// we can't split ShieldedData out of Option<ShieldedData> deserialization,
283// because the counts are read along with the arrays.
284#[cfg(any(test, feature = "proptest-impl"))]
285impl ZcashDeserialize for Option<sapling::ShieldedData<sapling::SharedAnchor>> {
286    #[allow(clippy::unwrap_in_result)]
287    fn zcash_deserialize<R: io::Read>(reader: R) -> Result<Self, SerializationError> {
288        deserialize_v5_sapling_shielded_data(reader, false)
289    }
290}
291
292/// Deserialize V5/V6 Sapling shielded data with an optional early coinbase
293/// rejection.
294///
295/// When `is_coinbase` is true, a non-zero `nSpendsSapling` count is rejected
296/// **before** allocating the spend vector, closing the late-validation gap
297/// described in GHSA-rgwx-8r98-p34c.
298#[allow(clippy::unwrap_in_result)]
299#[cfg(any(test, feature = "proptest-impl"))]
300fn deserialize_v5_sapling_shielded_data<R: io::Read>(
301    mut reader: R,
302    is_coinbase: bool,
303) -> Result<Option<sapling::ShieldedData<sapling::SharedAnchor>>, SerializationError> {
304    // Denoted as `nSpendsSapling` in the spec โ€” read count before allocating.
305    let spend_count: CompactSizeMessage = (&mut reader).zcash_deserialize_into()?;
306    let spend_count: usize = spend_count.into();
307
308    // # Consensus
309    //
310    // > A coinbase transaction MUST NOT have any Spend descriptions.
311    //
312    // <https://zips.z.cash/protocol/protocol.pdf#txnconsensus>
313    //
314    // Reject before allocating to prevent a peer from forcing thousands of
315    // spend-prefix allocations for a transaction that will always be invalid.
316    if is_coinbase && spend_count > 0 {
317        return Err(SerializationError::Parse(
318            "coinbase transaction must not have Sapling spends",
319        ));
320    }
321
322    // Denoted as `vSpendsSapling` in the spec.
323    let spend_prefixes: Vec<sapling::SpendPrefixInTransactionV5> =
324        zcash_deserialize_external_count(spend_count, &mut reader)?;
325
326    // Denoted as `nOutputsSapling` and `vOutputsSapling` in the spec.
327    let output_prefixes: Vec<_> = (&mut reader).zcash_deserialize_into()?;
328
329    // nSpendsSapling and nOutputsSapling as variables
330    let spends_count = spend_prefixes.len();
331    let outputs_count = output_prefixes.len();
332
333    // All the other fields depend on having spends or outputs
334    if spend_prefixes.is_empty() && output_prefixes.is_empty() {
335        return Ok(None);
336    }
337
338    // Denoted as `valueBalanceSapling` in the spec.
339    let value_balance = (&mut reader).zcash_deserialize_into()?;
340
341    // Denoted as `anchorSapling` in the spec.
342    //
343    // # Consensus
344    //
345    // > Elements of a Spend description MUST be valid encodings of the types given above.
346    //
347    // https://zips.z.cash/protocol/protocol.pdf#spenddesc
348    //
349    // Type is `B^{[โ„“_{Sapling}_{Merkle}]}`, i.e. 32 bytes
350    //
351    // > LEOS2IP_{256}(anchorSapling), if present, MUST be less than ๐‘ž_๐•.
352    //
353    // https://zips.z.cash/protocol/protocol.pdf#spendencodingandconsensus
354    //
355    // Validated in [`crate::sapling::tree::Root::zcash_deserialize`].
356    let shared_anchor = if spends_count > 0 {
357        Some((&mut reader).zcash_deserialize_into()?)
358    } else {
359        None
360    };
361
362    // Denoted as `vSpendProofsSapling` in the spec.
363    //
364    // # Consensus
365    //
366    // > Elements of a Spend description MUST be valid encodings of the types given above.
367    //
368    // https://zips.z.cash/protocol/protocol.pdf#spenddesc
369    //
370    // Type is `ZKSpend.Proof`, described in
371    // https://zips.z.cash/protocol/protocol.pdf#grothencoding
372    // It is not enforced here; this just reads 192 bytes.
373    // The type is validated when validating the proof, see
374    // [`groth16::Item::try_from`]. In #3179 we plan to validate here instead.
375    let spend_proofs = zcash_deserialize_external_count(spends_count, &mut reader)?;
376
377    // Denoted as `vSpendAuthSigsSapling` in the spec.
378    //
379    // # Consensus
380    //
381    // > Elements of a Spend description MUST be valid encodings of the types given above.
382    //
383    // https://zips.z.cash/protocol/protocol.pdf#spenddesc
384    //
385    // Type is SpendAuthSig^{Sapling}.Signature, i.e.
386    // B^Y^{[ceiling(โ„“_G/8) + ceiling(bitlength(๐‘Ÿ_G)/8)]} i.e. 64 bytes
387    // https://zips.z.cash/protocol/protocol.pdf#concretereddsa
388    // See [`redjubjub::Signature<SpendAuth>::zcash_deserialize`].
389    let spend_sigs = zcash_deserialize_external_count(spends_count, &mut reader)?;
390
391    // Denoted as `vOutputProofsSapling` in the spec.
392    //
393    // # Consensus
394    //
395    // > Elements of an Output description MUST be valid encodings of the types given above.
396    //
397    // https://zips.z.cash/protocol/protocol.pdf#outputdesc
398    //
399    // Type is `ZKOutput.Proof`, described in
400    // https://zips.z.cash/protocol/protocol.pdf#grothencoding
401    // It is not enforced here; this just reads 192 bytes.
402    // The type is validated when validating the proof, see
403    // [`groth16::Item::try_from`]. In #3179 we plan to validate here instead.
404    let output_proofs = zcash_deserialize_external_count(outputs_count, &mut reader)?;
405
406    // Denoted as `bindingSigSapling` in the spec.
407    let binding_sig = reader.read_64_bytes()?.into();
408
409    // Create shielded spends from deserialized parts
410    let spends: Vec<_> = spend_prefixes
411        .into_iter()
412        .zip(spend_proofs)
413        .zip(spend_sigs)
414        .map(|((prefix, proof), sig)| {
415            sapling::Spend::<sapling::SharedAnchor>::from_v5_parts(prefix, proof, sig)
416        })
417        .collect();
418
419    // Create shielded outputs from deserialized parts
420    let outputs = output_prefixes
421        .into_iter()
422        .zip(output_proofs)
423        .map(|(prefix, proof)| sapling::Output::from_v5_parts(prefix, proof))
424        .collect();
425
426    // Create transfers
427    //
428    // # Consensus
429    //
430    // > The anchor of each Spend description MUST refer to some earlier
431    // > blockโ€™s final Sapling treestate. The anchor is encoded separately
432    // > in each Spend description for v4 transactions, or encoded once and
433    // > shared between all Spend descriptions in a v5 transaction.
434    //
435    // <https://zips.z.cash/protocol/protocol.pdf#spendsandoutputs>
436    //
437    // This rule is also implemented in
438    // [`zebra_state::service::check::anchor`] and
439    // [`zebra_chain::sapling::spend`].
440    //
441    // The "anchor encoding for v5 transactions" is implemented here.
442    let transfers = match shared_anchor {
443        Some(shared_anchor) => sapling::TransferData::SpendsAndMaybeOutputs {
444            shared_anchor,
445            spends: spends
446                .try_into()
447                .expect("checked spends when parsing shared anchor"),
448            maybe_outputs: outputs,
449        },
450        None => sapling::TransferData::JustOutputs {
451            outputs: outputs
452                .try_into()
453                .expect("checked spends or outputs and returned early"),
454        },
455    };
456
457    Ok(Some(sapling::ShieldedData {
458        value_balance,
459        transfers,
460        binding_sig,
461    }))
462}
463
464/// Serializes an optional Orchard-protocol bundle (v5 Orchard, v6 Orchard, or Ironwood).
465///
466/// All three share the same wire encoding: an empty action list (`nActions = 0`) when the bundle is
467/// absent, otherwise the bundle's fields.
468///
469/// "The fields flagsOrchard, valueBalanceOrchard, anchorOrchard, sizeProofsOrchard, proofsOrchard,
470/// and bindingSigOrchard are present if and only if nActionsOrchard > 0." โ€” `ยง` note of the second
471/// table of <https://zips.z.cash/protocol/protocol.pdf#txnencoding>
472#[cfg(any(test, feature = "proptest-impl"))]
473fn zcash_serialize_optional_orchard_bundle<W: io::Write>(
474    shielded_data: Option<&orchard::ShieldedData>,
475    mut writer: W,
476) -> Result<(), io::Error> {
477    match shielded_data {
478        // Denoted as `nActionsOrchard` in the spec.
479        None => zcash_serialize_empty_list(&mut writer),
480        Some(shielded_data) => shielded_data.zcash_serialize(&mut writer),
481    }
482}
483
484#[cfg(any(test, feature = "proptest-impl"))]
485impl ZcashSerialize for Option<orchard::ShieldedData> {
486    fn zcash_serialize<W: io::Write>(&self, writer: W) -> Result<(), io::Error> {
487        zcash_serialize_optional_orchard_bundle(self.as_ref(), writer)
488    }
489}
490
491#[cfg(any(test, feature = "proptest-impl"))]
492impl ZcashSerialize for orchard::ShieldedData {
493    fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
494        // Split the AuthorizedAction
495        let (actions, sigs): (Vec<orchard::Action>, Vec<Signature<SpendAuth>>) = self
496            .actions
497            .iter()
498            .cloned()
499            .map(orchard::AuthorizedAction::into_parts)
500            .unzip();
501
502        // Denoted as `nActionsOrchard` and `vActionsOrchard` in the spec.
503        actions.zcash_serialize(&mut writer)?;
504
505        // Denoted as `flagsOrchard` in the spec.
506        self.flags.zcash_serialize(&mut writer)?;
507
508        // Denoted as `valueBalanceOrchard` in the spec.
509        self.value_balance.zcash_serialize(&mut writer)?;
510
511        // Denoted as `anchorOrchard` in the spec.
512        self.shared_anchor.zcash_serialize(&mut writer)?;
513
514        // Denoted as `sizeProofsOrchard` and `proofsOrchard` in the spec.
515        self.proof.zcash_serialize(&mut writer)?;
516
517        // Denoted as `vSpendAuthSigsOrchard` in the spec.
518        zcash_serialize_external_count(&sigs, &mut writer)?;
519
520        // Denoted as `bindingSigOrchard` in the spec.
521        self.binding_sig.zcash_serialize(&mut writer)?;
522
523        Ok(())
524    }
525}
526
527/// The `flagsOrchard` codec a v6 Orchard-protocol bundle newtype uses on deserialization.
528///
529/// A v6 Orchard or Ironwood bundle encodes identically on the wire to a v5 Orchard bundle (the flag
530/// byte is written as-is); the pools differ only in the reserved-bit rule applied to `flagsOrchard`.
531/// The `enableCrossAddress` bit (bit 2) is permitted only for the Ironwood pool, and is reserved
532/// (MUST be 0) for the Orchard pool regardless of tx version โ€” matching
533/// `orchard::bundle::Flags::from_byte`, which rejects bit 2 for `ValuePool::Orchard`. Tying the
534/// codec to the bundle type lets the (de)serializers below imply it instead of naming it explicitly.
535#[cfg(any(test, feature = "proptest-impl"))]
536trait V6FlagCodec {
537    /// The flag codec: `orchard::Flags` reserves bit 2, `orchard::FlagsV6` permits it.
538    type Codec: ZcashDeserialize + Into<orchard::Flags>;
539}
540
541#[cfg(any(test, feature = "proptest-impl"))]
542impl V6FlagCodec for orchard::ShieldedDataV6 {
543    // The v6 Orchard bundle parses with the pre-NU6.3 codec, exactly like v5.
544    type Codec = orchard::Flags;
545}
546
547#[cfg(any(test, feature = "proptest-impl"))]
548impl V6FlagCodec for ironwood::ShieldedData {
549    // Only the Ironwood bundle permits `enableCrossAddress`.
550    type Codec = orchard::FlagsV6;
551}
552
553/// Deserializes the shared Orchard-protocol bundle body of a v6 bundle newtype `T`, using the flag
554/// codec [implied by `T`](V6FlagCodec) rather than one named at the call site.
555#[cfg(any(test, feature = "proptest-impl"))]
556fn deserialize_v6_orchard_shielded_data<R, T>(
557    reader: R,
558) -> Result<Option<orchard::ShieldedData>, SerializationError>
559where
560    R: io::Read,
561    T: V6FlagCodec,
562{
563    deserialize_orchard_shielded_data::<R, T::Codec>(reader)
564}
565
566#[cfg(any(test, feature = "proptest-impl"))]
567impl ZcashDeserialize for Option<orchard::ShieldedDataV6> {
568    fn zcash_deserialize<R: io::Read>(reader: R) -> Result<Self, SerializationError> {
569        Ok(
570            deserialize_v6_orchard_shielded_data::<R, orchard::ShieldedDataV6>(reader)?
571                .map(orchard::ShieldedDataV6::new),
572        )
573    }
574}
575
576#[cfg(any(test, feature = "proptest-impl"))]
577impl ZcashSerialize for Option<orchard::ShieldedDataV6> {
578    fn zcash_serialize<W: io::Write>(&self, writer: W) -> Result<(), io::Error> {
579        zcash_serialize_optional_orchard_bundle(self.as_ref().map(|data| data.data()), writer)
580    }
581}
582
583#[cfg(any(test, feature = "proptest-impl"))]
584impl ZcashDeserialize for Option<ironwood::ShieldedData> {
585    fn zcash_deserialize<R: io::Read>(reader: R) -> Result<Self, SerializationError> {
586        Ok(
587            deserialize_v6_orchard_shielded_data::<R, ironwood::ShieldedData>(reader)?
588                .map(orchard::ShieldedDataV6::new)
589                .map(ironwood::ShieldedData::new),
590        )
591    }
592}
593
594#[cfg(any(test, feature = "proptest-impl"))]
595impl ZcashSerialize for Option<ironwood::ShieldedData> {
596    fn zcash_serialize<W: io::Write>(&self, writer: W) -> Result<(), io::Error> {
597        zcash_serialize_optional_orchard_bundle(self.as_ref().map(|data| data.data()), writer)
598    }
599}
600
601// we can't split ShieldedData out of Option<ShieldedData> deserialization,
602// because the counts are read along with the arrays.
603#[cfg(any(test, feature = "proptest-impl"))]
604impl ZcashDeserialize for Option<orchard::ShieldedData> {
605    fn zcash_deserialize<R: io::Read>(reader: R) -> Result<Self, SerializationError> {
606        // The bare `Option<orchard::ShieldedData>` codec is the v5 Orchard bundle, which uses the
607        // pre-NU6.3 flag-byte format (`orchard::Flags`). v6 Orchard and Ironwood bundles use the
608        // `orchard::FlagsV6` newtype.
609        deserialize_orchard_shielded_data::<R, orchard::Flags>(reader)
610    }
611}
612
613/// Deserializes an `Option<orchard::ShieldedData>`, parsing the flags byte via the flag type `F`.
614///
615/// v5 Orchard bundles pass `F = orchard::Flags` (pre-NU6.3 format); v6 Orchard and Ironwood bundles
616/// pass `F = orchard::FlagsV6` (the NU6.3 format, which permits the `enableCrossAddress` flag).
617#[cfg(any(test, feature = "proptest-impl"))]
618pub(crate) fn deserialize_orchard_shielded_data<R, F>(
619    mut reader: R,
620) -> Result<Option<orchard::ShieldedData>, SerializationError>
621where
622    R: io::Read,
623    F: ZcashDeserialize + Into<orchard::Flags>,
624{
625    // Denoted as `nActionsOrchard` and `vActionsOrchard` in the spec.
626    let actions: Vec<orchard::Action> = (&mut reader).zcash_deserialize_into()?;
627
628    // "The fields flagsOrchard, valueBalanceOrchard, anchorOrchard, sizeProofsOrchard,
629    // proofsOrchard , and bindingSigOrchard are present if and only if nActionsOrchard > 0."
630    // `ยง` note of the second table of https://zips.z.cash/protocol/protocol.pdf#txnencoding
631    if actions.is_empty() {
632        return Ok(None);
633    }
634
635    // # Consensus
636    //
637    // > Elements of an Action description MUST be canonical encodings of the types given above.
638    //
639    // https://zips.z.cash/protocol/protocol.pdf#actiondesc
640    //
641    // Some Action elements are validated in this function; they are described below.
642
643    // Denoted as `flagsOrchard` in the spec. The flag type `F` selects the reserved-bit rule
644    // (pre-NU6.3 reserves bits 2..7; NU6.3 reserves bits 3..7).
645    let flags: orchard::Flags = F::zcash_deserialize(&mut reader)?.into();
646
647    // Denoted as `valueBalanceOrchard` in the spec.
648    let value_balance: amount::Amount = (&mut reader).zcash_deserialize_into()?;
649
650    // Denoted as `anchorOrchard` in the spec.
651    // Consensus: type is `{0 .. ๐‘ž_โ„™ โˆ’ 1}`. See [`orchard::tree::Root::zcash_deserialize`].
652    let shared_anchor: orchard::tree::Root = (&mut reader).zcash_deserialize_into()?;
653
654    // Denoted as `sizeProofsOrchard` and `proofsOrchard` in the spec.
655    // Consensus: type is `ZKAction.Proof`, i.e. a byte sequence.
656    // https://zips.z.cash/protocol/protocol.pdf#halo2encoding
657    let proof: Halo2Proof = (&mut reader).zcash_deserialize_into()?;
658
659    // Denoted as `vSpendAuthSigsOrchard` in the spec.
660    // Consensus: this validates the `spendAuthSig` elements, whose type is
661    // SpendAuthSig^{Orchard}.Signature, i.e.
662    // B^Y^{[ceiling(โ„“_G/8) + ceiling(bitlength(๐‘Ÿ_G)/8)]} i.e. 64 bytes
663    // See [`Signature::zcash_deserialize`].
664    let sigs: Vec<Signature<SpendAuth>> =
665        zcash_deserialize_external_count(actions.len(), &mut reader)?;
666
667    // Denoted as `bindingSigOrchard` in the spec.
668    let binding_sig: Signature<Binding> = (&mut reader).zcash_deserialize_into()?;
669
670    // Create the AuthorizedAction from deserialized parts
671    let authorized_actions: Vec<orchard::AuthorizedAction> = actions
672        .into_iter()
673        .zip(sigs)
674        .map(|(action, spend_auth_sig)| {
675            orchard::AuthorizedAction::from_parts(action, spend_auth_sig)
676        })
677        .collect();
678
679    let actions: AtLeastOne<orchard::AuthorizedAction> = authorized_actions.try_into()?;
680
681    Ok(Some(orchard::ShieldedData {
682        flags,
683        value_balance,
684        shared_anchor,
685        proof,
686        actions,
687        binding_sig,
688    }))
689}
690
691impl<T: reddsa::SigType> ZcashSerialize for reddsa::Signature<T> {
692    fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
693        writer.write_all(&<[u8; 64]>::from(*self)[..])?;
694        Ok(())
695    }
696}
697
698impl<T: reddsa::SigType> ZcashDeserialize for reddsa::Signature<T> {
699    fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
700        Ok(reader.read_64_bytes()?.into())
701    }
702}
703
704impl<T> ZcashDeserialize for Arc<T>
705where
706    T: ZcashDeserialize,
707{
708    fn zcash_deserialize<R: io::Read>(reader: R) -> Result<Self, SerializationError> {
709        Ok(Arc::new(T::zcash_deserialize(reader)?))
710    }
711}
712
713impl<T> ZcashSerialize for Arc<T>
714where
715    T: ZcashSerialize,
716{
717    fn zcash_serialize<W: io::Write>(&self, writer: W) -> Result<(), io::Error> {
718        T::zcash_serialize(self, writer)
719    }
720}
721
722/// A Tx Input must have an Outpoint (32 byte hash + 4 byte index), a 4 byte sequence number,
723/// and a signature script, which always takes a min of 1 byte (for a length 0 script).
724pub(crate) const MIN_TRANSPARENT_INPUT_SIZE: u64 = 32 + 4 + 4 + 1;
725
726/// A Transparent output has an 8 byte value and script which takes a min of 1 byte.
727pub(crate) const MIN_TRANSPARENT_OUTPUT_SIZE: u64 = 8 + 1;
728
729/// All txs must have at least one input, a 4 byte locktime, and at least one output.
730///
731/// Shielded transfers are much larger than transparent transfers,
732/// so this is the minimum transaction size.
733pub const MIN_TRANSPARENT_TX_SIZE: u64 =
734    MIN_TRANSPARENT_INPUT_SIZE + 4 + MIN_TRANSPARENT_OUTPUT_SIZE;
735
736/// The minimum transaction size for v4 transactions.
737///
738/// v4 transactions also have an expiry height.
739pub const MIN_TRANSPARENT_TX_V4_SIZE: u64 = MIN_TRANSPARENT_TX_SIZE + 4;
740
741/// The minimum transaction size for v5 transactions.
742///
743/// v5 transactions also have an expiry height and a consensus branch ID.
744pub const MIN_TRANSPARENT_TX_V5_SIZE: u64 = MIN_TRANSPARENT_TX_SIZE + 4 + 4;
745
746/// No valid Zcash message contains more transactions than can fit in a single block
747///
748/// `tx` messages contain a single transaction, and `block` messages are limited to the maximum
749/// block size.
750impl TrustedPreallocate for Transaction {
751    fn max_allocation() -> u64 {
752        // A transparent transaction is the smallest transaction variant
753        MAX_BLOCK_BYTES / MIN_TRANSPARENT_TX_SIZE
754    }
755}
756
757/// The maximum number of inputs in a valid Zcash on-chain transaction.
758///
759/// If a transaction contains more inputs than can fit in maximally large block, it might be
760/// valid on the network and in the mempool, but it can never be mined into a block. So
761/// rejecting these large edge-case transactions can never break consensus.
762impl TrustedPreallocate for transparent::Input {
763    fn max_allocation() -> u64 {
764        MAX_BLOCK_BYTES / MIN_TRANSPARENT_INPUT_SIZE
765    }
766}
767
768/// The maximum number of outputs in a valid Zcash on-chain transaction.
769///
770/// If a transaction contains more outputs than can fit in maximally large block, it might be
771/// valid on the network and in the mempool, but it can never be mined into a block. So
772/// rejecting these large edge-case transactions can never break consensus.
773impl TrustedPreallocate for transparent::Output {
774    fn max_allocation() -> u64 {
775        MAX_BLOCK_BYTES / MIN_TRANSPARENT_OUTPUT_SIZE
776    }
777}
778
779/// A serialized transaction.
780///
781/// Stores bytes that are guaranteed to be deserializable into a [`Transaction`].
782///
783/// Sorts in lexicographic order of the transaction's serialized data.
784#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
785pub struct SerializedTransaction {
786    bytes: Vec<u8>,
787}
788
789impl fmt::Display for SerializedTransaction {
790    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
791        f.write_str(&hex::encode(&self.bytes))
792    }
793}
794
795impl fmt::Debug for SerializedTransaction {
796    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
797        // A transaction with a lot of transfers can be extremely long in logs.
798        let mut data_truncated = hex::encode(&self.bytes);
799        if data_truncated.len() > 1003 {
800            let end = data_truncated.len() - 500;
801            // Replace the middle bytes with "...", but leave 500 bytes on either side.
802            // The data is hex, so this replacement won't panic.
803            data_truncated.replace_range(500..=end, "...");
804        }
805
806        f.debug_tuple("SerializedTransaction")
807            .field(&data_truncated)
808            .finish()
809    }
810}
811
812/// Build a [`SerializedTransaction`] by serializing a block.
813impl<B: Borrow<Transaction>> From<B> for SerializedTransaction {
814    fn from(tx: B) -> Self {
815        SerializedTransaction {
816            bytes: tx
817                .borrow()
818                .zcash_serialize_to_vec()
819                .expect("Writing to a `Vec` should never fail"),
820        }
821    }
822}
823
824/// Access the serialized bytes of a [`SerializedTransaction`].
825impl AsRef<[u8]> for SerializedTransaction {
826    fn as_ref(&self) -> &[u8] {
827        self.bytes.as_ref()
828    }
829}
830
831impl From<Vec<u8>> for SerializedTransaction {
832    fn from(bytes: Vec<u8>) -> Self {
833        Self { bytes }
834    }
835}
836
837impl FromHex for SerializedTransaction {
838    type Error = <Vec<u8> as FromHex>::Error;
839
840    fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
841        let bytes = <Vec<u8>>::from_hex(hex)?;
842
843        Ok(bytes.into())
844    }
845}