From 2bfeef9430c6ab5c0b188c574325dff09e61f066 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Mon, 2 Oct 2023 13:33:33 +0000 Subject: [PATCH 1/4] zcash_proofs: Remove immediate verification of created Spend proofs It can be useful to verify proofs after they have been created, but we were only doing this for spend proofs, not output proofs. It also duplicated code from the verifier logic. Once the prover and verifier have been refactored, it will be easier to just call the verifier immediately after the prover. --- zcash_proofs/CHANGELOG.md | 6 ++++ zcash_proofs/src/prover.rs | 4 ++- zcash_proofs/src/sapling/prover.rs | 45 ++---------------------------- 3 files changed, 11 insertions(+), 44 deletions(-) diff --git a/zcash_proofs/CHANGELOG.md b/zcash_proofs/CHANGELOG.md index 33c36b7be..a35ba5669 100644 --- a/zcash_proofs/CHANGELOG.md +++ b/zcash_proofs/CHANGELOG.md @@ -6,6 +6,12 @@ and this library adheres to Rust's notion of [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Changed +- `zcash_proofs::sapling::prover`: + - The `verifying_key` argument `SaplingProvingContext::spend_proof` has been + removed. Callers should instead use `SaplingVerifyingContext` to verify + proofs after they have been created. + ### Removed - `zcash_proofs::circuit::sapling` (moved to `zcash_primitives::sapling::circuit`). - `zcash_proofs::circuit::{ecc, pedersen_hash}` diff --git a/zcash_proofs/src/prover.rs b/zcash_proofs/src/prover.rs index 63c1db79f..40b1240fb 100644 --- a/zcash_proofs/src/prover.rs +++ b/zcash_proofs/src/prover.rs @@ -22,6 +22,9 @@ use crate::{default_params_folder, SAPLING_OUTPUT_NAME, SAPLING_SPEND_NAME}; /// locally-accessible paths. pub struct LocalTxProver { spend_params: Parameters, + // TODO: Either re-introduce verification-after-proving (once the verifier is + // refactored), or remove this. + #[allow(unused)] spend_vk: PreparedVerifyingKey, output_params: Parameters, } @@ -164,7 +167,6 @@ impl TxProver for LocalTxProver { anchor, merkle_path, &self.spend_params, - &self.spend_vk, )?; let mut zkproof = [0u8; GROTH_PROOF_SIZE]; diff --git a/zcash_proofs/src/sapling/prover.rs b/zcash_proofs/src/sapling/prover.rs index 05767f348..9c31efc9f 100644 --- a/zcash_proofs/src/sapling/prover.rs +++ b/zcash_proofs/src/sapling/prover.rs @@ -1,9 +1,6 @@ -use bellman::{ - gadgets::multipack, - groth16::{create_random_proof, verify_proof, Parameters, PreparedVerifyingKey, Proof}, -}; +use bellman::groth16::{create_random_proof, Parameters, Proof}; use bls12_381::Bls12; -use group::{Curve, GroupEncoding}; +use group::GroupEncoding; use rand_core::OsRng; use zcash_primitives::{ sapling::{ @@ -52,7 +49,6 @@ impl SaplingProvingContext { anchor: bls12_381::Scalar, merkle_path: MerklePath, proving_key: &Parameters, - verifying_key: &PreparedVerifyingKey, ) -> Result<(Proof, ValueCommitment, PublicKey), ()> { // Initialize secure RNG let mut rng = OsRng; @@ -82,12 +78,6 @@ impl SaplingProvingContext { // Let's compute the nullifier while we have the position let note = Note::from_parts(payment_address, NoteValue::from_raw(value), rseed); - let nullifier = note.nf( - &viewing_key.nk, - u64::try_from(merkle_path.position()) - .expect("Sapling note commitment tree position must fit into a u64"), - ); - // We now have the full witness for our circuit let pos: u64 = merkle_path.position().into(); let instance = Spend { @@ -109,37 +99,6 @@ impl SaplingProvingContext { let proof = create_random_proof(instance, proving_key, &mut rng).expect("proving should not fail"); - // Try to verify the proof: - // Construct public input for circuit - let mut public_input = [bls12_381::Scalar::zero(); 7]; - { - let affine = rk.0.to_affine(); - let (u, v) = (affine.get_u(), affine.get_v()); - public_input[0] = u; - public_input[1] = v; - } - { - let affine = value_commitment.as_inner().to_affine(); - let (u, v) = (affine.get_u(), affine.get_v()); - public_input[2] = u; - public_input[3] = v; - } - public_input[4] = anchor; - - // Add the nullifier through multiscalar packing - { - let nullifier = multipack::bytes_to_bits_le(&nullifier.0); - let nullifier = multipack::compute_multipacking(&nullifier); - - assert_eq!(nullifier.len(), 2); - - public_input[5] = nullifier[0]; - public_input[6] = nullifier[1]; - } - - // Verify the proof - verify_proof(verifying_key, &proof, &public_input[..]).map_err(|_| ())?; - // Accumulate the value commitment in the context self.cv_sum += &value_commitment; From ea0fed39eb91a3727c47e12cc8cab1bc812a2cda Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Mon, 2 Oct 2023 14:03:09 +0000 Subject: [PATCH 2/4] zcash_proofs: Introduce newtype wrappers for Sapling parameters --- zcash_proofs/CHANGELOG.md | 9 +++++++++ zcash_proofs/src/lib.rs | 14 ++++++++++---- zcash_proofs/src/prover.rs | 11 +++++++---- zcash_proofs/src/sapling/prover.rs | 16 +++++++++------- 4 files changed, 35 insertions(+), 15 deletions(-) diff --git a/zcash_proofs/CHANGELOG.md b/zcash_proofs/CHANGELOG.md index a35ba5669..f544343b6 100644 --- a/zcash_proofs/CHANGELOG.md +++ b/zcash_proofs/CHANGELOG.md @@ -6,7 +6,16 @@ and this library adheres to Rust's notion of [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added +- `zcash_proofs::{SpendParameters, OutputParameters}` + ### Changed +- The new `SpendParameters` and `OutputParameters` types are used in the + following places: + - `zcash_proofs::ZcashParameters::{spend_params, output_params}` fields. + - `zcash_proofs::sapling::prover`: + - `SaplingProvingContext::{spend_proof, output_proof}` (the `proving_key` + arguments). - `zcash_proofs::sapling::prover`: - The `verifying_key` argument `SaplingProvingContext::spend_proof` has been removed. Callers should instead use `SaplingVerifyingContext` to verify diff --git a/zcash_proofs/src/lib.rs b/zcash_proofs/src/lib.rs index 992b3a737..bdd452bd4 100644 --- a/zcash_proofs/src/lib.rs +++ b/zcash_proofs/src/lib.rs @@ -283,11 +283,17 @@ fn stream_params_downloads_to_disk( Ok(()) } +/// The parameters for the Sapling Spend circuit. +pub struct SpendParameters(Parameters); + +/// The parameters for the Sapling Output circuit. +pub struct OutputParameters(Parameters); + /// Zcash Sprout and Sapling groth16 circuit parameters. pub struct ZcashParameters { - pub spend_params: Parameters, + pub spend_params: SpendParameters, pub spend_vk: PreparedVerifyingKey, - pub output_params: Parameters, + pub output_params: OutputParameters, pub output_vk: PreparedVerifyingKey, pub sprout_vk: Option>, } @@ -429,9 +435,9 @@ pub fn parse_parameters( let sprout_vk = sprout_vk.map(|vk| prepare_verifying_key(&vk)); ZcashParameters { - spend_params, + spend_params: SpendParameters(spend_params), spend_vk, - output_params, + output_params: OutputParameters(output_params), output_vk, sprout_vk, } diff --git a/zcash_proofs/src/prover.rs b/zcash_proofs/src/prover.rs index 40b1240fb..8bbd1abff 100644 --- a/zcash_proofs/src/prover.rs +++ b/zcash_proofs/src/prover.rs @@ -1,6 +1,6 @@ //! Abstractions over the proving system and parameters for ease of use. -use bellman::groth16::{Parameters, PreparedVerifyingKey}; +use bellman::groth16::PreparedVerifyingKey; use bls12_381::Bls12; use std::path::Path; use zcash_primitives::{ @@ -13,7 +13,10 @@ use zcash_primitives::{ transaction::components::{Amount, GROTH_PROOF_SIZE}, }; -use crate::{load_parameters, parse_parameters, sapling::SaplingProvingContext}; +use crate::{ + load_parameters, parse_parameters, sapling::SaplingProvingContext, OutputParameters, + SpendParameters, +}; #[cfg(feature = "local-prover")] use crate::{default_params_folder, SAPLING_OUTPUT_NAME, SAPLING_SPEND_NAME}; @@ -21,12 +24,12 @@ use crate::{default_params_folder, SAPLING_OUTPUT_NAME, SAPLING_SPEND_NAME}; /// An implementation of [`TxProver`] using Sapling Spend and Output parameters from /// locally-accessible paths. pub struct LocalTxProver { - spend_params: Parameters, + spend_params: SpendParameters, // TODO: Either re-introduce verification-after-proving (once the verifier is // refactored), or remove this. #[allow(unused)] spend_vk: PreparedVerifyingKey, - output_params: Parameters, + output_params: OutputParameters, } impl LocalTxProver { diff --git a/zcash_proofs/src/sapling/prover.rs b/zcash_proofs/src/sapling/prover.rs index 9c31efc9f..435290108 100644 --- a/zcash_proofs/src/sapling/prover.rs +++ b/zcash_proofs/src/sapling/prover.rs @@ -1,4 +1,4 @@ -use bellman::groth16::{create_random_proof, Parameters, Proof}; +use bellman::groth16::{create_random_proof, Proof}; use bls12_381::Bls12; use group::GroupEncoding; use rand_core::OsRng; @@ -13,6 +13,8 @@ use zcash_primitives::{ transaction::components::Amount, }; +use crate::{OutputParameters, SpendParameters}; + /// A context object for creating the Sapling components of a Zcash transaction. pub struct SaplingProvingContext { bsk: TrapdoorSum, @@ -48,7 +50,7 @@ impl SaplingProvingContext { value: u64, anchor: bls12_381::Scalar, merkle_path: MerklePath, - proving_key: &Parameters, + proving_key: &SpendParameters, ) -> Result<(Proof, ValueCommitment, PublicKey), ()> { // Initialize secure RNG let mut rng = OsRng; @@ -96,8 +98,8 @@ impl SaplingProvingContext { }; // Create proof - let proof = - create_random_proof(instance, proving_key, &mut rng).expect("proving should not fail"); + let proof = create_random_proof(instance, &proving_key.0, &mut rng) + .expect("proving should not fail"); // Accumulate the value commitment in the context self.cv_sum += &value_commitment; @@ -114,7 +116,7 @@ impl SaplingProvingContext { payment_address: PaymentAddress, rcm: jubjub::Fr, value: u64, - proving_key: &Parameters, + proving_key: &OutputParameters, ) -> (Proof, ValueCommitment) { // Initialize secure RNG let mut rng = OsRng; @@ -143,8 +145,8 @@ impl SaplingProvingContext { }; // Create proof - let proof = - create_random_proof(instance, proving_key, &mut rng).expect("proving should not fail"); + let proof = create_random_proof(instance, &proving_key.0, &mut rng) + .expect("proving should not fail"); // Accumulate the value commitment in the context. We do this to check internal consistency. self.cv_sum -= &value_commitment; // Outputs subtract from the total. From 290bfa8b3143dba6054fa79d3f5b9bbffcecdec6 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Mon, 2 Oct 2023 14:16:43 +0000 Subject: [PATCH 3/4] zcash_primitives: Introduce a `SpendProver` trait --- zcash_primitives/CHANGELOG.md | 1 + zcash_primitives/src/sapling/prover.rs | 35 ++++++++- zcash_proofs/src/sapling/prover.rs | 99 +++++++++++++++++--------- 3 files changed, 101 insertions(+), 34 deletions(-) diff --git a/zcash_primitives/CHANGELOG.md b/zcash_primitives/CHANGELOG.md index da7ba6f92..756d67ef3 100644 --- a/zcash_primitives/CHANGELOG.md +++ b/zcash_primitives/CHANGELOG.md @@ -11,6 +11,7 @@ and this library adheres to Rust's notion of - `zcash_primitives::sapling`: - `circuit` module (moved from `zcash_proofs::circuit::sapling`). - `constants` module. + - `prover::SpendProver` ### Removed - `zcash_primitives::constants`: diff --git a/zcash_primitives/src/sapling/prover.rs b/zcash_primitives/src/sapling/prover.rs index 3650d5458..dcf0d6505 100644 --- a/zcash_primitives/src/sapling/prover.rs +++ b/zcash_primitives/src/sapling/prover.rs @@ -1,16 +1,49 @@ //! Abstractions over the proving system and parameters. +use rand_core::RngCore; + use crate::{ sapling::{ self, redjubjub::{PublicKey, Signature}, - value::ValueCommitment, + value::{NoteValue, ValueCommitTrapdoor, ValueCommitment}, + MerklePath, }, transaction::components::{Amount, GROTH_PROOF_SIZE}, }; use super::{Diversifier, PaymentAddress, ProofGenerationKey, Rseed}; +/// Interface for creating Sapling Spend proofs. +pub trait SpendProver { + /// The proof type created by this prover. + type Proof; + + /// Prepares an instance of the Sapling Spend circuit for the given inputs. + /// + /// Returns `None` if `diversifier` is not a valid Sapling diversifier. + #[allow(clippy::too_many_arguments)] + fn prepare_circuit( + proof_generation_key: ProofGenerationKey, + diversifier: Diversifier, + rseed: Rseed, + value: NoteValue, + alpha: jubjub::Fr, + rcv: ValueCommitTrapdoor, + anchor: bls12_381::Scalar, + merkle_path: MerklePath, + ) -> Option; + + /// Create the proof for a Sapling [`SpendDescription`]. + /// + /// [`SpendDescription`]: crate::transaction::components::SpendDescription + fn create_proof( + &self, + circuit: sapling::circuit::Spend, + rng: &mut R, + ) -> Self::Proof; +} + /// Interface for creating zero-knowledge proofs for shielded transactions. pub trait TxProver { /// Type for persisting any necessary context across multiple Sapling proofs. diff --git a/zcash_proofs/src/sapling/prover.rs b/zcash_proofs/src/sapling/prover.rs index 435290108..6398f6119 100644 --- a/zcash_proofs/src/sapling/prover.rs +++ b/zcash_proofs/src/sapling/prover.rs @@ -1,11 +1,12 @@ use bellman::groth16::{create_random_proof, Proof}; use bls12_381::Bls12; use group::GroupEncoding; -use rand_core::OsRng; +use rand_core::{OsRng, RngCore}; use zcash_primitives::{ sapling::{ circuit::{Output, Spend, ValueCommitmentOpening}, constants::{SPENDING_KEY_GENERATOR, VALUE_COMMITMENT_RANDOMNESS_GENERATOR}, + prover::SpendProver, redjubjub::{PublicKey, Signature}, value::{CommitmentSum, NoteValue, TrapdoorSum, ValueCommitTrapdoor, ValueCommitment}, Diversifier, MerklePath, Note, PaymentAddress, ProofGenerationKey, Rseed, @@ -15,6 +16,56 @@ use zcash_primitives::{ use crate::{OutputParameters, SpendParameters}; +impl SpendProver for SpendParameters { + type Proof = Proof; + + fn prepare_circuit( + proof_generation_key: ProofGenerationKey, + diversifier: Diversifier, + rseed: Rseed, + value: NoteValue, + alpha: jubjub::Fr, + rcv: ValueCommitTrapdoor, + anchor: bls12_381::Scalar, + merkle_path: MerklePath, + ) -> Option { + // Construct the value commitment + let value_commitment_opening = ValueCommitmentOpening { + value: value.inner(), + randomness: rcv.inner(), + }; + + // Construct the viewing key + let viewing_key = proof_generation_key.to_viewing_key(); + + // Construct the payment address with the viewing key / diversifier + let payment_address = viewing_key.to_payment_address(diversifier)?; + + let note = Note::from_parts(payment_address, value, rseed); + + // We now have the full witness for our circuit + let pos: u64 = merkle_path.position().into(); + Some(Spend { + value_commitment_opening: Some(value_commitment_opening), + proof_generation_key: Some(proof_generation_key), + payment_address: Some(payment_address), + commitment_randomness: Some(note.rcm()), + ar: Some(alpha), + auth_path: merkle_path + .path_elems() + .iter() + .enumerate() + .map(|(i, node)| Some(((*node).into(), pos >> i & 0x1 == 1))) + .collect(), + anchor: Some(anchor), + }) + } + + fn create_proof(&self, circuit: Spend, rng: &mut R) -> Self::Proof { + create_random_proof(circuit, &self.0, rng).expect("proving should not fail") + } +} + /// A context object for creating the Sapling components of a Zcash transaction. pub struct SaplingProvingContext { bsk: TrapdoorSum, @@ -62,44 +113,26 @@ impl SaplingProvingContext { self.bsk += &rcv; // Construct the value commitment - let value_commitment_opening = ValueCommitmentOpening { - value, - randomness: rcv.inner(), - }; - let value_commitment = ValueCommitment::derive(NoteValue::from_raw(value), rcv); - - // Construct the viewing key - let viewing_key = proof_generation_key.to_viewing_key(); - - // Construct the payment address with the viewing key / diversifier - let payment_address = viewing_key.to_payment_address(diversifier).ok_or(())?; + let value = NoteValue::from_raw(value); + let value_commitment = ValueCommitment::derive(value, rcv.clone()); // This is the result of the re-randomization, we compute it for the caller let rk = PublicKey(proof_generation_key.ak.into()).randomize(ar, SPENDING_KEY_GENERATOR); - // Let's compute the nullifier while we have the position - let note = Note::from_parts(payment_address, NoteValue::from_raw(value), rseed); - - // We now have the full witness for our circuit - let pos: u64 = merkle_path.position().into(); - let instance = Spend { - value_commitment_opening: Some(value_commitment_opening), - proof_generation_key: Some(proof_generation_key), - payment_address: Some(payment_address), - commitment_randomness: Some(note.rcm()), - ar: Some(ar), - auth_path: merkle_path - .path_elems() - .iter() - .enumerate() - .map(|(i, node)| Some(((*node).into(), pos >> i & 0x1 == 1))) - .collect(), - anchor: Some(anchor), - }; + let instance = SpendParameters::prepare_circuit( + proof_generation_key, + diversifier, + rseed, + value, + ar, + rcv, + anchor, + merkle_path, + ) + .ok_or(())?; // Create proof - let proof = create_random_proof(instance, &proving_key.0, &mut rng) - .expect("proving should not fail"); + let proof = proving_key.create_proof(instance, &mut rng); // Accumulate the value commitment in the context self.cv_sum += &value_commitment; From 0d46fe72cc9e81154e78f41b6936f58a5fd6ccd2 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Mon, 2 Oct 2023 14:29:07 +0000 Subject: [PATCH 4/4] zcash_primitives: Introduce an `OutputProver` trait --- zcash_primitives/CHANGELOG.md | 2 +- zcash_primitives/src/sapling/prover.rs | 26 ++++++++++++++ zcash_proofs/src/sapling/prover.rs | 49 ++++++++++++++++++-------- 3 files changed, 62 insertions(+), 15 deletions(-) diff --git a/zcash_primitives/CHANGELOG.md b/zcash_primitives/CHANGELOG.md index 756d67ef3..5db0809c4 100644 --- a/zcash_primitives/CHANGELOG.md +++ b/zcash_primitives/CHANGELOG.md @@ -11,7 +11,7 @@ and this library adheres to Rust's notion of - `zcash_primitives::sapling`: - `circuit` module (moved from `zcash_proofs::circuit::sapling`). - `constants` module. - - `prover::SpendProver` + - `prover::{SpendProver, OutputProver}` ### Removed - `zcash_primitives::constants`: diff --git a/zcash_primitives/src/sapling/prover.rs b/zcash_primitives/src/sapling/prover.rs index dcf0d6505..4f1d32ebe 100644 --- a/zcash_primitives/src/sapling/prover.rs +++ b/zcash_primitives/src/sapling/prover.rs @@ -44,6 +44,32 @@ pub trait SpendProver { ) -> Self::Proof; } +/// Interface for creating Sapling Output proofs. +pub trait OutputProver { + /// The proof type created by this prover. + type Proof; + + /// Prepares an instance of the Sapling Output circuit for the given inputs. + /// + /// Returns `None` if `diversifier` is not a valid Sapling diversifier. + fn prepare_circuit( + esk: jubjub::Fr, + payment_address: PaymentAddress, + rcm: jubjub::Fr, + value: NoteValue, + rcv: ValueCommitTrapdoor, + ) -> sapling::circuit::Output; + + /// Create the proof for a Sapling [`OutputDescription`]. + /// + /// [`OutputDescription`]: crate::transaction::components::OutputDescription + fn create_proof( + &self, + circuit: sapling::circuit::Output, + rng: &mut R, + ) -> Self::Proof; +} + /// Interface for creating zero-knowledge proofs for shielded transactions. pub trait TxProver { /// Type for persisting any necessary context across multiple Sapling proofs. diff --git a/zcash_proofs/src/sapling/prover.rs b/zcash_proofs/src/sapling/prover.rs index 6398f6119..faac73e99 100644 --- a/zcash_proofs/src/sapling/prover.rs +++ b/zcash_proofs/src/sapling/prover.rs @@ -6,7 +6,7 @@ use zcash_primitives::{ sapling::{ circuit::{Output, Spend, ValueCommitmentOpening}, constants::{SPENDING_KEY_GENERATOR, VALUE_COMMITMENT_RANDOMNESS_GENERATOR}, - prover::SpendProver, + prover::{OutputProver, SpendProver}, redjubjub::{PublicKey, Signature}, value::{CommitmentSum, NoteValue, TrapdoorSum, ValueCommitTrapdoor, ValueCommitment}, Diversifier, MerklePath, Note, PaymentAddress, ProofGenerationKey, Rseed, @@ -66,6 +66,36 @@ impl SpendProver for SpendParameters { } } +impl OutputProver for OutputParameters { + type Proof = Proof; + + fn prepare_circuit( + esk: jubjub::Fr, + payment_address: PaymentAddress, + rcm: jubjub::Fr, + value: NoteValue, + rcv: ValueCommitTrapdoor, + ) -> Output { + // Construct the value commitment for the proof instance + let value_commitment_opening = ValueCommitmentOpening { + value: value.inner(), + randomness: rcv.inner(), + }; + + // We now have a full witness for the output proof. + Output { + value_commitment_opening: Some(value_commitment_opening), + payment_address: Some(payment_address), + commitment_randomness: Some(rcm), + esk: Some(esk), + } + } + + fn create_proof(&self, circuit: Output, rng: &mut R) -> Self::Proof { + create_random_proof(circuit, &self.0, rng).expect("proving should not fail") + } +} + /// A context object for creating the Sapling components of a Zcash transaction. pub struct SaplingProvingContext { bsk: TrapdoorSum, @@ -163,23 +193,14 @@ impl SaplingProvingContext { self.bsk -= &rcv; // Outputs subtract from the total. // Construct the value commitment for the proof instance - let value_commitment_opening = ValueCommitmentOpening { - value, - randomness: rcv.inner(), - }; - let value_commitment = ValueCommitment::derive(NoteValue::from_raw(value), rcv); + let value = NoteValue::from_raw(value); + let value_commitment = ValueCommitment::derive(value, rcv.clone()); // We now have a full witness for the output proof. - let instance = Output { - value_commitment_opening: Some(value_commitment_opening), - payment_address: Some(payment_address), - commitment_randomness: Some(rcm), - esk: Some(esk), - }; + let instance = OutputParameters::prepare_circuit(esk, payment_address, rcm, value, rcv); // Create proof - let proof = create_random_proof(instance, &proving_key.0, &mut rng) - .expect("proving should not fail"); + let proof = proving_key.create_proof(instance, &mut rng); // Accumulate the value commitment in the context. We do this to check internal consistency. self.cv_sum -= &value_commitment; // Outputs subtract from the total.