fix(send): accept P2SH/multisig recipients in the send + URI address gates

Same defect class as the import-key fix: a hardcoded prefix/length pre-filter
layered over the checksum validators rejected valid addresses before the daemon
saw them. The send-screen recipient gate required a[0]=='R', and the payment-URI
parser accepted only 'R'/'t' with rigid length bands — so every valid P2SH /
multisig address (DragonX SCRIPT_ADDRESS=85 → 'b…') was silently refused, leaving
the Send button disabled with no usable recipient.

Centralize recipient recognition in util/address_validation:
- isTransparentAddress: Base58Check with a 21-byte version+hash160 payload —
  covers P2PKH ('R…', v60) AND P2SH ('b…', v85) on every network, rejects WIF
  keys / typos by real checksum.
- isShieldedAddress: Bech32 + a Sapling payment-address HRP (zs / ztestsapling /
  zregtestsapling), distinguishing a payment address from a viewing key.
- isValidRecipientAddress: either of the above.

send_tab's two validity helpers (the single choke point for all 5 call sites) and
the payment-URI format check now route through these. The URI parser now
checksum-validates the recipient (fail-fast on transcription errors) rather than
being prefix/length-only.

Tests use real checksummed vectors (P2PKH/P2SH/shielded, WIF- and typo-rejection);
testPaymentUri updated off its old fake fixed-char addresses. Suite green (1/1).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-09 00:12:17 -05:00
parent d603a54618
commit c3e81a5fa6
5 changed files with 78 additions and 23 deletions

View File

@@ -148,14 +148,14 @@ static double GetAvailableBalance(App* app) {
return 0.0;
}
// 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.
// Recipient validity via the shared, structure-based recognizers: a real encoding-checksum check
// plus the actual DragonX address types — so a transcription error is never "Valid", and a valid
// P2SH/multisig ('b…') recipient is no longer dropped by a hardcoded 'R'-only prefix filter.
static bool IsValidShieldedAddr(const char* a) {
return a[0] == 'z' && a[1] == 's' && strlen(a) > 60 && dragonx::util::isValidBech32(a);
return a && dragonx::util::isShieldedAddress(a);
}
static bool IsValidTransparentAddr(const char* a) {
return a[0] == 'R' && strlen(a) >= 34 && dragonx::util::isValidBase58Check(a);
return a && dragonx::util::isTransparentAddress(a);
}
static std::string timeAgo(int64_t timestamp) {
@@ -1318,8 +1318,7 @@ void RenderSendTab(App* app)
trimmed.erase(trimmed.begin());
while (!trimmed.empty() && (trimmed.back() == ' ' || trimmed.back() == '\n' || trimmed.back() == '\r' || trimmed.back() == '\t'))
trimmed.pop_back();
bool looksValid = (trimmed.size() > 30 &&
((trimmed[0] == 'z' && trimmed[1] == 's') || trimmed[0] == 'R'));
bool looksValid = dragonx::util::isValidRecipientAddress(trimmed);
if (looksValid && s_to_address[0] == '\0') {
s_preview_text = trimmed;
s_paste_previewing = true;