diff --git a/README.md b/README.md index 2c033a3..94c2944 100644 --- a/README.md +++ b/README.md @@ -9,5 +9,48 @@ It uses + +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_\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`). + +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-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$. + +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. + + + +## Some numbers +> Current numbers using the Sonobe version at commit `c6f1a246e0705582a75de6becf4ad21f325fa5a1`. + +Thinkpad laptop, i7-1270P, 16 cores: + +- native: `~290ms` per step +- in-browser: `~2.2s` per step + +Other numbers: due the fixed overhead of folding, current implementation folding 1 single signature per folding step is not ideal. To get more 'real' values, the repo https://github.com/arnaucube/fold-babyjubjubs contains a similar implementation but that performs multiple signature verifications per each folding step, amortizing better the fixed folding costs, reducing the time per signature substantially (eg. on the same laptop it takes `~45ms` per signature in the folding step). + + ## Acknowledgements -Thanks to Michael Chu for proposing to build this prototype. This repo uses [Sonobe](https://github.com/privacy-scaling-explorations/sonobe), which relies on [arkworks-rs](https://github.com/arkworks-rs), and for the BabyJubJub EdDSA it uses [kilic/arkeddsa](https://github.com/kilic/arkeddsa). +Thanks to Michael Chu for proposing to build this prototype. This repo uses [Sonobe](https://github.com/privacy-scaling-explorations/sonobe), which relies on [arkworks-rs](https://github.com/arkworks-rs), and for the BabyJubJub EdDSA it uses [kilic/arkeddsa](https://github.com/kilic/arkeddsa). Thanks also to the [ETHdos](https://ethdos.xyz/blog) authors, the idea is very cool. diff --git a/img/ethdos-states-diagram.png b/img/ethdos-states-diagram.png new file mode 100644 index 0000000..e75d17e Binary files /dev/null and b/img/ethdos-states-diagram.png differ diff --git a/img/signatures-relations.png b/img/signatures-relations.png new file mode 100644 index 0000000..ba232c3 Binary files /dev/null and b/img/signatures-relations.png differ diff --git a/src/fcircuit.rs b/src/fcircuit.rs index cf538bd..aeb77a3 100644 --- a/src/fcircuit.rs +++ b/src/fcircuit.rs @@ -48,6 +48,7 @@ where fn state_len(&self) -> usize { 5 } + // This method defines the logic that is done in-circuit at each folding step fn generate_step_constraints( &self, cs: ConstraintSystemRef, @@ -55,33 +56,38 @@ where z_i: Vec>, external_inputs: Self::ExternalInputsVar, ) -> Result>, SynthesisError> { + // get the values from the state, where: state = [ pk_0, pk_i, i] let pk_0_x = z_i[0].clone(); let pk_0_y = z_i[1].clone(); let pk_i_x = z_i[2].clone(); let pk_i_y = z_i[3].clone(); let mut degree = z_i[4].clone(); - // get the 'msg' that has been signed, which is the hash of the previous-signer public key + // get the 'pk_i_hashed' value, which is the hash of the pk_i, and is the value that has + // been signed by the new public key (pk_i+1) let mut poseidon = PoseidonSpongeVar::new(cs.clone(), &self.config); poseidon.absorb(&vec![pk_i_x, pk_i_y])?; let h = poseidon.squeeze_field_elements(1)?; - let msg = h + let pk_i_hashed = h .first() .ok_or(ark_relations::r1cs::SynthesisError::Unsatisfiable)?; - // check that the last signer is signed by the new signer + // check that the last signer's public key (pk_i) hashed (=pk_i_hashed) is signed by the + // new signer public key (pk_i+1) let res = verify::( cs.clone(), self.config.clone(), - external_inputs.pk.clone(), + external_inputs.pk.clone(), // pk_{i+1} (external_inputs.sig_r, external_inputs.sig_s), - msg.clone(), + pk_i_hashed.clone(), )?; res.enforce_equal(&Boolean::::TRUE)?; // increment the degree degree = degree.clone() + FpVar::::one(); + // return the new IVC state, where we place the pk_{i+1} at the place where previously had + // the pk_i, together with the new updated degree of distance value let pk_i1_xy = external_inputs.pk.to_constraint_field()?; Ok([vec![pk_0_x, pk_0_y], pk_i1_xy, vec![degree]].concat()) }