Separate prover and verifier keys in CompressedSNARK (#145)

* checkpoint

* simplify further

* checkpoint

* gens --> ck

* update benches

* address clippy

* cleanup

* update version
This commit is contained in:
Srinath Setty
2023-03-02 18:36:13 -08:00
committed by GitHub
parent 01ae6446a9
commit 1e4995274b
17 changed files with 391 additions and 344 deletions

View File

@@ -38,9 +38,7 @@ use errors::NovaError;
use ff::Field;
use gadgets::utils::scalar_as_base;
use nifs::NIFS;
use r1cs::{
R1CSGens, R1CSInstance, R1CSShape, R1CSWitness, RelaxedR1CSInstance, RelaxedR1CSWitness,
};
use r1cs::{R1CSInstance, R1CSShape, R1CSWitness, RelaxedR1CSInstance, RelaxedR1CSWitness};
use serde::{Deserialize, Serialize};
use traits::{
circuit::StepCircuit,
@@ -63,14 +61,12 @@ where
F_arity_secondary: usize,
ro_consts_primary: ROConstants<G1>,
ro_consts_circuit_primary: ROConstantsCircuit<G2>,
r1cs_gens_primary: R1CSGens<G1>,
ck_primary: CommitmentKey<G1>,
r1cs_shape_primary: R1CSShape<G1>,
r1cs_shape_padded_primary: R1CSShape<G1>,
ro_consts_secondary: ROConstants<G2>,
ro_consts_circuit_secondary: ROConstantsCircuit<G1>,
r1cs_gens_secondary: R1CSGens<G2>,
ck_secondary: CommitmentKey<G2>,
r1cs_shape_secondary: R1CSShape<G2>,
r1cs_shape_padded_secondary: R1CSShape<G2>,
augmented_circuit_params_primary: NovaAugmentedCircuitParams,
augmented_circuit_params_secondary: NovaAugmentedCircuitParams,
_p_c1: PhantomData<C1>,
@@ -101,7 +97,7 @@ where
let ro_consts_circuit_primary: ROConstantsCircuit<G2> = ROConstantsCircuit::<G2>::new();
let ro_consts_circuit_secondary: ROConstantsCircuit<G1> = ROConstantsCircuit::<G1>::new();
// Initialize gens for the primary
// Initialize ck for the primary
let circuit_primary: NovaAugmentedCircuit<G2, C1> = NovaAugmentedCircuit::new(
augmented_circuit_params_primary.clone(),
None,
@@ -110,10 +106,9 @@ where
);
let mut cs: ShapeCS<G1> = ShapeCS::new();
let _ = circuit_primary.synthesize(&mut cs);
let (r1cs_shape_primary, r1cs_gens_primary) = (cs.r1cs_shape(), cs.r1cs_gens());
let r1cs_shape_padded_primary = r1cs_shape_primary.pad();
let (r1cs_shape_primary, ck_primary) = (cs.r1cs_shape(), cs.commitment_key());
// Initialize gens for the secondary
// Initialize ck for the secondary
let circuit_secondary: NovaAugmentedCircuit<G1, C2> = NovaAugmentedCircuit::new(
augmented_circuit_params_secondary.clone(),
None,
@@ -122,22 +117,19 @@ where
);
let mut cs: ShapeCS<G2> = ShapeCS::new();
let _ = circuit_secondary.synthesize(&mut cs);
let (r1cs_shape_secondary, r1cs_gens_secondary) = (cs.r1cs_shape(), cs.r1cs_gens());
let r1cs_shape_padded_secondary = r1cs_shape_secondary.pad();
let (r1cs_shape_secondary, ck_secondary) = (cs.r1cs_shape(), cs.commitment_key());
Self {
F_arity_primary,
F_arity_secondary,
ro_consts_primary,
ro_consts_circuit_primary,
r1cs_gens_primary,
ck_primary,
r1cs_shape_primary,
r1cs_shape_padded_primary,
ro_consts_secondary,
ro_consts_circuit_secondary,
r1cs_gens_secondary,
ck_secondary,
r1cs_shape_secondary,
r1cs_shape_padded_secondary,
augmented_circuit_params_primary,
augmented_circuit_params_secondary,
_p_c1: Default::default(),
@@ -230,7 +222,7 @@ where
);
let _ = circuit_primary.synthesize(&mut cs_primary);
let (u_primary, w_primary) = cs_primary
.r1cs_instance_and_witness(&pp.r1cs_shape_primary, &pp.r1cs_gens_primary)
.r1cs_instance_and_witness(&pp.r1cs_shape_primary, &pp.ck_primary)
.map_err(|_e| NovaError::UnSat)?;
// base case for the secondary
@@ -252,7 +244,7 @@ where
);
let _ = circuit_secondary.synthesize(&mut cs_secondary);
let (u_secondary, w_secondary) = cs_secondary
.r1cs_instance_and_witness(&pp.r1cs_shape_secondary, &pp.r1cs_gens_secondary)
.r1cs_instance_and_witness(&pp.r1cs_shape_secondary, &pp.ck_secondary)
.map_err(|_e| NovaError::UnSat)?;
// IVC proof for the primary circuit
@@ -261,7 +253,7 @@ where
let r_W_primary =
RelaxedR1CSWitness::from_r1cs_witness(&pp.r1cs_shape_primary, &l_w_primary);
let r_U_primary = RelaxedR1CSInstance::from_r1cs_instance(
&pp.r1cs_gens_primary,
&pp.ck_primary,
&pp.r1cs_shape_primary,
&l_u_primary,
);
@@ -271,7 +263,7 @@ where
let l_u_secondary = u_secondary;
let r_W_secondary = RelaxedR1CSWitness::<G2>::default(&pp.r1cs_shape_secondary);
let r_U_secondary =
RelaxedR1CSInstance::<G2>::default(&pp.r1cs_gens_secondary, &pp.r1cs_shape_secondary);
RelaxedR1CSInstance::<G2>::default(&pp.ck_secondary, &pp.r1cs_shape_secondary);
// Outputs of the two circuits thus far
let zi_primary = c_primary.output(&z0_primary);
@@ -300,7 +292,7 @@ where
Some(r_snark) => {
// fold the secondary circuit's instance
let (nifs_secondary, (r_U_secondary, r_W_secondary)) = NIFS::prove(
&pp.r1cs_gens_secondary,
&pp.ck_secondary,
&pp.ro_consts_secondary,
&pp.r1cs_shape_secondary,
&r_snark.r_U_secondary,
@@ -329,12 +321,12 @@ where
let _ = circuit_primary.synthesize(&mut cs_primary);
let (l_u_primary, l_w_primary) = cs_primary
.r1cs_instance_and_witness(&pp.r1cs_shape_primary, &pp.r1cs_gens_primary)
.r1cs_instance_and_witness(&pp.r1cs_shape_primary, &pp.ck_primary)
.map_err(|_e| NovaError::UnSat)?;
// fold the primary circuit's instance
let (nifs_primary, (r_U_primary, r_W_primary)) = NIFS::prove(
&pp.r1cs_gens_primary,
&pp.ck_primary,
&pp.ro_consts_primary,
&pp.r1cs_shape_primary,
&r_snark.r_U_primary,
@@ -363,7 +355,7 @@ where
let _ = circuit_secondary.synthesize(&mut cs_secondary);
let (l_u_secondary, l_w_secondary) = cs_secondary
.r1cs_instance_and_witness(&pp.r1cs_shape_secondary, &pp.r1cs_gens_secondary)
.r1cs_instance_and_witness(&pp.r1cs_shape_secondary, &pp.ck_secondary)
.map_err(|_e| NovaError::UnSat)?;
// update the running instances and witnesses
@@ -464,17 +456,14 @@ where
rayon::join(
|| {
pp.r1cs_shape_primary.is_sat_relaxed(
&pp.r1cs_gens_primary,
&pp.ck_primary,
&self.r_U_primary,
&self.r_W_primary,
)
},
|| {
pp.r1cs_shape_primary.is_sat(
&pp.r1cs_gens_primary,
&self.l_u_primary,
&self.l_w_primary,
)
pp.r1cs_shape_primary
.is_sat(&pp.ck_primary, &self.l_u_primary, &self.l_w_primary)
},
)
},
@@ -482,14 +471,14 @@ where
rayon::join(
|| {
pp.r1cs_shape_secondary.is_sat_relaxed(
&pp.r1cs_gens_secondary,
&pp.ck_secondary,
&self.r_U_secondary,
&self.r_W_secondary,
)
},
|| {
pp.r1cs_shape_secondary.is_sat(
&pp.r1cs_gens_secondary,
&pp.ck_secondary,
&self.l_u_secondary,
&self.l_w_secondary,
)
@@ -508,9 +497,51 @@ where
}
}
/// A SNARK that proves the knowledge of a valid `RecursiveSNARK`
/// A type that holds the prover key for `CompressedSNARK`
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(bound = "")]
pub struct ProverKey<G1, G2, C1, C2, S1, S2>
where
G1: Group<Base = <G2 as Group>::Scalar>,
G2: Group<Base = <G1 as Group>::Scalar>,
C1: StepCircuit<G1::Scalar>,
C2: StepCircuit<G2::Scalar>,
S1: RelaxedR1CSSNARKTrait<G1>,
S2: RelaxedR1CSSNARKTrait<G2>,
{
pk_primary: S1::ProverKey,
pk_secondary: S2::ProverKey,
_p_c1: PhantomData<C1>,
_p_c2: PhantomData<C2>,
}
/// A type that holds the verifier key for `CompressedSNARK`
#[derive(Clone, Serialize, Deserialize)]
#[serde(bound = "")]
pub struct VerifierKey<G1, G2, C1, C2, S1, S2>
where
G1: Group<Base = <G2 as Group>::Scalar>,
G2: Group<Base = <G1 as Group>::Scalar>,
C1: StepCircuit<G1::Scalar>,
C2: StepCircuit<G2::Scalar>,
S1: RelaxedR1CSSNARKTrait<G1>,
S2: RelaxedR1CSSNARKTrait<G2>,
{
F_arity_primary: usize,
F_arity_secondary: usize,
ro_consts_primary: ROConstants<G1>,
ro_consts_secondary: ROConstants<G2>,
r1cs_shape_primary_digest: G1::Scalar,
r1cs_shape_secondary_digest: G2::Scalar,
vk_primary: S1::VerifierKey,
vk_secondary: S2::VerifierKey,
_p_c1: PhantomData<C1>,
_p_c2: PhantomData<C2>,
}
/// A SNARK that proves the knowledge of a valid `RecursiveSNARK`
#[derive(Clone, Serialize, Deserialize)]
#[serde(bound = "")]
pub struct CompressedSNARK<G1, G2, C1, C2, S1, S2>
where
G1: Group<Base = <G2 as Group>::Scalar>,
@@ -546,16 +577,50 @@ where
S1: RelaxedR1CSSNARKTrait<G1>,
S2: RelaxedR1CSSNARKTrait<G2>,
{
/// Creates prover and verifier keys for `CompressedSNARK`
pub fn setup(
pp: &PublicParams<G1, G2, C1, C2>,
) -> (
ProverKey<G1, G2, C1, C2, S1, S2>,
VerifierKey<G1, G2, C1, C2, S1, S2>,
) {
let (pk_primary, vk_primary) = S1::setup(&pp.ck_primary, &pp.r1cs_shape_primary);
let (pk_secondary, vk_secondary) = S2::setup(&pp.ck_secondary, &pp.r1cs_shape_secondary);
let pk = ProverKey {
pk_primary,
pk_secondary,
_p_c1: Default::default(),
_p_c2: Default::default(),
};
let vk = VerifierKey {
F_arity_primary: pp.F_arity_primary,
F_arity_secondary: pp.F_arity_secondary,
ro_consts_primary: pp.ro_consts_primary.clone(),
ro_consts_secondary: pp.ro_consts_secondary.clone(),
r1cs_shape_primary_digest: pp.r1cs_shape_primary.get_digest(),
r1cs_shape_secondary_digest: pp.r1cs_shape_secondary.get_digest(),
vk_primary,
vk_secondary,
_p_c1: Default::default(),
_p_c2: Default::default(),
};
(pk, vk)
}
/// Create a new `CompressedSNARK`
pub fn prove(
pp: &PublicParams<G1, G2, C1, C2>,
pk: &ProverKey<G1, G2, C1, C2, S1, S2>,
recursive_snark: &RecursiveSNARK<G1, G2, C1, C2>,
) -> Result<Self, NovaError> {
let (res_primary, res_secondary) = rayon::join(
// fold the primary circuit's instance
|| {
NIFS::prove(
&pp.r1cs_gens_primary,
&pp.ck_primary,
&pp.ro_consts_primary,
&pp.r1cs_shape_primary,
&recursive_snark.r_U_primary,
@@ -567,7 +632,7 @@ where
|| {
// fold the secondary circuit's instance
NIFS::prove(
&pp.r1cs_gens_secondary,
&pp.ck_secondary,
&pp.ro_consts_secondary,
&pp.r1cs_shape_secondary,
&recursive_snark.r_U_secondary,
@@ -581,26 +646,15 @@ where
let (nifs_primary, (f_U_primary, f_W_primary)) = res_primary?;
let (nifs_secondary, (f_U_secondary, f_W_secondary)) = res_secondary?;
// produce a prover key for the SNARK
let (pk_primary, pk_secondary) = rayon::join(
|| S1::prover_key(&pp.r1cs_gens_primary, &pp.r1cs_shape_padded_primary),
|| S2::prover_key(&pp.r1cs_gens_secondary, &pp.r1cs_shape_padded_secondary),
);
// create SNARKs proving the knowledge of f_W_primary and f_W_secondary
let (f_W_snark_primary, f_W_snark_secondary) = rayon::join(
|| {
S1::prove(
&pk_primary,
&f_U_primary,
&f_W_primary.pad(&pp.r1cs_shape_padded_primary), // pad the witness since shape was padded
)
},
|| S1::prove(&pp.ck_primary, &pk.pk_primary, &f_U_primary, &f_W_primary),
|| {
S2::prove(
&pk_secondary,
&pp.ck_secondary,
&pk.pk_secondary,
&f_U_secondary,
&f_W_secondary.pad(&pp.r1cs_shape_padded_secondary), // pad the witness since the shape was padded
&f_W_secondary,
)
},
);
@@ -627,7 +681,7 @@ where
/// Verify the correctness of the `CompressedSNARK`
pub fn verify(
&self,
pp: &PublicParams<G1, G2, C1, C2>,
vk: &VerifierKey<G1, G2, C1, C2, S1, S2>,
num_steps: usize,
z0_primary: Vec<G1::Scalar>,
z0_secondary: Vec<G2::Scalar>,
@@ -649,10 +703,10 @@ where
// check if the output hashes in R1CS instances point to the right running instances
let (hash_primary, hash_secondary) = {
let mut hasher = <G2 as Group>::RO::new(
pp.ro_consts_secondary.clone(),
NUM_FE_WITHOUT_IO_FOR_CRHF + 2 * pp.F_arity_primary,
vk.ro_consts_secondary.clone(),
NUM_FE_WITHOUT_IO_FOR_CRHF + 2 * vk.F_arity_primary,
);
hasher.absorb(scalar_as_base::<G2>(pp.r1cs_shape_secondary.get_digest()));
hasher.absorb(scalar_as_base::<G2>(vk.r1cs_shape_secondary_digest));
hasher.absorb(G1::Scalar::from(num_steps as u64));
for e in z0_primary {
hasher.absorb(e);
@@ -663,10 +717,10 @@ where
self.r_U_secondary.absorb_in_ro(&mut hasher);
let mut hasher2 = <G1 as Group>::RO::new(
pp.ro_consts_primary.clone(),
NUM_FE_WITHOUT_IO_FOR_CRHF + 2 * pp.F_arity_secondary,
vk.ro_consts_primary.clone(),
NUM_FE_WITHOUT_IO_FOR_CRHF + 2 * vk.F_arity_secondary,
);
hasher2.absorb(scalar_as_base::<G1>(pp.r1cs_shape_primary.get_digest()));
hasher2.absorb(scalar_as_base::<G1>(vk.r1cs_shape_primary_digest));
hasher2.absorb(G2::Scalar::from(num_steps as u64));
for e in z0_secondary {
hasher2.absorb(e);
@@ -690,31 +744,25 @@ where
// fold the running instance and last instance to get a folded instance
let f_U_primary = self.nifs_primary.verify(
&pp.ro_consts_primary,
&pp.r1cs_shape_primary,
&vk.ro_consts_primary,
&vk.r1cs_shape_primary_digest,
&self.r_U_primary,
&self.l_u_primary,
)?;
let f_U_secondary = self.nifs_secondary.verify(
&pp.ro_consts_secondary,
&pp.r1cs_shape_secondary,
&vk.ro_consts_secondary,
&vk.r1cs_shape_secondary_digest,
&self.r_U_secondary,
&self.l_u_secondary,
)?;
// produce a verifier key for the SNARK
let (vk_primary, vk_secondary) = rayon::join(
|| S1::verifier_key(&pp.r1cs_gens_primary, &pp.r1cs_shape_padded_primary),
|| S2::verifier_key(&pp.r1cs_gens_secondary, &pp.r1cs_shape_padded_secondary),
);
// check the satisfiability of the folded instances using SNARKs proving the knowledge of their satisfying witnesses
let (res_primary, res_secondary) = rayon::join(
|| self.f_W_snark_primary.verify(&vk_primary, &f_U_primary),
|| self.f_W_snark_primary.verify(&vk.vk_primary, &f_U_primary),
|| {
self
.f_W_snark_secondary
.verify(&vk_secondary, &f_U_secondary)
.verify(&vk.vk_secondary, &f_U_secondary)
},
);
@@ -725,7 +773,7 @@ where
}
}
type CommitmentGens<G> = <<G as traits::Group>::CE as CommitmentEngineTrait<G>>::CommitmentGens;
type CommitmentKey<G> = <<G as traits::Group>::CE as CommitmentEngineTrait<G>>::CommitmentKey;
type Commitment<G> = <<G as Group>::CE as CommitmentEngineTrait<G>>::Commitment;
type CompressedCommitment<G> = <<<G as Group>::CE as CommitmentEngineTrait<G>>::Commitment as CommitmentTrait<G>>::CompressedCommitment;
type CE<G> = <G as Group>::CE;
@@ -962,14 +1010,17 @@ mod tests {
assert_eq!(zn_secondary, zn_secondary_direct);
assert_eq!(zn_secondary, vec![<G2 as Group>::Scalar::from(2460515u64)]);
// produce the prover and verifier keys for compressed snark
let (pk, vk) = CompressedSNARK::<_, _, _, _, S1, S2>::setup(&pp);
// produce a compressed SNARK
let res = CompressedSNARK::<_, _, _, _, S1, S2>::prove(&pp, &recursive_snark);
let res = CompressedSNARK::<_, _, _, _, S1, S2>::prove(&pp, &pk, &recursive_snark);
assert!(res.is_ok());
let compressed_snark = res.unwrap();
// verify the compressed SNARK
let res = compressed_snark.verify(
&pp,
&vk,
num_steps,
vec![<G1 as Group>::Scalar::one()],
vec![<G2 as Group>::Scalar::zero()],
@@ -1110,13 +1161,16 @@ mod tests {
let res = recursive_snark.verify(&pp, num_steps, z0_primary.clone(), z0_secondary.clone());
assert!(res.is_ok());
// produce the prover and verifier keys for compressed snark
let (pk, vk) = CompressedSNARK::<_, _, _, _, S1, S2>::setup(&pp);
// produce a compressed SNARK
let res = CompressedSNARK::<_, _, _, _, S1, S2>::prove(&pp, &recursive_snark);
let res = CompressedSNARK::<_, _, _, _, S1, S2>::prove(&pp, &pk, &recursive_snark);
assert!(res.is_ok());
let compressed_snark = res.unwrap();
// verify the compressed SNARK
let res = compressed_snark.verify(&pp, num_steps, z0_primary, z0_secondary);
let res = compressed_snark.verify(&vk, num_steps, z0_primary, z0_secondary);
assert!(res.is_ok());
}