tool.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660
  1. #!/usr/bin/env python
  2. # -*- coding: UTF-8 -*-
  3. #
  4. # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
  5. # Copyright (C)2013-2017 Philemon <mmgen-py@yandex.com>
  6. #
  7. # This program is free software: you can redistribute it and/or modify
  8. # it under the terms of the GNU General Public License as published by
  9. # the Free Software Foundation, either version 3 of the License, or
  10. # (at your option) any later version.
  11. #
  12. # This program is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU General Public License
  18. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  19. """
  20. tool.py: Routines and data for the 'mmgen-tool' utility
  21. """
  22. import binascii as ba
  23. import mmgen.bitcoin as mmb
  24. from mmgen.common import *
  25. from mmgen.crypto import *
  26. from mmgen.tx import *
  27. from mmgen.addr import *
  28. pnm = g.proj_name
  29. from collections import OrderedDict
  30. cmd_data = OrderedDict([
  31. ('Help', ['<tool command> [str]']),
  32. ('Usage', ['<tool command> [str]']),
  33. ('Strtob58', ['<string> [str-]','pad [int=0]']),
  34. ('B58tostr', ['<b58 number> [str-]']),
  35. ('Hextob58', ['<hex number> [str-]','pad [int=0]']),
  36. ('B58tohex', ['<b58 number> [str-]','pad [int=0]']),
  37. ('B58randenc', []),
  38. ('B32tohex', ['<b32 num> [str-]','pad [int=0]']),
  39. ('Hextob32', ['<hex num> [str-]','pad [int=0]']),
  40. ('Randhex', ['nbytes [int=32]']),
  41. ('Id8', ['<infile> [str]']),
  42. ('Id6', ['<infile> [str]']),
  43. ('Hash160', ['<hexadecimal string> [str-]']),
  44. ('Hash256', ['<str, hexstr or filename> [str]', # TODO handle stdin
  45. 'hex_input [bool=False]','file_input [bool=False]']),
  46. ('Str2id6', ['<string (spaces are ignored)> [str-]']),
  47. ('Hexdump', ['<infile> [str]', 'cols [int=8]', 'line_nums [bool=True]']),
  48. ('Unhexdump', ['<infile> [str]']),
  49. ('Hexreverse', ['<hexadecimal string> [str-]']),
  50. ('Hexlify', ['<string> [str-]']),
  51. ('Rand2file', ['<outfile> [str]','<nbytes> [str]','threads [int=4]','silent [bool=False]']),
  52. ('Randwif', ['compressed [bool=False]']),
  53. ('Randpair', ['compressed [bool=False]','segwit [bool=False]']),
  54. ('Hex2wif', ['<private key in hex format> [str-]','compressed [bool=False]']),
  55. ('Wif2hex', ['<wif> [str-]']),
  56. ('Wif2addr', ['<wif> [str-]','segwit [bool=False]']),
  57. ('Wif2segwit_pair',['<wif> [str-]']),
  58. ('Hexaddr2addr', ['<btc address in hex format> [str-]']),
  59. ('Addr2hexaddr', ['<btc address> [str-]']),
  60. ('Privhex2addr', ['<private key in hex format> [str-]','compressed [bool=False]','segwit [bool=False]']),
  61. ('Privhex2pubhex',['<private key in hex format> [str-]','compressed [bool=False]']),
  62. ('Pubhex2addr', ['<public key in hex format> [str-]','p2sh [bool=False]']), # new
  63. ('Pubhex2redeem_script',['<public key in hex format> [str-]']), # new
  64. ('Wif2redeem_script', ['<private key in WIF format> [str-]']), # new
  65. ('Hex2mn', ['<hexadecimal string> [str-]',"wordlist [str='electrum']"]),
  66. ('Mn2hex', ['<mnemonic> [str-]', "wordlist [str='electrum']"]),
  67. ('Mn_rand128', ["wordlist [str='electrum']"]),
  68. ('Mn_rand192', ["wordlist [str='electrum']"]),
  69. ('Mn_rand256', ["wordlist [str='electrum']"]),
  70. ('Mn_stats', ["wordlist [str='electrum']"]),
  71. ('Mn_printlist', ["wordlist [str='electrum']"]),
  72. ('Listaddress',['<{} address> [str]'.format(pnm),'minconf [int=1]','pager [bool=False]','showempty [bool=True]''showbtcaddr [bool=True]']),
  73. ('Listaddresses',["addrs [str='']",'minconf [int=1]','showempty [bool=False]','pager [bool=False]','showbtcaddrs [bool=True]','all_labels [bool=False]']),
  74. ('Getbalance', ['minconf [int=1]','quiet [bool=False]']),
  75. ('Txview', ['<{} TX file(s)> [str]'.format(pnm),'pager [bool=False]','terse [bool=False]',"sort [str='mtime'] (options: 'ctime','atime')",'MARGS']),
  76. ('Twview', ["sort [str='age']",'reverse [bool=False]','show_days [bool=True]','show_mmid [bool=True]','minconf [int=1]','wide [bool=False]','pager [bool=False]']),
  77. ('Add_label', ['<{} address> [str]'.format(pnm),'<label> [str]']),
  78. ('Remove_label', ['<{} address> [str]'.format(pnm)]),
  79. ('Addrfile_chksum', ['<{} addr file> [str]'.format(pnm)]),
  80. ('Keyaddrfile_chksum', ['<{} addr file> [str]'.format(pnm)]),
  81. ('Passwdfile_chksum', ['<{} password file> [str]'.format(pnm)]),
  82. ('Find_incog_data', ['<file or device name> [str]','<Incog ID> [str]','keep_searching [bool=False]']),
  83. ('Encrypt', ['<infile> [str]',"outfile [str='']","hash_preset [str='']"]),
  84. ('Decrypt', ['<infile> [str]',"outfile [str='']","hash_preset [str='']"]),
  85. ('Bytespec', ['<bytespec> [str]']),
  86. ('Regtest_setup',[]),
  87. ])
  88. def usage(command):
  89. for v in cmd_data.values():
  90. if v and v[0][-2:] == '-]':
  91. v[0] = v[0][:-2] + ' or STDIN]'
  92. if 'MARGS' in v: v.remove('MARGS')
  93. if not command:
  94. Msg('Usage information for mmgen-tool commands:')
  95. for k,v in cmd_data.items():
  96. Msg(' {:18} {}'.format(k.lower(),' '.join(v)))
  97. from mmgen.main_tool import stdin_msg
  98. Msg('\n '+'\n '.join(stdin_msg.split('\n')))
  99. sys.exit(0)
  100. Command = command.capitalize()
  101. if Command in cmd_data:
  102. import re
  103. from mmgen.main_tool import cmd_help
  104. for line in cmd_help.split('\n'):
  105. if re.match(r'\s+{}\s+'.format(command),line):
  106. c,h = line.split('-',1)
  107. Msg('MMGEN-TOOL {}: {}'.format(c.strip().upper(),h.strip()))
  108. cd = cmd_data[Command]
  109. msg('USAGE: %s %s %s' % (g.prog_name, command, ' '.join(cd)))
  110. else:
  111. msg("'%s': no such tool command" % command)
  112. sys.exit(1)
  113. Help = usage
  114. def process_args(command,cmd_args):
  115. if 'MARGS' in cmd_data[command]:
  116. cmd_data[command].remove('MARGS')
  117. margs = True
  118. else:
  119. margs = False
  120. c_args = [[i.split(' [')[0],i.split(' [')[1][:-1]]
  121. for i in cmd_data[command] if '=' not in i]
  122. c_kwargs = dict([[
  123. i.split(' [')[0],
  124. [i.split(' [')[1].split('=')[0], i.split(' [')[1].split('=')[1][:-1]]
  125. ] for i in cmd_data[command] if '=' in i])
  126. if not margs:
  127. u_args = [a for a in cmd_args[:len(c_args)]]
  128. if c_args and c_args[0][1][-1] == '-':
  129. c_args[0][1] = c_args[0][1][:-1] # [str-] -> [str]
  130. # If we're reading from a pipe, replace '-' with output of previous command
  131. if u_args and u_args[0] == '-':
  132. if not sys.stdin.isatty():
  133. u_args[0] = sys.stdin.read().strip()
  134. if not u_args[0]:
  135. die(2,'{}: ERROR: no output from previous command in pipe'.format(command.lower()))
  136. if not margs and len(u_args) < len(c_args):
  137. m1 = 'Command requires exactly %s non-keyword argument%s'
  138. msg(m1 % (len(c_args),suf(c_args,'s')))
  139. usage(command)
  140. extra_args = len(cmd_args) - len(c_args)
  141. u_kwargs = {}
  142. if margs:
  143. t = [a.split('=') for a in cmd_args if '=' in a]
  144. tk = [a[0] for a in t]
  145. tk_bad = [a for a in tk if a not in c_kwargs]
  146. if set(tk_bad) != set(tk[:len(tk_bad)]):
  147. die(1,"'{}': illegal keyword argument".format(tk_bad[-1]))
  148. u_kwargs = dict(t[len(tk_bad):])
  149. u_args = cmd_args[:-len(u_kwargs) or None]
  150. elif extra_args > 0:
  151. u_kwargs = dict([a.split('=') for a in cmd_args[len(c_args):] if '=' in a])
  152. if len(u_kwargs) != extra_args:
  153. msg('Command requires exactly %s non-keyword argument%s'
  154. % (len(c_args),suf(c_args,'s')))
  155. usage(command)
  156. if len(u_kwargs) > len(c_kwargs):
  157. msg('Command requires exactly %s keyword argument%s'
  158. % (len(c_kwargs),suf(c_kwargs,'s')))
  159. usage(command)
  160. # mdie(c_args,c_kwargs,u_args,u_kwargs)
  161. for k in u_kwargs:
  162. if k not in c_kwargs:
  163. msg("'%s': invalid keyword argument" % k)
  164. usage(command)
  165. def conv_type(arg,arg_name,arg_type):
  166. if arg_type == 'str': arg_type = 'unicode'
  167. if arg_type == 'bool':
  168. if arg.lower() in ('true','yes','1','on'): arg = True
  169. elif arg.lower() in ('false','no','0','off'): arg = False
  170. else:
  171. msg("'%s': invalid boolean value for keyword argument" % arg)
  172. usage(command)
  173. try:
  174. return __builtins__[arg_type](arg)
  175. except:
  176. die(1,"'%s': Invalid argument for argument %s ('%s' required)" % \
  177. (arg, arg_name, arg_type))
  178. if margs:
  179. args = [conv_type(u_args[i],c_args[0][0],c_args[0][1]) for i in range(len(u_args))]
  180. else:
  181. args = [conv_type(u_args[i],c_args[i][0],c_args[i][1]) for i in range(len(c_args))]
  182. kwargs = dict([(k,conv_type(u_kwargs[k],k,c_kwargs[k][0])) for k in u_kwargs])
  183. return args,kwargs
  184. # Individual cmd_data
  185. def are_equal(a,b,dtype=''):
  186. if dtype == 'str': return a.lstrip('\0') == b.lstrip('\0')
  187. if dtype == 'hex': return a.lstrip('0') == b.lstrip('0')
  188. if dtype == 'b58': return a.lstrip('1') == b.lstrip('1')
  189. else: return a == b
  190. def print_convert_results(indata,enc,dec,dtype):
  191. error = (True,False)[are_equal(indata,dec,dtype)]
  192. if error or opt.verbose:
  193. Msg('Input: %s' % repr(indata))
  194. Msg('Encoded data: %s' % repr(enc))
  195. Msg('Recoded data: %s' % repr(dec))
  196. else: Msg(enc)
  197. if error:
  198. die(3,"Error! Recoded data doesn't match input!")
  199. kg = KeyGenerator()
  200. def Hexdump(infile, cols=8, line_nums=True):
  201. Msg(pretty_hexdump(
  202. get_data_from_file(infile,dash=True,silent=True,binary=True),
  203. cols=cols,line_nums=line_nums))
  204. def Unhexdump(infile):
  205. if g.platform == 'win':
  206. import msvcrt
  207. msvcrt.setmode(sys.stdout.fileno(),os.O_BINARY)
  208. sys.stdout.write(decode_pretty_hexdump(
  209. get_data_from_file(infile,dash=True,silent=True)))
  210. def B58randenc():
  211. r = get_random(32)
  212. enc = baseconv.b58encode(r,pad=True)
  213. dec = baseconv.b58decode(enc,pad=True)
  214. print_convert_results(r,enc,dec,'str')
  215. def Randhex(nbytes='32'):
  216. Msg(ba.hexlify(get_random(int(nbytes))))
  217. def Randwif(compressed=False):
  218. Msg(PrivKey(get_random(32),compressed).wif)
  219. def Randpair(compressed=False,segwit=False):
  220. if segwit: compressed = True
  221. ag = AddrGenerator(('p2pkh','segwit')[bool(segwit)])
  222. privhex = PrivKey(get_random(32),compressed)
  223. addr = ag.to_addr(kg.to_pubhex(privhex))
  224. Vmsg('Key (hex): %s' % privhex)
  225. Vmsg_r('Key (WIF): '); Msg(privhex.wif)
  226. Vmsg_r('Addr: '); Msg(addr)
  227. def Wif2addr(wif,segwit=False):
  228. privhex = PrivKey(wif=wif)
  229. if segwit and not privhex.compressed:
  230. die(2,'Segwit addresses must use compressed public keys')
  231. ag = AddrGenerator(('p2pkh','segwit')[bool(segwit)])
  232. addr = ag.to_addr(kg.to_pubhex(privhex))
  233. Vmsg_r('Addr: '); Msg(addr)
  234. def Wif2segwit_pair(wif):
  235. privhex = PrivKey(wif=wif)
  236. if not privhex.compressed:
  237. die(1,'Segwit address cannot be generated from uncompressed WIF')
  238. ag = AddrGenerator('segwit')
  239. pubhex = kg.to_pubhex(privhex)
  240. addr = ag.to_addr(pubhex)
  241. rs = ag.to_segwit_redeem_script(pubhex)
  242. Msg('{}\n{}'.format(rs,addr))
  243. def Hexaddr2addr(hexaddr): Msg(mmb.hexaddr2addr(hexaddr))
  244. def Addr2hexaddr(addr): Msg(mmb.verify_addr(addr,return_dict=True)['hex'])
  245. def Hash160(pubkeyhex): Msg(mmb.hash160(pubkeyhex))
  246. def Pubhex2addr(pubkeyhex,p2sh=False): Msg(mmb.hexaddr2addr(mmb.hash160(pubkeyhex),p2sh=p2sh))
  247. def Wif2hex(wif): Msg(wif2hex(wif))
  248. def Hex2wif(hexpriv,compressed=False):
  249. Msg(mmb.hex2wif(hexpriv,compressed))
  250. def Privhex2addr(privhex,compressed=False,segwit=False):
  251. if segwit and not compressed:
  252. die(1,'Segwit address can be generated only from a compressed pubkey')
  253. Msg(mmb.privnum2addr(int(privhex,16),compressed,segwit=segwit))
  254. def Privhex2pubhex(privhex,compressed=False): # new
  255. Msg(mmb.privnum2pubhex(int(privhex,16),compressed))
  256. def Pubhex2redeem_script(pubhex): # new
  257. Msg(mmb.pubhex2redeem_script(pubhex))
  258. def Wif2redeem_script(wif): # new
  259. privhex = PrivKey(wif=wif)
  260. if not privhex.compressed:
  261. die(1,'Segwit redeem script cannot be generated from uncompressed WIF')
  262. ag = AddrGenerator('segwit')
  263. Msg(ag.to_segwit_redeem_script(kg.to_pubhex(privhex)))
  264. def wif2hex(wif): # wrapper
  265. ret = PrivKey(wif=wif)
  266. return ret or die(1,'{}: Invalid WIF'.format(wif))
  267. wordlists = 'electrum','tirosh'
  268. dfl_wl_id = 'electrum'
  269. def do_random_mn(nbytes,wordlist):
  270. hexrand = ba.hexlify(get_random(nbytes))
  271. Vmsg('Seed: %s' % hexrand)
  272. for wl_id in ([wordlist],wordlists)[wordlist=='all']:
  273. if wordlist == 'all':
  274. Msg('%s mnemonic:' % (capfirst(wl_id)))
  275. mn = baseconv.fromhex(hexrand,wl_id)
  276. Msg(' '.join(mn))
  277. def Mn_rand128(wordlist=dfl_wl_id): do_random_mn(16,wordlist)
  278. def Mn_rand192(wordlist=dfl_wl_id): do_random_mn(24,wordlist)
  279. def Mn_rand256(wordlist=dfl_wl_id): do_random_mn(32,wordlist)
  280. def Hex2mn(s,wordlist=dfl_wl_id): Msg(' '.join(baseconv.fromhex(s,wordlist)))
  281. def Mn2hex(s,wordlist=dfl_wl_id): Msg(baseconv.tohex(s.split(),wordlist))
  282. def Strtob58(s,pad=None): Msg(''.join(baseconv.fromhex(ba.hexlify(s),'b58',pad)))
  283. def Hextob58(s,pad=None): Msg(''.join(baseconv.fromhex(s,'b58',pad)))
  284. def Hextob32(s,pad=None): Msg(''.join(baseconv.fromhex(s,'b32',pad)))
  285. def B58tostr(s): Msg(ba.unhexlify(baseconv.tohex(s,'b58')))
  286. def B58tohex(s,pad=None): Msg(baseconv.tohex(s,'b58',pad))
  287. def B32tohex(s,pad=None): Msg(baseconv.tohex(s.upper(),'b32',pad))
  288. from mmgen.seed import Mnemonic
  289. def Mn_stats(wordlist=dfl_wl_id):
  290. wordlist in baseconv.digits or die(1,"'{}': not a valid wordlist".format(wordlist))
  291. baseconv.check_wordlist(wordlist)
  292. def Mn_printlist(wordlist=dfl_wl_id):
  293. wordlist in baseconv.digits or die(1,"'{}': not a valid wordlist".format(wordlist))
  294. Msg('\n'.join(baseconv.digits[wordlist]))
  295. def Id8(infile):
  296. Msg(make_chksum_8(
  297. get_data_from_file(infile,dash=True,silent=True,binary=True)
  298. ))
  299. def Id6(infile):
  300. Msg(make_chksum_6(
  301. get_data_from_file(infile,dash=True,silent=True,binary=True)
  302. ))
  303. def Str2id6(s): # retain ignoring of space for backwards compat
  304. Msg(make_chksum_6(''.join(s.split())))
  305. def Listaddress(addr,minconf=1,pager=False,showempty=True,showbtcaddr=True):
  306. return Listaddresses(addrs=addr,minconf=minconf,pager=pager,showempty=showempty,showbtcaddrs=showbtcaddr)
  307. # List MMGen addresses and their balances. TODO: move this code to AddrList
  308. def Listaddresses(addrs='',minconf=1,showempty=False,pager=False,showbtcaddrs=True,all_labels=False):
  309. c = rpc_connection()
  310. def check_dup_mmid(acct_labels):
  311. mmid_prev,err = None,False
  312. for mmid in sorted(a.mmid for a in acct_labels if a):
  313. if mmid == mmid_prev:
  314. err = True
  315. msg('Duplicate MMGen ID ({}) discovered in tracking wallet!\n'.format(mmid))
  316. mmid_prev = mmid
  317. if err: rdie(3,'Tracking wallet is corrupted!')
  318. def check_addr_array_lens(acct_pairs):
  319. err = False
  320. for label,addrs in acct_pairs:
  321. if not label: continue
  322. if len(addrs) != 1:
  323. err = True
  324. if len(addrs) == 0:
  325. msg("Label '{}': has no associated address!".format(label))
  326. else:
  327. msg("'{}': more than one {} address in account!".format(addrs,g.coin))
  328. if err: rdie(3,'Tracking wallet is corrupted!')
  329. usr_addr_list = []
  330. if addrs:
  331. a = addrs.rsplit(':',1)
  332. if len(a) != 2:
  333. m = "'{}': invalid address list argument (must be in form <seed ID>:[<type>:]<idx list>)"
  334. die(1,m.format(addrs))
  335. usr_addr_list = [MMGenID('{}:{}'.format(a[0],i)) for i in AddrIdxList(a[1])]
  336. class TwAddrList(dict,MMGenObject): pass
  337. addrs = TwAddrList() # reusing name!
  338. total = BTCAmt('0')
  339. for d in c.listunspent(0):
  340. if not 'account' in d: continue # skip coinbase outputs with missing account
  341. if d['confirmations'] < minconf: continue
  342. label = TwLabel(d['account'],on_fail='silent')
  343. if label:
  344. if usr_addr_list and (label.mmid not in usr_addr_list): continue
  345. if label.mmid in addrs:
  346. if addrs[label.mmid]['addr'] != d['address']:
  347. die(2,'duplicate {} address ({}) for this MMGen address! ({})'.format(
  348. g.coin,d['address'],addrs[label.mmid]['addr']))
  349. else:
  350. addrs[label.mmid] = { 'amt':BTCAmt('0'), 'lbl':label, 'addr':BTCAddr(d['address']) }
  351. addrs[label.mmid]['amt'] += d['amount']
  352. total += d['amount']
  353. # We use listaccounts only for empty addresses, as it shows false positive balances
  354. if showempty or all_labels:
  355. # for compatibility with old mmids, must use raw RPC rather than native data for matching
  356. # args: minconf,watchonly, MUST use keys() so we get list, not dict
  357. acct_list = c.listaccounts(0,True).keys() # raw list, no 'L'
  358. acct_labels = MMGenList([TwLabel(a,on_fail='silent') for a in acct_list])
  359. check_dup_mmid(acct_labels)
  360. acct_addrs = c.getaddressesbyaccount([[a] for a in acct_list],batch=True) # use raw list here
  361. assert len(acct_list) == len(acct_addrs), 'listaccounts() and getaddressesbyaccount() not equal in length'
  362. addr_pairs = zip(acct_labels,acct_addrs)
  363. check_addr_array_lens(addr_pairs)
  364. for label,addr_arr in addr_pairs:
  365. if not label: continue
  366. if all_labels and not showempty and not label.comment: continue
  367. if usr_addr_list and (label.mmid not in usr_addr_list): continue
  368. if label.mmid not in addrs:
  369. addrs[label.mmid] = { 'amt':BTCAmt('0'), 'lbl':label, 'addr':'' }
  370. if showbtcaddrs:
  371. addrs[label.mmid]['addr'] = BTCAddr(addr_arr[0])
  372. if not addrs:
  373. die(0,('No tracked addresses with balances!','No tracked addresses!')[showempty])
  374. out = ([],[green('Chain: {}'.format(g.chain.upper()))])[g.chain in ('testnet','regtest')]
  375. fs = ('{mid} {cmt} {amt}','{mid} {addr} {cmt} {amt}')[showbtcaddrs]
  376. mmaddrs = [k for k in addrs.keys() if k.type == 'mmgen']
  377. max_mmid_len = max(len(k) for k in mmaddrs) + 2 if mmaddrs else 10
  378. max_cmt_len = max(max(len(addrs[k]['lbl'].comment) for k in addrs),7)
  379. out += [fs.format(
  380. mid=MMGenID.fmtc('MMGenID',width=max_mmid_len),
  381. addr=BTCAddr.fmtc('ADDRESS'),
  382. cmt=TwComment.fmtc('COMMENT',width=max_cmt_len),
  383. amt='BALANCE'
  384. )]
  385. al_id_save = None
  386. for mmid in sorted(addrs,key=lambda j: j.sort_key):
  387. if mmid.type == 'mmgen':
  388. if al_id_save and al_id_save != mmid.obj.al_id:
  389. out.append('')
  390. al_id_save = mmid.obj.al_id
  391. mmid_disp = mmid
  392. else:
  393. if al_id_save:
  394. out.append('')
  395. al_id_save = None
  396. mmid_disp = 'Non-MMGen'
  397. out.append(fs.format(
  398. mid = MMGenID.fmtc(mmid_disp,width=max_mmid_len,color=True),
  399. addr=(addrs[mmid]['addr'].fmt(color=True) if showbtcaddrs else None),
  400. cmt=addrs[mmid]['lbl'].comment.fmt(width=max_cmt_len,color=True,nullrepl='-'),
  401. amt=addrs[mmid]['amt'].fmt('3.0',color=True)))
  402. out.append('\nTOTAL: {} {}'.format(total.hl(color=True),g.coin))
  403. o = '\n'.join(out)
  404. return do_pager(o) if pager else Msg(o)
  405. def Getbalance(minconf=1,quiet=False):
  406. accts = {}
  407. for d in rpc_connection().listunspent(0):
  408. ma = split2(d['account'] if 'account' in d else '')[0] # include coinbase outputs if spendable
  409. keys = ['TOTAL']
  410. if d['spendable']: keys += ['SPENDABLE']
  411. if is_mmgen_id(ma): keys += [ma.split(':')[0]]
  412. confs = d['confirmations']
  413. i = (1,2)[confs >= minconf]
  414. for key in keys:
  415. if key not in accts: accts[key] = [BTCAmt('0')] * 3
  416. for j in ([],[0])[confs==0] + [i]:
  417. accts[key][j] += d['amount']
  418. if quiet:
  419. Msg('{}'.format(accts['TOTAL'][2] if accts else BTCAmt('0')))
  420. else:
  421. fs = '{:13} {} {} {}'
  422. mc,lbl = str(minconf),'confirms'
  423. Msg(fs.format('Wallet',
  424. *[s.ljust(16) for s in ' Unconfirmed',' <%s %s'%(mc,lbl),' >=%s %s'%(mc,lbl)]))
  425. for key in sorted(accts.keys()):
  426. Msg(fs.format(key+':', *[a.fmt(color=True,suf=' '+g.coin) for a in accts[key]]))
  427. if 'SPENDABLE' in accts:
  428. Msg(red('Warning: this wallet contains PRIVATE KEYS for the SPENDABLE balance!'))
  429. def Txview(*infiles,**kwargs):
  430. from mmgen.filename import MMGenFileList
  431. pager = 'pager' in kwargs and kwargs['pager']
  432. terse = 'terse' in kwargs and kwargs['terse']
  433. sort_key = kwargs['sort'] if 'sort' in kwargs else 'mtime'
  434. flist = MMGenFileList(infiles,ftype=MMGenTX)
  435. flist.sort_by_age(key=sort_key) # in-place sort
  436. from mmgen.term import get_terminal_size
  437. sep = u'—'*77+'\n'
  438. out = sep.join([MMGenTX(fn).format_view(terse=terse) for fn in flist.names()])
  439. (Msg,do_pager)[pager](out.rstrip())
  440. def Twview(pager=False,reverse=False,wide=False,minconf=1,sort='age',show_days=True,show_mmid=True):
  441. from mmgen.tw import MMGenTrackingWallet
  442. tw = MMGenTrackingWallet(minconf=minconf)
  443. tw.do_sort(sort,reverse=reverse)
  444. tw.show_days = show_days
  445. tw.show_mmid = show_mmid
  446. out = tw.format_for_printing(color=True) if wide else tw.format_for_display()
  447. (Msg_r,do_pager)[pager](out)
  448. def Add_label(mmaddr,label):
  449. from mmgen.tw import MMGenTrackingWallet
  450. MMGenTrackingWallet.add_label(mmaddr,label) # dies on failure
  451. def Remove_label(mmaddr): Add_label(mmaddr,'')
  452. def Addrfile_chksum(infile):
  453. from mmgen.addr import AddrList
  454. AddrList(infile,chksum_only=True)
  455. def Keyaddrfile_chksum(infile):
  456. from mmgen.addr import KeyAddrList
  457. KeyAddrList(infile,chksum_only=True)
  458. def Passwdfile_chksum(infile):
  459. from mmgen.addr import PasswordList
  460. PasswordList(infile=infile,chksum_only=True)
  461. def Hexreverse(s):
  462. Msg(ba.hexlify(ba.unhexlify(s.strip())[::-1]))
  463. def Hexlify(s):
  464. Msg(ba.hexlify(s))
  465. def Hash256(s, file_input=False, hex_input=False):
  466. from hashlib import sha256
  467. if file_input: b = get_data_from_file(s,binary=True)
  468. elif hex_input: b = decode_pretty_hexdump(s)
  469. else: b = s
  470. Msg(sha256(sha256(b).digest()).hexdigest())
  471. def Encrypt(infile,outfile='',hash_preset=''):
  472. data = get_data_from_file(infile,'data for encryption',binary=True)
  473. enc_d = mmgen_encrypt(data,'user data',hash_preset)
  474. if not outfile:
  475. outfile = '%s.%s' % (os.path.basename(infile),g.mmenc_ext)
  476. write_data_to_file(outfile,enc_d,'encrypted data',binary=True)
  477. def Decrypt(infile,outfile='',hash_preset=''):
  478. enc_d = get_data_from_file(infile,'encrypted data',binary=True)
  479. while True:
  480. dec_d = mmgen_decrypt(enc_d,'user data',hash_preset)
  481. if dec_d: break
  482. msg('Trying again...')
  483. if not outfile:
  484. o = os.path.basename(infile)
  485. outfile = remove_extension(o,g.mmenc_ext)
  486. if outfile == o: outfile += '.dec'
  487. write_data_to_file(outfile,dec_d,'decrypted data',binary=True)
  488. def Find_incog_data(filename,iv_id,keep_searching=False):
  489. ivsize,bsize,mod = g.aesctr_iv_len,4096,4096*8
  490. n,carry = 0,' '*ivsize
  491. flgs = os.O_RDONLY|os.O_BINARY if g.platform == 'win' else os.O_RDONLY
  492. f = os.open(filename,flgs)
  493. for ch in iv_id:
  494. if ch not in '0123456789ABCDEF':
  495. die(2,"'%s': invalid Incog ID" % iv_id)
  496. while True:
  497. d = os.read(f,bsize)
  498. if not d: break
  499. d = carry + d
  500. for i in range(bsize):
  501. if sha256(d[i:i+ivsize]).hexdigest()[:8].upper() == iv_id:
  502. if n+i < ivsize: continue
  503. msg('\rIncog data for ID %s found at offset %s' %
  504. (iv_id,n+i-ivsize))
  505. if not keep_searching: sys.exit(0)
  506. carry = d[len(d)-ivsize:]
  507. n += bsize
  508. if not n % mod: msg_r('\rSearched: %s bytes' % n)
  509. msg('')
  510. os.close(f)
  511. def Rand2file(outfile, nbytes, threads=4, silent=False):
  512. nbytes = parse_nbytes(nbytes)
  513. from Crypto import Random
  514. rh = Random.new()
  515. from Queue import Queue
  516. from threading import Thread
  517. bsize = 2**20
  518. roll = bsize * 4
  519. if opt.outdir: outfile = make_full_path(opt.outdir,outfile)
  520. f = open(outfile,'wb')
  521. from Crypto.Cipher import AES
  522. from Crypto.Util import Counter
  523. key = get_random(32)
  524. def encrypt_worker(wid):
  525. while True:
  526. i,d = q1.get()
  527. c = AES.new(key, AES.MODE_CTR,
  528. counter=Counter.new(g.aesctr_iv_len*8,initial_value=i))
  529. enc_data = c.encrypt(d)
  530. q2.put(enc_data)
  531. q1.task_done()
  532. def output_worker():
  533. while True:
  534. data = q2.get()
  535. f.write(data)
  536. q2.task_done()
  537. q1 = Queue()
  538. for i in range(max(1,threads-2)):
  539. t = Thread(target=encrypt_worker, args=(i,))
  540. t.daemon = True
  541. t.start()
  542. q2 = Queue()
  543. t = Thread(target=output_worker)
  544. t.daemon = True
  545. t.start()
  546. i = 1; rbytes = nbytes
  547. while rbytes > 0:
  548. d = rh.read(min(bsize,rbytes))
  549. q1.put((i,d))
  550. rbytes -= bsize
  551. i += 1
  552. if not (bsize*i) % roll:
  553. msg_r('\rRead: %s bytes' % (bsize*i))
  554. if not silent:
  555. msg('\rRead: %s bytes' % nbytes)
  556. qmsg("\r%s bytes of random data written to file '%s'" % (nbytes,outfile))
  557. q1.join()
  558. q2.join()
  559. f.close()
  560. def Bytespec(s): Msg(str(parse_nbytes(s)))
  561. def Regtest_setup():
  562. print 'ok'
  563. return
  564. import subprocess as sp
  565. sp.check_output()
  566. pass