Browse Source

bip_hd: a minimal, easy-to-use BIP-32/BIP-44 implementation

- this is a work in progress, only a few coins are currently supported

Testing:

    $ test/unit_tests.py -v bip_hd

Examples:

    $ PYTHONPATH=. examples/bip_hd.py
The MMGen Project 3 months ago
parent
commit
ea1e8d1228

+ 88 - 0
examples/bip_hd.py

@@ -0,0 +1,88 @@
+#!/usr/bin/env python3
+#
+# mmgen = Multi-Mode GENerator, a command-line cryptocurrency wallet
+# Copyright (C)2013-2024 The MMGen Project <mmgen@tuta.io>
+# Licensed under the GNU General Public License, Version 3:
+#   https://www.gnu.org/licenses
+# Public project repositories:
+#   https://github.com/mmgen/mmgen-wallet
+#   https://gitlab.com/mmgen/mmgen-wallet
+
+"""
+examples/bip_hd.py: Usage examples for the MMGen BIP-32/-44 hierarchical/deterministic library
+"""
+
+from mmgen.cfg import Config
+from mmgen.util import fmt
+from mmgen.bip39 import bip39
+from mmgen.bip_hd import MasterNode,BipHDNode
+
+cfg = Config()
+
+bip39_mnemonic = 'cat swing flag economy stadium alone churn speed unique patch report train'
+
+seed = bip39().generate_seed(bip39_mnemonic.split())
+
+m = MasterNode(cfg, seed)
+
+# Derive sample path:
+
+# to_chain() derives default chain for coin/addr_type pair:
+dfl_pub_chain = m.to_chain(idx=0, coin='ltc', addr_type='bech32')
+dfl_chg_chain = m.to_chain(idx=1, coin='ltc', addr_type='bech32')
+
+print(f'Default path (LTC, bech32):\n')
+print(f'  public chain xpub:\n    {dfl_pub_chain.xpub}\n')
+print(f'  internal chain xpub:\n    {dfl_chg_chain.xpub}\n')
+print(f'  public chain addr 0:\n    {dfl_pub_chain.derive_public(0).address}\n')
+print(f'  public chain addr 1:\n    {dfl_pub_chain.derive_public(1).address}\n')
+
+# Derive sample path using path string:
+
+dfl_pub_chain_from_path = BipHDNode.from_path(
+	base_cfg  = cfg,
+	seed      = seed,
+	# purpose=84 (bech32 [BIP-84]), coin_type=2 (LTC mainnet [SLIP-44]), account=0, chain=0 (public)
+	# as per BIP-44, ‘purpose’, ‘coin_type’ and ‘account’ are hardened, while ‘chain’ is not
+	path_str  = "m/84'/2'/0'/0",
+	coin      = 'ltc',
+	addr_type = 'bech32')
+
+assert dfl_pub_chain_from_path.xpub == dfl_pub_chain.xpub
+
+# Derive sample path step-by-step:
+
+# Configure master node with coin/addr_type pair:
+master = m.init_cfg(coin='ltc', addr_type='bech32')
+
+# ‘idx’ and ‘hardened’ args may be omitted at depths where defaults exist:
+purpose = master.derive_private()     # ‘idx’ is auto-computed from addr_type (BIP-44/49/84)
+coin_type = purpose.derive_private()  # ‘idx’ is auto-computed from coin/network (SLIP-44)
+account = coin_type.derive_private(idx=0)
+pub_chain = account.derive_public(idx=0)
+
+assert pub_chain.xpub == dfl_pub_chain.xpub
+
+# Initialize node from xpub:
+pub_chain_from_xpub = BipHDNode.from_extended_key(cfg, 'ltc', pub_chain.xpub)
+
+assert pub_chain_from_xpub.xpub == pub_chain.xpub
+
+# To derive arbitrary BIP-32 paths, ignoring BIP-44, specify ‘no_path_checks’
+nonstd_path = BipHDNode.from_path(
+	base_cfg  = cfg,
+	seed      = seed,
+	path_str  = "m/111'/222/333/444",
+	coin      = 'eth',
+	addr_type = 'E',
+	no_path_checks = True)
+
+print(f'Non-standard path (ETH):\n')
+print(f'  xpub:\n    {nonstd_path.xpub}\n')
+print(f'  WIF key:\n    {nonstd_path.privkey.wif}\n')
+print(f'  address:\n    {nonstd_path.address}\n')
+
+# Display parsed xpub:
+parsed_xpub = nonstd_path.key_extended(public=True)
+print('Default path parsed xpub:\n')
+print(fmt(str(parsed_xpub), indent='  '))

+ 504 - 0
mmgen/bip_hd/__init__.py

