Browse Source

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
main
arnaucube 4 months ago
parent
commit
d5c1e5f72a
21 changed files with 634 additions and 263 deletions
  1. +8
    -0
      .github/workflows/ci.yml
  2. +3
    -1
      .gitignore
  3. +156
    -0
      examples/circom_full_flow.rs
  4. +60
    -51
      examples/external_inputs.rs
  5. +22
    -72
      examples/full_flow.rs
  6. +24
    -14
      examples/multi_inputs.rs
  7. +23
    -14
      examples/sha256.rs
  8. +75
    -25
      examples/utils.rs
  9. +10
    -3
      folding-schemes/src/folding/nova/circuits.rs
  10. +3
    -3
      folding-schemes/src/folding/nova/decider_eth.rs
  11. +8
    -8
      folding-schemes/src/folding/nova/decider_eth_circuit.rs
  12. +25
    -4
      folding-schemes/src/folding/nova/mod.rs
  13. +116
    -40
      folding-schemes/src/frontend/circom/mod.rs
  14. +3
    -1
      folding-schemes/src/frontend/circom/test_folder/compile.sh
  15. +0
    -1
      folding-schemes/src/frontend/circom/test_folder/cubic_circuit.circom
  16. +20
    -0
      folding-schemes/src/frontend/circom/test_folder/external_inputs.circom
  17. +7
    -0
      folding-schemes/src/frontend/circom/utils.rs
  18. +38
    -14
      folding-schemes/src/frontend/mod.rs
  19. +1
    -1
      folding-schemes/src/lib.rs
  20. +4
    -1
      solidity-verifiers/Cargo.toml
  21. +28
    -10
      solidity-verifiers/src/verifiers/nova_cyclefold.rs

+ 8
- 0
.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

+ 3
- 1
.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

+ 156
- 0
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::<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("");
}

+ 60
- 51
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<F: PrimeField>
where
@ -76,47 +71,53 @@ where
{
_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 @@ pub mod tests {
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 @@ 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::<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 @@ fn main() {
Projective2,
GVar2,
ExternalInputsCircuits<Fr>,
Pedersen<Projective>,
KZG<'static, Bn254>,
Pedersen<Projective2>,
>;
@ -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!(

+ 22
- 72
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<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 @@ impl FCircuit for CubicFCircuit {
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 @@ impl FCircuit for CubicFCircuit {
}
}
#[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 @@ fn main() {
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());
}

+ 24
- 14
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<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 @@ impl FCircuit for MultiInputsFCircuit {
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 @@ pub mod tests {
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 @@ 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::<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 @@ fn main() {
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 @@ fn main() {
Projective2,
GVar2,
MultiInputsFCircuit<Fr>,
Pedersen<Projective>,
KZG<'static, Bn254>,
Pedersen<Projective2>,
>;
@ -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());
}

+ 23
- 14
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<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 @@ impl FCircuit for Sha256FCircuit {
_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 @@ pub mod tests {
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 @@ fn main() {
Projective2,
GVar2,
Sha256FCircuit<Fr>,
Pedersen<Projective>,
KZG<'static, Bn254>,
Pedersen<Projective2>,
>;
@ -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());
}

+ 75
- 25
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<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 (pedersen_params, _) = Pedersen::<Projective>::setup(&mut rng, cf_len).unwrap();
let (cf_pedersen_params, _) = Pedersen::<Projective2>::setup(&mut rng, cf_cf_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 (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::<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 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() {}

+ 10
- 3
folding-schemes/src/folding/nova/circuits.rs

@ -258,6 +258,7 @@ pub struct AugmentedFCircuit<
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 @@ 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::<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);
let U_i = CommittedInstanceVar::<C1>::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()?;

+ 3
- 3
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::<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 @@ 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::<

+ 8
- 8
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::<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 @@ pub mod tests {
#[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 @@ 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::<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 @@ pub mod tests {
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 @@ 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::<Projective, Projective2> {
poseidon_config: poseidon_config.clone(),

+ 25
- 4
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<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 @@ 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::<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 @@ 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);

+ 116
- 40
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<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 @@ impl FCircuit for CircomFCircuit {
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)];
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<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 @@ pub mod tests {
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 @@ pub mod tests {
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 @@ pub mod tests {
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 @@ pub mod tests {
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();
@ -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::<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)]);
}
}

+ 3
- 1
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/
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/

+ 0
- 1
folding-schemes/src/frontend/circom/test_folder/cubic_circuit.circom

@ -10,4 +10,3 @@ template Example () {
}
component main {public [ivc_input]} = Example();

+ 20
- 0
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();

+ 7
- 0
folding-schemes/src/frontend/circom/utils.rs

@ -45,6 +45,13 @@ impl CircomWrapper {
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)?;

+ 38
- 14
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<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 @@ pub trait FCircuit: Clone + Debug {
&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 @@ pub trait FCircuit: Clone + Debug {
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 @@ pub mod tests {
}
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 @@ pub mod tests {
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 @@ pub mod tests {
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 @@ pub mod tests {
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 @@ pub mod tests {
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 @@ pub mod tests {
#[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 @@ pub mod tests {
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);

+ 1
- 1
folding-schemes/src/lib.rs

@ -124,7 +124,7 @@ where
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>;

+ 4
- 1
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"

+ 28
- 10
solidity-verifiers/src/verifiers/nova_cyclefold.rs

@ -162,13 +162,21 @@ mod tests {
}
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 @@ mod tests {
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 @@ mod tests {
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 @@ mod tests {
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 @@ mod tests {
) {
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 @@ mod tests {
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 @@ mod tests {
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;

Loading…
Cancel
Save