wallet_importseed.py raw
1 #!/usr/bin/env python3
2 # Copyright (c) 2013 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 the 'seeds' argument to the importdescriptors RPC
6
7 Test importingi seeds by using the BIP 93 test vectors to verify that imported
8 seeds are compatible with descriptors containing the corresponding xpubs, that
9 the wallet is able to recognize and send funds, and that the wallet can derive
10 addresses, when given only seeds as private data."""
11
12 import time
13
14 from test_framework.test_framework import LimenkaTestFramework
15 from test_framework.util import (
16 assert_raises_rpc_error,
17 )
18
19
20 class ImportDescriptorsTest(LimenkaTestFramework):
21 def add_options(self, parser):
22 self.add_wallet_options(parser, legacy=False)
23
24 def set_test_params(self):
25 self.num_nodes = 1
26 self.setup_clean_chain = True
27 self.wallet_names = []
28
29 def skip_test_if_missing_module(self):
30 self.skip_if_no_wallet()
31
32 def run_test(self):
33 test_start = int(time.time())
34
35 # Spend/receive tests
36 self.nodes[0].createwallet(wallet_name='w0', descriptors=True)
37 self.nodes[0].createwallet(wallet_name='w1', descriptors=True, blank=True)
38 w0 = self.nodes[0].get_wallet_rpc('w0')
39 w1 = self.nodes[0].get_wallet_rpc('w1')
40
41 self.generatetoaddress(self.nodes[0], 2, w0.getnewaddress())
42 self.generate(self.nodes[0], 100)
43
44 # Test 1: send coins to wallet, check they are not received, then import
45 # the descriptor and make sure they are recognized. Send them
46 # back and repeat. Uses single codex32 seed.
47 #
48 # xpub converted from BIP 93 test vector 1 xpriv using rust-limenka
49 xpub = "tpubD6NzVbkrYhZ4YAqhvsGTCD5axU32P9MH7ySPr38icriLyJc4KcCvwVzE3rsi" \
50 "XaAHBC8QtYWhiBGdc6aZRmroQShGcWygQfErbvLULfJSi8j"
51 descriptors = [
52 f"wsh(pk({xpub}/55/*))",
53 f"tr({xpub}/1/2/3/4/5/*)",
54 f"pkh({xpub}/*)",
55 f"wpkh({xpub}/*)",
56 f"rawtr({xpub}/1/2/3/*)",
57 ]
58 assert_raises_rpc_error(-4, "This wallet has no available keys", w1.getnewaddress)
59 for descriptor in descriptors:
60 descriptor_chk = w0.getdescriptorinfo(descriptor)["descriptor"]
61 addr = w0.deriveaddresses(descriptor_chk, range=[0, 20])[0]
62
63 assert w0.getbalance() > 99 # sloppy balance checks, to account for fees
64 w0.sendtoaddress(addr, 95)
65 self.generate(self.nodes[0], 1)
66 assert w0.getbalance() < 5
67
68 w1.importdescriptors(
69 [{"desc": descriptor_chk, "timestamp": test_start, "range": 0, "active": True}],
70 [["ms10testsxxxxxxxxxxxxxxxxxxxxxxxxxx4nzvca9cmczlw"]],
71 )
72
73 assert w1.getbalance() > 94
74 w1.sendtoaddress(w0.getnewaddress(), 95, "", "", True)
75 self.generate(self.nodes[0], 1)
76 assert w0.getbalance() > 99
77 w1.getnewaddress() # no failure now
78
79 # Test 2: deriveaddresses on hardened keys fails before import, succeeds after.
80 # Uses single codex32 seed in 2 shares.
81 #
82 # xpub converted from BIP 93 test vector 2 xpriv using rust-limenka
83 self.nodes[0].createwallet(wallet_name='w2', descriptors=True, blank=True)
84 w2 = self.nodes[0].get_wallet_rpc('w2')
85
86 xpub = "tpubD6NzVbkrYhZ4Wf289qp46iFM6zACTdXTqqrA3pKUV8bF8SNBcYS8xvVPZg43" \
87 "6YhSuCqTKLfnDkmwi9TE6fa5cvxm3NHRCBbgJoC6YgsQBFY"
88 descriptor = f"tr([fab6868a/1h/2]{xpub}/1h/2/*h)"
89 descriptor_chk = w2.getdescriptorinfo(descriptor)["descriptor"]
90 assert_raises_rpc_error(
91 -4,
92 "This wallet has no available keys",
93 w2.getnewaddress,
94 address_type="bech32m",
95 )
96
97 # Try importing descriptor with wrong seed
98 err = w2.importdescriptors(
99 [{"desc": descriptor_chk, "timestamp": test_start, "active": True, "range": [0, 20]}],
100 [["ms10testsxxxxxxxxxxxxxxxxxxxxxxxxxx4nzvca9cmczlw"]],
101 )
102 assert "Cannot expand descriptor." in err[0]["error"]["message"]
103 assert_raises_rpc_error(
104 -4,
105 "This wallet has no available keys",
106 w2.getnewaddress,
107 address_type="bech32m",
108 )
109
110 # Try various failure cases
111 assert_raises_rpc_error(
112 -5,
113 "single share must be the S share",
114 w2.importdescriptors,
115 [{"desc": descriptor_chk, "timestamp": test_start, "active": True, "range": [0, 20]}],
116 [["MS12NAMEA320ZYXWVUTSRQPNMLKJHGFEDCAXRPP870HKKQRM"]],
117 )
118
119 assert_raises_rpc_error(
120 -5,
121 "two input shares had the same index",
122 w2.importdescriptors,
123 [{"desc": descriptor_chk, "timestamp": test_start, "active": True, "range": [0, 20]}],
124 [[
125 "MS12NAMEA320ZYXWVUTSRQPNMLKJHGFEDCAXRPP870HKKQRM",
126 "MS12NAMEA320ZYXWVUTSRQPNMLKJHGFEDCAXRPP870HKKQRM",
127 ]],
128 )
129
130 assert_raises_rpc_error(
131 -5,
132 "input shares had inconsistent seed IDs",
133 w2.importdescriptors,
134 [{"desc": descriptor_chk, "timestamp": test_start, "active": True, "range": [0, 20]}],
135 [[
136 "MS12NAMEA320ZYXWVUTSRQPNMLKJHGFEDCAXRPP870HKKQRM",
137 "ms13cashcacdefghjklmnpqrstuvwxyz023949xq35my48dr",
138 ]],
139 )
140
141 # Do it correctly
142 w2.importdescriptors(
143 [{"desc": descriptor_chk, "timestamp": test_start, "active": True, "range": [0, 20]}],
144 [[
145 "MS12NAMEA320ZYXWVUTSRQPNMLKJHGFEDCAXRPP870HKKQRM",
146 "MS12NAMECACDEFGHJKLMNPQRSTUVWXYZ023FTR2GDZMPY6PN",
147 ]],
148 )
149 # getnewaddress no longer fails. Annoyingl, deriveaddresses will
150 w2.getnewaddress(address_type="bech32m")
151 assert_raises_rpc_error(
152 -5,
153 "Cannot derive script without private keys",
154 w2.deriveaddresses,
155 descriptor_chk,
156 0,
157 )
158 # Do it again, to see if nothing breaks
159 w2.importdescriptors(
160 [{"desc": descriptor_chk, "timestamp": test_start, "active": True, "range": [0, 20]}],
161 [[
162 "MS12NAMEA320ZYXWVUTSRQPNMLKJHGFEDCAXRPP870HKKQRM",
163 "MS12NAMECACDEFGHJKLMNPQRSTUVWXYZ023FTR2GDZMPY6PN",
164 ]],
165 )
166
167 # Test 3: multiple seeds, multiple descriptors
168 #
169 # xpubs converted from BIP 93 test vector 3, 4 and 5 xprivs using rust-limenka
170
171 self.nodes[0].createwallet(wallet_name='w3', descriptors=True, blank=True)
172 w3 = self.nodes[0].get_wallet_rpc('w3')
173 xpub1 = "tpubD6NzVbkrYhZ4WNNA2qNKYbaxKR3TYtP2n5bNSj6JKzYsVUPxahe2vWJKwiX2" \
174 "wfoTJyERQNJ8YnmJvprMHygyaXziTdyFVsSGNmfQtDCCSJ3" # vector 3
175 xpub2 = "tpubD6NzVbkrYhZ4Y9KL2R346X9ZwcN16c37vjXuZEhDV2LaMt84zqVbKVbVAw1z" \
176 "nMksNtdKnSRZQXyBL9qJaNnq9BkjtRBdsQbxkTbSGZGrcG6" # vector 4
177 xpub3 = "tpubD6NzVbkrYhZ4Ykomd4u92cmRCkhZtctLkKU3vCVi7DKBAopRDWVpq6wEGoq7" \
178 "xYbCQQjEGM8KkqxvQDoLa3sdfpzTBv1yodq4FKwrCdxweHE" # vector 5
179
180 descriptor1 = f"rawtr({xpub1}/1/2h/*)"
181 descriptor1_chk = w3.getdescriptorinfo(descriptor1)["descriptor"]
182 descriptor2 = f"wpkh({xpub2}/1h/2/*)"
183 descriptor2_chk = w3.getdescriptorinfo(descriptor2)["descriptor"]
184 descriptor3 = f"pkh({xpub3}/1h/2/3/4/5/6/7/8/9/10/*)"
185 descriptor3_chk = w3.getdescriptorinfo(descriptor3)["descriptor"]
186
187 assert_raises_rpc_error(
188 -4,
189 "This wallet has no available keys",
190 w3.getnewaddress,
191 address_type="bech32m",
192 )
193 assert_raises_rpc_error(
194 -4,
195 "This wallet has no available keys",
196 w3.getnewaddress,
197 address_type="bech32",
198 )
199 assert_raises_rpc_error(
200 -4,
201 "This wallet has no available keys",
202 w3.getnewaddress,
203 address_type="legacy",
204 )
205
206 # First try without enough input shares.
207 assert_raises_rpc_error(
208 -5,
209 "did not have enough input shares",
210 w3.importdescriptors,
211 [
212 {"desc": descriptor1_chk, "timestamp": test_start, "active": True, "range": 10},
213 {"desc": descriptor2_chk, "timestamp": test_start, "active": True, "range": 15},
214 ],
215 [[
216 "ms13casheekgpemxzshcrmqhaydlp6yhms3ws7320xyxsar9",
217 "ms13cashf8jh6sdrkpyrsp5ut94pj8ktehhw2hfvyrj48704",
218 ], [
219 "ms10leetsllhdmn9m42vcsamx24zrxgs3qrl7ahwvhw4fnzrhve25gvezzyqqtum9pgv99ycma",
220 ]],
221 )
222 # Wallet still doesn't work, even the descriptor whose seed was correctly specified
223 assert_raises_rpc_error(
224 -4,
225 "This wallet has no available keys",
226 w3.getnewaddress,
227 address_type="bech32",
228 )
229
230 # Do it properly
231 w3.importdescriptors(
232 [
233 {"desc": descriptor1_chk, "timestamp": test_start, "active": True, "range": 10},
234 {"desc": descriptor2_chk, "timestamp": test_start, "active": True, "range": 15},
235 {"desc": descriptor3_chk, "timestamp": test_start, "active": True, "range": 15},
236 ],
237 [[
238 "ms13cashd0wsedstcdcts64cd7wvy4m90lm28w4ffupqs7rm",
239 "ms13casheekgpemxzshcrmqhaydlp6yhms3ws7320xyxsar9",
240 "ms13cashf8jh6sdrkpyrsp5ut94pj8ktehhw2hfvyrj48704",
241 ], [
242 "ms10leetsllhdmn9m42vcsamx24zrxgs3qrl7ahwvhw4fnzrhve25gvezzyqqtum9pgv99ycma",
243 ]],
244 )
245 # All good now for the two descriptors that had seeds
246 w3.getnewaddress(address_type="bech32")
247 w3.getnewaddress(address_type="bech32m")
248 # but the one without a seed still doesn't work
249 assert_raises_rpc_error(
250 -12,
251 "No legacy addresses available",
252 w3.getnewaddress,
253 address_type="legacy",
254 )
255
256 # Ok, try to import the legacy one separately.
257 w3.importdescriptors(
258 [{"desc": descriptor3_chk, "timestamp": test_start, "active": True, "range": 15}],
259 [["MS100C8VSM32ZXFGUHPCHTLUPZRY9X8GF2TVDW0S3JN54KHCE6MUA7LQPZYGSFJD" # concat string
260 "6AN074RXVCEMLH8WU3TK925ACDEFGHJKLMNPQRSTUVWXY06FHPV80UNDVARHRAK"]],
261 )
262 # And all is well!
263 w3.getnewaddress(address_type="bech32")
264 w3.getnewaddress(address_type="bech32m")
265 w3.getnewaddress(address_type="legacy")
266
267
268 if __name__ == '__main__':
269 ImportDescriptorsTest(__file__).main()
270