Add GUI to import private keys

Addresses GH #1808.

There are several possible future improvements:

 * add a new icon for key import

 * reduce duplication of code between the RPC private key importer and
 the GUI private key importer

 * improving error messages for various error states
This commit is contained in:
chromatic 2021-04-21 16:35:06 -07:00
parent 5389fac4ea
commit 0565a85c7c
11 changed files with 469 additions and 0 deletions

View File

@ -5,6 +5,7 @@ FORMS += \
../src/qt/forms/coincontroldialog.ui \
../src/qt/forms/editaddressdialog.ui \
../src/qt/forms/helpmessagedialog.ui \
../src/qt/forms/importkeysdialog.ui \
../src/qt/forms/intro.ui \
../src/qt/forms/openuridialog.ui \
../src/qt/forms/optionsdialog.ui \

View File

@ -101,6 +101,7 @@ QT_FORMS_UI = \
qt/forms/editaddressdialog.ui \
qt/forms/helpmessagedialog.ui \
qt/forms/intro.ui \
qt/forms/importkeysdialog.ui \
qt/forms/modaloverlay.ui \
qt/forms/openuridialog.ui \
qt/forms/optionsdialog.ui \
@ -130,6 +131,7 @@ QT_MOC_CPP = \
qt/moc_editaddressdialog.cpp \
qt/moc_guiutil.cpp \
qt/moc_intro.cpp \
qt/moc_importkeysdialog.cpp \
qt/moc_macdockiconhandler.cpp \
qt/moc_macnotificationhandler.cpp \
qt/moc_modaloverlay.cpp \
@ -169,6 +171,7 @@ BITCOIN_MM = \
QT_MOC = \
qt/bitcoin.moc \
qt/bitcoinamountfield.moc \
qt/importkeysdialog.moc \
qt/intro.moc \
qt/overviewpage.moc \
qt/rpcconsole.moc
@ -199,6 +202,7 @@ BITCOIN_QT_H = \
qt/guiconstants.h \
qt/guiutil.h \
qt/intro.h \
qt/importkeysdialog.h \
qt/macdockiconhandler.h \
qt/macnotificationhandler.h \
qt/modaloverlay.h \
@ -331,6 +335,7 @@ BITCOIN_QT_WALLET_CPP = \
qt/coincontroldialog.cpp \
qt/coincontroltreewidget.cpp \
qt/editaddressdialog.cpp \
qt/importkeysdialog.cpp \
qt/openuridialog.cpp \
qt/overviewpage.cpp \
qt/paymentrequestplus.cpp \

View File

@ -102,6 +102,7 @@ BitcoinGUI::BitcoinGUI(const PlatformStyle *_platformStyle, const NetworkStyle *
sendCoinsMenuAction(0),
usedSendingAddressesAction(0),
usedReceivingAddressesAction(0),
importPrivateKeyAction(0),
signMessageAction(0),
verifyMessageAction(0),
aboutAction(0),
@ -392,6 +393,9 @@ void BitcoinGUI::createActions()
openAction = new QAction(platformStyle->TextColorIcon(":/icons/open"), tr("Open &URI..."), this);
openAction->setStatusTip(tr("Open a dogecoin: URI or payment request"));
importPrivateKeyAction = new QAction(platformStyle->TextColorIcon(":/icons/address-book"), tr("&Import Private Key..."), this);
importPrivateKeyAction->setStatusTip(tr("Import a Dogecoin private key"));
showHelpMessageAction = new QAction(platformStyle->TextColorIcon(":/icons/info"), tr("&Command-line options"), this);
showHelpMessageAction->setMenuRole(QAction::NoRole);
showHelpMessageAction->setStatusTip(tr("Show the %1 help message to get a list with possible Dogecoin command-line options").arg(tr(PACKAGE_NAME)));
@ -418,6 +422,7 @@ void BitcoinGUI::createActions()
connect(usedReceivingAddressesAction, SIGNAL(triggered()), walletFrame, SLOT(usedReceivingAddresses()));
connect(openAction, SIGNAL(triggered()), this, SLOT(openClicked()));
connect(paperWalletAction, SIGNAL(triggered()), walletFrame, SLOT(printPaperWallets()));
connect(importPrivateKeyAction, SIGNAL(triggered()), walletFrame, SLOT(importPrivateKey()));
}
#endif // ENABLE_WALLET
@ -445,6 +450,7 @@ void BitcoinGUI::createMenuBar()
file->addAction(verifyMessageAction);
file->addAction(paperWalletAction);
file->addSeparator();
file->addAction(importPrivateKeyAction);
file->addAction(usedSendingAddressesAction);
file->addAction(usedReceivingAddressesAction);
file->addSeparator();

