-
Notifications
You must be signed in to change notification settings - Fork 51
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
4b6352b
cf75ed5
c926647
e66adf2
db5c568
7a3334b
0e34bb8
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 |
|---|---|---|
| @@ -0,0 +1,104 @@ | ||
| import { expect } from "chai"; | ||
|
|
||
| import { Keystore } from "./keystore/index.js"; | ||
| import { RLNInstance } from "./rln.js"; | ||
| import { BytesUtils } from "./utils/index.js"; | ||
| import { | ||
| calculateRateCommitment, | ||
| getPathDirectionsFromIndex, | ||
| MERKLE_TREE_DEPTH, | ||
| reconstructMerkleRoot | ||
| } from "./utils/merkle.js"; | ||
| import { | ||
| TEST_CREDENTIALS, | ||
| TEST_KEYSTORE_PASSWORD, | ||
| TEST_MERKLE_ROOT | ||
| } from "./utils/test_keystore.js"; | ||
|
|
||
| describe("RLN Proof Integration Tests", function () { | ||
| this.timeout(30000); | ||
|
|
||
| TEST_CREDENTIALS.forEach((credential, index) => { | ||
| describe(`Credential ${index + 1}`, function () { | ||
| it("validate stored merkle proof data", function () { | ||
| const merkleProof = credential.merkleProof.map((p) => BigInt(p)); | ||
|
|
||
| expect(merkleProof).to.be.an("array"); | ||
| expect(merkleProof).to.have.lengthOf(MERKLE_TREE_DEPTH); | ||
|
|
||
| for (let i = 0; i < merkleProof.length; i++) { | ||
| const element = merkleProof[i]; | ||
| expect(element).to.be.a( | ||
| "bigint", | ||
| `Proof element ${i} should be a bigint` | ||
| ); | ||
| // Note: First element can be 0 for some tree positions (e.g., credential 3) | ||
| } | ||
| }); | ||
|
|
||
| it("should generate a valid RLN proof", async function () { | ||
| const rlnInstance = await RLNInstance.create(); | ||
| const keystore = Keystore.fromString(credential.keystoreJson); | ||
| if (!keystore) { | ||
| throw new Error("Failed to load test keystore"); | ||
| } | ||
| const credentialHash = credential.credentialHash; | ||
| const decrypted = await keystore.readCredential( | ||
| credentialHash, | ||
| TEST_KEYSTORE_PASSWORD | ||
| ); | ||
| if (!decrypted) { | ||
| throw new Error("Failed to unlock credential with provided password"); | ||
| } | ||
|
|
||
| const idCommitment = decrypted.identity.IDCommitmentBigInt; | ||
|
|
||
| const merkleProof = credential.merkleProof.map((p) => BigInt(p)); | ||
| const merkleRoot = BigInt(TEST_MERKLE_ROOT); | ||
| const membershipIndex = BigInt(credential.membershipIndex); | ||
| const rateLimit = BigInt(credential.rateLimit); | ||
|
|
||
| const rateCommitment = calculateRateCommitment(idCommitment, rateLimit); | ||
|
|
||
| const proofElementIndexes = getPathDirectionsFromIndex(membershipIndex); | ||
|
|
||
| expect(proofElementIndexes).to.have.lengthOf(MERKLE_TREE_DEPTH); | ||
|
|
||
| const reconstructedRoot = reconstructMerkleRoot( | ||
| merkleProof, | ||
| membershipIndex, | ||
| rateCommitment | ||
| ); | ||
|
|
||
| expect(reconstructedRoot).to.equal( | ||
| merkleRoot, | ||
| "Reconstructed root should match stored root" | ||
| ); | ||
|
|
||
| const testMessage = new TextEncoder().encode("test"); | ||
|
|
||
| const proof = await rlnInstance.zerokit.generateRLNProof( | ||
| testMessage, | ||
| new Date(), | ||
| decrypted.identity.IDSecretHash, | ||
| merkleProof.map((element) => | ||
| BytesUtils.bytes32FromBigInt(element, "little") | ||
| ), | ||
| proofElementIndexes.map((idx) => | ||
| BytesUtils.writeUintLE(new Uint8Array(1), idx, 0, 1) | ||
| ), | ||
| Number(rateLimit), | ||
| 0 | ||
| ); | ||
|
|
||
| const isValid = rlnInstance.zerokit.verifyRLNProof( | ||
| BytesUtils.writeUintLE(new Uint8Array(8), testMessage.length, 0, 8), | ||
| testMessage, | ||
| proof, | ||
| [BytesUtils.bytes32FromBigInt(merkleRoot, "little")] | ||
| ); | ||
| expect(isValid).to.be.true; | ||
| }); | ||
| }); | ||
| }); | ||
| }); |
| 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?
Member
Author
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. 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.
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. how fast it works in the browser? karma won't show accurate numbers |
||
| 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.