Browse Source

tool.py: new MMGenToolCmd class

MMGen 6 years ago
parent
commit
7b1b235a80
4 changed files with 618 additions and 608 deletions
  1. 8 10
      mmgen/main_tool.py
  2. 4 3
      mmgen/seed.py
  3. 604 593
      mmgen/tool.py
  4. 2 2
      test/test.py

+ 8 - 10
mmgen/main_tool.py

@@ -144,18 +144,16 @@ Type '{pn} help <command> for help on a particular command
 cmd_args = opts.init(opts_data,add_opts=['hidden_incog_input_params','in_fmt','use_old_ed25519'])
 cmd_args = opts.init(opts_data,add_opts=['hidden_incog_input_params','in_fmt','use_old_ed25519'])
 
 
 if len(cmd_args) < 1: opts.usage()
 if len(cmd_args) < 1: opts.usage()
-
-Command = cmd_args.pop(0).capitalize()
+cmd = cmd_args.pop(0)
 
 
 import mmgen.tool as tool
 import mmgen.tool as tool
+tc = tool.MMGenToolCmd()
 
 
-if Command == 'Help' and not cmd_args: tool.usage(None)
-
-if Command not in tool.cmd_data:
-	die(1,"'{}': no such command".format(Command.lower()))
-
-args,kwargs = tool.process_args(Command,cmd_args)
-
-ret = tool.__dict__[Command](*args,**kwargs)
+if cmd == 'help' and not cmd_args:
+	tool._usage(exit_val=0)
+if cmd not in dir(tc):
+	die(1,"'{}': no such command".format(cmd))
 
 
+args,kwargs = tool._process_args(cmd,cmd_args)
+ret = getattr(tc,cmd)(*args,**kwargs)
 sys.exit((1,0)[ret in (None,True)]) # some commands die, some return False on failure
 sys.exit((1,0)[ret in (None,True)]) # some commands die, some return False on failure

+ 4 - 3
mmgen/seed.py

@@ -25,7 +25,6 @@ from binascii import hexlify,unhexlify
 
 
 from mmgen.common import *
 from mmgen.common import *
 from mmgen.obj import *
 from mmgen.obj import *
-from mmgen.filename import *
 from mmgen.crypto import *
 from mmgen.crypto import *
 
 
 pnm = g.proj_name
 pnm = g.proj_name
@@ -80,6 +79,7 @@ class SeedSource(MMGenObject):
 			me.ss_in = ss
 			me.ss_in = ss
 			me.op = ('conv','pwchg_new')[bool(passchg)]
 			me.op = ('conv','pwchg_new')[bool(passchg)]
 		elif fn or opt.hidden_incog_input_params:
 		elif fn or opt.hidden_incog_input_params:
+			from mmgen.filename import Filename
 			if fn:
 			if fn:
 				f = Filename(fn)
 				f = Filename(fn)
 			else:
 			else:
@@ -1026,12 +1026,13 @@ harder to find, you're advised to choose a much larger file size than this.
 					if fsize >= min_fsize: break
 					if fsize >= min_fsize: break
 					msg('File size must be an integer no less than {}'.format(min_fsize))
 					msg('File size must be an integer no less than {}'.format(min_fsize))
 
 
-				from mmgen.tool import Rand2file # threaded routine
-				Rand2file(fn,str(fsize))
+				from mmgen.tool import MMGenToolCmd
+				MMGenToolCmd().rand2file(fn,str(fsize)) # threaded routine TODO: check safe
 				check_offset = False
 				check_offset = False
 			else:
 			else:
 				die(1,'Exiting at user request')
 				die(1,'Exiting at user request')
 
 
+		from mmgen.filename import Filename
 		f = Filename(fn,ftype=type(self),write=True)
 		f = Filename(fn,ftype=type(self),write=True)
 
 
 		dmsg('{} data len {}, offset {}'.format(capfirst(self.desc),d.target_data_len,d.hincog_offset))
 		dmsg('{} data len {}, offset {}'.format(capfirst(self.desc),d.target_data_len,d.hincog_offset))

+ 604 - 593
mmgen/tool.py

@@ -30,123 +30,120 @@ from mmgen.tx import *
 from mmgen.addr import *
 from mmgen.addr import *
 
 
 pnm = g.proj_name
 pnm = g.proj_name
-
 cmd_data = OrderedDict([
 cmd_data = OrderedDict([
-	('Help',         ['<tool command> [str]']),
-	('Usage',        ['<tool command> [str]']),
-	('Strtob58',     ['<string> [str-]','pad [int=0]']),
-	('B58tostr',     ['<b58 number> [str-]']),
-	('Hextob58',     ['<hex number> [str-]','pad [int=0]']),
-	('Hextob58chk',  ['<hex number> [str-]']),
-	('B58tohex',     ['<b58 number> [str-]','pad [int=0]']),
-	('B58chktohex',  ['<b58 number> [str-]']),
-	('B58randenc',   []),
-	('B32tohex',     ['<b32 num> [str-]','pad [int=0]']),
-	('Hextob32',     ['<hex num> [str-]','pad [int=0]']),
-	('Randhex',      ['nbytes [int=32]']),
-	('Id8',          ['<infile> [str]']),
-	('Id6',          ['<infile> [str]']),
-	('Hash160',      ['<hexadecimal string> [str-]']),
-	('Hash256',      ['<str, hexstr or filename> [str]', # TODO handle stdin
+	('help',         ['<tool command> [str]']),
+	('usage',        ['<tool command> [str]']),
+	('strtob58',     ['<string> [str-]','pad [int=0]']),
+	('b58tostr',     ['<b58 number> [str-]']),
+	('hextob58',     ['<hex number> [str-]','pad [int=0]']),
+	('hextob58chk',  ['<hex number> [str-]']),
+	('b58tohex',     ['<b58 number> [str-]','pad [int=0]']),
+	('b58chktohex',  ['<b58 number> [str-]']),
+	('b58randenc',   []),
+	('b32tohex',     ['<b32 num> [str-]','pad [int=0]']),
+	('hextob32',     ['<hex num> [str-]','pad [int=0]']),
+	('randhex',      ['nbytes [int=32]']),
+	('id8',          ['<infile> [str]']),
+	('id6',          ['<infile> [str]']),
+	('hash160',      ['<hexadecimal string> [str-]']),
+	('hash256',      ['<str, hexstr or filename> [str]', # TODO handle stdin
 							'hex_input [bool=False]','file_input [bool=False]']),
 							'hex_input [bool=False]','file_input [bool=False]']),
-	('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-]']),
-	('Rand2file',    ['<outfile> [str]','<nbytes> [str]','threads [int=4]','silent [bool=False]']),
-
-	('Randwif',    []),
-	('Randpair',   []),
-	('Hex2wif',    ['<private key in hex format> [str-]']),
-	('Wif2hex',    ['<wif> [str-]']),
-	('Wif2addr',   ['<wif> [str-]']),
-	('Wif2segwit_pair',['<wif> [str-]']),
-	('Pubhash2addr', ['<coin address in hex format> [str-]']),
-	('Addr2hexaddr', ['<coin address> [str-]']),
-	('Privhex2addr', ['<private key in hex format> [str-]']),
-	('Privhex2pubhex',['<private key in hex format> [str-]']),
-	('Pubhex2addr',  ['<public key in hex format> [str-]']), # new
-	('Pubhex2redeem_script',['<public key in hex format> [str-]']), # new
-	('Wif2redeem_script', ['<private key in WIF format> [str-]']), # new
-
-	('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']"]),
-	('Mn_stats',     ["wordlist [str='electrum']"]),
-	('Mn_printlist', ["wordlist [str='electrum']"]),
-
-	('Gen_addr',     ['<{} ID> [str]'.format(pnm),"wallet [str='']"]),
-	('Gen_key',      ['<{} ID> [str]'.format(pnm),"wallet [str='']"]),
-
-	('Listaddress',['<{} address> [str]'.format(pnm),'minconf [int=1]','pager [bool=False]','showempty [bool=True]','showbtcaddr [bool=True]','show_age [bool=False]','show_days [bool=True]']),
-	('Listaddresses',["addrs [str='']",'minconf [int=1]','showempty [bool=False]','pager [bool=False]','showbtcaddrs [bool=True]','all_labels [bool=False]',"sort [str=''] (options: reverse, age)",'show_age [bool=False]','show_days [bool=True]']),
-	('Getbalance',   ['minconf [int=1]','quiet [bool=False]','pager [bool=False]']),
-	('Txview',       ['<{} TX file(s)> [str]'.format(pnm),'pager [bool=False]','terse [bool=False]',"sort [str='mtime'] (options: ctime, atime)",'MARGS']),
-	('Twview',       ["sort [str='age']",'reverse [bool=False]','show_days [bool=True]','show_mmid [bool=True]','minconf [int=1]','wide [bool=False]','pager [bool=False]']),
-
-	('Add_label',       ['<{} or coin address> [str]'.format(pnm),'<label> [str]']),
-	('Remove_label',    ['<{} or coin address> [str]'.format(pnm)]),
-	('Remove_address',  ['<{} or coin address> [str]'.format(pnm)]),
-	('Addrfile_chksum', ['<{} addr file> [str]'.format(pnm),"mmtype [str='']"]),
-	('Keyaddrfile_chksum', ['<{} addr file> [str]'.format(pnm),"mmtype [str='']"]),
-	('Passwdfile_chksum', ['<{} password file> [str]'.format(pnm)]),
-	('Find_incog_data', ['<file or device name> [str]','<Incog ID> [str]','keep_searching [bool=False]']),
-
-	('Encrypt',      ['<infile> [str]',"outfile [str='']","hash_preset [str='']"]),
-	('Decrypt',      ['<infile> [str]',"outfile [str='']","hash_preset [str='']"]),
-	('Bytespec',     ['<bytespec> [str]']),
-
-	('Keyaddrlist2monerowallets',['<{} XMR key-address file> [str]'.format(pnm),'blockheight [int=(current height)]',"addrs [str=''] (addr idx list or range)"]),
-	('Syncmonerowallets',        ['<{} XMR key-address file> [str]'.format(pnm),"addrs [str=''] (addr idx list or range)"]),
+	('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-]']),
+	('rand2file',    ['<outfile> [str]','<nbytes> [str]','threads [int=4]','silent [bool=False]']),
+
+	('randwif',    []),
+	('randpair',   []),
+	('hex2wif',    ['<private key in hex format> [str-]']),
+	('wif2hex',    ['<wif> [str-]']),
+	('wif2addr',   ['<wif> [str-]']),
+	('wif2segwit_pair',['<wif> [str-]']),
+	('pubhash2addr', ['<coin address in hex format> [str-]']),
+	('addr2hexaddr', ['<coin address> [str-]']),
+	('privhex2addr', ['<private key in hex format> [str-]']),
+	('privhex2pubhex',['<private key in hex format> [str-]']),
+	('pubhex2addr',  ['<public key in hex format> [str-]']), # new
+	('pubhex2redeem_script',['<public key in hex format> [str-]']), # new
+	('wif2redeem_script', ['<private key in WIF format> [str-]']), # new
+
+	('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']"]),
+	('mn_stats',     ["wordlist [str='electrum']"]),
+	('mn_printlist', ["wordlist [str='electrum']"]),
+
+	('gen_addr',     ['<{} ID> [str]'.format(pnm),"wallet [str='']"]),
+	('gen_key',      ['<{} ID> [str]'.format(pnm),"wallet [str='']"]),
+
+	('listaddress',['<{} address> [str]'.format(pnm),'minconf [int=1]','pager [bool=False]','showempty [bool=True]','showbtcaddr [bool=True]','show_age [bool=False]','show_days [bool=True]']),
+	('listaddresses',["addrs [str='']",'minconf [int=1]','showempty [bool=False]','pager [bool=False]','showbtcaddrs [bool=True]','all_labels [bool=False]',"sort [str=''] (options: reverse, age)",'show_age [bool=False]','show_days [bool=True]']),
+	('getbalance',   ['minconf [int=1]','quiet [bool=False]','pager [bool=False]']),
+	('txview',       ['<{} TX file(s)> [str]'.format(pnm),'pager [bool=False]','terse [bool=False]',"sort [str='mtime'] (options: ctime, atime)",'MARGS']),
+	('twview',       ["sort [str='age']",'reverse [bool=False]','show_days [bool=True]','show_mmid [bool=True]','minconf [int=1]','wide [bool=False]','pager [bool=False]']),
+
+	('add_label',       ['<{} or coin address> [str]'.format(pnm),'<label> [str]']),
+	('remove_label',    ['<{} or coin address> [str]'.format(pnm)]),
+	('remove_address',  ['<{} or coin address> [str]'.format(pnm)]),
+	('addrfile_chksum', ['<{} addr file> [str]'.format(pnm),"mmtype [str='']"]),
+	('keyaddrfile_chksum', ['<{} addr file> [str]'.format(pnm),"mmtype [str='']"]),
+	('passwdfile_chksum', ['<{} password file> [str]'.format(pnm)]),
+	('find_incog_data', ['<file or device name> [str]','<Incog ID> [str]','keep_searching [bool=False]']),
+
+	('encrypt',      ['<infile> [str]',"outfile [str='']","hash_preset [str='']"]),
+	('decrypt',      ['<infile> [str]',"outfile [str='']","hash_preset [str='']"]),
+	('bytespec',     ['<bytespec> [str]']),
+
+	('keyaddrlist2monerowallets',['<{} XMR key-address file> [str]'.format(pnm),'blockheight [int=(current height)]',"addrs [str=''] (addr idx list or range)"]),
+	('syncmonerowallets',        ['<{} XMR key-address file> [str]'.format(pnm),"addrs [str=''] (addr idx list or range)"]),
 ])
 ])
 
 
