fix: Tier-1 UX robustness — backup/export integrity, verified installs, input validation
From the app-wide robustness audit (calibrated to the import-key dialog). These are the safety-critical fixes; several could cost users funds: - Backup/export false success (FUND LOSS): exportAllKeys pre-seeded a header, so a keyless result (the usual case when the wallet is encrypted+locked) still looked non-empty and backupWallet wrote a private-key-less file and reported "Backup saved". Now exportAllKeys returns an exported count; backupWallet and the export-all dialog refuse to write / report success on 0 keys, disclose the count, flag partial results as INCOMPLETE, and the backup dialog confirms before overwriting an existing file (+ trims the path). - Bootstrap: fail CLOSED — refuse to install an unverified multi-GB archive when no checksum is published (was `return true`), mirroring the xmrig/daemon updaters. - Restore-from-seed: require a valid BIP39 word count (12/15/18/21/24) with a live "should be 24 words — you have N" hint instead of accepting any non-empty text. - Encryption passphrase: detect leading/trailing whitespace and BLOCK with a warning (not a silent trim, which would change the passphrase and lock the user out). - Receive payment QR: emit the canonical `drgx:` scheme with a URL-encoded memo via a new shared util::buildPaymentUri (the request-payment dialog now routes through it too, so they can't diverge); the old "dragonx:" + raw memo was unparseable by the wallet's own scanner. Also clamps the receive "Recent Received" list to the 4 most recent (companion to the recent-lists commit). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -724,9 +724,13 @@ bool Bootstrap::verifyChecksums(const std::string& zipPath, const std::string& b
|
||||
bool haveMD5 = !md5Content.empty();
|
||||
|
||||
if (!haveSHA256 && !haveMD5) {
|
||||
DEBUG_LOGF("[Bootstrap] Warning: no checksum files available — skipping verification\n");
|
||||
// Allow the process to continue (server may not have checksum files yet)
|
||||
return true;
|
||||
// Fail CLOSED: refuse to install an unverified multi-GB archive (mirrors the xmrig/daemon
|
||||
// updaters, which refuse when no checksum is published). Trusting an unverified download of
|
||||
// this size is a real integrity/security risk; a missing checksum is a server-side gap to fix.
|
||||
DEBUG_LOGF("[Bootstrap] No checksum published — refusing to install an unverified archive\n");
|
||||
setProgress(State::Failed, "Verification failed: no checksum was published for the bootstrap. "
|
||||
"Refusing to install an unverified archive.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Determine progress ranges: if both checksums exist, split 0-50% / 50-100%
|
||||
|
||||
@@ -5,12 +5,58 @@
|
||||
#include "payment_uri.h"
|
||||
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstdio>
|
||||
|
||||
namespace dragonx {
|
||||
namespace util {
|
||||
|
||||
std::string urlEncode(const std::string& value)
|
||||
{
|
||||
std::string out;
|
||||
out.reserve(value.size());
|
||||
for (unsigned char c : value) {
|
||||
if (std::isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') {
|
||||
out += static_cast<char>(c);
|
||||
} else if (c == ' ') {
|
||||
out += "%20";
|
||||
} else {
|
||||
char hex[4];
|
||||
std::snprintf(hex, sizeof(hex), "%%%02X", c);
|
||||
out += hex;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string buildPaymentUri(const std::string& address, double amount,
|
||||
const std::string& label, const std::string& memo)
|
||||
{
|
||||
if (address.empty()) return "";
|
||||
|
||||
std::ostringstream uri;
|
||||
uri << "drgx:" << address;
|
||||
|
||||
bool hasParams = false;
|
||||
auto addParam = [&](const char* key, const std::string& value) {
|
||||
if (value.empty()) return;
|
||||
uri << (hasParams ? "&" : "?") << key << "=" << value;
|
||||
hasParams = true;
|
||||
};
|
||||
|
||||
if (amount > 0.0) {
|
||||
std::ostringstream amt;
|
||||
amt << std::fixed << std::setprecision(8) << amount;
|
||||
addParam("amount", amt.str());
|
||||
}
|
||||
addParam("label", urlEncode(label));
|
||||
addParam("memo", urlEncode(memo));
|
||||
|
||||
return uri.str();
|
||||
}
|
||||
|
||||
std::string urlDecode(const std::string& encoded)
|
||||
{
|
||||
std::string result;
|
||||
|
||||
@@ -40,6 +40,21 @@ PaymentURI parsePaymentURI(const std::string& uri);
|
||||
*/
|
||||
std::string urlDecode(const std::string& encoded);
|
||||
|
||||
/**
|
||||
* @brief URL-encode a string (RFC 3986 unreserved kept; ' ' -> %20; others -> %HH).
|
||||
*/
|
||||
std::string urlEncode(const std::string& value);
|
||||
|
||||
/**
|
||||
* @brief Build a canonical DragonX payment URI: drgx:<address>[?amount=..&label=..&memo=..],
|
||||
* with query parameters URL-encoded. amount<=0 and empty label/memo are omitted.
|
||||
*
|
||||
* Single source of truth so the receive tab and the request-payment dialog can't diverge on the
|
||||
* scheme or encoding (the receive tab previously emitted an unparseable "dragonx:" URI with a raw memo).
|
||||
*/
|
||||
std::string buildPaymentUri(const std::string& address, double amount = 0.0,
|
||||
const std::string& label = "", const std::string& memo = "");
|
||||
|
||||
/**
|
||||
* @brief Check if a string is a valid payment URI
|
||||
* @param str String to check
|
||||
|
||||
Reference in New Issue
Block a user