mirror of
https://github.com/logos-messaging/js-rln.git
synced 2026-01-05 23:23:12 +00:00
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:
parent
7e0966aef7
commit
e52107dcf8
10
README.md
10
README.md
@ -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
|
||||
);
|
||||
```
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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
18
package-lock.json
generated
@ -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",
|
||||
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
14
src/codec.ts
14
src/codec.ts
@ -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
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -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]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
13
src/index.ts
13
src/index.ts
@ -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.
@ -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;
|
||||
|
||||
103
src/rln.ts
103
src/rln.ts
@ -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
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -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
56
src/root_tracker.spec.ts
Normal 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
88
src/root_tracker.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user