wallet_importdescriptors.py raw
1 #!/usr/bin/env python3
2 # Copyright (c) 2019-2022 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 importdescriptors RPC.
6
7 Test importdescriptors by generating keys on node0, importing the corresponding
8 descriptors on node1 and then testing the address info for the different address
9 variants.
10
11 - `get_generate_key()` is called to generate keys and return the privkeys,
12 pubkeys and all variants of scriptPubKey and address.
13 - `test_importdesc()` is called to send an importdescriptors call to node1, test
14 success, and (if unsuccessful) test the error code and error message returned.
15 - `test_address()` is called to call getaddressinfo for an address on node1
16 and test the values returned."""
17
18 import concurrent.futures
19 import time
20
21 from test_framework.authproxy import JSONRPCException
22 from test_framework.blocktools import COINBASE_MATURITY
23 from test_framework.test_framework import LimenkaTestFramework
24 from test_framework.descriptors import descsum_create
25 from test_framework.util import (
26 assert_equal,
27 assert_raises_rpc_error,
28 )
29 from test_framework.wallet_util import (
30 get_generate_key,
31 test_address,
32 )
33
34 class ImportDescriptorsTest(LimenkaTestFramework):
35 def add_options(self, parser):
36 self.add_wallet_options(parser, legacy=False)
37
38 def set_test_params(self):
39 self.num_nodes = 2
40 # whitelist peers to speed up tx relay / mempool sync
41 self.noban_tx_relay = True
42 self.extra_args = [["-addresstype=legacy"],
43 ["-addresstype=bech32", "-keypool=5"]
44 ]
45 self.setup_clean_chain = True
46 self.wallet_names = []
47
48 def skip_test_if_missing_module(self):
49 self.skip_if_no_wallet()
50 self.skip_if_no_sqlite()
51
52 def test_importdesc(self, req, success, error_code=None, error_message=None, warnings=None, wallet=None):
53 """Run importdescriptors and assert success"""
54 if warnings is None:
55 warnings = []
56 wrpc = self.nodes[1].get_wallet_rpc('w1')
57 if wallet is not None:
58 wrpc = wallet
59
60 result = wrpc.importdescriptors([req])
61 observed_warnings = []
62 if 'warnings' in result[0]:
63 observed_warnings = result[0]['warnings']
64 assert_equal("\n".join(sorted(warnings)), "\n".join(sorted(observed_warnings)))
65 assert_equal(result[0]['success'], success)
66 if error_code is not None:
67 assert_equal(result[0]['error']['code'], error_code)
68 assert_equal(result[0]['error']['message'], error_message)
69
70 def run_test(self):
71 self.log.info('Setting up wallets')
72 self.nodes[0].createwallet(wallet_name='w0', disable_private_keys=False, descriptors=True)
73 w0 = self.nodes[0].get_wallet_rpc('w0')
74
75 self.nodes[1].createwallet(wallet_name='w1', disable_private_keys=True, blank=True, descriptors=True)
76 w1 = self.nodes[1].get_wallet_rpc('w1')
77 assert_equal(w1.getwalletinfo()['keypoolsize'], 0)
78
79 self.nodes[1].createwallet(wallet_name="wpriv", disable_private_keys=False, blank=True, descriptors=True)
80 wpriv = self.nodes[1].get_wallet_rpc("wpriv")
81 assert_equal(wpriv.getwalletinfo()['keypoolsize'], 0)
82
83 self.log.info('Mining coins')
84 self.generatetoaddress(self.nodes[0], COINBASE_MATURITY + 1, w0.getnewaddress())
85
86 # RPC importdescriptors -----------------------------------------------
87
88 # # Test import fails if no descriptor present
89 self.log.info("Import should fail if a descriptor is not provided")
90 self.test_importdesc({"timestamp": "now"},
91 success=False,
92 error_code=-8,
93 error_message='Descriptor not found.')
94
95 # # Test importing of a P2PKH descriptor
96 key = get_generate_key()
97 self.log.info("Should import a p2pkh descriptor")
98 import_request = {"desc": descsum_create("pkh(" + key.pubkey + ")"),
99 "timestamp": "now",
100 "label": "Descriptor import test"}
101 self.test_importdesc(import_request, success=True)
102 test_address(w1,
103 key.p2pkh_addr,
104 solvable=True,
105 ismine=True,
106 labels=["Descriptor import test"])
107 assert_equal(w1.getwalletinfo()['keypoolsize'], 0)
108
109 self.log.info("Test can import same descriptor with public key twice")
110 self.test_importdesc(import_request, success=True)
111
112 self.log.info("Test can update descriptor label")
113 self.test_importdesc({**import_request, "label": "Updated label"}, success=True)
114 test_address(w1, key.p2pkh_addr, solvable=True, ismine=True, labels=["Updated label"])
115
116 self.log.info("Internal addresses cannot have labels")
117 self.test_importdesc({**import_request, "internal": True},
118 success=False,
119 error_code=-8,
120 error_message="Internal addresses should not have a label")
121
122 self.log.info("External non-ranged addresses can have labels")
123 self.test_importdesc({**import_request, "internal": False}, success=True)
124
125 self.log.info("Internal addresses should be detected as such")
126 key = get_generate_key()
127 self.test_importdesc({"desc": descsum_create("pkh(" + key.pubkey + ")"),
128 "timestamp": "now",
129 "internal": True},
130 success=True)
131 info = w1.getaddressinfo(key.p2pkh_addr)
132 assert_equal(info["ismine"], True)
133 assert_equal(info["ischange"], True)
134
135 # # Test importing of a P2SH-P2WPKH descriptor
136 key = get_generate_key()
137 self.log.info("Should not import a p2sh-p2wpkh descriptor without checksum")
138 self.test_importdesc({"desc": "sh(wpkh(" + key.pubkey + "))",
139 "timestamp": "now"
140 },
141 success=False,
142 error_code=-5,
143 error_message="Missing checksum")
144
145 self.log.info("Should not import a p2sh-p2wpkh descriptor that has range specified")
146 self.test_importdesc({"desc": descsum_create("sh(wpkh(" + key.pubkey + "))"),
147 "timestamp": "now",
148 "range": 1,
149 },
150 success=False,
151 error_code=-8,
152 error_message="Range should not be specified for an un-ranged descriptor")
153
154 self.log.info("Should not import a p2sh-p2wpkh descriptor and have it set to active")
155 self.test_importdesc({"desc": descsum_create("sh(wpkh(" + key.pubkey + "))"),
156 "timestamp": "now",
157 "active": True,
158 },
159 success=False,
160 error_code=-8,
161 error_message="Active descriptors must be ranged")
162
163 self.log.info("Should import a (non-active) p2sh-p2wpkh descriptor")
164 self.test_importdesc({"desc": descsum_create("sh(wpkh(" + key.pubkey + "))"),
165 "timestamp": "now",
166 "active": False,
167 },
168 success=True)
169 assert_equal(w1.getwalletinfo()['keypoolsize'], 0)
170
171 test_address(w1,
172 key.p2sh_p2wpkh_addr,
173 ismine=True,
174 solvable=True)
175
176 # Check persistence of data and that loading works correctly
177 w1.unloadwallet()
178 self.nodes[1].loadwallet('w1')
179 test_address(w1,
180 key.p2sh_p2wpkh_addr,
181 ismine=True,
182 solvable=True)
183
184 # # Test importing of a multisig descriptor
185 key1 = get_generate_key()
186 key2 = get_generate_key()
187 self.log.info("Should import a 1-of-2 bare multisig from descriptor")
188 self.test_importdesc({"desc": descsum_create("multi(1," + key1.pubkey + "," + key2.pubkey + ")"),
189 "timestamp": "now"},
190 success=True)
191 self.log.info("Should not treat individual keys from the imported bare multisig as watchonly")
192 test_address(w1,
193 key1.p2pkh_addr,
194 ismine=False)
195
196 # # Test ranged descriptors
197 xpriv = "tprv8ZgxMBicQKsPeuVhWwi6wuMQGfPKi9Li5GtX35jVNknACgqe3CY4g5xgkfDDJcmtF7o1QnxWDRYw4H5P26PXq7sbcUkEqeR4fg3Kxp2tigg"
198 xpub = "tpubD6NzVbkrYhZ4YNXVQbNhMK1WqguFsUXceaVJKbmno2aZ3B6QfbMeraaYvnBSGpV3vxLyTTK9DYT1yoEck4XUScMzXoQ2U2oSmE2JyMedq3H"
199 addresses = ["2N7yv4p8G8yEaPddJxY41kPihnWvs39qCMf", "2MsHxyb2JS3pAySeNUsJ7mNnurtpeenDzLA"] # hdkeypath=m/0'/0'/0' and 1'
200 addresses += ["bcrt1qrd3n235cj2czsfmsuvqqpr3lu6lg0ju7scl8gn", "bcrt1qfqeppuvj0ww98r6qghmdkj70tv8qpchehegrg8"] # wpkh subscripts corresponding to the above addresses
201 desc = "sh(wpkh(" + xpub + "/0/0/*" + "))"
202
203 self.log.info("Ranged descriptors cannot have labels")
204 self.test_importdesc({"desc":descsum_create(desc),
205 "timestamp": "now",
206 "range": [0, 100],
207 "label": "test"},
208 success=False,
209 error_code=-8,
210 error_message='Ranged descriptors should not have a label')
211
212 self.log.info("Ranged descriptors cannot have labels - even if range not provided by user and only implied by asterisk (*)")
213 self.test_importdesc({"desc":descsum_create("wpkh(" + xpub + "/100/0/*)"),
214 "timestamp": "now",
215 "label": "test",
216 "active": True},
217 success=False,
218 warnings=['Range not given, using default keypool range'],
219 error_code=-8,
220 error_message='Ranged descriptors should not have a label')
221
222 self.log.info("Private keys required for private keys enabled wallet")
223 self.test_importdesc({"desc":descsum_create(desc),
224 "timestamp": "now",
225 "range": [0, 100]},
226 success=False,
227 error_code=-4,
228 error_message='Cannot import descriptor without private keys to a wallet with private keys enabled',
229 wallet=wpriv)
230
231 self.log.info("Ranged descriptor import should warn without a specified range")
232 self.test_importdesc({"desc": descsum_create(desc),
233 "timestamp": "now"},
234 success=True,
235 warnings=['Range not given, using default keypool range'])
236 assert_equal(w1.getwalletinfo()['keypoolsize'], 0)
237
238 # # Test importing of a ranged descriptor with xpriv
239 self.log.info("Should not import a ranged descriptor that includes xpriv into a watch-only wallet")
240 desc = "sh(wpkh(" + xpriv + "/0'/0'/*'" + "))"
241 self.test_importdesc({"desc": descsum_create(desc),
242 "timestamp": "now",
243 "range": 1},
244 success=False,
245 error_code=-4,
246 error_message='Cannot import private keys to a wallet with private keys disabled')
247
248 self.log.info("Should not import a descriptor with hardened derivations when private keys are disabled")
249 self.test_importdesc({"desc": descsum_create("wpkh(" + xpub + "/1h/*)"),
250 "timestamp": "now",
251 "range": 1},
252 success=False,
253 error_code=-4,
254 error_message='Cannot expand descriptor. Probably because of hardened derivations without private keys provided')
255
256 for address in addresses:
257 test_address(w1,
258 address,
259 ismine=False,
260 solvable=False)
261
262 self.test_importdesc({"desc": descsum_create(desc), "timestamp": "now", "range": -1},
263 success=False, error_code=-8, error_message='End of range is too high')
264
265 self.test_importdesc({"desc": descsum_create(desc), "timestamp": "now", "range": [-1, 10]},
266 success=False, error_code=-8, error_message='Range should be greater or equal than 0')
267
268 self.test_importdesc({"desc": descsum_create(desc), "timestamp": "now", "range": [(2 << 31 + 1) - 1000000, (2 << 31 + 1)]},
269 success=False, error_code=-8, error_message='End of range is too high')
270
271 self.test_importdesc({"desc": descsum_create(desc), "timestamp": "now", "range": [2, 1]},
272 success=False, error_code=-8, error_message='Range specified as [begin,end] must not have begin after end')
273
274 self.test_importdesc({"desc": descsum_create(desc), "timestamp": "now", "range": [0, 1000001]},
275 success=False, error_code=-8, error_message='Range is too large')
276
277 self.log.info("Verify we can only extend descriptor's range")
278 range_request = {"desc": descsum_create(desc), "timestamp": "now", "range": [5, 10], 'active': True}
279 self.test_importdesc(range_request, wallet=wpriv, success=True)
280 assert_equal(wpriv.getwalletinfo()['keypoolsize'], 6)
281 self.test_importdesc({**range_request, "range": [0, 10]}, wallet=wpriv, success=True)
282 assert_equal(wpriv.getwalletinfo()['keypoolsize'], 11)
283 self.test_importdesc({**range_request, "range": [0, 20]}, wallet=wpriv, success=True)
284 assert_equal(wpriv.getwalletinfo()['keypoolsize'], 21)
285 # Can keep range the same
286 self.test_importdesc({**range_request, "range": [0, 20]}, wallet=wpriv, success=True)
287 assert_equal(wpriv.getwalletinfo()['keypoolsize'], 21)
288
289 self.test_importdesc({**range_request, "range": [5, 10]}, wallet=wpriv, success=False,
290 error_code=-8, error_message='new range must include current range = [0,20]')
291 self.test_importdesc({**range_request, "range": [0, 10]}, wallet=wpriv, success=False,
292 error_code=-8, error_message='new range must include current range = [0,20]')
293 self.test_importdesc({**range_request, "range": [5, 20]}, wallet=wpriv, success=False,
294 error_code=-8, error_message='new range must include current range = [0,20]')
295 assert_equal(wpriv.getwalletinfo()['keypoolsize'], 21)
296
297 self.log.info("Check we can change descriptor internal flag")
298 self.test_importdesc({**range_request, "range": [0, 20], "internal": True}, wallet=wpriv, success=True)
299 assert_equal(wpriv.getwalletinfo()['keypoolsize'], 0)
300 assert_raises_rpc_error(-4, 'This wallet has no available keys', wpriv.getnewaddress, '', 'p2sh-segwit')
301 assert_equal(wpriv.getwalletinfo()['keypoolsize_hd_internal'], 21)
302 wpriv.getrawchangeaddress('p2sh-segwit')
303
304 self.test_importdesc({**range_request, "range": [0, 20], "internal": False}, wallet=wpriv, success=True)
305 assert_equal(wpriv.getwalletinfo()['keypoolsize'], 21)
306 wpriv.getnewaddress('', 'p2sh-segwit')
307 assert_equal(wpriv.getwalletinfo()['keypoolsize_hd_internal'], 0)
308 assert_raises_rpc_error(-4, 'This wallet has no available keys', wpriv.getrawchangeaddress, 'p2sh-segwit')
309
310 # Make sure ranged imports import keys in order
311 w1 = self.nodes[1].get_wallet_rpc('w1')
312 self.log.info('Key ranges should be imported in order')
313 xpub = "tpubDAXcJ7s7ZwicqjprRaEWdPoHKrCS215qxGYxpusRLLmJuT69ZSicuGdSfyvyKpvUNYBW1s2U3NSrT6vrCYB9e6nZUEvrqnwXPF8ArTCRXMY"
314 addresses = [
315 'bcrt1qtmp74ayg7p24uslctssvjm06q5phz4yrxucgnv', # m/0'/0'/0
316 'bcrt1q8vprchan07gzagd5e6v9wd7azyucksq2xc76k8', # m/0'/0'/1
317 'bcrt1qtuqdtha7zmqgcrr26n2rqxztv5y8rafjp9lulu', # m/0'/0'/2
318 'bcrt1qau64272ymawq26t90md6an0ps99qkrse58m640', # m/0'/0'/3
319 'bcrt1qsg97266hrh6cpmutqen8s4s962aryy77jp0fg0', # m/0'/0'/4
320 ]
321
322 self.test_importdesc({'desc': descsum_create('wpkh([80002067/0h/0h]' + xpub + '/*)'),
323 'active': True,
324 'range' : [0, 2],
325 'timestamp': 'now'
326 },
327 success=True)
328 self.test_importdesc({'desc': descsum_create('sh(wpkh([abcdef12/0h/0h]' + xpub + '/*))'),
329 'active': True,
330 'range' : [0, 2],
331 'timestamp': 'now'
332 },
333 success=True)
334 self.test_importdesc({'desc': descsum_create('pkh([12345678/0h/0h]' + xpub + '/*)'),
335 'active': True,
336 'range' : [0, 2],
337 'timestamp': 'now'
338 },
339 success=True)
340
341 assert_equal(w1.getwalletinfo()['keypoolsize'], 5 * 3)
342 for i, expected_addr in enumerate(addresses):
343 received_addr = w1.getnewaddress('', 'bech32')
344 assert_raises_rpc_error(-4, 'This wallet has no available keys', w1.getrawchangeaddress, 'bech32')
345 assert_equal(received_addr, expected_addr)
346 bech32_addr_info = w1.getaddressinfo(received_addr)
347 assert_equal(bech32_addr_info['desc'][:23], 'wpkh([80002067/0h/0h/{}]'.format(i))
348
349 shwpkh_addr = w1.getnewaddress('', 'p2sh-segwit')
350 shwpkh_addr_info = w1.getaddressinfo(shwpkh_addr)
351 assert_equal(shwpkh_addr_info['desc'][:26], 'sh(wpkh([abcdef12/0h/0h/{}]'.format(i))
352
353 pkh_addr = w1.getnewaddress('', 'legacy')
354 pkh_addr_info = w1.getaddressinfo(pkh_addr)
355 assert_equal(pkh_addr_info['desc'][:22], 'pkh([12345678/0h/0h/{}]'.format(i))
356
357 assert_equal(w1.getwalletinfo()['keypoolsize'], 4 * 3) # After retrieving a key, we don't refill the keypool again, so it's one less for each address type
358 w1.keypoolrefill()
359 assert_equal(w1.getwalletinfo()['keypoolsize'], 5 * 3)
360
361 self.log.info("Check we can change next_index")
362 # go back and forth with next_index
363 for i in [4, 0, 2, 1, 3]:
364 self.test_importdesc({'desc': descsum_create('wpkh([80002067/0h/0h]' + xpub + '/*)'),
365 'active': True,
366 'range': [0, 9],
367 'next_index': i,
368 'timestamp': 'now'
369 },
370 success=True)
371 assert_equal(w1.getnewaddress('', 'bech32'), addresses[i])
372
373 # Check active=False default
374 self.log.info('Check imported descriptors are not active by default')
375 self.test_importdesc({'desc': descsum_create('pkh([12345678/1h]' + xpub + '/*)'),
376 'range' : [0, 2],
377 'timestamp': 'now',
378 'internal': True
379 },
380 success=True)
381 assert_raises_rpc_error(-4, 'This wallet has no available keys', w1.getrawchangeaddress, 'legacy')
382
383 self.log.info('Check can activate inactive descriptor')
384 self.test_importdesc({'desc': descsum_create('pkh([12345678]' + xpub + '/*)'),
385 'range': [0, 5],
386 'active': True,
387 'timestamp': 'now',
388 'internal': True
389 },
390 success=True)
391 address = w1.getrawchangeaddress('legacy')
392 assert_equal(address, "mpA2Wh9dvZT7yfELq1UnrUmAoc5qCkMetg")
393
394 self.log.info('Check can deactivate active descriptor')
395 self.test_importdesc({'desc': descsum_create('pkh([12345678]' + xpub + '/*)'),
396 'range': [0, 5],
397 'active': False,
398 'timestamp': 'now',
399 'internal': True
400 },
401 success=True)
402 assert_raises_rpc_error(-4, 'This wallet has no available keys', w1.getrawchangeaddress, 'legacy')
403
404 self.log.info('Verify activation state is persistent')
405 w1.unloadwallet()
406 self.nodes[1].loadwallet('w1')
407 assert_raises_rpc_error(-4, 'This wallet has no available keys', w1.getrawchangeaddress, 'legacy')
408
409 # # Test importing a descriptor containing a WIF private key
410 wif_priv = "cTe1f5rdT8A8DFgVWTjyPwACsDPJM9ff4QngFxUixCSvvbg1x6sh"
411 address = "2MuhcG52uHPknxDgmGPsV18jSHFBnnRgjPg"
412 desc = "sh(wpkh(" + wif_priv + "))"
413 self.log.info("Should import a descriptor with a WIF private key as spendable")
414 self.test_importdesc({"desc": descsum_create(desc),
415 "timestamp": "now"},
416 success=True,
417 wallet=wpriv)
418
419 self.log.info('Test can import same descriptor with private key twice')
420 self.test_importdesc({"desc": descsum_create(desc), "timestamp": "now"}, success=True, wallet=wpriv)
421
422 test_address(wpriv,
423 address,
424 solvable=True,
425 ismine=True)
426 txid = w0.sendtoaddress(address, 49.99995540)
427 self.generatetoaddress(self.nodes[0], 6, w0.getnewaddress())
428 tx = wpriv.createrawtransaction([{"txid": txid, "vout": 0}], {w0.getnewaddress(): 49.999})
429 signed_tx = wpriv.signrawtransactionwithwallet(tx)
430 w1.sendrawtransaction(signed_tx['hex'])
431
432 # Make sure that we can use import and use multisig as addresses
433 self.log.info('Test that multisigs can be imported, signed for, and getnewaddress\'d')
434 self.nodes[1].createwallet(wallet_name="wmulti_priv", disable_private_keys=False, blank=True, descriptors=True)
435 wmulti_priv = self.nodes[1].get_wallet_rpc("wmulti_priv")
436 assert_equal(wmulti_priv.getwalletinfo()['keypoolsize'], 0)
437
438 xprv1 = 'tprv8ZgxMBicQKsPevADjDCWsa6DfhkVXicu8NQUzfibwX2MexVwW4tCec5mXdCW8kJwkzBRRmAay1KZya4WsehVvjTGVW6JLqiqd8DdZ4xSg52'
439 acc_xpub1 = 'tpubDCJtdt5dgJpdhW4MtaVYDhG4T4tF6jcLR1PxL43q9pq1mxvXgMS9Mzw1HnXG15vxUGQJMMSqCQHMTy3F1eW5VkgVroWzchsPD5BUojrcWs8' # /84'/0'/0'
440 chg_xpub1 = 'tpubDCXqdwWZcszwqYJSnZp8eARkxGJfHAk23KDxbztV4BbschfaTfYLTcSkSJ3TN64dRqwa1rnFUScsYormKkGqNbbPwkorQimVevXjxzUV9Gf' # /84'/1'/0'
441 xprv2 = 'tprv8ZgxMBicQKsPdSNWUhDiwTScDr6JfkZuLshTRwzvZGnMSnGikV6jxpmdDkC3YRc4T3GD6Nvg9uv6hQg73RVv1EiTXDZwxVbsLugVHU8B1aq'
442 acc_xprv2 = 'tprv8gVCsmRAxVSxyUpsL13Y7ZEWBFPWbgS5E2MmFVNGuANrknvmmn2vWnmHvU8AwEFYzR2ji6EeZLSCLVacsYkvor3Pcb5JY5FGcevqTwYvdYx'
443 acc_xpub2 = 'tpubDDBF2BTR6s8drwrfDei8WxtckGuSm1cyoKxYY1QaKSBFbHBYQArWhHPA6eJrzZej6nfHGLSURYSLHr7GuYch8aY5n61tGqgn8b4cXrMuoPH'
444 chg_xpub2 = 'tpubDCYfZY2ceyHzYzMMVPt9MNeiqtQ2T7Uyp9QSFwYXh8Vi9iJFYXcuphJaGXfF3jUQJi5Y3GMNXvM11gaL4txzZgNGK22BFAwMXynnzv4z2Jh'
445 xprv3 = 'tprv8ZgxMBicQKsPeonDt8Ka2mrQmHa61hQ5FQCsvWBTpSNzBFgM58cV2EuXNAHF14VawVpznnme3SuTbA62sGriwWyKifJmXntfNeK7zeqMCj1'
446 acc_xpub3 = 'tpubDCsWoW1kuQB9kG5MXewHqkbjPtqPueRnXju7uM2NK7y3JYb2ajAZ9EiuZXNNuE4661RAfriBWhL8UsnAPpk8zrKKnZw1Ug7X4oHgMdZiU4E'
447 chg_xpub3 = 'tpubDC6UGqnsQStngYuGD4MKsMy7eD1Yg9NTJfPdvjdG2JE5oZ7EsSL3WHg4Gsw2pR5K39ZwJ46M1wZayhedVdQtMGaUhq5S23PH6fnENK3V1sb'
448
449 self.test_importdesc({"desc":"wsh(multi(2," + xprv1 + "/84h/0h/0h/*," + xprv2 + "/84h/0h/0h/*," + xprv3 + "/84h/0h/0h/*))#m2sr93jn",
450 "active": True,
451 "range": 1000,
452 "next_index": 0,
453 "timestamp": "now"},
454 success=True,
455 wallet=wmulti_priv)
456 self.test_importdesc({"desc":"wsh(multi(2," + xprv1 + "/84h/1h/0h/*," + xprv2 + "/84h/1h/0h/*," + xprv3 + "/84h/1h/0h/*))#q3sztvx5",
457 "active": True,
458 "internal" : True,
459 "range": 1000,
460 "next_index": 0,
461 "timestamp": "now"},
462 success=True,
463 wallet=wmulti_priv)
464
465 assert_equal(wmulti_priv.getwalletinfo()['keypoolsize'], 1001) # Range end (1000) is inclusive, so 1001 addresses generated
466 addr = wmulti_priv.getnewaddress('', 'bech32') # uses receive 0
467 assert_equal(addr, 'bcrt1qdt0qy5p7dzhxzmegnn4ulzhard33s2809arjqgjndx87rv5vd0fq2czhy8') # Derived at m/84'/0'/0'/0
468 change_addr = wmulti_priv.getrawchangeaddress('bech32') # uses change 0
469 assert_equal(change_addr, 'bcrt1qt9uhe3a9hnq7vajl7a094z4s3crm9ttf8zw3f5v9gr2nyd7e3lnsy44n8e') # Derived at m/84'/1'/0'/0
470 assert_equal(wmulti_priv.getwalletinfo()['keypoolsize'], 1000)
471 txid = w0.sendtoaddress(addr, 10)
472 self.generate(self.nodes[0], 6)
473 send_txid = wmulti_priv.sendtoaddress(w0.getnewaddress(), 8) # uses change 1
474 decoded = wmulti_priv.gettransaction(txid=send_txid, verbose=True)['decoded']
475 assert_equal(len(decoded['vin'][0]['txinwitness']), 4)
476 self.sync_all()
477
478 self.nodes[1].createwallet(wallet_name="wmulti_pub", disable_private_keys=True, blank=True, descriptors=True)
479 wmulti_pub = self.nodes[1].get_wallet_rpc("wmulti_pub")
480 assert_equal(wmulti_pub.getwalletinfo()['keypoolsize'], 0)
481
482 self.test_importdesc({"desc":"wsh(multi(2,[7b2d0242/84h/0h/0h]" + acc_xpub1 + "/*,[59b09cd6/84h/0h/0h]" + acc_xpub2 + "/*,[e81a0532/84h/0h/0h]" + acc_xpub3 +"/*))#tsry0s5e",
483 "active": True,
484 "range": 1000,
485 "next_index": 0,
486 "timestamp": "now"},
487 success=True,
488 wallet=wmulti_pub)
489 self.test_importdesc({"desc":"wsh(multi(2,[7b2d0242/84h/1h/0h]" + chg_xpub1 + "/*,[59b09cd6/84h/1h/0h]" + chg_xpub2 + "/*,[e81a0532/84h/1h/0h]" + chg_xpub3 + "/*))#c08a2rzv",
490 "active": True,
491 "internal" : True,
492 "range": 1000,
493 "next_index": 0,
494 "timestamp": "now"},
495 success=True,
496 wallet=wmulti_pub)
497
498 assert_equal(wmulti_pub.getwalletinfo()['keypoolsize'], 1000) # The first one was already consumed by previous import and is detected as used
499 addr = wmulti_pub.getnewaddress('', 'bech32') # uses receive 1
500 assert_equal(addr, 'bcrt1qp8s25ckjl7gr6x2q3dx3tn2pytwp05upkjztk6ey857tt50r5aeqn6mvr9') # Derived at m/84'/0'/0'/1
501 change_addr = wmulti_pub.getrawchangeaddress('bech32') # uses change 2
502 assert_equal(change_addr, 'bcrt1qp6j3jw8yetefte7kw6v5pc89rkgakzy98p6gf7ayslaveaxqyjusnw580c') # Derived at m/84'/1'/0'/2
503 assert send_txid in self.nodes[0].getrawmempool(True)
504 assert send_txid in (x['txid'] for x in wmulti_pub.listunspent(0))
505 assert_equal(wmulti_pub.getwalletinfo()['keypoolsize'], 999)
506
507 # generate some utxos for next tests
508 utxo = self.create_outpoints(w0, outputs=[{addr: 10}])[0]
509
510 addr2 = wmulti_pub.getnewaddress('', 'bech32')
511 utxo2 = self.create_outpoints(w0, outputs=[{addr2: 10}])[0]
512
513 self.generate(self.nodes[0], 6)
514 assert_equal(wmulti_pub.getbalance(), wmulti_priv.getbalance())
515
516 # Make sure that descriptor wallets containing multiple xpubs in a single descriptor load correctly
517 wmulti_pub.unloadwallet()
518 self.nodes[1].loadwallet('wmulti_pub')
519
520 self.log.info("Multisig with distributed keys")
521 self.nodes[1].createwallet(wallet_name="wmulti_priv1", descriptors=True)
522 wmulti_priv1 = self.nodes[1].get_wallet_rpc("wmulti_priv1")
523 res = wmulti_priv1.importdescriptors([
524 {
525 "desc": descsum_create("wsh(multi(2," + xprv1 + "/84h/0h/0h/*,[59b09cd6/84h/0h/0h]" + acc_xpub2 + "/*,[e81a0532/84h/0h/0h]" + acc_xpub3 + "/*))"),
526 "active": True,
527 "range": 1000,
528 "next_index": 0,
529 "timestamp": "now"
530 },
531 {
532 "desc": descsum_create("wsh(multi(2," + xprv1 + "/84h/1h/0h/*,[59b09cd6/84h/1h/0h]" + chg_xpub2 + "/*,[e81a0532/84h/1h/0h]" + chg_xpub3 + "/*))"),
533 "active": True,
534 "internal" : True,
535 "range": 1000,
536 "next_index": 0,
537 "timestamp": "now"
538 }])
539 assert_equal(res[0]['success'], True)
540 assert_equal(res[0]['warnings'][0], 'Not all private keys provided. Some wallet functionality may return unexpected errors')
541 assert_equal(res[1]['success'], True)
542 assert_equal(res[1]['warnings'][0], 'Not all private keys provided. Some wallet functionality may return unexpected errors')
543
544 self.nodes[1].createwallet(wallet_name='wmulti_priv2', blank=True, descriptors=True)
545 wmulti_priv2 = self.nodes[1].get_wallet_rpc('wmulti_priv2')
546 res = wmulti_priv2.importdescriptors([
547 {
548 "desc": descsum_create("wsh(multi(2,[7b2d0242/84h/0h/0h]" + acc_xpub1 + "/*," + xprv2 + "/84h/0h/0h/*,[e81a0532/84h/0h/0h]" + acc_xpub3 + "/*))"),
549 "active": True,
550 "range": 1000,
551 "next_index": 0,
552 "timestamp": "now"
553 },
554 {
555 "desc": descsum_create("wsh(multi(2,[7b2d0242/84h/1h/0h]" + chg_xpub1 + "/*," + xprv2 + "/84h/1h/0h/*,[e81a0532/84h/1h/0h]" + chg_xpub3 + "/*))"),
556 "active": True,
557 "internal" : True,
558 "range": 1000,
559 "next_index": 0,
560 "timestamp": "now"
561 }])
562 assert_equal(res[0]['success'], True)
563 assert_equal(res[0]['warnings'][0], 'Not all private keys provided. Some wallet functionality may return unexpected errors')
564 assert_equal(res[1]['success'], True)
565 assert_equal(res[1]['warnings'][0], 'Not all private keys provided. Some wallet functionality may return unexpected errors')
566
567 rawtx = self.nodes[1].createrawtransaction([utxo], {w0.getnewaddress(): 9.999})
568 tx_signed_1 = wmulti_priv1.signrawtransactionwithwallet(rawtx)
569 assert_equal(tx_signed_1['complete'], False)
570 tx_signed_2 = wmulti_priv2.signrawtransactionwithwallet(tx_signed_1['hex'])
571 assert_equal(tx_signed_2['complete'], True)
572 self.nodes[1].sendrawtransaction(tx_signed_2['hex'])
573
574 self.log.info("We can create and use a huge multisig under P2WSH")
575 self.nodes[1].createwallet(wallet_name='wmulti_priv_big', blank=True, descriptors=True)
576 wmulti_priv_big = self.nodes[1].get_wallet_rpc('wmulti_priv_big')
577 xkey = "tprv8ZgxMBicQKsPeZSeYx7VXDDTs3XrTcmZQpRLbAeSQFCQGgKwR4gKpcxHaKdoTNHniv4EPDJNdzA3KxRrrBHcAgth8fU5X4oCndkkxk39iAt/*"
578 xkey_int = "tprv8ZgxMBicQKsPeZSeYx7VXDDTs3XrTcmZQpRLbAeSQFCQGgKwR4gKpcxHaKdoTNHniv4EPDJNdzA3KxRrrBHcAgth8fU5X4oCndkkxk39iAt/1/*"
579 res = wmulti_priv_big.importdescriptors([
580 {
581 "desc": descsum_create(f"wsh(sortedmulti(20,{(xkey + ',') * 19}{xkey}))"),
582 "active": True,
583 "range": 1000,
584 "next_index": 0,
585 "timestamp": "now"
586 },
587 {
588 "desc": descsum_create(f"wsh(sortedmulti(20,{(xkey_int + ',') * 19}{xkey_int}))"),
589 "active": True,
590 "internal": True,
591 "range": 1000,
592 "next_index": 0,
593 "timestamp": "now"
594 }])
595 assert_equal(res[0]['success'], True)
596 assert_equal(res[1]['success'], True)
597
598 addr = wmulti_priv_big.getnewaddress()
599 w0.sendtoaddress(addr, 10)
600 self.generate(self.nodes[0], 1)
601 # It is standard and would relay.
602 txid = wmulti_priv_big.sendtoaddress(w0.getnewaddress(), 9.999)
603 decoded = wmulti_priv_big.gettransaction(txid=txid, verbose=True)['decoded']
604 # 20 sigs + dummy + witness script
605 assert_equal(len(decoded['vin'][0]['txinwitness']), 22)
606
607
608 self.log.info("Under P2SH, multisig are standard with up to 15 "
609 "compressed keys")
610 self.nodes[1].createwallet(wallet_name='multi_priv_big_legacy',
611 blank=True, descriptors=True)
612 multi_priv_big = self.nodes[1].get_wallet_rpc('multi_priv_big_legacy')
613 res = multi_priv_big.importdescriptors([
614 {
615 "desc": descsum_create(f"sh(multi(15,{(xkey + ',') * 14}{xkey}))"),
616 "active": True,
617 "range": 1000,
618 "next_index": 0,
619 "timestamp": "now"
620 },
621 {
622 "desc": descsum_create(f"sh(multi(15,{(xkey_int + ',') * 14}{xkey_int}))"),
623 "active": True,
624 "internal": True,
625 "range": 1000,
626 "next_index": 0,
627 "timestamp": "now"
628 }])
629 assert_equal(res[0]['success'], True)
630 assert_equal(res[1]['success'], True)
631
632 addr = multi_priv_big.getnewaddress("", "legacy")
633 w0.sendtoaddress(addr, 10)
634 self.generate(self.nodes[0], 6)
635 # It is standard and would relay.
636 txid = multi_priv_big.sendtoaddress(w0.getnewaddress(), 10, "", "", True)
637 decoded = multi_priv_big.gettransaction(txid=txid, verbose=True)['decoded']
638
639 self.log.info("Amending multisig with new private keys")
640 self.nodes[1].createwallet(wallet_name="wmulti_priv3", descriptors=True)
641 wmulti_priv3 = self.nodes[1].get_wallet_rpc("wmulti_priv3")
642 res = wmulti_priv3.importdescriptors([
643 {
644 "desc": descsum_create("wsh(multi(2," + xprv1 + "/84h/0h/0h/*,[59b09cd6/84h/0h/0h]" + acc_xpub2 + "/*,[e81a0532/84h/0h/0h]" + acc_xpub3 + "/*))"),
645 "active": True,
646 "range": 1000,
647 "next_index": 0,
648 "timestamp": "now"
649 }])
650 assert_equal(res[0]['success'], True)
651 res = wmulti_priv3.importdescriptors([
652 {
653 "desc": descsum_create("wsh(multi(2," + xprv1 + "/84h/0h/0h/*,[59b09cd6/84h/0h/0h]" + acc_xprv2 + "/*,[e81a0532/84h/0h/0h]" + acc_xpub3 + "/*))"),
654 "active": True,
655 "range": 1000,
656 "next_index": 0,
657 "timestamp": "now"
658 }])
659 assert_equal(res[0]['success'], True)
660
661 rawtx = self.nodes[1].createrawtransaction([utxo2], {w0.getnewaddress(): 9.999})
662 tx = wmulti_priv3.signrawtransactionwithwallet(rawtx)
663 assert_equal(tx['complete'], True)
664 self.nodes[1].sendrawtransaction(tx['hex'])
665
666 self.log.info("Combo descriptors cannot be active")
667 self.test_importdesc({"desc": descsum_create("combo(tpubDCJtdt5dgJpdhW4MtaVYDhG4T4tF6jcLR1PxL43q9pq1mxvXgMS9Mzw1HnXG15vxUGQJMMSqCQHMTy3F1eW5VkgVroWzchsPD5BUojrcWs8/*)"),
668 "active": True,
669 "range": 1,
670 "timestamp": "now"},
671 success=False,
672 error_code=-4,
673 error_message="Combo descriptors cannot be set to active")
674
675 self.log.info("Descriptors with no type cannot be active")
676 self.test_importdesc({"desc": descsum_create("pk(tpubDCJtdt5dgJpdhW4MtaVYDhG4T4tF6jcLR1PxL43q9pq1mxvXgMS9Mzw1HnXG15vxUGQJMMSqCQHMTy3F1eW5VkgVroWzchsPD5BUojrcWs8/*)"),
677 "active": True,
678 "range": 1,
679 "timestamp": "now"},
680 success=True,
681 warnings=["Unknown output type, cannot set descriptor to active."])
682
683 self.log.info("Test importing a descriptor to an encrypted wallet")
684
685 descriptor = {"desc": descsum_create("pkh(" + xpriv + "/1h/*h)"),
686 "timestamp": "now",
687 "active": True,
688 "range": [0,4000],
689 "next_index": 4000}
690
691 self.nodes[0].createwallet("temp_wallet", blank=True, descriptors=True)
692 temp_wallet = self.nodes[0].get_wallet_rpc("temp_wallet")
693 temp_wallet.importdescriptors([descriptor])
694 self.generatetoaddress(self.nodes[0], COINBASE_MATURITY + 1, temp_wallet.getnewaddress())
695 self.generatetoaddress(self.nodes[0], COINBASE_MATURITY + 1, temp_wallet.getnewaddress())
696
697 self.nodes[0].createwallet("encrypted_wallet", blank=True, descriptors=True, passphrase="passphrase")
698 encrypted_wallet = self.nodes[0].get_wallet_rpc("encrypted_wallet")
699
700 descriptor["timestamp"] = 0
701 descriptor["next_index"] = 0
702
703 encrypted_wallet.walletpassphrase("passphrase", 99999)
704 with concurrent.futures.ThreadPoolExecutor(max_workers=1) as thread:
705 with self.nodes[0].assert_debug_log(expected_msgs=["Rescan started from block 0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206... (slow variant inspecting all blocks)"], timeout=10):
706 importing = thread.submit(encrypted_wallet.importdescriptors, requests=[descriptor])
707
708 # Set the passphrase timeout to 1 to test that the wallet remains unlocked during the rescan
709 self.nodes[0].cli("-rpcwallet=encrypted_wallet").walletpassphrase("passphrase", 1)
710
711 try:
712 self.nodes[0].cli("-rpcwallet=encrypted_wallet").walletlock()
713 except JSONRPCException as e:
714 assert e.error["code"] == -4 and "Error: the wallet is currently being used to rescan the blockchain for related transactions. Please call `abortrescan` before locking the wallet." in e.error["message"]
715
716 try:
717 self.nodes[0].cli("-rpcwallet=encrypted_wallet").walletpassphrasechange("passphrase", "newpassphrase")
718 except JSONRPCException as e:
719 assert e.error["code"] == -4 and "Error: the wallet is currently being used to rescan the blockchain for related transactions. Please call `abortrescan` before changing the passphrase." in e.error["message"]
720
721 assert_equal(importing.result(), [{"success": True}])
722
723 assert_equal(temp_wallet.getbalance(), encrypted_wallet.getbalance())
724
725 self.log.info("Multipath descriptors")
726 self.nodes[1].createwallet(wallet_name="multipath", descriptors=True, blank=True)
727 w_multipath = self.nodes[1].get_wallet_rpc("multipath")
728 self.nodes[1].createwallet(wallet_name="multipath_split", descriptors=True, blank=True)
729 w_multisplit = self.nodes[1].get_wallet_rpc("multipath_split")
730 timestamp = int(time.time())
731
732 self.test_importdesc({"desc": descsum_create(f"wpkh({xpriv}/<10;20>/0/*)"),
733 "active": True,
734 "range": 10,
735 "timestamp": "now",
736 "label": "some label"},
737 success=False,
738 error_code=-8,
739 error_message="Multipath descriptors should not have a label",
740 wallet=w_multipath)
741 self.test_importdesc({"desc": descsum_create(f"wpkh({xpriv}/<10;20>/0/*)"),
742 "active": True,
743 "range": 10,
744 "timestamp": timestamp,
745 "internal": True},
746 success=False,
747 error_code=-5,
748 error_message="Cannot have multipath descriptor while also specifying \'internal\'",
749 wallet=w_multipath)
750
751 self.test_importdesc({"desc": descsum_create(f"wpkh({xpriv}/<10;20>/0/*)"),
752 "active": True,
753 "range": 10,
754 "timestamp": timestamp},
755 success=True,
756 wallet=w_multipath)
757
758 self.test_importdesc({"desc": descsum_create(f"wpkh({xpriv}/10/0/*)"),
759 "active": True,
760 "range": 10,
761 "timestamp": timestamp},
762 success=True,
763 wallet=w_multisplit)
764 self.test_importdesc({"desc": descsum_create(f"wpkh({xpriv}/20/0/*)"),
765 "active": True,
766 "range": 10,
767 "internal": True,
768 "timestamp": timestamp},
769 success=True,
770 wallet=w_multisplit)
771 for _ in range(0, 10):
772 assert_equal(w_multipath.getnewaddress(address_type="bech32"), w_multisplit.getnewaddress(address_type="bech32"))
773 assert_equal(w_multipath.getrawchangeaddress(address_type="bech32"), w_multisplit.getrawchangeaddress(address_type="bech32"))
774 assert_equal(sorted(w_multipath.listdescriptors()["descriptors"], key=lambda x: x["desc"]), sorted(w_multisplit.listdescriptors()["descriptors"], key=lambda x: x["desc"]))
775
776 if __name__ == '__main__':
777 ImportDescriptorsTest(__file__).main()
778