@waku/rln 0.0.12 → 0.0.13-d77370f

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rln_contract.js","sourceRoot":"","sources":["../src/rln_contract.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAazC,MAAM,OAAO,WAAW;IAkBtB,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAmB;QAd1C,aAAQ,GAAa,EAAE,CAAC;QAe9B,IAAI,CAAC,SAAS,GAAG,IAAI,MAAM,CAAC,QAAQ,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;QACjE,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAChE,CAAC;IAfM,MAAM,CAAC,KAAK,CAAC,IAAI,CACtB,WAAwB,EACxB,OAAwB;QAExB,MAAM,WAAW,GAAG,IAAI,WAAW,CAAC,OAAO,CAAC,CAAC;QAE7C,MAAM,WAAW,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC;QAC5C,WAAW,CAAC,kBAAkB,CAAC,WAAW,CAAC,CAAC;QAE5C,OAAO,WAAW,CAAC;IACrB,CAAC;IAOD,IAAW,QAAQ;QACjB,OAAO,IAAI,CAAC,SAAS,CAAC;IACxB,CAAC;IAED,IAAW,OAAO;QAChB,OAAO,IAAI,CAAC,QAAQ,CAAC;IACvB,CAAC;IAEM,KAAK,CAAC,YAAY,CACvB,WAAwB,EACxB,SAAkB;QAElB,MAAM,sBAAsB,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,WAAW,CAC5D,IAAI,CAAC,aAAa,EAClB,SAAS,CACV,CAAC;QAEF,KAAK,MAAM,KAAK,IAAI,sBAAsB,EAAE;YAC1C,IAAI,CAAC,kBAAkB,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;SAC7C;IACH,CAAC;IAEM,kBAAkB,CAAC,WAAwB;QAChD,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,CAC9D,IAAI,CAAC,kBAAkB,CAAC,WAAW,EAAE,KAAK,CAAC,CAC5C,CAAC;IACJ,CAAC;IAEO,kBAAkB,CACxB,WAAwB,EACxB,KAAmB;QAEnB,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE;YACf,OAAO;SACR;QAED,MAAM,MAAM,GAAW,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC;QACzC,MAAM,KAAK,GAAW,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC;QAEvC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;QAErC,MAAM,YAAY,GAAG,MAAM,CAAC,KAAK,CAAC,OAAO,CACvC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,EAC7B,EAAE,CACH,CAAC;QACF,WAAW,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC;IACzC,CAAC;IAEM,KAAK,CAAC,cAAc,CACzB,WAAwB,EACxB,SAAiB;QAEjB,MAAM,aAAa,GAAG,MAAM,WAAW,CAAC,2BAA2B,CACjE,SAAS,CACV,CAAC;QACF,MAAM,YAAY,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,kBAAkB,EAAE,CAAC;QAE9D,MAAM,kBAAkB,GACtB,MAAM,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,aAAa,CAAC,kBAAkB,EAAE;YAC7D,KAAK,EAAE,YAAY;SACpB,CAAC,CAAC;QACL,MAAM,iBAAiB,GAAG,MAAM,kBAAkB,CAAC,IAAI,EAAE,CAAC;QAE1D,OAAO,iBAAiB,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC;IACxC,CAAC;CACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@waku/rln",
3
- "version": "0.0.12",
3
+ "version": "0.0.13-d77370f",
4
4
  "description": "Rate Limit Nullifier for js-waku",
5
5
  "types": "./dist/index.d.ts",
6
6
  "module": "./dist/index.js",
@@ -59,6 +59,7 @@
59
59
  "@size-limit/preset-big-lib": "^8.0.0",
60
60
  "@types/app-root-path": "^1.2.4",
61
61
  "@types/chai": "^4.2.15",
62
+ "@types/chai-spies": "^1.0.3",
62
63
  "@types/debug": "^4.1.7",
63
64
  "@types/mocha": "^9.1.0",
64
65
  "@types/node": "^17.0.6",
@@ -69,6 +70,7 @@
69
70
  "@web/rollup-plugin-import-meta-assets": "^1.0.7",
70
71
  "app-root-path": "^3.0.0",
71
72
  "chai": "^4.3.4",
