Skip to content

feat(rln): proof generation and verification with on-chain merkle proof/root#2749

Open
adklempner wants to merge 7 commits intomasterfrom
feat/rln-proof-gen-verification
Open

feat(rln): proof generation and verification with on-chain merkle proof/root#2749
adklempner wants to merge 7 commits intomasterfrom
feat/rln-proof-gen-verification

Conversation

@adklempner
Copy link
Copy Markdown
Member

@adklempner adklempner commented Dec 2, 2025

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

  • Update zerokit WASM dependencies
  • Update library functions used to call the WASM functions (assumes most inputs have been serialized to bytes)
  • Add static membership keystore and root/proof for testing (based on actual membership registered in deployed contract)
  • Test to generate and validate RLN proof using above membership

Notes


Checklist

  • Code changes are covered by unit tests.
  • Code changes are covered by e2e tests, if applicable.
  • Dogfooding has been performed, if feasible.
  • A test version has been published, if required.
  • All CI checks pass successfully.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Dec 2, 2025

size-limit report 📦

Path Size Loading time (3g) Running time (snapdragon) Total time
Waku node 96.24 KB (0%) 2 s (0%) 1.4 s (+62.59% 🔺) 3.3 s
Waku Simple Light Node 147.6 KB (0%) 3 s (0%) 648 ms (-55.89% 🔽) 3.6 s
ECIES encryption 22.62 KB (0%) 453 ms (0%) 550 ms (+96.94% 🔺) 1.1 s
Symmetric encryption 22 KB (0%) 440 ms (0%) 288 ms (-25.3% 🔽) 728 ms
DNS discovery 52.17 KB (0%) 1.1 s (0%) 580 ms (-11.3% 🔽) 1.7 s
Peer Exchange discovery 52.91 KB (0%) 1.1 s (0%) 433 ms (-14.48% 🔽) 1.5 s
Peer Cache Discovery 46.64 KB (0%) 933 ms (0%) 651 ms (+52.27% 🔺) 1.6 s
Privacy preserving protocols 77.31 KB (0%) 1.6 s (0%) 1.1 s (+46.71% 🔺) 2.6 s
Waku Filter 79.82 KB (0%) 1.6 s (0%) 1.2 s (+40.24% 🔺) 2.8 s
Waku LightPush 77.97 KB (0%) 1.6 s (0%) 1.2 s (+9.4% 🔺) 2.7 s
History retrieval protocols 83.74 KB (0%) 1.7 s (0%) 1.6 s (+52.63% 🔺) 3.2 s
Deterministic Message Hashing 28.98 KB (0%) 580 ms (0%) 548 ms (-17.37% 🔽) 1.2 s

@adklempner adklempner force-pushed the feat/rln-proof-gen-verification branch 2 times, most recently from a15b352 to 73a2c1d Compare December 2, 2025 19:31
@adklempner adklempner changed the title feat: implement proof generation and verification feat(rln): proof generation and verification with on-chain merkle proof/root Dec 2, 2025
@adklempner adklempner marked this pull request as ready for review December 2, 2025 23:30
public toString(): string {
return JSON.stringify(this.data);
// Custom replacer function to handle BigInt serialization
const bigIntReplacer = (_key: string, value: unknown): unknown => {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: remove this duplication as there is literally the same function below

*/
public static async create(): Promise<RLNInstance> {
try {
await initUtils();
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: how fast does it load?

Copy link
Copy Markdown
Member Author

@adklempner adklempner Dec 15, 2025

Choose a reason for hiding this comment

The 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.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The 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

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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() and verifyRLNProof() 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.

Comment on lines +114 to +124
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;
}
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed this, we now have an equivalent function with proper checks in bytesutils

Number(membershipIndex),
new Date(),
credential.identity.IDSecretHash,
merkleProof.map((proof) => BytesUtils.fromBigInt(proof, 32, "little")),
Copy link

Copilot AI Dec 9, 2025

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"))

Suggested change
merkleProof.map((proof) => BytesUtils.fromBigInt(proof, 32, "little")),
merkleProof.map((element) => BytesUtils.fromBigInt(element, 32, "little")),

Copilot uses AI. Check for mistakes.
epoch: Uint8Array,
rateLimit: number,
messageId: number // number of message sent by the user in this epoch
): Promise<Uint8Array> {
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
): Promise<Uint8Array> {
): Promise<Uint8Array> {
if (x.length !== 32) {
throw new Error("x must be a 32-byte SHA-256 hash");
}

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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> {
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
): Promise<Uint8Array> {
): Promise<Uint8Array> {
if (!(epoch instanceof Uint8Array) || epoch.length !== 32) {
throw new Error("epoch must be a 32-byte Uint8Array");
}

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this was added

Comment on lines +99 to +100
if (epoch.length !== 32) throw new Error("invalid epoch");
if (idSecretHash.length !== 32) throw new Error("invalid id secret hash");
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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}"

Suggested change
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}`);

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +40 to +82
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
);
}
Copy link

Copilot AI Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +86 to +106
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");
Copy link

Copilot AI Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +84 to +114
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}`
);
}
Copy link

Copilot AI Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +138 to 150
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)
);
Copy link

Copilot AI Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +138 to 151
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);
}
Copy link

Copilot AI Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +84 to +136
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
);
}
Copy link

Copilot AI Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +40 to +82
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
);
}
Copy link

Copilot AI Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +84 to +136
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
);
}
Copy link

Copilot AI Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copilot uses AI. Check for mistakes.
Comment on lines +138 to 151
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);
}
Copy link

Copilot AI Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
* @param outputEndianness - Endianness of the output bytes ('big' or 'little')
* @returns 32-byte Uint8Array representation of the BigInt
*/
public static bytes32FromBigInt(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: as copilot pointed out - unit tests would be nice

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or better yet to use some lib for it

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated to use viem util for the main logic

db.setUint32(0, epoch, true);
log.info("encoded epoch", epoch, bytes);
return bytes;
return BytesUtils.writeUIntLE(new Uint8Array(32), epoch, 0, 32);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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";
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the difference between hash and posseidonHash?

also I'd keep poseidonHash as it is, poseidon is kind of too generic

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: why it is this hardcoded string?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is the default rln identifier used in messaging-nim (and thus TWN)

msg: Uint8Array,
epoch: Uint8Array,
rateLimit: number,
messageId: number // number of message sent by the user in this epoch
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rule of thumb - if you use some comment in code - then code is not explicit enough

Suggested change
messageId: number // number of message sent by the user in this epoch
messageNumberId: number

);
}

public async generateRLNProof(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: public should be before private methods

Copy link
Copy Markdown
Collaborator

@weboko weboko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm except comment that I left + please, add unit tests for src/utils as you added some code easily testable

@adklempner adklempner force-pushed the feat/rln-proof-gen-verification branch from 478ec92 to 0e34bb8 Compare January 5, 2026 23:31
@adklempner adklempner requested a review from a team as a code owner January 5, 2026 23:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: generate proofs from roots without merkle tree

3 participants