From d5c1e5f72a4e3a03f882ef921f0225f7fe0dc465 Mon Sep 17 00:00:00 2001 From: arnaucube Date: Mon, 6 May 2024 16:06:08 +0200 Subject: [PATCH] Circom external inputs (#91) * circom: add external_inputs * adapt new external_inputs interface to the FoldingScheme trait and Nova impl * adapt examples to new FCircuit external_inputs interface * add state_len & external_inputs_len params to CircomFCircuit * add examples/circom_full_flow.rs * merge the params initializer functions, clippy * circom: move r1cs reading to FCircuit::new instead of each step * CI/examples: add circom so it can run the circom_full_flow example --- .github/workflows/ci.yml | 8 + .gitignore | 4 +- examples/circom_full_flow.rs | 156 ++++++++++++++++++ examples/external_inputs.rs | 111 +++++++------ examples/full_flow.rs | 94 +++-------- examples/multi_inputs.rs | 38 +++-- examples/sha256.rs | 37 +++-- examples/utils.rs | 100 ++++++++--- folding-schemes/src/folding/nova/circuits.rs | 13 +- .../src/folding/nova/decider_eth.rs | 6 +- .../src/folding/nova/decider_eth_circuit.rs | 16 +- folding-schemes/src/folding/nova/mod.rs | 29 +++- folding-schemes/src/frontend/circom/mod.rs | 156 +++++++++++++----- .../frontend/circom/test_folder/compile.sh | 4 +- .../circom/test_folder/cubic_circuit.circom | 1 - .../circom/test_folder/external_inputs.circom | 20 +++ folding-schemes/src/frontend/circom/utils.rs | 7 + folding-schemes/src/frontend/mod.rs | 52 ++++-- folding-schemes/src/lib.rs | 2 +- solidity-verifiers/Cargo.toml | 5 +- .../src/verifiers/nova_cyclefold.rs | 38 +++-- 21 files changed, 634 insertions(+), 263 deletions(-) create mode 100644 examples/circom_full_flow.rs create mode 100644 folding-schemes/src/frontend/circom/test_folder/external_inputs.circom diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4104059..531c0d2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -79,10 +79,18 @@ jobs: steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 + - name: Download Circom + run: | + mkdir -p $HOME/bin + curl -sSfL https://github.com/iden3/circom/releases/download/v2.1.6/circom-linux-amd64 -o $HOME/bin/circom + chmod +x $HOME/bin/circom + echo "$HOME/bin" >> $GITHUB_PATH - 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: Execute compile.sh to generate .r1cs and .wasm from .circom + run: bash ./folding-schemes/src/frontend/circom/test_folder/compile.sh - name: Run examples tests run: cargo test --examples - name: Run examples diff --git a/.gitignore b/.gitignore index 98dab9e..561a1d5 100644 --- a/.gitignore +++ b/.gitignore @@ -2,8 +2,10 @@ Cargo.lock # Circom generated files -folding-schemes/src/frontend/circom/test_folder/cubic_circuit.r1cs folding-schemes/src/frontend/circom/test_folder/cubic_circuit_js/ +folding-schemes/src/frontend/circom/test_folder/external_inputs_js/ +*.r1cs +*.sym # generated contracts at test time solidity-verifiers/generated diff --git a/examples/circom_full_flow.rs b/examples/circom_full_flow.rs new file mode 100644 index 0000000..5b3174d --- /dev/null +++ b/examples/circom_full_flow.rs @@ -0,0 +1,156 @@ +#![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_groth16::Groth16; +use ark_grumpkin::{constraints::GVar as GVar2, Projective as G2}; + +use std::path::PathBuf; +use std::time::Instant; + +use folding_schemes::{ + commitment::{kzg::KZG, pedersen::Pedersen}, + folding::nova::{ + decider_eth::{prepare_calldata, Decider as DeciderEth}, + Nova, + }, + frontend::{circom::CircomFCircuit, FCircuit}, + Decider, 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, +}; + +mod utils; +use utils::init_ivc_and_decider_params; + +fn main() { + // set the initial state + let z_0 = vec![Fr::from(3_u32)]; + + // set the external inputs to be used at each step of the IVC, it has length of 10 since this + // is the number of steps that we will do + let external_inputs = vec![ + vec![Fr::from(6u32), Fr::from(7u32)], + vec![Fr::from(8u32), Fr::from(9u32)], + vec![Fr::from(10u32), Fr::from(11u32)], + vec![Fr::from(12u32), Fr::from(13u32)], + vec![Fr::from(14u32), Fr::from(15u32)], + vec![Fr::from(6u32), Fr::from(7u32)], + vec![Fr::from(8u32), Fr::from(9u32)], + vec![Fr::from(10u32), Fr::from(11u32)], + vec![Fr::from(12u32), Fr::from(13u32)], + vec![Fr::from(14u32), Fr::from(15u32)], + ]; + + // initialize the Circom circuit + let r1cs_path = + PathBuf::from("./folding-schemes/src/frontend/circom/test_folder/external_inputs.r1cs"); + let wasm_path = PathBuf::from( + "./folding-schemes/src/frontend/circom/test_folder/external_inputs_js/external_inputs.wasm", + ); + + let f_circuit_params = (r1cs_path, wasm_path, 1, 2); + let f_circuit = CircomFCircuit::::new(f_circuit_params).unwrap(); + + let (fs_prover_params, kzg_vk, g16_pk, g16_vk) = + init_ivc_and_decider_params::>(f_circuit.clone()); + + pub type NOVA = + Nova, KZG<'static, Bn254>, Pedersen>; + pub type DECIDERETH_FCircuit = DeciderEth< + G1, + GVar, + G2, + GVar2, + CircomFCircuit, + KZG<'static, Bn254>, + Pedersen, + Groth16, + NOVA, + >; + + // initialize the folding scheme engine, in our case we use Nova + let mut nova = NOVA::init(&fs_prover_params, f_circuit.clone(), z_0).unwrap(); + // run n steps of the folding iteration + for (i, external_inputs_at_step) in external_inputs.iter().enumerate() { + let start = Instant::now(); + nova.prove_step(external_inputs_at_step.clone()).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/examples/external_inputs.rs b/examples/external_inputs.rs index 437c1fd..dd30367 100644 --- a/examples/external_inputs.rs +++ b/examples/external_inputs.rs @@ -3,6 +3,7 @@ #![allow(non_camel_case_types)] #![allow(clippy::upper_case_acronyms)] +use ark_bn254::{constraints::GVar, Bn254, Fr, G1Projective as Projective}; use ark_crypto_primitives::{ crh::{ poseidon::constraints::{CRHGadget, CRHParametersVar}, @@ -12,29 +13,27 @@ use ark_crypto_primitives::{ sponge::{poseidon::PoseidonConfig, Absorb}, }; use ark_ff::PrimeField; -use ark_pallas::{constraints::GVar, Fr, Projective}; +use ark_grumpkin::{constraints::GVar as GVar2, Projective as Projective2}; +use ark_r1cs_std::alloc::AllocVar; use ark_r1cs_std::fields::fp::FpVar; -use ark_r1cs_std::{alloc::AllocVar, fields::FieldVar}; use ark_relations::r1cs::{ConstraintSystemRef, SynthesisError}; -use ark_std::Zero; -use ark_vesta::{constraints::GVar as GVar2, Projective as Projective2}; use core::marker::PhantomData; use std::time::Instant; -use folding_schemes::commitment::pedersen::Pedersen; +use folding_schemes::commitment::{kzg::KZG, pedersen::Pedersen}; use folding_schemes::folding::nova::Nova; use folding_schemes::frontend::FCircuit; use folding_schemes::{Error, FoldingScheme}; mod utils; use folding_schemes::transcript::poseidon::poseidon_test_config; -use utils::test_nova_setup; +use utils::init_nova_ivc_params; /// This is the circuit that we want to fold, it implements the FCircuit trait. The parameter z_i -/// denotes the current state which contains 2 elements, and z_{i+1} denotes the next state which -/// we get by applying the step. +/// denotes the current state which contains 1 element, and z_{i+1} denotes the next state which we +/// get by applying the step. /// /// In this example we set the state to be the previous state together with an external input, and -/// the new state is an array which contains the new state and a zero which will be ignored. +/// the new state is an array which contains the new state. /// /// This is useful for example if we want to fold multiple verifications of signatures, where the /// circuit F checks the signature and is folded for each of the signatures and public keys. To @@ -56,9 +55,8 @@ use utils::test_nova_setup; /// │ │FCircuit │ /// │ │ │ /// └────►│ h =Hash(z_i[0],w_i)│ -/// │ │ =Hash(v, w_i) │ /// ────────►│ │ ├───────► -/// z_i=[v,0] │ └──►z_{i+1}=[h, 0] │ z_{i+1}=[h,0] +/// z_i │ └──►z_{i+1}=[h] │ z_{i+1} /// │ │ /// └────────────────────┘ /// @@ -66,9 +64,6 @@ use utils::test_nova_setup; /// /// The last state z_i is used together with the external input w_i as inputs to compute the new /// state z_{i+1}. -/// The function F will output the new state in an array of two elements, where the second element -/// is a 0. In other words, z_{i+1} = [F([z_i, w_i]), 0], and the 0 will be replaced by w_{i+1} in -/// the next iteration, so z_{i+2} = [F([z_{i+1}, w_{i+1}]), 0]. #[derive(Clone, Debug)] pub struct ExternalInputsCircuits where @@ -76,47 +71,53 @@ where { _f: PhantomData, poseidon_config: PoseidonConfig, - external_inputs: Vec, } impl FCircuit for ExternalInputsCircuits where F: Absorb, { - type Params = (PoseidonConfig, Vec); // where Vec contains the external inputs + type Params = PoseidonConfig; - fn new(params: Self::Params) -> Self { - Self { + fn new(params: Self::Params) -> Result { + Ok(Self { _f: PhantomData, - poseidon_config: params.0, - external_inputs: params.1, - } + poseidon_config: params, + }) } fn state_len(&self) -> usize { - 2 + 1 + } + fn external_inputs_len(&self) -> usize { + 1 } - /// computes the next state values in place, assigning z_{i+1} into z_i, and computing the new + /// computes the next state value for the step of F for the given z_i and external_inputs /// z_{i+1} - fn step_native(&self, i: usize, z_i: Vec) -> Result, Error> { - let input: [F; 2] = [z_i[0], self.external_inputs[i]]; - let h = CRH::::evaluate(&self.poseidon_config, input).unwrap(); - Ok(vec![h, F::zero()]) + fn step_native( + &self, + _i: usize, + z_i: Vec, + external_inputs: Vec, + ) -> Result, Error> { + let hash_input: [F; 2] = [z_i[0], external_inputs[0]]; + let h = CRH::::evaluate(&self.poseidon_config, hash_input).unwrap(); + Ok(vec![h]) } - /// generates the constraints for the step of F for the given z_i + /// generates the constraints and returns the next state value for the step of F for the given + /// z_i and external_inputs fn generate_step_constraints( &self, cs: ConstraintSystemRef, - i: usize, + _i: usize, z_i: Vec>, + external_inputs: Vec>, ) -> Result>, SynthesisError> { let crh_params = CRHParametersVar::::new_constant(cs.clone(), self.poseidon_config.clone())?; - let external_inputVar = - FpVar::::new_witness(cs.clone(), || Ok(self.external_inputs[i])).unwrap(); - let input: [FpVar; 2] = [z_i[0].clone(), external_inputVar.clone()]; - let h = CRHGadget::::evaluate(&crh_params, &input)?; - Ok(vec![h, FpVar::::zero()]) + let hash_input: [FpVar; 2] = [z_i[0].clone(), external_inputs[0].clone()]; + let h = CRHGadget::::evaluate(&crh_params, &hash_input)?; + Ok(vec![h]) } } @@ -134,14 +135,20 @@ pub mod tests { let cs = ConstraintSystem::::new_ref(); - let circuit = ExternalInputsCircuits::::new((poseidon_config, vec![Fr::from(3_u32)])); - let z_i = vec![Fr::from(1_u32), Fr::zero()]; + let circuit = ExternalInputsCircuits::::new(poseidon_config).unwrap(); + let z_i = vec![Fr::from(1_u32)]; + let external_inputs = vec![Fr::from(3_u32)]; - let z_i1 = circuit.step_native(0, z_i.clone()).unwrap(); + let z_i1 = circuit + .step_native(0, z_i.clone(), external_inputs.clone()) + .unwrap(); let z_iVar = Vec::>::new_witness(cs.clone(), || Ok(z_i)).unwrap(); + let external_inputsVar = + Vec::>::new_witness(cs.clone(), || Ok(external_inputs)).unwrap(); + let computed_z_i1Var = circuit - .generate_step_constraints(cs.clone(), 0, z_iVar.clone()) + .generate_step_constraints(cs.clone(), 0, z_iVar, external_inputsVar) .unwrap(); assert_eq!(computed_z_i1Var.value().unwrap(), z_i1); } @@ -150,24 +157,24 @@ pub mod tests { /// cargo run --release --example external_inputs fn main() { let num_steps = 5; - let initial_state = vec![Fr::from(1_u32), Fr::zero()]; + let initial_state = vec![Fr::from(1_u32)]; - // set the external inputs to be used at each folding step + // prepare the external inputs to be used at each folding step let external_inputs = vec![ - Fr::from(3_u32), - Fr::from(33_u32), - Fr::from(73_u32), - Fr::from(103_u32), - Fr::from(125_u32), + vec![Fr::from(3_u32)], + vec![Fr::from(33_u32)], + vec![Fr::from(73_u32)], + vec![Fr::from(103_u32)], + vec![Fr::from(125_u32)], ]; assert_eq!(external_inputs.len(), num_steps); let poseidon_config = poseidon_test_config::(); - let F_circuit = ExternalInputsCircuits::::new((poseidon_config, external_inputs)); + let F_circuit = ExternalInputsCircuits::::new(poseidon_config).unwrap(); println!("Prepare Nova ProverParams & VerifierParams"); - let (prover_params, verifier_params) = - test_nova_setup::>(F_circuit.clone()); + let (prover_params, verifier_params, _) = + init_nova_ivc_params::>(F_circuit.clone()); /// The idea here is that eventually we could replace the next line chunk that defines the /// `type NOVA = Nova<...>` by using another folding scheme that fulfills the `FoldingScheme` @@ -178,7 +185,7 @@ fn main() { Projective2, GVar2, ExternalInputsCircuits, - Pedersen, + KZG<'static, Bn254>, Pedersen, >; @@ -186,9 +193,11 @@ fn main() { let mut folding_scheme = NOVA::init(&prover_params, F_circuit, initial_state.clone()).unwrap(); // compute a step of the IVC - for i in 0..num_steps { + for (i, external_inputs_at_step) in external_inputs.iter().enumerate() { let start = Instant::now(); - folding_scheme.prove_step().unwrap(); + folding_scheme + .prove_step(external_inputs_at_step.clone()) + .unwrap(); println!("Nova::prove_step {}: {:?}", i, start.elapsed()); } println!( diff --git a/examples/full_flow.rs b/examples/full_flow.rs index 5a57707..6cf154f 100644 --- a/examples/full_flow.rs +++ b/examples/full_flow.rs @@ -10,32 +10,25 @@ /// - 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_groth16::Groth16; 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; +mod utils; +use utils::init_ivc_and_decider_params; + use folding_schemes::{ - commitment::{ - kzg::{ProverKey as KZGProverKey, KZG}, - pedersen::Pedersen, - CommitmentScheme, - }, + commitment::{kzg::KZG, pedersen::Pedersen}, folding::nova::{ decider_eth::{prepare_calldata, Decider as DeciderEth}, - decider_eth_circuit::DeciderEthCircuit, - get_cs_params_len, Nova, ProverParams, + Nova, }, frontend::FCircuit, - transcript::poseidon::poseidon_test_config, Decider, Error, FoldingScheme, }; use solidity_verifiers::{ @@ -52,13 +45,21 @@ pub struct CubicFCircuit { } impl FCircuit for CubicFCircuit { type Params = (); - fn new(_params: Self::Params) -> Self { - Self { _f: PhantomData } + fn new(_params: Self::Params) -> Result { + Ok(Self { _f: PhantomData }) } fn state_len(&self) -> usize { 1 } - fn step_native(&self, _i: usize, z_i: Vec) -> Result, Error> { + fn external_inputs_len(&self) -> usize { + 0 + } + fn step_native( + &self, + _i: usize, + z_i: Vec, + _external_inputs: 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( @@ -66,6 +67,7 @@ impl FCircuit for CubicFCircuit { cs: ConstraintSystemRef, _i: usize, z_i: Vec>, + _external_inputs: Vec>, ) -> Result>, SynthesisError> { let five = FpVar::::new_constant(cs.clone(), F::from(5u32))?; let z_i = z_i[0].clone(); @@ -74,65 +76,14 @@ impl FCircuit for CubicFCircuit { } } -#[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::>(); + let f_circuit = CubicFCircuit::::new(()).unwrap(); + let (fs_prover_params, kzg_vk, g16_pk, g16_vk) = + init_ivc_and_decider_params::>(f_circuit); pub type NOVA = Nova, KZG<'static, Bn254>, Pedersen>; pub type DECIDERETH_FCircuit = DeciderEth< @@ -146,14 +97,13 @@ fn main() { 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(); + nova.prove_step(vec![]).unwrap(); println!("Nova::prove_step {}: {:?}", i, start.elapsed()); } diff --git a/examples/multi_inputs.rs b/examples/multi_inputs.rs index e7c296e..bb15082 100644 --- a/examples/multi_inputs.rs +++ b/examples/multi_inputs.rs @@ -10,15 +10,15 @@ use ark_relations::r1cs::{ConstraintSystemRef, SynthesisError}; use core::marker::PhantomData; use std::time::Instant; -use ark_pallas::{constraints::GVar, Fr, Projective}; -use ark_vesta::{constraints::GVar as GVar2, Projective as Projective2}; +use ark_bn254::{constraints::GVar, Bn254, Fr, G1Projective as Projective}; +use ark_grumpkin::{constraints::GVar as GVar2, Projective as Projective2}; -use folding_schemes::commitment::pedersen::Pedersen; +use folding_schemes::commitment::{kzg::KZG, pedersen::Pedersen}; use folding_schemes::folding::nova::Nova; use folding_schemes::frontend::FCircuit; use folding_schemes::{Error, FoldingScheme}; mod utils; -use utils::test_nova_setup; +use utils::init_nova_ivc_params; /// This is the circuit that we want to fold, it implements the FCircuit trait. The parameter z_i /// denotes the current state which contains 5 elements, and z_{i+1} denotes the next state which @@ -32,16 +32,24 @@ pub struct MultiInputsFCircuit { impl FCircuit for MultiInputsFCircuit { type Params = (); - fn new(_params: Self::Params) -> Self { - Self { _f: PhantomData } + fn new(_params: Self::Params) -> Result { + Ok(Self { _f: PhantomData }) } fn state_len(&self) -> usize { 5 } + fn external_inputs_len(&self) -> usize { + 0 + } /// computes the next state values in place, assigning z_{i+1} into z_i, and computing the new /// z_{i+1} - fn step_native(&self, _i: usize, z_i: Vec) -> Result, Error> { + fn step_native( + &self, + _i: usize, + z_i: Vec, + _external_inputs: Vec, + ) -> Result, Error> { let a = z_i[0] + F::from(4_u32); let b = z_i[1] + F::from(40_u32); let c = z_i[2] * F::from(4_u32); @@ -57,6 +65,7 @@ impl FCircuit for MultiInputsFCircuit { cs: ConstraintSystemRef, _i: usize, z_i: Vec>, + _external_inputs: Vec>, ) -> Result>, SynthesisError> { let four = FpVar::::new_constant(cs.clone(), F::from(4u32))?; let forty = FpVar::::new_constant(cs.clone(), F::from(40u32))?; @@ -83,7 +92,7 @@ pub mod tests { fn test_f_circuit() { let cs = ConstraintSystem::::new_ref(); - let circuit = MultiInputsFCircuit::::new(()); + let circuit = MultiInputsFCircuit::::new(()).unwrap(); let z_i = vec![ Fr::from(1_u32), Fr::from(1_u32), @@ -92,11 +101,11 @@ pub mod tests { Fr::from(1_u32), ]; - let z_i1 = circuit.step_native(0, z_i.clone()).unwrap(); + let z_i1 = circuit.step_native(0, z_i.clone(), vec![]).unwrap(); let z_iVar = Vec::>::new_witness(cs.clone(), || Ok(z_i)).unwrap(); let computed_z_i1Var = circuit - .generate_step_constraints(cs.clone(), 0, z_iVar.clone()) + .generate_step_constraints(cs.clone(), 0, z_iVar.clone(), vec![]) .unwrap(); assert_eq!(computed_z_i1Var.value().unwrap(), z_i1); } @@ -113,10 +122,11 @@ fn main() { Fr::from(1_u32), ]; - let F_circuit = MultiInputsFCircuit::::new(()); + let F_circuit = MultiInputsFCircuit::::new(()).unwrap(); println!("Prepare Nova ProverParams & VerifierParams"); - let (prover_params, verifier_params) = test_nova_setup::>(F_circuit); + let (prover_params, verifier_params, _) = + init_nova_ivc_params::>(F_circuit); /// The idea here is that eventually we could replace the next line chunk that defines the /// `type NOVA = Nova<...>` by using another folding scheme that fulfills the `FoldingScheme` @@ -127,7 +137,7 @@ fn main() { Projective2, GVar2, MultiInputsFCircuit, - Pedersen, + KZG<'static, Bn254>, Pedersen, >; @@ -137,7 +147,7 @@ fn main() { // compute a step of the IVC for i in 0..num_steps { let start = Instant::now(); - folding_scheme.prove_step().unwrap(); + folding_scheme.prove_step(vec![]).unwrap(); println!("Nova::prove_step {}: {:?}", i, start.elapsed()); } diff --git a/examples/sha256.rs b/examples/sha256.rs index ee5c147..714eb01 100644 --- a/examples/sha256.rs +++ b/examples/sha256.rs @@ -16,15 +16,15 @@ use ark_relations::r1cs::{ConstraintSystemRef, SynthesisError}; use core::marker::PhantomData; use std::time::Instant; -use ark_pallas::{constraints::GVar, Fr, Projective}; -use ark_vesta::{constraints::GVar as GVar2, Projective as Projective2}; +use ark_bn254::{constraints::GVar, Bn254, Fr, G1Projective as Projective}; +use ark_grumpkin::{constraints::GVar as GVar2, Projective as Projective2}; -use folding_schemes::commitment::pedersen::Pedersen; +use folding_schemes::commitment::{kzg::KZG, pedersen::Pedersen}; use folding_schemes::folding::nova::Nova; use folding_schemes::frontend::FCircuit; use folding_schemes::{Error, FoldingScheme}; mod utils; -use utils::test_nova_setup; +use utils::init_nova_ivc_params; /// This is the circuit that we want to fold, it implements the FCircuit trait. /// The parameter z_i denotes the current state, and z_{i+1} denotes the next state which we get by @@ -38,16 +38,24 @@ pub struct Sha256FCircuit { impl FCircuit for Sha256FCircuit { type Params = (); - fn new(_params: Self::Params) -> Self { - Self { _f: PhantomData } + fn new(_params: Self::Params) -> Result { + Ok(Self { _f: PhantomData }) } fn state_len(&self) -> usize { 1 } + fn external_inputs_len(&self) -> usize { + 0 + } /// computes the next state values in place, assigning z_{i+1} into z_i, and computing the new /// z_{i+1} - fn step_native(&self, _i: usize, z_i: Vec) -> Result, Error> { + fn step_native( + &self, + _i: usize, + z_i: Vec, + _external_inputs: Vec, + ) -> Result, Error> { let out_bytes = Sha256::evaluate(&(), z_i[0].into_bigint().to_bytes_le()).unwrap(); let out: Vec = out_bytes.to_field_elements().unwrap(); @@ -60,6 +68,7 @@ impl FCircuit for Sha256FCircuit { _cs: ConstraintSystemRef, _i: usize, z_i: Vec>, + _external_inputs: Vec>, ) -> Result>, SynthesisError> { let unit_var = UnitVar::default(); let out_bytes = Sha256Gadget::evaluate(&unit_var, &z_i[0].to_bytes()?)?; @@ -80,14 +89,14 @@ pub mod tests { fn test_f_circuit() { let cs = ConstraintSystem::::new_ref(); - let circuit = Sha256FCircuit::::new(()); + let circuit = Sha256FCircuit::::new(()).unwrap(); let z_i = vec![Fr::from(1_u32)]; - let z_i1 = circuit.step_native(0, z_i.clone()).unwrap(); + let z_i1 = circuit.step_native(0, z_i.clone(), vec![]).unwrap(); let z_iVar = Vec::>::new_witness(cs.clone(), || Ok(z_i)).unwrap(); let computed_z_i1Var = circuit - .generate_step_constraints(cs.clone(), 0, z_iVar.clone()) + .generate_step_constraints(cs.clone(), 0, z_iVar.clone(), vec![]) .unwrap(); assert_eq!(computed_z_i1Var.value().unwrap(), z_i1); } @@ -98,10 +107,10 @@ fn main() { let num_steps = 10; let initial_state = vec![Fr::from(1_u32)]; - let F_circuit = Sha256FCircuit::::new(()); + let F_circuit = Sha256FCircuit::::new(()).unwrap(); println!("Prepare Nova ProverParams & VerifierParams"); - let (prover_params, verifier_params) = test_nova_setup::>(F_circuit); + let (prover_params, verifier_params, _) = init_nova_ivc_params::>(F_circuit); /// The idea here is that eventually we could replace the next line chunk that defines the /// `type NOVA = Nova<...>` by using another folding scheme that fulfills the `FoldingScheme` @@ -112,7 +121,7 @@ fn main() { Projective2, GVar2, Sha256FCircuit, - Pedersen, + KZG<'static, Bn254>, Pedersen, >; @@ -122,7 +131,7 @@ fn main() { // compute a step of the IVC for i in 0..num_steps { let start = Instant::now(); - folding_scheme.prove_step().unwrap(); + folding_scheme.prove_step(vec![]).unwrap(); println!("Nova::prove_step {}: {:?}", i, start.elapsed()); } diff --git a/examples/utils.rs b/examples/utils.rs index a2c86df..7b077b6 100644 --- a/examples/utils.rs +++ b/examples/utils.rs @@ -3,47 +3,97 @@ #![allow(non_camel_case_types)] #![allow(clippy::upper_case_acronyms)] #![allow(dead_code)] -use ark_pallas::{constraints::GVar, Fr, Projective}; -use ark_vesta::{constraints::GVar as GVar2, Projective as Projective2}; +use ark_bn254::{constraints::GVar, Bn254, Fr, G1Projective as G1}; +use ark_crypto_primitives::snark::SNARK; +use ark_groth16::{Groth16, ProvingKey, VerifyingKey as G16VerifierKey}; +use ark_grumpkin::{constraints::GVar as GVar2, Projective as G2}; +use ark_poly_commit::kzg10::VerifierKey as KZGVerifierKey; +use ark_std::Zero; +use std::time::Instant; -use folding_schemes::commitment::{pedersen::Pedersen, CommitmentScheme}; -use folding_schemes::folding::nova::{get_r1cs, ProverParams, VerifierParams}; -use folding_schemes::frontend::FCircuit; -use folding_schemes::transcript::poseidon::poseidon_test_config; +use folding_schemes::{ + commitment::{ + kzg::{ProverKey as KZGProverKey, KZG}, + pedersen::Pedersen, + CommitmentScheme, + }, + folding::nova::{ + decider_eth_circuit::DeciderEthCircuit, get_r1cs, Nova, ProverParams, VerifierParams, + }, + frontend::FCircuit, + transcript::poseidon::poseidon_test_config, + FoldingScheme, +}; // 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)] -pub(crate) fn test_nova_setup>( +pub(crate) fn init_nova_ivc_params>( F_circuit: FC, ) -> ( - ProverParams, Pedersen>, - VerifierParams, + ProverParams, Pedersen>, + VerifierParams, + KZGVerifierKey, ) { let mut rng = ark_std::test_rng(); let poseidon_config = poseidon_test_config::(); // get the CM & CF_CM len - let (r1cs, cf_r1cs) = - get_r1cs::(&poseidon_config, F_circuit).unwrap(); - let cf_len = r1cs.A.n_rows; - let cf_cf_len = cf_r1cs.A.n_rows; - - let (pedersen_params, _) = Pedersen::::setup(&mut rng, cf_len).unwrap(); - let (cf_pedersen_params, _) = Pedersen::::setup(&mut rng, cf_cf_len).unwrap(); - - let prover_params = - ProverParams::, Pedersen> { - poseidon_config: poseidon_config.clone(), - cs_params: pedersen_params, - cf_cs_params: cf_pedersen_params, - }; - let verifier_params = VerifierParams:: { + let (r1cs, cf_r1cs) = get_r1cs::(&poseidon_config, F_circuit).unwrap(); + let cs_len = r1cs.A.n_rows; + let cf_cs_len = cf_r1cs.A.n_rows; + + // let (pedersen_params, _) = Pedersen::::setup(&mut rng, cf_len).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, + }; + let fs_verifier_params = VerifierParams:: { poseidon_config: poseidon_config.clone(), r1cs, cf_r1cs, }; - (prover_params, verifier_params) + (fs_prover_params, fs_verifier_params, kzg_vk) +} + +/// Initializes Nova parameters and DeciderEth parameters. Only for test purposes. +#[allow(clippy::type_complexity)] +pub(crate) fn init_ivc_and_decider_params>( + f_circuit: FC, +) -> ( + ProverParams, Pedersen>, + KZGVerifierKey, + ProvingKey, + G16VerifierKey, +) { + let mut rng = rand::rngs::OsRng; + let start = Instant::now(); + let (fs_prover_params, _, kzg_vk) = init_nova_ivc_params::(f_circuit.clone()); + println!("generated Nova folding params: {:?}", start.elapsed()); + + 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() {} diff --git a/folding-schemes/src/folding/nova/circuits.rs b/folding-schemes/src/folding/nova/circuits.rs index dbf4749..f3d2663 100644 --- a/folding-schemes/src/folding/nova/circuits.rs +++ b/folding-schemes/src/folding/nova/circuits.rs @@ -258,6 +258,7 @@ pub struct AugmentedFCircuit< pub i_usize: Option, pub z_0: Option>, pub z_i: Option>, + pub external_inputs: Option>, pub u_i_cmW: Option, pub U_i: Option>, pub U_i1_cmE: Option, @@ -290,6 +291,7 @@ where i_usize: None, z_0: None, z_i: None, + external_inputs: None, u_i_cmW: None, U_i: None, U_i1_cmE: None, @@ -335,6 +337,11 @@ where .z_i .unwrap_or(vec![CF1::::zero(); self.F.state_len()])) })?; + let external_inputs = Vec::>>::new_witness(cs.clone(), || { + Ok(self + .external_inputs + .unwrap_or(vec![CF1::::zero(); self.F.external_inputs_len()])) + })?; let u_dummy = CommittedInstance::dummy(2); let U_i = CommittedInstanceVar::::new_witness(cs.clone(), || { @@ -364,9 +371,9 @@ where // get z_{i+1} from the F circuit let i_usize = self.i_usize.unwrap_or(0); - let z_i1 = self - .F - .generate_step_constraints(cs.clone(), i_usize, z_i.clone())?; + let z_i1 = + self.F + .generate_step_constraints(cs.clone(), i_usize, z_i.clone(), external_inputs)?; let is_basecase = i.is_zero()?; diff --git a/folding-schemes/src/folding/nova/decider_eth.rs b/folding-schemes/src/folding/nova/decider_eth.rs index c33c09d..2b5643b 100644 --- a/folding-schemes/src/folding/nova/decider_eth.rs +++ b/folding-schemes/src/folding/nova/decider_eth.rs @@ -321,7 +321,7 @@ pub mod tests { let mut rng = ark_std::test_rng(); let poseidon_config = poseidon_test_config::(); - let F_circuit = CubicFCircuit::::new(()); + let F_circuit = CubicFCircuit::::new(()).unwrap(); let z_0 = vec![Fr::from(3_u32)]; let (cs_len, cf_cs_len) = @@ -347,9 +347,9 @@ pub mod tests { 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(); + nova.prove_step(vec![]).unwrap(); println!("prove_step, {:?}", start.elapsed()); - nova.prove_step().unwrap(); // do a 2nd step + nova.prove_step(vec![]).unwrap(); // do a 2nd step // generate Groth16 setup let circuit = DeciderEthCircuit::< diff --git a/folding-schemes/src/folding/nova/decider_eth_circuit.rs b/folding-schemes/src/folding/nova/decider_eth_circuit.rs index 10daad2..0670c6e 100644 --- a/folding-schemes/src/folding/nova/decider_eth_circuit.rs +++ b/folding-schemes/src/folding/nova/decider_eth_circuit.rs @@ -671,11 +671,11 @@ pub mod tests { #[test] fn test_relaxed_r1cs_small_gadget_arkworks() { let z_i = vec![Fr::from(3_u32)]; - let cubic_circuit = CubicFCircuit::::new(()); + let cubic_circuit = CubicFCircuit::::new(()).unwrap(); let circuit = WrapperCircuit::> { FC: cubic_circuit, z_i: Some(z_i.clone()), - z_i1: Some(cubic_circuit.step_native(0, z_i).unwrap()), + z_i1: Some(cubic_circuit.step_native(0, z_i, vec![]).unwrap()), }; test_relaxed_r1cs_gadget(circuit); @@ -713,12 +713,12 @@ pub mod tests { #[test] fn test_relaxed_r1cs_custom_circuit() { let n_constraints = 10_000; - let custom_circuit = CustomFCircuit::::new(n_constraints); + let custom_circuit = CustomFCircuit::::new(n_constraints).unwrap(); let z_i = vec![Fr::from(5_u32)]; let circuit = WrapperCircuit::> { FC: custom_circuit, z_i: Some(z_i.clone()), - z_i1: Some(custom_circuit.step_native(0, z_i).unwrap()), + z_i1: Some(custom_circuit.step_native(0, z_i, vec![]).unwrap()), }; test_relaxed_r1cs_gadget(circuit); } @@ -729,12 +729,12 @@ pub mod tests { // in practice we would use CycleFoldCircuit, but is a very big circuit (when computed // non-natively inside the RelaxedR1CS circuit), so in order to have a short test we use a // custom circuit. - let custom_circuit = CustomFCircuit::::new(10); + let custom_circuit = CustomFCircuit::::new(10).unwrap(); let z_i = vec![Fq::from(5_u32)]; let circuit = WrapperCircuit::> { FC: custom_circuit, z_i: Some(z_i.clone()), - z_i1: Some(custom_circuit.step_native(0, z_i).unwrap()), + z_i1: Some(custom_circuit.step_native(0, z_i, vec![]).unwrap()), }; circuit.generate_constraints(cs.clone()).unwrap(); cs.finalize(); @@ -770,7 +770,7 @@ pub mod tests { let mut rng = ark_std::test_rng(); let poseidon_config = poseidon_test_config::(); - let F_circuit = CubicFCircuit::::new(()); + let F_circuit = CubicFCircuit::::new(()).unwrap(); let z_0 = vec![Fr::from(3_u32)]; // get the CS & CF_CS len @@ -802,7 +802,7 @@ pub mod tests { // generate a Nova instance and do a step of it let mut nova = NOVA::init(&prover_params, F_circuit, z_0.clone()).unwrap(); - nova.prove_step().unwrap(); + nova.prove_step(vec![]).unwrap(); let ivc_v = nova.clone(); let verifier_params = VerifierParams:: { poseidon_config: poseidon_config.clone(), diff --git a/folding-schemes/src/folding/nova/mod.rs b/folding-schemes/src/folding/nova/mod.rs index 7a0894d..73c52ca 100644 --- a/folding-schemes/src/folding/nova/mod.rs +++ b/folding-schemes/src/folding/nova/mod.rs @@ -339,9 +339,26 @@ where } /// Implements IVC.P of Nova+CycleFold - fn prove_step(&mut self) -> Result<(), Error> { + fn prove_step(&mut self, external_inputs: Vec) -> Result<(), Error> { let augmented_F_circuit: AugmentedFCircuit; + if self.z_i.len() != self.F.state_len() { + return Err(Error::NotSameLength( + "z_i.len()".to_string(), + self.z_i.len(), + "F.state_len()".to_string(), + self.F.state_len(), + )); + } + if external_inputs.len() != self.F.external_inputs_len() { + return Err(Error::NotSameLength( + "F.external_inputs_len()".to_string(), + self.F.external_inputs_len(), + "external_inputs.len()".to_string(), + external_inputs.len(), + )); + } + if self.i > C1::ScalarField::from_le_bytes_mod_order(&usize::MAX.to_le_bytes()) { return Err(Error::MaxStep); } @@ -349,7 +366,9 @@ where i_bytes.copy_from_slice(&self.i.into_bigint().to_bytes_le()[..8]); let i_usize: usize = usize::from_le_bytes(i_bytes); - let z_i1 = self.F.step_native(i_usize, self.z_i.clone())?; + let z_i1 = self + .F + .step_native(i_usize, self.z_i.clone(), external_inputs.clone())?; // compute T and cmT for AugmentedFCircuit let (T, cmT) = self.compute_cmT()?; @@ -392,6 +411,7 @@ where i_usize: Some(0), z_0: Some(self.z_0.clone()), // = z_i z_i: Some(self.z_i.clone()), + external_inputs: Some(external_inputs.clone()), u_i_cmW: Some(self.u_i.cmW), // = dummy U_i: Some(self.U_i.clone()), // = dummy U_i1_cmE: Some(U_i1.cmE), @@ -464,6 +484,7 @@ where i_usize: Some(i_usize), z_0: Some(self.z_0.clone()), z_i: Some(self.z_i.clone()), + external_inputs: Some(external_inputs.clone()), u_i_cmW: Some(self.u_i.cmW), U_i: Some(self.U_i.clone()), U_i1_cmE: Some(U_i1.cmE), @@ -803,7 +824,7 @@ pub mod tests { let mut rng = ark_std::test_rng(); let poseidon_config = poseidon_test_config::(); - let F_circuit = CubicFCircuit::::new(()); + let F_circuit = CubicFCircuit::::new(()).unwrap(); let (cs_len, cf_cs_len) = get_cs_params_len::>( @@ -854,7 +875,7 @@ pub mod tests { let num_steps: usize = 3; for _ in 0..num_steps { - nova.prove_step().unwrap(); + nova.prove_step(vec![]).unwrap(); } assert_eq!(Fr::from(num_steps as u32), nova.i); diff --git a/folding-schemes/src/frontend/circom/mod.rs b/folding-schemes/src/frontend/circom/mod.rs index b53e9b7..dd496ff 100644 --- a/folding-schemes/src/frontend/circom/mod.rs +++ b/folding-schemes/src/frontend/circom/mod.rs @@ -1,4 +1,4 @@ -use ark_circom::circom::CircomCircuit; +use ark_circom::circom::{CircomCircuit, R1CS as CircomR1CS}; use ark_ff::PrimeField; use ark_r1cs_std::alloc::AllocVar; use ark_r1cs_std::fields::fp::FpVar; @@ -18,32 +18,64 @@ use utils::CircomWrapper; #[derive(Clone, Debug)] pub struct CircomFCircuit { circom_wrapper: CircomWrapper, + state_len: usize, + external_inputs_len: usize, + r1cs: CircomR1CS, } impl FCircuit for CircomFCircuit { - type Params = (PathBuf, PathBuf); + /// (r1cs_path, wasm_path, state_len, external_inputs_len) + type Params = (PathBuf, PathBuf, usize, usize); - fn new(params: Self::Params) -> Self { - let (r1cs_path, wasm_path) = params; + fn new(params: Self::Params) -> Result { + let (r1cs_path, wasm_path, state_len, external_inputs_len) = params; let circom_wrapper = CircomWrapper::new(r1cs_path, wasm_path); - Self { circom_wrapper } + + let r1cs = circom_wrapper.extract_r1cs()?; + Ok(Self { + circom_wrapper, + state_len, + external_inputs_len, + r1cs, + }) } fn state_len(&self) -> usize { - 1 + self.state_len + } + fn external_inputs_len(&self) -> usize { + self.external_inputs_len } - fn step_native(&self, _i: usize, z_i: Vec) -> Result, Error> { - // Converts PrimeField values to BigInt for computing witness. - let input_num_bigint = z_i + fn step_native( + &self, + _i: usize, + z_i: Vec, + external_inputs: Vec, + ) -> Result, Error> { + #[cfg(test)] + assert_eq!(z_i.len(), self.state_len()); + #[cfg(test)] + assert_eq!(external_inputs.len(), self.external_inputs_len()); + + let inputs_bi = z_i .iter() .map(|val| self.circom_wrapper.ark_primefield_to_num_bigint(*val)) .collect::>(); + let mut inputs_map = vec![("ivc_input".to_string(), inputs_bi)]; + + if self.external_inputs_len() > 0 { + let external_inputs_bi = external_inputs + .iter() + .map(|val| self.circom_wrapper.ark_primefield_to_num_bigint(*val)) + .collect::>(); + inputs_map.push(("external_inputs".to_string(), external_inputs_bi)); + } // Computes witness let witness = self .circom_wrapper - .extract_witness(&[("ivc_input".to_string(), input_num_bigint)]) + .extract_witness(&inputs_map) .map_err(|e| { Error::WitnessCalculationError(format!("Failed to calculate witness: {}", e)) })?; @@ -58,54 +90,67 @@ impl FCircuit for CircomFCircuit { cs: ConstraintSystemRef, _i: usize, z_i: Vec>, + external_inputs: Vec>, ) -> Result>, SynthesisError> { - let mut input_values = Vec::new(); - // Converts each FpVar to PrimeField value, then to num_bigint::BigInt. - for fp_var in z_i.iter() { - // Extracts the PrimeField value from FpVar. - let primefield_value = fp_var.value()?; - // Converts the PrimeField value to num_bigint::BigInt. - let num_bigint_value = self - .circom_wrapper - .ark_primefield_to_num_bigint(primefield_value); - input_values.push(num_bigint_value); - } + #[cfg(test)] + assert_eq!(z_i.len(), self.state_len()); + #[cfg(test)] + assert_eq!(external_inputs.len(), self.external_inputs_len()); + + let input_values = self.fpvars_to_bigints(z_i)?; + let mut inputs_map = vec![("ivc_input".to_string(), input_values)]; - let num_bigint_inputs = vec![("ivc_input".to_string(), input_values)]; + if self.external_inputs_len() > 0 { + let external_inputs_bi = self.fpvars_to_bigints(external_inputs)?; + inputs_map.push(("external_inputs".to_string(), external_inputs_bi)); + } - // Extracts R1CS and witness. - let (r1cs, witness) = self + let witness = self .circom_wrapper - .extract_r1cs_and_witness(&num_bigint_inputs) + .extract_witness(&inputs_map) .map_err(|_| SynthesisError::AssignmentMissing)?; // Initializes the CircomCircuit. let circom_circuit = CircomCircuit { - r1cs, - witness: witness.clone(), + r1cs: self.r1cs.clone(), + witness: Some(witness.clone()), inputs_already_allocated: true, }; // Generates the constraints for the circom_circuit. - circom_circuit - .generate_constraints(cs.clone()) - .map_err(|_| SynthesisError::AssignmentMissing)?; + circom_circuit.generate_constraints(cs.clone())?; // Checks for constraint satisfaction. if !cs.is_satisfied().unwrap() { return Err(SynthesisError::Unsatisfiable); } - let w = witness.ok_or(SynthesisError::Unsatisfiable)?; - // Extracts the z_i1(next state) from the witness vector. - let z_i1: Vec> = - Vec::>::new_witness(cs.clone(), || Ok(w[1..1 + self.state_len()].to_vec()))?; + let z_i1: Vec> = Vec::>::new_witness(cs.clone(), || { + Ok(witness[1..1 + self.state_len()].to_vec()) + })?; Ok(z_i1) } } +impl CircomFCircuit { + fn fpvars_to_bigints(&self, fpVars: Vec>) -> Result, SynthesisError> { + let mut input_values = Vec::new(); + // converts each FpVar to PrimeField value, then to num_bigint::BigInt. + for fp_var in fpVars.iter() { + // extracts the PrimeField value from FpVar. + let primefield_value = fp_var.value()?; + // converts the PrimeField value to num_bigint::BigInt. + let num_bigint_value = self + .circom_wrapper + .ark_primefield_to_num_bigint(primefield_value); + input_values.push(num_bigint_value); + } + Ok(input_values) + } +} + #[cfg(test)] pub mod tests { use super::*; @@ -120,10 +165,10 @@ pub mod tests { let wasm_path = PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit_js/cubic_circuit.wasm"); - let circom_fcircuit = CircomFCircuit::::new((r1cs_path, wasm_path)); + let circom_fcircuit = CircomFCircuit::::new((r1cs_path, wasm_path, 1, 0)).unwrap(); // state_len:1, external_inputs_len:0 let z_i = vec![Fr::from(3u32)]; - let z_i1 = circom_fcircuit.step_native(1, z_i).unwrap(); + let z_i1 = circom_fcircuit.step_native(1, z_i, vec![]).unwrap(); assert_eq!(z_i1, vec![Fr::from(35u32)]); } @@ -134,7 +179,7 @@ pub mod tests { let wasm_path = PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit_js/cubic_circuit.wasm"); - let circom_fcircuit = CircomFCircuit::::new((r1cs_path, wasm_path)); + let circom_fcircuit = CircomFCircuit::::new((r1cs_path, wasm_path, 1, 0)).unwrap(); // state_len:1, external_inputs_len:0 let cs = ConstraintSystem::::new_ref(); @@ -144,7 +189,7 @@ pub mod tests { let cs = ConstraintSystem::::new_ref(); let z_i1_var = circom_fcircuit - .generate_step_constraints(cs.clone(), 1, z_i_var) + .generate_step_constraints(cs.clone(), 1, z_i_var, vec![]) .unwrap(); assert_eq!(z_i1_var.value().unwrap(), vec![Fr::from(35u32)]); } @@ -156,14 +201,14 @@ pub mod tests { let wasm_path = PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit_js/cubic_circuit.wasm"); - let circom_fcircuit = CircomFCircuit::::new((r1cs_path, wasm_path)); + let circom_fcircuit = CircomFCircuit::::new((r1cs_path, wasm_path, 1, 0)).unwrap(); // state_len:1, external_inputs_len:0 // Allocates z_i1 by using step_native function. let z_i = vec![Fr::from(3_u32)]; let wrapper_circuit = crate::frontend::tests::WrapperCircuit { FC: circom_fcircuit.clone(), z_i: Some(z_i.clone()), - z_i1: Some(circom_fcircuit.step_native(0, z_i.clone()).unwrap()), + z_i1: Some(circom_fcircuit.step_native(0, z_i.clone(), vec![]).unwrap()), }; let cs = ConstraintSystem::::new_ref(); @@ -174,4 +219,35 @@ pub mod tests { "Constraint system is not satisfied" ); } + + #[test] + fn test_circom_external_inputs() { + let r1cs_path = PathBuf::from("./src/frontend/circom/test_folder/external_inputs.r1cs"); + let wasm_path = PathBuf::from( + "./src/frontend/circom/test_folder/external_inputs_js/external_inputs.wasm", + ); + + let circom_fcircuit = CircomFCircuit::::new((r1cs_path, wasm_path, 1, 2)).unwrap(); // state_len:1, external_inputs_len:2 + + let cs = ConstraintSystem::::new_ref(); + + let z_i = vec![Fr::from(3u32)]; + let external_inputs = vec![Fr::from(6u32), Fr::from(7u32)]; + + // run native step + let z_i1 = circom_fcircuit + .step_native(1, z_i.clone(), external_inputs.clone()) + .unwrap(); + assert_eq!(z_i1, vec![Fr::from(52u32)]); + + // run gadget step + let z_i_var = Vec::>::new_witness(cs.clone(), || Ok(z_i)).unwrap(); + let external_inputs_var = + Vec::>::new_witness(cs.clone(), || Ok(external_inputs)).unwrap(); + + let z_i1_var = circom_fcircuit + .generate_step_constraints(cs.clone(), 1, z_i_var, external_inputs_var) + .unwrap(); + assert_eq!(z_i1_var.value().unwrap(), vec![Fr::from(52u32)]); + } } diff --git a/folding-schemes/src/frontend/circom/test_folder/compile.sh b/folding-schemes/src/frontend/circom/test_folder/compile.sh index a714797..d5b5e8f 100755 --- a/folding-schemes/src/frontend/circom/test_folder/compile.sh +++ b/folding-schemes/src/frontend/circom/test_folder/compile.sh @@ -1,2 +1,4 @@ #!/bin/bash -circom ./folding-schemes/src/frontend/circom/test_folder/cubic_circuit.circom --r1cs --wasm --prime bn128 --output ./folding-schemes/src/frontend/circom/test_folder/ \ No newline at end of file +circom ./folding-schemes/src/frontend/circom/test_folder/cubic_circuit.circom --r1cs --sym --wasm --prime bn128 --output ./folding-schemes/src/frontend/circom/test_folder/ + +circom ./folding-schemes/src/frontend/circom/test_folder/external_inputs.circom --r1cs --sym --wasm --prime bn128 --output ./folding-schemes/src/frontend/circom/test_folder/ diff --git a/folding-schemes/src/frontend/circom/test_folder/cubic_circuit.circom b/folding-schemes/src/frontend/circom/test_folder/cubic_circuit.circom index 247cdb3..28e2067 100644 --- a/folding-schemes/src/frontend/circom/test_folder/cubic_circuit.circom +++ b/folding-schemes/src/frontend/circom/test_folder/cubic_circuit.circom @@ -10,4 +10,3 @@ template Example () { } component main {public [ivc_input]} = Example(); - diff --git a/folding-schemes/src/frontend/circom/test_folder/external_inputs.circom b/folding-schemes/src/frontend/circom/test_folder/external_inputs.circom new file mode 100644 index 0000000..c79a713 --- /dev/null +++ b/folding-schemes/src/frontend/circom/test_folder/external_inputs.circom @@ -0,0 +1,20 @@ +pragma circom 2.0.3; + +/* + z_{i+1} == z_i^3 + z_i * external_input[0] + external_input[1] +*/ +template Example () { + signal input ivc_input[1]; // IVC state + signal input external_inputs[2]; // not state + + signal output ivc_output[1]; // next IVC state + + signal temp1; + signal temp2; + + temp1 <== ivc_input[0] * ivc_input[0]; + temp2 <== ivc_input[0] * external_inputs[0]; + ivc_output[0] <== temp1 * ivc_input[0] + temp2 + external_inputs[1]; +} + +component main {public [ivc_input]} = Example(); diff --git a/folding-schemes/src/frontend/circom/utils.rs b/folding-schemes/src/frontend/circom/utils.rs index 2599dcc..b2faa9a 100644 --- a/folding-schemes/src/frontend/circom/utils.rs +++ b/folding-schemes/src/frontend/circom/utils.rs @@ -45,6 +45,13 @@ impl CircomWrapper { Ok((r1cs, Some(witness_vec))) } + pub fn extract_r1cs(&self) -> Result, Error> { + let file = File::open(&self.r1cs_filepath)?; + let reader = BufReader::new(file); + let r1cs_file = r1cs_reader::R1CSFile::::new(reader)?; + Ok(r1cs_reader::R1CS::::from(r1cs_file)) + } + // Extracts the witness vector as a vector of PrimeField elements. pub fn extract_witness(&self, inputs: &[(String, Vec)]) -> Result, Error> { let witness_bigint = self.calculate_witness(inputs)?; diff --git a/folding-schemes/src/frontend/mod.rs b/folding-schemes/src/frontend/mod.rs index e5e7305..59f18eb 100644 --- a/folding-schemes/src/frontend/mod.rs +++ b/folding-schemes/src/frontend/mod.rs @@ -14,12 +14,16 @@ pub trait FCircuit: Clone + Debug { type Params: Debug; /// returns a new FCircuit instance - fn new(params: Self::Params) -> Self; + fn new(params: Self::Params) -> Result; /// returns the number of elements in the state of the FCircuit, which corresponds to the /// FCircuit inputs. fn state_len(&self) -> usize; + /// returns the number of elements in the external inputs used by the FCircuit. External inputs + /// are optional, and in case no external inputs are used, this method should return 0. + fn external_inputs_len(&self) -> usize; + /// computes the next state values in place, assigning z_{i+1} into z_i, and computing the new /// z_{i+1} fn step_native( @@ -28,6 +32,7 @@ pub trait FCircuit: Clone + Debug { &self, i: usize, z_i: Vec, + external_inputs: Vec, // inputs that are not part of the state ) -> Result, Error>; /// generates the constraints for the step of F for the given z_i @@ -38,6 +43,7 @@ pub trait FCircuit: Clone + Debug { cs: ConstraintSystemRef, i: usize, z_i: Vec>, + external_inputs: Vec>, // inputs that are not part of the state ) -> Result>, SynthesisError>; } @@ -61,13 +67,21 @@ pub mod tests { } impl FCircuit for CubicFCircuit { type Params = (); - fn new(_params: Self::Params) -> Self { - Self { _f: PhantomData } + fn new(_params: Self::Params) -> Result { + Ok(Self { _f: PhantomData }) } fn state_len(&self) -> usize { 1 } - fn step_native(&self, _i: usize, z_i: Vec) -> Result, Error> { + fn external_inputs_len(&self) -> usize { + 0 + } + fn step_native( + &self, + _i: usize, + z_i: Vec, + _external_inputs: 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( @@ -75,6 +89,7 @@ pub mod tests { cs: ConstraintSystemRef, _i: usize, z_i: Vec>, + _external_inputs: Vec>, ) -> Result>, SynthesisError> { let five = FpVar::::new_constant(cs.clone(), F::from(5u32))?; let z_i = z_i[0].clone(); @@ -93,16 +108,24 @@ pub mod tests { impl FCircuit for CustomFCircuit { type Params = usize; - fn new(params: Self::Params) -> Self { - Self { + fn new(params: Self::Params) -> Result { + Ok(Self { _f: PhantomData, n_constraints: params, - } + }) } fn state_len(&self) -> usize { 1 } - fn step_native(&self, _i: usize, z_i: Vec) -> Result, Error> { + fn external_inputs_len(&self) -> usize { + 0 + } + fn step_native( + &self, + _i: usize, + z_i: Vec, + _external_inputs: Vec, + ) -> Result, Error> { let mut z_i1 = F::one(); for _ in 0..self.n_constraints - 1 { z_i1 *= z_i[0]; @@ -114,6 +137,7 @@ pub mod tests { cs: ConstraintSystemRef, _i: usize, z_i: Vec>, + _external_inputs: Vec>, ) -> Result>, SynthesisError> { let mut z_i1 = FpVar::::new_witness(cs.clone(), || Ok(F::one()))?; for _ in 0..self.n_constraints - 1 { @@ -146,9 +170,9 @@ pub mod tests { let z_i1 = Vec::>::new_input(cs.clone(), || { Ok(self.z_i1.unwrap_or(vec![F::zero()])) })?; - let computed_z_i1 = self - .FC - .generate_step_constraints(cs.clone(), 0, z_i.clone())?; + let computed_z_i1 = + self.FC + .generate_step_constraints(cs.clone(), 0, z_i.clone(), vec![])?; computed_z_i1.enforce_equal(&z_i1)?; Ok(()) @@ -158,7 +182,7 @@ pub mod tests { #[test] fn test_testfcircuit() { let cs = ConstraintSystem::::new_ref(); - let F_circuit = CubicFCircuit::::new(()); + let F_circuit = CubicFCircuit::::new(()).unwrap(); let wrapper_circuit = WrapperCircuit::> { FC: F_circuit, @@ -173,12 +197,12 @@ pub mod tests { fn test_customtestfcircuit() { let cs = ConstraintSystem::::new_ref(); let n_constraints = 1000; - let custom_circuit = CustomFCircuit::::new(n_constraints); + let custom_circuit = CustomFCircuit::::new(n_constraints).unwrap(); let z_i = vec![Fr::from(5_u32)]; let wrapper_circuit = WrapperCircuit::> { FC: custom_circuit, z_i: Some(z_i.clone()), - z_i1: Some(custom_circuit.step_native(0, z_i).unwrap()), + z_i1: Some(custom_circuit.step_native(0, z_i, vec![]).unwrap()), }; wrapper_circuit.generate_constraints(cs.clone()).unwrap(); assert_eq!(cs.num_constraints(), n_constraints); diff --git a/folding-schemes/src/lib.rs b/folding-schemes/src/lib.rs index f731c1f..51d2ecc 100644 --- a/folding-schemes/src/lib.rs +++ b/folding-schemes/src/lib.rs @@ -124,7 +124,7 @@ where z_0: Vec, // initial state ) -> Result; - fn prove_step(&mut self) -> Result<(), Error>; + fn prove_step(&mut self, external_inputs: Vec) -> Result<(), Error>; // returns the state at the current step fn state(&self) -> Vec; diff --git a/solidity-verifiers/Cargo.toml b/solidity-verifiers/Cargo.toml index dc52557..f3ad089 100644 --- a/solidity-verifiers/Cargo.toml +++ b/solidity-verifiers/Cargo.toml @@ -43,4 +43,7 @@ parallel = [ [[example]] name = "full_flow" path = "../examples/full_flow.rs" -# required-features = ["light-test"] + +[[example]] +name = "circom_full_flow" +path = "../examples/circom_full_flow.rs" diff --git a/solidity-verifiers/src/verifiers/nova_cyclefold.rs b/solidity-verifiers/src/verifiers/nova_cyclefold.rs index a753779..a5337bc 100644 --- a/solidity-verifiers/src/verifiers/nova_cyclefold.rs +++ b/solidity-verifiers/src/verifiers/nova_cyclefold.rs @@ -162,13 +162,21 @@ mod tests { } impl FCircuit for CubicFCircuit { type Params = (); - fn new(_params: Self::Params) -> Self { - Self { _f: PhantomData } + fn new(_params: Self::Params) -> Result { + Ok(Self { _f: PhantomData }) } fn state_len(&self) -> usize { 1 } - fn step_native(&self, _i: usize, z_i: Vec) -> Result, Error> { + fn external_inputs_len(&self) -> usize { + 0 + } + fn step_native( + &self, + _i: usize, + z_i: Vec, + _external_inputs: 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( @@ -176,6 +184,7 @@ mod tests { cs: ConstraintSystemRef, _i: usize, z_i: Vec>, + _external_inputs: Vec>, ) -> Result>, SynthesisError> { let five = FpVar::::new_constant(cs.clone(), F::from(5u32))?; let z_i = z_i[0].clone(); @@ -196,16 +205,24 @@ mod tests { impl FCircuit for MultiInputsFCircuit { type Params = (); - fn new(_params: Self::Params) -> Self { - Self { _f: PhantomData } + fn new(_params: Self::Params) -> Result { + Ok(Self { _f: PhantomData }) } fn state_len(&self) -> usize { 5 } + fn external_inputs_len(&self) -> usize { + 0 + } /// computes the next state values in place, assigning z_{i+1} into z_i, and computing the new /// z_{i+1} - fn step_native(&self, _i: usize, z_i: Vec) -> Result, Error> { + fn step_native( + &self, + _i: usize, + z_i: Vec, + _external_inputs: Vec, + ) -> Result, Error> { let a = z_i[0] + F::from(4_u32); let b = z_i[1] + F::from(40_u32); let c = z_i[2] * F::from(4_u32); @@ -221,6 +238,7 @@ mod tests { cs: ConstraintSystemRef, _i: usize, z_i: Vec>, + _external_inputs: Vec>, ) -> Result>, SynthesisError> { let four = FpVar::::new_constant(cs.clone(), F::from(4u32))?; let forty = FpVar::::new_constant(cs.clone(), F::from(40u32))?; @@ -270,7 +288,7 @@ mod tests { ) { let mut rng = ark_std::test_rng(); let poseidon_config = poseidon_test_config::(); - let f_circuit = FC::new(()); + let f_circuit = FC::new(()).unwrap(); let (cs_len, cf_cs_len) = get_cs_params_len::(&poseidon_config, f_circuit).unwrap(); let (kzg_pk, kzg_vk): (KZGProverKey, KZGVerifierKey) = @@ -296,7 +314,7 @@ mod tests { 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(()); + let f_circuit = FC::new(()).unwrap(); pub type NOVA_FCircuit = Nova, Pedersen>; @@ -351,14 +369,14 @@ mod tests { Groth16, NOVA_FCircuit, >; - let f_circuit = FC::new(()); + let f_circuit = FC::new(()).unwrap(); let nova_cyclefold_vk = NovaCycleFoldVerifierKey::from((g16_vk.clone(), kzg_vk.clone(), f_circuit.state_len())); let mut nova = NOVA_FCircuit::init(&fs_prover_params, f_circuit, z_0).unwrap(); for _ in 0..n_steps { - nova.prove_step().unwrap(); + nova.prove_step(vec![]).unwrap(); } let rng = rand::rngs::OsRng;