Browse Source

`mmgen-tool usage`: various fixes and cleanups

The MMGen Project 2 years ago
parent
commit
642b45b2e3

+ 2 - 2
mmgen/baseconv.py

@@ -132,7 +132,7 @@ class baseconv(object):
 			die('BaseConversionPadError',f"{pad!r}: illegal value for 'pad' (must be None,'seed' or int)")
 			die('BaseConversionPadError',f"{pad!r}: illegal value for 'pad' (must be None,'seed' or int)")
 
 
 	def tohex(self,words_arg,pad=None):
 	def tohex(self,words_arg,pad=None):
-		"convert string or list data of instance base to hex string"
+		"convert string or list data of instance base to a hexadecimal string"
 		return self.tobytes(words_arg,pad//2 if type(pad)==int else pad).hex()
 		return self.tobytes(words_arg,pad//2 if type(pad)==int else pad).hex()
 
 
 	def tobytes(self,words_arg,pad=None):
 	def tobytes(self,words_arg,pad=None):
@@ -166,7 +166,7 @@ class baseconv(object):
 		return ret.to_bytes(max(pad_val,bl//8+bool(bl%8)),'big')
 		return ret.to_bytes(max(pad_val,bl//8+bool(bl%8)),'big')
 
 
 	def fromhex(self,hexstr,pad=None,tostr=False):
 	def fromhex(self,hexstr,pad=None,tostr=False):
-		"convert hex string to list or string data of instance base"
+		"convert a hexadecimal string to a list or string data of instance base"
 
 
 		from .util import is_hex_str
 		from .util import is_hex_str
 		if not is_hex_str(hexstr):
 		if not is_hex_str(hexstr):

+ 16 - 19
mmgen/main_tool.py

@@ -42,7 +42,7 @@ opts_data = {
 -q, --quiet            Produce quieter output
 -q, --quiet            Produce quieter output
 -r, --usr-randchars=n  Get 'n' characters of additional randomness from
 -r, --usr-randchars=n  Get 'n' characters of additional randomness from
                        user (min={g.min_urandchars}, max={g.max_urandchars})
                        user (min={g.min_urandchars}, max={g.max_urandchars})
--t, --type=t           Specify address type (valid options: 'legacy',
+-t, --type=t           Specify address type (valid choices: 'legacy',
                        'compressed', 'segwit', 'bech32', 'zcash_z')
                        'compressed', 'segwit', 'bech32', 'zcash_z')
 -v, --verbose          Produce more verbose output
 -v, --verbose          Produce more verbose output
 -X, --cached-balances  Use cached balances (Ethereum only)
 -X, --cached-balances  Use cached balances (Ethereum only)
@@ -53,7 +53,7 @@ opts_data = {
                                COMMANDS
                                COMMANDS
 
 
 {ch}
 {ch}
-Type '{pn} help <command>' for help on a particular command
+Type ‘{pn} help <command>’ for help on a particular command
 """
 """
 	},
 	},
 	'code': {
 	'code': {
@@ -171,7 +171,7 @@ mods = {
 	),
 	),
 }
 }
 
 
-def create_call_sig(cmd,cls,parsed=False):
+def create_call_sig(cmd,cls,as_string=False):
 
 
 	m = getattr(cls,cmd)
 	m = getattr(cls,cmd)
 
 
@@ -190,29 +190,26 @@ def create_call_sig(cmd,cls,parsed=False):
 		ann[a] if a in ann and isinstance(ann[a],type) else type(dfls[i])
 		ann[a] if a in ann and isinstance(ann[a],type) else type(dfls[i])
 			for i,a in enumerate(args[nargs:]) )
 			for i,a in enumerate(args[nargs:]) )
 
 
-	def get_type_from_ann(arg):
-		return (
-			('str' + ('' if parsed else ' or STDIN')) if ann[arg] == 'sstr' else
-			ann[arg].__name__ )
-
-	if parsed:
+	if as_string:
+		get_type_from_ann = lambda x: 'str or STDIN' if ann[x] == 'sstr' else ann[x].__name__
+		return ' '.join(
+			[f'{a} [{get_type_from_ann(a)}]' for a in args[:nargs]] +
+			['{a} [{b}={c!r}{d}]'.format(
+				a = a,
+				b = dfl_types[n].__name__,
+				c = dfls[n],
+				d = (' ' + ann[a] if a in ann and isinstance(ann[a],str) else ''))
+					for n,a in enumerate(args[nargs:])] )
+	else:
+		get_type_from_ann = lambda x: 'str' if ann[x] == 'sstr' else ann[x].__name__
 		return (
 		return (
 			[(a,get_type_from_ann(a)) for a in args[:nargs]],            # c_args
 			[(a,get_type_from_ann(a)) for a in args[:nargs]],            # c_args
 			dict([(a,dfls[n]) for n,a in enumerate(args[nargs:])]),      # c_kwargs
 			dict([(a,dfls[n]) for n,a in enumerate(args[nargs:])]),      # c_kwargs
 			dict([(a,dfl_types[n]) for n,a in enumerate(args[nargs:])]), # c_kwargs_types
 			dict([(a,dfl_types[n]) for n,a in enumerate(args[nargs:])]), # c_kwargs_types
 			('STDIN_OK' if nargs and ann[args[0]] == 'sstr' else flag) ) # flag
 			('STDIN_OK' if nargs and ann[args[0]] == 'sstr' else flag) ) # flag
-	else:
-		c_args = [f'{a} [{get_type_from_ann(a)}]' for a in args[:nargs]]
-		c_kwargs = ['{a} [{b}={c!r}{d}]'.format(
-			a = a,
-			b = dfl_types[n].__name__,
-			c = dfls[n],
-			d = (' ' + ann[a] if a in ann and isinstance(ann[a],str) else '') )
-				for n,a in enumerate(args[nargs:])]
-		return ' '.join(c_args + c_kwargs)
 
 
 def process_args(cmd,cmd_args,cls):
 def process_args(cmd,cmd_args,cls):
-	c_args,c_kwargs,c_kwargs_types,flag = create_call_sig(cmd,cls,parsed=True)
+	c_args,c_kwargs,c_kwargs_types,flag = create_call_sig(cmd,cls)
 	have_stdin_input = False
 	have_stdin_input = False
 
 
 	def usage_die(s):
 	def usage_die(s):

+ 2 - 2
mmgen/mn_entry.py

@@ -392,7 +392,7 @@ class MnemonicEntry(object):
 		}
 		}
 		wl = wl.lower()
 		wl = wl.lower()
 		if wl not in d:
 		if wl not in d:
-			raise ValueError(f'wordlist {wl!r} not recognized (valid options: {fmt_list(list(d))})')
+			raise ValueError(f'wordlist {wl!r} not recognized (valid choices: {fmt_list(list(d))})')
 		return d[wl]
 		return d[wl]
 
 
 	@classmethod
 	@classmethod
@@ -402,7 +402,7 @@ class MnemonicEntry(object):
 			if v not in tcls.entry_modes:
 			if v not in tcls.entry_modes:
 				raise ValueError(
 				raise ValueError(
 					f'entry mode {v!r} not recognized for wordlist {k!r}:' +
 					f'entry mode {v!r} not recognized for wordlist {k!r}:' +
-					f'\n    (valid options: {fmt_list(tcls.entry_modes)})' )
+					f'\n    (valid choices: {fmt_list(tcls.entry_modes)})' )
 			tcls.usr_dfl_entry_mode = v
 			tcls.usr_dfl_entry_mode = v
 
 
 class MnemonicEntryMMGen(MnemonicEntry):
 class MnemonicEntryMMGen(MnemonicEntry):

+ 20 - 17
mmgen/tool/coin.py

@@ -32,11 +32,11 @@ class tool_cmd(tool_cmd_base):
 	"""
 	"""
 	cryptocoin key/address utilities
 	cryptocoin key/address utilities
 
 
-		May require use of the '--coin', '--type' and/or '--testnet' options
+	May require use of the '--coin', '--type' and/or '--testnet' options
 
 
-		Examples:
-			mmgen-tool --coin=ltc --type=bech32 wif2addr <wif key>
-			mmgen-tool --coin=zec --type=zcash_z randpair
+	Examples:
+	  mmgen-tool --coin=ltc --type=bech32 wif2addr <wif key>
+	  mmgen-tool --coin=zec --type=zcash_z randpair
 	"""
 	"""
 
 
 	need_proto = True
 	need_proto = True
@@ -71,13 +71,13 @@ class tool_cmd(tool_cmd_base):
 			gd.ag.to_addr( gd.kg.gen_data(privkey) ))
 			gd.ag.to_addr( gd.kg.gen_data(privkey) ))
 
 
 	def wif2hex(self,wifkey:'sstr'):
 	def wif2hex(self,wifkey:'sstr'):
