From fe1795069027dfe130c2165ffc55e77bfc3b398e Mon Sep 17 00:00:00 2001 From: Joey Kraut Date: Fri, 5 Jan 2024 10:57:04 -0800 Subject: [PATCH] plonk: proof-system: prover: Emit `LinkingHint` with proof The linking hint is used by the proof linking protocol to generate the proof-link via the a(x) polynomials from the original r1cs protocol --- plonk/src/proof_system/proof_linking.rs | 87 +++++++++---------- plonk/src/proof_system/snark.rs | 43 ++++++++- plonk/src/proof_system/structs.rs | 13 +++ relation/src/constraint_system.rs | 9 ++ relation/src/proof_linking/arithmetization.rs | 8 +- 5 files changed, 110 insertions(+), 50 deletions(-) diff --git a/plonk/src/proof_system/proof_linking.rs b/plonk/src/proof_system/proof_linking.rs index 95cf77237..c8f7d0211 100644 --- a/plonk/src/proof_system/proof_linking.rs +++ b/plonk/src/proof_system/proof_linking.rs @@ -16,17 +16,14 @@ use jf_primitives::{ rescue::RescueParameter, }; use mpc_relation::{ - errors::CircuitError, gadgets::ecc::SWToTEConParam, proof_linking::{GroupLayout, PROOF_LINK_WIRE_IDX}, - traits::LinkGroup, - PlonkCircuit, }; use crate::{errors::PlonkError, transcript::PlonkTranscript}; use super::{ - structs::{Proof, ProvingKey, VerifyingKey}, + structs::{LinkingHint, Proof, ProvingKey, VerifyingKey}, PlonkKzgSnark, }; @@ -51,37 +48,29 @@ where { /// Link two proofs on a given domain pub fn link_proofs>( - lhs_circuit: &PlonkCircuit, - rhs_circuit: &PlonkCircuit, - lhs_proof: &Proof, - rhs_proof: &Proof, + lhs_link_hint: &LinkingHint, + rhs_link_hint: &LinkingHint, + group_layout: &GroupLayout, proving_key: &ProvingKey, - link_group: &LinkGroup, ) -> Result, PlonkError> { - // Get the placement of the group in the circuit's layout - let circuit_layout = lhs_circuit.gen_circuit_layout().map_err(PlonkError::CircuitError)?; - let group_layout = circuit_layout.group_layouts.get(&link_group.id).ok_or_else(|| { - PlonkError::CircuitError(CircuitError::LinkGroupNotFound(format!( - "link group {} not found in layout", - link_group.id - ))) - })?; - // Compute the wiring polynomials that encode the proof-linked values - let a1 = lhs_circuit.get_proof_linking_wire_poly(); - let a2 = rhs_circuit.get_proof_linking_wire_poly(); + let a1 = &lhs_link_hint.linking_wire_poly; + let a2 = &rhs_link_hint.linking_wire_poly; // Compute the quotient then commit to it - let quotient = Self::compute_linking_quotient(&a1, &a2, group_layout)?; + let quotient = Self::compute_linking_quotient(a1, a2, group_layout)?; let quotient_commitment = UnivariateKzgPCS::commit(&proving_key.commit_key, "ient) .map_err(PlonkError::PCSError)?; // Squeeze a challenge for the opening - let opening_challenge = - Self::compute_quotient_challenge::(lhs_proof, rhs_proof, "ient_commitment)?; + let opening_challenge = Self::compute_quotient_challenge::( + &lhs_link_hint.linking_wire_comm, + &rhs_link_hint.linking_wire_comm, + "ient_commitment, + )?; let opening_proof = Self::compute_identity_opening( - &a1, - &a2, + a1, + a2, "ient, opening_challenge, group_layout, @@ -164,15 +153,14 @@ where /// the proofs _after_ they have committed to the wiring polynomials /// that are being linked fn compute_quotient_challenge>( - lhs_proof: &Proof, - rhs_proof: &Proof, + a1_comm: &Commitment, + a2_comm: &Commitment, quotient_comm: &Commitment, ) -> Result { let mut transcript = T::new(b"PlonkLinkingProof"); - let a_comm1 = lhs_proof.wires_poly_comms[PROOF_LINK_WIRE_IDX]; - let a_comm2 = rhs_proof.wires_poly_comms[PROOF_LINK_WIRE_IDX]; - transcript.append_commitments(b"linking_wire_comms", &[a_comm1, a_comm2])?; + // We encode the proof linking gates in the first wire polynomial + transcript.append_commitments(b"linking_wire_comms", &[*a1_comm, *a2_comm])?; transcript.append_commitment(b"quotient_comm", quotient_comm)?; transcript.get_and_append_challenge::(b"eta") @@ -215,6 +203,11 @@ where P: SWCurveConfig, { /// Verify a linking proof + /// + /// The verifier does not have access to the link hint of the proofs -- this + /// exposes wiring information -- so it is simpler to pass a proof + /// reference directly (which the verifier will have). This avoids the need + /// to index into the commitments at the callsite pub fn verify_link_proof>( r1cs_proof1: &Proof, r1cs_proof2: &Proof, @@ -224,11 +217,11 @@ where ) -> Result<(), PlonkError> { // Squeeze a challenge for the opening let quotient_comm = &link_proof.quotient_commitment; - let eta = Self::compute_quotient_challenge::(r1cs_proof1, r1cs_proof2, quotient_comm)?; - - // Compute a commitment to the proof-linking identity polynomial let a1_comm = &r1cs_proof1.wires_poly_comms[PROOF_LINK_WIRE_IDX]; let a2_comm = &r1cs_proof2.wires_poly_comms[PROOF_LINK_WIRE_IDX]; + let eta = Self::compute_quotient_challenge::(a1_comm, a2_comm, quotient_comm)?; + + // Compute a commitment to the proof-linking identity polynomial let identity_comm = Self::compute_identity_commitment(a1_comm, a2_comm, quotient_comm, eta, layout); @@ -279,7 +272,7 @@ mod test { use crate::{ proof_system::{ - structs::{Proof, ProvingKey, VerifyingKey}, + structs::{LinkingHint, Proof, ProvingKey, VerifyingKey}, PlonkKzgSnark, UniversalSNARK, }, transcript::SolidityTranscript, @@ -322,13 +315,14 @@ mod test { (circuit, group) } - /// Generate a proof for a circuit - fn gen_test_proof(circuit: &PlonkCircuit) -> Proof { + /// Generate a proof and link hint for the circuit by proving its r1cs + /// relation + fn gen_test_proof(circuit: &PlonkCircuit) -> (Proof, LinkingHint) { let mut rng = thread_rng(); let (pk, _) = gen_keys(circuit); - PlonkKzgSnark::::prove::<_, _, SolidityTranscript>( - &mut rng, circuit, &pk, None, // extra_init_msg + PlonkKzgSnark::::prove_with_link_hint::<_, _, SolidityTranscript>( + &mut rng, circuit, &pk, ) .unwrap() } @@ -353,22 +347,23 @@ mod test { let witness = (0..N).map(|_| u64::rand(&mut rng)).collect_vec(); // Generate the two circuits - let (lhs_circuit, group) = gen_test_circuit(&witness); + let (mut lhs_circuit, group) = gen_test_circuit(&witness); let (rhs_circuit, _) = gen_test_circuit(&witness); + let circuit_layout = lhs_circuit.gen_circuit_layout().unwrap(); + let group_layout = circuit_layout.group_layouts.get(&group.id).unwrap(); + // Prove each circuit let (pk, vk) = gen_keys(&lhs_circuit); - let lhs_proof = gen_test_proof(&lhs_circuit); - let rhs_proof = gen_test_proof(&rhs_circuit); + let (lhs_proof, lhs_hint) = gen_test_proof(&lhs_circuit); + let (rhs_proof, rhs_hint) = gen_test_proof(&rhs_circuit); // Generate a link proof let proof = PlonkKzgSnark::link_proofs::( - &lhs_circuit, - &rhs_circuit, - &lhs_proof, - &rhs_proof, + &lhs_hint, + &rhs_hint, + group_layout, &pk, - &group, ) .unwrap(); diff --git a/plonk/src/proof_system/snark.rs b/plonk/src/proof_system/snark.rs index cea8ac6b4..162444649 100644 --- a/plonk/src/proof_system/snark.rs +++ b/plonk/src/proof_system/snark.rs @@ -8,8 +8,8 @@ use super::{ prover::Prover, structs::{ - BatchProof, Challenges, Oracles, PlookupProof, PlookupProvingKey, PlookupVerifyingKey, - Proof, ProvingKey, VerifyingKey, + BatchProof, Challenges, LinkingHint, Oracles, PlookupProof, PlookupProvingKey, + PlookupVerifyingKey, Proof, ProvingKey, VerifyingKey, }, verifier::Verifier, UniversalSNARK, @@ -39,7 +39,8 @@ use jf_primitives::{ }; use jf_utils::par_utils::parallelizable_slice_iter; use mpc_relation::{ - constants::compute_coset_representatives, gadgets::ecc::SWToTEConParam, traits::*, + constants::compute_coset_representatives, gadgets::ecc::SWToTEConParam, + proof_linking::PROOF_LINK_WIRE_IDX, traits::*, }; #[cfg(feature = "parallel")] use rayon::prelude::*; @@ -76,6 +77,42 @@ where Ok(batch_proof) } + /// Generate a proof and return a linking hint alongside + pub fn prove_with_link_hint( + prng: &mut R, + circuit: &C, + prove_key: &ProvingKey, + ) -> Result<(Proof, LinkingHint), PlonkError> + where + C: Arithmetization, + C: Circuit, + R: CryptoRng + RngCore, + T: PlonkTranscript, + { + // Prove the relation + let (batch_proof, oracles, _) = + Self::batch_prove_internal::<_, _, T>(prng, &[circuits], &[prove_keys], None)?; + + // Compute the linking hint + let hint = LinkingHint { + linking_wire_poly: oracles[0].wire_polys[PROOF_LINK_WIRE_IDX].clone(), + linking_wire_comm: batch_proof.wires_poly_comms_vec[0][PROOF_LINK_WIRE_IDX], + }; + + Ok(( + Proof { + wires_poly_comms: batch_proof.wires_poly_comms_vec[0].clone(), + prod_perm_poly_comm: batch_proof.prod_perm_poly_comms_vec[0], + split_quot_poly_comms: batch_proof.split_quot_poly_comms, + opening_proof: batch_proof.opening_proof, + shifted_opening_proof: batch_proof.shifted_opening_proof, + poly_evals: batch_proof.poly_evals_vec[0].clone(), + plookup_proof: batch_proof.plookup_proofs_vec[0].clone(), + }, + hint, + )) + } + /// Verify a single aggregated Plonk proof. pub fn verify_batch_proof( verify_keys: &[&VerifyingKey], diff --git a/plonk/src/proof_system/structs.rs b/plonk/src/proof_system/structs.rs index 7298b3c91..67383d90d 100644 --- a/plonk/src/proof_system/structs.rs +++ b/plonk/src/proof_system/structs.rs @@ -82,6 +82,19 @@ pub struct Proof { pub plookup_proof: Option>, } +/// A proof-linking hint generated by the prover in the course of proving an +/// R1CS relation +/// +/// The linking hint contains information about the prover's witness needed to +/// link the proof to another proof of a different circuit +#[derive(Debug, Clone)] +pub struct LinkingHint { + /// The wire polynomial that encodes the proof-linking gates for the circuit + pub linking_wire_poly: DensePolynomial, + /// The commitment to the linking wire poly generated while proving + pub linking_wire_comm: Commitment, +} + impl TryFrom> for Proof where E: Pairing>, diff --git a/relation/src/constraint_system.rs b/relation/src/constraint_system.rs index 475b0105f..e4c1ab516 100644 --- a/relation/src/constraint_system.rs +++ b/relation/src/constraint_system.rs @@ -218,6 +218,13 @@ where /// Groups without specified layouts will be given a layout when the circuit /// layout is determined pub(crate) link_group_layouts: HashMap, + /// The layout of the circuit if one has been generated + /// + /// After applying a layout to the circuit, the circuit's topology changes, + /// so subsequent calls to `gen_circuit_layout` will change the layout. + /// This field is used to cache the layout so that it can be reused even + /// after the layout is applied + pub(crate) layout: Option, /// The Plonk parameters. plonk_params: PlonkParams, @@ -262,6 +269,7 @@ impl PlonkCircuit { eval_domain: Radix2EvaluationDomain::new(1).unwrap(), link_groups: HashMap::new(), link_group_layouts: HashMap::new(), + layout: None, plonk_params, num_table_elems: 0, table_gate_ids: vec![], @@ -1140,6 +1148,7 @@ impl PlonkCircuit { // `link_groups` must be empty for both proofs link_groups: HashMap::new(), link_group_layouts: HashMap::new(), + layout: None, plonk_params: self.plonk_params, num_table_elems: 0, table_gate_ids: vec![], diff --git a/relation/src/proof_linking/arithmetization.rs b/relation/src/proof_linking/arithmetization.rs index 6690a0628..c2887b6fd 100644 --- a/relation/src/proof_linking/arithmetization.rs +++ b/relation/src/proof_linking/arithmetization.rs @@ -59,7 +59,11 @@ impl GroupLayout { impl PlonkCircuit { /// Generate a layout of the circuit, including where proof-linking gates /// will be placed - pub fn gen_circuit_layout(&self) -> Result { + pub fn gen_circuit_layout(&mut self) -> Result { + if let Some(layout) = &self.layout { + return Ok(layout.clone()); + } + // 1. Place the proof linking groups with specific layouts into the circuit let alignment = self.current_circuit_alignment(); let mut sorted_placements = self @@ -82,6 +86,8 @@ impl PlonkCircuit { CircuitLayout { n_inputs: self.num_inputs(), n_gates: self.num_gates(), group_layouts }; self.validate_layout(&circuit_layout)?; + self.layout = Some(circuit_layout.clone()); + Ok(circuit_layout) }