@@ -0,0 +1,504 @@
+#!/usr/bin/env python3
+#
+# mmgen = Multi-Mode GENerator, a command-line cryptocurrency wallet
+# Copyright (C)2013-2024 The MMGen Project <mmgen@tuta.io>
+# Licensed under the GNU General Public License, Version 3:
+#   https://www.gnu.org/licenses
+# Public project repositories:
+#   https://github.com/mmgen/mmgen-wallet
+#   https://gitlab.com/mmgen/mmgen-wallet
+
+"""
+bip_hd: BIP-44/49/84, SLIP-44 hierarchical-deterministic key derivation library
+"""
+
+# One motivation for this implementation:
+#   https://blog.unit410.com/bitcoin/bip32/bip39/kdf/2021/05/17/inconsistent-bip32-derivations.html
+
+import hmac
+from collections import namedtuple
+
+from ..cfg import Config
+from ..util import is_int, fmt
+from ..base_obj import Lockable
+from ..keygen import KeyGenerator, keygen_public_data
+from ..addrgen import AddrGenerator
+from ..addr import MMGenAddrType
+from ..key import PrivKey
+from ..protocol import CoinProtocol
+from ..proto.btc.common import hash160, b58chk_encode, b58chk_decode
+from ..proto.secp256k1.secp256k1 import pubkey_tweak_add, pubkey_check
+
+from . import chainparams
+chainparams_data = chainparams.parse_data()
+
+secp256k1_order = CoinProtocol.Secp256k1.secp256k1_group_order
+hardened_idx0 = 0x80000000
+
+def get_chain_params(bipnum, chain):
+	return chainparams_data[f'bip-{bipnum}'][chain.upper()]
+
+def get_version_bytes(bip_proto, coin, public):
+	return getattr(
+		chainparams_data[f'bip-{bip_proto}'][coin],
+		'vb_pub' if public else 'vb_prv')
+
+def parse_version_bytes(vb_hex):
+	e = chainparams_data['defaults']
+	if vb_hex in (e.vb_pub, e.vb_prv):
+		return (None, e)
+	for bipnum in (49, 84, 86, 44): # search bip-44 last, since it has the most entries
+		for e in chainparams_data[f'bip-{bipnum}'].values():
+			if vb_hex in (e.vb_pub, e.vb_prv):
+				return (bipnum, e)
+	else:
+		raise ValueError(f'0x{vb_hex}: unrecognized extended key version bytes')
+
+def compress_pubkey(pubkey_bytes):
+	# see: proto.secp256k1.keygen.pubkey_format()
+	return (b'\x02',b'\x03')[pubkey_bytes[-1] & 1] + pubkey_bytes[1:33]
+
+def decompress_pubkey(pubkey_bytes):
+	import ecdsa
+	return b'\x04' + ecdsa.VerifyingKey.from_string(pubkey_bytes, curve=ecdsa.curves.SECP256k1).to_string()
+
+class Bip32ExtendedKey(Lockable):
+
+	def __init__(self, key_b58):
+
+		try:
+			key = b58chk_decode(key_b58)
+		except Exception as e:
+			raise type(e)(f'invalid extended key: {e}')
+
+		assert len(key) == 78, f'len(key) == {len(key)} (not 78)'
+
+		# Serialization:
+		#   ver_bytes | depth | par_print | idx      | chaincode  | serialized_key
+		#   0:4 (4)   | 4 (1) | 5:9 (4)   | 9:13 (4) | 13:45 (32) | 45(46): 33(32)
+		ver_hex = key[:4].hex()
+		bipnum, cp_entry = parse_version_bytes(ver_hex)
+
+		public = ver_hex == cp_entry.vb_pub
+		idx_raw = int.from_bytes(key[9:13])
+
+		self.base58    = key_b58
+		self.ver_bytes = key[:4]
+		self.depth     = key[4]
+		self.par_print = key[5:9]
+		self.idx       = idx_raw if idx_raw < hardened_idx0 else idx_raw - hardened_idx0
+		self.chaincode = key[13:45]
+		self.key       = key[45 if public else 46:]
+		self.hardened  = idx_raw >= hardened_idx0 or self.depth == 0
+		self.bip_proto = bipnum or 44
+		self.network   = cp_entry.network if bipnum else 'mainnet'
+		self.public    = public
+		self.coin      = cp_entry.chain if bipnum and cp_entry.chain != 'BTC' else '-'
+
+		if self.public:
+			if not key[45] in (2, 3):
+				raise ValueError(f'0x{key[45]:02x}: invalid first byte for public key data (not 2 or 3)')
+		elif key[45]:
+			raise ValueError(f'0x{key[45]:02x}: invalid first byte for private key data (not zero)')
+
+		if self.depth == 0:
+			if self.par_print != bytes(4):
+				raise ValueError(f'{self.par_print.hex()}: non-zero parent fingerprint at depth 0')
+			if idx_raw:
+				raise ValueError(f'{idx_raw}: non-zero index at depth 0')
+
+	def __str__(self):
+		return fmt(f"""
+			base58:    {self.base58}
+			ver_bytes: {self.ver_bytes.hex()}
+			depth:     {self.depth} [{bip_hd_nodes[self.depth].desc}]
+			par_print: {self.par_print.hex()}
+			idx:       {self.idx}
+			chaincode: {self.chaincode.hex()}
+			key:       {self.key.hex()}
+			hardened:  {self.hardened}
+			bip_proto: {self.bip_proto}
+			network:   {self.network}
+			public:    {self.public}
+			coin:      {self.coin}
+		""")
+
+def get_bip_by_addr_type(addr_type):
+	return (
+		84 if addr_type.name == 'bech32' else
+		49 if addr_type.name == 'segwit' else
+		44)
+
+def check_privkey(key_int):
+	if key_int == 0:
+		raise ValueError('private key is zero!')
+	elif key_int >= secp256k1_order:
+		raise ValueError(f'{key_int:x}: private key >= group order!')
+
+class BipHDConfig(Lockable):
+
+	supported_coins = ('btc', 'eth', 'doge', 'ltc')
+
+	def __init__(self, base_cfg, coin, network, addr_type, from_path, no_path_checks):
+
+		if not coin.lower() in self.supported_coins:
+			raise ValueError(f'bip_hd: coin {coin.upper()} not supported')
+
+		base_cfg = Config({
+			'_clone':  base_cfg,
+			'coin':    coin,
+			'network': network,
+			'type':    addr_type or None,
+			'quiet':   True
+		})
+
+		dfl_type = base_cfg._proto.dfl_mmtype
+		addr_type = MMGenAddrType(
+			proto  = base_cfg._proto,
+			id_str = base_cfg.type or ('C' if dfl_type == 'L' else dfl_type))
+
+		self.base_cfg = base_cfg
+		self.addr_type = addr_type
+		self.kg = KeyGenerator(base_cfg, base_cfg._proto, addr_type.pubkey_type)
+		self.ag = AddrGenerator(base_cfg, base_cfg._proto, addr_type)
+		self.bip_proto = get_bip_by_addr_type(addr_type)
+		self.from_path = from_path
+		self.no_path_checks = no_path_checks
+
+class MasterNode(Lockable):
+	desc = 'Unconfigured Bip32 Master Node'
+	_use_class_attr = True
+
+	def __init__(self, base_cfg, bytes_data):
+
+		H = hmac.digest(b'Bitcoin seed', bytes_data, 'sha512')
+
+		self.par_print = bytes(4)
+		self.depth     = 0
+		self.key       = H[:32]
+		self.chaincode = H[32:]
+		self.idx       = 0
+		self.hardened  = True
+		self.public    = False
+		self.base_cfg  = base_cfg
+
+		check_privkey(int.from_bytes(self.key))
+
+	def init_cfg(self, coin=None, network=None, addr_type=None, from_path=False, no_path_checks=False):
+
+		new = BipHDNodeMaster()
+
+		new.cfg       = BipHDConfig(self.base_cfg, coin, network, addr_type, from_path, no_path_checks)
+		new.par_print = self.par_print
+		new.depth     = self.depth
+		new.key       = self.key
+		new.chaincode = self.chaincode
+		new.idx       = self.idx
+		new.hardened  = self.hardened
+		new.public    = self.public
+
+		new._lock()
+		return new
+
+	def to_coin_type(self, coin=None, network=None, addr_type=None):
+		return self.init_cfg(coin, network, addr_type).to_coin_type()
+
+	def to_chain(self, idx, coin=None, network=None, addr_type=None, hardened=False, public=False):
+		return self.init_cfg(coin, network, addr_type).to_chain(
+			idx      = idx,
+			hardened = hardened,
+			public   = public)
+
+class BipHDNode(Lockable):
+	_autolock = False
+	_generated_pubkey = None
+	_set_ok = ('_generated_pubkey',)
+
+	def check_param(self, name, val):
+		cls = type(self)
+		if val is None:
+			if not hasattr(cls, name):
+				raise ValueError(f'‘{name}’ at depth {self.depth} ({self.desc!r}) must be set')
+		elif hasattr(cls, name) and val != getattr(cls, name):
+			raise ValueError(
+				'{}: invalid value for ‘{}’ at depth {} ({!r}) (must be {})'.format(
+					val, name, self.depth, self.desc,
+					'None' if getattr(cls, name) is None else f'None or {getattr(cls, name)}')
+			)
+
+	def set_params(self, cfg, idx, hardened):
+		self.check_param('idx', idx)
+		self.check_param('hardened', hardened)
+		return (
+			type(self).idx if idx is None else idx,
+			type(self).hardened if hardened is None else hardened)
+
+	@property
+	def privkey(self):
+		assert not self.public
+		return PrivKey(
+			self.cfg.base_cfg._proto,
+			self.key,
+			compressed  = self.cfg.addr_type.compressed,
+			pubkey_type = self.cfg.addr_type.pubkey_type)
+
+	@property
+	def pubkey_bytes(self):
+		if self.public:
+			return self.key
+		elif self.cfg.addr_type.compressed:
+			return self.priv2pub().pubkey
+		else:
+			return compress_pubkey(self.priv2pub().pubkey)
+
+	def priv2pub(self):
+		if not self._generated_pubkey:
+			self._generated_pubkey = self.cfg.kg.gen_data(self.privkey)
+		return self._generated_pubkey
+
+	@property
+	def address(self):
+		return self.cfg.ag.to_addr(
+			keygen_public_data(
+					pubkey        = self.key if self.cfg.addr_type.compressed else decompress_pubkey(self.key),
+					viewkey_bytes = None,
+					pubkey_type   = self.cfg.addr_type.pubkey_type,
+					compressed    = self.cfg.addr_type.compressed)
+				if self.public else
+			self.priv2pub()
+		)
+
+	# Extended keys can be identified by the Hash160 (RIPEMD160 after SHA256) of the serialized ECDSA
+	# public key K, ignoring the chain code. This corresponds exactly to the data used in traditional
+	# Bitcoin addresses. It is not advised to represent this data in base58 format though, as it may be
+	# interpreted as an address that way (and wallet software is not required to accept payment to the
+	# chain key itself).
+	@property
+	def id(self):
+		return hash160(self.pubkey_bytes)
+
+	# The first 32 bits of the identifier are called the key fingerprint.
+	@property
+	def fingerprint(self):
+		return self.id[:4]
+
+	@property
+	def xpub(self):
+		return self.key_extended(public=True, as_str=True)
+
+	@property
+	def xprv(self):
+		return self.key_extended(public=False, as_str=True)
+
+	def key_extended(self, public, as_str=False):
+		if self.public and not public:
+			raise ValueError('cannot create extended private key for public node!')
+		ret = b58chk_encode(
+			bytes.fromhex(get_version_bytes(self.cfg.bip_proto, self.cfg.base_cfg.coin, public))
+			+ int.to_bytes(self.depth, length=1)
+			+ self.par_print
+			+ int.to_bytes(self.idx + (hardened_idx0 if self.hardened and self.depth else 0), length=4)
+			+ self.chaincode
+			+ (self.pubkey_bytes if public else b'\x00' + self.key)
+		)
+		return ret if as_str else Bip32ExtendedKey(ret)
+
+	def derive_public(self, idx=None):
+		return self.derive(idx=idx, hardened=False, public=True)
+
+	def derive_private(self, idx=None, hardened=None):
+		return self.derive(idx=idx, hardened=hardened, public=False)
+
+	def derive(self, idx, hardened, public):
+
+		if self.public and not public:
+			raise ValueError('cannot derive private node from public node!')
+
+		new = bip_hd_nodes[self.depth + 1]()
+
+		new.depth     = self.depth + 1
+		new.cfg       = self.cfg
+		new.par_print = self.fingerprint
+		new.public    = public
+
+		if new.cfg.no_path_checks:
+			new.idx, new.hardened = (idx, hardened)
+		else:
+			if new.public and type(new).hardened:
+				raise ValueError(
+					f'‘public’ requested, but node of depth {new.depth} ({new.desc}) must be hardened!')
+			new.idx, new.hardened = new.set_params(new.cfg, idx, hardened)
+
+		key_in = b'\x00' + self.key if new.hardened else self.pubkey_bytes
+
+		I = hmac.digest(
+			self.chaincode,
+			key_in + ((hardened_idx0 if new.hardened else 0) + new.idx).to_bytes(length=4),
+			'sha512')
+
+		pk_addend_bytes = I[:32]
+		new.chaincode   = I[32:]
+
+		if new.public:
+			new.key = pubkey_tweak_add(key_in, pk_addend_bytes) # checks range of pk_addend
+		else:
+			pk_addend = int.from_bytes(pk_addend_bytes)
+			check_privkey(pk_addend)
+			key_int = (int.from_bytes(self.key) + pk_addend) % secp256k1_order
+			check_privkey(key_int)
+			new.key = int.to_bytes(key_int, length=32)
+
+		new._lock()
+		return new
+
+	@staticmethod
+	def from_path(
+			base_cfg,
+			seed,
+			path_str,
+			coin           = None,
+			addr_type      = None,
+			no_path_checks = False):
+
+		path = path_str.lower().split('/')
+		if path.pop(0) != 'm':
+			raise ValueError(f'{path_str}: invalid path string (first component is not "m")')
+
+		res = MasterNode(base_cfg, seed).init_cfg(
+			coin           = coin or 'btc',
+			addr_type      = addr_type or 'compressed',
+			no_path_checks = no_path_checks,
+			from_path      = True)
+
+		for s in path:
+			for suf in ("'", 'h'):
+				if s.endswith(suf):
+					idx = s.removesuffix(suf)
+					hardened = True
+					break
+			else:
+				idx = s
+				hardened = False
+
+			if not is_int(idx):
+				raise ValueError(f'invalid path component {s!r}')
+
+			res = res.derive(int(idx), hardened, public=False)
+
+		return res
+
+	@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):
+		xk = Bip32ExtendedKey(xkey_b58)
+
+		if xk.public:
+			pubkey_check(xk.key)
+		else:
+			check_privkey(int.from_bytes(xk.key))
+
+		addr_types = {
+			84: 'bech32',
+			49: 'segwit',
+			44: None
+		}
+
+		new = bip_hd_nodes[xk.depth]()
+
+		new.cfg = BipHDConfig(
+			base_cfg,
+			coin,
+			xk.network,
+			addr_type or addr_types[xk.bip_proto],
+			False,
+			False)
+
+		new.par_print  = xk.par_print
+		new.depth      = xk.depth
+		new.key        = xk.key
+		new.chaincode  = xk.chaincode
+		new.idx        = xk.idx
+		new.hardened   = xk.hardened
+		new.public     = xk.public
+
+		new._lock()
+		return new
+
+class BipHDNodeMaster(BipHDNode):
+	desc = 'Bip32 Master Node'
+	hardened = True
+	idx = None
+
+	def to_coin_type(self):
+		#           purpose          coin_type
+		return self.derive_private().derive_private()
+
+	def to_chain(self, idx, hardened=False, public=False):
+		#           purpose          coin_type        account #0            chain
+		return self.derive_private().derive_private().derive_private(idx=0).derive(
+			idx      = idx,
+			hardened = False if public else hardened,
+			public   = public)
+
+class BipHDNodePurpose(BipHDNode):
+	desc = 'Purpose'
+	hardened = True
+
+	def set_params(self, cfg, idx, hardened):
+		self.check_param('hardened', hardened)
+		if idx not in (None, cfg.bip_proto):
+			raise ValueError(
+				f'index for path component {self.desc!r} with address type {cfg.addr_type!r} '
+				f'must be {cfg.bip_proto}, not {idx}')
+		return (cfg.bip_proto, type(self).hardened)
+
+class BipHDNodeCoinType(BipHDNode):
+	desc = 'Coin Type'
+	hardened = True
+
+	def set_params(self, cfg, idx, hardened):
+		self.check_param('hardened', hardened)
+		chain_idx = get_chain_params(
+			bipnum = get_bip_by_addr_type(cfg.addr_type),
+			chain  = cfg.base_cfg.coin).idx
+		if idx not in (None, chain_idx):
+			raise ValueError(
+				f'index {idx} at depth {self.depth} ({self.desc}) does not match '
+				f'chain index {chain_idx} for coin {cfg.base_cfg.coin!r}')
+		return (chain_idx, type(self).hardened)
+
+	def to_chain(self, idx, hardened=False, public=False):
+		#           account #0            chain
+		return self.derive_private(idx=0).derive(
+			idx      = idx,
+			hardened = False if public else hardened,
+			public   = public)
+
+class BipHDNodeAccount(BipHDNode):
+	desc = 'Account'
+	hardened = True
+
+class BipHDNodeChain(BipHDNode):
+	desc = 'Chain'
+	hardened = False
+
+	def set_params(self, cfg, idx, hardened):
+		self.check_param('hardened', hardened)
+		if idx not in (0, 1):
+			raise ValueError(
+				f'at depth {self.depth} ({self.desc}), ‘idx’ must be either 0 (external) or 1 (internal)')
+		return (idx, type(self).hardened)
+
+class BipHDNodeAddrIdx(BipHDNode):
+	desc = 'Address Index'
+	hardened = False
+
+bip_hd_nodes = {
+	0: BipHDNodeMaster,
+	1: BipHDNodePurpose,
+	2: BipHDNodeCoinType,
+	3: BipHDNodeAccount,
+	4: BipHDNodeChain,
+	5: BipHDNodeAddrIdx
+}

+ 1219 - 0
mmgen/bip_hd/chainparams.py

