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
jump to
@@ -82,13 +82,13 @@ let mut rng = rand::rngs::OsRng;
// 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() {
@@ -187,10 +187,11 @@ let mut rng = rand::rngs::OsRng;
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 @@ let (running_instance, incoming_instance, cyclefold_instance) = folding_scheme.instances();
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),
@@ -99,13 +99,13 @@ let mut rng = rand::rngs::OsRng;
// 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 {
@@ -141,10 +141,11 @@ >;
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 @@ let (running_instance, incoming_instance, cyclefold_instance) = folding_scheme.instances();
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),
@@ -126,10 +126,11 @@ let mut rng = rand::rngs::OsRng;
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 @@ let (running_instance, incoming_instance, cyclefold_instance) = folding_scheme.instances();
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),
@@ -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"}
@@ -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>; +}
@@ -4,8 +4,7 @@
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 @@ /// vector of coefficients
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 @@ }
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 @@
#[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;
@@ -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 B: SparseMatrix<F>,
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 {@@ -27,19 +51,6 @@
/// returns a tuple containing (w, x) (witness and public inputs respectively) pub fn split_z(&self, z: &[F]) -> (Vec<F>, Vec<F>) { (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
@@ -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 @@ /// CommitmentScheme defines the vector commitment scheme trait. Where `H` indicates if to use the
/// 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;
@@ -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 @@ #[allow(clippy::type_complexity)]
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 @@ for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
{ 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 @@ // Concatenate `U_i.cmE_is_inf`, `U_i.cmW_is_inf`, `u_i.cmE_is_inf`, `u_i.cmW_is_inf`, `cmT_is_inf`
// 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 @@ // compatible with the native get_challenge_native
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 @@ // Concatenate `U_i.cmE_is_inf`, `U_i.cmW_is_inf`, `u_i.cmE_is_inf`, `u_i.cmW_is_inf`, `cmT_is_inf`
// 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 @@ Ok(())
} } -/// 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 @@ )?;
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 @@ };
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 @@ })
.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 @@ x: std::iter::repeat_with(|| Fr::rand(&mut rng))
.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 @@ CycleFoldCommittedInstanceVar::<Projective, GVar>::new_witness(cs.clone(), || {
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();
@@ -7,7 +7,7 @@
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 @@ use ark_std::test_rng;
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
@@ -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 @@ #[allow(clippy::type_complexity)]
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 @@ self.r_x,
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 _c2: PhantomData<C2>,
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 @@ _c2: PhantomData,
_gc2: PhantomData, poseidon_config: poseidon_config.clone(), ccs, + pp_hash: None, i: None, i_usize: None, z_0: None,@@ -559,6 +562,7 @@ let u_i = CCCS::<C1>::dummy(ccs.l);
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 @@ _c2: PhantomData,
_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 @@ C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
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 @@
// 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 @@ // P.3. NIMFS.verify, obtains U_{i+1} by folding [U_i] & [u_i].
// 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 @@
// 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 @@ // cf_r_bits is denoted by rho* in the paper.
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 @@ // Back to Primary Part
// 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 @@ use std::time::Instant;
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 @@ let z1 = get_test_z::<Fr>(3);
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 @@ .to_lcccs::<_, _, Pedersen<Projective>>(&mut rng, &pedersen_params, &z1)
.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 @@ Pedersen::<Projective>::setup(&mut rng, ccs.n - ccs.l - 1).unwrap();
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 @@ let mut u_i = u_dummy.clone();
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 @@ W_i1 = Witness::<Fr>::dummy(&ccs);
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 @@ _c2: PhantomData,
_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 @@ };
} 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 @@ // sanity check: check the folded instance relation
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 @@ >(
&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 @@ .unwrap();
// 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 @@ _c2: PhantomData,
_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 @@ assert_eq!(u_i.x, r1cs_x_i1);
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);
@@ -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 @@ /// Returns `H(i, z_0, z_i, U_i)`, where `i` can be `i` but also `i+1`, and `U_i` is the LCCCS.
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 @@
CRH::<C::ScalarField>::evaluate( poseidon_config, vec![ - vec![i], + vec![pp_hash, i], z_0, z_i, C_x,@@ -164,9 +165,10 @@ use ark_std::Zero;
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;
@@ -24,16 +24,17 @@ cyclefold::{fold_cyclefold_circuit, CycleFoldCircuit},
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}, };@@ -57,22 +58,6 @@ }
} #[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 C1: CurveGroup,@@ -97,8 +82,27 @@ > {
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 @@ /// CycleFold CommitmentScheme::ProverParams, over C2
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 @@
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 @@
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 @@ cf_r1cs.dummy_instance();
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 @@ poseidon_config: pp.poseidon_config.clone(),
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 @@ U_i1 = LCCCS::dummy(self.ccs.l, self.ccs.t, self.ccs.s);
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 @@ )?;
// 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 @@ };
} 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 @@ U_i1.check_relation(&self.ccs, &W_i1)?;
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 @@ fold_cyclefold_circuit::<C1, GC1, C2, GC2, FC, CS1, CS2>(
&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 @@ if u_i.x.len() != 2 || U_i.x.len() != 2 {
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 @@
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 {
@@ -11,7 +11,7 @@ lcccs::LCCCS,
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 @@
#[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;
@@ -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 @@ use ark_std::UniformRand;
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;
@@ -94,6 +94,7 @@ #[allow(clippy::type_complexity)]
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 @@ self.cmE.to_constraint_field()?,
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 @@ <C as Group>::ScalarField: Absorb,
{ 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 @@ let (cmT_x, cmT_y) = nonnative_affine_to_field_elements::<C>(cmT)?;
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 @@ // compatible with the native get_challenge_native
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 @@ ) -> Result<Vec<Boolean<C::ScalarField>>, SynthesisError> {
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 @@ for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
{ 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 @@ pub fn empty(poseidon_config: &PoseidonConfig<CF1<C1>>, F_circuit: FC) -> Self {
Self { _gc2: PhantomData, poseidon_config: poseidon_config.clone(), + pp_hash: None, i: None, i_usize: None, z_0: None,@@ -317,6 +324,9 @@ C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
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 @@
// 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 @@ // compute r = H(u_i, U_i, cmT)
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 @@ // Base case: u_{i+1}.x[0] == H((i+1, z_0, z_{i+1}, U_{\bot})
// 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 @@ // cf_r_bits is denoted by rho* in the paper.
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 @@ // same for cf2_r:
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 @@ // Back to Primary Part
// 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 @@ #[test]
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 @@ };
// 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 @@
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 @@ x: vec![Fr::rand(&mut rng); 1],
}; 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 @@ .unwrap();
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 @@ .concat();
let r_bitsVar = ChallengeGadget::<Projective>::get_challenge_gadget( cs.clone(), &poseidon_config, + pp_hashVar, U_iVar_vec, u_iVar, cmTVar,
@@ -90,7 +90,8 @@ {
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 @@ #[allow(clippy::type_complexity)]
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 @@ if i <= C1::ScalarField::one() {
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 @@ let (cmW_x, cmW_y) = NonNativeAffineVar::inputize(U.cmW)?;
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 @@
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, )
@@ -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 @@ pub cf_r1cs: R1CS<C2::ScalarField>,
/// 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 @@ &nova.U_i.clone(),
)?; 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 @@ r1cs: nova.r1cs,
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 @@ R1CSVar::<C1::ScalarField, CF1<C1>, FpVar<CF1<C1>>>::new_witness(cs.clone(), || {
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 @@ u_i.cmE.y.enforce_equal_unaligned(&zero)?;
(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 @@ Ok(self.cf_W_i.unwrap_or(w_dummy_native.clone()))
})?; // 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 @@ NonNativeAffineVar::new_input(cs.clone(), || Ok(self.cmT.unwrap_or_else(C1::zero)))?;
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 @@ use ark_std::{One, UniformRand};
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 @@
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 @@ Pedersen<Projective>,
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,
@@ -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 @@ /// `CommittedInstance`.
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 @@
CRH::<C::ScalarField>::evaluate( poseidon_config, vec![ - vec![i], + vec![pp_hash, i], z_0, z_i, vec![self.u],@@ -140,9 +140,13 @@ /// Returns `H(U_i)`, where `U_i` is the `CommittedInstance` for CycleFold.
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 @@ 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 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 @@ /// CycleFold CommitmentScheme::ProverParams, over C2
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 @@ 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_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 @@ let verifier_params = VerifierParams::<C1, C2, CS1, CS2> {
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 @@ cs2.finalize();
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 @@ poseidon_config: pp.poseidon_config.clone(),
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 @@
// 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 @@ // folded instance output (public input, x)
// 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 @@ // u_{i+1}.x[1] = H(cf_U_{i+1})
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 @@ // fold [the output from folding self.cf_U_i + cfW_U] + cfE_U = folded_running_with_cfW + cfE
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 @@ if u_i.x.len() != 2 || U_i.x.len() != 2 {
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 @@ fold_cyclefold_circuit::<C1, GC1, C2, GC2, FC, CS1, CS2>(
&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 @@ cs_vp: None,
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 @@ assert_eq!(Fr::from(num_steps as u32), nova.i);
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,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 @@ use ark_ff::{BigInteger, PrimeField};
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 @@ .unwrap();
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,
@@ -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 @@ &self,
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 @@ self.cf_U_i.serialize_with_mode(&mut writer, compress)
} 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 @@ validate: ark_serialize::Validate,
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 @@ Ok(Nova {
_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 @@ W_i,
U_i, cf_W_i, cf_U_i, - r1cs, - cf_r1cs, - poseidon_config, - F: f_circuit, }) } }@@ -174,17 +178,12 @@ #[cfg(test)]
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 @@ fn test_serde_nova() {
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 @@ CubicFCircuit<Fr>,
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 @@ >::deserialize_nova(
bytes.as_slice(), Compress::No, Validate::No, - prover_params, + nova_params.0, // Nova's prover params poseidon_config, ) .unwrap();
@@ -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
@@ -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 @@ use super::*;
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};
@@ -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 @@ prep_param: &Self::PreprocessorParam,
) -> 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>;
@@ -104,7 +104,7 @@ #[cfg(test)]
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},
@@ -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 @@ let cm = cm.into_affine();
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, + )) +}
@@ -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 @@
// 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 @@ pub const FUNCTION_SELECTOR_GROTH16_VERIFY_PROOF: [u8; 4] = [0x43, 0x75, 0x3b, 0x4d];
#[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 @@
#[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();
@@ -102,7 +102,7 @@ const FUNCTION_SELECTOR_KZG10_CHECK: [u8; 4] = [0x9e, 0x78, 0xcc, 0xf7];
#[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 @@ }
#[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 @@ let poseidon_config = poseidon_canonical_config::<Fr>();
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))
@@ -97,6 +97,7 @@ #[allow(clippy::type_complexity)]
pub fn setup<'a>( n: usize, ) -> ( + Fr, // public params hash KZGProverKey<'a, G1>, KZGVerifierKey<Bn254>, ark_groth16::ProvingKey<Bn254>,@@ -115,6 +116,7 @@ let (g16_pk, g16_vk) = Groth16::<Bn254>::setup(circuit, &mut rng).unwrap();
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) } }
@@ -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 @@
#[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 @@ let groth16_verifier = Groth16Verifier::from(value.g16_vk);
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 @@ }
#[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 @@ .into_bytes()
} } -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 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 @@
#[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 @@ use std::marker::PhantomData;
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,@@ -155,6 +165,24 @@ utils::{get_function_selector_for_nova_cyclefold_verifier, HeaderInclusion},
verifiers::nova_cyclefold::get_decider_template_for_cyclefold_decider, 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)]@@ -256,10 +284,10 @@ }
#[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 @@ }
#[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 @@
save_solidity("NovaDecider.sol", &decider_solidity_code.render().unwrap()); } + /// Initializes Nova parameters and DeciderEth parameters. Only for test purposes. #[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(); + 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 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>, - ) { - 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 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 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 @@ /// - modifies the calldata and checks that it does not pass the EVM check
/// - 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(); + let (decider_pp, decider_vp) = decider_params; - 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 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 @@ }
#[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 @@ Fr::from(1_u32),
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, );
@@ -78,10 +78,11 @@
// 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 @@ // U_i.x + r * u_i.x
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 @@ uint256[{{num_limbs}}] memory cmE_x_limbs = LimbsDecomposition.decompose(cmE[0]);
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 @@ uint256[{{num_limbs}}] memory cmW_x_limbs = LimbsDecomposition.decompose(cmW[0]);
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 @@ }
{ // 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 @@ cmT_x_limbs = LimbsDecomposition.decompose(u_i_x_cmT[2]);
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`