wallet, rpc: Disallow importing unused() to wallets without privkeys
What changed, and why it matters
This change blocks users from importing a special type of Bitcoin wallet descriptor called unused() into wallets that have private keys disabled. Such wallets are watch-only: they can see transactions but cannot spend. The unused() descriptor is meant to reserve a key for future use. Allowing it in a no-private-key wallet could have led to confusion or situations where the wallet believes it controls funds it cannot actually spend, but the patch is a simple guard with no demonstrated exploit.
Treat as a hardening or bug-prevention patch. Reviewers should confirm the guard covers all import paths and that the error message is consistent with other descriptor import failures. No urgent deployment is indicated absent a disclosed exploit.
Security signals we found
New input-validation guard in wallet RPC
Prevents descriptor import into watch-only wallet
Adds functional test for error path
No CVE, advisory, or exploit chain referenced
Evidence from the diff
The commit adds a check in ProcessDescriptorImport that rejects imports of unused(KEY) descriptors when WALLET_FLAG_DISABLE_PRIVATE_KEYS is set, returning RPC_WALLET_ERROR. It also adds a functional test verifying the rejection. The change is defensive: unused() descriptors have no scripts, so they do not directly produce spendable outputs, but importing them into a private-key-disabled wallet could create misleading wallet state or edge cases in key tracking. The patch is small and does not fix a known exploit chain.
Changed components
src/wallet/rpc/backup.cppwallet importdescriptors RPCwatch-only wallets with disabled private keysInspect captured patch +18 / −0
diff --git a/src/wallet/rpc/backup.cpp b/src/wallet/rpc/backup.cpp
index cb417a6e..c692fdc2 100644
--- a/src/wallet/rpc/backup.cpp
+++ b/src/wallet/rpc/backup.cpp
@@ -267,6 +267,9 @@ static UniValue ProcessDescriptorImport(CWallet& wallet, const UniValue& data, c
// If this is an unused(KEY) descriptor, check that the wallet doesn't already have other descriptors with this key
if (!parsed_desc->HasScripts()) {
+ if (wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import unused() to wallet without private keys enabled");
+ }
// Unused descriptors must contain a single key.
// Earlier checks will have enforced that this key is either a private key when private keys are enabled,
// or that this key is a public key when private keys are disabled.
diff --git a/test/functional/wallet_importdescriptors.py b/test/functional/wallet_importdescriptors.py
index 23bb0cf6..3e8b05a2 100755
--- a/test/functional/wallet_importdescriptors.py
+++ b/test/functional/wallet_importdescriptors.py
@@ -104,6 +104,20 @@ class ImportDescriptorsTest(BitcoinTestFramework):
wallet=wallet)
wallet.unloadwallet()
+ def test_import_unused_noprivs(self):
+ self.log.info("Test import of unused(KEY) to wallet without privkeys")
+ self.nodes[0].createwallet(wallet_name="import_unused_noprivs", disable_private_keys=True)
+ wallet = self.nodes[0].get_wallet_rpc("import_unused_noprivs")
+
+ xpub = "tpubD6NzVbkrYhZ4YNXVQbNhMK1WqguFsUXceaVJKbmno2aZ3B6QfbMeraaYvnBSGpV3vxLyTTK9DYT1yoEck4XUScMzXoQ2U2oSmE2JyMedq3H"
+ self.test_importdesc({"timestamp": "now", "desc": descsum_create(f"unused({xpub})")},
+ success=False,
+ error_code=-4,
+ error_message="Cannot import unused() to wallet without private keys enabled",
+ wallet=wallet)
+ wallet.unloadwallet()
+
+
def run_test(self):
self.log.info('Setting up wallets')
self.nodes[0].createwallet(wallet_name='w0', disable_private_keys=False)
@@ -863,6 +877,7 @@ class ImportDescriptorsTest(BitcoinTestFramework):
self.test_import_unused_key()
self.test_import_unused_key_existing()
+ self.test_import_unused_noprivs()
if __name__ == '__main__':
ImportDescriptorsTest(__file__).main()
Why this scored 34/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.