-		"convert a private key from WIF to hex format"
+		"convert a private key from WIF to hexadecimal format"
 		return PrivKey(
 		return PrivKey(
 			self.proto,
 			self.proto,
 			wif = wifkey ).hex()
 			wif = wifkey ).hex()
 
 
 	def hex2wif(self,privhex:'sstr'):
 	def hex2wif(self,privhex:'sstr'):
-		"convert a private key from hex to WIF format"
+		"convert a private key from hexadecimal to WIF format"
 		return PrivKey(
 		return PrivKey(
 			self.proto,
 			self.proto,
 			bytes.fromhex(privhex),
 			bytes.fromhex(privhex),
@@ -102,7 +102,7 @@ class tool_cmd(tool_cmd_base):
 		return gd.ag.to_segwit_redeem_script( gd.kg.gen_data(privkey) )
 		return gd.ag.to_segwit_redeem_script( gd.kg.gen_data(privkey) )
 
 
 	def wif2segwit_pair(self,wifkey:'sstr'):
 	def wif2segwit_pair(self,wifkey:'sstr'):
-		"generate both a Segwit P2SH-P2WPKH redeem script and address from WIF"
+		"generate a Segwit P2SH-P2WPKH redeem script and address from a WIF private key"
 		assert self.mmtype.name == 'segwit','This command is meaningful only for --type=segwit'
 		assert self.mmtype.name == 'segwit','This command is meaningful only for --type=segwit'
 		gd = self._init_generators()
 		gd = self._init_generators()
 		data = gd.kg.gen_data(PrivKey(
 		data = gd.kg.gen_data(PrivKey(
@@ -112,8 +112,7 @@ class tool_cmd(tool_cmd_base):
 			gd.ag.to_segwit_redeem_script(data),
 			gd.ag.to_segwit_redeem_script(data),
 			gd.ag.to_addr(data) )
 			gd.ag.to_addr(data) )
 
 
-	def privhex2addr(self,privhex:'sstr',output_pubhex=False):
-		"generate coin address from raw private key data in hexadecimal format"
+	def _privhex2out(self,privhex:'sstr',output_pubhex=False):
 		gd = self._init_generators()
 		gd = self._init_generators()
 		pk = PrivKey(
 		pk = PrivKey(
 			self.proto,
 			self.proto,
@@ -123,12 +122,16 @@ class tool_cmd(tool_cmd_base):
 		data = gd.kg.gen_data(pk)
 		data = gd.kg.gen_data(pk)
 		return data.pubkey.hex() if output_pubhex else gd.ag.to_addr(data)
 		return data.pubkey.hex() if output_pubhex else gd.ag.to_addr(data)
 
 
+	def privhex2addr(self,privhex:'sstr'):
+		"generate a coin address from raw hexadecimal private key data"
+		return self._privhex2out(privhex)
+
 	def privhex2pubhex(self,privhex:'sstr'): # new
 	def privhex2pubhex(self,privhex:'sstr'): # new
-		"generate a hex public key from a hex private key"
-		return self.privhex2addr(privhex,output_pubhex=True)
+		"generate a hexadecimal public key from raw hexadecimal private key data"
+		return self._privhex2out(privhex,output_pubhex=True)
 
 
 	def pubhex2addr(self,pubkeyhex:'sstr'):
 	def pubhex2addr(self,pubkeyhex:'sstr'):
-		"convert a hex pubkey to an address"
+		"convert a hexadecimal pubkey to an address"
 		if self.proto.base_proto == 'Ethereum' and len(pubkeyhex) == 128: # support raw ETH pubkeys
 		if self.proto.base_proto == 'Ethereum' and len(pubkeyhex) == 128: # support raw ETH pubkeys
 			pubkeyhex = '04' + pubkeyhex
 			pubkeyhex = '04' + pubkeyhex
 		from ..keygen import keygen_public_data
 		from ..keygen import keygen_public_data
@@ -141,19 +144,19 @@ class tool_cmd(tool_cmd_base):
 		))
 		))
 
 
 	def pubhex2redeem_script(self,pubkeyhex:'sstr'): # new
 	def pubhex2redeem_script(self,pubkeyhex:'sstr'): # new
-		"convert a hex pubkey to a Segwit P2SH-P2WPKH redeem script"
+		"convert a hexadecimal pubkey to a Segwit P2SH-P2WPKH redeem script"
 		assert self.mmtype.name == 'segwit','This command is meaningful only for --type=segwit'
 		assert self.mmtype.name == 'segwit','This command is meaningful only for --type=segwit'
 		from ..proto.common import hash160
 		from ..proto.common import hash160
 		return self.proto.pubhash2redeem_script( hash160(bytes.fromhex(pubkeyhex)) ).hex()
 		return self.proto.pubhash2redeem_script( hash160(bytes.fromhex(pubkeyhex)) ).hex()
 
 
-	def redeem_script2addr(self,redeem_scripthex:'sstr'): # new
+	def redeem_script2addr(self,redeem_script_hex:'sstr'): # new
 		"convert a Segwit P2SH-P2WPKH redeem script to an address"
 		"convert a Segwit P2SH-P2WPKH redeem script to an address"
 		assert self.mmtype.name == 'segwit', 'This command is meaningful only for --type=segwit'
 		assert self.mmtype.name == 'segwit', 'This command is meaningful only for --type=segwit'
-		assert redeem_scripthex[:4] == '0014', f'{redeem_scripthex!r}: invalid redeem script'
-		assert len(redeem_scripthex) == 44, f'{len(redeem_scripthex)//2} bytes: invalid redeem script length'
+		assert redeem_script_hex[:4] == '0014', f'{redeem_script_hex!r}: invalid redeem script'
+		assert len(redeem_script_hex) == 44, f'{len(redeem_script_hex)//2} bytes: invalid redeem script length'
 		from ..proto.common import hash160
 		from ..proto.common import hash160
 		return self.proto.pubhash2addr(
 		return self.proto.pubhash2addr(
-			hash160( bytes.fromhex(redeem_scripthex) ),
+			hash160( bytes.fromhex(redeem_script_hex) ),
 			p2sh = True )
 			p2sh = True )
 
 
 	def pubhash2addr(self,pubhashhex:'sstr'):
 	def pubhash2addr(self,pubhashhex:'sstr'):

+ 1 - 1
mmgen/tool/common.py

@@ -23,7 +23,7 @@ tool/common.py: Base class and shared routines for the 'mmgen-tool' utility
 from ..objmethods import MMGenObject
 from ..objmethods import MMGenObject
 
 
 def options_annot_str(l):
 def options_annot_str(l):
-	return "(valid options: '{}')".format( "','".join(l) )
+	return "(valid choices: '{}')".format( "','".join(l) )
 
 
 class tool_cmd_base(MMGenObject):
 class tool_cmd_base(MMGenObject):
 
 

+ 2 - 2
mmgen/tool/file.py

@@ -78,13 +78,13 @@ class tool_cmd(tool_cmd_base):
 				'dfls': ( False, False, 'addr', 'mtime' ),
 				'dfls': ( False, False, 'addr', 'mtime' ),
 				'annots': {
 				'annots': {
 					'mmgen_tx_file(s)': str,
 					'mmgen_tx_file(s)': str,
-					'sort': options_annot_str(['addr','raw']),
+					'sort':     options_annot_str(['addr','raw']),
 					'filesort': options_annot_str(['mtime','ctime','atime']),
 					'filesort': options_annot_str(['mtime','ctime','atime']),
 				}
 				}
 			},
 			},
 			*infiles,
 			*infiles,
 			**kwargs ):
 			**kwargs ):
