addr.py 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. #!/usr/bin/env python3
  2. #
  3. # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
  4. # Copyright (C)2013-2022 The MMGen Project <mmgen@tuta.io>
  5. #
  6. # This program is free software: you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation, either version 3 of the License, or
  9. # (at your option) any later version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. """
  19. addr.py: MMGen address-related types
  20. """
  21. from collections import namedtuple
  22. from .objmethods import Hilite,InitErrors,MMGenObject
  23. from .obj import ImmutableAttr,MMGenIdx,HexStr,get_obj
  24. from .seed import SeedID
  25. ati = namedtuple('addrtype_info',
  26. ['name','pubkey_type','compressed','gen_method','addr_fmt','wif_label','extra_attrs','desc'])
  27. class MMGenAddrType(str,Hilite,InitErrors,MMGenObject):
  28. width = 1
  29. trunc_ok = False
  30. color = 'blue'
  31. name = ImmutableAttr(str)
  32. pubkey_type = ImmutableAttr(str)
  33. compressed = ImmutableAttr(bool,set_none_ok=True)
  34. gen_method = ImmutableAttr(str,set_none_ok=True)
  35. addr_fmt = ImmutableAttr(str,set_none_ok=True)
  36. wif_label = ImmutableAttr(str,set_none_ok=True)
  37. extra_attrs = ImmutableAttr(tuple,set_none_ok=True)
  38. desc = ImmutableAttr(str)
  39. mmtypes = {
  40. 'L': ati('legacy', 'std', False,'p2pkh', 'p2pkh', 'wif', (), 'Legacy uncompressed address'),
  41. 'C': ati('compressed','std', True, 'p2pkh', 'p2pkh', 'wif', (), 'Compressed P2PKH address'),
  42. 'S': ati('segwit', 'std', True, 'segwit', 'p2sh', 'wif', (), 'Segwit P2SH-P2WPKH address'),
  43. 'B': ati('bech32', 'std', True, 'bech32', 'bech32', 'wif', (), 'Native Segwit (Bech32) address'),
  44. 'E': ati('ethereum', 'std', False,'ethereum','ethereum','privkey', ('wallet_passwd',),'Ethereum address'),
  45. 'Z': ati('zcash_z','zcash_z',False,'zcash_z', 'zcash_z', 'wif', ('viewkey',), 'Zcash z-address'),
  46. 'M': ati('monero', 'monero', False,'monero', 'monero', 'spendkey',('viewkey','wallet_passwd'),'Monero address'),
  47. }
  48. def __new__(cls,proto,id_str,errmsg=None):
  49. if isinstance(id_str,cls):
  50. return id_str
  51. try:
  52. id_str = id_str.replace('-','_')
  53. for k,v in cls.mmtypes.items():
  54. if id_str in (k,v.name):
  55. if id_str == v.name:
  56. id_str = k
  57. me = str.__new__(cls,id_str)
  58. for k in v._fields:
  59. setattr(me,k,getattr(v,k))
  60. if me not in proto.mmtypes + ('P',):
  61. raise ValueError(f'{me.name!r}: invalid address type for {proto.name} protocol')
  62. me.proto = proto
  63. return me
  64. raise ValueError(f'{id_str}: unrecognized address type for protocol {proto.name}')
  65. except Exception as e:
  66. return cls.init_fail( e,
  67. f"{errmsg or ''}{id_str!r}: invalid value for {cls.__name__} ({e!s})",
  68. preformat = True )
  69. @classmethod
  70. def get_names(cls):
  71. return [v.name for v in cls.mmtypes.values()]
  72. class MMGenPasswordType(MMGenAddrType):
  73. mmtypes = {
  74. 'P': ati('password', 'password', None, None, None, None, None, 'Password generated from MMGen seed')
  75. }
  76. class AddrIdx(MMGenIdx):
  77. max_digits = 7
  78. def is_addr_idx(s):
  79. return get_obj( AddrIdx, n=s, silent=True, return_bool=True )
  80. class AddrListID(str,Hilite,InitErrors,MMGenObject):
  81. width = 10
  82. trunc_ok = False
  83. color = 'yellow'
  84. def __new__(cls,sid,mmtype):
  85. try:
  86. assert type(sid) == SeedID, f'{sid!r} not a SeedID instance'
  87. if not isinstance(mmtype,(MMGenAddrType,MMGenPasswordType)):
  88. raise ValueError(f'{mmtype!r}: not an instance of MMGenAddrType or MMGenPasswordType')
  89. me = str.__new__(cls,sid+':'+mmtype)
  90. me.sid = sid
  91. me.mmtype = mmtype
  92. return me
  93. except Exception as e:
  94. return cls.init_fail(e, f'sid={sid}, mmtype={mmtype}')
  95. def is_addrlist_id(s):
  96. return get_obj( AddrListID, sid=s, silent=True, return_bool=True )
  97. class MMGenID(str,Hilite,InitErrors,MMGenObject):
  98. color = 'orange'
  99. width = 0
  100. trunc_ok = False
  101. def __new__(cls,proto,id_str):
  102. try:
  103. ss = str(id_str).split(':')
  104. assert len(ss) in (2,3),'not 2 or 3 colon-separated items'
  105. t = proto.addr_type((ss[1],proto.dfl_mmtype)[len(ss)==2])
  106. me = str.__new__(cls,'{}:{}:{}'.format(ss[0],t,ss[-1]))
  107. me.sid = SeedID(sid=ss[0])
  108. me.idx = AddrIdx(ss[-1])
  109. me.mmtype = t
  110. assert t in proto.mmtypes, f'{t}: invalid address type for {proto.cls_name}'
  111. me.al_id = str.__new__(AddrListID,me.sid+':'+me.mmtype) # checks already done
  112. me.sort_key = '{}:{}:{:0{w}}'.format(me.sid,me.mmtype,me.idx,w=me.idx.max_digits)
  113. me.proto = proto
  114. return me
  115. except Exception as e:
  116. return cls.init_fail(e,id_str)
  117. def is_mmgen_id(proto,s):
  118. return get_obj( MMGenID, proto=proto, id_str=s, silent=True, return_bool=True )
  119. class CoinAddr(str,Hilite,InitErrors,MMGenObject):
  120. color = 'cyan'
  121. hex_width = 40
  122. width = 1
  123. trunc_ok = False
  124. def __new__(cls,proto,addr):
  125. if type(addr) == cls:
  126. return addr
  127. try:
  128. assert addr.isascii() and addr.isalnum(), 'not an ASCII alphanumeric string'
  129. me = str.__new__(cls,addr)
  130. ap = proto.parse_addr(addr)
  131. assert ap, f'coin address {addr!r} could not be parsed'
  132. me.addr_fmt = ap.fmt
  133. me.hex = ap.bytes.hex()
  134. me.proto = proto
  135. return me
  136. except Exception as e:
  137. return cls.init_fail(e,addr,objname=f'{proto.cls_name} address')
  138. @classmethod
  139. def fmtc(cls,addr,**kwargs):
  140. w = kwargs['width'] or cls.width
  141. return super().fmtc(addr[:w-2]+'..' if w < len(addr) else addr, **kwargs)
  142. def is_coin_addr(proto,s):
  143. return get_obj( CoinAddr, proto=proto, addr=s, silent=True, return_bool=True )
  144. class TokenAddr(CoinAddr):
  145. color = 'blue'
  146. class ViewKey(object):
  147. def __new__(cls,proto,viewkey):
  148. if proto.name == 'Zcash':
  149. return ZcashViewKey.__new__(ZcashViewKey,proto,viewkey)
  150. elif proto.name == 'Monero':
  151. return MoneroViewKey.__new__(MoneroViewKey,viewkey)
  152. else:
  153. raise ValueError(f'{proto.name}: protocol does not support view keys')
  154. class MoneroViewKey(HexStr):
  155. color,width,hexcase = 'cyan',64,'lower' # FIXME - no checking performed
  156. class ZcashViewKey(CoinAddr):
  157. hex_width = 128
  158. def KeyGenerator(proto,pubkey_type,backend=None,silent=False):
  159. """
  160. factory function returning a key generator backend for the specified pubkey type
  161. """
  162. assert pubkey_type in proto.pubkey_types, f'{pubkey_type!r}: invalid pubkey type for coin {proto.coin}'
  163. from .keygen import keygen_backend,_check_backend
  164. pubkey_type_cls = getattr(keygen_backend,pubkey_type)
  165. from .opts import opt
  166. backend = backend or opt.keygen_backend
  167. if backend:
  168. _check_backend(backend,pubkey_type)
  169. backend_id = pubkey_type_cls.backends[int(backend) - 1 if backend else 0]
  170. if backend_id == 'libsecp256k1':
  171. if not pubkey_type_cls.libsecp256k1.test_avail(silent=silent):
  172. backend_id = 'python-ecdsa'
  173. if not backend:
  174. qmsg('Using (slow) native Python ECDSA library for public key generation')
  175. return getattr(pubkey_type_cls,backend_id.replace('-','_'))()
  176. def AddrGenerator(proto,addr_type):
  177. """
  178. factory function returning an address generator for the specified address type
  179. """
  180. if type(addr_type) == str:
  181. addr_type = MMGenAddrType(proto=proto,id_str=addr_type)
  182. elif type(addr_type) == MMGenAddrType:
  183. assert addr_type in proto.mmtypes, f'{addr_type}: invalid address type for coin {proto.coin}'
  184. else:
  185. raise TypeError(f'{type(addr_type)}: incorrect argument type for {cls.__name__}()')
  186. from .addrgen import addr_generator
  187. return getattr(addr_generator,addr_type.name)(proto,addr_type)