-def usage(command):
+def _usage(cmd=None,exit_val=1):
 
 
 	for v in cmd_data.values():
 	for v in cmd_data.values():
 		if v and v[0][-2:] == '-]':
 		if v and v[0][-2:] == '-]':
 			v[0] = v[0][:-2] + ' or STDIN]'
 			v[0] = v[0][:-2] + ' or STDIN]'
 		if 'MARGS' in v: v.remove('MARGS')
 		if 'MARGS' in v: v.remove('MARGS')
 
 
-	if not command:
+	if not cmd:
 		Msg('Usage information for mmgen-tool commands:')
 		Msg('Usage information for mmgen-tool commands:')
 		for k,v in list(cmd_data.items()):
 		for k,v in list(cmd_data.items()):
-			Msg('  {:18} {}'.format(k.lower(),' '.join(v)))
+			Msg('  {:18} {}'.format(k,' '.join(v)))
 		from mmgen.main_tool import stdin_msg
 		from mmgen.main_tool import stdin_msg
 		Msg('\n  '+'\n  '.join(stdin_msg.split('\n')))
 		Msg('\n  '+'\n  '.join(stdin_msg.split('\n')))
 		sys.exit(0)
 		sys.exit(0)
 
 
-	Command = command.capitalize()
-	if Command in cmd_data:
+	if cmd in cmd_data:
 		import re
 		import re
 		from mmgen.main_tool import cmd_help
 		from mmgen.main_tool import cmd_help
 		for line in cmd_help.split('\n'):
 		for line in cmd_help.split('\n'):
-			if re.match(r'\s+{}\s+'.format(command),line):
+			if re.match(r'\s+{}\s+'.format(cmd),line):
 				c,h = line.split('-',1)
 				c,h = line.split('-',1)
 				Msg('MMGEN-TOOL {}: {}'.format(c.strip().upper(),h.strip()))
 				Msg('MMGEN-TOOL {}: {}'.format(c.strip().upper(),h.strip()))
-		cd = cmd_data[Command]
-		msg('USAGE: {} {} {}'.format(g.prog_name,command.lower(),' '.join(cd)))
+		cd = cmd_data[cmd]
+		msg('USAGE: {} {} {}'.format(g.prog_name,cmd,' '.join(cd)))
 	else:
 	else:
-		msg("'{}': no such tool command".format(command))
-	sys.exit(1)
+		die(1,"'{}': no such tool command".format(cmd))
 
 
-Help = usage
+	sys.exit(exit_val)
 
 
-def process_args(command,cmd_args):
-	if 'MARGS' in cmd_data[command]:
-		cmd_data[command].remove('MARGS')
+def _process_args(cmd,cmd_args):
+	if 'MARGS' in cmd_data[cmd]:
+		cmd_data[cmd].remove('MARGS')
 		margs = True
 		margs = True
 	else:
 	else:
 		margs = False
 		margs = False
 
 
 	c_args = [[i.split(' [')[0],i.split(' [')[1][:-1]]
 	c_args = [[i.split(' [')[0],i.split(' [')[1][:-1]]
-		for i in cmd_data[command] if '=' not in i]
+		for i in cmd_data[cmd] if '=' not in i]
 	c_kwargs = dict([[
 	c_kwargs = dict([[
 			i.split(' [')[0],
 			i.split(' [')[0],
 			[i.split(' [')[1].split('=')[0],i.split(' [')[1].split('=')[1][:-1]]
 			[i.split(' [')[1].split('=')[0],i.split(' [')[1].split('=')[1][:-1]]
-		] for i in cmd_data[command] if '=' in i])
+		] for i in cmd_data[cmd] if '=' in i])
 
 
 	if not margs:
 	if not margs:
 		u_args = [a for a in cmd_args[:len(c_args)]]
 		u_args = [a for a in cmd_args[:len(c_args)]]
@@ -158,12 +155,12 @@ def process_args(command,cmd_args):
 				if not sys.stdin.isatty():
 				if not sys.stdin.isatty():
 					u_args[0] = sys.stdin.read().strip()
 					u_args[0] = sys.stdin.read().strip()
 					if not u_args[0]:
 					if not u_args[0]:
-						die(2,'{}: ERROR: no output from previous command in pipe'.format(command.lower()))
+						die(2,'{}: ERROR: no output from previous command in pipe'.format(cmd))
 
 
 		if not margs and len(u_args) < len(c_args):
 		if not margs and len(u_args) < len(c_args):
 			m1 = 'Command requires exactly {} non-keyword argument{}'
 			m1 = 'Command requires exactly {} non-keyword argument{}'
 			msg(m1.format(len(c_args),suf(c_args,'s')))
 			msg(m1.format(len(c_args),suf(c_args,'s')))
