OO rewrite mostly done

Colored output
This commit is contained in:
philemon 2016-07-26 22:16:25 +03:00
commit 680ea8a5fc
26 changed files with 1593 additions and 1245 deletions

View file

@ -20,24 +20,68 @@
addr.py: Address generation/display routines for the MMGen suite
"""
from hashlib import sha256, sha512, new as hashlib_new
from binascii import hexlify, unhexlify
from hashlib import sha256, sha512
from mmgen.common import *
from mmgen.bitcoin import numtowif
from mmgen.tx import *
from mmgen.obj import *
from mmgen.tx import *
from mmgen.tw import *
pnm = g.proj_name
addrmsgs = {
'too_many_acct_addresses': """
ERROR: More than one address found for account: '%s'.
Your 'wallet.dat' file appears to have been altered by a non-{pnm} program.
Please restore your tracking wallet from a backup or create a new one and
re-import your addresses.
""".strip().format(pnm=pnm),
'addrfile_header': """
def test_for_keyconv(silent=False):
no_keyconv_errmsg = """
Executable '{kconv}' unavailable. Falling back on (slow) internal ECDSA library.
Please install '{kconv}' from the {vgen} package on your system for much
faster address generation.
""".format(kconv=g.keyconv_exec, vgen='vanitygen')
from subprocess import check_output,STDOUT
try:
check_output([g.keyconv_exec, '-G'],stderr=STDOUT)
except:
if not silent: msg(no_keyconv_errmsg)
return False
return True
class AddrListEntry(MMGenListItem):
attrs = 'idx','addr','label','wif','sec'
label = MMGenListItemAttr('label','MMGenAddrLabel')
idx = MMGenListItemAttr('idx','AddrIdx')
class AddrListChksum(str,Hilite):
color = 'pink'
trunc_ok = False
def __new__(cls,addrlist):
lines=[' '.join([str(e.idx),e.addr]+([e.wif] if addrlist.has_keys else []))
for e in addrlist.data]
return str.__new__(cls,make_chksum_N(' '.join(lines), nchars=16, sep=True))
class AddrListID(str,Hilite):
color = 'green'
trunc_ok = False
def __new__(cls,addrlist):
try: int(addrlist.data[0].idx)
except:
s = '(no idxs)'
else:
idxs = [e.idx for e in addrlist.data]
prev = idxs[0]
ret = prev,
for i in idxs[1:]:
if i == prev + 1:
if i == idxs[-1]: ret += '-', i
else:
if prev != ret[-1]: ret += '-', prev
ret += ',', i
prev = i
s = ''.join([str(i) for i in ret])
return str.__new__(cls,'%s[%s]' % (addrlist.seed_id,s))
class AddrList(MMGenObject): # Address info for a single seed ID
msgs = {
'file_header': """
# {pnm} address file
#
# This file is editable.
@ -46,37 +90,82 @@ re-import your addresses.
# address, and it will be appended to the bitcoind wallet label upon import.
# The label may contain any printable ASCII symbol.
""".strip().format(n=g.max_addr_label_len,pnm=pnm),
'keyfile_header': """
# {pnm} key file
#
# This file is editable.
# Everything following a hash symbol '#' is a comment and ignored by {pnm}.
""".strip().format(pnm=pnm),
'no_keyconv_msg': """
Executable '{kconv}' unavailable. Falling back on (slow) internal ECDSA library.
Please install '{kconv}' from the {vgen} package on your system for much
faster address generation.
""".format(kconv=g.keyconv_exec, vgen='vanitygen')
'record_chksum': """
Record this checksum: it will be used to verify the address file in the future
""".strip(),
'check_chksum': 'Check this value against your records',
'removed_dups': """
Removed %s duplicate wif key%s from keylist (also in {pnm} key-address file
""".strip().format(pnm=pnm)
}
data_desc = 'address'
file_desc = 'addresses'
gen_desc = 'address'
gen_desc_pl = 'es'
gen_addrs = True
gen_keys = False
has_keys = False
ext = 'addrs'
def test_for_keyconv(silent=False):
def __init__(self,addrfile='',sid='',adata=[],seed='',addr_idxs='',src='',
addrlist='',keylist='',do_chksum=True,chksum_only=False):
from subprocess import check_output,STDOUT
try:
check_output([g.keyconv_exec, '-G'],stderr=STDOUT)
except:
if not silent: msg(addrmsgs['no_keyconv_msg'])
return False
self.update_msgs()
return True
if addrfile: # data from MMGen address file
(sid,adata) = self.parse_file(addrfile)
elif sid and adata: # data from tracking wallet
do_chksum = False
elif seed and addr_idxs: # data from seed + idxs
sid,src = seed.sid,'gen'
adata = self.generate(seed,addr_idxs)
elif addrlist: # data from flat address list
sid = None
adata = [AddrListEntry(addr=a) for a in addrlist]
elif keylist: # data from flat key list
sid,do_chksum = None,False
adata = [AddrListEntry(wif=k) for k in keylist]
elif seed or addr_idxs:
die(3,'Must specify both seed and addr indexes')
elif sid or adata:
die(3,'Must specify both seed_id and adata')
else:
die(3,'Incorrect arguments for %s' % type(self).__name__)
# sid,adata now set
self.seed_id = sid
self.data = adata
self.num_addrs = len(adata)
self.fmt_data = ''
self.id_str = None
self.chksum = None
def generate_addrs(seed, addrnums, source='addrgen'):
if type(self) == KeyList:
self.id_str = AddrListID(self)
return
from util import make_chksum_8
seed_id = make_chksum_8(seed) # Must do this before seed gets clobbered
if do_chksum:
self.chksum = AddrListChksum(self)
if chksum_only:
Msg(self.chksum)
else:
self.id_str = AddrListID(self)
qmsg('Checksum for %s data %s: %s' %
(self.data_desc,self.id_str.hl(),self.chksum.hl()))
qmsg(self.msgs[('check_chksum','record_chksum')[src=='gen']])
if 'a' in opt.gen_what:
def update_msgs(self):
if type(self).msgs and type(self) != AddrList:
for k in AddrList.msgs:
if k not in self.msgs:
self.msgs[k] = AddrList.msgs[k]
def generate(self,seed,addrnums):
assert type(addrnums) is AddrIdxList
self.seed_id = SeedID(seed=seed)
seed = seed.get_data()
if self.gen_addrs:
if opt.no_keyconv or test_for_keyconv() == False:
msg('Using (slow) internal ECDSA library for address generation')
from mmgen.bitcoin import privnum2addr
@ -85,17 +174,8 @@ def generate_addrs(seed, addrnums, source='addrgen'):
from subprocess import check_output
keyconv = 'keyconv'
addrnums = sorted(set(addrnums)) # don't trust the calling function
t_addrs,num,pos,out = len(addrnums),0,0,[]
w = {
'ka': ('key/address pair','s'),
'k': ('key','s'),
'a': ('address','es')
}[opt.gen_what]
from mmgen.addr import AddrInfoEntry,AddrInfo
while pos != t_addrs:
seed = sha512(seed).digest()
num += 1 # round
@ -104,40 +184,163 @@ def generate_addrs(seed, addrnums, source='addrgen'):
pos += 1
qmsg_r('\rGenerating %s #%s (%s of %s)' % (w[0],num,pos,t_addrs))
qmsg_r('\rGenerating %s #%s (%s of %s)' % (self.gen_desc,num,pos,t_addrs))
e = AddrInfoEntry()
e.idx = num
e = AddrListEntry(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 opt.gen_what:
if self.gen_addrs:
if keyconv:
e.addr = check_output([keyconv, wif]).split()[1]
else:
e.addr = privnum2addr(int(sec,16))
if 'k' in opt.gen_what: e.wif = wif
if self.gen_keys:
e.wif = wif
if opt.b16: e.sec = sec
out.append(e)
m = w[0] if t_addrs == 1 else w[0]+w[1]
qmsg('\r%s: %s %s generated%s' % (seed_id,t_addrs,m,' '*15))
a = AddrInfo(has_keys='k' in opt.gen_what, source=source)
a.initialize(seed_id,out)
return a
qmsg('\r%s: %s %s%s generated%s' % (
self.seed_id.hl(),t_addrs,self.gen_desc,suf(t_addrs,self.gen_desc_pl),' '*15))
return out
def _parse_addrfile_body(lines,has_keys=False,check=False):
def encrypt(self):
from mmgen.crypto import mmgen_encrypt
self.fmt_data = mmgen_encrypt(self.fmt_data,'new key list','')
self.ext += '.'+g.mmenc_ext
if has_keys and len(lines) % 2:
def write_to_file(self,ask_tty=True,ask_write_default_yes=False):
fn = '{}.{}'.format(self.id_str,self.ext)
ask_tty = self.has_keys and not opt.quiet
write_data_to_file(fn,self.fmt_data,self.file_desc,ask_tty=ask_tty)
def idxs(self):
return [e.idx for e in self.data]
def addrs(self):
return ['%s:%s'%(self.seed_id,e.idx) for e in self.data]
def addrpairs(self):
return [(e.idx,e.addr) for e in self.data]
def btcaddrs(self):
return [e.addr for e in self.data]
def comments(self):
return [e.label for e in self.data]
def entry(self,idx):
for e in self.data:
if idx == e.idx: return e
def btcaddr(self,idx):
for e in self.data:
if idx == e.idx: return e.addr
def comment(self,idx):
for e in self.data:
if idx == e.idx: return e.label
def set_comment(self,idx,comment):
for e in self.data:
if idx == e.idx:
e.label = comment
def make_reverse_dict(self,btcaddrs):
d,b = {},btcaddrs
for e in self.data:
try:
d[b[b.index(e.addr)]] = ('%s:%s'%(self.seed_id,e.idx),e.label)
except: pass
return d
def flat_list(self):
class AddrListFlatEntry(AddrListEntry):
attrs = 'mmid','addr','wif'
return [AddrListFlatEntry(
mmid='{}:{}'.format(self.seed_id,e.idx),
addr=e.addr,
wif=e.wif)
for e in self.data]
def remove_dups(self,cmplist,key='wif'):
pop_list = []
for n,d in enumerate(self.data):
if getattr(d,key) == None: continue
for e in cmplist.data:
if getattr(e,key) and getattr(e,key) == getattr(d,key):
pop_list.append(n)
for n in reversed(pop_list): self.data.pop(n)
if pop_list:
vmsg(self.msgs['removed_dups'] % (len(pop_list),suf(removed,'k')))
def add_wifs(self,al_key):
for d in self.data:
for e in al_key.data:
if e.addr and e.wif and e.addr == d.addr:
d.wif = e.wif
def list_missing(self,key):
return [d for d in self.data if not getattr(d,key)]
def get(self,key):
return [getattr(d,key) for d in self.data if getattr(d,key)]
def get_addrs(self): return self.get('addr')
def get_wifs(self): return self.get('wif')
def generate_addrs(self):
wif2addr_f = get_wif2addr_f()
d = self.data
for n,e in enumerate(d,1):
qmsg_r('\rGenerating addresses from keylist: %s/%s' % (n,len(d)))
e.addr = wif2addr_f(e.wif)
qmsg('\rGenerated addresses from keylist: %s/%s ' % (n,len(d)))
def format(self,enable_comments=False):
def check_attrs(key,desc):
for e in self.data:
if not getattr(e,key):
die(3,'missing %s in addr data' % desc)
if type(self) != KeyList: check_attrs('addr','addresses')
if self.has_keys:
if opt.b16: check_attrs('sec','hex keys')
check_attrs('wif','wif keys')
out = [self.msgs['file_header']+'\n']
if self.chksum:
out.append('# {} data checksum for {}: {}'.format(
self.data_desc.capitalize(),self.id_str,self.chksum))
out.append('# Record this value to a secure location.\n')
out.append('%s {' % self.seed_id)
fs = ' {:<%s} {:<34}{}' % len(str(self.data[-1].idx))
for e in self.data:
c = ' '+e.label if enable_comments and e.label else ''
if type(self) == KeyList:
out.append(fs.format(e.idx, 'wif: '+e.wif,c))
else: # First line with idx
out.append(fs.format(e.idx, e.addr,c))
if self.has_keys:
if opt.b16: out.append(fs.format('', 'hex: '+e.sec,c))
out.append(fs.format('', 'wif: '+e.wif,c))
out.append('}')
self.fmt_data = '\n'.join([l.rstrip() for l in out]) + '\n'
def parse_file_body(self,lines):
if self.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)
@ -146,12 +349,11 @@ def _parse_addrfile_body(lines,has_keys=False,check=False):
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('')
if len(d) != 3: d.append('')
a.idx,a.addr,a.comment = int(d[0]),unicode(d[1]),unicode(d[2])
a = AddrListEntry(idx=int(d[0]),addr=d[1],label=d[2])
if has_keys:
if self.has_keys:
l = lines.pop(0)
d = l.split(None,2)
@ -160,11 +362,11 @@ def _parse_addrfile_body(lines,has_keys=False,check=False):
if not is_wif(d[1]):
return "'%s': invalid Bitcoin key" % d[1]
a.wif = unicode(d[1])
a.wif = d[1]
ret.append(a)
if has_keys and keypress_confirm('Check key-to-address validity?'):
if self.has_keys and keypress_confirm('Check key-to-address validity?'):
wif2addr_f = get_wif2addr_f()
llen = len(ret)
for n,e in enumerate(ret):
@ -175,11 +377,9 @@ def _parse_addrfile_body(lines,has_keys=False,check=False):
return ret
def parse_file(self,fn,buf=[],exit_on_error=True):
def _parse_addrfile(fn,buf=[],has_keys=False,exit_on_error=True):
if buf: lines = remove_comments(buf.splitlines()) # DOS-safe
else: lines = get_lines_from_file(fn,'address data',trim_comments=True)
lines = get_lines_from_file(fn,self.data_desc+' data',trim_comments=True)
try:
sid,obrace = lines[0].split()
@ -194,248 +394,104 @@ def _parse_addrfile(fn,buf=[],has_keys=False,exit_on_error=True):
elif not is_mmgen_seed_id(sid):
errmsg = "'%s': invalid Seed ID" % sid
else:
ret = _parse_addrfile_body(lines[1:-1],has_keys)
if type(ret) == list: return sid,ret
else: errmsg = ret
ret = self.parse_file_body(lines[1:-1])
if type(ret) == list:
return sid,ret
else:
errmsg = ret
if exit_on_error: die(3,errmsg)
else: return False
msg(errmsg)
return False
class KeyAddrList(AddrList):
data_desc = 'key-address'
file_desc = 'secret keys'
gen_desc = 'key/address pair'
gen_desc_pl = 's'
gen_addrs = True
gen_keys = True
has_keys = True
ext = 'akeys'
class KeyList(AddrList):
msgs = {
'file_header': """
# {pnm} key file
#
# This file is editable.
# Everything following a hash symbol '#' is a comment and ignored by {pnm}.
""".strip().format(pnm=pnm)
}
data_desc = 'key'
file_desc = 'secret keys'
gen_desc = 'key'
gen_desc_pl = 's'
gen_addrs = False
gen_keys = True
has_keys = True
ext = 'keys'
def _parse_keyaddr_file(fn):
from mmgen.crypto import mmgen_decrypt_file_maybe
d = mmgen_decrypt_file_maybe(fn,'key-address file')
return _parse_addrfile('',buf=d,has_keys=True,exit_on_error=False)
class AddrData(MMGenObject):
msgs = {
'too_many_acct_addresses': """
ERROR: More than one address found for account: '%s'.
Your 'wallet.dat' file appears to have been altered by a non-{pnm} program.
Please restore your tracking wallet from a backup or create a new one and
re-import your addresses.
""".strip().format(pnm=pnm)
}
class AddrInfoList(MMGenObject):
def __init__(self,addrinfo=None,bitcoind_connection=None):
self.data = {}
if bitcoind_connection:
self.add_wallet_data(bitcoind_connection)
def __init__(self,source=None):
self.sids = {}
if source == 'tw': self.add_tw_data()
def seed_ids(self):
return self.data.keys()
return self.sids.keys()
def addrinfo(self,sid):
def addrlist(self,sid):
# TODO: Validate sid
if sid in self.data:
return self.data[sid]
if sid in self.sids:
return self.sids[sid]
def mmaddr2btcaddr(self,mmaddr):
btcaddr = ''
sid,idx = mmaddr.split(':')
if sid in self.seed_ids():
btcaddr = self.addrinfo(sid).btcaddr(int(idx))
btcaddr = self.addrlist(sid).btcaddr(int(idx))
return btcaddr
def add_wallet_data(self,c):
vmsg_r('Getting account data from wallet...')
def add_tw_data(self):
vmsg_r('Getting address data from tracking wallet...')
c = bitcoin_connection()
accts = c.listaccounts(0,True)
data,i = {},0
alists = c.getaddressesbyaccount([[k] for k in accts],batch=True)
for acct,addrlist in zip(accts,alists):
ma,comment = parse_mmgen_label(acct)
if ma:
maddr,label = parse_tw_acct_label(acct)
if maddr:
i += 1
# addrlist = c.getaddressesbyaccount(acct)
if len(addrlist) != 1:
die(2,addrmsgs['too_many_acct_addresses'] % acct)
seed_id,idx = ma.split(':')
die(2,self.msgs['too_many_acct_addresses'] % acct)
seed_id,idx = maddr.split(':')
if seed_id not in data:
data[seed_id] = []
a = AddrInfoEntry()
a.idx,a.addr,a.comment = \
int(idx),unicode(addrlist[0]),unicode(comment)
data[seed_id].append(a)
data[seed_id].append(AddrListEntry(idx=idx,addr=addrlist[0],label=label))
vmsg('{n} {pnm} addresses found, {m} accounts total'.format(
n=i,pnm=pnm,m=len(accts)))
for sid in data:
self.add(AddrInfo(sid=sid,adata=data[sid]))
self.add(AddrList(sid=sid,adata=data[sid]))
def add(self,addrinfo):
if type(addrinfo) == AddrInfo:
self.data[addrinfo.seed_id] = addrinfo
def add(self,addrlist):
if type(addrlist) == AddrList:
self.sids[addrlist.seed_id] = addrlist
return True
else:
die(1,'Error: object %s is not of type AddrInfo' % repr(addrinfo))
raise TypeError, 'Error: object %s is not of type AddrList' % repr(addrlist)
def make_reverse_dict(self,btcaddrs):
d = {}
for k in self.data.keys():
d.update(self.data[k].make_reverse_dict(btcaddrs))
for sid in self.sids:
d.update(self.sids[sid].make_reverse_dict(btcaddrs))
return d
class AddrInfoEntry(MMGenObject):
def __init__(self): pass
class AddrInfo(MMGenObject):
def __init__(self,addrfile='',has_keys=False,sid='',adata=[],source='',caller=''):
self.has_keys = has_keys
self.caller = caller
do_chksum = True
if addrfile:
f = (_parse_addrfile,_parse_keyaddr_file)[bool(has_keys)]
sid,adata = f(addrfile)
self.source = 'addrfile'
elif sid and adata: # data from wallet
self.source = 'wallet'
elif sid or adata:
die(3,'Must specify address file, or seed_id + adata')
else:
self.source = source if source else 'unknown'
return
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)
if self.source in ('wallet','txsign'):
self.checksum = None
self.idxs_fmt = None
elif self.source == 'addrgen' and opt.gen_what == 'k':
self.checksum = None
self.fmt_addr_idxs()
else: # self.source in addrfile, addrgen
self.make_addrdata_chksum()
if self.caller == 'tool':
Msg(self.checksum)
else:
self.fmt_addr_idxs()
w = ('address','key-address')[bool(self.has_keys)]
qmsg('Checksum for %s data %s[%s]: %s' %
(w,self.seed_id,self.idxs_fmt,self.checksum))
if self.source == 'addrgen':
qmsg(
'Record this checksum: it will be used to verify the address file in the future')
elif self.source == 'addrfile':
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:
if is_valid_tx_comment(comment):
e.comment = comment
else:
sys.exit(2)
def make_reverse_dict(self,btcaddrs):
d,b = {},btcaddrs
for e in self.addrdata:
try:
d[b[b.index(e.addr)]] = ('%s:%s'%(self.seed_id,e.idx),e.comment)
except: pass
return d
def make_addrdata_chksum(self):
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=24, sep=True)
def fmt_data(self,enable_comments=False):
# Check data integrity - either all or none must exist for each attr
attrs = ['addr','wif','sec']
status = [0,0,0]
for d in self.addrdata:
for j,attr in enumerate(attrs):
if hasattr(d,attr):
status[j] += 1
for i,s in enumerate(status):
if s != 0 and s != self.num_addrs:
die(3,'%s missing %s in addr data'% (self.num_addrs-s,attrs[i]))
if status[0] == status[1] == 0:
die(3,'Addr data contains neither addresses nor keys')
# Header
out = []
k = ('addrfile_header','keyfile_header')[status[0]==0]
out.append(addrmsgs[k]+'\n')
if self.checksum:
w = ('Key-address','Address')[status[1]==0]
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)
# Body
fs = ' {:<%s} {:<34}{}' % len(str(self.addrdata[-1].idx))
for e in self.addrdata:
c = ''
if enable_comments:
try: c = ' '+e.comment
except: pass
if status[0]: # First line with idx
out.append(fs.format(e.idx, e.addr,c))
else:
out.append(fs.format(e.idx, 'wif: '+e.wif,c))
if status[1]: # Subsequent lines
if status[2]:
out.append(fs.format('', 'hex: '+e.sec,c))
if status[0]:
out.append(fs.format('', 'wif: '+e.wif,c))
out.append('}')
return '\n'.join([l.rstrip() for l in out]) + '\n'
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

