mirror of https://github.com/vacp2p/rfc.git
feat: 32/RLN raw spec
This commit is contained in:
parent
f03df037d6
commit
ca706eaf04
|
@ -0,0 +1,618 @@
|
||||||
|
---
|
||||||
|
slug: 32
|
||||||
|
title: 32/RLN
|
||||||
|
name: Rate Limit Nullifier
|
||||||
|
status: raw
|
||||||
|
editor: Blagoj Dimovski <blagoj.dimovski@yandex.com>
|
||||||
|
contributors:
|
||||||
|
- Barry Whitehat <barrywhitehat@protonmail.com>
|
||||||
|
- Sanaz Taheri <sanaz@status.im>
|
||||||
|
- Oskar Thorén <oskar@status.im>
|
||||||
|
- Onur Kilic <onurkilic1004@gmail.com>
|
||||||
|
---
|
||||||
|
|
||||||
|
# Abstract
|
||||||
|
|
||||||
|
The following specification covers the RLN construct as well as some auxiliary libraries useful for interacting with it.
|
||||||
|
Rate limiting nullifier (RLN) is a construct based on zero-knowledge proofs that provides an anonymous rate-limited signaling/messaging framework suitable for decentralized (and centralized) environments.
|
||||||
|
Anonymity refers to the unlinkability of messages to their owner.
|
||||||
|
|
||||||
|
# Motivation
|
||||||
|
|
||||||
|
RLN guarantees a messaging rate is enforced cryptographically while preserving the anonymity of the message owners.
|
||||||
|
A wide range of applications can benefit from RLN and provide desirable security features.
|
||||||
|
For example, an e-voting system can integrate RLN to contain the voting rate while protecting the voters-vote unlinkability.
|
||||||
|
Another use case is to protect an anonymous messaging system against DDoS and spam attacks by containing messaging rate of users.
|
||||||
|
This latter use case is explained in [17/Waku-RLN-Relay RFC](/spec/17).
|
||||||
|
|
||||||
|
|
||||||
|
# Flow
|
||||||
|
|
||||||
|
The users participate in the protocol by first registering to an application-defined group referred by the _membership group_.
|
||||||
|
Registration to the group is mandatory for signaling in the application.
|
||||||
|
After registration, group members can generate Zero-knowledge Proof of membership for their signals and can participate in the application.
|
||||||
|
Usually, the membership requires a financial or social stake which
|
||||||
|
is beneficial for the prevention of Sybil attacks and double-signaling.
|
||||||
|
Group members are allowed to send one signal per external nullifier (an identifier that groups signals and can be thought of as a voting booth. Usually a timestamp or time interval in the RLN apps).
|
||||||
|
If a user generates more signals than allowed,
|
||||||
|
the user risks being slashed - by revealing his membership secret credentials.
|
||||||
|
If the financial stake is put in place, the user also risks his stake being taken.
|
||||||
|
|
||||||
|
Generally the flow can be described by the following steps:
|
||||||
|
|
||||||
|
1. Registration
|
||||||
|
2. Signaling
|
||||||
|
3. Verification and slashing
|
||||||
|
|
||||||
|
|
||||||
|
## Registration
|
||||||
|
|
||||||
|
Depending on the application requirements, the registration can be implemented in different ways, for example:
|
||||||
|
- centralized registrations, by using a central server
|
||||||
|
- decentralized registrations, by using a smart contract
|
||||||
|
|
||||||
|
What is important is that the users' identity commitments (explained in section [User Indetity](#user-identity)) are stored in a Merkle tree,
|
||||||
|
and the users can obtain a merkle proof proving that they are part of the group.
|
||||||
|
|
||||||
|
Also depending on the application requirements,
|
||||||
|
usually a financial or social stake is introduced.
|
||||||
|
|
||||||
|
An example for financial stake is: For each registration a certain amount of ETH is required.
|
||||||
|
An example for social stake is using InterRep as a registry -
|
||||||
|
users need to prove that they have a highly reputable social media account.
|
||||||
|
|
||||||
|
### Implementation notes
|
||||||
|
|
||||||
|
#### User identity
|
||||||
|
|
||||||
|
The user's identity is composed of:
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
identity_secret: [identity_nullifier, identity_trapdoor],
|
||||||
|
identity_secret_hash: poseidonHash(identity_secret),
|
||||||
|
identity_commitment: poseidonHash([identity_secret_hash])
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
For registration, the user needs to submit their `identity_commitment` (along with any additional registration requirements) to the registry.
|
||||||
|
Upon registration, they should receive `leaf_index` value which represents their position in the merkle tree.
|
||||||
|
Receiving a `leaf_index` is not a hard requirement and is application specific.
|
||||||
|
The other way around is the users calculating the `leaf_index` themselves upon successful registration.
|
||||||
|
|
||||||
|
## Signaling
|
||||||
|
|
||||||
|
After registration,
|
||||||
|
the users can participate in the application by sending signals to the other participants in a decentralised manner or to a centralised server.
|
||||||
|
Along with their signal,
|
||||||
|
they need to generate a ZK-Proof by using the circuit with the specification described above.
|
||||||
|
|
||||||
|
For generating a proof,
|
||||||
|
the users need to obtain the required parameters or compute them themselves,
|
||||||
|
depending on the application implementation and client libraries supported by the application.
|
||||||
|
For example the users can store the membership merkle tree on their end and
|
||||||
|
generate a merkle proof whenever they want to generate a signal.
|
||||||
|
|
||||||
|
### Implementation notes
|
||||||
|
|
||||||
|
#### Signal hash
|
||||||
|
|
||||||
|
The signal hash can be generated by hashing the raw signal (or content) using the `keccak256` hash function.
|
||||||
|
|
||||||
|
#### External nullifier
|
||||||
|
|
||||||
|
The external nullifier can be generated by hashing a raw string (i.e UNIX timestamp) value using `keccak256`.
|
||||||
|
|
||||||
|
#### Obtaining merkle proof
|
||||||
|
|
||||||
|
The merkle proof should be obtained locally or from a trusted third party.
|
||||||
|
By using the [incremental merkle tree algorithm](https://github.com/appliedzkp/incrementalquintree/blob/master/ts/IncrementalQuinTree.ts),
|
||||||
|
the merkle can be obtained by providing the `leaf_index` of the `identity_commitment`.
|
||||||
|
The proof (`merkle_proof`) is composed of the following fields:
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
root: bigint
|
||||||
|
indices: number[]
|
||||||
|
path_elements: bigint[][]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
1. **root** - The root of membership group Merkle tree at the time of publishing the message
|
||||||
|
2. **indices** - The index fields of the leafs in the merkle tree - used by the merkle tree algorithm for verification
|
||||||
|
3. **path_elements** - Auxiliary data structure used for storing the path to the leaf - used by the merkle proof algorithm for verificaton
|
||||||
|
|
||||||
|
|
||||||
|
#### Generating proof
|
||||||
|
|
||||||
|
For proof generation,
|
||||||
|
the user need to submit the following fields to the circuit:
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
identity_secret: identity_secret_hash,
|
||||||
|
path_elements: merkle_proof.path_elements,
|
||||||
|
identity_path_index: merkle_proof.indices,
|
||||||
|
x: signal_hash,
|
||||||
|
external_nullifier: external_nullifier,
|
||||||
|
rln_identifier: rln_identifier
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
#### Calculating output
|
||||||
|
|
||||||
|
The proof output is calculated locally,
|
||||||
|
in order for the required fields for proof verification to be sent along with the proof.
|
||||||
|
The proof output is composed of the `y` share of the secret equation and the `internal_nullifier`.
|
||||||
|
The `internal_nullifier` represents an unique fingerprint for the user for the `external_nullifier`.
|
||||||
|
The following fields are needed for proof output calculation:
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
identity_secret_hash: bigint,
|
||||||
|
external_nullifier: bigint,
|
||||||
|
rln_identifier: bigint,
|
||||||
|
x: bigint,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The output `[y, internal_nullifier]` is calculated in the following way:
|
||||||
|
|
||||||
|
```
|
||||||
|
a_0 = identity_secret
|
||||||
|
a_1 = poseidonHash([a0, external_nullifier])
|
||||||
|
|
||||||
|
y = a_0 + x * a_1
|
||||||
|
|
||||||
|
internal_nullifier = poseidonHash([a_1, rln_identifier])
|
||||||
|
```
|
||||||
|
|
||||||
|
It relies on the properties of the [Shamir's Secret sharing scheme](https://en.wikipedia.org/wiki/Shamir%27s_Secret_Sharing).
|
||||||
|
|
||||||
|
#### Sending the output message
|
||||||
|
|
||||||
|
The user's output message (`output_message`),
|
||||||
|
containing the signal should contain the following fields at minimum:
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
signal: signal, # non-hashed signal
|
||||||
|
proof: zk_proof,
|
||||||
|
internal_nullifier: internal_nullifier,
|
||||||
|
x: x, # signal_hash
|
||||||
|
y: y,
|
||||||
|
rln_identifier: rln_identifier
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Additionally depending on the application,
|
||||||
|
the following fields might be required:
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
root: merkle_proof.root,
|
||||||
|
external_nullifier: external_nullifier
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Verification and slashing
|
||||||
|
|
||||||
|
The slashing implementation is dependent on the type of application.
|
||||||
|
If the application is implemented in a centralised manner,
|
||||||
|
and everything is stored on a single server,
|
||||||
|
the slashing will be implemented only on the server.
|
||||||
|
Otherwise if the application is distributed,
|
||||||
|
the slashing will be implemented on each user's client.
|
||||||
|
|
||||||
|
### Implementation notes
|
||||||
|
|
||||||
|
Each user of the protocol (server or otherwise) will need to store metadata for each message received by each user,
|
||||||
|
for the given `external_nullifier`.
|
||||||
|
The data can be deleted when the `external_nullifier` passes.
|
||||||
|
Storing metadata is required, so that if a user sends more than one unique signal per `external_nullifier`,
|
||||||
|
they can be slashed and removed from the protocol.
|
||||||
|
The metadata stored contains the `x`, `y` shares and the `internal_nullifier` for the user for each message.
|
||||||
|
If enough such shares are present, the user's secret can be retreived.
|
||||||
|
|
||||||
|
One way of storing received metadata (`messaging_metadata`) is the following format:
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
[external_nullifier]: {
|
||||||
|
[internal_nullifier]: {
|
||||||
|
x_shares: [],
|
||||||
|
y_shares: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Verification
|
||||||
|
|
||||||
|
The output message verification consists of the following steps:
|
||||||
|
- `external_nullifier` correctness
|
||||||
|
- non-duplicate message check
|
||||||
|
- `zk_proof` verification
|
||||||
|
- spam verification
|
||||||
|
|
||||||
|
**1. `external_nullifier` correctness**
|
||||||
|
Upon received `output_message`, first the `external_nullifier` field is checked,
|
||||||
|
to ensure that the message matches the current `external_nullifier`.
|
||||||
|
If the `external_nullifier` is correct the verification continues, otherwise, the message is discarded.
|
||||||
|
|
||||||
|
**2. non-duplicate message check**
|
||||||
|
The received message is checked to ensure it is not duplicate.
|
||||||
|
The duplicate message check is performed by verifying that the `x` and `y` fields do not exist in the `messaging_metadata` object.
|
||||||
|
If the `x` and `y` fields exist in the `x_shares` and `y_shares` array for the `external_nullifier` and
|
||||||
|
the `internal_nullifier` the message can be considered as a duplicate.
|
||||||
|
Duplicate messages are discarded.
|
||||||
|
|
||||||
|
**3. `zk_proof` verification**
|
||||||
|
|
||||||
|
The `zk_proof` should be verified by providing the `zk_proof` field to the circuit verifier along with the `public_signal`:
|
||||||
|
|
||||||
|
```
|
||||||
|
[
|
||||||
|
y,
|
||||||
|
merkle_proof.root,
|
||||||
|
internal_nullifier,
|
||||||
|
signal_hash,
|
||||||
|
external_nullifier,
|
||||||
|
rln_identifier
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
If the proof verification is correct,
|
||||||
|
the verification continues, otherwise the message is discarded.
|
||||||
|
|
||||||
|
**4. Double signaling verification**
|
||||||
|
|
||||||
|
After the proof is verified the `x`, and `y` fields are added to the `x_shares` and `y_shares` arrays of the `messaging_metadata` `external_nullifier` and `internal_nullifier` object.
|
||||||
|
If the length of the arrays is equal to the signaling threshold (`limit`), the user can be slashed.
|
||||||
|
|
||||||
|
#### Slashing
|
||||||
|
|
||||||
|
After the verification, the user can be slashed if two different shares are present to reconstruct their `identity_secret_hash` from `x_shares` and `y_shares` fields,
|
||||||
|
for their `internal_nullifier`.
|
||||||
|
The secret can be retreived by the properties of the Shamir's secret sharing scheme.
|
||||||
|
In particular the secret (`a_0`) can be retrieved by computing [Lagrange polynomials](https://en.wikipedia.org/wiki/Lagrange_polynomial).
|
||||||
|
|
||||||
|
After the secret is retreived,
|
||||||
|
the user's `identity_commitment` can be generated from the secret and it can be used for removing the user from the membership merkle tree (zeroing out the leaf that contains the user's `identity_commitment`).
|
||||||
|
Additionally, depending on the application the `identity_secret_hash` can be used for taking the user's provided stake.
|
||||||
|
|
||||||
|
# Technical overview
|
||||||
|
|
||||||
|
The main RLN construct is implemented using a [ZK-SNARK](https://z.cash/technology/zksnarks/) circuit.
|
||||||
|
However it is helpful to describe the other necessary outside components for interaction with the circuit,
|
||||||
|
which together with the ZK-SNARK circuit enable the above mentioned features.
|
||||||
|
|
||||||
|
|
||||||
|
## Terminology
|
||||||
|
|
||||||
|
| Term | Description |
|
||||||
|
|---------------------------|-------------------------------------------------------------------------------------|
|
||||||
|
| **ZK-SNARK** | https://z.cash/technology/zksnarks/ |
|
||||||
|
| **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** | An array of two unique random components (identity nullifier and identity trapdoor), which must be kept private by the user. Secret hash and identity commitment are derived from this array. |
|
||||||
|
| **Identity nullifier** | Random 32 byte value used as component for identity secret generation. |
|
||||||
|
| **Identity trapdoor** | Random 32 byte value used as component for identity secret generation. |
|
||||||
|
| **Identity secret hash** | The hash of the identity secret, obtained using the Poseidon hash function. It is used for deriving the identity commitment of the user, and as a private input for zk proof generation. The secret hash should be kept private by the user. |
|
||||||
|
| **Identity commitment** | Hash obtained from the `Identity secret hash` by using the poseidon hash function. 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** | Keccak 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. |
|
||||||
|
|
||||||
|
|
||||||
|
## RLN ZK-Circuit specific terms
|
||||||
|
|
||||||
|
| Term | Description |
|
||||||
|
|---------------------------|-------------------------------------------------------------------------------------|
|
||||||
|
| **x** | Keccak hash of the signal, same as signal hash (Defined above). |
|
||||||
|
| **A0** | The identity secret hash. |
|
||||||
|
| **A1** | Poseidon hash of [A0, External nullifier] (see about External nullifier below). |
|
||||||
|
| **y** | The result of the polynomial equation (y = a0 + a1*x). The public output of the circuit. |
|
||||||
|
| **External nullifier** | `keccak256` hash of a string. An identifier that groups signals and can be thought of as a voting booth. Usually a timestamp or time interval in the RLN apps. |
|
||||||
|
| **Internal nullifier** | Poseidon hash of [A1, RLN Identifier]. This field ensures that a user can send only one valid signal per external nullifier without risking being slashed. Public input of the circuit. |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## ZK Circuits specification
|
||||||
|
|
||||||
|
Anonymous signaling with a controlled rate limit is enabled by proving that the user is part of a group which has high barriers to entry (form of stake) and
|
||||||
|
enabling secret reveal if more than 1 unique signal is produced per external nullifier.
|
||||||
|
The membership part is implemented using membership [merkle trees](https://en.wikipedia.org/wiki/Merkle_tree) and merkle proofs,
|
||||||
|
while the secret reveal part is enabled by using the Shamir's Secret Sharing scheme.
|
||||||
|
Essentially the protocol requires the users to generate zero-knowledge proof to be able to send signals and participate in the application.
|
||||||
|
The zero knowledge proof proves that the user is member of a group,
|
||||||
|
but also enforces the user to share part of their secret for each signal in an external nullifier.
|
||||||
|
The external nullifier is usually represented by timestamp or a time interval.
|
||||||
|
It can also be thought of as a voting booth in voting applications.
|
||||||
|
|
||||||
|
The ZK Circuit is implemented using a [Groth-16 ZK-SNARK](https://eprint.iacr.org/2016/260.pdf),
|
||||||
|
using the [circomlib](https://docs.circom.io/) library.
|
||||||
|
|
||||||
|
|
||||||
|
### System parameters
|
||||||
|
|
||||||
|
- `n_levels` - merkle tree depth
|
||||||
|
|
||||||
|
|
||||||
|
### Circuit parameters
|
||||||
|
|
||||||
|
**Public Inputs**
|
||||||
|
- `x`
|
||||||
|
- `external_nullifier`
|
||||||
|
- `rln_identifier`
|
||||||
|
|
||||||
|
**Private Inputs**
|
||||||
|
* `identity_secret_hash`
|
||||||
|
* `path_elements` - rln membership proof component
|
||||||
|
* `identity_path_index` - rln membership proof component
|
||||||
|
|
||||||
|
**Outputs**
|
||||||
|
- `y`
|
||||||
|
- `root` - the rln membership tree root
|
||||||
|
- `internal_nullifier`
|
||||||
|
|
||||||
|
### Hash function
|
||||||
|
|
||||||
|
Canonical [Poseidon hash implementation](https://eprint.iacr.org/2019/458.pdf) is used,
|
||||||
|
as implemented in the [circomlib library](https://github.com/iden3/circomlib/blob/master/circuits/poseidon.circom), according to the Poseidon paper.
|
||||||
|
This Poseidon hash version (canonical implementation) uses the following parameters:
|
||||||
|
|
||||||
|
- `t`: 3
|
||||||
|
- `RF`: 8
|
||||||
|
- `RP`: 57
|
||||||
|
|
||||||
|
### Membership implementation
|
||||||
|
|
||||||
|
For a valid signal, a user's `identity_commitment` (more on identity commitments below) must exist in identity membership tree.
|
||||||
|
Membership is proven by providing a membership proof (witness).
|
||||||
|
The fields from the membership proof required for the verification are:
|
||||||
|
`path_elements` and `identity_path_index`.
|
||||||
|
|
||||||
|
[IncrementalQuinTree](https://github.com/appliedzkp/incrementalquintree) algorithm is used for constructing the Membership merkle tree.
|
||||||
|
The circuits are reused from this repository.
|
||||||
|
You can find out more details about the IncrementalQuinTree algorithm [here](https://ethresear.ch/t/gas-and-circuit-constraint-benchmarks-of-binary-and-quinary-incremental-merkle-trees-using-the-poseidon-hash-function/7446).
|
||||||
|
|
||||||
|
### Slashing and Shamir's Secret Sharing
|
||||||
|
|
||||||
|
Slashing is enabled by using polynomials and [Shamir's Secret sharing](https://en.wikipedia.org/wiki/Shamir%27s_Secret_Sharing).
|
||||||
|
In order to produce a valid proof, `identity_secret_hash` as a private input to the circuit.
|
||||||
|
Then a secret equation is created in the form of:
|
||||||
|
|
||||||
|
```
|
||||||
|
y = a_0 + x*a_1,
|
||||||
|
```
|
||||||
|
|
||||||
|
where `a_0` is the `identity_secret_hash` and `a_1 = hash(a_0, external nullifier)`.
|
||||||
|
Along with the generated proof,
|
||||||
|
the users need to provide a (x, y) share which satisfies the line equation,
|
||||||
|
in order for their proof to be verified.
|
||||||
|
`x` is the hashed signal, while the `y` is the circuit output.
|
||||||
|
With more than one pair of unique shares, anyone can derive `a_0`, the `identity_secret_hash` .
|
||||||
|
The hash of a signal will be evaluation point `x`.
|
||||||
|
So that a member who sends more than one unique signal per `external_nullifier` risks their identity secret being revealed.
|
||||||
|
|
||||||
|
Note that shares used for different external nullifiers and different RLN apps cannot be used to derive the secret key.
|
||||||
|
|
||||||
|
The `rln_identifier` is a random value from a finite field,
|
||||||
|
unique per RLN app,
|
||||||
|
and is used for additional cross-application security - to protect the user secrets being compromised if they use the same credentials accross different RLN apps.
|
||||||
|
If `rln_identifier` is not present,
|
||||||
|
the user uses the same credentials and sends a different message for two different RLN apps using the same `external_nullifier`,
|
||||||
|
then their user signals can be grouped by the `internal_nullifier` which could lead the user's secret revealed.
|
||||||
|
This is because two separate signals under the same `internal_nullifier` can be treated as rate limiting violation.
|
||||||
|
With adding the `rln_identifier` field we obscure the `internal_nullifier`,
|
||||||
|
so this kind of attack can be hardened because we don't have the same `internal_nullifier` anymore.
|
||||||
|
The only kind of attack that is possible is if we have an entity with a global view of all messages,
|
||||||
|
and they try to brute force different combinations of `x` and `y` shares for different `internal_nullifier`s.
|
||||||
|
|
||||||
|
|
||||||
|
## Identity credentials generation
|
||||||
|
|
||||||
|
In order to be able to generate valid proofs, the users need to be part of the identity membership merkle tree.
|
||||||
|
They are part of the identity membership merkle tree if their `identity_commitment` is placed in a leaf in the tree.
|
||||||
|
|
||||||
|
The identity credentials of a user are composed of:
|
||||||
|
|
||||||
|
- `identity_secret`
|
||||||
|
- `identity_secret_hash`
|
||||||
|
- `identity_commitment`
|
||||||
|
|
||||||
|
### `identity_secret`
|
||||||
|
|
||||||
|
The `identity_secret` is generated in the following way:
|
||||||
|
|
||||||
|
```
|
||||||
|
identity_nullifier = random_32_byte_buffer
|
||||||
|
identity_trapdoor = random_32_byte_buffer
|
||||||
|
identity_secret = [identity_nullifier, identity_trapdoor]
|
||||||
|
```
|
||||||
|
|
||||||
|
The same secret should not be used accross different protocols,
|
||||||
|
because revealing the secret at one protocol could break privacy for the user in the other protocols.
|
||||||
|
|
||||||
|
### `identity_secret_hash`
|
||||||
|
|
||||||
|
The `identity_secret_hash` is generated by obtaining a Poseidon hash of the `identity_secret` array:
|
||||||
|
|
||||||
|
```
|
||||||
|
identity_secret_hash = poseidonHash(identity_secret)
|
||||||
|
```
|
||||||
|
|
||||||
|
### `identity_commitment`
|
||||||
|
|
||||||
|
The `identity_commitment` is generated by obtaining a Poseidon hash of the `identity_secret_hash`:
|
||||||
|
|
||||||
|
```
|
||||||
|
identity_commitment = poseidonHash([identity_secret_hash])
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
# Appendix A: Security considerations
|
||||||
|
|
||||||
|
RLN is an experimental and still un-audited technology. This means that the circuits have not been yet audited.
|
||||||
|
Another consideration is the security of the underlying primitives.
|
||||||
|
zk-SNARKS require a trusted setup for generating a prover and verifier keys.
|
||||||
|
The standard for this is to use trusted [Multi-Party Computation (MPC)](https://en.wikipedia.org/wiki/Secure_multi-party_computation) ceremony,
|
||||||
|
which requires two phases.
|
||||||
|
Trusted MPC ceremony has not yet been performed for the RLN circuits.
|
||||||
|
|
||||||
|
# Appendix B: Identity scheme choice
|
||||||
|
|
||||||
|
The hashing scheme used is based on the design decisions which also include the Semaphore circuits.
|
||||||
|
Our goal was to ensure compatibility of the secrets for apps that use Semaphore and
|
||||||
|
RLN circuits while also not compromising on security because of using the same secrets.
|
||||||
|
|
||||||
|
For example let's say there is a voting app that uses Semaphore,
|
||||||
|
and also a chat app that uses RLN.
|
||||||
|
The UX would be better if the users would not need to care about complicated identity management (secrets and commitments) t
|
||||||
|
hey use for each app, and it would be much better if they could use a single id commitment for this.
|
||||||
|
Also in some cases these kind of dependency is required -
|
||||||
|
RLN chat app using Interep as a registry (instead of using financial stake).
|
||||||
|
One potential concern about this interoperability is a slashed user on the RLN app side
|
||||||
|
having their security compromised on the semaphore side apps as well.
|
||||||
|
I.e obtaining the user's secret, anyone would be able to generate valid semaphore proofs as the slashed user.
|
||||||
|
We don't want that, and we should keep user's app specific security threats in the domain of that app alone.
|
||||||
|
|
||||||
|
To achieve the above interoperability UX while preventing the shared app security model
|
||||||
|
(i.e slashing user on an RLN app having impact on Semaphore apps),
|
||||||
|
we had to do the follow in regard the identity secret and identity commitment:
|
||||||
|
|
||||||
|
```
|
||||||
|
identity_secret = [identity_nullifier, identity_trapdoor]
|
||||||
|
identity_secret_hash = poseidonHash(identity_secret)
|
||||||
|
identity_commitment = poseidonHash([identity_secret_hash])
|
||||||
|
```
|
||||||
|
|
||||||
|
Secret components for generating Semaphore proof:
|
||||||
|
|
||||||
|
```
|
||||||
|
identity_nullifier
|
||||||
|
identity_trapdoor
|
||||||
|
```
|
||||||
|
|
||||||
|
Secret components for generting RLN proof:
|
||||||
|
|
||||||
|
```
|
||||||
|
identity_secret_hash
|
||||||
|
```
|
||||||
|
|
||||||
|
When a user is slashed on the RLN app side, their identity secret hash is revealed.
|
||||||
|
However a semaphore proof can't be generated because we do not know the user's nullifier and trapdoor.
|
||||||
|
|
||||||
|
With this design we achieve:
|
||||||
|
|
||||||
|
identity commitment (Semaphore) == identity commitment (RLN)
|
||||||
|
secret (semaphore) != secret (RLN).
|
||||||
|
|
||||||
|
This is the only option we had for the scheme in order to satisfy the properties described above.
|
||||||
|
|
||||||
|
Also for RLN we do a single secret component input for the circuit.
|
||||||
|
Thus we need to hash the secret array (two components) to a secret hash,
|
||||||
|
and we use that as a secret component input.
|
||||||
|
|
||||||
|
# Apendix C: Auxiliary tooling
|
||||||
|
|
||||||
|
There are few additional tools implemented for easier integrations and usage of the RLN protocol.
|
||||||
|
|
||||||
|
[`zk-kit`](https://github.com/appliedzkp/zk-kit) is a typescript library which exposes APIs for identity credentials generation,
|
||||||
|
as well as proof generation.
|
||||||
|
It supports various protocols (`Semaphore`, `RLN`),.
|
||||||
|
|
||||||
|
[`zk-keeper`](https://github.com/akinovak/zk-keeper) is a browser plugin which allows for safe credential storing and proof generation.
|
||||||
|
You can think of MetaMask for ZK-Proofs.
|
||||||
|
It uses `zk-kit` under the hood.
|
||||||
|
|
||||||
|
# Apendix D: Example usage
|
||||||
|
|
||||||
|
The following examples are code snippets using the `zk-kit` library.
|
||||||
|
The examples are written in [typescript](https://www.typescriptlang.org/).
|
||||||
|
|
||||||
|
|
||||||
|
## Generating identity credentials
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { ZkIdentity } from "@zk-kit/identity"
|
||||||
|
|
||||||
|
const identity = new ZkIdentity()
|
||||||
|
const identityCommitment = identity.genIdentityCommitment()
|
||||||
|
const secretHash = identity.getSecretHash()
|
||||||
|
```
|
||||||
|
|
||||||
|
## Generating proof
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { RLN, MerkleProof, FullProof, genSignalHash, generateMerkleProof, genExternalNullifier } from '@zk-kit/protocols'
|
||||||
|
import { ZkIdentity } from '@zk-kit/identity'
|
||||||
|
import { bigintToHex, hexToBigint } from 'bigint-conversion'
|
||||||
|
|
||||||
|
const ZERO_VALUE = BigInt(0);
|
||||||
|
const TREE_DEPTH = 15;
|
||||||
|
const LEAVES_PER_NODE = 2;
|
||||||
|
const LEAVES = [...]; // leaves should be an array of the leaf values of the membership merkle tree
|
||||||
|
// the identity commitment generated below should be present in the LEAVES array
|
||||||
|
|
||||||
|
// this is for illustrative purposes only. The identityCommitment should be present in the LEAVES array above.
|
||||||
|
const identity = new ZkIdentity()
|
||||||
|
const secretHash = identity.getSecretHash()
|
||||||
|
const identityCommitment = identity.genIdentityCommitment()
|
||||||
|
|
||||||
|
const signal = "hey"
|
||||||
|
const signalHash = genSignalHash(signal)
|
||||||
|
const epoch = genExternalNullifier("test-epoch")
|
||||||
|
const rlnIdentifier = RLN.genIdentifier()
|
||||||
|
|
||||||
|
const merkleProof = generateMerkleProof(TREE_DEPTH, ZERO_VALUE, LEAVES_PER_NODE, LEAVES, identityCommitment)
|
||||||
|
const witness = RLN.genWitness(secretHash, merkleProof, epoch, signal, rlnIdentifier)
|
||||||
|
|
||||||
|
const [y, nullifier] = RLN.calculateOutput(secretHash, BigInt(epoch), rlnIdentifier, signalHash)
|
||||||
|
const publicSignals = [y, merkleProof.root, nullifier, signalHash, epoch, rlnIdentifier]
|
||||||
|
|
||||||
|
const wasmFilePath = path.join(zkeyFiles, "rln", "rln.wasm") // path to the WASM compiled circuit
|
||||||
|
const finalZkeyPath = path.join(zkeyFiles, "rln", "rln_final.zkey") // path to the prover key
|
||||||
|
|
||||||
|
const fullProof = await RLN.genProof(witness, wasmFilePath, finalZkeyPath)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Verifiying proof
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { RLN } from '@zk-kit/protocols'
|
||||||
|
|
||||||
|
// Public signal and the proof are received from the proving party
|
||||||
|
// const publicSignals = [y, merkleProof.root, nullifier, signalHash, epoch, rlnIdentifier]
|
||||||
|
// const proof = (await RLN.genProof(witness, wasmFilePath, finalZkeyPath)).proof
|
||||||
|
|
||||||
|
const vkeyPath = path.join(zkeyFiles, "rln", "verification_key.json") // Path to the verifier key
|
||||||
|
const vKey = JSON.parse(fs.readFileSync(vkeyPath, "utf-8")) // The verifier key
|
||||||
|
|
||||||
|
const response = await RLN.verifyProof(vKey, { proof: proof, publicSignals })
|
||||||
|
```
|
||||||
|
|
||||||
|
For more details please visit the [`zk-kit`](https://github.com/appliedzkp/zk-kit) library.
|
||||||
|
|
||||||
|
# Copyright
|
||||||
|
|
||||||
|
Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/)
|
||||||
|
|
||||||
|
# References
|
||||||
|
|
||||||
|
- [1] https://medium.com/privacy-scaling-explorations/rate-limiting-nullifier-a-spam-protection-mechanism-for-anonymous-environments-bbe4006a57d
|
||||||
|
- [2] https://github.com/appliedzkp/zk-kit
|
||||||
|
- [3] https://github.com/akinovak/zk-keeper
|
||||||
|
- [4] https://z.cash/technology/zksnarks/
|
||||||
|
- [5] https://en.wikipedia.org/wiki/Merkle_tree
|
||||||
|
- [6] https://eprint.iacr.org/2016/260.pdf
|
||||||
|
- [7] https://docs.circom.io/
|
||||||
|
- [8] https://eprint.iacr.org/2019/458.pdf
|
||||||
|
- [9] https://github.com/appliedzkp/incrementalquintree
|
||||||
|
- [10] https://ethresear.ch/t/gas-and-circuit-constraint-benchmarks-of-binary-and-quinary-incremental-merkle-trees-using-the-poseidon-hash-function/7446
|
||||||
|
- [11] https://en.wikipedia.org/wiki/Shamir%27s_Secret_Sharing
|
||||||
|
- [12] https://research.nccgroup.com/2020/06/24/security-considerations-of-zk-snark-parameter-multi-party-computation/
|
|
@ -8,6 +8,7 @@ bookMenuLevels: 1
|
||||||
- [24/STATUS-CURATION]({{< relref "/docs/rfcs/24/README.md" >}})
|
- [24/STATUS-CURATION]({{< relref "/docs/rfcs/24/README.md" >}})
|
||||||
- [28/STATUS-FEATURING]({{< relref "/docs/rfcs/28/README.md" >}})
|
- [28/STATUS-FEATURING]({{< relref "/docs/rfcs/28/README.md" >}})
|
||||||
- [31/WAKU2-ENR]({{< relref "/docs/rfcs/31/README.md" >}})
|
- [31/WAKU2-ENR]({{< relref "/docs/rfcs/31/README.md" >}})
|
||||||
|
- [32/RLN-SPEC]({{< relref "/docs/rfcs/32/README.md" >}})
|
||||||
- Draft
|
- Draft
|
||||||
- [1/COSS]({{< relref "/docs/rfcs/1/README.md" >}})
|
- [1/COSS]({{< relref "/docs/rfcs/1/README.md" >}})
|
||||||
- [3/REMOTE-LOG]({{< relref "/docs/rfcs/3/README.md" >}})
|
- [3/REMOTE-LOG]({{< relref "/docs/rfcs/3/README.md" >}})
|
||||||
|
|
Loading…
Reference in New Issue