diff --git a/src/codec.spec.ts b/src/codec.spec.ts index e0c88b0..1c46f88 100644 --- a/src/codec.spec.ts +++ b/src/codec.spec.ts @@ -25,11 +25,10 @@ import { RLNDecoder, RLNEncoder, } from "./codec.js"; +import { createRLN } from "./create.js"; import { RlnMessage } from "./message.js"; import { epochBytesToInt } from "./utils/index.js"; -import * as rln from "./index.js"; - const TestContentTopic = "/test/1/waku-message/utf8"; const EMPTY_PUBSUB_TOPIC = ""; @@ -44,12 +43,12 @@ const EMPTY_PROTO_MESSAGE = { describe("RLN codec with version 0", () => { it("toWire", async function () { - const rlnInstance = await rln.createRLN(); - const credential = rlnInstance.generateIdentityCredentials(); + const rlnInstance = await createRLN(); + const credential = rlnInstance.zerokit.generateIdentityCredentials(); const index = 0; const payload = new Uint8Array([1, 2, 3, 4, 5]); - rlnInstance.insertMember(credential.IDCommitment); + rlnInstance.zerokit.insertMember(credential.IDCommitment); const rlnEncoder = createRLNEncoder({ encoder: createEncoder({ contentTopic: TestContentTopic }), @@ -73,7 +72,7 @@ describe("RLN codec with version 0", () => { ))!; expect(msg.rateLimitProof).to.not.be.undefined; - expect(msg.verify([rlnInstance.getMerkleRoot()])).to.be.true; + expect(msg.verify([rlnInstance.zerokit.getMerkleRoot()])).to.be.true; expect(msg.verifyNoRoot()).to.be.true; expect(msg.epoch).to.not.be.undefined; expect(msg.epoch).to.be.gt(0); @@ -85,12 +84,12 @@ describe("RLN codec with version 0", () => { }); it("toProtoObj", async function () { - const rlnInstance = await rln.createRLN(); - const credential = rlnInstance.generateIdentityCredentials(); + const rlnInstance = await createRLN(); + const credential = rlnInstance.zerokit.generateIdentityCredentials(); const index = 0; const payload = new Uint8Array([1, 2, 3, 4, 5]); - rlnInstance.insertMember(credential.IDCommitment); + rlnInstance.zerokit.insertMember(credential.IDCommitment); const rlnEncoder = new RLNEncoder( createEncoder({ contentTopic: TestContentTopic }), @@ -114,7 +113,7 @@ describe("RLN codec with version 0", () => { expect(msg).to.not.be.undefined; expect(msg.rateLimitProof).to.not.be.undefined; - expect(msg.verify([rlnInstance.getMerkleRoot()])).to.be.true; + expect(msg.verify([rlnInstance.zerokit.getMerkleRoot()])).to.be.true; expect(msg.verifyNoRoot()).to.be.true; expect(msg.epoch).to.not.be.undefined; expect(msg.epoch).to.be.gt(0); @@ -128,12 +127,12 @@ describe("RLN codec with version 0", () => { describe("RLN codec with version 1", () => { it("Symmetric, toWire", async function () { - const rlnInstance = await rln.createRLN(); - const credential = rlnInstance.generateIdentityCredentials(); + const rlnInstance = await createRLN(); + const credential = rlnInstance.zerokit.generateIdentityCredentials(); const index = 0; const payload = new Uint8Array([1, 2, 3, 4, 5]); - rlnInstance.insertMember(credential.IDCommitment); + rlnInstance.zerokit.insertMember(credential.IDCommitment); const symKey = generateSymmetricKey(); @@ -163,7 +162,7 @@ describe("RLN codec with version 1", () => { ))!; expect(msg.rateLimitProof).to.not.be.undefined; - expect(msg.verify([rlnInstance.getMerkleRoot()])).to.be.true; + expect(msg.verify([rlnInstance.zerokit.getMerkleRoot()])).to.be.true; expect(msg.verifyNoRoot()).to.be.true; expect(msg.epoch).to.not.be.undefined; expect(msg.epoch).to.be.gt(0); @@ -175,12 +174,12 @@ describe("RLN codec with version 1", () => { }); it("Symmetric, toProtoObj", async function () { - const rlnInstance = await rln.createRLN(); - const credential = rlnInstance.generateIdentityCredentials(); + const rlnInstance = await createRLN(); + const credential = rlnInstance.zerokit.generateIdentityCredentials(); const index = 0; const payload = new Uint8Array([1, 2, 3, 4, 5]); - rlnInstance.insertMember(credential.IDCommitment); + rlnInstance.zerokit.insertMember(credential.IDCommitment); const symKey = generateSymmetricKey(); @@ -209,7 +208,7 @@ describe("RLN codec with version 1", () => { expect(msg).to.not.be.undefined; expect(msg.rateLimitProof).to.not.be.undefined; - expect(msg.verify([rlnInstance.getMerkleRoot()])).to.be.true; + expect(msg.verify([rlnInstance.zerokit.getMerkleRoot()])).to.be.true; expect(msg.verifyNoRoot()).to.be.true; expect(msg.epoch).to.not.be.undefined; expect(msg.epoch).to.be.gt(0); @@ -221,12 +220,12 @@ describe("RLN codec with version 1", () => { }); it("Asymmetric, toWire", async function () { - const rlnInstance = await rln.createRLN(); - const credential = rlnInstance.generateIdentityCredentials(); + const rlnInstance = await createRLN(); + const credential = rlnInstance.zerokit.generateIdentityCredentials(); const index = 0; const payload = new Uint8Array([1, 2, 3, 4, 5]); - rlnInstance.insertMember(credential.IDCommitment); + rlnInstance.zerokit.insertMember(credential.IDCommitment); const privateKey = generatePrivateKey(); const publicKey = getPublicKey(privateKey); @@ -257,7 +256,7 @@ describe("RLN codec with version 1", () => { ))!; expect(msg.rateLimitProof).to.not.be.undefined; - expect(msg.verify([rlnInstance.getMerkleRoot()])).to.be.true; + expect(msg.verify([rlnInstance.zerokit.getMerkleRoot()])).to.be.true; expect(msg.verifyNoRoot()).to.be.true; expect(msg.epoch).to.not.be.undefined; expect(msg.epoch).to.be.gt(0); @@ -269,12 +268,12 @@ describe("RLN codec with version 1", () => { }); it("Asymmetric, toProtoObj", async function () { - const rlnInstance = await rln.createRLN(); - const credential = rlnInstance.generateIdentityCredentials(); + const rlnInstance = await createRLN(); + const credential = rlnInstance.zerokit.generateIdentityCredentials(); const index = 0; const payload = new Uint8Array([1, 2, 3, 4, 5]); - rlnInstance.insertMember(credential.IDCommitment); + rlnInstance.zerokit.insertMember(credential.IDCommitment); const privateKey = generatePrivateKey(); const publicKey = getPublicKey(privateKey); @@ -304,7 +303,7 @@ describe("RLN codec with version 1", () => { expect(msg).to.not.be.undefined; expect(msg.rateLimitProof).to.not.be.undefined; - expect(msg.verify([rlnInstance.getMerkleRoot()])).to.be.true; + expect(msg.verify([rlnInstance.zerokit.getMerkleRoot()])).to.be.true; expect(msg.verifyNoRoot()).to.be.true; expect(msg.epoch).to.not.be.undefined; expect(msg.epoch).to.be.gt(0); @@ -318,12 +317,12 @@ describe("RLN codec with version 1", () => { describe("RLN Codec - epoch", () => { it("toProtoObj", async function () { - const rlnInstance = await rln.createRLN(); - const credential = rlnInstance.generateIdentityCredentials(); + const rlnInstance = await createRLN(); + const credential = rlnInstance.zerokit.generateIdentityCredentials(); const index = 0; const payload = new Uint8Array([1, 2, 3, 4, 5]); - rlnInstance.insertMember(credential.IDCommitment); + rlnInstance.zerokit.insertMember(credential.IDCommitment); const rlnEncoder = new RLNEncoder( createEncoder({ contentTopic: TestContentTopic }), @@ -350,7 +349,7 @@ describe("RLN Codec - epoch", () => { expect(msg).to.not.be.undefined; expect(msg.rateLimitProof).to.not.be.undefined; - expect(msg.verify([rlnInstance.getMerkleRoot()])).to.be.true; + expect(msg.verify([rlnInstance.zerokit.getMerkleRoot()])).to.be.true; expect(msg.verifyNoRoot()).to.be.true; expect(msg.epoch).to.not.be.undefined; expect(msg.epoch!.toString(10).length).to.eq(9); @@ -374,12 +373,12 @@ describe("RLN codec with version 0 and meta setter", () => { }; it("toWire", async function () { - const rlnInstance = await rln.createRLN(); - const credential = rlnInstance.generateIdentityCredentials(); + const rlnInstance = await createRLN(); + const credential = rlnInstance.zerokit.generateIdentityCredentials(); const index = 0; const payload = new Uint8Array([1, 2, 3, 4, 5]); - rlnInstance.insertMember(credential.IDCommitment); + rlnInstance.zerokit.insertMember(credential.IDCommitment); const rlnEncoder = createRLNEncoder({ encoder: createEncoder({ contentTopic: TestContentTopic, metaSetter }), @@ -410,7 +409,7 @@ describe("RLN codec with version 0 and meta setter", () => { expect(msg!.meta).to.deep.eq(expectedMeta); expect(msg.rateLimitProof).to.not.be.undefined; - expect(msg.verify([rlnInstance.getMerkleRoot()])).to.be.true; + expect(msg.verify([rlnInstance.zerokit.getMerkleRoot()])).to.be.true; expect(msg.verifyNoRoot()).to.be.true; expect(msg.epoch).to.not.be.undefined; expect(msg.epoch).to.be.gt(0); @@ -422,12 +421,12 @@ describe("RLN codec with version 0 and meta setter", () => { }); it("toProtoObj", async function () { - const rlnInstance = await rln.createRLN(); - const credential = rlnInstance.generateIdentityCredentials(); + const rlnInstance = await createRLN(); + const credential = rlnInstance.zerokit.generateIdentityCredentials(); const index = 0; const payload = new Uint8Array([1, 2, 3, 4, 5]); - rlnInstance.insertMember(credential.IDCommitment); + rlnInstance.zerokit.insertMember(credential.IDCommitment); const rlnEncoder = new RLNEncoder( createEncoder({ contentTopic: TestContentTopic, metaSetter }), @@ -458,7 +457,7 @@ describe("RLN codec with version 0 and meta setter", () => { expect(msg).to.not.be.undefined; expect(msg.rateLimitProof).to.not.be.undefined; - expect(msg.verify([rlnInstance.getMerkleRoot()])).to.be.true; + expect(msg.verify([rlnInstance.zerokit.getMerkleRoot()])).to.be.true; expect(msg.verifyNoRoot()).to.be.true; expect(msg.epoch).to.not.be.undefined; expect(msg.epoch).to.be.gt(0); diff --git a/src/codec.ts b/src/codec.ts index 06dc7ea..8a652b8 100644 --- a/src/codec.ts +++ b/src/codec.ts @@ -45,7 +45,7 @@ export class RLNEncoder implements IEncoder { private async generateProof(message: IMessage): Promise { const signal = toRLNSignal(this.contentTopic, message); - const proof = await this.rlnInstance.generateRLNProof( + const proof = await this.rlnInstance.zerokit.generateRLNProof( signal, this.index, message.timestamp, diff --git a/src/contract/rln_contract.spec.ts b/src/contract/rln_contract.spec.ts index 4218ed9..ae7b0d7 100644 --- a/src/contract/rln_contract.spec.ts +++ b/src/contract/rln_contract.spec.ts @@ -2,20 +2,23 @@ import chai from "chai"; import spies from "chai-spies"; import * as ethers from "ethers"; -import * as rln from "../index.js"; +import { createRLN } from "../create.js"; + +import { SEPOLIA_CONTRACT } from "./constants.js"; +import { RLNContract } from "./rln_contract.js"; chai.use(spies); describe("RLN Contract abstraction", () => { it("should be able to fetch members from events and store to rln instance", async () => { - const rlnInstance = await rln.createRLN(); + const rlnInstance = await createRLN(); - rlnInstance.insertMember = () => undefined; + rlnInstance.zerokit.insertMember = () => undefined; const insertMemberSpy = chai.spy.on(rlnInstance, "insertMember"); - const voidSigner = new ethers.VoidSigner(rln.SEPOLIA_CONTRACT.address); - const rlnContract = new rln.RLNContract(rlnInstance, { - registryAddress: rln.SEPOLIA_CONTRACT.address, + const voidSigner = new ethers.VoidSigner(SEPOLIA_CONTRACT.address); + const rlnContract = new RLNContract(rlnInstance, { + registryAddress: SEPOLIA_CONTRACT.address, signer: voidSigner, }); @@ -36,10 +39,10 @@ describe("RLN Contract abstraction", () => { const mockSignature = "0xdeb8a6b00a8e404deb1f52d3aa72ed7f60a2ff4484c737eedaef18a0aacb2dfb4d5d74ac39bb71fa358cf2eb390565a35b026cc6272f2010d4351e17670311c21c"; - const rlnInstance = await rln.createRLN(); - const voidSigner = new ethers.VoidSigner(rln.SEPOLIA_CONTRACT.address); - const rlnContract = new rln.RLNContract(rlnInstance, { - registryAddress: rln.SEPOLIA_CONTRACT.address, + const rlnInstance = await createRLN(); + const voidSigner = new ethers.VoidSigner(SEPOLIA_CONTRACT.address); + const rlnContract = new RLNContract(rlnInstance, { + registryAddress: SEPOLIA_CONTRACT.address, signer: voidSigner, }); @@ -58,7 +61,7 @@ describe("RLN Contract abstraction", () => { ); const identity = - rlnInstance.generateSeededIdentityCredential(mockSignature); + rlnInstance.zerokit.generateSeededIdentityCredential(mockSignature); await rlnContract.registerWithIdentity(identity); chai.expect(contractSpy).to.have.been.called(); diff --git a/src/contract/rln_contract.ts b/src/contract/rln_contract.ts index 0bbfa15..bb244ac 100644 --- a/src/contract/rln_contract.ts +++ b/src/contract/rln_contract.ts @@ -61,7 +61,7 @@ export class RLNContract { rlnInstance: RLNInstance, { registryAddress, signer }: RLNContractOptions ) { - const initialRoot = rlnInstance.getMerkleRoot(); + const initialRoot = rlnInstance.zerokit.getMerkleRoot(); this.registryContract = new ethers.Contract( registryAddress, @@ -182,14 +182,14 @@ export class RLNContract { } const idCommitment = zeroPadLE(hexToBytes(_idCommitment?._hex), 32); - rlnInstance.insertMember(idCommitment); + rlnInstance.zerokit.insertMember(idCommitment); this._members.set(index.toNumber(), { index, idCommitment: _idCommitment?._hex, }); }); - const currentRoot = rlnInstance.getMerkleRoot(); + const currentRoot = rlnInstance.zerokit.getMerkleRoot(); this.merkleRootTracker.pushRoot(blockNumber, currentRoot); }); } @@ -204,7 +204,7 @@ export class RLNContract { if (this._members.has(index)) { this._members.delete(index); } - rlnInstance.deleteMember(index); + rlnInstance.zerokit.deleteMember(index); }); this.merkleRootTracker.backFill(blockNumber); diff --git a/src/create.spec.ts b/src/create.spec.ts index acad8a4..7dc2e4f 100644 --- a/src/create.spec.ts +++ b/src/create.spec.ts @@ -6,7 +6,7 @@ describe("js-rln", () => { it("should verify a proof", async function () { const rlnInstance = await createRLN(); - const credential = rlnInstance.generateIdentityCredentials(); + const credential = rlnInstance.zerokit.generateIdentityCredentials(); //peer's index in the Merkle Tree const index = 5; @@ -15,11 +15,11 @@ describe("js-rln", () => { for (let i = 0; i < 10; i++) { if (i == index) { // insert the current peer's pk - rlnInstance.insertMember(credential.IDCommitment); + rlnInstance.zerokit.insertMember(credential.IDCommitment); } else { // create a new key pair - rlnInstance.insertMember( - rlnInstance.generateIdentityCredentials().IDCommitment + rlnInstance.zerokit.insertMember( + rlnInstance.zerokit.generateIdentityCredentials().IDCommitment ); } } @@ -33,7 +33,7 @@ describe("js-rln", () => { const epoch = new Date(); // generating proof - const proof = await rlnInstance.generateRLNProof( + const proof = await rlnInstance.zerokit.generateRLNProof( uint8Msg, index, epoch, @@ -42,7 +42,7 @@ describe("js-rln", () => { try { // verify the proof - const verifResult = rlnInstance.verifyRLNProof(proof, uint8Msg); + const verifResult = rlnInstance.zerokit.verifyRLNProof(proof, uint8Msg); expect(verifResult).to.be.true; } catch (err) { assert.fail(0, 1, "should not have failed proof verification"); @@ -52,7 +52,7 @@ describe("js-rln", () => { // Modifying the signal so it's invalid uint8Msg[4] = 4; // verify the proof - const verifResult = rlnInstance.verifyRLNProof(proof, uint8Msg); + const verifResult = rlnInstance.zerokit.verifyRLNProof(proof, uint8Msg); expect(verifResult).to.be.false; } catch (err) { console.log(err); @@ -61,7 +61,8 @@ describe("js-rln", () => { it("should verify a proof with a seeded membership key generation", async function () { const rlnInstance = await createRLN(); const seed = "This is a test seed"; - const credential = rlnInstance.generateSeededIdentityCredential(seed); + const credential = + rlnInstance.zerokit.generateSeededIdentityCredential(seed); //peer's index in the Merkle Tree const index = 5; @@ -70,11 +71,11 @@ describe("js-rln", () => { for (let i = 0; i < 10; i++) { if (i == index) { // insert the current peer's pk - rlnInstance.insertMember(credential.IDCommitment); + rlnInstance.zerokit.insertMember(credential.IDCommitment); } else { // create a new key pair - rlnInstance.insertMember( - rlnInstance.generateIdentityCredentials().IDCommitment + rlnInstance.zerokit.insertMember( + rlnInstance.zerokit.generateIdentityCredentials().IDCommitment ); } } @@ -88,7 +89,7 @@ describe("js-rln", () => { const epoch = new Date(); // generating proof - const proof = await rlnInstance.generateRLNProof( + const proof = await rlnInstance.zerokit.generateRLNProof( uint8Msg, index, epoch, @@ -97,7 +98,7 @@ describe("js-rln", () => { try { // verify the proof - const verifResult = rlnInstance.verifyRLNProof(proof, uint8Msg); + const verifResult = rlnInstance.zerokit.verifyRLNProof(proof, uint8Msg); expect(verifResult).to.be.true; } catch (err) { assert.fail(0, 1, "should not have failed proof verification"); @@ -107,7 +108,7 @@ describe("js-rln", () => { // Modifying the signal so it's invalid uint8Msg[4] = 4; // verify the proof - const verifResult = rlnInstance.verifyRLNProof(proof, uint8Msg); + const verifResult = rlnInstance.zerokit.verifyRLNProof(proof, uint8Msg); expect(verifResult).to.be.false; } catch (err) { console.log(err); @@ -117,8 +118,8 @@ describe("js-rln", () => { it("should generate the same membership key if the same seed is provided", async function () { const rlnInstance = await createRLN(); const seed = "This is a test seed"; - const memKeys1 = rlnInstance.generateSeededIdentityCredential(seed); - const memKeys2 = rlnInstance.generateSeededIdentityCredential(seed); + const memKeys1 = rlnInstance.zerokit.generateSeededIdentityCredential(seed); + const memKeys2 = rlnInstance.zerokit.generateSeededIdentityCredential(seed); memKeys1.IDCommitment.forEach((element, index) => { expect(element).to.equal(memKeys2.IDCommitment[index]); diff --git a/src/message.ts b/src/message.ts index 190dc06..869cf80 100644 --- a/src/message.ts +++ b/src/message.ts @@ -24,7 +24,7 @@ export class RlnMessage implements IDecodedMessage { public verify(roots: Uint8Array[]): boolean | undefined { return this.rateLimitProof - ? this.rlnInstance.verifyWithRoots( + ? this.rlnInstance.zerokit.verifyWithRoots( this.rateLimitProof, toRLNSignal(this.msg.contentTopic, this.msg), ...roots @@ -34,7 +34,7 @@ export class RlnMessage implements IDecodedMessage { public verifyNoRoot(): boolean | undefined { return this.rateLimitProof - ? this.rlnInstance.verifyWithNoRoot( + ? this.rlnInstance.zerokit.verifyWithNoRoot( this.rateLimitProof, toRLNSignal(this.msg.contentTopic, this.msg) ) // this.rlnInstance.verifyRLNProof once issue status-im/nwaku#1248 is fixed diff --git a/src/rln.ts b/src/rln.ts index 724cf76..c73b566 100644 --- a/src/rln.ts +++ b/src/rln.ts @@ -2,7 +2,6 @@ import { createDecoder, createEncoder } from "@waku/core"; import type { ContentTopic, IDecodedMessage, - IRateLimitProof, EncoderOptions as WakuEncoderOptions, } from "@waku/interfaces"; import init from "@waku/zerokit-rln-wasm"; @@ -23,17 +22,11 @@ import type { EncryptedCredentials, } from "./keystore/index.js"; import { KeystoreEntity, Password } from "./keystore/types.js"; -import { Proof, proofToBytes } from "./proof.js"; import verificationKey from "./resources/verification_key.js"; import * as wc from "./resources/witness_calculator.js"; import { WitnessCalculator } from "./resources/witness_calculator.js"; -import { - concatenate, - dateToEpoch, - epochIntToBytes, - extractMetaMaskSigner, - writeUIntLE, -} from "./utils/index.js"; +import { extractMetaMaskSigner } from "./utils/index.js"; +import { Zerokit } from "./zerokit.js"; async function loadWitnessCalculator(): Promise { const url = new URL("./resources/rln.wasm", import.meta.url); @@ -63,8 +56,9 @@ export async function create(): Promise { const DEPTH = 20; const zkRLN = zerokitRLN.newRLN(DEPTH, zkey, vkey); + const zerokit = new Zerokit(zkRLN, witnessCalculator); - return new RLNInstance(zkRLN, witnessCalculator); + return new RLNInstance(zerokit); } type StartRLNOptions = { @@ -101,10 +95,7 @@ export class RLNInstance { private keystore = Keystore.create(); private _credentials: undefined | DecryptedCredentials; - constructor( - private zkRLN: number, - private witnessCalculator: WitnessCalculator - ) {} + constructor(public zerokit: Zerokit) {} public get contract(): undefined | RLNContract { return this._contract; @@ -212,7 +203,9 @@ export class RLNInstance { let identity = "identity" in options && options.identity; if ("signature" in options) { - identity = await this.generateSeededIdentityCredential(options.signature); + identity = await this.zerokit.generateSeededIdentityCredential( + options.signature + ); } if (!identity) { @@ -289,166 +282,4 @@ export class RLNInstance { decoder: createDecoder(contentTopic), }); } - - generateIdentityCredentials(): IdentityCredential { - const memKeys = zerokitRLN.generateExtendedMembershipKey(this.zkRLN); // TODO: rename this function in zerokit rln-wasm - return IdentityCredential.fromBytes(memKeys); - } - - generateSeededIdentityCredential(seed: string): IdentityCredential { - const stringEncoder = new TextEncoder(); - const seedBytes = stringEncoder.encode(seed); - // TODO: rename this function in zerokit rln-wasm - const memKeys = zerokitRLN.generateSeededExtendedMembershipKey( - this.zkRLN, - seedBytes - ); - return IdentityCredential.fromBytes(memKeys); - } - - insertMember(idCommitment: Uint8Array): void { - zerokitRLN.insertMember(this.zkRLN, idCommitment); - } - - insertMembers(index: number, ...idCommitments: Array): void { - // serializes a seq of IDCommitments to a byte seq - // the order of serialization is |id_commitment_len<8>|id_commitment| - const idCommitmentLen = writeUIntLE( - new Uint8Array(8), - idCommitments.length, - 0, - 8 - ); - const idCommitmentBytes = concatenate(idCommitmentLen, ...idCommitments); - zerokitRLN.setLeavesFrom(this.zkRLN, index, idCommitmentBytes); - } - - deleteMember(index: number): void { - zerokitRLN.deleteLeaf(this.zkRLN, index); - } - - getMerkleRoot(): Uint8Array { - return zerokitRLN.getRoot(this.zkRLN); - } - - serializeMessage( - uint8Msg: Uint8Array, - memIndex: number, - epoch: Uint8Array, - idKey: Uint8Array - ): Uint8Array { - // calculate message length - const msgLen = writeUIntLE(new Uint8Array(8), uint8Msg.length, 0, 8); - - // Converting index to LE bytes - const memIndexBytes = writeUIntLE(new Uint8Array(8), memIndex, 0, 8); - - // [ id_key<32> | id_index<8> | epoch<32> | signal_len<8> | signal ] - return concatenate(idKey, memIndexBytes, epoch, msgLen, uint8Msg); - } - - async generateRLNProof( - msg: Uint8Array, - index: number, - epoch: Uint8Array | Date | undefined, - idSecretHash: Uint8Array - ): Promise { - if (epoch == undefined) { - epoch = epochIntToBytes(dateToEpoch(new Date())); - } else if (epoch instanceof Date) { - epoch = epochIntToBytes(dateToEpoch(epoch)); - } - - if (epoch.length != 32) throw "invalid epoch"; - if (idSecretHash.length != 32) throw "invalid id secret hash"; - if (index < 0) throw "index must be >= 0"; - - const serialized_msg = this.serializeMessage( - msg, - index, - epoch, - idSecretHash - ); - const rlnWitness = zerokitRLN.getSerializedRLNWitness( - this.zkRLN, - serialized_msg - ); - const inputs = zerokitRLN.RLNWitnessToJson(this.zkRLN, rlnWitness); - const calculatedWitness = await this.witnessCalculator.calculateWitness( - inputs, - false - ); // no sanity check being used in zerokit - - const proofBytes = zerokitRLN.generate_rln_proof_with_witness( - this.zkRLN, - calculatedWitness, - rlnWitness - ); - - return new Proof(proofBytes); - } - - verifyRLNProof( - proof: IRateLimitProof | Uint8Array, - msg: Uint8Array - ): boolean { - let pBytes: Uint8Array; - if (proof instanceof Uint8Array) { - pBytes = proof; - } else { - pBytes = proofToBytes(proof); - } - - // calculate message length - const msgLen = writeUIntLE(new Uint8Array(8), msg.length, 0, 8); - - return zerokitRLN.verifyRLNProof( - this.zkRLN, - concatenate(pBytes, msgLen, msg) - ); - } - - verifyWithRoots( - proof: IRateLimitProof | Uint8Array, - msg: Uint8Array, - ...roots: Array - ): boolean { - let pBytes: Uint8Array; - if (proof instanceof Uint8Array) { - pBytes = proof; - } else { - pBytes = proofToBytes(proof); - } - // calculate message length - const msgLen = writeUIntLE(new Uint8Array(8), msg.length, 0, 8); - - const rootsBytes = concatenate(...roots); - - return zerokitRLN.verifyWithRoots( - this.zkRLN, - concatenate(pBytes, msgLen, msg), - rootsBytes - ); - } - - verifyWithNoRoot( - proof: IRateLimitProof | Uint8Array, - msg: Uint8Array - ): boolean { - let pBytes: Uint8Array; - if (proof instanceof Uint8Array) { - pBytes = proof; - } else { - pBytes = proofToBytes(proof); - } - - // calculate message length - const msgLen = writeUIntLE(new Uint8Array(8), msg.length, 0, 8); - - return zerokitRLN.verifyWithRoots( - this.zkRLN, - concatenate(pBytes, msgLen, msg), - new Uint8Array() - ); - } } diff --git a/src/zerokit.ts b/src/zerokit.ts new file mode 100644 index 0000000..39d506f --- /dev/null +++ b/src/zerokit.ts @@ -0,0 +1,181 @@ +import type { IRateLimitProof } from "@waku/interfaces"; +import * as zerokitRLN from "@waku/zerokit-rln-wasm"; + +import { IdentityCredential } from "./identity.js"; +import { Proof, proofToBytes } from "./proof.js"; +import { WitnessCalculator } from "./resources/witness_calculator.js"; +import { + concatenate, + dateToEpoch, + epochIntToBytes, + writeUIntLE, +} from "./utils/index.js"; + +export class Zerokit { + constructor( + private zkRLN: number, + private witnessCalculator: WitnessCalculator + ) {} + + generateIdentityCredentials(): IdentityCredential { + const memKeys = zerokitRLN.generateExtendedMembershipKey(this.zkRLN); // TODO: rename this function in zerokit rln-wasm + return IdentityCredential.fromBytes(memKeys); + } + + generateSeededIdentityCredential(seed: string): IdentityCredential { + const stringEncoder = new TextEncoder(); + const seedBytes = stringEncoder.encode(seed); + // TODO: rename this function in zerokit rln-wasm + const memKeys = zerokitRLN.generateSeededExtendedMembershipKey( + this.zkRLN, + seedBytes + ); + return IdentityCredential.fromBytes(memKeys); + } + + insertMember(idCommitment: Uint8Array): void { + zerokitRLN.insertMember(this.zkRLN, idCommitment); + } + + insertMembers(index: number, ...idCommitments: Array): void { + // serializes a seq of IDCommitments to a byte seq + // the order of serialization is |id_commitment_len<8>|id_commitment| + const idCommitmentLen = writeUIntLE( + new Uint8Array(8), + idCommitments.length, + 0, + 8 + ); + const idCommitmentBytes = concatenate(idCommitmentLen, ...idCommitments); + zerokitRLN.setLeavesFrom(this.zkRLN, index, idCommitmentBytes); + } + + deleteMember(index: number): void { + zerokitRLN.deleteLeaf(this.zkRLN, index); + } + + getMerkleRoot(): Uint8Array { + return zerokitRLN.getRoot(this.zkRLN); + } + + serializeMessage( + uint8Msg: Uint8Array, + memIndex: number, + epoch: Uint8Array, + idKey: Uint8Array + ): Uint8Array { + // calculate message length + const msgLen = writeUIntLE(new Uint8Array(8), uint8Msg.length, 0, 8); + + // Converting index to LE bytes + const memIndexBytes = writeUIntLE(new Uint8Array(8), memIndex, 0, 8); + + // [ id_key<32> | id_index<8> | epoch<32> | signal_len<8> | signal ] + return concatenate(idKey, memIndexBytes, epoch, msgLen, uint8Msg); + } + + async generateRLNProof( + msg: Uint8Array, + index: number, + epoch: Uint8Array | Date | undefined, + idSecretHash: Uint8Array + ): Promise { + if (epoch == undefined) { + epoch = epochIntToBytes(dateToEpoch(new Date())); + } else if (epoch instanceof Date) { + epoch = epochIntToBytes(dateToEpoch(epoch)); + } + + if (epoch.length != 32) throw "invalid epoch"; + if (idSecretHash.length != 32) throw "invalid id secret hash"; + if (index < 0) throw "index must be >= 0"; + + const serialized_msg = this.serializeMessage( + msg, + index, + epoch, + idSecretHash + ); + const rlnWitness = zerokitRLN.getSerializedRLNWitness( + this.zkRLN, + serialized_msg + ); + const inputs = zerokitRLN.RLNWitnessToJson(this.zkRLN, rlnWitness); + const calculatedWitness = await this.witnessCalculator.calculateWitness( + inputs, + false + ); // no sanity check being used in zerokit + + const proofBytes = zerokitRLN.generate_rln_proof_with_witness( + this.zkRLN, + calculatedWitness, + rlnWitness + ); + + return new Proof(proofBytes); + } + + verifyRLNProof( + proof: IRateLimitProof | Uint8Array, + msg: Uint8Array + ): boolean { + let pBytes: Uint8Array; + if (proof instanceof Uint8Array) { + pBytes = proof; + } else { + pBytes = proofToBytes(proof); + } + + // calculate message length + const msgLen = writeUIntLE(new Uint8Array(8), msg.length, 0, 8); + + return zerokitRLN.verifyRLNProof( + this.zkRLN, + concatenate(pBytes, msgLen, msg) + ); + } + + verifyWithRoots( + proof: IRateLimitProof | Uint8Array, + msg: Uint8Array, + ...roots: Array + ): boolean { + let pBytes: Uint8Array; + if (proof instanceof Uint8Array) { + pBytes = proof; + } else { + pBytes = proofToBytes(proof); + } + // calculate message length + const msgLen = writeUIntLE(new Uint8Array(8), msg.length, 0, 8); + + const rootsBytes = concatenate(...roots); + + return zerokitRLN.verifyWithRoots( + this.zkRLN, + concatenate(pBytes, msgLen, msg), + rootsBytes + ); + } + + verifyWithNoRoot( + proof: IRateLimitProof | Uint8Array, + msg: Uint8Array + ): boolean { + let pBytes: Uint8Array; + if (proof instanceof Uint8Array) { + pBytes = proof; + } else { + pBytes = proofToBytes(proof); + } + + // calculate message length + const msgLen = writeUIntLE(new Uint8Array(8), msg.length, 0, 8); + + return zerokitRLN.verifyWithRoots( + this.zkRLN, + concatenate(pBytes, msgLen, msg), + new Uint8Array() + ); + } +}