Discussion:
- https://delvingbitcoin.org/t/disposing-of-dust-attack-utxos/2215
- https://groups.google.com/g/bitcoindev/c/pr1z3_j8vTc
co-author: @harismuzaffer
167 | + 168 | +Bitcoin relay policy enforces a minimum transaction base size of 65 bytes to prevent certain attack vectors. Compliant transactions must meet this threshold to be relayed by standard nodes. 169 | + 170 | +### Why 0.1 sat/vB Minimum Fee Rate? 171 | + 172 | +Dust disposal transactions must meet the minimum relay fee rate, which is currently 0.1 sat/vB (Bitcoin Core 28.3, 29.1, 30.0+). This allows dust UTXOs to be disposed of economically even when their value is small. Implementations targeting older node versions may need higher minimum fee rates.
Maybe address theoretical updating if the minimum relay fee rate changes in the future.
Would adding a sentence like this work?
"Dust disposal transactions should always pay at least the current standard minimum relay fee rate if it changes in the future."
Looks complete and correct at first glance.
19 | + 20 | +## Motivation 21 | + 22 | +### The Dust Attack Problem 23 | + 24 | +Dust attacks are a well-documented privacy threat where attackers send small amounts of bitcoin to numerous addresses. When wallet software later consolidates these dust UTXOs with non-dust UTXOs, the attacker can analyze the blockchain to link previously unassociated addresses, potentially deanonymizing users.
Perhaps:
Dust attacks are a well-documented privacy threat where attackers send small amounts of bitcoin to numerous addresses. When wallet software later consolidates these dust UTXOs with non-dust UTXOs, the attacker use the [common-input-ownership heuristic](https://en.bitcoin.it/wiki/Privacy#Common-input-ownership_heuristic) to link previously unassociated addresses together and with future spending, reducing the privacy of users.
36 | + 37 | +- **No New UTXOs**: OP_RETURN outputs are provably unspendable and not stored in the UTXO set. 38 | +- **No Address Linking**: Without a change output, there is no new address to link. 39 | +- **Permanent Removal**: The dust UTXOs are removed from the user's wallet entirely. 40 | +- **Miner Compensation**: OP_RETURN outputs are small, providing higher transaction fee rates. 41 | +- **No Cost to Victims**: Dust attack UTXO values are used to pay for their own disposal.
The term “dust” is so central to this proposal, that you might want to clarify how you use the term. Colloquially, dust refers often to any small amounts, Bitcoin Core defines dust as outputs that are less valuable than specific amounts for specific output types, and another colloquial meaning refers to outputs of amounts that cannot pay for themselves. E.g., 1-sat outputs from the Socchi spam wave cannot pay for themselves even at the new lower feerates.
Edit: I see that line 97 does sort of define “dust”, but perhaps that definition could be moved earlier in the text.
I agree we should clarify that the concern of this BIP is disposing of dust used in a "dust attack", i.e. dust used to de-anonymizing a user using the common input ownership heuristic (as you linked above). We're not concerned with UTXOs that just happen to be too small to pay for themselves or match the exact core definition. I'll work on improving the abstract and/or motivation.
Ah sounds good. I think another term of art for that is “forced address reuse attack”.
Is it OK to rename this proposal to "Dust Attack UTXO Disposal Protocol" ? The goal is to clarify that the protocol is focused on disposing of dust attack UTXOs and not the other kinds of dust UTXOs. I also added an aka "forced address reuse attack" in the Motivation section.
Keeping proposal name "Dust UTXO Disposal Protocol".
56 | +A compliant dust disposal transaction MUST satisfy all the following requirements: 57 | + 58 | +#### Overall 59 | + 60 | +1. The nLockTime MUST be set to block height 0. 61 | +2. The fee rate MUST be at least 0.1 sat/vB.
Perhaps:
2. The fee rate MUST be at least the minimum transaction relay feerate (0.1 sat/vB at the time of writing).
64 | + 65 | +1. The transaction MUST have exactly one output. 66 | +2. The single output MUST be an OP_RETURN. 67 | +3. The OP_RETURN data MUST be either: 68 | + - Empty: `0x6a 0x00` (OP_RETURN OP_0), or 69 | + - The ASCII string "ash": `0x6a 0x03 0x61 0x73 0x68` (OP_RETURN OP_PUSHBYTES_3 "ash").
I had the impression from our discussion on the mailing list that you were considering on always requiring the ash output in order for all transactions to always be aggregatable. Looking at it again, I realize that it was the answer from Steven Slater that made me think so. I assume you considered only standardizing a single output variant, but could you perhaps further explain in the Rationale why you decided to settle on two output variants rather than eating the slightly elevated size for legacy and wrapped segwit transactions in favor of a simpler protocol?
The rationale here is that in most cases there won't be an opportunity to batch, so we should be as efficient as possible for spending the legacy dust who don't get the witness discount. And in practice as legacy usage dies off most dust disposal tx will have witness inputs and need the OP_RETURN "ash" output anyway. I'll add this to the rationale section.
Working on an update to reference code per bubb1es71/ddust#36 and then will update BIP here.
71 | +#### Inputs 72 | + 73 | +1. All inputs MUST set the nSequence to 0xFFFFFFFF (do not signal for BIP 125 RBF). 74 | +2. All inputs MUST use the signature hash type `ALL|ANYONECANPAY` (0x81). 75 | +3. For Taproot (P2TR) inputs using key-path spending, implementations MUST explicitly append the signature hash type byte `ALL|ANYONECANPAY` (0x81) to enable ANYONECANPAY semantics, as the default sighash for Taproot (SIGHASH_DEFAULT, which omits the byte) does not include ANYONECANPAY. 76 | +4. All inputs must be confirmed in the blockchain at least one block deep.
I’m curious why you require that inputs must be confirmed. Could you please add that to the Rationale?
The thinking is that we don't want a dust attacker to see their dust is being disposed of and double spend it before it's confirmed. And if the dust is included in a batch with other disposed dust this could be a way for the dust attackers to fight back against (or at least grief) victims trying to dispose of their dust attack UTXOs. Additionally there's generally no urgency in disposing of dust since it can temporarily be locked to prevent it from being accidentally spent while unconfirmed. I'll update the rationale section.
88 | + 89 | +### Dust Selection 90 | + 91 | +Implementations SHOULD allow users to configure how dust UTXOs are selected based on: 92 | + 93 | +1. The current and anticipated fee rates.
Since the feerate directly follows from the potential dust UTXO (i.e. its amount and output type), it is not obvious to me why the current or anticipated feerates play a relevant role: whenever the transaction would be created it would have the same feerate anyway.
I do see that sending a dust disposal transaction when the current demand is low would lead to quick confirmation which would quickly resolve the wallet’s dust situation. However, broadcasting at high feerates would give more time for multiple dust disposals to be aggregated. So, really any time seems fine for different reasons.
I would also point out that if wallets have opinionated default behavior on when to create and broadcast dust disposal transactions, the broadcast time might become a (weak?) wallet fingerprint.
We were thinking here about the dynamic mempoolminfee and that if mempools are full or near full users (or their wallets) may have to make a judgement call when selecting viable dust UTXOs to try to dispose of.
Would it make more sense to rewrite this section to make the only user configurable parameters the max UTXO amount that is considered dust. This is needed since we don't know how the amounts may change in the future. And otherwise dust UTXOs should always be selected if the fee rate of their dispose tx is at least the mempoolminfee rate?
A configurable max amount sounds like a good idea generally. Broadcasting transactions only when they make it into the mempool also sounds reasonable. Although it is not logistically an issue if the transaction were created when it doesn’t propagate. If you e.g., create a transaction in Bitcoin Core it will be rebroadcast every 12–24h until it gets confirmed. Especially if you intend to use privatebroadcast it doesn’t seem like a big privacy issue either, if it were submitted to peers a few more times before it propagates widely.
I forgot that Bitcoin Core will keep rebroadcasting so I think it's safe here to just focus on letting the user configure the max amount.
97 | +A UTXO is generally considered dust if its value is less than the cost to spend it at a reasonable fee rate, but any small UTXO value could be used in a dust attack. 98 | + 99 | +Implementations SHOULD NOT select dust UTXOs for disposal if there are any unspent, non-dust UTXOs for the same address. This is especially important if: 100 | + 101 | +1. No funds have previously been spent from the address. 102 | +2. The address type uses a hashed public key (e.g. P2PKH, P2WPKH).
Perhaps give a recommendation what to do with such outputs:
Implementations SHOULD NOT select dust UTXOs for disposal if there are any unspent, non-dust UTXOs for the same address. This is especially important if:
1. No funds have previously been spent from the address.
2. The address type uses a hashed public key (e.g. P2PKH, P2WPKH).
Instead, the dust UTXO should be spent together with the other UTXOs that share the same output script at the time that any of the UTXOs is selected, or may be disposed after the other UTXOs have been spent, if it would reduce the feerate of the transaction that spends the other UTXOs.
Instead, the dust UTXO should be spent together with the other UTXOs that share the same output script at the time that any of the UTXOs is selected, or may be disposed after the other UTXOs have been spent, if it would reduce the feerate of the transaction that spends the other UTXOs.
I'd rather not include the above since it gets into the whole topic of how to safely spend dust UTXOs and not how to dispose of them.
A protocol for safely and efficiently spending dust UTXOs to a non-OR outputs should probably be its own BIP.
104 | +### Address Consolidation Rules 105 | + 106 | +Implementations consolidating dust UTXOs owned by a single user (i.e., not batching unrelated dust UTXOs): 107 | + 108 | +- MUST NOT spend dust UTXOs that were sent to different addresses in the same transaction. 109 | +- MUST NOT broadcast dust disposal transactions at the same time for dust sent to different addresses.
This text should perhaps make clearer that no user should broadcast a transaction with multiple dust inputs in the first instance any of those outputs are seen. However, especially if it were generally recommended best practice for wallets to add their own inputs to transactions seen in the mempool, or to disseminate queued dust disposal transactions whenever any are seen in the mempool, it could be indistinguishable for a disposer to privately broadcast multiple dust disposals in a slightly staggered sequence.
I'll add the recommendation to always try to batch a dust input with an unconfirmed disposal tx if one exists and refer to the batching section.
I get what you mean that privately broadcasting staggered dust should still be private. But there's no way to know what intervals would be safe since we don't know how frequently this will be used. And even if we had some historic data the patterns could change in unexpected ways. I think it's safer to leave it up to human judgement on when and which UTXOs to dispose of.
SGTM.
139 | +- **Key Security**: Signing dust disposal transactions requires signing with the user's wallet private keys. This could be a risk for cold storage wallets where the key or keys needed to sign are not easily accessible. 140 | +- **Transaction Correctness**: Transaction signers must carefully review and verify that only dust UTXOs are spent and no other inputs are signed. 141 | + 142 | +#### Privacy Preservation 143 | + 144 | +- **Network surveillance**: Internet service providers and other internet monitors may be able to determine the nodes that initially broadcast a dust disposal transaction. If available the `sendrawtransaction -privatebroadcast` RPC feature should be used (Bitcoin Core 31.0).
Just the version is a bit underspecified, and it may become available in other software later. How about:
- **Network surveillance**: Internet service providers and other internet monitors may be able to determine the nodes that initially broadcast a dust disposal transaction. If available the `sendrawtransaction -privatebroadcast` RPC feature should be used (e.g., available in Bitcoin Core 31.0+).
155 | +### Why Empty or "ash" OP_RETURN Data? 156 | + 157 | +- **Minimal Size**: Empty data (2 bytes: OP_RETURN OP_0) minimizes the transaction size. 158 | +- **Standardization**: Consistent transaction construction eliminates wallet fingerprinting. 159 | +- **Padding Option**: The "ash" string (5 bytes: OP_RETURN OP_PUSHBYTES_3 "ash") provides a standardized way to meet the minimum transaction size; e.g., for a single P2TR dust input. 160 | +- **Semantic Meaning**: The word "ash" metaphorically represents the result of "burning" the dust.
My gut feeling is that it is still preferable to only standardize one output variant. From the sender’s perspective, the transaction will for itself either way. Since the empty OR-output is only feasible for legacy and wrapped segwit inputs, it increases the transaction size by less than 1.8%, but it makes all transactions aggregatable. It’s just saving 3 bytes, but aggregating a solitary dust input into another transaction saves 21 bytes. So if increased compatibility caused one in seven transactions to be aggregated rather than solitary, it would break even. It would also make it simpler to implement support for this proposal, in particular the aggregation.
I'll think on this a bit more, it would simplify the spec and code.
As mentioned in my other comment our assumption is that batching won't happen too often and eventually be all segwit OR "ash" variety anyway. But OTOH if this spec becomes widely adopted there are plenty of dust UTXOs out there that can/should be disposed of and batching saves more than the extra "ash" bytes.
161 | + 162 | +### Why Per-Address Transactions? 163 | + 164 | +Consolidating dust from multiple addresses for the same wallet creates the same privacy harm that dust attacks attempt to achieve. By requiring implementations to create separate transactions per address (by default), the protocol ensures dust disposal doesn't harm privacy. 165 | + 166 | +### Why 65 Byte Minimum?
### Why 65-Byte Minimum?
239 | + 240 | +#### Example dust disposal transaction sizes 241 | + 242 | +| | P2PKH | P2SH (2-3) | P2WPKH | P2WSH (2-3) | P2TR | 243 | +|-------------------|-------|------------|--------|-------------|-------| 244 | +| Overhead (b) | 10 | 10 | 10 | 10 | 10 |
Nit: byte is abbreviated with a capital B
259 | +| 325 | 1.92 | 1.03 | 3.51 | 2.51 | 3.95 | 260 | +| 330 | 1.95 | 1.04 | 3.57 | 2.55 | 4.01 | 261 | + 262 | +### Batching dust disposal txs via RBF 263 | + 264 | +1. Adding a Bech32m dust input to an unconfirmed disposal transaction with a legacy dust input keeps the original single empty OP_RETURN output.
If you are going to retain the two output variants, perhaps it should be best practice that any non-native-segwit input that is added to a dust disposal transaction with an “ash” output is first broadcast as a solitary transaction with an empty OP_RETURN ouput, before the aggregated transaction. This would give the other dust disposer with the ASH output the opportunity to instead add their input to the transaction with the empty OR output.
Still, it seems simpler to only standardize one output variant.
Looks pretty good already! Thanks again for working on this so diligently before submitting it here.
Let’s call this BIP 451. Please add the number to the preamble, set the Assigned header to today, rename the BIP file, and add an entry to the README table.
I pushed 54bccb1 with some small edits. I'll squash this and other commits once the review process is done.
* Change title, abstract, motivation to focus on dust attack UTXOs
* Simpify dust selection section
* Add batching to address consolidation rules
* Fix core version in privacy preservation
* Fix table units
Yeah, thanks, adding separate commits without squashing them during the review phase is preferred.
0 | @@ -0,0 +1,281 @@ 1 | +``` 2 | + BIP: 451 3 | + Layer: Applications 4 | + Title: Dust Attack UTXO Disposal Protocol
Weak opinion weakly held, but I thought the title was better without “Attack”. It doesn’t really matter how the Dust came to be in the wallet, if the user decides to get rid of it.
Title: Dust UTXO Disposal Protocol
I don't have a strong preference here either, just want to be clear about the focus of the proposal. But that is already done in the text of the doc. I'll change it back since it's shorter and reads a little better.
The Preamble updates and table entry look good. Thanks for the quick turn-around. Given that you said that you are still mulling over some of the review comments, I’ll leave the “PR Author action required” tag for the time being. Please let me know when you’re ready for another review.
@murchandamus this one is ready for another look. In 686c1b0 I updated it to use only OP_RETURN "ash" and some other small edits.
Thanks, I looked only over the updates since my full review. Everything looks good to me, no further comments at this time.