pczt: Add fields necessary for creating proofs

This commit is contained in:
Jack Grigg 2024-10-12 06:14:47 +00:00
parent 8766c51142
commit 1a185afa06
12 changed files with 762 additions and 14 deletions

4
Cargo.lock generated
View File

@ -2867,6 +2867,7 @@ name = "pczt"
version = "0.0.0"
dependencies = [
"bls12_381",
"ff",
"jubjub",
"nonempty",
"orchard",
@ -3684,8 +3685,7 @@ dependencies = [
[[package]]
name = "sapling-crypto"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfff8cfce16aeb38da50b8e2ed33c9018f30552beff2210c266662a021b17f38"
source = "git+https://github.com/zcash/sapling-crypto.git?rev=a8fb276252894cf5d0e97e289f5509cc89bad095#a8fb276252894cf5d0e97e289f5509cc89bad095"
dependencies = [
"aes",
"bellman",

View File

@ -182,3 +182,6 @@ debug = true
[workspace.lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(zcash_unstable, values("zfuture"))'] }
[patch.crates-io]
sapling-crypto = { git = "https://github.com/zcash/sapling-crypto.git", rev = "a8fb276252894cf5d0e97e289f5509cc89bad095" }

View File

@ -20,6 +20,7 @@ rand_core = { workspace = true, optional = true }
# Payment protocols
# - Sapling
bls12_381 = { workspace = true, optional = true }
ff = { workspace = true, optional = true }
jubjub = { workspace = true, optional = true }
redjubjub = { workspace = true, optional = true }
sapling = { workspace = true, optional = true }
@ -38,6 +39,7 @@ orchard = [
]
sapling = [
"dep:bls12_381",
"dep:ff",
"dep:jubjub",
"dep:redjubjub",
"dep:sapling",
@ -45,5 +47,6 @@ sapling = [
"dep:zcash_protocol",
]
transparent = ["dep:zcash_primitives", "dep:zcash_protocol"]
prover = ["dep:rand_core", "sapling?/temporary-zcashd"]
signer = ["dep:rand_core", "orchard", "sapling", "transparent"]
tx-extractor = ["dep:rand_core", "orchard", "sapling", "transparent"]

View File

@ -10,9 +10,21 @@
//! `Global.tx_modifiable` field. Inputs may only be added if the Inputs Modifiable
//! flag is True. Outputs may only be added if the Outputs Modifiable flag is True.
//! - A single entity is likely to be both a Creator and Constructor.
//! - Prover (capability holders can contribute)
//! - Needs all private information for a single spend or output.
//! - In practice, the Updater that adds a given spend or output will either act as
//! the Prover themselves, or add the necessary data, offload to the Prover, and
//! then receive back the PCZT with private data stripped and proof added.
//! - Signer (capability holders can contribute)
//! - Needs the spend authorization randomizers to create signatures.
//! - Needs sufficient information to verify that the proof is over the correct data,
//! without needing to verify the proof itself.
//! - A Signer should only need to implement:
//! - Pedersen commitments using Jubjub / Pallas arithmetic (for note and value
//! commitments)
//! - BLAKE2b and BLAKE2s (and the various PRFs / CRHs they are used in)
//! - Nullifier check (using Jubjub / Pallas arithmetic)
//! - KDF plus note decryption (AEAD_CHACHA20_POLY1305)
//! - SignatureHash algorithm
//! - Signatures (RedJubjub / RedPallas)
//! - A source of randomness.

View File

@ -1,7 +1,14 @@
use crate::roles::combiner::merge_optional;
use crate::{roles::combiner::merge_optional, IgnoreMissing};
#[cfg(feature = "orchard")]
use pasta_curves::{group::ff::PrimeField, pallas};
use {
orchard::{
note::{RandomSeed, Rho},
value::NoteValue,
Address, Note,
},
pasta_curves::{group::ff::PrimeField, pallas},
};
/// PCZT fields that are specific to producing the transaction's Orchard bundle (if any).
#[derive(Clone, Debug)]
@ -58,6 +65,17 @@ pub(crate) struct Action {
pub(crate) cv_net: [u8; 32],
pub(crate) spend: Spend,
pub(crate) output: Output,
/// The value commitment randomness.
///
/// - This is set by the Constructor.
/// - The IO Finalizer compresses it into the bsk.
/// - This is required by the Prover.
/// - This may be used by Signers to verify that the value correctly matches `cv`.
///
/// This opens `cv` for all participants. For Signers who don't need this information,
/// or after proofs / signatures have been applied, this can be redacted.
pub(crate) rcv: Option<[u8; 32]>,
}
/// Information about a Sapling spend within a transaction.
@ -77,6 +95,48 @@ pub(crate) struct Spend {
/// This is set by the Signer.
pub(crate) spend_auth_sig: Option<[u8; 64]>,
/// The [raw encoding] of the Orchard payment address that received the note being spent.
///
/// - This is set by the Constructor.
/// - This is required by the Prover.
///
/// [raw encoding]: https://zips.z.cash/protocol/protocol.pdf#orchardpaymentaddrencoding
pub(crate) recipient: Option<[u8; 43]>,
/// The value of the input being spent.
///
/// - This is required by the Prover.
/// - This may be used by Signers to verify that the value matches `cv`, and to
/// confirm the values and change involved in the transaction.
///
/// This exposes the input value to all participants. For Signers who don't need this
/// information, or after signatures have been applied, this can be redacted.
pub(crate) value: Option<u64>,
/// The rho value for the note being spent.
///
/// - This is set by the Constructor.
/// - This is required by the Prover.
pub(crate) rho: Option<[u8; 32]>,
/// The seed randomness for the note being spent.
///
/// - This is set by the Constructor.
/// - This is required by the Prover.
pub(crate) rseed: Option<[u8; 32]>,
/// The full viewing key that received the note being spent.
///
/// - This is set by the Updater.
/// - This is required by the Prover.
pub(crate) fvk: Option<[u8; 96]>,
/// A witness from the note to the bundle's anchor.
///
/// - This is set by the Updater.
/// - This is required by the Prover.
pub(crate) witness: Option<(u32, [[u8; 32]; 32])>,
/// The spend authorization randomizer.
///
/// - This is chosen by the Constructor.
@ -115,6 +175,29 @@ pub(crate) struct Output {
///
/// Encoded as a `Vec<u8>` because its length depends on the transaction version.
pub(crate) out_ciphertext: Vec<u8>,
/// The [raw encoding] of the Orchard payment address that will receive the output.
///
/// - This is set by the Constructor.
/// - This is required by the Prover.
///
/// [raw encoding]: https://zips.z.cash/protocol/protocol.pdf#orchardpaymentaddrencoding
pub(crate) recipient: Option<[u8; 43]>,
/// The value of the output.
///
/// This may be used by Signers to verify that the value matches `cv`, and to confirm
/// the values and change involved in the transaction.
///
/// This exposes the value to all participants. For Signers who don't need this
/// information, we can drop the values and compress the rcvs into the bsk global.
pub(crate) value: Option<u64>,
/// The seed randomness for the output.
///
/// - This is set by the Constructor.
/// - This is required by the Prover, instead of disclosing `shared_secret` to them.
pub(crate) rseed: Option<[u8; 32]>,
}
impl Bundle {
@ -180,6 +263,12 @@ impl Bundle {
nullifier,
rk,
spend_auth_sig,
recipient,
value,
rho,
rseed,
fvk,
witness,
alpha,
dummy_sk,
},
@ -189,7 +278,11 @@ impl Bundle {
ephemeral_key,
enc_ciphertext,
out_ciphertext,
recipient: output_recipient,
value: output_value,
rseed: output_rseed,
},
rcv,
} = rhs;
if lhs.cv_net != cv_net
@ -204,8 +297,18 @@ impl Bundle {
}
if !(merge_optional(&mut lhs.spend.spend_auth_sig, spend_auth_sig)
&& merge_optional(&mut lhs.spend.recipient, recipient)
&& merge_optional(&mut lhs.spend.value, value)
&& merge_optional(&mut lhs.spend.rho, rho)
&& merge_optional(&mut lhs.spend.rseed, rseed)
&& merge_optional(&mut lhs.spend.fvk, fvk)
&& merge_optional(&mut lhs.spend.witness, witness)
&& merge_optional(&mut lhs.spend.alpha, alpha)
&& merge_optional(&mut lhs.spend.dummy_sk, dummy_sk))
&& merge_optional(&mut lhs.spend.dummy_sk, dummy_sk)
&& merge_optional(&mut lhs.output.recipient, output_recipient)
&& merge_optional(&mut lhs.output.value, output_value)
&& merge_optional(&mut lhs.output.rseed, output_rseed)
&& merge_optional(&mut lhs.rcv, rcv))
{
return None;
}
@ -313,6 +416,57 @@ impl Bundle {
#[cfg(feature = "orchard")]
impl Spend {
/// Parses a [`Note`] from the explicit fields of this spend.
pub(crate) fn note_from_fields(&self) -> Result<Note, Error> {
// We want to parse all fields that are present for validity, before raising any
// errors about missing fields. The only exception is that we raise an error for a
// missing rho before validating rseed (as the latter requires rho).
let recipient = self
.recipient
.as_ref()
.map(|r| {
Address::from_raw_address_bytes(r)
.into_option()
.ok_or(Error::InvalidSpendRecipient)
})
.transpose()?;
let value = self.value.map(NoteValue::from_raw);
let rho = self
.rho
.as_ref()
.map(|rho| Rho::from_bytes(rho).into_option().ok_or(Error::InvalidRho))
.transpose()?
.ok_or(Error::MissingRho)?;
let rseed = self
.rseed
.map(|rseed| {
RandomSeed::from_bytes(rseed, &rho)
.into_option()
.ok_or(Error::InvalidRandomSeed)
})
.transpose()?;
Note::from_parts(
recipient.ok_or(Error::MissingSpendRecipient)?,
value.ok_or(Error::MissingValue)?,
rho,
rseed.ok_or(Error::MissingRandomSeed)?,
)
.into_option()
.ok_or(Error::InvalidSpendNote)
}
pub(crate) fn fvk_from_field(&self) -> Result<orchard::keys::FullViewingKey, Error> {
orchard::keys::FullViewingKey::from_bytes(
self.fvk.as_ref().ok_or(Error::MissingFullViewingKey)?,
)
.ok_or(Error::InvalidFullViewingKey)
}
pub(crate) fn alpha_from_field(&self) -> Result<pallas::Scalar, Error> {
pallas::Scalar::from_repr(self.alpha.ok_or(Error::MissingSpendAuthRandomizer)?)
.into_option()
@ -326,12 +480,40 @@ pub enum Error {
InvalidAnchor,
InvalidEncCiphertext,
InvalidExtractedNoteCommitment,
InvalidFullViewingKey,
InvalidNullifier,
InvalidOutCiphertext,
InvalidRandomizedKey,
InvalidRandomSeed,
InvalidRho,
InvalidSpendAuthRandomizer,
InvalidSpendNote,
InvalidSpendRecipient,
InvalidValueBalance(zcash_protocol::value::BalanceError),
InvalidValueCommitment,
MissingFullViewingKey,
MissingRandomSeed,
MissingRho,
MissingSpendAuthRandomizer,
MissingSpendRecipient,
MissingValue,
UnexpectedFlagBitsSet,
}
#[cfg(feature = "orchard")]
impl<V> IgnoreMissing for Result<V, Error> {
type Value = V;
type Error = Error;
fn ignore_missing(self) -> Result<Option<Self::Value>, Self::Error> {
self.map(Some).or_else(|e| match e {
Error::MissingFullViewingKey
| Error::MissingRandomSeed
| Error::MissingRho
| Error::MissingSpendAuthRandomizer
| Error::MissingSpendRecipient
| Error::MissingValue => Ok(None),
_ => Err(e),
})
}
}

View File

@ -1,5 +1,8 @@
pub mod creator;
#[cfg(feature = "prover")]
pub mod prover;
#[cfg(feature = "signer")]
pub mod signer;

View File

@ -0,0 +1,27 @@
use crate::Pczt;
#[cfg(feature = "orchard")]
mod orchard;
#[cfg(feature = "orchard")]
pub use orchard::OrchardError;
#[cfg(feature = "sapling")]
mod sapling;
#[cfg(feature = "sapling")]
pub use sapling::SaplingError;
pub struct Prover {
pczt: Pczt,
}
impl Prover {
/// Instantiates the Prover role with the given PCZT.
pub fn new(pczt: Pczt) -> Self {
Self { pczt }
}
/// Finishes the Prover role, returning the updated PCZT.
pub fn finish(self) -> Pczt {
self.pczt
}
}

View File

@ -0,0 +1,164 @@
use orchard::{
builder::SpendInfo,
bundle::Flags,
circuit::{Circuit, Instance, ProvingKey},
note::{ExtractedNoteCommitment, Nullifier, RandomSeed, Rho},
primitives::redpallas,
tree::{MerkleHashOrchard, MerklePath},
value::{NoteValue, ValueCommitTrapdoor, ValueCommitment},
Address, Anchor, Note,
};
use rand_core::OsRng;
impl super::Prover {
pub fn create_orchard_proof(&mut self, pk: &ProvingKey) -> Result<(), OrchardError> {
let bundle = &self.pczt.orchard;
let flags =
Flags::from_byte(bundle.flags).ok_or(crate::orchard::Error::UnexpectedFlagBitsSet)?;
let anchor = Anchor::from_bytes(bundle.anchor)
.into_option()
.ok_or(crate::orchard::Error::InvalidAnchor)?;
let circuits = self
.pczt
.orchard
.actions
.iter()
.map(|action| {
let fvk = action.spend.fvk_from_field()?;
let note = action.spend.note_from_fields()?;
let merkle_path = {
let (position, auth_path_bytes) =
action.spend.witness.ok_or(OrchardError::MissingWitness)?;
let auth_path = auth_path_bytes
.into_iter()
.map(|hash| {
MerkleHashOrchard::from_bytes(&hash)
.into_option()
.ok_or(OrchardError::InvalidWitness)
})
.collect::<Result<Vec<_>, _>>()?;
MerklePath::from_parts(
position,
auth_path[..].try_into().expect("correct length"),
)
};
let spend =
SpendInfo::new(fvk, note, merkle_path).ok_or(OrchardError::WrongFvkForNote)?;
let output_note = {
let recipient = Address::from_raw_address_bytes(
action
.output
.recipient
.as_ref()
.ok_or(OrchardError::MissingOutputRecipient)?,
)
.into_option()
.ok_or(OrchardError::InvalidOutputRecipient)?;
let value =
NoteValue::from_raw(action.output.value.ok_or(OrchardError::MissingValue)?);
let rho = Rho::from_bytes(&action.spend.nullifier)
.into_option()
.ok_or(crate::orchard::Error::InvalidNullifier)?;
let rseed = RandomSeed::from_bytes(
action.output.rseed.ok_or(OrchardError::MissingRandomSeed)?,
&rho,
)
.into_option()
.ok_or(OrchardError::InvalidRandomSeed)?;
Note::from_parts(recipient, value, rho, rseed)
.into_option()
.ok_or(OrchardError::InvalidOutputNote)?
};
let alpha = action.spend.alpha_from_field()?;
let rcv = ValueCommitTrapdoor::from_bytes(
action.rcv.ok_or(OrchardError::MissingValueCommitTrapdoor)?,
)
.into_option()
.ok_or(OrchardError::InvalidValueCommitTrapdoor)?;
Ok(Circuit::from_action_context(spend, output_note, alpha, rcv)
.expect("rho should match nf by construction above")) // TODO: Check if this changes
})
.collect::<Result<Vec<_>, OrchardError>>()?;
let instances = self
.pczt
.orchard
.actions
.iter()
.map(|action| {
let cv_net = ValueCommitment::from_bytes(&action.cv_net)
.into_option()
.ok_or(crate::orchard::Error::InvalidValueCommitment)?;
let nf_old = Nullifier::from_bytes(&action.spend.nullifier)
.into_option()
.ok_or(crate::orchard::Error::InvalidNullifier)?;
let rk = redpallas::VerificationKey::try_from(action.spend.rk)
.map_err(|_| crate::orchard::Error::InvalidRandomizedKey)?;
let cmx = ExtractedNoteCommitment::from_bytes(&action.output.cmx)
.into_option()
.ok_or(crate::orchard::Error::InvalidExtractedNoteCommitment)?;
Ok(Instance::from_parts(
anchor,
cv_net,
nf_old,
rk,
cmx,
flags.spends_enabled(),
flags.outputs_enabled(),
))
})
.collect::<Result<Vec<_>, OrchardError>>()?;
let proof = orchard::Proof::create(pk, &circuits, &instances, OsRng)
.map_err(|_| OrchardError::ProofFailed)?;
self.pczt.orchard.zkproof = Some(proof.as_ref().to_vec());
Ok(())
}
}
/// Errors that can occur while creating Orchard proofs for a PCZT.
#[derive(Debug)]
pub enum OrchardError {
Data(crate::orchard::Error),
InvalidOutputNote,
InvalidOutputRecipient,
InvalidRandomSeed,
InvalidSpendAuthRandomizer,
InvalidValueCommitTrapdoor,
InvalidWitness,
MissingOutputRecipient,
MissingRandomSeed,
MissingSpendAuthRandomizer,
MissingValue,
MissingValueCommitTrapdoor,
MissingWitness,
ProofFailed,
WrongFvkForNote,
}
impl From<crate::orchard::Error> for OrchardError {
fn from(e: crate::orchard::Error) -> Self {
Self::Data(e)
}
}

View File

@ -0,0 +1,104 @@
use rand_core::OsRng;
use sapling::{
prover::{OutputProver, SpendProver},
value::{NoteValue, ValueCommitTrapdoor},
Note, PaymentAddress,
};
impl super::Prover {
pub fn create_sapling_proofs<S, O>(
&mut self,
spend_prover: &S,
output_prover: &O,
) -> Result<(), SaplingError>
where
S: SpendProver,
O: OutputProver,
{
let mut rng = OsRng;
let anchor = jubjub::Base::from_bytes(&self.pczt.sapling.anchor)
.into_option()
.ok_or(crate::sapling::Error::InvalidAnchor)?;
for spend in &mut self.pczt.sapling.spends {
let proof_generation_key = spend.proof_generation_key_from_field()?;
let note = spend.note_from_fields()?;
let alpha = spend.alpha_from_field()?;
let rcv = ValueCommitTrapdoor::from_bytes(
spend.rcv.ok_or(SaplingError::MissingValueCommitTrapdoor)?,
)
.into_option()
.ok_or(SaplingError::InvalidValueCommitTrapdoor)?;
let merkle_path = spend.witness_from_field()?;
let circuit = S::prepare_circuit(
proof_generation_key,
*note.recipient().diversifier(),
*note.rseed(),
note.value(),
alpha,
rcv,
anchor,
merkle_path,
)
.ok_or(SaplingError::InvalidDiversifier)?;
let proof = spend_prover.create_proof(circuit, &mut rng);
spend.zkproof = Some(S::encode_proof(proof));
}
for output in &mut self.pczt.sapling.outputs {
let recipient = PaymentAddress::from_bytes(
output
.recipient
.as_ref()
.ok_or(SaplingError::MissingRecipient)?,
)
.ok_or(SaplingError::InvalidRecipient)?;
let value = NoteValue::from_raw(output.value.ok_or(SaplingError::MissingValue)?);
let rseed =
sapling::Rseed::AfterZip212(output.rseed.ok_or(SaplingError::MissingRandomSeed)?);
let note = Note::from_parts(recipient, value, rseed);
let esk = note.generate_or_derive_esk(&mut rng);
let rcm = note.rcm();
let rcv = ValueCommitTrapdoor::from_bytes(
output.rcv.ok_or(SaplingError::MissingValueCommitTrapdoor)?,
)
.into_option()
.ok_or(SaplingError::InvalidValueCommitTrapdoor)?;
let circuit = O::prepare_circuit(&esk, recipient, rcm, value, rcv);
let proof = output_prover.create_proof(circuit, &mut rng);
output.zkproof = Some(O::encode_proof(proof));
}
Ok(())
}
}
/// Errors that can occur while creating Sapling proofs for a PCZT.
#[derive(Debug)]
pub enum SaplingError {
Data(crate::sapling::Error),
InvalidDiversifier,
InvalidRecipient,
InvalidValueCommitTrapdoor,
MissingRandomSeed,
MissingRecipient,
MissingValue,
MissingValueCommitTrapdoor,
}
impl From<crate::sapling::Error> for SaplingError {
fn from(e: crate::sapling::Error) -> Self {
Self::Data(e)
}
}

View File

@ -12,7 +12,7 @@ use zcash_primitives::{
};
use zcash_protocol::{consensus::BranchId, value::Zatoshis};
use crate::Pczt;
use crate::{IgnoreMissing, Pczt};
use super::tx_extractor::determine_lock_time;
@ -42,8 +42,6 @@ impl Signer {
index: usize,
ask: &sapling::keys::SpendAuthorizingKey,
) -> Result<(), Error> {
// TODO: Check consistency of the input being signed.
let spend = self
.pczt
.sapling
@ -51,6 +49,34 @@ impl Signer {
.get_mut(index)
.ok_or(Error::InvalidIndex)?;
// Check consistency of the input being signed.
let note_from_fields = spend
.note_from_fields()
.ignore_missing()
.map_err(Error::Sapling)?;
if let Some(note) = note_from_fields {
let tx_spend = self
.tx_data
.sapling_bundle()
.expect("index checked above")
.shielded_spends()
.get(index)
.expect("index checked above");
let proof_generation_key = spend
.proof_generation_key_from_field()
.map_err(Error::Sapling)?;
let nk = proof_generation_key.to_viewing_key().nk;
let merkle_path = spend.witness_from_field().map_err(Error::Sapling)?;
if &note.nf(&nk, merkle_path.position().into()) != tx_spend.nullifier() {
return Err(Error::InvalidNullifier);
}
}
let alpha = spend.alpha_from_field().map_err(Error::Sapling)?;
let rsk = ask.randomize(&alpha);
@ -79,8 +105,6 @@ impl Signer {
index: usize,
ask: &orchard::keys::SpendAuthorizingKey,
) -> Result<(), Error> {
// TODO: Check consistency of the input being signed.
let action = self
.pczt
.orchard
@ -88,6 +112,29 @@ impl Signer {
.get_mut(index)
.ok_or(Error::InvalidIndex)?;
// Check consistency of the input being signed.
let note_from_fields = action
.spend
.note_from_fields()
.ignore_missing()
.map_err(Error::Orchard)?;
if let Some(note) = note_from_fields {
let tx_action = self
.tx_data
.orchard_bundle()
.expect("index checked above")
.actions()
.get(index)
.expect("index checked above");
let fvk = action.spend.fvk_from_field().map_err(Error::Orchard)?;
if &note.nullifier(fvk) != tx_action.nullifier() {
return Err(Error::InvalidNullifier);
}
}
let alpha = action.spend.alpha_from_field().map_err(Error::Orchard)?;
let rsk = ask.randomize(&alpha);
@ -233,6 +280,8 @@ pub enum Error {
Global(GlobalError),
IncompatibleLockTimes,
InvalidIndex,
InvalidNoteCommitment,
InvalidNullifier,
Orchard(crate::orchard::Error),
Sapling(crate::sapling::Error),
Transparent(crate::transparent::Error),

View File

@ -1,9 +1,14 @@
use std::cmp::Ordering;
use crate::roles::combiner::merge_optional;
use crate::{roles::combiner::merge_optional, IgnoreMissing};
#[cfg(feature = "sapling")]
use pasta_curves::group::ff::PrimeField;
use {
ff::PrimeField,
sapling::{
keys::SpendValidatingKey, value::NoteValue, MerklePath, Node, Note, PaymentAddress, Rseed,
},
};
const GROTH_PROOF_SIZE: usize = 48 + 96 + 48;
@ -55,6 +60,65 @@ pub(crate) struct Spend {
/// This is set by the Signer.
pub(crate) spend_auth_sig: Option<[u8; 64]>,
/// The [raw encoding] of the Sapling payment address that received the note being spent.
///
/// - This is set by the Constructor.
/// - This is required by the Prover.
///
/// [raw encoding]: https://zips.z.cash/protocol/protocol.pdf#saplingpaymentaddrencoding
pub(crate) recipient: Option<[u8; 43]>,
/// The value of the input being spent.
///
/// This may be used by Signers to verify that the value matches `cv`, and to confirm
/// the values and change involved in the transaction.
///
/// This exposes the input value to all participants. For Signers who don't need this
/// information, or after signatures have been applied, this can be redacted.
pub(crate) value: Option<u64>,
/// The note commitment randomness.
///
/// - This is set by the Constructor. It MUST NOT be set if the note has an `rseed`
/// (i.e. was created after [ZIP 212] activation).
/// - The Prover requires either this or `rseed`.
///
/// [ZIP 212]: https://zips.z.cash/zip-0212
pub(crate) rcm: Option<[u8; 32]>,
/// The seed randomness for the note being spent.
///
/// - This is set by the Constructor. It MUST NOT be set if the note has no `rseed`
/// (i.e. was created before [ZIP 212] activation).
/// - The Prover requires either this or `rcm`.
///
/// [ZIP 212]: https://zips.z.cash/zip-0212
pub(crate) rseed: Option<[u8; 32]>,
/// The value commitment randomness.
///
/// - This is set by the Constructor.
/// - The IO Finalizer compresses it into `bsk`.
/// - This is required by the Prover.
/// - This may be used by Signers to verify that the value correctly matches `cv`.
///
/// This opens `cv` for all participants. For Signers who don't need this information,
/// or after proofs / signatures have been applied, this can be redacted.
pub(crate) rcv: Option<[u8; 32]>,
/// The proof generation key `(ak, nsk)` corresponding to the recipient that received
/// the note being spent.
///
/// - This is set by the Updater.
/// - This is required by the Prover.
pub(crate) proof_generation_key: Option<([u8; 32], [u8; 32])>,
/// A witness from the note to the bundle's anchor.
///
/// - This is set by the Updater.
/// - This is required by the Prover.
pub(crate) witness: Option<(u32, [[u8; 32]; 32])>,
/// The spend authorization randomizer.
///
/// - This is chosen by the Constructor.
@ -101,6 +165,40 @@ pub(crate) struct Output {
///
/// This is set by the Prover.
pub(crate) zkproof: Option<[u8; GROTH_PROOF_SIZE]>,
/// The [raw encoding] of the Sapling payment address that will receive the output.
///
/// - This is set by the Constructor.
/// - This is required by the Prover.
///
/// [raw encoding]: https://zips.z.cash/protocol/protocol.pdf#saplingpaymentaddrencoding
pub(crate) recipient: Option<[u8; 43]>,
/// The value of the output.
///
/// This may be used by Signers to verify that the value matches `cv`, and to confirm
/// the values and change involved in the transaction.
///
/// This exposes the output value to all participants. For Signers who don't need this
/// information, or after signatures have been applied, this can be redacted.
pub(crate) value: Option<u64>,
/// The seed randomness for the output.
///
/// - This is set by the Constructor.
/// - This is required by the Prover, instead of disclosing `shared_secret` to them.
pub(crate) rseed: Option<[u8; 32]>,
/// The value commitment randomness.
///
/// - This is set by the Constructor.
/// - The IO Finalizer compresses it into `bsk`.
/// - This is required by the Prover.
/// - This may be used by Signers to verify that the value correctly matches `cv`.
///
/// This opens `cv` for all participants. For Signers who don't need this information,
/// or after proofs / signatures have been applied, this can be redacted.
pub(crate) rcv: Option<[u8; 32]>,
}
impl Bundle {
@ -180,6 +278,13 @@ impl Bundle {
rk,
zkproof,
spend_auth_sig,
recipient,
value,
rcm,
rseed,
rcv,
proof_generation_key,
witness,
alpha,
dummy_ask,
} = rhs;
@ -190,6 +295,13 @@ impl Bundle {
if !(merge_optional(&mut lhs.zkproof, zkproof)
&& merge_optional(&mut lhs.spend_auth_sig, spend_auth_sig)
&& merge_optional(&mut lhs.recipient, recipient)
&& merge_optional(&mut lhs.value, value)
&& merge_optional(&mut lhs.rcm, rcm)
&& merge_optional(&mut lhs.rseed, rseed)
&& merge_optional(&mut lhs.rcv, rcv)
&& merge_optional(&mut lhs.proof_generation_key, proof_generation_key)
&& merge_optional(&mut lhs.witness, witness)
&& merge_optional(&mut lhs.alpha, alpha)
&& merge_optional(&mut lhs.dummy_ask, dummy_ask))
{
@ -206,6 +318,10 @@ impl Bundle {
enc_ciphertext,
out_ciphertext,
zkproof,
recipient,
value,
rseed,
rcv,
} = rhs;
if lhs.cv != cv
@ -217,7 +333,12 @@ impl Bundle {
return None;
}
if !merge_optional(&mut lhs.zkproof, zkproof) {
if !(merge_optional(&mut lhs.zkproof, zkproof)
&& merge_optional(&mut lhs.recipient, recipient)
&& merge_optional(&mut lhs.value, value)
&& merge_optional(&mut lhs.rseed, rseed)
&& merge_optional(&mut lhs.rcv, rcv))
{
return None;
}
}
@ -329,6 +450,59 @@ impl Bundle {
#[cfg(feature = "sapling")]
impl Spend {
/// Parses a [`Note`] from the explicit fields of this spend.
pub(crate) fn note_from_fields(&self) -> Result<Note, Error> {
// We want to parse all fields that are present for validity, before raising any
// errors about missing fields.
let recipient = self
.recipient
.as_ref()
.map(|r| PaymentAddress::from_bytes(r).ok_or(Error::InvalidRecipient))
.transpose()?;
let value = self.value.map(NoteValue::from_raw);
let rseed = self.rseed.map(Rseed::AfterZip212);
Ok(Note::from_parts(
recipient.ok_or(Error::MissingRecipient)?,
value.ok_or(Error::MissingValue)?,
rseed.ok_or(Error::MissingRandomSeed)?,
))
}
pub(crate) fn proof_generation_key_from_field(
&self,
) -> Result<sapling::ProofGenerationKey, Error> {
let (ak, nsk) = self
.proof_generation_key
.ok_or(Error::MissingProofGenerationKey)?;
Ok(sapling::ProofGenerationKey {
ak: SpendValidatingKey::temporary_zcash_from_bytes(&ak)
.ok_or(Error::InvalidProofGenerationKey)?,
nsk: jubjub::Scalar::from_repr(nsk)
.into_option()
.ok_or(Error::InvalidProofGenerationKey)?,
})
}
pub(crate) fn witness_from_field(&self) -> Result<MerklePath, Error> {
let (position, auth_path_bytes) = self.witness.ok_or(Error::MissingWitness)?;
let path_elems = auth_path_bytes
.into_iter()
.map(|hash| {
Node::from_bytes(hash)
.into_option()
.ok_or(Error::InvalidWitness)
})
.collect::<Result<Vec<_>, _>>()?;
MerklePath::from_parts(path_elems, u64::from(position).into())
.map_err(|()| Error::InvalidWitness)
}
pub(crate) fn alpha_from_field(&self) -> Result<jubjub::Scalar, Error> {
jubjub::Scalar::from_repr(self.alpha.ok_or(Error::MissingSpendAuthRandomizer)?)
.into_option()
@ -343,9 +517,35 @@ pub enum Error {
InvalidEncCiphertext,
InvalidExtractedNoteCommitment,
InvalidOutCiphertext,
InvalidProofGenerationKey,
InvalidRandomizedKey,
InvalidRecipient,
InvalidSpendAuthRandomizer,
InvalidValueBalance(zcash_protocol::value::BalanceError),
InvalidValueCommitment,
InvalidWitness,
MissingProofGenerationKey,
MissingRandomSeed,
MissingRecipient,
MissingSpendAuthRandomizer,
MissingValue,
MissingWitness,
}
#[cfg(feature = "sapling")]
impl<V> IgnoreMissing for Result<V, Error> {
type Value = V;
type Error = Error;
fn ignore_missing(self) -> Result<Option<Self::Value>, Self::Error> {
self.map(Some).or_else(|e| match e {
Error::MissingProofGenerationKey
| Error::MissingRandomSeed
| Error::MissingRecipient
| Error::MissingSpendAuthRandomizer
| Error::MissingValue
| Error::MissingWitness => Ok(None),
_ => Err(e),
})
}
}

View File

@ -6,6 +6,7 @@ use std::path::Path;
use sapling::{
bundle::GrothProofBytes,
keys::EphemeralSecretKey,
prover::{OutputProver, SpendProver},
value::{NoteValue, ValueCommitTrapdoor},
Diversifier, MerklePath, PaymentAddress, ProofGenerationKey, Rseed,
@ -176,7 +177,7 @@ impl OutputProver for LocalTxProver {
type Proof = Proof<Bls12>;
fn prepare_circuit(
esk: jubjub::Fr,
esk: &EphemeralSecretKey,
payment_address: PaymentAddress,
rcm: jubjub::Fr,
value: NoteValue,