diff --git a/zcash_primitives/CHANGELOG.md b/zcash_primitives/CHANGELOG.md index da7ba6f92..5db0809c4 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, OutputProver}` ### Removed - `zcash_primitives::constants`: diff --git a/zcash_primitives/src/sapling/prover.rs b/zcash_primitives/src/sapling/prover.rs index 3650d5458..4f1d32ebe 100644 --- a/zcash_primitives/src/sapling/prover.rs +++ b/zcash_primitives/src/sapling/prover.rs @@ -1,16 +1,75 @@ //! 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 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/CHANGELOG.md b/zcash_proofs/CHANGELOG.md index 33c36b7be..f544343b6 100644 --- a/zcash_proofs/CHANGELOG.md +++ b/zcash_proofs/CHANGELOG.md @@ -6,6 +6,21 @@ 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 + 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/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 63c1db79f..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,9 +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 { @@ -164,7 +170,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..faac73e99 100644 --- a/zcash_proofs/src/sapling/prover.rs +++ b/zcash_proofs/src/sapling/prover.rs @@ -1,14 +1,12 @@ -use bellman::{ - gadgets::multipack, - groth16::{create_random_proof, verify_proof, Parameters, PreparedVerifyingKey, Proof}, -}; +use bellman::groth16::{create_random_proof, Proof}; use bls12_381::Bls12; -use group::{Curve, GroupEncoding}; -use rand_core::OsRng; +use group::GroupEncoding; +use rand_core::{OsRng, RngCore}; use zcash_primitives::{ sapling::{ circuit::{Output, Spend, ValueCommitmentOpening}, constants::{SPENDING_KEY_GENERATOR, VALUE_COMMITMENT_RANDOMNESS_GENERATOR}, + prover::{OutputProver, SpendProver}, redjubjub::{PublicKey, Signature}, value::{CommitmentSum, NoteValue, TrapdoorSum, ValueCommitTrapdoor, ValueCommitment}, Diversifier, MerklePath, Note, PaymentAddress, ProofGenerationKey, Rseed, @@ -16,6 +14,88 @@ use zcash_primitives::{ transaction::components::Amount, }; +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") + } +} + +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, @@ -51,8 +131,7 @@ impl SaplingProvingContext { value: u64, anchor: bls12_381::Scalar, merkle_path: MerklePath, - proving_key: &Parameters, - verifying_key: &PreparedVerifyingKey, + proving_key: &SpendParameters, ) -> Result<(Proof, ValueCommitment, PublicKey), ()> { // Initialize secure RNG let mut rng = OsRng; @@ -64,81 +143,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); - - 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 { - 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, &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(|_| ())?; + let proof = proving_key.create_proof(instance, &mut rng); // Accumulate the value commitment in the context self.cv_sum += &value_commitment; @@ -155,7 +179,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; @@ -169,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, &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.