From 7aaa84a68c0f5ad3dcd308ff91aaa1c3b57827d9 Mon Sep 17 00:00:00 2001 From: Danish Arora Date: Thu, 3 Apr 2025 19:51:55 +0530 Subject: [PATCH] chore: use CredentialsManager for rln.ts --- packages/rln/src/create.ts | 2 +- packages/rln/src/credentials_manager.ts | 79 +++++- packages/rln/src/rln.ts | 312 ++++++------------------ 3 files changed, 140 insertions(+), 253 deletions(-) diff --git a/packages/rln/src/create.ts b/packages/rln/src/create.ts index 3b32302832..24d8b660d3 100644 --- a/packages/rln/src/create.ts +++ b/packages/rln/src/create.ts @@ -5,5 +5,5 @@ export async function createRLN(): Promise { // asynchronously. This file does the single async import, so // that no one else needs to worry about it again. const rlnModule = await import("./rln.js"); - return rlnModule.create(); + return rlnModule.RLNInstance.create(); } diff --git a/packages/rln/src/credentials_manager.ts b/packages/rln/src/credentials_manager.ts index a4e7681c55..86e78c3af6 100644 --- a/packages/rln/src/credentials_manager.ts +++ b/packages/rln/src/credentials_manager.ts @@ -17,6 +17,7 @@ import { buildBigIntFromUint8Array, extractMetaMaskSigner } from "./utils/index.js"; +import { Zerokit } from "./zerokit.js"; const log = new Logger("waku:credentials"); @@ -26,27 +27,50 @@ const log = new Logger("waku:credentials"); * It is used to register membership and generate identity credentials */ export class RLNCredentialsManager { - private started = false; - private starting = false; + protected started = false; + protected starting = false; private _contract: undefined | RLNBaseContract; private _signer: undefined | ethers.Signer; - private keystore = Keystore.create(); + protected keystore = Keystore.create(); private _credentials: undefined | DecryptedCredentials; - public constructor() { + public zerokit: undefined | Zerokit; + + public constructor(zerokit?: Zerokit) { log.info("RLNCredentialsManager initialized"); + this.zerokit = zerokit; } public get contract(): undefined | RLNBaseContract { return this._contract; } + public set contract(contract: RLNBaseContract | undefined) { + this._contract = contract; + } + public get signer(): undefined | ethers.Signer { return this._signer; } + public set signer(signer: ethers.Signer | undefined) { + this._signer = signer; + } + + public get credentials(): undefined | DecryptedCredentials { + return this._credentials; + } + + public set credentials(credentials: DecryptedCredentials | undefined) { + this._credentials = credentials; + } + + public get provider(): undefined | ethers.providers.Provider { + return this.contract?.provider; + } + public async start(options: StartRLNOptions = {}): Promise { if (this.started || this.starting) { log.info("RLNCredentialsManager already started or starting"); @@ -83,7 +107,7 @@ export class RLNCredentialsManager { this._contract = new RLNBaseContract({ address: address!, signer: signer!, - rateLimit: rateLimit + rateLimit: rateLimit ?? this.zerokit?.getRateLimit }); log.info("RLNCredentialsManager successfully started"); @@ -96,11 +120,7 @@ export class RLNCredentialsManager { } } - public get credentials(): DecryptedCredentials | undefined { - return this._credentials; - } - - private async determineStartOptions( + protected async determineStartOptions( options: StartRLNOptions, credentials: KeystoreEntity | undefined ): Promise { @@ -134,7 +154,7 @@ export class RLNCredentialsManager { }; } - private static async decryptCredentialsIfNeeded( + protected static async decryptCredentialsIfNeeded( credentials?: EncryptedCredentials | DecryptedCredentials ): Promise<{ credentials?: DecryptedCredentials; keystore?: Keystore }> { if (!credentials) { @@ -222,7 +242,15 @@ export class RLNCredentialsManager { if ("signature" in options) { log.info("Generating identity from signature"); - identity = this.generateSeededIdentityCredential(options.signature); + if (this.zerokit) { + log.info("Using Zerokit to generate identity"); + identity = this.zerokit.generateSeededIdentityCredential( + options.signature + ); + } else { + log.info("Using local implementation to generate identity"); + identity = this.generateSeededIdentityCredential(options.signature); + } } if (!identity) { @@ -248,4 +276,31 @@ export class RLNCredentialsManager { log.warn("Failed to load credentials"); } } + + protected async verifyCredentialsAgainstContract( + credentials: KeystoreEntity + ): Promise { + if (!this.contract) { + throw Error( + "Failed to verify chain coordinates: no contract initialized." + ); + } + + const registryAddress = credentials.membership.address; + const currentRegistryAddress = this.contract.address; + if (registryAddress !== currentRegistryAddress) { + throw Error( + `Failed to verify chain coordinates: credentials contract address=${registryAddress} is not equal to registryContract address=${currentRegistryAddress}` + ); + } + + const chainId = credentials.membership.chainId; + const network = await this.contract.provider.getNetwork(); + const currentChainId = network.chainId; + if (chainId !== currentChainId) { + throw Error( + `Failed to verify chain coordinates: credentials chainID=${chainId} is not equal to registryContract chainID=${currentChainId}` + ); + } + } } diff --git a/packages/rln/src/rln.ts b/packages/rln/src/rln.ts index 54013c7f47..98878756aa 100644 --- a/packages/rln/src/rln.ts +++ b/packages/rln/src/rln.ts @@ -7,7 +7,6 @@ import type { import { Logger } from "@waku/utils"; import init from "@waku/zerokit-rln-wasm"; import * as zerokitRLN from "@waku/zerokit-rln-wasm"; -import { ethers } from "ethers"; import { createRLNDecoder, @@ -16,234 +15,51 @@ import { type RLNEncoder } from "./codec.js"; import { DEFAULT_RATE_LIMIT } from "./contract/constants.js"; -import { LINEA_CONTRACT, RLNContract } from "./contract/index.js"; -import { Keystore } from "./keystore/index.js"; +import { RLNCredentialsManager } from "./credentials_manager.js"; import type { DecryptedCredentials, EncryptedCredentials } from "./keystore/index.js"; -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"; const log = new Logger("waku:rln"); -async function loadWitnessCalculator(): Promise { - try { - const url = new URL("./resources/rln.wasm", import.meta.url); - const response = await fetch(url); - - if (!response.ok) { - throw new Error( - `Failed to fetch witness calculator: ${response.status} ${response.statusText}` - ); - } - - return await wc.builder( - new Uint8Array(await response.arrayBuffer()), - false - ); - } catch (error) { - log.error("Error loading witness calculator:", error); - throw new Error( - `Failed to load witness calculator: ${error instanceof Error ? error.message : String(error)}` - ); - } -} - -async function loadZkey(): Promise { - try { - const url = new URL("./resources/rln_final.zkey", import.meta.url); - const response = await fetch(url); - - if (!response.ok) { - throw new Error( - `Failed to fetch zkey: ${response.status} ${response.statusText}` - ); - } - - return new Uint8Array(await response.arrayBuffer()); - } catch (error) { - log.error("Error loading zkey:", error); - throw new Error( - `Failed to load zkey: ${error instanceof Error ? error.message : String(error)}` - ); - } -} - -/** - * Create an instance of RLN - * @returns RLNInstance - */ -export async function create(): Promise { - try { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - await (init as any)?.(); - zerokitRLN.init_panic_hook(); - - const witnessCalculator = await loadWitnessCalculator(); - const zkey = await loadZkey(); - - const stringEncoder = new TextEncoder(); - const vkey = stringEncoder.encode(JSON.stringify(verificationKey)); - - const DEPTH = 20; - const zkRLN = zerokitRLN.newRLN(DEPTH, zkey, vkey); - const zerokit = new Zerokit(zkRLN, witnessCalculator, DEFAULT_RATE_LIMIT); - - return new RLNInstance(zerokit); - } catch (error) { - log.error("Failed to initialize RLN:", error); - throw error; - } -} - type WakuRLNEncoderOptions = WakuEncoderOptions & { credentials: EncryptedCredentials | DecryptedCredentials; }; -export class RLNInstance { - private started = false; - private starting = false; - - private _contract: undefined | RLNContract; - private _signer: undefined | ethers.Signer; - - private keystore = Keystore.create(); - private _credentials: undefined | DecryptedCredentials; - - public constructor(public zerokit: Zerokit) {} - - public get contract(): undefined | RLNContract { - return this._contract; - } - - public get signer(): undefined | ethers.Signer { - return this._signer; - } - - public async start(options: StartRLNOptions = {}): Promise { - if (this.started || this.starting) { - return; - } - - this.starting = true; - - try { - const { credentials, keystore } = - await RLNInstance.decryptCredentialsIfNeeded(options.credentials); - const { signer, address, rateLimit } = await this.determineStartOptions( - options, - credentials - ); - - if (keystore) { - this.keystore = keystore; - } - - this._credentials = credentials; - this._signer = signer!; - this._contract = await RLNContract.init(this, { - address: address!, - signer: signer!, - rateLimit: rateLimit ?? this.zerokit.getRateLimit - }); - this.started = true; - } finally { - this.starting = false; - } - } - - private async determineStartOptions( - options: StartRLNOptions, - credentials: KeystoreEntity | undefined - ): Promise { - let chainId = credentials?.membership.chainId; - const address = - credentials?.membership.address || - options.address || - LINEA_CONTRACT.address; - - if (address === LINEA_CONTRACT.address) { - chainId = LINEA_CONTRACT.chainId; - } - - const signer = options.signer || (await extractMetaMaskSigner()); - const currentChainId = await signer.getChainId(); - - if (chainId && chainId !== currentChainId) { - throw Error( - `Failed to start RLN contract, chain ID of contract is different from current one: contract-${chainId}, current network-${currentChainId}` - ); - } - - return { - signer, - address - }; - } - - private static async decryptCredentialsIfNeeded( - credentials?: EncryptedCredentials | DecryptedCredentials - ): Promise<{ credentials?: DecryptedCredentials; keystore?: Keystore }> { - if (!credentials) { - return {}; - } - - if ("identity" in credentials) { - return { credentials }; - } - - const keystore = Keystore.fromString(credentials.keystore); - - if (!keystore) { - return {}; - } - - const decryptedCredentials = await keystore.readCredential( - credentials.id, - credentials.password - ); - - return { - keystore, - credentials: decryptedCredentials - }; - } - - public async registerMembership( - options: RegisterMembershipOptions - ): Promise { - if (!this.contract) { - throw Error("RLN Contract is not initialized."); - } - - let identity = "identity" in options && options.identity; - - if ("signature" in options) { - identity = this.zerokit.generateSeededIdentityCredential( - options.signature - ); - } - - if (!identity) { - throw Error("Missing signature or identity to register membership."); - } - - return this.contract.registerWithIdentity(identity); - } - +export class RLNInstance extends RLNCredentialsManager { /** - * Changes credentials in use by relying on provided Keystore earlier in rln.start - * @param id: string, hash of credentials to select from Keystore - * @param password: string or bytes to use to decrypt credentials from Keystore + * Create an instance of RLN + * @returns RLNInstance */ - public async useCredentials(id: string, password: Password): Promise { - this._credentials = await this.keystore?.readCredential(id, password); + public static async create(): Promise { + try { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + await (init as any)?.(); + zerokitRLN.init_panic_hook(); + + const witnessCalculator = await RLNInstance.loadWitnessCalculator(); + const zkey = await RLNInstance.loadZkey(); + + const stringEncoder = new TextEncoder(); + const vkey = stringEncoder.encode(JSON.stringify(verificationKey)); + + const DEPTH = 20; + const zkRLN = zerokitRLN.newRLN(DEPTH, zkey, vkey); + const zerokit = new Zerokit(zkRLN, witnessCalculator, DEFAULT_RATE_LIMIT); + + return new RLNInstance(zerokit); + } catch (error) { + log.error("Failed to initialize RLN:", error); + throw error; + } + } + private constructor(public zerokit: Zerokit) { + super(zerokit); } public async createEncoder( @@ -251,7 +67,7 @@ export class RLNInstance { ): Promise { const { credentials: decryptedCredentials } = await RLNInstance.decryptCredentialsIfNeeded(options.credentials); - const credentials = decryptedCredentials || this._credentials; + const credentials = decryptedCredentials || this.credentials; if (!credentials) { throw Error( @@ -269,33 +85,6 @@ export class RLNInstance { }); } - private async verifyCredentialsAgainstContract( - credentials: KeystoreEntity - ): Promise { - if (!this._contract) { - throw Error( - "Failed to verify chain coordinates: no contract initialized." - ); - } - - const registryAddress = credentials.membership.address; - const currentRegistryAddress = this._contract.address; - if (registryAddress !== currentRegistryAddress) { - throw Error( - `Failed to verify chain coordinates: credentials contract address=${registryAddress} is not equal to registryContract address=${currentRegistryAddress}` - ); - } - - const chainId = credentials.membership.chainId; - const network = await this._contract.provider.getNetwork(); - const currentChainId = network.chainId; - if (chainId !== currentChainId) { - throw Error( - `Failed to verify chain coordinates: credentials chainID=${chainId} is not equal to registryContract chainID=${currentChainId}` - ); - } - } - public createDecoder( contentTopic: ContentTopic ): RLNDecoder { @@ -304,4 +93,47 @@ export class RLNInstance { decoder: createDecoder(contentTopic) }); } + + public static async loadWitnessCalculator(): Promise { + try { + const url = new URL("./resources/rln.wasm", import.meta.url); + const response = await fetch(url); + + if (!response.ok) { + throw new Error( + `Failed to fetch witness calculator: ${response.status} ${response.statusText}` + ); + } + + return await wc.builder( + new Uint8Array(await response.arrayBuffer()), + false + ); + } catch (error) { + log.error("Error loading witness calculator:", error); + throw new Error( + `Failed to load witness calculator: ${error instanceof Error ? error.message : String(error)}` + ); + } + } + + public static async loadZkey(): Promise { + try { + const url = new URL("./resources/rln_final.zkey", import.meta.url); + const response = await fetch(url); + + if (!response.ok) { + throw new Error( + `Failed to fetch zkey: ${response.status} ${response.statusText}` + ); + } + + return new Uint8Array(await response.arrayBuffer()); + } catch (error) { + log.error("Error loading zkey:", error); + throw new Error( + `Failed to load zkey: ${error instanceof Error ? error.message : String(error)}` + ); + } + } }