mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-01-25 10:43:19 -03:00
Merge bitcoin-core/gui#4: UI external signer support (e.g. hardware wallet)
1c4b456e1a
gui: send using external signer (Sjors Provoost)24815c6309
gui: wallet creation detects external signer (Sjors Provoost)3f845ea299
node: add externalSigners to interface (Sjors Provoost)62ac119f91
gui: display address on external signer (Sjors Provoost)450cb40a34
wallet: add displayAddress to interface (Sjors Provoost)eef8d64529
gui: create wallet with external signer (Sjors Provoost)6cdbc83e93
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 ACK1c4b456e1a
hebasto: ACK1c4b456e1a
, tested on Linux Mint 20.1 (Qt 5.12.8) with HWW `2.0.2-rc.1`. promag: Tested ACK1c4b456e1a
but rebased withe033ca1379
, with HWI 2.0.2, with Nano S and Nano X. meshcollider: re-code-review ACK1c4b456e1a
Tree-SHA512: 3503113c5c69d40adb6ce364d8e7cae23ce82d032a00474ba9aeb6202eb70f496ef4a6bf2e623e5171e524ad31ade7941a4e0e89539c64518aaec74f4562d86b
This commit is contained in:
commit
68a89d7a46
19 changed files with 284 additions and 11 deletions
|
@ -6,6 +6,7 @@
|
||||||
#define BITCOIN_INTERFACES_NODE_H
|
#define BITCOIN_INTERFACES_NODE_H
|
||||||
|
|
||||||
#include <amount.h> // For CAmount
|
#include <amount.h> // For CAmount
|
||||||
|
#include <external_signer.h>
|
||||||
#include <net.h> // For NodeId
|
#include <net.h> // For NodeId
|
||||||
#include <net_types.h> // For banmap_t
|
#include <net_types.h> // For banmap_t
|
||||||
#include <netaddress.h> // For Network
|
#include <netaddress.h> // For Network
|
||||||
|
@ -110,6 +111,11 @@ public:
|
||||||
//! Disconnect node by id.
|
//! Disconnect node by id.
|
||||||
virtual bool disconnectById(NodeId id) = 0;
|
virtual bool disconnectById(NodeId id) = 0;
|
||||||
|
|
||||||
|
#ifdef ENABLE_EXTERNAL_SIGNER
|
||||||
|
//! List external signers
|
||||||
|
virtual std::vector<ExternalSigner> externalSigners() = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
//! Get total bytes recv.
|
//! Get total bytes recv.
|
||||||
virtual int64_t getTotalBytesRecv() = 0;
|
virtual int64_t getTotalBytesRecv() = 0;
|
||||||
|
|
||||||
|
|
|
@ -118,6 +118,9 @@ public:
|
||||||
//! Save or remove receive request.
|
//! Save or remove receive request.
|
||||||
virtual bool setAddressReceiveRequest(const CTxDestination& dest, const std::string& id, const std::string& value) = 0;
|
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.
|
//! Lock coin.
|
||||||
virtual void lockCoin(const COutPoint& output) = 0;
|
virtual void lockCoin(const COutPoint& output) = 0;
|
||||||
|
|
||||||
|
@ -252,6 +255,9 @@ public:
|
||||||
// Return whether private keys enabled.
|
// Return whether private keys enabled.
|
||||||
virtual bool privateKeysDisabled() = 0;
|
virtual bool privateKeysDisabled() = 0;
|
||||||
|
|
||||||
|
// Return whether wallet uses an external signer.
|
||||||
|
virtual bool hasExternalSigner() = 0;
|
||||||
|
|
||||||
// Get default address type.
|
// Get default address type.
|
||||||
virtual OutputType getDefaultAddressType() = 0;
|
virtual OutputType getDefaultAddressType() = 0;
|
||||||
|
|
||||||
|
|
|
@ -170,6 +170,16 @@ public:
|
||||||
}
|
}
|
||||||
return false;
|
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 getTotalBytesRecv() override { return m_context->connman ? m_context->connman->GetTotalBytesRecv() : 0; }
|
||||||
int64_t getTotalBytesSent() override { return m_context->connman ? m_context->connman->GetTotalBytesSent() : 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; }
|
size_t getMempoolSize() override { return m_context->mempool ? m_context->mempool->size() : 0; }
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#include <config/bitcoin-config.h>
|
#include <config/bitcoin-config.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <external_signer.h>
|
||||||
#include <qt/createwalletdialog.h>
|
#include <qt/createwalletdialog.h>
|
||||||
#include <qt/forms/ui_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) {
|
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.
|
// set to true, enable it when isEncryptWalletChecked is false.
|
||||||
ui->disable_privkeys_checkbox->setEnabled(!checked);
|
ui->disable_privkeys_checkbox->setEnabled(!checked);
|
||||||
|
ui->external_signer_checkbox->setEnabled(!checked);
|
||||||
|
|
||||||
// When the disable_privkeys_checkbox is disabled, uncheck it.
|
// When the disable_privkeys_checkbox is disabled, uncheck it.
|
||||||
if (!ui->disable_privkeys_checkbox->isEnabled()) {
|
if (!ui->disable_privkeys_checkbox->isEnabled()) {
|
||||||
ui->disable_privkeys_checkbox->setChecked(false);
|
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) {
|
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->setToolTip(tr("Compiled without sqlite support (required for descriptor wallets)"));
|
||||||
ui->descriptor_checkbox->setEnabled(false);
|
ui->descriptor_checkbox->setEnabled(false);
|
||||||
ui->descriptor_checkbox->setChecked(false);
|
ui->descriptor_checkbox->setChecked(false);
|
||||||
|
ui->external_signer_checkbox->setEnabled(false);
|
||||||
|
ui->external_signer_checkbox->setChecked(false);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef USE_BDB
|
#ifndef USE_BDB
|
||||||
ui->descriptor_checkbox->setEnabled(false);
|
ui->descriptor_checkbox->setEnabled(false);
|
||||||
ui->descriptor_checkbox->setChecked(true);
|
ui->descriptor_checkbox->setChecked(true);
|
||||||
#endif
|
#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()
|
CreateWalletDialog::~CreateWalletDialog()
|
||||||
|
@ -75,6 +112,28 @@ CreateWalletDialog::~CreateWalletDialog()
|
||||||
delete ui;
|
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
|
QString CreateWalletDialog::walletName() const
|
||||||
{
|
{
|
||||||
return ui->wallet_name_line_edit->text();
|
return ui->wallet_name_line_edit->text();
|
||||||
|
@ -99,3 +158,8 @@ bool CreateWalletDialog::isDescriptorWalletChecked() const
|
||||||
{
|
{
|
||||||
return ui->descriptor_checkbox->isChecked();
|
return ui->descriptor_checkbox->isChecked();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CreateWalletDialog::isExternalSignerChecked() const
|
||||||
|
{
|
||||||
|
return ui->external_signer_checkbox->isChecked();
|
||||||
|
}
|
||||||
|
|
|
@ -9,6 +9,10 @@
|
||||||
|
|
||||||
class WalletModel;
|
class WalletModel;
|
||||||
|
|
||||||
|
#ifdef ENABLE_EXTERNAL_SIGNER
|
||||||
|
class ExternalSigner;
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
class CreateWalletDialog;
|
class CreateWalletDialog;
|
||||||
}
|
}
|
||||||
|
@ -23,11 +27,16 @@ public:
|
||||||
explicit CreateWalletDialog(QWidget* parent);
|
explicit CreateWalletDialog(QWidget* parent);
|
||||||
virtual ~CreateWalletDialog();
|
virtual ~CreateWalletDialog();
|
||||||
|
|
||||||
|
#ifdef ENABLE_EXTERNAL_SIGNER
|
||||||
|
void setSigners(std::vector<ExternalSigner>& signers);
|
||||||
|
#endif
|
||||||
|
|
||||||
QString walletName() const;
|
QString walletName() const;
|
||||||
bool isEncryptWalletChecked() const;
|
bool isEncryptWalletChecked() const;
|
||||||
bool isDisablePrivateKeysChecked() const;
|
bool isDisablePrivateKeysChecked() const;
|
||||||
bool isMakeBlankWalletChecked() const;
|
bool isMakeBlankWalletChecked() const;
|
||||||
bool isDescriptorWalletChecked() const;
|
bool isDescriptorWalletChecked() const;
|
||||||
|
bool isExternalSignerChecked() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Ui::CreateWalletDialog *ui;
|
Ui::CreateWalletDialog *ui;
|
||||||
|
|
|
@ -109,6 +109,16 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</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>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
@ -143,6 +153,7 @@
|
||||||
<tabstop>disable_privkeys_checkbox</tabstop>
|
<tabstop>disable_privkeys_checkbox</tabstop>
|
||||||
<tabstop>blank_wallet_checkbox</tabstop>
|
<tabstop>blank_wallet_checkbox</tabstop>
|
||||||
<tabstop>descriptor_checkbox</tabstop>
|
<tabstop>descriptor_checkbox</tabstop>
|
||||||
|
<tabstop>external_signer_checkbox</tabstop>
|
||||||
</tabstops>
|
</tabstops>
|
||||||
<resources/>
|
<resources/>
|
||||||
<connections>
|
<connections>
|
||||||
|
|
|
@ -229,6 +229,36 @@
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</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>&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>
|
<item>
|
||||||
<spacer name="verticalSpacer_Wallet">
|
<spacer name="verticalSpacer_Wallet">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
|
|
|
@ -254,6 +254,19 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="btnVerify">
|
||||||
|
<property name="text">
|
||||||
|
<string>&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>
|
<item>
|
||||||
<widget class="QPushButton" name="btnSaveAs">
|
<widget class="QPushButton" name="btnSaveAs">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
|
|
|
@ -199,6 +199,7 @@ void OptionsDialog::setModel(OptionsModel *_model)
|
||||||
connect(ui->prune, &QCheckBox::clicked, this, &OptionsDialog::togglePruneWarning);
|
connect(ui->prune, &QCheckBox::clicked, this, &OptionsDialog::togglePruneWarning);
|
||||||
connect(ui->pruneSize, qOverload<int>(&QSpinBox::valueChanged), this, &OptionsDialog::showRestartWarning);
|
connect(ui->pruneSize, qOverload<int>(&QSpinBox::valueChanged), this, &OptionsDialog::showRestartWarning);
|
||||||
connect(ui->databaseCache, 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);
|
connect(ui->threadsScriptVerif, qOverload<int>(&QSpinBox::valueChanged), this, &OptionsDialog::showRestartWarning);
|
||||||
/* Wallet */
|
/* Wallet */
|
||||||
connect(ui->spendZeroConfChange, &QCheckBox::clicked, this, &OptionsDialog::showRestartWarning);
|
connect(ui->spendZeroConfChange, &QCheckBox::clicked, this, &OptionsDialog::showRestartWarning);
|
||||||
|
@ -233,6 +234,7 @@ void OptionsDialog::setMapper()
|
||||||
/* Wallet */
|
/* Wallet */
|
||||||
mapper->addMapping(ui->spendZeroConfChange, OptionsModel::SpendZeroConfChange);
|
mapper->addMapping(ui->spendZeroConfChange, OptionsModel::SpendZeroConfChange);
|
||||||
mapper->addMapping(ui->coinControlFeatures, OptionsModel::CoinControlFeatures);
|
mapper->addMapping(ui->coinControlFeatures, OptionsModel::CoinControlFeatures);
|
||||||
|
mapper->addMapping(ui->externalSignerPath, OptionsModel::ExternalSignerPath);
|
||||||
|
|
||||||
/* Network */
|
/* Network */
|
||||||
mapper->addMapping(ui->mapPortUpnp, OptionsModel::MapPortUPnP);
|
mapper->addMapping(ui->mapPortUpnp, OptionsModel::MapPortUPnP);
|
||||||
|
|
|
@ -117,6 +117,13 @@ void OptionsModel::Init(bool resetSettings)
|
||||||
settings.setValue("bSpendZeroConfChange", true);
|
settings.setValue("bSpendZeroConfChange", true);
|
||||||
if (!gArgs.SoftSetBoolArg("-spendzeroconfchange", settings.value("bSpendZeroConfChange").toBool()))
|
if (!gArgs.SoftSetBoolArg("-spendzeroconfchange", settings.value("bSpendZeroConfChange").toBool()))
|
||||||
addOverriddenOption("-spendzeroconfchange");
|
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
|
#endif
|
||||||
|
|
||||||
// Network
|
// Network
|
||||||
|
@ -326,6 +333,8 @@ QVariant OptionsModel::data(const QModelIndex & index, int role) const
|
||||||
#ifdef ENABLE_WALLET
|
#ifdef ENABLE_WALLET
|
||||||
case SpendZeroConfChange:
|
case SpendZeroConfChange:
|
||||||
return settings.value("bSpendZeroConfChange");
|
return settings.value("bSpendZeroConfChange");
|
||||||
|
case ExternalSignerPath:
|
||||||
|
return settings.value("external_signer_path");
|
||||||
#endif
|
#endif
|
||||||
case DisplayUnit:
|
case DisplayUnit:
|
||||||
return nDisplayUnit;
|
return nDisplayUnit;
|
||||||
|
@ -445,6 +454,12 @@ bool OptionsModel::setData(const QModelIndex & index, const QVariant & value, in
|
||||||
setRestartRequired(true);
|
setRestartRequired(true);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case ExternalSignerPath:
|
||||||
|
if (settings.value("external_signer_path") != value.toString()) {
|
||||||
|
settings.setValue("external_signer_path", value.toString());
|
||||||
|
setRestartRequired(true);
|
||||||
|
}
|
||||||
|
break;
|
||||||
#endif
|
#endif
|
||||||
case DisplayUnit:
|
case DisplayUnit:
|
||||||
setDisplayUnit(value);
|
setDisplayUnit(value);
|
||||||
|
|
|
@ -65,6 +65,7 @@ public:
|
||||||
Prune, // bool
|
Prune, // bool
|
||||||
PruneSize, // int
|
PruneSize, // int
|
||||||
DatabaseCache, // int
|
DatabaseCache, // int
|
||||||
|
ExternalSignerPath, // QString
|
||||||
SpendZeroConfChange, // bool
|
SpendZeroConfChange, // bool
|
||||||
Listen, // bool
|
Listen, // bool
|
||||||
OptionIDRowCount,
|
OptionIDRowCount,
|
||||||
|
|
|
@ -89,6 +89,12 @@ void ReceiveRequestDialog::setInfo(const SendCoinsRecipient &_info)
|
||||||
ui->wallet_tag->hide();
|
ui->wallet_tag->hide();
|
||||||
ui->wallet_content->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()
|
void ReceiveRequestDialog::updateDisplayUnit()
|
||||||
|
|
|
@ -199,7 +199,16 @@ void SendCoinsDialog::setModel(WalletModel *_model)
|
||||||
// set default rbf checkbox state
|
// set default rbf checkbox state
|
||||||
ui->optInRBF->setCheckState(Qt::Checked);
|
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->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));
|
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);
|
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?"));
|
question_string.append(tr("Do you want to draft this transaction?"));
|
||||||
} else {
|
} else {
|
||||||
question_string.append(tr("Are you sure you want to send?"));
|
question_string.append(tr("Are you sure you want to send?"));
|
||||||
}
|
}
|
||||||
|
|
||||||
question_string.append("<br /><span style='font-size:10pt;'>");
|
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));
|
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 {
|
} else {
|
||||||
question_string.append(tr("Please, review your transaction."));
|
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;
|
if (!PrepareSendText(question_string, informative_text, detailed_text)) return;
|
||||||
assert(m_current_transaction);
|
assert(m_current_transaction);
|
||||||
|
|
||||||
const QString confirmation = model->wallet().privateKeysDisabled() ? tr("Confirm transaction proposal") : tr("Confirm send coins");
|
const QString confirmation = model->wallet().privateKeysDisabled() && !model->wallet().hasExternalSigner() ? tr("Confirm transaction proposal") : tr("Confirm send coins");
|
||||||
const QString confirmButtonText = model->wallet().privateKeysDisabled() ? tr("Create Unsigned") : tr("Send");
|
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);
|
SendConfirmationDialog confirmationDialog(confirmation, question_string, informative_text, detailed_text, SEND_CONFIRM_DELAY, confirmButtonText, this);
|
||||||
confirmationDialog.exec();
|
confirmationDialog.exec();
|
||||||
QMessageBox::StandardButton retval = static_cast<QMessageBox::StandardButton>(confirmationDialog.result());
|
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())};
|
CMutableTransaction mtx = CMutableTransaction{*(m_current_transaction->getWtx())};
|
||||||
PartiallySignedTransaction psbtx(mtx);
|
PartiallySignedTransaction psbtx(mtx);
|
||||||
bool complete = false;
|
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(!complete);
|
||||||
assert(err == TransactionError::OK);
|
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
|
// Serialize the PSBT
|
||||||
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
|
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
|
||||||
ssTx << psbtx;
|
ssTx << psbtx;
|
||||||
|
@ -447,7 +505,7 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
assert(false);
|
assert(false);
|
||||||
}
|
} // msgBox.exec()
|
||||||
} else {
|
} else {
|
||||||
// now send the prepared transaction
|
// now send the prepared transaction
|
||||||
WalletModel::SendCoinsReturn sendStatus = model->sendCoins(*m_current_transaction);
|
WalletModel::SendCoinsReturn sendStatus = model->sendCoins(*m_current_transaction);
|
||||||
|
@ -614,7 +672,9 @@ void SendCoinsDialog::setBalance(const interfaces::WalletBalances& balances)
|
||||||
if(model && model->getOptionsModel())
|
if(model && model->getOptionsModel())
|
||||||
{
|
{
|
||||||
CAmount balance = balances.balance;
|
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;
|
balance = balances.watch_only_balance;
|
||||||
ui->labelBalanceName->setText(tr("Watch-only balance:"));
|
ui->labelBalanceName->setText(tr("Watch-only balance:"));
|
||||||
}
|
}
|
||||||
|
@ -698,7 +758,7 @@ void SendCoinsDialog::on_buttonMinimizeFee_clicked()
|
||||||
void SendCoinsDialog::useAvailableBalance(SendCoinsEntry* entry)
|
void SendCoinsDialog::useAvailableBalance(SendCoinsEntry* entry)
|
||||||
{
|
{
|
||||||
// Include watch-only for wallets without private key
|
// 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.
|
// Calculate available amount to send.
|
||||||
CAmount amount = model->wallet().getAvailableBalance(*m_coin_control);
|
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_confirm_target = getConfTargetForIndex(ui->confTargetSelector->currentIndex());
|
||||||
m_coin_control->m_signal_bip125_rbf = ui->optInRBF->isChecked();
|
m_coin_control->m_signal_bip125_rbf = ui->optInRBF->isChecked();
|
||||||
// Include watch-only for wallets without private key
|
// 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) {
|
void SendCoinsDialog::updateNumberOfBlocks(int count, const QDateTime& blockDate, double nVerificationProgress, bool headers, SynchronizationState sync_state) {
|
||||||
|
|
|
@ -263,6 +263,9 @@ void CreateWalletActivity::createWallet()
|
||||||
if (m_create_wallet_dialog->isDescriptorWalletChecked()) {
|
if (m_create_wallet_dialog->isDescriptorWalletChecked()) {
|
||||||
flags |= WALLET_FLAG_DESCRIPTORS;
|
flags |= WALLET_FLAG_DESCRIPTORS;
|
||||||
}
|
}
|
||||||
|
if (m_create_wallet_dialog->isExternalSignerChecked()) {
|
||||||
|
flags |= WALLET_FLAG_EXTERNAL_SIGNER;
|
||||||
|
}
|
||||||
|
|
||||||
QTimer::singleShot(500, worker(), [this, name, flags] {
|
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);
|
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()
|
void CreateWalletActivity::create()
|
||||||
{
|
{
|
||||||
m_create_wallet_dialog = new CreateWalletDialog(m_parent_widget);
|
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->setWindowModality(Qt::ApplicationModal);
|
||||||
m_create_wallet_dialog->show();
|
m_create_wallet_dialog->show();
|
||||||
|
|
||||||
|
|
|
@ -552,6 +552,18 @@ bool WalletModel::bumpFee(uint256 hash, uint256& new_hash)
|
||||||
return true;
|
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()
|
bool WalletModel::isWalletEnabled()
|
||||||
{
|
{
|
||||||
return !gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET);
|
return !gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET);
|
||||||
|
|
|
@ -136,6 +136,7 @@ public:
|
||||||
UnlockContext requestUnlock();
|
UnlockContext requestUnlock();
|
||||||
|
|
||||||
bool bumpFee(uint256 hash, uint256& new_hash);
|
bool bumpFee(uint256 hash, uint256& new_hash);
|
||||||
|
bool displayAddress(std::string sAddress);
|
||||||
|
|
||||||
static bool isWalletEnabled();
|
static bool isWalletEnabled();
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,11 @@ CTransactionRef& WalletModelTransaction::getWtx()
|
||||||
return wtx;
|
return wtx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WalletModelTransaction::setWtx(const CTransactionRef& newTx)
|
||||||
|
{
|
||||||
|
wtx = newTx;
|
||||||
|
}
|
||||||
|
|
||||||
unsigned int WalletModelTransaction::getTransactionSize()
|
unsigned int WalletModelTransaction::getTransactionSize()
|
||||||
{
|
{
|
||||||
return wtx ? GetVirtualTransactionSize(*wtx) : 0;
|
return wtx ? GetVirtualTransactionSize(*wtx) : 0;
|
||||||
|
|
|
@ -27,6 +27,8 @@ public:
|
||||||
QList<SendCoinsRecipient> getRecipients() const;
|
QList<SendCoinsRecipient> getRecipients() const;
|
||||||
|
|
||||||
CTransactionRef& getWtx();
|
CTransactionRef& getWtx();
|
||||||
|
void setWtx(const CTransactionRef&);
|
||||||
|
|
||||||
unsigned int getTransactionSize();
|
unsigned int getTransactionSize();
|
||||||
|
|
||||||
void setTransactionFee(const CAmount& newFee);
|
void setTransactionFee(const CAmount& newFee);
|
||||||
|
|
|
@ -206,6 +206,11 @@ public:
|
||||||
WalletBatch batch{m_wallet->GetDatabase()};
|
WalletBatch batch{m_wallet->GetDatabase()};
|
||||||
return m_wallet->SetAddressReceiveRequest(batch, dest, id, value);
|
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
|
void lockCoin(const COutPoint& output) override
|
||||||
{
|
{
|
||||||
LOCK(m_wallet->cs_wallet);
|
LOCK(m_wallet->cs_wallet);
|
||||||
|
@ -446,6 +451,7 @@ public:
|
||||||
unsigned int getConfirmTarget() override { return m_wallet->m_confirm_target; }
|
unsigned int getConfirmTarget() override { return m_wallet->m_confirm_target; }
|
||||||
bool hdEnabled() override { return m_wallet->IsHDEnabled(); }
|
bool hdEnabled() override { return m_wallet->IsHDEnabled(); }
|
||||||
bool canGetAddresses() override { return m_wallet->CanGetAddresses(); }
|
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); }
|
bool privateKeysDisabled() override { return m_wallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS); }
|
||||||
OutputType getDefaultAddressType() override { return m_wallet->m_default_address_type; }
|
OutputType getDefaultAddressType() override { return m_wallet->m_default_address_type; }
|
||||||
CAmount getDefaultMaxTxFee() override { return m_wallet->m_default_max_tx_fee; }
|
CAmount getDefaultMaxTxFee() override { return m_wallet->m_default_max_tx_fee; }
|
||||||
|
|
Loading…
Add table
Reference in a new issue