diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index f1ea439..8326e44 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -40,8 +40,12 @@ MainWindow::MainWindow(QWidget *parent) : QDesktopServices::openUrl(QUrl("https://github.com/adityapk00/zec-qt-wallet/releases")); }); + // Import Private Key QObject::connect(ui->actionImport_Private_Key, &QAction::triggered, this, &MainWindow::importPrivKey); + // Export All Private Keys + QObject::connect(ui->actionExport_All_Private_Keys, &QAction::triggered, this, &MainWindow::exportAllKeys); + // Set up about action QObject::connect(ui->actionAbout, &QAction::triggered, [=] () { QDialog aboutDialog(this); @@ -436,6 +440,7 @@ void MainWindow::importPrivKey() { Ui_PrivKey pui; pui.setupUi(&d); + pui.buttonBox->button(QDialogButtonBox::Save)->setVisible(false); pui.helpLbl->setText(QString() % "Please paste your private keys (z-Addr or t-Addr) here, one per line.\n" % "The keys will be imported into your connected zcashd node"); @@ -462,6 +467,54 @@ void MainWindow::importPrivKey() { } } +void MainWindow::exportAllKeys() { + QDialog d(this); + Ui_PrivKey pui; + pui.setupUi(&d); + + auto ps = this->geometry(); + QMargins margin = QMargins() + 50; + d.setGeometry(ps.marginsRemoved(margin)); + + pui.privKeyTxt->setPlainText("Loading..."); + pui.privKeyTxt->setReadOnly(true); + pui.privKeyTxt->setLineWrapMode(QPlainTextEdit::LineWrapMode::NoWrap); + pui.helpLbl->setText("These are all the private keys for all the addresses in your wallet"); + + // Disable the save button until it finishes loading + pui.buttonBox->button(QDialogButtonBox::Save)->setEnabled(false); + pui.buttonBox->button(QDialogButtonBox::Ok)->setVisible(false); + + // Wire up save button + QObject::connect(pui.buttonBox->button(QDialogButtonBox::Save), &QPushButton::clicked, [=] () { + QString fileName = QFileDialog::getSaveFileName(this, tr("Save File"), + "zcash-all-privatekeys.txt"); + QFile file(fileName); + if (!file.open(QIODevice::WriteOnly)) { + QMessageBox::information(this, tr("Unable to open file"), file.errorString()); + return; + } + QTextStream out(&file); + out << pui.privKeyTxt->toPlainText(); + }); + + // Call the API + rpc->getAllPrivKeys([=] (auto privKeys) { + QString allKeysTxt; + for (auto keypair : privKeys) { + allKeysTxt = allKeysTxt % keypair.second % " # addr=" % keypair.first % "\n"; + } + + pui.privKeyTxt->setPlainText(allKeysTxt); + pui.buttonBox->button(QDialogButtonBox::Save)->setEnabled(true); + }); + + + + + d.exec(); +} + void MainWindow::setupBalancesTab() { ui->unconfirmedWarning->setVisible(false); @@ -486,13 +539,14 @@ void MainWindow::setupBalancesTab() { auto fnCB = [=] (const json& reply) { auto privKey = QString::fromStdString(reply.get()); QDialog d(this); - Ui_PrivKey pui; + Ui_PrivKey pui; pui.setupUi(&d); pui.helpLbl->setText("Private Key:"); pui.privKeyTxt->setPlainText(privKey); pui.privKeyTxt->setReadOnly(true); pui.privKeyTxt->selectAll(); + pui.buttonBox->button(QDialogButtonBox::Save)->setVisible(false); pui.buttonBox->button(QDialogButtonBox::Ok)->setVisible(false); d.exec(); @@ -542,6 +596,7 @@ void MainWindow::setupBalancesTab() { void MainWindow::setupTransactionsTab() { // Set up context menu on transactions tab ui->transactionsTable->setContextMenuPolicy(Qt::CustomContextMenu); + QObject::connect(ui->transactionsTable, &QTableView::customContextMenuRequested, [=] (QPoint pos) { QModelIndex index = ui->transactionsTable->indexAt(pos); if (index.row() < 0) return; diff --git a/src/mainwindow.h b/src/mainwindow.h index b7c50bd..a7a6871 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -82,6 +82,7 @@ private: void donate(); void importPrivKey(); + void exportAllKeys(); void doImport(QList* keys); void restoreSavedStates(); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index e0f75ac..cda1018 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -726,6 +726,7 @@ &File + @@ -781,6 +782,11 @@ &Import Private Key + + + Export All Private Keys + + diff --git a/src/precompiled.h b/src/precompiled.h index 9c045fd..34e5b02 100644 --- a/src/precompiled.h +++ b/src/precompiled.h @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include diff --git a/src/privkey.ui b/src/privkey.ui index 320e8bf..e2e5395 100644 --- a/src/privkey.ui +++ b/src/privkey.ui @@ -27,7 +27,7 @@ Qt::Horizontal - QDialogButtonBox::Close|QDialogButtonBox::Ok + QDialogButtonBox::Close|QDialogButtonBox::Ok|QDialogButtonBox::Save diff --git a/src/rpc.cpp b/src/rpc.cpp index 5a5e910..cd7b447 100644 --- a/src/rpc.cpp +++ b/src/rpc.cpp @@ -202,6 +202,88 @@ void RPC::sendZTransaction(json params, const std::function& cb) { conn->doRPCWithDefaultErrorHandling(payload, cb); } +/** + * Method to get all the private keys for both z and t addresses. It will make 2 batch calls, + * combine the result, and call the callback with a single list containing both the t-addr and z-addr + * private keys + */ +void RPC::getAllPrivKeys(const std::function>)> cb) { + + // A special function that will call the callback when two lists have been added + auto holder = new QPair>>(); + holder->first = 0; // This is the number of times the callback has been called, initalized to 0 + auto fnCombineTwoLists = [=] (QList> list) { + // Increment the callback counter + holder->first++; + + // Add all + std::copy(list.begin(), list.end(), std::back_inserter(holder->second)); + + // And if the caller has been called twice, do the parent callback with the + // collected list + if (holder->first == 2) { + // Sort so z addresses are on top + std::sort(holder->second.begin(), holder->second.end(), + [=] (auto a, auto b) { return a.first > b.first; }); + + cb(holder->second); + delete holder; + } + }; + + // A utility fn to do the batch calling + auto fnDoBatchGetPrivKeys = [=](json getAddressPayload, std::string privKeyDumpMethodName) { + conn->doRPCWithDefaultErrorHandling(getAddressPayload, [=] (json resp) { + QList addrs; + for (auto addr : resp.get()) { + addrs.push_back(QString::fromStdString(addr.get())); + } + + // Then, do a batch request to get all the private keys + conn->doBatchRPC( + addrs, + [=] (auto addr) { + json payload = { + {"jsonrpc", "1.0"}, + {"id", "someid"}, + {"method", privKeyDumpMethodName}, + {"params", { addr.toStdString() }}, + }; + return payload; + }, + [=] (QMap* privkeys) { + QList> allTKeys; + for (QString addr: privkeys->keys()) { + allTKeys.push_back( + QPair( + addr, + QString::fromStdString(privkeys->value(addr).get()))); + } + + fnCombineTwoLists(allTKeys); + } + ); + }); + }; + + + // First get all the T and Z addresses. + json payloadT = { + {"jsonrpc", "1.0"}, + {"id", "someid"}, + {"method", "getaddressesbyaccount"}, + {"params", {""} } + }; + + json payloadZ = { + {"jsonrpc", "1.0"}, + {"id", "someid"}, + {"method", "z_listaddresses"} + }; + + fnDoBatchGetPrivKeys(payloadT, "dumpprivkey"); + fnDoBatchGetPrivKeys(payloadZ, "z_exportkey"); +} // Build the RPC JSON Parameters for this tx (with the dev fee included if applicable) diff --git a/src/rpc.h b/src/rpc.h index 9092ce6..6ec7df7 100644 --- a/src/rpc.h +++ b/src/rpc.h @@ -56,6 +56,8 @@ public: void importZPrivKey(QString addr, bool rescan, const std::function& cb); void importTPrivKey(QString addr, bool rescan, const std::function& cb); + void getAllPrivKeys(const std::function>)>); + Turnstile* getTurnstile() { return turnstile; } Connection* getConnection() { return conn; }