From 54efc7679ee1d57bd34e50252940708375608eb0 Mon Sep 17 00:00:00 2001 From: philemon Date: Tue, 6 Jan 2015 20:10:29 +0300 Subject: [PATCH] Object-oriented reimplementation of addr data structures Reference wallet with checksums added to test/test.py --- mmgen/Opts.py | 24 ++- mmgen/addr.py | 323 ++++++++++++++++++++++++++++++++------- mmgen/config.py | 11 +- mmgen/crypto.py | 22 +-- mmgen/main_addrgen.py | 30 +--- mmgen/main_addrimport.py | 88 +++++------ mmgen/main_txcreate.py | 72 +++++---- mmgen/main_txsign.py | 27 ++-- mmgen/main_walletchk.py | 8 +- mmgen/main_walletgen.py | 3 +- mmgen/tool.py | 9 +- mmgen/tx.py | 201 ++++-------------------- mmgen/util.py | 15 +- test/test.py | 201 ++++++++++++++---------- 14 files changed, 590 insertions(+), 444 deletions(-) diff --git a/mmgen/Opts.py b/mmgen/Opts.py index 85d5636d..d7e67c32 100755 --- a/mmgen/Opts.py +++ b/mmgen/Opts.py @@ -23,7 +23,7 @@ Opts.py: Option handling routines for the MMGen suite import sys import mmgen.config as g import mmgen.opt.Opts -from mmgen.util import msg,check_infile,check_outfile,check_outdir +from mmgen.util import msg,check_infile,check_outfile,check_outdir,msgrepr_exit,msgrepr def usage(hd): mmgen.opt.Opts.usage(hd) @@ -60,17 +60,28 @@ def parse_opts(argv,help_data): ('quiet','verbose') ): warn_incompatible_opts(opts,l) - # check_opts() doesn't touch opts[] + if 'usr_randchars' in opts: g.use_urandchars = True + + # check opts[] dictionary without modifying it if not check_opts(opts,long_opts): sys.exit(1) - # If unset, set these to default values in mmgen.config (g): + # If user opt is unset, set it to default value in mmgen.config (g): for v in g.dfl_vars: if v in opts: typeconvert_override_var(opts,v) else: opts[v] = g.__dict__[v] - # Opposite of above: if set, override the default values in mmgen.config (g): - for k in 'no_keyconv','verbose','quiet': - if k in opts: g.__dict__[k] = opts[k] + # Opposite of above: set the value in mmgen.config (g) from user opt: + for k in g.usr_set_vars: + if k in opts: + v = opts[k] + try: v = type(g.__dict__[k])(v) + except: + msg( + "Argument '%s' for option '--%s' cannot be converted to target type %s" % + (v,k.replace("_","-"),type(g.__dict__[k])) + ) + sys.exit(1) + g.__dict__[k] = v if g.debug: print "opts after typeconvert: %s" % opts @@ -170,7 +181,6 @@ def check_opts(opts,long_opts): if not opt_is_in_list(val,g.hash_presets.keys(),what): return False elif opt == 'usr_randchars': if not opt_is_int(val,what): return False - if val == '0': continue if not opt_compares(val,">=",g.min_urandchars,what): return False if not opt_compares(val,"<=",g.max_urandchars,what): return False else: diff --git a/mmgen/addr.py b/mmgen/addr.py index feda97d7..543185d1 100755 --- a/mmgen/addr.py +++ b/mmgen/addr.py @@ -26,7 +26,9 @@ from hashlib import new as hashlib_new from binascii import hexlify, unhexlify from mmgen.bitcoin import numtowif -from mmgen.util import msg,qmsg,qmsg_r +# from mmgen.util import msg,qmsg,qmsg_r,make_chksum_N,get_lines_from_file,get_data_from_file,get_extension +from mmgen.util import * +from mmgen.tx import is_mmgen_idx,is_mmgen_seed_id,is_btc_addr,is_wip_key,get_wif2addr_f import mmgen.config as g addrmsgs = { @@ -58,7 +60,10 @@ def test_for_keyconv(): return True -def generate_addrs(seed, addrnums, opts, seed_id=""): +def generate_addrs(seed, addrnums, opts): + + from util import make_chksum_8 + seed_id = make_chksum_8(seed) # Must do this before seed gets clobbered if 'a' in opts['gen_what']: if g.no_keyconv or test_for_keyconv() == False: @@ -69,12 +74,6 @@ def generate_addrs(seed, addrnums, opts, seed_id=""): from subprocess import check_output keyconv = "keyconv" - ai_attrs = ("num,sec,wif,addr") if 'ka' in opts['gen_what'] else ( - ("num,sec,wif") if 'k' in opts['gen_what'] else ("num,addr")) - - from collections import namedtuple - addrinfo = namedtuple("addrinfo",ai_attrs.split(",")) - addrnums = sorted(set(addrnums)) # don't trust the calling function t_addrs,num,pos,out = len(addrnums),0,0,[] @@ -84,6 +83,8 @@ def generate_addrs(seed, addrnums, opts, seed_id=""): 'a': ('address','es') }[opts['gen_what']] + from mmgen.addr import AddrInfoEntry,AddrInfo + while pos != t_addrs: seed = sha512(seed).digest() num += 1 # round @@ -95,71 +96,281 @@ def generate_addrs(seed, addrnums, opts, seed_id=""): qmsg_r("\rGenerating %s #%s (%s of %s)" % (w[0],num,pos,t_addrs)) + e = AddrInfoEntry() + e.idx = num + # Secret key is double sha256 of seed hash round /num/ sec = sha256(sha256(seed).digest()).hexdigest() wif = numtowif(int(sec,16)) if 'a' in opts['gen_what']: if keyconv: - addr = check_output([keyconv, wif]).split()[1] + e.addr = check_output([keyconv, wif]).split()[1] else: - addr = privnum2addr(int(sec,16)) + e.addr = privnum2addr(int(sec,16)) - out.append(addrinfo(*eval(ai_attrs))) + if 'k' in opts['gen_what']: e.wif = wif + if 'b16' in opts: e.sec = sec + + out.append(e) m = w[0] if t_addrs == 1 else w[0]+w[1] - if seed_id: - qmsg("\r%s: %s %s generated%s" % (seed_id,t_addrs,m," "*15)) + qmsg("\r%s: %s %s generated%s" % (seed_id,t_addrs,m," "*15)) + a = AddrInfo(has_keys='k' in opts['gen_what']) + a.initialize(seed_id,out) + return a + +def _parse_addrfile_body(lines,has_keys=False,check=False): + + if has_keys and len(lines) % 2: + return "Key-address file has odd number of lines" + + ret = [] + while lines: + a = AddrInfoEntry() + l = lines.pop(0) + d = l.split(None,2) + + if not is_mmgen_idx(d[0]): + return "'%s': invalid address num. in line: '%s'" % (d[0],l) + if not is_btc_addr(d[1]): + return "'%s': invalid Bitcoin address" % d[1] + + if len(d) == 3: check_addr_label(d[2]) + else: d.append("") + + a.idx,a.addr,a.comment = int(d[0]),unicode(d[1]),unicode(d[2]) + + if has_keys: + l = lines.pop(0) + d = l.split(None,2) + + if d[0] != "wif:": + return "Invalid key line in file: '%s'" % l + if not is_wip_key(d[1]): + return "'%s': invalid Bitcoin key" % d[1] + + a.wif = unicode(d[1]) + + ret.append(a) + + if has_keys and keypress_confirm("Check key-to-address validity?"): + wif2addr_f = get_wif2addr_f() + llen = len(ret) + for n,e in enumerate(ret): + msg_r("\rVerifying keys %s/%s" % (n+1,llen)) + if e.addr != wif2addr_f(e.wif): + return "Key doesn't match address!\n %s\n %s" % (e.wif,e.addr) + msg(" - done") + + return ret + + +def _parse_addrfile(fn,buf=[],has_keys=False,exit_on_error=True): + + if buf: lines = remove_comments(buf.split("\n")) + else: lines = get_lines_from_file(fn,"address data",trim_comments=True) + + try: + sid,obrace = lines[0].split() + except: + errmsg = "Invalid first line: '%s'" % lines[0] else: - qmsg("\rGenerated %s %s%s" % (t_addrs,m," "*15)) - - return out - - -def format_addr_data(addr_data, addr_data_chksum, seed_id, addr_idxs, opts): - - fs = " {:<%s} {}" % len(str(addr_data[-1].num)) - - if 'a' in opts['gen_what']: - out = [] if 'stdout' in opts else [addrmsgs['addrfile_header']+"\n"] - w = "Key-address" if 'k' in opts['gen_what'] else "Address" - out.append("# {} data checksum for {}[{}]: {}".format( - w, seed_id, fmt_addr_idxs(addr_idxs), addr_data_chksum)) - out.append("# Record this value to a secure location\n") - else: out = [] - - out.append("%s {" % seed_id.upper()) - - for d in addr_data: - if 'a' in opts['gen_what']: # First line with number - out.append(fs.format(d.num, d.addr)) + cbrace = lines[-1] + if obrace != '{': + errmsg = "'%s': invalid first line" % lines[0] + elif cbrace != '}': + errmsg = "'%s': invalid last line" % cbrace + elif not is_mmgen_seed_id(sid): + errmsg = "'%s': invalid Seed ID" % sid else: - out.append(fs.format(d.num, "wif: "+d.wif)) + ret = _parse_addrfile_body(lines[1:-1],has_keys) + if type(ret) == list: return sid,ret + else: errmsg = ret - if 'k' in opts['gen_what']: # Subsequent lines - if 'b16' in opts: - out.append(fs.format("", "hex: "+d.sec)) - if 'a' in opts['gen_what']: - out.append(fs.format("", "wif: "+d.wif)) - - out.append("}") - - return "\n".join(out) + "\n" + if exit_on_error: + msg(errmsg) + sys.exit(3) + else: + return False -def fmt_addr_idxs(addr_idxs): +def _parse_keyaddr_file(infile): + d = get_data_from_file(infile,"%s key-address file data" % g.proj_name) + enc_ext = get_extension(infile) == g.mmenc_ext + if enc_ext or not is_utf8(d): + m = "Decrypting" if enc_ext else "Attempting to decrypt" + msg("%s key-address file %s" % (m,infile)) + from crypto import mmgen_decrypt_retry + d = mmgen_decrypt_retry(d,"key-address file") + return _parse_addrfile("",buf=d,has_keys=True,exit_on_error=False) - addr_idxs = list(sorted(set(addr_idxs))) - prev = addr_idxs[0] - ret = prev, +class AddrInfoList(object): - for i in addr_idxs[1:]: - if i == prev + 1: - if i == addr_idxs[-1]: ret += "-", i + def __init__(self,addrinfo=None): + self.data = {} + + def seed_ids(self): + return self.data.keys() + + def addrinfo(self,sid): + # TODO: Validate sid + if sid in self.data: + return self.data[sid] + + def add(self,addrinfo): + if type(addrinfo) == AddrInfo: + self.data[addrinfo.seed_id] = addrinfo + return True else: - if prev != ret[-1]: ret += "-", prev - ret += ",", i - prev = i + msg("Error: object %s is not of type AddrInfo" % repr(addrinfo)) + sys.exit(1) - return "".join([str(i) for i in ret]) + def make_reverse_dict(self,btcaddrs): + d = {} + for k in self.data.keys(): + d.update(self.data[k].make_reverse_dict(btcaddrs)) + return d + +class AddrInfoEntry(object): + + def __init__(self): + pass + +class AddrInfo(object): + + def __init__(self,addrfile="",has_keys=False): + self.has_keys=has_keys + if addrfile: + f = _parse_keyaddr_file if has_keys else _parse_addrfile + sid,adata = f(addrfile) + self.initialize(sid,adata) + + def initialize(self,seed_id,addrdata): + if seed_id in self.__dict__: + msg("Seed ID already set for object %s" % self) + return False + self.seed_id = seed_id + self.addrdata = addrdata + self.num_addrs = len(addrdata) + self.make_addrdata_chksum() + self.fmt_addr_idxs() + w = "key" if self.has_keys else "addr" + qmsg("Computed checksum for %s data %s[%s]: %s" % + (w,self.seed_id,self.idxs_fmt,self.checksum)) + qmsg("Check this value against your records") + + def idxs(self): + return [e.idx for e in self.addrdata] + + def addrs(self): + return ["%s:%s"%(self.seed_id,e.idx) for e in self.addrdata] + + def addrpairs(self): + return [(e.idx,e.addr) for e in self.addrdata] + + def btcaddrs(self): + return [e.addr for e in self.addrdata] + + def comments(self): + return [e.comment for e in self.addrdata] + + def entry(self,idx): + for e in self.addrdata: + if idx == e.idx: return e + + def btcaddr(self,idx): + for e in self.addrdata: + if idx == e.idx: return e.addr + + def comment(self,idx): + for e in self.addrdata: + if idx == e.idx: return e.comment + + def set_comment(self,idx,comment): + for e in self.addrdata: + if idx == e.idx: e.comment = comment + + def make_reverse_dict(self,btcaddrs): + d = {} + for e in self.addrdata: + try: + i = btcaddrs.index(e.addr) + d[btcaddrs[i]] = ("%s:%s"%(self.seed_id,e.idx),e.comment) + except: pass + return d + + def make_addrdata_chksum(self): + nchars = 24 + lines = [" ".join([str(e.idx),e.addr]+([e.wif] if self.has_keys else [])) + for e in self.addrdata] + self.checksum = make_chksum_N(" ".join(lines), nchars, sep=True) + + def fmt_data(self): + + fs = " {:<%s} {}" % len(str(self.addrdata[-1].idx)) + + # Header + have_addrs,have_wifs,have_secs = True,True,True + + try: self.addrdata[0].addr + except: have_addrs = False + + try: self.addrdata[0].wif + except: have_wifs = False + + try: self.addrdata[0].sec + except: have_secs = False + + if not (have_addrs or have_wifs): + msg("No addresses or wifs in addr data!") + sys.exit(3) + + out = [] + if have_addrs: + from mmgen.addr import addrmsgs + out.append(addrmsgs['addrfile_header'] + "\n") + w = "Key-address" if have_wifs else "Address" + out.append("# {} data checksum for {}[{}]: {}".format( + w, self.seed_id, self.idxs_fmt, self.checksum)) + out.append("# Record this value to a secure location\n") + + out.append("%s {" % self.seed_id) + + for e in self.addrdata: + if have_addrs: # First line with idx + out.append(fs.format(e.idx, e.addr)) + else: + out.append(fs.format(e.idx, "wif: "+e.wif)) + + if have_wifs: # Subsequent lines + if have_secs: + out.append(fs.format("", "hex: "+e.sec)) + if have_addrs: + out.append(fs.format("", "wif: "+e.wif)) + + out.append("}") + + return "\n".join(out) + + def fmt_addr_idxs(self): + + try: int(self.addrdata[0].idx) + except: + self.idxs_fmt = "(no idxs)" + return + + addr_idxs = [e.idx for e in self.addrdata] + prev = addr_idxs[0] + ret = prev, + + for i in addr_idxs[1:]: + if i == prev + 1: + if i == addr_idxs[-1]: ret += "-", i + else: + if prev != ret[-1]: ret += "-", prev + ret += ",", i + prev = i + + self.idxs_fmt = "".join([str(i) for i in ret]) diff --git a/mmgen/config.py b/mmgen/config.py index d7e46c7d..ff3fa201 100755 --- a/mmgen/config.py +++ b/mmgen/config.py @@ -59,7 +59,11 @@ mmenc_ext = "mmenc" default_wl = "electrum" #default_wl = "tirosh" -dfl_vars = "seed_len","hash_preset","usr_randchars" +# Global value sets user opt +dfl_vars = "seed_len","hash_preset" + +# User opt sets global value +usr_set_vars = "no_keyconv","verbose","quiet","usr_randchars" seed_lens = 128,192,256 seed_len = 256 @@ -78,8 +82,11 @@ disable_hold_protect = os.getenv("MMGEN_DISABLE_HOLD_PROTECT") mins_per_block = 8.5 passwd_max_tries = 5 -usr_randchars,usr_randchars_dfl = -1,30 # see get_random() + +usr_randchars = 30 max_urandchars,min_urandchars = 80,10 +use_urandchars = False + salt_len = 16 aesctr_iv_len = 16 diff --git a/mmgen/crypto.py b/mmgen/crypto.py index c5cdf833..576bbed2 100755 --- a/mmgen/crypto.py +++ b/mmgen/crypto.py @@ -117,7 +117,7 @@ def encrypt_data(data, key, iv=1, what="data", verify=True): counter=Counter.new(g.aesctr_iv_len*8,initial_value=iv)) dec_data = c.decrypt(enc_data) - if dec_data == data: vmsg("done\n") + if dec_data == data: vmsg("done") else: msg("ERROR.\nDecrypted %s doesn't match original %s" % (what,what)) sys.exit(2) @@ -149,10 +149,12 @@ def scrypt_hash_passphrase(passwd, salt, hash_preset, buflen=32): return scrypt.hash(passwd, salt, 2**N, r, p, buflen=buflen) -def make_key(passwd, salt, hash_preset, what="encryption key", verbose=False): +def make_key(passwd,salt,hash_preset, + what="encryption key",from_what="passphrase",verbose=False): + if from_what: what += " from " if g.verbose or verbose: - msg_r("Generating %s from passphrase.\nPlease wait..." % what) + msg_r("Generating %s%s.\nPlease wait..." % (what,from_what)) key = scrypt_hash_passphrase(passwd, salt, hash_preset) if g.verbose or verbose: msg("done") @@ -200,15 +202,15 @@ def get_random_data_from_user(uchars): def get_random(length,opts): from Crypto import Random os_rand = Random.new().read(length) - if 'usr_randchars' in opts and opts['usr_randchars'] not in (0,-1): - kwhat = "a key from OS random data plus " + if g.use_urandchars: + from_what = "OS random data" if not g.user_entropy: - g.user_entropy = sha256( - get_random_data_from_user(opts['usr_randchars'])).digest() - kwhat += "user entropy" + g.user_entropy = \ + sha256(get_random_data_from_user(g.usr_randchars)).digest() + from_what += " plus user-supplied entropy" else: - kwhat += "saved user entropy" - key = make_key(g.user_entropy, "", '2', what=kwhat, verbose=True) + from_what += " plus saved user-supplied entropy" + key = make_key(g.user_entropy, "", '2', from_what=from_what, verbose=True) return encrypt_data(os_rand,key,what="random data",verify=False) else: return os_rand diff --git a/mmgen/main_addrgen.py b/mmgen/main_addrgen.py index 4c3a30a1..58903269 100755 --- a/mmgen/main_addrgen.py +++ b/mmgen/main_addrgen.py @@ -29,7 +29,6 @@ from mmgen.license import * from mmgen.util import * from mmgen.crypto import * from mmgen.addr import * -from mmgen.tx import make_addr_data_chksum what = "keys" if sys.argv[0].split("-")[-1] == "keygen" else "addresses" @@ -148,40 +147,27 @@ if what == "keys" and not g.quiet: # Generate data: seed = get_seed_retry(infile,opts) -seed_id = make_chksum_8(seed) opts['gen_what'] = "a" if what == "addresses" else ( "k" if 'no_addresses' in opts else "ka") -addr_data = generate_addrs(seed, addr_idxs, opts) +ainfo = generate_addrs(seed, addr_idxs, opts) -if 'a' in opts['gen_what']: - if 'k' in opts['gen_what']: - def l(a): return ( a.num, (a.addr,"",a.wif) ) - keys = True - else: - def l(a): return ( a.num, (a.addr,) ) - keys = False - addr_data_chksum = make_addr_data_chksum([l(a) for a in addr_data],keys) -else: - addr_data_chksum = "" +addrdata_str = ainfo.fmt_data() +outfile_base = "{}[{}]".format(make_chksum_8(seed), ainfo.idxs_fmt) -addr_data_str = format_addr_data( - addr_data, addr_data_chksum, seed_id, addr_idxs, opts) - -outfile_base = "{}[{}]".format(seed_id, fmt_addr_idxs(addr_idxs)) if 'a' in opts['gen_what']: w = "key-address" if 'k' in opts['gen_what'] else "address" - qmsg("Checksum for %s data %s: %s" % (w,outfile_base,addr_data_chksum)) + qmsg("Checksum for %s data %s: %s" % (w,outfile_base,ainfo.checksum)) if 'save_checksum' in opts: write_to_file(outfile_base+"."+g.addrfile_chksum_ext, - addr_data_chksum+"\n",opts,"%s data checksum" % w,True,True,False) + ainfo.checksum+"\n",opts,"%s data checksum" % w,True,True,False) else: qmsg("This checksum will be used to verify the %s file in the future."%w) qmsg("Record it to a safe location.") if 'k' in opts['gen_what'] and keypress_confirm("Encrypt key list?"): - addr_data_str = mmgen_encrypt(addr_data_str,"new key list","",opts) + addrdata_str = mmgen_encrypt(addrdata_str,"new key list","",opts) enc_ext = "." + g.mmenc_ext else: enc_ext = "" @@ -190,11 +176,11 @@ if 'stdout' in opts or not sys.stdout.isatty(): if enc_ext and sys.stdout.isatty(): msg("Cannot write encrypted data to screen. Exiting") sys.exit(2) - write_to_stdout(addr_data_str,what, + write_to_stdout(addrdata_str,what, (what=="keys"and not g.quiet and sys.stdout.isatty())) else: outfile = "%s.%s%s" % (outfile_base, ( g.keyaddrfile_ext if "ka" in opts['gen_what'] else ( g.keyfile_ext if "k" in opts['gen_what'] else g.addrfile_ext)), enc_ext) - write_to_file(outfile,addr_data_str,opts,what,not g.quiet,True) + write_to_file(outfile,addrdata_str,opts,what,not g.quiet,True) diff --git a/mmgen/main_addrimport.py b/mmgen/main_addrimport.py index 34bb6c76..42ee0cf6 100755 --- a/mmgen/main_addrimport.py +++ b/mmgen/main_addrimport.py @@ -20,11 +20,12 @@ mmgen-addrimport: Import addresses into a MMGen bitcoind tracking wallet """ -import sys +import sys, time from mmgen.Opts import * from mmgen.license import * from mmgen.util import * -from mmgen.tx import connect_to_bitcoind,parse_addrfile,parse_keyaddr_file +from mmgen.tx import connect_to_bitcoind +from mmgen.addr import AddrInfo,AddrInfoEntry help_data = { 'prog_name': g.prog_name, @@ -38,6 +39,7 @@ help_data = { -q, --quiet Suppress warnings -r, --rescan Rescan the blockchain. Required if address to import is on the blockchain and has a balance. Rescanning is slow. +-t, --test Simulate operation; don't actually import addresses """, 'notes': """\n This command can also be used to update the comment fields of addresses already @@ -51,35 +53,38 @@ if len(cmd_args) == 1: infile = cmd_args[0] check_infile(infile) if 'addrlist' in opts: - lines = get_lines_from_file(infile,"non-{} addresses".format(g.proj_name), - trim_comments=True) - addr_list = [(None,l) for l in lines] - seed_id = "" + lines = get_lines_from_file( + infile,"non-{} addresses".format(g.proj_name),trim_comments=True) + ai,adata = AddrInfo(),[] + for btcaddr in lines: + a = AddrInfoEntry() + a.idx,a.addr,a.comment = None,btcaddr,None + adata.append(a) + ai.initialize(None,adata) else: - addr_data = {} - pf = parse_keyaddr_file if 'keyaddr_file' in opts else parse_addrfile - pf(infile,addr_data) - seed_id = addr_data.keys()[0] - e = addr_data[seed_id] - def s_addrdata(a): return ("{:>0%s}"%g.mmgen_idx_max_digits).format(a) - addr_list = [(k,e[k][0],e[k][1]) for k in sorted(e.keys(),key=s_addrdata)] + ai = AddrInfo(infile,has_keys='keyaddr_file' in opts) else: - msg_r("You must specify an mmgen address list (or a list of ") - msg("non-%s addresses with\nthe '--addrlist' option)" % g.proj_name) + msg(""" +"You must specify an mmgen address file (or a list of non-%s addresses +with the '--addrlist' option) +""".strip() % g.proj_name) sys.exit(1) from mmgen.bitcoin import verify_addr qmsg_r("Validating addresses...") -for n,i in enumerate(addr_list,1): - if not verify_addr(i[1],verbose=True): - msg("%s: invalid address" % i) +for e in ai.addrdata: + if not verify_addr(e.addr,verbose=True): + msg("%s: invalid address" % e.addr) sys.exit(2) -qmsg("OK. %s addresses%s" % (n," from seed ID "+seed_id if seed_id else "")) + +m = (" from seed ID %s" % ai.seed_id) if ai.seed_id else "" +qmsg("OK. %s addresses%s" % (ai.num_addrs,m)) import mmgen.config as g g.http_timeout = 3600 -c = connect_to_bitcoind() +if not 'test' in opts: + c = connect_to_bitcoind() m = """ WARNING: You've chosen the '--rescan' option. Rescanning the blockchain is @@ -101,31 +106,30 @@ err_flag = False def import_address(addr,label,rescan): try: - c.importaddress(addr,label,rescan) + if not 'test' in opts: + c.importaddress(addr,label,rescan) except: global err_flag err_flag = True - -w1 = len(str(len(addr_list))) * 2 + 2 -w2 = "" if 'addrlist' in opts else \ - len(str(max([i[0] for i in addr_list if i[0]]))) + 12 \ +w_n_of_m = len(str(ai.num_addrs)) * 2 + 2 +w_mmid = "" if 'addrlist' in opts else len(str(max(ai.idxs()))) + 12 if "rescan" in opts: import threading - import time - msg_fmt = "\r%s %-" + str(w1) + "s %-34s %-" + str(w2) + "s" + msg_fmt = "\r%s %-{}s %-34s %s".format(w_n_of_m) else: - msg_fmt = "\r%-" + str(w1) + "s %-34s %-" + str(w2) + "s" + msg_fmt = "\r%-{}s %-34s %s".format(w_n_of_m, w_mmid) msg("Importing addresses") -for n,i in enumerate(addr_list): - if i[0]: - label = "%s:%s%s" % (seed_id,i[0], (" "+i[2] if i[2] else "")) - else: label = "non-mmgen" +for n,e in enumerate(ai.addrdata): + if e.idx: + label = "%s:%s" % (ai.seed_id,e.idx) + if e.comment: label += " " + e.comment + else: label = "non-%s" % g.proj_name if "rescan" in opts: - t = threading.Thread(target=import_address, args=(i[1],label,True)) + t = threading.Thread(target=import_address, args=(e.addr,label,True)) t.daemon = True t.start() @@ -134,20 +138,18 @@ for n,i in enumerate(addr_list): while True: if t.is_alive(): elapsed = int(time.time() - start) - msg_r(msg_fmt % ( - secs_to_hms(elapsed), - ("%s/%s:" % (n+1,len(addr_list))), - i[1], "(" + label + ")" - ) - ) + count = "%s/%s:" % (n+1, ai.num_addrs) + msg_r(msg_fmt % (secs_to_hms(elapsed),count,e.addr,"(%s)"%label)) time.sleep(1) else: if err_flag: msg("\nImport failed"); sys.exit(2) msg("\nOK") break else: - import_address(i[1],label,rescan=False) - msg_r(msg_fmt % (("%s/%s:" % (n+1,len(addr_list))), - i[1], "(" + label + ")")) - if err_flag: msg("\nImport failed"); sys.exit(2) + import_address(e.addr,label,rescan=False) + count = "%s/%s:" % (n+1, ai.num_addrs) + msg_r(msg_fmt % (count, e.addr, "(%s)"%label)) + if err_flag: + msg("\nImport failed") + sys.exit(2) msg(" - OK") diff --git a/mmgen/main_txcreate.py b/mmgen/main_txcreate.py index 0b682fea..b9a9e7dc 100755 --- a/mmgen/main_txcreate.py +++ b/mmgen/main_txcreate.py @@ -115,7 +115,7 @@ def format_unspent_outputs_for_printing(out,sort_info,total): if i.skip == "txid" and "grouped" in sort_info else str(i.txid) s = pfs % (str(n+1)+")", tx+","+str(i.vout),addr, - i.mmid,i.amt,i.confirmations,i.days,i.label) + i.mmid,i.amt,i.confirmations,i.days,i.comment) pout.append(s.rstrip()) return \ @@ -131,15 +131,16 @@ def sort_and_view(unspent,opts): def s_addr(i): return i.address def s_age(i): return i.confirmations def s_mmgen(i): - m = parse_mmgen_label(i.account)[0] - if m: return "{}:{:>0{w}}".format(w=g.mmgen_idx_max_digits, *m.split(":")) - else: return "G" + i.account + if i.mmid: + return "{}:{:>0{w}}".format( + *i.mmid.split(":"), w=g.mmgen_idx_max_digits) + else: return "G" + i.comment sort,group,show_days,show_mmaddr,reverse = "age",False,False,True,True unspent.sort(key=s_age,reverse=reverse) # Reverse age sort by default total = trim_exponent(sum([i.amount for i in unspent])) - max_acct_len = max([len(i.account) for i in unspent]) + max_acct_len = max([len(i.mmid+" "+i.comment) for i in unspent]) hdr_fmt = "UNSPENT OUTPUTS (sort order: %s) Total BTC: %s" options_msg = """ @@ -149,6 +150,7 @@ Display options: show [D]ays, [g]roup, show [m]mgen addr, r[e]draw screen prompt = \ "('q' = quit sorting, 'p' = print to file, 'v' = pager view, 'w' = wide view): " + mmid_w = max(len(i.mmid) for i in unspent) from copy import deepcopy from mmgen.term import get_terminal_size @@ -184,7 +186,6 @@ Display options: show [D]ays, [g]roup, show [m]mgen addr, r[e]draw screen i.amt = " "*lfill + amt i.days = int(i.confirmations * g.mins_per_block / (60*24)) i.age = i.days if show_days else i.confirmations - i.mmid,i.label = parse_mmgen_label(i.account) if i.skip == "addr": i.addr = "|" + "." * 33 @@ -193,8 +194,10 @@ Display options: show [D]ays, [g]roup, show [m]mgen addr, r[e]draw screen dots = ".." if btaddr_w < len(i.address) else "" i.addr = "%s%s %s" % ( i.address[:btaddr_w-len(dots)], - dots, - i.account[:acct_w]) + dots, ( + ("{:<{w}} ".format(i.mmid,w=mmid_w) if i.mmid else "") + + i.comment)[:acct_w] + ) else: i.addr = i.address @@ -295,7 +298,7 @@ def get_acct_data_from_wallet(c,acct_data): def mmaddr2btcaddr_unspent(unspent,mmaddr): vmsg_r("Searching for {g.proj_name} address {m} in wallet...".format(g=g,m=mmaddr)) - m = [u for u in unspent if u.account.split()[0] == mmaddr] + m = [u for u in unspent if u.mmid == mmaddr] if len(m) == 0: vmsg("not found") return "","" @@ -303,18 +306,19 @@ def mmaddr2btcaddr_unspent(unspent,mmaddr): msg(wmsg['too_many_acct_addresses'] % acct); sys.exit(2) else: vmsg("success (%s)" % m[0].address) - return m[0].address, split2(m[0].account)[1] + return m[0].address, m[0].comment sys.exit() -def mmaddr2btcaddr(c,mmaddr,acct_data,addr_data,b2m_map): +def mmaddr2btcaddr(c,mmaddr,acct_data,ail): # assume mmaddr has already been checked if not acct_data: get_acct_data_from_wallet(c,acct_data) - btcaddr,comment = mmaddr2btcaddr_addrdata(mmaddr,acct_data,"wallet") + btcaddr = mmaddr2btcaddr_addrdata(mmaddr,acct_data,"wallet")[0] # btcaddr,comment = mmaddr2btcaddr_unspent(us,mmaddr) if not btcaddr: - if addr_data: - btcaddr,comment = mmaddr2btcaddr_addrdata(mmaddr,addr_data,"addr file") + if ail: + sid,idx = mmaddr.split(":") + btcaddr = ail.addrinfo(sid).btcaddr(int(idx)) if btcaddr: msg(wmsg['addr_in_addrfile_only'].format(mmgenaddr=mmaddr)) if not keypress_confirm("Continue anyway?"): @@ -326,7 +330,6 @@ def mmaddr2btcaddr(c,mmaddr,acct_data,addr_data,b2m_map): msg(wmsg['addr_not_found_no_addrfile'].format(mmgenaddr=mmaddr)) sys.exit(2) - b2m_map[btcaddr] = mmaddr,comment return btcaddr @@ -342,14 +345,16 @@ c = connect_to_bitcoind() if not 'info' in opts: do_license_msg(immed=True) - tx_out,addr_data,b2m_map,acct_data,change_addr = {},{},{},{},"" + tx_out,acct_data,change_addr = {},{},"" + from mmgen.addr import AddrInfo,AddrInfoList + ail = AddrInfoList() addrfiles = [a for a in cmd_args if get_extension(a) == g.addrfile_ext] cmd_args = set(cmd_args) - set(addrfiles) for a in addrfiles: check_infile(a) - parse_addrfile(a,addr_data) + ail.add(AddrInfo(a)) for a in cmd_args: if "," in a: @@ -357,13 +362,14 @@ if not 'info' in opts: if is_btc_addr(a1): btcaddr = a1 elif is_mmgen_addr(a1): - btcaddr = mmaddr2btcaddr(c,a1,acct_data,addr_data,b2m_map) + btcaddr = mmaddr2btcaddr(c,a1,acct_data,ail) else: msg("%s: unrecognized subargument in argument '%s'" % (a1,a)) sys.exit(2) - if is_btc_amt(a2): - tx_out[btcaddr] = normalize_btc_amt(a2) + ret = normalize_btc_amt(a2) + if ret: + tx_out[btcaddr] = ret else: msg("%s: invalid amount in argument '%s'" % (a2,a)) sys.exit(2) @@ -373,7 +379,7 @@ if not 'info' in opts: (change_addr, a)) sys.exit(2) change_addr = a if is_btc_addr(a) else \ - mmaddr2btcaddr(c,a,acct_data,addr_data,b2m_map) + mmaddr2btcaddr(c,a,acct_data,ail) tx_out[change_addr] = 0 else: msg("%s: unrecognized argument" % a) @@ -398,7 +404,9 @@ else: # write_to_file("bogus_unspent.json", repr(us), opts); sys.exit() if not us: msg(wmsg['no_spendable_outputs']); sys.exit(2) - +for o in us: + o.mmid,o.comment = parse_mmgen_label(o.account) + del o.account unspent = sort_and_view(us,opts) total = trim_exponent(sum([i.amount for i in unspent])) @@ -418,7 +426,7 @@ while True: ) sel_unspent = [unspent[i-1] for i in sel_nums] - mmaddrs = set([parse_mmgen_label(i.account)[0] for i in sel_unspent]) + mmaddrs = set([i.mmid for i in sel_unspent]) mmaddrs.discard("") if mmaddrs and len(mmaddrs) < len(sel_unspent): @@ -468,15 +476,23 @@ qmsg("Transaction successfully created") amt = send_amt or change tx_id = make_chksum_6(unhexlify(tx_hex)).upper() metadata = tx_id, amt, make_timestamp() +sel_unspent = [i.__dict__ for i in sel_unspent] + +def make_b2m_map(inputs_data,tx_out): + m = [(d['address'],(d['mmid'],d['comment'])) for d in inputs_data if d['mmid']] + d = ail.make_reverse_dict(tx_out.keys()) + d.update(m) + return d + +b2m_map = make_b2m_map(sel_unspent,tx_out) prompt_and_view_tx_data(c,"View decoded transaction?", - [i.__dict__ for i in sel_unspent],tx_hex,b2m_map,comment,metadata) + sel_unspent,tx_hex,b2m_map,comment,metadata) -prompt = "Save transaction?" -if keypress_confirm(prompt,default_yes=True): +if keypress_confirm("Save transaction?",default_yes=False): outfile = "tx_%s[%s].%s" % (tx_id,amt,g.rawtx_ext) - data = make_tx_data("{} {} {}".format(*metadata), tx_hex, - [i.__dict__ for i in sel_unspent], b2m_map, comment) + data = make_tx_data("{} {} {}".format(*metadata), + tx_hex,sel_unspent,b2m_map,comment) write_to_file(outfile,data,opts,"transaction",False,True) else: msg("Transaction not saved") diff --git a/mmgen/main_txsign.py b/mmgen/main_txsign.py index e908d9da..818345d6 100755 --- a/mmgen/main_txsign.py +++ b/mmgen/main_txsign.py @@ -139,9 +139,8 @@ def get_keys_for_mmgen_addrs(mmgen_addrs,infiles,saved_seeds,opts): # Returns only if seed is found seed = get_seed_for_seed_id(seed_id,infiles,saved_seeds,opts) addr_nums = [int(i[9:]) for i in mmgen_addrs if i[:8] == seed_id] -# num sec wif addr - d += [("{}:{}".format(seed_id,r.num),r.addr,r.wif) - for r in generate_addrs(seed,addr_nums,{'gen_what':"ka"},seed_id)] + ai = generate_addrs(seed,addr_nums,{'gen_what':"ka"}) + d += [("{}:{}".format(seed_id,e.idx),e.addr,e.wif) for e in ai.addrdata] return d @@ -216,15 +215,13 @@ for the following non-{} address{}:\n {}""".format( def parse_mmgen_keyaddr_file(opts): - adata = {} - parse_keyaddr_file(opts['mmgen_keys_from_file'],adata) - for sid in adata.keys(): # one seed id, one loop - idxs = adata[sid] - count = len(idxs.keys()) - vmsg("Found %s wif key%s for seed ID %s" % (count,suf(count,"k"),sid)) - # idx: (0=addr, 1=comment 2=wif) -> mmaddr: (0=addr, 1=wif) - return dict([("{}:{}".format(sid,k),(idxs[k][0],idxs[k][2])) - for k in idxs.keys()]) + from mmgen.addr import AddrInfo + ai = AddrInfo(opts['mmgen_keys_from_file'],has_keys=True) + vmsg("Found %s wif key%s for seed ID %s" % + (ai.num_addrs, suf(ai.num_addrs,"k"), ai.seed_id)) + # idx: (0=addr, 1=comment 2=wif) -> mmaddr: (0=addr, 1=wif) + return dict( + [("%s:%s"%(ai.seed_id,e.idx), (e.addr,e.wif)) for e in ai.addrdata]) def parse_keylist(opts,from_file): @@ -335,8 +332,7 @@ for tx_num,tx_file in enumerate(tx_files,1): inputs_data,tx_hex,b2m_map,comment,metadata) # Start - other_addrs = list(set([i['address'] for i in inputs_data - if not parse_mmgen_label(i['account'])[0]])) + other_addrs = list(set([i['address'] for i in inputs_data if not i['mmid']])) keys = get_keys_from_keylist(from_file['kldata'],other_addrs) @@ -344,8 +340,7 @@ for tx_num,tx_file in enumerate(tx_files,1): missing_keys_errormsg(other_addrs) sys.exit(2) - imap = dict([(i['account'].split()[0],i['address']) for i in inputs_data - if parse_mmgen_label(i['account'])[0]]) + imap = dict([(i['mmid'],i['address']) for i in inputs_data if i['mmid']]) omap = dict([(j[0],i) for i,j in b2m_map.items()]) sids = set([i[:8] for i in imap.keys()]) diff --git a/mmgen/main_walletchk.py b/mmgen/main_walletchk.py index e6ffafed..36eaf048 100755 --- a/mmgen/main_walletchk.py +++ b/mmgen/main_walletchk.py @@ -87,8 +87,7 @@ def wallet_to_incog_data(infile,opts): # IV is used BOTH to initialize counter and to salt password! key = make_key(passwd, iv, preset, "incog wrapper key") - m = "incog data" - wrap_enc = encrypt_data(salt + enc_seed, key, int(hexlify(iv),16), m) + wrap_enc = encrypt_data(salt+enc_seed,key,int(hexlify(iv),16),"incog data") return iv+wrap_enc,seed_id,key_id,iv_id,preset @@ -118,15 +117,16 @@ if len(cmd_args) != 1: usage(help_data) check_infile(cmd_args[0]) -if set(['outdir','export_incog_hidden']).issubset(set(opts.keys())): +if set(['outdir','export_incog_hidden']) <= set(opts.keys()): msg("Warning: '--outdir' option is ignored when exporting hidden incog data") +g.use_urandchars = True + if 'export_mnemonic' in opts: qmsg("Exporting mnemonic data to file by user request") elif 'export_seed' in opts: qmsg("Exporting seed data to file by user request") elif 'export_incog' in opts: - if opts['usr_randchars'] == -1: opts['usr_randchars'] = g.usr_randchars_dfl qmsg("Exporting wallet to incognito format by user request") incog_enc,seed_id,key_id,iv_id,preset = \ wallet_to_incog_data(cmd_args[0],opts) diff --git a/mmgen/main_walletgen.py b/mmgen/main_walletgen.py index f0b83c75..7268a4ee 100755 --- a/mmgen/main_walletgen.py +++ b/mmgen/main_walletgen.py @@ -124,7 +124,6 @@ future, you must continue using these same parameters opts,cmd_args = parse_opts(sys.argv,help_data) if 'show_hash_presets' in opts: show_hash_presets() -if opts['usr_randchars'] == -1: opts['usr_randchars'] = g.usr_randchars_dfl if g.debug: show_opts_and_cmd_args(opts,cmd_args) @@ -143,6 +142,8 @@ elif len(cmd_args) == 0: infile = "" else: usage(help_data) +g.use_urandchars = True + # Begin execution do_license_msg() diff --git a/mmgen/tool.py b/mmgen/tool.py index 073ab5cc..266142d3 100755 --- a/mmgen/tool.py +++ b/mmgen/tool.py @@ -431,8 +431,13 @@ def txview(infile,pager=False,terse=False): metadata,tx_hex,inputs_data,b2m_map,comment = parse_tx_file(tx_data,infile) view_tx_data(c,inputs_data,tx_hex,b2m_map,comment,metadata,pager,pause=False,terse=terse) -def addrfile_chksum(infile): parse_addrfile(infile,{}) -def keyaddrfile_chksum(infile): parse_keyaddr_file(infile,{}) +def addrfile_chksum(infile): + from mmgen.addr import AddrInfo + AddrInfo(infile) + +def keyaddrfile_chksum(infile): + from mmgen.addr import AddrInfo + AddrInfo(infile,has_keys=True) def hexreverse(hex_str): print ba.hexlify(decode_pretty_hexdump(hex_str)[::-1]) diff --git a/mmgen/tx.py b/mmgen/tx.py index 807daf40..f71ea7a7 100755 --- a/mmgen/tx.py +++ b/mmgen/tx.py @@ -23,6 +23,7 @@ tx.py: Bitcoin transaction routines import sys, os from binascii import unhexlify from decimal import Decimal +from collections import OrderedDict import mmgen.config as g from mmgen.util import * @@ -34,7 +35,7 @@ def trim_exponent(n): d = Decimal(n) return d.quantize(Decimal(1)) if d == d.to_integral() else d.normalize() -def is_btc_amt(amt): +def normalize_btc_amt(amt): # amt must be a string! from decimal import Decimal @@ -57,12 +58,6 @@ def is_btc_amt(amt): return trim_exponent(ret) -def normalize_btc_amt(amt): - # amt must be a string! - ret = is_btc_amt(amt) - if ret: return ret - else: sys.exit(3) - def parse_mmgen_label(s,check_label_len=False): l = split2(s) if not is_mmgen_addr(l[0]): return "",s @@ -74,9 +69,9 @@ def is_mmgen_seed_id(s): return re.match(r"^[0123456789ABCDEF]{8}$",s) is not None def is_mmgen_idx(s): - import re - m = g.mmgen_idx_max_digits - return re.match(r"^[0123456789]{1,"+str(m)+r"}$",s) is not None + try: int(s) + except: return False + return len(s) <= g.mmgen_idx_max_digits def is_mmgen_addr(s): seed_id,idx = split2(s,":") @@ -88,11 +83,9 @@ def is_btc_addr(s): def is_b58_str(s): from mmgen.bitcoin import b58a - for ch in s: - if ch not in b58a: return False - return True + return set(list(s)) <= set(b58a) -def is_btc_key(s): +def is_wip_key(s): if s == "": return False compressed = not s[0] == '5' from mmgen.bitcoin import wiftohex @@ -132,14 +125,14 @@ Only ASCII printable characters are permitted. """.strip() % (ch,label)) sys.exit(3) -def prompt_and_view_tx_data(c,prompt,inputs_data,tx_hex,b2m_map,comment,metadata): +def prompt_and_view_tx_data(c,prompt,inputs_data,tx_hex,adata,comment,metadata): prompt += " (y)es, (N)o, pager (v)iew, (t)erse view" reply = prompt_and_get_char(prompt,"YyNnVvTt",enter_ok=True) if reply and reply in "YyVvTt": - view_tx_data(c,inputs_data,tx_hex,b2m_map,comment,metadata, + view_tx_data(c,inputs_data,tx_hex,adata,comment,metadata, pager=reply in "Vv",terse=reply in "Tt") @@ -155,26 +148,24 @@ def view_tx_data(c,inputs_data,tx_hex,b2m_map,comment,metadata,pager=False,pause if comment: out += "Comment: %s\n%s" % (comment,enl) out += "Inputs:\n" + enl + nonmm_str = "non-%s address" % g.proj_name + total_in = 0 for n,i in enumerate(td['vin']): for j in inputs_data: if j['txid'] == i['txid'] and j['vout'] == i['vout']: days = int(j['confirmations'] * g.mins_per_block / (60*24)) total_in += j['amount'] - mmid,label,mmid_str = "","","" - if 'account' in j: - mmid,label = parse_mmgen_label(j['account']) - if not mmid: mmid = "non-%s address" % g.proj_name - mmid_str = " ({:>{l}})".format(mmid,l=34-len(j['address'])) - + if not j['mmid']: j['mmid'] = nonmm_str + mmid_fmt = " ({:>{l}})".format(j['mmid'],l=34-len(j['address'])) if terse: - out += " %s: %-54s %s BTC" % (n+1,j['address'] + mmid_str, + out += " %s: %-54s %s BTC" % (n+1,j['address'] + mmid_fmt, trim_exponent(j['amount'])) else: for d in ( (n+1, "tx,vout:", "%s,%s" % (i['txid'], i['vout'])), - ("", "address:", j['address'] + mmid_str), - ("", "label:", label), + ("", "address:", j['address'] + mmid_fmt), + ("", "comment:", j['comment']), ("", "amount:", "%s BTC" % trim_exponent(j['amount'])), ("", "confirmations:", "%s (around %s days)" % (j['confirmations'], days)) ): @@ -185,18 +176,17 @@ def view_tx_data(c,inputs_data,tx_hex,b2m_map,comment,metadata,pager=False,pause total_out = 0 out += "Outputs:\n" + enl for n,i in enumerate(td['vout']): - addr = i['scriptPubKey']['addresses'][0] - mmid,label = b2m_map[addr] if addr in b2m_map else ("","") - if not mmid: mmid = "non-%s address" % g.proj_name - mmid_str = " ({:>{l}})".format(mmid,l=34-len(j['address'])) + btcaddr = i['scriptPubKey']['addresses'][0] + mmid,comment=b2m_map[btcaddr] if btcaddr in b2m_map else (nonmm_str,"") + mmid_fmt = " ({:>{l}})".format(mmid,l=34-len(j['address'])) total_out += i['value'] if terse: - out += " %s: %-54s %s BTC" % (n+1,addr + mmid_str, + out += " %s: %-54s %s BTC" % (n+1,btcaddr + mmid_fmt, trim_exponent(i['value'])) else: for d in ( - (n+1, "address:", addr + mmid_str), - ("", "label:", label), + (n+1, "address:", btcaddr + mmid_fmt), + ("", "comment:", comment), ("", "amount:", trim_exponent(i['value'])) ): if d[2]: out += ("%3s %-8s %s\n" % d) @@ -213,7 +203,7 @@ def view_tx_data(c,inputs_data,tx_hex,b2m_map,comment,metadata,pager=False,pause o = out.encode("utf8") if pager: do_pager(o) else: - print o + sys.stdout.write(o) if pause: get_char("Press any key to continue: ") msg("") @@ -262,142 +252,19 @@ def parse_tx_file(tx_data,infile): return metadata.split(),tx_hex,inputs_data,outputs_data,comment +def wiftoaddr_keyconv(wif): + if wif[0] == '5': + from subprocess import check_output + return check_output(["keyconv", wif]).split()[1] + else: + return wiftoaddr(wif) + def get_wif2addr_f(): if g.no_keyconv: return wiftoaddr from mmgen.addr import test_for_keyconv return wiftoaddr_keyconv if test_for_keyconv() else wiftoaddr -def make_addr_data_chksum(adata,keys=False): - nchars = 24 - return make_chksum_N(" ".join([" ".join( - [str(n),d[0],d[2]] if keys else [str(n),d[0]] - ) for n,d in adata]), nchars, sep=True) - - -def get_addr_data_hash(e,keys=False): - def s_addrdata(a): return int(a[0]) - adata = [(k,e[k]) for k in e.keys()] - return make_addr_data_chksum(sorted(adata,key=s_addrdata),keys) - - -def _parse_addrfile_body(lines,keys=False,check=False): - - def parse_addr_lines(lines): - ret = [] - for l in lines: - d = l.split(None,2) - - if not is_mmgen_idx(d[0]): - msg("'%s': invalid address num. in line: '%s'" % (d[0],l)) - sys.exit(3) - - if not is_btc_addr(d[1]): - msg("'%s': invalid Bitcoin address" % d[1]) - sys.exit(3) - - if len(d) == 3: - comment = d[2] - check_addr_label(comment) - else: - comment = "" - - ret.append([d[0],d[1],comment]) - - return ret - - def parse_key_lines(lines): - ret = [] - for l in lines: - d = l.split(None,2) - - if d[0] != "wif:": - msg("Invalid key line in file: '%s'" % l) - sys.exit(3) - - if not is_btc_key(d[1]): - msg("'%s': invalid Bitcoin key" % d[1]) - sys.exit(3) - - ret.append(d[1]) - - return ret - - z = len(lines) / 2 - if keys: - # returns list of lists - adata = parse_addr_lines([lines[i*2] for i in range(z)]) - # returns list of strings - kdata = parse_key_lines([lines[i*2+1] for i in range(z)]) - if len(adata) != len(kdata): - msg("Odd number of lines in key file") - sys.exit(2) - if check or keypress_confirm("Check key-to-address validity?"): - wif2addr_f = get_wif2addr_f() - for i in range(z): - msg_r("\rVerifying keys %s/%s" % (i+1,z)) - if adata[i][1] != wif2addr_f(kdata[i]): - msg("Key doesn't match address!\n %s\n %s" % - kdata[i],adata[i][1]) - sys.exit(2) - msg(" - done") - return [adata[i] + [kdata[i]] for i in range(z)] - else: - return parse_addr_lines(lines) - - -def parse_addrfile(f,addr_data,keys=False,return_chk_and_sid=False): - return parse_addrfile_lines( - get_lines_from_file(f,"address data",trim_comments=True), - addr_data,keys,return_chk_and_sid=return_chk_and_sid) - -def parse_addrfile_lines(lines,addr_data,keys=False,exit_on_error=True,return_chk_and_sid=False): - - try: - seed_id,obrace = lines[0].split() - except: - errmsg = "Invalid first line: '%s'" % lines[0] - else: - cbrace = lines[-1] - if obrace != '{': - errmsg = "'%s': invalid first line" % lines[0] - elif cbrace != '}': - errmsg = "'%s': invalid last line" % cbrace - elif not is_mmgen_seed_id(seed_id): - errmsg = "'%s': invalid Seed ID" % seed_id - else: - ldata = _parse_addrfile_body(lines[1:-1],keys) - if seed_id not in addr_data: addr_data[seed_id] = {} - for l in ldata: - addr_data[seed_id][l[0]] = l[1:] - chk = get_addr_data_hash(addr_data[seed_id],keys) - if return_chk_and_sid: return chk,seed_id - from mmgen.addr import fmt_addr_idxs - fl = fmt_addr_idxs([int(i) for i in addr_data[seed_id].keys()]) - w = "key" if keys else "addr" - qmsg_r("Computed checksum for "+w+" data ",w.capitalize()+" checksum ") - msg("{}[{}]: {}".format(seed_id,fl,chk)) - qmsg("Check this value against your records") - return True - - if exit_on_error: - msg(errmsg) - sys.exit(3) - else: - return False - - -def parse_keyaddr_file(infile,addr_data): - d = get_data_from_file(infile,"%s key-address file data" % g.proj_name) - enc_ext = get_extension(infile) == g.mmenc_ext - if enc_ext or not is_utf8(d): - m = "Decrypting" if enc_ext else "Attempting to decrypt" - msg("%s key-address file %s" % (m,infile)) - from crypto import mmgen_decrypt_retry - d = mmgen_decrypt_retry(d,"key-address file") - parse_addrfile_lines(remove_comments(d.split("\n")),addr_data,True,False) - - def get_tx_comment_from_file(infile): s = get_data_from_file(infile,"transaction comment") if is_valid_tx_comment(s, verbose=True): @@ -469,11 +336,3 @@ def connect_to_bitcoind(): sys.exit(2) return c - - -def wiftoaddr_keyconv(wif): - if wif[0] == '5': - from subprocess import check_output - return check_output(["keyconv", wif]).split()[1] - else: - return wiftoaddr(wif) diff --git a/mmgen/util.py b/mmgen/util.py index e4009af3..93129bd2 100755 --- a/mmgen/util.py +++ b/mmgen/util.py @@ -41,6 +41,14 @@ def vmsg(s): def vmsg_r(s): if g.verbose: sys.stderr.write(s) +def msgrepr(*args): + for d in args: + sys.stdout.write(repr(d)+"\n") +def msgrepr_exit(*args): + for d in args: + sys.stdout.write(repr(d)+"\n") + sys.exit() + def suf(arg,what): t = type(arg) if t == int: @@ -65,6 +73,7 @@ def make_chksum_N(s,n,sep=False): s = sha256(sha256(s).digest()).hexdigest().upper() sep = " " if sep else "" return sep.join([s[i*4:i*4+4] for i in range(n/4)]) + def make_chksum_8(s,sep=False): s = sha256(sha256(s).digest()).hexdigest()[:8].upper() return "{} {}".format(s[:4],s[4:]) if sep else s @@ -206,11 +215,11 @@ def _validate_addr_num(n): try: n = int(n) except: - msg("'%s': address must be an integer" % n) + msg("'%s': addr index must be an integer" % n) return False if n < 1: - msg("'%s': address must be greater than zero" % n) + msg("'%s': addr index must be greater than zero" % n) return False return n @@ -280,8 +289,6 @@ def confirm_or_exit(message, question, expect="YES"): def confirm_or_false(message, question, expect="YES"): - vmsg("") - m = message.strip() if m: msg(m) diff --git a/test/test.py b/test/test.py index bfa0e96a..4f6a975a 100755 --- a/test/test.py +++ b/test/test.py @@ -8,12 +8,18 @@ pn = os.path.dirname(sys.argv[0]) os.chdir(os.path.join(pn,os.pardir)) sys.path.__setitem__(0,os.path.abspath(os.curdir)) +from mmgen.util import msgrepr, msgrepr_exit + hincog_fn = "rand_data" non_mmgen_fn = "btckey" from collections import OrderedDict cmd_data = OrderedDict([ # test description depends + ['refwalletgen', (6,'reference wallet seed ID', [[[],6]])], + ['refaddrgen', (6,'reference wallet address checksum', [[["mmdat"],6]])], + ['refkeyaddrgen', (6,'reference wallet key-address checksum', [[["mmdat"],6]])], + ['walletgen', (1,'wallet generation', [[[],1]])], ['walletchk', (1,'wallet check', [[["mmdat"],1]])], ['passchg', (5,'password, label and hash preset change',[[["mmdat"],1]])], @@ -39,18 +45,18 @@ 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)', [])], + ['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)',[[["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)', [])], + ['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]])], @@ -63,6 +69,25 @@ utils = { addrs_per_wallet = 8 cfgs = { + '6': { + 'name': "reference wallet check", + 'bw_passwd': "abc", + 'bw_hashparams': "256,1", + 'key_id': "98831F3A", + 'addrfile_chk': "6FEF 6FB9 7B13 5D91 854A 0BD3", + 'keyaddrfile_chk': "9F2D D781 1812 8BAD C396 9DEB", + + 'wpasswd': "reference password", + 'tmpdir': "test/tmp6", + 'kapasswd': "", + 'addr_idx_list': "1010,500-501,31-33,1,33,500,1011", # 8 addresses + 'dep_generators': { + 'mmdat': "refwalletgen", + 'addrs': "refaddrgen", + 'akeys.mmenc': "refkeyaddrgen" + }, + + }, '1': { 'tmpdir': "test/tmp1", 'wpasswd': "Dorian", @@ -85,6 +110,7 @@ cfgs = { 'tmpdir': "test/tmp2", 'wpasswd': "Hodling away", 'addr_idx_list': "37,45,3-6,22-23", # 8 addresses + 'seed_len': 128, 'dep_generators': { 'mmdat': "walletgen2", 'addrs': "addrgen2", @@ -108,6 +134,7 @@ cfgs = { 'tmpdir': "test/tmp4", 'wpasswd': "Hashrate rising", 'addr_idx_list': "63,1004,542-544,7-9", # 8 addresses + 'seed_len': 192, 'dep_generators': { 'mmdat': "walletgen4", 'mmbrain': "walletgen4", @@ -116,7 +143,7 @@ cfgs = { 'sig': "txsign4", }, 'bw_filename': "brainwallet.mmbrain", - 'bw_params': "256,1", + 'bw_params': "192,1", }, '5': { 'tmpdir': "test/tmp5", @@ -130,15 +157,6 @@ cfgs = { from binascii import hexlify def getrand(n): return int(hexlify(os.urandom(n)),16) -def msgrepr(*args): - for d in args: - sys.stdout.write(repr(d)+"\n") - -def msgrepr_exit(*args): - for d in args: - sys.stdout.write(repr(d)+"\n") - sys.exit() - # total of two outputs must be < 10 BTC for k in cfgs.keys(): cfgs[k]['amts'] = [0,0] @@ -146,6 +164,7 @@ for k in cfgs.keys(): cfgs[k]['amts'][idx] = "%s.%s" % ((getrand(2) % mod), str(getrand(4))[:5]) meta_cmds = OrderedDict([ + ['ref', (6,("refwalletgen","refaddrgen","refkeyaddrgen"))], ['gen', (1,("walletgen","walletchk","addrgen"))], ['pass', (5,("passchg","walletchk_newpass"))], ['tx', (1,("txcreate","txsign","txsend"))], @@ -217,6 +236,7 @@ def end_silence(): sys.stderr = stderr_save def errmsg(s): stderr_save.write(s+"\n") +def errmsg_r(s): stderr_save.write(s) def Msg(s): sys.stdout.write(s+"\n") @@ -237,7 +257,7 @@ if "list_cmds" in opts: import pexpect,time,re import mmgen.config as g -from mmgen.util import get_data_from_file, write_to_file, get_lines_from_file +from mmgen.util import get_data_from_file,write_to_file,get_lines_from_file redc,grnc,yelc,cyac,reset = ( ["\033[%sm" % c for c in "31;1","32;1","33;1","36;1","0"] @@ -310,8 +330,8 @@ def get_file_with_ext(ext,mydir,delete=True): def get_addrfile_checksum(display=False): addrfile = get_file_with_ext("addrs",cfg['tmpdir']) silence() - from mmgen.tx import parse_addrfile - chk = parse_addrfile(addrfile,{},return_chk_and_sid=True)[0] + from mmgen.addr import AddrInfo + chk = AddrInfo(addrfile).checksum if verbose and display: msg("Checksum: %s" % cyan(chk)) end_silence() return chk @@ -370,11 +390,6 @@ class MMGenExpect(object): my_expect(self.p,("Enter hash preset for %s, or ENTER .*?:" % what), str(preset)+"\n",regex=True) - def ok(self): - if verbose or exact_output: - sys.stderr.write(green("OK\n")) - else: msg(" OK") - def written_to_file(self,what,overwrite_unlikely=False,query="Overwrite? "): s1 = "%s written to file " % what s2 = query + "Type uppercase 'YES' to confirm: " @@ -425,7 +440,7 @@ from mmgen.bitcoin import verify_addr def add_fake_unspent_entry(out,address,comment): out.append(TransactionInfo( account = unicode(comment), - vout = (getrand(4) % 8), + vout = int(getrand(4) % 8), txid = unicode(hexlify(os.urandom(32))), amount = Decimal("%s.%s" % (10+(getrand(4) % 40), getrand(4) % 100000000)), address = address, @@ -434,14 +449,14 @@ def add_fake_unspent_entry(out,address,comment): confirmations = getrand(4) % 500 )) -def create_fake_unspent_data(addr_data,unspent_data_file,tx_data,non_mmgen_input=''): +def create_fake_unspent_data(adata,unspent_data_file,tx_data,non_mmgen_input=''): out = [] for s in tx_data.keys(): sid = tx_data[s]['sid'] - for idx in addr_data[sid].keys(): - address = unicode(addr_data[sid][idx][0]) - add_fake_unspent_entry(out,address, "%s:%s Test Wallet" % (sid,idx)) + a = adata.addrinfo(sid) + for idx,btcaddr in a.addrpairs(): + add_fake_unspent_entry(out,btcaddr,"%s:%s Test Wallet" % (sid,idx)) if non_mmgen_input: from mmgen.bitcoin import privnum2addr,hextowif @@ -453,25 +468,17 @@ def create_fake_unspent_data(addr_data,unspent_data_file,tx_data,non_mmgen_input add_fake_unspent_entry(out,btcaddr,"Non-MMGen address") +# msg("\n".join([repr(o) for o in out])); sys.exit() write_to_file(unspent_data_file,repr(out),{},"Unspent outputs",verbose=True) def add_comments_to_addr_file(addrfile,tfile): silence() msg(green("Adding comments to address file '%s'" % addrfile)) - d = get_lines_from_file(addrfile) - addr_data = {} - from mmgen.tx import parse_addrfile - parse_addrfile(addrfile,addr_data) - sid = addr_data.keys()[0] - def s(k): return int(k) - keys = sorted(addr_data[sid].keys(),key=s) - for n,k in enumerate(keys,1): - addr_data[sid][k][1] = ("Test address " + str(n)) - d = "#\n# Test address file with comments\n#\n%s {\n%s\n}\n" % (sid, - "\n".join([" {:<3} {:<36} {}".format(k,*addr_data[sid][k]) for k in keys])) - msg_r(d) - write_to_file(tfile,d,{}) + from mmgen.addr import AddrInfo + a = AddrInfo(addrfile) + for i in a.idxs(): a.set_comment(idx,"Test address %s" % idx) + write_to_file(tfile,a.fmt_data(),{}) end_silence() def make_brainwallet_file(fn): @@ -568,6 +575,22 @@ def mk_tmpdir(cfg): if e.errno != 17: raise else: msg("Created directory '%s'" % cfg['tmpdir']) +def refcheck(what,chk,refchk): + vmsg("Comparing %s '%s' to stored reference" % (what,chk)) + if chk == refchk: + ok() + else: + if not verbose: errmsg("") + errmsg(red(""" +Fatal error - %s '%s' does not match reference value '%s'. Aborting test +""".strip() % (what,chk,refchk))) + sys.exit(3) + +def ok(): + if verbose or exact_output: + sys.stderr.write(green("OK\n")) + else: msg(" OK") + class MMGenTestSuite(object): @@ -599,7 +622,7 @@ class MMGenTestSuite(object): def clean(self,name,dirs=[]): - dirlist = dirs if dirs else cfgs.keys() + dirlist = dirs if dirs else sorted(cfgs.keys()) for k in dirlist: if k in cfgs: cleandir(cfgs[k]['tmpdir']) @@ -611,6 +634,7 @@ class MMGenTestSuite(object): mk_tmpdir(cfg) args = ["-d",cfg['tmpdir'],"-p1","-r10"] +# if 'seed_len' in cfg: args += ["-l",cfg['seed_len']] if brain: bwf = os.path.join(cfg['tmpdir'],cfg['bw_filename']) args += ["-b",cfg['bw_params'],bwf] @@ -625,14 +649,23 @@ class MMGenTestSuite(object): t.expect("Type uppercase 'YES' to confirm: ","YES\n") t.usr_rand(10) - t.expect("Generating a key from OS random data plus user entropy") - - if not brain: - t.expect("Generating a key from OS random data plus saved user entropy") + for s in "user-supplied entropy","saved user-supplied entropy": + t.expect("Generating encryption key from OS random data plus %s" % s) + if brain: break t.passphrase_new("MMGen wallet",cfg['wpasswd']) t.written_to_file("Wallet") - t.ok() + ok() + + def refwalletgen(self,name): + mk_tmpdir(cfg) + args = ["-q","-d",cfg['tmpdir'],"-p1","-r10","-b"+cfg['bw_hashparams']] + t = MMGenExpect(name,"mmgen-walletgen", args) + t.expect("passphrase: ",cfg['bw_passwd']+"\n") + t.usr_rand(10) + t.passphrase_new("MMGen wallet",cfg['wpasswd']) + key_id = t.written_to_file("Wallet").split("-")[0].split("/")[-1] + refcheck("key id",key_id,cfg['key_id']) def passchg(self,name,walletfile): mk_tmpdir(cfg) @@ -647,11 +680,11 @@ class MMGenTestSuite(object): t.usr_rand(16) t.expect_getend("Key ID changed: ") t.written_to_file("Wallet") - t.ok() + ok() def walletchk_newpass(self,name,walletfile): t = self.walletchk_beg(name,[walletfile]) - t.ok() + ok() def walletchk_beg(self,name,args): t = MMGenExpect(name,"mmgen-walletchk", args) @@ -663,17 +696,23 @@ class MMGenTestSuite(object): def walletchk(self,name,walletfile): t = self.walletchk_beg(name,[walletfile]) - t.ok() + ok() - def addrgen(self,name,walletfile): + def addrgen(self,name,walletfile,check_ref=False): t = MMGenExpect(name,"mmgen-addrgen",["-d",cfg['tmpdir'],walletfile,cfg['addr_idx_list']]) t.license() t.passphrase("MMGen wallet",cfg['wpasswd']) t.expect("Passphrase is OK") - t.expect("Generated [0-9]+ addresses",regex=True) - t.expect_getend(r"Checksum for address data .*?: ",regex=True) + t.expect("[0-9]+ addresses generated",regex=True) + chk = t.expect_getend(r"Checksum for address data .*?: ",regex=True) + if check_ref: + refcheck("address data checksum",chk,cfg['addrfile_chk']) + return t.written_to_file("Addresses") - t.ok() + ok() + + def refaddrgen(self,name,walletfile): + self.addrgen(name,walletfile,check_ref=True) def addrimport(self,name,addrfile): outfile = os.path.join(cfg['tmpdir'],"addrfile_w_comments") @@ -683,7 +722,7 @@ class MMGenTestSuite(object): t.expect_getend("Validating addresses...OK. ") t.expect("Type uppercase 'YES' to confirm: ","\n") vmsg("This is a simulation, so no addresses were actually imported into the tracking\nwallet") - t.ok() + ok() def txcreate(self,name,addrfile): self.txcreate_common(name,sources=['1']) @@ -692,26 +731,27 @@ class MMGenTestSuite(object): if verbose or exact_output: sys.stderr.write(green("Generating fake transaction info\n")) silence() - tx_data,addr_data = {},{} - from mmgen.tx import parse_addrfile + from mmgen.addr import AddrInfo,AddrInfoList + tx_data,ail = {},AddrInfoList() from mmgen.util import parse_addr_idxs for s in sources: afile = get_file_with_ext("addrs",cfgs[s]["tmpdir"]) - chk,sid = parse_addrfile(afile,addr_data,return_chk_and_sid=True) + ai = AddrInfo(afile) + ail.add(ai) aix = parse_addr_idxs(cfgs[s]['addr_idx_list']) if len(aix) != addrs_per_wallet: errmsg(red("Addr index list length != %s: %s" % (addrs_per_wallet,repr(aix)))) sys.exit() tx_data[s] = { - 'addrfile': get_file_with_ext("addrs",cfgs[s]['tmpdir']), - 'chk': chk, - 'sid': sid, + 'addrfile': afile, + 'chk': ai.checksum, + 'sid': ai.seed_id, 'addr_idxs': aix[-2:], } unspent_data_file = os.path.join(cfg['tmpdir'],"unspent.json") - create_fake_unspent_data(addr_data,unspent_data_file,tx_data,non_mmgen_input) + create_fake_unspent_data(ail,unspent_data_file,tx_data,non_mmgen_input) # make the command line from mmgen.bitcoin import privnum2addr @@ -739,7 +779,6 @@ class MMGenTestSuite(object): t.license() for num in tx_data.keys(): t.expect_getend("Getting address data from file ") - from mmgen.addr import fmt_addr_idxs chk=t.expect_getend(r"Computed checksum for addr data .*?: ",regex=True) verify_checksum_or_exit(tx_data[num]['chk'],chk) @@ -763,9 +802,9 @@ class MMGenTestSuite(object): t.expect("OK? (Y/n): ","y") t.expect("Add a comment to transaction? (y/N): ","\n") t.tx_view() - t.expect("Save transaction? (Y/n): ","\n") + t.expect("Save transaction? (y/N): ","y") t.written_to_file("Transaction") - t.ok() + ok() def txsign(self,name,txfile,walletfile): t = MMGenExpect(name,"mmgen-txsign", ["-d",cfg['tmpdir'],txfile,walletfile]) @@ -774,7 +813,7 @@ class MMGenTestSuite(object): t.passphrase("MMGen wallet",cfg['wpasswd']) t.expect("Edit transaction comment? (y/N): ","\n") t.written_to_file("Signed transaction") - t.ok() + ok() def txsend(self,name,sigfile): t = MMGenExpect(name,"mmgen-txsend", ["-d",cfg['tmpdir'],sigfile]) @@ -785,7 +824,7 @@ class MMGenTestSuite(object): t.expect("Type uppercase 'YES, I REALLY WANT TO DO THIS' to confirm: ","\n") t.expect("Exiting at user request") vmsg("This is a simulation, so no transaction was sent") - t.ok() + ok() def export_seed(self,name,walletfile): t = self.walletchk_beg(name,["-s","-d",cfg['tmpdir'],walletfile]) @@ -793,7 +832,7 @@ class MMGenTestSuite(object): silence() msg("Seed data: %s" % cyan(get_data_from_file(f,"seed data"))) end_silence() - t.ok() + ok() def export_mnemonic(self,name,walletfile): t = self.walletchk_beg(name,["-m","-d",cfg['tmpdir'],walletfile]) @@ -801,7 +840,7 @@ class MMGenTestSuite(object): silence() msg_r("Mnemonic data: %s" % cyan(get_data_from_file(f,"mnemonic data"))) end_silence() - t.ok() + ok() def export_incog(self,name,walletfile,args=["-g"]): t = MMGenExpect(name,"mmgen-walletchk",args+["-d",cfg['tmpdir'],"-r","10",walletfile]) @@ -810,7 +849,7 @@ class MMGenTestSuite(object): t.expect_getend("Incog ID: ") if args[0] == "-G": return t t.written_to_file("Incognito wallet data",overwrite_unlikely=True) - t.ok() + ok() def export_incog_hex(self,name,walletfile): self.export_incog(name,walletfile,args=["-X"]) @@ -822,7 +861,7 @@ class MMGenTestSuite(object): write_to_file(rf,rd,{},verbose=verbose) t = self.export_incog(name,walletfile,args=["-G","%s,%s"%(rf,hincog_offset)]) t.written_to_file("Data",query="") - t.ok() + ok() def addrgen_seed(self,name,walletfile,foo,what="seed data",arg="-s"): t = MMGenExpect(name,"mmgen-addrgen", @@ -833,7 +872,7 @@ class MMGenTestSuite(object): chk = t.expect_getend(r"Checksum for address data .*?: ",regex=True) verify_checksum_or_exit(get_addrfile_checksum(),chk) t.no_overwrite() - t.ok() + ok() def addrgen_mnemonic(self,name,walletfile,foo): self.addrgen_seed(name,walletfile,foo,what="mnemonic",arg="-m") @@ -849,7 +888,7 @@ class MMGenTestSuite(object): chk = t.expect_getend(r"Checksum for address data .*?: ",regex=True) verify_checksum_or_exit(get_addrfile_checksum(),chk) t.no_overwrite() - t.ok() + ok() def addrgen_incog_hex(self,name,walletfile,foo): self.addrgen_incog(name,walletfile,foo,args=["-X"]) @@ -859,18 +898,24 @@ class MMGenTestSuite(object): self.addrgen_incog(name,walletfile,foo, args=["-G","%s,%s,%s"%(rf,hincog_offset,hincog_seedlen)]) - def keyaddrgen(self,name,walletfile): + def keyaddrgen(self,name,walletfile,check_ref=False): t = MMGenExpect(name,"mmgen-keygen", ["-d",cfg['tmpdir'],walletfile,cfg['addr_idx_list']]) t.license() t.expect("Type uppercase 'YES' to confirm: ","YES\n") t.passphrase("MMGen wallet",cfg['wpasswd']) - t.expect_getend(r"Checksum for key-address data .*?: ",regex=True) + chk = t.expect_getend(r"Checksum for key-address data .*?: ",regex=True) + if check_ref: + refcheck("key-address data checksum",chk,cfg['keyaddrfile_chk']) + return t.expect("Encrypt key list? (y/N): ","y") t.hash_preset("new key list",'1') t.passphrase_new("key list",cfg['kapasswd']) t.written_to_file("Keys") - t.ok() + ok() + + def refkeyaddrgen(self,name,walletfile): + self.keyaddrgen(name,walletfile,check_ref=True) def txsign_keyaddr(self,name,keyaddr_file,txfile): t = MMGenExpect(name,"mmgen-txsign", ["-d",cfg['tmpdir'],"-M",keyaddr_file,txfile]) @@ -882,7 +927,7 @@ class MMGenTestSuite(object): t.expect("Signing transaction...OK") t.expect("Edit transaction comment? (y/N): ","\n") t.written_to_file("Signed transaction") - t.ok() + ok() def walletgen2(self,name): self.walletgen(name) @@ -904,7 +949,7 @@ class MMGenTestSuite(object): t.expect("Edit transaction comment? (y/N): ","\n") t.written_to_file("Signed transaction #%s" % cnum) - t.ok() + ok() def export_mnemonic2(self,name,walletfile): self.export_mnemonic(name,walletfile) @@ -930,7 +975,7 @@ class MMGenTestSuite(object): t.expect_getend("Signing transaction") t.expect("Edit transaction comment? (y/N): ","\n") t.written_to_file("Signed transaction") - t.ok() + ok() def walletgen4(self,name): self.walletgen(name,brain=True) @@ -957,7 +1002,7 @@ class MMGenTestSuite(object): t.expect_getend("Signing transaction") t.expect("Edit transaction comment? (y/N): ","\n") t.written_to_file("Signed transaction") - t.ok() + ok() # main() ts = MMGenTestSuite()