@waku/rln 0.0.2-3670e82.0 → 0.0.2-6cb9c9c.0

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 (37) hide show
  1. package/bundle/_virtual/utils.js +2 -2
  2. package/bundle/_virtual/utils2.js +2 -2
  3. package/bundle/index.js +2 -0
  4. package/bundle/packages/rln/dist/contract/rln_contract.js +58 -59
  5. package/bundle/packages/rln/dist/identity_generator.js +75 -0
  6. package/bundle/packages/rln/dist/rln.js +2 -4
  7. package/bundle/packages/rln/dist/rln_light.js +223 -0
  8. package/bundle/packages/rln/node_modules/@chainsafe/bls-keystore/node_modules/ethereum-cryptography/random.js +1 -1
  9. package/bundle/packages/rln/node_modules/@chainsafe/bls-keystore/node_modules/ethereum-cryptography/utils.js +2 -2
  10. package/bundle/packages/rln/node_modules/@noble/hashes/_sha2.js +1 -1
  11. package/bundle/packages/rln/node_modules/@noble/hashes/hmac.js +1 -1
  12. package/bundle/packages/rln/node_modules/@noble/hashes/pbkdf2.js +1 -1
  13. package/bundle/packages/rln/node_modules/@noble/hashes/scrypt.js +1 -1
  14. package/bundle/packages/rln/node_modules/@noble/hashes/sha256.js +1 -1
  15. package/bundle/packages/rln/node_modules/@noble/hashes/sha512.js +1 -1
  16. package/bundle/packages/rln/node_modules/@noble/hashes/utils.js +1 -1
  17. package/dist/.tsbuildinfo +1 -1
  18. package/dist/contract/rln_contract.d.ts +4 -4
  19. package/dist/contract/rln_contract.js +58 -59
  20. package/dist/contract/rln_contract.js.map +1 -1
  21. package/dist/identity_generator.d.ts +19 -0
  22. package/dist/identity_generator.js +72 -0
  23. package/dist/identity_generator.js.map +1 -0
  24. package/dist/index.d.ts +4 -1
  25. package/dist/index.js +3 -1
  26. package/dist/index.js.map +1 -1
  27. package/dist/rln.js +2 -4
  28. package/dist/rln.js.map +1 -1
  29. package/dist/rln_light.d.ts +95 -0
  30. package/dist/rln_light.js +206 -0
  31. package/dist/rln_light.js.map +1 -0
  32. package/package.json +1 -1
  33. package/src/contract/rln_contract.ts +73 -107
  34. package/src/identity_generator.ts +108 -0
  35. package/src/index.ts +15 -0
  36. package/src/rln.ts +2 -5
  37. package/src/rln_light.ts +298 -0
