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