From 96de03663ea8184ccefa7a238ba4f20060f68a90 Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Tue, 4 Nov 2025 23:51:09 +0300 Subject: [PATCH 1/2] fix: HD chain encryption check ordering issue MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, LoadHDChain() would fail if CRYPTED_HDCHAIN records were read before MASTER_KEY records during wallet loading, because the check `m_storage.HasEncryptionKeys() != chain.IsCrypted()` would incorrectly fail when mapMasterKeys was still empty. This commit fixes the issue by: - Adding an optional fSkipEncryptionCheck parameter to LoadHDChain() - Skipping the encryption check during wallet loading in ReadKeyValue() - Adding comprehensive validation in LoadWallet() after all records are loaded to ensure HD chain encryption consistency This preserves the safety check for encryption operations while allowing wallet loading to succeed regardless of database record ordering. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- src/wallet/scriptpubkeyman.cpp | 4 ++-- src/wallet/scriptpubkeyman.h | 2 +- src/wallet/walletdb.cpp | 21 ++++++++++++++++++++- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp index 37cf86672cde..0ab2dc1d8249 100644 --- a/src/wallet/scriptpubkeyman.cpp +++ b/src/wallet/scriptpubkeyman.cpp @@ -427,11 +427,11 @@ void LegacyScriptPubKeyMan::GenerateNewHDChain(const SecureString& secureMnemoni } } -bool LegacyScriptPubKeyMan::LoadHDChain(const CHDChain& chain) +bool LegacyScriptPubKeyMan::LoadHDChain(const CHDChain& chain, bool skip_encryption_check) { LOCK(cs_KeyStore); - if (m_storage.HasEncryptionKeys() != chain.IsCrypted()) return false; + if (!skip_encryption_check && m_storage.HasEncryptionKeys() != chain.IsCrypted()) return false; m_hd_chain = chain; return true; diff --git a/src/wallet/scriptpubkeyman.h b/src/wallet/scriptpubkeyman.h index 7f4ee75c413b..ba3c6efbf7bb 100644 --- a/src/wallet/scriptpubkeyman.h +++ b/src/wallet/scriptpubkeyman.h @@ -404,7 +404,7 @@ class LegacyScriptPubKeyMan : public ScriptPubKeyMan, public FillableSigningProv /* Set the HD chain model (chain child index counters) and writes it to the database */ bool AddHDChain(WalletBatch &batch, const CHDChain& chain); //! Load a HD chain model (used by LoadWallet) - bool LoadHDChain(const CHDChain& chain); + bool LoadHDChain(const CHDChain& chain, bool skip_encryption_check = false); /** * Set the HD chain model (chain child index counters) using temporary wallet db object * which causes db flush every time these methods are used diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index e2e4452d64da..4226461ef3bf 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -572,7 +572,9 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, CHDChain chain; ssValue >> chain; assert ((strType == DBKeys::CRYPTED_HDCHAIN) == chain.IsCrypted()); - if (!pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadHDChain(chain)) + // Skip encryption check during loading as MASTER_KEY records may not be loaded yet. + // Consistency will be validated after all records are loaded. + if (!pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadHDChain(chain, /*skip_encryption_check=*/true)) { strErr = "Error reading wallet database: SetHDChain failed"; return false; @@ -875,6 +877,23 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet) } m_batch->CloseCursor(); + // Validate HD chain encryption consistency now that all data is loaded + if (auto spk_man = pwallet->GetLegacyScriptPubKeyMan()) { + CHDChain hdChain; + if (spk_man->GetHDChain(hdChain)) { + // If HD chain exists, validate encryption consistency + bool fHasMasterKeys = pwallet->HasEncryptionKeys(); + bool fChainCrypted = hdChain.IsCrypted(); + + if (fHasMasterKeys != fChainCrypted) { + pwallet->WalletLogPrintf("Error: HD chain encryption state (%s) inconsistent with wallet encryption state (%s)\n", + fChainCrypted ? "encrypted" : "not encrypted", + fHasMasterKeys ? "encrypted" : "not encrypted"); + return DBErrors::CORRUPT; + } + } + } + // Set the active ScriptPubKeyMans for (auto spk_man : wss.m_active_external_spks) { pwallet->LoadActiveScriptPubKeyMan(spk_man.second, /*internal=*/false); From 35dc258e926d4d12ee435102d07a8834db1a159b Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Tue, 4 Nov 2025 23:52:49 +0300 Subject: [PATCH 2/2] chore: trivial cleanup --- src/wallet/walletdb.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index 4226461ef3bf..df835e254726 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -574,9 +574,8 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, assert ((strType == DBKeys::CRYPTED_HDCHAIN) == chain.IsCrypted()); // Skip encryption check during loading as MASTER_KEY records may not be loaded yet. // Consistency will be validated after all records are loaded. - if (!pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadHDChain(chain, /*skip_encryption_check=*/true)) - { - strErr = "Error reading wallet database: SetHDChain failed"; + if (!pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadHDChain(chain, /*skip_encryption_check=*/true)) { + strErr = "Error reading wallet database: LoadHDChain failed"; return false; } } else if (strType == DBKeys::HDPUBKEY) {