diff --git a/contrib/bitcoin-qt.pro b/contrib/bitcoin-qt.pro --- a/contrib/bitcoin-qt.pro +++ b/contrib/bitcoin-qt.pro @@ -4,6 +4,7 @@ ../src/qt/forms/askpassphrasedialog.ui \ ../src/qt/forms/coincontroldialog.ui \ ../src/qt/forms/editaddressdialog.ui \ + ../src/qt/forms/encryptwalletadvanceddialog.ui \ ../src/qt/forms/helpmessagedialog.ui \ ../src/qt/forms/intro.ui \ ../src/qt/forms/openuridialog.ui \ diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -99,6 +99,7 @@ qt/forms/askpassphrasedialog.ui \ qt/forms/coincontroldialog.ui \ qt/forms/editaddressdialog.ui \ + qt/forms/encryptwalletadvanceddialog.ui \ qt/forms/helpmessagedialog.ui \ qt/forms/intro.ui \ qt/forms/modaloverlay.ui \ @@ -127,6 +128,7 @@ qt/moc_coincontroltreewidget.cpp \ qt/moc_csvmodelwriter.cpp \ qt/moc_editaddressdialog.cpp \ + qt/moc_encryptwalletadvanceddialog.cpp \ qt/moc_guiutil.cpp \ qt/moc_intro.cpp \ qt/moc_macdockiconhandler.cpp \ @@ -194,6 +196,7 @@ qt/coincontroltreewidget.h \ qt/csvmodelwriter.h \ qt/editaddressdialog.h \ + qt/encryptwalletadvanceddialog.h \ qt/guiconstants.h \ qt/guiutil.h \ qt/intro.h \ @@ -324,6 +327,7 @@ qt/coincontroldialog.cpp \ qt/coincontroltreewidget.cpp \ qt/editaddressdialog.cpp \ + qt/encryptwalletadvanceddialog.cpp \ qt/openuridialog.cpp \ qt/overviewpage.cpp \ qt/paymentrequestplus.cpp \ diff --git a/src/qt/CMakeLists.txt b/src/qt/CMakeLists.txt --- a/src/qt/CMakeLists.txt +++ b/src/qt/CMakeLists.txt @@ -37,6 +37,7 @@ forms/askpassphrasedialog.ui forms/coincontroldialog.ui forms/editaddressdialog.ui + forms/encryptwalletadvanceddialog.ui forms/helpmessagedialog.ui forms/intro.ui forms/modaloverlay.ui @@ -132,6 +133,7 @@ coincontroldialog.cpp coincontroltreewidget.cpp editaddressdialog.cpp + encryptwalletadvanceddialog.cpp openuridialog.cpp overviewpage.cpp paymentrequestplus.cpp diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -112,6 +112,7 @@ QAction *optionsAction; QAction *toggleHideAction; QAction *encryptWalletAction; + QAction *encryptWalletAdvancedAction; QAction *backupWalletAction; QAction *changePassphraseAction; QAction *aboutQtAction; diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -91,11 +91,12 @@ usedReceivingAddressesAction(0), signMessageAction(0), verifyMessageAction(0), aboutAction(0), receiveCoinsAction(0), receiveCoinsMenuAction(0), optionsAction(0), toggleHideAction(0), - encryptWalletAction(0), backupWalletAction(0), changePassphraseAction(0), - aboutQtAction(0), openRPCConsoleAction(0), openAction(0), - showHelpMessageAction(0), trayIcon(0), trayIconMenu(0), notificator(0), - rpcConsole(0), helpMessageDialog(0), modalOverlay(0), prevBlocks(0), - spinnerFrame(0), platformStyle(_platformStyle), cfg(cfg) { + encryptWalletAction(0), encryptWalletAdvancedAction(0), + backupWalletAction(0), changePassphraseAction(0), aboutQtAction(0), + openRPCConsoleAction(0), openAction(0), showHelpMessageAction(0), + trayIcon(0), trayIconMenu(0), notificator(0), rpcConsole(0), + helpMessageDialog(0), modalOverlay(0), prevBlocks(0), spinnerFrame(0), + platformStyle(_platformStyle), cfg(cfg) { GUIUtil::restoreWindowGeometry("nWindow", QSize(850, 550), this); QString windowTitle = tr(PACKAGE_NAME) + " - "; @@ -368,6 +369,14 @@ encryptWalletAction->setStatusTip( tr("Encrypt the private keys that belong to your wallet")); encryptWalletAction->setCheckable(true); + + encryptWalletAdvancedAction = + new QAction(platformStyle->TextColorIcon(":/icons/lock_closed"), + tr("&Encrypt Wallet Advanced..."), this); + encryptWalletAdvancedAction->setStatusTip( + tr("Encrypt and Recover or Manually Generate your wallet's HD key")); + encryptWalletAdvancedAction->setCheckable(true); + backupWalletAction = new QAction(platformStyle->TextColorIcon(":/icons/filesave"), tr("&Backup Wallet..."), this); @@ -439,6 +448,8 @@ if (walletFrame) { connect(encryptWalletAction, SIGNAL(triggered(bool)), walletFrame, SLOT(encryptWallet(bool))); + connect(encryptWalletAdvancedAction, SIGNAL(triggered(bool)), + walletFrame, SLOT(encryptWalletAdvanced(bool))); connect(backupWalletAction, SIGNAL(triggered()), walletFrame, SLOT(backupWallet())); connect(changePassphraseAction, SIGNAL(triggered()), walletFrame, @@ -488,6 +499,7 @@ QMenu *settings = appMenuBar->addMenu(tr("&Settings")); if (walletFrame) { settings->addAction(encryptWalletAction); + settings->addAction(encryptWalletAdvancedAction); settings->addAction(changePassphraseAction); settings->addSeparator(); } @@ -613,6 +625,7 @@ receiveCoinsMenuAction->setEnabled(enabled); historyAction->setEnabled(enabled); encryptWalletAction->setEnabled(enabled); + encryptWalletAdvancedAction->setEnabled(enabled); backupWalletAction->setEnabled(enabled); changePassphraseAction->setEnabled(enabled); signMessageAction->setEnabled(enabled); @@ -1098,8 +1111,10 @@ case WalletModel::Unencrypted: labelWalletEncryptionIcon->hide(); encryptWalletAction->setChecked(false); + encryptWalletAdvancedAction->setChecked(false); changePassphraseAction->setEnabled(false); encryptWalletAction->setEnabled(true); + encryptWalletAdvancedAction->setEnabled(true); break; case WalletModel::Unlocked: labelWalletEncryptionIcon->show(); @@ -1109,9 +1124,12 @@ labelWalletEncryptionIcon->setToolTip( tr("Wallet is encrypted and currently unlocked")); encryptWalletAction->setChecked(true); + encryptWalletAdvancedAction->setChecked(true); changePassphraseAction->setEnabled(true); encryptWalletAction->setEnabled( false); // TODO: decrypt currently not supported + encryptWalletAdvancedAction->setEnabled( + false); // TODO: decrypt currently not supported break; case WalletModel::Locked: labelWalletEncryptionIcon->show(); @@ -1121,9 +1139,12 @@ labelWalletEncryptionIcon->setToolTip( tr("Wallet is encrypted and currently locked")); encryptWalletAction->setChecked(true); + encryptWalletAdvancedAction->setChecked(true); changePassphraseAction->setEnabled(true); encryptWalletAction->setEnabled( false); // TODO: decrypt currently not supported + encryptWalletAdvancedAction->setEnabled( + false); // TODO: decrypt currently not supported break; } } diff --git a/src/qt/encryptwalletadvanceddialog.h b/src/qt/encryptwalletadvanceddialog.h new file mode 100644 --- /dev/null +++ b/src/qt/encryptwalletadvanceddialog.h @@ -0,0 +1,50 @@ +// Copyright (c) 2011-2015 The Bitcoin 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_ENCRYPTWALLETADVANCEDDIALOG_H +#define BITCOIN_QT_ENCRYPTWALLETADVANCEDDIALOG_H + +#include + +#include "base58.h" +#include "support/allocators/secure.h" +#include "util.h" + +class WalletModel; + +namespace Ui { +class AskEncryptAdvancedDialog; +} + +/** Multifunctional dialog to ask for passphrases. Used for encryption, + * unlocking, and changing the passphrase. + */ +class EncryptWalletAdvancedDialog : public QDialog { + Q_OBJECT + +public: + explicit EncryptWalletAdvancedDialog(QWidget *parent); + ~EncryptWalletAdvancedDialog(); + + void accept() override; + + void setModel(WalletModel *model); + +private: + Ui::AskEncryptAdvancedDialog *ui; + WalletModel *model; + bool fCapsLock; + void DecodeDiceRolls(SecureString diceRolls, std::vector &retVec); + void SetKeyWithVector(std::vector vec, CKey &destKey); + +private Q_SLOTS: + void textChanged(); + void secureClearPassFields(); + +protected: + bool event(QEvent *event) override; + bool eventFilter(QObject *object, QEvent *event) override; +}; + +#endif // BITCOIN_QT_ENCRYPTWALLETADVANCEDDIALOG_H diff --git a/src/qt/encryptwalletadvanceddialog.cpp b/src/qt/encryptwalletadvanceddialog.cpp new file mode 100644 --- /dev/null +++ b/src/qt/encryptwalletadvanceddialog.cpp @@ -0,0 +1,402 @@ +// Copyright (c) 2011-2018 The Bitcoin ABC 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 "encryptwalletadvanceddialog.h" +#include "ui_encryptwalletadvanceddialog.h" + +#include "guiconstants.h" +#include "walletmodel.h" + +#include "support/allocators/secure.h" + +#include "base58.h" +#include "util.h" + +#include +#include +#include + +EncryptWalletAdvancedDialog::EncryptWalletAdvancedDialog(QWidget *parent) + : QDialog(parent), ui(new Ui::AskEncryptAdvancedDialog), model(0), + fCapsLock(false) { + + ui->setupUi(this); + + ui->passEdit1->setMinimumSize(ui->passEdit1->sizeHint()); + ui->passEdit2->setMinimumSize(ui->passEdit2->sizeHint()); + + ui->keyEdit1->setMaxLength(MAX_PASSPHRASE_SIZE); + ui->keyEdit2->setMaxLength(128); + ui->passEdit1->setMaxLength(MAX_PASSPHRASE_SIZE); + ui->passEdit2->setMaxLength(MAX_PASSPHRASE_SIZE); + + // Setup Caps Lock detection. + ui->keyEdit1->installEventFilter(this); + ui->keyEdit2->installEventFilter(this); + ui->passEdit1->installEventFilter(this); + ui->passEdit2->installEventFilter(this); + + textChanged(); + connect(ui->keyEdit1, SIGNAL(textChanged(QString)), this, + SLOT(textChanged())); + connect(ui->keyEdit2, SIGNAL(textChanged(QString)), this, + SLOT(textChanged())); + connect(ui->passEdit1, SIGNAL(textChanged(QString)), this, + SLOT(textChanged())); + connect(ui->passEdit2, SIGNAL(textChanged(QString)), this, + SLOT(textChanged())); +} + +EncryptWalletAdvancedDialog::~EncryptWalletAdvancedDialog() { + secureClearPassFields(); + delete ui; +} + +void EncryptWalletAdvancedDialog::setModel(WalletModel *_model) { + this->model = _model; +} + +void EncryptWalletAdvancedDialog::accept() { + + if (!model) return; + + SecureString newkey1, newkey2, newpass1, newpass2; + + newkey1.reserve(MAX_PASSPHRASE_SIZE); + newkey2.reserve(MAX_PASSPHRASE_SIZE); + newpass1.reserve(MAX_PASSPHRASE_SIZE); + newpass2.reserve(MAX_PASSPHRASE_SIZE); + + // TODO: get rid of this .c_str() by implementing + // SecureString::operator=(std::string) + // Alternately, find a way to make this input mlock()'d to begin with. + newkey1.assign(ui->keyEdit1->text().toStdString().c_str()); + newkey2.assign(ui->keyEdit2->text().toStdString().c_str()); + newpass1.assign(ui->passEdit1->text().toStdString().c_str()); + newpass2.assign(ui->passEdit2->text().toStdString().c_str()); + + // Validate the passphrases + if (newpass1 != newpass2 || newpass1.empty() || newpass2.empty()) { + QMessageBox::critical(this, tr("Wallet encryption failed"), + tr("The supplied passphrases do not match.")); + return; + } + + CKey newSeed; + + if (ui->keyRadioButton1->isChecked()) { + + // Option 1: Generate New Master Seed + + // Leave newSeed as invalid, causes new seed to be generated + // in CWallet::GenerateNewHDMasterKey + + } else if (ui->keyRadioButton2->isChecked()) { + + // Option 2: Recover from backup HD Master Key + + std::vector decodedVec; + bool successful = DecodeBase58(newkey1.c_str(), decodedVec); + if (!successful || decodedVec.size() != 32) { + QMessageBox::critical( + this, tr("Wallet encryption failed"), + tr("Invalid Master Seed entered. " + "Re-enter the Master Seed and try again. ")); + return; + } + + newSeed.Set(decodedVec.begin(), decodedVec.end(), true); + if (!newSeed.IsValid()) { + QMessageBox::critical( + this, tr("Wallet encryption failed"), + tr("Master Seed rejected as invalid. " + "Re-enter the Master Seed and try again. ")); + return; + } + + } else if (ui->keyRadioButton3->isChecked()) { + + // Option 3: Create New Master Seed by rolling dice for entropy + + if (newkey2.size() != 128) { + std::string numrolls = std::to_string(newkey2.size()); + QMessageBox::critical( + this, tr("Wallet encryption failed"), + tr("Invalid number of dice rolls entered. " + "Requires 128 dice rolls but only %1 provided. " + "Re-enter the correct number and try again. ") + .arg(tr(numrolls.c_str()))); + return; + } + + if (strspn(newkey2.c_str(), "1234") != 128) { + QMessageBox::critical( + this, tr("Wallet encryption failed"), + tr("Invalid input entered. Dice rolls much be " + "entered as characters '1' to '4'. " + "Re-enter dice rolls correctly and try again. ")); + return; + } + + std::vector decodedVec; + DecodeDiceRolls(newkey2, decodedVec); + SetKeyWithVector(decodedVec, newSeed); + + } else { + + // No option provided, error and retry + QMessageBox::critical(this, tr("No option selected"), + tr("No Master Seed operation selected. " + "Select an operation to perform and retry.")); + return; + } + + secureClearPassFields(); + + QMessageBox::StandardButton retval = QMessageBox::question( + this, tr("Confirm wallet encryption"), + tr("Warning: If you encrypt your wallet and lose your " + "passphrase, you will LOSE ALL OF YOUR BITCOINS!") + + "

" + tr("Are you sure you wish to encrypt your wallet?"), + QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel); + + if (retval == QMessageBox::Yes) { + if (newSeed.IsValid()) { + LogPrintf("EncryptWalletAdvancedDialog::accept - Sending user " + "inputed masterseed\n"); + } else { + LogPrintf("EncryptWalletAdvancedDialog::accept - Requesting auto " + "generated masterseed \n"); + } + if (model->setWalletEncrypted(true, newpass1, &newSeed)) { + + if (ui->keyRadioButton1->isChecked() || + ui->keyRadioButton3->isChecked()) { + // Messages for newly generated Master Seeds + std::string masterseed = + EncodeBase58(newSeed.begin(), newSeed.end()); + QMessageBox::warning( + this, tr("Wallet encrypted"), + "" + + tr("%1 will close now to finish the encryption " + "process. " + "Remember, encrypting your wallet does not fully " + "protect " + "your bitcoins from being stolen by malware " + "infecting your computer.") + .arg(tr(PACKAGE_NAME)) + + "

" + + tr("IMPORTANT: Any previous backups made of your " + "wallet file must " + "be replaced with the newly encrypted wallet file. " + "For security, " + "prior backups of the unencrypted wallet file " + "cannot be used " + "once you start using the newly encrypted wallet.") + + "

" + + tr("The new Master Seed for this wallet is: " + "\"%1\"") + .arg(tr(masterseed.c_str())) + + "

" + + tr("Save this Master Seed to recover your wallet in " + "the event the " + "wallet file is lost. ") + + "

" + + tr("WARNING:" + "
  1. Access to the Master Seed provides " + "COMPLETE ACCESS to ALL " + "the bitcoins in your wallet. You must SECURELY " + "STORE the Master Seed " + "to protect your wallet.
  2. " + "
  3. The Master Seed can only recover new " + "addresses. Addresses " + "already in your wallet cannot be recovered with " + "this Master Seed " + "and require the wallet file to use.
  4. " + "
  5. It is highly recommended to TEST the new " + "Master Seed above " + "by using it to recover an empty wallet file to " + "ensure the Master " + "Seed is stored correctly and works. Testing should " + "be done prior to " + "using this new wallet.
") + + "
"); + QApplication::quit(); + } else { + // Messages for newly recovered from backup Master Seeds + std::string masterseed = + EncodeBase58(newSeed.begin(), newSeed.end()); + QMessageBox::warning( + this, tr("Wallet encrypted"), + "" + + tr("%1 will close now to finish the encryption " + "process. " + "Remember, encrypting your wallet does not fully " + "protect " + "your bitcoins from being stolen by malware " + "infecting your computer.") + .arg(tr(PACKAGE_NAME)) + + "

" + + tr("IMPORTANT: Any previous backups made of your " + "wallet file must " + "be replaced with the newly encrypted wallet file. " + "For security, " + "prior backups of the unencrypted wallet file " + "cannot be used " + "once you start using the newly encrypted wallet.") + + "

" + + tr("The Master Seed for this wallet was restored to: " + "\"%1\"") + .arg(tr(masterseed.c_str())) + + "

" + + tr("To complete the recovery process it is necessary " + "to rebuild " + "the wallet's keypool and rescan prior blocks for " + "transactions " + "with the follwing steps: ") + + tr("
    " + "
  1. Restart bitcoin-qt and from the console run " + "\"keypoolrefill " + "<number>\" to expand the wallet's keypool. " + "This process uses " + "the Master Seed to re-create addresses.
  2. " + "
  3. Quit and restart bitcoin-qt again with the " + "command line parameter " + "\"-rescan=1\". This forces previous blocks to be " + "rescanned for " + "new addresses added to the wallet in the prior " + "step. After rescanning is " + "complete the recovered wallet file should have " + "located and now " + "contain transactions and bitcoins from the lost " + "wallet.
") + + "
" + + tr("NOTE: This process requires the keypool to be " + "refilled " + "to contain at least the same number of addresses " + "as the " + "prior wallet file contained. You might need to " + "interate " + "and refill using a larger number of addresses " + "to recover all bitcoins from the lost wallet. ") + + "
"); + QApplication::quit(); + } + } else { + QMessageBox::critical( + this, tr("Wallet encryption failed"), + tr("Wallet encryption failed due to an internal " + "error. Your wallet was not encrypted.")); + } + QDialog::accept(); // Success + } else { + QDialog::reject(); // Cancelled + } +} + +// Decode a sequence of 128 four-sided dice rolls into a 32-byte vector +void EncryptWalletAdvancedDialog::DecodeDiceRolls( + SecureString diceRolls, std::vector &retVec) { + + uint8_t currVal = 0, vecPos = 0, bytePos = 0; + std::string currRoll; + + retVec.resize(32); + + // Loop through each dice role, each roll provides 2 bits and 4 rolls + // provides 1 byte Once 1 byte is decoded, add it to the return vector + for (uint32_t i = 0; i < diceRolls.size(); i++) { + bytePos = i % 4; + currRoll = diceRolls[i]; + currVal += ((std::stoul(currRoll) - 1) << (bytePos * 2)); + if (bytePos == 3) { + retVec[vecPos++] = currVal; + currVal = 0; + } + } +} + +// Assign a key the value of a random 32-byte vector. Due to requirements +// some randomly generated vectors are not valid, if this happens increment +// the vector until a valid value is found +void EncryptWalletAdvancedDialog::SetKeyWithVector(std::vector vec, + CKey &destKey) { + + uint8_t pos = 0, nextVal; + + destKey.Set(vec.begin(), vec.end(), true); + while (!destKey.IsValid()) { + nextVal = vec[pos] == 255 ? 0 : vec[pos] + 1; + vec[pos++] = nextVal; + if (pos == 32) pos = 0; + destKey.Set(vec.begin(), vec.end(), true); + } +} + +void EncryptWalletAdvancedDialog::textChanged() { + // Validate input, set Ok button to enabled when acceptable + bool acceptable = false; + acceptable = + !ui->passEdit1->text().isEmpty() && !ui->passEdit2->text().isEmpty(); + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(acceptable); +} + +bool EncryptWalletAdvancedDialog::event(QEvent *event) { + // Detect Caps Lock key press. + if (event->type() == QEvent::KeyPress) { + QKeyEvent *ke = static_cast(event); + if (ke->key() == Qt::Key_CapsLock) { + fCapsLock = !fCapsLock; + } + if (fCapsLock) { + ui->capsLabel->setText(tr("Warning: The Caps Lock key is on!")); + } else { + ui->capsLabel->clear(); + } + } + return QWidget::event(event); +} + +bool EncryptWalletAdvancedDialog::eventFilter(QObject *object, QEvent *event) { + /* Detect Caps Lock. + * There is no good OS-independent way to check a key state in Qt, but we + * can detect Caps Lock by checking for the following condition: + * Shift key is down and the result is a lower case character, or + * Shift key is not down and the result is an upper case character. + */ + if (event->type() == QEvent::KeyPress) { + QKeyEvent *ke = static_cast(event); + QString str = ke->text(); + if (str.length() != 0) { + const QChar *psz = str.unicode(); + bool fShift = (ke->modifiers() & Qt::ShiftModifier) != 0; + if ((fShift && *psz >= 'a' && *psz <= 'z') || + (!fShift && *psz >= 'A' && *psz <= 'Z')) { + fCapsLock = true; + ui->capsLabel->setText(tr("Warning: The Caps Lock key is on!")); + } else if (psz->isLetter()) { + fCapsLock = false; + ui->capsLabel->clear(); + } + } + } + return QDialog::eventFilter(object, event); +} + +static void SecureClearQLineAdvancedEdit(QLineEdit *edit) { + // Attempt to overwrite text so that they do not linger around in memory + edit->setText(QString(" ").repeated(edit->text().size())); + edit->clear(); +} + +void EncryptWalletAdvancedDialog::secureClearPassFields() { + SecureClearQLineAdvancedEdit(ui->keyEdit1); + SecureClearQLineAdvancedEdit(ui->keyEdit2); + SecureClearQLineAdvancedEdit(ui->passEdit1); + SecureClearQLineAdvancedEdit(ui->passEdit2); +} diff --git a/src/qt/forms/encryptwalletadvanceddialog.ui b/src/qt/forms/encryptwalletadvanceddialog.ui new file mode 100644 --- /dev/null +++ b/src/qt/forms/encryptwalletadvanceddialog.ui @@ -0,0 +1,366 @@ + + + AskEncryptAdvancedDialog + + + + 0 + 0 + 615 + 790 + + + + + 0 + 0 + + + + + 550 + 0 + + + + Encrypt Wallet Advanced Dialog + + + + QLayout::SetMinimumSize + + + + + This option is intended for Advanced users only. When wallets are encrypted a new Hierarchical Deterministic (HD) Master Seed is created for the wallet per BIP32 specifications. This dialog offers multiple options for how the new HD Master Seed is created, including: 1) Generating a new seed, 2) Manually providing a previously used seed to recover a wallet from backup, and 3) Manually creating a new seed using dice to ensure proper entropy. + + + Qt::RichText + + + true + + + + + + + QLayout::SetMaximumSize + + + QFormLayout::AllNonFixedFieldsGrow + + + + + + 75 + true + + + + HD Key Options + + + + + + + Qt::Horizontal + + + + 40 + 5 + + + + + + + + + 0 + 32 + + + + Qt::RightToLeft + + + false + + + Generate New Master Seed + + + + + + + Generate new HD Master Seed and encrypt wallet. This is identical to the standard Encrypt Wallet option. + + + true + + + + + + + + 0 + 10 + + + + Qt::Horizontal + + + + + + + + 0 + 32 + + + + Qt::RightToLeft + + + Recover Master Seed + + + + + + + Recover a previous wallet by restoring its Master Seed and encrypt wallet. + + + true + + + + + + + + + + + 0 + 10 + + + + Qt::Horizontal + + + + + + + + 0 + 49 + + + + Qt::RightToLeft + + + Create New Master Seed + + + + + + + + 0 + 0 + + + + Manually create new Master Seed and encrypt wallet. Performs the same steps as Generate New but uses the inputted seed instead generating a new seed randomly. <br><br><b>Input 128 six-sided dice rolls</b> with one digit entered for each roll. Discard rolls for sides '5' and '6' and only enter rolls for sides '1', '2','3' and '4'. Input each valid roll as a single character '1' to '4'. <b>Warning: You must make 128 successful dice rolls</b> to use this option, randomly typing digits provides very low entropy and will result in an insecure wallet. + + + true + + + + + + + Qt::Horizontal + + + + 80 + 5 + + + + + + + + New passphrase + + + + + + + QLineEdit::Password + + + + + + + Repeat new passphrase + + + + + + + QLineEdit::Password + + + + + + + + 75 + true + + + + + + + Qt::AlignCenter + + + + + + + + 0 + 10 + + + + Qt::Horizontal + + + + + + + + 150 + 10 + + + + Qt::Horizontal + + + + + + + + + + Enter 128 six sided Dice Rolls only entering rolls 1 to 4 + + + + + + + Enter Master Seed from Backup + + + + + + + + 75 + true + + + + Encryption Passphrase + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + keyRadioButton1 + keyRadioButton2 + keyRadioButton3 + keyEdit1 + keyEdit2 + passEdit1 + passEdit2 + + + + + buttonBox + accepted() + AskEncryptAdvancedDialog + accept() + + + 26 + 437 + + + 20 + 20 + + + + + buttonBox + rejected() + AskEncryptAdvancedDialog + reject() + + + 26 + 437 + + + 20 + 20 + + + + + diff --git a/src/qt/walletframe.h b/src/qt/walletframe.h --- a/src/qt/walletframe.h +++ b/src/qt/walletframe.h @@ -81,6 +81,8 @@ /** Encrypt the wallet */ void encryptWallet(bool status); + /** Encrypt the wallet Advanced */ + void encryptWalletAdvanced(bool status); /** Backup the wallet */ void backupWallet(); /** Change encrypted wallet passphrase */ diff --git a/src/qt/walletframe.cpp b/src/qt/walletframe.cpp --- a/src/qt/walletframe.cpp +++ b/src/qt/walletframe.cpp @@ -136,6 +136,11 @@ if (walletView) walletView->encryptWallet(status); } +void WalletFrame::encryptWalletAdvanced(bool status) { + WalletView *walletView = currentWalletView(); + if (walletView) walletView->encryptWalletAdvanced(status); +} + void WalletFrame::backupWallet() { WalletView *walletView = currentWalletView(); if (walletView) walletView->backupWallet(); diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -170,7 +170,8 @@ SendCoinsReturn sendCoins(WalletModelTransaction &transaction); // Wallet encryption - bool setWalletEncrypted(bool encrypted, const SecureString &passphrase); + bool setWalletEncrypted(bool encrypted, const SecureString &passphrase, + CKey *newSeed = nullptr); // Passphrase only needed when unlocking bool setWalletLocked(bool locked, const SecureString &passPhrase = SecureString()); diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -384,10 +384,11 @@ } bool WalletModel::setWalletEncrypted(bool encrypted, - const SecureString &passphrase) { + const SecureString &passphrase, + CKey *newSeed) { if (encrypted) { // Encrypt - return wallet->EncryptWallet(passphrase); + return wallet->EncryptWallet(passphrase, newSeed); } else { // Decrypt -- TODO; not supported yet return false; diff --git a/src/qt/walletview.h b/src/qt/walletview.h --- a/src/qt/walletview.h +++ b/src/qt/walletview.h @@ -99,6 +99,8 @@ int /*end*/); /** Encrypt the wallet */ void encryptWallet(bool status); + /** Encrypt the wallet Advanced options */ + void encryptWalletAdvanced(bool status); /** Backup the wallet */ void backupWallet(); /** Change encrypted wallet passphrase */ diff --git a/src/qt/walletview.cpp b/src/qt/walletview.cpp --- a/src/qt/walletview.cpp +++ b/src/qt/walletview.cpp @@ -8,6 +8,7 @@ #include "askpassphrasedialog.h" #include "bitcoingui.h" #include "clientmodel.h" +#include "encryptwalletadvanceddialog.h" #include "guiutil.h" #include "optionsmodel.h" #include "overviewpage.h" @@ -109,10 +110,12 @@ SLOT(setEncryptionStatus(int))); // Pass through transaction notifications - connect(this, SIGNAL(incomingTransaction(QString, int, Amount, QString, - QString, QString)), - gui, SLOT(incomingTransaction(QString, int, Amount, QString, - QString, QString))); + connect(this, + SIGNAL(incomingTransaction(QString, int, Amount, QString, + QString, QString)), + gui, + SLOT(incomingTransaction(QString, int, Amount, QString, QString, + QString))); // Connect HD enabled state signal connect(this, SIGNAL(hdEnabledStatusChanged(int)), gui, @@ -258,6 +261,15 @@ updateEncryptionStatus(); } +void WalletView::encryptWalletAdvanced(bool status) { + if (!walletModel) return; + EncryptWalletAdvancedDialog dlg(this); + dlg.setModel(walletModel); + dlg.exec(); + + updateEncryptionStatus(); +} + void WalletView::backupWallet() { QString filename = GUIUtil::getSaveFileName(this, tr("Backup Wallet"), QString(), diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -660,9 +660,8 @@ } CKey vchSecret; if (!pwallet->GetKey(*keyID, vchSecret)) { - throw JSONRPCError(RPC_WALLET_ERROR, - "Private key for address " + strAddress + - " is not known"); + throw JSONRPCError(RPC_WALLET_ERROR, "Private key for address " + + strAddress + " is not known"); } return CBitcoinSecret(vchSecret).ToString(); } @@ -748,6 +747,11 @@ if (!masterKeyID.IsNull()) { CKey key; if (pwallet->GetKey(masterKeyID, key)) { + + std::string masterseed = EncodeBase58(key.begin(), key.end()); + file << "# private masterseed (save to recover HD Wallet): " + << masterseed << "\n\n"; + CExtKey masterKey; masterKey.SetMaster(key.begin(), key.size()); diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -858,7 +858,8 @@ bool Unlock(const SecureString &strWalletPassphrase); bool ChangeWalletPassphrase(const SecureString &strOldWalletPassphrase, const SecureString &strNewWalletPassphrase); - bool EncryptWallet(const SecureString &strWalletPassphrase); + bool EncryptWallet(const SecureString &strWalletPassphrase, + CKey *newSeed = nullptr); void GetKeyBirthTimes(std::map &mapKeyBirth) const; unsigned int ComputeTimeSmart(const CWalletTx &wtx) const; @@ -888,7 +889,8 @@ const CBlockIndex *pIndex, int posInBlock, bool fUpdate); CBlockIndex *ScanForWalletTransactions(CBlockIndex *pindexStart, - bool fUpdate = false); + bool fUpdate = false, + int64_t forceAll = 0); void ReacceptWalletTransactions(); void ResendWalletTransactions(int64_t nBestBlockTime, CConnman *connman) override; @@ -1137,7 +1139,7 @@ bool IsHDEnabled(); /* Generates a new HD master key (will not be activated) */ - CPubKey GenerateNewHDMasterKey(); + CPubKey GenerateNewHDMasterKey(CKey *newSeed = nullptr); /** * Set the current HD master key (will reset the chain child index counters) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -5,6 +5,8 @@ #include "wallet/wallet.h" +#include "base58.h" +#include "cashaddr.h" #include "chain.h" #include "checkpoints.h" #include "config.h" @@ -209,17 +211,15 @@ // child-index-range // example: 1 | BIP32_HARDENED_KEY_LIMIT == 0x80000001 == 2147483649 if (internal) { - chainChildKey.Derive(childKey, - hdChain.nInternalChainCounter | - BIP32_HARDENED_KEY_LIMIT); + chainChildKey.Derive(childKey, hdChain.nInternalChainCounter | + BIP32_HARDENED_KEY_LIMIT); metadata.hdKeypath = "m/0'/1'/" + std::to_string(hdChain.nInternalChainCounter) + "'"; hdChain.nInternalChainCounter++; } else { - chainChildKey.Derive(childKey, - hdChain.nExternalChainCounter | - BIP32_HARDENED_KEY_LIMIT); + chainChildKey.Derive(childKey, hdChain.nExternalChainCounter | + BIP32_HARDENED_KEY_LIMIT); metadata.hdKeypath = "m/0'/0'/" + std::to_string(hdChain.nExternalChainCounter) + "'"; @@ -731,7 +731,8 @@ } } -bool CWallet::EncryptWallet(const SecureString &strWalletPassphrase) { +bool CWallet::EncryptWallet(const SecureString &strWalletPassphrase, + CKey *newSeed) { if (IsCrypted()) { return false; } @@ -819,7 +820,7 @@ // If we are using HD, replace the HD master key (seed) with a new one. if (IsHDEnabled()) { CKey key; - CPubKey masterPubKey = GenerateNewHDMasterKey(); + CPubKey masterPubKey = GenerateNewHDMasterKey(newSeed); // preserve the old chains version to not break backward // compatibility CHDChain oldChain = GetHDChain(); @@ -1532,9 +1533,25 @@ return nChange; } -CPubKey CWallet::GenerateNewHDMasterKey() { +CPubKey CWallet::GenerateNewHDMasterKey(CKey *newSeed) { CKey key; - key.MakeNewKey(true); + + if (newSeed == nullptr) { + // Standard path, no CKey was provided so generate a new one + key.MakeNewKey(true); + } else { + if (newSeed->IsValid()) { + // A CKey was provided and is valid, use it + LogPrintf("CWallet::GenerateNewHDMasterKey - Using given key\n"); + key = *newSeed; + } else { + // A CKey was passed down but empty + // Generate a new key and send back to the dialog box for backup + LogPrintf("CWallet::GenerateNewHDMasterKey - Generating new key\n"); + key.MakeNewKey(true); + *newSeed = key; + } + } int64_t nCreationTime = GetTime(); CKeyMetadata metadata(nCreationTime); @@ -1707,7 +1724,8 @@ * the range doesn't include chainActive.Tip(). */ CBlockIndex *CWallet::ScanForWalletTransactions(CBlockIndex *pindexStart, - bool fUpdate) { + bool fUpdate, + int64_t forceAll) { LOCK2(cs_main, cs_wallet); int64_t nNow = GetTime(); @@ -1717,9 +1735,12 @@ // No need to read and scan block, if block was created before our wallet // birthday (as adjusted for block time variability) - while (pindex && nTimeFirstKey && - (pindex->GetBlockTime() < (nTimeFirstKey - 7200))) { - pindex = chainActive.Next(pindex); + // Skip this adjustment if user requested a complete scan with -rescan=1 + if (forceAll == 0) { + while (pindex && nTimeFirstKey && + (pindex->GetBlockTime() < (nTimeFirstKey - 7200))) { + pindex = chainActive.Next(pindex); + } } // Show rescan progress in GUI as dialog or on splashscreen, if -rescan on @@ -4036,8 +4057,9 @@ _("Fee (in %s/kB) to add to transactions you send (default: %s)"), CURRENCY_UNIT, FormatMoney(payTxFee.GetFeePerK()))); strUsage += HelpMessageOpt( - "-rescan", - _("Rescan the block chain for missing wallet transactions on startup")); + "-rescan", _("Rescan the block chain for missing wallet transactions " + "on startup, use " + "-rescan=1 to force rescanning from the genesis block")); strUsage += HelpMessageOpt( "-salvagewallet", _("Attempt to recover private keys from a corrupt wallet on startup")); @@ -4082,8 +4104,9 @@ "-zapwallettxes=", _("Delete all wallet transactions and only recover those parts of the " "blockchain through -rescan on startup") + - " " + _("(1 = keep tx meta data e.g. account owner and payment " - "request information, 2 = drop tx meta data)")); + " " + + _("(1 = keep tx meta data e.g. account owner and payment " + "request information, 2 = drop tx meta data)")); if (showDebug) { strUsage += HelpMessageGroup(_("Wallet debugging/testing options:")); @@ -4273,7 +4296,8 @@ chainActive.Height() - pindexRescan->nHeight, pindexRescan->nHeight); nStart = GetTimeMillis(); - walletInstance->ScanForWalletTransactions(pindexRescan, true); + walletInstance->ScanForWalletTransactions(pindexRescan, true, + gArgs.GetArg("-rescan", 0)); LogPrintf(" rescan %15dms\n", GetTimeMillis() - nStart); walletInstance->SetBestChain(chainActive.GetLocator()); walletInstance->dbw->IncrementUpdateCounter();