diff --git a/depends/packages/bdb.mk b/depends/packages/bdb.mk index 7a221e8271b..c4ee141f18f 100644 --- a/depends/packages/bdb.mk +++ b/depends/packages/bdb.mk @@ -9,7 +9,6 @@ define $(package)_set_vars $(package)_config_opts=--disable-shared --enable-cxx --disable-replication $(package)_config_opts_mingw32=--enable-mingw $(package)_config_opts_linux=--with-pic - $(package)_cxxflags=-std=c++11 $(package)_cxxflags_darwin=$(shell command -v xcrun >/dev/null 2>&1 && echo "-isysroot$$(xcrun --show-sdk-path)") $(package)_cflags_darwin=$(shell command -v xcrun >/dev/null 2>&1 && echo "-isysroot$$(xcrun --show-sdk-path)") diff --git a/src/init.cpp b/src/init.cpp index f849b868ec4..6d9cd1d8baf 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -473,6 +473,7 @@ std::string HelpMessage(HelpMessageMode mode) strUsage += HelpMessageOpt("-experimentalfeatures", _("Enable use of experimental features")); strUsage += HelpMessageOpt("-help-debug", _("Show all debugging options (usage: --help -help-debug)")); strUsage += HelpMessageOpt("-logips", strprintf(_("Include IP addresses in debug output (default: %u)"), 0)); + strUsage += HelpMessageOpt("-debuglogfile", _("Write debug output to debug.log file (default: 0, disabled for privacy)")); strUsage += HelpMessageOpt("-logtimestamps", strprintf(_("Prepend debug output with timestamp (default: %u)"), 1)); if (showDebug) { @@ -14486,6 +14487,7 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) // Set this early so that parameter interactions go to console fPrintToConsole = GetBoolArg("-printtoconsole", false); fLogTimestamps = GetBoolArg("-logtimestamps", true); + fPrintToDebugLog = GetBoolArg("-debuglogfile", false); fLogIPs = GetBoolArg("-logips", false); LogPrintf("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"); diff --git a/src/keystore.cpp b/src/keystore.cpp index e1e3ae89b01..31a9790eef9 100644 --- a/src/keystore.cpp +++ b/src/keystore.cpp @@ -246,3 +246,38 @@ bool CBasicKeyStore::GetSaplingExtendedSpendingKey(const libzcash::SaplingPaymen GetSaplingFullViewingKey(ivk, fvk) && GetSaplingSpendingKey(fvk, extskOut); } + +void CBasicKeyStore::CleanupKeys() +{ + LOCK2(cs_KeyStore, cs_SpendingKeyStore); + + // Clear HD seed securely + hdSeed = HDSeed(); + + // Clear all keys - CKey destructor will securely wipe key material + mapKeys.clear(); + + // Clear scripts + mapScripts.clear(); + + // Clear watch-only set + setWatchOnly.clear(); + + // Clear Sprout spending keys + mapSproutSpendingKeys.clear(); + + // Clear Sprout viewing keys + mapSproutViewingKeys.clear(); + + // Clear note decryptors + mapNoteDecryptors.clear(); + + // Clear Sapling spending keys + mapSaplingSpendingKeys.clear(); + + // Clear Sapling full viewing keys + mapSaplingFullViewingKeys.clear(); + + // Clear Sapling incoming viewing keys + mapSaplingIncomingViewingKeys.clear(); +} diff --git a/src/keystore.h b/src/keystore.h index 6ff34b20de4..4b46179e0d0 100644 --- a/src/keystore.h +++ b/src/keystore.h @@ -98,6 +98,9 @@ class CKeyStore virtual bool GetSproutViewingKey( const libzcash::SproutPaymentAddress &address, libzcash::SproutViewingKey& vkOut) const =0; + + //! Securely wipe all keys from memory + virtual void CleanupKeys() =0; }; typedef std::map KeyMap; @@ -304,6 +307,9 @@ class CBasicKeyStore : public CKeyStore virtual bool GetSproutViewingKey( const libzcash::SproutPaymentAddress &address, libzcash::SproutViewingKey& vkOut) const; + + //! Securely wipe all keys from memory + void CleanupKeys(); }; typedef std::vector > CKeyingMaterial; diff --git a/src/util.cpp b/src/util.cpp index 26486d25c92..2ea5d583cc9 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -106,7 +106,7 @@ map mapArgs; map > mapMultiArgs; bool fDebug = false; bool fPrintToConsole = false; -bool fPrintToDebugLog = true; +bool fPrintToDebugLog = false; bool fDaemon = false; bool fServer = false; string strMiscWarning; diff --git a/src/wallet/crypter.cpp b/src/wallet/crypter.cpp index cf87c01d48f..fe5440fad28 100644 --- a/src/wallet/crypter.cpp +++ b/src/wallet/crypter.cpp @@ -7,6 +7,7 @@ #include "script/script.h" #include "script/standard.h" #include "streams.h" +#include "support/cleanse.h" #include "util.h" #include @@ -548,6 +549,56 @@ bool CCryptoKeyStore::GetSaplingSpendingKey(const libzcash::SaplingFullViewingKe return false; } +void CCryptoKeyStore::CleanupKeys() +{ + { + LOCK2(cs_KeyStore, cs_SpendingKeyStore); + + // Securely wipe the master key + if (!vMasterKey.empty()) { + memory_cleanse(vMasterKey.data(), vMasterKey.size()); + vMasterKey.clear(); + } + + // Clear crypted HD seed + if (!cryptedHDSeed.second.empty()) { + memory_cleanse(cryptedHDSeed.second.data(), cryptedHDSeed.second.size()); + cryptedHDSeed.second.clear(); + } + cryptedHDSeed.first.SetNull(); + + // Clear crypted keys (securely wipe and explicitly clear) + for (auto& entry : mapCryptedKeys) { + if (!entry.second.second.empty()) { + memory_cleanse(entry.second.second.data(), entry.second.second.size()); + entry.second.second.clear(); + } + } + mapCryptedKeys.clear(); + + // Clear crypted Sprout spending keys + for (auto& entry : mapCryptedSproutSpendingKeys) { + if (!entry.second.empty()) { + memory_cleanse(entry.second.data(), entry.second.size()); + entry.second.clear(); + } + } + mapCryptedSproutSpendingKeys.clear(); + + // Clear crypted Sapling spending keys + for (auto& entry : mapCryptedSaplingSpendingKeys) { + if (!entry.second.empty()) { + memory_cleanse(entry.second.data(), entry.second.size()); + entry.second.clear(); + } + } + mapCryptedSaplingSpendingKeys.clear(); + } + + // Call base class cleanup + CBasicKeyStore::CleanupKeys(); +} + bool CCryptoKeyStore::EncryptKeys(CKeyingMaterial& vMasterKeyIn) { { diff --git a/src/wallet/crypter.h b/src/wallet/crypter.h index 1cfefe8865d..2823cd90ad8 100644 --- a/src/wallet/crypter.h +++ b/src/wallet/crypter.h @@ -263,6 +263,9 @@ class CCryptoKeyStore : public CBasicKeyStore } bool GetSaplingSpendingKey(const libzcash::SaplingFullViewingKey &fvk, libzcash::SaplingExtendedSpendingKey &skOut) const; + //! Securely wipe all keys from memory (override base class) + void CleanupKeys(); + /** * Wallet status (encrypted, locked) changed. diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 272ae28de61..4cd4dd4eb53 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -9,6 +9,7 @@ #include "init.h" #include "key_io.h" #include "main.h" +#include "miner.h" #include "net.h" #include "netbase.h" #include "rpc/server.h" @@ -18,6 +19,7 @@ #include "utilmoneystr.h" #include "wallet.h" #include "walletdb.h" +#include "wallet/db.h" #include "primitives/transaction.h" #include "zcbenchmarks.h" #include "script/interpreter.h" @@ -36,6 +38,7 @@ #include #include +#include #include @@ -4600,6 +4603,194 @@ extern UniValue z_importwallet(const UniValue& params, bool fHelp); extern UniValue z_getpaymentdisclosure(const UniValue& params, bool fHelp); // in rpcdisclosure.cpp extern UniValue z_validatepaymentdisclosure(const UniValue ¶ms, bool fHelp); +UniValue loadwallet(const UniValue& params, bool fHelp) +{ + if (fHelp || params.size() != 1) + throw runtime_error( + "loadwallet \"filename\"\n" + "\nLoads a wallet from a wallet file.\n" + "Note that a blockchain rescan is performed to find transactions for the wallet's addresses.\n" + "\nArguments:\n" + "1. \"filename\" (string, required) The wallet file to load (relative to datadir)\n" + "\nResult:\n" + "{\n" + " \"name\" : , (string) The wallet name if loaded successfully.\n" + " \"warning\" : , (string) Any warning messages that occurred during loading.\n" + "}\n" + "\nExamples:\n" + + HelpExampleCli("loadwallet", "\"backup-wallet.dat\"") + + HelpExampleRpc("loadwallet", "\"backup-wallet.dat\"") + ); + + // Check if node is still in warmup + string statusOut; + if (RPCIsInWarmup(&statusOut)) + throw JSONRPCError(RPC_IN_WARMUP, "Node is still warming up: " + statusOut); + + // Check if shutdown is requested + if (ShutdownRequested()) + throw JSONRPCError(RPC_MISC_ERROR, "Node is shutting down"); + + // Check if a wallet is already loaded + if (pwalletMain != NULL) + throw JSONRPCError(RPC_WALLET_ERROR, "A wallet is already loaded. Please unload it first using unloadwallet."); + + string strWalletFile = params[0].get_str(); + + // Check if wallet file exists + boost::filesystem::path pathWalletFile = GetDataDir() / strWalletFile; + if (!boost::filesystem::exists(pathWalletFile)) + throw JSONRPCError(RPC_WALLET_ERROR, "Wallet file not found: " + strWalletFile); + + string warningString; + string errorString; + + // Verify wallet file + if (!CWallet::Verify(strWalletFile, warningString, errorString)) { + if (!errorString.empty()) + throw JSONRPCError(RPC_WALLET_ERROR, errorString); + } + + LogPrintf("loadwallet: Loading wallet %s\n", strWalletFile); + + // Create and load the wallet + bool fFirstRun = true; + pwalletMain = new CWallet(strWalletFile); + DBErrors nLoadWalletRet = pwalletMain->LoadWallet(fFirstRun); + + if (nLoadWalletRet != DB_LOAD_OK) { + string strError; + if (nLoadWalletRet == DB_CORRUPT) + strError = "Error loading wallet: Wallet corrupted"; + else if (nLoadWalletRet == DB_NONCRITICAL_ERROR) { + warningString += "Warning: error reading wallet! All keys read correctly, but transaction data or address book entries might be missing or incorrect."; + } + else if (nLoadWalletRet == DB_TOO_NEW) + strError = "Error loading wallet: Wallet requires newer version of ZClassic"; + else if (nLoadWalletRet == DB_NEED_REWRITE) + strError = "Wallet needed to be rewritten: restart ZClassic to complete"; + else + strError = "Error loading wallet"; + + if (!strError.empty()) { + delete pwalletMain; + pwalletMain = NULL; + throw JSONRPCError(RPC_WALLET_ERROR, strError); + } + } + + // Generate HD seed if needed + if (!pwalletMain->HaveHDSeed()) { + pwalletMain->GenerateNewSeed(); + } + + // Set default key if first run + if (fFirstRun) { + CPubKey newDefaultKey; + if (pwalletMain->GetKeyFromPool(newDefaultKey)) { + pwalletMain->SetDefaultKey(newDefaultKey); + pwalletMain->SetAddressBook(pwalletMain->vchDefaultKey.GetID(), "", "receive"); + } + pwalletMain->SetBestChain(chainActive.GetLocator()); + } + + // Register the wallet as a validation interface + RegisterValidationInterface(pwalletMain); + + // Perform blockchain rescan to find transactions for all addresses (required for z-addresses) + LogPrintf("loadwallet: Rescanning blockchain for wallet transactions...\n"); + { + LOCK2(cs_main, pwalletMain->cs_wallet); + pwalletMain->ClearNoteWitnessCache(); + CBlockIndex* pindexRescan = chainActive.Genesis(); + pwalletMain->ScanForWalletTransactions(pindexRescan, true); + pwalletMain->SetBestChain(chainActive.GetLocator()); + } + + // Re-enable mining if configured +#ifdef ENABLE_MINING + if (pwalletMain || !GetArg("-mineraddress", "").empty()) + GenerateBitcoins(GetBoolArg("-gen", false), GetArg("-genproclimit", 1), Params()); +#endif + + LogPrintf("loadwallet: Wallet %s loaded successfully\n", strWalletFile); + + UniValue result(UniValue::VOBJ); + result.push_back(Pair("name", strWalletFile)); + if (!warningString.empty()) + result.push_back(Pair("warning", warningString)); + + return result; +} + +UniValue unloadwallet(const UniValue& params, bool fHelp) +{ + if (fHelp || params.size() != 0) + throw runtime_error( + "unloadwallet\n" + "\nUnloads the currently loaded wallet.\n" + "Waits for any pending async operations to complete before unloading.\n" + "\nResult:\n" + "\"message\" (string) Confirmation message.\n" + "\nExamples:\n" + + HelpExampleCli("unloadwallet", "") + + HelpExampleRpc("unloadwallet", "") + ); + + // Check if shutdown is requested + if (ShutdownRequested()) + throw JSONRPCError(RPC_MISC_ERROR, "Node is shutting down"); + + // Check if a wallet is loaded + if (pwalletMain == NULL) + throw JSONRPCError(RPC_WALLET_ERROR, "No wallet is currently loaded."); + + LogPrintf("unloadwallet: Starting wallet unload...\n"); + + // Wait for all pending async operations to complete + std::shared_ptr q = getAsyncRPCQueue(); + if (q->getOperationCount() > 0) { + LogPrintf("unloadwallet: Waiting for %d pending async operations to complete...\n", q->getOperationCount()); + q->finishAndWait(); + } + + // Stop mining if running +#ifdef ENABLE_MINING + GenerateBitcoins(false, 0, Params()); +#endif + + // Unregister node signals + UnregisterNodeSignals(GetNodeSignals()); + + // Flush wallet to disk + if (pwalletMain) { + pwalletMain->Flush(false); // Non-shutdown flush + pwalletMain->Flush(true); // Shutdown flush + } + + // Unregister validation interface + UnregisterValidationInterface(pwalletMain); + + // Cleanup wallet (securely wipe sensitive data) + if (pwalletMain) { + pwalletMain->CleanupForUnload(); + } + + // Delete the wallet object + delete pwalletMain; + pwalletMain = NULL; + + // Reset the BerkeleyDB environment + bitdb.Reset(); + + // Re-register node signals + RegisterNodeSignals(GetNodeSignals()); + + LogPrintf("unloadwallet: Wallet unloaded successfully\n"); + + return "Wallet unloaded successfully."; +} + static const CRPCCommand commands[] = { // category name actor (function) okSafeMode // --------------------- ------------------------ ----------------------- ---------- @@ -4633,6 +4824,7 @@ static const CRPCCommand commands[] = { "wallet", "listsinceblock", &listsinceblock, false }, { "wallet", "listtransactions", &listtransactions, false }, { "wallet", "listunspent", &listunspent, false }, + { "wallet", "loadwallet", &loadwallet, true }, { "wallet", "lockunspent", &lockunspent, true }, { "wallet", "move", &movecmd, false }, { "wallet", "sendfrom", &sendfrom, false }, @@ -4641,6 +4833,7 @@ static const CRPCCommand commands[] = { "wallet", "setaccount", &setaccount, true }, { "wallet", "settxfee", &settxfee, true }, { "wallet", "signmessage", &signmessage, true }, + { "wallet", "unloadwallet", &unloadwallet, true }, { "wallet", "walletlock", &walletlock, true }, { "wallet", "walletpassphrasechange", &walletpassphrasechange, true }, { "wallet", "walletpassphrase", &walletpassphrase, true }, diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 5973c494d1c..7e4142ba8e2 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -18,6 +18,7 @@ #include "script/script.h" #include "script/sighashtype.h" #include "script/sign.h" +#include "support/cleanse.h" #include "timedata.h" #include "utilmoneystr.h" #include "zcash/Note.hpp" @@ -751,6 +752,63 @@ void CWallet::Flush(bool shutdown) bitdb.Flush(shutdown); } +void CWallet::CleanupForUnload() +{ + LOCK(cs_wallet); + + // Clear wallet transactions + mapWallet.clear(); + + // Clear nullifier maps + mapSproutNullifiersToNotes.clear(); + mapSaplingNullifiersToNotes.clear(); + + // Clear address book + mapAddressBook.clear(); + + // Clear key pool + setKeyPool.clear(); + + // Clear key metadata + mapKeyMetadata.clear(); + mapSproutZKeyMetadata.clear(); + mapSaplingZKeyMetadata.clear(); + + // Clear master keys (securely wipe and explicitly clear vectors) + for (auto& entry : mapMasterKeys) { + if (!entry.second.vchCryptedKey.empty()) { + memory_cleanse(entry.second.vchCryptedKey.data(), entry.second.vchCryptedKey.size()); + entry.second.vchCryptedKey.clear(); + } + if (!entry.second.vchSalt.empty()) { + memory_cleanse(entry.second.vchSalt.data(), entry.second.vchSalt.size()); + entry.second.vchSalt.clear(); + } + if (!entry.second.vchOtherDerivationParameters.empty()) { + memory_cleanse(entry.second.vchOtherDerivationParameters.data(), entry.second.vchOtherDerivationParameters.size()); + entry.second.vchOtherDerivationParameters.clear(); + } + } + mapMasterKeys.clear(); + + // Clear locked coins/notes + setLockedCoins.clear(); + setLockedSproutNotes.clear(); + setLockedSaplingNotes.clear(); + + // Clear request count + mapRequestCount.clear(); + + // Clear default key + vchDefaultKey = CPubKey(); + + // Clear witness cache + ClearNoteWitnessCache(); + + // Call base class cleanup to wipe all cryptographic keys + CleanupKeys(); +} + bool CWallet::Verify(const string& walletFile, string& warningString, string& errorString) { if (!bitdb.Open(GetDataDir())) diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index adb5a39d0ec..70041c2a084 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1241,6 +1241,9 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface //! Verify the wallet database and perform salvage if required static bool Verify(const std::string& walletFile, std::string& warningString, std::string& errorString); + + //! Cleanup wallet for unloading (securely wipe sensitive data) + void CleanupForUnload(); /** * Address book entry changed.