sonobe-docs/src/usage/frontend.md
src/usage/frontend.mdRaw
1# Frontend
2
3The frontend interface allows to define the circuit to be folded. The available frontends are [`circom`](https://github.com/iden3/circom) or [arkworks](https://github.com/arkworks-rs/r1cs-std). We will show here how to define a circuit using `arkworks`.
4
5# The `FCircuit` trait
6
7To be folded with sonobe, a circuit needs to implement the [`FCircuit` trait](https://github.com/privacy-scaling-explorations/sonobe/blob/main/sonobe/src/frontend/mod.rs). This trait defines the methods that sonobe expects from the circuit to be folded. It corresponds to the $F$ function that is being folded. The trait has the following methods:
8
9```rust
10/// FCircuit defines the trait of the circuit of the F function, which is the one being folded (ie.
11/// inside the agmented F' function).
12/// The parameter z_i denotes the current state, and z_{i+1} denotes the next state after applying
13/// the step.
14pub trait FCircuit<F: PrimeField>: Clone + Debug {
15 type Params: Debug;
16
17 /// returns a new FCircuit instance
18 fn new(params: Self::Params) -> Self;
19
20 /// returns the number of elements in the state of the FCircuit, which corresponds to the
21 /// FCircuit inputs.
22 fn state_len(&self) -> usize;
23
24 /// computes the next state values in place, assigning z_{i+1} into z_i, and computing the new
25 /// z_{i+1}
26 fn step_native(
27 // this method uses self, so that each FCircuit implementation (and different frontends)
28 // can hold a state if needed to store data to compute the next state.
29 &self,
30 i: usize,
31 z_i: Vec<F>,
32 ) -> Result<Vec<F>, Error>;
33
34 /// generates the constraints for the step of F for the given z_i
35 fn generate_step_constraints(
36 // this method uses self, so that each FCircuit implementation (and different frontends)
37 // can hold a state if needed to store data to generate the constraints.
38 &self,
39 cs: ConstraintSystemRef<F>,
40 i: usize,
41 z_i: Vec<FpVar<F>>,
42 ) -> Result<Vec<FpVar<F>>, SynthesisError>;
43}
44```
45
46# Example
47
48Let's walk through different simple examples implementing the `FCircuit` trait. By the end, you will hopefully be familiar with how to integrate an `arkworks` circuit into sonobe.
49
50## Folding a simple circuit
51
52The circuit we will fold has a state of 5 public elements. At each step, we will want the circuit to compute the next state by:
53
541. adding 4 to the first element
552. adding 40 to the second element
563. multiplying the third element by 4
574. multiplying the fourth element by 40
585. adding 100 to the fifth element
59
60Let's implement this now:
61
62```rust
63// Define a struct that will be our circuit. This struct will implement the FCircuit trait.
64#[derive(Clone, Copy, Debug)]
65pub struct MultiInputsFCircuit<F: PrimeField> {
66 _f: PhantomData<F>,
67}
68
69// Implement the FCircuit trait for the struct
70impl<F: PrimeField> FCircuit<F> for MultiInputsFCircuit<F> {
71 type Params = ();
72
73 fn new(_params: Self::Params) -> Self {
74 Self { _f: PhantomData }
75 }
76
77 fn state_len(&self) -> usize {
78 5 // This circuit has 5 inputs
79 }
80
81 // Computes the next state values in place, assigning z_{i+1} into z_i, and computing the new z_{i+1}
82 // We want the `step_native` method to implement the same logic as the `generate_step_constraints` method
83 fn step_native(&self, _i: usize, z_i: Vec<F>) -> Result<Vec<F>, Error> {
84 let a = z_i[0] + F::from(4_u32);
85 let b = z_i[1] + F::from(40_u32);
86 let c = z_i[2] * F::from(4_u32);
87 let d = z_i[3] * F::from(40_u32);
88 let e = z_i[4] + F::from(100_u32);
89
90 Ok(vec![a, b, c, d, e]) // The length of the returned vector should match `state_len`
91 }
92
93 /// generates R1CS constraints for the step of F for the given z_i
94 fn generate_step_constraints(
95 &self,
96 cs: ConstraintSystemRef<F>,
97 _i: usize,
98 z_i: Vec<FpVar<F>>,
99 ) -> Result<Vec<FpVar<F>>, SynthesisError> {
100 // Implementing the circuit constraints
101 let four = FpVar::<F>::new_constant(cs.clone(), F::from(4u32))?;
102 let forty = FpVar::<F>::new_constant(cs.clone(), F::from(40u32))?;
103 let onehundred = FpVar::<F>::new_constant(cs.clone(), F::from(100u32))?;
104 let a = z_i[0].clone() + four.clone();
105 let b = z_i[1].clone() + forty.clone();
106 let c = z_i[2].clone() * four;
107 let d = z_i[3].clone() * forty;
108 let e = z_i[4].clone() + onehundred;
109
110 Ok(vec![a, b, c, d, e]) // The length of the returned vector should match `state_len`
111 }
112}
113
114```
115
116## Folding a `Sha256` circuit
117
118We will fold a simple `Sha256` circuit. The circuit has a state of 1 public element. At each step, we will want the circuit to compute the next state by applying the `Sha256` function to the current state.
119
120```rust
121#[derive(Clone, Copy, Debug)]
122pub struct Sha256FCircuit<F: PrimeField> {
123 _f: PhantomData<F>,
124}
125impl<F: PrimeField> FCircuit<F> for Sha256FCircuit<F> {
126 type Params = ();
127
128 fn new(_params: Self::Params) -> Self {
129 Self { _f: PhantomData }
130 }
131 fn state_len(&self) -> usize {
132 1
133 }
134
135 /// computes the next state values in place, assigning z_{i+1} into z_i, and computing the new
136 /// z_{i+1}
137 fn step_native(&self, _i: usize, z_i: Vec<F>) -> Result<Vec<F>, Error> {
138 let out_bytes = Sha256::evaluate(&(), z_i[0].into_bigint().to_bytes_le()).unwrap();
139 let out: Vec<F> = out_bytes.to_field_elements().unwrap();
140
141 Ok(vec![out[0]])
142 }
143
144 /// generates the constraints for the step of F for the given z_i
145 fn generate_step_constraints(
146 &self,
147 _cs: ConstraintSystemRef<F>,
148 _i: usize,
149 z_i: Vec<FpVar<F>>,
150 ) -> Result<Vec<FpVar<F>>, SynthesisError> {
151 let unit_var = UnitVar::default();
152 let out_bytes = Sha256Gadget::evaluate(&unit_var, &z_i[0].to_bytes()?)?;
153 let out = out_bytes.0.to_constraint_field()?;
154 Ok(vec![out[0].clone()])
155 }
156}
157```
158
159## Folding a circuit with public and private inputs