mirror of
https://github.com/Rate-Limiting-Nullifier/rln-docs.git
synced 2026-01-08 23:08:09 -05:00
chore: add rln in details explanation
This commit is contained in:
@@ -40,7 +40,7 @@ Basically, it says how to calculate Poseidon hash, e.g. for two inputs \\(input1
|
||||
|
||||
### RangeCheck
|
||||
|
||||
**RangeCheck(LIMIT_BIT_SIZE)** template used for range check, e.g. \\(x \le y \le z\\).
|
||||
**RangeCheck(LIMIT_BIT_SIZE)** template used for range check.
|
||||
|
||||
**Parameters**:
|
||||
* \\(LIMIT\\_BIT\\_SIZE\\) - maximum bit size of numbers that are used in range check, f.e. for the \\(LIMIT\\_BIT\\_SIZE = 16\\), input numbers allowed to be in the interval \\([0, 65536)\\).
|
||||
|
||||
@@ -1,57 +1,76 @@
|
||||
# Technical side of RLN
|
||||
|
||||
*This topic is a less strict version of specifications. If you want a more formal description, you can find specs in the [references](./references.md). Also, if you're unfamiliar with Shamir's Secret Sharing scheme, you can [read it here](./sss.md).*
|
||||
*If you're unfamiliar with Shamir's Secret Sharing scheme, you can [read it here](./sss.md).*
|
||||
|
||||
___
|
||||
|
||||
|
||||

|
||||
<p align="center">
|
||||
<i>Under the hood: The <b>RLN</b> Circom Circuit</i>
|
||||
</p>
|
||||
|
||||
**RLN** consists of three parts:
|
||||
* User registration
|
||||
* User interaction (signaling)
|
||||
* User removal (slashing) - additional part
|
||||
* User removal/withdrawal (slashing) - additional part
|
||||
|
||||
Well, let's discuss them.
|
||||
|
||||
## User registration
|
||||
The first part of **RLN** is registration. There is nothing special in **RLN** registration; it's almost the same process as in other protocols/apps with anonymous environments: we need to create a Merkle Tree, and every participant must submit a `commitment` and place it in the Merkle Tree, and after that to interact with the app every participant will create a zkProof's, that they are a *member of the tree* (we use an *Incremental Merkle Tree*, as it more *GAS efficient*).
|
||||
|
||||
So, each member generates a secret key, denoted by \\(a_0\\). Identity commitment \\(q\\) is the hash (Poseidon) of the secret key: \\(q = Poseidon(a_0)\\).
|
||||
The first part of **RLN** is registration. There is nothing special in **RLN** registration; it's almost the same process as in other protocols/apps with anonymous environments: we need to create a Merkle tree, and every participant must submit an \\(identityCommitment\\) and place it in the Merkle Tree, and after that to interact with the app every participant will create a zk proof's, that they are a *member of the tree*.
|
||||
|
||||
So, each member generates a secret key, denoted by \\(a_0\\). Identity commitment is the Poseidon hash of the secret key:
|
||||
\\[identityCommitment = Poseidon(a_0)\\]
|
||||
|
||||
**RLN** wouldn't work if there were no punishment for spam; that's why to become a member, a user has to register and provide something at stake. So, whoever has our \\(a_0\\) can "slash" us.
|
||||
|
||||
The slight difference is that we must enable a *secret sharing* scheme (to split the `commitment` into parts). We need to come up with a polynomial. For simplicity we use linear polynomial (e.g. \\(f(x) = kx + b\\). Therefore, with two points, we can reconstruct the polynomial and recover the secret.
|
||||
|
||||
Our polynomial will be: \\(A(x) = a_1 * x + a_0\\), where \\(a_1 = Poseidon(a_0, external\\_nullifier)\\).
|
||||
The meaning of \\(external\\_nullifier\\) is described below.
|
||||
The slight difference is that we must enable a *secret sharing* scheme (to split the \\(a_0\\) into parts). We need to come up with a polynomial. For simplicity we use linear polynomial - \\(f(x) = kx + b\\). Therefore, with two points we can reconstruct the polynomial and recover the secret.
|
||||
|
||||
## Signalling
|
||||
Now that the user is registered, he wants to interact with the system. Imagine that the system is an *anonymous chat* and the interaction is the sending of messages.
|
||||
So, to send a message user have to come up with *share* - the point \\((x, y)\\) on her polynomial.
|
||||
We denote: \\(x = Poseidon(message), y = A(x)\\).
|
||||
|
||||
Thus, if the same epoch user sends more than one message, their polynomial and, therefore, their secret (\\(a_0\\)) can be recovered.
|
||||
Now that users are registered, they want to interact with the system. Imagine that the system is an *anonymous chat* and the interaction is the sending of messages.
|
||||
So, to send a message users have to come up with *share* - the point \\((x, y)\\) on their polynomial.
|
||||
We denote:
|
||||
\\[x = Poseidon(message)\\]
|
||||
\\[y = A(x)\\]
|
||||
|
||||
Of course, we somehow must prove that our *share* = \\((x, y)\\) is valid (that this is really a point on our `polynomial = A(x)`), as well as we must prove other things are valid too, that's why we use zkSNARK. An explanation of the zk-circuits can be found in the next topic.
|
||||
Thus, if during the same epoch user sends more than one message, their polynomial and, therefore, their secret - \\(a_0\\) can be recovered.
|
||||
|
||||
Of course, we somehow must prove that our *share* = \\((x, y)\\) is valid (that this is really a point on our polynomial), as well as we must prove other things are valid too, that's why we use zkSNARK.
|
||||
|
||||
### Range check trick and resulting polynomial
|
||||
|
||||
As it was said - we use first-degree polynomial for simplicity of the protocol and circuits. But you may ask - does it limit the system to only one message per epoch? Yes, and it's really undesirable, cause we want to have higher rate-limits. What we can do is to use polynomial of higher degree, but we also can do a clever trick: we can introduce an additional circuit input: \\(messageId\\), that will serve us as a simple counter.
|
||||
|
||||
Let's say we make \\(messageLimit = n\\). Then for each message we send (during the same epoch) - we also need an additional input \\(messageId\\). This value will be range checked that it's less than \\(messageLimit\\) (to be more precise: \\(0 \le messageId < messageLimit\\)). And our polynomial will depend on this input as well, so that for each message - different \\(messageId\\) will be used, therefore the resulting polynomials will be different.
|
||||
|
||||
Our polynomial will be:
|
||||
\\[A(x) = a_1 * x + a_0\\]
|
||||
\\[a_1 = Poseidon(a_0, externalNullifier, messageId)\\]
|
||||
|
||||
The meaning of \\(externalNullifier\\) is described [below](#nullifiers).
|
||||
|
||||
It's sound, because if we use the same \\(messageId\\) twice - we'll share two different points from our first-degree polynomial, therefore it'll be possible to recover the secret key. And at the same time user also cannot input \\(messageId\\) value that's bigger than the \\(messageLimit\\), because of the range check.
|
||||
|
||||
### Different rate-limits for different users
|
||||
|
||||
It's also may be desired to have different rate-limits for different users, for example based on their stake amount. We can also achieve that by calculating \\(userMessageLimit\\) value and then deriving \\(rateCommitment\\):
|
||||
\\[rateCommitment = Poseidon(identityCommitment, rateCommitment)\\]
|
||||
during the registration phase.
|
||||
|
||||
And it's the \\(rateCommitment\\) values that are stored in the membership Merkle tree.
|
||||
|
||||
Therefore, in the circuit users will have to prove that the:
|
||||
\\[identityCommitment = Poseidon(identitySecret)\\]
|
||||
\\[rateCommitment = Poseidon(identityCommitment, userMessageLimit)\\]
|
||||
\\[0 \le messageId < userMessageLimit\\]
|
||||
|
||||
We use the scheme with \\(userMessageLimit\\) as it more general, though it's not necessarily to have different rate-limits for different users. We can enforce that the users will have the same rate-limit during the registration. For more information on that read [smart-contract explanation](./smart_contract.md#registration).
|
||||
|
||||
## Slashing
|
||||
As it's been said, if a user sends more than one message, everyone else will be able to recover his secret, slash them and take their stake.
|
||||
As it's been said, if a user sends more than one message, everyone else will be able to recover his secret, slash them and take their stake. Based on [\\(nullifier\\)](#nullifiers) we can find the spammer, and therefore use polynomial interpolation using their shares. More information can be found in [smart-contract explanation](./smart_contract.md).
|
||||
|
||||
## Nullifiers
|
||||
There are also \\(internal\\_nullifier\\) and \\(external\\_nullifier\\), which can be found in the **RLN** protocol/circuits.
|
||||
There are also \\(nullifier\\) and \\(externalNullifier\\), which can be found in the **RLN** protocol/circuits.
|
||||
|
||||
\\(external\\_nullifier = Poseidon(epoch, rln\\_identifier)\\), where \\(rln\\_identifier\\) is a random finite field value, unique per RLN app.
|
||||
\\(externalNullifier = Poseidon(epoch, rln\\_identifier)\\), where \\(rln\\_identifier\\) is a random finite field value, unique per RLN app.
|
||||
|
||||
The \\(external\\_nullifier\\) is required so that the user can securely use the same private key \\(a_0\\) across different **RLN** apps - in different applications (and in different eras) with the same secret key, the user will have different values of the coefficient \\(a_1\\).
|
||||
The \\(externalNullifier\\) is required so that the user can securely use the same private key \\(a_0\\) across different **RLN** apps - in different applications (and in different eras) with the same secret key, the user will have different values of the coefficient \\(a_1\\).
|
||||
|
||||
Now, imagine there are a lot of users sending messages, and after each received message, we need to check if any member can be slashed. To do this, we can use all combinations of received *shares* and try to recover the polynomial, but this is a naive and non-optimal approach. Suppose we have a mechanism that will tell us about the connection between a person and their messages while not revealing their identity. In that case, we can solve this without brute-forcing all possibilities by using a public \\(internal\\_nullifier = Poseidon(a_1)\\), so if a user sends more than one message, it will be immediately visible to everyone.
|
||||
|
||||
## Some important notes
|
||||
|
||||
Also, in our example (and [zk-chat](https://github.com/njofce/zk-chat) implementation), we use linear polynomial, but [SSS](sss.md) allows us to use various degree polynomials; therefore we can implement a protocol, where more than one signal (message) can be sent in per epoch.
|
||||
|
||||
To learn more, check out the [specification](https://hackmd.io/7GR5Vi28Rz2EpEmLK0E0Aw?view); there are also [circuits](https://github.com/privacy-scaling-explorations/rln/tree/master/circuits) implemented for various degree polynomials too.
|
||||
Now, imagine there are a lot of users sending messages, and after each received message, we need to check if any member can be slashed. To do this, we can use all combinations of received *shares* and try to recover the polynomial, but this is a naive and non-optimal approach. Suppose we have a mechanism that will tell us about the connection between a person and their messages while not revealing their identity. In that case, we can solve this without brute-forcing all possibilities by using a public \\(nullifier = Poseidon(a_1)\\), so if a user sends more than one message, it will be immediately visible to everyone.
|
||||
|
||||
@@ -34,10 +34,12 @@ Fees play important role in the economical side of the protocol. They prevent se
|
||||
### Registration
|
||||
|
||||
Function **register** is used for registration. It receives \\(identityCommitment\\) as the argument as well as stake amount. Then it calculate messageLimit, based on the stake amount:
|
||||
\\[messageLimit = amount / MINIMAL\\_DEPOSIT\\]
|
||||
\\[messageLimit = amount / MINIMAL\\_DEPOSIT\\].
|
||||
|
||||
Then it stores the \\(identityCommitment\\) in the users' set, and emits an event. Client nodes that need to listen to the event, cause based on that they calculate \\(rateCommitment\\) value. It's important part, because by doing that we don't need to calculate Poseidon hash on-chain, that's not cheap.
|
||||
|
||||
It's also important to understand that we may not want to have different rate-limits for different users. We can enforce that by setting the hard value of stake amount.
|
||||
|
||||
### Withdraw | Slashing
|
||||
|
||||
There are two terms: *withdraw* and *slash* used in the RLN protocol. Let's define them:
|
||||
|
||||
Reference in New Issue
Block a user