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) 2025-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 wallet derivehdkey RPC."""7from test_framework.test_framework import BitcoinTestFramework8from test_framework.util import (9assert_equal,10assert_raises_rpc_error,11)12from test_framework.wallet_util import WalletUnlock15class WalletDeriveHDKeyTest(BitcoinTestFramework):16def set_test_params(self):17self.setup_clean_chain = True18self.num_nodes = 220def skip_test_if_missing_module(self):21self.skip_if_no_wallet()23def run_test(self):24self.test_basic_derivehdkey()25self.test_multiple_unused_keys()26self.test_active_descriptor()27self.test_inactive_descriptor()28self.test_noprivs_blank()29self.test_compare()31def test_basic_derivehdkey(self):32self.log.info("Test derivehdkey basics")33self.nodes[0].createwallet("basic", blank=True)34wallet = self.nodes[0].get_wallet_rpc("basic")35assert_raises_rpc_error(36-8,37"Derivation path requires at least one hardened step",38wallet.derivehdkey,39"m",40)41assert_raises_rpc_error(42-8,43"Derivation path requires at least one hardened step",44wallet.derivehdkey,45"m/87/0/0",46)47# A BIP32 keypath element must be in 0..2^31-1. The48# value 2^31 (2147483648 == 0x80000000) is out of range.49for path in ["m/2147483648", "m/2147483648h", "m/2147483648'"]:50assert_raises_rpc_error(51-8,52"Invalid BIP32 keypath",53wallet.derivehdkey,54path,55)56assert_raises_rpc_error(57-5,58"No active or unused(KEY) descriptor found",59wallet.derivehdkey,60"m/87h",61)62wallet.addhdkey()63xpub_info = wallet.derivehdkey("m/87h")64assert "xprv" not in xpub_info65xpub = xpub_info["xpub"]66root_fingerprint = wallet.derivehdkey("m/87h/0h")["origin"][1:9]67assert_equal(xpub_info["origin"], f"[{root_fingerprint}/87h]")68too_deep_path = "m/" + "/".join(["0h"] * 256)69assert_raises_rpc_error(70-8,71"Unable to derive HD key at the requested path",72wallet.derivehdkey,73too_deep_path,74)76xpub_info = wallet.derivehdkey("m/87h/0h/0h/0")77xpub_priv_info = wallet.derivehdkey("m/87h/0h/0h/0", private=True)78xprv = xpub_priv_info["xprv"]79assert_equal(xpub_priv_info["xpub"], xpub_info["xpub"])81self.log.info("HD pubkey can be retrieved from encrypted wallets")82prev_xprv = xprv83wallet.encryptwallet("pass")84assert_raises_rpc_error(85-13,86"Error: Please enter the wallet passphrase with walletpassphrase first",87wallet.derivehdkey,88"m/87h",89)90with WalletUnlock(wallet, "pass"):91xpub_info = wallet.derivehdkey("m/87h")92# Only automatically generated descriptors are rotated on93# encryption, unused(KEY) is not.94assert_equal(xpub_info["xpub"], xpub)95assert "xprv" not in xpub_info97self.log.info("HD privkey can be retrieved from encrypted wallets")98assert_raises_rpc_error(99-13,100"Error: Please enter the wallet passphrase with walletpassphrase first",101wallet.derivehdkey,102"m/87h/0h/0h/0",103private=True,104)105with WalletUnlock(wallet, "pass"):106xpub_info = wallet.derivehdkey("m/87h/0h/0h/0", private=True)107# Unused(KEY) is preferred over active descriptors and is not108# rotated on encryption.109assert_equal(xpub_info["xprv"], prev_xprv)111def test_multiple_unused_keys(self):112self.log.info("Test multiple unused(KEY) descriptors")113self.nodes[0].createwallet("multiple_unused_keys", blank=True)114wallet = self.nodes[0].get_wallet_rpc("multiple_unused_keys")115master_xpub_1 = wallet.addhdkey()['xpub']116master_xpub_2 = wallet.addhdkey()['xpub']117assert_raises_rpc_error(118-5,119"Unable to determine which HD key to use. Please specify with 'hdkey'",120wallet.derivehdkey,121"m/87h",122)123xpub_info_1 = wallet.derivehdkey("m/87h", hdkey=master_xpub_1)124xpub_info_2 = wallet.derivehdkey("m/87h", hdkey=master_xpub_2)125assert xpub_info_1["xpub"] != xpub_info_2["xpub"]127assert_raises_rpc_error(128-5,129"Unable to parse HD key. Please provide a valid xpub",130wallet.derivehdkey,131"m/87h",132hdkey="bad",133)135def test_active_descriptor(self):136self.log.info("Test derivation in wallet with regular descriptors")137self.nodes[0].createwallet("active_descriptor")138wallet = self.nodes[0].get_wallet_rpc("active_descriptor")139xpub_info = wallet.derivehdkey("m/44h/1h/0h")140active_xpub = wallet.gethdkeys(active_only=True)[0]["xpub"]141assert_equal(wallet.derivehdkey("m/44h/1h/0h", hdkey=active_xpub), xpub_info)143# Get the activate wpkh() receive descriptor144desc = list(filter(lambda d:145d["active"] and not d["internal"] and d["desc"][0:3] == "pkh",146wallet.listdescriptors()["descriptors"])147)[0]["desc"]148self.log.debug(desc)150assert(xpub_info["origin"] in desc)151assert(xpub_info["xpub"] in desc)153self.log.info("Test unused(KEY) descriptors are preferred over active descriptors")154master_xpub = wallet.addhdkey()["xpub"]155assert_equal(156wallet.derivehdkey("m/87h", hdkey=master_xpub)["xpub"],157wallet.derivehdkey("m/87h")["xpub"],158)160def test_inactive_descriptor(self):161self.log.info("Test that the HD key of a used inactive descriptor is rejected")162# Mint a spendable descriptor in a throwaway wallet, so we can import it163# into the wallet under test and deactivate it there.164self.nodes[0].createwallet(wallet_name="hdkey_source")165source = self.nodes[0].get_wallet_rpc("hdkey_source")166desc = next(d["desc"] for d in source.listdescriptors(private=True)["descriptors"]167if d["active"] and not d["internal"] and d["desc"].startswith("pkh("))169self.nodes[0].createwallet(wallet_name="inactive_descriptor", blank=True)170wallet = self.nodes[0].get_wallet_rpc("inactive_descriptor")171request = {"desc": desc, "range": [0, 9], "timestamp": "now", "internal": False}172assert_equal(wallet.importdescriptors([{**request, "active": True}])[0]["success"], True)174# While the descriptor is active its HD key can be used for derivation.175xpub = wallet.gethdkeys(active_only=True)[0]["xpub"]176wallet.derivehdkey("m/87h", hdkey=xpub)178# Once deactivated the descriptor is neither active nor unused(KEY), so179# its HD key is no longer a candidate.180assert_equal(wallet.importdescriptors([{**request, "active": False}])[0]["success"], True)181assert_raises_rpc_error(182-5,183"HD key is not used by an active or unused(KEY) descriptor",184wallet.derivehdkey,185"m/87h",186hdkey=xpub,187)189def test_noprivs_blank(self):190self.log.info("Test derivehdkey on wallet without private keys")191self.nodes[0].createwallet(wallet_name="noprivs", disable_private_keys=True)192wallet = self.nodes[0].get_wallet_rpc("noprivs")193assert_raises_rpc_error(194-4,195"derivehdkey is not available for watch-only wallets",196wallet.derivehdkey,197"m/87h",198)200self.log.info("Test derivehdkey on blank wallet")201self.nodes[0].createwallet(wallet_name="blank", blank=True)202wallet = self.nodes[0].get_wallet_rpc("blank")203assert_raises_rpc_error(204-5,205"No active or unused(KEY) descriptor found",206wallet.derivehdkey,207"m/87h",208)210def test_compare(self):211self.log.info(212"Compare with result of createwalletdescriptor")213self.nodes[0].createwallet(wallet_name="w1", blank=True)214wallet = self.nodes[0].get_wallet_rpc("w1")215master_xpub = wallet.addhdkey()["xpub"]217# Derive xpub for legacy descriptor218xpub_info = wallet.derivehdkey("m/44h/1h/0h")220# Generate legacy descriptor221wallet.createwalletdescriptor(type="legacy", hdkey=master_xpub)223# Get the activate wpkh() receive descriptor224desc = list(filter(lambda d:225d["active"] and not d["internal"] and d["desc"][0:3] == "pkh",226wallet.listdescriptors()["descriptors"])227)[0]["desc"]228self.log.debug(desc)230assert(xpub_info["origin"] in desc)231assert(xpub_info["xpub"] in desc)234if __name__ == '__main__':235WalletDeriveHDKeyTest(__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.