Browse Source

[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

philemon 8 years ago
parent
commit
c7fcf448d9
10 changed files with 75 additions and 46 deletions
  1. 7 3
      mmgen/bitcoin.py
  2. 1 1
      mmgen/color.py
  3. 1 1
      mmgen/main.py
  4. 2 0
      mmgen/rpc.py
  5. 9 1
      mmgen/seed.py
  6. 5 5
      mmgen/term.py
  7. 1 1
      mmgen/test.py
  8. 45 29
      mmgen/tool.py
  9. 2 2
      mmgen/util.py
  10. 2 3
      test/test.py

+ 7 - 3
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]

+ 1 - 1
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
 

+ 1 - 1
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)

+ 2 - 0
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',

+ 9 - 1
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)

+ 5 - 5
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

+ 1 - 1
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

+ 45 - 29
mmgen/tool.py

@@ -33,38 +33,38 @@ from collections import OrderedDict
 cmd_data = OrderedDict([
 	('help',         ['<tool command> [str]']),
 	('usage',        ['<tool command> [str]']),
-	('strtob58',     ['<string> [str]']),
-	('b58tostr',     ['<b58 number> [str]']),
-	('hextob58',     ['<hex number> [str]']),
-	('b58tohex',     ['<b58 number> [str]']),
+	('strtob58',     ['<string> [str-]']),
+	('b58tostr',     ['<b58 number> [str-]']),
+	('hextob58',     ['<hex number> [str-]']),
+	('b58tohex',     ['<b58 number> [str-]']),
 	('b58randenc',   []),
-	('b32tohex',     ['<b32 num> [str]']),
-	('hextob32',     ['<hex num> [str]']),
+	('b32tohex',     ['<b32 num> [str-]']),
+	('hextob32',     ['<hex num> [str-]']),
 	('randhex',      ['nbytes [int=32]']),
 	('id8',          ['<infile> [str]']),
 	('id6',          ['<infile> [str]']),
-	('sha256x2',     ['<str, hexstr or filename> [str]',
+	('sha256x2',     ['<str, hexstr or filename> [str]', # TODO handle stdin
 							'hex_input [bool=False]','file_input [bool=False]']),
-	('str2id6',      ['<string (spaces are ignored)> [str]']),
+	('str2id6',      ['<string (spaces are ignored)> [str-]']),
 	('hexdump',      ['<infile> [str]', 'cols [int=8]', 'line_nums [bool=True]']),
 	('unhexdump',    ['<infile> [str]']),
-	('hexreverse',   ['<hexadecimal string> [str]']),
-	('hexlify',      ['<string> [str]']),
+	('hexreverse',   ['<hexadecimal string> [str-]']),
+	('hexlify',      ['<string> [str-]']),
 	('rand2file',    ['<outfile> [str]','<nbytes> [str]','threads [int=4]','silent [bool=False]']),
 
 	('randwif',    ['compressed [bool=False]']),
 	('randpair',   ['compressed [bool=False]']),
-	('hex2wif',    ['<private key in hex format> [str]', 'compressed [bool=False]']),
-	('wif2hex',    ['<wif> [str]', 'compressed [bool=False]']),
-	('wif2addr',   ['<wif> [str]', 'compressed [bool=False]']),
-	('hexaddr2addr', ['<btc address in hex format> [str]']),
-	('addr2hexaddr', ['<btc address> [str]']),
-	('pubkey2addr',  ['<public key in hex format> [str]']),
-	('pubkey2hexaddr', ['<public key in hex format> [str]']),
-	('privhex2addr', ['<private key in hex format> [str]','compressed [bool=False]']),
-
-	('hex2mn',       ['<hexadecimal string> [str]',"wordlist [str='electrum']"]),
-	('mn2hex',       ['<mnemonic> [str]', "wordlist [str='electrum']"]),
+	('hex2wif',    ['<private key in hex format> [str-]', 'compressed [bool=False]']),
+	('wif2hex',    ['<wif> [str-]', 'compressed [bool=False]']),
+	('wif2addr',   ['<wif> [str-]', 'compressed [bool=False]']),
+	('hexaddr2addr', ['<btc address in hex format> [str-]']),
+	('addr2hexaddr', ['<btc address> [str-]']),
+	('pubkey2addr',  ['<public key in hex format> [str-]']),
+	('pubkey2hexaddr', ['<public key in hex format> [str-]']),
+	('privhex2addr', ['<private key in hex format> [str-]','compressed [bool=False]']),
+
+	('hex2mn',       ['<hexadecimal string> [str-]',"wordlist [str='electrum']"]),
+	('mn2hex',       ['<mnemonic> [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))

+ 2 - 2
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

+ 2 - 3
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)