wallet: erase the plaintext HD seed record when the wallet is encrypted
CWalletDB::WriteCryptedHDSeed wrote the "chdseed" record and left "hdseed" in place, unlike WriteCryptedKey which erases "key"/"wkey" after writing "ckey". No erase of "hdseed" existed anywhere in src/wallet/. CDB::Rewrite does not save us: EncryptWallet calls it with pszSkip defaulted, so it copies every surviving record verbatim into the new file. The result is that a wallet created unencrypted and later encrypted keeps its raw HD seed in cleartext on disk permanently, and reloads it into memory on every start. Add CWalletDB::EraseHDSeed and call it from CWallet::SetCryptedHDSeed after the encrypted record is written, through the same CWalletDB so it shares EncryptWallet's transaction. Erase returns true on DB_NOTFOUND, so a wallet that was never written in plaintext is unaffected. The erase is deliberately best-effort and only logs on failure. A hard failure here propagates into CCryptoKeyStore::EncryptKeys, which EncryptWallet turns into assert(false) with half the keys encrypted in memory; a warning is strictly better than that. Note this path is only reachable with -developerencryptwallet, which is experimental and off by default on this chain, so this is a latent fix rather than a live one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+27
-4
@@ -2523,10 +2523,33 @@ bool CWallet::SetCryptedHDSeed(const uint256& seedFp, const std::vector<unsigned
|
||||
|
||||
{
|
||||
LOCK(cs_wallet);
|
||||
if (pwalletdbEncryption)
|
||||
return pwalletdbEncryption->WriteCryptedHDSeed(seedFp, vchCryptedSecret);
|
||||
else
|
||||
return CWalletDB(strWalletFile).WriteCryptedHDSeed(seedFp, vchCryptedSecret);
|
||||
// Write the encrypted record, then drop the plaintext one. Both go
|
||||
// through the same CWalletDB (and therefore the same transaction when
|
||||
// EncryptWallet supplied pwalletdbEncryption), because CDB::Rewrite at
|
||||
// the end of EncryptWallet copies every surviving record into the fresh
|
||||
// file -- a leftover plaintext "hdseed" would keep the unencrypted seed
|
||||
// on disk for the life of the wallet.
|
||||
//
|
||||
// The erase is deliberately best-effort: a hard failure here propagates
|
||||
// into CCryptoKeyStore::EncryptKeys, which CWallet::EncryptWallet turns
|
||||
// into assert(false) with half the keys encrypted in memory. A logged
|
||||
// warning is strictly better than that.
|
||||
if (pwalletdbEncryption) {
|
||||
if (!pwalletdbEncryption->WriteCryptedHDSeed(seedFp, vchCryptedSecret))
|
||||
return false;
|
||||
if (!pwalletdbEncryption->EraseHDSeed(seedFp))
|
||||
LogPrintf("%s: WARNING: could not erase the plaintext hdseed record; "
|
||||
"the unencrypted HD seed may remain in wallet.dat\n", __func__);
|
||||
return true;
|
||||
} else {
|
||||
CWalletDB walletdb(strWalletFile);
|
||||
if (!walletdb.WriteCryptedHDSeed(seedFp, vchCryptedSecret))
|
||||
return false;
|
||||
if (!walletdb.EraseHDSeed(seedFp))
|
||||
LogPrintf("%s: WARNING: could not erase the plaintext hdseed record; "
|
||||
"the unencrypted HD seed may remain in wallet.dat\n", __func__);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user