__init__.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505
  1. #!/usr/bin/env python3
  2. #
  3. # MMGen Wallet, a terminal-based cryptocurrency wallet
  4. # Copyright (C)2013-2025 The MMGen Project <mmgen@tuta.io>
  5. # Licensed under the GNU General Public License, Version 3:
  6. # https://www.gnu.org/licenses
  7. # Public project repositories:
  8. # https://github.com/mmgen/mmgen-wallet
  9. # https://gitlab.com/mmgen/mmgen-wallet
  10. """
  11. bip_hd: BIP-44/49/84, SLIP-44 hierarchical-deterministic key derivation library
  12. """
  13. # One motivation for this implementation:
  14. # https://blog.unit410.com/bitcoin/bip32/bip39/kdf/2021/05/17/inconsistent-bip32-derivations.html
  15. import hmac
  16. from ..cfg import Config
  17. from ..util import is_int, fmt
  18. from ..base_obj import Lockable
  19. from ..keygen import KeyGenerator, keygen_public_data
  20. from ..addrgen import AddrGenerator
  21. from ..addr import MMGenAddrType
  22. from ..key import PrivKey
  23. from ..protocol import CoinProtocol
  24. from ..proto.btc.common import hash160, b58chk_encode, b58chk_decode
  25. from ..proto.secp256k1.secp256k1 import pubkey_tweak_add, pubkey_check
  26. from . import chainparams
  27. chainparams_data = chainparams.parse_data()
  28. secp256k1_order = CoinProtocol.Secp256k1.secp256k1_group_order
  29. hardened_idx0 = 0x80000000
  30. def get_chain_params(bipnum, chain):
  31. return chainparams_data[f'bip-{bipnum}'][chain.upper()]
  32. def get_version_bytes(bip_proto, coin, public):
  33. return getattr(
  34. chainparams_data[f'bip-{bip_proto}'][coin],
  35. 'vb_pub' if public else 'vb_prv')
  36. def parse_version_bytes(vb_hex):
  37. e = chainparams_data['defaults']
  38. if vb_hex in (e.vb_pub, e.vb_prv):
  39. return (None, e)
  40. for bipnum in (49, 84, 86, 44): # search bip-44 last, since it has the most entries
  41. for e in chainparams_data[f'bip-{bipnum}'].values():
  42. if vb_hex in (e.vb_pub, e.vb_prv):
  43. return (bipnum, e)
  44. raise ValueError(f'0x{vb_hex}: unrecognized extended key version bytes')
  45. def compress_pubkey(pubkey_bytes):
  46. # see: proto.secp256k1.keygen.pubkey_format()
  47. return (b'\x02', b'\x03')[pubkey_bytes[-1] & 1] + pubkey_bytes[1:33]
  48. def decompress_pubkey(pubkey_bytes):
  49. import ecdsa
  50. return b'\x04' + ecdsa.VerifyingKey.from_string(pubkey_bytes, curve=ecdsa.curves.SECP256k1).to_string()
  51. class Bip32ExtendedKey(Lockable):
  52. def __init__(self, key_b58):
  53. try:
  54. key = b58chk_decode(key_b58)
  55. except Exception as e:
  56. raise type(e)(f'invalid extended key: {e}')
  57. assert len(key) == 78, f'len(key) == {len(key)} (not 78)'
  58. # Serialization:
  59. # ver_bytes | depth | par_print | idx | chaincode | serialized_key
  60. # 0:4 (4) | 4 (1) | 5:9 (4) | 9:13 (4) | 13:45 (32) | 45(46): 33(32)
  61. ver_hex = key[:4].hex()
  62. bipnum, cp_entry = parse_version_bytes(ver_hex)
  63. public = ver_hex == cp_entry.vb_pub
  64. idx_raw = int.from_bytes(key[9:13], byteorder='big')
  65. self.base58 = key_b58
  66. self.ver_bytes = key[:4]
  67. self.depth = key[4]
  68. self.par_print = key[5:9]
  69. self.idx = idx_raw if idx_raw < hardened_idx0 else idx_raw - hardened_idx0
  70. self.chaincode = key[13:45]
  71. self.key = key[45 if public else 46:]
  72. self.hardened = idx_raw >= hardened_idx0 or self.depth == 0
  73. self.bip_proto = bipnum or 44
  74. self.network = cp_entry.network if bipnum else 'mainnet'
  75. self.public = public
  76. self.coin = cp_entry.chain if bipnum and cp_entry.chain != 'BTC' else '-'
  77. if self.public:
  78. if not key[45] in (2, 3):
  79. raise ValueError(f'0x{key[45]:02x}: invalid first byte for public key data (not 2 or 3)')
  80. elif key[45]:
  81. raise ValueError(f'0x{key[45]:02x}: invalid first byte for private key data (not zero)')
  82. if self.depth == 0:
  83. if self.par_print != bytes(4):
  84. raise ValueError(f'{self.par_print.hex()}: non-zero parent fingerprint at depth 0')
  85. if idx_raw:
  86. raise ValueError(f'{idx_raw}: non-zero index at depth 0')
  87. def __str__(self):
  88. return fmt(f"""
  89. base58: {self.base58}
  90. ver_bytes: {self.ver_bytes.hex()}
  91. depth: {self.depth} [{bip_hd_nodes[self.depth].desc}]
  92. par_print: {self.par_print.hex()}
  93. idx: {self.idx}
  94. chaincode: {self.chaincode.hex()}
  95. key: {self.key.hex()}
  96. hardened: {self.hardened}
  97. bip_proto: {self.bip_proto}
  98. network: {self.network}
  99. public: {self.public}
  100. coin: {self.coin}
  101. """)
  102. def get_bip_by_addr_type(addr_type):
  103. return (
  104. 84 if addr_type.name == 'bech32' else
  105. 49 if addr_type.name == 'segwit' else
  106. 44)
  107. def check_privkey(key_int):
  108. if key_int == 0:
  109. raise ValueError('private key is zero!')
  110. elif key_int >= secp256k1_order:
  111. raise ValueError(f'{key_int:x}: private key >= group order!')
  112. class BipHDConfig(Lockable):
  113. supported_coins = ('btc', 'eth', 'doge', 'ltc', 'bch')
  114. def __init__(self, base_cfg, coin, network, addr_type, from_path, no_path_checks):
  115. if not coin.lower() in self.supported_coins:
  116. raise ValueError(f'bip_hd: coin {coin.upper()} not supported')
  117. base_cfg = Config({
  118. '_clone': base_cfg,
  119. 'coin': coin,
  120. 'network': network,
  121. 'type': addr_type or None,
  122. 'quiet': True
  123. })
  124. dfl_type = base_cfg._proto.dfl_mmtype
  125. addr_type = MMGenAddrType(
  126. proto = base_cfg._proto,
  127. id_str = base_cfg.type or ('C' if dfl_type == 'L' else dfl_type))
  128. self.base_cfg = base_cfg
  129. self.addr_type = addr_type
  130. self.kg = KeyGenerator(base_cfg, base_cfg._proto, addr_type.pubkey_type)
  131. self.ag = AddrGenerator(base_cfg, base_cfg._proto, addr_type)
  132. self.bip_proto = get_bip_by_addr_type(addr_type)
  133. self.from_path = from_path
  134. self.no_path_checks = no_path_checks
  135. class MasterNode(Lockable):
  136. desc = 'Unconfigured Bip32 Master Node'
  137. _use_class_attr = True
  138. def __init__(self, base_cfg, bytes_data):
  139. H = hmac.digest(b'Bitcoin seed', bytes_data, 'sha512')
  140. self.par_print = bytes(4)
  141. self.depth = 0
  142. self.key = H[:32]
  143. self.chaincode = H[32:]
  144. self.idx = 0
  145. self.hardened = True
  146. self.public = False
  147. self.base_cfg = base_cfg
  148. check_privkey(int.from_bytes(self.key, byteorder='big'))
  149. def init_cfg(self, coin=None, network=None, addr_type=None, from_path=False, no_path_checks=False):
  150. new = BipHDNodeMaster()
  151. new.cfg = BipHDConfig(self.base_cfg, coin, network, addr_type, from_path, no_path_checks)
  152. new.par_print = self.par_print
  153. new.depth = self.depth
  154. new.key = self.key
  155. new.chaincode = self.chaincode
  156. new.idx = self.idx
  157. new.hardened = self.hardened
  158. new.public = self.public
  159. new._lock()
  160. return new
  161. def to_coin_type(self, coin=None, network=None, addr_type=None):
  162. return self.init_cfg(coin, network, addr_type).to_coin_type()
  163. def to_chain(self, idx, coin=None, network=None, addr_type=None, hardened=False, public=False):
  164. return self.init_cfg(coin, network, addr_type).to_chain(
  165. idx = idx,
  166. hardened = hardened,
  167. public = public)
  168. class BipHDNode(Lockable):
  169. _autolock = False
  170. _generated_pubkey = None
  171. _set_ok = ('_generated_pubkey',)
  172. def check_param(self, name, val):
  173. cls = type(self)
  174. if val is None:
  175. if not hasattr(cls, name):
  176. raise ValueError(f'‘{name}’ at depth {self.depth} ({self.desc!r}) must be set')
  177. elif hasattr(cls, name) and val != getattr(cls, name):
  178. raise ValueError(
  179. '{}: invalid value for ‘{}’ at depth {} ({!r}) (must be {})'.format(
  180. val, name, self.depth, self.desc,
  181. 'None' if getattr(cls, name) is None else f'None or {getattr(cls, name)}')
  182. )
  183. def set_params(self, cfg, idx, hardened):
  184. self.check_param('idx', idx)
  185. self.check_param('hardened', hardened)
  186. return (
  187. type(self).idx if idx is None else idx,
  188. type(self).hardened if hardened is None else hardened)
  189. @property
  190. def privkey(self):
  191. assert not self.public
  192. return PrivKey(
  193. self.cfg.base_cfg._proto,
  194. self.key,
  195. compressed = self.cfg.addr_type.compressed,
  196. pubkey_type = self.cfg.addr_type.pubkey_type)
  197. @property
  198. def pubkey_bytes(self):
  199. if self.public:
  200. return self.key
  201. elif self.cfg.addr_type.compressed:
  202. return self.priv2pub().pubkey
  203. else:
  204. return compress_pubkey(self.priv2pub().pubkey)
  205. def priv2pub(self):
  206. if not self._generated_pubkey:
  207. self._generated_pubkey = self.cfg.kg.gen_data(self.privkey)
  208. return self._generated_pubkey
  209. @property
  210. def address(self):
  211. return self.cfg.ag.to_addr(
  212. keygen_public_data(
  213. pubkey = self.key if self.cfg.addr_type.compressed else decompress_pubkey(self.key),
  214. viewkey_bytes = None,
  215. pubkey_type = self.cfg.addr_type.pubkey_type,
  216. compressed = self.cfg.addr_type.compressed)
  217. if self.public else
  218. self.priv2pub()
  219. )
  220. # Extended keys can be identified by the Hash160 (RIPEMD160 after SHA256) of the serialized ECDSA
  221. # public key K, ignoring the chain code. This corresponds exactly to the data used in traditional
  222. # Bitcoin addresses. It is not advised to represent this data in base58 format though, as it may be
  223. # interpreted as an address that way (and wallet software is not required to accept payment to the
  224. # chain key itself).
  225. @property
  226. def id(self):
  227. return hash160(self.pubkey_bytes)
  228. # The first 32 bits of the identifier are called the key fingerprint.
  229. @property
  230. def fingerprint(self):
  231. return self.id[:4]
  232. @property
  233. def xpub(self):
  234. return self.key_extended(public=True, as_str=True)
  235. @property
  236. def xprv(self):
  237. return self.key_extended(public=False, as_str=True)
  238. def key_extended(self, public, as_str=False):
  239. if self.public and not public:
  240. raise ValueError('cannot create extended private key for public node!')
  241. ret = b58chk_encode(
  242. bytes.fromhex(get_version_bytes(self.cfg.bip_proto, self.cfg.base_cfg.coin, public))
  243. + int.to_bytes(self.depth, length=1, byteorder='big')
  244. + self.par_print
  245. + int.to_bytes(
  246. self.idx + (hardened_idx0 if self.hardened and self.depth else 0),
  247. length = 4,
  248. byteorder = 'big')
  249. + self.chaincode
  250. + (self.pubkey_bytes if public else b'\x00' + self.key)
  251. )
  252. return ret if as_str else Bip32ExtendedKey(ret)
  253. def derive_public(self, idx=None):
  254. return self.derive(idx=idx, hardened=False, public=True)
  255. def derive_private(self, idx=None, hardened=None):
  256. return self.derive(idx=idx, hardened=hardened, public=False)
  257. def derive(self, idx, hardened, public):
  258. if self.public and not public:
  259. raise ValueError('cannot derive private node from public node!')
  260. new = bip_hd_nodes[self.depth + 1]()
  261. new.depth = self.depth + 1
  262. new.cfg = self.cfg
  263. new.par_print = self.fingerprint
  264. new.public = public
  265. if new.cfg.no_path_checks:
  266. new.idx, new.hardened = (idx, hardened)
  267. else:
  268. if new.public and type(new).hardened:
  269. raise ValueError(
  270. f'‘public’ requested, but node of depth {new.depth} ({new.desc}) must be hardened!')
  271. new.idx, new.hardened = new.set_params(new.cfg, idx, hardened)
  272. key_in = b'\x00' + self.key if new.hardened else self.pubkey_bytes
  273. I = hmac.digest(
  274. self.chaincode,
  275. key_in + ((hardened_idx0 if new.hardened else 0) + new.idx).to_bytes(length=4, byteorder='big'),
  276. 'sha512')
  277. pk_addend_bytes = I[:32]
  278. new.chaincode = I[32:]
  279. if new.public:
  280. new.key = pubkey_tweak_add(key_in, pk_addend_bytes) # checks range of pk_addend
  281. else:
  282. pk_addend = int.from_bytes(pk_addend_bytes, byteorder='big')
  283. check_privkey(pk_addend)
  284. key_int = (int.from_bytes(self.key, byteorder='big') + pk_addend) % secp256k1_order
  285. check_privkey(key_int)
  286. new.key = int.to_bytes(key_int, length=32, byteorder='big')
  287. new._lock()
  288. return new
  289. @staticmethod
  290. def from_path(
  291. base_cfg,
  292. seed,
  293. path_str,
  294. coin = None,
  295. addr_type = None,
  296. no_path_checks = False):
  297. path = path_str.lower().split('/')
  298. if path.pop(0) != 'm':
  299. raise ValueError(f'{path_str}: invalid path string (first component is not "m")')
  300. res = MasterNode(base_cfg, seed).init_cfg(
  301. coin = coin or 'btc',
  302. addr_type = addr_type or 'compressed',
  303. no_path_checks = no_path_checks,
  304. from_path = True)
  305. for s in path:
  306. for suf in ("'", 'h'):
  307. if s.endswith(suf):
  308. idx = s.removesuffix(suf)
  309. hardened = True
  310. break
  311. else:
  312. idx = s
  313. hardened = False
  314. if not is_int(idx):
  315. raise ValueError(f'invalid path component {s!r}')
  316. res = res.derive(int(idx), hardened, public=False)
  317. return res
  318. @staticmethod
  319. # ‘addr_type’ is required for broken coins with duplicate version bytes across BIP protocols
  320. # (i.e. Dogecoin)
  321. def from_extended_key(base_cfg, coin, xkey_b58, addr_type=None):
  322. xk = Bip32ExtendedKey(xkey_b58)
  323. if xk.public:
  324. pubkey_check(xk.key)
  325. else:
  326. check_privkey(int.from_bytes(xk.key, byteorder='big'))
  327. addr_types = {
  328. 84: 'bech32',
  329. 49: 'segwit',
  330. 44: None
  331. }
  332. new = bip_hd_nodes[xk.depth]()
  333. new.cfg = BipHDConfig(
  334. base_cfg,
  335. coin,
  336. xk.network,
  337. addr_type or addr_types[xk.bip_proto],
  338. False,
  339. False)
  340. new.par_print = xk.par_print
  341. new.depth = xk.depth
  342. new.key = xk.key
  343. new.chaincode = xk.chaincode
  344. new.idx = xk.idx
  345. new.hardened = xk.hardened
  346. new.public = xk.public
  347. new._lock()
  348. return new
  349. class BipHDNodeMaster(BipHDNode):
  350. desc = 'Bip32 Master Node'
  351. hardened = True
  352. idx = None
  353. def to_coin_type(self):
  354. # purpose coin_type
  355. return self.derive_private().derive_private()
  356. def to_chain(self, idx, hardened=False, public=False):
  357. # purpose coin_type account #0 chain
  358. return self.derive_private().derive_private().derive_private(idx=0).derive(
  359. idx = idx,
  360. hardened = False if public else hardened,
  361. public = public)
  362. class BipHDNodePurpose(BipHDNode):
  363. desc = 'Purpose'
  364. hardened = True
  365. def set_params(self, cfg, idx, hardened):
  366. self.check_param('hardened', hardened)
  367. if idx not in (None, cfg.bip_proto):
  368. raise ValueError(
  369. f'index for path component {self.desc!r} with address type {cfg.addr_type!r} '
  370. f'must be {cfg.bip_proto}, not {idx}')
  371. return (cfg.bip_proto, type(self).hardened)
  372. class BipHDNodeCoinType(BipHDNode):
  373. desc = 'Coin Type'
  374. hardened = True
  375. def set_params(self, cfg, idx, hardened):
  376. self.check_param('hardened', hardened)
  377. chain_idx = get_chain_params(
  378. bipnum = get_bip_by_addr_type(cfg.addr_type),
  379. chain = cfg.base_cfg.coin).idx
  380. if idx not in (None, chain_idx):
  381. raise ValueError(
  382. f'index {idx} at depth {self.depth} ({self.desc}) does not match '
  383. f'chain index {chain_idx} for coin {cfg.base_cfg.coin!r}')
  384. return (chain_idx, type(self).hardened)
  385. def to_chain(self, idx, hardened=False, public=False):
  386. # account #0 chain
  387. return self.derive_private(idx=0).derive(
  388. idx = idx,
  389. hardened = False if public else hardened,
  390. public = public)
  391. class BipHDNodeAccount(BipHDNode):
  392. desc = 'Account'
  393. hardened = True
  394. class BipHDNodeChain(BipHDNode):
  395. desc = 'Chain'
  396. hardened = False
  397. def set_params(self, cfg, idx, hardened):
  398. self.check_param('hardened', hardened)
  399. if idx not in (0, 1):
  400. raise ValueError(
  401. f'at depth {self.depth} ({self.desc}), ‘idx’ must be either 0 (external) or 1 (internal)')
  402. return (idx, type(self).hardened)
  403. class BipHDNodeAddrIdx(BipHDNode):
  404. desc = 'Address Index'
  405. hardened = False
  406. bip_hd_nodes = {
  407. 0: BipHDNodeMaster,
  408. 1: BipHDNodePurpose,
  409. 2: BipHDNodeCoinType,
  410. 3: BipHDNodeAccount,
  411. 4: BipHDNodeChain,
  412. 5: BipHDNodeAddrIdx
  413. }