From 4f18b886c9bb40f5cc6ea59e93f25cb9bf1b2fa2 Mon Sep 17 00:00:00 2001 From: "fryorcraken.eth" Date: Tue, 9 May 2023 16:27:00 +1000 Subject: [PATCH] feat: enable meta field to be set Ref: https://github.com/waku-org/js-waku/issues/1208 --- package-lock.json | 4 +-- src/codec.ts | 24 ++++++++++--- src/pairing.spec.ts | 82 ++++++++++++++++++++++++++++++++++++++++++++- src/pairing.ts | 26 ++++++++++---- 4 files changed, 122 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5d8bf83..9e37049 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@waku/noise", - "version": "0.0.2", + "version": "0.0.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@waku/noise", - "version": "0.0.2", + "version": "0.0.3", "license": "Apache-2.0 OR MIT", "dependencies": { "@stablelib/chacha20poly1305": "^1.0.1", diff --git a/src/codec.ts b/src/codec.ts index ec8c70b..9ce5f31 100644 --- a/src/codec.ts +++ b/src/codec.ts @@ -1,5 +1,5 @@ import { DecodedMessage } from "@waku/core/lib/message/version_0"; -import type { IDecodedMessage, IDecoder, IEncoder, IMessage, IProtoMessage } from "@waku/interfaces"; +import type { IDecodedMessage, IDecoder, IEncoder, IMessage, IMetaSetter, IProtoMessage } from "@waku/interfaces"; import { WakuMessage } from "@waku/proto"; import debug from "debug"; @@ -120,10 +120,17 @@ export class NoiseSecureMessage extends DecodedMessage implements IDecodedMessag */ 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 + * @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. */ - constructor(public contentTopic: string, private hsResult: HandshakeResult, public ephemeral: boolean = true) {} + constructor( + public contentTopic: string, + private hsResult: HandshakeResult, + public ephemeral: boolean = true, + public metaSetter?: IMetaSetter + ) {} async toWire(message: IMessage): Promise { const protoMessage = await this.toProtoObj(message); @@ -142,7 +149,7 @@ export class NoiseSecureTransferEncoder implements IEncoder { const payload = preparedPayload.serialize(); - return { + const protoMessage = { payload, rateLimitProof: undefined, ephemeral: this.ephemeral, @@ -151,6 +158,13 @@ export class NoiseSecureTransferEncoder implements IEncoder { contentTopic: this.contentTopic, timestamp: BigInt(timestamp.valueOf()) * OneMillion, }; + + if (this.metaSetter) { + const meta = this.metaSetter(protoMessage); + return { ...protoMessage, meta }; + } + + return protoMessage; } } diff --git a/src/pairing.spec.ts b/src/pairing.spec.ts index 571f574..ce7e444 100644 --- a/src/pairing.spec.ts +++ b/src/pairing.spec.ts @@ -1,6 +1,6 @@ import { HMACDRBG } from "@stablelib/hmac-drbg"; import { randomBytes } from "@stablelib/random"; -import type { IDecoder, IEncoder, IMessage, ISender } from "@waku/interfaces"; +import type { IDecoder, IEncoder, IMessage, IProtoMessage, ISender } from "@waku/interfaces"; import { expect } from "chai"; import { EventEmitter } from "eventemitter3"; import { pEvent } from "p-event"; @@ -13,6 +13,15 @@ import { ResponderParameters, WakuPairing } from "./pairing"; const PUBSUB_TOPIC = "default"; +const EMPTY_PROTOMESSAGE = { + timestamp: undefined, + contentTopic: "", + ephemeral: undefined, + meta: undefined, + rateLimitProof: undefined, + version: undefined, +}; + describe("js-noise: pairing object", () => { const rng = new HMACDRBG(); @@ -117,4 +126,75 @@ describe("js-noise: pairing object", () => { expect(message).to.be.equals("pairing has timed out"); } }); + + it("pairs and `meta` field is encoded", async function () { + const bobStaticKey = generateX25519KeyPair(); + const aliceStaticKey = generateX25519KeyPair(); + + // Encode the length of the payload + // Not a relevant real life example + const metaSetter = (msg: IProtoMessage & { meta: undefined }): Uint8Array => { + const buffer = new ArrayBuffer(4); + const view = new DataView(buffer); + view.setUint32(0, msg.payload.length, false); + return new Uint8Array(buffer); + }; + + const recvParameters = new ResponderParameters(); + const bobPairingObj = new WakuPairing(sender, responder, bobStaticKey, recvParameters, undefined, { metaSetter }); + const bobExecP1 = bobPairingObj.execute(); + + // Confirmation is done by manually + confirmAuthCodeFlow(bobPairingObj, true); + + const initParameters = bobPairingObj.getPairingInfo(); + const alicePairingObj = new WakuPairing(sender, responder, aliceStaticKey, initParameters, undefined, { + metaSetter, + }); + const aliceExecP1 = alicePairingObj.execute(); + + // Confirmation is done manually + confirmAuthCodeFlow(alicePairingObj, true); + + const [bobCodecs, aliceCodecs] = await Promise.all([bobExecP1, aliceExecP1]); + + const bobEncoder = bobCodecs[0]; + const bobDecoder = bobCodecs[1]; + const aliceEncoder = aliceCodecs[0]; + const aliceDecoder = aliceCodecs[1]; + + // We test read/write of random messages exchanged between Alice and Bob + // Note that we exchange more than the number of messages contained in the nametag buffer to test if they are filled correctly as the communication proceeds + for (let i = 0; i < 10 * MessageNametagBufferSize; i++) { + // Alice writes to Bob + let message = randomBytes(32, rng); + let encodedMsg = await aliceEncoder.toWire({ payload: message }); + let readMessageProto = await bobDecoder.fromWireToProtoObj(encodedMsg!); + let readMessage = await bobDecoder.fromProtoObj(PUBSUB_TOPIC, readMessageProto!); + + expect(uint8ArrayEquals(message, readMessage!.payload)).to.be.true; + + let expectedMeta = metaSetter({ + ...EMPTY_PROTOMESSAGE, + payload: readMessageProto!.payload, + }); + + expect(readMessage!.meta).to.deep.eq(expectedMeta); + + // Bob writes to Alice + message = randomBytes(32, rng); + encodedMsg = await bobEncoder.toWire({ payload: message }); + readMessageProto = await aliceDecoder.fromWireToProtoObj(encodedMsg!); + readMessage = await aliceDecoder.fromProtoObj(PUBSUB_TOPIC, readMessageProto!); + + expect(uint8ArrayEquals(message, readMessage!.payload)).to.be.true; + + expectedMeta = metaSetter({ + ...EMPTY_PROTOMESSAGE, + payload: readMessageProto!.payload, + }); + + expect(readMessage!.meta).to.deep.eq(expectedMeta); + } + }); }); diff --git a/src/pairing.ts b/src/pairing.ts index aa51003..1648dee 100644 --- a/src/pairing.ts +++ b/src/pairing.ts @@ -1,6 +1,6 @@ import { HMACDRBG } from "@stablelib/hmac-drbg"; import { randomBytes } from "@stablelib/random"; -import type { IDecoder, ISender } from "@waku/interfaces"; +import type { IDecoder, IMetaSetter, ISender } from "@waku/interfaces"; import debug from "debug"; import { EventEmitter } from "eventemitter3"; import { pEvent } from "p-event"; @@ -55,6 +55,11 @@ function delay(ms: number): Promise { const rng = new HMACDRBG(); +export interface EncoderParameters { + ephemeral?: boolean; + metaSetter?: IMetaSetter; +} + /** * Initiator parameters used to setup the pairing object */ @@ -108,13 +113,15 @@ export class WakuPairing { * @param myStaticKey x25519 keypair * @param pairingParameters Pairing parameters (depending if this is the initiator or responder) * @param myEphemeralKey optional ephemeral key + * @param encoderParameters optional parameters for the resulting encoders */ constructor( private sender: ISender, private responder: Responder, private myStaticKey: KeyPair, pairingParameters: InitiatorParameters | ResponderParameters, - private myEphemeralKey: KeyPair = generateX25519KeyPair() + private myEphemeralKey: KeyPair = generateX25519KeyPair(), + private readonly encoderParameters: EncoderParameters = {} ) { this.randomFixLenVal = randomBytes(32, rng); this.myCommittedStaticKey = commitPublicKey(this.myStaticKey.publicKey, this.randomFixLenVal); @@ -293,7 +300,7 @@ export class WakuPairing { this.eventEmitter.emit("pairingComplete"); - return WakuPairing.getSecureCodec(this.contentTopic, this.handshakeResult); + return WakuPairing.getSecureCodec(this.contentTopic, this.handshakeResult, this.encoderParameters); } private async responderHandshake(): Promise<[NoiseSecureTransferEncoder, NoiseSecureTransferDecoder]> { @@ -350,7 +357,7 @@ export class WakuPairing { this.eventEmitter.emit("pairingComplete"); - return WakuPairing.getSecureCodec(this.contentTopic, this.handshakeResult); + return WakuPairing.getSecureCodec(this.contentTopic, this.handshakeResult, this.encoderParameters); } /** @@ -358,13 +365,20 @@ export class WakuPairing { * to continue a session using a stored hsResult * @param contentTopic Content topic for the waku messages * @param hsResult Noise Pairing result + * @param encoderParameters Parameters for the resulting encoder * @returns an array with [NoiseSecureTransferEncoder, NoiseSecureTransferDecoder] */ static getSecureCodec( contentTopic: string, - hsResult: HandshakeResult + hsResult: HandshakeResult, + encoderParameters: EncoderParameters ): [NoiseSecureTransferEncoder, NoiseSecureTransferDecoder] { - const secureEncoder = new NoiseSecureTransferEncoder(contentTopic, hsResult); + const secureEncoder = new NoiseSecureTransferEncoder( + contentTopic, + hsResult, + encoderParameters.ephemeral, + encoderParameters.metaSetter + ); const secureDecoder = new NoiseSecureTransferDecoder(contentTopic, hsResult); return [secureEncoder, secureDecoder];