From 637dc3c5965bd51675a4b6aec6f46d40f7d5fcd3 Mon Sep 17 00:00:00 2001 From: Nick Dimitriou Date: Thu, 19 Sep 2024 17:18:09 +0100 Subject: [PATCH] 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 8f76a71..7964fdc 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 0000000..11b6836 --- /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 0000000..b2db3fd --- /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 0000000..72e7dcb --- /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 0000000..939be1e --- /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) + } +}