@waku/rln 0.1.1-5d7f77f → 0.1.1-77ba0a6

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.
Files changed (62) hide show
  1. package/README.md +26 -2
  2. package/bundle/assets/rln_wasm_bg-a503e304.wasm +0 -0
  3. package/bundle/index.js +68738 -22572
  4. package/dist/.tsbuildinfo +1 -1
  5. package/dist/byte_utils.d.ts +13 -0
  6. package/dist/byte_utils.js +22 -0
  7. package/dist/byte_utils.js.map +1 -1
  8. package/dist/codec.d.ts +5 -3
  9. package/dist/codec.js +8 -4
  10. package/dist/codec.js.map +1 -1
  11. package/dist/constants.d.ts +2 -2
  12. package/dist/constants.js +62 -9
  13. package/dist/constants.js.map +1 -1
  14. package/dist/create.d.ts +2 -0
  15. package/dist/create.js +8 -0
  16. package/dist/create.js.map +1 -0
  17. package/dist/index.d.ts +5 -3
  18. package/dist/index.js +5 -10
  19. package/dist/index.js.map +1 -1
  20. package/dist/keystore/cipher.d.ts +4 -0
  21. package/dist/keystore/cipher.js +28 -0
  22. package/dist/keystore/cipher.js.map +1 -0
  23. package/dist/keystore/credential_validation_generated.d.ts +8 -0
  24. package/dist/keystore/credential_validation_generated.js +121 -0
  25. package/dist/keystore/credential_validation_generated.js.map +1 -0
  26. package/dist/keystore/index.d.ts +4 -0
  27. package/dist/keystore/index.js +3 -0
  28. package/dist/keystore/index.js.map +1 -0
  29. package/dist/keystore/keystore.d.ts +50 -0
  30. package/dist/keystore/keystore.js +202 -0
  31. package/dist/keystore/keystore.js.map +1 -0
  32. package/dist/keystore/keystore_validation_generated.d.ts +8 -0
  33. package/dist/keystore/keystore_validation_generated.js +75 -0
  34. package/dist/keystore/keystore_validation_generated.js.map +1 -0
  35. package/dist/keystore/schema_validator.d.ts +2 -0
  36. package/dist/keystore/schema_validator.js +18 -0
  37. package/dist/keystore/schema_validator.js.map +1 -0
  38. package/dist/keystore/types.d.ts +29 -0
  39. package/dist/keystore/types.js +2 -0
  40. package/dist/keystore/types.js.map +1 -0
  41. package/dist/message.d.ts +1 -1
  42. package/dist/message.js +1 -1
  43. package/dist/metamask.d.ts +2 -0
  44. package/dist/metamask.js +11 -0
  45. package/dist/metamask.js.map +1 -0
  46. package/dist/rln.d.ts +47 -0
  47. package/dist/rln.js +110 -12
  48. package/dist/rln.js.map +1 -1
  49. package/dist/rln_contract.d.ts +24 -14
  50. package/dist/rln_contract.js +73 -33
  51. package/dist/rln_contract.js.map +1 -1
  52. package/package.json +23 -8
  53. package/src/byte_utils.ts +24 -0
  54. package/src/codec.ts +10 -5
  55. package/src/constants.ts +63 -9
  56. package/src/create.ts +9 -0
  57. package/src/index.ts +13 -11
  58. package/src/message.ts +1 -1
  59. package/src/metamask.ts +16 -0
  60. package/src/rln.ts +194 -13
  61. package/src/rln_contract.ts +127 -52
  62. package/bundle/assets/rln_wasm_bg-6f96f821.wasm +0 -0
package/src/rln.ts CHANGED
@@ -1,10 +1,28 @@
1
+ import { createDecoder, createEncoder } from "@waku/core";
1
2
  import type { IRateLimitProof } from "@waku/interfaces";
2
- import { default as init } from "@waku/zerokit-rln-wasm";
3
+ import type {
4
+ ContentTopic,
5
+ IDecodedMessage,
6
+ EncoderOptions as WakuEncoderOptions,
7
+ } from "@waku/interfaces";
8
+ import init from "@waku/zerokit-rln-wasm";
3
9
  import * as zerokitRLN from "@waku/zerokit-rln-wasm";
