From 4ce9a130d0cb27f28eb9cff5b8d64f7e5d2a7b82 Mon Sep 17 00:00:00 2001 From: arnaucube Date: Tue, 25 Jun 2024 11:19:55 +0200 Subject: [PATCH] Add CycleFold (https://eprint.iacr.org/2023/1192.pdf) to HyperNova impl (#113) --- .../folding/{nova => circuits}/cyclefold.rs | 116 ++++- folding-schemes/src/folding/circuits/mod.rs | 1 + .../src/folding/hypernova/circuits.rs | 438 +++++++++++++----- .../src/folding/hypernova/lcccs.rs | 4 +- .../src/folding/hypernova/nimfs.rs | 32 +- .../src/folding/hypernova/utils.rs | 2 +- folding-schemes/src/folding/nova/circuits.rs | 8 +- folding-schemes/src/folding/nova/mod.rs | 66 +-- folding-schemes/src/folding/nova/serialize.rs | 4 +- 9 files changed, 454 insertions(+), 217 deletions(-) rename folding-schemes/src/folding/{nova => circuits}/cyclefold.rs (83%) diff --git a/folding-schemes/src/folding/nova/cyclefold.rs b/folding-schemes/src/folding/circuits/cyclefold.rs similarity index 83% rename from folding-schemes/src/folding/nova/cyclefold.rs rename to folding-schemes/src/folding/circuits/cyclefold.rs index 6ac5bcd..184f018 100644 --- a/folding-schemes/src/folding/nova/cyclefold.rs +++ b/folding-schemes/src/folding/circuits/cyclefold.rs @@ -1,4 +1,5 @@ -/// contains [CycleFold](https://eprint.iacr.org/2023/1192.pdf) related circuits +/// Contains [CycleFold](https://eprint.iacr.org/2023/1192.pdf) related circuits and functions that +/// are shared across the different folding schemes use ark_crypto_primitives::{ crh::{ poseidon::constraints::{CRHGadget, CRHParametersVar}, @@ -10,8 +11,8 @@ use ark_crypto_primitives::{ Absorb, CryptographicSponge, }, }; -use ark_ec::{AffineRepr, CurveGroup}; -use ark_ff::{Field, PrimeField, ToConstraintField}; +use ark_ec::{AffineRepr, CurveGroup, Group}; +use ark_ff::{BigInteger, Field, PrimeField, ToConstraintField}; use ark_r1cs_std::{ alloc::{AllocVar, AllocationMode}, boolean::Boolean, @@ -21,18 +22,23 @@ use ark_r1cs_std::{ prelude::CurveVar, ToConstraintFieldGadget, }; -use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, Namespace, SynthesisError}; +use ark_relations::r1cs::{ + ConstraintSynthesizer, ConstraintSystem, ConstraintSystemRef, Namespace, SynthesisError, +}; use ark_std::fmt::Debug; use ark_std::{One, Zero}; use core::{borrow::Borrow, marker::PhantomData}; -use super::CommittedInstance; +use super::{nonnative::uint::NonNativeUintVar, CF2}; +use crate::ccs::r1cs::{extract_w_x, R1CS}; +use crate::commitment::CommitmentScheme; use crate::constants::N_BITS_RO; -use crate::folding::circuits::{nonnative::uint::NonNativeUintVar, CF2}; +use crate::folding::nova::{nifs::NIFS, CommittedInstance, Witness}; +use crate::frontend::FCircuit; use crate::Error; -// public inputs length for the CycleFoldCircuit: |[r, p1.x,y, p2.x,y, p3.x,y]| -pub const CF_IO_LEN: usize = 7; +/// Public inputs length for the CycleFoldCircuit: |[r, p1.x,y, p2.x,y, p3.x,y]| +pub(crate) const CF_IO_LEN: usize = 7; /// CycleFoldCommittedInstanceVar is the CycleFold CommittedInstance representation in the Nova /// circuit. @@ -78,8 +84,9 @@ where ::BaseField: ark_ff::PrimeField + Absorb, for<'a> &'a GC: GroupOpsBounds<'a, C, GC>, { - // Extract the underlying field elements from `CycleFoldCommittedInstanceVar`, in the order of - // `u`, `x`, `cmE.x`, `cmE.y`, `cmW.x`, `cmW.y`, `cmE.is_inf || cmW.is_inf` (|| is for concat). + /// Extracts the underlying field elements from `CycleFoldCommittedInstanceVar`, in the order + /// of `u`, `x`, `cmE.x`, `cmE.y`, `cmW.x`, `cmW.y`, `cmE.is_inf || cmW.is_inf` (|| is for + /// concat). fn to_constraint_field(&self) -> Result>>, SynthesisError> { let mut cmE_elems = self.cmE.to_constraint_field()?; let mut cmW_elems = self.cmW.to_constraint_field()?; @@ -112,10 +119,10 @@ where for<'a> &'a GC: GroupOpsBounds<'a, C, GC>, { /// hash implements the committed instance hash compatible with the native implementation from - /// CommittedInstance.hash_cyclefold. - /// Returns `H(U_i)`, where `U` is the `CommittedInstance` for CycleFold. - /// Additionally it returns the vector of the field elements from the self parameters, so they - /// can be reused in other gadgets avoiding recalculating (reconstraining) them. + /// CommittedInstance.hash_cyclefold. Returns `H(U_i)`, where `U` is the `CommittedInstance` + /// for CycleFold. Additionally it returns the vector of the field elements from the self + /// parameters, so they can be reused in other gadgets avoiding recalculating (reconstraining) + /// them. #[allow(clippy::type_complexity)] pub fn hash( self, @@ -365,15 +372,94 @@ where } } +/// Folds the given cyclefold circuit and its instances. This method is isolated from any folding +/// scheme struct because it is used both by Nova & HyperNova's CycleFold. +#[allow(clippy::type_complexity)] +pub fn fold_cyclefold_circuit( + poseidon_config: &PoseidonConfig, + cf_r1cs: R1CS, + cf_cs_params: CS2::ProverParams, + cf_W_i: Witness, // witness of the running instance + cf_U_i: CommittedInstance, // running instance + cf_u_i_x: Vec, + cf_circuit: CycleFoldCircuit, +) -> Result< + ( + Witness, + CommittedInstance, // u_i + Witness, // W_i1 + CommittedInstance, // U_i1 + C2, // cmT + C2::ScalarField, // r_Fq + ), + Error, +> +where + C1: CurveGroup, + GC1: CurveVar> + ToConstraintFieldGadget>, + C2: CurveGroup, + GC2: CurveVar> + ToConstraintFieldGadget>, + FC: FCircuit, + CS1: CommitmentScheme, + CS2: CommitmentScheme, + ::BaseField: PrimeField, + ::BaseField: PrimeField, + ::ScalarField: Absorb, + ::ScalarField: Absorb, + C1: CurveGroup, + for<'a> &'a GC1: GroupOpsBounds<'a, C1, GC1>, + for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>, +{ + let cs2 = ConstraintSystem::::new_ref(); + cf_circuit.generate_constraints(cs2.clone())?; + + let cs2 = cs2.into_inner().ok_or(Error::NoInnerConstraintSystem)?; + let (cf_w_i, cf_x_i) = extract_w_x::(&cs2); + if cf_x_i != cf_u_i_x { + return Err(Error::NotEqual); + } + + #[cfg(test)] + assert_eq!(cf_x_i.len(), CF_IO_LEN); + + // fold cyclefold instances + let cf_w_i = Witness::::new(cf_w_i.clone(), cf_r1cs.A.n_rows); + let cf_u_i: CommittedInstance = cf_w_i.commit::(&cf_cs_params, cf_x_i.clone())?; + + // compute T* and cmT* for CycleFoldCircuit + let (cf_T, cf_cmT) = NIFS::::compute_cyclefold_cmT( + &cf_cs_params, + &cf_r1cs, + &cf_w_i, + &cf_u_i, + &cf_W_i, + &cf_U_i, + )?; + + let cf_r_bits = CycleFoldChallengeGadget::::get_challenge_native( + poseidon_config, + cf_U_i.clone(), + cf_u_i.clone(), + cf_cmT, + )?; + let cf_r_Fq = C1::BaseField::from_bigint(BigInteger::from_bits_le(&cf_r_bits)) + .expect("cf_r_bits out of bounds"); + + let (cf_W_i1, cf_U_i1) = NIFS::::fold_instances( + cf_r_Fq, &cf_W_i, &cf_U_i, &cf_w_i, &cf_u_i, &cf_T, cf_cmT, + )?; + Ok((cf_w_i, cf_u_i, cf_W_i1, cf_U_i1, cf_cmT, cf_r_Fq)) +} + #[cfg(test)] pub mod tests { - use super::*; use ark_bn254::{constraints::GVar, Fq, Fr, G1Projective as Projective}; use ark_ff::BigInteger; use ark_r1cs_std::R1CSVar; use ark_relations::r1cs::ConstraintSystem; use ark_std::UniformRand; + use super::*; use crate::folding::nova::get_cm_coordinates; use crate::folding::nova::nifs::tests::prepare_simple_fold_inputs; use crate::transcript::poseidon::poseidon_canonical_config; diff --git a/folding-schemes/src/folding/circuits/mod.rs b/folding-schemes/src/folding/circuits/mod.rs index 3854c43..40d6b86 100644 --- a/folding-schemes/src/folding/circuits/mod.rs +++ b/folding-schemes/src/folding/circuits/mod.rs @@ -2,6 +2,7 @@ use ark_ec::{AffineRepr, CurveGroup}; use ark_ff::Field; +pub mod cyclefold; pub mod nonnative; pub mod sum_check; pub mod utils; diff --git a/folding-schemes/src/folding/hypernova/circuits.rs b/folding-schemes/src/folding/hypernova/circuits.rs index 06449f4..80ac548 100644 --- a/folding-schemes/src/folding/hypernova/circuits.rs +++ b/folding-schemes/src/folding/hypernova/circuits.rs @@ -1,4 +1,4 @@ -/// Implementation of [HyperNova](https://eprint.iacr.org/2023/573.pdf) NIMFS verifier circuit +/// Implementation of [HyperNova](https://eprint.iacr.org/2023/573.pdf) circuits use ark_crypto_primitives::crh::{ poseidon::constraints::{CRHGadget, CRHParametersVar}, CRHSchemeGadget, @@ -27,21 +27,24 @@ use super::{ nimfs::{NIMFSProof, NIMFS}, Witness, }; -use crate::folding::circuits::{ - nonnative::affine::NonNativeAffineVar, - sum_check::{IOPProofVar, SumCheckVerifierGadget, VPAuxInfoVar}, - utils::EqEvalGadget, - CF1, CF2, +use crate::constants::N_BITS_RO; +use crate::folding::{ + circuits::cyclefold::{ + CycleFoldChallengeGadget, CycleFoldCommittedInstanceVar, NIFSFullGadget, CF_IO_LEN, + }, + circuits::{ + nonnative::{affine::NonNativeAffineVar, uint::NonNativeUintVar}, + sum_check::{IOPProofVar, SumCheckVerifierGadget, VPAuxInfoVar}, + utils::EqEvalGadget, + CF1, CF2, + }, + nova::{get_r1cs_from_cs, CommittedInstance}, }; -use crate::folding::nova::get_r1cs_from_cs; use crate::frontend::FCircuit; use crate::utils::virtual_polynomial::VPAuxInfo; use crate::Error; use crate::{ - ccs::{ - r1cs::{extract_r1cs, extract_w_x}, - CCS, - }, + ccs::{r1cs::extract_r1cs, CCS}, transcript::{ poseidon::{PoseidonTranscript, PoseidonTranscriptVar}, Transcript, TranscriptVar, @@ -56,7 +59,7 @@ where { // Commitment to witness pub C: NonNativeAffineVar, - // Public input/output + // Public io pub x: Vec>>, } impl AllocVar, CF1> for CCCSVar @@ -91,7 +94,7 @@ where pub C: NonNativeAffineVar, // Relaxation factor of z for folded LCCCS pub u: FpVar>, - // Public input/output + // Public io pub x: Vec>>, // Random evaluation point for the v_i pub r_x: Vec>>, @@ -216,6 +219,9 @@ impl NIMFSGadget where ::BaseField: PrimeField, { + /// Runs (in-circuit) the NIMFS.V, which outputs the new folded LCCCS instance together with + /// the rho_bits, which will be used in other parts of the AugmentedFCircuit + #[allow(clippy::type_complexity)] pub fn verify( cs: ConstraintSystemRef>, // only used the CCS params, not the matrices @@ -226,7 +232,7 @@ where new_instances: &[CCCSVar], proof: ProofVar, enabled: Boolean, - ) -> Result, SynthesisError> { + ) -> Result<(LCCCSVar, Vec>>), SynthesisError> { // absorb instances to transcript for U_i in running_instances { let v = [ @@ -306,18 +312,24 @@ where let rho_scalar_raw = C::ScalarField::from_le_bytes_mod_order(b"rho"); let rho_scalar: FpVar> = FpVar::>::new_constant(cs.clone(), rho_scalar_raw)?; transcript.absorb(rho_scalar)?; - let rho: FpVar> = transcript.get_challenge()?; - - // return the folded instance - Self::fold( - running_instances, - new_instances, - proof.sigmas_thetas, - r_x_prime, - rho, - ) + let rho_bits: Vec>> = transcript.get_challenge_nbits(N_BITS_RO)?; + let rho = Boolean::le_bits_to_fp_var(&rho_bits)?; + + // return the folded instance, together with the rho_bits so they can be used in other + // parts of the AugmentedFCircuit + Ok(( + Self::fold( + running_instances, + new_instances, + proof.sigmas_thetas, + r_x_prime, + rho, + )?, + rho_bits, + )) } + /// Runs (in-circuit) the verifier side of the fold, computing the new folded LCCCS instance #[allow(clippy::type_complexity)] fn fold( lcccs: &[LCCCSVar], @@ -380,7 +392,7 @@ where } } -/// computes c from the step 5 in section 5 of HyperNova, adapted to multiple LCCCS & CCCS +/// Computes c from the step 5 in section 5 of HyperNova, adapted to multiple LCCCS & CCCS /// instances: /// $$ /// c = \sum_{i \in [\mu]} \left(\sum_{j \in [t]} \gamma^{i \cdot t + j} \cdot e_i \cdot \sigma_{i,j} \right) @@ -454,6 +466,12 @@ pub struct AugmentedFCircuit< pub F: FC, // F circuit pub x: Option>, // public input (u_{i+1}.x[0]) pub nimfs_proof: Option>, + + // cyclefold verifier on C1 + pub cf_u_i_cmW: Option, // input, cf_u_i.cmW + pub cf_U_i: Option>, // input, RelaxedR1CS CycleFold instance + pub cf_x: Option>, // public input (cf_u_{i+1}.x[1]) + pub cf_cmT: Option, } impl AugmentedFCircuit @@ -490,6 +508,10 @@ where F: F_circuit, x: None, nimfs_proof: None, + cf_u_i_cmW: None, + cf_U_i: None, + cf_x: None, + cf_cmT: None, } } @@ -527,68 +549,52 @@ where /// feed in as parameter for the AugmentedFCircuit::empty method to avoid computing them there. pub fn upper_bound_ccs(&self) -> Result, Error> { let r1cs = get_r1cs_from_cs::>(self.clone()).unwrap(); - let mut ccs = CCS::from_r1cs(r1cs.clone()); + let ccs = CCS::from_r1cs(r1cs.clone()); let z_0 = vec![C1::ScalarField::zero(); self.F.state_len()]; - let mut W_i = - Witness::::new(vec![C1::ScalarField::zero(); ccs.n - ccs.l - 1]); - let mut U_i = LCCCS::::dummy(ccs.l, ccs.t, ccs.s); - let mut w_i = W_i.clone(); - let mut u_i = CCCS::::dummy(ccs.l); - - let n_iters = 3; - - for _ in 0..n_iters { - let mut transcript_p: PoseidonTranscript = - PoseidonTranscript::::new(&self.poseidon_config.clone()); - let (nimfs_proof, U_i1, _) = NIMFS::>::prove( - &mut transcript_p, - &ccs, - &[U_i.clone()], - &[u_i.clone()], - &[W_i.clone()], - &[w_i.clone()], - ) - .unwrap(); + let W_i = Witness::::dummy(&ccs); + let U_i = LCCCS::::dummy(ccs.l, ccs.t, ccs.s); + let w_i = W_i.clone(); + let u_i = CCCS::::dummy(ccs.l); + + let mut transcript_p: PoseidonTranscript = + PoseidonTranscript::::new(&self.poseidon_config.clone()); + let (nimfs_proof, U_i1, _, _) = NIMFS::>::prove( + &mut transcript_p, + &ccs, + &[U_i.clone()], + &[u_i.clone()], + &[W_i.clone()], + &[w_i.clone()], + )?; - let augmented_f_circuit = Self { - _c2: PhantomData, - _gc2: PhantomData, - poseidon_config: self.poseidon_config.clone(), - ccs: ccs.clone(), - i: Some(C1::ScalarField::zero()), - i_usize: Some(0), - z_0: Some(z_0.clone()), - z_i: Some(z_0.clone()), - external_inputs: Some(vec![]), - u_i_C: Some(u_i.C), - U_i: Some(U_i.clone()), - U_i1_C: Some(U_i1.C), - F: self.F.clone(), - x: Some(C1::ScalarField::zero()), - nimfs_proof: Some(nimfs_proof), - }; - - let cs: ConstraintSystem; - (cs, ccs) = augmented_f_circuit.compute_cs_ccs()?; - - // prepare instances for next loop iteration - let (r1cs_w_i1, r1cs_x_i1) = extract_w_x::(&cs); // includes 1 and public inputs - u_i = CCCS:: { - C: u_i.C, - x: r1cs_x_i1, - }; - w_i = Witness:: { - w: r1cs_w_i1.clone(), - r_w: C1::ScalarField::one(), - }; - W_i = Witness::::dummy(&ccs); - U_i = LCCCS::::dummy(ccs.l, ccs.t, ccs.s); - } - Ok(ccs) + let augmented_f_circuit = Self { + _c2: PhantomData, + _gc2: PhantomData, + poseidon_config: self.poseidon_config.clone(), + ccs: ccs.clone(), + i: Some(C1::ScalarField::zero()), + i_usize: Some(0), + z_0: Some(z_0.clone()), + z_i: Some(z_0.clone()), + external_inputs: Some(vec![]), + u_i_C: Some(u_i.C), + U_i: Some(U_i.clone()), + U_i1_C: Some(U_i1.C), + F: self.F.clone(), + x: Some(C1::ScalarField::zero()), + nimfs_proof: Some(nimfs_proof), + // cyclefold values + cf_u_i_cmW: None, + cf_U_i: None, + cf_x: None, + cf_cmT: None, + }; + + Ok(augmented_f_circuit.compute_cs_ccs()?.1) } - /// returns the cs (ConstraintSystem) and the CCS out of the AugmentedFCircuit + /// Returns the cs (ConstraintSystem) and the CCS out of the AugmentedFCircuit #[allow(clippy::type_complexity)] fn compute_cs_ccs( &self, @@ -651,6 +657,12 @@ where Ok(self.nimfs_proof.unwrap_or(nimfs_proof_dummy)) })?; + let cf_u_dummy = CommittedInstance::dummy(CF_IO_LEN); + let cf_U_i = CycleFoldCommittedInstanceVar::::new_witness(cs.clone(), || { + Ok(self.cf_U_i.unwrap_or(cf_u_dummy.clone())) + })?; + let cf_cmT = GC2::new_witness(cs.clone(), || Ok(self.cf_cmT.unwrap_or_else(C2::zero)))?; + let crh_params = CRHParametersVar::::new_constant( cs.clone(), self.poseidon_config.clone(), @@ -671,6 +683,8 @@ where let (u_i_x, _) = U_i .clone() .hash(&crh_params, 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)?; // P.2. Construct u_i let u_i = CCCSVar:: { @@ -679,7 +693,7 @@ where Ok(self.u_i_C.unwrap_or(C1::zero())) })?, // u_i.x is computed in step 1 - x: vec![u_i_x], + x: vec![u_i_x, cf_u_i_x], }; // P.3. NIMFS.verify, obtains U_{i+1} by folding [U_i] & [u_i]. @@ -688,7 +702,7 @@ where // other curve. let transcript = PoseidonTranscriptVar::::new(cs.clone(), &self.poseidon_config); - let mut U_i1 = NIMFSGadget::::verify( + let (mut U_i1, rho_bits) = NIMFSGadget::::verify( cs.clone(), &self.ccs.clone(), transcript, @@ -700,23 +714,80 @@ where U_i1.C = U_i1_C; // P.4.a compute and check the first output of F' - // Base case: u_{i+1}.x[0] == H((1, z_0, z_{i+1}, U_{i+1}) - // 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, i + FpVar::>::one(), z_0.clone(), z_i1.clone(), )?; - let (u_i1_x_base, _) = U_i1.hash( - &crh_params, - FpVar::>::one(), - z_0.clone(), - z_i1.clone(), + let x = FpVar::new_input(cs.clone(), || Ok(self.x.unwrap_or(C1::ScalarField::zero())))?; + x.enforce_equal(&u_i1_x)?; + + // convert rho_bits to a `NonNativeFieldVar` + let rho_nonnat = { + let mut bits = rho_bits; + bits.resize(C1::BaseField::MODULUS_BIT_SIZE as usize, Boolean::FALSE); + NonNativeUintVar::from(&bits) + }; + + // CycleFold part + // C.1. Compute cf1_u_i.x and cf2_u_i.x + let cf_x = vec![ + rho_nonnat, U_i.C.x, U_i.C.y, u_i.C.x, u_i.C.y, U_i1.C.x, U_i1.C.y, + ]; + + // ensure that cf_u has as public inputs the C from main instances U_i, u_i, U_i+1 + // coordinates of the commitments. + // C.2. Construct `cf_u_i` + let cf_u_i = CycleFoldCommittedInstanceVar { + // cf1_u_i.cmE = 0. Notice that we enforce cmE to be equal to 0 since it is allocated + // as 0. + cmE: GC2::zero(), + // cf1_u_i.u = 1 + u: NonNativeUintVar::new_constant(cs.clone(), C1::BaseField::one())?, + // cf_u_i.cmW is provided by the prover as witness + cmW: GC2::new_witness(cs.clone(), || Ok(self.cf_u_i_cmW.unwrap_or(C2::zero())))?, + // cf_u_i.x is computed in step 1 + x: cf_x, + }; + + // C.3. nifs.verify (fold_committed_instance), obtains cf_U_{i+1} by folding cf_u_i & cf_U_i. + // compute cf_r = H(cf_u_i, cf_U_i, cf_cmT) + // cf_r_bits is denoted by rho* in the paper. + let cf_r_bits = CycleFoldChallengeGadget::::get_challenge_gadget( + cs.clone(), + &self.poseidon_config, + cf_U_i_vec, + cf_u_i.clone(), + cf_cmT.clone(), + )?; + // Convert cf_r_bits to a `NonNativeFieldVar` + let cf_r_nonnat = { + let mut bits = cf_r_bits.clone(); + bits.resize(C1::BaseField::MODULUS_BIT_SIZE as usize, Boolean::FALSE); + NonNativeUintVar::from(&bits) + }; + // Fold cf1_u_i & cf_U_i into cf1_U_{i+1} + let cf_U_i1 = NIFSFullGadget::::fold_committed_instance( + cf_r_bits, + cf_r_nonnat, + cf_cmT, + cf_U_i, + cf_u_i, )?; - let x = FpVar::new_input(cs.clone(), || Ok(self.x.unwrap_or(u_i1_x_base.value()?)))?; - x.enforce_equal(&is_basecase.select(&u_i1_x_base, &u_i1_x)?)?; + // 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_base, _) = + CycleFoldCommittedInstanceVar::new_constant(cs.clone(), cf_u_dummy)? + .hash(&crh_params)?; + let cf_x = FpVar::new_input(cs.clone(), || { + Ok(self.cf_x.unwrap_or(cf_u_i1_x_base.value()?)) + })?; + cf_x.enforce_equal(&is_basecase.select(&cf_u_i1_x_base, &cf_u_i1_x)?)?; Ok(()) } @@ -724,7 +795,8 @@ where #[cfg(test)] mod tests { - use ark_bn254::{Fr, G1Projective as Projective}; + use ark_bn254::{constraints::GVar, Fq, Fr, G1Projective as Projective}; + use ark_ff::BigInteger; use ark_grumpkin::{constraints::GVar as GVar2, Projective as Projective2}; use ark_r1cs_std::{alloc::AllocVar, fields::fp::FpVar, R1CSVar}; use ark_std::{test_rng, UniformRand}; @@ -738,10 +810,16 @@ mod tests { CCS, }, commitment::{pedersen::Pedersen, CommitmentScheme}, - folding::hypernova::{ - nimfs::NIMFS, - utils::{compute_c, compute_sigmas_thetas}, - Witness, + folding::{ + circuits::cyclefold::{fold_cyclefold_circuit, CycleFoldCircuit}, + hypernova::{ + nimfs::NIMFS, + utils::{compute_c, compute_sigmas_thetas}, + Witness, + }, + nova::{ + get_cm_coordinates, traits::NovaR1CS, CommittedInstance, Witness as NovaWitness, + }, }, frontend::tests::CubicFCircuit, transcript::{ @@ -891,7 +969,7 @@ mod tests { PoseidonTranscript::::new(&poseidon_config); // Run the prover side of the multifolding - let (proof, folded_lcccs, folded_witness) = + let (proof, folded_lcccs, folded_witness, _) = NIMFS::>::prove( &mut transcript_p, &ccs, @@ -935,7 +1013,7 @@ mod tests { let transcriptVar = PoseidonTranscriptVar::::new(cs.clone(), &poseidon_config); let enabled = Boolean::::new_witness(cs.clone(), || Ok(true)).unwrap(); - let folded_lcccsVar = NIMFSGadget::::verify( + let (folded_lcccsVar, _) = NIMFSGadget::::verify( cs.clone(), &ccs, transcriptVar, @@ -1005,26 +1083,46 @@ mod tests { println!("AugmentedFCircuit & CCS generation: {:?}", start.elapsed()); println!("CCS m x n: {} x {}", ccs.m, ccs.n); + // CycleFold circuit + let cs2 = ConstraintSystem::::new_ref(); + let cf_circuit = CycleFoldCircuit::::empty(); + cf_circuit.generate_constraints(cs2.clone()).unwrap(); + cs2.finalize(); + let cs2 = cs2 + .into_inner() + .ok_or(Error::NoInnerConstraintSystem) + .unwrap(); + let cf_r1cs = extract_r1cs::(&cs2); + let (pedersen_params, _) = Pedersen::::setup(&mut rng, ccs.n - ccs.l - 1).unwrap(); + let (cf_pedersen_params, _) = + Pedersen::::setup(&mut rng, cf_r1cs.A.n_cols - cf_r1cs.l - 1).unwrap(); // first step let z_0 = vec![Fr::from(3_u32)]; let mut z_i = z_0.clone(); + // prepare the dummy instances let W_dummy = Witness::::new(vec![Fr::zero(); ccs.n - ccs.l - 1]); let U_dummy = LCCCS::::dummy(ccs.l, ccs.t, ccs.s); let w_dummy = W_dummy.clone(); let u_dummy = CCCS::::dummy(ccs.l); + let (cf_w_dummy, cf_u_dummy): (NovaWitness, CommittedInstance) = + cf_r1cs.dummy_instance(); // set the initial dummy instances let mut W_i = W_dummy.clone(); let mut U_i = U_dummy.clone(); let mut w_i = w_dummy.clone(); let mut u_i = u_dummy.clone(); - u_i.x = vec![U_i - .hash(&poseidon_config, Fr::zero(), z_0.clone(), z_i.clone()) - .unwrap()]; + 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(), + ]; let n_steps: usize = 4; let mut iFr = Fr::zero(); @@ -1032,7 +1130,7 @@ mod tests { let start = Instant::now(); let mut transcript_p: PoseidonTranscript = PoseidonTranscript::::new(&poseidon_config.clone()); - let (nimfs_proof, U_i1, W_i1) = + let (nimfs_proof, U_i1, W_i1, rho_bits) = NIMFS::>::prove( &mut transcript_p, &ccs, @@ -1051,43 +1149,132 @@ mod tests { .hash(&poseidon_config, iFr + Fr::one(), z_0.clone(), z_i1.clone()) .unwrap(); - augmented_f_circuit = - AugmentedFCircuit::> { - _c2: PhantomData, - _gc2: PhantomData, - poseidon_config: poseidon_config.clone(), - ccs: ccs.clone(), - i: Some(iFr), - i_usize: Some(i), - z_0: Some(z_0.clone()), - z_i: Some(z_i.clone()), - external_inputs: Some(vec![]), - u_i_C: Some(u_i.C), - U_i: Some(U_i.clone()), - U_i1_C: Some(U_i1.C), - F: F_circuit, - x: Some(u_i1_x), - nimfs_proof: Some(nimfs_proof), + if i == 0 { + // 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(); + + augmented_f_circuit = + AugmentedFCircuit::> { + _c2: PhantomData, + _gc2: PhantomData, + poseidon_config: poseidon_config.clone(), + ccs: ccs.clone(), + i: Some(iFr), + i_usize: Some(i), + z_0: Some(z_0.clone()), + z_i: Some(z_i.clone()), + external_inputs: Some(vec![]), + u_i_C: Some(u_i.C), + U_i: Some(U_i.clone()), + U_i1_C: Some(U_i1.C), + F: F_circuit, + x: Some(u_i1_x), + nimfs_proof: Some(nimfs_proof), + + // cyclefold values + cf_u_i_cmW: None, + cf_U_i: None, + cf_x: Some(cf_u_i1_x), + cf_cmT: None, + }; + } else { + let rho_Fq = Fq::from_bigint(BigInteger::from_bits_le(&rho_bits)).unwrap(); + // CycleFold part: + // get the vector used as public inputs 'x' in the CycleFold circuit + // cyclefold circuit for cmW + let cf_u_i_x = [ + vec![rho_Fq], + get_cm_coordinates(&U_i.C), + get_cm_coordinates(&u_i.C), + get_cm_coordinates(&U_i1.C), + ] + .concat(); + + let cf_circuit = CycleFoldCircuit:: { + _gc: PhantomData, + r_bits: Some(rho_bits.clone()), + p1: Some(U_i.clone().C), + p2: Some(u_i.clone().C), + x: Some(cf_u_i_x.clone()), }; + let (_cf_w_i, cf_u_i, cf_W_i1, cf_U_i1, cf_cmT, _) = fold_cyclefold_circuit::< + Projective, + GVar, + Projective2, + GVar2, + CubicFCircuit, + Pedersen, + Pedersen, + >( + &poseidon_config, + cf_r1cs.clone(), + cf_pedersen_params.clone(), + cf_W_i.clone(), // CycleFold running instance witness + cf_U_i.clone(), // CycleFold running instance + cf_u_i_x, // CycleFold incoming instance + cf_circuit, + ) + .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(); + + augmented_f_circuit = + AugmentedFCircuit::> { + _c2: PhantomData, + _gc2: PhantomData, + poseidon_config: poseidon_config.clone(), + ccs: ccs.clone(), + i: Some(iFr), + i_usize: Some(i), + z_0: Some(z_0.clone()), + z_i: Some(z_i.clone()), + external_inputs: Some(vec![]), + u_i_C: Some(u_i.C), + U_i: Some(U_i.clone()), + U_i1_C: Some(U_i1.C), + F: F_circuit, + x: Some(u_i1_x), + nimfs_proof: Some(nimfs_proof), + + // cyclefold values + cf_u_i_cmW: Some(cf_u_i.cmW), + cf_U_i: Some(cf_U_i), + cf_x: Some(cf_u_i1_x), + cf_cmT: Some(cf_cmT), + }; + + // assign the next round instances + cf_W_i = cf_W_i1; + cf_U_i = cf_U_i1; + } + let (cs, _) = augmented_f_circuit.compute_cs_ccs().unwrap(); assert!(cs.is_satisfied().unwrap()); let (r1cs_w_i1, r1cs_x_i1) = extract_w_x::(&cs); // includes 1 and public inputs assert_eq!(r1cs_x_i1[0], augmented_f_circuit.x.unwrap()); - let r1cs_z = [vec![Fr::one()], r1cs_x_i1, r1cs_w_i1].concat(); + let r1cs_z = [vec![Fr::one()], r1cs_x_i1.clone(), r1cs_w_i1.clone()].concat(); // compute committed instances, w_{i+1}, u_{i+1}, which will be used as w_i, u_i, so we // assign them directly to w_i, u_i. (u_i, w_i) = ccs.to_cccs(&mut rng, &pedersen_params, &r1cs_z).unwrap(); u_i.check_relation(&pedersen_params, &ccs, &w_i).unwrap(); - // sanity check + // sanity checks + assert_eq!(w_i.w, r1cs_w_i1); + 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()) .unwrap(); + let expected_cf_U_i1_x = cf_U_i.hash_cyclefold(&poseidon_config).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); // set values for next iteration iFr += Fr::one(); @@ -1101,6 +1288,11 @@ mod tests { // check the new CCCS instance relation u_i.check_relation(&pedersen_params, &ccs, &w_i).unwrap(); + // check the CycleFold instance relation + cf_r1cs + .check_relaxed_instance_relation(&cf_W_i, &cf_U_i) + .unwrap(); + println!("augmented_f_circuit step {}: {:?}", i, start.elapsed()); } } diff --git a/folding-schemes/src/folding/hypernova/lcccs.rs b/folding-schemes/src/folding/hypernova/lcccs.rs index 3616b20..f17bf9c 100644 --- a/folding-schemes/src/folding/hypernova/lcccs.rs +++ b/folding-schemes/src/folding/hypernova/lcccs.rs @@ -127,8 +127,8 @@ where ::ScalarField: Absorb, ::BaseField: ark_ff::PrimeField, { - /// hash implements the committed instance hash compatible with the gadget implemented in - /// nova/circuits.rs::CommittedInstanceVar.hash. + /// [`LCCCS`].hash implements the committed instance hash compatible with the gadget + /// implemented in nova/circuits.rs::CommittedInstanceVar.hash. /// 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, diff --git a/folding-schemes/src/folding/hypernova/nimfs.rs b/folding-schemes/src/folding/hypernova/nimfs.rs index 9da471e..805d34e 100644 --- a/folding-schemes/src/folding/hypernova/nimfs.rs +++ b/folding-schemes/src/folding/hypernova/nimfs.rs @@ -1,15 +1,18 @@ use ark_crypto_primitives::sponge::Absorb; use ark_ec::{CurveGroup, Group}; -use ark_ff::{Field, PrimeField}; +use ark_ff::{BigInteger, Field, PrimeField}; use ark_poly::univariate::DensePolynomial; use ark_poly::{DenseUVPolynomial, Polynomial}; use ark_std::{One, Zero}; -use super::cccs::CCCS; -use super::lcccs::LCCCS; -use super::utils::{compute_c, compute_g, compute_sigmas_thetas}; -use super::Witness; +use super::{ + cccs::CCCS, + lcccs::LCCCS, + utils::{compute_c, compute_g, compute_sigmas_thetas}, + Witness, +}; use crate::ccs::CCS; +use crate::constants::N_BITS_RO; use crate::folding::circuits::nonnative::affine::nonnative_affine_to_field_elements; use crate::transcript::Transcript; use crate::utils::sum_check::structs::{IOPProof as SumCheckProof, IOPProverMessage}; @@ -179,7 +182,7 @@ where new_instances: &[CCCS], w_lcccs: &[Witness], w_cccs: &[Witness], - ) -> Result<(NIMFSProof, LCCCS, Witness), Error> { + ) -> Result<(NIMFSProof, LCCCS, Witness, Vec), Error> { // absorb instances to transcript for U_i in running_instances { let (C_x, C_y) = nonnative_affine_to_field_elements::(U_i.C)?; @@ -254,7 +257,9 @@ where // Step 6: Get the folding challenge let rho_scalar = C::ScalarField::from_le_bytes_mod_order(b"rho"); transcript.absorb(&rho_scalar); - let rho: C::ScalarField = transcript.get_challenge(); + let rho_bits: Vec = transcript.get_challenge_nbits(N_BITS_RO); + let rho: C::ScalarField = + C::ScalarField::from_bigint(BigInteger::from_bits_le(&rho_bits)).unwrap(); // Step 7: Create the folded instance let folded_lcccs = Self::fold( @@ -275,6 +280,7 @@ where }, folded_lcccs, folded_witness, + rho_bits, )) } @@ -382,7 +388,9 @@ where // Step 6: Get the folding challenge let rho_scalar = C::ScalarField::from_le_bytes_mod_order(b"rho"); transcript.absorb(&rho_scalar); - let rho: C::ScalarField = transcript.get_challenge(); + let rho_bits: Vec = transcript.get_challenge_nbits(N_BITS_RO); + let rho: C::ScalarField = + C::ScalarField::from_bigint(BigInteger::from_bits_le(&rho_bits)).unwrap(); // Step 7: Compute the folded instance Ok(Self::fold( @@ -477,7 +485,7 @@ pub mod tests { transcript_p.absorb(&Fr::from_le_bytes_mod_order(b"init init")); // Run the prover side of the multifolding - let (proof, folded_lcccs, folded_witness) = + let (proof, folded_lcccs, folded_witness, _) = NIMFS::>::prove( &mut transcript_p, &ccs, @@ -543,7 +551,7 @@ pub mod tests { let (new_instance, w2) = ccs.to_cccs(&mut rng, &pedersen_params, &z_2).unwrap(); // run the prover side of the multifolding - let (proof, folded_lcccs, folded_witness) = + let (proof, folded_lcccs, folded_witness, _) = NIMFS::>::prove( &mut transcript_p, &ccs, @@ -624,7 +632,7 @@ pub mod tests { transcript_p.absorb(&Fr::from_le_bytes_mod_order(b"init init")); // Run the prover side of the multifolding - let (proof, folded_lcccs, folded_witness) = + let (proof, folded_lcccs, folded_witness, _) = NIMFS::>::prove( &mut transcript_p, &ccs, @@ -716,7 +724,7 @@ pub mod tests { } // Run the prover side of the multifolding - let (proof, folded_lcccs, folded_witness) = + let (proof, folded_lcccs, folded_witness, _) = NIMFS::>::prove( &mut transcript_p, &ccs, diff --git a/folding-schemes/src/folding/hypernova/utils.rs b/folding-schemes/src/folding/hypernova/utils.rs index 83a20bf..6951d37 100644 --- a/folding-schemes/src/folding/hypernova/utils.rs +++ b/folding-schemes/src/folding/hypernova/utils.rs @@ -49,7 +49,7 @@ pub fn compute_sigmas_thetas( Ok(SigmasThetas(sigmas, thetas)) } -/// computes c from the step 5 in section 5 of HyperNova, adapted to multiple LCCCS & CCCS +/// Computes c from the step 5 in section 5 of HyperNova, adapted to multiple LCCCS & CCCS /// instances: /// $$ /// c = \sum_{i \in [\mu]} \left(\sum_{j \in [t]} \gamma^{i \cdot t + j} \cdot e_i \cdot \sigma_{i,j} \right) diff --git a/folding-schemes/src/folding/nova/circuits.rs b/folding-schemes/src/folding/nova/circuits.rs index a45ffff..6d23322 100644 --- a/folding-schemes/src/folding/nova/circuits.rs +++ b/folding-schemes/src/folding/nova/circuits.rs @@ -23,14 +23,12 @@ use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, Namespace, use ark_std::{fmt::Debug, One, Zero}; use core::{borrow::Borrow, marker::PhantomData}; -use super::{ +use super::CommittedInstance; +use crate::constants::N_BITS_RO; +use crate::folding::circuits::{ cyclefold::{ CycleFoldChallengeGadget, CycleFoldCommittedInstanceVar, NIFSFullGadget, CF_IO_LEN, }, - CommittedInstance, -}; -use crate::constants::N_BITS_RO; -use crate::folding::circuits::{ nonnative::{ affine::{nonnative_affine_to_field_elements, NonNativeAffineVar}, uint::NonNativeUintVar, diff --git a/folding-schemes/src/folding/nova/mod.rs b/folding-schemes/src/folding/nova/mod.rs index a938df7..541b2e3 100644 --- a/folding-schemes/src/folding/nova/mod.rs +++ b/folding-schemes/src/folding/nova/mod.rs @@ -16,6 +16,7 @@ use std::usize; use crate::ccs::r1cs::{extract_r1cs, extract_w_x, R1CS}; use crate::commitment::CommitmentScheme; +use crate::folding::circuits::cyclefold::{fold_cyclefold_circuit, CycleFoldCircuit}; use crate::folding::circuits::{ nonnative::{ affine::nonnative_affine_to_field_elements, uint::nonnative_field_to_field_elements, @@ -28,21 +29,15 @@ use crate::Error; use crate::FoldingScheme; pub mod circuits; -pub mod cyclefold; pub mod decider_eth; pub mod decider_eth_circuit; pub mod nifs; pub mod serialize; pub mod traits; - use circuits::{AugmentedFCircuit, ChallengeGadget}; -use cyclefold::{CycleFoldChallengeGadget, CycleFoldCircuit}; use nifs::NIFS; use traits::NovaR1CS; -#[cfg(test)] -use cyclefold::CF_IO_LEN; - #[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] pub struct CommittedInstance { pub cmE: C, @@ -644,23 +639,6 @@ where &self.U_i, ) } - // computes T* and cmT* for the CycleFoldCircuit - fn compute_cf_cmT( - &self, - cf_w_i: &Witness, - cf_u_i: &CommittedInstance, - cf_W_i: &Witness, - cf_U_i: &CommittedInstance, - ) -> Result<(Vec, C2), Error> { - NIFS::::compute_cyclefold_cmT( - &self.cf_cs_params, - &self.cf_r1cs, - cf_w_i, - cf_u_i, - cf_W_i, - cf_U_i, - ) - } } impl Nova @@ -699,41 +677,15 @@ where ), Error, > { - let cs2 = ConstraintSystem::::new_ref(); - cf_circuit.generate_constraints(cs2.clone())?; - - let cs2 = cs2.into_inner().ok_or(Error::NoInnerConstraintSystem)?; - let (cf_w_i, cf_x_i) = extract_w_x::(&cs2); - if cf_x_i != cf_u_i_x { - return Err(Error::NotEqual); - } - - #[cfg(test)] - if cf_x_i.len() != CF_IO_LEN { - return Err(Error::NotExpectedLength(cf_x_i.len(), CF_IO_LEN)); - } - - // fold cyclefold instances - let cf_w_i = Witness::::new(cf_w_i.clone(), self.cf_r1cs.A.n_rows); - let cf_u_i: CommittedInstance = - cf_w_i.commit::(&self.cf_cs_params, cf_x_i.clone())?; - - // compute T* and cmT* for CycleFoldCircuit - let (cf_T, cf_cmT) = self.compute_cf_cmT(&cf_w_i, &cf_u_i, &cf_W_i, &cf_U_i)?; - - let cf_r_bits = CycleFoldChallengeGadget::::get_challenge_native( + fold_cyclefold_circuit::( &self.poseidon_config, - cf_U_i.clone(), - cf_u_i.clone(), - cf_cmT, - )?; - let cf_r_Fq = C1::BaseField::from_bigint(BigInteger::from_bits_le(&cf_r_bits)) - .ok_or(Error::OutOfBounds)?; - - let (cf_W_i1, cf_U_i1) = NIFS::::fold_instances( - cf_r_Fq, &cf_W_i, &cf_U_i, &cf_w_i, &cf_u_i, &cf_T, cf_cmT, - )?; - Ok((cf_w_i, cf_u_i, cf_W_i1, cf_U_i1, cf_cmT, cf_r_Fq)) + self.cf_r1cs.clone(), + self.cf_cs_params.clone(), + cf_W_i, + cf_U_i, + cf_u_i_x, + cf_circuit, + ) } } diff --git a/folding-schemes/src/folding/nova/serialize.rs b/folding-schemes/src/folding/nova/serialize.rs index eb4e88b..170f29f 100644 --- a/folding-schemes/src/folding/nova/serialize.rs +++ b/folding-schemes/src/folding/nova/serialize.rs @@ -1,6 +1,6 @@ -use super::{circuits::AugmentedFCircuit, cyclefold::CycleFoldCircuit, Nova, ProverParams}; +use super::{circuits::AugmentedFCircuit, Nova, ProverParams}; pub use super::{CommittedInstance, Witness}; -pub use crate::folding::circuits::CF2; +pub use crate::folding::circuits::{cyclefold::CycleFoldCircuit, CF2}; use crate::{ ccs::r1cs::extract_r1cs, commitment::CommitmentScheme, folding::circuits::CF1, frontend::FCircuit,