Skip to content

Commit 5958634

Browse files
committed
Refactored the DeniabilityDialog class to use the new interfaces::Wallet deniabilization API. (+8 squashed commits)
Squashed commits: [188e5f0c5] Refactored uses of shared_ptr::unique as it's deprecated. [aa1850e63] Tentative fix for the Win64/VS2022 build. [852fce363] Removed leftover changes from walletmodel.cpp [155d9b57b] One more "tidy" CI fix. [f65557747] Fixed the last (hopefully) tidy CI error. [cb7fb873a] A couple more CI fixes. [1119dbdfb] Fixed a couple of issues reported by the CI [562313a0e] Deniability - a tool to automatically improve coin ownership privacy This PR 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. 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 via the WalletView class.  The implementation uses the interfaces::Wallet API and leverages functionality from the WalletModel and some adapted code from the SendCoinsDialog. 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 functions. 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. Thank you.
1 parent a035b42 commit 5958634

14 files changed

+2163
-7
lines changed

build_msvc/libbitcoin_qt/libbitcoin_qt.vcxproj

+2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
<ClCompile Include="..\..\src\qt\coincontroltreewidget.cpp" />
2323
<ClCompile Include="..\..\src\qt\createwalletdialog.cpp" />
2424
<ClCompile Include="..\..\src\qt\csvmodelwriter.cpp" />
25+
<ClCompile Include="..\..\src\qt\deniabilitydialog.cpp" />
2526
<ClCompile Include="..\..\src\qt\editaddressdialog.cpp" />
2627
<ClCompile Include="..\..\src\qt\guiutil.cpp" />
2728
<ClCompile Include="..\..\src\qt\initexecutor.cpp" />
@@ -78,6 +79,7 @@
7879
<ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_coincontroltreewidget.cpp" />
7980
<ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_createwalletdialog.cpp" />
8081
<ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_csvmodelwriter.cpp" />
82+
<ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_deniabilitydialog.cpp" />
8183
<ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_editaddressdialog.cpp" />
8284
<ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_guiutil.cpp" />
8385
<ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_initexecutor.cpp" />

src/Makefile.qt.include

+5
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ QT_FORMS_UI = \
1818
qt/forms/askpassphrasedialog.ui \
1919
qt/forms/coincontroldialog.ui \
2020
qt/forms/createwalletdialog.ui \
21+
qt/forms/deniabilitydialog.ui \
2122
qt/forms/editaddressdialog.ui \
2223
qt/forms/helpmessagedialog.ui \
2324
qt/forms/intro.ui \
@@ -49,6 +50,7 @@ QT_MOC_CPP = \
4950
qt/moc_coincontroldialog.cpp \
5051
qt/moc_coincontroltreewidget.cpp \
5152
qt/moc_csvmodelwriter.cpp \
53+
qt/moc_deniabilitydialog.cpp \
5254
qt/moc_editaddressdialog.cpp \
5355
qt/moc_guiutil.cpp \
5456
qt/moc_initexecutor.cpp \
@@ -120,6 +122,7 @@ BITCOIN_QT_H = \
120122
qt/coincontroltreewidget.h \
121123
qt/createwalletdialog.h \
122124
qt/csvmodelwriter.h \
125+
qt/deniabilitydialog.h \
123126
qt/editaddressdialog.h \
124127
qt/guiconstants.h \
125128
qt/guiutil.h \
@@ -188,6 +191,7 @@ QT_RES_ICONS = \
188191
qt/res/icons/connect2.png \
189192
qt/res/icons/connect3.png \
190193
qt/res/icons/connect4.png \
194+
qt/res/icons/crosseye.png \
191195
qt/res/icons/edit.png \
192196
qt/res/icons/editcopy.png \
193197
qt/res/icons/editpaste.png \
@@ -255,6 +259,7 @@ BITCOIN_QT_WALLET_CPP = \
255259
qt/coincontroldialog.cpp \
256260
qt/coincontroltreewidget.cpp \
257261
qt/createwalletdialog.cpp \
262+
qt/deniabilitydialog.cpp \
258263
qt/editaddressdialog.cpp \
259264
qt/openuridialog.cpp \
260265
qt/overviewpage.cpp \

src/qt/bitcoin.qrc

+1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
<file alias="hd_disabled">res/icons/hd_disabled.png</file>
4545
<file alias="network_disabled">res/icons/network_disabled.png</file>
4646
<file alias="proxy">res/icons/proxy.png</file>
47+
<file alias="crosseye">res/icons/crosseye.png</file>
4748
</qresource>
4849
<qresource prefix="/animation">
4950
<file alias="spinner-000">res/animation/spinner-000.png</file>

