Experimental frontends
Warning: the following frontends are experimental and some computational and time overhead is expected when using them compared to directly using the arkworks frontend.
This section overviews how to use the various experimental frontends:
Implementing new frontends
Support for new frontends can be added (even from outside this repo) by implementing the FCircuit
trait.
Circom frontend
Note: Circom frontend will be significantly slower than the Arkworks frontend. We explain below how to implement a custom
step_native
function with your circom circuits to speed things up!
Experimental frontend using arkworks/circom-compat.
We can define the circuit to be folded in Circom. The only interface that we need to fit in is:
template Example(ivc_state_len, aux_inputs_len) {
signal input ivc_input[ivc_state_len]; // IVC state
signal input external_inputs[aux_inputs_len]; // external inputs, not part of the folding state
signal output ivc_output[ivc_state_len]; // next IVC state
// here it goes the Circom circuit logic
}
component main {public [ivc_input]} = Example();
The ivc_input
is the array that defines the initial state, and the ivc_output
is the array that defines the output state after the step. Both need to be of the same size. The external_inputs
array expects auxiliary input values.
In the following image, the ivc_input
=, the external_inputs
=, and the ivc_output
=, and is the logic of our Circom circuit:
So for example, the following circuit proves (at each folding step) knowledge of such that for a known ( are the external_inputs[i]
):
pragma circom 2.0.3;
template CubicCircuit() {
signal input ivc_input[1]; // IVC state
signal input external_inputs[2]; // not part of the 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]} = CubicCircuit();
Once your circom
circuit is ready, you can instantiate it with Sonobe. To do this, you will need the struct CircomFCircuit
.
#![allow(unused)] fn main() { // we load our circom compiled R1CS, along with the witness wasm calculator 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 f_circuit_params = (r1cs_path, wasm_path, 1, 2); // state_len:1, external_inputs_len:2 let f_circuit = CircomFCircuit::<Fr>::new(f_circuit_params).unwrap(); // [optional] to speed things up, you can define a custom step function to avoid // defaulting to the snarkjs witness calculator for the native computations, // which would be slower than rust native operations circom_fcircuit.set_custom_step_native(Rc::new(|_i, z_i, _external| { let z = z_i[0]; Ok(vec![z * z * z + z + Fr::from(5)]) })); pub type N = Nova<G1, GVar, G2, GVar2, CircomFCircuit<Fr>, KZG<'static, Bn254>, Pedersen<G2>, false>; pub type D = DeciderEth< G1, GVar, G2, GVar2, CircomFCircuit<Fr>, KZG<'static, Bn254>, Pedersen<G2>, Groth16<Bn254>, N, >; let poseidon_config = poseidon_canonical_config::<Fr>(); let mut rng = rand::rngs::OsRng; // prepare the Nova prover & verifier params let nova_preprocess_params = PreprocessorParam::new(poseidon_config, f_circuit.clone()); let nova_params = N::preprocess(&mut rng, &nova_preprocess_params).unwrap(); // initialize the folding scheme engine, in this case we use Nova let mut nova = N::init(&nova_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(); // the last parameter at 'nova.prove_step()' is for schemes that support // folding more than 2 instances at each fold, such as HyperNova. Since // we're using Nova, we just set it to 'None' nova.prove_step(rng, external_inputs_at_step.clone(), None) .unwrap(); println!("Nova::prove_step {}: {:?}", i, start.elapsed()); } }
You can find a full example using Nova to fold a Circom circuit at sonobe/examples/circom_full_flow.rs.
Noname frontend
Experimental Noname. Under the hood, we bridge compiled Noname circuits to arkworks R1CS. Our Noname integration does not support Noname's standard library for now.
Using Noname with sonobe is similar to using any other frontend. Sonobe expects that the length of your public and private (external) inputs match what the Noname circuit expects. Note that sonobe does not expect your public inputs to follow some specific naming convention when using Noname: it will assume that whatever public input variable you have is the IVC state.
This example shows how to fold a simple Noname circuit having both public and external, private inputs.
Noir frontend
Experimental Noir frontend. Under the hood, we bridge compiled Noir circuits to arkworks R1CS. Beware that sonobe assumes that the compiled Noir circuit that is being folded does not use any other opcode than an arithmetic gate: you can not fold circuits calling oracles or using unconstrained functions. You should be able to use Noir's standard library though.
Using Noir with sonobe is similar to using any other frontend. Sonobe expects that the length of your public and private (external) inputs match what the Noir circuit expects. Note that sonobe does not expect your public inputs to follow some specific naming convention when using Noir: it will assume that whatever public input variable you have is the IVC state.
This example shows how to fold a poseidon circuit from the Noir standard library.