fix(import): recognize real DragonX key formats in the import gate

The client-side pre-check rejected legitimate keys before the daemon ever
saw them, surfacing "Unrecognized key format" / a cryptic daemon "Invalid"
error. Two concrete defects plus the brittle heuristic behind them:

- Viewing keys: isViewingKey looked for Zcash's "zxview" extended-FVK
  prefix, but DragonX's z_exportviewingkey emits a Sapling *incoming*
  viewing key (HRP "zivks"), which z_importviewingkey is the only form the
  daemon decodes. Every real DragonX viewing key was refused. (F1)
- Uncompressed transparent WIF: the length+first-char heuristic accepted
  {5,K,L,U} only, but a version-188 uncompressed key starts with '7'. (F2)

Replace the heuristic with structural validation using the existing
checksum validators (F3): add util::decodeBase58Check (checksum-stripped
payload) and util::bech32Hrp (HRP of a valid Bech32 string). Transparent
keys are now accepted by decoding Base58Check and checking the payload is a
33/34-byte secret key with a DragonX SECRET_KEY version byte (188 main/
regtest, 128 testnet) — covering compressed and uncompressed, rejecting
addresses/typos by real checksum. Viewing keys are matched by the real
incoming-VK HRPs (zivks / zivktestsapling / zivkregtestsapling).

The Sweep gate and the dialog's live type indicator run off the same
predicates, so they are fixed too (F4). Messaging now names the likely
cause and appends a wrong-coin/network hint to the daemon's raw "Invalid"
error (F5).

Adds testPrivateKeyImportRecognition plus decodeBase58Check/bech32Hrp
coverage; suite green (1/1).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-08 22:22:29 -05:00
parent c61c211dfe
commit d603a54618
6 changed files with 133 additions and 10 deletions

View File

@@ -3834,7 +3834,9 @@ void App::importPrivateKey(const std::string& rawKey, int startHeight,
// Reject anything that isn't a recognized Z/T private key or shielded viewing key before handing
// it to the daemon (the dialog's indicator and this guard share isRecognizedImportKey).
if (!services::WalletSecurityController::isRecognizedImportKey(key)) {
if (callback) callback(false, "Unrecognized key format.", "");
if (callback) callback(false,
"Not a recognized DragonX private key or viewing key. Check for missing or "
"mistyped characters, and that this is a DragonX key (not another coin).", "");
return;
}
@@ -3874,6 +3876,14 @@ void App::importPrivateKey(const std::string& rawKey, int startHeight,
}
// Scrub the worker's copy of the key now that the request has been sent (all paths).
if (!key.empty()) sodium_memzero(&key[0], key.size());
// A checksum-valid key the daemon still rejects is almost always the right *format* but the
// wrong network/coin (Komodo-family chains share version bytes) or a corrupted paste — say so,
// since the bare "Invalid …" text reads like a wallet bug (F5).
if (!err.empty() && err.find("Invalid") != std::string::npos &&
err.find("DragonX") == std::string::npos) {
err += " — check the key is for DragonX (not another coin or network) and has no missing "
"or altered characters.";
}
return [this, err, addr, callback]() {
if (!err.empty()) {
if (callback) callback(false, err, "");