From c7fcf448d95685a564f29395df7b90b5dcfe977e Mon Sep 17 00:00:00 2001 From: philemon Date: Thu, 15 Dec 2016 14:08:03 +0300 Subject: [PATCH] [tool]: accept stdin input for selected utils [bitcoin/seed]: input sanitizing and checking for base conversion routines [test]: replace env var with --popen-spawn option --- mmgen/bitcoin.py | 10 +++++-- mmgen/color.py | 2 +- mmgen/main.py | 2 +- mmgen/rpc.py | 2 ++ mmgen/seed.py | 10 ++++++- mmgen/term.py | 10 +++---- mmgen/test.py | 2 +- mmgen/tool.py | 72 +++++++++++++++++++++++++++++------------------- mmgen/util.py | 4 +-- test/test.py | 5 ++-- 10 files changed, 74 insertions(+), 45 deletions(-) diff --git a/mmgen/bitcoin.py b/mmgen/bitcoin.py index 3ef80ed8..1fd13ddf 100755 --- a/mmgen/bitcoin.py +++ b/mmgen/bitcoin.py @@ -54,12 +54,12 @@ b58a='123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' from mmgen.globalvars import g def pubhex2hexaddr(pubhex): - step1 = sha256(unhexlify(pubhex)).digest() + step1 = sha256(unhexlify(pubhex.strip())).digest() return hashlib_new('ripemd160',step1).hexdigest() def hexaddr2addr(hexaddr,p2sh=False): # devdoc/ref_transactions.md: - hexaddr2 = ('00','6f','05','c4')[g.testnet+(2*p2sh)] + hexaddr + hexaddr2 = ('00','6f','05','c4')[g.testnet+(2*p2sh)] + hexaddr.strip() step1 = sha256(unhexlify(hexaddr2)).digest() step2 = sha256(step1).hexdigest() pubkey = hexaddr2 + step2[:8] @@ -67,6 +67,7 @@ def hexaddr2addr(hexaddr,p2sh=False): return ('1' * lzeroes) + _numtob58(int(pubkey,16)) def verify_addr(addr,verbose=False,return_hex=False): + addr = addr.strip() for vers_num,ldigit in ('00','1'),('05','3'),('6f','mn'),('c4','2'): if addr[0] not in ldigit: continue num = _b58tonum(addr) @@ -95,6 +96,7 @@ def _numtob58(num): return ''.join(ret)[::-1] def _b58tonum(b58num): + b58num = b58num.strip() for i in b58num: if not i in b58a: return False return sum([b58a.index(n) * (58**i) for i,n in enumerate(list(b58num[::-1]))]) @@ -110,6 +112,7 @@ def b58encode(s): return _numtob58(num) def b58decode(b58num): + b58num = b58num.strip() if b58num == '': return '' # Zap all spaces: # Use translate() only with str, not unicode @@ -146,6 +149,7 @@ def b58decode_pad(s): # Compressed address support: def wif2hex(wif): + wif = wif.strip() compressed = wif[0] != ('5','9')[g.testnet] idx = (66,68)[bool(compressed)] num = _b58tonum(wif) @@ -157,7 +161,7 @@ def wif2hex(wif): return key[2:66] if (key[:2] == ('80','ef')[g.testnet] and key[idx:] == round2[:8]) else False def hex2wif(hexpriv,compressed=False): - step1 = ('80','ef')[g.testnet] + hexpriv + ('','01')[bool(compressed)] + step1 = ('80','ef')[g.testnet] + hexpriv.strip() + ('','01')[bool(compressed)] step2 = sha256(unhexlify(step1)).digest() step3 = sha256(step2).hexdigest() key = step1 + step3[:8] diff --git a/mmgen/color.py b/mmgen/color.py index 756c7011..de216c97 100755 --- a/mmgen/color.py +++ b/mmgen/color.py @@ -52,7 +52,7 @@ for c in _colors: else: globals()['_16_'+c] = '\033[{};{}m'.format(*e[1]) globals()['_clr_'+c] = ''; _reset = '' - exec "def {c}(s): return _clr_{c}+s+_reset".format(c=c) + exec 'def {c}(s): return _clr_{c}+s+_reset'.format(c=c) def nocolor(s): return s diff --git a/mmgen/main.py b/mmgen/main.py index a8ccd15f..55782b95 100755 --- a/mmgen/main.py +++ b/mmgen/main.py @@ -31,7 +31,7 @@ def launch(what): __import__('mmgen.main_' + what) else: import sys,os,atexit - if not os.getenv('MMGEN_PEXPECT_POPEN_SPAWN'): + if sys.stdin.isatty(): fd = sys.stdin.fileno() old = termios.tcgetattr(fd) def at_exit(): termios.tcsetattr(fd, termios.TCSADRAIN, old) diff --git a/mmgen/rpc.py b/mmgen/rpc.py index f2a6e511..e0db0002 100755 --- a/mmgen/rpc.py +++ b/mmgen/rpc.py @@ -145,6 +145,7 @@ class BitcoinRPCConnection(object): 'createrawtransaction', 'backupwallet', 'decoderawtransaction', + 'disconnectnode', 'estimatefee', 'getaddressesbyaccount', 'getbalance', @@ -152,6 +153,7 @@ class BitcoinRPCConnection(object): 'getblockcount', 'getblockhash', 'getinfo', + 'getpeerinfo', 'importaddress', 'listaccounts', 'listunspent', diff --git a/mmgen/seed.py b/mmgen/seed.py index 7721f62d..c722f2ff 100755 --- a/mmgen/seed.py +++ b/mmgen/seed.py @@ -368,7 +368,12 @@ class Mnemonic (SeedSourceUnenc): def _hex2mn_pad(hexnum): return len(hexnum) * 3 / 8 @staticmethod - def baseNtohex(base,words,wl,pad=0): + def baseNtohex(base,words_arg,wl,pad=0): # accepts both string and list input + words = words_arg + if type(words) not in (list,tuple): + words = tuple(words.strip()) + if not set(words).issubset(set(wl)): + die(2,'{} is not in base-{} format'.format(repr(words_arg),base)) deconv = [wl.index(words[::-1][i])*(base**i) for i in range(len(words))] ret = ('{:0%sx}' % pad).format(sum(deconv)) @@ -376,6 +381,9 @@ class Mnemonic (SeedSourceUnenc): @staticmethod def hextobaseN(base,hexnum,wl,pad=0): + hexnum = hexnum.strip() + if not is_hexstring(hexnum): + die(2,"'%s': not a hexadecimal number" % hexnum) num,ret = int(hexnum,16),[] while num: ret.append(num % base) diff --git a/mmgen/term.py b/mmgen/term.py index fe2e2577..4a22f8b1 100755 --- a/mmgen/term.py +++ b/mmgen/term.py @@ -33,7 +33,7 @@ except: _platform = 'win' except: die(2,'Unable to set terminal mode') - if os.getenv('MMGEN_PEXPECT_POPEN_SPAWN'): + if not sys.stdin.isatty(): msvcrt.setmode(sys.stdin.fileno(),os.O_BINARY) def _kb_hold_protect_unix(): @@ -140,7 +140,7 @@ def _get_keypress_mswin_raw(prompt='',immed_chars='',prehold_protect=None): if ord(ch) == 3: raise KeyboardInterrupt return ch -def _get_keypress_mswin_emu(prompt='',immed_chars='',prehold_protect=None): +def _get_keypress_mswin_stub(prompt='',immed_chars='',prehold_protect=None): msg_r(prompt) return sys.stdin.read(1) @@ -200,12 +200,12 @@ def set_terminal_vars(): if _platform == 'linux': get_char = (_get_keypress_unix_raw,_get_keypress_unix)[g.hold_protect] kb_hold_protect = (_kb_hold_protect_unix_raw,_kb_hold_protect_unix)[g.hold_protect] - if os.getenv('MMGEN_PEXPECT_POPEN_SPAWN'): + if not sys.stdin.isatty(): get_char,kb_hold_protect = _get_keypress_unix_stub,_kb_hold_protect_unix_raw get_terminal_size = _get_terminal_size_linux else: get_char = (_get_keypress_mswin_raw,_get_keypress_mswin)[g.hold_protect] kb_hold_protect = (_kb_hold_protect_mswin_raw,_kb_hold_protect_mswin)[g.hold_protect] - if os.getenv('MMGEN_PEXPECT_POPEN_SPAWN'): - get_char = _get_keypress_mswin_emu + if not sys.stdin.isatty(): + get_char = _get_keypress_mswin_stub get_terminal_size = _get_terminal_size_mswin diff --git a/mmgen/test.py b/mmgen/test.py index e544388b..45472bd6 100755 --- a/mmgen/test.py +++ b/mmgen/test.py @@ -38,7 +38,7 @@ def cleandir(d): rmtree(os.path.join(d,f)) def getrandnum(n): return int(hexlify(os.urandom(n)),16) -def getrandhex(n): return hexlify(os.urandom(n)) +def getrandhex(n): return hexlify(os.urandom(n).lstrip('0')) def getrandstr(num_chars,no_space=False): n,m = 95,32 if no_space: n,m = 94,33 diff --git a/mmgen/tool.py b/mmgen/tool.py index ad1e32cd..79648aa3 100755 --- a/mmgen/tool.py +++ b/mmgen/tool.py @@ -33,38 +33,38 @@ from collections import OrderedDict cmd_data = OrderedDict([ ('help', [' [str]']), ('usage', [' [str]']), - ('strtob58', [' [str]']), - ('b58tostr', [' [str]']), - ('hextob58', [' [str]']), - ('b58tohex', [' [str]']), + ('strtob58', [' [str-]']), + ('b58tostr', [' [str-]']), + ('hextob58', [' [str-]']), + ('b58tohex', [' [str-]']), ('b58randenc', []), - ('b32tohex', [' [str]']), - ('hextob32', [' [str]']), + ('b32tohex', [' [str-]']), + ('hextob32', [' [str-]']), ('randhex', ['nbytes [int=32]']), ('id8', [' [str]']), ('id6', [' [str]']), - ('sha256x2', [' [str]', + ('sha256x2', [' [str]', # TODO handle stdin 'hex_input [bool=False]','file_input [bool=False]']), - ('str2id6', [' [str]']), + ('str2id6', [' [str-]']), ('hexdump', [' [str]', 'cols [int=8]', 'line_nums [bool=True]']), ('unhexdump', [' [str]']), - ('hexreverse', [' [str]']), - ('hexlify', [' [str]']), + ('hexreverse', [' [str-]']), + ('hexlify', [' [str-]']), ('rand2file', [' [str]',' [str]','threads [int=4]','silent [bool=False]']), ('randwif', ['compressed [bool=False]']), ('randpair', ['compressed [bool=False]']), - ('hex2wif', [' [str]', 'compressed [bool=False]']), - ('wif2hex', [' [str]', 'compressed [bool=False]']), - ('wif2addr', [' [str]', 'compressed [bool=False]']), - ('hexaddr2addr', [' [str]']), - ('addr2hexaddr', [' [str]']), - ('pubkey2addr', [' [str]']), - ('pubkey2hexaddr', [' [str]']), - ('privhex2addr', [' [str]','compressed [bool=False]']), + ('hex2wif', [' [str-]', 'compressed [bool=False]']), + ('wif2hex', [' [str-]', 'compressed [bool=False]']), + ('wif2addr', [' [str-]', 'compressed [bool=False]']), + ('hexaddr2addr', [' [str-]']), + ('addr2hexaddr', [' [str-]']), + ('pubkey2addr', [' [str-]']), + ('pubkey2hexaddr', [' [str-]']), + ('privhex2addr', [' [str-]','compressed [bool=False]']), - ('hex2mn', [' [str]',"wordlist [str='electrum']"]), - ('mn2hex', [' [str]', "wordlist [str='electrum']"]), + ('hex2mn', [' [str-]',"wordlist [str='electrum']"]), + ('mn2hex', [' [str-]', "wordlist [str='electrum']"]), ('mn_rand128', ["wordlist [str='electrum']"]), ('mn_rand192', ["wordlist [str='electrum']"]), ('mn_rand256', ["wordlist [str='electrum']"]), @@ -162,7 +162,10 @@ def tool_usage(prog_name, command): if ' ' + command in line: c,h = line.split('-',1) Msg('MMGEN-TOOL {}: {}'.format(c.strip().upper(),h.strip())) - msg('USAGE: %s %s %s' % (prog_name, command, ' '.join(cmd_data[command]))) + cd = cmd_data[command] + if cd and cd[0][-2:] == '-]': + cd[0] = cd[0][:-2] + ' or STDIN]' + msg('USAGE: %s %s %s' % (prog_name, command, ' '.join(cd))) else: msg("'%s': no such tool command" % command) sys.exit(1) @@ -176,10 +179,19 @@ def process_args(prog_name, command, cmd_args): ] for i in cmd_data[command] if '=' in i]) u_args = [a for a in cmd_args[:len(c_args)]] + if c_args and c_args[0][1][-1] == '-': + c_args[0][1] = c_args[0][1][:-1] # [str-] -> [str] + # If we're reading from a pipe, make the input the first argument + if len(u_args) < len(c_kwargs) + len(c_args): + if not sys.stdin.isatty(): + u_args = [sys.stdin.read()] + u_args + if len(u_args) < len(c_args): - msg('Command requires exactly %s non-keyword argument%s' % (len(c_args),suf(c_args,'k'))) + m1 = 'Command requires exactly %s non-keyword argument%s' + msg(m1 % (len(c_args),suf(c_args,'k'))) tool_usage(prog_name,command) +# print u_args extra_args = len(cmd_args) - len(c_args) u_kwargs = {} if extra_args > 0: @@ -260,20 +272,23 @@ def strtob58(s): print_convert_results(s,enc,dec,'str') def hextob58(s,f_enc=bitcoin.b58encode, f_dec=bitcoin.b58decode): + s = s.strip() enc = f_enc(ba.unhexlify(s)) dec = ba.hexlify(f_dec(enc)) print_convert_results(s,enc,dec,'hex') def b58tohex(s,f_enc=bitcoin.b58decode, f_dec=bitcoin.b58encode): + s = s.strip() tmp = f_enc(s) - if tmp == False: sys.exit(1) + if tmp == False: die(1,"Unable to decode string '%s'" % s) enc = ba.hexlify(tmp) dec = f_dec(ba.unhexlify(enc)) print_convert_results(s,enc,dec,'b58') def b58tostr(s,f_enc=bitcoin.b58decode, f_dec=bitcoin.b58encode): + s = s.strip() enc = f_enc(s) - if enc == False: sys.exit(1) + if enc == False: die(1,"Unable to decode string '%s'" % s) dec = f_dec(enc) print_convert_results(s,enc,dec,'b58') @@ -334,7 +349,7 @@ def mn2hex(s,wordlist=dfl_wordlist): def b32tohex(s): b32a = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567' - Msg(Mnemonic.baseNtohex(32,s,b32a)) + Msg(Mnemonic.baseNtohex(32,s.upper(),b32a)) def hextob32(s): b32a = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567' @@ -355,7 +370,8 @@ def id6(infile): Msg(make_chksum_6( get_data_from_file(infile,dash=True,silent=True,binary=True) )) -def str2id6(s): Msg(make_chksum_6(''.join(s.split()))) +def str2id6(s): # retain ignoring of space for backwards compat + Msg(make_chksum_6(''.join(s.split()))) # List MMGen addresses and their balances: def listaddresses(addrs='',minconf=1,showempty=False,pager=False,showbtcaddrs=False): @@ -487,8 +503,8 @@ def keyaddrfile_chksum(infile): from mmgen.addr import KeyAddrList KeyAddrList(infile,chksum_only=True) -def hexreverse(hex_str): - Msg(ba.hexlify(decode_pretty_hexdump(hex_str)[::-1])) +def hexreverse(s): + Msg(ba.hexlify(ba.unhexlify(s.strip())[::-1])) def hexlify(s): Msg(ba.hexlify(s)) diff --git a/mmgen/util.py b/mmgen/util.py index 2bba3453..0f3af1c5 100755 --- a/mmgen/util.py +++ b/mmgen/util.py @@ -493,7 +493,7 @@ def write_data_to_file( if opt.stdout or outfile in ('','-'): do_stdout() - elif not sys.stdout.isatty() and not os.getenv('MMGEN_PEXPECT_POPEN_SPAWN'): + elif sys.stdin.isatty() and not sys.stdout.isatty(): do_stdout() else: do_file(outfile,ask_write_prompt) @@ -581,7 +581,7 @@ def my_raw_input(prompt,echo=True,insert_txt='',use_readline=True): from mmgen.term import kb_hold_protect kb_hold_protect() - if echo or os.getenv('MMGEN_PEXPECT_POPEN_SPAWN'): + if echo or not sys.stdin.isatty(): reply = raw_input(prompt) else: from getpass import getpass diff --git a/test/test.py b/test/test.py index 7b537d11..63cbf716 100755 --- a/test/test.py +++ b/test/test.py @@ -142,6 +142,7 @@ opts_data = { -L, --log Log commands to file {lf} -n, --names Display command names instead of descriptions -I, --interactive Interactive mode (without pexpect) +-O, --popen-spawn Use pexpect's popen_spawn instead of popen -p, --pause Pause between tests, resuming on keypress -P, --profile Record the execution time of each script -q, --quiet Produce minimal output. Suppress dependency info @@ -668,7 +669,7 @@ if opt.list_cmds: import time,re if g.platform == 'linux': import pexpect - if os.getenv('MMGEN_PEXPECT_POPEN_SPAWN'): + if opt.popen_spawn: import termios,atexit def at_exit(): os.system('stty sane') atexit.register(at_exit) @@ -690,8 +691,6 @@ else: # Windows if not keypress_confirm(green(m1)+grnbg(m2)+green(m3),default_yes=True): errmsg('Exiting at user request') sys.exit() - else: - os.environ['MMGEN_PEXPECT_POPEN_SPAWN'] = '1' def my_send(p,t,delay=send_delay,s=False): if delay: time.sleep(delay)