zebra/zebra-chain/src/transaction/shielded_data.rs

132 lines
4.8 KiB
Rust

// XXX this name seems too long?
use crate::note_commitment_tree::SaplingNoteTreeRootHash;
use crate::notes::sapling;
use crate::proofs::Groth16Proof;
use crate::redjubjub::{self, Binding, SpendAuth};
use crate::serde_helpers;
use futures::future::Either;
/// A _Spend Description_, as described in [protocol specification §7.3][ps].
///
/// [ps]: https://zips.z.cash/protocol/protocol.pdf#spendencoding
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Spend {
/// A value commitment to the value of the input note.
///
/// XXX refine to a specific type.
pub cv: [u8; 32],
/// A root of the Sapling note commitment tree at some block height in the past.
pub anchor: SaplingNoteTreeRootHash,
/// The nullifier of the input note.
///
/// XXX refine to a specific type.
pub nullifier: [u8; 32],
/// The randomized public key for `spend_auth_sig`.
pub rk: redjubjub::PublicKeyBytes<SpendAuth>,
/// The ZK spend proof.
pub zkproof: Groth16Proof,
/// A signature authorizing this spend.
pub spend_auth_sig: redjubjub::Signature<SpendAuth>,
}
/// A _Output Description_, as described in [protocol specification §7.4][ps].
///
/// [ps]: https://zips.z.cash/protocol/protocol.pdf#outputencoding
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Output {
/// A value commitment to the value of the input note.
///
/// XXX refine to a specific type.
pub cv: [u8; 32],
/// The u-coordinate of the note commitment for the output note.
///
/// XXX refine to a specific type.
pub cmu: [u8; 32],
/// An encoding of an ephemeral Jubjub public key.
#[serde(with = "serde_helpers::AffinePoint")]
pub ephemeral_key: jubjub::AffinePoint,
/// A ciphertext component for the encrypted output note.
pub enc_ciphertext: sapling::EncryptedCiphertext,
/// A ciphertext component for the encrypted output note.
pub out_ciphertext: sapling::OutCiphertext,
/// The ZK output proof.
pub zkproof: Groth16Proof,
}
impl Eq for Output {}
/// Sapling-on-Groth16 spend and output descriptions.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ShieldedData {
/// Either a spend or output description.
///
/// Storing this separately ensures that it is impossible to construct
/// an invalid `ShieldedData` with no spends or outputs.
///
/// However, it's not necessary to access or process `first` and `rest`
/// separately, as the [`ShieldedData::spends`] and [`ShieldedData::outputs`]
/// methods provide iterators over all of the [`SpendDescription`]s and
/// [`Output`]s.
#[serde(with = "serde_helpers::Either")]
pub first: Either<Spend, Output>,
/// The rest of the [`Spend`]s for this transaction.
///
/// Note that the [`ShieldedData::spends`] method provides an iterator
/// over all spend descriptions.
pub rest_spends: Vec<Spend>,
/// The rest of the [`Output`]s for this transaction.
///
/// Note that the [`ShieldedData::outputs`] method provides an iterator
/// over all output descriptions.
pub rest_outputs: Vec<Output>,
/// A signature on the transaction hash.
pub binding_sig: redjubjub::Signature<Binding>,
}
impl ShieldedData {
/// Iterate over the [`Spend`]s for this transaction.
pub fn spends(&self) -> impl Iterator<Item = &Spend> {
match self.first {
Either::Left(ref spend) => Some(spend),
Either::Right(_) => None,
}
.into_iter()
.chain(self.rest_spends.iter())
}
/// Iterate over the [`Output`]s for this transaction.
pub fn outputs(&self) -> impl Iterator<Item = &Output> {
match self.first {
Either::Left(_) => None,
Either::Right(ref output) => Some(output),
}
.into_iter()
.chain(self.rest_outputs.iter())
}
}
// Technically, it's possible to construct two equivalent representations
// of a ShieldedData with at least one spend and at least one output, depending
// on which goes in the `first` slot. This is annoying but a smallish price to
// pay for structural validity.
impl std::cmp::PartialEq for ShieldedData {
fn eq(&self, other: &Self) -> bool {
// First check that the lengths match, so we know it is safe to use zip,
// which truncates to the shorter of the two iterators.
if self.spends().count() != other.spends().count() {
return false;
}
if self.outputs().count() != other.outputs().count() {
return false;
}
// Now check that the binding_sig, spends, outputs match.
self.binding_sig == other.binding_sig
&& self.spends().zip(other.spends()).all(|(a, b)| a == b)
&& self.outputs().zip(other.outputs()).all(|(a, b)| a == b)
}
}
impl std::cmp::Eq for ShieldedData {}