Browse Source

document, add circuit diagram

main
arnaucube 3 months ago
parent
commit
2477471560
11 changed files with 156 additions and 116 deletions
  1. +11
    -0
      .github/workflows/ci.yml
  2. +2
    -0
      .github/workflows/typos.toml
  3. +0
    -2
      Cargo.toml
  4. +34
    -5
      README.md
  5. BIN
      img/ethdos-fcircuit.png
  6. +2
    -7
      index.html
  7. +2
    -0
      src/fcircuit.rs
  8. +0
    -84
      src/fold_ethdos.rs
  9. +90
    -5
      src/lib.rs
  10. +2
    -0
      src/signature.rs
  11. +13
    -13
      src/utils.rs

+ 11
- 0
.github/workflows/ci.yml

@ -18,3 +18,14 @@ jobs:
- uses: actions/checkout@v4
- name: Clippy
run: cargo clippy --all-targets --all-features
typos:
if: github.event.pull_request.draft == false
name: Spell Check with Typos
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Use typos with config file
uses: crate-ci/typos@master
with:
config: .github/workflows/typos.toml

+ 2
- 0
.github/workflows/typos.toml

@ -0,0 +1,2 @@
[default.extend-words]
groth = "groth"

+ 0
- 2
Cargo.toml

@ -26,9 +26,7 @@ rand_core = {version = "0.6", default-features = false}
base64 = "0.22.1"
folding-schemes = { git = "https://github.com/privacy-scaling-explorations/sonobe", package = "folding-schemes", features=["light-test"], rev="c6f1a246e0705582a75de6becf4ad21f325fa5a1"}
# folding-schemes = { path = "../sonobe/sonobe_FCIRCUIT-EXTERNALINPUTS-TO-VEC/folding-schemes", package = "folding-schemes", features=["light-test"]}
arkeddsa = { git = "https://github.com/arnaucube/arkeddsa", features=["r1cs"], rev="0a9ea7ac1df07363af0fda723e313e775563b9f4"}
# arkeddsa = { path = "../arkeddsa_TE-to-C", features=["r1cs"]}
blake2 = "0.10"

+ 34
- 5
README.md

