Browse Source

add hash of public params for Nova & HyperNova (#118)

- implement hash of public params for Nova & HyperNova
- abstract pp_hash computation for folding schemes
- add pp_hash to solidity contract generator to verify the decider proof
main
arnaucube 10 months ago
committed by GitHub
parent
commit
c17fcf56c6
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
33 changed files with 666 additions and 407 deletions
  1. +3
    -3
      examples/circom_full_flow.rs
  2. +4
    -3
      examples/external_inputs.rs
  3. +3
    -3
      examples/full_flow.rs
  4. +4
    -3
      examples/multi_inputs.rs
  5. +4
    -3
      examples/sha256.rs
  6. +1
    -0
      folding-schemes/Cargo.toml
  7. +16
    -5
      folding-schemes/src/arith/ccs.rs
  8. +15
    -0
      folding-schemes/src/arith/mod.rs
  9. +25
    -14
      folding-schemes/src/arith/r1cs.rs
  10. +2
    -1
      folding-schemes/src/commitment/mod.rs
  11. +25
    -7
      folding-schemes/src/folding/circuits/cyclefold.rs
  12. +2
    -2
      folding-schemes/src/folding/hypernova/cccs.rs
  13. +80
    -23
      folding-schemes/src/folding/hypernova/circuits.rs
  14. +6
    -4
      folding-schemes/src/folding/hypernova/lcccs.rs
  15. +86
    -47
      folding-schemes/src/folding/hypernova/mod.rs
  16. +5
    -2
      folding-schemes/src/folding/hypernova/nimfs.rs
  17. +5
    -2
      folding-schemes/src/folding/hypernova/utils.rs
  18. +37
    -9
      folding-schemes/src/folding/nova/circuits.rs
  19. +14
    -42
      folding-schemes/src/folding/nova/decider_eth.rs
  20. +39
    -37
      folding-schemes/src/folding/nova/decider_eth_circuit.rs
  21. +65
    -20
      folding-schemes/src/folding/nova/mod.rs
  22. +4
    -2
      folding-schemes/src/folding/nova/nifs.rs
  23. +16
    -30
      folding-schemes/src/folding/nova/serialize.rs
  24. +1
    -1
      folding-schemes/src/folding/nova/traits.rs
  25. +2
    -2
      folding-schemes/src/folding/protogalaxy/folding.rs
  26. +2
    -2
      folding-schemes/src/lib.rs
  27. +1
    -1
      folding-schemes/src/utils/mle.rs
  28. +68
    -0
      folding-schemes/src/utils/mod.rs
  29. +6
    -6
      solidity-verifiers/src/verifiers/g16.rs
  30. +3
    -3
      solidity-verifiers/src/verifiers/kzg.rs
  31. +3
    -1
      solidity-verifiers/src/verifiers/mod.rs
  32. +103
    -114
      solidity-verifiers/src/verifiers/nova_cyclefold.rs
  33. +16
    -15
      solidity-verifiers/templates/nova_cyclefold_decider.askama.sol

+ 3
- 3
examples/circom_full_flow.rs

@ -82,13 +82,13 @@ fn main() {
// prepare the Nova prover & verifier params
let nova_preprocess_params = PreprocessorParam::new(poseidon_config, f_circuit.clone());
let (fs_pp, fs_vp) = N::preprocess(&mut rng, &nova_preprocess_params).unwrap();
let nova_params = N::preprocess(&mut rng, &nova_preprocess_params).unwrap();
// initialize the folding scheme engine, in our case we use Nova
let mut nova = N::init(&fs_pp, f_circuit.clone(), z_0).unwrap();
let mut nova = N::init(nova_params.clone(), f_circuit.clone(), z_0).unwrap();
// prepare the Decider prover & verifier params
let (decider_pp, decider_vp) = D::preprocess(&mut rng, &(fs_pp, fs_vp), nova.clone()).unwrap();
let (decider_pp, decider_vp) = D::preprocess(&mut rng, &nova_params, nova.clone()).unwrap();
// run n steps of the folding iteration
for (i, external_inputs_at_step) in external_inputs.iter().enumerate() {

+ 4
- 3
examples/external_inputs.rs

@ -187,10 +187,11 @@ fn main() {
println!("Prepare Nova's ProverParams & VerifierParams");
let nova_preprocess_params = PreprocessorParam::new(poseidon_config, F_circuit.clone());
let (nova_pp, nova_vp) = N::preprocess(&mut rng, &nova_preprocess_params).unwrap();
let nova_params = N::preprocess(&mut rng, &nova_preprocess_params).unwrap();
println!("Initialize FoldingScheme");
let mut folding_scheme = N::init(&nova_pp, F_circuit, initial_state.clone()).unwrap();
let mut folding_scheme =
N::init(nova_params.clone(), F_circuit, initial_state.clone()).unwrap();
// compute a step of the IVC
for (i, external_inputs_at_step) in external_inputs.iter().enumerate() {
@ -210,7 +211,7 @@ fn main() {
println!("Run the Nova's IVC verifier");
N::verify(
nova_vp,
nova_params.1,
initial_state.clone(),
folding_scheme.state(), // latest state
Fr::from(num_steps as u32),

+ 3
- 3
examples/full_flow.rs

@ -99,13 +99,13 @@ fn main() {
// prepare the Nova prover & verifier params
let nova_preprocess_params = PreprocessorParam::new(poseidon_config.clone(), f_circuit);
let (fs_pp, fs_vp) = N::preprocess(&mut rng, &nova_preprocess_params).unwrap();
let nova_params = N::preprocess(&mut rng, &nova_preprocess_params).unwrap();
// initialize the folding scheme engine, in our case we use Nova
let mut nova = N::init(&fs_pp, f_circuit, z_0).unwrap();
let mut nova = N::init(nova_params.clone(), f_circuit, z_0).unwrap();
// prepare the Decider prover & verifier params
let (decider_pp, decider_vp) = D::preprocess(&mut rng, &(fs_pp, fs_vp), nova.clone()).unwrap();
let (decider_pp, decider_vp) = D::preprocess(&mut rng, &nova_params, nova.clone()).unwrap();
// run n steps of the folding iteration
for i in 0..n_steps {

+ 4
- 3
examples/multi_inputs.rs

@ -141,10 +141,11 @@ fn main() {
println!("Prepare Nova ProverParams & VerifierParams");
let nova_preprocess_params = PreprocessorParam::new(poseidon_config, F_circuit);
let (nova_pp, nova_vp) = N::preprocess(&mut rng, &nova_preprocess_params).unwrap();
let nova_params = N::preprocess(&mut rng, &nova_preprocess_params).unwrap();
println!("Initialize FoldingScheme");
let mut folding_scheme = N::init(&nova_pp, F_circuit, initial_state.clone()).unwrap();
let mut folding_scheme =
N::init(nova_params.clone(), F_circuit, initial_state.clone()).unwrap();
// compute a step of the IVC
for i in 0..num_steps {
@ -157,7 +158,7 @@ fn main() {
println!("Run the Nova's IVC verifier");
N::verify(
nova_vp,
nova_params.1,
initial_state.clone(),
folding_scheme.state(), // latest state
Fr::from(num_steps as u32),

+ 4
- 3
examples/sha256.rs

@ -126,10 +126,11 @@ fn main() {
println!("Prepare Nova ProverParams & VerifierParams");
let nova_preprocess_params = PreprocessorParam::new(poseidon_config, F_circuit);
let (nova_pp, nova_vp) = N::preprocess(&mut rng, &nova_preprocess_params).unwrap();
let nova_params = N::preprocess(&mut rng, &nova_preprocess_params).unwrap();
println!("Initialize FoldingScheme");
let mut folding_scheme = N::init(&nova_pp, F_circuit, initial_state.clone()).unwrap();
let mut folding_scheme =
N::init(nova_params.clone(), F_circuit, initial_state.clone()).unwrap();
// compute a step of the IVC
for i in 0..num_steps {
let start = Instant::now();
@ -141,7 +142,7 @@ fn main() {
println!("Run the Nova's IVC verifier");
N::verify(
nova_vp,
nova_params.1,
initial_state,
folding_scheme.state(), // latest state
Fr::from(num_steps as u32),

+ 1
- 0
folding-schemes/Cargo.toml

@ -23,6 +23,7 @@ num-integer = "0.1"
color-eyre = "=0.6.2"
ark-bn254 = {version="0.4.0"}
ark-groth16 = { version = "^0.4.0" }
sha3 = "0.10"
# tmp imports for espresso's sumcheck
espresso_subroutines = {git="https://github.com/EspressoSystems/hyperplonk", package="subroutines"}

folding-schemes/src/ccs/mod.rs → folding-schemes/src/arith/ccs.rs

@ -4,8 +4,7 @@ use ark_std::log2;
use crate::utils::vec::{hadamard, mat_vec_mul, vec_add, vec_scalar_mul, SparseMatrix};
use crate::Error;
pub mod r1cs;
use r1cs::R1CS;
use super::{r1cs::R1CS, Arith};
/// CCS represents the Customizable Constraint Systems structure defined in
/// the [CCS paper](https://eprint.iacr.org/2023/552)
@ -36,9 +35,9 @@ pub struct CCS {
pub c: Vec<F>,
}
impl<F: PrimeField> CCS<F> {
impl<F: PrimeField> Arith<F> for CCS<F> {
/// check that a CCS structure is satisfied by a z vector. Only for testing.
pub fn check_relation(&self, z: &[F]) -> Result<(), Error> {
fn check_relation(&self, z: &[F]) -> Result<(), Error> {
let mut result = vec![F::zero(); self.m];
for i in 0..self.q {
@ -67,6 +66,18 @@ impl CCS {
Ok(())
}
fn params_to_bytes(&self) -> Vec<u8> {
[
self.l.to_le_bytes(),
self.m.to_le_bytes(),
self.n.to_le_bytes(),
self.t.to_le_bytes(),
self.q.to_le_bytes(),
self.d.to_le_bytes(),
]
.concat()
}
}
impl<F: PrimeField> CCS<F> {
@ -102,7 +113,7 @@ impl CCS {
#[cfg(test)]
pub mod tests {
use super::*;
use crate::ccs::r1cs::tests::{get_test_r1cs, get_test_z as r1cs_get_test_z};
use crate::arith::r1cs::tests::{get_test_r1cs, get_test_z as r1cs_get_test_z};
use ark_ff::PrimeField;
use ark_pallas::Fr;

+ 15
- 0
folding-schemes/src/arith/mod.rs

@ -0,0 +1,15 @@
use ark_ff::PrimeField;
use crate::Error;
pub mod ccs;
pub mod r1cs;
pub trait Arith<F: PrimeField> {
/// Checks that the given Arith structure is satisfied by a z vector. Used only for testing.
fn check_relation(&self, z: &[F]) -> Result<(), Error>;
/// Returns the bytes that represent the parameters, that is, the matrices sizes, the amount of
/// public inputs, etc, without the matrices/polynomials values.
fn params_to_bytes(&self) -> Vec<u8>;
}

folding-schemes/src/ccs/r1cs.rs → folding-schemes/src/arith/r1cs.rs

@ -1,10 +1,11 @@
use ark_ff::PrimeField;
use ark_relations::r1cs::ConstraintSystem;
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
use ark_std::rand::Rng;
use super::Arith;
use crate::utils::vec::{hadamard, mat_vec_mul, vec_add, vec_scalar_mul, SparseMatrix};
use crate::Error;
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)]
pub struct R1CS<F: PrimeField> {
@ -14,6 +15,29 @@ pub struct R1CS {
pub C: SparseMatrix<F>,
}
impl<F: PrimeField> Arith<F> for R1CS<F> {
/// check that a R1CS structure is satisfied by a z vector. Only for testing.
fn check_relation(&self, z: &[F]) -> Result<(), Error> {
let Az = mat_vec_mul(&self.A, z)?;
let Bz = mat_vec_mul(&self.B, z)?;
let Cz = mat_vec_mul(&self.C, z)?;
let AzBz = hadamard(&Az, &Bz)?;
if AzBz != Cz {
return Err(Error::NotSatisfied);
}
Ok(())
}
fn params_to_bytes(&self) -> Vec<u8> {
[
self.l.to_le_bytes(),
self.A.n_rows.to_le_bytes(),
self.A.n_cols.to_le_bytes(),
]
.concat()
}
}
impl<F: PrimeField> R1CS<F> {
pub fn rand<R: Rng>(rng: &mut R, n_rows: usize, n_cols: usize) -> Self {
Self {
@ -29,19 +53,6 @@ impl R1CS {
(z[self.l + 1..].to_vec(), z[1..self.l + 1].to_vec())
}
/// check that a R1CS structure is satisfied by a z vector. Only for testing.
pub fn check_relation(&self, z: &[F]) -> Result<(), Error> {
let Az = mat_vec_mul(&self.A, z)?;
let Bz = mat_vec_mul(&self.B, z)?;
let Cz = mat_vec_mul(&self.C, z)?;
let AzBz = hadamard(&Az, &Bz)?;
if AzBz != Cz {
return Err(Error::NotSatisfied);
}
Ok(())
}
/// converts the R1CS instance into a RelaxedR1CS as described in
/// [Nova](https://eprint.iacr.org/2021/370.pdf) section 4.1.
pub fn relax(self) -> RelaxedR1CS<F> {

+ 2
- 1
folding-schemes/src/commitment/mod.rs

@ -1,4 +1,5 @@
use ark_ec::CurveGroup;
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
use ark_std::fmt::Debug;
use ark_std::rand::RngCore;
@ -13,7 +14,7 @@ pub mod pedersen;
/// commitment in hiding mode or not.
pub trait CommitmentScheme<C: CurveGroup, const H: bool = false>: Clone + Debug {
type ProverParams: Clone + Debug;
type VerifierParams: Clone + Debug;
type VerifierParams: Clone + Debug + CanonicalSerialize + CanonicalDeserialize;
type Proof: Clone + Debug;
type ProverChallenge: Clone + Debug;
type Challenge: Clone + Debug;

+ 25
- 7
folding-schemes/src/folding/circuits/cyclefold.rs

@ -30,7 +30,7 @@ use ark_std::{One, Zero};
use core::{borrow::Borrow, marker::PhantomData};
use super::{nonnative::uint::NonNativeUintVar, CF2};
use crate::ccs::r1cs::{extract_w_x, R1CS};
use crate::arith::r1cs::{extract_w_x, R1CS};
use crate::commitment::CommitmentScheme;
use crate::constants::N_BITS_RO;
use crate::folding::nova::{nifs::NIFS, CommittedInstance, Witness};
@ -127,9 +127,13 @@ where
pub fn hash(
self,
crh_params: &CRHParametersVar<CF2<C>>,
pp_hash: FpVar<CF2<C>>, // public params hash
) -> Result<(FpVar<CF2<C>>, Vec<FpVar<CF2<C>>>), SynthesisError> {
let U_vec = self.to_constraint_field()?;
Ok((CRHGadget::evaluate(crh_params, &U_vec)?, U_vec))
Ok((
CRHGadget::evaluate(crh_params, &[vec![pp_hash], U_vec.clone()].concat())?,
U_vec,
))
}
}
@ -252,6 +256,7 @@ where
{
pub fn get_challenge_native(
poseidon_config: &PoseidonConfig<C::BaseField>,
pp_hash: C::BaseField, // public params hash
U_i: CommittedInstance<C>,
u_i: CommittedInstance<C>,
cmT: C,
@ -276,7 +281,7 @@ where
// to save constraints for sponge.squeeze_bits in the corresponding circuit
let is_inf = U_cm_is_inf * CF2::<C>::from(8u8) + u_cm_is_inf.double() + cmT_is_inf;
let input = [U_vec, u_vec, vec![cmT_x, cmT_y, is_inf]].concat();
let input = [vec![pp_hash], U_vec, u_vec, vec![cmT_x, cmT_y, is_inf]].concat();
sponge.absorb(&input);
let bits = sponge.squeeze_bits(N_BITS_RO);
Ok(bits)
@ -286,6 +291,7 @@ where
pub fn get_challenge_gadget(
cs: ConstraintSystemRef<C::BaseField>,
poseidon_config: &PoseidonConfig<C::BaseField>,
pp_hash: FpVar<C::BaseField>, // public params hash
mut U_i_vec: Vec<FpVar<C::BaseField>>,
u_i: CycleFoldCommittedInstanceVar<C, GC>,
cmT: GC,
@ -303,7 +309,7 @@ where
// to save constraints for sponge.squeeze_bits
let is_inf = U_cm_is_inf * CF2::<C>::from(8u8) + u_cm_is_inf.double()? + cmT_is_inf;
let input = [U_i_vec, u_i_vec, cmT_vec, vec![is_inf]].concat();
let input = [vec![pp_hash], U_i_vec, u_i_vec, cmT_vec, vec![is_inf]].concat();
sponge.absorb(&input)?;
let bits = sponge.squeeze_bits(N_BITS_RO)?;
Ok(bits)
@ -372,13 +378,15 @@ where
}
}
/// Folds the given cyclefold circuit and its instances. This method is isolated from any folding
/// Folds the given cyclefold circuit and its instances. This method is abstracted from any folding
/// scheme struct because it is used both by Nova & HyperNova's CycleFold.
#[allow(clippy::type_complexity)]
#[allow(clippy::too_many_arguments)]
pub fn fold_cyclefold_circuit<C1, GC1, C2, GC2, FC, CS1, CS2>(
poseidon_config: &PoseidonConfig<C1::ScalarField>,
cf_r1cs: R1CS<C2::ScalarField>,
cf_cs_params: CS2::ProverParams,
pp_hash: C1::ScalarField, // public params hash
cf_W_i: Witness<C2>, // witness of the running instance
cf_U_i: CommittedInstance<C2>, // running instance
cf_u_i_x: Vec<C2::ScalarField>,
@ -438,6 +446,7 @@ where
let cf_r_bits = CycleFoldChallengeGadget::<C2, GC2>::get_challenge_native(
poseidon_config,
pp_hash,
cf_U_i.clone(),
cf_u_i.clone(),
cf_cmT,
@ -594,8 +603,10 @@ pub mod tests {
let cmT = Projective::rand(&mut rng);
// compute the challenge natively
let pp_hash = Fq::from(42u32); // only for test
let r_bits = CycleFoldChallengeGadget::<Projective, GVar>::get_challenge_native(
&poseidon_config,
pp_hash,
U_i.clone(),
u_i.clone(),
cmT,
@ -615,9 +626,11 @@ pub mod tests {
.unwrap();
let cmTVar = GVar::new_witness(cs.clone(), || Ok(cmT)).unwrap();
let pp_hashVar = FpVar::<Fq>::new_witness(cs.clone(), || Ok(pp_hash)).unwrap();
let r_bitsVar = CycleFoldChallengeGadget::<Projective, GVar>::get_challenge_gadget(
cs.clone(),
&poseidon_config,
pp_hashVar,
U_iVar.to_constraint_field().unwrap(),
u_iVar,
cmTVar,
@ -645,7 +658,8 @@ pub mod tests {
.take(CF_IO_LEN)
.collect(),
};
let h = U_i.hash_cyclefold(&poseidon_config).unwrap();
let pp_hash = Fq::from(42u32); // only for test
let h = U_i.hash_cyclefold(&poseidon_config, pp_hash).unwrap();
let cs = ConstraintSystem::<Fq>::new_ref();
let U_iVar =
@ -653,8 +667,12 @@ pub mod tests {
Ok(U_i.clone())
})
.unwrap();
let pp_hashVar = FpVar::<Fq>::new_witness(cs.clone(), || Ok(pp_hash)).unwrap();
let (hVar, _) = U_iVar
.hash(&CRHParametersVar::new_constant(cs.clone(), poseidon_config).unwrap())
.hash(
&CRHParametersVar::new_constant(cs.clone(), poseidon_config).unwrap(),
pp_hashVar,
)
.unwrap();
hVar.enforce_equal(&FpVar::new_witness(cs.clone(), || Ok(h)).unwrap())
.unwrap();

+ 2
- 2
folding-schemes/src/folding/hypernova/cccs.rs

@ -7,7 +7,7 @@ use std::sync::Arc;
use ark_std::rand::Rng;
use super::Witness;
use crate::ccs::CCS;
use crate::arith::{ccs::CCS, Arith};
use crate::commitment::CommitmentScheme;
use crate::utils::mle::dense_vec_to_dense_mle;
use crate::utils::vec::mat_vec_mul;
@ -125,7 +125,7 @@ pub mod tests {
use ark_std::UniformRand;
use super::*;
use crate::ccs::tests::{get_test_ccs, get_test_z};
use crate::arith::ccs::tests::{get_test_ccs, get_test_z};
use crate::utils::hypercube::BooleanHypercube;
/// Do some sanity checks on q(x). It's a multivariable polynomial and it should evaluate to zero inside the

+ 80
- 23
folding-schemes/src/folding/hypernova/circuits.rs

@ -44,7 +44,7 @@ use crate::frontend::FCircuit;
use crate::utils::virtual_polynomial::VPAuxInfo;
use crate::Error;
use crate::{
ccs::{r1cs::extract_r1cs, CCS},
arith::{ccs::CCS, r1cs::extract_r1cs},
transcript::{
poseidon::{PoseidonTranscript, PoseidonTranscriptVar},
Transcript, TranscriptVar,
@ -143,6 +143,7 @@ where
pub fn hash(
self,
crh_params: &CRHParametersVar<CF1<C>>,
pp_hash: FpVar<CF1<C>>,
i: FpVar<CF1<C>>,
z_0: Vec<FpVar<CF1<C>>>,
z_i: Vec<FpVar<CF1<C>>>,
@ -155,7 +156,7 @@ where
self.v,
]
.concat();
let input = [vec![i], z_0, z_i, U_vec.clone()].concat();
let input = [vec![pp_hash, i], z_0, z_i, U_vec.clone()].concat();
Ok((
CRHGadget::<C::ScalarField>::evaluate(crh_params, &input)?,
U_vec,
@ -455,6 +456,7 @@ pub struct AugmentedFCircuit<
pub _gc2: PhantomData<GC2>,
pub poseidon_config: PoseidonConfig<CF1<C1>>,
pub ccs: CCS<C1::ScalarField>, // CCS of the AugmentedFCircuit
pub pp_hash: Option<CF1<C1>>,
pub i: Option<CF1<C1>>,
pub i_usize: Option<usize>,
pub z_0: Option<Vec<C1::ScalarField>>,
@ -497,6 +499,7 @@ where
_gc2: PhantomData,
poseidon_config: poseidon_config.clone(),
ccs,
pp_hash: None,
i: None,
i_usize: None,
z_0: None,
@ -559,6 +562,7 @@ where
let mut transcript_p: PoseidonTranscript<C1> =
PoseidonTranscript::<C1>::new(&self.poseidon_config.clone());
// since this is only for the number of constraints, no need to absorb the pp_hash here
let (nimfs_proof, U_i1, _, _) = NIMFS::<C1, PoseidonTranscript<C1>>::prove(
&mut transcript_p,
&ccs,
@ -573,6 +577,7 @@ where
_gc2: PhantomData,
poseidon_config: self.poseidon_config.clone(),
ccs: ccs.clone(),
pp_hash: Some(C1::ScalarField::zero()),
i: Some(C1::ScalarField::zero()),
i_usize: Some(0),
z_0: Some(z_0.clone()),
@ -624,6 +629,9 @@ where
for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
{
fn generate_constraints(self, cs: ConstraintSystemRef<CF1<C1>>) -> Result<(), SynthesisError> {
let pp_hash = FpVar::<CF1<C1>>::new_witness(cs.clone(), || {
Ok(self.pp_hash.unwrap_or_else(CF1::<C1>::zero))
})?;
let i = FpVar::<CF1<C1>>::new_witness(cs.clone(), || {
Ok(self.i.unwrap_or_else(CF1::<C1>::zero))
})?;
@ -680,11 +688,15 @@ where
// Primary Part
// P.1. Compute u_i.x
// u_i.x[0] = H(i, z_0, z_i, U_i)
let (u_i_x, _) = U_i
.clone()
.hash(&crh_params, i.clone(), z_0.clone(), z_i.clone())?;
let (u_i_x, _) = U_i.clone().hash(
&crh_params,
pp_hash.clone(),
i.clone(),
z_0.clone(),
z_i.clone(),
)?;
// u_i.x[1] = H(cf_U_i)
let (cf_u_i_x, cf_U_i_vec) = cf_U_i.clone().hash(&crh_params)?;
let (cf_u_i_x, cf_U_i_vec) = cf_U_i.clone().hash(&crh_params, pp_hash.clone())?;
// P.2. Construct u_i
let u_i = CCCSVar::<C1> {
@ -700,8 +712,9 @@ where
// Notice that NIMFSGadget::fold_committed_instance does not fold C. We set `U_i1.C` to
// unconstrained witnesses `U_i1_C` respectively. Its correctness will be checked on the
// other curve.
let transcript =
let mut transcript =
PoseidonTranscriptVar::<C1::ScalarField>::new(cs.clone(), &self.poseidon_config);
transcript.absorb(pp_hash.clone())?;
let (mut U_i1, rho_bits) = NIMFSGadget::<C1>::verify(
cs.clone(),
&self.ccs.clone(),
@ -716,12 +729,14 @@ where
// P.4.a compute and check the first output of F'
let (u_i1_x, _) = U_i1.clone().hash(
&crh_params,
pp_hash.clone(),
i + FpVar::<CF1<C1>>::one(),
z_0.clone(),
z_i1.clone(),
)?;
let (u_i1_x_base, _) = LCCCSVar::new_constant(cs.clone(), U_dummy)?.hash(
&crh_params,
pp_hash.clone(),
FpVar::<CF1<C1>>::one(),
z_0.clone(),
z_i1.clone(),
@ -763,6 +778,7 @@ where
let cf_r_bits = CycleFoldChallengeGadget::<C2, GC2>::get_challenge_gadget(
cs.clone(),
&self.poseidon_config,
pp_hash.clone(),
cf_U_i_vec,
cf_u_i.clone(),
cf_cmT.clone(),
@ -786,10 +802,10 @@ where
// P.4.b compute and check the second output of F'
// Base case: u_{i+1}.x[1] == H(cf_U_{\bot})
// Non-base case: u_{i+1}.x[1] == H(cf_U_{i+1})
let (cf_u_i1_x, _) = cf_U_i1.clone().hash(&crh_params)?;
let (cf_u_i1_x, _) = cf_U_i1.clone().hash(&crh_params, pp_hash.clone())?;
let (cf_u_i1_x_base, _) =
CycleFoldCommittedInstanceVar::new_constant(cs.clone(), cf_u_dummy)?
.hash(&crh_params)?;
.hash(&crh_params, pp_hash)?;
let cf_x = FpVar::new_input(cs.clone(), || {
Ok(self.cf_x.unwrap_or(cf_u_i1_x_base.value()?))
})?;
@ -810,10 +826,12 @@ mod tests {
use super::*;
use crate::{
ccs::{
arith::{
ccs::{
tests::{get_test_ccs, get_test_z},
CCS,
},
r1cs::extract_w_x,
tests::{get_test_ccs, get_test_z},
CCS,
},
commitment::{pedersen::Pedersen, CommitmentScheme},
folding::{
@ -1049,6 +1067,7 @@ mod tests {
let (pedersen_params, _) =
Pedersen::<Projective>::setup(&mut rng, ccs.n - ccs.l - 1).unwrap();
let pp_hash = Fr::from(42u32); // only for test
let i = Fr::from(3_u32);
let z_0 = vec![Fr::from(3_u32)];
@ -1058,19 +1077,26 @@ mod tests {
.unwrap();
let h = lcccs
.clone()
.hash(&poseidon_config, i, z_0.clone(), z_i.clone())
.hash(&poseidon_config, pp_hash, i, z_0.clone(), z_i.clone())
.unwrap();
let cs = ConstraintSystem::<Fr>::new_ref();
let crh_params = CRHParametersVar::<Fr>::new_constant(cs.clone(), poseidon_config).unwrap();
let pp_hashVar = FpVar::<Fr>::new_witness(cs.clone(), || Ok(pp_hash)).unwrap();
let iVar = FpVar::<Fr>::new_witness(cs.clone(), || Ok(i)).unwrap();
let z_0Var = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(z_0.clone())).unwrap();
let z_iVar = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(z_i.clone())).unwrap();
let lcccsVar = LCCCSVar::<Projective>::new_witness(cs.clone(), || Ok(lcccs)).unwrap();
let (hVar, _) = lcccsVar
.clone()
.hash(&crh_params, iVar.clone(), z_0Var.clone(), z_iVar.clone())
.hash(
&crh_params,
pp_hashVar,
iVar.clone(),
z_0Var.clone(),
z_iVar.clone(),
)
.unwrap();
assert!(cs.is_satisfied().unwrap());
@ -1112,6 +1138,9 @@ mod tests {
let (cf_pedersen_params, _) =
Pedersen::<Projective2>::setup(&mut rng, cf_r1cs.A.n_cols - cf_r1cs.l - 1).unwrap();
// public params hash
let pp_hash = Fr::from(42u32); // only for test
// first step
let z_0 = vec![Fr::from(3_u32)];
let mut z_i = z_0.clone();
@ -1132,9 +1161,15 @@ mod tests {
let mut cf_W_i = cf_W_dummy.clone();
let mut cf_U_i = cf_U_dummy.clone();
u_i.x = vec![
U_i.hash(&poseidon_config, Fr::zero(), z_0.clone(), z_i.clone())
.unwrap(),
cf_U_i.hash_cyclefold(&poseidon_config).unwrap(),
U_i.hash(
&poseidon_config,
pp_hash,
Fr::zero(),
z_0.clone(),
z_i.clone(),
)
.unwrap(),
cf_U_i.hash_cyclefold(&poseidon_config, pp_hash).unwrap(),
];
let n_steps: usize = 4;
@ -1151,12 +1186,18 @@ mod tests {
U_i1 = LCCCS::dummy(ccs.l, ccs.t, ccs.s);
let u_i1_x = U_i1
.hash(&poseidon_config, Fr::one(), z_0.clone(), z_i1.clone())
.hash(
&poseidon_config,
pp_hash,
Fr::one(),
z_0.clone(),
z_i1.clone(),
)
.unwrap();
// hash the initial (dummy) CycleFold instance, which is used as the 2nd public
// input in the AugmentedFCircuit
let cf_u_i1_x = cf_U_i.hash_cyclefold(&poseidon_config).unwrap();
let cf_u_i1_x = cf_U_i.hash_cyclefold(&poseidon_config, pp_hash).unwrap();
augmented_f_circuit =
AugmentedFCircuit::<Projective, Projective2, GVar2, CubicFCircuit<Fr>> {
@ -1164,6 +1205,7 @@ mod tests {
_gc2: PhantomData,
poseidon_config: poseidon_config.clone(),
ccs: ccs.clone(),
pp_hash: Some(pp_hash),
i: Some(Fr::zero()),
i_usize: Some(0),
z_0: Some(z_0.clone()),
@ -1185,6 +1227,7 @@ mod tests {
} else {
let mut transcript_p: PoseidonTranscript<Projective> =
PoseidonTranscript::<Projective>::new(&poseidon_config.clone());
transcript_p.absorb(&pp_hash);
let (rho_bits, nimfs_proof);
(nimfs_proof, U_i1, W_i1, rho_bits) =
NIMFS::<Projective, PoseidonTranscript<Projective>>::prove(
@ -1201,7 +1244,13 @@ mod tests {
U_i1.check_relation(&ccs, &W_i1).unwrap();
let u_i1_x = U_i1
.hash(&poseidon_config, iFr + Fr::one(), z_0.clone(), z_i1.clone())
.hash(
&poseidon_config,
pp_hash,
iFr + Fr::one(),
z_0.clone(),
z_i1.clone(),
)
.unwrap();
let rho_Fq = Fq::from_bigint(BigInteger::from_bits_le(&rho_bits)).unwrap();
@ -1236,6 +1285,7 @@ mod tests {
&poseidon_config,
cf_r1cs.clone(),
cf_pedersen_params.clone(),
pp_hash,
cf_W_i.clone(), // CycleFold running instance witness
cf_U_i.clone(), // CycleFold running instance
cf_u_i_x, // CycleFold incoming instance
@ -1245,7 +1295,7 @@ mod tests {
// hash the CycleFold folded instance, which is used as the 2nd public input in the
// AugmentedFCircuit
let cf_u_i1_x = cf_U_i1.hash_cyclefold(&poseidon_config).unwrap();
let cf_u_i1_x = cf_U_i1.hash_cyclefold(&poseidon_config, pp_hash).unwrap();
augmented_f_circuit =
AugmentedFCircuit::<Projective, Projective2, GVar2, CubicFCircuit<Fr>> {
@ -1253,6 +1303,7 @@ mod tests {
_gc2: PhantomData,
poseidon_config: poseidon_config.clone(),
ccs: ccs.clone(),
pp_hash: Some(pp_hash),
i: Some(iFr),
i_usize: Some(i),
z_0: Some(z_0.clone()),
@ -1296,9 +1347,15 @@ mod tests {
assert_eq!(u_i.x[0], augmented_f_circuit.x.unwrap());
assert_eq!(u_i.x[1], augmented_f_circuit.cf_x.unwrap());
let expected_u_i1_x = U_i1
.hash(&poseidon_config, iFr + Fr::one(), z_0.clone(), z_i1.clone())
.hash(
&poseidon_config,
pp_hash,
iFr + Fr::one(),
z_0.clone(),
z_i1.clone(),
)
.unwrap();
let expected_cf_U_i1_x = cf_U_i.hash_cyclefold(&poseidon_config).unwrap();
let expected_cf_U_i1_x = cf_U_i.hash_cyclefold(&poseidon_config, pp_hash).unwrap();
// u_i is already u_i1 at this point, check that has the expected value at x[0]
assert_eq!(u_i.x[0], expected_u_i1_x);
assert_eq!(u_i.x[1], expected_cf_U_i1_x);

+ 6
- 4
folding-schemes/src/folding/hypernova/lcccs.rs

@ -10,7 +10,7 @@ use ark_std::rand::Rng;
use ark_std::Zero;
use super::Witness;
use crate::ccs::CCS;
use crate::arith::ccs::CCS;
use crate::commitment::CommitmentScheme;
use crate::folding::circuits::nonnative::affine::nonnative_affine_to_field_elements;
use crate::utils::mle::dense_vec_to_dense_mle;
@ -129,6 +129,7 @@ where
pub fn hash(
&self,
poseidon_config: &PoseidonConfig<C::ScalarField>,
pp_hash: C::ScalarField,
i: C::ScalarField,
z_0: Vec<C::ScalarField>,
z_i: Vec<C::ScalarField>,
@ -138,7 +139,7 @@ where
CRH::<C::ScalarField>::evaluate(
poseidon_config,
vec![
vec![i],
vec![pp_hash, i],
z_0,
z_i,
C_x,
@ -164,9 +165,10 @@ pub mod tests {
use std::sync::Arc;
use super::*;
use crate::ccs::{
use crate::arith::{
ccs::tests::{get_test_ccs, get_test_z},
r1cs::R1CS,
tests::{get_test_ccs, get_test_z},
Arith,
};
use crate::commitment::pedersen::Pedersen;
use crate::utils::hypercube::BooleanHypercube;

+ 86
- 47
folding-schemes/src/folding/hypernova/mod.rs

@ -24,16 +24,17 @@ use crate::folding::circuits::{
CF2,
};
use crate::folding::nova::{
get_r1cs_from_cs, traits::NovaR1CS, CommittedInstance, Witness as NovaWitness,
get_r1cs_from_cs, traits::NovaR1CS, CommittedInstance, PreprocessorParam,
Witness as NovaWitness,
};
use crate::frontend::FCircuit;
use crate::utils::get_cm_coordinates;
use crate::utils::{get_cm_coordinates, pp_hash};
use crate::Error;
use crate::FoldingScheme;
use crate::{
ccs::{
arith::{
ccs::CCS,
r1cs::{extract_w_x, R1CS},
CCS,
},
transcript::{poseidon::PoseidonTranscript, Transcript},
};
@ -56,22 +57,6 @@ impl Witness {
}
}
#[derive(Debug, Clone)]
pub struct PreprocessorParam<C1, C2, FC, CS1, CS2>
where
C1: CurveGroup,
C2: CurveGroup,
FC: FCircuit<C1::ScalarField>,
CS1: CommitmentScheme<C1>,
CS2: CommitmentScheme<C2>,
{
pub poseidon_config: PoseidonConfig<C1::ScalarField>,
pub F: FC,
// cs_params & cf_cs_params: if not provided, will be generated at the preprocess method
pub cs_params: Option<CS1::ProverParams>,
pub cf_cs_params: Option<CS2::ProverParams>,
}
#[derive(Debug, Clone)]
pub struct ProverParams<C1, C2, CS1, CS2>
where
@ -97,8 +82,27 @@ pub struct VerifierParams<
pub poseidon_config: PoseidonConfig<C1::ScalarField>,
pub ccs: CCS<C1::ScalarField>,
pub cf_r1cs: R1CS<C2::ScalarField>,
pub cs_params: CS1::ProverParams,
pub cf_cs_params: CS2::ProverParams,
pub cs_vp: CS1::VerifierParams,
pub cf_cs_vp: CS2::VerifierParams,
}
impl<C1, C2, CS1, CS2> VerifierParams<C1, C2, CS1, CS2>
where
C1: CurveGroup,
C2: CurveGroup,
CS1: CommitmentScheme<C1>,
CS2: CommitmentScheme<C2>,
{
/// returns the hash of the public parameters of HyperNova
pub fn pp_hash(&self) -> Result<C1::ScalarField, Error> {
pp_hash::<C1, C2, CS1, CS2>(
&self.ccs,
&self.cf_r1cs,
&self.cs_vp,
&self.cf_cs_vp,
&self.poseidon_config,
)
}
}
/// Implements HyperNova+CycleFold's IVC, described in
@ -130,6 +134,8 @@ where
pub cf_cs_params: CS2::ProverParams,
/// F circuit, the circuit that is being folded
pub F: FC,
/// public params hash
pub pp_hash: C1::ScalarField,
pub i: C1::ScalarField,
/// initial state
pub z_0: Vec<C1::ScalarField>,
@ -185,35 +191,49 @@ where
let cf_circuit = CycleFoldCircuit::<C1, GC1>::empty();
let cf_r1cs = get_r1cs_from_cs::<C2::ScalarField>(cf_circuit)?;
// if cs_params & cf_cs_params exist, use them, if not, generate new ones
let cs_params: CS1::ProverParams;
let cf_cs_params: CS2::ProverParams;
if prep_param.cs_params.is_some() && prep_param.cf_cs_params.is_some() {
cs_params = prep_param.clone().cs_params.unwrap();
cf_cs_params = prep_param.clone().cf_cs_params.unwrap();
// if cs params exist, use them, if not, generate new ones
let cs_pp: CS1::ProverParams;
let cs_vp: CS1::VerifierParams;
let cf_cs_pp: CS2::ProverParams;
let cf_cs_vp: CS2::VerifierParams;
if prep_param.cs_pp.is_some()
&& prep_param.cf_cs_pp.is_some()
&& prep_param.cs_vp.is_some()
&& prep_param.cf_cs_vp.is_some()
{
cs_pp = prep_param.clone().cs_pp.unwrap();
cs_vp = prep_param.clone().cs_vp.unwrap();
cf_cs_pp = prep_param.clone().cf_cs_pp.unwrap();
cf_cs_vp = prep_param.clone().cf_cs_vp.unwrap();
} else {
(cs_params, _) = CS1::setup(&mut rng, ccs.n - ccs.l - 1).unwrap();
(cf_cs_params, _) = CS2::setup(&mut rng, cf_r1cs.A.n_cols - cf_r1cs.l - 1).unwrap();
(cs_pp, cs_vp) = CS1::setup(&mut rng, ccs.n - ccs.l - 1)?;
(cf_cs_pp, cf_cs_vp) = CS2::setup(&mut rng, cf_r1cs.A.n_cols - cf_r1cs.l - 1)?;
}
let pp = ProverParams::<C1, C2, CS1, CS2> {
poseidon_config: prep_param.poseidon_config.clone(),
cs_params: cs_params.clone(),
cf_cs_params: cf_cs_params.clone(),
cs_params: cs_pp.clone(),
cf_cs_params: cf_cs_pp.clone(),
ccs: Some(ccs.clone()),
};
let vp = VerifierParams::<C1, C2, CS1, CS2> {
poseidon_config: prep_param.poseidon_config.clone(),
ccs,
cf_r1cs,
cs_params: cs_params.clone(),
cf_cs_params: cf_cs_params.clone(),
cs_vp: cs_vp.clone(),
cf_cs_vp: cf_cs_vp.clone(),
};
Ok((pp, vp))
}
/// Initializes the HyperNova+CycleFold's IVC for the given parameters and initial state `z_0`.
fn init(pp: &Self::ProverParam, F: FC, z_0: Vec<C1::ScalarField>) -> Result<Self, Error> {
fn init(
params: (Self::ProverParam, Self::VerifierParam),
F: FC,
z_0: Vec<C1::ScalarField>,
) -> Result<Self, Error> {
let (pp, vp) = params;
// prepare the HyperNova's AugmentedFCircuit and CycleFold's circuits and obtain its CCS
// and R1CS respectively
let augmented_f_circuit = AugmentedFCircuit::<C1, C2, GC2, FC>::empty(
@ -226,6 +246,9 @@ where
let cf_circuit = CycleFoldCircuit::<C1, GC1>::empty();
let cf_r1cs = get_r1cs_from_cs::<C2::ScalarField>(cf_circuit)?;
// compute the public params hash
let pp_hash = vp.pp_hash()?;
// setup the dummy instances
let W_dummy = Witness::<C1::ScalarField>::dummy(&ccs);
let U_dummy = LCCCS::<C1>::dummy(ccs.l, ccs.t, ccs.s);
@ -236,11 +259,12 @@ where
u_dummy.x = vec![
U_dummy.hash(
&pp.poseidon_config,
pp_hash,
C1::ScalarField::zero(),
z_0.clone(),
z_0.clone(),
)?,
cf_U_dummy.hash_cyclefold(&pp.poseidon_config)?,
cf_U_dummy.hash_cyclefold(&pp.poseidon_config, pp_hash)?,
];
// W_dummy=W_0 is a 'dummy witness', all zeroes, but with the size corresponding to the
@ -255,6 +279,7 @@ where
cs_params: pp.cs_params.clone(),
cf_cs_params: pp.cf_cs_params.clone(),
F,
pp_hash,
i: C1::ScalarField::zero(),
z_0: z_0.clone(),
z_i: z_0,
@ -315,6 +340,7 @@ where
let u_i1_x = U_i1.hash(
&self.poseidon_config,
self.pp_hash,
C1::ScalarField::one(),
self.z_0.clone(),
z_i1.clone(),
@ -322,13 +348,16 @@ where
// hash the initial (dummy) CycleFold instance, which is used as the 2nd public
// input in the AugmentedFCircuit
cf_u_i1_x = self.cf_U_i.hash_cyclefold(&self.poseidon_config)?;
cf_u_i1_x = self
.cf_U_i
.hash_cyclefold(&self.poseidon_config, self.pp_hash)?;
augmented_f_circuit = AugmentedFCircuit::<C1, C2, GC2, FC> {
_c2: PhantomData,
_gc2: PhantomData,
poseidon_config: self.poseidon_config.clone(),
ccs: self.ccs.clone(),
pp_hash: Some(self.pp_hash),
i: Some(C1::ScalarField::zero()),
i_usize: Some(0),
z_0: Some(self.z_0.clone()),
@ -350,6 +379,7 @@ where
} else {
let mut transcript_p: PoseidonTranscript<C1> =
PoseidonTranscript::<C1>::new(&self.poseidon_config);
transcript_p.absorb(&self.pp_hash);
let (rho_bits, nimfs_proof);
(nimfs_proof, U_i1, W_i1, rho_bits) = NIMFS::<C1, PoseidonTranscript<C1>>::prove(
&mut transcript_p,
@ -366,6 +396,7 @@ where
let u_i1_x = U_i1.hash(
&self.poseidon_config,
self.pp_hash,
self.i + C1::ScalarField::one(),
self.z_0.clone(),
z_i1.clone(),
@ -397,19 +428,21 @@ where
&self.poseidon_config,
self.cf_r1cs.clone(),
self.cf_cs_params.clone(),
self.pp_hash,
self.cf_W_i.clone(), // CycleFold running instance witness
self.cf_U_i.clone(), // CycleFold running instance
cf_u_i_x,
cf_circuit,
)?;
cf_u_i1_x = cf_U_i1.hash_cyclefold(&self.poseidon_config)?;
cf_u_i1_x = cf_U_i1.hash_cyclefold(&self.poseidon_config, self.pp_hash)?;
augmented_f_circuit = AugmentedFCircuit::<C1, C2, GC2, FC> {
_c2: PhantomData,
_gc2: PhantomData,
poseidon_config: self.poseidon_config.clone(),
ccs: self.ccs.clone(),
pp_hash: Some(self.pp_hash),
i: Some(self.i),
i_usize: Some(i_usize),
z_0: Some(self.z_0.clone()),
@ -516,14 +549,16 @@ where
return Err(Error::IVCVerificationFail);
}
let pp_hash = vp.pp_hash()?;
// check that u_i's output points to the running instance
// u_i.X[0] == H(i, z_0, z_i, U_i)
let expected_u_i_x = U_i.hash(&vp.poseidon_config, num_steps, z_0, z_i.clone())?;
let expected_u_i_x = U_i.hash(&vp.poseidon_config, pp_hash, num_steps, z_0, z_i.clone())?;
if expected_u_i_x != u_i.x[0] {
return Err(Error::IVCVerificationFail);
}
// u_i.X[1] == H(cf_U_i)
let expected_cf_u_i_x = cf_U_i.hash_cyclefold(&vp.poseidon_config)?;
let expected_cf_u_i_x = cf_U_i.hash_cyclefold(&vp.poseidon_config, pp_hash)?;
if expected_cf_u_i_x != u_i.x[1] {
return Err(Error::IVCVerificationFail);
}
@ -578,16 +613,20 @@ mod tests {
type HN<CS1, CS2> =
HyperNova<Projective, GVar, Projective2, GVar2, CubicFCircuit<Fr>, CS1, CS2>;
let prep_param = PreprocessorParam::<Projective, Projective2, CubicFCircuit<Fr>, CS1, CS2> {
poseidon_config,
F: F_circuit,
cs_params: None,
cf_cs_params: None,
};
let prep_param =
PreprocessorParam::<Projective, Projective2, CubicFCircuit<Fr>, CS1, CS2>::new(
poseidon_config.clone(),
F_circuit,
);
let (prover_params, verifier_params) = HN::preprocess(&mut rng, &prep_param).unwrap();
let z_0 = vec![Fr::from(3_u32)];
let mut hypernova = HN::init(&prover_params, F_circuit, z_0.clone()).unwrap();
let mut hypernova = HN::init(
(prover_params, verifier_params.clone()),
F_circuit,
z_0.clone(),
)
.unwrap();
let num_steps: usize = 3;
for _ in 0..num_steps {

+ 5
- 2
folding-schemes/src/folding/hypernova/nimfs.rs

@ -11,7 +11,7 @@ use super::{
utils::{compute_c, compute_g, compute_sigmas_thetas},
Witness,
};
use crate::ccs::CCS;
use crate::arith::ccs::CCS;
use crate::constants::N_BITS_RO;
use crate::folding::circuits::nonnative::affine::nonnative_affine_to_field_elements;
use crate::transcript::Transcript;
@ -408,7 +408,10 @@ where
#[cfg(test)]
pub mod tests {
use super::*;
use crate::ccs::tests::{get_test_ccs, get_test_z};
use crate::arith::{
ccs::tests::{get_test_ccs, get_test_z},
Arith,
};
use crate::transcript::poseidon::poseidon_canonical_config;
use crate::transcript::poseidon::PoseidonTranscript;
use ark_std::test_rng;

+ 5
- 2
folding-schemes/src/folding/hypernova/utils.rs

@ -7,7 +7,7 @@ use std::sync::Arc;
use super::lcccs::LCCCS;
use super::nimfs::SigmasThetas;
use crate::ccs::CCS;
use crate::arith::ccs::CCS;
use crate::utils::mle::dense_vec_to_dense_mle;
use crate::utils::vec::mat_vec_mul;
use crate::utils::virtual_polynomial::{build_eq_x_r_vec, eq_eval, VirtualPolynomial};
@ -167,7 +167,10 @@ pub mod tests {
use ark_std::Zero;
use super::*;
use crate::ccs::tests::{get_test_ccs, get_test_z};
use crate::arith::{
ccs::tests::{get_test_ccs, get_test_z},
Arith,
};
use crate::commitment::{pedersen::Pedersen, CommitmentScheme};
use crate::folding::hypernova::lcccs::tests::compute_Ls;
use crate::utils::hypercube::BooleanHypercube;

+ 37
- 9
folding-schemes/src/folding/nova/circuits.rs

@ -94,6 +94,7 @@ where
pub fn hash(
self,
crh_params: &CRHParametersVar<CF1<C>>,
pp_hash: FpVar<CF1<C>>,
i: FpVar<CF1<C>>,
z_0: Vec<FpVar<CF1<C>>>,
z_i: Vec<FpVar<CF1<C>>>,
@ -105,7 +106,7 @@ where
self.cmW.to_constraint_field()?,
]
.concat();
let input = [vec![i], z_0, z_i, U_vec.clone()].concat();
let input = [vec![pp_hash, i], z_0, z_i, U_vec.clone()].concat();
Ok((
CRHGadget::<C::ScalarField>::evaluate(crh_params, &input)?,
U_vec,
@ -175,6 +176,7 @@ where
{
pub fn get_challenge_native(
poseidon_config: &PoseidonConfig<C::ScalarField>,
pp_hash: C::ScalarField, // public params hash
U_i: CommittedInstance<C>,
u_i: CommittedInstance<C>,
cmT: C,
@ -187,6 +189,7 @@ where
let mut sponge = PoseidonSponge::<C::ScalarField>::new(poseidon_config);
let input = vec![
vec![pp_hash],
vec![U_i.u],
U_i.x.clone(),
U_cmE_x,
@ -212,6 +215,7 @@ where
pub fn get_challenge_gadget(
cs: ConstraintSystemRef<C::ScalarField>,
poseidon_config: &PoseidonConfig<C::ScalarField>,
pp_hash: FpVar<CF1<C>>, // public params hash
U_i_vec: Vec<FpVar<CF1<C>>>, // apready processed input, so we don't have to recompute these values
u_i: CommittedInstanceVar<C>,
cmT: NonNativeAffineVar<C>,
@ -219,6 +223,7 @@ where
let mut sponge = PoseidonSpongeVar::<C::ScalarField>::new(cs, poseidon_config);
let input: Vec<FpVar<C::ScalarField>> = [
vec![pp_hash],
U_i_vec,
vec![u_i.u.clone()],
u_i.x.clone(),
@ -247,6 +252,7 @@ pub struct AugmentedFCircuit<
{
pub _gc2: PhantomData<GC2>,
pub poseidon_config: PoseidonConfig<CF1<C1>>,
pub pp_hash: Option<CF1<C1>>,
pub i: Option<CF1<C1>>,
pub i_usize: Option<usize>,
pub z_0: Option<Vec<C1::ScalarField>>,
@ -280,6 +286,7 @@ where
Self {
_gc2: PhantomData,
poseidon_config: poseidon_config.clone(),
pp_hash: None,
i: None,
i_usize: None,
z_0: None,
@ -317,6 +324,9 @@ where
for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
{
fn generate_constraints(self, cs: ConstraintSystemRef<CF1<C1>>) -> Result<(), SynthesisError> {
let pp_hash = FpVar::<CF1<C1>>::new_witness(cs.clone(), || {
Ok(self.pp_hash.unwrap_or_else(CF1::<C1>::zero))
})?;
let i = FpVar::<CF1<C1>>::new_witness(cs.clone(), || {
Ok(self.i.unwrap_or_else(CF1::<C1>::zero))
})?;
@ -373,11 +383,15 @@ where
// Primary Part
// P.1. Compute u_i.x
// u_i.x[0] = H(i, z_0, z_i, U_i)
let (u_i_x, U_i_vec) =
U_i.clone()
.hash(&crh_params, i.clone(), z_0.clone(), z_i.clone())?;
let (u_i_x, U_i_vec) = U_i.clone().hash(
&crh_params,
pp_hash.clone(),
i.clone(),
z_0.clone(),
z_i.clone(),
)?;
// u_i.x[1] = H(cf_U_i)
let (cf_u_i_x, cf_U_i_vec) = cf_U_i.clone().hash(&crh_params)?;
let (cf_u_i_x, cf_U_i_vec) = cf_U_i.clone().hash(&crh_params, pp_hash.clone())?;
// P.2. Construct u_i
let u_i = CommittedInstanceVar {
@ -399,6 +413,7 @@ where
let r_bits = ChallengeGadget::<C1>::get_challenge_gadget(
cs.clone(),
&self.poseidon_config,
pp_hash.clone(),
U_i_vec,
u_i.clone(),
cmT.clone(),
@ -424,12 +439,14 @@ where
// Non-base case: u_{i+1}.x[0] == H((i+1, z_0, z_{i+1}, U_{i+1})
let (u_i1_x, _) = U_i1.clone().hash(
&crh_params,
pp_hash.clone(),
i + FpVar::<CF1<C1>>::one(),
z_0.clone(),
z_i1.clone(),
)?;
let (u_i1_x_base, _) = CommittedInstanceVar::new_constant(cs.clone(), u_dummy)?.hash(
&crh_params,
pp_hash.clone(),
FpVar::<CF1<C1>>::one(),
z_0.clone(),
z_i1.clone(),
@ -484,6 +501,7 @@ where
let cf1_r_bits = CycleFoldChallengeGadget::<C2, GC2>::get_challenge_gadget(
cs.clone(),
&self.poseidon_config,
pp_hash.clone(),
cf_U_i_vec,
cf1_u_i.clone(),
cf1_cmT.clone(),
@ -507,6 +525,7 @@ where
let cf2_r_bits = CycleFoldChallengeGadget::<C2, GC2>::get_challenge_gadget(
cs.clone(),
&self.poseidon_config,
pp_hash.clone(),
cf1_U_i1.to_constraint_field()?,
cf2_u_i.clone(),
cf2_cmT.clone(),
@ -528,10 +547,10 @@ where
// P.4.b compute and check the second output of F'
// Base case: u_{i+1}.x[1] == H(cf_U_{\bot})
// Non-base case: u_{i+1}.x[1] == H(cf_U_{i+1})
let (cf_u_i1_x, _) = cf_U_i1.clone().hash(&crh_params)?;
let (cf_u_i1_x, _) = cf_U_i1.clone().hash(&crh_params, pp_hash.clone())?;
let (cf_u_i1_x_base, _) =
CycleFoldCommittedInstanceVar::new_constant(cs.clone(), cf_u_dummy)?
.hash(&crh_params)?;
.hash(&crh_params, pp_hash)?;
let cf_x = FpVar::new_input(cs.clone(), || {
Ok(self.cf_x.unwrap_or(cf_u_i1_x_base.value()?))
})?;
@ -609,6 +628,7 @@ pub mod tests {
fn test_committed_instance_hash() {
let mut rng = ark_std::test_rng();
let poseidon_config = poseidon_canonical_config::<Fr>();
let pp_hash = Fr::from(42u32); // only for test
let i = Fr::from(3_u32);
let z_0 = vec![Fr::from(3_u32)];
@ -622,11 +642,12 @@ pub mod tests {
// compute the CommittedInstance hash natively
let h = ci
.hash(&poseidon_config, i, z_0.clone(), z_i.clone())
.hash(&poseidon_config, pp_hash, i, z_0.clone(), z_i.clone())
.unwrap();
let cs = ConstraintSystem::<Fr>::new_ref();
let pp_hashVar = FpVar::<Fr>::new_witness(cs.clone(), || Ok(pp_hash)).unwrap();
let iVar = FpVar::<Fr>::new_witness(cs.clone(), || Ok(i)).unwrap();
let z_0Var = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(z_0.clone())).unwrap();
let z_iVar = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(z_i.clone())).unwrap();
@ -636,7 +657,9 @@ pub mod tests {
let crh_params = CRHParametersVar::<Fr>::new_constant(cs.clone(), poseidon_config).unwrap();
// compute the CommittedInstance hash in-circuit
let (hVar, _) = ciVar.hash(&crh_params, iVar, z_0Var, z_iVar).unwrap();
let (hVar, _) = ciVar
.hash(&crh_params, pp_hashVar, iVar, z_0Var, z_iVar)
.unwrap();
assert!(cs.is_satisfied().unwrap());
// check that the natively computed and in-circuit computed hashes match
@ -663,9 +686,12 @@ pub mod tests {
};
let cmT = Projective::rand(&mut rng);
let pp_hash = Fr::from(42u32); // only for testing
// compute the challenge natively
let r_bits = ChallengeGadget::<Projective>::get_challenge_native(
&poseidon_config,
pp_hash,
U_i.clone(),
u_i.clone(),
cmT,
@ -674,6 +700,7 @@ pub mod tests {
let r = Fr::from_bigint(BigInteger::from_bits_le(&r_bits)).unwrap();
let cs = ConstraintSystem::<Fr>::new_ref();
let pp_hashVar = FpVar::<Fr>::new_witness(cs.clone(), || Ok(pp_hash)).unwrap();
let u_iVar =
CommittedInstanceVar::<Projective>::new_witness(cs.clone(), || Ok(u_i.clone()))
.unwrap();
@ -693,6 +720,7 @@ pub mod tests {
let r_bitsVar = ChallengeGadget::<Projective>::get_challenge_gadget(
cs.clone(),
&poseidon_config,
pp_hashVar,
U_iVar_vec,
u_iVar,
cmTVar,

+ 14
- 42
folding-schemes/src/folding/nova/decider_eth.rs

@ -90,7 +90,8 @@ where
type PreprocessorParam = (FS::ProverParam, FS::VerifierParam);
type ProverParam = (S::ProvingKey, CS1::ProverParams);
type Proof = Proof<C1, CS1, S>;
type VerifierParam = (S::VerifyingKey, CS1::VerifierParams);
/// VerifierParam = (pp_hash, snark::vk, commitment_scheme::vk)
type VerifierParam = (C1::ScalarField, S::VerifyingKey, CS1::VerifierParams);
type PublicInput = Vec<C1::ScalarField>;
type CommittedInstance = CommittedInstance<C1>;
@ -115,9 +116,10 @@ where
let nova_vp:
<Nova<C1, GC1, C2, GC2, FC, CS1, CS2> as FoldingScheme<C1, C2, FC>>::VerifierParam =
prep_param.1.clone().into();
let pp_hash = nova_vp.pp_hash()?;
let pp = (g16_pk, nova_pp.cs_pp);
let vp = (g16_vk, nova_vp.cs_vp);
let vp = (pp_hash, g16_vk, nova_vp.cs_vp);
Ok((pp, vp))
}
@ -186,7 +188,8 @@ where
return Err(Error::NotEnoughSteps);
}
let (snark_vk, cs_vk): (S::VerifyingKey, CS1::VerifierParams) = vp;
let (pp_hash, snark_vk, cs_vk): (C1::ScalarField, S::VerifyingKey, CS1::VerifierParams) =
vp;
// compute U = U_{d+1}= NIFS.V(U_d, u_d, cmT)
let U = NIFS::<C1, CS1>::verify(proof.r, running_instance, incoming_instance, &proof.cmT);
@ -196,7 +199,7 @@ where
let (cmT_x, cmT_y) = NonNativeAffineVar::inputize(proof.cmT)?;
let public_input: Vec<C1::ScalarField> = vec![
vec![i],
vec![pp_hash, i],
z_0,
z_i,
vec![U.u],
@ -317,13 +320,12 @@ pub mod tests {
use ark_bn254::{constraints::GVar, Bn254, Fr, G1Projective as Projective};
use ark_groth16::Groth16;
use ark_grumpkin::{constraints::GVar as GVar2, Projective as Projective2};
use ark_poly_commit::kzg10::VerifierKey as KZGVerifierKey;
use std::time::Instant;
use super::*;
use crate::commitment::kzg::{ProverKey as KZGProverKey, KZG};
use crate::commitment::kzg::KZG;
use crate::commitment::pedersen::Pedersen;
use crate::folding::nova::{get_cs_params_len, ProverParams};
use crate::folding::nova::PreprocessorParam;
use crate::frontend::tests::CubicFCircuit;
use crate::transcript::poseidon::poseidon_canonical_config;
@ -357,59 +359,29 @@ pub mod tests {
let F_circuit = CubicFCircuit::<Fr>::new(()).unwrap();
let z_0 = vec![Fr::from(3_u32)];
let (cs_len, cf_cs_len) =
get_cs_params_len::<Projective, GVar, Projective2, GVar2, CubicFCircuit<Fr>>(
&poseidon_config,
F_circuit,
)
.unwrap();
let start = Instant::now();
let (kzg_pk, kzg_vk): (KZGProverKey<Projective>, KZGVerifierKey<Bn254>) =
KZG::<Bn254>::setup(&mut rng, cs_len).unwrap();
let (cf_pedersen_params, _) = Pedersen::<Projective2>::setup(&mut rng, cf_cs_len).unwrap();
println!("generated KZG params, {:?}", start.elapsed());
let prover_params =
ProverParams::<Projective, Projective2, KZG<Bn254>, Pedersen<Projective2>> {
poseidon_config: poseidon_config.clone(),
cs_pp: kzg_pk.clone(),
cf_cs_pp: cf_pedersen_params,
};
let prep_param = PreprocessorParam::new(poseidon_config, F_circuit);
let nova_params = N::preprocess(&mut rng, &prep_param).unwrap();
let start = Instant::now();
let mut nova = N::init(&prover_params, F_circuit, z_0.clone()).unwrap();
let mut nova = N::init(nova_params.clone(), F_circuit, z_0.clone()).unwrap();
println!("Nova initialized, {:?}", start.elapsed());
let start = Instant::now();
nova.prove_step(&mut rng, vec![]).unwrap();
println!("prove_step, {:?}", start.elapsed());
nova.prove_step(&mut rng, vec![]).unwrap(); // do a 2nd step
// generate Groth16 setup
let circuit = DeciderEthCircuit::<
Projective,
GVar,
Projective2,
GVar2,
KZG<Bn254>,
Pedersen<Projective2>,
>::from_nova::<CubicFCircuit<Fr>>(nova.clone())
.unwrap();
let mut rng = rand::rngs::OsRng;
let start = Instant::now();
let (g16_pk, g16_vk) =
Groth16::<Bn254>::circuit_specific_setup(circuit.clone(), &mut rng).unwrap();
println!("Groth16 setup, {:?}", start.elapsed());
// prepare the Decider prover & verifier params
let (decider_pp, decider_vp) = D::preprocess(&mut rng, &nova_params, nova.clone()).unwrap();
// decider proof generation
let start = Instant::now();
let decider_pp = (g16_pk, kzg_pk);
let proof = D::prove(rng, decider_pp, nova.clone()).unwrap();
println!("Decider prove, {:?}", start.elapsed());
// decider proof verification
let start = Instant::now();
let decider_vp = (g16_vk, kzg_vk);
let verified = D::verify(
decider_vp, nova.i, nova.z_0, nova.z_i, &nova.U_i, &nova.u_i, &proof,
)

+ 39
- 37
folding-schemes/src/folding/nova/decider_eth_circuit.rs

@ -20,7 +20,7 @@ use ark_std::{log2, Zero};
use core::{borrow::Borrow, marker::PhantomData};
use super::{circuits::ChallengeGadget, nifs::NIFS};
use crate::ccs::r1cs::R1CS;
use crate::arith::r1cs::R1CS;
use crate::commitment::{pedersen::Params as PedersenParams, CommitmentScheme};
use crate::folding::circuits::{
nonnative::{
@ -223,6 +223,8 @@ where
/// CycleFold PedersenParams over C2
pub cf_pedersen_params: PedersenParams<C2>,
pub poseidon_config: PoseidonConfig<CF1<C1>>,
/// public params hash
pub pp_hash: Option<C1::ScalarField>,
pub i: Option<CF1<C1>>,
/// initial state
pub z_0: Option<Vec<C1::ScalarField>>,
@ -273,6 +275,7 @@ where
)?;
let r_bits = ChallengeGadget::<C1>::get_challenge_native(
&nova.poseidon_config,
nova.pp_hash,
nova.U_i.clone(),
nova.u_i.clone(),
cmT,
@ -317,6 +320,7 @@ where
cf_r1cs: nova.cf_r1cs,
cf_pedersen_params: nova.cf_cs_pp,
poseidon_config: nova.poseidon_config,
pp_hash: Some(nova.pp_hash),
i: Some(nova.i),
z_0: Some(nova.z_0),
z_i: Some(nova.z_i),
@ -360,6 +364,9 @@ where
Ok(self.r1cs.clone())
})?;
let pp_hash = FpVar::<CF1<C1>>::new_input(cs.clone(), || {
Ok(self.pp_hash.unwrap_or_else(CF1::<C1>::zero))
})?;
let i =
FpVar::<CF1<C1>>::new_input(cs.clone(), || Ok(self.i.unwrap_or_else(CF1::<C1>::zero)))?;
let z_0 = Vec::<FpVar<CF1<C1>>>::new_input(cs.clone(), || {
@ -421,9 +428,13 @@ where
(u_i.u.is_one()?).enforce_equal(&Boolean::TRUE)?;
// 3.a u_i.x[0] == H(i, z_0, z_i, U_i)
let (u_i_x, U_i_vec) =
U_i.clone()
.hash(&crh_params, i.clone(), z_0.clone(), z_i.clone())?;
let (u_i_x, U_i_vec) = U_i.clone().hash(
&crh_params,
pp_hash.clone(),
i.clone(),
z_0.clone(),
z_i.clone(),
)?;
(u_i.x[0]).enforce_equal(&u_i_x)?;
#[cfg(feature = "light-test")]
@ -454,7 +465,7 @@ where
})?;
// 3.b u_i.x[1] == H(cf_U_i)
let (cf_u_i_x, _) = cf_U_i.clone().hash(&crh_params)?;
let (cf_u_i_x, _) = cf_U_i.clone().hash(&crh_params, pp_hash.clone())?;
(u_i.x[1]).enforce_equal(&cf_u_i_x)?;
// 4. check Pedersen commitments of cf_U_i.{cmE, cmW}
@ -512,6 +523,7 @@ where
let r_bits = ChallengeGadget::<C1>::get_challenge_gadget(
cs.clone(),
&self.poseidon_config,
pp_hash,
U_i_vec,
u_i.clone(),
cmT.clone(),
@ -611,10 +623,15 @@ pub mod tests {
use ark_vesta::{constraints::GVar as GVar2, Projective as Projective2};
use super::*;
use crate::ccs::r1cs::tests::{get_test_r1cs, get_test_z};
use crate::ccs::r1cs::{extract_r1cs, extract_w_x};
use crate::arith::{
r1cs::{
tests::{get_test_r1cs, get_test_z},
{extract_r1cs, extract_w_x},
},
Arith,
};
use crate::commitment::pedersen::Pedersen;
use crate::folding::nova::{get_cs_params_len, ProverParams, VerifierParams};
use crate::folding::nova::PreprocessorParam;
use crate::frontend::tests::{CubicFCircuit, CustomFCircuit, WrapperCircuit};
use crate::transcript::poseidon::poseidon_canonical_config;
use crate::FoldingScheme;
@ -772,23 +789,6 @@ pub mod tests {
let F_circuit = CubicFCircuit::<Fr>::new(()).unwrap();
let z_0 = vec![Fr::from(3_u32)];
// get the CS & CF_CS len
let (cs_len, cf_cs_len) =
get_cs_params_len::<Projective, GVar, Projective2, GVar2, CubicFCircuit<Fr>>(
&poseidon_config,
F_circuit,
)
.unwrap();
let (pedersen_params, _) = Pedersen::<Projective>::setup(&mut rng, cs_len).unwrap();
let (cf_pedersen_params, _) = Pedersen::<Projective2>::setup(&mut rng, cf_cs_len).unwrap();
let prover_params =
ProverParams::<Projective, Projective2, Pedersen<Projective>, Pedersen<Projective2>> {
poseidon_config: poseidon_config.clone(),
cs_pp: pedersen_params.clone(),
cf_cs_pp: cf_pedersen_params.clone(),
};
type N = Nova<
Projective,
GVar,
@ -799,22 +799,24 @@ pub mod tests {
Pedersen<Projective2>,
>;
// generate a Nova instance and do a step of it
let mut nova = N::init(&prover_params, F_circuit, z_0.clone()).unwrap();
nova.prove_step(&mut rng, vec![]).unwrap();
let ivc_v = nova.clone();
let verifier_params = VerifierParams::<
let prep_param = PreprocessorParam::<
Projective,
Projective2,
CubicFCircuit<Fr>,
Pedersen<Projective>,
Pedersen<Projective2>,
> {
poseidon_config: poseidon_config.clone(),
r1cs: ivc_v.clone().r1cs,
cf_r1cs: ivc_v.clone().cf_r1cs,
cs_vp: pedersen_params,
cf_cs_vp: cf_pedersen_params,
};
>::new(poseidon_config, F_circuit);
let (prover_params, verifier_params) = N::preprocess(&mut rng, &prep_param).unwrap();
// generate a Nova instance and do a step of it
let mut nova = N::init(
(prover_params, verifier_params.clone()),
F_circuit,
z_0.clone(),
)
.unwrap();
nova.prove_step(&mut rng, vec![]).unwrap();
let ivc_v = nova.clone();
let (running_instance, incoming_instance, cyclefold_instance) = ivc_v.instances();
N::verify(
verifier_params,

+ 65
- 20
folding-schemes/src/folding/nova/mod.rs

@ -13,19 +13,18 @@ use ark_std::fmt::Debug;
use ark_std::rand::RngCore;
use ark_std::{One, Zero};
use core::marker::PhantomData;
use std::usize;
use crate::ccs::r1cs::{extract_r1cs, extract_w_x, R1CS};
use crate::arith::r1cs::{extract_r1cs, extract_w_x, R1CS};
use crate::commitment::CommitmentScheme;
use crate::folding::circuits::cyclefold::{fold_cyclefold_circuit, CycleFoldCircuit};
use crate::folding::circuits::{
cyclefold::{fold_cyclefold_circuit, CycleFoldCircuit},
nonnative::{
affine::nonnative_affine_to_field_elements, uint::nonnative_field_to_field_elements,
},
CF2,
};
use crate::frontend::FCircuit;
use crate::utils::{get_cm_coordinates, vec::is_zero_vec};
use crate::utils::{get_cm_coordinates, pp_hash, vec::is_zero_vec};
use crate::Error;
use crate::FoldingScheme;
@ -70,6 +69,7 @@ where
pub fn hash(
&self,
poseidon_config: &PoseidonConfig<C::ScalarField>,
pp_hash: C::ScalarField, // public params hash
i: C::ScalarField,
z_0: Vec<C::ScalarField>,
z_i: Vec<C::ScalarField>,
@ -80,7 +80,7 @@ where
CRH::<C::ScalarField>::evaluate(
poseidon_config,
vec![
vec![i],
vec![pp_hash, i],
z_0,
z_i,
vec![self.u],
@ -140,9 +140,13 @@ where
pub fn hash_cyclefold(
&self,
poseidon_config: &PoseidonConfig<C::BaseField>,
pp_hash: C::BaseField, // public params hash
) -> Result<C::BaseField, Error> {
CRH::<C::BaseField>::evaluate(poseidon_config, self.to_field_elements().unwrap())
.map_err(|e| Error::Other(e.to_string()))
CRH::<C::BaseField>::evaluate(
poseidon_config,
[vec![pp_hash], self.to_field_elements().unwrap()].concat(),
)
.map_err(|e| Error::Other(e.to_string()))
}
}
@ -253,6 +257,25 @@ where
pub cf_cs_vp: CS2::VerifierParams,
}
impl<C1, C2, CS1, CS2> VerifierParams<C1, C2, CS1, CS2>
where
C1: CurveGroup,
C2: CurveGroup,
CS1: CommitmentScheme<C1>,
CS2: CommitmentScheme<C2>,
{
/// returns the hash of the public parameters of Nova
pub fn pp_hash(&self) -> Result<C1::ScalarField, Error> {
pp_hash::<C1, C2, CS1, CS2>(
&self.r1cs,
&self.cf_r1cs,
&self.cs_vp,
&self.cf_cs_vp,
&self.poseidon_config,
)
}
}
/// Implements Nova+CycleFold's IVC, described in [Nova](https://eprint.iacr.org/2021/370.pdf) and
/// [CycleFold](https://eprint.iacr.org/2023/1192.pdf), following the FoldingScheme trait
#[derive(Clone, Debug)]
@ -280,6 +303,8 @@ where
pub cf_cs_pp: CS2::ProverParams,
/// F circuit, the circuit that is being folded
pub F: FC,
/// public params hash
pub pp_hash: C1::ScalarField,
pub i: C1::ScalarField,
/// initial state
pub z_0: Vec<C1::ScalarField>,
@ -343,8 +368,8 @@ where
cf_cs_pp = prep_param.clone().cf_cs_pp.unwrap();
cf_cs_vp = prep_param.clone().cf_cs_vp.unwrap();
} else {
(cs_pp, cs_vp) = CS1::setup(&mut rng, r1cs.A.n_rows).unwrap();
(cf_cs_pp, cf_cs_vp) = CS2::setup(&mut rng, cf_r1cs.A.n_rows).unwrap();
(cs_pp, cs_vp) = CS1::setup(&mut rng, r1cs.A.n_rows)?;
(cf_cs_pp, cf_cs_vp) = CS2::setup(&mut rng, cf_r1cs.A.n_rows)?;
}
let prover_params = ProverParams::<C1, C2, CS1, CS2> {
@ -356,14 +381,21 @@ where
poseidon_config: prep_param.poseidon_config.clone(),
r1cs,
cf_r1cs,
cs_vp: cs_vp.clone(),
cf_cs_vp: cf_cs_vp.clone(),
cs_vp,
cf_cs_vp,
};
Ok((prover_params.clone(), verifier_params))
Ok((prover_params, verifier_params))
}
/// Initializes the Nova+CycleFold's IVC for the given parameters and initial state `z_0`.
fn init(pp: &Self::ProverParam, F: FC, z_0: Vec<C1::ScalarField>) -> Result<Self, Error> {
fn init(
params: (Self::ProverParam, Self::VerifierParam),
F: FC,
z_0: Vec<C1::ScalarField>,
) -> Result<Self, Error> {
let (pp, vp) = params;
// prepare the circuit to obtain its R1CS
let cs = ConstraintSystem::<C1::ScalarField>::new_ref();
let cs2 = ConstraintSystem::<C1::BaseField>::new_ref();
@ -382,6 +414,9 @@ where
let cs2 = cs2.into_inner().ok_or(Error::NoInnerConstraintSystem)?;
let cf_r1cs = extract_r1cs::<C1::BaseField>(&cs2);
// compute the public params hash
let pp_hash = vp.pp_hash()?;
// setup the dummy instances
let (w_dummy, u_dummy) = r1cs.dummy_instance();
let (cf_w_dummy, cf_u_dummy) = cf_r1cs.dummy_instance();
@ -398,6 +433,7 @@ where
cs_pp: pp.cs_pp.clone(),
cf_cs_pp: pp.cf_cs_pp.clone(),
F,
pp_hash,
i: C1::ScalarField::zero(),
z_0: z_0.clone(),
z_i: z_0,
@ -453,6 +489,7 @@ where
// r_bits is the r used to the RLC of the F' instances
let r_bits = ChallengeGadget::<C1>::get_challenge_native(
&self.poseidon_config,
self.pp_hash,
self.U_i.clone(),
self.u_i.clone(),
cmT,
@ -471,6 +508,7 @@ where
// u_{i+1}.x[0] = H(i+1, z_0, z_{i+1}, U_{i+1})
let u_i1_x = U_i1.hash(
&self.poseidon_config,
self.pp_hash,
self.i + C1::ScalarField::one(),
self.z_0.clone(),
z_i1.clone(),
@ -479,11 +517,14 @@ where
let cf_u_i1_x: C1::ScalarField;
if self.i == C1::ScalarField::zero() {
cf_u_i1_x = self.cf_U_i.hash_cyclefold(&self.poseidon_config)?;
cf_u_i1_x = self
.cf_U_i
.hash_cyclefold(&self.poseidon_config, self.pp_hash)?;
// base case
augmented_F_circuit = AugmentedFCircuit::<C1, C2, GC2, FC> {
_gc2: PhantomData,
poseidon_config: self.poseidon_config.clone(),
pp_hash: Some(self.pp_hash),
i: Some(C1::ScalarField::zero()), // = i=0
i_usize: Some(0),
z_0: Some(self.z_0.clone()), // = z_i
@ -552,11 +593,12 @@ where
let (_cfE_w_i, cfE_u_i, cf_W_i1, cf_U_i1, cf_cmT, _) =
self.fold_cyclefold_circuit(cfW_W_i1, cfW_U_i1.clone(), cfE_u_i_x, cfE_circuit)?;
cf_u_i1_x = cf_U_i1.hash_cyclefold(&self.poseidon_config)?;
cf_u_i1_x = cf_U_i1.hash_cyclefold(&self.poseidon_config, self.pp_hash)?;
augmented_F_circuit = AugmentedFCircuit::<C1, C2, GC2, FC> {
_gc2: PhantomData,
poseidon_config: self.poseidon_config.clone(),
pp_hash: Some(self.pp_hash),
i: Some(self.i),
i_usize: Some(i_usize),
z_0: Some(self.z_0.clone()),
@ -670,14 +712,16 @@ where
return Err(Error::IVCVerificationFail);
}
let pp_hash = vp.pp_hash()?;
// check that u_i's output points to the running instance
// u_i.X[0] == H(i, z_0, z_i, U_i)
let expected_u_i_x = U_i.hash(&vp.poseidon_config, num_steps, z_0, z_i.clone())?;
let expected_u_i_x = U_i.hash(&vp.poseidon_config, pp_hash, num_steps, z_0, z_i.clone())?;
if expected_u_i_x != u_i.x[0] {
return Err(Error::IVCVerificationFail);
}
// u_i.X[1] == H(cf_U_i)
let expected_cf_u_i_x = cf_U_i.hash_cyclefold(&vp.poseidon_config)?;
let expected_cf_u_i_x = cf_U_i.hash_cyclefold(&vp.poseidon_config, pp_hash)?;
if expected_cf_u_i_x != u_i.x[1] {
return Err(Error::IVCVerificationFail);
}
@ -767,6 +811,7 @@ where
&self.poseidon_config,
self.cf_r1cs.clone(),
self.cf_cs_pp.clone(),
self.pp_hash,
cf_W_i,
cf_U_i,
cf_u_i_x,
@ -884,10 +929,10 @@ pub mod tests {
cf_cs_pp: None,
cf_cs_vp: None,
};
let (prover_params, verifier_params) = N::preprocess(&mut rng, &prep_param).unwrap();
let nova_params = N::preprocess(&mut rng, &prep_param).unwrap();
let z_0 = vec![Fr::from(3_u32)];
let mut nova = N::init(&prover_params, F_circuit, z_0.clone()).unwrap();
let mut nova = N::init(nova_params.clone(), F_circuit, z_0.clone()).unwrap();
let num_steps: usize = 3;
for _ in 0..num_steps {
@ -897,7 +942,7 @@ pub mod tests {
let (running_instance, incoming_instance, cyclefold_instance) = nova.instances();
N::<CS1, CS2>::verify(
verifier_params,
nova_params.1, // Nova's verifier params
z_0,
nova.z_i,
nova.i,

+ 4
- 2
folding-schemes/src/folding/nova/nifs.rs

@ -4,7 +4,7 @@ use ark_std::Zero;
use std::marker::PhantomData;
use super::{CommittedInstance, Witness};
use crate::ccs::r1cs::R1CS;
use crate::arith::r1cs::R1CS;
use crate::commitment::CommitmentScheme;
use crate::transcript::Transcript;
use crate::utils::vec::{hadamard, mat_vec_mul, vec_add, vec_scalar_mul, vec_sub};
@ -205,7 +205,7 @@ pub mod tests {
use ark_pallas::{Fr, Projective};
use ark_std::{ops::Mul, UniformRand};
use crate::ccs::r1cs::tests::{get_test_r1cs, get_test_z};
use crate::arith::r1cs::tests::{get_test_r1cs, get_test_z};
use crate::commitment::pedersen::{Params as PedersenParams, Pedersen};
use crate::folding::nova::circuits::ChallengeGadget;
use crate::folding::nova::traits::NovaR1CS;
@ -259,8 +259,10 @@ pub mod tests {
let poseidon_config = poseidon_canonical_config::<C::ScalarField>();
let pp_hash = C::ScalarField::from(42u32); // only for test
let r_bits = ChallengeGadget::<C>::get_challenge_native(
&poseidon_config,
pp_hash,
ci1.clone(),
ci2.clone(),
cmT,

+ 16
- 30
folding-schemes/src/folding/nova/serialize.rs

@ -14,7 +14,7 @@ use super::{circuits::AugmentedFCircuit, Nova, ProverParams};
use super::{CommittedInstance, Witness};
use crate::folding::circuits::{cyclefold::CycleFoldCircuit, CF2};
use crate::{
ccs::r1cs::extract_r1cs, commitment::CommitmentScheme, folding::circuits::CF1,
arith::r1cs::extract_r1cs, commitment::CommitmentScheme, folding::circuits::CF1,
frontend::FCircuit,
};
@ -41,6 +41,7 @@ where
mut writer: W,
compress: ark_serialize::Compress,
) -> Result<(), ark_serialize::SerializationError> {
self.pp_hash.serialize_with_mode(&mut writer, compress)?;
self.i.serialize_with_mode(&mut writer, compress)?;
self.z_0.serialize_with_mode(&mut writer, compress)?;
self.z_i.serialize_with_mode(&mut writer, compress)?;
@ -53,7 +54,8 @@ where
}
fn serialized_size(&self, compress: ark_serialize::Compress) -> usize {
self.i.serialized_size(compress)
self.pp_hash.serialized_size(compress)
+ self.i.serialized_size(compress)
+ self.z_0.serialized_size(compress)
+ self.z_i.serialized_size(compress)
+ self.w_i.serialized_size(compress)
@ -115,6 +117,7 @@ where
prover_params: ProverParams<C1, C2, CS1, CS2>,
poseidon_config: PoseidonConfig<C1::ScalarField>,
) -> Result<Self, ark_serialize::SerializationError> {
let pp_hash = C1::ScalarField::deserialize_with_mode(&mut reader, compress, validate)?;
let i = C1::ScalarField::deserialize_with_mode(&mut reader, compress, validate)?;
let z_0 = Vec::<C1::ScalarField>::deserialize_with_mode(&mut reader, compress, validate)?;
let z_i = Vec::<C1::ScalarField>::deserialize_with_mode(&mut reader, compress, validate)?;
@ -151,8 +154,13 @@ where
_gc1: PhantomData,
_c2: PhantomData,
_gc2: PhantomData,
r1cs,
cf_r1cs,
poseidon_config,
cs_pp: prover_params.cs_pp,
cf_cs_pp: prover_params.cf_cs_pp,
F: f_circuit,
pp_hash,
i,
z_0,
z_i,
@ -162,10 +170,6 @@ where
U_i,
cf_W_i,
cf_U_i,
r1cs,
cf_r1cs,
poseidon_config,
F: f_circuit,
})
}
}
@ -174,17 +178,12 @@ where
pub mod tests {
use ark_bn254::{constraints::GVar, Bn254, Fr, G1Projective as Projective};
use ark_grumpkin::{constraints::GVar as GVar2, Projective as Projective2};
use ark_poly_commit::kzg10::VerifierKey as KZGVerifierKey;
use ark_serialize::{CanonicalSerialize, Compress, Validate};
use std::{fs, io::Write};
use crate::{
commitment::{
kzg::{ProverKey as KZGProverKey, KZG},
pedersen::Pedersen,
CommitmentScheme,
},
folding::nova::{get_cs_params_len, Nova, ProverParams},
commitment::{kzg::KZG, pedersen::Pedersen},
folding::nova::{Nova, PreprocessorParam},
frontend::{tests::CubicFCircuit, FCircuit},
transcript::poseidon::poseidon_canonical_config,
FoldingScheme,
@ -195,15 +194,6 @@ pub mod tests {
let mut rng = ark_std::test_rng();
let poseidon_config = poseidon_canonical_config::<Fr>();
let F_circuit = CubicFCircuit::<Fr>::new(()).unwrap();
let (cs_len, cf_cs_len) =
get_cs_params_len::<Projective, GVar, Projective2, GVar2, CubicFCircuit<Fr>>(
&poseidon_config,
F_circuit,
)
.unwrap();
let (kzg_pk, _): (KZGProverKey<Projective>, KZGVerifierKey<Bn254>) =
KZG::<Bn254>::setup(&mut rng, cs_len).unwrap();
let (cf_pedersen_params, _) = Pedersen::<Projective2>::setup(&mut rng, cf_cs_len).unwrap();
// Initialize nova and make multiple `prove_step()`
type N = Nova<
@ -215,15 +205,11 @@ pub mod tests {
KZG<'static, Bn254>,
Pedersen<Projective2>,
>;
let prover_params =
ProverParams::<Projective, Projective2, KZG<Bn254>, Pedersen<Projective2>> {
poseidon_config: poseidon_config.clone(),
cs_pp: kzg_pk.clone(),
cf_cs_pp: cf_pedersen_params.clone(),
};
let prep_param = PreprocessorParam::new(poseidon_config.clone(), F_circuit);
let nova_params = N::preprocess(&mut rng, &prep_param).unwrap();
let z_0 = vec![Fr::from(3_u32)];
let mut nova = N::init(&prover_params, F_circuit, z_0.clone()).unwrap();
let mut nova = N::init(nova_params.clone(), F_circuit, z_0.clone()).unwrap();
let num_steps: usize = 3;
for _ in 0..num_steps {
@ -257,7 +243,7 @@ pub mod tests {
bytes.as_slice(),
Compress::No,
Validate::No,
prover_params,
nova_params.0, // Nova's prover params
poseidon_config,
)
.unwrap();

+ 1
- 1
folding-schemes/src/folding/nova/traits.rs

@ -3,7 +3,7 @@ use ark_ec::{CurveGroup, Group};
use ark_std::{One, Zero};
use super::{CommittedInstance, Witness};
use crate::ccs::r1cs::R1CS;
use crate::arith::{r1cs::R1CS, Arith};
use crate::Error;
/// NovaR1CS extends R1CS methods with Nova specific methods

+ 2
- 2
folding-schemes/src/folding/protogalaxy/folding.rs

@ -16,7 +16,7 @@ use super::utils::{all_powers, betas_star, exponential_powers};
use super::ProtoGalaxyError;
use super::{CommittedInstance, Witness};
use crate::ccs::r1cs::R1CS;
use crate::arith::r1cs::R1CS;
use crate::transcript::Transcript;
use crate::utils::vec::*;
use crate::utils::virtual_polynomial::bit_decompose;
@ -383,7 +383,7 @@ mod tests {
use ark_pallas::{Fr, Projective};
use ark_std::UniformRand;
use crate::ccs::r1cs::tests::{get_test_r1cs, get_test_z};
use crate::arith::r1cs::tests::{get_test_r1cs, get_test_z};
use crate::commitment::{pedersen::Pedersen, CommitmentScheme};
use crate::transcript::poseidon::{poseidon_canonical_config, PoseidonTranscript};

+ 2
- 2
folding-schemes/src/lib.rs

@ -10,7 +10,7 @@ use thiserror::Error;
use crate::frontend::FCircuit;
pub mod ccs;
pub mod arith;
pub mod commitment;
pub mod constants;
pub mod folding;
@ -122,7 +122,7 @@ where
) -> Result<(Self::ProverParam, Self::VerifierParam), Error>;
fn init(
pp: &Self::ProverParam,
params: (Self::ProverParam, Self::VerifierParam),
step_circuit: FC,
z_0: Vec<C1::ScalarField>, // initial state
) -> Result<Self, Error>;

+ 1
- 1
folding-schemes/src/utils/mle.rs

@ -104,7 +104,7 @@ pub fn dense_vec_to_mle(n_vars: usize, v: &[F]) -> SparseMultilin
mod tests {
use super::*;
use crate::{
ccs::tests::get_test_z,
arith::ccs::tests::get_test_z,
utils::multilinear_polynomial::fix_variables,
utils::multilinear_polynomial::tests::fix_last_variables,
utils::{hypercube::BooleanHypercube, vec::tests::to_F_matrix},

+ 68
- 0
folding-schemes/src/utils/mod.rs

@ -1,6 +1,13 @@
use ark_crypto_primitives::sponge::poseidon::PoseidonConfig;
use ark_ec::{AffineRepr, CurveGroup};
use ark_ff::PrimeField;
use ark_serialize::CanonicalSerialize;
use ark_std::Zero;
use sha3::{Digest, Sha3_256};
use crate::arith::Arith;
use crate::commitment::CommitmentScheme;
use crate::Error;
pub mod gadgets;
pub mod hypercube;
@ -32,3 +39,64 @@ pub fn get_cm_coordinates(cm: &C) -> Vec {
let (cm_x, cm_y) = cm.xy().unwrap_or(zero);
vec![*cm_x, *cm_y]
}
/// returns the hash of the given public parameters of the Folding Scheme
pub fn pp_hash<C1, C2, CS1, CS2>(
arith: &impl Arith<C1::ScalarField>,
cf_arith: &impl Arith<C2::ScalarField>,
cs_vp: &CS1::VerifierParams,
cf_cs_vp: &CS2::VerifierParams,
poseidon_config: &PoseidonConfig<C1::ScalarField>,
) -> Result<C1::ScalarField, Error>
where
C1: CurveGroup,
C2: CurveGroup,
CS1: CommitmentScheme<C1>,
CS2: CommitmentScheme<C2>,
{
let mut hasher = Sha3_256::new();
// Fr & Fq modulus bit size
hasher.update(C1::ScalarField::MODULUS_BIT_SIZE.to_le_bytes());
hasher.update(C2::ScalarField::MODULUS_BIT_SIZE.to_le_bytes());
// AugmentedFCircuit Arith params
hasher.update(arith.params_to_bytes());
// CycleFold Circuit Arith params
hasher.update(cf_arith.params_to_bytes());
// cs_vp & cf_cs_vp (commitments setup)
let mut cs_vp_bytes = Vec::new();
cs_vp.serialize_uncompressed(&mut cs_vp_bytes)?;
hasher.update(cs_vp_bytes);
let mut cf_cs_vp_bytes = Vec::new();
cf_cs_vp.serialize_uncompressed(&mut cf_cs_vp_bytes)?;
hasher.update(cf_cs_vp_bytes);
// poseidon params
let mut poseidon_config_bytes = Vec::new();
poseidon_config
.full_rounds
.serialize_uncompressed(&mut poseidon_config_bytes)?;
poseidon_config
.partial_rounds
.serialize_uncompressed(&mut poseidon_config_bytes)?;
poseidon_config
.alpha
.serialize_uncompressed(&mut poseidon_config_bytes)?;
poseidon_config
.ark
.serialize_uncompressed(&mut poseidon_config_bytes)?;
poseidon_config
.mds
.serialize_uncompressed(&mut poseidon_config_bytes)?;
poseidon_config
.rate
.serialize_uncompressed(&mut poseidon_config_bytes)?;
poseidon_config
.capacity
.serialize_uncompressed(&mut poseidon_config_bytes)?;
hasher.update(poseidon_config_bytes);
let public_params_hash = hasher.finalize();
Ok(C1::ScalarField::from_le_bytes_mod_order(
&public_params_hash,
))
}

+ 6
- 6
solidity-verifiers/src/verifiers/g16.rs

@ -3,7 +3,7 @@ use crate::utils::encoding::{G1Repr, G2Repr};
use crate::utils::HeaderInclusion;
use crate::{ProtocolVerifierKey, GPL3_SDPX_IDENTIFIER};
use ark_bn254::Bn254;
use ark_groth16::VerifyingKey;
use ark_groth16::VerifyingKey as ArkVerifyingKey;
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
use askama::Template;
@ -48,10 +48,10 @@ impl From for Groth16Verifier {
// Ideally this would be linked to the `Decider` trait in FoldingSchemes.
// For now, this is the easiest as NovaCycleFold isn't clear target from where we can get all it's needed arguments.
#[derive(CanonicalDeserialize, CanonicalSerialize, Clone, PartialEq, Debug)]
pub struct Groth16VerifierKey(pub(crate) VerifyingKey<Bn254>);
pub struct Groth16VerifierKey(pub(crate) ArkVerifyingKey<Bn254>);
impl From<VerifyingKey<Bn254>> for Groth16VerifierKey {
fn from(value: VerifyingKey<Bn254>) -> Self {
impl From<ArkVerifyingKey<Bn254>> for Groth16VerifierKey {
fn from(value: ArkVerifyingKey<Bn254>) -> Self {
Self(value)
}
}
@ -95,7 +95,7 @@ mod tests {
#[test]
fn groth16_vk_serde_roundtrip() {
let (_, _, _, vk, _) = setup(DEFAULT_SETUP_LEN);
let (_, _, _, _, vk, _) = setup(DEFAULT_SETUP_LEN);
let g16_vk = Groth16VerifierKey::from(vk);
let mut bytes = vec![];
@ -109,7 +109,7 @@ mod tests {
#[test]
fn test_groth16_verifier_accepts_and_rejects_proofs() {
let mut rng = ark_std::rand::rngs::StdRng::seed_from_u64(test_rng().next_u64());
let (_, _, g16_pk, g16_vk, circuit) = setup(DEFAULT_SETUP_LEN);
let (_, _, _, g16_pk, g16_vk, circuit) = setup(DEFAULT_SETUP_LEN);
let g16_vk = Groth16VerifierKey::from(g16_vk);
let proof = Groth16::<Bn254>::prove(&g16_pk, circuit, &mut rng).unwrap();

+ 3
- 3
solidity-verifiers/src/verifiers/kzg.rs

@ -102,7 +102,7 @@ mod tests {
#[test]
fn kzg_vk_serde_roundtrip() {
let (pk, vk, _, _, _) = setup(DEFAULT_SETUP_LEN);
let (_, pk, vk, _, _, _) = setup(DEFAULT_SETUP_LEN);
let kzg_vk = KZG10VerifierKey::from((vk, pk.powers_of_g[0..3].to_vec()));
let mut bytes = vec![];
@ -115,7 +115,7 @@ mod tests {
#[test]
fn kzg_verifier_compiles() {
let (kzg_pk, kzg_vk, _, _, _) = setup(DEFAULT_SETUP_LEN);
let (_, kzg_pk, kzg_vk, _, _, _) = setup(DEFAULT_SETUP_LEN);
let kzg_vk = KZG10VerifierKey::from((kzg_vk.clone(), kzg_pk.powers_of_g[0..3].to_vec()));
let res = HeaderInclusion::<KZG10Verifier>::builder()
@ -136,7 +136,7 @@ mod tests {
let transcript_p = &mut PoseidonTranscript::<G1>::new(&poseidon_config);
let transcript_v = &mut PoseidonTranscript::<G1>::new(&poseidon_config);
let (kzg_pk, kzg_vk, _, _, _) = setup(DEFAULT_SETUP_LEN);
let (_, kzg_pk, kzg_vk, _, _, _) = setup(DEFAULT_SETUP_LEN);
let kzg_vk = KZG10VerifierKey::from((kzg_vk.clone(), kzg_pk.powers_of_g[0..3].to_vec()));
let v: Vec<Fr> = std::iter::repeat_with(|| Fr::rand(&mut rng))

+ 3
- 1
solidity-verifiers/src/verifiers/mod.rs

@ -97,6 +97,7 @@ pub mod tests {
pub fn setup<'a>(
n: usize,
) -> (
Fr, // public params hash
KZGProverKey<'a, G1>,
KZGVerifierKey<Bn254>,
ark_groth16::ProvingKey<Bn254>,
@ -115,6 +116,7 @@ pub mod tests {
let (kzg_pk, kzg_vk): (KZGProverKey<G1>, KZGVerifierKey<Bn254>) =
KZG::<Bn254>::setup(&mut rng, n).unwrap();
(kzg_pk, kzg_vk, g16_pk, g16_vk, circuit)
let pp_hash = Fr::from(42u32); // only for test
(pp_hash, kzg_pk, kzg_vk, g16_pk, g16_vk, circuit)
}
}

+ 103
- 114
solidity-verifiers/src/verifiers/nova_cyclefold.rs

@ -1,9 +1,10 @@
#![allow(non_snake_case)]
#![allow(non_camel_case_types)]
#![allow(clippy::upper_case_acronyms)]
use ark_bn254::{Bn254, Fq, G1Affine};
use ark_groth16::VerifyingKey;
use ark_poly_commit::kzg10::VerifierKey;
use ark_bn254::{Bn254, Fq, Fr, G1Affine};
use ark_groth16::VerifyingKey as ArkG16VerifierKey;
use ark_poly_commit::kzg10::VerifierKey as ArkKZG10VerifierKey;
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
use askama::Template;
@ -27,6 +28,7 @@ pub fn get_decider_template_for_cyclefold_decider(
#[derive(Template, Default)]
#[template(path = "nova_cyclefold_decider.askama.sol", ext = "sol")]
pub struct NovaCycleFoldDecider {
pp_hash: Fr, // public params hash
groth16_verifier: Groth16Verifier,
kzg10_verifier: KZG10Verifier,
// z_len denotes the FCircuit state (z_i) length
@ -42,6 +44,7 @@ impl From for NovaCycleFoldDecider {
let public_inputs_len = groth16_verifier.gamma_abc_len;
let bits_per_limb = NonNativeUintVar::<Fq>::bits_per_limb();
Self {
pp_hash: value.pp_hash,
groth16_verifier,
kzg10_verifier: KZG10Verifier::from(value.kzg_vk),
z_len: value.z_len,
@ -54,6 +57,7 @@ impl From for NovaCycleFoldDecider {
#[derive(CanonicalDeserialize, CanonicalSerialize, PartialEq, Debug, Clone)]
pub struct NovaCycleFoldVerifierKey {
pp_hash: Fr,
g16_vk: Groth16VerifierKey,
kzg_vk: KZG10VerifierKey,
z_len: usize,
@ -73,25 +77,37 @@ impl ProtocolVerifierKey for NovaCycleFoldVerifierKey {
}
}
impl From<(Groth16VerifierKey, KZG10VerifierKey, usize)> for NovaCycleFoldVerifierKey {
fn from(value: (Groth16VerifierKey, KZG10VerifierKey, usize)) -> Self {
impl From<(Fr, Groth16VerifierKey, KZG10VerifierKey, usize)> for NovaCycleFoldVerifierKey {
fn from(value: (Fr, Groth16VerifierKey, KZG10VerifierKey, usize)) -> Self {
Self {
g16_vk: value.0,
kzg_vk: value.1,
z_len: value.2,
pp_hash: value.0,
g16_vk: value.1,
kzg_vk: value.2,
z_len: value.3,
}
}
}
// implements From assuming that the 'batchCheck' method from the KZG10 template will not be used
// in the NovaCycleFoldDecider verifier contract
impl From<((VerifyingKey<Bn254>, VerifierKey<Bn254>), usize)> for NovaCycleFoldVerifierKey {
fn from(value: ((VerifyingKey<Bn254>, VerifierKey<Bn254>), usize)) -> Self {
impl
From<(
(Fr, ArkG16VerifierKey<Bn254>, ArkKZG10VerifierKey<Bn254>),
usize,
)> for NovaCycleFoldVerifierKey
{
fn from(
value: (
(Fr, ArkG16VerifierKey<Bn254>, ArkKZG10VerifierKey<Bn254>),
usize,
),
) -> Self {
let decider_vp = value.0;
let g16_vk = Groth16VerifierKey::from(decider_vp.0);
let g16_vk = Groth16VerifierKey::from(decider_vp.1);
// pass `Vec::new()` since batchCheck will not be used
let kzg_vk = KZG10VerifierKey::from((decider_vp.1, Vec::new()));
let kzg_vk = KZG10VerifierKey::from((decider_vp.2, Vec::new()));
Self {
pp_hash: decider_vp.0,
g16_vk,
kzg_vk,
z_len: value.1,
@ -101,12 +117,14 @@ impl From<((VerifyingKey, VerifierKey), usize)> for NovaCycleFoldV
impl NovaCycleFoldVerifierKey {
pub fn new(
vkey_g16: VerifyingKey<Bn254>,
vkey_kzg: VerifierKey<Bn254>,
pp_hash: Fr,
vkey_g16: ArkG16VerifierKey<Bn254>,
vkey_kzg: ArkKZG10VerifierKey<Bn254>,
crs_points: Vec<G1Affine>,
z_len: usize,
) -> Self {
Self {
pp_hash,
g16_vk: Groth16VerifierKey::from(vkey_g16),
kzg_vk: KZG10VerifierKey::from((vkey_kzg, crs_points)),
z_len,
@ -117,12 +135,9 @@ impl NovaCycleFoldVerifierKey {
#[cfg(test)]
mod tests {
use ark_bn254::{constraints::GVar, Bn254, Fr, G1Projective as G1};
use ark_crypto_primitives::snark::SNARK;
use ark_ff::PrimeField;
use ark_groth16::VerifyingKey as G16VerifierKey;
use ark_groth16::{Groth16, ProvingKey};
use ark_groth16::Groth16;
use ark_grumpkin::{constraints::GVar as GVar2, Projective as G2};
use ark_poly_commit::kzg10::VerifierKey as KZGVerifierKey;
use ark_r1cs_std::alloc::AllocVar;
use ark_r1cs_std::fields::fp::FpVar;
use ark_relations::r1cs::{ConstraintSystemRef, SynthesisError};
@ -132,15 +147,10 @@ mod tests {
use std::time::Instant;
use folding_schemes::{
commitment::{
kzg::{ProverKey as KZGProverKey, KZG},
pedersen::Pedersen,
CommitmentScheme,
},
commitment::{kzg::KZG, pedersen::Pedersen},
folding::nova::{
decider_eth::{prepare_calldata, Decider as DeciderEth},
decider_eth_circuit::DeciderEthCircuit,
get_cs_params_len, Nova, ProverParams,
Nova, PreprocessorParam,
},
frontend::FCircuit,
transcript::poseidon::poseidon_canonical_config,
@ -156,6 +166,24 @@ mod tests {
NovaCycleFoldVerifierKey, ProtocolVerifierKey,
};
type NOVA<FC> = Nova<G1, GVar, G2, GVar2, FC, KZG<'static, Bn254>, Pedersen<G2>>;
type DECIDER<FC> = DeciderEth<
G1,
GVar,
G2,
GVar2,
FC,
KZG<'static, Bn254>,
Pedersen<G2>,
Groth16<Bn254>,
NOVA<FC>,
>;
type FS_PP<FC> = <NOVA<FC> as FoldingScheme<G1, G2, FC>>::ProverParam;
type FS_VP<FC> = <NOVA<FC> as FoldingScheme<G1, G2, FC>>::VerifierParam;
type DECIDER_PP<FC> = <DECIDER<FC> as Decider<G1, G2, FC, NOVA<FC>>>::ProverParam;
type DECIDER_VP<FC> = <DECIDER<FC> as Decider<G1, G2, FC, NOVA<FC>>>::VerifierParam;
/// Test circuit to be folded
#[derive(Clone, Copy, Debug)]
pub struct CubicFCircuit<F: PrimeField> {
@ -256,10 +284,10 @@ mod tests {
#[test]
fn nova_cyclefold_vk_serde_roundtrip() {
let (_, kzg_vk, _, g16_vk, _) = setup(DEFAULT_SETUP_LEN);
let (pp_hash, _, kzg_vk, _, g16_vk, _) = setup(DEFAULT_SETUP_LEN);
let mut bytes = vec![];
let nova_cyclefold_vk = NovaCycleFoldVerifierKey::from(((g16_vk, kzg_vk), 1));
let nova_cyclefold_vk = NovaCycleFoldVerifierKey::from(((pp_hash, g16_vk, kzg_vk), 1));
nova_cyclefold_vk
.serialize_protocol_verifier_key(&mut bytes)
@ -272,8 +300,8 @@ mod tests {
#[test]
fn nova_cyclefold_decider_template_renders() {
let (_, kzg_vk, _, g16_vk, _) = setup(DEFAULT_SETUP_LEN);
let nova_cyclefold_vk = NovaCycleFoldVerifierKey::from(((g16_vk, kzg_vk), 1));
let (pp_hash, _, kzg_vk, _, g16_vk, _) = setup(DEFAULT_SETUP_LEN);
let nova_cyclefold_vk = NovaCycleFoldVerifierKey::from(((pp_hash, g16_vk, kzg_vk), 1));
let decider_solidity_code = HeaderInclusion::<NovaCycleFoldDecider>::builder()
.template(nova_cyclefold_vk)
@ -282,59 +310,29 @@ mod tests {
save_solidity("NovaDecider.sol", &decider_solidity_code.render().unwrap());
}
#[allow(clippy::type_complexity)]
fn init_test_prover_params<FC: FCircuit<Fr, Params = ()>>() -> (
ProverParams<G1, G2, KZG<'static, Bn254>, Pedersen<G2>>,
KZGVerifierKey<Bn254>,
) {
let mut rng = ark_std::test_rng();
let poseidon_config = poseidon_canonical_config::<Fr>();
let f_circuit = FC::new(()).unwrap();
let (cs_len, cf_cs_len) =
get_cs_params_len::<G1, GVar, G2, GVar2, FC>(&poseidon_config, f_circuit).unwrap();
let (kzg_pk, kzg_vk): (KZGProverKey<G1>, KZGVerifierKey<Bn254>) =
KZG::<Bn254>::setup(&mut rng, cs_len).unwrap();
let (cf_pedersen_params, _) = Pedersen::<G2>::setup(&mut rng, cf_cs_len).unwrap();
let fs_prover_params = ProverParams::<G1, G2, KZG<Bn254>, Pedersen<G2>> {
poseidon_config: poseidon_config.clone(),
cs_pp: kzg_pk.clone(),
cf_cs_pp: cf_pedersen_params,
};
(fs_prover_params, kzg_vk)
}
/// Initializes Nova parameters and DeciderEth parameters. Only for test purposes.
#[allow(clippy::type_complexity)]
fn init_params<FC: FCircuit<Fr, Params = ()>>() -> (
ProverParams<G1, G2, KZG<'static, Bn254>, Pedersen<G2>>,
KZGVerifierKey<Bn254>,
ProvingKey<Bn254>,
G16VerifierKey<Bn254>,
) {
fn init_params<FC: FCircuit<Fr, Params = ()>>(
) -> ((FS_PP<FC>, FS_VP<FC>), (DECIDER_PP<FC>, DECIDER_VP<FC>)) {
let mut rng = rand::rngs::OsRng;
let start = Instant::now();
let (fs_prover_params, kzg_vk) = init_test_prover_params::<FC>();
println!("generated Nova folding params: {:?}", start.elapsed());
let f_circuit = FC::new(()).unwrap();
pub type NOVA_FCircuit<FC> =
Nova<G1, GVar, G2, GVar2, FC, KZG<'static, Bn254>, Pedersen<G2>>;
let z_0 = vec![Fr::zero(); f_circuit.state_len()];
let nova = NOVA_FCircuit::init(&fs_prover_params, f_circuit, z_0.clone()).unwrap();
let poseidon_config = poseidon_canonical_config::<Fr>();
let decider_circuit =
DeciderEthCircuit::<G1, GVar, G2, GVar2, KZG<Bn254>, Pedersen<G2>>::from_nova::<FC>(
nova.clone(),
)
.unwrap();
let start = Instant::now();
let (g16_pk, g16_vk) =
Groth16::<Bn254>::circuit_specific_setup(decider_circuit.clone(), &mut rng).unwrap();
println!(
"generated G16 (Decider circuit) params: {:?}",
start.elapsed()
let f_circuit = FC::new(()).unwrap();
let prep_param = PreprocessorParam::<G1, G2, FC, KZG<'static, Bn254>, Pedersen<G2>>::new(
poseidon_config,
f_circuit.clone(),
);
(fs_prover_params, kzg_vk, g16_pk, g16_vk)
let nova_params = NOVA::preprocess(&mut rng, &prep_param).unwrap();
let nova = NOVA::init(
nova_params.clone(),
f_circuit.clone(),
vec![Fr::zero(); f_circuit.state_len()].clone(),
)
.unwrap();
let decider_params =
DECIDER::preprocess(&mut rng, &nova_params.clone(), nova.clone()).unwrap();
(nova_params, decider_params)
}
/// This function allows to define which FCircuit to use for the test, and how many prove_step
@ -346,52 +344,31 @@ mod tests {
/// - modifies the z_0 and checks that it does not pass the EVM check
#[allow(clippy::type_complexity)]
fn nova_cyclefold_solidity_verifier_opt<FC: FCircuit<Fr, Params = ()>>(
params: (
ProverParams<G1, G2, KZG<'static, Bn254>, Pedersen<G2>>,
KZGVerifierKey<Bn254>,
ProvingKey<Bn254>,
G16VerifierKey<Bn254>,
),
fs_params: (FS_PP<FC>, FS_VP<FC>),
decider_params: (DECIDER_PP<FC>, DECIDER_VP<FC>),
z_0: Vec<Fr>,
n_steps: usize,
) {
let (fs_prover_params, kzg_vk, g16_pk, g16_vk) = params.clone();
pub type NOVA_FCircuit<FC> =
Nova<G1, GVar, G2, GVar2, FC, KZG<'static, Bn254>, Pedersen<G2>>;
pub type DECIDERETH_FCircuit<FC> = DeciderEth<
G1,
GVar,
G2,
GVar2,
FC,
KZG<'static, Bn254>,
Pedersen<G2>,
Groth16<Bn254>,
NOVA_FCircuit<FC>,
>;
let (decider_pp, decider_vp) = decider_params;
let f_circuit = FC::new(()).unwrap();
let nova_cyclefold_vk = NovaCycleFoldVerifierKey::from((
(g16_vk.clone(), kzg_vk.clone()),
f_circuit.state_len(),
));
let nova_cyclefold_vk =
NovaCycleFoldVerifierKey::from((decider_vp.clone(), f_circuit.state_len()));
let mut rng = rand::rngs::OsRng;
let mut nova = NOVA_FCircuit::init(&fs_prover_params, f_circuit, z_0).unwrap();
let mut nova = NOVA::<FC>::init(fs_params, f_circuit, z_0).unwrap();
for _ in 0..n_steps {
nova.prove_step(&mut rng, vec![]).unwrap();
}
let start = Instant::now();
let proof =
DECIDERETH_FCircuit::prove(rng, (g16_pk, fs_prover_params.cs_pp.clone()), nova.clone())
.unwrap();
let proof = DECIDER::<FC>::prove(rng, decider_pp, nova.clone()).unwrap();
println!("generated Decider proof: {:?}", start.elapsed());
let verified = DECIDERETH_FCircuit::<FC>::verify(
(g16_vk, kzg_vk),
let verified = DECIDER::<FC>::verify(
decider_vp,
nova.i,
nova.z_0.clone(),
nova.z_i.clone(),
@ -448,12 +425,22 @@ mod tests {
#[test]
fn nova_cyclefold_solidity_verifier() {
let params = init_params::<CubicFCircuit<Fr>>();
let (nova_params, decider_params) = init_params::<CubicFCircuit<Fr>>();
let z_0 = vec![Fr::from(3_u32)];
nova_cyclefold_solidity_verifier_opt::<CubicFCircuit<Fr>>(params.clone(), z_0.clone(), 2);
nova_cyclefold_solidity_verifier_opt::<CubicFCircuit<Fr>>(params.clone(), z_0.clone(), 3);
nova_cyclefold_solidity_verifier_opt::<CubicFCircuit<Fr>>(
nova_params.clone(),
decider_params.clone(),
z_0.clone(),
2,
);
nova_cyclefold_solidity_verifier_opt::<CubicFCircuit<Fr>>(
nova_params,
decider_params,
z_0,
3,
);
let params = init_params::<MultiInputsFCircuit<Fr>>();
let (nova_params, decider_params) = init_params::<MultiInputsFCircuit<Fr>>();
let z_0 = vec![
Fr::from(1_u32),
Fr::from(1_u32),
@ -462,12 +449,14 @@ mod tests {
Fr::from(1_u32),
];
nova_cyclefold_solidity_verifier_opt::<MultiInputsFCircuit<Fr>>(
params.clone(),
nova_params.clone(),
decider_params.clone(),
z_0.clone(),
2,
);
nova_cyclefold_solidity_verifier_opt::<MultiInputsFCircuit<Fr>>(
params.clone(),
nova_params,
decider_params,
z_0.clone(),
3,
);

+ 16
- 15
solidity-verifiers/templates/nova_cyclefold_decider.askama.sol

@ -78,10 +78,11 @@ contract NovaDecider is Groth16Verifier, KZG10Verifier {
// from gamma_abc_len, we subtract 1.
uint256[{{ public_inputs_len - 1 }}] memory public_inputs;
public_inputs[0] = i_z0_zi[0];
public_inputs[0] = {{pp_hash}};
public_inputs[1] = i_z0_zi[0];
for (uint i = 0; i < {{ z_len * 2 }}; i++) {
public_inputs[1 + i] = i_z0_zi[1 + i];
public_inputs[2 + i] = i_z0_zi[1 + i];
}
{
@ -91,9 +92,9 @@ contract NovaDecider is Groth16Verifier, KZG10Verifier {
uint256 x0 = rlc(U_i_x_u_i_cmW[0], U_i_u_u_i_u_r[2], u_i_x_cmT[0]);
uint256 x1 = rlc(U_i_x_u_i_cmW[1], U_i_u_u_i_u_r[2], u_i_x_cmT[1]);
public_inputs[{{ z_len * 2 + 1 }}] = u;
public_inputs[{{ z_len * 2 + 2 }}] = x0;
public_inputs[{{ z_len * 2 + 3 }}] = x1;
public_inputs[{{ z_len * 2 + 2 }}] = u;
public_inputs[{{ z_len * 2 + 3 }}] = x0;
public_inputs[{{ z_len * 2 + 4 }}] = x1;
}
{
@ -106,8 +107,8 @@ contract NovaDecider is Groth16Verifier, KZG10Verifier {
uint256[{{num_limbs}}] memory cmE_y_limbs = LimbsDecomposition.decompose(cmE[1]);
for (uint8 k = 0; k < {{num_limbs}}; k++) {
public_inputs[{{ z_len * 2 + 4 }} + k] = cmE_x_limbs[k];
public_inputs[{{ z_len * 2 + 4 + num_limbs }} + k] = cmE_y_limbs[k];
public_inputs[{{ z_len * 2 + 5 }} + k] = cmE_x_limbs[k];
public_inputs[{{ z_len * 2 + 5 + num_limbs }} + k] = cmE_y_limbs[k];
}
}
@ -124,8 +125,8 @@ contract NovaDecider is Groth16Verifier, KZG10Verifier {
uint256[{{num_limbs}}] memory cmW_y_limbs = LimbsDecomposition.decompose(cmW[1]);
for (uint8 k = 0; k < {{num_limbs}}; k++) {
public_inputs[{{ z_len * 2 + 4 + num_limbs * 2 }} + k] = cmW_x_limbs[k];
public_inputs[{{ z_len * 2 + 4 + num_limbs * 3 }} + k] = cmW_y_limbs[k];
public_inputs[{{ z_len * 2 + 5 + num_limbs * 2 }} + k] = cmW_x_limbs[k];
public_inputs[{{ z_len * 2 + 5 + num_limbs * 3 }} + k] = cmW_y_limbs[k];
}
}
@ -134,10 +135,10 @@ contract NovaDecider is Groth16Verifier, KZG10Verifier {
{
// add challenges
public_inputs[{{ z_len * 2 + 4 + num_limbs * 4 }}] = challenge_W_challenge_E_kzg_evals[0];
public_inputs[{{ z_len * 2 + 4 + num_limbs * 4 + 1 }}] = challenge_W_challenge_E_kzg_evals[1];
public_inputs[{{ z_len * 2 + 4 + num_limbs * 4 + 2 }}] = challenge_W_challenge_E_kzg_evals[2];
public_inputs[{{ z_len * 2 + 4 + num_limbs * 4 + 3 }}] = challenge_W_challenge_E_kzg_evals[3];
public_inputs[{{ z_len * 2 + 5 + num_limbs * 4 }}] = challenge_W_challenge_E_kzg_evals[0];
public_inputs[{{ z_len * 2 + 5 + num_limbs * 4 + 1 }}] = challenge_W_challenge_E_kzg_evals[1];
public_inputs[{{ z_len * 2 + 5 + num_limbs * 4 + 2 }}] = challenge_W_challenge_E_kzg_evals[2];
public_inputs[{{ z_len * 2 + 5 + num_limbs * 4 + 3 }}] = challenge_W_challenge_E_kzg_evals[3];
uint256[{{num_limbs}}] memory cmT_x_limbs;
uint256[{{num_limbs}}] memory cmT_y_limbs;
@ -146,8 +147,8 @@ contract NovaDecider is Groth16Verifier, KZG10Verifier {
cmT_y_limbs = LimbsDecomposition.decompose(u_i_x_cmT[3]);
for (uint8 k = 0; k < {{num_limbs}}; k++) {
public_inputs[{{ z_len * 2 + 4 + num_limbs * 4 }} + 4 + k] = cmT_x_limbs[k];
public_inputs[{{ z_len * 2 + 4 + num_limbs * 5}} + 4 + k] = cmT_y_limbs[k];
public_inputs[{{ z_len * 2 + 5 + num_limbs * 4 }} + 4 + k] = cmT_x_limbs[k];
public_inputs[{{ z_len * 2 + 5 + num_limbs * 5}} + 4 + k] = cmT_y_limbs[k];
}
// last element of the groth16 proof's public inputs is `r`

Loading…
Cancel
Save