Exposure-aware transaction construction for wallets
1 message, 19 Oct 2026. Started by Zeeshan Ali Khan.
Zeeshan Ali Khan
I would like to discuss an opt-in wallet transaction-construction policy for preserving resistance to BIP 360 long exposure attacks [1]. This is a companion to bitcoin/bips PR #2294 [2], which classifies exposure and informatively recommends SWEEP_WHEN_SPENDING for outputs sharing a script.
Bitcoin Core's -avoidpartialspends [3] and BitGo's quantum-risk controls [4] already group UTXOs by address, but both are best-effort. The proposal here is a stronger wallet-local rule based on everything the wallet knows it is about to disclose.
PR #2294 discussion: Delving Bitcoin [5]; bitcoin-dev [6].
## Motivation
If several P2WPKH outputs pay the same script, spending one reveals the public key needed to spend the others. A wallet preserving long exposure resistance should cover those newly affected outputs in the same spend workflow and send change to a fresh, non-exposed condition.
The boundary can cross scripts when key material is reused. Avoiding unnecessary disclosure may also preserve eligibility for future rescue mechanisms that depend on still-hidden one-way-function commitments, such as DropKick [7].
## Trust boundary
A wallet can reason only about disclosures it knows. Its trust boundary is the set of components and parties assumed not to disclose wallet material to the adversary. This mode is intentionally opt-in because restrictions on public-key sharing can be operationally burdensome [8].
Disclosure includes public keys, xpubs/descriptors, relevant PSBT derivation data, reuse in another protocol, and ECDSA signatures over known messages, from which public-key candidates can be recovered. BIP 340 Schnorr signatures do not have that public-key-recovery property [9]. If an account xpub has left the trust boundary, this protection cannot be claimed for that subtree.
## Exposure set
Call material hidden when it is hash-committed and a complete spend recipe has not been published. An output becomes long-exposure vulnerable only when disclosed material is enough to reconstruct some complete spend recipe: required key material plus any script, Merkle path, preimage, or other path data.
Let D_prior be material already disclosed before this spend, derived from on-chain history (for example PR #2294's classification) plus the wallet's local disclosure metadata. Let S contain the intended inputs. When an output is added, choose one spending path and keep that choice fixed while computing the set:
```text
S = intended inputs
repeat:
previous = S
D = D_prior | material disclosed by the chosen paths for S
V = wallet outputs whose complete spend recipe is reconstructible
from D but not from D_prior alone
S = S | V
until S == previous
R = S ```
Here | is set union. R is the exposure set, and "affected" below means an output in R. Outputs that were already vulnerable before this spend are handled by the remediation workflow and may be added to a protected spend at the user's option; they do not enter V merely because they were already exposed.
Example: if D_prior already contains key a and the intended spend reveals b, a standard sortedmulti 2-of-2 {a,b} script may become newly vulnerable once its script is reconstructible. A hash-committed 2-of-3 {a,b,d} script does not: without d the attacker cannot reconstruct the witnessScript. For ordinary P2SH/P2WSH this normally converges in one step; iteration mainly matters when prior disclosure and spending-path choice interact.
## Transaction construction
Before new material for R leaves the trust boundary, prepare spends covering every confirmed affected output the current signing workflow can spend now, without waiting for a timelock or additional cooperation. If an affected output cannot be covered, the wallet should not claim protection unless the user explicitly overrides it.
If several transactions are required, prepare them before disclosure and hand them to the network in the same batch. Replacements should jointly keep covering R; if coverage is lost through eviction, invalidation or replacement, rebroadcast or reconstruct/fee-bump the affected spends.
### Intended property
When new material is deliberately disclosed, every confirmed affected output in `R` that can be spent now has been handed to the network in the same batch.
## Unconfirmed and local state
Wallet-authored unconfirmed parents are controllable by the wallet; third-party payments may be replaced by their sender. The default should therefore be to wait for relevant third-party outputs to confirm. After a documented or user-configurable wait bound, ask whether to keep waiting or explicitly relax the guarantee. This creates an unavoidable griefing/DoS tradeoff.
Wallet-local disclosure state is deliberately broader than PR #2294's chain-derived classification: once material leaves the trust boundary, keep treating it as disclosed even if the transaction never confirms. A seed/descriptor restore cannot reconstruct every prior off-chain disclosure, so missing disclosure metadata should be reported as unknown rather than safe.
## Change and reused destinations
Change must use a condition whose required secp256k1 material is still hidden. Fresh P2WPKH can do this only if its derivation data stays inside the trust boundary, including through PSBT handling [10]. P2TR cannot, because its output key Q is public at rest and consensus accepts a key-path signature for Q however Q was constructed. P2MR is the intended direction once available [1].
Funds later received to an already disclosed destination are vulnerable immediately. Bitcoin Core's avoid_reuse is useful precedent [11]: mark them as reused/exposed, exclude them from the protection claim, warn the user, and offer remediation instead of automatically sweeping every receipt.
## Privacy and scope
Cross-script grouping can add input linkage. CoinJoin, Payjoin, explicit coin control, timelocks or unavailable cosigners may prevent full coverage. In those cases the wallet should decline to claim the property or require an explicit override. This is a wallet security/privacy/usability policy, not a consensus rule.
## Open questions
1. Is a separate companion proposal, cross-linked from PR #2294, the right place for this rule?
2. Is complete-spend-recipe exposure the right abstraction for Taproot, Miniscript and future scripts?
3. Should economically insignificant outputs ever be excluded automatically, or only by explicit override?
4. What wait policy should apply to relevant third-party unconfirmed outputs?
5. Could BIP 329 label records [12] carry disclosure metadata across backup/sync, or is separate metadata needed?
6. Are there implementations that go beyond the address grouping of Bitcoin Core APS and BitGo by reasoning across complete spending conditions?
The intended guarantee is narrow: wallet behavior should not unnecessarily turn hidden-key outputs into long-exposure-vulnerable outputs while leaving value behind under the newly disclosed spend recipe.
## References
[1] BIP 360: https://github.com/bitcoin/bips/blob/master/bip-0360.mediawiki [2] bitcoin/bips PR #2294: https://github.com/bitcoin/bips/pull/2294 [3] Bitcoin Core -avoidpartialspends source: https://github.com/bitcoin/bitcoin/blob/master/src/wallet/init.cpp [4] BitGo quantum-risk controls: https://www.bitgo.com/resources/blog/bitgo-adds-new-quantum-risk-management-capabilities-for-bitcoin-wallets/ [5] PR #2294 Delving discussion: https://delvingbitcoin.org/t/standardizing-an-exposure-classification-for-existing-outputs-pre-bip/2866 [6] PR #2294 bitcoin-dev discussion: https://groups.google.com/g/bitcoindev/c/A3MFJt8mOM4 [7] DropKick: https://gnusha.org/pi/bitcoindev/CAHPaHkr5r4+hodZv6F2S1yCq_UcHSr2iCrkYLgZ_vryNgwbRPA@mail.gmail.com/ [8] P2XX thread: https://groups.google.com/g/bitcoindev/c/aWYtPLVPZ3U [9] BIP 340: https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki [10] BIP 174 (PSBT): https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki [11] Bitcoin Core avoid_reuse release notes: https://github.com/bitcoin/bitcoin/blob/master/doc/release-notes/release-notes-0.19.0.1.md [12] BIP 329: https://github.com/bitcoin/bips/blob/master/bip-0329.mediawiki