diff --git a/folding-schemes/src/folding/circuits/cyclefold.rs b/folding-schemes/src/folding/circuits/cyclefold.rs index 3cb3402e..91a87877 100644 --- a/folding-schemes/src/folding/circuits/cyclefold.rs +++ b/folding-schemes/src/folding/circuits/cyclefold.rs @@ -24,7 +24,7 @@ use super::{nonnative::uint::NonNativeUintVar, CF1, CF2}; use crate::arith::r1cs::{extract_w_x, R1CS}; use crate::commitment::CommitmentScheme; use crate::constants::NOVA_N_BITS_RO; -use crate::folding::nova::nifs::NIFS; +use crate::folding::nova::{nifs::NIFS, traits::NIFSTrait}; use crate::transcript::{AbsorbNonNative, AbsorbNonNativeGadget, Transcript, TranscriptVar}; use crate::Error; @@ -570,9 +570,8 @@ where 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, - )?; + let (cf_W_i1, cf_U_i1) = + NIFS::::prove(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)) } @@ -584,10 +583,11 @@ pub mod tests { poseidon::{constraints::PoseidonSpongeVar, PoseidonSponge}, }; use ark_r1cs_std::R1CSVar; - use ark_std::UniformRand; + use ark_std::{One, UniformRand}; use super::*; - use crate::folding::nova::nifs::tests::prepare_simple_fold_inputs; + use crate::commitment::pedersen::Pedersen; + use crate::folding::nova::CommittedInstance; use crate::transcript::poseidon::poseidon_canonical_config; use crate::utils::get_cm_coordinates; @@ -669,12 +669,30 @@ pub mod tests { #[test] fn test_nifs_full_gadget() { - let (_, _, _, _, ci1, _, ci2, _, ci3, _, cmT, r_bits, _) = prepare_simple_fold_inputs(); + let mut rng = ark_std::test_rng(); - let cs = ConstraintSystem::::new_ref(); + // prepare the committed instances to test in-circuit + let ci: Vec> = (0..2) + .into_iter() + .map(|_| CommittedInstance:: { + cmE: Projective::rand(&mut rng), + u: Fr::rand(&mut rng), + cmW: Projective::rand(&mut rng), + x: vec![Fr::rand(&mut rng); 1], + }) + .collect(); + let (ci1, mut ci2) = (ci[0].clone(), ci[1].clone()); + // make the 2nd instance a 'fresh' instance (ie. cmE=0, u=1) + ci2.cmE = Projective::zero(); + ci2.u = Fr::one(); + let r_bits: Vec = + Fr::rand(&mut rng).into_bigint().to_bits_le()[..NOVA_N_BITS_RO].to_vec(); + let r_Fr = Fr::from_bigint(BigInteger::from_bits_le(&r_bits)).unwrap(); + let cmT = Projective::rand(&mut rng); + let ci3 = NIFS::>::verify(r_Fr, &ci1, &ci2, &cmT); + let cs = ConstraintSystem::::new_ref(); let r_bitsVar = Vec::>::new_witness(cs.clone(), || Ok(r_bits)).unwrap(); - let ci1Var = CycleFoldCommittedInstanceVar::::new_witness(cs.clone(), || { Ok(ci1.clone()) diff --git a/folding-schemes/src/folding/mod.rs b/folding-schemes/src/folding/mod.rs index e9d284a8..0f3a66fd 100644 --- a/folding-schemes/src/folding/mod.rs +++ b/folding-schemes/src/folding/mod.rs @@ -1,6 +1,5 @@ pub mod circuits; pub mod hypernova; pub mod nova; -pub mod ova; pub mod protogalaxy; pub mod traits; diff --git a/folding-schemes/src/folding/nova/circuits.rs b/folding-schemes/src/folding/nova/circuits.rs index cd484abf..b657b61e 100644 --- a/folding-schemes/src/folding/nova/circuits.rs +++ b/folding-schemes/src/folding/nova/circuits.rs @@ -168,10 +168,11 @@ where /// ChallengeGadget computes the RO challenge used for the Nova instances NIFS, it contains a /// rust-native and a in-circuit compatible versions. -pub struct ChallengeGadget { +pub struct ChallengeGadget { _c: PhantomData, + _ci: PhantomData, } -impl ChallengeGadget +impl ChallengeGadget where C: CurveGroup, ::BaseField: PrimeField, @@ -180,14 +181,17 @@ where pub fn get_challenge_native>( transcript: &mut T, pp_hash: C::ScalarField, // public params hash - U_i: CommittedInstance, - u_i: CommittedInstance, - cmT: C, + U_i: &CI, + u_i: &CI, + cmT: Option<&C>, ) -> Vec { transcript.absorb(&pp_hash); transcript.absorb(&U_i); transcript.absorb(&u_i); - transcript.absorb_nonnative(&cmT); + // in the Nova case we absorb the cmT, in Ova case we don't since it is not used. + if let Some(cmT_value) = cmT { + transcript.absorb_nonnative(cmT_value); + } transcript.squeeze_bits(NOVA_N_BITS_RO) } @@ -197,12 +201,15 @@ where pp_hash: FpVar>, // public params hash U_i_vec: Vec>>, // apready processed input, so we don't have to recompute these values u_i: CommittedInstanceVar, - cmT: NonNativeAffineVar, + cmT: Option>, ) -> Result>, SynthesisError> { transcript.absorb(&pp_hash)?; transcript.absorb(&U_i_vec)?; transcript.absorb(&u_i)?; - transcript.absorb_nonnative(&cmT)?; + // in the Nova case we absorb the cmT, in Ova case we don't since it is not used. + if let Some(cmT_value) = cmT { + transcript.absorb_nonnative(&cmT_value)?; + } transcript.squeeze_bits(NOVA_N_BITS_RO) } } @@ -376,12 +383,12 @@ where // P.3. nifs.verify, obtains U_{i+1} by folding u_i & U_i . // compute r = H(u_i, U_i, cmT) - let r_bits = ChallengeGadget::::get_challenge_gadget( + let r_bits = ChallengeGadget::>::get_challenge_gadget( &mut transcript, pp_hash.clone(), U_i_vec, u_i.clone(), - cmT.clone(), + Some(cmT.clone()), )?; let r = Boolean::le_bits_to_fp_var(&r_bits)?; // Also convert r_bits to a `NonNativeFieldVar` @@ -522,8 +529,8 @@ pub mod tests { use ark_std::UniformRand; use crate::commitment::pedersen::Pedersen; - use crate::folding::nova::nifs::tests::prepare_simple_fold_inputs; use crate::folding::nova::nifs::NIFS; + use crate::folding::nova::traits::NIFSTrait; use crate::folding::traits::CommittedInstanceOps; use crate::transcript::poseidon::poseidon_canonical_config; @@ -550,10 +557,22 @@ pub mod tests { #[test] fn test_nifs_gadget() { - let (_, _, _, _, ci1, _, ci2, _, ci3, _, cmT, _, r_Fr) = prepare_simple_fold_inputs(); + let mut rng = ark_std::test_rng(); - let ci3_verifier = NIFS::>::verify(r_Fr, &ci1, &ci2, &cmT); - assert_eq!(ci3_verifier, ci3); + // prepare the committed instances to test in-circuit + let ci: Vec> = (0..2) + .into_iter() + .map(|_| CommittedInstance:: { + cmE: Projective::rand(&mut rng), + u: Fr::rand(&mut rng), + cmW: Projective::rand(&mut rng), + x: vec![Fr::rand(&mut rng); 1], + }) + .collect(); + let (ci1, ci2) = (ci[0].clone(), ci[1].clone()); + let r_Fr = Fr::rand(&mut rng); + let cmT = Projective::rand(&mut rng); + let ci3 = NIFS::>::verify(r_Fr, &ci1, &ci2, &cmT); let cs = ConstraintSystem::::new_ref(); @@ -673,13 +692,14 @@ pub mod tests { let pp_hash = Fr::from(42u32); // only for testing // compute the challenge natively - let r_bits = ChallengeGadget::::get_challenge_native( - &mut transcript, - pp_hash, - U_i.clone(), - u_i.clone(), - cmT, - ); + let r_bits = + ChallengeGadget::>::get_challenge_native( + &mut transcript, + pp_hash, + &U_i, + &u_i, + Some(&cmT), + ); let r = Fr::from_bigint(BigInteger::from_bits_le(&r_bits)).unwrap(); let cs = ConstraintSystem::::new_ref(); @@ -701,14 +721,15 @@ pub mod tests { U_iVar.cmW.to_constraint_field().unwrap(), ] .concat(); - let r_bitsVar = ChallengeGadget::::get_challenge_gadget( - &mut transcriptVar, - pp_hashVar, - U_iVar_vec, - u_iVar, - cmTVar, - ) - .unwrap(); + let r_bitsVar = + ChallengeGadget::>::get_challenge_gadget( + &mut transcriptVar, + pp_hashVar, + U_iVar_vec, + u_iVar, + Some(cmTVar), + ) + .unwrap(); assert!(cs.is_satisfied().unwrap()); // check that the natively computed and in-circuit computed hashes match diff --git a/folding-schemes/src/folding/nova/decider.rs b/folding-schemes/src/folding/nova/decider.rs index 11e864b0..901f152a 100644 --- a/folding-schemes/src/folding/nova/decider.rs +++ b/folding-schemes/src/folding/nova/decider.rs @@ -13,7 +13,7 @@ use ark_std::{One, Zero}; use core::marker::PhantomData; use super::decider_circuits::{DeciderCircuit1, DeciderCircuit2}; -use super::{nifs::NIFS, CommittedInstance, Nova}; +use super::{nifs::NIFS, traits::NIFSTrait, CommittedInstance, Nova}; use crate::commitment::CommitmentScheme; use crate::folding::circuits::{ cyclefold::CycleFoldCommittedInstance, diff --git a/folding-schemes/src/folding/nova/decider_circuits.rs b/folding-schemes/src/folding/nova/decider_circuits.rs index 457d0571..6e84fc03 100644 --- a/folding-schemes/src/folding/nova/decider_circuits.rs +++ b/folding-schemes/src/folding/nova/decider_circuits.rs @@ -27,6 +27,7 @@ use super::{ circuits::{ChallengeGadget, CommittedInstanceVar}, decider_eth_circuit::{KZGChallengesGadget, R1CSVar, RelaxedR1CSGadget, WitnessVar}, nifs::NIFS, + traits::NIFSTrait, CommittedInstance, Nova, Witness, }; use crate::arith::r1cs::R1CS; @@ -122,18 +123,17 @@ where &nova.W_i.clone(), &nova.U_i.clone(), )?; - let r_bits = ChallengeGadget::::get_challenge_native( + let r_bits = NIFS::::get_challenge( &mut transcript, nova.pp_hash, - nova.U_i.clone(), - nova.u_i.clone(), - cmT, + &nova.U_i, + &nova.u_i, + &cmT, ); let r_Fr = C1::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits)) .ok_or(Error::OutOfBounds)?; - let (W_i1, U_i1) = NIFS::::fold_instances( - r_Fr, &nova.W_i, &nova.U_i, &nova.w_i, &nova.u_i, &T, cmT, - )?; + let (W_i1, U_i1) = + NIFS::::prove(r_Fr, &nova.W_i, &nova.U_i, &nova.w_i, &nova.u_i, &T, &cmT)?; // compute the commitment scheme challenges used as inputs in the circuit let (cs_challenge_W, cs_challenge_E) = @@ -283,12 +283,12 @@ where // do the actual checks later. let cmT = NonNativeAffineVar::new_input(cs.clone(), || Ok(self.cmT.unwrap_or_else(C1::zero)))?; - let r_bits = ChallengeGadget::::get_challenge_gadget( + let r_bits = ChallengeGadget::>::get_challenge_gadget( &mut transcript, pp_hash, U_i_vec, u_i.clone(), - cmT.clone(), + Some(cmT.clone()), )?; // 5.1. let (incircuit_c_W, incircuit_c_E) = diff --git a/folding-schemes/src/folding/nova/decider_eth.rs b/folding-schemes/src/folding/nova/decider_eth.rs index 9ab880be..8ef02519 100644 --- a/folding-schemes/src/folding/nova/decider_eth.rs +++ b/folding-schemes/src/folding/nova/decider_eth.rs @@ -15,6 +15,7 @@ use ark_std::{One, Zero}; use core::marker::PhantomData; pub use super::decider_eth_circuit::DeciderEthCircuit; +use super::traits::NIFSTrait; use super::{nifs::NIFS, CommittedInstance, Nova}; use crate::commitment::{ kzg::{Proof as KZGProof, KZG}, diff --git a/folding-schemes/src/folding/nova/decider_eth_circuit.rs b/folding-schemes/src/folding/nova/decider_eth_circuit.rs index 77113860..18abfe86 100644 --- a/folding-schemes/src/folding/nova/decider_eth_circuit.rs +++ b/folding-schemes/src/folding/nova/decider_eth_circuit.rs @@ -27,6 +27,7 @@ use core::{borrow::Borrow, marker::PhantomData}; use super::{ circuits::{ChallengeGadget, CommittedInstanceVar}, nifs::NIFS, + traits::NIFSTrait, CommittedInstance, Nova, Witness, }; use crate::commitment::{pedersen::Params as PedersenParams, CommitmentScheme}; @@ -245,7 +246,7 @@ where let mut transcript = PoseidonSponge::::new(&nova.poseidon_config); // compute the U_{i+1}, W_{i+1} - let (T, cmT) = NIFS::::compute_cmT( + let (aux_p, aux_v) = NIFS::::compute_aux( &nova.cs_pp, &nova.r1cs.clone(), &nova.w_i.clone(), @@ -253,17 +254,18 @@ where &nova.W_i.clone(), &nova.U_i.clone(), )?; - let r_bits = ChallengeGadget::::get_challenge_native( + let cmT = aux_v; + let r_bits = ChallengeGadget::>::get_challenge_native( &mut transcript, nova.pp_hash, - nova.U_i.clone(), - nova.u_i.clone(), - cmT, + &nova.U_i, + &nova.u_i, + Some(&cmT), ); let r_Fr = C1::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits)) .ok_or(Error::OutOfBounds)?; - let (W_i1, U_i1) = NIFS::::fold_instances( - r_Fr, &nova.W_i, &nova.U_i, &nova.w_i, &nova.u_i, &T, cmT, + let (W_i1, U_i1) = NIFS::::prove( + r_Fr, &nova.W_i, &nova.U_i, &nova.w_i, &nova.u_i, &aux_p, &aux_v, )?; // compute the KZG challenges used as inputs in the circuit @@ -483,12 +485,12 @@ where let cmT = NonNativeAffineVar::new_input(cs.clone(), || Ok(self.cmT.unwrap_or_else(C1::zero)))?; // 1.1.a - let r_bits = ChallengeGadget::::get_challenge_gadget( + let r_bits = ChallengeGadget::>::get_challenge_gadget( &mut transcript, pp_hash, U_i_vec, u_i.clone(), - cmT.clone(), + Some(cmT), )?; // 5.1. let (incircuit_c_W, incircuit_c_E) = diff --git a/folding-schemes/src/folding/nova/mod.rs b/folding-schemes/src/folding/nova/mod.rs index 7a6691d5..45ee51ac 100644 --- a/folding-schemes/src/folding/nova/mod.rs +++ b/folding-schemes/src/folding/nova/mod.rs @@ -35,15 +35,17 @@ use crate::{ }; use crate::{arith::Arith, commitment::CommitmentScheme}; -use circuits::{AugmentedFCircuit, ChallengeGadget, CommittedInstanceVar}; -use nifs::NIFS; - pub mod circuits; pub mod nifs; +pub mod ova; pub mod serialize; pub mod traits; pub mod zk; +use circuits::{AugmentedFCircuit, ChallengeGadget, CommittedInstanceVar}; +use nifs::NIFS; +use traits::NIFSTrait; + // offchain decider pub mod decider; pub mod decider_circuits; @@ -678,15 +680,16 @@ where .step_native(i_usize, self.z_i.clone(), external_inputs.clone())?; // compute T and cmT for AugmentedFCircuit - let (T, cmT) = self.compute_cmT()?; + let (aux_p, aux_v) = self.compute_cmT()?; + let cmT = aux_v; // r_bits is the r used to the RLC of the F' instances - let r_bits = ChallengeGadget::::get_challenge_native( + let r_bits = ChallengeGadget::>::get_challenge_native( &mut transcript, self.pp_hash, - self.U_i.clone(), - self.u_i.clone(), - cmT, + &self.U_i, + &self.u_i, + Some(&cmT), ); let r_Fr = C1::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits)) .ok_or(Error::OutOfBounds)?; @@ -694,10 +697,9 @@ where .ok_or(Error::OutOfBounds)?; // fold Nova instances - let (W_i1, U_i1): (Witness, CommittedInstance) = - NIFS::::fold_instances( - r_Fr, &self.W_i, &self.U_i, &self.w_i, &self.u_i, &T, cmT, - )?; + let (W_i1, U_i1): (Witness, CommittedInstance) = NIFS::::prove( + r_Fr, &self.W_i, &self.U_i, &self.w_i, &self.u_i, &aux_p, &aux_v, + )?; // folded instance output (public input, x) // u_{i+1}.x[0] = H(i+1, z_0, z_{i+1}, U_{i+1}) @@ -958,7 +960,7 @@ where { // computes T and cmT for the AugmentedFCircuit fn compute_cmT(&self) -> Result<(Vec, C1), Error> { - NIFS::::compute_cmT( + NIFS::::compute_aux( &self.cs_pp, &self.r1cs, &self.w_i, diff --git a/folding-schemes/src/folding/nova/nifs.rs b/folding-schemes/src/folding/nova/nifs.rs index 0c25afa4..0a2d807e 100644 --- a/folding-schemes/src/folding/nova/nifs.rs +++ b/folding-schemes/src/folding/nova/nifs.rs @@ -1,8 +1,12 @@ use ark_crypto_primitives::sponge::Absorb; use ark_ec::{CurveGroup, Group}; +use ark_ff::PrimeField; +use ark_std::rand::RngCore; use ark_std::Zero; use std::marker::PhantomData; +use super::circuits::ChallengeGadget; +use super::traits::NIFSTrait; use super::{CommittedInstance, Witness}; use crate::arith::r1cs::R1CS; use crate::commitment::CommitmentScheme; @@ -19,11 +23,119 @@ pub struct NIFS, const H: bool = false _cp: PhantomData, } +impl, const H: bool> NIFSTrait + for NIFS +where + ::ScalarField: Absorb, + ::BaseField: PrimeField, +{ + type CommittedInstance = CommittedInstance; + type Witness = Witness; + type ProverAux = Vec; + type VerifierAux = C; + + fn new_witness(w: Vec, e_len: usize, rng: impl RngCore) -> Self::Witness { + Witness::new::(w, e_len, rng) + } + + fn new_instance( + W: &Self::Witness, + params: &CS::ProverParams, + x: Vec, + _aux: Vec, + ) -> Result { + W.commit::(params, x) + } + + fn fold_witness( + r: C::ScalarField, + W_i: &Self::Witness, + w_i: &Self::Witness, + aux: &Self::ProverAux, + ) -> Result { + let r2 = r * r; + let E: Vec = vec_add( + &vec_add(&W_i.E, &vec_scalar_mul(aux, &r))?, // aux is Nova's T + &vec_scalar_mul(&w_i.E, &r2), + )?; + // use r_T=0 since we don't need hiding property for cm(T) + let rT = C::ScalarField::zero(); + let rE = W_i.rE + r * rT + r2 * w_i.rE; + let W: Vec = W_i + .W + .iter() + .zip(&w_i.W) + .map(|(a, b)| *a + (r * b)) + .collect(); + + let rW = W_i.rW + r * w_i.rW; + Ok(Self::Witness { E, rE, W, rW }) + } + + fn compute_aux( + cs_prover_params: &CS::ProverParams, + r1cs: &R1CS, + W_i: &Self::Witness, + U_i: &Self::CommittedInstance, + w_i: &Self::Witness, + u_i: &Self::CommittedInstance, + ) -> Result<(Self::ProverAux, Self::VerifierAux), Error> { + let z1: Vec = [vec![U_i.u], U_i.x.to_vec(), W_i.W.to_vec()].concat(); + let z2: Vec = [vec![u_i.u], u_i.x.to_vec(), w_i.W.to_vec()].concat(); + + // compute cross terms + let T = Self::compute_T(r1cs, U_i.u, u_i.u, &z1, &z2)?; + // use r_T=0 since we don't need hiding property for cm(T) + let cmT = CS::commit(cs_prover_params, &T, &C::ScalarField::zero())?; + Ok((T, cmT)) + } + + fn get_challenge>( + transcript: &mut T, + pp_hash: C::ScalarField, // public params hash + U_i: &Self::CommittedInstance, + u_i: &Self::CommittedInstance, + aux: &Self::VerifierAux, // cmT + ) -> Vec { + ChallengeGadget::::get_challenge_native( + transcript, + pp_hash, + U_i, + u_i, + Some(aux), + ) + } + + // Notice: `prove` method is implemented at the trait level. + + fn verify( + // r comes from the transcript, and is a n-bit (N_BITS_CHALLENGE) element + r: C::ScalarField, + U_i: &Self::CommittedInstance, + u_i: &Self::CommittedInstance, + cmT: &C, // VerifierAux + ) -> Self::CommittedInstance { + let r2 = r * r; + let cmE = U_i.cmE + cmT.mul(r) + u_i.cmE.mul(r2); + let u = U_i.u + r * u_i.u; + let cmW = U_i.cmW + u_i.cmW.mul(r); + let x = U_i + .x + .iter() + .zip(&u_i.x) + .map(|(a, b)| *a + (r * b)) + .collect::>(); + + Self::CommittedInstance { cmE, u, cmW, x } + } +} + impl, const H: bool> NIFS where ::ScalarField: Absorb, + ::BaseField: PrimeField, { - // compute_T: compute cross-terms T + /// compute_T: compute cross-terms T pub fn compute_T( r1cs: &R1CS, u1: C::ScalarField, @@ -49,48 +161,8 @@ where vec_sub(&vec_sub(&vec_add(&Az1_Bz2, &Az2_Bz1)?, &u1Cz2)?, &u2Cz1) } - pub fn fold_witness( - r: C::ScalarField, - w1: &Witness, - w2: &Witness, - T: &[C::ScalarField], - rT: C::ScalarField, - ) -> Result, Error> { - let r2 = r * r; - let E: Vec = vec_add( - &vec_add(&w1.E, &vec_scalar_mul(T, &r))?, - &vec_scalar_mul(&w2.E, &r2), - )?; - let rE = w1.rE + r * rT + r2 * w2.rE; - let W: Vec = w1.W.iter().zip(&w2.W).map(|(a, b)| *a + (r * b)).collect(); - - let rW = w1.rW + r * w2.rW; - Ok(Witness:: { E, rE, W, rW }) - } - - pub fn fold_committed_instance( - r: C::ScalarField, - ci1: &CommittedInstance, // U_i - ci2: &CommittedInstance, // u_i - cmT: &C, - ) -> CommittedInstance { - let r2 = r * r; - let cmE = ci1.cmE + cmT.mul(r) + ci2.cmE.mul(r2); - let u = ci1.u + r * ci2.u; - let cmW = ci1.cmW + ci2.cmW.mul(r); - let x = ci1 - .x - .iter() - .zip(&ci2.x) - .map(|(a, b)| *a + (r * b)) - .collect::>(); - - CommittedInstance:: { cmE, u, cmW, x } - } - - /// NIFS.P is the consecutive combination of compute_cmT with fold_instances - - /// compute_cmT is part of the NIFS.P logic + /// In Nova, NIFS.P is the consecutive combination of compute_cmT with fold_instances, + /// ie. compute_cmT is part of the NIFS.P logic. pub fn compute_cmT( cs_prover_params: &CS::ProverParams, r1cs: &R1CS, @@ -108,6 +180,7 @@ where let cmT = CS::commit(cs_prover_params, &T, &C::ScalarField::zero())?; Ok((T, cmT)) } + pub fn compute_cyclefold_cmT( cs_prover_params: &CS::ProverParams, r1cs: &R1CS, // R1CS over C2.Fr=C1.Fq (here C=C2) @@ -129,40 +202,6 @@ where Ok((T, cmT)) } - /// fold_instances is part of the NIFS.P logic described in - /// [Nova](https://eprint.iacr.org/2021/370.pdf)'s section 4. It returns the folded Committed - /// Instances and the Witness. - pub fn fold_instances( - r: C::ScalarField, - w1: &Witness, - ci1: &CommittedInstance, - w2: &Witness, - ci2: &CommittedInstance, - T: &[C::ScalarField], - cmT: C, - ) -> Result<(Witness, CommittedInstance), Error> { - // fold witness - // use r_T=0 since we don't need hiding property for cm(T) - let w3 = NIFS::::fold_witness(r, w1, w2, T, C::ScalarField::zero())?; - - // fold committed instances - let ci3 = NIFS::::fold_committed_instance(r, ci1, ci2, &cmT); - - Ok((w3, ci3)) - } - - /// verify implements NIFS.V logic described in [Nova](https://eprint.iacr.org/2021/370.pdf)'s - /// section 4. It returns the folded Committed Instance - pub fn verify( - // r comes from the transcript, and is a n-bit (N_BITS_CHALLENGE) element - r: C::ScalarField, - ci1: &CommittedInstance, - ci2: &CommittedInstance, - cmT: &C, - ) -> CommittedInstance { - NIFS::::fold_committed_instance(r, ci1, ci2, cmT) - } - /// Verify committed folded instance (ci) relations. Notice that this method does not open the /// commitments, but just checks that the given committed instances (ci1, ci2) when folded /// result in the folded committed instance (ci3) values. @@ -173,7 +212,7 @@ where ci3: &CommittedInstance, cmT: &C, ) -> Result<(), Error> { - let expected = Self::fold_committed_instance(r, ci1, ci2, cmT); + let expected = Self::verify(r, ci1, ci2, cmT); if ci3.cmE != expected.cmE || ci3.u != expected.u || ci3.cmW != expected.cmW @@ -202,220 +241,32 @@ where #[cfg(test)] pub mod tests { use super::*; - use ark_crypto_primitives::sponge::{ - poseidon::{PoseidonConfig, PoseidonSponge}, - CryptographicSponge, - }; + use crate::transcript::poseidon::poseidon_canonical_config; + use ark_crypto_primitives::sponge::{poseidon::PoseidonSponge, CryptographicSponge}; use ark_ff::{BigInteger, PrimeField}; use ark_pallas::{Fr, Projective}; - use ark_std::{ops::Mul, test_rng, UniformRand}; + use ark_std::{test_rng, UniformRand}; - use crate::commitment::pedersen::{Params as PedersenParams, Pedersen}; - use crate::folding::nova::circuits::ChallengeGadget; - use crate::transcript::poseidon::poseidon_canonical_config; - use crate::{ - arith::{ - r1cs::tests::{get_test_r1cs, get_test_z}, - Arith, - }, - folding::traits::Dummy, + use crate::arith::{ + r1cs::tests::{get_test_r1cs, get_test_z}, + Arith, }; + use crate::commitment::pedersen::Pedersen; + use crate::folding::nova::traits::NIFSTrait; - #[allow(clippy::type_complexity)] - pub(crate) fn prepare_simple_fold_inputs() -> ( - PedersenParams, - PoseidonConfig, - R1CS, - Witness, // w1 - CommittedInstance, // ci1 - Witness, // w2 - CommittedInstance, // ci2 - Witness, // w3 - CommittedInstance, // ci3 - Vec, // T - C, // cmT - Vec, // r_bits - C::ScalarField, // r_Fr - ) - where - C: CurveGroup, - ::BaseField: PrimeField, - C::ScalarField: Absorb, - { - let r1cs = get_test_r1cs(); - let z1 = get_test_z(3); - let z2 = get_test_z(4); - let (w1, x1) = r1cs.split_z(&z1); - let (w2, x2) = r1cs.split_z(&z2); - - let w1 = Witness::::new::(w1.clone(), r1cs.A.n_rows, test_rng()); - let w2 = Witness::::new::(w2.clone(), r1cs.A.n_rows, test_rng()); - - let mut rng = ark_std::test_rng(); - let (pedersen_params, _) = Pedersen::::setup(&mut rng, r1cs.A.n_cols).unwrap(); - - // compute committed instances - let ci1 = w1 - .commit::, false>(&pedersen_params, x1.clone()) - .unwrap(); - let ci2 = w2 - .commit::, false>(&pedersen_params, x2.clone()) - .unwrap(); - - // NIFS.P - let (T, cmT) = - NIFS::>::compute_cmT(&pedersen_params, &r1cs, &w1, &ci1, &w2, &ci2) - .unwrap(); - - let poseidon_config = poseidon_canonical_config::(); - let mut transcript = PoseidonSponge::::new(&poseidon_config); - - let pp_hash = C::ScalarField::from(42u32); // only for test - let r_bits = ChallengeGadget::::get_challenge_native( - &mut transcript, - pp_hash, - ci1.clone(), - ci2.clone(), - cmT, - ); - let r_Fr = C::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits)).unwrap(); - - let (w3, ci3) = - NIFS::>::fold_instances(r_Fr, &w1, &ci1, &w2, &ci2, &T, cmT).unwrap(); - - ( - pedersen_params, - poseidon_config, - r1cs, - w1, - ci1, - w2, - ci2, - w3, - ci3, - T, - cmT, - r_bits, - r_Fr, - ) - } - - // fold 2 dummy instances and check that the folded instance holds the relaxed R1CS relation #[test] - fn test_nifs_fold_dummy() { - let r1cs = get_test_r1cs::(); - let z1 = get_test_z(3); - let (_, x1) = r1cs.split_z(&z1); + fn test_nifs_nova() { + let (W, U) = test_nifs_opt::>>(); - let mut rng = ark_std::test_rng(); - let (pedersen_params, _) = Pedersen::::setup(&mut rng, r1cs.A.n_cols).unwrap(); - - // dummy instance, witness and public inputs zeroes - let w_dummy = Witness::::dummy(&r1cs); - let mut u_dummy = w_dummy - .commit::, false>(&pedersen_params, vec![Fr::zero(); x1.len()]) - .unwrap(); - u_dummy.u = Fr::zero(); - - let w_i = w_dummy.clone(); - let u_i = u_dummy.clone(); - let W_i = w_dummy.clone(); - let U_i = u_dummy.clone(); - r1cs.check_relation(&w_i, &u_i).unwrap(); - r1cs.check_relation(&W_i, &U_i).unwrap(); - - let r_Fr = Fr::from(3_u32); - - let (T, cmT) = NIFS::>::compute_cmT( - &pedersen_params, - &r1cs, - &w_i, - &u_i, - &W_i, - &U_i, - ) - .unwrap(); - let (W_i1, U_i1) = NIFS::>::fold_instances( - r_Fr, &w_i, &u_i, &W_i, &U_i, &T, cmT, - ) - .unwrap(); - r1cs.check_relation(&W_i1, &U_i1).unwrap(); - } - - // fold 2 instances into one - #[test] - fn test_nifs_one_fold() { - let (pedersen_params, poseidon_config, r1cs, w1, ci1, w2, ci2, w3, ci3, T, cmT, _, r) = - prepare_simple_fold_inputs(); - - // NIFS.V - let ci3_v = NIFS::>::verify(r, &ci1, &ci2, &cmT); - assert_eq!(ci3_v, ci3); - - // check that relations hold for the 2 inputted instances and the folded one - r1cs.check_relation(&w1, &ci1).unwrap(); - r1cs.check_relation(&w2, &ci2).unwrap(); - r1cs.check_relation(&w3, &ci3).unwrap(); - - // check that folded commitments from folded instance (ci) are equal to folding the - // use folded rE, rW to commit w3 - let ci3_expected = w3 - .commit::, false>(&pedersen_params, ci3.x.clone()) - .unwrap(); - assert_eq!(ci3_expected.cmE, ci3.cmE); - assert_eq!(ci3_expected.cmW, ci3.cmW); - - // next equalities should hold since we started from two cmE of zero-vector E's - assert_eq!(ci3.cmE, cmT.mul(r)); - assert_eq!(w3.E, vec_scalar_mul(T.as_slice(), &r)); - - // NIFS.Verify_Folded_Instance: - NIFS::>::verify_folded_instance(r, &ci1, &ci2, &ci3, &cmT) - .unwrap(); - - // init Prover's transcript - let mut transcript_p = PoseidonSponge::::new(&poseidon_config); - // init Verifier's transcript - let mut transcript_v = PoseidonSponge::::new(&poseidon_config); - - // prove the ci3.cmE, ci3.cmW, cmT commitments - let cm_proofs = NIFS::>::prove_commitments( - &mut transcript_p, - &pedersen_params, - &w3, - &ci3, - T, - &cmT, - ) - .unwrap(); - - // verify the ci3.cmE, ci3.cmW, cmT commitments - assert_eq!(cm_proofs.len(), 3); - Pedersen::::verify( - &pedersen_params, - &mut transcript_v, - &ci3.cmE, - &cm_proofs[0].clone(), - ) - .unwrap(); - Pedersen::::verify( - &pedersen_params, - &mut transcript_v, - &ci3.cmW, - &cm_proofs[1].clone(), - ) - .unwrap(); - Pedersen::::verify( - &pedersen_params, - &mut transcript_v, - &cmT, - &cm_proofs[2].clone(), - ) - .unwrap(); + // check the last folded instance relation + let r1cs = get_test_r1cs(); + r1cs.check_relation(&W, &U).unwrap(); } - #[test] - fn test_nifs_fold_loop() { + /// runs a loop using the NIFS trait, and returns the last Witness and CommittedInstance so + /// that their relation can be checked. + pub(crate) fn test_nifs_opt>>( + ) -> (N::Witness, N::CommittedInstance) { let r1cs = get_test_r1cs(); let z = get_test_z(3); let (w, x) = r1cs.split_z(&z); @@ -423,66 +274,68 @@ pub mod tests { let mut rng = ark_std::test_rng(); let (pedersen_params, _) = Pedersen::::setup(&mut rng, r1cs.A.n_cols).unwrap(); - // prepare the running instance - let mut running_instance_w = - Witness::::new::(w.clone(), r1cs.A.n_rows, test_rng()); - let mut running_committed_instance = running_instance_w - .commit::, false>(&pedersen_params, x) - .unwrap(); + let poseidon_config = poseidon_canonical_config::(); + let mut transcript = PoseidonSponge::::new(&poseidon_config); + let pp_hash = Fr::rand(&mut rng); - r1cs.check_relation(&running_instance_w, &running_committed_instance) - .unwrap(); + // prepare the running instance + let mut running_witness = N::new_witness(w.clone(), r1cs.A.n_rows, test_rng()); + let mut running_committed_instance = + N::new_instance(&running_witness, &pedersen_params, x, vec![]).unwrap(); let num_iters = 10; for i in 0..num_iters { // prepare the incoming instance let incoming_instance_z = get_test_z(i + 4); let (w, x) = r1cs.split_z(&incoming_instance_z); - let incoming_instance_w = - Witness::::new::(w.clone(), r1cs.A.n_rows, test_rng()); - let incoming_committed_instance = incoming_instance_w - .commit::, false>(&pedersen_params, x) - .unwrap(); - r1cs.check_relation(&incoming_instance_w, &incoming_committed_instance) - .unwrap(); + let incoming_witness = N::new_witness(w.clone(), r1cs.A.n_rows, test_rng()); + let incoming_committed_instance = + N::new_instance(&incoming_witness, &pedersen_params, x, vec![]).unwrap(); - let r = Fr::rand(&mut rng); // folding challenge would come from the RO - - // NIFS.P - let (T, cmT) = NIFS::>::compute_cmT( + let (aux_p, aux_v) = N::compute_aux( &pedersen_params, &r1cs, - &running_instance_w, + &running_witness, &running_committed_instance, - &incoming_instance_w, + &incoming_witness, &incoming_committed_instance, ) .unwrap(); - let (folded_w, _) = NIFS::>::fold_instances( + + let r_bits = N::get_challenge( + &mut transcript, + pp_hash, + &running_committed_instance, + &incoming_committed_instance, + &aux_v, + ); + let r = Fr::from_bigint(BigInteger::from_bits_le(&r_bits)).unwrap(); + + // NIFS.P + let (folded_witness, _) = N::prove( r, - &running_instance_w, + &running_witness, &running_committed_instance, - &incoming_instance_w, + &incoming_witness, &incoming_committed_instance, - &T, - cmT, + &aux_p, + &aux_v, ) .unwrap(); // NIFS.V - let folded_committed_instance = NIFS::>::verify( + let folded_committed_instance = N::verify( r, &running_committed_instance, &incoming_committed_instance, - &cmT, + &aux_v, ); - r1cs.check_relation(&folded_w, &folded_committed_instance) - .unwrap(); - // set running_instance for next loop iteration - running_instance_w = folded_w; + running_witness = folded_witness; running_committed_instance = folded_committed_instance; } + + (running_witness, running_committed_instance) } } diff --git a/folding-schemes/src/folding/nova/ova.rs b/folding-schemes/src/folding/nova/ova.rs new file mode 100644 index 00000000..95bd91ac --- /dev/null +++ b/folding-schemes/src/folding/nova/ova.rs @@ -0,0 +1,269 @@ +/// This module contains the implementation the NIFSTrait for the +/// [Ova](https://hackmd.io/V4838nnlRKal9ZiTHiGYzw) NIFS (Non-Interactive Folding Scheme) as +/// outlined in the protocol description doc: +/// authored by Benedikt Bünz. +use ark_crypto_primitives::sponge::Absorb; +use ark_ec::{CurveGroup, Group}; +use ark_ff::PrimeField; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_std::fmt::Debug; +use ark_std::rand::RngCore; +use ark_std::{One, UniformRand, Zero}; +use std::marker::PhantomData; + +use super::{circuits::ChallengeGadget, traits::NIFSTrait}; +use crate::arith::r1cs::R1CS; +use crate::commitment::CommitmentScheme; +use crate::folding::{circuits::CF1, traits::Dummy}; +use crate::transcript::{AbsorbNonNative, Transcript}; +use crate::utils::vec::{hadamard, mat_vec_mul, vec_scalar_mul, vec_sub}; +use crate::Error; + +/// A CommittedInstance in [Ova](https://hackmd.io/V4838nnlRKal9ZiTHiGYzw) is represented by `W` or +/// `W'`. It is the result of the commitment to a vector that contains the witness `w` concatenated +/// with `t` or `e` + the public inputs `x` and a relaxation factor `u`. (Notice that in the Ova +/// document `u` is denoted as `mu`, in this implementation we use `u` so it follows the original +/// Nova notation, so code is easier to follow). +#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] +pub struct CommittedInstance { + pub u: C::ScalarField, // in the Ova document is denoted as `mu` + pub x: Vec, + pub cmWE: C, +} + +impl Absorb for CommittedInstance +where + C::ScalarField: Absorb, +{ + fn to_sponge_bytes(&self, dest: &mut Vec) { + C::ScalarField::batch_to_sponge_bytes(&self.to_sponge_field_elements_as_vec(), dest); + } + + fn to_sponge_field_elements(&self, dest: &mut Vec) { + self.u.to_sponge_field_elements(dest); + self.x.to_sponge_field_elements(dest); + // We cannot call `to_native_sponge_field_elements(dest)` directly, as + // `to_native_sponge_field_elements` needs `F` to be `C::ScalarField`, + // but here `F` is a generic `PrimeField`. + self.cmWE + .to_native_sponge_field_elements_as_vec() + .to_sponge_field_elements(dest); + } +} + +// #[allow(dead_code)] // Clippy flag needed for now. +/// A Witness in Ova is represented by `w`. It also contains a blinder which can or not be used +/// when committing to the witness itself. +#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] +pub struct Witness { + pub w: Vec, + pub rW: C::ScalarField, +} + +impl Witness { + /// Generates a new `Witness` instance from a given witness vector. + /// If `H = true`, then we assume we want to blind it at commitment time, + /// hence sampling `rW` from the randomness passed. + pub fn new(w: Vec, mut rng: impl RngCore) -> Self { + Self { + w, + rW: if H { + C::ScalarField::rand(&mut rng) + } else { + C::ScalarField::zero() + }, + } + } + + /// Given `x` (public inputs) and `t` or `e` (which we always concatenate in Ova) and the + /// public inputs `x`, generates a [`CommittedInstance`] as a result which will or not be + /// blinded depending on how the const generic `HC` is set up. + pub fn commit, const HC: bool>( + &self, + params: &CS::ProverParams, + x: Vec, + t_or_e: Vec, + ) -> Result, Error> { + let cmWE = CS::commit(params, &[self.w.clone(), t_or_e].concat(), &self.rW)?; + Ok(CommittedInstance { + u: C::ScalarField::one(), + cmWE, + x, + }) + } +} + +impl Dummy<&R1CS>> for Witness { + fn dummy(r1cs: &R1CS>) -> Self { + Self { + w: vec![C::ScalarField::zero(); r1cs.A.n_cols - 1 - r1cs.l], + rW: C::ScalarField::zero(), + } + } +} + +/// Implements the NIFS (Non-Interactive Folding Scheme) trait for Ova. +pub struct NIFS, const H: bool = false> { + _c: PhantomData, + _cp: PhantomData, +} + +impl, const H: bool> NIFSTrait + for NIFS +where + ::ScalarField: Absorb, + ::BaseField: PrimeField, +{ + type CommittedInstance = CommittedInstance; + type Witness = Witness; + type ProverAux = (); + type VerifierAux = (); + + fn new_witness(w: Vec, _e_len: usize, rng: impl RngCore) -> Self::Witness { + Witness::new::(w, rng) + } + + fn new_instance( + W: &Self::Witness, + params: &CS::ProverParams, + x: Vec, + aux: Vec, // t_or_e + ) -> Result { + W.commit::(params, x, aux) + } + + fn fold_witness( + r: C::ScalarField, // in Ova's hackmd denoted as `alpha` + W_i: &Self::Witness, + w_i: &Self::Witness, + _aux: &Self::ProverAux, + ) -> Result { + let w: Vec = W_i + .w + .iter() + .zip(&w_i.w) + .map(|(a, b)| *a + (r * b)) + .collect(); + + let rW = W_i.rW + r * w_i.rW; + Ok(Self::Witness { w, rW }) + } + + fn compute_aux( + _cs_prover_params: &CS::ProverParams, + _r1cs: &R1CS, + _W_i: &Self::Witness, + _U_i: &Self::CommittedInstance, + _w_i: &Self::Witness, + _u_i: &Self::CommittedInstance, + ) -> Result<(Self::ProverAux, Self::VerifierAux), Error> { + Ok(((), ())) + } + + fn get_challenge>( + transcript: &mut T, + pp_hash: C::ScalarField, // public params hash + U_i: &Self::CommittedInstance, + u_i: &Self::CommittedInstance, + _aux: &Self::VerifierAux, + ) -> Vec { + // reuse Nova's get_challenge method + ChallengeGadget::::get_challenge_native( + transcript, pp_hash, U_i, u_i, None, // empty in Ova's case + ) + } + + // Notice: `prove` method is implemented at the trait level. + + fn verify( + // r comes from the transcript, and is a n-bit (N_BITS_CHALLENGE) element + r: C::ScalarField, + U_i: &Self::CommittedInstance, + u_i: &Self::CommittedInstance, + _aux: &Self::VerifierAux, + ) -> Self::CommittedInstance { + // recall that r <==> alpha, and u <==> mu between Nova and Ova respectively + let u = U_i.u + r; // u_i.u is always 1 IN ova as we just can do sequential IVC. + let cmWE = U_i.cmWE + u_i.cmWE.mul(r); + let x = U_i + .x + .iter() + .zip(&u_i.x) + .map(|(a, b)| *a + (r * b)) + .collect::>(); + + Self::CommittedInstance { cmWE, u, x } + } +} + +/// Computes the E parameter (error terms) for the given R1CS and the instance's z and u. This +/// method is used by the verifier to obtain E in order to check the RelaxedR1CS relation. +pub fn compute_E( + r1cs: &R1CS, + z: &[C::ScalarField], + u: C::ScalarField, +) -> Result, Error> { + let (A, B, C) = (r1cs.A.clone(), r1cs.B.clone(), r1cs.C.clone()); + + // this is parallelizable (for the future) + let Az = mat_vec_mul(&A, z)?; + let Bz = mat_vec_mul(&B, z)?; + let Cz = mat_vec_mul(&C, z)?; + + let Az_Bz = hadamard(&Az, &Bz)?; + let uCz = vec_scalar_mul(&Cz, &u); + + vec_sub(&Az_Bz, &uCz) +} + +#[cfg(test)] +pub mod tests { + use super::*; + use ark_pallas::{Fr, Projective}; + + use crate::arith::{r1cs::tests::get_test_r1cs, Arith}; + use crate::commitment::pedersen::Pedersen; + use crate::folding::nova::nifs::tests::test_nifs_opt; + + // Simple auxiliary structure mainly used to help pass a witness for which we can check + // easily an R1CS relation. + // Notice that checking it requires us to have `E` as per [`Arith`] trait definition. + // But since we don't hold `E` nor `e` within the NIFS, we create this structure to pass + // `e` such that the check can be done. + #[derive(Debug, Clone)] + pub(crate) struct TestingWitness { + pub(crate) w: Vec, + pub(crate) e: Vec, + } + impl Arith, CommittedInstance> for R1CS> { + type Evaluation = Vec>; + + fn eval_relation( + &self, + w: &TestingWitness, + u: &CommittedInstance, + ) -> Result { + self.eval_at_z(&[&[u.u], u.x.as_slice(), &w.w].concat()) + } + + fn check_evaluation( + w: &TestingWitness, + _u: &CommittedInstance, + e: Self::Evaluation, + ) -> Result<(), Error> { + (w.e == e).then_some(()).ok_or(Error::NotSatisfied) + } + } + + #[test] + fn test_nifs_ova() { + let (W, U) = test_nifs_opt::>>(); + + // check the last folded instance relation + let r1cs = get_test_r1cs(); + let z: Vec = [&[U.u][..], &U.x, &W.w].concat(); + let e = compute_E::(&r1cs, &z, U.u).unwrap(); + r1cs.check_relation(&TestingWitness:: { e, w: W.w.clone() }, &U) + .unwrap(); + } +} diff --git a/folding-schemes/src/folding/nova/traits.rs b/folding-schemes/src/folding/nova/traits.rs index 9411799d..ce325f1a 100644 --- a/folding-schemes/src/folding/nova/traits.rs +++ b/folding-schemes/src/folding/nova/traits.rs @@ -1,4 +1,6 @@ +use ark_crypto_primitives::sponge::Absorb; use ark_ec::CurveGroup; +use ark_std::fmt::Debug; use ark_std::{rand::RngCore, UniformRand}; use super::{CommittedInstance, Witness}; @@ -6,11 +8,79 @@ use crate::arith::ArithSampler; use crate::arith::{r1cs::R1CS, Arith}; use crate::commitment::CommitmentScheme; use crate::folding::circuits::CF1; -use crate::folding::ova::{ - CommittedInstance as OvaCommittedInstance, TestingWitness as OvaWitness, -}; +use crate::transcript::Transcript; use crate::Error; +/// Defines the NIFS (Non-Interactive Folding Scheme) trait, initially defined in +/// [Nova](https://eprint.iacr.org/2021/370.pdf), and it's variants +/// [Ova](https://hackmd.io/V4838nnlRKal9ZiTHiGYzw) and +/// [Mova](https://eprint.iacr.org/2024/1220.pdf). +/// `H` specifies whether the NIFS will use a blinding factor. +pub trait NIFSTrait, const H: bool = false> { + type CommittedInstance: Debug + Clone + Absorb; + type Witness: Debug + Clone; + type ProverAux: Debug + Clone; // Prover's aux params + type VerifierAux: Debug + Clone; // Verifier's aux params + + fn new_witness(w: Vec, e_len: usize, rng: impl RngCore) -> Self::Witness; + fn new_instance( + w: &Self::Witness, + params: &CS::ProverParams, + x: Vec, + aux: Vec, // t_or_e in Ova, empty for Nova + ) -> Result; + + fn fold_witness( + r: C::ScalarField, + W: &Self::Witness, // running witness + w: &Self::Witness, // incoming witness + aux: &Self::ProverAux, + ) -> Result; + + /// computes the auxiliary parameters, eg. in Nova: (T, cmT), in Ova: T + fn compute_aux( + cs_prover_params: &CS::ProverParams, + r1cs: &R1CS, + W_i: &Self::Witness, + U_i: &Self::CommittedInstance, + w_i: &Self::Witness, + u_i: &Self::CommittedInstance, + ) -> Result<(Self::ProverAux, Self::VerifierAux), Error>; + + fn get_challenge>( + transcript: &mut T, + pp_hash: C::ScalarField, // public params hash + U_i: &Self::CommittedInstance, + u_i: &Self::CommittedInstance, + aux: &Self::VerifierAux, // ie. in Nova wouild be cmT, in Ova it's empty + ) -> Vec; + + /// NIFS.P. Notice that this method is implemented at the trait level, and depends on the other + /// two methods `fold_witness` and `verify`. + fn prove( + r: C::ScalarField, + W_i: &Self::Witness, // running witness + U_i: &Self::CommittedInstance, // running committed instance + w_i: &Self::Witness, // incoming witness + u_i: &Self::CommittedInstance, // incoming committed instance + aux_p: &Self::ProverAux, + aux_v: &Self::VerifierAux, + ) -> Result<(Self::Witness, Self::CommittedInstance), Error> { + let w = Self::fold_witness(r, W_i, w_i, aux_p)?; + let ci = Self::verify(r, U_i, u_i, aux_v); + Ok((w, ci)) + } + + /// NIFS.V + fn verify( + // r comes from the transcript, and is a n-bit (N_BITS_CHALLENGE) element + r: C::ScalarField, + U_i: &Self::CommittedInstance, + u_i: &Self::CommittedInstance, + aux: &Self::VerifierAux, + ) -> Self::CommittedInstance; +} + /// Implements `Arith` for R1CS, where the witness is of type [`Witness`], and /// the committed instance is of type [`CommittedInstance`]. /// @@ -98,24 +168,3 @@ impl ArithSampler, CommittedInstance> for R1CS Arith, OvaCommittedInstance> for R1CS> { - type Evaluation = Vec>; - - fn eval_relation( - &self, - w: &OvaWitness, - u: &OvaCommittedInstance, - ) -> Result { - self.eval_at_z(&[&[u.mu], u.x.as_slice(), &w.w].concat()) - } - - fn check_evaluation( - w: &OvaWitness, - _u: &OvaCommittedInstance, - e: Self::Evaluation, - ) -> Result<(), Error> { - (w.e == e).then_some(()).ok_or(Error::NotSatisfied) - } -} diff --git a/folding-schemes/src/folding/nova/zk.rs b/folding-schemes/src/folding/nova/zk.rs index 4444eef2..33f30cde 100644 --- a/folding-schemes/src/folding/nova/zk.rs +++ b/folding-schemes/src/folding/nova/zk.rs @@ -51,7 +51,9 @@ use ark_r1cs_std::{ use crate::{commitment::CommitmentScheme, folding::circuits::CF2, frontend::FCircuit, Error}; -use super::{circuits::ChallengeGadget, nifs::NIFS, CommittedInstance, Nova, Witness}; +use super::{ + circuits::ChallengeGadget, nifs::NIFS, traits::NIFSTrait, CommittedInstance, Nova, Witness, +}; // We use the same definition of a folding proof as in https://eprint.iacr.org/2023/969.pdf // It consists in the commitment to the T term @@ -83,7 +85,13 @@ where u_i: CommittedInstance, cmT: C1, ) -> Result { - let r_bits = ChallengeGadget::::get_challenge_native(sponge, pp_hash, U_i, u_i, cmT); + let r_bits = ChallengeGadget::>::get_challenge_native( + sponge, + pp_hash, + &U_i, + &u_i, + Some(&cmT), + ); C1::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits)).ok_or(Error::OutOfBounds) } @@ -134,9 +142,8 @@ where )?; // c. Compute fold - let (W_f, U_f) = NIFS::::fold_instances( - r, &nova.w_i, &nova.u_i, &nova.W_i, &nova.U_i, &T, cmT, - )?; + let (W_f, U_f) = + NIFS::::prove(r, &nova.w_i, &nova.u_i, &nova.W_i, &nova.U_i, &T, &cmT)?; // d. Store folding proof let pi = FoldingProof { cmT }; @@ -161,15 +168,8 @@ where )?; // c. Compute fold - let (W_i_prime, _) = NIFS::::fold_instances( - r_2, - &W_f, - &U_f, - &W_r, - &U_r, - &T_i_prime, - cmT_i_prime, - )?; + let (W_i_prime, _) = + NIFS::::prove(r_2, &W_f, &U_f, &W_r, &U_r, &T_i_prime, &cmT_i_prime)?; // d. Store folding proof let pi_prime = FoldingProof { cmT: cmT_i_prime }; @@ -255,12 +255,7 @@ where )?; // b. Get the U_f instance - let U_f = NIFS::::fold_committed_instance( - r, - &proof.u_i, - &proof.U_i, - &proof.pi.cmT, - ); + let U_f = NIFS::::verify(r, &proof.u_i, &proof.U_i, &proof.pi.cmT); // 4. Obtain the U^{\prime}_i folded instance // a. Compute folding challenge @@ -273,12 +268,7 @@ where )?; // b. Compute fold - let U_i_prime = NIFS::::fold_committed_instance( - r_2, - &U_f, - &proof.U_r, - &proof.pi_prime.cmT, - ); + let U_i_prime = NIFS::::verify(r_2, &U_f, &proof.U_r, &proof.pi_prime.cmT); // 5. Check that W^{\prime}_i is a satisfying witness r1cs.check_relation(&proof.W_i_prime, &U_i_prime)?; diff --git a/folding-schemes/src/folding/ova/mod.rs b/folding-schemes/src/folding/ova/mod.rs deleted file mode 100644 index de109684..00000000 --- a/folding-schemes/src/folding/ova/mod.rs +++ /dev/null @@ -1,157 +0,0 @@ -use std::marker::PhantomData; - -/// Implements the scheme described in [Ova](https://hackmd.io/V4838nnlRKal9ZiTHiGYzw?view). -/// Ova is a slight modification to Nova that shaves off 1 group operation and a few hashes. -/// The key idea is that we can commit to $T$ and $W$ in one commitment. -/// -/// This slightly breaks the abstraction between the IVC scheme and the accumulation scheme. -/// We assume that the accumulation prover receives $\vec{w}$ as input which proves the current step -/// of the computation and that the *previous* accumulation was performed correctly. -/// Note that $\vec{w}$ can be generated without being aware of $\vec{t}$ or $\alpha$. -/// Alternatively the accumulation prover can receive the commitment to $\vec{w}$ as input -/// and add to it the commitment to $\vec{t}$. -/// -/// This yields a commitment to the concatenated vector $\vec{w}||\vec{t}$. -/// This works because both $W$ and $T$ are multiplied by the same challenge $\alpha$ in Nova. -use ark_crypto_primitives::sponge::Absorb; -use ark_ec::{CurveGroup, Group}; -use ark_ff::PrimeField; -use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; -use ark_std::fmt::Debug; -use ark_std::rand::RngCore; -use ark_std::{One, UniformRand, Zero}; - -use crate::arith::r1cs::R1CS; -use crate::commitment::CommitmentScheme; -use crate::constants::NOVA_N_BITS_RO; -use crate::folding::{circuits::CF1, traits::Dummy}; -use crate::transcript::{AbsorbNonNative, Transcript}; -use crate::Error; - -pub mod nifs; - -/// A CommittedInstance in [Ova](https://hackmd.io/V4838nnlRKal9ZiTHiGYzw?view) is represented by `W` or `W'`. -/// It is the result of the commitment to a vector that contains the witness `w` concatenated -/// with `t` or `e` + the public inputs `x` and a relaxation factor `mu`. -#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] -pub struct CommittedInstance { - pub mu: C::ScalarField, - pub x: Vec, - pub cmWE: C, -} - -impl Absorb for CommittedInstance -where - C::ScalarField: Absorb, -{ - fn to_sponge_bytes(&self, dest: &mut Vec) { - C::ScalarField::batch_to_sponge_bytes(&self.to_sponge_field_elements_as_vec(), dest); - } - - fn to_sponge_field_elements(&self, dest: &mut Vec) { - self.mu.to_sponge_field_elements(dest); - self.x.to_sponge_field_elements(dest); - // We cannot call `to_native_sponge_field_elements(dest)` directly, as - // `to_native_sponge_field_elements` needs `F` to be `C::ScalarField`, - // but here `F` is a generic `PrimeField`. - self.cmWE - .to_native_sponge_field_elements_as_vec() - .to_sponge_field_elements(dest); - } -} - -// Clippy flag needed for now. -#[allow(dead_code)] -/// A Witness in [Ova](https://hackmd.io/V4838nnlRKal9ZiTHiGYzw?view) is represented by `w`. -/// It also contains a blinder which can or not be used when committing to the witness itself. -#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] -pub struct Witness { - pub w: Vec, - pub rW: C::ScalarField, -} - -impl Witness { - /// Generates a new `Witness` instance from a given witness vector. - /// If `H = true`, then we assume we want to blind it at commitment time, - /// hence sampling `rW` from the randomness passed. - pub fn new(w: Vec, mut rng: impl RngCore) -> Self { - Self { - w, - rW: if H { - C::ScalarField::rand(&mut rng) - } else { - C::ScalarField::zero() - }, - } - } - - /// Computes the `W` or `W'` commitment (The accumulated-instance W' or the incoming-instance W) - /// as specified in Ova. See: . - /// - /// This is the result of concatenating the accumulated-instance `w` vector with - /// `e` or `t`. - /// Generates a [`CommittedInstance`] as a result which will or not be blinded depending on how the - /// const generic `HC` is set up. - /// - /// This is the exact trick that allows Ova to save up 1 commitment with respect to Nova. - /// At the cost of loosing the PCD property and only maintaining the IVC one. - pub fn commit, const HC: bool>( - &self, - params: &CS::ProverParams, - t_or_e: Vec, - x: Vec, - ) -> Result, Error> { - let cmWE = CS::commit(params, &[self.w.clone(), t_or_e].concat(), &self.rW)?; - Ok(CommittedInstance { - mu: C::ScalarField::one(), - cmWE, - x, - }) - } -} - -impl Dummy<&R1CS>> for Witness { - fn dummy(r1cs: &R1CS>) -> Self { - Self { - w: vec![C::ScalarField::zero(); r1cs.A.n_cols - 1 - r1cs.l], - rW: C::ScalarField::zero(), - } - } -} - -pub struct ChallengeGadget { - _c: PhantomData, -} -impl ChallengeGadget -where - C: CurveGroup, - ::BaseField: PrimeField, - ::ScalarField: Absorb, -{ - pub fn get_challenge_native>( - transcript: &mut T, - pp_hash: C::ScalarField, // public params hash - // Running instance - U_i: CommittedInstance, - // Incoming instance - u_i: CommittedInstance, - ) -> Vec { - // NOTICE: This isn't following the order of the HackMD. - // As long as we match it. We should not have any issues. - transcript.absorb(&pp_hash); - transcript.absorb(&U_i); - transcript.absorb(&u_i); - transcript.squeeze_bits(NOVA_N_BITS_RO) - } -} - -// Simple auxiliary structure mainly used to help pass a witness for which we can check -// easily an R1CS relation. -// Notice that checking it requires us to have `E` as per [`Arith`] trait definition. -// But since we don't hold `E` nor `e` within the NIFS, we create this structure to pass -// `e` such that the check can be done. -#[derive(Debug, Clone)] -pub(crate) struct TestingWitness { - pub(crate) w: Vec, - pub(crate) e: Vec, -} diff --git a/folding-schemes/src/folding/ova/nifs.rs b/folding-schemes/src/folding/ova/nifs.rs deleted file mode 100644 index 3672c19d..00000000 --- a/folding-schemes/src/folding/ova/nifs.rs +++ /dev/null @@ -1,482 +0,0 @@ -/// This module contains the implementation of the Ova scheme NIFS (Non-Interactive Folding Scheme) as -/// outlined in the protocol description doc: -/// authored by Benedikt Bünz. -use ark_crypto_primitives::sponge::Absorb; -use ark_ec::{CurveGroup, Group}; -use ark_std::One; -use std::marker::PhantomData; - -use super::{CommittedInstance, Witness}; -use crate::arith::r1cs::R1CS; -use crate::commitment::CommitmentScheme; -use crate::transcript::Transcript; -use crate::utils::vec::{hadamard, mat_vec_mul, vec_scalar_mul, vec_sub}; -use crate::Error; - -/// Implements all the operations executed by the Non-Interactive Folding Scheme described in the protocol -/// spec by Bünz in the [original HackMD](https://hackmd.io/V4838nnlRKal9ZiTHiGYzw?both#Construction). -/// `H` specifies whether the NIFS will use a blinding factor -pub struct NIFS, const H: bool = false> { - _c: PhantomData, - _cp: PhantomData, -} - -impl, const H: bool> NIFS -where - ::ScalarField: Absorb, -{ - /// Computes the T parameter (Cross Terms) as in Nova. - /// The wrapper is only in place to facilitate the calling as we need - /// to reconstruct the `z`s being folded in order to compute T. - pub fn compute_T( - r1cs: &R1CS, - w_i: &Witness, - x_i: &[C::ScalarField], - W_i: &Witness, - X_i: &[C::ScalarField], - mu: C::ScalarField, - ) -> Result, Error> { - crate::folding::nova::nifs::NIFS::::compute_T( - r1cs, - C::ScalarField::one(), - mu, - &[vec![C::ScalarField::one()], x_i.to_vec(), w_i.w.to_vec()].concat(), - &[vec![mu], X_i.to_vec(), W_i.w.to_vec()].concat(), - ) - } - - /// Computes the E parameter (Error Terms) as in Nova. - /// The wrapper is only in place to facilitate the calling as we need - /// to reconstruct the `z`s being folded in order to compute E. - /// - /// Not only that, but notice that the incoming-instance `mu` parameter is always - /// equal to 1. Therefore, we can save the some computations. - pub fn compute_E( - r1cs: &R1CS, - W_i: &Witness, - X_i: &[C::ScalarField], - mu: C::ScalarField, - ) -> Result, Error> { - let (A, B, C) = (r1cs.A.clone(), r1cs.B.clone(), r1cs.C.clone()); - - let z_prime = [&[mu], X_i, &W_i.w].concat(); - // this is parallelizable (for the future) - let Az_prime = mat_vec_mul(&A, &z_prime)?; - let Bz_prime = mat_vec_mul(&B, &z_prime)?; - let Cz_prime = mat_vec_mul(&C, &z_prime)?; - - let Az_prime_Bz_prime = hadamard(&Az_prime, &Bz_prime)?; - let muCz_prime = vec_scalar_mul(&Cz_prime, &mu); - - vec_sub(&Az_prime_Bz_prime, &muCz_prime) - } - - /// Folds 2 [`CommittedInstance`]s returning a freshly folded one as is specified - /// in: . - /// Here, alpha is a randomness sampled from a [`Transcript`]. - pub fn fold_committed_instance( - alpha: C::ScalarField, - // This is W (incoming) - u_i: &CommittedInstance, - // This is W' (running) - U_i: &CommittedInstance, - ) -> CommittedInstance { - let mu = U_i.mu + alpha; // u_i.mu **IS ALWAYS 1 in OVA** as we just can do sequential IVC. - let cmWE = U_i.cmWE + u_i.cmWE.mul(alpha); - let x = U_i - .x - .iter() - .zip(&u_i.x) - .map(|(a, b)| *a + (alpha * b)) - .collect::>(); - - CommittedInstance:: { cmWE, mu, x } - } - - /// Folds 2 [`Witness`]s returning a freshly folded one as is specified - /// in: . - /// Here, alpha is a randomness sampled from a [`Transcript`]. - pub fn fold_witness( - alpha: C::ScalarField, - // incoming instance - w_i: &Witness, - // running instance - W_i: &Witness, - ) -> Result, Error> { - let w: Vec = W_i - .w - .iter() - .zip(&w_i.w) - .map(|(a, b)| *a + (alpha * b)) - .collect(); - - let rW = W_i.rW + alpha * w_i.rW; - Ok(Witness:: { w, rW }) - } - - /// fold_instances is part of the NIFS.P logic described in - /// [Ova](https://hackmd.io/V4838nnlRKal9ZiTHiGYzw?both#Construction)'s Construction section. - /// It returns the folded [`CommittedInstance`] and [`Witness`]. - pub fn fold_instances( - r: C::ScalarField, - // incoming instance - w_i: &Witness, - u_i: &CommittedInstance, - // running instance - W_i: &Witness, - U_i: &CommittedInstance, - ) -> Result<(Witness, CommittedInstance), Error> { - // fold witness - let w3 = NIFS::::fold_witness(r, w_i, W_i)?; - - // fold committed instances - let ci3 = NIFS::::fold_committed_instance(r, u_i, U_i); - - Ok((w3, ci3)) - } - - /// Implements NIFS.V (accumulation verifier) logic described in [Ova](https://hackmd.io/V4838nnlRKal9ZiTHiGYzw?both#Construction)'s - /// Construction section. - /// It returns the folded [`CommittedInstance`]. - pub fn verify( - // r comes from the transcript, and is a n-bit (N_BITS_CHALLENGE) element - alpha: C::ScalarField, - // incoming instance - u_i: &CommittedInstance, - // running instance - U_i: &CommittedInstance, - ) -> CommittedInstance { - NIFS::::fold_committed_instance(alpha, u_i, U_i) - } - - #[cfg(test)] - /// Verify committed folded instance (ui) relations. Notice that this method does not open the - /// commitments, but just checks that the given committed instances (ui1, ui2) when folded - /// result in the folded committed instance (ui3) values. - pub(crate) fn verify_folded_instance( - r: C::ScalarField, - // incoming instance - u_i: &CommittedInstance, - // running instance - U_i: &CommittedInstance, - // folded instance - folded_instance: &CommittedInstance, - ) -> Result<(), Error> { - let expected = Self::fold_committed_instance(r, u_i, U_i); - if folded_instance.mu != expected.mu - || folded_instance.cmWE != expected.cmWE - || folded_instance.x != expected.x - { - return Err(Error::NotSatisfied); - } - Ok(()) - } - - /// Generates a [`CS::Proof`] for the given [`CommittedInstance`] and [`Witness`] pair. - pub fn prove_commitment( - r1cs: &R1CS, - tr: &mut impl Transcript, - cs_prover_params: &CS::ProverParams, - w: &Witness, - ci: &CommittedInstance, - ) -> Result { - let e = NIFS::::compute_E(r1cs, w, &ci.x, ci.mu).unwrap(); - let w_concat_e: Vec = [w.w.clone(), e].concat(); - CS::prove(cs_prover_params, tr, &ci.cmWE, &w_concat_e, &w.rW, None) - } -} - -#[cfg(test)] -pub mod tests { - use super::*; - use crate::folding::ova::ChallengeGadget; - use crate::transcript::poseidon::poseidon_canonical_config; - use crate::{ - arith::{ - r1cs::tests::{get_test_r1cs, get_test_z}, - Arith, - }, - folding::traits::Dummy, - }; - use crate::{ - commitment::pedersen::{Params as PedersenParams, Pedersen}, - folding::ova::TestingWitness, - }; - use ark_crypto_primitives::sponge::{ - poseidon::{PoseidonConfig, PoseidonSponge}, - CryptographicSponge, - }; - use ark_ff::{BigInteger, PrimeField}; - use ark_pallas::{Fr, Projective}; - use ark_std::{test_rng, UniformRand, Zero}; - - fn compute_E_check_relation, const H: bool>( - r1cs: &R1CS, - w: &Witness, - u: &CommittedInstance, - ) where - ::ScalarField: Absorb, - { - let e = NIFS::::compute_E(r1cs, w, &u.x, u.mu).unwrap(); - r1cs.check_relation(&TestingWitness:: { e, w: w.w.clone() }, u) - .unwrap(); - } - - #[allow(clippy::type_complexity)] - pub(crate) fn prepare_simple_fold_inputs() -> ( - PedersenParams, - PoseidonConfig, - R1CS, - Witness, // w - CommittedInstance, // u - Witness, // W - CommittedInstance, // U - Witness, // w_fold - CommittedInstance, // u_fold - Vec, // r_bits - C::ScalarField, // r_Fr - ) - where - C: CurveGroup, - ::BaseField: PrimeField, - C::ScalarField: Absorb, - { - // Index 1 represents the incoming instance - // Index 2 represents the accumulated instance - let r1cs = get_test_r1cs(); - let z1 = get_test_z(3); - let z2 = get_test_z(4); - let (w1, x1) = r1cs.split_z(&z1); - let (w2, x2) = r1cs.split_z(&z2); - - let w = Witness::::new::(w1.clone(), test_rng()); - let W: Witness = Witness::::new::(w2.clone(), test_rng()); - - let mut rng = ark_std::test_rng(); - let (pedersen_params, _) = Pedersen::::setup(&mut rng, r1cs.A.n_cols).unwrap(); - - // In order to be able to compute the committed instances, we need to compute `t` and `e` - // compute t - let t = NIFS::>::compute_T(&r1cs, &w, &x1, &W, &x2, C::ScalarField::one()) - .unwrap(); - // compute e (mu is 1 although is the running instance as we are "crafting it"). - let e = NIFS::>::compute_E(&r1cs, &W, &x2, C::ScalarField::one()).unwrap(); - // compute committed instances - // Incoming-instance - let u = w - .commit::, false>(&pedersen_params, t, x1.clone()) - .unwrap(); - // Running-instance - let U = W - .commit::, false>(&pedersen_params, e, x2.clone()) - .unwrap(); - - let poseidon_config = poseidon_canonical_config::(); - let mut transcript = PoseidonSponge::::new(&poseidon_config); - - let pp_hash = C::ScalarField::from(42u32); // only for test - - let alpha_bits = ChallengeGadget::::get_challenge_native( - &mut transcript, - pp_hash, - u.clone(), - U.clone(), - ); - let alpha_Fr = C::ScalarField::from_bigint(BigInteger::from_bits_le(&alpha_bits)).unwrap(); - - let (w_fold, u_fold) = - NIFS::, false>::fold_instances(alpha_Fr, &w, &u, &W, &U).unwrap(); - - // Check correctness of the R1CS relation of the folded instance. - compute_E_check_relation::, false>(&r1cs, &w_fold, &u_fold); - - ( - pedersen_params, - poseidon_config, - r1cs, - w, - u, - W, - U, - w_fold, - u_fold, - alpha_bits, - alpha_Fr, - ) - } - - // fold 2 dummy instances and check that the folded instance holds the relaxed R1CS relation - #[test] - fn test_nifs_fold_dummy() { - let mut rng = ark_std::test_rng(); - let r1cs = get_test_r1cs::(); - - let w_dummy = Witness::::dummy(&r1cs); - let (pedersen_params, _) = Pedersen::::setup(&mut rng, r1cs.A.n_cols).unwrap(); - - // In order to be able to compute the committed instances, we need to compute `t` and `e` - // compute t - let t = NIFS::>::compute_T( - &r1cs, - &w_dummy, - &[Fr::zero()], - &w_dummy, - &[Fr::zero()], - Fr::one(), - ) - .unwrap(); - - // compute e - let e = NIFS::>::compute_E( - &r1cs, - &w_dummy, - &[Fr::zero()], - Fr::one(), - ) - .unwrap(); - - // dummy incoming instance, witness and public inputs - let u_dummy = w_dummy - .commit::, false>(&pedersen_params, t, vec![Fr::zero()]) - .unwrap(); - - // dummy accumulated instance, witness and public inputs - let U_dummy = w_dummy - .commit::, false>(&pedersen_params, e.clone(), vec![Fr::zero()]) - .unwrap(); - - let w_i = w_dummy.clone(); - let u_i = u_dummy.clone(); - let W_i = w_dummy.clone(); - let U_i = U_dummy.clone(); - - // Check correctness of the R1CS relations of both instances. - compute_E_check_relation::, false>(&r1cs, &w_i, &u_i); - compute_E_check_relation::, false>(&r1cs, &W_i, &U_i); - - // NIFS.P - let r_Fr = Fr::from(3_u32); - let (w_fold, u_fold) = - NIFS::>::fold_instances(r_Fr, &w_i, &u_i, &W_i, &U_i) - .unwrap(); - - // Check correctness of the R1CS relation of both instances. - compute_E_check_relation::, false>( - &r1cs, &w_fold, &u_fold, - ); - } - - // fold 2 instances into one - #[test] - fn test_nifs_one_fold() { - let (pedersen_params, poseidon_config, r1cs, w, u, W, U, w_fold, u_fold, _, r) = - prepare_simple_fold_inputs(); - - // NIFS.V - let u_fold_v = NIFS::>::verify(r, &u, &U); - assert_eq!(u_fold_v, u_fold); - - // Check that relations hold for the 2 inputted instances and the folded one - compute_E_check_relation::, false>(&r1cs, &w, &u); - compute_E_check_relation::, false>(&r1cs, &W, &U); - compute_E_check_relation::, false>( - &r1cs, &w_fold, &u_fold, - ); - - // check that folded commitments from folded instance (u) are equal to folding the - // use folded rW to commit w_fold - let e_fold = NIFS::>::compute_E( - &r1cs, &w_fold, &u_fold.x, u_fold.mu, - ) - .unwrap(); - let mut u_fold_expected = w_fold - .commit::, false>(&pedersen_params, e_fold, u_fold.x.clone()) - .unwrap(); - u_fold_expected.mu = u_fold.mu; - assert_eq!(u_fold_expected.cmWE, u_fold.cmWE); - - // NIFS.Verify_Folded_Instance: - NIFS::>::verify_folded_instance(r, &u, &U, &u_fold) - .unwrap(); - - // init Prover's transcript - let mut transcript_p = PoseidonSponge::::new(&poseidon_config); - // init Verifier's transcript - let mut transcript_v = PoseidonSponge::::new(&poseidon_config); - - // prove the u_fold.cmWE - let cm_proof = NIFS::>::prove_commitment( - &r1cs, - &mut transcript_p, - &pedersen_params, - &w_fold, - &u_fold, - ) - .unwrap(); - - // verify the u_fold.cmWE. - Pedersen::::verify( - &pedersen_params, - &mut transcript_v, - &u_fold.cmWE, - &cm_proof, - ) - .unwrap(); - } - - #[test] - fn test_nifs_fold_loop() { - let r1cs = get_test_r1cs(); - let z = get_test_z(3); - let (w, x) = r1cs.split_z(&z); - - let mut rng = ark_std::test_rng(); - let (pedersen_params, _) = Pedersen::::setup(&mut rng, r1cs.A.n_cols).unwrap(); - - // prepare the running instance - let mut W = Witness::::new::(w.clone(), &mut rng); - // Compute e - let e = - NIFS::>::compute_E(&r1cs, &W, &x, Fr::one()).unwrap(); - // Compute running `CommittedInstance`. - let mut U = W - .commit::, false>(&pedersen_params, e, x) - .unwrap(); - - let num_iters = 10; - for i in 0..num_iters { - // prepare the incoming instance - let incoming_instance_z = get_test_z(i + 4); - let (w, x) = r1cs.split_z(&incoming_instance_z); - let w = Witness::::new::(w, test_rng()); - let t = - NIFS::>::compute_T(&r1cs, &w, &U.x, &w, &x, U.mu) - .unwrap(); - let u = w - .commit::, false>(&pedersen_params, t, x) - .unwrap(); - - // Check incoming instance is Ok. - compute_E_check_relation::, false>(&r1cs, &w, &u); - - // Generate "transcript randomness" - let alpha = Fr::rand(&mut rng); // folding challenge would come from the RO - - // NIFS.P - let (w_folded, _) = - NIFS::>::fold_instances(alpha, &w, &u, &W, &u) - .unwrap(); - - // NIFS.V - let u_folded = NIFS::>::verify(alpha, &u, &U); - - compute_E_check_relation::, false>( - &r1cs, &w_folded, &u_folded, - ); - - // set running_instance for next loop iteration - W = w_folded; - U = u_folded; - } - } -}