Summary
A witness datum is written in one layout and hashed in another. TransactionWitnessSet writes plutus_data with the transaction codec options (CML_DEFAULT_OPTIONS, every array and map definite). Data.toDatumHash and Redeemers.toScriptDataHash hash the same datum with the data default (CML_DATA_DEFAULT_OPTIONS, indefinite lists, maps and constructor fields). For any datum with constructor fields, a list or a map, the bytes in the transaction hash to neither value, so the node rejects a spend that supplies the datum in the witness set.
Evidence
Devnet on main (833d25e): lock at an always-succeeding script address under Data.toDatumHash(datum), then spend it with the datum in the witness set and the script data hash from toScriptDataHash(redeemers, costModels, [datum]), encoded with Transaction.toCBORHex and witnessed with addVKeyWitnessesBytes.
| Datum |
Witness set writes |
Hashes use |
Node |
Constr 0 [{1: 2}, [3]] |
d87982a101028103 |
d8799fbf0102ff9f03ffff |
3113, script integrity hash mismatch |
Constr 0 [1] |
d8798101 |
d8799f01ff |
3113 |
With the script data hash computed over the written bytes, the node moves on to 3111 (missing datum), because the written datum does not hash to the locked datum hash. Only a transaction where all three agree on one layout was accepted.
The transaction builder does not attach witness datums yet (txBuilder.ts L858-863 is a TODO), so today only hand-built transactions hit this. It will block datum hash spends once the builder supports them.
Fix
Write new witness datums in the data layout used for hashing, and keep decoded witness datums in their original bytes (the OriginalBytes approach from #581, as #603 did for inline datums). After that, the data default can follow the node (#395).
Regression test
- Devnet: the spend above is accepted for both datums.
- The witness datum bytes hash to
Data.toDatumHash(datum), and toScriptDataHash equals CML.hash_script_data over the written witness set.
- A decoded transaction keeps its witness datum bytes in every layout, including maps with byte-string keys, through
addVKeyWitnessesBytes.
Must FAIL on main today and PASS after the fix.
Summary
A witness datum is written in one layout and hashed in another.
TransactionWitnessSetwritesplutus_datawith the transaction codec options (CML_DEFAULT_OPTIONS, every array and map definite).Data.toDatumHashandRedeemers.toScriptDataHashhash the same datum with the data default (CML_DATA_DEFAULT_OPTIONS, indefinite lists, maps and constructor fields). For any datum with constructor fields, a list or a map, the bytes in the transaction hash to neither value, so the node rejects a spend that supplies the datum in the witness set.Evidence
Devnet on main (833d25e): lock at an always-succeeding script address under
Data.toDatumHash(datum), then spend it with the datum in the witness set and the script data hash fromtoScriptDataHash(redeemers, costModels, [datum]), encoded withTransaction.toCBORHexand witnessed withaddVKeyWitnessesBytes.Constr 0 [{1: 2}, [3]]d87982a101028103d8799fbf0102ff9f03ffffConstr 0 [1]d8798101d8799f01ffWith the script data hash computed over the written bytes, the node moves on to 3111 (missing datum), because the written datum does not hash to the locked datum hash. Only a transaction where all three agree on one layout was accepted.
The transaction builder does not attach witness datums yet (
txBuilder.tsL858-863 is a TODO), so today only hand-built transactions hit this. It will block datum hash spends once the builder supports them.Fix
Write new witness datums in the data layout used for hashing, and keep decoded witness datums in their original bytes (the
OriginalBytesapproach from #581, as #603 did for inline datums). After that, the data default can follow the node (#395).Regression test
Data.toDatumHash(datum), andtoScriptDataHashequalsCML.hash_script_dataover the written witness set.addVKeyWitnessesBytes.Must FAIL on main today and PASS after the fix.