-		"show raw/signed MMGen transaction in human-readable form"
+		"display specified raw or signed MMGen transaction files in human-readable form"
 
 
 		terse = bool(kwargs.get('terse'))
 		terse = bool(kwargs.get('terse'))
 		tx_sort = kwargs.get('sort') or 'addr'
 		tx_sort = kwargs.get('sort') or 'addr'

+ 4 - 4
mmgen/tool/filecrypt.py

@@ -30,10 +30,10 @@ class tool_cmd(tool_cmd_base):
 	"""
 	"""
 	file encryption and decryption
 	file encryption and decryption
 
 
-		MMGen encryption suite:
-		* Key: Scrypt (user-configurable hash parameters, 32-byte salt)
-		* Enc: AES256_CTR, 16-byte rand IV, sha256 hash + 32-byte nonce + data
-		* The encrypted file is indistinguishable from random data
+	MMGen encryption suite:
+	* Key: Scrypt (user-configurable hash parameters, 32-byte salt)
+	* Enc: AES256_CTR, 16-byte rand IV, sha256 hash + 32-byte nonce + data
+	* The encrypted file is indistinguishable from random data
 	"""
 	"""
 	def encrypt(self,infile:str,outfile='',hash_preset=''):
 	def encrypt(self,infile:str,outfile='',hash_preset=''):
 		"encrypt a file"
 		"encrypt a file"

+ 3 - 1
mmgen/tool/fileutil.py

@@ -65,7 +65,9 @@ class tool_cmd(tool_cmd_base):
 		return True
 		return True
 
 
 	def rand2file(self,outfile:str,nbytes:str,threads=4,silent=False):
 	def rand2file(self,outfile:str,nbytes:str,threads=4,silent=False):
-		"write 'n' bytes of random data to specified file"
+		"""
+		write ‘nbytes’ bytes of random data to specified file (dd-style byte specifiers supported)
+		"""
 		from threading import Thread
 		from threading import Thread
 		from queue import Queue
 		from queue import Queue
 		from cryptography.hazmat.primitives.ciphers import Cipher,algorithms,modes
 		from cryptography.hazmat.primitives.ciphers import Cipher,algorithms,modes

+ 57 - 24
mmgen/tool/help.py

@@ -59,7 +59,9 @@ def main_help():
 
 
 	return '\n'.join(do())
 	return '\n'.join(do())
 
 
-def usage(cmdname=None,exit_val=1):
+def gen_tool_usage():
+
+	from ..util import capfirst
 
 
 	m1 = """
 	m1 = """
 		USAGE INFORMATION FOR MMGEN-TOOL COMMANDS:
 		USAGE INFORMATION FOR MMGEN-TOOL COMMANDS:
@@ -68,12 +70,16 @@ def usage(cmdname=None,exit_val=1):
 
 
 		  Arguments with both type and default value specified in square brackets are
 		  Arguments with both type and default value specified in square brackets are
 		  optional and must be specified in the form ‘name=value’
 		  optional and must be specified in the form ‘name=value’
+
+		  For more detailed usage information for a particular tool command, type
+		  ‘mmgen-tool help <command name>’
 		"""
 		"""
 
 
 	m2 = """
 	m2 = """
 		  To force a command to read from STDIN instead of file (for commands taking
 		  To force a command to read from STDIN instead of file (for commands taking
 		  a filename as their first argument), substitute "-" for the filename.
 		  a filename as their first argument), substitute "-" for the filename.
 
 
+
 		EXAMPLES:
 		EXAMPLES:
 
 
 		  Generate a random LTC Bech32 public/private keypair:
 		  Generate a random LTC Bech32 public/private keypair:
@@ -97,39 +103,66 @@ def usage(cmdname=None,exit_val=1):
 		  Reverse a hex string:
 		  Reverse a hex string:
 		  $ mmgen-tool hexreverse "deadbeefcafe"
 		  $ mmgen-tool hexreverse "deadbeefcafe"
 
 
-		  Same as above, but supply input via stdin:
+		  Same as above, but supply input via STDIN:
 		  $ echo "deadbeefcafe" | mmgen-tool hexreverse -
 		  $ echo "deadbeefcafe" | mmgen-tool hexreverse -
 		"""
 		"""
 
 
-	from ..util import Msg,Msg_r,fmt,die,capfirst
+	for line in m1.lstrip().split('\n'):
+		yield line.lstrip('\t')
+
+	for clsname,cmdlist in main_tool.mods.items():
+		cls = main_tool.get_mod_cls(clsname)
+		cls_docstr = cls.__doc__.strip()
+		yield ''
+		yield '  {}:'.format( capfirst(cls_docstr.split('\n')[0].strip()) )
+		yield ''
+
+		if '\n' in cls_docstr:
+			for line in cls_docstr.split('\n')[2:]:
+				yield '    ' + line.lstrip('\t')
+			yield ''
+
+		max_w = max(map(len,cmdlist))
+		for cmdname in cmdlist:
+			yield '    {a:{w}} {b}'.format(
+				a = cmdname,
+				b = main_tool.create_call_sig(cmdname,cls,as_string=True),
+				w = max_w )
+		yield ''
+
+	for line in m2.rstrip().split('\n'):
+		yield line.lstrip('\t')
+
+def gen_tool_cmd_usage(mod,cmdname):
+
+	from ..globalvars import g
+	from ..util import capfirst
+
+	cls = main_tool.get_mod_cls(mod)
+	docstr = getattr(cls,cmdname).__doc__.strip()
+	args,kwargs,kwargs_types,flag = main_tool.create_call_sig(cmdname,cls)
+
+	yield '{a}\n\nUSAGE: {b} {c} {d}{e}'.format(
+		a = capfirst( docstr.split('\n')[0].strip() ),
+		b = g.prog_name,
+		c = cmdname,
+		d = main_tool.create_call_sig(cmdname,cls,as_string=True),
+		e = '\n\n' + fmt('\n'.join(docstr.split('\n')[1:]),strip_char='\t').rstrip()
+			if '\n' in docstr else '' )
+
+def usage(cmdname=None,exit_val=1):
+
+	from ..util import Msg,die,do_pager
 
 
 	if cmdname:
 	if cmdname:
-		from ..globalvars import g
 		for mod,cmdlist in main_tool.mods.items():
 		for mod,cmdlist in main_tool.mods.items():
 			if cmdname in cmdlist:
 			if cmdname in cmdlist:
-				cls = main_tool.get_mod_cls(mod)
-				docstr = getattr(cls,cmdname).__doc__.strip()
-				Msg('{a}\n\nUSAGE: {b} {c} {d}{e}'.format(
-					a = capfirst( docstr.split('\n')[0].strip() ),
-					b = g.prog_name,
-					c = cmdname,
-					d = main_tool.create_call_sig(cmdname,cls),
-					e = '\n\n' + fmt('\n'.join(docstr.split('\n')[1:]),strip_char='\t').rstrip()
-						if '\n' in docstr else '' ))
+				Msg('\n'.join(gen_tool_cmd_usage(mod,cmdname)))
 				break
 				break
 		else:
 		else:
 			die(1,f'{cmdname!r}: no such tool command')
 			die(1,f'{cmdname!r}: no such tool command')
 	else:
 	else:
