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