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

View File

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

View File

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

18
package-lock.json generated
View File

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

View File

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

View File

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

View File

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

View File

@ -6,7 +6,7 @@ describe("js-rln", () => {
it("should verify a proof", async function () { it("should verify a proof", async function () {
const rlnInstance = await rln.create(); const rlnInstance = await rln.create();
const memKeys = rlnInstance.generateMembershipKey(); const credential = rlnInstance.generateIdentityCredentials();
//peer's index in the Merkle Tree //peer's index in the Merkle Tree
const index = 5; const index = 5;
@ -15,11 +15,11 @@ describe("js-rln", () => {
for (let i = 0; i < 10; i++) { for (let i = 0; i < 10; i++) {
if (i == index) { if (i == index) {
// insert the current peer's pk // insert the current peer's pk
rlnInstance.insertMember(memKeys.IDCommitment); rlnInstance.insertMember(credential.IDCommitment);
} else { } else {
// create a new key pair // create a new key pair
rlnInstance.insertMember( rlnInstance.insertMember(
rlnInstance.generateMembershipKey().IDCommitment rlnInstance.generateIdentityCredentials().IDCommitment
); );
} }
} }
@ -37,7 +37,7 @@ describe("js-rln", () => {
uint8Msg, uint8Msg,
index, index,
epoch, epoch,
memKeys.IDKey credential.IDSecretHash
); );
try { try {
@ -61,7 +61,7 @@ describe("js-rln", () => {
it("should verify a proof with a seeded membership key generation", async function () { it("should verify a proof with a seeded membership key generation", async function () {
const rlnInstance = await rln.create(); const rlnInstance = await rln.create();
const seed = "This is a test seed"; const seed = "This is a test seed";
const memKeys = rlnInstance.generateSeededMembershipKey(seed); const credential = rlnInstance.generateSeededIdentityCredential(seed);
//peer's index in the Merkle Tree //peer's index in the Merkle Tree
const index = 5; const index = 5;
@ -70,11 +70,11 @@ describe("js-rln", () => {
for (let i = 0; i < 10; i++) { for (let i = 0; i < 10; i++) {
if (i == index) { if (i == index) {
// insert the current peer's pk // insert the current peer's pk
rlnInstance.insertMember(memKeys.IDCommitment); rlnInstance.insertMember(credential.IDCommitment);
} else { } else {
// create a new key pair // create a new key pair
rlnInstance.insertMember( rlnInstance.insertMember(
rlnInstance.generateMembershipKey().IDCommitment rlnInstance.generateIdentityCredentials().IDCommitment
); );
} }
} }
@ -92,7 +92,7 @@ describe("js-rln", () => {
uint8Msg, uint8Msg,
index, index,
epoch, epoch,
memKeys.IDKey credential.IDSecretHash
); );
try { try {
@ -116,14 +116,20 @@ describe("js-rln", () => {
it("should generate the same membership key if the same seed is provided", async function () { it("should generate the same membership key if the same seed is provided", async function () {
const rlnInstance = await rln.create(); const rlnInstance = await rln.create();
const seed = "This is a test seed"; const seed = "This is a test seed";
const memKeys1 = rlnInstance.generateSeededMembershipKey(seed); const memKeys1 = rlnInstance.generateSeededIdentityCredential(seed);
const memKeys2 = rlnInstance.generateSeededMembershipKey(seed); const memKeys2 = rlnInstance.generateSeededIdentityCredential(seed);
memKeys1.IDCommitment.forEach((element, index) => { memKeys1.IDCommitment.forEach((element, index) => {
expect(element).to.equal(memKeys2.IDCommitment[index]); expect(element).to.equal(memKeys2.IDCommitment[index]);
}); });
memKeys1.IDKey.forEach((element, index) => { memKeys1.IDNullifier.forEach((element, index) => {
expect(element).to.equal(memKeys2.IDKey[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 { RLNDecoder, RLNEncoder } from "./codec.js";
import { GOERLI_CONTRACT, RLN_ABI } from "./constants.js"; import { GOERLI_CONTRACT, RLN_ABI } from "./constants.js";
import { Proof, RLNInstance } from "./rln.js"; import {
import { MembershipKey } from "./rln.js"; IdentityCredential,
Proof,
ProofMetadata,
RLNInstance,
} from "./rln.js";
import { RLNContract } from "./rln_contract.js"; import { RLNContract } from "./rln_contract.js";
import { MerkleRootTracker } from "./root_tracker.js";
// reexport the create function, dynamically imported from rln.ts // reexport the create function, dynamically imported from rln.ts
export async function create(): Promise<RLNInstance> { export async function create(): Promise<RLNInstance> {
@ -15,10 +20,12 @@ export async function create(): Promise<RLNInstance> {
export { export {
RLNInstance, RLNInstance,
MembershipKey, IdentityCredential,
Proof, Proof,
ProofMetadata,
RLNEncoder, RLNEncoder,
RLNDecoder, RLNDecoder,
MerkleRootTracker,
RLNContract, RLNContract,
RLN_ABI, RLN_ABI,
GOERLI_CONTRACT, GOERLI_CONTRACT,

Binary file not shown.

Binary file not shown.

View File

@ -1,120 +1,112 @@
const verificationKey = { const verificationKey = {
"protocol": "groth16", protocol: "groth16",
"curve": "bn128", curve: "bn128",
"nPublic": 6, nPublic: 6,
"vk_alpha_1": [ vk_alpha_1: [
"1805378556360488226980822394597799963030511477964155500103132920745199284516", "20124996762962216725442980738609010303800849578410091356605067053491763969391",
"11990395240534218699464972016456017378439762088320057798320175886595281336136", "9118593021526896828671519912099489027245924097793322973632351264852174143923",
"1" "1",
], ],
"vk_beta_2": [ vk_beta_2: [
[ [
"11031529986141021025408838211017932346992429731488270384177563837022796743627", "4693952934005375501364248788849686435240706020501681709396105298107971354382",
"16042159910707312759082561183373181639420894978640710177581040523252926273854" "14346958885444710485362620645446987998958218205939139994511461437152241966681",
], ],
[ [
"20112698439519222240302944148895052359035104222313380895334495118294612255131", "16851772916911573982706166384196538392731905827088356034885868448550849804972",
"19441583024670359810872018179190533814486480928824742448673677460151702019379" "823612331030938060799959717749043047845343400798220427319188951998582076532",
], ],
[ ["1", "0"],
"1",
"0"
]
], ],
"vk_gamma_2": [ vk_gamma_2: [
[ [
"10857046999023057135944570762232829481370756359578518086990519993285655852781", "10857046999023057135944570762232829481370756359578518086990519993285655852781",
"11559732032986387107991004021392285783925812861821192530917403151452391805634" "11559732032986387107991004021392285783925812861821192530917403151452391805634",
], ],
[ [
"8495653923123431417604973247489272438418190587263600148770280649306958101930", "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", "1",
"0"
]
],
"vk_delta_2": [
[
"1948496782571164085469528023647105317580208688174386157591917599801657832035",
"20445814069256658101339037520922621162739470138213615104905368409238414511981"
],
[
"10024680869920840984813249386422727863826862577760330492647062850849851925340",
"10512156247842686783409460795717734694774542185222602679117887145206209285142"
], ],
[ [
"17018665030246167677911144513385572506766200776123272044534328594850561667818",
"18601114175490465275436712413925513066546725461375425769709566180981674884464",
"1", "1",
"0" ],
] [
"18799470100699658367834559797874857804183288553462108031963980039244731716542",
"13064227487174191981628537974951887429496059857753101852163607049188825592007",
"1",
],
[
"17432501889058124609368103715904104425610382063762621017593209214189134571156",
"13406815149699834788256141097399354592751313348962590382887503595131085938635",
"1",
],
[
"10320964835612716439094703312987075811498239445882526576970512041988148264481",
"9024164961646353611176283204118089412001502110138072989569118393359029324867",
"1",
],
[
"718355081067365548229685160476620267257521491773976402837645005858953849298",
"14635482993933988261008156660773180150752190597753512086153001683711587601974",
"1",
],
[
"11777720285956632126519898515392071627539405001940313098390150593689568177535",
"8483603647274280691250972408211651407952870456587066148445913156086740744515",
"1",
],
], ],
"vk_alphabeta_12": [ };
[
[ export default verificationKey;
"5151991366823434428398919091000210787450832786814248297320989361921939794156",
"15735191313289001022885148627913534790382722933676436876510746491415970766821"
],
[
"3387907257437913904447588318761906430938415556102110876587455322225272831272",
"1998779853452712881084781956683721603875246565720647583735935725110674288056"
],
[
"14280074182991498185075387990446437410077692353432005297922275464876153151820",
"17092408446352310039633488224969232803092763095456307462247653153107223117633"
]
],
[
[
"4359046709531668109201634396816565829237358165496082832279660960675584351266",
"4511888308846208349307186938266411423935335853916317436093178288331845821336"
],
[
"11429499807090785857812316277335883295048773373068683863667725283965356423273",
"16232274853200678548795010078253506586114563833318973594428907292096178657392"
],
[
"18068999605870933925311275504102553573815570223888590384919752303726860800970",
"17309569111965782732372130116757295842160193489132771344011460471298173784984"
]
]
],
"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

View File

@ -66,18 +66,29 @@ export async function create(): Promise<RLNInstance> {
return new RLNInstance(zkRLN, witnessCalculator); return new RLNInstance(zkRLN, witnessCalculator);
} }
export class MembershipKey { export class IdentityCredential {
constructor( constructor(
public readonly IDKey: Uint8Array, public readonly IDTrapdoor: Uint8Array,
public readonly IDNullifier: Uint8Array,
public readonly IDSecretHash: Uint8Array,
public readonly IDCommitment: Uint8Array, public readonly IDCommitment: Uint8Array,
public readonly IDCommitmentBigInt: bigint public readonly IDCommitmentBigInt: bigint
) {} ) {}
static fromBytes(memKeys: Uint8Array): MembershipKey { static fromBytes(memKeys: Uint8Array): IdentityCredential {
const idKey = memKeys.subarray(0, 32); const idTrapdoor = memKeys.subarray(0, 32);
const idCommitment = memKeys.subarray(32); const idNullifier = memKeys.subarray(32, 64);
const idSecretHash = memKeys.subarray(64, 96);
const idCommitment = memKeys.subarray(96);
const idCommitmentBigInt = buildBigIntFromUint8Array(idCommitment); 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 nullifierOffset = shareYOffset + 32;
const rlnIdentifierOffset = nullifierOffset + 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 { export class Proof implements IRateLimitProof {
readonly proof: Uint8Array; readonly proof: Uint8Array;
readonly merkleRoot: Uint8Array; readonly merkleRoot: Uint8Array;
@ -112,6 +131,16 @@ export class Proof implements IRateLimitProof {
rlnIdentifierOffset 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 { 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 { export class RLNInstance {
constructor( constructor(
private zkRLN: number, private zkRLN: number,
private witnessCalculator: WitnessCalculator private witnessCalculator: WitnessCalculator
) {} ) {}
generateMembershipKey(): MembershipKey { generateIdentityCredentials(): IdentityCredential {
const memKeys = zerokitRLN.generateMembershipKey(this.zkRLN); const memKeys = zerokitRLN.generateExtendedMembershipKey(this.zkRLN); // TODO: rename this function in zerokit rln-wasm
return MembershipKey.fromBytes(memKeys); return IdentityCredential.fromBytes(memKeys);
} }
generateSeededMembershipKey(seed: string): MembershipKey { generateSeededIdentityCredential(seed: string): IdentityCredential {
const seedBytes = stringEncoder.encode(seed); const seedBytes = stringEncoder.encode(seed);
const memKeys = zerokitRLN.generateSeededMembershipKey( // TODO: rename this function in zerokit rln-wasm
const memKeys = zerokitRLN.generateSeededExtendedMembershipKey(
this.zkRLN, this.zkRLN,
seedBytes seedBytes
); );
return MembershipKey.fromBytes(memKeys); return IdentityCredential.fromBytes(memKeys);
} }
insertMember(idCommitment: Uint8Array): void { insertMember(idCommitment: Uint8Array): void {
zerokitRLN.insertMember(this.zkRLN, idCommitment); 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 { getMerkleRoot(): Uint8Array {
return zerokitRLN.getRoot(this.zkRLN); return zerokitRLN.getRoot(this.zkRLN);
} }
@ -174,7 +233,7 @@ export class RLNInstance {
msg: Uint8Array, msg: Uint8Array,
index: number, index: number,
epoch: Uint8Array | Date | undefined, epoch: Uint8Array | Date | undefined,
idKey: Uint8Array idSecretHash: Uint8Array
): Promise<IRateLimitProof> { ): Promise<IRateLimitProof> {
if (epoch == undefined) { if (epoch == undefined) {
epoch = epochIntToBytes(dateToEpoch(new Date())); epoch = epochIntToBytes(dateToEpoch(new Date()));
@ -183,10 +242,15 @@ export class RLNInstance {
} }
if (epoch.length != 32) throw "invalid epoch"; 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"; 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( const rlnWitness = zerokitRLN.getSerializedRLNWitness(
this.zkRLN, this.zkRLN,
serialized_msg serialized_msg
@ -228,7 +292,8 @@ export class RLNInstance {
verifyWithRoots( verifyWithRoots(
proof: IRateLimitProof | Uint8Array, proof: IRateLimitProof | Uint8Array,
msg: Uint8Array msg: Uint8Array,
...roots: Array<Uint8Array>
): boolean { ): boolean {
let pBytes: Uint8Array; let pBytes: Uint8Array;
if (proof instanceof Uint8Array) { if (proof instanceof Uint8Array) {
@ -236,17 +301,15 @@ export class RLNInstance {
} else { } else {
pBytes = proofToBytes(proof); pBytes = proofToBytes(proof);
} }
// calculate message length // calculate message length
const msgLen = writeUIntLE(new Uint8Array(8), msg.length, 0, 8); const msgLen = writeUIntLE(new Uint8Array(8), msg.length, 0, 8);
// obtain root const rootsBytes = concatenate(...roots);
const root = zerokitRLN.getRoot(this.zkRLN);
return zerokitRLN.verifyWithRoots( return zerokitRLN.verifyWithRoots(
this.zkRLN, this.zkRLN,
concatenate(pBytes, msgLen, msg), concatenate(pBytes, msgLen, msg),
root rootsBytes
); );
} }

View File

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