Skip to content

fix: hash Plutus scripts over serialised bytes (ledger rule), not CBOR-unwrapped bytes - #852

Open
Kshot3000 wants to merge 1 commit into
MeshJS:mainfrom
Kshot3000:fix/plutus-script-hash-763
Open

Kshot3000 wants to merge 1 commit into
MeshJS:mainfrom
Kshot3000:fix/plutus-script-hash-763

Conversation

@Kshot3000

Copy link
Copy Markdown

Summary

Fixes #763. Mesh derived Plutus script hashes from the deserialized script object's hash(), which unwraps the CBOR byte string and hashes only the inner UPLC bytes. The Cardano ledger hashes the serialised script bytes exactly as they appear in the CBOR byte string:

script_hash = blake2b_224(language_tag || script_cbor_bytes)

So resolveScriptHash() / deserializePlutusScript() disagreed with Aiken blueprint hashes and cardano-cli for the same script, and resolvePlutusScriptAddress() derived addresses under a script hash that doesn't exist on-chain — the failure mode reported in #763 (funds locked at a wrong script address on Preview).

Verification of the bug (before this change)

Using the Aiken blueprint in this repo (packages/mesh-contract/src/escrow/aiken-workspace-v3/plutus.json, Plutus V3):

  • Aiken blueprint hash: 1bf1264ddd67f3555b0d103f71422f44c7373a8a42744f0d75052aae
  • resolveScriptHash(compiledCode, "V3") returned: 7ffcbf83d9ad48dd672f13c08fdc257b82ff86f7f4ddd0a68357657c
  • Manual check: blake2b_224(0x03 || compiledCode bytes) = the Aiken hash; blake2b_224(0x03 || CBOR-unwrapped bytes) = the Mesh result. Reproduced on the published @meshsdk/core / @meshsdk/core-cst / @meshsdk/core-csl 1.9.1 as well.

Change

  • New hashPlutusScript(scriptCbor, version) helper in @meshsdk/common implementing the ledger rule (blakejs is already a dependency there).
  • Used at every site that previously trusted the script object's hash():
    • mesh-core: resolveScriptHash(), serializePlutusScript()
    • mesh-core-cst: serializer deserializePlutusScript(), resolvePlutusScriptAddress()
    • mesh-core-csl: serializer deserializePlutusScript(), resolvePlutusScriptAddress()
    • mesh-transaction: policy id derivation in the deprecated Transaction mint/burn Plutus paths
  • Script CBOR serialization (toCbor(), script refs, witness construction) is deliberately untouched — only the reported hash/address/policy id changes.
  • One existing expectation (mesh-core-cst resolvePlutusScriptAddress test) encoded the old unwrapped-bytes hash; corrected, with a comment explaining why (verified by decoding both addresses: old payload embeds the unwrapped-bytes hash, new one the ledger hash).

Tests

New regression tests use Aiken blueprint vectors from mesh-contract's own giftcard workspace (mint 401c9670…, redeem b2386630…):

  • mesh-common: 181/181 pass (incl. 3 new hashPlutusScript tests)
  • mesh-core-cst: 180/180 pass (incl. new serializer test + corrected address test)
  • mesh-core-csl: serializer suite 9/9 pass (4 pre-existing Offline Evaluator step-count failures in that package reproduce identically on unmodified main in my environment — UPLC evaluator version drift, unrelated to this change)
  • mesh-core: 2/2 pass (incl. new resolveScriptHash blueprint test)
  • mesh-transaction: builds clean; its suite shows the same 36 pre-existing failures on unmodified main in my environment, no new ones

Independent Cardano builder — @Kshot9000 · https://x.com/kshot9000
Tips (ADA): addr1q8hnl6vl5a6k3rw3n5g3jtte696zcl76kfatzv7gpswa9r0dj7fma6klq55y4ffm7tf0em09udnyhuk4ah92pl5x9jpqjae44v

…R-unwrapped bytes

resolveScriptHash, deserializePlutusScript (both serializer backends),
resolvePlutusScriptAddress, serializePlutusScript and the deprecated
Transaction mint/burn policy id derivation all took the script hash from
the deserialized script object, whose hash() unwraps the CBOR byte string
and hashes only the inner UPLC bytes. The ledger hashes the serialised
script bytes exactly as they appear in the CBOR byte string:
blake2b_224(language_tag || script_cbor_bytes). The results therefore
disagreed with Aiken blueprint hashes and cardano-cli, deriving wrong
script addresses and policy ids (funds sent to such an address are
locked under a script that does not exist there).

Adds hashPlutusScript() in @meshsdk/common implementing the ledger rule
and uses it at every affected site in mesh-core, mesh-core-cst,
mesh-core-csl and mesh-transaction. Script CBOR serialization
(toCbor/scriptRef/witness paths) is unchanged.

Tests: Aiken blueprint vectors from mesh-contract's giftcard workspace
(mint 401c9670..., redeem b2386630...) now hash correctly in
mesh-common, mesh-core, mesh-core-cst and mesh-core-csl suites; the
mesh-core-cst resolvePlutusScriptAddress expectation that encoded the
old unwrapped-bytes hash is corrected with a note.

Fixes MeshJS#763

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.

CRITICAL: PlutusV3 Script Hash Mismatch in Conway Era (Permanent Fund Lock)

1 participant