-		Msg(fmt(m1,strip_char='\t'))
-		for clsname,cmdlist in main_tool.mods.items():
-			cls = main_tool.get_mod_cls(clsname)
-			cls_info = cls.__doc__.strip().split('\n')[0]
-			Msg('  {}{}:\n'.format( cls_info[0].upper(), cls_info[1:] ))
-			max_w = max(map(len,cmdlist))
-			for cmdname in cmdlist:
-				Msg(f'    {cmdname:{max_w}} {main_tool.create_call_sig(cmdname,cls)}')
-			Msg('')
-		Msg_r('  ' + fmt(m2,strip_char='\t'))
+		do_pager('\n'.join(gen_tool_usage()))
 
 
 	import sys
 	import sys
 	sys.exit(exit_val)
 	sys.exit(exit_val)
@@ -142,5 +175,5 @@ class tool_cmd(tool_cmd_base):
 		usage(command_name,exit_val=0)
 		usage(command_name,exit_val=0)
 
 
 	def usage(self,command_name=''):
 	def usage(self,command_name=''):
-		"display usage information for a single command"
+		"display usage information for a single command or all commands"
 		usage(command_name,exit_val=0)
 		usage(command_name,exit_val=0)

+ 27 - 25
mmgen/tool/mnemonic.py

@@ -39,23 +39,25 @@ mn_opts_disp = options_annot_str(mnemonic_fmts)
 
 
 class tool_cmd(tool_cmd_base):
 class tool_cmd(tool_cmd_base):
 	"""
 	"""
-	seed phrase utilities (valid formats: 'mmgen' (default), 'bip39', 'xmrseed')
-
-		IMPORTANT NOTE: MMGen's default seed phrase format uses the Electrum
-		wordlist, however seed phrases are computed using a different algorithm
-		and are NOT Electrum-compatible!
-
-		BIP39 support is fully compatible with the standard, allowing users to
-		import and export seed entropy from BIP39-compatible wallets.  However,
-		users should be aware that BIP39 support does not imply BIP32 support!
-		MMGen uses its own key derivation scheme differing from the one described
-		by the BIP32 protocol.
-
-		For Monero ('xmrseed') seed phrases, input data is reduced to a spendkey
-		before conversion so that a canonical seed phrase is produced.  This is
-		required because Monero seeds, unlike ordinary wallet seeds, are tied
-		to a concrete key/address pair.  To manually generate a Monero spendkey,
-		use the 'hex2wif' command.
+	seed phrase utilities
+
+	Supported seed phrase formats: 'mmgen' (default), 'bip39', 'xmrseed'
+
+	IMPORTANT NOTE: MMGen’s default seed phrase format uses the Electrum
+	wordlist, however seed phrases are computed using a different algorithm
+	and are NOT Electrum-compatible!
+
+	BIP39 support is fully compatible with the standard, allowing users to
+	import and export seed entropy from BIP39-compatible wallets.  However,
+	users should be aware that BIP39 support does not imply BIP32 support!
+	MMGen uses its own key derivation scheme differing from the one described
+	by the BIP32 protocol.
+
+	For Monero (‘xmrseed’) seed phrases, input data is reduced to a spendkey
+	before conversion so that a canonical seed phrase is produced.  This is
+	required because Monero seeds, unlike ordinary wallet seeds, are tied
+	to a concrete key/address pair.  To manually generate a Monero spendkey,
+	use the ‘hex2wif’ command.
 	"""
 	"""
 
 
 	@staticmethod
 	@staticmethod
@@ -81,31 +83,31 @@ class tool_cmd(tool_cmd_base):
 		return self.hex2mn(randbytes.hex(),fmt=fmt)
 		return self.hex2mn(randbytes.hex(),fmt=fmt)
 
 
 	def mn_rand128(self, fmt:mn_opts_disp = dfl_mnemonic_fmt ):
 	def mn_rand128(self, fmt:mn_opts_disp = dfl_mnemonic_fmt ):
-		"generate random 128-bit mnemonic seed phrase"
+		"generate a random 128-bit mnemonic seed phrase"
 		return self._do_random_mn(16,fmt)
 		return self._do_random_mn(16,fmt)
 
 
 	def mn_rand192(self, fmt:mn_opts_disp = dfl_mnemonic_fmt ):
 	def mn_rand192(self, fmt:mn_opts_disp = dfl_mnemonic_fmt ):
-		"generate random 192-bit mnemonic seed phrase"
+		"generate a random 192-bit mnemonic seed phrase"
 		return self._do_random_mn(24,fmt)
 		return self._do_random_mn(24,fmt)
 
 
 	def mn_rand256(self, fmt:mn_opts_disp = dfl_mnemonic_fmt ):
 	def mn_rand256(self, fmt:mn_opts_disp = dfl_mnemonic_fmt ):
-		"generate random 256-bit mnemonic seed phrase"
+		"generate a random 256-bit mnemonic seed phrase"
 		return self._do_random_mn(32,fmt)
 		return self._do_random_mn(32,fmt)
 
 
 	def hex2mn( self, hexstr:'sstr', fmt:mn_opts_disp = dfl_mnemonic_fmt ):
 	def hex2mn( self, hexstr:'sstr', fmt:mn_opts_disp = dfl_mnemonic_fmt ):
-		"convert a 16, 24 or 32-byte hexadecimal number to a mnemonic seed phrase"
+		"convert a 16, 24 or 32-byte hexadecimal string to a mnemonic seed phrase"
 		if fmt == 'xmrseed':
 		if fmt == 'xmrseed':
 			hexstr = self._xmr_reduce(bytes.fromhex(hexstr)).hex()
 			hexstr = self._xmr_reduce(bytes.fromhex(hexstr)).hex()
 		f = mnemonic_fmts[fmt]
 		f = mnemonic_fmts[fmt]
 		return ' '.join( f.conv_cls(fmt).fromhex(hexstr,f.pad) )
 		return ' '.join( f.conv_cls(fmt).fromhex(hexstr,f.pad) )
 
 
 	def mn2hex( self, seed_mnemonic:'sstr', fmt:mn_opts_disp = dfl_mnemonic_fmt ):
 	def mn2hex( self, seed_mnemonic:'sstr', fmt:mn_opts_disp = dfl_mnemonic_fmt ):