View File

@ -99,6 +99,7 @@ private:
QAction *sendCoinsMenuAction;
QAction *usedSendingAddressesAction;
QAction *usedReceivingAddressesAction;
QAction *importPrivateKeyAction;
QAction *signMessageAction;
QAction *verifyMessageAction;
QAction *paperWalletAction;

View File

@ -0,0 +1,208 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ImportKeysDialog</class>
<widget class="QDialog" name="ImportKeysDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>560</width>
<height>200</height>
</rect>
</property>
<property name="windowTitle">
<string>Import Private Key</string>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QWidget" name="tabImportPrivateKey">
<attribute name="title">
<string>&amp;Import Private Key</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_Display">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_1_ImportPrivateKey">
<item>
<widget class="QLabel" name="privateKeyWidgetLabel">
<property name="text">
<string>Private Key:</string>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="privateKey">
<property name="toolTip">
<string>Private key to import into your wallet</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2_ImportPrivateKey">
<item>
<widget class="QLabel" name="privateKeyLabelWidgetLabel">
<property name="toolTip">
<string>Label for this private key in your wallet</string>
</property>
<property name="text">
<string>Label (optional):</string>
</property>
<property name="buddy">
<cstring>privateKeyLabel</cstring>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="privateKeyLabel">
<property name="toolTip">
<string>Label for this private key in your wallet</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="rescanCheckBox">
<property name="toolTip">
<string extracomment="Rescan the blockchain on import (disabled when running in pruned mode)."/>
</property>
<property name="text">
<string>Rescan</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="privateKeyImportTextMessage">
<property name="text">
<string></string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_ImportPrivateKey">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QFrame" name="frame">
<layout class="QVBoxLayout" name="verticalLayout_Bottom">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_Bottom"/>
</item>
<item>
<widget class="QLabel" name="overriddenByCommandLineLabel">
<property name="text">
<string/>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_Buttons">
<item>
<widget class="QPushButton" name="resetButton">
<property name="toolTip">
<string>Reset all key management options to default.</string>
</property>
<property name="text">
<string>&amp;Reset</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_1">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>48</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="statusLabel">
<property name="minimumSize">
<size>
<width>200</width>
<height>0</height>
</size>
</property>
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string/>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="okButton">
<property name="text">
<string>&amp;Import</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
<property name="default">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="cancelButton">
<property name="text">
<string>&amp;Cancel</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

164
src/qt/importkeysdialog.cpp Normal file
View File

