From 17c44521bef734ff14af9912a3edb7a5ba5460cc Mon Sep 17 00:00:00 2001 From: Duke Date: Wed, 27 Dec 2023 11:59:05 -0500 Subject: [PATCH 01/17] Use takeFirst() instead of first() and pop_front() --- src/mainwindow.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index ef52210..3caf91c 100755 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1138,9 +1138,8 @@ void MainWindow::doImport(QList* keys) { return; } - // Pop the first key - QString key = keys->first(); - keys->pop_front(); + // Get the first key + QString key = keys->takeFirst(); bool rescan = keys->isEmpty(); if (key.startsWith("SK") || From 4ef65d1a3ffb3dc4c22ee0e79a7bcf5b7a1c4092 Mon Sep 17 00:00:00 2001 From: onryo Date: Wed, 27 Dec 2023 21:17:06 +0100 Subject: [PATCH 02/17] add chkrescan --- src/privkey.ui | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/privkey.ui b/src/privkey.ui index 57c3d8f..8d94676 100644 --- a/src/privkey.ui +++ b/src/privkey.ui @@ -21,6 +21,16 @@ + + + + Rescan + + + true + + + From 868bf831553d11898a7220d0fae908f2b2f68535 Mon Sep 17 00:00:00 2001 From: onryo Date: Wed, 27 Dec 2023 22:35:50 +0100 Subject: [PATCH 03/17] add rescanfrom --- src/mainwindow.cpp | 1 - src/privkey.ui | 7 +++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 3caf91c..d68584e 100755 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1246,7 +1246,6 @@ void MainWindow::payHushURI(QString uri, QString myAddr) { } } - void MainWindow::importPrivKey() { QDialog d(this); Ui_PrivKey pui; diff --git a/src/privkey.ui b/src/privkey.ui index 8d94676..83c3afa 100644 --- a/src/privkey.ui +++ b/src/privkey.ui @@ -31,6 +31,13 @@ + + + + 1600000 + + + From 227dcaddbba56ee9dc332fac2359dbf867d5f184 Mon Sep 17 00:00:00 2001 From: Duke Date: Thu, 28 Dec 2023 04:38:05 -0500 Subject: [PATCH 04/17] Slightly better zaddr privkey validation --- src/mainwindow.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index d68584e..7056c00 100755 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1142,8 +1142,7 @@ void MainWindow::doImport(QList* keys) { QString key = keys->takeFirst(); bool rescan = keys->isEmpty(); - if (key.startsWith("SK") || - key.startsWith("secret")) { // Z key + if (key.startsWith("SK") || Settings::getInstance()->isValidSaplingPrivateKey(key) ) { rpc->importZPrivKey(key, rescan, [=] (auto) { this->doImport(keys); }); } else { rpc->importTPrivKey(key, rescan, [=] (auto) { this->doImport(keys); }); From ec6327a6b0a93e6897e7c38f72355b5e09297437 Mon Sep 17 00:00:00 2001 From: Duke Date: Thu, 28 Dec 2023 05:16:21 -0500 Subject: [PATCH 05/17] Add function to validate taddr privkeys --- src/settings.cpp | 21 +++++++++++++++++++++ src/settings.h | 1 + 2 files changed, 22 insertions(+) diff --git a/src/settings.cpp b/src/settings.cpp index f10d16a..e6b1cd6 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -435,6 +435,27 @@ double Settings::getMinerFee() { return 0.0001; } +bool Settings::isValidTransparentPrivateKey(QString pk) { + if (pk.length() > 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")) { + // TODO: verify only contains base58 characters + return true; + } 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..7a80879 100644 --- a/src/settings.h +++ b/src/settings.h @@ -50,6 +50,7 @@ public: bool isSproutAddress(QString addr); bool isValidSaplingPrivateKey(QString pk); + bool isValidTransparentPrivateKey(QString pk); bool isSyncing(); void setSyncing(bool syncing); From 219e41ccb5e4e96a937cac14f2f925f381ae4bcf Mon Sep 17 00:00:00 2001 From: onryo Date: Thu, 28 Dec 2023 12:36:56 +0100 Subject: [PATCH 06/17] dialog text --- src/mainwindow.cpp | 3 +-- src/privkey.ui | 5 ++++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 7056c00..d1406b0 100755 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1284,8 +1284,7 @@ void MainWindow::importPrivKey() { QTimer::singleShot(1, [=]() {doImport(keys);}); // 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"), + 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); } } diff --git a/src/privkey.ui b/src/privkey.ui index 83c3afa..f7f49e0 100644 --- a/src/privkey.ui +++ b/src/privkey.ui @@ -27,8 +27,11 @@ Rescan - true + false + + false + From f689547a36581b3a3c83daa1eed4611597a1240d Mon Sep 17 00:00:00 2001 From: Duke Date: Thu, 28 Dec 2023 14:57:45 -0500 Subject: [PATCH 07/17] Add basic rescan options to import popup --- src/privkey.ui | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/privkey.ui b/src/privkey.ui index f7f49e0..c85ca2d 100644 --- a/src/privkey.ui +++ b/src/privkey.ui @@ -21,27 +21,34 @@ - + Rescan - false + true - false + true - + + + + Rescan Height + + + + - 1600000 + 1 - + Qt::Horizontal From 8bb0b3df5cd24b0dd15ab1834b394de7c7ae14ab Mon Sep 17 00:00:00 2001 From: Duke Date: Thu, 28 Dec 2023 15:08:07 -0500 Subject: [PATCH 08/17] Make import popup look nicer --- src/privkey.ui | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/privkey.ui b/src/privkey.ui index c85ca2d..26596de 100644 --- a/src/privkey.ui +++ b/src/privkey.ui @@ -14,7 +14,7 @@ Private Keys - + @@ -46,6 +46,11 @@ 1 + + + 50 + + From 7db95fbf49f9e2340fb1bff706d9ac68a70a47e1 Mon Sep 17 00:00:00 2001 From: Duke Date: Thu, 28 Dec 2023 15:57:58 -0500 Subject: [PATCH 09/17] Support disabling rescan or a custom rescan height when importing privkeys --- src/mainwindow.cpp | 39 +++++++++++++++++++++++++++++++++++---- src/rpc.cpp | 4 ++-- 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index d1406b0..e37e092 100755 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1132,19 +1132,29 @@ 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")); + /* + if (rescan) { + ui->statusBar->showMessage(tr("Private key import with rescan finished")); + } else { + ui->statusBar->showMessage(tr("Private key import finished")); + } + */ return; } // Get the first key QString key = keys->takeFirst(); - bool rescan = keys->isEmpty(); + bool rescan = false; if (key.startsWith("SK") || 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); }); } } @@ -1280,12 +1290,33 @@ void MainWindow::importPrivKey() { delete multiline; } + //TODO: if rescan is not checked, disable the rescan height input + bool rescan = pui.chkrescan->isChecked(); + // Start the import. The function takes ownership of keys - QTimer::singleShot(1, [=]() {doImport(keys);}); + QTimer::singleShot(1, [=]() { + // we import all keys without rescanning and then finally decide if we will rescan once + doImport(keys); + + if (rescan) { + rpc->rescan(pui.rescanfrom->text().toInt() , [=] (QJsonValue response){ + qDebug() << __func__ << ":rescanning from height " << pui.rescanfrom << " 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 hours 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/rpc.cpp b/src/rpc.cpp index 6181e54..9ecd34e 100644 --- a/src/rpc.cpp +++ b/src/rpc.cpp @@ -374,7 +374,7 @@ void RPC::importTPrivKey(QString privkey, bool rescan, const std::function Date: Thu, 28 Dec 2023 15:58:28 -0500 Subject: [PATCH 10/17] We do not support sprout privkeys that begin with SK --- src/mainwindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index e37e092..0b2e69e 100755 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1150,7 +1150,7 @@ void MainWindow::doImport(QList* keys) { QString key = keys->takeFirst(); bool rescan = false; - if (key.startsWith("SK") || Settings::getInstance()->isValidSaplingPrivateKey(key) ) { + if (Settings::getInstance()->isValidSaplingPrivateKey(key) ) { DEBUG("importing zaddr privkey with rescan=" << rescan); rpc->importZPrivKey(key, rescan, [=] (auto) { this->doImport(keys); }); } else { From 2000377783b81176df245df184f61f9d09fabb6c Mon Sep 17 00:00:00 2001 From: Duke Date: Thu, 28 Dec 2023 16:49:50 -0500 Subject: [PATCH 11/17] Too bad this code does not seem to work --- src/mainwindow.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 0b2e69e..b037569 100755 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1290,9 +1290,18 @@ void MainWindow::importPrivKey() { delete multiline; } - //TODO: if rescan is not checked, disable the rescan height input bool rescan = pui.chkrescan->isChecked(); + //TODO: if rescan is not checked, disable the rescan height input + // Why don't these work? + if (rescan) { + //pui.rescanfrom->setEnabled(true); + pui.rescanfrom->setReadOnly(false); + } else { + //pui.rescanfrom->setDisabled(true); + pui.rescanfrom->setReadOnly(true); + } + // Start the import. The function takes ownership of keys QTimer::singleShot(1, [=]() { // we import all keys without rescanning and then finally decide if we will rescan once @@ -1300,7 +1309,7 @@ void MainWindow::importPrivKey() { if (rescan) { rpc->rescan(pui.rescanfrom->text().toInt() , [=] (QJsonValue response){ - qDebug() << __func__ << ":rescanning from height " << pui.rescanfrom << " finished" << response; + qDebug() << __func__ << ":rescanning from height " << pui.rescanfrom->text().toInt() << " finished" << response; ui->statusBar->showMessage(tr("Rescanning finished"), 5000); }); } From 68bd0db44c354dd4e527c3d34e115e7c0d8859a8 Mon Sep 17 00:00:00 2001 From: Duke Date: Thu, 28 Dec 2023 19:18:33 -0500 Subject: [PATCH 12/17] Only import valid zaddr+taddr privkeys and only rescan if there was at least 1 valid privkey --- src/mainwindow.cpp | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index b037569..a4a3d28 100755 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1270,16 +1270,18 @@ void MainWindow::importPrivKey() { 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(); + // split multiple privkeys seperated by spaces on a single line 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 @@ -1290,6 +1292,16 @@ void MainWindow::importPrivKey() { delete multiline; } + // 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(); //TODO: if rescan is not checked, disable the rescan height input @@ -1302,12 +1314,21 @@ void MainWindow::importPrivKey() { pui.rescanfrom->setReadOnly(true); } - // Start the import. The function takes ownership of keys + // 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(keys); + doImport(keysValidated); if (rescan) { + //TODO: verify rescanfrom is a valid integer and trim whitespace rpc->rescan(pui.rescanfrom->text().toInt() , [=] (QJsonValue response){ qDebug() << __func__ << ":rescanning from height " << pui.rescanfrom->text().toInt() << " finished" << response; ui->statusBar->showMessage(tr("Rescanning finished"), 5000); From 8b41a3b0f4f6a91e50c332f75f08dabb308fefb3 Mon Sep 17 00:00:00 2001 From: Duke Date: Fri, 29 Dec 2023 17:22:11 -0500 Subject: [PATCH 13/17] Disable rescanfrom input if rescan is unchecked --- src/mainwindow.cpp | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index a4a3d28..e31fc44 100755 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1266,6 +1266,11 @@ 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"); @@ -1276,7 +1281,7 @@ void MainWindow::importPrivKey() { }); auto keys = new QList(); - // split multiple privkeys seperated by spaces on a single line + // 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]; }); @@ -1302,17 +1307,8 @@ void MainWindow::importPrivKey() { }); DEBUG("found " << keysValidated->size() << " valid privkeys"); - bool rescan = pui.chkrescan->isChecked(); - //TODO: if rescan is not checked, disable the rescan height input - // Why don't these work? - if (rescan) { - //pui.rescanfrom->setEnabled(true); - pui.rescanfrom->setReadOnly(false); - } else { - //pui.rescanfrom->setDisabled(true); - pui.rescanfrom->setReadOnly(true); - } + bool rescan = pui.chkrescan->isChecked(); // avoid giving invalid data to RPCs and a rescan if there were no valid privkeys if(keysValidated->size() == 0) { From a0cc4184bcfb2502900ac7ca24f53f9abfc34480 Mon Sep 17 00:00:00 2001 From: Duke Date: Fri, 29 Dec 2023 17:23:44 -0500 Subject: [PATCH 14/17] Trim leading and trailing whitespace from rescan height when importing a privkey --- src/mainwindow.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index e31fc44..53dfcff 100755 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1324,8 +1324,8 @@ void MainWindow::importPrivKey() { doImport(keysValidated); if (rescan) { - //TODO: verify rescanfrom is a valid integer and trim whitespace - rpc->rescan(pui.rescanfrom->text().toInt() , [=] (QJsonValue response){ + //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); }); From b52c7bc151cc75e95e8cedff7e8fe7a23fe949ac Mon Sep 17 00:00:00 2001 From: Duke Date: Fri, 29 Dec 2023 17:37:42 -0500 Subject: [PATCH 15/17] Remove isSproutAddress --- src/settings.cpp | 7 ------- src/settings.h | 1 - 2 files changed, 8 deletions(-) diff --git a/src/settings.cpp b/src/settings.cpp index e6b1cd6..a862702 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -134,13 +134,6 @@ bool Settings::isSaplingAddress(QString addr) { (!isTestnet() && addr.startsWith("zs1")); } -bool Settings::isSproutAddress(QString addr) { - if (!isValidAddress(addr)) - return false; - - return isZAddress(addr) && !isSaplingAddress(addr); -} - bool Settings::isZAddress(QString addr) { if (!isValidAddress(addr)) return false; diff --git a/src/settings.h b/src/settings.h index 7a80879..7e752bd 100644 --- a/src/settings.h +++ b/src/settings.h @@ -47,7 +47,6 @@ public: void setTestnet(bool isTestnet); bool isSaplingAddress(QString addr); - bool isSproutAddress(QString addr); bool isValidSaplingPrivateKey(QString pk); bool isValidTransparentPrivateKey(QString pk); From d35a601dcc4e4a799bf015b19ae6b7496a3a059e Mon Sep 17 00:00:00 2001 From: Duke Date: Fri, 29 Dec 2023 17:59:22 -0500 Subject: [PATCH 16/17] verify that taddr privkeys are base58 --- src/settings.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/settings.cpp b/src/settings.cpp index a862702..2a154cf 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -441,8 +441,11 @@ bool Settings::isValidTransparentPrivateKey(QString pk) { // 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")) { - // TODO: verify only contains base58 characters - return true; + // 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; From 1b10aefbe8451a18035a7f07e73da6a2b1e4eba5 Mon Sep 17 00:00:00 2001 From: Duke Date: Fri, 29 Dec 2023 18:02:41 -0500 Subject: [PATCH 17/17] Cleanup --- src/mainwindow.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 53dfcff..1e69e33 100755 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1136,13 +1136,6 @@ void MainWindow::doImport(QList* keys) { if (keys->isEmpty()) { delete keys; - /* - if (rescan) { - ui->statusBar->showMessage(tr("Private key import with rescan finished")); - } else { - ui->statusBar->showMessage(tr("Private key import finished")); - } - */ return; }