diff --git a/.travis.yml b/.travis.yml index 6cded8f..c3810b7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,7 +25,7 @@ before_install: - sudo add-apt-repository ppa:beineri/opt-qt-5.14.2-xenial -y - sudo apt-get update -qq - sudo apt-get install libsodium-dev pkg-config - - sudo apt-get install qt514base qt514websockets libgl1-mesa-dev + - sudo apt-get install qt514base libgl1-mesa-dev - source /opt/qt514/bin/qt514-env.sh - chmod +x res/libsodium/buildlibsodium.sh diff --git a/README.md b/README.md index b67cb66..2a7b523 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ Compiling can take some time, so be patient and wait for it to finish. It will t ##### Ubuntu 18.04 and 20.04: ```shell script -sudo apt-get -y install build-essential qt5-default qt5-qmake libqt5websockets5-dev qtcreator qttools5-dev-tools +sudo apt-get -y install build-essential qt5-default qt5-qmake qtcreator qttools5-dev-tools git clone https://git.hush.is/hush/SilentDragonLite cd SilentDragonLite ./build.sh linguist diff --git a/doc/win/DEVELOPING-Ubuntu-18-04.md b/doc/win/DEVELOPING-Ubuntu-18-04.md index fca6704..8aa2e54 100644 --- a/doc/win/DEVELOPING-Ubuntu-18-04.md +++ b/doc/win/DEVELOPING-Ubuntu-18-04.md @@ -56,7 +56,7 @@ mkdir ~/git && cd ~/git git clone https://github.com/mxe/mxe.git cd mxe -make -j$(nproc) MXE_TARGETS=x86_64-w64-mingw32.static qtbase qtwebsockets +make -j$(nproc) MXE_TARGETS=x86_64-w64-mingw32.static qtbase ``` # Build SilentDragonLite .exe diff --git a/res/libsodium.a b/res/libsodium.a index 3a24a38..1e89fb7 100644 Binary files a/res/libsodium.a and b/res/libsodium.a differ diff --git a/silentdragon-lite.pro b/silentdragon-lite.pro index 3056c1c..24fe11f 100644 --- a/silentdragon-lite.pro +++ b/silentdragon-lite.pro @@ -13,7 +13,6 @@ CONFIG += precompile_header PRECOMPILED_HEADER = src/precompiled.h QT += widgets -QT += websockets TARGET = SilentDragonLite TEMPLATE = app @@ -59,7 +58,6 @@ SOURCES += \ src/addressbook.cpp \ src/logger.cpp \ src/addresscombo.cpp \ - src/websockets.cpp \ src/mobileappconnector.cpp \ src/recurring.cpp \ src/requestdialog.cpp \ @@ -104,7 +102,6 @@ HEADERS += \ src/addressbook.h \ src/logger.h \ src/addresscombo.h \ - src/websockets.h \ src/mobileappconnector.h \ src/recurring.h \ src/requestdialog.h \ diff --git a/src/connection.cpp b/src/connection.cpp index 48f2135..9f6ec58 100644 --- a/src/connection.cpp +++ b/src/connection.cpp @@ -9,6 +9,7 @@ #include "controller.h" #include "../lib/silentdragonlitelib.h" #include "precompiled.h" +#include using json = nlohmann::json; diff --git a/src/connection.h b/src/connection.h index 431cd38..784f1bc 100644 --- a/src/connection.h +++ b/src/connection.h @@ -6,6 +6,7 @@ #include "mainwindow.h" #include "ui_connection.h" #include "precompiled.h" +#include using json = nlohmann::json; diff --git a/src/controller.cpp b/src/controller.cpp index 61660cd..61cbd44 100644 --- a/src/controller.cpp +++ b/src/controller.cpp @@ -7,7 +7,6 @@ #include "settings.h" #include "version.h" #include "camount.h" -#include "websockets.h" #include "Model/ChatItem.h" #include "DataStore/DataStore.h" @@ -896,9 +895,6 @@ void Controller::refreshBalances() model->setBalVerified(balVerified); model->setBalSpendable(balSpendable); - // This is for the websockets - AppDataModel::getInstance()->setBalances(balT, balZ); - // This is for the datamodel CAmount balAvailable = balT + balVerified; model->setAvailableBalance(balAvailable); diff --git a/src/datamodel.h b/src/datamodel.h index 0a1cf6d..4c32bdf 100644 --- a/src/datamodel.h +++ b/src/datamodel.h @@ -5,7 +5,7 @@ #include "camount.h" #include "precompiled.h" - +#include struct UnspentOutput { QString address; diff --git a/src/main.cpp b/src/main.cpp index 347ee2a..3f91846 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,6 +6,7 @@ #include "mainwindow.h" #include "controller.h" #include "settings.h" +#include #include "version.h" diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 4bd099d..7f2c785 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -25,7 +25,6 @@ #include "ui_startupencryption.h" #include "ui_removeencryption.h" #include "ui_seedrestore.h" -#include "websockets.h" #include "sodium.h" #include "sodium/crypto_generichash_blake2b.h" #include @@ -305,16 +304,6 @@ MainWindow::MainWindow(QWidget *parent) : restoreSavedStates(); - if (AppDataServer::getInstance()->isAppConnected()) { - qDebug() << __func__ << ": app is connected to wormhole"; - auto ads = AppDataServer::getInstance(); - - QString wormholecode = ""; - if (ads->getAllowInternetConnection()) - wormholecode = ads->getWormholeCode(ads->getSecretHex()); - - createWebsocket(wormholecode); - } } bool MainWindow::fileExists(QString path) @@ -323,36 +312,6 @@ bool MainWindow::fileExists(QString path) return (check_file.exists() && check_file.isFile()); } -void MainWindow::createWebsocket(QString wormholecode) { - qDebug() << "Listening for app connections on port 8777"; - // Create the websocket server, for listening to direct connections - wsserver = new WSServer(8777, false, this); - - if (!wormholecode.isEmpty()) { - // Connect to the wormhole service - wormhole = new WormholeClient(this, wormholecode); - } -} - -void MainWindow::stopWebsocket() { - delete wsserver; - wsserver = nullptr; - - delete wormhole; - wormhole = nullptr; - - qDebug() << "Websockets for app connections shut down"; -} - -bool MainWindow::isWebsocketListening() { - return wsserver != nullptr; -} - -void MainWindow::replaceWormholeClient(WormholeClient* newClient) { - delete wormhole; - wormhole = newClient; -} - void MainWindow::restoreSavedStates() { QSettings s; restoreGeometry(s.value("geometry").toByteArray()); @@ -2735,8 +2694,6 @@ MainWindow::~MainWindow() delete loadingMovie; delete logger; - delete wsserver; - delete wormhole; } void MainWindow::on_givemeZaddr_clicked() { diff --git a/src/mainwindow.h b/src/mainwindow.h index 6a2486e..45466a9 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -14,8 +14,6 @@ using json = nlohmann::json; // Forward declare to break circular dependency. class Controller; class Settings; -class WSServer; -class WormholeClient; class ChatModel; @@ -70,10 +68,6 @@ public: - void replaceWormholeClient(WormholeClient* newClient); - bool isWebsocketListening(); - void createWebsocket(QString wormholecode); - void stopWebsocket(); void saveContact(); void saveandsendContact(); void showRequesthush(); @@ -200,10 +194,6 @@ private: bool uiPaymentsReady = false; QString pendingURIPayment; - WSServer* wsserver = nullptr; - WormholeClient* wormhole = nullptr; - - Controller* rpc = nullptr; QCompleter* labelCompleter = nullptr; diff --git a/src/precompiled.h b/src/precompiled.h index 25fa01f..f358b8b 100644 --- a/src/precompiled.h +++ b/src/precompiled.h @@ -64,7 +64,6 @@ #include #include #include -#include #include #include #include diff --git a/src/scripts/docker/Dockerfile b/src/scripts/docker/Dockerfile index 361d2fe..6391c24 100644 --- a/src/scripts/docker/Dockerfile +++ b/src/scripts/docker/Dockerfile @@ -40,7 +40,7 @@ RUN cd /opt && rm qt-everywhere-src-5.11.2.tar.xz && rm -rf qt-everywhere-src-5. RUN cd /opt && \ git clone https://github.com/mxe/mxe.git && \ cd /opt/mxe && \ - make -j$(nproc) MXE_TARGETS=x86_64-w64-mingw32.static qtbase qtwebsockets + make -j$(nproc) MXE_TARGETS=x86_64-w64-mingw32.static qtbase # Add rust RUN apt install -y gcc-aarch64-linux-gnu diff --git a/src/settings.cpp b/src/settings.cpp index 3665053..db098e5 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -4,6 +4,7 @@ #include "settings.h" #include "camount.h" #include "../lib/silentdragonlitelib.h" +#include Settings* Settings::instance = nullptr; diff --git a/src/websockets.cpp b/src/websockets.cpp deleted file mode 100644 index e09d1b0..0000000 --- a/src/websockets.cpp +++ /dev/null @@ -1,940 +0,0 @@ -// Copyright 2019-2023 The Hush developers -// Released under the GPLv3 -#include "websockets.h" -#include "controller.h" -#include "settings.h" -#include "ui_mobileappconnector.h" -#include "version.h" - -// Weap the sendTextMessage to check if the connection is valid and that the parent WebServer didn't close this connection -// for some reason. -void ClientWebSocket::sendTextMessage(QString m) { - if (client) { - if (server && !server->isValidConnection(client)) { - return; - } - - if (client->isValid()) - client->sendTextMessage(m); - } -} - -WSServer::WSServer(quint16 port, bool debug, QObject *parent) : - QObject(parent), - m_pWebSocketServer(new QWebSocketServer(QStringLiteral("Direct Connection Server"), - QWebSocketServer::NonSecureMode, this)), - m_debug(debug) -{ - m_mainWindow = (MainWindow *) parent; - if (m_pWebSocketServer->listen(QHostAddress::AnyIPv4, port)) { - if (m_debug) - qDebug() << "Echoserver listening on port" << port; - connect(m_pWebSocketServer, &QWebSocketServer::newConnection, - this, &WSServer::onNewConnection); - connect(m_pWebSocketServer, &QWebSocketServer::closed, this, &WSServer::closed); - } -} - -WSServer::~WSServer() -{ - qDebug() << "Closing websocket server"; - m_pWebSocketServer->close(); - qDeleteAll(m_clients.begin(), m_clients.end()); - qDebug() << "Deleted all websocket clients"; -} - -void WSServer::onNewConnection() -{ - qDebug() << "Websocket server: new connection"; - QWebSocket *pSocket = m_pWebSocketServer->nextPendingConnection(); - - connect(pSocket, &QWebSocket::textMessageReceived, this, &WSServer::processTextMessage); - connect(pSocket, &QWebSocket::binaryMessageReceived, this, &WSServer::processBinaryMessage); - connect(pSocket, &QWebSocket::disconnected, this, &WSServer::socketDisconnected); - - m_clients << pSocket; -} - -void WSServer::processTextMessage(QString message) -{ - QWebSocket *pClient = qobject_cast(sender()); - if (m_debug) - qDebug() << "Message received:" << message; - - if (pClient) { - std::shared_ptr client = std::make_shared(pClient, this); - AppDataServer::getInstance()->processMessage(message, m_mainWindow, client, AppConnectionType::DIRECT); - } -} - -void WSServer::processBinaryMessage(QByteArray message) -{ - //QWebSocket *pClient = qobject_cast(sender()); - if (m_debug) - qDebug() << "Binary Message received:" << message; - -} - -void WSServer::socketDisconnected() -{ - QWebSocket *pClient = qobject_cast(sender()); - if (m_debug) - qDebug() << "socketDisconnected:" << pClient; - if (pClient) { - m_clients.removeAll(pClient); - pClient->deleteLater(); - } -} - -//=============================== -// WormholeClient -//=============================== -WormholeClient::WormholeClient(MainWindow* p, QString wormholeCode) { - this->parent = p; - this->code = wormholeCode; - connect(); - qDebug() << "New wormhole client after connect()"; -} - -WormholeClient::~WormholeClient() { - qDebug() << "WormholeClient destructor"; - shuttingDown = true; - - if (m_webSocket && m_webSocket->isValid()) { - qDebug() << "Wormhole closing!"; - m_webSocket->close(); - - } - - if (timer) { - qDebug() << "Wormhole timer stopping"; - timer->stop(); - } - - delete timer; - qDebug() << "Wormhole timer deleted"; -} - -void WormholeClient::connect() { - qDebug() << "Wormhole::connect"; - delete m_webSocket; - m_webSocket = new QWebSocket(); - - if (m_webSocket) { - QObject::connect(m_webSocket, &QWebSocket::connected, this, &WormholeClient::onConnected); - QObject::connect(m_webSocket, &QWebSocket::disconnected, this, &WormholeClient::closed); - } else { - qDebug() << "Invalid websocket object!"; - } - - m_webSocket->open(QUrl("wss://wormhole.hush.is:443")); - //m_webSocket->open(QUrl("ws://127.0.0.1:7070")); -} - -void WormholeClient::retryConnect() { - QTimer::singleShot(5 * 1000 * pow(2, retryCount), [=]() { - if (retryCount < 10) { - qDebug() << "Retrying websocket connection"; - this->retryCount++; - connect(); - } else { - qDebug() << "Retry count exceeded, will not attempt retry any more"; - } - }); -} - -// Called when the websocket is closed. If this was closed without our explicitly closing it, -// then we need to try and reconnect -void WormholeClient::closed() { - if (!shuttingDown) { - retryConnect(); - } -} - -void WormholeClient::onConnected() -{ - qDebug() << "WebSocket connected"; - retryCount = 0; - qDebug() << "WebSocket connected, retryCount=" << retryCount; - QObject::connect(m_webSocket, &QWebSocket::textMessageReceived, - this, &WormholeClient::onTextMessageReceived); - - auto payload = QJsonDocument( QJsonObject { - {"register", code} - }).toJson(); - - m_webSocket->sendTextMessage(payload); - - // On connected, we'll also create a timer to ping it every 4 minutes, since the websocket - // will timeout after 5 minutes - if (m_webSocket && m_webSocket->isValid()) { - m_webSocket->sendTextMessage(payload); - qDebug() << "Sent registration message with code=" << code; - - // On connected, we'll also create a timer to ping it every 4 minutes, since the websocket - // will timeout after 5 minutes - timer = new QTimer(parent); - qDebug() << "Created QTimer"; - QObject::connect(timer, &QTimer::timeout, [=]() { - qDebug() << "Timer timeout!"; - if (!shuttingDown && m_webSocket && m_webSocket->isValid()) { - auto payload = QJsonDocument(QJsonObject { {"ping", "ping"} }).toJson(); - qint64 bytes = m_webSocket->sendTextMessage(payload); - qDebug() << "Sent ping, " << bytes << " bytes"; - } - }); - unsigned int interval = 4*60*1000; - timer->start(interval); // 4 minutes - qDebug() << "Started timer with interval=" << interval; - } else { - qDebug() << "Invalid websocket object onConnected!"; - } -} - -void WormholeClient::onTextMessageReceived(QString message) -{ - AppDataServer::getInstance()->processMessage(message, parent, std::make_shared(m_webSocket), AppConnectionType::INTERNET); - qDebug() << "Destroyed tempWormholeClient and ui"; -} - - -// ============================== -// AppDataServer -// ============================== -AppDataServer* AppDataServer::instance = nullptr; - -QString AppDataServer::getWormholeCode(QString secretHex) { - unsigned char* secret = new unsigned char[crypto_secretbox_KEYBYTES]; - sodium_hex2bin(secret, crypto_secretbox_KEYBYTES, secretHex.toStdString().c_str(), crypto_secretbox_KEYBYTES*2, - NULL, NULL, NULL); - - unsigned char* out1 = new unsigned char[crypto_hash_sha256_BYTES]; - crypto_hash_sha256(out1, secret, crypto_secretbox_KEYBYTES); - - unsigned char* out2 = new unsigned char[crypto_hash_sha256_BYTES]; - crypto_hash_sha256(out2, out1, crypto_hash_sha256_BYTES); - - char* wmcode = new char[crypto_hash_sha256_BYTES*2 + 1]; - sodium_bin2hex(wmcode, crypto_hash_sha256_BYTES*2 + 1, out2, crypto_hash_sha256_BYTES); - - QString wmcodehex(wmcode); - - delete[] wmcode; - delete[] out2; - delete[] out1; - delete[] secret; - - return wmcodehex; -} - -QString AppDataServer::getSecretHex() { - QSettings s; - - return s.value("mobileapp/secret", "").toString(); -} - -void AppDataServer::saveNewSecret(QString secretHex) { - QSettings().setValue("mobileapp/secret", secretHex); - - if (secretHex.isEmpty()) - setAllowInternetConnection(false); -} - -bool AppDataServer::getAllowInternetConnection() { - return QSettings().value("mobileapp/allowinternet", false).toBool(); -} - -void AppDataServer::setAllowInternetConnection(bool allow) { - QSettings().setValue("mobileapp/allowinternet", allow); -} - -void AppDataServer::saveLastConnectedOver(AppConnectionType type) { - QSettings().setValue("mobileapp/lastconnectedover", type); -} - -AppConnectionType AppDataServer::getLastConnectionType() { - return (AppConnectionType) QSettings().value("mobileapp/lastconnectedover", AppConnectionType::DIRECT).toInt(); -} - -void AppDataServer::saveLastSeenTime() { - QSettings().setValue("mobileapp/lastseentime", QDateTime::currentSecsSinceEpoch()); -} - -QDateTime AppDataServer::getLastSeenTime() { - return QDateTime::fromSecsSinceEpoch(QSettings().value("mobileapp/lastseentime", 0).toLongLong()); -} - -void AppDataServer::setConnectedName(QString name) { - QSettings().setValue("mobileapp/connectedname", name); -} - -QString AppDataServer::getConnectedName() { - return QSettings().value("mobileapp/connectedname", "").toString(); -} - -bool AppDataServer::isAppConnected() { - return !getConnectedName().isEmpty() && - getLastSeenTime().daysTo(QDateTime::currentDateTime()) < 14; -} - -void AppDataServer::connectAppDialog(MainWindow* parent) { - QDialog d(parent); - ui = new Ui_MobileAppConnector(); - ui->setupUi(&d); - Settings::saveRestore(&d); - - updateUIWithNewQRCode(parent); - updateConnectedUI(); - - QObject::connect(ui->btnDisconnect, &QPushButton::clicked, [=] () { - QSettings().setValue("mobileapp/connectedname", ""); - saveNewSecret(""); - - updateConnectedUI(); - }); - - QObject::connect(ui->txtConnStr, &QLineEdit::cursorPositionChanged, [=](int, int) { - ui->txtConnStr->selectAll(); - }); - - QObject::connect(ui->chkInternetConn, &QCheckBox::stateChanged, [=] (int state) { - if (state == Qt::Checked) { - - } - updateUIWithNewQRCode(parent); - }); - - // If we're not listening for the app, then start the websockets - if (!parent->isWebsocketListening()) { - QString wormholecode = ""; - if (getAllowInternetConnection()) - wormholecode = AppDataServer::getInstance()->getWormholeCode(AppDataServer::getInstance()->getSecretHex()); - - parent->createWebsocket(wormholecode); - } - - d.exec(); - - // If there is nothing connected when the dialog exits, then shutdown the websockets - if (!isAppConnected()) { - parent->stopWebsocket(); - } - - // Cleanup - tempSecret = ""; - - delete tempWormholeClient; - tempWormholeClient = nullptr; - - delete ui; - ui = nullptr; -} - -void AppDataServer::updateUIWithNewQRCode(MainWindow* mainwindow) { - // Get the address of the localhost - auto addrList = QNetworkInterface::allAddresses(); - - // Find a suitable address - QString ipv4Addr; - for (auto addr : addrList) { - if (addr.isLoopback() || addr.protocol() == QAbstractSocket::IPv6Protocol) - continue; - - ipv4Addr = addr.toString(); - break; - } - - if (ipv4Addr.isEmpty()) - return; - - QString uri = "ws://" + ipv4Addr + ":8777"; - qDebug() << "Websocket URI: " << uri; - - // Get a new secret - unsigned char* secretBin = new unsigned char[crypto_secretbox_KEYBYTES]; - randombytes_buf(secretBin, crypto_secretbox_KEYBYTES); - char* secretHex = new char[crypto_secretbox_KEYBYTES*2 + 1]; - sodium_bin2hex(secretHex, crypto_secretbox_KEYBYTES*2+1, secretBin, crypto_secretbox_KEYBYTES); - - QString secretStr(secretHex); - QString codeStr = uri + "," + secretStr; - - if (ui->chkInternetConn->isChecked()) { - codeStr = codeStr + ",1"; - } - - registerNewTempSecret(secretStr, ui->chkInternetConn->isChecked(), mainwindow); - - ui->qrcode->setQrcodeString(codeStr); - ui->txtConnStr->setText(codeStr); - qDebug() << "New QR="<lblRemoteName->setText(remoteName.isEmpty() ? "(Not connected to any device)" : remoteName); - ui->lblLastSeen->setText(remoteName.isEmpty() ? "" : getLastSeenTime().toString(Qt::SystemLocaleLongDate)); - ui->lblConnectionType->setText(remoteName.isEmpty() ? "" : connDesc(getLastConnectionType())); - - ui->btnDisconnect->setEnabled(!remoteName.isEmpty()); -} - -QString AppDataServer::getNonceHex(NonceType nt) { - QSettings s; - QString hex; - if (nt == NonceType::LOCAL) { - // The default local nonce starts from 1, to always keep it odd - auto defaultLocalNonce = "01" + QString("00").repeated(crypto_secretbox_NONCEBYTES-1); - hex = s.value("mobileapp/localnoncehex", defaultLocalNonce).toString(); - } - else { - hex = s.value("mobileapp/remotenoncehex", QString("00").repeated(crypto_secretbox_NONCEBYTES)).toString(); - } - return hex; -} - -void AppDataServer::saveNonceHex(NonceType nt, QString noncehex) { - QSettings s; - assert(noncehex.length() == crypto_secretbox_NONCEBYTES * 2); - if (nt == NonceType::LOCAL) { - s.setValue("mobileapp/localnoncehex", noncehex); - } - else { - s.setValue("mobileapp/remotenoncehex", noncehex); - } - s.sync(); -} - -// Encrypt an outgoing message with the stored secret key. -QString AppDataServer::encryptOutgoing(QString msg) { - int padding = 16*1024; - qDebug() << "Encrypt msg(pad="< ((int)crypto_secretbox_NONCEBYTES * 2) || - encryptedhex.length() > 2 * 50 * 1024 /*50kb*/) { - return "error"; - } - - // Check to make sure that the nonce is greater than the last known remote nonce - unsigned char* lastRemoteBin = new unsigned char[crypto_secretbox_NONCEBYTES]; - sodium_hex2bin(lastRemoteBin, crypto_secretbox_NONCEBYTES, lastRemoteNonceHex.toStdString().c_str(), lastRemoteNonceHex.length(), - NULL, NULL, NULL); - - unsigned char* noncebin = new unsigned char[crypto_secretbox_NONCEBYTES]; - sodium_hex2bin(noncebin, crypto_secretbox_NONCEBYTES, noncehex.toStdString().c_str(), noncehex.length(), - NULL, NULL, NULL); - - assert(crypto_secretbox_KEYBYTES == crypto_hash_sha256_BYTES); - if (sodium_compare(lastRemoteBin, noncebin, crypto_secretbox_NONCEBYTES) != -1) { - // Refuse to accept a lower nonce, return an error - delete[] lastRemoteBin; - delete[] noncebin; - return "error"; - } - - unsigned char* secret = new unsigned char[crypto_secretbox_KEYBYTES]; - sodium_hex2bin(secret, crypto_secretbox_KEYBYTES, secretHex.toStdString().c_str(), crypto_secretbox_KEYBYTES*2, - NULL, NULL, NULL); - - unsigned char* encrypted = new unsigned char[encryptedhex.length() / 2]; - sodium_hex2bin(encrypted, encryptedhex.length() / 2, encryptedhex.toStdString().c_str(), encryptedhex.length(), - NULL, NULL, NULL); - - int decryptedLen = encryptedhex.length() / 2 - crypto_secretbox_MACBYTES; - unsigned char* decrypted = new unsigned char[decryptedLen]; - int result = crypto_secretbox_open_easy(decrypted, encrypted, encryptedhex.length() / 2, noncebin, secret); - - QString payload; - if (result == -1) { - payload = "error"; - } else { - // Update the last seen remote hex - saveNonceHex(NonceType::REMOTE, noncehex); - saveLastSeenTime(); - - char* decryptedStr = new char[decryptedLen + 1]; - sodium_memzero(decryptedStr, decryptedLen + 1); - memcpy(decryptedStr, decrypted, decryptedLen); - - payload = QString(decryptedStr); - - delete[] decryptedStr; - } - - delete[] secret; - delete[] lastRemoteBin; - delete[] noncebin; - delete[] encrypted; - delete[] decrypted; - - qDebug() << "Returning decrypted payload="< pClient, AppConnectionType connType) { - qDebug() << "processMessage message"; - //qDebug() << "processMessage message=" << message; // this can log sensitive info - auto replyWithError = [=]() { - auto r = QJsonDocument(QJsonObject{ - {"error", "Encryption error"}, - {"to", getWormholeCode(getSecretHex())} - }).toJson(); - pClient->sendTextMessage(r); - return; - }; - - // First, extract the command from the message - auto msg = QJsonDocument::fromJson(message.toUtf8()); - - // Check if we got an error from the websocket - if (msg.object().contains("error")) { - qDebug() << "Error:" << msg.toJson(); - return; - } - - // If the message is a ping, just ignore it - if (msg.object().contains("ping")) { - return; - } - - // Then, check if the message is encrpted - if (!msg.object().contains("nonce")) { - replyWithError(); - return; - } - - auto decrypted = decryptMessage(msg, getSecretHex(), getNonceHex(NonceType::REMOTE)); - - // If the decryption failed, maybe this is a new connection, so see if the dialog is open and a - // temp secret is in place - if (decrypted == "error") { - // If the dialog is open, then there might be a temporary, new secret key. Attempt to decrypt - // with that. - if (!tempSecret.isEmpty()) { - // Since this is a temp secret, the last seen nonce will be "0", so basically we'll accept any nonce - QString zeroNonce = QString("00").repeated(crypto_secretbox_NONCEBYTES); - decrypted = decryptMessage(msg, tempSecret, zeroNonce); - if (decrypted == "error") { - // Oh, well. Just return an error - replyWithError(); - return; - } - else { - // This is a new connection. So, update the the secret. Note the last seen remote nonce has already been updated by - // decryptMessage() - saveNewSecret(tempSecret); - setAllowInternetConnection(tempWormholeClient != nullptr); - - // Swap out the wormhole connection - mainWindow->replaceWormholeClient(tempWormholeClient); - tempWormholeClient = nullptr; - - saveLastConnectedOver(connType); - processDecryptedMessage(decrypted, mainWindow, pClient); - - // If the Connection UI is showing, we have to update the UI as well - if (ui != nullptr) { - // Update the connected phone information - updateConnectedUI(); - - // Update with a new QR Code for safety, so this secret isn't used by anyone else - updateUIWithNewQRCode(mainWindow); - } - - return; - } - } - else { - replyWithError(); - return; - } - } else { - saveLastConnectedOver(connType); - processDecryptedMessage(decrypted, mainWindow, pClient); - return; - } -} - -// Decrypted method will be executed here. -void AppDataServer::processDecryptedMessage(QString message, MainWindow* mainWindow, std::shared_ptr pClient) { - //qDebug() << "processDecryptedMessage message=" << message; - // First, extract the command from the message - auto msg = QJsonDocument::fromJson(message.toUtf8()); - - if (!msg.object().contains("command")) { - auto r = QJsonDocument(QJsonObject{ - {"errorCode", -1}, - {"errorMessage", "Unknown JSON format"} - }).toJson(); - pClient->sendTextMessage(encryptOutgoing(r)); - return; - } - - if (msg.object()["command"] == "getInfo") { - processGetInfo(msg.object(), mainWindow, pClient); - } - else if (msg.object()["command"] == "getTransactions") { - processGetTransactions(mainWindow, pClient); - } - else if (msg.object()["command"] == "sendTx") { - processSendTx(msg.object()["tx"].toObject(), mainWindow, pClient); - } - else if (msg.object()["command"] == "sendmanyTx") { - processSendManyTx(msg.object()["tx"].toObject(), mainWindow, pClient); - } - else { - auto r = QJsonDocument(QJsonObject{ - {"errorCode", -1}, - {"errorMessage", "Command not found:" + msg.object()["command"].toString()} - }).toJson(); - pClient->sendTextMessage(encryptOutgoing(r)); - } -} - -// "sendTx" command. This method will actually send money, so be careful with everything -void AppDataServer::processSendTx(QJsonObject sendTx, MainWindow* mainwindow, std::shared_ptr pClient) { - qDebug() << "processSendTx with to=" << sendTx["to"].toString(); - auto error = [=](QString reason) { - auto r = QJsonDocument(QJsonObject{ - {"errorCode", -1}, - {"errorMessage", "Couldn't send Tx:" + reason} - }).toJson(); - pClient->sendTextMessage(encryptOutgoing(r)); - return; - }; - - // Refuse to send if the node is still syncing - if (Settings::getInstance()->isSyncing()) { - error(QObject::tr("Node is still syncing.")); - return; - } - - // Create a Tx Object - Tx tx; - tx.fee = Settings::getMinerFee(); - - // Find a from address that has at least the sending amout - CAmount amt = CAmount::fromDecimalString(sendTx["amount"].toString()); - auto allBalances = mainwindow->getRPC()->getModel()->getAllBalances(); - QList> bals; - for (auto i : allBalances.keys()) { - // Filter out balances that don't have the requisite amount - if (allBalances.value(i) < amt) - continue; - - bals.append(QPair(i, allBalances.value(i))); - } - - if (bals.isEmpty()) { - error(QObject::tr("No sapling or transparent addresses with enough balance to spend.")); - return; - } - - std::sort(bals.begin(), bals.end(), [=](const QPaira, const QPair b) -> bool { - // Sort z addresses first - return a.first > b.first; - }); - - tx.fromAddr = bals[0].first; - tx.toAddrs = { ToFields{ sendTx["to"].toString(), amt, sendTx["memo"].toString()} }; - - // TODO: Respect the autoshield change setting - - QString validation = mainwindow->doSendTxValidations(tx); - if (!validation.isEmpty()) { - error(validation); - return; - } - - json params = json::array(); - mainwindow->getRPC()->fillTxJsonParams(params, tx); - std::cout << std::setw(2) << params << std::endl; - - // And send the Tx - mainwindow->getRPC()->executeTransaction(tx, - [=] (QString txid) { - auto r = QJsonDocument(QJsonObject{ - {"version", 1.0}, - {"command", "sendTxSubmitted"}, - {"txid", txid} - }).toJson(); - pClient->sendTextMessage(encryptOutgoing(r)); - }, - // Errored while submitting Tx - [=] (QString, QString errStr) { - auto r = QJsonDocument(QJsonObject{ - {"version", 1.0}, - {"command", "sendTxFailed"}, - {"err", errStr} - }).toJson(); - pClient->sendTextMessage(encryptOutgoing(r)); - } - ); - - auto r = QJsonDocument(QJsonObject{ - {"version", 1.0}, - {"command", "sendTx"}, - {"result", "success"} - }).toJson(); - pClient->sendTextMessage(encryptOutgoing(r)); -} - -// "sendmanyTx" command. This method will actually send money, so be careful with everything -void AppDataServer::processSendManyTx(QJsonObject sendmanyTx, MainWindow* mainwindow, std::shared_ptr pClient) { - qDebug() << "processSendManyTx with to=" << sendmanyTx["to"].toString(); - auto error = [=](QString reason) { - auto r = QJsonDocument(QJsonObject{ - {"errorCode", -1}, - {"errorMessage", "Couldn't send Tx:" + reason} - }).toJson(); - pClient->sendTextMessage(encryptOutgoing(r)); - return; - }; - - // Refuse to send if the node is still syncing - if (Settings::getInstance()->isSyncing()) { - error(QObject::tr("Node is still syncing.")); - return; - } - - // Create a Tx Object - Tx tx; - tx.fee = Settings::getMinerFee(); - - // Find a from address that has at least the sending amout - CAmount amt = CAmount::fromDecimalString(sendmanyTx["amount"].toString()); - auto allBalances = mainwindow->getRPC()->getModel()->getAllBalances(); - QList> bals; - for (auto i : allBalances.keys()) { - // Filter out balances that don't have the requisite amount - if (allBalances.value(i) < amt) - continue; - - bals.append(QPair(i, allBalances.value(i))); - } - - if (bals.isEmpty()) { - error(QObject::tr("No sapling or transparent addresses with enough balance to spend.")); - return; - } - - std::sort(bals.begin(), bals.end(), [=](const QPaira, const QPair b) -> bool { - // Sort z addresses first - return a.first > b.first; - }); - - //send to more then one Receipent - - int totalSendManyItems = sendmanyTx.size(); - for (int i=0; i < totalSendManyItems; i++) { - - amt = CAmount::fromDecimalString(sendmanyTx["amount"].toString() % QString::number(i+1)); - QString addr = sendmanyTx["to"].toString() % QString::number(i+1); - QString memo = sendmanyTx["memo"].toString() % QString::number(i+1); - - tx.fromAddr = bals[0].first; - tx.toAddrs = { ToFields{ addr, amt, memo} }; -} - // TODO: Respect the autoshield change setting - - QString validation = mainwindow->doSendTxValidations(tx); - if (!validation.isEmpty()) { - error(validation); - return; - } - - json params = json::array(); - mainwindow->getRPC()->fillTxJsonParams(params, tx); - std::cout << std::setw(2) << params << std::endl; - - // And send the Tx - mainwindow->getRPC()->executeTransaction(tx, - [=] (QString txid) { - auto r = QJsonDocument(QJsonObject{ - {"version", 1.0}, - {"command", "sendTxSubmitted"}, - {"txid", txid} - }).toJson(); - pClient->sendTextMessage(encryptOutgoing(r)); - }, - // Errored while submitting Tx - [=] (QString, QString errStr) { - auto r = QJsonDocument(QJsonObject{ - {"version", 1.0}, - {"command", "sendTxFailed"}, - {"err", errStr} - }).toJson(); - pClient->sendTextMessage(encryptOutgoing(r)); - } - ); - - auto r = QJsonDocument(QJsonObject{ - {"version", 1.0}, - {"command", "sendTx"}, - {"result", "success"} - }).toJson(); - pClient->sendTextMessage(encryptOutgoing(r)); -} - - -// "getInfo" command -void AppDataServer::processGetInfo(QJsonObject jobj, MainWindow* mainWindow, std::shared_ptr pClient) { - auto connectedName = jobj["name"].toString(); - - if (mainWindow == nullptr || mainWindow->getRPC() == nullptr) { - pClient->close(QWebSocketProtocol::CloseCodeNormal, "Not yet ready"); - return; - } - - // Max spendable safely from a z address and from any address - CAmount maxZSpendable; - CAmount maxSpendable; - for (auto a : mainWindow->getRPC()->getModel()->getAllBalances().keys()) { - if (Settings::getInstance()->isSaplingAddress(a)) { - if (mainWindow->getRPC()->getModel()->getAllBalances().value(a) > maxZSpendable) { - maxZSpendable = mainWindow->getRPC()->getModel()->getAllBalances().value(a); - } - } - if (mainWindow->getRPC()->getModel()->getAllBalances().value(a) > maxSpendable) { - maxSpendable = mainWindow->getRPC()->getModel()->getAllBalances().value(a); - } - } - - setConnectedName(connectedName); - - auto r = QJsonDocument(QJsonObject { - {"version", 1.0}, - {"command", "getInfo"}, - {"saplingAddress", mainWindow->getRPC()->getDefaultSaplingAddress()}, - {"tAddress", mainWindow->getRPC()->getDefaultTAddress()}, - {"balance", AppDataModel::getInstance()->getTotalBalance().toDecimalDouble()}, - {"maxspendable", maxSpendable.toDecimalDouble()}, - {"maxzspendable", maxZSpendable.toDecimalDouble()}, - {"tokenName", Settings::getTokenName()}, - // changing this to hushprice is a backward incompatible change that requires - // changing SDL, litewalletd and SDA in unison, and would break older clients - // so we just leave it for now - {"zecprice", Settings::getInstance()->getHUSHPrice()}, - {"serverversion", QString(APP_VERSION)} - }).toJson(); - pClient->sendTextMessage(encryptOutgoing(r)); -} - -void AppDataServer::processGetTransactions(MainWindow* mainWindow, std::shared_ptr pClient) { - QJsonArray txns; - auto model = mainWindow->getRPC()->getTransactionsModel(); - qDebug() << "processGetTransactions"; - - - // Add transactions - for (int i = 0; i < model->rowCount(QModelIndex()) && i < Settings::getMaxMobileAppTxns(); i++) { - txns.append(QJsonObject{ - {"type", model->getType(i)}, - {"datetime", model->getDate(i)}, - {"amount", model->getAmt(i)}, - {"txid", model->getTxId(i)}, - {"address", model->getAddr(i)}, - // {"memo", model->getMemo(i)}, - {"confirmations", model->getConfirmations(i)} - }); - } - - auto r = QJsonDocument(QJsonObject{ - {"version", 1.0}, - {"command", "getTransactions"}, - {"transactions", txns} - }).toJson(); - pClient->sendTextMessage(encryptOutgoing(r)); -} - -// ============================== -// AppDataModel -// ============================== -AppDataModel* AppDataModel::instance = nullptr; diff --git a/src/websockets.h b/src/websockets.h deleted file mode 100644 index 65e9311..0000000 --- a/src/websockets.h +++ /dev/null @@ -1,179 +0,0 @@ -// Copyright 2019-2023 The Hush developers -// Released under the GPLv3 -#ifndef WEBSOCKETS_H -#define WEBSOCKETS_H - -#include "precompiled.h" -#include "camount.h" -#include "mainwindow.h" -#include "ui_mobileappconnector.h" - -QT_FORWARD_DECLARE_CLASS(QWebSocketServer) -QT_FORWARD_DECLARE_CLASS(QWebSocket) - -class WSServer; - -// We're going to wrap the websocket in this class, because the underlying QWebSocket might get closed -// or deleted while a callback is waiting to get the data back. Therefore, we write a custom "sendTextMessage" -// class that checks all this before sending. -class ClientWebSocket { -public: - ClientWebSocket(QWebSocket* c, WSServer* s = nullptr) { client = c; server = s; } - - void sendTextMessage(QString m); - void close(QWebSocketProtocol::CloseCode code, const QString& msg) { client->close(code, msg); } -private: - QWebSocket* client; - WSServer* server; -}; - -class WSServer : public QObject -{ - Q_OBJECT -public: - explicit WSServer(quint16 port, bool debug = false, QObject *parent = nullptr); - bool isValidConnection(QWebSocket* c) { return m_clients.contains(c); } - ~WSServer(); - -Q_SIGNALS: - void closed(); - -private Q_SLOTS: - void onNewConnection(); - void processTextMessage(QString message); - void processBinaryMessage(QByteArray message); - void socketDisconnected(); - -private: - QWebSocketServer *m_pWebSocketServer; - MainWindow *m_mainWindow; - QList m_clients; - bool m_debug; -}; - -class WormholeClient : public QObject { - Q_OBJECT - -private Q_SLOTS: - void onConnected(); - void onTextMessageReceived(QString message); - void closed(); - -public: - WormholeClient(MainWindow* parent, QString wormholeCode); - ~WormholeClient(); - - void connect(); - void retryConnect(); - -private: - MainWindow* parent = nullptr; - QWebSocket* m_webSocket = nullptr; - - QTimer* timer = nullptr; - - QString code; - int retryCount = 0; - bool shuttingDown = false; -}; - -enum NonceType { - LOCAL = 1, - REMOTE -}; - -enum AppConnectionType { - DIRECT = 1, - INTERNET -}; - -class AppDataServer { -public: - static AppDataServer* getInstance() { - if (instance == nullptr) { - instance = new AppDataServer(); - } - return instance; - } - - void connectAppDialog(MainWindow* parent); - void updateConnectedUI(); - void updateUIWithNewQRCode(MainWindow* mainwindow); - - void processSendTx(QJsonObject sendTx, MainWindow* mainwindow, std::shared_ptr pClient); - void processSendManyTx(QJsonObject sendmanyTx, MainWindow* mainwindow, std::shared_ptr pClient); - void processMessage(QString message, MainWindow* mainWindow, std::shared_ptr pClient, AppConnectionType connType); - void processGetInfo(QJsonObject jobj, MainWindow* mainWindow, std::shared_ptr pClient); - void processDecryptedMessage(QString message, MainWindow* mainWindow, std::shared_ptr pClient); - void processGetTransactions(MainWindow* mainWindow, std::shared_ptr pClient); - - QString decryptMessage(QJsonDocument msg, QString secretHex, QString lastRemoteNonceHex); - QString encryptOutgoing(QString msg); - - QString getWormholeCode(QString secretHex); - QString getSecretHex(); - void saveNewSecret(QString secretHex); - - void registerNewTempSecret(QString tmpSecretHex, bool allowInternet, MainWindow* main); - - QString getNonceHex(NonceType nt); - void saveNonceHex(NonceType nt, QString noncehex); - - bool getAllowInternetConnection(); - void setAllowInternetConnection(bool allow); - - void saveLastSeenTime(); - QDateTime getLastSeenTime(); - - void setConnectedName(QString name); - QString getConnectedName(); - bool isAppConnected(); - - QString connDesc(AppConnectionType t); - - void saveLastConnectedOver(AppConnectionType type); - AppConnectionType getLastConnectionType(); - -private: - AppDataServer() = default; - - static AppDataServer* instance; - Ui_MobileAppConnector* ui; - - QString tempSecret; - WormholeClient* tempWormholeClient = nullptr; -}; - -class AppDataModel { -public: - static AppDataModel* getInstance() { - if (instance == NULL) - instance = new AppDataModel(); - - return instance; - } - - CAmount getTBalance() { return balTransparent; } - CAmount getZBalance() { return balShielded; } - CAmount getTotalBalance() { return balTotal; } - - void setBalances(CAmount transparent, CAmount shielded) { - balTransparent = transparent; - balShielded = shielded; - balTotal = balTransparent + balShielded; - } - -private: - AppDataModel() = default; // Private, for singleton - - CAmount balTransparent; - CAmount balShielded; - CAmount balTotal; - - QString saplingAddress; - - static AppDataModel* instance; -}; - - -#endif // WEBSOCKETS_H