10
+ import { ethers } from "ethers";
4
11
 
5
- import { writeUIntLE } from "./byte_utils.js";
12
+ import { buildBigIntFromUint8Array, writeUIntLE } from "./byte_utils.js";
13
+ import type { RLNDecoder, RLNEncoder } from "./codec.js";
14
+ import { createRLNDecoder, createRLNEncoder } from "./codec.js";
15
+ import { SEPOLIA_CONTRACT } from "./constants.js";
6
16
  import { dateToEpoch, epochIntToBytes } from "./epoch.js";
17
+ import { Keystore } from "./keystore/index.js";
18
+ import type {
19
+ DecryptedCredentials,
20
+ EncryptedCredentials,
21
+ } from "./keystore/index.js";
22
+ import { Password } from "./keystore/types.js";
23
+ import { extractMetaMaskSigner } from "./metamask.js";
7
24
  import verificationKey from "./resources/verification_key.js";
25
+ import { RLNContract } from "./rln_contract.js";
8
26
  import * as wc from "./witness_calculator.js";
9
27
  import { WitnessCalculator } from "./witness_calculator.js";
10
28
 
@@ -27,16 +45,6 @@ function concatenate(...input: Uint8Array[]): Uint8Array {
27
45
  return result;
28
46
  }
29
47
 
30
- /**
31
- * Transforms Uint8Array into BigInt
32
- * @param array: Uint8Array
33
- * @returns BigInt
34
- */
35
- function buildBigIntFromUint8Array(array: Uint8Array): bigint {
36
- const dataView = new DataView(array.buffer);
37
- return dataView.getBigUint64(0, true);
38
- }
39
-
40
48
  const stringEncoder = new TextEncoder();
41
49
 
42
50
  const DEPTH = 20;
