Browse Source

Merge pull request #1 from arnaucube/update-nifs-interface

Refactor NIFSTrait & port Mova impl to it
adapt-preparecalldata
Nick Dimitriou 3 weeks ago
committed by GitHub
parent
commit
b291a14b50
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
18 changed files with 939 additions and 1096 deletions
  1. +97
    -17
      folding-schemes/src/folding/circuits/cyclefold.rs
  2. +0
    -1
      folding-schemes/src/folding/mod.rs
  3. +0
    -134
      folding-schemes/src/folding/mova/mod.rs
  4. +0
    -440
      folding-schemes/src/folding/mova/nifs.rs
  5. +0
    -28
      folding-schemes/src/folding/mova/traits.rs
  6. +13
    -4
      folding-schemes/src/folding/nova/circuits.rs
  7. +18
    -7
      folding-schemes/src/folding/nova/decider.rs
  8. +6
    -14
      folding-schemes/src/folding/nova/decider_circuits.rs
  9. +21
    -9
      folding-schemes/src/folding/nova/decider_eth.rs
  10. +5
    -15
      folding-schemes/src/folding/nova/decider_eth_circuit.rs
  11. +30
    -52
      folding-schemes/src/folding/nova/mod.rs
  12. +152
    -0
      folding-schemes/src/folding/nova/nifs/mod.rs
  13. +411
    -0
      folding-schemes/src/folding/nova/nifs/mova.rs
  14. +84
    -168
      folding-schemes/src/folding/nova/nifs/nova.rs
  15. +58
    -37
      folding-schemes/src/folding/nova/nifs/ova.rs
  16. +9
    -8
      folding-schemes/src/folding/nova/nifs/pointvsline.rs
  17. +0
    -73
      folding-schemes/src/folding/nova/traits.rs
  18. +35
    -89
      folding-schemes/src/folding/nova/zk.rs

+ 97
- 17
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::arith::r1cs::{extract_w_x, R1CS};
use crate::commitment::CommitmentScheme; use crate::commitment::CommitmentScheme;
use crate::constants::NOVA_N_BITS_RO; 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::transcript::{AbsorbNonNative, AbsorbNonNativeGadget, Transcript, TranscriptVar};
use crate::Error; use crate::Error;
use ark_crypto_primitives::sponge::poseidon::PoseidonSponge;
/// Re-export the Nova committed instance as `CycleFoldCommittedInstance` and /// Re-export the Nova committed instance as `CycleFoldCommittedInstance` and
/// witness as `CycleFoldWitness`, for clarity and consistency /// 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<C2, CF2<C2>> + ToConstraintFieldGadget<CF2<C2>>,
CS2: CommitmentScheme<C2, H>,
const H: bool = false,
> where
<C1 as CurveGroup>::BaseField: PrimeField,
<C2 as CurveGroup>::BaseField: PrimeField,
for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
{
_c1: PhantomData<C1>,
_c2: PhantomData<C2>,
_gc2: PhantomData<GC2>,
_cs: PhantomData<CS2>,
}
impl<C1: CurveGroup, C2: CurveGroup, GC2, CS2: CommitmentScheme<C2, H>, const H: bool>
CycleFoldNIFS<C1, C2, GC2, CS2, H>
where
<C1 as CurveGroup>::BaseField: PrimeField,
<C2 as CurveGroup>::BaseField: PrimeField,
<C1 as Group>::ScalarField: Absorb,
<C2 as Group>::ScalarField: Absorb,
C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<CF2<C2>>,
for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
{
fn prove(
cf_r_Fq: C2::ScalarField, // C2::Fr==C1::Fq
cf_W_i: &CycleFoldWitness<C2>,
cf_U_i: &CycleFoldCommittedInstance<C2>,
cf_w_i: &CycleFoldWitness<C2>,
cf_u_i: &CycleFoldCommittedInstance<C2>,
aux_p: &[C2::ScalarField], // = cf_T
aux_v: C2, // = cf_cmT
) -> Result<(CycleFoldWitness<C2>, CycleFoldCommittedInstance<C2>), Error> {
let w = NIFS::<C2, CS2, PoseidonSponge<C2::ScalarField>, 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<C2>,
u_i: &CycleFoldCommittedInstance<C2>,
cmT: &C2, // VerifierAux
) -> Result<CycleFoldCommittedInstance<C2>, Error> {
Ok(
NIFS::<C2, CS2, PoseidonSponge<C2::ScalarField>, 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 /// 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. /// scheme struct because it is used both by Nova & HyperNova's CycleFold.
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
@ -551,14 +618,15 @@ where
cf_w_i.commit::<CS2, H>(&cf_cs_params, cf_x_i.clone())?; cf_w_i.commit::<CS2, H>(&cf_cs_params, cf_x_i.clone())?;
// compute T* and cmT* for CycleFoldCircuit // compute T* and cmT* for CycleFoldCircuit
let (cf_T, cf_cmT) = NIFS::<C2, CS2, H>::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::<C2, CS2, PoseidonSponge<C2::ScalarField>, 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::<C2, GC2>::get_challenge_native( let cf_r_bits = CycleFoldChallengeGadget::<C2, GC2>::get_challenge_native(
transcript, transcript,
@ -570,8 +638,11 @@ where
let cf_r_Fq = C1::BaseField::from_bigint(BigInteger::from_bits_le(&cf_r_bits)) let cf_r_Fq = C1::BaseField::from_bigint(BigInteger::from_bits_le(&cf_r_bits))
.expect("cf_r_bits out of bounds"); .expect("cf_r_bits out of bounds");
let (cf_W_i1, cf_U_i1) =
NIFS::<C2, CS2, H>::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::<C1, C2, GC2, CS2, H>::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)) 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() { fn test_nifs_full_gadget() {
let mut rng = ark_std::test_rng(); let mut rng = ark_std::test_rng();
let poseidon_config = poseidon_canonical_config::<Fr>();
let mut transcript_v = PoseidonSponge::<Fr>::new(&poseidon_config);
let pp_hash = Fr::rand(&mut rng);
// prepare the committed instances to test in-circuit // prepare the committed instances to test in-circuit
let ci: Vec<CommittedInstance<Projective>> = (0..2) let ci: Vec<CommittedInstance<Projective>> = (0..2)
.into_iter() .into_iter()
@ -685,11 +760,16 @@ pub mod tests {
// make the 2nd instance a 'fresh' instance (ie. cmE=0, u=1) // make the 2nd instance a 'fresh' instance (ie. cmE=0, u=1)
ci2.cmE = Projective::zero(); ci2.cmE = Projective::zero();
ci2.u = Fr::one(); ci2.u = Fr::one();
let r_bits: Vec<bool> =
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::<Projective, Pedersen<Projective>>::verify(r_Fr, &ci1, &ci2, &cmT);
let cmT = Projective::rand(&mut rng); // random only for testing
let (ci3, r_bits) = NIFS::<Projective, Pedersen<Projective>, PoseidonSponge<Fr>>::verify(
&mut transcript_v,
pp_hash,
&ci1,
&ci2,
&cmT,
)
.unwrap();
let cs = ConstraintSystem::<Fq>::new_ref(); let cs = ConstraintSystem::<Fq>::new_ref();
let r_bitsVar = Vec::<Boolean<Fq>>::new_witness(cs.clone(), || Ok(r_bits)).unwrap(); let r_bitsVar = Vec::<Boolean<Fq>>::new_witness(cs.clone(), || Ok(r_bits)).unwrap();
@ -737,7 +817,7 @@ pub mod tests {
.take(TestCycleFoldConfig::<Projective, 2>::IO_LEN) .take(TestCycleFoldConfig::<Projective, 2>::IO_LEN)
.collect(), .collect(),
}; };
let cmT = Projective::rand(&mut rng);
let cmT = Projective::rand(&mut rng); // random only for testing
// compute the challenge natively // compute the challenge natively
let pp_hash = Fq::from(42u32); // only for test let pp_hash = Fq::from(42u32); // only for test

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

@ -1,6 +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;
pub mod traits; pub mod traits;

+ 0
- 134
folding-schemes/src/folding/mova/mod.rs

@ -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<C: CurveGroup> {
// Random evaluation point for the E
pub rE: Vec<C::ScalarField>,
// Evaluation of the MLE of E at r_E
pub mleE: C::ScalarField,
pub u: C::ScalarField,
pub cmW: C,
pub x: Vec<C::ScalarField>,
}
/// 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<C: CurveGroup> {
pub E: Vec<C::ScalarField>,
pub W: Vec<C::ScalarField>,
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<C: CurveGroup> {
pub ci: CommittedInstance<C>,
pub w: Witness<C>,
}
impl<C: CurveGroup> Witness<C> {
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 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> Dummy<&R1CS<CF1<C>>> for Witness<C> {
fn dummy(r1cs: &R1CS<CF1<C>>) -> 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<C: CurveGroup> Dummy<usize> for CommittedInstance<C> {
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<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);
}
}

+ 0
- 440
folding-schemes/src/folding/mova/nifs.rs

@ -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<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,
<C as CurveGroup>::BaseField: PrimeField,
{
// 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 a_squared = a * a;
let E: Vec<C::ScalarField> = vec_add(
&vec_add(&w1.E, &vec_scalar_mul(T, &a))?,
&vec_scalar_mul(&w2.E, &a_squared),
)?;
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: Vec<C::ScalarField>,
mleE1_prime: &C::ScalarField,
mleE2_prime: &C::ScalarField,
mleT: &C::ScalarField,
) -> Result<CommittedInstance<C>, 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::<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 = NovaNIFS::<C, CS, H>::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::<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 both the folding and the pt-vs-line proofs.
/// 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: 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<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(&r1cs);
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_relation(&w_i, &u_i).unwrap();
r1cs.check_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_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_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<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_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_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_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;
}
}
}

+ 0
- 28
folding-schemes/src/folding/mova/traits.rs

@ -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<C: CurveGroup> Arith<Witness<C>, CommittedInstance<C>> for R1CS<CF1<C>> {
type Evaluation = Vec<CF1<C>>;
fn eval_relation(
&self,
w: &Witness<C>,
u: &CommittedInstance<C>,
) -> Result<Self::Evaluation, Error> {
self.eval_at_z(&[&[u.u][..], &u.x, &w.W].concat())
}
fn check_evaluation(
w: &Witness<C>,
_u: &CommittedInstance<C>,
e: Self::Evaluation,
) -> Result<(), Error> {
(w.E == e).then_some(()).ok_or(Error::NotSatisfied)
}
}

+ 13
- 4
folding-schemes/src/folding/nova/circuits.rs

@ -529,8 +529,7 @@ pub mod tests {
use ark_std::UniformRand; use ark_std::UniformRand;
use crate::commitment::pedersen::Pedersen; 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::folding::traits::CommittedInstanceOps;
use crate::transcript::poseidon::poseidon_canonical_config; use crate::transcript::poseidon::poseidon_canonical_config;
@ -570,9 +569,19 @@ pub mod tests {
}) })
.collect(); .collect();
let (ci1, ci2) = (ci[0].clone(), ci[1].clone()); 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 cmT = Projective::rand(&mut rng);
let ci3 = NIFS::<Projective, Pedersen<Projective>>::verify(r_Fr, &ci1, &ci2, &cmT);
let poseidon_config = poseidon_canonical_config::<Fr>();
let mut transcript = PoseidonSponge::<Fr>::new(&poseidon_config);
let (ci3, r_bits) = NIFS::<Projective, Pedersen<Projective>, PoseidonSponge<Fr>>::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::<Fr>::new_ref(); let cs = ConstraintSystem::<Fr>::new_ref();

+ 18
- 7
folding-schemes/src/folding/nova/decider.rs

@ -2,7 +2,7 @@
/// DeciderEth from decider_eth.rs file. /// DeciderEth from decider_eth.rs file.
/// More details can be found at the documentation page: /// More details can be found at the documentation page:
/// https://privacy-scaling-explorations.github.io/sonobe-docs/design/nova-decider-offchain.html /// 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_ec::{AffineRepr, CurveGroup, Group};
use ark_ff::{BigInteger, PrimeField}; use ark_ff::{BigInteger, PrimeField};
use ark_r1cs_std::{groups::GroupOpsBounds, prelude::CurveVar, ToConstraintFieldGadget}; use ark_r1cs_std::{groups::GroupOpsBounds, prelude::CurveVar, ToConstraintFieldGadget};
@ -13,7 +13,10 @@ use ark_std::{One, Zero};
use core::marker::PhantomData; use core::marker::PhantomData;
use super::decider_circuits::{DeciderCircuit1, DeciderCircuit2}; 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::commitment::CommitmentScheme;
use crate::folding::circuits::{ use crate::folding::circuits::{
cyclefold::CycleFoldCommittedInstance, cyclefold::CycleFoldCommittedInstance,
@ -21,6 +24,7 @@ use crate::folding::circuits::{
CF2, CF2,
}; };
use crate::frontend::FCircuit; use crate::frontend::FCircuit;
use crate::transcript::poseidon::poseidon_canonical_config;
use crate::Error; use crate::Error;
use crate::{Decider as DeciderTrait, FoldingScheme}; 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 // 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 // checked in-circuit
cmT: C1, cmT: C1,
r: C1::ScalarField,
// cyclefold committed instance // cyclefold committed instance
cf_U_i: CycleFoldCommittedInstance<C2>, cf_U_i: CycleFoldCommittedInstance<C2>,
// the CS challenges are provided by the prover, but in-circuit they are checked to match the // 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()))?; .map_err(|e| Error::Other(e.to_string()))?;
let cmT = circuit1.cmT.unwrap(); let cmT = circuit1.cmT.unwrap();
let r_Fr = circuit1.r.unwrap();
let W_i1 = circuit1.W_i1.unwrap(); let W_i1 = circuit1.W_i1.unwrap();
let cf_W_i = circuit2.cf_W_i.unwrap(); let cf_W_i = circuit2.cf_W_i.unwrap();
@ -265,7 +267,6 @@ where
cs1_proofs: [U_cmW_proof, U_cmE_proof], cs1_proofs: [U_cmW_proof, U_cmE_proof],
cs2_proofs: [cf_cmW_proof, cf_cmE_proof], cs2_proofs: [cf_cmW_proof, cf_cmE_proof],
cmT, cmT,
r: r_Fr,
cf_U_i: circuit1.cf_U_i.unwrap(), cf_U_i: circuit1.cf_U_i.unwrap(),
cs1_challenges: [challenge_W, challenge_E], cs1_challenges: [challenge_W, challenge_E],
cs2_challenges: [c2_challenge_W, c2_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) // compute U = U_{d+1}= NIFS.V(U_d, u_d, cmT)
let U = NIFS::<C1, CS1>::verify(proof.r, running_instance, incoming_instance, &proof.cmT);
let poseidon_config = poseidon_canonical_config::<C1::ScalarField>();
let mut transcript = PoseidonSponge::<C1::ScalarField>::new(&poseidon_config);
let (U, r_bits) = NIFS::<C1, CS1, PoseidonSponge<C1::ScalarField>>::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 (cmE_x, cmE_y) = NonNativeAffineVar::inputize(U.cmE)?;
let (cmW_x, cmW_y) = NonNativeAffineVar::inputize(U.cmW)?; let (cmW_x, cmW_y) = NonNativeAffineVar::inputize(U.cmW)?;
@ -332,7 +343,7 @@ where
// NIFS values: // NIFS values:
cmT_x, cmT_x,
cmT_y, cmT_y,
vec![proof.r],
vec![r],
] ]
.concat(); .concat();

+ 6
- 14
folding-schemes/src/folding/nova/decider_circuits.rs