-			usage(command)
+			_usage(cmd)
 
 
 	extra_args = len(cmd_args) - len(c_args)
 	extra_args = len(cmd_args) - len(c_args)
 	u_kwargs = {}
 	u_kwargs = {}
@@ -179,17 +176,17 @@ def process_args(command,cmd_args):
 		u_kwargs = dict([a.split('=') for a in cmd_args[len(c_args):] if '=' in a])
 		u_kwargs = dict([a.split('=') for a in cmd_args[len(c_args):] if '=' in a])
 		if len(u_kwargs) != extra_args:
 		if len(u_kwargs) != extra_args:
 			msg('Command requires exactly {} non-keyword argument{}'.format(len(c_args),suf(c_args,'s')))
 			msg('Command requires exactly {} non-keyword argument{}'.format(len(c_args),suf(c_args,'s')))
-			usage(command)
+			_usage(cmd)
 		if len(u_kwargs) > len(c_kwargs):
 		if len(u_kwargs) > len(c_kwargs):
 			msg('Command requires exactly {} keyword argument{}'.format(len(c_kwargs),suf(c_kwargs,'s')))
 			msg('Command requires exactly {} keyword argument{}'.format(len(c_kwargs),suf(c_kwargs,'s')))
-			usage(command)
+			_usage(cmd)
 
 
-#	mdie(c_args,c_kwargs,u_args,u_kwargs)
+	# mdie(c_args,c_kwargs,u_args,u_kwargs)
 
 
 	for k in u_kwargs:
 	for k in u_kwargs:
 		if k not in c_kwargs:
 		if k not in c_kwargs:
 			msg("'{}': invalid keyword argument".format(k))
 			msg("'{}': invalid keyword argument".format(k))
-			usage(command)
+			_usage(cmd)
 
 
 	def conv_type(arg,arg_name,arg_type):
 	def conv_type(arg,arg_name,arg_type):
 		if arg_type == 'bytes': pdie(arg,arg_name,arg_type)
 		if arg_type == 'bytes': pdie(arg,arg_name,arg_type)
@@ -198,7 +195,7 @@ def process_args(command,cmd_args):
 			elif arg.lower() in ('false','no','0','off'): arg = False
 			elif arg.lower() in ('false','no','0','off'): arg = False
 			else:
 			else:
 				msg("'{}': invalid boolean value for keyword argument".format(arg))
 				msg("'{}': invalid boolean value for keyword argument".format(arg))
-				usage(command)
+				_usage(cmd)
 		try:
 		try:
 			return __builtins__[arg_type](arg)
 			return __builtins__[arg_type](arg)
 		except:
 		except:
@@ -214,13 +211,14 @@ def process_args(command,cmd_args):
 
 
 # Individual cmd_data
 # Individual cmd_data
 
 
-def are_equal(a,b,dtype=''):
-	if dtype == 'str': return a.lstrip('\0') == b.lstrip('\0')
-	if dtype == 'hex': return a.lstrip('0') == b.lstrip('0')
-	if dtype == 'b58': return a.lstrip('1') == b.lstrip('1')
-	else:              return a == b
+def _print_convert_results(indata,enc,dec,dtype):
+
+	def are_equal(a,b,dtype=''):
+		if dtype == 'str': return a.lstrip('\0') == b.lstrip('\0')
+		if dtype == 'hex': return a.lstrip('0') == b.lstrip('0')
+		if dtype == 'b58': return a.lstrip('1') == b.lstrip('1')
+		else:              return a == b
 
 
-def print_convert_results(indata,enc,dec,dtype):
 	error = (True,False)[are_equal(indata,dec,dtype)]
 	error = (True,False)[are_equal(indata,dec,dtype)]
 	if error or opt.verbose:
 	if error or opt.verbose:
 		Msg('Input:         {}'.format(indata))
 		Msg('Input:         {}'.format(indata))
@@ -234,515 +232,528 @@ from mmgen.obj import MMGenAddrType
 at = MMGenAddrType((hasattr(opt,'type') and opt.type) or g.proto.dfl_mmtype)
 at = MMGenAddrType((hasattr(opt,'type') and opt.type) or g.proto.dfl_mmtype)
 kg = KeyGenerator(at)
 kg = KeyGenerator(at)
 ag = AddrGenerator(at)
 ag = AddrGenerator(at)
