From c85dd6831254097fb0d975d9a412e9ea10aaeeb6 Mon Sep 17 00:00:00 2001 From: The MMGen Project Date: Sat, 22 Aug 2026 18:34:47 +0000 Subject: [PATCH] BipHDNode.from_path(): support initialization from xprv instead of seed --- mmgen/bip_hd/__init__.py | 24 +++++++++++++++++------- mmgen/bip_hd/chainparams.py | 6 +++++- test/modtest_d/bip_hd.py | 12 ++++++++++++ 3 files changed, 34 insertions(+), 8 deletions(-) diff --git a/mmgen/bip_hd/__init__.py b/mmgen/bip_hd/__init__.py index 63dc7218..21ce9c7a 100755 --- a/mmgen/bip_hd/__init__.py +++ b/mmgen/bip_hd/__init__.py @@ -368,6 +368,7 @@ class BipHDNode(Lockable): seed, path_str, *, + xprv = None, coin = None, addr_type = None, no_path_checks = False): @@ -378,11 +379,20 @@ class BipHDNode(Lockable): proto = init_proto(base_cfg, coin or 'btc') - res = MasterNode(base_cfg, seed).init_cfg( - coin = proto.coin, - addr_type = addr_type or proto.dfl_mmtype, - no_path_checks = no_path_checks, - from_path = True) + if xprv: + assert seed == None, 'BipHDNode.from_path(): `seed` must be None if `xprv` is specified' + res = BipHDNode.from_extended_key( + base_cfg = base_cfg, + coin = coin or proto.coin, + xkey_b58 = xprv, + no_path_checks = no_path_checks, + addr_type = addr_type or proto.dfl_mmtype) + else: + res = MasterNode(base_cfg, seed).init_cfg( + coin = proto.coin, + addr_type = addr_type or proto.dfl_mmtype, + no_path_checks = no_path_checks, + from_path = True) for s in path: for suf in ("'", 'h'): @@ -404,7 +414,7 @@ class BipHDNode(Lockable): @staticmethod # ‘addr_type’ is required for broken coins with duplicate version bytes across BIP protocols # (i.e. Dogecoin) - def from_extended_key(base_cfg, coin, xkey_b58, *, addr_type=None): + def from_extended_key(base_cfg, coin, xkey_b58, *, addr_type=None, no_path_checks=False): xk = Bip32ExtendedKey(xkey_b58) if xk.public: @@ -425,7 +435,7 @@ class BipHDNode(Lockable): network = xk.network, addr_type = addr_type or addr_types[xk.bip_proto], from_path = False, - no_path_checks = False) + no_path_checks = no_path_checks) new.par_print = xk.par_print new.depth = xk.depth diff --git a/mmgen/bip_hd/chainparams.py b/mmgen/bip_hd/chainparams.py index 40fd04b8..99a58f09 100755 --- a/mmgen/bip_hd/chainparams.py +++ b/mmgen/bip_hd/chainparams.py @@ -5,6 +5,10 @@ # https://github.com/MetaMask/slip44/blob/main/slip44.json (1bc984bee) # https://github.com/ebellocchia/bip_utils (5649541c6) +""" +bip_hd.chainparams: BIP-44/49/84, SLIP-44 hierarchical-deterministic key derivation library +""" + from collections import namedtuple def parse_data(): @@ -132,7 +136,7 @@ IDX CHAIN CURVE NW ADDR_CLS VB_PRV VB_PUB VB_WIF VB_ADDR DFL_PA [bip-84] IDX CHAIN CURVE NW ADDR_CLS VB_PRV VB_PUB VB_WIF VB_ADDR DFL_PATH NAME 0 BTC x m P2WPKH 04b2430c 04b24746 80 h:bc x Bitcoin -1 LTC x T P2WPKH 0436ef7d 0436f6e1 ef h:tltc x Litecoin TestNet +1 - x T P2WPKH 0436ef7d 0436f6e1 ef h:tb x TestNet (all coins) 2 LTC x m P2WPKH 04b2430c 04b24746 b0 h:ltc x Litecoin [bip-86] diff --git a/test/modtest_d/bip_hd.py b/test/modtest_d/bip_hd.py index 48e2e5ee..a271471f 100755 --- a/test/modtest_d/bip_hd.py +++ b/test/modtest_d/bip_hd.py @@ -192,6 +192,16 @@ class unit_tests: vmsg('seed: 98831F3A (default derivation)') m = MasterNode(cfg, self._seed) + master = m.init_cfg('btc', network=None, addr_type='bech32') + vmsg(f' {master.xprv=}') + + chain_from_master_xprv = BipHDNode.from_path( + cfg, + None, + "m/84'/0'/0'/0", + no_path_checks = True, + addr_type = 'bech32', + xprv = master.xprv) purpose = m.init_cfg(coin='btc', addr_type='bech32').derive_private() vmsg(f' {purpose.address=}') @@ -210,7 +220,9 @@ class unit_tests: chain3 = m.to_coin_type(coin='btc', addr_type='bech32').to_chain(0, public=True) assert chain3.address == chain1.address + assert chain1.address == chain_from_master_xprv.address vmsg(f' {chain1.address=}') + vmsg(f' {chain_from_master_xprv.address=}') a = BipHDNode.from_extended_key(cfg, 'btc', chain2.xpub) b = BipHDNode.from_extended_key(cfg, 'btc', chain2.xprv)