@ -0,0 +1,164 @@
// Copyright (c) 2021 The Dogecoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#if defined(HAVE_CONFIG_H)
#include "config/bitcoin-config.h"
#endif
#include "importkeysdialog.h"
#include "ui_importkeysdialog.h"
#include "base58.h"
#include "guiutil.h"
#include "platformstyle.h"
#include "validation.h"
#include "wallet/wallet.h"
#include "walletmodel.h"
#include "util.h"
#include <QThread>
#include <QDebug>
/* Object for executing key import commands in a separate thread.
*/
class ImportKeyExecutor : public QObject
{
Q_OBJECT
public Q_SLOTS:
void rescan(CWallet*, CBlockIndex*);
Q_SIGNALS:
void rescanWallet(CWallet*, CBlockIndex*);
};
#include "importkeysdialog.moc"
void ImportKeyExecutor::rescan(CWallet* pwallet, CBlockIndex* genesisBlock)
{
qWarning() << "started import key thread";
pwallet->UpdateTimeFirstKey(1);
pwallet->ScanForWalletTransactions(genesisBlock, true);
qWarning() << "quitting import key thread";
QObject::thread()->quit();
}
ImportKeysDialog::ImportKeysDialog(const PlatformStyle *_platformStyle, QWidget *parent) :
QDialog(parent),
ui(new Ui::ImportKeysDialog),
platformStyle(_platformStyle)
{
ui->setupUi(this);
/* Main elements init */
if (fPruneMode) {
ui->rescanCheckBox->setEnabled(false);
}
}
ImportKeysDialog::~ImportKeysDialog()
{
thread.wait();
delete ui;
}
void ImportKeysDialog::on_okButton_clicked()
{
if (importKey()) {
resetDialogValues();
accept();
};
}
void ImportKeysDialog::on_cancelButton_clicked()
{
reject();
}
void ImportKeysDialog::on_resetButton_clicked()
{
resetDialogValues();
}
bool ImportKeysDialog::importKey()
{
const QString privateKey = ui->privateKey->text();
const QString privateKeyLabel = ui->privateKeyLabel->text();
const bool rescan = ui->rescanCheckBox->isChecked();
resetDialogValues();
CBitcoinSecret vchSecret;
bool fGood = vchSecret.SetString(privateKey.toStdString());
if (!fGood) {
vchSecret.SetString("");
ui->privateKeyImportTextMessage->setText(tr("Invalid private key; please check and try again!"));
return false;
}
CKey key = vchSecret.GetKey();
if (!key.IsValid()) {
vchSecret.SetString("");
ui->privateKeyImportTextMessage->setText(tr("Invalid private key; please check and try again!"));
return false;
}
CPubKey pubkey = key.GetPubKey();
assert(key.VerifyPubKey(pubkey));
CKeyID vchAddress = pubkey.GetID();
pwalletMain->MarkDirty();
pwalletMain->SetAddressBook(vchAddress, privateKeyLabel.toStdString(), "receive");
if (pwalletMain->HaveKey(vchAddress)) {
vchSecret.SetString("");
ui->privateKeyImportTextMessage->setText(
tr("Invalid address generated from private key; please check and try again!"
));
return false;
}
pwalletMain->mapKeyMetadata[vchAddress].nCreateTime = 1;
if (!pwalletMain->AddKeyPubKey(key, pubkey)) {
vchSecret.SetString("");
ui->privateKeyImportTextMessage->setText(tr("Failed to add private key."));
return false;
}
pwalletMain->UpdateTimeFirstKey(1);
if (rescan) {
ImportKeyExecutor *executor = new ImportKeyExecutor();
executor->moveToThread(&thread);
connect(this, SIGNAL(rescanWallet(CWallet*, CBlockIndex*)), executor, SLOT(rescan(CWallet*, CBlockIndex*)));
// On stopExecutor signal
// - quit the Qt event loop in the execution thread
connect(this, SIGNAL(stopExecutor()), &thread, SLOT(quit()));
// - queue executor for deletion (in execution thread)
connect(&thread, SIGNAL(finished()), executor, SLOT(deleteLater()), Qt::DirectConnection);
// Default implementation of QThread::run() simply spins up an event loop in the thread,
// which is what we want.
thread.start();
ui->privateKeyImportTextMessage->setText(tr("Rescanning..."));
Q_EMIT rescanWallet(pwalletMain, chainActive.Genesis());
}
vchSecret.SetString("");
return true;
}
void ImportKeysDialog::resetDialogValues()
{
ui->privateKey->clear();
ui->privateKeyLabel->clear();
ui->rescanCheckBox->setCheckState(Qt::Unchecked);
}
void ImportKeysDialog::setOkButtonState(bool fState)
{
ui->okButton->setEnabled(fState);
}

50
src/qt/importkeysdialog.h Normal file
View File

