aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorSamuel Dobson <dobsonsa68@gmail.com>2021-06-09 18:17:26 +1200
committerSamuel Dobson <dobsonsa68@gmail.com>2021-06-09 18:59:59 +1200
commit68a89d7a46bf61c2156da3ad68d20095397b943b (patch)
treeaa836280e5ecaa850427873daf8fdbdceaa59f9d /src
parent7cac26246a23b2843cb368fe8a68c54dde04d5d8 (diff)
parent1c4b456e1a0ccf0397d652f8c18201c3224c5c21 (diff)
Merge bitcoin-core/gui#4: UI external signer support (e.g. hardware wallet)
1c4b456e1a0ccf0397d652f8c18201c3224c5c21 gui: send using external signer (Sjors Provoost) 24815c6309431cb0797defaf7add1150bcf4b567 gui: wallet creation detects external signer (Sjors Provoost) 3f845ea2994f53e29abeb3fa158c35f1ee56e7e8 node: add externalSigners to interface (Sjors Provoost) 62ac119f919ae1160ed67af796f24b78025fa8e3 gui: display address on external signer (Sjors Provoost) 450cb40a344605dda3bcc39495c35869580b9fc2 wallet: add displayAddress to interface (Sjors Provoost) eef8d6452962cd4a8956d9ad268164715365b9ab gui: create wallet with external signer (Sjors Provoost) 6cdbc83e9341d1552faee4ccd8c190babc63e8d1 gui: add external signer path to options dialog (Sjors Provoost) Pull request description: Big picture overview in [this gist](https://gist.github.com/Sjors/29d06728c685e6182828c1ce9b74483d). This PR adds GUI support for external signers, based on the since merged bitcoin/bitcoin#16546 (RPC). The UX isn't amazing - especially the blocking calls - but it works. First we adds a GUI setting for the signer script (e.g. path to HWI): <img width="625" alt="Schermafbeelding 2019-08-05 om 19 32 59" src="https://user-images.githubusercontent.com/10217/62483415-e1ff1680-b7b7-11e9-97ca-8d2ce54ca1cb.png"> Then we add an external signer checkbox to the wallet creation dialog: <img width="374" alt="Schermafbeelding 2019-11-07 om 19 17 23" src="https://user-images.githubusercontent.com/10217/68416387-b57ee000-0194-11ea-9730-127d60273008.png"> It's checked by default if HWI detects a device. It also grabs the name. It then creates a fresh wallet and imports the keys. You can verify an address on the device (blocking...): <img width="673" alt="Schermafbeelding 2019-08-05 om 19 29 22" src="https://user-images.githubusercontent.com/10217/62483560-43bf8080-b7b8-11e9-9902-8a036116dc4b.png"> Sending, including coin selection, Just Works(tm) as long the device is present. ~External signer support is enabled by default when the GUI is configured and Boost::Process is present.~ External signer support remains disabled by default, see https://github.com/bitcoin/bitcoin/pull/21935. ACKs for top commit: achow101: Code Review ACK 1c4b456e1a0ccf0397d652f8c18201c3224c5c21 hebasto: ACK 1c4b456e1a0ccf0397d652f8c18201c3224c5c21, tested on Linux Mint 20.1 (Qt 5.12.8) with HWW `2.0.2-rc.1`. promag: Tested ACK 1c4b456e1a0ccf0397d652f8c18201c3224c5c21 but rebased with e033ca1379, with HWI 2.0.2, with Nano S and Nano X. meshcollider: re-code-review ACK 1c4b456e1a0ccf0397d652f8c18201c3224c5c21 Tree-SHA512: 3503113c5c69d40adb6ce364d8e7cae23ce82d032a00474ba9aeb6202eb70f496ef4a6bf2e623e5171e524ad31ade7941a4e0e89539c64518aaec74f4562d86b
Diffstat (limited to 'src')
-rw-r--r--src/interfaces/node.h6
-rw-r--r--src/interfaces/wallet.h6
-rw-r--r--src/node/interfaces.cpp10
-rw-r--r--src/qt/createwalletdialog.cpp66
-rw-r--r--src/qt/createwalletdialog.h9
-rw-r--r--src/qt/forms/createwalletdialog.ui11
-rw-r--r--src/qt/forms/optionsdialog.ui30
-rw-r--r--src/qt/forms/receiverequestdialog.ui13
-rw-r--r--src/qt/optionsdialog.cpp2
-rw-r--r--src/qt/optionsmodel.cpp15
-rw-r--r--src/qt/optionsmodel.h1
-rw-r--r--src/qt/receiverequestdialog.cpp6
-rw-r--r--src/qt/sendcoinsdialog.cpp80
-rw-r--r--src/qt/walletcontroller.cpp14
-rw-r--r--src/qt/walletmodel.cpp12
-rw-r--r--src/qt/walletmodel.h1
-rw-r--r--src/qt/walletmodeltransaction.cpp5
-rw-r--r--src/qt/walletmodeltransaction.h2
-rw-r--r--src/wallet/interfaces.cpp6
19 files changed, 284 insertions, 11 deletions
diff --git a/src/interfaces/node.h b/src/interfaces/node.h
index 1dd1e92e2f..35b6160cea 100644
--- a/src/interfaces/node.h
+++ b/src/interfaces/node.h
@@ -6,6 +6,7 @@
#define BITCOIN_INTERFACES_NODE_H
#include <amount.h> // For CAmount
+#include <external_signer.h>
#include <net.h> // For NodeId
#include <net_types.h> // For banmap_t
#include <netaddress.h> // For Network
@@ -110,6 +111,11 @@ public:
//! Disconnect node by id.
virtual bool disconnectById(NodeId id) = 0;
+#ifdef ENABLE_EXTERNAL_SIGNER
+ //! List external signers
+ virtual std::vector<ExternalSigner> externalSigners() = 0;
+#endif
+
//! Get total bytes recv.
virtual int64_t getTotalBytesRecv() = 0;
diff --git a/src/interfaces/wallet.h b/src/interfaces/wallet.h
index 88f93321f9..a0cb2787b7 100644
--- a/src/interfaces/wallet.h
+++ b/src/interfaces/wallet.h
@@ -118,6 +118,9 @@ public:
//! Save or remove receive request.
virtual bool setAddressReceiveRequest(const CTxDestination& dest, const std::string& id, const std::string& value) = 0;
+ //! Display address on external signer
+ virtual bool displayAddress(const CTxDestination& dest) = 0;
+
//! Lock coin.
virtual void lockCoin(const COutPoint& output) = 0;
@@ -252,6 +255,9 @@ public:
// Return whether private keys enabled.
virtual bool privateKeysDisabled() = 0;
+ // Return whether wallet uses an external signer.
+ virtual bool hasExternalSigner() = 0;
+
// Get default address type.
virtual OutputType getDefaultAddressType() = 0;
diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp
index 8befbf5e30..171f15d4fb 100644
--- a/src/node/interfaces.cpp
+++ b/src/node/interfaces.cpp
@@ -170,6 +170,16 @@ public:
}
return false;
}
+#ifdef ENABLE_EXTERNAL_SIGNER
+ std::vector<ExternalSigner> externalSigners() override
+ {
+ std::vector<ExternalSigner> signers = {};
+ const std::string command = gArgs.GetArg("-signer", "");
+ if (command == "") return signers;
+ ExternalSigner::Enumerate(command, signers, Params().NetworkIDString());
+ return signers;
+ }
+#endif
int64_t getTotalBytesRecv() override { return m_context->connman ? m_context->connman->GetTotalBytesRecv() : 0; }
int64_t getTotalBytesSent() override { return m_context->connman ? m_context->connman->GetTotalBytesSent() : 0; }
size_t getMempoolSize() override { return m_context->mempool ? m_context->mempool->size() : 0; }
diff --git a/src/qt/createwalletdialog.cpp b/src/qt/createwalletdialog.cpp
index 113bd30a0c..e593697b46 100644
--- a/src/qt/createwalletdialog.cpp
+++ b/src/qt/createwalletdialog.cpp
@@ -6,6 +6,7 @@
#include <config/bitcoin-config.h>
#endif
+#include <external_signer.h>
#include <qt/createwalletdialog.h>
#include <qt/forms/ui_createwalletdialog.h>
@@ -27,14 +28,39 @@ CreateWalletDialog::CreateWalletDialog(QWidget* parent) :
});
connect(ui->encrypt_wallet_checkbox, &QCheckBox::toggled, [this](bool checked) {
- // Disable the disable_privkeys_checkbox when isEncryptWalletChecked is
+ // Disable the disable_privkeys_checkbox and external_signer_checkbox when isEncryptWalletChecked is
// set to true, enable it when isEncryptWalletChecked is false.
ui->disable_privkeys_checkbox->setEnabled(!checked);
+ ui->external_signer_checkbox->setEnabled(!checked);
// When the disable_privkeys_checkbox is disabled, uncheck it.
if (!ui->disable_privkeys_checkbox->isEnabled()) {
ui->disable_privkeys_checkbox->setChecked(false);
}
+
+ // When the external_signer_checkbox box is disabled, uncheck it.
+ if (!ui->external_signer_checkbox->isEnabled()) {
+ ui->external_signer_checkbox->setChecked(false);
+ }
+
+ });
+
+ connect(ui->external_signer_checkbox, &QCheckBox::toggled, [this](bool checked) {
+ ui->encrypt_wallet_checkbox->setEnabled(!checked);
+ ui->blank_wallet_checkbox->setEnabled(!checked);
+ ui->disable_privkeys_checkbox->setEnabled(!checked);
+ ui->descriptor_checkbox->setEnabled(!checked);
+
+ // The external signer checkbox is only enabled when a device is detected.
+ // In that case it is checked by default. Toggling it restores the other
+ // options to their default.
+ ui->descriptor_checkbox->setChecked(checked);
+ ui->encrypt_wallet_checkbox->setChecked(false);
+ ui->disable_privkeys_checkbox->setChecked(checked);
+ // The blank check box is ambiguous. This flag is always true for a
+ // watch-only wallet, even though we immedidately fetch keys from the
+ // external signer.
+ ui->blank_wallet_checkbox->setChecked(checked);
});
connect(ui->disable_privkeys_checkbox, &QCheckBox::toggled, [this](bool checked) {
@@ -63,11 +89,22 @@ CreateWalletDialog::CreateWalletDialog(QWidget* parent) :
ui->descriptor_checkbox->setToolTip(tr("Compiled without sqlite support (required for descriptor wallets)"));
ui->descriptor_checkbox->setEnabled(false);
ui->descriptor_checkbox->setChecked(false);
+ ui->external_signer_checkbox->setEnabled(false);
+ ui->external_signer_checkbox->setChecked(false);
#endif
+
#ifndef USE_BDB
ui->descriptor_checkbox->setEnabled(false);
ui->descriptor_checkbox->setChecked(true);
#endif
+
+#ifndef ENABLE_EXTERNAL_SIGNER
+ //: "External signing" means using devices such as hardware wallets.
+ ui->external_signer_checkbox->setToolTip(tr("Compiled without external signing support (required for external signing)"));
+ ui->external_signer_checkbox->setEnabled(false);
+ ui->external_signer_checkbox->setChecked(false);
+#endif
+
}
CreateWalletDialog::~CreateWalletDialog()
@@ -75,6 +112,28 @@ CreateWalletDialog::~CreateWalletDialog()
delete ui;
}
+#ifdef ENABLE_EXTERNAL_SIGNER
+void CreateWalletDialog::setSigners(std::vector<ExternalSigner>& signers)
+{
+ if (!signers.empty()) {
+ ui->external_signer_checkbox->setEnabled(true);
+ ui->external_signer_checkbox->setChecked(true);
+ ui->encrypt_wallet_checkbox->setEnabled(false);
+ ui->encrypt_wallet_checkbox->setChecked(false);
+ // The order matters, because connect() is called when toggling a checkbox:
+ ui->blank_wallet_checkbox->setEnabled(false);
+ ui->blank_wallet_checkbox->setChecked(false);
+ ui->disable_privkeys_checkbox->setEnabled(false);
+ ui->disable_privkeys_checkbox->setChecked(true);
+ const std::string label = signers[0].m_name;
+ ui->wallet_name_line_edit->setText(QString::fromStdString(label));
+ ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
+ } else {
+ ui->external_signer_checkbox->setEnabled(false);
+ }
+}
+#endif
+
QString CreateWalletDialog::walletName() const
{
return ui->wallet_name_line_edit->text();
@@ -99,3 +158,8 @@ bool CreateWalletDialog::isDescriptorWalletChecked() const
{
return ui->descriptor_checkbox->isChecked();
}
+
+bool CreateWalletDialog::isExternalSignerChecked() const
+{
+ return ui->external_signer_checkbox->isChecked();
+}
diff --git a/src/qt/createwalletdialog.h b/src/qt/createwalletdialog.h
index 20cce937c8..585b1461f7 100644
--- a/src/qt/createwalletdialog.h
+++ b/src/qt/createwalletdialog.h
@@ -9,6 +9,10 @@
class WalletModel;
+#ifdef ENABLE_EXTERNAL_SIGNER
+class ExternalSigner;
+#endif
+
namespace Ui {
class CreateWalletDialog;
}
@@ -23,11 +27,16 @@ public:
explicit CreateWalletDialog(QWidget* parent);
virtual ~CreateWalletDialog();
+#ifdef ENABLE_EXTERNAL_SIGNER
+ void setSigners(std::vector<ExternalSigner>& signers);
+#endif
+
QString walletName() const;
bool isEncryptWalletChecked() const;
bool isDisablePrivateKeysChecked() const;
bool isMakeBlankWalletChecked() const;
bool isDescriptorWalletChecked() const;
+ bool isExternalSignerChecked() const;
private:
Ui::CreateWalletDialog *ui;
diff --git a/src/qt/forms/createwalletdialog.ui b/src/qt/forms/createwalletdialog.ui
index 881869a46c..b11fb026b0 100644
--- a/src/qt/forms/createwalletdialog.ui
+++ b/src/qt/forms/createwalletdialog.ui
@@ -109,6 +109,16 @@
</property>
</widget>
</item>
+ <item>
+ <widget class="QCheckBox" name="external_signer_checkbox">
+ <property name="toolTip">
+ <string>Use an external signing device such as a hardware wallet. Configure the external signer script in wallet preferences first.</string>
+ </property>
+ <property name="text">
+ <string>External signer</string>
+ </property>
+ </widget>
+ </item>
</layout>
</widget>
</item>
@@ -143,6 +153,7 @@
<tabstop>disable_privkeys_checkbox</tabstop>
<tabstop>blank_wallet_checkbox</tabstop>
<tabstop>descriptor_checkbox</tabstop>
+ <tabstop>external_signer_checkbox</tabstop>
</tabstops>
<resources/>
<connections>
diff --git a/src/qt/forms/optionsdialog.ui b/src/qt/forms/optionsdialog.ui
index f199e8c1a1..bd72328c02 100644
--- a/src/qt/forms/optionsdialog.ui
+++ b/src/qt/forms/optionsdialog.ui
@@ -230,6 +230,36 @@
</widget>
</item>
<item>
+ <widget class="QGroupBox" name="groupBoxHww">
+ <property name="title">
+ <string>External Signer (e.g. hardware wallet)</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayoutHww">
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayoutHww">
+ <item>
+ <widget class="QLabel" name="externalSignerPathLabel">
+ <property name="text">
+ <string>&amp;External signer script path</string>
+ </property>
+ <property name="buddy">
+ <cstring>externalSignerPath</cstring>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="externalSignerPath">
+ <property name="toolTip">
+ <string>Full path to a Bitcoin Core compatible script (e.g. C:\Downloads\hwi.exe or /Users/you/Downloads/hwi.py). Beware: malware can steal your coins!</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
<spacer name="verticalSpacer_Wallet">
<property name="orientation">
<enum>Qt::Vertical</enum>
diff --git a/src/qt/forms/receiverequestdialog.ui b/src/qt/forms/receiverequestdialog.ui
index 7d95a8bc90..70a7cf71de 100644
--- a/src/qt/forms/receiverequestdialog.ui
+++ b/src/qt/forms/receiverequestdialog.ui
@@ -255,6 +255,19 @@
</widget>
</item>
<item>
+ <widget class="QPushButton" name="btnVerify">
+ <property name="text">
+ <string>&amp;Verify</string>
+ </property>
+ <property name="toolTip">
+ <string>Verify this address on e.g. a hardware wallet screen</string>
+ </property>
+ <property name="autoDefault">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
<widget class="QPushButton" name="btnSaveAs">
<property name="text">
<string>&amp;Save Imageā€¦</string>
diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp
index 8a32994e3f..6ad8db4348 100644
--- a/src/qt/optionsdialog.cpp
+++ b/src/qt/optionsdialog.cpp
@@ -199,6 +199,7 @@ void OptionsDialog::setModel(OptionsModel *_model)
connect(ui->prune, &QCheckBox::clicked, this, &OptionsDialog::togglePruneWarning);
connect(ui->pruneSize, qOverload<int>(&QSpinBox::valueChanged), this, &OptionsDialog::showRestartWarning);
connect(ui->databaseCache, qOverload<int>(&QSpinBox::valueChanged), this, &OptionsDialog::showRestartWarning);
+ connect(ui->externalSignerPath, &QLineEdit::textChanged, [this]{ showRestartWarning(); });
connect(ui->threadsScriptVerif, qOverload<int>(&QSpinBox::valueChanged), this, &OptionsDialog::showRestartWarning);
/* Wallet */
connect(ui->spendZeroConfChange, &QCheckBox::clicked, this, &OptionsDialog::showRestartWarning);
@@ -233,6 +234,7 @@ void OptionsDialog::setMapper()
/* Wallet */
mapper->addMapping(ui->spendZeroConfChange, OptionsModel::SpendZeroConfChange);
mapper->addMapping(ui->coinControlFeatures, OptionsModel::CoinControlFeatures);
+ mapper->addMapping(ui->externalSignerPath, OptionsModel::ExternalSignerPath);
/* Network */
mapper->addMapping(ui->mapPortUpnp, OptionsModel::MapPortUPnP);
diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp
index abdf9e9ae6..24a4e9ee96 100644
--- a/src/qt/optionsmodel.cpp
+++ b/src/qt/optionsmodel.cpp
@@ -117,6 +117,13 @@ void OptionsModel::Init(bool resetSettings)
settings.setValue("bSpendZeroConfChange", true);
if (!gArgs.SoftSetBoolArg("-spendzeroconfchange", settings.value("bSpendZeroConfChange").toBool()))
addOverriddenOption("-spendzeroconfchange");
+
+ if (!settings.contains("external_signer_path"))
+ settings.setValue("external_signer_path", "");
+
+ if (!gArgs.SoftSetArg("-signer", settings.value("external_signer_path").toString().toStdString())) {
+ addOverriddenOption("-signer");
+ }
#endif
// Network
@@ -326,6 +333,8 @@ QVariant OptionsModel::data(const QModelIndex & index, int role) const
#ifdef ENABLE_WALLET
case SpendZeroConfChange:
return settings.value("bSpendZeroConfChange");
+ case ExternalSignerPath:
+ return settings.value("external_signer_path");
#endif
case DisplayUnit:
return nDisplayUnit;
@@ -445,6 +454,12 @@ bool OptionsModel::setData(const QModelIndex & index, const QVariant & value, in
setRestartRequired(true);
}
break;
+ case ExternalSignerPath:
+ if (settings.value("external_signer_path") != value.toString()) {
+ settings.setValue("external_signer_path", value.toString());
+ setRestartRequired(true);
+ }
+ break;
#endif
case DisplayUnit:
setDisplayUnit(value);
diff --git a/src/qt/optionsmodel.h b/src/qt/optionsmodel.h
index 4d012a9b8f..535843e8ba 100644
--- a/src/qt/optionsmodel.h
+++ b/src/qt/optionsmodel.h
@@ -65,6 +65,7 @@ public:
Prune, // bool
PruneSize, // int
DatabaseCache, // int
+ ExternalSignerPath, // QString
SpendZeroConfChange, // bool
Listen, // bool
OptionIDRowCount,
diff --git a/src/qt/receiverequestdialog.cpp b/src/qt/receiverequestdialog.cpp
index 78ae5c07da..abe7de8f89 100644
--- a/src/qt/receiverequestdialog.cpp
+++ b/src/qt/receiverequestdialog.cpp
@@ -89,6 +89,12 @@ void ReceiveRequestDialog::setInfo(const SendCoinsRecipient &_info)
ui->wallet_tag->hide();
ui->wallet_content->hide();
}
+
+ ui->btnVerify->setVisible(this->model->wallet().hasExternalSigner());
+
+ connect(ui->btnVerify, &QPushButton::clicked, [this] {
+ model->displayAddress(info.address.toStdString());
+ });
}
void ReceiveRequestDialog::updateDisplayUnit()
diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp
index 160b43324f..e87a2b97bc 100644
--- a/src/qt/sendcoinsdialog.cpp
+++ b/src/qt/sendcoinsdialog.cpp
@@ -199,7 +199,16 @@ void SendCoinsDialog::setModel(WalletModel *_model)
// set default rbf checkbox state
ui->optInRBF->setCheckState(Qt::Checked);
- if (model->wallet().privateKeysDisabled()) {
+ if (model->wallet().hasExternalSigner()) {
+ ui->sendButton->setText(tr("Sign on device"));
+ if (gArgs.GetArg("-signer", "") != "") {
+ ui->sendButton->setEnabled(true);
+ ui->sendButton->setToolTip(tr("Connect your hardware wallet first."));
+ } else {
+ ui->sendButton->setEnabled(false);
+ ui->sendButton->setToolTip(tr("Set external signer script path in Options -> Wallet"));
+ }
+ } else if (model->wallet().privateKeysDisabled()) {
ui->sendButton->setText(tr("Cr&eate Unsigned"));
ui->sendButton->setToolTip(tr("Creates a Partially Signed Bitcoin Transaction (PSBT) for use with e.g. an offline %1 wallet, or a PSBT-compatible hardware wallet.").arg(PACKAGE_NAME));
}
@@ -313,14 +322,14 @@ bool SendCoinsDialog::PrepareSendText(QString& question_string, QString& informa
formatted.append(recipientElement);
}
- if (model->wallet().privateKeysDisabled()) {
+ if (model->wallet().privateKeysDisabled() && !model->wallet().hasExternalSigner()) {
question_string.append(tr("Do you want to draft this transaction?"));
} else {
question_string.append(tr("Are you sure you want to send?"));
}
question_string.append("<br /><span style='font-size:10pt;'>");
- if (model->wallet().privateKeysDisabled()) {
+ if (model->wallet().privateKeysDisabled() && !model->wallet().hasExternalSigner()) {
question_string.append(tr("Please, review your transaction proposal. This will produce a Partially Signed Bitcoin Transaction (PSBT) which you can save or copy and then sign with e.g. an offline %1 wallet, or a PSBT-compatible hardware wallet.").arg(PACKAGE_NAME));
} else {
question_string.append(tr("Please, review your transaction."));
@@ -386,8 +395,8 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
if (!PrepareSendText(question_string, informative_text, detailed_text)) return;
assert(m_current_transaction);
- const QString confirmation = model->wallet().privateKeysDisabled() ? tr("Confirm transaction proposal") : tr("Confirm send coins");
- const QString confirmButtonText = model->wallet().privateKeysDisabled() ? tr("Create Unsigned") : tr("Send");
+ const QString confirmation = model->wallet().privateKeysDisabled() && !model->wallet().hasExternalSigner() ? tr("Confirm transaction proposal") : tr("Confirm send coins");
+ const QString confirmButtonText = model->wallet().privateKeysDisabled() && !model->wallet().hasExternalSigner() ? tr("Create Unsigned") : tr("Sign and send");
SendConfirmationDialog confirmationDialog(confirmation, question_string, informative_text, detailed_text, SEND_CONFIRM_DELAY, confirmButtonText, this);
confirmationDialog.exec();
QMessageBox::StandardButton retval = static_cast<QMessageBox::StandardButton>(confirmationDialog.result());
@@ -403,9 +412,58 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
CMutableTransaction mtx = CMutableTransaction{*(m_current_transaction->getWtx())};
PartiallySignedTransaction psbtx(mtx);
bool complete = false;
- const TransactionError err = model->wallet().fillPSBT(SIGHASH_ALL, false /* sign */, true /* bip32derivs */, psbtx, complete, nullptr);
+ // Always fill without signing first. This prevents an external signer
+ // from being called prematurely and is not expensive.
+ TransactionError err = model->wallet().fillPSBT(SIGHASH_ALL, false /* sign */, true /* bip32derivs */, psbtx, complete, nullptr);
assert(!complete);
assert(err == TransactionError::OK);
+ if (model->wallet().hasExternalSigner()) {
+ try {
+ err = model->wallet().fillPSBT(SIGHASH_ALL, true /* sign */, true /* bip32derivs */, psbtx, complete, nullptr);
+ } catch (const std::runtime_error& e) {
+ QMessageBox::critical(nullptr, tr("Sign failed"), e.what());
+ send_failure = true;
+ return;
+ }
+ if (err == TransactionError::EXTERNAL_SIGNER_NOT_FOUND) {
+ QMessageBox::critical(nullptr, tr("External signer not found"), "External signer not found");
+ send_failure = true;
+ return;
+ }
+ if (err == TransactionError::EXTERNAL_SIGNER_FAILED) {
+ QMessageBox::critical(nullptr, tr("External signer failure"), "External signer failure");
+ send_failure = true;
+ return;
+ }
+ if (err != TransactionError::OK) {
+ tfm::format(std::cerr, "Failed to sign PSBT");
+ processSendCoinsReturn(WalletModel::TransactionCreationFailed);
+ send_failure = true;
+ return;
+ }
+ // fillPSBT does not always properly finalize
+ complete = FinalizeAndExtractPSBT(psbtx, mtx);
+ }
+
+ // Broadcast transaction if complete (even with an external signer this
+ // is not always the case, e.g. in a multisig wallet).
+ if (complete) {
+ const CTransactionRef tx = MakeTransactionRef(mtx);
+ m_current_transaction->setWtx(tx);
+ WalletModel::SendCoinsReturn sendStatus = model->sendCoins(*m_current_transaction);
+ // process sendStatus and on error generate message shown to user
+ processSendCoinsReturn(sendStatus);
+
+ if (sendStatus.status == WalletModel::OK) {
+ Q_EMIT coinsSent(m_current_transaction->getWtx()->GetHash());
+ } else {
+ send_failure = true;
+ }
+ return;
+ }
+
+ // Copy PSBT to clipboard and offer to save
+ assert(!complete);
// Serialize the PSBT
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
ssTx << psbtx;
@@ -447,7 +505,7 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
break;
default:
assert(false);
- }
+ } // msgBox.exec()
} else {
// now send the prepared transaction
WalletModel::SendCoinsReturn sendStatus = model->sendCoins(*m_current_transaction);
@@ -614,7 +672,9 @@ void SendCoinsDialog::setBalance(const interfaces::WalletBalances& balances)
if(model && model->getOptionsModel())
{
CAmount balance = balances.balance;
- if (model->wallet().privateKeysDisabled()) {
+ if (model->wallet().hasExternalSigner()) {
+ ui->labelBalanceName->setText(tr("External balance:"));
+ } else if (model->wallet().privateKeysDisabled()) {
balance = balances.watch_only_balance;
ui->labelBalanceName->setText(tr("Watch-only balance:"));
}
@@ -698,7 +758,7 @@ void SendCoinsDialog::on_buttonMinimizeFee_clicked()
void SendCoinsDialog::useAvailableBalance(SendCoinsEntry* entry)
{
// Include watch-only for wallets without private key
- m_coin_control->fAllowWatchOnly = model->wallet().privateKeysDisabled();
+ m_coin_control->fAllowWatchOnly = model->wallet().privateKeysDisabled() && !model->wallet().hasExternalSigner();
// Calculate available amount to send.
CAmount amount = model->wallet().getAvailableBalance(*m_coin_control);
@@ -753,7 +813,7 @@ void SendCoinsDialog::updateCoinControlState()
m_coin_control->m_confirm_target = getConfTargetForIndex(ui->confTargetSelector->currentIndex());
m_coin_control->m_signal_bip125_rbf = ui->optInRBF->isChecked();
// Include watch-only for wallets without private key
- m_coin_control->fAllowWatchOnly = model->wallet().privateKeysDisabled();
+ m_coin_control->fAllowWatchOnly = model->wallet().privateKeysDisabled() && !model->wallet().hasExternalSigner();
}
void SendCoinsDialog::updateNumberOfBlocks(int count, const QDateTime& blockDate, double nVerificationProgress, bool headers, SynchronizationState sync_state) {
diff --git a/src/qt/walletcontroller.cpp b/src/qt/walletcontroller.cpp
index aa26a01541..7e5790fd87 100644
--- a/src/qt/walletcontroller.cpp
+++ b/src/qt/walletcontroller.cpp
@@ -263,6 +263,9 @@ void CreateWalletActivity::createWallet()
if (m_create_wallet_dialog->isDescriptorWalletChecked()) {
flags |= WALLET_FLAG_DESCRIPTORS;
}
+ if (m_create_wallet_dialog->isExternalSignerChecked()) {
+ flags |= WALLET_FLAG_EXTERNAL_SIGNER;
+ }
QTimer::singleShot(500, worker(), [this, name, flags] {
std::unique_ptr<interfaces::Wallet> wallet = node().walletClient().createWallet(name, m_passphrase, flags, m_error_message, m_warning_message);
@@ -291,6 +294,17 @@ void CreateWalletActivity::finish()
void CreateWalletActivity::create()
{
m_create_wallet_dialog = new CreateWalletDialog(m_parent_widget);
+
+#ifdef ENABLE_EXTERNAL_SIGNER
+ std::vector<ExternalSigner> signers;
+ try {
+ signers = node().externalSigners();
+ } catch (const std::runtime_error& e) {
+ QMessageBox::critical(nullptr, tr("Can't list signers"), e.what());
+ }
+ m_create_wallet_dialog->setSigners(signers);
+#endif
+
m_create_wallet_dialog->setWindowModality(Qt::ApplicationModal);
m_create_wallet_dialog->show();
diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp
index 7c58b8afd2..e32b7c2807 100644
--- a/src/qt/walletmodel.cpp
+++ b/src/qt/walletmodel.cpp
@@ -552,6 +552,18 @@ bool WalletModel::bumpFee(uint256 hash, uint256& new_hash)
return true;
}
+bool WalletModel::displayAddress(std::string sAddress)
+{
+ CTxDestination dest = DecodeDestination(sAddress);
+ bool res = false;
+ try {
+ res = m_wallet->displayAddress(dest);
+ } catch (const std::runtime_error& e) {
+ QMessageBox::critical(nullptr, tr("Can't display address"), e.what());
+ }
+ return res;
+}
+
bool WalletModel::isWalletEnabled()
{
return !gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET);
diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h
index b2ce5d69fb..47a21bcfcf 100644
--- a/src/qt/walletmodel.h
+++ b/src/qt/walletmodel.h
@@ -136,6 +136,7 @@ public:
UnlockContext requestUnlock();
bool bumpFee(uint256 hash, uint256& new_hash);
+ bool displayAddress(std::string sAddress);
static bool isWalletEnabled();
diff --git a/src/qt/walletmodeltransaction.cpp b/src/qt/walletmodeltransaction.cpp
index 25172e774c..d185ddb7e8 100644
--- a/src/qt/walletmodeltransaction.cpp
+++ b/src/qt/walletmodeltransaction.cpp
@@ -26,6 +26,11 @@ CTransactionRef& WalletModelTransaction::getWtx()
return wtx;
}
+void WalletModelTransaction::setWtx(const CTransactionRef& newTx)
+{
+ wtx = newTx;
+}
+
unsigned int WalletModelTransaction::getTransactionSize()
{
return wtx ? GetVirtualTransactionSize(*wtx) : 0;
diff --git a/src/qt/walletmodeltransaction.h b/src/qt/walletmodeltransaction.h
index f9a95362c8..120d240d91 100644
--- a/src/qt/walletmodeltransaction.h
+++ b/src/qt/walletmodeltransaction.h
@@ -27,6 +27,8 @@ public:
QList<SendCoinsRecipient> getRecipients() const;
CTransactionRef& getWtx();
+ void setWtx(const CTransactionRef&);
+
unsigned int getTransactionSize();
void setTransactionFee(const CAmount& newFee);
diff --git a/src/wallet/interfaces.cpp b/src/wallet/interfaces.cpp
index aca52964ee..ee92316b89 100644
--- a/src/wallet/interfaces.cpp
+++ b/src/wallet/interfaces.cpp
@@ -206,6 +206,11 @@ public:
WalletBatch batch{m_wallet->GetDatabase()};
return m_wallet->SetAddressReceiveRequest(batch, dest, id, value);
}
+ bool displayAddress(const CTxDestination& dest) override
+ {
+ LOCK(m_wallet->cs_wallet);
+ return m_wallet->DisplayAddress(dest);
+ }
void lockCoin(const COutPoint& output) override
{
LOCK(m_wallet->cs_wallet);
@@ -446,6 +451,7 @@ public:
unsigned int getConfirmTarget() override { return m_wallet->m_confirm_target; }
bool hdEnabled() override { return m_wallet->IsHDEnabled(); }
bool canGetAddresses() override { return m_wallet->CanGetAddresses(); }
+ bool hasExternalSigner() override { return m_wallet->IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER); }
bool privateKeysDisabled() override { return m_wallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS); }
OutputType getDefaultAddressType() override { return m_wallet->m_default_address_type; }
CAmount getDefaultMaxTxFee() override { return m_wallet->m_default_max_tx_fee; }