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.
1#!/usr/bin/env python32# Copyright (c) 2016-present The Bitcoin Core developers3# Distributed under the MIT software license, see the accompanying4# file COPYING or http://www.opensource.org/licenses/mit-license.php.5"""Test Hierarchical Deterministic wallet function."""7import shutil9from test_framework.blocktools import COINBASE_MATURITY10from test_framework.descriptors import descsum_create11from test_framework.test_framework import BitcoinTestFramework12from test_framework.util import (13assert_equal,14wallet_importprivkey,15assert_raises_rpc_error,16)19class WalletHDTest(BitcoinTestFramework):20def set_test_params(self):21self.setup_clean_chain = True22self.num_nodes = 223self.extra_args = [[], ['-keypool=0']]24# whitelist peers to speed up tx relay / mempool sync25self.noban_tx_relay = True27def skip_test_if_missing_module(self):28self.skip_if_no_wallet()30def test_addhdkey(self):31self.log.info("Test addhdkey")32def_wallet = self.nodes[0].get_wallet_rpc(self.default_wallet_name)33self.nodes[0].createwallet("hdkey")34wallet = self.nodes[0].get_wallet_rpc("hdkey")36assert_equal(len(wallet.gethdkeys()), 1)38wallet.addhdkey()39xpub_info = wallet.gethdkeys()40assert_equal(len(xpub_info), 2)41for x in xpub_info:42if len(x["descriptors"]) == 1 and x["descriptors"][0]["desc"].startswith("unused("):43break44else:45assert False, "Did not find HD key with no descriptors"47imp_xpub_info = def_wallet.gethdkeys(private=True)[0]48imp_xpub = imp_xpub_info["xpub"]49imp_xprv = imp_xpub_info["xprv"]51assert_raises_rpc_error(-5, "Extended public key (xpub) provided, but extended private key (xprv) is required", wallet.addhdkey, imp_xpub)52add_res = wallet.addhdkey(imp_xprv)53expected_unused_desc = descsum_create(f"unused({imp_xpub})")54assert_equal(add_res["xpub"], imp_xpub)55xpub_info = wallet.gethdkeys()56assert_equal(len(xpub_info), 3)57for x in xpub_info:58if x["xpub"] == imp_xpub:59assert_equal(len(x["descriptors"]), 1)60assert_equal(x["descriptors"][0]["desc"], expected_unused_desc)61break62else:63assert False, "Added HD key was not found in wallet"65for d in wallet.listdescriptors()["descriptors"]:66if d["desc"] == expected_unused_desc:67assert_equal(d["active"], False)68break69else:70assert False, "Added HD key's descriptor was not found in wallet"72assert_raises_rpc_error(-4, "HD key already exists", wallet.addhdkey, imp_xprv)74def test_addhdkey_noprivs(self):75self.log.info("Test addhdkey is not available for wallets without privkeys")76self.nodes[0].createwallet("hdkey_noprivs", disable_private_keys=True)77wallet = self.nodes[0].get_wallet_rpc("hdkey_noprivs")78assert_raises_rpc_error(-4, "addhdkey is not available for wallets without private keys", wallet.addhdkey)80def run_test(self):81# Make sure we use hd, keep masterkeyid82hd_fingerprint = self.nodes[1].getaddressinfo(self.nodes[1].getnewaddress())['hdmasterfingerprint']83assert_equal(len(hd_fingerprint), 8)85# create an internal key86change_addr = self.nodes[1].getrawchangeaddress()87change_addrV = self.nodes[1].getaddressinfo(change_addr)88assert_equal(change_addrV["hdkeypath"], "m/84h/1h/0h/1/0")90# Import a non-HD private key in the HD wallet91non_hd_add = 'bcrt1qmevj8zfx0wdvp05cqwkmr6mxkfx60yezwjksmt'92non_hd_key = 'cS9umN9w6cDMuRVYdbkfE4c7YUFLJRoXMfhQ569uY4odiQbVN8Rt'93wallet_importprivkey(self.nodes[1], non_hd_key, "now")95# This should be enough to keep the master key and the non-HD key96self.nodes[1].backupwallet(self.nodes[1].datadir_path / "hd.bak")98# Derive some HD addresses and remember the last99# Also send funds to each add100self.generate(self.nodes[0], COINBASE_MATURITY + 1)101hd_add = None102NUM_HD_ADDS = 10103for i in range(1, NUM_HD_ADDS + 1):104hd_add = self.nodes[1].getnewaddress()105hd_info = self.nodes[1].getaddressinfo(hd_add)106assert_equal(hd_info["hdkeypath"], "m/84h/1h/0h/0/" + str(i))107assert_equal(hd_info["hdmasterfingerprint"], hd_fingerprint)108self.nodes[0].sendtoaddress(hd_add, 1)109self.generate(self.nodes[0], 1)110self.nodes[0].sendtoaddress(non_hd_add, 1)111self.generate(self.nodes[0], 1)113# create an internal key (again)114change_addr = self.nodes[1].getrawchangeaddress()115change_addrV = self.nodes[1].getaddressinfo(change_addr)116assert_equal(change_addrV["hdkeypath"], "m/84h/1h/0h/1/1")118self.sync_all()119assert_equal(self.nodes[1].getbalance(), NUM_HD_ADDS + 1)121self.log.info("Restore backup ...")122self.stop_node(1)123# we need to delete the complete chain directory124# otherwise node1 would auto-recover all funds in flag the keypool keys as used125self.cleanup_folder(self.nodes[1].blocks_path)126self.cleanup_folder(self.nodes[1].chain_path / "chainstate")127shutil.copyfile(128self.nodes[1].datadir_path / "hd.bak",129self.nodes[1].wallets_path / self.default_wallet_name / self.wallet_data_filename130)131self.start_node(1)133# Assert that derivation is deterministic134hd_add_2 = None135for i in range(1, NUM_HD_ADDS + 1):136hd_add_2 = self.nodes[1].getnewaddress()137hd_info_2 = self.nodes[1].getaddressinfo(hd_add_2)138assert_equal(hd_info_2["hdkeypath"], "m/84h/1h/0h/0/" + str(i))139assert_equal(hd_info_2["hdmasterfingerprint"], hd_fingerprint)140assert_equal(hd_add, hd_add_2)141self.connect_nodes(0, 1)142self.sync_all()144# Needs rescan145self.nodes[1].rescanblockchain()146assert_equal(self.nodes[1].getbalance(), NUM_HD_ADDS + 1)148# Try a RPC based rescan149self.stop_node(1)150self.cleanup_folder(self.nodes[1].blocks_path)151self.cleanup_folder(self.nodes[1].chain_path / "chainstate")152shutil.copyfile(153self.nodes[1].datadir_path / "hd.bak",154self.nodes[1].wallets_path / self.default_wallet_name / self.wallet_data_filename155)156self.start_node(1, extra_args=self.extra_args[1])157self.connect_nodes(0, 1)158self.sync_all()159# Wallet automatically scans blocks older than key on startup160assert_equal(self.nodes[1].getbalance(), NUM_HD_ADDS + 1)161out = self.nodes[1].rescanblockchain(0, 1)162assert_equal(out['start_height'], 0)163assert_equal(out['stop_height'], 1)164out = self.nodes[1].rescanblockchain()165assert_equal(out['start_height'], 0)166assert_equal(out['stop_height'], self.nodes[1].getblockcount())167assert_equal(self.nodes[1].getbalance(), NUM_HD_ADDS + 1)169# send a tx and make sure its using the internal chain for the changeoutput170txid = self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), 1)171outs = self.nodes[1].gettransaction(txid=txid, verbose=True)['decoded']['vout']172keypath = ""173for out in outs:174if out['value'] != 1:175keypath = self.nodes[1].getaddressinfo(out['scriptPubKey']['address'])['hdkeypath']177assert_equal(keypath[0:14], "m/84h/1h/0h/1/")179self.test_addhdkey()180self.test_addhdkey_noprivs()182if __name__ == '__main__':183WalletHDTest(__file__).main()
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.