diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index ef52210..1e69e33 100755 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1132,21 +1132,22 @@ void MainWindow::doImport(QList* keys) { return; } + DEBUG(" keys.size= " << keys->size() ); + if (keys->isEmpty()) { delete keys; - ui->statusBar->showMessage(tr("Private key import rescan finished")); return; } - // Pop the first key - QString key = keys->first(); - keys->pop_front(); - bool rescan = keys->isEmpty(); + // Get the first key + QString key = keys->takeFirst(); - if (key.startsWith("SK") || - key.startsWith("secret")) { // Z key + bool rescan = false; + if (Settings::getInstance()->isValidSaplingPrivateKey(key) ) { + DEBUG("importing zaddr privkey with rescan=" << rescan); rpc->importZPrivKey(key, rescan, [=] (auto) { this->doImport(keys); }); } else { + DEBUG("importing taddr privkey with rescan=" << rescan); rpc->importTPrivKey(key, rescan, [=] (auto) { this->doImport(keys); }); } } @@ -1247,7 +1248,6 @@ void MainWindow::payHushURI(QString uri, QString myAddr) { } } - void MainWindow::importPrivKey() { QDialog d(this); Ui_PrivKey pui; @@ -1259,20 +1259,27 @@ void MainWindow::importPrivKey() { tr("Please paste your private keys here, one per line") % ".\n" % tr("The keys will be imported into your connected Hush node")); + // if rescan is not checked, disable the rescan height input + QObject::connect(pui.chkrescan, &QCheckBox::stateChanged, [=](auto checked) { + pui.rescanfrom->setEnabled(checked); + }); + if (d.exec() == QDialog::Accepted && !pui.privKeyTxt->toPlainText().trimmed().isEmpty()) { auto rawkeys = pui.privKeyTxt->toPlainText().trimmed().split("\n"); QList keysTmp; - // Filter out all the empty keys. + // Filter out all the empty keys and comment lines std::copy_if(rawkeys.begin(), rawkeys.end(), std::back_inserter(keysTmp), [=] (auto key) { return !key.startsWith("#") && !key.trimmed().isEmpty(); }); auto keys = new QList(); + // ignore anything after the first space of a line, such as if you paste a line from z_exportwallet output std::transform(keysTmp.begin(), keysTmp.end(), std::back_inserter(*keys), [=](auto key) { return key.trimmed().split(" ")[0]; }); + // Special case. // Sometimes, when importing from a paperwallet or such, the key is split by newlines, and might have // been pasted like that. So check to see if the whole thing is one big private key @@ -1283,13 +1290,52 @@ void MainWindow::importPrivKey() { delete multiline; } - // Start the import. The function takes ownership of keys - QTimer::singleShot(1, [=]() {doImport(keys);}); + // Finally, validate all keys, removing any which are invalid + auto keysValidated = new QList(); + auto settings = Settings::getInstance(); + std::copy_if(keys->begin(), keys->end(), std::back_inserter(*keysValidated), [=] (auto key) { + bool isValid = settings->isValidSaplingPrivateKey(key) || settings->isValidTransparentPrivateKey(key); + if (!isValid) { DEBUG("privkey " << key << " is not valid"); } + return isValid; + }); + DEBUG("found " << keysValidated->size() << " valid privkeys"); + + + bool rescan = pui.chkrescan->isChecked(); + + // avoid giving invalid data to RPCs and a rescan if there were no valid privkeys + if(keysValidated->size() == 0) { + QMessageBox::information(this, "No valid keys", + tr("No valid private keys were found, please make sure you copy and pasted correctly"), + QMessageBox::Ok); + return; + } + + // Start the import. The function takes ownership of keysValidated + QTimer::singleShot(1, [=]() { + // we import all keys without rescanning and then finally decide if we will rescan once + doImport(keysValidated); + + if (rescan) { + //TODO: verify rescanfrom is a valid integer + rpc->rescan(pui.rescanfrom->text().trimmed().toInt() , [=] (QJsonValue response){ + qDebug() << __func__ << ":rescanning from height " << pui.rescanfrom->text().toInt() << " finished" << response; + ui->statusBar->showMessage(tr("Rescanning finished"), 5000); + }); + } + + }); // Show the dialog that keys will be imported. - QMessageBox::information(this, - "Imported", tr("The keys were imported! It may take several minutes to rescan the blockchain. Until then, functionality may be limited"), + if(rescan) { + QMessageBox::information(this, "Imported", + tr("The keys were imported! It may take several hours to rescan the blockchain. Until then, functionality may be limited"), QMessageBox::Ok); + } else { + QMessageBox::information(this, "Imported", + tr("The keys were imported! You chose to not rescan, so funds in that address will not show up in your wallet yet."), + QMessageBox::Ok); + } } } diff --git a/src/privkey.ui b/src/privkey.ui index 57c3d8f..26596de 100644 --- a/src/privkey.ui +++ b/src/privkey.ui @@ -14,7 +14,7 @@ Private Keys - + @@ -22,6 +22,38 @@ + + + Rescan + + + true + + + true + + + + + + + Rescan Height + + + + + + + 1 + + + + 50 + + + + + Qt::Horizontal diff --git a/src/rpc.cpp b/src/rpc.cpp index 335d113..e55bcbc 100644 --- a/src/rpc.cpp +++ b/src/rpc.cpp @@ -374,7 +374,7 @@ void RPC::importTPrivKey(QString privkey, bool rescan, const std::function 52) { + DEBUG("privkey invalid, too long"); + return false; + } + + if (pk.length() < 51) { + DEBUG("privkey invalid, too short"); + return false; + } + + // TODO: can a taddr privkey start with anything else? + if (pk.startsWith("U") || pk.startsWith("5") || pk.startsWith("L") || pk.startsWith("K") || pk.startsWith("7")) { + // verify only contains base58 characters + QRegExp exp("^[U5LK7][123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{51,52}$", Qt::CaseSensitive); + bool valid = exp.exactMatch(pk); + if(!valid) { DEBUG("privkey invalid, not base58"); } + return valid; + } else { + DEBUG("privkey invalid, wrong prefix"); + return false; + } +} + bool Settings::isValidSaplingPrivateKey(QString pk) { if (isTestnet()) { QRegExp zspkey("^secret-extended-key-test[0-9a-z]{278}$", Qt::CaseInsensitive); diff --git a/src/settings.h b/src/settings.h index a3b1a74..7e752bd 100644 --- a/src/settings.h +++ b/src/settings.h @@ -47,9 +47,9 @@ public: void setTestnet(bool isTestnet); bool isSaplingAddress(QString addr); - bool isSproutAddress(QString addr); bool isValidSaplingPrivateKey(QString pk); + bool isValidTransparentPrivateKey(QString pk); bool isSyncing(); void setSyncing(bool syncing);