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 external_inputs_len(&self) -> usize {
 26        0
 27    }
 28    fn step_native(&self, _i: usize, z_i: Vec<F>, _external_inputs: Vec<F>) -> Result<Vec<F>, Error> {
 29        Ok(vec![z_i[0] * z_i[0] * z_i[0] + z_i[0] + F::from(5_u32)])
 30    }
 31    fn generate_step_constraints(
 32        &self,
 33        cs: ConstraintSystemRef<F>,
 34        _i: usize,
 35        z_i: Vec<FpVar<F>>,
 36        _external_inputs: Vec<FpVar<F>>,
 37    ) -> Result<Vec<FpVar<F>>, SynthesisError> {
 38        let five = FpVar::<F>::new_constant(cs.clone(), F::from(5u32))?;
 39        let z_i = z_i[0].clone();
 40
 41        Ok(vec![&z_i * &z_i * &z_i + &z_i + &five])
 42    }
 43}
 44```
 45
 46
 47## Folding a simple circuit
 48
 49The 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:
 50
 511. adding 4 to the first element
 522. adding 40 to the second element
 533. multiplying the third element by 4
 544. multiplying the fourth element by 40
 555. adding 100 to the fifth element
 56
 57Let's implement this now:
 58
 59```rust
 60// Define a struct that will be our circuit. This struct will implement the FCircuit trait.
 61#[derive(Clone, Copy, Debug)]
 62pub struct MultiInputsFCircuit<F: PrimeField> {
 63    _f: PhantomData<F>,
 64}
 65
 66// Implement the FCircuit trait for the struct
 67impl<F: PrimeField> FCircuit<F> for MultiInputsFCircuit<F> {
 68    type Params = ();
 69
 70    fn new(_params: Self::Params) -> Self {
 71        Self { _f: PhantomData }
 72    }
 73
 74    fn state_len(&self) -> usize {
 75        5 // This circuit has 5 inputs
 76    }
 77    fn external_inputs_len(&self) -> usize {
 78        0
 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>, _external_inputs: 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        _external_inputs: Vec<FpVar<F>>,
100    ) -> Result<Vec<FpVar<F>>, SynthesisError> {
101        // Implementing the circuit constraints
102        let four = FpVar::<F>::new_constant(cs.clone(), F::from(4u32))?;
103        let forty = FpVar::<F>::new_constant(cs.clone(), F::from(40u32))?;
104        let onehundred = FpVar::<F>::new_constant(cs.clone(), F::from(100u32))?;
105        let a = z_i[0].clone() + four.clone();
106        let b = z_i[1].clone() + forty.clone();
107        let c = z_i[2].clone() * four;
108        let d = z_i[3].clone() * forty;
109        let e = z_i[4].clone() + onehundred;
110
111        Ok(vec![a, b, c, d, e]) // The length of the returned vector should match `state_len`
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
120Note 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.
121
122Note: to simplify things for the example, only the first byte outputted by the sha256 is used for the next state $z_{i+1}$.
123
124```rust
125// Define a struct that will be our circuit. This struct will implement the FCircuit trait.
126#[derive(Clone, Copy, Debug)]
127pub struct Sha256FCircuit<F: PrimeField> {
128    _f: PhantomData<F>,
129}
130
131impl<F: PrimeField> FCircuit<F> for Sha256FCircuit<F> {
132    type Params = ();
133
134    fn new(_params: Self::Params) -> Self {
135        Self { _f: PhantomData }
136    }
137    fn state_len(&self) -> usize {
138        1
139    }
140    fn external_inputs_len(&self) -> usize {
141        0
142    }
143
144    /// Computes the next state values in place, assigning z_{i+1} into z_i, and computing the new
145    /// z_{i+1}
146    fn step_native(&self, _i: usize, z_i: Vec<F>, _external_inputs: Vec<F>) -> Result<Vec<F>, Error> {
147        let out_bytes = Sha256::evaluate(&(), z_i[0].into_bigint().to_bytes_le()).unwrap();
148        let out: Vec<F> = out_bytes.to_field_elements().unwrap();
149
150        Ok(vec![out[0]])
151    }
152
153    /// Generates the constraints for the step of F for the given z_i
154    fn generate_step_constraints(
155        &self,
156        _cs: ConstraintSystemRef<F>,
157        _i: usize,
158        z_i: Vec<FpVar<F>>,
159        _external_inputs: Vec<FpVar<F>>,
160    ) -> Result<Vec<FpVar<F>>, SynthesisError> {
161        let unit_var = UnitVar::default();
162        let out_bytes = Sha256Gadget::evaluate(&unit_var, &z_i[0].to_bytes()?)?;
163        let out = out_bytes.0.to_constraint_field()?;
164        Ok(vec![out[0].clone()])
165    }
166}
167```
168
169
170
171## Using external inputs
172
173In 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.
174
175This 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$).
176
177```
178       w_1     w_2     w_3     w_4     
179       │       │       │       │      
180       ▼       ▼       ▼       ▼      
181      ┌─┐     ┌─┐     ┌─┐     ┌─┐     
182─────►│F├────►│F├────►│F├────►│F├────►
183 z_1  └─┘ z_2 └─┘ z_3 └─┘ z_4 └─┘ z_5
184
185
186where each F is:
187  w_i                                        
188   │     ┌────────────────────┐              
189   │     │FCircuit            │              
190   │     │                    │              
191   └────►│ h =Hash(z_i[0],w_i)│              
192────────►│ │                  ├───────►      
193 z_i     │ └──►z_{i+1}=[h]    │  z_{i+1}
194         │                    │              
195         └────────────────────┘
196```
197
198where each $w_i$ value is set at the `external_inputs` array.
199
200The last state $z_i$ is used together with the external input w_i as inputs to compute the new state $z_{i+1}$.
201
202```rust
203#[derive(Clone, Debug)]
204pub struct ExternalInputsCircuits<F: PrimeField>
205where
206    F: Absorb,
207{
208    _f: PhantomData<F>,
209    poseidon_config: PoseidonConfig<F>,
210}
211impl<F: PrimeField> FCircuit<F> for ExternalInputsCircuits<F>
212where
213    F: Absorb,
214{
215    type Params = (PoseidonConfig<F>);
216
217    fn new(params: Self::Params) -> Self {
218        Self {
219            _f: PhantomData,
220            poseidon_config: params.0,
221        }
222    }
223    fn state_len(&self) -> usize {
224        1
225    }
226    fn external_inputs_len(&self) -> usize {
227        1
228    }
229
230    /// computes the next state value for the step of F for the given z_i and external_inputs
231    /// z_{i+1}
232    fn step_native(&self, i: usize, z_i: Vec<F>, external_inputs: Vec<F>) -> Result<Vec<F>, Error> {
233        let hash_input: [F; 2] = [z_i[0], external_inputs[0]];
234        let h = CRH::<F>::evaluate(&self.poseidon_config, hash_input).unwrap();
235        Ok(vec![h])
236    }
237
238    /// generates the constraints and returns the next state value for the step of F for the given
239    /// z_i and external_inputs
240    fn generate_step_constraints(
241        &self,
242        cs: ConstraintSystemRef<F>,
243        i: usize,
244        z_i: Vec<FpVar<F>>,
245        external_inputs: Vec<FpVar<F>>,
246    ) -> Result<Vec<FpVar<F>>, SynthesisError> {
247        let crh_params =
248            CRHParametersVar::<F>::new_constant(cs.clone(), self.poseidon_config.clone())?;
249        let hash_input: [FpVar<F>; 2] = [z_i[0].clone(), external_inputs[0].clone()];
250        let h = CRHGadget::<F>::evaluate(&crh_params, &hash_input)?;
251        Ok(vec![h])
252    }
253}
254```