Zebra/zebra-chain/src/sapling/output.rs

232 lines
8.9 KiB
Rust

//! Sapling _Output descriptions_, as described in [protocol specification §7.4][ps].
//!
//! [ps]: https://zips.z.cash/protocol/protocol.pdf#outputencoding
use std::io;
use crate::{
block::MAX_BLOCK_BYTES,
primitives::Groth16Proof,
serialization::{
serde_helpers, SerializationError, TrustedPreallocate, ZcashDeserialize, ZcashSerialize,
},
};
use super::{commitment, keys, note};
/// A _Output Description_, as described in [protocol specification §7.4][ps].
///
/// # Differences between Transaction Versions
///
/// `V4` transactions serialize the fields of spends and outputs together.
/// `V5` transactions split them into multiple arrays.
///
/// [ps]: https://zips.z.cash/protocol/protocol.pdf#outputencoding
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Output {
/// A value commitment to the value of the input note.
pub cv: commitment::ValueCommitment,
/// The u-coordinate of the note commitment for the output note.
#[serde(with = "serde_helpers::Fq")]
pub cm_u: jubjub::Fq,
/// An encoding of an ephemeral Jubjub public key.
pub ephemeral_key: keys::EphemeralPublicKey,
/// A ciphertext component for the encrypted output note.
pub enc_ciphertext: note::EncryptedNote,
/// A ciphertext component for the encrypted output note.
pub out_ciphertext: note::WrappedNoteKey,
/// The ZK output proof.
pub zkproof: Groth16Proof,
}
/// Wrapper for `Output` serialization in a `V4` transaction.
///
/// https://zips.z.cash/protocol/protocol.pdf#outputencoding
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct OutputInTransactionV4(pub Output);
/// The serialization prefix fields of an `Output` in Transaction V5.
///
/// In `V5` transactions, spends are split into multiple arrays, so the prefix
/// and proof must be serialised and deserialized separately.
///
/// Serialized as `OutputDescriptionV5` in [protocol specification §7.3][ps].
///
/// [ps]: https://zips.z.cash/protocol/protocol.pdf#outputencoding
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct OutputPrefixInTransactionV5 {
/// A value commitment to the value of the input note.
pub cv: commitment::ValueCommitment,
/// The u-coordinate of the note commitment for the output note.
#[serde(with = "serde_helpers::Fq")]
pub cm_u: jubjub::Fq,
/// An encoding of an ephemeral Jubjub public key.
pub ephemeral_key: keys::EphemeralPublicKey,
/// A ciphertext component for the encrypted output note.
pub enc_ciphertext: note::EncryptedNote,
/// A ciphertext component for the encrypted output note.
pub out_ciphertext: note::WrappedNoteKey,
}
impl Output {
/// Remove the V4 transaction wrapper from this output.
pub fn from_v4(output: OutputInTransactionV4) -> Output {
output.0
}
/// Add a V4 transaction wrapper to this output.
pub fn into_v4(self) -> OutputInTransactionV4 {
OutputInTransactionV4(self)
}
/// Combine the prefix and non-prefix fields from V5 transaction
/// deserialization.
pub fn from_v5_parts(prefix: OutputPrefixInTransactionV5, zkproof: Groth16Proof) -> Output {
Output {
cv: prefix.cv,
cm_u: prefix.cm_u,
ephemeral_key: prefix.ephemeral_key,
enc_ciphertext: prefix.enc_ciphertext,
out_ciphertext: prefix.out_ciphertext,
zkproof,
}
}
/// Split out the prefix and non-prefix fields for V5 transaction
/// serialization.
pub fn into_v5_parts(self) -> (OutputPrefixInTransactionV5, Groth16Proof) {
let prefix = OutputPrefixInTransactionV5 {
cv: self.cv,
cm_u: self.cm_u,
ephemeral_key: self.ephemeral_key,
enc_ciphertext: self.enc_ciphertext,
out_ciphertext: self.out_ciphertext,
};
(prefix, self.zkproof)
}
/// Encodes the primary inputs for the proof statement as 5 Bls12_381 base
/// field elements, to match bellman::groth16::verify_proof.
///
/// NB: jubjub::Fq is a type alias for bls12_381::Scalar.
///
/// https://zips.z.cash/protocol/protocol.pdf#cctsaplingoutput
pub fn primary_inputs(&self) -> Vec<jubjub::Fq> {
let mut inputs = vec![];
let cv_affine = jubjub::AffinePoint::from_bytes(self.cv.into()).unwrap();
inputs.push(cv_affine.get_u());
inputs.push(cv_affine.get_v());
let epk_affine = jubjub::AffinePoint::from_bytes(self.ephemeral_key.into()).unwrap();
inputs.push(epk_affine.get_u());
inputs.push(epk_affine.get_v());
inputs.push(self.cm_u);
inputs
}
}
impl OutputInTransactionV4 {
/// Add V4 transaction wrapper to this output.
pub fn from_output(output: Output) -> OutputInTransactionV4 {
OutputInTransactionV4(output)
}
/// Remove the V4 transaction wrapper from this output.
pub fn into_output(self) -> Output {
self.0
}
}
impl ZcashSerialize for OutputInTransactionV4 {
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
let output = self.0.clone();
output.cv.zcash_serialize(&mut writer)?;
writer.write_all(&output.cm_u.to_bytes())?;
output.ephemeral_key.zcash_serialize(&mut writer)?;
output.enc_ciphertext.zcash_serialize(&mut writer)?;
output.out_ciphertext.zcash_serialize(&mut writer)?;
output.zkproof.zcash_serialize(&mut writer)?;
Ok(())
}
}
impl ZcashDeserialize for OutputInTransactionV4 {
fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
Ok(OutputInTransactionV4(Output {
cv: commitment::ValueCommitment::zcash_deserialize(&mut reader)?,
cm_u: jubjub::Fq::zcash_deserialize(&mut reader)?,
ephemeral_key: keys::EphemeralPublicKey::zcash_deserialize(&mut reader)?,
enc_ciphertext: note::EncryptedNote::zcash_deserialize(&mut reader)?,
out_ciphertext: note::WrappedNoteKey::zcash_deserialize(&mut reader)?,
zkproof: Groth16Proof::zcash_deserialize(&mut reader)?,
}))
}
}
// In a V5 transaction, zkproof is deserialized separately, so we can only
// deserialize V5 outputs in the context of a V5 transaction.
//
// Instead, implement serialization and deserialization for the
// Output prefix fields, which are stored in the same array.
impl ZcashSerialize for OutputPrefixInTransactionV5 {
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
self.cv.zcash_serialize(&mut writer)?;
writer.write_all(&self.cm_u.to_bytes())?;
self.ephemeral_key.zcash_serialize(&mut writer)?;
self.enc_ciphertext.zcash_serialize(&mut writer)?;
self.out_ciphertext.zcash_serialize(&mut writer)?;
Ok(())
}
}
impl ZcashDeserialize for OutputPrefixInTransactionV5 {
fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
Ok(OutputPrefixInTransactionV5 {
cv: commitment::ValueCommitment::zcash_deserialize(&mut reader)?,
cm_u: jubjub::Fq::zcash_deserialize(&mut reader)?,
ephemeral_key: keys::EphemeralPublicKey::zcash_deserialize(&mut reader)?,
enc_ciphertext: note::EncryptedNote::zcash_deserialize(&mut reader)?,
out_ciphertext: note::WrappedNoteKey::zcash_deserialize(&mut reader)?,
})
}
}
/// The size of a v5 output, without associated fields.
///
/// This is the size of outputs in the initial array, there is another
/// array of zkproofs required in the transaction format.
pub(crate) const OUTPUT_PREFIX_SIZE: u64 = 32 + 32 + 32 + 580 + 80;
/// An output contains: a 32 byte cv, a 32 byte cmu, a 32 byte ephemeral key
/// a 580 byte encCiphertext, an 80 byte outCiphertext, and a 192 byte zkproof
/// [ps]: https://zips.z.cash/protocol/protocol.pdf#outputencoding
pub(crate) const OUTPUT_SIZE: u64 = OUTPUT_PREFIX_SIZE + 192;
/// The maximum number of sapling outputs in a valid Zcash on-chain transaction.
/// This maximum is the same for transaction V4 and V5, even though the fields are
/// serialized in a different order.
///
/// If a transaction contains more outputs than can fit in maximally large block, it might be
/// valid on the network and in the mempool, but it can never be mined into a block. So
/// rejecting these large edge-case transactions can never break consensus
impl TrustedPreallocate for OutputInTransactionV4 {
fn max_allocation() -> u64 {
// Since a serialized Vec<Output> uses at least one byte for its length,
// the max allocation can never exceed (MAX_BLOCK_BYTES - 1) / OUTPUT_SIZE
(MAX_BLOCK_BYTES - 1) / OUTPUT_SIZE
}
}
impl TrustedPreallocate for OutputPrefixInTransactionV5 {
fn max_allocation() -> u64 {
// Since V4 and V5 have the same fields,
// and the V5 associated fields are required,
// a valid max allocation can never exceed this size
OutputInTransactionV4::max_allocation()
}
}