zebra_chain/sapling/
output.rs

1//! Sapling _Output descriptions_, as described in [protocol specification §7.4][ps].
2//!
3//! [ps]: https://zips.z.cash/protocol/protocol.pdf#outputencoding
4
5use std::io;
6
7use crate::{
8    block::MAX_BLOCK_BYTES,
9    primitives::Groth16Proof,
10    serialization::{
11        serde_helpers, SerializationError, TrustedPreallocate, ZcashDeserialize, ZcashSerialize,
12    },
13};
14
15use super::{commitment, keys, note};
16
17/// A _Output Description_, as described in [protocol specification §7.4][ps].
18///
19/// # Differences between Transaction Versions
20///
21/// `V4` transactions serialize the fields of spends and outputs together.
22/// `V5` transactions split them into multiple arrays.
23///
24/// [ps]: https://zips.z.cash/protocol/protocol.pdf#outputencoding
25#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
26pub struct Output {
27    /// A value commitment to the value of the input note.
28    pub cv: commitment::NotSmallOrderValueCommitment,
29    /// The u-coordinate of the note commitment for the output note.
30    #[serde(with = "serde_helpers::Fq")]
31    pub cm_u: jubjub::Fq,
32    /// An encoding of an ephemeral Jubjub public key.
33    pub ephemeral_key: keys::EphemeralPublicKey,
34    /// A ciphertext component for the encrypted output note.
35    pub enc_ciphertext: note::EncryptedNote,
36    /// A ciphertext component for the encrypted output note.
37    pub out_ciphertext: note::WrappedNoteKey,
38    /// The ZK output proof.
39    pub zkproof: Groth16Proof,
40}
41
42/// Wrapper for `Output` serialization in a `V4` transaction.
43///
44/// <https://zips.z.cash/protocol/protocol.pdf#outputencoding>
45#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
46pub struct OutputInTransactionV4(pub Output);
47
48/// The serialization prefix fields of an `Output` in Transaction V5.
49///
50/// In `V5` transactions, spends are split into multiple arrays, so the prefix
51/// and proof must be serialised and deserialized separately.
52///
53/// Serialized as `OutputDescriptionV5` in [protocol specification §7.3][ps].
54///
55/// [ps]: https://zips.z.cash/protocol/protocol.pdf#outputencoding
56#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
57pub struct OutputPrefixInTransactionV5 {
58    /// A value commitment to the value of the input note.
59    pub cv: commitment::NotSmallOrderValueCommitment,
60    /// The u-coordinate of the note commitment for the output note.
61    #[serde(with = "serde_helpers::Fq")]
62    pub cm_u: jubjub::Fq,
63    /// An encoding of an ephemeral Jubjub public key.
64    pub ephemeral_key: keys::EphemeralPublicKey,
65    /// A ciphertext component for the encrypted output note.
66    pub enc_ciphertext: note::EncryptedNote,
67    /// A ciphertext component for the encrypted output note.
68    pub out_ciphertext: note::WrappedNoteKey,
69}
70
71impl Output {
72    /// Remove the V4 transaction wrapper from this output.
73    pub fn from_v4(output: OutputInTransactionV4) -> Output {
74        output.0
75    }
76
77    /// Add a V4 transaction wrapper to this output.
78    pub fn into_v4(self) -> OutputInTransactionV4 {
79        OutputInTransactionV4(self)
80    }
81
82    /// Combine the prefix and non-prefix fields from V5 transaction
83    /// deserialization.
84    pub fn from_v5_parts(prefix: OutputPrefixInTransactionV5, zkproof: Groth16Proof) -> Output {
85        Output {
86            cv: prefix.cv,
87            cm_u: prefix.cm_u,
88            ephemeral_key: prefix.ephemeral_key,
89            enc_ciphertext: prefix.enc_ciphertext,
90            out_ciphertext: prefix.out_ciphertext,
91            zkproof,
92        }
93    }
94
95    /// Split out the prefix and non-prefix fields for V5 transaction
96    /// serialization.
97    pub fn into_v5_parts(self) -> (OutputPrefixInTransactionV5, Groth16Proof) {
98        let prefix = OutputPrefixInTransactionV5 {
99            cv: self.cv,
100            cm_u: self.cm_u,
101            ephemeral_key: self.ephemeral_key,
102            enc_ciphertext: self.enc_ciphertext,
103            out_ciphertext: self.out_ciphertext,
104        };
105
106        (prefix, self.zkproof)
107    }
108}
109
110impl OutputInTransactionV4 {
111    /// Add V4 transaction wrapper to this output.
112    pub fn from_output(output: Output) -> OutputInTransactionV4 {
113        OutputInTransactionV4(output)
114    }
115
116    /// Remove the V4 transaction wrapper from this output.
117    pub fn into_output(self) -> Output {
118        self.0
119    }
120}
121
122impl ZcashSerialize for OutputInTransactionV4 {
123    fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
124        let output = self.0.clone();
125        output.cv.zcash_serialize(&mut writer)?;
126        writer.write_all(&output.cm_u.to_bytes())?;
127        output.ephemeral_key.zcash_serialize(&mut writer)?;
128        output.enc_ciphertext.zcash_serialize(&mut writer)?;
129        output.out_ciphertext.zcash_serialize(&mut writer)?;
130        output.zkproof.zcash_serialize(&mut writer)?;
131        Ok(())
132    }
133}
134
135impl ZcashDeserialize for OutputInTransactionV4 {
136    fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
137        // # Consensus
138        //
139        // > Elements of an Output description MUST be valid encodings of the types given above.
140        //
141        // https://zips.z.cash/protocol/protocol.pdf#outputdesc
142        //
143        // > LEOS2IP_{256}(cmu) MUST be less than 𝑞_J.
144        //
145        // https://zips.z.cash/protocol/protocol.pdf#outputencodingandconsensus
146        //
147        // See comments below for each specific type.
148        Ok(OutputInTransactionV4(Output {
149            // Type is `ValueCommit^{Sapling}.Output`, i.e. J
150            // https://zips.z.cash/protocol/protocol.pdf#abstractcommit
151            // See [`commitment::NotSmallOrderValueCommitment::zcash_deserialize`].
152            cv: commitment::NotSmallOrderValueCommitment::zcash_deserialize(&mut reader)?,
153            // Type is `B^{[ℓ_{Sapling}_{Merkle}]}`, i.e. 32 bytes.
154            // However, the consensus rule above restricts it even more.
155            // See [`jubjub::Fq::zcash_deserialize`].
156            cm_u: jubjub::Fq::zcash_deserialize(&mut reader)?,
157            // Type is `KA^{Sapling}.Public`, i.e. J
158            // https://zips.z.cash/protocol/protocol.pdf#concretesaplingkeyagreement
159            // See [`keys::EphemeralPublicKey::zcash_deserialize`].
160            ephemeral_key: keys::EphemeralPublicKey::zcash_deserialize(&mut reader)?,
161            // Type is `Sym.C`, i.e. `B^Y^{\[N\]}`, i.e. arbitrary-sized byte arrays
162            // https://zips.z.cash/protocol/protocol.pdf#concretesym but fixed to
163            // 580 bytes in https://zips.z.cash/protocol/protocol.pdf#outputencodingandconsensus
164            // See [`note::EncryptedNote::zcash_deserialize`].
165            enc_ciphertext: note::EncryptedNote::zcash_deserialize(&mut reader)?,
166            // Type is `Sym.C`, i.e. `B^Y^{\[N\]}`, i.e. arbitrary-sized byte arrays.
167            // https://zips.z.cash/protocol/protocol.pdf#concretesym but fixed to
168            // 80 bytes in https://zips.z.cash/protocol/protocol.pdf#outputencodingandconsensus
169            // See [`note::WrappedNoteKey::zcash_deserialize`].
170            out_ciphertext: note::WrappedNoteKey::zcash_deserialize(&mut reader)?,
171            // Type is `ZKOutput.Proof`, described in
172            // https://zips.z.cash/protocol/protocol.pdf#grothencoding
173            // It is not enforced here; this just reads 192 bytes.
174            // The type is validated when validating the proof, see
175            // [`groth16::Item::try_from`]. In #3179 we plan to validate here instead.
176            zkproof: Groth16Proof::zcash_deserialize(&mut reader)?,
177        }))
178    }
179}
180
181// In a V5 transaction, zkproof is deserialized separately, so we can only
182// deserialize V5 outputs in the context of a V5 transaction.
183//
184// Instead, implement serialization and deserialization for the
185// Output prefix fields, which are stored in the same array.
186
187impl ZcashSerialize for OutputPrefixInTransactionV5 {
188    fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
189        self.cv.zcash_serialize(&mut writer)?;
190        writer.write_all(&self.cm_u.to_bytes())?;
191        self.ephemeral_key.zcash_serialize(&mut writer)?;
192        self.enc_ciphertext.zcash_serialize(&mut writer)?;
193        self.out_ciphertext.zcash_serialize(&mut writer)?;
194        Ok(())
195    }
196}
197
198impl ZcashDeserialize for OutputPrefixInTransactionV5 {
199    fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
200        // # Consensus
201        //
202        // > Elements of an Output description MUST be valid encodings of the types given above.
203        //
204        // https://zips.z.cash/protocol/protocol.pdf#outputdesc
205        //
206        // > LEOS2IP_{256}(cmu) MUST be less than 𝑞_J.
207        //
208        // https://zips.z.cash/protocol/protocol.pdf#outputencodingandconsensus
209        //
210        // See comments below for each specific type.
211        Ok(OutputPrefixInTransactionV5 {
212            // Type is `ValueCommit^{Sapling}.Output`, i.e. J
213            // https://zips.z.cash/protocol/protocol.pdf#abstractcommit
214            // See [`commitment::NotSmallOrderValueCommitment::zcash_deserialize`].
215            cv: commitment::NotSmallOrderValueCommitment::zcash_deserialize(&mut reader)?,
216            // Type is `B^{[ℓ_{Sapling}_{Merkle}]}`, i.e. 32 bytes.
217            // However, the consensus rule above restricts it even more.
218            // See [`jubjub::Fq::zcash_deserialize`].
219            cm_u: jubjub::Fq::zcash_deserialize(&mut reader)?,
220            // Type is `KA^{Sapling}.Public`, i.e. J
221            // https://zips.z.cash/protocol/protocol.pdf#concretesaplingkeyagreement
222            // See [`keys::EphemeralPublicKey::zcash_deserialize`].
223            ephemeral_key: keys::EphemeralPublicKey::zcash_deserialize(&mut reader)?,
224            // Type is `Sym.C`, i.e. `B^Y^{\[N\]}`, i.e. arbitrary-sized byte arrays
225            // https://zips.z.cash/protocol/protocol.pdf#concretesym but fixed to
226            // 580 bytes in https://zips.z.cash/protocol/protocol.pdf#outputencodingandconsensus
227            // See [`note::EncryptedNote::zcash_deserialize`].
228            enc_ciphertext: note::EncryptedNote::zcash_deserialize(&mut reader)?,
229            // Type is `Sym.C`, i.e. `B^Y^{\[N\]}`, i.e. arbitrary-sized byte arrays.
230            // https://zips.z.cash/protocol/protocol.pdf#concretesym but fixed to
231            // 80 bytes in https://zips.z.cash/protocol/protocol.pdf#outputencodingandconsensus
232            // See [`note::WrappedNoteKey::zcash_deserialize`].
233            out_ciphertext: note::WrappedNoteKey::zcash_deserialize(&mut reader)?,
234        })
235    }
236}
237
238/// The size of a v5 output, without associated fields.
239///
240/// This is the size of outputs in the initial array, there is another
241/// array of zkproofs required in the transaction format.
242pub(crate) const OUTPUT_PREFIX_SIZE: u64 = 32 + 32 + 32 + 580 + 80;
243/// An output contains: a 32 byte cv, a 32 byte cmu, a 32 byte ephemeral key
244/// a 580 byte encCiphertext, an 80 byte outCiphertext, and a 192 byte zkproof
245///
246/// [ps]: https://zips.z.cash/protocol/protocol.pdf#outputencoding
247pub(crate) const OUTPUT_SIZE: u64 = OUTPUT_PREFIX_SIZE + 192;
248
249/// The maximum number of sapling outputs in a valid Zcash on-chain transaction.
250/// This maximum is the same for transaction V4 and V5, even though the fields are
251/// serialized in a different order.
252///
253/// If a transaction contains more outputs than can fit in maximally large block, it might be
254/// valid on the network and in the mempool, but it can never be mined into a block. So
255/// rejecting these large edge-case transactions can never break consensus
256impl TrustedPreallocate for OutputInTransactionV4 {
257    fn max_allocation() -> u64 {
258        // Since a serialized Vec<Output> uses at least one byte for its length,
259        // the max allocation can never exceed (MAX_BLOCK_BYTES - 1) / OUTPUT_SIZE
260        const MAX: u64 = (MAX_BLOCK_BYTES - 1) / OUTPUT_SIZE;
261        // # Consensus
262        //
263        // > [NU5 onward] nSpendsSapling, nOutputsSapling, and nActionsOrchard MUST all be less than 2^16.
264        //
265        // https://zips.z.cash/protocol/protocol.pdf#txnconsensus
266        //
267        // This acts as nOutputsSapling and is therefore subject to the rule.
268        // The maximum value is actually smaller due to the block size limit,
269        // but we ensure the 2^16 limit with a static assertion.
270        // (The check is not required pre-NU5, but it doesn't cause problems.)
271        static_assertions::const_assert!(MAX < (1 << 16));
272        MAX
273    }
274}
275
276impl TrustedPreallocate for OutputPrefixInTransactionV5 {
277    fn max_allocation() -> u64 {
278        // Since V4 and V5 have the same fields,
279        // and the V5 associated fields are required,
280        // a valid max allocation can never exceed this size
281        OutputInTransactionV4::max_allocation()
282    }
283}