feat: track roots in rln contract and use sepolia instead of goerli (#62)

This commit is contained in:
RichΛrd 2023-05-14 12:18:21 -04:00 committed by GitHub
parent b7cb3f9bbf
commit 7e93896538
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 166 additions and 93 deletions

View File

@ -9,6 +9,7 @@
"merkle",
"nwaku",
"rlnjs",
"sepolia",
"vkey",
"Waku",
"zerokit",

View File

@ -1,58 +1,64 @@
import * as rln from "@waku/rln";
rln.create().then(async rlnInstance => {
const credentials = rlnInstance.generateIdentityCredentials();
rln.create().then(async (rlnInstance) => {
const credentials = rlnInstance.generateIdentityCredentials();
//peer's index in the Merkle Tree
const index = 5
//peer's index in the Merkle Tree
const index = 5;
// Create a Merkle tree with random members
for (let i = 0; i < 10; i++) {
if (i == index) {
// insert the current peer's pk
rlnInstance.insertMember(credentials.IDCommitment);
} else {
// create a new key pair
const credentials = rlnInstance.generateIdentityCredentials(); // TODO: handle error
rlnInstance.insertMember(credentials.IDCommitment);
}
// Create a Merkle tree with random members
for (let i = 0; i < 10; i++) {
if (i == index) {
// insert the current peer's pk
rlnInstance.insertMember(credentials.IDCommitment);
} else {
// create a new key pair
const credentials = rlnInstance.generateIdentityCredentials(); // TODO: handle error
rlnInstance.insertMember(credentials.IDCommitment);
}
}
// prepare the message
const uint8Msg = Uint8Array.from("Hello World".split("").map(x => x.charCodeAt()));
// prepare the message
const uint8Msg = Uint8Array.from(
"Hello World".split("").map((x) => x.charCodeAt())
);
// setting up the epoch
const epoch = new Date();
// setting up the epoch
const epoch = new Date();
console.log("Generating proof...");
console.time("proof_gen_timer");
let proof = await rlnInstance.generateRLNProof(uint8Msg, index, epoch, credentials.IDSecretHash)
console.timeEnd("proof_gen_timer");
console.log("Proof", proof)
console.log("Generating proof...");
console.time("proof_gen_timer");
let proof = await rlnInstance.generateRLNProof(
uint8Msg,
index,
epoch,
credentials.IDSecretHash
);
console.timeEnd("proof_gen_timer");
console.log("Proof", proof);
try {
// verify the proof
let verifResult = rlnInstance.verifyRLNProof(proof, uint8Msg);
console.log("Is proof verified?", verifResult ? "yes" : "no");
} catch (err) {
console.log("Invalid proof")
}
try {
// verify the proof
let verifResult = rlnInstance.verifyRLNProof(proof, uint8Msg);
console.log("Is proof verified?", verifResult ? "yes" : "no");
} catch (err) {
console.log("Invalid proof");
}
const provider = new ethers.providers.Web3Provider(
window.ethereum,
"any"
);
const provider = new ethers.providers.Web3Provider(window.ethereum, "any");
const DEFAULT_SIGNATURE_MESSAGE =
"The signature of this message will be used to generate your RLN credentials. Anyone accessing it may send messages on your behalf, please only share with the RLN dApp";
const DEFAULT_SIGNATURE_MESSAGE =
"The signature of this message will be used to generate your RLN credentials. Anyone accessing it may send messages on your behalf, please only share with the RLN dApp";
const signer = provider.getSigner();
const signature = await signer.signMessage(DEFAULT_SIGNATURE_MESSAGE);
console.log(`Got signature: ${signature}`);
const signer = provider.getSigner();
const signature = await signer.signMessage(DEFAULT_SIGNATURE_MESSAGE);
console.log(`Got signature: ${signature}`);
const contract = await rln.RLNContract.init(rlnInstance, {address: rln.GOERLI_CONTRACT.address, provider: signer });
const contract = await rln.RLNContract.init(rlnInstance, {
address: rln.SEPOLIA_CONTRACT.address,
provider: signer,
});
const event = await contract.registerMember(rlnInstance, signature);
console.log(`Registered as member with ${event}`);
const event = await contract.registerMember(rlnInstance, signature);
console.log(`Registered as member with ${event}`);
});

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "@waku/rln",
"version": "0.1.0",
"version": "0.1.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@waku/rln",
"version": "0.1.0",
"version": "0.1.1",
"license": "MIT OR Apache-2.0",
"dependencies": {
"@waku/utils": "^0.0.5",

View File

@ -1,6 +1,6 @@
{
"name": "@waku/rln",
"version": "0.1.0",
"version": "0.1.1",
"description": "Rate Limit Nullifier for js-waku",
"types": "./dist/index.d.ts",
"module": "./dist/index.js",

View File

@ -63,7 +63,7 @@ describe("RLN codec with version 0", () => {
))!;
expect(msg.rateLimitProof).to.not.be.undefined;
expect(msg.verify()).to.be.true;
expect(msg.verify([rlnInstance.getMerkleRoot()])).to.be.true;
expect(msg.verifyNoRoot()).to.be.true;
expect(msg.epoch).to.not.be.undefined;
expect(msg.epoch).to.be.gt(0);
@ -104,7 +104,7 @@ describe("RLN codec with version 0", () => {
expect(msg).to.not.be.undefined;
expect(msg.rateLimitProof).to.not.be.undefined;
expect(msg.verify()).to.be.true;
expect(msg.verify([rlnInstance.getMerkleRoot()])).to.be.true;
expect(msg.verifyNoRoot()).to.be.true;
expect(msg.epoch).to.not.be.undefined;
expect(msg.epoch).to.be.gt(0);
@ -153,7 +153,7 @@ describe("RLN codec with version 1", () => {
))!;
expect(msg.rateLimitProof).to.not.be.undefined;
expect(msg.verify()).to.be.true;
expect(msg.verify([rlnInstance.getMerkleRoot()])).to.be.true;
expect(msg.verifyNoRoot()).to.be.true;
expect(msg.epoch).to.not.be.undefined;
expect(msg.epoch).to.be.gt(0);
@ -199,7 +199,7 @@ describe("RLN codec with version 1", () => {
expect(msg).to.not.be.undefined;
expect(msg.rateLimitProof).to.not.be.undefined;
expect(msg.verify()).to.be.true;
expect(msg.verify([rlnInstance.getMerkleRoot()])).to.be.true;
expect(msg.verifyNoRoot()).to.be.true;
expect(msg.epoch).to.not.be.undefined;
expect(msg.epoch).to.be.gt(0);
@ -247,7 +247,7 @@ describe("RLN codec with version 1", () => {
))!;
expect(msg.rateLimitProof).to.not.be.undefined;
expect(msg.verify()).to.be.true;
expect(msg.verify([rlnInstance.getMerkleRoot()])).to.be.true;
expect(msg.verifyNoRoot()).to.be.true;
expect(msg.epoch).to.not.be.undefined;
expect(msg.epoch).to.be.gt(0);
@ -294,7 +294,7 @@ describe("RLN codec with version 1", () => {
expect(msg).to.not.be.undefined;
expect(msg.rateLimitProof).to.not.be.undefined;
expect(msg.verify()).to.be.true;
expect(msg.verify([rlnInstance.getMerkleRoot()])).to.be.true;
expect(msg.verifyNoRoot()).to.be.true;
expect(msg.epoch).to.not.be.undefined;
expect(msg.epoch).to.be.gt(0);
@ -340,7 +340,7 @@ describe("RLN Codec - epoch", () => {
expect(msg).to.not.be.undefined;
expect(msg.rateLimitProof).to.not.be.undefined;
expect(msg.verify()).to.be.true;
expect(msg.verify([rlnInstance.getMerkleRoot()])).to.be.true;
expect(msg.verifyNoRoot()).to.be.true;
expect(msg.epoch).to.not.be.undefined;
expect(msg.epoch!.toString(10).length).to.eq(9);

View File

@ -6,9 +6,9 @@ export const RLN_ABI = [
"event MemberWithdrawn(uint256 pubkey, uint256 index)",
];
export const GOERLI_CONTRACT = {
chainId: 5,
startBlock: 7109391,
address: "0x4252105670fe33d2947e8ead304969849e64f2a6",
export const SEPOLIA_CONTRACT = {
chainId: 11155111,
startBlock: 3193048,
address: "0x9C09146844C1326c2dBC41c451766C7138F88155",
abi: RLN_ABI,
};

View File

@ -1,5 +1,5 @@
import { RLNDecoder, RLNEncoder } from "./codec.js";
import { GOERLI_CONTRACT, RLN_ABI } from "./constants.js";
import { RLN_ABI, SEPOLIA_CONTRACT } from "./constants.js";
import {
IdentityCredential,
Proof,
@ -28,5 +28,5 @@ export {
MerkleRootTracker,
RLNContract,
RLN_ABI,
GOERLI_CONTRACT,
SEPOLIA_CONTRACT,
};

View File

@ -22,11 +22,12 @@ export class RlnMessage<T extends IDecodedMessage> implements IDecodedMessage {
public rateLimitProof: IRateLimitProof | undefined
) {}
public verify(): boolean | undefined {
public verify(roots: Uint8Array[]): boolean | undefined {
return this.rateLimitProof
? this.rlnInstance.verifyWithRoots(
this.rateLimitProof,
toRLNSignal(this.msg.contentTopic, this.msg)
toRLNSignal(this.msg.contentTopic, this.msg),
...roots
) // this.rlnInstance.verifyRLNProof once issue status-im/nwaku#1248 is fixed
: undefined;
}

View File

@ -13,9 +13,9 @@ describe("RLN Contract abstraction", () => {
rlnInstance.insertMember = () => undefined;
const insertMemberSpy = chai.spy.on(rlnInstance, "insertMember");
const voidSigner = new ethers.VoidSigner(rln.GOERLI_CONTRACT.address);
const rlnContract = new rln.RLNContract({
address: rln.GOERLI_CONTRACT.address,
const voidSigner = new ethers.VoidSigner(rln.SEPOLIA_CONTRACT.address);
const rlnContract = new rln.RLNContract(rlnInstance, {
address: rln.SEPOLIA_CONTRACT.address,
provider: voidSigner,
});
@ -33,9 +33,9 @@ describe("RLN Contract abstraction", () => {
"0xdeb8a6b00a8e404deb1f52d3aa72ed7f60a2ff4484c737eedaef18a0aacb2dfb4d5d74ac39bb71fa358cf2eb390565a35b026cc6272f2010d4351e17670311c21c";
const rlnInstance = await rln.create();
const voidSigner = new ethers.VoidSigner(rln.GOERLI_CONTRACT.address);
const rlnContract = new rln.RLNContract({
address: rln.GOERLI_CONTRACT.address,
const voidSigner = new ethers.VoidSigner(rln.SEPOLIA_CONTRACT.address);
const rlnContract = new rln.RLNContract(rlnInstance, {
address: rln.SEPOLIA_CONTRACT.address,
provider: voidSigner,
});

View File

@ -2,6 +2,7 @@ import { ethers } from "ethers";
import { RLN_ABI } from "./constants.js";
import { IdentityCredential, RLNInstance } from "./rln.js";
import { MerkleRootTracker } from "./root_tracker";
type Member = {
pubkey: string;
@ -22,6 +23,7 @@ type FetchMembersOptions = {
export class RLNContract {
private _contract: ethers.Contract;
private membersFilter: ethers.EventFilter;
private merkleRootTracker: MerkleRootTracker;
private _members: Member[] = [];
@ -29,7 +31,7 @@ export class RLNContract {
rlnInstance: RLNInstance,
options: ContractOptions
): Promise<RLNContract> {
const rlnContract = new RLNContract(options);
const rlnContract = new RLNContract(rlnInstance, options);
await rlnContract.fetchMembers(rlnInstance);
rlnContract.subscribeToMembers(rlnInstance);
@ -37,8 +39,14 @@ export class RLNContract {
return rlnContract;
}
constructor({ address, provider }: ContractOptions) {
constructor(
rlnInstance: RLNInstance,
{ address, provider }: ContractOptions
) {
const initialRoot = rlnInstance.getMerkleRoot();
this._contract = new ethers.Contract(address, RLN_ABI, provider);
this.merkleRootTracker = new MerkleRootTracker(5, initialRoot);
this.membersFilter = this.contract.filters.MemberRegistered();
}
@ -58,38 +66,91 @@ export class RLNContract {
...options,
membersFilter: this.membersFilter,
});
this.processEvents(rlnInstance, registeredMemberEvents);
}
for (const event of registeredMemberEvents) {
this.addMemberFromEvent(rlnInstance, event);
}
public processEvents(rlnInstance: RLNInstance, events: ethers.Event[]): void {
const toRemoveTable = new Map<number, number[]>();
const toInsertTable = new Map<number, ethers.Event[]>();
events.forEach((evt) => {
if (!evt.args) {
return;
}
if (evt.removed) {
const index: number = evt.args.index;
const toRemoveVal = toRemoveTable.get(evt.blockNumber);
if (toRemoveVal != undefined) {
toRemoveVal.push(index);
toRemoveTable.set(evt.blockNumber, toRemoveVal);
} else {
toRemoveTable.set(evt.blockNumber, [index]);
}
} else {
let eventsPerBlock = toInsertTable.get(evt.blockNumber);
if (eventsPerBlock == undefined) {
eventsPerBlock = [];
}
eventsPerBlock.push(evt);
toInsertTable.set(evt.blockNumber, eventsPerBlock);
}
this.removeMembers(rlnInstance, toRemoveTable);
this.insertMembers(rlnInstance, toInsertTable);
});
}
private insertMembers(
rlnInstance: RLNInstance,
toInsert: Map<number, ethers.Event[]>
): void {
toInsert.forEach((events: ethers.Event[], blockNumber: number) => {
events.forEach((evt) => {
if (!evt.args) {
return;
}
const pubkey = evt.args.pubkey;
const index = evt.args.index;
const idCommitment = ethers.utils.zeroPad(
ethers.utils.arrayify(pubkey),
32
);
rlnInstance.insertMember(idCommitment);
this.members.push({ index, pubkey });
});
const currentRoot = rlnInstance.getMerkleRoot();
this.merkleRootTracker.pushRoot(blockNumber, currentRoot);
});
}
private removeMembers(
rlnInstance: RLNInstance,
toRemove: Map<number, number[]>
): void {
const removeDescending = new Map([...toRemove].sort().reverse());
removeDescending.forEach((indexes: number[], blockNumber: number) => {
indexes.forEach((index) => {
const idx = this.members.findIndex((m) => m.index === index);
if (idx > -1) {
this.members.splice(idx, 1);
}
rlnInstance.deleteMember(index);
});
this.merkleRootTracker.backFill(blockNumber);
});
}
public subscribeToMembers(rlnInstance: RLNInstance): void {
this.contract.on(this.membersFilter, (_pubkey, _index, event) =>
this.addMemberFromEvent(rlnInstance, event)
this.processEvents(rlnInstance, event)
);
}
private addMemberFromEvent(
rlnInstance: RLNInstance,
event: ethers.Event
): void {
if (!event.args) {
return;
}
const pubkey: string = event.args.pubkey;
const index: number = event.args.index;
this.members.push({ index, pubkey });
const idCommitment = ethers.utils.zeroPad(
ethers.utils.arrayify(pubkey),
32
);
rlnInstance.insertMember(idCommitment);
}
public async registerWithSignature(
rlnInstance: RLNInstance,
signature: string
@ -113,6 +174,10 @@ export class RLNContract {
return txRegisterReceipt?.events?.[0];
}
public roots(): Uint8Array[] {
return this.merkleRootTracker.roots();
}
}
type CustomQueryOptions = FetchMembersOptions & {