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
|
||||
>;
|
||||
|
||||
// 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 (pk, vk) =
|
||||
Groth16::<Bn254>::circuit_specific_setup(circuit.clone(), &mut rng).unwrap();
|
||||
// prepare the Decider prover & verifier params for the given nova_params and nova instance. This involves generating the Groth16 and KZG10 setup
|
||||
let (decider_pp, decider_vp) = DECIDER::preprocess(&mut rng, &nova_params, nova.clone()).unwrap();
|
||||
|
||||
// decider proof generation
|
||||
let decider_pp = (poseidon_config.clone(), g16_pk, kzg_pk);
|
||||
let proof = DECIDER::prove(decider_pp, rng, nova.clone()).unwrap();
|
||||
let proof = DECIDER::prove(rng, decider_pp, 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,
|
||||
>;
|
||||
|
||||
let decider_vp = (g16_vk, kzg_vk);
|
||||
let verified = DECIDER::verify(
|
||||
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:
|
||||
|
||||
```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`
|
||||
// trait, and the rest of our code would be working without needing to be updated.
|
||||
type NOVA = Nova<
|
||||
type F = Nova<
|
||||
Projective,
|
||||
GVar,
|
||||
Projective2,
|
||||
@@ -14,6 +14,7 @@ type NOVA = Nova<
|
||||
Sha256FCircuit<Fr>,
|
||||
KZG<'static, Bn254>,
|
||||
Pedersen<Projective2>,
|
||||
false,
|
||||
>;
|
||||
|
||||
let num_steps = 10;
|
||||
@@ -21,25 +22,35 @@ let initial_state = vec![Fr::from(1_u32)];
|
||||
|
||||
let F_circuit = Sha256FCircuit::<Fr>::new(());
|
||||
|
||||
let poseidon_config = poseidon_canonical_config::<Fr>();
|
||||
let mut rng = rand::rngs::OsRng;
|
||||
|
||||
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");
|
||||
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
|
||||
for i in 0..num_steps {
|
||||
let start = Instant::now();
|
||||
// here we pass an empty vec since it does not use external_inputs
|
||||
folding_scheme.prove_step(vec![]).unwrap();
|
||||
// - 2nd parameter: here we pass an empty vec since the FCircuit that we're
|
||||
// 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());
|
||||
}
|
||||
|
||||
let (running_instance, incoming_instance, cyclefold_instance) = folding_scheme.instances();
|
||||
|
||||
println!("Run the Nova's IVC verifier");
|
||||
NOVA::verify(
|
||||
verifier_params,
|
||||
FS::verify(
|
||||
nova_params.1,
|
||||
initial_state,
|
||||
folding_scheme.state(), // latest state
|
||||
Fr::from(num_steps as u32),
|
||||
@@ -49,3 +60,26 @@ NOVA::verify(
|
||||
)
|
||||
.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.
|
||||
|
||||
## 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$.
|
||||
|
||||
$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> {
|
||||
type Params = ();
|
||||
fn new(_params: Self::Params) -> Self {
|
||||
Self { _f: PhantomData }
|
||||
fn new(_params: Self::Params) -> Result<Self, Error> {
|
||||
Ok(Self { _f: PhantomData })
|
||||
}
|
||||
fn state_len(&self) -> usize {
|
||||
1
|
||||
@@ -25,7 +25,12 @@ impl<F: PrimeField> FCircuit<F> for CubicFCircuit<F> {
|
||||
fn external_inputs_len(&self) -> usize {
|
||||
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)])
|
||||
}
|
||||
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
|
||||
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
|
||||
5. adding 100 to the fifth element
|
||||
|
||||
Let's implement this now:
|
||||
|
||||
```rust
|
||||
// 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> {
|
||||
_f: PhantomData<F>,
|
||||
}
|
||||
|
||||
// Implement the FCircuit trait for the struct
|
||||
impl<F: PrimeField> FCircuit<F> for MultiInputsFCircuit<F> {
|
||||
type Params = ();
|
||||
|
||||
fn new(_params: Self::Params) -> Self {
|
||||
Self { _f: PhantomData }
|
||||
fn new(_params: Self::Params) -> Result<Self, Error> {
|
||||
Ok(Self { _f: PhantomData })
|
||||
}
|
||||
|
||||
fn state_len(&self) -> usize {
|
||||
5 // This circuit has 5 inputs
|
||||
5 // since the circuit has 5 inputs
|
||||
}
|
||||
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}
|
||||
// We want the `step_native` method to implement the same logic as the `generate_step_constraints` method
|
||||
fn step_native(&self, _i: usize, z_i: Vec<F>, _external_inputs: Vec<F>) -> Result<Vec<F>, Error> {
|
||||
/// 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 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 = z_i[3] * F::from(40_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(
|
||||
&self,
|
||||
cs: ConstraintSystemRef<F>,
|
||||
@@ -98,7 +104,6 @@ impl<F: PrimeField> FCircuit<F> for MultiInputsFCircuit<F> {
|
||||
z_i: Vec<FpVar<F>>,
|
||||
_external_inputs: Vec<FpVar<F>>,
|
||||
) -> Result<Vec<FpVar<F>>, SynthesisError> {
|
||||
// Implementing the circuit constraints
|
||||
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))?;
|
||||
@@ -108,65 +113,11 @@ impl<F: PrimeField> FCircuit<F> for MultiInputsFCircuit<F> {
|
||||
let d = z_i[3].clone() * forty;
|
||||
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
|
||||
|
||||
@@ -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}$.
|
||||
|
||||
```rust
|
||||
use ark_crypto_primitives::{
|
||||
crh::{
|
||||
poseidon::constraints::{CRHGadget, CRHParametersVar},
|
||||
poseidon::CRH,
|
||||
CRHScheme, CRHSchemeGadget,
|
||||
},
|
||||
sponge::{poseidon::PoseidonConfig, Absorb},
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ExternalInputsCircuits<F: PrimeField>
|
||||
where
|
||||
@@ -208,17 +168,17 @@ where
|
||||
_f: PhantomData<F>,
|
||||
poseidon_config: PoseidonConfig<F>,
|
||||
}
|
||||
impl<F: PrimeField> FCircuit<F> for ExternalInputsCircuits<F>
|
||||
impl<F: PrimeField> FCircuit<F> for ExternalInputsCircuit<F>
|
||||
where
|
||||
F: Absorb,
|
||||
{
|
||||
type Params = (PoseidonConfig<F>);
|
||||
type Params = PoseidonConfig<F>;
|
||||
|
||||
fn new(params: Self::Params) -> Self {
|
||||
Self {
|
||||
fn new(params: Self::Params) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
_f: PhantomData,
|
||||
poseidon_config: params.0,
|
||||
}
|
||||
poseidon_config: params,
|
||||
})
|
||||
}
|
||||
fn state_len(&self) -> usize {
|
||||
1
|
||||
@@ -229,7 +189,12 @@ where
|
||||
|
||||
/// computes the next state value for the step of F for the given z_i and external_inputs
|
||||
/// 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 h = CRH::<F>::evaluate(&self.poseidon_config, hash_input).unwrap();
|
||||
Ok(vec![h])
|
||||
@@ -240,7 +205,7 @@ where
|
||||
fn generate_step_constraints(
|
||||
&self,
|
||||
cs: ConstraintSystemRef<F>,
|
||||
i: usize,
|
||||
_i: usize,
|
||||
z_i: Vec<FpVar<F>>,
|
||||
external_inputs: Vec<FpVar<F>>,
|
||||
) -> 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
|
||||
template FCircuit(ivc_state_len, aux_inputs_len) {
|
||||
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
|
||||
|
||||
// [...]
|
||||
// here it goes the Circom circuit logic
|
||||
}
|
||||
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.
|
||||
|
||||
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
|
||||
pragma circom 2.0.3;
|
||||
|
||||
template CubicCircuit() {
|
||||
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
|
||||
|
||||
@@ -41,47 +49,61 @@ template 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`.
|
||||
|
||||
```rust
|
||||
// we load our circom compiled R1CS, along with the witness wasm calculator
|
||||
let r1cs_path = PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit.r1cs");
|
||||
let wasm_path =
|
||||
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 wasm_path =
|
||||
PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit_js/cubic_circuit.wasm");
|
||||
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| {
|
||||
let z = z_i[0];
|
||||
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
|
||||
let (fs_prover_params, kzg_vk, g16_pk, g16_vk) =
|
||||
init_ivc_and_decider_params::<CircomFCircuit<Fr>>(f_circuit.clone());
|
||||
|
||||
pub type NOVA = Nova<G1, GVar, G2, GVar2, CircomFCircuit<Fr>, KZG<'static, Bn254>, Pedersen<G2>>;
|
||||
pub type DECIDERETH_FCircuit = DeciderEth<
|
||||
G1,
|
||||
GVar,
|
||||
G2,
|
||||
GVar2,
|
||||
CircomFCircuit<Fr>,
|
||||
KZG<'static, Bn254>,
|
||||
Pedersen<G2>,
|
||||
Groth16<Bn254>,
|
||||
NOVA,
|
||||
pub type N =
|
||||
Nova<G1, GVar, G2, GVar2, CircomFCircuit<Fr>, KZG<'static, Bn254>, Pedersen<G2>, false>;
|
||||
pub type D = DeciderEth<
|
||||
G1,
|
||||
GVar,
|
||||
G2,
|
||||
GVar2,
|
||||
CircomFCircuit<Fr>,
|
||||
KZG<'static, Bn254>,
|
||||
Pedersen<G2>,
|
||||
Groth16<Bn254>,
|
||||
N,
|
||||
>;
|
||||
|
||||
// initialize the folding scheme engine, in our case we use Nova
|
||||
let mut nova = NOVA::init(&fs_prover_params, f_circuit.clone(), z_0).unwrap();
|
||||
let poseidon_config = poseidon_canonical_config::<Fr>();
|
||||
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
|
||||
for (i, external_inputs_at_step) in external_inputs.iter().enumerate() {
|
||||
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());
|
||||
}
|
||||
|
||||
```
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
@@ -14,18 +20,18 @@ To be folded with sonobe, a circuit needs to implement the [`FCircuit` trait](ht
|
||||
pub trait FCircuit<F: PrimeField>: Clone + Debug {
|
||||
type Params: Debug;
|
||||
|
||||
/// Returns a new FCircuit instance
|
||||
fn new(params: Self::Params) -> Self;
|
||||
/// returns a new FCircuit instance
|
||||
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.
|
||||
fn state_len(&self) -> usize;
|
||||
|
||||
|
||||
/// returns the number of elements in the external inputs used by the FCircuit. External inputs
|
||||
/// are optional, and in case no external inputs are used, this method should return 0.
|
||||
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}
|
||||
fn step_native(
|
||||
// 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
|
||||
) -> 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.
|
||||
|
||||
@@ -5,3 +5,9 @@ This section showcases how to use the Sonobe library to:
|
||||
- Fold the circuit using one of the folding schemes
|
||||
- Generate a final Decider proof
|
||||
- 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();
|
||||
|
||||
// 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
|
||||
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);
|
||||
|
||||
// save smart contract and the calldata
|
||||
println!("storing nova-verifier.sol and the calldata into files");
|
||||
use std::fs;
|
||||
fs::write( "./examples/nova-verifier.sol", decider_solidity_code.clone()).unwrap();
|
||||
fs::write("./examples/solidity-calldata.calldata", calldata.clone()).unwrap();
|
||||
|
||||
Reference in New Issue
Block a user