+wordlists = 'electrum','tirosh'
+dfl_wl_id = 'electrum'
 
 
-def Hexdump(infile,cols=8,line_nums=True):
-	Msg(pretty_hexdump(
-			get_data_from_file(infile,dash=True,silent=True,binary=True),
-				cols=cols,line_nums=line_nums))
-
-def Unhexdump(infile):
-	if g.platform == 'win':
-		import msvcrt
-		msvcrt.setmode(sys.stdout.fileno(),os.O_BINARY)
-	hexdata = get_data_from_file(infile,dash=True,silent=True)
-	ret = decode_pretty_hexdump(hexdata)
-	os.write(g.stdout_fileno,ret)
-
-def B58randenc():
-	r = get_random(32)
-	enc = baseconv.b58encode(r,pad=True)
-	dec = baseconv.b58decode(enc,pad=True)
-	print_convert_results(r,enc.encode(),dec,'bytes')
-
-def Randhex(nbytes='32'):
-	Msg(binascii.hexlify(get_random(int(nbytes))).decode())
-
-def Randwif():
-	Msg(PrivKey(get_random(32),pubkey_type=at.pubkey_type,compressed=at.compressed).wif)
-
-def Randpair():
-	privhex = PrivKey(get_random(32),pubkey_type=at.pubkey_type,compressed=at.compressed)
-	addr = ag.to_addr(kg.to_pubhex(privhex))
-	Vmsg('Key (hex):  {}'.format(privhex))
-	Vmsg_r('Key (WIF):  '); Msg(privhex.wif)
-	Vmsg_r('Addr:       '); Msg(addr)
-
-def Wif2addr(wif):
-	privhex = PrivKey(wif=wif)
-	addr = ag.to_addr(kg.to_pubhex(privhex))
-	Vmsg_r('Addr: '); Msg(addr)
-
-def Wif2segwit_pair(wif):
-	pubhex = kg.to_pubhex(PrivKey(wif=wif))
-	addr = ag.to_addr(pubhex)
-	rs = ag.to_segwit_redeem_script(pubhex)
-	Msg('{}\n{}'.format(rs.decode(),addr))
-
-def Pubhash2addr(pubhash):
-	if opt.type == 'bech32':
-		ret = g.proto.pubhash2bech32addr(pubhash.encode())
-	else:
-		ret = g.proto.pubhash2addr(pubhash.encode(),at.addr_fmt=='p2sh')
-	Msg(ret)
+class MMGenToolCmd(object):
+
+	def help(self,cmd=None):
+		_usage(cmd,exit_val=0)
+
+	def usage(self,cmd=None):
+		_usage(cmd,exit_val=0)
+
+	def hexdump(self,infile,cols=8,line_nums=True):
+		Msg(pretty_hexdump(
+				get_data_from_file(infile,dash=True,silent=True,binary=True),
+					cols=cols,line_nums=line_nums))
+
+	def unhexdump(self,infile):
+		if g.platform == 'win':
+			import msvcrt
+			msvcrt.setmode(sys.stdout.fileno(),os.O_BINARY)
+		hexdata = get_data_from_file(infile,dash=True,silent=True)
+		ret = decode_pretty_hexdump(hexdata)
+		os.write(g.stdout_fileno,ret)
+
+	def b58randenc(self):
+		r = get_random(32)
+		enc = baseconv.b58encode(r,pad=True)
+		dec = baseconv.b58decode(enc,pad=True)
+		_print_convert_results(r,enc.encode(),dec,'bytes')
+
+	def randhex(self,nbytes='32'):
+		Msg(binascii.hexlify(get_random(int(nbytes))).decode())
+
+	def randwif(self):
+		Msg(PrivKey(get_random(32),pubkey_type=at.pubkey_type,compressed=at.compressed).wif)
+
+	def randpair(self):
+		privhex = PrivKey(get_random(32),pubkey_type=at.pubkey_type,compressed=at.compressed)
+		addr = ag.to_addr(kg.to_pubhex(privhex))
+		Vmsg('Key (hex):  {}'.format(privhex))
+		Vmsg_r('Key (WIF):  '); Msg(privhex.wif)
+		Vmsg_r('Addr:       '); Msg(addr)
+
+	def wif2addr(self,wif):
+		privhex = PrivKey(wif=wif)
+		addr = ag.to_addr(kg.to_pubhex(privhex))
+		Vmsg_r('Addr: '); Msg(addr)
+
+	def wif2segwit_pair(self,wif):
+		pubhex = kg.to_pubhex(PrivKey(wif=wif))
+		addr = ag.to_addr(pubhex)
+		rs = ag.to_segwit_redeem_script(pubhex)
+		Msg('{}\n{}'.format(rs.decode(),addr))
+
+	def pubhash2addr(self,pubhash):
+		if opt.type == 'bech32':
+			ret = g.proto.pubhash2bech32addr(pubhash.encode())
+		else:
+			ret = g.proto.pubhash2addr(pubhash.encode(),at.addr_fmt=='p2sh')
+		Msg(ret)
+
+	def addr2hexaddr(self,addr):
+		Msg(g.proto.verify_addr(addr,CoinAddr.hex_width,return_dict=True)['hex'].decode())
+
+	def hash160(self,pubkeyhex):
+		Msg(hash160(pubkeyhex).decode())
+
+	def pubhex2addr(self,pubkeyhex):
+		self.pubhash2addr(hash160(pubkeyhex.encode()).decode())
+
+	def wif2hex(self,wif):
+		Msg(PrivKey(wif=wif).decode())
+
+	def hex2wif(self,hexpriv):
+		Msg(g.proto.hex2wif(hexpriv.encode(),pubkey_type=at.pubkey_type,compressed=at.compressed))
+
+	def privhex2addr(self,privhex,output_pubhex=False):
+		pk = PrivKey(binascii.unhexlify(privhex),compressed=at.compressed,pubkey_type=at.pubkey_type)
+		ph = kg.to_pubhex(pk)
+		Msg(ph.decode() if output_pubhex else ag.to_addr(ph))
+
+	def privhex2pubhex(self,privhex): # new
+		self.privhex2addr(privhex,output_pubhex=True)
+
+	def pubhex2redeem_script(self,pubhex): # new
+		Msg(g.proto.pubhex2redeem_script(pubhex).decode())
+
+	def wif2redeem_script(self,wif): # new
+		privhex = PrivKey(wif=wif)
+		Msg(ag.to_segwit_redeem_script(kg.to_pubhex(privhex)).decode())
+
+	def do_random_mn(self,nbytes,wordlist):
+		hexrand = binascii.hexlify(get_random(nbytes))
+		Vmsg('Seed: {}'.format(hexrand))
+		for wl_id in ([wordlist],wordlists)[wordlist=='all']:
+			if wordlist == 'all':
+				Msg('{} mnemonic:'.format(capfirst(wl_id)))
+			mn = baseconv.fromhex(hexrand,wl_id)
+			Msg(' '.join(mn))
+
+	def mn_rand128(self,wordlist=dfl_wl_id): self.do_random_mn(16,wordlist)
+	def mn_rand192(self,wordlist=dfl_wl_id): self.do_random_mn(24,wordlist)
+	def mn_rand256(self,wordlist=dfl_wl_id): self.do_random_mn(32,wordlist)
+
+	def hex2mn(self,s,wordlist=dfl_wl_id): Msg(' '.join(baseconv.fromhex(s.encode(),wordlist)))
+	def mn2hex(self,s,wordlist=dfl_wl_id): Msg(baseconv.tohex(s.split(),wordlist))
+
+	def strtob58(self,s,pad=None): Msg(baseconv.fromhex(binascii.hexlify(s.encode()),'b58',pad,tostr=True))
+	def hextob58(self,s,pad=None): Msg(baseconv.fromhex(s.encode(),'b58',pad,tostr=True))
+	def hextob58chk(self,s):
+		from mmgen.protocol import _b58chk_encode
+		Msg(_b58chk_encode(s.encode()))
+	def hextob32(self,s,pad=None): Msg(baseconv.fromhex(s.encode(),'b32',pad,tostr=True))
+
+	def b58tostr(self,s):          Msg(binascii.unhexlify(baseconv.tohex(s,'b58')).decode())
+	def b58tohex(self,s,pad=None): Msg(baseconv.tohex(s,'b58',pad))
+	def b58chktohex(self,s):
+		from mmgen.protocol import _b58chk_decode
+		Msg(_b58chk_decode(s).decode())
+	def b32tohex(self,s,pad=None): Msg(baseconv.tohex(s.upper(),'b32',pad))
+
+	def mn_stats(self,wordlist=dfl_wl_id):
+		wordlist in baseconv.digits or die(1,"'{}': not a valid wordlist".format(wordlist))
+		baseconv.check_wordlist(wordlist)
+	def mn_printlist(self,wordlist=dfl_wl_id):
+		wordlist in baseconv.digits or die(1,"'{}': not a valid wordlist".format(wordlist))
+		Msg('\n'.join(baseconv.digits[wordlist]))
+
+	def id8(self,infile):
+		Msg(make_chksum_8(
+			get_data_from_file(infile,dash=True,silent=True,binary=True)
+		))
+	def id6(self,infile):
+		Msg(make_chksum_6(
+			get_data_from_file(infile,dash=True,silent=True,binary=True)
+		))
+	def str2id6(self,s): # retain ignoring of space for backwards compat
+		Msg(make_chksum_6(''.join(s.split())))
+
+	def addrfile_chksum(self,infile,mmtype=''):
+		from mmgen.addr import AddrList
+		mmtype = None if not mmtype else MMGenAddrType(mmtype)
+		AddrList(infile,chksum_only=True,mmtype=mmtype)
 
 
-def Addr2hexaddr(addr): Msg(g.proto.verify_addr(addr,CoinAddr.hex_width,return_dict=True)['hex'].decode())
-def Hash160(pubkeyhex):     Msg(hash160(pubkeyhex).decode())
-def Pubhex2addr(pubkeyhex): Pubhash2addr(hash160(pubkeyhex.encode()).decode())
-def Wif2hex(wif):           Msg(PrivKey(wif=wif).decode())
+	def keyaddrfile_chksum(self,infile,mmtype=''):
+		from mmgen.addr import KeyAddrList
+		mmtype = None if not mmtype else MMGenAddrType(mmtype)
+		KeyAddrList(infile,chksum_only=True,mmtype=mmtype)
 
 
-def Hex2wif(hexpriv):
-	Msg(g.proto.hex2wif(hexpriv.encode(),pubkey_type=at.pubkey_type,compressed=at.compressed))
+	def passwdfile_chksum(self,infile):
+		from mmgen.addr import PasswordList
+		PasswordList(infile=infile,chksum_only=True)
 
 
-def Privhex2addr(privhex,output_pubhex=False):
-	pk = PrivKey(binascii.unhexlify(privhex),compressed=at.compressed,pubkey_type=at.pubkey_type)
-	ph = kg.to_pubhex(pk)
-	Msg(ph.decode() if output_pubhex else ag.to_addr(ph))
+	def hexreverse(self,s):
+		Msg(binascii.hexlify(binascii.unhexlify(s.strip())[::-1]).decode())
 
 
-def Privhex2pubhex(privhex): # new
-	Privhex2addr(privhex,output_pubhex=True)
+	def hexlify(self,s):
+		Msg(binascii.hexlify(s.encode()).decode())
 
 
-def Pubhex2redeem_script(pubhex): # new
-	Msg(g.proto.pubhex2redeem_script(pubhex).decode())
+	def hash256(self,s,file_input=False,hex_input=False):
+		from hashlib import sha256
+		if file_input:  b = get_data_from_file(s,binary=True)
+		elif hex_input: b = decode_pretty_hexdump(s)
+		else:           b = s
+		Msg(sha256(sha256(b.encode()).digest()).hexdigest())
 
 
-def Wif2redeem_script(wif): # new
-	privhex = PrivKey(wif=wif)
-	Msg(ag.to_segwit_redeem_script(kg.to_pubhex(privhex)).decode())
+	def encrypt(self,infile,outfile='',hash_preset=''):
+		data = get_data_from_file(infile,'data for encryption',binary=True)
+		enc_d = mmgen_encrypt(data,'user data',hash_preset)
+		if not outfile:
+			outfile = '{}.{}'.format(os.path.basename(infile),g.mmenc_ext)
 
 
-wordlists = 'electrum','tirosh'
-dfl_wl_id = 'electrum'
+		write_data_to_file(outfile,enc_d,'encrypted data',binary=True)
 
 
-def do_random_mn(nbytes,wordlist):
-	hexrand = binascii.hexlify(get_random(nbytes))
-	Vmsg('Seed: {}'.format(hexrand))
-	for wl_id in ([wordlist],wordlists)[wordlist=='all']:
-		if wordlist == 'all':
-			Msg('{} mnemonic:'.format(capfirst(wl_id)))
-		mn = baseconv.fromhex(hexrand,wl_id)
-		Msg(' '.join(mn))
-
-def Mn_rand128(wordlist=dfl_wl_id): do_random_mn(16,wordlist)
-def Mn_rand192(wordlist=dfl_wl_id): do_random_mn(24,wordlist)
-def Mn_rand256(wordlist=dfl_wl_id): do_random_mn(32,wordlist)
-
-def Hex2mn(s,wordlist=dfl_wl_id): Msg(' '.join(baseconv.fromhex(s.encode(),wordlist)))
-def Mn2hex(s,wordlist=dfl_wl_id): Msg(baseconv.tohex(s.split(),wordlist))
-
-def Strtob58(s,pad=None): Msg(baseconv.fromhex(binascii.hexlify(s.encode()),'b58',pad,tostr=True))
-def Hextob58(s,pad=None): Msg(baseconv.fromhex(s.encode(),'b58',pad,tostr=True))
-def Hextob58chk(s):
-	from mmgen.protocol import _b58chk_encode
-	Msg(_b58chk_encode(s.encode()))
-def Hextob32(s,pad=None): Msg(baseconv.fromhex(s.encode(),'b32',pad,tostr=True))
-def B58tostr(s):          Msg(binascii.unhexlify(baseconv.tohex(s,'b58')).decode())
-def B58tohex(s,pad=None): Msg(baseconv.tohex(s,'b58',pad))
-def B58chktohex(s):
-	from mmgen.protocol import _b58chk_decode
-	Msg(_b58chk_decode(s).decode())
-def B32tohex(s,pad=None): Msg(baseconv.tohex(s.upper(),'b32',pad))
-
-from mmgen.seed import Mnemonic
-def Mn_stats(wordlist=dfl_wl_id):
-	wordlist in baseconv.digits or die(1,"'{}': not a valid wordlist".format(wordlist))
-	baseconv.check_wordlist(wordlist)
-def Mn_printlist(wordlist=dfl_wl_id):
-	wordlist in baseconv.digits or die(1,"'{}': not a valid wordlist".format(wordlist))
-	Msg('\n'.join(baseconv.digits[wordlist]))
-
-def Id8(infile):
-	Msg(make_chksum_8(
-		get_data_from_file(infile,dash=True,silent=True,binary=True)
-	))
-def Id6(infile):
-	Msg(make_chksum_6(
-		get_data_from_file(infile,dash=True,silent=True,binary=True)
-	))
-def Str2id6(s): # retain ignoring of space for backwards compat
-	Msg(make_chksum_6(''.join(s.split())))
-
-def Addrfile_chksum(infile,mmtype=''):
-	from mmgen.addr import AddrList
-	mmtype = None if not mmtype else MMGenAddrType(mmtype)
-	AddrList(infile,chksum_only=True,mmtype=mmtype)
-
-def Keyaddrfile_chksum(infile,mmtype=''):
-	from mmgen.addr import KeyAddrList
-	mmtype = None if not mmtype else MMGenAddrType(mmtype)
-	KeyAddrList(infile,chksum_only=True,mmtype=mmtype)
-
-def Passwdfile_chksum(infile):
-	from mmgen.addr import PasswordList
-	PasswordList(infile=infile,chksum_only=True)
-
-def Hexreverse(s):
-	Msg(binascii.hexlify(binascii.unhexlify(s.strip())[::-1]).decode())
-
-def Hexlify(s):
-	Msg(binascii.hexlify(s.encode()).decode())
-
-def Hash256(s,file_input=False,hex_input=False):
-	from hashlib import sha256
-	if file_input:  b = get_data_from_file(s,binary=True)
-	elif hex_input: b = decode_pretty_hexdump(s)
-	else:           b = s
-	Msg(sha256(sha256(b.encode()).digest()).hexdigest())
-
-def Encrypt(infile,outfile='',hash_preset=''):
-	data = get_data_from_file(infile,'data for encryption',binary=True)
-	enc_d = mmgen_encrypt(data,'user data',hash_preset)
-	if not outfile:
-		outfile = '{}.{}'.format(os.path.basename(infile),g.mmenc_ext)
-
-	write_data_to_file(outfile,enc_d,'encrypted data',binary=True)
-
-def Decrypt(infile,outfile='',hash_preset=''):
-	enc_d = get_data_from_file(infile,'encrypted data',binary=True)
-	while True:
-		dec_d = mmgen_decrypt(enc_d,'user data',hash_preset)
-		if dec_d: break
-		msg('Trying again...')
-
-	if not outfile:
-		o = os.path.basename(infile)
-		outfile = remove_extension(o,g.mmenc_ext)
-		if outfile == o: outfile += '.dec'
-
-	write_data_to_file(outfile,dec_d,'decrypted data',binary=True)
-
-def Find_incog_data(filename,iv_id,keep_searching=False):
-	ivsize,bsize,mod = g.aesctr_iv_len,4096,4096*8
-	n,carry = 0,b' '*ivsize
-	flgs = os.O_RDONLY|os.O_BINARY if g.platform == 'win' else os.O_RDONLY
-	f = os.open(filename,flgs)
-	for ch in iv_id:
-		if ch not in '0123456789ABCDEF':
-			die(2,"'{}': invalid Incog ID".format(iv_id))
-	while True:
-		d = os.read(f,bsize)
-		if not d: break
-		d = carry + d
-		for i in range(bsize):
-			if sha256(d[i:i+ivsize]).hexdigest()[:8].upper() == iv_id:
-				if n+i < ivsize: continue
-				msg('\rIncog data for ID {} found at offset {}'.format(iv_id,n+i-ivsize))
-				if not keep_searching: sys.exit(0)
-		carry = d[len(d)-ivsize:]
-		n += bsize
-		if not n % mod:
-			msg_r('\rSearched: {} bytes'.format(n))
-
-	msg('')
-	os.close(f)
-
-def Rand2file(outfile,nbytes,threads=4,silent=False):
-	nbytes = parse_nbytes(nbytes)
-	from Crypto import Random
-	rh = Random.new()
-	from queue import Queue
-	from threading import Thread
-	bsize = 2**20
-	roll = bsize * 4
-	if opt.outdir: outfile = make_full_path(opt.outdir,outfile)
-	f = open(outfile,'wb')
-
-	from Crypto.Cipher import AES
-	from Crypto.Util import Counter
-
-	key = get_random(32)
-
-	def encrypt_worker(wid):
+	def decrypt(self,infile,outfile='',hash_preset=''):
+		enc_d = get_data_from_file(infile,'encrypted data',binary=True)
 		while True:
 		while True:
