From 8e62e362e2fbe6fd6c5d66ddb354ec544f8cf0db Mon Sep 17 00:00:00 2001 From: Nsable-sh Date: Thu, 13 Aug 2026 17:36:09 -0300 Subject: wallet: add storage audit command --- src/main_gui.cpp | 9 +++ src/selftest.cpp | 32 ++++++++++ src/walletcmd.cpp | 178 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/walletcmd.h | 4 ++ 4 files changed, 223 insertions(+) (limited to 'src') diff --git a/src/main_gui.cpp b/src/main_gui.cpp index 50c4f34..532bc3b 100644 --- a/src/main_gui.cpp +++ b/src/main_gui.cpp @@ -172,6 +172,8 @@ static void PrintUsage() printf(" /recoveryaudit (show how much spendable balance is\n"); printf(" covered by the recovery phrase;\n"); printf(" exits 2 when wallet.dat is still needed)\n"); + printf(" /walletstorageaudit (count wallet.dat record types without\n"); + printf(" printing wallet values, then exit)\n"); printf(" /rescan (walk the chain for coins this wallet owns\n"); printf(" but never recorded, then exit)\n"); printf("\n"); @@ -704,6 +706,13 @@ int main(int argc, char* argv[]) return nRet; } + if (arg(argc,argv,"/walletstorageaudit") || arg(argc,argv,"-walletstorageaudit")) + { + int nRet = CmdWalletStorageAudit(); + DBFlush(true); + return nRet; + } + if (arg(argc,argv,"/newphrase") || arg(argc,argv,"-newphrase")) { int nRet = CmdNewPhrase(); diff --git a/src/selftest.cpp b/src/selftest.cpp index 8a448f0..b112238 100644 --- a/src/selftest.cpp +++ b/src/selftest.cpp @@ -1314,6 +1314,21 @@ static int RunWalletPortabilitySelfTest() FileContainsText(strOriginalAudit, "SLIP-0044 BITFLASH"), "the original wallet reports its recovery phrase") ? 0 : 1; + string strOriginalStorageAudit = tmp + "/original-storage-audit.txt"; + vector vOriginalStorageAuditArgs; + vOriginalStorageAuditArgs.push_back("-datadir=" + strOriginal); + vOriginalStorageAuditArgs.push_back("-nomanagedtor"); + vOriginalStorageAuditArgs.push_back("-nogui"); + vOriginalStorageAuditArgs.push_back("-walletstorageaudit"); + int nOriginalStorageAuditRet = + RunBitflashChild(strExe, vOriginalStorageAuditArgs, NULL, &strOriginalStorageAudit); + nFail += Check(nOriginalStorageAuditRet == 0 && + FileContainsText(strOriginalStorageAudit, "Wallet storage audit") && + FileContainsText(strOriginalStorageAudit, "plain HD seed: complete") && + FileContainsText(strOriginalStorageAudit, "encrypted HD seed: none") && + FileContainsText(strOriginalStorageAudit, "malformed records: 0"), + "the storage audit recognizes a plaintext phrase wallet") ? 0 : 1; + string strPortableWallet = tmp + "/portable-wallet.dat"; string strBackupOut = tmp + "/backupwallet.txt"; vector vBackupArgs; @@ -1368,6 +1383,23 @@ static int RunWalletPortabilitySelfTest() nFail += Check(nEncryptRet == 0 && !DirectoryHasFileWithPrefix(strOriginal + "/database", "log."), "the encryptwallet command purges Berkeley DB environment logs") ? 0 : 1; + + string strEncryptedStorageAudit = tmp + "/encrypted-storage-audit.txt"; + vector vEncryptedStorageAuditArgs; + vEncryptedStorageAuditArgs.push_back("-datadir=" + strOriginal); + vEncryptedStorageAuditArgs.push_back("-nomanagedtor"); + vEncryptedStorageAuditArgs.push_back("-nogui"); + vEncryptedStorageAuditArgs.push_back("-walletstorageaudit"); + int nEncryptedStorageAuditRet = + RunBitflashChild(strExe, vEncryptedStorageAuditArgs, NULL, &strEncryptedStorageAudit); + nFail += Check(nEncryptedStorageAuditRet == 0 && + FileContainsText(strEncryptedStorageAudit, "plain private keys: 0") && + FileContainsText(strEncryptedStorageAudit, "encrypted private keys:") && + FileContainsText(strEncryptedStorageAudit, "plain HD seed: none") && + FileContainsText(strEncryptedStorageAudit, "encrypted HD seed: complete") && + FileContainsText(strEncryptedStorageAudit, "wallet minimum version: present") && + FileContainsText(strEncryptedStorageAudit, "malformed records: 0"), + "the storage audit recognizes an encrypted wallet") ? 0 : 1; } catch (const std::exception& e) { diff --git a/src/walletcmd.cpp b/src/walletcmd.cpp index eccb9b3..896d593 100644 --- a/src/walletcmd.cpp +++ b/src/walletcmd.cpp @@ -470,6 +470,184 @@ int CmdRecoveryAudit() return 0; } +struct WalletStorageAuditCounts +{ + unsigned int nTotal; + unsigned int nMalformed; + unsigned int nUnknown; + unsigned int nVersion; + unsigned int nNames; + unsigned int nTransactions; + unsigned int nPlainKeys; + unsigned int nEncryptedKeys; + unsigned int nMasterKeys; + unsigned int nDefaultKey; + unsigned int nPlainHDMaster; + unsigned int nPlainHDChainCode; + unsigned int nCryptedHDMaster; + unsigned int nCryptedHDChainCode; + unsigned int nWalletMinVersion; + unsigned int nPool; + unsigned int nSettings; + unsigned int nHDNext; + unsigned int nHDSchema; + unsigned int nHDCoinType; + unsigned int nHDReceiveNext; + unsigned int nHDChangeNext; + + WalletStorageAuditCounts() + { + memset(this, 0, sizeof(*this)); + } +}; + +static std::string HDSeedStorageState(unsigned int nMaster, unsigned int nChain) +{ + if (nMaster == 0 && nChain == 0) + return "none"; + if (nMaster > 0 && nChain > 0) + return "complete"; + return "incomplete"; +} + +static void CountWalletStorageType(const std::string& strType, + WalletStorageAuditCounts& c) +{ + if (strType == "version") + c.nVersion++; + else if (strType == "name") + c.nNames++; + else if (strType == "tx") + c.nTransactions++; + else if (strType == "key") + c.nPlainKeys++; + else if (strType == "ckey") + c.nEncryptedKeys++; + else if (strType == "mkey") + c.nMasterKeys++; + else if (strType == "defaultkey") + c.nDefaultKey++; + else if (strType == "hdmaster") + c.nPlainHDMaster++; + else if (strType == "hdchaincode") + c.nPlainHDChainCode++; + else if (strType == "cryptedhdmaster") + c.nCryptedHDMaster++; + else if (strType == "cryptedhdchaincode") + c.nCryptedHDChainCode++; + else if (strType == "walletminversion") + c.nWalletMinVersion++; + else if (strType == "pool") + c.nPool++; + else if (strType == "setting") + c.nSettings++; + else if (strType == "hdnext") + c.nHDNext++; + else if (strType == "hdschema") + c.nHDSchema++; + else if (strType == "hdcointype") + c.nHDCoinType++; + else if (strType == "hdreceivenext") + c.nHDReceiveNext++; + else if (strType == "hdchangenext") + c.nHDChangeNext++; + else + c.nUnknown++; +} + +static bool ReadWalletStorageCounts(WalletStorageAuditCounts& counts, + std::string& strError) +{ + class CWalletAuditDB : public CWalletDB + { + public: + CWalletAuditDB() : CWalletDB("r") { } + using CDB::GetCursor; + using CDB::ReadAtCursor; + }; + + CWalletAuditDB walletdb; + Dbc* pcursor = walletdb.GetCursor(); + if (!pcursor) + { + strError = "cannot open wallet.dat cursor"; + return false; + } + + for (;;) + { + CDataStream ssKey(SER_DISK); + CDataStream ssValue(SER_DISK); + int ret = walletdb.ReadAtCursor(pcursor, ssKey, ssValue); + if (ret == DB_NOTFOUND) + break; + if (ret != 0) + { + pcursor->close(); + strError = strprintf("Berkeley DB cursor read failed (%d)", ret); + return false; + } + + counts.nTotal++; + try + { + std::string strType; + ssKey >> strType; + CountWalletStorageType(strType, counts); + } + catch (...) + { + counts.nMalformed++; + } + } + + pcursor->close(); + return true; +} + +int CmdWalletStorageAudit() +{ + AttachTerminal(); + + WalletStorageAuditCounts counts; + std::string strError; + if (!ReadWalletStorageCounts(counts, strError)) + { + fprintf(stderr, "Cannot audit wallet storage: %s\n", strError.c_str()); + return 1; + } + + printf("Wallet storage audit\n"); + printf(" records total: %u\n", counts.nTotal); + printf(" malformed records: %u\n", counts.nMalformed); + printf(" unknown records: %u\n", counts.nUnknown); + printf(" plain private keys: %u\n", counts.nPlainKeys); + printf(" encrypted private keys: %u\n", counts.nEncryptedKeys); + printf(" encryption master keys: %u\n", counts.nMasterKeys); + printf(" default public key: %s\n", counts.nDefaultKey ? "present" : "none"); + printf(" plain HD seed: %s\n", + HDSeedStorageState(counts.nPlainHDMaster, + counts.nPlainHDChainCode).c_str()); + printf(" encrypted HD seed: %s\n", + HDSeedStorageState(counts.nCryptedHDMaster, + counts.nCryptedHDChainCode).c_str()); + printf(" wallet minimum version: %s\n", + counts.nWalletMinVersion ? "present" : "none"); + printf(" keypool entries: %u\n", counts.nPool); + printf(" wallet transactions: %u\n", counts.nTransactions); + printf(" address book labels: %u\n", counts.nNames); + printf(" settings: %u\n", counts.nSettings); + printf(" HD metadata records: %u\n", + counts.nHDNext + counts.nHDSchema + counts.nHDCoinType + + counts.nHDReceiveNext + counts.nHDChangeNext); + printf(" database version records: %u\n", counts.nVersion); + fflush(stdout); + + if (counts.nMalformed > 0) + return 2; + return 0; +} + int CmdEncryptWallet(const std::string& strPassphrase) { AttachTerminal(); diff --git a/src/walletcmd.h b/src/walletcmd.h index 007b8b5..4a703db 100644 --- a/src/walletcmd.h +++ b/src/walletcmd.h @@ -62,6 +62,10 @@ int CmdShowDerived(int nCount); // by the installed recovery phrase or still depends on wallet.dat-only keys. int CmdRecoveryAudit(); +// Diagnostic: count wallet.dat record types without printing keys, +// addresses, transaction ids, labels, or other wallet values. +int CmdWalletStorageAudit(); + // Rewrite wallet.dat so private keys and the HD seed are encrypted with the // given passphrase. Returns 0 on success and exits without starting the node. int CmdEncryptWallet(const std::string& strPassphrase); -- cgit v1.2.3