Object-oriented reimplementation of addr data structures

Reference wallet with checksums added to test/test.py
This commit is contained in:
The MMGen Project 2015-01-06 20:10:29 +03:00
commit 54efc7679e
14 changed files with 584 additions and 438 deletions

View file

@ -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:

View file

@ -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])

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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")

View file

@ -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")

View file

@ -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()])

View file

@ -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)

View file

@ -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()

View file

@ -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])

View file

@ -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)

View file

@ -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)

View file

@ -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()