-			i,d = q1.get()
-			c = AES.new(key,AES.MODE_CTR,counter=Counter.new(g.aesctr_iv_len*8,initial_value=i))
-			enc_data = c.encrypt(d)
-			q2.put(enc_data)
-			q1.task_done()
-
-	def output_worker():
+			dec_d = mmgen_decrypt(enc_d,'user data',hash_preset)
+			if dec_d: break
+			msg('Trying again...')
+
+		if not outfile:
+			o = os.path.basename(infile)
+			outfile = remove_extension(o,g.mmenc_ext)
+			if outfile == o: outfile += '.dec'
+
+		write_data_to_file(outfile,dec_d,'decrypted data',binary=True)
+
+	def find_incog_data(self,filename,iv_id,keep_searching=False):
+		ivsize,bsize,mod = g.aesctr_iv_len,4096,4096*8
+		n,carry = 0,b' '*ivsize
+		flgs = os.O_RDONLY|os.O_BINARY if g.platform == 'win' else os.O_RDONLY
+		f = os.open(filename,flgs)
+		for ch in iv_id:
+			if ch not in '0123456789ABCDEF':
+				die(2,"'{}': invalid Incog ID".format(iv_id))
 		while True:
 		while True:
-			data = q2.get()
-			f.write(data)
-			q2.task_done()
-
-	q1 = Queue()
-	for i in range(max(1,threads-2)):
-		t = Thread(target=encrypt_worker,args=(i,))
+			d = os.read(f,bsize)
+			if not d: break
+			d = carry + d
+			for i in range(bsize):
+				if sha256(d[i:i+ivsize]).hexdigest()[:8].upper() == iv_id:
+					if n+i < ivsize: continue
+					msg('\rIncog data for ID {} found at offset {}'.format(iv_id,n+i-ivsize))
+					if not keep_searching: sys.exit(0)
+			carry = d[len(d)-ivsize:]
+			n += bsize
+			if not n % mod:
+				msg_r('\rSearched: {} bytes'.format(n))
+
+		msg('')
+		os.close(f)
+
+	def rand2file(self,outfile,nbytes,threads=4,silent=False):
+		nbytes = parse_nbytes(nbytes)
+		from Crypto import Random
+		rh = Random.new()
+		from queue import Queue
+		from threading import Thread
+		bsize = 2**20
+		roll = bsize * 4
+		if opt.outdir: outfile = make_full_path(opt.outdir,outfile)
+		f = open(outfile,'wb')
+
+		from Crypto.Cipher import AES
+		from Crypto.Util import Counter
+
+		key = get_random(32)
+
+		def encrypt_worker(wid):
+			while True:
+				i,d = q1.get()
+				c = AES.new(key,AES.MODE_CTR,counter=Counter.new(g.aesctr_iv_len*8,initial_value=i))
+				enc_data = c.encrypt(d)
+				q2.put(enc_data)
+				q1.task_done()
+
+		def output_worker():
+			while True:
+				data = q2.get()
+				f.write(data)
+				q2.task_done()
+
+		q1 = Queue()
+		for i in range(max(1,threads-2)):
+			t = Thread(target=encrypt_worker,args=(i,))
+			t.daemon = True
+			t.start()
+
+		q2 = Queue()
+		t = Thread(target=output_worker)
 		t.daemon = True
 		t.daemon = True
 		t.start()
 		t.start()
 
 
