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>
101 lines
3.5 KiB
C++
101 lines
3.5 KiB
C++
#pragma once
|
|
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <string>
|
|
|
|
namespace dragonx {
|
|
namespace services {
|
|
|
|
class WalletSecurityController {
|
|
public:
|
|
enum class PinValidationError {
|
|
None,
|
|
Empty,
|
|
Mismatch,
|
|
TooShort,
|
|
NonDigit
|
|
};
|
|
|
|
struct PinValidationResult {
|
|
bool ok = false;
|
|
PinValidationError error = PinValidationError::None;
|
|
const char* message = "";
|
|
};
|
|
|
|
struct DeferredEncryptionSnapshot {
|
|
std::string passphrase;
|
|
std::string pin;
|
|
};
|
|
|
|
class RpcGateway {
|
|
public:
|
|
virtual ~RpcGateway() = default;
|
|
virtual bool encryptWallet(const std::string& passphrase, std::string& error) = 0;
|
|
virtual bool unlockWallet(const std::string& passphrase, int timeoutSeconds, std::string& error) = 0;
|
|
virtual bool exportWallet(const std::string& fileName, long timeoutSeconds, std::string& error) = 0;
|
|
virtual bool importWallet(const std::string& filePath, long timeoutSeconds, std::string& error) = 0;
|
|
};
|
|
|
|
class VaultGateway {
|
|
public:
|
|
virtual ~VaultGateway() = default;
|
|
virtual bool storePin(const std::string& pin, const std::string& passphrase) = 0;
|
|
};
|
|
|
|
enum class KeyKind {
|
|
Transparent,
|
|
Shielded
|
|
};
|
|
|
|
struct DeferredEncryptionResult {
|
|
bool encrypted = false;
|
|
bool pinProvided = false;
|
|
bool pinStored = false;
|
|
bool restartRequired = false;
|
|
std::string error;
|
|
};
|
|
|
|
~WalletSecurityController();
|
|
|
|
void beginDeferredEncryption(std::string passphrase, std::string pin = {});
|
|
bool hasDeferredEncryption() const { return deferred_.pending; }
|
|
DeferredEncryptionSnapshot deferredEncryption() const;
|
|
bool shouldAttemptDeferredConnect(double nowSeconds, double minIntervalSeconds = 3.0);
|
|
void clearDeferredEncryption();
|
|
|
|
DeferredEncryptionResult runDeferredEncryption(DeferredEncryptionSnapshot request,
|
|
RpcGateway& rpc,
|
|
VaultGateway* vault);
|
|
|
|
static PinValidationResult validatePinSetup(const std::string& pin,
|
|
const std::string& confirmation,
|
|
bool allowEmpty = false,
|
|
std::size_t minLength = 4);
|
|
static KeyKind classifyAddress(const std::string& address);
|
|
static KeyKind classifyPrivateKey(const std::string& key);
|
|
// True if `key` is a shielded viewing key (Sapling incoming viewing key, "zivks…" — watch-only).
|
|
static bool isViewingKey(const std::string& key);
|
|
// True if `key` looks like a recognized Z (Sapling/Sprout spending) or T (WIF) private key.
|
|
static bool isRecognizedPrivateKey(const std::string& key);
|
|
// As above, but also accepts a recognized shielded viewing key — the import dialog auto-detects
|
|
// both, so this is the single source of truth for its indicator AND its submit guard.
|
|
static bool isRecognizedImportKey(const std::string& key);
|
|
static const char* importSuccessMessage(KeyKind kind);
|
|
static std::string decryptExportFileName(std::uint64_t timestampSeconds);
|
|
static void secureClear(std::string& value);
|
|
|
|
private:
|
|
struct DeferredEncryptionState {
|
|
std::string passphrase;
|
|
std::string pin;
|
|
bool pending = false;
|
|
double lastConnectAttempt = -10.0;
|
|
};
|
|
|
|
DeferredEncryptionState deferred_;
|
|
};
|
|
|
|
} // namespace services
|
|
} // namespace dragonx
|