From 836d6b8793a5124747684f6ea76b6dd47c73048b Mon Sep 17 00:00:00 2001 From: Sasha <118575614+weboko@users.noreply.github.com> Date: Thu, 21 Aug 2025 11:58:43 +0200 Subject: [PATCH] feat: expose message hash from IDecodedMessage (#2578) * expose message hash from IDecodedMessage * up mock * optimize hashing and add tests --- .../core/src/lib/message/version_0.spec.ts | 61 ++++++++++++++++++- packages/core/src/lib/message/version_0.ts | 20 ++++++ packages/interfaces/src/message.ts | 2 + packages/relay/src/topic_only_message.ts | 6 ++ packages/rln/src/message.ts | 8 +++ packages/sdk/src/store/store.spec.ts | 4 +- 6 files changed, 99 insertions(+), 2 deletions(-) diff --git a/packages/core/src/lib/message/version_0.spec.ts b/packages/core/src/lib/message/version_0.spec.ts index d95963905c..943769c5d9 100644 --- a/packages/core/src/lib/message/version_0.spec.ts +++ b/packages/core/src/lib/message/version_0.spec.ts @@ -1,9 +1,17 @@ import type { AutoSharding, IProtoMessage } from "@waku/interfaces"; import { createRoutingInfo } from "@waku/utils"; +import { bytesToHex } from "@waku/utils/bytes"; import { expect } from "chai"; import fc from "fast-check"; -import { createDecoder, createEncoder, DecodedMessage } from "./version_0.js"; +import { messageHash } from "../message_hash/index.js"; + +import { + createDecoder, + createEncoder, + DecodedMessage, + proto +} from "./version_0.js"; const testContentTopic = "/js-waku/1/tests/bytes"; @@ -165,3 +173,54 @@ describe("Sets sharding configuration correctly", () => { expect(staticshardingEncoder.pubsubTopic).to.be.eq("/waku/2/rs/0/3"); }); }); + +describe("DecodedMessage lazy hash initialization", () => { + it("should compute hash only when first accessed", () => { + const pubsubTopic = "/waku/2/default-waku/proto"; + const protoMessage: proto.WakuMessage = { + payload: new Uint8Array([1, 2, 3]), + contentTopic: "/test/1/test-proto/proto", + timestamp: BigInt(1234567890000000), + ephemeral: false + }; + + const message = new DecodedMessage(pubsubTopic, protoMessage); + + expect((message as any)._hash).to.be.undefined; + expect((message as any)._hashStr).to.be.undefined; + + const hash = message.hash; + expect((message as any)._hash).to.not.be.undefined; + expect((message as any)._hashStr).to.be.undefined; + + const hashStr = message.hashStr; + expect((message as any)._hashStr).to.not.be.undefined; + + const expectedHash = messageHash( + pubsubTopic, + protoMessage as IProtoMessage + ); + expect(hash).to.deep.equal(expectedHash); + expect(hashStr).to.equal(bytesToHex(expectedHash)); + }); + + it("should return cached hash on subsequent access", () => { + const pubsubTopic = "/waku/2/default-waku/proto"; + const protoMessage: proto.WakuMessage = { + payload: new Uint8Array([1, 2, 3]), + contentTopic: "/test/1/test-proto/proto", + timestamp: BigInt(1234567890000000), + ephemeral: false + }; + + const message = new DecodedMessage(pubsubTopic, protoMessage); + + const hash1 = message.hash; + const hash2 = message.hash; + expect(hash1).to.equal(hash2); + + const hashStr1 = message.hashStr; + const hashStr2 = message.hashStr; + expect(hashStr1).to.equal(hashStr2); + }); +}); diff --git a/packages/core/src/lib/message/version_0.ts b/packages/core/src/lib/message/version_0.ts index a8706817d9..c127120e74 100644 --- a/packages/core/src/lib/message/version_0.ts +++ b/packages/core/src/lib/message/version_0.ts @@ -12,6 +12,9 @@ import type { } from "@waku/interfaces"; import { proto_message as proto } from "@waku/proto"; import { Logger } from "@waku/utils"; +import { bytesToHex } from "@waku/utils/bytes"; + +import { messageHash } from "../message_hash/index.js"; const log = new Logger("message:version-0"); const OneMillion = BigInt(1_000_000); @@ -20,6 +23,9 @@ export const Version = 0; export { proto }; export class DecodedMessage implements IDecodedMessage { + private _hash: Uint8Array | undefined; + private _hashStr: string | undefined; + public constructor( public pubsubTopic: string, private proto: proto.WakuMessage @@ -37,6 +43,20 @@ export class DecodedMessage implements IDecodedMessage { return this.proto.contentTopic; } + public get hash(): Uint8Array { + if (this._hash === undefined) { + this._hash = messageHash(this.pubsubTopic, this.proto as IProtoMessage); + } + return this._hash; + } + + public get hashStr(): string { + if (this._hashStr === undefined) { + this._hashStr = bytesToHex(this.hash); + } + return this._hashStr; + } + public get timestamp(): Date | undefined { // In the case we receive a value that is bigger than JS's max number, // we catch the error and return undefined. diff --git a/packages/interfaces/src/message.ts b/packages/interfaces/src/message.ts index 1b34700010..caecb73aec 100644 --- a/packages/interfaces/src/message.ts +++ b/packages/interfaces/src/message.ts @@ -20,6 +20,8 @@ export interface IDecodedMessage { rateLimitProof: IRateLimitProof | undefined; ephemeral: boolean | undefined; meta: Uint8Array | undefined; + hash: Uint8Array; + hashStr: string; } export interface IRlnMessage extends IDecodedMessage { diff --git a/packages/relay/src/topic_only_message.ts b/packages/relay/src/topic_only_message.ts index 0929361166..a1dfab2fd1 100644 --- a/packages/relay/src/topic_only_message.ts +++ b/packages/relay/src/topic_only_message.ts @@ -14,6 +14,12 @@ export class TopicOnlyMessage implements ITopicOnlyMessage { public get payload(): Uint8Array { throw "Only content topic can be accessed on this message"; } + public get hash(): Uint8Array { + throw "Only content topic can be accessed on this message"; + } + public get hashStr(): string { + throw "Only content topic can be accessed on this message"; + } public rateLimitProof: undefined; public timestamp: undefined; public meta: undefined; diff --git a/packages/rln/src/message.ts b/packages/rln/src/message.ts index d3474bb052..1cca8a4ed2 100644 --- a/packages/rln/src/message.ts +++ b/packages/rln/src/message.ts @@ -48,6 +48,14 @@ export class RlnMessage implements IRlnMessage { return this.msg.payload; } + public get hash(): Uint8Array { + return this.msg.hash; + } + + public get hashStr(): string { + return this.msg.hashStr; + } + public get contentTopic(): string { return this.msg.contentTopic; } diff --git a/packages/sdk/src/store/store.spec.ts b/packages/sdk/src/store/store.spec.ts index 025f2df425..4931aafa7c 100644 --- a/packages/sdk/src/store/store.spec.ts +++ b/packages/sdk/src/store/store.spec.ts @@ -76,7 +76,9 @@ describe("Store", () => { timestamp: new Date(), rateLimitProof: undefined, ephemeral: undefined, - meta: undefined + meta: undefined, + hash: new Uint8Array([1, 2, 3]), + hashStr: "010203" }; it("should successfully query store with valid decoders and options", async () => {