73
+ "chai-spies": "^1.0.0",
72
74
  "cspell": "^5.14.0",
73
75
  "eslint": "^8.6.0",
74
76
  "eslint-config-prettier": "^8.3.0",
@@ -125,6 +127,7 @@
125
127
  ]
126
128
  },
127
129
  "dependencies": {
128
- "@waku/zerokit-rln-wasm": "^0.0.4"
130
+ "@waku/zerokit-rln-wasm": "^0.0.5",
131
+ "ethers": "^5.7.2"
129
132
  }
130
133
  }
package/src/codec.ts CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  import { RlnMessage, toRLNSignal } from "./message.js";
11
11
  import { MembershipKey, RLNInstance } from "./rln.js";
12
12
 
13
- const log = debug("waku:message:rln-encoder");
13
+ const log = debug("waku:rln:encoder");
14
14
 
15
15
  export class RLNEncoder implements Encoder {
16
16
  public contentTopic: string;
@@ -30,7 +30,7 @@ export class RLNEncoder implements Encoder {
30
30
  async toWire(message: Partial<Message>): Promise<Uint8Array | undefined> {
31
31
  message.contentTopic = this.contentTopic;
32
32
  message.rateLimitProof = await this.generateProof(message);
33
-
33
+ log("Proof generated", message.rateLimitProof);
34
34
  return this.encoder.toWire(message);
35
35
  }
36
36
 
@@ -42,7 +42,7 @@ export class RLNEncoder implements Encoder {
42
42
  if (!protoMessage) return;
43
43
 
44
44
  protoMessage.rateLimitProof = await this.generateProof(message);
45
-
45
+ log("Proof generated", protoMessage.rateLimitProof);
46
46
  return protoMessage;
47
47
  }
48
48
 
@@ -0,0 +1,14 @@
1
+ export const RLN_ABI = [
2
+ "function MEMBERSHIP_DEPOSIT() public view returns(uint256)",
3
+ "function register(uint256 pubkey) external payable",
4
+ "function withdraw(uint256 secret, uint256 _pubkeyIndex, address payable receiver) external",
5
+ "event MemberRegistered(uint256 pubkey, uint256 index)",
6
+ "event MemberWithdrawn(uint256 pubkey, uint256 index)",
7
+ ];
8
+
9
+ export const GOERLI_CONTRACT = {
10
+ chainId: 5,
11
+ startBlock: 7109391,
12
+ address: "0x4252105670fe33d2947e8ead304969849e64f2a6",
13
+ abi: RLN_ABI,
14
+ };
package/src/epoch.ts CHANGED
@@ -1,21 +1,30 @@
1
+ import debug from "debug";
2
+
1
3
  const DefaultEpochUnitSeconds = 10; // the rln-relay epoch length in seconds
2
4
 
5
+ const log = debug("waku:rln:epoch");
6
+
3
7
  export function dateToEpoch(
4
8
  timestamp: Date,
5
9
  epochUnitSeconds: number = DefaultEpochUnitSeconds
6
10
  ): number {
7
11
  const time = timestamp.getTime();
8
- return Math.floor(time / 1000 / epochUnitSeconds);
12
+ const epoch = Math.floor(time / 1000 / epochUnitSeconds);
13
+ log("generated epoch", epoch);
14
+ return epoch;
9
15
  }
10
16
 
11
17
  export function epochIntToBytes(epoch: number): Uint8Array {
12
18
  const bytes = new Uint8Array(32);
13
19
  const db = new DataView(bytes.buffer);
14
20
  db.setUint32(0, epoch, true);
21
+ log("encoded epoch", epoch, bytes);
15
22
  return bytes;
16
23
  }
17
24
 
18
25
  export function epochBytesToInt(bytes: Uint8Array): number {
19
- const dv = new DataView(bytes.buffer);
20
- return dv.getUint32(0, true);
26
+ const dv = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
27
+ const epoch = dv.getUint32(0, true);
28
+ log("decoded epoch", epoch, bytes);
29
+ return epoch;
21
30
  }
package/src/index.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import { RLNDecoder, RLNEncoder } from "./codec.js";
2
- import type { Proof, RLNInstance } from "./rln.js";
2
+ import { GOERLI_CONTRACT, RLN_ABI } from "./constants.js";
3
+ import { Proof, RLNInstance } from "./rln.js";
3
4
  import { MembershipKey } from "./rln.js";
5
+ import { RLNContract } from "./rln_contract.js";
4
6
 
5
7
  // reexport the create function, dynamically imported from rln.ts
6
8
  export async function create(): Promise<RLNInstance> {
@@ -11,4 +13,13 @@ export async function create(): Promise<RLNInstance> {
11
13
  return await rlnModule.create();
12
14
  }
13
15
 
14
- export { RLNInstance, MembershipKey, Proof, RLNEncoder, RLNDecoder };
16
+ export {
17
+ RLNInstance,
18
+ MembershipKey,
19
+ Proof,
20
+ RLNEncoder,
21
+ RLNDecoder,
22
+ RLNContract,
23
+ RLN_ABI,
24
+ GOERLI_CONTRACT,
25
+ };
package/src/message.ts CHANGED
@@ -22,6 +22,15 @@ export class RlnMessage<T extends Message> implements Message {
22
22
  : undefined;
23
23
  }
24
24
 
25
+ public verifyNoRoot(): boolean | undefined {
26
+ return this.rateLimitProof
27
+ ? this.rlnInstance.verifyWithNoRoot(
28
+ this.rateLimitProof,
29
+ toRLNSignal(this)
30
+ ) // this.rlnInstance.verifyRLNProof once issue status-im/nwaku#1248 is fixed
31
+ : undefined;
32
+ }
33
+
25
34
  get payload(): Uint8Array | undefined {
26
35
  return this.msg.payload;
27
36
  }
package/src/rln.ts CHANGED
@@ -26,6 +26,16 @@ function concatenate(...input: Uint8Array[]): Uint8Array {
26
26
  return result;
27
27
  }
28
28
 
29
+ /**
30
+ * Transforms Uint8Array into BigInt
31
+ * @param array: Uint8Array
32
+ * @returns BigInt
33
+ */
34
+ function buildBigIntFromUint8Array(array: Uint8Array): bigint {
35
+ const dataView = new DataView(array.buffer);
36
+ return dataView.getBigUint64(0, true);
37
+ }
38
+
29
39
  const stringEncoder = new TextEncoder();
30
40
 
31
41
  const DEPTH = 20;
@@ -59,13 +69,15 @@ export async function create(): Promise<RLNInstance> {
59
69
  export class MembershipKey {
60
70
  constructor(
61
71
  public readonly IDKey: Uint8Array,
62
- public readonly IDCommitment: Uint8Array
72
+ public readonly IDCommitment: Uint8Array,
73
+ public readonly IDCommitmentBigInt: bigint
63
74
  ) {}
64
75
 
65
76
  static fromBytes(memKeys: Uint8Array): MembershipKey {
66
77
  const idKey = memKeys.subarray(0, 32);
67
78
  const idCommitment = memKeys.subarray(32);
68
- return new MembershipKey(idKey, idCommitment);
79
+ const idCommitmentBigInt = buildBigIntFromUint8Array(idCommitment);
80
+ return new MembershipKey(idKey, idCommitment, idCommitmentBigInt);
69
81
  }
70
82
  }
71
83
 
@@ -125,6 +137,15 @@ export class RLNInstance {
125
137
  return MembershipKey.fromBytes(memKeys);
126
138
  }
127
139
 
140
+ generateSeededMembershipKey(seed: string): MembershipKey {
141
+ const seedBytes = stringEncoder.encode(seed);
142
+ const memKeys = zerokitRLN.generateSeededMembershipKey(
143
+ this.zkRLN,
144
+ seedBytes
145
+ );
146
+ return MembershipKey.fromBytes(memKeys);
147
+ }
148
+
128
149
  insertMember(idCommitment: Uint8Array): void {
129
150
  zerokitRLN.insertMember(this.zkRLN, idCommitment);
130
151
  }
@@ -225,4 +246,25 @@ export class RLNInstance {
225
246
  root
226
247
  );
227
248
  }
249
+
250
+ verifyWithNoRoot(
251
+ proof: RateLimitProof | Uint8Array,
252
+ msg: Uint8Array
253
+ ): boolean {
254
+ let pBytes: Uint8Array;
255
+ if (proof instanceof Uint8Array) {
256
+ pBytes = proof;
257
+ } else {
258
+ pBytes = proofToBytes(proof);
259
+ }
260
+
261
+ // calculate message length
262
+ const msgLen = writeUIntLE(new Uint8Array(8), msg.length, 0, 8);
263
+
264
+ return zerokitRLN.verifyWithRoots(
265
+ this.zkRLN,
266
+ concatenate(pBytes, msgLen, msg),
267
+ new Uint8Array()
268
+ );
269
+ }
228
270
  }
@@ -0,0 +1,104 @@
1
+ import { ethers } from "ethers";
2
+
3
+ import { RLN_ABI } from "./constants.js";
4
+ import { RLNInstance } from "./rln.js";
5
+
6
+ type Member = {
7
+ pubkey: string;
8
+ index: number;
9
+ };
10
+
11
+ type ContractOptions = {
12
+ address: string;
13
+ provider: ethers.Signer | ethers.providers.Provider;
14
+ };
15
+
16
+ export class RLNContract {
17
+ private _contract: ethers.Contract;
18
+ private membersFilter: ethers.EventFilter;
19
+
20
+ private _members: Member[] = [];
21
+
22
+ public static async init(
23
+ rlnInstance: RLNInstance,
24
+ options: ContractOptions
25
+ ): Promise<RLNContract> {
26
+ const rlnContract = new RLNContract(options);
27
+
28
+ await rlnContract.fetchMembers(rlnInstance);
29
+ rlnContract.subscribeToMembers(rlnInstance);
30
+
31
+ return rlnContract;
32
+ }
33
+
34
+ constructor({ address, provider }: ContractOptions) {
35
+ this._contract = new ethers.Contract(address, RLN_ABI, provider);
36
+ this.membersFilter = this.contract.filters.MemberRegistered();
37
+ }
38
+
39
+ public get contract(): ethers.Contract {
40
+ return this._contract;
41
+ }
42
+
43
+ public get members(): Member[] {
44
+ return this._members;
45
+ }
46
+
47
+ public async fetchMembers(
48
+ rlnInstance: RLNInstance,
49
+ fromBlock?: number
50
+ ): Promise<void> {
51
+ const registeredMemberEvents = await this.contract.queryFilter(
52
+ this.membersFilter,
53
+ fromBlock
54
+ );
55
+
56
+ for (const event of registeredMemberEvents) {
57
+ this.addMemberFromEvent(rlnInstance, event);
58
+ }
59
+ }
60
+
61
+ public subscribeToMembers(rlnInstance: RLNInstance): void {
62
+ this.contract.on(this.membersFilter, (_pubkey, _index, event) =>
63
+ this.addMemberFromEvent(rlnInstance, event)
64
+ );
65
+ }
66
+
67
+ private addMemberFromEvent(
68
+ rlnInstance: RLNInstance,
69
+ event: ethers.Event
70
+ ): void {
71
+ if (!event.args) {
72
+ return;
73
+ }
74
+
75
+ const pubkey: string = event.args.pubkey;
76
+ const index: number = event.args.index;
77
+
78
+ this.members.push({ index, pubkey });
79
+
80
+ const idCommitment = ethers.utils.zeroPad(
81
+ ethers.utils.arrayify(pubkey),
82
+ 32
83
+ );
84
+ rlnInstance.insertMember(idCommitment);
85
+ }
86
+
87
+ public async registerMember(
88
+ rlnInstance: RLNInstance,
89
+ signature: string
90
+ ): Promise<ethers.Event | undefined> {
91
+ const membershipKey = await rlnInstance.generateSeededMembershipKey(
92
+ signature
93
+ );
94
+ const depositValue = await this.contract.MEMBERSHIP_DEPOSIT();
95
+
96
+ const txRegisterResponse: ethers.ContractTransaction =
97
+ await this.contract.register(membershipKey.IDCommitmentBigInt, {
98
+ value: depositValue,
99
+ });
100
+ const txRegisterReceipt = await txRegisterResponse.wait();
101
+
102
+ return txRegisterReceipt?.events?.[0];
103
+ }
104
+ }