@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.
@@ -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":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAGrC,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAEpE,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAM/C,OAAO,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAEzD,MAAM,GAAG,GAAG,IAAI,MAAM,CAAC,gBAAgB,CAAC,CAAC;AAgCzC;;;GAGG;AACH,MAAM,OAAO,QAAQ;IACX,OAAO,GAAG,KAAK,CAAC;IAChB,QAAQ,GAAG,KAAK,CAAC;IAEjB,SAAS,CAA0B;IACnC,OAAO,CAA4B;IAEnC,QAAQ,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC;IAC7B,YAAY,CAAmC;IAC/C,iBAAiB,GAAG,IAAI,iBAAiB,EAAE,CAAC;IAEpD,IAAW,QAAQ;QACjB,OAAO,IAAI,CAAC,SAAS,CAAC;IACxB,CAAC;IAED,IAAW,MAAM;QACf,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED;;OAEG;IACH,IAAW,WAAW;QACpB,OAAO,IAAI,CAAC,YAAY,CAAC;IAC3B,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,KAAK,CAAC,UAAgC,EAAE;QACnD,IAAI,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClC,GAAG,CAAC,IAAI,CAAC,sCAAsC,CAAC,CAAC;YACjD,OAAO;QACT,CAAC;QAED,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QAErB,IAAI,CAAC;YACH,yDAAyD;YACzD,MAAM,mBAAmB,GAAG,OAAO,CAAC,OAAO;gBACzC,CAAC,CAAC,MAAM,IAAI,CAAC,wBAAwB,CAAC,OAAO,CAAC,OAAO,CAAC;gBACtD,CAAC,CAAC,SAAS,CAAC;YAEd,MAAM,YAAY,GAAG,MAAM,IAAI,CAAC,qBAAqB,CACnD,OAAO,EACP,mBAAmB,CACpB,CAAC;YAEF,iBAAiB;YACjB,IAAI,YAAY,CAAC,MAAM,EAAE,CAAC;gBACxB,IAAI,CAAC,OAAO,GAAG,YAAY,CAAC,MAAM,CAAC;YACrC,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,OAAO,GAAG,MAAM,qBAAqB,EAAE,CAAC;YAC/C,CAAC;YAED,0DAA0D;YAC1D,MAAM,YAAY,GAChB,OAAO,YAAY,CAAC,OAAO,KAAK,QAAQ;gBACtC,CAAC,CAAC,YAAY,CAAC,OAAO;gBACtB,CAAC,CAAC,gBAAgB,CAAC,OAAO,CAAC;YAE/B,MAAM,eAAe,GAAG;gBACtB,MAAM,EAAE,IAAI,CAAC,OAAO;gBACpB,OAAO,EAAE,YAAY;gBACrB,SAAS,EAAE,YAAY,CAAC,SAAS;aAClC,CAAC;YAEF,2EAA2E;YAC3E,kDAAkD;YAClD,MAAM,aAAa,GAAG;gBACpB,OAAO,EAAE;oBACP,aAAa,EAAE,GAAG,EAAE,CAAC,IAAI,UAAU,CAAC,EAAE,CAAC;oBACvC,YAAY,EAAE,CAAC,aAAyB,EAAE,EAAE,GAAE,CAAC;oBAC/C,aAAa,EAAE,CACb,MAAc,EACd,GAAG,cAAiC,EACpC,EAAE,GAAE,CAAC;oBACP,YAAY,EAAE,CAAC,MAAc,EAAE,EAAE,GAAE,CAAC;iBACrC;aACF,CAAC;YAEF,IAAI,CAAC,SAAS,GAAG,MAAM,WAAW,CAAC,IAAI;YACrC,qEAAqE;YACrE,8DAA8D;YAC9D,aAAoB,EACpB,eAAe,CAChB,CAAC;YAEF,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACtB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,GAAG,CAAC,KAAK,CAAC,2BAA2B,EAAE,KAAK,CAAC,CAAC;YAC9C,MAAM,KAAK,CAAC;QACd,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;QACxB,CAAC;IACH,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,wBAAwB,CACpC,OAAe;QAEf,kEAAkE;QAClE,6CAA6C;QAC7C,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;QAEpC,KAAK,MAAM,IAAI,IAAI,MAAM,EAAE,CAAC;YAC1B,IAAI,CAAC;gBACH,wEAAwE;gBACxE,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,cAAc,CACnD,IAAI,EACJ,IAAI,UAAU,CAAC,CAAC,CAAC,CAClB,CAAC;gBACF,IAAI,UAAU,EAAE,UAAU,CAAC,OAAO,KAAK,OAAO,EAAE,CAAC;oBAC/C,OAAO,UAAU,CAAC;gBACpB,CAAC;YACH,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,6DAA6D;YAC/D,CAAC;QACH,CAAC;QAED,OAAO,SAAS,CAAC;IACnB,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,qBAAqB,CACjC,OAA6B,EAC7B,WAAuC;QAEvC,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;YACxB,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,GAC9B,MAAM,QAAQ,CAAC,0BAA0B,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;YAEjE,IAAI,SAAS,EAAE,UAAU,EAAE,CAAC;gBAC1B,IAAI,CAAC,YAAY,GAAG,SAAS,CAAC;gBAC9B,OAAO;oBACL,GAAG,OAAO;oBACV,OAAO,EAAE,SAAS,CAAC,UAAU,CAAC,OAAO;iBACtC,CAAC;YACJ,CAAC;QACH,CAAC;aAAM,IAAI,WAAW,EAAE,CAAC;YACvB,OAAO;gBACL,GAAG,OAAO;gBACV,OAAO,EAAE,WAAW,CAAC,UAAU,CAAC,OAAO;aACxC,CAAC;QACJ,CAAC;QAED,OAAO,OAAO,CAAC;IACjB,CAAC;IAED;;OAEG;IACK,MAAM,CAAC,KAAK,CAAC,0BAA0B,CAC7C,WAAyD;QAEzD,IAAI,CAAC,WAAW,EAAE,CAAC;YACjB,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,IAAI,UAAU,IAAI,WAAW,EAAE,CAAC;YAC9B,OAAO,EAAE,WAAW,EAAE,CAAC;QACzB,CAAC;aAAM,CAAC;YACN,MAAM,QAAQ,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC;YACnC,IAAI,CAAC;gBACH,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC,cAAc,CAC7C,WAAW,CAAC,EAAE,EACd,WAAW,CAAC,QAAQ,CACrB,CAAC;gBAEF,IAAI,CAAC,SAAS,EAAE,CAAC;oBACf,OAAO,EAAE,CAAC;gBACZ,CAAC;gBAED,OAAO;oBACL,WAAW,EAAE;wBACX,QAAQ,EAAE,SAAS,CAAC,QAAQ;wBAC5B,UAAU,EAAE,SAAS,CAAC,UAAU;qBACjC;oBACD,QAAQ;iBACT,CAAC;YACJ,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,GAAG,CAAC,KAAK,CAAC,+BAA+B,EAAE,CAAC,CAAC,CAAC;gBAC9C,OAAO,EAAE,CAAC;YACZ,CAAC;QACH,CAAC;IACH,CAAC;IAED;;;;OAIG;IACI,KAAK,CAAC,kBAAkB,CAC7B,OAAkC;QAElC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnB,MAAM,KAAK,CAAC,kCAAkC,CAAC,CAAC;QAClD,CAAC;QAED,IAAI,QAAQ,GAAG,UAAU,IAAI,OAAO,IAAI,OAAO,CAAC,QAAQ,CAAC;QAEzD,IAAI,WAAW,IAAI,OAAO,EAAE,CAAC;YAC3B,QAAQ,GAAG,IAAI,CAAC,iBAAiB,CAAC,gCAAgC,CAChE,OAAO,CAAC,SAAS,CAClB,CAAC;QACJ,CAAC;QAED,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,MAAM,KAAK,CAAC,uDAAuD,CAAC,CAAC;QACvE,CAAC;QAED,OAAO,IAAI,CAAC,QAAQ,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAC;IACtD,CAAC;IAED;;;;OAIG;IACI,KAAK,CAAC,cAAc,CAAC,EAAU,EAAE,QAAkB;QACxD,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;QAEnE,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAC;QACjE,CAAC;QAED,IAAI,CAAC,YAAY,GAAG;YAClB,QAAQ,EAAE,SAAS,CAAC,QAAQ;YAC5B,UAAU,EAAE,SAAS,CAAC,UAAU;SACjC,CAAC;IACJ,CAAC;IAED;;;OAGG;IACI,0BAA0B;QAC/B,OAAO,IAAI,CAAC,iBAAiB,CAAC,2BAA2B,EAAE,CAAC;IAC9D,CAAC;IAED;;;;OAIG;IACI,gCAAgC,CAAC,IAAY;QAClD,OAAO,IAAI,CAAC,iBAAiB,CAAC,gCAAgC,CAAC,IAAI,CAAC,CAAC;IACvE,CAAC;CACF"}
package/package.json CHANGED
@@ -1 +1 @@
1
- {"name":"@waku/rln","version":"0.0.2-8a6571f.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-8a6571f.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-8a6571f.0","@waku/utils":"0.0.22-8a6571f.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-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
- rateLimit: number;
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 _membersRemovedFilter: ethers.EventFilter;
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 for MembershipRegistered and MembershipErased
115
+ // Initialize event filters
115
116
  this._membersFilter = this.contract.filters.MembershipRegistered();
116
- this._membersRemovedFilter = this.contract.filters.MembershipErased();
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.sub(currentTotal).toNumber();
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 membersRemovedFilter(): ethers.EventFilter {
211
- if (!this._membersRemovedFilter) {
212
- 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.");
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._membersRemovedFilter;
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.membersRemovedFilter
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 = [...registeredMemberEvents, ...removedMemberEvents];
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 (evt.event === "MembershipErased") {
246
- const index = evt.args.index as ethers.BigNumber;
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
- const index = evt.args.index as ethers.BigNumber;
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
- this._members.set(index.toNumber(), {
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
- _rateLimit: number,
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.membersRemovedFilter,
376
+ this.membersExpiredFilter,
329
377
  (
330
378
  _idCommitment: string,
331
- _rateLimit: number,
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: 300000 }
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
- rateLimit: memberRegistered.args.rateLimit,
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.rateLimit}`
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.toNumber();
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
- log.error(`Error in registerWithIdentity: ${(error as Error).message}`);
393
- 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
+ }
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
- rateLimit: memberRegistered.args.rateLimit,
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.rateLimit}, Erased ${idCommitmentsToErase.length} commitments`
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.toNumber();
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";