test: wallet: Check that loading wallet with both unencrypted and encrypted keys fails.
What changed, and why it matters
This commit adds a new automated test to Bitcoin Core. The test checks that the software correctly refuses to open a wallet that has been corrupted so that it contains both encrypted and unencrypted copies of the same key. It does not change any production wallet code; it only adds a regression test for an already-existing safety check.
No action required. Review the existing production wallet loading logic if you want to confirm the detection path is robust, but this commit itself is safe and only improves test coverage.
Security signals we found
Test-only addition, no production code change
Validates existing wallet corruption detection logic
Prevents loading wallets with mixed encrypted/unencrypted key state
Uses direct SQLite manipulation to simulate on-disk corruption
Evidence from the diff
The change is in test/functional/wallet_descriptor.py. It creates a descriptor wallet, unloads it, opens the SQLite wallet database directly, and inserts a duplicate key record using the encrypted-key prefix (walletdescriptorckey) alongside the original unencrypted-key record (walletdescriptorkey). It then verifies that loadwallet fails with RPC error -4 and the debug log message ‘Wallet contains both unencrypted and encrypted keys’. This is a test-only addition validating an existing corruption-detection path.
Changed components
test/functional/wallet_descriptor.pyInspect captured patch +20 / −0
diff --git a/test/functional/wallet_descriptor.py b/test/functional/wallet_descriptor.py
index 39b80bf7..ee8ea192 100755
--- a/test/functional/wallet_descriptor.py
+++ b/test/functional/wallet_descriptor.py
@@ -12,6 +12,7 @@ except ImportError:
import re
from test_framework.blocktools import COINBASE_MATURITY
+from test_framework.messages import ser_string
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_not_equal,
@@ -268,6 +269,25 @@ class WalletDescriptorTest(BitcoinTestFramework):
conn.close()
assert_raises_rpc_error(-4, "Unexpected legacy entry in descriptor wallet found.", self.nodes[0].loadwallet, "crashme")
+ self.log.info("Test that loading descriptor wallet containing both unencrypted and encrypted keys for same descriptor fails to load")
+ wallet_name = "mixed_crypt"
+ self.nodes[0].createwallet(wallet_name)
+ self.nodes[0].unloadwallet(wallet_name)
+ wallet_db = self.nodes[0].wallets_path / wallet_name / self.wallet_data_filename
+ conn = sqlite3.connect(wallet_db)
+ with conn:
+ key_prefix = ser_string(b"walletdescriptorkey")
+ ckey_prefix = ser_string(b"walletdescriptorckey")
+ rows = conn.execute('SELECT key, value FROM main').fetchall()
+ key_rows = [(k, v) for k, v in rows if k.startswith(key_prefix)]
+ # Test the test, want to be sure there is at least one unencrypted key.
+ assert len(key_rows) >= 1
+ k, v = key_rows[0]
+ conn.execute('INSERT INTO main VALUES(?, ?)', (k.replace(key_prefix, ckey_prefix), v))
+ conn.close()
+ with self.nodes[0].assert_debug_log(["Wallet contains both unencrypted and encrypted keys"]):
+ assert_raises_rpc_error(-4, "Wallet corrupted", self.nodes[0].loadwallet, wallet_name)
+
self.test_parent_descriptors()
if __name__ == '__main__':
Why this scored 18/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.