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 currently available frontends are [`circom`](https://github.com/iden3/circom) and [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 of this section, 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## Folding a `Sha256` circuit
116
117We 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.
118
119Note that the logic here is also very similar to the previous example: write a struct that will hold the circuit, implement the `FCircuit` trait for the struct, ensure that the length of the state is correct, and implement the `step_native` and `generate_step_constraints` methods.
120
121```rust
122// Define a struct that will be our circuit. This struct will implement the FCircuit trait.
123#[derive(Clone, Copy, Debug)]
124pub struct Sha256FCircuit<F: PrimeField> {
125 _f: PhantomData<F>,
126}
127
128impl<F: PrimeField> FCircuit<F> for Sha256FCircuit<F> {
129 type Params = ();
130
131 fn new(_params: Self::Params) -> Self {
132 Self { _f: PhantomData }
133 }
134 fn state_len(&self) -> usize {
135 1
136 }
137
138 /// Computes the next state values in place, assigning z_{i+1} into z_i, and computing the new
139 /// z_{i+1}
140 fn step_native(&self, _i: usize, z_i: Vec<F>) -> Result<Vec<F>, Error> {
141 let out_bytes = Sha256::evaluate(&(), z_i[0].into_bigint().to_bytes_le()).unwrap();
142 let out: Vec<F> = out_bytes.to_field_elements().unwrap();
143
144 Ok(vec![out[0]])
145 }
146
147 /// Generates the constraints for the step of F for the given z_i
148 fn generate_step_constraints(
149 &self,
150 _cs: ConstraintSystemRef<F>,
151 _i: usize,
152 z_i: Vec<FpVar<F>>,
153 ) -> Result<Vec<FpVar<F>>, SynthesisError> {
154 let unit_var = UnitVar::default();
155 let out_bytes = Sha256Gadget::evaluate(&unit_var, &z_i[0].to_bytes()?)?;
156 let out = out_bytes.0.to_constraint_field()?;
157 Ok(vec![out[0].clone()])
158 }
159}
160```
161
162## Folding a circuit with public and private inputs
163
164Sometimes, the circuit to be folded will have private inputs. Let's see how we can setup such a circuit to be folded with sonobe. Again, the logic here is also very similar to our previous examples. The main difference is that the `struct` which will hold the circuit also holds a `Vec` of private inputs.
165
166```rust
167#[derive(Clone, Debug)]
168pub struct ACircuitWithPrivateState<F: PrimeField> {
169 _f: PhantomData<F>,
170 private_state: Vec<F>, // private inputs, here a `Vec` of field elements, but you can specify whatever type you prefer here
171}
172
173impl<F: PrimeField> FCircuit<F> for ACircuitWithPrivateState<F> {
174 type Params = Vec<F>;
175
176 fn new(params: Self::Params) -> Self {
177 Self {
178 _f: PhantomData,
179 private_state: params,
180 }
181 }
182
183 fn state_len(&self) -> usize {
184 3 // the length of the state should match the size of the public inputs, not including the private inputs
185 }
186
187 fn step_native(&self, i: usize, z_i: Vec<F>) -> Result<Vec<F>, folding_schemes::Error> {
188 let a = z_i[0] + F::from(4_u32);
189 let b = z_i[1] + F::from(40_u32);
190 let c = z_i[2] * F::from(4_u32);
191 let d = self.private_state[0] * a;
192 let e = self.private_state[1] * c;
193
194 Ok(vec![a, b, c])
195 Ok(new_z_i)
196 }
197
198 fn generate_step_constraints(
199 &self,
200 cs: ConstraintSystemRef<F>,
201 i: usize,
202 z_i: Vec<ark_r1cs_std::fields::fp::FpVar<F>>,
203 ) -> Result<Vec<ark_r1cs_std::fields::fp::FpVar<F>>, SynthesisError> {
204 let four = FpVar::<F>::new_constant(cs.clone(), F::from(4u32))?;
205 let forty = FpVar::<F>::new_constant(cs.clone(), F::from(40u32))?;
206 let onehundred = FpVar::<F>::new_constant(cs.clone(), F::from(100u32))?;
207
208 // we still need to allocate our private state as witnesses
209 let priv_var_0 = FpVar::<F>::new_witness(cs.clone(), || Ok(self.private_state[0].clone()))?;
210 let priv_var_1 = FpVar::<F>::new_witness(cs.clone(), || Ok(self.private_state[1].clone()))?;
211
212 let a = z_i[0].clone() + four.clone();
213 let b = z_i[1].clone() + forty.clone();
214 let c = z_i[2].clone() * four;
215 let d = priv_var_0 * a;
216 let e = priv_var_1 * c;
217
218 Ok(vec![a, b, c])
219 }
220}
221
222```