-
Notifications
You must be signed in to change notification settings - Fork 37.7k
wallet: Strictly match tx change type to improve privacy #23789
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Obviously this will fail tests and need docs fixed up. I'll do that unless there are NACKs. |
cb66fbf
to
26682d5
Compare
Concept ACK |
Concept ACK |
1 similar comment
Concept ACK |
26682d5
to
faf4958
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Concept + tACK faf4958
This mixes change with destination better than previous logic.
Below are just some small nits.
@@ -348,9 +348,9 @@ def run_test(self): | |||
if self.options.descriptors: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems now the change type behavior for both type of wallets is same. So this can be removed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the whole test needs to be re-written from scratch, but I want to keep the changes minimal here.
faf4958
to
fada6c6
Compare
Force pushed to fix test logs |
Concept & Approach ACK fada6c6. Also did light code review . |
Tested with some transactions on testnet by using different types of address:
|
Excellent. Thank you for testing. If you want to match P2TR, you need a descriptor wallet with a |
Yes that works for P2TR: |
and for completeness: P2WSH is impossible to match, since the wallet doesn't support it |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And it's useless even to try to match as it will likely differ on spend. |
WSH is a corner case anyway (less than 5%) according to https://transactionfee.info/charts/output-type-distribution-count/ |
I was assuming its used for multisig because of this note mentioned in caravan: |
Not necessarily. A descriptor wallet can have an active |
ACK fada6c6 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tACK fada6c6
Tested on signet with legacy
, p2sh-segwit
, bech32
, and bech32m
address types.
Interesting. Could the wallet also spend the created wsh change output? |
@achow101 Ready for merge? |
This feels like an incredibly fast merge for a privacy related feature that requires careful consideration. I need to review the code specifically, but if I understand it correctly this will majorly slow the adoption of Taproot because you'll have transitive slowness to upgrade (e.g., I made a segwit change to be compatible with a old segwit destination I was paying to, now my change is segwit, now future users will make segwit change to pay me, etc). Please revert and make a mailing list post. |
I gues maybe part of what I'm missing is that only outputs are analyzed, not inputs, so it's not as bad as I thought it might be in terms of transitivity. I still think this could slow Tr adoption quite a bit, and I think that's worth getting a better idea of before deploying this. |
No, inputs are not considered.
This is a local wallet policy, it does not need to be discussed on the mailing list.
I see that it could slow adoption a bit, but it is overall an improvement to how we determine change addresses IMO. We have plenty of time until 23.0 to change it, and changing the order of preferences would not be particularly difficult. |
Making it difficult to identify change in a transaction is a great improvement. |
Well it still does worsen privacy since then my wallet will be spending a mix out output types rather than just all taproot. And the observability of this pattern (change matching) is also... pretty observable, especially if bitcoin core (and only upgraded wallets) are the ones doing it. This becomes worse the slower taproot uptake is. Further, when you do a subsequent spend, unless our coin selection algorithm is primed to only spend like-kind togehter (not sure it is?) we end up re-correlating the legacy output with the new format, linking the idea that that output was likely one from our wallet as it's with the upgraded type. In order to safely get taproot outputs you'd have to double-tap your old style outputs into a all taproot self-pay payout txn, or find an opportunity to do so nautrally as a payment. If you were to take your old outputs and pay to a new txn and make a Tr change, you also get screwed because then it's known that your wallet did really have Tr enabled and was faking. I think it's really hard to avoid these cases. The main benefit here is decorrelation of change / payment at time of flight of txn. But privacy is a property that we also have to weigh the costs and benefits for future behaviors as well, and I think it's worth tightly considering this PR under that lens. I agree that privacy > all else, but this change should have IMO a pretty negative effect on the herd benefit. And privacy only can be created within the walls of an anonymity set. The only thing worse than no privacy is a false sense of one. I think the best path to build the anonymity set is to just go all-in taproot change. |
I don't see how this is any worse than a normal spend which is likely to correlate change to your wallet anyways. The purpose is to make it harder to determine which is change while the UTXOs are unspent. Hiding which outputs are change is already tenuous at best.
Or just set The behavior implemented here also is fairly similar to what we did for segwit. The default address type was p2sh-segwit, and we would only use bech32 if a p2wpkh or p2wsh recipient was observed. |
It's worse because the subsequent spend mixing inputs shows that our wallet did in fact support (other type) and is therefore likely to be the change address because it's unlikely we requested to be paid in (old type).
I'm analyzing the impact for a user who uses the default configuration, not what someone can do. If the default configuration isn't a good idea, it shouldn't be default.
I don't think that results from segwit v0 apply here since there was an entirely different mood around adoption which necessitated the segwit have 2 address types for p2sh wrapping and there was real risk of refusing to integrate. Noting also the negative scaling impact this has on a CISA future, for example, if a lot of the change outputs created are to be long lived. |
There are few other wallets that do the same thing.
Good point. Will have to test this. I think this can be improved but it would be out of scope for this PR. |
the best case we could do would probably be to draw output types from some distribution matching the chain data over the last 2 weeks and encode a small bias towards the newer type. The herd would move in unison towards new types, and you would actually not be able to learn more information than the bias amount. note: this would have to apply to address creation too |
Not true. The default output type is still bech32 - users using the current defaults will still give out bech32 addresses, and so they will be requesting payments to bech32 addresses. However, their wallet may have tr descriptors, in which case they support Taproot. So they would be giving out p2wpkh addresses, and potentially receiving change as p2tr if the recipient gives them a p2tr address. Additionally, this is all about address type and not script type. Future witness versions (are expected to) use bech32m as well, so in the future, unless the recipient was asked to be paid in (really old type), the change is likely to be (future witness version) even if the recipient asked for (older witness version).
You gave a scenario where the user is trying to use all taproot. If a user knows enough about address types and wants to only use a specific type, I think they can figure out how to set the option. We can also add a GUI setting for it too. |
@JeremyRubin For reference I implemented what you suggest first in #23731, but then decided to abandon it. re: process. I think that a pull request is ready to be merged if it was reviewed. Surely, a behaviour changing pull request should be open for a few days to let more eyes take a look at it. I think 11 days is more than enough for that, since it will never be possible to get everyone's eye on it. And this is "just" the master branch. If for some reason all reviewers messed up and did the wrong thing, there is still plenty of time to revisit before a release. re: content: With this patch, there should still be a bias toward "promoting" tr, since any tr output will lead to tr change. Also, I am not sure if it makes sense to consider input-bundling here. If the analysis firm uses input-bundling as a heuristic, then it can determine the wallet inputs regardless of their type already. If they don't, since it might be a coinjoin, then it shouldn't matter. |
The |
…pes` during coin selection 71d1d13 test: add unit test for AvailableCoins (josibake) da03cb4 test: functional test for new coin selection logic (josibake) 438e048 wallet: run coin selection by `OutputType` (josibake) 77b0707 refactor: use CoinsResult struct in SelectCoins (josibake) 2e67291 refactor: store by OutputType in CoinsResult (josibake) Pull request description: # Concept Following bitcoin/bitcoin#23789, Bitcoin Core wallet will now generate a change address that matches the payment address type. This improves privacy by not revealing which of the outputs is the change at the time of the transaction in scenarios where the input address types differ from the payment address type. However, information about the change can be leaked in a later transaction. This proposal attempts to address that concern. ## Leaking information in a later transaction Consider the following scenario:  1. Alice has a wallet with bech32 type UTXOs and pays Bob, who gives her a P2SH address 2. Alice's wallet generates a P2SH change output, preserving her privacy in `txid: a` 3. Alice then pays Carol, who gives her a bech32 address 4. Alice's wallet combines the P2SH UTXO with a bech32 UTXO and `txid: b` has two bech32 outputs From a chain analysis perspective, it is reasonable to infer that the P2SH input in `txid: b` was the change from `txid: a`. To avoid leaking information in this scenario, Alice's wallet should avoid picking the P2SH output and instead fund the transaction with only bech32 Outputs. If the payment to Carol can be funded with just the P2SH output, it should be preferred over the bech32 outputs as this will convert the P2SH UTXO to bech32 UTXOs via the payment and change outputs of the new transaction. **TLDR;** Avoid mixing output types, spend non-default `OutputTypes` when it is economical to do so. # Approach `AvailableCoins` now populates a struct, which makes it easier to access coins by `OutputType`. Coin selection tries to find a funding solution by each output type and chooses the most economical by waste metric. If a solution can't be found without mixing, coin selection runs over the entire wallet, allowing mixing, which is the same as the current behavior. I've also added a functional test (`test/functional/wallet_avoid_mixing_output_types.py`) and unit test (`src/wallet/test/availablecoins_tests.cpp`. ACKs for top commit: achow101: re-ACK 71d1d13 aureleoules: ACK 71d1d13. Xekyo: reACK 71d1d13 via `git range-diff master 6530d19 71d1d13` LarryRuane: ACK 71d1d13 Tree-SHA512: 2e0716efdae5adf5479446fabc731ae81d595131d3b8bade98b64ba323d0e0c6d964a67f8c14c89c428998bda47993fa924f3cfca1529e2bd49eaa4e31b7e426
Currently the change type will only match a destination by accident, making it easier to determine the change.
Fix that by strictly matching one of the destinations.