Standardizing an exposure classification for existing outputs (pre-BIP)

BIP 360 gives us an output type whose key stays off-chain until a script-path spend, and BIP 361 proposes a phased sunset of legacy signature verification. Both presuppose something no BIP currently specifies: a shared answer to “which existing outputs are exposed?”

I’ve drafted a specification for exactly that, and I’d like this forum’s scrutiny before taking it further.

Why I think it’s worth specifying. Published estimates of the exposed supply range from ~25% to over 34%. Having chased the sources, I’m fairly convinced the spread is definitional rather than measurement error — tools disagree on whether P2TR counts as exposed at rest, whether a spent-from address holding a balance differs from P2PK, and what to report for a P2SH whose script was never revealed. If a BIP 361-style migration activates, that question gets asked at scale, with money attached, by software that ought to agree.

What the draft does. Four levels — EXPOSED_AT_REST, EXPOSED_ON_SPEND, NOT_EXPOSED, UNDETERMINED — with a per-output-type assignment table and a fail-closed rule: where the data can’t distinguish two levels, the more-exposed one must be assigned. Two consequences are likely to be the contentious ones:

  • A reused, spent-from P2PKH that still holds a balance is EXPOSED_AT_REST, the same level as P2PK. To an adversary they’re the same situation, and labelling them differently quietly implies reuse is safer than P2PK.

  • P2TR is at rest whether or not the internal key is NUMS, since consensus never checks how Q was constructed. As I read it, that’s precisely why BIP 360 removes the key path — the discussion in the BIP-360 changes thread is what convinced me to state it this plainly.

Draft, a dependency-free Python reference implementation, and test vectors:

It’s a draft, not a finished BIP. What I’d most like to know is whether the four-level partition holds up against situations you’ve hit in practice, and whether this belongs in a BIP at all versus staying an implementation detail.

If the answer is the latter, I’d rather hear it now.

From a wallet user’s point of view, each level may be more useful if it also has a clear recommended action—for example, no action needed, avoid reusing the address, or move the funds when a safer option is available. Is that guidance intended to be standardized, or left to each wallet? Otherwise, different wallets could show very different warnings for the same situation.

Apologies for the slow reply — this got lost behind the mailing-list thread.

This is a distinction I should have drawn in the draft and didn’t. I excluded risk scoring on purpose — weighing balance, dormancy and CRQC timelines is subjective and not chain-observable. But a recommended action is different: each level implies one almost deterministically, and you’re right that leaving it unstated just moves the divergence from “which coins are exposed” to “what should I do about it”.

What I think each level implies, as a floor:

  • EXPOSED_AT_REST — move to a non-exposed output type as soon as one you trust exists; until then, stop adding to it.
  • EXPOSED_ON_SPEND — nothing needed at rest; when you do spend, sweep the whole balance and never send change or new funds back, since the first spend makes it AT_REST.
  • NOT_EXPOSED — nothing.
  • UNDETERMINED — treat as AT_REST until your software can classify it.

The awkward part is “when a safer option is available”: before something like BIP 360 activates, the only safer option is fresh-key hygiene; after, there’s an actual destination. So the action text can’t hardcode a target type.

Proposal for v0.4.0: an informative appendix mapping each level to a SHOULD-level minimum action, with wallets free to say more but not to contradict it. That standardizes the floor without dragging the spec into scoring. In the scanner I run, the four levels already map to exactly four advice strings, and that’s held up in production — so a deterministic mapping seems workable.

From a wallet’s side: is that granularity useful, and would you want the action as a machine-readable key alongside the level, or is prose enough?

(v0.3.0 is up, addressing the P2TR points raised on the mailing list.)

From the user side, clear prose is essential. Having a shared action key behind it also seems useful, so different wallets can translate and present the same basic advice consistently. I would keep the actions simple, with each wallet explaining the reason and urgency in plain language.

Thanks, that settles it. v0.4.0 will carry an informative appendix with a small fixed set of action keys, one per level, and a one-line SHOULD floor for each. Wallets own the wording, the reasons and the urgency on top of that. I’ll post here when it’s up.

v0.4.0 is up: pubkey-exposure-classification/bip-output-pubkey-exposure-classification.md at fb0bc77a58991d43cad1095912e38984d88702fb · duncan0k/pubkey-exposure-classification · GitHub

Appendix A gives each level one action key — MIGRATE, SWEEP_WHEN_SPENDING, NONE, TREAT_AS_MIGRATE — plus a one-line floor. Wallets own everything above that. The Abstract and Motivation were also rewritten to state the reasoning directly. If the floor wording needs adjusting from the wallet side, say so and I’ll fold it in.