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 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.
DrahtBot
commented at 6:32 am on May 21, 2023:
contributor
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.
Reviews
See the guideline for information on the review process.
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.
DrahtBot added the label
CI failed
on May 21, 2023
hebasto added the label
Feature
on May 21, 2023
hebasto added the label
Wallet
on May 21, 2023
hebasto
commented at 8:01 am on May 21, 2023:
member
This PR is an implementation of the ideas in Paul Sztorc’s blog post “Deniability - Unilateral Transaction Meta-Privacy”(truthcoin.info/blog/deniability).
Has that idea been discussed somewhere already?
denavila
commented at 7:17 am on May 23, 2023:
none
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.
We looked around to see if there were existing discussions or implementations, but we didn’t find anything on bitcoin-dev or the repo here.
There’s an old implementation in a Bitcoin fork (Drivechain) which I used as a starting point, but ended up with a pretty different approach and implementation.
We were thinking of perhaps writing down the spec into a Process BIP, if this would make it easier to review on a conceptual level?
VladdyC
commented at 12:33 pm on May 24, 2023:
none
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 :)
achow101
commented at 3:59 pm on May 24, 2023:
member
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.
denavila
commented at 5:31 pm on May 24, 2023:
none
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.
Yes, CoinJoin is definitely more powerful, however it has some drawbacks.
In particular, if it’s possible to detect that an UTXO has come from a coinjoin, it may be considered “tainted” by CEXs and they could refuse processing it (eg demand source of funds etc).
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.
At any rate, I’m definitely open to suggestions or ideas we can borrow from CoinJoin.
In particular if there’s a way we can re-merge UTXOs in a privacy-preserving way. Or at bare minimum, use “deniability-aware” logic in the automatic coin selection during user-initiated spends to minimize the privacy reduction.
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 :)
Thank you. I believe so too.
denavila
commented at 5:39 pm on May 24, 2023:
none
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.
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.
Of course, if this PR is accepted, I’d be happy to move functionality into the API.
achow101
commented at 6:08 pm on May 24, 2023:
member
Do you mean moving some of the core logic inside the interfaces::Wallet API, or the CWallet implementation?
Inside of CWallet.
Since this was my first Bitcoin Core change I decided to be more conservative and avoid API changes.
Of course, if this PR is accepted, I’d be happy to move functionality into the API.
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 CWallet will probably make this feature more likely to be accepted. It will be easier to write tests (adding an RPC allows for functional tests, and implementing in CWallet allows unit tests to better access the data) and it can be split up into at least 2 chunks - the CWallet and RPC only implementation, and then the GUI and interface changes. This makes it more likely to be accepted.
denavila
commented at 6:15 pm on May 24, 2023:
none
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 CWallet will probably make this feature more likely to be accepted. It will be easier to write tests (adding an RPC allows for functional tests, and implementing in CWallet allows unit tests to better access the data) and it can be split up into at least 2 chunks - the CWallet and RPC only implementation, and then the GUI and interface changes. This makes it more likely to be accepted.
Ah, I see. I had assumed the opposite - that not touching the API would be easier to accept.
Ok, I can look into refactoring the PR and moving the core functionality into CWallet.
I’d need to open a PR against the main repo in that case, right?
Should I keep this PR and leave the GUI bits here, and open another PR in the main repo with just the CWallet changes?
Or is it better to close this PR and open a new one in the main repo with all the changes?
achow101
commented at 6:31 pm on May 24, 2023:
member
I’d need to open a PR against the main repo in that case, right?
Yes
Should I keep this PR and leave the GUI bits here, and open another PR in the main repo with just the CWallet changes?
Or is it better to close this PR and open a new one in the main repo with all the changes?
You can leave this open and rebase it onto the main repo PR, just mention it in the description.
denavila
commented at 4:05 am on May 25, 2023:
none
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.
Btw, do we have some docs on how to setup git locally so I can have dependent changes across the “bitcoin” and the “gui” repos? So far I was moving changes via patches but that doesn’t seem too convenient.
denavila referenced this in commit
a035b42fd6
on May 26, 2023
hebasto removed the label
CI failed
on May 26, 2023
denavila force-pushed
on May 30, 2023
denavila
commented at 4:31 pm on May 30, 2023:
none
I refactored the code and updated the PR. The new code is split between two branches:
deniability-api - new API and implementation of core “deniabilization” functions. I’ll open a PR in the main repo once I add RPC and unit tests.
deniability - the GUI portion of the Deniability dialog which uses the new deniability-api functions.
denavila force-pushed
on May 30, 2023
DrahtBot added the label
CI failed
on May 30, 2023
denavila force-pushed
on May 30, 2023
denavila force-pushed
on May 30, 2023
denavila force-pushed
on May 30, 2023
denavila force-pushed
on May 31, 2023
denavila force-pushed
on May 31, 2023
denavila referenced this in commit
246589f3f7
on May 31, 2023
denavila force-pushed
on May 31, 2023
denavila force-pushed
on May 31, 2023
DrahtBot removed the label
CI failed
on May 31, 2023
maflcko
commented at 9:17 pm on June 13, 2023:
contributor
Maybe mark as draft as long as it depends on the upstream changes?
denavila marked this as a draft
on Jun 13, 2023
denavila
commented at 10:00 pm on June 13, 2023:
none
Maybe mark as draft as long as it depends on the upstream changes?
I converted the PR to a draft. Thanks!
DrahtBot added the label
Needs rebase
on Jul 19, 2023
denavila referenced this in commit
1925738074
on Sep 2, 2023
denavila referenced this in commit
379b724eb5
on Sep 2, 2023
denavila force-pushed
on Sep 2, 2023
DrahtBot removed the label
Needs rebase
on Sep 2, 2023
DrahtBot added the label
CI failed
on Sep 3, 2023
DrahtBot removed the label
CI failed
on Sep 4, 2023
DrahtBot added the label
CI failed
on Sep 13, 2023
DrahtBot removed the label
CI failed
on Sep 18, 2023
DrahtBot added the label
CI failed
on Sep 20, 2023
denavila referenced this in commit
403dba36a5
on Oct 6, 2023
denavila force-pushed
on Oct 6, 2023
denavila force-pushed
on Oct 8, 2023
denavila force-pushed
on Oct 8, 2023
DrahtBot removed the label
CI failed
on Oct 8, 2023
denavila force-pushed
on Oct 10, 2023
denavila referenced this in commit
ed75f632a9
on Oct 20, 2023
denavila force-pushed
on Oct 21, 2023
DrahtBot added the label
CI failed
on Oct 21, 2023
denavila referenced this in commit
4d1a6d147e
on Oct 21, 2023
denavila force-pushed
on Oct 21, 2023
denavila force-pushed
on Oct 23, 2023
denavila force-pushed
on Oct 23, 2023
denavila force-pushed
on Oct 25, 2023
DrahtBot removed the label
CI failed
on Oct 25, 2023
denavila force-pushed
on Oct 25, 2023
DrahtBot added the label
CI failed
on Oct 25, 2023
denavila force-pushed
on Oct 25, 2023
DrahtBot removed the label
CI failed
on Oct 26, 2023
denavila force-pushed
on Oct 26, 2023
DrahtBot added the label
CI failed
on Oct 26, 2023
in
src/qt/test/wallettests.cpp:548
in
16d50b3699outdated
541+
542+ // mine a block to confirm the transaction
543+ MineBlock(test.m_node, GetScriptForRawPubKey(test.coinbaseKey.GetPubKey()));
544+ {
545+ LOCK(wallet->cs_wallet);
546+ wallet->SetLastBlockProcessed(106, WITH_LOCK(node.context()->chainman->GetMutex(), return node.context()->chainman->ActiveChain().Tip()->GetBlockHash()));
katesalazar
commented at 6:30 pm on October 26, 2023:
denavila
commented at 8:52 pm on October 28, 2023:
There was a bug introduced in the fee bumping after the last rebase. Interestingly it didn’t get flagged by the compiler … even though it was an obviously wrong type being assigned to CRecipient struct … :-O
At any rate, I fixed it in the latest commit. Please let me know if you’re still seeing an issue running the tests.
Thanks!
in
src/wallet/feebumper.cpp:390
in
16d50b3699outdated
denavila
commented at 8:49 pm on October 28, 2023:
CreateRateBumpDeniabilizationTransaction essentially does the job of CreateRateBumpTransaction and SignTransaction, but specialized for bumping deniabilization transactions.
This functionality is responsible for the presence of the “txid”, “old_fee”, “new_fee” and “sign” parameters.
“confirm_target” takes the place of the “coin_control” parameter (since that’s all we need), and the “error” and “new_tx” are needed to communicate the result/error with the caller.
The reason we need a new API function here is that we need to take the new bump fee from one of the recipients of the old transaction (fSubtractFeeFromAmount), and there didn’t seem to be a way to do that via CreateRateBumpTransaction. And generally CreateRateBumpTransaction just does too many other things that we don’t need once we know we’re bumping a deniabilization transaction.
in
src/wallet/spend.cpp:1737
in
16d50b3699outdated
katesalazar
commented at 6:34 pm on October 26, 2023:
same
denavila
commented at 8:32 pm on October 28, 2023:
The “inputs”, “out_output_type” and “confirm_target” are usually passed in as part of CCoinControl.
However for deniabilization we create a specifically built CCoinControl, so I didn’t want the API user to have to fill one out, just to have the majority of the fields essentially ignored, so that’s why I decided to list these individually.
I could make a new class to pass them together though, eg CDeniabilizationControl?
Does that sound reasonable?
katesalazar
commented at 2:39 pm on October 29, 2023:
just by thinking of the possibility seems reasonable enough for me at this point
for now make as you feel it’s best made
in
src/wallet/spend.cpp:1736
in
16d50b3699outdated
1731+ // we don't expect to get change, but we provide the address to prevent CreateTransactionInternal from generating a change address
1732+ coin_control.destChange = dest;
1733+ }
1734+ }
1735+
1736+ CAmount recipient_amount = std::accumulate(recipients.cbegin(), recipients.cend(), CAmount{0}, [](CAmount sum, const CRecipient& recipient) { return sum + recipient.nAmount; });
katesalazar
commented at 6:35 pm on October 26, 2023:
fold to several lines?
denavila
commented at 9:03 pm on October 28, 2023:
Yes, indeed. I split the code into several lines in the latest commit.
Thanks!
katesalazar
commented at 2:50 pm on October 29, 2023:
I hate that indent standard though, and yet I think now it’s a bit better than before. Thanks
katesalazar
commented at 6:36 pm on October 26, 2023:
contributor
what a long shot
Concept ACK
keep up the good works
denavila force-pushed
on Oct 28, 2023
denavila force-pushed
on Oct 28, 2023
DrahtBot removed the label
CI failed
on Oct 28, 2023
in
src/wallet/spend.cpp:1474
in
c781cd8863outdated
katesalazar
commented at 2:42 pm on October 29, 2023:
why is this hardwired to 2?
denavila
commented at 1:02 am on October 30, 2023:
2 outputs is a common spend transaction and we’re trying to mimic that.
Another concern with a larger output count is that the number of deniabilization chunks grows much larger with each “cycle”, and thus becomes more expensive to perform further cycles.
In the end 2 looked like the sweet spot, but I made the constant just in case we want to play with other output counts in the future.
I’ll add comments in the code to that effect so that it’s more obvious.
Thanks!
katesalazar
commented at 4:49 pm on October 30, 2023:
Maybe commenting the constant helps,
maybe best commented in some function,
at the extremely minimum least I think such intrincacies should be commented in the commit message introducing either the constant or whatever function
in
src/wallet/spend.cpp:1412
in
c781cd8863outdated
katesalazar
commented at 2:45 pm on October 29, 2023:
why this magic number?
is this the number of legacy and p2sh bytes per txin?
can’t name some constant with that?
denavila
commented at 1:08 am on October 30, 2023:
Yes, these are legacy vs Segwit tx size calculation, I think I found the code in GetDustThreshold. I’ll see if we have named constants for some of those.
katesalazar
commented at 4:46 pm on October 30, 2023:
and/or simple one line comments could help clarify,
I feel like named constant for magic number is a better strategy than commenting,
but maintainer should take final choice :thinking:
in
src/wallet/spend.cpp:1474
in
c781cd8863outdated
1469+
1470+ // this cycle will use all the UTXOs, while following cycles will have just one UTXO
1471+ CAmount deniabilizationFee = CalculateDeniabilizationTxFee(shared_script, total_value, num_utxos, fee_rate);
1472+
1473+ // calculate the fees from future deniabilization cycles
1474+ CAmount futureDeniabilizationFee = CalculateDeniabilizationFeeEstimate(shared_script, total_value / NUM_DENIABILIZATION_OUTPUTS, 1, deniabilization_cycles + 1, fee_rate) * 2;
katesalazar
commented at 2:45 pm on October 29, 2023:
why compute fee estimate and then multiply by a hardwired 2?
denavila
commented at 1:11 am on October 30, 2023:
Oh, that 2 probably needs to be NUM_DENIABILIZATION_OUTPUTS. Basically we calculate the future cycle cost for each of the chunks we split into. Each chunk should cost the same if the number of splits is the same, so we can calculate one chunk and multiply by the number of splits.
Thanks for spotting that. I’ll fix it.
katesalazar
commented at 4:42 pm on October 30, 2023:
happpy to help
denavila force-pushed
on Nov 9, 2023
denavila force-pushed
on Nov 16, 2023
DrahtBot added the label
CI failed
on Nov 18, 2023
denavila referenced this in commit
842cb26c18
on Nov 19, 2023
denavila force-pushed
on Nov 19, 2023
DrahtBot removed the label
CI failed
on Nov 19, 2023
denavila referenced this in commit
21e39b96bc
on Nov 22, 2023
denavila force-pushed
on Nov 22, 2023
DrahtBot added the label
CI failed
on Nov 27, 2023
denavila referenced this in commit
37722d6f27
on Nov 29, 2023
denavila force-pushed
on Nov 29, 2023
DrahtBot removed the label
CI failed
on Nov 29, 2023
DrahtBot added the label
Needs rebase
on Dec 11, 2023
denavila referenced this in commit
9e6fdce439
on Dec 11, 2023
denavila referenced this in commit
737fbc4643
on Dec 12, 2023
denavila force-pushed
on Dec 12, 2023
DrahtBot removed the label
Needs rebase
on Dec 12, 2023
DrahtBot added the label
CI failed
on Jan 14, 2024
DrahtBot added the label
Needs rebase
on Jan 23, 2024
denavila referenced this in commit
95e53e7766
on Jan 26, 2024
denavila force-pushed
on Jan 26, 2024
DrahtBot removed the label
CI failed
on Jan 26, 2024
DrahtBot removed the label
Needs rebase
on Jan 26, 2024
DrahtBot
commented at 5:19 am on February 29, 2024:
contributor
🚧 At least one of the CI tasks failed. Make sure to run all tests locally, according to the
documentation.
Possibly this is due to a silent merge conflict (the changes in this pull request being
incompatible with the current code in the target branch). If so, make sure to rebase on the latest
commit of the target branch.
Leave a comment here, if you need help tracking down a confusing failure.
There should be an option to select “random” frequency.
There should be an option to select minimum amount for resulting UTXOs so that user doesn’t end up with dust outputs that need to be used together.
Post deniability UTXOs have to be carefully spent but there is nothing to differentiate them in the labels and all are labelled as “deniability”. Users will need to do manual analysis before spending which is not good UX.
I don’t understand why the next cycle starts after 1 hr 34 minutes when frequency is 1 hour: https://i.imgur.com/fxE0AvG.png
denavila
commented at 4:39 pm on April 29, 2024:
none
@1440000bytes,
Thank you so much for testing the PR and for the feedback. That’s very helpful.
(1) Yes, that’s a good idea, I’ll rename it.
(2) and (5) are related. The selectable frequencies are actually also randomized, within the selected frequency range - for example hourly is random from 1 to 2 hours, daily is random from 1 to 2 days etc (see line 934 in deniabilitydialog.cpp). Perhaps we need to rename the frequency UI selection to something that better describes this?
(3) Great idea as well - especially since we’re getting into long stretches of overloaded mempool with fees sky rocketing. I’m actually even more concerned that any number the user picks, may not be good enough long term, as smaller UTXOs may effectively become dust if fees rise and stay elevated (and there’s a good chance that’s going to happen). I’m not sure we can solve that just on the transaction end though … A minimum UTXO amount does sound like a good option regardless, so I’ll implement that.
(4) Indeed - I’ll have to think how to solve this. Perhaps we need to add some “deniability” awareness to the regular UTXO selection logic. I’m not entirely clear what that logic would look like though. Let me know if you have ideas - eg, what kind of rules for UTXO selection should we add etc.
I’m a bit busy with my main job at the moment, but when I get a chance I’ll work on this as time allows.
Thanks!
DrahtBot
commented at 6:56 am on May 8, 2024:
contributor
🚧 At least one of the CI tasks failed. Make sure to run all tests locally, according to the
documentation.
Possibly this is due to a silent merge conflict (the changes in this pull request being
incompatible with the current code in the target branch). If so, make sure to rebase on the latest
commit of the target branch.
Leave a comment here, if you need help tracking down a confusing failure.
denavila referenced this in commit
4fd852bb3d
on May 18, 2024
denavila force-pushed
on May 18, 2024
1Helpmeout3 approved
1Helpmeout3 approved
1Helpmeout3 approved
denavila referenced this in commit
0b66676c2f
on Jul 30, 2024
denavila referenced this in commit
c2c12c0a94
on Aug 1, 2024
denavila force-pushed
on Aug 1, 2024
DrahtBot added the label
Needs rebase
on Aug 28, 2024
jonatack
commented at 7:29 pm on August 28, 2024:
member
Didn’t conceptually review yet. While rebasing, the clang-tidy CI highlighted these issues to be addressed:
0wallet/spend.cpp:1539:9: error: function 'CalculateDeniabilizationFeeEstimate' is within a recursive call chain [misc-no-recursion,-warnings-as-errors]
11539| CAmount CalculateDeniabilizationFeeEstimate(const CScript& shared_script, CAmount total_value, unsigned int num_utxos, unsigned int deniabilization_cycles, const CFeeRate& fee_rate)
2|^ 3wallet/spend.cpp:1539:9: note: example recursive call chain, starting from function 'CalculateDeniabilizationFeeEstimate' 4 5wallet/spend.cpp:1552:40: note: Frame [#1](/bitcoin-core-gui/1/): function 'CalculateDeniabilizationFeeEstimate' calls function 'CalculateDeniabilizationFeeEstimate' here: 61552| CAmount futureDeniabilizationFee = CalculateDeniabilizationFeeEstimate(shared_script, total_value / NUM_DENIABILIZATION_OUTPUTS, 1, deniabilization_cycles +1, fee_rate) * NUM_DENIABILIZATION_OUTPUTS;
7|^ 8wallet/spend.cpp:1552:40: note: ... which was the starting point of the recursive call chain; there may be other cycles
910wallet/spend.cpp:1561:31: error: function 'CalculateDeniabilizationCycles' is within a recursive call chain [misc-no-recursion,-warnings-as-errors]
111561| std::pair<unsigned int, bool> CalculateDeniabilizationCycles(CWallet& wallet, const COutPoint& outpoint)
12|^13wallet/spend.cpp:1561:31: note: example recursive call chain, starting from function 'CalculateDeniabilizationCycles'1415wallet/spend.cpp:1634:23: note: Frame [#1](/bitcoin-core-gui/1/): function 'CalculateDeniabilizationCycles' calls function 'CalculateDeniabilizationCycles' here:161634| auto inputStats = CalculateDeniabilizationCycles(wallet, txIn.prevout);
denavila
commented at 10:35 pm on September 3, 2024:
none
Yes, it looks like recursive functions were disallowed recently.
I’ll rework the code to not use recursion.
denavila referenced this in commit
a869bd1740
on Sep 4, 2024
denavila referenced this in commit
a24133189d
on Sep 4, 2024
denavila force-pushed
on Sep 4, 2024
denavila referenced this in commit
5f4549cf3a
on Sep 4, 2024
denavila force-pushed
on Sep 4, 2024
denavila referenced this in commit
38119b91f9
on Sep 4, 2024
denavila force-pushed
on Sep 4, 2024
DrahtBot removed the label
Needs rebase
on Sep 4, 2024
DrahtBot removed the label
CI failed
on Sep 4, 2024
DrahtBot
commented at 7:27 pm on September 12, 2024:
contributor
Make sure to run all tests locally, according to the documentation.
The failure may happen due to a number of reasons, for example:
Possibly due to a silent merge conflict (the changes in this pull request being
incompatible with the current code in the target branch). If so, make sure to rebase on the latest
commit of the target branch.
A sanitizer issue, which can only be found by compiling with the sanitizer and running the
affected test.
An intermittent issue.
Leave a comment here, if you need help tracking down a confusing failure.
DrahtBot added the label
CI failed
on Sep 12, 2024
denavila force-pushed
on Sep 18, 2024
denavila force-pushed
on Sep 18, 2024
DrahtBot removed the label
CI failed
on Sep 19, 2024
DrahtBot added the label
CI failed
on Oct 10, 2024
DrahtBot
commented at 8:18 pm on October 10, 2024:
contributor
Try to run the tests locally, according to the documentation. However, a CI failure may still
happen due to a number of reasons, for example:
Possibly due to a silent merge conflict (the changes in this pull request being
incompatible with the current code in the target branch). If so, make sure to rebase on the latest
commit of the target branch.
A sanitizer issue, which can only be found by compiling with the sanitizer and running the
affected test.
An intermittent issue.
Leave a comment here, if you need help tracking down a confusing failure.
Deniability API
This PR is the wallet API and implementation portion of the GUI PR ( https://github.com/bitcoin-core/gui/pull/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.
c5ea2c6fdb
denavila force-pushed
on Oct 14, 2024
1440000bytes
commented at 9:40 pm on October 14, 2024:
none
Deniability - a tool to automatically improve coin ownership privacy
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.
b48a95a79f
denavila force-pushed
on Oct 15, 2024
DrahtBot removed the label
CI failed
on Oct 15, 2024
achow101
commented at 2:32 pm on October 15, 2024:
member
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.
This is a metadata mirror of the GitHub repository
bitcoin-core/gui.
This site is not affiliated with GitHub.
Content is generated from a GitHub metadata backup.
generated: 2024-12-22 03:20 UTC
This site is hosted by @0xB10C More mirrored repositories can be found on mirror.b10c.me