feat: upgrade zerokit

This commit is contained in:
Arseniy Klempner 2025-12-19 14:32:43 -08:00
parent 94a1eb4dd6
commit bdbe6181e2
No known key found for this signature in database
GPG Key ID: 51653F18863BD24B
15 changed files with 116 additions and 184 deletions

17
package-lock.json generated
View File

@ -7138,15 +7138,10 @@
"link": true "link": true
}, },
"node_modules/@waku/zerokit-rln-wasm": { "node_modules/@waku/zerokit-rln-wasm": {
"version": "0.2.1", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/@waku/zerokit-rln-wasm/-/zerokit-rln-wasm-0.2.1.tgz", "resolved": "https://registry.npmjs.org/@waku/zerokit-rln-wasm/-/zerokit-rln-wasm-1.0.0.tgz",
"integrity": "sha512-2Xp7e92y4qZpsiTPGBSVr4gVJ9mJTLaudlo0DQxNpxJUBtoJKpxdH5xDCQDiorbkWZC2j9EId+ohhxHO/xC1QQ==", "integrity": "sha512-kRAeUePAY3++i5XXniCx+tqDH+3rdfPKED/lFRrbQ8ZiNWpu059fKxtPQqqvd8jNZQUOWDc7HRTpq2TVbWd8yQ==",
"license": "MIT or Apache2" "license": "MIT OR Apache-2.0"
},
"node_modules/@waku/zerokit-rln-wasm-utils": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/@waku/zerokit-rln-wasm-utils/-/zerokit-rln-wasm-utils-0.1.0.tgz",
"integrity": "sha512-3ccyg9+CtRXFJfWaxI/kx8Aec5B2S9YUmZAVhPRdN1EG6iQYG2hgvAurx8ZF9/zOppdrhzzyvCgDPg5kRUlOfQ=="
}, },
"node_modules/@webassemblyjs/ast": { "node_modules/@webassemblyjs/ast": {
"version": "1.14.1", "version": "1.14.1",
@ -34727,8 +34722,7 @@
"@wagmi/core": "^2.22.1", "@wagmi/core": "^2.22.1",
"@waku/core": "^0.0.40", "@waku/core": "^0.0.40",
"@waku/utils": "^0.0.27", "@waku/utils": "^0.0.27",
"@waku/zerokit-rln-wasm": "^0.2.1", "@waku/zerokit-rln-wasm": "^1.0.0",
"@waku/zerokit-rln-wasm-utils": "^0.1.0",
"chai": "^5.1.2", "chai": "^5.1.2",
"chai-as-promised": "^8.0.1", "chai-as-promised": "^8.0.1",
"chai-spies": "^1.1.0", "chai-spies": "^1.1.0",
@ -34752,6 +34746,7 @@
"@waku/build-utils": "^1.0.0", "@waku/build-utils": "^1.0.0",
"@waku/message-encryption": "^0.0.37", "@waku/message-encryption": "^0.0.37",
"@waku/sdk": "^0.0.36", "@waku/sdk": "^0.0.36",
"@waku/tests": "*",
"deep-equal-in-any-order": "^2.0.6", "deep-equal-in-any-order": "^2.0.6",
"fast-check": "^3.23.2", "fast-check": "^3.23.2",
"rollup-plugin-copy": "^3.5.0" "rollup-plugin-copy": "^3.5.0"

View File

@ -26,7 +26,7 @@ module.exports = function (config) {
nocache: true nocache: true
}, },
{ {
pattern: "src/resources/**/*.zkey", pattern: "src/resources/**/*.arkzkey",
included: false, included: false,
served: true, served: true,
watched: false, watched: false,
@ -39,14 +39,6 @@ module.exports = function (config) {
watched: false, watched: false,
type: "wasm", type: "wasm",
nocache: true nocache: true
},
{
pattern: "../../node_modules/@waku/zerokit-rln-wasm-utils/*.wasm",
included: false,
served: true,
watched: false,
type: "wasm",
nocache: true
} }
], ],
@ -68,7 +60,7 @@ module.exports = function (config) {
mime: { mime: {
"application/wasm": ["wasm"], "application/wasm": ["wasm"],
"application/octet-stream": ["zkey"] "application/octet-stream": ["arkzkey"]
}, },
customHeaders: [ customHeaders: [
@ -78,7 +70,7 @@ module.exports = function (config) {
value: "application/wasm" value: "application/wasm"
}, },
{ {
match: ".*\\.zkey$", match: ".*\\.arkzkey$",
name: "Content-Type", name: "Content-Type",
value: "application/octet-stream" value: "application/octet-stream"
} }
@ -91,16 +83,10 @@ module.exports = function (config) {
__dirname, __dirname,
"../../node_modules/@waku/zerokit-rln-wasm/rln_wasm_bg.wasm" "../../node_modules/@waku/zerokit-rln-wasm/rln_wasm_bg.wasm"
), ),
"/base/rln_wasm_utils_bg.wasm":
"/absolute" +
path.resolve(
__dirname,
"../../node_modules/@waku/zerokit-rln-wasm-utils/rln_wasm_utils_bg.wasm"
),
"/base/rln.wasm": "/base/rln.wasm":
"/absolute" + path.resolve(__dirname, "src/resources/rln.wasm"), "/absolute" + path.resolve(__dirname, "src/resources/rln.wasm"),
"/base/rln_final.zkey": "/base/rln_final.arkzkey":
"/absolute" + path.resolve(__dirname, "src/resources/rln_final.zkey") "/absolute" + path.resolve(__dirname, "src/resources/rln_final.arkzkey")
}, },
webpack: { webpack: {
@ -131,7 +117,7 @@ module.exports = function (config) {
} }
}, },
{ {
test: /\.zkey$/, test: /\.arkzkey$/,
type: "asset/resource", type: "asset/resource",
generator: { generator: {
filename: "[name][ext]" filename: "[name][ext]"

View File

@ -33,7 +33,7 @@ module.exports = function (config) {
nocache: true nocache: true
}, },
{ {
pattern: "src/resources/**/*.zkey", pattern: "src/resources/**/*.arkzkey",
included: false, included: false,
served: true, served: true,
watched: false, watched: false,
@ -47,14 +47,6 @@ module.exports = function (config) {
type: "wasm", type: "wasm",
nocache: true nocache: true
}, },
{
pattern: "../../node_modules/@waku/zerokit-rln-wasm-utils/*.wasm",
included: false,
served: true,
watched: false,
type: "wasm",
nocache: true
},
{ {
// Fleet info is written by the integration test runner // Fleet info is written by the integration test runner
pattern: "fleet-info.json", pattern: "fleet-info.json",
@ -83,7 +75,7 @@ module.exports = function (config) {
mime: { mime: {
"application/wasm": ["wasm"], "application/wasm": ["wasm"],
"application/octet-stream": ["zkey"] "application/octet-stream": ["arkzkey"]
}, },
customHeaders: [ customHeaders: [
@ -93,7 +85,7 @@ module.exports = function (config) {
value: "application/wasm" value: "application/wasm"
}, },
{ {
match: ".*\\.zkey$", match: ".*\\.arkzkey$",
name: "Content-Type", name: "Content-Type",
value: "application/octet-stream" value: "application/octet-stream"
} }
@ -106,16 +98,10 @@ module.exports = function (config) {
__dirname, __dirname,
"../../node_modules/@waku/zerokit-rln-wasm/rln_wasm_bg.wasm" "../../node_modules/@waku/zerokit-rln-wasm/rln_wasm_bg.wasm"
), ),
"/base/rln_wasm_utils_bg.wasm":
"/absolute" +
path.resolve(
__dirname,
"../../node_modules/@waku/zerokit-rln-wasm-utils/rln_wasm_utils_bg.wasm"
),
"/base/rln.wasm": "/base/rln.wasm":
"/absolute" + path.resolve(__dirname, "src/resources/rln.wasm"), "/absolute" + path.resolve(__dirname, "src/resources/rln.wasm"),
"/base/rln_final.zkey": "/base/rln_final.arkzkey":
"/absolute" + path.resolve(__dirname, "src/resources/rln_final.zkey") "/absolute" + path.resolve(__dirname, "src/resources/rln_final.arkzkey")
}, },
webpack: { webpack: {
@ -146,7 +132,7 @@ module.exports = function (config) {
} }
}, },
{ {
test: /\.zkey$/, test: /\.arkzkey$/,
type: "asset/resource", type: "asset/resource",
generator: { generator: {
filename: "[name][ext]" filename: "[name][ext]"

View File

@ -85,8 +85,7 @@
"@wagmi/core": "^2.22.1", "@wagmi/core": "^2.22.1",
"@waku/core": "^0.0.40", "@waku/core": "^0.0.40",
"@waku/utils": "^0.0.27", "@waku/utils": "^0.0.27",
"@waku/zerokit-rln-wasm": "^0.2.1", "@waku/zerokit-rln-wasm": "^1.0.0",
"@waku/zerokit-rln-wasm-utils": "^0.1.0",
"chai": "^5.1.2", "chai": "^5.1.2",
"chai-as-promised": "^8.0.1", "chai-as-promised": "^8.0.1",
"chai-spies": "^1.1.0", "chai-spies": "^1.1.0",

View File

@ -84,7 +84,7 @@ export class RLNEncoder implements IEncoder {
0 // TODO: need to track messages sent per epoch 0 // TODO: need to track messages sent per epoch
); );
return new Proof(proof, epoch, rlnIdentifier); return new Proof(proof.toBytesLE(), epoch, rlnIdentifier);
} }
public get pubsubTopic(): string { public get pubsubTopic(): string {

View File

@ -3,6 +3,7 @@ import { publicActions } from "viem";
import { RLN_CONTRACT } from "./contract/constants.js"; import { RLN_CONTRACT } from "./contract/constants.js";
import { RLNBaseContract } from "./contract/rln_base_contract.js"; import { RLNBaseContract } from "./contract/rln_base_contract.js";
import { IdentityCredential } from "./identity.js";
import { Keystore } from "./keystore/index.js"; import { Keystore } from "./keystore/index.js";
import type { import type {
DecryptedCredentials, DecryptedCredentials,
@ -111,9 +112,10 @@ export class RLNCredentialsManager {
if ("signature" in options) { if ("signature" in options) {
log.info("Using Zerokit to generate identity"); log.info("Using Zerokit to generate identity");
identity = this.zerokit.generateSeededIdentityCredential( const extendedIdentity = this.zerokit.generateSeededIdentityCredential(
options.signature options.signature
); );
identity = IdentityCredential.fromBytes(extendedIdentity.toBytesLE());
} }
if (!identity) { if (!identity) {

View File

@ -133,7 +133,7 @@ describe("RLN Proof Unit Tests", function () {
); );
// Parse proof bytes into Proof class // Parse proof bytes into Proof class
const parsedProof = new Proof(proof, epoch, rlnIdentifier); const parsedProof = new Proof(proof.toBytesLE(), epoch, rlnIdentifier);
// Verify all fields have correct lengths according to Nim format: // Verify all fields have correct lengths according to Nim format:
// proof<128> | root<32> | external_nullifier<32> | share_x<32> | share_y<32> | nullifier<32> // proof<128> | root<32> | external_nullifier<32> | share_x<32> | share_y<32> | nullifier<32>
@ -154,7 +154,7 @@ describe("RLN Proof Unit Tests", function () {
// Verify round-trip: proofToBytes should reconstruct original bytes // Verify round-trip: proofToBytes should reconstruct original bytes
const reconstructedBytes = proofToBytes(parsedProof); const reconstructedBytes = proofToBytes(parsedProof);
expect(reconstructedBytes).to.deep.equal( expect(reconstructedBytes).to.deep.equal(
proof, proof.toBytesLE(),
"Reconstructed bytes should match original" "Reconstructed bytes should match original"
); );

Binary file not shown.

View File

@ -1,6 +1,5 @@
import { Logger } from "@waku/utils"; import { Logger } from "@waku/utils";
import init, * as zerokitRLN from "@waku/zerokit-rln-wasm"; import init, { WasmRLN } from "@waku/zerokit-rln-wasm";
import initUtils from "@waku/zerokit-rln-wasm-utils";
import { DEFAULT_RATE_LIMIT } from "./contract/constants.js"; import { DEFAULT_RATE_LIMIT } from "./contract/constants.js";
import { RLNCredentialsManager } from "./credentials_manager.js"; import { RLNCredentialsManager } from "./credentials_manager.js";
@ -17,14 +16,12 @@ export class RLNInstance extends RLNCredentialsManager {
*/ */
public static async create(): Promise<RLNInstance> { public static async create(): Promise<RLNInstance> {
try { try {
await initUtils();
await init(); await init();
zerokitRLN.initPanicHook();
const witnessCalculator = await RLNInstance.loadWitnessCalculator(); const witnessCalculator = await RLNInstance.loadWitnessCalculator();
const zkey = await RLNInstance.loadZkey(); const zkey = await RLNInstance.loadZkey();
const zkRLN = zerokitRLN.newRLN(zkey); const zkRLN = new WasmRLN(zkey);
const zerokit = new Zerokit(zkRLN, witnessCalculator, DEFAULT_RATE_LIMIT); const zerokit = new Zerokit(zkRLN, witnessCalculator, DEFAULT_RATE_LIMIT);
return new RLNInstance(zerokit); return new RLNInstance(zerokit);
@ -63,7 +60,7 @@ export class RLNInstance extends RLNCredentialsManager {
public static async loadZkey(): Promise<Uint8Array> { public static async loadZkey(): Promise<Uint8Array> {
try { try {
const url = new URL("./resources/rln_final.zkey", import.meta.url); const url = new URL("./resources/rln_final.arkzkey", import.meta.url);
const response = await fetch(url); const response = await fetch(url);
if (!response.ok) { if (!response.ok) {

View File

@ -1,18 +0,0 @@
import { hash, poseidonHash as poseidon } from "@waku/zerokit-rln-wasm-utils";
import { BytesUtils } from "./bytes.js";
export function poseidonHash(...input: Array<Uint8Array>): Uint8Array {
const inputLen = BytesUtils.writeUIntLE(
new Uint8Array(8),
input.length,
0,
8
);
const lenPrefixedData = BytesUtils.concatenate(inputLen, ...input);
return poseidon(lenPrefixedData, true);
}
export function sha256(input: Uint8Array): Uint8Array {
return hash(input, true);
}

View File

@ -1,6 +1,5 @@
export { createViemClientFromWindow, RpcClient } from "./rpcClient.js"; export { createViemClientFromWindow, RpcClient } from "./rpcClient.js";
export { BytesUtils } from "./bytes.js"; export { BytesUtils } from "./bytes.js";
export { sha256, poseidonHash } from "./hash.js";
export { export {
dateToEpoch, dateToEpoch,
epochIntToBytes, epochIntToBytes,

View File

@ -1,5 +1,6 @@
import { Hasher, WasmFr } from "@waku/zerokit-rln-wasm";
import { BytesUtils } from "./bytes.js"; import { BytesUtils } from "./bytes.js";
import { poseidonHash } from "./hash.js";
/** /**
* The fixed depth of the Merkle tree used in the RLN contract * The fixed depth of the Merkle tree used in the RLN contract
@ -26,23 +27,27 @@ export function reconstructMerkleRoot(
); );
} }
let currentValue = BytesUtils.bytes32FromBigInt(leafValue); let currentValue = WasmFr.fromBytesLE(
BytesUtils.bytes32FromBigInt(leafValue)
);
for (let level = 0; level < MERKLE_TREE_DEPTH; level++) { for (let level = 0; level < MERKLE_TREE_DEPTH; level++) {
const bit = (leafIndex >> BigInt(level)) & 1n; const bit = (leafIndex >> BigInt(level)) & 1n;
const proofBytes = BytesUtils.bytes32FromBigInt(proof[level]); const proofFr = WasmFr.fromBytesLE(
BytesUtils.bytes32FromBigInt(proof[level])
);
if (bit === 0n) { if (bit === 0n) {
// Current node is a left child: hash(current, proof[level]) // Current node is a left child: hash(current, proof[level])
currentValue = poseidonHash(currentValue, proofBytes); currentValue = Hasher.poseidonHashPair(currentValue, proofFr);
} else { } else {
// Current node is a right child: hash(proof[level], current) // Current node is a right child: hash(proof[level], current)
currentValue = poseidonHash(proofBytes, currentValue); currentValue = Hasher.poseidonHashPair(proofFr, currentValue);
} }
} }
return BytesUtils.toBigInt(currentValue, "little"); return BytesUtils.toBigInt(currentValue.toBytesLE(), "little");
} }
/** /**
@ -60,8 +65,11 @@ export function calculateRateCommitment(
const idBytes = BytesUtils.bytes32FromBigInt(idCommitment); const idBytes = BytesUtils.bytes32FromBigInt(idCommitment);
const rateLimitBytes = BytesUtils.bytes32FromBigInt(rateLimit); const rateLimitBytes = BytesUtils.bytes32FromBigInt(rateLimit);
const hashResult = poseidonHash(idBytes, rateLimitBytes); const hashResult = Hasher.poseidonHashPair(
return BytesUtils.toBigInt(hashResult, "little"); WasmFr.fromBytesLE(idBytes),
WasmFr.fromBytesLE(rateLimitBytes)
);
return BytesUtils.toBigInt(hashResult.toBytesLE(), "little");
} }
/** /**

View File

@ -10,17 +10,29 @@ describe("@waku/rln", () => {
const memKeys1 = rlnInstance.zerokit.generateSeededIdentityCredential(seed); const memKeys1 = rlnInstance.zerokit.generateSeededIdentityCredential(seed);
const memKeys2 = rlnInstance.zerokit.generateSeededIdentityCredential(seed); const memKeys2 = rlnInstance.zerokit.generateSeededIdentityCredential(seed);
memKeys1.IDCommitment.forEach((element, index) => { memKeys1
expect(element).to.equal(memKeys2.IDCommitment[index]); .getCommitment()
}); .toBytesLE()
memKeys1.IDNullifier.forEach((element, index) => { .forEach((element, index) => {
expect(element).to.equal(memKeys2.IDNullifier[index]); expect(element).to.equal(memKeys2.getCommitment().toBytesLE()[index]);
}); });
memKeys1.IDSecretHash.forEach((element, index) => { memKeys1
expect(element).to.equal(memKeys2.IDSecretHash[index]); .getNullifier()
}); .toBytesLE()
memKeys1.IDTrapdoor.forEach((element, index) => { .forEach((element, index) => {
expect(element).to.equal(memKeys2.IDTrapdoor[index]); expect(element).to.equal(memKeys2.getNullifier().toBytesLE()[index]);
}); });
memKeys1
.getSecretHash()
.toBytesLE()
.forEach((element, index) => {
expect(element).to.equal(memKeys2.getSecretHash().toBytesLE()[index]);
});
memKeys1
.getTrapdoor()
.toBytesLE()
.forEach((element, index) => {
expect(element).to.equal(memKeys2.getTrapdoor().toBytesLE()[index]);
});
}); });
}); });

View File

@ -1,17 +1,21 @@
import * as zerokitRLN from "@waku/zerokit-rln-wasm"; import {
import { generateSeededExtendedMembershipKey } from "@waku/zerokit-rln-wasm-utils"; ExtendedIdentity,
Hasher,
VecWasmFr,
WasmFr,
WasmRLN,
WasmRLNProof,
WasmRLNWitnessInput
} from "@waku/zerokit-rln-wasm";
import { DEFAULT_RATE_LIMIT, RATE_LIMIT_PARAMS } from "./contract/constants.js"; import { DEFAULT_RATE_LIMIT, RATE_LIMIT_PARAMS } from "./contract/constants.js";
import { IdentityCredential } from "./identity.js";
import { WitnessCalculator } from "./resources/witness_calculator"; import { WitnessCalculator } from "./resources/witness_calculator";
import { BytesUtils } from "./utils/bytes.js";
import { dateToEpochBytes } from "./utils/epoch.js"; import { dateToEpochBytes } from "./utils/epoch.js";
import { poseidonHash, sha256 } from "./utils/hash.js";
import { MERKLE_TREE_DEPTH } from "./utils/merkle.js"; import { MERKLE_TREE_DEPTH } from "./utils/merkle.js";
export class Zerokit { export class Zerokit {
public constructor( public constructor(
private readonly zkRLN: number, private readonly zkRLN: WasmRLN,
private readonly witnessCalculator: WitnessCalculator, private readonly witnessCalculator: WitnessCalculator,
public readonly rateLimit: number = DEFAULT_RATE_LIMIT, public readonly rateLimit: number = DEFAULT_RATE_LIMIT,
public readonly rlnIdentifier: Uint8Array = (() => { public readonly rlnIdentifier: Uint8Array = (() => {
@ -22,63 +26,14 @@ export class Zerokit {
})() })()
) {} ) {}
public get getZkRLN(): number {
return this.zkRLN;
}
public get getWitnessCalculator(): WitnessCalculator { public get getWitnessCalculator(): WitnessCalculator {
return this.witnessCalculator; return this.witnessCalculator;
} }
public generateSeededIdentityCredential(seed: string): IdentityCredential { public generateSeededIdentityCredential(seed: string): ExtendedIdentity {
const stringEncoder = new TextEncoder(); const stringEncoder = new TextEncoder();
const seedBytes = stringEncoder.encode(seed); const seedBytes = stringEncoder.encode(seed);
const memKeys = generateSeededExtendedMembershipKey(seedBytes, true); return ExtendedIdentity.generateSeeded(seedBytes);
return IdentityCredential.fromBytes(memKeys);
}
private 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
);
} }
public async generateRLNProof( public async generateRLNProof(
@ -90,7 +45,7 @@ export class Zerokit {
rateLimit: number, rateLimit: number,
messageId: number // number of message sent by the user in this epoch messageId: number // number of message sent by the user in this epoch
): Promise<{ ): Promise<{
proof: Uint8Array; proof: WasmRLNProof;
epoch: Uint8Array; epoch: Uint8Array;
rlnIdentifier: Uint8Array; rlnIdentifier: Uint8Array;
}> { }> {
@ -120,26 +75,37 @@ export class Zerokit {
`messageId must be an integer between 0 and ${rateLimit - 1}, got ${messageId}` `messageId must be an integer between 0 and ${rateLimit - 1}, got ${messageId}`
); );
} }
const pathElementsVec = new VecWasmFr();
const serializedWitness = await this.serializeWitness( for (const element of pathElements) {
idSecretHash, pathElementsVec.push(WasmFr.fromBytesLE(element));
pathElements, }
identityPathIndex, const identityPathIndexBytes = new Uint8Array(identityPathIndex.length);
msg, for (let i = 0; i < identityPathIndex.length; i++) {
epoch, // We assume that each identity path index is already in little-endian format
rateLimit, identityPathIndexBytes.set(identityPathIndex[i], i);
messageId }
const x = Hasher.hashToFieldLE(msg);
const externalNullifier = Hasher.poseidonHashPair(
Hasher.hashToFieldLE(epoch),
Hasher.hashToFieldLE(this.rlnIdentifier)
); );
const witnessJson: Record<string, unknown> = zerokitRLN.rlnWitnessToJson( const witness = new WasmRLNWitnessInput(
this.zkRLN, WasmFr.fromBytesLE(idSecretHash),
serializedWitness WasmFr.fromUint(rateLimit),
) as Record<string, unknown>; WasmFr.fromUint(messageId),
pathElementsVec,
identityPathIndexBytes,
x,
externalNullifier
);
const calculatedWitness: bigint[] = const calculatedWitness: bigint[] =
await this.witnessCalculator.calculateWitness(witnessJson); await this.witnessCalculator.calculateWitness(
const proof = zerokitRLN.generateRLNProofWithWitness( witness.toBigIntJson() as Record<string, unknown>
this.zkRLN, );
const proof = this.zkRLN.generateRLNProofWithWitness(
calculatedWitness, calculatedWitness,
serializedWitness witness
); );
return { return {
proof, proof,
@ -151,21 +117,21 @@ export class Zerokit {
public verifyRLNProof( public verifyRLNProof(
signalLength: Uint8Array, signalLength: Uint8Array,
signal: Uint8Array, signal: Uint8Array,
proof: Uint8Array, proof: WasmRLNProof,
roots: Uint8Array[] roots: Uint8Array[]
): boolean { ): boolean {
if (signalLength.length !== 8) if (signalLength.length !== 8)
throw new Error("signalLength must be 8 bytes"); throw new Error("signalLength must be 8 bytes");
if (proof.length !== 288) throw new Error("proof must be 288 bytes");
if (roots.length == 0) throw new Error("roots array is empty"); if (roots.length == 0) throw new Error("roots array is empty");
if (roots.find((root) => root.length !== 32)) { if (roots.find((root) => root.length !== 32)) {
throw new Error("All roots must be 32 bytes"); throw new Error("All roots must be 32 bytes");
} }
return zerokitRLN.verifyWithRoots( const rootsVec = new VecWasmFr();
this.zkRLN, for (const root of roots) {
BytesUtils.concatenate(proof, signalLength, signal), rootsVec.push(WasmFr.fromBytesLE(root));
BytesUtils.concatenate(...roots) }
); const x = Hasher.hashToFieldLE(signal);
return this.zkRLN.verifyWithRoots(proof, rootsVec, x);
} }
} }