@@ -0,0 +1,1219 @@
+#!/usr/bin/env python3
+#
+# Created using scripts/create-bip-hd-chain-params.py
+# Source data:
+#   https://github.com/MetaMask/slip44/blob/main/slip44.json (1bc984bee)
+#   https://github.com/ebellocchia/bip_utils (5649541c6)
+
+from collections import namedtuple
+
+def parse_data():
+
+	_d = namedtuple(
+		'bip_hd_data',
+		'idx chain curve network addr_cls vb_prv vb_pub vb_wif vb_addr def_path name')
+	_u = namedtuple(
+		'bip_hd_data_partial',
+		'idx chain name')
+
+	def parse_line(line):
+		l = line.split()
+
+		if l[2] == '-':
+			return _u(
+				idx   = int(l[0]),
+				chain = l[1],
+				name  = ' '.join(l[3:]),
+			)
+		else:
+			return _d(
+				idx      = int(l[0]),
+				chain    = l[1],
+				curve    = defaults.curve if l[2] == 'x' else l[2],
+				network  = 'mainnet' if l[3] == 'm' else 'testnet' if l[3] == 'T' else None,
+				addr_cls = l[4],
+				vb_prv   = defaults.vb_prv if l[5] == 'x' else l[5],
+				vb_pub   = defaults.vb_pub if l[6] == 'x' else l[6],
+				vb_wif   = l[7],
+				vb_addr  = l[8],
+				def_path = defaults.def_path if l[9] == 'x' else l[9],
+				name     = ' '.join(l[10:]),
+			)
+
+	out = {}
+	for line in _data_in.strip().splitlines():
+		if not line or line.startswith('IDX'):
+			continue
+		if line.startswith('['):
+			key = line[1:-1]
+			continue
+		p = parse_line(line)
+		if key in out:
+			out[key][p[1]] = p
+		elif key == 'defaults':
+			out['defaults'] = p
+			defaults = p
+		else:
+			out[key] = {p[1]: p}
+
+	return out
+
+_data_in = """
+
+[defaults]
+IDX    CHAIN  CURVE NW ADDR_CLS         VB_PRV   VB_PUB   VB_WIF VB_ADDR  DFL_PATH NAME
+0      -      secp  -  -                0488ade4 0488b21e -      -        0'/0/0   -
+
+[bip-44]
+IDX    CHAIN  CURVE NW ADDR_CLS         VB_PRV   VB_PUB   VB_WIF VB_ADDR  DFL_PATH NAME
+0      BTC    x     m  P2PKH            x        x        80     00       x        Bitcoin
+1      ---    x     T  P2PKH            04358394 043587cf ef     1d25     x        Testnet (all coins)
+2      LTC    x     m  P2PKH            x        x        b0     spec     x        Litecoin
+3      DOGE   x     m  P2PKH            02fac398 02facafd 9e     1e       x        Dogecoin
+5      DASH   x     m  P2PKH            x        x        cc     4c       x        Dash
+60     ETH    x     m  Eth              x        x        -      -        x        Ethereum
+61     ETC    x     m  Eth              x        x        -      -        x        Ether Classic
+74     ICX    x     m  Icx              x        x        -      -        x        ICON
+77     XVG    x     m  P2PKH            x        x        9e     1e       x        Verge Currency
+118    ATOM   x     m  Atom             x        x        -      h:stafi  x        Atom
+128    XMR    x     m  Xmr              x        x        -      -        x        Monero
+133    ZEC    x     m  P2PKH            x        x        80     1cb8     x        Zcash
+144    XRP    x     m  Xrp              x        x        -      -        x        Ripple
+145    BCH    x     m  BchP2PKH         x        x        80     spec     x        Bitcoin Cash
+148    XLM    edw   m  Xlm              x        x        -      spec     0'       Stellar Lumens
+165    XNO    blk   m  Nano             x        x        -      -        0'       Nano
+194    EOS    x     m  Eos              x        x        -      -        x        EOS
+195    TRX    x     m  Trx              x        x        -      -        x        Tron
+236    BSV    x     m  P2PKH            x        x        80     00       x        BitcoinSV
+283    ALGO   edw   m  Algo             x        x        -      -        0'/0'/0' Algorand
+313    ZIL    x     m  Zil              x        x        -      -        x        Zilliqa
+330    LUNA   x     m  Atom             x        x        -      h:terra  x        Terra
+354    DOT    edw   m  SubstrateEd25519 x        x        -      spec     0'/0'/0' Polkadot
+397    NEAR   edw   m  Near             x        x        -      -        0'       NEAR Protocol
+429    ERG    x     T  ErgoP2PKH        04358394 043587cf -      spec     x        Ergo
+434    KSM    edw   m  SubstrateEd25519 x        x        -      spec     0'/0'/0' Kusama
+459    KAVA   x     m  Atom             x        x        -      h:kava   x        Kava
+461    FIL    x     m  FilSecp256k1     x        x        -      -        x        Filecoin
+494    BAND   x     m  Atom             x        x        -      h:band   x        Band
+500    THETA  x     m  Eth              x        x        -      -        x        Theta
+501    SOL    edw   m  Sol              x        x        -      -        0'       Solana
+508    EGLD   edw   m  Egld             x        x        -      -        0'/0'/0' MultiversX
+529    SCRT   x     m  Atom             x        x        -      h:secret x        Secret Network
+567    NCG    x     m  Eth              x        x        -      -        x        Nine Chronicles
+637    APTOS  edw   m  Aptos            x        x        -      -        0'/0'/0' Aptos
+714    BNB    x     m  Atom             x        x        -      h:bnb    x        Binance
+784    SUI    edw   m  Sui              x        x        -      -        0'/0'/0' Sui
+818    VET    x     m  Eth              x        x        -      -        x        VeChain Token
+888    NEO    nist  m  Neo              x        x        -      spec     x        NEO
+996    OKT    x     m  Okex             x        x        -      -        x        OKChain Token
+1023   ONE    x     m  One              x        x        -      -        x        HARMONY-ONE (Legacy)
+1024   ONT    nist  m  Neo              x        x        -      spec     x        Ontology
+1729   XTZ    edw   m  Xtz              x        x        -      spec     0'/0'    Tezos
+1815   ADA    khol  m  AdaByronIcarus   0f4331d4 x        -      spec     x        Cardano
+9000   AVAX   x     m  AvaxXChain       x        x        -      -        x        Avalanche
+52752  CELO   x     m  Eth              x        x        -      -        x        Celo
+314159 PI     edw   m  Xlm              x        x        -      spec     0'       Pi Network
+
+[bip-49]
+IDX    CHAIN  CURVE NW ADDR_CLS         VB_PRV   VB_PUB   VB_WIF VB_ADDR  DFL_PATH NAME
+0      BTC    x     m  P2SH             049d7878 049d7cb2 80     05       x        Bitcoin
+1      ZEC    x     T  P2SH             044a4e28 044a5262 ef     1cba     x        Zcash TestNet
+2      LTC    x     m  P2SH             049d7878 049d7cb2 b0     spec     x        Litecoin
+3      DOGE   x     m  P2SH             02fac398 02facafd 9e     16       x        Dogecoin
+5      DASH   x     m  P2SH             049d7878 049d7cb2 cc     10       x        Dash
+133    ZEC    x     m  P2SH             049d7878 049d7cb2 80     1cbd     x        Zcash
+145    XEC    x     m  BchP2SH          049d7878 049d7cb2 80     spec     x        eCash
+236    BSV    x     m  P2SH             049d7878 049d7cb2 80     05       x        BitcoinSV
+
+[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
+2      LTC    x     m  P2WPKH           04b2430c 04b24746 b0     h:ltc    x        Litecoin
+
+[bip-86]
+IDX    CHAIN  CURVE NW ADDR_CLS         VB_PRV   VB_PUB   VB_WIF VB_ADDR  DFL_PATH NAME
+0      BTC    x     m  P2TR             x        x        80     h:bc     x        Bitcoin
+1      BTC    x     T  P2TR             04358394 043587cf ef     h:tb     x        Bitcoin TestNet
+
+[bip-44-unsupported]
+IDX    CHAIN    NAME
+4      RDD    - Reddcoin
+6      PPC    - Peercoin
+7      NMC    - Namecoin
+8      FTC    - Feathercoin
+9      XCP    - Counterparty
+10     BLK    - Blackcoin
+11     NSR    - NuShares
+12     NBT    - NuBits
+13     MZC    - Mazacoin
+14     VIA    - Viacoin
+15     XCH    - ClearingHouse
+16     RBY    - Rubycoin
+17     GRS    - Groestlcoin
+18     DGC    - Digitalcoin
+19     CCN    - Cannacoin
+20     DGB    - DigiByte
+21     ---    - Open Assets
+22     MONA   - Monacoin
+23     CLAM   - Clams
+24     XPM    - Primecoin
+25     NEOS   - Neoscoin
+26     JBS    - Jumbucks
+27     ZRC    - ziftrCOIN
+28     VTC    - Vertcoin
+29     NXT    - NXT
+30     BURST  - Burst
+31     MUE    - MonetaryUnit
+32     ZOOM   - Zoom
+33     VASH   - Virtual Cash
+34     CDN    - Canada eCoin
+35     SDC    - ShadowCash
+36     PKB    - ParkByte
+37     PND    - Pandacoin
+38     START  - StartCOIN
+39     MOIN   - MOIN
+40     EXP    - Expanse
+41     EMC2   - Einsteinium
+42     DCR    - Decred
+43     XEM    - NEM
+44     PART   - Particl
+45     ARG    - Argentum (dead)
+46     ---    - Libertas
+47     ---    - Posw coin
+48     SHR    - Shreeji
+49     GCR    - Global Currency Reserve (GCRcoin)
+50     NVC    - Novacoin
+51     AC     - Asiacoin
+52     BTCD   - BitcoinDark
+53     DOPE   - Dopecoin
+54     TPC    - Templecoin
+55     AIB    - AIB
+56     EDRC   - EDRCoin
+57     SYS    - Syscoin
+58     SLR    - Solarcoin
+59     SMLY   - Smileycoin
+62     PSB    - Pesobit
+63     LDCN   - Landcoin (dead)
+64     ---    - Open Chain
+65     XBC    - Bitcoinplus
+66     IOP    - Internet of People
+67     NXS    - Nexus
+68     INSN   - InsaneCoin
+69     OK     - OKCash
+70     BRIT   - BritCoin
+71     CMP    - Compcoin
+72     CRW    - Crown
+73     BELA   - BelaCoin
+75     FJC    - FujiCoin
+76     MIX    - MIX
+78     EFL    - Electronic Gulden
+79     CLUB   - ClubCoin
+80     RICHX  - RichCoin
+81     POT    - Potcoin
+82     QRK    - Quarkcoin
+83     TRC    - Terracoin
+84     GRC    - Gridcoin
+85     AUR    - Auroracoin
+86     IXC    - IXCoin
+87     NLG    - Gulden
+88     BITB   - BitBean
+89     BTA    - Bata
+90     XMY    - Myriadcoin
+91     BSD    - BitSend
+92     UNO    - Unobtanium
+93     MTR    - MasterTrader
+94     GB     - GoldBlocks
+95     SHM    - Saham
+96     CRX    - Chronos
+97     BIQ    - Ubiquoin
+98     EVO    - Evotion
+99     STO    - SaveTheOcean
+100    BIGUP  - BigUp
+101    GAME   - GameCredits
+102    DLC    - Dollarcoins
+103    ZYD    - Zayedcoin
+104    DBIC   - Dubaicoin
+105    STRAT  - Stratis
+106    SH     - Shilling
+107    MARS   - MarsCoin
+108    UBQ    - Ubiq
+109    PTC    - Pesetacoin
+110    NRO    - Neurocoin
+111    ARK    - ARK
+112    USC    - UltimateSecureCashMain
+113    THC    - Hempcoin
+114    LINX   - Linx
+115    ECN    - Ecoin
+116    DNR    - Denarius
+117    PINK   - Pinkcoin
+119    PIVX   - Pivx
+120    FLASH  - Flashcoin
+121    ZEN    - Zencash
+122    PUT    - Putincoin
+123    ZNY    - BitZeny
+124    UNIFY  - Unify
+125    XST    - StealthCoin
+126    BRK    - Breakout Coin
+127    VC     - Vcash
+129    VOX    - Voxels
+130    NAV    - NavCoin
+131    FCT    - Factom Factoids
+132    EC     - Factom Entry Credits
+134    LSK    - Lisk
+135    STEEM  - Steem
+136    XZC    - ZCoin
+137    RBTC   - RSK
+138    ---    - Giftblock
+139    RPT    - RealPointCoin
+140    LBC    - LBRY Credits
+141    KMD    - Komodo
+142    BSQ    - bisq Token
+143    RIC    - Riecoin
+146    NEBL   - Neblio
+147    ZCL    - ZClassic
+149    NLC2   - NoLimitCoin2
+150    WHL    - WhaleCoin
+151    ERC    - EuropeCoin
+152    DMD    - Diamond
+153    BTM    - Bytom
+154    BIO    - Biocoin
+155    XWCC   - Whitecoin Classic
+156    BTG    - Bitcoin Gold
+157    BTC2X  - Bitcoin 2x
+158    SSN    - SuperSkynet
+159    TOA    - TOACoin
+160    BTX    - Bitcore
+161    ACC    - Adcoin
+162    BCO    - Bridgecoin
+163    ELLA   - Ellaism
+164    PIRL   - Pirl
+166    VIVO   - Vivo
+167    FRST   - Firstcoin
+168    HNC    - Helleniccoin
+169    BUZZ   - BUZZ
+170    MBRS   - Ember
+171    HC     - Hcash
+172    HTML   - HTMLCOIN
+173    ODN    - Obsidian
+174    ONX    - OnixCoin
+175    RVN    - Ravencoin
+176    GBX    - GoByte
+177    BTCZ   - BitcoinZ
+178    POA    - Poa
+179    NYC    - NewYorkCoin
+180    MXT    - MarteXcoin
+181    WC     - Wincoin
+182    MNX    - Minexcoin
+183    BTCP   - Bitcoin Private
+184    MUSIC  - Musicoin
+185    BCA    - Bitcoin Atom
+186    CRAVE  - Crave
+187    STAK   - STRAKS
+188    WBTC   - World Bitcoin
+189    LCH    - LiteCash
+190    EXCL   - ExclusiveCoin
+191    ---    - Lynx
+192    LCC    - LitecoinCash
+193    XFE    - Feirm
+196    KOBO   - Kobocoin
+197    HUSH   - HUSH
+198    BAN    - Banano
+199    ETF    - ETF
+200    OMNI   - Omni
+201    BIFI   - BitcoinFile
+202    UFO    - Uniform Fiscal Object
+203    CNMC   - Cryptonodes
+204    BCN    - Bytecoin
+205    RIN    - Ringo
+206    ATP    - Alaya
+207    EVT    - everiToken
+208    ATN    - ATN
+209    BIS    - Bismuth
+210    NEET   - NEETCOIN
+211    BOPO   - BopoChain
+212    OOT    - Utrum
+213    ALIAS  - Alias
+214    MONK   - Monkey Project
+215    BOXY   - BoxyCoin
+216    FLO    - Flo
+217    MEC    - Megacoin
+218    BTDX   - BitCloud
+219    XAX    - Artax
+220    ANON   - ANON
+221    LTZ    - LitecoinZ
+222    BITG   - Bitcoin Green
+223    ICP    - Internet Computer (DFINITY)
+224    SMART  - Smartcash
+225    XUEZ   - XUEZ
+226    HLM    - Helium
+227    WEB    - Webchain
+228    ACM    - Actinium
+229    NOS    - NOS Stable Coins
+230    BITC   - BitCash
+231    HTH    - Help The Homeless Coin
+232    TZC    - Trezarcoin
+233    VAR    - Varda
+234    IOV    - IOV
+235    FIO    - FIO
+237    DXN    - DEXON
+238    QRL    - Quantum Resistant Ledger
+239    PCX    - ChainX
+240    LOKI   - Loki
+241    ---    - Imagewallet
+242    NIM    - Nimiq
+243    SOV    - Sovereign Coin
+244    JCT    - Jibital Coin
+245    SLP    - Simple Ledger Protocol
+246    EWT    - Energy Web
+247    UC     - Ulord
+248    EXOS   - EXOS
+249    ECA    - Electra
+250    SOOM   - Soom
+251    XRD    - Redstone
+252    FREE   - FreeCoin
+253    NPW    - NewPowerCoin
+254    BST    - BlockStamp
+255    ---    - SmartHoldem
+256    NANO   - Bitcoin Nano
+257    BTCC   - Bitcoin Core
+258    ---    - Zen Protocol
+259    ZEST   - Zest
+260    ABT    - ArcBlock
+261    PION   - Pion
+262    DT3    - DreamTeam3
+263    ZBUX   - Zbux
+264    KPL    - Kepler
+265    TPAY   - TokenPay
+266    ZILLA  - ChainZilla
+267    ANK    - Anker
+268    BCC    - BCChain
+269    HPB    - HPB
+270    ONE    - ONE
+271    SBC    - SBC
+272    IPC    - IPChain
+273    DMTC   - Dominantchain
+274    OGC    - Onegram
+275    SHIT   - Shitcoin
+276    ANDES  - Andescoin
+277    AREPA  - Arepacoin
+278    BOLI   - Bolivarcoin
+279    RIL    - Rilcoin
+280    HTR    - Hathor Network
+281    ACME   - Accumulate
+282    BRAVO  - BRAVO
+284    BZX    - Bitcoinzero
+285    GXX    - GravityCoin
+286    HEAT   - HEAT
+287    XDN    - DigitalNote
+288    FSN    - FUSION
+289    CPC    - Capricoin
+290    BOLD   - Bold
+291    IOST   - IOST
+292    TKEY   - Tkeycoin
+293    USE    - Usechain
+294    BCZ    - BitcoinCZ
+295    IOC    - Iocoin
+296    ASF    - Asofe
+297    MASS   - MASS
+298    FAIR   - FairCoin
+299    NUKO   - Nekonium
+300    GNX    - Genaro Network
+301    DIVI   - Divi Project
+302    CMT    - Community
+303    EUNO   - EUNO
+304    IOTX   - IoTeX
+305    ONION  - DeepOnion
+306    8BIT   - 8Bit
+307    ATC    - AToken Coin
+308    BTS    - Bitshares
+309    CKB    - Nervos CKB
+310    UGAS   - Ultrain
+311    ADS    - Adshares
+312    ARA    - Aura
+314    MOAC   - MOAC
+315    SWTC   - SWTC
+316    VNSC   - vnscoin
+317    PLUG   - Pl^g
+318    MAN    - Matrix AI Network
+319    ECC    - ECCoin
+320    RPD    - Rapids
+321    RAP    - Rapture
+322    GARD   - Hashgard
+323    ZER    - Zero
+324    EBST   - eBoost
+325    SHARD  - Shard
+326    MRX    - Metrix Coin
+327    CMM    - Commercium
+328    BLOCK  - Blocknet
+329    AUDAX  - AUDAX
+331    ZPM    - zPrime
+332    KUVA   - Kuva Utility Note
+333    MEM    - MemCoin
+334    CS     - Credits
+335    SWIFT  - SwiftCash
+336    FIX    - FIX
+337    CPC    - CPChain
+338    VGO    - VirtualGoodsToken
+339    DVT    - DeVault
+340    N8V    - N8VCoin
+341    MTNS   - OmotenashiCoin
+342    BLAST  - BLAST
+343    DCT    - DECENT
+344    AUX    - Auxilium
+345    USDP   - USDP
+346    HTDF   - HTDF
+347    YEC    - Ycash
+348    QLC    - QLC Chain
+349    TEA    - Icetea Blockchain
+350    ARW    - ArrowChain
+351    MDM    - Medium
+352    CYB    - Cybex
+353    LTO    - LTO Network
+355    AEON   - Aeon
+356    RES    - Resistance
+357    AYA    - Aryacoin
+358    DAPS   - Dapscoin
+359    CSC    - CasinoCoin
+360    VSYS   - V Systems
+361    NOLLAR - Nollar
+362    XNOS   - NOS
+363    CPU    - CPUchain
+364    LAMB   - Lambda Storage Chain
+365    VCT    - ValueCyber
+366    CZR    - Canonchain
+367    ABBC   - ABBC
+368    HET    - HET
+369    XAS    - Asch
+370    VDL    - Vidulum
+371    MED    - MediBloc
+372    ZVC    - ZVChain
+373    VESTX  - Vestx
+374    DBT    - DarkBit
+375    SEOS   - SuperEOS
+376    MXW    - Maxonrow
+377    ZNZ    - ZENZO
+378    XCX    - XChain
+379    SOX    - SonicX
+380    NYZO   - Nyzo
+381    ULC    - ULCoin
+382    RYO    - Ryo Currency
+383    KAL    - Kaleidochain
+384    XSN    - Stakenet
+385    DOGEC  - DogeCash
+386    BMV    - Bitcoin Matteo's Vision
+387    QBC    - Quebecoin
+388    IMG    - ImageCoin
+389    QOS    - QOS
+390    PKT    - PKT
+391    LHD    - LitecoinHD
+392    CENNZ  - CENNZnet
+393    HSN    - Hyper Speed Network
+394    CRO    - Crypto Chain
+395    UMBRU  - Umbru
+396    EVER   - Everscale
+398    XPC    - XPChain
+399    ZOC    - 01coin
+400    NIX    - NIX
+401    UC     - Utopiacoin
+402    GALI   - Galilel
+403    OLT    - Oneledger
+404    XBI    - XBI
+405    DONU   - DONU
+406    EARTHS - Earths
+407    HDD    - HDDCash
+408    SUGAR  - Sugarchain
+409    AILE   - AileCoin
+410    TENT   - TENT
+411    TAN    - Tangerine Network
+412    AIN    - AIN
+413    MSR    - Masari
+414    SUMO   - Sumokoin
+415    ETN    - Electroneum
+416    BYTZ   - BYTZ
+417    WOW    - Wownero
+418    XTNC   - XtendCash
+419    LTHN   - Lethean
+420    NODE   - NodeHost
+421    AGM    - Argoneum
+422    CCX    - Conceal Network
+423    TNET   - Title Network
+424    TELOS  - TelosCoin
+425    AION   - Aion
+426    BC     - Bitcoin Confidential
+427    KTV    - KmushiCoin
+428    ZCR    - ZCore
+430    PESO   - Criptopeso
+431    BTC2   - Bitcoin 2
+432    XRPHD  - XRPHD
+433    WE     - WE Coin
+435    PCN    - Peepcoin
+436    NCH    - NetCloth
+437    ICU    - CHIPO
+438    FNSA   - FINSCHIA
+439    DTP    - DeVault Token Protocol
+440    BTCR   - Bitcoin Royale
+441    AERGO  - AERGO
+442    XTH    - Dothereum
+443    LV     - Lava
+444    PHR    - Phore
+445    VITAE  - Vitae
+446    COCOS  - Cocos-BCX
+447    DIN    - Dinero
+448    SPL    - Simplicity
+449    YCE    - MYCE
+450    XLR    - Solaris
+451    KTS    - Klimatas
+452    DGLD   - DGLD
+453    XNS    - Insolar
+454    EM     - EMPOW
+455    SHN    - ShineBlocks
+456    SEELE  - Seele
+457    AE     - æternity
+458    ODX    - ObsidianX
+460    GLEEC  - GLEEC
+462    RUTA   - Rutanio
+463    CSDT   - CSDT
+464    ETI    - EtherInc
+465    ZSLP   - Zclassic Simple Ledger Protocol
+466    ERE    - EtherCore
+467    DX     - DxChain Token
+468    CPS    - Capricoin+
+469    BTH    - Bithereum
+470    MESG   - MESG
+471    FIMK   - FIMK
+472    AR     - Arweave
+473    OGO    - Origo
+474    ROSE   - Oasis Network
+475    BARE   - BARE Network
+476    GLEEC  - GleecBTC
+477    CLR    - Color Coin
+478    RNG    - Ring
+479    OLO    - Tool Global
+480    PEXA   - Pexa
+481    MOON   - Mooncoin
+482    OCEAN  - Ocean Protocol
+483    BNT    - Bluzelle Native
+484    AMO    - AMO Blockchain
+485    FCH    - FreeCash
+486    LAT    - PlatON
+487    COIN   - Bitcoin Bank
+488    VEO    - Amoveo
+489    CCA    - Counos Coin
+490    GFN    - Graphene
+491    BIP    - Minter Network
+492    KPG    - Kunpeng Network
+493    FIN    - FINL Chain
+495    DROP   - Dropil
+496    BHT    - Bluehelix Chain
+497    LYRA   - Scrypta
+498    CS     - Credits
+499    RUPX   - Rupaya
+502    THT    - ThoughtAI
+503    CFX    - Conflux
+504    KUMA   - Kumacoin
+505    HASH   - Provenance
+506    CSPR   - Casper
+507    EARTH  - EARTH
+509    CHI    - Xaya
+510    KOTO   - Koto
+511    OTC    - θ
+512    XRD    - Radiant
+513    SEELEN - Seele-N
+514    AETH   - AETH
+515    DNA    - Idena
+516    VEE    - Virtual Economy Era
+517    SIERRA - SierraCoin
+518    LET    - Linkeye
+519    BSC    - Bitcoin Smart Contract
+520    BTCV   - BitcoinVIP
+521    ABA    - Dabacus
+522    SCC    - StakeCubeCoin
+523    EDG    - Edgeware
+524    AMS    - AmsterdamCoin
+525    GOSS   - GOSSIP Coin
+526    BU     - BUMO
+527    GRAM   - GRAM
+528    YAP    - Yapstone
+530    NOVO   - Novo
+531    GHOST  - Ghost
+532    HST    - HST
+533    PRJ    - ProjectCoin
+534    YOU    - YOUChain
+535    XHV    - Haven Protocol
+536    BYND   - Beyondcoin
+537    JOYS   - Joys Digital
+538    VAL    - Valorbit
+539    FLOW   - Flow
+540    SMESH  - Spacemesh Coin
+541    SCDO   - SCDO
+542    IQS    - IQ-Cash
+543    BIND   - Compendia
+544    COINEVO - Coinevo
+545    SCRIBE - Scribe
+546    HYN    - Hyperion
+547    BHP    - BHP
+548    BBC    - BigBang Core
+549    MKF    - MarketFinance
+550    XDC    - XinFin
+551    STR    - Straightedge
+552    SUM    - Sumcoin
+553    HBC    - HuobiChain
+554    ---    - reserved
+555    BCS    - Bitcoin Smart
+556    KTS    - Kratos
+557    LKR    - Lkrcoin
+558    TAO    - Tao
+559    XWC    - Whitecoin
+560    DEAL   - DEAL
+561    NTY    - Nexty
+562    TOP    - TOP NetWork
+563    ---    - reserved
+564    AG     - Agoric
+565    CICO   - Coinicles
+566    IRIS   - Irisnet
+568    LRG    - Large Coin
+569    SERO   - Super Zero Protocol
+570    BDX    - Beldex
+571    CCXX   - Counos X
+572    SLS    - Saluscoin
+573    SRM    - Serum
+574    ---    - reserved
+575    VIVT   - VIDT Datalink
+576    BPS    - BitcoinPoS
+577    NKN    - NKN
+578    ICL    - ILCOIN
+579    BONO   - Bonorum
+580    PLC    - PLATINCOIN
+581    DUN    - Dune
+582    DMCH   - Darmacash
+583    CTC    - Creditcoin
+584    KELP   - Haidai Network
+585    GBCR   - GoldBCR
+586    XDAG   - XDAG
+587    PRV    - Incognito Privacy
+588    SCAP   - SafeCapital
+589    TFUEL  - Theta Fuel
+590    GTM    - Gentarium
+591    RNL    - RentalChain
+592    GRIN   - Grin
+593    MWC    - MimbleWimbleCoin
+594    DOCK   - Dock
+595    POLYX  - Polymesh
+596    DIVER  - Divergenti
+597    XEP    - Electra Protocol
+598    APN    - Apron
+599    TFC    - Turbo File Coin
+600    UTE    - Unit-e
+601    MTC    - Metacoin
+602    NC     - NobodyCash
+603    XINY   - Xinyuehu
+604    DYN    - Dynamo
+605    BUFS   - Buffer
+606    STOS   - Stratos
+607    TON    - TON
+608    TAFT   - TAFT
+609    HYDRA  - HYDRA
+610    NOR    - Noir
+611    ---    - Manta Network Private Asset
+612    ---    - Calamari Network Private Asset
+613    WCN    - Widecoin
+614    OPT    - Optimistic Ethereum
+615    PSWAP  - PolkaSwap
+616    VAL    - Validator
+617    XOR    - Sora
+618    SSP    - SmartShare
+619    DEI    - DeimosX
+620    ---    - reserved
+621    ZERO   - Singularity
+622    ALPHA  - AlphaDAO
+623    BDECO  - BDCashProtocol Ecosystem
+624    NOBL   - Nobility
+625    EAST   - Eastcoin
+626    KDA    - Kadena
+627    SOUL   - Phantasma
+628    LORE   - Gitopia
+629    FNR    - Fincor
+630    NEXUS  - Nexus
+631    QTZ    - Quartz
+632    MAS    - Massa
+633    CALL   - Callchain
+634    VAL    - Validity
+635    POKT   - Pocket Network
+636    EMIT   - EMIT
+638    ADON   - ADON
+639    BTSG   - BitSong
+640    LFC    - Leofcoin
+641    KCS    - KuCoin Shares
+642    KCC    - KuCoin Community Chain
+643    AZERO  - Aleph Zero
+644    TREE   - Tree
+645    LX     - Lynx
+646    XLN    - Lunarium
+647    CIC    - CIC Chain
+648    ZRB    - Zarb
+649    ---    - reserved
+650    UCO    - Archethic
+651    SFX    - Safex Cash
+652    SFT    - Safex Token
+653    WSFX   - Wrapped Safex Cash
+654    USDG   - US Digital Gold
+655    WMP    - WAMP
+656    EKTA   - Ekta
+657    YDA    - YadaCoin
+659    KOIN   - Koinos
+660    PIRATE - PirateCash
+661    UNQ    - Unique
+663    SFRX   - EtherGem Sapphire
+666    ACT    - Achain
+667    PRKL   - Perkle
+668    SSC    - SelfSell
+669    GC     - GateChain
+670    PLGR   - Pledger
+671    MPLGR  - Pledger
+672    KNOX   - Knox
+673    ZED    - ZED
+674    CNDL   - Candle
+675    WLKR   - Walker Crypto Innovation Index
+676    WLKRR  - Walker
+677    YUNGE  - Yunge
+678    Voken  - Voken
+679    APL    - Apollo
+680    Evrynet - Evrynet
+681    NENG   - Nengcoin
+682    CHTA   - Cheetahcoin
+683    ALEO   - Aleo Network
+685    OAS    - Oasys
+686    KAR    - Karura Network
+688    CET    - CoinEx Chain
+690    KLV    - KleverChain
+694    VTBC   - VTB Community
+698    VEIL   - Veil
+699    GTB    - GotaBit
+700    XDAI   - xDai
+701    COM    - Commercio
+702    CCC    - Commercio Cash Credit
+707    MCOIN  - Moneta Coin
+710    FURY   - Highbury
+711    CHC    - Chaincoin
+712    SERF   - Serfnet
+713    XTL    - Katal Chain
+715    SIN    - Sinovate
+716    DLN    - Delion
+717    BONTE  - Bontecoin
+718    PEER   - Peer
+719    ZET    - Zetacoin
+720    ABY    - Artbyte
+721    PGX    - Mirai Chain
+722    IL8P   - InfiniLooP
+724    XVC    - Vanillacash
+725    MCX    - MultiCash
+727    BLU    - BluCrates
+730    HEALIOS - Tenacity
+731    BMK    - Bitmark
+734    DENTX  - DENTNet
+737    ATOP   - Financial Blockchain
+747    CFG    - Centrifuge
+750    XPRT   - Persistence
+753    ---    - Age X25519 Encryption
+754    ---    - Age NIST Encryption
+757    HONEY  - HoneyWood
+768    BALLZ  - Ballzcoin
+770    COSA   - Cosanta
+771    BR     - BR
+775    PLSR   - Pulsar Coin
+776    KEY    - Keymaker Coin
+777    BTW    - Bitcoin World
+780    PLCUC  - PLC Ultima Classic
+781    PLCUX  - PLC Ultima X
+782    PLCU   - PLC Ultima
+783    SMARTBC - SMART Blockchain
+786    UIDD   - UIDD
+787    ACA    - Acala
+788    BNC    - Bifrost
+789    TAU    - Lamden
+799    PDEX   - Polkadex
+800    BEET   - Beetle Coin
+801    DST    - DSTRA
+802    CY     - Cyberyen
+804    ZKS    - zkSync
+808    QVT    - Qvolta
+809    SDN    - Shiden Network
+810    ASTR   - Astar Network
+811    ---    - reserved
+813    MEER   - Qitmeer
+819    REEF   - Reef
+820    CLO    - Callisto
+822    BDB    - BigchainDB
+827    ACE    - Endurance
+828    CCN    - ComputeCoin
+829    BBA    - BBACHAIN
+831    CRUZ   - cruzbit
+832    SAPP   - Sapphire
+833    777    - Jackpot
+834    KYAN   - Kyanite
+835    AZR    - Azzure
+836    CFL    - CryptoFlow
+837    DASHD  - Dash Diamond
+838    TRTT   - Trittium
+839    UCR    - Ultra Clear
+840    PNY    - Peony
+841    BECN   - Beacon
+842    MONK   - Monk
+843    SAGA   - CryptoSaga
+844    SUV    - Suvereno
+845    ESK    - EskaCoin
+846    OWO    - OneWorld Coin
+847    PEPS   - PEPS Coin
+848    BIR    - Birake
+849    MOBIC  - MobilityCoin
+850    FLS    - Flits
+852    DSM    - Desmos
+853    PRCY   - PRCY Coin
+858    HVH    - HAVAH
+866    MOB    - MobileCoin
+868    IF     - Infinitefuture
+877    NAM    - Namada
+878    SCR    - Scorum Network
+880    LUM    - Lum Network
+883    ZBC    - ZooBC
+886    ADF    - AD Token
+889    TOMO   - TOMO
+890    XSEL   - Seln
+896    LKSC   - LKSCoin
+898    AS     - Assetchain
+899    XEC    - eCash
+900    LMO    - Lumeneo
+901    NXT    - NxtMeta
+904    HNT    - Helium
+907    FIS    - StaFi
+909    SGE    - Saage
+911    GERT   - Gert
+913    VARA   - Vara Network
+916    META   - Metadium
+917    FRA    - Findora
+919    CCD    - Concordium
+921    AVN    - Avian Network
+925    DIP    - Dipper Network
+928    GHM    - HermitMatrixNetwork
+931    RUNE   - THORChain (RUNE)
+941    ---    - reserved
+945    UNLOCK - Jasiri protocol
+955    LTP    - LifetionCoin
+958    ---    - KickSoccer
+960    VKAX   - Vkax
+966    MATIC  - Matic
+968    UNW    - UNW
+970    TWINS  - TWINS
+977    TLOS   - Telos
+981    TAFECO - Taf ECO Chain
+985    AU     - Autonomy
+987    VCG    - VipCoin
+988    XAZAB  - Xazab core
+989    AIOZ   - AIOZ
+990    CORE   - Coreum
+991    PEC    - Phoenix
+992    UNT    - Unit
+993    XRB    - X Currency
+994    QUAI   - Quai Network
+995    CAPS   - Ternoa
+997    SUM    - Solidum
+998    LBTC   - Lightning Bitcoin
+999    BCD    - Bitcoin Diamond
+1000   BTN    - Bitcoin New
+1001   TT     - ThunderCore
+1002   BKT    - BanKitt
+1003   NODL   - Nodle
+1004   PCOIN  - PCOIN
+1005   TAO    - Bittensor
+1006   HSK    - HashKey Chain
+1007   FTM    - Fantom
+1008   RPG    - RPG
+1009   LAKE   - iconLake
+1010   HT     - Huobi ECO Chain
+1011   ELV    - Eluvio
+1012   JOC    - Japan Open Chain
+1013   BIC    - Beincrypto
+1016   ---    - reserved
+1020   EVC    - Evrice
+1022   XRD    - Radix DLT
+1025   CZZ    - Classzz
+1026   KEX    - Kira Exchange Token
+1027   MCM    - Mochimo
+1028   PLS    - Pulse Coin
+1032   BTCR   - BTCR
+1042   MFID   - Moonfish ID
+1111   BBC    - Big Bitcoin
+1116   CORE   - Core
+1120   RISE   - RISE
+1122   CMT    - CyberMiles Token
+1128   ETSC   - Ethereum Social
+1129   DFI    - DeFiChain
+1130   DFI    - DeFiChain EVM Network
+1137   $DAG   - Constellation Labs
+1145   CDY    - Bitcoin Candy
+1155   ENJ    - Enjin Coin
+1170   HOO    - Hoo Smart Chain
+1234   ALPH   - Alephium
+1236   ---    - Masca
+1237   ---    - Nostr
+1280   ---    - Kudos Setler
+1284   GLMR   - Moonbeam
+1285   MOVR   - Moonriver
+1298   WPC    - Wpc
+1308   WEI    - WEI
+1337   DFC    - Defcoin
+1348   ISLM   - IslamicCoin
+1397   HYC    - Hycon
+1410   TENTSLP - TENT Simple Ledger Protocol
+1510   XSC    - XT Smart Chain
+1512   AAC    - Double-A Chain
+1524   ---    - Taler
+1533   BEAM   - Beam
+1551   SDK    - Sovereign SDK
+1555   APC    - Apc Chain
+1616   ELF    - AELF
+1618   AUDL   - AUDL
+1620   ATH    - Atheios
+1627   LUME   - Lume Web
+1642   NEW    - Newton
+1657   BTA    - Btachain
+1668   NEOX   - Neoxa
+1669   MEWC   - Meowcoin
+1688   BCX    - BitcoinX
+1776   LBTC   - Liquid BTC
+1777   BBP    - Biblepay
+1784   JPYS   - JPY Stablecoin
+1789   VEGA   - Vega Protocol
+1818   CUBE   - Cube Chain Native Token
+1856   TES    - Teslacoin
+1888   ZTX    - Zetrix
+1899   XEC    - eCash token
+1901   CLC    - Classica
+1907   BITCI  - Bitcicoin
+1919   VIPS   - VIPSTARCOIN
+1926   CITY   - City Coin
+1955   XX     - xx coin
+1977   XMX    - Xuma
+1984   TRTL   - TurtleCoin
+1985   SLRT   - Solarti Chain
+1986   QTH    - Qing Tong Horizon
+1987   EGEM   - EtherGem
+1988   MIRA   - Mira Chain
+1989   HODL   - HOdlcoin
+1990   PHL    - Placeholders
+1991   SC     - Sia
+1996   MYT    - Mineyourtime
+1997   POLIS  - Polis
+1998   XMCC   - Monoeci
+1999   COLX   - ColossusXT
+2000   GIN    - GinCoin
+2001   MNP    - MNPCoin
+2002   MLN    - Miraland
+2017   KIN    - Kin
+2018   EOSC   - EOSClassic
+2019   GBT    - GoldBean Token
+2020   PKC    - PKC
+2021   SKT    - Sukhavati
+2022   XHT    - Xinghuo Token
+2023   COC    - Chat On Chain
+2024   USBC   - Universal Ledger USBC
+2046   ANY    - Any
+2048   MCASH  - MCashChain
+2049   TRUE   - TrueChain
+2050   MOVO   - Movo Smart Chain
+2086   KILT   - KILT Spiritnet
+2109   SAMA   - Exosama Network
+2112   IoTE   - IoTE
+2125   BAY    - BitBay
+2137   XRG    - Ergon
+2182   CHZ    - Chiliz
+2199   SAMA   - Moonsama Network
+2221   ASK    - ASK
+2222   CWEB   - Coinweb
+2285   ---    - Qiyi Chain
+2301   QTUM   - QTUM
+2302   ETP    - Metaverse
+2303   GXC    - GXChain
+2304   CRP    - CranePay
+2305   ELA    - Elastos
+2338   SNOW   - Snowblossom
+2365   XIN    - Mixin
+2500   NEXI   - Nexi
+2570   AOA    - Aurora
+2718   NAS    - Nebulas
+2894   REOSC  - REOSC Ecosystem
+2941   BND    - Blocknode
+3000   SM     - Stealth Message
+3003   LUX    - LUX
+3030   HBAR   - Hedera HBAR
+3077   COS    - Contentos
+3276   CCC    - CodeChain
+3333   SXP    - Solar
+3377   ROI    - ROIcoin
+3381   DYN    - Dynamic
+3383   SEQ    - Sequence
+3552   DEO    - Destocoin
+3564   DST    - DeStream
+3601   CY     - Cybits
+3757   MPC    - Partisia Blockchain
+4040   FC8    - FCH Network
+4096   YEE    - YeeCo
+4218   IOTA   - IOTA
+4219   SMR    - Shimmer
+4242   AXE    - Axe
+4343   XYM    - Symbol
+4444   C4E    - Chain4Energy
+4919   XVM    - Venidium
+4999   BXN    - BlackFort Exchange Network
+5006   SBC    - Senior Blockchain
+5248   FIC    - FIC
+5353   HNS    - Handshake
+5404   ISK    - ISKRA
+5467   ALTME  - ALTME
+5555   FUND   - Unification
+5757   STX    - Stacks
+5895   VOW    - VowChain VOW
+5920   SLU    - SILUBIUM
+6060   GO     - GoChain GO
+6174   MOI    - My Own Internet
+6532   UM     - Penumbra
+6599   RSC    - Royal Sports City
+6666   BPA    - Bitcoin Pizza
+6688   SAFE   - SAFE
+6779   COTI   - COTI
+6969   ROGER  - TheHolyrogerCoin
+7027   ELLA   - Ella the heart
+7028   AA     - Arthera
+7091   TOPL   - Topl
+7331   KLY    - KLYNTAR
+7341   SHFT   - Shyft
+7518   MEV    - MEVerse
+7576   ADIL   - ADIL Chain
+7777   BTV    - Bitvote
+8000   SKY    - Skycoin
+8080   ---    - DSRV
+8181   BOC    - BeOne Chain
+8192   PAC    - pacprotocol
+8217   KLAY   - KLAY
+8339   BTQ    - BitcoinQuark
+8444   XCH    - Chia
+8520   ---    - reserved
+8680   PLMNT  - Planetmint
+8866   GGX    - Golden Gate
+8886   GGXT   - Golden Gate Sydney
+8888   SBTC   - Super Bitcoin
+8964   NULS   - NULS
+8997   BBC    - Babacoin
+8998   JGC    - JagoanCoin
+8999   BTP    - Bitcoin Pay
+9001   ARB1   - Arbitrum
+9002   BOBA   - Boba
+9003   LOOP   - Loopring
+9004   STRK   - StarkNet
+9005   AVAXC  - Avalanche C-Chain
+9006   BSC    - Binance Smart Chain
+9797   NRG    - Energi
+9888   BTF    - Bitcoin Faith
+9999   GOD    - Bitcoin God
+10000  FO     - FIBOS
+10111  DHP    - dHealth
+10226  RTM    - Raptoreum
+10291  XRC    - XRhodium
+10507  NUM    - Numbers Protocol
+10605  XPI    - Lotus
+11111  ESS    - Essentia One
+11742  VARCH  - InvArch
+11743  TNKR   - Tinkernet
+12345  IPOS   - IPOS
+12586  MINA   - Mina
+13107  BTY    - BitYuan
+13108  YCC    - Yuan Chain Coin
+14001  WAX    - Worldwide Asset Exchange
+15845  SDGO   - SanDeGo
+16181  XTX    - Totem Live Network
+16754  ARDR   - Ardor
+18000  MTR    - Meter
+19165  SAFE   - Safecoin
+19167  FLUX   - Flux
+19169  RITO   - Ritocoin
+19788  ML     - Mintlayer
+20036  XND    - ndau
+21004  C4EI   - c4ei
+21888  PAC    - Pactus
+22504  PWR    - PWRcoin
+23000  EPIC   - Epic Cash
+25252  BELL   - Bellcoin
+25718  CHX    - Own
+29223  NEXA   - Nexa
+30001  ---    - reserved
+31102  ESN    - EtherSocial Network
+31337  ---    - ThePower
+33416  TEO    - Trust Eth reOrigin
+33878  BTCS   - Bitcoin Stake
+34952  BTT    - ByteTrade
+37992  FXTC   - FixedTradeCoin
+39321  AMA    - Amabig
+42069  FACT   - FACT0RN
+43028  AXIV   - AXIV
+49262  EVE    - evan
+49344  STASH  - STASH
+61616  TH     - TianHe
+65536  KETH   - Krypton World
+69420  GRLC   - Garlicoin
+70007  GWL    - Gewel
+77777  ZYN    - Wethio
+88888  RYO    - c0ban
+99999  WICC   - Waykichain
+100500 HOME   - HomeCoin
+101010 STC    - Starcoin
+105105 STRAX  - Strax
+111111 KAS    - Kaspa
+161803 APTA   - Bloqs4Good
+200625 AKA    - Akroma
+200665 GENOM  - GENOM
+246529 ATS    - ARTIS sigma1
+261131 ZAMA   - Zama
+333332 VALUE  - Value Chain
+333333 3333   - Pi Value Consensus
+424242 X42    - x42
+534352 SCR    - Scroll
+666666 VITE   - Vite
+888888 SEA    - Second Exchange Alliance
+999999 WTC    - WaltonChain
+1048576 AMAX   - Armonia Meta Chain
+1171337 ILT    - iOlite
+1313114 ETHO   - Etho Protocol
+1313500 XERO   - Xerom
+1712144 LAX    - LAPO
+3924011 EPK    - EPIK Protocol
+4741444 HYD    - Hydra Token
+5249353 BCO    - BitcoinOre
+5249354 BHD    - BitcoinHD
+5264462 PTN    - PalletOne
+5655640 VLX    - Velas
+5718350 WAN    - Wanchain
+5741564 WAVES  - Waves
+5741565 WEST   - Waves Enterprise
+6382179 ABC    - Abcmint
+6517357 CRM    - Creamcoin
+7171666 BROCK  - Bitrock
+7562605 SEM    - Semux
+7567736 ION    - ION
+7777777 FCT    - FirmaChain
+7825266 WGR    - WGR
+7825267 OBSR   - OBServer
+8163271 AFS    - ANFS
+11259375 LBR    - 0L
+15118976 XDS    - XDS
+61717561 AQUA   - Aquachain
+88888888 HATCH  - Hatch
+91927009 kUSD   - kUSD
+99999996 GENS   - GENS
+99999997 EQ     - EQ
+99999998 FLUID  - Fluid Chains
+99999999 QKC    - QuarkChain
+608589380 FVDC   - ForumCoin
+1179993420 ---    - Fuel
+
+"""

