tool.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645
  1. #!/usr/bin/env python
  2. #
  3. # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
  4. # Copyright (C)2013-2016 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. tool.py: Routines and data for the 'mmgen-tool' utility
  20. """
  21. import binascii as ba
  22. import mmgen.bitcoin as bitcoin
  23. from mmgen.common import *
  24. from mmgen.crypto import *
  25. from mmgen.tx import *
  26. pnm = g.proj_name
  27. from collections import OrderedDict
  28. cmd_data = OrderedDict([
  29. ('help', ['<tool command> [str]']),
  30. ('usage', ['<tool command> [str]']),
  31. ('strtob58', ['<string> [str-]']),
  32. ('b58tostr', ['<b58 number> [str-]']),
  33. ('hextob58', ['<hex number> [str-]']),
  34. ('b58tohex', ['<b58 number> [str-]']),
  35. ('b58randenc', []),
  36. ('b32tohex', ['<b32 num> [str-]']),
  37. ('hextob32', ['<hex num> [str-]']),
  38. ('randhex', ['nbytes [int=32]']),
  39. ('id8', ['<infile> [str]']),
  40. ('id6', ['<infile> [str]']),
  41. ('sha256x2', ['<str, hexstr or filename> [str]', # TODO handle stdin
  42. 'hex_input [bool=False]','file_input [bool=False]']),
  43. ('str2id6', ['<string (spaces are ignored)> [str-]']),
  44. ('hexdump', ['<infile> [str]', 'cols [int=8]', 'line_nums [bool=True]']),
  45. ('unhexdump', ['<infile> [str]']),
  46. ('hexreverse', ['<hexadecimal string> [str-]']),
  47. ('hexlify', ['<string> [str-]']),
  48. ('rand2file', ['<outfile> [str]','<nbytes> [str]','threads [int=4]','silent [bool=False]']),
  49. ('randwif', ['compressed [bool=False]']),
  50. ('randpair', ['compressed [bool=False]']),
  51. ('hex2wif', ['<private key in hex format> [str-]', 'compressed [bool=False]']),
  52. ('wif2hex', ['<wif> [str-]', 'compressed [bool=False]']),
  53. ('wif2addr', ['<wif> [str-]', 'compressed [bool=False]']),
  54. ('hexaddr2addr', ['<btc address in hex format> [str-]']),
  55. ('addr2hexaddr', ['<btc address> [str-]']),
  56. ('pubkey2addr', ['<public key in hex format> [str-]']),
  57. ('pubkey2hexaddr', ['<public key in hex format> [str-]']),
  58. ('privhex2addr', ['<private key in hex format> [str-]','compressed [bool=False]']),
  59. ('hex2mn', ['<hexadecimal string> [str-]',"wordlist [str='electrum']"]),
  60. ('mn2hex', ['<mnemonic> [str-]', "wordlist [str='electrum']"]),
  61. ('mn_rand128', ["wordlist [str='electrum']"]),
  62. ('mn_rand192', ["wordlist [str='electrum']"]),
  63. ('mn_rand256', ["wordlist [str='electrum']"]),
  64. ('mn_stats', ["wordlist [str='electrum']"]),
  65. ('mn_printlist', ["wordlist [str='electrum']"]),
  66. ('listaddresses',["addrs [str='']",'minconf [int=1]','showempty [bool=False]','pager [bool=False]','showbtcaddrs [bool=False]']),
  67. ('getbalance', ['minconf [int=1]']),
  68. ('txview', ['<{} TX file> [str]'.format(pnm),'pager [bool=False]','terse [bool=False]']),
  69. ('twview', ["sort [str='age']",'reverse [bool=False]','minconf [int=1]','wide [bool=False]','pager [bool=False]']),
  70. ('add_label', ['<{} address> [str]'.format(pnm),'<label> [str]']),
  71. ('remove_label', ['<{} address> [str]'.format(pnm)]),
  72. ('addrfile_chksum', ['<{} addr file> [str]'.format(pnm)]),
  73. ('keyaddrfile_chksum', ['<{} addr file> [str]'.format(pnm)]),
  74. ('find_incog_data', ['<file or device name> [str]','<Incog ID> [str]','keep_searching [bool=False]']),
  75. ('encrypt', ['<infile> [str]',"outfile [str='']","hash_preset [str='']"]),
  76. ('decrypt', ['<infile> [str]',"outfile [str='']","hash_preset [str='']"]),
  77. ('bytespec', ['<bytespec> [str]']),
  78. ])
  79. cmd_help = """
  80. Bitcoin address/key operations (compressed public keys supported):
  81. addr2hexaddr - convert Bitcoin address from base58 to hex format
  82. hex2wif - convert a private key from hex to WIF format
  83. hexaddr2addr - convert Bitcoin address from hex to base58 format
  84. privhex2addr - generate Bitcoin address from private key in hex format
  85. pubkey2addr - convert Bitcoin public key to address
  86. pubkey2hexaddr - convert Bitcoin public key to address in hex format
  87. randpair - generate a random private key/address pair
  88. randwif - generate a random private key in WIF format
  89. wif2addr - generate a Bitcoin address from a key in WIF format
  90. wif2hex - convert a private key from WIF to hex format
  91. Wallet/TX operations (bitcoind must be running):
  92. getbalance - like 'bitcoin-cli getbalance' but shows confirmed/unconfirmed,
  93. spendable/unspendable balances for individual {pnm} wallets
  94. listaddresses - list {pnm} addresses and their balances
  95. txview - show raw/signed {pnm} transaction in human-readable form
  96. twview - view tracking wallet
  97. General utilities:
  98. hexdump - encode data into formatted hexadecimal form (file or stdin)
  99. unhexdump - decode formatted hexadecimal data (file or stdin)
  100. bytespec - convert a byte specifier such as '1GB' into an integer
  101. hexlify - display string in hexadecimal format
  102. hexreverse - reverse bytes of a hexadecimal string
  103. rand2file - write 'n' bytes of random data to specified file
  104. randhex - print 'n' bytes (default 32) of random data in hex format
  105. sha256x2 - compute a double sha256 hash of data
  106. b58randenc - generate a random 32-byte number and convert it to base 58
  107. b58tostr - convert a base 58 number to a string
  108. strtob58 - convert a string to base 58
  109. b58tohex - convert a base 58 number to hexadecimal
  110. hextob58 - convert a hexadecimal number to base 58
  111. b32tohex - convert a base 32 number to hexadecimal
  112. hextob32 - convert a hexadecimal number to base 32
  113. File encryption:
  114. encrypt - encrypt a file
  115. decrypt - decrypt a file
  116. {pnm} encryption suite:
  117. * Key: Scrypt (user-configurable hash parameters, 32-byte salt)
  118. * Enc: AES256_CTR, 16-byte rand IV, sha256 hash + 32-byte nonce + data
  119. * The encrypted file is indistinguishable from random data
  120. {pnm}-specific operations:
  121. add_label - add descriptive label for {pnm} address in tracking wallet
  122. remove_label - remove descriptive label for {pnm} address in tracking wallet
  123. addrfile_chksum - compute checksum for {pnm} address file
  124. keyaddrfile_chksum - compute checksum for {pnm} key-address file
  125. find_incog_data - Use an Incog ID to find hidden incognito wallet data
  126. id6 - generate 6-character {pnm} ID for a file (or stdin)
  127. id8 - generate 8-character {pnm} ID for a file (or stdin)
  128. str2id6 - generate 6-character {pnm} ID for a string, ignoring spaces
  129. Mnemonic operations (choose 'electrum' (default), 'tirosh' or 'all'
  130. wordlists):
  131. mn_rand128 - generate random 128-bit mnemonic
  132. mn_rand192 - generate random 192-bit mnemonic
  133. mn_rand256 - generate random 256-bit mnemonic
  134. mn_stats - show stats for mnemonic wordlist
  135. mn_printlist - print mnemonic wordlist
  136. hex2mn - convert a 16, 24 or 32-byte number in hex format to a mnemonic
  137. mn2hex - convert a 12, 18 or 24-word mnemonic to a number in hex format
  138. IMPORTANT NOTE: Though {pnm} mnemonics use the Electrum wordlist, they're
  139. computed using a different algorithm and are NOT Electrum-compatible!
  140. """.format(pnm=pnm)
  141. def tool_usage(prog_name, command):
  142. if command in cmd_data:
  143. for line in cmd_help.split('\n'):
  144. if ' ' + command in line:
  145. c,h = line.split('-',1)
  146. Msg('MMGEN-TOOL {}: {}'.format(c.strip().upper(),h.strip()))
  147. cd = cmd_data[command]
  148. if cd and cd[0][-2:] == '-]':
  149. cd[0] = cd[0][:-2] + ' or STDIN]'
  150. msg('USAGE: %s %s %s' % (prog_name, command, ' '.join(cd)))
  151. else:
  152. msg("'%s': no such tool command" % command)
  153. sys.exit(1)
  154. def process_args(prog_name, command, cmd_args):
  155. c_args = [[i.split(' [')[0],i.split(' [')[1][:-1]]
  156. for i in cmd_data[command] if '=' not in i]
  157. c_kwargs = dict([[
  158. i.split(' [')[0],
  159. [i.split(' [')[1].split('=')[0], i.split(' [')[1].split('=')[1][:-1]]
  160. ] for i in cmd_data[command] if '=' in i])
  161. u_args = [a for a in cmd_args[:len(c_args)]]
  162. if c_args and c_args[0][1][-1] == '-':
  163. c_args[0][1] = c_args[0][1][:-1] # [str-] -> [str]
  164. # If we're reading from a pipe, make the input the first argument
  165. if len(u_args) < len(c_kwargs) + len(c_args):
  166. if not sys.stdin.isatty():
  167. u_args = [sys.stdin.read()] + u_args
  168. if len(u_args) < len(c_args):
  169. m1 = 'Command requires exactly %s non-keyword argument%s'
  170. msg(m1 % (len(c_args),suf(c_args,'k')))
  171. tool_usage(prog_name,command)
  172. # print u_args
  173. extra_args = len(cmd_args) - len(c_args)
  174. u_kwargs = {}
  175. if extra_args > 0:
  176. u_kwargs = dict([a.split('=') for a in cmd_args[len(c_args):] if '=' in a])
  177. if len(u_kwargs) != extra_args:
  178. msg('Command requires exactly %s non-keyword argument%s'
  179. % (len(c_args),suf(c_args,'k')))
  180. tool_usage(prog_name,command)
  181. if len(u_kwargs) > len(c_kwargs):
  182. msg('Command requires exactly %s keyword argument%s'
  183. % (len(c_kwargs),suf(c_kwargs,'k')))
  184. tool_usage(prog_name,command)
  185. # mdie(c_args,c_kwargs,u_args,u_kwargs)
  186. for k in u_kwargs:
  187. if k not in c_kwargs:
  188. msg("'%s': invalid keyword argument" % k)
  189. tool_usage(prog_name,command)
  190. def conv_type(arg,arg_name,arg_type):
  191. if arg_type == 'bool':
  192. if arg.lower() in ('true','yes','1','on'): arg = True
  193. elif arg.lower() in ('false','no','0','off'): arg = False
  194. else:
  195. msg("'%s': invalid boolean value for keyword argument" % arg)
  196. tool_usage(prog_name,command)
  197. try:
  198. return __builtins__[arg_type](arg)
  199. except:
  200. die(1,"'%s': Invalid argument for argument %s ('%s' required)" % \
  201. (arg, arg_name, arg_type))
  202. args = [conv_type(u_args[i],c_args[i][0],c_args[i][1]) for i in range(len(c_args))]
  203. kwargs = dict([(k,conv_type(u_kwargs[k],k,c_kwargs[k][0])) for k in u_kwargs])
  204. # mdie(args,kwargs)
  205. return args,kwargs
  206. # Individual cmd_data
  207. def are_equal(a,b,dtype=''):
  208. if dtype == 'str': return a.lstrip('\0') == b.lstrip('\0')
  209. if dtype == 'hex': return a.lstrip('0') == b.lstrip('0')
  210. if dtype == 'b58': return a.lstrip('1') == b.lstrip('1')
  211. else: return a == b
  212. def print_convert_results(indata,enc,dec,dtype):
  213. error = (True,False)[are_equal(indata,dec,dtype)]
  214. if error or opt.verbose:
  215. Msg('Input: %s' % repr(indata))
  216. Msg('Encoded data: %s' % repr(enc))
  217. Msg('Recoded data: %s' % repr(dec))
  218. else: Msg(enc)
  219. if error:
  220. die(3,"Error! Recoded data doesn't match input!")
  221. def usage(cmd):
  222. tool_usage(g.prog_name, cmd)
  223. help = usage
  224. def hexdump(infile, cols=8, line_nums=True):
  225. Msg(pretty_hexdump(
  226. get_data_from_file(infile,dash=True,silent=True,binary=True),
  227. cols=cols,line_nums=line_nums))
  228. def unhexdump(infile):
  229. if g.platform == 'win':
  230. import msvcrt
  231. msvcrt.setmode(sys.stdout.fileno(),os.O_BINARY)
  232. sys.stdout.write(decode_pretty_hexdump(
  233. get_data_from_file(infile,dash=True,silent=True)))
  234. def strtob58(s):
  235. enc = bitcoin.b58encode(s)
  236. dec = bitcoin.b58decode(enc)
  237. print_convert_results(s,enc,dec,'str')
  238. def hextob58(s,f_enc=bitcoin.b58encode, f_dec=bitcoin.b58decode):
  239. s = s.strip()
  240. enc = f_enc(ba.unhexlify(s))
  241. dec = ba.hexlify(f_dec(enc))
  242. print_convert_results(s,enc,dec,'hex')
  243. def b58tohex(s,f_enc=bitcoin.b58decode, f_dec=bitcoin.b58encode):
  244. s = s.strip()
  245. tmp = f_enc(s)
  246. if tmp == False: die(1,"Unable to decode string '%s'" % s)
  247. enc = ba.hexlify(tmp)
  248. dec = f_dec(ba.unhexlify(enc))
  249. print_convert_results(s,enc,dec,'b58')
  250. def b58tostr(s,f_enc=bitcoin.b58decode, f_dec=bitcoin.b58encode):
  251. s = s.strip()
  252. enc = f_enc(s)
  253. if enc == False: die(1,"Unable to decode string '%s'" % s)
  254. dec = f_dec(enc)
  255. print_convert_results(s,enc,dec,'b58')
  256. def b58randenc():
  257. r = get_random(32)
  258. enc = bitcoin.b58encode(r)
  259. dec = bitcoin.b58decode(enc)
  260. print_convert_results(r,enc,dec,'str')
  261. def randhex(nbytes='32'):
  262. Msg(ba.hexlify(get_random(int(nbytes))))
  263. def randwif(compressed=False):
  264. r_hex = ba.hexlify(get_random(32))
  265. enc = bitcoin.hex2wif(r_hex,compressed)
  266. dec = bitcoin.wif2hex(enc)
  267. print_convert_results(r_hex,enc,dec,'hex')
  268. def randpair(compressed=False):
  269. r_hex = ba.hexlify(get_random(32))
  270. wif = bitcoin.hex2wif(r_hex,compressed)
  271. addr = bitcoin.privnum2addr(int(r_hex,16),compressed)
  272. Vmsg('Key (hex): %s' % r_hex)
  273. Vmsg_r('Key (WIF): '); Msg(wif)
  274. Vmsg_r('Addr: '); Msg(addr)
  275. def wif2addr(wif,compressed=False):
  276. s_enc = bitcoin.wif2hex(wif)
  277. if s_enc == False:
  278. die(1,'Invalid address')
  279. addr = bitcoin.privnum2addr(int(s_enc,16),compressed)
  280. Vmsg_r('Addr: '); Msg(addr)
  281. wordlists = 'electrum','tirosh'
  282. dfl_wordlist = 'electrum'
  283. from mmgen.seed import Mnemonic
  284. def do_random_mn(nbytes,wordlist):
  285. hexrand = ba.hexlify(get_random(nbytes))
  286. Vmsg('Seed: %s' % hexrand)
  287. for wlname in ([wordlist],wordlists)[wordlist=='all']:
  288. if wordlist == 'all':
  289. Msg('%s mnemonic:' % (wlname.capitalize()))
  290. mn = Mnemonic.hex2mn(hexrand,wordlist=wlname)
  291. Msg(' '.join(mn))
  292. def mn_rand128(wordlist=dfl_wordlist): do_random_mn(16,wordlist)
  293. def mn_rand192(wordlist=dfl_wordlist): do_random_mn(24,wordlist)
  294. def mn_rand256(wordlist=dfl_wordlist): do_random_mn(32,wordlist)
  295. def hex2mn(s,wordlist=dfl_wordlist):
  296. mn = Mnemonic.hex2mn(s,wordlist)
  297. Msg(' '.join(mn))
  298. def mn2hex(s,wordlist=dfl_wordlist):
  299. hexnum = Mnemonic.mn2hex(s.split(),wordlist)
  300. Msg(hexnum)
  301. def b32tohex(s):
  302. b32a = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'
  303. Msg(Mnemonic.baseNtohex(32,s.upper(),b32a))
  304. def hextob32(s):
  305. b32a = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'
  306. Msg(''.join(Mnemonic.hextobaseN(32,s,b32a)))
  307. def mn_stats(wordlist=dfl_wordlist):
  308. Mnemonic.check_wordlist(wordlist)
  309. def mn_printlist(wordlist=dfl_wordlist):
  310. wl = Mnemonic.get_wordlist(wordlist)
  311. Msg('\n'.join(wl))
  312. def id8(infile):
  313. Msg(make_chksum_8(
  314. get_data_from_file(infile,dash=True,silent=True,binary=True)
  315. ))
  316. def id6(infile):
  317. Msg(make_chksum_6(
  318. get_data_from_file(infile,dash=True,silent=True,binary=True)
  319. ))
  320. def str2id6(s): # retain ignoring of space for backwards compat
  321. Msg(make_chksum_6(''.join(s.split())))
  322. # List MMGen addresses and their balances:
  323. def listaddresses(addrs='',minconf=1,showempty=False,pager=False,showbtcaddrs=False):
  324. # TODO - move some or all of this code to AddrList
  325. usr_addr_list = []
  326. if addrs:
  327. sid_s,idxs = split2(addrs,':')
  328. sid = SeedID(sid=sid_s)
  329. usr_addr_list = ['{}:{}'.format(sid,a) for a in AddrIdxList(idxs)]
  330. c = bitcoin_connection()
  331. addrs = {} # reusing variable name!
  332. total = BTCAmt('0')
  333. for d in c.listunspent(0):
  334. mmaddr,comment = split2(d['account'])
  335. if usr_addr_list and (mmaddr not in usr_addr_list): continue
  336. if (mmaddr[:4] == 'btc:' or is_mmgen_id(mmaddr)) and d['confirmations'] >= minconf:
  337. key = mmaddr.replace(':','_')
  338. if key in addrs:
  339. if addrs[key][2] != d['address']:
  340. die(2,'duplicate BTC address ({}) for this MMGen address! ({})'.format(
  341. (d['address'], addrs[key][2])))
  342. else:
  343. addrs[key] = [BTCAmt('0'),MMGenAddrLabel(comment),BTCAddr(d['address'])]
  344. addrs[key][0] += d['amount']
  345. total += d['amount']
  346. # We use listaccounts only for empty addresses, as it shows false positive balances
  347. if showempty:
  348. accts = c.listaccounts(0,True) # minconf,watchonly
  349. save_a = []
  350. for acct in accts:
  351. mmaddr,comment = split2(acct)
  352. if usr_addr_list and (mmaddr not in usr_addr_list): continue
  353. if mmaddr[:4] == 'btc:' or is_mmgen_id(mmaddr):
  354. key = mmaddr.replace(':','_')
  355. if key not in addrs:
  356. if showbtcaddrs: save_a.append([acct])
  357. addrs[key] = [BTCAmt('0'),MMGenAddrLabel(comment),'']
  358. for acct,addr in zip(save_a,c.getaddressesbyaccount(save_a,batch=True)):
  359. if len(addr) != 1:
  360. die(2,"Account '%s' has more or less than one BTC address!" % addr)
  361. key = split2(acct[0])[0].replace(':','_')
  362. addrs[key][2] = BTCAddr(addr[0])
  363. if not addrs:
  364. die(0,('No addresses with balances!','No tracked addresses!')[showempty])
  365. fs = ('{mid} {lbl} {amt}','{mid} {addr} {lbl} {amt}')[showbtcaddrs]
  366. max_mmid_len = max([len(k) for k in addrs if k[:4] != 'btc_'] or [10])
  367. max_lbl_len = max(len(addrs[k][1]) for k in addrs) or 7
  368. out = [fs.format(
  369. mid=MMGenID.fmtc('MMGenID',width=max_mmid_len),
  370. addr=BTCAddr.fmtc('ADDRESS'),
  371. lbl=MMGenAddrLabel.fmtc('COMMENT',width=max_lbl_len),
  372. amt='BALANCE'
  373. )]
  374. old_sid = ''
  375. def s_mmgen(k): return '{:>0{w}}'.format(k,w=AddrIdx.max_digits+9) # TODO
  376. for k in sorted(addrs,key=s_mmgen):
  377. if old_sid and old_sid != k.split('_')[0]: out.append('')
  378. old_sid = k.split('_')[0]
  379. m = 'non-'+g.proj_name if k[:4] == 'btc_' else k.replace('_',':')
  380. out.append(fs.format(
  381. mid = MMGenID.fmtc(m,width=max_mmid_len,color=True),
  382. addr=(addrs[k][2].fmt(color=True) if showbtcaddrs else None),
  383. lbl=addrs[k][1].fmt(width=max_lbl_len,color=True,nullrepl='-'),
  384. amt=addrs[k][0].fmt('3.0',color=True)))
  385. out.append('\nTOTAL: %s BTC' % total.hl(color=True))
  386. o = '\n'.join(out)
  387. if pager: do_pager(o)
  388. else: Msg(o)
  389. def getbalance(minconf=1):
  390. accts = {}
  391. for d in bitcoin_connection().listunspent(0):
  392. ma = split2(d['account'])[0]
  393. keys = ['TOTAL']
  394. if d['spendable']: keys += ['SPENDABLE']
  395. if is_mmgen_id(ma): keys += [ma.split(':')[0]]
  396. confs = d['confirmations']
  397. i = (1,2)[confs >= minconf]
  398. for key in keys:
  399. if key not in accts: accts[key] = [BTCAmt('0')] * 3
  400. for j in ([],[0])[confs==0] + [i]:
  401. accts[key][j] += d['amount']
  402. fs = '{:13} {} {} {}'
  403. mc,lbl = str(minconf),'confirms'
  404. Msg(fs.format('Wallet',
  405. *[s.ljust(16) for s in ' Unconfirmed',' <%s %s'%(mc,lbl),' >=%s %s'%(mc,lbl)]))
  406. for key in sorted(accts.keys()):
  407. Msg(fs.format(key+':', *[a.fmt(color=True,suf=' BTC') for a in accts[key]]))
  408. def txview(infile,pager=False,terse=False):
  409. c = bitcoin_connection()
  410. tx = MMGenTX(infile)
  411. tx.view(pager,pause=False,terse=terse)
  412. def twview(pager=False,reverse=False,wide=False,minconf=1,sort='age'):
  413. from mmgen.tw import MMGenTrackingWallet
  414. tw = MMGenTrackingWallet(minconf=minconf)
  415. tw.do_sort(sort,reverse=reverse)
  416. out = tw.format_for_printing(color=True) if wide else tw.format_for_display()
  417. do_pager(out) if pager else sys.stdout.write(out)
  418. def add_label(mmaddr,label):
  419. from mmgen.tw import MMGenTrackingWallet
  420. if MMGenTrackingWallet.add_label(mmaddr,label): # returns on failure
  421. s = '{pnm} address {a} in tracking wallet'.format(a=mmaddr,pnm=pnm)
  422. if label: msg("Added label '{}' for {}".format(label,s))
  423. else: msg('Removed label for {}'.format(s))
  424. else:
  425. die(1,'Label could not be %s' % ('removed','added')[bool(label)])
  426. def remove_label(mmaddr): add_label(mmaddr,'')
  427. def addrfile_chksum(infile):
  428. from mmgen.addr import AddrList
  429. AddrList(infile,chksum_only=True)
  430. def keyaddrfile_chksum(infile):
  431. from mmgen.addr import KeyAddrList
  432. KeyAddrList(infile,chksum_only=True)
  433. def hexreverse(s):
  434. Msg(ba.hexlify(ba.unhexlify(s.strip())[::-1]))
  435. def hexlify(s):
  436. Msg(ba.hexlify(s))
  437. def sha256x2(s, file_input=False, hex_input=False):
  438. from hashlib import sha256
  439. if file_input: b = get_data_from_file(s,binary=True)
  440. elif hex_input: b = decode_pretty_hexdump(s)
  441. else: b = s
  442. Msg(sha256(sha256(b).digest()).hexdigest())
  443. def hexaddr2addr(hexaddr):
  444. Msg(bitcoin.hexaddr2addr(hexaddr))
  445. def addr2hexaddr(addr):
  446. Msg(bitcoin.verify_addr(addr,return_hex=True))
  447. def pubkey2hexaddr(pubkeyhex):
  448. Msg(bitcoin.pubhex2hexaddr(pubkeyhex))
  449. def pubkey2addr(pubkeyhex):
  450. Msg(bitcoin.hexaddr2addr(bitcoin.pubhex2hexaddr(pubkeyhex)))
  451. def privhex2addr(privkeyhex,compressed=False):
  452. Msg(bitcoin.privnum2addr(int(privkeyhex,16),compressed))
  453. def wif2hex(wif,compressed=False):
  454. Msg(bitcoin.wif2hex(wif))
  455. def hex2wif(hexpriv,compressed=False):
  456. Msg(bitcoin.hex2wif(hexpriv,compressed))
  457. def encrypt(infile,outfile='',hash_preset=''):
  458. data = get_data_from_file(infile,'data for encryption',binary=True)
  459. enc_d = mmgen_encrypt(data,'user data',hash_preset)
  460. if not outfile:
  461. outfile = '%s.%s' % (os.path.basename(infile),g.mmenc_ext)
  462. write_data_to_file(outfile,enc_d,'encrypted data',binary=True)
  463. def decrypt(infile,outfile='',hash_preset=''):
  464. enc_d = get_data_from_file(infile,'encrypted data',binary=True)
  465. while True:
  466. dec_d = mmgen_decrypt(enc_d,'user data',hash_preset)
  467. if dec_d: break
  468. msg('Trying again...')
  469. if not outfile:
  470. o = os.path.basename(infile)
  471. outfile = remove_extension(o,g.mmenc_ext)
  472. if outfile == o: outfile += '.dec'
  473. write_data_to_file(outfile,dec_d,'decrypted data',binary=True)
  474. def find_incog_data(filename,iv_id,keep_searching=False):
  475. ivsize,bsize,mod = g.aesctr_iv_len,4096,4096*8
  476. n,carry = 0,' '*ivsize
  477. flgs = os.O_RDONLY|os.O_BINARY if g.platform == 'win' else os.O_RDONLY
  478. f = os.open(filename,flgs)
  479. for ch in iv_id:
  480. if ch not in '0123456789ABCDEF':
  481. die(2,"'%s': invalid Incog ID" % iv_id)
  482. while True:
  483. d = os.read(f,bsize)
  484. if not d: break
  485. d = carry + d
  486. for i in range(bsize):
  487. if sha256(d[i:i+ivsize]).hexdigest()[:8].upper() == iv_id:
  488. if n+i < ivsize: continue
  489. msg('\rIncog data for ID %s found at offset %s' %
  490. (iv_id,n+i-ivsize))
  491. if not keep_searching: sys.exit()
  492. carry = d[len(d)-ivsize:]
  493. n += bsize
  494. if not n % mod: msg_r('\rSearched: %s bytes' % n)
  495. msg('')
  496. os.close(f)
  497. def rand2file(outfile, nbytes, threads=4, silent=False):
  498. nbytes = parse_nbytes(nbytes)
  499. from Crypto import Random
  500. rh = Random.new()
  501. from Queue import Queue
  502. from threading import Thread
  503. bsize = 2**20
  504. roll = bsize * 4
  505. if opt.outdir: outfile = make_full_path(opt.outdir,outfile)
  506. f = open(outfile,'wb')
  507. from Crypto.Cipher import AES
  508. from Crypto.Util import Counter
  509. key = get_random(32)
  510. def encrypt_worker(wid):
  511. while True:
  512. i,d = q1.get()
  513. c = AES.new(key, AES.MODE_CTR,
  514. counter=Counter.new(g.aesctr_iv_len*8,initial_value=i))
  515. enc_data = c.encrypt(d)
  516. q2.put(enc_data)
  517. q1.task_done()
  518. def output_worker():
  519. while True:
  520. data = q2.get()
  521. f.write(data)
  522. q2.task_done()
  523. q1 = Queue()
  524. for i in range(max(1,threads-2)):
  525. t = Thread(target=encrypt_worker, args=(i,))
  526. t.daemon = True
  527. t.start()
  528. q2 = Queue()
  529. t = Thread(target=output_worker)
  530. t.daemon = True
  531. t.start()
  532. i = 1; rbytes = nbytes
  533. while rbytes > 0:
  534. d = rh.read(min(bsize,rbytes))
  535. q1.put((i,d))
  536. rbytes -= bsize
  537. i += 1
  538. if not (bsize*i) % roll:
  539. msg_r('\rRead: %s bytes' % (bsize*i))
  540. if not silent:
  541. msg('\rRead: %s bytes' % nbytes)
  542. qmsg("\r%s bytes of random data written to file '%s'" % (nbytes,outfile))
  543. q1.join()
  544. q2.join()
  545. f.close()
  546. def bytespec(s): Msg(str(parse_nbytes(s)))