From b0e56396d641b2f8077995d1378b39bf58d6534c Mon Sep 17 00:00:00 2001 From: Aaryamann Challani <43716372+rymnc@users.noreply.github.com> Date: Thu, 2 May 2024 21:19:00 +0530 Subject: [PATCH] feat(rlog: rln light verifiers): init (#136) * feat(rlog: rln light verifiers): init * fix: alter title to be more descriptive * fix: title case * fix: title for metrics * fix: capitalized m * fix: capitalized c * fix: comma in number * fix: wording for number of rpc calls * fix: comma in 2k blocks * fix: remove comma between leaves & it * fix: capitalization for billion gas * fix: remove commas * fix: remove main * fix: remove comma number of leaves * fix: better wording on gas cost * fix: remove comma between subtree & and * fix: comma wording * fix: remove comma after subtree * fix: comma in insertion section * fix: section on syncing * fix: s/which/this * fix: remove comma in gas cost section * fix: future work fixes * fix: conclusion fix p1 * fix: conclusion fix p2 * fix: intro comma * fix: intro commas p2 --- rlog/2024-04-20-rln-light-verifiers.mdx | 160 ++++++++++++++++++++++++ static/img/light-rln-verifiers.png | Bin 0 -> 34905 bytes 2 files changed, 160 insertions(+) create mode 100644 rlog/2024-04-20-rln-light-verifiers.mdx create mode 100644 static/img/light-rln-verifiers.png diff --git a/rlog/2024-04-20-rln-light-verifiers.mdx b/rlog/2024-04-20-rln-light-verifiers.mdx new file mode 100644 index 00000000..689f955c --- /dev/null +++ b/rlog/2024-04-20-rln-light-verifiers.mdx @@ -0,0 +1,160 @@ +--- +title: 'Verifying RLN Proofs in Light Clients with Subtrees' +date: 2024-04-20 12:00:00 +authors: p1ge0nh8er +published: true +slug: rln-light-verifiers +categories: research + +toc_min_heading_level: 2 +toc_max_heading_level: 4 +--- + +How resource-restricted devices can verify RLN proofs fast and efficiently. + + + +## Introduction + +Recommended previous reading: [Strengthening Anonymous DoS Prevention with Rate Limiting Nullifiers in Waku](https://vac.dev/rlog/rln-anonymous-dos-prevention). + +This post expands upon ideas described in the previous post, +focusing on how resource-restricted devices can verify RLN proofs fast and efficiently. + +Previously, it was required to fetch all the memberships from the smart contract, +construct the merkle tree locally, +and derive the merkle root, +which is subsequently used to verify RLN proofs. + +This process is not feasible for resource-restricted devices since it involves a lot of RPC calls, computation and fault tolerance. +One cannot expect a mobile phone to fetch all the memberships from the smart contract and construct the merkle tree locally. + +## Constraints and requirements + +An alternative solution to the one proposed in this post is to construct the merkle tree on-chain, +and have the root accessible with a single RPC call. +However, this approach increases gas costs for inserting new memberships and _may_ not be feasible until it is optimized further with batching mechanisms, etc. + +The other methods have been explored in more depth [here](https://hackmd.io/@rymnc/rln-tree-storages). + +Following are the requirements and constraints for the solution proposed in this post: + +1. Cheap membership insertions. +2. As few RPC calls as possible to reduce startup time. +3. Merkle root of the tree is available on-chain. +4. No centralized services to sequence membership insertions. +5. Map inserted commitments to the block in which they were inserted. + +## Metrics on sync time for a tree with 2,653 leaves + +The following metrics are based on the current implementation of RLN in the Waku gen0 network. + +### Test bench + +- Hardware: Macbook Air M2, 16GB RAM +- Network: 120 Megabits/sec +- Nwaku commit: [e61e4ff](https://github.com/waku-org/nwaku/tree/e61e4ff90a235657a7dc4248f5be41b6e031e98c) +- RLN membership set contract: [0xF471d71E9b1455bBF4b85d475afb9BB0954A29c4](https://sepolia.etherscan.io/address/0xF471d71E9b1455bBF4b85d475afb9BB0954A29c4#code) +- Deployed block number: 4,230,716 +- RLN Membership set depth: 20 +- Hash function: PoseidonT3 (which is a gas guzzler) +- Max size of the membership set: 2^20 = 1,048,576 leaves + +### Metrics + +- Time to sync the whole tree: 4 minutes +- RPC calls: 702 +- Number of leaves: 2,653 + +One can argue that the time to sync the tree at the current state is not _that_ bad. +However, the number of RPC calls is a concern, +which scales linearly with the number of blocks since the contract was deployed +This is because the implementation fetches all events from the contract, +chunking 2,000 blocks at a time. +This is done to avoid hitting the block limit of 10,000 events per call, +which is a limitation of popular RPC providers. + +## Proposed solution + +From a theoretical perspective, +one could construct the merkle tree on-chain, +in a view call, in-memory. +However, this is not feasible due to the gas costs associated with it. + +To compute the root of a Merkle tree with $2^{20}$ leaves it costs approximately 2 billion gas. +With Infura and Alchemy capping the gas limit to 350M and 550M gas respectively, +it is not possible to compute the root of the tree in a single call. + +Acknowledging that [Polygon Miden](https://polygon.technology/blog/polygon-miden-state-model) and [Penumbra](https://penumbra.zone/blog/tiered-commitment-tree/) both make use of a tiered commitment tree, +we propose a similar approach for RLN. + +A tiered commitment tree is a tree which is sharded into multiple smaller subtrees, +each of which is a tree in itself. +This allows scaling in terms of the number of leaves, +as well as reducing state bloat by just storing the root of a subtree when it is full instead of all its leaves. + +Here, the question arises: +What is the maximum number of leaves in a subtree with which the root can be computed in a single call? + +It costs approximately 217M gas to compute the root of a Merkle tree with $2^{10}$ leaves. + +This is a feasible number for a single call, +and hence we propose a tiered commitment tree with a maximum of $2^{10}$ leaves in a subtree and the number of subtrees is $2^{10}$. +Therefore, the maximum number of leaves in the tree is $2^{20}$ (the same as the current implementation). + +![img](/img/light-rln-verifiers.png) + +### Insertion + +When a commitment is inserted into the tree it is first inserted into the first subtree. +When the first subtree is full the next insertions go into the second subtree and so on. + +### Syncing + +When syncing the tree, +one only needs to fetch the roots of the subtrees. +The root of the full tree can be computed in-memory or on-chain. + +This allows us to derive the following relation: + +$$ +number\_of\_rpc\_calls = number\_of\_filled\_subtrees + 1 +$$ + +This is a significant improvement over the current implementation, +which requires fetching all the memberships from the smart contract. + +### Gas costs + +The gas costs for inserting a commitment into the tree are the same as the current implementation except it consists of an extra SSTORE operation to store the `shardIndex` of the commitment. + +### Events + +The events emitted by the contract are the same as the current implementation, +appending the `shardIndex` of the commitment. + +### Proof of concept + +A proof of concept implementation of the tiered commitment tree is available [here](https://github.com/vacp2p/rln-contract/pull/37), +and is deployed on Sepolia at [0xE7987c70B54Ff32f0D5CBbAA8c8Fc1cAf632b9A5](https://sepolia.etherscan.io/address/0xE7987c70B54Ff32f0D5CBbAA8c8Fc1cAf632b9A5). + +It is compatible with the current implementation of the RLN verifier. + +## Future work + +1. Optimize the gas costs of the tiered commitment tree. +2. Explore using different number of leaves under a given node in the tree (currently set to 2). + +## Conclusion + +The tiered commitment tree is a promising approach to reduce the number of RPC calls required to sync the tree and reduce the gas costs associated with computing the root of the tree. +Consequently, it allows for a more scalable and efficient RLN verifier. + +## References + +- [RLN Circuits](https://github.com/rate-limiting-nullifier/circom-rln) +- [Zerokit](https://github.com/vacp2p/zerokit) +- [RLN-V1 RFC](https://rfc.vac.dev/spec/32/) +- [RLN-V2 RFC](https://rfc.vac.dev/spec/58/) +- [RLN Implementers guide](https://hackmd.io/7cBCMU5hS5OYv8PTaW2wAQ?view) +- [Strengthening Anonymous DoS Prevention with Rate Limiting Nullifiers in Waku](https://vac.dev/rlog/rln-anonymous-dos-prevention) diff --git a/static/img/light-rln-verifiers.png b/static/img/light-rln-verifiers.png new file mode 100644 index 0000000000000000000000000000000000000000..51959a1f5672041ee91a136207a1316363536219 GIT binary patch literal 34905 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