Object-oriented reimplementation of addr data structures
Reference wallet with checksums added to test/test.py
This commit is contained in:
parent
b7a7d666e8
commit
54efc7679e
14 changed files with 584 additions and 438 deletions
|
|
@ -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:
|
||||
|
|
|
|||
323
mmgen/addr.py
323
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])
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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()])
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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])
|
||||
|
|
|
|||
201
mmgen/tx.py
201
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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
201
test/test.py
201
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()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue