Conversation
- Add `TxBuilder::avoid_reuse` to exclude UTXOs on reused addresses from automatic coin selection (privacy, addresses bitcoindevkit#28 / Core #13756)
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #541 +/- ##
==========================================
+ Coverage 81.91% 81.97% +0.06%
==========================================
Files 25 25
Lines 6535 6557 +22
Branches 302 302
==========================================
+ Hits 5353 5375 +22
Misses 1075 1075
Partials 107 107
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Dmenec
left a comment
There was a problem hiding this comment.
cACK, nice feature :)
AFAIK Core only flags an address as used once the wallet has spent from it from then on, any coin sent to it is avoided. The PR description describes it the same way, but the implementation flags any address with more than one received output, even if it was never spent from.
Small test showing it:
let addr = wallet.reveal_next_address(KeychainKind::External).address;
// sent 2 outputs to the same address
receive_output_to_address(&mut wallet, addr.clone(), Amount::from_sat(100_000), ReceiveTo::Mempool(0));
receive_output_to_address(&mut wallet, addr, Amount::from_sat(546), ReceiveTo::Mempool(0));
let mut builder = wallet.build_tx();
builder.add_recipient(recipient.script_pubkey(), Amount::from_sat(10_000)).avoid_reuse();
// the 100k sats are excluded
assert!(matches!(
builder.finish(),
Err(CreateTxError::CoinSelection(e)) if e.available == Amount::ZERO
));Not sure if this was intentional. If it was, I'd document that it differs from Core. I would follow Core's approach as anyone could grief UTXOs knowing this feature.
| // `(keychain, derivation index)` maps 1:1 to a script pubkey, so a count > 1 means the | ||
| // address received funds more than once, i.e. it was reused. | ||
| let mut output_counts: HashMap<(KeychainKind, u32), usize> = HashMap::new(); | ||
| for &((keychain, index), _) in self.wallet.spk_index().outpoints() { |
There was a problem hiding this comment.
wallet.list_output() may fit better here. It is canonical and includes both spent and unspent outputs.
I think in any case it should be canonicalized, for cases like someone paying you and bumping fees, which right now would be seen as a double payment to this address, excluding your coin.
Maybe we could add some tests trying replaced payments.
| .unwrap() | ||
| .add_utxo(reused_outpoint_2) | ||
| .unwrap(); | ||
| assert!(builder.finish().is_ok()); |
There was a problem hiding this comment.
nit: could assert the reused coins are actually selected
| assert!(builder.finish().is_ok()); | |
| let psbt = builder.finish().unwrap(); | |
| let selected: Vec<OutPoint> = psbt | |
| .unsigned_tx | |
| .input | |
| .iter() | |
| .map(|i| i.previous_output) | |
| .collect(); | |
| assert!(selected.contains(&reused_outpoint)); | |
| assert!(selected.contains(&reused_outpoint_2)); |
|
Should “reused address” mean “received more than one output,” or should it mean “the wallet has already spent from this address, and then later received more coins there”? |
Description
Problem:
An adversary can attack a wallet's privacy through forced address reuse: after observing one of the addresses the user have already spent from, they can send small outputs to it. If the users' wallet later select those coins into a transaction, the adversary learns which inputs the user controls and the destinations they pay to, linking their UTXOs.
The TxBuilder has no way to automatically prevent spending outputs in an address that has already been spent from. This is one of the open privacy items tracked in #28, and mirrors Bitcoin Core's
avoid_reusewallet flag (bitcoin/bitcoin#13756).Approach:
Add an opt-in transaction builder method:
Tradeoff:
Notes to the reviewers
add_utxo().Changelog notice
TxBuilder::avoid_reuseto prevent TxBuilder from selecting reused address UTXOsBefore submitting