-		"convert a mnemonic seed phrase to a hexadecimal number"
+		"convert a mnemonic seed phrase to a hexadecimal string"
 		f = mnemonic_fmts[fmt]
 		f = mnemonic_fmts[fmt]
 		return f.conv_cls(fmt).tohex( seed_mnemonic.split(), f.pad )
 		return f.conv_cls(fmt).tohex( seed_mnemonic.split(), f.pad )
 
 
 	def mn2hex_interactive( self, fmt:mn_opts_disp = dfl_mnemonic_fmt, mn_len=24, print_mn=False ):
 	def mn2hex_interactive( self, fmt:mn_opts_disp = dfl_mnemonic_fmt, mn_len=24, print_mn=False ):
-		"convert an interactively supplied mnemonic seed phrase to a hexadecimal number"
+		"convert an interactively supplied mnemonic seed phrase to a hexadecimal string"
 		from ..mn_entry import mn_entry
 		from ..mn_entry import mn_entry
 		mn = mn_entry(fmt).get_mnemonic_from_user(25 if fmt == 'xmrseed' else mn_len,validate=False)
 		mn = mn_entry(fmt).get_mnemonic_from_user(25 if fmt == 'xmrseed' else mn_len,validate=False)
 		if print_mn:
 		if print_mn:
@@ -114,11 +116,11 @@ class tool_cmd(tool_cmd_base):
 		return self.mn2hex(seed_mnemonic=mn,fmt=fmt)
 		return self.mn2hex(seed_mnemonic=mn,fmt=fmt)
 
 
 	def mn_stats(self, fmt:mn_opts_disp = dfl_mnemonic_fmt ):
 	def mn_stats(self, fmt:mn_opts_disp = dfl_mnemonic_fmt ):
-		"show stats for mnemonic wordlist"
+		"show stats for a mnemonic wordlist"
 		return mnemonic_fmts[fmt].conv_cls(fmt).check_wordlist()
 		return mnemonic_fmts[fmt].conv_cls(fmt).check_wordlist()
 
 
 	def mn_printlist( self, fmt:mn_opts_disp = dfl_mnemonic_fmt, enum=False, pager=False ):
 	def mn_printlist( self, fmt:mn_opts_disp = dfl_mnemonic_fmt, enum=False, pager=False ):
-		"print mnemonic wordlist"
+		"print a mnemonic wordlist"
 		ret = mnemonic_fmts[fmt].conv_cls(fmt).get_wordlist()
 		ret = mnemonic_fmts[fmt].conv_cls(fmt).get_wordlist()
 		if enum:
 		if enum:
 			ret = [f'{n:>4} {e}' for n,e in enumerate(ret)]
 			ret = [f'{n:>4} {e}' for n,e in enumerate(ret)]

+ 3 - 7
mmgen/tool/rpc.py

@@ -24,7 +24,7 @@ from .common import tool_cmd_base,options_annot_str
 from ..tw.common import TwCommon
 from ..tw.common import TwCommon
 
 
 class tool_cmd(tool_cmd_base):
 class tool_cmd(tool_cmd_base):
-	"tracking wallet commands using the JSON-RPC interface"
+	"tracking-wallet commands using the JSON-RPC interface"
 
 
 	need_proto = True
 	need_proto = True
 	need_amt = True
 	need_amt = True
@@ -43,17 +43,13 @@ class tool_cmd(tool_cmd_base):
 	async def listaddress(self,
 	async def listaddress(self,
 			mmgen_addr:str,
 			mmgen_addr:str,
 			minconf     = 1,
 			minconf     = 1,
-			pager       = False,
-			showempty   = True,
 			showbtcaddr = True,
 			showbtcaddr = True,
 			age_fmt: options_annot_str(TwCommon.age_fmts) = 'confs' ):
 			age_fmt: options_annot_str(TwCommon.age_fmts) = 'confs' ):
-		"list the specified MMGen address and its balance"
+		"list the specified MMGen address in the tracking wallet and its balance"
 
 
 		return await self.listaddresses(
 		return await self.listaddresses(
 			mmgen_addrs  = mmgen_addr,
 			mmgen_addrs  = mmgen_addr,
 			minconf      = minconf,
 			minconf      = minconf,
-			pager        = pager,
-			showempty    = showempty,
 			showbtcaddrs = showbtcaddr,
 			showbtcaddrs = showbtcaddr,
 			age_fmt      = age_fmt )
 			age_fmt      = age_fmt )
 
 
@@ -140,7 +136,7 @@ class tool_cmd(tool_cmd_base):
 			sort            = 'age',
 			sort            = 'age',
 			age_fmt: options_annot_str(TwCommon.age_fmts) = 'confs',
 			age_fmt: options_annot_str(TwCommon.age_fmts) = 'confs',
 			interactive     = False ):
 			interactive     = False ):
-		"view transaction history"
+		"view transaction history of tracking wallet"
 
 
 		from ..tw.txhistory import TwTxHistory
 		from ..tw.txhistory import TwTxHistory
 		obj = await TwTxHistory(self.proto,sinceblock=sinceblock)
 		obj = await TwTxHistory(self.proto,sinceblock=sinceblock)

+ 27 - 27
mmgen/tool/util.py

@@ -39,10 +39,10 @@ class tool_cmd(tool_cmd_base):
 		from ..util import int2bytespec
 		from ..util import int2bytespec
 		return int2bytespec( n, dd_style_byte_specifier, fmt, print_sym )
 		return int2bytespec( n, dd_style_byte_specifier, fmt, print_sym )
 
 
