fix(ui): validate shielded addresses with the correct RPC
The Validate address dialog chose its RPC from the first character of the input, then asked the transparent-only validateaddress for every case. That RPC is Base58-only, so a valid Sapling (z) address came back isvalid=false and was reported invalid. - The input is now classified with the checksum-verified recognizers, and a shielded address is sent to z_validateaddress while a transparent (mining) address keeps validateaddress. - Anything that is not an address - a typo, garbage, or a pasted viewing/spending key - is judged invalid on the spot and never sent to the node, so a key pasted into the field is not leaked to an RPC. - On Lite there is no node to ask, so the dialog shows the checksum verdict alone and hides the "owned by this wallet" row. Tests: validateRpcMethod maps shielded to z_validateaddress, transparent to validateaddress, and a key or empty string to no RPC. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
#include "../../app.h"
|
||||
#include "../../rpc/rpc_client.h"
|
||||
#include "../../rpc/rpc_worker.h"
|
||||
#include "../../util/address_validation.h"
|
||||
#include "../../util/i18n.h"
|
||||
#include "../schema/ui_schema.h"
|
||||
#include "../material/draw_helpers.h"
|
||||
@@ -23,6 +24,7 @@ char ValidateAddressDialog::s_address_input[512] = "";
|
||||
bool ValidateAddressDialog::s_is_valid = false;
|
||||
bool ValidateAddressDialog::s_is_mine = false;
|
||||
bool ValidateAddressDialog::s_is_mining = false;
|
||||
bool ValidateAddressDialog::s_ownership_known = true;
|
||||
std::string ValidateAddressDialog::s_address_type;
|
||||
std::string ValidateAddressDialog::s_error_message;
|
||||
|
||||
@@ -35,6 +37,7 @@ void ValidateAddressDialog::show()
|
||||
s_is_valid = false;
|
||||
s_is_mine = false;
|
||||
s_is_mining = false;
|
||||
s_ownership_known = true;
|
||||
s_address_type.clear();
|
||||
s_error_message.clear();
|
||||
}
|
||||
@@ -104,65 +107,60 @@ void ValidateAddressDialog::render(App* app)
|
||||
while (!address.empty() && (address.front()==' '||address.front()=='\t'||address.front()=='\n'||address.front()=='\r')) address.erase(address.begin());
|
||||
while (!address.empty() && (address.back()==' '||address.back()=='\t'||address.back()=='\n'||address.back()=='\r')) address.pop_back();
|
||||
|
||||
// Determine if z-address or t-address
|
||||
bool is_zaddr = !address.empty() && address[0] == 'z';
|
||||
|
||||
if (is_zaddr) {
|
||||
if (app->worker()) {
|
||||
app->worker()->post([rpc = app->rpc(), address]() -> rpc::RPCWorker::MainCb {
|
||||
bool valid = false, mine = false;
|
||||
std::string error;
|
||||
try {
|
||||
rpc::RPCClient::TraceScope trace("Receive tab / Validate address");
|
||||
auto result = rpc->call("validateaddress", {address});
|
||||
valid = result.value("isvalid", false);
|
||||
mine = result.value("ismine", false);
|
||||
} catch (const std::exception& e) {
|
||||
error = e.what();
|
||||
// Classify with the checksum-verified recognizers, not address[0]=='z': the daemon's validateaddress
|
||||
// is transparent-only (Base58), so a Sapling address must go to z_validateaddress.
|
||||
const util::RecipientKind kind = util::classifyRecipient(address);
|
||||
const char* method = util::validateRpcMethod(kind);
|
||||
const bool mining = (kind == util::RecipientKind::MiningOnly);
|
||||
|
||||
if (!method) {
|
||||
// Not an address (typo, a pasted key, garbage): INVALID without asking the node, so a viewing
|
||||
// or spending key pasted here is never sent to an RPC.
|
||||
s_is_valid = false;
|
||||
s_is_mine = false;
|
||||
s_is_mining = false;
|
||||
s_ownership_known = true;
|
||||
s_address_type.clear();
|
||||
s_validated = true;
|
||||
s_validating = false;
|
||||
} else if (app->isLiteBuild()) {
|
||||
// Lite has no node to ask: the checksum verdict is the answer, ownership is unknown (hidden).
|
||||
s_is_valid = true;
|
||||
s_is_mine = false;
|
||||
s_ownership_known = false;
|
||||
s_is_mining = mining;
|
||||
s_address_type = TR(mining ? "validate_mining_type" : "validate_shielded_type");
|
||||
s_validated = true;
|
||||
s_validating = false;
|
||||
} else if (app->worker()) {
|
||||
app->worker()->post([rpc = app->rpc(), address, method = std::string(method), mining]() -> rpc::RPCWorker::MainCb {
|
||||
bool valid = false, mine = false;
|
||||
std::string error;
|
||||
try {
|
||||
rpc::RPCClient::TraceScope trace("Receive tab / Validate address");
|
||||
auto result = rpc->call(method, {address});
|
||||
valid = result.value("isvalid", false);
|
||||
mine = result.value("ismine", false);
|
||||
} catch (const std::exception& e) {
|
||||
error = e.what();
|
||||
}
|
||||
return [valid, mine, error, mining]() {
|
||||
if (error.empty()) {
|
||||
s_is_valid = valid;
|
||||
s_is_mine = valid && mine;
|
||||
s_ownership_known = true;
|
||||
s_address_type = TR(mining ? "validate_mining_type" : "validate_shielded_type");
|
||||
s_is_mining = mining;
|
||||
} else {
|
||||
s_error_message = error;
|
||||
s_is_valid = false;
|
||||
}
|
||||
return [valid, mine, error]() {
|
||||
if (error.empty()) {
|
||||
s_is_valid = valid;
|
||||
s_is_mine = mine;
|
||||
s_address_type = TR("validate_shielded_type");
|
||||
s_is_mining = false;
|
||||
} else {
|
||||
s_error_message = error;
|
||||
s_is_valid = false;
|
||||
}
|
||||
s_validated = true;
|
||||
s_validating = false;
|
||||
};
|
||||
});
|
||||
}
|
||||
s_validated = true;
|
||||
s_validating = false;
|
||||
};
|
||||
});
|
||||
} else {
|
||||
if (app->worker()) {
|
||||
app->worker()->post([rpc = app->rpc(), address]() -> rpc::RPCWorker::MainCb {
|
||||
bool valid = false, mine = false;
|
||||
std::string error;
|
||||
try {
|
||||
rpc::RPCClient::TraceScope trace("Receive tab / Validate address");
|
||||
auto result = rpc->call("validateaddress", {address});
|
||||
valid = result.value("isvalid", false);
|
||||
mine = result.value("ismine", false);
|
||||
} catch (const std::exception& e) {
|
||||
error = e.what();
|
||||
}
|
||||
return [valid, mine, error]() {
|
||||
if (error.empty()) {
|
||||
s_is_valid = valid;
|
||||
s_is_mine = mine;
|
||||
s_address_type = TR("validate_mining_type");
|
||||
s_is_mining = true;
|
||||
} else {
|
||||
s_error_message = error;
|
||||
s_is_valid = false;
|
||||
}
|
||||
s_validated = true;
|
||||
s_validating = false;
|
||||
};
|
||||
});
|
||||
}
|
||||
s_validating = false; // no worker: don't leave the button disabled forever
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,13 +209,15 @@ void ValidateAddressDialog::render(App* app)
|
||||
ImGui::SameLine(lbl.position);
|
||||
ImGui::Text("%s", s_address_type.c_str());
|
||||
|
||||
// Is mine?
|
||||
ImGui::Text("%s", TR("validate_ownership"));
|
||||
ImGui::SameLine(lbl.position);
|
||||
if (s_is_mine) {
|
||||
ImGui::TextColored(ImVec4(0.3f, 0.8f, 0.3f, 1.0f), "%s", TR("validate_is_mine"));
|
||||
} else {
|
||||
ImGui::TextDisabled("%s", TR("validate_not_mine"));
|
||||
// Is mine? (not shown when no node was asked - lite)
|
||||
if (s_ownership_known) {
|
||||
ImGui::Text("%s", TR("validate_ownership"));
|
||||
ImGui::SameLine(lbl.position);
|
||||
if (s_is_mine) {
|
||||
ImGui::TextColored(ImVec4(0.3f, 0.8f, 0.3f, 1.0f), "%s", TR("validate_is_mine"));
|
||||
} else {
|
||||
ImGui::TextDisabled("%s", TR("validate_not_mine"));
|
||||
}
|
||||
}
|
||||
if (s_is_mining) { // valid, but not for payments
|
||||
ImGui::Spacing();
|
||||
|
||||
@@ -51,6 +51,7 @@ private:
|
||||
static bool s_is_valid;
|
||||
static bool s_is_mine;
|
||||
static bool s_is_mining; // a valid transparent (mining) address: explain it can't be paid
|
||||
static bool s_ownership_known; // false when no node was asked (lite): the ownership row is hidden
|
||||
static std::string s_address_type;
|
||||
static std::string s_error_message;
|
||||
};
|
||||
|
||||
@@ -198,5 +198,15 @@ RecipientKind classifyRecipient(const std::string& s)
|
||||
return RecipientKind::Invalid;
|
||||
}
|
||||
|
||||
const char* validateRpcMethod(RecipientKind kind)
|
||||
{
|
||||
switch (kind) {
|
||||
case RecipientKind::Shielded: return "z_validateaddress";
|
||||
case RecipientKind::MiningOnly: return "validateaddress";
|
||||
case RecipientKind::Invalid: break;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace util
|
||||
} // namespace dragonx
|
||||
|
||||
@@ -58,5 +58,11 @@ bool isValidRecipientAddress(const std::string& s);
|
||||
enum class RecipientKind { Invalid, Shielded, MiningOnly };
|
||||
RecipientKind classifyRecipient(const std::string& s);
|
||||
|
||||
// The daemon RPC that can validate an address of this kind: "z_validateaddress" for a shielded (Sapling)
|
||||
// address, "validateaddress" for a transparent one (z_validateaddress reports isvalid=false for those, and
|
||||
// validateaddress is Base58-only so it reports false for Sapling), nullptr for Invalid (not an address:
|
||||
// answer client-side, send nothing to the node).
|
||||
const char* validateRpcMethod(RecipientKind kind);
|
||||
|
||||
} // namespace util
|
||||
} // namespace dragonx
|
||||
|
||||
Reference in New Issue
Block a user