diff --git a/README.md b/README.md index 00cb897..256a7b5 100644 --- a/README.md +++ b/README.md @@ -70,10 +70,10 @@ import * as rln from "@waku/rln"; const rlnInstance = await rln.create(); ``` -### Generating RLN Membership Keypair +#### Generating RLN Membership Credentials ```js -let memKeys = rlnInstance.generateMembershipKey(); +let credentials = rlnInstance.generateIdentityCredentials(); ``` ### Generating RLN Membership Keypair Using a Seed @@ -81,13 +81,13 @@ let memKeys = rlnInstance.generateMembershipKey(); This can be used to generate credentials from a signature hash (e.g. signed by an Ethereum account). ```js -let memKeys = rlnInstance.generateSeededMembershipKey(seed); +let credentials = rlnInstance.generateSeededIdentityCredentials(seed); ``` ### Adding Membership Keys Into Merkle Tree ```js -rlnInstance.insertMember(memKeys.IDCommitment); +rlnInstance.insertMember(credentials.IDCommitment); ``` ### Generating a Proof @@ -106,7 +106,7 @@ const proof = await rlnInstance.generateProof( uint8Msg, index, epoch, - memKeys.IDKey + credentials.IDSecretHash ); ``` diff --git a/example/index.js b/example/index.js index 643930e..dde6b22 100644 --- a/example/index.js +++ b/example/index.js @@ -1,7 +1,7 @@ import * as rln from "@waku/rln"; rln.create().then(async rlnInstance => { - let memKeys = rlnInstance.generateMembershipKey(); + const credentials = rlnInstance.generateIdentityCredentials(); //peer's index in the Merkle Tree const index = 5 @@ -10,11 +10,11 @@ rln.create().then(async rlnInstance => { for (let i = 0; i < 10; i++) { if (i == index) { // insert the current peer's pk - rlnInstance.insertMember(memKeys.IDCommitment); + rlnInstance.insertMember(credentials.IDCommitment); } else { // create a new key pair - let memKeys = rlnInstance.generateMembershipKey(); // TODO: handle error - rlnInstance.insertMember(memKeys.IDCommitment); + const credentials = rlnInstance.generateIdentityCredentials(); // TODO: handle error + rlnInstance.insertMember(credentials.IDCommitment); } } @@ -27,7 +27,7 @@ rln.create().then(async rlnInstance => { console.log("Generating proof..."); console.time("proof_gen_timer"); - let proof = await rlnInstance.generateRLNProof(uint8Msg, index, epoch, memKeys.IDKey) + let proof = await rlnInstance.generateRLNProof(uint8Msg, index, epoch, credentials.IDSecretHash) console.timeEnd("proof_gen_timer"); console.log("Proof", proof) diff --git a/karma.conf.cjs b/karma.conf.cjs index 893bfde..15316d1 100644 --- a/karma.conf.cjs +++ b/karma.conf.cjs @@ -30,10 +30,11 @@ module.exports = function (config) { envPreprocessor: ["CI"], reporters: ["progress"], browsers: ["ChromeHeadless"], + pingTimeout: 60000, singleRun: true, client: { mocha: { - timeout: 6000, // Default is 2s + timeout: 60000, // Default is 2s }, }, webpack: { diff --git a/package-lock.json b/package-lock.json index 1cd7c19..e6d8397 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,16 +1,16 @@ { "name": "@waku/rln", - "version": "0.0.14", + "version": "0.1.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@waku/rln", - "version": "0.0.14", + "version": "0.1.0", "license": "MIT OR Apache-2.0", "dependencies": { "@waku/utils": "^0.0.4", - "@waku/zerokit-rln-wasm": "^0.0.5", + "@waku/zerokit-rln-wasm": "^0.0.10", "ethers": "^5.7.2" }, "devDependencies": { @@ -2942,9 +2942,9 @@ } }, "node_modules/@waku/zerokit-rln-wasm": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/@waku/zerokit-rln-wasm/-/zerokit-rln-wasm-0.0.5.tgz", - "integrity": "sha512-uZHZRk06WrnqJJOVwIIKtsjWf2d6g2JpK8FtC0lHg4JJkOxhJy0pgEIuBCPw8Je4MpF9FCtIO/ww7xicdlC2GA==" + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/@waku/zerokit-rln-wasm/-/zerokit-rln-wasm-0.0.10.tgz", + "integrity": "sha512-qegIK1P54mxEp59uTa8C0/zidUffLc2Iee61yiKRIuGJDui2mQ+0V+KzPSPImKpIoqfVLT192EqgZkqPmj8VEw==" }, "node_modules/@web/rollup-plugin-import-meta-assets": { "version": "1.0.7", @@ -13629,9 +13629,9 @@ } }, "@waku/zerokit-rln-wasm": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/@waku/zerokit-rln-wasm/-/zerokit-rln-wasm-0.0.5.tgz", - "integrity": "sha512-uZHZRk06WrnqJJOVwIIKtsjWf2d6g2JpK8FtC0lHg4JJkOxhJy0pgEIuBCPw8Je4MpF9FCtIO/ww7xicdlC2GA==" + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/@waku/zerokit-rln-wasm/-/zerokit-rln-wasm-0.0.10.tgz", + "integrity": "sha512-qegIK1P54mxEp59uTa8C0/zidUffLc2Iee61yiKRIuGJDui2mQ+0V+KzPSPImKpIoqfVLT192EqgZkqPmj8VEw==" }, "@web/rollup-plugin-import-meta-assets": { "version": "1.0.7", diff --git a/package.json b/package.json index ffecfff..61940b4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@waku/rln", - "version": "0.0.14", + "version": "0.1.0", "description": "Rate Limit Nullifier for js-waku", "types": "./dist/index.d.ts", "module": "./dist/index.js", @@ -130,7 +130,7 @@ }, "dependencies": { "@waku/utils": "^0.0.4", - "@waku/zerokit-rln-wasm": "^0.0.5", + "@waku/zerokit-rln-wasm": "^0.0.10", "ethers": "^5.7.2" } } diff --git a/src/codec.spec.ts b/src/codec.spec.ts index b90ece7..6a9c98f 100644 --- a/src/codec.spec.ts +++ b/src/codec.spec.ts @@ -35,17 +35,17 @@ const EMPTY_PUBSUB_TOPIC = ""; describe("RLN codec with version 0", () => { it("toWire", async function () { const rlnInstance = await rln.create(); - const memKeys = rlnInstance.generateMembershipKey(); + const credential = rlnInstance.generateIdentityCredentials(); const index = 0; const payload = new Uint8Array([1, 2, 3, 4, 5]); - rlnInstance.insertMember(memKeys.IDCommitment); + rlnInstance.insertMember(credential.IDCommitment); const rlnEncoder = createRLNEncoder({ encoder: createEncoder({ contentTopic: TestContentTopic }), rlnInstance, index, - membershipKey: memKeys, + credential, }); const rlnDecoder = createRLNDecoder({ rlnInstance, @@ -76,17 +76,17 @@ describe("RLN codec with version 0", () => { it("toProtoObj", async function () { const rlnInstance = await rln.create(); - const memKeys = rlnInstance.generateMembershipKey(); + const credential = rlnInstance.generateIdentityCredentials(); const index = 0; const payload = new Uint8Array([1, 2, 3, 4, 5]); - rlnInstance.insertMember(memKeys.IDCommitment); + rlnInstance.insertMember(credential.IDCommitment); const rlnEncoder = new RLNEncoder( createEncoder({ contentTopic: TestContentTopic }), rlnInstance, index, - memKeys + credential ); const rlnDecoder = new RLNDecoder( rlnInstance, @@ -119,11 +119,11 @@ describe("RLN codec with version 0", () => { describe("RLN codec with version 1", () => { it("Symmetric, toWire", async function () { const rlnInstance = await rln.create(); - const memKeys = rlnInstance.generateMembershipKey(); + const credential = rlnInstance.generateIdentityCredentials(); const index = 0; const payload = new Uint8Array([1, 2, 3, 4, 5]); - rlnInstance.insertMember(memKeys.IDCommitment); + rlnInstance.insertMember(credential.IDCommitment); const symKey = generateSymmetricKey(); @@ -134,7 +134,7 @@ describe("RLN codec with version 1", () => { }), rlnInstance, index, - memKeys + credential ); const rlnDecoder = new RLNDecoder( rlnInstance, @@ -166,11 +166,11 @@ describe("RLN codec with version 1", () => { it("Symmetric, toProtoObj", async function () { const rlnInstance = await rln.create(); - const memKeys = rlnInstance.generateMembershipKey(); + const credential = rlnInstance.generateIdentityCredentials(); const index = 0; const payload = new Uint8Array([1, 2, 3, 4, 5]); - rlnInstance.insertMember(memKeys.IDCommitment); + rlnInstance.insertMember(credential.IDCommitment); const symKey = generateSymmetricKey(); @@ -181,7 +181,7 @@ describe("RLN codec with version 1", () => { }), rlnInstance, index, - memKeys + credential ); const rlnDecoder = new RLNDecoder( rlnInstance, @@ -212,11 +212,11 @@ describe("RLN codec with version 1", () => { it("Asymmetric, toWire", async function () { const rlnInstance = await rln.create(); - const memKeys = rlnInstance.generateMembershipKey(); + const credential = rlnInstance.generateIdentityCredentials(); const index = 0; const payload = new Uint8Array([1, 2, 3, 4, 5]); - rlnInstance.insertMember(memKeys.IDCommitment); + rlnInstance.insertMember(credential.IDCommitment); const privateKey = generatePrivateKey(); const publicKey = getPublicKey(privateKey); @@ -228,7 +228,7 @@ describe("RLN codec with version 1", () => { }), rlnInstance, index, - memKeys + credential ); const rlnDecoder = new RLNDecoder( rlnInstance, @@ -260,11 +260,11 @@ describe("RLN codec with version 1", () => { it("Asymmetric, toProtoObj", async function () { const rlnInstance = await rln.create(); - const memKeys = rlnInstance.generateMembershipKey(); + const credential = rlnInstance.generateIdentityCredentials(); const index = 0; const payload = new Uint8Array([1, 2, 3, 4, 5]); - rlnInstance.insertMember(memKeys.IDCommitment); + rlnInstance.insertMember(credential.IDCommitment); const privateKey = generatePrivateKey(); const publicKey = getPublicKey(privateKey); @@ -276,7 +276,7 @@ describe("RLN codec with version 1", () => { }), rlnInstance, index, - memKeys + credential ); const rlnDecoder = new RLNDecoder( rlnInstance, @@ -309,17 +309,17 @@ describe("RLN codec with version 1", () => { describe("RLN Codec - epoch", () => { it("toProtoObj", async function () { const rlnInstance = await rln.create(); - const memKeys = rlnInstance.generateMembershipKey(); + const credential = rlnInstance.generateIdentityCredentials(); const index = 0; const payload = new Uint8Array([1, 2, 3, 4, 5]); - rlnInstance.insertMember(memKeys.IDCommitment); + rlnInstance.insertMember(credential.IDCommitment); const rlnEncoder = new RLNEncoder( createEncoder({ contentTopic: TestContentTopic }), rlnInstance, index, - memKeys + credential ); const rlnDecoder = new RLNDecoder( rlnInstance, diff --git a/src/codec.ts b/src/codec.ts index f2af6f4..edb815f 100644 --- a/src/codec.ts +++ b/src/codec.ts @@ -9,21 +9,21 @@ import type { import debug from "debug"; import { RlnMessage, toRLNSignal } from "./message.js"; -import { MembershipKey, RLNInstance } from "./rln.js"; +import { IdentityCredential, RLNInstance } from "./rln.js"; const log = debug("waku:rln:encoder"); export class RLNEncoder implements IEncoder { - private readonly idKey: Uint8Array; + private readonly idSecretHash: Uint8Array; constructor( private encoder: IEncoder, private rlnInstance: RLNInstance, private index: number, - membershipKey: MembershipKey + identityCredential: IdentityCredential ) { if (index < 0) throw "invalid membership index"; - this.idKey = membershipKey.IDKey; + this.idSecretHash = identityCredential.IDSecretHash; } async toWire(message: IMessage): Promise { @@ -50,7 +50,7 @@ export class RLNEncoder implements IEncoder { signal, this.index, message.timestamp, - this.idKey + this.idSecretHash ); console.timeEnd("proof_gen_timer"); return proof; @@ -69,7 +69,7 @@ type RLNEncoderOptions = { encoder: IEncoder; rlnInstance: RLNInstance; index: number; - membershipKey: MembershipKey; + credential: IdentityCredential; }; export const createRLNEncoder = (options: RLNEncoderOptions): RLNEncoder => { @@ -77,7 +77,7 @@ export const createRLNEncoder = (options: RLNEncoderOptions): RLNEncoder => { options.encoder, options.rlnInstance, options.index, - options.membershipKey + options.credential ); }; diff --git a/src/index.spec.ts b/src/index.spec.ts index 83ac546..cd231ce 100644 --- a/src/index.spec.ts +++ b/src/index.spec.ts @@ -6,7 +6,7 @@ describe("js-rln", () => { it("should verify a proof", async function () { const rlnInstance = await rln.create(); - const memKeys = rlnInstance.generateMembershipKey(); + const credential = rlnInstance.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(memKeys.IDCommitment); + rlnInstance.insertMember(credential.IDCommitment); } else { // create a new key pair rlnInstance.insertMember( - rlnInstance.generateMembershipKey().IDCommitment + rlnInstance.generateIdentityCredentials().IDCommitment ); } } @@ -37,7 +37,7 @@ describe("js-rln", () => { uint8Msg, index, epoch, - memKeys.IDKey + credential.IDSecretHash ); try { @@ -61,7 +61,7 @@ describe("js-rln", () => { it("should verify a proof with a seeded membership key generation", async function () { const rlnInstance = await rln.create(); const seed = "This is a test seed"; - const memKeys = rlnInstance.generateSeededMembershipKey(seed); + const credential = rlnInstance.generateSeededIdentityCredential(seed); //peer's index in the Merkle Tree const index = 5; @@ -70,11 +70,11 @@ describe("js-rln", () => { for (let i = 0; i < 10; i++) { if (i == index) { // insert the current peer's pk - rlnInstance.insertMember(memKeys.IDCommitment); + rlnInstance.insertMember(credential.IDCommitment); } else { // create a new key pair rlnInstance.insertMember( - rlnInstance.generateMembershipKey().IDCommitment + rlnInstance.generateIdentityCredentials().IDCommitment ); } } @@ -92,7 +92,7 @@ describe("js-rln", () => { uint8Msg, index, epoch, - memKeys.IDKey + credential.IDSecretHash ); try { @@ -116,14 +116,20 @@ describe("js-rln", () => { it("should generate the same membership key if the same seed is provided", async function () { const rlnInstance = await rln.create(); const seed = "This is a test seed"; - const memKeys1 = rlnInstance.generateSeededMembershipKey(seed); - const memKeys2 = rlnInstance.generateSeededMembershipKey(seed); + const memKeys1 = rlnInstance.generateSeededIdentityCredential(seed); + const memKeys2 = rlnInstance.generateSeededIdentityCredential(seed); memKeys1.IDCommitment.forEach((element, index) => { expect(element).to.equal(memKeys2.IDCommitment[index]); }); - memKeys1.IDKey.forEach((element, index) => { - expect(element).to.equal(memKeys2.IDKey[index]); + memKeys1.IDNullifier.forEach((element, index) => { + expect(element).to.equal(memKeys2.IDNullifier[index]); + }); + memKeys1.IDSecretHash.forEach((element, index) => { + expect(element).to.equal(memKeys2.IDSecretHash[index]); + }); + memKeys1.IDTrapdoor.forEach((element, index) => { + expect(element).to.equal(memKeys2.IDTrapdoor[index]); }); }); }); diff --git a/src/index.ts b/src/index.ts index 34e5f7e..d127c19 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,13 @@ import { RLNDecoder, RLNEncoder } from "./codec.js"; import { GOERLI_CONTRACT, RLN_ABI } from "./constants.js"; -import { Proof, RLNInstance } from "./rln.js"; -import { MembershipKey } from "./rln.js"; +import { + IdentityCredential, + Proof, + ProofMetadata, + RLNInstance, +} from "./rln.js"; import { RLNContract } from "./rln_contract.js"; +import { MerkleRootTracker } from "./root_tracker.js"; // reexport the create function, dynamically imported from rln.ts export async function create(): Promise { @@ -15,10 +20,12 @@ export async function create(): Promise { export { RLNInstance, - MembershipKey, + IdentityCredential, Proof, + ProofMetadata, RLNEncoder, RLNDecoder, + MerkleRootTracker, RLNContract, RLN_ABI, GOERLI_CONTRACT, diff --git a/src/resources/rln.wasm b/src/resources/rln.wasm index b670603..04aaeef 100644 Binary files a/src/resources/rln.wasm and b/src/resources/rln.wasm differ diff --git a/src/resources/rln_final.zkey b/src/resources/rln_final.zkey index f6bc6d9..c6cc7d4 100644 Binary files a/src/resources/rln_final.zkey and b/src/resources/rln_final.zkey differ diff --git a/src/resources/verification_key.js b/src/resources/verification_key.js index 5606389..53a660a 100644 --- a/src/resources/verification_key.js +++ b/src/resources/verification_key.js @@ -1,120 +1,112 @@ const verificationKey = { - "protocol": "groth16", - "curve": "bn128", - "nPublic": 6, - "vk_alpha_1": [ - "1805378556360488226980822394597799963030511477964155500103132920745199284516", - "11990395240534218699464972016456017378439762088320057798320175886595281336136", - "1" + protocol: "groth16", + curve: "bn128", + nPublic: 6, + vk_alpha_1: [ + "20124996762962216725442980738609010303800849578410091356605067053491763969391", + "9118593021526896828671519912099489027245924097793322973632351264852174143923", + "1", ], - "vk_beta_2": [ + vk_beta_2: [ [ - "11031529986141021025408838211017932346992429731488270384177563837022796743627", - "16042159910707312759082561183373181639420894978640710177581040523252926273854" + "4693952934005375501364248788849686435240706020501681709396105298107971354382", + "14346958885444710485362620645446987998958218205939139994511461437152241966681", ], [ - "20112698439519222240302944148895052359035104222313380895334495118294612255131", - "19441583024670359810872018179190533814486480928824742448673677460151702019379" + "16851772916911573982706166384196538392731905827088356034885868448550849804972", + "823612331030938060799959717749043047845343400798220427319188951998582076532", ], - [ - "1", - "0" - ] + ["1", "0"], ], - "vk_gamma_2": [ + vk_gamma_2: [ [ "10857046999023057135944570762232829481370756359578518086990519993285655852781", - "11559732032986387107991004021392285783925812861821192530917403151452391805634" + "11559732032986387107991004021392285783925812861821192530917403151452391805634", ], [ "8495653923123431417604973247489272438418190587263600148770280649306958101930", - "4082367875863433681332203403145435568316851327593401208105741076214120093531" + "4082367875863433681332203403145435568316851327593401208105741076214120093531", ], - [ - "1", - "0" - ] + ["1", "0"], ], - "vk_delta_2": [ + vk_delta_2: [ [ - "1948496782571164085469528023647105317580208688174386157591917599801657832035", - "20445814069256658101339037520922621162739470138213615104905368409238414511981" + "8353516066399360694538747105302262515182301251524941126222712285088022964076", + "9329524012539638256356482961742014315122377605267454801030953882967973561832", ], [ - "10024680869920840984813249386422727863826862577760330492647062850849851925340", - "10512156247842686783409460795717734694774542185222602679117887145206209285142" + "16805391589556134376869247619848130874761233086443465978238468412168162326401", + "10111259694977636294287802909665108497237922060047080343914303287629927847739", ], - [ - "1", - "0" - ] + ["1", "0"], ], - "vk_alphabeta_12": [ + vk_alphabeta_12: [ [ [ - "5151991366823434428398919091000210787450832786814248297320989361921939794156", - "15735191313289001022885148627913534790382722933676436876510746491415970766821" + "12608968655665301215455851857466367636344427685631271961542642719683786103711", + "9849575605876329747382930567422916152871921500826003490242628251047652318086", ], [ - "3387907257437913904447588318761906430938415556102110876587455322225272831272", - "1998779853452712881084781956683721603875246565720647583735935725110674288056" + "6322029441245076030714726551623552073612922718416871603535535085523083939021", + "8700115492541474338049149013125102281865518624059015445617546140629435818912", ], [ - "14280074182991498185075387990446437410077692353432005297922275464876153151820", - "17092408446352310039633488224969232803092763095456307462247653153107223117633" - ] + "10674973475340072635573101639867487770811074181475255667220644196793546640210", + "2926286967251299230490668407790788696102889214647256022788211245826267484824", + ], ], [ [ - "4359046709531668109201634396816565829237358165496082832279660960675584351266", - "4511888308846208349307186938266411423935335853916317436093178288331845821336" + "9660441540778523475944706619139394922744328902833875392144658911530830074820", + "19548113127774514328631808547691096362144426239827206966690021428110281506546", ], [ - "11429499807090785857812316277335883295048773373068683863667725283965356423273", - "16232274853200678548795010078253506586114563833318973594428907292096178657392" + "1870837942477655969123169532603615788122896469891695773961478956740992497097", + "12536105729661705698805725105036536744930776470051238187456307227425796690780", ], [ - "18068999605870933925311275504102553573815570223888590384919752303726860800970", - "17309569111965782732372130116757295842160193489132771344011460471298173784984" - ] - ] + "21811903352654147452884857281720047789720483752548991551595462057142824037334", + "19021616763967199151052893283384285352200445499680068407023236283004353578353", + ], + ], ], - "IC": [ + IC: [ [ - "18693301901828818437917730940595978397160482710354161265484535387752523310572", - "17985273354976640088538673802000794244421192643855111089693820179790551470769", - "1" + "11992897507809711711025355300535923222599547639134311050809253678876341466909", + "17181525095924075896332561978747020491074338784673526378866503154966799128110", + "1", ], [ - "21164641723988537620541455173278629777250883365474191521194244273980931825942", - "998385854410718613441067082771678946155853656328717326195057262123686425518", - "1" + "17018665030246167677911144513385572506766200776123272044534328594850561667818", + "18601114175490465275436712413925513066546725461375425769709566180981674884464", + "1", ], [ - "21666968581672145768705229094968410656430989593283335488162701230986314747515", - "17996457608540683483506630273632100555125353447506062045735279661096094677264", - "1" + "18799470100699658367834559797874857804183288553462108031963980039244731716542", + "13064227487174191981628537974951887429496059857753101852163607049188825592007", + "1", ], [ - "20137761979695192602424300886442379728165712610493092740175904438282083668117", - "19184814924890679891263780109959113289320127263583260218200636509492157834679", - "1" + "17432501889058124609368103715904104425610382063762621017593209214189134571156", + "13406815149699834788256141097399354592751313348962590382887503595131085938635", + "1", ], [ - "10943171273393803842589314082509655332154393332394322726077270895078286354146", - "10872472035685319847811233167729172672344935625121511932198535224727331126439", - "1" + "10320964835612716439094703312987075811498239445882526576970512041988148264481", + "9024164961646353611176283204118089412001502110138072989569118393359029324867", + "1", ], [ - "13049169779481227658517545034348883391527506091990880778783387628208561946597", - "10083689369261379027228809473568899816311684698866922944902456565434209079955", - "1" + "718355081067365548229685160476620267257521491773976402837645005858953849298", + "14635482993933988261008156660773180150752190597753512086153001683711587601974", + "1", ], [ - "19633516378466409167014413361365552102431118630694133723053441455184566611083", - "8059525100726933978719058611146131904598011633549012007359165766216730722269", - "1" - ] - ] -} -export default verificationKey + "11777720285956632126519898515392071627539405001940313098390150593689568177535", + "8483603647274280691250972408211651407952870456587066148445913156086740744515", + "1", + ], + ], +}; + +export default verificationKey; diff --git a/src/rln.ts b/src/rln.ts index 203ff91..f30833c 100644 --- a/src/rln.ts +++ b/src/rln.ts @@ -66,18 +66,29 @@ export async function create(): Promise { return new RLNInstance(zkRLN, witnessCalculator); } -export class MembershipKey { +export class IdentityCredential { constructor( - public readonly IDKey: Uint8Array, + public readonly IDTrapdoor: Uint8Array, + public readonly IDNullifier: Uint8Array, + public readonly IDSecretHash: Uint8Array, public readonly IDCommitment: Uint8Array, public readonly IDCommitmentBigInt: bigint ) {} - static fromBytes(memKeys: Uint8Array): MembershipKey { - const idKey = memKeys.subarray(0, 32); - const idCommitment = memKeys.subarray(32); + static fromBytes(memKeys: Uint8Array): IdentityCredential { + const idTrapdoor = memKeys.subarray(0, 32); + const idNullifier = memKeys.subarray(32, 64); + const idSecretHash = memKeys.subarray(64, 96); + const idCommitment = memKeys.subarray(96); const idCommitmentBigInt = buildBigIntFromUint8Array(idCommitment); - return new MembershipKey(idKey, idCommitment, idCommitmentBigInt); + + return new IdentityCredential( + idTrapdoor, + idNullifier, + idSecretHash, + idCommitment, + idCommitmentBigInt + ); } } @@ -89,6 +100,14 @@ const shareYOffset = shareXOffset + 32; const nullifierOffset = shareYOffset + 32; const rlnIdentifierOffset = nullifierOffset + 32; +export class ProofMetadata { + constructor( + public readonly nullifier: Uint8Array, + public readonly shareX: Uint8Array, + public readonly shareY: Uint8Array, + public readonly externalNullifier: Uint8Array + ) {} +} export class Proof implements IRateLimitProof { readonly proof: Uint8Array; readonly merkleRoot: Uint8Array; @@ -112,6 +131,16 @@ export class Proof implements IRateLimitProof { rlnIdentifierOffset ); } + + extractMetadata(): ProofMetadata { + const externalNullifier = poseidonHash(this.epoch, this.rlnIdentifier); + return new ProofMetadata( + this.nullifier, + this.shareX, + this.shareY, + externalNullifier + ); + } } export function proofToBytes(p: IRateLimitProof): Uint8Array { @@ -126,30 +155,60 @@ export function proofToBytes(p: IRateLimitProof): Uint8Array { ); } +export function poseidonHash(...input: Array): Uint8Array { + const inputLen = writeUIntLE(new Uint8Array(8), input.length, 0, 8); + const lenPrefixedData = concatenate(inputLen, ...input); + return zerokitRLN.poseidonHash(lenPrefixedData); +} + +export function sha256(input: Uint8Array): Uint8Array { + const inputLen = writeUIntLE(new Uint8Array(8), input.length, 0, 8); + const lenPrefixedData = concatenate(inputLen, input); + return zerokitRLN.hash(lenPrefixedData); +} + export class RLNInstance { constructor( private zkRLN: number, private witnessCalculator: WitnessCalculator ) {} - generateMembershipKey(): MembershipKey { - const memKeys = zerokitRLN.generateMembershipKey(this.zkRLN); - return MembershipKey.fromBytes(memKeys); + generateIdentityCredentials(): IdentityCredential { + const memKeys = zerokitRLN.generateExtendedMembershipKey(this.zkRLN); // TODO: rename this function in zerokit rln-wasm + return IdentityCredential.fromBytes(memKeys); } - generateSeededMembershipKey(seed: string): MembershipKey { + generateSeededIdentityCredential(seed: string): IdentityCredential { const seedBytes = stringEncoder.encode(seed); - const memKeys = zerokitRLN.generateSeededMembershipKey( + // TODO: rename this function in zerokit rln-wasm + const memKeys = zerokitRLN.generateSeededExtendedMembershipKey( this.zkRLN, seedBytes ); - return MembershipKey.fromBytes(memKeys); + 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); } @@ -174,7 +233,7 @@ export class RLNInstance { msg: Uint8Array, index: number, epoch: Uint8Array | Date | undefined, - idKey: Uint8Array + idSecretHash: Uint8Array ): Promise { if (epoch == undefined) { epoch = epochIntToBytes(dateToEpoch(new Date())); @@ -183,10 +242,15 @@ export class RLNInstance { } if (epoch.length != 32) throw "invalid epoch"; - if (idKey.length != 32) throw "invalid id key"; + 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, idKey); + const serialized_msg = this.serializeMessage( + msg, + index, + epoch, + idSecretHash + ); const rlnWitness = zerokitRLN.getSerializedRLNWitness( this.zkRLN, serialized_msg @@ -228,7 +292,8 @@ export class RLNInstance { verifyWithRoots( proof: IRateLimitProof | Uint8Array, - msg: Uint8Array + msg: Uint8Array, + ...roots: Array ): boolean { let pBytes: Uint8Array; if (proof instanceof Uint8Array) { @@ -236,17 +301,15 @@ export class RLNInstance { } else { pBytes = proofToBytes(proof); } - // calculate message length const msgLen = writeUIntLE(new Uint8Array(8), msg.length, 0, 8); - // obtain root - const root = zerokitRLN.getRoot(this.zkRLN); + const rootsBytes = concatenate(...roots); return zerokitRLN.verifyWithRoots( this.zkRLN, concatenate(pBytes, msgLen, msg), - root + rootsBytes ); } diff --git a/src/rln_contract.ts b/src/rln_contract.ts index 9e24035..b241e4b 100644 --- a/src/rln_contract.ts +++ b/src/rln_contract.ts @@ -1,7 +1,7 @@ import { ethers } from "ethers"; import { RLN_ABI } from "./constants.js"; -import { MembershipKey, RLNInstance } from "./rln.js"; +import { IdentityCredential, RLNInstance } from "./rln.js"; type Member = { pubkey: string; @@ -94,20 +94,19 @@ export class RLNContract { rlnInstance: RLNInstance, signature: string ): Promise { - const membershipKey = await rlnInstance.generateSeededMembershipKey( - signature - ); + const identityCredential = + await rlnInstance.generateSeededIdentityCredential(signature); - return this.registerWithKey(membershipKey); + return this.registerWithKey(identityCredential); } public async registerWithKey( - membershipKey: MembershipKey + credential: IdentityCredential ): Promise { const depositValue = await this.contract.MEMBERSHIP_DEPOSIT(); const txRegisterResponse: ethers.ContractTransaction = - await this.contract.register(membershipKey.IDCommitmentBigInt, { + await this.contract.register(credential.IDCommitmentBigInt, { value: depositValue, }); const txRegisterReceipt = await txRegisterResponse.wait(); diff --git a/src/root_tracker.spec.ts b/src/root_tracker.spec.ts new file mode 100644 index 0000000..d34e768 --- /dev/null +++ b/src/root_tracker.spec.ts @@ -0,0 +1,56 @@ +import { assert, expect } from "chai"; + +import { MerkleRootTracker } from "./root_tracker"; + +describe("js-rln", () => { + it("should track merkle roots and backfill from block number", async function () { + const acceptableRootWindow = 3; + + const tracker = new MerkleRootTracker( + acceptableRootWindow, + new Uint8Array([0, 0, 0, 0]) + ); + expect(tracker.roots()).to.have.length(1); + expect(tracker.buffer()).to.have.length(0); + expect(tracker.roots()[0]).to.deep.equal(new Uint8Array([0, 0, 0, 0])); + + for (let i = 1; i <= 30; i++) { + tracker.pushRoot(i, new Uint8Array([0, 0, 0, i])); + } + + expect(tracker.roots()).to.have.length(acceptableRootWindow); + expect(tracker.buffer()).to.have.length(20); + assert.sameDeepMembers(tracker.roots(), [ + new Uint8Array([0, 0, 0, 30]), + new Uint8Array([0, 0, 0, 29]), + new Uint8Array([0, 0, 0, 28]), + ]); + + // Buffer should keep track of 20 blocks previous to the current valid merkle root window + expect(tracker.buffer()[0]).to.be.eql(new Uint8Array([0, 0, 0, 8])); + expect(tracker.buffer()[19]).to.be.eql(new Uint8Array([0, 0, 0, 27])); + + // Remove roots 29 and 30 + tracker.backFill(29); + assert.sameDeepMembers(tracker.roots(), [ + new Uint8Array([0, 0, 0, 28]), + new Uint8Array([0, 0, 0, 27]), + new Uint8Array([0, 0, 0, 26]), + ]); + + expect(tracker.buffer()).to.have.length(18); + expect(tracker.buffer()[0]).to.be.eql(new Uint8Array([0, 0, 0, 8])); + expect(tracker.buffer()[17]).to.be.eql(new Uint8Array([0, 0, 0, 25])); + + // Remove roots from block 15 onwards. These blocks exists within the buffer + tracker.backFill(15); + assert.sameDeepMembers(tracker.roots(), [ + new Uint8Array([0, 0, 0, 14]), + new Uint8Array([0, 0, 0, 13]), + new Uint8Array([0, 0, 0, 12]), + ]); + expect(tracker.buffer()).to.have.length(4); + expect(tracker.buffer()[0]).to.be.eql(new Uint8Array([0, 0, 0, 8])); + expect(tracker.buffer()[3]).to.be.eql(new Uint8Array([0, 0, 0, 11])); + }); +}); diff --git a/src/root_tracker.ts b/src/root_tracker.ts new file mode 100644 index 0000000..d648d0c --- /dev/null +++ b/src/root_tracker.ts @@ -0,0 +1,88 @@ +class RootPerBlock { + constructor(public root: Uint8Array, public blockNumber: number) {} +} + +const maxBufferSize = 20; + +export class MerkleRootTracker { + private validMerkleRoots: Array = new Array(); + private merkleRootBuffer: Array = new Array(); + constructor( + private acceptableRootWindowSize: number, + initialRoot: Uint8Array + ) { + this.pushRoot(0, initialRoot); + } + + backFill(fromBlockNumber: number): void { + if (this.validMerkleRoots.length == 0) return; + + let numBlocks = 0; + for (let i = this.validMerkleRoots.length - 1; i >= 0; i--) { + if (this.validMerkleRoots[i].blockNumber >= fromBlockNumber) { + numBlocks++; + } + } + + if (numBlocks == 0) return; + + const olderBlock = fromBlockNumber < this.validMerkleRoots[0].blockNumber; + + // Remove last roots + let rootsToPop = numBlocks; + if (this.validMerkleRoots.length < rootsToPop) { + rootsToPop = this.validMerkleRoots.length; + } + + this.validMerkleRoots = this.validMerkleRoots.slice( + 0, + this.validMerkleRoots.length - rootsToPop + ); + + if (this.merkleRootBuffer.length == 0) return; + + if (olderBlock) { + const idx = this.merkleRootBuffer.findIndex( + (x) => x.blockNumber == fromBlockNumber + ); + if (idx > -1) { + this.merkleRootBuffer = this.merkleRootBuffer.slice(0, idx); + } + } + + // Backfill the tree's acceptable roots + let rootsToRestore = + this.acceptableRootWindowSize - this.validMerkleRoots.length; + if (this.merkleRootBuffer.length < rootsToRestore) { + rootsToRestore = this.merkleRootBuffer.length; + } + + for (let i = 0; i < rootsToRestore; i++) { + const x = this.merkleRootBuffer.pop(); + if (x) this.validMerkleRoots.unshift(x); + } + } + + pushRoot(blockNumber: number, root: Uint8Array): void { + this.validMerkleRoots.push(new RootPerBlock(root, blockNumber)); + + // Maintain valid merkle root window + if (this.validMerkleRoots.length > this.acceptableRootWindowSize) { + const x = this.validMerkleRoots.shift(); + if (x) this.merkleRootBuffer.push(x); + } + + // Maintain merkle root buffer + if (this.merkleRootBuffer.length > maxBufferSize) { + this.merkleRootBuffer.shift(); + } + } + + roots(): Array { + return this.validMerkleRoots.map((x) => x.root); + } + + buffer(): Array { + return this.merkleRootBuffer.map((x) => x.root); + } +}