import type { IDecodedMessage, IDecoder, IEncoder, IMessage, IProtoMessage, IRateLimitProof, IRoutingInfo } from "@waku/interfaces"; import { Logger } from "@waku/utils"; import type { IdentityCredential } from "./identity.js"; import { RlnMessage, toRLNSignal } from "./message.js"; import { RLNInstance } from "./rln.js"; const log = new Logger("rln:encoder"); export class RLNEncoder implements IEncoder { private readonly idSecretHash: Uint8Array; public constructor( private readonly encoder: IEncoder, private readonly rlnInstance: RLNInstance, private readonly index: number, identityCredential: IdentityCredential ) { if (index < 0) throw new Error("Invalid membership index"); this.idSecretHash = identityCredential.IDSecretHash; } public async toWire(message: IMessage): Promise { message.rateLimitProof = await this.generateProof(message); log.info("Proof generated", message.rateLimitProof); return this.encoder.toWire(message); } public async toProtoObj( message: IMessage ): Promise { const protoMessage = await this.encoder.toProtoObj(message); if (!protoMessage) return; protoMessage.contentTopic = this.contentTopic; protoMessage.rateLimitProof = await this.generateProof(message); log.info("Proof generated", protoMessage.rateLimitProof); return protoMessage; } private async generateProof(message: IMessage): Promise { const signal = toRLNSignal(this.contentTopic, message); return this.rlnInstance.zerokit.generateRLNProof( signal, this.index, message.timestamp, this.idSecretHash ); } public get pubsubTopic(): string { return this.encoder.pubsubTopic; } public get routingInfo(): IRoutingInfo { return this.encoder.routingInfo; } public get contentTopic(): string { return this.encoder.contentTopic; } public get ephemeral(): boolean { return this.encoder.ephemeral; } } type RLNEncoderOptions = { encoder: IEncoder; rlnInstance: RLNInstance; index: number; credential: IdentityCredential; }; export const createRLNEncoder = (options: RLNEncoderOptions): RLNEncoder => { return new RLNEncoder( options.encoder, options.rlnInstance, options.index, options.credential ); }; export class RLNDecoder implements IDecoder> { public constructor( private readonly rlnInstance: RLNInstance, private readonly decoder: IDecoder ) {} public get pubsubTopic(): string { return this.decoder.pubsubTopic; } public get contentTopic(): string { return this.decoder.contentTopic; } public fromWireToProtoObj( bytes: Uint8Array ): Promise { const protoMessage = this.decoder.fromWireToProtoObj(bytes); log.info("Message decoded", protoMessage); return Promise.resolve(protoMessage); } public async fromProtoObj( pubsubTopic: string, proto: IProtoMessage ): Promise | undefined> { const msg: T | undefined = await this.decoder.fromProtoObj( pubsubTopic, proto ); if (!msg) return; return new RlnMessage(this.rlnInstance, msg, proto.rateLimitProof); } } type RLNDecoderOptions = { decoder: IDecoder; rlnInstance: RLNInstance; }; export const createRLNDecoder = ( options: RLNDecoderOptions ): RLNDecoder => { return new RLNDecoder(options.rlnInstance, options.decoder); };