diff --git a/.release-please-manifest.json b/.release-please-manifest.json index e0a9768aa1..39f303fd5f 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -2,6 +2,7 @@ "packages/utils": "0.0.4", "packages/proto": "0.0.4", "packages/interfaces": "0.0.11", + "packages/message-hash": "0.1.0", "packages/enr": "0.0.10", "packages/peer-exchange": "0.0.9", "packages/core": "0.0.16", diff --git a/.size-limit.cjs b/.size-limit.cjs index c74ee498cd..8b277194ce 100644 --- a/.size-limit.cjs +++ b/.size-limit.cjs @@ -43,4 +43,9 @@ module.exports = [ path: "packages/core/bundle/index.js", import: "{ wakuStore }", }, + { + name: "Deterministic Message Hashing", + path: "packages/message-hash/bundle/index.js", + import: "{ messageHash }", + }, ]; diff --git a/package-lock.json b/package-lock.json index b9c200f05d..e12185721f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,9 +9,9 @@ "packages/utils", "packages/proto", "packages/interfaces", + "packages/message-hash", "packages/enr", "packages/core", - "packages/message-hash", "packages/peer-exchange", "packages/dns-discovery", "packages/message-encryption", @@ -29596,6 +29596,10 @@ "name": "@waku/message-hash", "version": "0.0.10", "license": "MIT OR Apache-2.0", + "dependencies": { + "@noble/hashes": "^1.2.0", + "@waku/utils": "*" + }, "devDependencies": { "@rollup/plugin-commonjs": "^24.0.1", "@rollup/plugin-json": "^6.0.0", @@ -29606,6 +29610,7 @@ "@typescript-eslint/eslint-plugin": "^5.54.1", "@typescript-eslint/parser": "^5.51.0", "@waku/build-utils": "*", + "@waku/interfaces": "*", "chai": "^4.3.7", "cspell": "^6.28.0", "eslint": "^8.35.0", @@ -34724,6 +34729,7 @@ "@waku/message-hash": { "version": "file:packages/message-hash", "requires": { + "@noble/hashes": "^1.2.0", "@rollup/plugin-commonjs": "^24.0.1", "@rollup/plugin-json": "^6.0.0", "@rollup/plugin-node-resolve": "^15.0.1", @@ -34733,6 +34739,8 @@ "@typescript-eslint/eslint-plugin": "^5.54.1", "@typescript-eslint/parser": "^5.51.0", "@waku/build-utils": "*", + "@waku/interfaces": "*", + "@waku/utils": "*", "chai": "^4.3.7", "cspell": "^6.28.0", "eslint": "^8.35.0", diff --git a/packages/message-hash/package.json b/packages/message-hash/package.json index b5b8ad05a1..2ec2476796 100644 --- a/packages/message-hash/package.json +++ b/packages/message-hash/package.json @@ -1,6 +1,6 @@ { "name": "@waku/message-hash", - "version": "0.0.10", + "version": "0.1.0", "description": "TypeScript implementation of the Deterministic Message Hashing as specified in 14/WAKU2-MESSAGE", "types": "./dist/index.d.ts", "module": "./dist/index.js", @@ -51,6 +51,10 @@ "engines": { "node": ">=16" }, + "dependencies": { + "@noble/hashes": "^1.2.0", + "@waku/utils": "*" + }, "devDependencies": { "@rollup/plugin-commonjs": "^24.0.1", "@rollup/plugin-json": "^6.0.0", @@ -61,6 +65,7 @@ "@typescript-eslint/eslint-plugin": "^5.54.1", "@typescript-eslint/parser": "^5.51.0", "@waku/build-utils": "*", + "@waku/interfaces": "*", "chai": "^4.3.7", "cspell": "^6.28.0", "eslint": "^8.35.0", diff --git a/packages/message-hash/src/index.spec.ts b/packages/message-hash/src/index.spec.ts new file mode 100644 index 0000000000..d88a92af79 --- /dev/null +++ b/packages/message-hash/src/index.spec.ts @@ -0,0 +1,68 @@ +import type { IProtoMessage } from "@waku/interfaces"; +import { bytesToHex, hexToBytes } from "@waku/utils/bytes"; +import { expect } from "chai"; + +import { messageHash } from "./index.js"; + +// https://rfc.vac.dev/spec/14/#test-vectors +describe("RFC Test Vectors", () => { + it("Waku message hash computation", () => { + const expectedHash = + "4fdde1099c9f77f6dae8147b6b3179aba1fc8e14a7bf35203fc253ee479f135f"; + + const pubSubTopic = "/waku/2/default-waku/proto"; + const message: IProtoMessage = { + payload: hexToBytes("0x010203045445535405060708"), + contentTopic: "/waku/2/default-content/proto", + meta: hexToBytes("0x73757065722d736563726574"), + ephemeral: undefined, + rateLimitProof: undefined, + timestamp: undefined, + version: undefined, + }; + + const hash = messageHash(pubSubTopic, message); + + expect(bytesToHex(hash)).to.equal(expectedHash); + }); + + it("Waku message hash computation (meta attribute not present)", () => { + const expectedHash = + "87619d05e563521d9126749b45bd4cc2430df0607e77e23572d874ed9c1aaa62"; + + const pubSubTopic = "/waku/2/default-waku/proto"; + const message: IProtoMessage = { + payload: hexToBytes("0x010203045445535405060708"), + contentTopic: "/waku/2/default-content/proto", + meta: undefined, + ephemeral: undefined, + rateLimitProof: undefined, + timestamp: undefined, + version: undefined, + }; + + const hash = messageHash(pubSubTopic, message); + + expect(bytesToHex(hash)).to.equal(expectedHash); + }); + + it("Waku message hash computation (payload length 0)", () => { + const expectedHash = + "e1a9596237dbe2cc8aaf4b838c46a7052df6bc0d42ba214b998a8bfdbe8487d6"; + + const pubSubTopic = "/waku/2/default-waku/proto"; + const message: IProtoMessage = { + payload: new Uint8Array(), + contentTopic: "/waku/2/default-content/proto", + meta: hexToBytes("0x73757065722d736563726574"), + ephemeral: undefined, + rateLimitProof: undefined, + timestamp: undefined, + version: undefined, + }; + + const hash = messageHash(pubSubTopic, message); + + expect(bytesToHex(hash)).to.equal(expectedHash); + }); +}); diff --git a/packages/message-hash/src/index.ts b/packages/message-hash/src/index.ts index e69de29bb2..897f07d865 100644 --- a/packages/message-hash/src/index.ts +++ b/packages/message-hash/src/index.ts @@ -0,0 +1,33 @@ +import { sha256 } from "@noble/hashes/sha256"; +import type { IProtoMessage } from "@waku/interfaces"; +import { concat, utf8ToBytes } from "@waku/utils/bytes"; + +/** + * Deterministic Message Hashing as defined in + * [14/WAKU2-MESSAGE](https://rfc.vac.dev/spec/14/#deterministic-message-hashing) + */ +export function messageHash( + pubsubTopic: string, + message: IProtoMessage +): Uint8Array { + const pubsubTopicBytes = utf8ToBytes(pubsubTopic); + const contentTopicBytes = utf8ToBytes(message.contentTopic); + + let bytesToHash; + + if (message.meta) { + bytesToHash = concat([ + pubsubTopicBytes, + message.payload, + contentTopicBytes, + message.meta, + ]); + } else { + bytesToHash = concat([ + pubsubTopicBytes, + message.payload, + contentTopicBytes, + ]); + } + return sha256(bytesToHash); +} diff --git a/release-please-config.json b/release-please-config.json index 116708d338..f9faf22ca7 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -9,9 +9,9 @@ "packages/utils": {}, "packages/proto": {}, "packages/interfaces": {}, + "packages/message-hash": {}, "packages/enr": {}, "packages/peer-exchange": {}, - "packages/message-hash": {}, "packages/core": {}, "packages/dns-discovery": {}, "packages/message-encryption": {},