From 97e973a6853ed94eb95306ff5ab618e821c6cbdb Mon Sep 17 00:00:00 2001 From: arnaucube Date: Thu, 8 Feb 2024 07:42:41 +0100 Subject: [PATCH] Add Decider impl for Nova onchain (#66) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 --- Cargo.toml | 8 + src/folding/circuits/nonnative.rs | 40 +++-- src/folding/nova/decider_eth.rs | 205 ++++++++++++++++++++++++ src/folding/nova/decider_eth_circuit.rs | 14 +- src/folding/nova/mod.rs | 6 +- src/lib.rs | 40 +++-- 6 files changed, 278 insertions(+), 35 deletions(-) create mode 100644 src/folding/nova/decider_eth.rs diff --git a/Cargo.toml b/Cargo.toml index 6c85af4..5913108 100644 --- a/Cargo.toml +++ b/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 diff --git a/src/folding/circuits/nonnative.rs b/src/folding/circuits/nonnative.rs index 6d964ee..d6b1c99 100644 --- a/src/folding/circuits/nonnative.rs +++ b/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( p: C, ) -> Result<(Vec, Vec), SynthesisError> +where + ::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( + p: C, + optimization_type: OptimizationType, +) -> Result<(Vec, Vec), SynthesisError> where ::BaseField: ark_ff::PrimeField, { @@ -68,12 +82,12 @@ where let x = AllocatedNonNativeFieldVar::::get_limbs_representations( &C::BaseField::zero(), - OptimizationType::Weight, + optimization_type, )?; let y = AllocatedNonNativeFieldVar::::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::::get_limbs_representations( x, - OptimizationType::Weight, + optimization_type, )?; let y = AllocatedNonNativeFieldVar::::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::::new_ref(); // dealing with the 'zero' point should not panic when doing the unwrap let p = Projective::zero(); NonNativeAffineVar::::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::::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); } } diff --git a/src/folding/nova/decider_eth.rs b/src/folding/nova/decider_eth.rs new file mode 100644 index 0000000..4aadf15 --- /dev/null +++ b/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: PhantomData, + _gc1: PhantomData, + _c2: PhantomData, + _gc2: PhantomData, + _fc: PhantomData, + _cp1: PhantomData, + _cp2: PhantomData, + _s: PhantomData, + _fs: PhantomData, +} + +impl DeciderTrait + for Decider +where + C1: CurveGroup, + C2: CurveGroup, + GC1: CurveVar>, + GC2: CurveVar>, + FC: FCircuit, + CP1: CommitmentProver, + // enforce that the CP2 is Pedersen commitment, since we're at Ethereum's EVM decider + CP2: CommitmentProver>, + S: SNARK, + FS: FoldingScheme, + ::BaseField: PrimeField, + ::BaseField: PrimeField, + ::ScalarField: Absorb, + ::ScalarField: Absorb, + C1: CurveGroup, + for<'b> &'b GC2: GroupOpsBounds<'b, C2, GC2>, + // constrain FS into Nova, since this is a Decider specificly for Nova + Nova: From, +{ + type ProverParam = S::ProvingKey; + type Proof = S::Proof; + type VerifierParam = S::VerifyingKey; + type PublicInput = Vec; + type CommittedInstanceWithWitness = (); + type CommittedInstance = CommittedInstance; + + fn prove( + pp: &Self::ProverParam, + mut rng: impl RngCore + CryptoRng, + folding_scheme: FS, + ) -> Result { + let circuit = + DeciderEthCircuit::::from_nova::(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, + z_i: Vec, + running_instance: &Self::CommittedInstance, + proof: Self::Proof, + ) -> Result { + let (cmE_x, cmE_y) = point_to_nonnative_limbs_custom_opt::( + running_instance.cmE, + OptimizationType::Constraints, + )?; + let (cmW_x, cmW_y) = point_to_nonnative_limbs_custom_opt::( + running_instance.cmW, + OptimizationType::Constraints, + )?; + let public_input: Vec = 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, + Pedersen, + Pedersen, + >; + type DECIDER = Decider< + Projective, + GVar, + Projective2, + GVar2, + CubicFCircuit, + Pedersen, + Pedersen, + Groth16, // 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::(); + + let F_circuit = CubicFCircuit::::new(()); + let z_0 = vec![Fr::from(3_u32)]; + + let (cm_len, cf_cm_len) = + get_pedersen_params_len::>( + &poseidon_config, + F_circuit, + ) + .unwrap(); + let pedersen_params = Pedersen::::new_params(&mut rng, cm_len); + let cf_pedersen_params = Pedersen::::new_params(&mut rng, cf_cm_len); + + let start = Instant::now(); + let prover_params = + ProverParams::, Pedersen> { + 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, + Pedersen, + >::from_nova::>(nova.clone()); + let mut rng = rand::rngs::OsRng; + + let start = Instant::now(); + let (pk, vk) = + Groth16::::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); + } +} diff --git a/src/folding/nova/decider_eth_circuit.rs b/src/folding/nova/decider_eth_circuit.rs index b79bc26..12fb98b 100644 --- a/src/folding/nova/decider_eth_circuit.rs +++ b/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 where C1: CurveGroup, @@ -281,13 +282,12 @@ where Ok(self.r1cs.clone()) })?; - let i = FpVar::>::new_witness(cs.clone(), || { - Ok(self.i.unwrap_or_else(CF1::::zero)) - })?; - let z_0 = Vec::>>::new_witness(cs.clone(), || { + let i = + FpVar::>::new_input(cs.clone(), || Ok(self.i.unwrap_or_else(CF1::::zero)))?; + let z_0 = Vec::>>::new_input(cs.clone(), || { Ok(self.z_0.unwrap_or(vec![CF1::::zero()])) })?; - let z_i = Vec::>>::new_witness(cs.clone(), || { + let z_i = Vec::>>::new_input(cs.clone(), || { Ok(self.z_i.unwrap_or(vec![CF1::::zero()])) })?; @@ -303,7 +303,7 @@ where let w_i = WitnessVar::::new_witness(cs.clone(), || { Ok(self.w_i.unwrap_or(w_dummy_native.clone())) })?; - let U_i = CommittedInstanceVar::::new_witness(cs.clone(), || { + let U_i = CommittedInstanceVar::::new_input(cs.clone(), || { Ok(self.U_i.unwrap_or(u_dummy_native.clone())) })?; let W_i = WitnessVar::::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; diff --git a/src/folding/nova/mod.rs b/src/folding/nova/mod.rs index d9efa2d..203b023 100644 --- a/src/folding/nova/mod.rs +++ b/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; type VerifierParam = VerifierParams; - type Witness = Witness; type CommittedInstanceWithWitness = (CommittedInstance, Witness); type CFCommittedInstanceWithWitness = (CommittedInstance, Witness); - type CommittedInstance = CommittedInstance; fn preprocess( prep_param: &Self::PreprocessorParam, diff --git a/src/lib.rs b/src/lib.rs index 7e32f94..c8b967c 100644 --- a/src/lib.rs +++ b/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: Clone + Debug { - type PreprocessorParam: Debug; - type ProverParam: Debug; - type VerifierParam: Debug; - type FreshInstance: Debug; +pub trait Decider< + C1: CurveGroup, + C2: CurveGroup, + FC: FCircuit, + FS: FoldingScheme, +> where + C1: CurveGroup, + 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, - rng: impl RngCore, - ) -> Result<(), Error>; + rng: impl RngCore + CryptoRng, + folding_scheme: FS, + ) -> Result; fn verify( vp: &Self::VerifierParam, + i: C1::ScalarField, + z_0: Vec, + z_i: Vec, running_instance: &Self::CommittedInstance, - transcript: &mut impl Transcript, - rng: impl RngCore, - ) -> Result<(), Error>; + proof: Self::Proof, + // returns `Result` to differentiate between an error occurred while performing + // the verification steps, and the verification logic of the scheme not passing. + ) -> Result; }