Compare commits

...

53 Commits

Author SHA1 Message Date
onryo fe04ef3095 Merge branch 'dev' 3 months ago
onryo 23e3e3899a Add release name before merging 3 months ago
Duke 23813ae00e Update relnotes 3 months ago
Duke d8e528f696 Bump version to 1.4.2 3 months ago
Duke 5610d6c572 Update relnotes for 1.4.2 3 months ago
Duke 3a6f632b9b Try to prevent #155 4 months ago
Duke 2754629a95 Do not log sensitive data to STDOUT 4 months ago
Duke eea2a9a6bf Choose number of compile jobs in build.sh #156 4 months ago
duke e6195fd918 Merge pull request 'Replace old graphics with new' (#147) from onryo into dev 4 months ago
Duke b3f195432e Merge branch 'dev' 5 months ago
Duke 02325ad7ae relnotes 5 months ago
Duke 0c9a3f43ad linguist 5 months ago
Duke 00b143e04f Create relnotes for 1.4.1 5 months ago
Duke c442377473 bump version 5 months ago
duke 5659a2a725 Merge pull request 'Debug tab' (#148) from debuglog into dev 5 months ago
Duke 702619a8e0 Support dragonx debug log and custom number of lines 5 months ago
onryo ee5e94cfd0 fix build when removing old files 5 months ago
onryo 8119c5b6e3 fix 146 5 months ago
Duke da3fb9c8a4 Add refresh button for debug log 5 months ago
Duke 0f45b3fe5b Add tab for viewing the debug log 5 months ago
Duke 05790935ba Revert "change gif when sending" 5 months ago
duke 203c23dad2 Merge pull request 'Allow custom rescan height on privkey import' (#145) from import into dev 5 months ago
Duke 1b10aefbe8 Cleanup 5 months ago
Duke d35a601dcc verify that taddr privkeys are base58 5 months ago
Duke b52c7bc151 Remove isSproutAddress 5 months ago
Duke a0cc4184bc Trim leading and trailing whitespace from rescan height when importing a privkey 5 months ago
Duke 8b41a3b0f4 Disable rescanfrom input if rescan is unchecked 5 months ago
Duke 68bd0db44c Only import valid zaddr+taddr privkeys and only rescan if there was at least 1 valid privkey 5 months ago
Duke 2000377783 Too bad this code does not seem to work 5 months ago
Duke e042c1fb09 We do not support sprout privkeys that begin with SK 5 months ago
Duke 7db95fbf49 Support disabling rescan or a custom rescan height when importing privkeys 5 months ago
Duke 8bb0b3df5c Make import popup look nicer 5 months ago
Duke f689547a36 Add basic rescan options to import popup 5 months ago
onryo f50ccd78b5 change gif when sending 5 months ago
onryo 219e41ccb5 dialog text 5 months ago
Duke ec6327a6b0 Add function to validate taddr privkeys 5 months ago
Duke 227dcaddbb Slightly better zaddr privkey validation 5 months ago
onryo 868bf83155 add rescanfrom 5 months ago
onryo 4ef65d1a3f add chkrescan 5 months ago
Duke 17c44521be Use takeFirst() instead of first() and pop_front() 5 months ago
Duke c5ceac8443 View transparent spends and shielded outputs in txs; Allow double clicking on a tx in tx tab to view tx 5 months ago
Duke 8b8bca02ac Add View transaction as right menu option in tx tab 5 months ago
Duke 81702d25c1 Add view transaction ui file 5 months ago
Duke fb67b86d0d View Transaction 5 months ago
Duke f914f6d61a Just say Error, since we show this error for all errors, not just transaction errors 5 months ago
Duke a18014b6d4 Render valuePools json and each tx in their own property 5 months ago
Duke 1e4095651a Allow items to be selected 5 months ago
Duke 946ed661e9 Tor v3 has been supported for a while 5 months ago
Duke 9e4c26ccd2 Show transaction list in CSV format when viewing a block; allow viewing block 0 5 months ago
Duke 5dc1944084 More debugging about block properties 5 months ago
Duke 9187f3c620 Add getrawtransaction RPC method 5 months ago
Duke 4d28ac2969 Add z_viewtransaction RPC method 5 months ago
Duke d3846c7bdf Merge branch 'master' into dev 6 months ago
  1. 1
      README.md
  2. 3
      application.qrc
  3. 23
      build.sh
  4. 30
      doc/relnotes/README.md
  5. BIN
      res/silentdragon-animated-dark.gif
  6. BIN
      res/silentdragon-animated-startup.gif
  7. BIN
      res/silentdragon-animated.gif
  8. BIN
      res/silentdragon_be.qm
  9. 635
      res/silentdragon_be.ts
  10. 639
      res/silentdragon_bg.ts
  11. BIN
      res/silentdragon_de.qm
  12. 635
      res/silentdragon_de.ts
  13. BIN
      res/silentdragon_es.qm
  14. 635
      res/silentdragon_es.ts
  15. BIN
      res/silentdragon_fi.qm
  16. 635
      res/silentdragon_fi.ts
  17. BIN
      res/silentdragon_fil.qm
  18. 635
      res/silentdragon_fil.ts
  19. BIN
      res/silentdragon_fr.qm
  20. 635
      res/silentdragon_fr.ts
  21. BIN
      res/silentdragon_hr.qm
  22. 635
      res/silentdragon_hr.ts
  23. BIN
      res/silentdragon_it.qm
  24. 635
      res/silentdragon_it.ts
  25. BIN
      res/silentdragon_nl.qm
  26. 635
      res/silentdragon_nl.ts
  27. BIN
      res/silentdragon_pl.qm
  28. 635
      res/silentdragon_pl.ts
  29. BIN
      res/silentdragon_pt.qm
  30. 635
      res/silentdragon_pt.ts
  31. BIN
      res/silentdragon_ro.qm
  32. 635
      res/silentdragon_ro.ts
  33. BIN
      res/silentdragon_ru.qm
  34. 635
      res/silentdragon_ru.ts
  35. BIN
      res/silentdragon_sr.qm
  36. 635
      res/silentdragon_sr.ts
  37. BIN
      res/silentdragon_tr.qm
  38. 635
      res/silentdragon_tr.ts
  39. BIN
      res/silentdragon_uk.qm
  40. 635
      res/silentdragon_uk.ts
  41. BIN
      res/silentdragon_zh.qm
  42. 637
      res/silentdragon_zh.ts
  43. 1
      silentdragon.pro
  44. 1
      silentdragonx.pro
  45. 23
      src/connection.cpp
  46. 351
      src/mainwindow.cpp
  47. 4
      src/mainwindow.h
  48. 69
      src/mainwindow.ui
  49. 34
      src/privkey.ui
  50. 34
      src/rpc.cpp
  51. 2
      src/rpc.h
  52. 4
      src/sendtab.cpp
  53. 31
      src/settings.cpp
  54. 2
      src/settings.h
  55. 3
      src/validateaddress.cpp
  56. 2
      src/validateaddress.h
  57. 2
      src/version.h
  58. 85
      src/viewtransaction.ui

1
README.md

@ -37,7 +37,6 @@ bits of data.
This means your IP address is known to these servers. Enable Tor setting
in SilentDragon to prevent this, or better yet, use TAILS: https://tails.boum.org/
NOTE: Tor v3 is not yet supported.
# Installation

3
application.qrc

@ -26,9 +26,6 @@
<qresource prefix="/img">
<file alias="hushdlogo.png">res/hushdlogo.png</file>
<file alias="logobig.gif">res/logobig.gif</file>
<file alias="silentdragon-animated.gif">res/silentdragon-animated.gif</file>
<file alias="silentdragon-animated-dark.gif">res/silentdragon-animated-dark.gif</file>
<file alias="silentdragon-animated-startup.gif">res/silentdragon-animated-startup.gif</file>
<file alias="silentdragon-animated-startup-dark.gif">res/silentdragon-animated-startup-dark.gif</file>
</qresource>
<qresource prefix="/translations">

23
build.sh

@ -4,18 +4,7 @@
set -e
UNAME=$(uname)
if [ "$UNAME" == "Linux" ] ; then
JOBS=$(nproc)
elif [ "$UNAME" == "FreeBSD" ] ; then
JOBS=$(nproc)
elif [ "$UNAME" == "Darwin" ] ; then
JOBS=$(sysctl -n hw.ncpu)
else
JOBS=1
fi
VERSION=$(cat src/version.h |cut -d\" -f2)
VERSION=$(grep APP_VERSION src/version.h |cut -d\" -f2)
CONF=${SDCONF:-silentdragon.pro}
WALLET="SilentDragon"
if [ "$CONF" == "silentdragonx.pro" ] ; then
@ -40,12 +29,12 @@ make --version
qbuild () {
qmake $CONF -spec linux-clang CONFIG+=debug
make -j$JOBS
make -j2 "$@"
}
qbuild_release () {
qmake $CONF -spec linux-clang CONFIG+=release
make -j$JOBS
make -j2 "$@"
}
if [ "$1" == "clean" ]; then
@ -55,9 +44,9 @@ elif [ "$1" == "linguist" ]; then
lrelease $CONF
elif [ "$1" == "cleanbuild" ]; then
make clean
qbuild
qbuild "$@"
elif [ "$1" == "release" ]; then
qbuild_release
qbuild_release "$@"
else
qbuild
qbuild "$@"
fi

30
doc/relnotes/README.md

@ -10,6 +10,36 @@ and no longer on Github, since they banned Duke Leto and
also because they censor many people around the world and work with
evil organizations.
# SilentDragon 1.4.2 "Waggish Weevil"
```
11 files changed, 35 insertions(+), 32 deletions(-)
```
* Fix a coredump that can happen after importing a private key https://git.hush.is/hush/SilentDragon/issues/155
* Prevent the logging of sensitive data to STDOUT, since it could be redirected to a file or shared in a bug report or screenshot https://git.hush.is/hush/SilentDragon/commit/2754629a95c1efe603b3c3245f90a26b3ed7f177
* Update startup animation graphic https://git.hush.is/hush/SilentDragon/pulls/147
* Allow compiling with a custom number of jobs in build.sh https://git.hush.is/hush/SilentDragon/issues/156
# SilentDragon 1.4.1 "Scintillating Sundew"
```
52 files changed, 7163 insertions(+), 5023 deletions(-)
```
* View details of a transaction #136
* If a tx has no memo, you can simply double click on it to view more details.
* Otherwise, right click and choose View Transaction
* Render more details in View Block #135
* Now all transactions are listed as well as valuePools key
* Greatly improved Private Key Importing
* A custom rescan height or disabling rescanning is now possible
* Invalid privkeys are now filtered out before being sent to the full node
* This prevents confusing popups #142
* Fixed bug where importing multiple taddrs would rescan multiple times
* New Tab "Debug Log" renders content of debug.log
* Allows showing the last X lines of debug.log, defaults to 50 lines
# SilentDragon 1.4.0 "Zany Zooid"
```

BIN
res/silentdragon-animated-dark.gif

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 KiB

BIN
res/silentdragon-animated-startup.gif

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

BIN
res/silentdragon-animated.gif

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 KiB

BIN
res/silentdragon_be.qm

Binary file not shown.

635
res/silentdragon_be.ts

File diff suppressed because it is too large

639
res/silentdragon_bg.ts

File diff suppressed because it is too large

BIN
res/silentdragon_de.qm

Binary file not shown.

635
res/silentdragon_de.ts

File diff suppressed because it is too large

BIN
res/silentdragon_es.qm

Binary file not shown.

635
res/silentdragon_es.ts

File diff suppressed because it is too large

BIN
res/silentdragon_fi.qm

Binary file not shown.

635
res/silentdragon_fi.ts

File diff suppressed because it is too large

BIN
res/silentdragon_fil.qm

Binary file not shown.

635
res/silentdragon_fil.ts

File diff suppressed because it is too large

BIN
res/silentdragon_fr.qm

Binary file not shown.

635
res/silentdragon_fr.ts

File diff suppressed because it is too large

BIN
res/silentdragon_hr.qm

Binary file not shown.

635
res/silentdragon_hr.ts

File diff suppressed because it is too large

BIN
res/silentdragon_it.qm

Binary file not shown.

635
res/silentdragon_it.ts

File diff suppressed because it is too large

BIN
res/silentdragon_nl.qm

Binary file not shown.

635
res/silentdragon_nl.ts

File diff suppressed because it is too large

BIN
res/silentdragon_pl.qm

Binary file not shown.

635
res/silentdragon_pl.ts

File diff suppressed because it is too large

BIN
res/silentdragon_pt.qm

Binary file not shown.

635
res/silentdragon_pt.ts

File diff suppressed because it is too large

BIN
res/silentdragon_ro.qm

Binary file not shown.

635
res/silentdragon_ro.ts

File diff suppressed because it is too large

BIN
res/silentdragon_ru.qm

Binary file not shown.

635
res/silentdragon_ru.ts

File diff suppressed because it is too large

BIN
res/silentdragon_sr.qm

Binary file not shown.

635
res/silentdragon_sr.ts

File diff suppressed because it is too large

BIN
res/silentdragon_tr.qm

Binary file not shown.

635
res/silentdragon_tr.ts

File diff suppressed because it is too large

BIN
res/silentdragon_uk.qm

Binary file not shown.

635
res/silentdragon_uk.ts

File diff suppressed because it is too large

BIN
res/silentdragon_zh.qm

Binary file not shown.

637
res/silentdragon_zh.ts

File diff suppressed because it is too large

1
silentdragon.pro

@ -90,6 +90,7 @@ HEADERS += \
FORMS += \
src/getblock.ui \
src/viewtransaction.ui \
src/mainwindow.ui \
src/qrcode.ui \
src/rescandialog.ui \

1
silentdragonx.pro

@ -89,6 +89,7 @@ HEADERS += \
FORMS += \
src/getblock.ui \
src/viewtransaction.ui \
src/mainwindow.ui \
src/qrcode.ui \
src/rescandialog.ui \

23
src/connection.cpp

@ -8,6 +8,7 @@
#include "rpc.h"
#include "precompiled.h"
#include "version.h"
#include "sd.h"
extern bool isdragonx;
@ -889,15 +890,25 @@ Connection::~Connection() {
void Connection::doRPC(const QJsonValue& payload, const std::function<void(QJsonValue)>& cb,
const std::function<void(QNetworkReply*, const QJsonValue&)>& ne) {
if (shutdownInProgress) {
qDebug() << __func__ << ": Ignoring RPC because shutdown in progress";
DEBUG("Ignoring RPC because shutdown in progress");
return;
}
if(payload.isNull() || payload.isUndefined()) {
qDebug() << "no payload! ignoring";
DEBUG("no payload! ignoring");
return;
} else {
qDebug() << __func__ << ": " << payload["method"].toString() << payload;
// this will match importprivkey z_importkey z_importviewingkey importwallet z_importwallet
// and some other RPCs that have no GUI
// So this code ends up redacting payloads which contain private keys and filenames which contain private keys
QRegExp re("import");
//DEBUG("payload.toString==" << payload["method"].toString());
//DEBUG("payload.toString.indexIn==" << re.indexIn(payload["method"].toString()) );
if( re.indexIn(payload["method"].toString()) == -1 ) {
DEBUG( payload["method"].toString() << payload );
} else {
DEBUG( payload["method"].toString() << " PAYLOAD REDACTED " );
}
}
QJsonDocument jd_rpc_call(payload.toObject());
@ -908,7 +919,7 @@ void Connection::doRPC(const QJsonValue& payload, const std::function<void(QJson
QObject::connect(reply, &QNetworkReply::finished, [=] {
reply->deleteLater();
if (shutdownInProgress) {
// Ignoring callback because shutdown in progress
DEBUG("Ignoring callback because shutdown in progress");
return;
}
@ -936,8 +947,10 @@ void Connection::doRPC(const QJsonValue& payload, const std::function<void(QJson
void Connection::doRPCWithDefaultErrorHandling(const QJsonValue& payload, const std::function<void(QJsonValue)>& cb) {
doRPC(payload, cb, [=] (QNetworkReply* reply, const QJsonValue &parsed) {
if (!parsed.isUndefined() && !parsed["error"].toObject()["message"].isNull()) {
DEBUG("got a parse error");
this->showTxError(parsed["error"].toObject()["message"].toString());
} else {
DEBUG("got a reply error");
this->showTxError(reply->errorString());
}
});
@ -958,7 +971,7 @@ void Connection::showTxError(const QString& error) {
return;
shown = true;
QMessageBox::critical(main, QObject::tr("Transaction Error"), QObject::tr("There was an error! : ") + "\n\n"
QMessageBox::critical(main, QObject::tr("Error"), QObject::tr("There was an error! : ") + "\n\n"
+ error, QMessageBox::StandardButton::Ok);
shown = false;
}

351
src/mainwindow.cpp

@ -13,6 +13,7 @@
#include "ui_settings.h"
#include "ui_viewalladdresses.h"
#include "ui_validateaddress.h"
#include "ui_viewtransaction.h"
#include "ui_rescandialog.h"
#include "ui_getblock.h"
#include "rpc.h"
@ -99,6 +100,9 @@ MainWindow::MainWindow(QWidget *parent) :
// Get Block
QObject::connect(ui->actionGet_Block, &QAction::triggered, this, &MainWindow::getBlock);
// View tx
QObject::connect(ui->actionView_Transaction, &QAction::triggered, this, &MainWindow::viewTransaction);
// Address Book
QObject::connect(ui->action_Address_Book, &QAction::triggered, this, &MainWindow::addressBook);
@ -132,6 +136,7 @@ MainWindow::MainWindow(QWidget *parent) :
qDebug() << "Created RPC";
setupMiningTab();
setupDebugLogTab();
restoreSavedStates();
}
@ -903,6 +908,149 @@ void MainWindow::validateAddress() {
});
}
// Ask user for txid to view
void MainWindow::viewTransaction() {
// Make sure everything is up and running
if (!getRPC() || !getRPC()->getConnection())
return;
// First thing is ask the user for a txid
bool ok;
QString txid = QInputDialog::getText(this, tr("View Transaction"),
tr("Enter Transaction ID (txid):"), QLineEdit::Normal, "", &ok);
if (!ok)
return;
viewTxid(txid);
}
// view a given txid
void MainWindow::viewTxid(QString txid) {
// ignore leading and trailing whitespace
txid = txid.trimmed();
QRegExp rx("^[0-9a-f]{64}$");
if(!rx.exactMatch(txid)) {
DEBUG("invalid txid " << txid );
return;
}
// we got a valid txid
getRPC()->getrawtransaction(txid, [=] (QJsonValue props) {
// TODO: only z_viewtransaction shows memo
// getRPC()->z_viewtransaction(txid, [=] (QJsonValue props) {
QDialog d(this);
Ui_ViewTransaction vt;
vt.setupUi(&d);
Settings::saveRestore(&d);
Settings::saveRestoreTableHeader(vt.tblProps, &d, "getblockprops");
vt.tblProps->horizontalHeader()->setStretchLastSection(true);
vt.lblHeight->setText(txid);
QList<QPair<QString, QString>> propsList;
for (QString property_name: props.toObject().keys()) {
QString property_value;
DEBUG("property " << property_name << "=" << props.toObject()[property_name] );
if (props.toObject()[property_name].isString()) {
property_value = props.toObject()[property_name].toString();
} else if (props.toObject()[property_name].isDouble()) {
property_value = QString::number( props.toObject()[property_name].toDouble(), 'f', 0);
} else if (props.toObject()[property_name].isBool()) {
property_value = props.toObject()[property_name].toBool() ? "true" : "false" ;
} else if (props.toObject()[property_name].isArray()) {
DEBUG( property_name << " is an array");
// vin is the Vector of INputs of transparent spends
if( property_name == "vin" ) {
int index = 0;
auto vins = props.toObject()[property_name].toArray();
for (const auto& vin : vins) {
QString this_vin = "vin " + QString::number(index);
auto vinObj = vin.toObject();
// Is this a normal input or coinbase input?
bool is_coinbase = vinObj["coinbase"].toString().length() > 0;
if (is_coinbase) {
propsList.append(
QPair<QString, QString>( this_vin + " coinbase", vinObj["coinbase"].toString() )
);
} else {
// the address of this spend
propsList.append(
QPair<QString, QString>( this_vin + " address", vinObj["address"].toString() )
);
// the value of this spend
propsList.append(
QPair<QString, QString>( this_vin + " value", QString::number(vinObj["value"].toDouble()) )
);
// the txid in which this UTXO was created
propsList.append(
QPair<QString, QString>( this_vin + " txid", vinObj["txid"].toString() )
);
// the index of vout in the txid it was created
propsList.append(
QPair<QString, QString>( this_vin + " vout", QString::number(vinObj["vout"].toInt()) )
);
}
// sequence number is part of both coinbase and non-coinbase vins
propsList.append(
QPair<QString, QString>( this_vin + " sequence", QString::number(vinObj["sequence"].toDouble()) )
);
index++;
}
} else if (property_name == "vout") {
// vout = Vector of OUTputs of transparent sends
auto vouts = props.toObject()[property_name].toArray();
if(vouts.size() == 0) {
propsList.append( QPair<QString, QString>( "vout", "Empty") );
} else {
//...
}
} else if (property_name == "vShieldedOutput") {
// the vector of shielded outputs
auto zouts = props.toObject()[property_name].toArray();
if(zouts.size() == 0) {
propsList.append( QPair<QString, QString>( property_name, "Empty"));
} else {
int index = 0;
QString property_value = "";
for (const auto& zout : zouts) {
auto zoutObj = zout.toObject();
auto properties = {"proof", "cv", "cmu", "encCiphertext", "outCiphertext", "ephemeralKey" };
for(const auto& prop : properties ) {
propsList.append(
QPair<QString, QString>( "vShieldedOutput " + QString::number(index) + " " + prop, zoutObj[prop].toString() )
);
}
index++;
}
}
} else if (property_name == "vShieldedSpend") {
// the vector of shielded spends
auto zins = props.toObject()[property_name].toArray();
if(zins.size() == 0) {
propsList.append( QPair<QString, QString>( property_name, "Empty"));
} else {
// TODO
}
}
} else if (props.toObject()[property_name].isObject()) {
DEBUG( property_name << " is an object");
}
if (property_name != "vin" && property_name != "vout" &&
property_name != "vShieldedOutput" && property_name != "vShieldedSpend" ) {
propsList.append( QPair<QString, QString>( property_name, property_value ));
}
}
ValidateAddressesModel model(vt.tblProps, propsList);
vt.tblProps->setModel(&model);
d.exec();
});
}
// Get block info
void MainWindow::getBlock() {
// Make sure everything is up and running
@ -912,7 +1060,7 @@ void MainWindow::getBlock() {
// First thing is ask the user for a block height
bool ok;
int i = QInputDialog::getInt(this, tr("Get Block Info"),
tr("Enter Block Height:"), 1, 1, 2147483647, 1, &ok);
tr("Enter Block Height:"), 12345, 0, 2147483647, 1, &ok);
if (!ok)
return;
@ -934,19 +1082,42 @@ void MainWindow::getBlock() {
QString property_value;
DEBUG("property = " << props.toObject()[property_name] );
DEBUG("property " << property_name << "=" << props.toObject()[property_name] );
if (props.toObject()[property_name].isString()) {
property_value = props.toObject()[property_name].toString();
} else if (props.toObject()[property_name].isDouble()) {
property_value = QString::number( props.toObject()[property_name].toDouble(), 'f', 0);
} else if (props.toObject()[property_name].isBool()) {
property_value = props.toObject()[property_name].toBool() ? "true" : "false" ;
} else if (props.toObject()[property_name].isArray()) {
DEBUG( property_name << " is an array");
if( property_name == "tx") {
auto txs = props.toObject()[property_name].toArray();
int index = 0;
// create a property for each tx in the block so it renders in a more useful way
for (const auto& tx : txs) {
QString this_property_name = "tx " + QString::number(index);
propsList.append(
QPair<QString, QString>( this_property_name, tx.toString() )
);
index++;
}
} else if (property_name == "valuePools") {
auto stuff = props.toObject()[property_name].toArray();
property_value = QJsonDocument(stuff).toJson();
property_value.remove("\n");
property_value.remove(" ");
}
} else if (props.toObject()[property_name].isObject()) {
DEBUG( property_name << " is an object");
}
propsList.append(
QPair<QString, QString>( property_name,
property_value )
);
// tx properties are added in their own special way above
if (property_name != "tx") {
propsList.append(
QPair<QString, QString>( property_name, property_value )
);
}
}
ValidateAddressesModel model(gb.tblProps, propsList);
@ -962,21 +1133,22 @@ void MainWindow::doImport(QList<QString>* 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); });
}
}
@ -1077,7 +1249,6 @@ void MainWindow::payHushURI(QString uri, QString myAddr) {
}
}
void MainWindow::importPrivKey() {
QDialog d(this);
Ui_PrivKey pui;
@ -1089,20 +1260,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<QString> 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<QString>();
// 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
@ -1113,13 +1291,53 @@ 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<QString>();
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){
//DEBUG("rescanning from height " << pui.rescanfrom->text().toInt() << " finished" << response);
DEBUG("rescanning 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);
}
}
}
@ -1768,6 +1986,66 @@ void MainWindow::setupMiningTab() {
// Mining tab currently only enabled for DragonX
}
}
QString MainWindow::readDebugLines(uint32_t lines) {
QString coin = isdragonx ? "DRAGONX" : "HUSH3";
#ifdef Q_OS_LINUX
QFile file(QDir::homePath() + "/.hush/" + coin + "/debug.log");
#elif defined(Q_OS_DARWIN)
QFile file(QDir::homePath() + "/Library/Application Support/Hush/" + coin + "/debug.log");
#elif defined(Q_OS_WIN64)
QFile file(QDir::homePath() + "/AppData/Roaming/Hush/" + coin + "/debug.log");
#else
// Bless Your Heart, You Like Danger!
QFile file(QDir::homePath() + "/.hush/" + coin + "/debug.log");
#endif // Q_OS_LINUX
if(file.exists()) {
DEBUG(": Found debug.log at " << file);
} else {
DEBUG("No debug.log found!");
return "";
}
if(file.open(QIODevice::ReadOnly))
{
qint64 fileSize = file.size();
DEBUG("debug.log size=" << fileSize);
if(fileSize < 2) {
DEBUG("debug.log is too small");
return "";
}
file.seek(file.size()-1);
uint32_t count = 0;
while ( (count < lines) && (file.pos() > 0) )
{
QString ch = file.read(1);
file.seek(file.pos()-2);
if (ch == "\n")
count++;
}
file.seek(file.pos()+2);
QString debugText = file.readAll();
DEBUG("got " << debugText.size() << " bytes of debugText");
file.close();
return debugText;
}
return "";
}
void MainWindow::setupDebugLogTab() {
ui->debugLog->setReadOnly(true);
ui->debugLog->setPlainText("Loading debug log...");
QObject::connect(ui->refreshDebugButton, &QPushButton::clicked, [=] () {
uint32_t debugLines = ui->debugLines->text().trimmed().toInt();
if (debugLines == 0) { debugLines = 50; }
DEBUG("refresh debug log clicked with debugLines=" << debugLines);
ui->debugLog->setPlainText( readDebugLines(debugLines) );
});
ui->debugLog->setPlainText( readDebugLines() );
}
void MainWindow::setupPeersTab() {
qDebug() << __FUNCTION__;
// Set up context menu on peers tab
@ -1918,33 +2196,8 @@ void MainWindow::setupPeersTab() {
menu.exec(ui->peersTable->viewport()->mapToGlobal(pos));
});
/*
//grep 'BAN THRESHOLD EXCEEDED' ~/.hush/HUSH3/debug.log
//grep Disconnected ...
QFile debuglog = "";
#ifdef Q_OS_LINUX
debuglog = "~/.hush/HUSH3/debug.log";
#elif defined(Q_OS_DARWIN)
debuglog = "~/Library/Application Support/Hush/HUSH3/debug.log";
#elif defined(Q_OS_WIN64)
// "C:/Users/<USER>/AppData/Roaming/<APPNAME>",
// TODO: get current username
debuglog = "C:/Users/<USER>/AppData/Roaming/Hush/HUSH3/debug.log";
#else
// Bless Your Heart, You Like Danger!
// There are open bounties to port HUSH softtware to OpenBSD and friends:
// git.hush.is/hush/tasks
debuglog = "~/.hush/HUSH3/debug.log";
#endif // Q_OS_LINUX
if(debuglog.exists()) {
qDebug() << __func__ << ": Found debuglog at " << debuglog;
} else {
qDebug() << __func__ << ": No debug.log found";
}
*/
//ui->recentlyBannedPeers = "Could not open " + debuglog;
}
@ -2049,6 +2302,11 @@ void MainWindow::setupTransactionsTab() {
mb.exec();
}
}
} else {
// if no memo, show View Transaction
DEBUG("double clicked tx index=" << index);
QString txid = txModel->getTxId(index.row());
viewTxid(txid);
}
});
@ -2073,6 +2331,11 @@ void MainWindow::setupTransactionsTab() {
ui->statusBar->showMessage(tr("Copied to clipboard"), 3 * 1000);
});
menu.addAction(tr("View transaction"), [=] () {
ui->statusBar->showMessage(tr("Viewing transaction") + " " + txid, 3 * 1000);
viewTxid(txid);
});
if (!addr.isEmpty()) {
menu.addAction(tr("Copy address"), [=] () {
QGuiApplication::clipboard()->setText(addr);

4
src/mainwindow.h

@ -53,6 +53,8 @@ public:
void validateAddress();
void getBlock();
void viewTransaction();
void viewTxid(QString txid = "");
void updateLabels();
void updateTAddrCombo(bool checked);
@ -83,6 +85,7 @@ private:
void closeEvent(QCloseEvent* event);
QString readDebugLines(uint32_t lines = 50);
void setupSendTab();
void setupPeersTab();
void setupTransactionsTab();
@ -92,6 +95,7 @@ private:
void setupChatTab();
void setupMarketTab();
void setupMiningTab();
void setupDebugLogTab();
void slot_change_theme(QString& themeName);
void slot_change_currency(const QString& currencyName);

69
src/mainwindow.ui

@ -1130,6 +1130,68 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_debug">
<attribute name="title">
<string>Debug Log</string>
</attribute>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QPushButton" name="refreshDebugButton">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
Click to see the latest debug log data
<string/>
</property>
<property name="text">
<string>Refresh</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="debugLabel">
<property name="text">
<string>Number of lines to show</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLineEdit" name="debugLines">
<property name="enabled">
<bool>true</bool>
</property>
<property name="placeholderText">
<string>50</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="4">
<widget class="QPlainTextEdit" name="debugLog">
<property name="plainText">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_5">
<attribute name="title">
<string>Node info</string>
@ -1602,6 +1664,7 @@
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menuBar">
<property name="geometry">
<rect>
@ -1641,6 +1704,7 @@
<string>&amp;Apps</string>
</property>
<addaction name="actionGet_Block"/>
<addaction name="actionView_Transaction"/>
<addaction name="actionValidate_Address"/>
<addaction name="separator"/>
</widget>
@ -1751,6 +1815,11 @@
<string>Get Block Info</string>
</property>
</action>
<action name="actionView_Transaction">
<property name="text">
<string>View Transaction Info</string>
</property>
</action>
</widget>
<layoutdefault spacing="6" margin="11"/>
<customwidgets>

34
src/privkey.ui

@ -14,7 +14,7 @@
<string>Private Keys</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="0">
<item row="1" column="0" colspan="3">
<widget class="QPlainTextEdit" name="privKeyTxt">
<property name="plainText">
<string/>
@ -22,6 +22,38 @@
</widget>
</item>
<item row="2" column="0">
<widget class="QCheckBox" name="chkrescan">
<property name="text">
<string>Rescan</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="rescanlabel">
<property name="text">
<string>Rescan Height</string>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QLineEdit" name="rescanfrom">
<property name="text">
<string notr="true">1</string>
</property>
<property name="minimumSize">
<size>
<width>50</width>
</size>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>

34
src/rpc.cpp

@ -275,6 +275,28 @@ void RPC::getZUnspent(const std::function<void(QJsonValue)>& cb) {
conn->doRPCWithDefaultErrorHandling(payload, cb);
}
void RPC::z_viewtransaction(QString txid, const std::function<void(QJsonValue)>& cb) {
QJsonObject payload = {
{"jsonrpc", "1.0"},
{"id", "42"},
{"method", "z_viewtransaction"},
{"params", QJsonArray {txid}}
};
conn->doRPCWithDefaultErrorHandling(payload, cb);
}
void RPC::getrawtransaction(QString txid, const std::function<void(QJsonValue)>& cb) {
QJsonObject payload = {
{"jsonrpc", "1.0"},
{"id", "42"},
{"method", "getrawtransaction"},
{"params", QJsonArray {txid, 1}}
};
conn->doRPCWithDefaultErrorHandling(payload, cb);
}
void RPC::newZaddr(const std::function<void(QJsonValue)>& cb) {
QJsonObject payload = {
{"jsonrpc", "1.0"},
@ -347,24 +369,24 @@ void RPC::importTPrivKey(QString privkey, bool rescan, const std::function<void(
// If privkey starts with 5, K or L, use old-style Hush params, same as BTC+ZEC
if( privkey.startsWith("5") || privkey.startsWith("K") || privkey.startsWith("L") ) {
qDebug() << "Detected old-style HUSH WIF";
DEBUG("Detected old-style taddr HUSH WIF");
payload = {
{"jsonrpc", "1.0"},
{"id", "42"},
{"method", "importprivkey"},
{"params", QJsonArray { privkey, "", "false", "0", "128" }},
{"params", QJsonArray { privkey, "", rescan , "0", "128" }},
};
} else {
qDebug() << "Detected new-style HUSH WIF";
DEBUG("Detected new-style taddr HUSH WIF");
payload = {
{"jsonrpc", "1.0"},
{"id", "42"},
{"method", "importprivkey"},
{"params", QJsonArray { privkey, (rescan? "yes" : "no") }},
{"params", QJsonArray { privkey, "", rescan }},
};
}
qDebug() << "Importing WIF with rescan=" << rescan;
DEBUG("Importing taddr WIF with rescan=" << rescan);
conn->doRPCWithDefaultErrorHandling(payload, cb);
}
@ -1693,7 +1715,7 @@ void RPC::shutdownHushd() {
d.setWindowTitle("SilentDragonX");
}
QMovie *movie1 = new QMovie(":/img/silentdragon-animated-dark.gif");;
QMovie *movie1 = new QMovie(":/img/silentdragon-animated-startup-dark.gif");;
auto theme = Settings::getInstance()->get_theme_name();
movie1->setScaledSize(QSize(512,512));
connD.topIcon->setMovie(movie1);

2
src/rpc.h

@ -101,6 +101,8 @@ public:
void newZaddr(const std::function<void(QJsonValue)>& cb);
void newTaddr(const std::function<void(QJsonValue)>& cb);
void z_viewtransaction(QString txid, const std::function<void(QJsonValue)>& cb);
void getrawtransaction(QString txid, const std::function<void(QJsonValue)>& cb);
void setGenerate(int proclimit, const std::function<void(QJsonValue)>& cb);
void stopGenerate(int proclimit, const std::function<void(QJsonValue)>& cb);

4
src/sendtab.cpp

@ -693,7 +693,7 @@ bool MainWindow::confirmTx(Tx tx) {
return true;
} else {
return false;
}
}
}
// Send button clicked
@ -720,7 +720,7 @@ void MainWindow::sendButton() {
auto connD = new Ui_ConnectionDialog();
connD->setupUi(d);
QMovie *movie1 = new QMovie(":/img/silentdragon-animated-dark.gif");;
QMovie *movie1 = new QMovie(":/img/silentdragon-animated-startup-dark.gif");;
auto theme = Settings::getInstance()->get_theme_name();
movie1->setScaledSize(QSize(512,512));
connD->topIcon->setMovie(movie1);

31
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;
@ -435,6 +428,30 @@ 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")) {
// 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);

2
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);

3
src/validateaddress.cpp

@ -29,6 +29,9 @@ QVariant ValidateAddressesModel::data(const QModelIndex &index, int role) const
return QVariant();
}
Qt::ItemFlags ValidateAddressesModel::flags(const QModelIndex &index) const {
return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}
QVariant ValidateAddressesModel::headerData(int section, Qt::Orientation orientation, int role) const {
if (role == Qt::DisplayRole && orientation == Qt::Horizontal) {

2
src/validateaddress.h

@ -18,6 +18,8 @@ public:
QVariant data(const QModelIndex &index, int role) const;
QVariant headerData(int section, Qt::Orientation orientation, int role) const;
Qt::ItemFlags flags(const QModelIndex &index) const;
private:
QList<QPair<QString, QString>> props;
QStringList headers;

2
src/version.h

@ -1 +1 @@
#define APP_VERSION "1.4.0"
#define APP_VERSION "1.4.2"

85
src/viewtransaction.ui

@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ViewTransaction</class>
<widget class="QDialog" name="ViewTransaction">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>View Transaction</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="1">
<widget class="QLabel" name="lblHeight">
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Transaction ID (txid):</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QTableView" name="tblProps">
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Close</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>ViewTransaction</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>ViewTransaction</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>
Loading…
Cancel
Save