-	q2 = Queue()
-	t = Thread(target=output_worker)
-	t.daemon = True
-	t.start()
-
-	i = 1; rbytes = nbytes
-	while rbytes > 0:
-		d = rh.read(min(bsize,rbytes))
-		q1.put((i,d))
-		rbytes -= bsize
-		i += 1
-		if not (bsize*i) % roll:
-			msg_r('\rRead: {} bytes'.format(bsize*i))
-
-	if not silent:
-		msg('\rRead: {} bytes'.format(nbytes))
-		qmsg("\r{} bytes of random data written to file '{}'".format(nbytes,outfile))
-	q1.join()
-	q2.join()
-	f.close()
-
-def Bytespec(s): Msg(str(parse_nbytes(s)))
-
-def Keyaddrlist2monerowallets(infile,blockheight=None,addrs=None):
-	monero_wallet_ops(infile=infile,op='create',blockheight=blockheight,addrs=addrs)
-
-def Syncmonerowallets(infile,addrs=None):
-	monero_wallet_ops(infile=infile,op='sync',addrs=addrs)
-
-def monero_wallet_ops(infile,op,blockheight=None,addrs=None):
-
-	def run_cmd(cmd):
-		import subprocess as sp
-		p = sp.Popen(cmd,stdin=sp.PIPE,stdout=sp.PIPE,stderr=sp.PIPE)
-		return p
-
-	def test_rpc():
-		p = run_cmd(['monero-wallet-cli','--version'])
-		if not b'Monero' in p.stdout.read():
-			die(1,"Unable to run 'monero-wallet-cli'!")
-		p = run_cmd(['monerod','status'])
-		import re
-		m = re.search(r'Height: (\d+)/\d+ ',p.stdout.read().decode())
-		if not m:
-			die(1,'Unable to connect to monerod!')
-		return int(m.group(1))
-
-	def my_expect(p,m,s,regex=False):
-		if m: msg_r('  {}...'.format(m))
-		ret = (p.expect_exact,p.expect)[regex](s)
-		vmsg("\nexpect: '{}' => {}".format(s,ret))
-		if not (ret == 0 or (type(s) == list and ret in (0,1))):
-			die(2,"Expect failed: '{}' (return value: {})".format(s,ret))
-		if m: msg('OK')
-		return ret
-
-	def my_sendline(p,m,s,usr_ret):
-		if m: msg_r('  {}...'.format(m))
-		ret = p.sendline(s)
-		if ret != usr_ret:
-			die(2,"Unable to send line '{}' (return value {})".format(s,ret))
-		if m: msg('OK')
-		vmsg("sendline: '{}' => {}".format(s,ret))
-
-	def create(n,d,fn):
-		try: os.stat(fn)
-		except: pass
-		else: die(1,"Wallet '{}' already exists!".format(fn))
-		p = pexpect.spawn('monero-wallet-cli --generate-from-spend-key {}'.format(fn))
-		if g.debug: p.logfile = sys.stdout
-		my_expect(p,'Awaiting initial prompt','Secret spend key: ')
-		my_sendline(p,'',d.sec.decode(),65)
-		my_expect(p,'','Enter.* new.* password.*: ',regex=True)
-		my_sendline(p,'Sending password',d.wallet_passwd,33)
-		my_expect(p,'','Confirm password: ')
-		my_sendline(p,'Sending password again',d.wallet_passwd,33)
-		my_expect(p,'','of your choice: ')
-		my_sendline(p,'','1',2)
-		my_expect(p,'monerod generating wallet','Generated new wallet: ')
-		my_expect(p,'','\n')
-		if d.addr not in p.before.decode():
-			die(3,'Addresses do not match!\n  MMGen: {}\n Monero: {}'.format(d.addr,p.before.decode()))
-		my_expect(p,'','View key: ')
-		my_expect(p,'','\n')
-		if d.viewkey not in p.before.decode():
-			die(3,'View keys do not match!\n  MMGen: {}\n Monero: {}'.format(d.viewkey,p.before.decode()))
-		my_expect(p,'','(YYYY-MM-DD): ')
-		h = str(blockheight or cur_height-1)
-		my_sendline(p,'',h,len(h)+1)
-		ret = my_expect(p,'',['Starting refresh','Still apply restore height?  (Y/Yes/N/No): '])
-		if ret == 1:
-			my_sendline(p,'','Y',2)
-			m = '  Warning: {}: blockheight argument is higher than current blockheight'
-			ymsg(m.format(blockheight))
-		elif blockheight != None:
-			p.logfile = sys.stderr
-		my_expect(p,'Syncing wallet','\[wallet.*$',regex=True)
-		p.logfile = None
-		my_sendline(p,'Exiting','exit',5)
-		p.read()
-
-	def sync(n,d,fn):
-		try: os.stat(fn)
-		except: die(1,"Wallet '{}' does not exist!".format(fn))
-		p = pexpect.spawn('monero-wallet-cli --wallet-file={}'.format(fn))
-		if g.debug: p.logfile = sys.stdout
-		my_expect(p,'Awaiting password prompt','Wallet password: ')
-		my_sendline(p,'Sending password',d.wallet_passwd,33)
-
-		msg('  Starting refresh...')
-		height = None
-		while True:
-			ret = p.expect([r' / .*',r'\[wallet.*:.*'])
-			if ret == 0: # TODO: coverage
-				height = p.after
-				msg_r('\r  Block {}{}'.format(p.before.split()[-1],height))
-			elif ret == 1:
-				if height:
-					height = height.split()[-1]
-					msg('\r  Block {h} / {h}'.format(h=height))
+		i = 1; rbytes = nbytes
+		while rbytes > 0:
+			d = rh.read(min(bsize,rbytes))
+			q1.put((i,d))
+			rbytes -= bsize
+			i += 1
+			if not (bsize*i) % roll:
+				msg_r('\rRead: {} bytes'.format(bsize*i))
+
+		if not silent:
+			msg('\rRead: {} bytes'.format(nbytes))
+			qmsg("\r{} bytes of random data written to file '{}'".format(nbytes,outfile))
+		q1.join()
+		q2.join()
+		f.close()
+
+	def bytespec(self,s): Msg(str(parse_nbytes(s)))
+
+	def keyaddrlist2monerowallets(self,infile,blockheight=None,addrs=None):
+		monero_wallet_ops(infile=infile,op='create',blockheight=blockheight,addrs=addrs)
+
+	def syncmonerowallets(self,infile,addrs=None):
+		monero_wallet_ops(infile=infile,op='sync',addrs=addrs)
+
+	def monero_wallet_ops(self,infile,op,blockheight=None,addrs=None):
+
+		def run_cmd(cmd):
+			import subprocess as sp
+			p = sp.Popen(cmd,stdin=sp.PIPE,stdout=sp.PIPE,stderr=sp.PIPE)
+			return p
+
+		def test_rpc():
+			p = run_cmd(['monero-wallet-cli','--version'])
+			if not b'Monero' in p.stdout.read():
+				die(1,"Unable to run 'monero-wallet-cli'!")
+			p = run_cmd(['monerod','status'])
+			import re
+			m = re.search(r'Height: (\d+)/\d+ ',p.stdout.read().decode())
+			if not m:
+				die(1,'Unable to connect to monerod!')
+			return int(m.group(1))
+
+		def my_expect(p,m,s,regex=False):
+			if m: msg_r('  {}...'.format(m))
+			ret = (p.expect_exact,p.expect)[regex](s)
+			vmsg("\nexpect: '{}' => {}".format(s,ret))
+			if not (ret == 0 or (type(s) == list and ret in (0,1))):
+				die(2,"Expect failed: '{}' (return value: {})".format(s,ret))
+			if m: msg('OK')
+			return ret
+
+		def my_sendline(p,m,s,usr_ret):
+			if m: msg_r('  {}...'.format(m))
+			ret = p.sendline(s)
+			if ret != usr_ret:
+				die(2,"Unable to send line '{}' (return value {})".format(s,ret))
+			if m: msg('OK')
+			vmsg("sendline: '{}' => {}".format(s,ret))
+
+		def create(n,d,fn):
+			try: os.stat(fn)
+			except: pass
+			else: die(1,"Wallet '{}' already exists!".format(fn))
+			p = pexpect.spawn('monero-wallet-cli --generate-from-spend-key {}'.format(fn))
+			if g.debug: p.logfile = sys.stdout
+			my_expect(p,'Awaiting initial prompt','Secret spend key: ')
+			my_sendline(p,'',d.sec.decode(),65)
+			my_expect(p,'','Enter.* new.* password.*: ',regex=True)
+			my_sendline(p,'Sending password',d.wallet_passwd,33)
+			my_expect(p,'','Confirm password: ')
+			my_sendline(p,'Sending password again',d.wallet_passwd,33)
+			my_expect(p,'','of your choice: ')
+			my_sendline(p,'','1',2)
+			my_expect(p,'monerod generating wallet','Generated new wallet: ')
+			my_expect(p,'','\n')
+			if d.addr not in p.before.decode():
+				die(3,'Addresses do not match!\n  MMGen: {}\n Monero: {}'.format(d.addr,p.before.decode()))
+			my_expect(p,'','View key: ')
+			my_expect(p,'','\n')
+			if d.viewkey not in p.before.decode():
+				die(3,'View keys do not match!\n  MMGen: {}\n Monero: {}'.format(d.viewkey,p.before.decode()))
+			my_expect(p,'','(YYYY-MM-DD): ')
+			h = str(blockheight or cur_height-1)
+			my_sendline(p,'',h,len(h)+1)
+			ret = my_expect(p,'',['Starting refresh','Still apply restore height?  (Y/Yes/N/No): '])
+			if ret == 1:
+				my_sendline(p,'','Y',2)
+				m = '  Warning: {}: blockheight argument is higher than current blockheight'
+				ymsg(m.format(blockheight))
+			elif blockheight != None:
+				p.logfile = sys.stderr
+			my_expect(p,'Syncing wallet','\[wallet.*$',regex=True)
+			p.logfile = None
+			my_sendline(p,'Exiting','exit',5)
+			p.read()
+
+		def sync(n,d,fn):
+			try: os.stat(fn)
+			except: die(1,"Wallet '{}' does not exist!".format(fn))
+			p = pexpect.spawn('monero-wallet-cli --wallet-file={}'.format(fn))
+			if g.debug: p.logfile = sys.stdout
+			my_expect(p,'Awaiting password prompt','Wallet password: ')
+			my_sendline(p,'Sending password',d.wallet_passwd,33)
+
+			msg('  Starting refresh...')
+			height = None
+			while True:
+				ret = p.expect([r' / .*',r'\[wallet.*:.*'])
+				if ret == 0: # TODO: coverage
+					height = p.after
+					msg_r('\r  Block {}{}'.format(p.before.split()[-1],height))
+				elif ret == 1:
+					if height:
+						height = height.split()[-1]
+						msg('\r  Block {h} / {h}'.format(h=height))
+					else:
+						msg('  Wallet in sync')
+					b = [l for l in p.before.decode().splitlines() if len(l) > 7 and l[:8] == 'Balance:'][0].split()
+					msg('  Balance: {} Unlocked balance: {}'.format(b[1],b[4]))
+					from mmgen.obj import XMRAmt
+					bals[fn] = ( XMRAmt(b[1][:-1]), XMRAmt(b[4]) )
+					my_sendline(p,'Exiting','exit',5)
+					p.read()
+					break
 				else:
 				else:
