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
PR-B of the #383 split. Discover CAT coins by derived puzzle hash, stage them, and write into coinsonly what a lineage proof establishes.
Decision: #383 (comment) (normative — read it first).
Sibling: PR-A carries the attributor wiring and closes #382. This ticket closes #380.
The shape
Derived-hash arrivals land in a new cat_admission_pending table. Only lineage-proven coins are ever written to coins, fully attributed. coins keeps exactly origin/main's semantics.
Why a staging table and not a flag on coins
git grep "FROM coins" finds 18 production sites in db.rs alone, plus 4 in rpc.rs and arrivals.rs — 22 readers. A lineage_proven column makes safety depend on every one of them remembering to filter.
This PR already proved that enumerations here are incomplete. SPEC §18.11a named the puzzle-hash sets; the implementing lane found seven; the gate found an eighth at sync.rs:957 — and that eighth is exactly where a false "you were paid" notification came from.
A staging table makes a fabricated coin unreachable by absence from the table those readers read, not by a guard they must remember to apply.
The obvious alternative does NOT work — checked, do not retry it
Admitting to coins and letting the existing out-of-band pass promote it cannot work: unexamined_attribution_candidates (crates/dig-wallet/src/sage/db.rs:2652) filters on AND asset_id IS NULL, so a derived-hash row admitted with asset_id = DIG is never a candidate and attribution_examined can never be set on it.
Making it reachable requires asset_id = NULL — which means XCH (db.rs:1021) and feeds the spend-input selector (rpc.rs:2933, :3319). That is the round-5 rejection reason, and it survives intact.
What this must defend against — all five rounds' defects
No unbounded or amplified chain read. Zero on the frame path; roughly one out-of-band read per coin, terminal (a coin is resolved once), and capped.
No denial. A promotion error must never reach a.attribute(db).await? — that is what turned a read failure into a session kill in rounds 3 and 4.
Failure mode is incompleteness, never a wrong figure. A coin not yet promoted is absent, not zero, and not XCH.
A fabricated coin never enters coins — so the balance, the spend selector and the arrivals notification are structurally clean rather than guarded.
The attack this exists to stop: anyone may CREATE_COIN at any puzzle hash, so a coin at the derived CAT hash costs 1 mojo per displayed base unit and needs only the victim's public address — no malicious peer. Round 5 admitted those directly, producing a fabricated balance, a permanent $DIG send kill-switch (selection is largest-first, the coin is unspendable, it never leaves the set), and a false payment notification.
Notes
admit_hinted stays deleted. Its job — distinguishing a real CAT from mojos paid to its address — moves to promotion, off the frame path. That is the whole point: it was the only such defence, and round 4 deleted it for a DoS that promotion does not have.
The eight puzzle-hash sets are the hazard map; re-derive them rather than trusting the list, since it grew twice already.
§2.6 governs: the thinnest path to a real wallet showing a real $DIG balance from discovered coins.
Task
PR-B of the #383 split. Discover CAT coins by derived puzzle hash, stage them, and write into
coinsonly what a lineage proof establishes.Decision: #383 (comment) (normative — read it first).
Sibling: PR-A carries the attributor wiring and closes #382. This ticket closes #380.
The shape
Derived-hash arrivals land in a new
cat_admission_pendingtable. Only lineage-proven coins are ever written tocoins, fully attributed.coinskeeps exactlyorigin/main's semantics.Why a staging table and not a flag on
coinsgit grep "FROM coins"finds 18 production sites indb.rsalone, plus 4 inrpc.rsandarrivals.rs— 22 readers. Alineage_provencolumn makes safety depend on every one of them remembering to filter.This PR already proved that enumerations here are incomplete. SPEC §18.11a named the puzzle-hash sets; the implementing lane found seven; the gate found an eighth at
sync.rs:957— and that eighth is exactly where a false "you were paid" notification came from.A staging table makes a fabricated coin unreachable by absence from the table those readers read, not by a guard they must remember to apply.
The obvious alternative does NOT work — checked, do not retry it
Admitting to
coinsand letting the existing out-of-band pass promote it cannot work:unexamined_attribution_candidates(crates/dig-wallet/src/sage/db.rs:2652) filters onAND asset_id IS NULL, so a derived-hash row admitted withasset_id = DIGis never a candidate andattribution_examinedcan never be set on it.Making it reachable requires
asset_id = NULL— which means XCH (db.rs:1021) and feeds the spend-input selector (rpc.rs:2933,:3319). That is the round-5 rejection reason, and it survives intact.What this must defend against — all five rounds' defects
a.attribute(db).await?— that is what turned a read failure into a session kill in rounds 3 and 4.coins— so the balance, the spend selector and the arrivals notification are structurally clean rather than guarded.The attack this exists to stop: anyone may
CREATE_COINat any puzzle hash, so a coin at the derived CAT hash costs 1 mojo per displayed base unit and needs only the victim's public address — no malicious peer. Round 5 admitted those directly, producing a fabricated balance, a permanent $DIG send kill-switch (selection is largest-first, the coin is unspendable, it never leaves the set), and a false payment notification.Notes
admit_hintedstays deleted. Its job — distinguishing a real CAT from mojos paid to its address — moves to promotion, off the frame path. That is the whole point: it was the only such defence, and round 4 deleted it for a DoS that promotion does not have.