upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBitflash <admin@bitflash.network>2026-08-14 08:56:57 -0300
committerBitflash <admin@bitflash.network>2026-08-14 08:56:57 -0300
commitcf597ca37aec53b0dd7016399fee73e1959dc604 (patch)
tree8487caa5b4471b1203b46e0f73fce0362bf2c950 /src
parent5306542f6121e0c0b4ed1210e24d55fa4fbb7127 (diff)
parent33af98c042340f6431aa93b4b6f893d85f40f237 (diff)
Merge pull request 'wallet: fail closed on unsafe wallet storage state' (#39) from wallet-storage-sanity-checks into main
Diffstat (limited to 'src')
-rw-r--r--src/main_gui.cpp9
-rw-r--r--src/selftest.cpp27
-rw-r--r--src/walletcmd.cpp135
-rw-r--r--src/walletcmd.h4
4 files changed, 148 insertions, 27 deletions
diff --git a/src/main_gui.cpp b/src/main_gui.cpp
index 395cc0f..8763211 100644
--- a/src/main_gui.cpp
+++ b/src/main_gui.cpp
@@ -176,6 +176,8 @@ static void PrintUsage()
176 printf(" printing wallet values, then exit)\n"); 176 printf(" printing wallet values, then exit)\n");
177 printf(" /walletstorageauditjson=FILE\n"); 177 printf(" /walletstorageauditjson=FILE\n");
178 printf(" (write the same audit as deterministic JSON)\n"); 178 printf(" (write the same audit as deterministic JSON)\n");
179 printf(" /walletstoragecheck (fail closed on inconsistent or unsafe\n");
180 printf(" wallet.dat storage state)\n");
179 printf(" /rescan (walk the chain for coins this wallet owns\n"); 181 printf(" /rescan (walk the chain for coins this wallet owns\n");
180 printf(" but never recorded, then exit)\n"); 182 printf(" but never recorded, then exit)\n");
181 printf("\n"); 183 printf("\n");
@@ -719,6 +721,13 @@ int main(int argc, char* argv[])
719 return nRet; 721 return nRet;
720 } 722 }
721 723
724 if (arg(argc,argv,"/walletstoragecheck") || arg(argc,argv,"-walletstoragecheck"))
725 {
726 int nRet = CmdWalletStorageCheck();
727 DBFlush(true);
728 return nRet;
729 }
730
722 if (arg(argc,argv,"/newphrase") || arg(argc,argv,"-newphrase")) 731 if (arg(argc,argv,"/newphrase") || arg(argc,argv,"-newphrase"))
723 { 732 {
724 int nRet = CmdNewPhrase(); 733 int nRet = CmdNewPhrase();
diff --git a/src/selftest.cpp b/src/selftest.cpp
index 73c2935..5e5bab6 100644
--- a/src/selftest.cpp
+++ b/src/selftest.cpp
@@ -1329,6 +1329,19 @@ static int RunWalletPortabilitySelfTest()
1329 FileContainsText(strOriginalStorageAudit, "malformed records: 0"), 1329 FileContainsText(strOriginalStorageAudit, "malformed records: 0"),
1330 "the storage audit recognizes a plaintext phrase wallet") ? 0 : 1; 1330 "the storage audit recognizes a plaintext phrase wallet") ? 0 : 1;
1331 1331
1332 string strOriginalStorageCheck = tmp + "/original-storage-check.txt";
1333 vector<string> vOriginalStorageCheckArgs;
1334 vOriginalStorageCheckArgs.push_back("-datadir=" + strOriginal);
1335 vOriginalStorageCheckArgs.push_back("-nomanagedtor");
1336 vOriginalStorageCheckArgs.push_back("-nogui");
1337 vOriginalStorageCheckArgs.push_back("-walletstoragecheck");
1338 int nOriginalStorageCheckRet =
1339 RunBitflashChild(strExe, vOriginalStorageCheckArgs, NULL, &strOriginalStorageCheck);
1340 nFail += Check(nOriginalStorageCheckRet == 0 &&
1341 FileContainsText(strOriginalStorageCheck, "storage sanity: ok") &&
1342 FileContainsText(strOriginalStorageCheck, "plain HD seed: complete"),
1343 "the storage sanity check accepts a plaintext phrase wallet") ? 0 : 1;
1344
1332 string strOriginalStorageAuditJson = tmp + "/original-storage-audit.json"; 1345 string strOriginalStorageAuditJson = tmp + "/original-storage-audit.json";
1333 vector<string> vOriginalStorageAuditJsonArgs; 1346 vector<string> vOriginalStorageAuditJsonArgs;
1334 vOriginalStorageAuditJsonArgs.push_back("-datadir=" + strOriginal); 1347 vOriginalStorageAuditJsonArgs.push_back("-datadir=" + strOriginal);
@@ -1419,6 +1432,20 @@ static int RunWalletPortabilitySelfTest()
1419 FileContainsText(strEncryptedStorageAudit, "malformed records: 0"), 1432 FileContainsText(strEncryptedStorageAudit, "malformed records: 0"),
1420 "the storage audit recognizes an encrypted wallet") ? 0 : 1; 1433 "the storage audit recognizes an encrypted wallet") ? 0 : 1;
1421 1434
1435 string strEncryptedStorageCheck = tmp + "/encrypted-storage-check.txt";
1436 vector<string> vEncryptedStorageCheckArgs;
1437 vEncryptedStorageCheckArgs.push_back("-datadir=" + strOriginal);
1438 vEncryptedStorageCheckArgs.push_back("-nomanagedtor");
1439 vEncryptedStorageCheckArgs.push_back("-nogui");
1440 vEncryptedStorageCheckArgs.push_back("-walletstoragecheck");
1441 int nEncryptedStorageCheckRet =
1442 RunBitflashChild(strExe, vEncryptedStorageCheckArgs, NULL, &strEncryptedStorageCheck);
1443 nFail += Check(nEncryptedStorageCheckRet == 0 &&
1444 FileContainsText(strEncryptedStorageCheck, "storage sanity: ok") &&
1445 FileContainsText(strEncryptedStorageCheck, "plain private keys: 0") &&
1446 FileContainsText(strEncryptedStorageCheck, "encrypted HD seed: complete"),
1447 "the storage sanity check accepts an encrypted wallet") ? 0 : 1;
1448
1422 string strEncryptedStorageAuditJson = tmp + "/encrypted-storage-audit.json"; 1449 string strEncryptedStorageAuditJson = tmp + "/encrypted-storage-audit.json";
1423 vector<string> vEncryptedStorageAuditJsonArgs; 1450 vector<string> vEncryptedStorageAuditJsonArgs;
1424 vEncryptedStorageAuditJsonArgs.push_back("-datadir=" + strOriginal); 1451 vEncryptedStorageAuditJsonArgs.push_back("-datadir=" + strOriginal);
diff --git a/src/walletcmd.cpp b/src/walletcmd.cpp
index 6f8258d..ce0da02 100644
--- a/src/walletcmd.cpp
+++ b/src/walletcmd.cpp
@@ -472,33 +472,28 @@ int CmdRecoveryAudit()
472 472
473struct WalletStorageAuditCounts 473struct WalletStorageAuditCounts
474{ 474{
475 unsigned int nTotal; 475 unsigned int nTotal = 0;
476 unsigned int nMalformed; 476 unsigned int nMalformed = 0;
477 unsigned int nUnknown; 477 unsigned int nUnknown = 0;
478 unsigned int nVersion; 478 unsigned int nVersion = 0;
479 unsigned int nNames; 479 unsigned int nNames = 0;
480 unsigned int nTransactions; 480 unsigned int nTransactions = 0;
481 unsigned int nPlainKeys; 481 unsigned int nPlainKeys = 0;
482 unsigned int nEncryptedKeys; 482 unsigned int nEncryptedKeys = 0;
483 unsigned int nMasterKeys; 483 unsigned int nMasterKeys = 0;
484 unsigned int nDefaultKey; 484 unsigned int nDefaultKey = 0;
485 unsigned int nPlainHDMaster; 485 unsigned int nPlainHDMaster = 0;
486 unsigned int nPlainHDChainCode; 486 unsigned int nPlainHDChainCode = 0;
487 unsigned int nCryptedHDMaster; 487 unsigned int nCryptedHDMaster = 0;
488 unsigned int nCryptedHDChainCode; 488 unsigned int nCryptedHDChainCode = 0;
489 unsigned int nWalletMinVersion; 489 unsigned int nWalletMinVersion = 0;
490 unsigned int nPool; 490 unsigned int nPool = 0;
491 unsigned int nSettings; 491 unsigned int nSettings = 0;
492 unsigned int nHDNext; 492 unsigned int nHDNext = 0;
493 unsigned int nHDSchema; 493 unsigned int nHDSchema = 0;
494 unsigned int nHDCoinType; 494 unsigned int nHDCoinType = 0;
495 unsigned int nHDReceiveNext; 495 unsigned int nHDReceiveNext = 0;
496 unsigned int nHDChangeNext; 496 unsigned int nHDChangeNext = 0;
497
498 WalletStorageAuditCounts()
499 {
500 memset(this, 0, sizeof(*this));
501 }
502}; 497};
503 498
504static std::string HDSeedStorageState(unsigned int nMaster, unsigned int nChain) 499static std::string HDSeedStorageState(unsigned int nMaster, unsigned int nChain)
@@ -658,6 +653,92 @@ static bool WriteAuditTextFile(const std::string& strPath,
658 return true; 653 return true;
659} 654}
660 655
656static void AddWalletStorageFailure(std::vector<std::string>& vFailures,
657 const std::string& strFailure)
658{
659 vFailures.push_back(strFailure);
660}
661
662static void BuildWalletStorageSanityFailures(const WalletStorageAuditCounts& counts,
663 std::vector<std::string>& vFailures)
664{
665 bool fPlainHD = counts.nPlainHDMaster > 0 || counts.nPlainHDChainCode > 0;
666 bool fCryptedHD = counts.nCryptedHDMaster > 0 || counts.nCryptedHDChainCode > 0;
667 bool fEncryptedRecords = counts.nEncryptedKeys > 0 ||
668 counts.nMasterKeys > 0 ||
669 fCryptedHD;
670
671 if (counts.nMalformed > 0)
672 AddWalletStorageFailure(vFailures, "wallet.dat contains malformed records");
673 if (counts.nUnknown > 0)
674 AddWalletStorageFailure(vFailures, "wallet.dat contains unknown records");
675 if ((counts.nPlainHDMaster > 0) != (counts.nPlainHDChainCode > 0))
676 AddWalletStorageFailure(vFailures, "plain HD seed is incomplete");
677 if ((counts.nCryptedHDMaster > 0) != (counts.nCryptedHDChainCode > 0))
678 AddWalletStorageFailure(vFailures, "encrypted HD seed is incomplete");
679 if (fPlainHD && fCryptedHD)
680 AddWalletStorageFailure(vFailures, "wallet.dat contains both plain and encrypted HD seed records");
681 if (counts.nDefaultKey > 1)
682 AddWalletStorageFailure(vFailures, "wallet.dat contains more than one default public key record");
683 if (counts.nWalletMinVersion > 1)
684 AddWalletStorageFailure(vFailures, "wallet.dat contains more than one minimum-version record");
685
686 if (fEncryptedRecords)
687 {
688 if (counts.nPlainKeys > 0)
689 AddWalletStorageFailure(vFailures, "encrypted wallet still contains plain private key records");
690 if (fPlainHD)
691 AddWalletStorageFailure(vFailures, "encrypted wallet still contains plain HD seed records");
692 if (counts.nMasterKeys == 0)
693 AddWalletStorageFailure(vFailures, "encrypted wallet has no encryption master key record");
694 if (counts.nWalletMinVersion == 0)
695 AddWalletStorageFailure(vFailures, "encrypted wallet has no minimum-version record");
696 }
697}
698
699int CmdWalletStorageCheck()
700{
701 AttachTerminal();
702
703 WalletStorageAuditCounts counts;
704 std::string strError;
705 if (!ReadWalletStorageCounts(counts, strError))
706 {
707 fprintf(stderr, "Cannot check wallet storage: %s\n", strError.c_str());
708 return 1;
709 }
710
711 std::vector<std::string> vFailures;
712 BuildWalletStorageSanityFailures(counts, vFailures);
713
714 printf("Wallet storage sanity check\n");
715 printf(" records total: %u\n", counts.nTotal);
716 printf(" encrypted records: %s\n",
717 (counts.nEncryptedKeys || counts.nMasterKeys ||
718 counts.nCryptedHDMaster || counts.nCryptedHDChainCode) ? "yes" : "no");
719 printf(" plain private keys: %u\n", counts.nPlainKeys);
720 printf(" encrypted private keys: %u\n", counts.nEncryptedKeys);
721 printf(" plain HD seed: %s\n",
722 HDSeedStorageState(counts.nPlainHDMaster,
723 counts.nPlainHDChainCode).c_str());
724 printf(" encrypted HD seed: %s\n",
725 HDSeedStorageState(counts.nCryptedHDMaster,
726 counts.nCryptedHDChainCode).c_str());
727 if (vFailures.empty())
728 {
729 printf(" storage sanity: ok\n");
730 fflush(stdout);
731 return 0;
732 }
733
734 printf(" storage sanity: failed\n");
735 for (std::vector<std::string>::const_iterator it = vFailures.begin();
736 it != vFailures.end(); ++it)
737 printf(" failure: %s\n", it->c_str());
738 fflush(stdout);
739 return 2;
740}
741
661int CmdWalletStorageAudit(const std::string& strJsonOut) 742int CmdWalletStorageAudit(const std::string& strJsonOut)
662{ 743{
663 AttachTerminal(); 744 AttachTerminal();
diff --git a/src/walletcmd.h b/src/walletcmd.h
index c6342a6..6077669 100644
--- a/src/walletcmd.h
+++ b/src/walletcmd.h
@@ -66,6 +66,10 @@ int CmdRecoveryAudit();
66// addresses, transaction ids, labels, or other wallet values. 66// addresses, transaction ids, labels, or other wallet values.
67int CmdWalletStorageAudit(const std::string& strJsonOut = ""); 67int CmdWalletStorageAudit(const std::string& strJsonOut = "");
68 68
69// Diagnostic: fail closed when wallet.dat contains an internally inconsistent
70// or migration-unsafe mix of storage records.
71int CmdWalletStorageCheck();
72
69// Rewrite wallet.dat so private keys and the HD seed are encrypted with the 73// Rewrite wallet.dat so private keys and the HD seed are encrypted with the
70// given passphrase. Returns 0 on success and exits without starting the node. 74// given passphrase. Returns 0 on success and exits without starting the node.
71int CmdEncryptWallet(const std::string& strPassphrase); 75int CmdEncryptWallet(const std::string& strPassphrase);