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()