-					msg('  Wallet in sync')
-				b = [l for l in p.before.decode().splitlines() if len(l) > 7 and l[:8] == 'Balance:'][0].split()
-				msg('  Balance: {} Unlocked balance: {}'.format(b[1],b[4]))
+					die(2,"\nExpect failed: (return value: {})".format(ret))
+
+		def process_wallets():
+			m =   { 'create': ('Creat','Generat',create,False),
+					'sync':   ('Sync', 'Sync',   sync,  True) }
+			opt.accept_defaults = opt.accept_defaults or m[op][3]
+			from mmgen.protocol import init_coin
+			init_coin('xmr')
+			from mmgen.addr import AddrList
+			al = KeyAddrList(infile)
+			data = [d for d in al.data if addrs == None or d.idx in AddrIdxList(addrs)]
+			dl = len(data)
+			assert dl,"No addresses in addrfile within range '{}'".format(addrs)
+			gmsg('\n{}ing {} wallet{}'.format(m[op][0],dl,suf(dl)))
+			for n,d in enumerate(data): # [d.sec,d.wallet_passwd,d.viewkey,d.addr]
+				fn = os.path.join(
+					opt.outdir or '','{}-{}-MoneroWallet{}'.format(
+						al.al_id.sid,
+						d.idx,
+						'-α' if g.debug_utf8 else ''))
+				gmsg('\n{}ing wallet {}/{} ({})'.format(m[op][1],n+1,dl,fn))
+				m[op][2](n,d,fn)
+			gmsg('\n{} wallet{} {}ed'.format(dl,suf(dl),m[op][0].lower()))
+			if op == 'sync':
+				col1_w = max(map(len,bals)) + 1
+				fs = '{:%s} {} {}' % col1_w
+				msg('\n'+fs.format('Wallet','Balance           ','Unlocked Balance  '))
 				from mmgen.obj import XMRAmt
 				from mmgen.obj import XMRAmt
-				bals[fn] = ( XMRAmt(b[1][:-1]), XMRAmt(b[4]) )
-				my_sendline(p,'Exiting','exit',5)
-				p.read()
-				break
-			else:
-				die(2,"\nExpect failed: (return value: {})".format(ret))
-
-	def process_wallets():
-		m =   { 'create': ('Creat','Generat',create,False),
-				'sync':   ('Sync', 'Sync',   sync,  True) }
-		opt.accept_defaults = opt.accept_defaults or m[op][3]
-		from mmgen.protocol import init_coin
-		init_coin('xmr')
-		from mmgen.addr import AddrList
-		al = KeyAddrList(infile)
-		data = [d for d in al.data if addrs == None or d.idx in AddrIdxList(addrs)]
-		dl = len(data)
-		assert dl,"No addresses in addrfile within range '{}'".format(addrs)
-		gmsg('\n{}ing {} wallet{}'.format(m[op][0],dl,suf(dl)))
-		for n,d in enumerate(data): # [d.sec,d.wallet_passwd,d.viewkey,d.addr]
-			fn = os.path.join(
-				opt.outdir or '','{}-{}-MoneroWallet{}'.format(
-					al.al_id.sid,
-					d.idx,
-					'-α' if g.debug_utf8 else ''))
-			gmsg('\n{}ing wallet {}/{} ({})'.format(m[op][1],n+1,dl,fn))
-			m[op][2](n,d,fn)
-		gmsg('\n{} wallet{} {}ed'.format(dl,suf(dl),m[op][0].lower()))
-		if op == 'sync':
-			col1_w = max(map(len,bals)) + 1
-			fs = '{:%s} {} {}' % col1_w
-			msg('\n'+fs.format('Wallet','Balance           ','Unlocked Balance  '))
-			from mmgen.obj import XMRAmt
-			tbals = [XMRAmt('0'),XMRAmt('0')]
-			for bal in bals:
-				for i in (0,1): tbals[i] += bals[bal][i]
-				msg(fs.format(bal+':',*[XMRAmt(b).fmt(fs='5.12',color=True) for b in bals[bal]]))
-			msg(fs.format('-'*col1_w,'-'*18,'-'*18))
-			msg(fs.format('TOTAL:',*[XMRAmt(b).fmt(fs='5.12',color=True) for b in tbals]))
-
-	os.environ['LANG'] = 'C'
-	import pexpect
-	if blockheight != None and int(blockheight) < 0:
-		blockheight = 0 # TODO: non-zero coverage
-	cur_height = test_rpc()
-	bals = OrderedDict() # locked,unlocked
-
-	try:
-		process_wallets()
-	except KeyboardInterrupt:
-		rdie(1,'\nUser interrupt\n')
-	except EOFError:
-		rdie(2,'\nEnd of file\n')
-	except Exception as e:
+				tbals = [XMRAmt('0'),XMRAmt('0')]
+				for bal in bals:
+					for i in (0,1): tbals[i] += bals[bal][i]
+					msg(fs.format(bal+':',*[XMRAmt(b).fmt(fs='5.12',color=True) for b in bals[bal]]))
+				msg(fs.format('-'*col1_w,'-'*18,'-'*18))
+				msg(fs.format('TOTAL:',*[XMRAmt(b).fmt(fs='5.12',color=True) for b in tbals]))
+
+		os.environ['LANG'] = 'C'
+		import pexpect
+		if blockheight != None and int(blockheight) < 0:
+			blockheight = 0 # TODO: non-zero coverage
+		cur_height = test_rpc()
+		bals = OrderedDict() # locked,unlocked
+
 		try:
 		try:
