mirror of https://github.com/waku-org/js-waku.git
fix(message-hash): account for `timestamp` (#1986)
* fix: message-hash algo + tests * rm: only * chore: move numberToBytes to utils package * chore: shorten implementation
This commit is contained in:
parent
1562f0fd6f
commit
73d4f19746
|
@ -6,63 +6,73 @@ import { messageHash } from "./index.js";
|
||||||
|
|
||||||
// https://rfc.vac.dev/spec/14/#test-vectors
|
// https://rfc.vac.dev/spec/14/#test-vectors
|
||||||
describe("RFC Test Vectors", () => {
|
describe("RFC Test Vectors", () => {
|
||||||
it("Waku message hash computation", () => {
|
it("Waku message hash computation (meta size of 12 bytes)", () => {
|
||||||
const expectedHash =
|
const expectedHash =
|
||||||
"4fdde1099c9f77f6dae8147b6b3179aba1fc8e14a7bf35203fc253ee479f135f";
|
"64cce733fed134e83da02b02c6f689814872b1a0ac97ea56b76095c3c72bfe05";
|
||||||
|
|
||||||
const pubsubTopic = "/waku/2/default-waku/proto";
|
const pubsubTopic = "/waku/2/default-waku/proto";
|
||||||
const message: IProtoMessage = {
|
const message: IProtoMessage = {
|
||||||
payload: hexToBytes("0x010203045445535405060708"),
|
payload: hexToBytes("0x010203045445535405060708"),
|
||||||
contentTopic: "/waku/2/default-content/proto",
|
contentTopic: "/waku/2/default-content/proto",
|
||||||
meta: hexToBytes("0x73757065722d736563726574"),
|
meta: hexToBytes("0x73757065722d736563726574"),
|
||||||
|
timestamp: BigInt("0x175789bfa23f8400"),
|
||||||
ephemeral: undefined,
|
ephemeral: undefined,
|
||||||
rateLimitProof: undefined,
|
rateLimitProof: undefined,
|
||||||
timestamp: undefined,
|
|
||||||
version: undefined
|
version: undefined
|
||||||
};
|
};
|
||||||
|
|
||||||
const hash = messageHash(pubsubTopic, message);
|
const hash = messageHash(pubsubTopic, message);
|
||||||
|
expect(bytesToHex(hash)).to.equal(expectedHash);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Waku message hash computation (meta size of 64 bytes)", () => {
|
||||||
|
const expectedHash =
|
||||||
|
"7158b6498753313368b9af8f6e0a0a05104f68f972981da42a43bc53fb0c1b27";
|
||||||
|
const pubsubTopic = "/waku/2/default-waku/proto";
|
||||||
|
const message: IProtoMessage = {
|
||||||
|
payload: hexToBytes("0x010203045445535405060708"),
|
||||||
|
contentTopic: "/waku/2/default-content/proto",
|
||||||
|
meta: hexToBytes(
|
||||||
|
"0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f"
|
||||||
|
),
|
||||||
|
timestamp: BigInt("0x175789bfa23f8400"),
|
||||||
|
ephemeral: undefined,
|
||||||
|
rateLimitProof: undefined,
|
||||||
|
version: undefined
|
||||||
|
};
|
||||||
|
const hash = messageHash(pubsubTopic, message);
|
||||||
expect(bytesToHex(hash)).to.equal(expectedHash);
|
expect(bytesToHex(hash)).to.equal(expectedHash);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Waku message hash computation (meta attribute not present)", () => {
|
it("Waku message hash computation (meta attribute not present)", () => {
|
||||||
const expectedHash =
|
const expectedHash =
|
||||||
"87619d05e563521d9126749b45bd4cc2430df0607e77e23572d874ed9c1aaa62";
|
"a2554498b31f5bcdfcbf7fa58ad1c2d45f0254f3f8110a85588ec3cf10720fd8";
|
||||||
|
|
||||||
const pubsubTopic = "/waku/2/default-waku/proto";
|
const pubsubTopic = "/waku/2/default-waku/proto";
|
||||||
const message: IProtoMessage = {
|
const message: IProtoMessage = {
|
||||||
payload: hexToBytes("0x010203045445535405060708"),
|
payload: hexToBytes("0x010203045445535405060708"),
|
||||||
contentTopic: "/waku/2/default-content/proto",
|
contentTopic: "/waku/2/default-content/proto",
|
||||||
meta: undefined,
|
meta: undefined,
|
||||||
|
timestamp: BigInt("0x175789bfa23f8400"),
|
||||||
ephemeral: undefined,
|
ephemeral: undefined,
|
||||||
rateLimitProof: undefined,
|
rateLimitProof: undefined,
|
||||||
timestamp: undefined,
|
|
||||||
version: undefined
|
version: undefined
|
||||||
};
|
};
|
||||||
|
|
||||||
const hash = messageHash(pubsubTopic, message);
|
const hash = messageHash(pubsubTopic, message);
|
||||||
|
|
||||||
expect(bytesToHex(hash)).to.equal(expectedHash);
|
expect(bytesToHex(hash)).to.equal(expectedHash);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Waku message hash computation (payload length 0)", () => {
|
it("Waku message hash computation (payload length 0)", () => {
|
||||||
const expectedHash =
|
const expectedHash =
|
||||||
"e1a9596237dbe2cc8aaf4b838c46a7052df6bc0d42ba214b998a8bfdbe8487d6";
|
"483ea950cb63f9b9d6926b262bb36194d3f40a0463ce8446228350bd44e96de4";
|
||||||
|
|
||||||
const pubsubTopic = "/waku/2/default-waku/proto";
|
const pubsubTopic = "/waku/2/default-waku/proto";
|
||||||
const message: IProtoMessage = {
|
const message: IProtoMessage = {
|
||||||
payload: new Uint8Array(),
|
payload: new Uint8Array(),
|
||||||
contentTopic: "/waku/2/default-content/proto",
|
contentTopic: "/waku/2/default-content/proto",
|
||||||
meta: hexToBytes("0x73757065722d736563726574"),
|
meta: hexToBytes("0x73757065722d736563726574"),
|
||||||
|
timestamp: BigInt("0x175789bfa23f8400"),
|
||||||
ephemeral: undefined,
|
ephemeral: undefined,
|
||||||
rateLimitProof: undefined,
|
rateLimitProof: undefined,
|
||||||
timestamp: undefined,
|
|
||||||
version: undefined
|
version: undefined
|
||||||
};
|
};
|
||||||
|
|
||||||
const hash = messageHash(pubsubTopic, message);
|
const hash = messageHash(pubsubTopic, message);
|
||||||
|
|
||||||
expect(bytesToHex(hash)).to.equal(expectedHash);
|
expect(bytesToHex(hash)).to.equal(expectedHash);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
import { sha256 } from "@noble/hashes/sha256";
|
import { sha256 } from "@noble/hashes/sha256";
|
||||||
import type { IProtoMessage } from "@waku/interfaces";
|
import type { IProtoMessage } from "@waku/interfaces";
|
||||||
import { bytesToUtf8, concat, utf8ToBytes } from "@waku/utils/bytes";
|
import { isDefined } from "@waku/utils";
|
||||||
|
import {
|
||||||
|
bytesToUtf8,
|
||||||
|
concat,
|
||||||
|
numberToBytes,
|
||||||
|
utf8ToBytes
|
||||||
|
} from "@waku/utils/bytes";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deterministic Message Hashing as defined in
|
* Deterministic Message Hashing as defined in
|
||||||
|
@ -12,19 +18,20 @@ export function messageHash(
|
||||||
): Uint8Array {
|
): Uint8Array {
|
||||||
const pubsubTopicBytes = utf8ToBytes(pubsubTopic);
|
const pubsubTopicBytes = utf8ToBytes(pubsubTopic);
|
||||||
const contentTopicBytes = utf8ToBytes(message.contentTopic);
|
const contentTopicBytes = utf8ToBytes(message.contentTopic);
|
||||||
|
const timestampBytes = message.timestamp
|
||||||
|
? numberToBytes(message.timestamp)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
let bytes;
|
const bytes = concat(
|
||||||
|
[
|
||||||
if (message.meta) {
|
|
||||||
bytes = concat([
|
|
||||||
pubsubTopicBytes,
|
pubsubTopicBytes,
|
||||||
message.payload,
|
message.payload,
|
||||||
contentTopicBytes,
|
contentTopicBytes,
|
||||||
message.meta
|
message.meta,
|
||||||
]);
|
timestampBytes
|
||||||
} else {
|
].filter(isDefined)
|
||||||
bytes = concat([pubsubTopicBytes, message.payload, contentTopicBytes]);
|
);
|
||||||
}
|
|
||||||
return sha256(bytes);
|
return sha256(bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,19 @@ export function hexToBytes(hex: string | Uint8Array): Uint8Array {
|
||||||
return hex;
|
return hex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function numberToBytes(value: number | bigint): Uint8Array {
|
||||||
|
const buffer = new ArrayBuffer(8);
|
||||||
|
const view = new DataView(buffer);
|
||||||
|
|
||||||
|
if (typeof value === "number") {
|
||||||
|
view.setFloat64(0, value, false);
|
||||||
|
} else {
|
||||||
|
view.setBigInt64(0, value, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Uint8Array(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert byte array to hex string (no `0x` prefix).
|
* Convert byte array to hex string (no `0x` prefix).
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Reference in New Issue