From 6f9e07186d06f772c93832f0dcbd4f9432b44579 Mon Sep 17 00:00:00 2001 From: Nsable-sh Date: Thu, 13 Aug 2026 17:19:37 -0300 Subject: tests: cover portable wallet copies --- src/main_gui.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/main_gui.cpp') diff --git a/src/main_gui.cpp b/src/main_gui.cpp index 3f52e91..50c4f34 100644 --- a/src/main_gui.cpp +++ b/src/main_gui.cpp @@ -114,7 +114,8 @@ static void PrintUsage() printf(" /gen\n"); printf(" /nogui or /daemon\n"); printf(" /selftest=wallet-keypool, wallet-hd, wallet-format, wallet-crypto, wallet-encrypt,\n"); - printf(" net-message, consensus-limits, pool-stratum, parse-money, socks5-proxy, or managed-tor\n"); + printf(" wallet-portability, net-message, consensus-limits, pool-stratum,\n"); + printf(" parse-money, socks5-proxy, or managed-tor\n"); printf("\n"); printf("Mining mode:\n"); printf(" /operator\n"); -- cgit v1.2.3 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/main_gui.cpp') 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 From 805c5757b801d80ea575c6a8b58e2855254f9046 Mon Sep 17 00:00:00 2001 From: Nsable-sh Date: Thu, 13 Aug 2026 17:40:20 -0300 Subject: wallet: export storage audit json --- src/main_gui.cpp | 10 ++++++-- src/selftest.cpp | 38 +++++++++++++++++++++++++++++ src/walletcmd.cpp | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- src/walletcmd.h | 2 +- 4 files changed, 117 insertions(+), 4 deletions(-) (limited to 'src/main_gui.cpp') diff --git a/src/main_gui.cpp b/src/main_gui.cpp index 532bc3b..395cc0f 100644 --- a/src/main_gui.cpp +++ b/src/main_gui.cpp @@ -174,6 +174,8 @@ static void PrintUsage() 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(" /walletstorageauditjson=FILE\n"); + printf(" (write the same audit as deterministic JSON)\n"); printf(" /rescan (walk the chain for coins this wallet owns\n"); printf(" but never recorded, then exit)\n"); printf("\n"); @@ -706,9 +708,13 @@ int main(int argc, char* argv[]) return nRet; } - if (arg(argc,argv,"/walletstorageaudit") || arg(argc,argv,"-walletstorageaudit")) + string strWalletStorageAuditJson = + argval2(argc, argv, "/walletstorageauditjson", "-walletstorageauditjson"); + if (arg(argc,argv,"/walletstorageaudit") || + arg(argc,argv,"-walletstorageaudit") || + !strWalletStorageAuditJson.empty()) { - int nRet = CmdWalletStorageAudit(); + int nRet = CmdWalletStorageAudit(strWalletStorageAuditJson); DBFlush(true); return nRet; } diff --git a/src/selftest.cpp b/src/selftest.cpp index b112238..73c2935 100644 --- a/src/selftest.cpp +++ b/src/selftest.cpp @@ -1329,6 +1329,24 @@ static int RunWalletPortabilitySelfTest() FileContainsText(strOriginalStorageAudit, "malformed records: 0"), "the storage audit recognizes a plaintext phrase wallet") ? 0 : 1; + string strOriginalStorageAuditJson = tmp + "/original-storage-audit.json"; + vector vOriginalStorageAuditJsonArgs; + vOriginalStorageAuditJsonArgs.push_back("-datadir=" + strOriginal); + vOriginalStorageAuditJsonArgs.push_back("-nomanagedtor"); + vOriginalStorageAuditJsonArgs.push_back("-nogui"); + vOriginalStorageAuditJsonArgs.push_back("-walletstorageauditjson=" + + strOriginalStorageAuditJson); + int nOriginalStorageAuditJsonRet = + RunBitflashChild(strExe, vOriginalStorageAuditJsonArgs); + nFail += Check(nOriginalStorageAuditJsonRet == 0 && + FileContainsText(strOriginalStorageAuditJson, + "\"format\": \"bitflash-wallet-storage-audit-v1\"") && + FileContainsText(strOriginalStorageAuditJson, + "\"plain_hd_seed\": \"complete\"") && + FileContainsText(strOriginalStorageAuditJson, + "\"encrypted_hd_seed\": \"none\""), + "the storage audit can write plaintext-wallet JSON") ? 0 : 1; + string strPortableWallet = tmp + "/portable-wallet.dat"; string strBackupOut = tmp + "/backupwallet.txt"; vector vBackupArgs; @@ -1400,6 +1418,26 @@ static int RunWalletPortabilitySelfTest() FileContainsText(strEncryptedStorageAudit, "wallet minimum version: present") && FileContainsText(strEncryptedStorageAudit, "malformed records: 0"), "the storage audit recognizes an encrypted wallet") ? 0 : 1; + + string strEncryptedStorageAuditJson = tmp + "/encrypted-storage-audit.json"; + vector vEncryptedStorageAuditJsonArgs; + vEncryptedStorageAuditJsonArgs.push_back("-datadir=" + strOriginal); + vEncryptedStorageAuditJsonArgs.push_back("-nomanagedtor"); + vEncryptedStorageAuditJsonArgs.push_back("-nogui"); + vEncryptedStorageAuditJsonArgs.push_back("-walletstorageauditjson=" + + strEncryptedStorageAuditJson); + int nEncryptedStorageAuditJsonRet = + RunBitflashChild(strExe, vEncryptedStorageAuditJsonArgs); + nFail += Check(nEncryptedStorageAuditJsonRet == 0 && + FileContainsText(strEncryptedStorageAuditJson, + "\"plain_private_keys\": 0") && + FileContainsText(strEncryptedStorageAuditJson, + "\"plain_hd_seed\": \"none\"") && + FileContainsText(strEncryptedStorageAuditJson, + "\"encrypted_hd_seed\": \"complete\"") && + FileContainsText(strEncryptedStorageAuditJson, + "\"wallet_minimum_version\": \"present\""), + "the storage audit can write encrypted-wallet JSON") ? 0 : 1; } catch (const std::exception& e) { diff --git a/src/walletcmd.cpp b/src/walletcmd.cpp index 896d593..6f8258d 100644 --- a/src/walletcmd.cpp +++ b/src/walletcmd.cpp @@ -605,7 +605,60 @@ static bool ReadWalletStorageCounts(WalletStorageAuditCounts& counts, return true; } -int CmdWalletStorageAudit() +static std::string WalletStorageAuditJson(const WalletStorageAuditCounts& counts) +{ + std::string strJson; + strJson += "{\n"; + strJson += " \"format\": \"bitflash-wallet-storage-audit-v1\",\n"; + strJson += strprintf(" \"records_total\": %u,\n", counts.nTotal); + strJson += strprintf(" \"malformed_records\": %u,\n", counts.nMalformed); + strJson += strprintf(" \"unknown_records\": %u,\n", counts.nUnknown); + strJson += strprintf(" \"plain_private_keys\": %u,\n", counts.nPlainKeys); + strJson += strprintf(" \"encrypted_private_keys\": %u,\n", counts.nEncryptedKeys); + strJson += strprintf(" \"encryption_master_keys\": %u,\n", counts.nMasterKeys); + strJson += strprintf(" \"default_public_key\": \"%s\",\n", + counts.nDefaultKey ? "present" : "none"); + strJson += strprintf(" \"plain_hd_seed\": \"%s\",\n", + HDSeedStorageState(counts.nPlainHDMaster, + counts.nPlainHDChainCode).c_str()); + strJson += strprintf(" \"encrypted_hd_seed\": \"%s\",\n", + HDSeedStorageState(counts.nCryptedHDMaster, + counts.nCryptedHDChainCode).c_str()); + strJson += strprintf(" \"wallet_minimum_version\": \"%s\",\n", + counts.nWalletMinVersion ? "present" : "none"); + strJson += strprintf(" \"keypool_entries\": %u,\n", counts.nPool); + strJson += strprintf(" \"wallet_transactions\": %u,\n", counts.nTransactions); + strJson += strprintf(" \"address_book_labels\": %u,\n", counts.nNames); + strJson += strprintf(" \"settings\": %u,\n", counts.nSettings); + strJson += strprintf(" \"hd_metadata_records\": %u,\n", + counts.nHDNext + counts.nHDSchema + counts.nHDCoinType + + counts.nHDReceiveNext + counts.nHDChangeNext); + strJson += strprintf(" \"database_version_records\": %u\n", counts.nVersion); + strJson += "}\n"; + return strJson; +} + +static bool WriteAuditTextFile(const std::string& strPath, + const std::string& strText, + std::string& strError) +{ + FILE* pf = fopen(strPath.c_str(), "wb"); + if (!pf) + { + strError = "cannot open output file"; + return false; + } + size_t nWritten = fwrite(strText.data(), 1, strText.size(), pf); + bool fCloseOk = fclose(pf) == 0; + if (nWritten != strText.size() || !fCloseOk) + { + strError = "could not write the complete output file"; + return false; + } + return true; +} + +int CmdWalletStorageAudit(const std::string& strJsonOut) { AttachTerminal(); @@ -617,6 +670,22 @@ int CmdWalletStorageAudit() return 1; } + if (!strJsonOut.empty()) + { + std::string strWriteError; + if (!WriteAuditTextFile(strJsonOut, + WalletStorageAuditJson(counts), + strWriteError)) + { + fprintf(stderr, "Cannot write wallet storage audit JSON: %s\n", + strWriteError.c_str()); + return 1; + } + printf("Wallet storage audit written to %s\n", strJsonOut.c_str()); + fflush(stdout); + return counts.nMalformed > 0 ? 2 : 0; + } + printf("Wallet storage audit\n"); printf(" records total: %u\n", counts.nTotal); printf(" malformed records: %u\n", counts.nMalformed); diff --git a/src/walletcmd.h b/src/walletcmd.h index 4a703db..c6342a6 100644 --- a/src/walletcmd.h +++ b/src/walletcmd.h @@ -64,7 +64,7 @@ int CmdRecoveryAudit(); // Diagnostic: count wallet.dat record types without printing keys, // addresses, transaction ids, labels, or other wallet values. -int CmdWalletStorageAudit(); +int CmdWalletStorageAudit(const std::string& strJsonOut = ""); // 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. -- cgit v1.2.3