From 2ef425241192549bfe3236b8e95aa833b9726f19 Mon Sep 17 00:00:00 2001 From: Sasha Date: Tue, 23 Jan 2024 23:00:52 +0100 Subject: [PATCH 1/7] add MetaMask abstraction, add default start to RLN Instance, expose RLN Contract --- src/create.ts | 9 +++++++++ src/index.ts | 11 ++--------- src/metamask.ts | 15 +++++++++++++++ src/rln.ts | 31 +++++++++++++++++++++++++++++++ 4 files changed, 57 insertions(+), 9 deletions(-) create mode 100644 src/create.ts create mode 100644 src/metamask.ts diff --git a/src/create.ts b/src/create.ts new file mode 100644 index 0000000..9d7f6e0 --- /dev/null +++ b/src/create.ts @@ -0,0 +1,9 @@ +import type { RLNInstance } from "./rln.js"; + +export async function create(): Promise { + // A dependency graph that contains any wasm must all be imported + // asynchronously. This file does the single async import, so + // that no one else needs to worry about it again. + const rlnModule = await import("./rln.js"); + return rlnModule.create(); +} diff --git a/src/index.ts b/src/index.ts index 0d27230..6b53efc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,6 +4,7 @@ import { RLN_STORAGE_ABI, SEPOLIA_CONTRACT, } from "./constants.js"; +import { create } from "./create.js"; import { Keystore } from "./keystore/index.js"; import { IdentityCredential, @@ -14,16 +15,8 @@ import { 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 { - // A dependency graph that contains any wasm must all be imported - // asynchronously. This file does the single async import, so - // that no one else needs to worry about it again. - const rlnModule = await import("./rln.js"); - return await rlnModule.create(); -} - export { + create, Keystore, RLNInstance, IdentityCredential, diff --git a/src/metamask.ts b/src/metamask.ts new file mode 100644 index 0000000..69e3084 --- /dev/null +++ b/src/metamask.ts @@ -0,0 +1,15 @@ +import { ethers } from "ethers"; + +export const extractMetaMaskAccount = + async (): Promise => { + const ethereum = (window as any).ethereum; + + if (!ethereum) { + throw Error( + "Missing or invalid Ethereum provider. Please install MetaMask." + ); + } + + await ethereum.request({ method: "eth_requestAccounts" }); + return new ethers.providers.Web3Provider(ethereum, "any"); + }; diff --git a/src/rln.ts b/src/rln.ts index ccab1ac..3a770ab 100644 --- a/src/rln.ts +++ b/src/rln.ts @@ -1,10 +1,14 @@ import type { IRateLimitProof } from "@waku/interfaces"; import init from "@waku/zerokit-rln-wasm"; import * as zerokitRLN from "@waku/zerokit-rln-wasm"; +import { ethers } from "ethers"; import { buildBigIntFromUint8Array, writeUIntLE } from "./byte_utils.js"; +import { SEPOLIA_CONTRACT } from "./constants.js"; import { dateToEpoch, epochIntToBytes } from "./epoch.js"; +import { extractMetaMaskAccount } from "./metamask.js"; import verificationKey from "./resources/verification_key.js"; +import { RLNContract } from "./rln_contract.js"; import * as wc from "./witness_calculator.js"; import { WitnessCalculator } from "./witness_calculator.js"; @@ -158,12 +162,39 @@ export function sha256(input: Uint8Array): Uint8Array { return zerokitRLN.hash(lenPrefixedData); } +type StartRLNOptions = { + /** + * If not set - will extract MetaMask account and get provider from it. + */ + provider?: ethers.providers.Web3Provider; + /** + * If not set - will use default SEPOLIA_CONTRACT address. + */ + registryAddress?: string; +}; + export class RLNInstance { + private _contract: null | RLNContract = null; + constructor( private zkRLN: number, private witnessCalculator: WitnessCalculator ) {} + public get contract(): null | RLNContract { + return this._contract; + } + + public async start(options: StartRLNOptions = {}): Promise { + const provider = options.provider || (await extractMetaMaskAccount()); + const registryAddress = options.registryAddress || SEPOLIA_CONTRACT.address; + + this._contract = await RLNContract.init(this, { + registryAddress, + provider: provider.getSigner(), + }); + } + generateIdentityCredentials(): IdentityCredential { const memKeys = zerokitRLN.generateExtendedMembershipKey(this.zkRLN); // TODO: rename this function in zerokit rln-wasm return IdentityCredential.fromBytes(memKeys); From cadb0e9543b707a1b774f6572b2c8dee6a6cc4b4 Mon Sep 17 00:00:00 2001 From: Sasha Date: Tue, 23 Jan 2024 23:02:21 +0100 Subject: [PATCH 2/7] remove console timer --- src/codec.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/codec.ts b/src/codec.ts index a614fe1..1a093c7 100644 --- a/src/codec.ts +++ b/src/codec.ts @@ -44,15 +44,12 @@ export class RLNEncoder implements IEncoder { private async generateProof(message: IMessage): Promise { const signal = toRLNSignal(this.contentTopic, message); - - console.time("proof_gen_timer"); const proof = await this.rlnInstance.generateRLNProof( signal, this.index, message.timestamp, this.idSecretHash ); - console.timeEnd("proof_gen_timer"); return proof; } From 2bdc3c2530e24851dbcc4876c66ddeafb3f2d53f Mon Sep 17 00:00:00 2001 From: Sasha Date: Wed, 24 Jan 2024 20:37:10 +0100 Subject: [PATCH 3/7] improve text --- src/metamask.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/metamask.ts b/src/metamask.ts index 69e3084..e171205 100644 --- a/src/metamask.ts +++ b/src/metamask.ts @@ -6,7 +6,7 @@ export const extractMetaMaskAccount = if (!ethereum) { throw Error( - "Missing or invalid Ethereum provider. Please install MetaMask." + "Missing or invalid Ethereum provider. Please install a Web3 wallet such as MetaMask." ); } From 82a986437a4700513fe9b9e22e2352031531078e Mon Sep 17 00:00:00 2001 From: Sasha Date: Wed, 24 Jan 2024 20:45:50 +0100 Subject: [PATCH 4/7] rename to createRLN --- src/create.ts | 2 +- src/index.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/create.ts b/src/create.ts index 9d7f6e0..3b32302 100644 --- a/src/create.ts +++ b/src/create.ts @@ -1,6 +1,6 @@ import type { RLNInstance } from "./rln.js"; -export async function create(): Promise { +export async function createRLN(): Promise { // A dependency graph that contains any wasm must all be imported // asynchronously. This file does the single async import, so // that no one else needs to worry about it again. diff --git a/src/index.ts b/src/index.ts index 6b53efc..ae89bec 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,7 +4,7 @@ import { RLN_STORAGE_ABI, SEPOLIA_CONTRACT, } from "./constants.js"; -import { create } from "./create.js"; +import { createRLN } from "./create.js"; import { Keystore } from "./keystore/index.js"; import { IdentityCredential, @@ -16,7 +16,7 @@ import { RLNContract } from "./rln_contract.js"; import { MerkleRootTracker } from "./root_tracker.js"; export { - create, + createRLN, Keystore, RLNInstance, IdentityCredential, From 453085245e0ed419987a665de14445d3199b0352 Mon Sep 17 00:00:00 2001 From: Sasha Date: Wed, 24 Jan 2024 20:59:00 +0100 Subject: [PATCH 5/7] update README, fix tests --- README.md | 22 +++++++++++++++++++++- src/codec.spec.ts | 18 +++++++++--------- src/index.spec.ts | 6 +++--- src/rln_contract.spec.ts | 4 ++-- 4 files changed, 35 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 9c58302..87eb2a5 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,16 @@ Browse http://localhost:8080 and open the dev tools console to see the proof bei ```js import * as rln from "@waku/rln"; -const rlnInstance = await rln.create(); +const rlnInstance = await rln.createRLN(); +``` + +### Starting RLN to listen to a contract + +```js +import * as rln from "@waku/rln"; + +const rlnInstance = await rln.createRLN(); +await rlnInstance.start(); // will use default Sepolia contract ``` #### Generating RLN Membership Credentials @@ -94,6 +103,17 @@ let credentials = rlnInstance.generateSeededIdentityCredentials(seed); rlnInstance.insertMember(credentials.IDCommitment); ``` +### Registering Membership on a contract + +```js +import * as rln from "@waku/rln"; + +const rlnInstance = await rln.createRLN(); +await rlnInstance.start(); // will use default Sepolia contract + +const membershipInfo = await rlnInstance.contract.registerWithKey(credentials); +``` + ### Generating a Proof ```js diff --git a/src/codec.spec.ts b/src/codec.spec.ts index 5f6249f..a707b4b 100644 --- a/src/codec.spec.ts +++ b/src/codec.spec.ts @@ -44,7 +44,7 @@ const EMPTY_PROTO_MESSAGE = { describe("RLN codec with version 0", () => { it("toWire", async function () { - const rlnInstance = await rln.create(); + const rlnInstance = await rln.createRLN(); const credential = rlnInstance.generateIdentityCredentials(); const index = 0; const payload = new Uint8Array([1, 2, 3, 4, 5]); @@ -85,7 +85,7 @@ describe("RLN codec with version 0", () => { }); it("toProtoObj", async function () { - const rlnInstance = await rln.create(); + const rlnInstance = await rln.createRLN(); const credential = rlnInstance.generateIdentityCredentials(); const index = 0; const payload = new Uint8Array([1, 2, 3, 4, 5]); @@ -128,7 +128,7 @@ describe("RLN codec with version 0", () => { describe("RLN codec with version 1", () => { it("Symmetric, toWire", async function () { - const rlnInstance = await rln.create(); + const rlnInstance = await rln.createRLN(); const credential = rlnInstance.generateIdentityCredentials(); const index = 0; const payload = new Uint8Array([1, 2, 3, 4, 5]); @@ -175,7 +175,7 @@ describe("RLN codec with version 1", () => { }); it("Symmetric, toProtoObj", async function () { - const rlnInstance = await rln.create(); + const rlnInstance = await rln.createRLN(); const credential = rlnInstance.generateIdentityCredentials(); const index = 0; const payload = new Uint8Array([1, 2, 3, 4, 5]); @@ -221,7 +221,7 @@ describe("RLN codec with version 1", () => { }); it("Asymmetric, toWire", async function () { - const rlnInstance = await rln.create(); + const rlnInstance = await rln.createRLN(); const credential = rlnInstance.generateIdentityCredentials(); const index = 0; const payload = new Uint8Array([1, 2, 3, 4, 5]); @@ -269,7 +269,7 @@ describe("RLN codec with version 1", () => { }); it("Asymmetric, toProtoObj", async function () { - const rlnInstance = await rln.create(); + const rlnInstance = await rln.createRLN(); const credential = rlnInstance.generateIdentityCredentials(); const index = 0; const payload = new Uint8Array([1, 2, 3, 4, 5]); @@ -318,7 +318,7 @@ describe("RLN codec with version 1", () => { describe("RLN Codec - epoch", () => { it("toProtoObj", async function () { - const rlnInstance = await rln.create(); + const rlnInstance = await rln.createRLN(); const credential = rlnInstance.generateIdentityCredentials(); const index = 0; const payload = new Uint8Array([1, 2, 3, 4, 5]); @@ -374,7 +374,7 @@ describe("RLN codec with version 0 and meta setter", () => { }; it("toWire", async function () { - const rlnInstance = await rln.create(); + const rlnInstance = await rln.createRLN(); const credential = rlnInstance.generateIdentityCredentials(); const index = 0; const payload = new Uint8Array([1, 2, 3, 4, 5]); @@ -422,7 +422,7 @@ describe("RLN codec with version 0 and meta setter", () => { }); it("toProtoObj", async function () { - const rlnInstance = await rln.create(); + const rlnInstance = await rln.createRLN(); const credential = rlnInstance.generateIdentityCredentials(); const index = 0; const payload = new Uint8Array([1, 2, 3, 4, 5]); diff --git a/src/index.spec.ts b/src/index.spec.ts index 30f5fea..25a78cd 100644 --- a/src/index.spec.ts +++ b/src/index.spec.ts @@ -4,7 +4,7 @@ import * as rln from "./index.js"; describe("js-rln", () => { it("should verify a proof", async function () { - const rlnInstance = await rln.create(); + const rlnInstance = await rln.createRLN(); const credential = rlnInstance.generateIdentityCredentials(); @@ -59,7 +59,7 @@ describe("js-rln", () => { } }); it("should verify a proof with a seeded membership key generation", async function () { - const rlnInstance = await rln.create(); + const rlnInstance = await rln.createRLN(); const seed = "This is a test seed"; const credential = rlnInstance.generateSeededIdentityCredential(seed); @@ -115,7 +115,7 @@ describe("js-rln", () => { }); it("should generate the same membership key if the same seed is provided", async function () { - const rlnInstance = await rln.create(); + const rlnInstance = await rln.createRLN(); const seed = "This is a test seed"; const memKeys1 = rlnInstance.generateSeededIdentityCredential(seed); const memKeys2 = rlnInstance.generateSeededIdentityCredential(seed); diff --git a/src/rln_contract.spec.ts b/src/rln_contract.spec.ts index 05c2737..7116e9b 100644 --- a/src/rln_contract.spec.ts +++ b/src/rln_contract.spec.ts @@ -8,7 +8,7 @@ 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.create(); + const rlnInstance = await rln.createRLN(); rlnInstance.insertMember = () => undefined; const insertMemberSpy = chai.spy.on(rlnInstance, "insertMember"); @@ -36,7 +36,7 @@ describe("RLN Contract abstraction", () => { const mockSignature = "0xdeb8a6b00a8e404deb1f52d3aa72ed7f60a2ff4484c737eedaef18a0aacb2dfb4d5d74ac39bb71fa358cf2eb390565a35b026cc6272f2010d4351e17670311c21c"; - const rlnInstance = await rln.create(); + 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, From 2015d93d7f39e0f427ae21239455d12ce1b04c63 Mon Sep 17 00:00:00 2001 From: Sasha Date: Wed, 24 Jan 2024 21:02:22 +0100 Subject: [PATCH 6/7] use Provider type --- src/rln.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rln.ts b/src/rln.ts index 3a770ab..a4727f3 100644 --- a/src/rln.ts +++ b/src/rln.ts @@ -166,7 +166,7 @@ type StartRLNOptions = { /** * If not set - will extract MetaMask account and get provider from it. */ - provider?: ethers.providers.Web3Provider; + provider?: ethers.providers.Provider; /** * If not set - will use default SEPOLIA_CONTRACT address. */ From 4826875d668cc10748b9187b68099995dec5e131 Mon Sep 17 00:00:00 2001 From: Sasha Date: Wed, 24 Jan 2024 21:10:14 +0100 Subject: [PATCH 7/7] use provider as it is --- src/rln.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rln.ts b/src/rln.ts index a4727f3..f96bdc4 100644 --- a/src/rln.ts +++ b/src/rln.ts @@ -191,7 +191,7 @@ export class RLNInstance { this._contract = await RLNContract.init(this, { registryAddress, - provider: provider.getSigner(), + provider, }); }