feat: use identity credentials, and expose hash, bulk insert and delete members functions (#58)

* feat: use identity credentials, and expose hash, bulk insert and delete members functions
* feat: merkle root tracker
This commit is contained in:
RichΛrd 2023-05-08 18:10:26 -04:00 committed by GitHub
parent 7e0966aef7
commit e52107dcf8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 405 additions and 193 deletions

View File

@ -70,10 +70,10 @@ import * as rln from "@waku/rln";
const rlnInstance = await rln.create();
```
### Generating RLN Membership Keypair
#### Generating RLN Membership Credentials
```js
let memKeys = rlnInstance.generateMembershipKey();
let credentials = rlnInstance.generateIdentityCredentials();
```
### Generating RLN Membership Keypair Using a Seed
@ -81,13 +81,13 @@ let memKeys = rlnInstance.generateMembershipKey();
This can be used to generate credentials from a signature hash (e.g. signed by an Ethereum account).
```js
let memKeys = rlnInstance.generateSeededMembershipKey(seed);
let credentials = rlnInstance.generateSeededIdentityCredentials(seed);
```
### Adding Membership Keys Into Merkle Tree
```js
rlnInstance.insertMember(memKeys.IDCommitment);
rlnInstance.insertMember(credentials.IDCommitment);
```
### Generating a Proof
@ -106,7 +106,7 @@ const proof = await rlnInstance.generateProof(
uint8Msg,
index,
epoch,
memKeys.IDKey
credentials.IDSecretHash
);
```

View File

@ -1,7 +1,7 @@
import * as rln from "@waku/rln";
rln.create().then(async rlnInstance => {
let memKeys = rlnInstance.generateMembershipKey();
const credentials = rlnInstance.generateIdentityCredentials();
//peer's index in the Merkle Tree
const index = 5
@ -10,11 +10,11 @@ rln.create().then(async rlnInstance => {
for (let i = 0; i < 10; i++) {
if (i == index) {
// insert the current peer's pk
rlnInstance.insertMember(memKeys.IDCommitment);
rlnInstance.insertMember(credentials.IDCommitment);
} else {
// create a new key pair
let memKeys = rlnInstance.generateMembershipKey(); // TODO: handle error
rlnInstance.insertMember(memKeys.IDCommitment);
const credentials = rlnInstance.generateIdentityCredentials(); // TODO: handle error
rlnInstance.insertMember(credentials.IDCommitment);
}
}
@ -27,7 +27,7 @@ rln.create().then(async rlnInstance => {
console.log("Generating proof...");
console.time("proof_gen_timer");
let proof = await rlnInstance.generateRLNProof(uint8Msg, index, epoch, memKeys.IDKey)
let proof = await rlnInstance.generateRLNProof(uint8Msg, index, epoch, credentials.IDSecretHash)
console.timeEnd("proof_gen_timer");
console.log("Proof", proof)

View File

@ -30,10 +30,11 @@ module.exports = function (config) {
envPreprocessor: ["CI"],
reporters: ["progress"],
browsers: ["ChromeHeadless"],
pingTimeout: 60000,
singleRun: true,
client: {
mocha: {
timeout: 6000, // Default is 2s
timeout: 60000, // Default is 2s
},
},
webpack: {

18
package-lock.json generated
View File

@ -1,16 +1,16 @@
{
"name": "@waku/rln",
"version": "0.0.14",
"version": "0.1.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@waku/rln",
"version": "0.0.14",
"version": "0.1.0",
"license": "MIT OR Apache-2.0",
"dependencies": {
"@waku/utils": "^0.0.4",
"@waku/zerokit-rln-wasm": "^0.0.5",
"@waku/zerokit-rln-wasm": "^0.0.10",
"ethers": "^5.7.2"
},
"devDependencies": {
@ -2942,9 +2942,9 @@
}
},
"node_modules/@waku/zerokit-rln-wasm": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/@waku/zerokit-rln-wasm/-/zerokit-rln-wasm-0.0.5.tgz",
"integrity": "sha512-uZHZRk06WrnqJJOVwIIKtsjWf2d6g2JpK8FtC0lHg4JJkOxhJy0pgEIuBCPw8Je4MpF9FCtIO/ww7xicdlC2GA=="
"version": "0.0.10",
"resolved": "https://registry.npmjs.org/@waku/zerokit-rln-wasm/-/zerokit-rln-wasm-0.0.10.tgz",
"integrity": "sha512-qegIK1P54mxEp59uTa8C0/zidUffLc2Iee61yiKRIuGJDui2mQ+0V+KzPSPImKpIoqfVLT192EqgZkqPmj8VEw=="
},
"node_modules/@web/rollup-plugin-import-meta-assets": {
"version": "1.0.7",
@ -13629,9 +13629,9 @@
}
},
"@waku/zerokit-rln-wasm": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/@waku/zerokit-rln-wasm/-/zerokit-rln-wasm-0.0.5.tgz",
"integrity": "sha512-uZHZRk06WrnqJJOVwIIKtsjWf2d6g2JpK8FtC0lHg4JJkOxhJy0pgEIuBCPw8Je4MpF9FCtIO/ww7xicdlC2GA=="
"version": "0.0.10",
"resolved": "https://registry.npmjs.org/@waku/zerokit-rln-wasm/-/zerokit-rln-wasm-0.0.10.tgz",
"integrity": "sha512-qegIK1P54mxEp59uTa8C0/zidUffLc2Iee61yiKRIuGJDui2mQ+0V+KzPSPImKpIoqfVLT192EqgZkqPmj8VEw=="
},
"@web/rollup-plugin-import-meta-assets": {
"version": "1.0.7",

View File

@ -1,6 +1,6 @@
{
"name": "@waku/rln",
"version": "0.0.14",
"version": "0.1.0",
"description": "Rate Limit Nullifier for js-waku",
"types": "./dist/index.d.ts",
"module": "./dist/index.js",
@ -130,7 +130,7 @@
},
"dependencies": {
"@waku/utils": "^0.0.4",
"@waku/zerokit-rln-wasm": "^0.0.5",
"@waku/zerokit-rln-wasm": "^0.0.10",
"ethers": "^5.7.2"
}
}

View File

@ -35,17 +35,17 @@ const EMPTY_PUBSUB_TOPIC = "";
describe("RLN codec with version 0", () => {
it("toWire", async function () {
const rlnInstance = await rln.create();
const memKeys = rlnInstance.generateMembershipKey();
const credential = rlnInstance.generateIdentityCredentials();
const index = 0;
const payload = new Uint8Array([1, 2, 3, 4, 5]);
rlnInstance.insertMember(memKeys.IDCommitment);
rlnInstance.insertMember(credential.IDCommitment);
const rlnEncoder = createRLNEncoder({
encoder: createEncoder({ contentTopic: TestContentTopic }),
rlnInstance,
index,
membershipKey: memKeys,
credential,
});
const rlnDecoder = createRLNDecoder({
rlnInstance,
@ -76,17 +76,17 @@ describe("RLN codec with version 0", () => {
it("toProtoObj", async function () {
const rlnInstance = await rln.create();
const memKeys = rlnInstance.generateMembershipKey();
const credential = rlnInstance.generateIdentityCredentials();
const index = 0;
const payload = new Uint8Array([1, 2, 3, 4, 5]);
rlnInstance.insertMember(memKeys.IDCommitment);
rlnInstance.insertMember(credential.IDCommitment);
const rlnEncoder = new RLNEncoder(
createEncoder({ contentTopic: TestContentTopic }),
rlnInstance,
index,
memKeys
credential
);
const rlnDecoder = new RLNDecoder(
rlnInstance,
@ -119,11 +119,11 @@ describe("RLN codec with version 0", () => {
describe("RLN codec with version 1", () => {
it("Symmetric, toWire", async function () {
const rlnInstance = await rln.create();
const memKeys = rlnInstance.generateMembershipKey();
const credential = rlnInstance.generateIdentityCredentials();
const index = 0;
const payload = new Uint8Array([1, 2, 3, 4, 5]);
rlnInstance.insertMember(memKeys.IDCommitment);
rlnInstance.insertMember(credential.IDCommitment);
const symKey = generateSymmetricKey();
@ -134,7 +134,7 @@ describe("RLN codec with version 1", () => {
}),
rlnInstance,
index,
memKeys
credential
);
const rlnDecoder = new RLNDecoder(
rlnInstance,
@ -166,11 +166,11 @@ describe("RLN codec with version 1", () => {
it("Symmetric, toProtoObj", async function () {
const rlnInstance = await rln.create();
const memKeys = rlnInstance.generateMembershipKey();
const credential = rlnInstance.generateIdentityCredentials();
const index = 0;
const payload = new Uint8Array([1, 2, 3, 4, 5]);
rlnInstance.insertMember(memKeys.IDCommitment);
rlnInstance.insertMember(credential.IDCommitment);
const symKey = generateSymmetricKey();
@ -181,7 +181,7 @@ describe("RLN codec with version 1", () => {
}),
rlnInstance,
index,
memKeys
credential
);
const rlnDecoder = new RLNDecoder(
rlnInstance,
@ -212,11 +212,11 @@ describe("RLN codec with version 1", () => {
it("Asymmetric, toWire", async function () {
const rlnInstance = await rln.create();
const memKeys = rlnInstance.generateMembershipKey();
const credential = rlnInstance.generateIdentityCredentials();
const index = 0;
const payload = new Uint8Array([1, 2, 3, 4, 5]);
rlnInstance.insertMember(memKeys.IDCommitment);
rlnInstance.insertMember(credential.IDCommitment);
const privateKey = generatePrivateKey();
const publicKey = getPublicKey(privateKey);
@ -228,7 +228,7 @@ describe("RLN codec with version 1", () => {
}),
rlnInstance,
index,
memKeys
credential
);
const rlnDecoder = new RLNDecoder(
rlnInstance,
@ -260,11 +260,11 @@ describe("RLN codec with version 1", () => {
it("Asymmetric, toProtoObj", async function () {
const rlnInstance = await rln.create();
const memKeys = rlnInstance.generateMembershipKey();
const credential = rlnInstance.generateIdentityCredentials();
const index = 0;
const payload = new Uint8Array([1, 2, 3, 4, 5]);
rlnInstance.insertMember(memKeys.IDCommitment);
rlnInstance.insertMember(credential.IDCommitment);
const privateKey = generatePrivateKey();
const publicKey = getPublicKey(privateKey);
@ -276,7 +276,7 @@ describe("RLN codec with version 1", () => {
}),
rlnInstance,
index,
memKeys
credential
);
const rlnDecoder = new RLNDecoder(
rlnInstance,
@ -309,17 +309,17 @@ describe("RLN codec with version 1", () => {
describe("RLN Codec - epoch", () => {
it("toProtoObj", async function () {
const rlnInstance = await rln.create();
const memKeys = rlnInstance.generateMembershipKey();
const credential = rlnInstance.generateIdentityCredentials();
const index = 0;
const payload = new Uint8Array([1, 2, 3, 4, 5]);
rlnInstance.insertMember(memKeys.IDCommitment);
rlnInstance.insertMember(credential.IDCommitment);
const rlnEncoder = new RLNEncoder(
createEncoder({ contentTopic: TestContentTopic }),
rlnInstance,
index,
memKeys
credential
);
const rlnDecoder = new RLNDecoder(
rlnInstance,

View File

@ -9,21 +9,21 @@ import type {
import debug from "debug";
import { RlnMessage, toRLNSignal } from "./message.js";
import { MembershipKey, RLNInstance } from "./rln.js";
import { IdentityCredential, RLNInstance } from "./rln.js";
const log = debug("waku:rln:encoder");
export class RLNEncoder implements IEncoder {
private readonly idKey: Uint8Array;
private readonly idSecretHash: Uint8Array;
constructor(
private encoder: IEncoder,
private rlnInstance: RLNInstance,
private index: number,
membershipKey: MembershipKey
identityCredential: IdentityCredential
) {
if (index < 0) throw "invalid membership index";
this.idKey = membershipKey.IDKey;
this.idSecretHash = identityCredential.IDSecretHash;
}
async toWire(message: IMessage): Promise<Uint8Array | undefined> {
@ -50,7 +50,7 @@ export class RLNEncoder implements IEncoder {
signal,
this.index,
message.timestamp,
this.idKey
this.idSecretHash
);
console.timeEnd("proof_gen_timer");
return proof;
@ -69,7 +69,7 @@ type RLNEncoderOptions = {
encoder: IEncoder;
rlnInstance: RLNInstance;
index: number;
membershipKey: MembershipKey;
credential: IdentityCredential;
};
export const createRLNEncoder = (options: RLNEncoderOptions): RLNEncoder => {
@ -77,7 +77,7 @@ export const createRLNEncoder = (options: RLNEncoderOptions): RLNEncoder => {
options.encoder,
options.rlnInstance,
options.index,
options.membershipKey
options.credential
);
};

View File

@ -6,7 +6,7 @@ describe("js-rln", () => {
it("should verify a proof", async function () {
const rlnInstance = await rln.create();
const memKeys = rlnInstance.generateMembershipKey();
const credential = rlnInstance.generateIdentityCredentials();
//peer's index in the Merkle Tree
const index = 5;
@ -15,11 +15,11 @@ describe("js-rln", () => {
for (let i = 0; i < 10; i++) {
if (i == index) {
// insert the current peer's pk
rlnInstance.insertMember(memKeys.IDCommitment);
rlnInstance.insertMember(credential.IDCommitment);
} else {
// create a new key pair
rlnInstance.insertMember(
rlnInstance.generateMembershipKey().IDCommitment
rlnInstance.generateIdentityCredentials().IDCommitment
);
}
}
@ -37,7 +37,7 @@ describe("js-rln", () => {
uint8Msg,
index,
epoch,
memKeys.IDKey
credential.IDSecretHash
);
try {
@ -61,7 +61,7 @@ describe("js-rln", () => {
it("should verify a proof with a seeded membership key generation", async function () {
const rlnInstance = await rln.create();
const seed = "This is a test seed";
const memKeys = rlnInstance.generateSeededMembershipKey(seed);
const credential = rlnInstance.generateSeededIdentityCredential(seed);
//peer's index in the Merkle Tree
const index = 5;
@ -70,11 +70,11 @@ describe("js-rln", () => {
for (let i = 0; i < 10; i++) {
if (i == index) {
// insert the current peer's pk
rlnInstance.insertMember(memKeys.IDCommitment);
rlnInstance.insertMember(credential.IDCommitment);
} else {
// create a new key pair
rlnInstance.insertMember(
rlnInstance.generateMembershipKey().IDCommitment
rlnInstance.generateIdentityCredentials().IDCommitment
);
}
}
@ -92,7 +92,7 @@ describe("js-rln", () => {
uint8Msg,
index,
epoch,
memKeys.IDKey
credential.IDSecretHash
);
try {
@ -116,14 +116,20 @@ describe("js-rln", () => {
it("should generate the same membership key if the same seed is provided", async function () {
const rlnInstance = await rln.create();
const seed = "This is a test seed";
const memKeys1 = rlnInstance.generateSeededMembershipKey(seed);
const memKeys2 = rlnInstance.generateSeededMembershipKey(seed);
const memKeys1 = rlnInstance.generateSeededIdentityCredential(seed);
const memKeys2 = rlnInstance.generateSeededIdentityCredential(seed);
memKeys1.IDCommitment.forEach((element, index) => {
expect(element).to.equal(memKeys2.IDCommitment[index]);
});
memKeys1.IDKey.forEach((element, index) => {
expect(element).to.equal(memKeys2.IDKey[index]);
memKeys1.IDNullifier.forEach((element, index) => {
expect(element).to.equal(memKeys2.IDNullifier[index]);
});
memKeys1.IDSecretHash.forEach((element, index) => {
expect(element).to.equal(memKeys2.IDSecretHash[index]);
});
memKeys1.IDTrapdoor.forEach((element, index) => {
expect(element).to.equal(memKeys2.IDTrapdoor[index]);
});
});
});

View File

@ -1,8 +1,13 @@
import { RLNDecoder, RLNEncoder } from "./codec.js";
import { GOERLI_CONTRACT, RLN_ABI } from "./constants.js";
import { Proof, RLNInstance } from "./rln.js";
import { MembershipKey } from "./rln.js";
import {
IdentityCredential,
Proof,
ProofMetadata,
RLNInstance,
} from "./rln.js";
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> {
@ -15,10 +20,12 @@ export async function create(): Promise<RLNInstance> {
export {
RLNInstance,
MembershipKey,
IdentityCredential,
Proof,
ProofMetadata,
RLNEncoder,
RLNDecoder,
MerkleRootTracker,
RLNContract,
RLN_ABI,
GOERLI_CONTRACT,

Binary file not shown.

Binary file not shown.

View File

@ -1,120 +1,112 @@
const verificationKey = {
"protocol": "groth16",
"curve": "bn128",
"nPublic": 6,
"vk_alpha_1": [
"1805378556360488226980822394597799963030511477964155500103132920745199284516",
"11990395240534218699464972016456017378439762088320057798320175886595281336136",
"1"
],
"vk_beta_2": [
[
"11031529986141021025408838211017932346992429731488270384177563837022796743627",
"16042159910707312759082561183373181639420894978640710177581040523252926273854"
],
[
"20112698439519222240302944148895052359035104222313380895334495118294612255131",
"19441583024670359810872018179190533814486480928824742448673677460151702019379"
],
[
protocol: "groth16",
curve: "bn128",
nPublic: 6,
vk_alpha_1: [
"20124996762962216725442980738609010303800849578410091356605067053491763969391",
"9118593021526896828671519912099489027245924097793322973632351264852174143923",
"1",
"0"
]
],
"vk_gamma_2": [
vk_beta_2: [
[
"4693952934005375501364248788849686435240706020501681709396105298107971354382",
"14346958885444710485362620645446987998958218205939139994511461437152241966681",
],
[
"16851772916911573982706166384196538392731905827088356034885868448550849804972",
"823612331030938060799959717749043047845343400798220427319188951998582076532",
],
["1", "0"],
],
vk_gamma_2: [
[
"10857046999023057135944570762232829481370756359578518086990519993285655852781",
"11559732032986387107991004021392285783925812861821192530917403151452391805634"
"11559732032986387107991004021392285783925812861821192530917403151452391805634",
],
[
"8495653923123431417604973247489272438418190587263600148770280649306958101930",
"4082367875863433681332203403145435568316851327593401208105741076214120093531"
"4082367875863433681332203403145435568316851327593401208105741076214120093531",
],
["1", "0"],
],
vk_delta_2: [
[
"8353516066399360694538747105302262515182301251524941126222712285088022964076",
"9329524012539638256356482961742014315122377605267454801030953882967973561832",
],
[
"16805391589556134376869247619848130874761233086443465978238468412168162326401",
"10111259694977636294287802909665108497237922060047080343914303287629927847739",
],
["1", "0"],
],
vk_alphabeta_12: [
[
[
"12608968655665301215455851857466367636344427685631271961542642719683786103711",
"9849575605876329747382930567422916152871921500826003490242628251047652318086",
],
[
"6322029441245076030714726551623552073612922718416871603535535085523083939021",
"8700115492541474338049149013125102281865518624059015445617546140629435818912",
],
[
"10674973475340072635573101639867487770811074181475255667220644196793546640210",
"2926286967251299230490668407790788696102889214647256022788211245826267484824",
],
],
[
[
"9660441540778523475944706619139394922744328902833875392144658911530830074820",
"19548113127774514328631808547691096362144426239827206966690021428110281506546",
],
[
"1870837942477655969123169532603615788122896469891695773961478956740992497097",
"12536105729661705698805725105036536744930776470051238187456307227425796690780",
],
[
"21811903352654147452884857281720047789720483752548991551595462057142824037334",
"19021616763967199151052893283384285352200445499680068407023236283004353578353",
],
],
],
IC: [
[
"11992897507809711711025355300535923222599547639134311050809253678876341466909",
"17181525095924075896332561978747020491074338784673526378866503154966799128110",
"1",
"0"
]
],
"vk_delta_2": [
[
"1948496782571164085469528023647105317580208688174386157591917599801657832035",
"20445814069256658101339037520922621162739470138213615104905368409238414511981"
],
[
"10024680869920840984813249386422727863826862577760330492647062850849851925340",
"10512156247842686783409460795717734694774542185222602679117887145206209285142"
],
[
"17018665030246167677911144513385572506766200776123272044534328594850561667818",
"18601114175490465275436712413925513066546725461375425769709566180981674884464",
"1",
"0"
]
],
"vk_alphabeta_12": [
[
[
"5151991366823434428398919091000210787450832786814248297320989361921939794156",
"15735191313289001022885148627913534790382722933676436876510746491415970766821"
],
[
"3387907257437913904447588318761906430938415556102110876587455322225272831272",
"1998779853452712881084781956683721603875246565720647583735935725110674288056"
"18799470100699658367834559797874857804183288553462108031963980039244731716542",
"13064227487174191981628537974951887429496059857753101852163607049188825592007",
"1",
],
[
"14280074182991498185075387990446437410077692353432005297922275464876153151820",
"17092408446352310039633488224969232803092763095456307462247653153107223117633"
]
"17432501889058124609368103715904104425610382063762621017593209214189134571156",
"13406815149699834788256141097399354592751313348962590382887503595131085938635",
"1",
],
[
[
"4359046709531668109201634396816565829237358165496082832279660960675584351266",
"4511888308846208349307186938266411423935335853916317436093178288331845821336"
"10320964835612716439094703312987075811498239445882526576970512041988148264481",
"9024164961646353611176283204118089412001502110138072989569118393359029324867",
"1",
],
[
"11429499807090785857812316277335883295048773373068683863667725283965356423273",
"16232274853200678548795010078253506586114563833318973594428907292096178657392"
"718355081067365548229685160476620267257521491773976402837645005858953849298",
"14635482993933988261008156660773180150752190597753512086153001683711587601974",
"1",
],
[
"18068999605870933925311275504102553573815570223888590384919752303726860800970",
"17309569111965782732372130116757295842160193489132771344011460471298173784984"
]
]
"11777720285956632126519898515392071627539405001940313098390150593689568177535",
"8483603647274280691250972408211651407952870456587066148445913156086740744515",
"1",
],
"IC": [
[
"18693301901828818437917730940595978397160482710354161265484535387752523310572",
"17985273354976640088538673802000794244421192643855111089693820179790551470769",
"1"
],
[
"21164641723988537620541455173278629777250883365474191521194244273980931825942",
"998385854410718613441067082771678946155853656328717326195057262123686425518",
"1"
],
[
"21666968581672145768705229094968410656430989593283335488162701230986314747515",
"17996457608540683483506630273632100555125353447506062045735279661096094677264",
"1"
],
[
"20137761979695192602424300886442379728165712610493092740175904438282083668117",
"19184814924890679891263780109959113289320127263583260218200636509492157834679",
"1"
],
[
"10943171273393803842589314082509655332154393332394322726077270895078286354146",
"10872472035685319847811233167729172672344935625121511932198535224727331126439",
"1"
],
[
"13049169779481227658517545034348883391527506091990880778783387628208561946597",
"10083689369261379027228809473568899816311684698866922944902456565434209079955",
"1"
],
[
"19633516378466409167014413361365552102431118630694133723053441455184566611083",
"8059525100726933978719058611146131904598011633549012007359165766216730722269",
"1"
]
]
}
export default verificationKey
};
export default verificationKey;

View File

@ -66,18 +66,29 @@ export async function create(): Promise<RLNInstance> {
return new RLNInstance(zkRLN, witnessCalculator);
}
export class MembershipKey {
export class IdentityCredential {
constructor(
public readonly IDKey: Uint8Array,
public readonly IDTrapdoor: Uint8Array,
public readonly IDNullifier: Uint8Array,
public readonly IDSecretHash: Uint8Array,
public readonly IDCommitment: Uint8Array,
public readonly IDCommitmentBigInt: bigint
) {}
static fromBytes(memKeys: Uint8Array): MembershipKey {
const idKey = memKeys.subarray(0, 32);
const idCommitment = memKeys.subarray(32);
static fromBytes(memKeys: Uint8Array): IdentityCredential {
const idTrapdoor = memKeys.subarray(0, 32);
const idNullifier = memKeys.subarray(32, 64);
const idSecretHash = memKeys.subarray(64, 96);
const idCommitment = memKeys.subarray(96);
const idCommitmentBigInt = buildBigIntFromUint8Array(idCommitment);
return new MembershipKey(idKey, idCommitment, idCommitmentBigInt);
return new IdentityCredential(
idTrapdoor,
idNullifier,
idSecretHash,
idCommitment,
idCommitmentBigInt
);
}
}
@ -89,6 +100,14 @@ const shareYOffset = shareXOffset + 32;
const nullifierOffset = shareYOffset + 32;
const rlnIdentifierOffset = nullifierOffset + 32;
export class ProofMetadata {
constructor(
public readonly nullifier: Uint8Array,
public readonly shareX: Uint8Array,
public readonly shareY: Uint8Array,
public readonly externalNullifier: Uint8Array
) {}
}
export class Proof implements IRateLimitProof {
readonly proof: Uint8Array;
readonly merkleRoot: Uint8Array;
@ -112,6 +131,16 @@ export class Proof implements IRateLimitProof {
rlnIdentifierOffset
);
}
extractMetadata(): ProofMetadata {
const externalNullifier = poseidonHash(this.epoch, this.rlnIdentifier);
return new ProofMetadata(
this.nullifier,
this.shareX,
this.shareY,
externalNullifier
);
}
}
export function proofToBytes(p: IRateLimitProof): Uint8Array {
@ -126,30 +155,60 @@ export function proofToBytes(p: IRateLimitProof): Uint8Array {
);
}
export function poseidonHash(...input: Array<Uint8Array>): Uint8Array {
const inputLen = writeUIntLE(new Uint8Array(8), input.length, 0, 8);
const lenPrefixedData = concatenate(inputLen, ...input);
return zerokitRLN.poseidonHash(lenPrefixedData);
}
export function sha256(input: Uint8Array): Uint8Array {
const inputLen = writeUIntLE(new Uint8Array(8), input.length, 0, 8);
const lenPrefixedData = concatenate(inputLen, input);
return zerokitRLN.hash(lenPrefixedData);
}
export class RLNInstance {
constructor(
private zkRLN: number,
private witnessCalculator: WitnessCalculator
) {}
generateMembershipKey(): MembershipKey {
const memKeys = zerokitRLN.generateMembershipKey(this.zkRLN);
return MembershipKey.fromBytes(memKeys);
generateIdentityCredentials(): IdentityCredential {
const memKeys = zerokitRLN.generateExtendedMembershipKey(this.zkRLN); // TODO: rename this function in zerokit rln-wasm
return IdentityCredential.fromBytes(memKeys);
}
generateSeededMembershipKey(seed: string): MembershipKey {
generateSeededIdentityCredential(seed: string): IdentityCredential {
const seedBytes = stringEncoder.encode(seed);
const memKeys = zerokitRLN.generateSeededMembershipKey(
// TODO: rename this function in zerokit rln-wasm
const memKeys = zerokitRLN.generateSeededExtendedMembershipKey(
this.zkRLN,
seedBytes
);
return MembershipKey.fromBytes(memKeys);
return IdentityCredential.fromBytes(memKeys);
}
insertMember(idCommitment: Uint8Array): void {
zerokitRLN.insertMember(this.zkRLN, idCommitment);
}
insertMembers(index: number, ...idCommitments: Array<Uint8Array>): void {
// serializes a seq of IDCommitments to a byte seq
// the order of serialization is |id_commitment_len<8>|id_commitment<var>|
const idCommitmentLen = writeUIntLE(
new Uint8Array(8),
idCommitments.length,
0,
8
);
const idCommitmentBytes = concatenate(idCommitmentLen, ...idCommitments);
zerokitRLN.setLeavesFrom(this.zkRLN, index, idCommitmentBytes);
}
deleteMember(index: number): void {
zerokitRLN.deleteLeaf(this.zkRLN, index);
}
getMerkleRoot(): Uint8Array {
return zerokitRLN.getRoot(this.zkRLN);
}
@ -174,7 +233,7 @@ export class RLNInstance {
msg: Uint8Array,
index: number,
epoch: Uint8Array | Date | undefined,
idKey: Uint8Array
idSecretHash: Uint8Array
): Promise<IRateLimitProof> {
if (epoch == undefined) {
epoch = epochIntToBytes(dateToEpoch(new Date()));
@ -183,10 +242,15 @@ export class RLNInstance {
}
if (epoch.length != 32) throw "invalid epoch";
if (idKey.length != 32) throw "invalid id key";
if (idSecretHash.length != 32) throw "invalid id secret hash";
if (index < 0) throw "index must be >= 0";
const serialized_msg = this.serializeMessage(msg, index, epoch, idKey);
const serialized_msg = this.serializeMessage(
msg,
index,
epoch,
idSecretHash
);
const rlnWitness = zerokitRLN.getSerializedRLNWitness(
this.zkRLN,
serialized_msg
@ -228,7 +292,8 @@ export class RLNInstance {
verifyWithRoots(
proof: IRateLimitProof | Uint8Array,
msg: Uint8Array
msg: Uint8Array,
...roots: Array<Uint8Array>
): boolean {
let pBytes: Uint8Array;
if (proof instanceof Uint8Array) {
@ -236,17 +301,15 @@ export class RLNInstance {
} else {
pBytes = proofToBytes(proof);
}
// calculate message length
const msgLen = writeUIntLE(new Uint8Array(8), msg.length, 0, 8);
// obtain root
const root = zerokitRLN.getRoot(this.zkRLN);
const rootsBytes = concatenate(...roots);
return zerokitRLN.verifyWithRoots(
this.zkRLN,
concatenate(pBytes, msgLen, msg),
root
rootsBytes
);
}

View File

@ -1,7 +1,7 @@
import { ethers } from "ethers";
import { RLN_ABI } from "./constants.js";
import { MembershipKey, RLNInstance } from "./rln.js";
import { IdentityCredential, RLNInstance } from "./rln.js";
type Member = {
pubkey: string;
@ -94,20 +94,19 @@ export class RLNContract {
rlnInstance: RLNInstance,
signature: string
): Promise<ethers.Event | undefined> {
const membershipKey = await rlnInstance.generateSeededMembershipKey(
signature
);
const identityCredential =
await rlnInstance.generateSeededIdentityCredential(signature);
return this.registerWithKey(membershipKey);
return this.registerWithKey(identityCredential);
}
public async registerWithKey(
membershipKey: MembershipKey
credential: IdentityCredential
): Promise<ethers.Event | undefined> {
const depositValue = await this.contract.MEMBERSHIP_DEPOSIT();
const txRegisterResponse: ethers.ContractTransaction =
await this.contract.register(membershipKey.IDCommitmentBigInt, {
await this.contract.register(credential.IDCommitmentBigInt, {
value: depositValue,
});
const txRegisterReceipt = await txRegisterResponse.wait();

56
src/root_tracker.spec.ts Normal file
View File

@ -0,0 +1,56 @@
import { assert, expect } from "chai";
import { MerkleRootTracker } from "./root_tracker";
describe("js-rln", () => {
it("should track merkle roots and backfill from block number", async function () {
const acceptableRootWindow = 3;
const tracker = new MerkleRootTracker(
acceptableRootWindow,
new Uint8Array([0, 0, 0, 0])
);
expect(tracker.roots()).to.have.length(1);
expect(tracker.buffer()).to.have.length(0);
expect(tracker.roots()[0]).to.deep.equal(new Uint8Array([0, 0, 0, 0]));
for (let i = 1; i <= 30; i++) {
tracker.pushRoot(i, new Uint8Array([0, 0, 0, i]));
}
expect(tracker.roots()).to.have.length(acceptableRootWindow);
expect(tracker.buffer()).to.have.length(20);
assert.sameDeepMembers(tracker.roots(), [
new Uint8Array([0, 0, 0, 30]),
new Uint8Array([0, 0, 0, 29]),
new Uint8Array([0, 0, 0, 28]),
]);
// Buffer should keep track of 20 blocks previous to the current valid merkle root window
expect(tracker.buffer()[0]).to.be.eql(new Uint8Array([0, 0, 0, 8]));
expect(tracker.buffer()[19]).to.be.eql(new Uint8Array([0, 0, 0, 27]));
// Remove roots 29 and 30
tracker.backFill(29);
assert.sameDeepMembers(tracker.roots(), [
new Uint8Array([0, 0, 0, 28]),
new Uint8Array([0, 0, 0, 27]),
new Uint8Array([0, 0, 0, 26]),
]);
expect(tracker.buffer()).to.have.length(18);
expect(tracker.buffer()[0]).to.be.eql(new Uint8Array([0, 0, 0, 8]));
expect(tracker.buffer()[17]).to.be.eql(new Uint8Array([0, 0, 0, 25]));
// Remove roots from block 15 onwards. These blocks exists within the buffer
tracker.backFill(15);
assert.sameDeepMembers(tracker.roots(), [
new Uint8Array([0, 0, 0, 14]),
new Uint8Array([0, 0, 0, 13]),
new Uint8Array([0, 0, 0, 12]),
]);
expect(tracker.buffer()).to.have.length(4);
expect(tracker.buffer()[0]).to.be.eql(new Uint8Array([0, 0, 0, 8]));
expect(tracker.buffer()[3]).to.be.eql(new Uint8Array([0, 0, 0, 11]));
});
});

88
src/root_tracker.ts Normal file
View File

@ -0,0 +1,88 @@
class RootPerBlock {
constructor(public root: Uint8Array, public blockNumber: number) {}
}
const maxBufferSize = 20;
export class MerkleRootTracker {
private validMerkleRoots: Array<RootPerBlock> = new Array<RootPerBlock>();
private merkleRootBuffer: Array<RootPerBlock> = new Array<RootPerBlock>();
constructor(
private acceptableRootWindowSize: number,
initialRoot: Uint8Array
) {
this.pushRoot(0, initialRoot);
}
backFill(fromBlockNumber: number): void {
if (this.validMerkleRoots.length == 0) return;
let numBlocks = 0;
for (let i = this.validMerkleRoots.length - 1; i >= 0; i--) {
if (this.validMerkleRoots[i].blockNumber >= fromBlockNumber) {
numBlocks++;
}
}
if (numBlocks == 0) return;
const olderBlock = fromBlockNumber < this.validMerkleRoots[0].blockNumber;
// Remove last roots
let rootsToPop = numBlocks;
if (this.validMerkleRoots.length < rootsToPop) {
rootsToPop = this.validMerkleRoots.length;
}
this.validMerkleRoots = this.validMerkleRoots.slice(
0,
this.validMerkleRoots.length - rootsToPop
);
if (this.merkleRootBuffer.length == 0) return;
if (olderBlock) {
const idx = this.merkleRootBuffer.findIndex(
(x) => x.blockNumber == fromBlockNumber
);
if (idx > -1) {
this.merkleRootBuffer = this.merkleRootBuffer.slice(0, idx);
}
}
// Backfill the tree's acceptable roots
let rootsToRestore =
this.acceptableRootWindowSize - this.validMerkleRoots.length;
if (this.merkleRootBuffer.length < rootsToRestore) {
rootsToRestore = this.merkleRootBuffer.length;
}
for (let i = 0; i < rootsToRestore; i++) {
const x = this.merkleRootBuffer.pop();
if (x) this.validMerkleRoots.unshift(x);
}
}
pushRoot(blockNumber: number, root: Uint8Array): void {
this.validMerkleRoots.push(new RootPerBlock(root, blockNumber));
// Maintain valid merkle root window
if (this.validMerkleRoots.length > this.acceptableRootWindowSize) {
const x = this.validMerkleRoots.shift();
if (x) this.merkleRootBuffer.push(x);
}
// Maintain merkle root buffer
if (this.merkleRootBuffer.length > maxBufferSize) {
this.merkleRootBuffer.shift();
}
}
roots(): Array<Uint8Array> {
return this.validMerkleRoots.map((x) => x.root);
}
buffer(): Array<Uint8Array> {
return this.merkleRootBuffer.map((x) => x.root);
}
}