From 9bbdfc5a857a65a02a150921f6e9873506fa45f2 Mon Sep 17 00:00:00 2001 From: arnaucube Date: Fri, 26 Apr 2024 08:37:49 +0200 Subject: [PATCH] Full flow example (#90) * expose params & structs for external usage * add full_flow example, move examples into 'examples' dir --- .github/workflows/ci.yml | 4 + .gitignore | 3 + .../examples => examples}/external_inputs.rs | 0 examples/full_flow.rs | 222 ++++++++++++++++++ .../examples => examples}/multi_inputs.rs | 0 .../examples => examples}/sha256.rs | 0 .../examples => examples}/utils.rs | 2 +- folding-schemes/Cargo.toml | 13 + folding-schemes/src/frontend/circom/mod.rs | 2 +- folding-schemes/src/frontend/circom/utils.rs | 2 +- folding-schemes/src/frontend/mod.rs | 7 +- solidity-verifiers/Cargo.toml | 5 +- solidity-verifiers/src/verifiers/g16.rs | 14 +- solidity-verifiers/src/verifiers/kzg.rs | 6 +- solidity-verifiers/src/verifiers/mod.rs | 14 +- .../src/verifiers/nova_cyclefold.rs | 2 +- 16 files changed, 270 insertions(+), 26 deletions(-) rename {folding-schemes/examples => examples}/external_inputs.rs (100%) create mode 100644 examples/full_flow.rs rename {folding-schemes/examples => examples}/multi_inputs.rs (100%) rename {folding-schemes/examples => examples}/sha256.rs (100%) rename {folding-schemes/examples => examples}/utils.rs (96%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 521870c..4104059 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -79,6 +79,10 @@ jobs: steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 + - name: Download solc + run: | + curl -sSfL https://github.com/ethereum/solidity/releases/download/v0.8.4/solc-static-linux -o /usr/local/bin/solc + chmod +x /usr/local/bin/solc - name: Run examples tests run: cargo test --examples - name: Run examples diff --git a/.gitignore b/.gitignore index c2504d2..98dab9e 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,6 @@ folding-schemes/src/frontend/circom/test_folder/cubic_circuit_js/ # generated contracts at test time solidity-verifiers/generated +examples/*.sol +examples/*.calldata +examples/*.inputs diff --git a/folding-schemes/examples/external_inputs.rs b/examples/external_inputs.rs similarity index 100% rename from folding-schemes/examples/external_inputs.rs rename to examples/external_inputs.rs diff --git a/examples/full_flow.rs b/examples/full_flow.rs new file mode 100644 index 0000000..5a57707 --- /dev/null +++ b/examples/full_flow.rs @@ -0,0 +1,222 @@ +#![allow(non_snake_case)] +#![allow(non_camel_case_types)] +#![allow(clippy::upper_case_acronyms)] +/// +/// This example performs the full flow: +/// - define the circuit to be folded +/// - fold the circuit with Nova+CycleFold's IVC +/// - generate a DeciderEthCircuit final proof +/// - generate the Solidity contract that verifies the proof +/// - verify the proof in the EVM +/// +use ark_bn254::{constraints::GVar, Bn254, Fr, G1Projective as G1}; +use ark_crypto_primitives::snark::SNARK; +use ark_ff::PrimeField; +use ark_groth16::VerifyingKey as G16VerifierKey; +use ark_groth16::{Groth16, ProvingKey}; +use ark_grumpkin::{constraints::GVar as GVar2, Projective as G2}; +use ark_poly_commit::kzg10::VerifierKey as KZGVerifierKey; +use ark_r1cs_std::alloc::AllocVar; +use ark_r1cs_std::fields::fp::FpVar; +use ark_relations::r1cs::{ConstraintSystemRef, SynthesisError}; +use ark_std::Zero; +use std::marker::PhantomData; +use std::time::Instant; + +use folding_schemes::{ + commitment::{ + kzg::{ProverKey as KZGProverKey, KZG}, + pedersen::Pedersen, + CommitmentScheme, + }, + folding::nova::{ + decider_eth::{prepare_calldata, Decider as DeciderEth}, + decider_eth_circuit::DeciderEthCircuit, + get_cs_params_len, Nova, ProverParams, + }, + frontend::FCircuit, + transcript::poseidon::poseidon_test_config, + Decider, Error, FoldingScheme, +}; +use solidity_verifiers::{ + evm::{compile_solidity, Evm}, + utils::get_function_selector_for_nova_cyclefold_verifier, + verifiers::nova_cyclefold::get_decider_template_for_cyclefold_decider, + NovaCycleFoldVerifierKey, +}; + +/// Test circuit to be folded +#[derive(Clone, Copy, Debug)] +pub struct CubicFCircuit { + _f: PhantomData, +} +impl FCircuit for CubicFCircuit { + type Params = (); + fn new(_params: Self::Params) -> Self { + Self { _f: PhantomData } + } + fn state_len(&self) -> usize { + 1 + } + fn step_native(&self, _i: usize, z_i: Vec) -> Result, Error> { + Ok(vec![z_i[0] * z_i[0] * z_i[0] + z_i[0] + F::from(5_u32)]) + } + fn generate_step_constraints( + &self, + cs: ConstraintSystemRef, + _i: usize, + z_i: Vec>, + ) -> Result>, SynthesisError> { + let five = FpVar::::new_constant(cs.clone(), F::from(5u32))?; + let z_i = z_i[0].clone(); + + Ok(vec![&z_i * &z_i * &z_i + &z_i + &five]) + } +} + +#[allow(clippy::type_complexity)] +fn init_test_prover_params>() -> ( + ProverParams, Pedersen>, + KZGVerifierKey, +) { + let mut rng = ark_std::test_rng(); + let poseidon_config = poseidon_test_config::(); + let f_circuit = FC::new(()); + let (cs_len, cf_cs_len) = + get_cs_params_len::(&poseidon_config, f_circuit).unwrap(); + let (kzg_pk, kzg_vk): (KZGProverKey, KZGVerifierKey) = + KZG::::setup(&mut rng, cs_len).unwrap(); + let (cf_pedersen_params, _) = Pedersen::::setup(&mut rng, cf_cs_len).unwrap(); + let fs_prover_params = ProverParams::, Pedersen> { + poseidon_config: poseidon_config.clone(), + cs_params: kzg_pk.clone(), + cf_cs_params: cf_pedersen_params, + }; + (fs_prover_params, kzg_vk) +} +/// Initializes Nova parameters and DeciderEth parameters. Only for test purposes. +#[allow(clippy::type_complexity)] +fn init_params>() -> ( + ProverParams, Pedersen>, + KZGVerifierKey, + ProvingKey, + G16VerifierKey, +) { + let mut rng = rand::rngs::OsRng; + let start = Instant::now(); + let (fs_prover_params, kzg_vk) = init_test_prover_params::(); + println!("generated Nova folding params: {:?}", start.elapsed()); + let f_circuit = FC::new(()); + + pub type NOVA = Nova, Pedersen>; + let z_0 = vec![Fr::zero(); f_circuit.state_len()]; + let nova = NOVA::init(&fs_prover_params, f_circuit, z_0.clone()).unwrap(); + + let decider_circuit = + DeciderEthCircuit::, Pedersen>::from_nova::( + nova.clone(), + ) + .unwrap(); + let start = Instant::now(); + let (g16_pk, g16_vk) = + Groth16::::circuit_specific_setup(decider_circuit.clone(), &mut rng).unwrap(); + println!( + "generated G16 (Decider circuit) params: {:?}", + start.elapsed() + ); + (fs_prover_params, kzg_vk, g16_pk, g16_vk) +} + +fn main() { + let n_steps = 10; + // set the initial state + let z_0 = vec![Fr::from(3_u32)]; + + let (fs_prover_params, kzg_vk, g16_pk, g16_vk) = init_params::>(); + + pub type NOVA = Nova, KZG<'static, Bn254>, Pedersen>; + pub type DECIDERETH_FCircuit = DeciderEth< + G1, + GVar, + G2, + GVar2, + CubicFCircuit, + KZG<'static, Bn254>, + Pedersen, + Groth16, + NOVA, + >; + let f_circuit = CubicFCircuit::::new(()); + + // initialize the folding scheme engine, in our case we use Nova + let mut nova = NOVA::init(&fs_prover_params, f_circuit, z_0).unwrap(); + // run n steps of the folding iteration + for i in 0..n_steps { + let start = Instant::now(); + nova.prove_step().unwrap(); + println!("Nova::prove_step {}: {:?}", i, start.elapsed()); + } + + let rng = rand::rngs::OsRng; + let start = Instant::now(); + let proof = DECIDERETH_FCircuit::prove( + (g16_pk, fs_prover_params.cs_params.clone()), + rng, + nova.clone(), + ) + .unwrap(); + println!("generated Decider proof: {:?}", start.elapsed()); + + let verified = DECIDERETH_FCircuit::verify( + (g16_vk.clone(), kzg_vk.clone()), + nova.i, + nova.z_0.clone(), + nova.z_i.clone(), + &nova.U_i, + &nova.u_i, + &proof, + ) + .unwrap(); + assert!(verified); + println!("Decider proof verification: {}", verified); + + // Now, let's generate the Solidity code that verifies this Decider final proof + let function_selector = + get_function_selector_for_nova_cyclefold_verifier(nova.z_0.len() * 2 + 1); + + let calldata: Vec = prepare_calldata( + function_selector, + nova.i, + nova.z_0, + nova.z_i, + &nova.U_i, + &nova.u_i, + proof, + ) + .unwrap(); + + // prepare the setup params for the solidity verifier + let nova_cyclefold_vk = NovaCycleFoldVerifierKey::from((g16_vk, kzg_vk, f_circuit.state_len())); + + // generate the solidity code + let decider_solidity_code = get_decider_template_for_cyclefold_decider(nova_cyclefold_vk); + + // verify the proof against the solidity code in the EVM + let nova_cyclefold_verifier_bytecode = compile_solidity(&decider_solidity_code, "NovaDecider"); + let mut evm = Evm::default(); + let verifier_address = evm.create(nova_cyclefold_verifier_bytecode); + let (_, output) = evm.call(verifier_address, calldata.clone()); + assert_eq!(*output.last().unwrap(), 1); + + // save smart contract and the calldata + println!("storing nova-verifier.sol and the calldata into files"); + use std::fs; + fs::write( + "./examples/nova-verifier.sol", + decider_solidity_code.clone(), + ) + .unwrap(); + fs::write("./examples/solidity-calldata.calldata", calldata.clone()).unwrap(); + let s = solidity_verifiers::utils::get_formatted_calldata(calldata.clone()); + fs::write("./examples/solidity-calldata.inputs", s.join(",\n")).expect(""); +} diff --git a/folding-schemes/examples/multi_inputs.rs b/examples/multi_inputs.rs similarity index 100% rename from folding-schemes/examples/multi_inputs.rs rename to examples/multi_inputs.rs diff --git a/folding-schemes/examples/sha256.rs b/examples/sha256.rs similarity index 100% rename from folding-schemes/examples/sha256.rs rename to examples/sha256.rs diff --git a/folding-schemes/examples/utils.rs b/examples/utils.rs similarity index 96% rename from folding-schemes/examples/utils.rs rename to examples/utils.rs index 66b3624..a2c86df 100644 --- a/folding-schemes/examples/utils.rs +++ b/examples/utils.rs @@ -11,7 +11,7 @@ use folding_schemes::folding::nova::{get_r1cs, ProverParams, VerifierParams}; use folding_schemes::frontend::FCircuit; use folding_schemes::transcript::poseidon::poseidon_test_config; -// This method computes the Prover & Verifier parameters for the example. +// This method computes the Nova's Prover & Verifier parameters for the example. // Warning: this method is only for testing purposes. For a real world use case those parameters // should be generated carefully (both the PoseidonConfig and the PedersenParams). #[allow(clippy::type_complexity)] diff --git a/folding-schemes/Cargo.toml b/folding-schemes/Cargo.toml index d6e2aef..964a633 100644 --- a/folding-schemes/Cargo.toml +++ b/folding-schemes/Cargo.toml @@ -47,3 +47,16 @@ parallel = [ "ark-crypto-primitives/parallel", "ark-r1cs-std/parallel", ] + + +[[example]] +name = "sha256" +path = "../examples/sha256.rs" + +[[example]] +name = "multi_inputs" +path = "../examples/multi_inputs.rs" + +[[example]] +name = "external_inputs" +path = "../examples/external_inputs.rs" diff --git a/folding-schemes/src/frontend/circom/mod.rs b/folding-schemes/src/frontend/circom/mod.rs index 6315480..b53e9b7 100644 --- a/folding-schemes/src/frontend/circom/mod.rs +++ b/folding-schemes/src/frontend/circom/mod.rs @@ -83,7 +83,7 @@ impl FCircuit for CircomFCircuit { let circom_circuit = CircomCircuit { r1cs, witness: witness.clone(), - inputs_already_computed: true, + inputs_already_allocated: true, }; // Generates the constraints for the circom_circuit. diff --git a/folding-schemes/src/frontend/circom/utils.rs b/folding-schemes/src/frontend/circom/utils.rs index 7aba643..2599dcc 100644 --- a/folding-schemes/src/frontend/circom/utils.rs +++ b/folding-schemes/src/frontend/circom/utils.rs @@ -140,7 +140,7 @@ mod tests { let circom_circuit = CircomCircuit { r1cs, witness, - inputs_already_computed: false, + inputs_already_allocated: false, }; circom_circuit.generate_constraints(cs.clone()).unwrap(); diff --git a/folding-schemes/src/frontend/mod.rs b/folding-schemes/src/frontend/mod.rs index 96f4e38..e5e7305 100644 --- a/folding-schemes/src/frontend/mod.rs +++ b/folding-schemes/src/frontend/mod.rs @@ -52,10 +52,9 @@ pub mod tests { use core::marker::PhantomData; /// CubicFCircuit is a struct that implements the FCircuit trait, for the R1CS example circuit - /// from https://www.vitalik.ca/general/2016/12/10/qap.html, which checks `x^3 + x + 5 = y`. It - /// has 2 public inputs which are used as the state. `z_i` is used as `x`, and `z_{i+1}` is - /// used as `y`, and at the next step, `z_{i+1}` will be assigned to `z_i`, and a new `z+{i+1}` - /// will be computted. + /// from https://www.vitalik.ca/general/2016/12/10/qap.html, which checks `x^3 + x + 5 = y`. + /// `z_i` is used as `x`, and `z_{i+1}` is used as `y`, and at the next step, `z_{i+1}` will be + /// assigned to `z_i`, and a new `z+{i+1}` will be computted. #[derive(Clone, Copy, Debug)] pub struct CubicFCircuit { _f: PhantomData, diff --git a/solidity-verifiers/Cargo.toml b/solidity-verifiers/Cargo.toml index eb75e59..dc52557 100644 --- a/solidity-verifiers/Cargo.toml +++ b/solidity-verifiers/Cargo.toml @@ -40,4 +40,7 @@ parallel = [ ] - +[[example]] +name = "full_flow" +path = "../examples/full_flow.rs" +# required-features = ["light-test"] diff --git a/solidity-verifiers/src/verifiers/g16.rs b/solidity-verifiers/src/verifiers/g16.rs index ac8faa4..0063e14 100644 --- a/solidity-verifiers/src/verifiers/g16.rs +++ b/solidity-verifiers/src/verifiers/g16.rs @@ -11,19 +11,19 @@ use super::PRAGMA_GROTH16_VERIFIER; #[derive(Template, Default)] #[template(path = "groth16_verifier.askama.sol", ext = "sol")] -pub(crate) struct Groth16Verifier { +pub struct Groth16Verifier { /// The `alpha * G`, where `G` is the generator of `G1`. - pub(crate) vkey_alpha_g1: G1Repr, + pub vkey_alpha_g1: G1Repr, /// The `alpha * H`, where `H` is the generator of `G2`. - pub(crate) vkey_beta_g2: G2Repr, + pub vkey_beta_g2: G2Repr, /// The `gamma * H`, where `H` is the generator of `G2`. - pub(crate) vkey_gamma_g2: G2Repr, + pub vkey_gamma_g2: G2Repr, /// The `delta * H`, where `H` is the generator of `G2`. - pub(crate) vkey_delta_g2: G2Repr, + pub vkey_delta_g2: G2Repr, /// Length of the `gamma_abc_g1` vector. - pub(crate) gamma_abc_len: usize, + pub gamma_abc_len: usize, /// The `gamma^{-1} * (beta * a_i + alpha * b_i + c_i) * H`, where `H` is the generator of `E::G1`. - pub(crate) gamma_abc_g1: Vec, + pub gamma_abc_g1: Vec, } impl From for Groth16Verifier { diff --git a/solidity-verifiers/src/verifiers/kzg.rs b/solidity-verifiers/src/verifiers/kzg.rs index 15cac6c..9902398 100644 --- a/solidity-verifiers/src/verifiers/kzg.rs +++ b/solidity-verifiers/src/verifiers/kzg.rs @@ -11,7 +11,7 @@ use super::PRAGMA_KZG10_VERIFIER; #[derive(Template, Default)] #[template(path = "kzg10_verifier.askama.sol", ext = "sol")] -pub(crate) struct KZG10Verifier { +pub struct KZG10Verifier { /// The generator of `G1`. pub(crate) g1: G1Repr, /// The generator of `G2`. @@ -42,8 +42,8 @@ impl From for KZG10Verifier { #[derive(CanonicalDeserialize, CanonicalSerialize, Clone, PartialEq, Debug)] pub struct KZG10VerifierKey { - pub(crate) vk: VerifierKey, - pub(crate) g1_crs_batch_points: Vec, + pub vk: VerifierKey, + pub g1_crs_batch_points: Vec, } impl From<(VerifierKey, Vec)> for KZG10VerifierKey { diff --git a/solidity-verifiers/src/verifiers/mod.rs b/solidity-verifiers/src/verifiers/mod.rs index 5db4bee..db283a4 100644 --- a/solidity-verifiers/src/verifiers/mod.rs +++ b/solidity-verifiers/src/verifiers/mod.rs @@ -2,17 +2,17 @@ //! We use askama for templating and define which variables are required for each template. // Pragma statements for verifiers -pub(crate) const PRAGMA_GROTH16_VERIFIER: &str = "pragma solidity >=0.7.0 <0.9.0;"; // from snarkjs, avoid changing -pub(crate) const PRAGMA_KZG10_VERIFIER: &str = "pragma solidity >=0.8.1 <=0.8.4;"; +pub const PRAGMA_GROTH16_VERIFIER: &str = "pragma solidity >=0.7.0 <0.9.0;"; // from snarkjs, avoid changing +pub const PRAGMA_KZG10_VERIFIER: &str = "pragma solidity >=0.8.1 <=0.8.4;"; /// Default SDPX License identifier -pub(crate) const GPL3_SDPX_IDENTIFIER: &str = "// SPDX-License-Identifier: GPL-3.0"; -pub(crate) const MIT_SDPX_IDENTIFIER: &str = "// SPDX-License-Identifier: MIT"; +pub const GPL3_SDPX_IDENTIFIER: &str = "// SPDX-License-Identifier: GPL-3.0"; +pub const MIT_SDPX_IDENTIFIER: &str = "// SPDX-License-Identifier: MIT"; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Read, SerializationError, Write}; -mod g16; -mod kzg; -mod nova_cyclefold; +pub mod g16; +pub mod kzg; +pub mod nova_cyclefold; pub use g16::Groth16VerifierKey; pub use kzg::KZG10VerifierKey; diff --git a/solidity-verifiers/src/verifiers/nova_cyclefold.rs b/solidity-verifiers/src/verifiers/nova_cyclefold.rs index ec706c5..a753779 100644 --- a/solidity-verifiers/src/verifiers/nova_cyclefold.rs +++ b/solidity-verifiers/src/verifiers/nova_cyclefold.rs @@ -26,7 +26,7 @@ pub fn get_decider_template_for_cyclefold_decider( #[derive(Template, Default)] #[template(path = "nova_cyclefold_decider.askama.sol", ext = "sol")] -pub(crate) struct NovaCycleFoldDecider { +pub struct NovaCycleFoldDecider { groth16_verifier: Groth16Verifier, kzg10_verifier: KZG10Verifier, // z_len denotes the FCircuit state (z_i) length