+ 1 - 1
mmgen/data/version

@@ -1 +1 @@
-14.1.dev3
+14.1.dev4

+ 115 - 0
scripts/create-bip-hd-chain-params.py

@@ -0,0 +1,115 @@
+#!/usr/bin/env python3
+
+import json
+
+from bip_utils.slip.slip44 import Slip44
+from bip_utils.bip.conf.bip44.bip44_conf import Bip44Conf
+from bip_utils.bip.conf.bip49.bip49_conf import Bip49Conf
+from bip_utils.bip.conf.bip84.bip84_conf import Bip84Conf
+from bip_utils.bip.conf.bip86.bip86_conf import Bip86Conf
+
+import script_init
+from mmgen.cfg import Config
+from mmgen.main import launch
+
+opts_data = {
+	'text': {
+		'desc': 'Aggregate bip_lib and SLIP-44 data into a bip_hd chainparams table',
+		'usage':'[opts] infile',
+		'options': """
+-h, --help               Print this help message.
+""",
+	'notes': """
+source: https://github.com/MetaMask/slip44/blob/main/slip44.json
+"""
+	}
+}
+
+cfg = Config(opts_data=opts_data)
+
+def curve_clsname_abbr(s):
+	return {
+		'Bip32Slip10Secp256k1':      'x',
+		'Bip32Slip10Ed25519':        'edw',
+		'Bip32Slip10Ed25519Blake2b': 'blk',
+		'Bip32Slip10Nist256p1':      'nist',
+		'Bip32KholawEd25519':        'khol',
+	}.get(s,s)
+
+fs2 = '{:5} {:2} {:16} {:8} {:8} {:6} {:8} {:8}'
+hdr2 = fs2.format('CURVE','NW','ADDR_CLS','VB_PRV','VB_PUB','VB_WIF','VB_ADDR','DFL_PATH')
+
+dfl_vb_prv = '0488ade4'
+dfl_vb_pub = '0488b21e'
+dfl_curve = 'secp'
+dfl_dfl_path = "0'/0/0"
+
+def get_bip_utils_data(bipnum,n):
+	name,v = bip_utils_data[bipnum][n]
+	#pexit(v.__dict__)
+	vb_prv = v.m_key_net_ver.m_priv_net_ver.hex()
+	vb_pub = v.m_key_net_ver.m_pub_net_ver.hex()
+	ap = v.m_addr_params
+	return fs2.format(
+		curve_clsname_abbr(v.m_bip32_cls.__name__),
+		'T' if v.m_is_testnet else 'm',
+		v.m_addr_cls.__name__.removesuffix('AddrEncoder'),
+		'x' if vb_prv == dfl_vb_prv else vb_prv,
+		'x' if vb_pub == dfl_vb_pub else vb_pub,
+		v.m_wif_net_ver.hex() if isinstance(v.m_wif_net_ver,bytes) else '-',
+		ap['net_ver'].hex() if 'net_ver' in ap else 'h:'+ap['hrp'] if 'hrp' in ap else 'spec' if ap else '-',
+		'x' if v.m_def_path == dfl_dfl_path else v.m_def_path,
+	)
+
+def gen():
+
+	def format_data(bipnum,n,sym,name):
+		return fs.format(
+			n,
+			sym if sym else '---',
+			get_bip_utils_data(bipnum,n) if bipnum else '-',
+			name if name else '---')
+
+	fs = '{:<6} {:6} {:1} {}'
+
+	yield f'[defaults]'
+	yield fs.format('IDX','CHAIN',hdr2,'NAME')
+	yield fs.format('0', '-', fs2.format(dfl_curve, '-', '-', dfl_vb_prv, dfl_vb_pub, '-', '-', dfl_dfl_path), '-')
+
+	yield f'\n[bip-44]'
+	yield fs.format('IDX','CHAIN',hdr2,'NAME')
+	for k,v in slip44_data.items():
+		if int(k) in bip_utils_data[44]:
+			yield format_data(44,int(k),v['symbol'],v['name'])
+
+	for bipnum in (49, 84, 86):
+		yield f'\n[bip-{bipnum}]'
+		yield fs.format('IDX','CHAIN',hdr2,'NAME')
+		for n,v in sorted(bip_utils_data[bipnum].items()):
+			nd = v[1].m_coin_names
+			yield format_data(bipnum,n,nd.m_abbr,nd.m_name)
+
+	yield f'\n[bip-44-unsupported]'
+	yield fs.format('IDX','CHAIN','','NAME')
+	for k,v in slip44_data.items():
+		if not int(k) in bip_utils_data[44]:
+			yield format_data(None,int(k),v['symbol'],v['name'])
+
+def main():
+
+	global slip44_data, bip_utils_data
+
+	if len(cfg._args) != 1:
+		cfg._opts.usage()
+
+	with open(cfg._args[0]) as fh:
+		slip44_data = json.loads(fh.read())
+
+	bip_utils_data = {
+		n:{v.m_coin_idx:(k,v) for k,v in globals()[f'Bip{n}Conf'].__dict__.items() if not k.startswith('_')}
+			for n in (44, 49, 84, 86)
+	}
+
+	print('\n'.join(gen()))
+
+launch(func=main)

+ 2 - 1
setup.cfg

@@ -35,7 +35,7 @@ classifiers  =
 	Development Status :: 5 - Production/Stable
 
 [options]
-python_requires = >=3.8
+python_requires = >=3.9
 include_package_data = True
 
 install_requires =
@@ -53,6 +53,7 @@ install_requires =
 packages =
 	mmgen
 	mmgen.altcoin
+	mmgen.bip_hd
 	mmgen.contrib
 	mmgen.data
 	mmgen.help

+ 14 - 0
test/cmdtest_py_d/ct_misc.py

@@ -34,6 +34,7 @@ class CmdTestDev(CmdTestBase):
 	networks = ('btc',)
 	cmd_group = (
 		('compute_file_chksum', 'scripts/compute-file-chksum.py'),
+		('create_bip_hd_chain_params', 'scripts/create-bip-hd-chain-params.py'),
 	)
 	tmpdir_nums = [99]
 	color = True
@@ -46,6 +47,15 @@ class CmdTestDev(CmdTestBase):
 		t.expect('3df942')
 		return t
 
+	def create_bip_hd_chain_params(self):
+		t = self._spawn('scripts/create-bip-hd-chain-params.py', ['test/ref/altcoin/slip44-mini.json'])
+		t.expect('[defaults]')
+		t.expect(r"secp.*0488ade4.*0488b21e.*0'\/0\/0",regex=True)
+		t.expect('[bip-44]')
+		t.expect('[bip-49]')
+		t.match_expect_list(['0','BTC','x','m','P2SH','049d7878','049d7cb2','80','05','x','Bitcoin','1'])
+		return t
+
 class CmdTestMisc(CmdTestBase):
 	'miscellaneous tests (RPC backends, xmrwallet_txview, term)'
 	networks = ('btc',)
