feat: simplify API of bootstrapping, connection to MetaMask (#92)

* 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
This commit is contained in:
Sasha 2024-01-24 21:23:52 +01:00 committed by GitHub
parent 18ce994d5e
commit bafbe01e52
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 92 additions and 27 deletions

View File

@ -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

View File

@ -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]);

View File

@ -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;
}

9
src/create.ts Normal file
View File

@ -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();
}

View File

@ -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);

View File

@ -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<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,

15
src/metamask.ts Normal file
View File

@ -0,0 +1,15 @@
import { ethers } from "ethers";
export const extractMetaMaskAccount =
async (): Promise<ethers.providers.Web3Provider> => {
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");
};

View File

@ -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<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);

View File

@ -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,