addr.py 36 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072
  1. #!/usr/bin/env python3
  2. #
  3. # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
  4. # Copyright (C)2013-2019 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: Address generation/display routines for the MMGen suite
  20. """
  21. from hashlib import sha256,sha512
  22. from mmgen.common import *
  23. from mmgen.obj import *
  24. from mmgen.baseconv import *
  25. pnm = g.proj_name
  26. def dmsg_sc(desc,data):
  27. if g.debug_addrlist: Msg('sc_debug_{}: {}'.format(desc,data))
  28. class AddrGenerator(MMGenObject):
  29. def __new__(cls,addr_type):
  30. if type(addr_type) == str: # allow override w/o check
  31. gen_method = addr_type
  32. elif type(addr_type) == MMGenAddrType:
  33. assert addr_type in g.proto.mmtypes,'{}: invalid address type for coin {}'.format(addr_type,g.coin)
  34. gen_method = addr_type.gen_method
  35. else:
  36. raise TypeError('{}: incorrect argument type for {}()'.format(type(addr_type),cls.__name__))
  37. gen_methods = {
  38. 'p2pkh': AddrGeneratorP2PKH,
  39. 'segwit': AddrGeneratorSegwit,
  40. 'bech32': AddrGeneratorBech32,
  41. 'ethereum': AddrGeneratorEthereum,
  42. 'zcash_z': AddrGeneratorZcashZ,
  43. 'monero': AddrGeneratorMonero}
  44. assert gen_method in gen_methods
  45. me = super(cls,cls).__new__(gen_methods[gen_method])
  46. me.desc = gen_methods
  47. return me
  48. class AddrGeneratorP2PKH(AddrGenerator):
  49. def to_addr(self,pubhex):
  50. from mmgen.protocol import hash160
  51. assert type(pubhex) == PubKey
  52. return CoinAddr(g.proto.pubhash2addr(hash160(pubhex),p2sh=False))
  53. def to_segwit_redeem_script(self,pubhex):
  54. raise NotImplementedError('Segwit redeem script not supported by this address type')
  55. class AddrGeneratorSegwit(AddrGenerator):
  56. def to_addr(self,pubhex):
  57. assert pubhex.compressed,'Uncompressed public keys incompatible with Segwit'
  58. return CoinAddr(g.proto.pubhex2segwitaddr(pubhex))
  59. def to_segwit_redeem_script(self,pubhex):
  60. assert pubhex.compressed,'Uncompressed public keys incompatible with Segwit'
  61. return HexStr(g.proto.pubhex2redeem_script(pubhex))
  62. class AddrGeneratorBech32(AddrGenerator):
  63. def to_addr(self,pubhex):
  64. assert pubhex.compressed,'Uncompressed public keys incompatible with Segwit'
  65. from mmgen.protocol import hash160
  66. return CoinAddr(g.proto.pubhash2bech32addr(hash160(pubhex)))
  67. def to_segwit_redeem_script(self,pubhex):
  68. raise NotImplementedError('Segwit redeem script not supported by this address type')
  69. class AddrGeneratorEthereum(AddrGenerator):
  70. def __init__(self,addr_type):
  71. try:
  72. assert not g.use_internal_keccak_module
  73. from sha3 import keccak_256
  74. except:
  75. from mmgen.keccak import keccak_256
  76. self.keccak_256 = keccak_256
  77. from mmgen.protocol import hash256
  78. self.hash256 = hash256
  79. return AddrGenerator.__init__(addr_type)
  80. def to_addr(self,pubhex):
  81. assert type(pubhex) == PubKey
  82. return CoinAddr(self.keccak_256(bytes.fromhex(pubhex[2:])).hexdigest()[24:])
  83. def to_wallet_passwd(self,sk_hex):
  84. return WalletPassword(self.hash256(sk_hex)[:32])
  85. def to_segwit_redeem_script(self,pubhex):
  86. raise NotImplementedError('Segwit redeem script not supported by this address type')
  87. # github.com/FiloSottile/zcash-mini/zcash/address.go
  88. class AddrGeneratorZcashZ(AddrGenerator):
  89. def zhash256(self,s,t):
  90. s = bytearray(s + bytes(32))
  91. s[0] |= 0xc0
  92. s[32] = t
  93. from mmgen.sha2 import Sha256
  94. return Sha256(s,preprocess=False).digest()
  95. def to_addr(self,pubhex): # pubhex is really privhex
  96. key = bytes.fromhex(pubhex)
  97. assert len(key) == 32,'{}: incorrect privkey length'.format(len(key))
  98. from nacl.bindings import crypto_scalarmult_base
  99. p2 = crypto_scalarmult_base(self.zhash256(key,1))
  100. from mmgen.protocol import _b58chk_encode
  101. ver_bytes = g.proto.addr_fmt_to_ver_bytes('zcash_z')
  102. ret = _b58chk_encode(ver_bytes + self.zhash256(key,0) + p2)
  103. return CoinAddr(ret)
  104. def to_viewkey(self,pubhex): # pubhex is really privhex
  105. key = bytes.fromhex(pubhex)
  106. assert len(key) == 32,'{}: incorrect privkey length'.format(len(key))
  107. vk = bytearray(self.zhash256(key,0)+self.zhash256(key,1))
  108. vk[32] &= 0xf8
  109. vk[63] &= 0x7f
  110. vk[63] |= 0x40
  111. from mmgen.protocol import _b58chk_encode
  112. ver_bytes = g.proto.addr_fmt_to_ver_bytes('viewkey')
  113. ret = _b58chk_encode(ver_bytes + vk)
  114. return ZcashViewKey(ret)
  115. def to_segwit_redeem_script(self,pubhex):
  116. raise NotImplementedError('Zcash z-addresses incompatible with Segwit')
  117. class AddrGeneratorMonero(AddrGenerator):
  118. def __init__(self,addr_type):
  119. try:
  120. assert not g.use_internal_keccak_module
  121. from sha3 import keccak_256
  122. except:
  123. from mmgen.keccak import keccak_256
  124. self.keccak_256 = keccak_256
  125. from mmgen.protocol import hash256
  126. self.hash256 = hash256
  127. if opt.use_old_ed25519:
  128. from mmgen.ed25519 import edwards,encodepoint,B,scalarmult
  129. else:
  130. from mmgen.ed25519ll_djbec import scalarmult
  131. from mmgen.ed25519 import edwards,encodepoint,B
  132. self.edwards = edwards
  133. self.encodepoint = encodepoint
  134. self.scalarmult = scalarmult
  135. self.B = B
  136. return AddrGenerator.__init__(addr_type)
  137. def b58enc(self,addr_bytes):
  138. enc = baseconv.frombytes
  139. l = len(addr_bytes)
  140. a = ''.join([enc(addr_bytes[i*8:i*8+8],'b58',pad=11,tostr=True) for i in range(l//8)])
  141. b = enc(addr_bytes[l-l%8:],'b58',pad=7,tostr=True)
  142. return a + b
  143. def to_addr(self,sk_hex): # sk_hex instead of pubhex
  144. # Source and license for scalarmultbase function:
  145. # https://github.com/bigreddmachine/MoneroPy/blob/master/moneropy/crypto/ed25519.py
  146. # Copyright (c) 2014-2016, The Monero Project
  147. # All rights reserved.
  148. def scalarmultbase(e):
  149. if e == 0: return [0, 1]
  150. Q = self.scalarmult(self.B, e//2)
  151. Q = self.edwards(Q, Q)
  152. if e & 1: Q = self.edwards(Q, self.B)
  153. return Q
  154. def hex2int_le(hexstr):
  155. return int((bytes.fromhex(hexstr)[::-1]).hex(),16)
  156. vk_hex = self.to_viewkey(sk_hex)
  157. pk_str = self.encodepoint(scalarmultbase(hex2int_le(sk_hex)))
  158. pvk_str = self.encodepoint(scalarmultbase(hex2int_le(vk_hex)))
  159. addr_p1 = g.proto.addr_fmt_to_ver_bytes('monero') + pk_str + pvk_str
  160. return CoinAddr(self.b58enc(addr_p1 + self.keccak_256(addr_p1).digest()[:4]))
  161. def to_wallet_passwd(self,sk_hex):
  162. return WalletPassword(self.hash256(sk_hex)[:32])
  163. def to_viewkey(self,sk_hex):
  164. assert len(sk_hex) == 64,'{}: incorrect privkey length'.format(len(sk_hex))
  165. return MoneroViewKey(g.proto.preprocess_key(self.keccak_256(bytes.fromhex(sk_hex)).digest(),None).hex())
  166. def to_segwit_redeem_script(self,sk_hex):
  167. raise NotImplementedError('Monero addresses incompatible with Segwit')
  168. class KeyGenerator(MMGenObject):
  169. def __new__(cls,addr_type,generator=None,silent=False):
  170. if type(addr_type) == str: # allow override w/o check
  171. pubkey_type = addr_type
  172. elif type(addr_type) == MMGenAddrType:
  173. assert addr_type in g.proto.mmtypes,'{}: invalid address type for coin {}'.format(addr_type,g.coin)
  174. pubkey_type = addr_type.pubkey_type
  175. else:
  176. raise TypeError('{}: incorrect argument type for {}()'.format(type(addr_type),cls.__name__))
  177. if pubkey_type == 'std':
  178. if cls.test_for_secp256k1(silent=silent) and generator != 1:
  179. if not opt.key_generator or opt.key_generator == 2 or generator == 2:
  180. return super(cls,cls).__new__(KeyGeneratorSecp256k1)
  181. else:
  182. qmsg('Using (slow) native Python ECDSA library for address generation')
  183. return super(cls,cls).__new__(KeyGeneratorPython)
  184. elif pubkey_type in ('zcash_z','monero'):
  185. me = super(cls,cls).__new__(KeyGeneratorDummy)
  186. me.desc = 'mmgen-'+pubkey_type
  187. return me
  188. else:
  189. raise ValueError('{}: invalid pubkey_type argument'.format(pubkey_type))
  190. @classmethod
  191. def test_for_secp256k1(self,silent=False):
  192. try:
  193. from mmgen.secp256k1 import priv2pub
  194. m = 'Unable to execute priv2pub() from secp256k1 extension module'
  195. assert priv2pub(bytes.fromhex('deadbeef'*8),1),m
  196. return True
  197. except:
  198. return False
  199. import ecdsa
  200. class KeyGeneratorPython(KeyGenerator):
  201. desc = 'mmgen-python-ecdsa'
  202. def __init__(self,*args,**kwargs):
  203. # secp256k1: http://www.oid-info.com/get/1.3.132.0.10
  204. p = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f
  205. r = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141
  206. b = 0x0000000000000000000000000000000000000000000000000000000000000007
  207. a = 0x0000000000000000000000000000000000000000000000000000000000000000
  208. Gx = 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798
  209. Gy = 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8
  210. curve_fp = ecdsa.ellipticcurve.CurveFp(p,a,b)
  211. G = ecdsa.ellipticcurve.Point(curve_fp,Gx,Gy,r)
  212. oid = (1,3,132,0,10)
  213. self.secp256k1 = ecdsa.curves.Curve('secp256k1',curve_fp,G,oid)
  214. # devdoc/guide_wallets.md:
  215. # Uncompressed public keys start with 0x04; compressed public keys begin with 0x03 or
  216. # 0x02 depending on whether they're greater or less than the midpoint of the curve.
  217. def privnum2pubhex(self,numpriv,compressed=False):
  218. pko = ecdsa.SigningKey.from_secret_exponent(numpriv,self.secp256k1)
  219. # pubkey = x (32 bytes) + y (32 bytes) (unsigned big-endian)
  220. pubkey = (pko.get_verifying_key().to_string()).hex()
  221. if compressed: # discard Y coord, replace with appropriate version byte
  222. # even y: <0, odd y: >0 -- https://bitcointalk.org/index.php?topic=129652.0
  223. return ('03','02')[pubkey[-1] in '02468ace'] + pubkey[:64]
  224. else:
  225. return '04' + pubkey
  226. def to_pubhex(self,privhex):
  227. assert type(privhex) == PrivKey
  228. return PubKey(self.privnum2pubhex(
  229. int(privhex,16),compressed=privhex.compressed),compressed=privhex.compressed)
  230. class KeyGeneratorSecp256k1(KeyGenerator):
  231. desc = 'mmgen-secp256k1'
  232. def to_pubhex(self,privhex):
  233. assert type(privhex) == PrivKey
  234. from mmgen.secp256k1 import priv2pub
  235. return PubKey(priv2pub(bytes.fromhex(privhex),int(privhex.compressed)).hex(),compressed=privhex.compressed)
  236. class KeyGeneratorDummy(KeyGenerator):
  237. desc = 'mmgen-dummy'
  238. def to_pubhex(self,privhex):
  239. assert type(privhex) == PrivKey
  240. return PubKey(privhex,compressed=privhex.compressed)
  241. class AddrListEntry(MMGenListItem):
  242. addr = MMGenListItemAttr('addr','CoinAddr')
  243. idx = MMGenListItemAttr('idx','AddrIdx') # not present in flat addrlists
  244. label = MMGenListItemAttr('label','TwComment',reassign_ok=True)
  245. sec = MMGenListItemAttr('sec',PrivKey,typeconv=False)
  246. viewkey = MMGenListItemAttr('viewkey','ViewKey')
  247. wallet_passwd = MMGenListItemAttr('wallet_passwd','WalletPassword')
  248. class PasswordListEntry(MMGenListItem):
  249. passwd = MMGenListItemAttr('passwd',str,typeconv=False) # TODO: create Password type
  250. idx = MMGenImmutableAttr('idx','AddrIdx')
  251. label = MMGenListItemAttr('label','TwComment',reassign_ok=True)
  252. sec = MMGenListItemAttr('sec',PrivKey,typeconv=False)
  253. class AddrListChksum(str,Hilite):
  254. color = 'pink'
  255. trunc_ok = False
  256. def __new__(cls,addrlist):
  257. ea = addrlist.al_id.mmtype.extra_attrs # add viewkey and passwd to the mix, if present
  258. if ea == None: ea = ()
  259. lines = [' '.join(
  260. addrlist.chksum_rec_f(e) +
  261. tuple(getattr(e,a) for a in ea if getattr(e,a))
  262. ) for e in addrlist.data]
  263. return str.__new__(cls,make_chksum_N(' '.join(lines), nchars=16, sep=True))
  264. class AddrListIDStr(str,Hilite):
  265. color = 'green'
  266. trunc_ok = False
  267. def __new__(cls,addrlist,fmt_str=None):
  268. idxs = [e.idx for e in addrlist.data]
  269. prev = idxs[0]
  270. ret = prev,
  271. for i in idxs[1:]:
  272. if i == prev + 1:
  273. if i == idxs[-1]: ret += '-', i
  274. else:
  275. if prev != ret[-1]: ret += '-', prev
  276. ret += ',', i
  277. prev = i
  278. s = ''.join(map(str,ret))
  279. if fmt_str:
  280. ret = fmt_str.format(s)
  281. else:
  282. bc = (g.proto.base_coin,g.coin)[g.proto.base_coin=='ETH']
  283. mt = addrlist.al_id.mmtype
  284. ret = '{}{}{}[{}]'.format(addrlist.al_id.sid,('-'+bc,'')[bc=='BTC'],('-'+mt,'')[mt in ('L','E')],s)
  285. dmsg_sc('id_str',ret[8:].split('[')[0])
  286. return str.__new__(cls,ret)
  287. class AddrList(MMGenObject): # Address info for a single seed ID
  288. msgs = {
  289. 'file_header': """
  290. # {pnm} address file
  291. #
  292. # This file is editable.
  293. # Everything following a hash symbol '#' is a comment and ignored by {pnm}.
  294. # A text label of {n} screen cells or less may be added to the right of each
  295. # address, and it will be appended to the tracking wallet label upon import.
  296. # The label may contain any printable ASCII symbol.
  297. """.strip().format(n=TwComment.max_screen_width,pnm=pnm),
  298. 'record_chksum': """
  299. Record this checksum: it will be used to verify the address file in the future
  300. """.strip(),
  301. 'check_chksum': 'Check this value against your records',
  302. 'removed_dup_keys': """
  303. Removed {{}} duplicate WIF key{{}} from keylist (also in {pnm} key-address file
  304. """.strip().format(pnm=pnm)
  305. }
  306. entry_type = AddrListEntry
  307. main_attr = 'addr'
  308. data_desc = 'address'
  309. file_desc = 'addresses'
  310. gen_desc = 'address'
  311. gen_desc_pl = 'es'
  312. gen_addrs = True
  313. gen_passwds = False
  314. gen_keys = False
  315. has_keys = False
  316. ext = 'addrs'
  317. chksum_rec_f = lambda foo,e: (str(e.idx), e.addr)
  318. line_ctr = 0
  319. def __init__(self,addrfile='',al_id='',adata=[],seed='',addr_idxs='',src='',
  320. addrlist='',keylist='',mmtype=None):
  321. do_chksum = True
  322. self.update_msgs()
  323. mmtype = mmtype or g.proto.dfl_mmtype
  324. assert mmtype in MMGenAddrType.mmtypes,'{}: mmtype not in {}'.format(mmtype,repr(MMGenAddrType.mmtypes))
  325. if seed and addr_idxs: # data from seed + idxs
  326. self.al_id,src = AddrListID(seed.sid,mmtype),'gen'
  327. adata = self.generate(seed,addr_idxs)
  328. elif addrfile: # data from MMGen address file
  329. self.infile = addrfile
  330. adata = self.parse_file(addrfile) # sets self.al_id
  331. elif al_id and adata: # data from tracking wallet
  332. self.al_id = al_id
  333. do_chksum = False
  334. elif addrlist: # data from flat address list
  335. self.al_id = None
  336. adata = AddrListList([AddrListEntry(addr=a) for a in set(addrlist)])
  337. elif keylist: # data from flat key list
  338. self.al_id = None
  339. adata = AddrListList([AddrListEntry(sec=PrivKey(wif=k)) for k in set(keylist)])
  340. elif seed or addr_idxs:
  341. die(3,'Must specify both seed and addr indexes')
  342. elif al_id or adata:
  343. die(3,'Must specify both al_id and adata')
  344. else:
  345. die(3,'Incorrect arguments for {}'.format(type(self).__name__))
  346. # al_id,adata now set
  347. self.data = adata
  348. self.num_addrs = len(adata)
  349. self.fmt_data = ''
  350. self.chksum = None
  351. if self.al_id == None: return
  352. self.id_str = AddrListIDStr(self)
  353. if type(self) == KeyList: return
  354. if do_chksum:
  355. self.chksum = AddrListChksum(self)
  356. qmsg('Checksum for {} data {}: {}'.format(
  357. self.data_desc,self.id_str.hl(),self.chksum.hl()))
  358. qmsg(self.msgs[('check_chksum','record_chksum')[src=='gen']])
  359. def update_msgs(self):
  360. self.msgs = AddrList.msgs
  361. self.msgs.update(type(self).msgs)
  362. def generate(self,seed,addrnums):
  363. assert type(addrnums) is AddrIdxList
  364. seed = self.scramble_seed(seed.data)
  365. dmsg_sc('seed',seed[:8].hex())
  366. compressed = self.al_id.mmtype.compressed
  367. pubkey_type = self.al_id.mmtype.pubkey_type
  368. gen_wallet_passwd = type(self) == KeyAddrList and 'wallet_passwd' in self.al_id.mmtype.extra_attrs
  369. gen_viewkey = type(self) == KeyAddrList and 'viewkey' in self.al_id.mmtype.extra_attrs
  370. if self.gen_addrs:
  371. kg = KeyGenerator(self.al_id.mmtype)
  372. ag = AddrGenerator(self.al_id.mmtype)
  373. t_addrs,num,pos,out = len(addrnums),0,0,AddrListList()
  374. le = self.entry_type
  375. while pos != t_addrs:
  376. seed = sha512(seed).digest()
  377. num += 1 # round
  378. if num != addrnums[pos]: continue
  379. pos += 1
  380. if not g.debug:
  381. qmsg_r('\rGenerating {} #{} ({} of {})'.format(self.gen_desc,num,pos,t_addrs))
  382. e = le(idx=num)
  383. # Secret key is double sha256 of seed hash round /num/
  384. e.sec = PrivKey(sha256(sha256(seed).digest()).digest(),compressed=compressed,pubkey_type=pubkey_type)
  385. if self.gen_addrs:
  386. pubhex = kg.to_pubhex(e.sec)
  387. e.addr = ag.to_addr(pubhex)
  388. if gen_viewkey:
  389. e.viewkey = ag.to_viewkey(pubhex)
  390. if gen_wallet_passwd:
  391. e.wallet_passwd = ag.to_wallet_passwd(e.sec)
  392. if type(self) == PasswordList:
  393. e.passwd = str(self.make_passwd(e.sec)) # TODO - own type
  394. dmsg('Key {:>03}: {}'.format(pos,e.passwd))
  395. out.append(e)
  396. if g.debug_addrlist:
  397. Msg('generate():\n{}'.format(e.pfmt()))
  398. qmsg('\r{}: {} {}{} generated{}'.format(
  399. self.al_id.hl(),t_addrs,self.gen_desc,suf(t_addrs,self.gen_desc_pl),' '*15))
  400. return out
  401. def check_format(self,addr):
  402. return True # format is checked when added to list entry object
  403. def scramble_seed(self,seed):
  404. is_btcfork = g.proto.base_coin == 'BTC'
  405. if is_btcfork and self.al_id.mmtype == 'L' and not g.proto.is_testnet():
  406. dmsg_sc('str','(none)')
  407. return seed
  408. if g.proto.base_coin == 'ETH':
  409. scramble_key = g.coin.lower()
  410. else:
  411. scramble_key = (g.coin.lower()+':','')[is_btcfork] + self.al_id.mmtype.name
  412. from mmgen.crypto import scramble_seed
  413. if g.proto.is_testnet():
  414. scramble_key += ':testnet'
  415. dmsg_sc('str',scramble_key)
  416. return scramble_seed(seed,scramble_key.encode())
  417. def encrypt(self,desc='new key list'):
  418. from mmgen.crypto import mmgen_encrypt
  419. self.fmt_data = mmgen_encrypt(self.fmt_data.encode(),desc,'')
  420. self.ext += '.'+g.mmenc_ext
  421. def write_to_file(self,ask_tty=True,ask_write_default_yes=False,binary=False,desc=None):
  422. tn = ('','.testnet')[g.proto.is_testnet()]
  423. fn = '{}{x}{}.{}'.format(self.id_str,tn,self.ext,x='-α' if g.debug_utf8 else '')
  424. ask_tty = self.has_keys and not opt.quiet
  425. write_data_to_file(fn,self.fmt_data,desc or self.file_desc,ask_tty=ask_tty,binary=binary)
  426. def idxs(self):
  427. return [e.idx for e in self.data]
  428. def addrs(self):
  429. return ['{}:{}'.format(self.al_id.sid,e.idx) for e in self.data]
  430. def addrpairs(self):
  431. return [(e.idx,e.addr) for e in self.data]
  432. def coinaddrs(self):
  433. return [e.addr for e in self.data]
  434. def comments(self):
  435. return [e.label for e in self.data]
  436. def entry(self,idx):
  437. for e in self.data:
  438. if idx == e.idx: return e
  439. def coinaddr(self,idx):
  440. for e in self.data:
  441. if idx == e.idx: return e.addr
  442. def comment(self,idx):
  443. for e in self.data:
  444. if idx == e.idx: return e.label
  445. def set_comment(self,idx,comment):
  446. for e in self.data:
  447. if idx == e.idx:
  448. e.label = comment
  449. def make_reverse_dict(self,coinaddrs):
  450. d,b = MMGenDict(),coinaddrs
  451. for e in self.data:
  452. try:
  453. d[b[b.index(e.addr)]] = MMGenID('{}:{}'.format(self.al_id,e.idx)),e.label
  454. except: pass
  455. return d
  456. def remove_dup_keys(self,cmplist):
  457. assert self.has_keys
  458. pop_list = []
  459. for n,d in enumerate(self.data):
  460. for e in cmplist.data:
  461. if e.sec.wif == d.sec.wif:
  462. pop_list.append(n)
  463. for n in reversed(pop_list): self.data.pop(n)
  464. if pop_list:
  465. vmsg(self.msgs['removed_dup_keys'].format(len(pop_list),suf(removed)))
  466. def add_wifs(self,key_list):
  467. if not key_list: return
  468. for d in self.data:
  469. for e in key_list.data:
  470. if e.addr and e.sec and e.addr == d.addr:
  471. d.sec = e.sec
  472. def list_missing(self,key):
  473. return [d.addr for d in self.data if not getattr(d,key)]
  474. def generate_addrs_from_keys(self):
  475. # assume that the first listed mmtype is valid for flat key list
  476. t = MMGenAddrType(g.proto.mmtypes[0])
  477. kg = KeyGenerator(t.pubkey_type)
  478. ag = AddrGenerator(t.gen_method)
  479. d = self.data
  480. for n,e in enumerate(d,1):
  481. qmsg_r('\rGenerating addresses from keylist: {}/{}'.format(n,len(d)))
  482. e.addr = ag.to_addr(kg.to_pubhex(e.sec))
  483. if g.debug_addrlist:
  484. Msg('generate_addrs_from_keys():\n{}'.format(e.pfmt()))
  485. qmsg('\rGenerated addresses from keylist: {}/{} '.format(n,len(d)))
  486. def make_label(self):
  487. bc,mt = g.proto.base_coin,self.al_id.mmtype
  488. l_coin = [] if bc == 'BTC' else [g.coin] if bc == 'ETH' else [bc]
  489. l_type = [] if mt == 'E' or (mt == 'L' and not g.proto.is_testnet()) else [mt.name.upper()]
  490. l_tn = [] if not g.proto.is_testnet() else ['TESTNET']
  491. lbl_p2 = ':'.join(l_coin+l_type+l_tn)
  492. return self.al_id.sid + ('',' ')[bool(lbl_p2)] + lbl_p2
  493. def format(self,add_comments=False):
  494. out = [self.msgs['file_header']+'\n']
  495. if self.chksum:
  496. out.append('# {} data checksum for {}: {}'.format(
  497. capfirst(self.data_desc),self.id_str,self.chksum))
  498. out.append('# Record this value to a secure location.\n')
  499. lbl = self.make_label()
  500. dmsg_sc('lbl',lbl[9:])
  501. out.append('{} {{'.format(lbl))
  502. fs = ' {:<%s} {:<34}{}' % len(str(self.data[-1].idx))
  503. for e in self.data:
  504. c = ' '+e.label if add_comments and e.label else ''
  505. if type(self) == KeyList:
  506. out.append(fs.format(e.idx,'{}: {}'.format(self.al_id.mmtype.wif_label,e.sec.wif),c))
  507. elif type(self) == PasswordList:
  508. out.append(fs.format(e.idx,e.passwd,c))
  509. else: # First line with idx
  510. out.append(fs.format(e.idx,e.addr,c))
  511. if self.has_keys:
  512. if opt.b16:
  513. out.append(fs.format('', 'orig_hex: '+e.sec.orig_hex,c))
  514. out.append(fs.format('','{}: {}'.format(self.al_id.mmtype.wif_label,e.sec.wif),c))
  515. for k in ('viewkey','wallet_passwd'):
  516. v = getattr(e,k)
  517. if v: out.append(fs.format('','{}: {}'.format(k,v),c))
  518. out.append('}')
  519. self.fmt_data = '\n'.join([l.rstrip() for l in out]) + '\n'
  520. def get_line(self,lines):
  521. ret = lines.pop(0).split(None,2)
  522. self.line_ctr += 1
  523. if ret[0] == 'orig_hex:': # hacky
  524. ret = lines.pop(0).split(None,2)
  525. self.line_ctr += 1
  526. return ret if len(ret) == 3 else ret + ['']
  527. def parse_file_body(self,lines):
  528. ret = AddrListList()
  529. le = self.entry_type
  530. iifs = "{!r}: invalid identifier [expected '{}:']"
  531. while lines:
  532. idx,addr,lbl = self.get_line(lines)
  533. assert is_mmgen_idx(idx),'invalid address index {!r}'.format(idx)
  534. self.check_format(addr)
  535. a = le(**{ 'idx':int(idx), self.main_attr:addr, 'label':lbl })
  536. if self.has_keys: # order: wif,(orig_hex),viewkey,wallet_passwd
  537. d = self.get_line(lines)
  538. assert d[0] == self.al_id.mmtype.wif_label+':',iifs.format(d[0],self.al_id.mmtype.wif_label)
  539. a.sec = PrivKey(wif=d[1])
  540. for k,dtype in (('viewkey',ViewKey),('wallet_passwd',WalletPassword)):
  541. if k in self.al_id.mmtype.extra_attrs:
  542. d = self.get_line(lines)
  543. assert d[0] == k+':',iifs.format(d[0],k)
  544. setattr(a,k,dtype(d[1]))
  545. ret.append(a)
  546. if self.has_keys:
  547. if (hasattr(opt,'yes') and opt.yes) or keypress_confirm('Check key-to-address validity?'):
  548. kg = KeyGenerator(self.al_id.mmtype)
  549. ag = AddrGenerator(self.al_id.mmtype)
  550. llen = len(ret)
  551. for n,e in enumerate(ret):
  552. qmsg_r('\rVerifying keys {}/{}'.format(n+1,llen))
  553. assert e.addr == ag.to_addr(kg.to_pubhex(e.sec)),(
  554. "Key doesn't match address!\n {}\n {}".format(e.sec.wif,e.addr))
  555. qmsg(' - done')
  556. return ret
  557. def parse_file(self,fn,buf=[],exit_on_error=True):
  558. def parse_addrfile_label(lbl): # we must maintain backwards compat, so parse is tricky
  559. al_coin,al_mmtype = None,None
  560. tn = lbl[-8:] == ':TESTNET'
  561. if tn:
  562. assert g.proto.is_testnet(),'{} file is testnet but protocol is mainnet!'.format(self.data_desc)
  563. lbl = lbl[:-8]
  564. else:
  565. assert not g.proto.is_testnet(),'{} file is mainnet but protocol is testnet!'.format(self.data_desc)
  566. lbl = lbl.split(':',1)
  567. if len(lbl) == 2:
  568. al_coin,al_mmtype = lbl[0],lbl[1].lower()
  569. else:
  570. if lbl[0].lower() in MMGenAddrType.get_names():
  571. al_mmtype = lbl[0].lower()
  572. else:
  573. al_coin = lbl[0]
  574. # this block fails if al_mmtype is invalid for g.coin
  575. if not al_mmtype:
  576. mmtype = MMGenAddrType('E' if al_coin in ('ETH','ETC') else 'L',on_fail='raise')
  577. else:
  578. mmtype = MMGenAddrType(al_mmtype,on_fail='raise')
  579. from mmgen.protocol import CoinProtocol
  580. base_coin = CoinProtocol(al_coin or 'BTC',testnet=False).base_coin
  581. return base_coin,mmtype,tn
  582. def check_coin_mismatch(base_coin): # die if addrfile coin doesn't match g.coin
  583. m = '{} address file format, but base coin is {}!'
  584. assert base_coin == g.proto.base_coin, m.format(base_coin,g.proto.base_coin)
  585. lines = get_lines_from_file(fn,self.data_desc+' data',trim_comments=True)
  586. try:
  587. assert len(lines) >= 3, 'Too few lines in address file ({})'.format(len(lines))
  588. ls = lines[0].split()
  589. assert 1 < len(ls) < 5, "Invalid first line for {} file: '{}'".format(self.gen_desc,lines[0])
  590. assert ls.pop() == '{', "'{}': invalid first line".format(ls)
  591. assert lines[-1] == '}', "'{}': invalid last line".format(lines[-1])
  592. sid = ls.pop(0)
  593. assert is_mmgen_seed_id(sid),"'{}': invalid Seed ID".format(ls[0])
  594. if type(self) == PasswordList and len(ls) == 2:
  595. ss = ls.pop().split(':')
  596. assert len(ss) == 2,"'{}': invalid password length specifier (must contain colon)".format(ls[2])
  597. self.set_pw_fmt(ss[0])
  598. self.set_pw_len(ss[1])
  599. self.pw_id_str = MMGenPWIDString(ls.pop())
  600. base_coin,mmtype = None,MMGenPasswordType('P')
  601. testnet = False
  602. elif len(ls) == 1:
  603. base_coin,mmtype,testnet = parse_addrfile_label(ls[0])
  604. check_coin_mismatch(base_coin)
  605. elif len(ls) == 0:
  606. base_coin,mmtype = 'BTC',MMGenAddrType('L')
  607. testnet = False
  608. check_coin_mismatch(base_coin)
  609. else:
  610. raise ValueError("'{}': Invalid first line for {} file '{}'".format(lines[0],self.gen_desc,fn))
  611. self.base_coin = base_coin
  612. self.is_testnet = testnet
  613. self.al_id = AddrListID(SeedID(sid=sid),mmtype)
  614. data = self.parse_file_body(lines[1:-1])
  615. assert isinstance(data,list),'Invalid file body data'
  616. except Exception as e:
  617. lcs = ', content line {}'.format(self.line_ctr) if self.line_ctr else ''
  618. m = 'Invalid data in {} list file {!r}{} ({})'.format(self.data_desc,self.infile,lcs,e.args[0])
  619. if exit_on_error: die(3,m)
  620. msg(msg)
  621. return False
  622. return data
  623. class KeyAddrList(AddrList):
  624. data_desc = 'key-address'
  625. file_desc = 'secret keys'
  626. gen_desc = 'key/address pair'
  627. gen_desc_pl = 's'
  628. gen_addrs = True
  629. gen_keys = True
  630. has_keys = True
  631. ext = 'akeys'
  632. chksum_rec_f = lambda foo,e: (str(e.idx), e.addr, e.sec.wif)
  633. class KeyList(AddrList):
  634. msgs = {
  635. 'file_header': """
  636. # {pnm} key file
  637. #
  638. # This file is editable.
  639. # Everything following a hash symbol '#' is a comment and ignored by {pnm}.
  640. """.strip().format(pnm=pnm)
  641. }
  642. data_desc = 'key'
  643. file_desc = 'secret keys'
  644. gen_desc = 'key'
  645. gen_desc_pl = 's'
  646. gen_addrs = False
  647. gen_keys = True
  648. has_keys = True
  649. ext = 'keys'
  650. chksum_rec_f = lambda foo,e: (str(e.idx), e.addr, e.sec.wif)
  651. def is_bip39_str(s):
  652. from mmgen.bip39 import bip39
  653. return bool(bip39.tohex(s.split(),wl_id='bip39'))
  654. from collections import namedtuple
  655. class PasswordList(AddrList):
  656. msgs = {
  657. 'file_header': """
  658. # {pnm} password file
  659. #
  660. # This file is editable.
  661. # Everything following a hash symbol '#' is a comment and ignored by {pnm}.
  662. # A text label of {n} screen cells or less may be added to the right of each
  663. # password. The label may contain any printable ASCII symbol.
  664. #
  665. """.strip().format(n=TwComment.max_screen_width,pnm=pnm),
  666. 'file_header_bip39': """
  667. # {pnm} BIP39 password file
  668. #
  669. # This file is editable.
  670. # Everything following a hash symbol '#' is a comment and ignored by {pnm}.
  671. #
  672. """.strip().format(pnm=pnm),
  673. 'record_chksum': """
  674. Record this checksum: it will be used to verify the password file in the future
  675. """.strip()
  676. }
  677. entry_type = PasswordListEntry
  678. main_attr = 'passwd'
  679. data_desc = 'password'
  680. file_desc = 'passwords'
  681. gen_desc = 'password'
  682. gen_desc_pl = 's'
  683. gen_addrs = False
  684. gen_keys = False
  685. gen_passwds = True
  686. has_keys = False
  687. ext = 'pws'
  688. pw_len = None
  689. dfl_pw_fmt = 'b58'
  690. pwinfo = namedtuple('passwd_info',['min_len','max_len','dfl_len','valid_lens','desc','chk_func'])
  691. pw_info = {
  692. 'b32': pwinfo(10, 42 ,24, None, 'base32 password', is_b32_str), # 32**24 < 2**128
  693. 'b58': pwinfo(8, 36 ,20, None, 'base58 password', is_b58_str), # 58**20 < 2**128
  694. 'bip39': pwinfo(12, 24 ,24, [12,18,24], 'BIP39 mnemonic', is_bip39_str),
  695. 'hex': pwinfo(32, 64 ,64, [32,48,64], 'hexadecimal password', is_hex_str),
  696. }
  697. chksum_rec_f = lambda foo,e: (str(e.idx), e.passwd)
  698. feature_warn_fs = 'WARNING: {!r} is a potentially dangerous feature. Use at your own risk!'
  699. hex2bip39 = False
  700. def __init__( self,infile=None,seed=None,
  701. pw_idxs=None,pw_id_str=None,pw_len=None,pw_fmt=None,
  702. chk_params_only=False):
  703. self.update_msgs()
  704. if infile:
  705. self.infile = infile
  706. self.data = self.parse_file(infile) # sets self.pw_id_str,self.pw_fmt,self.pw_len
  707. else:
  708. if not chk_params_only:
  709. for k in (seed,pw_idxs):
  710. assert k
  711. self.pw_id_str = MMGenPWIDString(pw_id_str)
  712. self.set_pw_fmt(pw_fmt)
  713. self.set_pw_len(pw_len)
  714. if chk_params_only:
  715. return
  716. if self.hex2bip39:
  717. ymsg(self.feature_warn_fs.format(pw_fmt))
  718. self.set_pw_len_vs_seed_len(pw_len,seed)
  719. self.al_id = AddrListID(seed.sid,MMGenPasswordType('P'))
  720. self.data = self.generate(seed,pw_idxs)
  721. if self.pw_fmt == 'bip39':
  722. self.msgs['file_header'] = self.msgs['file_header_bip39']
  723. self.num_addrs = len(self.data)
  724. self.fmt_data = ''
  725. self.chksum = AddrListChksum(self)
  726. fs = '{}-{}-{}-{}[{{}}]'.format(self.al_id.sid,self.pw_id_str,self.pw_fmt_disp,self.pw_len)
  727. self.id_str = AddrListIDStr(self,fs)
  728. qmsg('Checksum for {} data {}: {}'.format(self.data_desc,self.id_str.hl(),self.chksum.hl()))
  729. qmsg(self.msgs[('record_chksum','check_chksum')[bool(infile)]])
  730. def set_pw_fmt(self,pw_fmt):
  731. if pw_fmt == 'hex2bip39':
  732. self.hex2bip39 = True
  733. self.pw_fmt = 'bip39'
  734. self.pw_fmt_disp = 'hex2bip39'
  735. else:
  736. self.pw_fmt = pw_fmt
  737. self.pw_fmt_disp = pw_fmt
  738. if self.pw_fmt not in self.pw_info:
  739. m = '{!r}: invalid password format. Valid formats: {}'
  740. raise InvalidPasswdFormat(m.format(self.pw_fmt,', '.join(sorted(self.pw_info))))
  741. def chk_pw_len(self,passwd=None):
  742. if passwd is None:
  743. assert self.pw_len,'either passwd or pw_len must be set'
  744. pw_len = self.pw_len
  745. fs = '{l}: invalid user-requested length for {b} ({c}{m})'
  746. else:
  747. pw_len = len(passwd)
  748. fs = '{pw}: {b} has invalid length {l} ({c}{m} characters)'
  749. d = self.pw_info[self.pw_fmt]
  750. if d.valid_lens:
  751. if pw_len not in d.valid_lens:
  752. die(2,fs.format(l=pw_len,b=d.desc,c='not one of ',m=d.valid_lens,pw=passwd))
  753. elif pw_len > d.max_len:
  754. die(2,fs.format(l=pw_len,b=d.desc,c='>',m=d.max_len,pw=passwd))
  755. elif pw_len < d.min_len:
  756. die(2,fs.format(l=pw_len,b=d.desc,c='<',m=d.min_len,pw=passwd))
  757. def set_pw_len(self,pw_len):
  758. d = self.pw_info[self.pw_fmt]
  759. if pw_len is None:
  760. self.pw_len = d.dfl_len
  761. return
  762. if not is_int(pw_len):
  763. die(2,"'{}': invalid user-requested password length (not an integer)".format(pw_len,d.desc))
  764. self.pw_len = int(pw_len)
  765. self.chk_pw_len()
  766. def set_pw_len_vs_seed_len(self,pw_len,seed):
  767. pf = self.pw_fmt
  768. if pf == 'hex':
  769. pw_bytes = self.pw_len // 2
  770. good_pw_len = seed.byte_len * 2
  771. elif pf == 'bip39':
  772. from mmgen.bip39 import bip39
  773. pw_bytes = bip39.nwords2seedlen(self.pw_len,in_bytes=True)
  774. good_pw_len = bip39.seedlen2nwords(len(seed.data),in_bytes=True)
  775. elif pf in ('b32','b58'):
  776. pw_int = (32 if pf == 'b32' else 58) ** self.pw_len
  777. pw_bytes = pw_int.bit_length() // 8
  778. good_pw_len = len(baseconv.frombytes(b'\xff'*seed.byte_len,wl_id=pf))
  779. else:
  780. raise NotImplementedError('{!r}: unknown password format'.format(pf))
  781. if pw_bytes > seed.byte_len:
  782. m1 = 'Cannot generate passwords with more entropy than underlying seed! ({} bits)'
  783. m2 = ( 'Re-run the command with --passwd-len={}' if pf in ('bip39','hex') else
  784. 'Re-run the command, specifying a password length of {} or less' )
  785. die(1,(m1+'\n'+m2).format(len(seed.data) * 8,good_pw_len))
  786. if pf in ('bip39','hex') and pw_bytes < seed.byte_len:
  787. m1 = 'WARNING: requested {} length has less entropy than underlying seed!'
  788. m2 = 'Is this what you want?'
  789. if not keypress_confirm((m1+'\n'+m2).format(self.pw_info[pf].desc),default_yes=True):
  790. die(1,'Exiting at user request')
  791. def make_passwd(self,hex_sec):
  792. assert self.pw_fmt in self.pw_info
  793. if self.pw_fmt == 'hex':
  794. # take most significant part
  795. return hex_sec[:self.pw_len]
  796. elif self.pw_fmt == 'bip39':
  797. from mmgen.bip39 import bip39
  798. pw_len_hex = bip39.nwords2seedlen(self.pw_len,in_hex=True)
  799. # take most significant part
  800. return ' '.join(bip39.fromhex(hex_sec[:pw_len_hex],wl_id='bip39'))
  801. else:
  802. # take least significant part
  803. return baseconv.fromhex(hex_sec,self.pw_fmt,pad=self.pw_len,tostr=True)[-self.pw_len:]
  804. def check_format(self,pw):
  805. if not self.pw_info[self.pw_fmt].chk_func(pw):
  806. raise ValueError('Password is not valid {} data'.format(self.pw_info[self.pw_fmt].desc))
  807. pwlen = len(pw.split()) if self.pw_fmt == 'bip39' else len(pw)
  808. if pwlen != self.pw_len:
  809. raise ValueError('Password has incorrect length ({} != {})'.format(pwlen,self.pw_len))
  810. return True
  811. def scramble_seed(self,seed):
  812. # Changing either pw_fmt or pw_len will cause a different, unrelated
  813. # set of passwords to be generated: this is what we want.
  814. # NB: In original implementation, pw_id_str was 'baseN', not 'bN'
  815. scramble_key = '{}:{}:{}'.format(self.pw_fmt,self.pw_len,self.pw_id_str)
  816. if self.hex2bip39:
  817. from mmgen.bip39 import bip39
  818. pwlen = bip39.nwords2seedlen(self.pw_len,in_hex=True)
  819. scramble_key = '{}:{}:{}'.format('hex',pwlen,self.pw_id_str)
  820. from mmgen.crypto import scramble_seed
  821. dmsg_sc('str',scramble_key)
  822. return scramble_seed(seed,scramble_key.encode())
  823. def get_line(self,lines):
  824. self.line_ctr += 1
  825. if self.pw_fmt == 'bip39':
  826. ret = lines.pop(0).split(None,self.pw_len+1)
  827. if len(ret) > self.pw_len+1:
  828. m1 = 'extraneous text {!r} found after password'.format(ret[self.pw_len+1])
  829. m2 = '[bare comments not allowed in BIP39 password files]'
  830. m = m1+' '+m2
  831. elif len(ret) < self.pw_len+1:
  832. m = 'invalid password length {}'.format(len(ret)-1)
  833. else:
  834. return (ret[0],' '.join(ret[1:self.pw_len+1]),'')
  835. raise ValueError(m)
  836. else:
  837. ret = lines.pop(0).split(None,2)
  838. return ret if len(ret) == 3 else ret + ['']
  839. def make_label(self):
  840. return '{} {} {}:{}'.format(self.al_id.sid,self.pw_id_str,self.pw_fmt_disp,self.pw_len)
  841. class AddrData(MMGenObject):
  842. msgs = {
  843. 'too_many_acct_addresses': """
  844. ERROR: More than one address found for account: '{{}}'.
  845. Your 'wallet.dat' file appears to have been altered by a non-{pnm} program.
  846. Please restore your tracking wallet from a backup or create a new one and
  847. re-import your addresses.
  848. """.strip().format(pnm=pnm)
  849. }
  850. def __new__(cls,*args,**kwargs):
  851. return MMGenObject.__new__(altcoin_subclass(cls,'tw','AddrData'))
  852. def __init__(self,source=None,wallet=None):
  853. self.al_ids = {}
  854. if source == 'tw':
  855. self.add_tw_data(wallet)
  856. def seed_ids(self):
  857. return list(self.al_ids.keys())
  858. def addrlist(self,al_id):
  859. # TODO: Validate al_id
  860. if al_id in self.al_ids:
  861. return self.al_ids[al_id]
  862. def mmaddr2coinaddr(self,mmaddr):
  863. al_id,idx = MMGenID(mmaddr).rsplit(':',1)
  864. coinaddr = ''
  865. if al_id in self.al_ids:
  866. coinaddr = self.addrlist(al_id).coinaddr(int(idx))
  867. return coinaddr or None
  868. def coinaddr2mmaddr(self,coinaddr):
  869. d = self.make_reverse_dict([coinaddr])
  870. return (list(d.values())[0][0]) if d else None
  871. @classmethod
  872. def get_tw_data(cls,wallet=None):
  873. vmsg('Getting address data from tracking wallet')
  874. if 'label_api' in g.rpch.caps:
  875. accts = g.rpch.listlabels()
  876. alists = [list(a.keys()) for a in g.rpch.getaddressesbylabel([[k] for k in accts],batch=True)]
  877. else:
  878. accts = g.rpch.listaccounts(0,True)
  879. alists = g.rpch.getaddressesbyaccount([[k] for k in accts],batch=True)
  880. return list(zip(accts,alists))
  881. def add_tw_data(self,wallet):
  882. d,out,i = self.get_tw_data(wallet),{},0
  883. for acct,addr_array in d:
  884. l = TwLabel(acct,on_fail='silent')
  885. if l and l.mmid.type == 'mmgen':
  886. obj = l.mmid.obj
  887. i += 1
  888. if len(addr_array) != 1:
  889. die(2,self.msgs['too_many_acct_addresses'].format(acct))
  890. al_id = AddrListID(SeedID(sid=obj.sid),MMGenAddrType(obj.mmtype))
  891. if al_id not in out:
  892. out[al_id] = []
  893. out[al_id].append(AddrListEntry(idx=obj.idx,addr=addr_array[0],label=l.comment))
  894. vmsg('{n} {pnm} addresses found, {m} accounts total'.format(n=i,pnm=pnm,m=len(d)))
  895. for al_id in out:
  896. self.add(AddrList(al_id=al_id,adata=AddrListList(sorted(out[al_id],key=lambda a: a.idx))))
  897. def add(self,addrlist):
  898. if type(addrlist) == AddrList:
  899. self.al_ids[addrlist.al_id] = addrlist
  900. return True
  901. else:
  902. raise TypeError('Error: object {!r} is not of type AddrList'.format(addrlist))
  903. def make_reverse_dict(self,coinaddrs):
  904. d = MMGenDict()
  905. for al_id in self.al_ids:
  906. d.update(self.al_ids[al_id].make_reverse_dict(coinaddrs))
  907. return d