From 88bbd9cff72e7d199234fd8081a8def94f338b9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20P=C3=A9rez?= <37264926+CPerezz@users.noreply.github.com> Date: Thu, 3 Oct 2024 10:36:09 +0200 Subject: [PATCH] Implement OVA NIFS (#163) * feat: Basic Ova NIFS impl working The implementation follows the spec outlined by Bunz in: https://hackmd.io/V4838nnlRKal9ZiTHiGYzw?view. With this, the NIFS works and passes all tests. * chore: Resolve all TODOs and warnings * add: Docs for the NIMFS of the scheme and related structs * chore: update imports * add: Docs for all Ova NIFS functions * fix: Unify nomenclature for all variables and elements within NIFS tests * fix: Uniformize instance order in fn calls * chore: pass clippy * chore: clear all clippy findings in tests * chore: Remove `mimc` from spelling checks * chore: Address PR reviews --- .github/workflows/typos.toml | 1 + .../src/folding/hypernova/decider_eth.rs | 4 +- folding-schemes/src/folding/hypernova/mod.rs | 3 +- folding-schemes/src/folding/mod.rs | 1 + .../src/folding/nova/decider_eth.rs | 4 +- folding-schemes/src/folding/nova/nifs.rs | 2 +- folding-schemes/src/folding/nova/traits.rs | 24 + folding-schemes/src/folding/ova/mod.rs | 157 ++++++ folding-schemes/src/folding/ova/nifs.rs | 482 ++++++++++++++++++ 9 files changed, 672 insertions(+), 6 deletions(-) create mode 100644 folding-schemes/src/folding/ova/mod.rs create mode 100644 folding-schemes/src/folding/ova/nifs.rs diff --git a/.github/workflows/typos.toml b/.github/workflows/typos.toml index 30e6074..e64bbc6 100644 --- a/.github/workflows/typos.toml +++ b/.github/workflows/typos.toml @@ -1,2 +1,3 @@ [default.extend-words] groth = "groth" +mimc = "mimc" diff --git a/folding-schemes/src/folding/hypernova/decider_eth.rs b/folding-schemes/src/folding/hypernova/decider_eth.rs index e45876e..f897eff 100644 --- a/folding-schemes/src/folding/hypernova/decider_eth.rs +++ b/folding-schemes/src/folding/hypernova/decider_eth.rs @@ -416,7 +416,7 @@ pub mod tests { let verified = D::verify( decider_vp.clone(), - hypernova.i.clone(), + hypernova.i, hypernova.z_0.clone(), hypernova.z_i.clone(), &(), @@ -483,7 +483,7 @@ pub mod tests { let verified = D::verify( decider_vp_deserialized, - i_deserialized.clone(), + i_deserialized, z_0_deserialized.clone(), z_i_deserialized.clone(), &(), diff --git a/folding-schemes/src/folding/hypernova/mod.rs b/folding-schemes/src/folding/hypernova/mod.rs index 5d570fe..f476b21 100644 --- a/folding-schemes/src/folding/hypernova/mod.rs +++ b/folding-schemes/src/folding/hypernova/mod.rs @@ -950,6 +950,7 @@ mod tests { test_ivc_opt::, Pedersen, false>(poseidon_config, F_circuit); } + #[allow(clippy::type_complexity)] // test_ivc allowing to choose the CommitmentSchemes pub fn test_ivc_opt< CS1: CommitmentScheme, @@ -1028,7 +1029,7 @@ mod tests { hypernova_params.1.clone(), // verifier_params z_0, hypernova.z_i.clone(), - hypernova.i.clone(), + hypernova.i, running_instance.clone(), incoming_instance.clone(), cyclefold_instance.clone(), diff --git a/folding-schemes/src/folding/mod.rs b/folding-schemes/src/folding/mod.rs index 0f3a66f..e9d284a 100644 --- a/folding-schemes/src/folding/mod.rs +++ b/folding-schemes/src/folding/mod.rs @@ -1,5 +1,6 @@ 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/decider_eth.rs b/folding-schemes/src/folding/nova/decider_eth.rs index 12ab244..abf718e 100644 --- a/folding-schemes/src/folding/nova/decider_eth.rs +++ b/folding-schemes/src/folding/nova/decider_eth.rs @@ -402,7 +402,7 @@ pub mod tests { let start = Instant::now(); let verified = D::verify( decider_vp.clone(), - nova.i.clone(), + nova.i, nova.z_0.clone(), nova.z_i.clone(), &nova.U_i, @@ -516,7 +516,7 @@ pub mod tests { let start = Instant::now(); let verified = D::verify( decider_vp.clone(), - nova.i.clone(), + nova.i, nova.z_0.clone(), nova.z_i.clone(), &nova.U_i, diff --git a/folding-schemes/src/folding/nova/nifs.rs b/folding-schemes/src/folding/nova/nifs.rs index 238655b..0c25afa 100644 --- a/folding-schemes/src/folding/nova/nifs.rs +++ b/folding-schemes/src/folding/nova/nifs.rs @@ -367,7 +367,7 @@ pub mod tests { // 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, &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) diff --git a/folding-schemes/src/folding/nova/traits.rs b/folding-schemes/src/folding/nova/traits.rs index 8bf336a..9411799 100644 --- a/folding-schemes/src/folding/nova/traits.rs +++ b/folding-schemes/src/folding/nova/traits.rs @@ -6,6 +6,9 @@ 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::Error; /// Implements `Arith` for R1CS, where the witness is of type [`Witness`], and @@ -95,3 +98,24 @@ 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/ova/mod.rs b/folding-schemes/src/folding/ova/mod.rs new file mode 100644 index 0000000..de10968 --- /dev/null +++ b/folding-schemes/src/folding/ova/mod.rs @@ -0,0 +1,157 @@ +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 new file mode 100644 index 0000000..3672c19 --- /dev/null +++ b/folding-schemes/src/folding/ova/nifs.rs @@ -0,0 +1,482 @@ +/// 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; + } + } +}