@ -28,8 +28,7 @@ use super::{
decider_eth_circuit::{ decider_eth_circuit::{
evaluate_gadget, KZGChallengesGadget, R1CSVar, RelaxedR1CSGadget, WitnessVar, evaluate_gadget, KZGChallengesGadget, R1CSVar, RelaxedR1CSGadget, WitnessVar,
}, },
nifs::NIFS,
traits::NIFSTrait,
nifs::{nova::NIFS, NIFSTrait},
CommittedInstance, Nova, Witness, CommittedInstance, Nova, Witness,
}; };
use crate::arith::r1cs::R1CS; use crate::arith::r1cs::R1CS;
@ -114,28 +113,21 @@ where
CS2: CommitmentScheme<C2, H>, CS2: CommitmentScheme<C2, H>,
{ {
let mut transcript = PoseidonSponge::<C1::ScalarField>::new(&nova.poseidon_config); let mut transcript = PoseidonSponge::<C1::ScalarField>::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} // compute the U_{i+1}, W_{i+1}
let (T, cmT) = NIFS::<C1, CS1, H>::compute_cmT(
let (W_i1, U_i1, cmT, r_bits) = NIFS::<C1, CS1, PoseidonSponge<C1::ScalarField>, H>::prove(
&nova.cs_pp, &nova.cs_pp,
&nova.r1cs.clone(), &nova.r1cs.clone(),
&nova.w_i.clone(),
&nova.u_i.clone(),
&nova.W_i.clone(),
&nova.U_i.clone(),
)?;
let r_bits = NIFS::<C1, CS1, H>::get_challenge(
&mut transcript, &mut transcript,
nova.pp_hash, nova.pp_hash,
&nova.W_i,
&nova.U_i, &nova.U_i,
&nova.w_i,
&nova.u_i, &nova.u_i,
&cmT,
);
)?;
let r_Fr = C1::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits)) let r_Fr = C1::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits))
.ok_or(Error::OutOfBounds)?; .ok_or(Error::OutOfBounds)?;
let (W_i1, U_i1) =
NIFS::<C1, CS1, H>::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 // compute the commitment scheme challenges used as inputs in the circuit
let (cs_challenge_W, cs_challenge_E) = let (cs_challenge_W, cs_challenge_E) =

+ 21
- 9
folding-schemes/src/folding/nova/decider_eth.rs

@ -3,7 +3,7 @@
/// More details can be found at the documentation page: /// More details can be found at the documentation page:
/// https://privacy-scaling-explorations.github.io/sonobe-docs/design/nova-decider-onchain.html /// https://privacy-scaling-explorations.github.io/sonobe-docs/design/nova-decider-onchain.html
use ark_bn254::Bn254; 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_ec::{AffineRepr, CurveGroup, Group};
use ark_ff::{BigInteger, PrimeField}; use ark_ff::{BigInteger, PrimeField};
use ark_groth16::Groth16; use ark_groth16::Groth16;
@ -15,8 +15,10 @@ use ark_std::{One, Zero};
use core::marker::PhantomData; use core::marker::PhantomData;
pub use super::decider_eth_circuit::DeciderEthCircuit; 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::{ use crate::commitment::{
kzg::{Proof as KZGProof, KZG}, kzg::{Proof as KZGProof, KZG},
pedersen::Params as PedersenParams, pedersen::Params as PedersenParams,
@ -24,6 +26,7 @@ use crate::commitment::{
}; };
use crate::folding::circuits::{nonnative::affine::NonNativeAffineVar, CF2}; use crate::folding::circuits::{nonnative::affine::NonNativeAffineVar, CF2};
use crate::frontend::FCircuit; use crate::frontend::FCircuit;
use crate::transcript::poseidon::poseidon_canonical_config;
use crate::Error; use crate::Error;
use crate::{Decider as DeciderTrait, FoldingScheme}; 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 // 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 // checked in-circuit
cmT: C1, cmT: C1,
r: C1::ScalarField,
// the KZG challenges are provided by the prover, but in-circuit they are checked to match // the KZG challenges are provided by the prover, but in-circuit they are checked to match
// the in-circuit computed computed ones. // the in-circuit computed computed ones.
kzg_challenges: [C1::ScalarField; 2], kzg_challenges: [C1::ScalarField; 2],
@ -161,7 +163,6 @@ where
.map_err(|e| Error::Other(e.to_string()))?; .map_err(|e| Error::Other(e.to_string()))?;
let cmT = circuit.cmT.unwrap(); let cmT = circuit.cmT.unwrap();
let r_Fr = circuit.r.unwrap();
let W_i1 = circuit.W_i1.unwrap(); let W_i1 = circuit.W_i1.unwrap();
// get the challenges that have been already computed when preparing the circuit inputs in // get the challenges that have been already computed when preparing the circuit inputs in
@ -193,7 +194,6 @@ where
snark_proof, snark_proof,
kzg_proofs: [U_cmW_proof, U_cmE_proof], kzg_proofs: [U_cmW_proof, U_cmE_proof],
cmT, cmT,
r: r_Fr,
kzg_challenges: [challenge_W, challenge_E], kzg_challenges: [challenge_W, challenge_E],
}) })
} }
@ -212,7 +212,17 @@ where
} }
// compute U = U_{d+1}= NIFS.V(U_d, u_d, cmT) // compute U = U_{d+1}= NIFS.V(U_d, u_d, cmT)
let U = NIFS::<C1, CS1>::verify(proof.r, running_instance, incoming_instance, &proof.cmT);
let poseidon_config = poseidon_canonical_config::<C1::ScalarField>();
let mut transcript = PoseidonSponge::<C1::ScalarField>::new(&poseidon_config);
let (U, r_bits) = NIFS::<C1, CS1, PoseidonSponge<C1::ScalarField>>::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 (cmE_x, cmE_y) = NonNativeAffineVar::inputize(U.cmE)?;
let (cmW_x, cmW_y) = NonNativeAffineVar::inputize(U.cmW)?; let (cmW_x, cmW_y) = NonNativeAffineVar::inputize(U.cmW)?;
@ -235,7 +245,7 @@ where
], ],
cmT_x, cmT_x,
cmT_y, cmT_y,
vec![proof.r],
vec![r],
] ]
.concat(); .concat();
@ -264,11 +274,13 @@ where
} }
/// Prepares solidity calldata for calling the NovaDecider contract /// Prepares solidity calldata for calling the NovaDecider contract
#[allow(clippy::too_many_arguments)]
pub fn prepare_calldata( pub fn prepare_calldata(
function_signature_check: [u8; 4], function_signature_check: [u8; 4],
i: ark_bn254::Fr, i: ark_bn254::Fr,
z_0: Vec<ark_bn254::Fr>, z_0: Vec<ark_bn254::Fr>,
z_i: Vec<ark_bn254::Fr>, z_i: Vec<ark_bn254::Fr>,
r: ark_bn254::Fr,
running_instance: &CommittedInstance<ark_bn254::G1Projective>, running_instance: &CommittedInstance<ark_bn254::G1Projective>,
incoming_instance: &CommittedInstance<ark_bn254::G1Projective>, incoming_instance: &CommittedInstance<ark_bn254::G1Projective>,
proof: Proof<ark_bn254::G1Projective, KZG<'static, Bn254>, Groth16<Bn254>>, proof: Proof<ark_bn254::G1Projective, KZG<'static, Bn254>, Groth16<Bn254>>,
@ -286,7 +298,7 @@ pub fn prepare_calldata(
point_to_eth_format(running_instance.cmE.into_affine())?, // U_i_cmE point_to_eth_format(running_instance.cmE.into_affine())?, // U_i_cmE
running_instance.u.into_bigint().to_bytes_be(), // U_i_u running_instance.u.into_bigint().to_bytes_be(), // U_i_u
incoming_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 running_instance
.x .x
.iter() .iter()

+ 5
- 15
folding-schemes/src/folding/nova/decider_eth_circuit.rs

@ -26,8 +26,7 @@ use core::{borrow::Borrow, marker::PhantomData};
use super::{ use super::{
circuits::{ChallengeGadget, CommittedInstanceVar}, circuits::{ChallengeGadget, CommittedInstanceVar},
nifs::NIFS,
traits::NIFSTrait,
nifs::{nova::NIFS, NIFSTrait},
CommittedInstance, Nova, Witness, CommittedInstance, Nova, Witness,
}; };
use crate::commitment::{pedersen::Params as PedersenParams, CommitmentScheme}; use crate::commitment::{pedersen::Params as PedersenParams, CommitmentScheme};
@ -246,27 +245,18 @@ where
let mut transcript = PoseidonSponge::<C1::ScalarField>::new(&nova.poseidon_config); let mut transcript = PoseidonSponge::<C1::ScalarField>::new(&nova.poseidon_config);
// compute the U_{i+1}, W_{i+1} // compute the U_{i+1}, W_{i+1}
let (aux_p, aux_v) = NIFS::<C1, CS1, H>::compute_aux(
let (W_i1, U_i1, cmT, r_bits) = NIFS::<C1, CS1, PoseidonSponge<C1::ScalarField>, H>::prove(
&nova.cs_pp, &nova.cs_pp,
&nova.r1cs.clone(), &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::<C1, CommittedInstance<C1>>::get_challenge_native(
&mut transcript, &mut transcript,
nova.pp_hash, nova.pp_hash,
&nova.W_i,
&nova.U_i, &nova.U_i,
&nova.w_i,
&nova.u_i, &nova.u_i,
Some(&cmT),
);
)?;
let r_Fr = C1::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits)) let r_Fr = C1::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits))
.ok_or(Error::OutOfBounds)?; .ok_or(Error::OutOfBounds)?;
let (W_i1, U_i1) = NIFS::<C1, CS1, H>::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 // compute the KZG challenges used as inputs in the circuit
let (kzg_challenge_W, kzg_challenge_E) = let (kzg_challenge_W, kzg_challenge_E) =

+ 30
- 52
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 /// Implements the scheme described in [Nova](https://eprint.iacr.org/2021/370.pdf) and
/// [CycleFold](https://eprint.iacr.org/2023/1192.pdf). /// [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::{ use ark_crypto_primitives::sponge::{
poseidon::{PoseidonConfig, PoseidonSponge}, poseidon::{PoseidonConfig, PoseidonSponge},
Absorb, CryptographicSponge, Absorb, CryptographicSponge,
@ -36,14 +40,14 @@ use crate::{
use crate::{arith::Arith, commitment::CommitmentScheme}; use crate::{arith::Arith, commitment::CommitmentScheme};
pub mod circuits; pub mod circuits;
pub mod nifs;
pub mod ova;
pub mod traits; pub mod traits;
pub mod zk; 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 // offchain decider
pub mod decider; pub mod decider;
@ -714,28 +718,21 @@ where
.F .F
.step_native(i_usize, self.z_i.clone(), external_inputs.clone())?; .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::<C1, CommittedInstance<C1>>::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<C1>, CommittedInstance<C1>, C1, Vec<bool>) =
NIFS::<C1, CS1, PoseidonSponge<C1::ScalarField>, 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)) let r_Fq = C1::BaseField::from_bigint(BigInteger::from_bits_le(&r_bits))
.ok_or(Error::OutOfBounds)?; .ok_or(Error::OutOfBounds)?;
// fold Nova instances
let (W_i1, U_i1): (Witness<C1>, CommittedInstance<C1>) = NIFS::<C1, CS1, H>::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) // folded instance output (public input, x)
// u_{i+1}.x[0] = H(i+1, z_0, z_{i+1}, U_{i+1}) // u_{i+1}.x[0] = H(i+1, z_0, z_{i+1}, U_{i+1})
let u_i1_x = U_i1.hash( let u_i1_x = U_i1.hash(
@ -776,7 +773,15 @@ where
}; };
#[cfg(test)] #[cfg(test)]
NIFS::<C1, CS1, H>::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::<C1, CS1, PoseidonSponge<C1::ScalarField>, H>::fold_committed_instances(
r_Fr, &self.U_i, &self.u_i, &cmT,
);
assert_eq!(U_i1, expected);
}
} else { } else {
// CycleFold part: // CycleFold part:
// get the vector used as public inputs 'x' in the CycleFold circuit // get the vector used as public inputs 'x' in the CycleFold circuit
@ -1037,33 +1042,6 @@ where
} }
} }
impl<C1, GC1, C2, GC2, FC, CS1, CS2, const H: bool> Nova<C1, GC1, C2, GC2, FC, CS1, CS2, H>
where
C1: CurveGroup,
GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
C2: CurveGroup,
GC2: CurveVar<C2, CF2<C2>>,
FC: FCircuit<C1::ScalarField>,
CS1: CommitmentScheme<C1, H>,
CS2: CommitmentScheme<C2, H>,
<C2 as CurveGroup>::BaseField: PrimeField,
<C1 as Group>::ScalarField: Absorb,
<C2 as Group>::ScalarField: Absorb,
C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
{
// computes T and cmT for the AugmentedFCircuit
fn compute_cmT(&self) -> Result<(Vec<C1::ScalarField>, C1), Error> {
NIFS::<C1, CS1, H>::compute_aux(
&self.cs_pp,
&self.r1cs,
&self.w_i,
&self.u_i,
&self.W_i,
&self.U_i,
)
}
}
impl<C1, GC1, C2, GC2, FC, CS1, CS2, const H: bool> Nova<C1, GC1, C2, GC2, FC, CS1, CS2, H> impl<C1, GC1, C2, GC2, FC, CS1, CS2, const H: bool> Nova<C1, GC1, C2, GC2, FC, CS1, CS2, H>
where where
C1: CurveGroup, C1: CurveGroup,

+ 152
- 0
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<C, H>,
T: Transcript<C::ScalarField>,
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<C::ScalarField>, e_len: usize, rng: impl RngCore) -> Self::Witness;
fn new_instance(
rng: impl RngCore,
params: &CS::ProverParams,
w: &Self::Witness,
x: Vec<C::ScalarField>,
aux: Vec<C::ScalarField>, // t_or_e in Ova, empty for Nova
) -> Result<Self::CommittedInstance, Error>;
fn fold_witness(
r: C::ScalarField,
W: &Self::Witness, // running witness
w: &Self::Witness, // incoming witness
aux: &Self::ProverAux,
) -> Result<Self::Witness, Error>;
/// 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<C::ScalarField>,
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<bool>,
),
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<bool>), 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<Projective, Pedersen<Projective>, PoseidonSponge<Fr>>,
>() -> (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::<Projective>::setup(&mut rng, r1cs.A.n_cols).unwrap();
let poseidon_config = poseidon_canonical_config::<Fr>();
let mut transcript_p = PoseidonSponge::<Fr>::new(&poseidon_config);
let mut transcript_v = PoseidonSponge::<Fr>::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)
}
}

