OO rewrite mostly done
Colored output
This commit is contained in:
parent
b96d6528e7
commit
680ea8a5fc
26 changed files with 1593 additions and 1245 deletions
794
mmgen/addr.py
794
mmgen/addr.py
|
|
@ -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,396 +90,408 @@ 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': """
|
||||
'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 __init__(self,addrfile='',sid='',adata=[],seed='',addr_idxs='',src='',
|
||||
addrlist='',keylist='',do_chksum=True,chksum_only=False):
|
||||
|
||||
self.update_msgs()
|
||||
|
||||
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
|
||||
|
||||
if type(self) == KeyList:
|
||||
self.id_str = AddrListID(self)
|
||||
return
|
||||
|
||||
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']])
|
||||
|
||||
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
|
||||
keyconv = False
|
||||
else:
|
||||
from subprocess import check_output
|
||||
keyconv = 'keyconv'
|
||||
|
||||
t_addrs,num,pos,out = len(addrnums),0,0,[]
|
||||
|
||||
while pos != t_addrs:
|
||||
seed = sha512(seed).digest()
|
||||
num += 1 # round
|
||||
|
||||
if num != addrnums[pos]: continue
|
||||
|
||||
pos += 1
|
||||
|
||||
qmsg_r('\rGenerating %s #%s (%s of %s)' % (self.gen_desc,num,pos,t_addrs))
|
||||
|
||||
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 self.gen_addrs:
|
||||
if keyconv:
|
||||
e.addr = check_output([keyconv, wif]).split()[1]
|
||||
else:
|
||||
e.addr = privnum2addr(int(sec,16))
|
||||
|
||||
if self.gen_keys:
|
||||
e.wif = wif
|
||||
if opt.b16: e.sec = sec
|
||||
|
||||
out.append(e)
|
||||
|
||||
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 encrypt(self):
|
||||
from mmgen.crypto import mmgen_encrypt
|
||||
self.fmt_data = mmgen_encrypt(self.fmt_data,'new key list','')
|
||||
self.ext += '.'+g.mmenc_ext
|
||||
|
||||
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:
|
||||
l = lines.pop(0)
|
||||
d = l.split(None,2)
|
||||
|
||||
if not is_mmgen_idx(d[0]):
|
||||
return "'%s': invalid address num. in line: '%s'" % (d[0],l)
|
||||
if not is_btc_addr(d[1]):
|
||||
return "'%s': invalid Bitcoin address" % d[1]
|
||||
|
||||
if len(d) != 3: d.append('')
|
||||
|
||||
a = AddrListEntry(idx=int(d[0]),addr=d[1],label=d[2])
|
||||
|
||||
if self.has_keys:
|
||||
l = lines.pop(0)
|
||||
d = l.split(None,2)
|
||||
|
||||
if d[0] != 'wif:':
|
||||
return "Invalid key line in file: '%s'" % l
|
||||
if not is_wif(d[1]):
|
||||
return "'%s': invalid Bitcoin key" % d[1]
|
||||
|
||||
a.wif = d[1]
|
||||
|
||||
ret.append(a)
|
||||
|
||||
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):
|
||||
msg_r('\rVerifying keys %s/%s' % (n+1,llen))
|
||||
if e.addr != wif2addr_f(e.wif):
|
||||
return "Key doesn't match address!\n %s\n %s" % (e.wif,e.addr)
|
||||
msg(' - done')
|
||||
|
||||
return ret
|
||||
|
||||
def parse_file(self,fn,buf=[],exit_on_error=True):
|
||||
|
||||
lines = get_lines_from_file(fn,self.data_desc+' data',trim_comments=True)
|
||||
|
||||
try:
|
||||
sid,obrace = lines[0].split()
|
||||
except:
|
||||
errmsg = "Invalid first line: '%s'" % lines[0]
|
||||
else:
|
||||
cbrace = lines[-1]
|
||||
if obrace != '{':
|
||||
errmsg = "'%s': invalid first line" % lines[0]
|
||||
elif cbrace != '}':
|
||||
errmsg = "'%s': invalid last line" % cbrace
|
||||
elif not is_mmgen_seed_id(sid):
|
||||
errmsg = "'%s': invalid Seed ID" % sid
|
||||
else:
|
||||
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)
|
||||
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),
|
||||
'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')
|
||||
}
|
||||
|
||||
def test_for_keyconv(silent=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
|
||||
|
||||
return True
|
||||
""".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 generate_addrs(seed, addrnums, source='addrgen'):
|
||||
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)
|
||||
}
|
||||
|
||||
from util import make_chksum_8
|
||||
seed_id = make_chksum_8(seed) # Must do this before seed gets clobbered
|
||||
|
||||
if 'a' in opt.gen_what:
|
||||
if opt.no_keyconv or test_for_keyconv() == False:
|
||||
msg('Using (slow) internal ECDSA library for address generation')
|
||||
from mmgen.bitcoin import privnum2addr
|
||||
keyconv = False
|
||||
else:
|
||||
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
|
||||
|
||||
if num != addrnums[pos]: continue
|
||||
|
||||
pos += 1
|
||||
|
||||
qmsg_r('\rGenerating %s #%s (%s of %s)' % (w[0],num,pos,t_addrs))
|
||||
|
||||
e = AddrInfoEntry()
|
||||
e.idx = num
|
||||
|
||||
# Secret key is double sha256 of seed hash round /num/
|
||||
sec = sha256(sha256(seed).digest()).hexdigest()
|
||||
wif = numtowif(int(sec,16))
|
||||
|
||||
if 'a' in opt.gen_what:
|
||||
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 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
|
||||
|
||||
def _parse_addrfile_body(lines,has_keys=False,check=False):
|
||||
|
||||
if has_keys and len(lines) % 2:
|
||||
return 'Key-address file has odd number of lines'
|
||||
|
||||
ret = []
|
||||
while lines:
|
||||
a = AddrInfoEntry()
|
||||
l = lines.pop(0)
|
||||
d = l.split(None,2)
|
||||
|
||||
if not is_mmgen_idx(d[0]):
|
||||
return "'%s': invalid address num. in line: '%s'" % (d[0],l)
|
||||
if not is_btc_addr(d[1]):
|
||||
return "'%s': invalid Bitcoin address" % d[1]
|
||||
|
||||
if len(d) == 3: check_addr_label(d[2])
|
||||
else: d.append('')
|
||||
|
||||
a.idx,a.addr,a.comment = int(d[0]),unicode(d[1]),unicode(d[2])
|
||||
|
||||
if has_keys:
|
||||
l = lines.pop(0)
|
||||
d = l.split(None,2)
|
||||
|
||||
if d[0] != 'wif:':
|
||||
return "Invalid key line in file: '%s'" % l
|
||||
if not is_wif(d[1]):
|
||||
return "'%s': invalid Bitcoin key" % d[1]
|
||||
|
||||
a.wif = unicode(d[1])
|
||||
|
||||
ret.append(a)
|
||||
|
||||
if has_keys and keypress_confirm('Check key-to-address validity?'):
|
||||
wif2addr_f = get_wif2addr_f()
|
||||
llen = len(ret)
|
||||
for n,e in enumerate(ret):
|
||||
msg_r('\rVerifying keys %s/%s' % (n+1,llen))
|
||||
if e.addr != wif2addr_f(e.wif):
|
||||
return "Key doesn't match address!\n %s\n %s" % (e.wif,e.addr)
|
||||
msg(' - done')
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def _parse_addrfile(fn,buf=[],has_keys=False,exit_on_error=True):
|
||||
|
||||
if buf: lines = remove_comments(buf.splitlines()) # DOS-safe
|
||||
else: lines = get_lines_from_file(fn,'address data',trim_comments=True)
|
||||
|
||||
try:
|
||||
sid,obrace = lines[0].split()
|
||||
except:
|
||||
errmsg = "Invalid first line: '%s'" % lines[0]
|
||||
else:
|
||||
cbrace = lines[-1]
|
||||
if obrace != '{':
|
||||
errmsg = "'%s': invalid first line" % lines[0]
|
||||
elif cbrace != '}':
|
||||
errmsg = "'%s': invalid last line" % cbrace
|
||||
elif not is_mmgen_seed_id(sid):
|
||||
errmsg = "'%s': invalid Seed ID" % sid
|
||||
else:
|
||||
ret = _parse_addrfile_body(lines[1:-1],has_keys)
|
||||
if type(ret) == list: return sid,ret
|
||||
else: errmsg = ret
|
||||
|
||||
if exit_on_error: die(3,errmsg)
|
||||
else: return False
|
||||
|
||||
|
||||
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 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])
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
self.ext = get_extension(fn)
|
||||
self.ftype = None # the file's associated class
|
||||
|
||||
# This should be done before license msg instead
|
||||
# check_infile(fn)
|
||||
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)
|
||||
|
||||
if not ftype:
|
||||
self.ext = get_extension(fn)
|
||||
if not (self.ext):
|
||||
die(2,"Unrecognized extension '.%s' for file '%s'" % (self.ext,fn))
|
||||
|
||||
# TODO: Check for Windows
|
||||
mode = (os.O_RDONLY,os.O_RDWR)[bool(write)]
|
||||
|
|
|
|||
|
|
@ -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,46 +64,27 @@ 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
|
||||
|
||||
salt_len = 16
|
||||
aesctr_iv_len = 16
|
||||
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
|
||||
|
||||
hash_presets = {
|
||||
|
|
@ -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_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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)))
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
355
mmgen/obj.py
355
mmgen/obj.py
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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':
|
||||
|
|
|
|||
13
mmgen/rpc.py
13
mmgen/rpc.py
|
|
@ -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]:
|
||||
|
|
|
|||
105
mmgen/seed.py
105
mmgen/seed.py
|
|
@ -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))
|
||||
|
|
|
|||
126
mmgen/tool.py
126
mmgen/tool.py
|
|
@ -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')))
|
||||
tool_usage(prog_name, command)
|
||||
sys.exit(1)
|
||||
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')
|
||||
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('Command requires exactly %s keyword argument%s'
|
||||
% (len(c_kwargs),suf(c_kwargs,'k')))
|
||||
tool_usage(prog_name,command)
|
||||
|
||||
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]))
|
||||
|
|
|
|||
250
mmgen/tw.py
250
mmgen/tw.py
|
|
@ -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
|
||||
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))
|
||||
while True:
|
||||
self.cols = get_terminal_size()[0]
|
||||
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)
|
||||
|
|
|
|||
375
mmgen/tx.py
375
mmgen/tx.py
|
|
@ -20,86 +20,17 @@
|
|||
tx.py: Bitcoin transaction routines
|
||||
"""
|
||||
|
||||
import sys, os
|
||||
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'
|
||||
|
||||
|
||||
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=None):
|
||||
def sign(self,c,tx_num_str,keys):
|
||||
|
||||
if keys:
|
||||
qmsg('Passing %s key%s to bitcoind' % (len(keys),suf(keys,'k')))
|
||||
dmsg('Keys:\n %s' % '\n '.join(keys))
|
||||
if not keys:
|
||||
msg('No keys. Cannot sign!')
|
||||
return False
|
||||
|
||||
sig_data = [dict([(k,d[k]) for k in 'txid','vout','scriptPubKey']) for d in self.inputs]
|
||||
qmsg('Passing %s key%s to bitcoind' % (len(keys),suf(keys,'k')))
|
||||
dmsg('Keys:\n %s' % '\n '.join(keys))
|
||||
|
||||
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)
|
||||
|
||||
|
||||
|
|
|
|||
142
mmgen/util.py
142
mmgen/util.py
|
|
@ -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 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
23
scripts/compute-file-chksum.py
Executable 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)
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
6
test/ref/FFB367[1.234].rawtx
Normal file
6
test/ref/FFB367[1.234].rawtx
Normal 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
|
||||
|
|
@ -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.
233
test/test.py
233
test/test.py
|
|
@ -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))
|
||||
|
|
@ -39,7 +55,7 @@ scripts = (
|
|||
'walletchk', 'walletconv', 'walletgen'
|
||||
)
|
||||
|
||||
tb_cmd = 'scripts/traceback.py'
|
||||
tb_cmd = 'scripts/traceback.py'
|
||||
hincog_fn = 'rand_data'
|
||||
hincog_bytes = 1024*1024
|
||||
hincog_offset = 98765
|
||||
|
|
@ -114,19 +130,31 @@ 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': {
|
||||
'mmdat': 'walletgen4',
|
||||
'mmbrain': 'walletgen4',
|
||||
'addrs': 'addrgen4',
|
||||
'rawtx': 'txcreate4',
|
||||
'sigtx': 'txsign4',
|
||||
'rawtx': 'txcreate4',
|
||||
'sigtx': 'txsign4',
|
||||
},
|
||||
'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
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue