Browse Source

Recursion implementation (#65)

* recursion attempt

* address clippy

* initialize the running instance and witness of the primary correctly

* add asserts for debugging

* fix a bug in AllocatedPoint

* add debug statements

* fix an issue with how we inputize hashes; remove debug statements

* rename

* cleanup

* speedup tests

* require step_circuit implementors to provide a way to execute step computation
main
Srinath Setty 2 years ago
committed by GitHub
parent
commit
1fd4eee2b6
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 153 additions and 29 deletions
  1. +1
    -1
      .github/workflows/rust.yml
  2. +6
    -2
      src/circuit.rs
  3. +1
    -1
      src/gadgets/ecc.rs
  4. +120
    -24
      src/lib.rs
  5. +1
    -1
      src/nifs.rs
  6. +21
    -0
      src/r1cs.rs
  7. +3
    -0
      src/traits.rs

+ 1
- 1
.github/workflows/rust.yml

@ -20,7 +20,7 @@ jobs:
- name: Build - name: Build
run: cargo build --verbose run: cargo build --verbose
- name: Run tests - name: Run tests
run: cargo test --verbose
run: cargo test --release --verbose
- name: Check Rustfmt Code Style - name: Check Rustfmt Code Style
run: cargo fmt --all -- --check run: cargo fmt --all -- --check
- name: Check clippy warnings - name: Check clippy warnings

+ 6
- 2
src/circuit.rs

@ -93,9 +93,9 @@ where
SC: StepCircuit<G::Base>, SC: StepCircuit<G::Base>,
{ {
params: NIFSVerifierCircuitParams, params: NIFSVerifierCircuitParams,
ro_consts: ROConstantsCircuit<G::Base>,
inputs: Option<NIFSVerifierCircuitInputs<G>>, inputs: Option<NIFSVerifierCircuitInputs<G>>,
step_circuit: SC, // The function that is applied for each step step_circuit: SC, // The function that is applied for each step
ro_consts: ROConstantsCircuit<G::Base>,
} }
impl<G, SC> NIFSVerifierCircuit<G, SC> impl<G, SC> NIFSVerifierCircuit<G, SC>
@ -335,10 +335,10 @@ where
let hash = le_bits_to_num(cs.namespace(|| "convert hash to num"), hash_bits)?; let hash = le_bits_to_num(cs.namespace(|| "convert hash to num"), hash_bits)?;
// Outputs the computed hash and u.X[1] that corresponds to the hash of the other circuit // Outputs the computed hash and u.X[1] that corresponds to the hash of the other circuit
let _ = hash.inputize(cs.namespace(|| "output new hash of this circuit"))?;
let _ = u let _ = u
.X1 .X1
.inputize(cs.namespace(|| "Output unmodified hash of the other circuit"))?; .inputize(cs.namespace(|| "Output unmodified hash of the other circuit"))?;
let _ = hash.inputize(cs.namespace(|| "output new hash of this circuit"))?;
Ok(()) Ok(())
} }
@ -373,6 +373,10 @@ mod tests {
) -> Result<AllocatedNum<F>, SynthesisError> { ) -> Result<AllocatedNum<F>, SynthesisError> {
Ok(z) Ok(z)
} }
fn compute(&self, z: &F) -> F {
*z
}
} }
#[test] #[test]

+ 1
- 1
src/gadgets/ecc.rs

@ -38,7 +38,7 @@ where
Ok(coords.map_or(Fp::zero(), |c| c.0)) Ok(coords.map_or(Fp::zero(), |c| c.0))
})?; })?;
let y = AllocatedNum::alloc(cs.namespace(|| "y"), || { let y = AllocatedNum::alloc(cs.namespace(|| "y"), || {
Ok(coords.map_or(Fp::zero(), |c| c.0))
Ok(coords.map_or(Fp::zero(), |c| c.1))
})?; })?;
let is_infinity = AllocatedNum::alloc(cs.namespace(|| "is_infinity"), || { let is_infinity = AllocatedNum::alloc(cs.namespace(|| "is_infinity"), || {
Ok(if coords.map_or(true, |c| c.2) { Ok(if coords.map_or(true, |c| c.2) {

+ 120
- 24
src/lib.rs

@ -20,13 +20,14 @@ use crate::bellperson::{
shape_cs::ShapeCS, shape_cs::ShapeCS,
solver::SatisfyingAssignment, solver::SatisfyingAssignment,
}; };
use crate::poseidon::ROConstantsCircuit; // TODO: make this a trait so we can use it without the concrete implementation
use ::bellperson::{Circuit, ConstraintSystem}; use ::bellperson::{Circuit, ConstraintSystem};
use circuit::{NIFSVerifierCircuit, NIFSVerifierCircuitInputs, NIFSVerifierCircuitParams}; use circuit::{NIFSVerifierCircuit, NIFSVerifierCircuitInputs, NIFSVerifierCircuitParams};
use constants::{BN_LIMB_WIDTH, BN_N_LIMBS}; use constants::{BN_LIMB_WIDTH, BN_N_LIMBS};
use core::marker::PhantomData; use core::marker::PhantomData;
use errors::NovaError; use errors::NovaError;
use ff::Field; use ff::Field;
use nifs::NIFS;
use poseidon::ROConstantsCircuit; // TODO: make this a trait so we can use it without the concrete implementation
use r1cs::{ use r1cs::{
R1CSGens, R1CSInstance, R1CSShape, R1CSWitness, RelaxedR1CSInstance, RelaxedR1CSWitness, R1CSGens, R1CSInstance, R1CSShape, R1CSWitness, RelaxedR1CSInstance, RelaxedR1CSWitness,
}; };
@ -148,6 +149,7 @@ where
pp: &PublicParams<G1, G2, C1, C2>, pp: &PublicParams<G1, G2, C1, C2>,
z0_primary: G1::Scalar, z0_primary: G1::Scalar,
z0_secondary: G2::Scalar, z0_secondary: G2::Scalar,
num_steps: usize,
) -> Result<Self, NovaError> { ) -> Result<Self, NovaError> {
// Execute the base case for the primary // Execute the base case for the primary
let mut cs_primary: SatisfyingAssignment<G1> = SatisfyingAssignment::new(); let mut cs_primary: SatisfyingAssignment<G1> = SatisfyingAssignment::new();
@ -171,11 +173,6 @@ where
.r1cs_instance_and_witness(&pp.r1cs_shape_primary, &pp.r1cs_gens_primary) .r1cs_instance_and_witness(&pp.r1cs_shape_primary, &pp.r1cs_gens_primary)
.map_err(|_e| NovaError::UnSat)?; .map_err(|_e| NovaError::UnSat)?;
// check if the base case is satisfied
pp.r1cs_shape_primary
.is_sat(&pp.r1cs_gens_primary, &u_primary, &w_primary)
.map_err(|_e| NovaError::UnSat)?;
// Execute the base case for the secondary // Execute the base case for the secondary
let mut cs_secondary: SatisfyingAssignment<G2> = SatisfyingAssignment::new(); let mut cs_secondary: SatisfyingAssignment<G2> = SatisfyingAssignment::new();
let inputs_secondary: NIFSVerifierCircuitInputs<G1> = NIFSVerifierCircuitInputs::new( let inputs_secondary: NIFSVerifierCircuitInputs<G1> = NIFSVerifierCircuitInputs::new(
@ -198,26 +195,115 @@ where
.r1cs_instance_and_witness(&pp.r1cs_shape_secondary, &pp.r1cs_gens_secondary) .r1cs_instance_and_witness(&pp.r1cs_shape_secondary, &pp.r1cs_gens_secondary)
.map_err(|_e| NovaError::UnSat)?; .map_err(|_e| NovaError::UnSat)?;
// check if the base case is satisfied
pp.r1cs_shape_secondary
.is_sat(&pp.r1cs_gens_secondary, &u_secondary, &w_secondary)
.map_err(|_e| NovaError::UnSat)?;
// execute the remaining steps, alternating between G1 and G2
let mut l_w_primary = w_primary;
let mut l_u_primary = u_primary;
let mut r_W_primary =
RelaxedR1CSWitness::from_r1cs_witness(&pp.r1cs_shape_primary, &l_w_primary);
let mut r_U_primary = RelaxedR1CSInstance::from_r1cs_instance(
&pp.r1cs_gens_primary,
&pp.r1cs_shape_primary,
&l_u_primary,
);
Ok(Self {
r_W_primary: RelaxedR1CSWitness::<G1>::default(&pp.r1cs_shape_primary),
r_U_primary: RelaxedR1CSInstance::<G1>::default(
&pp.r1cs_gens_primary,
&pp.r1cs_shape_primary,
),
l_w_primary: w_primary,
l_u_primary: u_primary,
r_W_secondary: RelaxedR1CSWitness::<G2>::default(&pp.r1cs_shape_secondary),
r_U_secondary: RelaxedR1CSInstance::<G2>::default(
let mut r_W_secondary = RelaxedR1CSWitness::<G2>::default(&pp.r1cs_shape_secondary);
let mut r_U_secondary =
RelaxedR1CSInstance::<G2>::default(&pp.r1cs_gens_secondary, &pp.r1cs_shape_secondary);
let mut l_w_secondary = w_secondary;
let mut l_u_secondary = u_secondary;
let mut z_next_primary = z0_primary;
let mut z_next_secondary = z0_secondary;
// TODO: execute the provided step circuit(s) to feed real z_i into the verifier circuit
for i in 1..num_steps {
// fold the secondary circuit's instance into r_W_primary
let (nifs_secondary, (r_U_next_secondary, r_W_next_secondary)) = NIFS::prove(
&pp.r1cs_gens_secondary, &pp.r1cs_gens_secondary,
&pp._ro_consts_secondary,
&pp.r1cs_shape_secondary, &pp.r1cs_shape_secondary,
),
l_w_secondary: w_secondary,
l_u_secondary: u_secondary,
&r_U_secondary,
&r_W_secondary,
&l_u_secondary,
&l_w_secondary,
)?;
z_next_primary = pp.c_primary.compute(&z_next_primary);
z_next_secondary = pp.c_secondary.compute(&z_next_secondary);
let mut cs_primary: SatisfyingAssignment<G1> = SatisfyingAssignment::new();
let inputs_primary: NIFSVerifierCircuitInputs<G2> = NIFSVerifierCircuitInputs::new(
pp.r1cs_shape_secondary.get_digest(),
<G2 as Group>::Base::from(i as u64),
z0_primary,
Some(z_next_primary),
Some(r_U_secondary),
Some(l_u_secondary),
Some(nifs_secondary.comm_T.decompress()?),
);
let circuit_primary: NIFSVerifierCircuit<G2, C1> = NIFSVerifierCircuit::new(
pp.params_primary.clone(),
Some(inputs_primary),
pp.c_primary.clone(),
pp.ro_consts_circuit_primary.clone(),
);
let _ = circuit_primary.synthesize(&mut cs_primary);
(l_u_primary, l_w_primary) = cs_primary
.r1cs_instance_and_witness(&pp.r1cs_shape_primary, &pp.r1cs_gens_primary)
.map_err(|_e| NovaError::UnSat)?;
// fold the secondary circuit's instance into r_W_primary
let (nifs_primary, (r_U_next_primary, r_W_next_primary)) = NIFS::prove(
&pp.r1cs_gens_primary,
&pp._ro_consts_primary,
&pp.r1cs_shape_primary,
&r_U_primary.clone(),
&r_W_primary.clone(),
&l_u_primary.clone(),
&l_w_primary.clone(),
)?;
let mut cs_secondary: SatisfyingAssignment<G2> = SatisfyingAssignment::new();
let inputs_secondary: NIFSVerifierCircuitInputs<G1> = NIFSVerifierCircuitInputs::new(
pp.r1cs_shape_primary.get_digest(),
<G1 as Group>::Base::from(i as u64),
z0_secondary,
Some(z_next_secondary),
Some(r_U_primary.clone()),
Some(l_u_primary.clone()),
Some(nifs_primary.comm_T.decompress()?),
);
let circuit_secondary: NIFSVerifierCircuit<G1, C2> = NIFSVerifierCircuit::new(
pp.params_secondary.clone(),
Some(inputs_secondary),
pp.c_secondary.clone(),
pp.ro_consts_circuit_secondary.clone(),
);
let _ = circuit_secondary.synthesize(&mut cs_secondary);
(l_u_secondary, l_w_secondary) = cs_secondary
.r1cs_instance_and_witness(&pp.r1cs_shape_secondary, &pp.r1cs_gens_secondary)
.map_err(|_e| NovaError::UnSat)?;
// update the running instances and witnesses
r_U_secondary = r_U_next_secondary;
r_W_secondary = r_W_next_secondary;
r_U_primary = r_U_next_primary;
r_W_primary = r_W_next_primary;
}
Ok(Self {
r_W_primary,
r_U_primary,
l_w_primary,
l_u_primary,
r_W_secondary,
r_U_secondary,
l_w_secondary,
l_u_secondary,
_p_c1: Default::default(), _p_c1: Default::default(),
_p_c2: Default::default(), _p_c2: Default::default(),
}) })
@ -225,18 +311,23 @@ where
/// Verify the correctness of the `RecursiveSNARK` /// Verify the correctness of the `RecursiveSNARK`
pub fn verify(&self, pp: &PublicParams<G1, G2, C1, C2>) -> Result<(), NovaError> { pub fn verify(&self, pp: &PublicParams<G1, G2, C1, C2>) -> Result<(), NovaError> {
// TODO: perform additional checks on whether (shape_digest, z_0, z_i, i) are correct
pp.r1cs_shape_primary.is_sat_relaxed( pp.r1cs_shape_primary.is_sat_relaxed(
&pp.r1cs_gens_primary, &pp.r1cs_gens_primary,
&self.r_U_primary, &self.r_U_primary,
&self.r_W_primary, &self.r_W_primary,
)?; )?;
pp.r1cs_shape_primary pp.r1cs_shape_primary
.is_sat(&pp.r1cs_gens_primary, &self.l_u_primary, &self.l_w_primary)?; .is_sat(&pp.r1cs_gens_primary, &self.l_u_primary, &self.l_w_primary)?;
pp.r1cs_shape_secondary.is_sat_relaxed( pp.r1cs_shape_secondary.is_sat_relaxed(
&pp.r1cs_gens_secondary, &pp.r1cs_gens_secondary,
&self.r_U_secondary, &self.r_U_secondary,
&self.r_W_secondary, &self.r_W_secondary,
)?; )?;
pp.r1cs_shape_secondary.is_sat( pp.r1cs_shape_secondary.is_sat(
&pp.r1cs_gens_secondary, &pp.r1cs_gens_secondary,
&self.l_u_secondary, &self.l_u_secondary,
@ -272,10 +363,14 @@ mod tests {
) -> Result<AllocatedNum<F>, SynthesisError> { ) -> Result<AllocatedNum<F>, SynthesisError> {
Ok(z) Ok(z)
} }
fn compute(&self, z: &F) -> F {
*z
}
} }
#[test] #[test]
fn test_base_case() {
fn test_ivc() {
// produce public parameters // produce public parameters
let pp = PublicParams::< let pp = PublicParams::<
G1, G1,
@ -296,6 +391,7 @@ mod tests {
&pp, &pp,
<G2 as Group>::Base::zero(), <G2 as Group>::Base::zero(),
<G1 as Group>::Base::zero(), <G1 as Group>::Base::zero(),
3,
); );
assert!(res.is_ok()); assert!(res.is_ok());
let recursive_snark = res.unwrap(); let recursive_snark = res.unwrap();

+ 1
- 1
src/nifs.rs

@ -12,7 +12,7 @@ use std::marker::PhantomData;
/// A SNARK that holds the proof of a step of an incremental computation /// A SNARK that holds the proof of a step of an incremental computation
pub struct NIFS<G: Group> { pub struct NIFS<G: Group> {
comm_T: CompressedCommitment<G::CompressedGroupElement>,
pub(crate) comm_T: CompressedCommitment<G::CompressedGroupElement>,
_p: PhantomData<G>, _p: PhantomData<G>,
} }

+ 21
- 0
src/r1cs.rs

@ -438,6 +438,14 @@ impl RelaxedR1CSWitness {
} }
} }
/// Initializes a new RelaxedR1CSWitness from an R1CSWitness
pub fn from_r1cs_witness(S: &R1CSShape<G>, witness: &R1CSWitness<G>) -> RelaxedR1CSWitness<G> {
RelaxedR1CSWitness {
W: witness.W.clone(),
E: vec![G::Scalar::zero(); S.num_cons],
}
}
/// Commits to the witness using the supplied generators /// Commits to the witness using the supplied generators
pub fn commit(&self, gens: &R1CSGens<G>) -> (Commitment<G>, Commitment<G>) { pub fn commit(&self, gens: &R1CSGens<G>) -> (Commitment<G>, Commitment<G>) {
(self.W.commit(&gens.gens_W), self.E.commit(&gens.gens_E)) (self.W.commit(&gens.gens_W), self.E.commit(&gens.gens_E))
@ -483,6 +491,19 @@ impl RelaxedR1CSInstance {
} }
} }
/// Initializes a new RelaxedR1CSInstance from an R1CSInstance
pub fn from_r1cs_instance(
gens: &R1CSGens<G>,
S: &R1CSShape<G>,
instance: &R1CSInstance<G>,
) -> RelaxedR1CSInstance<G> {
let mut r_instance = RelaxedR1CSInstance::default(gens, S);
r_instance.comm_W = instance.comm_W;
r_instance.u = G::Scalar::one();
r_instance.X = instance.X.clone();
r_instance
}
/// Folds an incoming RelaxedR1CSInstance into the current one /// Folds an incoming RelaxedR1CSInstance into the current one
pub fn fold( pub fn fold(
&self, &self,

+ 3
- 0
src/traits.rs

@ -143,6 +143,9 @@ pub trait StepCircuit {
cs: &mut CS, cs: &mut CS,
z: AllocatedNum<F>, z: AllocatedNum<F>,
) -> Result<AllocatedNum<F>, SynthesisError>; ) -> Result<AllocatedNum<F>, SynthesisError>;
/// Execute the circuit for a computation step and return output
fn compute(&self, z: &F) -> F;
} }
impl<F: PrimeField> AppendToTranscriptTrait for F { impl<F: PrimeField> AppendToTranscriptTrait for F {

Loading…
Cancel
Save