arnaucube
arnaucube/sonobe-docsPublic Self-hosted repository
sonobe-docs/src/usage/frontend-circom.md
src/usage/frontend-circom.mdRaw
  1# Circom frontend
  2 > **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!
  3 
  4Experimental frontend using [arkworks/circom-compat](https://github.com/arkworks-rs/circom-compat).
  5
  6We can define the circuit to be folded in Circom. The only interface that we need to fit in is:
  7
  8```c
  9template Example(ivc_state_len, aux_inputs_len) {
 10    signal input ivc_input[ivc_state_len]; // IVC state
 11    signal input external_inputs[aux_inputs_len]; // external inputs, not part of the folding state
 12
 13    signal output ivc_output[ivc_state_len]; // next IVC state
 14    
 15    // here it goes the Circom circuit logic
 16}
 17component main {public [ivc_input]} = Example();
 18```
 19
 20The `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.
 21
 22
 23In the following image, the `ivc_input`=$z_i$, the `external_inputs`=$w_i$, and the `ivc_output`=$z_{i+1}$, and $F$ is the logic of our Circom circuit:
 24<p align="center">
 25    <img src="../imgs/folding-main-idea-diagram.png" style="width:70%;" />
 26</p>
 27
 28<br>
 29
 30So for example, the following circuit proves (at each folding step) knowledge of $x$ such that $y==x^3 + e_0x + e_1$ for a known $y$ ($e_i$ are the `external_inputs[i]`):
 31
 32```c
 33pragma circom 2.0.3;
 34
 35template CubicCircuit() {
 36    signal input ivc_input[1]; // IVC state
 37    signal input external_inputs[2]; // not part of the state
 38
 39    signal output ivc_output[1]; // next IVC state
 40
 41    signal temp1;
 42    signal temp2;
 43    
 44    temp1 <== ivc_input[0] * ivc_input[0];
 45    temp2 <== ivc_input[0] * external_inputs[0];
 46    ivc_output[0] <== temp1 * ivc_input[0] + temp2 + external_inputs[1];
 47}
 48
 49component main {public [ivc_input]} = CubicCircuit();
 50```
 51
 52<br><br>
 53
 54Once your `circom` circuit is ready, you can instantiate it with Sonobe. To do this, you will need the `struct CircomFCircuit`.
 55
 56```rust
 57// we load our circom compiled R1CS, along with the witness wasm calculator
 58let r1cs_path = PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit.r1cs");
 59let wasm_path =
 60    PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit_js/cubic_circuit.wasm"); 
 61let f_circuit_params = (r1cs_path, wasm_path, 1, 2); // state_len:1, external_inputs_len:2
 62let f_circuit = CircomFCircuit::<Fr>::new(f_circuit_params).unwrap();
 63
 64
 65// [optional] to speed things up, you can define a custom step function to avoid
 66// defaulting to the snarkjs witness calculator for the native computations,
 67// which would be slower than rust native operations
 68circom_fcircuit.set_custom_step_native(Rc::new(|_i, z_i, _external| {
 69            let z = z_i[0];
 70            Ok(vec![z * z * z + z + Fr::from(5)])
 71        }));
 72
 73pub type N =
 74    Nova<G1, GVar, G2, GVar2, CircomFCircuit<Fr>, KZG<'static, Bn254>, Pedersen<G2>, false>;
 75pub type D = DeciderEth<
 76    G1,
 77    GVar,
 78    G2,
 79    GVar2,
 80    CircomFCircuit<Fr>,
 81    KZG<'static, Bn254>,
 82    Pedersen<G2>,
 83    Groth16<Bn254>,
 84    N,
 85>;
 86
 87let poseidon_config = poseidon_canonical_config::<Fr>();
 88let mut rng = rand::rngs::OsRng;
 89
 90// prepare the Nova prover & verifier params
 91let nova_preprocess_params = PreprocessorParam::new(poseidon_config, f_circuit.clone());
 92let nova_params = N::preprocess(&mut rng, &nova_preprocess_params).unwrap();
 93
 94// initialize the folding scheme engine, in this case we use Nova
 95let mut nova = N::init(&nova_params, f_circuit.clone(), z_0).unwrap();
 96
 97// run n steps of the folding iteration
 98for (i, external_inputs_at_step) in external_inputs.iter().enumerate() {
 99    let start = Instant::now();
100    // the last parameter at 'nova.prove_step()' is for schemes that support
101    // folding more than 2 instances at each fold, such as HyperNova. Since
102    // we're using Nova, we just set it to 'None'
103    nova.prove_step(rng, external_inputs_at_step.clone(), None)
104        .unwrap();
105    println!("Nova::prove_step {}: {:?}", i, start.elapsed());
106}
107
108```
109You can find a full example using Nova to fold a Circom circuit at [sonobe/examples/circom_full_flow.rs](https://github.com/privacy-scaling-explorations/sonobe/blob/main/examples/circom_full_flow.rs).