You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This issue follows up on #756 (closed), per @fxamacker's suggestion to open an issue with specification context.
Apologies for two things upfront:
Submitting the PR before opening an issue — I did not follow CONTRIBUTING correctly.
The delayed reply — I wanted to gather the specification context you asked for properly rather than respond without it.
Motivation: externally-mandated wire format
The feature is not about choosing indefinite-length for its own sake. It is about matching an externally-mandated wire format that Go clients cannot opt out of: Cardano on-chain Plutus Data.
Cardano's cardano-node (via Haskell's cborg library) serializes PlutusData lists and Constr as indefinite-length CBOR arrays (0x9f … 0xff), with one exception (empty list → 0x80). This is documented in:
Per @JaredCorduan (cardano-ledger maintainer): "Plutus V1 is doomed to have to always use this sad script integrity hash" — i.e. PlutusV1 permanently requires this encoding (cannot be fixed in a hard fork).
Why it matters — failure mode
Cardano computes the script integrity hash over the exact bytes of the serialized data (the ledger memoizes original binary; it does not canonicalize). If a Go client produces definite-length CBOR for a PlutusList, the hash differs from what cardano-node expects → transaction submission fails with NonOutputSupplimentaryDatums / mismatched datum hash. Plutus script validation then rejects the transaction.
This is not a preference; it is a wire-format requirement imposed by an existing production blockchain.
CDDL itself does not prescribe definite vs indefinite encoding (per RFC 8610). However, the operational wire format chosen by cardano-node is indefinite-length for non-empty arrays, and clients must match it bit-for-bit.
Ecosystem evidence — Go users already hand-rolling this
Several Go Cardano libraries already implement this workaround manually because fxamacker/cbor lacks the declarative option:
blinklabs-io/plutigo — data/data.go: List.MarshalCBOR() explicitly comments // Haskell's cborg behavior: indefinite for non-empty, definite for empty. and hand-writes 0x9f … 0xff in an encodeCBORArray helper.
Each of these libraries has to write MarshalCBOR for every Plutus struct type, which duplicates the role that toarray already plays declaratively for definite-length.
On the "use cbor.Marshaler workaround" suggestion
The workaround is functional and I appreciate the pointer to StartIndefiniteArray/EndIndefinite. My concern is ergonomic scaling: a realistic Cardano dApp has tens or hundreds of datum/redeemer structs. Each would need an 8–10 line hand-written MarshalCBOR that must stay in sync with the struct fields (easy to drift when adding fields; no compiler help). plutigo's ~60 LOC helper + per-type MarshalCBOR is the public proof of this cost.
The proposed toindefarray tag is symmetric with the existing toarray — same code paths, same cache, same semantics, just the wire-form header byte differs.
Addressing security considerations
You raised valid concerns about indefinite-length and security. A few points on how the proposed feature limits the blast radius:
Opt-in per-struct via tag. Users who do not add toindefarray see zero behavior change.
Honors existing IndefLengthForbidden mode. The implementation can check em.indefLength == IndefLengthForbidden and return the existing IndefiniteLengthError, consistent with how Encoder.StartIndefiniteArray() already behaves (stream.go:247–250). This preserves the defense that profiles like CTAP2 Canonical and Core Deterministic Encoding rely on (per RFC 8949 §4.2, indefinite-length items are not allowed in deterministic encoding — that invariant is retained).
No new indefinite-length behavior on the wire. The library already produces indefinite-length arrays when the user explicitly calls StartIndefiniteArray(). The feature just exposes that capability declaratively.
No impact on users not using the option — aligning with the CONTRIBUTING guidance that PRs "should not reduce speed, increase memory use, reduce security, etc. for people not using the new option."
Would you prefer the option to always check IndefLengthForbidden, or to gate it additionally behind a new EncOptions flag?
Should the CTAP2 preset explicitly forbid toindefarray? It already sets IndefLengthForbidden, so this would be automatic with the check above.
Any additional test coverage you would like to see (round-trip with nested structs, empty-array edge case matching Cardano's rule, rejection when IndefLengthForbidden)?
Next steps
If this issue is welcomed, I will open a fresh PR addressing:
Full PR checklist form with DCO sign-off (commits signed with Signed-off-by:).
The IndefLengthForbidden check.
Additional tests per your guidance.
Link to this issue.
Thank you for your time reviewing this request and for maintaining fxamacker/cbor — it's the de-facto CBOR library in Go and Cardano Go tooling depends on it.
Context
This issue follows up on #756 (closed), per @fxamacker's suggestion to open an issue with specification context.
Apologies for two things upfront:
Motivation: externally-mandated wire format
The feature is not about choosing indefinite-length for its own sake. It is about matching an externally-mandated wire format that Go clients cannot opt out of: Cardano on-chain Plutus Data.
Cardano's
cardano-node(via Haskell'scborglibrary) serializesPlutusDatalists andConstras indefinite-length CBOR arrays (0x9f … 0xff), with one exception (empty list →0x80). This is documented in:Why it matters — failure mode
Cardano computes the script integrity hash over the exact bytes of the serialized data (the ledger memoizes original binary; it does not canonicalize). If a Go client produces definite-length CBOR for a
PlutusList, the hash differs from whatcardano-nodeexpects → transaction submission fails withNonOutputSupplimentaryDatums/ mismatched datum hash. Plutus script validation then rejects the transaction.This is not a preference; it is a wire-format requirement imposed by an existing production blockchain.
CDDL reference
From the Plutus Data CDDL in cardano-ledger/conway:
CDDL itself does not prescribe definite vs indefinite encoding (per RFC 8610). However, the operational wire format chosen by
cardano-nodeis indefinite-length for non-empty arrays, and clients must match it bit-for-bit.Ecosystem evidence — Go users already hand-rolling this
Several Go Cardano libraries already implement this workaround manually because
fxamacker/cborlacks the declarative option:data/data.go:List.MarshalCBOR()explicitly comments// Haskell's cborg behavior: indefinite for non-empty, definite for empty.and hand-writes0x9f … 0xffin anencodeCBORArrayhelper.cbor/encode.go: same pattern.cip25.go: usesenc.StartIndefiniteArray()/enc.EndIndefinite()manually.Each of these libraries has to write
MarshalCBORfor every Plutus struct type, which duplicates the role thattoarrayalready plays declaratively for definite-length.On the "use
cbor.Marshalerworkaround" suggestionThe workaround is functional and I appreciate the pointer to
StartIndefiniteArray/EndIndefinite. My concern is ergonomic scaling: a realistic Cardano dApp has tens or hundreds of datum/redeemer structs. Each would need an 8–10 line hand-writtenMarshalCBORthat must stay in sync with the struct fields (easy to drift when adding fields; no compiler help).plutigo's ~60 LOC helper + per-typeMarshalCBORis the public proof of this cost.The proposed
toindefarraytag is symmetric with the existingtoarray— same code paths, same cache, same semantics, just the wire-form header byte differs.Addressing security considerations
You raised valid concerns about indefinite-length and security. A few points on how the proposed feature limits the blast radius:
toarraystructs viagetHeadWithIndefiniteLengthFlag()— no new decode surface area is introduced.toindefarraysee zero behavior change.IndefLengthForbiddenmode. The implementation can checkem.indefLength == IndefLengthForbiddenand return the existingIndefiniteLengthError, consistent with howEncoder.StartIndefiniteArray()already behaves (stream.go:247–250). This preserves the defense that profiles like CTAP2 Canonical and Core Deterministic Encoding rely on (per RFC 8949 §4.2, indefinite-length items are not allowed in deterministic encoding — that invariant is retained).StartIndefiniteArray(). The feature just exposes that capability declaratively.Proposed scope
Minimal change, matching what was in #756:
toindefarrayas a struct tag option (parallel totoarray).0x9f … 0xffinstead of82 …/83 ….IndefLengthForbidden.Open questions for your guidance:
IndefLengthForbidden, or to gate it additionally behind a newEncOptionsflag?toindefarray? It already setsIndefLengthForbidden, so this would be automatic with the check above.IndefLengthForbidden)?Next steps
If this issue is welcomed, I will open a fresh PR addressing:
Signed-off-by:).IndefLengthForbiddencheck.Thank you for your time reviewing this request and for maintaining
fxamacker/cbor— it's the de-facto CBOR library in Go and Cardano Go tooling depends on it.