@ -120,10 +120,11 @@ def b58encode(s):
def b58decode(b58num):
if b58num == '': return ''
# Zap all spaces:
num = _b58tonum(b58num.translate(None,' \t\n\r'))
# Use translate() only with str, not unicode
num = _b58tonum(str(b58num).translate(None,' \t\n\r'))
if num == False: return False
out = '{:x}'.format(num)
return unhexlify('0'*(len(out)%2) + out)
out = u'{:x}'.format(num)
return unhexlify(u'0'*(len(out)%2) + out)
# These yield bytewise equivalence in our special cases:

View file

@ -242,15 +242,6 @@ def mmgen_decrypt(data,desc='data',hash_preset=''):
msg('Incorrect passphrase or hash preset')
return False
def mmgen_decrypt_file_maybe(fn,desc):
d = get_data_from_file(fn,'{} data'.format(desc),binary=True)
have_enc_ext = get_extension(fn) == g.mmenc_ext
if have_enc_ext or not is_ascii(d):
m = ('Attempting to decrypt','Decrypting')[have_enc_ext]
msg('%s %s %s' % (m,desc,fn))
d = mmgen_decrypt_retry(d,desc)
return d
def mmgen_decrypt_retry(d,desc='data'):
while True:
d_dec = mmgen_decrypt(d,desc)

View file

@ -29,16 +29,24 @@ class Filename(MMGenObject):
self.name = fn
self.dirname = os.path.dirname(fn)
self.basename = os.path.basename(fn)
self.ext = None
self.ftype = ftype
# This should be done before license msg instead
# check_infile(fn)
if not ftype:
self.ext = get_extension(fn)
if not (self.ext):
die(2,"Unrecognized extension '.%s' for file '%s'" % (self.ext,fn))
self.ftype = None # the file's associated class
from mmgen.seed import SeedSource
if ftype:
if type(ftype) == type:
if issubclass(ftype,SeedSource):
self.ftype = ftype
# elif: # other MMGen file types
else:
die(3,"'%s': not a recognized file type for SeedSource" % ftype)
else:
die(3,"'%s': not a class" % ftype)
else:
self.ftype = SeedSource.ext_to_type(self.ext)
if not self.ftype:
die(3,"'%s': not a recognized extension for SeedSource" % self.ext)
# TODO: Check for Windows
mode = (os.O_RDONLY,os.O_RDWR)[bool(write)]

View file