-			die(1,'Error: {}'.format(e.args[0]))
-		except:
-			rdie(1,'Error: {!r}'.format(e.args[0]))
-
-# ================ RPC commands ================== #
-
-def Gen_addr(addr,wallet='',target='addr',return_result=False):
-	addr = MMGenID(addr)
-	sf = get_seed_file([wallet] if wallet else [],1)
-	opt.quiet = True
-	from mmgen.seed import SeedSource
-	ss = SeedSource(sf)
-	if ss.seed.sid != addr.sid:
-		m = 'Seed ID of requested address ({}) does not match wallet ({})'
-		die(1,m.format(addr.sid,ss.seed.sid))
-	al = AddrList(seed=ss.seed,addr_idxs=AddrIdxList(str(addr.idx)),mmtype=addr.mmtype,do_chksum=False)
-	d = al.data[0]
-	ret = d.sec.wif if target=='wif' else d.addr
-	if return_result: return ret
-	else: Msg(ret)
-
-def Gen_key(addr,wallet=''):
-	return Gen_addr(addr,wallet,target='wif')
-
-def Listaddress(addr,minconf=1,pager=False,showempty=True,showbtcaddr=True,show_age=False,show_days=None):
-	return Listaddresses(addrs=addr,minconf=minconf,pager=pager,
-			showempty=showempty,showbtcaddrs=showbtcaddr,show_age=show_age,show_days=show_days)
-
-# List MMGen addresses and their balances.  TODO: move this code to AddrList
-def Listaddresses(addrs='',minconf=1,
-	showempty=False,pager=False,showbtcaddrs=True,all_labels=False,sort=None,show_age=False,show_days=None):
-
-	if show_days == None: show_days = False # user-set show_days triggers show_age
-	else: show_age = True
-
-	if sort:
-		sort = set(sort.split(','))
-		sort_params = set(['reverse','age'])
-		if not sort.issubset(sort_params):
-			die(1,"The sort option takes the following parameters: '{}'".format("','".join(sort_params)))
-
-	usr_addr_list = []
-	if addrs:
-		a = addrs.rsplit(':',1)
-		if len(a) != 2:
-			m = "'{}': invalid address list argument (must be in form <seed ID>:[<type>:]<idx list>)"
-			die(1,m.format(addrs))
-		usr_addr_list = [MMGenID('{}:{}'.format(a[0],i)) for i in AddrIdxList(a[1])]
-
-	from mmgen.tw import TwAddrList
-	al = TwAddrList(usr_addr_list,minconf,showempty,showbtcaddrs,all_labels)
-	if not al:
-		die(0,('No tracked addresses with balances!','No tracked addresses!')[showempty])
-	o = al.format(showbtcaddrs,sort,show_age,show_days)
-	return do_pager(o) if pager else Msg(o)
-
-def Getbalance(minconf=1,quiet=False,return_val=False,pager=False):
-	from mmgen.tw import TwGetBalance
-	o = TwGetBalance(minconf,quiet).format()
-	return o if return_val else do_pager(o) if pager else Msg_r(o)
-
-def Txview(*infiles,**kwargs):
-	from mmgen.filename import MMGenFileList
-	pager = 'pager' in kwargs and kwargs['pager']
-	terse = 'terse' in kwargs and kwargs['terse']
-	sort_key = kwargs['sort'] if 'sort' in kwargs else 'mtime'
-	flist = MMGenFileList(infiles,ftype=MMGenTX)
-	flist.sort_by_age(key=sort_key) # in-place sort
-	from mmgen.term import get_terminal_size
-	sep = '—'*77+'\n'
-	out = sep.join([MMGenTX(fn).format_view(terse=terse) for fn in flist.names()])
-	(Msg,do_pager)[pager](out.rstrip())
-
-def Twview(pager=False,reverse=False,wide=False,minconf=1,sort='age',show_days=True,show_mmid=True):
-	rpc_init()
-	from mmgen.tw import TwUnspentOutputs
-	tw = TwUnspentOutputs(minconf=minconf)
-	tw.do_sort(sort,reverse=reverse)
-	tw.show_days = show_days
-	tw.show_mmid = show_mmid
-	out = tw.format_for_printing(color=True) if wide else tw.format_for_display()
-	(Msg_r,do_pager)[pager](out)
-
-def Add_label(mmaddr_or_coin_addr,label):
-	rpc_init()
-	from mmgen.tw import TrackingWallet
-	TrackingWallet(mode='w').add_label(mmaddr_or_coin_addr,label,on_fail='raise')
-
-def Remove_label(mmaddr_or_coin_addr):
-	Add_label(mmaddr_or_coin_addr,'')
-
-def Remove_address(mmaddr_or_coin_addr):
-	from mmgen.tw import TrackingWallet
-	tw = TrackingWallet(mode='w')
-	ret = tw.remove_address(mmaddr_or_coin_addr)
-	if ret:
-		msg("Address '{}' deleted from tracking wallet".format(ret))
+			process_wallets()
+		except KeyboardInterrupt:
+			rdie(1,'\nUser interrupt\n')
+		except EOFError:
+			rdie(2,'\nEnd of file\n')
+		except Exception as e:
+			try:
+				die(1,'Error: {}'.format(e.args[0]))
+			except:
+				rdie(1,'Error: {!r}'.format(e.args[0]))
+
+	# ================ RPC commands ================== #
+
+	def gen_addr(self,addr,wallet='',target='addr',return_result=False):
+		addr = MMGenID(addr)
+		sf = get_seed_file([wallet] if wallet else [],1)
+		opt.quiet = True
+		from mmgen.seed import SeedSource
+		ss = SeedSource(sf)
+		if ss.seed.sid != addr.sid:
+			m = 'Seed ID of requested address ({}) does not match wallet ({})'
+			die(1,m.format(addr.sid,ss.seed.sid))
+		al = AddrList(seed=ss.seed,addr_idxs=AddrIdxList(str(addr.idx)),mmtype=addr.mmtype,do_chksum=False)
+		d = al.data[0]
+		ret = d.sec.wif if target=='wif' else d.addr
+		if return_result: return ret
+		else: Msg(ret)
+
+	def gen_key(self,addr,wallet=''):
+		return self.gen_addr(addr,wallet,target='wif')
+
+	def listaddress(self,addr,minconf=1,pager=False,showempty=True,showbtcaddr=True,show_age=False,show_days=None):
+		return self.listaddresses(addrs=addr,minconf=minconf,pager=pager,
+				showempty=showempty,showbtcaddrs=showbtcaddr,show_age=show_age,show_days=show_days)
+
+	def listaddresses(self,addrs='',minconf=1,
+		showempty=False,pager=False,showbtcaddrs=True,all_labels=False,sort=None,show_age=False,show_days=None):
+
+		if show_days == None: show_days = False # user-set show_days triggers show_age
+		else: show_age = True
+
+		if sort:
+			sort = set(sort.split(','))
+			sort_params = set(['reverse','age'])
+			if not sort.issubset(sort_params):
+				die(1,"The sort option takes the following parameters: '{}'".format("','".join(sort_params)))
+
+		usr_addr_list = []
+		if addrs:
+			a = addrs.rsplit(':',1)
+			if len(a) != 2:
+				m = "'{}': invalid address list argument (must be in form <seed ID>:[<type>:]<idx list>)"
+				die(1,m.format(addrs))
+			usr_addr_list = [MMGenID('{}:{}'.format(a[0],i)) for i in AddrIdxList(a[1])]
+
+		from mmgen.tw import TwAddrList
+		al = TwAddrList(usr_addr_list,minconf,showempty,showbtcaddrs,all_labels)
+		if not al:
+			die(0,('No tracked addresses with balances!','No tracked addresses!')[showempty])
+		o = al.format(showbtcaddrs,sort,show_age,show_days)
+		return do_pager(o) if pager else Msg(o)
+
+	def getbalance(self,minconf=1,quiet=False,return_val=False,pager=False):
+		from mmgen.tw import TwGetBalance
+		o = TwGetBalance(minconf,quiet).format()
+		return o if return_val else do_pager(o) if pager else Msg_r(o)
+
+	def txview(self,*infiles,**kwargs):
+		from mmgen.filename import MMGenFileList
+		pager = 'pager' in kwargs and kwargs['pager']
+		terse = 'terse' in kwargs and kwargs['terse']
+		sort_key = kwargs['sort'] if 'sort' in kwargs else 'mtime'
+		flist = MMGenFileList(infiles,ftype=MMGenTX)
+		flist.sort_by_age(key=sort_key) # in-place sort
+		from mmgen.term import get_terminal_size
+		sep = '—'*77+'\n'
+		out = sep.join([MMGenTX(fn).format_view(terse=terse) for fn in flist.names()])
+		(Msg,do_pager)[pager](out.rstrip())
+
+	def twview(self,pager=False,reverse=False,wide=False,minconf=1,sort='age',show_days=True,show_mmid=True):
+		rpc_init()
+		from mmgen.tw import TwUnspentOutputs
+		tw = TwUnspentOutputs(minconf=minconf)
+		tw.do_sort(sort,reverse=reverse)
+		tw.show_days = show_days
+		tw.show_mmid = show_mmid
+		out = tw.format_for_printing(color=True) if wide else tw.format_for_display()
+		(Msg_r,do_pager)[pager](out)
+
+	def add_label(self,mmaddr_or_coin_addr,label):
+		rpc_init()
+		from mmgen.tw import TrackingWallet
+		TrackingWallet(mode='w').add_label(mmaddr_or_coin_addr,label,on_fail='raise')
+
+	def remove_label(self,mmaddr_or_coin_addr):
+		self.add_label(mmaddr_or_coin_addr,'')
+
+	def remove_address(self,mmaddr_or_coin_addr):
+		from mmgen.tw import TrackingWallet
+		tw = TrackingWallet(mode='w')
+		ret = tw.remove_address(mmaddr_or_coin_addr)
+		if ret:
+			msg("Address '{}' deleted from tracking wallet".format(ret))

+ 2 - 2
test/test.py

@@ -3786,9 +3786,9 @@ class MMGenTestSuite(object):
 	def ethdev_token_transfer_ops(self,name,op,amt=1000):
 	def ethdev_token_transfer_ops(self,name,op,amt=1000):
 		MMGenExpect(name,'',msg_only=True)
 		MMGenExpect(name,'',msg_only=True)
 		sid = cfgs['8']['seed_id']
 		sid = cfgs['8']['seed_id']
-		from mmgen.tool import Gen_addr
+		from mmgen.tool import MMGenToolCmd
 		usr_mmaddrs = ['{}:E:{}'.format(sid,i) for i in (11,21)]
 		usr_mmaddrs = ['{}:E:{}'.format(sid,i) for i in (11,21)]
-		usr_addrs = [Gen_addr(addr,dfl_words,return_result=True) for addr in usr_mmaddrs]
+		usr_addrs = [MMGenToolCmd().gen_addr(addr,dfl_words,return_result=True) for addr in usr_mmaddrs]
 		self.ethdev_rpc_init()
 		self.ethdev_rpc_init()
 
 
 		from mmgen.altcoins.eth.contract import Token
 		from mmgen.altcoins.eth.contract import Token