@ -0,0 +1,50 @@
// Copyright (c) 2021 The Dogecoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef BITCOIN_QT_OPTIONSDIALOG_H
#define BITCOIN_QT_OPTIONSDIALOG_H
class CWallet;
class CBlockIndex;
#include <QDialog>
#include <QThread>
class ImportKeysDialog;
class PlatformStyle;
namespace Ui {
class ImportKeysDialog;
}
/** Preferences dialog. */
class ImportKeysDialog : public QDialog
{
Q_OBJECT
public:
explicit ImportKeysDialog(const PlatformStyle *_platformStyle, QWidget *parent = 0);
~ImportKeysDialog();
Q_SIGNALS:
void stopExecutor();
void rescanWallet(CWallet*, CBlockIndex*);
private:
Ui::ImportKeysDialog *ui;
const PlatformStyle *platformStyle;
QThread thread;
private Q_SLOTS:
/* set OK button state (enabled / disabled) */
void setOkButtonState(bool fState);
void on_resetButton_clicked();
void on_okButton_clicked();
void on_cancelButton_clicked();
void resetDialogValues();
/* import a private key */
bool importKey();
};
#endif // BITCOIN_QT_OPTIONSDIALOG_H

View File

@ -186,6 +186,13 @@ void WalletFrame::printPaperWallets()
walletView->printPaperWallets();
}
void WalletFrame::importPrivateKey()
{
WalletView *walletView = currentWalletView();
if (walletView)
walletView->importPrivateKey();
}
void WalletFrame::usedSendingAddresses()
{
WalletView *walletView = currentWalletView();

View File

@ -85,6 +85,8 @@ public Q_SLOTS:
/** Ask for passphrase to unlock wallet temporarily */
void unlockWallet();
/** import a private key */
void importPrivateKey();
void printPaperWallets();
/** Show used sending addresses */

View File

@ -9,6 +9,7 @@
#include "bitcoingui.h"
#include "clientmodel.h"
#include "guiutil.h"
#include "importkeysdialog.h"
#include "optionsmodel.h"
#include "overviewpage.h"
#include "platformstyle.h"
@ -65,6 +66,8 @@ WalletView::WalletView(const PlatformStyle *_platformStyle, QWidget *parent):
addWidget(receiveCoinsPage);
addWidget(sendCoinsPage);
importKeysDialog = new ImportKeysDialog(platformStyle);
// Clicking on a transaction on the overview pre-selects the transaction on the transaction history page
connect(overviewPage, SIGNAL(transactionClicked(QModelIndex)), transactionView, SLOT(focusTransaction(QModelIndex)));
connect(overviewPage, SIGNAL(outOfSyncWarningClicked()), this, SLOT(requestedSyncWarningInfo()));
@ -217,6 +220,11 @@ void WalletView::gotoVerifyMessageTab(QString addr)
signVerifyMessageDialog->setAddress_VM(addr);
}
void WalletView::gotoImportKeysDialog()
{
setCurrentWidget(importKeysDialog);
}
bool WalletView::handlePaymentRequest(const SendCoinsRecipient& recipient)
{
return sendCoinsPage->handlePaymentRequest(recipient);
@ -302,6 +310,16 @@ void WalletView::usedReceivingAddresses()
usedReceivingAddressesPage->activateWindow();
}
void WalletView::importPrivateKey()
{
if(!walletModel)
return;
importKeysDialog->show();
importKeysDialog->raise();
importKeysDialog->activateWindow();
}
void WalletView::showProgress(const QString &title, int nProgress)
{
if (nProgress == 0)

View File

@ -19,6 +19,7 @@ class SendCoinsRecipient;
class TransactionView;
class WalletModel;
class AddressBookPage;
class ImportKeysDialog;
QT_BEGIN_NAMESPACE
class QModelIndex;
@ -64,6 +65,7 @@ private:
SendCoinsDialog *sendCoinsPage;
AddressBookPage *usedSendingAddressesPage;
AddressBookPage *usedReceivingAddressesPage;
ImportKeysDialog *importKeysDialog;
TransactionView *transactionView;
@ -75,6 +77,8 @@ public Q_SLOTS:
void gotoOverviewPage();
/** Switch to history (transactions) page */
void gotoHistoryPage();
/** Switch to import keys dialog */
void gotoImportKeysDialog();
/** Switch to receive coins page */
void gotoReceiveCoinsPage();
/** Switch to send coins page */
@ -106,6 +110,9 @@ public Q_SLOTS:
/** Show used receiving addresses */
void usedReceivingAddresses();
/** Import a private key */
void importPrivateKey();
/** Re-emit encryption status signal */
void updateEncryptionStatus();