Inside Bitcoin
Opening the selected file…
Inside Bitcoin
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
1252# Craft wsh(pkh(key))1253pubkey = wallet.getaddressinfo(wallet.getnewaddress())["pubkey"]1254pkh_script = key_to_p2pkh_script(pubkey).hex()1255wsh_pkh_script = script_to_p2wsh_script(pkh_script).hex()1256wsh_pkh_addr = script_to_p2wsh(pkh_script)1258# Legacy single key scripts (i.e. pkh(key) and pk(key)) are not inserted into mapScripts1259# automatically, they need to be imported directly if we want to receive to P2WSH (or P2SH)1260# wrappings of such scripts.1261wallet.importaddress(address=pkh_script, p2sh=False)1262wallet.importaddress(address=wsh_pkh_script, p2sh=False)1264def_wallet.sendtoaddress(wsh_pkh_addr, 5)1265self.generate(self.nodes[0], 6)1266assert_equal(wallet.getbalances()['mine']['trusted'], 5)1268_, wallet = self.migrate_and_get_rpc("p2wsh")1270assert_equal(wallet.getbalances()['mine']['trusted'], 5)1271addr_info = wallet.getaddressinfo(wsh_pkh_addr)1272assert_equal(addr_info["ismine"], True)1273assert_equal(addr_info["solvable"], True)1275wallet.unloadwallet()1277def test_disallowed_p2wsh(self):1278self.log.info("Test that P2WSH output scripts with invalid witnessScripts are not migrated and do not cause migration failure")1279def_wallet = self.master_node.get_wallet_rpc(self.default_wallet_name)1281wallet = self.create_legacy_wallet("invalid_p2wsh")1283invalid_addrs = []1285# For a P2WSH output script stored in the legacy wallet's mapScripts, both the native P2WSH1286# and the P2SH-P2WSH are detected by IsMine. We need to verify that descriptors for both1287# output scripts are added to the resulting descriptor wallet.1288# However, this cannot be done using a multisig as wallet migration treats multisigs specially.1289# Instead, this is tested by importing a wsh(pkh()) script. But importing this directly will1290# insert the wsh() into setWatchOnly which means that the setWatchOnly migration ends up handling1291# this case, which we do not want.1292# In order to get the wsh(pkh()) into only mapScripts and not setWatchOnly, we need to utilize1293# importmulti and wrap the wsh(pkh()) inside of a sh(). This will insert the sh(wsh(pkh())) into1294# setWatchOnly but not the wsh(pkh()).1295# Furthermore, migration should not migrate the wsh(pkh()) if the key is uncompressed.1296comp_wif, comp_pubkey = generate_keypair(compressed=True, wif=True)1297comp_pkh_script = key_to_p2pkh_script(comp_pubkey).hex()1298comp_wsh_pkh_script = script_to_p2wsh_script(comp_pkh_script).hex()1299comp_sh_wsh_pkh_script = script_to_p2sh_script(comp_wsh_pkh_script).hex()1300comp_wsh_pkh_addr = script_to_p2wsh(comp_pkh_script)1302uncomp_wif, uncomp_pubkey = generate_keypair(compressed=False, wif=True)1303uncomp_pkh_script = key_to_p2pkh_script(uncomp_pubkey).hex()1304uncomp_wsh_pkh_script = script_to_p2wsh_script(uncomp_pkh_script).hex()1305uncomp_sh_wsh_pkh_script = script_to_p2sh_script(uncomp_wsh_pkh_script).hex()1306uncomp_wsh_pkh_addr = script_to_p2wsh(uncomp_pkh_script)1307invalid_addrs.append(uncomp_wsh_pkh_addr)1309import_res = wallet.importmulti([1310{1311"scriptPubKey": comp_sh_wsh_pkh_script,1312"timestamp": "now",1313"redeemscript": comp_wsh_pkh_script,1314"witnessscript": comp_pkh_script,1315"keys": [1316comp_wif,1317],1318},1319{1320"scriptPubKey": uncomp_sh_wsh_pkh_script,1321"timestamp": "now",1322"redeemscript": uncomp_wsh_pkh_script,1323"witnessscript": uncomp_pkh_script,1324"keys": [1325uncomp_wif,1326],1327},1328])1329assert_equal(import_res[0]["success"], True)1330assert_equal(import_res[1]["success"], True)1332# Create a wsh(sh(pkh())) - P2SH inside of P2WSH is invalid1333comp_sh_pkh_script = script_to_p2sh_script(comp_pkh_script).hex()1334wsh_sh_pkh_script = script_to_p2wsh_script(comp_sh_pkh_script).hex()1335wsh_sh_pkh_addr = script_to_p2wsh(comp_sh_pkh_script)1336invalid_addrs.append(wsh_sh_pkh_addr)1338# Import wsh(sh(pkh()))1339wallet.importaddress(address=comp_sh_pkh_script, p2sh=False)1340wallet.importaddress(address=wsh_sh_pkh_script, p2sh=False)1342# Create a wsh(wsh(pkh())) - P2WSH inside of P2WSH is invalid1343wsh_wsh_pkh_script = script_to_p2wsh_script(comp_wsh_pkh_script).hex()1344wsh_wsh_pkh_addr = script_to_p2wsh(comp_wsh_pkh_script)1345invalid_addrs.append(wsh_wsh_pkh_addr)1347# Import wsh(wsh(pkh()))1348wallet.importaddress(address=wsh_wsh_pkh_script, p2sh=False)1350# The wsh(pkh()) with a compressed key is always valid, so we should see that the wallet detects it as ismine, not1351# watchonly, and can provide us information about the witnessScript via "embedded"1352comp_wsh_pkh_addr_info = wallet.getaddressinfo(comp_wsh_pkh_addr)1353assert_equal(comp_wsh_pkh_addr_info["ismine"], True)1354assert_equal(comp_wsh_pkh_addr_info["iswatchonly"], False)1355assert "embedded" in comp_wsh_pkh_addr_info1357# The invalid addresses are invalid, so the legacy wallet should not detect them as ismine,1358# nor consider them watchonly. However, because the legacy wallet has the witnessScripts/redeemScripts,1359# we should see information about those in "embedded"1360for addr in invalid_addrs:1361addr_info = wallet.getaddressinfo(addr)1362assert_equal(addr_info["ismine"], False)1363assert_equal(addr_info["iswatchonly"], False)1364assert "embedded" in addr_info1366# Fund those output scripts, although the invalid addresses will not have any balance.1367# This behavior follows as the addresses are not ismine.1368def_wallet.send([{comp_wsh_pkh_addr: 1}] + [{k: i + 1} for i, k in enumerate(invalid_addrs)])1369self.generate(self.nodes[0], 6)1370bal = wallet.getbalances()1371assert_equal(bal["mine"]["trusted"], 1)1372assert_equal(bal["watchonly"]["trusted"], 0)1374res, wallet = self.migrate_and_get_rpc("invalid_p2wsh")1375assert "watchonly_name" not in res1376assert "solvables_name" not in res1378assert_equal(wallet.getbalances()["mine"]["trusted"], 1)1380# After migration, the wsh(pkh()) with a compressed key is still valid and the descriptor wallet will have1381# information about the witnessScript1382comp_wsh_pkh_addr_info = wallet.getaddressinfo(comp_wsh_pkh_addr)1383assert_equal(comp_wsh_pkh_addr_info["ismine"], True)1384assert "embedded" in comp_wsh_pkh_addr_info1386# After migration, the invalid addresses should still not be detected as ismine and not watchonly.1387# The descriptor wallet should not have migrated these at all, so there should additionally be no1388# information in "embedded" about the witnessScripts/redeemScripts.1389for addr in invalid_addrs:1390addr_info = wallet.getaddressinfo(addr)1391assert_equal(addr_info["ismine"], False)1392assert "embedded" not in addr_info1394wallet.unloadwallet()1396def test_miniscript(self):1397# It turns out that due to how signing logic works, legacy wallets that have valid miniscript witnessScripts1398# and the private keys for them can still sign and spend them, even though output scripts involving them1399# as a witnessScript would not be detected as ISMINE_SPENDABLE.1400self.log.info("Test migration of a legacy wallet containing miniscript")1401def_wallet = self.master_node.get_wallet_rpc(self.default_wallet_name)1402wallet = self.create_legacy_wallet("miniscript")1404privkey, _ = generate_keypair(compressed=True, wif=True)1406# Make a descriptor where we only have some of the keys. This will be migrated to the watchonly wallet.1407some_keys_priv_desc = descsum_create(f"wsh(or_b(pk({privkey}),s:pk(029ffbe722b147f3035c87cb1c60b9a5947dd49c774cc31e94773478711a929ac0)))")1408some_keys_addr = self.master_node.deriveaddresses(some_keys_priv_desc)[0]1410# Make a descriptor where we have all of the keys. This will stay in the migrated wallet1411all_keys_priv_desc = descsum_create(f"wsh(and_v(v:pk({privkey}),1))")1412all_keys_addr = self.master_node.deriveaddresses(all_keys_priv_desc)[0]1414imp = wallet.importmulti([1415{1416"desc": some_keys_priv_desc,1417"timestamp": "now",1418},1419{1420"desc": all_keys_priv_desc,1421"timestamp": "now",1422}1423])1424assert_equal(imp[0]["success"], True)1425assert_equal(imp[1]["success"], True)1427def_wallet.sendtoaddress(some_keys_addr, 1)1428def_wallet.sendtoaddress(all_keys_addr, 1)1429self.generate(self.master_node, 6)1430# Check that the miniscript can be spent by the legacy wallet1431send_res = wallet.send(outputs=[{some_keys_addr: 1},{all_keys_addr: 0.75}], include_watching=True, change_address=def_wallet.getnewaddress())1432assert_equal(send_res["complete"], True)1433self.generate(self.old_node, 6)1434assert_equal(wallet.getbalances()["watchonly"]["trusted"], 1.75)1436_, wallet = self.migrate_and_get_rpc("miniscript")1438# The miniscript with all keys should be in the migrated wallet1439assert_equal(wallet.getbalances()["mine"], {"trusted": 0.75, "untrusted_pending": 0, "immature": 0, "nonmempool": 0})1440assert_equal(wallet.getaddressinfo(all_keys_addr)["ismine"], True)1441assert_equal(wallet.getaddressinfo(some_keys_addr)["ismine"], False)1443# The miniscript with some keys should be in the watchonly wallet1444assert "miniscript_watchonly" in self.master_node.listwallets()1445watchonly = self.master_node.get_wallet_rpc("miniscript_watchonly")1446assert_equal(watchonly.getbalances()["mine"], {"trusted": 1, "untrusted_pending": 0, "immature": 0, "nonmempool": 0})1447assert_equal(watchonly.getaddressinfo(some_keys_addr)["ismine"], True)1448assert_equal(watchonly.getaddressinfo(all_keys_addr)["ismine"], False)1450def test_taproot(self):1451# It turns out that due to how signing logic works, legacy wallets that have the private key for a Taproot1452# output key will be able to sign and spend those scripts, even though they would not be detected as ISMINE_SPENDABLE.1453self.log.info("Test migration of Taproot scripts")1454def_wallet = self.master_node.get_wallet_rpc(self.default_wallet_name)1455wallet = self.create_legacy_wallet("taproot")1457privkey, _ = generate_keypair(compressed=True, wif=True)1459rawtr_desc = descsum_create(f"rawtr({privkey})")1460rawtr_addr = self.master_node.deriveaddresses(rawtr_desc)[0]1461rawtr_spk = self.master_node.validateaddress(rawtr_addr)["scriptPubKey"]1462tr_desc = descsum_create(f"tr({privkey})")1463tr_addr = self.master_node.deriveaddresses(tr_desc)[0]1464tr_spk = self.master_node.validateaddress(tr_addr)["scriptPubKey"]1465tr_script_desc = descsum_create(f"tr(9ffbe722b147f3035c87cb1c60b9a5947dd49c774cc31e94773478711a929ac0,pk({privkey}))")1466tr_script_addr = self.master_node.deriveaddresses(tr_script_desc)[0]1467tr_script_spk = self.master_node.validateaddress(tr_script_addr)["scriptPubKey"]1469wallet.importaddress(rawtr_spk)1470wallet.importaddress(tr_spk)1471wallet.importaddress(tr_script_spk)1472wallet.importprivkey(privkey)1474txid = def_wallet.send([{rawtr_addr: 1},{tr_addr: 2}, {tr_script_addr: 3}])["txid"]1475rawtr_vout = find_vout_for_address(self.master_node, txid, rawtr_addr)1476tr_vout = find_vout_for_address(self.master_node, txid, tr_addr)1477tr_script_vout = find_vout_for_address(self.master_node, txid, tr_script_addr)1478self.generate(self.master_node, 6)1479assert_equal(wallet.getbalances()["watchonly"]["trusted"], 6)1481# Check that the rawtr can be spent by the legacy wallet1482send_res = wallet.send(outputs=[{rawtr_addr: 0.5}], include_watching=True, change_address=def_wallet.getnewaddress(), inputs=[{"txid": txid, "vout": rawtr_vout}])1483assert_equal(send_res["complete"], True)1484self.generate(self.old_node, 6)1485assert_equal(wallet.getbalances()["watchonly"]["trusted"], 5.5)1486assert_equal(wallet.getbalances()["mine"]["trusted"], 0)1488# Check that the tr() cannot be spent by the legacy wallet1489send_res = wallet.send(outputs=[{def_wallet.getnewaddress(): 4}], include_watching=True, inputs=[{"txid": txid, "vout": tr_vout}, {"txid": txid, "vout": tr_script_vout}])1490assert_equal(send_res["complete"], False)1492res, wallet = self.migrate_and_get_rpc("taproot")1494# The rawtr should be migrated1495assert_equal(wallet.getbalances()["mine"], {"trusted": 0.5, "untrusted_pending": 0, "immature": 0, "nonmempool": 0})1496assert_equal(wallet.getaddressinfo(rawtr_addr)["ismine"], True)1497assert_equal(wallet.getaddressinfo(tr_addr)["ismine"], False)1498assert_equal(wallet.getaddressinfo(tr_script_addr)["ismine"], False)1500# The tr() with some keys should be in the watchonly wallet
Independent explanations by MyBitcoinExplorer. Not affiliated with or endorsed by Bitcoin Core. Guides are educational and do not replace the source or its tests.
Bitcoin Core source © its contributors, distributed under the MIT license. Original notices and bundled third-party licenses are retained in the source tree.