Skip to content

Commit

Permalink
plonk: proof-system: prover: Emit LinkingHint with proof
Browse files Browse the repository at this point in the history
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
  • Loading branch information
Joey Kraut authored and joeykraut committed Jan 5, 2024
1 parent 7977755 commit 8dcdc69
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 50 deletions.
87 changes: 41 additions & 46 deletions plonk/src/proof_system/proof_linking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};

Expand All @@ -51,37 +48,29 @@ where
{
/// Link two proofs on a given domain
pub fn link_proofs<T: PlonkTranscript<F>>(
lhs_circuit: &PlonkCircuit<E::ScalarField>,
rhs_circuit: &PlonkCircuit<E::ScalarField>,
lhs_proof: &Proof<E>,
rhs_proof: &Proof<E>,
lhs_link_hint: &LinkingHint<E>,
rhs_link_hint: &LinkingHint<E>,
group_layout: &GroupLayout,
proving_key: &ProvingKey<E>,
link_group: &LinkGroup,
) -> Result<LinkingProof<E>, 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, &quotient)
.map_err(PlonkError::PCSError)?;

// Squeeze a challenge for the opening
let opening_challenge =
Self::compute_quotient_challenge::<T>(lhs_proof, rhs_proof, &quotient_commitment)?;
let opening_challenge = Self::compute_quotient_challenge::<T>(
&lhs_link_hint.linking_wire_comm,
&rhs_link_hint.linking_wire_comm,
&quotient_commitment,
)?;
let opening_proof = Self::compute_identity_opening(
&a1,
&a2,
a1,
a2,
&quotient,
opening_challenge,
group_layout,
Expand Down Expand Up @@ -164,15 +153,14 @@ where
/// the proofs _after_ they have committed to the wiring polynomials
/// that are being linked
fn compute_quotient_challenge<T: PlonkTranscript<E::BaseField>>(
lhs_proof: &Proof<E>,
rhs_proof: &Proof<E>,
a1_comm: &Commitment<E>,
a2_comm: &Commitment<E>,
quotient_comm: &Commitment<E>,
) -> Result<E::ScalarField, PlonkError> {
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::<E>(b"eta")
Expand Down Expand Up @@ -215,6 +203,11 @@ where
P: SWCurveConfig<BaseField = F, ScalarField = E::ScalarField>,
{
/// 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<T: PlonkTranscript<E::BaseField>>(
r1cs_proof1: &Proof<E>,
r1cs_proof2: &Proof<E>,
Expand All @@ -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::<T>(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::<T>(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);

Expand Down Expand Up @@ -279,7 +272,7 @@ mod test {

use crate::{
proof_system::{
structs::{Proof, ProvingKey, VerifyingKey},
structs::{LinkingHint, Proof, ProvingKey, VerifyingKey},
PlonkKzgSnark, UniversalSNARK,
},
transcript::SolidityTranscript,
Expand Down Expand Up @@ -322,13 +315,14 @@ mod test {
(circuit, group)
}

/// Generate a proof for a circuit
fn gen_test_proof(circuit: &PlonkCircuit<FrBn254>) -> Proof<Bn254> {
/// Generate a proof and link hint for the circuit by proving its r1cs
/// relation
fn gen_test_proof(circuit: &PlonkCircuit<FrBn254>) -> (Proof<Bn254>, LinkingHint<Bn254>) {
let mut rng = thread_rng();
let (pk, _) = gen_keys(circuit);

PlonkKzgSnark::<Bn254>::prove::<_, _, SolidityTranscript>(
&mut rng, circuit, &pk, None, // extra_init_msg
PlonkKzgSnark::<Bn254>::prove_with_link_hint::<_, _, SolidityTranscript>(
&mut rng, circuit, &pk,
)
.unwrap()
}
Expand All @@ -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::<SolidityTranscript>(
&lhs_circuit,
&rhs_circuit,
&lhs_proof,
&rhs_proof,
&lhs_hint,
&rhs_hint,
group_layout,
&pk,
&group,
)
.unwrap();

Expand Down
43 changes: 40 additions & 3 deletions plonk/src/proof_system/snark.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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::*;
Expand Down Expand Up @@ -76,6 +77,42 @@ where
Ok(batch_proof)
}

/// Generate a proof and return a linking hint alongside
pub fn prove_with_link_hint<C, R, T>(
prng: &mut R,
circuit: &C,
prove_key: &ProvingKey<E>,
) -> Result<(Proof<E>, LinkingHint<E>), PlonkError>
where
C: Arithmetization<E::ScalarField>,
C: Circuit<E::ScalarField, Wire = E::ScalarField, Constant = E::ScalarField>,
R: CryptoRng + RngCore,
T: PlonkTranscript<F>,
{
// 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<T>(
verify_keys: &[&VerifyingKey<E>],
Expand Down
13 changes: 13 additions & 0 deletions plonk/src/proof_system/structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,19 @@ pub struct Proof<E: Pairing> {
pub plookup_proof: Option<PlookupProof<E>>,
}

/// 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<E: Pairing> {
/// The wire polynomial that encodes the proof-linking gates for the circuit
pub linking_wire_poly: DensePolynomial<E::ScalarField>,
/// The commitment to the linking wire poly generated while proving
pub linking_wire_comm: Commitment<E>,
}

impl<E, P> TryFrom<Vec<E::BaseField>> for Proof<E>
where
E: Pairing<G1Affine = Affine<P>>,
Expand Down
9 changes: 9 additions & 0 deletions relation/src/constraint_system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, GroupLayout>,
/// 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<CircuitLayout>,

/// The Plonk parameters.
plonk_params: PlonkParams,
Expand Down Expand Up @@ -262,6 +269,7 @@ impl<F: FftField> PlonkCircuit<F> {
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![],
Expand Down Expand Up @@ -1140,6 +1148,7 @@ impl<F: PrimeField> PlonkCircuit<F> {
// `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![],
Expand Down
8 changes: 7 additions & 1 deletion relation/src/proof_linking/arithmetization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,11 @@ impl GroupLayout {
impl<F: PrimeField> PlonkCircuit<F> {
/// Generate a layout of the circuit, including where proof-linking gates
/// will be placed
pub fn gen_circuit_layout(&self) -> Result<CircuitLayout, CircuitError> {
pub fn gen_circuit_layout(&mut self) -> Result<CircuitLayout, CircuitError> {
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
Expand All @@ -82,6 +86,8 @@ impl<F: PrimeField> PlonkCircuit<F> {
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)
}

Expand Down

0 comments on commit 8dcdc69

Please sign in to comment.