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, ReadZcashExt, SerializationError,
19        TrustedPreallocate, ZcashDeserialize, ZcashDeserializeInto, ZcashSerialize,
20    },
21};
22
23#[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
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>(mut reader: R) -> Result<Self, SerializationError> {
187        // Denoted as `nSpendsSapling` and `vSpendsSapling` in the spec.
188        let spend_prefixes: Vec<_> = (&mut reader).zcash_deserialize_into()?;
189
190        // Denoted as `nOutputsSapling` and `vOutputsSapling` in the spec.
191        let output_prefixes: Vec<_> = (&mut reader).zcash_deserialize_into()?;
192
193        // nSpendsSapling and nOutputsSapling as variables
194        let spends_count = spend_prefixes.len();
195        let outputs_count = output_prefixes.len();
196
197        // All the other fields depend on having spends or outputs
198        if spend_prefixes.is_empty() && output_prefixes.is_empty() {
199            return Ok(None);
200        }
201
202        // Denoted as `valueBalanceSapling` in the spec.
203        let value_balance = (&mut reader).zcash_deserialize_into()?;
204
205        // Denoted as `anchorSapling` in the spec.
206        //
207        // # Consensus
208        //
209        // > Elements of a Spend description MUST be valid encodings of the types given above.
210        //
211        // https://zips.z.cash/protocol/protocol.pdf#spenddesc
212        //
213        // Type is `B^{[ℓ_{Sapling}_{Merkle}]}`, i.e. 32 bytes
214        //
215        // > LEOS2IP_{256}(anchorSapling), if present, MUST be less than 𝑞_𝕁.
216        //
217        // https://zips.z.cash/protocol/protocol.pdf#spendencodingandconsensus
218        //
219        // Validated in [`crate::sapling::tree::Root::zcash_deserialize`].
220        let shared_anchor = if spends_count > 0 {
221            Some((&mut reader).zcash_deserialize_into()?)
222        } else {
223            None
224        };
225
226        // Denoted as `vSpendProofsSapling` in the spec.
227        //
228        // # Consensus
229        //
230        // > Elements of a Spend description MUST be valid encodings of the types given above.
231        //
232        // https://zips.z.cash/protocol/protocol.pdf#spenddesc
233        //
234        // Type is `ZKSpend.Proof`, described in
235        // https://zips.z.cash/protocol/protocol.pdf#grothencoding
236        // It is not enforced here; this just reads 192 bytes.
237        // The type is validated when validating the proof, see
238        // [`groth16::Item::try_from`]. In #3179 we plan to validate here instead.
239        let spend_proofs = zcash_deserialize_external_count(spends_count, &mut reader)?;
240
241        // Denoted as `vSpendAuthSigsSapling` in the spec.
242        //
243        // # Consensus
244        //
245        // > Elements of a Spend description MUST be valid encodings of the types given above.
246        //
247        // https://zips.z.cash/protocol/protocol.pdf#spenddesc
248        //
249        // Type is SpendAuthSig^{Sapling}.Signature, i.e.
250        // B^Y^{[ceiling(ℓ_G/8) + ceiling(bitlength(𝑟_G)/8)]} i.e. 64 bytes
251        // https://zips.z.cash/protocol/protocol.pdf#concretereddsa
252        // See [`redjubjub::Signature<SpendAuth>::zcash_deserialize`].
253        let spend_sigs = zcash_deserialize_external_count(spends_count, &mut reader)?;
254
255        // Denoted as `vOutputProofsSapling` in the spec.
256        //
257        // # Consensus
258        //
259        // > Elements of an Output description MUST be valid encodings of the types given above.
260        //
261        // https://zips.z.cash/protocol/protocol.pdf#outputdesc
262        //
263        // Type is `ZKOutput.Proof`, described in
264        // https://zips.z.cash/protocol/protocol.pdf#grothencoding
265        // It is not enforced here; this just reads 192 bytes.
266        // The type is validated when validating the proof, see
267        // [`groth16::Item::try_from`]. In #3179 we plan to validate here instead.
268        let output_proofs = zcash_deserialize_external_count(outputs_count, &mut reader)?;
269
270        // Denoted as `bindingSigSapling` in the spec.
271        let binding_sig = reader.read_64_bytes()?.into();
272
273        // Create shielded spends from deserialized parts
274        let spends: Vec<_> = spend_prefixes
275            .into_iter()
276            .zip(spend_proofs)
277            .zip(spend_sigs)
278            .map(|((prefix, proof), sig)| {
279                sapling::Spend::<sapling::SharedAnchor>::from_v5_parts(prefix, proof, sig)
280            })
281            .collect();
282
283        // Create shielded outputs from deserialized parts
284        let outputs = output_prefixes
285            .into_iter()
286            .zip(output_proofs)
287            .map(|(prefix, proof)| sapling::Output::from_v5_parts(prefix, proof))
288            .collect();
289
290        // Create transfers
291        //
292        // # Consensus
293        //
294        // > The anchor of each Spend description MUST refer to some earlier
295        // > block’s final Sapling treestate. The anchor is encoded separately
296        // > in each Spend description for v4 transactions, or encoded once and
297        // > shared between all Spend descriptions in a v5 transaction.
298        //
299        // <https://zips.z.cash/protocol/protocol.pdf#spendsandoutputs>
300        //
301        // This rule is also implemented in
302        // [`zebra_state::service::check::anchor`] and
303        // [`zebra_chain::sapling::spend`].
304        //
305        // The "anchor encoding for v5 transactions" is implemented here.
306        let transfers = match shared_anchor {
307            Some(shared_anchor) => sapling::TransferData::SpendsAndMaybeOutputs {
308                shared_anchor,
309                spends: spends
310                    .try_into()
311                    .expect("checked spends when parsing shared anchor"),
312                maybe_outputs: outputs,
313            },
314            None => sapling::TransferData::JustOutputs {
315                outputs: outputs
316                    .try_into()
317                    .expect("checked spends or outputs and returned early"),
318            },
319        };
320
321        Ok(Some(sapling::ShieldedData {
322            value_balance,
323            transfers,
324            binding_sig,
325        }))
326    }
327}
328
329impl ZcashSerialize for Option<orchard::ShieldedData> {
330    fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
331        match self {
332            None => {
333                // Denoted as `nActionsOrchard` in the spec.
334                zcash_serialize_empty_list(writer)?;
335
336                // We don't need to write anything else here.
337                // "The fields flagsOrchard, valueBalanceOrchard, anchorOrchard, sizeProofsOrchard,
338                // proofsOrchard , and bindingSigOrchard are present if and only if nActionsOrchard > 0."
339                // `§` note of the second table of https://zips.z.cash/protocol/protocol.pdf#txnencoding
340            }
341            Some(orchard_shielded_data) => {
342                orchard_shielded_data.zcash_serialize(&mut writer)?;
343            }
344        }
345        Ok(())
346    }
347}
348
349impl ZcashSerialize for orchard::ShieldedData {
350    fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
351        // Split the AuthorizedAction
352        let (actions, sigs): (Vec<orchard::Action>, Vec<Signature<SpendAuth>>) = self
353            .actions
354            .iter()
355            .cloned()
356            .map(orchard::AuthorizedAction::into_parts)
357            .unzip();
358
359        // Denoted as `nActionsOrchard` and `vActionsOrchard` in the spec.
360        actions.zcash_serialize(&mut writer)?;
361
362        // Denoted as `flagsOrchard` in the spec.
363        self.flags.zcash_serialize(&mut writer)?;
364
365        // Denoted as `valueBalanceOrchard` in the spec.
366        self.value_balance.zcash_serialize(&mut writer)?;
367
368        // Denoted as `anchorOrchard` in the spec.
369        self.shared_anchor.zcash_serialize(&mut writer)?;
370
371        // Denoted as `sizeProofsOrchard` and `proofsOrchard` in the spec.
372        self.proof.zcash_serialize(&mut writer)?;
373
374        // Denoted as `vSpendAuthSigsOrchard` in the spec.
375        zcash_serialize_external_count(&sigs, &mut writer)?;
376
377        // Denoted as `bindingSigOrchard` in the spec.
378        self.binding_sig.zcash_serialize(&mut writer)?;
379
380        Ok(())
381    }
382}
383
384// we can't split ShieldedData out of Option<ShieldedData> deserialization,
385// because the counts are read along with the arrays.
386impl ZcashDeserialize for Option<orchard::ShieldedData> {
387    fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
388        // Denoted as `nActionsOrchard` and `vActionsOrchard` in the spec.
389        let actions: Vec<orchard::Action> = (&mut reader).zcash_deserialize_into()?;
390
391        // "The fields flagsOrchard, valueBalanceOrchard, anchorOrchard, sizeProofsOrchard,
392        // proofsOrchard , and bindingSigOrchard are present if and only if nActionsOrchard > 0."
393        // `§` note of the second table of https://zips.z.cash/protocol/protocol.pdf#txnencoding
394        if actions.is_empty() {
395            return Ok(None);
396        }
397
398        // # Consensus
399        //
400        // > Elements of an Action description MUST be canonical encodings of the types given above.
401        //
402        // https://zips.z.cash/protocol/protocol.pdf#actiondesc
403        //
404        // Some Action elements are validated in this function; they are described below.
405
406        // Denoted as `flagsOrchard` in the spec.
407        // Consensus: type of each flag is 𝔹, i.e. a bit. This is enforced implicitly
408        // in [`Flags::zcash_deserialized`].
409        let flags: orchard::Flags = (&mut reader).zcash_deserialize_into()?;
410
411        // Denoted as `valueBalanceOrchard` in the spec.
412        let value_balance: amount::Amount = (&mut reader).zcash_deserialize_into()?;
413
414        // Denoted as `anchorOrchard` in the spec.
415        // Consensus: type is `{0 .. 𝑞_ℙ − 1}`. See [`orchard::tree::Root::zcash_deserialize`].
416        let shared_anchor: orchard::tree::Root = (&mut reader).zcash_deserialize_into()?;
417
418        // Denoted as `sizeProofsOrchard` and `proofsOrchard` in the spec.
419        // Consensus: type is `ZKAction.Proof`, i.e. a byte sequence.
420        // https://zips.z.cash/protocol/protocol.pdf#halo2encoding
421        let proof: Halo2Proof = (&mut reader).zcash_deserialize_into()?;
422
423        // Denoted as `vSpendAuthSigsOrchard` in the spec.
424        // Consensus: this validates the `spendAuthSig` elements, whose type is
425        // SpendAuthSig^{Orchard}.Signature, i.e.
426        // B^Y^{[ceiling(ℓ_G/8) + ceiling(bitlength(𝑟_G)/8)]} i.e. 64 bytes
427        // See [`Signature::zcash_deserialize`].
428        let sigs: Vec<Signature<SpendAuth>> =
429            zcash_deserialize_external_count(actions.len(), &mut reader)?;
430
431        // Denoted as `bindingSigOrchard` in the spec.
432        let binding_sig: Signature<Binding> = (&mut reader).zcash_deserialize_into()?;
433
434        // Create the AuthorizedAction from deserialized parts
435        let authorized_actions: Vec<orchard::AuthorizedAction> = actions
436            .into_iter()
437            .zip(sigs)
438            .map(|(action, spend_auth_sig)| {
439                orchard::AuthorizedAction::from_parts(action, spend_auth_sig)
440            })
441            .collect();
442
443        let actions: AtLeastOne<orchard::AuthorizedAction> = authorized_actions.try_into()?;
444
445        Ok(Some(orchard::ShieldedData {
446            flags,
447            value_balance,
448            shared_anchor,
449            proof,
450            actions,
451            binding_sig,
452        }))
453    }
454}
455
456impl<T: reddsa::SigType> ZcashSerialize for reddsa::Signature<T> {
457    fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
458        writer.write_all(&<[u8; 64]>::from(*self)[..])?;
459        Ok(())
460    }
461}
462
463impl<T: reddsa::SigType> ZcashDeserialize for reddsa::Signature<T> {
464    fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
465        Ok(reader.read_64_bytes()?.into())
466    }
467}
468
469impl ZcashSerialize for Transaction {
470    #[allow(clippy::unwrap_in_result)]
471    fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
472        // Post-Sapling, transaction size is limited to MAX_BLOCK_BYTES.
473        // (Strictly, the maximum transaction size is about 1.5 kB less,
474        // because blocks also include a block header.)
475        //
476        // Currently, all transaction structs are parsed as part of a
477        // block. So we don't need to check transaction size here, until
478        // we start parsing mempool transactions, or generating our own
479        // transactions (see #483).
480        //
481        // Since we checkpoint on Canopy activation, we won't ever need
482        // to check the smaller pre-Sapling transaction size limit.
483
484        // Denoted as `header` in the spec, contains the `fOverwintered` flag and the `version` field.
485        // Write `version` and set the `fOverwintered` bit if necessary
486        let overwintered_flag = if self.is_overwintered() { 1 << 31 } else { 0 };
487        let version = overwintered_flag | self.version();
488
489        writer.write_u32::<LittleEndian>(version)?;
490
491        match self {
492            Transaction::V1 {
493                inputs,
494                outputs,
495                lock_time,
496            } => {
497                // Denoted as `tx_in_count` and `tx_in` in the spec.
498                inputs.zcash_serialize(&mut writer)?;
499
500                // Denoted as `tx_out_count` and `tx_out` in the spec.
501                outputs.zcash_serialize(&mut writer)?;
502
503                // Denoted as `lock_time` in the spec.
504                lock_time.zcash_serialize(&mut writer)?;
505            }
506            Transaction::V2 {
507                inputs,
508                outputs,
509                lock_time,
510                joinsplit_data,
511            } => {
512                // Denoted as `tx_in_count` and `tx_in` in the spec.
513                inputs.zcash_serialize(&mut writer)?;
514
515                // Denoted as `tx_out_count` and `tx_out` in the spec.
516                outputs.zcash_serialize(&mut writer)?;
517
518                // Denoted as `lock_time` in the spec.
519                lock_time.zcash_serialize(&mut writer)?;
520
521                // A bundle of fields denoted in the spec as `nJoinSplit`, `vJoinSplit`,
522                // `joinSplitPubKey` and `joinSplitSig`.
523                match joinsplit_data {
524                    // Write 0 for nJoinSplits to signal no JoinSplitData.
525                    None => zcash_serialize_empty_list(writer)?,
526                    Some(jsd) => jsd.zcash_serialize(&mut writer)?,
527                }
528            }
529            Transaction::V3 {
530                inputs,
531                outputs,
532                lock_time,
533                expiry_height,
534                joinsplit_data,
535            } => {
536                // Denoted as `nVersionGroupId` in the spec.
537                writer.write_u32::<LittleEndian>(OVERWINTER_VERSION_GROUP_ID)?;
538
539                // Denoted as `tx_in_count` and `tx_in` in the spec.
540                inputs.zcash_serialize(&mut writer)?;
541
542                // Denoted as `tx_out_count` and `tx_out` in the spec.
543                outputs.zcash_serialize(&mut writer)?;
544
545                // Denoted as `lock_time` in the spec.
546                lock_time.zcash_serialize(&mut writer)?;
547
548                writer.write_u32::<LittleEndian>(expiry_height.0)?;
549
550                // A bundle of fields denoted in the spec as `nJoinSplit`, `vJoinSplit`,
551                // `joinSplitPubKey` and `joinSplitSig`.
552                match joinsplit_data {
553                    // Write 0 for nJoinSplits to signal no JoinSplitData.
554                    None => zcash_serialize_empty_list(writer)?,
555                    Some(jsd) => jsd.zcash_serialize(&mut writer)?,
556                }
557            }
558            Transaction::V4 {
559                inputs,
560                outputs,
561                lock_time,
562                expiry_height,
563                sapling_shielded_data,
564                joinsplit_data,
565            } => {
566                // Transaction V4 spec:
567                // https://zips.z.cash/protocol/protocol.pdf#txnencoding
568
569                // Denoted as `nVersionGroupId` in the spec.
570                writer.write_u32::<LittleEndian>(SAPLING_VERSION_GROUP_ID)?;
571
572                // Denoted as `tx_in_count` and `tx_in` in the spec.
573                inputs.zcash_serialize(&mut writer)?;
574
575                // Denoted as `tx_out_count` and `tx_out` in the spec.
576                outputs.zcash_serialize(&mut writer)?;
577
578                // Denoted as `lock_time` in the spec.
579                lock_time.zcash_serialize(&mut writer)?;
580
581                // Denoted as `nExpiryHeight` in the spec.
582                writer.write_u32::<LittleEndian>(expiry_height.0)?;
583
584                // The previous match arms serialize in one go, because the
585                // internal structure happens to nicely line up with the
586                // serialized structure. However, this is not possible for
587                // version 4 transactions, as the binding_sig for the
588                // ShieldedData is placed at the end of the transaction. So
589                // instead we have to interleave serialization of the
590                // ShieldedData and the JoinSplitData.
591
592                match sapling_shielded_data {
593                    None => {
594                        // Signal no value balance.
595                        writer.write_i64::<LittleEndian>(0)?;
596                        // Signal no shielded spends and no shielded outputs.
597                        zcash_serialize_empty_list(&mut writer)?;
598                        zcash_serialize_empty_list(&mut writer)?;
599                    }
600                    Some(sapling_shielded_data) => {
601                        // Denoted as `valueBalanceSapling` in the spec.
602                        sapling_shielded_data
603                            .value_balance
604                            .zcash_serialize(&mut writer)?;
605
606                        // Denoted as `nSpendsSapling` and `vSpendsSapling` in the spec.
607                        let spends: Vec<_> = sapling_shielded_data.spends().cloned().collect();
608                        spends.zcash_serialize(&mut writer)?;
609
610                        // Denoted as `nOutputsSapling` and `vOutputsSapling` in the spec.
611                        let outputs: Vec<_> = sapling_shielded_data
612                            .outputs()
613                            .cloned()
614                            .map(sapling::OutputInTransactionV4)
615                            .collect();
616                        outputs.zcash_serialize(&mut writer)?;
617                    }
618                }
619
620                // A bundle of fields denoted in the spec as `nJoinSplit`, `vJoinSplit`,
621                // `joinSplitPubKey` and `joinSplitSig`.
622                match joinsplit_data {
623                    None => zcash_serialize_empty_list(&mut writer)?,
624                    Some(jsd) => jsd.zcash_serialize(&mut writer)?,
625                }
626
627                // Denoted as `bindingSigSapling` in the spec.
628                if let Some(shielded_data) = sapling_shielded_data {
629                    writer.write_all(&<[u8; 64]>::from(shielded_data.binding_sig)[..])?;
630                }
631            }
632
633            Transaction::V5 {
634                network_upgrade,
635                lock_time,
636                expiry_height,
637                inputs,
638                outputs,
639                sapling_shielded_data,
640                orchard_shielded_data,
641            } => {
642                // Transaction V5 spec:
643                // https://zips.z.cash/protocol/protocol.pdf#txnencoding
644
645                // Denoted as `nVersionGroupId` in the spec.
646                writer.write_u32::<LittleEndian>(TX_V5_VERSION_GROUP_ID)?;
647
648                // Denoted as `nConsensusBranchId` in the spec.
649                writer.write_u32::<LittleEndian>(u32::from(
650                    network_upgrade
651                        .branch_id()
652                        .expect("valid transactions must have a network upgrade with a branch id"),
653                ))?;
654
655                // Denoted as `lock_time` in the spec.
656                lock_time.zcash_serialize(&mut writer)?;
657
658                // Denoted as `nExpiryHeight` in the spec.
659                writer.write_u32::<LittleEndian>(expiry_height.0)?;
660
661                // Denoted as `tx_in_count` and `tx_in` in the spec.
662                inputs.zcash_serialize(&mut writer)?;
663
664                // Denoted as `tx_out_count` and `tx_out` in the spec.
665                outputs.zcash_serialize(&mut writer)?;
666
667                // A bundle of fields denoted in the spec as `nSpendsSapling`, `vSpendsSapling`,
668                // `nOutputsSapling`,`vOutputsSapling`, `valueBalanceSapling`, `anchorSapling`,
669                // `vSpendProofsSapling`, `vSpendAuthSigsSapling`, `vOutputProofsSapling` and
670                // `bindingSigSapling`.
671                sapling_shielded_data.zcash_serialize(&mut writer)?;
672
673                // A bundle of fields denoted in the spec as `nActionsOrchard`, `vActionsOrchard`,
674                // `flagsOrchard`,`valueBalanceOrchard`, `anchorOrchard`, `sizeProofsOrchard`,
675                // `proofsOrchard`, `vSpendAuthSigsOrchard`, and `bindingSigOrchard`.
676                orchard_shielded_data.zcash_serialize(&mut writer)?;
677            }
678
679            #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
680            Transaction::V6 {
681                network_upgrade,
682                lock_time,
683                expiry_height,
684                zip233_amount,
685                inputs,
686                outputs,
687                sapling_shielded_data,
688                orchard_shielded_data,
689            } => {
690                // Transaction V6 spec:
691                // https://zips.z.cash/zip-0230#specification
692
693                // Denoted as `nVersionGroupId` in the spec.
694                writer.write_u32::<LittleEndian>(TX_V6_VERSION_GROUP_ID)?;
695
696                // Denoted as `nConsensusBranchId` in the spec.
697                writer.write_u32::<LittleEndian>(u32::from(
698                    network_upgrade
699                        .branch_id()
700                        .expect("valid transactions must have a network upgrade with a branch id"),
701                ))?;
702
703                // Denoted as `lock_time` in the spec.
704                lock_time.zcash_serialize(&mut writer)?;
705
706                // Denoted as `nExpiryHeight` in the spec.
707                writer.write_u32::<LittleEndian>(expiry_height.0)?;
708
709                // Denoted as `zip233_amount` in the spec.
710                zip233_amount.zcash_serialize(&mut writer)?;
711
712                // Denoted as `tx_in_count` and `tx_in` in the spec.
713                inputs.zcash_serialize(&mut writer)?;
714
715                // Denoted as `tx_out_count` and `tx_out` in the spec.
716                outputs.zcash_serialize(&mut writer)?;
717
718                // A bundle of fields denoted in the spec as `nSpendsSapling`, `vSpendsSapling`,
719                // `nOutputsSapling`,`vOutputsSapling`, `valueBalanceSapling`, `anchorSapling`,
720                // `vSpendProofsSapling`, `vSpendAuthSigsSapling`, `vOutputProofsSapling` and
721                // `bindingSigSapling`.
722                sapling_shielded_data.zcash_serialize(&mut writer)?;
723
724                // A bundle of fields denoted in the spec as `nActionsOrchard`, `vActionsOrchard`,
725                // `flagsOrchard`,`valueBalanceOrchard`, `anchorOrchard`, `sizeProofsOrchard`,
726                // `proofsOrchard`, `vSpendAuthSigsOrchard`, and `bindingSigOrchard`.
727                orchard_shielded_data.zcash_serialize(&mut writer)?;
728            }
729        }
730        Ok(())
731    }
732}
733
734impl ZcashDeserialize for Transaction {
735    #[allow(clippy::unwrap_in_result)]
736    fn zcash_deserialize<R: io::Read>(reader: R) -> Result<Self, SerializationError> {
737        // # Consensus
738        //
739        // > [Pre-Sapling] The encoded size of the transaction MUST be less than or
740        // > equal to 100000 bytes.
741        //
742        // https://zips.z.cash/protocol/protocol.pdf#txnconsensus
743        //
744        // Zebra does not verify this rule because we checkpoint up to Canopy blocks, but:
745        // Since transactions must get mined into a block to be useful,
746        // we reject transactions that are larger than blocks.
747        //
748        // If the limit is reached, we'll get an UnexpectedEof error.
749        let mut limited_reader = reader.take(MAX_BLOCK_BYTES);
750
751        let (version, overwintered) = {
752            const LOW_31_BITS: u32 = (1 << 31) - 1;
753            // Denoted as `header` in the spec, contains the `fOverwintered` flag and the `version` field.
754            let header = limited_reader.read_u32::<LittleEndian>()?;
755            (header & LOW_31_BITS, header >> 31 != 0)
756        };
757
758        // # Consensus
759        //
760        // The next rules apply for different transaction versions as follows:
761        //
762        // [Pre-Overwinter]: Transactions version 1 and 2.
763        // [Overwinter onward]: Transactions version 3 and above.
764        // [Overwinter only, pre-Sapling]: Transactions version 3.
765        // [Sapling to Canopy inclusive, pre-NU5]: Transactions version 4.
766        // [NU5 onward]: Transactions version 4 and above.
767        //
768        // > The transaction version number MUST be greater than or equal to 1.
769        //
770        // > [Pre-Overwinter] The fOverwintered fag MUST NOT be set.
771        //
772        // > [Overwinter onward] The version group ID MUST be recognized.
773        //
774        // > [Overwinter onward] The fOverwintered flag MUST be set.
775        //
776        // > [Overwinter only, pre-Sapling] The transaction version number MUST be 3,
777        // > and the version group ID MUST be 0x03C48270.
778        //
779        // > [Sapling to Canopy inclusive, pre-NU5] The transaction version number MUST be 4,
780        // > and the version group ID MUST be 0x892F2085.
781        //
782        // > [NU5 onward] The transaction version number MUST be 4 or 5.
783        // > If the transaction version number is 4 then the version group ID MUST be 0x892F2085.
784        // > If the transaction version number is 5 then the version group ID MUST be 0x26A7270A.
785        //
786        // Note: Zebra checkpoints until Canopy blocks, this means only transactions versions
787        // 4 and 5 get fully verified. This satisfies "The transaction version number MUST be 4"
788        // and "The transaction version number MUST be 4 or 5" from the last two rules above.
789        // This is done in the zebra-consensus crate, in the transactions checks.
790        //
791        // https://zips.z.cash/protocol/protocol.pdf#txnconsensus
792        match (version, overwintered) {
793            (1, false) => Ok(Transaction::V1 {
794                // Denoted as `tx_in_count` and `tx_in` in the spec.
795                inputs: Vec::zcash_deserialize(&mut limited_reader)?,
796                // Denoted as `tx_out_count` and `tx_out` in the spec.
797                outputs: Vec::zcash_deserialize(&mut limited_reader)?,
798                // Denoted as `lock_time` in the spec.
799                lock_time: LockTime::zcash_deserialize(&mut limited_reader)?,
800            }),
801            (2, false) => {
802                // Version 2 transactions use Sprout-on-BCTV14.
803                type OptV2Jsd = Option<JoinSplitData<Bctv14Proof>>;
804                Ok(Transaction::V2 {
805                    // Denoted as `tx_in_count` and `tx_in` in the spec.
806                    inputs: Vec::zcash_deserialize(&mut limited_reader)?,
807                    // Denoted as `tx_out_count` and `tx_out` in the spec.
808                    outputs: Vec::zcash_deserialize(&mut limited_reader)?,
809                    // Denoted as `lock_time` in the spec.
810                    lock_time: LockTime::zcash_deserialize(&mut limited_reader)?,
811                    // A bundle of fields denoted in the spec as `nJoinSplit`, `vJoinSplit`,
812                    // `joinSplitPubKey` and `joinSplitSig`.
813                    joinsplit_data: OptV2Jsd::zcash_deserialize(&mut limited_reader)?,
814                })
815            }
816            (3, true) => {
817                // Denoted as `nVersionGroupId` in the spec.
818                let id = limited_reader.read_u32::<LittleEndian>()?;
819                if id != OVERWINTER_VERSION_GROUP_ID {
820                    return Err(SerializationError::Parse(
821                        "expected OVERWINTER_VERSION_GROUP_ID",
822                    ));
823                }
824                // Version 3 transactions use Sprout-on-BCTV14.
825                type OptV3Jsd = Option<JoinSplitData<Bctv14Proof>>;
826                Ok(Transaction::V3 {
827                    // Denoted as `tx_in_count` and `tx_in` in the spec.
828                    inputs: Vec::zcash_deserialize(&mut limited_reader)?,
829                    // Denoted as `tx_out_count` and `tx_out` in the spec.
830                    outputs: Vec::zcash_deserialize(&mut limited_reader)?,
831                    // Denoted as `lock_time` in the spec.
832                    lock_time: LockTime::zcash_deserialize(&mut limited_reader)?,
833                    // Denoted as `nExpiryHeight` in the spec.
834                    expiry_height: block::Height(limited_reader.read_u32::<LittleEndian>()?),
835                    // A bundle of fields denoted in the spec as `nJoinSplit`, `vJoinSplit`,
836                    // `joinSplitPubKey` and `joinSplitSig`.
837                    joinsplit_data: OptV3Jsd::zcash_deserialize(&mut limited_reader)?,
838                })
839            }
840            (4, true) => {
841                // Transaction V4 spec:
842                // https://zips.z.cash/protocol/protocol.pdf#txnencoding
843
844                // Denoted as `nVersionGroupId` in the spec.
845                let id = limited_reader.read_u32::<LittleEndian>()?;
846                if id != SAPLING_VERSION_GROUP_ID {
847                    return Err(SerializationError::Parse(
848                        "expected SAPLING_VERSION_GROUP_ID",
849                    ));
850                }
851                // Version 4 transactions use Sprout-on-Groth16.
852                type OptV4Jsd = Option<JoinSplitData<Groth16Proof>>;
853
854                // The previous match arms deserialize in one go, because the
855                // internal structure happens to nicely line up with the
856                // serialized structure. However, this is not possible for
857                // version 4 transactions, as the binding_sig for the
858                // ShieldedData is placed at the end of the transaction. So
859                // instead we have to pull the component parts out manually and
860                // then assemble them.
861
862                // Denoted as `tx_in_count` and `tx_in` in the spec.
863                let inputs = Vec::zcash_deserialize(&mut limited_reader)?;
864
865                // Denoted as `tx_out_count` and `tx_out` in the spec.
866                let outputs = Vec::zcash_deserialize(&mut limited_reader)?;
867
868                // Denoted as `lock_time` in the spec.
869                let lock_time = LockTime::zcash_deserialize(&mut limited_reader)?;
870
871                // Denoted as `nExpiryHeight` in the spec.
872                let expiry_height = block::Height(limited_reader.read_u32::<LittleEndian>()?);
873
874                // Denoted as `valueBalanceSapling` in the spec.
875                let value_balance = (&mut limited_reader).zcash_deserialize_into()?;
876
877                // Denoted as `nSpendsSapling` and `vSpendsSapling` in the spec.
878                let shielded_spends = Vec::zcash_deserialize(&mut limited_reader)?;
879
880                // Denoted as `nOutputsSapling` and `vOutputsSapling` in the spec.
881                let shielded_outputs =
882                    Vec::<sapling::OutputInTransactionV4>::zcash_deserialize(&mut limited_reader)?
883                        .into_iter()
884                        .map(sapling::Output::from_v4)
885                        .collect();
886
887                // A bundle of fields denoted in the spec as `nJoinSplit`, `vJoinSplit`,
888                // `joinSplitPubKey` and `joinSplitSig`.
889                let joinsplit_data = OptV4Jsd::zcash_deserialize(&mut limited_reader)?;
890
891                let sapling_transfers = if !shielded_spends.is_empty() {
892                    Some(sapling::TransferData::SpendsAndMaybeOutputs {
893                        shared_anchor: FieldNotPresent,
894                        spends: shielded_spends.try_into().expect("checked for spends"),
895                        maybe_outputs: shielded_outputs,
896                    })
897                } else if !shielded_outputs.is_empty() {
898                    Some(sapling::TransferData::JustOutputs {
899                        outputs: shielded_outputs.try_into().expect("checked for outputs"),
900                    })
901                } else {
902                    // # Consensus
903                    //
904                    // > [Sapling onward] If effectiveVersion = 4 and there are no Spend
905                    // > descriptions or Output descriptions, then valueBalanceSapling MUST be 0.
906                    //
907                    // https://zips.z.cash/protocol/protocol.pdf#txnconsensus
908                    if value_balance != 0 {
909                        return Err(SerializationError::BadTransactionBalance);
910                    }
911                    None
912                };
913
914                let sapling_shielded_data = match sapling_transfers {
915                    Some(transfers) => Some(sapling::ShieldedData {
916                        value_balance,
917                        transfers,
918                        // Denoted as `bindingSigSapling` in the spec.
919                        binding_sig: limited_reader.read_64_bytes()?.into(),
920                    }),
921                    None => None,
922                };
923
924                Ok(Transaction::V4 {
925                    inputs,
926                    outputs,
927                    lock_time,
928                    expiry_height,
929                    sapling_shielded_data,
930                    joinsplit_data,
931                })
932            }
933            (5, true) => {
934                // Transaction V5 spec:
935                // https://zips.z.cash/protocol/protocol.pdf#txnencoding
936
937                // Denoted as `nVersionGroupId` in the spec.
938                let id = limited_reader.read_u32::<LittleEndian>()?;
939                if id != TX_V5_VERSION_GROUP_ID {
940                    return Err(SerializationError::Parse("expected TX_V5_VERSION_GROUP_ID"));
941                }
942                // Denoted as `nConsensusBranchId` in the spec.
943                // Convert it to a NetworkUpgrade
944                let network_upgrade =
945                    NetworkUpgrade::try_from(limited_reader.read_u32::<LittleEndian>()?)?;
946
947                // Denoted as `lock_time` in the spec.
948                let lock_time = LockTime::zcash_deserialize(&mut limited_reader)?;
949
950                // Denoted as `nExpiryHeight` in the spec.
951                let expiry_height = block::Height(limited_reader.read_u32::<LittleEndian>()?);
952
953                // Denoted as `tx_in_count` and `tx_in` in the spec.
954                let inputs = Vec::zcash_deserialize(&mut limited_reader)?;
955
956                // Denoted as `tx_out_count` and `tx_out` in the spec.
957                let outputs = Vec::zcash_deserialize(&mut limited_reader)?;
958
959                // A bundle of fields denoted in the spec as `nSpendsSapling`, `vSpendsSapling`,
960                // `nOutputsSapling`,`vOutputsSapling`, `valueBalanceSapling`, `anchorSapling`,
961                // `vSpendProofsSapling`, `vSpendAuthSigsSapling`, `vOutputProofsSapling` and
962                // `bindingSigSapling`.
963                let sapling_shielded_data = (&mut limited_reader).zcash_deserialize_into()?;
964
965                // A bundle of fields denoted in the spec as `nActionsOrchard`, `vActionsOrchard`,
966                // `flagsOrchard`,`valueBalanceOrchard`, `anchorOrchard`, `sizeProofsOrchard`,
967                // `proofsOrchard`, `vSpendAuthSigsOrchard`, and `bindingSigOrchard`.
968                let orchard_shielded_data = (&mut limited_reader).zcash_deserialize_into()?;
969
970                Ok(Transaction::V5 {
971                    network_upgrade,
972                    lock_time,
973                    expiry_height,
974                    inputs,
975                    outputs,
976                    sapling_shielded_data,
977                    orchard_shielded_data,
978                })
979            }
980            #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
981            (6, true) => {
982                // Denoted as `nVersionGroupId` in the spec.
983                let id = limited_reader.read_u32::<LittleEndian>()?;
984                if id != TX_V6_VERSION_GROUP_ID {
985                    return Err(SerializationError::Parse("expected TX_V6_VERSION_GROUP_ID"));
986                }
987                // Denoted as `nConsensusBranchId` in the spec.
988                // Convert it to a NetworkUpgrade
989                let network_upgrade =
990                    NetworkUpgrade::try_from(limited_reader.read_u32::<LittleEndian>()?)?;
991
992                // Denoted as `lock_time` in the spec.
993                let lock_time = LockTime::zcash_deserialize(&mut limited_reader)?;
994
995                // Denoted as `nExpiryHeight` in the spec.
996                let expiry_height = block::Height(limited_reader.read_u32::<LittleEndian>()?);
997
998                // Denoted as `zip233_amount` in the spec.
999                let zip233_amount = (&mut limited_reader).zcash_deserialize_into()?;
1000
1001                // Denoted as `tx_in_count` and `tx_in` in the spec.
1002                let inputs = Vec::zcash_deserialize(&mut limited_reader)?;
1003
1004                // Denoted as `tx_out_count` and `tx_out` in the spec.
1005                let outputs = Vec::zcash_deserialize(&mut limited_reader)?;
1006
1007                // A bundle of fields denoted in the spec as `nSpendsSapling`, `vSpendsSapling`,
1008                // `nOutputsSapling`,`vOutputsSapling`, `valueBalanceSapling`, `anchorSapling`,
1009                // `vSpendProofsSapling`, `vSpendAuthSigsSapling`, `vOutputProofsSapling` and
1010                // `bindingSigSapling`.
1011                let sapling_shielded_data = (&mut limited_reader).zcash_deserialize_into()?;
1012
1013                // A bundle of fields denoted in the spec as `nActionsOrchard`, `vActionsOrchard`,
1014                // `flagsOrchard`,`valueBalanceOrchard`, `anchorOrchard`, `sizeProofsOrchard`,
1015                // `proofsOrchard`, `vSpendAuthSigsOrchard`, and `bindingSigOrchard`.
1016                let orchard_shielded_data = (&mut limited_reader).zcash_deserialize_into()?;
1017
1018                Ok(Transaction::V6 {
1019                    network_upgrade,
1020                    lock_time,
1021                    expiry_height,
1022                    zip233_amount,
1023                    inputs,
1024                    outputs,
1025                    sapling_shielded_data,
1026                    orchard_shielded_data,
1027                })
1028            }
1029            (_, _) => Err(SerializationError::Parse("bad tx header")),
1030        }
1031    }
1032}
1033
1034impl<T> ZcashDeserialize for Arc<T>
1035where
1036    T: ZcashDeserialize,
1037{
1038    fn zcash_deserialize<R: io::Read>(reader: R) -> Result<Self, SerializationError> {
1039        Ok(Arc::new(T::zcash_deserialize(reader)?))
1040    }
1041}
1042
1043impl<T> ZcashSerialize for Arc<T>
1044where
1045    T: ZcashSerialize,
1046{
1047    fn zcash_serialize<W: io::Write>(&self, writer: W) -> Result<(), io::Error> {
1048        T::zcash_serialize(self, writer)
1049    }
1050}
1051
1052/// A Tx Input must have an Outpoint (32 byte hash + 4 byte index), a 4 byte sequence number,
1053/// and a signature script, which always takes a min of 1 byte (for a length 0 script).
1054pub(crate) const MIN_TRANSPARENT_INPUT_SIZE: u64 = 32 + 4 + 4 + 1;
1055
1056/// A Transparent output has an 8 byte value and script which takes a min of 1 byte.
1057pub(crate) const MIN_TRANSPARENT_OUTPUT_SIZE: u64 = 8 + 1;
1058
1059/// All txs must have at least one input, a 4 byte locktime, and at least one output.
1060///
1061/// Shielded transfers are much larger than transparent transfers,
1062/// so this is the minimum transaction size.
1063pub const MIN_TRANSPARENT_TX_SIZE: u64 =
1064    MIN_TRANSPARENT_INPUT_SIZE + 4 + MIN_TRANSPARENT_OUTPUT_SIZE;
1065
1066/// The minimum transaction size for v4 transactions.
1067///
1068/// v4 transactions also have an expiry height.
1069pub const MIN_TRANSPARENT_TX_V4_SIZE: u64 = MIN_TRANSPARENT_TX_SIZE + 4;
1070
1071/// The minimum transaction size for v5 transactions.
1072///
1073/// v5 transactions also have an expiry height and a consensus branch ID.
1074pub const MIN_TRANSPARENT_TX_V5_SIZE: u64 = MIN_TRANSPARENT_TX_SIZE + 4 + 4;
1075
1076/// No valid Zcash message contains more transactions than can fit in a single block
1077///
1078/// `tx` messages contain a single transaction, and `block` messages are limited to the maximum
1079/// block size.
1080impl TrustedPreallocate for Transaction {
1081    fn max_allocation() -> u64 {
1082        // A transparent transaction is the smallest transaction variant
1083        MAX_BLOCK_BYTES / MIN_TRANSPARENT_TX_SIZE
1084    }
1085}
1086
1087/// The maximum number of inputs in a valid Zcash on-chain transaction.
1088///
1089/// If a transaction contains more inputs than can fit in maximally large block, it might be
1090/// valid on the network and in the mempool, but it can never be mined into a block. So
1091/// rejecting these large edge-case transactions can never break consensus.
1092impl TrustedPreallocate for transparent::Input {
1093    fn max_allocation() -> u64 {
1094        MAX_BLOCK_BYTES / MIN_TRANSPARENT_INPUT_SIZE
1095    }
1096}
1097
1098/// The maximum number of outputs in a valid Zcash on-chain transaction.
1099///
1100/// If a transaction contains more outputs than can fit in maximally large block, it might be
1101/// valid on the network and in the mempool, but it can never be mined into a block. So
1102/// rejecting these large edge-case transactions can never break consensus.
1103impl TrustedPreallocate for transparent::Output {
1104    fn max_allocation() -> u64 {
1105        MAX_BLOCK_BYTES / MIN_TRANSPARENT_OUTPUT_SIZE
1106    }
1107}
1108
1109/// A serialized transaction.
1110///
1111/// Stores bytes that are guaranteed to be deserializable into a [`Transaction`].
1112///
1113/// Sorts in lexicographic order of the transaction's serialized data.
1114#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
1115pub struct SerializedTransaction {
1116    bytes: Vec<u8>,
1117}
1118
1119impl fmt::Display for SerializedTransaction {
1120    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1121        f.write_str(&hex::encode(&self.bytes))
1122    }
1123}
1124
1125impl fmt::Debug for SerializedTransaction {
1126    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1127        // A transaction with a lot of transfers can be extremely long in logs.
1128        let mut data_truncated = hex::encode(&self.bytes);
1129        if data_truncated.len() > 1003 {
1130            let end = data_truncated.len() - 500;
1131            // Replace the middle bytes with "...", but leave 500 bytes on either side.
1132            // The data is hex, so this replacement won't panic.
1133            data_truncated.replace_range(500..=end, "...");
1134        }
1135
1136        f.debug_tuple("SerializedTransaction")
1137            .field(&data_truncated)
1138            .finish()
1139    }
1140}
1141
1142/// Build a [`SerializedTransaction`] by serializing a block.
1143impl<B: Borrow<Transaction>> From<B> for SerializedTransaction {
1144    fn from(tx: B) -> Self {
1145        SerializedTransaction {
1146            bytes: tx
1147                .borrow()
1148                .zcash_serialize_to_vec()
1149                .expect("Writing to a `Vec` should never fail"),
1150        }
1151    }
1152}
1153
1154/// Access the serialized bytes of a [`SerializedTransaction`].
1155impl AsRef<[u8]> for SerializedTransaction {
1156    fn as_ref(&self) -> &[u8] {
1157        self.bytes.as_ref()
1158    }
1159}
1160
1161impl From<Vec<u8>> for SerializedTransaction {
1162    fn from(bytes: Vec<u8>) -> Self {
1163        Self { bytes }
1164    }
1165}
1166
1167impl FromHex for SerializedTransaction {
1168    type Error = <Vec<u8> as FromHex>::Error;
1169
1170    fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
1171        let bytes = <Vec<u8>>::from_hex(hex)?;
1172
1173        Ok(bytes.into())
1174    }
1175}