The digest is the hash of the receipt’s canonical form — keys sorted recursively — so it’s reproducible regardless of how Walrus serialized the stored JSON:
function sortValue(v) { if (Array.isArray(v)) return v.map(sortValue); if (v && typeof v === "object") return Object.fromEntries(Object.keys(v).sort().map((k) => [k, sortValue(v[k])])); return v;}const canonicalReceiptBytes = (r) => new TextEncoder().encode(JSON.stringify(sortValue(r)));const digestHex = (bytes) => bytesToHex(blake2b(bytes, { dkLen: 32 }));
The verifier re-canonicalizes the fetched blob before hashing, so the check is robust to key order and whitespace introduced in storage. The proof binds the content, not the exact bytes.
The TypeScript verifier and the Move contract must produce identical hashes or honest proofs would fail to verify. This is pinned by a golden test vector on both sides:
Both are asserted against the same constant in their test suites — so any drift fails CI, not a user’s verification.
@noble/hashes v2 exposes blake2b at the @noble/hashes/blake2.js subpath, and @mysten/sui v2.20 exposes the JSON-RPC client at @mysten/sui/jsonRpc — both are used by the verifier.