@@ -58,7 +66,7 @@ async function loadZkey(): Promise<Uint8Array> {
58
66
  * @returns RLNInstance
59
67
  */
60
68
  export async function create(): Promise<RLNInstance> {
61
- await init();
69
+ await (init as any)?.();
62
70
  zerokitRLN.init_panic_hook();
63
71
  const witnessCalculator = await loadWitnessCalculator();
64
72
  const zkey = await loadZkey();
@@ -168,12 +176,185 @@ export function sha256(input: Uint8Array): Uint8Array {
168
176
  return zerokitRLN.hash(lenPrefixedData);
169
177
  }
170
178
 
179
+ type StartRLNOptions = {
180
+ /**
181
+ * If not set - will extract MetaMask account and get signer from it.
182
+ */
183
+ signer?: ethers.Signer;
184
+ /**
185
+ * If not set - will use default SEPOLIA_CONTRACT address.
186
+ */
187
+ registryAddress?: string;
188
+ /**
189
+ * Credentials to use for generating proofs and connecting to the contract and network.
190
+ * If provided used for validating the network chainId and connecting to registry contract.
191
+ */
192
+ credentials?: EncryptedCredentials | DecryptedCredentials;
193
+ };
194
+
195
+ type RegisterMembershipOptions =
196
+ | { signature: string }
197
+ | { identity: IdentityCredential };
198
+
171
199
  export class RLNInstance {
200
+ private started = false;
201
+ private starting = false;
202
+
203
+ private _contract: undefined | RLNContract;
204
+ private _signer: undefined | ethers.Signer;
205
+
206
+ private keystore = Keystore.create();
207
+ private _credentials: undefined | DecryptedCredentials;
208
+
172
209
  constructor(
173
210
  private zkRLN: number,
174
211
  private witnessCalculator: WitnessCalculator
175
212
  ) {}
176
213
 
214
+ public get contract(): undefined | RLNContract {
215
+ return this._contract;
216
+ }
217
+
218
+ public get signer(): undefined | ethers.Signer {
219
+ return this._signer;
220
+ }
221
+
222
+ public async start(options: StartRLNOptions = {}): Promise<void> {
223
+ if (this.started || this.starting) {
224
+ return;
225
+ }
226
+
227
+ this.starting = true;
228
+
229
+ try {
230
+ const { signer, registryAddress } = await this.determineStartOptions(
231
+ options
232
+ );
233
+
234
+ this._signer = signer!;
235
+ this._contract = await RLNContract.init(this, {
236
+ registryAddress: registryAddress!,
237
+ signer: signer!,
238
+ });
239
+ this.started = true;
240
+ } finally {
241
+ this.starting = false;
242
+ }
243
+ }
244
+
245
+ private async determineStartOptions(
246
+ options: StartRLNOptions
247
+ ): Promise<StartRLNOptions> {
248
+ const credentials = await this.decryptCredentialsIfNeeded(
249
+ options.credentials
250
+ );
251
+
252
+ let chainId = credentials?.membership.chainId;
253
+ const registryAddress =
254
+ credentials?.membership.address ||
255
+ options.registryAddress ||
256
+ SEPOLIA_CONTRACT.address;
257
+
258
+ if (registryAddress === SEPOLIA_CONTRACT.address) {
259
+ chainId = SEPOLIA_CONTRACT.chainId;
260
+ }
261
+
262
+ const signer = options.signer || (await extractMetaMaskSigner());
263
+ const currentChainId = await signer.getChainId();
264
+
265
+ if (chainId && chainId !== currentChainId) {
266
+ throw Error(
267
+ `Failed to start RLN contract, chain ID of contract is different from current one: contract-${chainId}, current network-${currentChainId}`
268
+ );
269
+ }
270
+
271
+ return {
272
+ signer,
273
+ registryAddress,
274
+ credentials: options.credentials,
275
+ };
276
+ }
277
+
278
+ private async decryptCredentialsIfNeeded(
279
+ credentials?: EncryptedCredentials | DecryptedCredentials
280
+ ): Promise<undefined | DecryptedCredentials> {
281
+ if (!credentials) {
282
+ return;
283
+ }
284
+
285
+ if ("identity" in credentials) {
286
+ this._credentials = credentials;
287
+ return credentials;
288
+ }
289
+
290
+ const keystore = Keystore.fromString(credentials.keystore);
291
+
292
+ if (!keystore) {
293
+ throw Error("Failed to start RLN: cannot read Keystore provided.");
294
+ }
295
+
296
+ this.keystore = keystore;
297
+ this._credentials = await keystore.readCredential(
298
+ credentials.id,
299
+ credentials.password
300
+ );
301
+
302
+ return this._credentials;
303
+ }
304
+
305
+ public async registerMembership(
306
+ options: RegisterMembershipOptions
307
+ ): Promise<undefined | DecryptedCredentials> {
308
+ if (!this.contract) {
309
+ throw Error("RLN Contract is not initialized.");
310
+ }
311
+
312
+ let identity = "identity" in options && options.identity;
313
+
314
+ if ("signature" in options) {
315
+ identity = await this.generateSeededIdentityCredential(options.signature);
316
+ }
317
+
318
+ if (!identity) {
319
+ throw Error("Missing signature or identity to register membership.");
320
+ }
321
+
322
+ return this.contract.registerWithIdentity(identity);
323
+ }
324
+
325
+ /**
326
+ * Changes credentials in use by relying on provided Keystore earlier in rln.start
327
+ * @param id: string, hash of credentials to select from Keystore
328
+ * @param password: string or bytes to use to decrypt credentials from Keystore
329
+ */
330
+ public async useCredentials(id: string, password: Password): Promise<void> {
331
+ this._credentials = await this.keystore?.readCredential(id, password);
332
+ }
333
+
334
+ public createEncoder(options: WakuEncoderOptions): RLNEncoder {
335
+ if (!this._credentials) {
336
+ throw Error(
337
+ "Failed to create Encoder: missing RLN credentials. Use createRLNEncoder directly."
338
+ );
339
+ }
340
+
341
+ return createRLNEncoder({
342
+ encoder: createEncoder(options),
343
+ rlnInstance: this,
344
+ index: this._credentials.membership.treeIndex,
345
+ credential: this._credentials.identity,
346
+ });
347
+ }
348
+
349
+ public createDecoder(
350
+ contentTopic: ContentTopic
351
+ ): RLNDecoder<IDecodedMessage> {
352
+ return createRLNDecoder({
353
+ rlnInstance: this,
354
+ decoder: createDecoder(contentTopic),
355
+ });
356
+ }
357
+
177
358
  generateIdentityCredentials(): IdentityCredential {
178
359
  const memKeys = zerokitRLN.generateExtendedMembershipKey(this.zkRLN); // TODO: rename this function in zerokit rln-wasm
179
360
  return IdentityCredential.fromBytes(memKeys);
@@ -1,19 +1,30 @@
1
+ import { hexToBytes } from "@waku/utils/bytes";
1
2
  import { ethers } from "ethers";
2
3
 
3
- import { RLN_ABI } from "./constants.js";
4
- import { IdentityCredential, RLNInstance } from "./rln.js";
4
+ import { zeroPadLE } from "./byte_utils.js";
5
+ import { RLN_REGISTRY_ABI, RLN_STORAGE_ABI } from "./constants.js";
6
+ import type { DecryptedCredentials } from "./keystore/index.js";
7
+ import { type IdentityCredential, RLNInstance } from "./rln.js";
5
8
  import { MerkleRootTracker } from "./root_tracker.js";
6
9
 
7
10
  type Member = {
8
- pubkey: string;
9
- index: number;
11
+ idCommitment: string;
12
+ index: ethers.BigNumber;
10
13
  };
11
14
 
12
- type ContractOptions = {
13
- address: string;
14
- provider: ethers.Signer | ethers.providers.Provider;
15
+ type Signer = ethers.Signer;
16
+
17
+ type RLNContractOptions = {
18
+ signer: Signer;
19
+ registryAddress: string;
20
+ };
21
+
22
+ type RLNStorageOptions = {
23
+ storageIndex?: number;
15
24
  };
16
25
 
26
+ type RLNContractInitOptions = RLNContractOptions & RLNStorageOptions;
27
+
17
28
  type FetchMembersOptions = {
18
29
  fromBlock?: number;
19
30
  fetchRange?: number;
@@ -21,18 +32,23 @@ type FetchMembersOptions = {
21
32
  };
22
33
 
23
34
  export class RLNContract {
24
- private _contract: ethers.Contract;
25
- private membersFilter: ethers.EventFilter;
35
+ private registryContract: ethers.Contract;
26
36
  private merkleRootTracker: MerkleRootTracker;
27
37
 
28
- private _members: Member[] = [];
38
+ private deployBlock: undefined | number;
39
+ private storageIndex: undefined | number;
40
+ private storageContract: undefined | ethers.Contract;
41
+ private _membersFilter: undefined | ethers.EventFilter;
42
+
43
+ private _members: Map<number, Member> = new Map();
29
44
 
30
45
  public static async init(
31
46
  rlnInstance: RLNInstance,
32
- options: ContractOptions
47
+ options: RLNContractInitOptions
33
48
  ): Promise<RLNContract> {
34
49
  const rlnContract = new RLNContract(rlnInstance, options);
35
50
 
51
+ await rlnContract.initStorageContract(options.signer);
36
52
  await rlnContract.fetchMembers(rlnInstance);
37
53
  rlnContract.subscribeToMembers(rlnInstance);
38
54
 
@@ -41,21 +57,61 @@ export class RLNContract {
41
57
 
42
58
  constructor(
43
59
  rlnInstance: RLNInstance,
44
- { address, provider }: ContractOptions
60
+ { registryAddress, signer }: RLNContractOptions
45
61
  ) {
46
62
  const initialRoot = rlnInstance.getMerkleRoot();
47
63
 
48
- this._contract = new ethers.Contract(address, RLN_ABI, provider);
64
+ this.registryContract = new ethers.Contract(
65
+ registryAddress,
66
+ RLN_REGISTRY_ABI,
67
+ signer
68
+ );
49
69
  this.merkleRootTracker = new MerkleRootTracker(5, initialRoot);
50
- this.membersFilter = this.contract.filters.MemberRegistered();
70
+ }
71
+
72
+ private async initStorageContract(
73
+ signer: Signer,
74
+ options: RLNStorageOptions = {}
75
+ ): Promise<void> {
76
+ const storageIndex = options?.storageIndex
77
+ ? options.storageIndex
78
+ : await this.registryContract.usingStorageIndex();
79
+ const storageAddress = await this.registryContract.storages(storageIndex);
80
+
81
+ if (!storageAddress || storageAddress === ethers.constants.AddressZero) {
82
+ throw Error("No RLN Storage initialized on registry contract.");
83
+ }
84
+
85
+ this.storageIndex = storageIndex;
86
+ this.storageContract = new ethers.Contract(
87
+ storageAddress,
88
+ RLN_STORAGE_ABI,
89
+ signer
90
+ );
91
+ this._membersFilter = this.storageContract.filters.MemberRegistered();
92
+
93
+ this.deployBlock = await this.storageContract.deployedBlockNumber();
51
94
  }
52
95
 
53
96
  public get contract(): ethers.Contract {
54
- return this._contract;
97
+ if (!this.storageContract) {
98
+ throw Error("Storage contract was not initialized");
99
+ }
100
+ return this.storageContract as ethers.Contract;
55
101
  }
56
102
 
57
103
  public get members(): Member[] {
58
- return this._members;
104
+ const sortedMembers = Array.from(this._members.values()).sort(
105
+ (left, right) => left.index.toNumber() - right.index.toNumber()
106
+ );
107
+ return sortedMembers;
108
+ }
109
+
110
+ private get membersFilter(): ethers.EventFilter {
111
+ if (!this._membersFilter) {
112
+ throw Error("Members filter was not initialized.");
113
+ }
114
+ return this._membersFilter as ethers.EventFilter;
59
115
  }
60
116
 
61
117
  public async fetchMembers(
@@ -63,6 +119,7 @@ export class RLNContract {
63
119
  options: FetchMembersOptions = {}
64
120
  ): Promise<void> {
65
121
  const registeredMemberEvents = await queryFilter(this.contract, {
122
+ fromBlock: this.deployBlock,
66
123
  ...options,
67
124
  membersFilter: this.membersFilter,
68
125
  });
@@ -79,13 +136,13 @@ export class RLNContract {
79
136
  }
80
137
 
81
138
  if (evt.removed) {
82
- const index: number = evt.args.index;
139
+ const index: ethers.BigNumber = evt.args.index;
83
140
  const toRemoveVal = toRemoveTable.get(evt.blockNumber);
84
141
  if (toRemoveVal != undefined) {
85
- toRemoveVal.push(index);
142
+ toRemoveVal.push(index.toNumber());
86
143
  toRemoveTable.set(evt.blockNumber, toRemoveVal);
87
144
  } else {
88
- toRemoveTable.set(evt.blockNumber, [index]);
145
+ toRemoveTable.set(evt.blockNumber, [index.toNumber()]);
89
146
  }
90
147
  } else {
91
148
  let eventsPerBlock = toInsertTable.get(evt.blockNumber);
@@ -96,10 +153,10 @@ export class RLNContract {
96
153
  eventsPerBlock.push(evt);
97
154
  toInsertTable.set(evt.blockNumber, eventsPerBlock);
98
155
  }
99
-
100
- this.removeMembers(rlnInstance, toRemoveTable);
101
- this.insertMembers(rlnInstance, toInsertTable);
102
156
  });
157
+
158
+ this.removeMembers(rlnInstance, toRemoveTable);
159
+ this.insertMembers(rlnInstance, toInsertTable);
103
160
  }
104
161
 
105
162
  private insertMembers(
@@ -108,18 +165,19 @@ export class RLNContract {
108
165
  ): void {
109
166
  toInsert.forEach((events: ethers.Event[], blockNumber: number) => {
110
167
  events.forEach((evt) => {
111
- if (!evt.args) {
168
+ const _idCommitment = evt?.args?.idCommitment;
169
+ const index: ethers.BigNumber = evt?.args?.index;
170
+
171
+ if (!_idCommitment || !index) {
112
172
  return;
113
173
  }
114
174
 
115
- const pubkey = evt.args.pubkey;
116
- const index = evt.args.index;
117
- const idCommitment = ethers.utils.zeroPad(
118
- ethers.utils.arrayify(pubkey),
119
- 32
120
- );
175
+ const idCommitment = zeroPadLE(hexToBytes(_idCommitment?._hex), 32);
121
176
  rlnInstance.insertMember(idCommitment);
122
- this.members.push({ index, pubkey });
177
+ this._members.set(index.toNumber(), {
178
+ index,
179
+ idCommitment: _idCommitment?._hex,
180
+ });
123
181
  });
124
182
 
125
183
  const currentRoot = rlnInstance.getMerkleRoot();
@@ -134,9 +192,8 @@ export class RLNContract {
134
192
  const removeDescending = new Map([...toRemove].sort().reverse());
135
193
  removeDescending.forEach((indexes: number[], blockNumber: number) => {
136
194
  indexes.forEach((index) => {
137
- const idx = this.members.findIndex((m) => m.index === index);
138
- if (idx > -1) {
139
- this.members.splice(idx, 1);
195
+ if (this._members.has(index)) {
196
+ this._members.delete(index);
140
197
  }
141
198
  rlnInstance.deleteMember(index);
142
199
  });
@@ -147,32 +204,50 @@ export class RLNContract {
147
204
 
148
205
  public subscribeToMembers(rlnInstance: RLNInstance): void {
149
206
  this.contract.on(this.membersFilter, (_pubkey, _index, event) =>
150
- this.processEvents(rlnInstance, event)
207
+ this.processEvents(rlnInstance, [event])
151
208
  );
152
209
  }
153
210
 
154
- public async registerWithSignature(
155
- rlnInstance: RLNInstance,
156
- signature: string
157
- ): Promise<ethers.Event | undefined> {
158
- const identityCredential =
159
- await rlnInstance.generateSeededIdentityCredential(signature);
211
+ public async registerWithIdentity(
212
+ identity: IdentityCredential
213
+ ): Promise<DecryptedCredentials | undefined> {
214
+ if (this.storageIndex === undefined) {
215
+ throw Error(
216
+ "Cannot register credential, no storage contract index found."
217
+ );
218
+ }
219
+ const txRegisterResponse: ethers.ContractTransaction =
220
+ await this.registryContract["register(uint16,uint256)"](
221
+ this.storageIndex,
222
+ identity.IDCommitmentBigInt,
223
+ { gasLimit: 100000 }
224
+ );
225
+ const txRegisterReceipt = await txRegisterResponse.wait();
160
226
 
161
- return this.registerWithKey(identityCredential);
162
- }
227
+ // assumption: register(uint16,uint256) emits one event
228
+ const memberRegistered = txRegisterReceipt?.events?.[0];
163
229
 
164
- public async registerWithKey(
165
- credential: IdentityCredential
166
- ): Promise<ethers.Event | undefined> {
167
- const depositValue = await this.contract.MEMBERSHIP_DEPOSIT();
230
+ if (!memberRegistered) {
231
+ return undefined;
232
+ }
168
233
 
169
- const txRegisterResponse: ethers.ContractTransaction =
170
- await this.contract.register(credential.IDCommitmentBigInt, {
171
- value: depositValue,
172
- });
173
- const txRegisterReceipt = await txRegisterResponse.wait();
234
+ const decodedData = this.contract.interface.decodeEventLog(
235
+ "MemberRegistered",
236
+ memberRegistered.data
237
+ );
174
238
 
175
- return txRegisterReceipt?.events?.[0];
239
+ const network = await this.registryContract.provider.getNetwork();
240
+ const address = this.registryContract.address;
241
+ const membershipId = decodedData.index.toNumber();
242
+
243
+ return {
244
+ identity,
245
+ membership: {
246
+ address,
247
+ treeIndex: membershipId,
248
+ chainId: network.chainId,
249
+ },
250
+ };
176
251
  }
177
252
 
178
253
  public roots(): Uint8Array[] {