diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp index 37cf86672cdeb..0ab2dc1d82499 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 7f4ee75c413bc..ba3c6efbf7bb1 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 e2e4452d64daa..df835e2547269 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -572,9 +572,10 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, CHDChain chain; ssValue >> chain; assert ((strType == DBKeys::CRYPTED_HDCHAIN) == chain.IsCrypted()); - if (!pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadHDChain(chain)) - { - strErr = "Error reading wallet database: SetHDChain failed"; + // 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: LoadHDChain failed"; return false; } } else if (strType == DBKeys::HDPUBKEY) { @@ -875,6 +876,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);