From c8160070564336d12c0a4973b627b8e0b79b2513 Mon Sep 17 00:00:00 2001 From: Sergei Tikhomirov Date: Fri, 9 Aug 2024 19:02:36 +0200 Subject: [PATCH] major edit incl RFC-speak --- standards/core/rln-contract.md | 361 ++++++++++++++++----------------- 1 file changed, 178 insertions(+), 183 deletions(-) diff --git a/standards/core/rln-contract.md b/standards/core/rln-contract.md index 4c3c7fb..132764a 100644 --- a/standards/core/rln-contract.md +++ b/standards/core/rln-contract.md @@ -9,55 +9,35 @@ contributors: ## Abstract -This specification describes the smart contract that governs RLN memberships, in particular: +This document specifies RLN membership management in the context of mainnet deployment of the RLN smart contract, in particular: +- membership-related contract functionality; +- contract parameters for the initial mainnet deployment; +- contract governance and upgradability. -- the overall smart contract functionality; -- contract parameters; -- the values of those parameters for the initial deployment on a mainnet chain; -- the way in which the parameters can be modified. +We only consider contract functionality relevant for membership management. +The document might later evolve into a full-fledged contract specification. ## Background -[Rate-Limiting Nullifiers](https://rate-limiting-nullifier.github.io/rln-docs/) (RLN) is a ZK-based rate-limiting technique used in Waku. -Before sending messages, users must register in a smart contract. -They then attach a ZK proof of valid membership to every message. -Relaying nodes check message validity and drop invalid messages. - -A message is considered valid in terms of RLN if: -- its sender has an active RLN membership; and -- the sender hasn't exceeded their rate limit within the current epoch. +Rate-Limiting Nullifier (RLN) is a ZK-based gadget used in Waku. +RLN provides a privacy-preserving way to limit each user's burden on the network. +The RLN smart contract is the core element of RLN architecture. +Users interact with the contract to manage their memberships, +as well as to get the data necessary for proof generation and verification. -RLN is managed by a smart contract that keeps track of active memberships. -Users interact with the contract to register a new membership and -to obtain Merkle proofs and the tree root, -which is necessary for proof generation and verification. +To relay a message: +- the sender MUST register a membership in a smart contract; +- the sender MUST attach a ZK-proof of membership to every message; +- each relaying node MUST drop the message if: + - the proof is invalid; or + - the sender has exceeded their rate limit within the current epoch. -In testnet deployment, the RLN contract does not require any payment apart from gas costs. -This document aims to make the RLN contract suitable for mainnet deployment. -In particular, the contract would require users to put down a deposit. -The aim of the deposit initially is purely to protect the system from denial-of-service attacks using bandwidth capping. -It may also be explored as a revenue stream for Waku in later versions. +RLN is only deployed on Sepolia testnet as of August 2024. +This document aims to outline the path to its mainnet deployment. ## Membership lifecycle -This section is not intended to describe the full contract functionality. -We only focus on functions required for managing memberships. -The document might later evolve into a full-fledged contract specification. - -The RLN contract provides the following functionalities: -- register a membership; -- extend a membership; -- terminate the membership (withdraw the deposit). - -For the _Contract Owner_, the contract provides the following additional functionality: -- change the modifiable parameters (see parameter table below); -- (TBD) freeze certain functionality (e.g., in case of an attack, stop new registrations, deposit withdrawals, etc). - -The deposit is not taken away (slashed) for exceeding the rate limit. - -## Membership state transitions - -A membership can be in one of the following states: +Any existing membership MUST always be in exactly one of the following states: - active; - grace-period; - expired; @@ -66,215 +46,222 @@ A membership can be in one of the following states: ```mermaid graph TD; NonExistent --> |"register"| Active; - Active --> |"+90 days"| GracePeriod; + Active --> |"time `T` passed"| GracePeriod; GracePeriod --> |"extend"| Active; - GracePeriod --> |"+30 days"| Expired; + GracePeriod --> |"time `G` passed"| Expired; GracePeriod --> |"withdraw"| Expired; Expired --> |"reuse_slot"| Erased; ``` -A user locks up a deposit to register a membership, which starts its lifecycle in the _active_ state. -After the expiration term (see parameter table), a membership moves to the _grace period_ state. -During grace period, the membership owner can either extend the membership or withdraw the deposit. -Deposit withdrawal makes a _grace-period_ membership _expired_ immediately. -Extension makes a _grace-period_ membership _active_ immediately. -A _grace-period_ membership becomes _expired_ automatically after its grace period ends. -The slot in the RLN tree of an _expired_ membership can be overridden by a new membership. -An _expired_ membership becomes _erased_ when its tree slot is overridden. +State updates triggered by a transaction MUST be updated immediately. +State updates defined by time progression MAY be updated lazily. +Before providing any membership-specific functionality, the contract MUST: +- check whether the state of the membership involved is up-to-date; +- if necessary, update the membership state; +- process the transaction in accordance with the up-to-date membership state. -The user can only extend their membership during its grace period. -Otherwise, the user assumes the risk of the membership being erased at any moment. +Memberships MUST NOT be transferable. +One Ethereum address MAY register multiple memberships. +One Waku node MAY manage multiple memberships (this functionality is not yet implemented as of August 2024). -The user cannot withdraw their deposit from an _active_ membership. -The rationale for this limitation is to prevent users from abusing the contract by making deposits and withdrawals in short succession. +## Contract functionalities -Memberships are not transferable. -One Ethereum address MAY register multiple memberships. -One Waku node MAY manage multiple memberships, -although this functionality is not yet implemented. +The contract MUST provide the following functionalities: +- register a membership; +- extend a membership; +- withdraw a deposit. -Availability of user functionalities depending on the membership state is as follows: +Availability of membership-specific functionalities MUST be as follows: | | Active | Grace Period | Expired | Erased | | --------------------- | ------ | ------------ | ------- | ------ | | Send a message | Yes | Yes | Yes | No | | Extend the membership | No | Yes | No | No | | Withdraw the deposit | No | Yes | Yes | Yes | +### Governance and upgradability + +At initial mainnet deployment, the contract MUST have an _Owner_ with the following additional functionalities: +- change any of the modifiable parameters, as listed in the Parameter table; +- disable any of the following contract functionalities: + - register a membership; + - extend a membership; + - (TBD) withdraw a deposit. + +At some point, the _Owner_ SHOULD renounce their privileges, and the contract MUST become immutable. +Further upgrades, if necessary, SHOULD be done by deploying a new contract and migrating the membership set. + +### Register a membership + +Membership registration is subject to the following conditions: +- if there are _expired_ memberships in the contract, the new membership MUST overwrite an expired membership; +- the new membership SHOULD overwrite the membership that had been in _expired_ state for the longest time; +- if the new membership overwrites another membership, the latter MUST transition from _expired_ to _erased_ state; +- if the deposit from the newly _erased_ membership has not been withdrawn, the contract MUST take all necessary steps to ensure that the owner of that membership can withdraw their deposit later; +- registration MUST fail if the total rate limit of _active_, _grace-period_, and _expired_ memberships, including the one being created, would exceed the limit; +- registration MUST fail if the requested rate limit for the new membership is lower than allowed; +- the user MUST lock-up a deposit to register a membership; +- the user MUST specify the requested rate limit of the new membership; +- the size of the deposit MUST be calculated depending on the requested rate limit; +- in case of successful registration, the new membership MUST be in _active_ state; +- a newly created membership MUST have an expiration time `T` and a grace period `G` (suggested values listed below). + +### Send a message + +Sending messages is handled by Waku Relay nodes, not by the RLN smart contract. +For completeness, sending messages is mentioned below where relevant as one of broader Waku functionalities. +The full specification of Relay node behavior is out of scope for this document. + +A Relay node MUST relay a message unless: +- the message is committed to a different epoch than the current epoch; or +- the user has exceed their allowed rate limit for the current epoch; or +- the RLN proof fails to prove that the message sender owns an existing membership. + +### Extend a membership + +Extending a membership is subject to the following condition: +- extension MUST fail if the membership is in any state other than _grace-period_; +- the membership owner MUST be able to extend the membership; +- any user except the membership owner MUST NOT be able to extend the membership; +- after a successful extension, the membership MUST become _active_. + +Owning a membership means controlling the private key from which the RLN commitment ID (i.e., public key) was derived. + +### Withdraw the deposit + +Deposit withdrawal is subject to the following conditions: +- the owner of a membership MUST be able to withdraw their deposit; +- a deposit MUST be withdrawn in full; +- any user except the membership owner MUST NOT be able to withdraw its deposit; +- a withdrawal MUST fail if the membership is in any state other than _grace-period_, _expired_, or _erased_; +- a withdrawal from a _grace-period_ membership MUST move it into the _expired_ state; +- a withdrawal from an _expired_ membership MUST NOT change its state (TBD); +- a withdrawal from an _erased_ membership MUST NOT change its state (TBD). -Note that the user can still send messages when their membership is _expired_. -This is explained by the fact that Relay nodes cannot verify the state of the membership, only its presence in the RLN tree. -_Expired_ memberships are not erased from the tree proactively, as this would require someone to send a transaction and pay the gas costs. -Instead, an _expired_ memberships is only erased when a new memberships overtakes its slot. -We expect that an honest user would not want to risk their membership being erased at any time, and would either extend or terminate it during its grace period. - -We do not allow extending an _active_ membership. -The rationale here is that if the _Owner_ changes some contract parameters (e.g., for security purposes), -users with extended memberships will not be affected by the changes for a long time. - -Membership states are updated lazily in the contract. -Before handling any membership-specific function, the contract MUST check if the membership's state is outdated and update it as necessary. - - -### User actions -In more detail, user actions are handled as follows. +## Implementation Suggestions -#### Register a membership +The current version of the contract (RLNv2) is deployed on Sepolia testnet ([source code](https://github.com/waku-org/waku-rlnv2-contract/blob/main/src/WakuRlnV2.sol)). -Registering a membership: -1. Check whether there are _expired_ memberships. - 1. If there are _expired_ memberships: - 1. Select the _expired_ membership with the earliest end of its grace period; - 2. Move that membership to the _erased_ state; - 3. Create a new _active_ membership using the erased membership's tree slot. - 2. If there are no _expired_ memberships, check whether the total limits are respected. - 1. If the new membership would exceed the maximum total number of _active_, _grace-period_, and _expired_ memberships, fail the registration; - 2. If the new membership would exceed the maximum total rate limit of _active_, _grace-period_, and _expired_ memberships, fail the registration; - 3. Otherwise, create a new membership in the _active_ state. +The RECOMMENDED parameter values for the initial mainnet deployment are listed in the following table. +All parameter values MUST be modifiable by the contract _Owner_. -#### Send a message +| Parameter | Symbol | Value | Units | +| ------------------------------------------- | --------- | ------- | ---------------------------------- | +| Epoch length | `epoch` | `10` | minutes | +| Maximum total rate limit of all memberships | `R_{max}` | `20000` | messages per `epoch` | +| Minimal rate limit of one membership | `r_{min}` | `20` | messages per `epoch` | +| Price of `1` message per epoch | `p_u` | `0.01` | `USD` per one period of length `T` | +| Membership expiration term | `T` | `90` | days | +| Membership grace period | `G` | `30` | days | +| Accepted tokens | | `DAI` | | +| Reference currency | | `USD` | | +| Pricing function | | linear | | -Note: when sending a message, the user only interacts with a Relay node, not with the smart contract. -The Relay node, in turn, interacts with the smart contract to check the user-provided RLN proof. +Applications MAY suggest the following rate limits to their users: +- `20` messages per epoch as low-tier; +- `200` messages per epoch as mid-tier; +- `600` messages per epoch as high-tier. -Sending a message: -1. If the message is committed to a different epoch than the current epoch, drop the message; -2. If the user has exceed their allowed rate limit for the current epoch, drop the message; -3. If the RLN proof fails, drop the message; -4. Otherwise, propagate the message. +## Q&A -#### Extend a membership +### Why can't I withdraw a deposit from an _active_ membership? -(TBD) The contract doesn't check whether the transaction sender is the owner of the membership in question. -In other words, any user is permitted to extend any _grace-period_ membership. +The rationale for this limitation is to prevent an undesirable usage pattern where users make deposits and withdrawals in short succession. -Note: an attacker can extend other users' _grace-period_ memberships without their consent, -which has at least two negative consequences: -1. users who planned to withdraw their deposit later would have to wait for another membership term; -2. if an attacker extends many memberships, the cap on total active rate limit can be exceeded, preventing the registration of new memberships. +### Why can't I extend an active membership? -Extending a membership: -1. Check whether the membership is in its _grace-period_ state: - 1. If it is not, fail the transaction; - 2. Otherwise, mark the membership as _active_. +We do not allow extending an _active_ membership. +The rationale here is that if the _Owner_ changes some contract parameters (e.g., for security purposes), +users with extended memberships will not be affected by the changes for a long time. -#### Withdraw the deposit +### What happens if I don't extend my membership during its grace period? -Withdrawing the deposit: -1. (TBD) Check whether the user is the "owner" of the membership: - 1. If not, fail the transaction; - 2. Otherwise, continue. -2. Check whether the membership is in one of the states that allow withdrawal: - 1. If not, fail the transaction; - 2. Otherwise, continue. -3. Check whether the deposit has not yet been withdrawn: - 1. If not, fail the transaction; - 2. Otherwise, withdraw the deposit. +The user who does not extend their membership during its grace period, assumes the risk of the membership being overwritten (and therefore _erased_) at any moment. +We expect that most honest users would not want to take that risk and would either extend their memberships or withdraw their deposits during the grace period. -(TBD) If an _expired_ membership becomes _erased_, -and its deposit has not been withdrawn, -data is saved in the smart contract that would allow the user to withdraw the deposit later. +TBD: should we make membership _erased_ immediately on deposit withdrawal? -## Contract governance and mutability +### Can I send messages after my membership expires? -The contract governance is as follows: +A membership allows sending messages for some time after expiry. -1. the first deployment of the contract allows the _Owner_ to modify certain parameters (TBD) under certain constraints (TBD); -2. at some point, the _Owner_ SHOULD renounce their privileges, and the contract MUST become immutable; -3. further upgrades, if necessary, can be done by deploying a new contract and migrating the membership set. +Sending messages is managed by Relay nodes, not by RLN contract. +The RLN proof that message senders provide to Relay nodes only proves whether the sender owns _some_ membership included in the RLN tree. +The sender cannot prove the state of that membership. -## Parameters +_Expired_ memberships are not erased from the tree proactively, +as this would require someone to send a transaction and pay the gas costs. +Instead, an _expired_ membership is only _erased_ when a new memberships overwrites it. -We make the following design choices: -- there are `3` membership tiers: low-, mid-, and high-tier; -- prices are quoted in `USD`; -- payment is accepted in `DAI`; -- the price for a given tier is linearly proportional to its rate limit. +### Will my deposit be slashed if I exceed the rate limit? -For the initial deployment, we suggest the following values. +The aim of the deposit initially is to protect the network from denial-of-service attacks with bandwidth capping. +The current version of RLN does not involve slashing. -| Parameter | Symbol | Value | Units | Modifiable? | Comment | -| --------------------------------------------------------- | ---------- | ------- | ------------------------------------------------------------------------------------ | ----------- | ------------------------------ | -| Epoch length | `epoch` | `10` | minutes | Yes | | -| Maximum total rate limit of concurrent active memberships | `R_{max}` | TBD | messages per `epoch` | Yes | Can be replaced with `N_{max}` | -| Maximum number of concurrent active memberships | `N_{max}` | `10000` | units | Yes | Can be replaced with `R_{max}` | -| Rate limit for low-tier | `R_{low}` | `20` | messages per `epoch` | TBD | | -| Rate limit for mid-tier | `R_{mid}` | `200` | messages per `epoch` | TBD | | -| Rate limit for high-tier | `R_{high}` | `600` | messages per `epoch` | TBD | | -| Unit price | `p_u` | `0.01` | `USD` per `1` message per `epoch` for the duration of one membership expiration term | Yes | | -| Membership expiration term | `T` | `90` | days | Yes | | -| Grace period | `G` | `30` | days | Yes | | +### Do I need an extra deposit to extend a membership? -### Discussion on some parameter values +Membership extension requires no additional deposit. +The opportunity cost of locked-up capital plus gas fees for extension transactions make extensions non-free, which is sufficient for the initial mainnet deployment. -#### Epoch length +### Why this particular epoch length? Epoch length is a global parameter set in the smart contract. Rate limits are defined in terms of the maximum allowed messages per epoch. There is a trade-off between short and long epochs. -On the one hand, longer epochs allow for short-term spikes, -which allows for more flexibility. -Arguably, short-term bursts followed by a period of silence is a common pattern for some use cases. -Usage peaks are expected to average out over longer time periods, -allowing us to reason about network utilization on a longer time scale. +On the one hand, longer epochs allow for better accommodating short-term usage peaks. +Peaks tend to average out over longer time periods, +which allows us to reason about network utilization on a longer time scale. -On the other hand, long epochs lead to more memory requirements for nodes due to the growth of the nullifier log. +On the other hand, long epochs increases memory requirements for Relay nodes. Each message contains a nullifier that proves its validity in terms of RLN. -Within the latest epoch, each relaying node stores all nullifiers to detect users exceeding the rate limit. -Nullifier log data needs to be stored in memory. +Each Relay node must keeps in memory a nullifier log for the current epoch. + +We chose an epoch length of `10` minutes as a reasonable middle-ground. Each nullifier plus metadata is `128` bytes (per message). -This means that one user with a `1` message per second rate limit (high-tier) generates up to `600 * 128 / 1024 = 75 KiB` of nullifier log data per 10-min epoch. +With a `10`-minute epoch, one high-tier user with a `1` message per second rate limit generates up to `600 * 128 / 1024 = 75 KiB` of nullifier log data per epoch. This corresponds to: - for 1000 users: approximately `73 MiB`; - for 10 thousand users: approximately `732 MiB`. -#### Maximum total message limit / concurrently active memberships +### Why is there a cap on the total rate limit? -We may want to limit the total rate limit available to all users. -The rationale is that the total network bandwidth is a limited resource. -The total active rate limit should not exceed the network's real capabilities. +Total network bandwidth is a limited resource. +We want to cap the total rate limit, at least in the initial mainnet deployment, to avoid overstretching the network's capabilities. -There may be two ways to implement a global cap: -- limit the total rate limits; -- limit the total number of active memberships (e.g., at 10K memberships). +### Why is there a minimal rate limit? -#### Proportional pricing +The minimal rate limit prevents an attack where someone registers a large number of memberships with a tiny rate limit each, causing the RLN tree to contain too many elements. -We suggest proportional pricing for simplicity. -Proportional pricing means that if tier A offers N times the limit of tier B, then tier A is N times more expensive than tier B. +### Are there bulk discounts for high-rate memberships? -An alternative approach could be bulk discounts for high-tier memberships. -Discounts are more efficient but promote centralization. -Finding the right trade-off remains subject for future work. +For the initial mainnet deployment, membership price is linearly proportional to its rate limit. +We choose this pricing scheme for simplicity. +In other words, there are no bulk discounts. +High-rate memberships are arguably more efficient but can incentivize centralization. +Finding a pricing scheme with the right trade-off remains subject for future work. -#### Accepted tokens +### Why only accept DAI? -When choosing a token to accept, we consider the following criteria: - -- a stablecoin: USD-denominated pricing is simpler for users and for developers (avoiding an oracle); +When choosing a token to accept, we considered the following criteria: +- a stablecoin, as USD-denominated pricing is familiar for users and requires no oracle; - popular, high liquidity; - preferably decentralized; - with a reasonably good track record w.r.t. censorship. -Based on these criteria, we suggest accepting DAI for the initial deployment. +Based on these criteria, we chose DAI for the initial mainnet deployment. Other tokens may be added in the future. -#### Grace period and membership extension - -Membership extension does not require a new deposit. -However, the opportunity cost of locked-up capital plus gas fees for extension transactions make extensions non-free. -Moreover, a legitimate user is unlikely to risk their membership being taken over at any moment by not renewing their membership. -We argue that no new deposit requirement for membership extension is sufficient for the initial mainnet deployment. - -## Implementation Suggestions - -The current version of the contract (RLNv2) is deployed on Sepolia testnet ([source code](https://github.com/waku-org/waku-rlnv2-contract/blob/main/src/WakuRlnV2.sol)). - ## Security / Privacy Considerations -Requesting a Merkle proof for one's own membership through a third-party RPC provider may endanger the requester's privacy. +Issuing membership-specific transactions (e.g., membership extension and deposit withdrawal) publicly links it to an Ethereum address. +Note that this does not degrade the privacy of the relayed messages. + +To produce an RLN proof, a message sender must obtain a Merkle proof for their RLN membership. +One way to obtain this proof is to request it from the RLN smart contract. +Requesting a proof through a third-party RPC provider may endanger the sender's privacy. +The provider would be able to link the requester's Ethereum address and the RLN membership with the corresponding API key. ## Copyright @@ -282,6 +269,14 @@ Copyright and related rights waived via [CC0](https://creativecommons.org/public ## References +- [Rate-Limiting Nullifier](https://rate-limiting-nullifier.github.io/rln-docs/) - [11/WAKU2-RELAY](https://github.com/vacp2p/rfc-index/blob/main/waku/standards/core/11/relay.md) - [17/WAKU2-RLN-RELAY](https://github.com/vacp2p/rfc-index/blob/main/waku/standards/core/17/rln-relay.md) + + +--- + + + +