fix(send): validate recipient address checksums (Base58Check + Bech32)
The send screen labelled any prefix+length match as a "Valid" address, so a mistyped address that still matched the pattern passed the gate. Add pure, offline checksum validation — Base58Check (transparent R-addresses) and Bech32 (Sapling zs-addresses) — and require it in the validity check. Both verifiers are version-byte/HRP agnostic (the HRP is taken from the string, the Base58 checksum is chain-independent), so a correct implementation never rejects a genuine address while catching transcription errors. Works for both build variants (no daemon round-trip), unit-tested against standard BIP173 / Base58Check vectors. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -11,6 +11,7 @@
|
||||
#include "../../app.h"
|
||||
#include "../../config/version.h"
|
||||
#include "../../data/wallet_state.h"
|
||||
#include "../../util/address_validation.h"
|
||||
#include "../../util/i18n.h"
|
||||
#include "../notifications.h"
|
||||
#include "../layout.h"
|
||||
@@ -151,6 +152,16 @@ static std::string TruncateAddress(const std::string& addr, size_t maxLen = 40)
|
||||
return addr.substr(0, halfLen) + "..." + addr.substr(addr.length() - halfLen);
|
||||
}
|
||||
|
||||
// Recipient validity = prefix/length pre-filter AND a real encoding-checksum check, so a
|
||||
// transcription error that still matches the prefix/length is no longer labelled "Valid".
|
||||
// The checksum verifiers are version-agnostic, so they never reject a genuine address.
|
||||
static bool IsValidShieldedAddr(const char* a) {
|
||||
return a[0] == 'z' && a[1] == 's' && strlen(a) > 60 && dragonx::util::isValidBech32(a);
|
||||
}
|
||||
static bool IsValidTransparentAddr(const char* a) {
|
||||
return a[0] == 'R' && strlen(a) >= 34 && dragonx::util::isValidBase58Check(a);
|
||||
}
|
||||
|
||||
static std::string timeAgo(int64_t timestamp) {
|
||||
if (timestamp <= 0) return "";
|
||||
int64_t now = (int64_t)std::time(nullptr);
|
||||
@@ -309,8 +320,8 @@ static void RenderSourceDropdown(App* app, float width) {
|
||||
static void RenderAddressSuggestions(const WalletState& state, float width, const char* childId) {
|
||||
std::string partial(s_to_address);
|
||||
if (partial.length() < 2) return;
|
||||
bool is_valid_z = (s_to_address[0] == 'z' && s_to_address[1] == 's' && strlen(s_to_address) > 60);
|
||||
bool is_valid_t = (s_to_address[0] == 'R' && strlen(s_to_address) >= 34);
|
||||
bool is_valid_z = IsValidShieldedAddr(s_to_address);
|
||||
bool is_valid_t = IsValidTransparentAddr(s_to_address);
|
||||
if (is_valid_z || is_valid_t) return;
|
||||
|
||||
std::vector<std::string> suggestions;
|
||||
@@ -655,7 +666,7 @@ void RenderSendConfirmPopup(App* app) {
|
||||
// Called every frame while the popup should be visible.
|
||||
// OpenPopup is idempotent when the popup is already open.
|
||||
ImGui::OpenPopup(TR("confirm_send"));
|
||||
bool is_valid_z = (s_to_address[0] == 'z' && s_to_address[1] == 's' && strlen(s_to_address) > 60);
|
||||
bool is_valid_z = IsValidShieldedAddr(s_to_address);
|
||||
auto& S = schema::UI();
|
||||
const auto& state = app->getWalletState();
|
||||
const auto& market = state.market;
|
||||
@@ -1121,8 +1132,8 @@ void RenderSendTab(App* app)
|
||||
glassSpec.rounding = glassRound;
|
||||
|
||||
double available = GetAvailableBalance(app);
|
||||
bool is_valid_z = (s_to_address[0] == 'z' && s_to_address[1] == 's' && strlen(s_to_address) > 60);
|
||||
bool is_valid_t = (s_to_address[0] == 'R' && strlen(s_to_address) >= 34);
|
||||
bool is_valid_z = IsValidShieldedAddr(s_to_address);
|
||||
bool is_valid_t = IsValidTransparentAddr(s_to_address);
|
||||
bool is_valid_address = is_valid_z || is_valid_t;
|
||||
float sectionGap = Layout::spacingXl() * vScale;
|
||||
|
||||
@@ -1204,8 +1215,8 @@ void RenderSendTab(App* app)
|
||||
checkPreview = true;
|
||||
}
|
||||
if (validAddr[0] != '\0') {
|
||||
bool vz = (validAddr[0] == 'z' && validAddr[1] == 's' && strlen(validAddr) > 60);
|
||||
bool vt = (validAddr[0] == 'R' && strlen(validAddr) >= 34);
|
||||
bool vz = IsValidShieldedAddr(validAddr);
|
||||
bool vt = IsValidTransparentAddr(validAddr);
|
||||
if (vz || vt) {
|
||||
ImGui::SameLine();
|
||||
if (vz)
|
||||
|
||||
Reference in New Issue
Block a user