diff --git a/src/codec.ts b/src/codec.ts index 0ca235a..8c36933 100644 --- a/src/codec.ts +++ b/src/codec.ts @@ -1,18 +1,22 @@ import debug from "debug"; import { DecodedMessage, Decoder, Encoder, proto } from "@waku/core/lib/message/version_0"; +import { IEncoder, IDecoder, IDecodedMessage, IProtoMessage, IMessage } from "@waku/interfaces"; +import { WakuMessage } from "@waku/proto"; import { HandshakeResult, HandshakeStepResult } from "./handshake.js"; import { PayloadV2 } from "./payload.js"; const log = debug("waku:message:noise-codec"); +const OneMillion = BigInt(1_000_000); + // WakuMessage version for noise protocol const version = 2; /** * Used internally in the pairing object to represent a handshake message */ -export class NoiseHandshakeMessage extends DecodedMessage { +export class NoiseHandshakeMessage extends DecodedMessage implements IDecodedMessage { get payloadV2(): PayloadV2 { if (!this.payload) throw new Error("no payload available"); return PayloadV2.deserialize(this.payload); @@ -23,13 +27,38 @@ export class NoiseHandshakeMessage extends DecodedMessage { * Used in the pairing object for encoding the messages exchanged * during the handshake process */ -export class NoiseHandshakeEncoder extends Encoder { +export class NoiseHandshakeEncoder implements IEncoder { /** * @param contentTopic content topic on which the encoded WakuMessages will be sent * @param hsStepResult the result of a step executed while performing the handshake process + * @param ephemeral makes messages ephemeral in the Waku network */ - constructor(public contentTopic: string, private hsStepResult: HandshakeStepResult) { - super(contentTopic); + constructor(public contentTopic: string, private hsStepResult: HandshakeStepResult, public ephemeral: boolean = true) {} + + async toWire(message: IMessage): Promise { + return this.encode(message); + } + + async toProtoObj(message: IMessage): Promise { + return this.encodeProto(message); + } + + async encode(message: IMessage): Promise { + const protoMessage = await this.encodeProto(message); + if (!protoMessage) return; + return WakuMessage.encode(protoMessage); + } + + async encodeProto(message: IMessage): Promise { + const timestamp = message.timestamp ?? new Date(); + return { + ephemeral: this.ephemeral, + rateLimitProof: undefined, + payload: this.hsStepResult.payload2.serialize(), + version: version, + contentTopic: this.contentTopic, + timestamp: BigInt(timestamp.valueOf()) * OneMillion, + }; } } @@ -37,15 +66,27 @@ export class NoiseHandshakeEncoder extends Encoder { * Used in the pairing object for decoding the messages exchanged * during the handshake process */ -export class NoiseHandshakeDecoder extends Decoder { +export class NoiseHandshakeDecoder implements IDecoder { /** * @param contentTopic content topic on which the encoded WakuMessages were sent */ - constructor(public contentTopic: string) { - super(contentTopic); + constructor(public contentTopic: string) {} + + fromProtoObj(proto: IProtoMessage): Promise { + return this.decode(proto); + } + + fromWireToProtoObj(bytes: Uint8Array): Promise { + return this.decodeProto(bytes); } - async fromProtoObj(proto: proto.WakuMessage): Promise { + decodeProto(bytes: Uint8Array): Promise { + const protoMessage = WakuMessage.decode(bytes); + log("Message decoded", protoMessage); + return Promise.resolve(protoMessage as IProtoMessage); + } + + async decode(proto: IProtoMessage): Promise { // https://github.com/status-im/js-waku/issues/921 if (proto.version === undefined) { proto.version = 0; @@ -69,7 +110,18 @@ export class NoiseHandshakeDecoder extends Decoder { * Represents a secure message. These are messages that are transmitted * after a successful handshake is performed. */ -export class NoiseSecureMessage extends DecodedMessage {} +export class NoiseSecureMessage extends DecodedMessage implements IDecodedMessage { + private readonly _decodedPayload: Uint8Array; + + constructor(proto: WakuMessage, decodedPayload: Uint8Array) { + super(proto); + this._decodedPayload = decodedPayload; + } + + get payload(): Uint8Array { + return this._decodedPayload; + } +} /** * js-waku encoder for secure messages. After a handshake is successful, a @@ -77,13 +129,46 @@ export class NoiseSecureMessage extends DecodedMessage {} * codec will be encrypted with the cipherstates and message nametags that were * created after a handshake is complete */ -export class NoiseSecureTransferEncoder extends Encoder { +export class NoiseSecureTransferEncoder implements IEncoder { /** * @param contentTopic content topic on which the encoded WakuMessages were sent * @param hsResult handshake result obtained after the handshake is successful */ - constructor(public contentTopic: string, private hsResult: HandshakeResult) { - super(contentTopic); + constructor(public contentTopic: string, private hsResult: HandshakeResult, public ephemeral: boolean = true) {} + + toWire(message: IMessage): Promise { + return this.encode(message); + } + + toProtoObj(message: IMessage): Promise { + return this.encodeProto(message); + } + + async encode(message: IMessage): Promise { + const protoMessage = await this.encodeProto(message); + if (!protoMessage) return; + return WakuMessage.encode(protoMessage); + } + + async encodeProto(message: IMessage): Promise { + const timestamp = message.timestamp ?? new Date(); + if (!message.payload) { + log("No payload to encrypt, skipping: ", message); + return; + } + + const preparedPayload = this.hsResult.writeMessage(message.payload); + + const payload = preparedPayload.serialize(); + + return { + payload, + rateLimitProof: undefined, + ephemeral: this.ephemeral, + version: version, + contentTopic: this.contentTopic, + timestamp: BigInt(timestamp.valueOf()) * OneMillion, + }; } } @@ -93,35 +178,44 @@ export class NoiseSecureTransferEncoder extends Encoder { * messages with the cipherstates and message nametags that were created after a * handshake is complete */ -export class NoiseSecureTransferDecoder extends Decoder { +export class NoiseSecureTransferDecoder implements IDecoder { /** * @param contentTopic content topic on which the encoded WakuMessages were sent * @param hsResult handshake result obtained after the handshake is successful */ - constructor(public contentTopic: string, private hsResult: HandshakeResult) { - super(contentTopic); + constructor(public contentTopic: string, private hsResult: HandshakeResult) {} + + fromProtoObj(proto: IProtoMessage): Promise { + return this.decode(proto); } - async fromProtoObj(proto: proto.WakuMessage): Promise { + fromWireToProtoObj(bytes: Uint8Array): Promise { + return this.decodeProto(bytes); + } + + decodeProto(bytes: Uint8Array): Promise { + const protoMessage = WakuMessage.decode(bytes); + log("Message decoded", protoMessage); + return Promise.resolve(protoMessage as IProtoMessage); + } + + async decode(proto: IProtoMessage): Promise { // https://github.com/status-im/js-waku/issues/921 if (proto.version === undefined) { proto.version = 0; } - if (proto.version !== version) { log("Failed to decode due to incorrect version, expected:", version, ", actual:", proto.version); return Promise.resolve(undefined); } - if (!proto.payload) { log("No payload, skipping: ", proto); return; } - try { const payloadV2 = PayloadV2.deserialize(proto.payload); const decryptedPayload = this.hsResult.readMessage(payloadV2); - return new NoiseSecureMessage(proto); + return new NoiseSecureMessage(proto, decryptedPayload); } catch (err) { log("could not decode message ", err); return;