chore: add example with private state
dmpierre pdaixmoreux@gmail.com
Fri, 12 Apr 2024 15:26:37 +0200
1 files changed,
75 insertions(+),
12 deletions(-)
jump to
M
src/usage/frontend.md
→
src/usage/frontend.md
@@ -1,6 +1,6 @@
# Frontend -The 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`. +The 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`. # The `FCircuit` trait@@ -14,14 +14,14 @@ /// the step.
pub trait FCircuit<F: PrimeField>: Clone + Debug { type Params: Debug; - /// returns a new FCircuit instance + /// Returns a new FCircuit instance fn new(params: Self::Params) -> Self; - /// returns the number of elements in the state of the FCircuit, which corresponds to the + /// Returns the number of elements in the state of the FCircuit, which corresponds to the /// FCircuit inputs. fn state_len(&self) -> usize; - /// computes the next state values in place, assigning z_{i+1} into z_i, and computing the new + /// Computes the next state values in place, assigning z_{i+1} into z_i, and computing the new /// z_{i+1} fn step_native( // this method uses self, so that each FCircuit implementation (and different frontends)@@ -31,7 +31,7 @@ i: usize,
z_i: Vec<F>, ) -> Result<Vec<F>, Error>; - /// generates the constraints for the step of F for the given z_i + /// Generates the constraints for the step of F for the given z_i fn generate_step_constraints( // this method uses self, so that each FCircuit implementation (and different frontends) // can hold a state if needed to store data to generate the constraints.@@ -45,7 +45,7 @@ ```
# Example -Let'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. +Let'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. ## Folding a simple circuit@@ -90,7 +90,7 @@
Ok(vec![a, b, c, d, e]) // The length of the returned vector should match `state_len` } - /// generates R1CS constraints for the step of F for the given z_i + /// Generates R1CS constraints for the step of F for the given z_i fn generate_step_constraints( &self, cs: ConstraintSystemRef<F>,@@ -110,18 +110,21 @@
Ok(vec![a, b, c, d, e]) // The length of the returned vector should match `state_len` } } - ``` ## Folding a `Sha256` circuit -We 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. +We 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. + +Note 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. ```rust +// Define a struct that will be our circuit. This struct will implement the FCircuit trait. #[derive(Clone, Copy, Debug)] pub struct Sha256FCircuit<F: PrimeField> { _f: PhantomData<F>, } + impl<F: PrimeField> FCircuit<F> for Sha256FCircuit<F> { type Params = ();@@ -132,7 +135,7 @@ fn state_len(&self) -> usize {
1 } - /// computes the next state values in place, assigning z_{i+1} into z_i, and computing the new + /// Computes the next state values in place, assigning z_{i+1} into z_i, and computing the new /// z_{i+1} fn step_native(&self, _i: usize, z_i: Vec<F>) -> Result<Vec<F>, Error> { let out_bytes = Sha256::evaluate(&(), z_i[0].into_bigint().to_bytes_le()).unwrap();@@ -141,7 +144,7 @@
Ok(vec![out[0]]) } - /// generates the constraints for the step of F for the given z_i + /// Generates the constraints for the step of F for the given z_i fn generate_step_constraints( &self, _cs: ConstraintSystemRef<F>,@@ -156,4 +159,64 @@ }
} ``` -## Folding a circuit with public and private inputs+## Folding a circuit with public and private inputs + +Sometimes, 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. + +```rust +#[derive(Clone, Debug)] +pub struct ACircuitWithPrivateState<F: PrimeField> { + _f: PhantomData<F>, + private_state: Vec<F>, // private inputs, here a `Vec` of field elements, but you can specify whatever type you prefer here +} + +impl<F: PrimeField> FCircuit<F> for ACircuitWithPrivateState<F> { + type Params = Vec<F>; + + fn new(params: Self::Params) -> Self { + Self { + _f: PhantomData, + private_state: params, + } + } + + fn state_len(&self) -> usize { + 3 // the length of the state should match the size of the public inputs, not including the private inputs + } + + fn step_native(&self, i: usize, z_i: Vec<F>) -> Result<Vec<F>, folding_schemes::Error> { + let a = z_i[0] + F::from(4_u32); + let b = z_i[1] + F::from(40_u32); + let c = z_i[2] * F::from(4_u32); + let d = self.private_state[0] * a; + let e = self.private_state[1] * c; + + Ok(vec![a, b, c]) + Ok(new_z_i) + } + + fn generate_step_constraints( + &self, + cs: ConstraintSystemRef<F>, + i: usize, + z_i: Vec<ark_r1cs_std::fields::fp::FpVar<F>>, + ) -> Result<Vec<ark_r1cs_std::fields::fp::FpVar<F>>, SynthesisError> { + let four = FpVar::<F>::new_constant(cs.clone(), F::from(4u32))?; + let forty = FpVar::<F>::new_constant(cs.clone(), F::from(40u32))?; + let onehundred = FpVar::<F>::new_constant(cs.clone(), F::from(100u32))?; + + // we still need to allocate our private state as witnesses + let priv_var_0 = FpVar::<F>::new_witness(cs.clone(), || Ok(self.private_state[0].clone()))?; + let priv_var_1 = FpVar::<F>::new_witness(cs.clone(), || Ok(self.private_state[1].clone()))?; + + let a = z_i[0].clone() + four.clone(); + let b = z_i[1].clone() + forty.clone(); + let c = z_i[2].clone() * four; + let d = priv_var_0 * a; + let e = priv_var_1 * c; + + Ok(vec![a, b, c]) + } +} + +```