-	def randhex(self,nbytes='32'):
+	def randhex(self,nbytes=32):
 		"print 'n' bytes (default 32) of random data in hex format"
 		"print 'n' bytes (default 32) of random data in hex format"
 		from ..crypto import get_random
 		from ..crypto import get_random
-		return get_random( int(nbytes) ).hex()
+		return get_random( nbytes ).hex()
 
 
 	def hexreverse(self,hexstr:'sstr'):
 	def hexreverse(self,hexstr:'sstr'):
 		"reverse bytes of a hexadecimal string"
 		"reverse bytes of a hexadecimal string"
@@ -55,7 +55,7 @@ class tool_cmd(tool_cmd_base):
 		return data.hex()
 		return data.hex()
 
 
 	def unhexlify(self,hexstr:'sstr'):
 	def unhexlify(self,hexstr:'sstr'):
-		"convert hexadecimal value to bytes (warning: outputs binary data)"
+		"convert a hexadecimal string to bytes (warning: outputs binary data)"
 		return bytes.fromhex(hexstr)
 		return bytes.fromhex(hexstr)
 
 
 	def hexdump(self,infile:str,cols=8,line_nums='hex'):
 	def hexdump(self,infile:str,cols=8,line_nums='hex'):
@@ -81,17 +81,17 @@ class tool_cmd(tool_cmd_base):
 		from ..proto.common import hash160
 		from ..proto.common import hash160
 		return hash160( bytes.fromhex(hexstr) ).hex()
 		return hash160( bytes.fromhex(hexstr) ).hex()
 
 
-	def hash256(self,string_or_bytes:str,file_input=False,hex_input=False): # TODO: handle stdin
+	def hash256(self,data:str,file_input=False,hex_input=False): # TODO: handle stdin
 		"compute sha256(sha256(data)) (double sha256)"
 		"compute sha256(sha256(data)) (double sha256)"
 		from hashlib import sha256
 		from hashlib import sha256
 		if file_input:
 		if file_input:
 			from ..fileutil import get_data_from_file
 			from ..fileutil import get_data_from_file
-			b = get_data_from_file( string_or_bytes, binary=True )
+			b = get_data_from_file( data, binary=True )
 		elif hex_input:
 		elif hex_input:
 			from ..util import decode_pretty_hexdump
 			from ..util import decode_pretty_hexdump
-			b = decode_pretty_hexdump(string_or_bytes)
+			b = decode_pretty_hexdump(data)
 		else:
 		else:
-			b = string_or_bytes
+			b = data
 		return sha256(sha256(b.encode()).digest()).hexdigest()
 		return sha256(sha256(b.encode()).digest()).hexdigest()
 
 
 	def id6(self,infile:str):
 	def id6(self,infile:str):
@@ -102,7 +102,7 @@ class tool_cmd(tool_cmd_base):
 			get_data_from_file( infile, dash=True, quiet=True, binary=True ))
 			get_data_from_file( infile, dash=True, quiet=True, binary=True ))
 
 
 	def str2id6(self,string:'sstr'): # retain ignoring of space for backwards compat
 	def str2id6(self,string:'sstr'): # retain ignoring of space for backwards compat
-		"generate 6-character MMGen ID for a string, ignoring spaces"
+		"generate 6-character MMGen ID for a string, ignoring spaces in string"
 		from ..util import make_chksum_6
 		from ..util import make_chksum_6
 		return make_chksum_6( ''.join(string.split()) )
 		return make_chksum_6( ''.join(string.split()) )
 
 
@@ -126,50 +126,50 @@ class tool_cmd(tool_cmd_base):
 		data = get_data_from_file( infile, dash=True, quiet=True, binary=True )
 		data = get_data_from_file( infile, dash=True, quiet=True, binary=True )
 		return baseconv('b58').frombytes( data, pad=pad, tostr=True )
 		return baseconv('b58').frombytes( data, pad=pad, tostr=True )
 
 
-	def b58tobytes(self,b58num:'sstr',pad=0):
-		"convert a base 58 number to bytes (warning: outputs binary data)"
+	def b58tobytes(self,b58_str:'sstr',pad=0):
+		"convert a base 58 string to bytes (warning: outputs binary data)"
 		from ..baseconv import baseconv
 		from ..baseconv import baseconv
-		return baseconv('b58').tobytes( b58num, pad=pad )
+		return baseconv('b58').tobytes( b58_str, pad=pad )
 
 
 	def hextob58(self,hexstr:'sstr',pad=0):
 	def hextob58(self,hexstr:'sstr',pad=0):
-		"convert a hexadecimal number to base 58"
+		"convert a hexadecimal string to base 58"
 		from ..baseconv import baseconv
 		from ..baseconv import baseconv
 		return baseconv('b58').fromhex( hexstr, pad=pad, tostr=True )
 		return baseconv('b58').fromhex( hexstr, pad=pad, tostr=True )
 
 
-	def b58tohex(self,b58num:'sstr',pad=0):
-		"convert a base 58 number to hexadecimal"
+	def b58tohex(self,b58_str:'sstr',pad=0):
+		"convert a base 58 string to hexadecimal"
 		from ..baseconv import baseconv
 		from ..baseconv import baseconv
-		return baseconv('b58').tohex( b58num, pad=pad )
+		return baseconv('b58').tohex( b58_str, pad=pad )
 
 
 	def hextob58chk(self,hexstr:'sstr'):
 	def hextob58chk(self,hexstr:'sstr'):
-		"convert a hexadecimal number to base58-check encoding"
+		"convert a hexadecimal string to base58-check encoding"
 		from ..proto.common import b58chk_encode
 		from ..proto.common import b58chk_encode
 		return b58chk_encode( bytes.fromhex(hexstr) )
 		return b58chk_encode( bytes.fromhex(hexstr) )
 
 
-	def b58chktohex(self,b58chk_num:'sstr'):
-		"convert a base58-check encoded number to hexadecimal"
+	def b58chktohex(self,b58chk_str:'sstr'):
+		"convert a base58-check encoded string to hexadecimal"
 		from ..proto.common import b58chk_decode
 		from ..proto.common import b58chk_decode