@ -24,21 +24,50 @@ So for example, in the previous diagram:
- $pk_3$ is 3 degrees of distance from $pk_0$
- $pk_3$ has signed $pk_2$, who has signed $pk_1$, who has signed $pk_0$
- $pk_B$ is 4 degrees of distance from $pk_0$
- $pk_B$ has signed $pk_A$, who has signed $pk_2$, who has signed $pk_2$, who has signed $pk_1$
- $pk_B$ has signed $pk_A$, who has signed $pk_2$, who has signed $pk_1$, who has signed $pk_0$
- $pk_\beta$ is 3 degrees of distance from $pk_0$
- $pk_\beta$ has signed $pk_\alpha$, who has signed $pk_1$, who has signed $pk_0$
With folding schemes, we can map those relations into an IVC model, where at each recursive step we're proving the [`FCircuit` relation](https://github.com/arnaucube/ethdos-fold/blob/main/src/fcircuit.rs) (key part: the method `EthDosCircuit.generate_step_constraints`).
With folding schemes, we can map those relations into an IVC model, where at each recursive step we're proving the [`FCircuit` relation](https://github.com/arnaucube/ethdos-fold/blob/main/src/fcircuit.rs) (key part: the method `EthDosCircuit.generate_step_constraints`), which ensures that the new state ($s_{i+1}$) comes from the previous state ($s_i$) with the verification of the new signature of the $pk_{i+1}$ over the previous public key $pk_i$.
The *state* of the IVC is $s_i = [pk_0, pk_i, i]$, where $pk_i$ is the public key $i$ degrees of distance from $pk_0$. At each step $i$ we have the IVC proof $\pi_i$, which proves this relation.
![](img/ethdos-fcircuit.png)
The *state* of the IVC is $s_{i+1} = [pk_0, pk_{i+1}, i+1]$, where $pk_i$ is the public key $i$ degrees of distance from $pk_0$, and $pk_{i+1}$ is $i+1$ degrees of distance from $pk_0$. At each step $i$ we have the IVC proof $\pi_i$, which proves this relation.
The following diagram shows the relation between the states and signatures at each folding step, showing also divergent paths.
![](img/ethdos-states-diagram.png)
Each new folding step, only needs to have the previous step's state ($s_i = [pk_0, pk_i, i]$) and the respective IVC proof ($\pi_i$), which proves that the given public key $pk_i$ is $i$ degrees of distance from the public key $pk_0$.
A new recursive step is done from the $\pi_i$ and the $s_i$, and by inputing the new signature $sig_{pk_{i+1}}(pk_i)$, which is at degree of distance $i+1$ from $pk_0$.
A new recursive step is done from the $\pi_i$ and the $s_i$, and by inputting the new signature $sig_{pk_{i+1}}(pk_i)$, which is at degree of distance $i+1$ from $pk_0$.
Notice that in order to generate the proof of relations between different public keys, it is not necessary to know any of their private keys, but just by knowing their public keys and having their signatures suffices to generate the proofs. So for example the signatures could be publicly accessible, and any user could just fetch them to generate their specific proofs of degrees of distance from other keys.
## Code structure
As you can see, thanks to the simplicity & modularity of Sonobe and arkworks, this whole implementation reduces to defining the [`FCircuit` trait](https://github.com/arnaucube/ethdos-fold/blob/main/src/fcircuit.rs), which takes less than 70 lines of code, the key part being the method `generate_step_constraints` which takes <40 lines of code.
Additionally, we can swap between folding schemes:
With Sonobe we define the Folding Scheme being used at the line (file `src/lib.rs`):
```rust
type FS = Nova<G1, G2, FC, Pedersen<G1>, Pedersen<G2>>;
```
which we could switch it to use HyperNova, is as simple as updating the previous line to:
```rust
type FS = HyperNova< G1, G2, FC, Pedersen<G1>, Pedersen<G2>, 1, 1>;
```
similarly we can switch to using ProtoGalaxy folding scheme:
```rust
type FS = ProtoGalaxy<G1, G2, FC, Pedersen<G1>, Pedersen<G2>>;
```
Notice that in order to generate the proof of relations between different public keys, it is not necessary to know any of their private keys, but just by knowing their public keys and having their signatures suffices to generate the proofs.
And the rest of the code would remain the same, while using a completely different folding scheme.
We can also use any arkworks available cycle of curves at the `G1` and `G2`, the current implementation uses BN254 and Grumpkin curves, since we're verifying EdDSA signatures over the BabyJubJub curve.
## Some numbers

BIN
img/ethdos-fcircuit.png

Before After
Width: 1400  |  Height: 362  |  Size: 78 KiB

+ 2
- 7
index.html

@ -5,7 +5,6 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>ETHdos fold</title>
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
@ -69,29 +68,26 @@
</div>
<div class="col-md-6 box">
<h2 class="text-center mb-4">ETHdos fold</h2>
<p style="font-style:italic;font-size:80%;" class="text-center">(simple browser demo)</p>
<p>Follows the ideas of ETHdos (<a target="_blank" href="https://ethdos.xyz/blog">https://ethdos.xyz/blog</a>), but using Folding Schemes.</p>
<p style="font-size:90%;">It uses <a target="_blank" href="https://github.com/privacy-scaling-explorations/sonobe">Sonobe</a> under the hood, compiled to WASM.</a>
<p style="font-style:italic;font-size:80%;">
Current version does not parallelize in wasm. Same execution can be run natively (no wasm), instructions <a target="_blank" href="https://github.com/arnaucube/ethdos-fold">in the repo</a>.<br>
In the same laptop, natively takes ~290ms per step, in-browser takes ~1700ms per step.
Current version does not parallelize in wasm. Same execution can be run natively (no wasm), instructions <a target="_blank" href="https://github.com/arnaucube/ethdos-fold">in the ethdos-fold repo</a>.<br>
</p>
<button id="btn_gen_params" class="btn btn-primary">1. gen_params</button>
<div class="mb-3">
<!-- <label for="params" class="form-label">params:</label> -->
<textarea id="params" class="form-control" rows="3">params</textarea>
</div>
<div class="mb-3">
<button id="btn_gen_sigs" class="btn btn-primary">2. gen_sigs</button><br>
<!-- <label for="sigs" class="form-label">sigs and pks:</label> -->
<textarea id="sigs" class="form-control" rows="3">sigs and pks</textarea>
</div>
<div class="mb-3">
<button id="btn_fold_sigs" class="btn btn-primary">3. fold_sigs</button><br>
<!-- <label for="ivc_proof" class="form-label">ivc proof:</label> -->
<textarea id="ivc_proof" class="form-control" rows="3">ivc proof</textarea>
</div>
<button id="btn_verify_proof" class="btn btn-primary">4. verify_proof</button>
@ -108,7 +104,6 @@
</div>
</div>
<!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script type="module">

+ 2
- 0
src/fcircuit.rs

@ -1,3 +1,4 @@
//! This file contains the FCircuit (Sonobe's trait) implementation for the ETHdos logic.
use ark_crypto_primitives::sponge::{
constraints::CryptographicSpongeVar,
poseidon::{constraints::PoseidonSpongeVar, PoseidonConfig},
@ -92,6 +93,7 @@ where
Ok([vec![pk_0_x, pk_0_y], pk_i1_xy, vec![degree]].concat())
}
}
#[cfg(test)]
pub mod tests {
use super::*;

+ 0
- 84
src/fold_ethdos.rs

@ -1,84 +0,0 @@
#[cfg(test)]
mod tests {
use ark_bn254::{Fr, G1Projective as G1};
use ark_ec::AffineRepr;
use ark_grumpkin::Projective as G2;
use ark_std::Zero;
use rand::rngs::OsRng;
use arkeddsa::ed_on_bn254_twist::{constraints::EdwardsVar, EdwardsProjective};
use folding_schemes::{
commitment::pedersen::Pedersen,
folding::nova::{Nova, PreprocessorParam},
frontend::FCircuit,
transcript::poseidon::poseidon_canonical_config,
FoldingScheme,
};
use crate::{
fcircuit::EthDosCircuit,
signature::gen_signatures,
utils::{dbg, elapsed, get_time},
};
#[test]
fn full_flow() {
// set how many steps of folding we want to compute
const N_STEPS: usize = 10;
dbg(format!(
"running Nova folding scheme on EthDosCircuit, with N_STEPS={}",
N_STEPS
));
let mut rng = OsRng;
let poseidon_config = poseidon_canonical_config::<Fr>();
let pks_sigs =
gen_signatures::<OsRng, EdwardsProjective>(&mut rng, &poseidon_config, N_STEPS);
// set the initial state
let xy = pks_sigs[0].pk.0.xy().unwrap();
let pk0 = vec![xy.0, xy.1];
let z_0: Vec<Fr> = vec![pk0.clone(), pk0, vec![Fr::zero()]].concat();
type FC = EthDosCircuit<Fr, EdwardsProjective, EdwardsVar>;
let f_circuit = FC::new(poseidon_config.clone()).unwrap();
// define type aliases for the FoldingScheme (FS) and Decider (D), to avoid writting the
// whole type each time
pub type FS = Nova<G1, G2, FC, Pedersen<G1>, Pedersen<G2>, false>;
// prepare the Nova prover & verifier params
let nova_preprocess_params =
PreprocessorParam::new(poseidon_config.clone(), f_circuit.clone());
let start = get_time();
let nova_params = FS::preprocess(&mut rng, &nova_preprocess_params).unwrap();
dbg(format!("Nova params generated: {:?}", elapsed(start)));
// initialize the folding scheme engine, in our case we use Nova
let mut nova = FS::init(&nova_params, f_circuit, z_0.clone()).unwrap();
// run n steps of the folding iteration
let start_full = get_time();
for i in 0..N_STEPS {
let start = get_time();
nova.prove_step(rng, pks_sigs[i].clone(), None).unwrap();
dbg(format!("Nova::prove_step {}: {:?}", nova.i, elapsed(start)));
}
dbg(format!(
"Nova's all {} steps time: {:?}",
N_STEPS,
elapsed(start_full)
));
// verify the last IVC proof
let ivc_proof = nova.ivc_proof();
dbg!(&ivc_proof.z_i);
FS::verify(
nova_params.1.clone(), // Nova's verifier params
ivc_proof,
)
.unwrap();
}
}

+ 90
- 5
src/lib.rs

@ -1,4 +1,4 @@
//! This file contains the WASM bindings.
//! This file contains the WASM bindings, and at the bottom a test running the full flow.
//!
#![allow(non_snake_case)]
#![allow(dead_code)]
@ -28,16 +28,15 @@ use crate::signature::{gen_signatures, SigPk};
use crate::utils::{dbg, elapsed, get_time};
mod fcircuit;
mod fold_ethdos;
mod signature;
mod utils;
use wasm_bindgen::prelude::*;
// define type aliases for the FCircuit (FC) and the FoldingScheme (FS), to avoid writting the
// whole type each time.
// define type aliases for the FCircuit (FC) and the FoldingScheme (FS), to avoid writing the whole
// type each time.
type FC = EthDosCircuit<Fr, EdwardsProjective, EdwardsVar>;
type FS = Nova<G1, G2, FC, Pedersen<G1>, Pedersen<G2>, false>;
type FS = Nova<G1, G2, FC, Pedersen<G1>, Pedersen<G2>>;
#[wasm_bindgen]
extern "C" {
@ -207,3 +206,89 @@ pub fn verify_proof(verifier_params: String, ivc_proof: String) -> String {
.unwrap();
"verified".to_string()
}
#[cfg(test)]
mod tests {
use ark_bn254::{Fr, G1Projective as G1};
use ark_ec::AffineRepr;
use ark_grumpkin::Projective as G2;
use ark_std::Zero;
use rand::rngs::OsRng;
use arkeddsa::ed_on_bn254_twist::{constraints::EdwardsVar, EdwardsProjective};
use folding_schemes::{
commitment::pedersen::Pedersen,
folding::nova::{Nova, PreprocessorParam},
frontend::FCircuit,
transcript::poseidon::poseidon_canonical_config,
FoldingScheme,
};
use crate::{
fcircuit::EthDosCircuit,
signature::gen_signatures,
utils::{dbg, elapsed, get_time},
};
// test showing a full-execution example.
#[test]
fn test_full_flow() {
// set how many steps of folding we want to compute
const N_STEPS: usize = 10;
dbg(format!(
"running Nova folding scheme on EthDosCircuit, with N_STEPS={}",
N_STEPS
));
let mut rng = OsRng;
let poseidon_config = poseidon_canonical_config::<Fr>();
let pks_sigs =
gen_signatures::<OsRng, EdwardsProjective>(&mut rng, &poseidon_config, N_STEPS);
// set the initial state
let xy = pks_sigs[0].pk.0.xy().unwrap();
let pk0 = vec![xy.0, xy.1];
let z_0: Vec<Fr> = vec![pk0.clone(), pk0, vec![Fr::zero()]].concat();
type FC = EthDosCircuit<Fr, EdwardsProjective, EdwardsVar>;
let f_circuit = FC::new(poseidon_config.clone()).unwrap();
// define type aliases for the FoldingScheme (FS) and Decider (D), to avoid writing the
// whole type each time
pub type FS = Nova<G1, G2, FC, Pedersen<G1>, Pedersen<G2>, false>;
// prepare the Nova prover & verifier params
let nova_preprocess_params =
PreprocessorParam::new(poseidon_config.clone(), f_circuit.clone());
let start = get_time();
let nova_params = FS::preprocess(&mut rng, &nova_preprocess_params).unwrap();
dbg(format!("Nova params generated: {:?}", elapsed(start)));
// initialize the folding scheme engine, in our case we use Nova
let mut nova = FS::init(&nova_params, f_circuit, z_0.clone()).unwrap();
// run n steps of the folding iteration
let start_full = get_time();
for i in 0..N_STEPS {
let start = get_time();
nova.prove_step(rng, pks_sigs[i].clone(), None).unwrap();
dbg(format!("Nova::prove_step {}: {:?}", nova.i, elapsed(start)));
}
dbg(format!(
"Nova's all {} steps time: {:?}",
N_STEPS,
elapsed(start_full)
));
// verify the last IVC proof
let ivc_proof = nova.ivc_proof();
dbg!(&ivc_proof.z_i);
FS::verify(
nova_params.1.clone(), // Nova's verifier params
ivc_proof,
)
.unwrap();
}
}

+ 2
- 0
src/signature.rs

@ -1,3 +1,5 @@
//! This file is just some helper methods on top of https://github.com/kilic/arkeddsa in order to
//! use the Signature and PublicKey as ExternalInputs in the FCircuit.
use ark_crypto_primitives::sponge::{
poseidon::{PoseidonConfig, PoseidonSponge},
Absorb, CryptographicSponge,

+ 13
- 13
src/utils.rs

@ -1,17 +1,6 @@
#[cfg(target_arch = "wasm32")]
use web_sys::console;
pub fn set_panic_hook() {
// When the `console_error_panic_hook` feature is enabled, we can call the
// `set_panic_hook` function at least once during initialization, and then
// we will get better error messages if our code ever panics.
//
// For more details see
// https://github.com/rustwasm/console_error_panic_hook#readme
#[cfg(feature = "console_error_panic_hook")]
console_error_panic_hook::set_once();
}
pub fn dbg(s: String) {
#[cfg(target_arch = "wasm32")]
console::log_1(&s.into());
@ -49,9 +38,20 @@ pub fn elapsed(start: u64) -> u64 {
#[cfg(target_arch = "wasm32")]
fn get_wasm_time() -> u64 {
use web_sys::window;
let window = window().expect("should have a window in this context");
let window = window().expect("no window");
let performance = window
.performance()
.expect("performance should be available");
.expect("window.performance() not found");
performance.now() as u64
}
pub fn set_panic_hook() {
// When the `console_error_panic_hook` feature is enabled, we can call the
// `set_panic_hook` function at least once during initialization, and then
// we will get better error messages if our code ever panics.
//
// For more details see
// https://github.com/rustwasm/console_error_panic_hook#readme
#[cfg(feature = "console_error_panic_hook")]
console_error_panic_hook::set_once();
}

Loading…
Cancel
Save