//! Sapling spends for `V4` and `V5` `Transaction`s. //! //! Zebra uses a generic spend type for `V4` and `V5` transactions. //! The anchor change is handled using the `AnchorVariant` type trait. use std::{fmt, io}; use crate::{ block::MAX_BLOCK_BYTES, primitives::{ redjubjub::{self, SpendAuth}, Groth16Proof, }, serialization::{ ReadZcashExt, SerializationError, TrustedPreallocate, WriteZcashExt, ZcashDeserialize, ZcashSerialize, }, }; use super::{commitment, note, tree, AnchorVariant, FieldNotPresent, PerSpendAnchor, SharedAnchor}; /// A _Spend Description_, as described in [protocol specification §7.3][ps]. /// /// # Differences between Transaction Versions /// /// In `Transaction::V4`, each `Spend` has its own anchor. In `Transaction::V5`, /// there is a single `shared_anchor` for the entire transaction. This /// structural difference is modeled using the `AnchorVariant` type trait. /// /// `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#spendencoding #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct Spend { /// A value commitment to the value of the input note. pub cv: commitment::ValueCommitment, /// An anchor for this spend. /// /// The anchor is the root of the Sapling note commitment tree in a previous /// block. This root should be in the best chain for a transaction to be /// mined, and it must be in the relevant chain for a transaction to be /// valid. /// /// Some transaction versions have a shared anchor, rather than a per-spend /// anchor. pub per_spend_anchor: AnchorV::PerSpend, /// The nullifier of the input note. pub nullifier: note::Nullifier, /// The randomized public key for `spend_auth_sig`. pub rk: redjubjub::VerificationKeyBytes, /// The ZK spend proof. pub zkproof: Groth16Proof, /// A signature authorizing this spend. pub spend_auth_sig: redjubjub::Signature, } /// The serialization prefix fields of a `Spend` in Transaction V5. /// /// In `V5` transactions, spends are split into multiple arrays, so the prefix, /// proof, and signature must be serialised and deserialized separately. /// /// Serialized as `SpendDescriptionV5` in [protocol specification §7.3][ps]. /// /// [ps]: https://zips.z.cash/protocol/protocol.pdf#spendencoding #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct SpendPrefixInTransactionV5 { /// A value commitment to the value of the input note. pub cv: commitment::ValueCommitment, /// The nullifier of the input note. pub nullifier: note::Nullifier, /// The randomized public key for `spend_auth_sig`. pub rk: redjubjub::VerificationKeyBytes, } impl fmt::Display for Spend where AnchorV: AnchorVariant + Clone, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut fmter = f .debug_struct(format!("sapling::Spend<{}>", std::any::type_name::()).as_str()); fmter.field("per_spend_anchor", &self.per_spend_anchor); fmter.field("nullifier", &self.nullifier); fmter.finish() } } impl From<(Spend, tree::Root)> for Spend { /// Convert a `Spend` and its shared anchor, into a /// `Spend`. fn from(shared_spend: (Spend, tree::Root)) -> Self { Spend:: { per_spend_anchor: shared_spend.1, cv: shared_spend.0.cv, nullifier: shared_spend.0.nullifier, rk: shared_spend.0.rk, zkproof: shared_spend.0.zkproof, spend_auth_sig: shared_spend.0.spend_auth_sig, } } } impl From<(Spend, FieldNotPresent)> for Spend { /// Take the `Spend` from a spend + anchor tuple. fn from(per_spend: (Spend, FieldNotPresent)) -> Self { per_spend.0 } } impl Spend { /// Encodes the primary inputs for the proof statement as 7 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#cctsaplingspend pub fn primary_inputs(&self) -> Vec { let mut inputs = vec![]; let rk_affine = jubjub::AffinePoint::from_bytes(self.rk.into()).unwrap(); inputs.push(rk_affine.get_u()); inputs.push(rk_affine.get_v()); let cv_affine = jubjub::AffinePoint::from_bytes(self.cv.into()).unwrap(); inputs.push(cv_affine.get_u()); inputs.push(cv_affine.get_v()); // TODO: V4 only inputs.push(jubjub::Fq::from_bytes(&self.per_spend_anchor.into()).unwrap()); let nullifier_limbs: [jubjub::Fq; 2] = self.nullifier.into(); inputs.push(nullifier_limbs[0]); inputs.push(nullifier_limbs[1]); inputs } } impl Spend { /// Combine the prefix and non-prefix fields from V5 transaction /// deserialization. pub fn from_v5_parts( prefix: SpendPrefixInTransactionV5, zkproof: Groth16Proof, spend_auth_sig: redjubjub::Signature, ) -> Spend { Spend:: { cv: prefix.cv, per_spend_anchor: FieldNotPresent, nullifier: prefix.nullifier, rk: prefix.rk, zkproof, spend_auth_sig, } } /// Split out the prefix and non-prefix fields for V5 transaction /// serialization. pub fn into_v5_parts( self, ) -> ( SpendPrefixInTransactionV5, Groth16Proof, redjubjub::Signature, ) { let prefix = SpendPrefixInTransactionV5 { cv: self.cv, nullifier: self.nullifier, rk: self.rk, }; (prefix, self.zkproof, self.spend_auth_sig) } } impl ZcashSerialize for Spend { fn zcash_serialize(&self, mut writer: W) -> Result<(), io::Error> { self.cv.zcash_serialize(&mut writer)?; writer.write_all(&self.per_spend_anchor.0[..])?; writer.write_32_bytes(&self.nullifier.into())?; writer.write_all(&<[u8; 32]>::from(self.rk)[..])?; self.zkproof.zcash_serialize(&mut writer)?; writer.write_all(&<[u8; 64]>::from(self.spend_auth_sig)[..])?; Ok(()) } } impl ZcashDeserialize for Spend { fn zcash_deserialize(mut reader: R) -> Result { Ok(Spend { cv: commitment::ValueCommitment::zcash_deserialize(&mut reader)?, per_spend_anchor: tree::Root(reader.read_32_bytes()?), nullifier: note::Nullifier::from(reader.read_32_bytes()?), rk: reader.read_32_bytes()?.into(), zkproof: Groth16Proof::zcash_deserialize(&mut reader)?, spend_auth_sig: reader.read_64_bytes()?.into(), }) } } // zkproof and spend_auth_sig are deserialized separately, so we can only // deserialize Spend in the context of a V5 transaction. // // Instead, implement serialization and deserialization for the // Spend prefix fields, which are stored in the same array. impl ZcashSerialize for SpendPrefixInTransactionV5 { fn zcash_serialize(&self, mut writer: W) -> Result<(), io::Error> { self.cv.zcash_serialize(&mut writer)?; writer.write_32_bytes(&self.nullifier.into())?; writer.write_all(&<[u8; 32]>::from(self.rk)[..])?; Ok(()) } } impl ZcashDeserialize for SpendPrefixInTransactionV5 { fn zcash_deserialize(mut reader: R) -> Result { Ok(SpendPrefixInTransactionV5 { cv: commitment::ValueCommitment::zcash_deserialize(&mut reader)?, nullifier: note::Nullifier::from(reader.read_32_bytes()?), rk: reader.read_32_bytes()?.into(), }) } } /// In Transaction V5, SpendAuth signatures are serialized and deserialized in a /// separate array. impl ZcashSerialize for redjubjub::Signature { fn zcash_serialize(&self, mut writer: W) -> Result<(), io::Error> { writer.write_all(&<[u8; 64]>::from(*self)[..])?; Ok(()) } } impl ZcashDeserialize for redjubjub::Signature { fn zcash_deserialize(mut reader: R) -> Result { Ok(reader.read_64_bytes()?.into()) } } /// The size of a spend with a per-spend anchor. pub(crate) const ANCHOR_PER_SPEND_SIZE: u64 = SHARED_ANCHOR_SPEND_SIZE + 32; /// The size of a spend with a shared anchor, without associated fields. /// /// This is the size of spends in the initial array, there are another /// 2 arrays of zkproofs and spend_auth_sigs required in the transaction format. pub(crate) const SHARED_ANCHOR_SPEND_PREFIX_SIZE: u64 = 32 + 32 + 32; /// The size of a spend with a shared anchor, including associated fields. /// /// A Spend contains: a 32 byte cv, a 32 byte anchor (transaction V4 only), /// a 32 byte nullifier, a 32 byte rk, a 192 byte zkproof (serialized separately /// in V5), and a 64 byte spendAuthSig (serialized separately in V5). /// /// [ps]: https://zips.z.cash/protocol/protocol.pdf#spendencoding pub(crate) const SHARED_ANCHOR_SPEND_SIZE: u64 = SHARED_ANCHOR_SPEND_PREFIX_SIZE + 192 + 64; /// The maximum number of sapling spends in a valid Zcash on-chain transaction V4. impl TrustedPreallocate for Spend { fn max_allocation() -> u64 { const MAX: u64 = (MAX_BLOCK_BYTES - 1) / ANCHOR_PER_SPEND_SIZE; // > [NU5 onward] nSpendsSapling, nOutputsSapling, and nActionsOrchard MUST all be less than 2^16. // https://zips.z.cash/protocol/protocol.pdf#txnencodingandconsensus // This acts as nSpendsSapling and is therefore subject to the rule. // The maximum value is actually smaller due to the block size limit, // but we ensure the 2^16 limit with a static assertion. // (The check is not required pre-NU5, but it doesn't cause problems.) static_assertions::const_assert!(MAX < (1 << 16)); MAX } } /// The maximum number of sapling spends in a valid Zcash on-chain transaction V5. /// /// If a transaction contains more spends 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 SpendPrefixInTransactionV5 { fn max_allocation() -> u64 { // Since a serialized Vec uses at least one byte for its length, // and the associated fields are required, // a valid max allocation can never exceed this size const MAX: u64 = (MAX_BLOCK_BYTES - 1) / SHARED_ANCHOR_SPEND_SIZE; // > [NU5 onward] nSpendsSapling, nOutputsSapling, and nActionsOrchard MUST all be less than 2^16. // https://zips.z.cash/protocol/protocol.pdf#txnencodingandconsensus // This acts as nSpendsSapling and is therefore subject to the rule. // The maximum value is actually smaller due to the block size limit, // but we ensure the 2^16 limit with a static assertion. // (The check is not required pre-NU5, but it doesn't cause problems.) static_assertions::const_assert!(MAX < (1 << 16)); MAX } } impl TrustedPreallocate for redjubjub::Signature { fn max_allocation() -> u64 { // Each associated field must have a corresponding spend prefix. SpendPrefixInTransactionV5::max_allocation() } }