@@ -56,6 +66,7 @@ class CmdTestMisc(CmdTestBase):
 		('xmrwallet_txview', "'mmgen-xmrwallet' txview"),
 		('xmrwallet_txlist', "'mmgen-xmrwallet' txlist"),
 		('coin_daemon_info', "'examples/coin-daemon-info.py'"),
+		('examples_bip_hd',  "'examples/bip_hd.py'"),
 		('term_echo',        "term.set('echo')"),
 		('term_cleanup',     'term.register_cleanup()'),
 	)
@@ -87,6 +98,9 @@ class CmdTestMisc(CmdTestBase):
 	def xmrwallet_txlist(self):
 		return self.xmrwallet_txview(op='txlist')
 
+	def examples_bip_hd(self):
+		return self.spawn('examples/bip_hd.py',cmd_dir='.')
+
 	def coin_daemon_info(self):
 		if cfg.no_altcoin:
 			coins = ['btc']

+ 1 - 0
test/ref/altcoin/slip44-mini.json

@@ -0,0 +1 @@
+{"0":{"index":"0","hex":"0x80000000","symbol":"BTC","name":"Bitcoin"},"1":{"index":"1","hex":"0x80000001","symbol":"","name":"Testnet (all coins)"},"2":{"index":"2","hex":"0x80000002","symbol":"LTC","name":"Litecoin"},"3":{"index":"3","hex":"0x80000003","symbol":"DOGE","name":"Dogecoin"},"5":{"index":"5","hex":"0x80000005","symbol":"DASH","name":"Dash"},"7":{"index":"7","hex":"0x80000007","symbol":"NMC","name":"Namecoin"},"99999999":{"index":"99999999","hex":"0x85f5e0ff","symbol":"QKC","name":"QuarkChain"},"608589380":{"index":"608589380","hex":"0xa4465644","symbol":"FVDC","name":"ForumCoin"},"1179993420":{"index":"1179993420","hex":"0xc655454c","symbol":"","name":"Fuel"}}

