arnaucube
arnaucube/sonobe-docsPublic Self-hosted repository
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) 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 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```