@ -34,13 +34,11 @@ debug = os.getenv('MMGEN_DEBUG')
no_license = os.getenv('MMGEN_NOLICENSE')
bogus_wallet_data = os.getenv('MMGEN_BOGUS_WALLET_DATA')
disable_hold_protect = os.getenv('MMGEN_DISABLE_HOLD_PROTECT')
color = (False,True)[sys.stdout.isatty() and not os.getenv('MMGEN_DISABLE_COLOR')]
btc_amt_decimal_places = 8
from decimal import Decimal
tx_fee = Decimal('0.0003')
max_tx_fee = Decimal('0.01')
tx_fee_adj = Decimal('1.0')
from mmgen.obj import BTCAmt
tx_fee = BTCAmt('0.0003')
tx_fee_adj = 1.0
tx_confs = 3
seed_len = 256
@ -58,7 +56,7 @@ version = '0.8.4'
required_opts = [
'quiet','verbose','debug','outdir','echo_passphrase','passwd_file',
'usr_randchars','stdout','show_hash_presets','label',
'keep_passphrase','keep_hash_preset','brain_params'
'keep_passphrase','keep_hash_preset','brain_params','b16'
]
incompatible_opts = (
('quiet','verbose'),
@ -66,44 +64,25 @@ incompatible_opts = (
('tx_id', 'info'),
('tx_id', 'terse_info'),
)
min_screen_width = 80
wallet_ext = 'mmdat'
seed_ext = 'mmseed'
mn_ext = 'mmwords'
brain_ext = 'mmbrain'
incog_ext = 'mmincog'
incog_hex_ext = 'mmincox'
seedfile_exts = (
wallet_ext, seed_ext, mn_ext, brain_ext, incog_ext, incog_hex_ext
)
rawtx_ext = 'rawtx'
sigtx_ext = 'sigtx'
txid_ext = 'txid'
addrfile_ext = 'addrs'
addrfile_chksum_ext = 'chk'
keyfile_ext = 'keys'
keyaddrfile_ext = 'akeys'
mmenc_ext = 'mmenc'
default_wordlist = 'electrum'
#default_wordlist = 'tirosh'
# Global value sets user opt
dfl_vars = 'seed_len','hash_preset','usr_randchars','debug','tx_fee','tx_confs','tx_fee_adj'
seed_lens = 128,192,256
mn_lens = [i / 32 * 3 for i in seed_lens]
dfl_vars = 'seed_len','hash_preset','usr_randchars','debug','tx_confs','tx_fee_adj','tx_fee'
keyconv_exec = 'keyconv'
mins_per_block = 9
passwd_max_tries = 5
max_urandchars,min_urandchars = 80,10
max_urandchars = 80
_x = os.getenv('MMGEN_MIN_URANDCHARS')
min_urandchars = int(_x) if _x and int(_x) else 10
seed_lens = 128,192,256
mn_lens = [i / 32 * 3 for i in seed_lens]
mmenc_ext = 'mmenc'
salt_len = 16
aesctr_iv_len = 16
hincog_chk_len = 8
@ -125,9 +104,8 @@ mmgen_idx_max_digits = 7
printable_nonl = [chr(i+32) for i in range(95)]
printable = printable_nonl + ['\n','\t']
addr_label_symbols = wallet_label_symbols = printable_nonl
max_addr_label_len = 32
max_wallet_label_len = 48
max_tx_comment_len = 72 # Comment is b58 encoded, so can permit all UTF-8
max_tx_comment_len = 72 # Comment is b58 encoded, so can permit UTF-8

View file

@ -101,35 +101,21 @@ if len(cmd_args) < nargs and not (opt.hidden_incog_input_params or opt.in_fmt):
elif len(cmd_args) > nargs - int(bool(opt.hidden_incog_input_params)):
opts.usage()
addrlist_arg = cmd_args.pop()
addr_idxs = parse_addr_idxs(addrlist_arg)
if not addr_idxs:
die(1,"'%s': invalid address list argument" % addrlist_arg)
addridxlist_str = cmd_args.pop()
idxs = AddrIdxList(fmt_str=addridxlist_str)
do_license_msg()
opt.gen_what = 'a' if gen_what == 'addresses' \
else 'k' if opt.no_addresses else 'ka'
ss = SeedSource(*cmd_args) # *(cmd_args[0] if cmd_args else [])
# Generate data:
ss = SeedSource(*cmd_args)
i = (gen_what=='addresses') or bool(opt.no_addresses)*2
al = (KeyAddrList,AddrList,KeyList)[i](seed=ss.seed,addr_idxs=idxs)
al.format()
ainfo = generate_addrs(ss.seed.data,addr_idxs)
if al.gen_addrs and opt.print_checksum:
Die(0,al.checksum)
addrdata_str = ainfo.fmt_data()
outfile_base = '{}[{}]'.format(ss.seed.sid, ainfo.idxs_fmt)
if al.gen_keys and keypress_confirm('Encrypt key list?'):
al.encrypt()
if 'a' in opt.gen_what and opt.print_checksum:
Die(0,ainfo.checksum)
if 'k' in opt.gen_what and keypress_confirm('Encrypt key list?'):
addrdata_str = mmgen_encrypt(addrdata_str,'new key list','')
enc_ext = '.' + g.mmenc_ext
else: enc_ext = ''
ext = (g.keyfile_ext,g.keyaddrfile_ext)['ka' in opt.gen_what]
ext = (g.addrfile_ext,ext)['k' in opt.gen_what]
outfile = '%s.%s%s' % (outfile_base, ext, enc_ext)
ask_tty = 'k' in opt.gen_what and not opt.quiet
if gen_what == 'keys': gen_what = 'secret keys'
write_data_to_file(outfile,addrdata_str,gen_what,ask_tty=ask_tty)
al.write_to_file()

View file

@ -23,7 +23,7 @@ mmgen-addrimport: Import addresses into a MMGen bitcoind tracking wallet
import time
from mmgen.common import *
from mmgen.addr import AddrInfo,AddrInfoEntry
from mmgen.addr import AddrList,KeyAddrList
opts_data = {
'desc': """Import addresses (both {pnm} and non-{pnm}) into a bitcoind
@ -53,14 +53,9 @@ if len(cmd_args) == 1:
if opt.addrlist:
lines = get_lines_from_file(
infile,'non-{pnm} addresses'.format(pnm=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)
ai = AddrList(addrlist=lines)
else:
ai = AddrInfo(infile,has_keys=opt.keyaddr_file)
ai = (AddrList,KeyAddrList)[bool(opt.keyaddr_file)](infile)
else:
die(1,"""
You must specify an {pnm} address file (or a list of non-{pnm} addresses
@ -69,7 +64,7 @@ with the '--addrlist' option)
from mmgen.bitcoin import verify_addr
qmsg_r('Validating addresses...')
for e in ai.addrdata:
for e in ai.data:
if not verify_addr(e.addr,verbose=True):
die(2,'%s: invalid address' % e.addr)
@ -114,13 +109,13 @@ else:
msg_fmt = '\r%-{}s %-34s %s'.format(w_n_of_m, w_mmid)
msg("Importing %s addresses from '%s'%s" %
(len(ai.addrdata),infile,('',' (batch mode)')[bool(opt.batch)]))
(len(ai.data),infile,('',' (batch mode)')[bool(opt.batch)]))
arg_list = []
for n,e in enumerate(ai.addrdata):
for n,e in enumerate(ai.data):
if e.idx:
label = '%s:%s' % (ai.seed_id,e.idx)
if e.comment: label += ' ' + e.comment
if e.label: label += ' ' + e.label
else: label = 'non-{pnm}'.format(pnm=g.proj_name)
if opt.batch:

View file

@ -21,8 +21,6 @@ mmgen-txcreate: Create a Bitcoin transaction to and from MMGen- or non-MMGen
inputs and outputs
"""
from decimal import Decimal
from mmgen.common import *
from mmgen.tx import *
from mmgen.tw import *
@ -83,13 +81,12 @@ No data for {pnm} address {mmgenaddr} could be found in the tracking wallet.
Please import this address into your tracking wallet or supply an address file
for it on the command line.
""".strip(),
'mixed_inputs': """
NOTE: This transaction uses a mixture of both {pnm} and non-{pnm} inputs, which
makes the signing process more complicated. When signing the transaction, keys
for the non-{pnm} inputs must be supplied to '{pnl}-txsign' in a file with the
'--keys-from-file' option.
Selected mmgen inputs: %s
'non_mmgen_inputs': """
NOTE: This transaction includes non-{pnm} inputs, which makes the signing
process more complicated. When signing the transaction, keys for non-{pnm}
inputs must be supplied to '{pnl}-txsign' in a file with the '--keys-from-file'
option.
Selected non-{pnm} inputs: %s
""".strip().format(pnm=pnm,pnl=pnm.lower()),
'not_enough_btc': """
Not enough BTC in the inputs for this transaction (%s BTC)
@ -100,7 +97,7 @@ was specified.
""".strip(),
}
def select_outputs(unspent,prompt):
def select_unspent(unspent,prompt):
while True:
reply = my_raw_input(prompt).strip()
@ -118,14 +115,14 @@ def select_outputs(unspent,prompt):
return selected
def mmaddr2baddr(c,mmaddr,ail_w,ail_f):
def mmaddr2baddr(c,mmaddr,ad_w,ad_f):
# assume mmaddr has already been checked
btc_addr = ail_w.mmaddr2btcaddr(mmaddr)
btc_addr = ad_w.mmaddr2btcaddr(mmaddr)
if not btc_addr:
if ail_f:
btc_addr = ail_f.mmaddr2btcaddr(mmaddr)
if ad_f:
btc_addr = ad_f.mmaddr2btcaddr(mmaddr)
if btc_addr:
msg(wmsg['addr_in_addrfile_only'].format(mmgenaddr=mmaddr))
if not keypress_confirm('Continue anyway?'):
@ -135,16 +132,16 @@ def mmaddr2baddr(c,mmaddr,ail_w,ail_f):
else:
die(2,wmsg['addr_not_found_no_addrfile'].format(pnm=pnm,mmgenaddr=mmaddr))
return btc_addr
return BTCAddr(btc_addr)
def get_fee_estimate():
if 'tx_fee' in opt.set_by_user:
if 'tx_fee' in opt.set_by_user: # TODO
return None
else:
ret = c.estimatefee(opt.tx_confs)
if ret != -1:
return ret
return BTCAmt(ret)
else:
m = """
Fee estimation failed!
@ -172,46 +169,41 @@ c = bitcoin_connection()
if not opt.info:
do_license_msg(immed=True)
addrfiles = [a for a in cmd_args if get_extension(a) == g.addrfile_ext]
from mmgen.addr import AddrList,AddrData
addrfiles = [a for a in cmd_args if get_extension(a) == AddrList.ext]
cmd_args = set(cmd_args) - set(addrfiles)
from mmgen.addr import AddrInfo,AddrInfoList
ail_f = AddrInfoList()
ad_f = AddrData()
for a in addrfiles:
check_infile(a)
ail_f.add(AddrInfo(a))
ad_f.add(AddrList(a))
ail_w = AddrInfoList(bitcoind_connection=c)
ad_w = AddrData(source='tw')
for a in cmd_args:
if ',' in a:
a1,a2 = split2(a,',')
a1,a2 = a.split(',',1)
if is_btc_addr(a1):
btc_addr = a1
elif is_mmgen_addr(a1):
btc_addr = mmaddr2baddr(c,a1,ail_w,ail_f)
btc_addr = BTCAddr(a1)
elif is_mmgen_id(a1):
btc_addr = mmaddr2baddr(c,a1,ad_w,ad_f)
else:
die(2,"%s: unrecognized subargument in argument '%s'" % (a1,a))
btc_amt = convert_to_btc_amt(a2)
if btc_amt:
tx.add_output(btc_addr,btc_amt)
else:
die(2,"%s: invalid amount in argument '%s'" % (a2,a))
elif is_mmgen_addr(a) or is_btc_addr(a):
tx.add_output(btc_addr,BTCAmt(a2))
elif is_mmgen_id(a) or is_btc_addr(a):
if tx.change_addr:
die(2,'ERROR: More than one change address specified: %s, %s' %
(change_addr, a))
tx.change_addr = a if is_btc_addr(a) else mmaddr2baddr(c,a,ail_w,ail_f)
tx.add_output(tx.change_addr,Decimal('0'))
tx.change_addr = mmaddr2baddr(c,a,ad_w,ad_f) if is_mmgen_id(a) else BTCAddr(a)
tx.add_output(tx.change_addr,BTCAmt('0'))
else:
die(2,'%s: unrecognized argument' % a)
if not tx.outputs:
die(2,'At least one output must be specified on the command line')
if opt.tx_fee > g.max_tx_fee:
die(2,'Transaction fee too large: %s > %s' % (opt.tx_fee,g.max_tx_fee))
if opt.tx_fee > tx.max_fee:
die(2,'Transaction fee too large: %s > %s' % (opt.tx_fee,tx.max_fee))
fee_estimate = get_fee_estimate()
@ -223,10 +215,10 @@ if opt.info: sys.exit()
tx.send_amt = tx.sum_outputs()
msg('Total amount to spend: %s' % ('Unknown','%s BTC'%tx.send_amt,)[bool(tx.send_amt)])
msg('Total amount to spend: %s' % ('Unknown','%s BTC'%tx.send_amt.hl())[bool(tx.send_amt)])
while True:
sel_nums = select_outputs(tw.unspent,
sel_nums = select_unspent(tw.unspent,
'Enter a range or space-separated list of outputs to spend: ')
msg('Selected output%s: %s' % (
('s','')[len(sel_nums)==1],
@ -234,31 +226,31 @@ while True:
))
sel_unspent = [tw.unspent[i-1] for i in sel_nums]
mmaddrs = set([i['mmid'] for i in sel_unspent])
if '' in mmaddrs and len(mmaddrs) > 1:
mmaddrs.discard('')
msg(wmsg['mixed_inputs'] % ', '.join(sorted(mmaddrs)))
non_mmaddrs = [i for i in sel_unspent if i.mmid == None]
if non_mmaddrs:
msg(wmsg['non_mmgen_inputs'] % ', '.join(set(sorted([a.addr.hl() for a in non_mmaddrs]))))
if not keypress_confirm('Accept?'):
continue
tx.copy_inputs(sel_unspent) # makes tx.inputs
tx.copy_inputs_from_tw(sel_unspent) # makes tx.inputs
tx.calculate_size_and_fee(fee_estimate) # sets tx.size, tx.fee
change_amt = tx.sum_inputs() - tx.send_amt - tx.fee
if change_amt >= 0:
prompt = 'Transaction produces %s BTC in change. OK?' % change_amt
prompt = 'Transaction produces %s BTC in change. OK?' % change_amt.hl()
if keypress_confirm(prompt,default_yes=True):
break
else:
msg(wmsg['not_enough_btc'] % change_amt)
if change_amt > 0:
change_amt = BTCAmt(change_amt)
if not tx.change_addr:
die(2,wmsg['throwaway_change'] % change_amt)
tx.add_output(tx.change_addr,change_amt)
tx.del_output(tx.change_addr)
tx.add_output(BTCAddr(tx.change_addr),change_amt)
elif tx.change_addr:
msg('Warning: Change address will be unused as transaction produces no change')
tx.del_output(tx.change_addr)
@ -270,7 +262,7 @@ dmsg('tx: %s' % tx)
tx.add_comment() # edits an existing comment
tx.create_raw(c) # creates tx.hex, tx.txid
tx.add_mmaddrs_to_outputs(ail_w,ail_f)
tx.add_mmaddrs_to_outputs(ad_w,ad_f)
tx.add_timestamp()
tx.add_blockcount(c)

View file

@ -21,8 +21,9 @@ mmgen-txsign: Sign a transaction generated by 'mmgen-txcreate'
"""
from mmgen.common import *
from mmgen.seed import *
from mmgen.tx import *
from mmgen.seed import SeedSource
from mmgen.addr import *
pnm = g.proj_name
@ -86,28 +87,29 @@ mappings are verified. Therefore, seed material or a key-address file for
these addresses must be supplied on the command line.
Seed data supplied in files must have the following extensions:
wallet: '.{g.wallet_ext}'
seed: '.{g.seed_ext}'
mnemonic: '.{g.mn_ext}'
brainwallet: '.{g.brain_ext}'
wallet: '.{w.ext}'
seed: '.{s.ext}'
mnemonic: '.{m.ext}'
brainwallet: '.{b.ext}'
FMT CODES:
{f}
""".format(
f='\n '.join(SeedSource.format_fmt_codes().splitlines()),
g=g,pnm=pnm,pnl=pnm.lower()
pnm=pnm,pnl=pnm.lower(),
w=Wallet,s=SeedFile,m=Mnemonic,b=Brainwallet
)
}
wmsg = {
'mm2btc_mapping_error': """
'mapping_error': """
{pnm} -> BTC address mappings differ!
From %-18s %s -> %s
From %-18s %s -> %s
""".strip().format(pnm=pnm),
'removed_dups': """
Removed %s duplicate wif key%s from keylist (also in {pnm} key-address file
%-23s %s -> %s
%-23s %s -> %s
""".strip().format(pnm=pnm),
'missing_keys_error': """
A key file must be supplied for the following non-{pnm} address%s:\n %s
""".format(pnm=pnm).strip()
}
def get_seed_for_seed_id(seed_id,infiles,saved_seeds):
@ -121,30 +123,47 @@ def get_seed_for_seed_id(seed_id,infiles,saved_seeds):
elif opt.in_fmt:
qmsg('Need seed data for Seed ID %s' % seed_id)
ss = SeedSource()
msg('User input produced Seed ID %s' % make_chksum_8(seed))
msg('User input produced Seed ID %s' % ss.seed.sid)
else:
die(2,'ERROR: No seed source found for Seed ID: %s' % seed_id)
saved_seeds[ss.seed.sid] = ss.seed.data
if ss.seed.sid == seed_id: return ss.seed.data
saved_seeds[ss.seed.sid] = ss.seed
if ss.seed.sid == seed_id: return ss.seed
def generate_keys_for_mmgen_addrs(mmgen_addrs,infiles,saved_seeds):
seed_ids = set([i[:8] for i in mmgen_addrs])
vmsg('Need seed%s: %s' % (suf(seed_ids,'k'),' '.join(seed_ids)))
d = []
from mmgen.addr import generate_addrs
from mmgen.addr import KeyAddrList
for seed_id in seed_ids:
# Returns only if seed is found
seed = get_seed_for_seed_id(seed_id,infiles,saved_seeds)
addr_nums = [int(i[9:]) for i in mmgen_addrs if i[:8] == seed_id]
opt.gen_what = 'ka'
ai = generate_addrs(seed,addr_nums,source='txsign')
d += [('{}:{}'.format(seed_id,e.idx),e.addr,e.wif) for e in ai.addrdata]
addr_idxs = AddrIdxList(idx_list=[int(i[9:]) for i in mmgen_addrs if i[:8] == seed_id])
d += KeyAddrList(seed=seed,addr_idxs=addr_idxs,do_chksum=False).flat_list()
return d
def add_keys(tx,src,infiles=None,saved_seeds=None,keyaddr_list=None):
need_keys = [e for e in getattr(tx,src) if e.mmid and not e.have_wif]
if not need_keys: return []
desc,m1 = ('key-address file','From key-address file:') if keyaddr_list else \
('seed(s)','Generated from seed:')
qmsg('Checking {} -> BTC address mappings for {} (from {})'.format(pnm,src,desc))
d = keyaddr_list.flat_list() if keyaddr_list else \
generate_keys_for_mmgen_addrs([e.mmid for e in need_keys],infiles,saved_seeds)
new_keys = []
for e in need_keys:
for f in d:
if f.mmid == e.mmid:
if f.addr == e.addr:
e.have_wif = True
if src == 'inputs':
new_keys.append(f.wif)
else:
die(3,wmsg['mapping_error'] % (m1,f.mmid,f.addr,'tx file:',e.mmid,e.addr))
if new_keys:
vmsg('Added %s wif key%s from %s' % (len(new_keys),suf(new_keys,'k'),desc))
return new_keys
# # function unneeded - use bitcoin-cli walletdump instead
# def sign_tx_with_bitcoind_wallet(c,tx,tx_num_str,keys):
# ok = tx.sign(c,tx_num_str,keys) # returns false on failure
@ -171,86 +190,6 @@ def generate_keys_for_mmgen_addrs(mmgen_addrs,infiles,saved_seeds):
#
# return ok
#
def missing_keys_errormsg(addrs):
Msg("""
A key file must be supplied (or use the '--use-wallet-dat' option)
for the following non-{pnm} address{suf}:\n {l}""".format(
pnm=pnm, suf=suf(addrs,'a'), l='\n '.join(addrs)).strip())
def parse_mmgen_keyaddr_file():
from mmgen.addr import AddrInfo
ai = AddrInfo(opt.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(key_data):
fn = opt.keys_from_file
from mmgen.crypto import mmgen_decrypt_file_maybe
dec = mmgen_decrypt_file_maybe(fn,'non-{} keylist file'.format(pnm))
# Key list could be bitcoind dump, so remove first space and everything following
keys_all = set([line.split()[0] for line in remove_comments(dec.splitlines())]) # DOS-safe
dmsg(repr(keys_all))
ka_keys = [d[k][1] for k in key_data['kafile']]
keys = [k for k in keys_all if k not in ka_keys]
removed = len(keys_all) - len(keys)
if removed:
vmsg(wmsg['removed_dups'] % (removed,suf(removed,'k')))
addrs = []
wif2addr_f = get_wif2addr_f()
for n,k in enumerate(keys,1):
qmsg_r('\rGenerating addresses from keylist: %s/%s' % (n,len(keys)))
addrs.append(wif2addr_f(k))
qmsg('\rGenerated addresses from keylist: %s/%s ' % (n,len(keys)))
return dict(zip(addrs,keys))
# Check inputs and outputs maps against key-address file, deleting entries:
def check_maps_from_kafile(io_map,desc,kadata,return_keys=False):
if not kadata: return []
qmsg('Checking {pnm} -> BTC address mappings for {w}s (from key-address file)'.format(pnm=pnm,w=desc))
ret = []
for k in io_map.keys():
if k in kadata:
if kadata[k][0] == io_map[k]:
del io_map[k]
ret += [kadata[k][1]]
else:
kl,il = 'key-address file:','tx file:'
die(2,wmsg['mm2btc_mapping_error']%(kl,k,kadata[k][0],il,k,io_map[k]))
if ret: vmsg('Removed %s address%s from %ss map' % (len(ret),suf(ret,'a'),desc))
if return_keys:
vmsg('Added %s wif key%s from %ss map' % (len(ret),suf(ret,'k'),desc))
return ret
# Check inputs and outputs maps against values generated from seeds
def check_maps_from_seeds(io_map,desc,infiles,saved_seeds,return_keys=False):
if not io_map: return []
qmsg('Checking {pnm} -> BTC address mappings for {w}s (from seed(s))'.format(
pnm=pnm,w=desc))
d = generate_keys_for_mmgen_addrs(io_map.keys(),infiles,saved_seeds)
# 0=mmaddr 1=addr 2=wif
m = dict([(e[0],e[1]) for e in d])
for a,b in zip(sorted(m),sorted(io_map)):
if a != b:
al,bl = 'generated seed:','tx file:'
die(3,wmsg['mm2btc_mapping_error'] % (al,a,m[a],bl,b,io_map[b]))
if return_keys:
vmsg('Added %s wif key%s from seeds' % (len(d),suf(d,'k')))
return [e[2] for e in d]
def get_keys_from_keylist(kldata,addrs):
ret = []
for addr in addrs[:]:
if addr in kldata:
ret += [kldata[addr]]
addrs.remove(addr)
vmsg('Added %s wif key%s from user-supplied keylist' %
(len(ret),suf(ret,'k')))
return ret
# main(): execution begins here
@ -262,22 +201,27 @@ for i in infiles: check_infile(i)
c = bitcoin_connection()
saved_seeds = {}
tx_files = [i for i in infiles if get_extension(i) == g.rawtx_ext]
seed_files = [i for i in infiles if get_extension(i) in g.seedfile_exts]
tx_files = [i for i in infiles if get_extension(i) == MMGenTX.raw_ext]
seed_files = [i for i in infiles if get_extension(i) in SeedSource.get_extensions()]
if not tx_files:
die(1,'You must specify a raw transaction file!')
if not (seed_files or opt.mmgen_keys_from_file or opt.keys_from_file or opt.use_wallet_dat):
if not (seed_files or opt.mmgen_keys_from_file or opt.keys_from_file): # or opt.use_wallet_dat):
die(1,'You must specify a seed or key source!')
if not opt.info and not opt.terse_info:
do_license_msg(immed=True)
key_data = { 'kafile':{}, 'klfile':{} }
kal,kl = None,None
if opt.mmgen_keys_from_file:
key_data['kafile'] = parse_mmgen_keyaddr_file() or {}
kal = KeyAddrList(opt.mmgen_keys_from_file)
if opt.keys_from_file:
key_data['klfile'] = parse_keylist(key_data) or {}
l = get_lines_from_file(opt.keys_from_file,'key-address data',trim_comments=True)
kl = KeyAddrList(keylist=l)
if kal: kl.remove_dups(kal,key='wif')
kl.generate_addrs()
# pp_die(kl)
tx_num_str = ''
for tx_num,tx_file in enumerate(tx_files,1):
@ -301,26 +245,26 @@ for tx_num,tx_file in enumerate(tx_files,1):
tx.view_with_prompt('View data for transaction%s?' % tx_num_str)
# Start
other_addrs = list(set([i['address'] for i in tx.inputs if not i['mmid']]))
keys = []
non_mm_addrs = tx.get_non_mmaddrs('inputs')
if non_mm_addrs:
tmp = KeyAddrList(addrlist=non_mm_addrs,do_chksum=False)
tmp.add_wifs(kl)
m = tmp.list_missing('wif')
if m: die(2,wmsg['missing_keys_error'] % (suf(m,'es'),'\n '.join(m)))
keys += tmp.get_wifs()
# should remove all elements from other_addrs
keys = get_keys_from_keylist(key_data['klfile'],other_addrs)
if opt.mmgen_keys_from_file:
keys += add_keys(tx,'inputs',keyaddr_list=kal)
add_keys(tx,'outputs',keyaddr_list=kal)
if other_addrs and not opt.use_wallet_dat:
missing_keys_errormsg(other_addrs)
sys.exit(2)
keys += add_keys(tx,'inputs',seed_files,saved_seeds)
add_keys(tx,'outputs',seed_files,saved_seeds)
imap = dict([(i['mmid'],i['address']) for i in tx.inputs if i['mmid']])
omap = dict([(tx.outputs[k][1],k) for k in tx.outputs if len(tx.outputs[k]) > 1])
sids = set([i[:8] for i in imap])
tx.delete_attrs('inputs','have_wif')
tx.delete_attrs('outputs','have_wif')
keys += check_maps_from_kafile(imap,'input',key_data['kafile'],True)
check_maps_from_kafile(omap,'output',key_data['kafile'])
keys += check_maps_from_seeds(imap,'input',seed_files,saved_seeds,True)
check_maps_from_seeds(omap,'output',seed_files,saved_seeds)
extra_sids = set(saved_seeds) - sids
extra_sids = set(saved_seeds) - tx.get_input_sids()
if extra_sids:
msg('Unused Seed ID%s: %s' %
(suf(extra_sids,'k'),' '.join(extra_sids)))

View file

@ -24,6 +24,7 @@ import os,re
from mmgen.common import *
from mmgen.seed import SeedSource
from mmgen.obj import MMGenWalletLabel
bn = os.path.basename(sys.argv[0])
invoked_as = re.sub(r'^wallet','',bn.split('-')[-1])
@ -110,6 +111,9 @@ FMT CODES:
cmd_args = opts.init(opts_data,opt_filter=opt_filter)
if opt.label:
opt.label = MMGenWalletLabel(opt.label,msg="Error in option '--label'")
if len(cmd_args) < nargs \
and not opt.hidden_incog_input_params and not opt.in_fmt:
die(1,'An input file or input format must be specified')
@ -128,10 +132,12 @@ if invoked_as in ('conv','passchg'): msg(green('Processing input wallet'))
ss_in = None if invoked_as == 'gen' \
else SeedSource(*cmd_args,passchg=invoked_as=='passchg')
if invoked_as == 'chk':
sys.exit()
if invoked_as == 'chk': sys.exit()
if invoked_as in ('conv','passchg'): msg(green('Processing output wallet'))
ss_out = SeedSource(ss=ss_in,passchg=invoked_as=='passchg')
if invoked_as == 'gen': qmsg("This wallet's Seed ID: %s" % ss_out.seed.sid.hl())
ss_out.write_to_file()

View file

@ -19,9 +19,8 @@
"""
obj.py: The MMGenObject class and methods
"""
import mmgen.globalvars as g
from decimal import Decimal
from decimal import *
lvl = 0
class MMGenObject(object):
@ -40,11 +39,12 @@ class MMGenObject(object):
def conv(v,col_w):
vret = ''
if type(v) in (str,unicode):
import mmgen.globalvars as g
if not (set(list(v)) <= set(list(g.printable))):
vret = repr(v)
else:
vret = fix_linebreaks(v,fixed_indent=0)
elif type(v) in (int,long,Decimal):
elif type(v) in (int,long,BTCAmt):
vret = str(v)
elif type(v) == dict:
sep = '\n{}{}'.format(indent,' '*4)
@ -68,8 +68,8 @@ class MMGenObject(object):
out = []
def f(k): return k[:2] != '__'
keys = filter(f, dir(self))
col_w = max(len(k) for k in keys)
keys = filter(f, self.__dict__.keys())
col_w = max(len(k) for k in keys) if keys else 1
fs = '{}%-{}s: %s'.format(indent,col_w)
methods = [k for k in keys if repr(getattr(self,k))[:14] == '<bound method ']
@ -91,3 +91,348 @@ class MMGenObject(object):
out.append(fs % (k, conv(val,col_w)))
return repr(self) + '\n ' + '\n '.join(out)
class MMGenListItemAttr(object):
def __init__(self,name,dtype):
self.name = name
self.dtype = dtype
def __get__(self,instance,owner):
return instance.__dict__[self.name]
def __set__(self,instance,value):
# if self.name == 'mmid': print repr(instance), repr(value) # DEBUG
instance.__dict__[self.name] = globals()[self.dtype](value)
def __delete__(self,instance):
del instance.__dict__[self.name]
class MMGenListItem(MMGenObject):
addr = MMGenListItemAttr('addr','BTCAddr')
amt = MMGenListItemAttr('amt','BTCAmt')
mmid = MMGenListItemAttr('mmid','MMGenID')
label = MMGenListItemAttr('label','MMGenLabel')
attrs = ()
attrs_priv = ()
attrs_reassign = 'label',
def attr_error(self,arg):
raise AttributeError, "'{}': invalid attribute for {}".format(arg,type(self).__name__)
def set_error(self,attr,val):
raise ValueError, \
"'{}': attribute '{}' in instance of class '{}' cannot be reassigned".format(
val,attr,type(self).__name__)
attrs_base = ('attrs','attrs_priv','attrs_reassign','attrs_base','attr_error','set_error','__dict__')
def __init__(self,*args,**kwargs):
if args:
raise ValueError, 'Non-keyword args not allowed'
for k in kwargs:
if kwargs[k] != None:
setattr(self,k,kwargs[k])
def __getattribute__(self,name):
ga = object.__getattribute__
if name in ga(self,'attrs') + ga(self,'attrs_priv') + ga(self,'attrs_base'):
try:
return ga(self,name)
except:
return None
else:
self.attr_error(name)
def __setattr__(self,name,val):
if name in (self.attrs + self.attrs_priv + self.attrs_base):
if getattr(self,name) == None or name in self.attrs_reassign:
object.__setattr__(self,name,val)
else:
# object.__setattr__(self,name,val) # DEBUG
self.set_error(name,val)
else:
self.attr_error(name)
def __delattr__(self,name):
if name in (self.attrs + self.attrs_priv + self.attrs_base):
try: # don't know why this is necessary
object.__delattr__(self,name)
except:
pass
else:
self.attr_error(name)
class InitErrors(object):
@staticmethod
def arg_chk(cls,on_fail):
assert on_fail in ('die','return','silent','raise'),"'on_fail' in class %s" % cls.__name__
@staticmethod
def init_fail(m,on_fail,silent=False):
if silent: m = ''
from mmgen.util import die,msg
if on_fail == 'die': die(1,m)
elif on_fail == 'return':
if m: msg(m)
return None
elif on_fail == 'silent': return None
elif on_fail == 'raise': raise ValueError,m
class AddrIdx(int,InitErrors):
max_digits = 7
def __new__(cls,num,on_fail='die'):
cls.arg_chk(cls,on_fail)
try:
assert type(num) is not float
me = int.__new__(cls,num)
except:
m = "'%s': value cannot be converted to addr idx" % num
else:
if len(str(me)) > cls.max_digits:
m = "'%s': too many digits in addr idx" % num
elif me < 1:
m = "'%s': addr idx cannot be less than one" % num
else:
return me
return cls.init_fail(m,on_fail)
class AddrIdxList(list,InitErrors):
max_len = 1000000
def __init__(self,fmt_str=None,idx_list=None,on_fail='die',sep=','):
self.arg_chk(type(self),on_fail)
assert fmt_str or idx_list
if idx_list:
return list.__init__(self,sorted(set(idx_list)))
elif fmt_str:
ret,fs = [],"'%s': value cannot be converted to addr idx"
from mmgen.util import msg
for i in (fmt_str.split(sep)):
j = i.split('-')
if len(j) == 1:
idx = AddrIdx(i,on_fail='return')
if not idx: break
ret.append(idx)
elif len(j) == 2:
beg = AddrIdx(j[0],on_fail='return')
if not beg: break
end = AddrIdx(j[1],on_fail='return')
if not beg: break
if end < beg:
msg(fs % "%s-%s (invalid range)" % (beg,end)); break
ret.extend([AddrIdx(x) for x in range(beg,end+1)])
else:
msg((fs % i) + ' list'); break
else:
return list.__init__(self,sorted(set(ret))) # fell off end of loop - success
return self.init_fail(fs % err,on_fail,silent=True)
class Hilite(object):
color = 'red'
color_always = False
width = 0
trunc_ok = True
@classmethod
def fmtc(cls,s,width=None,color=False,encl='',trunc_ok=None):
if width == None: width = cls.width
if trunc_ok == None: trunc_ok = cls.trunc_ok
assert width > 0
assert type(encl) is str and len(encl) in (0,2)
a,b = list(encl) if encl else ('','')
if trunc_ok and len(s) > width: s = s[:width]
return cls.colorize((a+s+b).ljust(width),color=color)
def fmt(self,width=None,color=False,encl='',trunc_ok=None):
if width == None: width = self.width
if trunc_ok == None: trunc_ok = self.trunc_ok
return self.fmtc(self,width=width,color=color,encl=encl,trunc_ok=trunc_ok)
@classmethod
def hlc(cls,s,color=True):
return cls.colorize(s,color=color)
def hl(self,color=True):
return self.colorize(self,color=color)
def __str__(self):
return self.colorize(self,color=False)
@classmethod
def colorize(cls,s,color=True):
import mmgen.globalvars as g
from mmgen.util import red,blue,green,yellow,pink,cyan,gray,orange,magenta
return locals()[cls.color](s) if (color or cls.color_always) and g.color else s
class BTCAmt(Decimal,Hilite,InitErrors):
color = 'yellow'
max_prec = 8
max_amt = 21000000
def __new__(cls,num,on_fail='die'):
cls.arg_chk(cls,on_fail)
try:
me = Decimal.__new__(cls,str(num))
except:
m = "'%s': value cannot be converted to decimal" % num
else:
if me.normalize().as_tuple()[-1] < -cls.max_prec:
m = "'%s': too many decimal places in BTC amount" % num
elif me > cls.max_amt:
m = "'%s': BTC amount too large (>%s)" % (num,cls.max_amt)
# elif me.as_tuple()[0]:
# m = "'%s': BTC amount cannot be negative" % num
else:
return me
return cls.init_fail(m,on_fail)
@classmethod
def fmtc(cls):
raise NotImplemented
def fmt(self,fs='3.8',color=False,suf=''):
s = self.__str__(color=False)
if '.' in fs:
p1,p2 = [int(i) for i in fs.split('.',1)]
ss = s.split('.',1)
if len(ss) == 2:
a,b = ss
ret = a.rjust(p1) + '.' + (b+suf).ljust(p2+len(suf))
else:
ret = s.rjust(p1) + suf + ' ' * (p2+1)
else:
ret = s.ljust(int(fs))
return self.colorize(ret,color=color)
def hl(self,color=True):
return self.__str__(color=color)
def __str__(self,color=False): # format simply, no exponential notation
if int(self) == self:
ret = str(int(self))
else:
ret = self.normalize().__format__('f')
return self.colorize(ret,color=color)
def __repr__(self):
return "{}('{}')".format(type(self).__name__,self.__str__())
def __add__(self,other,context=None):
return type(self)(Decimal.__add__(self,other,context))
__radd__ = __add__
def __sub__(self,other,context=None):
return type(self)(Decimal.__sub__(self,other,context))
def __mul__(self,other,context=None):
return type(self)('{:0.8f}'.format(Decimal.__mul__(self,Decimal(other),context)))
def __div__(self,other,context=None):
return type(self)('{:0.8f}'.format(Decimal.__div__(self,Decimal(other),context)))
def __neg__(self,other,context=None):
return type(self)(Decimal.__neg__(self,other,context))
class BTCAddr(str,Hilite,InitErrors):
color = 'cyan'
width = 34
def __new__(cls,s,on_fail='die'):
cls.arg_chk(cls,on_fail)
me = str.__new__(cls,s)
from mmgen.bitcoin import verify_addr
if verify_addr(s):
return me
else:
m = "'%s': value is not a Bitcoin address" % s
return cls.init_fail(m,on_fail)
def fmt(self,width=width,color=False):
return self.fmtc(self,width=width,color=color)
@classmethod
def fmtc(cls,s,width=width,color=False):
if width >= len(s):
s = s.ljust(width)
else:
s = s[:width-2] + '..'
return cls.colorize(s,color=color)
class SeedID(str,Hilite,InitErrors):
color = 'blue'
width = 8
trunc_ok = False
def __new__(cls,seed=None,sid=None,on_fail='die'):
cls.arg_chk(cls,on_fail)
assert seed or sid
if seed:
from mmgen.seed import Seed
from mmgen.util import make_chksum_8
assert type(seed) == Seed
return str.__new__(cls,make_chksum_8(seed.get_data()))
elif sid:
from string import hexdigits
assert len(sid) == cls.width and set(sid) <= set(hexdigits.upper())
return str.__new__(cls,sid)
m = "'%s': value cannot be converted to SeedID" % s
return cls.init_fail(m,on_fail)
class MMGenID(str,Hilite,InitErrors):
color = 'orange'
width = 0
trunc_ok = False
def __new__(cls,s,on_fail='die'):
cls.arg_chk(cls,on_fail)
s = str(s)
if ':' in s:
a,b = s.split(':',1)
sid = SeedID(sid=a,on_fail='return')
if sid:
idx = AddrIdx(b,on_fail='return')
if idx:
return str.__new__(cls,'%s:%s' % (sid,idx))
m = "'%s': value cannot be converted to MMGenID" % s
return cls.init_fail(m,on_fail)
class MMGenLabel(unicode,Hilite,InitErrors):
color = 'pink'
allowed = u''
max_len = 0
desc = 'label'
def __new__(cls,s,on_fail='die',msg=None):
cls.arg_chk(cls,on_fail)
try:
s = s.decode('utf8').strip()
except:
m = "'%s: value is not a valid UTF-8 string" % s
else:
if len(s) > cls.max_len:
m = '%s too long (>%s symbols)' % (cls.desc.capitalize(),cls.max_len)
elif cls.allowed and not set(list(s)).issubset(set(list(cls.allowed))):
m = '%s contains non-permitted symbols: %s' % (cls.desc.capitalize(),
' '.join(set(list(s)) - set(list(cls.allowed))))
else:
return unicode.__new__(cls,s)
return cls.init_fail((msg+'\n' if msg else '') + m,on_fail)
class MMGenWalletLabel(MMGenLabel):
max_len = 48
allowed = [chr(i+32) for i in range(95)]
desc = 'wallet label'
class MMGenAddrLabel(MMGenLabel):
max_len = 32
desc = 'address label'
class MMGenTXLabel(MMGenLabel):
max_len = 72
desc = 'transaction label'

View file

@ -230,14 +230,10 @@ def check_opts(usr_opts): # Returns false if any check fails
if key == 'outdir':
check_outdir(val) # exits on error
elif key == 'label':
if not is_mmgen_wallet_label(val):
msg("Illegal value for option '%s': '%s'" % (fmt_opt(key),val))
return False
# NEW
# # NEW
elif key in ('in_fmt','out_fmt'):
from mmgen.seed import SeedSource,IncogWallet,Brainwallet,IncogWalletHidden
sstype = SeedSource.fmt_code_to_sstype(val)
sstype = SeedSource.fmt_code_to_type(val)
if not sstype:
return opt_unrecognized(key,val,'format code')
if key == 'out_fmt':

View file

@ -78,17 +78,17 @@ class BitcoinRPCConnection(object):
dmsg('=== rpc.py debug ===')
dmsg(' RPC POST data ==> %s\n' % p)
from decimal import Decimal
class JSONDecEncoder(json.JSONEncoder):
from mmgen.obj import BTCAmt
class MyJSONEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, Decimal):
if isinstance(obj, BTCAmt):
return str(obj)
return json.JSONEncoder.default(self, obj)
# pp_msg(json.dumps(p,cls=JSONDecEncoder))
# pp_msg(json.dumps(p,cls=MyJSONEncoder))
try:
c.request('POST', '/', json.dumps(p,cls=JSONDecEncoder), {
c.request('POST', '/', json.dumps(p,cls=MyJSONEncoder), {
'Host': self.host,
'Authorization': 'Basic ' + base64.b64encode(self.auth_str)
})
@ -112,7 +112,8 @@ class BitcoinRPCConnection(object):
if not r2:
return die_maybe(r,2,'Error: empty reply')
r3 = json.loads(r2.decode('utf8'), parse_float=decimal.Decimal)
from decimal import Decimal
r3 = json.loads(r2.decode('utf8'), parse_float=Decimal)
ret = []
for resp in r3 if cf['batch'] else [r3]:

View file

@ -37,7 +37,6 @@ def check_usr_seed_len(seed_len):
"doesn't match seed length of source (%s)"
die(1, m % (opt.seed_len,seed_len))
class Seed(MMGenObject):
def __init__(self,seed_bin=None):
if not seed_bin:
@ -48,9 +47,12 @@ class Seed(MMGenObject):
self.data = seed_bin
self.hexdata = hexlify(seed_bin)
self.sid = make_chksum_8(seed_bin)
self.sid = SeedID(seed=self)
self.length = len(seed_bin) * 8
def get_data(self):
return self.data
class SeedSource(MMGenObject):
@ -64,21 +66,17 @@ class SeedSource(MMGenObject):
class SeedSourceData(MMGenObject): pass
def __new__(cls,fn=None,ss=None,seed=None,
ignore_in_fmt=False,passchg=False):
def __new__(cls,fn=None,ss=None,seed=None,ignore_in_fmt=False,passchg=False):
def die_on_opt_mismatch(opt,sstype):
opt_sstype = cls.fmt_code_to_sstype(opt)
opt_sstype = cls.fmt_code_to_type(opt)
compare_or_die(
opt_sstype.__name__, 'input format requested on command line',
sstype.__name__, 'input file format'
)
if ss:
if passchg:
sstype = ss.__class__
else:
sstype = cls.fmt_code_to_sstype(opt.out_fmt)
sstype = ss.__class__ if passchg else cls.fmt_code_to_type(opt.out_fmt)
me = super(cls,cls).__new__(sstype or Wallet) # default: Wallet
me.seed = ss.seed
me.ss_in = ss
@ -86,32 +84,28 @@ class SeedSource(MMGenObject):
elif fn or opt.hidden_incog_input_params:
if fn:
f = Filename(fn)
sstype = cls.ext_to_sstype(f.ext)
else:
fn = opt.hidden_incog_input_params.split(',')[0]
f = Filename(fn,ftype='hincog')
sstype = cls.fmt_code_to_sstype('hincog')
f = Filename(fn,ftype=IncogWalletHidden)
if opt.in_fmt and not ignore_in_fmt:
die_on_opt_mismatch(opt.in_fmt,sstype)
me = super(cls,cls).__new__(sstype)
die_on_opt_mismatch(opt.in_fmt,f.ftype)
me = super(cls,cls).__new__(f.ftype)
me.infile = f
me.op = ('old','pwchg_old')[bool(passchg)]
elif opt.in_fmt: # Input format
sstype = cls.fmt_code_to_sstype(opt.in_fmt)
sstype = cls.fmt_code_to_type(opt.in_fmt)
me = super(cls,cls).__new__(sstype)
me.op = ('old','pwchg_old')[bool(passchg)]
else: # Called with no inputs - initialize with random seed
sstype = cls.fmt_code_to_sstype(opt.out_fmt)
sstype = cls.fmt_code_to_type(opt.out_fmt)
me = super(cls,cls).__new__(sstype or Wallet) # default: Wallet
me.seed = Seed(seed_bin=seed or None)
me.op = 'new'
# die(1,me.seed.sid.hl()) # DEBUG
return me
def __init__(self,fn=None,ss=None,seed=None,
ignore_in_fmt=False,passchg=False):
def __init__(self,fn=None,ss=None,seed=None,ignore_in_fmt=False,passchg=False):
self.ssdata = self.SeedSourceData()
self.msg = {}
@ -135,7 +129,7 @@ class SeedSource(MMGenObject):
self._decrypt_retry()
m = ('',', seed length %s' % self.seed.length)[self.seed.length!=256]
qmsg('Valid %s for Seed ID %s%s' % (self.desc,self.seed.sid,m))
qmsg('Valid %s for Seed ID %s%s' % (self.desc,self.seed.sid.hl(),m))
def _get_data(self):
if hasattr(self,'infile'):
@ -162,36 +156,36 @@ class SeedSource(MMGenObject):
die(2,'Passphrase from password file, so exiting')
msg('Trying again...')
subclasses = []
@classmethod
def get_subclasses(cls):
if not hasattr(cls,'subclasses'):
gl = globals()
setattr(cls,'subclasses',
[gl[k] for k in gl if type(gl[k]) == type and issubclass(gl[k],cls)])
return cls.subclasses
@classmethod
def _get_subclasses(cls):
if cls.subclasses: return cls.subclasses
ret,gl = [],globals()
for c in [gl[k] for k in gl]:
try:
if issubclass(c,cls):
ret.append(c)
except:
pass
cls.subclasses = ret
return ret
def get_subclasses_str(cls):
def GetSubclassesTree(cls):
return ''.join([c.__name__ +' '+ GetSubclassesTree(c) for c in cls.__subclasses__()])
return GetSubclassesTree(cls)
@classmethod
def fmt_code_to_sstype(cls,fmt_code):
def get_extensions(cls):
return [s.ext for s in cls.get_subclasses() if hasattr(s,'ext')]
@classmethod
def fmt_code_to_type(cls,fmt_code):
if not fmt_code: return None
for c in cls._get_subclasses():
for c in cls.get_subclasses():
if hasattr(c,'fmt_codes') and fmt_code in c.fmt_codes:
return c
return None
@classmethod
def ext_to_sstype(cls,ext):
def ext_to_type(cls,ext):
if not ext: return None
for c in cls._get_subclasses():
for c in cls.get_subclasses():
if hasattr(c,'ext') and ext == c.ext:
return c
return None
@ -199,7 +193,7 @@ class SeedSource(MMGenObject):
@classmethod
def format_fmt_codes(cls):
d = [(c.__name__,('.'+c.ext if c.ext else c.ext),','.join(c.fmt_codes))
for c in cls._get_subclasses()
for c in cls.get_subclasses()
if hasattr(c,'fmt_codes')]
w = max([len(a) for a,b,c in d])
ret = ['{:<{w}} {:<9} {}'.format(a,b,c,w=w) for a,b,c in [
@ -365,6 +359,8 @@ class Mnemonic (SeedSourceUnenc):
}
mn_base = 1626
wordlists = sorted(wl_checksums)
dfl_wordlist = 'electrum'
# dfl_wordlist = 'tirosh'
@staticmethod
def _mn2hex_pad(mn): return len(mn) * 8 / 3
@ -399,7 +395,7 @@ class Mnemonic (SeedSourceUnenc):
@classmethod
def get_wordlist(cls,wordlist=None):
wordlist = wordlist or g.default_wordlist
wordlist = wordlist or cls.dfl_wordlist
if wordlist not in cls.wordlists:
die(1,"'%s': invalid wordlist. Valid choices: '%s'" %
(wordlist,"' '".join(cls.wordlists)))
@ -536,28 +532,31 @@ class Wallet (SeedSourceEnc):
ext = 'mmdat'
def _get_label_from_user(self,old_lbl=''):
d = ("to reuse the label '%s'" % old_lbl) if old_lbl else 'for no label'
d = ("to reuse the label '%s'" % old_lbl.hl()) if old_lbl else 'for no label'
p = 'Enter a wallet label, or hit ENTER %s: ' % d
while True:
ret = my_raw_input(p)
msg_r(p)
ret = my_raw_input('')
if ret:
if is_mmgen_wallet_label(ret):
self.ssdata.label = ret; return ret
self.ssdata.label = MMGenWalletLabel(ret,on_fail='return')
if self.ssdata.label:
break
else:
msg('Invalid label. Trying again...')
else:
ret = old_lbl or 'No Label'
self.ssdata.label = ret; return ret
self.ssdata.label = old_lbl or MMGenWalletLabel('No Label')
break
return self.ssdata.label
# nearly identical to _get_hash_preset() - factor?
def _get_label(self):
if hasattr(self,'ss_in') and hasattr(self.ss_in.ssdata,'label'):
old_lbl = self.ss_in.ssdata.label
if opt.keep_label:
qmsg("Reusing label '%s' at user request" % old_lbl)
qmsg("Reusing label '%s' at user request" % old_lbl.hl())
self.ssdata.label = old_lbl
elif opt.label:
qmsg("Using label '%s' requested on command line" % opt.label)
qmsg("Using label '%s' requested on command line" % opt.label.hl())
lbl = self.ssdata.label = opt.label
else: # Prompt, using old value as default
lbl = self._get_label_from_user(old_lbl)
@ -566,7 +565,7 @@ class Wallet (SeedSourceEnc):
m = ("changed to '%s'" % lbl,'unchanged')[lbl==old_lbl]
qmsg('Label %s' % m)
elif opt.label:
qmsg("Using label '%s' requested on command line" % opt.label)
qmsg("Using label '%s' requested on command line" % opt.label.hl())
self.ssdata.label = opt.label
else:
self._get_label_from_user()
@ -619,7 +618,7 @@ class Wallet (SeedSourceEnc):
if not check_master_chksum(lines,self.desc): return False
d = self.ssdata
d.label = lines[1]
d.label = MMGenWalletLabel(lines[1])
d1,d2,d3,d4,d5 = lines[2].split()
d.seed_id = d1.upper()
@ -994,7 +993,7 @@ harder to find, you're advised to choose a much larger file size than this.
else:
die(1,'Exiting at user request')
self.outfile = f = Filename(fn,ftype=self.fmt_codes[0],write=True)
f = Filename(fn,ftype=type(self),write=True)
dmsg('%s data len %s, offset %s' % (
capfirst(self.desc),d.target_data_len,d.hincog_offset))

View file

@ -162,10 +162,11 @@ def tool_usage(prog_name, command):
for line in cmd_help.split('\n'):
if ' ' + command in line:
c,h = line.split('-',1)
Msg('{}: {}'.format(c.strip(),h.strip()))
Msg('USAGE: %s %s %s' % (prog_name, command, ' '.join(cmd_data[command])))
Msg('MMGEN-TOOL {}: {}'.format(c.strip().upper(),h.strip()))
msg('USAGE: %s %s %s' % (prog_name, command, ' '.join(cmd_data[command])))
else:
Msg("'%s': no such tool command" % command)
msg("'%s': no such tool command" % command)
sys.exit(1)
def process_args(prog_name, command, cmd_args):
c_args = [[i.split(' [')[0],i.split(' [')[1][:-1]]
@ -174,61 +175,53 @@ def process_args(prog_name, command, cmd_args):
i.split(' [')[0],
[i.split(' [')[1].split('=')[0], i.split(' [')[1].split('=')[1][:-1]]
] for i in cmd_data[command] if '=' in i])
u_args = cmd_args[:len(c_args)]
u_kwargs = cmd_args[len(c_args):]
u_args = [a for a in cmd_args[:len(c_args)]]
if len(u_args) < len(c_args):
msg('%s argument%s required' % (len(c_args),suf(c_args,'k')))
msg('Command requires exactly %s non-keyword argument%s' % (len(c_args),suf(c_args,'k')))
tool_usage(prog_name,command)
sys.exit(1)
extra_args = len(cmd_args) - len(c_args)
u_kwargs = {}
if extra_args > 0:
u_kwargs = dict([a.split('=') for a in cmd_args[len(c_args):] if '=' in a])
if len(u_kwargs) != extra_args:
msg('Command requires exactly %s non-keyword argument%s'
% (len(c_args),suf(c_args,'k')))
tool_usage(prog_name,command)
if len(u_kwargs) > len(c_kwargs):
msg('Too many arguments')
msg('Command requires exactly %s keyword argument%s'
% (len(c_kwargs),suf(c_kwargs,'k')))
tool_usage(prog_name,command)
sys.exit(1)
u_kwargs = dict([a.split('=') for a in u_kwargs])
# mdie(c_args,c_kwargs,u_args,u_kwargs)
# print c_args; print c_kwargs; print u_args; print u_kwargs; sys.exit()
for k in u_kwargs:
if k not in c_kwargs:
msg("'%s': invalid keyword argument" % k)
tool_usage(prog_name,command)
if set(u_kwargs) > set(c_kwargs):
die(1,'Invalid named argument')
def convert_type(arg,arg_name,arg_type):
def conv_type(arg,arg_name,arg_type):
if arg_type == 'bool':
if arg.lower() in ('true','yes','1','on'): arg = True
elif arg.lower() in ('false','no','0','off'): arg = False
else:
msg("'%s': invalid boolean value for keyword argument" % arg)
tool_usage(prog_name,command)
try:
return __builtins__[arg_type](arg)
except:
die(1,"'%s': Invalid argument for argument %s ('%s' required)" % \
(arg, arg_name, arg_type))
def convert_to_bool_maybe(arg, arg_type):
if arg_type == 'bool':
if arg.lower() in ('true','yes','1','on'): return True
if arg.lower() in ('false','no','0','off'): return False
return arg
args = []
for i in range(len(c_args)):
arg_type = c_args[i][1]
arg = convert_to_bool_maybe(u_args[i], arg_type)
args.append(convert_type(arg,c_args[i][0],arg_type))
kwargs = {}
for k in u_kwargs:
arg_type = c_kwargs[k][0]
arg = convert_to_bool_maybe(u_kwargs[k], arg_type)
kwargs[k] = convert_type(arg,k,arg_type)
args = [conv_type(u_args[i],c_args[i][0],c_args[i][1]) for i in range(len(c_args))]
kwargs = dict([(k,conv_type(u_kwargs[k],k,c_kwargs[k][0])) for k in u_kwargs])
# mdie(args,kwargs)
return args,kwargs
# Individual cmd_data
# def help():
# Msg('Available commands:')
# for k in sorted(cmd_data.keys()):
# Msg('%-16s %s' % (k,' '.join(cmd_data[k])))
def are_equal(a,b,dtype=''):
if dtype == 'str': return a.lstrip('\0') == b.lstrip('\0')
if dtype == 'hex': return a.lstrip('0') == b.lstrip('0')
@ -387,19 +380,19 @@ def listaddresses(addrs='',minconf=1,showempty=False,pager=False,showbtcaddrs=Fa
c = bitcoin_connection()
addrs = {} # reusing variable name!
from decimal import Decimal
total = Decimal('0')
from mmgen.obj import BTCAmt
total = BTCAmt('0')
for d in c.listunspent(0):
mmaddr,comment = split2(d['account'])
if usr_addr_list and (mmaddr not in usr_addr_list): continue
if is_mmgen_addr(mmaddr) and d['confirmations'] >= minconf:
if is_mmgen_id(mmaddr) and d['confirmations'] >= minconf:
key = mmaddr.replace(':','_')
if key in addrs:
if addrs[key][2] != d['address']:
die(2,'duplicate BTC address ({}) for this MMGen address! ({})'.format(
(d['address'], addrs[key][2])))
else:
addrs[key] = [Decimal('0'),comment,d['address']]
addrs[key] = [BTCAmt('0'),comment,d['address']]
addrs[key][0] += d['amount']
total += d['amount']
@ -410,11 +403,11 @@ def listaddresses(addrs='',minconf=1,showempty=False,pager=False,showbtcaddrs=Fa
for acct in accts:
mmaddr,comment = split2(acct)
if usr_addr_list and (mmaddr not in usr_addr_list): continue
if is_mmgen_addr(mmaddr):
if is_mmgen_id(mmaddr):
key = mmaddr.replace(':','_')
if key not in addrs:
if showbtcaddrs: save_a.append([acct])
addrs[key] = [Decimal('0'),comment,'']
addrs[key] = [BTCAmt('0'),comment,'']
for acct,addr in zip(save_a,c.getaddressesbyaccount(save_a,batch=True)):
if len(addr) != 1:
@ -431,17 +424,17 @@ def listaddresses(addrs='',minconf=1,showempty=False,pager=False,showbtcaddrs=Fa
max(max(len(addrs[k][1]) for k in addrs) + 1,8) # pad 8 if no comments
)
def s_mmgen(key):
def s_mmgen(key): # TODO
return '{}:{:>0{w}}'.format(w=g.mmgen_idx_max_digits, *key.split('_'))
out = []
for k in sorted(addrs,key=s_mmgen):
if out and k.split('_')[0] != out[-1].split(':')[0]: out.append('')
baddr = ' ' + addrs[k][2] if showbtcaddrs else ''
out.append(fs % (k.replace('_',':'), baddr, addrs[k][1], normalize_btc_amt(addrs[k][0])))
out.append(fs % (k.replace('_',':'), baddr, addrs[k][1], addrs[k][0].fmt('3.0',color=1)))
o = (fs + '\n%s\nTOTAL: %s BTC') % (
'ADDRESS','','COMMENT','BALANCE', '\n'.join(out), normalize_btc_amt(total)
'ADDRESS','','COMMENT',' BALANCE', '\n'.join(out), total.hl()
)
if pager: do_pager(o)
else: Msg(o)
@ -456,21 +449,21 @@ def getbalance(minconf=1):
ma = split2(d['account'])[0]
keys = ['TOTAL']
if d['spendable']: keys += ['SPENDABLE']
if is_mmgen_addr(ma): keys += [ma.split(':')[0]]
if is_mmgen_id(ma): keys += [ma.split(':')[0]]
confs = d['confirmations']
i = (1,2)[confs >= minconf]
for key in keys:
if key not in accts: accts[key] = [Decimal('0')] * 3
if key not in accts: accts[key] = [BTCAmt('0')] * 3
for j in ([],[0])[confs==0] + [i]:
accts[key][j] += d['amount']
fs = '{:12} {:<%s} {:<%s} {:<}' % (16,16)
fs = '{:13} {} {} {}'
mc,lbl = str(minconf),'confirms'
Msg(fs.format('Wallet','Unconfirmed','<%s %s'%(mc,lbl),'>=%s %s'%(mc,lbl)))
Msg(fs.format('Wallet',
*[s.ljust(16) for s in ' Unconfirmed',' <%s %s'%(mc,lbl),' >=%s %s'%(mc,lbl)]))
for key in sorted(accts.keys()):
line = [str(normalize_btc_amt(a))+' BTC' for a in accts[key]]
Msg(fs.format(key+':', *line))
Msg(fs.format(key+':', *[a.fmt(color=True,suf=' BTC') for a in accts[key]]))
def txview(infile,pager=False,terse=False):
c = bitcoin_connection()
@ -481,23 +474,22 @@ def twview(pager=False,reverse=False,wide=False,sort='age'):
from mmgen.tw import MMGenTrackingWallet
tw = MMGenTrackingWallet()
tw.do_sort(sort,reverse=reverse)
out = tw.format(wide=wide)
out = tw.format_for_printing(color=True) if wide else tw.format_for_display()
do_pager(out) if pager else sys.stdout.write(out)
def add_label(mmaddr,label,remove=False):
if not is_mmgen_addr(mmaddr):
if not is_mmgen_id(mmaddr):
die(1,'{a}: not a valid {pnm} address'.format(pnm=pnm,a=mmaddr))
check_addr_label(label) # Exits on failure
MMGenAddrLabel(label) # Exits on failure
c = bitcoin_connection()
from mmgen.addr import AddrInfoList
btcaddr = AddrInfoList(bitcoind_connection=c).mmaddr2btcaddr(mmaddr)
from mmgen.addr import AddrData
btcaddr = AddrData(source='tw').mmaddr2btcaddr(mmaddr)
if not btcaddr:
die(1,'{pnm} address {a} not found in tracking wallet'.format(
pnm=pnm,a=mmaddr))
c = bitcoin_connection()
try:
l = ' ' + label if label else ''
c.importaddress(btcaddr,mmaddr+l,False) # addr,label,rescan,p2sh
@ -511,12 +503,12 @@ def add_label(mmaddr,label,remove=False):
def remove_label(mmaddr): add_label(mmaddr,'',remove=True)
def addrfile_chksum(infile):
from mmgen.addr import AddrInfo
AddrInfo(infile,caller='tool')
from mmgen.addr import AddrList
AddrList(infile,chksum_only=True)
def keyaddrfile_chksum(infile):
from mmgen.addr import AddrInfo
AddrInfo(infile,has_keys=True,caller='tool')
from mmgen.addr import KeyAddrList
KeyAddrList(infile,chksum_only=True)
def hexreverse(hex_str):
Msg(ba.hexlify(decode_pretty_hexdump(hex_str)[::-1]))

View file

@ -22,28 +22,70 @@ tw: Tracking wallet methods for the MMGen suite
from mmgen.common import *
from mmgen.obj import *
from mmgen.tx import parse_mmgen_label,normalize_btc_amt
from mmgen.term import get_char
class MMGenTrackingWallet(MMGenObject):
def parse_tw_acct_label(s):
ret = s.split(None,1)
if ret and MMGenID(ret[0],on_fail='silent'):
if len(ret) == 2:
return tuple(ret)
else:
return ret[0],None
else:
return None,None
class MMGenTWOutput(MMGenListItem):
attrs_reassign = 'label','skip'
attrs = 'txid','vout','amt','label','mmid','addr','confs','scriptPubKey','days','skip'
label = MMGenListItemAttr('label','MMGenAddrLabel')
class MMGenTrackingWallet(MMGenObject):
wmsg = {
'no_spendable_outputs': """
No spendable outputs found! Import addresses with balances into your
watch-only wallet using '{}-addrimport' and then re-run this program.
""".strip().format(g.proj_name)
}
def __init__(self):
if g.bogus_wallet_data: # for debugging purposes only
us_rpc = eval(get_data_from_file(g.bogus_wallet_data))
else:
us_rpc = bitcoin_connection().listunspent()
# write_data_to_file('bogus_unspent.json', repr(us), 'bogus unspent data')
# sys.exit()
sort_keys = 'address','age','amount','txid','mmaddr'
def s_address(self,i): return i['address']
def s_age(self,i): return 0 - i['confirmations']
def s_amount(self,i): return i['amount']
def s_txid(self,i): return '%s %03s' % (i['txid'],i['vout'])
def s_mmaddr(self,i):
if i['mmid']:
if not us_rpc: die(2,self.wmsg['no_spendable_outputs'])
for o in us_rpc:
o['mmid'],o['label'] = parse_tw_acct_label(o['account'])
o['days'] = int(o['confirmations'] * g.mins_per_block / (60*24))
o['amt'] = o['amount'] # TODO
o['addr'] = o['address']
o['confs'] = o['confirmations']
us = [MMGenTWOutput(**dict([(k,v) for k,v in o.items() if k in MMGenTWOutput.attrs and o[k] not in (None,'')])) for o in us_rpc]
# die(1,''.join([str(i)+'\n' for i in us]))
# die(1,''.join([pp_format(i)+'\n' for i in us_rpc]))
self.unspent = us
self.fmt_display = ''
self.fmt_print = ''
self.cols = None
self.reverse = False
self.group = False
self.show_days = True
self.show_mmid = True
self.do_sort('age')
self.total = sum([i.amt for i in self.unspent])
sort_keys = 'addr','age','amt','txid','mmid'
def s_addr(self,i): return i.addr
def s_age(self,i): return 0 - i.confs
def s_amt(self,i): return i.amt
def s_txid(self,i): return '%s %03s' % (i.txid,i.vout)
def s_mmid(self,i):
if i.mmid:
return '{}:{:>0{w}}'.format(
*i['mmid'].split(':'), w=g.mmgen_idx_max_digits)
else: return 'G' + i['comment']
*i.mmid.split(':'), w=g.mmgen_idx_max_digits)
else: return 'G' + (i.label or '')
def do_sort(self,key,reverse=None):
if key not in self.sort_keys:
@ -54,115 +96,93 @@ watch-only wallet using '{}-addrimport' and then re-run this program.
self.unspent.sort(key=getattr(self,'s_'+key),reverse=reverse)
def sort_info(self,include_group=True):
ret = ([],['reverse'])[self.reverse]
ret.append(self.sort)
if include_group and self.group and (self.sort in ('address','txid')):
ret.append('grouped')
ret = ([],['Reverse'])[self.reverse]
ret.append(self.sort.capitalize().replace('Mmid','MMGenId'))
if include_group and self.group and (self.sort in ('addr','txid','mmid')):
ret.append('Grouped')
return ret
def __init__(self):
if g.bogus_wallet_data: # for debugging purposes only
us = eval(get_data_from_file(g.bogus_wallet_data))
else:
us = bitcoin_connection().listunspent()
# write_data_to_file('bogus_unspent.json', repr(us), 'bogus unspent data')
# sys.exit()
if not us: die(2,self.wmsg['no_spendable_outputs'])
for o in us:
o['mmid'],o['comment'] = parse_mmgen_label(o['account'])
del o['account']
o['skip'] = ''
amt = str(normalize_btc_amt(o['amount']))
lfill = 3 - len(amt.split('.')[0]) if '.' in amt else 3 - len(amt)
o['amt_fmt'] = ' '*lfill + amt
o['days'] = int(o['confirmations'] * g.mins_per_block / (60*24))
self.unspent = us
self.fmt_display = ''
self.fmt_print = ''
self.cols = None
self.reverse = False
self.group = False
self.show_days = True
self.show_mmaddr = True
self.do_sort('age')
self.total = sum([i['amount'] for i in self.unspent])
def set_cols(self):
def set_term_columns(self):
from mmgen.term import get_terminal_size
while True:
self.cols = get_terminal_size()[0]
if self.cols < g.min_screen_width:
m = 'A screen at least {} characters wide is required to display the tracking wallet'
die(2,m.format(g.min_screen_width))
if self.cols >= g.min_screen_width: break
m1 = 'Screen too narrow to display the tracking wallet'
m2 = 'Please resize your screen to at least {} characters and hit ENTER '
my_raw_input(m1+'\n'+m2.format(g.min_screen_width))
def display(self):
msg(self.format_for_display())
def format(self,wide=False):
return self.format_for_printing() if wide else self.format_for_display()
def format_for_display(self):
unspent = self.unspent
total = sum([i['amount'] for i in unspent])
mmid_w = max(len(i['mmid']) for i in unspent)
self.set_cols()
max_acct_len = max([len(i['mmid']+i['comment'])+1 for i in self.unspent])
addr_w = min(34+((1+max_acct_len) if self.show_mmaddr else 0),self.cols-46)
acct_w = min(max_acct_len, max(24,int(addr_w-10)))
btaddr_w = addr_w - acct_w - 1
tx_w = max(11,min(64, self.cols-addr_w-32))
txdots = ('','...')[tx_w < 64]
fs = ' %-4s %-' + str(tx_w) + 's %-2s %-' + str(addr_w) + 's %-13s %-s'
table_hdr = fs % ('Num','TX id Vout','','Address','Amount (BTC)',
('Conf.','Age(d)')[self.show_days])
from copy import deepcopy
unsp = deepcopy(unspent)
for i in unsp: i['skip'] = ''
if self.group and (self.sort in ('address','txid')):
for a,b in [(unsp[i],unsp[i+1]) for i in range(len(unsp)-1)]:
if self.sort == 'address' and a['address'] == b['address']: b['skip'] = 'addr'
elif self.sort == 'txid' and a['txid'] == b['txid']: b['skip'] = 'txid'
unsp = [MMGenTWOutput(**i.__dict__) for i in self.unspent]
self.set_term_columns()
for i in unsp:
addr_disp = (i['address'],'|' + '.'*33)[i['skip']=='addr']
mmid_disp = (i['mmid'],'.'*len(i['mmid']))[i['skip']=='addr']
if self.show_mmaddr:
dots = ('','..')[btaddr_w < len(i['address'])]
i['addr'] = '%s%s %s' % (
addr_disp[:btaddr_w-len(dots)],
dots, (
('{:<{w}} '.format(mmid_disp,w=mmid_w) if i['mmid'] else '')
+ i['comment'])[:acct_w]
)
else:
i['addr'] = addr_disp
if i.label == None: i.label = ''
i.skip = ''
i['tx'] = ' ' * (tx_w-4) + '|...' if i['skip'] == 'txid' \
else i['txid'][:tx_w-len(txdots)]+txdots
mmid_w = max(len(i.mmid or '') for i in unsp)
max_acct_len = max([len((i.mmid or '')+i.label)+1 for i in unsp])
addr_w = min(34+((1+max_acct_len) if self.show_mmid else 0),self.cols-46) + 6
acct_w = min(max_acct_len, max(24,int(addr_w-10)))
btaddr_w = addr_w - acct_w - 1
label_w = acct_w - mmid_w - 1
tx_w = max(11,min(64, self.cols-addr_w-32))
txdots = ('','...')[tx_w < 64]
fs = ' %-4s %-' + str(tx_w) + 's %-2s %s %s %s'
table_hdr = fs % ('Num',
'TX id'.ljust(tx_w - 5) + ' Vout',
'',
BTCAddr.fmtc('Address',width=addr_w+1),
'Amt(BTC) ',
('Conf.','Age(d)')[self.show_days])
if self.group and (self.sort in ('addr','txid','mmid')):
for a,b in [(unsp[i],unsp[i+1]) for i in range(len(unsp)-1)]:
for k in ('addr','txid','mmid'):
if self.sort == k and getattr(a,k) == getattr(b,k):
b.skip = (k,'addr')[k=='mmid']
hdr_fmt = 'UNSPENT OUTPUTS (sort order: %s) Total BTC: %s'
out = [hdr_fmt % (' '.join(self.sort_info()), normalize_btc_amt(total)), table_hdr]
out += [fs % (str(n+1)+')',i['tx'],i['vout'],i['addr'],i['amt_fmt'],
i['days'] if self.show_days else i['confirmations'])
for n,i in enumerate(unsp)]
self.fmt_display = '\n'.join(out)
out = [hdr_fmt % (' '.join(self.sort_info()), self.total.hl()), table_hdr]
for n,i in enumerate(unsp):
addr_dots = '|' + '.'*33
mmid_disp = (MMGenID.hlc('.'*mmid_w) \
if i.skip=='addr' else i.mmid.fmt(width=mmid_w,color=True)) \
if i.mmid else ' ' * mmid_w
if self.show_mmid and i.mmid:
addr_out = '%s %s' % (
type(i.addr).fmtc(addr_dots,width=btaddr_w,color=True) if i.skip == 'addr' \
else i.addr.fmt(width=btaddr_w,color=True),
'{} {}'.format(mmid_disp,i.label.fmt(width=label_w,color=True))
)
else:
addr_out = type(i.addr).fmtc(addr_dots,width=addr_w,color=True) if i.skip=='addr' \
else i.addr.fmt(width=addr_w,color=True)
tx = ' ' * (tx_w-4) + '|...' if i.skip == 'txid' \
else i.txid[:tx_w-len(txdots)]+txdots
out.append(fs % (str(n+1)+')',tx,i.vout,addr_out,i.amt.fmt(color=True),
i.days if self.show_days else i.confs))
self.fmt_display = '\n'.join(out) + '\n'
return self.fmt_display
def format_for_printing(self):
def format_for_printing(self,color=False):
total = sum([i['amount'] for i in self.unspent])
fs = ' %-4s %-67s %-34s %-14s %-12s %-8s %-6s %s'
out = [fs % ('Num','Tx ID,Vout','Address','{} ID'.format(g.proj_name),
'Amount(BTC)','Conf.','Age(d)', 'Comment')]
fs = ' %-4s %-67s %s %s %s %-8s %-6s %s'
out = [fs % ('Num','Tx ID,Vout','Address'.ljust(34),'MMGen ID'.ljust(15),
'Amount(BTC)','Conf.','Age(d)', 'Label')]
for n,i in enumerate(self.unspent):
addr = '=' if i['skip'] == 'addr' and self.group else i['address']
tx = ' ' * 63 + '=' if i['skip'] == 'txid' and self.group else str(i['txid'])
s = fs % (str(n+1)+')', tx+','+str(i['vout']),addr,
i['mmid'],i['amt_fmt'].strip(),i['confirmations'],i['days'],i['comment'])
addr = '=' if i.skip == 'addr' and self.group else i.addr.fmt(color=color)
tx = ' ' * 63 + '=' if i.skip == 'txid' and self.group else str(i.txid)
s = fs % (str(n+1)+')', tx+','+str(i.vout),addr,
(i.mmid.fmt(14,color=color) if i.mmid else ''.ljust(14)),
i.amt.fmt(color=color),i.confs,i.days,i.label.hl(color=color) if i.label else '')
out.append(s.rstrip())
fs = 'Unspent outputs ({} UTC)\nSort order: {}\n\n{}\n\nTotal BTC: {}\n'
@ -170,51 +190,53 @@ watch-only wallet using '{}-addrimport' and then re-run this program.
make_timestr(),
' '.join(self.sort_info(include_group=False)),
'\n'.join(out),
normalize_btc_amt(total))
self.total.hl(color=color))
return self.fmt_print
def display_total(self):
fs = '\nTotal unspent: %s BTC (%s outputs)'
msg(fs % (normalize_btc_amt(self.total), len(self.unspent)))
msg(fs % (self.total.hl(),len(self.unspent)))
def view_and_sort(self):
from mmgen.term import do_pager
s = """
prompt = """
Sort options: [t]xid, [a]mount, a[d]dress, [A]ge, [r]everse, [M]mgen addr
Display options: show [D]ays, [g]roup, show [m]mgen addr, r[e]draw screen
""".strip()
self.display()
msg(s)
msg(prompt)
p = "('q' = quit sorting, 'p' = print to file, 'v' = pager view, 'w' = wide view): "
while True:
reply = get_char(p, immed_chars='atDdAMrgmeqpvw')
if reply == 'a': self.do_sort('amount')
if reply == 'a': self.do_sort('amt')
elif reply == 't': self.do_sort('txid')
elif reply == 'D': self.show_days = not self.show_days
elif reply == 'd': self.do_sort('address')
elif reply == 'd': self.do_sort('addr')
elif reply == 'A': self.do_sort('age')
elif reply == 'M': self.do_sort('mmaddr'); self.show_mmaddr = True
elif reply == 'M': self.do_sort('mmid'); self.show_mmid = True
elif reply == 'r': self.unspent.reverse(); self.reverse = not self.reverse
elif reply == 'g': self.group = not self.group
elif reply == 'm': self.show_mmaddr = not self.show_mmaddr
elif reply == 'e': msg("\n%s\n%s\n%s" % (self.fmt_display,s,p))
elif reply == 'm': self.show_mmid = not self.show_mmid
elif reply == 'e': msg("\n%s\n%s\n%s" % (self.fmt_display,prompt,p))
elif reply == 'q': return self.unspent
elif reply == 'p':
of = 'listunspent[%s].out' % ','.join(self.sort_info(include_group=False))
msg('')
write_data_to_file(of,self.format_for_printing(),'unspent outputs listing')
m = yellow("Data written to '%s'" % of)
msg('\n%s\n\n%s\n\n%s' % (self.fmt_display,m,s))
msg('\n%s\n%s\n\n%s' % (self.fmt_display,m,prompt))
continue
elif reply == 'v':
do_pager(self.fmt_display)
continue
elif reply == 'w':
do_pager(self.format_for_printing())
do_pager(self.format_for_printing(color=True))
continue
else:
msg('\nInvalid input')
continue
msg('\n')
self.display()
msg(s)
msg(prompt)

View file

@ -22,84 +22,15 @@ tx.py: Bitcoin transaction routines
import sys,os
from stat import *
from binascii import hexlify,unhexlify
from decimal import Decimal
from collections import OrderedDict
from binascii import unhexlify
from mmgen.common import *
from mmgen.obj import *
from mmgen.term import do_pager
def normalize_btc_amt(amt):
'''Remove exponent and trailing zeros.
'''
# to_integral() needed to keep ints > 9 from being shown in exp. notation
if is_btc_amt(amt):
return amt.quantize(Decimal(1)) if amt == amt.to_integral() else amt.normalize()
else:
die(2,'%s: not a BTC amount' % amt)
def is_btc_amt(amt):
if type(amt) is not Decimal:
msg('%s: not a decimal number' % amt)
return False
if amt.as_tuple()[-1] < -g.btc_amt_decimal_places:
msg('%s: Too many decimal places in amount' % amt)
return False
return True
def convert_to_btc_amt(amt,return_on_fail=False):
# amt must be a string!
from decimal import Decimal
try:
ret = Decimal(amt)
except:
m = '%s: amount cannot be converted to decimal' % amt
if return_on_fail:
msg(m); return False
else:
die(2,m)
dmsg('Decimal(amt): %s' % repr(amt))
if ret.as_tuple()[-1] < -g.btc_amt_decimal_places:
m = '%s: Too many decimal places in amount' % amt
if return_on_fail:
msg(m); return False
else:
die(2,m)
if ret == 0:
msg('WARNING: BTC amount is zero')
return ret
def parse_mmgen_label(s,check_label_len=False):
l = split2(s)
if not is_mmgen_addr(l[0]): return '',s
if check_label_len: check_addr_label(l[1])
return tuple(l)
def is_mmgen_seed_id(s):
import re
return re.match(r'^[0123456789ABCDEF]{8}$',s) is not None
def is_mmgen_idx(s):
try: int(s)
except: return False
return len(s) <= g.mmgen_idx_max_digits
def is_mmgen_addr(s):
seed_id,idx = split2(s,':')
return is_mmgen_seed_id(seed_id) and is_mmgen_idx(idx)
def is_btc_addr(s):
from mmgen.bitcoin import verify_addr
return verify_addr(s)
def is_mmgen_seed_id(s): return SeedID(sid=s,on_fail='silent')
def is_mmgen_idx(s): return AddrIdx(s,on_fail='silent')
def is_mmgen_id(s): return MMGenID(s,on_fail='silent')
def is_btc_addr(s): return BTCAddr(s,on_fail='silent')
def is_b58_str(s):
from mmgen.bitcoin import b58a
@ -111,7 +42,7 @@ def is_wif(s):
from mmgen.bitcoin import wiftohex
return wiftohex(s,compressed) is not False
def wiftoaddr(s):
def _wiftoaddr(s):
if s == '': return False
compressed = not s[0] == '5'
from mmgen.bitcoin import wiftohex,privnum2addr
@ -119,75 +50,51 @@ def wiftoaddr(s):
if not hex_key: return False
return privnum2addr(int(hex_key,16),compressed)
def is_valid_tx_comment(s):
try: s = s.decode('utf8')
except:
msg('Invalid transaction comment (not UTF-8)')
return False
if len(s) > g.max_tx_comment_len:
msg('Invalid transaction comment (longer than %s characters)' %
g.max_tx_comment_len)
return False
return True
def check_addr_label(label):
if len(label) > g.max_addr_label_len:
msg("'%s': overlong label (length must be <=%s)" %
(label,g.max_addr_label_len))
sys.exit(3)
for ch in label:
if ch not in g.addr_label_symbols:
msg("""
'%s': illegal character in label '%s'.
Only ASCII printable characters are permitted.
""".strip() % (ch,label))
sys.exit(3)
def wiftoaddr_keyconv(wif):
def _wiftoaddr_keyconv(wif):
if wif[0] == '5':
from subprocess import check_output
return check_output(['keyconv', wif]).split()[1]
else:
return wiftoaddr(wif)
return _wiftoaddr(wif)
def get_wif2addr_f():
if opt.no_keyconv: return wiftoaddr
if opt.no_keyconv: return _wiftoaddr
from mmgen.addr import test_for_keyconv
return (wiftoaddr,wiftoaddr_keyconv)[bool(test_for_keyconv())]
return (_wiftoaddr,_wiftoaddr_keyconv)[bool(test_for_keyconv())]
class MMGenTxInputOldFmt(MMGenListItem): # for converting old tx files only
tr = {'amount':'amt', 'address':'addr', 'confirmations':'confs','comment':'label'}
attrs = 'txid','vout','amt','label','mmid','addr','confs','scriptPubKey','wif'
attrs_priv = 'tr',
def mmaddr2btcaddr_addrdata(mmaddr,addr_data,source=''):
seed_id,idx = mmaddr.split(':')
if seed_id in addr_data:
if idx in addr_data[seed_id]:
vmsg('%s -> %s%s' % (mmaddr,addr_data[seed_id][idx][0],
' (from %s)' % source if source else ''))
return addr_data[seed_id][idx]
class MMGenTxInput(MMGenListItem):
attrs = 'txid','vout','amt','label','mmid','addr','confs','scriptPubKey','have_wif'
label = MMGenListItemAttr('label','MMGenAddrLabel')
return '',''
from mmgen.obj import *
class MMGenTxOutput(MMGenListItem):
attrs = 'txid','vout','amt','label','mmid','addr','have_wif'
label = MMGenListItemAttr('label','MMGenAddrLabel')
class MMGenTX(MMGenObject):
ext = g.rawtx_ext
ext = 'rawtx'
raw_ext = 'rawtx'
sig_ext = 'sigtx'
txid_ext = 'txid'
desc = 'transaction'
max_fee = BTCAmt('0.01')
def __init__(self,filename=None):
self.inputs = []
self.outputs = {}
self.inputs_enc = []
self.outputs = []
self.outputs_enc = []
self.change_addr = ''
self.size = 0 # size of raw serialized tx
self.fee = Decimal('0')
self.send_amt = Decimal('0') # total amt minus change
self.fee = BTCAmt('0')
self.send_amt = BTCAmt('0') # total amt minus change
self.hex = '' # raw serialized hex transaction
self.comment = ''
self.label = MMGenTXLabel('')
self.txid = ''
self.btc_txid = ''
self.timestamp = ''
@ -195,37 +102,56 @@ class MMGenTX(MMGenObject):
self.fmt_data = ''
self.blockcount = 0
if filename:
if get_extension(filename) == g.sigtx_ext:
if get_extension(filename) == self.sig_ext:
self.mark_signed()
self.parse_tx_file(filename)
def add_output(self,btcaddr,amt):
self.outputs[btcaddr] = (amt,)
def add_output(self,btcaddr,amt): # 'txid','vout','amount','label','mmid','address'
self.outputs.append(MMGenTxOutput(addr=btcaddr,amt=amt))
def del_output(self,btcaddr):
del self.outputs[btcaddr]
for i in range(len(self.outputs)):
if self.outputs[i].addr == btcaddr:
self.outputs.pop(i); return
raise ValueError
def sum_outputs(self):
return sum([self.outputs[k][0] for k in self.outputs])
return BTCAmt(sum([e.amt for e in self.outputs]))
def add_mmaddrs_to_outputs(self,ad_w,ad_f):
a = [e.addr for e in self.outputs]
d = ad_w.make_reverse_dict(a)
d.update(ad_f.make_reverse_dict(a))
for e in self.outputs:
if e.addr and e.addr in d:
e.mmid,f = d[e.addr]
if f: e.label = f
# def encode_io(self,desc):
# tr = getattr((MMGenTxOutput,MMGenTxInput)[desc=='inputs'],'tr')
# tr_rev = dict([(v,k) for k,v in tr.items()])
# return [dict([(tr_rev[e] if e in tr_rev else e,getattr(d,e)) for e in d.__dict__])
# for d in getattr(self,desc)]
#
def create_raw(self,c):
i = [{'txid':e.txid,'vout':e.vout} for e in self.inputs]
o = dict([(e.addr,e.amt) for e in self.outputs])
self.hex = c.createrawtransaction(i,o)
self.txid = make_chksum_6(unhexlify(self.hex)).upper()
# returns true if comment added or changed
def add_comment(self,infile=None):
if infile:
s = get_data_from_file(infile,'transaction comment')
if is_valid_tx_comment(s):
self.comment = s.decode('utf8').strip()
return True
else:
sys.exit(2)
self.label = MMGenTXLabel(get_data_from_file(infile,'transaction comment'))
else: # get comment from user, or edit existing comment
m = ('Add a comment to transaction?','Edit transaction comment?')[bool(self.comment)]
m = ('Add a comment to transaction?','Edit transaction comment?')[bool(self.label)]
if keypress_confirm(m,default_yes=False):
while True:
s = my_raw_input('Comment: ',insert_txt=self.comment.encode('utf8'))
if is_valid_tx_comment(s):
csave = self.comment
self.comment = s.decode('utf8').strip()
return (True,False)[csave == self.comment]
s = MMGenTXLabel(my_raw_input('Comment: ',insert_txt=self.label))
if s:
lbl_save = self.label
self.label = s
return (True,False)[lbl_save == self.label]
else:
msg('Invalid comment')
return False
@ -237,53 +163,57 @@ class MMGenTX(MMGenObject):
def calculate_size_and_fee(self,fee_estimate):
self.size = len(self.inputs)*180 + len(self.outputs)*34 + 10
if fee_estimate:
ftype,fee = 'Calculated','{:.8f}'.format(fee_estimate*opt.tx_fee_adj*self.size / 1024)
ftype,fee = 'Calculated',fee_estimate*opt.tx_fee_adj*self.size / 1024
else:
ftype,fee = 'User-selected',opt.tx_fee
ufee = None
if not keypress_confirm('{} TX fee: {} BTC. OK?'.format(ftype,fee),default_yes=True):
if not keypress_confirm('{} TX fee is {} BTC. OK?'.format(ftype,fee.hl()),default_yes=True):
while True:
ufee = my_raw_input('Enter transaction fee: ')
if convert_to_btc_amt(ufee,return_on_fail=True):
if Decimal(ufee) > g.max_tx_fee:
msg('{} BTC: fee too large (maximum fee: {} BTC)'.format(ufee,g.max_tx_fee))
if BTCAmt(ufee,on_fail='return'):
ufee = BTCAmt(ufee)
if ufee > self.max_fee:
msg('{} BTC: fee too large (maximum fee: {} BTC)'.format(ufee,self.max_fee))
else:
fee = ufee
break
self.fee = convert_to_btc_amt(fee)
self.fee = fee
vmsg('Inputs:{} Outputs:{} TX size:{}'.format(
len(self.inputs),len(self.outputs),self.size))
vmsg('Fee estimate: {} (1024 bytes, {} confs)'.format(fee_estimate,opt.tx_confs))
m = ('',' (after %sx adjustment)' % opt.tx_fee_adj)[opt.tx_fee_adj != 1 and not ufee]
vmsg('TX fee: {}{}'.format(self.fee,m))
def copy_inputs(self,source):
copy_keys = 'txid','vout','amount','comment','mmid','address',\
'confirmations','scriptPubKey'
self.inputs = [dict([(k,d[k] if k in d else '') for k in copy_keys]) for d in source]
# inputs methods
def list_wifs(self,desc,mmaddrs_only=False):
return [e.wif for e in getattr(self,desc) if e.mmid] if mmaddrs_only \
else [e.wif for e in getattr(self,desc)]
def delete_attrs(self,desc,attr):
for e in getattr(self,desc):
if hasattr(e,attr): delattr(e,attr)
def decode_io(self,desc,data):
io = (MMGenTxOutput,MMGenTxInput)[desc=='inputs']
return [io(**dict([(k,d[k]) for k in io.attrs
if k in d and d[k] not in ('',None)])) for d in data]
def decode_io_oldfmt(self,data):
io = MMGenTxInputOldFmt
tr_rev = dict([(v,k) for k,v in io.tr.items()])
copy_keys = [tr_rev[k] if k in tr_rev else k for k in io.attrs]
return [io(**dict([(io.tr[k] if k in io.tr else k,d[k])
for k in copy_keys if k in d and d[k] != ''])) for d in data]
def copy_inputs_from_tw(self,data):
self.inputs = self.decode_io('inputs',[e.__dict__ for e in data])
def get_input_sids(self):
return set([e.mmid[:8] for e in self.inputs if e.mmid])
def sum_inputs(self):
return sum([i['amount'] for i in self.inputs])
def create_raw(self,c):
o = dict([(k,v[0]) for k,v in self.outputs.items()])
self.hex = c.createrawtransaction(self.inputs,o)
self.txid = make_chksum_6(unhexlify(self.hex)).upper()
# def make_b2m_map(self,ail_w,ail_f):
# d = dict([(d['address'], (d['mmid'],d['comment']))
# for d in self.inputs if d['mmid']])
# d = ail_w.make_reverse_dict(self.outputs.keys())
# d.update(ail_f.make_reverse_dict(self.outputs.keys()))
# self.b2m_map = d
def add_mmaddrs_to_outputs(self,ail_w,ail_f):
d = ail_w.make_reverse_dict(self.outputs.keys())
d.update(ail_f.make_reverse_dict(self.outputs.keys()))
for k in self.outputs:
if k in d:
self.outputs[k] += d[k]
return sum([e.amt for e in self.inputs])
def add_timestamp(self):
self.timestamp = make_timestamp()
@ -301,20 +231,28 @@ class MMGenTX(MMGenObject):
(self.blockcount or 'None')
),
self.hex,
repr(self.inputs),
repr(self.outputs)
) + ((b58encode(self.comment.encode('utf8')),) if self.comment else ())
repr([e.__dict__ for e in self.inputs]),
repr([e.__dict__ for e in self.outputs])
) + ((b58encode(self.label),) if self.label else ())
self.chksum = make_chksum_6(' '.join(lines))
self.fmt_data = '\n'.join((self.chksum,) + lines)+'\n'
# return true or false, don't exit
def sign(self,c,tx_num_str,keys=None):
if keys:
def get_non_mmaddrs(self,desc):
return list(set([i.addr for i in getattr(self,desc) if not i.mmid]))
# return true or false, don't exit
def sign(self,c,tx_num_str,keys):
if not keys:
msg('No keys. Cannot sign!')
return False
qmsg('Passing %s key%s to bitcoind' % (len(keys),suf(keys,'k')))
dmsg('Keys:\n %s' % '\n '.join(keys))
sig_data = [dict([(k,d[k]) for k in 'txid','vout','scriptPubKey']) for d in self.inputs]
sig_data = [dict([(k,getattr(d,k)) for k in 'txid','vout','scriptPubKey'])
for d in self.inputs]
dmsg('Sig data:\n%s' % pp_format(sig_data))
dmsg('Raw hex:\n%s' % self.hex)
@ -333,7 +271,7 @@ class MMGenTX(MMGenObject):
def mark_signed(self):
self.desc = 'signed transaction'
self.ext = g.sigtx_ext
self.ext = self.sig_ext
def check_signed(self,c):
d = c.decoderawtransaction(self.hex)
@ -351,7 +289,7 @@ class MMGenTX(MMGenObject):
msg(m % self.btc_txid)
def write_txid_to_file(self,ask_write=False,ask_write_default_yes=True):
fn = '%s[%s].%s' % (self.txid,self.send_amt,g.txid_ext)
fn = '%s[%s].%s' % (self.txid,self.send_amt,self.txid_ext)
write_data_to_file(fn,self.btc_txid+'\n','transaction ID',
ask_write=ask_write,
ask_write_default_yes=ask_write_default_yes)
@ -392,49 +330,45 @@ class MMGenTX(MMGenObject):
'Transaction {} - {} BTC - {} UTC\n'
)[bool(terse)]
out = fs.format(self.txid,self.send_amt,self.timestamp)
out = fs.format(self.txid,self.send_amt.hl(),self.timestamp)
enl = ('\n','')[bool(terse)]
if self.comment:
out += 'Comment: %s\n%s' % (self.comment,enl)
if self.label:
out += 'Comment: %s\n%s' % (self.label.hl(),enl)
out += 'Inputs:\n' + enl
nonmm_str = 'non-{pnm} address'.format(pnm=g.proj_name)
for n,i in enumerate(self.inputs):
nonmm_str = '(non-{pnm} address)'.format(pnm=g.proj_name)
# for i in self.inputs: print i #DEBUG
for n,e in enumerate(self.inputs):
if blockcount:
confirmations = i['confirmations'] + blockcount - self.blockcount
days = int(confirmations * g.mins_per_block / (60*24))
if not i['mmid']:
i['mmid'] = nonmm_str
mmid_fmt = ' ({:>{l}})'.format(i['mmid'],l=34-len(i['address']))
confs = e.confs + blockcount - self.blockcount
days = int(confs * g.mins_per_block / (60*24))
mmid_fmt = e.mmid.fmt(width=len(nonmm_str),encl='()',color=True) if e.mmid \
else MMGenID.hlc(nonmm_str)
if terse:
out += ' %s: %-54s %s BTC' % (n+1,i['address'] + mmid_fmt,
normalize_btc_amt(i['amount']))
out += '%3s: %s %s %s BTC' % (n+1, e.addr.fmt(color=True),mmid_fmt, e.amt.hl())
else:
for d in (
(n+1, 'tx,vout:', '%s,%s' % (i['txid'], i['vout'])),
('', 'address:', i['address'] + mmid_fmt),
('', 'comment:', i['comment']),
('', 'amount:', '%s BTC' % normalize_btc_amt(i['amount'])),
('', 'confirmations:', '%s (around %s days)' % (confirmations,days) if blockcount else '')
(n+1, 'tx,vout:', '%s,%s' % (e.txid, e.vout)),
('', 'address:', e.addr.fmt(color=True) + ' ' + mmid_fmt),
('', 'comment:', e.label.hl() if e.label else ''),
('', 'amount:', '%s BTC' % e.amt.hl()),
('', 'confirmations:', '%s (around %s days)' % (confs,days) if blockcount else '')
):
if d[2]: out += ('%3s %-8s %s\n' % d)
out += '\n'
out += 'Outputs:\n' + enl
for n,k in enumerate(self.outputs):
btcaddr = k
v = self.outputs[k]
btc_amt,mmid,comment = (v[0],'Non-MMGen address','') if len(v) == 1 else v
mmid_fmt = ' ({:>{l}})'.format(mmid,l=34-len(btcaddr))
for n,e in enumerate(self.outputs):
mmid_fmt = e.mmid.fmt(width=len(nonmm_str),encl='()',color=True) if e.mmid \
else MMGenID.hlc(nonmm_str)
if terse:
out += ' %s: %-54s %s BTC' % (n+1, btcaddr+mmid_fmt, normalize_btc_amt(btc_amt))
out += '%3s: %s %s %s BTC' % (n+1, e.addr.fmt(color=True),mmid_fmt, e.amt.hl())
else:
for d in (
(n+1, 'address:', btcaddr + mmid_fmt),
('', 'comment:', comment),
('', 'amount:', '%s BTC' % normalize_btc_amt(btc_amt))
(n+1, 'address:', e.addr.fmt(color=True) + ' ' + mmid_fmt),
('', 'comment:', e.label.hl() if e.label else ''),
('', 'amount:', '%s BTC' % e.amt.hl())
):
if d[2]: out += ('%3s %-8s %s\n' % d)
out += '\n'
@ -447,9 +381,9 @@ class MMGenTX(MMGenObject):
total_in = self.sum_inputs()
total_out = self.sum_outputs()
out += fs % (
normalize_btc_amt(total_in),
normalize_btc_amt(total_out),
normalize_btc_amt(total_in-total_out)
total_in.hl(),
total_out.hl(),
(total_in-total_out).hl()
)
return out
@ -477,15 +411,15 @@ class MMGenTX(MMGenObject):
err_str = 'metadata'
else:
self.txid,send_amt,self.timestamp,blockcount = metadata.split()
self.send_amt = Decimal(send_amt)
self.send_amt = BTCAmt(send_amt)
self.blockcount = int(blockcount)
try: unhexlify(self.hex)
except: err_str = 'hex data'
else:
try: self.inputs = eval(inputs_data)
try: self.inputs = self.decode_io('inputs',eval(inputs_data))
except: err_str = 'inputs data'
else:
try: self.outputs = eval(outputs_data)
try: self.outputs = self.decode_io('outputs',eval(outputs_data))
except: err_str = 'btc-to-mmgen address map data'
else:
if comment:
@ -494,13 +428,10 @@ class MMGenTX(MMGenObject):
if comment == False:
err_str = 'encoded comment (not base58)'
else:
if is_valid_tx_comment(comment):
self.comment = comment.decode('utf8')
else:
self.label = MMGenTXLabel(comment,on_fail='return')
if not self.label:
err_str = 'comment'
if err_str:
msg(err_fmt % err_str)
sys.exit(2)

