Skip to content

Decode Plutus Data exactly as plutus decodeData, strict or leading-item (#237) - #238

Open
satran004 wants to merge 1 commit into
mainfrom
fix/plutus-data-decoder-parity
Open

satran004 wants to merge 1 commit into
mainfrom
fix/plutus-data-decoder-parity

Conversation

@satran004

Copy link
Copy Markdown
Member

Fixes #237. This is related to #229 / #236, the encoder.

Summary

PlutusDataCborDecoder accepted CBOR that plutus's decodeData rejects (plutus 1.65.0.0 Data.hs:209-307, cborg 0.2.8.0). It backs FLAT data constants, so a script the ledger rejects as MalformedScriptWitnesses decoded fine in julc. That gives Yano's java-julc phase-1 well-formedness check a false accept.

What it wrongly accepted:

  • byte strings and bignum payloads over 64 bytes;
  • nested or tagged chunks;
  • d802 bignum tags;
  • nested and unknown tags, decoded as I 0;
  • a tagged tag-102 index or fields;
  • an extra element in an indefinite tag-102 array;
  • simple values dropped by filterBreaks (81f5 decoded as List []);
  • indefinite integers and tags;
  • 64-bit lengths read as signed.

Trailing bytes now follow the ledger context:

Entry point Behaviour Haskell Used for
decode() strict: exactly one item, no leftover inline datums: makeBinaryData → decodeFull' (ledger-core Plutus/Data.hs:154-173); datums and redeemers are exact item slices datums, redeemers, inline datums
decodeFirst() leading item, remainder ignored FLAT data constants: FlatViaSerialise → deserialiseOrFail (serialise Codec/Serialise.hs:134-143) only UplcFlatDecoder.readData

On main, julc's hybrid matched neither context. It also falsely rejected a FLAT constant followed by some trailing items, which turned a valid script into MalformedScriptWitnesses.

Tests

  • PlutusDataDecoderParityTest: accept, reject and trailing-byte vectors, plus FLAT end-to-end cases. Every verdict is confirmed against plutus decodeData running on cborg 0.2.8.0.
  • Differential fuzz: 90k vectors, with no difference in either mode. On main, about 8.2k of every 30k were false accepts.
  • Removed vector: one MapDecodingFidelityTest vector (tag 1000 on a map) is removed, because plutus rejects it.
Suite Result
julc ./gradlew cleanTest test 11,457 tests, 0 failures
Committed FLAT fixtures all 3,273 keep their verdicts
Yano, local build ledger-conformance 133/0, anchor 15/15, appchain-anchor-onchain 120/120
Yano shadow replays identical to the baseline
Yano script-evaluators all pass, apart from the 4 expected deviation canaries that #219/#227/#228 fix

Independently reviewed (the reviewer ran its own 72 vectors through the oracle): approve. The trailing-byte design follows that review.

Note: Yano's own ledger-rules decoding of script constants has the same trailing-byte false reject, independent of julc. It's fixed separately in Yano.

🤖 Generated with Claude Code

PlutusDataCborDecoder accepted CBOR that plutus's decodeData rejects
(plutus 1.65.0.0 Data.hs:209-307, cborg 0.2.8.0): byte strings and bignum
payloads over 64 bytes, nested or tagged chunks, d802 bignum tags, nested
and unknown tags (read as I 0), tagged tag-102 index/fields, extra
elements in an indefinite tag-102 array, simple values dropped by
filterBreaks, indefinite integers and tags, and 64-bit lengths read as
signed. A script with such a Data constant is MalformedScriptWitnesses in
the ledger but decoded in julc.

- MapPreservingCborDecoder bounds byte strings (indefinite only as
  definite chunks of at most 64 bytes, bignum payloads included), rejects
  indefinite integers and tags and arrays longer than the input, and
  allows a tag only around bytes (c2/c3) or an array.
- PlutusDataCborDecoder.fromDataItem rejects unknown and nested tags and
  a tagged tag-102 index or fields, and drops only the closing break.

Trailing bytes follow the ledger context:
- decode() is strict: one item, no leftover (inline datums: makeBinaryData
  -> decodeFull', ledger-core Plutus/Data.hs:154-173; datums and
  redeemers are exact item slices).
- decodeFirst() ignores the remainder, as FLAT data constants do
  (FlatViaSerialise -> deserialiseOrFail, serialise Codec/Serialise.hs:
  134-143). Used only by UplcFlatDecoder.readData.

Tests: PlutusDataDecoderParityTest (accept/reject/trailing vectors and
FLAT end-to-end cases), every verdict confirmed against plutus decodeData
on cborg 0.2.8.0; a 90k-vector differential fuzz shows no difference for
either mode. One MapDecodingFidelityTest vector (tag 1000 on a map) is
removed: plutus rejects it.

Fixes #237

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012MQvHGqr1BvpfjkzfbHWfW
@satran004 satran004 added the yano Found or needed by Yano (Cardano node) label Oct 1, 2026

@satran004 satran004 left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correctness-focused review of 8297793: no blocking correctness finding identified in this diff.

Checked the decoder against plutus-core 1.65.0.0 decodeData and inspected cbor-java 0.9's delegated array/tag parsing. Reviewed bounded chunks/bignums, general-constructor Word64 and arity checks, nested/unknown tags, misplaced breaks, unsigned lengths, duplicate map associations, and the strict decode versus FLAT-only decodeFirst call path.

Independently ran core (799 tests), Java VM (2,469 discovered; 262 explicitly inapplicable PV10 cases skipped), and cardano-client-lib tests: no failures. Also ran core with #236 and #238 combined successfully. Did not rerun the author's Haskell differential fuzz campaign, Yano, or an external DevKit.

Documentation follow-up: ADR-037 explicitly preserves the old top-level sequence handling, unknown-tag behavior, and non-map permissiveness. Please record that this change supersedes those constraints, and add release guidance for the public decode() behavior change. The stricter behavior is intentional and appropriate; the governing documentation should say so.

Recommendation: no correctness blocker found within the reviewed change.

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

yano Found or needed by Yano (Cardano node)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

PlutusDataCborDecoder accepts CBOR Data that plutus decodeData rejects (>64-byte bytes, unknown tags, …) and mishandles trailing bytes

1 participant