Browse Source

Implement OVA NIFS (#163)

* feat: Basic Ova NIFS impl working

The implementation follows the spec outlined by Bunz in:
https://hackmd.io/V4838nnlRKal9ZiTHiGYzw?view.

With this, the NIFS works and passes all tests.

* chore: Resolve all TODOs and warnings

* add: Docs for the NIMFS of the scheme and related structs

* chore: update imports

* add: Docs for all Ova NIFS functions

* fix: Unify nomenclature for all variables and elements within NIFS tests

* fix: Uniformize instance order in fn calls

* chore: pass clippy

* chore: clear all clippy findings in tests

* chore: Remove `mimc` from spelling checks

* chore: Address PR reviews
main
Carlos Pérez 1 month ago
committed by GitHub
parent
commit
88bbd9cff7
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
9 changed files with 672 additions and 6 deletions
  1. +1
    -0
      .github/workflows/typos.toml
  2. +2
    -2
      folding-schemes/src/folding/hypernova/decider_eth.rs
  3. +2
    -1
      folding-schemes/src/folding/hypernova/mod.rs
  4. +1
    -0
      folding-schemes/src/folding/mod.rs
  5. +2
    -2
      folding-schemes/src/folding/nova/decider_eth.rs
  6. +1
    -1
      folding-schemes/src/folding/nova/nifs.rs
  7. +24
    -0
      folding-schemes/src/folding/nova/traits.rs
  8. +157
    -0
      folding-schemes/src/folding/ova/mod.rs
  9. +482
    -0
      folding-schemes/src/folding/ova/nifs.rs

+ 1
- 0
.github/workflows/typos.toml

@ -1,2 +1,3 @@
[default.extend-words] [default.extend-words]
groth = "groth" groth = "groth"
mimc = "mimc"

+ 2
- 2
folding-schemes/src/folding/hypernova/decider_eth.rs

@ -416,7 +416,7 @@ pub mod tests {
let verified = D::verify( let verified = D::verify(
decider_vp.clone(), decider_vp.clone(),
hypernova.i.clone(),
hypernova.i,
hypernova.z_0.clone(), hypernova.z_0.clone(),
hypernova.z_i.clone(), hypernova.z_i.clone(),
&(), &(),
@ -483,7 +483,7 @@ pub mod tests {
let verified = D::verify( let verified = D::verify(
decider_vp_deserialized, decider_vp_deserialized,
i_deserialized.clone(),
i_deserialized,
z_0_deserialized.clone(), z_0_deserialized.clone(),
z_i_deserialized.clone(), z_i_deserialized.clone(),
&(), &(),

+ 2
- 1
folding-schemes/src/folding/hypernova/mod.rs

@ -950,6 +950,7 @@ mod tests {
test_ivc_opt::<KZG<Bn254>, Pedersen<Projective2>, false>(poseidon_config, F_circuit); test_ivc_opt::<KZG<Bn254>, Pedersen<Projective2>, false>(poseidon_config, F_circuit);
} }
#[allow(clippy::type_complexity)]
// test_ivc allowing to choose the CommitmentSchemes // test_ivc allowing to choose the CommitmentSchemes
pub fn test_ivc_opt< pub fn test_ivc_opt<
CS1: CommitmentScheme<Projective, H>, CS1: CommitmentScheme<Projective, H>,
@ -1028,7 +1029,7 @@ mod tests {
hypernova_params.1.clone(), // verifier_params hypernova_params.1.clone(), // verifier_params
z_0, z_0,
hypernova.z_i.clone(), hypernova.z_i.clone(),
hypernova.i.clone(),
hypernova.i,
running_instance.clone(), running_instance.clone(),
incoming_instance.clone(), incoming_instance.clone(),
cyclefold_instance.clone(), cyclefold_instance.clone(),

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

@ -1,5 +1,6 @@
pub mod circuits; pub mod circuits;
pub mod hypernova; pub mod hypernova;
pub mod nova; pub mod nova;
pub mod ova;
pub mod protogalaxy; pub mod protogalaxy;
pub mod traits; pub mod traits;

+ 2
- 2
folding-schemes/src/folding/nova/decider_eth.rs

@ -402,7 +402,7 @@ pub mod tests {
let start = Instant::now(); let start = Instant::now();
let verified = D::verify( let verified = D::verify(
decider_vp.clone(), decider_vp.clone(),
nova.i.clone(),
nova.i,
nova.z_0.clone(), nova.z_0.clone(),
nova.z_i.clone(), nova.z_i.clone(),
&nova.U_i, &nova.U_i,
@ -516,7 +516,7 @@ pub mod tests {
let start = Instant::now(); let start = Instant::now();
let verified = D::verify( let verified = D::verify(
decider_vp.clone(), decider_vp.clone(),
nova.i.clone(),
nova.i,
nova.z_0.clone(), nova.z_0.clone(),
nova.z_i.clone(), nova.z_i.clone(),
&nova.U_i, &nova.U_i,

+ 1
- 1
folding-schemes/src/folding/nova/nifs.rs

@ -367,7 +367,7 @@ pub mod tests {
// next equalities should hold since we started from two cmE of zero-vector E's // next equalities should hold since we started from two cmE of zero-vector E's
assert_eq!(ci3.cmE, cmT.mul(r)); assert_eq!(ci3.cmE, cmT.mul(r));
assert_eq!(w3.E, vec_scalar_mul(&T, &r));
assert_eq!(w3.E, vec_scalar_mul(T.as_slice(), &r));
// NIFS.Verify_Folded_Instance: // NIFS.Verify_Folded_Instance:
NIFS::<Projective, Pedersen<Projective>>::verify_folded_instance(r, &ci1, &ci2, &ci3, &cmT) NIFS::<Projective, Pedersen<Projective>>::verify_folded_instance(r, &ci1, &ci2, &ci3, &cmT)

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

@ -6,6 +6,9 @@ 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::folding::ova::{
CommittedInstance as OvaCommittedInstance, TestingWitness as OvaWitness,
};
use crate::Error; use crate::Error;
/// Implements `Arith` for R1CS, where the witness is of type [`Witness`], and /// Implements `Arith` for R1CS, where the witness is of type [`Witness`], and
@ -95,3 +98,24 @@ impl ArithSampler, CommittedInstance> for R1CS
Ok((witness, cm_witness)) Ok((witness, cm_witness))
} }
} }
// Sadly, this forces duplication of code. We can try to abstract with more traits if needed..
impl<C: CurveGroup> Arith<OvaWitness<C>, OvaCommittedInstance<C>> for R1CS<CF1<C>> {
type Evaluation = Vec<CF1<C>>;
fn eval_relation(
&self,
w: &OvaWitness<C>,
u: &OvaCommittedInstance<C>,
) -> Result<Self::Evaluation, Error> {
self.eval_at_z(&[&[u.mu], u.x.as_slice(), &w.w].concat())
}
fn check_evaluation(
w: &OvaWitness<C>,
_u: &OvaCommittedInstance<C>,
e: Self::Evaluation,
) -> Result<(), Error> {
(w.e == e).then_some(()).ok_or(Error::NotSatisfied)
}
}

+ 157
- 0
folding-schemes/src/folding/ova/mod.rs

@ -0,0 +1,157 @@
use std::marker::PhantomData;
/// Implements the scheme described in [Ova](https://hackmd.io/V4838nnlRKal9ZiTHiGYzw?view).
/// Ova is a slight modification to Nova that shaves off 1 group operation and a few hashes.
/// The key idea is that we can commit to $T$ and $W$ in one commitment.
///
/// This slightly breaks the abstraction between the IVC scheme and the accumulation scheme.
/// We assume that the accumulation prover receives $\vec{w}$ as input which proves the current step
/// of the computation and that the *previous* accumulation was performed correctly.
/// Note that $\vec{w}$ can be generated without being aware of $\vec{t}$ or $\alpha$.
/// Alternatively the accumulation prover can receive the commitment to $\vec{w}$ as input
/// and add to it the commitment to $\vec{t}$.
///
/// This yields a commitment to the concatenated vector $\vec{w}||\vec{t}$.
/// This works because both $W$ and $T$ are multiplied by the same challenge $\alpha$ in Nova.
use ark_crypto_primitives::sponge::Absorb;
use ark_ec::{CurveGroup, Group};
use ark_ff::PrimeField;
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
use ark_std::fmt::Debug;
use ark_std::rand::RngCore;
use ark_std::{One, UniformRand, Zero};
use crate::arith::r1cs::R1CS;
use crate::commitment::CommitmentScheme;
use crate::constants::NOVA_N_BITS_RO;
use crate::folding::{circuits::CF1, traits::Dummy};
use crate::transcript::{AbsorbNonNative, Transcript};
use crate::Error;
pub mod nifs;
/// A CommittedInstance in [Ova](https://hackmd.io/V4838nnlRKal9ZiTHiGYzw?view) is represented by `W` or `W'`.
/// It is the result of the commitment to a vector that contains the witness `w` concatenated
/// with `t` or `e` + the public inputs `x` and a relaxation factor `mu`.
#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)]
pub struct CommittedInstance<C: CurveGroup> {
pub mu: C::ScalarField,
pub x: Vec<C::ScalarField>,
pub cmWE: C,
}
impl<C: CurveGroup> Absorb for CommittedInstance<C>
where
C::ScalarField: Absorb,
{
fn to_sponge_bytes(&self, dest: &mut Vec<u8>) {
C::ScalarField::batch_to_sponge_bytes(&self.to_sponge_field_elements_as_vec(), dest);
}
fn to_sponge_field_elements<F: PrimeField>(&self, dest: &mut Vec<F>) {
self.mu.to_sponge_field_elements(dest);
self.x.to_sponge_field_elements(dest);
// We cannot call `to_native_sponge_field_elements(dest)` directly, as
// `to_native_sponge_field_elements` needs `F` to be `C::ScalarField`,
// but here `F` is a generic `PrimeField`.
self.cmWE
.to_native_sponge_field_elements_as_vec()
.to_sponge_field_elements(dest);
}
}
// Clippy flag needed for now.
#[allow(dead_code)]
/// A Witness in [Ova](https://hackmd.io/V4838nnlRKal9ZiTHiGYzw?view) is represented by `w`.
/// It also contains a blinder which can or not be used when committing to the witness itself.
#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)]
pub struct Witness<C: CurveGroup> {
pub w: Vec<C::ScalarField>,
pub rW: C::ScalarField,
}
impl<C: CurveGroup> Witness<C> {
/// Generates a new `Witness` instance from a given witness vector.
/// If `H = true`, then we assume we want to blind it at commitment time,
/// hence sampling `rW` from the randomness passed.
pub fn new<const H: bool>(w: Vec<C::ScalarField>, mut rng: impl RngCore) -> Self {
Self {
w,
rW: if H {
C::ScalarField::rand(&mut rng)
} else {
C::ScalarField::zero()
},
}
}
/// Computes the `W` or `W'` commitment (The accumulated-instance W' or the incoming-instance W)
/// as specified in Ova. See: <https://hackmd.io/V4838nnlRKal9ZiTHiGYzw?view#Construction>.
///
/// This is the result of concatenating the accumulated-instance `w` vector with
/// `e` or `t`.
/// Generates a [`CommittedInstance`] as a result which will or not be blinded depending on how the
/// const generic `HC` is set up.
///
/// This is the exact trick that allows Ova to save up 1 commitment with respect to Nova.
/// At the cost of loosing the PCD property and only maintaining the IVC one.
pub fn commit<CS: CommitmentScheme<C, HC>, const HC: bool>(
&self,
params: &CS::ProverParams,
t_or_e: Vec<C::ScalarField>,
x: Vec<C::ScalarField>,
) -> Result<CommittedInstance<C>, Error> {
let cmWE = CS::commit(params, &[self.w.clone(), t_or_e].concat(), &self.rW)?;
Ok(CommittedInstance {
mu: C::ScalarField::one(),
cmWE,
x,
})
}
}
impl<C: CurveGroup> Dummy<&R1CS<CF1<C>>> for Witness<C> {
fn dummy(r1cs: &R1CS<CF1<C>>) -> Self {
Self {
w: vec![C::ScalarField::zero(); r1cs.A.n_cols - 1 - r1cs.l],
rW: C::ScalarField::zero(),
}
}
}
pub struct ChallengeGadget<C: CurveGroup> {
_c: PhantomData<C>,
}
impl<C: CurveGroup> ChallengeGadget<C>
where
C: CurveGroup,
<C as CurveGroup>::BaseField: PrimeField,
<C as Group>::ScalarField: Absorb,
{
pub fn get_challenge_native<T: Transcript<C::ScalarField>>(
transcript: &mut T,
pp_hash: C::ScalarField, // public params hash
// Running instance
U_i: CommittedInstance<C>,
// Incoming instance
u_i: CommittedInstance<C>,
) -> Vec<bool> {
// NOTICE: This isn't following the order of the HackMD.
// As long as we match it. We should not have any issues.
transcript.absorb(&pp_hash);
transcript.absorb(&U_i);
transcript.absorb(&u_i);
transcript.squeeze_bits(NOVA_N_BITS_RO)
}
}
// Simple auxiliary structure mainly used to help pass a witness for which we can check
// easily an R1CS relation.
// Notice that checking it requires us to have `E` as per [`Arith`] trait definition.
// But since we don't hold `E` nor `e` within the NIFS, we create this structure to pass
// `e` such that the check can be done.
#[derive(Debug, Clone)]
pub(crate) struct TestingWitness<C: CurveGroup> {
pub(crate) w: Vec<C::ScalarField>,
pub(crate) e: Vec<C::ScalarField>,
}

+ 482
- 0
folding-schemes/src/folding/ova/nifs.rs

@ -0,0 +1,482 @@
/// This module contains the implementation of the Ova scheme NIFS (Non-Interactive Folding Scheme) as
/// outlined in the protocol description doc: <https://hackmd.io/V4838nnlRKal9ZiTHiGYzw?both#Construction>
/// authored by Benedikt Bünz.
use ark_crypto_primitives::sponge::Absorb;
use ark_ec::{CurveGroup, Group};
use ark_std::One;
use std::marker::PhantomData;
use super::{CommittedInstance, Witness};
use crate::arith::r1cs::R1CS;
use crate::commitment::CommitmentScheme;
use crate::transcript::Transcript;
use crate::utils::vec::{hadamard, mat_vec_mul, vec_scalar_mul, vec_sub};
use crate::Error;
/// Implements all the operations executed by the Non-Interactive Folding Scheme described in the protocol
/// spec by Bünz in the [original HackMD](https://hackmd.io/V4838nnlRKal9ZiTHiGYzw?both#Construction).
/// `H` specifies whether the NIFS will use a blinding factor
pub struct NIFS<C: CurveGroup, CS: CommitmentScheme<C, H>, const H: bool = false> {
_c: PhantomData<C>,
_cp: PhantomData<CS>,
}
impl<C: CurveGroup, CS: CommitmentScheme<C, H>, const H: bool> NIFS<C, CS, H>
where
<C as Group>::ScalarField: Absorb,
{
/// Computes the T parameter (Cross Terms) as in Nova.
/// The wrapper is only in place to facilitate the calling as we need
/// to reconstruct the `z`s being folded in order to compute T.
pub fn compute_T(
r1cs: &R1CS<C::ScalarField>,
w_i: &Witness<C>,
x_i: &[C::ScalarField],
W_i: &Witness<C>,
X_i: &[C::ScalarField],
mu: C::ScalarField,
) -> Result<Vec<C::ScalarField>, Error> {
crate::folding::nova::nifs::NIFS::<C, CS, H>::compute_T(
r1cs,
C::ScalarField::one(),
mu,
&[vec![C::ScalarField::one()], x_i.to_vec(), w_i.w.to_vec()].concat(),
&[vec![mu], X_i.to_vec(), W_i.w.to_vec()].concat(),
)
}
/// Computes the E parameter (Error Terms) as in Nova.
/// The wrapper is only in place to facilitate the calling as we need
/// to reconstruct the `z`s being folded in order to compute E.
///
/// Not only that, but notice that the incoming-instance `mu` parameter is always
/// equal to 1. Therefore, we can save the some computations.
pub fn compute_E(
r1cs: &R1CS<C::ScalarField>,
W_i: &Witness<C>,
X_i: &[C::ScalarField],
mu: C::ScalarField,
) -> Result<Vec<C::ScalarField>, Error> {
let (A, B, C) = (r1cs.A.clone(), r1cs.B.clone(), r1cs.C.clone());
let z_prime = [&[mu], X_i, &W_i.w].concat();
// this is parallelizable (for the future)
let Az_prime = mat_vec_mul(&A, &z_prime)?;
let Bz_prime = mat_vec_mul(&B, &z_prime)?;
let Cz_prime = mat_vec_mul(&C, &z_prime)?;
let Az_prime_Bz_prime = hadamard(&Az_prime, &Bz_prime)?;
let muCz_prime = vec_scalar_mul(&Cz_prime, &mu);
vec_sub(&Az_prime_Bz_prime, &muCz_prime)
}
/// Folds 2 [`CommittedInstance`]s returning a freshly folded one as is specified
/// in: <https://hackmd.io/V4838nnlRKal9ZiTHiGYzw?both#Construction>.
/// Here, alpha is a randomness sampled from a [`Transcript`].
pub fn fold_committed_instance(
alpha: C::ScalarField,
// This is W (incoming)
u_i: &CommittedInstance<C>,
// This is W' (running)
U_i: &CommittedInstance<C>,
) -> CommittedInstance<C> {
let mu = U_i.mu + alpha; // u_i.mu **IS ALWAYS 1 in OVA** as we just can do sequential IVC.
let cmWE = U_i.cmWE + u_i.cmWE.mul(alpha);
let x = U_i
.x
.iter()
.zip(&u_i.x)
.map(|(a, b)| *a + (alpha * b))
.collect::<Vec<C::ScalarField>>();
CommittedInstance::<C> { cmWE, mu, x }
}
/// Folds 2 [`Witness`]s returning a freshly folded one as is specified
/// in: <https://hackmd.io/V4838nnlRKal9ZiTHiGYzw?both#Construction>.
/// Here, alpha is a randomness sampled from a [`Transcript`].
pub fn fold_witness(
alpha: C::ScalarField,
// incoming instance
w_i: &Witness<C>,
// running instance
W_i: &Witness<C>,
) -> Result<Witness<C>, Error> {
let w: Vec<C::ScalarField> = W_i
.w
.iter()
.zip(&w_i.w)
.map(|(a, b)| *a + (alpha * b))
.collect();
let rW = W_i.rW + alpha * w_i.rW;
Ok(Witness::<C> { w, rW })
}
/// fold_instances is part of the NIFS.P logic described in
/// [Ova](https://hackmd.io/V4838nnlRKal9ZiTHiGYzw?both#Construction)'s Construction section.
/// It returns the folded [`CommittedInstance`] and [`Witness`].
pub fn fold_instances(
r: C::ScalarField,
// incoming instance
w_i: &Witness<C>,
u_i: &CommittedInstance<C>,
// running instance
W_i: &Witness<C>,
U_i: &CommittedInstance<C>,
) -> Result<(Witness<C>, CommittedInstance<C>), Error> {
// fold witness
let w3 = NIFS::<C, CS, H>::fold_witness(r, w_i, W_i)?;
// fold committed instances
let ci3 = NIFS::<C, CS, H>::fold_committed_instance(r, u_i, U_i);
Ok((w3, ci3))
}
/// Implements NIFS.V (accumulation verifier) logic described in [Ova](https://hackmd.io/V4838nnlRKal9ZiTHiGYzw?both#Construction)'s
/// Construction section.
/// It returns the folded [`CommittedInstance`].
pub fn verify(
// r comes from the transcript, and is a n-bit (N_BITS_CHALLENGE) element
alpha: C::ScalarField,
// incoming instance
u_i: &CommittedInstance<C>,
// running instance
U_i: &CommittedInstance<C>,
) -> CommittedInstance<C> {
NIFS::<C, CS, H>::fold_committed_instance(alpha, u_i, U_i)
}
#[cfg(test)]
/// Verify committed folded instance (ui) relations. Notice that this method does not open the
/// commitments, but just checks that the given committed instances (ui1, ui2) when folded
/// result in the folded committed instance (ui3) values.
pub(crate) fn verify_folded_instance(
r: C::ScalarField,
// incoming instance
u_i: &CommittedInstance<C>,
// running instance
U_i: &CommittedInstance<C>,
// folded instance
folded_instance: &CommittedInstance<C>,
) -> Result<(), Error> {
let expected = Self::fold_committed_instance(r, u_i, U_i);
if folded_instance.mu != expected.mu
|| folded_instance.cmWE != expected.cmWE
|| folded_instance.x != expected.x
{
return Err(Error::NotSatisfied);
}
Ok(())
}
/// Generates a [`CS::Proof`] for the given [`CommittedInstance`] and [`Witness`] pair.
pub fn prove_commitment(
r1cs: &R1CS<C::ScalarField>,
tr: &mut impl Transcript<C::ScalarField>,
cs_prover_params: &CS::ProverParams,
w: &Witness<C>,
ci: &CommittedInstance<C>,
) -> Result<CS::Proof, Error> {
let e = NIFS::<C, CS, H>::compute_E(r1cs, w, &ci.x, ci.mu).unwrap();
let w_concat_e: Vec<C::ScalarField> = [w.w.clone(), e].concat();
CS::prove(cs_prover_params, tr, &ci.cmWE, &w_concat_e, &w.rW, None)
}
}
#[cfg(test)]
pub mod tests {
use super::*;
use crate::folding::ova::ChallengeGadget;
use crate::transcript::poseidon::poseidon_canonical_config;
use crate::{
arith::{
r1cs::tests::{get_test_r1cs, get_test_z},
Arith,
},
folding::traits::Dummy,
};
use crate::{
commitment::pedersen::{Params as PedersenParams, Pedersen},
folding::ova::TestingWitness,
};
use ark_crypto_primitives::sponge::{
poseidon::{PoseidonConfig, PoseidonSponge},
CryptographicSponge,
};
use ark_ff::{BigInteger, PrimeField};
use ark_pallas::{Fr, Projective};
use ark_std::{test_rng, UniformRand, Zero};
fn compute_E_check_relation<C: CurveGroup, CS: CommitmentScheme<C, H>, const H: bool>(
r1cs: &R1CS<C::ScalarField>,
w: &Witness<C>,
u: &CommittedInstance<C>,
) where
<C as Group>::ScalarField: Absorb,
{
let e = NIFS::<C, CS, H>::compute_E(r1cs, w, &u.x, u.mu).unwrap();
r1cs.check_relation(&TestingWitness::<C> { e, w: w.w.clone() }, u)
.unwrap();
}
#[allow(clippy::type_complexity)]
pub(crate) fn prepare_simple_fold_inputs<C>() -> (
PedersenParams<C>,
PoseidonConfig<C::ScalarField>,
R1CS<C::ScalarField>,
Witness<C>, // w
CommittedInstance<C>, // u
Witness<C>, // W
CommittedInstance<C>, // U
Witness<C>, // w_fold
CommittedInstance<C>, // u_fold
Vec<bool>, // r_bits
C::ScalarField, // r_Fr
)
where
C: CurveGroup,
<C as CurveGroup>::BaseField: PrimeField,
C::ScalarField: Absorb,
{
// Index 1 represents the incoming instance
// Index 2 represents the accumulated instance
let r1cs = get_test_r1cs();
let z1 = get_test_z(3);
let z2 = get_test_z(4);
let (w1, x1) = r1cs.split_z(&z1);
let (w2, x2) = r1cs.split_z(&z2);
let w = Witness::<C>::new::<false>(w1.clone(), test_rng());
let W: Witness<C> = Witness::<C>::new::<false>(w2.clone(), test_rng());
let mut rng = ark_std::test_rng();
let (pedersen_params, _) = Pedersen::<C>::setup(&mut rng, r1cs.A.n_cols).unwrap();
// In order to be able to compute the committed instances, we need to compute `t` and `e`
// compute t
let t = NIFS::<C, Pedersen<C>>::compute_T(&r1cs, &w, &x1, &W, &x2, C::ScalarField::one())
.unwrap();
// compute e (mu is 1 although is the running instance as we are "crafting it").
let e = NIFS::<C, Pedersen<C>>::compute_E(&r1cs, &W, &x2, C::ScalarField::one()).unwrap();
// compute committed instances
// Incoming-instance
let u = w
.commit::<Pedersen<C>, false>(&pedersen_params, t, x1.clone())
.unwrap();
// Running-instance
let U = W
.commit::<Pedersen<C>, false>(&pedersen_params, e, x2.clone())
.unwrap();
let poseidon_config = poseidon_canonical_config::<C::ScalarField>();
let mut transcript = PoseidonSponge::<C::ScalarField>::new(&poseidon_config);
let pp_hash = C::ScalarField::from(42u32); // only for test
let alpha_bits = ChallengeGadget::<C>::get_challenge_native(
&mut transcript,
pp_hash,
u.clone(),
U.clone(),
);
let alpha_Fr = C::ScalarField::from_bigint(BigInteger::from_bits_le(&alpha_bits)).unwrap();
let (w_fold, u_fold) =
NIFS::<C, Pedersen<C>, false>::fold_instances(alpha_Fr, &w, &u, &W, &U).unwrap();
// Check correctness of the R1CS relation of the folded instance.
compute_E_check_relation::<C, Pedersen<C>, false>(&r1cs, &w_fold, &u_fold);
(
pedersen_params,
poseidon_config,
r1cs,
w,
u,
W,
U,
w_fold,
u_fold,
alpha_bits,
alpha_Fr,
)
}
// fold 2 dummy instances and check that the folded instance holds the relaxed R1CS relation
#[test]
fn test_nifs_fold_dummy() {
let mut rng = ark_std::test_rng();
let r1cs = get_test_r1cs::<Fr>();
let w_dummy = Witness::<Projective>::dummy(&r1cs);
let (pedersen_params, _) = Pedersen::<Projective>::setup(&mut rng, r1cs.A.n_cols).unwrap();
// In order to be able to compute the committed instances, we need to compute `t` and `e`
// compute t
let t = NIFS::<Projective, Pedersen<Projective>>::compute_T(
&r1cs,
&w_dummy,
&[Fr::zero()],
&w_dummy,
&[Fr::zero()],
Fr::one(),
)
.unwrap();
// compute e
let e = NIFS::<Projective, Pedersen<Projective>>::compute_E(
&r1cs,
&w_dummy,
&[Fr::zero()],
Fr::one(),
)
.unwrap();
// dummy incoming instance, witness and public inputs
let u_dummy = w_dummy
.commit::<Pedersen<Projective>, false>(&pedersen_params, t, vec![Fr::zero()])
.unwrap();
// dummy accumulated instance, witness and public inputs
let U_dummy = w_dummy
.commit::<Pedersen<Projective>, false>(&pedersen_params, e.clone(), vec![Fr::zero()])
.unwrap();
let w_i = w_dummy.clone();
let u_i = u_dummy.clone();
let W_i = w_dummy.clone();
let U_i = U_dummy.clone();
// Check correctness of the R1CS relations of both instances.
compute_E_check_relation::<Projective, Pedersen<Projective>, false>(&r1cs, &w_i, &u_i);
compute_E_check_relation::<Projective, Pedersen<Projective>, false>(&r1cs, &W_i, &U_i);
// NIFS.P
let r_Fr = Fr::from(3_u32);
let (w_fold, u_fold) =
NIFS::<Projective, Pedersen<Projective>>::fold_instances(r_Fr, &w_i, &u_i, &W_i, &U_i)
.unwrap();
// Check correctness of the R1CS relation of both instances.
compute_E_check_relation::<Projective, Pedersen<Projective>, false>(
&r1cs, &w_fold, &u_fold,
);
}
// fold 2 instances into one
#[test]
fn test_nifs_one_fold() {
let (pedersen_params, poseidon_config, r1cs, w, u, W, U, w_fold, u_fold, _, r) =
prepare_simple_fold_inputs();
// NIFS.V
let u_fold_v = NIFS::<Projective, Pedersen<Projective>>::verify(r, &u, &U);
assert_eq!(u_fold_v, u_fold);
// Check that relations hold for the 2 inputted instances and the folded one
compute_E_check_relation::<Projective, Pedersen<Projective>, false>(&r1cs, &w, &u);
compute_E_check_relation::<Projective, Pedersen<Projective>, false>(&r1cs, &W, &U);
compute_E_check_relation::<Projective, Pedersen<Projective>, false>(
&r1cs, &w_fold, &u_fold,
);
// check that folded commitments from folded instance (u) are equal to folding the
// use folded rW to commit w_fold
let e_fold = NIFS::<Projective, Pedersen<Projective>>::compute_E(
&r1cs, &w_fold, &u_fold.x, u_fold.mu,
)
.unwrap();
let mut u_fold_expected = w_fold
.commit::<Pedersen<Projective>, false>(&pedersen_params, e_fold, u_fold.x.clone())
.unwrap();
u_fold_expected.mu = u_fold.mu;
assert_eq!(u_fold_expected.cmWE, u_fold.cmWE);
// NIFS.Verify_Folded_Instance:
NIFS::<Projective, Pedersen<Projective>>::verify_folded_instance(r, &u, &U, &u_fold)
.unwrap();
// init Prover's transcript
let mut transcript_p = PoseidonSponge::<Fr>::new(&poseidon_config);
// init Verifier's transcript
let mut transcript_v = PoseidonSponge::<Fr>::new(&poseidon_config);
// prove the u_fold.cmWE
let cm_proof = NIFS::<Projective, Pedersen<Projective>>::prove_commitment(
&r1cs,
&mut transcript_p,
&pedersen_params,
&w_fold,
&u_fold,
)
.unwrap();
// verify the u_fold.cmWE.
Pedersen::<Projective>::verify(
&pedersen_params,
&mut transcript_v,
&u_fold.cmWE,
&cm_proof,
)
.unwrap();
}
#[test]
fn test_nifs_fold_loop() {
let r1cs = get_test_r1cs();
let z = get_test_z(3);
let (w, x) = r1cs.split_z(&z);
let mut rng = ark_std::test_rng();
let (pedersen_params, _) = Pedersen::<Projective>::setup(&mut rng, r1cs.A.n_cols).unwrap();
// prepare the running instance
let mut W = Witness::<Projective>::new::<false>(w.clone(), &mut rng);
// Compute e
let e =
NIFS::<Projective, Pedersen<Projective>>::compute_E(&r1cs, &W, &x, Fr::one()).unwrap();
// Compute running `CommittedInstance`.
let mut U = W
.commit::<Pedersen<Projective>, false>(&pedersen_params, e, x)
.unwrap();
let num_iters = 10;
for i in 0..num_iters {
// prepare the incoming instance
let incoming_instance_z = get_test_z(i + 4);
let (w, x) = r1cs.split_z(&incoming_instance_z);
let w = Witness::<Projective>::new::<false>(w, test_rng());
let t =
NIFS::<Projective, Pedersen<Projective>>::compute_T(&r1cs, &w, &U.x, &w, &x, U.mu)
.unwrap();
let u = w
.commit::<Pedersen<Projective>, false>(&pedersen_params, t, x)
.unwrap();
// Check incoming instance is Ok.
compute_E_check_relation::<Projective, Pedersen<Projective>, false>(&r1cs, &w, &u);
// Generate "transcript randomness"
let alpha = Fr::rand(&mut rng); // folding challenge would come from the RO
// NIFS.P
let (w_folded, _) =
NIFS::<Projective, Pedersen<Projective>>::fold_instances(alpha, &w, &u, &W, &u)
.unwrap();
// NIFS.V
let u_folded = NIFS::<Projective, Pedersen<Projective>>::verify(alpha, &u, &U);
compute_E_check_relation::<Projective, Pedersen<Projective>, false>(
&r1cs, &w_folded, &u_folded,
);
// set running_instance for next loop iteration
W = w_folded;
U = u_folded;
}
}
}

Loading…
Cancel
Save