Browse Source

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>
update-nifs-interface
Nick Dimitriou 1 month ago
parent
commit
637dc3c596
5 changed files with 908 additions and 0 deletions
  1. +1
    -0
      folding-schemes/src/folding/mod.rs
  2. +117
    -0
      folding-schemes/src/folding/mova/mod.rs
  3. +459
    -0
      folding-schemes/src/folding/mova/nifs.rs
  4. +280
    -0
      folding-schemes/src/folding/mova/pointvsline.rs
  5. +51
    -0
      folding-schemes/src/folding/mova/traits.rs

+ 1
- 0
folding-schemes/src/folding/mod.rs

@ -1,4 +1,5 @@
pub mod circuits; pub mod circuits;
pub mod hypernova; pub mod hypernova;
pub mod mova;
pub mod nova; pub mod nova;
pub mod protogalaxy; pub mod protogalaxy;

+ 117
- 0
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<C: CurveGroup> {
// Random evaluation point for the E
pub rE: Vec<C::ScalarField>,
// MLE of E
pub mleE: C::ScalarField,
pub u: C::ScalarField,
pub cmW: C,
pub x: Vec<C::ScalarField>,
}
#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)]
pub struct Witness<C: CurveGroup> {
pub E: Vec<C::ScalarField>,
pub W: Vec<C::ScalarField>,
pub rW: C::ScalarField,
}
#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)]
pub struct InstanceWitness<C: CurveGroup> {
pub ci: CommittedInstance<C>,
pub w: Witness<C>,
}
impl<C: CurveGroup> Witness<C>
where
<C as Group>::ScalarField: Absorb,
{
pub fn new<const H: bool>(w: Vec<C::ScalarField>, 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<CS: CommitmentScheme<C>>(
&self,
params: &CS::ProverParams,
x: Vec<C::ScalarField>,
rE: Vec<C::ScalarField>,
) -> Result<CommittedInstance<C>, Error> {
let mut mleE = C::ScalarField::zero();
if !is_zero_vec::<C::ScalarField>(&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<C: CurveGroup> Absorb for CommittedInstance<C>
where
C::ScalarField: Absorb,
{
fn to_sponge_bytes(&self, _dest: &mut Vec<u8>) {
// This is never called
unimplemented!()
}
fn to_sponge_field_elements<F: PrimeField>(&self, dest: &mut Vec<F>) {
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);
}
}

+ 459
- 0
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<C, H>,
T: Transcript<C::ScalarField>,
const H: bool = false,
> {
_c: PhantomData<C>,
_cp: PhantomData<CS>,
_ct: PhantomData<T>,
}
pub struct Proof<C: CurveGroup> {
pub h_proof: PointVsLineProof<C>,
pub mleE1_prime: C::ScalarField,
pub mleE2_prime: C::ScalarField,
pub mleT: C::ScalarField,
}
impl<C: CurveGroup, CS: CommitmentScheme<C, H>, T: Transcript<C::ScalarField>, const H: bool>
NIFS<C, CS, T, H>
where
<C as Group>::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<C::ScalarField>,
u1: C::ScalarField,
u2: C::ScalarField,
z1: &[C::ScalarField],
z2: &[C::ScalarField],
) -> Result<Vec<C::ScalarField>, Error> {
Nova::<C, CS, H>::compute_T(r1cs, u1, u2, z1, z2)
}
// Protocol 7 - point 3 (16)
pub fn fold_witness(
a: C::ScalarField,
w1: &Witness<C>,
w2: &Witness<C>,
T: &[C::ScalarField],
) -> Result<Witness<C>, Error> {
let a2 = a * a;
let E: Vec<C::ScalarField> = vec_add(
&vec_add(&w1.E, &vec_scalar_mul(T, &a))?,
&vec_scalar_mul(&w2.E, &a2),
)?;
let W: Vec<C::ScalarField> =
w1.W.iter()
.zip(&w2.W)
.map(|(i1, i2)| *i1 + (a * i2))
.collect();
let rW = w1.rW + a * w2.rW;
Ok(Witness::<C> { E, W, rW })
}
// Protocol 7 - point 3 (15)
pub fn fold_committed_instance(
a: C::ScalarField,
ci1: &CommittedInstance<C>,
ci2: &CommittedInstance<C>,
rE_prime: &[C::ScalarField],
mleE1_prime: &C::ScalarField,
mleE2_prime: &C::ScalarField,
mleT: &C::ScalarField,
) -> Result<CommittedInstance<C>, 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::<Vec<C::ScalarField>>();
Ok(CommittedInstance::<C> {
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<C::ScalarField>,
transcript: &mut impl Transcript<C::ScalarField>,
ci1: &CommittedInstance<C>,
ci2: &CommittedInstance<C>,
w1: &Witness<C>,
w2: &Witness<C>,
) -> Result<(Proof<C>, InstanceWitness<C>), 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::<C, T>::prove(transcript, ci1, ci2, w1, w2)?;
// Protocol 7
transcript.absorb(&mleE1_prime);
transcript.absorb(&mleE2_prime);
// Remember Z = (W, x, u)
let z1: Vec<C::ScalarField> = [vec![ci1.u], ci1.x.to_vec(), w1.W.to_vec()].concat();
let z2: Vec<C::ScalarField> = [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::<C> {
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<C::ScalarField>,
ci1: &CommittedInstance<C>,
ci2: &CommittedInstance<C>,
proof: &Proof<C>,
) -> Result<CommittedInstance<C>, Error> {
transcript.absorb(ci1);
transcript.absorb(ci2);
let rE_prime = PointVsLine::<C, T>::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<C>() -> (
PedersenParams<C>,
PoseidonConfig<C::ScalarField>,
R1CS<C::ScalarField>,
Witness<C>, // w1
CommittedInstance<C>, // ci1
Witness<C>, // w2
CommittedInstance<C>, // ci2
Proof<C>, // pt-vs-line
InstanceWitness<C>, // w3, ci3
)
where
C: CurveGroup,
<C as 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::<C>::new::<false>(w1.clone(), r1cs.A.n_rows, &mut rng);
let w2 = Witness::<C>::new::<false>(w2.clone(), r1cs.A.n_rows, &mut rng);
let (pedersen_params, _) = Pedersen::<C>::setup(&mut rng, r1cs.A.n_cols).unwrap();
// compute committed instances
let rE_1: Vec<C::ScalarField> = (0..log2(3))
.map(|_| C::ScalarField::rand(&mut rng))
.collect();
let rE_2: Vec<C::ScalarField> = (0..log2(4))
.map(|_| C::ScalarField::rand(&mut rng))
.collect();
let ci1 = w1
.commit::<Pedersen<C>>(&pedersen_params, x1.clone(), rE_1)
.unwrap();
let ci2 = w2
.commit::<Pedersen<C>>(&pedersen_params, x2.clone(), rE_2)
.unwrap();
let poseidon_config = poseidon_canonical_config::<C::ScalarField>();
let mut transcript_p = PoseidonSponge::<C::ScalarField>::new(&poseidon_config);
let result = NIFS::<C, Pedersen<C>, PoseidonSponge<C::ScalarField>>::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::<Fr>();
let z1 = get_test_z(3);
let (w1, x1) = r1cs.split_z(&z1);
let mut rng = ark_std::test_rng();
let (pedersen_params, _) = Pedersen::<Projective>::setup(&mut rng, r1cs.A.n_cols).unwrap();
// dummy instance, witness and public inputs zeroes
let w_dummy = Witness::<Projective>::dummy(w1.len(), r1cs.A.n_rows);
let mut u_dummy = w_dummy
.commit::<Pedersen<Projective>>(
&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::<ark_pallas::Fr>();
let mut transcript_p: PoseidonSponge<Fr> = PoseidonSponge::<Fr>::new(&poseidon_config);
let result = NIFS::<Projective, Pedersen<Projective>, PoseidonSponge<Fr>>::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::<Projective>();
// NIFS.V
let mut transcript_v: PoseidonSponge<Fr> = PoseidonSponge::<Fr>::new(&poseidon_config);
let ci3 = NIFS::<Projective, Pedersen<Projective>, PoseidonSponge<Fr>>::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<Projective>>(&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::<Projective>::setup(&mut rng, r1cs.A.n_cols).unwrap();
// prepare the running instance
let rE: Vec<Fr> = (0..log2(3)).map(|_| Fr::rand(&mut rng)).collect();
let mut running_instance_w =
Witness::<Projective>::new::<false>(w.clone(), r1cs.A.n_rows, test_rng());
let mut running_committed_instance = running_instance_w
.commit::<Pedersen<Projective>>(&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::<Projective>::new::<false>(w.clone(), r1cs.A.n_rows, test_rng());
let rE: Vec<Fr> = (0..log2(3)).map(|_| Fr::rand(&mut rng)).collect();
let incoming_committed_instance = incoming_instance_w
.commit::<Pedersen<Projective>>(&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::<Fr>();
let mut transcript_p = PoseidonSponge::<Fr>::new(&poseidon_config);
let result = NIFS::<Projective, Pedersen<Projective>, PoseidonSponge<Fr>>::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<Fr> = PoseidonSponge::<Fr>::new(&poseidon_config);
let _ci3 = NIFS::<Projective, Pedersen<Projective>, PoseidonSponge<Fr>>::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;
}
}
}

+ 280
- 0
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<C: CurveGroup> {
pub mleE1_prime: C::ScalarField,
pub mleE2_prime: C::ScalarField,
pub rE_prime: Vec<C::ScalarField>,
}
/// Proof from step 1 protocol 6
#[derive(Clone, Debug)]
pub struct PointVsLineProof<C: CurveGroup> {
pub h1: DensePolynomial<C::ScalarField>,
pub h2: DensePolynomial<C::ScalarField>,
}
#[derive(Clone, Debug, Default)]
pub struct PointVsLine<C: CurveGroup, T: Transcript<C::ScalarField>> {
_phantom_C: std::marker::PhantomData<C>,
_phantom_T: std::marker::PhantomData<T>,
}
/// Protocol 6 from Mova
impl<C: CurveGroup, T: Transcript<C::ScalarField>> PointVsLine<C, T>
where
<C as Group>::ScalarField: Absorb,
{
pub fn prove(
transcript: &mut impl Transcript<C::ScalarField>,
ci1: &CommittedInstance<C>,
ci2: &CommittedInstance<C>,
w1: &Witness<C>,
w2: &Witness<C>,
) -> Result<(PointVsLineProof<C>, PointvsLineEvaluationClaim<C>), 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<<C>::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<C::ScalarField>,
ci1: &CommittedInstance<C>,
ci2: &CommittedInstance<C>,
proof: &PointVsLineProof<C>,
mleE1_prime: &<C>::ScalarField,
mleE2_prime: &<C>::ScalarField,
) -> Result<
Vec<<C>::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<<C>::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<F: PrimeField>(
mle: &DenseMultilinearExtension<F>,
r1: &[F],
r1_sub_r2: &[F],
) -> Result<DensePolynomial<F>, 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<DensePolynomial<F>> = 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<F: PrimeField>(r1: &[F], r1_sub_r2: &[F], x: F) -> Result<Vec<F>, 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<Fq> = 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<Fq> = 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<Fq> = 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<Fq> = 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<Fq> = 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);
}
}

+ 51
- 0
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<C: CurveGroup> {
/// checks the R1CS relation (un-relaxed) for the given Witness and CommittedInstance.
fn check_instance_relation(
&self,
W: &Witness<C>,
U: &CommittedInstance<C>,
) -> 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<C>,
U: &CommittedInstance<C>,
) -> Result<(), Error>;
}
impl<C: CurveGroup> MovaR1CS<C> for R1CS<C::ScalarField>
where
<C as Group>::ScalarField: Absorb,
<C as CurveGroup>::BaseField: ark_ff::PrimeField,
{
fn check_instance_relation(
&self,
_W: &Witness<C>,
_U: &CommittedInstance<C>,
) -> Result<(), Error> {
// This is never called
unimplemented!()
}
fn check_relaxed_instance_relation(
&self,
W: &Witness<C>,
U: &CommittedInstance<C>,
) -> Result<(), Error> {
let mut rel_r1cs = self.clone().relax();
rel_r1cs.u = U.u;
rel_r1cs.E = W.E.clone();
let Z: Vec<C::ScalarField> = [vec![U.u], U.x.to_vec(), W.W.to_vec()].concat();
rel_r1cs.check_relation(&Z)
}
}

Loading…
Cancel
Save