-
Notifications
You must be signed in to change notification settings - Fork 48
feat(rln): proof generation and verification with on-chain merkle proof/root #2749
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -160,7 +160,15 @@ export class Keystore { | |
| } | ||
|
|
||
| public toString(): string { | ||
| return JSON.stringify(this.data); | ||
| // Custom replacer function to handle BigInt serialization | ||
| const bigIntReplacer = (_key: string, value: unknown): unknown => { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: remove this duplication as there is literally the same function below |
||
| if (typeof value === "bigint") { | ||
| return value.toString(); | ||
| } | ||
| return value; | ||
| }; | ||
|
|
||
| return JSON.stringify(this.data, bigIntReplacer); | ||
| } | ||
|
|
||
| public toObject(): NwakuKeystore { | ||
|
|
@@ -327,21 +335,32 @@ export class Keystore { | |
| const { IDCommitment, IDNullifier, IDSecretHash, IDTrapdoor } = | ||
| options.identity; | ||
|
|
||
| // Custom replacer function to handle BigInt serialization | ||
| const bigIntReplacer = (_key: string, value: unknown): unknown => { | ||
| if (typeof value === "bigint") { | ||
| return value.toString(); | ||
| } | ||
| return value; | ||
| }; | ||
|
|
||
| return utf8ToBytes( | ||
| JSON.stringify({ | ||
| treeIndex: options.membership.treeIndex, | ||
| identityCredential: { | ||
| idCommitment: Array.from(IDCommitment), | ||
| idNullifier: Array.from(IDNullifier), | ||
| idSecretHash: Array.from(IDSecretHash), | ||
| idTrapdoor: Array.from(IDTrapdoor) | ||
| }, | ||
| membershipContract: { | ||
| chainId: options.membership.chainId, | ||
| address: options.membership.address | ||
| JSON.stringify( | ||
| { | ||
| treeIndex: options.membership.treeIndex, | ||
| identityCredential: { | ||
| idCommitment: Array.from(IDCommitment), | ||
| idNullifier: Array.from(IDNullifier), | ||
| idSecretHash: Array.from(IDSecretHash), | ||
| idTrapdoor: Array.from(IDTrapdoor) | ||
| }, | ||
| membershipContract: { | ||
| chainId: options.membership.chainId, | ||
| address: options.membership.address | ||
| }, | ||
| userMessageLimit: options.membership.rateLimit | ||
| }, | ||
| userMessageLimit: options.membership.rateLimit | ||
| }) | ||
| bigIntReplacer | ||
| ) | ||
| ); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,101 @@ | ||||||||
| import { expect } from "chai"; | ||||||||
|
|
||||||||
| import { Keystore } from "./keystore/index.js"; | ||||||||
| import { RLNInstance } from "./rln.js"; | ||||||||
| import { BytesUtils } from "./utils/index.js"; | ||||||||
| import { | ||||||||
| calculateRateCommitment, | ||||||||
| extractPathDirectionsFromProof, | ||||||||
| MERKLE_TREE_DEPTH, | ||||||||
| reconstructMerkleRoot | ||||||||
| } from "./utils/merkle.js"; | ||||||||
| import { TEST_KEYSTORE_DATA } from "./utils/test_keystore.js"; | ||||||||
|
|
||||||||
| describe("RLN Proof Integration Tests", function () { | ||||||||
| this.timeout(30000); | ||||||||
|
|
||||||||
| it("validate stored merkle proof data", function () { | ||||||||
| // Convert stored merkle proof strings to bigints | ||||||||
| const merkleProof = TEST_KEYSTORE_DATA.merkleProof.map((p) => BigInt(p)); | ||||||||
|
|
||||||||
| expect(merkleProof).to.be.an("array"); | ||||||||
| expect(merkleProof).to.have.lengthOf(MERKLE_TREE_DEPTH); // RLN uses fixed depth merkle tree | ||||||||
|
|
||||||||
| merkleProof.forEach((element, i) => { | ||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: |
||||||||
| expect(element).to.be.a( | ||||||||
| "bigint", | ||||||||
| `Proof element ${i} should be a bigint` | ||||||||
| ); | ||||||||
| expect(element).to.not.equal(0n, `Proof element ${i} should not be zero`); | ||||||||
| }); | ||||||||
| }); | ||||||||
|
|
||||||||
| it("should generate a valid RLN proof", async function () { | ||||||||
| const rlnInstance = await RLNInstance.create(); | ||||||||
| // Load credential from test keystore | ||||||||
| const keystore = Keystore.fromString(TEST_KEYSTORE_DATA.keystoreJson); | ||||||||
| if (!keystore) { | ||||||||
| throw new Error("Failed to load test keystore"); | ||||||||
| } | ||||||||
| const credentialHash = TEST_KEYSTORE_DATA.credentialHash; | ||||||||
| const password = TEST_KEYSTORE_DATA.password; | ||||||||
| const credential = await keystore.readCredential(credentialHash, password); | ||||||||
| if (!credential) { | ||||||||
| throw new Error("Failed to unlock credential with provided password"); | ||||||||
| } | ||||||||
|
|
||||||||
| const idCommitment = credential.identity.IDCommitmentBigInt; | ||||||||
|
|
||||||||
| const merkleProof = TEST_KEYSTORE_DATA.merkleProof.map((p) => BigInt(p)); | ||||||||
| const merkleRoot = BigInt(TEST_KEYSTORE_DATA.merkleRoot); | ||||||||
| const membershipIndex = BigInt(TEST_KEYSTORE_DATA.membershipIndex); | ||||||||
| const rateLimit = BigInt(TEST_KEYSTORE_DATA.rateLimit); | ||||||||
|
|
||||||||
| const rateCommitment = calculateRateCommitment(idCommitment, rateLimit); | ||||||||
|
|
||||||||
| const proofElementIndexes = extractPathDirectionsFromProof( | ||||||||
| merkleProof, | ||||||||
| rateCommitment, | ||||||||
| merkleRoot | ||||||||
|
||||||||
| merkleRoot | |
| merkleRoot, | |
| membershipIndex + 100n |
Copilot
AI
Dec 9, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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")), |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,6 @@ | ||
| import { Logger } from "@waku/utils"; | ||
| import init, * as zerokitRLN from "@waku/zerokit-rln-wasm"; | ||
| import initUtils from "@waku/zerokit-rln-wasm-utils"; | ||
|
|
||
| import { DEFAULT_RATE_LIMIT } from "./contract/constants.js"; | ||
| import { RLNCredentialsManager } from "./credentials_manager.js"; | ||
|
|
@@ -16,6 +17,7 @@ export class RLNInstance extends RLNCredentialsManager { | |
| */ | ||
| public static async create(): Promise<RLNInstance> { | ||
| try { | ||
| await initUtils(); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. question: how fast does it load? |
||
| await init(); | ||
| zerokitRLN.initPanicHook(); | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
@waku/interfacespackage was removed from devDependencies, but there's no explanation in the PR description about why this dependency is no longer needed. If it was truly unused, this is good cleanup. However, if there are references to it elsewhere in the codebase, this could cause build failures. Please verify that this removal is intentional and that no code depends on this package.