src/qt/bitcoingui.cpp

+18
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,13 @@ void BitcoinGUI::createActions()
278278
historyAction->setShortcut(QKeySequence(QStringLiteral("Alt+4")));
279279
tabGroup->addAction(historyAction);
280280

281+
deniabilityAction = new QAction(platformStyle->SingleColorIcon(":/icons/crosseye"), tr("&Deniability"), this);
282+
deniabilityAction->setStatusTip(tr("Improve coin ownership privacy"));
283+
deniabilityAction->setToolTip(deniabilityAction->statusTip());
284+
deniabilityAction->setCheckable(true);
285+
deniabilityAction->setShortcut(QKeySequence(QStringLiteral("Alt+5")));
286+
tabGroup->addAction(deniabilityAction);
287+
281288
#ifdef ENABLE_WALLET
282289
// These showNormalIfMinimized are needed because Send Coins and Receive Coins
283290
// can be triggered from the tray menu, and need to show the GUI to be useful.
@@ -289,6 +296,8 @@ void BitcoinGUI::createActions()
289296
connect(receiveCoinsAction, &QAction::triggered, this, &BitcoinGUI::gotoReceiveCoinsPage);
290297
connect(historyAction, &QAction::triggered, [this]{ showNormalIfMinimized(); });
291298
connect(historyAction, &QAction::triggered, this, &BitcoinGUI::gotoHistoryPage);
299+
connect(deniabilityAction, &QAction::triggered, [this] { showNormalIfMinimized(); });
300+
connect(deniabilityAction, &QAction::triggered, this, &BitcoinGUI::gotoDeniabilityPage);
292301
#endif // ENABLE_WALLET
293302

294303
quitAction = new QAction(tr("E&xit"), this);
@@ -575,6 +584,7 @@ void BitcoinGUI::createToolBars()
575584
toolbar->addAction(sendCoinsAction);
576585
toolbar->addAction(receiveCoinsAction);
577586
toolbar->addAction(historyAction);
587+
toolbar->addAction(deniabilityAction);
578588
overviewAction->setChecked(true);
579589

580590
#ifdef ENABLE_WALLET
@@ -789,6 +799,7 @@ void BitcoinGUI::setWalletActionsEnabled(bool enabled)
789799
sendCoinsAction->setEnabled(enabled);
790800
receiveCoinsAction->setEnabled(enabled);
791801
historyAction->setEnabled(enabled);
802+
deniabilityAction->setEnabled(enabled);
792803
encryptWalletAction->setEnabled(enabled);
793804
backupWalletAction->setEnabled(enabled);
794805
changePassphraseAction->setEnabled(enabled);
@@ -947,6 +958,12 @@ void BitcoinGUI::gotoHistoryPage()
947958
if (walletFrame) walletFrame->gotoHistoryPage();
948959
}
949960

961+
void BitcoinGUI::gotoDeniabilityPage()
962+
{
963+
deniabilityAction->setChecked(true);
964+
if (walletFrame) walletFrame->gotoDeniabilityPage();
965+
}
966+
950967
void BitcoinGUI::gotoReceiveCoinsPage()
951968
{
952969
receiveCoinsAction->setChecked(true);
@@ -1246,6 +1263,7 @@ void BitcoinGUI::changeEvent(QEvent *e)
12461263
sendCoinsAction->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/send")));
12471264
receiveCoinsAction->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/receiving_addresses")));
12481265
historyAction->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/history")));
1266+
deniabilityAction->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/crosseye")));
12491267
}
12501268

12511269
QMainWindow::changeEvent(e);

src/qt/bitcoingui.h

+3
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ class BitcoinGUI : public QMainWindow
136136
QToolBar* appToolBar = nullptr;
137137
QAction* overviewAction = nullptr;
138138
QAction* historyAction = nullptr;
139+
QAction* deniabilityAction = nullptr;
139140
QAction* quitAction = nullptr;
140141
QAction* sendCoinsAction = nullptr;
141142
QAction* usedSendingAddressesAction = nullptr;
@@ -277,6 +278,8 @@ public Q_SLOTS:
277278
void gotoOverviewPage();
278279
/** Switch to history (transactions) page */
279280
void gotoHistoryPage();
281+
/** Switch to deniability (ownership obfuscation) page */
282+
void gotoDeniabilityPage();
280283
/** Switch to receive coins page */
281284
void gotoReceiveCoinsPage();
282285
/** Switch to send coins page */

0 commit comments

Comments
 (0)