js-noise/src/codec.ts

239 lines
7.5 KiB
TypeScript
Raw Normal View History

2023-04-04 01:02:07 +02:00
import { DecodedMessage } from "@waku/core/lib/message/version_0";
2024-04-11 17:08:55 +02:00
import {
type IDecodedMessage,
type IDecoder,
type IEncoder,
type IMessage,
type IMetaSetter,
type IProtoMessage,
} from "@waku/interfaces";
import { WakuMessage } from "@waku/proto";
import { contentTopicToPubsubTopic } from "@waku/utils";
2023-01-31 01:07:57 +01:00
import debug from "debug";
2022-11-21 18:07:21 -04:00
2022-12-06 22:36:03 -04:00
import { HandshakeResult, HandshakeStepResult } from "./handshake.js";
import { PayloadV2 } from "./payload.js";
2022-11-21 18:07:21 -04:00
2022-12-16 16:05:35 -04:00
const log = debug("waku:message:noise-codec");
2022-11-21 18:07:21 -04:00
const OneMillion = BigInt(1_000_000);
2023-01-06 13:34:32 -04:00
// WakuMessage version for noise protocol
const version = 2;
2022-11-21 18:07:21 -04:00
2023-01-06 13:34:32 -04:00
/**
* Used internally in the pairing object to represent a handshake message
*/
2023-01-31 01:07:57 +01:00
export class NoiseHandshakeMessage extends DecodedMessage implements IDecodedMessage {
2023-01-06 13:34:32 -04:00
get payloadV2(): PayloadV2 {
if (!this.payload) throw new Error("no payload available");
return PayloadV2.deserialize(this.payload);
}
}
/**
* Used in the pairing object for encoding the messages exchanged
* during the handshake process
*/
export class NoiseHandshakeEncoder implements IEncoder {
2023-01-06 13:34:32 -04:00
/**
* @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
2023-01-06 13:34:32 -04:00
*/
pubsubTopic: string;
2024-04-11 17:08:55 +02:00
2023-01-31 01:07:57 +01:00
constructor(
public contentTopic: string,
private hsStepResult: HandshakeStepResult,
public ephemeral: boolean = true
) {
this.pubsubTopic = contentTopicToPubsubTopic(contentTopic);
}
async toWire(message: IMessage): Promise<Uint8Array | undefined> {
const protoMessage = await this.toProtoObj(message);
if (!protoMessage) return;
return WakuMessage.encode(protoMessage);
}
async toProtoObj(message: IMessage): Promise<IProtoMessage | undefined> {
const timestamp = message.timestamp ?? new Date();
return {
ephemeral: this.ephemeral,
rateLimitProof: undefined,
payload: this.hsStepResult.payload2.serialize(),
version: version,
2023-04-04 01:02:07 +02:00
meta: undefined,
contentTopic: this.contentTopic,
timestamp: BigInt(timestamp.valueOf()) * OneMillion,
};
2022-11-21 18:07:21 -04:00
}
}
2023-01-06 13:34:32 -04:00
/**
* Used in the pairing object for decoding the messages exchanged
* during the handshake process
*/
export class NoiseHandshakeDecoder implements IDecoder<NoiseHandshakeMessage> {
2023-01-06 13:34:32 -04:00
/**
* @param contentTopic content topic on which the encoded WakuMessages were sent
*/
pubsubTopic: string;
2024-04-11 17:08:55 +02:00
constructor(public contentTopic: string) {
this.pubsubTopic = contentTopicToPubsubTopic(contentTopic);
}
fromWireToProtoObj(bytes: Uint8Array): Promise<IProtoMessage | undefined> {
const protoMessage = WakuMessage.decode(bytes);
log("Message decoded", protoMessage);
return Promise.resolve(protoMessage as IProtoMessage);
}
2023-04-04 01:02:07 +02:00
async fromProtoObj(pubSubTopic: string, proto: IProtoMessage): Promise<NoiseHandshakeMessage | undefined> {
2022-11-21 18:07:21 -04:00
// https://github.com/status-im/js-waku/issues/921
if (proto.version === undefined) {
proto.version = 0;
}
2023-01-06 13:34:32 -04:00
if (proto.version !== version) {
log("Failed to decode due to incorrect version, expected:", version, ", actual:", proto.version);
2022-11-21 18:07:21 -04:00
return Promise.resolve(undefined);
}
if (!proto.payload) {
log("No payload, skipping: ", proto);
return;
}
2023-04-04 01:02:07 +02:00
return new NoiseHandshakeMessage(pubSubTopic, proto);
2022-11-21 18:07:21 -04:00
}
}
2023-01-06 13:34:32 -04:00
/**
* Represents a secure message. These are messages that are transmitted
* after a successful handshake is performed.
*/
export class NoiseSecureMessage extends DecodedMessage implements IDecodedMessage {
private readonly _decodedPayload: Uint8Array;
2024-04-11 17:08:55 +02:00
pubsubTopic: string;
2023-04-04 01:02:07 +02:00
constructor(pubSubTopic: string, proto: WakuMessage, decodedPayload: Uint8Array) {
super(pubSubTopic, proto);
this._decodedPayload = decodedPayload;
2024-04-11 17:08:55 +02:00
this.pubsubTopic = pubSubTopic;
}
get payload(): Uint8Array {
return this._decodedPayload;
}
}
2023-01-06 13:34:32 -04:00
/**
* js-waku encoder for secure messages. After a handshake is successful, a
* codec for encoding messages is generated. The messages encoded with this
* codec will be encrypted with the cipherstates and message nametags that were
* created after a handshake is complete
*/
export class NoiseSecureTransferEncoder implements IEncoder {
2023-01-06 13:34:32 -04:00
/**
* @param contentTopic content topic on which the encoded WakuMessages were sent.
* @param hsResult handshake result obtained after the handshake is successful.
* @param ephemeral whether messages should be tagged as ephemeral defaults to true.
* @param metaSetter callback function that set the `meta` field.
2023-01-06 13:34:32 -04:00
*/
pubsubTopic: string;
2024-04-11 17:08:55 +02:00
constructor(
public contentTopic: string,
private hsResult: HandshakeResult,
public ephemeral: boolean = true,
public metaSetter?: IMetaSetter
) {
this.pubsubTopic = contentTopicToPubsubTopic(contentTopic);
}
async toWire(message: IMessage): Promise<Uint8Array | undefined> {
const protoMessage = await this.toProtoObj(message);
if (!protoMessage) return;
return WakuMessage.encode(protoMessage);
}
async toProtoObj(message: IMessage): Promise<IProtoMessage | undefined> {
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();
const protoMessage = {
payload,
rateLimitProof: undefined,
ephemeral: this.ephemeral,
version: version,
2023-04-04 01:02:07 +02:00
meta: undefined,
contentTopic: this.contentTopic,
timestamp: BigInt(timestamp.valueOf()) * OneMillion,
};
if (this.metaSetter) {
const meta = this.metaSetter(protoMessage);
return { ...protoMessage, meta };
}
return protoMessage;
2022-11-21 18:07:21 -04:00
}
}
2023-01-06 13:34:32 -04:00
/**
* js-waku decoder for secure messages. After a handshake is successful, a codec
* for decoding messages is generated. This decoder will attempt to decrypt
* messages with the cipherstates and message nametags that were created after a
* handshake is complete
*/
export class NoiseSecureTransferDecoder implements IDecoder<NoiseSecureMessage> {
2023-01-06 13:34:32 -04:00
/**
* @param contentTopic content topic on which the encoded WakuMessages were sent
* @param hsResult handshake result obtained after the handshake is successful
*/
pubsubTopic: string;
2024-04-11 17:08:55 +02:00
constructor(public contentTopic: string, private hsResult: HandshakeResult) {
this.pubsubTopic = contentTopicToPubsubTopic(contentTopic);
}
fromWireToProtoObj(bytes: Uint8Array): Promise<IProtoMessage | undefined> {
const protoMessage = WakuMessage.decode(bytes);
log("Message decoded", protoMessage);
return Promise.resolve(protoMessage as IProtoMessage);
}
2023-04-04 01:02:07 +02:00
async fromProtoObj(pubSubTopic: string, proto: IProtoMessage): Promise<NoiseSecureMessage | undefined> {
2022-11-22 10:13:57 -04:00
// https://github.com/status-im/js-waku/issues/921
if (proto.version === undefined) {
proto.version = 0;
}
2023-01-06 13:34:32 -04:00
if (proto.version !== version) {
log("Failed to decode due to incorrect version, expected:", version, ", actual:", proto.version);
2022-11-22 10:13:57 -04:00
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);
2023-04-04 01:02:07 +02:00
return new NoiseSecureMessage(pubSubTopic, proto, decryptedPayload);
} catch (err) {
log("could not decode message ", err);
return;
}
2022-11-22 10:13:57 -04:00
}
2024-04-11 17:12:59 +02:00
}