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:
@@ -726,7 +726,9 @@ void testConnectionConfig()
|
||||
|
||||
void testPaymentUri()
|
||||
{
|
||||
std::string taddr = "R" + std::string(33, 'a');
|
||||
// Real checksummed addresses — the parser now checksum-validates the recipient (not a bare
|
||||
// prefix/length filter), so it accepts P2PKH / P2SH / shielded and rejects transcription errors.
|
||||
std::string taddr = "R9NXAVJezHiBnT3ijTpg3JUZre7PxhJWti"; // P2PKH (v60)
|
||||
auto parsed = dragonx::util::parsePaymentURI(
|
||||
"drgx:" + taddr + "?amount=1.25000000&label=Main+Wallet&memo=hello%20there&message=thanks");
|
||||
|
||||
@@ -737,11 +739,19 @@ void testPaymentUri()
|
||||
EXPECT_EQ(parsed.memo, std::string("hello there"));
|
||||
EXPECT_EQ(parsed.message, std::string("thanks"));
|
||||
|
||||
std::string zaddr = "zs" + std::string(76, 'b');
|
||||
std::string zaddr = "zs1qqqsyqcyq5rqwzqfpg9scrgwpugpzysnzs23v9ccrydpk8qarc0jqgfzyvjz2f389q5j5ctfvp5";
|
||||
auto zparsed = dragonx::util::parsePaymentURI("hush://" + zaddr + "?amt=0.5");
|
||||
EXPECT_TRUE(zparsed.valid);
|
||||
EXPECT_NEAR(zparsed.amount, 0.5, 0.00000001);
|
||||
|
||||
// Regression: a P2SH/multisig recipient ("b…", v85) must parse — the old 'R'/'t'-only filter dropped it.
|
||||
auto p2sh = dragonx::util::parsePaymentURI("drgx:bCpbnCkrjoJ6EHXtLx9eASHEbFYyikt35C?amount=1");
|
||||
EXPECT_TRUE(p2sh.valid);
|
||||
|
||||
// A transcription error (flipped checksum char) is now rejected at parse time.
|
||||
auto typo = dragonx::util::parsePaymentURI("drgx:R9NXAVJezHiBnT3ijTpg3JUZre7PxhJWtX?amount=1");
|
||||
EXPECT_FALSE(typo.valid);
|
||||
|
||||
auto invalid = dragonx::util::parsePaymentURI("drgx:" + taddr + "?amount=-1");
|
||||
EXPECT_FALSE(invalid.valid);
|
||||
EXPECT_EQ(invalid.error, std::string("Invalid negative amount"));
|
||||
@@ -5966,6 +5976,26 @@ void testAddressChecksumValidation()
|
||||
EXPECT_EQ(bech32Hrp("abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw"), std::string("abcdef"));
|
||||
EXPECT_EQ(bech32Hrp("A12UEL5L"), std::string("a")); // lower-cased
|
||||
EXPECT_EQ(bech32Hrp("A12UEL5M"), std::string("")); // invalid → empty
|
||||
|
||||
// Address type recognizers: accept every real DragonX recipient form, reject non-addresses.
|
||||
using dragonx::util::isTransparentAddress;
|
||||
using dragonx::util::isShieldedAddress;
|
||||
using dragonx::util::isValidRecipientAddress;
|
||||
const std::string p2pkh = "R9NXAVJezHiBnT3ijTpg3JUZre7PxhJWti"; // v60
|
||||
const std::string p2sh = "bCpbnCkrjoJ6EHXtLx9eASHEbFYyikt35C"; // v85 multisig — the regression
|
||||
const std::string zaddr = "zs1qqqsyqcyq5rqwzqfpg9scrgwpugpzysnzs23v9ccrydpk8qarc0jqgfzyvjz2f389q5j5ctfvp5";
|
||||
EXPECT_TRUE(isTransparentAddress(p2pkh));
|
||||
EXPECT_TRUE(isTransparentAddress(p2sh)); // was silently dropped by the old 'R'-only filter
|
||||
EXPECT_FALSE(isShieldedAddress(p2pkh));
|
||||
EXPECT_TRUE(isShieldedAddress(zaddr));
|
||||
EXPECT_FALSE(isTransparentAddress(zaddr));
|
||||
EXPECT_TRUE(isValidRecipientAddress(p2pkh));
|
||||
EXPECT_TRUE(isValidRecipientAddress(p2sh));
|
||||
EXPECT_TRUE(isValidRecipientAddress(zaddr));
|
||||
// A WIF spending key is NOT a recipient (33/34-byte payload, not 21); nor is a typo'd address.
|
||||
EXPECT_FALSE(isTransparentAddress("Up3W7uVYkLxCfH91APxjSpkkGBWJyBrm3tt1bCz64V5fpZK9ef3C"));
|
||||
EXPECT_FALSE(isValidRecipientAddress("R9NXAVJezHiBnT3ijTpg3JUZre7PxhJWtX")); // flipped checksum char
|
||||
EXPECT_FALSE(isValidRecipientAddress(""));
|
||||
}
|
||||
|
||||
// Import-key recognition: the client gate must accept every real DragonX key form and reject
|
||||
|
||||
Reference in New Issue
Block a user