Skip to content

fix(evolution): hash a decoded datum over its original bytes (#397 part 2) - #647

Open
Kshot3000 wants to merge 1 commit into
IntersectMBO:mainfrom
Kshot3000:fix/data-datum-hash-original-bytes
Open

Kshot3000 wants to merge 1 commit into
IntersectMBO:mainfrom
Kshot3000:fix/data-datum-hash-original-bytes

Conversation

@Kshot3000

@Kshot3000 Kshot3000 commented Oct 15, 2026 •

Copy link
Copy Markdown

Fixes part 2 of #397 — datum hashing is now byte faithful. Part 1 is deliberately left out, with the reason below.

Data.toDatumHash hashed a re-encode of the decoded value. The ledger hashes a datum over the bytes it arrived in — cardano-ledger's Plutus Data keeps the original memoized bytes — so a datum decoded from a valid but non-canonical encoding produced a hash the node never computed:

811a0000000a   [10], definite list, 10 in a 4-byte integer header
               → this module re-encodes it as 9f0aff, and hashed that
820102         [1, 2] definite → re-encoded 9f0102ff, hashed that
5802aabb       bytes(2) with a 1-byte length header → re-encoded 42aabb
bf0102ff       {1: 2} indefinite → re-encoded a10102

Change

Following the pattern #397 itself points at — the WeakMap memo Transaction already uses for its format tree:

  • Data.fromCBORBytes / Data.fromCBORHex remember a copy of the source bytes for the decoded datum, in a module-private WeakMap, when decoding with the default codec options. Non-default options skip the cache, exactly as Transaction.fromCBORBytes does — explicit options signal the caller wants the re-encode.
  • Data.toDatumHash under the default options hashes the remembered bytes when there are any, and otherwise hashes the re-encode, as before.
  • Only the hash changes. toCBORBytes / toCBORHex still write the module's own encoding; nothing about encoding or decoding behaviour moves.

Scope notes, both taken from the issue text:

  • Bare integer datums fall back to the re-encode. A top-level integer datum is a bigint, which cannot be a WeakMap key — the issue calls this case out, and a test pins the fallback.
  • Part 1 (duplicate map keys) is not in this PR. CBOR: repeated map keys are silently collapsed, where the ledger either rejects them or keeps every pair #578 records that the ledger keeps every pair in a Plutus data map and that a devnet accepted {1: 1, 1: 2} as an inline datum — so rejecting duplicate keys on decode, the fail-closed option, would refuse valid on-chain datums, and preserving them needs the Map model itself to change. That is a design call I did not want to bury inside a hashing fix; the issue's own regression line allows either outcome ("rejected (or preserved, per chosen approach)"). Happy to take it up separately in whichever direction you prefer.

Tests

Ten new cases in test/Data.DatumHashOriginalBytes.test.ts, each checked against main first:

  • five non-canonical vectors (non-minimal integer in a list, definite list, non-minimal byte-string length header, non-minimal map value, indefinite map), plus one of them decoded through fromCBORBytes — RED on main, where toDatumHash returned the hash of the re-encode;
  • controls that pass on both sides: a datum already in this module's layout hashes identically decoded and rebuilt, a locally built datum hashes its own encoding, the bare-integer fallback hashes the re-encode, and an explicit non-default codec option re-encodes instead of replaying the source bytes.

Results: the new file passes 10/10; the Data and hashing files around it pass (Data 116, Data.encoder 44, Data.golden 30, Data.prop 13, CBOR.PlutusDataPresets 27, UtilsHash.CML 14, TransactionWitnessSet.DataOptions 118). The full package suite passes: 1688 passed / 75 skipped / 0 failed, test files 102 passed / 1 skipped of 103 — exactly main's 1678 baseline + 10 new, files +1. tsc --noEmit and eslint are clean, and Data.ts's prettier diff count matches main's exactly (7 = 7). A patch changeset is included.


Independent builder — not affiliated with IntersectMBO, the Cardano Foundation or IOG.

@Kshot9000 (https://x.com/kshot9000) · ADA: addr1q8hnl6vl5a6k3rw3n5g3jtte696zcl76kfatzv7gpswa9r0dj7fma6klq55y4ffm7tf0em09udnyhuk4ah92pl5x9jpqjae44v

cc @cardano-foundation @IntersectMBO

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant