DC-MITHRIL-06
DC true enforcedThe Stage-2 tables (MemPack-decoded TxOuts) materialize into Ade's authoritative UTxOState with
hash-critical bytes PRESERVED and full Word64 quantities carried through -- the converter that unblocks
the native Mithril FirstRun UTxO seed (the DC-MITHRIL-03 / S1b blocker). (a) PURE CONVERTER: a pure
decoded_txout_to_ledger(DecodedTxOut) -> Result<TxOut, TxOutMaterializeError> (closed error enum). No
datum AND no script -> TxOut::ShelleyMary (or TxOut::Byron when the address header nibble is Byron),
the multi-asset bundle built by wrapping each Stage-2 u64 quantity into OutputAssetQuantity(u64) --
NEVER truncated / saturated / i64-cast. Datum OR script present -> TxOut::AlonzoPlus { raw, address, coin } where raw is the canonical Conway TxOut CBOR map (keys ascending: 0=address; 1=value -- coin
uint when ada-only, else [coin, {policy: {name: qty}}] with each qty a CBOR UNSIGNED int u64 and the
policy/name maps canonical-sorted; 2 if datum -- Hash(h)->[0,h], Inline(b)->[1, #6.24(b)]; 3 if
script -- #6.24([type, script_bytes]) with Native->[0,bytes] and Plutus version n->[n,bytes],
V1->1/V2->2/V3->3). (b) HASH-CRITICAL BYTES VERBATIM: the inline-datum bytes and the script bytes are
embedded VERBATIM inside the tag-24 (#6.24, CBOR-encoded-CBOR) via the single workspace tag-24
authority ade_codec::wrap_tag24 -- NEVER re-decoded/re-encoded (they are the identity bytes Cardano
hashes; ade_plutus reads raw directly for the ScriptContext). The raw map is built with the shared
ade_codec cbor primitives (write_map_header / write_uint_canonical / write_bytes_canonical /
write_array_header), not a forked encoder. (c) MATERIALIZATION: materialize_tables_to_utxo iterates
the tables CBOR map in canonical ASCENDING TxIn order (non-ascending / duplicate key is terminal),
parses each 34-byte TxIn key (32 txid + 2 big-endian index), decodes each TxOut via the Stage-2
read_txout and promotes it, accumulates a BTreeMap<TxIn, TxOut> -> UTxOState::from_map; era-bound
to Conway (taken from the SAME snapshot's Stage-1 state, never the tables file or a CLI flag);
FAIL-CLOSED on any unsupported TxOut tag / address form / value tag / script language / non-ascending
or malformed key -- a STRUCTURED terminal error, NEVER an opaque keep-bytes fallback. (d) COMMITMENT
BINDING: the materialized UTxOState -> fingerprint_utxo_v2; a bind_utxo_to_manifest record
(blake2b) over the manifest certified point hash + the Stage-1 NativeSnapshotNonUtxoState commitment +
the Stage-2 decode_tables_commitment + the UTxO fingerprint_v2, with verify_utxo_binding TERMINAL on
any mismatch -- the UTxO authority is visible only when all four bind to the one manifest point. (e)
RECOVERY: a materialized UTxOState survives persist (encode_utxo_state) -> recover
(decode_utxo_state) with an IDENTICAL fingerprint_v2, and a u64 > i64::MAX output quantity round-trips
exactly through that cycle. DERIVED -- the Stage-2 faithful-u64 TxOuts (DC-MITHRIL-05) are promoted into
the widened OUTPUT value model (DC-LEDGER-VALUE-01) and into UTxOState without loss. SCOPE:
materialization + commitment binding ONLY -- NOT node_lifecycle wiring, NOT live CLI flags (step 4,
gated on this), NOT the Conway per-byte min-UTxO calculator (DC-LEDGER-PARAMS-01).
- Source
docs/clusters/MITHRIL-VERIFIED-ANCHOR-INTEGRATION/SLICE-S1c-tables-to-utxostate.md; user directive 2026-06-24 (S1c = the Stage-2 tables -> authoritative UTxOState materialization: a pure DecodedTxOut -> ledger TxOut converter with the hash-critical inline-datum / reference-script bytes embedded verbatim via the tag-24 authority and the faithful u64 quantities carried into OutputAssetQuantity, canonical-ascending TxIn materialization into UTxOState::from_map, fail-closed on any unsupported form, and a fingerprint_utxo_v2 binding to the one manifest point + the Stage-1 + Stage-2 commitments; reuse the value model, the ledger TxOut / UTxOState::from_map / fingerprint_utxo_v2, the Stage-2 read_txout, and the existing CBOR primitives -- do not fork them; this unblocks the native FirstRun route step 4, does NOT touch node_lifecycle or add CLI flags)
- Introduced in
- MITHRIL-VERIFIED-ANCHOR-INTEGRATION-S1c
Enforcement trace
Code
Tests 11
- deterministic_utxo_commitment
- u64_above_i64_max_materializes_persists_recovers_exactly
- datum_and_script_bytes_preserved_verbatim_in_raw
- alonzo_plus_raw_round_trips_to_same_fields
- canonical_txin_ordering_asserted
- fail_closed_negatives
- binding_is_terminal_on_mismatch
- persist_recover_identical_fingerprint
- no_datum_no_script_is_shelley_mary_byron_is_byron
- materialized_count_matches_stage2_commitment_count
- materialize_real_preprod_tables_to_utxo_state
Cross-references
Attack rationale
Three UTxO-authority corruption classes at the snapshot-import boundary are closed. (1) Hash-critical byte loss: a Cardano inline datum / reference script is hashed by the network on its WIRE bytes; if the converter re-decoded then re-encoded those bytes (e.g. through a Plutus-Data round-trip or a non-canonical re-serialization), the resulting datum/script hash would differ and a script-bearing UTxO would fail validation or, worse, validate against a DIFFERENT script -- a different ledger than the network's. Closed by embedding the inline-datum and script bytes VERBATIM inside a #6.24 tag-24 envelope via the single workspace tag-24 authority (ade_codec::wrap_tag24, which copies the inner bytes and never re-encodes), with the ci gate's negative-controlled grep failing on any decode_plutus_data / reencode token in the production body and a hermetic byte-preservation test asserting the inner bytes inside raw equal the DecodedTxOut's DatumField::Inline / ScriptField bytes exactly. (2) Word64 quantity loss: a real Cardano output can hold up to 2^64-1 of a token (i64::MAX is the common max-supply mint; some exceed it); promoting a decoded quantity through an i64 cast / saturation / truncation would store a negative or a different magnitude -- a different ledger, undermining replay and conservation. Closed by wrapping each Stage-2 u64 into OutputAssetQuantity(u64) with the ci gate failing on any as i64 / saturating / truncate in code and a hermetic test materializing + persisting + recovering a quantity ABOVE i64::MAX exactly. (3) Wrong-snapshot / wrong-set authority: a materialized UTxO set bound to the wrong manifest point, wrong Stage-1 non-UTxO state, or wrong Stage-2 tables would seed an authority divorced from the certified snapshot. Closed by the fingerprint_utxo_v2 binding record over the manifest point + Stage-1 + Stage-2 commitments + the UTxO fingerprint, with verify_utxo_binding TERMINAL on any of the four inputs being wrong (tested: wrong point / Stage-1 / Stage-2 / a different UTxO set each rejects). Additionally, a non-canonical (non-ascending) or malformed tables key stream is terminal (no opaque accept), and the materialization is era-bound to Conway from the Stage-1 state (never the untrusted tables file).
Evidence notes
Introduced as MITHRIL-VERIFIED-ANCHOR-INTEGRATION S1c (2026-06-24), the materialization that unblocks the native FirstRun route step 4 (the DC-MITHRIL-03 / S1b open obligation: 'BLOCKED BY the Stage-2 tables -> UTxOState promotion'). The converter is PURE (ade_ledger, BLUE), reusing the Stage-2 read_txout (DC-MITHRIL-05), the widened OUTPUT value model OutputAssetQuantity (DC-LEDGER-VALUE-01), the ledger TxOut / UTxOState::from_map / fingerprint_utxo_v2, the single workspace tag-24 authority ade_codec::wrap_tag24 (CN-WIRE-08), and the shared ade_codec cbor primitives -- none forked. CROSS-CHECKS (all required, like Stage-2's PO#1): (i) ROUND-TRIP -- a hermetic test decodes the AlonzoPlus raw back via the public cbor primitives to the SAME address/coin/datum_option/script_ref. (ii) BYTE PRESERVATION -- a hermetic test asserts the inline-datum + script bytes inside raw are byte-identical to the DecodedTxOut's DatumField::Inline / ScriptField bytes (embedded verbatim in #6.24). (iii) REAL-SNAPSHOT -- crates/ade_runtime/tests/mithril_tables_to_utxostate.rs materializes a 300000-entry sample of the real Mithril preprod tables (.mithril-scratch/restore-ancillary/db/ledger/126400064/tables): 300000 outputs materialize with ZERO error (66149 AlonzoPlus datum/script + 233289 ShelleyMary + 562 Byron -- matching the Stage-2 tag counts exactly), the fingerprint_utxo_v2 is deterministic (materialize twice -> identical), and the binding holds + rejects a wrong point. (iv) cardano-cli ORACLE -- the live preprod node (cardano-node 11.0.1, epoch 295 Conway, 127.0.0.1 N2C socket) was reachable and 8 materialized TxIns were cross-checked against cardano-cli query utxo --tx-in <id> --testnet-magic 1 --output-json: every materialized address matched the bech32-decoded cli address byte-for-byte (5/5 via cardano-cli address info base16), every coin matched, the inline-datum byte-length + presence matched (e.g. 581c...30b, d87980 3b), the datum-hash matched exactly (e.g. dfab8187...02f4d, 73a47e55...ab25d), the multi-asset policy+name+qty matched (387c0fb5...NIGHT=4), and the reference-script type + flat-byte-length matched (PlutusScriptV3 4555 bytes, PlutusScriptV2 2398 bytes). Verified: cargo build --workspace clean; cargo test -p ade_ledger --lib mithril_utxo_materialize green (10/10); cargo test -p ade_runtime --test mithril_tables_to_utxostate green (real-snapshot, ~17s); ci/ci_check_tables_to_utxostate.sh PASS (negative-controlled: it fails on an injected as i64 in the production body); ci/ci_check_registry_unique_ids.sh PASS (DC-MITHRIL-06 fresh). Gated on targeted suites (NOT cargo test -p ade_testkit -- the pre-existing all_epoch_boundaries_fire hang). SCOPE FENCE: materialization + commitment binding ONLY; NOT node_lifecycle wiring (step 4), NOT live CLI flags, NOT the Conway per-byte min-UTxO calculator (DC-LEDGER-PARAMS-01 release blocker).