addr.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746
  1. #!/usr/bin/env python
  2. #
  3. # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
  4. # Copyright (C)2013-2017 Philemon <mmgen-py@yandex.com>
  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 binascii import hexlify,unhexlify
  23. from mmgen.common import *
  24. from mmgen.bitcoin import hex2wif,wif2hex,wif_is_compressed
  25. from mmgen.obj import *
  26. from mmgen.tx import *
  27. from mmgen.tw import *
  28. pnm = g.proj_name
  29. def _test_for_secp256k1(silent=False):
  30. no_secp256k1_errmsg = """
  31. secp256k1 library unavailable. Using (slow) native Python ECDSA library for address generation.
  32. """
  33. try:
  34. from mmgen.secp256k1 import priv2pub
  35. assert priv2pub(os.urandom(32),1)
  36. except:
  37. if not silent: msg(no_secp256k1_errmsg.strip())
  38. return False
  39. return True
  40. def _pubhex2addr(pubhex,mmtype):
  41. if mmtype == 'L':
  42. from mmgen.bitcoin import hexaddr2addr,hash160
  43. return hexaddr2addr(hash160(pubhex))
  44. elif mmtype == 'S':
  45. from mmgen.bitcoin import pubhex2segwitaddr
  46. return pubhex2segwitaddr(pubhex)
  47. else:
  48. die(2,"'{}': mmtype unrecognized".format(mmtype))
  49. def _privhex2addr_python(privhex,compressed,mmtype):
  50. assert compressed or mmtype != 'S'
  51. from mmgen.bitcoin import privnum2pubhex
  52. pubhex = privnum2pubhex(int(privhex,16),compressed=compressed)
  53. return _pubhex2addr(pubhex,mmtype=mmtype)
  54. def _privhex2addr_secp256k1(privhex,compressed,mmtype):
  55. assert compressed or mmtype != 'S'
  56. from mmgen.secp256k1 import priv2pub
  57. pubhex = hexlify(priv2pub(unhexlify(privhex),int(compressed)))
  58. return _pubhex2addr(pubhex,mmtype=mmtype)
  59. def _wif2addr_python(wif,mmtype):
  60. privhex = wif2hex(wif)
  61. if not privhex: return False
  62. return _privhex2addr_python(privhex,wif_is_compressed(wif),mmtype=mmtype)
  63. def _wif2addr_secp256k1(wif,mmtype):
  64. privhex = wif2hex(wif)
  65. if not privhex: return False
  66. return _privhex2addr_secp256k1(privhex,wif_is_compressed(wif),mmtype=mmtype)
  67. def keygen_wif2pubhex(wif,selector):
  68. privhex = wif2hex(wif)
  69. if not privhex: return False
  70. if selector == 1:
  71. from mmgen.secp256k1 import priv2pub
  72. return hexlify(priv2pub(unhexlify(privhex),int(wif_is_compressed(wif))))
  73. elif selector == 0:
  74. from mmgen.bitcoin import privnum2pubhex
  75. return privnum2pubhex(int(privhex,16),compressed=wif_is_compressed(wif))
  76. def keygen_selector(generator=None):
  77. if _test_for_secp256k1() and generator != 1:
  78. if opt.key_generator != 1:
  79. return 1
  80. msg('Using (slow) native Python ECDSA library for address generation')
  81. return 0
  82. def get_wif2addr_f(generator=None):
  83. gen = keygen_selector(generator=generator)
  84. return (_wif2addr_python,_wif2addr_secp256k1)[gen]
  85. def get_privhex2addr_f(generator=None):
  86. gen = keygen_selector(generator=generator)
  87. return (_privhex2addr_python,_privhex2addr_secp256k1)[gen]
  88. class AddrListEntry(MMGenListItem):
  89. attrs = 'idx','addr','label','wif','sec'
  90. idx = MMGenListItemAttr('idx','AddrIdx')
  91. wif = MMGenListItemAttr('wif','WifKey')
  92. class AddrListChksum(str,Hilite):
  93. color = 'pink'
  94. trunc_ok = False
  95. def __new__(cls,addrlist):
  96. els = ['addr','wif'] if addrlist.has_keys else ['sec'] if addrlist.gen_passwds else ['addr']
  97. lines = [' '.join([str(e.idx)] + [getattr(e,f) for f in els]) for e in addrlist.data]
  98. # print '[{}]'.format(' '.join(lines))
  99. return str.__new__(cls,make_chksum_N(' '.join(lines), nchars=16, sep=True))
  100. class AddrListIDStr(unicode,Hilite):
  101. color = 'green'
  102. trunc_ok = False
  103. def __new__(cls,addrlist,fmt_str=None):
  104. try: int(addrlist.data[0].idx)
  105. except:
  106. s = '(no idxs)'
  107. else:
  108. idxs = [e.idx for e in addrlist.data]
  109. prev = idxs[0]
  110. ret = prev,
  111. for i in idxs[1:]:
  112. if i == prev + 1:
  113. if i == idxs[-1]: ret += '-', i
  114. else:
  115. if prev != ret[-1]: ret += '-', prev
  116. ret += ',', i
  117. prev = i
  118. s = ''.join([unicode(i) for i in ret])
  119. if fmt_str:
  120. ret = fmt_str.format(s)
  121. elif addrlist.al_id.mmtype == 'L':
  122. ret = '{}[{}]'.format(addrlist.al_id.sid,s)
  123. else:
  124. ret = '{}-{}[{}]'.format(addrlist.al_id.sid,addrlist.al_id.mmtype,s)
  125. return unicode.__new__(cls,ret)
  126. class AddrList(MMGenObject): # Address info for a single seed ID
  127. msgs = {
  128. 'file_header': """
  129. # {pnm} address file
  130. #
  131. # This file is editable.
  132. # Everything following a hash symbol '#' is a comment and ignored by {pnm}.
  133. # A text label of {n} characters or less may be added to the right of each
  134. # address, and it will be appended to the bitcoind wallet label upon import.
  135. # The label may contain any printable ASCII symbol.
  136. """.strip().format(n=TwComment.max_len,pnm=pnm),
  137. 'record_chksum': """
  138. Record this checksum: it will be used to verify the address file in the future
  139. """.strip(),
  140. 'check_chksum': 'Check this value against your records',
  141. 'removed_dups': """
  142. Removed %s duplicate wif key%s from keylist (also in {pnm} key-address file
  143. """.strip().format(pnm=pnm)
  144. }
  145. main_key = 'addr'
  146. data_desc = 'address'
  147. file_desc = 'addresses'
  148. gen_desc = 'address'
  149. gen_desc_pl = 'es'
  150. gen_addrs = True
  151. gen_passwds = False
  152. gen_keys = False
  153. has_keys = False
  154. ext = 'addrs'
  155. dfl_mmtype = MMGenAddrType('L')
  156. cook_hash_rounds = 10 # not too many rounds, so hand decoding can still be feasible
  157. def __init__(self,addrfile='',al_id='',adata=[],seed='',addr_idxs='',src='',
  158. addrlist='',keylist='',mmtype=None,do_chksum=True,chksum_only=False):
  159. self.update_msgs()
  160. mmtype = mmtype or self.dfl_mmtype
  161. assert mmtype in MMGenAddrType.mmtypes
  162. if seed and addr_idxs: # data from seed + idxs
  163. self.al_id,src = AddrListID(seed.sid,mmtype),'gen'
  164. adata = self.generate(seed,addr_idxs,compressed=(mmtype=='S'))
  165. elif addrfile: # data from MMGen address file
  166. adata = self.parse_file(addrfile) # sets self.al_id
  167. elif al_id and adata: # data from tracking wallet
  168. self.al_id = al_id
  169. do_chksum = False
  170. elif addrlist: # data from flat address list
  171. self.al_id = None
  172. adata = AddrListList([AddrListEntry(addr=a) for a in set(addrlist)])
  173. elif keylist: # data from flat key list
  174. self.al_id = None
  175. adata = AddrListList([AddrListEntry(wif=k) for k in set(keylist)])
  176. elif seed or addr_idxs:
  177. die(3,'Must specify both seed and addr indexes')
  178. elif al_id or adata:
  179. die(3,'Must specify both al_id and adata')
  180. else:
  181. die(3,'Incorrect arguments for %s' % type(self).__name__)
  182. # al_id,adata now set
  183. self.data = adata
  184. self.num_addrs = len(adata)
  185. self.fmt_data = ''
  186. self.chksum = None
  187. if self.al_id == None: return
  188. self.id_str = AddrListIDStr(self)
  189. if type(self) == KeyList: return
  190. if do_chksum:
  191. self.chksum = AddrListChksum(self)
  192. if chksum_only:
  193. Msg(self.chksum)
  194. else:
  195. qmsg('Checksum for %s data %s: %s' %
  196. (self.data_desc,self.id_str.hl(),self.chksum.hl()))
  197. qmsg(self.msgs[('check_chksum','record_chksum')[src=='gen']])
  198. def update_msgs(self):
  199. if type(self).msgs and type(self) != AddrList:
  200. for k in AddrList.msgs:
  201. if k not in self.msgs:
  202. self.msgs[k] = AddrList.msgs[k]
  203. def generate(self,seed,addrnums,compressed):
  204. assert type(addrnums) is AddrIdxList
  205. assert compressed in (True,False,None)
  206. seed = seed.get_data()
  207. seed = self.cook_seed(seed)
  208. if self.gen_addrs:
  209. privhex2addr_f = get_privhex2addr_f() # choose internal ECDSA or secp256k1 generator
  210. t_addrs,num,pos,out = len(addrnums),0,0,AddrListList()
  211. while pos != t_addrs:
  212. seed = sha512(seed).digest()
  213. num += 1 # round
  214. if num != addrnums[pos]: continue
  215. pos += 1
  216. if not g.debug:
  217. qmsg_r('\rGenerating %s #%s (%s of %s)' % (self.gen_desc,num,pos,t_addrs))
  218. e = AddrListEntry(idx=num)
  219. # Secret key is double sha256 of seed hash round /num/
  220. sec = sha256(sha256(seed).digest()).hexdigest()
  221. if self.gen_addrs:
  222. e.addr = privhex2addr_f(sec,compressed=compressed,mmtype=self.al_id.mmtype)
  223. if self.gen_keys:
  224. e.wif = hex2wif(sec,compressed=compressed)
  225. if opt.b16: e.sec = sec
  226. if self.gen_passwds:
  227. e.sec = self.make_passwd(sec)
  228. dmsg('Key {:>03}: {}'.format(pos,sec))
  229. out.append(e)
  230. if g.debug: print 'generate():\n', e.pformat()
  231. qmsg('\r%s: %s %s%s generated%s' % (
  232. self.al_id.hl(),t_addrs,self.gen_desc,suf(t_addrs,self.gen_desc_pl),' '*15))
  233. return out
  234. def is_mainnet(self):
  235. return self.data[0].addr.is_mainnet()
  236. def is_for_current_chain(self):
  237. return self.data[0].addr.is_for_current_chain()
  238. def chk_addr_or_pw(self,addr):
  239. return {'L':'p2pkh','S':'p2sh'}[self.al_id.mmtype] == is_btc_addr(addr).addr_fmt
  240. def cook_seed(self,seed):
  241. if self.al_id.mmtype == 'L':
  242. return seed
  243. else:
  244. from mmgen.crypto import sha256_rounds
  245. import hmac
  246. key = self.al_id.mmtype.name
  247. cseed = hmac.new(seed,key,sha256).digest()
  248. dmsg('Seed: {}\nKey: {}\nCseed: {}\nCseed len: {}'.format(hexlify(seed),key,hexlify(cseed),len(cseed)))
  249. return sha256_rounds(cseed,self.cook_hash_rounds)
  250. def encrypt(self,desc='new key list'):
  251. from mmgen.crypto import mmgen_encrypt
  252. self.fmt_data = mmgen_encrypt(self.fmt_data.encode('utf8'),desc,'')
  253. self.ext += '.'+g.mmenc_ext
  254. def write_to_file(self,ask_tty=True,ask_write_default_yes=False,binary=False,desc=None):
  255. fn = u'{}.{}'.format(self.id_str,self.ext)
  256. ask_tty = self.has_keys and not opt.quiet
  257. write_data_to_file(fn,self.fmt_data,desc or self.file_desc,ask_tty=ask_tty,binary=binary)
  258. def idxs(self):
  259. return [e.idx for e in self.data]
  260. def addrs(self):
  261. return ['%s:%s'%(self.al_id.sid,e.idx) for e in self.data]
  262. def addrpairs(self):
  263. return [(e.idx,e.addr) for e in self.data]
  264. def btcaddrs(self):
  265. return [e.addr for e in self.data]
  266. def comments(self):
  267. return [e.label for e in self.data]
  268. def entry(self,idx):
  269. for e in self.data:
  270. if idx == e.idx: return e
  271. def btcaddr(self,idx):
  272. for e in self.data:
  273. if idx == e.idx: return e.addr
  274. def comment(self,idx):
  275. for e in self.data:
  276. if idx == e.idx: return e.label
  277. def set_comment(self,idx,comment):
  278. for e in self.data:
  279. if idx == e.idx:
  280. e.label = comment
  281. def make_reverse_dict(self,btcaddrs):
  282. d,b = MMGenDict(),btcaddrs
  283. for e in self.data:
  284. try:
  285. d[b[b.index(e.addr)]] = MMGenID('{}:{}'.format(self.al_id,e.idx)),e.label
  286. except: pass
  287. return d
  288. def flat_list(self):
  289. class AddrListFlatEntry(AddrListEntry):
  290. attrs = 'mmid','addr','wif'
  291. return [AddrListFlatEntry(mmid='{}:{}'.format(self.al_id,e.idx),addr=e.addr,wif=e.wif)
  292. for e in self.data]
  293. def remove_dups(self,cmplist,key='wif'):
  294. pop_list = []
  295. for n,d in enumerate(self.data):
  296. if getattr(d,key) == None: continue
  297. for e in cmplist.data:
  298. if getattr(e,key) and getattr(e,key) == getattr(d,key):
  299. pop_list.append(n)
  300. for n in reversed(pop_list): self.data.pop(n)
  301. if pop_list:
  302. vmsg(self.msgs['removed_dups'] % (len(pop_list),suf(removed,'s')))
  303. def add_wifs(self,al_key):
  304. if not al_key: return
  305. for d in self.data:
  306. for e in al_key.data:
  307. if e.addr and e.wif and e.addr == d.addr:
  308. d.wif = e.wif
  309. def list_missing(self,key):
  310. return [d.addr for d in self.data if not getattr(d,key)]
  311. def get(self,key):
  312. return [getattr(d,key) for d in self.data if getattr(d,key)]
  313. def get_addrs(self): return self.get('addr')
  314. def get_wifs(self): return self.get('wif')
  315. def get_addr_wif_pairs(self):
  316. return [(d.addr,d.wif) for d in self.data if hasattr(d,'wif')]
  317. def generate_addrs_from_keylist(self):
  318. wif2addr_f = get_wif2addr_f()
  319. d = self.data
  320. for n,e in enumerate(d,1):
  321. qmsg_r('\rGenerating addresses from keylist: %s/%s' % (n,len(d)))
  322. e.addr = wif2addr_f(e.wif,mmtype='L') # 'L' == p2pkh
  323. qmsg('\rGenerated addresses from keylist: %s/%s ' % (n,len(d)))
  324. def format(self,enable_comments=False):
  325. def check_attrs(key,desc):
  326. for e in self.data:
  327. if not getattr(e,key):
  328. die(3,'missing %s in addr data' % desc)
  329. if type(self) not in (KeyList,PasswordList): check_attrs('addr','addresses')
  330. if self.has_keys:
  331. if opt.b16: check_attrs('sec','hex keys')
  332. check_attrs('wif','wif keys')
  333. out = [self.msgs['file_header']+'\n']
  334. if self.chksum:
  335. out.append(u'# {} data checksum for {}: {}'.format(
  336. capfirst(self.data_desc),self.id_str,self.chksum))
  337. out.append('# Record this value to a secure location.\n')
  338. if type(self) == PasswordList:
  339. out.append(u'{} {} {}:{} {{'.format(
  340. self.al_id.sid,self.pw_id_str,self.pw_fmt,self.pw_len))
  341. elif self.al_id.mmtype == 'L':
  342. out.append('{} {{'.format(self.al_id.sid))
  343. else:
  344. out.append('{} {} {{'.format(self.al_id.sid,MMGenAddrType.mmtypes[self.al_id.mmtype].upper()))
  345. fs = ' {:<%s} {:<34}{}' % len(str(self.data[-1].idx))
  346. for e in self.data:
  347. c = ' '+e.label if enable_comments and e.label else ''
  348. if type(self) == KeyList:
  349. out.append(fs.format(e.idx, 'wif: '+e.wif,c))
  350. elif type(self) == PasswordList:
  351. out.append(fs.format(e.idx, e.sec, c))
  352. else: # First line with idx
  353. out.append(fs.format(e.idx, e.addr,c))
  354. if self.has_keys:
  355. if opt.b16: out.append(fs.format('', 'hex: '+e.sec,c))
  356. out.append(fs.format('', 'wif: '+e.wif,c))
  357. out.append('}')
  358. self.fmt_data = '\n'.join([l.rstrip() for l in out]) + '\n'
  359. def parse_file_body(self,lines):
  360. if self.has_keys and len(lines) % 2:
  361. return 'Key-address file has odd number of lines'
  362. ret = AddrListList()
  363. while lines:
  364. l = lines.pop(0)
  365. d = l.split(None,2)
  366. if not is_mmgen_idx(d[0]):
  367. return "'%s': invalid address num. in line: '%s'" % (d[0],l)
  368. if not self.chk_addr_or_pw(d[1]):
  369. return "'{}': invalid {}".format(d[1],self.data_desc)
  370. if len(d) != 3: d.append('')
  371. a = AddrListEntry(**{'idx':int(d[0]),self.main_key:d[1],'label':d[2]})
  372. if self.has_keys:
  373. l = lines.pop(0)
  374. d = l.split(None,2)
  375. if d[0] != 'wif:':
  376. return "Invalid key line in file: '%s'" % l
  377. if not is_wif(d[1]):
  378. return "'%s': invalid Bitcoin key" % d[1]
  379. a.wif = d[1]
  380. ret.append(a)
  381. if self.has_keys and keypress_confirm('Check key-to-address validity?'):
  382. wif2addr_f = get_wif2addr_f()
  383. llen = len(ret)
  384. for n,e in enumerate(ret):
  385. msg_r('\rVerifying keys %s/%s' % (n+1,llen))
  386. if e.addr != wif2addr_f(e.wif,mmtype=self.al_id.mmtype):
  387. return "Key doesn't match address!\n %s\n %s" % (e.wif,e.addr)
  388. msg(' - done')
  389. return ret
  390. def parse_file(self,fn,buf=[],exit_on_error=True):
  391. def do_error(msg):
  392. if exit_on_error: die(3,msg)
  393. msg(msg)
  394. return False
  395. lines = get_lines_from_file(fn,self.data_desc+' data',trim_comments=True)
  396. if len(lines) < 3:
  397. return do_error("Too few lines in address file (%s)" % len(lines))
  398. ls = lines[0].split()
  399. if not 1 < len(ls) < 5:
  400. return do_error("Invalid first line for {} file: '{}'".format(self.gen_desc,lines[0]))
  401. if ls.pop() != '{':
  402. return do_error("'%s': invalid first line" % ls)
  403. if lines[-1] != '}':
  404. return do_error("'%s': invalid last line" % lines[-1])
  405. sid = ls.pop(0)
  406. if not is_mmgen_seed_id(sid):
  407. return do_error("'%s': invalid Seed ID" % ls[0])
  408. if type(self) == PasswordList and len(ls) == 2:
  409. ss = ls.pop().split(':')
  410. if len(ss) != 2:
  411. return do_error("'%s': invalid password length specifier (must contain colon)" % ls[2])
  412. self.set_pw_fmt(ss[0])
  413. self.set_pw_len(ss[1])
  414. self.pw_id_str = MMGenPWIDString(ls.pop())
  415. mmtype = MMGenPasswordType('P')
  416. elif len(ls) == 1:
  417. mmtype = ls.pop().lower()
  418. try:
  419. mmtype = MMGenAddrType(mmtype)
  420. except:
  421. return do_error(u"'{}': invalid address type in address file. Must be one of: {}".format(
  422. mmtype.upper(),' '.join(MMGenAddrType.mmtypes.values()).upper()))
  423. elif len(ls) == 0:
  424. mmtype = MMGenAddrType('L')
  425. else:
  426. return do_error(u"Invalid first line for {} file: '{}'".format(self.gen_desc,lines[0]))
  427. self.al_id = AddrListID(SeedID(sid=sid),mmtype)
  428. data = self.parse_file_body(lines[1:-1])
  429. if not issubclass(type(data),list):
  430. return do_error(data)
  431. return data
  432. class KeyAddrList(AddrList):
  433. data_desc = 'key-address'
  434. file_desc = 'secret keys'
  435. gen_desc = 'key/address pair'
  436. gen_desc_pl = 's'
  437. gen_addrs = True
  438. gen_keys = True
  439. has_keys = True
  440. ext = 'akeys'
  441. class KeyList(AddrList):
  442. msgs = {
  443. 'file_header': """
  444. # {pnm} key file
  445. #
  446. # This file is editable.
  447. # Everything following a hash symbol '#' is a comment and ignored by {pnm}.
  448. """.strip().format(pnm=pnm)
  449. }
  450. data_desc = 'key'
  451. file_desc = 'secret keys'
  452. gen_desc = 'key'
  453. gen_desc_pl = 's'
  454. gen_addrs = False
  455. gen_keys = True
  456. has_keys = True
  457. ext = 'keys'
  458. class PasswordList(AddrList):
  459. msgs = {
  460. 'file_header': """
  461. # {pnm} password file
  462. #
  463. # This file is editable.
  464. # Everything following a hash symbol '#' is a comment and ignored by {pnm}.
  465. # A text label of {n} characters or less may be added to the right of each
  466. # password. The label may contain any printable ASCII symbol.
  467. #
  468. """.strip().format(n=TwComment.max_len,pnm=pnm),
  469. 'record_chksum': """
  470. Record this checksum: it will be used to verify the password file in the future
  471. """.strip()
  472. }
  473. main_key = 'sec'
  474. data_desc = 'password'
  475. file_desc = 'passwords'
  476. gen_desc = 'password'
  477. gen_desc_pl = 's'
  478. gen_addrs = False
  479. gen_keys = False
  480. gen_passwds = True
  481. has_keys = False
  482. ext = 'pws'
  483. pw_len = None
  484. pw_fmt = None
  485. pw_info = {
  486. 'b58': { 'min_len': 8 , 'max_len': 36 ,'dfl_len': 20, 'desc': 'base-58 password' },
  487. 'b32': { 'min_len': 10 ,'max_len': 42 ,'dfl_len': 24, 'desc': 'base-32 password' }
  488. }
  489. def __init__(self,infile=None,seed=None,pw_idxs=None,pw_id_str=None,pw_len=None,pw_fmt=None,
  490. chksum_only=False,chk_params_only=False):
  491. self.update_msgs()
  492. if infile:
  493. self.data = self.parse_file(infile) # sets self.pw_id_str,self.pw_fmt,self.pw_len
  494. else:
  495. for k in seed,pw_idxs: assert chk_params_only or k
  496. for k in (pw_id_str,pw_fmt): assert k
  497. self.pw_id_str = MMGenPWIDString(pw_id_str)
  498. self.set_pw_fmt(pw_fmt)
  499. self.set_pw_len(pw_len)
  500. if chk_params_only: return
  501. self.al_id = AddrListID(seed.sid,MMGenPasswordType('P'))
  502. self.data = self.generate(seed,pw_idxs,compressed=None)
  503. self.num_addrs = len(self.data)
  504. self.fmt_data = ''
  505. self.chksum = AddrListChksum(self)
  506. if chksum_only:
  507. Msg(self.chksum)
  508. else:
  509. fs = u'{}-{}-{}-{}[{{}}]'.format(self.al_id.sid,self.pw_id_str,self.pw_fmt,self.pw_len)
  510. self.id_str = AddrListIDStr(self,fs)
  511. qmsg(u'Checksum for {} data {}: {}'.format(self.data_desc,self.id_str.hl(),self.chksum.hl()))
  512. qmsg(self.msgs[('record_chksum','check_chksum')[bool(infile)]])
  513. def set_pw_fmt(self,pw_fmt):
  514. assert pw_fmt in self.pw_info
  515. self.pw_fmt = pw_fmt
  516. def chk_pw_len(self,passwd=None):
  517. if passwd is None:
  518. assert self.pw_len
  519. pw_len = self.pw_len
  520. fs = '{l}: invalid user-requested length for {b} ({c}{m})'
  521. else:
  522. pw_len = len(passwd)
  523. fs = '{pw}: {b} has invalid length {l} ({c}{m} characters)'
  524. d = self.pw_info[self.pw_fmt]
  525. if pw_len > d['max_len']:
  526. die(2,fs.format(l=pw_len,b=d['desc'],c='>',m=d['max_len'],pw=passwd))
  527. elif pw_len < d['min_len']:
  528. die(2,fs.format(l=pw_len,b=d['desc'],c='<',m=d['min_len'],pw=passwd))
  529. def set_pw_len(self,pw_len):
  530. assert self.pw_fmt in self.pw_info
  531. d = self.pw_info[self.pw_fmt]
  532. if pw_len is None:
  533. self.pw_len = d['dfl_len']
  534. return
  535. if not is_int(pw_len):
  536. die(2,"'{}': invalid user-requested password length (not an integer)".format(pw_len,d['desc']))
  537. self.pw_len = int(pw_len)
  538. self.chk_pw_len()
  539. def make_passwd(self,hex_sec):
  540. assert self.pw_fmt in self.pw_info
  541. # we take least significant part
  542. return ''.join(baseconv.fromhex(hex_sec,self.pw_fmt,pad=self.pw_len))[-self.pw_len:]
  543. def chk_addr_or_pw(self,pw):
  544. if not (is_b58_str,is_b32_str)[self.pw_fmt=='b32'](pw):
  545. msg('Password is not a valid {} string'.format(self.pw_fmt))
  546. return False
  547. if len(pw) != self.pw_len:
  548. msg('Password has incorrect length ({} != {})'.format(len(pw),self.pw_len))
  549. return False
  550. return True
  551. def cook_seed(self,seed):
  552. from mmgen.crypto import sha256_rounds
  553. # Changing either pw_fmt, pw_len or id_str will cause a different, unrelated set of
  554. # passwords to be generated: this is what we want
  555. # NB: In original implementation, pw_id_str was 'baseN', not 'bN'
  556. fid_str = '{}:{}:{}'.format(self.pw_fmt,self.pw_len,self.pw_id_str.encode('utf8'))
  557. dmsg(u'Full ID string: {}'.format(fid_str.decode('utf8')))
  558. # Original implementation was 'cseed = seed + fid_str'; hmac was not used
  559. import hmac
  560. cseed = hmac.new(seed,fid_str,sha256).digest()
  561. dmsg('Seed: {}\nCooked seed: {}\nCooked seed len: {}'.format(hexlify(seed),hexlify(cseed),len(cseed)))
  562. return sha256_rounds(cseed,self.cook_hash_rounds)
  563. class AddrData(MMGenObject):
  564. msgs = {
  565. 'too_many_acct_addresses': """
  566. ERROR: More than one address found for account: '%s'.
  567. Your 'wallet.dat' file appears to have been altered by a non-{pnm} program.
  568. Please restore your tracking wallet from a backup or create a new one and
  569. re-import your addresses.
  570. """.strip().format(pnm=pnm)
  571. }
  572. def __init__(self,source=None):
  573. self.al_ids = {}
  574. if source == 'tw': self.add_tw_data()
  575. def seed_ids(self):
  576. return self.al_ids.keys()
  577. def addrlist(self,al_id):
  578. # TODO: Validate al_id
  579. if al_id in self.al_ids:
  580. return self.al_ids[al_id]
  581. def mmaddr2btcaddr(self,mmaddr):
  582. al_id,idx = MMGenID(mmaddr).rsplit(':',1)
  583. btcaddr = ''
  584. if al_id in self.al_ids:
  585. btcaddr = self.addrlist(al_id).btcaddr(int(idx))
  586. return btcaddr or None
  587. def btcaddr2mmaddr(self,btcaddr):
  588. d = self.make_reverse_dict([btcaddr])
  589. return (d.values()[0][0]) if d else None
  590. def add_tw_data(self):
  591. vmsg('Getting address data from tracking wallet')
  592. c = bitcoin_connection()
  593. accts = c.listaccounts(0,True)
  594. data,i = {},0
  595. alists = c.getaddressesbyaccount([[k] for k in accts],batch=True)
  596. for acct,addrlist in zip(accts,alists):
  597. l = TwLabel(acct,on_fail='silent')
  598. if l and l.mmid.type == 'mmgen':
  599. obj = l.mmid.obj
  600. i += 1
  601. if len(addrlist) != 1:
  602. die(2,self.msgs['too_many_acct_addresses'] % acct)
  603. al_id = AddrListID(SeedID(sid=obj.sid),MMGenAddrType(obj.mmtype))
  604. if al_id not in data:
  605. data[al_id] = []
  606. data[al_id].append(AddrListEntry(idx=obj.idx,addr=addrlist[0],label=l.comment))
  607. vmsg('{n} {pnm} addresses found, {m} accounts total'.format(n=i,pnm=pnm,m=len(accts)))
  608. for al_id in data:
  609. self.add(AddrList(al_id=al_id,adata=AddrListList(sorted(data[al_id],key=lambda a: a.idx))))
  610. def add(self,addrlist):
  611. if type(addrlist) == AddrList:
  612. self.al_ids[addrlist.al_id] = addrlist
  613. return True
  614. else:
  615. raise TypeError, 'Error: object %s is not of type AddrList' % repr(addrlist)
  616. def make_reverse_dict(self,btcaddrs):
  617. d = MMGenDict()
  618. for al_id in self.al_ids:
  619. d.update(self.al_ids[al_id].make_reverse_dict(btcaddrs))
  620. return d