From 35bbee63746b567c867bb6d2c3db6f6670286826 Mon Sep 17 00:00:00 2001 From: Ava Chow Date: Thu, 4 Jan 2024 13:50:35 -0500 Subject: [PATCH] wallet, rpc: Disallow import of unused() if key already exists --- src/wallet/rpc/backup.cpp | 17 +++++++++++++++++ test/functional/wallet_importdescriptors.py | 17 +++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/src/wallet/rpc/backup.cpp b/src/wallet/rpc/backup.cpp index 4b87ba23238..cb417a6e29f 100644 --- a/src/wallet/rpc/backup.cpp +++ b/src/wallet/rpc/backup.cpp @@ -265,6 +265,23 @@ 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()) { + // 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. + // If we can retrieve the corresponding private key from the wallet, then this key is already in the wallet + // and we should not import it. + std::set pubkeys; + std::set extpubs; + parsed_desc->GetPubKeys(pubkeys, extpubs); + std::transform(extpubs.begin(), extpubs.end(), std::inserter(pubkeys, pubkeys.begin()), [](const CExtPubKey& xpub) { return xpub.pubkey; }); + CHECK_NONFATAL(pubkeys.size() == 1); + if (wallet.GetKey(pubkeys.begin()->GetID())) { + throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import an unused() descriptor when its private key is already in the wallet"); + } + } + WalletDescriptor w_desc(std::move(parsed_desc), timestamp, range_start, range_end, next_index); // Add descriptor to the wallet diff --git a/test/functional/wallet_importdescriptors.py b/test/functional/wallet_importdescriptors.py index c22bf10310f..23bb0cf6ca7 100755 --- a/test/functional/wallet_importdescriptors.py +++ b/test/functional/wallet_importdescriptors.py @@ -88,6 +88,22 @@ class ImportDescriptorsTest(BitcoinTestFramework): assert_equal(hdkeys[0]["xpub"], xpub) wallet.unloadwallet() + def test_import_unused_key_existing(self): + self.log.info("Test import of unused(KEY) with existing KEY") + self.nodes[0].createwallet(wallet_name="import_existing_unused") + wallet = self.nodes[0].get_wallet_rpc("import_existing_unused") + + hdkeys = wallet.gethdkeys(private=True) + assert_equal(len(hdkeys), 1) + xprv = hdkeys[0]["xprv"] + + self.test_importdesc({"timestamp": "now", "desc": descsum_create(f"unused({xprv})")}, + success=False, + error_code=-4, + error_message="Cannot import an unused() descriptor when its private key is already in the wallet", + 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) @@ -846,6 +862,7 @@ class ImportDescriptorsTest(BitcoinTestFramework): self.test_import_unused_key() + self.test_import_unused_key_existing() if __name__ == '__main__': ImportDescriptorsTest(__file__).main()