chore: minor refactor

This commit is contained in:
Magamedrasul Ibragimov
2023-07-06 17:49:02 +04:00
parent afc92146df
commit caf528a4e3
11 changed files with 18 additions and 322 deletions

View File

@@ -2,17 +2,8 @@
- [RLN](./rln.md)
- [Overview](./overview.md)
- [What is RLN](./what_is_rln.md)
- [Under the hood](./under_the_hood.md)
- [Protocol spec V1](./protocol_spec.md)
- [Protocol spec V2](./protocol_spec_v2.md)
- [Formal spec](./formal_spec.md)
<!-- - [Circuits](./circuits.md) -->
- [Protocol spec V1](./protocol_spec.md)
- [Protocol spec V2](./protocol_spec_v2.md)
- [Formal spec](./formal_spec.md)
- [Uses](./uses.md)
- [How to use](./how_to_use.md)
- [JavaScript RLN]()
- [Rust RLN]()
- [Theory](./theory.md)
- [Shamir's Secret Sharing](./sss.md)
- [Appendix](./appendix.md)
- [A - Terminology](./terminology.md)
- [B - References](./references.md)
- [Shamir's Secret Sharing](./sss.md)

View File

@@ -1,5 +0,0 @@
# Appendix
The following sections contain reference material you may find useful:
* Terminology
* References

View File

@@ -1,233 +0,0 @@
# Circuits
*[zkSNARK](https://vitalik.ca/general/2022/06/15/using_snarks.html) is used in the **RLN** core. Therefore, we must represent the protocol in [R1CS](https://www.zeroknowledgeblog.com/index.php/the-pinocchio-protocol/r1cs) (as we use [Groth16](https://www.zeroknowledgeblog.com/index.php/groth16)). [Circom](https://docs.circom.io/) was chosen for this. This section explains **RLN** circuits for the linear polynomial case (one message per epoch). You can find implementation for the general case [here](https://github.com/privacy-scaling-explorations/rln/blob/master/circuits/nrln-base.circom)*
___
**RLN** circuits implement the logic described in [previous topic](./protocol_spec.md).
## Merkle Tree circuit
One of the critical components of **RLN** is the *Incremental Merkle Tree* for the membership tree. Any Merkle tree can be used, but we have chosen the Incremental Merkle Tree for gas efficiency.
Let's look at the [implementation](https://github.com/privacy-scaling-explorations/rln/blob/master/circuits/incrementalMerkleTree.circom).
At the beginning of the file, we denote that we use Circom 2.0 and include two helper *zk-gadgets*:
```swift
pragma circom 2.0.0;
include "../node_modules/circomlib/circuits/poseidon.circom";
include "../node_modules/circomlib/circuits/mux1.circom";
```
*Poseidon* gadget is just the implementation of the *Poseidon* hash function; the *mux1* gadget will be described later.
Next, we can see two implemented gadgets:
```swift
template PoseidonHashT3() {
var nInputs = 2;
signal input inputs[nInputs];
signal output out;
component hasher = Poseidon(nInputs);
for (var i = 0; i < nInputs; i ++) {
hasher.inputs[i] <== inputs[i];
}
out <== hasher.out;
}
template HashLeftRight() {
signal input left;
signal input right;
signal output hash;
component hasher = PoseidonHashT3();
left ==> hasher.inputs[0];
right ==> hasher.inputs[1];
hash <== hasher.out;
}
```
These are helper gadgets to make the code more clean. *Poseidon* gadget is implemented with the ability to take a different number of arguments. We use `PoseidonHashT3()` to initialize it like a function with two arguments. And `HashLeftRight` use `PoseidonHashT3` in a more "readable" way: it takes two inputs, `left` and `right,` and outputs the result of the calculation.
Next comes the core of the Merkle Tree gadget:
```swift
template MerkleTreeInclusionProof(n_levels) {
signal input leaf;
signal input path_index[n_levels];
signal input path_elements[n_levels][1];
signal output root;
component hashers[n_levels];
component mux[n_levels];
signal levelHashes[n_levels + 1];
levelHashes[0] <== leaf;
...
root <== levelHashes[n_levels];
}
```
Here we have three inputs: `leaf,` `path_index,` and `path_elements.`
`path_index` is the position of the leaf represented in binary. We need the binary representation of the position in the Merkle tree to understand the hashing path from the leaf to the root (more on that *["3. Recursive Incremental Merkle Tree Algorithm, page 4"]()*).
`path_elements` are sibling leaves that are part of Merkle Proof.
`leaf = Poseidon(identity_secret)`, so it's just *identity commitment*.
There is a Merkle Tree hashing algorithm in the omitted part, no more than that.
## RLN core
RLN circuit is the implementation of **RLN** logic itself (which in turn uses the *Merkle Tree* gadget). You can find the implementation [here](https://github.com/privacy-scaling-explorations/rln/blob/master/circuits/rln-base.circom).
So, let's start with helper gadgets:
```swift
template CalculateIdentityCommitment() {
signal input identity_secret;
signal output out;
component hasher = Poseidon(1);
hasher.inputs[0] <== identity_secret;
out <== hasher.out;
}
template CalculateExternalNullifier() {
signal input epoch;
signal input rln_identifier;
signal output out;
component hasher = Poseidon(2);
hasher.inputs[0] <== epoch;
hasher.inputs[1] <== rln_identifier;
out <== hasher.out;
}
template CalculateA1() {
signal input a_0;
signal input external_nullifier;
signal output out;
component hasher = Poseidon(2);
hasher.inputs[0] <== a_0;
hasher.inputs[1] <== external_nullifier;
out <== hasher.out;
}
template CalculateInternalNullifier() {
signal input a_1;
signal output out;
component hasher = Poseidon(1);
hasher.inputs[0] <== a_1;
out <== hasher.out;
}
```
It's easy to understand these samples: `CalculateIdentityCommitment()`, `CalculateA1()`, `CalculateInternalNullifier()`, `CalculateExternalNullifier()` - they do exactly what their name says; they are implemented as it's described in [previous topic](./protocol_spec.md).
Now, let's look at the core logic of the **RLN** circuit.
```swift
...
signal input identity_secret;
signal input path_elements[n_levels][LEAVES_PER_PATH_LEVEL];
signal input identity_path_index[n_levels];
signal input x;
signal input epoch;
signal input rln_identifier;
signal output y;
signal output root;
signal output nullifier;
...
```
So, here we have many inputs. Private inputs are: `identity_secret` (basically `a_0` from the polynomial), `path_elements[][]`, `identity_path_index[]`. Public inputs are: `x` (actually just the hash of a signal), `epoch,` `rln_identifier`. Outputs are: `y` (polynomial share/secret share), `root` of a Merkle Tree, and `nullifier` (which is basically `internal_nullifier`).
**RLN** circuit consists of two checks:
* Membership in Merkle Tree
* Correctness of secret share
### Membership in Merkle Tree
To check membership in a Merkle Tree, we can simply use the previously described Merkle Tree gadget:
```swift
...
component identity_commitment = CalculateIdentityCommitment();
identity_commitment.identity_secret <== identity_secret;
var i;
var j;
component inclusionProof = MerkleTreeInclusionProof(n_levels);
inclusionProof.leaf <== identity_commitment.out;
for (i = 0; i < n_levels; i++) {
for (j = 0; j < LEAVES_PER_PATH_LEVEL; j++) {
inclusionProof.path_elements[i][j] <== path_elements[i][j];
}
inclusionProof.path_index[i] <== identity_path_index[i];
}
...
```
Here we are calculating the `identity_commitment` and passing it along with sibling leaves and binary representation of the position to a Merkle Tree gadget. It gives us the calculated root as an output, and we can put the constraint on that:
```swift
root <== inclusionProof.root;
```
### Correctness of secret share
As we use linear polynomial we need to check that `y = a_1 * x + a_0` (`a_0` is identity secret). For that, we need to calculate `external_nullifier` and constraints on `a_1` and secret share:
```swift
...
component external_nullifier = CalculateExternalNullifier();
external_nullifier.epoch <== epoch;
external_nullifier.rln_identifier <== rln_identifier;
component a_1 = CalculateA1();
a_1.a_0 <== identity_secret;
a_1.external_nullifier <== external_nullifier.out;
y <== identity_secret + a_1.out * x;
...
```
To calculate and reveal the `nullifier`:
```swift
...
component calculateNullifier = CalculateInternalNullifier();
calculateNullifier.a_1 <== a_1.out;
nullifier <== calculateNullifier.out;
...
```
## Main runner of the circuits
Now the Circuits can be used as gadgets. If we want to use it in our app, we need to initialize it and have a *main* - starting point function. It can be found [here](https://github.com/privacy-scaling-explorations/rln/blob/master/circuits/rln.circom).
The implementation is super basic:
```swift
pragma circom 2.0.0;
include "./rln-base.circom";
component main { public [x, epoch, rln_identifier] } = RLN(15);
```
That's the whole **RLN** Circom Circuit :) Here we just need to list all public inputs (`x,` `epoch,` `rln_identifier`; the rest of the inputs are private). Also, we set the depth of the Merkle Tree = 15 (max of 32768 members).

View File

@@ -1,6 +1,4 @@
# Formal spec
*[RFC for RLN-V2](https://rfc.vac.dev/spec/58/)*
# Formal spec of circom-rln
- [Utils](#utils-templates)
- [MerkleTreeInclusionProof](#merkletreeinclusionproof)

View File

@@ -1,5 +0,0 @@
# How to use
This section provides information on how to use **RLN** in your project:
* JavaScript RLN (for [rln-js](https://github.com/Rate-Limiting-Nullifier/rlnjs))
* Rust RLN (for [zerokit-rln](https://github.com/vacp2p/zerokit/tree/master/rln))

View File

@@ -1,25 +0,0 @@
# References
* [RFC V1](https://rfc.vac.dev/spec/32/)
* [RFC V2](https://rfc.vac.dev/spec/58/)
* [zkResearch post](https://zkresear.ch/t/rate-limit-nullifier-v2-circuits/102)
* [First Proposal/Idea of RLN by Barry WhiteHat](https://ethresear.ch/t/semaphore-rln-rate-limiting-nullifier-for-spam-prevention-in-anonymous-p2p-setting/5009)
* [RLN Overview by Blagoj](https://medium.com/privacy-scaling-explorations/rate-limiting-nullifier-a-spam-protection-mechanism-for-anonymous-environments-bbe4006a57d)
* [Demo RLN Spec](https://hackmd.io/@aeAuSD7mSCKofwwx445eAQ/BJcfDByNF)
* [VAC RLN Spec](https://rfc.vac.dev/spec/32/)
* [Understand zkSNARK](https://vitalik.ca/general/2016/12/10/qap.html)
* [Circom Docs](https://docs.circom.io/)
* [rln-js](https://github.com/Rate-Limiting-Nullifier/rlnjs)
* [zerokit-rln](https://github.com/vacp2p/zerokit)
* [Incremental Merkle Tree paper](https://arxiv.org/pdf/2105.06009v1.pdf)

View File

@@ -1,15 +0,0 @@
# Terminology
Term | Description
---- | -----------
zkSNARK | Proof construction where one can prove possession of certain information, e.g. a secret key, without revealing that information, and without any interaction between the prover and verifier.
Circuit | A program, that describes constraints for the prover in zkSNARK (for more information read [this](https://medium.com/@VitalikButerin/quadratic-arithmetic-programs-from-zero-to-hero-f6d558cea649)).
zk-Gadget | Circuit, that can be used as a building block for another circuit, e.g. Poseidon hash function gadget.
Stake | Financial or social stake required for registering in the RLN applications. Common stake examples are: locking cryptocurrency (financial), linking reputable social identity.
Identity secret | Random number, which must be kept private by the user.
Identity commitment | The result of Poseidon(Identity secret) calculation. It is used by the users for registering in the protocol.
Signal | The message generated by a user. It is an arbitrary bit string that may represent a chat message, a URL request, protobuf message, etc.
Signal hash | Hash of the signal, used as an input in the RLN circuit.
RLN Identifier | Random finite field value unique per RLN app. It is used for additional cross-application security. The role of the RLN identifier is protection of the user secrets being compromised if signals are being generated with the same credentials at different apps.
RLN membership tree | Merkle tree data structure, filled with identity commitments of the users. Serves as a data structure that ensures user registrations.
Merkle proof | Proof that a user is member of the RLN membership tree.

View File

@@ -1,6 +0,0 @@
# Theory
This section provides theoretical information that underpins **RLN**.
Here we'll discuss:
* Shamir's Secret Sharing

View File

@@ -1,7 +0,0 @@
# Under the hood
This section provides deep and technical **RLN** overview.
We'll discuss:
* Technical side of **RLN** (specification demo)
* How circuits are implemented

View File

@@ -2,6 +2,7 @@
This section contains list of apps that use **RLN**:
* [zk-chat](https://github.com/njofce/zk-chat) - A spam resistant instant messaging application for private and anonymous communication
* [rln-chat-app](https://github.com/b-d1/rln-anonymous-chat-app) - PoC app, created using rln-js
* [waku-rln-relay](https://rfc.vac.dev/spec/17/) - Extension of [waku-relay](https://rfc.vac.dev/spec/11/) (spam protection with **RLN**)
* [zk-chat](https://github.com/Rate-Limiting-Nullifier/zk-chat-client-server) - a spam resistant instant messaging application for private and anonymous communication;
* [waku-rln-relay](https://rfc.vac.dev/spec/17/) - extension of [waku-relay](https://rfc.vac.dev/spec/11/) (spam protection with **RLN**);
* [lambdadelta](https://github.com/nabladelta/lambdadelta) - P2P event deed secured by RLN proofs;
* [bernkastel](https://github.com/nabladelta/bernkastel) - decentralized event feed, based on lambdadelta library.

View File

@@ -1,9 +1,9 @@
# What is Rate-Limiting Nullifier?
**RLN** is a zero-knowledge gadget that enables spam prevention for anonymous environments.
**RLN** is a zero-knowledge gadget that enables spam prevention in anonymous environments.
The anonymity property opens up the possibility for spam and Sybil attack vectors for certain applications, which could seriously degrade the user experience and the overall functioning of the application. For example, imagine a chat application where users are anonymous. Now, everyone can write an unlimited number of spam messages, but we don't have the ability to kick this member because the spammer is anonymous.
The anonymity property opens up the possibility for spam, which could seriously degrade the user experience and the overall functioning of the application. For example, imagine a chat application where users are anonymous. Now, everyone can write an unlimited number of spam messages, but we don't have the ability to kick this member because the spammer is anonymous.
**RLN** helps us identify and "kick" the spammer.
@@ -11,12 +11,14 @@ Moreover, **RLN** can be useful not only to prevent spam attacks but, in general
## How it works
The **RLN** construct's functionality consists of three parts, which, when integrated together, provide spam and Sybil attack protection. These parts should be integrated by the upstream applications, which require anonymity and spam protection. The applications can be centralized or decentralized. For decentralized applications, each user maintains separate storage and compute resources for the application. The three parts are:
The **RLN** construct's functionality consists of three parts These parts should be integrated by the upstream applications, that require anonymity and spam protection. The applications can be centralized or decentralized. For decentralized applications, each user maintains separate storage and compute resources for the application.
The three parts are:
* registration;
* interaction;
* withdrawal/slashing;
* withdrawal (or slashing);
### User registration
### Registration
Before registering to the application, the user needs to generate a secret key and derive an identity commitment from the secret key using the Poseidon hash function:
@@ -24,7 +26,7 @@ Before registering to the application, the user needs to generate a secret key a
The user registers to the application by providing a form of stake and their identity commitment, which is derived from the secret key. The application maintains a Merkle tree data structure (in the latest iteration of **RLN**, we use an Incremental Merkle Tree algorithm for gas efficiency, but the Merkle tree does not have to be on-chain), which stores the identity commitments of the registered users. Upon successful registration, the user's identity commitment is stored in a leaf of the Merkle tree, and an index is given to them, representing their position in the tree.
### User interaction
### Interaction
For each interaction that the user wants to make with the application, the user must generate a zero-knowledge proof ensuring that their identity commitment is part of the membership Merkle tree.
There are a number of use-cases for **RLN**, such as voting applications (1 vote per election), chat (one message per second), and rate-limiting cache access (CDN denial of service protection). The verifier can be a server for centralized applications or the other users for decentralized applications.
@@ -40,5 +42,5 @@ Thus, users have to split their `secret_key` into `n` parts, and for every inter
If they make more interactions than allowed per epoch, their secret key can be fully reconstructed.
### User removal (slashing)
### Withdrawal (or slashing)
The final property of the **RLN** mechanism is that it allows for the users to be removed from the membership tree by anyone that knows their secret key. The membership tree contains the identity commitments of all registered users. Users' identity commitment is derived from their secret key, and the secret key of the user is only revealed in a spam event (except for the scenarios where the original users want to remove themselves, which they can always do because they know their secret key). When an economic stake is present, the **RLN** mechanism can be implemented in a way that the spammer's stake is sent to the first user that correctly reports the spammer by providing the reconstructed secret key of the spammer as proof.