Browse Source

Add Decider impl for Nova onchain (#66)

* Add Decider impl for Nova onchain

Add Decider impl for Nova onchain.
Update also the Decider trait.

Nova onchain decider: (compressed SNARK / final proof), in order to
later verify the Nova+CycleFold proofs onchain (in Ethereum’s EVM).

* PR review updates and few other changes
main
arnaucube 10 months ago
committed by GitHub
parent
commit
97e973a685
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
6 changed files with 278 additions and 35 deletions
  1. +8
    -0
      Cargo.toml
  2. +31
    -9
      src/folding/circuits/nonnative.rs
  3. +205
    -0
      src/folding/nova/decider_eth.rs
  4. +7
    -7
      src/folding/nova/decider_eth_circuit.rs
  5. +3
    -3
      src/folding/nova/mod.rs
  6. +24
    -16
      src/lib.rs

+ 8
- 0
Cargo.toml

@ -12,6 +12,7 @@ ark-crypto-primitives = { version = "^0.4.0", default-features = false, features
ark-poly-commit = "^0.4.0"
ark-relations = { version = "^0.4.0", default-features = false }
ark-r1cs-std = { default-features = false } # use latest version from the patch
ark-snark = { version = "^0.4.0"}
ark-serialize = "^0.4.0"
ark-circom = { git = "https://github.com/gakonst/ark-circom.git" }
thiserror = "1.0"
@ -26,6 +27,10 @@ espresso_subroutines = {git="https://github.com/EspressoSystems/hyperplonk", pac
ark-pallas = {version="0.4.0", features=["r1cs"]}
ark-vesta = {version="0.4.0", features=["r1cs"]}
ark-bn254 = "0.4.0"
ark-mnt4-298 = {version="0.4.0", features=["r1cs"]}
ark-mnt6-298 = {version="0.4.0", features=["r1cs"]}
ark-groth16 = { version = "^0.4.0" }
rand = "0.8.5"
tracing = { version = "0.1", default-features = false, features = [ "attributes" ] }
tracing-subscriber = { version = "0.2" }
@ -35,7 +40,10 @@ default = ["parallel"]
parallel = [
"ark-std/parallel",
"ark-ff/parallel",
"ark-ec/parallel",
"ark-poly/parallel",
"ark-crypto-primitives/parallel",
"ark-r1cs-std/parallel",
]
# The following patch is to use a version of ark-r1cs-std compatible with

+ 31
- 9
src/folding/circuits/nonnative.rs

@ -54,12 +54,26 @@ where
}
}
/// point_to_nonnative_limbs is used to compute (outside the circuit) the limbs representation of a
/// point that matches the one used in-circuit.
/// Wrapper on top of [`point_to_nonnative_limbs_custom_opt`] which always uses
/// [`OptimizationType::Weight`].
#[allow(clippy::type_complexity)]
pub fn point_to_nonnative_limbs<C: CurveGroup>(
p: C,
) -> Result<(Vec<C::ScalarField>, Vec<C::ScalarField>), SynthesisError>
where
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
{
point_to_nonnative_limbs_custom_opt(p, OptimizationType::Weight)
}
/// Used to compute (outside the circuit) the limbs representation of a point that matches the one
/// used in-circuit, and in particular this method allows to specify which [`OptimizationType`] to
/// use.
#[allow(clippy::type_complexity)]
pub fn point_to_nonnative_limbs_custom_opt<C: CurveGroup>(
p: C,
optimization_type: OptimizationType,
) -> Result<(Vec<C::ScalarField>, Vec<C::ScalarField>), SynthesisError>
where
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
{
@ -68,12 +82,12 @@ where
let x =
AllocatedNonNativeFieldVar::<C::BaseField, C::ScalarField>::get_limbs_representations(
&C::BaseField::zero(),
OptimizationType::Weight,
optimization_type,
)?;
let y =
AllocatedNonNativeFieldVar::<C::BaseField, C::ScalarField>::get_limbs_representations(
&C::BaseField::one(),
OptimizationType::Weight,
optimization_type,
)?;
return Ok((x, y));
}
@ -81,11 +95,11 @@ where
let (x, y) = affine.xy().unwrap();
let x = AllocatedNonNativeFieldVar::<C::BaseField, C::ScalarField>::get_limbs_representations(
x,
OptimizationType::Weight,
optimization_type,
)?;
let y = AllocatedNonNativeFieldVar::<C::BaseField, C::ScalarField>::get_limbs_representations(
y,
OptimizationType::Weight,
optimization_type,
)?;
Ok((x, y))
}
@ -94,16 +108,24 @@ where
mod tests {
use super::*;
use ark_pallas::{Fr, Projective};
use ark_r1cs_std::alloc::AllocVar;
use ark_r1cs_std::{alloc::AllocVar, R1CSVar};
use ark_relations::r1cs::ConstraintSystem;
use ark_std::Zero;
use ark_std::{UniformRand, Zero};
#[test]
fn test_alloc_nonnativeaffinevar_zero() {
fn test_alloc_nonnativeaffinevar() {
let cs = ConstraintSystem::<Fr>::new_ref();
// dealing with the 'zero' point should not panic when doing the unwrap
let p = Projective::zero();
NonNativeAffineVar::<Fr>::new_witness(cs.clone(), || Ok(p)).unwrap();
// check that point_to_nonnative_limbs returns the expected values
let mut rng = ark_std::test_rng();
let p = Projective::rand(&mut rng);
let pVar = NonNativeAffineVar::<Fr>::new_witness(cs.clone(), || Ok(p)).unwrap();
let (x, y) = point_to_nonnative_limbs(p).unwrap();
assert_eq!(pVar.x.value().unwrap(), x);
assert_eq!(pVar.y.value().unwrap(), y);
}
}

+ 205
- 0
src/folding/nova/decider_eth.rs

@ -0,0 +1,205 @@
/// This file implements the onchain (Ethereum's EVM) decider.
use ark_crypto_primitives::sponge::Absorb;
use ark_ec::{CurveGroup, Group};
use ark_ff::PrimeField;
use ark_r1cs_std::{groups::GroupOpsBounds, prelude::CurveVar};
use ark_snark::SNARK;
use ark_std::rand::CryptoRng;
use ark_std::rand::RngCore;
use core::marker::PhantomData;
pub use super::decider_eth_circuit::DeciderEthCircuit;
use crate::commitment::{pedersen::Params as PedersenParams, CommitmentProver};
use crate::folding::circuits::nonnative::point_to_nonnative_limbs_custom_opt;
use crate::folding::nova::{circuits::CF2, CommittedInstance, Nova};
use crate::frontend::FCircuit;
use crate::Error;
use crate::{Decider as DeciderTrait, FoldingScheme};
use ark_r1cs_std::fields::nonnative::params::OptimizationType;
/// Onchain Decider, for ethereum use cases
#[derive(Clone, Debug)]
pub struct Decider<C1, GC1, C2, GC2, FC, CP1, CP2, S, FS> {
_c1: PhantomData<C1>,
_gc1: PhantomData<GC1>,
_c2: PhantomData<C2>,
_gc2: PhantomData<GC2>,
_fc: PhantomData<FC>,
_cp1: PhantomData<CP1>,
_cp2: PhantomData<CP2>,
_s: PhantomData<S>,
_fs: PhantomData<FS>,
}
impl<C1, GC1, C2, GC2, FC, CP1, CP2, S, FS> DeciderTrait<C1, C2, FC, FS>
for Decider<C1, GC1, C2, GC2, FC, CP1, CP2, S, FS>
where
C1: CurveGroup,
C2: CurveGroup,
GC1: CurveVar<C1, CF2<C1>>,
GC2: CurveVar<C2, CF2<C2>>,
FC: FCircuit<C1::ScalarField>,
CP1: CommitmentProver<C1>,
// enforce that the CP2 is Pedersen commitment, since we're at Ethereum's EVM decider
CP2: CommitmentProver<C2, Params = PedersenParams<C2>>,
S: SNARK<C1::ScalarField>,
FS: FoldingScheme<C1, C2, FC>,
<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>,
for<'b> &'b GC2: GroupOpsBounds<'b, C2, GC2>,
// constrain FS into Nova, since this is a Decider specificly for Nova
Nova<C1, GC1, C2, GC2, FC, CP1, CP2>: From<FS>,
{
type ProverParam = S::ProvingKey;
type Proof = S::Proof;
type VerifierParam = S::VerifyingKey;
type PublicInput = Vec<C1::ScalarField>;
type CommittedInstanceWithWitness = ();
type CommittedInstance = CommittedInstance<C1>;
fn prove(
pp: &Self::ProverParam,
mut rng: impl RngCore + CryptoRng,
folding_scheme: FS,
) -> Result<Self::Proof, Error> {
let circuit =
DeciderEthCircuit::<C1, GC1, C2, GC2, CP1, CP2>::from_nova::<FC>(folding_scheme.into());
let proof = S::prove(pp, circuit.clone(), &mut rng).unwrap();
Ok(proof)
}
fn verify(
vp: &Self::VerifierParam,
i: C1::ScalarField,
z_0: Vec<C1::ScalarField>,
z_i: Vec<C1::ScalarField>,
running_instance: &Self::CommittedInstance,
proof: Self::Proof,
) -> Result<bool, Error> {
let (cmE_x, cmE_y) = point_to_nonnative_limbs_custom_opt::<C1>(
running_instance.cmE,
OptimizationType::Constraints,
)?;
let (cmW_x, cmW_y) = point_to_nonnative_limbs_custom_opt::<C1>(
running_instance.cmW,
OptimizationType::Constraints,
)?;
let public_input: Vec<C1::ScalarField> = vec![
vec![i],
z_0,
z_i,
vec![running_instance.u],
running_instance.x.clone(),
cmE_x,
cmE_y,
cmW_x,
cmW_y,
]
.concat();
S::verify(vp, &public_input, &proof).map_err(|e| Error::Other(e.to_string()))
}
}
#[cfg(test)]
pub mod tests {
use super::*;
use ark_groth16::Groth16;
use ark_mnt4_298::{constraints::G1Var as GVar, Fr, G1Projective as Projective, MNT4_298};
use ark_mnt6_298::{constraints::G1Var as GVar2, G1Projective as Projective2};
use std::time::Instant;
use crate::commitment::pedersen::Pedersen;
use crate::folding::nova::{get_pedersen_params_len, ProverParams};
use crate::frontend::tests::CubicFCircuit;
use crate::transcript::poseidon::poseidon_test_config;
// Note: since we're testing a big circuit, this test takes a bit more of computation and time,
// do not run in the normal CI.
// To run the test use `--ignored` flag, eg. `cargo test -- --ignored`
#[test]
#[ignore]
fn test_decider() {
type NOVA = Nova<
Projective,
GVar,
Projective2,
GVar2,
CubicFCircuit<Fr>,
Pedersen<Projective>,
Pedersen<Projective2>,
>;
type DECIDER = Decider<
Projective,
GVar,
Projective2,
GVar2,
CubicFCircuit<Fr>,
Pedersen<Projective>,
Pedersen<Projective2>,
Groth16<MNT4_298>, // here we define the Snark to use in the decider
NOVA, // here we define the FoldingScheme to use
>;
let mut rng = ark_std::test_rng();
let poseidon_config = poseidon_test_config::<Fr>();
let F_circuit = CubicFCircuit::<Fr>::new(());
let z_0 = vec![Fr::from(3_u32)];
let (cm_len, cf_cm_len) =
get_pedersen_params_len::<Projective, GVar, Projective2, GVar2, CubicFCircuit<Fr>>(
&poseidon_config,
F_circuit,
)
.unwrap();
let pedersen_params = Pedersen::<Projective>::new_params(&mut rng, cm_len);
let cf_pedersen_params = Pedersen::<Projective2>::new_params(&mut rng, cf_cm_len);
let start = Instant::now();
let prover_params =
ProverParams::<Projective, Projective2, Pedersen<Projective>, Pedersen<Projective2>> {
poseidon_config: poseidon_config.clone(),
cm_params: pedersen_params,
cf_cm_params: cf_pedersen_params,
};
println!("generating pedersen params, {:?}", start.elapsed());
// use Nova as FoldingScheme
let start = Instant::now();
let mut nova = NOVA::init(&prover_params, F_circuit, z_0.clone()).unwrap();
println!("Nova initialized, {:?}", start.elapsed());
let start = Instant::now();
nova.prove_step().unwrap();
println!("prove_step, {:?}", start.elapsed());
// generate Groth16 setup
let circuit = DeciderEthCircuit::<
Projective,
GVar,
Projective2,
GVar2,
Pedersen<Projective>,
Pedersen<Projective2>,
>::from_nova::<CubicFCircuit<Fr>>(nova.clone());
let mut rng = rand::rngs::OsRng;
let start = Instant::now();
let (pk, vk) =
Groth16::<MNT4_298>::circuit_specific_setup(circuit.clone(), &mut rng).unwrap();
println!("Groth16 setup, {:?}", start.elapsed());
// decider proof generation
let start = Instant::now();
let proof = DECIDER::prove(&pk, rng, nova.clone()).unwrap();
println!("Decider Groth16 prove, {:?}", start.elapsed());
// decider proof verification
let verified = DECIDER::verify(&vk, nova.i, nova.z_0, nova.z_i, &nova.U_i, proof).unwrap();
assert!(verified);
}
}

+ 7
- 7
src/folding/nova/decider_eth_circuit.rs

@ -178,6 +178,7 @@ where
/// Circuit that implements the in-circuit checks needed for the onchain (Ethereum's EVM)
/// verification.
#[derive(Clone, Debug)]
pub struct DeciderEthCircuit<C1, GC1, C2, GC2, CP1, CP2>
where
C1: CurveGroup,
@ -281,13 +282,12 @@ where
Ok(self.r1cs.clone())
})?;
let i = FpVar::<CF1<C1>>::new_witness(cs.clone(), || {
Ok(self.i.unwrap_or_else(CF1::<C1>::zero))
})?;
let z_0 = Vec::<FpVar<CF1<C1>>>::new_witness(cs.clone(), || {
let i =
FpVar::<CF1<C1>>::new_input(cs.clone(), || Ok(self.i.unwrap_or_else(CF1::<C1>::zero)))?;
let z_0 = Vec::<FpVar<CF1<C1>>>::new_input(cs.clone(), || {
Ok(self.z_0.unwrap_or(vec![CF1::<C1>::zero()]))
})?;
let z_i = Vec::<FpVar<CF1<C1>>>::new_witness(cs.clone(), || {
let z_i = Vec::<FpVar<CF1<C1>>>::new_input(cs.clone(), || {
Ok(self.z_i.unwrap_or(vec![CF1::<C1>::zero()]))
})?;
@ -303,7 +303,7 @@ where
let w_i = WitnessVar::<C1>::new_witness(cs.clone(), || {
Ok(self.w_i.unwrap_or(w_dummy_native.clone()))
})?;
let U_i = CommittedInstanceVar::<C1>::new_witness(cs.clone(), || {
let U_i = CommittedInstanceVar::<C1>::new_input(cs.clone(), || {
Ok(self.U_i.unwrap_or(u_dummy_native.clone()))
})?;
let W_i = WitnessVar::<C1>::new_witness(cs.clone(), || {
@ -367,7 +367,7 @@ where
#[cfg(not(test))]
{
// imports here instead of at the top of the file, so we avoid having multiple
// `#[cfg(not(test))]
// `#[cfg(not(test))]`
use crate::commitment::pedersen::PedersenGadget;
use crate::folding::nova::cyclefold::{CycleFoldCommittedInstanceVar, CF_IO_LEN};
use ark_r1cs_std::ToBitsGadget;

+ 3
- 3
src/folding/nova/mod.rs

@ -1,4 +1,5 @@
/// Implements the scheme described in [Nova](https://eprint.iacr.org/2021/370.pdf)
/// Implements the scheme described in [Nova](https://eprint.iacr.org/2021/370.pdf) and
/// [CycleFold](https://eprint.iacr.org/2023/1192.pdf).
use ark_crypto_primitives::{
crh::{poseidon::CRH, CRHScheme},
sponge::{poseidon::PoseidonConfig, Absorb},
@ -22,6 +23,7 @@ use crate::FoldingScheme;
pub mod circuits;
pub mod cyclefold;
pub mod decider_eth;
pub mod decider_eth_circuit;
pub mod nifs;
pub mod traits;
@ -216,10 +218,8 @@ where
type PreprocessorParam = (Self::ProverParam, FC);
type ProverParam = ProverParams<C1, C2, CP1, CP2>;
type VerifierParam = VerifierParams<C1, C2>;
type Witness = Witness<C1>;
type CommittedInstanceWithWitness = (CommittedInstance<C1>, Witness<C1>);
type CFCommittedInstanceWithWitness = (CommittedInstance<C2>, Witness<C2>);
type CommittedInstance = CommittedInstance<C1>;
fn preprocess(
prep_param: &Self::PreprocessorParam,

+ 24
- 16
src/lib.rs

@ -5,18 +5,18 @@
use ark_ec::CurveGroup;
use ark_ff::PrimeField;
use ark_std::rand::CryptoRng;
use ark_std::{fmt::Debug, rand::RngCore};
use thiserror::Error;
use crate::frontend::FCircuit;
pub mod transcript;
use transcript::Transcript;
pub mod ccs;
pub mod commitment;
pub mod constants;
pub mod folding;
pub mod frontend;
pub mod transcript;
pub mod utils;
#[derive(Debug, Error)]
@ -82,10 +82,8 @@ where
type PreprocessorParam: Debug;
type ProverParam: Debug;
type VerifierParam: Debug;
type Witness: Debug;
type CommittedInstanceWithWitness: Debug;
type CFCommittedInstanceWithWitness: Debug; // CycleFold CommittedInstance & Witness
type CommittedInstance: Clone + Debug;
fn preprocess(
prep_param: &Self::PreprocessorParam,
@ -123,26 +121,36 @@ where
) -> Result<(), Error>;
}
pub trait Decider<C: CurveGroup>: Clone + Debug {
type PreprocessorParam: Debug;
type ProverParam: Debug;
type VerifierParam: Debug;
type FreshInstance: Debug;
pub trait Decider<
C1: CurveGroup,
C2: CurveGroup,
FC: FCircuit<C1::ScalarField>,
FS: FoldingScheme<C1, C2, FC>,
> where
C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
C2::BaseField: PrimeField,
{
type ProverParam: Clone;
type Proof: Clone;
type VerifierParam;
type PublicInput: Debug;
type CommittedInstanceWithWitness: Debug;
type CommittedInstance: Clone + Debug;
fn prove(
pp: &Self::ProverParam,
running_instance: &Self::CommittedInstanceWithWitness,
transcript: &mut impl Transcript<C>,
rng: impl RngCore,
) -> Result<(), Error>;
rng: impl RngCore + CryptoRng,
folding_scheme: FS,
) -> Result<Self::Proof, Error>;
fn verify(
vp: &Self::VerifierParam,
i: C1::ScalarField,
z_0: Vec<C1::ScalarField>,
z_i: Vec<C1::ScalarField>,
running_instance: &Self::CommittedInstance,
transcript: &mut impl Transcript<C>,
rng: impl RngCore,
) -> Result<(), Error>;
proof: Self::Proof,
// returns `Result<bool, Error>` to differentiate between an error occurred while performing
// the verification steps, and the verification logic of the scheme not passing.
) -> Result<bool, Error>;
}

Loading…
Cancel
Save