Skip to content

Nothing verifies a mirror-coin claim against chain — the collateral economy is unenforced #466

Description

@MichaelTaylor3d

Task

Nothing anywhere verifies a mirror-coin claim against chain. Build the consumer.

Measured 2026-08-31 across all of modules/: MirrorCoin::advertises has eight call sites, and every one is inside dig-mirror-coin itself — two crate-internal (census.rs:723, query.rs:373) and six in its own tests. Zero consumers. No node, no peer, no DHT reader, no download path calls it.

So the economic guarantee of the whole mirror-coin system is unenforced end to end. Coins are created, listed, and reclaimed correctly; a peer's claim that it holds content backed by collateral is never checked by anybody.

Why this is the load-bearing gap, not one of several

Everything else in the family works or is close. The flywheel's premise is that a holder's advertisement is backed — that is what makes the collateral mean anything and what a bad actor would attack. Without a verifier, unverified_mirror_coin_id is a field that travels the wire and is read by nothing, and a peer can claim any bond it likes at zero cost.

The name of the wire field says this outright: unverified_. It is an attacker-supplied shortcut, correctly named, and the verification it is a shortcut to does not exist.

The contract already exists — implement against it, do not invent one

SYSTEM.md's mirror-coin row is normative and states the whole algorithm. Consult it first; the summary:

  1. Fetch the coin and verify it sits at dig_mirror_coin::mirror_coin_puzzle_hash().
  2. Verify it is $DIG, with the asset id re-derived from the creating spend.
  3. Verify it carries the full collateral.
  4. Call MirrorCoin::advertises(store_launcher_id, root_hash, epoch)exact equality on the coin's memo-declared triple, plus the hint recomputed with the owner taken from the coin's own lineage proof, never from a memo.

Steps 1-3 alone prove only that a valid mirror coin exists somewhere. Step 4 is what binds it to the claim.

Verification MUST NOT be an arithmetic recompute of the morph alone. mirror_hint sums four terms, one an unbounded freely-chosen epoch, so a coin's author can solve for a value placing their advertisement on any other's hint — the crate asserts this itself in namespace.rs (a_freely_chosen_epoch_solves_onto_any_other_advertisements_hint).

The three failure directions that must be right

  • Absence of unverified_mirror_coin_id is NORMAL and must never read as "uncollateralised". A publisher that has not created its coin, an older publisher, and one mid-epoch-rollover all legitimately omit it. Fall back to the hint scan (discover/list) — slower, not weaker.
  • A wrong pointer costs the publisher, not the verifier. One chain read, no retry loop; a miss falls straight back to the scan. It MUST NOT blocklist or penalise the publisher — a stale pointer is indistinguishable from an epoch rollover.
  • A chain that cannot answer is not a failed verification. Keep "unverified" and "verified absent" distinct, the same three-state discipline absence_established uses on the wire. Collapsing them is how a partitioned node starts rejecting honest peers.

Settle the shape first (§1.10)

Who verifies, and when? Candidates, each with a different cost profile: the download path before accepting a holder; the DHT reader on ingesting a provider record; a background sweep over known holders. Verifying on every provider record is a chain read per record and will not scale; verifying never is where we are now.

And what does a failed verification DO? Deprioritise the holder, refuse it, or merely record it? A verifier whose result nothing acts on reproduces today's situation with more code. State the answer before building.

Scope

The deliverable is a verifier that is actually called on a production path, plus what acts on its result. A crate-internal helper with no consumer is exactly the state this ticket exists to end — the acceptance is a call site, not a function.

Out of scope: changing dig-mirror-coin's verification primitive, which is sound and exercised by its own tests.

Evidence

Sweep of all modules/ on 2026-08-31, both origin/main and origin/loop/batch-mirror. Also note dig-node's only use of the crate's verifying surface is collateral_census.rs:42, which prices this node's own requirement network-wide — not a check of any peer's claim.

Acceptance

A peer advertising a bond it does not hold is detected on a production path, proven by a test with a coin that does not advertise the claimed triple — plus the three controls: a valid bond verifies, an absent pointer falls back to the scan rather than failing, and an unreachable chain yields "unverified" rather than "absent".

Parent

Epic https://github.com/DIG-Network/dig_ecosystem/issues/3166 · lifecycle #412 · pointer supply #435

Metadata

Metadata

Assignees

No one assigned

    Labels

    kind:businessa person can DO something new, or money moves, or a shipped surface stops lying to them

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions