diff --git a/mmgen/baseconv.py b/mmgen/baseconv.py index f6c34c13..b78e8c6b 100755 --- a/mmgen/baseconv.py +++ b/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)") 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() 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') 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 if not is_hex_str(hexstr): diff --git a/mmgen/main_tool.py b/mmgen/main_tool.py index 231d5f7f..654f2d2e 100755 --- a/mmgen/main_tool.py +++ b/mmgen/main_tool.py @@ -42,7 +42,7 @@ opts_data = { -q, --quiet Produce quieter output -r, --usr-randchars=n Get 'n' characters of additional randomness from 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') -v, --verbose Produce more verbose output -X, --cached-balances Use cached balances (Ethereum only) @@ -53,7 +53,7 @@ opts_data = { COMMANDS {ch} -Type '{pn} help ' for help on a particular command +Type ‘{pn} help ’ for help on a particular command """ }, '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) @@ -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]) 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 ( [(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,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 - 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): - 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 def usage_die(s): diff --git a/mmgen/mn_entry.py b/mmgen/mn_entry.py index fe246e14..2b14c01b 100755 --- a/mmgen/mn_entry.py +++ b/mmgen/mn_entry.py @@ -392,7 +392,7 @@ class MnemonicEntry(object): } wl = wl.lower() 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] @classmethod @@ -402,7 +402,7 @@ class MnemonicEntry(object): if v not in tcls.entry_modes: raise ValueError( 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 class MnemonicEntryMMGen(MnemonicEntry): diff --git a/mmgen/tool/coin.py b/mmgen/tool/coin.py index d171664f..42a30378 100755 --- a/mmgen/tool/coin.py +++ b/mmgen/tool/coin.py @@ -32,11 +32,11 @@ class tool_cmd(tool_cmd_base): """ 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 - mmgen-tool --coin=zec --type=zcash_z randpair + Examples: + mmgen-tool --coin=ltc --type=bech32 wif2addr + mmgen-tool --coin=zec --type=zcash_z randpair """ need_proto = True @@ -71,13 +71,13 @@ class tool_cmd(tool_cmd_base): gd.ag.to_addr( gd.kg.gen_data(privkey) )) 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( self.proto, wif = wifkey ).hex() 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( self.proto, 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) ) 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' gd = self._init_generators() 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_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() pk = PrivKey( self.proto, @@ -123,12 +122,16 @@ class tool_cmd(tool_cmd_base): data = gd.kg.gen_data(pk) 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 - "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'): - "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 pubkeyhex = '04' + pubkeyhex from ..keygen import keygen_public_data @@ -141,19 +144,19 @@ class tool_cmd(tool_cmd_base): )) 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' from ..proto.common import hash160 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" 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 return self.proto.pubhash2addr( - hash160( bytes.fromhex(redeem_scripthex) ), + hash160( bytes.fromhex(redeem_script_hex) ), p2sh = True ) def pubhash2addr(self,pubhashhex:'sstr'): diff --git a/mmgen/tool/common.py b/mmgen/tool/common.py index 9027eb12..e2e69799 100755 --- a/mmgen/tool/common.py +++ b/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 def options_annot_str(l): - return "(valid options: '{}')".format( "','".join(l) ) + return "(valid choices: '{}')".format( "','".join(l) ) class tool_cmd_base(MMGenObject): diff --git a/mmgen/tool/file.py b/mmgen/tool/file.py index 6baa1fc0..63ae947f 100755 --- a/mmgen/tool/file.py +++ b/mmgen/tool/file.py @@ -78,13 +78,13 @@ class tool_cmd(tool_cmd_base): 'dfls': ( False, False, 'addr', 'mtime' ), 'annots': { 'mmgen_tx_file(s)': str, - 'sort': options_annot_str(['addr','raw']), + 'sort': options_annot_str(['addr','raw']), 'filesort': options_annot_str(['mtime','ctime','atime']), } }, *infiles, **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')) tx_sort = kwargs.get('sort') or 'addr' diff --git a/mmgen/tool/filecrypt.py b/mmgen/tool/filecrypt.py index 162993b0..87f8fb61 100755 --- a/mmgen/tool/filecrypt.py +++ b/mmgen/tool/filecrypt.py @@ -30,10 +30,10 @@ class tool_cmd(tool_cmd_base): """ 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=''): "encrypt a file" diff --git a/mmgen/tool/fileutil.py b/mmgen/tool/fileutil.py index 098a4fb4..bae1908e 100755 --- a/mmgen/tool/fileutil.py +++ b/mmgen/tool/fileutil.py @@ -65,7 +65,9 @@ class tool_cmd(tool_cmd_base): return True 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 queue import Queue from cryptography.hazmat.primitives.ciphers import Cipher,algorithms,modes diff --git a/mmgen/tool/help.py b/mmgen/tool/help.py index 50f6edcb..a793765f 100755 --- a/mmgen/tool/help.py +++ b/mmgen/tool/help.py @@ -59,7 +59,9 @@ def main_help(): return '\n'.join(do()) -def usage(cmdname=None,exit_val=1): +def gen_tool_usage(): + + from ..util import capfirst m1 = """ 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 optional and must be specified in the form ‘name=value’ + + For more detailed usage information for a particular tool command, type + ‘mmgen-tool help ’ """ m2 = """ To force a command to read from STDIN instead of file (for commands taking a filename as their first argument), substitute "-" for the filename. + EXAMPLES: Generate a random LTC Bech32 public/private keypair: @@ -97,39 +103,66 @@ def usage(cmdname=None,exit_val=1): Reverse a hex string: $ 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 - """ - 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: - from ..globalvars import g for mod,cmdlist in main_tool.mods.items(): 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 else: die(1,f'{cmdname!r}: no such tool command') 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 sys.exit(exit_val) @@ -142,5 +175,5 @@ class tool_cmd(tool_cmd_base): usage(command_name,exit_val=0) 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) diff --git a/mmgen/tool/mnemonic.py b/mmgen/tool/mnemonic.py index fb41d693..012e9dc4 100755 --- a/mmgen/tool/mnemonic.py +++ b/mmgen/tool/mnemonic.py @@ -39,23 +39,25 @@ mn_opts_disp = options_annot_str(mnemonic_fmts) class tool_cmd(tool_cmd_base): """ - seed phrase utilities (valid formats: 'mmgen' (default), 'bip39', 'xmrseed') + seed phrase utilities - 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! + Supported seed phrase formats: 'mmgen' (default), 'bip39', 'xmrseed' - 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. + 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! - 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. + 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 @@ -81,31 +83,31 @@ class tool_cmd(tool_cmd_base): return self.hex2mn(randbytes.hex(),fmt=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) 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) 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) 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': hexstr = self._xmr_reduce(bytes.fromhex(hexstr)).hex() f = mnemonic_fmts[fmt] return ' '.join( f.conv_cls(fmt).fromhex(hexstr,f.pad) ) 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] 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 ): - "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 mn = mn_entry(fmt).get_mnemonic_from_user(25 if fmt == 'xmrseed' else mn_len,validate=False) if print_mn: @@ -114,11 +116,11 @@ class tool_cmd(tool_cmd_base): return self.mn2hex(seed_mnemonic=mn,fmt=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() 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() if enum: ret = [f'{n:>4} {e}' for n,e in enumerate(ret)] diff --git a/mmgen/tool/rpc.py b/mmgen/tool/rpc.py index c8773ddc..c34b333d 100755 --- a/mmgen/tool/rpc.py +++ b/mmgen/tool/rpc.py @@ -24,7 +24,7 @@ from .common import tool_cmd_base,options_annot_str from ..tw.common import TwCommon 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_amt = True @@ -43,17 +43,13 @@ class tool_cmd(tool_cmd_base): async def listaddress(self, mmgen_addr:str, minconf = 1, - pager = False, - showempty = True, showbtcaddr = True, 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( mmgen_addrs = mmgen_addr, minconf = minconf, - pager = pager, - showempty = showempty, showbtcaddrs = showbtcaddr, age_fmt = age_fmt ) @@ -140,7 +136,7 @@ class tool_cmd(tool_cmd_base): sort = 'age', age_fmt: options_annot_str(TwCommon.age_fmts) = 'confs', interactive = False ): - "view transaction history" + "view transaction history of tracking wallet" from ..tw.txhistory import TwTxHistory obj = await TwTxHistory(self.proto,sinceblock=sinceblock) diff --git a/mmgen/tool/util.py b/mmgen/tool/util.py index c116feff..a8e38035 100755 --- a/mmgen/tool/util.py +++ b/mmgen/tool/util.py @@ -39,10 +39,10 @@ class tool_cmd(tool_cmd_base): from ..util import int2bytespec 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" from ..crypto import get_random - return get_random( int(nbytes) ).hex() + return get_random( nbytes ).hex() def hexreverse(self,hexstr:'sstr'): "reverse bytes of a hexadecimal string" @@ -55,7 +55,7 @@ class tool_cmd(tool_cmd_base): return data.hex() 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) 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 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)" from hashlib import sha256 if file_input: 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: from ..util import decode_pretty_hexdump - b = decode_pretty_hexdump(string_or_bytes) + b = decode_pretty_hexdump(data) else: - b = string_or_bytes + b = data return sha256(sha256(b.encode()).digest()).hexdigest() 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 )) 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 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 ) 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 - return baseconv('b58').tobytes( b58num, pad=pad ) + return baseconv('b58').tobytes( b58_str, pad=pad ) 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 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 - return baseconv('b58').tohex( b58num, pad=pad ) + return baseconv('b58').tohex( b58_str, pad=pad ) 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 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 - return b58chk_decode(b58chk_num).hex() + return b58chk_decode(b58chk_str).hex() 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 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 - 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): - "convert a hexadecimal number to die roll base6 (base6d)" + "convert a hexadecimal string to die roll base6 (base6d)" from ..baseconv import baseconv from ..util import block_format ret = baseconv('b6d').fromhex(hexstr,pad,tostr=True) 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 ..util import remove_whitespace - return baseconv('b6d').tohex( remove_whitespace(b6d_num), pad ) + return baseconv('b6d').tohex( remove_whitespace(b6d_str), pad ) diff --git a/mmgen/tool/wallet.py b/mmgen/tool/wallet.py index 5c3f53b6..381348b9 100755 --- a/mmgen/tool/wallet.py +++ b/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() 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" + return self._gen_keyaddr( mmgen_addr, 'addr', wallet ) + + def _gen_keyaddr(self,mmgen_addr,target,wallet=''): from ..addr import MMGenID from ..addrlist import AddrList,AddrIdxList + addr = MMGenID( self.proto, mmgen_addr ) opt.quiet = True sf = get_seed_file([wallet] if wallet else [],1) ss = Wallet(sf) + if ss.seed.sid != addr.sid: from ..util import die die(1,f'Seed ID of requested address ({addr.sid}) does not match wallet ({ss.seed.sid})') + d = AddrList( proto = self.proto, seed = ss.seed, addr_idxs = AddrIdxList(str(addr.idx)), mmtype = addr.mmtype ).data[0] - return d.sec.wif if target == 'wif' else d.addr + + return { 'wif': d.sec.wif, 'addr': d.addr }[target] diff --git a/test/tooltest.py b/test/tooltest.py index 76884a38..6b0fe8b8 100755 --- a/test/tooltest.py +++ b/test/tooltest.py @@ -43,7 +43,7 @@ opts_data = { -L, --list-names List the names of all tested 'mmgen-tool' commands -s, --system Test scripts and modules installed on system rather than 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 """, 'notes': """