diff --git a/lib/Cargo.lock b/lib/Cargo.lock index 1aa85ee..1bd2058 100644 --- a/lib/Cargo.lock +++ b/lib/Cargo.lock @@ -1051,7 +1051,7 @@ version = "0.1.0" dependencies = [ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", - "silentdragonlitelib 0.1.0 (git+https://github.com/DenioD/silentdragonlite-cli?rev=a9d235e117da1fdbe3c576d0713b87cfdf97baf6)", + "silentdragonlitelib 0.1.0 (git+https://github.com/DenioD/silentdragonlite-cli?rev=4d9c09433007e71e259b10f15c28ba1c6ac3efae)", ] [[package]] @@ -1467,7 +1467,7 @@ dependencies = [ [[package]] name = "silentdragonlitelib" version = "0.1.0" -source = "git+https://github.com/DenioD/silentdragonlite-cli?rev=a9d235e117da1fdbe3c576d0713b87cfdf97baf6#a9d235e117da1fdbe3c576d0713b87cfdf97baf6" +source = "git+https://github.com/DenioD/silentdragonlite-cli?rev=4d9c09433007e71e259b10f15c28ba1c6ac3efae#4d9c09433007e71e259b10f15c28ba1c6ac3efae" dependencies = [ "base58 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "bellman 0.1.0 (git+https://github.com/DenioD/librustzcash.git?rev=caaee693c47c2ee9ecd1e1546b8fe3c714f342bc)", @@ -2481,7 +2481,7 @@ dependencies = [ "checksum serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)" = "2f72eb2a68a7dc3f9a691bfda9305a1c017a6215e5a4545c258500d2099a37c2" "checksum serde_yaml 0.8.11 (registry+https://github.com/rust-lang/crates.io-index)" = "691b17f19fc1ec9d94ec0b5864859290dff279dbd7b03f017afda54eb36c3c35" "checksum sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b4d8bfd0e469f417657573d8451fb33d16cfe0989359b93baf3a1ffc639543d" -"checksum silentdragonlitelib 0.1.0 (git+https://github.com/DenioD/silentdragonlite-cli?rev=a9d235e117da1fdbe3c576d0713b87cfdf97baf6)" = "" +"checksum silentdragonlitelib 0.1.0 (git+https://github.com/DenioD/silentdragonlite-cli?rev=4d9c09433007e71e259b10f15c28ba1c6ac3efae)" = "" "checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" "checksum smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ab606a9c5e214920bb66c458cd7be8ef094f813f20fe77a54cc7dbfff220d4b7" "checksum sodiumoxide 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "585232e78a4fc18133eef9946d3080befdf68b906c51b621531c37e91787fa2b" diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 69f7e64..fba4348 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -11,4 +11,4 @@ crate-type = ["staticlib"] [dependencies] libc = "0.2.58" lazy_static = "1.4.0" -silentdragonlitelib = { git = "https://github.com/DenioD/silentdragonlite-cli", rev = "a9d235e117da1fdbe3c576d0713b87cfdf97baf6" } \ No newline at end of file +silentdragonlitelib = { git = "https://github.com/DenioD/silentdragonlite-cli", rev = "4d9c09433007e71e259b10f15c28ba1c6ac3efae" } diff --git a/lib/silentdragonlitelib.h b/lib/silentdragonlitelib.h index a9c45fb..4546e70 100644 --- a/lib/silentdragonlitelib.h +++ b/lib/silentdragonlitelib.h @@ -5,13 +5,21 @@ extern "C" { #endif -extern bool litelib_wallet_exists (const char* chain_name); -extern char * litelib_initialze_existing (bool dangerous, const char* server); -extern char * litelib_execute (const char* s, const char* args); -extern void litelib_rust_free_string (char* s); +extern bool litelib_wallet_exists (const char* chain_name); +extern char * litelib_initialize_new (bool dangerous, const char* server); +extern char * litelib_initialize_new_from_phrase + (bool dangerous, const char* server, const char* seed, + unsigned long long birthday); +extern char * litelib_initialize_existing (bool dangerous, const char* server); +extern char * litelib_execute (const char* s, const char* args); +extern void litelib_rust_free_string (char* s); #ifdef __cplusplus } #endif +// This is a function implemented in connection.cpp that will process a string response from +// the litelib and turn into into a QString in a memory-safe way. +QString litelib_process_response(char* resp); + #endif diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 73e8d56..a3df75c 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -17,11 +17,23 @@ lazy_static! { } // Check if there is an existing wallet - - -// Initialize a new lightclient and store its value #[no_mangle] -pub extern fn litelib_initialze_existing(dangerous: bool, server: *const c_char) -> *mut c_char { +pub extern fn litelib_wallet_exists(chain_name: *const c_char) -> bool { + let chain_name_str = unsafe { + assert!(!chain_name.is_null()); + + CStr::from_ptr(chain_name).to_string_lossy().into_owned() + }; + + let config = LightClientConfig::create_unconnected(chain_name_str, None); + + println!("Wallet exists: {}", config.wallet_exists()); + config.wallet_exists() +} + +/// Create a new wallet and return the seed for the newly created wallet. +#[no_mangle] +pub extern fn litelib_initialize_new(dangerous: bool, server: *const c_char) -> *mut c_char { let server_str = unsafe { assert!(!server.is_null()); @@ -38,8 +50,95 @@ pub extern fn litelib_initialze_existing(dangerous: bool, server: *const c_char) }; - - let lightclient = match LightClient::read_from_disk(&config) { + let lightclient = match LightClient::new(&config, latest_block_height) { + Ok(l) => l, + Err(e) => { + let e_str = CString::new(format!("Error: {}", e)).unwrap(); + return e_str.into_raw(); + } + }; + + let seed = match lightclient.do_seed_phrase() { + Ok(s) => s.dump(), + Err(e) => { + let e_str = CString::new(format!("Error: {}", e)).unwrap(); + return e_str.into_raw(); + } + }; + + LIGHTCLIENT.lock().unwrap().replace(Some(lightclient)); + + // Return the wallet's seed + let s_str = CString::new(seed).unwrap(); + return s_str.into_raw(); +} + +/// Restore a wallet from the seed phrase +#[no_mangle] +pub extern fn litelib_initialize_new_from_phrase(dangerous: bool, server: *const c_char, + seed: *const c_char, birthday: u64) -> *mut c_char { + let server_str = unsafe { + assert!(!server.is_null()); + + CStr::from_ptr(server).to_string_lossy().into_owned() + }; + + let seed_str = unsafe { + assert!(!seed.is_null()); + + CStr::from_ptr(seed).to_string_lossy().into_owned() + }; + + let server = LightClientConfig::get_server_or_default(Some(server_str)); + let (config, _latest_block_height) = match LightClientConfig::create(server, dangerous) { + Ok((c, h)) => (c, h), + Err(e) => { + let e_str = CString::new(format!("Error: {}", e)).unwrap(); + return e_str.into_raw(); + } + }; + + let lightclient = match LightClient::new_from_phrase(seed_str, &config, birthday) { + Ok(l) => l, + Err(e) => { + let e_str = CString::new(format!("Error: {}", e)).unwrap(); + return e_str.into_raw(); + } + }; + + let seed = match lightclient.do_seed_phrase() { + Ok(s) => s.dump(), + Err(e) => { + let e_str = CString::new(format!("Error: {}", e)).unwrap(); + return e_str.into_raw(); + } + }; + + LIGHTCLIENT.lock().unwrap().replace(Some(lightclient)); + + let c_str = CString::new("OK").unwrap(); + return c_str.into_raw(); +} + +// Initialize a new lightclient and store its value +#[no_mangle] +pub extern fn litelib_initialize_existing(dangerous: bool, server: *const c_char) -> *mut c_char { + let server_str = unsafe { + assert!(!server.is_null()); + + CStr::from_ptr(server).to_string_lossy().into_owned() + }; + + let server = LightClientConfig::get_server_or_default(Some(server_str)); + let (config, _latest_block_height) = match LightClientConfig::create(server, dangerous) { + Ok((c, h)) => (c, h), + Err(e) => { + let e_str = CString::new(format!("Error: {}", e)).unwrap(); + return e_str.into_raw(); + } + }; + + let lightclient = match LightClient::read_from_disk(&config) { Ok(l) => l, Err(e) => { let e_str = CString::new(format!("Error: {}", e)).unwrap(); diff --git a/silentdragon-lite.pro b/silentdragon-lite.pro index 57f1357..de67ff7 100644 --- a/silentdragon-lite.pro +++ b/silentdragon-lite.pro @@ -36,6 +36,7 @@ UI_DIR = src CONFIG += c++14 SOURCES += \ + src/firsttimewizard.cpp \ src/main.cpp \ src/mainwindow.cpp \ src/balancestablemodel.cpp \ @@ -62,6 +63,7 @@ SOURCES += \ src/liteinterface.cpp HEADERS += \ + src/firsttimewizard.h \ src/mainwindow.h \ src/precompiled.h \ src/balancestablemodel.h \ @@ -91,7 +93,10 @@ HEADERS += \ FORMS += \ src/mainwindow.ui \ src/migration.ui \ + src/newseed.ui \ + src/newwallet.ui \ src/recurringpayments.ui \ + src/restoreseed.ui \ src/settings.ui \ src/about.ui \ src/confirm.ui \ diff --git a/silentdragonlite b/silentdragonlite index 09d9bbe..65a8589 100755 Binary files a/silentdragonlite and b/silentdragonlite differ diff --git a/src/connection.cpp b/src/connection.cpp index 2e56272..fe91567 100644 --- a/src/connection.cpp +++ b/src/connection.cpp @@ -2,9 +2,11 @@ #include "mainwindow.h" #include "settings.h" #include "ui_connection.h" +#include "firsttimewizard.h" #include "ui_createhushconfdialog.h" #include "controller.h" + #include "../lib/silentdragonlitelib.h" #include "precompiled.h" @@ -42,7 +44,15 @@ void ConnectionLoader::doAutoConnect() { // Initialize the library main->logger->write(QObject::tr("Attempting to initialize")); - litelib_initialze_existing(config->dangerous, config->server.toStdString().c_str()); + + // Check to see if there's an existing wallet + if (litelib_wallet_exists(Settings::getChainName().toStdString().c_str())) { + main->logger->write(QObject::tr("Using existing wallet.")); + litelib_initialize_existing(config->dangerous, config->server.toStdString().c_str()); + } else { + main->logger->write(QObject::tr("Create/restore wallet.")); + createOrRestore(config->dangerous, config->server); + } auto connection = makeConnection(config); @@ -54,6 +64,16 @@ void ConnectionLoader::doAutoConnect() { }, [=](auto err) {}); } +void ConnectionLoader::createOrRestore(bool dangerous, QString server) { + // Close the startup dialog, since we'll be showing the wizard + d->hide(); + + // Create a wizard + FirstTimeWizard wizard(dangerous, server); + + wizard.exec(); +} + void ConnectionLoader::doRPCSetConnection(Connection* conn) { rpc->setConnection(conn); @@ -94,15 +114,7 @@ void ConnectionLoader::showError(QString explanation) { d->close(); } - - -/*********************************************************************************** - * Connection, Executor and Callback Class - ************************************************************************************/ -void Executor::run() { - char* resp = litelib_execute(this->cmd.toStdString().c_str(), this->args.toStdString().c_str()); - - // Copy the string, since we need to return this back to rust +QString litelib_process_response(char* resp) { char* resp_copy = new char[strlen(resp) + 1]; strcpy(resp_copy, resp); litelib_rust_free_string(resp); @@ -111,6 +123,17 @@ void Executor::run() { memset(resp_copy, '-', strlen(resp_copy)); delete[] resp_copy; + return reply; +} + +/*********************************************************************************** + * Connection, Executor and Callback Class + ************************************************************************************/ +void Executor::run() { + char* resp = litelib_execute(this->cmd.toStdString().c_str(), this->args.toStdString().c_str()); + + QString reply = litelib_process_response(resp); + qDebug() << "Reply=" << reply; auto parsed = json::parse(reply.toStdString().c_str(), nullptr, false); if (parsed.is_discarded() || parsed.is_null()) { @@ -126,8 +149,6 @@ void Executor::run() { void Callback::processRPCCallback(json resp) { - const bool isGuiThread = QThread::currentThread() == QCoreApplication::instance()->thread(); - qDebug() << "Doing RPC callback: isGUI=" << isGuiThread; this->cb(resp); // Destroy self @@ -135,8 +156,6 @@ void Callback::processRPCCallback(json resp) { } void Callback::processError(QString resp) { - const bool isGuiThread = QThread::currentThread() == QCoreApplication::instance()->thread(); - qDebug() << "Doing RPC callback: isGUI=" << isGuiThread; this->errCb(resp); // Destroy self @@ -158,9 +177,7 @@ void Connection::doRPC(const QString cmd, const QString args, const std::functio return; } - const bool isGuiThread = - QThread::currentThread() == QCoreApplication::instance()->thread(); - qDebug() << "Doing RPC: isGUI=" << isGuiThread; + qDebug() << "Doing RPC: " << cmd; // Create a runner. auto runner = new Executor(cmd, args); diff --git a/src/connection.h b/src/connection.h index f19a5ac..97c8562 100644 --- a/src/connection.h +++ b/src/connection.h @@ -33,6 +33,8 @@ private: void doAutoConnect(); + void createOrRestore(bool dangerous, QString server); + void showError(QString explanation); void showInformation(QString info, QString detail = ""); diff --git a/src/controller.cpp b/src/controller.cpp index 4040a8d..596eaf9 100644 --- a/src/controller.cpp +++ b/src/controller.cpp @@ -417,7 +417,7 @@ void Controller::executeTransaction(Tx tx, std::cout << std::setw(2) << params << std::endl; zrpc->sendTransaction(QString::fromStdString(params.dump()), [=](const json& reply) { - if (reply["result"].is_null() || reply["result"] != "success") { + if (reply.find("txid") == reply.end()) { error("", "Couldn't understand Response: " + QString::fromStdString(reply.dump())); } diff --git a/src/firsttimewizard.cpp b/src/firsttimewizard.cpp new file mode 100644 index 0000000..6d7f387 --- /dev/null +++ b/src/firsttimewizard.cpp @@ -0,0 +1,151 @@ +#include "firsttimewizard.h" + +#include "ui_newseed.h" +#include "ui_restoreseed.h" +#include "ui_newwallet.h" + +#include "../lib/silentdragonlitelib.h" + +using json = nlohmann::json; + +FirstTimeWizard::FirstTimeWizard(bool dangerous, QString server) +{ + setWindowTitle("New wallet wizard"); + this->dangerous = dangerous; + this->server = server; + + // Create the pages + setPage(Page_NewOrRestore, new NewOrRestorePage(this)); + setPage(Page_New, new NewSeedPage(this)); + setPage(Page_Restore,new RestoreSeedPage(this)); +} + +int FirstTimeWizard::nextId() const { + switch (currentId()) { + case Page_NewOrRestore: + if (field("intro.new").toBool()) { + return Page_New; + } else { + return Page_Restore; + } + case Page_New: + case Page_Restore: + default: + return -1; + } +} + +NewOrRestorePage::NewOrRestorePage(FirstTimeWizard *parent) : QWizardPage(parent) { + setTitle("Create or Restore wallet."); + + QWidget* pageWidget = new QWidget(); + Ui_CreateWalletForm form; + form.setupUi(pageWidget); + + // Exclusive buttons + QObject::connect(form.radioNewWallet, &QRadioButton::clicked, [=](bool checked) { + if (checked) { + form.radioRestoreWallet->setChecked(false); + } + }); + + QObject::connect(form.radioRestoreWallet, &QRadioButton::clicked, [=](bool checked) { + if (checked) { + form.radioNewWallet->setChecked(false); + } + }); + form.radioNewWallet->setChecked(true); + + registerField("intro.new", form.radioNewWallet); + + QVBoxLayout *layout = new QVBoxLayout; + layout->addWidget(pageWidget); + setLayout(layout); + setCommitPage(true); + setButtonText(QWizard::CommitButton, "Next"); +} + +NewSeedPage::NewSeedPage(FirstTimeWizard *parent) : QWizardPage(parent) { + this->parent = parent; + + setTitle("Your new wallet"); + + QWidget* pageWidget = new QWidget(); + form.setupUi(pageWidget); + + QVBoxLayout *layout = new QVBoxLayout; + layout->addWidget(pageWidget); + + setLayout(layout); +} + +void NewSeedPage::initializePage() { + // Call the library to create a new wallet. + char* resp = litelib_initialize_new(parent->dangerous, parent->server.toStdString().c_str()); + QString reply = litelib_process_response(resp); + + auto parsed = json::parse(reply.toStdString().c_str(), nullptr, false); + if (parsed.is_discarded() || parsed.is_null() || parsed.find("seed") == parsed.end()) { + form.txtSeed->setPlainText(tr("Error creating a wallet") + "\n" + reply); + } else { + QString seed = QString::fromStdString(parsed["seed"].get()); + form.txtSeed->setPlainText(seed); + } +} + +// Will be called just before closing. Make sure we can save the seed in the wallet +// before we allow the page to be closed +bool NewSeedPage::validatePage() { + char* resp = litelib_execute("save", ""); + QString reply = litelib_process_response(resp); + + auto parsed = json::parse(reply.toStdString().c_str(), nullptr, false); + if (parsed.is_discarded() || parsed.is_null() || parsed.find("result") == parsed.end()) { + QMessageBox::warning(this, tr("Failed to save wallet"), + tr("Couldn't save the wallet") + "\n" + reply, + QMessageBox::Ok); + return false; + } else { + return true; + } +} + + +RestoreSeedPage::RestoreSeedPage(FirstTimeWizard *parent) : QWizardPage(parent) { + this->parent = parent; + + setTitle("Restore wallet from seed"); + + QWidget* pageWidget = new QWidget(); + form.setupUi(pageWidget); + + QVBoxLayout *layout = new QVBoxLayout; + layout->addWidget(pageWidget); + + setLayout(layout); +} + +bool RestoreSeedPage::validatePage() { + // 1. Validate that we do have 24 words + QString seed = form.txtSeed->toPlainText().replace(QRegExp("[ \n\r]+"), " "); + if (seed.trimmed().split(" ").length() != 24) { + QMessageBox::warning(this, tr("Failed to restore wallet"), + tr("SilentDragonLite needs 24 words to restore wallet"), + QMessageBox::Ok); + return false; + } + + // 2. Attempt to restore wallet with the seed phrase + char* resp = litelib_initialize_new_from_phrase(parent->dangerous, parent->server.toStdString().c_str(), + seed.toStdString().c_str(), 0); + QString reply = litelib_process_response(resp); + + if (reply.toUpper().trimmed() != "OK") { + QMessageBox::warning(this, tr("Failed to restore wallet"), + tr("Couldn't restore the wallet") + "\n" + reply, + QMessageBox::Ok); + return false; + } else { + return true; + } +} \ No newline at end of file diff --git a/src/firsttimewizard.h b/src/firsttimewizard.h new file mode 100644 index 0000000..276c0cd --- /dev/null +++ b/src/firsttimewizard.h @@ -0,0 +1,66 @@ +#ifndef FIRSTTIMEWIZARD_H +#define FIRSTTIMEWIZARD_H + +#include "precompiled.h" + +#include "ui_newseed.h" +#include "ui_restoreseed.h" + +class FirstTimeWizard: public QWizard +{ +public: + FirstTimeWizard(bool dangerous, QString server); + +protected: + int nextId() const; + +private: + enum { + Page_NewOrRestore, + Page_New, + Page_Restore + }; + + bool dangerous; + QString server; + + friend class NewOrRestorePage; + friend class NewSeedPage; + friend class RestoreSeedPage; +}; + +class NewOrRestorePage: public QWizardPage { +public: + NewOrRestorePage(FirstTimeWizard* parent); +}; + + +class NewSeedPage: public QWizardPage { +public: + NewSeedPage(FirstTimeWizard* parent); + +protected: + virtual void initializePage(); + virtual bool validatePage(); + +private: + FirstTimeWizard* parent; + Ui_NewSeedForm form; +}; + + +class RestoreSeedPage: public QWizardPage { +public: + RestoreSeedPage(FirstTimeWizard* parent); + +protected: + bool validatePage(); + +private: + FirstTimeWizard* parent; + Ui_RestoreSeedForm form; +}; + + + +#endif // FIRSTTIMEWIZARD_H diff --git a/src/mainwindow.h b/src/mainwindow.h index 4008920..16107f3 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -17,7 +17,7 @@ using json = nlohmann::json; // Struct used to hold destination info when sending a Tx. struct ToFields { QString addr; - double amount; + qint64 amount; QString memo; }; @@ -25,7 +25,7 @@ struct ToFields { struct Tx { QString fromAddr; QList toAddrs; - double fee; + qint64 fee; }; namespace Ui { diff --git a/src/newseed.ui b/src/newseed.ui new file mode 100644 index 0000000..ed5d9ae --- /dev/null +++ b/src/newseed.ui @@ -0,0 +1,63 @@ + + + NewSeedForm + + + + 0 + 0 + 400 + 300 + + + + Form + + + + + + This is your new wallet's seed phrase. PLEASE BACK IT UP SECURELY. + + + true + + + + + + + The seed phrase is the only way to restore the wallet. If you forget the seed phrase, THERE IS NO WAY TO RESTORE YOUR WALLET AND THE FUNDS in it + + + true + + + + + + + + 0 + 0 + + + + + + + + + + true + + + + + + + + + + + diff --git a/src/newwallet.ui b/src/newwallet.ui new file mode 100644 index 0000000..9cf34c5 --- /dev/null +++ b/src/newwallet.ui @@ -0,0 +1,85 @@ + + + CreateWalletForm + + + + 0 + 0 + 400 + 300 + + + + Form + + + + + + + 0 + 0 + + + + + + + + + + Restore wallet from seed + + + + + + + Restore an existing wallet, using the 24-word seed. + + + true + + + + + + + + + + + 0 + 0 + + + + + + + + + + Create a new Wallet + + + + + + + Create a new wallet with a randomly generated seed. + + + true + + + + + + + + + + + diff --git a/src/precompiled.h b/src/precompiled.h index 79c4c8b..1826cda 100644 --- a/src/precompiled.h +++ b/src/precompiled.h @@ -17,6 +17,8 @@ #include #include #include +#include +#include #include #include #include diff --git a/src/restoreseed.ui b/src/restoreseed.ui new file mode 100644 index 0000000..2169bcf --- /dev/null +++ b/src/restoreseed.ui @@ -0,0 +1,53 @@ + + + RestoreSeedForm + + + + 0 + 0 + 400 + 300 + + + + Form + + + + + + Please enter your 24-word seed below + + + true + + + + + + + + 0 + 0 + + + + + + + + + + false + + + + + + + + + + + diff --git a/src/settings.cpp b/src/settings.cpp index 1cd152e..52dbc6b 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -315,7 +315,7 @@ bool Settings::removeFromhushConf(QString confLocation, QString option) { } double Settings::getMinerFee() { - return 0.0001; + return 10000; } double Settings::getZboardAmount() { diff --git a/src/settings.h b/src/settings.h index 63d03ee..6e0c28d 100644 --- a/src/settings.h +++ b/src/settings.h @@ -119,6 +119,8 @@ public: static bool addTohushConf(QString confLocation, QString line); static bool removeFromhushConf(QString confLocation, QString option); + static QString getChainName() { return QString("test"); } + static const QString labelRegExp; static const int updateSpeed = 20 * 1000; // 10 sec