Task
One unauthenticatable coin, placed by anyone for dust, permanently blocks every mirror-coin create on a node.
The mechanism
crates/dig-node-service/src/mirror/funding.rs:269-271:
cats.push(authenticate(...)?);
The ? aborts the entire selection on the first coin that fails authentication. Three facts compose into the attack:
- The address is publicly derivable. Coins are gathered from
dig_cat_puzzle_hash(owner), which anyone can compute from the operator's public owner puzzle hash.
- Selection is largest-first. So a coin with an amount above the largest honest coin is examined first.
- Failure aborts rather than skips. One unauthenticatable coin at the front of that ordering means the selection never reaches the operator's real coins.
Cost to the attacker: dust. A single CAT coin at that address with a large declared amount and no valid lineage. Cost to the operator: no mirror coin can ever be created, so no content can be bonded, and the node silently stops participating in the collateral economy it is configured for.
The fix, and the direction it must fail
Skip and continue, rather than abort. An unauthenticatable coin is not this operator's coin — it is noise at a public address, and noise at a public address is the normal condition of a public address. It should cost one authentication attempt and nothing else.
Two things for whoever takes it:
- Do not simply swallow the error. A coin that fails authentication for an unexpected reason is worth a log line, because the same code path also covers a genuine bug in lineage checking. Skipping silently would hide that. Skip, count, and log at a level an operator can see.
- Watch the bound interaction.
MAX_SELECTED_FUNDING_COINS = 32 (funding.rs:69) caps how many inputs are selected. If skipping consumes budget against that cap, an attacker can still exhaust it with 32 dust coins — the skip must not count toward the selection limit, or the DoS survives in a slower form.
Provenance — not introduced by the work that found it
Found by the security gate of #457, which correctly rated it pre-existing and outside its diff and did not gate on it. That PR strictly improves this surface (it adds the input bound); it did not create this.
Evidence
funding.rs:269-271 read directly. Not fired — no coin was placed at a real address to confirm the ordering empirically, and the largest-first claim is from reading the selection, not from observing it. Confirming that ordering is the first step of any fix.
Acceptance
An unauthenticatable coin at the operator's public CAT address does not prevent a create — proven by a test that places one ahead of a valid coin in the selection order and asserts the create still succeeds using the valid one. A test that merely asserts authenticate returns an error proves nothing about the selection.
Task
One unauthenticatable coin, placed by anyone for dust, permanently blocks every mirror-coin create on a node.
The mechanism
crates/dig-node-service/src/mirror/funding.rs:269-271:The
?aborts the entire selection on the first coin that fails authentication. Three facts compose into the attack:dig_cat_puzzle_hash(owner), which anyone can compute from the operator's public owner puzzle hash.Cost to the attacker: dust. A single CAT coin at that address with a large declared amount and no valid lineage. Cost to the operator: no mirror coin can ever be created, so no content can be bonded, and the node silently stops participating in the collateral economy it is configured for.
The fix, and the direction it must fail
Skip and continue, rather than abort. An unauthenticatable coin is not this operator's coin — it is noise at a public address, and noise at a public address is the normal condition of a public address. It should cost one authentication attempt and nothing else.
Two things for whoever takes it:
MAX_SELECTED_FUNDING_COINS = 32(funding.rs:69) caps how many inputs are selected. If skipping consumes budget against that cap, an attacker can still exhaust it with 32 dust coins — the skip must not count toward the selection limit, or the DoS survives in a slower form.Provenance — not introduced by the work that found it
Found by the security gate of #457, which correctly rated it pre-existing and outside its diff and did not gate on it. That PR strictly improves this surface (it adds the input bound); it did not create this.
Evidence
funding.rs:269-271read directly. Not fired — no coin was placed at a real address to confirm the ordering empirically, and the largest-first claim is from reading the selection, not from observing it. Confirming that ordering is the first step of any fix.Acceptance
An unauthenticatable coin at the operator's public CAT address does not prevent a create — proven by a test that places one ahead of a valid coin in the selection order and asserts the create still succeeds using the valid one. A test that merely asserts
authenticatereturns an error proves nothing about the selection.