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