@waku/rln 0.0.2-8a6571f.0 → 0.0.2-9094860.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/index.js +2 -0
- package/bundle/packages/rln/dist/contract/rln_contract.js +95 -25
- package/bundle/packages/rln/dist/identity_generator.js +75 -0
- package/bundle/packages/rln/dist/rln.js +0 -8
- package/bundle/packages/rln/dist/rln_light.js +228 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/contract/rln_contract.d.ts +5 -3
- package/dist/contract/rln_contract.js +95 -25
- 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 +0 -8
- package/dist/rln.js.map +1 -1
- package/dist/rln_light.d.ts +95 -0
- package/dist/rln_light.js +210 -0
- package/dist/rln_light.js.map +1 -0
- package/package.json +1 -1
- package/src/contract/rln_contract.ts +129 -27
- package/src/identity_generator.ts +108 -0
- package/src/index.ts +15 -0
- package/src/rln.ts +0 -9
- package/src/rln_light.ts +304 -0
@@ -0,0 +1,210 @@
|
|
1
|
+
import { Logger } from "@waku/utils";
|
2
|
+
import { RLNContract, SEPOLIA_CONTRACT } 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 startOptions = await this.determineStartOptions(options, keystoreCredentials);
|
47
|
+
// Set the signer
|
48
|
+
if (startOptions.signer) {
|
49
|
+
this._signer = startOptions.signer;
|
50
|
+
}
|
51
|
+
else {
|
52
|
+
this._signer = await extractMetaMaskSigner();
|
53
|
+
}
|
54
|
+
// Initialize the contract with the correct address format
|
55
|
+
const finalAddress = typeof startOptions.address === "string"
|
56
|
+
? startOptions.address
|
57
|
+
: SEPOLIA_CONTRACT.address;
|
58
|
+
const contractOptions = {
|
59
|
+
signer: this._signer,
|
60
|
+
address: finalAddress,
|
61
|
+
rateLimit: startOptions.rateLimit
|
62
|
+
};
|
63
|
+
// We need to create a minimal compatible object with RLNInstance interface
|
64
|
+
// but we only need it for contract initialization
|
65
|
+
const compatibleRLN = {
|
66
|
+
zerokit: {
|
67
|
+
getMerkleRoot: () => new Uint8Array(32),
|
68
|
+
insertMember: (_idCommitment) => { },
|
69
|
+
insertMembers: (_index, ..._idCommitments) => { },
|
70
|
+
deleteMember: (_index) => { }
|
71
|
+
}
|
72
|
+
};
|
73
|
+
this._contract = await RLNContract.init(
|
74
|
+
// Type assertion needed for compatibility with RLNInstance interface
|
75
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
76
|
+
compatibleRLN, contractOptions);
|
77
|
+
this.started = true;
|
78
|
+
}
|
79
|
+
catch (error) {
|
80
|
+
log.error("Failed to start RLNLight:", error);
|
81
|
+
throw error;
|
82
|
+
}
|
83
|
+
finally {
|
84
|
+
this.starting = false;
|
85
|
+
}
|
86
|
+
}
|
87
|
+
/**
|
88
|
+
* Find credentials in the keystore by contract address
|
89
|
+
*/
|
90
|
+
async findCredentialsByAddress(address) {
|
91
|
+
// Since Keystore doesn't have a direct method to find by address,
|
92
|
+
// we need to iterate through all credentials
|
93
|
+
const hashes = this.keystore.keys();
|
94
|
+
for (const hash of hashes) {
|
95
|
+
try {
|
96
|
+
// Try with an empty password - this won't work but lets us check schema
|
97
|
+
const credential = await this.keystore.readCredential(hash, new Uint8Array(0));
|
98
|
+
if (credential?.membership.address === address) {
|
99
|
+
return credential;
|
100
|
+
}
|
101
|
+
}
|
102
|
+
catch (e) {
|
103
|
+
// Ignore errors, just means we couldn't read this credential
|
104
|
+
}
|
105
|
+
}
|
106
|
+
return undefined;
|
107
|
+
}
|
108
|
+
/**
|
109
|
+
* Determine the correct options for starting RLNLight
|
110
|
+
*/
|
111
|
+
async determineStartOptions(options, credentials) {
|
112
|
+
if (options.credentials) {
|
113
|
+
const { credentials: decrypted } = await RLNLight.decryptCredentialsIfNeeded(options.credentials);
|
114
|
+
if (decrypted?.membership) {
|
115
|
+
this._credentials = decrypted;
|
116
|
+
return {
|
117
|
+
...options,
|
118
|
+
address: decrypted.membership.address
|
119
|
+
};
|
120
|
+
}
|
121
|
+
}
|
122
|
+
else if (credentials) {
|
123
|
+
return {
|
124
|
+
...options,
|
125
|
+
address: credentials.membership.address
|
126
|
+
};
|
127
|
+
}
|
128
|
+
return options;
|
129
|
+
}
|
130
|
+
/**
|
131
|
+
* Decrypt credentials if they are encrypted
|
132
|
+
*/
|
133
|
+
static async decryptCredentialsIfNeeded(credentials) {
|
134
|
+
if (!credentials) {
|
135
|
+
return {};
|
136
|
+
}
|
137
|
+
if ("identity" in credentials) {
|
138
|
+
return { credentials };
|
139
|
+
}
|
140
|
+
else {
|
141
|
+
const keystore = Keystore.create();
|
142
|
+
try {
|
143
|
+
const decrypted = await keystore.readCredential(credentials.id, credentials.password);
|
144
|
+
if (!decrypted) {
|
145
|
+
return {};
|
146
|
+
}
|
147
|
+
return {
|
148
|
+
credentials: {
|
149
|
+
identity: decrypted.identity,
|
150
|
+
membership: decrypted.membership
|
151
|
+
},
|
152
|
+
keystore
|
153
|
+
};
|
154
|
+
}
|
155
|
+
catch (e) {
|
156
|
+
log.error("Failed to decrypt credentials", e);
|
157
|
+
return {};
|
158
|
+
}
|
159
|
+
}
|
160
|
+
}
|
161
|
+
/**
|
162
|
+
* Register a membership using an identity or signature
|
163
|
+
* @param options Options including identity or signature
|
164
|
+
* @returns Decrypted credentials if successful
|
165
|
+
*/
|
166
|
+
async registerMembership(options) {
|
167
|
+
if (!this.contract) {
|
168
|
+
throw Error("RLN Contract is not initialized.");
|
169
|
+
}
|
170
|
+
let identity = "identity" in options && options.identity;
|
171
|
+
if ("signature" in options) {
|
172
|
+
identity = this.identityGenerator.generateSeededIdentityCredential(options.signature);
|
173
|
+
}
|
174
|
+
if (!identity) {
|
175
|
+
throw Error("Missing signature or identity to register membership.");
|
176
|
+
}
|
177
|
+
return this.contract.registerWithIdentity(identity);
|
178
|
+
}
|
179
|
+
/**
|
180
|
+
* Changes credentials in use by relying on provided Keystore
|
181
|
+
* @param id Hash of credentials to select from Keystore
|
182
|
+
* @param password Password to decrypt credentials from Keystore
|
183
|
+
*/
|
184
|
+
async useCredentials(id, password) {
|
185
|
+
const decrypted = await this.keystore.readCredential(id, password);
|
186
|
+
if (!decrypted) {
|
187
|
+
throw new Error("Credentials not found or incorrect password");
|
188
|
+
}
|
189
|
+
this._credentials = {
|
190
|
+
identity: decrypted.identity,
|
191
|
+
membership: decrypted.membership
|
192
|
+
};
|
193
|
+
}
|
194
|
+
/**
|
195
|
+
* Generate a new identity credential
|
196
|
+
* @returns New identity credential
|
197
|
+
*/
|
198
|
+
generateIdentityCredential() {
|
199
|
+
return this.identityGenerator.generateIdentityCredentials();
|
200
|
+
}
|
201
|
+
/**
|
202
|
+
* Generate a seeded identity credential
|
203
|
+
* @param seed Seed string to generate deterministic identity
|
204
|
+
* @returns Seeded identity credential
|
205
|
+
*/
|
206
|
+
generateSeededIdentityCredential(seed) {
|
207
|
+
return this.identityGenerator.generateSeededIdentityCredential(seed);
|
208
|
+
}
|
209
|
+
}
|
210
|
+
//# 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-9094860.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-9094860.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-9094860.0","@waku/utils":"0.0.22-9094860.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"}}
|
@@ -30,7 +30,7 @@ interface RLNContractInitOptions extends RLNContractOptions {
|
|
30
30
|
|
31
31
|
export interface MembershipRegisteredEvent {
|
32
32
|
idCommitment: string;
|
33
|
-
|
33
|
+
membershipRateLimit: ethers.BigNumber;
|
34
34
|
index: ethers.BigNumber;
|
35
35
|
}
|
36
36
|
|
@@ -65,7 +65,8 @@ export class RLNContract {
|
|
65
65
|
|
66
66
|
private _members: Map<number, Member> = new Map();
|
67
67
|
private _membersFilter: ethers.EventFilter;
|
68
|
-
private
|
68
|
+
private _membershipErasedFilter: ethers.EventFilter;
|
69
|
+
private _membersExpiredFilter: ethers.EventFilter;
|
69
70
|
|
70
71
|
/**
|
71
72
|
* Asynchronous initializer for RLNContract.
|
@@ -111,9 +112,10 @@ export class RLNContract {
|
|
111
112
|
this.contract = contract || new ethers.Contract(address, RLN_ABI, signer);
|
112
113
|
this.merkleRootTracker = new MerkleRootTracker(5, initialRoot);
|
113
114
|
|
114
|
-
// Initialize event filters
|
115
|
+
// Initialize event filters
|
115
116
|
this._membersFilter = this.contract.filters.MembershipRegistered();
|
116
|
-
this.
|
117
|
+
this._membershipErasedFilter = this.contract.filters.MembershipErased();
|
118
|
+
this._membersExpiredFilter = this.contract.filters.MembershipExpired();
|
117
119
|
}
|
118
120
|
|
119
121
|
/**
|
@@ -182,7 +184,7 @@ export class RLNContract {
|
|
182
184
|
this.contract.maxTotalRateLimit(),
|
183
185
|
this.contract.currentTotalRateLimit()
|
184
186
|
]);
|
185
|
-
return maxTotal
|
187
|
+
return Number(maxTotal) - Number(currentTotal);
|
186
188
|
}
|
187
189
|
|
188
190
|
/**
|
@@ -207,11 +209,18 @@ export class RLNContract {
|
|
207
209
|
return this._membersFilter;
|
208
210
|
}
|
209
211
|
|
210
|
-
private get
|
211
|
-
if (!this.
|
212
|
-
throw Error("
|
212
|
+
private get membershipErasedFilter(): ethers.EventFilter {
|
213
|
+
if (!this._membershipErasedFilter) {
|
214
|
+
throw Error("MembershipErased filter was not initialized.");
|
215
|
+
}
|
216
|
+
return this._membershipErasedFilter;
|
217
|
+
}
|
218
|
+
|
219
|
+
private get membersExpiredFilter(): ethers.EventFilter {
|
220
|
+
if (!this._membersExpiredFilter) {
|
221
|
+
throw Error("MembersExpired filter was not initialized.");
|
213
222
|
}
|
214
|
-
return this.
|
223
|
+
return this._membersExpiredFilter;
|
215
224
|
}
|
216
225
|
|
217
226
|
public async fetchMembers(
|
@@ -226,10 +235,19 @@ export class RLNContract {
|
|
226
235
|
const removedMemberEvents = await queryFilter(this.contract, {
|
227
236
|
fromBlock: this.deployBlock,
|
228
237
|
...options,
|
229
|
-
membersFilter: this.
|
238
|
+
membersFilter: this.membershipErasedFilter
|
239
|
+
});
|
240
|
+
const expiredMemberEvents = await queryFilter(this.contract, {
|
241
|
+
fromBlock: this.deployBlock,
|
242
|
+
...options,
|
243
|
+
membersFilter: this.membersExpiredFilter
|
230
244
|
});
|
231
245
|
|
232
|
-
const events = [
|
246
|
+
const events = [
|
247
|
+
...registeredMemberEvents,
|
248
|
+
...removedMemberEvents,
|
249
|
+
...expiredMemberEvents
|
250
|
+
];
|
233
251
|
this.processEvents(rlnInstance, events);
|
234
252
|
}
|
235
253
|
|
@@ -242,8 +260,20 @@ export class RLNContract {
|
|
242
260
|
return;
|
243
261
|
}
|
244
262
|
|
245
|
-
if (
|
246
|
-
|
263
|
+
if (
|
264
|
+
evt.event === "MembershipErased" ||
|
265
|
+
evt.event === "MembershipExpired"
|
266
|
+
) {
|
267
|
+
let index = evt.args.index;
|
268
|
+
|
269
|
+
if (!index) {
|
270
|
+
return;
|
271
|
+
}
|
272
|
+
|
273
|
+
if (typeof index === "number" || typeof index === "string") {
|
274
|
+
index = ethers.BigNumber.from(index);
|
275
|
+
}
|
276
|
+
|
247
277
|
const toRemoveVal = toRemoveTable.get(evt.blockNumber);
|
248
278
|
if (toRemoveVal != undefined) {
|
249
279
|
toRemoveVal.push(index.toNumber());
|
@@ -275,15 +305,21 @@ export class RLNContract {
|
|
275
305
|
if (!evt.args) return;
|
276
306
|
|
277
307
|
const _idCommitment = evt.args.idCommitment as string;
|
278
|
-
|
308
|
+
let index = evt.args.index;
|
279
309
|
|
280
310
|
if (!_idCommitment || !index) {
|
281
311
|
return;
|
282
312
|
}
|
283
313
|
|
314
|
+
if (typeof index === "number" || typeof index === "string") {
|
315
|
+
index = ethers.BigNumber.from(index);
|
316
|
+
}
|
317
|
+
|
284
318
|
const idCommitment = zeroPadLE(hexToBytes(_idCommitment), 32);
|
285
319
|
rlnInstance.zerokit.insertMember(idCommitment);
|
286
|
-
|
320
|
+
|
321
|
+
const numericIndex = index.toNumber();
|
322
|
+
this._members.set(numericIndex, {
|
287
323
|
index,
|
288
324
|
idCommitment: _idCommitment
|
289
325
|
});
|
@@ -316,7 +352,19 @@ export class RLNContract {
|
|
316
352
|
this.membersFilter,
|
317
353
|
(
|
318
354
|
_idCommitment: string,
|
319
|
-
|
355
|
+
_membershipRateLimit: ethers.BigNumber,
|
356
|
+
_index: ethers.BigNumber,
|
357
|
+
event: ethers.Event
|
358
|
+
) => {
|
359
|
+
this.processEvents(rlnInstance, [event]);
|
360
|
+
}
|
361
|
+
);
|
362
|
+
|
363
|
+
this.contract.on(
|
364
|
+
this.membershipErasedFilter,
|
365
|
+
(
|
366
|
+
_idCommitment: string,
|
367
|
+
_membershipRateLimit: ethers.BigNumber,
|
320
368
|
_index: ethers.BigNumber,
|
321
369
|
event: ethers.Event
|
322
370
|
) => {
|
@@ -325,10 +373,10 @@ export class RLNContract {
|
|
325
373
|
);
|
326
374
|
|
327
375
|
this.contract.on(
|
328
|
-
this.
|
376
|
+
this.membersExpiredFilter,
|
329
377
|
(
|
330
378
|
_idCommitment: string,
|
331
|
-
|
379
|
+
_membershipRateLimit: ethers.BigNumber,
|
332
380
|
_index: ethers.BigNumber,
|
333
381
|
event: ethers.Event
|
334
382
|
) => {
|
@@ -345,15 +393,45 @@ export class RLNContract {
|
|
345
393
|
`Registering identity with rate limit: ${this.rateLimit} messages/epoch`
|
346
394
|
);
|
347
395
|
|
396
|
+
// Check if the ID commitment is already registered
|
397
|
+
const existingIndex = await this.getMemberIndex(
|
398
|
+
identity.IDCommitmentBigInt.toString()
|
399
|
+
);
|
400
|
+
if (existingIndex) {
|
401
|
+
throw new Error(
|
402
|
+
`ID commitment is already registered with index ${existingIndex}`
|
403
|
+
);
|
404
|
+
}
|
405
|
+
|
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
|
+
[]
|
418
|
+
);
|
419
|
+
const gasLimit = estimatedGas.add(10000);
|
420
|
+
|
348
421
|
const txRegisterResponse: ethers.ContractTransaction =
|
349
422
|
await this.contract.register(
|
350
423
|
identity.IDCommitmentBigInt,
|
351
424
|
this.rateLimit,
|
352
425
|
[],
|
353
|
-
{ gasLimit
|
426
|
+
{ gasLimit }
|
354
427
|
);
|
428
|
+
|
355
429
|
const txRegisterReceipt = await txRegisterResponse.wait();
|
356
430
|
|
431
|
+
if (txRegisterReceipt.status === 0) {
|
432
|
+
throw new Error("Transaction failed on-chain");
|
433
|
+
}
|
434
|
+
|
357
435
|
const memberRegistered = txRegisterReceipt.events?.find(
|
358
436
|
(event) => event.event === "MembershipRegistered"
|
359
437
|
);
|
@@ -367,18 +445,18 @@ export class RLNContract {
|
|
367
445
|
|
368
446
|
const decodedData: MembershipRegisteredEvent = {
|
369
447
|
idCommitment: memberRegistered.args.idCommitment,
|
370
|
-
|
448
|
+
membershipRateLimit: memberRegistered.args.membershipRateLimit,
|
371
449
|
index: memberRegistered.args.index
|
372
450
|
};
|
373
451
|
|
374
452
|
log.info(
|
375
453
|
`Successfully registered membership with index ${decodedData.index} ` +
|
376
|
-
`and rate limit ${decodedData.
|
454
|
+
`and rate limit ${decodedData.membershipRateLimit}`
|
377
455
|
);
|
378
456
|
|
379
457
|
const network = await this.contract.provider.getNetwork();
|
380
458
|
const address = this.contract.address;
|
381
|
-
const membershipId = decodedData.index
|
459
|
+
const membershipId = Number(decodedData.index);
|
382
460
|
|
383
461
|
return {
|
384
462
|
identity,
|
@@ -389,8 +467,32 @@ export class RLNContract {
|
|
389
467
|
}
|
390
468
|
};
|
391
469
|
} catch (error) {
|
392
|
-
|
393
|
-
|
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
|
+
}
|
394
496
|
}
|
395
497
|
}
|
396
498
|
|
@@ -468,18 +570,18 @@ export class RLNContract {
|
|
468
570
|
|
469
571
|
const decodedData: MembershipRegisteredEvent = {
|
470
572
|
idCommitment: memberRegistered.args.idCommitment,
|
471
|
-
|
573
|
+
membershipRateLimit: memberRegistered.args.membershipRateLimit,
|
472
574
|
index: memberRegistered.args.index
|
473
575
|
};
|
474
576
|
|
475
577
|
log.info(
|
476
578
|
`Successfully registered membership with permit. Index: ${decodedData.index}, ` +
|
477
|
-
`Rate limit: ${decodedData.
|
579
|
+
`Rate limit: ${decodedData.membershipRateLimit}, Erased ${idCommitmentsToErase.length} commitments`
|
478
580
|
);
|
479
581
|
|
480
582
|
const network = await this.contract.provider.getNetwork();
|
481
583
|
const address = this.contract.address;
|
482
|
-
const membershipId = decodedData.index
|
584
|
+
const membershipId = Number(decodedData.index);
|
483
585
|
|
484
586
|
return {
|
485
587
|
identity,
|
@@ -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
|
+
}
|
package/src/index.ts
CHANGED
@@ -3,9 +3,11 @@ import { RLN_ABI } from "./contract/abi.js";
|
|
3
3
|
import { RLNContract, SEPOLIA_CONTRACT } from "./contract/index.js";
|
4
4
|
import { createRLN } from "./create.js";
|
5
5
|
import { IdentityCredential } from "./identity.js";
|
6
|
+
import { IdentityGenerator } from "./identity_generator.js";
|
6
7
|
import { Keystore } from "./keystore/index.js";
|
7
8
|
import { Proof } from "./proof.js";
|
8
9
|
import { RLNInstance } from "./rln.js";
|
10
|
+
import { RLNLight } from "./rln_light.js";
|
9
11
|
import { MerkleRootTracker } from "./root_tracker.js";
|
10
12
|
import { extractMetaMaskSigner } from "./utils/index.js";
|
11
13
|
|
@@ -13,6 +15,8 @@ export {
|
|
13
15
|
createRLN,
|
14
16
|
Keystore,
|
15
17
|
RLNInstance,
|
18
|
+
RLNLight,
|
19
|
+
IdentityGenerator,
|
16
20
|
IdentityCredential,
|
17
21
|
Proof,
|
18
22
|
RLNEncoder,
|
@@ -23,3 +27,14 @@ export {
|
|
23
27
|
extractMetaMaskSigner,
|
24
28
|
RLN_ABI
|
25
29
|
};
|
30
|
+
|
31
|
+
export type {
|
32
|
+
DecryptedCredentials,
|
33
|
+
EncryptedCredentials,
|
34
|
+
Keccak256Hash,
|
35
|
+
KeystoreEntity,
|
36
|
+
MembershipHash,
|
37
|
+
MembershipInfo,
|
38
|
+
Password,
|
39
|
+
Sha256Hash
|
40
|
+
} from "./keystore/types.js";
|