mirror of
https://github.com/logos-messaging/js-rln.git
synced 2026-01-02 13:43:06 +00:00
feat: track roots in rln contract and use sepolia instead of goerli (#62)
This commit is contained in:
parent
b7cb3f9bbf
commit
7e93896538
@ -9,6 +9,7 @@
|
||||
"merkle",
|
||||
"nwaku",
|
||||
"rlnjs",
|
||||
"sepolia",
|
||||
"vkey",
|
||||
"Waku",
|
||||
"zerokit",
|
||||
|
||||
@ -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
4
package-lock.json
generated
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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,
|
||||
};
|
||||
|
||||
@ -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,
|
||||
};
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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,
|
||||
});
|
||||
|
||||
|
||||
@ -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 & {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user