Skip to content

Commit

Permalink
feat: simplify API of bootstrapping, connection to MetaMask (#92)
Browse files Browse the repository at this point in the history
* add MetaMask abstraction, add default start to RLN Instance, expose RLN Contract

* remove console timer

* improve text

* rename to createRLN

* update README, fix tests

* use Provider type

* use provider as it is
  • Loading branch information
weboko authored Jan 24, 2024
1 parent 18ce994 commit bafbe01
Show file tree
Hide file tree
Showing 9 changed files with 92 additions and 27 deletions.
22 changes: 21 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
18 changes: 9 additions & 9 deletions src/codec.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
Expand Down Expand Up @@ -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]);
Expand Down Expand Up @@ -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]);
Expand Down Expand Up @@ -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]);
Expand Down Expand Up @@ -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]);
Expand Down Expand Up @@ -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]);
Expand Down Expand Up @@ -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]);
Expand Down Expand Up @@ -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]);
Expand Down Expand Up @@ -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]);
Expand Down
3 changes: 0 additions & 3 deletions src/codec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,12 @@ export class RLNEncoder implements IEncoder {

private async generateProof(message: IMessage): Promise<IRateLimitProof> {
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;
}

Expand Down
9 changes: 9 additions & 0 deletions src/create.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { RLNInstance } from "./rln.js";

export async function createRLN(): Promise<RLNInstance> {
// 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();
}
6 changes: 3 additions & 3 deletions src/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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);
Expand Down
11 changes: 2 additions & 9 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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<RLNInstance> {
// 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,
Expand Down
15 changes: 15 additions & 0 deletions src/metamask.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { ethers } from "ethers";

export const extractMetaMaskAccount =
async (): Promise<ethers.providers.Web3Provider> => {
const ethereum = (window as any).ethereum;

Check warning on line 5 in src/metamask.ts

View workflow job for this annotation

GitHub Actions / check

Unexpected any. Specify a different type

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");
};
31 changes: 31 additions & 0 deletions src/rln.ts
Original file line number Diff line number Diff line change
@@ -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";

Expand Down Expand Up @@ -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<void> {
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);
Expand Down
4 changes: 2 additions & 2 deletions src/rln_contract.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -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,
Expand Down

0 comments on commit bafbe01

Please sign in to comment.