feat(rln): proof generation and verification with on-chain merkle proof/root#2749
feat(rln): proof generation and verification with on-chain merkle proof/root#2749adklempner wants to merge 7 commits intomasterfrom
Conversation
size-limit report 📦
|
a15b352 to
73a2c1d
Compare
| public toString(): string { | ||
| return JSON.stringify(this.data); | ||
| // Custom replacer function to handle BigInt serialization | ||
| const bigIntReplacer = (_key: string, value: unknown): unknown => { |
There was a problem hiding this comment.
nit: remove this duplication as there is literally the same function below
| */ | ||
| public static async create(): Promise<RLNInstance> { | ||
| try { | ||
| await initUtils(); |
There was a problem hiding this comment.
question: how fast does it load?
There was a problem hiding this comment.
I tried adding benchmarks and it logged 0.00 ms for both init functions. However, this is likely because of how karma prepares the bundles for each test. If the corresponding wasm files are not already loaded and need to be fetched, then this would probably take longer.
There was a problem hiding this comment.
how fast it works in the browser? karma won't show accurate numbers
There was a problem hiding this comment.
Pull request overview
This PR implements stateless RLN proof generation and verification using on-chain Merkle proofs, eliminating the need to maintain a local Merkle tree. The implementation updates the zerokit WASM dependencies and adds comprehensive functionality for generating and verifying RLN proofs directly in the browser using smart contract-provided roots and proofs.
Key Changes:
- Added
generateRLNProof()andverifyRLNProof()methods to the Zerokit class for proof generation and verification - Implemented Merkle tree utilities for root reconstruction and path direction extraction from on-chain proofs
- Updated hash utilities to use new zerokit-rln-wasm-utils package with simplified APIs
- Fixed BigInt serialization in Keystore to handle JSON conversion properly
Reviewed changes
Copilot reviewed 11 out of 14 changed files in this pull request and generated 14 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/rln/src/zerokit.ts | Adds core proof generation/verification methods with witness serialization and external nullifier calculation |
| packages/rln/src/utils/merkle.ts | New Merkle tree utilities for root reconstruction, rate commitment calculation, and path direction extraction |
| packages/rln/src/utils/hash.ts | Updates hash functions to use new zerokit-rln-wasm-utils package |
| packages/rln/src/utils/bytes.ts | Adds fromBigInt() method for converting BigInt to byte arrays with configurable endianness |
| packages/rln/src/utils/epoch.ts | Simplifies epoch encoding using BytesUtils.writeUIntLE |
| packages/rln/src/proof.spec.ts | Integration test validating end-to-end proof generation and verification with real keystore data |
| packages/rln/src/zerokit.browser.spec.ts | Browser test for seeded identity credential generation consistency |
| packages/rln/src/utils/test_keystore.ts | Static test data with membership keystore and on-chain Merkle proof/root |
| packages/rln/src/rln.ts | Initializes new zerokit-rln-wasm-utils package |
| packages/rln/src/keystore/keystore.ts | Fixes BigInt JSON serialization with custom replacer function |
| packages/rln/karma.conf.cjs | Adds WASM file configuration for zerokit-rln-wasm-utils |
| packages/rln/package.json | Adds zerokit-rln-wasm-utils dependency, removes unused @waku/interfaces |
| packages/rln/.mocharc.cjs | Minor formatting fix (trailing newline) |
| package-lock.json | Updates dependencies including viem, @wagmi/cli, and adds zerokit-rln-wasm-utils |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
packages/rln/src/utils/merkle.ts
Outdated
| function bigIntToBytes32(value: bigint): Uint8Array { | ||
| const bytes = new Uint8Array(32); | ||
| let temp = value; | ||
|
|
||
| for (let i = 0; i < 32; i++) { | ||
| bytes[i] = Number(temp & 0xffn); | ||
| temp >>= 8n; | ||
| } | ||
|
|
||
| return bytes; | ||
| } |
There was a problem hiding this comment.
Missing validation for value parameter. The function should validate that the BigInt value fits within 32 bytes (i.e., value < 2n ** 256n). If a larger value is provided, the function will silently truncate it by only taking the lower 32 bytes, which could lead to incorrect results without any indication of error.
There was a problem hiding this comment.
removed this, we now have an equivalent function with proper checks in bytesutils
packages/rln/src/proof.spec.ts
Outdated
| Number(membershipIndex), | ||
| new Date(), | ||
| credential.identity.IDSecretHash, | ||
| merkleProof.map((proof) => BytesUtils.fromBigInt(proof, 32, "little")), |
There was a problem hiding this comment.
Variable name shadowing: the parameter name proof in the map callback shadows the outer variable merkleProof being mapped. This is confusing and could lead to errors. Consider renaming to: merkleProof.map((element) => BytesUtils.fromBigInt(element, 32, "little"))
| merkleProof.map((proof) => BytesUtils.fromBigInt(proof, 32, "little")), | |
| merkleProof.map((element) => BytesUtils.fromBigInt(element, 32, "little")), |
| epoch: Uint8Array, | ||
| rateLimit: number, | ||
| messageId: number // number of message sent by the user in this epoch | ||
| ): Promise<Uint8Array> { |
There was a problem hiding this comment.
Missing validation for x parameter. The function should validate that x.length === 32 since it represents a SHA-256 hash output. Incorrect length could lead to malformed witness data.
| ): Promise<Uint8Array> { | |
| ): Promise<Uint8Array> { | |
| if (x.length !== 32) { | |
| throw new Error("x must be a 32-byte SHA-256 hash"); | |
| } |
There was a problem hiding this comment.
we don't use x outside of this function, so i'm moving the generation of x from the input into here
| epoch: Uint8Array, | ||
| rateLimit: number, | ||
| messageId: number // number of message sent by the user in this epoch | ||
| ): Promise<Uint8Array> { |
There was a problem hiding this comment.
Missing validation for epoch parameter. The function should validate that epoch.length === 32 to ensure it represents a properly formatted epoch value. Incorrect length could lead to invalid external nullifier calculation.
| ): Promise<Uint8Array> { | |
| ): Promise<Uint8Array> { | |
| if (!(epoch instanceof Uint8Array) || epoch.length !== 32) { | |
| throw new Error("epoch must be a 32-byte Uint8Array"); | |
| } |
packages/rln/src/zerokit.ts
Outdated
| if (epoch.length !== 32) throw new Error("invalid epoch"); | ||
| if (idSecretHash.length !== 32) throw new Error("invalid id secret hash"); |
There was a problem hiding this comment.
Error messages "invalid epoch" and "invalid id secret hash" are not descriptive enough. They should specify the expected length. Consider: "Epoch must be 32 bytes, got ${epoch.length}" and "ID secret hash must be 32 bytes, got ${idSecretHash.length}"
| if (epoch.length !== 32) throw new Error("invalid epoch"); | |
| if (idSecretHash.length !== 32) throw new Error("invalid id secret hash"); | |
| if (epoch.length !== 32) throw new Error(`Epoch must be 32 bytes, got ${epoch.length}`); | |
| if (idSecretHash.length !== 32) throw new Error(`ID secret hash must be 32 bytes, got ${idSecretHash.length}`); |
73a2c1d to
80bf606
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 14 changed files in this pull request and generated 13 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
packages/rln/src/zerokit.ts
Outdated
| public async serializeWitness( | ||
| idSecretHash: Uint8Array, | ||
| pathElements: Uint8Array[], | ||
| identityPathIndex: Uint8Array[], | ||
| msg: Uint8Array, | ||
| epoch: Uint8Array, | ||
| rateLimit: number, | ||
| messageId: number // number of message sent by the user in this epoch | ||
| ): Promise<Uint8Array> { | ||
| const externalNullifier = poseidonHash( | ||
| sha256(epoch), | ||
| sha256(this.rlnIdentifier) | ||
| ); | ||
| const pathElementsBytes = new Uint8Array(8 + pathElements.length * 32); | ||
| BytesUtils.writeUIntLE(pathElementsBytes, pathElements.length, 0, 8); | ||
| for (let i = 0; i < pathElements.length; i++) { | ||
| // We assume that the path elements are already in little-endian format | ||
| pathElementsBytes.set(pathElements[i], 8 + i * 32); | ||
| } | ||
| const identityPathIndexBytes = new Uint8Array( | ||
| 8 + identityPathIndex.length * 1 | ||
| ); | ||
| BytesUtils.writeUIntLE( | ||
| identityPathIndexBytes, | ||
| identityPathIndex.length, | ||
| 0, | ||
| 8 | ||
| ); | ||
| for (let i = 0; i < identityPathIndex.length; i++) { | ||
| // We assume that each identity path index is already in little-endian format | ||
| identityPathIndexBytes.set(identityPathIndex[i], 8 + i * 1); | ||
| } | ||
| const x = sha256(msg); | ||
| return BytesUtils.concatenate( | ||
| idSecretHash, | ||
| BytesUtils.writeUIntLE(new Uint8Array(32), rateLimit, 0, 32), | ||
| BytesUtils.writeUIntLE(new Uint8Array(32), messageId, 0, 32), | ||
| pathElementsBytes, | ||
| identityPathIndexBytes, | ||
| x, | ||
| externalNullifier | ||
| ); | ||
| } |
There was a problem hiding this comment.
Missing JSDoc documentation for the serializeWitness method. This public method performs complex serialization and should include documentation explaining the purpose, the format of each parameter, and what the returned Uint8Array represents.
packages/rln/src/zerokit.ts
Outdated
| index: number, // index of the leaf in the merkle tree | ||
| epoch: Uint8Array | Date | undefined, | ||
| idSecretHash: Uint8Array, | ||
| pathElements: Uint8Array[], | ||
| identityPathIndex: Uint8Array[], | ||
| rateLimit: number, | ||
| messageId: number // number of message sent by the user in this epoch | ||
| ): Promise<Uint8Array> { | ||
| if (epoch === undefined) { | ||
| epoch = epochIntToBytes(dateToEpoch(new Date())); | ||
| } else if (epoch instanceof Date) { | ||
| epoch = epochIntToBytes(dateToEpoch(epoch)); | ||
| } | ||
|
|
||
| if (epoch.length !== 32) | ||
| throw new Error(`Epoch must be 32 bytes, got ${epoch.length}`); | ||
| if (idSecretHash.length !== 32) | ||
| throw new Error( | ||
| `ID secret hash must be 32 bytes, got ${idSecretHash.length}` | ||
| ); | ||
| if (index < 0) throw new Error("index must be >= 0"); |
There was a problem hiding this comment.
The index parameter is validated but never used in the function. While identityPathIndex contains path direction bits derived from the index, the raw index parameter itself serves no purpose in the proof generation. Consider removing this parameter or adding a comment explaining why it's validated but not used.
| public async generateRLNProof( | ||
| msg: Uint8Array, | ||
| index: number, // index of the leaf in the merkle tree | ||
| epoch: Uint8Array | Date | undefined, | ||
| idSecretHash: Uint8Array, | ||
| pathElements: Uint8Array[], | ||
| identityPathIndex: Uint8Array[], | ||
| rateLimit: number, | ||
| messageId: number // number of message sent by the user in this epoch | ||
| ): Promise<Uint8Array> { | ||
| if (epoch === undefined) { | ||
| epoch = epochIntToBytes(dateToEpoch(new Date())); | ||
| } else if (epoch instanceof Date) { | ||
| epoch = epochIntToBytes(dateToEpoch(epoch)); | ||
| } | ||
|
|
||
| if (epoch.length !== 32) | ||
| throw new Error(`Epoch must be 32 bytes, got ${epoch.length}`); | ||
| if (idSecretHash.length !== 32) | ||
| throw new Error( | ||
| `ID secret hash must be 32 bytes, got ${idSecretHash.length}` | ||
| ); | ||
| if (index < 0) throw new Error("index must be >= 0"); | ||
| if ( | ||
| rateLimit < RATE_LIMIT_PARAMS.MIN_RATE || | ||
| rateLimit > RATE_LIMIT_PARAMS.MAX_RATE | ||
| ) { | ||
| throw new Error( | ||
| `Rate limit must be between ${RATE_LIMIT_PARAMS.MIN_RATE} and ${RATE_LIMIT_PARAMS.MAX_RATE}` | ||
| ); | ||
| } |
There was a problem hiding this comment.
Missing input validation for pathElements and identityPathIndex arrays. The function should validate that pathElements has length equal to MERKLE_TREE_DEPTH (20), that each element is 32 bytes, and that identityPathIndex has the same length with each element being 1 byte. Without these checks, incorrect inputs could lead to silent failures or incorrect proofs.
| public verifyRLNProof( | ||
| signalLength: Uint8Array, | ||
| signal: Uint8Array, | ||
| proof: Uint8Array, | ||
| roots: Uint8Array[] | ||
| ): boolean { | ||
| if (signalLength.length !== 8) | ||
| throw new Error("signalLength must be 8 bytes"); | ||
| return zerokitRLN.verifyWithRoots( | ||
| this.zkRLN, | ||
| BytesUtils.concatenate(proof, signalLength, signal), | ||
| BytesUtils.concatenate(...roots) | ||
| ); |
There was a problem hiding this comment.
Missing validation for the roots array parameter. The function should validate that the roots array is not empty and that each root is 32 bytes. An empty roots array or incorrectly sized roots could lead to verification failures or unexpected behavior.
| public verifyRLNProof( | ||
| signalLength: Uint8Array, | ||
| signal: Uint8Array, | ||
| proof: Uint8Array, | ||
| roots: Uint8Array[] | ||
| ): boolean { | ||
| if (signalLength.length !== 8) | ||
| throw new Error("signalLength must be 8 bytes"); | ||
| return zerokitRLN.verifyWithRoots( | ||
| this.zkRLN, | ||
| BytesUtils.concatenate(proof, signalLength, signal), | ||
| BytesUtils.concatenate(...roots) | ||
| ); | ||
| return IdentityCredential.fromBytes(memKeys); | ||
| } |
There was a problem hiding this comment.
Missing test coverage for verifyRLNProof method. While the integration test verifies a successful proof verification, there should be unit tests for edge cases like invalid proof format, empty roots array, incorrect signal length, and invalid proofs that should fail verification.
| public async generateRLNProof( | ||
| msg: Uint8Array, | ||
| index: number, // index of the leaf in the merkle tree | ||
| epoch: Uint8Array | Date | undefined, | ||
| idSecretHash: Uint8Array, | ||
| pathElements: Uint8Array[], | ||
| identityPathIndex: Uint8Array[], | ||
| rateLimit: number, | ||
| messageId: number // number of message sent by the user in this epoch | ||
| ): Promise<Uint8Array> { | ||
| if (epoch === undefined) { | ||
| epoch = epochIntToBytes(dateToEpoch(new Date())); | ||
| } else if (epoch instanceof Date) { | ||
| epoch = epochIntToBytes(dateToEpoch(epoch)); | ||
| } | ||
|
|
||
| if (epoch.length !== 32) | ||
| throw new Error(`Epoch must be 32 bytes, got ${epoch.length}`); | ||
| if (idSecretHash.length !== 32) | ||
| throw new Error( | ||
| `ID secret hash must be 32 bytes, got ${idSecretHash.length}` | ||
| ); | ||
| if (index < 0) throw new Error("index must be >= 0"); | ||
| if ( | ||
| rateLimit < RATE_LIMIT_PARAMS.MIN_RATE || | ||
| rateLimit > RATE_LIMIT_PARAMS.MAX_RATE | ||
| ) { | ||
| throw new Error( | ||
| `Rate limit must be between ${RATE_LIMIT_PARAMS.MIN_RATE} and ${RATE_LIMIT_PARAMS.MAX_RATE}` | ||
| ); | ||
| } | ||
|
|
||
| const serializedWitness = await this.serializeWitness( | ||
| idSecretHash, | ||
| pathElements, | ||
| identityPathIndex, | ||
| msg, | ||
| epoch, | ||
| rateLimit, | ||
| messageId | ||
| ); | ||
| const witnessJson: Record<string, unknown> = zerokitRLN.rlnWitnessToJson( | ||
| this.zkRLN, | ||
| seedBytes | ||
| serializedWitness | ||
| ) as Record<string, unknown>; | ||
| const calculatedWitness: bigint[] = | ||
| await this.witnessCalculator.calculateWitness(witnessJson); | ||
| return zerokitRLN.generateRLNProofWithWitness( | ||
| this.zkRLN, | ||
| calculatedWitness, | ||
| serializedWitness | ||
| ); | ||
| } |
There was a problem hiding this comment.
Missing documentation for the return value format. The method returns a Uint8Array representing the RLN proof, but the documentation doesn't specify the structure or format of this proof data, which would be helpful for users of this API to understand what they're receiving.
packages/rln/src/zerokit.ts
Outdated
| public async serializeWitness( | ||
| idSecretHash: Uint8Array, | ||
| pathElements: Uint8Array[], | ||
| identityPathIndex: Uint8Array[], | ||
| msg: Uint8Array, | ||
| epoch: Uint8Array, | ||
| rateLimit: number, | ||
| messageId: number // number of message sent by the user in this epoch | ||
| ): Promise<Uint8Array> { | ||
| const externalNullifier = poseidonHash( | ||
| sha256(epoch), | ||
| sha256(this.rlnIdentifier) | ||
| ); | ||
| const pathElementsBytes = new Uint8Array(8 + pathElements.length * 32); | ||
| BytesUtils.writeUIntLE(pathElementsBytes, pathElements.length, 0, 8); | ||
| for (let i = 0; i < pathElements.length; i++) { | ||
| // We assume that the path elements are already in little-endian format | ||
| pathElementsBytes.set(pathElements[i], 8 + i * 32); | ||
| } | ||
| const identityPathIndexBytes = new Uint8Array( | ||
| 8 + identityPathIndex.length * 1 | ||
| ); | ||
| BytesUtils.writeUIntLE( | ||
| identityPathIndexBytes, | ||
| identityPathIndex.length, | ||
| 0, | ||
| 8 | ||
| ); | ||
| for (let i = 0; i < identityPathIndex.length; i++) { | ||
| // We assume that each identity path index is already in little-endian format | ||
| identityPathIndexBytes.set(identityPathIndex[i], 8 + i * 1); | ||
| } | ||
| const x = sha256(msg); | ||
| return BytesUtils.concatenate( | ||
| idSecretHash, | ||
| BytesUtils.writeUIntLE(new Uint8Array(32), rateLimit, 0, 32), | ||
| BytesUtils.writeUIntLE(new Uint8Array(32), messageId, 0, 32), | ||
| pathElementsBytes, | ||
| identityPathIndexBytes, | ||
| x, | ||
| externalNullifier | ||
| ); | ||
| } |
There was a problem hiding this comment.
Missing test coverage for serializeWitness method. This is a public method that performs critical serialization for proof generation and should have unit tests to verify correct serialization of all input parameters, including edge cases like empty arrays or maximum-length inputs.
| public async generateRLNProof( | ||
| msg: Uint8Array, | ||
| index: number, // index of the leaf in the merkle tree | ||
| epoch: Uint8Array | Date | undefined, | ||
| idSecretHash: Uint8Array, | ||
| pathElements: Uint8Array[], | ||
| identityPathIndex: Uint8Array[], | ||
| rateLimit: number, | ||
| messageId: number // number of message sent by the user in this epoch | ||
| ): Promise<Uint8Array> { | ||
| if (epoch === undefined) { | ||
| epoch = epochIntToBytes(dateToEpoch(new Date())); | ||
| } else if (epoch instanceof Date) { | ||
| epoch = epochIntToBytes(dateToEpoch(epoch)); | ||
| } | ||
|
|
||
| if (epoch.length !== 32) | ||
| throw new Error(`Epoch must be 32 bytes, got ${epoch.length}`); | ||
| if (idSecretHash.length !== 32) | ||
| throw new Error( | ||
| `ID secret hash must be 32 bytes, got ${idSecretHash.length}` | ||
| ); | ||
| if (index < 0) throw new Error("index must be >= 0"); | ||
| if ( | ||
| rateLimit < RATE_LIMIT_PARAMS.MIN_RATE || | ||
| rateLimit > RATE_LIMIT_PARAMS.MAX_RATE | ||
| ) { | ||
| throw new Error( | ||
| `Rate limit must be between ${RATE_LIMIT_PARAMS.MIN_RATE} and ${RATE_LIMIT_PARAMS.MAX_RATE}` | ||
| ); | ||
| } | ||
|
|
||
| const serializedWitness = await this.serializeWitness( | ||
| idSecretHash, | ||
| pathElements, | ||
| identityPathIndex, | ||
| msg, | ||
| epoch, | ||
| rateLimit, | ||
| messageId | ||
| ); | ||
| const witnessJson: Record<string, unknown> = zerokitRLN.rlnWitnessToJson( | ||
| this.zkRLN, | ||
| seedBytes | ||
| serializedWitness | ||
| ) as Record<string, unknown>; | ||
| const calculatedWitness: bigint[] = | ||
| await this.witnessCalculator.calculateWitness(witnessJson); | ||
| return zerokitRLN.generateRLNProofWithWitness( | ||
| this.zkRLN, | ||
| calculatedWitness, | ||
| serializedWitness | ||
| ); | ||
| } |
There was a problem hiding this comment.
Missing test coverage for error handling in generateRLNProof. The function has multiple validation checks that throw errors, but there are no tests verifying that these error conditions are properly handled (e.g., invalid epoch length, invalid ID secret hash length, negative index, out-of-range rate limit).
| public verifyRLNProof( | ||
| signalLength: Uint8Array, | ||
| signal: Uint8Array, | ||
| proof: Uint8Array, | ||
| roots: Uint8Array[] | ||
| ): boolean { | ||
| if (signalLength.length !== 8) | ||
| throw new Error("signalLength must be 8 bytes"); | ||
| return zerokitRLN.verifyWithRoots( | ||
| this.zkRLN, | ||
| BytesUtils.concatenate(proof, signalLength, signal), | ||
| BytesUtils.concatenate(...roots) | ||
| ); | ||
| return IdentityCredential.fromBytes(memKeys); | ||
| } |
There was a problem hiding this comment.
Missing JSDoc documentation for the verifyRLNProof method. This is a public API method that should include documentation explaining the parameters (especially the format/encoding requirements for signalLength, signal, proof, and roots) and what the boolean return value indicates.
| * @param outputEndianness - Endianness of the output bytes ('big' or 'little') | ||
| * @returns 32-byte Uint8Array representation of the BigInt | ||
| */ | ||
| public static bytes32FromBigInt( |
There was a problem hiding this comment.
nit: as copilot pointed out - unit tests would be nice
There was a problem hiding this comment.
or better yet to use some lib for it
There was a problem hiding this comment.
updated to use viem util for the main logic
packages/rln/src/utils/epoch.ts
Outdated
| db.setUint32(0, epoch, true); | ||
| log.info("encoded epoch", epoch, bytes); | ||
| return bytes; | ||
| return BytesUtils.writeUIntLE(new Uint8Array(32), epoch, 0, 32); |
There was a problem hiding this comment.
nit: to keep compatible with JS definitions it should be writeUintLE instead of writeUIntLE
| @@ -1,4 +1,4 @@ | |||
| import * as zerokitRLN from "@waku/zerokit-rln-wasm"; | |||
| import { hash, poseidonHash as poseidon } from "@waku/zerokit-rln-wasm-utils"; | |||
There was a problem hiding this comment.
what is the difference between hash and posseidonHash?
also I'd keep poseidonHash as it is, poseidon is kind of too generic
There was a problem hiding this comment.
hash is sha256, poseidon is poseidon
poseidonHash is how it's used outside of this file, need to import as poseidon to avoid naming conflicts
| private readonly _rateLimit: number = DEFAULT_RATE_LIMIT | ||
| private readonly _rateLimit: number = DEFAULT_RATE_LIMIT, | ||
| private readonly rlnIdentifier: Uint8Array = new TextEncoder().encode( | ||
| "rln/waku-rln-relay/v2.0.0" |
There was a problem hiding this comment.
question: why it is this hardcoded string?
There was a problem hiding this comment.
this is the default rln identifier used in messaging-nim (and thus TWN)
packages/rln/src/zerokit.ts
Outdated
| msg: Uint8Array, | ||
| epoch: Uint8Array, | ||
| rateLimit: number, | ||
| messageId: number // number of message sent by the user in this epoch |
There was a problem hiding this comment.
rule of thumb - if you use some comment in code - then code is not explicit enough
| messageId: number // number of message sent by the user in this epoch | |
| messageNumberId: number |
| ); | ||
| } | ||
|
|
||
| public async generateRLNProof( |
There was a problem hiding this comment.
nit: public should be before private methods
weboko
left a comment
There was a problem hiding this comment.
lgtm except comment that I left + please, add unit tests for src/utils as you added some code easily testable
fix: update tests fix: store merkle proof/root as constant, remove use of RPC in proof gen/verification test
478ec92 to
0e34bb8
Compare
Problem / Description
The RLN contracts and zerokit libraries have been updated to work in a stateless manner, where maintaining a local merkle tree of memberships is no longer necessary; instead, the smart contracts provide a function for getting the latest root of the on-chain tree along with a proof for a given identity commitment in that tree.
The RLN package needs to be updated to use this functionality for generating and verifying RLN proofs in the browser.
Solution
Notes
Checklist