@waku/rln 0.1.1-60a5070 → 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.
package/src/rln.ts CHANGED
@@ -1,10 +1,28 @@
1
+ import { createDecoder, createEncoder } from "@waku/core";
1
2
  import type { IRateLimitProof } from "@waku/interfaces";
3
+ import type {
4
+ ContentTopic,
5
+ IDecodedMessage,
6
+ EncoderOptions as WakuEncoderOptions,
7
+ } from "@waku/interfaces";
2
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
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
 
@@ -158,12 +176,185 @@ export function sha256(input: Uint8Array): Uint8Array {
158
176
  return zerokitRLN.hash(lenPrefixedData);
159
177
  }
160
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
+
161
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
+
162
209
  constructor(
163
210
  private zkRLN: number,
164
211
  private witnessCalculator: WitnessCalculator
165
212
  ) {}
166
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
+
167
358
  generateIdentityCredentials(): IdentityCredential {
168
359
  const memKeys = zerokitRLN.generateExtendedMembershipKey(this.zkRLN); // TODO: rename this function in zerokit rln-wasm
169
360
  return IdentityCredential.fromBytes(memKeys);
@@ -1,7 +1,10 @@
1
+ import { hexToBytes } from "@waku/utils/bytes";
1
2
  import { ethers } from "ethers";
2
3
 
4
+ import { zeroPadLE } from "./byte_utils.js";
3
5
  import { RLN_REGISTRY_ABI, RLN_STORAGE_ABI } from "./constants.js";
4
- import { IdentityCredential, RLNInstance } from "./rln.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 = {
@@ -9,10 +12,10 @@ type Member = {
9
12
  index: ethers.BigNumber;
10
13
  };
11
14
 
12
- type Provider = ethers.Signer | ethers.providers.Provider;
15
+ type Signer = ethers.Signer;
13
16
 
14
17
  type RLNContractOptions = {
15
- provider: Provider;
18
+ signer: Signer;
16
19
  registryAddress: string;
17
20
  };
18
21
 
@@ -45,7 +48,7 @@ export class RLNContract {
45
48
  ): Promise<RLNContract> {
46
49
  const rlnContract = new RLNContract(rlnInstance, options);
47
50
 
48
- await rlnContract.initStorageContract(options.provider);
51
+ await rlnContract.initStorageContract(options.signer);
49
52
  await rlnContract.fetchMembers(rlnInstance);
50
53
  rlnContract.subscribeToMembers(rlnInstance);
51
54
 
@@ -54,20 +57,20 @@ export class RLNContract {
54
57
 
55
58
  constructor(
56
59
  rlnInstance: RLNInstance,
57
- { registryAddress, provider }: RLNContractOptions
60
+ { registryAddress, signer }: RLNContractOptions
58
61
  ) {
59
62
  const initialRoot = rlnInstance.getMerkleRoot();
60
63
 
61
64
  this.registryContract = new ethers.Contract(
62
65
  registryAddress,
63
66
  RLN_REGISTRY_ABI,
64
- provider
67
+ signer
65
68
  );
66
69
  this.merkleRootTracker = new MerkleRootTracker(5, initialRoot);
67
70
  }
68
71
 
69
72
  private async initStorageContract(
70
- provider: Provider,
73
+ signer: Signer,
71
74
  options: RLNStorageOptions = {}
72
75
  ): Promise<void> {
73
76
  const storageIndex = options?.storageIndex
@@ -83,7 +86,7 @@ export class RLNContract {
83
86
  this.storageContract = new ethers.Contract(
84
87
  storageAddress,
85
88
  RLN_STORAGE_ABI,
86
- provider
89
+ signer
87
90
  );
88
91
  this._membersFilter = this.storageContract.filters.MemberRegistered();
89
92
 
@@ -169,15 +172,11 @@ export class RLNContract {
169
172
  return;
170
173
  }
171
174
 
172
- const idCommitment = ethers.utils.zeroPad(
173
- ethers.utils.arrayify(_idCommitment),
174
- 32
175
- );
175
+ const idCommitment = zeroPadLE(hexToBytes(_idCommitment?._hex), 32);
176
176
  rlnInstance.insertMember(idCommitment);
177
177
  this._members.set(index.toNumber(), {
178
178
  index,
179
- idCommitment:
180
- _idCommitment?._hex || ethers.utils.hexlify(idCommitment),
179
+ idCommitment: _idCommitment?._hex,
181
180
  });
182
181
  });
183
182
 
@@ -209,19 +208,9 @@ export class RLNContract {
209
208
  );
210
209
  }
211
210
 
212
- public async registerWithSignature(
213
- rlnInstance: RLNInstance,
214
- signature: string
215
- ): Promise<Member | undefined> {
216
- const identityCredential =
217
- await rlnInstance.generateSeededIdentityCredential(signature);
218
-
219
- return this.registerWithKey(identityCredential);
220
- }
221
-
222
- public async registerWithKey(
223
- credential: IdentityCredential
224
- ): Promise<Member | undefined> {
211
+ public async registerWithIdentity(
212
+ identity: IdentityCredential
213
+ ): Promise<DecryptedCredentials | undefined> {
225
214
  if (this.storageIndex === undefined) {
226
215
  throw Error(
227
216
  "Cannot register credential, no storage contract index found."
@@ -230,7 +219,7 @@ export class RLNContract {
230
219
  const txRegisterResponse: ethers.ContractTransaction =
231
220
  await this.registryContract["register(uint16,uint256)"](
232
221
  this.storageIndex,
233
- credential.IDCommitmentBigInt,
222
+ identity.IDCommitmentBigInt,
234
223
  { gasLimit: 100000 }
235
224
  );
236
225
  const txRegisterReceipt = await txRegisterResponse.wait();
@@ -247,9 +236,17 @@ export class RLNContract {
247
236
  memberRegistered.data
248
237
  );
249
238
 
239
+ const network = await this.registryContract.provider.getNetwork();
240
+ const address = this.registryContract.address;
241
+ const membershipId = decodedData.index.toNumber();
242
+
250
243
  return {
251
- idCommitment: decodedData.idCommitment,
252
- index: decodedData.index,
244
+ identity,
245
+ membership: {
246
+ address,
247
+ treeIndex: membershipId,
248
+ chainId: network.chainId,
249
+ },
253
250
  };
254
251
  }
255
252