Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refinements of RLN on mainnet spec #34

Merged
merged 20 commits into from
Oct 15, 2024
Merged
Changes from 13 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
f15b30a
clarify terminology: epoch length vs period
s-tikhomirov Aug 30, 2024
586d70b
clarify parameter update implications for existing memberships
s-tikhomirov Aug 30, 2024
818ce98
clarify definition of tx sender as membership keeper
s-tikhomirov Aug 30, 2024
82b4bc7
clarify handling of call chains from EOAs
s-tikhomirov Aug 30, 2024
8d7a41b
clarify slot reuse: from spec-level to impl-level decision
s-tikhomirov Aug 30, 2024
2a1b433
clarify membership unit price definition
s-tikhomirov Sep 9, 2024
dfe5e53
separate "membership set" and "Merkle tree" as its impl in the spec
s-tikhomirov Sep 25, 2024
5ef1d30
separate withdraw and erase (in line with the implementation)
s-tikhomirov Sep 25, 2024
258a07e
clarify functionality table
s-tikhomirov Sep 25, 2024
0f9d680
edit membership registration section for clarity
s-tikhomirov Sep 25, 2024
fd91933
edit for clarity
s-tikhomirov Sep 25, 2024
3a1aabb
clarify types of state transitions in the diagram
s-tikhomirov Sep 25, 2024
ca0c416
rename "expiration term" to "active state duration"
s-tikhomirov Sep 25, 2024
eb4dfd6
clarify requirements: A>0, G >=0
s-tikhomirov Sep 27, 2024
cc1a917
fix: add semantic break
s-tikhomirov Sep 30, 2024
090c65c
make spec less strict on overwriting memberships
s-tikhomirov Sep 30, 2024
7c29616
retain remaining grace period time on membership extention
s-tikhomirov Sep 30, 2024
66945c7
unify holder and keeper terms - only use holder (fixes #41)
s-tikhomirov Oct 1, 2024
b8ea666
unify overwrite / reuse terminology
s-tikhomirov Oct 1, 2024
80ba2bd
define period boundaries: start inclusive, end exclusive
s-tikhomirov Oct 3, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
160 changes: 106 additions & 54 deletions standards/core/rln-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL

Rate-Limiting Nullifier (RLN) is a Zero-Knowledge (ZK) based gadget used for privacy-preserving rate limiting in Waku.
The RLN smart contract (referred to as "the contract" hereinafter) is the central component of the RLN architecture.
The contract stores the RLN tree, which contains all current memberships.
The contract stores the membership set, which contains all current memberships.
Users interact with the contract to manage their memberships
and obtain the necessary data for proof generation and verification.

Expand All @@ -39,33 +39,45 @@ For the full specification of RLN Relay, see See [17/WAKU2-RLN-RELAY](https://gi

## Contract overview

The contract MUST provide the following functionalities:
Let us define membership-related functionalities (hereinafter, functionalities) as follows:
- register a membership;
- extend a membership;
- erase a membership;
- withdraw a deposit.

The contract MUST provide the functionalities.

A membership _holder_ is the entity that controls the secret associated with the respective RLN commitment.
A membership _keeper_ is the entity that controls the Ethereum address used to register that membership.
A membership _keeper_ is the sender (`msg.sender` in Solidity semantics) of the transaction that registered that membership.
The holder and the keeper MAY be different entities for the same membership.
When authorizing membership-related requests,
the contract SHOULD distinguish between the keeper and non-keepers,
and MAY also use additional criteria.

The contract MUST support transactions sent directly from externally-owned accounts (EOA).
The contract MAY support transactions sent via a chain of contract calls,
in which case the last contract in the call chain MAY be designated as the membership keeper.
The contract MAY also support meta-transactions sent via paymasters or relayers,
which MAY require additional authentication-related logic.

Contract parameters and their RECOMMENDED values for the initial mainnet deployment are as follows:

| Parameter | Symbol | Value | Units |
| ------------------------------------------------------- | --------- | -------- | -------------------- |
| Epoch length | `epoch` | `10` | minutes |
| Maximum total rate limit of all memberships in the tree | `R_{max}` | `160000` | messages per `epoch` |
| Minimum rate limit of one membership | `r_{min}` | `20` | messages per `epoch` |
| Maximum rate limit of one membership | `r_{max}` | `600` | messages per `epoch` |
| Membership price for `1` message per epoch | `p_u` | `0.05` | `USD` |
| Membership expiration term | `T` | `180` | days |
| Membership grace period | `G` | `30` | days |
| Accepted tokens | | `DAI` | |
| Parameter | Symbol | Value | Units |
| ----------------------------------------------------------------- | --------- | -------- | ------------------ |
| Epoch length | `t_{ep}` | `600` | seconds |
| Maximum total rate limit of all memberships in the membership set | `R_{max}` | `160000` | messages per epoch |
| Minimum rate limit of one membership | `r_{min}` | `20` | messages per epoch |
| Maximum rate limit of one membership | `r_{max}` | `600` | messages per epoch |
| Membership active state duration | `A` | `180` | days |
| Membership grace period duration | `G` | `30` | days |
| Membership price for `1` message per epoch for period `A` | `p_u` | `0.05` | `USD` |
| Accepted tokens | | `DAI` | |

The pricing function SHOULD be linear in the rate limit per epoch.

Note: epoch length means the same as `period` as defined in [17/WAKU2-RLN-RELAY](https://github.com/vacp2p/rfc-index/blob/main/waku/standards/core/17/rln-relay.md).
This specification uses the term "epoch length" instead of "period" to avoid confusion with "grace period".

## Membership lifecycle

Any existing membership MUST always be in exactly one of the following states:
Expand All @@ -79,26 +91,33 @@ Any existing membership MUST always be in exactly one of the following states:
graph TD;
NonExistent --> |"register"| Active;
Active -.-> |"time T passed"| GracePeriod;
GracePeriod --> |"extend"| Active;
GracePeriod ==> |"extend"| Active;
GracePeriod -.-> |"time G passed"| Expired;
GracePeriod --> |"withdraw"| Erased;
Expired --> |"withdraw"| Erased;
Expired --> |"another membership reuses slot"| ErasedAwaitsWithdrawal;
ErasedAwaitsWithdrawal --> |"withdraw"| Erased;
GracePeriod ==> |"erase"| ErasedAwaitsWithdrawal;
Expired --> |"erase"| ErasedAwaitsWithdrawal;
Expired --> |"overwritten by a new membership"| ErasedAwaitsWithdrawal;
ErasedAwaitsWithdrawal ==> |"withdraw"| Erased;

```

State updates triggered by a transaction (e.g., from _GracePeriod_ to _Active_ as a result of `extend`) MUST be applied immediately.
State updates defined by time progression (e.g., from _GracePeriod_ to _Expired_ after time `G`) MAY be applied lazily.
Different line types denote the types of state transitions:

| Line type | Triggered by | Requirements |
| -------------- | ---------------- | ------------------------------------------------------------------------------------ |
| Thick (`==`) | Transaction | MUST be initiable by the membership keeper and MUST NOT be initiable by other users. |
| Thin (`--`) | Transaction | MAY be initiable by any user. |
| Dotted (`-.-`) | Time progression | MAY be applied lazily. |

Transaction-triggered state transitions MUST be applied immediately.

When handling a membership-specific transaction, the contract MUST:
- check whether the state of the involved membership is up-to-date;
- if necessary, update the membership state;
- process the transaction in accordance with the updated membership state.

Memberships MUST be included in the RLN tree according to the following table:
Memberships MUST be included in the membership set according to the following table:

| State | Included in the RLN tree |
| State | Included in the membership set |
| ------------------------ | ------------------------ |
| _Active_ | Yes |
| _GracePeriod_ | Yes |
Expand All @@ -115,34 +134,50 @@ A user MAY use one Waku node[^1] to manage multiple memberships.

## Contract functionalities

Availability of membership-specific functionalities[^2] MUST be as follows:
Availability of functionalities[^2] MUST be as follows:

| | Active | GracePeriod | Expired | ErasedAwaitsWithdrawal | Erased |
| --------------------- | ------ | ----------- | ------- | ---------------------- | ------ |
| Send a message | Yes | Yes | Yes | No | No |
| Extend the membership | No | Yes | No | No | No |
| Withdraw the deposit | No | Yes | Yes | Yes | No |
| | Active | GracePeriod | Expired | ErasedAwaitsWithdrawal | Erased |
| --------------------- | ------ | ----------------- | ------- | ---------------------- | ------ |
| Extend the membership | No | Yes (keeper only) | No | No | No |
| Erase the membership | No | Yes (keeper only) | Yes | No | No |
| Withdraw the deposit | No | No | No | Yes (keeper only) | No |

[^2]: Sending a message is included here for completeness, although it is part of the RLN Relay protocol and not the contract.
[^2]: Sending a message is not present in this table because it is part of the RLN Relay protocol and not the contract. For completeness, we note that the membership holder MUST be able to send a message if their membership is _Active_, in _GracePeriod_, or _Expired_. Sending messages with _Expired_ memberships is allowed, because the inclusion (Merkle) proof that the holder provides to RLN Relay only proves that the membership belongs to the membership set, and not that membership's state.

### Register a membership

Membership registration is subject to the following conditions:
- If there are _Expired_ memberships in the RLN tree, the new membership MUST overwrite an _Expired_ membership.
- The new membership SHOULD overwrite the membership that has been _Expired_ for the longest time.
- If a new membership A overwrites an _Expired_ membership B:
- membership B MUST become _ErasedAwaitsWithdrawal_;
- the current total rate limit MUST be decremented by the rate limit of membership B;
- the contract MUST take all necessary steps to ensure that the keeper of membership B can withdraw their deposit later.
- Registration MUST fail if the total rate limit of _Active_, _GracePeriod_, and _Expired_ memberships, including the one being created, would exceed `R_{max}`.
- Registration MUST fail if the requested rate limit for a new membership is lower than `r_{min}` or higher than `r_{max}`.
Membership registration is subject to the following requirements:
- The keeper MUST specify the requested rate limit `r` of a new membership at registration time[^3].
- Registration MUST fail if `r < r_{min}` or `r > r_{max}`.
- The keeper MUST lock up a deposit to register a membership.
- The keeper MUST specify the rate limit[^3] of a membership at registration time.
- The size of the deposit MUST depend on the specified rate limit.
- In case of a successful registration:
- the new membership MUST become _Active_;
- the new membership MUST have an active state duration `A` and a grace period duration `G`;
- the current total rate limit MUST be incremented by the rate limit of the new membership.
- A membership MUST have an expiration time `T` and a grace period `G`.
#### Overwriting other memberships

Let us define the following rate limits:
- `R_{active}` is the total rate limit of all _Active_ memberships;
- `R_{grace_period}` is the total rate limit of all _GracePeriod_ memberships;
- `R_{expired}` is the total rate limit of all _Expired_ memberships.

Let us define the free rate limit that is available without overwriting _Expired_ memberships as follows:

```
R_{free} = R_{max} - R_{active} - R_{grace_period} - R_{expired}
```

Membership registration is additionally subject to the following requirements:
- If `r <= R_{free}`, the new membership MUST be registered (assuming all other necessary conditions hold). The new membership MAY overwrite one or multiple _Expired_ memberships.
s-tikhomirov marked this conversation as resolved.
Show resolved Hide resolved
- If `r > R_{free}`:
- if `r > R_{free} + R_{expired}`, registration MUST fail;
- if `r <= R_{free} + R_{expired}`, the new membership MUST be registered by overwriting some _Expired_ memberships.
- The sender of the registration transaction MAY specify a list of _Expired_ memberships to be overwritten. If the list is not provided, the contract MAY use any criteria to select _Expired_ memberships to overwrite (see Implementation Suggestions).

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sembr please.

If the list is not provided, the contract MAY use any criteria to select Expired memberships to overwrite (see Implementation Suggestions).

I would not bother implementing that @richard-ramos

- If a new membership A overwrites an _Expired_ membership B:
- membership B MUST become _ErasedAwaitsWithdrawal_;
- the current total rate limit MUST be decremented by the rate limit of membership B;
- the contract MUST take all necessary steps to ensure that the keeper of membership B can withdraw their deposit later.

[^3]: A user-facing application SHOULD suggest default rate limits to the keeper (see Implementation Suggestions).

Expand All @@ -160,17 +195,18 @@ Deposit withdrawal is subject to the following conditions:
- The membership keeper MUST be able to withdraw their deposit.
- Any user other than the membership keeper MUST NOT be able to withdraw its deposit.
- A deposit MUST be withdrawn in full.
- A withdrawal MUST fail if the membership is not in _GracePeriod_, _Expired_, or _ErasedAwaitsWithdrawal_.
- A withdrawal MUST fail if the membership is not in _ErasedAwaitsWithdrawal_.
- A membership MUST become _Erased_ after withdrawal.

## Governance and upgradability

At initial mainnet deployment, the contract MUST have an _Owner_.
The _Owner_ MUST be able to change the values of all contract parameters.
The _Owner_ MUST be able to pause any of the following contract functionalities:
- register a membership;
- extend a membership;
- withdraw a deposit.
The updated parameter values MUST apply to all new memberships.
The parameters of existing memberships MUST NOT change if the _Owner_ updates global parameters.
The contract MAY restrict extensions for memberships created before the latest parameter update.

The _Owner_ MUST be able to pause any of the functionalities (see definition above).

At some point, the _Owner_ SHOULD renounce their privileges,
and the contract MUST become immutable.
Expand All @@ -180,12 +216,27 @@ and the membership set SHOULD be migrated.

## Implementation Suggestions

### Membership Set Implementation

The membership set MAY be implemented as a Merkle tree, such as an [Incremental Merkle Tree](https://zkkit.pse.dev/modules/_zk_kit_imt.html) (IMT).

### Choosing Which _Expired_ Memberships to Overwrite

When registering a new membership, the contract needs to decide which _Expired_ memberships, if any, to overwrite.
The criteria for this selection can vary depending on the implementation.

Key considerations include:
- To minimize gas costs, it's better to overwrite a single high-rate membership rather than multiple low-rate ones.
- To encourage timely deposit withdrawals, it's better to overwrite memberships that have been _Expired_ for a long time.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is unneeded complexity in the smart contract. I did push in favour of lazy override initially. But I was convinced otherwise by higher gas cost and fluctuation of price.

IMO it would make more sense for the app to select the membership to override.
Meaning the contract would just revert if the total rate limit is exceed and no memberships are being overridden (no gas lost, right @richard-ramos ?)
It's then up to the application to be clever on how to select memberships to override.


### Considerations for User-facing Applications

User-facing applications SHOULD suggest one or more rate limits (tiers) to simplify user selection among the following RECOMMENDED options:
- `20` messages per epoch as low-tier;
- `200` messages per epoch as mid-tier;
- `600` messages per epoch as high-tier.

User-facing applications SHOULD save membership expiration dates in a local keystore during registration,
User-facing applications SHOULD save membership expiration timestamps in a local keystore during registration,
and notify the user when their membership is about to expire.

## Q&A
Expand All @@ -203,19 +254,20 @@ The rationale is to make possible parameter changes that the contract _Owner_ mi

### What if I don't extend my membership within its _GracePeriod_?

If a user does not extend their membership during the _GracePeriod_,
they risk having their _Expired_ membership overwritten.
Generally, users are expected to either extend their membership or withdraw their deposit to avoid this risk.
If the membership is not extended during its _GracePeriod_,
it becomes _Expired_ and can be overwritten.
Users are expected to either extend their membership on time to avoid this risk,
or erase them and withdraw their deposit.

### Can I send messages when my membership is _Expired_?

An _Expired_ membership allows sending messages for a certain period.
The RLN proof that message senders provide to RLN Relay nodes does not prove the state of the membership,
only its inclusion in the tree.
only its inclusion in the membership set.

_Expired_ memberships are not proactively erased from the tree.
_Expired_ memberships are not proactively erased from the membership set.
An _Expired_ membership is erased only when a new membership overwrites it or when its deposit is withdrawn.
Once erased (i.e., _Erased_ or _ErasedAwaitsWithdrawal_), the membership can no longer be used to send messages.
Once in _Erased_ or _ErasedAwaitsWithdrawal_ state, the membership can no longer be used to send messages.

### Will my deposit be slashed if I exceed the rate limit?

Expand Down Expand Up @@ -254,7 +306,7 @@ we define a cap `R_{max}` on the total rate limit.

### Why is there a minimum rate limit?

The minimum rate limit `r_{min}` prevents an attack where a large number of tiny memberships cause RLN tree bloat.
The minimum rate limit `r_{min}` prevents an attack where a large number of tiny memberships cause membership set bloat.

### Why is there a maximum rate limit?

Expand Down Expand Up @@ -289,7 +341,7 @@ publicly associates a membership with an Ethereum address.
However, this association does not compromise the privacy of the relayed messages,
as the protocol does not require the sender to disclose their specific membership to RLN Relay nodes.

To generate an RLN proof, a message sender must obtain a Merkle proof confirming that their membership belongs to the RLN tree.
To generate an RLN proof, a message sender must obtain a proof that their membership belongs to the membership set.
This proof can be requested directly from the contract.
Requesting the proof through a third-party RPC provider could compromise the sender's privacy,
as the provider might link the requester's Ethereum address, their RLN membership, and the corresponding API key.
Expand Down
Loading