Skip to content

Commit

Permalink
Implemented Mova folding scheme (#161)
Browse files Browse the repository at this point in the history
* Adding Mova

Co-Authored-By: Togzhan Barakbayeva <[email protected]>
Co-Authored-By: Ilia Vlasov <[email protected]>
Co-Authored-By: matthew-a-klein <[email protected]>

* Fix CLI

* Updated from main

* Solution to stop the CLI from complaining about deadcode

PR comment

Co-authored-by: arnaucube <[email protected]>

* Requested changes and update from main

* Refactor NIFSTrait & port Mova impl to it

* refactor NIFSTrait interface to fit Nova variants (Nova,Mova,Ova)

  Refactor NIFSTrait interface to fit Nova variants (Nova,Mova,Ova). The relevant
  change is instead of passing the challenge as input, now it passes the
  transcript and computes the challenges internally (Nova & Ova still compute a
  single challenge, but Mova computes multiple while absorbing at different
  steps).

* port Mova impl to the NIFSTrait

* remove unnecessary wrappers in the nova/zk.rs

* remove Nova NIFS methods that are no longer needed after the refactor

* put together the different NIFS implementations (Nova, Mova, Ova) so
  that they can interchanged at usage.

The idea is that Nova and its variants (Ova & Mova) share most of the
logic for the circuits & IVC & Deciders, so with the abstracted NIFS
interface we will be able to reuse most of the already existing Nova
code for having the Mova & Ova circuits, IVC, and Decider.

* adapt Nova's DeciderEth prepare_calldata & update examples to it

* small update to fix solidity tests

---------

Co-authored-by: Togzhan Barakbayeva <[email protected]>
Co-authored-by: Ilia Vlasov <[email protected]>
Co-authored-by: matthew-a-klein <[email protected]>
Co-authored-by: arnaucube <[email protected]>
Co-authored-by: arnaucube <[email protected]>
  • Loading branch information
6 people authored Oct 23, 2024
1 parent 234600b commit 6d8f297
Show file tree
Hide file tree
Showing 19 changed files with 1,239 additions and 485 deletions.
2 changes: 2 additions & 0 deletions examples/circom_full_flow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ fn main() {
// prepare the Nova prover & verifier params
let nova_preprocess_params = PreprocessorParam::new(poseidon_config, f_circuit.clone());
let nova_params = N::preprocess(&mut rng, &nova_preprocess_params).unwrap();
let pp_hash = nova_params.1.pp_hash().unwrap();

// initialize the folding scheme engine, in our case we use Nova
let mut nova = N::init(&nova_params, f_circuit.clone(), z_0).unwrap();
Expand Down Expand Up @@ -130,6 +131,7 @@ fn main() {

let calldata: Vec<u8> = prepare_calldata(
function_selector,
pp_hash,
nova.i,
nova.z_0,
nova.z_i,
Expand Down
2 changes: 2 additions & 0 deletions examples/full_flow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ fn main() {
// prepare the Nova prover & verifier params
let nova_preprocess_params = PreprocessorParam::new(poseidon_config.clone(), f_circuit);
let nova_params = N::preprocess(&mut rng, &nova_preprocess_params).unwrap();
let pp_hash = nova_params.1.pp_hash().unwrap();

// initialize the folding scheme engine, in our case we use Nova
let mut nova = N::init(&nova_params, f_circuit, z_0).unwrap();
Expand Down Expand Up @@ -138,6 +139,7 @@ fn main() {

let calldata: Vec<u8> = prepare_calldata(
function_selector,
pp_hash,
nova.i,
nova.z_0,
nova.z_i,
Expand Down
2 changes: 2 additions & 0 deletions examples/noir_full_flow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ fn main() {
// prepare the Nova prover & verifier params
let nova_preprocess_params = PreprocessorParam::new(poseidon_config, f_circuit.clone());
let nova_params = N::preprocess(&mut rng, &nova_preprocess_params).unwrap();
let pp_hash = nova_params.1.pp_hash().unwrap();

// initialize the folding scheme engine, in our case we use Nova
let mut nova = N::init(&nova_params, f_circuit.clone(), z_0).unwrap();
Expand Down Expand Up @@ -117,6 +118,7 @@ fn main() {

let calldata: Vec<u8> = prepare_calldata(
function_selector,
pp_hash,
nova.i,
nova.z_0,
nova.z_i,
Expand Down
2 changes: 2 additions & 0 deletions examples/noname_full_flow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ fn main() {
// prepare the Nova prover & verifier params
let nova_preprocess_params = PreprocessorParam::new(poseidon_config, f_circuit.clone());
let nova_params = N::preprocess(&mut rng, &nova_preprocess_params).unwrap();
let pp_hash = nova_params.1.pp_hash().unwrap();

// initialize the folding scheme engine, in our case we use Nova
let mut nova = N::init(&nova_params, f_circuit.clone(), z_0).unwrap();
Expand Down Expand Up @@ -132,6 +133,7 @@ fn main() {

let calldata: Vec<u8> = prepare_calldata(
function_selector,
pp_hash,
nova.i,
nova.z_0,
nova.z_i,
Expand Down
114 changes: 97 additions & 17 deletions folding-schemes/src/folding/circuits/cyclefold.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@ use super::{nonnative::uint::NonNativeUintVar, CF1, CF2};
use crate::arith::r1cs::{extract_w_x, R1CS};
use crate::commitment::CommitmentScheme;
use crate::constants::NOVA_N_BITS_RO;
use crate::folding::nova::{nifs::NIFS, traits::NIFSTrait};
use crate::folding::nova::nifs::{nova::NIFS, NIFSTrait};
use crate::transcript::{AbsorbNonNative, AbsorbNonNativeGadget, Transcript, TranscriptVar};
use crate::Error;
use ark_crypto_primitives::sponge::poseidon::PoseidonSponge;

/// Re-export the Nova committed instance as `CycleFoldCommittedInstance` and
/// witness as `CycleFoldWitness`, for clarity and consistency
Expand Down Expand Up @@ -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
/// scheme struct because it is used both by Nova & HyperNova's CycleFold.
#[allow(clippy::type_complexity)]
Expand Down Expand Up @@ -551,14 +618,15 @@ where
cf_w_i.commit::<CS2, H>(&cf_cs_params, cf_x_i.clone())?;

// 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(
transcript,
Expand All @@ -570,8 +638,11 @@ where
let cf_r_Fq = C1::BaseField::from_bigint(BigInteger::from_bits_le(&cf_r_bits))
.expect("cf_r_bits out of bounds");

let (cf_W_i1, cf_U_i1) =
NIFS::<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))
}

Expand Down Expand Up @@ -671,6 +742,10 @@ pub mod tests {
fn test_nifs_full_gadget() {
let mut rng = ark_std::test_rng();

let poseidon_config = poseidon_canonical_config::<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
let ci: Vec<CommittedInstance<Projective>> = (0..2)
.into_iter()
Expand All @@ -685,11 +760,16 @@ pub mod tests {
// make the 2nd instance a 'fresh' instance (ie. cmE=0, u=1)
ci2.cmE = Projective::zero();
ci2.u = Fr::one();
let r_bits: Vec<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 r_bitsVar = Vec::<Boolean<Fq>>::new_witness(cs.clone(), || Ok(r_bits)).unwrap();
Expand Down Expand Up @@ -737,7 +817,7 @@ pub mod tests {
.take(TestCycleFoldConfig::<Projective, 2>::IO_LEN)
.collect(),
};
let cmT = Projective::rand(&mut rng);
let cmT = Projective::rand(&mut rng); // random only for testing

// compute the challenge natively
let pp_hash = Fq::from(42u32); // only for test
Expand Down
17 changes: 13 additions & 4 deletions folding-schemes/src/folding/nova/circuits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -529,8 +529,7 @@ pub mod tests {
use ark_std::UniformRand;

use crate::commitment::pedersen::Pedersen;
use crate::folding::nova::nifs::NIFS;
use crate::folding::nova::traits::NIFSTrait;
use crate::folding::nova::nifs::{nova::NIFS, NIFSTrait};
use crate::folding::traits::CommittedInstanceOps;
use crate::transcript::poseidon::poseidon_canonical_config;

Expand Down Expand Up @@ -570,9 +569,19 @@ pub mod tests {
})
.collect();
let (ci1, ci2) = (ci[0].clone(), ci[1].clone());
let r_Fr = Fr::rand(&mut rng);
let pp_hash = Fr::rand(&mut rng);
let cmT = Projective::rand(&mut rng);
let ci3 = NIFS::<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();

Expand Down
25 changes: 18 additions & 7 deletions folding-schemes/src/folding/nova/decider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
/// DeciderEth from decider_eth.rs file.
/// More details can be found at the documentation page:
/// https://privacy-scaling-explorations.github.io/sonobe-docs/design/nova-decider-offchain.html
use ark_crypto_primitives::sponge::Absorb;
use ark_crypto_primitives::sponge::{poseidon::PoseidonSponge, Absorb, CryptographicSponge};
use ark_ec::{AffineRepr, CurveGroup, Group};
use ark_ff::{BigInteger, PrimeField};
use ark_r1cs_std::{groups::GroupOpsBounds, prelude::CurveVar, ToConstraintFieldGadget};
Expand All @@ -13,14 +13,18 @@ use ark_std::{One, Zero};
use core::marker::PhantomData;

use super::decider_circuits::{DeciderCircuit1, DeciderCircuit2};
use super::{nifs::NIFS, traits::NIFSTrait, CommittedInstance, Nova};
use super::{
nifs::{nova::NIFS, NIFSTrait},
CommittedInstance, Nova,
};
use crate::commitment::CommitmentScheme;
use crate::folding::circuits::{
cyclefold::CycleFoldCommittedInstance,
nonnative::{affine::NonNativeAffineVar, uint::NonNativeUintVar},
CF2,
};
use crate::frontend::FCircuit;
use crate::transcript::poseidon::poseidon_canonical_config;
use crate::Error;
use crate::{Decider as DeciderTrait, FoldingScheme};

Expand All @@ -41,7 +45,6 @@ where
// cmT and r are values for the last fold, U_{i+1}=NIFS.V(r, U_i, u_i, cmT), and they are
// checked in-circuit
cmT: C1,
r: C1::ScalarField,
// cyclefold committed instance
cf_U_i: CycleFoldCommittedInstance<C2>,
// the CS challenges are provided by the prover, but in-circuit they are checked to match the
Expand Down Expand Up @@ -209,7 +212,6 @@ where
.map_err(|e| Error::Other(e.to_string()))?;

let cmT = circuit1.cmT.unwrap();
let r_Fr = circuit1.r.unwrap();
let W_i1 = circuit1.W_i1.unwrap();
let cf_W_i = circuit2.cf_W_i.unwrap();

Expand Down Expand Up @@ -265,7 +267,6 @@ where
cs1_proofs: [U_cmW_proof, U_cmE_proof],
cs2_proofs: [cf_cmW_proof, cf_cmE_proof],
cmT,
r: r_Fr,
cf_U_i: circuit1.cf_U_i.unwrap(),
cs1_challenges: [challenge_W, challenge_E],
cs2_challenges: [c2_challenge_W, c2_challenge_E],
Expand All @@ -286,7 +287,17 @@ where
}

// 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 (cmW_x, cmW_y) = NonNativeAffineVar::inputize(U.cmW)?;
Expand Down Expand Up @@ -332,7 +343,7 @@ where
// NIFS values:
cmT_x,
cmT_y,
vec![proof.r],
vec![r],
]
.concat();

Expand Down
20 changes: 6 additions & 14 deletions folding-schemes/src/folding/nova/decider_circuits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,7 @@ use super::{
decider_eth_circuit::{
evaluate_gadget, KZGChallengesGadget, R1CSVar, RelaxedR1CSGadget, WitnessVar,
},
nifs::NIFS,
traits::NIFSTrait,
nifs::{nova::NIFS, NIFSTrait},
CommittedInstance, Nova, Witness,
};
use crate::arith::r1cs::R1CS;
Expand Down Expand Up @@ -114,28 +113,21 @@ where
CS2: CommitmentScheme<C2, H>,
{
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}
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.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,
nova.pp_hash,
&nova.W_i,
&nova.U_i,
&nova.w_i,
&nova.u_i,
&cmT,
);
)?;
let r_Fr = C1::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits))
.ok_or(Error::OutOfBounds)?;
let (W_i1, U_i1) =
NIFS::<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
let (cs_challenge_W, cs_challenge_E) =
Expand Down
Loading

0 comments on commit 6d8f297

Please sign in to comment.