wallet_gethdkeys.py
1 #!/usr/bin/env python3 2 # Copyright (c) 2023-present The Bitcoin Core developers 3 # Distributed under the MIT software license, see the accompanying 4 # file COPYING or http://www.opensource.org/licenses/mit-license.php. 5 """Test wallet gethdkeys RPC.""" 6 7 from test_framework.descriptors import descsum_create 8 from test_framework.test_framework import BitcoinTestFramework 9 from test_framework.util import ( 10 assert_equal, 11 assert_raises_rpc_error, 12 assert_not_equal, 13 ) 14 from test_framework.wallet_util import WalletUnlock 15 16 17 class WalletGetHDKeyTest(BitcoinTestFramework): 18 def set_test_params(self): 19 self.setup_clean_chain = True 20 self.num_nodes = 1 21 22 def skip_test_if_missing_module(self): 23 self.skip_if_no_wallet() 24 25 def run_test(self): 26 self.test_basic_gethdkeys() 27 self.test_ranged_imports() 28 self.test_lone_key_imports() 29 self.test_ranged_multisig() 30 self.test_mixed_multisig() 31 32 def test_basic_gethdkeys(self): 33 self.log.info("Test gethdkeys basics") 34 self.nodes[0].createwallet("basic") 35 wallet = self.nodes[0].get_wallet_rpc("basic") 36 xpub_info = wallet.gethdkeys() 37 assert_equal(len(xpub_info), 1) 38 assert_equal(xpub_info[0]["has_private"], True) 39 40 assert "xprv" not in xpub_info[0] 41 xpub = xpub_info[0]["xpub"] 42 43 xpub_info = wallet.gethdkeys(private=True) 44 xprv = xpub_info[0]["xprv"] 45 assert_equal(xpub_info[0]["xpub"], xpub) 46 assert_equal(xpub_info[0]["has_private"], True) 47 48 descs = wallet.listdescriptors(True) 49 for desc in descs["descriptors"]: 50 assert xprv in desc["desc"] 51 52 self.log.info("HD pubkey can be retrieved from encrypted wallets") 53 prev_xprv = xprv 54 wallet.encryptwallet("pass") 55 # HD key is rotated on encryption, there should now be 2 HD keys 56 assert_equal(len(wallet.gethdkeys()), 2) 57 # New key is active, should be able to get only that one and its descriptors 58 xpub_info = wallet.gethdkeys(active_only=True) 59 assert_equal(len(xpub_info), 1) 60 assert_not_equal(xpub_info[0]["xpub"], xpub) 61 assert "xprv" not in xpub_info[0] 62 assert_equal(xpub_info[0]["has_private"], True) 63 64 self.log.info("HD privkey can be retrieved from encrypted wallets") 65 assert_raises_rpc_error(-13, "Error: Please enter the wallet passphrase with walletpassphrase first", wallet.gethdkeys, private=True) 66 with WalletUnlock(wallet, "pass"): 67 xpub_info = wallet.gethdkeys(active_only=True, private=True)[0] 68 assert_not_equal(xpub_info["xprv"], xprv) 69 for desc in wallet.listdescriptors(True)["descriptors"]: 70 if desc["active"]: 71 # After encrypting, HD key was rotated and should appear in all active descriptors 72 assert xpub_info["xprv"] in desc["desc"] 73 else: 74 # Inactive descriptors should have the previous HD key 75 assert prev_xprv in desc["desc"] 76 77 def test_ranged_imports(self): 78 self.log.info("Keys of imported ranged descriptors appear in gethdkeys") 79 def_wallet = self.nodes[0].get_wallet_rpc(self.default_wallet_name) 80 self.nodes[0].createwallet("imports") 81 wallet = self.nodes[0].get_wallet_rpc("imports") 82 83 xpub_info = wallet.gethdkeys() 84 assert_equal(len(xpub_info), 1) 85 active_xpub = xpub_info[0]["xpub"] 86 87 import_xpub = def_wallet.gethdkeys(active_only=True)[0]["xpub"] 88 desc_import = def_wallet.listdescriptors(True)["descriptors"] 89 for desc in desc_import: 90 desc["active"] = False 91 wallet.importdescriptors(desc_import) 92 assert_equal(wallet.gethdkeys(active_only=True), xpub_info) 93 94 xpub_info = wallet.gethdkeys() 95 assert_equal(len(xpub_info), 2) 96 for x in xpub_info: 97 if x["xpub"] == active_xpub: 98 for desc in x["descriptors"]: 99 assert_equal(desc["active"], True) 100 elif x["xpub"] == import_xpub: 101 for desc in x["descriptors"]: 102 assert_equal(desc["active"], False) 103 else: 104 assert False 105 106 107 def test_lone_key_imports(self): 108 self.log.info("Non-HD keys do not appear in gethdkeys") 109 self.nodes[0].createwallet("lonekey", blank=True) 110 wallet = self.nodes[0].get_wallet_rpc("lonekey") 111 112 assert_equal(wallet.gethdkeys(), []) 113 wallet.importdescriptors([{"desc": descsum_create("wpkh(cTe1f5rdT8A8DFgVWTjyPwACsDPJM9ff4QngFxUixCSvvbg1x6sh)"), "timestamp": "now"}]) 114 assert_equal(wallet.gethdkeys(), []) 115 116 self.log.info("HD keys of non-ranged descriptors should appear in gethdkeys") 117 def_wallet = self.nodes[0].get_wallet_rpc(self.default_wallet_name) 118 xpub_info = def_wallet.gethdkeys(private=True) 119 xpub = xpub_info[0]["xpub"] 120 xprv = xpub_info[0]["xprv"] 121 prv_desc = descsum_create(f"wpkh({xprv})") 122 pub_desc = descsum_create(f"wpkh({xpub})") 123 assert_equal(wallet.importdescriptors([{"desc": prv_desc, "timestamp": "now"}])[0]["success"], True) 124 xpub_info = wallet.gethdkeys() 125 assert_equal(len(xpub_info), 1) 126 assert_equal(xpub_info[0]["xpub"], xpub) 127 assert_equal(len(xpub_info[0]["descriptors"]), 1) 128 assert_equal(xpub_info[0]["descriptors"][0]["desc"], pub_desc) 129 assert_equal(xpub_info[0]["descriptors"][0]["active"], False) 130 131 def test_ranged_multisig(self): 132 self.log.info("HD keys of a multisig appear in gethdkeys") 133 def_wallet = self.nodes[0].get_wallet_rpc(self.default_wallet_name) 134 self.nodes[0].createwallet("ranged_multisig") 135 wallet = self.nodes[0].get_wallet_rpc("ranged_multisig") 136 137 xpub1 = wallet.gethdkeys()[0]["xpub"] 138 xprv1 = wallet.gethdkeys(private=True)[0]["xprv"] 139 xpub2 = def_wallet.gethdkeys()[0]["xpub"] 140 141 prv_multi_desc = descsum_create(f"wsh(multi(2,{xprv1}/*,{xpub2}/*))") 142 pub_multi_desc = descsum_create(f"wsh(multi(2,{xpub1}/*,{xpub2}/*))") 143 assert_equal(wallet.importdescriptors([{"desc": prv_multi_desc, "timestamp": "now"}])[0]["success"], True) 144 145 xpub_info = wallet.gethdkeys() 146 assert_equal(len(xpub_info), 2) 147 for x in xpub_info: 148 if x["xpub"] == xpub1: 149 found_desc = next((d for d in xpub_info[0]["descriptors"] if d["desc"] == pub_multi_desc), None) 150 assert found_desc is not None 151 assert_equal(found_desc["active"], False) 152 elif x["xpub"] == xpub2: 153 assert_equal(len(x["descriptors"]), 1) 154 assert_equal(x["descriptors"][0]["desc"], pub_multi_desc) 155 assert_equal(x["descriptors"][0]["active"], False) 156 else: 157 assert False 158 159 def test_mixed_multisig(self): 160 self.log.info("Non-HD keys of a multisig do not appear in gethdkeys") 161 def_wallet = self.nodes[0].get_wallet_rpc(self.default_wallet_name) 162 self.nodes[0].createwallet("single_multisig") 163 wallet = self.nodes[0].get_wallet_rpc("single_multisig") 164 165 xpub = wallet.gethdkeys()[0]["xpub"] 166 xprv = wallet.gethdkeys(private=True)[0]["xprv"] 167 pub = def_wallet.getaddressinfo(def_wallet.getnewaddress())["pubkey"] 168 169 prv_multi_desc = descsum_create(f"wsh(multi(2,{xprv},{pub}))") 170 pub_multi_desc = descsum_create(f"wsh(multi(2,{xpub},{pub}))") 171 import_res = wallet.importdescriptors([{"desc": prv_multi_desc, "timestamp": "now"}]) 172 assert_equal(import_res[0]["success"], True) 173 174 xpub_info = wallet.gethdkeys() 175 assert_equal(len(xpub_info), 1) 176 assert_equal(xpub_info[0]["xpub"], xpub) 177 found_desc = next((d for d in xpub_info[0]["descriptors"] if d["desc"] == pub_multi_desc), None) 178 assert found_desc is not None 179 assert_equal(found_desc["active"], False) 180 181 182 if __name__ == '__main__': 183 WalletGetHDKeyTest(__file__).main()