+ 419 - 0
test/unit_tests_d/ut_bip_hd.py

@@ -0,0 +1,419 @@
+#!/usr/bin/env python3
+#
+# mmgen = Multi-Mode GENerator, a command-line cryptocurrency wallet
+# Copyright (C)2013-2024 The MMGen Project <mmgen@tuta.io>
+# Licensed under the GNU General Public License, Version 3:
+#   https://www.gnu.org/licenses
+# Public project repositories:
+#   https://github.com/mmgen/mmgen-wallet
+#   https://gitlab.com/mmgen/mmgen-wallet
+
+"""
+test.unit_tests_d.ut_bip_hd: bip_hd unit test for the MMGen suite
+"""
+
+from mmgen.color import gray,pink,blue
+from mmgen.util import fmt
+from mmgen.bip_hd import Bip32ExtendedKey,BipHDConfig,BipHDNode,MasterNode,get_chain_params
+
+from ..include.common import cfg,vmsg
+
+# Source: BIP-32
+vectors_bip32 = [
+{
+	'seed': '000102030405060708090a0b0c0d0e0f',
+	"m": {
+		'xpub': 'xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8',
+		'xprv': 'xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi',
+	},
+	"m/0'": {
+		'xpub': 'xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw',
+		'xprv': 'xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7',
+	},
+	"m/0'/1": {
+		'xpub': 'xpub6ASuArnXKPbfEwhqN6e3mwBcDTgzisQN1wXN9BJcM47sSikHjJf3UFHKkNAWbWMiGj7Wf5uMash7SyYq527Hqck2AxYysAA7xmALppuCkwQ',
+		'xprv': 'xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs',
+	},
+	"m/0'/1/2'": {
+		'xpub': 'xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5',
+		'xprv': 'xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptWmT7FwuEzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM',
+	},
+	"m/0'/1/2'/2": {
+		'xpub': 'xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV',
+		'xprv': 'xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334',
+	},
+	"m/0'/1/2'/2/1000000000": {
+		'xpub': 'xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy',
+		'xprv': 'xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76',
+	},
+},{
+	'seed': 'fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542',
+	'm': {
+		'xpub': 'xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB',
+		'xprv': 'xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U',
+	},
+	"m/0": {
+		'xpub': 'xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH',
+		'xprv': 'xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt',
+	},
+	"m/0/2147483647'": {
+		'xpub': 'xpub6ASAVgeehLbnwdqV6UKMHVzgqAG8Gr6riv3Fxxpj8ksbH9ebxaEyBLZ85ySDhKiLDBrQSARLq1uNRts8RuJiHjaDMBU4Zn9h8LZNnBC5y4a',
+		'xprv': 'xprv9wSp6B7kry3Vj9m1zSnLvN3xH8RdsPP1Mh7fAaR7aRLcQMKTR2vidYEeEg2mUCTAwCd6vnxVrcjfy2kRgVsFawNzmjuHc2YmYRmagcEPdU9',
+	},
+	"m/0/2147483647'/1": {
+		'xpub': 'xpub6DF8uhdarytz3FWdA8TvFSvvAh8dP3283MY7p2V4SeE2wyWmG5mg5EwVvmdMVCQcoNJxGoWaU9DCWh89LojfZ537wTfunKau47EL2dhHKon',
+		'xprv': 'xprv9zFnWC6h2cLgpmSA46vutJzBcfJ8yaJGg8cX1e5StJh45BBciYTRXSd25UEPVuesF9yog62tGAQtHjXajPPdbRCHuWS6T8XA2ECKADdw4Ef',
+	},
+	"m/0/2147483647'/1/2147483646'": {
+		'xpub': 'xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL',
+		'xprv': 'xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc',
+	},
+	"m/0/2147483647'/1/2147483646'/2": {
+		'xpub': 'xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt',
+		'xprv': 'xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j',
+	},
+},{
+	'comment': 'These vectors test for the retention of leading zeros. See bitpay/bitcore-lib#47 and iancoleman/bip39#58 for more information.',
+	'seed': '4b381541583be4423346c643850da4b320e46a87ae3d2a4e6da11eba819cd4acba45d239319ac14f863b8d5ab5a0d0c64d2e8a1e7d1457df2e5a3c51c73235be',
+	'm': {
+		'xpub': 'xpub661MyMwAqRbcEZVB4dScxMAdx6d4nFc9nvyvH3v4gJL378CSRZiYmhRoP7mBy6gSPSCYk6SzXPTf3ND1cZAceL7SfJ1Z3GC8vBgp2epUt13',
+		'xprv': 'xprv9s21ZrQH143K25QhxbucbDDuQ4naNntJRi4KUfWT7xo4EKsHt2QJDu7KXp1A3u7Bi1j8ph3EGsZ9Xvz9dGuVrtHHs7pXeTzjuxBrCmmhgC6',
+	},
+	"m/0'": {
+		'xpub': 'xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y',
+		'xprv': 'xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L',
+	},
+},{
+	'comment': 'These vectors test for the retention of leading zeros. See btcsuite/btcutil#172 for more information.',
+	'seed': '3ddd5602285899a946114506157c7997e5444528f3003f6134712147db19b678',
+	"m": {
+		'xpub': 'xpub661MyMwAqRbcGczjuMoRm6dXaLDEhW1u34gKenbeYqAix21mdUKJyuyu5F1rzYGVxyL6tmgBUAEPrEz92mBXjByMRiJdba9wpnN37RLLAXa',
+		'xprv': 'xprv9s21ZrQH143K48vGoLGRPxgo2JNkJ3J3fqkirQC2zVdk5Dgd5w14S7fRDyHH4dWNHUgkvsvNDCkvAwcSHNAQwhwgNMgZhLtQC63zxwhQmRv',
+	},
+	"m/0'": {
+		'xpub': 'xpub69AUMk3qDBi3uW1sXgjCmVjJ2G6WQoYSnNHyzkmdCHEhSZ4tBok37xfFEqHd2AddP56Tqp4o56AePAgCjYdvpW2PU2jbUPFKsav5ut6Ch1m',
+		'xprv': 'xprv9vB7xEWwNp9kh1wQRfCCQMnZUEG21LpbR9NPCNN1dwhiZkjjeGRnaALmPXCX7SgjFTiCTT6bXes17boXtjq3xLpcDjzEuGLQBM5ohqkao9G',
+	},
+	"m/0'/1'": {
+		'xpub': 'xpub6BJA1jSqiukeaesWfxe6sNK9CCGaujFFSJLomWHprUL9DePQ4JDkM5d88n49sMGJxrhpjazuXYWdMf17C9T5XnxkopaeS7jGk1GyyVziaMt',
+		'xprv': 'xprv9xJocDuwtYCMNAo3Zw76WENQeAS6WGXQ55RCy7tDJ8oALr4FWkuVoHJeHVAcAqiZLE7Je3vZJHxspZdFHfnBEjHqU5hG1Jaj32dVoS6XLT1',
+	},
+}]
+
+# Source: BIP-32
+# These vectors test that invalid extended keys are recognized as invalid.
+vectors_bip32_invalid = [
+	('xpub661MyMwAqRbcEYS8w7XLSVeEsBXy79zSzH1J8vCdxAZningWLdN3zgtU6LBpB85b3D2yc8sfvZU521AAwdZafEz7mnzBBsz4wKY5fTtTQBm', 'pubkey version / prvkey mismatch'),
+	('xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzFGTQQD3dC4H2D5GBj7vWvSQaaBv5cxi9gafk7NF3pnBju6dwKvH', 'prvkey version / pubkey mismatch'),
+	('xpub661MyMwAqRbcEYS8w7XLSVeEsBXy79zSzH1J8vCdxAZningWLdN3zgtU6Txnt3siSujt9RCVYsx4qHZGc62TG4McvMGcAUjeuwZdduYEvFn', 'invalid pubkey prefix 04'),
+	('xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzFGpWnsj83BHtEy5Zt8CcDr1UiRXuWCmTQLxEK9vbz5gPstX92JQ', 'invalid prvkey prefix 04'),
+	('xpub661MyMwAqRbcEYS8w7XLSVeEsBXy79zSzH1J8vCdxAZningWLdN3zgtU6N8ZMMXctdiCjxTNq964yKkwrkBJJwpzZS4HS2fxvyYUA4q2Xe4', 'invalid pubkey prefix 01'),
+	('xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzFAzHGBP2UuGCqWLTAPLcMtD9y5gkZ6Eq3Rjuahrv17fEQ3Qen6J', 'invalid prvkey prefix 01'),
+	('xprv9s2SPatNQ9Vc6GTbVMFPFo7jsaZySyzk7L8n2uqKXJen3KUmvQNTuLh3fhZMBoG3G4ZW1N2kZuHEPY53qmbZzCHshoQnNf4GvELZfqTUrcv', 'zero depth with non-zero parent fingerprint'),
+	('xpub661no6RGEX3uJkY4bNnPcw4URcQTrSibUZ4NqJEw5eBkv7ovTwgiT91XX27VbEXGENhYRCf7hyEbWrR3FewATdCEebj6znwMfQkhRYHRLpJ', 'zero depth with non-zero parent fingerprint'),
+	('xprv9s21ZrQH4r4TsiLvyLXqM9P7k1K3EYhA1kkD6xuquB5i39AU8KF42acDyL3qsDbU9NmZn6MsGSUYZEsuoePmjzsB3eFKSUEh3Gu1N3cqVUN', 'zero depth with non-zero index'),
+	('xpub661MyMwAuDcm6CRQ5N4qiHKrJ39Xe1R1NyfouMKTTWcguwVcfrZJaNvhpebzGerh7gucBvzEQWRugZDuDXjNDRmXzSZe4c7mnTK97pTvGS8', 'zero depth with non-zero index'),
+	('DMwo58pR1QLEFihHiXPVykYB6fJmsTeHvyTp7hRThAtCX8CvYzgPcn8XnmdfHGMQzT7ayAmfo4z3gY5KfbrZWZ6St24UVf2Qgo6oujFktLHdHY4', 'unknown extended key version'),
+	('DMwo58pR1QLEFihHiXPVykYB6fJmsTeHvyTp7hRThAtCX8CvYzgPcn8XnmdfHPmHJiEDXkTiJTVV9rHEBUem2mwVbbNfvT2MTcAqj3nesx8uBf9', 'unknown extended key version'),
+	('xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzF93Y5wvzdUayhgkkFoicQZcP3y52uPPxFnfoLZB21Teqt1VvEHx', 'private key 0 not in 1..n-1'),
+	('xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzFAzHGBP2UuGCqWLTAPLcMtD5SDKr24z3aiUvKr9bJpdrcLg1y3G', 'private key n not in 1..n-1'),
+	('xpub661MyMwAqRbcEYS8w7XLSVeEsBXy79zSzH1J8vCdxAZningWLdN3zgtU6Q5JXayek4PRsn35jii4veMimro1xefsM58PgBMrvdYre8QyULY', 'invalid pubkey 02000000...07'),
+	('xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHL', 'invalid checksum'),
+]
+
+# Source: bip_utils
+vectors_derive = {
+	'bech32': {
+		0: 'bc1qwg77fxw0tkmc3h58tcnnpegxk7mp3h6ly44d3n',
+		1: 'bc1q6g79y6kwpkufevv2njacvnqnsdxmen68jyvjde',
+		2: 'bc1qknujpwlxc9e9e6avz50q5k90p552xy8g3qjd8u',
+	}
+}
+
+# Source: bip_utils
+vectors_addrfmt = {
+	'pub': {
+		'compressed': 'xpub6GJknXsmpFcEubJsddGacncHhyY5Bk9zMQKC8pC97vBVAphrchYxuoJsAqfZW2uEMfPr6umSPRrhuaA7zeuExkwuAWiUcKcXjSf437VMLwR',
+		'segwit': 'ypub6aNved9dRKbMRjLfMbGPoXKgNN1tr86qi8WoBxSVr4mHX9fdzUxJFtEH63QZZxArk4f2fwFZQUQ7FRkNiBarTLu2Y69SRxzn68WysngXPrp',
+		'bech32': 'zpub6urFg31yVogtpf3Y7aV3CLuxQUsEpZ2asqBx3fzYMuFRTFrMNKxn5B2QXAGMSuwVfEA9KSJr2CUs8vqbmhUCkzVvxysB4p6vybLS2CgQnze',
+	},
+	'prv': {
+		'compressed': 'xprvA3KQP2Lsyt3wh7EQXbjaFefZ9whanHS8zBPbLRnXZaeWJ2Ni5AEiMzzPKbKHsG7Dn6hkhSkG8V4H4XUsjszxU4nd2sMnc5ag9sHLLYBqrr4',
+		'segwit': 'yprvAMPaF7cjax34DFGCFZjPSPNwpLBQSfNzLubCPa2tHjEJeMLVSwe3i5uoEnUg4etxP3XEqr5ZJinjGkUJrte3xFNZ1jVKbjVaFJVHi4Msekw',
+		'bech32': 'zprvAgruGXV5fS8bcAy51Yx2qCyDrT2kR6JjWcGMFHavoZiSaTXCpneXXNhvfsMLeyBmjzACqkpxB2KGCMAe85wUrW1dnenu6kVHCk4kXh7XFE6',
+	}
+}
+
+# Source: Asgardex Wallet
+vectors_multicoin = {
+	'btc_bech32':   'bc1qwg77fxw0tkmc3h58tcnnpegxk7mp3h6ly44d3n',
+	'eth':          '373731f4d885Fc7Da05498F9f0804a87A14F891b',
+	'doge':         'DFX88RXpi4S4W24YVvuMgbdUcCAYNeEYGd',
+	'avax-c':       '0x373731f4d885Fc7Da05498F9f0804a87A14F891b',
+	'ltc_bech32':   'ltc1q3uh5ga5cp9kkdfx6a52uymxj9keq4tpzep7er0',
+	'bch_cashaddr': 'bitcoincash:qpqpcllprftg4s0chdgkpxhxv23wfymq3gj7n0a9vw',
+	'bsc_smart':    '0x373731f4d885Fc7Da05498F9f0804a87A14F891b',
+	'bnb_beacon':   'bnb179c3ymltqm4utlp089zxqeta5dvn48a305rhe5',
+}
+
+def wif2addr(cfg,wif):
+	from mmgen.tool.coin import tool_cmd
+	return tool_cmd(
+		cfg     = cfg.base_cfg,
+		cmdname = 'wif2addr',
+		proto   = cfg.base_cfg._proto,
+		mmtype  = cfg.addr_type).wif2addr(wif)
+
+class unit_tests:
+
+	altcoin_deps = ('multicoin',)
+
+	@property
+	def _seed(self):
+		if not hasattr(self,'__seed'):
+			with open('test/ref/98831F3A.bip39') as fh:
+				mnemonic = fh.read().strip()
+			from mmgen.bip39 import bip39
+			self.__seed = bip39().generate_seed(mnemonic.split())
+		return self.__seed
+
+	def chainparams(self,name,ut):
+		for bipnum,idx,chain,addr_cls in (
+				(44, 0,  'btc',  'P2PKH'),
+				(49, 0,  'btc',  'P2SH'),
+				(84, 0,  'btc',  'P2WPKH'),
+				(44, 60, 'eth',  'Eth'),
+				(44, 61, 'etc',  'Eth'),
+				(44, 2,  'ltc',  'P2PKH'),
+				(44, 3,  'doge', 'P2PKH'),
+			):
+			res = get_chain_params(bipnum,chain)
+			assert res.idx == idx, res.idx
+			assert res.chain == chain.upper()
+			assert res.addr_cls == addr_cls
+			vmsg(f'  {res}')
+		vmsg('')
+		return True
+
+	def derive(self,name,ut):
+		vmsg('seed: 98831F3A (default derivation)')
+
+		m = MasterNode(cfg,self._seed)
+
+		purpose = m.init_cfg(coin='btc',addr_type='bech32').derive_private()
+		vmsg(f'  {purpose.address=}')
+
+		coin_type1 = purpose.derive_private()
+
+		coin_type2 = m.to_coin_type('btc',addr_type='bech32')
+		assert coin_type1.address == coin_type2.address
+		vmsg(f'  {coin_type1.address=}')
+
+		acct = coin_type2.derive_private(idx=0)
+		chain1 = acct.derive_private(idx=0,hardened=False)
+
+		chain2 = m.to_chain(idx=0,coin='btc',addr_type='bech32',public=False)
+		assert chain2.address == chain1.address
+
+		chain3 = m.to_coin_type(coin='btc',addr_type='bech32').to_chain(0,public=True)
+		assert chain3.address == chain1.address
+		vmsg(f'  {chain1.address=}')
+
+		a = BipHDNode.from_extended_key(cfg,'btc',chain2.xpub)
+		b = BipHDNode.from_extended_key(cfg,'btc',chain2.xprv)
+		vmsg(
+			'\n  xpub:\n' +
+			fmt(str(Bip32ExtendedKey(b.xpub)),indent='    ')
+		)
+		assert a.xpub == b.xpub
+
+		vmsg('  Addresses:')
+		for i in range(3):
+			res = chain1.derive_public(i)
+			vmsg(f'    {i} {res.address}')
+			assert res.address == vectors_derive['bech32'][i]
+			res = chain1.derive_private(i)
+			assert res.address == vectors_derive['bech32'][i]
+
+		vmsg('')
+		return True
+
+	def derive_addrfmt(self,name,ut):
+		vmsg('seed: 98831F3A (default derivation)')
+
+		m = MasterNode(cfg,self._seed)
+
+		for addr_type in ('compressed','segwit','bech32'):
+			chk_xpub = vectors_addrfmt['pub'][addr_type]
+			chk_xprv = vectors_addrfmt['prv'][addr_type]
+
+			res1 = m.to_chain(idx=0,coin='btc',addr_type=addr_type).derive_public(0)
+			vmsg(f'  {addr_type}: {res1.xpub}')
+			assert res1.xpub == chk_xpub
+
+			res2 = m.to_chain(idx=0,coin='btc',addr_type=addr_type).derive_private(0,False)
+			vmsg(f'  {addr_type}: {res2.xprv}')
+			assert res2.xprv == chk_xprv
+			assert res2.xpub == chk_xpub
+
+			assert res2.address == wif2addr(res2.cfg, res2.privkey.wif)
+
+		vmsg('')
+		return True
+
+	def path(self,name,ut):
+
+		for vec in vectors_bip32:
+			seed = bytes.fromhex(vec['seed'])
+			vmsg(f'Seed: {vec["seed"]}')
+
+			for n,path_str in enumerate(vec):
+				if path_str in ('seed','comment'):
+					continue
+
+				path_arg = path_str.replace("'",'H') if n % 2 else path_str
+				node = BipHDNode.from_path(cfg,seed,path_arg,no_path_checks=True)
+				vmsg('  Path {} {}'.format(pink(path_str),blue('('+node.desc+')')))
+
+				for xkey_type in ('xpub','xprv'):
+					vmsg(f'    {getattr(node,xkey_type)}')
+					assert getattr(node,xkey_type) == vec[path_str][xkey_type]
+
+			vmsg('')
+
+		return True
+
+	def parse_extended(self,name,ut):
+		vmsg('Parsing and validating extended keys:\n')
+
+		for vec in vectors_bip32:
+			seed = bytes.fromhex(vec['seed'])
+			vmsg(f'  Seed: {vec["seed"]}')
+
+			for n,path_str in enumerate(vec):
+				if path_str in ('seed','comment'):
+					continue
+
+				vmsg('    Path {}'.format(pink(path_str)))
+				for xkey_type in ('xpub','xprv'):
+					xkey = vec[path_str][xkey_type]
+					vmsg(f'      {xkey}')
+					node = BipHDNode.from_extended_key(cfg,'btc',xkey)
+					assert getattr(node,xkey_type) == xkey
+
+			vmsg('')
+
+		return True
+
+	def multicoin(self,name,ut):
+		m = MasterNode(cfg,self._seed)
+
+		fs = '  {:6} {:10} {}'
+		vmsg(fs.format('COIN','ADDR_TYPE','ADDR'))
+		for id_str,addr_chk in vectors_multicoin.items():
+			ss = id_str.split('_')
+			coin = ss[0]
+			addr_type = ss[1] if len(ss) == 2 else None
+			if coin not in BipHDConfig.supported_coins:
+				vmsg(gray(fs.format(coin.upper(), (addr_type or ''), '[not supported yet]')))
+				continue
+			node = m.to_chain(idx=0,coin=coin,addr_type=addr_type).derive_private(0)
+			xpub_parsed = node.key_extended(public=True)
+			xprv_parsed = node.key_extended(public=False)
+			addr = node.address
+			at_arg = 'compressed' if coin == 'doge' else None
+			from_xpub = BipHDNode.from_extended_key(node.cfg.base_cfg, coin, xpub_parsed.base58, addr_type=at_arg)
+			from_xprv = BipHDNode.from_extended_key(node.cfg.base_cfg, coin, xprv_parsed.base58, addr_type=at_arg)
+			assert from_xpub.xpub == node.xpub, f'{from_xpub.xpub=} != {node.xpub}'
+			assert from_xprv.xpub == node.xpub, f'{from_xprv.xpub=} != {node.xpub}'
+			assert from_xpub.address == addr, f'{from_xpub.address} != {addr}'
+			assert from_xprv.address == addr, f'{from_xprv.address} != {addr}'
+			addr_from_wif = wif2addr(node.cfg, node.privkey.wif)
+			proto = node.cfg.base_cfg._proto
+			if proto.base_proto == 'Ethereum':
+				addr = proto.checksummed_addr(node.address)
+				addr_from_wif = proto.checksummed_addr(addr_from_wif)
+			vmsg(fs.format(coin.upper(), (addr_type or 'auto'), addr))
+			assert addr == addr_chk, f'{addr} != {addr_chk}'
+			assert addr == addr_from_wif, f'{addr} != {addr_from_wif}'
+
+		vmsg('')
+		return True
+
+	def errors(self,name,ut):
+		vmsg('Checking error handling:')
+
+		m = MasterNode(cfg,self._seed)
+		m_btc = m.init_cfg(coin='btc',addr_type='bech32')
+
+		purpose = m_btc.derive_private()
+		coin_type = purpose.derive_private()
+		acct = coin_type.derive_private(idx=0)
+		chain = acct.derive_private(idx=0,hardened=False)
+
+		def bad01():
+			m.to_chain(idx=0,coin='erq',addr_type='C')
+		def bad02():
+			m_btc.derive_private(idx=0)
+		def bad03():
+			m_btc.derive_private(hardened=False)
+		def bad04():
+			purpose.derive_private(idx=8)
+		def bad05():
+			purpose.derive_private(hardened=False)
+		def bad06():
+			coin_type.derive_private() # no acct idx
+		def bad08():
+			m_btc.derive_public() # must be private
+		def bad09():
+			coin_type.derive_private(idx=8,hardened=False)
+		def bad10():
+			acct.derive_private()
+		def bad11():
+			chain.derive_private()
+		def bad12():
+			chain.derive_private(hardened=True,idx=3)
+
+		bad_data = (
+			('unsupported coin',                       'ValueError', 'not supported',        bad01),
+			('depth 1 (purpose):   idx not None',      'ValueError', 'index for path comp',  bad02),
+			('depth 1 (purpose):   hardened False',    'ValueError', 'value for ‘hardened’', bad03),
+			('depth 2 (coin type): idx mismatch',      'ValueError', 'index 8 at depth',     bad04),
+			('depth 2 (coin type): hardened False',    'ValueError', 'value for ‘hardened’', bad05),
+			('depth 3 (account):   idx not set',       'ValueError', 'must be set',          bad06),
+			('depth 1 (purpose):   node not hardened', 'ValueError', 'must be hardened',     bad08),
+			('depth 3 (account):   node not hardened', 'ValueError', 'value for ‘hardened’', bad09),
+			('depth 4 (chain):     idx not set',       'ValueError', 'must be either 0',     bad10),
+			('depth 5 (leaf node): idx not set',       'ValueError', 'must be set',          bad11),
+			('depth 5 (leaf node): hardened True',     'ValueError', 'must be None',         bad12),
+		)
+
+		ut.process_bad_data(bad_data,pfx='')
+		vmsg('')
+		return True
+
+	def parse_extended_errors(self,name,ut):
+		vmsg('Parsing and validating invalid extended keys:')
+		vec = vectors_bip32_invalid
+		func = [lambda m=n: BipHDNode.from_extended_key(cfg,'btc',vec[m][0]) for n in range(len(vec))]
+		exc = (
+			'first byte for public',
+			'first byte for private',
+			'first byte for public',
+			'first byte for private',
+			'first byte for public',
+			'first byte for private',
+			'non-zero parent fingerprint',
+			'non-zero parent fingerprint',
+			'non-zero index',
+			'non-zero index',
+			'unrecognized extended key v',
+			'unrecognized extended key v',
+			'private key is zero!',
+			'private key >= group order!',
+			'Public key could not be parsed', # extmod
+			'incorrect checksum',
+		)
+		ut.process_bad_data([(vec[n][1], 'ValueError', exc[n], func[n]) for n in range(len(vec))],pfx='')
+		vmsg('')
+		return True