From 23a18194edcdcddd4307b90a5326d91ce38058b4 Mon Sep 17 00:00:00 2001 From: "fryorcraken.eth" Date: Mon, 13 Mar 2023 14:15:57 +1100 Subject: [PATCH 1/3] chore: make topics arbitrary data --- .../core/src/lib/message/version_0.spec.ts | 77 ++++++++++--------- packages/message-encryption/src/ecies.spec.ts | 43 ++++++----- .../message-encryption/src/symmetric.spec.ts | 37 ++++----- 3 files changed, 80 insertions(+), 77 deletions(-) diff --git a/packages/core/src/lib/message/version_0.spec.ts b/packages/core/src/lib/message/version_0.spec.ts index a71fc84f21..14a5e5bc08 100644 --- a/packages/core/src/lib/message/version_0.spec.ts +++ b/packages/core/src/lib/message/version_0.spec.ts @@ -3,51 +3,58 @@ import fc from "fast-check"; import { createDecoder, createEncoder, DecodedMessage } from "./version_0.js"; -const TestContentTopic = "/test/1/waku-message/utf8"; -const TestPubSubTopic = "/test/pubsub/topic"; - describe("Waku Message version 0", function () { it("Round trip binary serialization", async function () { await fc.assert( - fc.asyncProperty(fc.uint8Array({ minLength: 1 }), async (payload) => { - const encoder = createEncoder({ - contentTopic: TestContentTopic, - }); - const bytes = await encoder.toWire({ payload }); - const decoder = createDecoder(TestContentTopic); - const protoResult = await decoder.fromWireToProtoObj(bytes); - const result = (await decoder.fromProtoObj( - TestPubSubTopic, - protoResult! - )) as DecodedMessage; + fc.asyncProperty( + fc.string(), + fc.string(), + fc.uint8Array({ minLength: 1 }), + async (contentTopic, pubSubTopic, payload) => { + const encoder = createEncoder({ + contentTopic, + }); + const bytes = await encoder.toWire({ payload }); + const decoder = createDecoder(contentTopic); + const protoResult = await decoder.fromWireToProtoObj(bytes); + const result = (await decoder.fromProtoObj( + pubSubTopic, + protoResult! + )) as DecodedMessage; - expect(result.contentTopic).to.eq(TestContentTopic); - expect(result.pubSubTopic).to.eq(TestPubSubTopic); - expect(result.version).to.eq(0); - expect(result.ephemeral).to.be.false; - expect(result.payload).to.deep.eq(payload); - expect(result.timestamp).to.not.be.undefined; - }) + expect(result.contentTopic).to.eq(contentTopic); + expect(result.pubSubTopic).to.eq(pubSubTopic); + expect(result.version).to.eq(0); + expect(result.ephemeral).to.be.false; + expect(result.payload).to.deep.eq(payload); + expect(result.timestamp).to.not.be.undefined; + } + ) ); }); it("Ephemeral field set to true", async function () { await fc.assert( - fc.asyncProperty(fc.uint8Array({ minLength: 1 }), async (payload) => { - const encoder = createEncoder({ - contentTopic: TestContentTopic, - ephemeral: true, - }); - const bytes = await encoder.toWire({ payload }); - const decoder = createDecoder(TestContentTopic); - const protoResult = await decoder.fromWireToProtoObj(bytes); - const result = (await decoder.fromProtoObj( - TestPubSubTopic, - protoResult! - )) as DecodedMessage; + fc.asyncProperty( + fc.string(), + fc.string(), + fc.uint8Array({ minLength: 1 }), + async (contentTopic, pubSubTopic, payload) => { + const encoder = createEncoder({ + contentTopic, + ephemeral: true, + }); + const bytes = await encoder.toWire({ payload }); + const decoder = createDecoder(contentTopic); + const protoResult = await decoder.fromWireToProtoObj(bytes); + const result = (await decoder.fromProtoObj( + pubSubTopic, + protoResult! + )) as DecodedMessage; - expect(result.ephemeral).to.be.true; - }) + expect(result.ephemeral).to.be.true; + } + ) ); }); }); diff --git a/packages/message-encryption/src/ecies.spec.ts b/packages/message-encryption/src/ecies.spec.ts index fb2f969d99..71d68dc87b 100644 --- a/packages/message-encryption/src/ecies.spec.ts +++ b/packages/message-encryption/src/ecies.spec.ts @@ -4,35 +4,31 @@ import fc from "fast-check"; import { getPublicKey } from "./crypto/index.js"; import { createDecoder, createEncoder } from "./ecies.js"; -const TestContentTopic = "/test/1/waku-message/utf8"; -const TestPubSubTopic = "/test/pubsub/topic"; - describe("Ecies Encryption", function () { it("Round trip binary encryption [ecies, no signature]", async function () { await fc.assert( fc.asyncProperty( + fc.string(), + fc.string(), fc.uint8Array({ minLength: 1 }), fc.uint8Array({ min: 1, minLength: 32, maxLength: 32 }), - async (payload, privateKey) => { + async (pubSubTopic, contentTopic, payload, privateKey) => { const publicKey = getPublicKey(privateKey); const encoder = createEncoder({ - contentTopic: TestContentTopic, + contentTopic, publicKey, }); const bytes = await encoder.toWire({ payload }); - const decoder = createDecoder(TestContentTopic, privateKey); + const decoder = createDecoder(contentTopic, privateKey); const protoResult = await decoder.fromWireToProtoObj(bytes!); if (!protoResult) throw "Failed to proto decode"; - const result = await decoder.fromProtoObj( - TestPubSubTopic, - protoResult - ); + const result = await decoder.fromProtoObj(pubSubTopic, protoResult); if (!result) throw "Failed to decode"; - expect(result.contentTopic).to.equal(TestContentTopic); - expect(result.pubSubTopic).to.equal(TestPubSubTopic); + expect(result.contentTopic).to.equal(contentTopic); + expect(result.pubSubTopic).to.equal(pubSubTopic); expect(result.version).to.equal(1); expect(result?.payload).to.deep.equal(payload); expect(result.signature).to.be.undefined; @@ -47,31 +43,36 @@ describe("Ecies Encryption", function () { await fc.assert( fc.asyncProperty( + fc.string(), + fc.string(), fc.uint8Array({ minLength: 1 }), fc.uint8Array({ min: 1, minLength: 32, maxLength: 32 }), fc.uint8Array({ min: 1, minLength: 32, maxLength: 32 }), - async (payload, alicePrivateKey, bobPrivateKey) => { + async ( + pubSubTopic, + contentTopic, + payload, + alicePrivateKey, + bobPrivateKey + ) => { const alicePublicKey = getPublicKey(alicePrivateKey); const bobPublicKey = getPublicKey(bobPrivateKey); const encoder = createEncoder({ - contentTopic: TestContentTopic, + contentTopic, publicKey: bobPublicKey, sigPrivKey: alicePrivateKey, }); const bytes = await encoder.toWire({ payload }); - const decoder = createDecoder(TestContentTopic, bobPrivateKey); + const decoder = createDecoder(contentTopic, bobPrivateKey); const protoResult = await decoder.fromWireToProtoObj(bytes!); if (!protoResult) throw "Failed to proto decode"; - const result = await decoder.fromProtoObj( - TestPubSubTopic, - protoResult - ); + const result = await decoder.fromProtoObj(pubSubTopic, protoResult); if (!result) throw "Failed to decode"; - expect(result.contentTopic).to.equal(TestContentTopic); - expect(result.pubSubTopic).to.equal(TestPubSubTopic); + expect(result.contentTopic).to.equal(contentTopic); + expect(result.pubSubTopic).to.equal(pubSubTopic); expect(result.version).to.equal(1); expect(result?.payload).to.deep.equal(payload); expect(result.signature).to.not.be.undefined; diff --git a/packages/message-encryption/src/symmetric.spec.ts b/packages/message-encryption/src/symmetric.spec.ts index 477848477f..837c88fa10 100644 --- a/packages/message-encryption/src/symmetric.spec.ts +++ b/packages/message-encryption/src/symmetric.spec.ts @@ -4,33 +4,29 @@ import fc from "fast-check"; import { getPublicKey } from "./crypto/index.js"; import { createDecoder, createEncoder } from "./symmetric.js"; -const TestContentTopic = "/test/1/waku-message/utf8"; -const TestPubSubTopic = "/test/pubsub/topic"; - describe("Symmetric Encryption", function () { it("Round trip binary encryption [symmetric, no signature]", async function () { await fc.assert( fc.asyncProperty( + fc.string(), + fc.string(), fc.uint8Array({ minLength: 1 }), fc.uint8Array({ min: 1, minLength: 32, maxLength: 32 }), - async (payload, symKey) => { + async (pubSubTopic, contentTopic, payload, symKey) => { const encoder = createEncoder({ - contentTopic: TestContentTopic, + contentTopic, symKey, }); const bytes = await encoder.toWire({ payload }); - const decoder = createDecoder(TestContentTopic, symKey); + const decoder = createDecoder(contentTopic, symKey); const protoResult = await decoder.fromWireToProtoObj(bytes!); if (!protoResult) throw "Failed to proto decode"; - const result = await decoder.fromProtoObj( - TestPubSubTopic, - protoResult - ); + const result = await decoder.fromProtoObj(pubSubTopic, protoResult); if (!result) throw "Failed to decode"; - expect(result.contentTopic).to.equal(TestContentTopic); - expect(result.pubSubTopic).to.equal(TestPubSubTopic); + expect(result.contentTopic).to.equal(contentTopic); + expect(result.pubSubTopic).to.equal(pubSubTopic); expect(result.version).to.equal(1); expect(result?.payload).to.deep.equal(payload); expect(result.signature).to.be.undefined; @@ -43,30 +39,29 @@ describe("Symmetric Encryption", function () { it("Round trip binary encryption [symmetric, signature]", async function () { await fc.assert( fc.asyncProperty( + fc.string(), + fc.string(), fc.uint8Array({ minLength: 1 }), fc.uint8Array({ min: 1, minLength: 32, maxLength: 32 }), fc.uint8Array({ min: 1, minLength: 32, maxLength: 32 }), - async (payload, sigPrivKey, symKey) => { + async (pubSubTopic, contentTopic, payload, sigPrivKey, symKey) => { const sigPubKey = getPublicKey(sigPrivKey); const encoder = createEncoder({ - contentTopic: TestContentTopic, + contentTopic, symKey, sigPrivKey, }); const bytes = await encoder.toWire({ payload }); - const decoder = createDecoder(TestContentTopic, symKey); + const decoder = createDecoder(contentTopic, symKey); const protoResult = await decoder.fromWireToProtoObj(bytes!); if (!protoResult) throw "Failed to proto decode"; - const result = await decoder.fromProtoObj( - TestPubSubTopic, - protoResult - ); + const result = await decoder.fromProtoObj(pubSubTopic, protoResult); if (!result) throw "Failed to decode"; - expect(result.contentTopic).to.equal(TestContentTopic); - expect(result.pubSubTopic).to.equal(TestPubSubTopic); + expect(result.contentTopic).to.equal(contentTopic); + expect(result.pubSubTopic).to.equal(pubSubTopic); expect(result.version).to.equal(1); expect(result?.payload).to.deep.equal(payload); expect(result.signature).to.not.be.undefined; From 702d3ddf34c822015c33870277cdacf23c9cb890 Mon Sep 17 00:00:00 2001 From: "fryorcraken.eth" Date: Fri, 10 Mar 2023 14:41:07 +1100 Subject: [PATCH 2/3] feat!: enable encoding of `meta` field --- .../src/lib/message/topic_only_message.ts | 2 + .../core/src/lib/message/version_0.spec.ts | 48 +++++++++++++++++++ packages/core/src/lib/message/version_0.ts | 27 +++++++++-- packages/core/src/lib/to_proto_message.ts | 1 + packages/interfaces/src/message.ts | 11 +++++ packages/message-encryption/src/ecies.spec.ts | 48 +++++++++++++++++++ packages/message-encryption/src/ecies.ts | 23 +++++++-- .../message-encryption/src/symmetric.spec.ts | 47 ++++++++++++++++++ packages/message-encryption/src/symmetric.ts | 18 +++++-- packages/proto/src/lib/filter.ts | 9 ++++ packages/proto/src/lib/light_push.ts | 9 ++++ packages/proto/src/lib/message.proto | 1 + packages/proto/src/lib/message.ts | 9 ++++ packages/proto/src/lib/store.ts | 9 ++++ 14 files changed, 252 insertions(+), 10 deletions(-) diff --git a/packages/core/src/lib/message/topic_only_message.ts b/packages/core/src/lib/message/topic_only_message.ts index 6bf90b3db4..fee30680aa 100644 --- a/packages/core/src/lib/message/topic_only_message.ts +++ b/packages/core/src/lib/message/topic_only_message.ts @@ -12,6 +12,7 @@ export class TopicOnlyMessage implements IDecodedMessage { public payload: Uint8Array = new Uint8Array(); public rateLimitProof: undefined; public timestamp: undefined; + public meta: undefined; public ephemeral: undefined; constructor( @@ -35,6 +36,7 @@ export class TopicOnlyDecoder implements IDecoder { payload: new Uint8Array(), rateLimitProof: undefined, timestamp: undefined, + meta: undefined, version: undefined, ephemeral: undefined, }); diff --git a/packages/core/src/lib/message/version_0.spec.ts b/packages/core/src/lib/message/version_0.spec.ts index 14a5e5bc08..e1158fe0a9 100644 --- a/packages/core/src/lib/message/version_0.spec.ts +++ b/packages/core/src/lib/message/version_0.spec.ts @@ -1,3 +1,4 @@ +import type { IProtoMessage } from "@waku/interfaces"; import { expect } from "chai"; import fc from "fast-check"; @@ -57,4 +58,51 @@ describe("Waku Message version 0", function () { ) ); }); + + it("Meta field set when metaSetter is specified", async function () { + await fc.assert( + fc.asyncProperty( + fc.string(), + fc.string(), + fc.uint8Array({ minLength: 1 }), + async (contentTopic, pubSubTopic, payload) => { + // 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 encoder = createEncoder({ + contentTopic, + ephemeral: true, + metaSetter, + }); + const bytes = await encoder.toWire({ payload }); + const decoder = createDecoder(contentTopic); + const protoResult = await decoder.fromWireToProtoObj(bytes); + const result = (await decoder.fromProtoObj( + pubSubTopic, + protoResult! + )) as DecodedMessage; + + const expectedMeta = metaSetter({ + payload, + timestamp: undefined, + contentTopic: "", + ephemeral: undefined, + meta: undefined, + rateLimitProof: undefined, + version: undefined, + }); + + expect(result.meta).to.deep.eq(expectedMeta); + } + ) + ); + }); }); diff --git a/packages/core/src/lib/message/version_0.ts b/packages/core/src/lib/message/version_0.ts index 38bd22fa26..0a5376eec0 100644 --- a/packages/core/src/lib/message/version_0.ts +++ b/packages/core/src/lib/message/version_0.ts @@ -1,3 +1,4 @@ +import { IMetaSetter } from "@waku/interfaces"; import type { EncoderOptions, IDecodedMessage, @@ -50,6 +51,10 @@ export class DecodedMessage implements IDecodedMessage { } } + get meta(): Uint8Array | undefined { + return this.proto.meta; + } + get version(): number { // https://rfc.vac.dev/spec/14/ // > If omitted, the value SHOULD be interpreted as version 0. @@ -62,7 +67,11 @@ export class DecodedMessage implements IDecodedMessage { } export class Encoder implements IEncoder { - constructor(public contentTopic: string, public ephemeral: boolean = false) {} + constructor( + public contentTopic: string, + public ephemeral: boolean = false, + public metaSetter?: IMetaSetter + ) {} async toWire(message: IMessage): Promise { return proto.WakuMessage.encode(await this.toProtoObj(message)); @@ -71,14 +80,22 @@ export class Encoder implements IEncoder { async toProtoObj(message: IMessage): Promise { const timestamp = message.timestamp ?? new Date(); - return { + const protoMessage = { payload: message.payload, version: Version, contentTopic: this.contentTopic, timestamp: BigInt(timestamp.valueOf()) * OneMillion, + meta: undefined, rateLimitProof: message.rateLimitProof, ephemeral: this.ephemeral, }; + + if (this.metaSetter) { + const meta = this.metaSetter(protoMessage); + return { ...protoMessage, meta }; + } + + return protoMessage; } } @@ -94,8 +111,9 @@ export class Encoder implements IEncoder { export function createEncoder({ contentTopic, ephemeral, + metaSetter, }: EncoderOptions): Encoder { - return new Encoder(contentTopic, ephemeral); + return new Encoder(contentTopic, ephemeral, metaSetter); } export class Decoder implements IDecoder { @@ -109,6 +127,7 @@ export class Decoder implements IDecoder { contentTopic: protoMessage.contentTopic, version: protoMessage.version ?? undefined, timestamp: protoMessage.timestamp ?? undefined, + meta: protoMessage.meta ?? undefined, rateLimitProof: protoMessage.rateLimitProof ?? undefined, ephemeral: protoMessage.ephemeral ?? false, }); @@ -135,7 +154,7 @@ export class Decoder implements IDecoder { } /** - * Creates an decoder that decode messages without Waku level encryption. + * Creates a decoder that decode messages without Waku level encryption. * * A decoder is used to decode messages from the [14/WAKU2-MESSAGE](https://rfc.vac.dev/spec/14/) * format when received from the Waku network. The resulting decoder can then be diff --git a/packages/core/src/lib/to_proto_message.ts b/packages/core/src/lib/to_proto_message.ts index c9d968a1b9..102d14c409 100644 --- a/packages/core/src/lib/to_proto_message.ts +++ b/packages/core/src/lib/to_proto_message.ts @@ -6,6 +6,7 @@ const EmptyMessage: IProtoMessage = { contentTopic: "", version: undefined, timestamp: undefined, + meta: undefined, rateLimitProof: undefined, ephemeral: undefined, }; diff --git a/packages/interfaces/src/message.ts b/packages/interfaces/src/message.ts index d2e52f04f0..c16d700109 100644 --- a/packages/interfaces/src/message.ts +++ b/packages/interfaces/src/message.ts @@ -17,6 +17,7 @@ export interface IProtoMessage { contentTopic: string; version: number | undefined; timestamp: bigint | undefined; + meta: Uint8Array | undefined; rateLimitProof: IRateLimitProof | undefined; ephemeral: boolean | undefined; } @@ -30,6 +31,10 @@ export interface IMessage { rateLimitProof?: IRateLimitProof; } +export interface IMetaSetter { + (message: IProtoMessage & { meta: undefined }): Uint8Array; +} + export interface EncoderOptions { /** The content topic to set on outgoing messages. */ contentTopic: string; @@ -38,6 +43,12 @@ export interface EncoderOptions { * @defaultValue `false` */ ephemeral?: boolean; + /** + * A function called when encoding messages to set the meta field. + * @param IProtoMessage The message encoded for wire, without the meta field. + * If encryption is used, `metaSetter` only accesses _encrypted_ payload. + */ + metaSetter?: IMetaSetter; } export interface IEncoder { diff --git a/packages/message-encryption/src/ecies.spec.ts b/packages/message-encryption/src/ecies.spec.ts index 71d68dc87b..698ecddeb0 100644 --- a/packages/message-encryption/src/ecies.spec.ts +++ b/packages/message-encryption/src/ecies.spec.ts @@ -1,3 +1,4 @@ +import { IProtoMessage } from "@waku/interfaces"; import { expect } from "chai"; import fc from "fast-check"; @@ -81,4 +82,51 @@ describe("Ecies Encryption", function () { ) ); }); + + it("Check meta is set [ecies]", async function () { + await fc.assert( + fc.asyncProperty( + fc.string(), + fc.string(), + fc.uint8Array({ minLength: 1 }), + fc.uint8Array({ min: 1, minLength: 32, maxLength: 32 }), + async (pubSubTopic, contentTopic, payload, privateKey) => { + const publicKey = getPublicKey(privateKey); + 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 encoder = createEncoder({ + contentTopic, + publicKey, + metaSetter, + }); + const bytes = await encoder.toWire({ payload }); + + const decoder = createDecoder(contentTopic, privateKey); + const protoResult = await decoder.fromWireToProtoObj(bytes!); + if (!protoResult) throw "Failed to proto decode"; + const result = await decoder.fromProtoObj(pubSubTopic, protoResult); + if (!result) throw "Failed to decode"; + + const expectedMeta = metaSetter({ + payload: protoResult.payload, + timestamp: undefined, + contentTopic: "", + ephemeral: undefined, + meta: undefined, + rateLimitProof: undefined, + version: undefined, + }); + + expect(result.meta).to.deep.equal(expectedMeta); + } + ) + ); + }); }); diff --git a/packages/message-encryption/src/ecies.ts b/packages/message-encryption/src/ecies.ts index c991db9c69..98d241e6e2 100644 --- a/packages/message-encryption/src/ecies.ts +++ b/packages/message-encryption/src/ecies.ts @@ -1,4 +1,5 @@ import { Decoder as DecoderV0 } from "@waku/core/lib/message/version_0"; +import { IMetaSetter } from "@waku/interfaces"; import type { EncoderOptions as BaseEncoderOptions, IDecoder, @@ -34,7 +35,8 @@ class Encoder implements IEncoder { public contentTopic: string, private publicKey: Uint8Array, private sigPrivKey?: Uint8Array, - public ephemeral: boolean = false + public ephemeral: boolean = false, + public metaSetter?: IMetaSetter ) {} async toWire(message: IMessage): Promise { @@ -50,14 +52,22 @@ class Encoder implements IEncoder { const payload = await encryptAsymmetric(preparedPayload, this.publicKey); - return { + const protoMessage = { payload, version: Version, contentTopic: this.contentTopic, timestamp: BigInt(timestamp.valueOf()) * OneMillion, + meta: undefined, rateLimitProof: message.rateLimitProof, ephemeral: this.ephemeral, }; + + if (this.metaSetter) { + const meta = this.metaSetter(protoMessage); + return { ...protoMessage, meta }; + } + + return protoMessage; } } @@ -85,8 +95,15 @@ export function createEncoder({ publicKey, sigPrivKey, ephemeral = false, + metaSetter, }: EncoderOptions): Encoder { - return new Encoder(contentTopic, publicKey, sigPrivKey, ephemeral); + return new Encoder( + contentTopic, + publicKey, + sigPrivKey, + ephemeral, + metaSetter + ); } class Decoder extends DecoderV0 implements IDecoder { diff --git a/packages/message-encryption/src/symmetric.spec.ts b/packages/message-encryption/src/symmetric.spec.ts index 837c88fa10..cf724d2fa0 100644 --- a/packages/message-encryption/src/symmetric.spec.ts +++ b/packages/message-encryption/src/symmetric.spec.ts @@ -1,3 +1,4 @@ +import { IProtoMessage } from "@waku/interfaces"; import { expect } from "chai"; import fc from "fast-check"; @@ -70,4 +71,50 @@ describe("Symmetric Encryption", function () { ) ); }); + + it("Check meta is set [symmetric]", async function () { + await fc.assert( + fc.asyncProperty( + fc.string(), + fc.string(), + fc.uint8Array({ minLength: 1 }), + fc.uint8Array({ min: 1, minLength: 32, maxLength: 32 }), + async (pubSubTopic, contentTopic, payload, symKey) => { + 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 encoder = createEncoder({ + contentTopic, + symKey, + metaSetter, + }); + const bytes = await encoder.toWire({ payload }); + + const decoder = createDecoder(contentTopic, symKey); + const protoResult = await decoder.fromWireToProtoObj(bytes!); + if (!protoResult) throw "Failed to proto decode"; + const result = await decoder.fromProtoObj(pubSubTopic, protoResult); + if (!result) throw "Failed to decode"; + + const expectedMeta = metaSetter({ + payload: protoResult.payload, + timestamp: undefined, + contentTopic: "", + ephemeral: undefined, + meta: undefined, + rateLimitProof: undefined, + version: undefined, + }); + + expect(result.meta).to.deep.equal(expectedMeta); + } + ) + ); + }); }); diff --git a/packages/message-encryption/src/symmetric.ts b/packages/message-encryption/src/symmetric.ts index 6e18301b7f..53bf05ee38 100644 --- a/packages/message-encryption/src/symmetric.ts +++ b/packages/message-encryption/src/symmetric.ts @@ -4,6 +4,7 @@ import type { IDecoder, IEncoder, IMessage, + IMetaSetter, IProtoMessage, } from "@waku/interfaces"; import { WakuMessage } from "@waku/proto"; @@ -29,7 +30,8 @@ class Encoder implements IEncoder { public contentTopic: string, private symKey: Uint8Array, private sigPrivKey?: Uint8Array, - public ephemeral: boolean = false + public ephemeral: boolean = false, + public metaSetter?: IMetaSetter ) {} async toWire(message: IMessage): Promise { @@ -44,14 +46,23 @@ class Encoder implements IEncoder { const preparedPayload = await preCipher(message.payload, this.sigPrivKey); const payload = await encryptSymmetric(preparedPayload, this.symKey); - return { + + const protoMessage = { payload, version: Version, contentTopic: this.contentTopic, timestamp: BigInt(timestamp.valueOf()) * OneMillion, + meta: undefined, rateLimitProof: message.rateLimitProof, ephemeral: this.ephemeral, }; + + if (this.metaSetter) { + const meta = this.metaSetter(protoMessage); + return { ...protoMessage, meta }; + } + + return protoMessage; } } @@ -80,8 +91,9 @@ export function createEncoder({ symKey, sigPrivKey, ephemeral = false, + metaSetter, }: EncoderOptions): Encoder { - return new Encoder(contentTopic, symKey, sigPrivKey, ephemeral); + return new Encoder(contentTopic, symKey, sigPrivKey, ephemeral, metaSetter); } class Decoder extends DecoderV0 implements IDecoder { diff --git a/packages/proto/src/lib/filter.ts b/packages/proto/src/lib/filter.ts index 13659c7d8e..1445132018 100644 --- a/packages/proto/src/lib/filter.ts +++ b/packages/proto/src/lib/filter.ts @@ -430,6 +430,7 @@ export interface WakuMessage { contentTopic: string; version?: number; timestamp?: bigint; + meta?: Uint8Array; rateLimitProof?: RateLimitProof; ephemeral?: boolean; } @@ -465,6 +466,11 @@ export namespace WakuMessage { w.sint64(obj.timestamp); } + if (obj.meta != null) { + w.uint32(90); + w.bytes(obj.meta); + } + if (obj.rateLimitProof != null) { w.uint32(170); RateLimitProof.codec().encode(obj.rateLimitProof, w); @@ -503,6 +509,9 @@ export namespace WakuMessage { case 10: obj.timestamp = reader.sint64(); break; + case 11: + obj.meta = reader.bytes(); + break; case 21: obj.rateLimitProof = RateLimitProof.codec().decode( reader, diff --git a/packages/proto/src/lib/light_push.ts b/packages/proto/src/lib/light_push.ts index de186f2603..4a1f609b50 100644 --- a/packages/proto/src/lib/light_push.ts +++ b/packages/proto/src/lib/light_push.ts @@ -362,6 +362,7 @@ export interface WakuMessage { contentTopic: string; version?: number; timestamp?: bigint; + meta?: Uint8Array; rateLimitProof?: RateLimitProof; ephemeral?: boolean; } @@ -397,6 +398,11 @@ export namespace WakuMessage { w.sint64(obj.timestamp); } + if (obj.meta != null) { + w.uint32(90); + w.bytes(obj.meta); + } + if (obj.rateLimitProof != null) { w.uint32(170); RateLimitProof.codec().encode(obj.rateLimitProof, w); @@ -435,6 +441,9 @@ export namespace WakuMessage { case 10: obj.timestamp = reader.sint64(); break; + case 11: + obj.meta = reader.bytes(); + break; case 21: obj.rateLimitProof = RateLimitProof.codec().decode( reader, diff --git a/packages/proto/src/lib/message.proto b/packages/proto/src/lib/message.proto index 93c573f4d1..dcb9bac151 100644 --- a/packages/proto/src/lib/message.proto +++ b/packages/proto/src/lib/message.proto @@ -17,6 +17,7 @@ message WakuMessage { string content_topic = 2; optional uint32 version = 3; optional sint64 timestamp = 10; + optional bytes meta = 11; optional RateLimitProof rate_limit_proof = 21; optional bool ephemeral = 31; } diff --git a/packages/proto/src/lib/message.ts b/packages/proto/src/lib/message.ts index a724b7222a..db22724f26 100644 --- a/packages/proto/src/lib/message.ts +++ b/packages/proto/src/lib/message.ts @@ -134,6 +134,7 @@ export interface WakuMessage { contentTopic: string; version?: number; timestamp?: bigint; + meta?: Uint8Array; rateLimitProof?: RateLimitProof; ephemeral?: boolean; } @@ -169,6 +170,11 @@ export namespace WakuMessage { w.sint64(obj.timestamp); } + if (obj.meta != null) { + w.uint32(90); + w.bytes(obj.meta); + } + if (obj.rateLimitProof != null) { w.uint32(170); RateLimitProof.codec().encode(obj.rateLimitProof, w); @@ -207,6 +213,9 @@ export namespace WakuMessage { case 10: obj.timestamp = reader.sint64(); break; + case 11: + obj.meta = reader.bytes(); + break; case 21: obj.rateLimitProof = RateLimitProof.codec().decode( reader, diff --git a/packages/proto/src/lib/store.ts b/packages/proto/src/lib/store.ts index 6d9a663501..881d6d2d58 100644 --- a/packages/proto/src/lib/store.ts +++ b/packages/proto/src/lib/store.ts @@ -676,6 +676,7 @@ export interface WakuMessage { contentTopic: string; version?: number; timestamp?: bigint; + meta?: Uint8Array; rateLimitProof?: RateLimitProof; ephemeral?: boolean; } @@ -711,6 +712,11 @@ export namespace WakuMessage { w.sint64(obj.timestamp); } + if (obj.meta != null) { + w.uint32(90); + w.bytes(obj.meta); + } + if (obj.rateLimitProof != null) { w.uint32(170); RateLimitProof.codec().encode(obj.rateLimitProof, w); @@ -749,6 +755,9 @@ export namespace WakuMessage { case 10: obj.timestamp = reader.sint64(); break; + case 11: + obj.meta = reader.bytes(); + break; case 21: obj.rateLimitProof = RateLimitProof.codec().decode( reader, From 2dcd16f6f5bc28ce30e06c5beb4a5c5a740c8cf4 Mon Sep 17 00:00:00 2001 From: "fryorcraken.eth" Date: Mon, 13 Mar 2023 14:09:59 +1100 Subject: [PATCH 3/3] feat!: enable validation of `meta` field --- .../src/lib/message/topic_only_message.ts | 4 + .../core/src/lib/message/version_0.spec.ts | 100 ++++++++++++++++ packages/core/src/lib/message/version_0.ts | 30 ++++- packages/interfaces/src/message.ts | 12 ++ .../message-encryption/src/decoded_message.ts | 5 +- packages/message-encryption/src/ecies.spec.ts | 113 ++++++++++++++++++ packages/message-encryption/src/ecies.ts | 21 +++- .../message-encryption/src/symmetric.spec.ts | 106 ++++++++++++++++ packages/message-encryption/src/symmetric.ts | 20 +++- 9 files changed, 394 insertions(+), 17 deletions(-) diff --git a/packages/core/src/lib/message/topic_only_message.ts b/packages/core/src/lib/message/topic_only_message.ts index fee30680aa..18e8ff96a1 100644 --- a/packages/core/src/lib/message/topic_only_message.ts +++ b/packages/core/src/lib/message/topic_only_message.ts @@ -23,6 +23,10 @@ export class TopicOnlyMessage implements IDecodedMessage { get contentTopic(): string { return this.proto.contentTopic; } + + isMetaValid(): boolean { + return true; + } } export class TopicOnlyDecoder implements IDecoder { diff --git a/packages/core/src/lib/message/version_0.spec.ts b/packages/core/src/lib/message/version_0.spec.ts index e1158fe0a9..275a45995a 100644 --- a/packages/core/src/lib/message/version_0.spec.ts +++ b/packages/core/src/lib/message/version_0.spec.ts @@ -105,4 +105,104 @@ describe("Waku Message version 0", function () { ) ); }); + + it("isMetaValid returns true when no validator specified", async function () { + await fc.assert( + fc.asyncProperty( + fc.string(), + fc.string(), + fc.uint8Array({ minLength: 1 }), + async (pubSubTopic, contentTopic, payload) => { + const encoder = createEncoder({ + contentTopic, + }); + const bytes = await encoder.toWire({ payload }); + const decoder = createDecoder(contentTopic); + const protoResult = await decoder.fromWireToProtoObj(bytes); + const result = (await decoder.fromProtoObj( + pubSubTopic, + protoResult! + )) as DecodedMessage; + + expect(result.isMetaValid()).to.be.true; + } + ) + ); + }); + + it("isMetaValid returns false when validator specified returns false", async function () { + await fc.assert( + fc.asyncProperty( + fc.string(), + fc.string(), + fc.uint8Array({ minLength: 1 }), + async (pubSubTopic, contentTopic, payload) => { + const encoder = createEncoder({ + contentTopic, + }); + const decoder = createDecoder(contentTopic, () => false); + + const bytes = await encoder.toWire({ payload }); + const protoResult = await decoder.fromWireToProtoObj(bytes); + const result = (await decoder.fromProtoObj( + pubSubTopic, + protoResult! + )) as DecodedMessage; + + expect(result.isMetaValid()).to.be.false; + } + ) + ); + }); + + it("isMetaValid returns true when matching meta setter", async function () { + await fc.assert( + fc.asyncProperty( + fc.string(), + fc.string(), + fc.uint8Array({ minLength: 1 }), + async (pubSubTopic, contentTopic, payload) => { + const metaSetter = ( + msg: IProtoMessage & { meta: undefined } + ): Uint8Array => { + const buffer = new ArrayBuffer(4); + const view = new DataView(buffer); + view.setUint32(0, msg.payload.length); + return new Uint8Array(buffer); + }; + + const encoder = createEncoder({ + contentTopic, + metaSetter, + }); + + const metaValidator = ( + _pubSubTopic: string, + message: IProtoMessage + ): boolean => { + if (!message.meta) return false; + + const view = new DataView( + message.meta.buffer, + message.meta.byteOffset, + 4 + ); + const metaInt = view.getUint32(0); + + return metaInt === message.payload.length; + }; + const decoder = createDecoder(contentTopic, metaValidator); + + const bytes = await encoder.toWire({ payload }); + const protoResult = await decoder.fromWireToProtoObj(bytes); + const result = (await decoder.fromProtoObj( + pubSubTopic, + protoResult! + )) as DecodedMessage; + + expect(result.isMetaValid()).to.be.true; + } + ) + ); + }); }); diff --git a/packages/core/src/lib/message/version_0.ts b/packages/core/src/lib/message/version_0.ts index 0a5376eec0..3aa0d46720 100644 --- a/packages/core/src/lib/message/version_0.ts +++ b/packages/core/src/lib/message/version_0.ts @@ -1,4 +1,4 @@ -import { IMetaSetter } from "@waku/interfaces"; +import { IMetaSetter, IMetaValidator } from "@waku/interfaces"; import type { EncoderOptions, IDecodedMessage, @@ -11,6 +11,8 @@ import type { import { proto_message as proto } from "@waku/proto"; import debug from "debug"; +import { toProtoMessage } from "../to_proto_message.js"; + const log = debug("waku:message:version-0"); const OneMillion = BigInt(1_000_000); @@ -18,7 +20,11 @@ export const Version = 0; export { proto }; export class DecodedMessage implements IDecodedMessage { - constructor(public pubSubTopic: string, protected proto: proto.WakuMessage) {} + constructor( + public pubSubTopic: string, + protected proto: proto.WakuMessage, + private metaValidator: IMetaValidator + ) {} get ephemeral(): boolean { return Boolean(this.proto.ephemeral); @@ -64,6 +70,10 @@ export class DecodedMessage implements IDecodedMessage { get rateLimitProof(): IRateLimitProof | undefined { return this.proto.rateLimitProof; } + + isMetaValid(): boolean { + return this.metaValidator(this.pubSubTopic, toProtoMessage(this.proto)); + } } export class Encoder implements IEncoder { @@ -117,7 +127,10 @@ export function createEncoder({ } export class Decoder implements IDecoder { - constructor(public contentTopic: string) {} + constructor( + public contentTopic: string, + protected metaValidator?: IMetaValidator + ) {} fromWireToProtoObj(bytes: Uint8Array): Promise { const protoMessage = proto.WakuMessage.decode(bytes); @@ -149,7 +162,8 @@ export class Decoder implements IDecoder { return Promise.resolve(undefined); } - return new DecodedMessage(pubSubTopic, proto); + const metaValidator = this.metaValidator ?? (() => true); + return new DecodedMessage(pubSubTopic, proto, metaValidator); } } @@ -163,7 +177,11 @@ export class Decoder implements IDecoder { * messages. * * @param contentTopic The resulting decoder will only decode messages with this content topic. + * @param metaValidator Validator to use to verify meta field. */ -export function createDecoder(contentTopic: string): Decoder { - return new Decoder(contentTopic); +export function createDecoder( + contentTopic: string, + metaValidator?: IMetaValidator +): Decoder { + return new Decoder(contentTopic, metaValidator); } diff --git a/packages/interfaces/src/message.ts b/packages/interfaces/src/message.ts index c16d700109..af77f3aaab 100644 --- a/packages/interfaces/src/message.ts +++ b/packages/interfaces/src/message.ts @@ -58,6 +58,13 @@ export interface IEncoder { toProtoObj: (message: IMessage) => Promise; } +export interface IMetaValidator { + /** + * Used to validate the `meta` field of a message. + */ + (pubSubTopic: string, message: IProtoMessage): boolean; +} + export interface IDecodedMessage { payload: Uint8Array; contentTopic: string; @@ -65,6 +72,11 @@ export interface IDecodedMessage { timestamp: Date | undefined; rateLimitProof: IRateLimitProof | undefined; ephemeral: boolean | undefined; + /** + * Calls the { @link @waku/interface.message.IMetaValidator } passed on the + * decoder. Returns true if no meta validator is passed. + */ + isMetaValid: () => boolean; } export interface IDecoder { diff --git a/packages/message-encryption/src/decoded_message.ts b/packages/message-encryption/src/decoded_message.ts index 72e1626419..7aabbbe2f7 100644 --- a/packages/message-encryption/src/decoded_message.ts +++ b/packages/message-encryption/src/decoded_message.ts @@ -2,7 +2,7 @@ import { DecodedMessage as DecodedMessageV0, proto, } from "@waku/core/lib/message/version_0"; -import type { IDecodedMessage } from "@waku/interfaces"; +import type { IDecodedMessage, IMetaValidator } from "@waku/interfaces"; export class DecodedMessage extends DecodedMessageV0 @@ -14,10 +14,11 @@ export class DecodedMessage pubSubTopic: string, proto: proto.WakuMessage, decodedPayload: Uint8Array, + metaValidator: IMetaValidator, public signature?: Uint8Array, public signaturePublicKey?: Uint8Array ) { - super(pubSubTopic, proto); + super(pubSubTopic, proto, metaValidator); this._decodedPayload = decodedPayload; } diff --git a/packages/message-encryption/src/ecies.spec.ts b/packages/message-encryption/src/ecies.spec.ts index 698ecddeb0..ec14e7b813 100644 --- a/packages/message-encryption/src/ecies.spec.ts +++ b/packages/message-encryption/src/ecies.spec.ts @@ -1,3 +1,4 @@ +import { DecodedMessage } from "@waku/core"; import { IProtoMessage } from "@waku/interfaces"; import { expect } from "chai"; import fc from "fast-check"; @@ -129,4 +130,116 @@ describe("Ecies Encryption", function () { ) ); }); + + it("isMetaValid returns true when no meta validator is specified [ecies]", async function () { + await fc.assert( + fc.asyncProperty( + fc.string(), + fc.string(), + fc.uint8Array({ minLength: 1 }), + fc.uint8Array({ min: 1, minLength: 32, maxLength: 32 }), + async (pubSubTopic, contentTopic, payload, privateKey) => { + const publicKey = getPublicKey(privateKey); + const encoder = createEncoder({ + contentTopic, + publicKey, + }); + const bytes = await encoder.toWire({ payload }); + + const decoder = createDecoder(contentTopic, privateKey); + const protoResult = await decoder.fromWireToProtoObj(bytes!); + if (!protoResult) throw "Failed to proto decode"; + const result = await decoder.fromProtoObj(pubSubTopic, protoResult); + if (!result) throw "Failed to decode"; + + expect(result.isMetaValid()).to.be.true; + } + ) + ); + }); + + it("isMetaValid returns false when validator specified returns false [ecies]", async function () { + await fc.assert( + fc.asyncProperty( + fc.string(), + fc.string(), + fc.uint8Array({ minLength: 1 }), + fc.uint8Array({ min: 1, minLength: 32, maxLength: 32 }), + async (pubSubTopic, contentTopic, payload, privateKey) => { + const publicKey = getPublicKey(privateKey); + const encoder = createEncoder({ + contentTopic, + publicKey, + }); + const bytes = await encoder.toWire({ payload }); + + const decoder = createDecoder(contentTopic, privateKey, () => false); + const protoResult = await decoder.fromWireToProtoObj(bytes!); + if (!protoResult) throw "Failed to proto decode"; + const result = await decoder.fromProtoObj(pubSubTopic, protoResult); + if (!result) throw "Failed to decode"; + + expect(result.isMetaValid()).to.be.false; + } + ) + ); + }); + + it("isMetaValid returns true when matching meta setter [ecies]", async function () { + await fc.assert( + fc.asyncProperty( + fc.string(), + fc.string(), + fc.uint8Array({ minLength: 1 }), + fc.uint8Array({ min: 1, minLength: 32, maxLength: 32 }), + async (pubSubTopic, contentTopic, payload, privateKey) => { + const publicKey = getPublicKey(privateKey); + const metaSetter = ( + msg: IProtoMessage & { meta: undefined } + ): Uint8Array => { + const buffer = new ArrayBuffer(4); + const view = new DataView(buffer); + view.setUint32(0, msg.payload.length); + return new Uint8Array(buffer); + }; + + const encoder = createEncoder({ + contentTopic, + publicKey, + metaSetter, + }); + + const metaValidator = ( + _pubSubTopic: string, + message: IProtoMessage + ): boolean => { + if (!message.meta) return false; + + const view = new DataView( + message.meta.buffer, + message.meta.byteOffset, + 4 + ); + const metaInt = view.getUint32(0); + + return metaInt === message.payload.length; + }; + const decoder = createDecoder( + contentTopic, + privateKey, + metaValidator + ); + + const bytes = await encoder.toWire({ payload }); + const protoResult = await decoder.fromWireToProtoObj(bytes!); + const result = (await decoder.fromProtoObj( + pubSubTopic, + protoResult! + )) as DecodedMessage; + + expect(result.isMetaValid()).to.be.true; + } + ) + ); + }); }); diff --git a/packages/message-encryption/src/ecies.ts b/packages/message-encryption/src/ecies.ts index 98d241e6e2..2b25ec7179 100644 --- a/packages/message-encryption/src/ecies.ts +++ b/packages/message-encryption/src/ecies.ts @@ -1,5 +1,5 @@ import { Decoder as DecoderV0 } from "@waku/core/lib/message/version_0"; -import { IMetaSetter } from "@waku/interfaces"; +import { IMetaSetter, IMetaValidator } from "@waku/interfaces"; import type { EncoderOptions as BaseEncoderOptions, IDecoder, @@ -107,8 +107,12 @@ export function createEncoder({ } class Decoder extends DecoderV0 implements IDecoder { - constructor(contentTopic: string, private privateKey: Uint8Array) { - super(contentTopic); + constructor( + contentTopic: string, + private privateKey: Uint8Array, + metaValidator?: IMetaValidator + ) { + super(contentTopic, metaValidator); } async fromProtoObj( @@ -152,10 +156,14 @@ class Decoder extends DecoderV0 implements IDecoder { } log("Message decrypted", protoMessage); + + const metaValidator = this.metaValidator ?? (() => true); + return new DecodedMessage( pubSubTopic, protoMessage, res.payload, + metaValidator, res.sig?.signature, res.sig?.publicKey ); @@ -174,10 +182,13 @@ class Decoder extends DecoderV0 implements IDecoder { * * @param contentTopic The resulting decoder will only decode messages with this content topic. * @param privateKey The private key used to decrypt the message. + * @param metaValidator function to validate the meta field. Available via + * { @link DecodedMessage.isMetaValid }. */ export function createDecoder( contentTopic: string, - privateKey: Uint8Array + privateKey: Uint8Array, + metaValidator?: IMetaValidator ): Decoder { - return new Decoder(contentTopic, privateKey); + return new Decoder(contentTopic, privateKey, metaValidator); } diff --git a/packages/message-encryption/src/symmetric.spec.ts b/packages/message-encryption/src/symmetric.spec.ts index cf724d2fa0..2fbd7bf914 100644 --- a/packages/message-encryption/src/symmetric.spec.ts +++ b/packages/message-encryption/src/symmetric.spec.ts @@ -1,3 +1,4 @@ +import { DecodedMessage } from "@waku/core"; import { IProtoMessage } from "@waku/interfaces"; import { expect } from "chai"; import fc from "fast-check"; @@ -117,4 +118,109 @@ describe("Symmetric Encryption", function () { ) ); }); + + it("isMetaValid returns true when no meta validator is specified [symmetric]", async function () { + await fc.assert( + fc.asyncProperty( + fc.string(), + fc.string(), + fc.uint8Array({ minLength: 1 }), + fc.uint8Array({ min: 1, minLength: 32, maxLength: 32 }), + async (pubSubTopic, contentTopic, payload, symKey) => { + const encoder = createEncoder({ + contentTopic, + symKey, + }); + const bytes = await encoder.toWire({ payload }); + + const decoder = createDecoder(contentTopic, symKey); + const protoResult = await decoder.fromWireToProtoObj(bytes!); + if (!protoResult) throw "Failed to proto decode"; + const result = await decoder.fromProtoObj(pubSubTopic, protoResult); + if (!result) throw "Failed to decode"; + + expect(result.isMetaValid()).to.be.true; + } + ) + ); + }); + + it("isMetaValid returns false when validator specified returns false [symmetric]", async function () { + await fc.assert( + fc.asyncProperty( + fc.string(), + fc.string(), + fc.uint8Array({ minLength: 1 }), + fc.uint8Array({ min: 1, minLength: 32, maxLength: 32 }), + async (pubSubTopic, contentTopic, payload, symKey) => { + const encoder = createEncoder({ + contentTopic, + symKey, + }); + const bytes = await encoder.toWire({ payload }); + + const decoder = createDecoder(contentTopic, symKey, () => false); + const protoResult = await decoder.fromWireToProtoObj(bytes!); + if (!protoResult) throw "Failed to proto decode"; + const result = await decoder.fromProtoObj(pubSubTopic, protoResult); + if (!result) throw "Failed to decode"; + + expect(result.isMetaValid()).to.be.false; + } + ) + ); + }); + + it("isMetaValid returns true when matching meta setter [symmetric]", async function () { + await fc.assert( + fc.asyncProperty( + fc.string(), + fc.string(), + fc.uint8Array({ minLength: 1 }), + fc.uint8Array({ min: 1, minLength: 32, maxLength: 32 }), + async (pubSubTopic, contentTopic, payload, symKey) => { + const metaSetter = ( + msg: IProtoMessage & { meta: undefined } + ): Uint8Array => { + const buffer = new ArrayBuffer(4); + const view = new DataView(buffer); + view.setUint32(0, msg.payload.length); + return new Uint8Array(buffer); + }; + + const encoder = createEncoder({ + contentTopic, + symKey, + metaSetter, + }); + + const metaValidator = ( + _pubSubTopic: string, + message: IProtoMessage + ): boolean => { + if (!message.meta) return false; + + const view = new DataView( + message.meta.buffer, + message.meta.byteOffset, + 4 + ); + const metaInt = view.getUint32(0); + + return metaInt === message.payload.length; + }; + const decoder = createDecoder(contentTopic, symKey, metaValidator); + + const bytes = await encoder.toWire({ payload }); + const protoResult = await decoder.fromWireToProtoObj(bytes!); + const result = (await decoder.fromProtoObj( + pubSubTopic, + protoResult! + )) as DecodedMessage; + + expect(result.isMetaValid()).to.be.true; + } + ) + ); + }); }); diff --git a/packages/message-encryption/src/symmetric.ts b/packages/message-encryption/src/symmetric.ts index 53bf05ee38..78ff56f144 100644 --- a/packages/message-encryption/src/symmetric.ts +++ b/packages/message-encryption/src/symmetric.ts @@ -1,4 +1,5 @@ import { Decoder as DecoderV0 } from "@waku/core/lib/message/version_0"; +import { IMetaValidator } from "@waku/interfaces"; import type { EncoderOptions as BaseEncoderOptions, IDecoder, @@ -97,8 +98,12 @@ export function createEncoder({ } class Decoder extends DecoderV0 implements IDecoder { - constructor(contentTopic: string, private symKey: Uint8Array) { - super(contentTopic); + constructor( + contentTopic: string, + private symKey: Uint8Array, + metaValidator?: IMetaValidator + ) { + super(contentTopic, metaValidator); } async fromProtoObj( @@ -142,10 +147,14 @@ class Decoder extends DecoderV0 implements IDecoder { } log("Message decrypted", protoMessage); + + const metaValidator = this.metaValidator ?? (() => true); + return new DecodedMessage( pubSubTopic, protoMessage, res.payload, + metaValidator, res.sig?.signature, res.sig?.publicKey ); @@ -164,10 +173,13 @@ class Decoder extends DecoderV0 implements IDecoder { * * @param contentTopic The resulting decoder will only decode messages with this content topic. * @param symKey The symmetric key used to decrypt the message. + * @param metaValidator function to validate the meta field. Available via + * { @link DecodedMessage.isMetaValid }. */ export function createDecoder( contentTopic: string, - symKey: Uint8Array + symKey: Uint8Array, + metaValidator?: IMetaValidator ): Decoder { - return new Decoder(contentTopic, symKey); + return new Decoder(contentTopic, symKey, metaValidator); }