@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.
- package/bundle/_virtual/utils.js +2 -2
- package/bundle/_virtual/utils2.js +2 -2
- package/bundle/index.js +2 -0
- package/bundle/packages/rln/dist/contract/rln_contract.js +58 -59
- package/bundle/packages/rln/dist/identity_generator.js +75 -0
- package/bundle/packages/rln/dist/rln.js +2 -4
- package/bundle/packages/rln/dist/rln_light.js +223 -0
- package/bundle/packages/rln/node_modules/@chainsafe/bls-keystore/node_modules/ethereum-cryptography/random.js +1 -1
- package/bundle/packages/rln/node_modules/@chainsafe/bls-keystore/node_modules/ethereum-cryptography/utils.js +2 -2
- package/bundle/packages/rln/node_modules/@noble/hashes/_sha2.js +1 -1
- package/bundle/packages/rln/node_modules/@noble/hashes/hmac.js +1 -1
- package/bundle/packages/rln/node_modules/@noble/hashes/pbkdf2.js +1 -1
- package/bundle/packages/rln/node_modules/@noble/hashes/scrypt.js +1 -1
- package/bundle/packages/rln/node_modules/@noble/hashes/sha256.js +1 -1
- package/bundle/packages/rln/node_modules/@noble/hashes/sha512.js +1 -1
- package/bundle/packages/rln/node_modules/@noble/hashes/utils.js +1 -1
- package/dist/.tsbuildinfo +1 -1
- package/dist/contract/rln_contract.d.ts +4 -4
- package/dist/contract/rln_contract.js +58 -59
- package/dist/contract/rln_contract.js.map +1 -1
- package/dist/identity_generator.d.ts +19 -0
- package/dist/identity_generator.js +72 -0
- package/dist/identity_generator.js.map +1 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/rln.js +2 -4
- package/dist/rln.js.map +1 -1
- package/dist/rln_light.d.ts +95 -0
- package/dist/rln_light.js +206 -0
- package/dist/rln_light.js.map +1 -0
- package/package.json +1 -1
- package/src/contract/rln_contract.ts +73 -107
- package/src/identity_generator.ts +108 -0
- package/src/index.ts +15 -0
- package/src/rln.ts +2 -5
- 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-
|
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
|
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.
|
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
|
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
|
214
|
-
if (!this.
|
215
|
-
throw Error("
|
212
|
+
private get membershipErasedFilter(): ethers.EventFilter {
|
213
|
+
if (!this._membershipErasedFilter) {
|
214
|
+
throw Error("MembershipErased filter was not initialized.");
|
216
215
|
}
|
217
|
-
return this.
|
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.
|
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,
|
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.
|
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
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
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
|
-
|
400
|
+
if (existingIndex) {
|
401
|
+
throw new Error(
|
402
|
+
`ID commitment is already registered with index ${existingIndex}`
|
403
|
+
);
|
404
|
+
}
|
405
405
|
|
406
|
-
|
407
|
-
|
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
|
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
|
-
|
429
|
-
|
430
|
-
|
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
|
-
|
498
|
-
|
499
|
-
const membershipId = decodedData.index.toNumber();
|
500
|
-
console.log("registerWithIdentity - membershipId:", membershipId);
|
459
|
+
const membershipId = Number(decodedData.index);
|
501
460
|
|
502
|
-
|
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
|
-
|
515
|
-
|
516
|
-
"registerWithIdentity - error message:",
|
517
|
-
(error
|
518
|
-
|
519
|
-
|
520
|
-
"
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
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
|
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.
|
680
|
-
|
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.
|
688
|
-
|
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
|
-
|
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
|
+
}
|