arnaucube
arnaucube/sonobe-docsPublic Self-hosted repository
sonobe-docs/src/usage/frontend-arkworks.md
src/usage/frontend-arkworks.mdRaw
  1# Arkworks frontend
  2
  3Let'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.
  4
  5You can find most of the following examples with the rest of code to run them at the [`examples`](https://github.com/privacy-scaling-explorations/sonobe/tree/main/examples) directory of the Sonobe repo.
  6
  7## Cubic circuit
  8This first example implements the `FCircuit` trait for the R1CS example circuit from [Vitalik's post](https://www.vitalik.ca/general/2016/12/10/qap.html), which checks $x^3 + x + 5 == y$.
  9
 10$z_i$ is used as $x$, and $z_{i+1}$ is used as $y$, and at the next step, $z_{i+1}$ will be assigned to $z_i$, and a new $z_{i+1}$ will be computted.
 11
 12```rust
 13#[derive(Clone, Copy, Debug)]
 14pub struct CubicFCircuit<F: PrimeField> {
 15    _f: PhantomData<F>,
 16}
 17impl<F: PrimeField> FCircuit<F> for CubicFCircuit<F> {
 18    type Params = ();
 19    fn new(_params: Self::Params) -> Self {
 20        Self { _f: PhantomData }
 21    }
 22    fn state_len(&self) -> usize {
 23        1
 24    }
 25    fn step_native(&self, _i: usize, z_i: Vec<F>) -> Result<Vec<F>, Error> {
 26        Ok(vec![z_i[0] * z_i[0] * z_i[0] + z_i[0] + F::from(5_u32)])
 27    }
 28    fn generate_step_constraints(
 29        &self,
 30        cs: ConstraintSystemRef<F>,
 31        _i: usize,
 32        z_i: Vec<FpVar<F>>,
 33    ) -> Result<Vec<FpVar<F>>, SynthesisError> {
 34        let five = FpVar::<F>::new_constant(cs.clone(), F::from(5u32))?;
 35        let z_i = z_i[0].clone();
 36
 37        Ok(vec![&z_i * &z_i * &z_i + &z_i + &five])
 38    }
 39}
 40```
 41
 42
 43## Folding a simple circuit
 44
 45The 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:
 46
 471. adding 4 to the first element
 482. adding 40 to the second element
 493. multiplying the third element by 4
 504. multiplying the fourth element by 40
 515. adding 100 to the fifth element
 52
 53Let's implement this now:
 54
 55```rust
 56// Define a struct that will be our circuit. This struct will implement the FCircuit trait.
 57#[derive(Clone, Copy, Debug)]
 58pub struct MultiInputsFCircuit<F: PrimeField> {
 59    _f: PhantomData<F>,
 60}
 61
 62// Implement the FCircuit trait for the struct
 63impl<F: PrimeField> FCircuit<F> for MultiInputsFCircuit<F> {
 64    type Params = ();
 65
 66    fn new(_params: Self::Params) -> Self {
 67        Self { _f: PhantomData }
 68    }
 69
 70    fn state_len(&self) -> usize {
 71        5 // This circuit has 5 inputs
 72    }
 73
 74    // Computes the next state values in place, assigning z_{i+1} into z_i, and computing the new z_{i+1}
 75    // We want the `step_native` method to implement the same logic as the `generate_step_constraints` method
 76    fn step_native(&self, _i: usize, z_i: Vec<F>) -> Result<Vec<F>, Error> {
 77        let a = z_i[0] + F::from(4_u32);
 78        let b = z_i[1] + F::from(40_u32);
 79        let c = z_i[2] * F::from(4_u32);
 80        let d = z_i[3] * F::from(40_u32);
 81        let e = z_i[4] + F::from(100_u32);
 82
 83        Ok(vec![a, b, c, d, e]) // The length of the returned vector should match `state_len`
 84    }
 85
 86    /// Generates R1CS constraints for the step of F for the given z_i
 87    fn generate_step_constraints(
 88        &self,
 89        cs: ConstraintSystemRef<F>,
 90        _i: usize,
 91        z_i: Vec<FpVar<F>>,
 92    ) -> Result<Vec<FpVar<F>>, SynthesisError> {
 93        // Implementing the circuit constraints
 94        let four = FpVar::<F>::new_constant(cs.clone(), F::from(4u32))?;
 95        let forty = FpVar::<F>::new_constant(cs.clone(), F::from(40u32))?;
 96        let onehundred = FpVar::<F>::new_constant(cs.clone(), F::from(100u32))?;
 97        let a = z_i[0].clone() + four.clone();
 98        let b = z_i[1].clone() + forty.clone();
 99        let c = z_i[2].clone() * four;
100        let d = z_i[3].clone() * forty;
101        let e = z_i[4].clone() + onehundred;
102
103        Ok(vec![a, b, c, d, e]) // The length of the returned vector should match `state_len`
104    }
105}
106```
107
108## Folding a `Sha256` circuit
109
110We 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. 
111
112Note 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.
113
114Note: to simplify things for the example, only the first byte outputted by the sha256 is used for the next state $z_{i+1}$.
115
116```rust
117// Define a struct that will be our circuit. This struct will implement the FCircuit trait.
118#[derive(Clone, Copy, Debug)]
119pub struct Sha256FCircuit<F: PrimeField> {
120    _f: PhantomData<F>,
121}
122
123impl<F: PrimeField> FCircuit<F> for Sha256FCircuit<F> {
124    type Params = ();
125
126    fn new(_params: Self::Params) -> Self {
127        Self { _f: PhantomData }
128    }
129    fn state_len(&self) -> usize {
130        1
131    }
132
133    /// Computes the next state values in place, assigning z_{i+1} into z_i, and computing the new
134    /// z_{i+1}
135    fn step_native(&self, _i: usize, z_i: Vec<F>) -> Result<Vec<F>, Error> {
136        let out_bytes = Sha256::evaluate(&(), z_i[0].into_bigint().to_bytes_le()).unwrap();
137        let out: Vec<F> = out_bytes.to_field_elements().unwrap();
138
139        Ok(vec![out[0]])
140    }
141
142    /// Generates the constraints for the step of F for the given z_i
143    fn generate_step_constraints(
144        &self,
145        _cs: ConstraintSystemRef<F>,
146        _i: usize,
147        z_i: Vec<FpVar<F>>,
148    ) -> Result<Vec<FpVar<F>>, SynthesisError> {
149        let unit_var = UnitVar::default();
150        let out_bytes = Sha256Gadget::evaluate(&unit_var, &z_i[0].to_bytes()?)?;
151        let out = out_bytes.0.to_constraint_field()?;
152        Ok(vec![out[0].clone()])
153    }
154}
155```
156
157
158
159## Using external inputs
160
161In this example we set the state to be the previous state together with an external input, and the new state is an array which contains the new state and a zero which will be ignored.
162
163This is useful for example if we want to fold multiple verifications of signatures, where the circuit F checks the signature and is folded for each of the signatures and public keys. To keep things simpler, the following example does not verify signatures but does a similar approach with a chain of hashes, where each iteration hashes the previous step output ($z_i$) together with an external input ($w_i$).
164
165```
166       w_1     w_2     w_3     w_4     
167       │       │       │       │      
168       ▼       ▼       ▼       ▼      
169      ┌─┐     ┌─┐     ┌─┐     ┌─┐     
170─────►│F├────►│F├────►│F├────►│F├────►
171 z_1  └─┘ z_2 └─┘ z_3 └─┘ z_4 └─┘ z_5
172
173
174where each F is:
175   w_i                                        
176    │     ┌────────────────────┐              
177    │     │FCircuit            │              
178    │     │                    │              
179    └────►│ h =Hash(z_i[0],w_i)│              
180          │ │ =Hash(v, w_i)    │              
181 ────────►│ │                  ├───────►      
182z_i=[v,0] │ └──►z_{i+1}=[h, 0] │ z_{i+1}=[h,0]
183          │                    │              
184          └────────────────────┘
185```
186
187where each $w_i$ value is set at the `external_inputs` array.
188
189The last state $z_i$ is used together with the external input w_i as inputs to compute the new state $z_{i+1}$.
190The function F will output the new state in an array of two elements, where the second element is a 0. In other words, $z_{i+1} = [F([z_i, w_i]), 0]$, and the 0 will be replaced by $w_{i+1}$ in the next iteration, so $z_{i+2} = [F([z_{i+1}, w_{i+1}]), 0]$.
191
192```rust
193#[derive(Clone, Debug)]
194pub struct ExternalInputsCircuits<F: PrimeField>
195where
196    F: Absorb,
197{
198    _f: PhantomData<F>,
199    poseidon_config: PoseidonConfig<F>,
200    external_inputs: Vec<F>,
201}
202impl<F: PrimeField> FCircuit<F> for ExternalInputsCircuits<F>
203where
204    F: Absorb,
205{
206    type Params = (PoseidonConfig<F>, Vec<F>); // where Vec<F> contains the external inputs
207
208    fn new(params: Self::Params) -> Self {
209        Self {
210            _f: PhantomData,
211            poseidon_config: params.0,
212            external_inputs: params.1,
213        }
214    }
215    fn state_len(&self) -> usize {
216        2
217    }
218
219    /// computes the next state values in place, assigning z_{i+1} into z_i, and computing the new
220    /// z_{i+1}
221    fn step_native(&self, i: usize, z_i: Vec<F>) -> Result<Vec<F>, Error> {
222        let input: [F; 2] = [z_i[0], self.external_inputs[i]];
223        let h = CRH::<F>::evaluate(&self.poseidon_config, input).unwrap();
224        Ok(vec![h, F::zero()])
225    }
226
227    /// generates the constraints for the step of F for the given z_i
228    fn generate_step_constraints(
229        &self,
230        cs: ConstraintSystemRef<F>,
231        i: usize,
232        z_i: Vec<FpVar<F>>,
233    ) -> Result<Vec<FpVar<F>>, SynthesisError> {
234        let crh_params =
235            CRHParametersVar::<F>::new_constant(cs.clone(), self.poseidon_config.clone())?;
236        let external_inputVar =
237            FpVar::<F>::new_witness(cs.clone(), || Ok(self.external_inputs[i])).unwrap();
238        let input: [FpVar<F>; 2] = [z_i[0].clone(), external_inputVar.clone()];
239        let h = CRHGadget::<F>::evaluate(&crh_params, &input)?;
240        Ok(vec![h, FpVar::<F>::zero()])
241    }
242}
243```