-
Notifications
You must be signed in to change notification settings - Fork 262
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
Deniability - a tool to automatically improve coin ownership privacy #733
Conversation
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. ReviewsSee the guideline for information on the review process.
If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update. ConflictsReviewers, this pull request conflicts with the following ones:
If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first. |
Has that idea been discussed somewhere already? |
The idea was published quite a while ago, but I learned about it from some Bitcoiner friends just recently. |
This is really cool and I'd love to have it as an option in Bitcoin Core. However, most CoinJoin implementations are more advanced in this regard – for example, WabiSabi performs both divisions and consolidations. And it makes me wonder to which extent it's possible to decentralize coordination to the point where clients randomly take turns in doing it. Anyway, this is just my curiosity/wish list. I believe that anything is better than having no privacy options at all, so I'd be happy with some more basic deniability too :) |
It seems like a feature like this would be better to integrate into the wallet directly rather than something that is done through and managed by the GUI. |
Yes, CoinJoin is definitely more powerful, however it has some drawbacks. That's the "meta-privacy" that Paul talks about in his blog post. The idea that the knowledge of a secret has to also be kept a secret. Here's another one of Paul's blog posts where he talks about drawbacks of other privacy methods: https://www.truthcoin.info/blog/expensive-privacy/ At any rate, I'm definitely open to suggestions or ideas we can borrow from CoinJoin.
Thank you. I believe so too. |
Do you mean moving some of the core logic inside the interfaces::Wallet API, or the CWallet implementation? Yes, certainly, that could have benefits. I had to workaround certain things that weren't in the Wallet API (namely address reservation, bump fee handling etc). So that would clean up the wallet side for sure. Since this was my first Bitcoin Core change I decided to be more conservative and avoid API changes. |
Inside of
The API isn't public, so changing it isn't a big concern. Really the only consumer of it is the GUI. Having it inside of |
Ah, I see. I had assumed the opposite - that not touching the API would be easier to accept. I'd need to open a PR against the main repo in that case, right? |
Yes
You can leave this open and rebase it onto the main repo PR, just mention it in the description. |
All right, I'll start working on this refactor. |
This PR is the wallet API and implementation portion of the GUI PR ( bitcoin-core/gui#733 ) which is an implementation of the ideas in Paul Sztorc's blog post "Deniability - Unilateral Transaction Meta-Privacy"(https://www.truthcoin.info/blog/deniability/). The GUI PR has all the details and screenshots of the GUI additions. Here I'll just copy the relevant context for the wallet API changes: " In short, Paul's idea is to periodically split coins and send them to yourself, making it look like common "spend" transactions, such that blockchain ownership analysis becomes more difficult, and thus improving the user's privacy. I've implemented this as an additional "Deniability" wallet view. The majority of the code is in a new deniabilitydialog.cpp/h source files containing a new DeniabilityDialog class, hooked up to the WalletView class. " While the Deniability dialog can be implemented entirely with the existing API, adding the core "deniabilization" functions to the CWallet and interfaces::Wallet API allows us to implement the GUI portion with much less code, and more importantly allows us to add RPC support and more thorough unit tests.
I refactored the code and updated the PR. The new code is split between two branches:
|
db722e4
to
747f1fb
Compare
This PR is the wallet API and implementation portion of the GUI PR ( bitcoin-core/gui#733 ) which is an implementation of the ideas in Paul Sztorc's blog post "Deniability - Unilateral Transaction Meta-Privacy"(https://www.truthcoin.info/blog/deniability/). The GUI PR has all the details and screenshots of the GUI additions. Here I'll just copy the relevant context for the wallet API changes: " In short, Paul's idea is to periodically split coins and send them to yourself, making it look like common "spend" transactions, such that blockchain ownership analysis becomes more difficult, and thus improving the user's privacy. I've implemented this as an additional "Deniability" wallet view. The majority of the code is in a new deniabilitydialog.cpp/h source files containing a new DeniabilityDialog class, hooked up to the WalletView class. " While the Deniability dialog can be implemented entirely with the existing API, adding the core "deniabilization" functions to the CWallet and interfaces::Wallet API allows us to implement the GUI portion with much less code, and more importantly allows us to add RPC support and more thorough unit tests.
b3e349c
to
6d07de6
Compare
This PR is the wallet API and implementation portion of the GUI PR ( bitcoin-core/gui#733 ) which is an implementation of the ideas in Paul Sztorc's blog post "Deniability - Unilateral Transaction Meta-Privacy"(https://www.truthcoin.info/blog/deniability/). The GUI PR has all the details and screenshots of the GUI additions. Here I'll just copy the relevant context for the wallet API changes: " In short, Paul's idea is to periodically split coins and send them to yourself, making it look like common "spend" transactions, such that blockchain ownership analysis becomes more difficult, and thus improving the user's privacy. I've implemented this as an additional "Deniability" wallet view. The majority of the code is in a new deniabilitydialog.cpp/h source files containing a new DeniabilityDialog class, hooked up to the WalletView class. " While the Deniability dialog can be implemented entirely with the existing API, adding the core "deniabilization" functions to the CWallet and interfaces::Wallet API allows us to implement the GUI portion with much less code, and more importantly allows us to add RPC support and more thorough unit tests. ----- Implemented basic deniability unit tests to wallet_tests ----- Implemented a new 'walletdeniabilizecoin' RPC. ----- Implemented fingerprint spoofing for deniabilization (and fee bump) transactions. Currently spoofing with data for 6 different wallet implementations, with 4 specific fingerprint-able behaviors (version, anti-fee-sniping, bip69 ordering, no-rbf).
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.
Didn't conceptually review yet. While rebasing, the clang-tidy CI highlighted these issues to be addressed:
wallet/spend.cpp:1539:9: error: function 'CalculateDeniabilizationFeeEstimate' is within a recursive call chain [misc-no-recursion,-warnings-as-errors]
1539 | CAmount CalculateDeniabilizationFeeEstimate(const CScript& shared_script, CAmount total_value, unsigned int num_utxos, unsigned int deniabilization_cycles, const CFeeRate& fee_rate)
| ^
wallet/spend.cpp:1539:9: note: example recursive call chain, starting from function 'CalculateDeniabilizationFeeEstimate'
wallet/spend.cpp:1552:40: note: Frame #1: function 'CalculateDeniabilizationFeeEstimate' calls function 'CalculateDeniabilizationFeeEstimate' here:
1552 | CAmount futureDeniabilizationFee = CalculateDeniabilizationFeeEstimate(shared_script, total_value / NUM_DENIABILIZATION_OUTPUTS, 1, deniabilization_cycles + 1, fee_rate) * NUM_DENIABILIZATION_OUTPUTS;
| ^
wallet/spend.cpp:1552:40: note: ... which was the starting point of the recursive call chain; there may be other cycles
wallet/spend.cpp:1561:31: error: function 'CalculateDeniabilizationCycles' is within a recursive call chain [misc-no-recursion,-warnings-as-errors]
1561 | std::pair<unsigned int, bool> CalculateDeniabilizationCycles(CWallet& wallet, const COutPoint& outpoint)
| ^
wallet/spend.cpp:1561:31: note: example recursive call chain, starting from function 'CalculateDeniabilizationCycles'
wallet/spend.cpp:1634:23: note: Frame #1: function 'CalculateDeniabilizationCycles' calls function 'CalculateDeniabilizationCycles' here:
1634 | auto inputStats = CalculateDeniabilizationCycles(wallet, txIn.prevout);
Yes, it looks like recursive functions were disallowed recently. |
This PR is the wallet API and implementation portion of the GUI PR ( bitcoin-core/gui#733 ) which is an implementation of the ideas in Paul Sztorc's blog post "Deniability - Unilateral Transaction Meta-Privacy"(https://www.truthcoin.info/blog/deniability/). The GUI PR has all the details and screenshots of the GUI additions. Here I'll just copy the relevant context for the wallet API changes: " In short, Paul's idea is to periodically split coins and send them to yourself, making it look like common "spend" transactions, such that blockchain ownership analysis becomes more difficult, and thus improving the user's privacy. I've implemented this as an additional "Deniability" wallet view. The majority of the code is in a new deniabilitydialog.cpp/h source files containing a new DeniabilityDialog class, hooked up to the WalletView class. " While the Deniability dialog can be implemented entirely with the existing API, adding the core "deniabilization" functions to the CWallet and interfaces::Wallet API allows us to implement the GUI portion with much less code, and more importantly allows us to add RPC support and more thorough unit tests. ----- Implemented basic deniability unit tests to wallet_tests ----- Implemented a new 'walletdeniabilizecoin' RPC. ----- Implemented fingerprint spoofing for deniabilization (and fee bump) transactions. Currently spoofing with data for 6 different wallet implementations, with 4 specific fingerprint-able behaviors (version, anti-fee-sniping, bip69 ordering, no-rbf). ----- Implemented CalculateDeniabilizationFeeEstimate as a non-recursive function.
This PR is the wallet API and implementation portion of the GUI PR ( bitcoin-core/gui#733 ) which is an implementation of the ideas in Paul Sztorc's blog post "Deniability - Unilateral Transaction Meta-Privacy"(https://www.truthcoin.info/blog/deniability/). The GUI PR has all the details and screenshots of the GUI additions. Here I'll just copy the relevant context for the wallet API changes: " In short, Paul's idea is to periodically split coins and send them to yourself, making it look like common "spend" transactions, such that blockchain ownership analysis becomes more difficult, and thus improving the user's privacy. I've implemented this as an additional "Deniability" wallet view. The majority of the code is in a new deniabilitydialog.cpp/h source files containing a new DeniabilityDialog class, hooked up to the WalletView class. " While the Deniability dialog can be implemented entirely with the existing API, adding the core "deniabilization" functions to the CWallet and interfaces::Wallet API allows us to implement the GUI portion with much less code, and more importantly allows us to add RPC support and more thorough unit tests. ----- Implemented basic deniability unit tests to wallet_tests ----- Implemented a new 'walletdeniabilizecoin' RPC. ----- Implemented fingerprint spoofing for deniabilization (and fee bump) transactions. Currently spoofing with data for 6 different wallet implementations, with 4 specific fingerprint-able behaviors (version, anti-fee-sniping, bip69 ordering, no-rbf). ----- Implemented CalculateDeniabilizationFeeEstimate and CalculateDeniabilizationCycles as non-recursive functions.
e147c60
to
5379115
Compare
This PR is the wallet API and implementation portion of the GUI PR ( bitcoin-core/gui#733 ) which is an implementation of the ideas in Paul Sztorc's blog post "Deniability - Unilateral Transaction Meta-Privacy"(https://www.truthcoin.info/blog/deniability/). The GUI PR has all the details and screenshots of the GUI additions. Here I'll just copy the relevant context for the wallet API changes: " In short, Paul's idea is to periodically split coins and send them to yourself, making it look like common "spend" transactions, such that blockchain ownership analysis becomes more difficult, and thus improving the user's privacy. I've implemented this as an additional "Deniability" wallet view. The majority of the code is in a new deniabilitydialog.cpp/h source files containing a new DeniabilityDialog class, hooked up to the WalletView class. " While the Deniability dialog can be implemented entirely with the existing API, adding the core "deniabilization" functions to the CWallet and interfaces::Wallet API allows us to implement the GUI portion with much less code, and more importantly allows us to add RPC support and more thorough unit tests. ----- Implemented basic deniability unit tests to wallet_tests ----- Implemented a new 'walletdeniabilizecoin' RPC. ----- Implemented fingerprint spoofing for deniabilization (and fee bump) transactions. Currently spoofing with data for 6 different wallet implementations, with 4 specific fingerprint-able behaviors (version, anti-fee-sniping, bip69 ordering, no-rbf). ----- Implemented CalculateDeniabilizationFeeEstimate and CalculateDeniabilizationCycles as non-recursive functions.
5379115
to
ee225c6
Compare
This PR is the wallet API and implementation portion of the GUI PR ( bitcoin-core/gui#733 ) which is an implementation of the ideas in Paul Sztorc's blog post "Deniability - Unilateral Transaction Meta-Privacy"(https://www.truthcoin.info/blog/deniability/). The GUI PR has all the details and screenshots of the GUI additions. Here I'll just copy the relevant context for the wallet API changes: " In short, Paul's idea is to periodically split coins and send them to yourself, making it look like common "spend" transactions, such that blockchain ownership analysis becomes more difficult, and thus improving the user's privacy. I've implemented this as an additional "Deniability" wallet view. The majority of the code is in a new deniabilitydialog.cpp/h source files containing a new DeniabilityDialog class, hooked up to the WalletView class. " While the Deniability dialog can be implemented entirely with the existing API, adding the core "deniabilization" functions to the CWallet and interfaces::Wallet API allows us to implement the GUI portion with much less code, and more importantly allows us to add RPC support and more thorough unit tests. ----- Implemented basic deniability unit tests to wallet_tests ----- Implemented a new 'walletdeniabilizecoin' RPC. ----- Implemented fingerprint spoofing for deniabilization (and fee bump) transactions. Currently spoofing with data for 6 different wallet implementations, with 4 specific fingerprint-able behaviors (version, anti-fee-sniping, bip69 ordering, no-rbf). ----- Implemented CalculateDeniabilizationFeeEstimate and CalculateDeniabilizationCycles as non-recursive functions.
ee225c6
to
a0dc448
Compare
🚧 At least one of the CI tasks failed. HintsMake sure to run all tests locally, according to the documentation. The failure may happen due to a number of reasons, for example:
Leave a comment here, if you need help tracking down a confusing failure. |
6a8d732
to
2a75a71
Compare
🚧 At least one of the CI tasks failed. HintsTry to run the tests locally, according to the documentation. However, a CI failure may still
Leave a comment here, if you need help tracking down a confusing failure. |
This PR is the wallet API and implementation portion of the GUI PR ( bitcoin-core#733 ) which is an implementation of the ideas in Paul Sztorc's blog post "Deniability - Unilateral Transaction Meta-Privacy"(https://www.truthcoin.info/blog/deniability/). The GUI PR has all the details and screenshots of the GUI additions. Here I'll just copy the relevant context for the wallet API changes: " In short, Paul's idea is to periodically split coins and send them to yourself, making it look like common "spend" transactions, such that blockchain ownership analysis becomes more difficult, and thus improving the user's privacy. I've implemented this as an additional "Deniability" wallet view. The majority of the code is in a new deniabilitydialog.cpp/h source files containing a new DeniabilityDialog class, hooked up to the WalletView class. " While the Deniability dialog can be implemented entirely with the existing API, adding the core "deniabilization" functions to the CWallet and interfaces::Wallet API allows us to implement the GUI portion with much less code, and more importantly allows us to add RPC support and more thorough unit tests. ----- Implemented basic deniability unit tests to wallet_tests ----- Implemented a new 'walletdeniabilizecoin' RPC. ----- Implemented fingerprint spoofing for deniabilization (and fee bump) transactions. Currently spoofing with data for 6 different wallet implementations, with 4 specific fingerprint-able behaviors (version, anti-fee-sniping, bip69 ordering, no-rbf). ----- Implemented CalculateDeniabilizationFeeEstimate and CalculateDeniabilizationCycles as non-recursive functions.
@denavila any update on 3 and 4 from #733 (comment)? |
This new feature is an implementation of the ideas in Paul Sztorc's blog post "Deniability - Unilateral Transaction Meta-Privacy"(https://www.truthcoin.info/blog/deniability/). In short, the idea is to periodically split coins and send them to yourself, making it look like a common "spend" transaction, such that blockchain ownership analysis becomes more difficult, and thus improving the user's privacy. This is the GUI portion of the PR (bitcoin-core/gui). The core functionality PR is in the main repo (bitcoin/bitcoin). This PR implements an additional "Deniability" wallet view. The majority of the GUI code is in a new deniabilitydialog.cpp/h source files containing a new DeniabilityDialog class, hooked up via the WalletView class. On startup and on notable events (new blocks, new transactions, etc), we evaluate the privacy of all coins in the wallet, and we build a "deniabilization" candidate list. UTXOs that share the same destination address are grouped together into a single candidate (see DeniabilityDialog::updateCoins and DeniabilityDialog::updateCoinTable). We inspect the blockchain data to find out if we have performed "deniabilization" transactions already, and we count how many "cycles" (self-sends) have been performed for each coin (see DeniabilityDialog::calculateDeniabilizationStats). Since we infer the state entirely from the blockchain data, even if the wallet is restored from a seed phrase, the state would not be lost. This also means that if the user has performed manual self-sends that have improved the ownership privacy, they will be counted too. The user can initiate the "deniabillization" process by pressing a Start button (DeniabilityDialog::startDeniabilization). The process periodically perform a "deniabilization" cycle (DeniabilityDialog::deniabilizeProc). Each such cycle goes as follows: A coin is selected form the candidate list. The more a coin is "deniabilized", the less likely it is to be selected. Smaller coins are also less likely to be selected. If a coin is selected, we prepare and broadcast a transaction, which splits the coin into a pair of new wallet addresses (DeniabilityDialog::deniabilizeCoin). The user can control this process via a Frequency selector and a Budget spinner, which respectively determine how often to perform the cycle and how much total BTC to spend on transaction fees. If Bitcoin Core is left running continuously, the cycles would be performed at the selected frequency (with some randomization). If Bitcoin Core is shutdown, the "deniabilization" process will resume at the next restart, and if more time has elapsed than the selected frequency, it will perform a single cycle. We deliberately don't "catch up" all missed cycles, since that would expose the process to blockchain analysis. The state is saved and restored via QSettings (DeniabilityDialog::loadSettings and DeniabilityDialog::saveSettings). We monitor each broadcasted transaction and we automatically attempt a fee bump if the transaction is still in the memory pool since the previous cycle (DeniabilityDialog::bumpDeniabilizationTx). We don't issue any other deniabilization transactions until the previous transaction is confirmed (or abandoned/dropped). The process ends when the budget is exhausted or if there's no candidates left. The user can also stop the process manually by pressing a Stop button (DeniabilityDialog::stopDeniabilization). External signers are supported in a "best effort" way - since the user needs to manually sign, we postpone the processing till the external signer is connected and use some additional UI to get the user's attention to sign (see the codepath predicated on hasExternalSigner). This is not ideal, so I'm looking for some ideas if we can improve this in some way. Watch-only wallets are partially supported, where we display the candidate list, but we don't allow any processing (since we don't have the private keys to issue transactions). I've tested all this functionality on regtest, testnet, signet and mainnet. I've also added some unit tests (under WalletTests) to exercise the main GUI functionality. This is my first change and PR for Bitcoin Core, and I tried as much as possible to validate everything against the guidelines and documentation and to follow the patterns in the existing code, but I'm sure there are things I missed, so I'm looking forward to your feedback. In particular things I'm not very sure about - the save/restore of state via QSettings makes me a bit nervous as we store some wallet specific data there which I put some effort to validate on load, however keying the settings based on wallet name is not ideal, so I'd like to improve this somehow - either by storing the settings based on some wallet identity signature, or by storing the state in the wallet database (however that doesn't seem accessible via the interfaces::Wallet API). Please let me know your thoughts and suggestions. ----- Refactored the coin update to explicitly match utxos by scriptPubKey and not rely on the ListCoin destination grouping. ----- Fixed linter error about bitcoin-config.h not being included. ----- Post-rebase fixes for cmake build. ----- Refactored deprecated hex from string conversions.
This PR does not seem to have attracted much attention from reviewers. As such, it does not seem important enough right now to keep it sitting idle in the list of open PRs. Closing due to lack of interest. |
This new feature is an implementation of the ideas in Paul Sztorc's blog post "Deniability - Unilateral Transaction Meta-Privacy"(https://www.truthcoin.info/blog/deniability/).
In short, the idea is to periodically split coins and send them to yourself, making it look like common "spend" transactions, such that blockchain ownership analysis becomes more difficult, and thus improving the user's privacy.
This is the GUI portion of the PR (bitcoin-core/gui). The core functionality PR ( bitcoin/bitcoin#27792 ) is in the main repo (bitcoin/bitcoin).
This PR implements an additional "Deniability" wallet view. The majority of the GUI code is in a new deniabilitydialog.cpp/h source files containing a new DeniabilityDialog class, hooked up via the WalletView class.
On startup and on notable events (new blocks, new transactions, etc), we evaluate the privacy of all coins in the wallet, and we build a "deniabilization" candidate list. UTXOs that share the same destination address are grouped together into a single candidate (see DeniabilityDialog::updateCoins and DeniabilityDialog::updateCoinTable).
We inspect the blockchain data to find out if we have performed "deniabilization" transactions already, and we count how many "cycles" (self-sends) have been performed for each coin (see DeniabilityDialog::calculateDeniabilizationStats). Since we infer the state entirely from the blockchain data, even if the wallet is restored from a backup, the state would not be lost. This also means that if the user has performed manual self-sends that have improved the ownership privacy, they will be counted too.
The user has a couple of controls for the "deniabillization" process - a Frequency selector and a Budget spinner, which respectively determine how often to perform a cycle (see below) and how much total BTC to spend on transaction fees.
The user can initiate the "deniabillization" process by pressing the Start button (DeniabilityDialog::startDeniabilization).
The first time the button is pressed, we offer an information / confirmation box to explain to the user how Deniability works.
The process periodically perform a "deniabilization" cycle (DeniabilityDialog::deniabilizeProc). In each such cycle we do the following:
A coin is selected form the candidate list. The more a coin is "deniabilized", the less likely it is to be selected. Smaller coins are also less likely to be selected. If a coin is selected, we prepare and broadcast a transaction, which splits the coin into a pair of new wallet addresses (DeniabilityDialog::deniabilizeCoin).
If Bitcoin Core is left running continuously, the cycles would be performed at the selected frequency (with some randomization). If Bitcoin Core is shutdown, the "deniabilization" process will resume at the next restart, and if more time has elapsed than the selected frequency, it will perform a single cycle. We deliberately don't "catch up" all missed cycles, since that would expose the process to blockchain analysis. The state is saved and restored via QSettings (DeniabilityDialog::loadSettings and DeniabilityDialog::saveSettings).
We monitor each broadcasted transaction and we automatically attempt a fee bump if the transaction is still in the memory pool since the previous cycle (DeniabilityDialog::bumpDeniabilizationTx). We don't issue any other deniabilization transactions until the previous transaction is confirmed (or abandoned/dropped).
The process ends when the budget is exhausted or if there's no candidates left. The user can also stop the process manually by pressing a Stop button (DeniabilityDialog::stopDeniabilization).
External signers are supported in a "best effort" way - since the user needs to manually sign, we postpone the deniabilization processing till the external signer is connected and use some additional UI to get the user's attention to sign (see the codepath predicated on hasExternalSigner). This is not ideal, so I'm looking for ideas if we can improve this in some way.
Watch-only wallets are partially supported, where we display the candidate list, but we don't allow any processing (since we don't have the private keys to issue transactions).
I've tested all this functionality on regtest, testnet, signet and mainnet. I've also added some unit tests (under WalletTests) to exercise the main functions.
This is my first change and PR for Bitcoin Core, and I did my best to validate everything against the guidelines and documentation and to follow the patterns in the existing code, but I'm sure there may be things I've missed, so I'm looking forward to your feedback.
In particular one thing I'm not particularly happy with - the save/restore of state via QSettings makes me a bit nervous as we store wallet specific data keyed on wallet name, however wallets can be erased and new ones created with the same name, so this is not ideal. I made an effort to validate all data on load, but it would be great if we could improve this somehow - either by storing the settings based on some wallet identity signature, or by storing the state in the wallet database (however that doesn't seem accessible via the interfaces::Wallet API). Please let me know your thoughts and suggestions.
Thank you.