//! Sapling shielded data for `V4` and `V5` `Transaction`s. //! //! Zebra uses a generic shielded data type for `V4` and `V5` transactions. //! The `value_balance` change is handled using the default zero value. //! The anchor change is handled using the `AnchorVariant` type trait. use serde::{de::DeserializeOwned, Serialize}; use crate::{ amount::Amount, primitives::{ redjubjub::{Binding, Signature}, Groth16Proof, }, sapling::{ output::OutputPrefixInTransactionV5, spend::SpendPrefixInTransactionV5, tree, Nullifier, Output, Spend, ValueCommitment, }, serialization::{AtLeastOne, TrustedPreallocate}, }; use std::{ cmp::{max, Eq, PartialEq}, fmt::Debug, }; /// Per-Spend Sapling anchors, used in Transaction V4 and the /// `spends_per_anchor` method. #[derive(Copy, Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] pub struct PerSpendAnchor {} /// Shared Sapling anchors, used in Transaction V5. #[derive(Copy, Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] pub struct SharedAnchor {} /// This field is not present in this transaction version. #[derive(Copy, Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] pub struct FieldNotPresent; impl AnchorVariant for PerSpendAnchor { type Shared = FieldNotPresent; type PerSpend = tree::Root; } impl AnchorVariant for SharedAnchor { type Shared = tree::Root; type PerSpend = FieldNotPresent; } /// A type trait to handle structural differences between V4 and V5 Sapling /// Transaction anchors. /// /// In Transaction V4, anchors are per-Spend. In Transaction V5, there is a /// single transaction anchor for all Spends in a transaction. pub trait AnchorVariant { /// The type of the shared anchor. type Shared: Clone + Debug + DeserializeOwned + Serialize + Eq + PartialEq; /// The type of the per-spend anchor. type PerSpend: Clone + Debug + DeserializeOwned + Serialize + Eq + PartialEq; } /// A bundle of [`Spend`] and [`Output`] descriptions and signature data. /// /// Spend and Output descriptions are optional, but Zcash transactions must /// include a binding signature if and only if there is at least one Spend *or* /// Output description. This wrapper type bundles at least one Spend or Output /// description with the required signature data, so that an /// `Option` correctly models the presence or absence of any /// shielded data. /// /// # Differences between Transaction Versions /// /// The Sapling `value_balance` field is optional in `Transaction::V5`, but /// required in `Transaction::V4`. In both cases, if there is no `ShieldedData`, /// then the field value must be zero. Therefore, we only need to store /// `value_balance` when there is some Sapling `ShieldedData`. /// /// In `Transaction::V4`, each `Spend` has its own anchor. In `Transaction::V5`, /// there is a single `shared_anchor` for the entire transaction, which is only /// present when there is at least one spend. These structural differences are /// modeled using the `AnchorVariant` type trait and `TransferData` enum. #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct ShieldedData where AnchorV: AnchorVariant + Clone, { /// The net value of Sapling spend transfers minus output transfers. /// /// [`ShieldedData`] validates this [value balance consensus /// rule](https://zips.z.cash/protocol/nu5.pdf#txnencodingandconsensus): /// /// "If effectiveVersion = 4 and there are no Spend descriptions or Output /// descriptions, then valueBalanceSapling MUST be 0." /// /// During deserialization, this rule is checked when there are no spends and /// no outputs. /// /// During serialization, this rule is structurally validated by [`ShieldedData`]. /// `value_balance` is a field in [`ShieldedData`], which must have at least /// one spend or output in its `transfers` field. If [`ShieldedData`] is `None` /// then there can not possibly be any spends or outputs, and the /// `value_balance` is always serialized as zero. pub value_balance: Amount, /// A bundle of spends and outputs, containing at least one spend or /// output. /// /// In V5 transactions, also contains a shared anchor, if there are any /// spends. pub transfers: TransferData, /// A signature on the transaction hash. pub binding_sig: Signature, } /// A bundle of [`Spend`] and [`Output`] descriptions, and a shared anchor. /// /// This wrapper type bundles at least one Spend or Output description with /// the required anchor data, so that an `Option` (which contains /// this type) correctly models the presence or absence of any spends and /// shielded data, across both V4 and V5 transactions. /// /// Specifically, TransferData ensures that: /// * there is at least one spend or output, and /// * the shared anchor is only present when there are spends. #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum TransferData where AnchorV: AnchorVariant + Clone, { /// A bundle containing at least one spend, and the shared spend anchor. /// There can also be zero or more outputs. /// /// In Transaction::V5, if there are any spends, there must also be a shared /// spend anchor. SpendsAndMaybeOutputs { /// The shared anchor for all `Spend`s in this transaction. /// /// 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 per-spend anchor, rather than a shared /// anchor. /// /// Use the `shared_anchor` method to access this field. shared_anchor: AnchorV::Shared, /// At least one spend. /// /// Use the [`ShieldedData::spends`] method to get an iterator over the /// [`Spend`]s in this `TransferData`. spends: AtLeastOne>, /// Maybe some outputs (can be empty). /// /// Use the [`ShieldedData::outputs`] method to get an iterator over the /// [`Outputs`]s in this `TransferData`. maybe_outputs: Vec, }, /// A bundle containing at least one output, with no spends and no shared /// spend anchor. /// /// In Transaction::V5, if there are no spends, there must not be a shared /// anchor. JustOutputs { /// At least one output. /// /// Use the [`ShieldedData::outputs`] method to get an iterator over the /// [`Outputs`]s in this `TransferData`. outputs: AtLeastOne, }, } impl ShieldedData where AnchorV: AnchorVariant + Clone, Spend: From<(Spend, AnchorV::Shared)>, { /// Iterate over the [`Spend`]s for this transaction, returning /// `Spend` regardless of the underlying transaction version. /// /// # Correctness /// /// Do not use this function for serialization. pub fn spends_per_anchor(&self) -> impl Iterator> + '_ { self.transfers.spends_per_anchor() } } impl ShieldedData where AnchorV: AnchorVariant + Clone, { /// Iterate over the [`Spend`]s for this transaction, returning them as /// their generic type. /// /// # Correctness /// /// Use this function for serialization. pub fn spends(&self) -> impl Iterator> { self.transfers.spends() } /// Iterate over the [`Output`]s for this transaction. pub fn outputs(&self) -> impl Iterator { self.transfers.outputs() } /// Provide the shared anchor for this transaction, if present. /// /// The shared anchor is only present if: /// * there is at least one spend, and /// * this is a `V5` transaction. pub fn shared_anchor(&self) -> Option { self.transfers.shared_anchor() } /// Collect the [`Nullifier`]s for this transaction, if it contains /// [`Spend`]s. pub fn nullifiers(&self) -> impl Iterator { self.spends().map(|spend| &spend.nullifier) } /// Collect the cm_u's for this transaction, if it contains [`Output`]s. pub fn note_commitments(&self) -> impl Iterator { self.outputs().map(|output| &output.cm_u) } /// Calculate the Spend/Output binding verification key. /// /// Getting the binding signature validating key from the Spend and Output /// description value commitments and the balancing value implicitly checks /// that the balancing value is consistent with the value transfered in the /// Spend and Output descriptions but also proves that the signer knew the /// randomness used for the Spend and Output value commitments, which /// prevents replays of Output descriptions. /// /// The net value of Spend transfers minus Output transfers in a transaction /// is called the balancing value, measured in zatoshi as a signed integer /// v_balance. /// /// Consistency of v_balance with the value commitments in Spend /// descriptions and Output descriptions is enforced by the binding /// signature. /// /// Instead of generating a key pair at random, we generate it as a function /// of the value commitments in the Spend descriptions and Output /// descriptions of the transaction, and the balancing value. /// /// https://zips.z.cash/protocol/protocol.pdf#saplingbalance pub fn binding_verification_key(&self) -> redjubjub::VerificationKeyBytes { let cv_old: ValueCommitment = self.spends().map(|spend| spend.cv).sum(); let cv_new: ValueCommitment = self.outputs().map(|output| output.cv).sum(); let cv_balance: ValueCommitment = ValueCommitment::new(jubjub::Fr::zero(), self.value_balance); let key_bytes: [u8; 32] = (cv_old - cv_new - cv_balance).into(); key_bytes.into() } } impl TransferData where AnchorV: AnchorVariant + Clone, Spend: From<(Spend, AnchorV::Shared)>, { /// Iterate over the [`Spend`]s for this transaction, returning /// `Spend` regardless of the underlying transaction version. /// /// Allows generic operations over V4 and V5 transactions, including: /// * spend verification, and /// * non-malleable transaction ID generation. /// /// # Correctness /// /// Do not use this function for serialization. pub fn spends_per_anchor(&self) -> impl Iterator> + '_ { self.spends().cloned().map(move |spend| { Spend::::from(( spend, self.shared_anchor() .expect("shared anchor must be Some if there are any spends"), )) }) } } impl TransferData where AnchorV: AnchorVariant + Clone, { /// Iterate over the [`Spend`]s for this transaction, returning them as /// their generic type. pub fn spends(&self) -> impl Iterator> { use TransferData::*; let spends = match self { SpendsAndMaybeOutputs { spends, .. } => Some(spends.iter()), JustOutputs { .. } => None, }; // this awkward construction avoids returning a newtype struct or // type-erased boxed iterator spends.into_iter().flatten() } /// Iterate over the [`Output`]s for this transaction. pub fn outputs(&self) -> impl Iterator { use TransferData::*; match self { SpendsAndMaybeOutputs { maybe_outputs, .. } => maybe_outputs, JustOutputs { outputs, .. } => outputs.as_vec(), } .iter() } /// Provide the shared anchor for this transaction, if present. /// /// The shared anchor is only present if: /// * there is at least one spend, and /// * this is a `V5` transaction. pub fn shared_anchor(&self) -> Option { use TransferData::*; match self { SpendsAndMaybeOutputs { shared_anchor, .. } => Some(shared_anchor.clone()), JustOutputs { .. } => None, } } } impl TrustedPreallocate for Groth16Proof { fn max_allocation() -> u64 { // Each V5 transaction proof array entry must have a corresponding // spend or output prefix. We use the larger limit, so we don't reject // any valid large blocks. // // TODO: put a separate limit on proofs in spends and outputs max( SpendPrefixInTransactionV5::max_allocation(), OutputPrefixInTransactionV5::max_allocation(), ) } }