mirror of
https://github.com/arnaucube/sonobe-docs.git
synced 2026-02-10 05:06:45 +01:00
update to newer sonobe's interface
This commit is contained in:
@@ -27,21 +27,14 @@ type DECIDER = Decider<
|
|||||||
NOVA, // here we define the FoldingScheme to use
|
NOVA, // here we define the FoldingScheme to use
|
||||||
>;
|
>;
|
||||||
|
|
||||||
// generate Groth16 setup
|
|
||||||
let circuit = DeciderEthCircuit::<
|
|
||||||
Projective,
|
|
||||||
GVar,
|
|
||||||
Projective2,
|
|
||||||
GVar2,
|
|
||||||
Pedersen<Projective>,
|
|
||||||
Pedersen<Projective2>,
|
|
||||||
>::from_nova::<CubicFCircuit<Fr>>(nova.clone());
|
|
||||||
let mut rng = rand::rngs::OsRng;
|
let mut rng = rand::rngs::OsRng;
|
||||||
|
|
||||||
let (pk, vk) =
|
// prepare the Decider prover & verifier params for the given nova_params and nova instance. This involves generating the Groth16 and KZG10 setup
|
||||||
Groth16::<Bn254>::circuit_specific_setup(circuit.clone(), &mut rng).unwrap();
|
let (decider_pp, decider_vp) = DECIDER::preprocess(&mut rng, &nova_params, nova.clone()).unwrap();
|
||||||
|
|
||||||
// decider proof generation
|
// decider proof generation
|
||||||
let decider_pp = (poseidon_config.clone(), g16_pk, kzg_pk);
|
let proof = DECIDER::prove(rng, decider_pp, nova.clone()).unwrap();
|
||||||
let proof = DECIDER::prove(decider_pp, rng, nova.clone()).unwrap();
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
As in the previous sections, you can find a full examples with all the code at [sonobe/examples](https://github.com/privacy-scaling-explorations/sonobe/tree/main/examples).
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ type DECIDER = Decider<
|
|||||||
NOVA,
|
NOVA,
|
||||||
>;
|
>;
|
||||||
|
|
||||||
let decider_vp = (g16_vk, kzg_vk);
|
|
||||||
let verified = DECIDER::verify(
|
let verified = DECIDER::verify(
|
||||||
decider_vp, nova.i, nova.z_0, nova.z_i, &nova.U_i, &nova.u_i, proof,
|
decider_vp, nova.i, nova.z_0, nova.z_i, &nova.U_i, &nova.u_i, proof,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,10 +3,10 @@
|
|||||||
We plug our `FCircuit` into the library:
|
We plug our `FCircuit` into the library:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// The idea here is that eventually we could replace the next line chunk that defines the
|
// The idea here is that we could replace the next line chunk that defines the
|
||||||
// `type NOVA = Nova<...>` by using another folding scheme that fulfills the `FoldingScheme`
|
// `type NOVA = Nova<...>` by using another folding scheme that fulfills the `FoldingScheme`
|
||||||
// trait, and the rest of our code would be working without needing to be updated.
|
// trait, and the rest of our code would be working without needing to be updated.
|
||||||
type NOVA = Nova<
|
type F = Nova<
|
||||||
Projective,
|
Projective,
|
||||||
GVar,
|
GVar,
|
||||||
Projective2,
|
Projective2,
|
||||||
@@ -14,6 +14,7 @@ type NOVA = Nova<
|
|||||||
Sha256FCircuit<Fr>,
|
Sha256FCircuit<Fr>,
|
||||||
KZG<'static, Bn254>,
|
KZG<'static, Bn254>,
|
||||||
Pedersen<Projective2>,
|
Pedersen<Projective2>,
|
||||||
|
false,
|
||||||
>;
|
>;
|
||||||
|
|
||||||
let num_steps = 10;
|
let num_steps = 10;
|
||||||
@@ -21,25 +22,35 @@ let initial_state = vec![Fr::from(1_u32)];
|
|||||||
|
|
||||||
let F_circuit = Sha256FCircuit::<Fr>::new(());
|
let F_circuit = Sha256FCircuit::<Fr>::new(());
|
||||||
|
|
||||||
|
let poseidon_config = poseidon_canonical_config::<Fr>();
|
||||||
|
let mut rng = rand::rngs::OsRng;
|
||||||
|
|
||||||
println!("Prepare Nova ProverParams & VerifierParams");
|
println!("Prepare Nova ProverParams & VerifierParams");
|
||||||
let (prover_params, verifier_params) = nova_setup::<Sha256FCircuit<Fr>>(F_circuit);
|
let nova_preprocess_params = PreprocessorParam::new(poseidon_config, F_circuit);
|
||||||
|
let nova_params = FS::preprocess(&mut rng, &nova_preprocess_params).unwrap();
|
||||||
|
|
||||||
|
|
||||||
println!("Initialize FoldingScheme");
|
println!("Initialize FoldingScheme");
|
||||||
let mut folding_scheme = NOVA::init(&prover_params, F_circuit, initial_state.clone()).unwrap();
|
let mut folding_scheme = FS::init(&nova_params, F_circuit, initial_state.clone()).unwrap();
|
||||||
|
|
||||||
|
|
||||||
// compute a step of the IVC
|
// compute a step of the IVC
|
||||||
for i in 0..num_steps {
|
for i in 0..num_steps {
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
// here we pass an empty vec since it does not use external_inputs
|
// - 2nd parameter: here we pass an empty vec since the FCircuit that we're
|
||||||
folding_scheme.prove_step(vec![]).unwrap();
|
// using does not use external_inputs
|
||||||
|
// - 3rd parameter: is for schemes that support folding more than 2
|
||||||
|
// instances at each fold, such as HyperNova. Since we're using Nova, we just
|
||||||
|
// set it to 'None'
|
||||||
|
folding_scheme.prove_step(rng, vec![], None).unwrap();
|
||||||
println!("Nova::prove_step {}: {:?}", i, start.elapsed());
|
println!("Nova::prove_step {}: {:?}", i, start.elapsed());
|
||||||
}
|
}
|
||||||
|
|
||||||
let (running_instance, incoming_instance, cyclefold_instance) = folding_scheme.instances();
|
let (running_instance, incoming_instance, cyclefold_instance) = folding_scheme.instances();
|
||||||
|
|
||||||
println!("Run the Nova's IVC verifier");
|
println!("Run the Nova's IVC verifier");
|
||||||
NOVA::verify(
|
FS::verify(
|
||||||
verifier_params,
|
nova_params.1,
|
||||||
initial_state,
|
initial_state,
|
||||||
folding_scheme.state(), // latest state
|
folding_scheme.state(), // latest state
|
||||||
Fr::from(num_steps as u32),
|
Fr::from(num_steps as u32),
|
||||||
@@ -49,3 +60,26 @@ NOVA::verify(
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
```
|
```
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
Now imagine that we want to switch the folding scheme being used. Is as simple as replacing `FS` by:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
type FS = HyperNova<
|
||||||
|
Projective,
|
||||||
|
GVar,
|
||||||
|
Projective2,
|
||||||
|
GVar2,
|
||||||
|
CubicFCircuit<Fr>,
|
||||||
|
KZG<'static, Bn254>,
|
||||||
|
Pedersen<Projective2>,
|
||||||
|
1, 1, false,
|
||||||
|
>;
|
||||||
|
```
|
||||||
|
and then adapting the `folding_scheme.prove_step(...)` call accordingly.
|
||||||
|
|
||||||
|
|
||||||
|
<br><br>
|
||||||
|
|
||||||
|
As in the previous sections, you can find a full examples with all the code at [sonobe/examples](https://github.com/privacy-scaling-explorations/sonobe/tree/main/examples).
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ Let's walk through different simple examples implementing the `FCircuit` trait.
|
|||||||
|
|
||||||
You 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.
|
You 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.
|
||||||
|
|
||||||
## Cubic circuit
|
## Cubic circuit example
|
||||||
This 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$.
|
This 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$.
|
||||||
|
|
||||||
$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.
|
$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.
|
||||||
@@ -16,8 +16,8 @@ pub struct CubicFCircuit<F: PrimeField> {
|
|||||||
}
|
}
|
||||||
impl<F: PrimeField> FCircuit<F> for CubicFCircuit<F> {
|
impl<F: PrimeField> FCircuit<F> for CubicFCircuit<F> {
|
||||||
type Params = ();
|
type Params = ();
|
||||||
fn new(_params: Self::Params) -> Self {
|
fn new(_params: Self::Params) -> Result<Self, Error> {
|
||||||
Self { _f: PhantomData }
|
Ok(Self { _f: PhantomData })
|
||||||
}
|
}
|
||||||
fn state_len(&self) -> usize {
|
fn state_len(&self) -> usize {
|
||||||
1
|
1
|
||||||
@@ -25,7 +25,12 @@ impl<F: PrimeField> FCircuit<F> for CubicFCircuit<F> {
|
|||||||
fn external_inputs_len(&self) -> usize {
|
fn external_inputs_len(&self) -> usize {
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
fn step_native(&self, _i: usize, z_i: Vec<F>, _external_inputs: Vec<F>) -> Result<Vec<F>, Error> {
|
fn step_native(
|
||||||
|
&self,
|
||||||
|
_i: usize,
|
||||||
|
z_i: Vec<F>,
|
||||||
|
_external_inputs: Vec<F>,
|
||||||
|
) -> Result<Vec<F>, Error> {
|
||||||
Ok(vec![z_i[0] * z_i[0] * z_i[0] + z_i[0] + F::from(5_u32)])
|
Ok(vec![z_i[0] * z_i[0] * z_i[0] + z_i[0] + F::from(5_u32)])
|
||||||
}
|
}
|
||||||
fn generate_step_constraints(
|
fn generate_step_constraints(
|
||||||
@@ -44,9 +49,9 @@ impl<F: PrimeField> FCircuit<F> for CubicFCircuit<F> {
|
|||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## Folding a simple circuit
|
## Multiple inputs circuit example
|
||||||
|
|
||||||
The 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:
|
The following example has a state of 5 public elements. At each step, we will want the circuit to compute the next state by:
|
||||||
|
|
||||||
1. adding 4 to the first element
|
1. adding 4 to the first element
|
||||||
2. adding 40 to the second element
|
2. adding 40 to the second element
|
||||||
@@ -54,7 +59,6 @@ The circuit we will fold has a state of 5 public elements. At each step, we will
|
|||||||
4. multiplying the fourth element by 40
|
4. multiplying the fourth element by 40
|
||||||
5. adding 100 to the fifth element
|
5. adding 100 to the fifth element
|
||||||
|
|
||||||
Let's implement this now:
|
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// Define a struct that will be our circuit. This struct will implement the FCircuit trait.
|
// Define a struct that will be our circuit. This struct will implement the FCircuit trait.
|
||||||
@@ -62,35 +66,37 @@ Let's implement this now:
|
|||||||
pub struct MultiInputsFCircuit<F: PrimeField> {
|
pub struct MultiInputsFCircuit<F: PrimeField> {
|
||||||
_f: PhantomData<F>,
|
_f: PhantomData<F>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implement the FCircuit trait for the struct
|
|
||||||
impl<F: PrimeField> FCircuit<F> for MultiInputsFCircuit<F> {
|
impl<F: PrimeField> FCircuit<F> for MultiInputsFCircuit<F> {
|
||||||
type Params = ();
|
type Params = ();
|
||||||
|
|
||||||
fn new(_params: Self::Params) -> Self {
|
fn new(_params: Self::Params) -> Result<Self, Error> {
|
||||||
Self { _f: PhantomData }
|
Ok(Self { _f: PhantomData })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn state_len(&self) -> usize {
|
fn state_len(&self) -> usize {
|
||||||
5 // This circuit has 5 inputs
|
5 // since the circuit has 5 inputs
|
||||||
}
|
}
|
||||||
fn external_inputs_len(&self) -> usize {
|
fn external_inputs_len(&self) -> usize {
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Computes the next state values in place, assigning z_{i+1} into z_i, and computing the new z_{i+1}
|
/// computes the next state values in place, assigning z_{i+1} into z_i, and computing the new
|
||||||
// We want the `step_native` method to implement the same logic as the `generate_step_constraints` method
|
/// z_{i+1}
|
||||||
fn step_native(&self, _i: usize, z_i: Vec<F>, _external_inputs: Vec<F>) -> Result<Vec<F>, Error> {
|
fn step_native(
|
||||||
|
&self,
|
||||||
|
_i: usize,
|
||||||
|
z_i: Vec<F>,
|
||||||
|
_external_inputs: Vec<F>,
|
||||||
|
) -> Result<Vec<F>, Error> {
|
||||||
let a = z_i[0] + F::from(4_u32);
|
let a = z_i[0] + F::from(4_u32);
|
||||||
let b = z_i[1] + F::from(40_u32);
|
let b = z_i[1] + F::from(40_u32);
|
||||||
let c = z_i[2] * F::from(4_u32);
|
let c = z_i[2] * F::from(4_u32);
|
||||||
let d = z_i[3] * F::from(40_u32);
|
let d = z_i[3] * F::from(40_u32);
|
||||||
let e = z_i[4] + F::from(100_u32);
|
let e = z_i[4] + F::from(100_u32);
|
||||||
|
|
||||||
Ok(vec![a, b, c, d, e]) // The length of the returned vector should match `state_len`
|
Ok(vec![a, b, c, d, e])
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generates R1CS 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(
|
fn generate_step_constraints(
|
||||||
&self,
|
&self,
|
||||||
cs: ConstraintSystemRef<F>,
|
cs: ConstraintSystemRef<F>,
|
||||||
@@ -98,7 +104,6 @@ impl<F: PrimeField> FCircuit<F> for MultiInputsFCircuit<F> {
|
|||||||
z_i: Vec<FpVar<F>>,
|
z_i: Vec<FpVar<F>>,
|
||||||
_external_inputs: Vec<FpVar<F>>,
|
_external_inputs: Vec<FpVar<F>>,
|
||||||
) -> Result<Vec<FpVar<F>>, SynthesisError> {
|
) -> Result<Vec<FpVar<F>>, SynthesisError> {
|
||||||
// Implementing the circuit constraints
|
|
||||||
let four = FpVar::<F>::new_constant(cs.clone(), F::from(4u32))?;
|
let four = FpVar::<F>::new_constant(cs.clone(), F::from(4u32))?;
|
||||||
let forty = FpVar::<F>::new_constant(cs.clone(), F::from(40u32))?;
|
let forty = FpVar::<F>::new_constant(cs.clone(), F::from(40u32))?;
|
||||||
let onehundred = FpVar::<F>::new_constant(cs.clone(), F::from(100u32))?;
|
let onehundred = FpVar::<F>::new_constant(cs.clone(), F::from(100u32))?;
|
||||||
@@ -108,65 +113,11 @@ impl<F: PrimeField> FCircuit<F> for MultiInputsFCircuit<F> {
|
|||||||
let d = z_i[3].clone() * forty;
|
let d = z_i[3].clone() * forty;
|
||||||
let e = z_i[4].clone() + onehundred;
|
let e = z_i[4].clone() + onehundred;
|
||||||
|
|
||||||
Ok(vec![a, b, c, d, e]) // The length of the returned vector should match `state_len`
|
Ok(vec![a, b, c, d, e])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
Note: to simplify things for the example, only the first byte outputted by the sha256 is used for the next state $z_{i+1}$.
|
|
||||||
|
|
||||||
```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 = ();
|
|
||||||
|
|
||||||
fn new(_params: Self::Params) -> Self {
|
|
||||||
Self { _f: PhantomData }
|
|
||||||
}
|
|
||||||
fn state_len(&self) -> usize {
|
|
||||||
1
|
|
||||||
}
|
|
||||||
fn external_inputs_len(&self) -> usize {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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>, _external_inputs: Vec<F>) -> Result<Vec<F>, Error> {
|
|
||||||
let out_bytes = Sha256::evaluate(&(), z_i[0].into_bigint().to_bytes_le()).unwrap();
|
|
||||||
let out: Vec<F> = out_bytes.to_field_elements().unwrap();
|
|
||||||
|
|
||||||
Ok(vec![out[0]])
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generates the constraints for the step of F for the given z_i
|
|
||||||
fn generate_step_constraints(
|
|
||||||
&self,
|
|
||||||
_cs: ConstraintSystemRef<F>,
|
|
||||||
_i: usize,
|
|
||||||
z_i: Vec<FpVar<F>>,
|
|
||||||
_external_inputs: Vec<FpVar<F>>,
|
|
||||||
) -> Result<Vec<FpVar<F>>, SynthesisError> {
|
|
||||||
let unit_var = UnitVar::default();
|
|
||||||
let out_bytes = Sha256Gadget::evaluate(&unit_var, &z_i[0].to_bytes()?)?;
|
|
||||||
let out = out_bytes.0.to_constraint_field()?;
|
|
||||||
Ok(vec![out[0].clone()])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Using external inputs
|
## Using external inputs
|
||||||
|
|
||||||
@@ -200,6 +151,15 @@ where each $w_i$ value is set at the `external_inputs` array.
|
|||||||
The last state $z_i$ is used together with the external input w_i as inputs to compute the new state $z_{i+1}$.
|
The last state $z_i$ is used together with the external input w_i as inputs to compute the new state $z_{i+1}$.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
use ark_crypto_primitives::{
|
||||||
|
crh::{
|
||||||
|
poseidon::constraints::{CRHGadget, CRHParametersVar},
|
||||||
|
poseidon::CRH,
|
||||||
|
CRHScheme, CRHSchemeGadget,
|
||||||
|
},
|
||||||
|
sponge::{poseidon::PoseidonConfig, Absorb},
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct ExternalInputsCircuits<F: PrimeField>
|
pub struct ExternalInputsCircuits<F: PrimeField>
|
||||||
where
|
where
|
||||||
@@ -208,17 +168,17 @@ where
|
|||||||
_f: PhantomData<F>,
|
_f: PhantomData<F>,
|
||||||
poseidon_config: PoseidonConfig<F>,
|
poseidon_config: PoseidonConfig<F>,
|
||||||
}
|
}
|
||||||
impl<F: PrimeField> FCircuit<F> for ExternalInputsCircuits<F>
|
impl<F: PrimeField> FCircuit<F> for ExternalInputsCircuit<F>
|
||||||
where
|
where
|
||||||
F: Absorb,
|
F: Absorb,
|
||||||
{
|
{
|
||||||
type Params = (PoseidonConfig<F>);
|
type Params = PoseidonConfig<F>;
|
||||||
|
|
||||||
fn new(params: Self::Params) -> Self {
|
fn new(params: Self::Params) -> Result<Self, Error> {
|
||||||
Self {
|
Ok(Self {
|
||||||
_f: PhantomData,
|
_f: PhantomData,
|
||||||
poseidon_config: params.0,
|
poseidon_config: params,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
fn state_len(&self) -> usize {
|
fn state_len(&self) -> usize {
|
||||||
1
|
1
|
||||||
@@ -229,7 +189,12 @@ where
|
|||||||
|
|
||||||
/// computes the next state value for the step of F for the given z_i and external_inputs
|
/// computes the next state value for the step of F for the given z_i and external_inputs
|
||||||
/// z_{i+1}
|
/// z_{i+1}
|
||||||
fn step_native(&self, i: usize, z_i: Vec<F>, external_inputs: Vec<F>) -> Result<Vec<F>, Error> {
|
fn step_native(
|
||||||
|
&self,
|
||||||
|
_i: usize,
|
||||||
|
z_i: Vec<F>,
|
||||||
|
external_inputs: Vec<F>,
|
||||||
|
) -> Result<Vec<F>, Error> {
|
||||||
let hash_input: [F; 2] = [z_i[0], external_inputs[0]];
|
let hash_input: [F; 2] = [z_i[0], external_inputs[0]];
|
||||||
let h = CRH::<F>::evaluate(&self.poseidon_config, hash_input).unwrap();
|
let h = CRH::<F>::evaluate(&self.poseidon_config, hash_input).unwrap();
|
||||||
Ok(vec![h])
|
Ok(vec![h])
|
||||||
@@ -240,7 +205,7 @@ where
|
|||||||
fn generate_step_constraints(
|
fn generate_step_constraints(
|
||||||
&self,
|
&self,
|
||||||
cs: ConstraintSystemRef<F>,
|
cs: ConstraintSystemRef<F>,
|
||||||
i: usize,
|
_i: usize,
|
||||||
z_i: Vec<FpVar<F>>,
|
z_i: Vec<FpVar<F>>,
|
||||||
external_inputs: Vec<FpVar<F>>,
|
external_inputs: Vec<FpVar<F>>,
|
||||||
) -> Result<Vec<FpVar<F>>, SynthesisError> {
|
) -> Result<Vec<FpVar<F>>, SynthesisError> {
|
||||||
@@ -252,3 +217,8 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
<br><br>
|
||||||
|
|
||||||
|
# Complete examples
|
||||||
|
You can find the complete examples with all the imports at [sonobe/examples](https://github.com/privacy-scaling-explorations/sonobe/tree/main/examples).
|
||||||
|
|||||||
@@ -8,25 +8,33 @@ We can define the circuit to be folded in Circom. The only interface that we nee
|
|||||||
```c
|
```c
|
||||||
template FCircuit(ivc_state_len, aux_inputs_len) {
|
template FCircuit(ivc_state_len, aux_inputs_len) {
|
||||||
signal input ivc_input[ivc_state_len]; // IVC state
|
signal input ivc_input[ivc_state_len]; // IVC state
|
||||||
signal input external_inputs[aux_inputs_len]; // not state,
|
signal input external_inputs[aux_inputs_len]; // external inputs, not part of the folding state
|
||||||
|
|
||||||
signal output ivc_output[ivc_state_len]; // next IVC state
|
signal output ivc_output[ivc_state_len]; // next IVC state
|
||||||
|
|
||||||
// [...]
|
// here it goes the Circom circuit logic
|
||||||
}
|
}
|
||||||
component main {public [ivc_input]} = Example();
|
component main {public [ivc_input]} = Example();
|
||||||
```
|
```
|
||||||
|
|
||||||
The `ivc_input` is the array that defines the initial state, and the `ivc_output` is the array that defines the output state after the step. Both need to be of the same size. The `external_inputs` array expects auxiliary input values.
|
The `ivc_input` is the array that defines the initial state, and the `ivc_output` is the array that defines the output state after the step. Both need to be of the same size. The `external_inputs` array expects auxiliary input values.
|
||||||
|
|
||||||
So for example, the following circuit does the traditional example at each step, which proves knowledge of $x$ such that $y==x^3 + x + e_0 + e_1$ for a known $y$ ($e_i$ are the `external_inputs[i]`):
|
|
||||||
|
In the following image, the `ivc_input`=$z_i$, the `external_inputs`=$w_i$, and the `ivc_output`=$z_{i+1}$, and $F$ is the logic of our Circom circuit:
|
||||||
|
<p align="center">
|
||||||
|
<img src="../imgs/folding-main-idea-diagram.png" style="width:70%;" />
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
So for example, the following circuit proves (at each folding step) knowledge of $x$ such that $y==x^3 + x + e_0 + e_1$ for a known $y$ ($e_i$ are the `external_inputs[i]`):
|
||||||
|
|
||||||
```c
|
```c
|
||||||
pragma circom 2.0.3;
|
pragma circom 2.0.3;
|
||||||
|
|
||||||
template CubicCircuit() {
|
template CubicCircuit() {
|
||||||
signal input ivc_input[1]; // IVC state
|
signal input ivc_input[1]; // IVC state
|
||||||
signal input external_inputs[2]; // not state
|
signal input external_inputs[2]; // not part of the state
|
||||||
|
|
||||||
signal output ivc_output[1]; // next IVC state
|
signal output ivc_output[1]; // next IVC state
|
||||||
|
|
||||||
@@ -41,6 +49,8 @@ template CubicCircuit() {
|
|||||||
component main {public [ivc_input]} = CubicCircuit();
|
component main {public [ivc_input]} = CubicCircuit();
|
||||||
```
|
```
|
||||||
|
|
||||||
|
<br><br>
|
||||||
|
|
||||||
Once your `circom` circuit is ready, you can instantiate it with Sonobe. To do this, you will need the `struct CircomFCircuit`.
|
Once your `circom` circuit is ready, you can instantiate it with Sonobe. To do this, you will need the `struct CircomFCircuit`.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
@@ -48,20 +58,21 @@ Once your `circom` circuit is ready, you can instantiate it with Sonobe. To do t
|
|||||||
let r1cs_path = PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit.r1cs");
|
let r1cs_path = PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit.r1cs");
|
||||||
let wasm_path =
|
let wasm_path =
|
||||||
PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit_js/cubic_circuit.wasm");
|
PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit_js/cubic_circuit.wasm");
|
||||||
let mut circom_fcircuit = CircomFCircuit::<Fr>::new((r1cs_path, wasm_path, 1, 2)).unwrap(); // state_len:1, external_inputs_len:2
|
let f_circuit_params = (r1cs_path, wasm_path, 1, 2); // state_len:1, external_inputs_len:2
|
||||||
|
let f_circuit = CircomFCircuit::<Fr>::new(f_circuit_params).unwrap();
|
||||||
|
|
||||||
// to speed things up, you can define a custom step function to avoid defaulting to the snarkjs witness calculator
|
|
||||||
|
// [optional] to speed things up, you can define a custom step function to avoid
|
||||||
|
// defaulting to the snarkjs witness calculator for the native computations,
|
||||||
|
// which would be slower than rust native operations
|
||||||
circom_fcircuit.set_custom_step_native(Rc::new(|_i, z_i, _external| {
|
circom_fcircuit.set_custom_step_native(Rc::new(|_i, z_i, _external| {
|
||||||
let z = z_i[0];
|
let z = z_i[0];
|
||||||
Ok(vec![z * z * z + z + Fr::from(5)])
|
Ok(vec![z * z * z + z + Fr::from(5)])
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// we initialize the required folding schemes parameters, the folding scheme and the final decider that we want to use
|
pub type N =
|
||||||
let (fs_prover_params, kzg_vk, g16_pk, g16_vk) =
|
Nova<G1, GVar, G2, GVar2, CircomFCircuit<Fr>, KZG<'static, Bn254>, Pedersen<G2>, false>;
|
||||||
init_ivc_and_decider_params::<CircomFCircuit<Fr>>(f_circuit.clone());
|
pub type D = DeciderEth<
|
||||||
|
|
||||||
pub type NOVA = Nova<G1, GVar, G2, GVar2, CircomFCircuit<Fr>, KZG<'static, Bn254>, Pedersen<G2>>;
|
|
||||||
pub type DECIDERETH_FCircuit = DeciderEth<
|
|
||||||
G1,
|
G1,
|
||||||
GVar,
|
GVar,
|
||||||
G2,
|
G2,
|
||||||
@@ -70,18 +81,29 @@ pub type DECIDERETH_FCircuit = DeciderEth<
|
|||||||
KZG<'static, Bn254>,
|
KZG<'static, Bn254>,
|
||||||
Pedersen<G2>,
|
Pedersen<G2>,
|
||||||
Groth16<Bn254>,
|
Groth16<Bn254>,
|
||||||
NOVA,
|
N,
|
||||||
>;
|
>;
|
||||||
|
|
||||||
// initialize the folding scheme engine, in our case we use Nova
|
let poseidon_config = poseidon_canonical_config::<Fr>();
|
||||||
let mut nova = NOVA::init(&fs_prover_params, f_circuit.clone(), z_0).unwrap();
|
let mut rng = rand::rngs::OsRng;
|
||||||
|
|
||||||
|
// prepare the Nova prover & verifier params
|
||||||
|
let nova_preprocess_params = PreprocessorParam::new(poseidon_config, f_circuit.clone());
|
||||||
|
let nova_params = N::preprocess(&mut rng, &nova_preprocess_params).unwrap();
|
||||||
|
|
||||||
|
// initialize the folding scheme engine, in this case we use Nova
|
||||||
|
let mut nova = N::init(&nova_params, f_circuit.clone(), z_0).unwrap();
|
||||||
|
|
||||||
// run n steps of the folding iteration
|
// run n steps of the folding iteration
|
||||||
for (i, external_inputs_at_step) in external_inputs.iter().enumerate() {
|
for (i, external_inputs_at_step) in external_inputs.iter().enumerate() {
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
nova.prove_step(external_inputs_at_step.clone()).unwrap();
|
// the last parameter at 'nova.prove_step()' is for schemes that support
|
||||||
|
// folding more than 2 instances at each fold, such as HyperNova. Since
|
||||||
|
// we're using Nova, we just set it to 'None'
|
||||||
|
nova.prove_step(rng, external_inputs_at_step.clone(), None)
|
||||||
|
.unwrap();
|
||||||
println!("Nova::prove_step {}: {:?}", i, start.elapsed());
|
println!("Nova::prove_step {}: {:?}", i, start.elapsed());
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
You can find an example for using Nova over a circom circuit [here](https://github.com/privacy-scaling-explorations/sonobe/blob/main/examples/circom_full_flow.rs).
|
You can find a full example using Nova to fold a Circom circuit at [sonobe/examples/circom_full_flow.rs](https://github.com/privacy-scaling-explorations/sonobe/blob/main/examples/circom_full_flow.rs).
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
# Frontend
|
# Frontend
|
||||||
|
|
||||||
The 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`.
|
The frontend interface allows to define the circuit to be folded. The currently available frontends are:
|
||||||
|
- [arkworks](https://github.com/arkworks-rs/r1cs-std)
|
||||||
|
- [Circom](https://github.com/iden3/circom)
|
||||||
|
- [Noir](https://noir-lang.org/)
|
||||||
|
- [Noname](https://github.com/zksecurity/noname)
|
||||||
|
|
||||||
|
Defining a circuit to be folded is as simple as fulfilling the `FCircuit` trait interface. Henceforth, integrating a new zk circuits language into Sonobe, can be done by building a wrapper on top of it that satisfies the `FCircuit` trait.
|
||||||
|
|
||||||
# The `FCircuit` trait
|
# The `FCircuit` trait
|
||||||
|
|
||||||
@@ -14,10 +20,10 @@ To be folded with sonobe, a circuit needs to implement the [`FCircuit` trait](ht
|
|||||||
pub trait FCircuit<F: PrimeField>: Clone + Debug {
|
pub trait FCircuit<F: PrimeField>: Clone + Debug {
|
||||||
type Params: Debug;
|
type Params: Debug;
|
||||||
|
|
||||||
/// Returns a new FCircuit instance
|
/// returns a new FCircuit instance
|
||||||
fn new(params: Self::Params) -> Self;
|
fn new(params: Self::Params) -> Result<Self, Error>;
|
||||||
|
|
||||||
/// 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.
|
/// FCircuit inputs.
|
||||||
fn state_len(&self) -> usize;
|
fn state_len(&self) -> usize;
|
||||||
|
|
||||||
@@ -25,7 +31,7 @@ pub trait FCircuit<F: PrimeField>: Clone + Debug {
|
|||||||
/// are optional, and in case no external inputs are used, this method should return 0.
|
/// are optional, and in case no external inputs are used, this method should return 0.
|
||||||
fn external_inputs_len(&self) -> usize;
|
fn external_inputs_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}
|
/// z_{i+1}
|
||||||
fn step_native(
|
fn step_native(
|
||||||
// this method uses self, so that each FCircuit implementation (and different frontends)
|
// this method uses self, so that each FCircuit implementation (and different frontends)
|
||||||
@@ -36,7 +42,7 @@ pub trait FCircuit<F: PrimeField>: Clone + Debug {
|
|||||||
external_inputs: Vec<F>, // inputs that are not part of the state
|
external_inputs: Vec<F>, // inputs that are not part of the state
|
||||||
) -> Result<Vec<F>, Error>;
|
) -> 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(
|
fn generate_step_constraints(
|
||||||
// this method uses self, so that each FCircuit implementation (and different frontends)
|
// 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.
|
// can hold a state if needed to store data to generate the constraints.
|
||||||
|
|||||||
@@ -5,3 +5,9 @@ This section showcases how to use the Sonobe library to:
|
|||||||
- Fold the circuit using one of the folding schemes
|
- Fold the circuit using one of the folding schemes
|
||||||
- Generate a final Decider proof
|
- Generate a final Decider proof
|
||||||
- Verify the Decider proof, and in Ethereum case, generate a Solidity verifier
|
- Verify the Decider proof, and in Ethereum case, generate a Solidity verifier
|
||||||
|
|
||||||
|
<br><br>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img src="../imgs/sonobe-lib-pipeline.png" />
|
||||||
|
</p>
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ let calldata: Vec<u8> = prepare_calldata(
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// prepare the setup params for the solidity verifier
|
// prepare the setup params for the solidity verifier
|
||||||
let nova_cyclefold_vk = NovaCycleFoldVerifierKey::from((g16_vk, kzg_vk, f_circuit.state_len()));
|
let nova_cyclefold_vk = NovaCycleFoldVerifierKey::from((decider_vp, f_circuit.state_len()));
|
||||||
|
|
||||||
// generate the solidity code
|
// generate the solidity code
|
||||||
let decider_solidity_code = get_decider_template_for_cyclefold_decider(nova_cyclefold_vk);
|
let decider_solidity_code = get_decider_template_for_cyclefold_decider(nova_cyclefold_vk);
|
||||||
@@ -43,6 +43,7 @@ let (_, output) = evm.call(verifier_address, calldata.clone());
|
|||||||
assert_eq!(*output.last().unwrap(), 1);
|
assert_eq!(*output.last().unwrap(), 1);
|
||||||
|
|
||||||
// save smart contract and the calldata
|
// save smart contract and the calldata
|
||||||
|
println!("storing nova-verifier.sol and the calldata into files");
|
||||||
use std::fs;
|
use std::fs;
|
||||||
fs::write( "./examples/nova-verifier.sol", decider_solidity_code.clone()).unwrap();
|
fs::write( "./examples/nova-verifier.sol", decider_solidity_code.clone()).unwrap();
|
||||||
fs::write("./examples/solidity-calldata.calldata", calldata.clone()).unwrap();
|
fs::write("./examples/solidity-calldata.calldata", calldata.clone()).unwrap();
|
||||||
|
|||||||
Reference in New Issue
Block a user