-		return b58chk_decode(b58chk_num).hex()
+		return b58chk_decode(b58chk_str).hex()
 
 
 	def hextob32(self,hexstr:'sstr',pad=0):
 	def hextob32(self,hexstr:'sstr',pad=0):
-		"convert a hexadecimal number to MMGen's flavor of base 32"
+		"convert a hexadecimal string to an MMGen-flavor base 32 string"
 		from ..baseconv import baseconv
 		from ..baseconv import baseconv
 		return baseconv('b32').fromhex( hexstr, pad, tostr=True )
 		return baseconv('b32').fromhex( hexstr, pad, tostr=True )
 
 
-	def b32tohex(self,b32num:'sstr',pad=0):
-		"convert an MMGen-flavor base 32 number to hexadecimal"
+	def b32tohex(self,b32_str:'sstr',pad=0):
+		"convert an MMGen-flavor base 32 string to hexadecimal"
 		from ..baseconv import baseconv
 		from ..baseconv import baseconv
-		return baseconv('b32').tohex( b32num.upper(), pad )
+		return baseconv('b32').tohex( b32_str.upper(), pad )
 
 
 	def hextob6d(self,hexstr:'sstr',pad=0,add_spaces=True):
 	def hextob6d(self,hexstr:'sstr',pad=0,add_spaces=True):
-		"convert a hexadecimal number to die roll base6 (base6d)"
+		"convert a hexadecimal string to die roll base6 (base6d)"
 		from ..baseconv import baseconv
 		from ..baseconv import baseconv
 		from ..util import block_format
 		from ..util import block_format
 		ret = baseconv('b6d').fromhex(hexstr,pad,tostr=True)
 		ret = baseconv('b6d').fromhex(hexstr,pad,tostr=True)
 		return block_format( ret, gw=5, cols=None ).strip() if add_spaces else ret
 		return block_format( ret, gw=5, cols=None ).strip() if add_spaces else ret
 
 
-	def b6dtohex(self,b6d_num:'sstr',pad=0):
-		"convert a die roll base6 (base6d) number to hexadecimal"
+	def b6dtohex(self,b6d_str:'sstr',pad=0):
+		"convert a die roll base6 (base6d) string to hexadecimal"
 		from ..baseconv import baseconv
 		from ..baseconv import baseconv
 		from ..util import remove_whitespace
 		from ..util import remove_whitespace
-		return baseconv('b6d').tohex( remove_whitespace(b6d_num), pad )
+		return baseconv('b6d').tohex( remove_whitespace(b6d_str), pad )

+ 11 - 4
mmgen/tool/wallet.py

@@ -67,23 +67,30 @@ class tool_cmd(tool_cmd_base):
 		return Wallet(sf).seed.split( share_count, id_str, master_share ).format()
 		return Wallet(sf).seed.split( share_count, id_str, master_share ).format()
 
 
 	def gen_key(self,mmgen_addr:str,wallet=''):
 	def gen_key(self,mmgen_addr:str,wallet=''):
-		"generate a single MMGen WIF key from default or specified wallet"
-		return self.gen_addr( mmgen_addr, wallet, target='wif' )
+		"generate a single WIF key for specified MMGen address from default or specified wallet"
+		return self._gen_keyaddr( mmgen_addr, 'wif', wallet )
 
 
-	def gen_addr(self,mmgen_addr:str,wallet='',target='addr'):
+	def gen_addr(self,mmgen_addr:str,wallet=''):
 		"generate a single MMGen address from default or specified wallet"
 		"generate a single MMGen address from default or specified wallet"
+		return self._gen_keyaddr( mmgen_addr, 'addr', wallet )
+
+	def _gen_keyaddr(self,mmgen_addr,target,wallet=''):
 		from ..addr import MMGenID
 		from ..addr import MMGenID
 		from ..addrlist import AddrList,AddrIdxList
 		from ..addrlist import AddrList,AddrIdxList
+
 		addr = MMGenID( self.proto, mmgen_addr )
 		addr = MMGenID( self.proto, mmgen_addr )
 		opt.quiet = True
 		opt.quiet = True
 		sf = get_seed_file([wallet] if wallet else [],1)
 		sf = get_seed_file([wallet] if wallet else [],1)
 		ss = Wallet(sf)
 		ss = Wallet(sf)
+
 		if ss.seed.sid != addr.sid:
 		if ss.seed.sid != addr.sid:
 			from ..util import die
 			from ..util import die
 			die(1,f'Seed ID of requested address ({addr.sid}) does not match wallet ({ss.seed.sid})')
 			die(1,f'Seed ID of requested address ({addr.sid}) does not match wallet ({ss.seed.sid})')
+
 		d = AddrList(
 		d = AddrList(
 			proto     = self.proto,
 			proto     = self.proto,
 			seed      = ss.seed,
 			seed      = ss.seed,
 			addr_idxs = AddrIdxList(str(addr.idx)),
 			addr_idxs = AddrIdxList(str(addr.idx)),
 			mmtype    = addr.mmtype ).data[0]
 			mmtype    = addr.mmtype ).data[0]
-		return d.sec.wif if target == 'wif' else d.addr
+
+		return { 'wif': d.sec.wif, 'addr': d.addr }[target]

+ 1 - 1
test/tooltest.py

@@ -43,7 +43,7 @@ opts_data = {
 -L, --list-names    List the names of all tested 'mmgen-tool' commands
 -L, --list-names    List the names of all tested 'mmgen-tool' commands
 -s, --system        Test scripts and modules installed on system rather than
 -s, --system        Test scripts and modules installed on system rather than
                     those in the repo root
                     those in the repo root
--t, --type=t        Specify address type (valid options: 'zcash_z')
+-t, --type=t        Specify address type (valid choices: 'zcash_z')
 -v, --verbose       Produce more verbose output
 -v, --verbose       Produce more verbose output
 """,
 """,
 	'notes': """
 	'notes': """