UNPKG

@waku/rln

Version:

RLN (Rate Limiting Nullifier) implementation for Waku

141 lines (120 loc) 4.14 kB
import { createDecoder, createEncoder } from "@waku/core"; import type { ContentTopic, IDecodedMessage, EncoderOptions as WakuEncoderOptions } from "@waku/interfaces"; import { Logger } from "@waku/utils"; import init from "@waku/zerokit-rln-wasm"; import * as zerokitRLN from "@waku/zerokit-rln-wasm"; import { createRLNDecoder, createRLNEncoder, type RLNDecoder, type RLNEncoder } from "./codec.js"; import { DEFAULT_RATE_LIMIT } from "./contract/constants.js"; import { RLNCredentialsManager } from "./credentials_manager.js"; import type { DecryptedCredentials, EncryptedCredentials } from "./keystore/index.js"; import verificationKey from "./resources/verification_key"; import * as wc from "./resources/witness_calculator"; import { WitnessCalculator } from "./resources/witness_calculator"; import { Zerokit } from "./zerokit.js"; const log = new Logger("waku:rln"); type WakuRLNEncoderOptions = WakuEncoderOptions & { credentials: EncryptedCredentials | DecryptedCredentials; }; export class RLNInstance extends RLNCredentialsManager { /** * Create an instance of RLN * @returns RLNInstance */ public static async create(): Promise<RLNInstance> { 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( options: WakuRLNEncoderOptions ): Promise<RLNEncoder> { const { credentials: decryptedCredentials } = await RLNInstance.decryptCredentialsIfNeeded(options.credentials); const credentials = decryptedCredentials || this.credentials; if (!credentials) { throw Error( "Failed to create Encoder: missing RLN credentials. Use createRLNEncoder directly." ); } await this.verifyCredentialsAgainstContract(credentials); return createRLNEncoder({ encoder: createEncoder(options), rlnInstance: this, index: credentials.membership.treeIndex, credential: credentials.identity }); } public createDecoder( contentTopic: ContentTopic ): RLNDecoder<IDecodedMessage> { return createRLNDecoder({ rlnInstance: this, decoder: createDecoder(contentTopic) }); } public static async loadWitnessCalculator(): Promise<WitnessCalculator> { 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<Uint8Array> { 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)}` ); } } }