From 143c33de48252ae578e56fb05ca35b01510d4328 Mon Sep 17 00:00:00 2001 From: DanS Date: Sun, 23 Aug 2026 06:14:31 +0200 Subject: [PATCH] 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) --- src/wallet/wallet.cpp | 31 +++++++++++++++++++++++++++---- src/wallet/walletdb.cpp | 10 ++++++++++ src/wallet/walletdb.h | 5 +++++ 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index fd2f31fed..5c63a319a 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2523,10 +2523,33 @@ bool CWallet::SetCryptedHDSeed(const uint256& seedFp, const std::vectorWriteCryptedHDSeed(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; } diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index 478a5ec24..4f159f9a1 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -1335,6 +1335,16 @@ bool CWalletDB::WriteCryptedHDSeed(const uint256& seedFp, const std::vector& vchCryptedSecret); + //! Remove the PLAINTEXT hdseed record. Must be called once the seed has been + //! written in encrypted form: CDB::Rewrite (invoked at the end of + //! CWallet::EncryptWallet) copies whatever records still exist into the new + //! file, so a leftover "hdseed" leaves the unencrypted seed on disk forever. + bool EraseHDSeed(const uint256& seedFp); //! write the hdchain model (external chain child index counter) bool WriteHDChain(const CHDChain& chain);