View file

@ -29,14 +29,31 @@ import mmgen.globalvars as g
pnm = g.proj_name
_red,_grn,_yel,_cya,_reset,_grnbg = \
['\033[%sm' % c for c in '31;1','32;1','33;1','36;1','0','30;102']
# If 88- or 256-color support is compiled, the following apply.
# P s = 3 8 ; 5 ; P s -> Set foreground color to the second P s .
# P s = 4 8 ; 5 ; P s -> Set background color to the second P s .
if os.environ['TERM'][-8:] == '256color':
_blk,_red,_grn,_yel,_blu,_mag,_cya,_bright,_dim,_ybright,_ydim,_pnk,_orng,_gry = [
'\033[38;5;%s;1m' % c for c in 232,210,121,229,75,90,122,231,245,187,243,218,215,246]
_redbg = '\033[38;5;232;48;5;210;1m'
_grnbg = '\033[38;5;232;48;5;121;1m'
_grybg = '\033[38;5;231;48;5;240;1m'
_reset = '\033[0m'
else:
_blk,_red,_grn,_yel,_blu,_mag,_cya,_reset,_grnbg = \
['\033[%sm' % c for c in '30;1','31;1','32;1','33;1','34;1','35;1','36;1','0','30;102']
_gry = _orng = _pnk = _redbg = _ybright = _ydim = _bright = _dim = _grybg = _mag # TODO
def red(s): return _red+s+_reset
def green(s): return _grn+s+_reset
def grnbg(s): return _grnbg+s+_reset
def yellow(s): return _yel+s+_reset
def cyan(s): return _cya+s+_reset
def blue(s): return _blu+s+_reset
def pink(s): return _pnk+s+_reset
def orange(s): return _orng+s+_reset
def gray(s): return _gry+s+_reset
def magenta(s): return _mag+s+_reset
def nocolor(s): return s
def start_mscolor():
@ -65,10 +82,12 @@ def mdie(*args):
sys.stdout.write(repr(d)+'\n')
sys.exit()
def die(ev,s):
sys.stderr.write(s+'\n'); sys.exit(ev)
def Die(ev,s):
sys.stdout.write(s+'\n'); sys.exit(ev)
def die(ev=0,s=''):
if s: sys.stderr.write(s+'\n')
sys.exit(ev)
def Die(ev=0,s=''):
if s: sys.stdout.write(s+'\n')
sys.exit(ev)
def pp_format(d):
import pprint
@ -144,7 +163,7 @@ def suf(arg,suf_type):
t = type(arg)
if t == int:
n = arg
elif t == list or t == tuple or t == set:
elif t in (list,tuple,set,dict):
n = len(arg)
else:
msg('%s: invalid parameter' % arg)
@ -376,7 +395,7 @@ def _validate_addr_num(n):
msg("'%s': invalid %s address index" % (n,g.proj_name))
return False
def parse_addr_idxs(arg,sep=','):
def parse_addr_idxs(arg,sep=','): # TODO - delete
ret = []
@ -517,52 +536,6 @@ def write_data_to_file(
return True
def _check_mmseed_format(words):
valid = False
desc = '%s data' % g.seed_ext
try:
chklen = len(words[0])
except:
return False
if len(words) < 3 or len(words) > 12:
msg('Invalid data length (%s) in %s' % (len(words),desc))
elif not is_hexstring(words[0]):
msg("Invalid format of checksum '%s' in %s"%(words[0], desc))
elif chklen != 6:
msg('Incorrect length of checksum (%s) in %s' % (chklen,desc))
else: valid = True
return valid
def _check_wallet_format(infile, lines):
desc = "wallet file '%s'" % infile
valid = False
chklen = len(lines[0])
if len(lines) != 6:
vmsg('Invalid number of lines (%s) in %s' % (len(lines),desc))
elif chklen != 6:
vmsg('Incorrect length of Master checksum (%s) in %s' % (chklen,desc))
elif not is_hexstring(lines[0]):
vmsg("Invalid format of Master checksum '%s' in %s"%(lines[0], desc))
else: valid = True
if valid == False:
die(2,'Invalid %s' % desc)
def _check_chksum_6(chk,val,desc,infile):
comp_chk = make_chksum_6(val)
if chk != comp_chk:
msg("%s checksum incorrect in file '%s'!" % (desc,infile))
die(2,'Checksum: %s. Computed value: %s' % (chk,comp_chk))
dmsg('%s checksum passed: %s' % (capfirst(desc),chk))
def get_words_from_user(prompt):
# split() also strips
words = my_raw_input(prompt, echo=opt.echo_passphrase).split()
@ -591,19 +564,25 @@ def remove_comments(lines):
# re.sub(pattern, repl, string, count=0, flags=0)
ret = []
for i in lines:
i = re.sub('#.*','',i,1)
i = re.sub('\s+$','',i)
i = re.sub(ur'#.*',u'',i,1)
i = re.sub(ur'\s+$',u'',i)
if i: ret.append(i)
return ret
def get_lines_from_file(infile,desc='',trim_comments=False):
if desc != '':
qmsg("Getting %s from file '%s'" % (desc,infile))
f = open_file_or_exit(infile,'r')
lines = f.read().splitlines() # DOS-safe
f.close()
return remove_comments(lines) if trim_comments else lines
def mmgen_decrypt_file_maybe(fn,desc=''):
d = get_data_from_file(fn,desc,binary=True)
have_enc_ext = get_extension(fn) == g.mmenc_ext
if have_enc_ext or not is_ascii(d):
m = ('Attempting to decrypt','Decrypting')[have_enc_ext]
msg("%s %s '%s'" % (m,desc,fn))
from mmgen.crypto import mmgen_decrypt_retry
d = mmgen_decrypt_retry(d,desc)
return d
def get_lines_from_file(fn,desc='',trim_comments=False):
dec = mmgen_decrypt_file_maybe(fn,desc)
ret = dec.decode('utf8').splitlines() # DOS-safe
return remove_comments(ret) if trim_comments else ret
def get_data_from_user(desc='data',silent=False):
data = my_raw_input('Enter %s: ' % desc, echo=opt.echo_passphrase)
@ -612,40 +591,13 @@ def get_data_from_user(desc='data',silent=False):
def get_data_from_file(infile,desc='data',dash=False,silent=False,binary=False):
if dash and infile == '-': return sys.stdin.read()
if not silent:
if not silent and desc:
qmsg("Getting %s from file '%s'" % (desc,infile))
f = open_file_or_exit(infile,('r','rb')[bool(binary)])
data = f.read()
f.close()
return data
def get_seed_from_seed_data(words):
if not _check_mmseed_format(words):
msg('Invalid %s data' % g.seed_ext)
return False
stored_chk = words[0]
seed_b58 = ''.join(words[1:])
chk = make_chksum_6(seed_b58)
vmsg_r('Validating %s checksum...' % g.seed_ext)
if compare_chksums(chk, 'seed', stored_chk, 'input'):
from mmgen.bitcoin import b58decode_pad
seed = b58decode_pad(seed_b58)
if seed == False:
msg('Invalid b58 number: %s' % val)
return False
msg('Valid seed data for Seed ID %s' % make_chksum_8(seed))
return seed
else:
msg('Invalid checksum for {pnm} seed'.format(pnm=pnm))
return False
passwd_file_used = False
def pwfile_reuse_warning():
@ -790,8 +742,8 @@ def get_bitcoind_cfg_options(cfg_keys):
cfg_file = os.path.join(get_homedir(), get_datadir(), 'bitcoin.conf')
cfg = dict([(k,v) for k,v in [split2(line.translate(None,'\t '),'=')
for line in get_lines_from_file(cfg_file)] if k in cfg_keys]) \
cfg = dict([(k,v) for k,v in [split2(str(line).translate(None,'\t '),'=')
for line in get_lines_from_file(cfg_file,'')] if k in cfg_keys]) \
if file_is_readable(cfg_file) else {}
for k in set(cfg_keys) - set(cfg.keys()): cfg[k] = ''
@ -803,7 +755,7 @@ def get_bitcoind_auth_cookie():
f = os.path.join(get_homedir(), get_datadir(), '.cookie')
if file_is_readable(f):
return get_lines_from_file(f)[0]
return get_lines_from_file(f,'')[0]
else:
return ''

23
scripts/compute-file-chksum.py Executable file
View file

@ -0,0 +1,23 @@
#!/usr/bin/env python
from mmgen.common import *
from mmgen.util import *
opts_data = {
'desc': 'Compute checksum for a MMGen data file',
'usage':'[opts] infile',
'options': """
-h, --help Print this help message.
-i, --include-first-line Include the first line of the file (you probably don't want this)
""".strip()
}
cmd_args = opts.init(opts_data)
lines = get_lines_from_file(cmd_args[0])
start = (1,0)[bool(opt.include_first_line)]
a = make_chksum_6(' '.join(lines[start:]))
if start == 1:
b = lines[0]
msg(("Checksum in file (%s) doesn't match computed value!" % b,"Checksum in file OK")[a==b])
Msg(a)

View file

@ -94,22 +94,44 @@ def find_block_by_time(c,timestamp):
tx = MMGenTX()
[tx.txid,send_amt,tx.timestamp],tx.hex,inputs,b2m_map,tx.comment = parse_tx_file(cmd_args[0])
[tx.txid,send_amt,tx.timestamp],tx.hex,inputs,b2m_map,tx.label = parse_tx_file(cmd_args[0])
tx.send_amt = Decimal(send_amt)
c = bitcoin_connection()
tx.copy_inputs(inputs)
# attrs = 'txid','vout','amt','comment','mmid','addr','wif'
#pp_msg(inputs)
for i in inputs:
if not 'mmid' in i and 'account' in i:
from mmgen.tw import parse_tw_acct_label
a,b = parse_tw_acct_label(i['account'])
if a:
i['mmid'] = a.decode('utf8')
if b: i['comment'] = b.decode('utf8')
#pp_msg(inputs)
tx.inputs = tx.decode_io_oldfmt(inputs)
if tx.check_signed(c):
msg('Transaction is signed')
dec_tx = c.decoderawtransaction(tx.hex)
tx.outputs = dict([(i['scriptPubKey']['addresses'][0],(i['value'],)) for i in dec_tx['vout']])
tx.outputs = [MMGenTxOutput(addr=i['scriptPubKey']['addresses'][0],amt=i['value'])
for i in dec_tx['vout']]
for e in tx.outputs:
if e.addr in b2m_map:
f = b2m_map[e.addr]
e.mmid = f[0]
if f[1]: e.label = f[1].decode('utf8')
else:
for f in tx.inputs:
if e.addr == f.addr and f.mmid:
e.mmid = f.mmid
if f.label: e.label = f.label.decode('utf8')
#for i in tx.inputs: print i
#for i in tx.outputs: print i
#die(1,'')
tx.blockcount = find_block_by_time(c,tx.timestamp)
for k in tx.outputs:
if k in b2m_map:
tx.outputs[k] += b2m_map[k]
tx.write_to_file(ask_write=False)

View file

@ -0,0 +1,6 @@
2957e0
FFB367 1.234 20150405_102927 350828
01000000013364630b6d290a82c822facc2f7c1db4452cea459b2ce22371135530485a5d010600000000ffffffff0205d7d600010000001976a914bba3993079ccdf40c9bbbe495473f0b3d2dc5eec88ac40ef5a07000000001976a914abe58e1e45f6176910a4c1ac1ee62328d5cc4fd588ac00000000
[{'label': u'Test Wallet', 'mmid': u'98831F3A:500', 'vout': 6, 'txid': u'015d5a483055137123e22c9b45ea2c45b41d7c2fccfa22c8820a296d0b636433', 'amt': BTCAmt('44.32452045'), 'confs': 495L, 'addr': u'1EZNuddPnaZFah9QVbGvzvTcP4KeRrRFt8', 'scriptPubKey': '76a91494b93bbe8a32f1db80b307482e83c25fa4e99b8c88ac'}]
[{'amt': BTCAmt('43.09047045'), 'mmid': '98831F3A:3', 'addr': u'1J79LtWctedRLnMfFNRgzzSFsozQqDeoKD'}, {'amt': BTCAmt('1.23400000'), 'mmid': '98831F3A:2', 'addr': u'1GfuYaKHrhdiVybXMGCcjadSgfjvpdt2x9'}]
3SBcsGkhcKRVB2gr98BmscU8HtWJ12HTXpJa5XmvbEUateQ3bJBEgvLd5kPGAzg1rFkzjVpZJgiKGwvnq5mJpwnbJqcHpVEAopWyALDmtjrDwEvPiTY

View file

@ -1,5 +0,0 @@
FFB367 1.234 20150405_102927
01000000013364630b6d290a82c822facc2f7c1db4452cea459b2ce22371135530485a5d010600000000ffffffff0205d7d600010000001976a914bba3993079ccdf40c9bbbe495473f0b3d2dc5eec88ac40ef5a07000000001976a914abe58e1e45f6176910a4c1ac1ee62328d5cc4fd588ac00000000
[{'comment': u'Test Wallet', 'mmid': u'98831F3A:500', 'vout': 6, 'txid': u'015d5a483055137123e22c9b45ea2c45b41d7c2fccfa22c8820a296d0b636433', 'amount': Decimal('44.32452045'), 'confirmations': 495L, 'address': u'1EZNuddPnaZFah9QVbGvzvTcP4KeRrRFt8', 'spendable': False, 'scriptPubKey': '76a91494b93bbe8a32f1db80b307482e83c25fa4e99b8c88ac'}]
{u'1J79LtWctedRLnMfFNRgzzSFsozQqDeoKD': ('98831F3A:3', u''), u'1EZNuddPnaZFah9QVbGvzvTcP4KeRrRFt8': (u'98831F3A:500', u'Test Wallet'), u'1GfuYaKHrhdiVybXMGCcjadSgfjvpdt2x9': ('98831F3A:2', u'')}
3SBcsGkhcKRVB2gr98BmscU8HtWJ12HTXpJa5XmvbEUateQ3bJBEgvLd5kPGAzg1rFkzjVpZJgiKGwvnq5mJpwnbJqcHpVEAopWyALDmtjrDwEvPiTY

Binary file not shown.

Binary file not shown.

View file

@ -22,6 +22,22 @@ test/test.py: Test suite for the MMGen suite
import sys,os
def run_in_tb():
fn = sys.argv[0]
source = open(fn)
try:
exec source in {'inside_tb':1}
except SystemExit:
pass
except:
def color(s): return '\033[36;1m' + s + '\033[0m'
e = sys.exc_info()
sys.stdout.write(color('\nTest script returned: %s\n' % (e[0].__name__)))
if not 'inside_tb' in globals() and 'MMGEN_TEST_TRACEBACK' in os.environ:
run_in_tb()
sys.exit()
pn = os.path.dirname(sys.argv[0])
os.chdir(os.path.join(pn,os.pardir))
sys.path.__setitem__(0,os.path.abspath(os.curdir))
@ -114,7 +130,7 @@ cfgs = {
},
'4': {
'tmpdir': os.path.join('test','tmp4'),
'wpasswd': 'Hashrate rising',
'wpasswd': 'Hashrate good',
'addr_idx_list': '63,1004,542-544,7-9', # 8 addresses
'seed_len': 192,
'dep_generators': {
@ -127,6 +143,18 @@ cfgs = {
'bw_filename': 'brainwallet.mmbrain',
'bw_params': '192,1',
},
'14': {
'kapasswd': 'Maxwell',
'tmpdir': os.path.join('test','tmp14'),
'wpasswd': 'The Halving',
'addr_idx_list': '61,998,502-504,7-9', # 8 addresses
'seed_len': 256,
'dep_generators': {
'mmdat': 'walletgen14',
'addrs': 'addrgen14',
'akeys.mmenc': 'keyaddrgen14',
},
},
'5': {
'tmpdir': os.path.join('test','tmp5'),
'wpasswd': 'My changed password',
@ -141,8 +169,8 @@ cfgs = {
'seed_len': 128,
'seed_id': 'FE3C6545',
'ref_bw_seed_id': '33F10310',
'addrfile_chk': 'B230 7526 638F 38CB 8FDC 8B76',
'keyaddrfile_chk': 'CF83 32FB 8A8B 08E2 0F00 D601',
'addrfile_chk': 'B230 7526 638F 38CB',
'keyaddrfile_chk': 'CF83 32FB 8A8B 08E2',
'wpasswd': 'reference password',
'ref_wallet': 'FE3C6545-D782B529[128,1].mmdat',
'ic_wallet': 'FE3C6545-E29303EA-5E229E30[128,1].mmincog',
@ -167,8 +195,8 @@ cfgs = {
'seed_len': 192,
'seed_id': '1378FC64',
'ref_bw_seed_id': 'CE918388',
'addrfile_chk': '8C17 A5FA 0470 6E89 3A87 8182',
'keyaddrfile_chk': '9648 5132 B98E 3AD9 6FC3 C5AD',
'addrfile_chk': '8C17 A5FA 0470 6E89',
'keyaddrfile_chk': '9648 5132 B98E 3AD9',
'wpasswd': 'reference password',
'ref_wallet': '1378FC64-6F0F9BB4[192,1].mmdat',
'ic_wallet': '1378FC64-2907DE97-F980D21F[192,1].mmincog',
@ -193,14 +221,14 @@ cfgs = {
'seed_len': 256,
'seed_id': '98831F3A',
'ref_bw_seed_id': 'B48CD7FC',
'addrfile_chk': '6FEF 6FB9 7B13 5D91 854A 0BD3',
'keyaddrfile_chk': '9F2D D781 1812 8BAD C396 9DEB',
'addrfile_chk': '6FEF 6FB9 7B13 5D91',
'keyaddrfile_chk': '9F2D D781 1812 8BAD',
'wpasswd': 'reference password',
'ref_wallet': '98831F3A-27F2BF93[256,1].mmdat',
'ref_addrfile': '98831F3A[1,31-33,500-501,1010-1011].addrs',
'ref_keyaddrfile': '98831F3A[1,31-33,500-501,1010-1011].akeys.mmenc',
'ref_addrfile_chksum': '6FEF 6FB9 7B13 5D91 854A 0BD3',
'ref_keyaddrfile_chksum': '9F2D D781 1812 8BAD C396 9DEB',
'ref_addrfile_chksum': '6FEF 6FB9 7B13 5D91',
'ref_keyaddrfile_chksum': '9F2D D781 1812 8BAD',
# 'ref_fake_unspent_data':'98831F3A_unspent.json',
'ref_tx_file': 'FFB367[1.234].rawtx',
@ -285,10 +313,13 @@ cmd_group['main'] = OrderedDict([
['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','rawtx'],3]])],
['walletgen14', (14,'wallet generation (14)', [[[],14]],14)],
['addrgen14', (14,'address generation (14)', [[['mmdat'],14]])],
['keyaddrgen14',(14,'key-address file generation (14)', [[['mmdat'],14]],14)],
['walletgen4',(4,'wallet generation (4) (brainwallet)', [])],
['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','rawtx'],4]])],
['txcreate4', (4,'tx creation with inputs and outputs from four seed sources, key-address file and non-MMGen inputs and outputs', [[['addrs'],1],[['addrs'],2],[['addrs'],3],[['addrs'],4],[['addrs','akeys.mmenc'],14]])],
['txsign4', (4,'tx signing with inputs and outputs from incog file, mnemonic file, wallet, brainwallet, key-address file and non-MMGen inputs and outputs', [[['mmincog'],1],[['mmwords'],2],[['mmdat'],3],[['mmbrain','rawtx'],4],[['akeys.mmenc'],14]])],
])
cmd_group['tool'] = OrderedDict([
@ -447,6 +478,7 @@ opts_data = {
-I, --non-interactive Non-interactive operation (MS Windows mode)
-p, --pause Pause between tests, resuming on keypress.
-q, --quiet Produce minimal output. Suppress dependency info.
-r, --resume=c Resume at command 'c' after interrupted run
-s, --system Test scripts and modules installed on system rather
than those in the repo root.
-S, --skip-deps Skip dependency checking for command
@ -460,6 +492,8 @@ If no command is given, the whole suite of tests is run.
}
cmd_args = opts.init(opts_data)
if opt.resume: opt.skip_deps = True
if opt.log:
log_fd = open(log_file,'a')
log_fd.write('\nLog started: %s\n' % make_timestr())
@ -470,6 +504,8 @@ ni = bool(opt.non_interactive)
# Disable MS color in spawned scripts due to bad interactions
os.environ['MMGEN_NOMSCOLOR'] = '1'
os.environ['MMGEN_NOLICENSE'] = '1'
os.environ['MMGEN_DISABLE_COLOR'] = '1'
os.environ['MMGEN_MIN_URANDCHARS'] = '3'
if opt.debug_scripts: os.environ['MMGEN_DEBUG'] = '1'
@ -599,11 +635,19 @@ def get_file_with_ext(ext,mydir,delete=True,no_dot=False):
else:
return flist[0]
def find_generated_exts(cmd):
out = []
for k in cfgs:
for ext,prog in cfgs[k]['dep_generators'].items():
if prog == cmd:
out.append((ext,cfgs[k]['tmpdir']))
return out
def get_addrfile_checksum(display=False):
addrfile = get_file_with_ext('addrs',cfg['tmpdir'])
silence()
from mmgen.addr import AddrInfo
chk = AddrInfo(addrfile).checksum
from mmgen.addr import AddrList
chk = AddrList(addrfile).chksum
if opt.verbose and display: msg('Checksum: %s' % cyan(chk))
end_silence()
return chk
@ -622,6 +666,9 @@ class MMGenExpect(object):
mmgen_cmd = os.path.join(os.curdir,mmgen_cmd)
desc = (cmd_data[name][1],name)[bool(opt.names)]
if extra_desc: desc += ' ' + extra_desc
for i in cmd_args:
if type(i) not in (str,unicode):
die(2,'Error: missing input files in cmd line?:\n%s' % cmd_args)
cmd_str = mmgen_cmd + ' ' + ' '.join(cmd_args)
if opt.log:
log_fd.write(cmd_str+'\n')
@ -737,30 +784,59 @@ class MMGenExpect(object):
def read(self,n=None):
return self.p.read(n)
from decimal import Decimal
from mmgen.obj import BTCAmt
from mmgen.bitcoin import verify_addr
def add_fake_unspent_entry(out,address,comment):
out.append({
'account': unicode(comment),
def create_fake_unspent_entry(address,sid=None,idx=None,lbl=None,non_mmgen=None):
if lbl: lbl = ' ' + lbl
return {
'account': (non_mmgen or ('%s:%s%s' % (sid,idx,lbl))).decode('utf8'),
'vout': int(getrandnum(4) % 8),
'txid': unicode(hexlify(os.urandom(32))),
'amount': Decimal('%s.%s' % (10+(getrandnum(4) % 40), getrandnum(4) % 100000000)),
'txid': hexlify(os.urandom(32)).decode('utf8'),
'amount': BTCAmt('%s.%s' % (10+(getrandnum(4) % 40), getrandnum(4) % 100000000)),
'address': address,
'spendable': False,
'scriptPubKey': ('76a914'+verify_addr(address,return_hex=True)+'88ac'),
'confirmations': getrandnum(4) % 500
})
'confirmations': getrandnum(4) % 50000
}
labels = [
"Automotive",
"Travel expenses",
"Healthcare",
"Freelancing 1",
"Freelancing 2",
"Alice's assets",
"Bob's bequest",
"House purchase",
"Real estate fund",
"Job 1",
"XYZ Corp.",
"Eddie's endowment",
"Emergency fund",
"Real estate fund",
"Ian's inheritance",
"",
"Rainy day",
"Fred's funds",
"Job 2",
"Carl's capital",
]
label_iter = None
def create_fake_unspent_data(adata,unspent_data_file,tx_data,non_mmgen_input=''):
out = []
for s in tx_data:
sid = tx_data[s]['sid']
a = adata.addrinfo(sid)
a = adata.addrlist(sid)
for n,(idx,btcaddr) in enumerate(a.addrpairs(),1):
lbl = ('',' addr %02i' % n)[bool(n%3)]
add_fake_unspent_entry(out,btcaddr,'%s:%s%s' % (sid,idx,lbl))
while True:
try: lbl = next(label_iter)
except: label_iter = iter(labels)
else: break
out.append(create_fake_unspent_entry(btcaddr,sid,idx,lbl))
if n == 1: # create a duplicate address. This means addrs_per_wallet += 1
out.append(create_fake_unspent_entry(btcaddr,sid,idx,lbl))
if non_mmgen_input:
from mmgen.bitcoin import privnum2addr,hextowif
@ -770,7 +846,7 @@ def create_fake_unspent_data(adata,unspent_data_file,tx_data,non_mmgen_input='')
write_data_to_file(of, hextowif('{:064x}'.format(privnum),
compressed=True)+'\n','compressed bitcoin key',silent=True)
add_fake_unspent_entry(out,btcaddr,'Non-MMGen address')
out.append(create_fake_unspent_entry(btcaddr,non_mmgen='Non-MMGen address'))
# msg('\n'.join([repr(o) for o in out])); sys.exit()
write_data_to_file(unspent_data_file,repr(out),'Unspent outputs',silent=True)
@ -779,11 +855,12 @@ def create_fake_unspent_data(adata,unspent_data_file,tx_data,non_mmgen_input='')
def add_comments_to_addr_file(addrfile,outfile):
silence()
msg(green("Adding comments to address file '%s'" % addrfile))
from mmgen.addr import AddrInfo
a = AddrInfo(addrfile)
from mmgen.addr import AddrList
a = AddrList(addrfile)
for n,idx in enumerate(a.idxs(),1):
if n % 2: a.set_comment(idx,'Test address %s' % n)
write_data_to_file(outfile,a.fmt_data(enable_comments=True),silent=True)
a.format(enable_comments=True)
write_data_to_file(outfile,a.fmt_data,silent=True)
end_silence()
def make_brainwallet_file(fn):
@ -928,12 +1005,26 @@ class MMGenTestSuite(object):
if ni and (len(cmd_data[cmd]) < 4 or cmd_data[cmd][3] != 1): return
# delete files produced by this cmd
# for ext,tmpdir in find_generated_exts(cmd):
# print cmd, get_file_with_ext(ext,tmpdir)
d = [(str(num),ext) for exts,num in cmd_data[cmd][2] for ext in exts]
# delete files depended on by this cmd
al = [get_file_with_ext(ext,cfgs[num]['tmpdir']) for num,ext in d]
global cfg
cfg = cfgs[str(cmd_data[cmd][0])]
if opt.resume:
if cmd == opt.resume:
msg(yellow("Resuming at '%s'" % cmd))
opt.resume = False
opt.skip_deps = False
else:
return
self.__class__.__dict__[cmd](*([self,cmd] + al))
def generate_file_deps(self,cmd):
@ -951,7 +1042,7 @@ class MMGenTestSuite(object):
def walletgen(self,name,seed_len=None):
write_to_tmpfile(cfg,pwfile,cfg['wpasswd']+'\n')
add_args = (['-r10'],
add_args = (['-r5'],
['-q','-r0','-L','NI Wallet','-P',get_tmpfile_fn(cfg,pwfile)])[bool(ni)]
args = ['-d',cfg['tmpdir'],'-p1']
if seed_len: args += ['-l',str(seed_len)]
@ -977,7 +1068,7 @@ class MMGenTestSuite(object):
add_args = ['-r0', '-q', '-P%s' % get_tmpfile_fn(cfg,pwfile),
get_tmpfile_fn(cfg,bf)]
else:
add_args = ['-r10']
add_args = ['-r5']
t = MMGenExpect(name,'mmgen-walletconv', args + add_args)
if ni: return
t.license()
@ -1038,8 +1129,8 @@ class MMGenTestSuite(object):
def walletchk_newpass (self,name,wf,pf):
return self.walletchk(name,wf,pf,pw=True)
def addrgen(self,name,wf,pf,check_ref=False):
add_args = ([],['-P',pf,'-q'])[ni]
def addrgen(self,name,wf,pf=None,check_ref=False):
add_args = ([],['-q'] + ([],['-P',pf])[bool(pf)])[ni]
t = MMGenExpect(name,'mmgen-addrgen', add_args +
['-d',cfg['tmpdir'],wf,cfg['addr_idx_list']])
if ni: return
@ -1074,14 +1165,14 @@ class MMGenTestSuite(object):
def txcreate_common(self,name,sources=['1'],non_mmgen_input=''):
if opt.verbose or opt.exact_output:
sys.stderr.write(green('Generating fake transaction info\n'))
sys.stderr.write(green('Generating fake tracking wallet info\n'))
silence()
from mmgen.addr import AddrInfo,AddrInfoList
tx_data,ail = {},AddrInfoList()
from mmgen.addr import AddrList,AddrData
tx_data,ad = {},AddrData()
for s in sources:
afile = get_file_with_ext('addrs',cfgs[s]['tmpdir'])
ai = AddrInfo(afile)
ail.add(ai)
ai = AddrList(afile)
ad.add(ai)
aix = parse_addr_idxs(cfgs[s]['addr_idx_list'])
if len(aix) != addrs_per_wallet:
errmsg(red('Address index list length != %s: %s' %
@ -1089,13 +1180,15 @@ class MMGenTestSuite(object):
sys.exit()
tx_data[s] = {
'addrfile': afile,
'chk': ai.checksum,
'chk': ai.chksum,
'sid': ai.seed_id,
'addr_idxs': aix[-2:],
}
unspent_data_file = os.path.join(cfg['tmpdir'],'unspent.json')
create_fake_unspent_data(ail,unspent_data_file,tx_data,non_mmgen_input)
create_fake_unspent_data(ad,unspent_data_file,tx_data,non_mmgen_input)
if opt.verbose or opt.exact_output:
sys.stderr.write("Fake transaction wallet data written to file '%s'\n" % unspent_data_file)
# make the command line
from mmgen.bitcoin import privnum2addr
@ -1144,8 +1237,8 @@ class MMGenTestSuite(object):
t.expect('Continue anyway? (y/N): ','y')
t.expect(r"'q' = quit sorting, .*?: ",'M', regex=True)
t.expect(r"'q' = quit sorting, .*?: ",'q', regex=True)
outputs_list = [addrs_per_wallet*i + 1 for i in range(len(tx_data))]
if non_mmgen_input: outputs_list.append(len(tx_data)*addrs_per_wallet + 1)
outputs_list = [(addrs_per_wallet+1)*i + 1 for i in range(len(tx_data))]
if non_mmgen_input: outputs_list.append(len(tx_data)*(addrs_per_wallet+1) + 1)
t.expect('Enter a range or space-separated list of outputs to spend: ',
' '.join([str(i) for i in outputs_list])+'\n')
if non_mmgen_input: t.expect('Accept? (y/N): ','y')
@ -1229,7 +1322,7 @@ class MMGenTestSuite(object):
self.export_seed(name,wf,desc='mnemonic data',out_fmt='words')
def export_incog(self,name,wf,desc='incognito data',out_fmt='i',add_args=[]):
uargs = ['-p1','-r10'] + add_args
uargs = ['-p1','-r5'] + add_args
self.walletconv_export(name,wf,desc=desc,out_fmt=out_fmt,uargs=uargs,pw=True)
ok()
@ -1282,12 +1375,12 @@ class MMGenTestSuite(object):
self.addrgen_incog(name,[],'',in_fmt='hi',desc='hidden incognito data',
args=['-H','%s,%s'%(rf,hincog_offset),'-l',str(hincog_seedlen)])
def keyaddrgen(self,name,wf,pf,check_ref=False):
def keyaddrgen(self,name,wf,pf=None,check_ref=False):
args = ['-d',cfg['tmpdir'],wf,cfg['addr_idx_list']]
if ni:
m = "\nAnswer 'n' at the interactive prompt"
msg(grnbg(m))
args = ['-q','-P',pf] + args
args = ['-q'] + ([],['-P',pf])[bool(pf)] + args
t = MMGenExpect(name,'mmgen-keygen', args)
if ni: return
t.license()
@ -1308,8 +1401,8 @@ class MMGenTestSuite(object):
def txsign_keyaddr(self,name,keyaddr_file,txfile):
t = MMGenExpect(name,'mmgen-txsign', ['-d',cfg['tmpdir'],'-M',keyaddr_file,txfile])
t.license()
t.hash_preset('key-address file','1')
t.passphrase('key-address file',cfg['kapasswd'])
t.hash_preset('key-address data','1')
t.passphrase('key-address data',cfg['kapasswd'])
t.expect('Check key-to-address validity? (y/N): ','y')
t.tx_view()
self.txsign_end(t)
@ -1359,7 +1452,7 @@ class MMGenTestSuite(object):
bwf = os.path.join(cfg['tmpdir'],cfg['bw_filename'])
make_brainwallet_file(bwf)
seed_len = str(cfg['seed_len'])
args = ['-d',cfg['tmpdir'],'-p1','-r10','-l'+seed_len,'-ib']
args = ['-d',cfg['tmpdir'],'-p1','-r5','-l'+seed_len,'-ib']
t = MMGenExpect(name,'mmgen-walletconv', args + [bwf])
t.license()
t.passphrase_new('new MMGen wallet',cfg['wpasswd'])
@ -1371,14 +1464,19 @@ class MMGenTestSuite(object):
def addrgen4(self,name,wf):
self.addrgen(name,wf,pf='')
def txcreate4(self,name,f1,f2,f3,f4):
self.txcreate_common(name,sources=['1','2','3','4'],non_mmgen_input='4')
def txcreate4(self,name,f1,f2,f3,f4,f5,f6):
self.txcreate_common(name,sources=['1','2','3','4','14'],non_mmgen_input='4')
def txsign4(self,name,f1,f2,f3,f4,f5):
def txsign4(self,name,f1,f2,f3,f4,f5,f6):
non_mm_fn = os.path.join(cfg['tmpdir'],non_mmgen_fn)
t = MMGenExpect(name,'mmgen-txsign',
['-d',cfg['tmpdir'],'-i','brain','-b'+cfg['bw_params'],'-p1','-k',non_mm_fn,f1,f2,f3,f4,f5])
a = ['-d',cfg['tmpdir'],'-i','brain','-b'+cfg['bw_params'],'-p1','-k',non_mm_fn,'-M',f6,f1,f2,f3,f4,f5]
t = MMGenExpect(name,'mmgen-txsign',a)
t.license()
t.hash_preset('key-address data','1')
t.passphrase('key-address data',cfgs['14']['kapasswd'])
t.expect('Check key-to-address validity? (y/N): ','y')
t.tx_view()
for cnum,desc in ('1','incognito data'),('3','MMGen wallet'):
@ -1530,12 +1628,16 @@ class MMGenTestSuite(object):
pf = None
self.walletchk(name,wf,pf=pf,pw=True,sid=cfg['seed_id'])
def ref_seed_chk(self,name,ext=g.seed_ext):
from mmgen.seed import SeedFile
def ref_seed_chk(self,name,ext=SeedFile.ext):
wf = os.path.join(ref_dir,'%s.%s' % (cfg['seed_id'],ext))
desc = ('mnemonic data','seed data')[ext==g.seed_ext]
from mmgen.seed import SeedFile
desc = ('mnemonic data','seed data')[ext==SeedFile.ext]
self.walletchk(name,wf,pf=None,desc=desc,sid=cfg['seed_id'])
def ref_mn_chk(self,name): self.ref_seed_chk(name,ext=g.mn_ext)
def ref_mn_chk(self,name):
from mmgen.seed import Mnemonic
self.ref_seed_chk(name,ext=Mnemonic.ext)
def ref_brain_chk(self,name,bw_file=ref_bw_file):
wf = os.path.join(ref_dir,bw_file)
@ -1593,7 +1695,7 @@ class MMGenTestSuite(object):
msg(grnbg('%s %s' % (m,n)))
return
if ftype == 'keyaddr':
w = 'key-address file'
w = 'key-address data'
t.hash_preset(w,ref_kafile_hash_preset)
t.passphrase(w,ref_kafile_pass)
t.expect('Check key-to-address validity? (y/N): ','y')
@ -1631,7 +1733,7 @@ class MMGenTestSuite(object):
# wallet conversion tests
def walletconv_in(self,name,infile,desc,uopts=[],pw=False,oo=False):
opts = ['-d',cfg['tmpdir'],'-o','words','-r10']
opts = ['-d',cfg['tmpdir'],'-o','words','-r5']
if_arg = [infile] if infile else []
d = '(convert)'
if ni:
@ -1685,7 +1787,7 @@ class MMGenTestSuite(object):
rd = os.urandom(ref_wallet_incog_offset+128)
write_to_tmpfile(cfg,hincog_fn,rd)
else:
aa = ['-r10']
aa = ['-r5']
infile = os.path.join(ref_dir,cfg['seed_id']+'.mmwords')
t = MMGenExpect(name,'mmgen-walletconv',aa+opts+[infile],extra_desc='(convert)')
@ -1696,7 +1798,7 @@ class MMGenTestSuite(object):
pf = get_tmpfile_fn(cfg,pfn)
if desc != 'hidden incognito data':
from mmgen.seed import SeedSource
ext = SeedSource.fmt_code_to_sstype(out_fmt).ext
ext = SeedSource.fmt_code_to_type(out_fmt).ext
hps = ('',',1')[bool(pw)] # TODO real hp
pre_ext = '[%s%s].' % (cfg['seed_len'],hps)
wf = get_file_with_ext(pre_ext+ext,cfg['tmpdir'],no_dot=True)
@ -1755,6 +1857,8 @@ class MMGenTestSuite(object):
for i in ('1','2','3'):
locals()[k+i] = locals()[k]
for k in ('walletgen','addrgen','keyaddrgen'): locals()[k+'14'] = locals()[k]
# main()
if opt.pause:
@ -1809,10 +1913,13 @@ try:
clean()
for cmd in cmd_data:
if cmd[:5] == 'info_':
msg(green('\nTesting ' + cmd_data[cmd][0]))
msg(green('%sTesting %s' % (('\n','')[bool(opt.resume)],cmd_data[cmd][0])))
continue
ts.do_cmd(cmd)
if cmd is not cmd_data.keys()[-1]: do_between()
except KeyboardInterrupt:
die(1,'\nExiting at user request')
raise
except:
sys.stderr = stderr_save
raise

View file

@ -105,7 +105,7 @@ cfg = {
'refdir': 'test/ref',
'txfile': 'FFB367[1.234].rawtx',
'addrfile': '98831F3A[1,31-33,500-501,1010-1011].addrs',
'addrfile_chk': '6FEF 6FB9 7B13 5D91 854A 0BD3',
'addrfile_chk': '6FEF 6FB9 7B13 5D91',
}
opts_data = {