#include "wallet_security_controller.h" #include "../util/secure_vault.h" #include #include #include namespace dragonx { namespace services { WalletSecurityController::~WalletSecurityController() { clearDeferredEncryption(); } void WalletSecurityController::beginDeferredEncryption(std::string passphrase, std::string pin) { clearDeferredEncryption(); deferred_.passphrase = std::move(passphrase); deferred_.pin = std::move(pin); deferred_.pending = true; deferred_.lastConnectAttempt = -10.0; } WalletSecurityController::DeferredEncryptionSnapshot WalletSecurityController::deferredEncryption() const { return {deferred_.passphrase, deferred_.pin}; } bool WalletSecurityController::shouldAttemptDeferredConnect(double nowSeconds, double minIntervalSeconds) { if (!deferred_.pending) return false; if (nowSeconds - deferred_.lastConnectAttempt < minIntervalSeconds) return false; deferred_.lastConnectAttempt = nowSeconds; return true; } void WalletSecurityController::clearDeferredEncryption() { secureClear(deferred_.passphrase); secureClear(deferred_.pin); deferred_.pending = false; deferred_.lastConnectAttempt = -10.0; } WalletSecurityController::DeferredEncryptionResult WalletSecurityController::runDeferredEncryption( DeferredEncryptionSnapshot request, RpcGateway& rpc, VaultGateway* vault) { DeferredEncryptionResult result; result.pinProvided = !request.pin.empty(); std::string error; if (!rpc.encryptWallet(request.passphrase, error)) { result.error = error.empty() ? "encryptwallet failed" : error; secureClear(request.passphrase); secureClear(request.pin); return result; } result.encrypted = true; result.restartRequired = true; if (result.pinProvided && vault) { result.pinStored = vault->storePin(request.pin, request.passphrase); } secureClear(request.passphrase); secureClear(request.pin); return result; } WalletSecurityController::PinValidationResult WalletSecurityController::validatePinSetup( const std::string& pin, const std::string& confirmation, bool allowEmpty, std::size_t minLength) { if (pin.empty() && confirmation.empty()) { return allowEmpty ? PinValidationResult{true, PinValidationError::None, ""} : PinValidationResult{false, PinValidationError::Empty, "PIN is required"}; } if (pin != confirmation) { return {false, PinValidationError::Mismatch, "PINs do not match"}; } if (pin.size() < minLength) { return {false, PinValidationError::TooShort, "PIN is too short"}; } for (unsigned char c : pin) { if (!std::isdigit(c)) { return {false, PinValidationError::NonDigit, "PIN must contain only digits"}; } } return {true, PinValidationError::None, ""}; } WalletSecurityController::KeyKind WalletSecurityController::classifyAddress(const std::string& address) { return !address.empty() && address[0] == 'z' ? KeyKind::Shielded : KeyKind::Transparent; } WalletSecurityController::KeyKind WalletSecurityController::classifyPrivateKey(const std::string& key) { // Shielded: Sapling extended spending key (secret-extended-key-...) or legacy Sprout (SK...). // (The old `key[0]=='s'` test misrouted an uppercase "SK..." shielded key to the transparent RPC.) if (key.rfind("secret-extended-key-", 0) == 0) return KeyKind::Shielded; if (key.size() >= 2 && key[0] == 'S' && key[1] == 'K') return KeyKind::Shielded; // A "z"-prefixed key is a shielded viewing key (zxview…); "s"-prefixed is a legacy Sprout key. if (!key.empty() && (key[0] == 's' || key[0] == 'z')) return KeyKind::Shielded; return KeyKind::Transparent; } bool WalletSecurityController::isViewingKey(const std::string& key) { // Sapling extended full viewing key (mainnet HRP "zxviews"; "zxview" also matches the prefix the // lite backend recognizes). Watch-only: reveals the address's funds but cannot spend them. return key.rfind("zxview", 0) == 0; } bool WalletSecurityController::isRecognizedPrivateKey(const std::string& key) { if (key.rfind("secret-extended-key-", 0) == 0) return true; // Sapling z spending key if (key.size() >= 2 && key[0] == 'S' && key[1] == 'K') return true; // Sprout z spending key // Transparent WIF: base58, ~51-52 chars, common version prefixes. if (key.size() >= 51 && key.size() <= 52 && (key[0] == '5' || key[0] == 'K' || key[0] == 'L' || key[0] == 'U')) return true; return false; } bool WalletSecurityController::isRecognizedImportKey(const std::string& key) { return isRecognizedPrivateKey(key) || isViewingKey(key); } const char* WalletSecurityController::importSuccessMessage(KeyKind kind) { return kind == KeyKind::Shielded ? "Z-address key imported successfully. Wallet is rescanning." : "T-address key imported successfully. Wallet is rescanning."; } std::string WalletSecurityController::decryptExportFileName(std::uint64_t timestampSeconds) { char buffer[64]; snprintf(buffer, sizeof(buffer), "obsidiandecryptexport%llu", static_cast(timestampSeconds)); return std::string(buffer); } void WalletSecurityController::secureClear(std::string& value) { if (!value.empty()) { util::SecureVault::secureZero(&value[0], value.size()); value.clear(); } } } // namespace services } // namespace dragonx