Skip to content

Commit

Permalink
feat!: enable validation of meta field
Browse files Browse the repository at this point in the history
  • Loading branch information
fryorcraken committed Mar 13, 2023
1 parent 702d3dd commit 2dcd16f
Show file tree
Hide file tree
Showing 9 changed files with 394 additions and 17 deletions.
4 changes: 4 additions & 0 deletions packages/core/src/lib/message/topic_only_message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<TopicOnlyMessage> {
Expand Down
100 changes: 100 additions & 0 deletions packages/core/src/lib/message/version_0.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
)
);
});
});
30 changes: 24 additions & 6 deletions packages/core/src/lib/message/version_0.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IMetaSetter } from "@waku/interfaces";
import { IMetaSetter, IMetaValidator } from "@waku/interfaces";
import type {
EncoderOptions,
IDecodedMessage,
Expand All @@ -11,14 +11,20 @@ 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);

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);
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -117,7 +127,10 @@ export function createEncoder({
}

export class Decoder implements IDecoder<DecodedMessage> {
constructor(public contentTopic: string) {}
constructor(
public contentTopic: string,
protected metaValidator?: IMetaValidator
) {}

fromWireToProtoObj(bytes: Uint8Array): Promise<IProtoMessage | undefined> {
const protoMessage = proto.WakuMessage.decode(bytes);
Expand Down Expand Up @@ -149,7 +162,8 @@ export class Decoder implements IDecoder<DecodedMessage> {
return Promise.resolve(undefined);
}

return new DecodedMessage(pubSubTopic, proto);
const metaValidator = this.metaValidator ?? (() => true);
return new DecodedMessage(pubSubTopic, proto, metaValidator);
}
}

Expand All @@ -163,7 +177,11 @@ export class Decoder implements IDecoder<DecodedMessage> {
* 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);
}
12 changes: 12 additions & 0 deletions packages/interfaces/src/message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,25 @@ export interface IEncoder {
toProtoObj: (message: IMessage) => Promise<IProtoMessage | undefined>;
}

export interface IMetaValidator {
/**
* Used to validate the `meta` field of a message.
*/
(pubSubTopic: string, message: IProtoMessage): boolean;
}

export interface IDecodedMessage {
payload: Uint8Array;
contentTopic: string;
pubSubTopic: string;
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<T extends IDecodedMessage> {
Expand Down
5 changes: 3 additions & 2 deletions packages/message-encryption/src/decoded_message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;
}

Expand Down
113 changes: 113 additions & 0 deletions packages/message-encryption/src/ecies.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { DecodedMessage } from "@waku/core";
import { IProtoMessage } from "@waku/interfaces";
import { expect } from "chai";
import fc from "fast-check";
Expand Down Expand Up @@ -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;
}
)
);
});
});
Loading

0 comments on commit 2dcd16f

Please sign in to comment.