diff --git a/mmgen-tool b/mmgen-tool index 49f7b9a6..ae0111c7 100755 --- a/mmgen-tool +++ b/mmgen-tool @@ -20,10 +20,6 @@ mmgen-tool: Perform various Bitcoin-related operations. Part of the MMGen suite """ -import sys -print "MMGEN-TOOL was determined to be buggy and is in the process of maintenance." -print "Please check back soon!" -sys.exit(3) from mmgen.main import launch launch("tool") diff --git a/mmgen/main_tool.py b/mmgen/main_tool.py index 28dcb827..70f3a1e8 100755 --- a/mmgen/main_tool.py +++ b/mmgen/main_tool.py @@ -62,9 +62,9 @@ if cmd_args and cmd_args[0] == '--help': tool.tool_usage(g.prog_name, command) sys.exit(0) -args = tool.process_args(g.prog_name, command, cmd_args) +args,kwargs = tool.process_args(g.prog_name, command, cmd_args) +#msgrepr(args,kwargs) tool.opts = opts -#print command + "(" + ", ".join(args) + ")" -eval("tool." + command + "(" + ", ".join(args) + ")") +tool.__dict__[command](*args,**kwargs) diff --git a/mmgen/tool.py b/mmgen/tool.py index 266142d3..ea5d96a2 100755 --- a/mmgen/tool.py +++ b/mmgen/tool.py @@ -155,65 +155,53 @@ def tool_usage(prog_name, command): print "USAGE: '%s %s%s'" % (prog_name, command, (" "+" ".join(commands[command]) if commands[command] else "")) -def process_args(prog_name, command, uargs): - cargs = commands[command] - cargs_req = [[i.split(" [")[0],i.split(" [")[1][:-1]] - for i in cargs if "=" not in i] - cargs_nam = dict([[ +def process_args(prog_name, command, cmd_args): + c_args = [[i.split(" [")[0],i.split(" [")[1][:-1]] + for i in commands[command] if "=" not in i] + c_kwargs = dict([[ i.split(" [")[0], [i.split(" [")[1].split("=")[0], i.split(" [")[1].split("=")[1][:-1]] - ] for i in cargs if "=" in i]) - uargs_req = [i for i in uargs if "=" not in i] - uargs_nam = dict([i.split("=") for i in uargs if "=" in i]) + ] for i in commands[command] if "=" in i]) + u_args = cmd_args[:len(c_args)] + u_kwargs = dict([i.split("=") for i in cmd_args[len(c_args):]]) -# print cargs_req; print cargs_nam; print uargs_req; print uargs_nam; sys.exit() +# print c_args; print c_kwargs; print u_args; print u_kwargs; sys.exit() - n = len(cargs_req) - if len(uargs_req) != n: + if len(u_args) != len(c_args): tool_usage(prog_name, command) sys.exit(1) - for a in uargs_nam.keys(): - if a not in cargs_nam.keys(): - print "'%s' invalid named argument" % a - sys.exit(1) + if set(u_kwargs) > set(c_kwargs): + print "Invalid named argument" + sys.exit(1) - def test_type(arg_type,arg,name=""): + def convert_type(arg,arg_name,arg_type): try: - t = type(eval(arg)) - assert(t == eval(arg_type)) + return __builtins__[arg_type](arg) except: print "'%s': Invalid argument for argument %s ('%s' required)" % \ - (arg, name, arg_type) + (arg, arg_name, arg_type) sys.exit(1) - return True - ret = [] - - def normalize_arg(arg, arg_type): + def convert_to_bool_maybe(arg, arg_type): if arg_type == "bool": - if arg.lower() in ("true","yes","1","on"): return "True" - if arg.lower() in ("false","no","0","off"): return "False" + if arg.lower() in ("true","yes","1","on"): return True + if arg.lower() in ("false","no","0","off"): return False return arg - for i in range(len(cargs_req)): - arg_type = cargs_req[i][1] - arg = normalize_arg(uargs_req[i], arg_type) - if arg_type == "str": - ret.append('"%s"' % (arg)) - elif test_type(arg_type, arg, "#"+str(i+1)): - ret.append('%s' % (arg)) + args = [] + for i in range(len(c_args)): + arg_type = c_args[i][1] + arg = convert_to_bool_maybe(u_args[i], arg_type) + args.append(convert_type(arg,c_args[i][0],arg_type)) - for k in uargs_nam.keys(): - arg_type = cargs_nam[k][0] - arg = normalize_arg(uargs_nam[k], arg_type) - if arg_type == "str": - ret.append('%s="%s"' % (k, arg)) - elif test_type(arg_type, arg, "'"+k+"'"): - ret.append('%s=%s' % (k, arg)) - - return ret + kwargs = {} + for k in u_kwargs: + arg_type = c_kwargs[k][0] + arg = convert_to_bool_maybe(u_kwargs[k], arg_type) + kwargs[k] = convert_type(arg,k,arg_type) + return args,kwargs # Individual commands diff --git a/mmgen/util.py b/mmgen/util.py index 93129bd2..927876c0 100755 --- a/mmgen/util.py +++ b/mmgen/util.py @@ -316,7 +316,7 @@ def write_to_stdout(data, what, confirm=True): sys.stdout.write(data) -def write_to_file(outfile,data,opts,what="data",confirm_overwrite=False,verbose=False,exit_on_error=True): +def write_to_file(outfile,data,opts,what="data",confirm_overwrite=False,verbose=False,exit_on_error=True,silent=False): if 'outdir' in opts: outfile = make_full_path(opts['outdir'],outfile) @@ -333,13 +333,13 @@ def write_to_file(outfile,data,opts,what="data",confirm_overwrite=False,verbose= msg("Not overwriting file at user request") return False else: - msg("Overwriting file '%s'" % outfile) + if not silent: msg("Overwriting file '%s'" % outfile) f = open_file_or_exit(outfile,'wb') try: f.write(data) except: - msg("Failed to write %s to file '%s'" % (what,outfile)) + if not silent: msg("Failed to write %s to file '%s'" % (what,outfile)) sys.exit(2) f.close @@ -549,9 +549,10 @@ def get_lines_from_file(infile,what="",trim_comments=False): return remove_comments(lines) if trim_comments else lines -def get_data_from_file(infile,what="data",dash=False): +def get_data_from_file(infile,what="data",dash=False,silent=False): if dash and infile == "-": return sys.stdin.read() - qmsg("Getting %s from file '%s'" % (what,infile)) + if not silent: + qmsg("Getting %s from file '%s'" % (what,infile)) f = open_file_or_exit(infile,'rb') data = f.read() f.close() diff --git a/test/test.py b/test/test.py index 4f6a975a..7fb751ba 100755 --- a/test/test.py +++ b/test/test.py @@ -45,23 +45,55 @@ cmd_data = OrderedDict([ ['keyaddrgen', (1,'key-address file generation', [[["mmdat"],1]])], ['txsign_keyaddr',(1,'transaction signing with key-address file', [[["akeys.mmenc","raw"],1]])], - ['walletgen2',(2,'wallet generation (2), 128-bit seed (WIP)', [])], + ['walletgen2',(2,'wallet generation (2)', [])], +# ['walletgen2',(2,'wallet generation (2), 128-bit seed (WIP)', [])], ['addrgen2', (2,'address generation (2)', [[["mmdat"],2]])], ['txcreate2', (2,'transaction creation (2)', [[["addrs"],2]])], ['txsign2', (2,'transaction signing, two transactions',[[["mmdat","raw"],1],[["mmdat","raw"],2]])], - ['export_mnemonic2', (2,'seed export to mmwords format (2), 128-bit seed (WIP)',[[["mmdat"],2]])], + ['export_mnemonic2', (2,'seed export to mmwords format (2)',[[["mmdat"],2]])], +# ['export_mnemonic2', (2,'seed export to mmwords format (2), 128-bit seed (WIP)',[[["mmdat"],2]])], ['walletgen3',(3,'wallet generation (3)', [])], ['addrgen3', (3,'address generation (3)', [[["mmdat"],3]])], ['txcreate3', (3,'tx creation with inputs and outputs from two wallets', [[["addrs"],1],[["addrs"],3]])], ['txsign3', (3,'tx signing with inputs and outputs from two wallets',[[["mmdat"],1],[["mmdat","raw"],3]])], - ['walletgen4',(4,'wallet generation (4) (brainwallet, 192-bit seed (WIP))', [])], + ['walletgen4',(4,'wallet generation (4) (brainwallet)', [])], +# ['walletgen4',(4,'wallet generation (4) (brainwallet, 192-bit seed (WIP))', [])], ['addrgen4', (4,'address generation (4)', [[["mmdat"],4]])], ['txcreate4', (4,'tx creation with inputs and outputs from four seed sources, plus non-MMGen inputs and outputs', [[["addrs"],1],[["addrs"],2],[["addrs"],3],[["addrs"],4]])], ['txsign4', (4,'tx signing with inputs and outputs from incog file, mnemonic file, wallet and brainwallet, plus non-MMGen inputs and outputs', [[["mmincog"],1],[["mmwords"],2],[["mmdat"],3],[["mmbrain","raw"],4]])], ]) +tool_cmd_data = OrderedDict([ + ['strtob58', (10, '', [])], + ['b58tostr', (10, '', [[["strtob58.in","strtob58.out"],10]])], + ['hextob58', (10, '', [])], + ['b58tohex', (10, '', [[["hextob58.in","hextob58.out"],10]])], +# "b58randenc": [], +# "randhex": ['nbytes [int=32]'], +# "randwif": ['compressed [bool=False]'], +# "randpair": ['compressed [bool=False]'], +# "wif2hex": [' [str]', 'compressed [bool=False]'], +# "wif2addr": [' [str]', 'compressed [bool=False]'], +# "hex2wif": [' [str]', 'compressed [bool=False]'], +# "hexdump": [' [str]', 'cols [int=8]', 'line_nums [bool=True]'], +# "unhexdump": [' [str]'], +# "hex2mn": [' [str]','wordlist [str="electrum"]'], +# "mn2hex": [' [str]', 'wordlist [str="electrum"]'], +# "b32tohex": [' [str]'], +# "hextob32": [' [str]'], +# "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"]'], +# "id8": [' [str]'], +# "id6": [' [str]'], +# "str2id6": [' [str]'], +]) + + utils = { 'check_deps': 'check dependencies for specified command', 'clean': 'clean specified tmp dir(s) 1, 2, 3 or 4 (no arg = all dirs)', @@ -69,6 +101,16 @@ utils = { addrs_per_wallet = 8 cfgs = { + '10': { + 'name': "test the tool utility", + 'enc_passwd': "Ten Satoshis", + 'tmpdir': "test/tmp10", + 'dep_generators': { + 'strtob58.out': "strtob58", + 'strtob58.in': "strtob58", + }, + + }, '6': { 'name': "reference wallet check", 'bw_passwd': "abc", @@ -156,6 +198,7 @@ cfgs = { from binascii import hexlify def getrand(n): return int(hexlify(os.urandom(n)),16) +def getrandhex(n): return hexlify(os.urandom(n)) # total of two outputs must be < 10 BTC for k in cfgs.keys(): @@ -342,12 +385,17 @@ def verify_checksum_or_exit(checksum,chk): sys.exit(1) vmsg(green("Checksums match: %s") % (cyan(chk))) +def get_rand_printable_chars(num_chars,no_punc=False): + return [chr(ord(i)%94+33) for i in list(os.urandom(num_chars))] + + class MMGenExpect(object): def __init__(self,name,mmgen_cmd,cmd_args=[],env=env): if not 'system' in opts: mmgen_cmd = os.path.join(os.curdir,mmgen_cmd) desc = cmd_data[name][1] + if not desc: desc = name if verbose or exact_output: sys.stderr.write( green("Testing %s\nExecuting " % desc) + @@ -365,7 +413,7 @@ class MMGenExpect(object): my_expect(self.p,p,'c') def usr_rand(self,num_chars): - rand_chars = [chr(ord(i)%94+33) for i in list(os.urandom(num_chars))] + rand_chars = get_rand_printable_chars(num_chars) my_expect(self.p,'symbols left: ','x') try: vmsg_r("SEND ") @@ -429,7 +477,10 @@ class MMGenExpect(object): def readline(self): return self.p.readline() - def read(self,n): + def readlines(self): + return [l.rstrip()+"\n" for l in self.p.readlines()] + + def read(self,n=None): return self.p.read(n) @@ -513,7 +564,7 @@ def do_cmd(ts,cmd): global cfg cfg = cfgs[str(cmd_data[cmd][0])] - MMGenTestSuite.__dict__[cmd](*([ts,cmd] + al)) + ts.__class__.__dict__[cmd](*([ts,cmd] + al)) hincog_bytes = 1024*1024 @@ -591,7 +642,6 @@ def ok(): sys.stderr.write(green("OK\n")) else: msg(" OK") - class MMGenTestSuite(object): def __init__(self): @@ -1004,10 +1054,85 @@ class MMGenTestSuite(object): t.written_to_file("Signed transaction") ok() -# main() -ts = MMGenTestSuite() -start_time = int(time.time()) +def write_to_tmpfile(fn,data): + write_to_file(os.path.join(cfg['tmpdir'],fn),data,{},silent=True) + +def read_from_tmpfile(fn): + from mmgen.util import get_data_from_file + return get_data_from_file(os.path.join(cfg['tmpdir'],fn),silent=True) + +def read_from_file(fn): + from mmgen.util import get_data_from_file + return get_data_from_file(fn,silent=True) + +class MMGenToolTestSuite(object): + + def __init__(self): + global cmd_data,tool_cmd_data + cmd_data = tool_cmd_data + pass + + def clean(self,name): + cleandir(cfgs['10']['tmpdir']) + + def cmd(self,name,tool_args): + mk_tmpdir(cfg) + t = MMGenExpect(name,"mmgen-tool", ["-d",cfg['tmpdir']] + tool_args) + return t.read() + + def cmd_to_tmpfile(self,name,tool_args,tmpfile): + ret = self.cmd(name,tool_args) + if ret: + write_to_tmpfile(tmpfile,ret) + ok() + + def strtob58(self,name): + s = "".join(get_rand_printable_chars(15)) + write_to_tmpfile('strtob58.in',s) + self.cmd_to_tmpfile(name,["strtob58",s],'strtob58.out') + + def b58tostr(self,name,f1,f2): + idata = read_from_file(f1) + odata = read_from_file(f2)[:-2] + res = self.cmd(name,["b58tostr",odata])[:-2] + if res == idata: ok() + else: errmsg(red("Error")) + + def hextob58(self,name): + hexnum = getrandhex(32) + write_to_tmpfile('hextob58.in',hexnum) + self.cmd_to_tmpfile(name,["hextob58",hexnum],'hextob58.out') + + def b58tohex(self,name,f1,f2): + idata = read_from_file(f1) + odata = read_from_file(f2)[:-2] + res = self.cmd(name,["b58tohex",odata])[:-2] + if res == idata: ok() + else: errmsg(red("Error")) +# "b58randenc": [], +# "randhex": ['nbytes [int=32]'], +# "randwif": ['compressed [bool=False]'], +# "randpair": ['compressed [bool=False]'], +# "wif2hex": [' [str]', 'compressed [bool=False]'], +# "wif2addr": [' [str]', 'compressed [bool=False]'], +# "hex2wif": [' [str]', 'compressed [bool=False]'], +# "hexdump": [' [str]', 'cols [int=8]', 'line_nums [bool=True]'], +# "unhexdump": [' [str]'], +# "hex2mn": [' [str]','wordlist [str="electrum"]'], +# "mn2hex": [' [str]', 'wordlist [str="electrum"]'], +# "b32tohex": [' [str]'], +# "hextob32": [' [str]'], +# "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"]'], +# "id8": [' [str]'], +# "id6": [' [str]'], +# "str2id6": [' [str]'], + +# main() if pause: import termios,atexit fd = sys.stdin.fileno() @@ -1016,20 +1141,24 @@ if pause: termios.tcsetattr(fd, termios.TCSADRAIN, old) atexit.register(at_exit) +start_time = int(time.time()) + try: - if cmd_args: + if cmd_args and cmd_args[0] != "tool": arg1 = cmd_args[0] if arg1 in utils: MMGenTestSuite.__dict__[arg1](ts,arg1,cmd_args[1:]) sys.exit() elif arg1 in meta_cmds: + ts = MMGenTestSuite() if len(cmd_args) == 1: for cmd in meta_cmds[arg1][1]: check_needs_rerun(cmd,build=True,force_delete=True) else: msg("Only one meta command may be specified") sys.exit(1) - elif arg1 in cmd_data: + elif arg1 in cmd_data.keys() + tool_cmd_data.keys(): + ts = MMGenTestSuite() if arg1 in cmd_data else MMGenToolTestSuite() if len(cmd_args) == 1: check_needs_rerun(arg1,build=True) else: @@ -1039,6 +1168,14 @@ try: errmsg("%s: unrecognized command" % arg1) sys.exit(1) else: + if cmd_args: # tool + if len(cmd_args) != 1: + msg("Only one command may be specified") + sys.exit(1) + ts = MMGenToolTestSuite() + else: + ts = MMGenTestSuite() + ts.clean("clean") for cmd in cmd_data: do_cmd(ts,cmd)