From f57d58155cdf65ec2ddb1a01c9d9466bf60df9ab Mon Sep 17 00:00:00 2001 From: Nsable-sh Date: Thu, 13 Aug 2026 17:15:24 -0300 Subject: wallet: report locked encrypted recovery seed --- src/main.cpp | 3 +++ src/main.h | 2 ++ src/selftest.cpp | 44 +++++++++++++++++++++++++++++++++++++++++--- src/walletcmd.cpp | 16 +++++++++++++++- 4 files changed, 61 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/main.cpp b/src/main.cpp index f83164f..5bd4028 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4355,6 +4355,9 @@ WalletRecoveryAudit GetWalletRecoveryAudit() CRITICAL_BLOCK(cs_keyPool) { audit.fHaveSeed = HaveHDSeed(); + audit.fSeedEncryptedLocked = !audit.fHaveSeed && + (!vchCryptedHDMaster.empty() || + !vchCryptedHDChainCode.empty()); audit.nSchema = nHDKeySchema; audit.nDerivedKnown = nHDNext; audit.nReceiveNext = nHDReceiveNext; diff --git a/src/main.h b/src/main.h index b389f1d..dc5527e 100644 --- a/src/main.h +++ b/src/main.h @@ -292,6 +292,7 @@ int64 GetBalance(); struct WalletRecoveryAudit { bool fHaveSeed; + bool fSeedEncryptedLocked; bool fDeriveComplete; int nSchema; unsigned int nDerivedKnown; @@ -311,6 +312,7 @@ struct WalletRecoveryAudit WalletRecoveryAudit() { fHaveSeed = false; + fSeedEncryptedLocked = false; fDeriveComplete = true; nSchema = HD_SCHEMA_NONE; nDerivedKnown = 0; diff --git a/src/selftest.cpp b/src/selftest.cpp index 3c93927..79feddf 100644 --- a/src/selftest.cpp +++ b/src/selftest.cpp @@ -941,7 +941,8 @@ static string QuoteCommandArg(const string& str) static int RunBitflashChild(const string& strExe, const vector& vArgs, - const string* pStdinFile = NULL) + const string* pStdinFile = NULL, + const string* pOutputFile = NULL) { #ifdef _WIN32 vector argv; @@ -954,8 +955,16 @@ static int RunBitflashChild(const string& strExe, int nOldErr = _dup(2); int nOldIn = _dup(0); int nNull = _open("NUL", _O_WRONLY); + int nOut = -1; int nIn = -1; - if (nNull >= 0) + if (pOutputFile) + nOut = _open(pOutputFile->c_str(), _O_WRONLY|_O_CREAT|_O_TRUNC|_O_BINARY, _S_IREAD|_S_IWRITE); + if (nOut >= 0) + { + _dup2(nOut, 1); + _dup2(nOut, 2); + } + else if (nNull >= 0) { _dup2(nNull, 1); _dup2(nNull, 2); @@ -969,6 +978,8 @@ static int RunBitflashChild(const string& strExe, int nRet = _spawnv(_P_WAIT, strExe.c_str(), &argv[0]); if (nIn >= 0) _close(nIn); + if (nOut >= 0) + _close(nOut); if (nNull >= 0) _close(nNull); if (nOldIn >= 0) @@ -993,7 +1004,10 @@ static int RunBitflashChild(const string& strExe, strCmd += " " + QuoteCommandArg(vArgs[i]); if (pStdinFile) strCmd += " < " + QuoteCommandArg(*pStdinFile); - strCmd += " > /dev/null 2>&1"; + if (pOutputFile) + strCmd += " > " + QuoteCommandArg(*pOutputFile) + " 2>&1"; + else + strCmd += " > /dev/null 2>&1"; return system(strCmd.c_str()); #endif } @@ -1066,6 +1080,8 @@ static int RunWalletEncryptSelfTest() string strRightDump = tmp + "/right-pass-dump.txt"; string strLiteralDump = tmp + "/literal-pass-dump.txt"; string strStdinDump = tmp + "/stdin-pass-dump.txt"; + string strLockedAudit = tmp + "/locked-recovery-audit.txt"; + string strUnlockedAudit = tmp + "/unlocked-recovery-audit.txt"; string strWrongPassFile = tmp + "/wrong-pass.txt"; string strRightPassFile = tmp + "/right-pass.txt"; nFail += Check(WriteTextFile(strWrongPassFile, "wrong-passphrase\n") && @@ -1081,6 +1097,17 @@ static int RunWalletEncryptSelfTest() nFail += Check(nWrongRet != 0 && !FileExists(strWrongDump.c_str()), "a restarted wallet rejects the wrong-passphrase") ? 0 : 1; + vector vLockedAuditArgs; + vLockedAuditArgs.push_back("-datadir=" + tmp); + vLockedAuditArgs.push_back("-recoveryaudit"); + vLockedAuditArgs.push_back("-nogui"); + int nLockedAuditRet = RunBitflashChild(strExe, vLockedAuditArgs, NULL, &strLockedAudit); + nFail += Check(nLockedAuditRet == 2 && + FileContainsText(strLockedAudit, "recovery phrase: encrypted, unlock wallet to audit") && + FileContainsText(strLockedAudit, "recovery coverage: unavailable while wallet is locked") && + FileContainsText(strLockedAudit, "Unlock the wallet with /walletpassphrase"), + "a locked encrypted wallet reports that phrase coverage needs unlock") ? 0 : 1; + vector vLiteralArgs; vLiteralArgs.push_back("-datadir=" + tmp); vLiteralArgs.push_back("-walletpassphrase=btf-test-passphrase"); @@ -1101,6 +1128,17 @@ static int RunWalletEncryptSelfTest() nFail += Check(FileContainsText(strRightDump, HexStrLocal(vchDefaultPrivBytes)), "the restarted wallet can decrypt and dump the original key") ? 0 : 1; + vector vUnlockedAuditArgs; + vUnlockedAuditArgs.push_back("-datadir=" + tmp); + vUnlockedAuditArgs.push_back("-walletpassphrase=@" + strRightPassFile); + vUnlockedAuditArgs.push_back("-recoveryaudit"); + vUnlockedAuditArgs.push_back("-nogui"); + int nUnlockedAuditRet = RunBitflashChild(strExe, vUnlockedAuditArgs, NULL, &strUnlockedAudit); + nFail += Check(nUnlockedAuditRet == 0 && + FileContainsText(strUnlockedAudit, "recovery phrase: present") && + !FileContainsText(strUnlockedAudit, "recovery phrase: not installed"), + "an unlocked encrypted wallet audits the installed recovery phrase") ? 0 : 1; + vector vStdinArgs; vStdinArgs.push_back("-datadir=" + tmp); vStdinArgs.push_back("-walletpassphrase"); diff --git a/src/walletcmd.cpp b/src/walletcmd.cpp index e8ac4bf..eccb9b3 100644 --- a/src/walletcmd.cpp +++ b/src/walletcmd.cpp @@ -400,7 +400,12 @@ int CmdRecoveryAudit() int64 nImmatureTotal = audit.nRecoverableImmatureCredit + audit.nLegacyImmatureCredit; printf("Wallet recovery audit\n"); - printf(" recovery phrase: %s\n", audit.fHaveSeed ? "present" : "not installed"); + if (audit.fHaveSeed) + printf(" recovery phrase: present\n"); + else if (audit.fSeedEncryptedLocked) + printf(" recovery phrase: encrypted, unlock wallet to audit\n"); + else + printf(" recovery phrase: not installed\n"); if (audit.fHaveSeed) { printf(" derivation schema: %s\n", HDKeySchemaName(audit.nSchema).c_str()); @@ -412,6 +417,15 @@ int CmdRecoveryAudit() if (!audit.fDeriveComplete) printf(" derivation warning: %s\n", audit.strDeriveError.c_str()); printf(" total spendable balance: %s BTF\n", FormatMoney(nTotal).c_str()); + if (audit.fSeedEncryptedLocked) + { + printf(" recovery coverage: unavailable while wallet is locked\n"); + printf(" immature mining rewards: %s BTF\n", FormatMoney(nImmatureTotal).c_str()); + printf("\n"); + printf("Unlock the wallet with /walletpassphrase or /walletpassphrase=@FILE to audit phrase coverage.\n"); + fflush(stdout); + return 2; + } printf(" covered by recovery phrase: %s BTF (%d transaction(s))\n", FormatMoney(audit.nRecoverableCredit).c_str(), audit.nRecoverableTx); printf(" wallet.dat-only balance: %s BTF (%d transaction(s))\n", -- cgit v1.2.3