mirror of https://github.com/vacp2p/rfc.git
fix(32/RLN): update RFC according to design changes (#561)
* fix(rln): update rfc according to design changes * fix(rln): formatting table * fix(rln): format table * fix(rln): typos, distinction epoch/external nullifier, rust code, removed obsolete stuff * fix(rln): wrong capital letter in link * fix(rln): specify internal_nullifier depends on app
This commit is contained in:
parent
2b72238134
commit
66d73a27f4
|
@ -33,7 +33,7 @@ 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.
|
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
|
Usually, the membership requires a financial or social stake which
|
||||||
is beneficial for the prevention of Sybil attacks and double-signaling.
|
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).
|
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).
|
||||||
If a user generates more signals than allowed,
|
If a user generates more signals than allowed,
|
||||||
the user risks being slashed - by revealing his membership secret credentials.
|
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.
|
If the financial stake is put in place, the user also risks his stake being taken.
|
||||||
|
@ -52,7 +52,7 @@ Depending on the application requirements, the registration can be implemented i
|
||||||
- decentralized registrations, by using a smart contract
|
- 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,
|
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.
|
and the users can obtain a Merkle proof proving that they are part of the group.
|
||||||
|
|
||||||
Also depending on the application requirements,
|
Also depending on the application requirements,
|
||||||
usually a financial or social stake is introduced.
|
usually a financial or social stake is introduced.
|
||||||
|
@ -76,7 +76,7 @@ The user's identity is composed of:
|
||||||
```
|
```
|
||||||
|
|
||||||
For registration, the user needs to submit their `identity_commitment` (along with any additional registration requirements) to the registry.
|
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.
|
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.
|
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.
|
The other way around is the users calculating the `leaf_index` themselves upon successful registration.
|
||||||
|
|
||||||
|
@ -90,8 +90,8 @@ they need to generate a ZK-Proof by using the circuit with the specification des
|
||||||
For generating a proof,
|
For generating a proof,
|
||||||
the users need to obtain the required parameters or compute them themselves,
|
the users need to obtain the required parameters or compute them themselves,
|
||||||
depending on the application implementation and client libraries supported by the application.
|
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
|
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.
|
generate a Merkle proof whenever they want to generate a signal.
|
||||||
|
|
||||||
### Implementation notes
|
### Implementation notes
|
||||||
|
|
||||||
|
@ -101,14 +101,18 @@ The signal hash can be generated by hashing the raw signal (or content) using th
|
||||||
|
|
||||||
#### External nullifier
|
#### External nullifier
|
||||||
|
|
||||||
The external nullifier can be generated by hashing a raw string (i.e UNIX timestamp) value using `keccak256`.
|
The external nullifier MUST be computed as the Poseidon hash of the current epoch (e.g. a value equal to or derived from the current UNIX timestamp divided by the epoch length) and the RLN identifier.
|
||||||
|
|
||||||
#### Obtaining merkle proof
|
```
|
||||||
|
external_nullifier = poseidonHash([epoch, rln_identifier])
|
||||||
|
```
|
||||||
|
|
||||||
The merkle proof should be obtained locally or from a trusted third party.
|
#### Obtaining Merkle proof
|
||||||
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 Merkle proof should be obtained locally or from a trusted third party.
|
||||||
The proof (`merkle_proof`) is composed of the following fields:
|
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:
|
||||||
|
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
|
@ -119,8 +123,8 @@ The proof (`merkle_proof`) is composed of the following fields:
|
||||||
```
|
```
|
||||||
|
|
||||||
1. **root** - The root of membership group Merkle tree at the time of publishing the message
|
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
|
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
|
3. **path_elements** - Auxiliary data structure used for storing the path to the leaf - used by the Merkle proof algorithm for verificaton
|
||||||
|
|
||||||
|
|
||||||
#### Generating proof
|
#### Generating proof
|
||||||
|
@ -131,10 +135,10 @@ the user need to submit the following fields to the circuit:
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
identity_secret: identity_secret_hash,
|
identity_secret: identity_secret_hash,
|
||||||
path_elements: merkle_proof.path_elements,
|
path_elements: Merkle_proof.path_elements,
|
||||||
identity_path_index: merkle_proof.indices,
|
identity_path_index: Merkle_proof.indices,
|
||||||
x: signal_hash,
|
x: signal_hash,
|
||||||
external_nullifier: external_nullifier,
|
epoch: epoch,
|
||||||
rln_identifier: rln_identifier
|
rln_identifier: rln_identifier
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -145,13 +149,13 @@ the user need to submit the following fields to the circuit:
|
||||||
The proof output is calculated locally,
|
The proof output is calculated locally,
|
||||||
in order for the required fields for proof verification to be sent along with the proof.
|
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 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 `internal_nullifier` represents a unique fingerprint of a user for a given `epoch` and app.
|
||||||
The following fields are needed for proof output calculation:
|
The following fields are needed for proof output calculation:
|
||||||
|
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
identity_secret_hash: bigint,
|
identity_secret_hash: bigint,
|
||||||
external_nullifier: bigint,
|
epoch: bigint,
|
||||||
rln_identifier: bigint,
|
rln_identifier: bigint,
|
||||||
x: bigint,
|
x: bigint,
|
||||||
}
|
}
|
||||||
|
@ -160,12 +164,14 @@ The following fields are needed for proof output calculation:
|
||||||
The output `[y, internal_nullifier]` is calculated in the following way:
|
The output `[y, internal_nullifier]` is calculated in the following way:
|
||||||
|
|
||||||
```
|
```
|
||||||
a_0 = identity_secret
|
external_nullifier = poseidonHash([epoch, rln_identifier])
|
||||||
|
|
||||||
|
a_0 = identity_secret_hash
|
||||||
a_1 = poseidonHash([a0, external_nullifier])
|
a_1 = poseidonHash([a0, external_nullifier])
|
||||||
|
|
||||||
y = a_0 + x * a_1
|
y = a_0 + x * a_1
|
||||||
|
|
||||||
internal_nullifier = poseidonHash([a_1, rln_identifier])
|
internal_nullifier = poseidonHash([a_1])
|
||||||
```
|
```
|
||||||
|
|
||||||
It relies on the properties of the [Shamir's Secret sharing scheme](https://en.wikipedia.org/wiki/Shamir%27s_Secret_Sharing).
|
It relies on the properties of the [Shamir's Secret sharing scheme](https://en.wikipedia.org/wiki/Shamir%27s_Secret_Sharing).
|
||||||
|
@ -191,8 +197,8 @@ the following fields might be required:
|
||||||
|
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
root: merkle_proof.root,
|
root: Merkle_proof.root,
|
||||||
external_nullifier: external_nullifier
|
epoch: epoch
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -209,9 +215,9 @@ the slashing will be implemented on each user's client.
|
||||||
### Implementation notes
|
### Implementation notes
|
||||||
|
|
||||||
Each user of the protocol (server or otherwise) will need to store metadata for each message received by each user,
|
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`.
|
for the given `epoch`.
|
||||||
The data can be deleted when the `external_nullifier` passes.
|
The data can be deleted when the `epoch` passes.
|
||||||
Storing metadata is required, so that if a user sends more than one unique signal per `external_nullifier`,
|
Storing metadata is required, so that if a user sends more than one unique signal per `epoch`,
|
||||||
they can be slashed and removed from the protocol.
|
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.
|
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.
|
If enough such shares are present, the user's secret can be retreived.
|
||||||
|
@ -238,7 +244,7 @@ The output message verification consists of the following steps:
|
||||||
- spam verification
|
- spam verification
|
||||||
|
|
||||||
**1. `external_nullifier` correctness**
|
**1. `external_nullifier` correctness**
|
||||||
Upon received `output_message`, first the `external_nullifier` field is checked,
|
Upon received `output_message`, first the `epoch` and `rln_identifier` fields are checked,
|
||||||
to ensure that the message matches the current `external_nullifier`.
|
to ensure that the message matches the current `external_nullifier`.
|
||||||
If the `external_nullifier` is correct the verification continues, otherwise, the message is discarded.
|
If the `external_nullifier` is correct the verification continues, otherwise, the message is discarded.
|
||||||
|
|
||||||
|
@ -256,10 +262,10 @@ The `zk_proof` should be verified by providing the `zk_proof` field to the circu
|
||||||
```
|
```
|
||||||
[
|
[
|
||||||
y,
|
y,
|
||||||
merkle_proof.root,
|
Merkle_proof.root,
|
||||||
internal_nullifier,
|
internal_nullifier,
|
||||||
signal_hash,
|
x, # signal_hash
|
||||||
external_nullifier,
|
epoch,
|
||||||
rln_identifier
|
rln_identifier
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
@ -280,13 +286,13 @@ The secret can be retreived by the properties of the Shamir's secret sharing sch
|
||||||
In particular the secret (`a_0`) can be retrieved by computing [Lagrange polynomials](https://en.wikipedia.org/wiki/Lagrange_polynomial).
|
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,
|
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`).
|
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.
|
Additionally, depending on the application the `identity_secret_hash` can be used for taking the user's provided stake.
|
||||||
|
|
||||||
# Technical overview
|
# Technical overview
|
||||||
|
|
||||||
The main RLN construct is implemented using a [ZK-SNARK](https://z.cash/technology/zksnarks/) circuit.
|
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,
|
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.
|
which together with the ZK-SNARK circuit enable the above mentioned features.
|
||||||
|
|
||||||
|
|
||||||
|
@ -302,8 +308,8 @@ which together with the ZK-SNARK circuit enable the above mentioned features.
|
||||||
| **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 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. |
|
| **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** | 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. |
|
| **Signal hash** | Keccak256 hash of the signal modulo circuit's field characteristic, 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 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 from being compromised when signals are being generated with the same credentials in 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. |
|
| **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. |
|
| **Merkle proof** | Proof that a user is member of the RLN membership tree. |
|
||||||
|
|
||||||
|
@ -316,8 +322,8 @@ which together with the ZK-SNARK circuit enable the above mentioned features.
|
||||||
| **A0** | The identity secret hash. |
|
| **A0** | The identity secret hash. |
|
||||||
| **A1** | Poseidon hash of [A0, External nullifier] (see about External nullifier below). |
|
| **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. |
|
| **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. |
|
| **External nullifier** | Poseidon hash of [Epoch, RLN Identifier]. An identifier that groups signals and can be thought of as a voting booth. |
|
||||||
| **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. |
|
| **Internal nullifier** | Poseidon hash of [A1]. This field ensures that a user can send only one valid signal per external nullifier without risking being slashed. Public input of the circuit. |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -325,7 +331,7 @@ which together with the ZK-SNARK circuit enable the above mentioned features.
|
||||||
|
|
||||||
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
|
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.
|
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,
|
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.
|
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.
|
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,
|
The zero knowledge proof proves that the user is member of a group,
|
||||||
|
@ -339,14 +345,14 @@ using the [circomlib](https://docs.circom.io/) library.
|
||||||
|
|
||||||
### System parameters
|
### System parameters
|
||||||
|
|
||||||
- `n_levels` - merkle tree depth
|
- `n_levels` - Merkle tree depth
|
||||||
|
|
||||||
|
|
||||||
### Circuit parameters
|
### Circuit parameters
|
||||||
|
|
||||||
**Public Inputs**
|
**Public Inputs**
|
||||||
- `x`
|
- `x`
|
||||||
- `external_nullifier`
|
- `epoch`
|
||||||
- `rln_identifier`
|
- `rln_identifier`
|
||||||
|
|
||||||
**Private Inputs**
|
**Private Inputs**
|
||||||
|
@ -365,9 +371,17 @@ Canonical [Poseidon hash implementation](https://eprint.iacr.org/2019/458.pdf) i
|
||||||
as implemented in the [circomlib library](https://github.com/iden3/circomlib/blob/master/circuits/poseidon.circom), according to the Poseidon paper.
|
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:
|
This Poseidon hash version (canonical implementation) uses the following parameters:
|
||||||
|
|
||||||
- `t`: 3
|
| Hash inputs | `t` | `RF` | `RP`|
|
||||||
- `RF`: 8
|
|:---:|:---:|:---:|:---:|
|
||||||
- `RP`: 57
|
|1 | 2 | 8 | 56|
|
||||||
|
|2 | 3 | 8 | 57|
|
||||||
|
|3 | 4 | 8 | 56|
|
||||||
|
|4 | 5 | 8 | 60|
|
||||||
|
|5 | 6 | 8 | 60|
|
||||||
|
|6 | 7 | 8 | 63|
|
||||||
|
|7 | 8 | 8 | 64|
|
||||||
|
|8 | 9 | 8 | 63|
|
||||||
|
|
||||||
|
|
||||||
### Membership implementation
|
### Membership implementation
|
||||||
|
|
||||||
|
@ -376,9 +390,9 @@ Membership is proven by providing a membership proof (witness).
|
||||||
The fields from the membership proof required for the verification are:
|
The fields from the membership proof required for the verification are:
|
||||||
`path_elements` and `identity_path_index`.
|
`path_elements` and `identity_path_index`.
|
||||||
|
|
||||||
[IncrementalQuinTree](https://github.com/appliedzkp/incrementalquintree) algorithm is used for constructing the Membership merkle tree.
|
[IncrementalQuinTree](https://github.com/appliedzkp/incrementalquintree) algorithm is used for constructing the Membership Merkle tree.
|
||||||
The circuits are reused from this repository.
|
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).
|
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 and Shamir's Secret Sharing
|
||||||
|
|
||||||
|
@ -387,19 +401,21 @@ In order to produce a valid proof, `identity_secret_hash` as a private input to
|
||||||
Then a secret equation is created in the form of:
|
Then a secret equation is created in the form of:
|
||||||
|
|
||||||
```
|
```
|
||||||
y = a_0 + x*a_1,
|
y = a_0 + x * a_1,
|
||||||
```
|
```
|
||||||
|
|
||||||
where `a_0` is the `identity_secret_hash` and `a_1 = hash(a_0, external nullifier)`.
|
where `a_0` is the `identity_secret_hash` and `a_1 = hash(a_0, external nullifier)`.
|
||||||
Along with the generated proof,
|
Along with the generated proof,
|
||||||
the users need to provide a (x, y) share which satisfies the line equation,
|
the users need to provide a `(x, y)` share which satisfies the line equation,
|
||||||
in order for their proof to be verified.
|
in order for their proof to be verified.
|
||||||
`x` is the hashed signal, while the `y` is the circuit output.
|
`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` .
|
With more than one pair of unique shares, anyone can derive `a_0`, i.e. the `identity_secret_hash` .
|
||||||
The hash of a signal will be evaluation point `x`.
|
The hash of a signal will be the evaluation point `x`.
|
||||||
So that a member who sends more than one unique signal per `external_nullifier` risks their identity secret being revealed.
|
In this way, 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.
|
Note that shares used in different epochs and different RLN apps cannot be used to derive the identity secret hash.
|
||||||
|
|
||||||
|
Thanks to the `external_nullifier` definition, also shares computed from same secret within same epoch but in different RLN apps cannot be used to derive the identity secret hash.
|
||||||
|
|
||||||
The `rln_identifier` is a random value from a finite field,
|
The `rln_identifier` is a random value from a finite field,
|
||||||
unique per RLN app,
|
unique per RLN app,
|
||||||
|
@ -410,14 +426,11 @@ then their user signals can be grouped by the `internal_nullifier` which could l
|
||||||
This is because two separate signals under the same `internal_nullifier` can be treated as rate limiting violation.
|
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`,
|
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.
|
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
|
## Identity credentials generation
|
||||||
|
|
||||||
In order to be able to generate valid proofs, the users need to be part of the identity membership merkle tree.
|
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.
|
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:
|
The identity credentials of a user are composed of:
|
||||||
|
|
||||||
|
@ -518,85 +531,108 @@ 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,
|
Thus we need to hash the secret array (two components) to a secret hash,
|
||||||
and we use that as a secret component input.
|
and we use that as a secret component input.
|
||||||
|
|
||||||
# Apendix C: Auxiliary tooling
|
# Appendix C: Auxiliary tooling
|
||||||
|
|
||||||
There are few additional tools implemented for easier integrations and usage of the RLN protocol.
|
There are few additional tools implemented for easier integrations and usage of the RLN protocol.
|
||||||
|
|
||||||
|
[`zerokit`](https://github.com/vacp2p/zerokit) is a set of Zero Knowledge modules, written in Rust and designed to be used in many different environments.
|
||||||
|
Among different modules, it supports `Semaphore` and `RLN`.
|
||||||
|
|
||||||
[`zk-kit`](https://github.com/appliedzkp/zk-kit) is a typescript library which exposes APIs for identity credentials generation,
|
[`zk-kit`](https://github.com/appliedzkp/zk-kit) is a typescript library which exposes APIs for identity credentials generation,
|
||||||
as well as proof generation.
|
as well as proof generation.
|
||||||
It supports various protocols (`Semaphore`, `RLN`),.
|
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.
|
[`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.
|
You can think of MetaMask for ZK-Proofs.
|
||||||
It uses `zk-kit` under the hood.
|
It uses `zk-kit` under the hood.
|
||||||
|
|
||||||
# Apendix D: Example usage
|
# Appendix D: Example usage
|
||||||
|
|
||||||
The following examples are code snippets using the `zk-kit` library.
|
The following examples are code snippets using the `zerokit` RLN module.
|
||||||
The examples are written in [typescript](https://www.typescriptlang.org/).
|
The examples are written in [rust](https://www.rust-lang.org/).
|
||||||
|
|
||||||
|
## Creating a RLN object
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use rln::protocol::*;
|
||||||
|
use rln::public::*;
|
||||||
|
use std::io::Cursor;
|
||||||
|
|
||||||
|
// We set the RLN parameters:
|
||||||
|
// - the tree height;
|
||||||
|
// - the circuit resource folder (requires a trailing "/").
|
||||||
|
let tree_height = 20;
|
||||||
|
let resources = Cursor::new("../zerokit/rln/resources/tree_height_20/");
|
||||||
|
|
||||||
|
// We create a new RLN instance
|
||||||
|
let mut rln = RLN::new(tree_height, resources);
|
||||||
|
```
|
||||||
|
|
||||||
## Generating identity credentials
|
## Generating identity credentials
|
||||||
|
|
||||||
```typescript
|
```rust
|
||||||
import { ZkIdentity } from "@zk-kit/identity"
|
// We generate an identity tuple
|
||||||
|
let mut buffer = Cursor::new(Vec::<u8>::new());
|
||||||
|
rln.extended_key_gen(&mut buffer).unwrap();
|
||||||
|
|
||||||
const identity = new ZkIdentity()
|
// We deserialize the keygen output to obtain
|
||||||
const identityCommitment = identity.genIdentityCommitment()
|
// the identiy_secret and id_commitment
|
||||||
const secretHash = identity.getSecretHash()
|
let (identity_trapdoor, identity_nullifier, identity_secret_hash, id_commitment) = deserialize_identity_tuple(buffer.into_inner());
|
||||||
|
```
|
||||||
|
|
||||||
|
## Adding ID commitment to the RLN Merkle tree
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// We define the tree index where id_commitment will be added
|
||||||
|
let id_index = 10;
|
||||||
|
|
||||||
|
// We serialize id_commitment and pass it to set_leaf
|
||||||
|
let mut buffer = Cursor::new(serialize_field_element(id_commitment));
|
||||||
|
rln.set_leaf(id_index, &mut buffer).unwrap();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Setting epoch and signal
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// We generate epoch from a date seed and we ensure is
|
||||||
|
// mapped to a field element by hashing-to-field its content
|
||||||
|
let epoch = hash_to_field(b"Today at noon, this year");
|
||||||
|
|
||||||
|
// We set our signal
|
||||||
|
let signal = b"RLN is awesome";
|
||||||
```
|
```
|
||||||
|
|
||||||
## Generating proof
|
## Generating proof
|
||||||
|
|
||||||
```typescript
|
```rust
|
||||||
import { RLN, MerkleProof, FullProof, genSignalHash, generateMerkleProof, genExternalNullifier } from '@zk-kit/protocols'
|
// We prepare input to the proof generation routine
|
||||||
import { ZkIdentity } from '@zk-kit/identity'
|
let proof_input = prepare_prove_input(identity_secret, id_index, epoch, signal);
|
||||||
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.
|
// We generate a RLN proof for proof_input
|
||||||
const identity = new ZkIdentity()
|
let mut in_buffer = Cursor::new(proof_input);
|
||||||
const secretHash = identity.getSecretHash()
|
let mut out_buffer = Cursor::new(Vec::<u8>::new());
|
||||||
const identityCommitment = identity.genIdentityCommitment()
|
rln.generate_rln_proof(&mut in_buffer, &mut out_buffer)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
const signal = "hey"
|
// We get the public outputs returned by the circuit evaluation
|
||||||
const signalHash = genSignalHash(signal)
|
let proof_data = out_buffer.into_inner();
|
||||||
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
|
## Verifiying proof
|
||||||
|
|
||||||
```typescript
|
```rust
|
||||||
import { RLN } from '@zk-kit/protocols'
|
// We prepare input to the proof verification routine
|
||||||
|
let verify_data = prepare_verify_input(proof_data, signal);
|
||||||
// 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
|
// We verify the zk-proof against the provided proof values
|
||||||
const vKey = JSON.parse(fs.readFileSync(vkeyPath, "utf-8")) // The verifier key
|
let mut in_buffer = Cursor::new(verify_data);
|
||||||
|
let verified = rln.verify(&mut in_buffer).unwrap();
|
||||||
|
|
||||||
const response = await RLN.verifyProof(vKey, { proof: proof, publicSignals })
|
// We ensure the proof is valid
|
||||||
|
assert!(verified);
|
||||||
```
|
```
|
||||||
|
|
||||||
For more details please visit the [`zk-kit`](https://github.com/appliedzkp/zk-kit) library.
|
For more details please visit the [`zerokit`](https://github.com/vacp2p/zerokit) library.
|
||||||
|
|
||||||
# Copyright
|
# Copyright
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue