From 637dc3c5965bd51675a4b6aec6f46d40f7d5fcd3 Mon Sep 17 00:00:00 2001 From: Nick Dimitriou Date: Thu, 19 Sep 2024 17:18:09 +0100 Subject: [PATCH 1/8] Adding Mova Co-Authored-By: Togzhan Barakbayeva <45527668+btogzhan2000@users.noreply.github.com> Co-Authored-By: Ilia Vlasov <5365540+elijahvlasov@users.noreply.github.com> Co-Authored-By: matthew-a-klein <96837318+matthew-a-klein@users.noreply.github.com> --- folding-schemes/src/folding/mod.rs | 1 + folding-schemes/src/folding/mova/mod.rs | 117 +++++ folding-schemes/src/folding/mova/nifs.rs | 459 ++++++++++++++++++ .../src/folding/mova/pointvsline.rs | 280 +++++++++++ folding-schemes/src/folding/mova/traits.rs | 51 ++ 5 files changed, 908 insertions(+) create mode 100644 folding-schemes/src/folding/mova/mod.rs create mode 100644 folding-schemes/src/folding/mova/nifs.rs create mode 100644 folding-schemes/src/folding/mova/pointvsline.rs create mode 100644 folding-schemes/src/folding/mova/traits.rs diff --git a/folding-schemes/src/folding/mod.rs b/folding-schemes/src/folding/mod.rs index 8f76a714..7964fdc7 100644 --- a/folding-schemes/src/folding/mod.rs +++ b/folding-schemes/src/folding/mod.rs @@ -1,4 +1,5 @@ pub mod circuits; pub mod hypernova; +pub mod mova; pub mod nova; pub mod protogalaxy; diff --git a/folding-schemes/src/folding/mova/mod.rs b/folding-schemes/src/folding/mova/mod.rs new file mode 100644 index 00000000..11b6836f --- /dev/null +++ b/folding-schemes/src/folding/mova/mod.rs @@ -0,0 +1,117 @@ +use crate::commitment::CommitmentScheme; +use crate::transcript::AbsorbNonNative; +use crate::utils::mle::dense_vec_to_dense_mle; +use crate::utils::vec::is_zero_vec; +use crate::Error; +use ark_crypto_primitives::sponge::Absorb; +use ark_ec::{CurveGroup, Group}; +use ark_ff::PrimeField; +use ark_poly::MultilinearExtension; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_std::{log2, One, UniformRand, Zero}; +use rand::RngCore; + +/// Implements the scheme described in [Mova](https://eprint.iacr.org/2024/1220.pdf) +mod nifs; +mod pointvsline; +mod traits; + +#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] +pub struct CommittedInstance { + // Random evaluation point for the E + pub rE: Vec, + // MLE of E + pub mleE: C::ScalarField, + pub u: C::ScalarField, + pub cmW: C, + pub x: Vec, +} +#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] +pub struct Witness { + pub E: Vec, + pub W: Vec, + pub rW: C::ScalarField, +} + +#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] +pub struct InstanceWitness { + pub ci: CommittedInstance, + pub w: Witness, +} + +impl Witness +where + ::ScalarField: Absorb, +{ + pub fn new(w: Vec, e_len: usize, mut rng: impl RngCore) -> Self { + let rW = if H { + C::ScalarField::rand(&mut rng) + } else { + C::ScalarField::zero() + }; + + Self { + E: vec![C::ScalarField::zero(); e_len], + W: w, + rW, + } + } + + pub fn dummy(w_len: usize, e_len: usize) -> Self { + let rW = C::ScalarField::zero(); + let w = vec![C::ScalarField::zero(); w_len]; + + Self { + E: vec![C::ScalarField::zero(); e_len], + W: w, + rW, + } + } + + pub fn commit>( + &self, + params: &CS::ProverParams, + x: Vec, + rE: Vec, + ) -> Result, Error> { + let mut mleE = C::ScalarField::zero(); + if !is_zero_vec::(&self.E) { + let E = dense_vec_to_dense_mle(log2(self.E.len()) as usize, &self.E); + mleE = E.evaluate(&rE).ok_or(Error::NotExpectedLength( + rE.len(), + log2(self.E.len()) as usize, + ))?; + } + let cmW = CS::commit(params, &self.W, &self.rW)?; + Ok(CommittedInstance { + rE, + mleE, + u: C::ScalarField::one(), + cmW, + x, + }) + } +} + +impl Absorb for CommittedInstance +where + C::ScalarField: Absorb, +{ + fn to_sponge_bytes(&self, _dest: &mut Vec) { + // This is never called + unimplemented!() + } + + fn to_sponge_field_elements(&self, dest: &mut Vec) { + self.u.to_sponge_field_elements(dest); + self.x.to_sponge_field_elements(dest); + self.rE.to_sponge_field_elements(dest); + self.mleE.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.cmW + .to_native_sponge_field_elements_as_vec() + .to_sponge_field_elements(dest); + } +} diff --git a/folding-schemes/src/folding/mova/nifs.rs b/folding-schemes/src/folding/mova/nifs.rs new file mode 100644 index 00000000..b2db3fd0 --- /dev/null +++ b/folding-schemes/src/folding/mova/nifs.rs @@ -0,0 +1,459 @@ +use super::{CommittedInstance, InstanceWitness, Witness}; +use crate::arith::r1cs::R1CS; +use crate::commitment::CommitmentScheme; +use crate::folding::mova::pointvsline::{ + PointVsLine, PointVsLineProof, PointvsLineEvaluationClaim, +}; +use crate::folding::nova::nifs::NIFS as Nova; +use crate::transcript::Transcript; +use crate::utils::mle::dense_vec_to_dense_mle; +use crate::utils::vec::{vec_add, vec_scalar_mul}; +use crate::Error; +use ark_crypto_primitives::sponge::Absorb; +use ark_ec::{CurveGroup, Group}; +use ark_ff::PrimeField; +use ark_poly::MultilinearExtension; +use ark_std::log2; +use std::marker::PhantomData; + +/// Implements the Non-Interactive Folding Scheme described in section 4 of +/// [Mova](https://eprint.iacr.org/2024/1220.pdf) +/// `H` specifies whether the NIFS will use a blinding factor +pub struct NIFS< + C: CurveGroup, + CS: CommitmentScheme, + T: Transcript, + const H: bool = false, +> { + _c: PhantomData, + _cp: PhantomData, + _ct: PhantomData, +} + +pub struct Proof { + pub h_proof: PointVsLineProof, + pub mleE1_prime: C::ScalarField, + pub mleE2_prime: C::ScalarField, + pub mleT: C::ScalarField, +} + +impl, T: Transcript, const H: bool> + NIFS +where + ::ScalarField: Absorb, +{ + // Just a wrapper for Nova compute_T (compute cross-terms T) since the process is the same + pub fn compute_T( + r1cs: &R1CS, + u1: C::ScalarField, + u2: C::ScalarField, + z1: &[C::ScalarField], + z2: &[C::ScalarField], + ) -> Result, Error> { + Nova::::compute_T(r1cs, u1, u2, z1, z2) + } + + // Protocol 7 - point 3 (16) + pub fn fold_witness( + a: C::ScalarField, + w1: &Witness, + w2: &Witness, + T: &[C::ScalarField], + ) -> Result, Error> { + let a2 = a * a; + let E: Vec = vec_add( + &vec_add(&w1.E, &vec_scalar_mul(T, &a))?, + &vec_scalar_mul(&w2.E, &a2), + )?; + let W: Vec = + w1.W.iter() + .zip(&w2.W) + .map(|(i1, i2)| *i1 + (a * i2)) + .collect(); + + let rW = w1.rW + a * w2.rW; + Ok(Witness:: { E, W, rW }) + } + + // Protocol 7 - point 3 (15) + pub fn fold_committed_instance( + a: C::ScalarField, + ci1: &CommittedInstance, + ci2: &CommittedInstance, + rE_prime: &[C::ScalarField], + mleE1_prime: &C::ScalarField, + mleE2_prime: &C::ScalarField, + mleT: &C::ScalarField, + ) -> Result, Error> { + let a2 = a * a; + let mleE = *mleE1_prime + a * mleT + a2 * mleE2_prime; + let u = ci1.u + a * ci2.u; + let cmW = ci1.cmW + ci2.cmW.mul(a); + let x = ci1 + .x + .iter() + .zip(&ci2.x) + .map(|(i1, i2)| *i1 + (a * i2)) + .collect::>(); + + Ok(CommittedInstance:: { + rE: rE_prime.to_vec(), + mleE, + u, + cmW, + x, + }) + } + + /// [Mova](https://eprint.iacr.org/2024/1220.pdf)'s section 4. Protocol 8 + /// Returns a proof for the pt-vs-line operations along with the folded committed instance + /// instances and witness + #[allow(clippy::type_complexity)] + pub fn prove( + r1cs: &R1CS, + transcript: &mut impl Transcript, + ci1: &CommittedInstance, + ci2: &CommittedInstance, + w1: &Witness, + w2: &Witness, + ) -> Result<(Proof, InstanceWitness), Error> { + // Protocol 5 is pre-processing + transcript.absorb(ci1); + transcript.absorb(ci2); + + // Protocol 6 + let ( + h_proof, + PointvsLineEvaluationClaim { + mleE1_prime, + mleE2_prime, + rE_prime, + }, + ) = PointVsLine::::prove(transcript, ci1, ci2, w1, w2)?; + + // Protocol 7 + + transcript.absorb(&mleE1_prime); + transcript.absorb(&mleE2_prime); + + // Remember Z = (W, x, u) + let z1: Vec = [vec![ci1.u], ci1.x.to_vec(), w1.W.to_vec()].concat(); + let z2: Vec = [vec![ci2.u], ci2.x.to_vec(), w2.W.to_vec()].concat(); + + let T = Self::compute_T(r1cs, ci1.u, ci2.u, &z1, &z2)?; + + let n_vars: usize = log2(w1.E.len()) as usize; + if log2(T.len()) as usize != n_vars { + return Err(Error::NotEqual); + } + + let mleT = dense_vec_to_dense_mle(n_vars, &T); + let mleT_evaluated = mleT.evaluate(&rE_prime).ok_or(Error::EvaluationFail)?; + + transcript.absorb(&mleT_evaluated); + + let alpha_scalar = C::ScalarField::from_le_bytes_mod_order(b"alpha"); + transcript.absorb(&alpha_scalar); + let alpha: C::ScalarField = transcript.get_challenge(); + + Ok(( + Proof:: { + h_proof, + mleE1_prime, + mleE2_prime, + mleT: mleT_evaluated, + }, + InstanceWitness { + ci: Self::fold_committed_instance( + alpha, + ci1, + ci2, + &rE_prime, + &mleE1_prime, + &mleE2_prime, + &mleT_evaluated, + )?, + w: Self::fold_witness(alpha, w1, w2, &T)?, + }, + )) + } + + /// [Mova](https://eprint.iacr.org/2024/1220.pdf)'s section 4. It verifies the results from the proof + /// Both the folding and the pt-vs-line proof + /// returns the folded committed instance + pub fn verify( + transcript: &mut impl Transcript, + ci1: &CommittedInstance, + ci2: &CommittedInstance, + proof: &Proof, + ) -> Result, Error> { + transcript.absorb(ci1); + transcript.absorb(ci2); + let rE_prime = PointVsLine::::verify( + transcript, + ci1, + ci2, + &proof.h_proof, + &proof.mleE1_prime, + &proof.mleE2_prime, + )?; + + transcript.absorb(&proof.mleE1_prime); + transcript.absorb(&proof.mleE2_prime); + transcript.absorb(&proof.mleT); + + let alpha_scalar = C::ScalarField::from_le_bytes_mod_order(b"alpha"); + transcript.absorb(&alpha_scalar); + let alpha: C::ScalarField = transcript.get_challenge(); + + Self::fold_committed_instance( + alpha, + ci1, + ci2, + &rE_prime, + &proof.mleE1_prime, + &proof.mleE2_prime, + &proof.mleT, + ) + } +} + +#[cfg(test)] +pub mod tests { + use ark_crypto_primitives::sponge::{ + poseidon::{PoseidonConfig, PoseidonSponge}, + CryptographicSponge, + }; + use ark_ff::PrimeField; + use ark_pallas::{Fr, Projective}; + use ark_std::{test_rng, UniformRand, Zero}; + + use crate::arith::r1cs::tests::{get_test_r1cs, get_test_z}; + use crate::commitment::pedersen::{Params as PedersenParams, Pedersen}; + use crate::folding::mova::traits::MovaR1CS; + use crate::transcript::poseidon::poseidon_canonical_config; + + use super::*; + + #[allow(clippy::type_complexity)] + fn prepare_simple_fold_inputs() -> ( + PedersenParams, + PoseidonConfig, + R1CS, + Witness, // w1 + CommittedInstance, // ci1 + Witness, // w2 + CommittedInstance, // ci2 + Proof, // pt-vs-line + InstanceWitness, // w3, ci3 + ) + 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 mut rng = ark_std::test_rng(); + + let w1 = Witness::::new::(w1.clone(), r1cs.A.n_rows, &mut rng); + let w2 = Witness::::new::(w2.clone(), r1cs.A.n_rows, &mut rng); + + let (pedersen_params, _) = Pedersen::::setup(&mut rng, r1cs.A.n_cols).unwrap(); + + // compute committed instances + let rE_1: Vec = (0..log2(3)) + .map(|_| C::ScalarField::rand(&mut rng)) + .collect(); + let rE_2: Vec = (0..log2(4)) + .map(|_| C::ScalarField::rand(&mut rng)) + .collect(); + let ci1 = w1 + .commit::>(&pedersen_params, x1.clone(), rE_1) + .unwrap(); + let ci2 = w2 + .commit::>(&pedersen_params, x2.clone(), rE_2) + .unwrap(); + + let poseidon_config = poseidon_canonical_config::(); + let mut transcript_p = PoseidonSponge::::new(&poseidon_config); + + let result = NIFS::, PoseidonSponge>::prove( + &r1cs, + &mut transcript_p, + &ci1, + &ci2, + &w1, + &w2, + ) + .unwrap(); + let (proof, instance) = result; + + ( + pedersen_params, + poseidon_config, + r1cs, + w1, + ci1, + w2, + ci2, + proof, + instance, + ) + } + + // 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 (w1, x1) = r1cs.split_z(&z1); + + 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(w1.len(), r1cs.A.n_rows); + let mut u_dummy = w_dummy + .commit::>( + &pedersen_params, + vec![Fr::zero(); x1.len()], + vec![Fr::zero(); log2(3) as usize], + ) + .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_relaxed_instance_relation(&w_i, &u_i).unwrap(); + r1cs.check_relaxed_instance_relation(&W_i, &U_i).unwrap(); + + let poseidon_config = poseidon_canonical_config::(); + let mut transcript_p: PoseidonSponge = PoseidonSponge::::new(&poseidon_config); + + let result = NIFS::, PoseidonSponge>::prove( + &r1cs, + &mut transcript_p, + &u_i, + &U_i, + &w_i, + &W_i, + ) + .unwrap(); + + let (_proof, instance_witness) = result; + r1cs.check_relaxed_instance_relation(&instance_witness.w, &instance_witness.ci) + .unwrap(); + } + + // fold 2 instances into one + #[test] + fn test_nifs_one_fold() { + let (pedersen_params, poseidon_config, r1cs, w1, ci1, w2, ci2, proof, instance) = + prepare_simple_fold_inputs::(); + + // NIFS.V + let mut transcript_v: PoseidonSponge = PoseidonSponge::::new(&poseidon_config); + let ci3 = NIFS::, PoseidonSponge>::verify( + &mut transcript_v, + &ci1, + &ci2, + &proof, + ) + .unwrap(); + assert_eq!(ci3, instance.ci); + + // check that relations hold for the 2 inputted instances and the folded one + r1cs.check_relaxed_instance_relation(&w1, &ci1).unwrap(); + r1cs.check_relaxed_instance_relation(&w2, &ci2).unwrap(); + r1cs.check_relaxed_instance_relation(&instance.w, &instance.ci) + .unwrap(); + + // check that folded commitments from folded instance (ci) are equal to folding the + // use folded rE, rW to commit w3 + let ci3_expected = instance + .w + .commit::>(&pedersen_params, ci3.x.clone(), instance.ci.rE) + .unwrap(); + assert_eq!(ci3_expected.cmW, instance.ci.cmW); + } + + #[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 rE: Vec = (0..log2(3)).map(|_| Fr::rand(&mut rng)).collect(); + let mut running_instance_w = + Witness::::new::(w.clone(), r1cs.A.n_rows, test_rng()); + let mut running_committed_instance = running_instance_w + .commit::>(&pedersen_params, x, rE) + .unwrap(); + + r1cs.check_relaxed_instance_relation(&running_instance_w, &running_committed_instance) + .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 rE: Vec = (0..log2(3)).map(|_| Fr::rand(&mut rng)).collect(); + let incoming_committed_instance = incoming_instance_w + .commit::>(&pedersen_params, x, rE) + .unwrap(); + r1cs.check_relaxed_instance_relation( + &incoming_instance_w, + &incoming_committed_instance, + ) + .unwrap(); + + // NIFS.P + let poseidon_config = poseidon_canonical_config::(); + let mut transcript_p = PoseidonSponge::::new(&poseidon_config); + + let result = NIFS::, PoseidonSponge>::prove( + &r1cs, + &mut transcript_p, + &running_committed_instance, + &incoming_committed_instance, + &running_instance_w, + &incoming_instance_w, + ) + .unwrap(); + + let (proof, instance_witness) = result; + + // NIFS.V + let mut transcript_v: PoseidonSponge = PoseidonSponge::::new(&poseidon_config); + let _ci3 = NIFS::, PoseidonSponge>::verify( + &mut transcript_v, + &running_committed_instance, + &incoming_committed_instance, + &proof, + ) + .unwrap(); + + r1cs.check_relaxed_instance_relation(&instance_witness.w, &instance_witness.ci) + .unwrap(); + + // set running_instance for next loop iteration + running_instance_w = instance_witness.w; + running_committed_instance = instance_witness.ci; + } + } +} diff --git a/folding-schemes/src/folding/mova/pointvsline.rs b/folding-schemes/src/folding/mova/pointvsline.rs new file mode 100644 index 00000000..72e7dcbf --- /dev/null +++ b/folding-schemes/src/folding/mova/pointvsline.rs @@ -0,0 +1,280 @@ +use crate::folding::mova::{CommittedInstance, Witness}; +use crate::transcript::Transcript; +use crate::utils::mle::dense_vec_to_dense_mle; +use crate::Error; +use ark_crypto_primitives::sponge::Absorb; +use ark_ec::{CurveGroup, Group}; +use ark_ff::{One, PrimeField}; +use ark_poly::univariate::DensePolynomial; +use ark_poly::{DenseMultilinearExtension, DenseUVPolynomial, Polynomial}; +use ark_std::{log2, Zero}; + +/// Implements the Points vs Line as described in +/// [Mova](https://eprint.iacr.org/2024/1220.pdf) and Section 4.5.2 from Thaler’s book + +/// Claim from step 3 protocol 6 +pub struct PointvsLineEvaluationClaim { + pub mleE1_prime: C::ScalarField, + pub mleE2_prime: C::ScalarField, + pub rE_prime: Vec, +} +/// Proof from step 1 protocol 6 +#[derive(Clone, Debug)] +pub struct PointVsLineProof { + pub h1: DensePolynomial, + pub h2: DensePolynomial, +} + +#[derive(Clone, Debug, Default)] +pub struct PointVsLine> { + _phantom_C: std::marker::PhantomData, + _phantom_T: std::marker::PhantomData, +} + +/// Protocol 6 from Mova +impl> PointVsLine +where + ::ScalarField: Absorb, +{ + pub fn prove( + transcript: &mut impl Transcript, + ci1: &CommittedInstance, + ci2: &CommittedInstance, + w1: &Witness, + w2: &Witness, + ) -> Result<(PointVsLineProof, PointvsLineEvaluationClaim), Error> { + let n_vars: usize = log2(w1.E.len()) as usize; + + let mleE1 = dense_vec_to_dense_mle(n_vars, &w1.E); + let mleE2 = dense_vec_to_dense_mle(n_vars, &w2.E); + + // We have l(0) = r1, l(1) = r2 so we know that l(x) = r1 + x(r2-r1) thats why we need r2-r1 + let r1_sub_r2: Vec<::ScalarField> = ci1 + .rE + .iter() + .zip(&ci2.rE) + .map(|(&r1, r2)| r1 - r2) + .collect(); + + let h1 = compute_h(&mleE1, &ci1.rE, &r1_sub_r2)?; + let h2 = compute_h(&mleE2, &ci1.rE, &r1_sub_r2)?; + + transcript.absorb(&h1.coeffs()); + transcript.absorb(&h2.coeffs()); + + let beta_scalar = C::ScalarField::from_le_bytes_mod_order(b"beta"); + transcript.absorb(&beta_scalar); + let beta = transcript.get_challenge(); + + let mleE1_prime = h1.evaluate(&beta); + let mleE2_prime = h2.evaluate(&beta); + + let rE_prime = compute_l(&ci1.rE, &r1_sub_r2, beta)?; + + Ok(( + PointVsLineProof { h1, h2 }, + PointvsLineEvaluationClaim { + mleE1_prime, + mleE2_prime, + rE_prime, + }, + )) + } + + pub fn verify( + transcript: &mut impl Transcript, + ci1: &CommittedInstance, + ci2: &CommittedInstance, + proof: &PointVsLineProof, + mleE1_prime: &::ScalarField, + mleE2_prime: &::ScalarField, + ) -> Result< + Vec<::ScalarField>, // rE=rE1'=rE2'. + Error, + > { + if proof.h1.evaluate(&C::ScalarField::zero()) != ci1.mleE { + return Err(Error::NotEqual); + } + + if proof.h2.evaluate(&C::ScalarField::one()) != ci2.mleE { + return Err(Error::NotEqual); + } + + transcript.absorb(&proof.h1.coeffs()); + transcript.absorb(&proof.h2.coeffs()); + + let beta_scalar = C::ScalarField::from_le_bytes_mod_order(b"beta"); + transcript.absorb(&beta_scalar); + let beta = transcript.get_challenge(); + + if *mleE1_prime != proof.h1.evaluate(&beta) { + return Err(Error::NotEqual); + } + + if *mleE2_prime != proof.h2.evaluate(&beta) { + return Err(Error::NotEqual); + } + + let r1_sub_r2: Vec<::ScalarField> = ci1 + .rE + .iter() + .zip(&ci2.rE) + .map(|(&r1, r2)| r1 - r2) + .collect(); + let rE_prime = compute_l(&ci1.rE, &r1_sub_r2, beta)?; + + Ok(rE_prime) + } +} + +fn compute_h( + mle: &DenseMultilinearExtension, + r1: &[F], + r1_sub_r2: &[F], +) -> Result, Error> { + let n_vars: usize = mle.num_vars; + if r1.len() != r1_sub_r2.len() || r1.len() != n_vars { + return Err(Error::NotEqual); + } + + // Initialize the polynomial vector from the evaluations in the multilinear extension. + // Each evaluation is turned into a constant polynomial. + let mut poly: Vec> = mle + .evaluations + .iter() + .map(|&x| DensePolynomial::from_coefficients_slice(&[x])) + .collect(); + + for (i, (&r1_i, &r1_sub_r2_i)) in r1.iter().zip(r1_sub_r2.iter()).enumerate().take(n_vars) { + // Create a linear polynomial r(X) = r1_i + (r1_sub_r2_i) * X (basically l) + let r = DensePolynomial::from_coefficients_slice(&[r1_i, r1_sub_r2_i]); + let half_len = 1 << (n_vars - i - 1); + + for b in 0..half_len { + let left = &poly[b << 1]; + let right = &poly[(b << 1) + 1]; + poly[b] = left + &(&r * &(right - left)); + } + } + + // After the loop, we should be left with a single polynomial, so return it. + Ok(poly.swap_remove(0)) +} + +fn compute_l(r1: &[F], r1_sub_r2: &[F], x: F) -> Result, Error> { + if r1.len() != r1_sub_r2.len() { + return Err(Error::NotEqual); + } + + // we have l(x) = r1 + x(r2-r1) so return the result + Ok(r1 + .iter() + .zip(r1_sub_r2) + .map(|(&r1, &r1_sub_r0)| r1 + x * r1_sub_r0) + .collect()) +} + +#[cfg(test)] +mod tests { + use super::{compute_h, compute_l}; + use ark_pallas::Fq; + use ark_poly::{DenseMultilinearExtension, DenseUVPolynomial}; + + #[test] + fn test_compute_h() { + let mle = DenseMultilinearExtension::from_evaluations_slice(1, &[Fq::from(1), Fq::from(2)]); + let r0 = [Fq::from(5)]; + let r1 = [Fq::from(6)]; + let r1_sub_r0: Vec = r1.iter().zip(&r0).map(|(&x, y)| x - y).collect(); + + let result = compute_h(&mle, &r0, &r1_sub_r0).unwrap(); + assert_eq!( + result, + DenseUVPolynomial::from_coefficients_slice(&[Fq::from(6), Fq::from(1)]) + ); + + let mle = DenseMultilinearExtension::from_evaluations_slice(1, &[Fq::from(1), Fq::from(2)]); + let r0 = [Fq::from(4)]; + let r1 = [Fq::from(7)]; + let r1_sub_r0: Vec = r1.iter().zip(&r0).map(|(&x, y)| x - y).collect(); + + let result = compute_h(&mle, &r0, &r1_sub_r0).unwrap(); + assert_eq!( + result, + DenseUVPolynomial::from_coefficients_slice(&[Fq::from(5), Fq::from(3)]) + ); + + let mle = DenseMultilinearExtension::from_evaluations_slice( + 2, + &[Fq::from(1), Fq::from(2), Fq::from(3), Fq::from(4)], + ); + let r0 = [Fq::from(5), Fq::from(4)]; + let r1 = [Fq::from(2), Fq::from(7)]; + let r1_sub_r0: Vec = r1.iter().zip(&r0).map(|(&x, y)| x - y).collect(); + + let result = compute_h(&mle, &r0, &r1_sub_r0).unwrap(); + assert_eq!( + result, + DenseUVPolynomial::from_coefficients_slice(&[Fq::from(14), Fq::from(3)]) + ); + let mle = DenseMultilinearExtension::from_evaluations_slice( + 3, + &[ + Fq::from(1), + Fq::from(2), + Fq::from(3), + Fq::from(4), + Fq::from(5), + Fq::from(6), + Fq::from(7), + Fq::from(8), + ], + ); + let r0 = [Fq::from(1), Fq::from(2), Fq::from(3)]; + let r1 = [Fq::from(5), Fq::from(6), Fq::from(7)]; + let r1_sub_r0: Vec = r1.iter().zip(&r0).map(|(&x, y)| x - y).collect(); + + let result = compute_h(&mle, &r0, &r1_sub_r0).unwrap(); + assert_eq!( + result, + DenseUVPolynomial::from_coefficients_slice(&[Fq::from(18), Fq::from(28)]) + ); + } + + #[test] + fn test_compute_h_errors() { + let mle = DenseMultilinearExtension::from_evaluations_slice(1, &[Fq::from(1), Fq::from(2)]); + let r0 = [Fq::from(5)]; + let r1_sub_r0 = []; + let result = compute_h(&mle, &r0, &r1_sub_r0); + assert!(result.is_err()); + + let mle = DenseMultilinearExtension::from_evaluations_slice( + 2, + &[Fq::from(1), Fq::from(2), Fq::from(1), Fq::from(2)], + ); + let r0 = [Fq::from(4)]; + let r1 = [Fq::from(7)]; + let r1_sub_r0: Vec = r1.iter().zip(&r0).map(|(&x, y)| x - y).collect(); + + let result = compute_h(&mle, &r0, &r1_sub_r0); + assert!(result.is_err()) + } + + #[test] + fn test_compute_l() { + // Test with simple non-zero values + let r1 = vec![Fq::from(1), Fq::from(2), Fq::from(3)]; + let r1_sub_r2 = vec![Fq::from(4), Fq::from(5), Fq::from(6)]; + let x = Fq::from(2); + + let expected = vec![ + Fq::from(1) + Fq::from(2) * Fq::from(4), + Fq::from(2) + Fq::from(2) * Fq::from(5), + Fq::from(3) + Fq::from(2) * Fq::from(6), + ]; + + let result = compute_l(&r1, &r1_sub_r2, x).unwrap(); + assert_eq!(result, expected); + } +} diff --git a/folding-schemes/src/folding/mova/traits.rs b/folding-schemes/src/folding/mova/traits.rs new file mode 100644 index 00000000..939be1e7 --- /dev/null +++ b/folding-schemes/src/folding/mova/traits.rs @@ -0,0 +1,51 @@ +use crate::arith::r1cs::R1CS; +use crate::folding::mova::{CommittedInstance, Witness}; +use crate::Error; +use ark_crypto_primitives::sponge::Absorb; +use ark_ec::{CurveGroup, Group}; + +///MovaR1CS extends R1CS methods with Mova specific methods +pub trait MovaR1CS { + /// checks the R1CS relation (un-relaxed) for the given Witness and CommittedInstance. + fn check_instance_relation( + &self, + W: &Witness, + U: &CommittedInstance, + ) -> Result<(), Error>; + + /// checks the Relaxed R1CS relation (corresponding to the current R1CS) for the given Witness + /// and CommittedInstance. + fn check_relaxed_instance_relation( + &self, + W: &Witness, + U: &CommittedInstance, + ) -> Result<(), Error>; +} + +impl MovaR1CS for R1CS +where + ::ScalarField: Absorb, + ::BaseField: ark_ff::PrimeField, +{ + fn check_instance_relation( + &self, + _W: &Witness, + _U: &CommittedInstance, + ) -> Result<(), Error> { + // This is never called + unimplemented!() + } + + fn check_relaxed_instance_relation( + &self, + W: &Witness, + U: &CommittedInstance, + ) -> Result<(), Error> { + let mut rel_r1cs = self.clone().relax(); + rel_r1cs.u = U.u; + rel_r1cs.E = W.E.clone(); + + let Z: Vec = [vec![U.u], U.x.to_vec(), W.W.to_vec()].concat(); + rel_r1cs.check_relation(&Z) + } +} From 4940b48c749cc2664455702232de147451668b03 Mon Sep 17 00:00:00 2001 From: Nick Dimitriou Date: Thu, 19 Sep 2024 17:53:31 +0100 Subject: [PATCH 2/8] Fix CLI --- folding-schemes/src/folding/mova/mod.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/folding-schemes/src/folding/mova/mod.rs b/folding-schemes/src/folding/mova/mod.rs index 11b6836f..596b8bb8 100644 --- a/folding-schemes/src/folding/mova/mod.rs +++ b/folding-schemes/src/folding/mova/mod.rs @@ -9,7 +9,8 @@ use ark_ff::PrimeField; use ark_poly::MultilinearExtension; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use ark_std::{log2, One, UniformRand, Zero}; -use rand::RngCore; +use ark_std::rand::RngCore; + /// Implements the scheme described in [Mova](https://eprint.iacr.org/2024/1220.pdf) mod nifs; @@ -58,7 +59,7 @@ where } pub fn dummy(w_len: usize, e_len: usize) -> Self { - let rW = C::ScalarField::zero(); + let rW = C::ScalarField::zero(); let w = vec![C::ScalarField::zero(); w_len]; Self { From a38eba049a1a110c336499130a200647c4066700 Mon Sep 17 00:00:00 2001 From: Nick Dimitriou Date: Fri, 20 Sep 2024 10:13:28 +0100 Subject: [PATCH 3/8] Updated from main --- folding-schemes/src/folding/mova/mod.rs | 21 ++++-- folding-schemes/src/folding/mova/nifs.rs | 34 +++++----- folding-schemes/src/folding/mova/traits.rs | 78 +++++++++++----------- 3 files changed, 69 insertions(+), 64 deletions(-) diff --git a/folding-schemes/src/folding/mova/mod.rs b/folding-schemes/src/folding/mova/mod.rs index 596b8bb8..42b4ddd2 100644 --- a/folding-schemes/src/folding/mova/mod.rs +++ b/folding-schemes/src/folding/mova/mod.rs @@ -4,13 +4,13 @@ use crate::utils::mle::dense_vec_to_dense_mle; use crate::utils::vec::is_zero_vec; use crate::Error; use ark_crypto_primitives::sponge::Absorb; -use ark_ec::{CurveGroup, Group}; +use ark_ec::CurveGroup; use ark_ff::PrimeField; use ark_poly::MultilinearExtension; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; -use ark_std::{log2, One, UniformRand, Zero}; use ark_std::rand::RngCore; +use ark_std::{log2, One, UniformRand, Zero}; /// Implements the scheme described in [Mova](https://eprint.iacr.org/2024/1220.pdf) mod nifs; @@ -40,10 +40,7 @@ pub struct InstanceWitness { pub w: Witness, } -impl Witness -where - ::ScalarField: Absorb, -{ +impl Witness { pub fn new(w: Vec, e_len: usize, mut rng: impl RngCore) -> Self { let rW = if H { C::ScalarField::rand(&mut rng) @@ -94,6 +91,18 @@ where } } +impl CommittedInstance { + pub fn dummy(io_len: usize) -> Self { + Self { + rE: vec![C::ScalarField::zero(); io_len], + mleE: C::ScalarField::zero(), + u: C::ScalarField::zero(), + cmW: C::zero(), + x: vec![C::ScalarField::zero(); io_len], + } + } +} + impl Absorb for CommittedInstance where C::ScalarField: Absorb, diff --git a/folding-schemes/src/folding/mova/nifs.rs b/folding-schemes/src/folding/mova/nifs.rs index b2db3fd0..85cb4e48 100644 --- a/folding-schemes/src/folding/mova/nifs.rs +++ b/folding-schemes/src/folding/mova/nifs.rs @@ -220,6 +220,12 @@ where #[cfg(test)] pub mod tests { + use crate::arith::r1cs::{ + tests::{get_test_r1cs, get_test_z}, + RelaxedR1CS, + }; + use crate::commitment::pedersen::{Params as PedersenParams, Pedersen}; + use crate::transcript::poseidon::poseidon_canonical_config; use ark_crypto_primitives::sponge::{ poseidon::{PoseidonConfig, PoseidonSponge}, CryptographicSponge, @@ -228,11 +234,6 @@ pub mod tests { use ark_pallas::{Fr, Projective}; use ark_std::{test_rng, UniformRand, Zero}; - use crate::arith::r1cs::tests::{get_test_r1cs, get_test_z}; - use crate::commitment::pedersen::{Params as PedersenParams, Pedersen}; - use crate::folding::mova::traits::MovaR1CS; - use crate::transcript::poseidon::poseidon_canonical_config; - use super::*; #[allow(clippy::type_complexity)] @@ -332,8 +333,8 @@ pub mod tests { let W_i = w_dummy.clone(); let U_i = u_dummy.clone(); - r1cs.check_relaxed_instance_relation(&w_i, &u_i).unwrap(); - r1cs.check_relaxed_instance_relation(&W_i, &U_i).unwrap(); + r1cs.check_relaxed_relation(&w_i, &u_i).unwrap(); + r1cs.check_relaxed_relation(&W_i, &U_i).unwrap(); let poseidon_config = poseidon_canonical_config::(); let mut transcript_p: PoseidonSponge = PoseidonSponge::::new(&poseidon_config); @@ -349,7 +350,7 @@ pub mod tests { .unwrap(); let (_proof, instance_witness) = result; - r1cs.check_relaxed_instance_relation(&instance_witness.w, &instance_witness.ci) + r1cs.check_relaxed_relation(&instance_witness.w, &instance_witness.ci) .unwrap(); } @@ -371,9 +372,9 @@ pub mod tests { assert_eq!(ci3, instance.ci); // check that relations hold for the 2 inputted instances and the folded one - r1cs.check_relaxed_instance_relation(&w1, &ci1).unwrap(); - r1cs.check_relaxed_instance_relation(&w2, &ci2).unwrap(); - r1cs.check_relaxed_instance_relation(&instance.w, &instance.ci) + r1cs.check_relaxed_relation(&w1, &ci1).unwrap(); + r1cs.check_relaxed_relation(&w2, &ci2).unwrap(); + r1cs.check_relaxed_relation(&instance.w, &instance.ci) .unwrap(); // check that folded commitments from folded instance (ci) are equal to folding the @@ -402,7 +403,7 @@ pub mod tests { .commit::>(&pedersen_params, x, rE) .unwrap(); - r1cs.check_relaxed_instance_relation(&running_instance_w, &running_committed_instance) + r1cs.check_relaxed_relation(&running_instance_w, &running_committed_instance) .unwrap(); let num_iters = 10; @@ -416,11 +417,8 @@ pub mod tests { let incoming_committed_instance = incoming_instance_w .commit::>(&pedersen_params, x, rE) .unwrap(); - r1cs.check_relaxed_instance_relation( - &incoming_instance_w, - &incoming_committed_instance, - ) - .unwrap(); + r1cs.check_relaxed_relation(&incoming_instance_w, &incoming_committed_instance) + .unwrap(); // NIFS.P let poseidon_config = poseidon_canonical_config::(); @@ -448,7 +446,7 @@ pub mod tests { ) .unwrap(); - r1cs.check_relaxed_instance_relation(&instance_witness.w, &instance_witness.ci) + r1cs.check_relaxed_relation(&instance_witness.w, &instance_witness.ci) .unwrap(); // set running_instance for next loop iteration diff --git a/folding-schemes/src/folding/mova/traits.rs b/folding-schemes/src/folding/mova/traits.rs index 939be1e7..bcd6f2d2 100644 --- a/folding-schemes/src/folding/mova/traits.rs +++ b/folding-schemes/src/folding/mova/traits.rs @@ -1,51 +1,49 @@ -use crate::arith::r1cs::R1CS; +use crate::arith::r1cs::{RelaxedR1CS, R1CS}; use crate::folding::mova::{CommittedInstance, Witness}; use crate::Error; -use ark_crypto_primitives::sponge::Absorb; -use ark_ec::{CurveGroup, Group}; +use ark_ec::CurveGroup; +use ark_std::{rand::RngCore, One, Zero}; -///MovaR1CS extends R1CS methods with Mova specific methods -pub trait MovaR1CS { - /// checks the R1CS relation (un-relaxed) for the given Witness and CommittedInstance. - fn check_instance_relation( - &self, - W: &Witness, - U: &CommittedInstance, - ) -> Result<(), Error>; +impl RelaxedR1CS, CommittedInstance> for R1CS { + fn dummy_running_instance(&self) -> (Witness, CommittedInstance) { + let w_len = self.A.n_cols - 1 - self.l; + let w_dummy = Witness::::dummy(w_len, self.A.n_rows); + let u_dummy = CommittedInstance::::dummy(self.l); + (w_dummy, u_dummy) + } - /// checks the Relaxed R1CS relation (corresponding to the current R1CS) for the given Witness - /// and CommittedInstance. - fn check_relaxed_instance_relation( - &self, - W: &Witness, - U: &CommittedInstance, - ) -> Result<(), Error>; -} + fn dummy_incoming_instance(&self) -> (Witness, CommittedInstance) { + self.dummy_running_instance() + } -impl MovaR1CS for R1CS -where - ::ScalarField: Absorb, - ::BaseField: ark_ff::PrimeField, -{ - fn check_instance_relation( - &self, - _W: &Witness, - _U: &CommittedInstance, - ) -> Result<(), Error> { - // This is never called - unimplemented!() + fn is_relaxed(_w: &Witness, u: &CommittedInstance) -> bool { + u.mleE != C::ScalarField::zero() || u.u != C::ScalarField::one() } - fn check_relaxed_instance_relation( - &self, - W: &Witness, - U: &CommittedInstance, + fn extract_z(w: &Witness, u: &CommittedInstance) -> Vec { + [&[u.u][..], &u.x, &w.W].concat() + } + + fn check_error_terms( + w: &Witness, + _u: &CommittedInstance, + e: Vec, ) -> Result<(), Error> { - let mut rel_r1cs = self.clone().relax(); - rel_r1cs.u = U.u; - rel_r1cs.E = W.E.clone(); + if w.E == e { + Ok(()) + } else { + Err(Error::NotSatisfied) + } + } - let Z: Vec = [vec![U.u], U.x.to_vec(), W.W.to_vec()].concat(); - rel_r1cs.check_relation(&Z) + fn sample( + &self, + _params: &CS::ProverParams, + _rng: impl RngCore, + ) -> Result<(Witness, CommittedInstance), Error> + where + CS: crate::commitment::CommitmentScheme, + { + unimplemented!() } } From 9e60b9d2c79da9561a53e5542a91f2b7d7263a32 Mon Sep 17 00:00:00 2001 From: Nick Dimitriou <81875532+NiDimi@users.noreply.github.com> Date: Sun, 6 Oct 2024 16:28:28 +0100 Subject: [PATCH 4/8] Solution to stop the CLI from complaining about deadcode PR comment Co-authored-by: arnaucube --- folding-schemes/src/folding/mova/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/folding-schemes/src/folding/mova/mod.rs b/folding-schemes/src/folding/mova/mod.rs index 42b4ddd2..1f0d48e4 100644 --- a/folding-schemes/src/folding/mova/mod.rs +++ b/folding-schemes/src/folding/mova/mod.rs @@ -1,3 +1,4 @@ +#![allow(unused)] use crate::commitment::CommitmentScheme; use crate::transcript::AbsorbNonNative; use crate::utils::mle::dense_vec_to_dense_mle; From f61d6605e73816efc3b9535d10b0c9d24d049ca8 Mon Sep 17 00:00:00 2001 From: Nick Dimitriou Date: Thu, 17 Oct 2024 12:58:59 +0100 Subject: [PATCH 5/8] Requested changes and update from main --- folding-schemes/src/folding/mova/mod.rs | 34 +++++---- folding-schemes/src/folding/mova/nifs.rs | 69 +++++++------------ .../src/folding/mova/pointvsline.rs | 1 + folding-schemes/src/folding/mova/traits.rs | 53 +++++--------- 4 files changed, 63 insertions(+), 94 deletions(-) diff --git a/folding-schemes/src/folding/mova/mod.rs b/folding-schemes/src/folding/mova/mod.rs index 1f0d48e4..93bc257f 100644 --- a/folding-schemes/src/folding/mova/mod.rs +++ b/folding-schemes/src/folding/mova/mod.rs @@ -11,6 +11,9 @@ use ark_poly::MultilinearExtension; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use ark_std::rand::RngCore; +use crate::arith::r1cs::R1CS; +use crate::folding::circuits::CF1; +use crate::folding::traits::Dummy; use ark_std::{log2, One, UniformRand, Zero}; /// Implements the scheme described in [Mova](https://eprint.iacr.org/2024/1220.pdf) @@ -22,12 +25,15 @@ mod traits; pub struct CommittedInstance { // Random evaluation point for the E pub rE: Vec, - // MLE of E + // Evaluation of the MLE of E at r_E pub mleE: C::ScalarField, pub u: C::ScalarField, pub cmW: C, pub x: Vec, } + +/// Witness for the R1CS containing the W vector, the r_w used as randomness for the commitment and the Error term E. +/// The wi the prover receives in most protocols in the paper. #[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] pub struct Witness { pub E: Vec, @@ -35,6 +41,7 @@ pub struct Witness { pub rW: C::ScalarField, } +/// A helper struct to group together the result of the folded witness and the folded committed instance #[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] pub struct InstanceWitness { pub ci: CommittedInstance, @@ -56,17 +63,6 @@ impl Witness { } } - pub fn dummy(w_len: usize, e_len: usize) -> Self { - let rW = C::ScalarField::zero(); - let w = vec![C::ScalarField::zero(); w_len]; - - Self { - E: vec![C::ScalarField::zero(); e_len], - W: w, - rW, - } - } - pub fn commit>( &self, params: &CS::ProverParams, @@ -92,8 +88,18 @@ impl Witness { } } -impl CommittedInstance { - pub fn dummy(io_len: usize) -> Self { +impl Dummy<&R1CS>> for Witness { + fn dummy(r1cs: &R1CS>) -> Self { + Self { + E: vec![C::ScalarField::zero(); r1cs.A.n_rows], + W: vec![C::ScalarField::zero(); r1cs.A.n_cols - 1 - r1cs.l], + rW: C::ScalarField::zero(), + } + } +} + +impl Dummy for CommittedInstance { + fn dummy(io_len: usize) -> Self { Self { rE: vec![C::ScalarField::zero(); io_len], mleE: C::ScalarField::zero(), diff --git a/folding-schemes/src/folding/mova/nifs.rs b/folding-schemes/src/folding/mova/nifs.rs index 85cb4e48..549854ae 100644 --- a/folding-schemes/src/folding/mova/nifs.rs +++ b/folding-schemes/src/folding/mova/nifs.rs @@ -4,7 +4,7 @@ use crate::commitment::CommitmentScheme; use crate::folding::mova::pointvsline::{ PointVsLine, PointVsLineProof, PointvsLineEvaluationClaim, }; -use crate::folding::nova::nifs::NIFS as Nova; +use crate::folding::nova::nifs::NIFS as NovaNIFS; use crate::transcript::Transcript; use crate::utils::mle::dense_vec_to_dense_mle; use crate::utils::vec::{vec_add, vec_scalar_mul}; @@ -41,18 +41,8 @@ impl, T: Transcript, c NIFS where ::ScalarField: Absorb, + ::BaseField: PrimeField, { - // Just a wrapper for Nova compute_T (compute cross-terms T) since the process is the same - pub fn compute_T( - r1cs: &R1CS, - u1: C::ScalarField, - u2: C::ScalarField, - z1: &[C::ScalarField], - z2: &[C::ScalarField], - ) -> Result, Error> { - Nova::::compute_T(r1cs, u1, u2, z1, z2) - } - // Protocol 7 - point 3 (16) pub fn fold_witness( a: C::ScalarField, @@ -60,10 +50,10 @@ where w2: &Witness, T: &[C::ScalarField], ) -> Result, Error> { - let a2 = a * a; + let a_squared = a * a; let E: Vec = vec_add( &vec_add(&w1.E, &vec_scalar_mul(T, &a))?, - &vec_scalar_mul(&w2.E, &a2), + &vec_scalar_mul(&w2.E, &a_squared), )?; let W: Vec = w1.W.iter() @@ -80,13 +70,13 @@ where a: C::ScalarField, ci1: &CommittedInstance, ci2: &CommittedInstance, - rE_prime: &[C::ScalarField], + rE_prime: Vec, mleE1_prime: &C::ScalarField, mleE2_prime: &C::ScalarField, mleT: &C::ScalarField, ) -> Result, Error> { - let a2 = a * a; - let mleE = *mleE1_prime + a * mleT + a2 * mleE2_prime; + let a_squared = a * a; + let mleE = *mleE1_prime + a * mleT + a_squared * mleE2_prime; let u = ci1.u + a * ci2.u; let cmW = ci1.cmW + ci2.cmW.mul(a); let x = ci1 @@ -132,7 +122,6 @@ where ) = PointVsLine::::prove(transcript, ci1, ci2, w1, w2)?; // Protocol 7 - transcript.absorb(&mleE1_prime); transcript.absorb(&mleE2_prime); @@ -140,7 +129,7 @@ where let z1: Vec = [vec![ci1.u], ci1.x.to_vec(), w1.W.to_vec()].concat(); let z2: Vec = [vec![ci2.u], ci2.x.to_vec(), w2.W.to_vec()].concat(); - let T = Self::compute_T(r1cs, ci1.u, ci2.u, &z1, &z2)?; + let T = NovaNIFS::::compute_T(r1cs, ci1.u, ci2.u, &z1, &z2)?; let n_vars: usize = log2(w1.E.len()) as usize; if log2(T.len()) as usize != n_vars { @@ -152,8 +141,6 @@ where transcript.absorb(&mleT_evaluated); - let alpha_scalar = C::ScalarField::from_le_bytes_mod_order(b"alpha"); - transcript.absorb(&alpha_scalar); let alpha: C::ScalarField = transcript.get_challenge(); Ok(( @@ -168,7 +155,7 @@ where alpha, ci1, ci2, - &rE_prime, + rE_prime, &mleE1_prime, &mleE2_prime, &mleT_evaluated, @@ -178,9 +165,9 @@ where )) } - /// [Mova](https://eprint.iacr.org/2024/1220.pdf)'s section 4. It verifies the results from the proof - /// Both the folding and the pt-vs-line proof - /// returns the folded committed instance + /// [Mova](https://eprint.iacr.org/2024/1220.pdf)'s section 4. + /// It verifies the results from both the folding and the pt-vs-line proofs. + /// Returns the folded committed instance. pub fn verify( transcript: &mut impl Transcript, ci1: &CommittedInstance, @@ -202,15 +189,13 @@ where transcript.absorb(&proof.mleE2_prime); transcript.absorb(&proof.mleT); - let alpha_scalar = C::ScalarField::from_le_bytes_mod_order(b"alpha"); - transcript.absorb(&alpha_scalar); let alpha: C::ScalarField = transcript.get_challenge(); Self::fold_committed_instance( alpha, ci1, ci2, - &rE_prime, + rE_prime, &proof.mleE1_prime, &proof.mleE2_prime, &proof.mleT, @@ -220,11 +205,10 @@ where #[cfg(test)] pub mod tests { - use crate::arith::r1cs::{ - tests::{get_test_r1cs, get_test_z}, - RelaxedR1CS, - }; + use crate::arith::r1cs::tests::{get_test_r1cs, get_test_z}; + use crate::arith::Arith; use crate::commitment::pedersen::{Params as PedersenParams, Pedersen}; + use crate::folding::traits::Dummy; use crate::transcript::poseidon::poseidon_canonical_config; use ark_crypto_primitives::sponge::{ poseidon::{PoseidonConfig, PoseidonSponge}, @@ -318,7 +302,7 @@ pub mod tests { let (pedersen_params, _) = Pedersen::::setup(&mut rng, r1cs.A.n_cols).unwrap(); // dummy instance, witness and public inputs zeroes - let w_dummy = Witness::::dummy(w1.len(), r1cs.A.n_rows); + let w_dummy = Witness::::dummy(&r1cs); let mut u_dummy = w_dummy .commit::>( &pedersen_params, @@ -333,8 +317,8 @@ pub mod tests { let W_i = w_dummy.clone(); let U_i = u_dummy.clone(); - r1cs.check_relaxed_relation(&w_i, &u_i).unwrap(); - r1cs.check_relaxed_relation(&W_i, &U_i).unwrap(); + r1cs.check_relation(&w_i, &u_i).unwrap(); + r1cs.check_relation(&W_i, &U_i).unwrap(); let poseidon_config = poseidon_canonical_config::(); let mut transcript_p: PoseidonSponge = PoseidonSponge::::new(&poseidon_config); @@ -350,7 +334,7 @@ pub mod tests { .unwrap(); let (_proof, instance_witness) = result; - r1cs.check_relaxed_relation(&instance_witness.w, &instance_witness.ci) + r1cs.check_relation(&instance_witness.w, &instance_witness.ci) .unwrap(); } @@ -372,10 +356,9 @@ pub mod tests { assert_eq!(ci3, instance.ci); // check that relations hold for the 2 inputted instances and the folded one - r1cs.check_relaxed_relation(&w1, &ci1).unwrap(); - r1cs.check_relaxed_relation(&w2, &ci2).unwrap(); - r1cs.check_relaxed_relation(&instance.w, &instance.ci) - .unwrap(); + r1cs.check_relation(&w1, &ci1).unwrap(); + r1cs.check_relation(&w2, &ci2).unwrap(); + r1cs.check_relation(&instance.w, &instance.ci).unwrap(); // check that folded commitments from folded instance (ci) are equal to folding the // use folded rE, rW to commit w3 @@ -403,7 +386,7 @@ pub mod tests { .commit::>(&pedersen_params, x, rE) .unwrap(); - r1cs.check_relaxed_relation(&running_instance_w, &running_committed_instance) + r1cs.check_relation(&running_instance_w, &running_committed_instance) .unwrap(); let num_iters = 10; @@ -417,7 +400,7 @@ pub mod tests { let incoming_committed_instance = incoming_instance_w .commit::>(&pedersen_params, x, rE) .unwrap(); - r1cs.check_relaxed_relation(&incoming_instance_w, &incoming_committed_instance) + r1cs.check_relation(&incoming_instance_w, &incoming_committed_instance) .unwrap(); // NIFS.P @@ -446,7 +429,7 @@ pub mod tests { ) .unwrap(); - r1cs.check_relaxed_relation(&instance_witness.w, &instance_witness.ci) + r1cs.check_relation(&instance_witness.w, &instance_witness.ci) .unwrap(); // set running_instance for next loop iteration diff --git a/folding-schemes/src/folding/mova/pointvsline.rs b/folding-schemes/src/folding/mova/pointvsline.rs index 72e7dcbf..0a66d458 100644 --- a/folding-schemes/src/folding/mova/pointvsline.rs +++ b/folding-schemes/src/folding/mova/pointvsline.rs @@ -18,6 +18,7 @@ pub struct PointvsLineEvaluationClaim { pub mleE2_prime: C::ScalarField, pub rE_prime: Vec, } + /// Proof from step 1 protocol 6 #[derive(Clone, Debug)] pub struct PointVsLineProof { diff --git a/folding-schemes/src/folding/mova/traits.rs b/folding-schemes/src/folding/mova/traits.rs index bcd6f2d2..ec12d0bc 100644 --- a/folding-schemes/src/folding/mova/traits.rs +++ b/folding-schemes/src/folding/mova/traits.rs @@ -1,49 +1,28 @@ -use crate::arith::r1cs::{RelaxedR1CS, R1CS}; +use crate::arith::ccs::CCS; +use crate::arith::{r1cs::R1CS, Arith}; +use crate::folding::circuits::CF1; use crate::folding::mova::{CommittedInstance, Witness}; +use crate::utils::mle::dense_vec_to_dense_mle; +use crate::utils::vec::mat_vec_mul; use crate::Error; use ark_ec::CurveGroup; -use ark_std::{rand::RngCore, One, Zero}; -impl RelaxedR1CS, CommittedInstance> for R1CS { - fn dummy_running_instance(&self) -> (Witness, CommittedInstance) { - let w_len = self.A.n_cols - 1 - self.l; - let w_dummy = Witness::::dummy(w_len, self.A.n_rows); - let u_dummy = CommittedInstance::::dummy(self.l); - (w_dummy, u_dummy) - } - - fn dummy_incoming_instance(&self) -> (Witness, CommittedInstance) { - self.dummy_running_instance() - } - - fn is_relaxed(_w: &Witness, u: &CommittedInstance) -> bool { - u.mleE != C::ScalarField::zero() || u.u != C::ScalarField::one() - } +impl Arith, CommittedInstance> for R1CS> { + type Evaluation = Vec>; - fn extract_z(w: &Witness, u: &CommittedInstance) -> Vec { - [&[u.u][..], &u.x, &w.W].concat() + fn eval_relation( + &self, + w: &Witness, + u: &CommittedInstance, + ) -> Result { + self.eval_at_z(&[&[u.u][..], &u.x, &w.W].concat()) } - fn check_error_terms( + fn check_evaluation( w: &Witness, _u: &CommittedInstance, - e: Vec, + e: Self::Evaluation, ) -> Result<(), Error> { - if w.E == e { - Ok(()) - } else { - Err(Error::NotSatisfied) - } - } - - fn sample( - &self, - _params: &CS::ProverParams, - _rng: impl RngCore, - ) -> Result<(Witness, CommittedInstance), Error> - where - CS: crate::commitment::CommitmentScheme, - { - unimplemented!() + (w.E == e).then_some(()).ok_or(Error::NotSatisfied) } } From 54e6b60a60115b40d31ca0a9fff2fbccbdb47ea9 Mon Sep 17 00:00:00 2001 From: arnaucube Date: Sun, 20 Oct 2024 23:24:23 +0200 Subject: [PATCH 6/8] Refactor NIFSTrait & port Mova impl to it * refactor NIFSTrait interface to fit Nova variants (Nova,Mova,Ova) Refactor NIFSTrait interface to fit Nova variants (Nova,Mova,Ova). The relevant change is instead of passing the challenge as input, now it passes the transcript and computes the challenges internally (Nova & Ova still compute a single challenge, but Mova computes multiple while absorbing at different steps). * port Mova impl to the NIFSTrait * remove unnecessary wrappers in the nova/zk.rs * remove Nova NIFS methods that are no longer needed after the refactor * put together the different NIFS implementations (Nova, Mova, Ova) so that they can interchanged at usage. The idea is that Nova and its variants (Ova & Mova) share most of the logic for the circuits & IVC & Deciders, so with the abstracted NIFS interface we will be able to reuse most of the already existing Nova code for having the Mova & Ova circuits, IVC, and Decider. --- .../src/folding/circuits/cyclefold.rs | 114 ++++- folding-schemes/src/folding/mod.rs | 1 - folding-schemes/src/folding/mova/mod.rs | 134 ------ folding-schemes/src/folding/mova/nifs.rs | 440 ------------------ folding-schemes/src/folding/mova/traits.rs | 28 -- folding-schemes/src/folding/nova/circuits.rs | 17 +- folding-schemes/src/folding/nova/decider.rs | 25 +- .../src/folding/nova/decider_circuits.rs | 20 +- .../src/folding/nova/decider_eth.rs | 30 +- .../src/folding/nova/decider_eth_circuit.rs | 20 +- folding-schemes/src/folding/nova/mod.rs | 82 ++-- folding-schemes/src/folding/nova/nifs/mod.rs | 152 ++++++ folding-schemes/src/folding/nova/nifs/mova.rs | 411 ++++++++++++++++ .../folding/nova/{nifs.rs => nifs/nova.rs} | 252 ++++------ .../src/folding/nova/{ => nifs}/ova.rs | 95 ++-- .../{mova => nova/nifs}/pointvsline.rs | 17 +- folding-schemes/src/folding/nova/traits.rs | 73 --- folding-schemes/src/folding/nova/zk.rs | 124 ++--- 18 files changed, 939 insertions(+), 1096 deletions(-) delete mode 100644 folding-schemes/src/folding/mova/mod.rs delete mode 100644 folding-schemes/src/folding/mova/nifs.rs delete mode 100644 folding-schemes/src/folding/mova/traits.rs create mode 100644 folding-schemes/src/folding/nova/nifs/mod.rs create mode 100644 folding-schemes/src/folding/nova/nifs/mova.rs rename folding-schemes/src/folding/nova/{nifs.rs => nifs/nova.rs} (50%) rename folding-schemes/src/folding/nova/{ => nifs}/ova.rs (79%) rename folding-schemes/src/folding/{mova => nova/nifs}/pointvsline.rs (97%) diff --git a/folding-schemes/src/folding/circuits/cyclefold.rs b/folding-schemes/src/folding/circuits/cyclefold.rs index 91a87877..a6632877 100644 --- a/folding-schemes/src/folding/circuits/cyclefold.rs +++ b/folding-schemes/src/folding/circuits/cyclefold.rs @@ -24,9 +24,10 @@ 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, traits::NIFSTrait}; +use crate::folding::nova::nifs::{nova::NIFS, NIFSTrait}; use crate::transcript::{AbsorbNonNative, AbsorbNonNativeGadget, Transcript, TranscriptVar}; use crate::Error; +use ark_crypto_primitives::sponge::poseidon::PoseidonSponge; /// Re-export the Nova committed instance as `CycleFoldCommittedInstance` and /// witness as `CycleFoldWitness`, for clarity and consistency @@ -493,6 +494,72 @@ where } } +/// CycleFoldNIFS is a wrapper on top of Nova's NIFS, which just replaces the `prove` and `verify` +/// methods to use a different ChallengeGadget, but internally reuses the other Nova's NIFS +/// methods. +/// It is a custom implementation that does not follow the NIFSTrait because it needs to work over +/// different fields than the main NIFS impls (Nova, Mova, Ova). Could be abstracted, but it's a +/// tradeoff between overcomplexity at the NIFSTrait and the (not much) need of generalization at +/// the CycleFoldNIFS. +pub struct CycleFoldNIFS< + C1: CurveGroup, + C2: CurveGroup, + GC2: CurveVar> + ToConstraintFieldGadget>, + CS2: CommitmentScheme, + const H: bool = false, +> where + ::BaseField: PrimeField, + ::BaseField: PrimeField, + for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>, +{ + _c1: PhantomData, + _c2: PhantomData, + _gc2: PhantomData, + _cs: PhantomData, +} +impl, const H: bool> + CycleFoldNIFS +where + ::BaseField: PrimeField, + ::BaseField: PrimeField, + ::ScalarField: Absorb, + ::ScalarField: Absorb, + C1: CurveGroup, + GC2: CurveVar> + ToConstraintFieldGadget>, + for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>, +{ + fn prove( + cf_r_Fq: C2::ScalarField, // C2::Fr==C1::Fq + cf_W_i: &CycleFoldWitness, + cf_U_i: &CycleFoldCommittedInstance, + cf_w_i: &CycleFoldWitness, + cf_u_i: &CycleFoldCommittedInstance, + aux_p: &[C2::ScalarField], // = cf_T + aux_v: C2, // = cf_cmT + ) -> Result<(CycleFoldWitness, CycleFoldCommittedInstance), Error> { + let w = NIFS::, H>::fold_witness( + cf_r_Fq, + cf_W_i, + cf_w_i, + &aux_p.to_vec(), + )?; + let ci = Self::verify(cf_r_Fq, cf_U_i, cf_u_i, &aux_v)?; + Ok((w, ci)) + } + fn verify( + r: C2::ScalarField, + U_i: &CycleFoldCommittedInstance, + u_i: &CycleFoldCommittedInstance, + cmT: &C2, // VerifierAux + ) -> Result, Error> { + Ok( + NIFS::, H>::fold_committed_instances( + r, U_i, u_i, cmT, + ), + ) + } +} + /// Folds the given cyclefold circuit and its instances. This method is abstracted from any folding /// scheme struct because it is used both by Nova & HyperNova's CycleFold. #[allow(clippy::type_complexity)] @@ -551,14 +618,15 @@ where 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_T, cf_cmT) = + NIFS::, H>::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( transcript, @@ -570,8 +638,11 @@ 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::::prove(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) = CycleFoldNIFS::::prove( + cf_r_Fq, &cf_W_i, &cf_U_i, &cf_w_i, &cf_u_i, &cf_T, cf_cmT, + )?; + let cf_r_Fq = C1::BaseField::from_bigint(BigInteger::from_bits_le(&cf_r_bits)) + .expect("cf_r_bits out of bounds"); Ok((cf_w_i, cf_u_i, cf_W_i1, cf_U_i1, cf_cmT, cf_r_Fq)) } @@ -671,6 +742,10 @@ pub mod tests { fn test_nifs_full_gadget() { let mut rng = ark_std::test_rng(); + let poseidon_config = poseidon_canonical_config::(); + let mut transcript_v = PoseidonSponge::::new(&poseidon_config); + let pp_hash = Fr::rand(&mut rng); + // prepare the committed instances to test in-circuit let ci: Vec> = (0..2) .into_iter() @@ -685,11 +760,16 @@ pub mod tests { // 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 cmT = Projective::rand(&mut rng); // random only for testing + let (ci3, r_bits) = NIFS::, PoseidonSponge>::verify( + &mut transcript_v, + pp_hash, + &ci1, + &ci2, + &cmT, + ) + .unwrap(); let cs = ConstraintSystem::::new_ref(); let r_bitsVar = Vec::>::new_witness(cs.clone(), || Ok(r_bits)).unwrap(); @@ -737,7 +817,7 @@ pub mod tests { .take(TestCycleFoldConfig::::IO_LEN) .collect(), }; - let cmT = Projective::rand(&mut rng); + let cmT = Projective::rand(&mut rng); // random only for testing // compute the challenge natively let pp_hash = Fq::from(42u32); // only for test diff --git a/folding-schemes/src/folding/mod.rs b/folding-schemes/src/folding/mod.rs index a56f12a6..32342886 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 mova; pub mod nova; pub mod protogalaxy; pub mod traits; diff --git a/folding-schemes/src/folding/mova/mod.rs b/folding-schemes/src/folding/mova/mod.rs deleted file mode 100644 index 93bc257f..00000000 --- a/folding-schemes/src/folding/mova/mod.rs +++ /dev/null @@ -1,134 +0,0 @@ -#![allow(unused)] -use crate::commitment::CommitmentScheme; -use crate::transcript::AbsorbNonNative; -use crate::utils::mle::dense_vec_to_dense_mle; -use crate::utils::vec::is_zero_vec; -use crate::Error; -use ark_crypto_primitives::sponge::Absorb; -use ark_ec::CurveGroup; -use ark_ff::PrimeField; -use ark_poly::MultilinearExtension; -use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; -use ark_std::rand::RngCore; - -use crate::arith::r1cs::R1CS; -use crate::folding::circuits::CF1; -use crate::folding::traits::Dummy; -use ark_std::{log2, One, UniformRand, Zero}; - -/// Implements the scheme described in [Mova](https://eprint.iacr.org/2024/1220.pdf) -mod nifs; -mod pointvsline; -mod traits; - -#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] -pub struct CommittedInstance { - // Random evaluation point for the E - pub rE: Vec, - // Evaluation of the MLE of E at r_E - pub mleE: C::ScalarField, - pub u: C::ScalarField, - pub cmW: C, - pub x: Vec, -} - -/// Witness for the R1CS containing the W vector, the r_w used as randomness for the commitment and the Error term E. -/// The wi the prover receives in most protocols in the paper. -#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] -pub struct Witness { - pub E: Vec, - pub W: Vec, - pub rW: C::ScalarField, -} - -/// A helper struct to group together the result of the folded witness and the folded committed instance -#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] -pub struct InstanceWitness { - pub ci: CommittedInstance, - pub w: Witness, -} - -impl Witness { - pub fn new(w: Vec, e_len: usize, mut rng: impl RngCore) -> Self { - let rW = if H { - C::ScalarField::rand(&mut rng) - } else { - C::ScalarField::zero() - }; - - Self { - E: vec![C::ScalarField::zero(); e_len], - W: w, - rW, - } - } - - pub fn commit>( - &self, - params: &CS::ProverParams, - x: Vec, - rE: Vec, - ) -> Result, Error> { - let mut mleE = C::ScalarField::zero(); - if !is_zero_vec::(&self.E) { - let E = dense_vec_to_dense_mle(log2(self.E.len()) as usize, &self.E); - mleE = E.evaluate(&rE).ok_or(Error::NotExpectedLength( - rE.len(), - log2(self.E.len()) as usize, - ))?; - } - let cmW = CS::commit(params, &self.W, &self.rW)?; - Ok(CommittedInstance { - rE, - mleE, - u: C::ScalarField::one(), - cmW, - x, - }) - } -} - -impl Dummy<&R1CS>> for Witness { - fn dummy(r1cs: &R1CS>) -> Self { - Self { - E: vec![C::ScalarField::zero(); r1cs.A.n_rows], - W: vec![C::ScalarField::zero(); r1cs.A.n_cols - 1 - r1cs.l], - rW: C::ScalarField::zero(), - } - } -} - -impl Dummy for CommittedInstance { - fn dummy(io_len: usize) -> Self { - Self { - rE: vec![C::ScalarField::zero(); io_len], - mleE: C::ScalarField::zero(), - u: C::ScalarField::zero(), - cmW: C::zero(), - x: vec![C::ScalarField::zero(); io_len], - } - } -} - -impl Absorb for CommittedInstance -where - C::ScalarField: Absorb, -{ - fn to_sponge_bytes(&self, _dest: &mut Vec) { - // This is never called - unimplemented!() - } - - fn to_sponge_field_elements(&self, dest: &mut Vec) { - self.u.to_sponge_field_elements(dest); - self.x.to_sponge_field_elements(dest); - self.rE.to_sponge_field_elements(dest); - self.mleE.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.cmW - .to_native_sponge_field_elements_as_vec() - .to_sponge_field_elements(dest); - } -} diff --git a/folding-schemes/src/folding/mova/nifs.rs b/folding-schemes/src/folding/mova/nifs.rs deleted file mode 100644 index 549854ae..00000000 --- a/folding-schemes/src/folding/mova/nifs.rs +++ /dev/null @@ -1,440 +0,0 @@ -use super::{CommittedInstance, InstanceWitness, Witness}; -use crate::arith::r1cs::R1CS; -use crate::commitment::CommitmentScheme; -use crate::folding::mova::pointvsline::{ - PointVsLine, PointVsLineProof, PointvsLineEvaluationClaim, -}; -use crate::folding::nova::nifs::NIFS as NovaNIFS; -use crate::transcript::Transcript; -use crate::utils::mle::dense_vec_to_dense_mle; -use crate::utils::vec::{vec_add, vec_scalar_mul}; -use crate::Error; -use ark_crypto_primitives::sponge::Absorb; -use ark_ec::{CurveGroup, Group}; -use ark_ff::PrimeField; -use ark_poly::MultilinearExtension; -use ark_std::log2; -use std::marker::PhantomData; - -/// Implements the Non-Interactive Folding Scheme described in section 4 of -/// [Mova](https://eprint.iacr.org/2024/1220.pdf) -/// `H` specifies whether the NIFS will use a blinding factor -pub struct NIFS< - C: CurveGroup, - CS: CommitmentScheme, - T: Transcript, - const H: bool = false, -> { - _c: PhantomData, - _cp: PhantomData, - _ct: PhantomData, -} - -pub struct Proof { - pub h_proof: PointVsLineProof, - pub mleE1_prime: C::ScalarField, - pub mleE2_prime: C::ScalarField, - pub mleT: C::ScalarField, -} - -impl, T: Transcript, const H: bool> - NIFS -where - ::ScalarField: Absorb, - ::BaseField: PrimeField, -{ - // Protocol 7 - point 3 (16) - pub fn fold_witness( - a: C::ScalarField, - w1: &Witness, - w2: &Witness, - T: &[C::ScalarField], - ) -> Result, Error> { - let a_squared = a * a; - let E: Vec = vec_add( - &vec_add(&w1.E, &vec_scalar_mul(T, &a))?, - &vec_scalar_mul(&w2.E, &a_squared), - )?; - let W: Vec = - w1.W.iter() - .zip(&w2.W) - .map(|(i1, i2)| *i1 + (a * i2)) - .collect(); - - let rW = w1.rW + a * w2.rW; - Ok(Witness:: { E, W, rW }) - } - - // Protocol 7 - point 3 (15) - pub fn fold_committed_instance( - a: C::ScalarField, - ci1: &CommittedInstance, - ci2: &CommittedInstance, - rE_prime: Vec, - mleE1_prime: &C::ScalarField, - mleE2_prime: &C::ScalarField, - mleT: &C::ScalarField, - ) -> Result, Error> { - let a_squared = a * a; - let mleE = *mleE1_prime + a * mleT + a_squared * mleE2_prime; - let u = ci1.u + a * ci2.u; - let cmW = ci1.cmW + ci2.cmW.mul(a); - let x = ci1 - .x - .iter() - .zip(&ci2.x) - .map(|(i1, i2)| *i1 + (a * i2)) - .collect::>(); - - Ok(CommittedInstance:: { - rE: rE_prime.to_vec(), - mleE, - u, - cmW, - x, - }) - } - - /// [Mova](https://eprint.iacr.org/2024/1220.pdf)'s section 4. Protocol 8 - /// Returns a proof for the pt-vs-line operations along with the folded committed instance - /// instances and witness - #[allow(clippy::type_complexity)] - pub fn prove( - r1cs: &R1CS, - transcript: &mut impl Transcript, - ci1: &CommittedInstance, - ci2: &CommittedInstance, - w1: &Witness, - w2: &Witness, - ) -> Result<(Proof, InstanceWitness), Error> { - // Protocol 5 is pre-processing - transcript.absorb(ci1); - transcript.absorb(ci2); - - // Protocol 6 - let ( - h_proof, - PointvsLineEvaluationClaim { - mleE1_prime, - mleE2_prime, - rE_prime, - }, - ) = PointVsLine::::prove(transcript, ci1, ci2, w1, w2)?; - - // Protocol 7 - transcript.absorb(&mleE1_prime); - transcript.absorb(&mleE2_prime); - - // Remember Z = (W, x, u) - let z1: Vec = [vec![ci1.u], ci1.x.to_vec(), w1.W.to_vec()].concat(); - let z2: Vec = [vec![ci2.u], ci2.x.to_vec(), w2.W.to_vec()].concat(); - - let T = NovaNIFS::::compute_T(r1cs, ci1.u, ci2.u, &z1, &z2)?; - - let n_vars: usize = log2(w1.E.len()) as usize; - if log2(T.len()) as usize != n_vars { - return Err(Error::NotEqual); - } - - let mleT = dense_vec_to_dense_mle(n_vars, &T); - let mleT_evaluated = mleT.evaluate(&rE_prime).ok_or(Error::EvaluationFail)?; - - transcript.absorb(&mleT_evaluated); - - let alpha: C::ScalarField = transcript.get_challenge(); - - Ok(( - Proof:: { - h_proof, - mleE1_prime, - mleE2_prime, - mleT: mleT_evaluated, - }, - InstanceWitness { - ci: Self::fold_committed_instance( - alpha, - ci1, - ci2, - rE_prime, - &mleE1_prime, - &mleE2_prime, - &mleT_evaluated, - )?, - w: Self::fold_witness(alpha, w1, w2, &T)?, - }, - )) - } - - /// [Mova](https://eprint.iacr.org/2024/1220.pdf)'s section 4. - /// It verifies the results from both the folding and the pt-vs-line proofs. - /// Returns the folded committed instance. - pub fn verify( - transcript: &mut impl Transcript, - ci1: &CommittedInstance, - ci2: &CommittedInstance, - proof: &Proof, - ) -> Result, Error> { - transcript.absorb(ci1); - transcript.absorb(ci2); - let rE_prime = PointVsLine::::verify( - transcript, - ci1, - ci2, - &proof.h_proof, - &proof.mleE1_prime, - &proof.mleE2_prime, - )?; - - transcript.absorb(&proof.mleE1_prime); - transcript.absorb(&proof.mleE2_prime); - transcript.absorb(&proof.mleT); - - let alpha: C::ScalarField = transcript.get_challenge(); - - Self::fold_committed_instance( - alpha, - ci1, - ci2, - rE_prime, - &proof.mleE1_prime, - &proof.mleE2_prime, - &proof.mleT, - ) - } -} - -#[cfg(test)] -pub mod tests { - use crate::arith::r1cs::tests::{get_test_r1cs, get_test_z}; - use crate::arith::Arith; - use crate::commitment::pedersen::{Params as PedersenParams, Pedersen}; - use crate::folding::traits::Dummy; - use crate::transcript::poseidon::poseidon_canonical_config; - use ark_crypto_primitives::sponge::{ - poseidon::{PoseidonConfig, PoseidonSponge}, - CryptographicSponge, - }; - use ark_ff::PrimeField; - use ark_pallas::{Fr, Projective}; - use ark_std::{test_rng, UniformRand, Zero}; - - use super::*; - - #[allow(clippy::type_complexity)] - fn prepare_simple_fold_inputs() -> ( - PedersenParams, - PoseidonConfig, - R1CS, - Witness, // w1 - CommittedInstance, // ci1 - Witness, // w2 - CommittedInstance, // ci2 - Proof, // pt-vs-line - InstanceWitness, // w3, ci3 - ) - 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 mut rng = ark_std::test_rng(); - - let w1 = Witness::::new::(w1.clone(), r1cs.A.n_rows, &mut rng); - let w2 = Witness::::new::(w2.clone(), r1cs.A.n_rows, &mut rng); - - let (pedersen_params, _) = Pedersen::::setup(&mut rng, r1cs.A.n_cols).unwrap(); - - // compute committed instances - let rE_1: Vec = (0..log2(3)) - .map(|_| C::ScalarField::rand(&mut rng)) - .collect(); - let rE_2: Vec = (0..log2(4)) - .map(|_| C::ScalarField::rand(&mut rng)) - .collect(); - let ci1 = w1 - .commit::>(&pedersen_params, x1.clone(), rE_1) - .unwrap(); - let ci2 = w2 - .commit::>(&pedersen_params, x2.clone(), rE_2) - .unwrap(); - - let poseidon_config = poseidon_canonical_config::(); - let mut transcript_p = PoseidonSponge::::new(&poseidon_config); - - let result = NIFS::, PoseidonSponge>::prove( - &r1cs, - &mut transcript_p, - &ci1, - &ci2, - &w1, - &w2, - ) - .unwrap(); - let (proof, instance) = result; - - ( - pedersen_params, - poseidon_config, - r1cs, - w1, - ci1, - w2, - ci2, - proof, - instance, - ) - } - - // 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 (w1, x1) = r1cs.split_z(&z1); - - 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::>( - &pedersen_params, - vec![Fr::zero(); x1.len()], - vec![Fr::zero(); log2(3) as usize], - ) - .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 poseidon_config = poseidon_canonical_config::(); - let mut transcript_p: PoseidonSponge = PoseidonSponge::::new(&poseidon_config); - - let result = NIFS::, PoseidonSponge>::prove( - &r1cs, - &mut transcript_p, - &u_i, - &U_i, - &w_i, - &W_i, - ) - .unwrap(); - - let (_proof, instance_witness) = result; - r1cs.check_relation(&instance_witness.w, &instance_witness.ci) - .unwrap(); - } - - // fold 2 instances into one - #[test] - fn test_nifs_one_fold() { - let (pedersen_params, poseidon_config, r1cs, w1, ci1, w2, ci2, proof, instance) = - prepare_simple_fold_inputs::(); - - // NIFS.V - let mut transcript_v: PoseidonSponge = PoseidonSponge::::new(&poseidon_config); - let ci3 = NIFS::, PoseidonSponge>::verify( - &mut transcript_v, - &ci1, - &ci2, - &proof, - ) - .unwrap(); - assert_eq!(ci3, instance.ci); - - // 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(&instance.w, &instance.ci).unwrap(); - - // check that folded commitments from folded instance (ci) are equal to folding the - // use folded rE, rW to commit w3 - let ci3_expected = instance - .w - .commit::>(&pedersen_params, ci3.x.clone(), instance.ci.rE) - .unwrap(); - assert_eq!(ci3_expected.cmW, instance.ci.cmW); - } - - #[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 rE: Vec = (0..log2(3)).map(|_| Fr::rand(&mut rng)).collect(); - let mut running_instance_w = - Witness::::new::(w.clone(), r1cs.A.n_rows, test_rng()); - let mut running_committed_instance = running_instance_w - .commit::>(&pedersen_params, x, rE) - .unwrap(); - - r1cs.check_relation(&running_instance_w, &running_committed_instance) - .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 rE: Vec = (0..log2(3)).map(|_| Fr::rand(&mut rng)).collect(); - let incoming_committed_instance = incoming_instance_w - .commit::>(&pedersen_params, x, rE) - .unwrap(); - r1cs.check_relation(&incoming_instance_w, &incoming_committed_instance) - .unwrap(); - - // NIFS.P - let poseidon_config = poseidon_canonical_config::(); - let mut transcript_p = PoseidonSponge::::new(&poseidon_config); - - let result = NIFS::, PoseidonSponge>::prove( - &r1cs, - &mut transcript_p, - &running_committed_instance, - &incoming_committed_instance, - &running_instance_w, - &incoming_instance_w, - ) - .unwrap(); - - let (proof, instance_witness) = result; - - // NIFS.V - let mut transcript_v: PoseidonSponge = PoseidonSponge::::new(&poseidon_config); - let _ci3 = NIFS::, PoseidonSponge>::verify( - &mut transcript_v, - &running_committed_instance, - &incoming_committed_instance, - &proof, - ) - .unwrap(); - - r1cs.check_relation(&instance_witness.w, &instance_witness.ci) - .unwrap(); - - // set running_instance for next loop iteration - running_instance_w = instance_witness.w; - running_committed_instance = instance_witness.ci; - } - } -} diff --git a/folding-schemes/src/folding/mova/traits.rs b/folding-schemes/src/folding/mova/traits.rs deleted file mode 100644 index ec12d0bc..00000000 --- a/folding-schemes/src/folding/mova/traits.rs +++ /dev/null @@ -1,28 +0,0 @@ -use crate::arith::ccs::CCS; -use crate::arith::{r1cs::R1CS, Arith}; -use crate::folding::circuits::CF1; -use crate::folding::mova::{CommittedInstance, Witness}; -use crate::utils::mle::dense_vec_to_dense_mle; -use crate::utils::vec::mat_vec_mul; -use crate::Error; -use ark_ec::CurveGroup; - -impl Arith, CommittedInstance> for R1CS> { - type Evaluation = Vec>; - - fn eval_relation( - &self, - w: &Witness, - u: &CommittedInstance, - ) -> Result { - self.eval_at_z(&[&[u.u][..], &u.x, &w.W].concat()) - } - - fn check_evaluation( - w: &Witness, - _u: &CommittedInstance, - e: Self::Evaluation, - ) -> Result<(), Error> { - (w.E == e).then_some(()).ok_or(Error::NotSatisfied) - } -} diff --git a/folding-schemes/src/folding/nova/circuits.rs b/folding-schemes/src/folding/nova/circuits.rs index b657b61e..b5803a2d 100644 --- a/folding-schemes/src/folding/nova/circuits.rs +++ b/folding-schemes/src/folding/nova/circuits.rs @@ -529,8 +529,7 @@ pub mod tests { use ark_std::UniformRand; use crate::commitment::pedersen::Pedersen; - use crate::folding::nova::nifs::NIFS; - use crate::folding::nova::traits::NIFSTrait; + use crate::folding::nova::nifs::{nova::NIFS, NIFSTrait}; use crate::folding::traits::CommittedInstanceOps; use crate::transcript::poseidon::poseidon_canonical_config; @@ -570,9 +569,19 @@ pub mod tests { }) .collect(); let (ci1, ci2) = (ci[0].clone(), ci[1].clone()); - let r_Fr = Fr::rand(&mut rng); + let pp_hash = Fr::rand(&mut rng); let cmT = Projective::rand(&mut rng); - let ci3 = NIFS::>::verify(r_Fr, &ci1, &ci2, &cmT); + let poseidon_config = poseidon_canonical_config::(); + let mut transcript = PoseidonSponge::::new(&poseidon_config); + let (ci3, r_bits) = NIFS::, PoseidonSponge>::verify( + &mut transcript, + pp_hash, + &ci1, + &ci2, + &cmT, + ) + .unwrap(); + let r_Fr = Fr::from_bigint(BigInteger::from_bits_le(&r_bits)).unwrap(); let cs = ConstraintSystem::::new_ref(); diff --git a/folding-schemes/src/folding/nova/decider.rs b/folding-schemes/src/folding/nova/decider.rs index 901f152a..e11757c6 100644 --- a/folding-schemes/src/folding/nova/decider.rs +++ b/folding-schemes/src/folding/nova/decider.rs @@ -2,7 +2,7 @@ /// DeciderEth from decider_eth.rs file. /// More details can be found at the documentation page: /// https://privacy-scaling-explorations.github.io/sonobe-docs/design/nova-decider-offchain.html -use ark_crypto_primitives::sponge::Absorb; +use ark_crypto_primitives::sponge::{poseidon::PoseidonSponge, Absorb, CryptographicSponge}; use ark_ec::{AffineRepr, CurveGroup, Group}; use ark_ff::{BigInteger, PrimeField}; use ark_r1cs_std::{groups::GroupOpsBounds, prelude::CurveVar, ToConstraintFieldGadget}; @@ -13,7 +13,10 @@ use ark_std::{One, Zero}; use core::marker::PhantomData; use super::decider_circuits::{DeciderCircuit1, DeciderCircuit2}; -use super::{nifs::NIFS, traits::NIFSTrait, CommittedInstance, Nova}; +use super::{ + nifs::{nova::NIFS, NIFSTrait}, + CommittedInstance, Nova, +}; use crate::commitment::CommitmentScheme; use crate::folding::circuits::{ cyclefold::CycleFoldCommittedInstance, @@ -21,6 +24,7 @@ use crate::folding::circuits::{ CF2, }; use crate::frontend::FCircuit; +use crate::transcript::poseidon::poseidon_canonical_config; use crate::Error; use crate::{Decider as DeciderTrait, FoldingScheme}; @@ -41,7 +45,6 @@ where // cmT and r are values for the last fold, U_{i+1}=NIFS.V(r, U_i, u_i, cmT), and they are // checked in-circuit cmT: C1, - r: C1::ScalarField, // cyclefold committed instance cf_U_i: CycleFoldCommittedInstance, // the CS challenges are provided by the prover, but in-circuit they are checked to match the @@ -209,7 +212,6 @@ where .map_err(|e| Error::Other(e.to_string()))?; let cmT = circuit1.cmT.unwrap(); - let r_Fr = circuit1.r.unwrap(); let W_i1 = circuit1.W_i1.unwrap(); let cf_W_i = circuit2.cf_W_i.unwrap(); @@ -265,7 +267,6 @@ where cs1_proofs: [U_cmW_proof, U_cmE_proof], cs2_proofs: [cf_cmW_proof, cf_cmE_proof], cmT, - r: r_Fr, cf_U_i: circuit1.cf_U_i.unwrap(), cs1_challenges: [challenge_W, challenge_E], cs2_challenges: [c2_challenge_W, c2_challenge_E], @@ -286,7 +287,17 @@ where } // compute U = U_{d+1}= NIFS.V(U_d, u_d, cmT) - let U = NIFS::::verify(proof.r, running_instance, incoming_instance, &proof.cmT); + let poseidon_config = poseidon_canonical_config::(); + let mut transcript = PoseidonSponge::::new(&poseidon_config); + let (U, r_bits) = NIFS::>::verify( + &mut transcript, + vp.pp_hash, + running_instance, + incoming_instance, + &proof.cmT, + )?; + let r = C1::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits)) + .ok_or(Error::OutOfBounds)?; let (cmE_x, cmE_y) = NonNativeAffineVar::inputize(U.cmE)?; let (cmW_x, cmW_y) = NonNativeAffineVar::inputize(U.cmW)?; @@ -332,7 +343,7 @@ where // NIFS values: cmT_x, cmT_y, - vec![proof.r], + vec![r], ] .concat(); diff --git a/folding-schemes/src/folding/nova/decider_circuits.rs b/folding-schemes/src/folding/nova/decider_circuits.rs index cfd56c1b..d15ccf0a 100644 --- a/folding-schemes/src/folding/nova/decider_circuits.rs +++ b/folding-schemes/src/folding/nova/decider_circuits.rs @@ -28,8 +28,7 @@ use super::{ decider_eth_circuit::{ evaluate_gadget, KZGChallengesGadget, R1CSVar, RelaxedR1CSGadget, WitnessVar, }, - nifs::NIFS, - traits::NIFSTrait, + nifs::{nova::NIFS, NIFSTrait}, CommittedInstance, Nova, Witness, }; use crate::arith::r1cs::R1CS; @@ -114,28 +113,21 @@ where CS2: CommitmentScheme, { let mut transcript = PoseidonSponge::::new(&nova.poseidon_config); - // pp_hash is absorbed to transcript at the ChallengeGadget::get_challenge_native call + // pp_hash is absorbed to transcript at the NIFS::prove call // compute the U_{i+1}, W_{i+1} - let (T, cmT) = NIFS::::compute_cmT( + let (W_i1, U_i1, cmT, r_bits) = NIFS::, H>::prove( &nova.cs_pp, &nova.r1cs.clone(), - &nova.w_i.clone(), - &nova.u_i.clone(), - &nova.W_i.clone(), - &nova.U_i.clone(), - )?; - let r_bits = NIFS::::get_challenge( &mut transcript, nova.pp_hash, + &nova.W_i, &nova.U_i, + &nova.w_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::::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) = diff --git a/folding-schemes/src/folding/nova/decider_eth.rs b/folding-schemes/src/folding/nova/decider_eth.rs index 24d27edb..4e4719e5 100644 --- a/folding-schemes/src/folding/nova/decider_eth.rs +++ b/folding-schemes/src/folding/nova/decider_eth.rs @@ -3,7 +3,7 @@ /// More details can be found at the documentation page: /// https://privacy-scaling-explorations.github.io/sonobe-docs/design/nova-decider-onchain.html use ark_bn254::Bn254; -use ark_crypto_primitives::sponge::Absorb; +use ark_crypto_primitives::sponge::{poseidon::PoseidonSponge, Absorb, CryptographicSponge}; use ark_ec::{AffineRepr, CurveGroup, Group}; use ark_ff::{BigInteger, PrimeField}; use ark_groth16::Groth16; @@ -15,8 +15,10 @@ 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 super::{ + nifs::{nova::NIFS, NIFSTrait}, + CommittedInstance, Nova, +}; use crate::commitment::{ kzg::{Proof as KZGProof, KZG}, pedersen::Params as PedersenParams, @@ -24,6 +26,7 @@ use crate::commitment::{ }; use crate::folding::circuits::{nonnative::affine::NonNativeAffineVar, CF2}; use crate::frontend::FCircuit; +use crate::transcript::poseidon::poseidon_canonical_config; use crate::Error; use crate::{Decider as DeciderTrait, FoldingScheme}; @@ -39,7 +42,6 @@ where // cmT and r are values for the last fold, U_{i+1}=NIFS.V(r, U_i, u_i, cmT), and they are // checked in-circuit cmT: C1, - r: C1::ScalarField, // the KZG challenges are provided by the prover, but in-circuit they are checked to match // the in-circuit computed computed ones. kzg_challenges: [C1::ScalarField; 2], @@ -161,7 +163,6 @@ where .map_err(|e| Error::Other(e.to_string()))?; let cmT = circuit.cmT.unwrap(); - let r_Fr = circuit.r.unwrap(); let W_i1 = circuit.W_i1.unwrap(); // get the challenges that have been already computed when preparing the circuit inputs in @@ -193,7 +194,6 @@ where snark_proof, kzg_proofs: [U_cmW_proof, U_cmE_proof], cmT, - r: r_Fr, kzg_challenges: [challenge_W, challenge_E], }) } @@ -212,7 +212,17 @@ where } // compute U = U_{d+1}= NIFS.V(U_d, u_d, cmT) - let U = NIFS::::verify(proof.r, running_instance, incoming_instance, &proof.cmT); + let poseidon_config = poseidon_canonical_config::(); + let mut transcript = PoseidonSponge::::new(&poseidon_config); + let (U, r_bits) = NIFS::>::verify( + &mut transcript, + vp.pp_hash, + running_instance, + incoming_instance, + &proof.cmT, + )?; + let r = C1::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits)) + .ok_or(Error::OutOfBounds)?; let (cmE_x, cmE_y) = NonNativeAffineVar::inputize(U.cmE)?; let (cmW_x, cmW_y) = NonNativeAffineVar::inputize(U.cmW)?; @@ -235,7 +245,7 @@ where ], cmT_x, cmT_y, - vec![proof.r], + vec![r], ] .concat(); @@ -264,11 +274,13 @@ where } /// Prepares solidity calldata for calling the NovaDecider contract +#[allow(clippy::too_many_arguments)] pub fn prepare_calldata( function_signature_check: [u8; 4], i: ark_bn254::Fr, z_0: Vec, z_i: Vec, + r: ark_bn254::Fr, running_instance: &CommittedInstance, incoming_instance: &CommittedInstance, proof: Proof, Groth16>, @@ -286,7 +298,7 @@ pub fn prepare_calldata( point_to_eth_format(running_instance.cmE.into_affine())?, // U_i_cmE running_instance.u.into_bigint().to_bytes_be(), // U_i_u incoming_instance.u.into_bigint().to_bytes_be(), // u_i_u - proof.r.into_bigint().to_bytes_be(), // r + r.into_bigint().to_bytes_be(), // r running_instance .x .iter() diff --git a/folding-schemes/src/folding/nova/decider_eth_circuit.rs b/folding-schemes/src/folding/nova/decider_eth_circuit.rs index a1dd8091..a8ae6750 100644 --- a/folding-schemes/src/folding/nova/decider_eth_circuit.rs +++ b/folding-schemes/src/folding/nova/decider_eth_circuit.rs @@ -26,8 +26,7 @@ use core::{borrow::Borrow, marker::PhantomData}; use super::{ circuits::{ChallengeGadget, CommittedInstanceVar}, - nifs::NIFS, - traits::NIFSTrait, + nifs::{nova::NIFS, NIFSTrait}, CommittedInstance, Nova, Witness, }; use crate::commitment::{pedersen::Params as PedersenParams, CommitmentScheme}; @@ -246,27 +245,18 @@ where let mut transcript = PoseidonSponge::::new(&nova.poseidon_config); // compute the U_{i+1}, W_{i+1} - let (aux_p, aux_v) = NIFS::::compute_aux( + let (W_i1, U_i1, cmT, r_bits) = NIFS::, H>::prove( &nova.cs_pp, &nova.r1cs.clone(), - &nova.w_i.clone(), - &nova.u_i.clone(), - &nova.W_i.clone(), - &nova.U_i.clone(), - )?; - let cmT = aux_v; - let r_bits = ChallengeGadget::>::get_challenge_native( &mut transcript, nova.pp_hash, + &nova.W_i, &nova.U_i, + &nova.w_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::::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 let (kzg_challenge_W, kzg_challenge_E) = diff --git a/folding-schemes/src/folding/nova/mod.rs b/folding-schemes/src/folding/nova/mod.rs index d67c33b9..5e701cd0 100644 --- a/folding-schemes/src/folding/nova/mod.rs +++ b/folding-schemes/src/folding/nova/mod.rs @@ -1,5 +1,9 @@ /// Implements the scheme described in [Nova](https://eprint.iacr.org/2021/370.pdf) and /// [CycleFold](https://eprint.iacr.org/2023/1192.pdf). +/// +/// The structure of the Nova code is the following: +/// - NIFS implementation for Nova (nifs.rs), Mova (mova.rs), Ova (ova.rs) +/// - IVC and the Decider (offchain Decider & onchain Decider) implementations for Nova use ark_crypto_primitives::sponge::{ poseidon::{PoseidonConfig, PoseidonSponge}, Absorb, CryptographicSponge, @@ -36,14 +40,14 @@ use crate::{ use crate::{arith::Arith, commitment::CommitmentScheme}; pub mod circuits; -pub mod nifs; -pub mod ova; pub mod traits; pub mod zk; -use circuits::{AugmentedFCircuit, ChallengeGadget, CommittedInstanceVar}; -use nifs::NIFS; -use traits::NIFSTrait; +// NIFS related: +pub mod nifs; + +use circuits::{AugmentedFCircuit, CommittedInstanceVar}; +use nifs::{nova::NIFS, NIFSTrait}; // offchain decider pub mod decider; @@ -714,28 +718,21 @@ where .F .step_native(i_usize, self.z_i.clone(), external_inputs.clone())?; - // compute T and cmT for AugmentedFCircuit - 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( - &mut transcript, - self.pp_hash, - &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)?; + // fold Nova instances + let (W_i1, U_i1, cmT, r_bits): (Witness, CommittedInstance, C1, Vec) = + NIFS::, H>::prove( + &self.cs_pp, + &self.r1cs, + &mut transcript, + self.pp_hash, + &self.W_i, + &self.U_i, + &self.w_i, + &self.u_i, + )?; let r_Fq = C1::BaseField::from_bigint(BigInteger::from_bits_le(&r_bits)) .ok_or(Error::OutOfBounds)?; - // fold Nova instances - 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}) let u_i1_x = U_i1.hash( @@ -776,7 +773,15 @@ where }; #[cfg(test)] - NIFS::::verify_folded_instance(r_Fr, &self.U_i, &self.u_i, &U_i1, &cmT)?; + { + let r_Fr = C1::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits)) + .ok_or(Error::OutOfBounds)?; + let expected = + NIFS::, H>::fold_committed_instances( + r_Fr, &self.U_i, &self.u_i, &cmT, + ); + assert_eq!(U_i1, expected); + } } else { // CycleFold part: // get the vector used as public inputs 'x' in the CycleFold circuit @@ -1037,33 +1042,6 @@ where } } -impl Nova -where - C1: CurveGroup, - GC1: CurveVar> + ToConstraintFieldGadget>, - C2: CurveGroup, - GC2: CurveVar>, - FC: FCircuit, - CS1: CommitmentScheme, - CS2: CommitmentScheme, - ::BaseField: PrimeField, - ::ScalarField: Absorb, - ::ScalarField: Absorb, - C1: CurveGroup, -{ - // computes T and cmT for the AugmentedFCircuit - fn compute_cmT(&self) -> Result<(Vec, C1), Error> { - NIFS::::compute_aux( - &self.cs_pp, - &self.r1cs, - &self.w_i, - &self.u_i, - &self.W_i, - &self.U_i, - ) - } -} - impl Nova where C1: CurveGroup, diff --git a/folding-schemes/src/folding/nova/nifs/mod.rs b/folding-schemes/src/folding/nova/nifs/mod.rs new file mode 100644 index 00000000..98e40cf5 --- /dev/null +++ b/folding-schemes/src/folding/nova/nifs/mod.rs @@ -0,0 +1,152 @@ +/// This module defines the NIFSTrait, which is set to implement the NIFS (Non-Interactive Folding +/// Scheme) by the various schemes (Nova, Mova, Ova). +use ark_crypto_primitives::sponge::Absorb; +use ark_ec::CurveGroup; +use ark_std::fmt::Debug; +use ark_std::rand::RngCore; + +use crate::arith::r1cs::R1CS; +use crate::commitment::CommitmentScheme; +use crate::transcript::Transcript; +use crate::Error; + +pub mod mova; +pub mod nova; +pub mod ova; +pub mod pointvsline; + +/// 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< + C: CurveGroup, + CS: CommitmentScheme, + T: Transcript, + const H: bool = false, +> +{ + type CommittedInstance: Debug + Clone + Absorb; + type Witness: Debug + Clone; + type ProverAux: Debug + Clone; // Prover's aux params. eg. in Nova is T + type Proof: Debug + Clone; // proof. eg. in Nova is cmT + + fn new_witness(w: Vec, e_len: usize, rng: impl RngCore) -> Self::Witness; + + fn new_instance( + rng: impl RngCore, + params: &CS::ProverParams, + w: &Self::Witness, + 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; + + /// NIFS.P. Returns a tuple containing the folded Witness, the folded CommittedInstance, and + /// the used challenge `r` as a vector of bits, so that it can be reused in other methods. + #[allow(clippy::type_complexity)] + #[allow(clippy::too_many_arguments)] + fn prove( + cs_prover_params: &CS::ProverParams, + r1cs: &R1CS, + transcript: &mut T, + pp_hash: 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 + ) -> Result< + ( + Self::Witness, + Self::CommittedInstance, + Self::Proof, + Vec, + ), + Error, + >; + + /// NIFS.V. Returns the folded CommittedInstance and the used challenge `r` as a vector of + /// bits, so that it can be reused in other methods. + fn verify( + transcript: &mut T, + pp_hash: C::ScalarField, + U_i: &Self::CommittedInstance, + u_i: &Self::CommittedInstance, + proof: &Self::Proof, + ) -> Result<(Self::CommittedInstance, Vec), Error>; +} + +#[cfg(test)] +pub mod tests { + use super::*; + use crate::transcript::poseidon::poseidon_canonical_config; + use ark_crypto_primitives::sponge::{poseidon::PoseidonSponge, CryptographicSponge}; + use ark_pallas::{Fr, Projective}; + use ark_std::{test_rng, UniformRand}; + + use super::NIFSTrait; + use crate::arith::r1cs::tests::{get_test_r1cs, get_test_z}; + use crate::commitment::pedersen::Pedersen; + + /// Test method used to test the different implementations of the NIFSTrait (ie. Nova, Mova, + /// Ova). 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: NIFSTrait, PoseidonSponge>, + >() -> (N::Witness, N::CommittedInstance) { + 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(); + + let poseidon_config = poseidon_canonical_config::(); + let mut transcript_p = PoseidonSponge::::new(&poseidon_config); + let mut transcript_v = PoseidonSponge::::new(&poseidon_config); + let pp_hash = Fr::rand(&mut rng); + + // prepare the running instance + let mut W_i = N::new_witness(w.clone(), r1cs.A.n_rows, test_rng()); + let mut U_i = N::new_instance(&mut rng, &pedersen_params, &W_i, 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 w_i = N::new_witness(w.clone(), r1cs.A.n_rows, test_rng()); + let u_i = N::new_instance(&mut rng, &pedersen_params, &w_i, x, vec![]).unwrap(); + + // NIFS.P + let (folded_witness, _, proof, _) = N::prove( + &pedersen_params, + &r1cs, + &mut transcript_p, + pp_hash, + &W_i, + &U_i, + &w_i, + &u_i, + ) + .unwrap(); + + // NIFS.V + let (folded_committed_instance, _) = + N::verify(&mut transcript_v, pp_hash, &U_i, &u_i, &proof).unwrap(); + + // set running_instance for next loop iteration + W_i = folded_witness; + U_i = folded_committed_instance; + } + + (W_i, U_i) + } +} diff --git a/folding-schemes/src/folding/nova/nifs/mova.rs b/folding-schemes/src/folding/nova/nifs/mova.rs new file mode 100644 index 00000000..6f3353bd --- /dev/null +++ b/folding-schemes/src/folding/nova/nifs/mova.rs @@ -0,0 +1,411 @@ +/// This module contains the implementation the NIFSTrait for the +/// [Mova](https://eprint.iacr.org/2024/1220.pdf) NIFS (Non-Interactive Folding Scheme). +use ark_crypto_primitives::sponge::Absorb; +use ark_ec::{CurveGroup, Group}; +use ark_ff::PrimeField; +use ark_poly::MultilinearExtension; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_std::log2; +use ark_std::rand::RngCore; +use ark_std::{One, UniformRand, Zero}; +use std::marker::PhantomData; + +use super::{ + nova::NIFS as NovaNIFS, + pointvsline::{PointVsLine, PointVsLineProof, PointvsLineEvaluationClaim}, + NIFSTrait, +}; +use crate::arith::{r1cs::R1CS, Arith}; +use crate::commitment::CommitmentScheme; +use crate::folding::circuits::CF1; +use crate::folding::traits::Dummy; +use crate::transcript::AbsorbNonNative; +use crate::transcript::Transcript; +use crate::utils::{ + mle::dense_vec_to_dense_mle, + vec::{is_zero_vec, vec_add, vec_scalar_mul}, +}; +use crate::Error; + +#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] +pub struct CommittedInstance { + // Random evaluation point for the E + pub rE: Vec, + // mleE is the evaluation of the MLE of E at r_E + pub mleE: C::ScalarField, + pub u: C::ScalarField, + pub cmW: C, + pub x: Vec, +} + +impl Absorb for CommittedInstance +where + C::ScalarField: Absorb, +{ + fn to_sponge_bytes(&self, _dest: &mut Vec) { + // This is never called + unimplemented!() + } + + fn to_sponge_field_elements(&self, dest: &mut Vec) { + self.u.to_sponge_field_elements(dest); + self.x.to_sponge_field_elements(dest); + self.rE.to_sponge_field_elements(dest); + self.mleE.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.cmW + .to_native_sponge_field_elements_as_vec() + .to_sponge_field_elements(dest); + } +} + +impl Dummy for CommittedInstance { + fn dummy(io_len: usize) -> Self { + Self { + rE: vec![C::ScalarField::zero(); io_len], + mleE: C::ScalarField::zero(), + u: C::ScalarField::zero(), + cmW: C::zero(), + x: vec![C::ScalarField::zero(); io_len], + } + } +} + +#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] +pub struct Witness { + pub E: Vec, + pub W: Vec, + pub rW: C::ScalarField, +} + +impl Dummy<&R1CS> for Witness { + fn dummy(r1cs: &R1CS) -> Self { + Self { + E: vec![C::ScalarField::zero(); r1cs.A.n_rows], + W: vec![C::ScalarField::zero(); r1cs.A.n_cols - 1 - r1cs.l], + rW: C::ScalarField::zero(), + } + } +} + +impl Witness { + pub fn new(w: Vec, e_len: usize, mut rng: impl RngCore) -> Self { + let rW = if H { + C::ScalarField::rand(&mut rng) + } else { + C::ScalarField::zero() + }; + + Self { + E: vec![C::ScalarField::zero(); e_len], + W: w, + rW, + } + } + + pub fn commit, const H: bool>( + &self, + params: &CS::ProverParams, + x: Vec, + rE: Vec, + ) -> Result, Error> { + let mut mleE = C::ScalarField::zero(); + if !is_zero_vec::(&self.E) { + let E = dense_vec_to_dense_mle(log2(self.E.len()) as usize, &self.E); + mleE = E.evaluate(&rE).ok_or(Error::NotExpectedLength( + rE.len(), + log2(self.E.len()) as usize, + ))?; + } + let cmW = CS::commit(params, &self.W, &self.rW)?; + Ok(CommittedInstance { + rE, + mleE, + u: C::ScalarField::one(), + cmW, + x, + }) + } +} + +#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] +pub struct Proof { + pub h_proof: PointVsLineProof, + pub mleE1_prime: C::ScalarField, + pub mleE2_prime: C::ScalarField, + pub mleT: C::ScalarField, +} + +/// Implements the Non-Interactive Folding Scheme described in section 4 of +/// [Mova](https://eprint.iacr.org/2024/1220.pdf). +/// `H` specifies whether the NIFS will use a blinding factor +pub struct NIFS< + C: CurveGroup, + CS: CommitmentScheme, + T: Transcript, + const H: bool = false, +> { + _c: PhantomData, + _cp: PhantomData, + _ct: PhantomData, +} + +impl, T: Transcript, const H: bool> + NIFSTrait for NIFS +where + ::ScalarField: Absorb, + ::BaseField: PrimeField, +{ + type CommittedInstance = CommittedInstance; + type Witness = Witness; + type ProverAux = Vec; // T in Mova's notation + type Proof = Proof; + + fn new_witness(w: Vec, e_len: usize, rng: impl RngCore) -> Self::Witness { + Witness::new::(w, e_len, rng) + } + + fn new_instance( + mut rng: impl RngCore, + params: &CS::ProverParams, + W: &Self::Witness, + x: Vec, + aux: Vec, // = r_E + ) -> Result { + let mut rE = aux.clone(); + if is_zero_vec(&rE) { + // means that we're in a fresh instance, so generate random value + rE = (0..log2(W.E.len())) + .map(|_| C::ScalarField::rand(&mut rng)) + .collect(); + } + + W.commit::(params, x, rE) + } + + // Protocol 7 - point 3 (16) + fn fold_witness( + a: C::ScalarField, + W_i: &Witness, + w_i: &Witness, + aux: &Vec, // T in Mova's notation + ) -> Result, Error> { + let a2 = a * a; + let E: Vec = vec_add( + &vec_add(&W_i.E, &vec_scalar_mul(aux, &a))?, + &vec_scalar_mul(&w_i.E, &a2), + )?; + let W: Vec = W_i + .W + .iter() + .zip(&w_i.W) + .map(|(i1, i2)| *i1 + (a * i2)) + .collect(); + + let rW = W_i.rW + a * w_i.rW; + Ok(Witness:: { E, W, rW }) + } + + /// [Mova](https://eprint.iacr.org/2024/1220.pdf)'s section 4. Protocol 8 + /// Returns a proof for the pt-vs-line operations along with the folded committed instance + /// instances and witness + #[allow(clippy::type_complexity)] + fn prove( + _cs_prover_params: &CS::ProverParams, // not used in Mova since we don't commit to T + r1cs: &R1CS, + transcript: &mut T, + pp_hash: C::ScalarField, + W_i: &Witness, + U_i: &CommittedInstance, + w_i: &Witness, + u_i: &CommittedInstance, + ) -> Result< + ( + Self::Witness, + Self::CommittedInstance, + Self::Proof, + Vec, + ), + Error, + > { + transcript.absorb(&pp_hash); + // Protocol 5 is pre-processing + transcript.absorb(U_i); + transcript.absorb(u_i); + + // Protocol 6 + let ( + h_proof, + PointvsLineEvaluationClaim { + mleE1_prime, + mleE2_prime, + rE_prime, + }, + ) = PointVsLine::::prove(transcript, U_i, u_i, W_i, w_i)?; + + // Protocol 7 + + transcript.absorb(&mleE1_prime); + transcript.absorb(&mleE2_prime); + + // compute the cross terms + 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(); + let T = NovaNIFS::::compute_T(r1cs, U_i.u, u_i.u, &z1, &z2)?; + + let n_vars: usize = log2(W_i.E.len()) as usize; + if log2(T.len()) as usize != n_vars { + return Err(Error::NotExpectedLength(T.len(), n_vars)); + } + + let mleT = dense_vec_to_dense_mle(n_vars, &T); + let mleT_evaluated = mleT.evaluate(&rE_prime).ok_or(Error::EvaluationFail)?; + + transcript.absorb(&mleT_evaluated); + + let alpha: C::ScalarField = transcript.get_challenge(); + + let ci = Self::fold_committed_instance( + alpha, + U_i, + u_i, + &rE_prime, + &mleE1_prime, + &mleE2_prime, + &mleT_evaluated, + )?; + let w = Self::fold_witness(alpha, W_i, w_i, &T)?; + + let proof = Self::Proof { + h_proof, + mleE1_prime, + mleE2_prime, + mleT: mleT_evaluated, + }; + Ok(( + w, + ci, + proof, + vec![], // r_bits, returned to be passed as inputs to the circuit, not used at the + // current impl status + )) + } + + /// [Mova](https://eprint.iacr.org/2024/1220.pdf)'s section 4. It verifies the results from the proof + /// Both the folding and the pt-vs-line proof + /// returns the folded committed instance + fn verify( + transcript: &mut T, + pp_hash: C::ScalarField, + U_i: &CommittedInstance, + u_i: &CommittedInstance, + proof: &Proof, + ) -> Result<(Self::CommittedInstance, Vec), Error> { + transcript.absorb(&pp_hash); + transcript.absorb(U_i); + transcript.absorb(u_i); + let rE_prime = PointVsLine::::verify( + transcript, + U_i, + u_i, + &proof.h_proof, + &proof.mleE1_prime, + &proof.mleE2_prime, + )?; + + transcript.absorb(&proof.mleE1_prime); + transcript.absorb(&proof.mleE2_prime); + transcript.absorb(&proof.mleT); + + let alpha: C::ScalarField = transcript.get_challenge(); + + Ok(( + Self::fold_committed_instance( + alpha, + U_i, + u_i, + &rE_prime, + &proof.mleE1_prime, + &proof.mleE2_prime, + &proof.mleT, + )?, + vec![], + )) + } +} + +impl, T: Transcript, const H: bool> + NIFS +{ + // Protocol 7 - point 3 (15) + fn fold_committed_instance( + a: C::ScalarField, + U_i: &CommittedInstance, + u_i: &CommittedInstance, + rE_prime: &[C::ScalarField], + mleE1_prime: &C::ScalarField, + mleE2_prime: &C::ScalarField, + mleT: &C::ScalarField, + ) -> Result, Error> { + let a2 = a * a; + let mleE = *mleE1_prime + a * mleT + a2 * mleE2_prime; + let u = U_i.u + a * u_i.u; + let cmW = U_i.cmW + u_i.cmW.mul(a); + let x = U_i + .x + .iter() + .zip(&u_i.x) + .map(|(i1, i2)| *i1 + (a * i2)) + .collect::>(); + + Ok(CommittedInstance:: { + rE: rE_prime.to_vec(), + mleE, + u, + cmW, + x, + }) + } +} + +impl Arith, CommittedInstance> for R1CS> { + type Evaluation = Vec>; + + fn eval_relation( + &self, + w: &Witness, + u: &CommittedInstance, + ) -> Result { + self.eval_at_z(&[&[u.u][..], &u.x, &w.W].concat()) + } + + fn check_evaluation( + w: &Witness, + _u: &CommittedInstance, + e: Self::Evaluation, + ) -> Result<(), Error> { + (w.E == e).then_some(()).ok_or(Error::NotSatisfied) + } +} + +#[cfg(test)] +pub mod tests { + use super::*; + use ark_crypto_primitives::sponge::poseidon::PoseidonSponge; + 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; + + #[test] + fn test_nifs_mova() { + let (W, U) = test_nifs_opt::, PoseidonSponge>>(); + + // check the last folded instance relation + let r1cs = get_test_r1cs(); + r1cs.check_relation(&W, &U).unwrap(); + } +} diff --git a/folding-schemes/src/folding/nova/nifs.rs b/folding-schemes/src/folding/nova/nifs/nova.rs similarity index 50% rename from folding-schemes/src/folding/nova/nifs.rs rename to folding-schemes/src/folding/nova/nifs/nova.rs index 0a2d807e..0323b505 100644 --- a/folding-schemes/src/folding/nova/nifs.rs +++ b/folding-schemes/src/folding/nova/nifs/nova.rs @@ -1,46 +1,56 @@ +/// This module contains the implementation the NIFSTrait for the +/// [Nova](https://eprint.iacr.org/2021/370.pdf) NIFS (Non-Interactive Folding Scheme). use ark_crypto_primitives::sponge::Absorb; use ark_ec::{CurveGroup, Group}; -use ark_ff::PrimeField; +use ark_ff::{BigInteger, 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 super::NIFSTrait; use crate::arith::r1cs::R1CS; use crate::commitment::CommitmentScheme; use crate::folding::circuits::cyclefold::{CycleFoldCommittedInstance, CycleFoldWitness}; +use crate::folding::nova::circuits::ChallengeGadget; +use crate::folding::nova::{CommittedInstance, Witness}; use crate::transcript::Transcript; use crate::utils::vec::{hadamard, mat_vec_mul, vec_add, vec_scalar_mul, vec_sub}; use crate::Error; /// Implements the Non-Interactive Folding Scheme described in section 4 of -/// [Nova](https://eprint.iacr.org/2021/370.pdf) +/// [Nova](https://eprint.iacr.org/2021/370.pdf). /// `H` specifies whether the NIFS will use a blinding factor -pub struct NIFS, const H: bool = false> { +pub struct NIFS< + C: CurveGroup, + CS: CommitmentScheme, + T: Transcript, + const H: bool = false, +> { _c: PhantomData, _cp: PhantomData, + _t: PhantomData, } -impl, const H: bool> NIFSTrait - for NIFS +impl, T: Transcript, const H: bool> + NIFSTrait for NIFS where ::ScalarField: Absorb, ::BaseField: PrimeField, + ::ScalarField: PrimeField, { type CommittedInstance = CommittedInstance; type Witness = Witness; type ProverAux = Vec; - type VerifierAux = C; + type Proof = 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, + _rng: impl RngCore, params: &CS::ProverParams, + W: &Self::Witness, x: Vec, _aux: Vec, ) -> Result { @@ -51,7 +61,7 @@ where r: C::ScalarField, W_i: &Self::Witness, w_i: &Self::Witness, - aux: &Self::ProverAux, + aux: &Self::ProverAux, // T in Nova's notation ) -> Result { let r2 = r * r; let E: Vec = vec_add( @@ -72,65 +82,72 @@ where Ok(Self::Witness { E, rE, W, rW }) } - fn compute_aux( + fn prove( cs_prover_params: &CS::ProverParams, r1cs: &R1CS, + transcript: &mut T, + pp_hash: C::ScalarField, W_i: &Self::Witness, U_i: &Self::CommittedInstance, w_i: &Self::Witness, u_i: &Self::CommittedInstance, - ) -> Result<(Self::ProverAux, Self::VerifierAux), Error> { + ) -> Result< + ( + Self::Witness, + Self::CommittedInstance, + Self::Proof, + Vec, + ), + Error, + > { + // compute the cross terms 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( + let r_bits = ChallengeGadget::::get_challenge_native( transcript, pp_hash, U_i, u_i, - Some(aux), - ) - } + Some(&cmT), + ); + let r_Fr = C::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits)) + .ok_or(Error::OutOfBounds)?; + + let w = Self::fold_witness(r_Fr, W_i, w_i, &T)?; - // Notice: `prove` method is implemented at the trait level. + let ci = Self::fold_committed_instances(r_Fr, U_i, u_i, &cmT); + + Ok((w, ci, cmT, r_bits)) + } fn verify( - // r comes from the transcript, and is a n-bit (N_BITS_CHALLENGE) element - r: C::ScalarField, + transcript: &mut T, + pp_hash: 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::>(); + cmT: &C, // Proof + ) -> Result<(Self::CommittedInstance, Vec), Error> { + let r_bits = ChallengeGadget::::get_challenge_native( + transcript, + pp_hash, + U_i, + u_i, + Some(cmT), + ); + let r = C::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits)) + .ok_or(Error::OutOfBounds)?; - Self::CommittedInstance { cmE, u, cmW, x } + Ok((Self::fold_committed_instances(r, U_i, u_i, cmT), r_bits)) } } -impl, const H: bool> NIFS +impl, T: Transcript, const H: bool> + NIFS where ::ScalarField: Absorb, ::BaseField: PrimeField, @@ -161,26 +178,6 @@ where vec_sub(&vec_sub(&vec_add(&Az1_Bz2, &Az2_Bz1)?, &u1Cz2)?, &u2Cz1) } - /// 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, - w1: &Witness, - ci1: &CommittedInstance, - w2: &Witness, - ci2: &CommittedInstance, - ) -> Result<(Vec, C), Error> { - let z1: Vec = [vec![ci1.u], ci1.x.to_vec(), w1.W.to_vec()].concat(); - let z2: Vec = [vec![ci2.u], ci2.x.to_vec(), w2.W.to_vec()].concat(); - - // compute cross terms - let T = Self::compute_T(r1cs, ci1.u, ci2.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)) - } - pub fn compute_cyclefold_cmT( cs_prover_params: &CS::ProverParams, r1cs: &R1CS, // R1CS over C2.Fr=C1.Fq (here C=C2) @@ -202,25 +199,26 @@ where Ok((T, 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. - pub fn verify_folded_instance( + /// folds two committed instances with the given r and cmT. This method is used by + /// Nova::verify, but also by Nova::prove and the CycleFoldNIFS::verify. + pub fn fold_committed_instances( r: C::ScalarField, - ci1: &CommittedInstance, - ci2: &CommittedInstance, - ci3: &CommittedInstance, + U_i: &CommittedInstance, + u_i: &CommittedInstance, cmT: &C, - ) -> Result<(), Error> { - let expected = Self::verify(r, ci1, ci2, cmT); - if ci3.cmE != expected.cmE - || ci3.u != expected.u - || ci3.cmW != expected.cmW - || ci3.x != expected.x - { - return Err(Error::NotSatisfied); - } - Ok(()) + ) -> 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::>(); + + CommittedInstance { cmE, u, cmW, x } } pub fn prove_commitments( @@ -241,101 +239,19 @@ where #[cfg(test)] pub mod tests { use super::*; - use crate::transcript::poseidon::poseidon_canonical_config; - use ark_crypto_primitives::sponge::{poseidon::PoseidonSponge, CryptographicSponge}; - use ark_ff::{BigInteger, PrimeField}; + use ark_crypto_primitives::sponge::poseidon::PoseidonSponge; use ark_pallas::{Fr, Projective}; - use ark_std::{test_rng, UniformRand}; - use crate::arith::{ - r1cs::tests::{get_test_r1cs, get_test_z}, - Arith, - }; + use crate::arith::{r1cs::tests::get_test_r1cs, Arith}; use crate::commitment::pedersen::Pedersen; - use crate::folding::nova::traits::NIFSTrait; + use crate::folding::nova::nifs::tests::test_nifs_opt; #[test] fn test_nifs_nova() { - let (W, U) = test_nifs_opt::>>(); + let (W, U) = test_nifs_opt::, PoseidonSponge>>(); // check the last folded instance relation let r1cs = get_test_r1cs(); r1cs.check_relation(&W, &U).unwrap(); } - - /// 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); - - let mut rng = ark_std::test_rng(); - let (pedersen_params, _) = Pedersen::::setup(&mut rng, r1cs.A.n_cols).unwrap(); - - let poseidon_config = poseidon_canonical_config::(); - let mut transcript = PoseidonSponge::::new(&poseidon_config); - let pp_hash = Fr::rand(&mut rng); - - // 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_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 (aux_p, aux_v) = N::compute_aux( - &pedersen_params, - &r1cs, - &running_witness, - &running_committed_instance, - &incoming_witness, - &incoming_committed_instance, - ) - .unwrap(); - - 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_witness, - &running_committed_instance, - &incoming_witness, - &incoming_committed_instance, - &aux_p, - &aux_v, - ) - .unwrap(); - - // NIFS.V - let folded_committed_instance = N::verify( - r, - &running_committed_instance, - &incoming_committed_instance, - &aux_v, - ); - - // set running_instance for next loop iteration - 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/nifs/ova.rs similarity index 79% rename from folding-schemes/src/folding/nova/ova.rs rename to folding-schemes/src/folding/nova/nifs/ova.rs index 95bd91ac..f45df1c0 100644 --- a/folding-schemes/src/folding/nova/ova.rs +++ b/folding-schemes/src/folding/nova/nifs/ova.rs @@ -1,19 +1,18 @@ /// 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. +/// [Ova](https://hackmd.io/V4838nnlRKal9ZiTHiGYzw) NIFS (Non-Interactive Folding Scheme). use ark_crypto_primitives::sponge::Absorb; use ark_ec::{CurveGroup, Group}; -use ark_ff::PrimeField; +use ark_ff::{BigInteger, 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 super::NIFSTrait; use crate::arith::r1cs::R1CS; use crate::commitment::CommitmentScheme; +use crate::folding::nova::circuits::ChallengeGadget; 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}; @@ -51,7 +50,6 @@ where } } -// #[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)] @@ -103,13 +101,19 @@ impl Dummy<&R1CS>> for Witness { } /// Implements the NIFS (Non-Interactive Folding Scheme) trait for Ova. -pub struct NIFS, const H: bool = false> { +pub struct NIFS< + C: CurveGroup, + CS: CommitmentScheme, + T: Transcript, + const H: bool = false, +> { _c: PhantomData, _cp: PhantomData, + _t: PhantomData, } -impl, const H: bool> NIFSTrait - for NIFS +impl, T: Transcript, const H: bool> + NIFSTrait for NIFS where ::ScalarField: Absorb, ::BaseField: PrimeField, @@ -117,15 +121,16 @@ where type CommittedInstance = CommittedInstance; type Witness = Witness; type ProverAux = (); - type VerifierAux = (); + type Proof = (); fn new_witness(w: Vec, _e_len: usize, rng: impl RngCore) -> Self::Witness { Witness::new::(w, rng) } fn new_instance( - W: &Self::Witness, + _rng: impl RngCore, params: &CS::ProverParams, + W: &Self::Witness, x: Vec, aux: Vec, // t_or_e ) -> Result { @@ -149,40 +154,55 @@ where Ok(Self::Witness { w, rW }) } - fn compute_aux( + fn prove( _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 + pp_hash: C::ScalarField, + W_i: &Self::Witness, U_i: &Self::CommittedInstance, + w_i: &Self::Witness, 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 - ) - } + ) -> Result< + ( + Self::Witness, + Self::CommittedInstance, + Self::Proof, + Vec, + ), + Error, + > { + let mut transcript_v = transcript.clone(); - // Notice: `prove` method is implemented at the trait level. + let r_bits = ChallengeGadget::::get_challenge_native( + transcript, pp_hash, U_i, u_i, None, // cmT not used in Ova + ); + let r_Fr = C::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits)) + .ok_or(Error::OutOfBounds)?; + + let w = Self::fold_witness(r_Fr, W_i, w_i, &())?; + + let (ci, _r_bits_v) = Self::verify(&mut transcript_v, pp_hash, U_i, u_i, &())?; + #[cfg(test)] + assert_eq!(_r_bits_v, r_bits); + + Ok((w, ci, (), r_bits)) + } fn verify( - // r comes from the transcript, and is a n-bit (N_BITS_CHALLENGE) element - r: C::ScalarField, + transcript: &mut T, + pp_hash: 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 + _proof: &Self::Proof, // unused in Ova + ) -> Result<(Self::CommittedInstance, Vec), Error> { + let r_bits = ChallengeGadget::::get_challenge_native( + transcript, pp_hash, U_i, u_i, None, // cmT not used in Ova + ); + let r = C::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits)) + .ok_or(Error::OutOfBounds)?; + + // 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 @@ -192,7 +212,7 @@ where .map(|(a, b)| *a + (r * b)) .collect::>(); - Self::CommittedInstance { cmWE, u, x } + Ok((Self::CommittedInstance { cmWE, u, x }, r_bits)) } } @@ -224,6 +244,7 @@ pub mod tests { use crate::arith::{r1cs::tests::get_test_r1cs, Arith}; use crate::commitment::pedersen::Pedersen; use crate::folding::nova::nifs::tests::test_nifs_opt; + use ark_crypto_primitives::sponge::poseidon::PoseidonSponge; // Simple auxiliary structure mainly used to help pass a witness for which we can check // easily an R1CS relation. @@ -257,7 +278,7 @@ pub mod tests { #[test] fn test_nifs_ova() { - let (W, U) = test_nifs_opt::>>(); + let (W, U) = test_nifs_opt::, PoseidonSponge>>(); // check the last folded instance relation let r1cs = get_test_r1cs(); diff --git a/folding-schemes/src/folding/mova/pointvsline.rs b/folding-schemes/src/folding/nova/nifs/pointvsline.rs similarity index 97% rename from folding-schemes/src/folding/mova/pointvsline.rs rename to folding-schemes/src/folding/nova/nifs/pointvsline.rs index 0a66d458..a41c7f64 100644 --- a/folding-schemes/src/folding/mova/pointvsline.rs +++ b/folding-schemes/src/folding/nova/nifs/pointvsline.rs @@ -1,14 +1,16 @@ -use crate::folding::mova::{CommittedInstance, Witness}; -use crate::transcript::Transcript; -use crate::utils::mle::dense_vec_to_dense_mle; -use crate::Error; use ark_crypto_primitives::sponge::Absorb; use ark_ec::{CurveGroup, Group}; use ark_ff::{One, PrimeField}; use ark_poly::univariate::DensePolynomial; use ark_poly::{DenseMultilinearExtension, DenseUVPolynomial, Polynomial}; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use ark_std::{log2, Zero}; +use super::mova::{CommittedInstance, Witness}; +use crate::transcript::Transcript; +use crate::utils::mle::dense_vec_to_dense_mle; +use crate::Error; + /// Implements the Points vs Line as described in /// [Mova](https://eprint.iacr.org/2024/1220.pdf) and Section 4.5.2 from Thaler’s book @@ -18,9 +20,8 @@ pub struct PointvsLineEvaluationClaim { pub mleE2_prime: C::ScalarField, pub rE_prime: Vec, } - /// Proof from step 1 protocol 6 -#[derive(Clone, Debug)] +#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] pub struct PointVsLineProof { pub h1: DensePolynomial, pub h2: DensePolynomial, @@ -38,7 +39,7 @@ where ::ScalarField: Absorb, { pub fn prove( - transcript: &mut impl Transcript, + transcript: &mut T, ci1: &CommittedInstance, ci2: &CommittedInstance, w1: &Witness, @@ -83,7 +84,7 @@ where } pub fn verify( - transcript: &mut impl Transcript, + transcript: &mut T, ci1: &CommittedInstance, ci2: &CommittedInstance, proof: &PointVsLineProof, diff --git a/folding-schemes/src/folding/nova/traits.rs b/folding-schemes/src/folding/nova/traits.rs index ce325f1a..8bf336ac 100644 --- a/folding-schemes/src/folding/nova/traits.rs +++ b/folding-schemes/src/folding/nova/traits.rs @@ -1,6 +1,4 @@ -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}; @@ -8,79 +6,8 @@ use crate::arith::ArithSampler; use crate::arith::{r1cs::R1CS, Arith}; use crate::commitment::CommitmentScheme; use crate::folding::circuits::CF1; -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`]. /// diff --git a/folding-schemes/src/folding/nova/zk.rs b/folding-schemes/src/folding/nova/zk.rs index 33f30cde..fe6cf1e4 100644 --- a/folding-schemes/src/folding/nova/zk.rs +++ b/folding-schemes/src/folding/nova/zk.rs @@ -30,8 +30,7 @@ /// paper). /// And the Use-case-2 would require a modified version of the Decider circuits. /// -use ark_crypto_primitives::sponge::CryptographicSponge; -use ark_ff::{BigInteger, PrimeField}; +use ark_ff::PrimeField; use ark_std::{One, Zero}; use crate::{ @@ -41,7 +40,7 @@ use crate::{ }; use ark_crypto_primitives::sponge::{ poseidon::{PoseidonConfig, PoseidonSponge}, - Absorb, + Absorb, CryptographicSponge, }; use ark_ec::{CurveGroup, Group}; use ark_r1cs_std::{ @@ -52,21 +51,16 @@ use ark_r1cs_std::{ use crate::{commitment::CommitmentScheme, folding::circuits::CF2, frontend::FCircuit, Error}; use super::{ - circuits::ChallengeGadget, nifs::NIFS, traits::NIFSTrait, CommittedInstance, Nova, Witness, + nifs::{nova::NIFS, 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 -pub struct FoldingProof { - cmT: C, -} - pub struct RandomizedIVCProof { pub U_i: CommittedInstance, pub u_i: CommittedInstance, pub U_r: CommittedInstance, - pub pi: FoldingProof, - pub pi_prime: FoldingProof, + pub pi: C1, // proof = cmT + pub pi_prime: C1, // proof' = cmT' pub W_i_prime: Witness, pub cf_U_i: CommittedInstance, pub cf_W_i: Witness, @@ -77,24 +71,6 @@ where ::ScalarField: Absorb, ::BaseField: PrimeField, { - /// Computes challenge required before folding instances - fn get_folding_challenge( - sponge: &mut PoseidonSponge, - pp_hash: C1::ScalarField, - U_i: CommittedInstance, - u_i: CommittedInstance, - cmT: C1, - ) -> Result { - 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) - } - /// Compute a zero-knowledge proof of a Nova IVC proof /// It implements the prover of appendix D.4.in https://eprint.iacr.org/2023/573.pdf /// For further details on why folding is hiding, see lemma 9 @@ -118,68 +94,45 @@ where GC2: ToConstraintFieldGadget<::BaseField>, C1: CurveGroup, { - let mut challenges_sponge = PoseidonSponge::::new(&nova.poseidon_config); + let mut transcript = PoseidonSponge::::new(&nova.poseidon_config); // I. Compute proof for 'regular' instances // 1. Fold the instance-witness pairs (U_i, W_i) with (u_i, w_i) - // a. Compute T - let (T, cmT) = NIFS::::compute_cmT( + let (W_f, U_f, cmT, _) = NIFS::, true>::prove( &nova.cs_pp, &nova.r1cs, + &mut transcript, + nova.pp_hash, &nova.w_i, &nova.u_i, &nova.W_i, &nova.U_i, )?; - // b. Compute folding challenge - let r = RandomizedIVCProof::::get_folding_challenge( - &mut challenges_sponge, - nova.pp_hash, - nova.U_i.clone(), - nova.u_i.clone(), - cmT, - )?; - - // c. Compute fold - 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 }; - // 2. Sample a satisfying relaxed R1CS instance-witness pair (W_r, U_r) let (W_r, U_r) = nova .r1cs .sample_witness_instance::(&nova.cs_pp, &mut rng)?; // 3. Fold the instance-witness pair (U_f, W_f) with (U_r, W_r) - // a. Compute T - let (T_i_prime, cmT_i_prime) = - NIFS::::compute_cmT(&nova.cs_pp, &nova.r1cs, &W_f, &U_f, &W_r, &U_r)?; - - // b. Compute folding challenge - let r_2 = RandomizedIVCProof::::get_folding_challenge( - &mut challenges_sponge, - nova.pp_hash, - U_f.clone(), - U_r.clone(), - cmT_i_prime, - )?; - - // c. Compute fold - 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 }; + let (W_i_prime, _, cmT_i_prime, _) = + NIFS::, true>::prove( + &nova.cs_pp, + &nova.r1cs, + &mut transcript, + nova.pp_hash, + &W_f, + &U_f, + &W_r, + &U_r, + )?; Ok(RandomizedIVCProof { U_i: nova.U_i.clone(), u_i: nova.u_i.clone(), U_r, - pi, - pi_prime, + pi: cmT, + pi_prime: cmT_i_prime, W_i_prime, cf_U_i: nova.cf_U_i.clone(), cf_W_i: nova.cf_W_i.clone(), @@ -228,7 +181,7 @@ where } // b. Check computed hashes are correct - let mut sponge = PoseidonSponge::::new(poseidon_config); + let sponge = PoseidonSponge::::new(poseidon_config); let expected_u_i_x = proof.U_i.hash(&sponge, pp_hash, i, &z_0, &z_i); if expected_u_i_x != proof.u_i.x[0] { return Err(Error::zkIVCVerificationFail); @@ -244,32 +197,25 @@ where return Err(Error::zkIVCVerificationFail); } + let mut transcript = PoseidonSponge::::new(poseidon_config); // 3. Obtain the U_f folded instance - // a. Compute folding challenge - let r = RandomizedIVCProof::::get_folding_challenge( - &mut sponge, + let (U_f, _) = NIFS::, true>::verify( + &mut transcript, pp_hash, - proof.U_i.clone(), - proof.u_i.clone(), - proof.pi.cmT, + &proof.u_i, + &proof.U_i, + &proof.pi, )?; - // b. Get the U_f instance - 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 - let r_2 = RandomizedIVCProof::::get_folding_challenge( - &mut sponge, + let (U_i_prime, _) = NIFS::, true>::verify( + &mut transcript, pp_hash, - U_f.clone(), - proof.U_r.clone(), - proof.pi_prime.cmT, + &U_f, + &proof.U_r, + &proof.pi_prime, )?; - // b. Compute fold - 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)?; From b2db660ba7a260c812c7d8f9bacc5233ab15546f Mon Sep 17 00:00:00 2001 From: arnaucube Date: Tue, 22 Oct 2024 22:30:14 +0200 Subject: [PATCH 7/8] adapt Nova's DeciderEth prepare_calldata & update examples to it --- examples/circom_full_flow.rs | 2 ++ examples/full_flow.rs | 2 ++ examples/noir_full_flow.rs | 2 ++ examples/noname_full_flow.rs | 2 ++ .../src/folding/nova/decider_eth.rs | 19 ++++++++++++++++++- 5 files changed, 26 insertions(+), 1 deletion(-) diff --git a/examples/circom_full_flow.rs b/examples/circom_full_flow.rs index dccf0d8e..31755c74 100644 --- a/examples/circom_full_flow.rs +++ b/examples/circom_full_flow.rs @@ -84,6 +84,7 @@ fn main() { // prepare the Nova prover & verifier params let nova_preprocess_params = PreprocessorParam::new(poseidon_config, f_circuit.clone()); let nova_params = N::preprocess(&mut rng, &nova_preprocess_params).unwrap(); + let pp_hash = nova_params.1.pp_hash().unwrap(); // initialize the folding scheme engine, in our case we use Nova let mut nova = N::init(&nova_params, f_circuit.clone(), z_0).unwrap(); @@ -131,6 +132,7 @@ fn main() { let calldata: Vec = prepare_calldata( function_selector, + pp_hash, nova.i, nova.z_0, nova.z_i, diff --git a/examples/full_flow.rs b/examples/full_flow.rs index bf1f28f3..7b586ea6 100644 --- a/examples/full_flow.rs +++ b/examples/full_flow.rs @@ -101,6 +101,7 @@ fn main() { // prepare the Nova prover & verifier params let nova_preprocess_params = PreprocessorParam::new(poseidon_config.clone(), f_circuit); let nova_params = N::preprocess(&mut rng, &nova_preprocess_params).unwrap(); + let pp_hash = nova_params.1.pp_hash().unwrap(); // initialize the folding scheme engine, in our case we use Nova let mut nova = N::init(&nova_params, f_circuit, z_0).unwrap(); @@ -138,6 +139,7 @@ fn main() { let calldata: Vec = prepare_calldata( function_selector, + pp_hash, nova.i, nova.z_0, nova.z_i, diff --git a/examples/noir_full_flow.rs b/examples/noir_full_flow.rs index 64531098..30f2e89e 100644 --- a/examples/noir_full_flow.rs +++ b/examples/noir_full_flow.rs @@ -74,6 +74,7 @@ fn main() { // prepare the Nova prover & verifier params let nova_preprocess_params = PreprocessorParam::new(poseidon_config, f_circuit.clone()); let nova_params = N::preprocess(&mut rng, &nova_preprocess_params).unwrap(); + let pp_hash = nova_params.1.pp_hash().unwrap(); // initialize the folding scheme engine, in our case we use Nova let mut nova = N::init(&nova_params, f_circuit.clone(), z_0).unwrap(); @@ -119,6 +120,7 @@ fn main() { let calldata: Vec = prepare_calldata( function_selector, + pp_hash, nova.i, nova.z_0, nova.z_i, diff --git a/examples/noname_full_flow.rs b/examples/noname_full_flow.rs index 00dccbfb..0a6c0d4a 100644 --- a/examples/noname_full_flow.rs +++ b/examples/noname_full_flow.rs @@ -84,6 +84,7 @@ fn main() { // prepare the Nova prover & verifier params let nova_preprocess_params = PreprocessorParam::new(poseidon_config, f_circuit.clone()); let nova_params = N::preprocess(&mut rng, &nova_preprocess_params).unwrap(); + let pp_hash = nova_params.1.pp_hash().unwrap(); // initialize the folding scheme engine, in our case we use Nova let mut nova = N::init(&nova_params, f_circuit.clone(), z_0).unwrap(); @@ -131,6 +132,7 @@ fn main() { let calldata: Vec = prepare_calldata( function_selector, + pp_hash, nova.i, nova.z_0, nova.z_i, diff --git a/folding-schemes/src/folding/nova/decider_eth.rs b/folding-schemes/src/folding/nova/decider_eth.rs index 4e4719e5..d17b3fe3 100644 --- a/folding-schemes/src/folding/nova/decider_eth.rs +++ b/folding-schemes/src/folding/nova/decider_eth.rs @@ -25,6 +25,7 @@ use crate::commitment::{ CommitmentScheme, }; use crate::folding::circuits::{nonnative::affine::NonNativeAffineVar, CF2}; +use crate::folding::nova::circuits::ChallengeGadget; use crate::frontend::FCircuit; use crate::transcript::poseidon::poseidon_canonical_config; use crate::Error; @@ -277,14 +278,30 @@ where #[allow(clippy::too_many_arguments)] pub fn prepare_calldata( function_signature_check: [u8; 4], + pp_hash: ark_bn254::Fr, i: ark_bn254::Fr, z_0: Vec, z_i: Vec, - r: ark_bn254::Fr, running_instance: &CommittedInstance, incoming_instance: &CommittedInstance, proof: Proof, Groth16>, ) -> Result, Error> { + // compute the challenge r + let poseidon_config = poseidon_canonical_config::(); + let mut transcript = PoseidonSponge::::new(&poseidon_config); + let r_bits = ChallengeGadget::< + ark_bn254::G1Projective, + CommittedInstance, + >::get_challenge_native( + &mut transcript, + pp_hash, + running_instance, + incoming_instance, + Some(&proof.cmT), + ); + let r = + ark_bn254::Fr::from_bigint(BigInteger::from_bits_le(&r_bits)).ok_or(Error::OutOfBounds)?; + Ok(vec![ function_signature_check.to_vec(), i.into_bigint().to_bytes_be(), // i From eb45123ce4c7a7cadf72ca0753613fd3913277f9 Mon Sep 17 00:00:00 2001 From: arnaucube Date: Tue, 22 Oct 2024 22:49:37 +0200 Subject: [PATCH 8/8] small update to fix solidity tests --- solidity-verifiers/src/verifiers/nova_cyclefold.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/solidity-verifiers/src/verifiers/nova_cyclefold.rs b/solidity-verifiers/src/verifiers/nova_cyclefold.rs index e82bd1f3..86e70987 100644 --- a/solidity-verifiers/src/verifiers/nova_cyclefold.rs +++ b/solidity-verifiers/src/verifiers/nova_cyclefold.rs @@ -366,6 +366,7 @@ mod tests { n_steps: usize, ) { let (decider_pp, decider_vp) = decider_params; + let pp_hash = fs_params.1.pp_hash().unwrap(); let f_circuit = FC::new(()).unwrap(); @@ -400,6 +401,7 @@ mod tests { let calldata: Vec = prepare_calldata( function_selector, + pp_hash, nova.i, nova.z_0, nova.z_i,