@@ -0,0 +1,206 @@
1
+ import { Logger } from "@waku/utils";
2
+ import { RLNContract } from "./contract/index.js";
3
+ import { IdentityGenerator } from "./identity_generator.js";
4
+ import { Keystore } from "./keystore/index.js";
5
+ import { extractMetaMaskSigner } from "./utils/index.js";
6
+ const log = new Logger("waku:rln-light");
7
+ /**
8
+ * Lightweight RLN implementation without Zerokit
9
+ * Only supports Keystore and membership registration
10
+ */
11
+ export class RLNLight {
12
+ started = false;
13
+ starting = false;
14
+ _contract;
15
+ _signer;
16
+ keystore = Keystore.create();
17
+ _credentials;
18
+ identityGenerator = new IdentityGenerator();
19
+ get contract() {
20
+ return this._contract;
21
+ }
22
+ get signer() {
23
+ return this._signer;
24
+ }
25
+ /**
26
+ * Get the current credentials
27
+ */
28
+ get credentials() {
29
+ return this._credentials;
30
+ }
31
+ /**
32
+ * Start the RLNLight instance
33
+ * @param options Configuration options
34
+ */
35
+ async start(options = {}) {
36
+ if (this.started || this.starting) {
37
+ log.warn("RLNLight already started or starting");
38
+ return;
39
+ }
40
+ this.starting = true;
41
+ try {
42
+ // Determine correct options based on credentials, if any
43
+ const keystoreCredentials = options.address
44
+ ? await this.findCredentialsByAddress(options.address)
45
+ : undefined;
46
+ const { address, signer, rateLimit } = await this.determineStartOptions(options, keystoreCredentials);
47
+ // Set the signer
48
+ if (signer) {
49
+ this._signer = signer;
50
+ }
51
+ else {
52
+ this._signer = await extractMetaMaskSigner();
53
+ }
54
+ const contractOptions = {
55
+ signer: this._signer,
56
+ address: address,
57
+ rateLimit: rateLimit
58
+ };
59
+ // We need to create a minimal compatible object with RLNInstance interface
60
+ // but we only need it for contract initialization
61
+ const compatibleRLN = {
62
+ zerokit: {
63
+ getMerkleRoot: () => new Uint8Array(32),
64
+ insertMember: (_idCommitment) => { },
65
+ insertMembers: (_index, ..._idCommitments) => { },
66
+ deleteMember: (_index) => { }
67
+ }
68
+ };
69
+ this._contract = await RLNContract.init(
70
+ // Type assertion needed for compatibility with RLNInstance interface
71
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
72
+ compatibleRLN, contractOptions);
73
+ this.started = true;
74
+ }
75
+ catch (error) {
76
+ log.error("Failed to start RLNLight:", error);
77
+ throw error;
78
+ }
79
+ finally {
80
+ this.starting = false;
81
+ }
82
+ }
83
+ /**
84
+ * Find credentials in the keystore by contract address
85
+ */
86
+ async findCredentialsByAddress(address) {
87
+ // Since Keystore doesn't have a direct method to find by address,
88
+ // we need to iterate through all credentials
89
+ const hashes = this.keystore.keys();
90
+ for (const hash of hashes) {
91
+ try {
92
+ // Try with an empty password - this won't work but lets us check schema
93
+ const credential = await this.keystore.readCredential(hash, new Uint8Array(0));
94
+ if (credential?.membership.address === address) {
95
+ return credential;
96
+ }
97
+ }
98
+ catch (e) {
99
+ // Ignore errors, just means we couldn't read this credential
100
+ }
101
+ }
102
+ return undefined;
103
+ }
104
+ /**
105
+ * Determine the correct options for starting RLNLight
106
+ */
107
+ async determineStartOptions(options, credentials) {
108
+ if (options.credentials) {
109
+ const { credentials: decrypted } = await RLNLight.decryptCredentialsIfNeeded(options.credentials);
110
+ if (decrypted?.membership) {
111
+ this._credentials = decrypted;
112
+ return {
113
+ ...options,
114
+ address: decrypted.membership.address
115
+ };
116
+ }
117
+ }
118
+ else if (credentials) {
119
+ return {
120
+ ...options,
121
+ address: credentials.membership.address
122
+ };
123
+ }
124
+ return options;
125
+ }
126
+ /**
127
+ * Decrypt credentials if they are encrypted
128
+ */
129
+ static async decryptCredentialsIfNeeded(credentials) {
130
+ if (!credentials) {
131
+ return {};
132
+ }
133
+ if ("identity" in credentials) {
134
+ return { credentials };
135
+ }
136
+ else {
137
+ const keystore = Keystore.create();
138
+ try {
139
+ const decrypted = await keystore.readCredential(credentials.id, credentials.password);
140
+ if (!decrypted) {
141
+ return {};
142
+ }
143
+ return {
144
+ credentials: {
145
+ identity: decrypted.identity,
146
+ membership: decrypted.membership
147
+ },
148
+ keystore
149
+ };
150
+ }
151
+ catch (e) {
152
+ log.error("Failed to decrypt credentials", e);
153
+ return {};
154
+ }
155
+ }
156
+ }
157
+ /**
158
+ * Register a membership using an identity or signature
159
+ * @param options Options including identity or signature
160
+ * @returns Decrypted credentials if successful
161
+ */
162
+ async registerMembership(options) {
163
+ if (!this.contract) {
164
+ throw Error("RLN Contract is not initialized.");
165
+ }
166
+ let identity = "identity" in options && options.identity;
167
+ if ("signature" in options) {
168
+ identity = this.identityGenerator.generateSeededIdentityCredential(options.signature);
169
+ }
170
+ if (!identity) {
171
+ throw Error("Missing signature or identity to register membership.");
172
+ }
173
+ return this.contract.registerWithIdentity(identity);
174
+ }
175
+ /**
176
+ * Changes credentials in use by relying on provided Keystore
177
+ * @param id Hash of credentials to select from Keystore
178
+ * @param password Password to decrypt credentials from Keystore
179
+ */
180
+ async useCredentials(id, password) {
181
+ const decrypted = await this.keystore.readCredential(id, password);
182
+ if (!decrypted) {
183
+ throw new Error("Credentials not found or incorrect password");
184
+ }
185
+ this._credentials = {
186
+ identity: decrypted.identity,
187
+ membership: decrypted.membership
188
+ };
189
+ }
190
+ /**
191
+ * Generate a new identity credential
192
+ * @returns New identity credential
193
+ */
194
+ generateIdentityCredential() {
195
+ return this.identityGenerator.generateIdentityCredentials();
196
+ }
197
+ /**
198
+ * Generate a seeded identity credential
199
+ * @param seed Seed string to generate deterministic identity
200
+ * @returns Seeded identity credential
201
+ */
202
+ generateSeededIdentityCredential(seed) {
203
+ return this.identityGenerator.generateSeededIdentityCredential(seed);
204
+ }
205
+ }
206
+ //# sourceMappingURL=rln_light.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rln_light.js","sourceRoot":"","sources":["../src/rln_light.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1 +1 @@
1
- {"name":"@waku/rln","version":"0.0.2-3670e82.0","description":"RLN (Rate Limiting Nullifier) implementation for Waku","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"type":"module","homepage":"https://github.com/waku-org/js-waku/tree/master/packages/rln#readme","repository":{"type":"git","url":"https://github.com/waku-org/js-waku.git"},"bugs":{"url":"https://github.com/waku-org/js-waku/issues"},"license":"MIT OR Apache-2.0","keywords":["waku","rln","rate-limiting","privacy","web3"],"scripts":{"build":"run-s build:**","build:copy":"mkdir -p dist/resources && cp -r src/resources/* dist/resources/","build:esm":"tsc","build:bundle":"rollup --config rollup.config.js","fix":"run-s fix:*","fix:lint":"eslint src *.js --fix","check":"run-s check:*","check:tsc":"tsc -p tsconfig.dev.json","check:lint":"eslint \"src/!(resources)/**/*.{ts,js}\" *.js","check:spelling":"cspell \"{README.md,src/**/*.ts}\"","test":"NODE_ENV=test run-s test:*","test:browser":"karma start karma.conf.cjs","watch:build":"tsc -p tsconfig.json -w","watch:test":"mocha --watch","prepublish":"npm run build","reset-hard":"git clean -dfx -e .idea && git reset --hard && npm i && npm run build"},"engines":{"node":">=20"},"devDependencies":{"@rollup/plugin-commonjs":"^25.0.7","@rollup/plugin-json":"^6.0.0","@rollup/plugin-node-resolve":"^15.2.3","@types/chai":"^5.0.1","@types/chai-spies":"^1.0.6","@types/deep-equal-in-any-order":"^1.0.4","@types/lodash":"^4.17.15","@types/sinon":"^17.0.3","@waku/build-utils":"^1.0.0","@waku/message-encryption":"0.0.32-3670e82.0","chai":"^5.1.2","chai-as-promised":"^8.0.1","chai-spies":"^1.1.0","chai-subset":"^1.6.0","deep-equal-in-any-order":"^2.0.6","fast-check":"^3.23.2","rollup-plugin-copy":"^3.5.0","sinon":"^19.0.2"},"files":["dist","bundle","src/**/*.ts","!**/*.spec.*","!**/*.json","CHANGELOG.md","LICENSE","README.md"],"dependencies":{"@chainsafe/bls-keystore":"3.0.0","@waku/core":"0.0.34-3670e82.0","@waku/utils":"0.0.22-3670e82.0","@waku/zerokit-rln-wasm":"^0.0.13","ethereum-cryptography":"^3.1.0","ethers":"^5.7.2","lodash":"^4.17.21","uuid":"^11.0.5"}}
1
+ {"name":"@waku/rln","version":"0.0.2-6cb9c9c.0","description":"RLN (Rate Limiting Nullifier) implementation for Waku","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"type":"module","homepage":"https://github.com/waku-org/js-waku/tree/master/packages/rln#readme","repository":{"type":"git","url":"https://github.com/waku-org/js-waku.git"},"bugs":{"url":"https://github.com/waku-org/js-waku/issues"},"license":"MIT OR Apache-2.0","keywords":["waku","rln","rate-limiting","privacy","web3"],"scripts":{"build":"run-s build:**","build:copy":"mkdir -p dist/resources && cp -r src/resources/* dist/resources/","build:esm":"tsc","build:bundle":"rollup --config rollup.config.js","fix":"run-s fix:*","fix:lint":"eslint src *.js --fix","check":"run-s check:*","check:tsc":"tsc -p tsconfig.dev.json","check:lint":"eslint \"src/!(resources)/**/*.{ts,js}\" *.js","check:spelling":"cspell \"{README.md,src/**/*.ts}\"","test":"NODE_ENV=test run-s test:*","test:browser":"karma start karma.conf.cjs","watch:build":"tsc -p tsconfig.json -w","watch:test":"mocha --watch","prepublish":"npm run build","reset-hard":"git clean -dfx -e .idea && git reset --hard && npm i && npm run build"},"engines":{"node":">=20"},"devDependencies":{"@rollup/plugin-commonjs":"^25.0.7","@rollup/plugin-json":"^6.0.0","@rollup/plugin-node-resolve":"^15.2.3","@types/chai":"^5.0.1","@types/chai-spies":"^1.0.6","@types/deep-equal-in-any-order":"^1.0.4","@types/lodash":"^4.17.15","@types/sinon":"^17.0.3","@waku/build-utils":"^1.0.0","@waku/message-encryption":"0.0.32-6cb9c9c.0","chai":"^5.1.2","chai-as-promised":"^8.0.1","chai-spies":"^1.1.0","chai-subset":"^1.6.0","deep-equal-in-any-order":"^2.0.6","fast-check":"^3.23.2","rollup-plugin-copy":"^3.5.0","sinon":"^19.0.2"},"files":["dist","bundle","src/**/*.ts","!**/*.spec.*","!**/*.json","CHANGELOG.md","LICENSE","README.md"],"dependencies":{"@chainsafe/bls-keystore":"3.0.0","@ethersproject/keccak256":"^5.7.0","@ethersproject/strings":"^5.7.0","@waku/core":"0.0.34-6cb9c9c.0","@waku/utils":"0.0.22-6cb9c9c.0","@waku/zerokit-rln-wasm":"^0.0.13","ethereum-cryptography":"^3.1.0","ethers":"^5.7.2","lodash":"^4.17.21","uuid":"^11.0.5"}}
@@ -1,4 +1,3 @@
1
- /* eslint-disable no-console */
2
1
  import { Logger } from "@waku/utils";
3
2
  import { hexToBytes } from "@waku/utils/bytes";
4
3
  import { ethers } from "ethers";
@@ -66,7 +65,7 @@ export class RLNContract {
66
65
 
67
66
  private _members: Map<number, Member> = new Map();
68
67
  private _membersFilter: ethers.EventFilter;
69
- private _membersRemovedFilter: ethers.EventFilter;
68
+ private _membershipErasedFilter: ethers.EventFilter;
70
69
  private _membersExpiredFilter: ethers.EventFilter;
71
70
 
72
71
  /**
@@ -115,7 +114,7 @@ export class RLNContract {
115
114
 
116
115
  // Initialize event filters
117
116
  this._membersFilter = this.contract.filters.MembershipRegistered();
118
- this._membersRemovedFilter = this.contract.filters.MembershipErased();
117
+ this._membershipErasedFilter = this.contract.filters.MembershipErased();
119
118
  this._membersExpiredFilter = this.contract.filters.MembershipExpired();
120
119
  }
121
120
 
@@ -185,7 +184,7 @@ export class RLNContract {
185
184
  this.contract.maxTotalRateLimit(),
186
185
  this.contract.currentTotalRateLimit()
187
186
  ]);
188
- return maxTotal.sub(currentTotal).toNumber();
187
+ return Number(maxTotal) - Number(currentTotal);
189
188
  }
190
189
 
191
190
  /**
@@ -210,11 +209,11 @@ export class RLNContract {
210
209
  return this._membersFilter;
211
210
  }
212
211
 
213
- private get membersRemovedFilter(): ethers.EventFilter {
214
- if (!this._membersRemovedFilter) {
215
- throw Error("MembersErased filter was not initialized.");
212
+ private get membershipErasedFilter(): ethers.EventFilter {
213
+ if (!this._membershipErasedFilter) {
214
+ throw Error("MembershipErased filter was not initialized.");
216
215
  }
217
- return this._membersRemovedFilter;
216
+ return this._membershipErasedFilter;
218
217
  }
219
218
 
220
219
  private get membersExpiredFilter(): ethers.EventFilter {
@@ -236,7 +235,7 @@ export class RLNContract {
236
235
  const removedMemberEvents = await queryFilter(this.contract, {
237
236
  fromBlock: this.deployBlock,
238
237
  ...options,
239
- membersFilter: this.membersRemovedFilter
238
+ membersFilter: this.membershipErasedFilter
240
239
  });
241
240
  const expiredMemberEvents = await queryFilter(this.contract, {
242
241
  fromBlock: this.deployBlock,
@@ -265,14 +264,12 @@ export class RLNContract {
265
264
  evt.event === "MembershipErased" ||
266
265
  evt.event === "MembershipExpired"
267
266
  ) {
268
- // Both MembershipErased and MembershipExpired events should remove members
269
267
  let index = evt.args.index;
270
268
 
271
269
  if (!index) {
272
270
  return;
273
271
  }
274
272
 
275
- // Convert index to ethers.BigNumber if it's not already
276
273
  if (typeof index === "number" || typeof index === "string") {
277
274
  index = ethers.BigNumber.from(index);
278
275
  }
@@ -310,12 +307,10 @@ export class RLNContract {
310
307
  const _idCommitment = evt.args.idCommitment as string;
311
308
  let index = evt.args.index;
312
309
 
313
- // Ensure index is an ethers.BigNumber
314
310
  if (!_idCommitment || !index) {
315
311
  return;
316
312
  }
317
313
 
318
- // Convert index to ethers.BigNumber if it's not already
319
314
  if (typeof index === "number" || typeof index === "string") {
320
315
  index = ethers.BigNumber.from(index);
321
316
  }
@@ -323,10 +318,9 @@ export class RLNContract {
323
318
  const idCommitment = zeroPadLE(hexToBytes(_idCommitment), 32);
324
319
  rlnInstance.zerokit.insertMember(idCommitment);
325
320
 
326
- // Always store the numeric index as the key, but the BigNumber as the value
327
321
  const numericIndex = index.toNumber();
328
322
  this._members.set(numericIndex, {
329
- index, // This is always a BigNumber
323
+ index,
330
324
  idCommitment: _idCommitment
331
325
  });
332
326
  });
@@ -367,7 +361,7 @@ export class RLNContract {
367
361
  );
368
362
 
369
363
  this.contract.on(
370
- this.membersRemovedFilter,
364
+ this.membershipErasedFilter,
371
365
  (
372
366
  _idCommitment: string,
373
367
  _membershipRateLimit: ethers.BigNumber,
@@ -395,111 +389,76 @@ export class RLNContract {
395
389
  identity: IdentityCredential
396
390
  ): Promise<DecryptedCredentials | undefined> {
397
391
  try {
398
- console.log("registerWithIdentity - starting registration process");
399
- console.log("registerWithIdentity - identity:", identity);
400
- console.log(
401
- "registerWithIdentity - IDCommitmentBigInt:",
392
+ log.info(
393
+ `Registering identity with rate limit: ${this.rateLimit} messages/epoch`
394
+ );
395
+
396
+ // Check if the ID commitment is already registered
397
+ const existingIndex = await this.getMemberIndex(
402
398
  identity.IDCommitmentBigInt.toString()
403
399
  );
404
- console.log("registerWithIdentity - rate limit:", this.rateLimit);
400
+ if (existingIndex) {
401
+ throw new Error(
402
+ `ID commitment is already registered with index ${existingIndex}`
403
+ );
404
+ }
405
405
 
406
- log.info(
407
- `Registering identity with rate limit: ${this.rateLimit} messages/epoch`
406
+ // Check if there's enough remaining rate limit
407
+ const remainingRateLimit = await this.getRemainingTotalRateLimit();
408
+ if (remainingRateLimit < this.rateLimit) {
409
+ throw new Error(
410
+ `Not enough remaining rate limit. Requested: ${this.rateLimit}, Available: ${remainingRateLimit}`
411
+ );
412
+ }
413
+
414
+ const estimatedGas = await this.contract.estimateGas.register(
415
+ identity.IDCommitmentBigInt,
416
+ this.rateLimit,
417
+ []
408
418
  );
419
+ const gasLimit = estimatedGas.add(10000);
409
420
 
410
- console.log("registerWithIdentity - calling contract.register");
411
421
  const txRegisterResponse: ethers.ContractTransaction =
412
422
  await this.contract.register(
413
423
  identity.IDCommitmentBigInt,
414
424
  this.rateLimit,
415
425
  [],
416
- { gasLimit: 300000 }
426
+ { gasLimit }
417
427
  );
418
- console.log(
419
- "registerWithIdentity - txRegisterResponse:",
420
- txRegisterResponse
421
- );
422
- console.log("registerWithIdentity - hash:", txRegisterResponse.hash);
423
- console.log(
424
- "registerWithIdentity - waiting for transaction confirmation..."
425
- );
426
428
 
427
429
  const txRegisterReceipt = await txRegisterResponse.wait();
428
- console.log(
429
- "registerWithIdentity - txRegisterReceipt:",
430
- txRegisterReceipt
431
- );
432
- console.log(
433
- "registerWithIdentity - transaction status:",
434
- txRegisterReceipt.status
435
- );
436
- console.log(
437
- "registerWithIdentity - block number:",
438
- txRegisterReceipt.blockNumber
439
- );
440
- console.log(
441
- "registerWithIdentity - gas used:",
442
- txRegisterReceipt.gasUsed.toString()
443
- );
430
+
431
+ if (txRegisterReceipt.status === 0) {
432
+ throw new Error("Transaction failed on-chain");
433
+ }
444
434
 
445
435
  const memberRegistered = txRegisterReceipt.events?.find(
446
436
  (event) => event.event === "MembershipRegistered"
447
437
  );
448
- console.log(
449
- "registerWithIdentity - memberRegistered event:",
450
- memberRegistered
451
- );
452
438
 
453
439
  if (!memberRegistered || !memberRegistered.args) {
454
- console.log(
455
- "registerWithIdentity - ERROR: no memberRegistered event found"
456
- );
457
- console.log(
458
- "registerWithIdentity - all events:",
459
- txRegisterReceipt.events
460
- );
461
440
  log.error(
462
441
  "Failed to register membership: No MembershipRegistered event found"
463
442
  );
464
443
  return undefined;
465
444
  }
466
445
 
467
- console.log(
468
- "registerWithIdentity - memberRegistered args:",
469
- memberRegistered.args
470
- );
471
446
  const decodedData: MembershipRegisteredEvent = {
472
447
  idCommitment: memberRegistered.args.idCommitment,
473
448
  membershipRateLimit: memberRegistered.args.membershipRateLimit,
474
449
  index: memberRegistered.args.index
475
450
  };
476
- console.log("registerWithIdentity - decodedData:", decodedData);
477
- console.log(
478
- "registerWithIdentity - index:",
479
- decodedData.index.toString()
480
- );
481
- console.log(
482
- "registerWithIdentity - membershipRateLimit:",
483
- decodedData.membershipRateLimit.toString()
484
- );
485
451
 
486
452
  log.info(
487
453
  `Successfully registered membership with index ${decodedData.index} ` +
488
454
  `and rate limit ${decodedData.membershipRateLimit}`
489
455
  );
490
456
 
491
- console.log("registerWithIdentity - getting network information");
492
457
  const network = await this.contract.provider.getNetwork();
493
- console.log("registerWithIdentity - network:", network);
494
- console.log("registerWithIdentity - chainId:", network.chainId);
495
-
496
458
  const address = this.contract.address;
497
- console.log("registerWithIdentity - contract address:", address);
498
-
499
- const membershipId = decodedData.index.toNumber();
500
- console.log("registerWithIdentity - membershipId:", membershipId);
459
+ const membershipId = Number(decodedData.index);
501
460
 
502
- const result = {
461
+ return {
503
462
  identity,
504
463
  membership: {
505
464
  address,
@@ -507,21 +466,33 @@ export class RLNContract {
507
466
  chainId: network.chainId
508
467
  }
509
468
  };
510
- console.log("registerWithIdentity - returning result:", result);
511
-
512
- return result;
513
469
  } catch (error) {
514
- console.log("registerWithIdentity - ERROR:", error);
515
- console.log(
516
- "registerWithIdentity - error message:",
517
- (error as Error).message
518
- );
519
- console.log(
520
- "registerWithIdentity - error stack:",
521
- (error as Error).stack
522
- );
523
- log.error(`Error in registerWithIdentity: ${(error as Error).message}`);
524
- return undefined;
470
+ if (error instanceof Error) {
471
+ const errorMessage = error.message;
472
+ log.error("registerWithIdentity - error message:", errorMessage);
473
+ log.error("registerWithIdentity - error stack:", error.stack);
474
+
475
+ // Try to extract more specific error information
476
+ if (errorMessage.includes("CannotExceedMaxTotalRateLimit")) {
477
+ throw new Error(
478
+ "Registration failed: Cannot exceed maximum total rate limit"
479
+ );
480
+ } else if (errorMessage.includes("InvalidIdCommitment")) {
481
+ throw new Error("Registration failed: Invalid ID commitment");
482
+ } else if (errorMessage.includes("InvalidMembershipRateLimit")) {
483
+ throw new Error("Registration failed: Invalid membership rate limit");
484
+ } else if (errorMessage.includes("execution reverted")) {
485
+ throw new Error(
486
+ "Contract execution reverted. Check contract requirements."
487
+ );
488
+ } else {
489
+ throw new Error(`Error in registerWithIdentity: ${errorMessage}`);
490
+ }
491
+ } else {
492
+ throw new Error("Unknown error in registerWithIdentity", {
493
+ cause: error
494
+ });
495
+ }
525
496
  }
526
497
  }
527
498
 
@@ -610,7 +581,7 @@ export class RLNContract {
610
581
 
611
582
  const network = await this.contract.provider.getNetwork();
612
583
  const address = this.contract.address;
613
- const membershipId = decodedData.index.toNumber();
584
+ const membershipId = Number(decodedData.index);
614
585
 
615
586
  return {
616
587
  identity,
@@ -676,20 +647,18 @@ export class RLNContract {
676
647
 
677
648
  public async extendMembership(
678
649
  idCommitment: string
679
- ): Promise<ethers.ContractReceipt> {
680
- const tx = await this.contract.extendMemberships([idCommitment]);
681
- return await tx.wait();
650
+ ): Promise<ethers.ContractTransaction> {
651
+ return this.contract.extendMemberships([idCommitment]);
682
652
  }
683
653
 
684
654
  public async eraseMembership(
685
655
  idCommitment: string,
686
656
  eraseFromMembershipSet: boolean = true
687
- ): Promise<ethers.ContractReceipt> {
688
- const tx = await this.contract.eraseMemberships(
657
+ ): Promise<ethers.ContractTransaction> {
658
+ return this.contract.eraseMemberships(
689
659
  [idCommitment],
690
660
  eraseFromMembershipSet
691
661
  );
692
- return await tx.wait();
693
662
  }
694
663
 
695
664
  public async registerMembership(
@@ -704,10 +673,7 @@ export class RLNContract {
704
673
  `Rate limit must be between ${RATE_LIMIT_PARAMS.MIN_RATE} and ${RATE_LIMIT_PARAMS.MAX_RATE}`
705
674
  );
706
675
  }
707
- console.log("registering membership", idCommitment, rateLimit);
708
- const txn = this.contract.register(idCommitment, rateLimit, []);
709
- console.log("txn", txn);
710
- return txn;
676
+ return this.contract.register(idCommitment, rateLimit, []);
711
677
  }
712
678
 
713
679
  private async getMemberIndex(
@@ -0,0 +1,108 @@
1
+ import { keccak256 } from "@ethersproject/keccak256";
2
+ import { toUtf8Bytes } from "@ethersproject/strings";
3
+
4
+ import { IdentityCredential } from "./identity.js";
5
+ import { buildBigIntFromUint8Array } from "./utils/index.js";
6
+
7
+ /**
8
+ * A standalone implementation of identity generation for RLN memberships
9
+ * without Zerokit dependency. Only supports identity creation and membership
10
+ * registration.
11
+ */
12
+ export class IdentityGenerator {
13
+ /**
14
+ * Generate a new random identity credential
15
+ * @returns An IdentityCredential object
16
+ */
17
+ public generateIdentityCredentials(): IdentityCredential {
18
+ // Generate random values for IDTrapdoor and IDNullifier
19
+ const idTrapdoor = crypto.getRandomValues(new Uint8Array(32));
20
+ const idNullifier = crypto.getRandomValues(new Uint8Array(32));
21
+
22
+ // Generate IDSecretHash by concatenating and hashing
23
+ const combined = new Uint8Array(64);
24
+ combined.set(idTrapdoor, 0);
25
+ combined.set(idNullifier, 32);
26
+ const idSecretHash = new Uint8Array(
27
+ keccak256(combined)
28
+ .substring(2)
29
+ .match(/.{1,2}/g)!
30
+ .map((byte) => parseInt(byte, 16))
31
+ );
32
+
33
+ // Generate IDCommitment by hashing IDSecretHash
34
+ const idCommitment = new Uint8Array(
35
+ keccak256(idSecretHash)
36
+ .substring(2)
37
+ .match(/.{1,2}/g)!
38
+ .map((byte) => parseInt(byte, 16))
39
+ );
40
+
41
+ // Generate BigInt from IDCommitment
42
+ const idCommitmentBigInt = buildBigIntFromUint8Array(idCommitment, 32);
43
+
44
+ return new IdentityCredential(
45
+ idTrapdoor,
46
+ idNullifier,
47
+ idSecretHash,
48
+ idCommitment,
49
+ idCommitmentBigInt
50
+ );
51
+ }
52
+
53
+ /**
54
+ * Generate an identity credential from a seed
55
+ * @param seed A string seed to generate a deterministic identity
56
+ * @returns An IdentityCredential object
57
+ */
58
+ public generateSeededIdentityCredential(seed: string): IdentityCredential {
59
+ // Convert seed to bytes
60
+ const seedBytes = toUtf8Bytes(seed);
61
+
62
+ // Use the seed to derive IDTrapdoor and IDNullifier deterministically
63
+ const seedHash = keccak256(seedBytes);
64
+ const seedHashBytes = new Uint8Array(
65
+ seedHash
66
+ .substring(2)
67
+ .match(/.{1,2}/g)!
68
+ .map((byte) => parseInt(byte, 16))
69
+ );
70
+
71
+ // Use first half for IDTrapdoor, second half for IDNullifier
72
+ const idTrapdoor = seedHashBytes.slice(0, 32);
73
+ const idNullifier = keccak256(seedHashBytes).substring(2);
74
+ const idNullifierBytes = new Uint8Array(
75
+ idNullifier.match(/.{1,2}/g)!.map((byte) => parseInt(byte, 16))
76
+ ).slice(0, 32);
77
+
78
+ // Generate IDSecretHash by concatenating and hashing
79
+ const combined = new Uint8Array(64);
80
+ combined.set(idTrapdoor, 0);
81
+ combined.set(idNullifierBytes, 32);
82
+ const idSecretHash = new Uint8Array(
83
+ keccak256(combined)
84
+ .substring(2)
85
+ .match(/.{1,2}/g)!
86
+ .map((byte) => parseInt(byte, 16))
87
+ );
88
+
89
+ // Generate IDCommitment by hashing IDSecretHash
90
+ const idCommitment = new Uint8Array(
91
+ keccak256(idSecretHash)
92
+ .substring(2)
93
+ .match(/.{1,2}/g)!
94
+ .map((byte) => parseInt(byte, 16))
95
+ );
96
+
97
+ // Generate BigInt from IDCommitment
98
+ const idCommitmentBigInt = buildBigIntFromUint8Array(idCommitment, 32);
99
+
100
+ return new IdentityCredential(
101
+ idTrapdoor,
102
+ idNullifierBytes,
103
+ idSecretHash,
104
+ idCommitment,
105
+ idCommitmentBigInt
106
+ );
107
+ }
108
+ }