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/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; } diff --git a/src/create.ts b/src/create.ts new file mode 100644 index 0000000..3b32302 --- /dev/null +++ b/src/create.ts @@ -0,0 +1,9 @@ +import type { RLNInstance } from "./rln.js"; + +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. + const rlnModule = await import("./rln.js"); + return rlnModule.create(); +} 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/index.ts b/src/index.ts index 0d27230..ae89bec 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,6 +4,7 @@ import { RLN_STORAGE_ABI, SEPOLIA_CONTRACT, } from "./constants.js"; +import { createRLN } 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 { + createRLN, Keystore, RLNInstance, IdentityCredential, diff --git a/src/metamask.ts b/src/metamask.ts new file mode 100644 index 0000000..e171205 --- /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 a Web3 wallet such as 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..f96bdc4 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.Provider; + /** + * 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, + }); + } + generateIdentityCredentials(): IdentityCredential { const memKeys = zerokitRLN.generateExtendedMembershipKey(this.zkRLN); // TODO: rename this function in zerokit rln-wasm return IdentityCredential.fromBytes(memKeys); 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,