+ 411
- 0
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<C: CurveGroup> {
// Random evaluation point for the E
pub rE: Vec<C::ScalarField>,
// 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<C::ScalarField>,
}
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);
}
}
impl<C: CurveGroup> Dummy<usize> for CommittedInstance<C> {
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<C: CurveGroup> {
pub E: Vec<C::ScalarField>,
pub W: Vec<C::ScalarField>,
pub rW: C::ScalarField,
}
impl<C: CurveGroup> Dummy<&R1CS<C::ScalarField>> for Witness<C> {
fn dummy(r1cs: &R1CS<C::ScalarField>) -> 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<C: CurveGroup> Witness<C> {
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 commit<CS: CommitmentScheme<C, H>, const H: bool>(
&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,
})
}
}
#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)]
pub struct Proof<C: CurveGroup> {
pub h_proof: PointVsLineProof<C>,
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<C, H>,
T: Transcript<C::ScalarField>,
const H: bool = false,
> {
_c: PhantomData<C>,
_cp: PhantomData<CS>,
_ct: PhantomData<T>,
}
impl<C: CurveGroup, CS: CommitmentScheme<C, H>, T: Transcript<C::ScalarField>, const H: bool>
NIFSTrait<C, CS, T, H> for NIFS<C, CS, T, H>
where
<C as Group>::ScalarField: Absorb,
<C as CurveGroup>::BaseField: PrimeField,
{
type CommittedInstance = CommittedInstance<C>;
type Witness = Witness<C>;
type ProverAux = Vec<C::ScalarField>; // T in Mova's notation
type Proof = Proof<C>;
fn new_witness(w: Vec<C::ScalarField>, e_len: usize, rng: impl RngCore) -> Self::Witness {
Witness::new::<H>(w, e_len, rng)
}
fn new_instance(
mut rng: impl RngCore,
params: &CS::ProverParams,
W: &Self::Witness,
x: Vec<C::ScalarField>,
aux: Vec<C::ScalarField>, // = r_E
) -> Result<Self::CommittedInstance, Error> {
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::<CS, H>(params, x, rE)
}
// Protocol 7 - point 3 (16)
fn fold_witness(
a: C::ScalarField,
W_i: &Witness<C>,
w_i: &Witness<C>,
aux: &Vec<C::ScalarField>, // T in Mova's notation
) -> Result<Witness<C>, Error> {
let a2 = a * a;
let E: Vec<C::ScalarField> = vec_add(
&vec_add(&W_i.E, &vec_scalar_mul(aux, &a))?,
&vec_scalar_mul(&w_i.E, &a2),
)?;
let W: Vec<C::ScalarField> = 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::<C> { 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<C::ScalarField>,
transcript: &mut T,
pp_hash: C::ScalarField,
W_i: &Witness<C>,
U_i: &CommittedInstance<C>,
w_i: &Witness<C>,
u_i: &CommittedInstance<C>,
) -> Result<
(
Self::Witness,
Self::CommittedInstance,
Self::Proof,
Vec<bool>,
),
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::<C, T>::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<C::ScalarField> = [vec![U_i.u], U_i.x.to_vec(), W_i.W.to_vec()].concat();
let z2: Vec<C::ScalarField> = [vec![u_i.u], u_i.x.to_vec(), w_i.W.to_vec()].concat();
let T = NovaNIFS::<C, CS, T, H>::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<C>,
u_i: &CommittedInstance<C>,
proof: &Proof<C>,
) -> Result<(Self::CommittedInstance, Vec<bool>), Error> {
transcript.absorb(&pp_hash);
transcript.absorb(U_i);
transcript.absorb(u_i);
let rE_prime = PointVsLine::<C, T>::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<C: CurveGroup, CS: CommitmentScheme<C, H>, T: Transcript<C::ScalarField>, const H: bool>
NIFS<C, CS, T, H>
{
// Protocol 7 - point 3 (15)
fn fold_committed_instance(
a: C::ScalarField,
U_i: &CommittedInstance<C>,
u_i: &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 = 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::<Vec<C::ScalarField>>();
Ok(CommittedInstance::<C> {
rE: rE_prime.to_vec(),
mleE,
u,
cmW,
x,
})
}
}
impl<C: CurveGroup> Arith<Witness<C>, CommittedInstance<C>> for R1CS<CF1<C>> {
type Evaluation = Vec<CF1<C>>;
fn eval_relation(
&self,
w: &Witness<C>,
u: &CommittedInstance<C>,
) -> Result<Self::Evaluation, Error> {
self.eval_at_z(&[&[u.u][..], &u.x, &w.W].concat())
}
fn check_evaluation(
w: &Witness<C>,
_u: &CommittedInstance<C>,
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::<NIFS<Projective, Pedersen<Projective>, PoseidonSponge<Fr>>>();
// check the last folded instance relation
let r1cs = get_test_r1cs();
r1cs.check_relation(&W, &U).unwrap();
}
}

folding-schemes/src/folding/nova/nifs.rs → 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_crypto_primitives::sponge::Absorb;
use ark_ec::{CurveGroup, Group}; use ark_ec::{CurveGroup, Group};
use ark_ff::PrimeField;
use ark_ff::{BigInteger, PrimeField};
use ark_std::rand::RngCore; use ark_std::rand::RngCore;
use ark_std::Zero; use ark_std::Zero;
use std::marker::PhantomData; 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::arith::r1cs::R1CS;
use crate::commitment::CommitmentScheme; use crate::commitment::CommitmentScheme;
use crate::folding::circuits::cyclefold::{CycleFoldCommittedInstance, CycleFoldWitness}; 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::transcript::Transcript;
use crate::utils::vec::{hadamard, mat_vec_mul, vec_add, vec_scalar_mul, vec_sub}; use crate::utils::vec::{hadamard, mat_vec_mul, vec_add, vec_scalar_mul, vec_sub};
use crate::Error; use crate::Error;
/// Implements the Non-Interactive Folding Scheme described in section 4 of /// 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 /// `H` specifies whether the NIFS will use a blinding factor
pub struct NIFS<C: CurveGroup, CS: CommitmentScheme<C, H>, const H: bool = false> {
pub struct NIFS<
C: CurveGroup,
CS: CommitmentScheme<C, H>,
T: Transcript<C::ScalarField>,
const H: bool = false,
> {
_c: PhantomData<C>, _c: PhantomData<C>,
_cp: PhantomData<CS>, _cp: PhantomData<CS>,
_t: PhantomData<T>,
} }
impl<C: CurveGroup, CS: CommitmentScheme<C, H>, const H: bool> NIFSTrait<C, CS, H>
for NIFS<C, CS, H>
impl<C: CurveGroup, CS: CommitmentScheme<C, H>, T: Transcript<C::ScalarField>, const H: bool>
NIFSTrait<C, CS, T, H> for NIFS<C, CS, T, H>
where where
<C as Group>::ScalarField: Absorb, <C as Group>::ScalarField: Absorb,
<C as CurveGroup>::BaseField: PrimeField, <C as CurveGroup>::BaseField: PrimeField,
<C as Group>::ScalarField: PrimeField,
{ {
type CommittedInstance = CommittedInstance<C>; type CommittedInstance = CommittedInstance<C>;
type Witness = Witness<C>; type Witness = Witness<C>;
type ProverAux = Vec<C::ScalarField>; type ProverAux = Vec<C::ScalarField>;
type VerifierAux = C;
type Proof = C;
fn new_witness(w: Vec<C::ScalarField>, e_len: usize, rng: impl RngCore) -> Self::Witness { fn new_witness(w: Vec<C::ScalarField>, e_len: usize, rng: impl RngCore) -> Self::Witness {
Witness::new::<H>(w, e_len, rng) Witness::new::<H>(w, e_len, rng)
} }
fn new_instance( fn new_instance(
W: &Self::Witness,
_rng: impl RngCore,
params: &CS::ProverParams, params: &CS::ProverParams,
W: &Self::Witness,
x: Vec<C::ScalarField>, x: Vec<C::ScalarField>,
_aux: Vec<C::ScalarField>, _aux: Vec<C::ScalarField>,
) -> Result<Self::CommittedInstance, Error> { ) -> Result<Self::CommittedInstance, Error> {
@ -51,7 +61,7 @@ where
r: C::ScalarField, r: C::ScalarField,
W_i: &Self::Witness, W_i: &Self::Witness,
w_i: &Self::Witness, w_i: &Self::Witness,
aux: &Self::ProverAux,
aux: &Self::ProverAux, // T in Nova's notation
) -> Result<Self::Witness, Error> { ) -> Result<Self::Witness, Error> {
let r2 = r * r; let r2 = r * r;
let E: Vec<C::ScalarField> = vec_add( let E: Vec<C::ScalarField> = vec_add(
@ -72,65 +82,72 @@ where
Ok(Self::Witness { E, rE, W, rW }) Ok(Self::Witness { E, rE, W, rW })
} }
fn compute_aux(
fn prove(
cs_prover_params: &CS::ProverParams, cs_prover_params: &CS::ProverParams,
r1cs: &R1CS<C::ScalarField>, r1cs: &R1CS<C::ScalarField>,
transcript: &mut T,
pp_hash: C::ScalarField,
W_i: &Self::Witness, W_i: &Self::Witness,
U_i: &Self::CommittedInstance, U_i: &Self::CommittedInstance,
w_i: &Self::Witness, w_i: &Self::Witness,
u_i: &Self::CommittedInstance, u_i: &Self::CommittedInstance,
) -> Result<(Self::ProverAux, Self::VerifierAux), Error> {
) -> Result<
(
Self::Witness,
Self::CommittedInstance,
Self::Proof,
Vec<bool>,
),
Error,
> {
// compute the cross terms
let z1: Vec<C::ScalarField> = [vec![U_i.u], U_i.x.to_vec(), W_i.W.to_vec()].concat(); let z1: Vec<C::ScalarField> = [vec![U_i.u], U_i.x.to_vec(), W_i.W.to_vec()].concat();
let z2: Vec<C::ScalarField> = [vec![u_i.u], u_i.x.to_vec(), w_i.W.to_vec()].concat(); let z2: Vec<C::ScalarField> = [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)?; 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) // 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())?; let cmT = CS::commit(cs_prover_params, &T, &C::ScalarField::zero())?;
Ok((T, cmT))
}
fn get_challenge<T: Transcript<C::ScalarField>>(
transcript: &mut T,
pp_hash: C::ScalarField, // public params hash
U_i: &Self::CommittedInstance,
u_i: &Self::CommittedInstance,
aux: &Self::VerifierAux, // cmT
) -> Vec<bool> {
ChallengeGadget::<C, Self::CommittedInstance>::get_challenge_native(
let r_bits = ChallengeGadget::<C, Self::CommittedInstance>::get_challenge_native(
transcript, transcript,
pp_hash, pp_hash,
U_i, U_i,
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( 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,
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::<Vec<C::ScalarField>>();
cmT: &C, // Proof
) -> Result<(Self::CommittedInstance, Vec<bool>), Error> {
let r_bits = ChallengeGadget::<C, Self::CommittedInstance>::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<C: CurveGroup, CS: CommitmentScheme<C, H>, const H: bool> NIFS<C, CS, H>
impl<C: CurveGroup, CS: CommitmentScheme<C, H>, T: Transcript<C::ScalarField>, const H: bool>
NIFS<C, CS, T, H>
where where
<C as Group>::ScalarField: Absorb, <C as Group>::ScalarField: Absorb,
<C as CurveGroup>::BaseField: PrimeField, <C as CurveGroup>::BaseField: PrimeField,
@ -161,26 +178,6 @@ where
vec_sub(&vec_sub(&vec_add(&Az1_Bz2, &Az2_Bz1)?, &u1Cz2)?, &u2Cz1) 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<C::ScalarField>,
w1: &Witness<C>,
ci1: &CommittedInstance<C>,
w2: &Witness<C>,
ci2: &CommittedInstance<C>,
) -> Result<(Vec<C::ScalarField>, C), Error> {
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();
// 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( pub fn compute_cyclefold_cmT(
cs_prover_params: &CS::ProverParams, cs_prover_params: &CS::ProverParams,
r1cs: &R1CS<C::ScalarField>, // R1CS over C2.Fr=C1.Fq (here C=C2) r1cs: &R1CS<C::ScalarField>, // R1CS over C2.Fr=C1.Fq (here C=C2)
@ -202,25 +199,26 @@ where
Ok((T, cmT)) 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, r: C::ScalarField,
ci1: &CommittedInstance<C>,
ci2: &CommittedInstance<C>,
ci3: &CommittedInstance<C>,
U_i: &CommittedInstance<C>,
u_i: &CommittedInstance<C>,
cmT: &C, 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<C> {
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::<Vec<C::ScalarField>>();
CommittedInstance { cmE, u, cmW, x }
} }
pub fn prove_commitments( pub fn prove_commitments(
@ -241,101 +239,19 @@ where
#[cfg(test)] #[cfg(test)]
pub mod tests { pub mod tests {
use super::*; 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_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::commitment::pedersen::Pedersen;
use crate::folding::nova::traits::NIFSTrait;
use crate::folding::nova::nifs::tests::test_nifs_opt;
#[test] #[test]
fn test_nifs_nova() { fn test_nifs_nova() {
let (W, U) = test_nifs_opt::<NIFS<Projective, Pedersen<Projective>>>();
let (W, U) = test_nifs_opt::<NIFS<Projective, Pedersen<Projective>, PoseidonSponge<Fr>>>();
// check the last folded instance relation // check the last folded instance relation
let r1cs = get_test_r1cs(); let r1cs = get_test_r1cs();
r1cs.check_relation(&W, &U).unwrap(); 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: NIFSTrait<Projective, Pedersen<Projective>>>(
) -> (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::<Projective>::setup(&mut rng, r1cs.A.n_cols).unwrap();
let poseidon_config = poseidon_canonical_config::<Fr>();
let mut transcript = PoseidonSponge::<Fr>::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)
}
} }

folding-schemes/src/folding/nova/ova.rs → folding-schemes/src/folding/nova/nifs/ova.rs

@ -1,19 +1,18 @@
/// This module contains the implementation the NIFSTrait for the /// 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:
/// <https://hackmd.io/V4838nnlRKal9ZiTHiGYzw#Construction> authored by Benedikt Bünz.
/// [Ova](https://hackmd.io/V4838nnlRKal9ZiTHiGYzw) NIFS (Non-Interactive Folding Scheme).
use ark_crypto_primitives::sponge::Absorb; use ark_crypto_primitives::sponge::Absorb;
use ark_ec::{CurveGroup, Group}; use ark_ec::{CurveGroup, Group};
use ark_ff::PrimeField;
use ark_ff::{BigInteger, PrimeField};
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
use ark_std::fmt::Debug; use ark_std::fmt::Debug;
use ark_std::rand::RngCore; use ark_std::rand::RngCore;
use ark_std::{One, UniformRand, Zero}; use ark_std::{One, UniformRand, Zero};
use std::marker::PhantomData; use std::marker::PhantomData;
use super::{circuits::ChallengeGadget, traits::NIFSTrait};
use super::NIFSTrait;
use crate::arith::r1cs::R1CS; use crate::arith::r1cs::R1CS;
use crate::commitment::CommitmentScheme; use crate::commitment::CommitmentScheme;
use crate::folding::nova::circuits::ChallengeGadget;
use crate::folding::{circuits::CF1, traits::Dummy}; use crate::folding::{circuits::CF1, traits::Dummy};
use crate::transcript::{AbsorbNonNative, Transcript}; use crate::transcript::{AbsorbNonNative, Transcript};
use crate::utils::vec::{hadamard, mat_vec_mul, vec_scalar_mul, vec_sub}; 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 /// 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. /// when committing to the witness itself.
#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] #[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. /// Implements the NIFS (Non-Interactive Folding Scheme) trait for Ova.
pub struct NIFS<C: CurveGroup, CS: CommitmentScheme<C, H>, const H: bool = false> {
pub struct NIFS<
C: CurveGroup,
CS: CommitmentScheme<C, H>,
T: Transcript<C::ScalarField>,
const H: bool = false,
> {
_c: PhantomData<C>, _c: PhantomData<C>,
_cp: PhantomData<CS>, _cp: PhantomData<CS>,
_t: PhantomData<T>,
} }
impl<C: CurveGroup, CS: CommitmentScheme<C, H>, const H: bool> NIFSTrait<C, CS, H>
for NIFS<C, CS, H>
impl<C: CurveGroup, CS: CommitmentScheme<C, H>, T: Transcript<C::ScalarField>, const H: bool>
NIFSTrait<C, CS, T, H> for NIFS<C, CS, T, H>
where where
<C as Group>::ScalarField: Absorb, <C as Group>::ScalarField: Absorb,
<C as CurveGroup>::BaseField: PrimeField, <C as CurveGroup>::BaseField: PrimeField,
@ -117,15 +121,16 @@ where
type CommittedInstance = CommittedInstance<C>; type CommittedInstance = CommittedInstance<C>;
type Witness = Witness<C>; type Witness = Witness<C>;
type ProverAux = (); type ProverAux = ();
type VerifierAux = ();
type Proof = ();
fn new_witness(w: Vec<C::ScalarField>, _e_len: usize, rng: impl RngCore) -> Self::Witness { fn new_witness(w: Vec<C::ScalarField>, _e_len: usize, rng: impl RngCore) -> Self::Witness {
Witness::new::<H>(w, rng) Witness::new::<H>(w, rng)
} }
fn new_instance( fn new_instance(
W: &Self::Witness,
_rng: impl RngCore,
params: &CS::ProverParams, params: &CS::ProverParams,
W: &Self::Witness,
x: Vec<C::ScalarField>, x: Vec<C::ScalarField>,
aux: Vec<C::ScalarField>, // t_or_e aux: Vec<C::ScalarField>, // t_or_e
) -> Result<Self::CommittedInstance, Error> { ) -> Result<Self::CommittedInstance, Error> {
@ -149,40 +154,55 @@ where
Ok(Self::Witness { w, rW }) Ok(Self::Witness { w, rW })
} }
fn compute_aux(
fn prove(
_cs_prover_params: &CS::ProverParams, _cs_prover_params: &CS::ProverParams,
_r1cs: &R1CS<C::ScalarField>, _r1cs: &R1CS<C::ScalarField>,
_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<T: Transcript<C::ScalarField>>(
transcript: &mut T, transcript: &mut T,
pp_hash: C::ScalarField, // public params hash
pp_hash: C::ScalarField,
W_i: &Self::Witness,
U_i: &Self::CommittedInstance, U_i: &Self::CommittedInstance,
w_i: &Self::Witness,
u_i: &Self::CommittedInstance, u_i: &Self::CommittedInstance,
_aux: &Self::VerifierAux,
) -> Vec<bool> {
// reuse Nova's get_challenge method
ChallengeGadget::<C, Self::CommittedInstance>::get_challenge_native(
transcript, pp_hash, U_i, u_i, None, // empty in Ova's case
)
}
) -> Result<
(
Self::Witness,
Self::CommittedInstance,
Self::Proof,
Vec<bool>,
),
Error,
> {
let mut transcript_v = transcript.clone();
// Notice: `prove` method is implemented at the trait level.
let r_bits = ChallengeGadget::<C, Self::CommittedInstance>::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( 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,
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<bool>), Error> {
let r_bits = ChallengeGadget::<C, Self::CommittedInstance>::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 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 cmWE = U_i.cmWE + u_i.cmWE.mul(r);
let x = U_i let x = U_i
@ -192,7 +212,7 @@ where
.map(|(a, b)| *a + (r * b)) .map(|(a, b)| *a + (r * b))
.collect::<Vec<C::ScalarField>>(); .collect::<Vec<C::ScalarField>>();
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::arith::{r1cs::tests::get_test_r1cs, Arith};
use crate::commitment::pedersen::Pedersen; use crate::commitment::pedersen::Pedersen;
use crate::folding::nova::nifs::tests::test_nifs_opt; 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 // Simple auxiliary structure mainly used to help pass a witness for which we can check
// easily an R1CS relation. // easily an R1CS relation.
@ -257,7 +278,7 @@ pub mod tests {
#[test] #[test]
fn test_nifs_ova() { fn test_nifs_ova() {
let (W, U) = test_nifs_opt::<NIFS<Projective, Pedersen<Projective>>>();
let (W, U) = test_nifs_opt::<NIFS<Projective, Pedersen<Projective>, PoseidonSponge<Fr>>>();
// check the last folded instance relation // check the last folded instance relation
let r1cs = get_test_r1cs(); let r1cs = get_test_r1cs();

folding-schemes/src/folding/mova/pointvsline.rs → 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_crypto_primitives::sponge::Absorb;
use ark_ec::{CurveGroup, Group}; use ark_ec::{CurveGroup, Group};
use ark_ff::{One, PrimeField}; use ark_ff::{One, PrimeField};
use ark_poly::univariate::DensePolynomial; use ark_poly::univariate::DensePolynomial;
use ark_poly::{DenseMultilinearExtension, DenseUVPolynomial, Polynomial}; use ark_poly::{DenseMultilinearExtension, DenseUVPolynomial, Polynomial};
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
use ark_std::{log2, Zero}; 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 /// 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 /// [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 mleE2_prime: C::ScalarField,
pub rE_prime: Vec<C::ScalarField>, pub rE_prime: Vec<C::ScalarField>,
} }
/// Proof from step 1 protocol 6 /// Proof from step 1 protocol 6
#[derive(Clone, Debug)]
#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)]
pub struct PointVsLineProof<C: CurveGroup> { pub struct PointVsLineProof<C: CurveGroup> {
pub h1: DensePolynomial<C::ScalarField>, pub h1: DensePolynomial<C::ScalarField>,
pub h2: DensePolynomial<C::ScalarField>, pub h2: DensePolynomial<C::ScalarField>,
@ -38,7 +39,7 @@ where
<C as Group>::ScalarField: Absorb, <C as Group>::ScalarField: Absorb,
{ {
pub fn prove( pub fn prove(
transcript: &mut impl Transcript<C::ScalarField>,
transcript: &mut T,
ci1: &CommittedInstance<C>, ci1: &CommittedInstance<C>,
ci2: &CommittedInstance<C>, ci2: &CommittedInstance<C>,
w1: &Witness<C>, w1: &Witness<C>,
@ -83,7 +84,7 @@ where
} }
pub fn verify( pub fn verify(
transcript: &mut impl Transcript<C::ScalarField>,
transcript: &mut T,
ci1: &CommittedInstance<C>, ci1: &CommittedInstance<C>,
ci2: &CommittedInstance<C>, ci2: &CommittedInstance<C>,
proof: &PointVsLineProof<C>, proof: &PointVsLineProof<C>,

+ 0
- 73
folding-schemes/src/folding/nova/traits.rs

@ -1,6 +1,4 @@
use ark_crypto_primitives::sponge::Absorb;
use ark_ec::CurveGroup; use ark_ec::CurveGroup;
use ark_std::fmt::Debug;
use ark_std::{rand::RngCore, UniformRand}; use ark_std::{rand::RngCore, UniformRand};
use super::{CommittedInstance, Witness}; use super::{CommittedInstance, Witness};
@ -8,79 +6,8 @@ use crate::arith::ArithSampler;
use crate::arith::{r1cs::R1CS, Arith}; use crate::arith::{r1cs::R1CS, Arith};
use crate::commitment::CommitmentScheme; use crate::commitment::CommitmentScheme;
use crate::folding::circuits::CF1; use crate::folding::circuits::CF1;
use crate::transcript::Transcript;
use crate::Error; 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<C: CurveGroup, CS: CommitmentScheme<C, H>, 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<C::ScalarField>, e_len: usize, rng: impl RngCore) -> Self::Witness;
fn new_instance(
w: &Self::Witness,
params: &CS::ProverParams,
x: Vec<C::ScalarField>,
aux: Vec<C::ScalarField>, // t_or_e in Ova, empty for Nova
) -> Result<Self::CommittedInstance, Error>;
fn fold_witness(
r: C::ScalarField,
W: &Self::Witness, // running witness
w: &Self::Witness, // incoming witness
aux: &Self::ProverAux,
) -> Result<Self::Witness, Error>;
/// computes the auxiliary parameters, eg. in Nova: (T, cmT), in Ova: T
fn compute_aux(
cs_prover_params: &CS::ProverParams,
r1cs: &R1CS<C::ScalarField>,
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<T: Transcript<C::ScalarField>>(
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<bool>;
/// 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 /// Implements `Arith` for R1CS, where the witness is of type [`Witness`], and
/// the committed instance is of type [`CommittedInstance`]. /// the committed instance is of type [`CommittedInstance`].
/// ///

+ 35
- 89
folding-schemes/src/folding/nova/zk.rs

@ -30,8 +30,7 @@
/// paper). /// paper).
/// And the Use-case-2 would require a modified version of the Decider circuits. /// 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 ark_std::{One, Zero};
use crate::{ use crate::{
@ -41,7 +40,7 @@ use crate::{
}; };
use ark_crypto_primitives::sponge::{ use ark_crypto_primitives::sponge::{
poseidon::{PoseidonConfig, PoseidonSponge}, poseidon::{PoseidonConfig, PoseidonSponge},
Absorb,
Absorb, CryptographicSponge,
}; };
use ark_ec::{CurveGroup, Group}; use ark_ec::{CurveGroup, Group};
use ark_r1cs_std::{ use ark_r1cs_std::{
@ -52,21 +51,16 @@ use ark_r1cs_std::{
use crate::{commitment::CommitmentScheme, folding::circuits::CF2, frontend::FCircuit, Error}; use crate::{commitment::CommitmentScheme, folding::circuits::CF2, frontend::FCircuit, Error};
use super::{ 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<C: CurveGroup> {
cmT: C,
}
pub struct RandomizedIVCProof<C1: CurveGroup, C2: CurveGroup> { pub struct RandomizedIVCProof<C1: CurveGroup, C2: CurveGroup> {
pub U_i: CommittedInstance<C1>, pub U_i: CommittedInstance<C1>,
pub u_i: CommittedInstance<C1>, pub u_i: CommittedInstance<C1>,
pub U_r: CommittedInstance<C1>, pub U_r: CommittedInstance<C1>,
pub pi: FoldingProof<C1>,
pub pi_prime: FoldingProof<C1>,
pub pi: C1, // proof = cmT
pub pi_prime: C1, // proof' = cmT'
pub W_i_prime: Witness<C1>, pub W_i_prime: Witness<C1>,
pub cf_U_i: CommittedInstance<C2>, pub cf_U_i: CommittedInstance<C2>,
pub cf_W_i: Witness<C2>, pub cf_W_i: Witness<C2>,
@ -77,24 +71,6 @@ where
<C1 as Group>::ScalarField: Absorb, <C1 as Group>::ScalarField: Absorb,
<C1 as CurveGroup>::BaseField: PrimeField, <C1 as CurveGroup>::BaseField: PrimeField,
{ {
/// Computes challenge required before folding instances
fn get_folding_challenge(
sponge: &mut PoseidonSponge<C1::ScalarField>,
pp_hash: C1::ScalarField,
U_i: CommittedInstance<C1>,
u_i: CommittedInstance<C1>,
cmT: C1,
) -> Result<C1::ScalarField, Error> {
let r_bits = ChallengeGadget::<C1, CommittedInstance<C1>>::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 /// 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 /// 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 /// For further details on why folding is hiding, see lemma 9
@ -118,68 +94,45 @@ where
GC2: ToConstraintFieldGadget<<C2 as CurveGroup>::BaseField>, GC2: ToConstraintFieldGadget<<C2 as CurveGroup>::BaseField>,
C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>, C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
{ {
let mut challenges_sponge = PoseidonSponge::<C1::ScalarField>::new(&nova.poseidon_config);
let mut transcript = PoseidonSponge::<C1::ScalarField>::new(&nova.poseidon_config);
// I. Compute proof for 'regular' instances // I. Compute proof for 'regular' instances
// 1. Fold the instance-witness pairs (U_i, W_i) with (u_i, w_i) // 1. Fold the instance-witness pairs (U_i, W_i) with (u_i, w_i)
// a. Compute T
let (T, cmT) = NIFS::<C1, CS1, true>::compute_cmT(
let (W_f, U_f, cmT, _) = NIFS::<C1, CS1, PoseidonSponge<C1::ScalarField>, true>::prove(
&nova.cs_pp, &nova.cs_pp,
&nova.r1cs, &nova.r1cs,
&mut transcript,
nova.pp_hash,
&nova.w_i, &nova.w_i,
&nova.u_i, &nova.u_i,
&nova.W_i, &nova.W_i,
&nova.U_i, &nova.U_i,
)?; )?;
// b. Compute folding challenge
let r = RandomizedIVCProof::<C1, C2>::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::<C1, CS1, true>::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) // 2. Sample a satisfying relaxed R1CS instance-witness pair (W_r, U_r)
let (W_r, U_r) = nova let (W_r, U_r) = nova
.r1cs .r1cs
.sample_witness_instance::<CS1>(&nova.cs_pp, &mut rng)?; .sample_witness_instance::<CS1>(&nova.cs_pp, &mut rng)?;
// 3. Fold the instance-witness pair (U_f, W_f) with (U_r, W_r) // 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::<C1, CS1, true>::compute_cmT(&nova.cs_pp, &nova.r1cs, &W_f, &U_f, &W_r, &U_r)?;
// b. Compute folding challenge
let r_2 = RandomizedIVCProof::<C1, C2>::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::<C1, CS1, true>::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::<C1, CS1, PoseidonSponge<C1::ScalarField>, true>::prove(
&nova.cs_pp,
&nova.r1cs,
&mut transcript,
nova.pp_hash,
&W_f,
&U_f,
&W_r,
&U_r,
)?;
Ok(RandomizedIVCProof { Ok(RandomizedIVCProof {
U_i: nova.U_i.clone(), U_i: nova.U_i.clone(),
u_i: nova.u_i.clone(), u_i: nova.u_i.clone(),
U_r, U_r,
pi,
pi_prime,
pi: cmT,
pi_prime: cmT_i_prime,
W_i_prime, W_i_prime,
cf_U_i: nova.cf_U_i.clone(), cf_U_i: nova.cf_U_i.clone(),
cf_W_i: nova.cf_W_i.clone(), cf_W_i: nova.cf_W_i.clone(),
@ -228,7 +181,7 @@ where
} }
// b. Check computed hashes are correct // b. Check computed hashes are correct
let mut sponge = PoseidonSponge::<C1::ScalarField>::new(poseidon_config);
let sponge = PoseidonSponge::<C1::ScalarField>::new(poseidon_config);
let expected_u_i_x = proof.U_i.hash(&sponge, pp_hash, i, &z_0, &z_i); 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] { if expected_u_i_x != proof.u_i.x[0] {
return Err(Error::zkIVCVerificationFail); return Err(Error::zkIVCVerificationFail);
@ -244,32 +197,25 @@ where
return Err(Error::zkIVCVerificationFail); return Err(Error::zkIVCVerificationFail);
} }
let mut transcript = PoseidonSponge::<C1::ScalarField>::new(poseidon_config);
// 3. Obtain the U_f folded instance // 3. Obtain the U_f folded instance
// a. Compute folding challenge
let r = RandomizedIVCProof::<C1, C2>::get_folding_challenge(
&mut sponge,
let (U_f, _) = NIFS::<C1, CS1, PoseidonSponge<C1::ScalarField>, true>::verify(
&mut transcript,
pp_hash, 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::<C1, CS1, true>::verify(r, &proof.u_i, &proof.U_i, &proof.pi.cmT);
// 4. Obtain the U^{\prime}_i folded instance // 4. Obtain the U^{\prime}_i folded instance
// a. Compute folding challenge
let r_2 = RandomizedIVCProof::<C1, C2>::get_folding_challenge(
&mut sponge,
let (U_i_prime, _) = NIFS::<C1, CS1, PoseidonSponge<C1::ScalarField>, true>::verify(
&mut transcript,
pp_hash, 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::<C1, CS1, true>::verify(r_2, &U_f, &proof.U_r, &proof.pi_prime.cmT);
// 5. Check that W^{\prime}_i is a satisfying witness // 5. Check that W^{\prime}_i is a satisfying witness
r1cs.check_relation(&proof.W_i_prime, &U_i_prime)?; r1cs.check_relation(&proof.W_i_prime, &U_i_prime)?;

Loading…
Cancel
Save