diff --git a/packages/rln/src/rln_light.ts b/packages/rln/src/credentials_manager.ts similarity index 68% rename from packages/rln/src/rln_light.ts rename to packages/rln/src/credentials_manager.ts index 1c2fde41be..c0100e7486 100644 --- a/packages/rln/src/rln_light.ts +++ b/packages/rln/src/credentials_manager.ts @@ -12,51 +12,20 @@ import type { EncryptedCredentials } from "./keystore/index.js"; import { KeystoreEntity, Password } from "./keystore/types.js"; +import { RegisterMembershipOptions, StartRLNOptions } from "./types.js"; import { buildBigIntFromUint8Array, extractMetaMaskSigner } from "./utils/index.js"; -const log = new Logger("waku:rln"); +const log = new Logger("waku:credentials"); /** - * Create an instance of RLN - * @returns RLNInstance + * Manages credentials for RLN + * This is a lightweight implementation of the RLN contract that doesn't require Zerokit + * It is used to register membership and generate identity credentials */ -export async function create(): Promise { - try { - return new RLNLightInstance(); - } catch (error) { - log.error("Failed to initialize RLN:", error); - throw error; - } -} - -type StartRLNOptions = { - /** - * If not set - will extract MetaMask account and get signer from it. - */ - signer?: ethers.Signer; - /** - * If not set - will use default SEPOLIA_CONTRACT address. - */ - address?: string; - /** - * Credentials to use for generating proofs and connecting to the contract and network. - * If provided used for validating the network chainId and connecting to registry contract. - */ - credentials?: EncryptedCredentials | DecryptedCredentials; - /** - * Rate limit for the member. - */ - rateLimit?: number; -}; - -type RegisterMembershipOptions = - | { signature: string } - | { identity: IdentityCredential }; - -export class RLNLightInstance { +export class RLNCredentialsManager { private started = false; private starting = false; @@ -66,7 +35,9 @@ export class RLNLightInstance { private keystore = Keystore.create(); private _credentials: undefined | DecryptedCredentials; - public constructor() {} + public constructor() { + log.info("RLNCredentialsManager initialized"); + } public get contract(): undefined | RLNLightContract { return this._contract; @@ -78,21 +49,33 @@ export class RLNLightInstance { public async start(options: StartRLNOptions = {}): Promise { if (this.started || this.starting) { + log.info("RLNCredentialsManager already started or starting"); return; } + log.info("Starting RLNCredentialsManager"); this.starting = true; try { const { credentials, keystore } = - await RLNLightInstance.decryptCredentialsIfNeeded(options.credentials); + await RLNCredentialsManager.decryptCredentialsIfNeeded( + options.credentials + ); + + if (credentials) { + log.info("Credentials successfully decrypted"); + } + const { signer, address, rateLimit } = await this.determineStartOptions( options, credentials ); + log.info(`Using contract address: ${address}`); + if (keystore) { this.keystore = keystore; + log.info("Using provided keystore"); } this._credentials = credentials; @@ -102,7 +85,12 @@ export class RLNLightInstance { signer: signer!, rateLimit: rateLimit }); + + log.info("RLNCredentialsManager successfully started"); this.started = true; + } catch (error) { + log.error("Failed to start RLNCredentialsManager", error); + throw error; } finally { this.starting = false; } @@ -124,12 +112,17 @@ export class RLNLightInstance { if (address === LINEA_CONTRACT.address) { chainId = LINEA_CONTRACT.chainId; + log.info(`Using Linea contract with chainId: ${chainId}`); } const signer = options.signer || (await extractMetaMaskSigner()); const currentChainId = await signer.getChainId(); + log.info(`Current chain ID: ${currentChainId}`); if (chainId && chainId !== currentChainId) { + log.error( + `Chain ID mismatch: contract=${chainId}, current=${currentChainId}` + ); throw Error( `Failed to start RLN contract, chain ID of contract is different from current one: contract-${chainId}, current network-${currentChainId}` ); @@ -145,28 +138,38 @@ export class RLNLightInstance { credentials?: EncryptedCredentials | DecryptedCredentials ): Promise<{ credentials?: DecryptedCredentials; keystore?: Keystore }> { if (!credentials) { + log.info("No credentials provided"); return {}; } if ("identity" in credentials) { + log.info("Using already decrypted credentials"); return { credentials }; } + log.info("Attempting to decrypt credentials"); const keystore = Keystore.fromString(credentials.keystore); if (!keystore) { + log.warn("Failed to create keystore from string"); return {}; } - const decryptedCredentials = await keystore.readCredential( - credentials.id, - credentials.password - ); + try { + const decryptedCredentials = await keystore.readCredential( + credentials.id, + credentials.password + ); + log.info(`Successfully decrypted credentials with ID: ${credentials.id}`); - return { - keystore, - credentials: decryptedCredentials - }; + return { + keystore, + credentials: decryptedCredentials + }; + } catch (error) { + log.error("Failed to decrypt credentials", error); + throw error; + } } /** @@ -176,6 +179,7 @@ export class RLNLightInstance { * @returns IdentityCredential */ private generateSeededIdentityCredential(seed: string): IdentityCredential { + log.info("Generating seeded identity credential"); // Convert the seed to bytes const encoder = new TextEncoder(); const seedBytes = encoder.encode(seed); @@ -195,6 +199,7 @@ export class RLNLightInstance { // Convert IDCommitment to BigInt const idCommitmentBigInt = buildBigIntFromUint8Array(idCommitment); + log.info("Successfully generated identity credential"); return new IdentityCredential( idTrapdoor, idNullifier, @@ -208,19 +213,24 @@ export class RLNLightInstance { options: RegisterMembershipOptions ): Promise { if (!this.contract) { + log.error("RLN Contract is not initialized"); throw Error("RLN Contract is not initialized."); } + log.info("Registering membership"); let identity = "identity" in options && options.identity; if ("signature" in options) { + log.info("Generating identity from signature"); identity = this.generateSeededIdentityCredential(options.signature); } if (!identity) { + log.error("Missing signature or identity to register membership"); throw Error("Missing signature or identity to register membership."); } + log.info("Registering identity with contract"); return this.contract.registerWithIdentity(identity); } @@ -230,6 +240,12 @@ export class RLNLightInstance { * @param password: string or bytes to use to decrypt credentials from Keystore */ public async useCredentials(id: string, password: Password): Promise { + log.info(`Attempting to use credentials with ID: ${id}`); this._credentials = await this.keystore?.readCredential(id, password); + if (this._credentials) { + log.info("Successfully loaded credentials"); + } else { + log.warn("Failed to load credentials"); + } } } diff --git a/packages/rln/src/index.ts b/packages/rln/src/index.ts index aa18f8e89c..0d7e825e3d 100644 --- a/packages/rln/src/index.ts +++ b/packages/rln/src/index.ts @@ -3,16 +3,16 @@ import { RLN_ABI } from "./contract/abi.js"; import { LINEA_CONTRACT, RLNContract } from "./contract/index.js"; import { RLNLightContract } from "./contract/rln_light_contract.js"; import { createRLN } from "./create.js"; +import { RLNCredentialsManager } from "./credentials_manager.js"; import { IdentityCredential } from "./identity.js"; import { Keystore } from "./keystore/index.js"; import { Proof } from "./proof.js"; import { RLNInstance } from "./rln.js"; -import { RLNLightInstance } from "./rln_light.js"; import { MerkleRootTracker } from "./root_tracker.js"; import { extractMetaMaskSigner } from "./utils/index.js"; export { - RLNLightInstance, + RLNCredentialsManager, RLNLightContract, createRLN, Keystore, diff --git a/packages/rln/src/rln.ts b/packages/rln/src/rln.ts index ec16f79e7f..54013c7f47 100644 --- a/packages/rln/src/rln.ts +++ b/packages/rln/src/rln.ts @@ -17,7 +17,6 @@ import { } from "./codec.js"; import { DEFAULT_RATE_LIMIT } from "./contract/constants.js"; import { LINEA_CONTRACT, RLNContract } from "./contract/index.js"; -import { IdentityCredential } from "./identity.js"; import { Keystore } from "./keystore/index.js"; import type { DecryptedCredentials, @@ -27,6 +26,7 @@ import { KeystoreEntity, Password } from "./keystore/types.js"; import verificationKey from "./resources/verification_key"; import * as wc from "./resources/witness_calculator"; import { WitnessCalculator } from "./resources/witness_calculator"; +import { RegisterMembershipOptions, StartRLNOptions } from "./types.js"; import { extractMetaMaskSigner } from "./utils/index.js"; import { Zerokit } from "./zerokit.js"; @@ -102,30 +102,6 @@ export async function create(): Promise { } } -type StartRLNOptions = { - /** - * If not set - will extract MetaMask account and get signer from it. - */ - signer?: ethers.Signer; - /** - * If not set - will use default LINEA_CONTRACT address. - */ - address?: string; - /** - * Credentials to use for generating proofs and connecting to the contract and network. - * If provided used for validating the network chainId and connecting to registry contract. - */ - credentials?: EncryptedCredentials | DecryptedCredentials; - /** - * Rate limit for the member. - */ - rateLimit?: number; -}; - -type RegisterMembershipOptions = - | { signature: string } - | { identity: IdentityCredential }; - type WakuRLNEncoderOptions = WakuEncoderOptions & { credentials: EncryptedCredentials | DecryptedCredentials; }; diff --git a/packages/rln/src/types.ts b/packages/rln/src/types.ts new file mode 100644 index 0000000000..7980c8aea9 --- /dev/null +++ b/packages/rln/src/types.ts @@ -0,0 +1,31 @@ +import { ethers } from "ethers"; + +import { IdentityCredential } from "./identity.js"; +import { + DecryptedCredentials, + EncryptedCredentials +} from "./keystore/types.js"; + +export type StartRLNOptions = { + /** + * If not set - will extract MetaMask account and get signer from it. + */ + signer?: ethers.Signer; + /** + * If not set - will use default SEPOLIA_CONTRACT address. + */ + address?: string; + /** + * Credentials to use for generating proofs and connecting to the contract and network. + * If provided used for validating the network chainId and connecting to registry contract. + */ + credentials?: EncryptedCredentials | DecryptedCredentials; + /** + * Rate limit for the member. + */ + rateLimit?: number; +}; + +export type RegisterMembershipOptions = + | { signature: string } + | { identity: IdentityCredential };