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
jump to
@@ -79,10 +79,18 @@ runs-on: ubuntu-latest
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
@@ -2,8 +2,10 @@ /target
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
@@ -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::<Fr>::new(f_circuit_params).unwrap(); + + let (fs_prover_params, kzg_vk, g16_pk, g16_vk) = + init_ivc_and_decider_params::<CircomFCircuit<Fr>>(f_circuit.clone()); + + pub type NOVA = + Nova<G1, GVar, G2, GVar2, CircomFCircuit<Fr>, KZG<'static, Bn254>, Pedersen<G2>>; + pub type DECIDERETH_FCircuit = DeciderEth< + G1, + GVar, + G2, + GVar2, + CircomFCircuit<Fr>, + KZG<'static, Bn254>, + Pedersen<G2>, + Groth16<Bn254>, + 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<u8> = 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(""); +}
@@ -3,6 +3,7 @@ #![allow(non_upper_case_globals)]
#![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 @@ },
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 @@ /// │ ┌────────────────────┐
/// │ │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 @@ /// where each w_i value is set at the external_inputs array.
/// /// 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<F: PrimeField> where@@ -76,47 +71,53 @@ F: Absorb,
{ _f: PhantomData<F>, poseidon_config: PoseidonConfig<F>, - external_inputs: Vec<F>, } impl<F: PrimeField> FCircuit<F> for ExternalInputsCircuits<F> where F: Absorb, { - type Params = (PoseidonConfig<F>, Vec<F>); // where Vec<F> contains the external inputs + type Params = PoseidonConfig<F>; - fn new(params: Self::Params) -> Self { - Self { + fn new(params: Self::Params) -> Result<Self, Error> { + 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<F>) -> Result<Vec<F>, Error> { - let input: [F; 2] = [z_i[0], self.external_inputs[i]]; - let h = CRH::<F>::evaluate(&self.poseidon_config, input).unwrap(); - Ok(vec![h, F::zero()]) + fn step_native( + &self, + _i: usize, + z_i: Vec<F>, + external_inputs: Vec<F>, + ) -> Result<Vec<F>, Error> { + let hash_input: [F; 2] = [z_i[0], external_inputs[0]]; + let h = CRH::<F>::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<F>, - i: usize, + _i: usize, z_i: Vec<FpVar<F>>, + external_inputs: Vec<FpVar<F>>, ) -> Result<Vec<FpVar<F>>, SynthesisError> { let crh_params = CRHParametersVar::<F>::new_constant(cs.clone(), self.poseidon_config.clone())?; - let external_inputVar = - FpVar::<F>::new_witness(cs.clone(), || Ok(self.external_inputs[i])).unwrap(); - let input: [FpVar<F>; 2] = [z_i[0].clone(), external_inputVar.clone()]; - let h = CRHGadget::<F>::evaluate(&crh_params, &input)?; - Ok(vec![h, FpVar::<F>::zero()]) + let hash_input: [FpVar<F>; 2] = [z_i[0].clone(), external_inputs[0].clone()]; + let h = CRHGadget::<F>::evaluate(&crh_params, &hash_input)?; + Ok(vec![h]) } }@@ -134,14 +135,20 @@ let poseidon_config = poseidon_test_config::<Fr>();
let cs = ConstraintSystem::<Fr>::new_ref(); - let circuit = ExternalInputsCircuits::<Fr>::new((poseidon_config, vec![Fr::from(3_u32)])); - let z_i = vec![Fr::from(1_u32), Fr::zero()]; + let circuit = ExternalInputsCircuits::<Fr>::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::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(z_i)).unwrap(); + let external_inputsVar = + Vec::<FpVar<Fr>>::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 @@
/// 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::<Fr>(); - let F_circuit = ExternalInputsCircuits::<Fr>::new((poseidon_config, external_inputs)); + let F_circuit = ExternalInputsCircuits::<Fr>::new(poseidon_config).unwrap(); println!("Prepare Nova ProverParams & VerifierParams"); - let (prover_params, verifier_params) = - test_nova_setup::<ExternalInputsCircuits<Fr>>(F_circuit.clone()); + let (prover_params, verifier_params, _) = + init_nova_ivc_params::<ExternalInputsCircuits<Fr>>(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 @@ GVar,
Projective2, GVar2, ExternalInputsCircuits<Fr>, - Pedersen<Projective>, + KZG<'static, Bn254>, Pedersen<Projective2>, >;@@ -186,9 +193,11 @@ println!("Initialize FoldingScheme");
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!(
@@ -10,32 +10,25 @@ /// - 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_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 @@ _f: PhantomData<F>,
} impl<F: PrimeField> FCircuit<F> for CubicFCircuit<F> { type Params = (); - fn new(_params: Self::Params) -> Self { - Self { _f: PhantomData } + fn new(_params: Self::Params) -> Result<Self, Error> { + Ok(Self { _f: PhantomData }) } fn state_len(&self) -> usize { 1 } - fn step_native(&self, _i: usize, z_i: Vec<F>) -> Result<Vec<F>, Error> { + fn external_inputs_len(&self) -> usize { + 0 + } + fn step_native( + &self, + _i: usize, + z_i: Vec<F>, + _external_inputs: Vec<F>, + ) -> Result<Vec<F>, 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 @@ &self,
cs: ConstraintSystemRef<F>, _i: usize, z_i: Vec<FpVar<F>>, + _external_inputs: Vec<FpVar<F>>, ) -> Result<Vec<FpVar<F>>, SynthesisError> { let five = FpVar::<F>::new_constant(cs.clone(), F::from(5u32))?; let z_i = z_i[0].clone();@@ -74,65 +76,14 @@ Ok(vec![&z_i * &z_i * &z_i + &z_i + &five])
} } -#[allow(clippy::type_complexity)] -fn init_test_prover_params<FC: FCircuit<Fr, Params = ()>>() -> ( - ProverParams<G1, G2, KZG<'static, Bn254>, Pedersen<G2>>, - KZGVerifierKey<Bn254>, -) { - let mut rng = ark_std::test_rng(); - let poseidon_config = poseidon_test_config::<Fr>(); - let f_circuit = FC::new(()); - let (cs_len, cf_cs_len) = - get_cs_params_len::<G1, GVar, G2, GVar2, FC>(&poseidon_config, f_circuit).unwrap(); - let (kzg_pk, kzg_vk): (KZGProverKey<G1>, KZGVerifierKey<Bn254>) = - KZG::<Bn254>::setup(&mut rng, cs_len).unwrap(); - let (cf_pedersen_params, _) = Pedersen::<G2>::setup(&mut rng, cf_cs_len).unwrap(); - let fs_prover_params = ProverParams::<G1, G2, KZG<Bn254>, Pedersen<G2>> { - 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<FC: FCircuit<Fr, Params = ()>>() -> ( - ProverParams<G1, G2, KZG<'static, Bn254>, Pedersen<G2>>, - KZGVerifierKey<Bn254>, - ProvingKey<Bn254>, - G16VerifierKey<Bn254>, -) { - let mut rng = rand::rngs::OsRng; - let start = Instant::now(); - let (fs_prover_params, kzg_vk) = init_test_prover_params::<FC>(); - println!("generated Nova folding params: {:?}", start.elapsed()); - let f_circuit = FC::new(()); - - pub type NOVA<FC> = Nova<G1, GVar, G2, GVar2, FC, KZG<'static, Bn254>, Pedersen<G2>>; - 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::<G1, GVar, G2, GVar2, KZG<Bn254>, Pedersen<G2>>::from_nova::<FC>( - nova.clone(), - ) - .unwrap(); - let start = Instant::now(); - let (g16_pk, g16_vk) = - Groth16::<Bn254>::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::<CubicFCircuit<Fr>>(); + let f_circuit = CubicFCircuit::<Fr>::new(()).unwrap(); + let (fs_prover_params, kzg_vk, g16_pk, g16_vk) = + init_ivc_and_decider_params::<CubicFCircuit<Fr>>(f_circuit); pub type NOVA = Nova<G1, GVar, G2, GVar2, CubicFCircuit<Fr>, KZG<'static, Bn254>, Pedersen<G2>>; pub type DECIDERETH_FCircuit = DeciderEth<@@ -146,14 +97,13 @@ Pedersen<G2>,
Groth16<Bn254>, NOVA, >; - let f_circuit = CubicFCircuit::<Fr>::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()); }
@@ -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 @@ }
impl<F: PrimeField> FCircuit<F> for MultiInputsFCircuit<F> { type Params = (); - fn new(_params: Self::Params) -> Self { - Self { _f: PhantomData } + fn new(_params: Self::Params) -> Result<Self, Error> { + 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<F>) -> Result<Vec<F>, Error> { + fn step_native( + &self, + _i: usize, + z_i: Vec<F>, + _external_inputs: Vec<F>, + ) -> Result<Vec<F>, 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 @@ &self,
cs: ConstraintSystemRef<F>, _i: usize, z_i: Vec<FpVar<F>>, + _external_inputs: Vec<FpVar<F>>, ) -> Result<Vec<FpVar<F>>, SynthesisError> { let four = FpVar::<F>::new_constant(cs.clone(), F::from(4u32))?; let forty = FpVar::<F>::new_constant(cs.clone(), F::from(40u32))?;@@ -83,7 +92,7 @@ #[test]
fn test_f_circuit() { let cs = ConstraintSystem::<Fr>::new_ref(); - let circuit = MultiInputsFCircuit::<Fr>::new(()); + let circuit = MultiInputsFCircuit::<Fr>::new(()).unwrap(); let z_i = vec![ Fr::from(1_u32), Fr::from(1_u32),@@ -92,11 +101,11 @@ Fr::from(1_u32),
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::<FpVar<Fr>>::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 @@ Fr::from(1_u32),
Fr::from(1_u32), ]; - let F_circuit = MultiInputsFCircuit::<Fr>::new(()); + let F_circuit = MultiInputsFCircuit::<Fr>::new(()).unwrap(); println!("Prepare Nova ProverParams & VerifierParams"); - let (prover_params, verifier_params) = test_nova_setup::<MultiInputsFCircuit<Fr>>(F_circuit); + let (prover_params, verifier_params, _) = + init_nova_ivc_params::<MultiInputsFCircuit<Fr>>(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 @@ GVar,
Projective2, GVar2, MultiInputsFCircuit<Fr>, - Pedersen<Projective>, + KZG<'static, Bn254>, Pedersen<Projective2>, >;@@ -137,7 +147,7 @@
// 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()); }
@@ -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 @@ }
impl<F: PrimeField> FCircuit<F> for Sha256FCircuit<F> { type Params = (); - fn new(_params: Self::Params) -> Self { - Self { _f: PhantomData } + fn new(_params: Self::Params) -> Result<Self, Error> { + 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<F>) -> Result<Vec<F>, Error> { + fn step_native( + &self, + _i: usize, + z_i: Vec<F>, + _external_inputs: Vec<F>, + ) -> Result<Vec<F>, Error> { let out_bytes = Sha256::evaluate(&(), z_i[0].into_bigint().to_bytes_le()).unwrap(); let out: Vec<F> = out_bytes.to_field_elements().unwrap();@@ -60,6 +68,7 @@ &self,
_cs: ConstraintSystemRef<F>, _i: usize, z_i: Vec<FpVar<F>>, + _external_inputs: Vec<FpVar<F>>, ) -> Result<Vec<FpVar<F>>, SynthesisError> { let unit_var = UnitVar::default(); let out_bytes = Sha256Gadget::evaluate(&unit_var, &z_i[0].to_bytes()?)?;@@ -80,14 +89,14 @@ #[test]
fn test_f_circuit() { let cs = ConstraintSystem::<Fr>::new_ref(); - let circuit = Sha256FCircuit::<Fr>::new(()); + let circuit = Sha256FCircuit::<Fr>::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::<FpVar<Fr>>::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::<Fr>::new(()); + let F_circuit = Sha256FCircuit::<Fr>::new(()).unwrap(); println!("Prepare Nova ProverParams & VerifierParams"); - let (prover_params, verifier_params) = test_nova_setup::<Sha256FCircuit<Fr>>(F_circuit); + let (prover_params, verifier_params, _) = init_nova_ivc_params::<Sha256FCircuit<Fr>>(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 @@ GVar,
Projective2, GVar2, Sha256FCircuit<Fr>, - Pedersen<Projective>, + KZG<'static, Bn254>, Pedersen<Projective2>, >;@@ -122,7 +131,7 @@
// 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()); }
@@ -3,47 +3,97 @@ #![allow(non_upper_case_globals)]
#![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<FC: FCircuit<Fr>>( +pub(crate) fn init_nova_ivc_params<FC: FCircuit<Fr>>( F_circuit: FC, ) -> ( - ProverParams<Projective, Projective2, Pedersen<Projective>, Pedersen<Projective2>>, - VerifierParams<Projective, Projective2>, + ProverParams<G1, G2, KZG<'static, Bn254>, Pedersen<G2>>, + VerifierParams<G1, G2>, + KZGVerifierKey<Bn254>, ) { let mut rng = ark_std::test_rng(); let poseidon_config = poseidon_test_config::<Fr>(); // get the CM & CF_CM len - let (r1cs, cf_r1cs) = - get_r1cs::<Projective, GVar, Projective2, GVar2, FC>(&poseidon_config, F_circuit).unwrap(); - let cf_len = r1cs.A.n_rows; - let cf_cf_len = cf_r1cs.A.n_rows; + let (r1cs, cf_r1cs) = get_r1cs::<G1, GVar, G2, GVar2, FC>(&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::<Projective>::setup(&mut rng, cf_len).unwrap(); - let (cf_pedersen_params, _) = Pedersen::<Projective2>::setup(&mut rng, cf_cf_len).unwrap(); + // let (pedersen_params, _) = Pedersen::<G1>::setup(&mut rng, cf_len).unwrap(); + let (kzg_pk, kzg_vk): (KZGProverKey<G1>, KZGVerifierKey<Bn254>) = + KZG::<Bn254>::setup(&mut rng, cs_len).unwrap(); + let (cf_pedersen_params, _) = Pedersen::<G2>::setup(&mut rng, cf_cs_len).unwrap(); - let prover_params = - ProverParams::<Projective, Projective2, Pedersen<Projective>, Pedersen<Projective2>> { - poseidon_config: poseidon_config.clone(), - cs_params: pedersen_params, - cf_cs_params: cf_pedersen_params, - }; - let verifier_params = VerifierParams::<Projective, Projective2> { + let fs_prover_params = ProverParams::<G1, G2, KZG<Bn254>, Pedersen<G2>> { + poseidon_config: poseidon_config.clone(), + cs_params: kzg_pk.clone(), + cf_cs_params: cf_pedersen_params, + }; + let fs_verifier_params = VerifierParams::<G1, G2> { 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<FC: FCircuit<Fr>>( + f_circuit: FC, +) -> ( + ProverParams<G1, G2, KZG<'static, Bn254>, Pedersen<G2>>, + KZGVerifierKey<Bn254>, + ProvingKey<Bn254>, + G16VerifierKey<Bn254>, +) { + let mut rng = rand::rngs::OsRng; + let start = Instant::now(); + let (fs_prover_params, _, kzg_vk) = init_nova_ivc_params::<FC>(f_circuit.clone()); + println!("generated Nova folding params: {:?}", start.elapsed()); + + pub type NOVA<FC> = Nova<G1, GVar, G2, GVar2, FC, KZG<'static, Bn254>, Pedersen<G2>>; + 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::<G1, GVar, G2, GVar2, KZG<Bn254>, Pedersen<G2>>::from_nova::<FC>( + nova.clone(), + ) + .unwrap(); + let start = Instant::now(); + let (g16_pk, g16_vk) = + Groth16::<Bn254>::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() {}
@@ -258,6 +258,7 @@ pub i: Option<CF1<C1>>,
pub i_usize: Option<usize>, pub z_0: Option<Vec<C1::ScalarField>>, pub z_i: Option<Vec<C1::ScalarField>>, + pub external_inputs: Option<Vec<C1::ScalarField>>, pub u_i_cmW: Option<C1>, pub U_i: Option<CommittedInstance<C1>>, pub U_i1_cmE: Option<C1>,@@ -290,6 +291,7 @@ i: None,
i_usize: None, z_0: None, z_i: None, + external_inputs: None, u_i_cmW: None, U_i: None, U_i1_cmE: None,@@ -334,6 +336,11 @@ let z_i = Vec::<FpVar<CF1<C1>>>::new_witness(cs.clone(), || {
Ok(self .z_i .unwrap_or(vec![CF1::<C1>::zero(); self.F.state_len()])) + })?; + let external_inputs = Vec::<FpVar<CF1<C1>>>::new_witness(cs.clone(), || { + Ok(self + .external_inputs + .unwrap_or(vec![CF1::<C1>::zero(); self.F.external_inputs_len()])) })?; let u_dummy = CommittedInstance::dummy(2);@@ -364,9 +371,9 @@ )?;
// 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()?;
@@ -321,7 +321,7 @@
let mut rng = ark_std::test_rng(); let poseidon_config = poseidon_test_config::<Fr>(); - let F_circuit = CubicFCircuit::<Fr>::new(()); + let F_circuit = CubicFCircuit::<Fr>::new(()).unwrap(); let z_0 = vec![Fr::from(3_u32)]; let (cs_len, cf_cs_len) =@@ -347,9 +347,9 @@ 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(); + 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::<
@@ -671,11 +671,11 @@
#[test] fn test_relaxed_r1cs_small_gadget_arkworks() { let z_i = vec![Fr::from(3_u32)]; - let cubic_circuit = CubicFCircuit::<Fr>::new(()); + let cubic_circuit = CubicFCircuit::<Fr>::new(()).unwrap(); let circuit = WrapperCircuit::<Fr, CubicFCircuit<Fr>> { 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 @@
#[test] fn test_relaxed_r1cs_custom_circuit() { let n_constraints = 10_000; - let custom_circuit = CustomFCircuit::<Fr>::new(n_constraints); + let custom_circuit = CustomFCircuit::<Fr>::new(n_constraints).unwrap(); let z_i = vec![Fr::from(5_u32)]; let circuit = WrapperCircuit::<Fr, CustomFCircuit<Fr>> { 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 @@ let cs = ConstraintSystem::<Fq>::new_ref();
// 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::<Fq>::new(10); + let custom_circuit = CustomFCircuit::<Fq>::new(10).unwrap(); let z_i = vec![Fq::from(5_u32)]; let circuit = WrapperCircuit::<Fq, CustomFCircuit<Fq>> { 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 @@ fn test_decider_circuit() {
let mut rng = ark_std::test_rng(); let poseidon_config = poseidon_test_config::<Fr>(); - let F_circuit = CubicFCircuit::<Fr>::new(()); + let F_circuit = CubicFCircuit::<Fr>::new(()).unwrap(); let z_0 = vec![Fr::from(3_u32)]; // get the CS & CF_CS len@@ -802,7 +802,7 @@ >;
// 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::<Projective, Projective2> { poseidon_config: poseidon_config.clone(),
@@ -339,9 +339,26 @@ })
} /// Implements IVC.P of Nova+CycleFold - fn prove_step(&mut self) -> Result<(), Error> { + fn prove_step(&mut self, external_inputs: Vec<C1::ScalarField>) -> Result<(), Error> { let augmented_F_circuit: AugmentedFCircuit<C1, C2, GC2, FC>; + 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 @@ let mut i_bytes: [u8; 8] = [0; 8];
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 @@ i: Some(C1::ScalarField::zero()), // = i=0
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 @@ i: Some(self.i),
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 @@ fn test_ivc() {
let mut rng = ark_std::test_rng(); let poseidon_config = poseidon_test_config::<Fr>(); - let F_circuit = CubicFCircuit::<Fr>::new(()); + let F_circuit = CubicFCircuit::<Fr>::new(()).unwrap(); let (cs_len, cf_cs_len) = get_cs_params_len::<Projective, GVar, Projective2, GVar2, CubicFCircuit<Fr>>(@@ -854,7 +875,7 @@ let mut nova = NOVA::init(&prover_params, F_circuit, z_0.clone()).unwrap();
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);
@@ -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 @@ /// Define CircomFCircuit
#[derive(Clone, Debug)] pub struct CircomFCircuit<F: PrimeField> { circom_wrapper: CircomWrapper<F>, + state_len: usize, + external_inputs_len: usize, + r1cs: CircomR1CS<F>, } impl<F: PrimeField> FCircuit<F> for CircomFCircuit<F> { - 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<Self, Error> { + 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<F>) -> Result<Vec<F>, Error> { - // Converts PrimeField values to BigInt for computing witness. - let input_num_bigint = z_i + fn step_native( + &self, + _i: usize, + z_i: Vec<F>, + external_inputs: Vec<F>, + ) -> Result<Vec<F>, 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::<Vec<BigInt>>(); + 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::<Vec<BigInt>>(); + 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 @@ &self,
cs: ConstraintSystemRef<F>, _i: usize, z_i: Vec<FpVar<F>>, + external_inputs: Vec<FpVar<F>>, ) -> Result<Vec<FpVar<F>>, 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)]; + + 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)); } - let num_bigint_inputs = vec![("ivc_input".to_string(), input_values)]; - - // 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<FpVar<F>> = - Vec::<FpVar<F>>::new_witness(cs.clone(), || Ok(w[1..1 + self.state_len()].to_vec()))?; + let z_i1: Vec<FpVar<F>> = Vec::<FpVar<F>>::new_witness(cs.clone(), || { + Ok(witness[1..1 + self.state_len()].to_vec()) + })?; Ok(z_i1) } } +impl<F: PrimeField> CircomFCircuit<F> { + fn fpvars_to_bigints(&self, fpVars: Vec<FpVar<F>>) -> Result<Vec<BigInt>, 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 @@ let r1cs_path = PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit.r1cs");
let wasm_path = PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit_js/cubic_circuit.wasm"); - let circom_fcircuit = CircomFCircuit::<Fr>::new((r1cs_path, wasm_path)); + let circom_fcircuit = CircomFCircuit::<Fr>::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 @@ let r1cs_path = PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit.r1cs");
let wasm_path = PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit_js/cubic_circuit.wasm"); - let circom_fcircuit = CircomFCircuit::<Fr>::new((r1cs_path, wasm_path)); + let circom_fcircuit = CircomFCircuit::<Fr>::new((r1cs_path, wasm_path, 1, 0)).unwrap(); // state_len:1, external_inputs_len:0 let cs = ConstraintSystem::<Fr>::new_ref();@@ -144,7 +189,7 @@ let z_i_var = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(z_i)).unwrap();
let cs = ConstraintSystem::<Fr>::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 @@ let r1cs_path = PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit.r1cs");
let wasm_path = PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit_js/cubic_circuit.wasm"); - let circom_fcircuit = CircomFCircuit::<Fr>::new((r1cs_path, wasm_path)); + let circom_fcircuit = CircomFCircuit::<Fr>::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::<Fr>::new_ref();@@ -173,5 +218,36 @@ assert!(
cs.is_satisfied().unwrap(), "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::<Fr>::new((r1cs_path, wasm_path, 1, 2)).unwrap(); // state_len:1, external_inputs_len:2 + + let cs = ConstraintSystem::<Fr>::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::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(z_i)).unwrap(); + let external_inputs_var = + Vec::<FpVar<Fr>>::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)]); } }
@@ -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/+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/
@@ -10,4 +10,3 @@ ivc_output[0] <== temp * ivc_input[0] + ivc_input[0] + 5;
} component main {public [ivc_input]} = Example(); -
@@ -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();
@@ -45,6 +45,13 @@
Ok((r1cs, Some(witness_vec))) } + pub fn extract_r1cs(&self) -> Result<R1CS<F>, Error> { + let file = File::open(&self.r1cs_filepath)?; + let reader = BufReader::new(file); + let r1cs_file = r1cs_reader::R1CSFile::<F>::new(reader)?; + Ok(r1cs_reader::R1CS::<F>::from(r1cs_file)) + } + // Extracts the witness vector as a vector of PrimeField elements. pub fn extract_witness(&self, inputs: &[(String, Vec<BigInt>)]) -> Result<Vec<F>, Error> { let witness_bigint = self.calculate_witness(inputs)?;
@@ -14,12 +14,16 @@ pub trait FCircuit<F: PrimeField>: Clone + Debug {
type Params: Debug; /// returns a new FCircuit instance - fn new(params: Self::Params) -> Self; + fn new(params: Self::Params) -> Result<Self, Error>; /// 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 @@ // can hold a state if needed to store data to compute the next state.
&self, i: usize, z_i: Vec<F>, + external_inputs: Vec<F>, // inputs that are not part of the state ) -> Result<Vec<F>, Error>; /// generates the constraints for the step of F for the given z_i@@ -38,6 +43,7 @@ &self,
cs: ConstraintSystemRef<F>, i: usize, z_i: Vec<FpVar<F>>, + external_inputs: Vec<FpVar<F>>, // inputs that are not part of the state ) -> Result<Vec<FpVar<F>>, SynthesisError>; }@@ -61,13 +67,21 @@ _f: PhantomData<F>,
} impl<F: PrimeField> FCircuit<F> for CubicFCircuit<F> { type Params = (); - fn new(_params: Self::Params) -> Self { - Self { _f: PhantomData } + fn new(_params: Self::Params) -> Result<Self, Error> { + Ok(Self { _f: PhantomData }) } fn state_len(&self) -> usize { 1 } - fn step_native(&self, _i: usize, z_i: Vec<F>) -> Result<Vec<F>, Error> { + fn external_inputs_len(&self) -> usize { + 0 + } + fn step_native( + &self, + _i: usize, + z_i: Vec<F>, + _external_inputs: Vec<F>, + ) -> Result<Vec<F>, 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 @@ &self,
cs: ConstraintSystemRef<F>, _i: usize, z_i: Vec<FpVar<F>>, + _external_inputs: Vec<FpVar<F>>, ) -> Result<Vec<FpVar<F>>, SynthesisError> { let five = FpVar::<F>::new_constant(cs.clone(), F::from(5u32))?; let z_i = z_i[0].clone();@@ -93,16 +108,24 @@ }
impl<F: PrimeField> FCircuit<F> for CustomFCircuit<F> { type Params = usize; - fn new(params: Self::Params) -> Self { - Self { + fn new(params: Self::Params) -> Result<Self, Error> { + Ok(Self { _f: PhantomData, n_constraints: params, - } + }) } fn state_len(&self) -> usize { 1 } - fn step_native(&self, _i: usize, z_i: Vec<F>) -> Result<Vec<F>, Error> { + fn external_inputs_len(&self) -> usize { + 0 + } + fn step_native( + &self, + _i: usize, + z_i: Vec<F>, + _external_inputs: Vec<F>, + ) -> Result<Vec<F>, Error> { let mut z_i1 = F::one(); for _ in 0..self.n_constraints - 1 { z_i1 *= z_i[0];@@ -114,6 +137,7 @@ &self,
cs: ConstraintSystemRef<F>, _i: usize, z_i: Vec<FpVar<F>>, + _external_inputs: Vec<FpVar<F>>, ) -> Result<Vec<FpVar<F>>, SynthesisError> { let mut z_i1 = FpVar::<F>::new_witness(cs.clone(), || Ok(F::one()))?; for _ in 0..self.n_constraints - 1 {@@ -146,9 +170,9 @@ })?;
let z_i1 = Vec::<FpVar<F>>::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 @@
#[test] fn test_testfcircuit() { let cs = ConstraintSystem::<Fr>::new_ref(); - let F_circuit = CubicFCircuit::<Fr>::new(()); + let F_circuit = CubicFCircuit::<Fr>::new(()).unwrap(); let wrapper_circuit = WrapperCircuit::<Fr, CubicFCircuit<Fr>> { FC: F_circuit,@@ -173,12 +197,12 @@ #[test]
fn test_customtestfcircuit() { let cs = ConstraintSystem::<Fr>::new_ref(); let n_constraints = 1000; - let custom_circuit = CustomFCircuit::<Fr>::new(n_constraints); + let custom_circuit = CustomFCircuit::<Fr>::new(n_constraints).unwrap(); let z_i = vec![Fr::from(5_u32)]; let wrapper_circuit = WrapperCircuit::<Fr, CustomFCircuit<Fr>> { 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);
@@ -124,7 +124,7 @@ step_circuit: FC,
z_0: Vec<C1::ScalarField>, // initial state ) -> Result<Self, Error>; - fn prove_step(&mut self) -> Result<(), Error>; + fn prove_step(&mut self, external_inputs: Vec<C1::ScalarField>) -> Result<(), Error>; // returns the state at the current step fn state(&self) -> Vec<C1::ScalarField>;
@@ -43,4 +43,7 @@
[[example]] name = "full_flow" path = "../examples/full_flow.rs" -# required-features = ["light-test"] + +[[example]] +name = "circom_full_flow" +path = "../examples/circom_full_flow.rs"
@@ -162,13 +162,21 @@ _f: PhantomData<F>,
} impl<F: PrimeField> FCircuit<F> for CubicFCircuit<F> { type Params = (); - fn new(_params: Self::Params) -> Self { - Self { _f: PhantomData } + fn new(_params: Self::Params) -> Result<Self, Error> { + Ok(Self { _f: PhantomData }) } fn state_len(&self) -> usize { 1 } - fn step_native(&self, _i: usize, z_i: Vec<F>) -> Result<Vec<F>, Error> { + fn external_inputs_len(&self) -> usize { + 0 + } + fn step_native( + &self, + _i: usize, + z_i: Vec<F>, + _external_inputs: Vec<F>, + ) -> Result<Vec<F>, 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 @@ &self,
cs: ConstraintSystemRef<F>, _i: usize, z_i: Vec<FpVar<F>>, + _external_inputs: Vec<FpVar<F>>, ) -> Result<Vec<FpVar<F>>, SynthesisError> { let five = FpVar::<F>::new_constant(cs.clone(), F::from(5u32))?; let z_i = z_i[0].clone();@@ -196,16 +205,24 @@ }
impl<F: PrimeField> FCircuit<F> for MultiInputsFCircuit<F> { type Params = (); - fn new(_params: Self::Params) -> Self { - Self { _f: PhantomData } + fn new(_params: Self::Params) -> Result<Self, Error> { + 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<F>) -> Result<Vec<F>, Error> { + fn step_native( + &self, + _i: usize, + z_i: Vec<F>, + _external_inputs: Vec<F>, + ) -> Result<Vec<F>, 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 @@ &self,
cs: ConstraintSystemRef<F>, _i: usize, z_i: Vec<FpVar<F>>, + _external_inputs: Vec<FpVar<F>>, ) -> Result<Vec<FpVar<F>>, SynthesisError> { let four = FpVar::<F>::new_constant(cs.clone(), F::from(4u32))?; let forty = FpVar::<F>::new_constant(cs.clone(), F::from(40u32))?;@@ -270,7 +288,7 @@ KZGVerifierKey<Bn254>,
) { let mut rng = ark_std::test_rng(); let poseidon_config = poseidon_test_config::<Fr>(); - let f_circuit = FC::new(()); + let f_circuit = FC::new(()).unwrap(); let (cs_len, cf_cs_len) = get_cs_params_len::<G1, GVar, G2, GVar2, FC>(&poseidon_config, f_circuit).unwrap(); let (kzg_pk, kzg_vk): (KZGProverKey<G1>, KZGVerifierKey<Bn254>) =@@ -296,7 +314,7 @@ let mut rng = rand::rngs::OsRng;
let start = Instant::now(); let (fs_prover_params, kzg_vk) = init_test_prover_params::<FC>(); println!("generated Nova folding params: {:?}", start.elapsed()); - let f_circuit = FC::new(()); + let f_circuit = FC::new(()).unwrap(); pub type NOVA_FCircuit<FC> = Nova<G1, GVar, G2, GVar2, FC, KZG<'static, Bn254>, Pedersen<G2>>;@@ -351,14 +369,14 @@ Pedersen<G2>,
Groth16<Bn254>, NOVA_FCircuit<FC>, >; - 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;