Support for Segwit (P2SH-P2WPKH) addresses:
- Generate Segwit addresses by invoking 'mmgen-addrgen' with the '--type segwit' option - Import Segwit addresses into the tracking wallet as usual - Segwit and legacy MMGen addresses are distinguished by 'S' and 'L' identifiers in the tracking wallet and command line Transaction example: mmgen-txcreate F00BAA12:L:21,1.23 F00BAA12:S:1 (spend 1.23 BTC to legacy address 21 of your default wallet (with Seed ID F00BAA12) and send the change to Segwit address 1) Segwit and legacy addresses for a given seed are generated from different sub-seeds so are cryptographically unrelated to each other. Since MMGen's legacy P2PKH addresses are uncompressed, use of the new Segwit addresses significantly reduces transaction size. Until Segwit activation on mainnet, users can try out the new functionality on testnet or in regtest mode.
This commit is contained in:
parent
919bd62a79
commit
3b0257358b
33 changed files with 2038 additions and 1150 deletions
|
|
@ -17,6 +17,9 @@
|
|||
# Uncomment to force 256-color output when 'color' is true:
|
||||
# force_256_color true
|
||||
|
||||
# Uncomment to use regtest mode (this also sets testnet to true):
|
||||
# regtest true
|
||||
|
||||
# Uncomment to use testnet instead of mainnet:
|
||||
# testnet true
|
||||
|
||||
|
|
|
|||
333
mmgen/addr.py
333
mmgen/addr.py
|
|
@ -23,31 +23,17 @@ addr.py: Address generation/display routines for the MMGen suite
|
|||
from hashlib import sha256,sha512
|
||||
from binascii import hexlify,unhexlify
|
||||
from mmgen.common import *
|
||||
from mmgen.bitcoin import privnum2addr,hex2wif,wif2hex
|
||||
from mmgen.bitcoin import hex2wif,wif2hex,wif_is_compressed
|
||||
from mmgen.obj import *
|
||||
from mmgen.tx import *
|
||||
from mmgen.tw import *
|
||||
|
||||
pnm = g.proj_name
|
||||
|
||||
def _test_for_keyconv(silent=False):
|
||||
no_keyconv_errmsg = """
|
||||
Executable '{kconv}' unavailable. Please install '{kconv}' from the {vgen}
|
||||
package on your system or specify the secp256k1 library.
|
||||
""".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.strip())
|
||||
return False
|
||||
return True
|
||||
|
||||
def _test_for_secp256k1(silent=False):
|
||||
no_secp256k1_errmsg = """
|
||||
secp256k1 library unavailable. Will use '{kconv}', or failing that, the (slow)
|
||||
native Python ECDSA library for address generation.
|
||||
""".format(kconv=g.keyconv_exec)
|
||||
secp256k1 library unavailable. Using (slow) native Python ECDSA library for address generation.
|
||||
"""
|
||||
try:
|
||||
from mmgen.secp256k1 import priv2pub
|
||||
assert priv2pub(os.urandom(32),1)
|
||||
|
|
@ -56,58 +42,68 @@ native Python ECDSA library for address generation.
|
|||
return False
|
||||
return True
|
||||
|
||||
def _wif2addr_python(wif):
|
||||
def _pubhex2addr(pubhex,mmtype):
|
||||
if mmtype == 'L':
|
||||
from mmgen.bitcoin import hexaddr2addr,hash160
|
||||
return hexaddr2addr(hash160(pubhex))
|
||||
elif mmtype == 'S':
|
||||
from mmgen.bitcoin import pubhex2segwitaddr
|
||||
return pubhex2segwitaddr(pubhex)
|
||||
else:
|
||||
die(2,"'{}': mmtype unrecognized".format(mmtype))
|
||||
|
||||
def _privhex2addr_python(privhex,compressed,mmtype):
|
||||
assert compressed or mmtype != 'S'
|
||||
from mmgen.bitcoin import privnum2pubhex
|
||||
pubhex = privnum2pubhex(int(privhex,16),compressed=compressed)
|
||||
return _pubhex2addr(pubhex,mmtype=mmtype)
|
||||
|
||||
def _privhex2addr_secp256k1(privhex,compressed,mmtype):
|
||||
assert compressed or mmtype != 'S'
|
||||
from mmgen.secp256k1 import priv2pub
|
||||
pubhex = hexlify(priv2pub(unhexlify(privhex),int(compressed)))
|
||||
return _pubhex2addr(pubhex,mmtype=mmtype)
|
||||
|
||||
def _wif2addr_python(wif,mmtype):
|
||||
privhex = wif2hex(wif)
|
||||
if not privhex: return False
|
||||
return privnum2addr(int(privhex,16),wif[0] != ('5','9')[g.testnet])
|
||||
return _privhex2addr_python(privhex,wif_is_compressed(wif),mmtype=mmtype)
|
||||
|
||||
def _wif2addr_keyconv(wif):
|
||||
if wif[0] == ('5','9')[g.testnet]:
|
||||
from subprocess import check_output
|
||||
return check_output(['keyconv', wif]).split()[1]
|
||||
else:
|
||||
return _wif2addr_python(wif)
|
||||
def _wif2addr_secp256k1(wif,mmtype):
|
||||
privhex = wif2hex(wif)
|
||||
if not privhex: return False
|
||||
return _privhex2addr_secp256k1(privhex,wif_is_compressed(wif),mmtype=mmtype)
|
||||
|
||||
def _wif2addr_secp256k1(wif):
|
||||
return _privhex2addr_secp256k1(wif2hex(wif),wif[0] != ('5','9')[g.testnet])
|
||||
def keygen_wif2pubhex(wif,selector):
|
||||
privhex = wif2hex(wif)
|
||||
if not privhex: return False
|
||||
if selector == 1:
|
||||
from mmgen.secp256k1 import priv2pub
|
||||
return hexlify(priv2pub(unhexlify(privhex),int(wif_is_compressed(wif))))
|
||||
elif selector == 0:
|
||||
from mmgen.bitcoin import privnum2pubhex
|
||||
return privnum2pubhex(int(privhex,16),compressed=wif_is_compressed(wif))
|
||||
|
||||
def _privhex2addr_python(privhex,compressed=False):
|
||||
return privnum2addr(int(privhex,16),compressed)
|
||||
|
||||
def _privhex2addr_keyconv(privhex,compressed=False):
|
||||
if compressed:
|
||||
return privnum2addr(int(privhex,16),compressed)
|
||||
else:
|
||||
from subprocess import check_output
|
||||
return check_output(['keyconv', hex2wif(privhex,compressed=False)]).split()[1]
|
||||
|
||||
def _privhex2addr_secp256k1(privhex,compressed=False):
|
||||
from mmgen.secp256k1 import priv2pub
|
||||
from mmgen.bitcoin import hexaddr2addr,pubhex2hexaddr
|
||||
pubkey = priv2pub(unhexlify(privhex),int(compressed))
|
||||
return hexaddr2addr(pubhex2hexaddr(hexlify(pubkey)))
|
||||
|
||||
def _keygen_selector(generator=None):
|
||||
if generator:
|
||||
if generator == 3 and _test_for_secp256k1(): return 2
|
||||
elif generator in (2,3) and _test_for_keyconv(): return 1
|
||||
else:
|
||||
if opt.key_generator == 3 and _test_for_secp256k1(): return 2
|
||||
elif opt.key_generator in (2,3) and _test_for_keyconv(): return 1
|
||||
def keygen_selector(generator=None):
|
||||
if _test_for_secp256k1() and generator != 1:
|
||||
if opt.key_generator != 1:
|
||||
return 1
|
||||
msg('Using (slow) native Python ECDSA library for address generation')
|
||||
return 0
|
||||
|
||||
def get_wif2addr_f(generator=None):
|
||||
gen = _keygen_selector(generator=generator)
|
||||
return (_wif2addr_python,_wif2addr_keyconv,_wif2addr_secp256k1)[gen]
|
||||
gen = keygen_selector(generator=generator)
|
||||
return (_wif2addr_python,_wif2addr_secp256k1)[gen]
|
||||
|
||||
def get_privhex2addr_f(generator=None):
|
||||
gen = _keygen_selector(generator=generator)
|
||||
return (_privhex2addr_python,_privhex2addr_keyconv,_privhex2addr_secp256k1)[gen]
|
||||
gen = keygen_selector(generator=generator)
|
||||
return (_privhex2addr_python,_privhex2addr_secp256k1)[gen]
|
||||
|
||||
|
||||
class AddrListEntry(MMGenListItem):
|
||||
attrs = 'idx','addr','label','wif','sec'
|
||||
idx = MMGenListItemAttr('idx','AddrIdx')
|
||||
wif = MMGenListItemAttr('wif','WifKey')
|
||||
|
||||
class AddrListChksum(str,Hilite):
|
||||
color = 'pink'
|
||||
|
|
@ -119,7 +115,7 @@ class AddrListChksum(str,Hilite):
|
|||
# print '[{}]'.format(' '.join(lines))
|
||||
return str.__new__(cls,make_chksum_N(' '.join(lines), nchars=16, sep=True))
|
||||
|
||||
class AddrListID(unicode,Hilite):
|
||||
class AddrListIDStr(unicode,Hilite):
|
||||
color = 'green'
|
||||
trunc_ok = False
|
||||
def __new__(cls,addrlist,fmt_str=None):
|
||||
|
|
@ -138,7 +134,15 @@ class AddrListID(unicode,Hilite):
|
|||
ret += ',', i
|
||||
prev = i
|
||||
s = ''.join([unicode(i) for i in ret])
|
||||
return unicode.__new__(cls,fmt_str.format(s) if fmt_str else '{}[{}]'.format(addrlist.seed_id,s))
|
||||
|
||||
if fmt_str:
|
||||
ret = fmt_str.format(s)
|
||||
elif addrlist.al_id.mmtype == 'L':
|
||||
ret = '{}[{}]'.format(addrlist.al_id.sid,s)
|
||||
else:
|
||||
ret = '{}-{}[{}]'.format(addrlist.al_id.sid,addrlist.al_id.mmtype,s)
|
||||
|
||||
return unicode.__new__(cls,ret)
|
||||
|
||||
class AddrList(MMGenObject): # Address info for a single seed ID
|
||||
msgs = {
|
||||
|
|
@ -150,7 +154,7 @@ class AddrList(MMGenObject): # Address info for a single seed ID
|
|||
# A text label of {n} characters or less may be added to the right of each
|
||||
# address, and it will be appended to the bitcoind wallet label upon import.
|
||||
# The label may contain any printable ASCII symbol.
|
||||
""".strip().format(n=MMGenAddrLabel.max_len,pnm=pnm),
|
||||
""".strip().format(n=TwComment.max_len,pnm=pnm),
|
||||
'record_chksum': """
|
||||
Record this checksum: it will be used to verify the address file in the future
|
||||
""".strip(),
|
||||
|
|
@ -169,40 +173,46 @@ Removed %s duplicate wif key%s from keylist (also in {pnm} key-address file
|
|||
gen_keys = False
|
||||
has_keys = False
|
||||
ext = 'addrs'
|
||||
dfl_mmtype = MMGenAddrType('L')
|
||||
cook_hash_rounds = 10 # not too many rounds, so hand decoding can still be feasible
|
||||
|
||||
def __init__(self,addrfile='',sid='',adata=[],seed='',addr_idxs='',src='',
|
||||
addrlist='',keylist='',do_chksum=True,chksum_only=False):
|
||||
def __init__(self,addrfile='',al_id='',adata=[],seed='',addr_idxs='',src='',
|
||||
addrlist='',keylist='',mmtype=None,do_chksum=True,chksum_only=False):
|
||||
|
||||
self.update_msgs()
|
||||
mmtype = mmtype or self.dfl_mmtype
|
||||
assert mmtype in MMGenAddrType.mmtypes
|
||||
|
||||
if addrfile: # data from MMGen address file
|
||||
(sid,adata) = self.parse_file(addrfile)
|
||||
elif sid and adata: # data from tracking wallet
|
||||
if seed and addr_idxs: # data from seed + idxs
|
||||
self.al_id,src = AddrListID(seed.sid,mmtype),'gen'
|
||||
adata = self.generate(seed,addr_idxs,compressed=(mmtype=='S'))
|
||||
elif addrfile: # data from MMGen address file
|
||||
adata = self.parse_file(addrfile) # sets self.al_id
|
||||
elif al_id and adata: # data from tracking wallet
|
||||
self.al_id = al_id
|
||||
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 set(addrlist)]
|
||||
self.al_id = None
|
||||
adata = AddrListList([AddrListEntry(addr=a) for a in set(addrlist)])
|
||||
elif keylist: # data from flat key list
|
||||
sid,do_chksum = None,False
|
||||
adata = [AddrListEntry(wif=k) for k in set(keylist)]
|
||||
self.al_id = None
|
||||
adata = AddrListList([AddrListEntry(wif=k) for k in set(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')
|
||||
elif al_id or adata:
|
||||
die(3,'Must specify both al_id and adata')
|
||||
else:
|
||||
die(3,'Incorrect arguments for %s' % type(self).__name__)
|
||||
|
||||
# sid,adata now set
|
||||
self.seed_id = sid
|
||||
# al_id,adata now set
|
||||
self.data = adata
|
||||
self.num_addrs = len(adata)
|
||||
self.fmt_data = ''
|
||||
self.id_str = None
|
||||
self.chksum = None
|
||||
self.id_str = AddrListID(self)
|
||||
|
||||
if self.al_id == None: return
|
||||
|
||||
self.id_str = AddrListIDStr(self)
|
||||
|
||||
if type(self) == KeyList: return
|
||||
|
||||
|
|
@ -221,17 +231,17 @@ Removed %s duplicate wif key%s from keylist (also in {pnm} key-address file
|
|||
if k not in self.msgs:
|
||||
self.msgs[k] = AddrList.msgs[k]
|
||||
|
||||
def generate(self,seed,addrnums):
|
||||
def generate(self,seed,addrnums,compressed):
|
||||
assert type(addrnums) is AddrIdxList
|
||||
self.seed_id = SeedID(seed=seed)
|
||||
seed = seed.get_data()
|
||||
assert compressed in (True,False,None)
|
||||
|
||||
seed = seed.get_data()
|
||||
seed = self.cook_seed(seed)
|
||||
|
||||
if self.gen_addrs:
|
||||
privhex2addr_f = get_privhex2addr_f()
|
||||
privhex2addr_f = get_privhex2addr_f() # choose internal ECDSA or secp256k1 generator
|
||||
|
||||
t_addrs,num,pos,out = len(addrnums),0,0,[]
|
||||
t_addrs,num,pos,out = len(addrnums),0,0,AddrListList()
|
||||
|
||||
while pos != t_addrs:
|
||||
seed = sha512(seed).digest()
|
||||
|
|
@ -250,10 +260,10 @@ Removed %s duplicate wif key%s from keylist (also in {pnm} key-address file
|
|||
sec = sha256(sha256(seed).digest()).hexdigest()
|
||||
|
||||
if self.gen_addrs:
|
||||
e.addr = privhex2addr_f(sec,compressed=False)
|
||||
e.addr = privhex2addr_f(sec,compressed=compressed,mmtype=self.al_id.mmtype)
|
||||
|
||||
if self.gen_keys:
|
||||
e.wif = hex2wif(sec,compressed=False)
|
||||
e.wif = hex2wif(sec,compressed=compressed)
|
||||
if opt.b16: e.sec = sec
|
||||
|
||||
if self.gen_passwds:
|
||||
|
|
@ -261,14 +271,31 @@ Removed %s duplicate wif key%s from keylist (also in {pnm} key-address file
|
|||
dmsg('Key {:>03}: {}'.format(pos,sec))
|
||||
|
||||
out.append(e)
|
||||
if g.debug: print 'generate():\n', e.pformat()
|
||||
|
||||
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))
|
||||
self.al_id.hl(),t_addrs,self.gen_desc,suf(t_addrs,self.gen_desc_pl),' '*15))
|
||||
return out
|
||||
|
||||
def chk_addr_or_pw(self,addr): return is_btc_addr(addr)
|
||||
def is_mainnet(self):
|
||||
return self.data[0].addr.is_mainnet()
|
||||
|
||||
def cook_seed(self,seed): return seed
|
||||
def is_for_current_chain(self):
|
||||
return self.data[0].addr.is_for_current_chain()
|
||||
|
||||
def chk_addr_or_pw(self,addr):
|
||||
return {'L':'p2pkh','S':'p2sh'}[self.al_id.mmtype] == is_btc_addr(addr).addr_fmt
|
||||
|
||||
def cook_seed(self,seed):
|
||||
if self.al_id.mmtype == 'L':
|
||||
return seed
|
||||
else:
|
||||
from mmgen.crypto import sha256_rounds
|
||||
import hmac
|
||||
key = self.al_id.mmtype.name
|
||||
cseed = hmac.new(seed,key,sha256).digest()
|
||||
dmsg('Seed: {}\nKey: {}\nCseed: {}\nCseed len: {}'.format(hexlify(seed),key,hexlify(cseed),len(cseed)))
|
||||
return sha256_rounds(cseed,self.cook_hash_rounds)
|
||||
|
||||
def encrypt(self,desc='new key list'):
|
||||
from mmgen.crypto import mmgen_encrypt
|
||||
|
|
@ -284,7 +311,7 @@ Removed %s duplicate wif key%s from keylist (also in {pnm} key-address file
|
|||
return [e.idx for e in self.data]
|
||||
|
||||
def addrs(self):
|
||||
return ['%s:%s'%(self.seed_id,e.idx) for e in self.data]
|
||||
return ['%s:%s'%(self.al_id.sid,e.idx) for e in self.data]
|
||||
|
||||
def addrpairs(self):
|
||||
return [(e.idx,e.addr) for e in self.data]
|
||||
|
|
@ -313,21 +340,18 @@ Removed %s duplicate wif key%s from keylist (also in {pnm} key-address file
|
|||
e.label = comment
|
||||
|
||||
def make_reverse_dict(self,btcaddrs):
|
||||
d,b = {},btcaddrs
|
||||
d,b = MMGenDict(),btcaddrs
|
||||
for e in self.data:
|
||||
try:
|
||||
d[b[b.index(e.addr)]] = ('%s:%s'%(self.seed_id,e.idx),e.label)
|
||||
d[b[b.index(e.addr)]] = MMGenID('{}:{}'.format(self.al_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]
|
||||
return [AddrListFlatEntry(mmid='{}:{}'.format(self.al_id,e.idx),addr=e.addr,wif=e.wif)
|
||||
for e in self.data]
|
||||
|
||||
def remove_dups(self,cmplist,key='wif'):
|
||||
pop_list = []
|
||||
|
|
@ -338,7 +362,7 @@ Removed %s duplicate wif key%s from keylist (also in {pnm} key-address file
|
|||
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')))
|
||||
vmsg(self.msgs['removed_dups'] % (len(pop_list),suf(removed,'s')))
|
||||
|
||||
def add_wifs(self,al_key):
|
||||
if not al_key: return
|
||||
|
|
@ -355,13 +379,15 @@ Removed %s duplicate wif key%s from keylist (also in {pnm} key-address file
|
|||
|
||||
def get_addrs(self): return self.get('addr')
|
||||
def get_wifs(self): return self.get('wif')
|
||||
def get_addr_wif_pairs(self):
|
||||
return [(d.addr,d.wif) for d in self.data if hasattr(d,'wif')]
|
||||
|
||||
def generate_addrs(self):
|
||||
def generate_addrs_from_keylist(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)
|
||||
e.addr = wif2addr_f(e.wif,mmtype='L') # 'L' == p2pkh
|
||||
qmsg('\rGenerated addresses from keylist: %s/%s ' % (n,len(d)))
|
||||
|
||||
def format(self,enable_comments=False):
|
||||
|
|
@ -385,9 +411,11 @@ Removed %s duplicate wif key%s from keylist (also in {pnm} key-address file
|
|||
|
||||
if type(self) == PasswordList:
|
||||
out.append(u'{} {} {}:{} {{'.format(
|
||||
self.seed_id,self.pw_id_str,self.pw_fmt,self.pw_len))
|
||||
self.al_id.sid,self.pw_id_str,self.pw_fmt,self.pw_len))
|
||||
elif self.al_id.mmtype == 'L':
|
||||
out.append('{} {{'.format(self.al_id.sid))
|
||||
else:
|
||||
out.append('{} {{'.format(self.seed_id))
|
||||
out.append('{} {} {{'.format(self.al_id.sid,MMGenAddrType.mmtypes[self.al_id.mmtype].upper()))
|
||||
|
||||
fs = ' {:<%s} {:<34}{}' % len(str(self.data[-1].idx))
|
||||
for e in self.data:
|
||||
|
|
@ -410,7 +438,7 @@ Removed %s duplicate wif key%s from keylist (also in {pnm} key-address file
|
|||
if self.has_keys and len(lines) % 2:
|
||||
return 'Key-address file has odd number of lines'
|
||||
|
||||
ret = []
|
||||
ret = AddrListList()
|
||||
|
||||
while lines:
|
||||
l = lines.pop(0)
|
||||
|
|
@ -444,7 +472,7 @@ Removed %s duplicate wif key%s from keylist (also in {pnm} key-address file
|
|||
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):
|
||||
if e.addr != wif2addr_f(e.wif,mmtype=self.al_id.mmtype):
|
||||
return "Key doesn't match address!\n %s\n %s" % (e.wif,e.addr)
|
||||
msg(' - done')
|
||||
|
||||
|
|
@ -463,29 +491,44 @@ Removed %s duplicate wif key%s from keylist (also in {pnm} key-address file
|
|||
return do_error("Too few lines in address file (%s)" % len(lines))
|
||||
|
||||
ls = lines[0].split()
|
||||
ls_len = (2,4)[type(self)==PasswordList]
|
||||
if len(ls) != ls_len:
|
||||
if not 1 < len(ls) < 5:
|
||||
return do_error("Invalid first line for {} file: '{}'".format(self.gen_desc,lines[0]))
|
||||
if ls[-1] != '{':
|
||||
if ls.pop() != '{':
|
||||
return do_error("'%s': invalid first line" % ls)
|
||||
if lines[-1] != '}':
|
||||
return do_error("'%s': invalid last line" % lines[-1])
|
||||
if not is_mmgen_seed_id(ls[0]):
|
||||
|
||||
sid = ls.pop(0)
|
||||
if not is_mmgen_seed_id(sid):
|
||||
return do_error("'%s': invalid Seed ID" % ls[0])
|
||||
|
||||
if type(self) == PasswordList:
|
||||
self.pw_id_str = MMGenPWIDString(ls[1])
|
||||
ss = ls[2].split(':')
|
||||
if type(self) == PasswordList and len(ls) == 2:
|
||||
ss = ls.pop().split(':')
|
||||
if len(ss) != 2:
|
||||
return do_error("'%s': invalid password length specifier (must contain colon)" % ls[2])
|
||||
self.set_pw_fmt(ss[0])
|
||||
self.set_pw_len(ss[1])
|
||||
self.pw_id_str = MMGenPWIDString(ls.pop())
|
||||
mmtype = MMGenPasswordType('P')
|
||||
elif len(ls) == 1:
|
||||
mmtype = ls.pop().lower()
|
||||
try:
|
||||
mmtype = MMGenAddrType(mmtype)
|
||||
except:
|
||||
return do_error(u"'{}': invalid address type in address file. Must be one of: {}".format(
|
||||
mmtype,' '.join(MMGenAddrType.mmtypes.values()).upper()))
|
||||
elif len(ls) == 0:
|
||||
mmtype = MMGenAddrType('L')
|
||||
else:
|
||||
return do_error(u"Invalid first line for {} file: '{}'".format(self.gen_desc,lines[0]))
|
||||
|
||||
ret = self.parse_file_body(lines[1:-1])
|
||||
if type(ret) != list:
|
||||
return do_error(ret)
|
||||
self.al_id = AddrListID(SeedID(sid=sid),mmtype)
|
||||
|
||||
return ls[0],ret
|
||||
data = self.parse_file_body(lines[1:-1])
|
||||
if not issubclass(type(data),list):
|
||||
return do_error(data)
|
||||
|
||||
return data
|
||||
|
||||
class KeyAddrList(AddrList):
|
||||
data_desc = 'key-address'
|
||||
|
|
@ -525,7 +568,7 @@ class PasswordList(AddrList):
|
|||
# A text label of {n} characters or less may be added to the right of each
|
||||
# password. The label may contain any printable ASCII symbol.
|
||||
#
|
||||
""".strip().format(n=MMGenAddrLabel.max_len,pnm=pnm),
|
||||
""".strip().format(n=TwComment.max_len,pnm=pnm),
|
||||
'record_chksum': """
|
||||
Record this checksum: it will be used to verify the password file in the future
|
||||
""".strip()
|
||||
|
|
@ -546,7 +589,6 @@ Record this checksum: it will be used to verify the password file in the future
|
|||
'b58': { 'min_len': 8 , 'max_len': 36 ,'dfl_len': 20, 'desc': 'base-58 password' },
|
||||
'b32': { 'min_len': 10 ,'max_len': 42 ,'dfl_len': 24, 'desc': 'base-32 password' }
|
||||
}
|
||||
cook_hash_rounds = 10 # not too many rounds, so hand decoding can still be feasible
|
||||
|
||||
def __init__(self,infile=None,seed=None,pw_idxs=None,pw_id_str=None,pw_len=None,pw_fmt=None,
|
||||
chksum_only=False,chk_params_only=False):
|
||||
|
|
@ -554,7 +596,7 @@ Record this checksum: it will be used to verify the password file in the future
|
|||
self.update_msgs()
|
||||
|
||||
if infile:
|
||||
(self.seed_id,self.data) = self.parse_file(infile) # sets self.pw_id_str,self.pw_fmt,self.pw_len
|
||||
self.data = self.parse_file(infile) # sets self.pw_id_str,self.pw_fmt,self.pw_len
|
||||
else:
|
||||
for k in seed,pw_idxs: assert chk_params_only or k
|
||||
for k in pw_id_str,pw_fmt: assert k
|
||||
|
|
@ -562,8 +604,8 @@ Record this checksum: it will be used to verify the password file in the future
|
|||
self.set_pw_fmt(pw_fmt)
|
||||
self.set_pw_len(pw_len)
|
||||
if chk_params_only: return
|
||||
self.seed_id = seed.sid
|
||||
self.data = self.generate(seed,pw_idxs)
|
||||
self.al_id = AddrListID(seed.sid,MMGenPasswordType('P'))
|
||||
self.data = self.generate(seed,pw_idxs,compressed=None)
|
||||
|
||||
self.num_addrs = len(self.data)
|
||||
self.fmt_data = ''
|
||||
|
|
@ -572,8 +614,8 @@ Record this checksum: it will be used to verify the password file in the future
|
|||
if chksum_only:
|
||||
Msg(self.chksum)
|
||||
else:
|
||||
self.id_str = AddrListID(self,fmt_str=u'{}-{}-{}-{}[{{}}]'.format(
|
||||
self.seed_id,self.pw_id_str,self.pw_fmt,self.pw_len))
|
||||
fs = u'{}-{}-{}-{}[{{}}]'.format(self.al_id.sid,self.pw_id_str,self.pw_fmt,self.pw_len)
|
||||
self.id_str = AddrListIDStr(self,fs)
|
||||
qmsg(u'Checksum for {} data {}: {}'.format(self.data_desc,self.id_str.hl(),self.chksum.hl()))
|
||||
qmsg(self.msgs[('record_chksum','check_chksum')[bool(infile)]])
|
||||
|
||||
|
|
@ -626,6 +668,7 @@ Record this checksum: it will be used to verify the password file in the future
|
|||
from mmgen.crypto import sha256_rounds
|
||||
# Changing either pw_fmt, pw_len or id_str will cause a different, unrelated set of
|
||||
# passwords to be generated: this is what we want
|
||||
# NB: In original implementation, pw_id_str was 'baseN', not 'bN'
|
||||
fid_str = '{}:{}:{}'.format(self.pw_fmt,self.pw_len,self.pw_id_str.encode('utf8'))
|
||||
dmsg(u'Full ID string: {}'.format(fid_str.decode('utf8')))
|
||||
# Original implementation was 'cseed = seed + fid_str'; hmac was not used
|
||||
|
|
@ -646,54 +689,58 @@ re-import your addresses.
|
|||
}
|
||||
|
||||
def __init__(self,source=None):
|
||||
self.sids = {}
|
||||
self.al_ids = {}
|
||||
if source == 'tw': self.add_tw_data()
|
||||
|
||||
def seed_ids(self):
|
||||
return self.sids.keys()
|
||||
return self.al_ids.keys()
|
||||
|
||||
def addrlist(self,sid):
|
||||
# TODO: Validate sid
|
||||
if sid in self.sids:
|
||||
return self.sids[sid]
|
||||
def addrlist(self,al_id):
|
||||
# TODO: Validate al_id
|
||||
if al_id in self.al_ids:
|
||||
return self.al_ids[al_id]
|
||||
|
||||
def mmaddr2btcaddr(self,mmaddr):
|
||||
al_id,idx = MMGenID(mmaddr).rsplit(':',1)
|
||||
btcaddr = ''
|
||||
sid,idx = mmaddr.split(':')
|
||||
if sid in self.seed_ids():
|
||||
btcaddr = self.addrlist(sid).btcaddr(int(idx))
|
||||
return btcaddr
|
||||
if al_id in self.al_ids:
|
||||
btcaddr = self.addrlist(al_id).btcaddr(int(idx))
|
||||
return btcaddr or None
|
||||
|
||||
def btcaddr2mmaddr(self,btcaddr):
|
||||
d = self.make_reverse_dict([btcaddr])
|
||||
return (d.values()[0][0]) if d else None
|
||||
|
||||
def add_tw_data(self):
|
||||
vmsg_r('Getting address data from tracking wallet...')
|
||||
vmsg('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):
|
||||
maddr,label = parse_tw_acct_label(acct)
|
||||
if maddr:
|
||||
l = TwLabel(acct,on_fail='silent')
|
||||
if l and l.mmid.type == 'mmgen':
|
||||
obj = l.mmid.obj
|
||||
i += 1
|
||||
if len(addrlist) != 1:
|
||||
die(2,self.msgs['too_many_acct_addresses'] % acct)
|
||||
seed_id,idx = maddr.split(':')
|
||||
if seed_id not in data:
|
||||
data[seed_id] = []
|
||||
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(AddrList(sid=sid,adata=data[sid]))
|
||||
al_id = AddrListID(SeedID(sid=obj.sid),MMGenAddrType(obj.mmtype))
|
||||
if al_id not in data:
|
||||
data[al_id] = []
|
||||
data[al_id].append(AddrListEntry(idx=obj.idx,addr=addrlist[0],label=l.comment))
|
||||
vmsg('{n} {pnm} addresses found, {m} accounts total'.format(n=i,pnm=pnm,m=len(accts)))
|
||||
for al_id in data:
|
||||
self.add(AddrList(al_id=al_id,adata=AddrListList(sorted(data[al_id],key=lambda a: a.idx))))
|
||||
|
||||
def add(self,addrlist):
|
||||
if type(addrlist) == AddrList:
|
||||
self.sids[addrlist.seed_id] = addrlist
|
||||
self.al_ids[addrlist.al_id] = addrlist
|
||||
return True
|
||||
else:
|
||||
raise TypeError, 'Error: object %s is not of type AddrList' % repr(addrlist)
|
||||
|
||||
def make_reverse_dict(self,btcaddrs):
|
||||
d = {}
|
||||
for sid in self.sids:
|
||||
d.update(self.sids[sid].make_reverse_dict(btcaddrs))
|
||||
d = MMGenDict()
|
||||
for al_id in self.al_ids:
|
||||
d.update(self.al_ids[al_id].make_reverse_dict(btcaddrs))
|
||||
return d
|
||||
|
|
|
|||
|
|
@ -53,33 +53,40 @@ b58a='123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
|
|||
|
||||
from mmgen.globalvars import g
|
||||
|
||||
def hash256(hexnum): # take hex, return hex - OP_HASH256
|
||||
return sha256(sha256(unhexlify(hexnum)).digest()).hexdigest()
|
||||
|
||||
def hash160(hexnum): # take hex, return hex - OP_HASH160
|
||||
return hashlib_new('ripemd160',sha256(unhexlify(hexnum)).digest()).hexdigest()
|
||||
|
||||
pubhex2hexaddr = hash160
|
||||
def hash256(hexnum): # take hex, return hex - OP_HASH256
|
||||
return sha256(sha256(unhexlify(hexnum)).digest()).hexdigest()
|
||||
|
||||
# devdoc/ref_transactions.md:
|
||||
btc_ver_nums = {
|
||||
'p2pkh': (('00','1'),('6f','mn')),
|
||||
'p2sh': (('05','3'),('c4','2'))
|
||||
}
|
||||
addr_pfxs = { 'mainnet': '13', 'testnet': 'mn2', 'regtest': 'mn2' }
|
||||
vnum_all = tuple([k for k,v in btc_ver_nums['p2pkh'] + btc_ver_nums['p2sh']])
|
||||
|
||||
def hexaddr2addr(hexaddr,p2sh=False):
|
||||
# devdoc/ref_transactions.md:
|
||||
s = ('00','6f','05','c4')[g.testnet+(2*p2sh)] + hexaddr.strip()
|
||||
s = vnum_all[g.testnet+(2*p2sh)] + hexaddr.strip()
|
||||
lzeroes = (len(s) - len(s.lstrip('0'))) / 2
|
||||
return ('1' * lzeroes) + _numtob58(int(s+hash256(s)[:8],16))
|
||||
|
||||
def verify_addr(addr,verbose=False,return_hex=False):
|
||||
def verify_addr(addr,verbose=False,return_hex=False,return_type=False):
|
||||
addr = addr.strip()
|
||||
for vers_num,ldigit in ('00','1'),('05','3'),('6f','mn'),('c4','2'):
|
||||
if addr[0] not in ldigit: continue
|
||||
num = _b58tonum(addr)
|
||||
if num == False: break
|
||||
addr_hex = '{:050x}'.format(num)
|
||||
if addr_hex[:2] != vers_num: continue
|
||||
if hash256(addr_hex[:42])[:8] == addr_hex[42:]:
|
||||
return addr_hex[2:42] if return_hex else True
|
||||
else:
|
||||
if verbose: Msg("Invalid checksum in address '%s'" % addr)
|
||||
break
|
||||
|
||||
for k in ('p2pkh','p2sh'):
|
||||
for ver_num,ldigit in btc_ver_nums[k]:
|
||||
if addr[0] not in ldigit: continue
|
||||
num = _b58tonum(addr)
|
||||
if num == False: break
|
||||
addr_hex = '{:050x}'.format(num)
|
||||
if addr_hex[:2] != ver_num: continue
|
||||
if hash256(addr_hex[:42])[:8] == addr_hex[42:]:
|
||||
return addr_hex[2:42] if return_hex else k if return_type else True
|
||||
else:
|
||||
if verbose: Msg("Invalid checksum in address '%s'" % addr)
|
||||
break
|
||||
|
||||
if verbose: Msg("Invalid address '%s'" % addr)
|
||||
return False
|
||||
|
|
@ -97,7 +104,7 @@ def _b58tonum(b58num):
|
|||
b58num = b58num.strip()
|
||||
for i in b58num:
|
||||
if not i in b58a: return False
|
||||
return sum([b58a.index(n) * (58**i) for i,n in enumerate(list(b58num[::-1]))])
|
||||
return sum(b58a.index(n) * (58**i) for i,n in enumerate(list(b58num[::-1])))
|
||||
|
||||
# The following are MMGen internal (non-Bitcoin) b58 functions
|
||||
|
||||
|
|
@ -146,9 +153,11 @@ def b58decode_pad(s):
|
|||
|
||||
# Compressed address support:
|
||||
|
||||
def wif_is_compressed(wif): return wif[0] != ('5','9')[g.testnet]
|
||||
|
||||
def wif2hex(wif):
|
||||
wif = wif.strip()
|
||||
compressed = wif[0] != ('5','9')[g.testnet]
|
||||
compressed = wif_is_compressed(wif)
|
||||
num = _b58tonum(wif)
|
||||
if num == False: return False
|
||||
key = '{:x}'.format(num)
|
||||
|
|
@ -178,5 +187,16 @@ def privnum2pubhex(numpriv,compressed=False):
|
|||
else:
|
||||
return '04'+pubkey
|
||||
|
||||
def privnum2addr(numpriv,compressed=False):
|
||||
return hexaddr2addr(pubhex2hexaddr(privnum2pubhex(numpriv,compressed)))
|
||||
def privnum2addr(numpriv,compressed=False,segwit=False): # used only by tool and testsuite
|
||||
pubhex = privnum2pubhex(numpriv,compressed)
|
||||
return pubhex2segwitaddr(pubhex) if segwit else hexaddr2addr(hash160(pubhex))
|
||||
|
||||
# Segwit:
|
||||
def pubhex2redeem_script(pubhex):
|
||||
# https://bitcoincore.org/en/segwit_wallet_dev/
|
||||
# The P2SH redeemScript is always 22 bytes. It starts with a OP_0, followed
|
||||
# by a canonical push of the keyhash (i.e. 0x0014{20-byte keyhash})
|
||||
return '0014' + hash160(pubhex)
|
||||
|
||||
def pubhex2segwitaddr(pubhex):
|
||||
return hexaddr2addr(hash160(pubhex2redeem_script(pubhex)),p2sh=True)
|
||||
|
|
|
|||
|
|
@ -25,3 +25,17 @@ from mmgen.globalvars import g
|
|||
import mmgen.opts as opts
|
||||
from mmgen.opts import opt
|
||||
from mmgen.util import *
|
||||
|
||||
pw_note = """
|
||||
For passphrases all combinations of whitespace are equal and leading and
|
||||
trailing space is ignored. This permits reading passphrase or brainwallet
|
||||
data from a multi-line file with free spacing and indentation.
|
||||
""".strip()
|
||||
|
||||
bw_note = """
|
||||
BRAINWALLET NOTE:
|
||||
|
||||
To thwart dictionary attacks, it's recommended to use a strong hash preset
|
||||
with brainwallets. For a brainwallet passphrase to generate the correct
|
||||
seed, the same seed length and hash preset parameters must always be used.
|
||||
""".strip()
|
||||
|
|
|
|||
|
|
@ -32,11 +32,15 @@ class Filename(MMGenObject):
|
|||
self.basename = os.path.basename(fn)
|
||||
self.ext = get_extension(fn)
|
||||
self.ftype = None # the file's associated class
|
||||
self.mtime = None
|
||||
self.ctime = None
|
||||
self.atime = None
|
||||
|
||||
from mmgen.seed import SeedSource
|
||||
from mmgen.tx import MMGenTX
|
||||
if ftype:
|
||||
if type(ftype) == type:
|
||||
if issubclass(ftype,SeedSource):
|
||||
if issubclass(ftype,SeedSource) or issubclass(ftype,MMGenTX):
|
||||
self.ftype = ftype
|
||||
# elif: # other MMGen file types
|
||||
else:
|
||||
|
|
@ -44,6 +48,7 @@ class Filename(MMGenObject):
|
|||
else:
|
||||
die(3,"'%s': not a class" % ftype)
|
||||
else:
|
||||
# TODO: other file types
|
||||
self.ftype = SeedSource.ext_to_type(self.ext)
|
||||
if not self.ftype:
|
||||
die(3,"'%s': not a recognized extension for SeedSource" % self.ext)
|
||||
|
|
@ -64,6 +69,23 @@ class Filename(MMGenObject):
|
|||
os.close(fd)
|
||||
else:
|
||||
self.size = os.stat(fn).st_size
|
||||
self.mtime = os.stat(fn).st_mtime
|
||||
self.ctime = os.stat(fn).st_ctime
|
||||
self.atime = os.stat(fn).st_atime
|
||||
|
||||
class MMGenFileList(list,MMGenObject):
|
||||
|
||||
def __init__(self,fns,ftype):
|
||||
flist = [Filename(fn,ftype) for fn in fns]
|
||||
return list.__init__(self,flist)
|
||||
|
||||
def names(self):
|
||||
return [f.name for f in self]
|
||||
|
||||
def sort_by_age(self,key='mtime',reverse=False):
|
||||
if key not in ('atime','ctime','mtime'):
|
||||
die(1,"'{}': illegal sort key".format(key))
|
||||
self.sort(key=lambda a: getattr(a,key),reverse=reverse)
|
||||
|
||||
def find_files_in_dir(ftype,fdir,no_dups=False):
|
||||
if type(ftype) != type:
|
||||
|
|
|
|||
|
|
@ -31,13 +31,15 @@ from mmgen.obj import BTCAmt
|
|||
|
||||
class g(object):
|
||||
|
||||
skip_segwit_active_check = bool(os.getenv('MMGEN_TEST_SUITE'))
|
||||
|
||||
def die(ev=0,s=''):
|
||||
if s: sys.stderr.write(s+'\n')
|
||||
sys.exit(ev)
|
||||
# Variables - these might be altered at runtime:
|
||||
|
||||
version = '0.9.1'
|
||||
release_date = 'May 2017'
|
||||
version = '0.9.199'
|
||||
release_date = 'July 2017'
|
||||
|
||||
proj_name = 'MMGen'
|
||||
proj_url = 'https://github.com/mmgen/mmgen'
|
||||
|
|
@ -70,6 +72,10 @@ class g(object):
|
|||
color = (False,True)[sys.stdout.isatty()]
|
||||
force_256_color = False
|
||||
testnet = False
|
||||
regtest = False
|
||||
chain = None # set by first call to bitcoin_connection()
|
||||
chains = 'mainnet','testnet','regtest'
|
||||
bitcoind_version = None # set by first call to bitcoin_connection()
|
||||
rpc_host = ''
|
||||
rpc_port = 0
|
||||
rpc_user = ''
|
||||
|
|
@ -97,7 +103,7 @@ class g(object):
|
|||
# User opt sets global var:
|
||||
common_opts = (
|
||||
'color','no_license','rpc_host','rpc_port','testnet','rpc_user','rpc_password',
|
||||
'bitcoin_data_dir','force_256_color'
|
||||
'bitcoin_data_dir','force_256_color','regtest'
|
||||
)
|
||||
required_opts = (
|
||||
'quiet','verbose','debug','outdir','echo_passphrase','passwd_file','stdout',
|
||||
|
|
@ -114,7 +120,7 @@ class g(object):
|
|||
cfg_file_opts = (
|
||||
'color','debug','hash_preset','http_timeout','no_license','rpc_host','rpc_port',
|
||||
'quiet','tx_fee_adj','usr_randchars','testnet','rpc_user','rpc_password',
|
||||
'bitcoin_data_dir','force_256_color','max_tx_fee'
|
||||
'bitcoin_data_dir','force_256_color','max_tx_fee','regtest'
|
||||
)
|
||||
env_opts = (
|
||||
'MMGEN_BOGUS_WALLET_DATA',
|
||||
|
|
@ -127,6 +133,7 @@ class g(object):
|
|||
'MMGEN_NO_LICENSE',
|
||||
'MMGEN_RPC_HOST',
|
||||
'MMGEN_TESTNET'
|
||||
'MMGEN_REGTEST'
|
||||
)
|
||||
|
||||
min_screen_width = 80
|
||||
|
|
@ -136,8 +143,6 @@ class g(object):
|
|||
global_sets_opt = ['minconf','seed_len','hash_preset','usr_randchars','debug',
|
||||
'quiet','tx_confs','tx_fee_adj','key_generator']
|
||||
|
||||
keyconv_exec = 'keyconv'
|
||||
|
||||
mins_per_block = 9
|
||||
passwd_max_tries = 5
|
||||
|
||||
|
|
@ -151,8 +156,8 @@ class g(object):
|
|||
aesctr_iv_len = 16
|
||||
hincog_chk_len = 8
|
||||
|
||||
key_generators = 'python-ecdsa','keyconv','secp256k1' # 1,2,3
|
||||
key_generator = 3 # secp256k1 is default
|
||||
key_generators = 'python-ecdsa','secp256k1' # '1','2'
|
||||
key_generator = 2 # secp256k1 is default
|
||||
|
||||
hash_presets = {
|
||||
# Scrypt params:
|
||||
|
|
|
|||
|
|
@ -25,18 +25,19 @@ from mmgen.common import *
|
|||
from mmgen.crypto import *
|
||||
from mmgen.addr import *
|
||||
from mmgen.seed import SeedSource
|
||||
MAT = MMGenAddrType
|
||||
|
||||
if sys.argv[0].split('-')[-1] == 'keygen':
|
||||
gen_what = 'keys'
|
||||
gen_desc = 'secret keys'
|
||||
opt_filter = None
|
||||
note2 = 'By default, both addresses and secret keys are generated.\n\n'
|
||||
note_addrkey = 'By default, both addresses and secret keys are generated.\n\n'
|
||||
else:
|
||||
gen_what = 'addresses'
|
||||
gen_desc = 'addresses'
|
||||
opt_filter = 'hbcdeiHOKlpzPqrSv-'
|
||||
note2 = ''
|
||||
note1 = """
|
||||
opt_filter = 'hbcdeiHOKlpzPqrStv-'
|
||||
note_addrkey = ''
|
||||
note_secp256k1 = """
|
||||
If available, the secp256k1 library will be used for address generation.
|
||||
""".strip()
|
||||
|
||||
|
|
@ -70,6 +71,8 @@ opts_data = {
|
|||
-r, --usr-randchars=n Get 'n' characters of additional randomness from user
|
||||
(min={g.min_urandchars}, max={g.max_urandchars}, default={g.usr_randchars})
|
||||
-S, --stdout Print {what} to stdout
|
||||
-t, --type=t Choose address type. Options: see ADDRESS TYPES below
|
||||
(default: {dmat})
|
||||
-v, --verbose Produce more verbose output
|
||||
-x, --b16 Print secret keys in hexadecimal too
|
||||
""".format(
|
||||
|
|
@ -77,7 +80,8 @@ opts_data = {
|
|||
pnm=g.proj_name,
|
||||
kgs=' '.join(['{}:{}'.format(n,k) for n,k in enumerate(g.key_generators,1)]),
|
||||
kg=g.key_generator,
|
||||
what=gen_what,g=g
|
||||
what=gen_what,g=g,
|
||||
dmat="'{}' or '{}'".format(MAT.dfl_mmtype,MAT.mmtypes[MAT.dfl_mmtype])
|
||||
),
|
||||
'notes': """
|
||||
|
||||
|
|
@ -87,26 +91,32 @@ opts_data = {
|
|||
Address indexes are given as a comma-separated list and/or hyphen-separated
|
||||
range(s).
|
||||
|
||||
{n2}{n1}
|
||||
{n_addrkey}{n_secp}
|
||||
|
||||
ADDRESS TYPES:
|
||||
{n_at}
|
||||
|
||||
NOTES FOR ALL GENERATOR COMMANDS
|
||||
|
||||
{o.pw_note}
|
||||
{pwn}
|
||||
|
||||
{o.bw_note}
|
||||
{bwn}
|
||||
|
||||
FMT CODES:
|
||||
{f}
|
||||
""".format(
|
||||
n1=note1,n2=note2,
|
||||
n_secp=note_secp256k1,n_addrkey=note_addrkey,pwn=pw_note,bwn=bw_note,
|
||||
f='\n '.join(SeedSource.format_fmt_codes().splitlines()),
|
||||
n_at='\n '.join(["'{}', '{}'".format(k,v) for k,v in MAT.mmtypes.items()]),
|
||||
o=opts
|
||||
)
|
||||
}
|
||||
|
||||
cmd_args = opts.init(opts_data,add_opts=['b16'],opt_filter=opt_filter)
|
||||
|
||||
errmsg = "'{}': invalid parameter for --type option".format(opt.type)
|
||||
addr_type = MAT(opt.type or MAT.dfl_mmtype,errmsg=errmsg)
|
||||
|
||||
if len(cmd_args) < 1: opts.usage()
|
||||
idxs = AddrIdxList(fmt_str=cmd_args.pop())
|
||||
|
||||
|
|
@ -117,7 +127,7 @@ do_license_msg()
|
|||
ss = SeedSource(sf)
|
||||
|
||||
i = (gen_what=='addresses') or bool(opt.no_addresses)*2
|
||||
al = (KeyAddrList,AddrList,KeyList)[i](seed=ss.seed,addr_idxs=idxs)
|
||||
al = (KeyAddrList,AddrList,KeyList)[i](seed=ss.seed,addr_idxs=idxs,mmtype=addr_type)
|
||||
al.format()
|
||||
|
||||
if al.gen_addrs and opt.print_checksum:
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import time
|
|||
|
||||
from mmgen.common import *
|
||||
from mmgen.addr import AddrList,KeyAddrList
|
||||
from mmgen.obj import TwLabel
|
||||
|
||||
# In batch mode, bitcoind just rescans each address separately anyway, so make
|
||||
# --batch and --rescan incompatible.
|
||||
|
|
@ -35,6 +36,7 @@ opts_data = {
|
|||
'options': """
|
||||
-h, --help Print this help message
|
||||
--, --longhelp Print help message for long options (common options)
|
||||
-a, --address=a Import the single Bitcoin address 'a'
|
||||
-b, --batch Import all addresses in one RPC call.
|
||||
-l, --addrlist Address source is a flat list of (non-MMGen) Bitcoin addresses
|
||||
-k, --keyaddr-file Address source is a key-address file
|
||||
|
|
@ -53,29 +55,46 @@ The --batch and --rescan options cannot be used together.
|
|||
|
||||
cmd_args = opts.init(opts_data)
|
||||
|
||||
def import_mmgen_list(infile):
|
||||
al = (AddrList,KeyAddrList)[bool(opt.keyaddr_file)](infile)
|
||||
if al.al_id.mmtype == 'S':
|
||||
from mmgen.tx import segwit_is_active
|
||||
if not segwit_is_active():
|
||||
rdie(2,'Segwit is not active on this chain. Cannot import Segwit addresses')
|
||||
return al
|
||||
|
||||
def import_flat_list(lines):
|
||||
al = AddrList(addrlist=lines)
|
||||
from mmgen.bitcoin import verify_addr
|
||||
qmsg_r('Validating addresses...')
|
||||
for e in al.data:
|
||||
if not verify_addr(e.addr,verbose=True):
|
||||
die(2,'\n%s: invalid address' % e.addr)
|
||||
if e.addr.addr_fmt == 'p2sh':
|
||||
fs = "\n'{}':\n Non-{} P2SH addresses may not be imported into the tracking wallet"
|
||||
rdie(2,fs.format(e.addr,g.proj_name))
|
||||
return al
|
||||
|
||||
if len(cmd_args) == 1:
|
||||
infile = cmd_args[0]
|
||||
check_infile(infile)
|
||||
if opt.addrlist:
|
||||
lines = get_lines_from_file(
|
||||
infile,'non-{pnm} addresses'.format(pnm=g.proj_name),trim_comments=True)
|
||||
ai = AddrList(addrlist=lines)
|
||||
al = import_flat_list(lines)
|
||||
else:
|
||||
ai = (AddrList,KeyAddrList)[bool(opt.keyaddr_file)](infile)
|
||||
al = import_mmgen_list(infile)
|
||||
elif len(cmd_args) == 0 and opt.address:
|
||||
al = import_flat_list([opt.address])
|
||||
infile = 'command line'
|
||||
else:
|
||||
die(1,"""
|
||||
You must specify an {pnm} address file (or a list of non-{pnm} addresses
|
||||
with the '--addrlist' option)
|
||||
You must specify an {pnm} address file, a single address, or a list of
|
||||
non-{pnm} addresses with the '--addrlist' option)
|
||||
""".strip().format(pnm=g.proj_name))
|
||||
|
||||
from mmgen.bitcoin import verify_addr
|
||||
qmsg_r('Validating addresses...')
|
||||
for e in ai.data:
|
||||
if not verify_addr(e.addr,verbose=True):
|
||||
die(2,'%s: invalid address' % e.addr)
|
||||
|
||||
m = (' from Seed ID %s' % ai.seed_id) if ai.seed_id else ''
|
||||
qmsg('OK. %s addresses%s' % (ai.num_addrs,m))
|
||||
m = ' from Seed ID {}'.format(al.al_id.sid) if hasattr(al.al_id,'sid') else ''
|
||||
qmsg('OK. {} addresses{}'.format(al.num_addrs,m))
|
||||
|
||||
if not opt.test:
|
||||
c = bitcoin_connection()
|
||||
|
|
@ -104,8 +123,8 @@ def import_address(addr,label,rescan):
|
|||
global err_flag
|
||||
err_flag = True
|
||||
|
||||
w_n_of_m = len(str(ai.num_addrs)) * 2 + 2
|
||||
w_mmid = '' if opt.addrlist else len(str(max(ai.idxs()))) + 12
|
||||
w_n_of_m = len(str(al.num_addrs)) * 2 + 2
|
||||
w_mmid = '' if opt.addrlist else len(str(max(al.idxs()))) + 12
|
||||
|
||||
if opt.rescan:
|
||||
import threading
|
||||
|
|
@ -113,19 +132,26 @@ if opt.rescan:
|
|||
else:
|
||||
msg_fmt = '\r%-{}s %-34s %s'.format(w_n_of_m, w_mmid)
|
||||
|
||||
msg("Importing %s addresses from '%s'%s" %
|
||||
(len(ai.data),infile,('',' (batch mode)')[bool(opt.batch)]))
|
||||
msg("Importing {} address{} from {}{}".format(
|
||||
len(al.data), suf(al.data,'es'), infile,
|
||||
('',' (batch mode)')[bool(opt.batch)]
|
||||
))
|
||||
|
||||
if not al.is_for_current_chain():
|
||||
die(2,"Address{} not compatible with {} chain!".format((' list','')[bool(opt.address)],g.chain))
|
||||
|
||||
arg_list = []
|
||||
for n,e in enumerate(ai.data):
|
||||
for n,e in enumerate(al.data):
|
||||
if e.idx:
|
||||
label = '%s:%s' % (ai.seed_id,e.idx)
|
||||
label = '{}:{}'.format(al.al_id,e.idx)
|
||||
if e.label: label += ' ' + e.label
|
||||
m = label
|
||||
else:
|
||||
label = 'btc:{}'.format(e.addr)
|
||||
m = 'non-'+g.proj_name
|
||||
|
||||
label = TwLabel(label)
|
||||
|
||||
if opt.batch:
|
||||
arg_list.append((e.addr,label,False))
|
||||
elif opt.rescan:
|
||||
|
|
@ -138,7 +164,7 @@ for n,e in enumerate(ai.data):
|
|||
while True:
|
||||
if t.is_alive():
|
||||
elapsed = int(time.time() - start)
|
||||
count = '%s/%s:' % (n+1, ai.num_addrs)
|
||||
count = '%s/%s:' % (n+1, al.num_addrs)
|
||||
msg_r(msg_fmt % (secs_to_hms(elapsed),count,e.addr,'(%s)' % m))
|
||||
time.sleep(1)
|
||||
else:
|
||||
|
|
@ -147,7 +173,7 @@ for n,e in enumerate(ai.data):
|
|||
break
|
||||
else:
|
||||
import_address(e.addr,label,False)
|
||||
count = '%s/%s:' % (n+1, ai.num_addrs)
|
||||
count = '%s/%s:' % (n+1, al.num_addrs)
|
||||
msg_r(msg_fmt % (count, e.addr, '(%s)' % m))
|
||||
if err_flag: die(2,'\nImport failed')
|
||||
msg(' - OK')
|
||||
|
|
|
|||
|
|
@ -98,9 +98,9 @@ EXAMPLE:
|
|||
|
||||
NOTES FOR ALL GENERATOR COMMANDS
|
||||
|
||||
{o.pw_note}
|
||||
{pwn}
|
||||
|
||||
{o.bw_note}
|
||||
{bwn}
|
||||
|
||||
FMT CODES:
|
||||
{f}
|
||||
|
|
@ -108,6 +108,7 @@ FMT CODES:
|
|||
f='\n '.join(SeedSource.format_fmt_codes().splitlines()),
|
||||
o=opts,g=g,d58=dfl_len['b58'],d32=dfl_len['b32'],
|
||||
ml=MMGenPWIDString.max_len,
|
||||
pwn=pw_note,bwn=bw_note,
|
||||
fs="', '".join(MMGenPWIDString.forbidden)
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,30 +39,23 @@ opts_data = {
|
|||
""".format(g=g),
|
||||
'notes': """
|
||||
|
||||
COMMANDS:{}
|
||||
COMMANDS
|
||||
{}
|
||||
Type '{} help <command> for help on a particular command
|
||||
""".format(tool.cmd_help,g.prog_name)
|
||||
}
|
||||
|
||||
cmd_args = opts.init(opts_data,
|
||||
add_opts=[
|
||||
'hidden_incog_input_params',
|
||||
'in_fmt'
|
||||
])
|
||||
cmd_args = opts.init(opts_data,add_opts=['hidden_incog_input_params','in_fmt'])
|
||||
|
||||
if len(cmd_args) < 1:
|
||||
opts.usage()
|
||||
sys.exit(1)
|
||||
if len(cmd_args) < 1: opts.usage()
|
||||
|
||||
command = cmd_args.pop(0)
|
||||
Command = cmd_args.pop(0).capitalize()
|
||||
|
||||
if command not in tool.cmd_data:
|
||||
die(1,"'%s': no such command" % command)
|
||||
if Command == 'Help' and not cmd_args: tool.usage(None)
|
||||
|
||||
if cmd_args and cmd_args[0] == '--help':
|
||||
tool.tool_usage(g.prog_name, command)
|
||||
sys.exit()
|
||||
if Command not in tool.cmd_data:
|
||||
die(1,"'%s': no such command" % Command.lower())
|
||||
|
||||
args,kwargs = tool.process_args(g.prog_name, command, cmd_args)
|
||||
ret = tool.__dict__[command](*args,**kwargs)
|
||||
args,kwargs = tool.process_args(Command,cmd_args)
|
||||
ret = tool.__dict__[Command](*args,**kwargs)
|
||||
sys.exit(0 if ret in (None,True) else 1) # some commands die, some return False on failure
|
||||
|
|
|
|||
|
|
@ -51,5 +51,5 @@ opts_data = {
|
|||
|
||||
cmd_args = opts.init(opts_data)
|
||||
do_license_msg()
|
||||
tx = txcreate(opt,cmd_args,do_info=opt.info)
|
||||
tx = txcreate(cmd_args,do_info=opt.info)
|
||||
tx.write_to_file(ask_write=not opt.yes,ask_overwrite=not opt.yes,ask_write_default_yes=False)
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ kal = get_keyaddrlist(opt)
|
|||
kl = get_keylist(opt)
|
||||
if kl and kal: kl.remove_dups(kal,key='wif')
|
||||
|
||||
tx = txcreate(opt,cmd_args,caller='txdo')
|
||||
tx = txcreate(cmd_args,caller='txdo')
|
||||
txsign(opt,c,tx,seed_files,kl,kal)
|
||||
tx.write_to_file(ask_write=False)
|
||||
|
||||
|
|
|
|||
|
|
@ -44,17 +44,13 @@ if len(cmd_args) == 1:
|
|||
else: opts.usage()
|
||||
|
||||
do_license_msg()
|
||||
tx = MMGenTX(infile)
|
||||
c = bitcoin_connection()
|
||||
|
||||
if not tx.check_signed(c):
|
||||
die(1,'Transaction is not signed!')
|
||||
|
||||
if tx.btc_txid:
|
||||
msg('Warning: transaction has already been sent!')
|
||||
|
||||
tx = MMGenTX(infile) # sig check performed here
|
||||
qmsg("Signed transaction file '%s' is valid" % infile)
|
||||
|
||||
if not tx.marked_signed(c):
|
||||
die(1,'Transaction is not signed!')
|
||||
|
||||
if not opt.yes:
|
||||
tx.view_with_prompt('View transaction data?')
|
||||
if tx.add_comment(): # edits an existing comment, returns true if changed
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ for tx_num,tx_file in enumerate(tx_files,1):
|
|||
tx_num_str = ' #%s' % tx_num
|
||||
tx = MMGenTX(tx_file)
|
||||
|
||||
if tx.check_signed(c):
|
||||
if tx.marked_signed():
|
||||
die(1,'Transaction is already signed!')
|
||||
vmsg("Successfully opened transaction file '%s'" % tx_file)
|
||||
|
||||
|
|
|
|||
|
|
@ -30,8 +30,6 @@ usage = '[opts] [infile]'
|
|||
nargs = 1
|
||||
iaction = 'convert'
|
||||
oaction = 'convert'
|
||||
bw_note = opts.bw_note
|
||||
pw_note = opts.pw_note
|
||||
|
||||
invoked_as = 'passchg' if g.prog_name == 'mmgen-passchg' else g.prog_name.partition('-wallet')[2]
|
||||
|
||||
|
|
@ -99,14 +97,14 @@ opts_data = {
|
|||
),
|
||||
'notes': """
|
||||
|
||||
{pw_note}{bw_note}
|
||||
{pwn}{bwn}
|
||||
|
||||
FMT CODES:
|
||||
{f}
|
||||
""".format(
|
||||
f='\n '.join(SeedSource.format_fmt_codes().splitlines()),
|
||||
pw_note=pw_note,
|
||||
bw_note=('','\n\n' + bw_note)[bool(bw_note)]
|
||||
pwn=pw_note,
|
||||
bwn=('','\n\n' + bw_note)[bool(bw_note)]
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -125,12 +123,11 @@ if invoked_as in ('conv','passchg'):
|
|||
msg(green('Processing input wallet')+dw_msg)
|
||||
|
||||
ss_in = None if invoked_as == 'gen' else SeedSource(sf,passchg=(invoked_as=='passchg'))
|
||||
|
||||
if invoked_as == 'chk':
|
||||
lbl = ss_in.ssdata.label.hl() if hasattr(ss_in.ssdata,'label') else 'NONE'
|
||||
vmsg('Wallet label: {}'.format(lbl))
|
||||
# TODO: display creation date
|
||||
sys.exit()
|
||||
sys.exit(0)
|
||||
|
||||
if invoked_as in ('conv','passchg'):
|
||||
msg(green('Processing output wallet'))
|
||||
|
|
@ -141,7 +138,7 @@ if invoked_as == 'gen':
|
|||
qmsg("This wallet's Seed ID: %s" % ss_out.seed.sid.hl())
|
||||
|
||||
if invoked_as == 'passchg':
|
||||
if not (opt.force_update or [k for k in 'passwd','hash_preset','label'
|
||||
if not (opt.force_update or [k for k in ('passwd','hash_preset','label')
|
||||
if getattr(ss_out.ssdata,k) != getattr(ss_in.ssdata,k)]):
|
||||
die(1,'Password, hash preset and label are unchanged. Taking no action')
|
||||
|
||||
|
|
|
|||
288
mmgen/obj.py
288
mmgen/obj.py
|
|
@ -20,6 +20,7 @@
|
|||
obj.py: MMGen native classes
|
||||
"""
|
||||
|
||||
import sys
|
||||
from decimal import *
|
||||
from mmgen.color import *
|
||||
lvl = 0
|
||||
|
|
@ -27,38 +28,73 @@ lvl = 0
|
|||
class MMGenObject(object):
|
||||
|
||||
# Pretty-print any object of type MMGenObject, recursing into sub-objects - WIP
|
||||
def pprint(self): print self.pformat()
|
||||
# def pmsg(self): sys.stderr.write(self.pformat()+'\n')
|
||||
# def pdie(self): sys.stderr.write(self.pformat()+'\n'); sys.exit(0)
|
||||
def pmsg(self): print(self.pformat())
|
||||
def pdie(self): print(self.pformat()); sys.exit(0)
|
||||
def pformat(self,lvl=0):
|
||||
def do_list(out,e,lvl=0):
|
||||
add_spc = False
|
||||
if e and type(e[0]) not in (str,unicode):
|
||||
out.append('\n')
|
||||
from decimal import Decimal
|
||||
scalars = (str,unicode,int,float,Decimal)
|
||||
def do_list(out,e,lvl=0,is_dict=False):
|
||||
out.append('\n')
|
||||
for i in e:
|
||||
if hasattr(i,'pformat'):
|
||||
out.append('{:>{l}}{}'.format('',i.pformat(lvl=lvl+1),l=(lvl+1)*8))
|
||||
elif type(i) in (str,unicode):
|
||||
add_spc = True
|
||||
out.append(u' {}'.format(repr(i)))
|
||||
elif type(i) == list:
|
||||
out.append(u'{:>{l}}{:16}'.format('','<'+type(i).__name__+'>',l=(lvl*8)+4))
|
||||
do_list(out,i,lvl=lvl)
|
||||
el = i if not is_dict else e[i]
|
||||
if is_dict:
|
||||
out.append('{s}{:<{l}}'.format(i,s=' '*(4*lvl+8),l=10,l2=8*(lvl+1)+8))
|
||||
if hasattr(el,'pformat'):
|
||||
out.append('{:>{l}}{}'.format('',el.pformat(lvl=lvl+1),l=(lvl+1)*8))
|
||||
elif type(el) in scalars:
|
||||
if isList(e):
|
||||
out.append(u'{:>{l}}{:16}\n'.format('',repr(el),l=lvl*8))
|
||||
else:
|
||||
out.append(u' {}'.format(repr(el)))
|
||||
elif isList(el) or isDict(el):
|
||||
indent = 1 if is_dict else lvl*8+4
|
||||
out.append(u'{:>{l}}{:16}'.format('','<'+type(el).__name__+'>',l=indent))
|
||||
if isList(el) and type(el[0]) in scalars: out.append('\n')
|
||||
do_list(out,el,lvl=lvl+1,is_dict=isDict(el))
|
||||
else:
|
||||
out.append(u'{:>{l}}{:16} {}\n'.format('','<'+type(i).__name__+'>',repr(i),l=(lvl*8)+8))
|
||||
out.append(u'{:>{l}}{:16} {}\n'.format('','<'+type(el).__name__+'>',repr(el),l=(lvl*8)+8))
|
||||
out.append('\n')
|
||||
if not e: out.append('{}\n'.format(repr(e)))
|
||||
if add_spc: out.append('\n')
|
||||
out = []
|
||||
out.append(u'<{}>\n'.format(type(self).__name__))
|
||||
d = self.__dict__
|
||||
for k in d:
|
||||
|
||||
from collections import OrderedDict
|
||||
def isDict(obj):
|
||||
return issubclass(type(obj),dict) or issubclass(type(obj),OrderedDict)
|
||||
def isList(obj):
|
||||
return issubclass(type(obj),list) and type(obj) != OrderedDict
|
||||
def isScalar(obj):
|
||||
return any(issubclass(type(obj),t) for t in scalars)
|
||||
|
||||
# print type(self)
|
||||
# print dir(self)
|
||||
# print self.__dict__ # *attributes* of object
|
||||
# print self.__dict__.keys() # *attributes* of object
|
||||
# print self.keys()
|
||||
|
||||
out = [u'<{}>{}\n'.format(type(self).__name__,' '+repr(self) if isScalar(self) else '')]
|
||||
if isList(self) or isDict(self):
|
||||
do_list(out,self,lvl=lvl,is_dict=isDict(self))
|
||||
|
||||
# print repr(self.__dict__.keys())
|
||||
|
||||
for k in self.__dict__:
|
||||
if k in ('_OrderedDict__root', '_OrderedDict__map'): continue # exclude these because of recursion
|
||||
e = getattr(self,k)
|
||||
if type(e) == list:
|
||||
if isList(e) or isDict(e):
|
||||
out.append(u'{:>{l}}{:<10} {:16}'.format('',k,'<'+type(e).__name__+'>',l=(lvl*8)+4))
|
||||
do_list(out,e,lvl=lvl)
|
||||
do_list(out,e,lvl=lvl,is_dict=isDict(e))
|
||||
elif hasattr(e,'pformat') and type(e) != type:
|
||||
out.append(u'{:>{l}}{:10} {}'.format('',k,e.pformat(lvl=lvl+1),l=(lvl*8)+4))
|
||||
else:
|
||||
out.append(u'{:>{l}}{:<10} {:16} {}\n'.format('',k,'<'+type(e).__name__+'>',repr(e),l=(lvl*8)+4))
|
||||
return ''.join(out)
|
||||
out.append(u'{:>{l}}{:<10} {:16} {}\n'.format(
|
||||
'',k,'<'+type(e).__name__+'>',repr(e),l=(lvl*8)+4))
|
||||
|
||||
import re
|
||||
return re.sub('\n+','\n',''.join(out))
|
||||
|
||||
class MMGenList(list,MMGenObject): pass
|
||||
class MMGenDict(dict,MMGenObject): pass
|
||||
|
||||
# Descriptor: https://docs.python.org/2/howto/descriptor.html
|
||||
class MMGenListItemAttr(object):
|
||||
|
|
@ -75,10 +111,10 @@ class MMGenListItemAttr(object):
|
|||
|
||||
class MMGenListItem(MMGenObject):
|
||||
|
||||
addr = MMGenListItemAttr('addr','BTCAddr')
|
||||
amt = MMGenListItemAttr('amt','BTCAmt')
|
||||
mmid = MMGenListItemAttr('mmid','MMGenID')
|
||||
label = MMGenListItemAttr('label','MMGenAddrLabel')
|
||||
addr = MMGenListItemAttr('addr','BTCAddr')
|
||||
amt = MMGenListItemAttr('amt','BTCAmt')
|
||||
mmid = MMGenListItemAttr('mmid','MMGenID')
|
||||
label = MMGenListItemAttr('label','TwComment')
|
||||
|
||||
attrs = ()
|
||||
attrs_priv = ()
|
||||
|
|
@ -91,7 +127,8 @@ class MMGenListItem(MMGenObject):
|
|||
"'{}': 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__','pformat')
|
||||
attrs_base = ('attrs','attrs_priv','attrs_reassign','attrs_base','attr_error','set_error',
|
||||
'__dict__','pformat','pmsg','pdie')
|
||||
|
||||
def __init__(self,*args,**kwargs):
|
||||
if args:
|
||||
|
|
@ -133,7 +170,7 @@ 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__
|
||||
assert on_fail in ('die','return','silent','raise'),"arg_chk in class %s" % cls.__name__
|
||||
|
||||
@staticmethod
|
||||
def init_fail(m,on_fail,silent=False):
|
||||
|
|
@ -142,8 +179,8 @@ class InitErrors(object):
|
|||
if on_fail == 'die': die(1,m)
|
||||
elif on_fail == 'return':
|
||||
if m: msg(m)
|
||||
return None
|
||||
elif on_fail == 'silent': return None
|
||||
return None # TODO: change to False
|
||||
elif on_fail == 'silent': return None # same here
|
||||
elif on_fail == 'raise': raise ValueError,m
|
||||
|
||||
class AddrIdx(int,InitErrors):
|
||||
|
|
@ -167,7 +204,7 @@ class AddrIdx(int,InitErrors):
|
|||
|
||||
return cls.init_fail(m,on_fail)
|
||||
|
||||
class AddrIdxList(list,InitErrors):
|
||||
class AddrIdxList(list,InitErrors,MMGenObject):
|
||||
|
||||
max_len = 1000000
|
||||
|
||||
|
|
@ -176,7 +213,7 @@ class AddrIdxList(list,InitErrors):
|
|||
assert fmt_str or idx_list
|
||||
if idx_list:
|
||||
# dies on failure
|
||||
return list.__init__(self,sorted(set([AddrIdx(i) for i in idx_list])))
|
||||
return list.__init__(self,sorted(set(AddrIdx(i) for i in idx_list)))
|
||||
elif fmt_str:
|
||||
desc = fmt_str
|
||||
ret,fs = [],"'%s': value cannot be converted to address index"
|
||||
|
|
@ -315,17 +352,19 @@ class BTCAmt(Decimal,Hilite,InitErrors):
|
|||
def __neg__(self,other,context=None):
|
||||
return type(self)(Decimal.__neg__(self,other,context))
|
||||
|
||||
class BTCAddr(str,Hilite,InitErrors):
|
||||
class BTCAddr(str,Hilite,InitErrors,MMGenObject):
|
||||
color = 'cyan'
|
||||
width = 34
|
||||
width = 35 # max len of testnet p2sh addr
|
||||
def __new__(cls,s,on_fail='die'):
|
||||
cls.arg_chk(cls,on_fail)
|
||||
m = "'%s': value is not a Bitcoin address" % s
|
||||
me = str.__new__(cls,s)
|
||||
from mmgen.bitcoin import verify_addr
|
||||
if type(s) in (str,unicode,BTCAddr) and verify_addr(s):
|
||||
return me
|
||||
else:
|
||||
m = "'%s': value is not a Bitcoin address" % s
|
||||
from mmgen.bitcoin import verify_addr,addr_pfxs
|
||||
if type(s) in (str,unicode,BTCAddr):
|
||||
me.addr_fmt = verify_addr(s,return_type=True)
|
||||
me.testnet = s[0] in addr_pfxs['testnet']
|
||||
if me.addr_fmt:
|
||||
return me
|
||||
return cls.init_fail(m,on_fail)
|
||||
|
||||
@classmethod
|
||||
|
|
@ -338,6 +377,21 @@ class BTCAddr(str,Hilite,InitErrors):
|
|||
s = s[:kwargs['width']-2] + '..'
|
||||
return Hilite.fmtc(s,**kwargs)
|
||||
|
||||
def is_for_current_chain(self):
|
||||
from mmgen.globalvars import g
|
||||
assert g.chain, 'global chain variable unset'
|
||||
from bitcoin import addr_pfxs
|
||||
return self[0] in addr_pfxs[g.chain]
|
||||
|
||||
def is_mainnet(self):
|
||||
from bitcoin import addr_pfxs
|
||||
return self[0] in addr_pfxs['mainnet']
|
||||
|
||||
def is_in_tracking_wallet(self):
|
||||
from mmgen.rpc import bitcoin_connection
|
||||
d = bitcoin_connection().validateaddress(self)
|
||||
return d['iswatchonly'] and 'account' in d
|
||||
|
||||
class SeedID(str,Hilite,InitErrors):
|
||||
color = 'blue'
|
||||
width = 8
|
||||
|
|
@ -351,6 +405,7 @@ class SeedID(str,Hilite,InitErrors):
|
|||
if type(seed) == Seed:
|
||||
return str.__new__(cls,make_chksum_8(seed.get_data()))
|
||||
elif sid:
|
||||
sid = str(sid)
|
||||
from string import hexdigits
|
||||
if len(sid) == cls.width and set(sid) <= set(hexdigits.upper()):
|
||||
return str.__new__(cls,sid)
|
||||
|
|
@ -358,7 +413,7 @@ class SeedID(str,Hilite,InitErrors):
|
|||
m = "'%s': value cannot be converted to SeedID" % str(seed or sid)
|
||||
return cls.init_fail(m,on_fail)
|
||||
|
||||
class MMGenID(str,Hilite,InitErrors):
|
||||
class MMGenID(str,Hilite,InitErrors,MMGenObject):
|
||||
|
||||
color = 'orange'
|
||||
width = 0
|
||||
|
|
@ -367,15 +422,83 @@ class MMGenID(str,Hilite,InitErrors):
|
|||
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='silent')
|
||||
if sid:
|
||||
idx = AddrIdx(b,on_fail='silent')
|
||||
if idx:
|
||||
return str.__new__(cls,'%s:%s' % (sid,idx))
|
||||
try:
|
||||
ss = s.split(':')
|
||||
assert len(ss) in (2,3)
|
||||
sid = SeedID(sid=ss[0],on_fail='silent')
|
||||
assert sid
|
||||
idx = AddrIdx(ss[-1],on_fail='silent')
|
||||
assert idx
|
||||
t = MMGenAddrType((MMGenAddrType.dfl_mmtype,ss[1])[len(ss) != 2],on_fail='silent')
|
||||
assert t
|
||||
me = str.__new__(cls,'{}:{}:{}'.format(sid,t,idx))
|
||||
me.sid = sid
|
||||
me.mmtype = t
|
||||
me.idx = idx
|
||||
me.al_id = AddrListID(sid,me.mmtype) # key with colon!
|
||||
assert me.al_id
|
||||
me.sort_key = '{}:{}:{:0{w}}'.format(sid,t,idx,w=idx.max_digits)
|
||||
return me
|
||||
except:
|
||||
m = "'%s': value cannot be converted to MMGenID" % s
|
||||
return cls.init_fail(m,on_fail)
|
||||
|
||||
m = "'%s': value cannot be converted to MMGenID" % s
|
||||
class TwMMGenID(str,Hilite,InitErrors,MMGenObject):
|
||||
|
||||
color = 'orange'
|
||||
width = 0
|
||||
trunc_ok = False
|
||||
|
||||
def __new__(cls,s,on_fail='die'):
|
||||
cls.arg_chk(cls,on_fail)
|
||||
obj,sort_key = None,None
|
||||
try:
|
||||
obj = MMGenID(s,on_fail='silent')
|
||||
sort_key,t = obj.sort_key,'mmgen'
|
||||
except:
|
||||
try:
|
||||
assert len(s) > 4 and s[:4] == 'btc:'
|
||||
obj,sort_key,t = str(s),'z_'+s,'non-mmgen'
|
||||
except:
|
||||
pass
|
||||
|
||||
if obj and sort_key:
|
||||
me = str.__new__(cls,obj)
|
||||
me.obj = obj
|
||||
me.sort_key = sort_key
|
||||
me.type = t
|
||||
return me
|
||||
|
||||
m = "'{}': value cannot be converted to {}".format(s,cls.__name__)
|
||||
return cls.init_fail(m,on_fail)
|
||||
|
||||
# contains TwMMGenID,TwComment. Not for display
|
||||
class TwLabel(str,InitErrors,MMGenObject):
|
||||
|
||||
def __new__(cls,s,on_fail='die'):
|
||||
cls.arg_chk(cls,on_fail)
|
||||
try:
|
||||
ss = s.split(None,1)
|
||||
me = str.__new__(cls,s)
|
||||
me.mmid = TwMMGenID(ss[0],on_fail='silent')
|
||||
assert me.mmid
|
||||
me.comment = TwComment(ss[1] if len(ss) == 2 else '',on_fail='silent')
|
||||
assert me.comment != None
|
||||
return me
|
||||
except:
|
||||
m = "'{}': value cannot be converted to {}".format(s,cls.__name__)
|
||||
return cls.init_fail(m,on_fail)
|
||||
|
||||
class HexStr(str,Hilite,InitErrors):
|
||||
color = 'red'
|
||||
trunc_ok = False
|
||||
def __new__(cls,s,on_fail='die',case='lower'):
|
||||
assert case in ('upper','lower')
|
||||
cls.arg_chk(cls,on_fail)
|
||||
from string import hexdigits
|
||||
if set(s) <= set(getattr(hexdigits,case)()) and not len(s) % 2:
|
||||
return str.__new__(cls,s)
|
||||
m = "'{}': value cannot be converted to {}".format(s,cls.__name__)
|
||||
return cls.init_fail(m,on_fail)
|
||||
|
||||
class MMGenTxID(str,Hilite,InitErrors):
|
||||
|
|
@ -396,6 +519,65 @@ class BitcoinTxID(MMGenTxID):
|
|||
width = 64
|
||||
hexcase = 'lower'
|
||||
|
||||
class WifKey(str,Hilite,InitErrors):
|
||||
width = 53
|
||||
color = 'blue'
|
||||
desc = 'WIF key'
|
||||
def __new__(cls,s,on_fail='die',errmsg=None):
|
||||
cls.arg_chk(cls,on_fail)
|
||||
from mmgen.tx import is_wif
|
||||
if is_wif(s):
|
||||
me = str.__new__(cls,s)
|
||||
return me
|
||||
m = errmsg or "'{}': invalid value for {}".format(s,cls.desc)
|
||||
return cls.init_fail(m,on_fail)
|
||||
|
||||
class MMGenAddrType(str,Hilite,InitErrors):
|
||||
width = 1
|
||||
trunc_ok = False
|
||||
color = 'blue'
|
||||
mmtypes = {
|
||||
# TODO 'L' is ambiguous: For user, it means MMGen legacy uncompressed address.
|
||||
# For generator functions, 'L' means any p2pkh address, and 'S' any ps2h address
|
||||
'L': 'legacy',
|
||||
'S': 'segwit',
|
||||
# 'l': 'litecoin',
|
||||
# 'e': 'ethereum',
|
||||
# 'E': 'ethereum_classic',
|
||||
# 'm': 'monero',
|
||||
# 'z': 'zcash',
|
||||
}
|
||||
dfl_mmtype = 'L'
|
||||
def __new__(cls,s,on_fail='die',errmsg=None):
|
||||
cls.arg_chk(cls,on_fail)
|
||||
for k,v in cls.mmtypes.items():
|
||||
if s in (k,v):
|
||||
if s == v: s = k
|
||||
me = str.__new__(cls,s)
|
||||
me.name = cls.mmtypes[s]
|
||||
return me
|
||||
m = errmsg or "'{}': invalid value for {}".format(s,cls.__name__)
|
||||
return cls.init_fail(m,on_fail)
|
||||
|
||||
class MMGenPasswordType(MMGenAddrType):
|
||||
mmtypes = { 'P': 'password' }
|
||||
|
||||
class AddrListID(str,Hilite,InitErrors):
|
||||
width = 10
|
||||
trunc_ok = False
|
||||
color = 'yellow'
|
||||
def __new__(cls,sid,mmtype,on_fail='die'):
|
||||
cls.arg_chk(cls,on_fail)
|
||||
m = "'{}': not a SeedID. Cannot create {}".format(sid,cls.__name__)
|
||||
if type(sid) == SeedID:
|
||||
m = "'{}': not an MMGenAddrType object. Cannot create {}".format(mmtype,cls.__name__)
|
||||
if type(mmtype) in (MMGenAddrType,MMGenPasswordType):
|
||||
me = str.__new__(cls,sid+':'+mmtype) # colon in key is OK
|
||||
me.sid = sid
|
||||
me.mmtype = mmtype
|
||||
return me
|
||||
return cls.init_fail(m,on_fail)
|
||||
|
||||
class MMGenLabel(unicode,Hilite,InitErrors):
|
||||
|
||||
color = 'pink'
|
||||
|
|
@ -425,7 +607,7 @@ class MMGenLabel(unicode,Hilite,InitErrors):
|
|||
elif cls.allowed and not set(list(s)).issubset(set(cls.allowed)):
|
||||
m = u"{} '{}' contains non-allowed symbols: {}".format(capfirst(cls.desc),s,
|
||||
' '.join(set(list(s)) - set(cls.allowed)))
|
||||
elif cls.forbidden and any([ch in s for ch in cls.forbidden]):
|
||||
elif cls.forbidden and any(ch in s for ch in cls.forbidden):
|
||||
m = u"{} '{}' contains one of these forbidden symbols: '{}'".format(capfirst(cls.desc),s,
|
||||
"', '".join(cls.forbidden))
|
||||
else:
|
||||
|
|
@ -437,10 +619,10 @@ class MMGenWalletLabel(MMGenLabel):
|
|||
allowed = [unichr(i+32) for i in range(95)]
|
||||
desc = 'wallet label'
|
||||
|
||||
class MMGenAddrLabel(MMGenLabel):
|
||||
class TwComment(MMGenLabel):
|
||||
max_len = 32
|
||||
allowed = [unichr(i+32) for i in range(95)]
|
||||
desc = 'address label'
|
||||
desc = 'tracking wallet comment'
|
||||
|
||||
class MMGenTXLabel(MMGenLabel):
|
||||
max_len = 72
|
||||
|
|
@ -451,3 +633,5 @@ class MMGenPWIDString(MMGenLabel):
|
|||
min_len = 1
|
||||
desc = 'password ID string'
|
||||
forbidden = list(u' :/\\')
|
||||
|
||||
class AddrListList(list,MMGenObject): pass
|
||||
|
|
|
|||
|
|
@ -27,26 +27,6 @@ from mmgen.globalvars import g
|
|||
import mmgen.share.Opts
|
||||
from mmgen.util import *
|
||||
|
||||
pw_note = """
|
||||
For passphrases all combinations of whitespace are equal and leading and
|
||||
trailing space is ignored. This permits reading passphrase or brainwallet
|
||||
data from a multi-line file with free spacing and indentation.
|
||||
""".strip()
|
||||
|
||||
bw_note = """
|
||||
BRAINWALLET NOTE:
|
||||
|
||||
To thwart dictionary attacks, it's recommended to use a strong hash preset
|
||||
with brainwallets. For a brainwallet passphrase to generate the correct
|
||||
seed, the same seed length and hash preset parameters must always be used.
|
||||
""".strip()
|
||||
|
||||
version_info = """
|
||||
{pgnm_uc} version {g.version}
|
||||
Part of the {pnm} suite, a Bitcoin cold-storage solution for the command line.
|
||||
Copyright (C) {g.Cdates} {g.author} {g.email}
|
||||
""".format(pnm=g.proj_name, g=g, pgnm_uc=g.prog_name.upper()).strip()
|
||||
|
||||
def usage(): Die(2,'USAGE: %s %s' % (g.prog_name, usage_txt))
|
||||
|
||||
def die_on_incompatible_opts(incompat_list):
|
||||
|
|
@ -76,6 +56,7 @@ common_opts_data = """
|
|||
--, --rpc-port=p Communicate with bitcoind listening on port 'p'
|
||||
--, --rpc-user=user Override 'rpcuser' in bitcoin.conf
|
||||
--, --rpc-password=pass Override 'rpcpassword' in bitcoin.conf
|
||||
--, --regtest=0|1 Disable or enable regtest mode
|
||||
--, --testnet=0|1 Disable or enable testnet
|
||||
--, --skip-cfg-file Skip reading the configuration file
|
||||
--, --version Print version information and exit
|
||||
|
|
@ -179,6 +160,13 @@ def override_from_env():
|
|||
setattr(g,gname,set_for_type(val,getattr(g,gname),name,invert_bool))
|
||||
|
||||
def init(opts_data,add_opts=[],opt_filter=None):
|
||||
|
||||
version_info = """
|
||||
{pgnm_uc} version {g.version}
|
||||
Part of the {pnm} suite, a Bitcoin cold-storage solution for the command line.
|
||||
Copyright (C) {g.Cdates} {g.author} {g.email}
|
||||
""".format(pnm=g.proj_name, g=g, pgnm_uc=g.prog_name.upper()).strip()
|
||||
|
||||
opts_data['long_options'] = common_opts_data
|
||||
|
||||
uopts,args,short_opts,long_opts,skipped_opts = \
|
||||
|
|
@ -218,6 +206,8 @@ def init(opts_data,add_opts=[],opt_filter=None):
|
|||
val = getattr(opt,k)
|
||||
if val != None: setattr(g,k,set_for_type(val,getattr(g,k),'--'+k))
|
||||
|
||||
if g.regtest: g.testnet = True # These are equivalent for now
|
||||
|
||||
# Global vars are now final, including g.testnet, so we can set g.data_dir
|
||||
g.data_dir=os.path.normpath(os.path.join(g.data_dir_root,('',g.testnet_name)[g.testnet]))
|
||||
|
||||
|
|
@ -238,15 +228,16 @@ def init(opts_data,add_opts=[],opt_filter=None):
|
|||
|
||||
if opt.show_hash_presets:
|
||||
_show_hash_presets()
|
||||
sys.exit()
|
||||
sys.exit(0)
|
||||
|
||||
if g.debug: opt_postproc_debug()
|
||||
if opt.verbose: opt.quiet = None
|
||||
|
||||
die_on_incompatible_opts(g.incompatible_opts)
|
||||
|
||||
opt_postproc_initializations()
|
||||
|
||||
if g.debug: opt_postproc_debug()
|
||||
|
||||
return args
|
||||
|
||||
def check_opts(usr_opts): # Returns false if any check fails
|
||||
|
|
|
|||
23
mmgen/rpc.py
23
mmgen/rpc.py
|
|
@ -28,8 +28,6 @@ from mmgen.obj import BTCAmt
|
|||
|
||||
class BitcoinRPCConnection(object):
|
||||
|
||||
client_version = 0
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
host=g.rpc_host,port=(8332,18332)[g.testnet],
|
||||
|
|
@ -78,7 +76,7 @@ class BitcoinRPCConnection(object):
|
|||
p = {'method':cmd,'params':args,'id':1}
|
||||
|
||||
def die_maybe(*args):
|
||||
if cf['on_fail'] == 'return':
|
||||
if cf['on_fail'] in ('return','silent'):
|
||||
return 'rpcfail',args
|
||||
else:
|
||||
die(*args[1:])
|
||||
|
|
@ -89,7 +87,7 @@ class BitcoinRPCConnection(object):
|
|||
class MyJSONEncoder(json.JSONEncoder):
|
||||
def default(self, obj):
|
||||
if isinstance(obj, BTCAmt):
|
||||
return (float,str)[caller.client_version>=120000](obj)
|
||||
return (float,str)[g.bitcoind_version>=120000](obj)
|
||||
return json.JSONEncoder.default(self, obj)
|
||||
|
||||
# Can't do UTF-8 labels yet: httplib only ascii?
|
||||
|
|
@ -111,8 +109,9 @@ class BitcoinRPCConnection(object):
|
|||
dmsg(' RPC GETRESPONSE data ==> %s\n' % r.__dict__)
|
||||
|
||||
if r.status != 200:
|
||||
msg_r(yellow('Bitcoind RPC Error: '))
|
||||
msg(red('{} {}'.format(r.status,r.reason)))
|
||||
if cf['on_fail'] != 'silent':
|
||||
msg_r(yellow('Bitcoind RPC Error: '))
|
||||
msg(red('{} {}'.format(r.status,r.reason)))
|
||||
e1 = r.read()
|
||||
try:
|
||||
e3 = json.loads(e1)['error']
|
||||
|
|
@ -142,26 +141,30 @@ class BitcoinRPCConnection(object):
|
|||
|
||||
return ret if cf['batch'] else ret[0]
|
||||
|
||||
|
||||
rpcmethods = (
|
||||
'createrawtransaction',
|
||||
'backupwallet',
|
||||
'createrawtransaction',
|
||||
'decoderawtransaction',
|
||||
'disconnectnode',
|
||||
'estimatefee',
|
||||
'getaddressesbyaccount',
|
||||
'getbalance',
|
||||
'getblock',
|
||||
'getblockchaininfo',
|
||||
'getblockcount',
|
||||
'getblockhash',
|
||||
'getinfo',
|
||||
'getmempoolentry',
|
||||
'getnetworkinfo',
|
||||
'getpeerinfo',
|
||||
'getrawmempool',
|
||||
'getrawtransaction',
|
||||
'gettransaction',
|
||||
'importaddress',
|
||||
'listaccounts',
|
||||
'listunspent',
|
||||
'sendrawtransaction',
|
||||
'signrawtransaction',
|
||||
'getrawmempool',
|
||||
'validateaddress',
|
||||
'walletpassphrase',
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -159,18 +159,21 @@ class SeedSource(MMGenObject):
|
|||
msg('Trying again...')
|
||||
|
||||
@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
|
||||
def get_subclasses_str(cls): # returns name of calling class too
|
||||
return cls.__name__ + ' ' + ''.join([c.get_subclasses_str() for c in cls.__subclasses__()])
|
||||
|
||||
@classmethod
|
||||
def get_subclasses_str(cls):
|
||||
def GetSubclassesTree(cls):
|
||||
return ''.join([c.__name__ +' '+ GetSubclassesTree(c) for c in cls.__subclasses__()])
|
||||
return GetSubclassesTree(cls)
|
||||
def get_subclasses_easy(cls,acc=[]):
|
||||
return [globals()[c] for c in cls.get_subclasses_str().split()]
|
||||
|
||||
@classmethod
|
||||
def get_subclasses(cls): # returns calling class too
|
||||
def GetSubclassesTree(cls,acc):
|
||||
acc += [cls]
|
||||
for c in cls.__subclasses__(): GetSubclassesTree(c,acc)
|
||||
acc = []
|
||||
GetSubclassesTree(cls,acc)
|
||||
return acc
|
||||
|
||||
@classmethod
|
||||
def get_extensions(cls):
|
||||
|
|
@ -1027,8 +1030,8 @@ harder to find, you're advised to choose a much larger file size than this.
|
|||
msg('File size must be an integer no less than %s' %
|
||||
min_fsize)
|
||||
|
||||
from mmgen.tool import rand2file
|
||||
rand2file(fn, str(fsize))
|
||||
from mmgen.tool import Rand2file # threaded routine
|
||||
Rand2file(fn,str(fsize))
|
||||
check_offset = False
|
||||
else:
|
||||
die(1,'Exiting at user request')
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ Opts.py: Generic options handling
|
|||
"""
|
||||
|
||||
import sys, getopt
|
||||
# from mmgen.util import mdie,die,pp_die,pp_msg # DEBUG
|
||||
# from mmgen.util import mdie,die,pdie,pmsg # DEBUG
|
||||
|
||||
def usage(opts_data):
|
||||
print 'USAGE: %s %s' % (opts_data['prog_name'], opts_data['usage'])
|
||||
|
|
@ -54,8 +54,8 @@ def process_opts(argv,opts_data,short_opts,long_opts):
|
|||
opts = {}
|
||||
|
||||
for opt, arg in cl_opts:
|
||||
if opt in ('-h','--help'): print_help(opts_data); sys.exit()
|
||||
elif opt == '--longhelp': print_help(opts_data,longhelp=True); sys.exit()
|
||||
if opt in ('-h','--help'): print_help(opts_data); sys.exit(0)
|
||||
elif opt == '--longhelp': print_help(opts_data,longhelp=True); sys.exit(0)
|
||||
elif opt[:2] == '--' and opt[2:] in long_opts:
|
||||
opts[opt[2:].replace('-','_')] = True
|
||||
elif opt[:2] == '--' and opt[2:]+'=' in long_opts:
|
||||
|
|
|
|||
|
|
@ -48,7 +48,8 @@ def mk_tmpdir(d):
|
|||
try: os.mkdir(d,0755)
|
||||
except OSError as e:
|
||||
if e.errno != 17: raise
|
||||
else: msg("Created directory '%s'" % d)
|
||||
else:
|
||||
qmsg("Created directory '%s'" % d)
|
||||
|
||||
def mk_tmpdir_path(path,cfg):
|
||||
try:
|
||||
|
|
|
|||
577
mmgen/tool.py
577
mmgen/tool.py
|
|
@ -1,4 +1,5 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: UTF-8 -*-
|
||||
#
|
||||
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
|
||||
# Copyright (C)2013-2017 Philemon <mmgen-py@yandex.com>
|
||||
|
|
@ -31,84 +32,99 @@ pnm = g.proj_name
|
|||
|
||||
from collections import OrderedDict
|
||||
cmd_data = OrderedDict([
|
||||
('help', ['<tool command> [str]']),
|
||||
('usage', ['<tool command> [str]']),
|
||||
('strtob58', ['<string> [str-]','pad [int=0]']),
|
||||
('b58tostr', ['<b58 number> [str-]']),
|
||||
('hextob58', ['<hex number> [str-]','pad [int=0]']),
|
||||
('b58tohex', ['<b58 number> [str-]','pad [int=0]']),
|
||||
('b58randenc', []),
|
||||
('b32tohex', ['<b32 num> [str-]','pad [int=0]']),
|
||||
('hextob32', ['<hex num> [str-]','pad [int=0]']),
|
||||
('randhex', ['nbytes [int=32]']),
|
||||
('id8', ['<infile> [str]']),
|
||||
('id6', ['<infile> [str]']),
|
||||
('sha256x2', ['<str, hexstr or filename> [str]', # TODO handle stdin
|
||||
('Help', ['<tool command> [str]']),
|
||||
('Usage', ['<tool command> [str]']),
|
||||
('Strtob58', ['<string> [str-]','pad [int=0]']),
|
||||
('B58tostr', ['<b58 number> [str-]']),
|
||||
('Hextob58', ['<hex number> [str-]','pad [int=0]']),
|
||||
('B58tohex', ['<b58 number> [str-]','pad [int=0]']),
|
||||
('B58randenc', []),
|
||||
('B32tohex', ['<b32 num> [str-]','pad [int=0]']),
|
||||
('Hextob32', ['<hex num> [str-]','pad [int=0]']),
|
||||
('Randhex', ['nbytes [int=32]']),
|
||||
('Id8', ['<infile> [str]']),
|
||||
('Id6', ['<infile> [str]']),
|
||||
('Hash160', ['<hexadecimal string> [str-]']),
|
||||
('Hash256', ['<str, hexstr or filename> [str]', # TODO handle stdin
|
||||
'hex_input [bool=False]','file_input [bool=False]']),
|
||||
('str2id6', ['<string (spaces are ignored)> [str-]']),
|
||||
('hexdump', ['<infile> [str]', 'cols [int=8]', 'line_nums [bool=True]']),
|
||||
('unhexdump', ['<infile> [str]']),
|
||||
('hexreverse', ['<hexadecimal string> [str-]']),
|
||||
('hexlify', ['<string> [str-]']),
|
||||
('rand2file', ['<outfile> [str]','<nbytes> [str]','threads [int=4]','silent [bool=False]']),
|
||||
('Str2id6', ['<string (spaces are ignored)> [str-]']),
|
||||
('Hexdump', ['<infile> [str]', 'cols [int=8]', 'line_nums [bool=True]']),
|
||||
('Unhexdump', ['<infile> [str]']),
|
||||
('Hexreverse', ['<hexadecimal string> [str-]']),
|
||||
('Hexlify', ['<string> [str-]']),
|
||||
('Rand2file', ['<outfile> [str]','<nbytes> [str]','threads [int=4]','silent [bool=False]']),
|
||||
|
||||
('randwif', ['compressed [bool=False]']),
|
||||
('randpair', ['compressed [bool=False]']),
|
||||
('hex2wif', ['<private key in hex format> [str-]', 'compressed [bool=False]']),
|
||||
('wif2hex', ['<wif> [str-]', 'compressed [bool=False]']),
|
||||
('wif2addr', ['<wif> [str-]', 'compressed [bool=False]']),
|
||||
('hexaddr2addr', ['<btc address in hex format> [str-]']),
|
||||
('addr2hexaddr', ['<btc address> [str-]']),
|
||||
('pubkey2addr', ['<public key in hex format> [str-]']),
|
||||
('pubkey2hexaddr', ['<public key in hex format> [str-]']),
|
||||
('privhex2addr', ['<private key in hex format> [str-]','compressed [bool=False]']),
|
||||
('Randwif', ['compressed [bool=False]']),
|
||||
('Randpair', ['compressed [bool=False]','segwit [bool=False]']),
|
||||
('Hex2wif', ['<private key in hex format> [str-]','compressed [bool=False]']),
|
||||
('Wif2hex', ['<wif> [str-]']),
|
||||
('Wif2addr', ['<wif> [str-]','segwit [bool=False]']),
|
||||
('Wif2segwit_pair',['<wif> [str-]']),
|
||||
('Hexaddr2addr', ['<btc address in hex format> [str-]']),
|
||||
('Addr2hexaddr', ['<btc address> [str-]']),
|
||||
('Privhex2addr', ['<private key in hex format> [str-]','compressed [bool=False]','segwit [bool=False]']),
|
||||
('Privhex2pubhex',['<private key in hex format> [str-]','compressed [bool=False]']),
|
||||
('Pubhex2addr', ['<public key in hex format> [str-]','p2sh [bool=False]']), # new
|
||||
('Pubhex2redeem_script',['<public key in hex format> [str-]']), # new
|
||||
('Wif2redeem_script', ['<private key in WIF format> [str-]']), # new
|
||||
|
||||
('hex2mn', ['<hexadecimal string> [str-]',"wordlist [str='electrum']"]),
|
||||
('mn2hex', ['<mnemonic> [str-]', "wordlist [str='electrum']"]),
|
||||
('mn_rand128', ["wordlist [str='electrum']"]),
|
||||
('mn_rand192', ["wordlist [str='electrum']"]),
|
||||
('mn_rand256', ["wordlist [str='electrum']"]),
|
||||
('mn_stats', ["wordlist [str='electrum']"]),
|
||||
('mn_printlist', ["wordlist [str='electrum']"]),
|
||||
('Hex2mn', ['<hexadecimal string> [str-]',"wordlist [str='electrum']"]),
|
||||
('Mn2hex', ['<mnemonic> [str-]', "wordlist [str='electrum']"]),
|
||||
('Mn_rand128', ["wordlist [str='electrum']"]),
|
||||
('Mn_rand192', ["wordlist [str='electrum']"]),
|
||||
('Mn_rand256', ["wordlist [str='electrum']"]),
|
||||
('Mn_stats', ["wordlist [str='electrum']"]),
|
||||
('Mn_printlist', ["wordlist [str='electrum']"]),
|
||||
|
||||
('listaddresses',["addrs [str='']",'minconf [int=1]','showempty [bool=False]','pager [bool=False]','showbtcaddrs [bool=False]']),
|
||||
('getbalance', ['minconf [int=1]']),
|
||||
('txview', ['<{} TX file> [str]'.format(pnm),'pager [bool=False]','terse [bool=False]']),
|
||||
('twview', ["sort [str='age']",'reverse [bool=False]','minconf [int=1]','wide [bool=False]','pager [bool=False]']),
|
||||
('Listaddress',['<{} address> [str]'.format(pnm),'minconf [int=1]','pager [bool=False]','showempty [bool=True]''showbtcaddr [bool=True]']),
|
||||
('Listaddresses',["addrs [str='']",'minconf [int=1]','showempty [bool=False]','pager [bool=False]','showbtcaddrs [bool=False]']),
|
||||
('Getbalance', ['minconf [int=1]']),
|
||||
('Txview', ['<{} TX file(s)> [str]'.format(pnm),'pager [bool=False]','terse [bool=False]',"sort [str='mtime'] (options: 'ctime','atime')",'MARGS']),
|
||||
('Twview', ["sort [str='age']",'reverse [bool=False]','show_days [bool=True]','show_mmid [bool=True]','minconf [int=1]','wide [bool=False]','pager [bool=False]']),
|
||||
|
||||
('add_label', ['<{} address> [str]'.format(pnm),'<label> [str]']),
|
||||
('remove_label', ['<{} address> [str]'.format(pnm)]),
|
||||
('addrfile_chksum', ['<{} addr file> [str]'.format(pnm)]),
|
||||
('keyaddrfile_chksum', ['<{} addr file> [str]'.format(pnm)]),
|
||||
('passwdfile_chksum', ['<{} password file> [str]'.format(pnm)]),
|
||||
('find_incog_data', ['<file or device name> [str]','<Incog ID> [str]','keep_searching [bool=False]']),
|
||||
('Add_label', ['<{} address> [str]'.format(pnm),'<label> [str]']),
|
||||
('Remove_label', ['<{} address> [str]'.format(pnm)]),
|
||||
('Addrfile_chksum', ['<{} addr file> [str]'.format(pnm)]),
|
||||
('Keyaddrfile_chksum', ['<{} addr file> [str]'.format(pnm)]),
|
||||
('Passwdfile_chksum', ['<{} password file> [str]'.format(pnm)]),
|
||||
('Find_incog_data', ['<file or device name> [str]','<Incog ID> [str]','keep_searching [bool=False]']),
|
||||
|
||||
('encrypt', ['<infile> [str]',"outfile [str='']","hash_preset [str='']"]),
|
||||
('decrypt', ['<infile> [str]',"outfile [str='']","hash_preset [str='']"]),
|
||||
('bytespec', ['<bytespec> [str]']),
|
||||
('Encrypt', ['<infile> [str]',"outfile [str='']","hash_preset [str='']"]),
|
||||
('Decrypt', ['<infile> [str]',"outfile [str='']","hash_preset [str='']"]),
|
||||
('Bytespec', ['<bytespec> [str]']),
|
||||
])
|
||||
|
||||
cmd_help = """
|
||||
Bitcoin address/key operations (compressed public keys supported):
|
||||
addr2hexaddr - convert Bitcoin address from base58 to hex format
|
||||
hex2wif - convert a private key from hex to WIF format
|
||||
hexaddr2addr - convert Bitcoin address from hex to base58 format
|
||||
privhex2addr - generate Bitcoin address from private key in hex format
|
||||
pubkey2addr - convert Bitcoin public key to address
|
||||
pubkey2hexaddr - convert Bitcoin public key to address in hex format
|
||||
randpair - generate a random private key/address pair
|
||||
randwif - generate a random private key in WIF format
|
||||
wif2addr - generate a Bitcoin address from a key in WIF format
|
||||
wif2hex - convert a private key from WIF to hex format
|
||||
stdin_msg = """
|
||||
To force a command to read from STDIN in place of its first argument (for
|
||||
supported commands), use '-' as the first argument.
|
||||
""".strip()
|
||||
|
||||
Wallet/TX operations (bitcoind must be running):
|
||||
cmd_help = """
|
||||
Bitcoin address/key operations (compressed public keys supported):
|
||||
addr2hexaddr - convert Bitcoin address from base58 to hex format
|
||||
hex2wif - convert a private key from hex to WIF format
|
||||
hexaddr2addr - convert Bitcoin address from hex to base58 format
|
||||
privhex2addr - generate Bitcoin address from private key in hex format
|
||||
privhex2pubhex - generate a hex public key from a hex private key
|
||||
pubhex2addr - convert a hex pubkey to an address
|
||||
pubhex2redeem_script - convert a hex pubkey to a witness redeem script
|
||||
wif2redeem_script - convert a WIF private key to a witness redeem script
|
||||
wif2segwit_pair - generate both a Segwit redeem script and address from WIF
|
||||
pubkey2addr - convert Bitcoin public key to address
|
||||
randpair - generate a random private key/address pair
|
||||
randwif - generate a random private key in WIF format
|
||||
wif2addr - generate a Bitcoin address from a key in WIF format
|
||||
wif2hex - convert a private key from WIF to hex format
|
||||
|
||||
Wallet/TX operations (bitcoind must be running):
|
||||
getbalance - like 'bitcoin-cli getbalance' but shows confirmed/unconfirmed,
|
||||
spendable/unspendable balances for individual {pnm} wallets
|
||||
listaddress - list the specified {pnm} address and its balance
|
||||
listaddresses - list {pnm} addresses and their balances
|
||||
txview - show raw/signed {pnm} transaction in human-readable form
|
||||
twview - view tracking wallet
|
||||
|
||||
General utilities:
|
||||
General utilities:
|
||||
hexdump - encode data into formatted hexadecimal form (file or stdin)
|
||||
unhexdump - decode formatted hexadecimal data (file or stdin)
|
||||
bytespec - convert a byte specifier such as '1GB' into an integer
|
||||
|
|
@ -116,7 +132,8 @@ cmd_help = """
|
|||
hexreverse - reverse bytes of a hexadecimal string
|
||||
rand2file - write 'n' bytes of random data to specified file
|
||||
randhex - print 'n' bytes (default 32) of random data in hex format
|
||||
sha256x2 - compute a double sha256 hash of data
|
||||
hash256 - compute sha256(sha256(data)) (double sha256)
|
||||
hash160 - compute ripemd160(sha256(data)) (converts hexpubkey to hexaddr)
|
||||
b58randenc - generate a random 32-byte number and convert it to base 58
|
||||
b58tostr - convert a base 58 number to a string
|
||||
strtob58 - convert a string to base 58
|
||||
|
|
@ -125,7 +142,7 @@ cmd_help = """
|
|||
b32tohex - convert a base 32 number to hexadecimal
|
||||
hextob32 - convert a hexadecimal number to base 32
|
||||
|
||||
File encryption:
|
||||
File encryption:
|
||||
encrypt - encrypt a file
|
||||
decrypt - decrypt a file
|
||||
{pnm} encryption suite:
|
||||
|
|
@ -133,7 +150,7 @@ cmd_help = """
|
|||
* Enc: AES256_CTR, 16-byte rand IV, sha256 hash + 32-byte nonce + data
|
||||
* The encrypted file is indistinguishable from random data
|
||||
|
||||
{pnm}-specific operations:
|
||||
{pnm}-specific operations:
|
||||
add_label - add descriptive label for {pnm} address in tracking wallet
|
||||
remove_label - remove descriptive label for {pnm} address in tracking wallet
|
||||
addrfile_chksum - compute checksum for {pnm} address file
|
||||
|
|
@ -144,7 +161,7 @@ cmd_help = """
|
|||
id8 - generate 8-character {pnm} ID for a file (or stdin)
|
||||
str2id6 - generate 6-character {pnm} ID for a string, ignoring spaces
|
||||
|
||||
Mnemonic operations (choose 'electrum' (default), 'tirosh' or 'all'
|
||||
Mnemonic operations (choose 'electrum' (default), 'tirosh' or 'all'
|
||||
wordlists):
|
||||
mn_rand128 - generate random 128-bit mnemonic
|
||||
mn_rand192 - generate random 192-bit mnemonic
|
||||
|
|
@ -156,63 +173,97 @@ cmd_help = """
|
|||
|
||||
IMPORTANT NOTE: Though {pnm} mnemonics use the Electrum wordlist, they're
|
||||
computed using a different algorithm and are NOT Electrum-compatible!
|
||||
""".format(pnm=pnm)
|
||||
|
||||
def tool_usage(prog_name, command):
|
||||
if command in cmd_data:
|
||||
{sm}
|
||||
""".format(pnm=pnm,sm='\n '.join(stdin_msg.split('\n')))
|
||||
|
||||
def usage(command):
|
||||
|
||||
for v in cmd_data.values():
|
||||
if v and v[0][-2:] == '-]':
|
||||
v[0] = v[0][:-2] + ' or STDIN]'
|
||||
if 'MARGS' in v: v.remove('MARGS')
|
||||
|
||||
if not command:
|
||||
Msg('Usage information for mmgen-tool commands:')
|
||||
for k,v in cmd_data.items():
|
||||
Msg(' {:18} {}'.format(k.lower(),' '.join(v)))
|
||||
Msg('\n '+'\n '.join(stdin_msg.split('\n')))
|
||||
sys.exit(0)
|
||||
|
||||
Command = command.capitalize()
|
||||
if Command in cmd_data:
|
||||
import re
|
||||
for line in cmd_help.split('\n'):
|
||||
if ' ' + command in line:
|
||||
if re.match(r'\s+{}\s+'.format(command),line):
|
||||
c,h = line.split('-',1)
|
||||
Msg('MMGEN-TOOL {}: {}'.format(c.strip().upper(),h.strip()))
|
||||
cd = cmd_data[command]
|
||||
if cd and cd[0][-2:] == '-]':
|
||||
cd[0] = cd[0][:-2] + ' or STDIN]'
|
||||
msg('USAGE: %s %s %s' % (prog_name, command, ' '.join(cd)))
|
||||
cd = cmd_data[Command]
|
||||
msg('USAGE: %s %s %s' % (g.prog_name, command, ' '.join(cd)))
|
||||
else:
|
||||
msg("'%s': no such tool command" % command)
|
||||
sys.exit(1)
|
||||
|
||||
def process_args(prog_name, command, cmd_args):
|
||||
Help = usage
|
||||
|
||||
def process_args(command,cmd_args):
|
||||
if 'MARGS' in cmd_data[command]:
|
||||
cmd_data[command].remove('MARGS')
|
||||
margs = True
|
||||
else:
|
||||
margs = False
|
||||
|
||||
c_args = [[i.split(' [')[0],i.split(' [')[1][:-1]]
|
||||
for i in cmd_data[command] if '=' not in i]
|
||||
c_kwargs = dict([[
|
||||
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 = [a for a in cmd_args[:len(c_args)]]
|
||||
|
||||
if c_args and c_args[0][1][-1] == '-':
|
||||
c_args[0][1] = c_args[0][1][:-1] # [str-] -> [str]
|
||||
# If we're reading from a pipe, make the input the first argument
|
||||
if len(u_args) < len(c_kwargs) + len(c_args):
|
||||
if not sys.stdin.isatty():
|
||||
u_args = [sys.stdin.read()] + u_args
|
||||
if not margs:
|
||||
u_args = [a for a in cmd_args[:len(c_args)]]
|
||||
|
||||
if len(u_args) < len(c_args):
|
||||
m1 = 'Command requires exactly %s non-keyword argument%s'
|
||||
msg(m1 % (len(c_args),suf(c_args,'k')))
|
||||
tool_usage(prog_name,command)
|
||||
if c_args and c_args[0][1][-1] == '-':
|
||||
c_args[0][1] = c_args[0][1][:-1] # [str-] -> [str]
|
||||
# If we're reading from a pipe, replace '-' with output of previous command
|
||||
if u_args and u_args[0] == '-':
|
||||
if not sys.stdin.isatty():
|
||||
u_args[0] = sys.stdin.read().strip()
|
||||
if not u_args[0]:
|
||||
die(2,'{}: ERROR: no output from previous command in pipe'.format(command.lower()))
|
||||
|
||||
if not margs and len(u_args) < len(c_args):
|
||||
m1 = 'Command requires exactly %s non-keyword argument%s'
|
||||
msg(m1 % (len(c_args),suf(c_args,'s')))
|
||||
usage(command)
|
||||
|
||||
# print u_args
|
||||
extra_args = len(cmd_args) - len(c_args)
|
||||
u_kwargs = {}
|
||||
if extra_args > 0:
|
||||
if margs:
|
||||
t = [a.split('=') for a in cmd_args if '=' in a]
|
||||
tk = [a[0] for a in t]
|
||||
tk_bad = [a for a in tk if a not in c_kwargs]
|
||||
if set(tk_bad) != set(tk[:len(tk_bad)]):
|
||||
die(1,"'{}': illegal keyword argument".format(tk_bad[-1]))
|
||||
u_kwargs = dict(t[len(tk_bad):])
|
||||
u_args = cmd_args[:-len(u_kwargs) or None]
|
||||
elif 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)
|
||||
% (len(c_args),suf(c_args,'s')))
|
||||
usage(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)
|
||||
% (len(c_kwargs),suf(c_kwargs,'s')))
|
||||
usage(command)
|
||||
|
||||
# mdie(c_args,c_kwargs,u_args,u_kwargs)
|
||||
|
||||
for k in u_kwargs:
|
||||
if k not in c_kwargs:
|
||||
msg("'%s': invalid keyword argument" % k)
|
||||
tool_usage(prog_name,command)
|
||||
usage(command)
|
||||
|
||||
def conv_type(arg,arg_name,arg_type):
|
||||
if arg_type == 'str': arg_type = 'unicode'
|
||||
|
|
@ -221,17 +272,19 @@ def process_args(prog_name, command, cmd_args):
|
|||
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)
|
||||
usage(command)
|
||||
try:
|
||||
return __builtins__[arg_type](arg)
|
||||
except:
|
||||
die(1,"'%s': Invalid argument for argument %s ('%s' required)" % \
|
||||
(arg, arg_name, arg_type))
|
||||
|
||||
args = [conv_type(u_args[i],c_args[i][0],c_args[i][1]) for i in range(len(c_args))]
|
||||
if margs:
|
||||
args = [conv_type(u_args[i],c_args[0][0],c_args[0][1]) for i in range(len(u_args))]
|
||||
else:
|
||||
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
|
||||
|
|
@ -252,53 +305,86 @@ def print_convert_results(indata,enc,dec,dtype):
|
|||
if error:
|
||||
die(3,"Error! Recoded data doesn't match input!")
|
||||
|
||||
def usage(cmd):
|
||||
tool_usage(g.prog_name, cmd)
|
||||
|
||||
help = usage
|
||||
|
||||
def hexdump(infile, cols=8, line_nums=True):
|
||||
def Hexdump(infile, cols=8, line_nums=True):
|
||||
Msg(pretty_hexdump(
|
||||
get_data_from_file(infile,dash=True,silent=True,binary=True),
|
||||
cols=cols,line_nums=line_nums))
|
||||
|
||||
def unhexdump(infile):
|
||||
def Unhexdump(infile):
|
||||
if g.platform == 'win':
|
||||
import msvcrt
|
||||
msvcrt.setmode(sys.stdout.fileno(),os.O_BINARY)
|
||||
sys.stdout.write(decode_pretty_hexdump(
|
||||
get_data_from_file(infile,dash=True,silent=True)))
|
||||
|
||||
def b58randenc():
|
||||
def B58randenc():
|
||||
r = get_random(32)
|
||||
enc = mmb.b58encode(r)
|
||||
dec = mmb.b58decode(enc)
|
||||
print_convert_results(r,enc,dec,'str')
|
||||
|
||||
def randhex(nbytes='32'):
|
||||
def Randhex(nbytes='32'):
|
||||
Msg(ba.hexlify(get_random(int(nbytes))))
|
||||
|
||||
def randwif(compressed=False):
|
||||
def Randwif(compressed=False):
|
||||
r_hex = ba.hexlify(get_random(32))
|
||||
enc = mmb.hex2wif(r_hex,compressed)
|
||||
dec = mmb.wif2hex(enc)
|
||||
dec = wif2hex(enc)
|
||||
print_convert_results(r_hex,enc,dec,'hex')
|
||||
|
||||
def randpair(compressed=False):
|
||||
def Randpair(compressed=False,segwit=False):
|
||||
if segwit: compressed = True
|
||||
r_hex = ba.hexlify(get_random(32))
|
||||
wif = mmb.hex2wif(r_hex,compressed)
|
||||
addr = mmb.privnum2addr(int(r_hex,16),compressed)
|
||||
addr = mmb.privnum2addr(int(r_hex,16),compressed,segwit=segwit)
|
||||
Vmsg('Key (hex): %s' % r_hex)
|
||||
Vmsg_r('Key (WIF): '); Msg(wif)
|
||||
Vmsg_r('Addr: '); Msg(addr)
|
||||
|
||||
def wif2addr(wif,compressed=False):
|
||||
s_enc = mmb.wif2hex(wif)
|
||||
if s_enc == False:
|
||||
die(1,'Invalid address')
|
||||
addr = mmb.privnum2addr(int(s_enc,16),compressed)
|
||||
def Wif2addr(wif,segwit=False):
|
||||
compressed = mmb.wif_is_compressed(wif)
|
||||
if segwit and not compressed:
|
||||
die(1,'Segwit address cannot be generated from uncompressed WIF')
|
||||
privhex = wif2hex(wif)
|
||||
addr = mmb.privnum2addr(int(privhex,16),compressed,segwit=segwit)
|
||||
Vmsg_r('Addr: '); Msg(addr)
|
||||
|
||||
def Wif2segwit_pair(wif):
|
||||
if not mmb.wif_is_compressed(wif):
|
||||
die(1,'Segwit address cannot be generated from uncompressed WIF')
|
||||
privhex = wif2hex(wif)
|
||||
pubhex = mmb.privnum2pubhex(int(privhex,16),compressed=True)
|
||||
rs = mmb.pubhex2redeem_script(pubhex)
|
||||
addr = mmb.hexaddr2addr(mmb.hash160(rs),p2sh=True)
|
||||
addr_chk = mmb.privnum2addr(int(privhex,16),compressed=True,segwit=True)
|
||||
assert addr == addr_chk
|
||||
Msg('{}\n{}'.format(rs,addr))
|
||||
|
||||
def Hexaddr2addr(hexaddr): Msg(mmb.hexaddr2addr(hexaddr))
|
||||
def Addr2hexaddr(addr): Msg(mmb.verify_addr(addr,return_hex=True))
|
||||
def Hash160(pubkeyhex): Msg(mmb.hash160(pubkeyhex))
|
||||
def Pubhex2addr(pubkeyhex,p2sh=False): Msg(mmb.hexaddr2addr(mmb.hash160(pubkeyhex),p2sh=p2sh))
|
||||
def Wif2hex(wif): Msg(wif2hex(wif))
|
||||
def Hex2wif(hexpriv,compressed=False):
|
||||
Msg(mmb.hex2wif(hexpriv,compressed))
|
||||
def Privhex2addr(privhex,compressed=False,segwit=False):
|
||||
if segwit and not compressed:
|
||||
die(1,'Segwit address can be generated only from a compressed pubkey')
|
||||
Msg(mmb.privnum2addr(int(privhex,16),compressed,segwit=segwit))
|
||||
def Privhex2pubhex(privhex,compressed=False): # new
|
||||
Msg(mmb.privnum2pubhex(int(privhex,16),compressed))
|
||||
def Pubhex2redeem_script(pubhex): # new
|
||||
Msg(mmb.pubhex2redeem_script(pubhex))
|
||||
def Wif2redeem_script(wif): # new
|
||||
if not mmb.wif_is_compressed(wif):
|
||||
die(1,'Witness redeem script cannot be generated from uncompressed WIF')
|
||||
pubhex = mmb.privnum2pubhex(int(wif2hex(wif),16),compressed=True)
|
||||
Msg(mmb.pubhex2redeem_script(pubhex))
|
||||
|
||||
def wif2hex(wif): # wrapper
|
||||
ret = mmb.wif2hex(wif)
|
||||
return ret or die(1,'{}: Invalid WIF'.format(wif))
|
||||
|
||||
wordlists = 'electrum','tirosh'
|
||||
dfl_wl_id = 'electrum'
|
||||
|
||||
|
|
@ -311,123 +397,157 @@ def do_random_mn(nbytes,wordlist):
|
|||
mn = baseconv.fromhex(hexrand,wl_id)
|
||||
Msg(' '.join(mn))
|
||||
|
||||
def mn_rand128(wordlist=dfl_wl_id): do_random_mn(16,wordlist)
|
||||
def mn_rand192(wordlist=dfl_wl_id): do_random_mn(24,wordlist)
|
||||
def mn_rand256(wordlist=dfl_wl_id): do_random_mn(32,wordlist)
|
||||
def Mn_rand128(wordlist=dfl_wl_id): do_random_mn(16,wordlist)
|
||||
def Mn_rand192(wordlist=dfl_wl_id): do_random_mn(24,wordlist)
|
||||
def Mn_rand256(wordlist=dfl_wl_id): do_random_mn(32,wordlist)
|
||||
|
||||
def hex2mn(s,wordlist=dfl_wl_id): Msg(' '.join(baseconv.fromhex(s,wordlist)))
|
||||
def mn2hex(s,wordlist=dfl_wl_id): Msg(baseconv.tohex(s.split(),wordlist))
|
||||
def Hex2mn(s,wordlist=dfl_wl_id): Msg(' '.join(baseconv.fromhex(s,wordlist)))
|
||||
def Mn2hex(s,wordlist=dfl_wl_id): Msg(baseconv.tohex(s.split(),wordlist))
|
||||
|
||||
def strtob58(s,pad=None): Msg(''.join(baseconv.fromhex(ba.hexlify(s),'b58',pad)))
|
||||
def hextob58(s,pad=None): Msg(''.join(baseconv.fromhex(s,'b58',pad)))
|
||||
def hextob32(s,pad=None): Msg(''.join(baseconv.fromhex(s,'b32',pad)))
|
||||
def b58tostr(s): Msg(ba.unhexlify(baseconv.tohex(s,'b58')))
|
||||
def b58tohex(s,pad=None): Msg(baseconv.tohex(s,'b58',pad))
|
||||
def b32tohex(s,pad=None): Msg(baseconv.tohex(s.upper(),'b32',pad))
|
||||
def Strtob58(s,pad=None): Msg(''.join(baseconv.fromhex(ba.hexlify(s),'b58',pad)))
|
||||
def Hextob58(s,pad=None): Msg(''.join(baseconv.fromhex(s,'b58',pad)))
|
||||
def Hextob32(s,pad=None): Msg(''.join(baseconv.fromhex(s,'b32',pad)))
|
||||
def B58tostr(s): Msg(ba.unhexlify(baseconv.tohex(s,'b58')))
|
||||
def B58tohex(s,pad=None): Msg(baseconv.tohex(s,'b58',pad))
|
||||
def B32tohex(s,pad=None): Msg(baseconv.tohex(s.upper(),'b32',pad))
|
||||
|
||||
from mmgen.seed import Mnemonic
|
||||
def mn_stats(wordlist=dfl_wl_id):
|
||||
def Mn_stats(wordlist=dfl_wl_id):
|
||||
wordlist in baseconv.digits or die(1,"'{}': not a valid wordlist".format(wordlist))
|
||||
baseconv.check_wordlist(wordlist)
|
||||
def mn_printlist(wordlist=dfl_wl_id):
|
||||
def Mn_printlist(wordlist=dfl_wl_id):
|
||||
wordlist in baseconv.digits or die(1,"'{}': not a valid wordlist".format(wordlist))
|
||||
Msg('\n'.join(baseconv.digits[wordlist]))
|
||||
|
||||
def id8(infile):
|
||||
def Id8(infile):
|
||||
Msg(make_chksum_8(
|
||||
get_data_from_file(infile,dash=True,silent=True,binary=True)
|
||||
))
|
||||
def id6(infile):
|
||||
def Id6(infile):
|
||||
Msg(make_chksum_6(
|
||||
get_data_from_file(infile,dash=True,silent=True,binary=True)
|
||||
))
|
||||
def str2id6(s): # retain ignoring of space for backwards compat
|
||||
def Str2id6(s): # retain ignoring of space for backwards compat
|
||||
Msg(make_chksum_6(''.join(s.split())))
|
||||
|
||||
# List MMGen addresses and their balances:
|
||||
def listaddresses(addrs='',minconf=1,showempty=False,pager=False,showbtcaddrs=False):
|
||||
def Listaddress(addr,minconf=1,pager=False,showempty=True,showbtcaddr=True):
|
||||
return Listaddresses(addrs=addr,minconf=minconf,pager=pager,showempty=showempty,showbtcaddrs=showbtcaddr)
|
||||
|
||||
# TODO - move some or all of this code to AddrList
|
||||
usr_addr_list = []
|
||||
if addrs:
|
||||
sid_s,idxs = split2(addrs,':')
|
||||
sid = SeedID(sid=sid_s)
|
||||
usr_addr_list = ['{}:{}'.format(sid,a) for a in AddrIdxList(idxs)]
|
||||
# List MMGen addresses and their balances. TODO: move this code to AddrList
|
||||
def Listaddresses(addrs='',minconf=1,showempty=False,pager=False,showbtcaddrs=False):
|
||||
|
||||
c = bitcoin_connection()
|
||||
addrs = {} # reusing variable name!
|
||||
|
||||
def check_dup_mmid(accts):
|
||||
help_msg = """
|
||||
Your tracking wallet is corrupted or has been altered by a non-{pnm} program.
|
||||
|
||||
You might be able to salvage your wallet by determining which of the offending
|
||||
addresses doesn't belong to {pnm} ID {mid} and then typing:
|
||||
|
||||
bitcoin-cli importaddress <offending address> "" false
|
||||
"""
|
||||
m_prev = None
|
||||
|
||||
for m in sorted([l.mmid for l in accts]):
|
||||
if m == m_prev:
|
||||
msg('Duplicate MMGen ID ({}) discovered in tracking wallet!\n'.format(m))
|
||||
bad_accts = MMGenList([l for l in accts if l.mmid == m])
|
||||
msg(' Affected Bitcoin RPC accounts:\n {}\n'.format('\n '.join(bad_accts)))
|
||||
bad_addrs = [a[0] for a in c.getaddressesbyaccount([[a] for a in bad_accts],batch=True)]
|
||||
if len(set(bad_addrs)) != 1:
|
||||
msg(' Offending addresses:\n {}'.format('\n '.join(bad_addrs)))
|
||||
msg(help_msg.format(mid=m,pnm=pnm))
|
||||
die(3,red('Exiting on error'))
|
||||
m_prev = m
|
||||
|
||||
usr_addr_list = []
|
||||
if addrs:
|
||||
a = addrs.rsplit(':',1)
|
||||
if len(a) != 2:
|
||||
m = "'{}': invalid address list argument (must be in form <seed ID>:[<type>:]<idx list>)"
|
||||
die(1,m.format(addrs))
|
||||
usr_addr_list = [MMGenID('{}:{}'.format(a[0],i)) for i in AddrIdxList(a[1])]
|
||||
|
||||
class TwAddrList(dict,MMGenObject): pass
|
||||
|
||||
addrs = TwAddrList() # reusing name!
|
||||
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 (mmaddr[:4] == 'btc:' or is_mmgen_id(mmaddr)) and d['confirmations'] >= minconf:
|
||||
key = mmaddr.replace(':','_')
|
||||
if key in addrs:
|
||||
if addrs[key][2] != d['address']:
|
||||
if not 'account' in d: continue # skip coinbase outputs with missing account
|
||||
if d['confirmations'] < minconf: continue
|
||||
label = TwLabel(d['account'],on_fail='silent')
|
||||
if label:
|
||||
if usr_addr_list and (label.mmid not in usr_addr_list): continue
|
||||
if label.mmid in addrs:
|
||||
if addrs[label.mmid]['addr'] != d['address']:
|
||||
die(2,'duplicate BTC address ({}) for this MMGen address! ({})'.format(
|
||||
(d['address'], addrs[key][2])))
|
||||
(d['address'], addrs[label.mmid]['addr'])))
|
||||
else:
|
||||
addrs[key] = [BTCAmt('0'),MMGenAddrLabel(comment),BTCAddr(d['address'])]
|
||||
addrs[key][0] += d['amount']
|
||||
addrs[label.mmid] = { 'amt':BTCAmt('0'), 'lbl':label, 'addr':BTCAddr(d['address']) }
|
||||
addrs[label.mmid]['amt'] += d['amount']
|
||||
total += d['amount']
|
||||
|
||||
# We use listaccounts only for empty addresses, as it shows false positive balances
|
||||
if showempty:
|
||||
accts = c.listaccounts(0,True) # minconf,watchonly
|
||||
save_a = []
|
||||
for acct in accts:
|
||||
mmaddr,comment = split2(acct)
|
||||
if usr_addr_list and (mmaddr not in usr_addr_list): continue
|
||||
if mmaddr[:4] == 'btc:' or is_mmgen_id(mmaddr):
|
||||
key = mmaddr.replace(':','_')
|
||||
if key not in addrs:
|
||||
if showbtcaddrs: save_a.append([acct])
|
||||
addrs[key] = [BTCAmt('0'),MMGenAddrLabel(comment),'']
|
||||
|
||||
for acct,addr in zip(save_a,c.getaddressesbyaccount(save_a,batch=True)):
|
||||
if len(addr) != 1:
|
||||
die(2,"Account '%s' has more or less than one BTC address!" % addr)
|
||||
key = split2(acct[0])[0].replace(':','_')
|
||||
addrs[key][2] = BTCAddr(addr[0])
|
||||
# args: minconf,watchonly
|
||||
accts = MMGenList([b for b in [TwLabel(a,on_fail='silent') for a in c.listaccounts(0,True)] if b])
|
||||
check_dup_mmid(accts)
|
||||
acct_addrs = c.getaddressesbyaccount([[a] for a in accts],batch=True)
|
||||
assert len(accts) == len(acct_addrs), 'listaccounts() and getaddressesbyaccount() not of same length'
|
||||
for a in acct_addrs:
|
||||
if len(a) != 1:
|
||||
die(2,"'{}': more than one BTC address in account!".format(a))
|
||||
for label,addr in zip(accts,[b[0] for b in acct_addrs]):
|
||||
if usr_addr_list and (label.mmid not in usr_addr_list): continue
|
||||
if label.mmid not in addrs:
|
||||
addrs[label.mmid] = { 'amt':BTCAmt('0'), 'lbl':label, 'addr':'' }
|
||||
if showbtcaddrs:
|
||||
addrs[label.mmid]['addr'] = BTCAddr(addr)
|
||||
|
||||
if not addrs:
|
||||
die(0,('No addresses with balances!','No tracked addresses!')[showempty])
|
||||
die(0,('No tracked addresses with balances!','No tracked addresses!')[showempty])
|
||||
|
||||
fs = ('{mid} {lbl} {amt}','{mid} {addr} {lbl} {amt}')[showbtcaddrs]
|
||||
max_mmid_len = max([len(k) for k in addrs if k[:4] != 'btc_'] or [10])
|
||||
max_lbl_len = max(len(addrs[k][1]) for k in addrs) or 7
|
||||
out = [fs.format(
|
||||
out = ([],[green('Chain: {}'.format(g.chain.upper()))])[g.chain in ('testnet','regtest')]
|
||||
|
||||
fs = ('{mid} {cmt} {amt}','{mid} {addr} {cmt} {amt}')[showbtcaddrs]
|
||||
mmaddrs = [k for k in addrs.keys() if k.type == 'mmgen']
|
||||
max_mmid_len = max(len(k) for k in mmaddrs) + 2 if mmaddrs else 10
|
||||
max_cmt_len = max(max(len(addrs[k]['lbl'].comment) for k in addrs),7)
|
||||
out += [fs.format(
|
||||
mid=MMGenID.fmtc('MMGenID',width=max_mmid_len),
|
||||
addr=BTCAddr.fmtc('ADDRESS'),
|
||||
lbl=MMGenAddrLabel.fmtc('COMMENT',width=max_lbl_len),
|
||||
cmt=TwComment.fmtc('COMMENT',width=max_cmt_len),
|
||||
amt='BALANCE'
|
||||
)]
|
||||
|
||||
old_sid = ''
|
||||
def s_mmgen(k):
|
||||
s = k.split('_')
|
||||
a,b = s if len(s) == 2 else (k,'')
|
||||
return '{}_{:>0{w}}'.format(a,b,w=AddrIdx.max_digits+9)
|
||||
for k in sorted(addrs,key=s_mmgen):
|
||||
if old_sid and old_sid != k.split('_')[0]: out.append('')
|
||||
old_sid = k.split('_')[0]
|
||||
m = 'non-'+g.proj_name if k[:4] == 'btc_' else k.replace('_',':')
|
||||
al_id_save = None
|
||||
for mmid in sorted(addrs,key=lambda j: j.sort_key):
|
||||
if mmid.type == 'mmgen':
|
||||
if al_id_save and al_id_save != mmid.obj.al_id:
|
||||
out.append('')
|
||||
al_id_save = mmid.obj.al_id
|
||||
mmid_disp = mmid
|
||||
else:
|
||||
if al_id_save:
|
||||
out.append('')
|
||||
al_id_save = None
|
||||
mmid_disp = mmid.type
|
||||
out.append(fs.format(
|
||||
mid = MMGenID.fmtc(m,width=max_mmid_len,color=True),
|
||||
addr=(addrs[k][2].fmt(color=True) if showbtcaddrs else None),
|
||||
lbl=addrs[k][1].fmt(width=max_lbl_len,color=True,nullrepl='-'),
|
||||
amt=addrs[k][0].fmt('3.0',color=True)))
|
||||
mid = MMGenID.fmtc(mmid_disp,width=max_mmid_len,color=True),
|
||||
addr=(addrs[mmid]['addr'].fmt(color=True) if showbtcaddrs else None),
|
||||
cmt=addrs[mmid]['lbl'].comment.fmt(width=max_cmt_len,color=True,nullrepl='-'),
|
||||
amt=addrs[mmid]['amt'].fmt('3.0',color=True)))
|
||||
|
||||
out.append('\nTOTAL: %s BTC' % total.hl(color=True))
|
||||
o = '\n'.join(out)
|
||||
if pager: do_pager(o)
|
||||
else: Msg(o)
|
||||
|
||||
def getbalance(minconf=1):
|
||||
return do_pager(o) if pager else Msg(o)
|
||||
|
||||
def Getbalance(minconf=1):
|
||||
accts = {}
|
||||
for d in bitcoin_connection().listunspent(0):
|
||||
ma = split2(d['account'])[0]
|
||||
ma = split2(d['account'] if 'account' in d else '')[0] # include coinbase outputs if spendable
|
||||
keys = ['TOTAL']
|
||||
if d['spendable']: keys += ['SPENDABLE']
|
||||
if is_mmgen_id(ma): keys += [ma.split(':')[0]]
|
||||
|
|
@ -445,77 +565,62 @@ def getbalance(minconf=1):
|
|||
*[s.ljust(16) for s in ' Unconfirmed',' <%s %s'%(mc,lbl),' >=%s %s'%(mc,lbl)]))
|
||||
for key in sorted(accts.keys()):
|
||||
Msg(fs.format(key+':', *[a.fmt(color=True,suf=' BTC') for a in accts[key]]))
|
||||
if 'SPENDABLE' in accts:
|
||||
Msg(red('Warning: this wallet contains PRIVATE KEYS for the SPENDABLE balance!'))
|
||||
|
||||
def txview(infile,pager=False,terse=False):
|
||||
c = bitcoin_connection()
|
||||
tx = MMGenTX(infile)
|
||||
tx.view(pager,pause=False,terse=terse)
|
||||
def Txview(*infiles,**kwargs):
|
||||
from mmgen.filename import MMGenFileList
|
||||
pager = 'pager' in kwargs and kwargs['pager']
|
||||
terse = 'terse' in kwargs and kwargs['terse']
|
||||
sort_key = kwargs['sort'] if 'sort' in kwargs else 'mtime'
|
||||
flist = MMGenFileList(infiles,ftype=MMGenTX)
|
||||
flist.sort_by_age(key=sort_key) # in-place sort
|
||||
from mmgen.term import get_terminal_size
|
||||
sep = u'—'*get_terminal_size()[0]+'\n'
|
||||
out = sep.join([MMGenTX(fn).format_view(terse=terse) for fn in flist.names()])
|
||||
(Msg,do_pager)[pager](out.rstrip())
|
||||
|
||||
def twview(pager=False,reverse=False,wide=False,minconf=1,sort='age'):
|
||||
def Twview(pager=False,reverse=False,wide=False,minconf=1,sort='age',show_days=True,show_mmid=True):
|
||||
from mmgen.tw import MMGenTrackingWallet
|
||||
tw = MMGenTrackingWallet(minconf=minconf)
|
||||
tw.do_sort(sort,reverse=reverse)
|
||||
tw.show_days = show_days
|
||||
tw.show_mmid = show_mmid
|
||||
out = tw.format_for_printing(color=True) if wide else tw.format_for_display()
|
||||
do_pager(out) if pager else sys.stdout.write(out)
|
||||
(Msg_r,do_pager)[pager](out)
|
||||
|
||||
def add_label(mmaddr,label):
|
||||
def Add_label(mmaddr,label):
|
||||
from mmgen.tw import MMGenTrackingWallet
|
||||
if MMGenTrackingWallet.add_label(mmaddr,label): # returns on failure
|
||||
s = '{pnm} address {a} in tracking wallet'.format(a=mmaddr,pnm=pnm)
|
||||
if label: msg("Added label '{}' for {}".format(label,s))
|
||||
else: msg('Removed label for {}'.format(s))
|
||||
else:
|
||||
die(1,'Label could not be %s' % ('removed','added')[bool(label)])
|
||||
MMGenTrackingWallet.add_label(mmaddr,label) # dies on failure
|
||||
|
||||
def remove_label(mmaddr): add_label(mmaddr,'')
|
||||
def Remove_label(mmaddr): Add_label(mmaddr,'')
|
||||
|
||||
def addrfile_chksum(infile):
|
||||
def Addrfile_chksum(infile):
|
||||
from mmgen.addr import AddrList
|
||||
AddrList(infile,chksum_only=True)
|
||||
|
||||
def keyaddrfile_chksum(infile):
|
||||
def Keyaddrfile_chksum(infile):
|
||||
from mmgen.addr import KeyAddrList
|
||||
KeyAddrList(infile,chksum_only=True)
|
||||
|
||||
def passwdfile_chksum(infile):
|
||||
def Passwdfile_chksum(infile):
|
||||
from mmgen.addr import PasswordList
|
||||
PasswordList(infile=infile,chksum_only=True)
|
||||
|
||||
def hexreverse(s):
|
||||
def Hexreverse(s):
|
||||
Msg(ba.hexlify(ba.unhexlify(s.strip())[::-1]))
|
||||
|
||||
def hexlify(s):
|
||||
def Hexlify(s):
|
||||
Msg(ba.hexlify(s))
|
||||
|
||||
def sha256x2(s, file_input=False, hex_input=False):
|
||||
def Hash256(s, file_input=False, hex_input=False):
|
||||
from hashlib import sha256
|
||||
if file_input: b = get_data_from_file(s,binary=True)
|
||||
elif hex_input: b = decode_pretty_hexdump(s)
|
||||
else: b = s
|
||||
Msg(sha256(sha256(b).digest()).hexdigest())
|
||||
|
||||
def hexaddr2addr(hexaddr):
|
||||
Msg(mmb.hexaddr2addr(hexaddr))
|
||||
|
||||
def addr2hexaddr(addr):
|
||||
Msg(mmb.verify_addr(addr,return_hex=True))
|
||||
|
||||
def pubkey2hexaddr(pubkeyhex):
|
||||
Msg(mmb.pubhex2hexaddr(pubkeyhex))
|
||||
|
||||
def pubkey2addr(pubkeyhex):
|
||||
Msg(mmb.hexaddr2addr(mmb.pubhex2hexaddr(pubkeyhex)))
|
||||
|
||||
def privhex2addr(privkeyhex,compressed=False):
|
||||
Msg(mmb.privnum2addr(int(privkeyhex,16),compressed))
|
||||
|
||||
def wif2hex(wif,compressed=False):
|
||||
Msg(mmb.wif2hex(wif))
|
||||
|
||||
def hex2wif(hexpriv,compressed=False):
|
||||
Msg(mmb.hex2wif(hexpriv,compressed))
|
||||
|
||||
def encrypt(infile,outfile='',hash_preset=''):
|
||||
def Encrypt(infile,outfile='',hash_preset=''):
|
||||
data = get_data_from_file(infile,'data for encryption',binary=True)
|
||||
enc_d = mmgen_encrypt(data,'user data',hash_preset)
|
||||
if not outfile:
|
||||
|
|
@ -523,7 +628,7 @@ def encrypt(infile,outfile='',hash_preset=''):
|
|||
|
||||
write_data_to_file(outfile,enc_d,'encrypted data',binary=True)
|
||||
|
||||
def decrypt(infile,outfile='',hash_preset=''):
|
||||
def Decrypt(infile,outfile='',hash_preset=''):
|
||||
enc_d = get_data_from_file(infile,'encrypted data',binary=True)
|
||||
while True:
|
||||
dec_d = mmgen_decrypt(enc_d,'user data',hash_preset)
|
||||
|
|
@ -537,7 +642,7 @@ def decrypt(infile,outfile='',hash_preset=''):
|
|||
|
||||
write_data_to_file(outfile,dec_d,'decrypted data',binary=True)
|
||||
|
||||
def find_incog_data(filename,iv_id,keep_searching=False):
|
||||
def Find_incog_data(filename,iv_id,keep_searching=False):
|
||||
ivsize,bsize,mod = g.aesctr_iv_len,4096,4096*8
|
||||
n,carry = 0,' '*ivsize
|
||||
flgs = os.O_RDONLY|os.O_BINARY if g.platform == 'win' else os.O_RDONLY
|
||||
|
|
@ -554,7 +659,7 @@ def find_incog_data(filename,iv_id,keep_searching=False):
|
|||
if n+i < ivsize: continue
|
||||
msg('\rIncog data for ID %s found at offset %s' %
|
||||
(iv_id,n+i-ivsize))
|
||||
if not keep_searching: sys.exit()
|
||||
if not keep_searching: sys.exit(0)
|
||||
carry = d[len(d)-ivsize:]
|
||||
n += bsize
|
||||
if not n % mod: msg_r('\rSearched: %s bytes' % n)
|
||||
|
|
@ -562,7 +667,7 @@ def find_incog_data(filename,iv_id,keep_searching=False):
|
|||
msg('')
|
||||
os.close(f)
|
||||
|
||||
def rand2file(outfile, nbytes, threads=4, silent=False):
|
||||
def Rand2file(outfile, nbytes, threads=4, silent=False):
|
||||
nbytes = parse_nbytes(nbytes)
|
||||
from Crypto import Random
|
||||
rh = Random.new()
|
||||
|
|
@ -620,4 +725,4 @@ def rand2file(outfile, nbytes, threads=4, silent=False):
|
|||
q2.join()
|
||||
f.close()
|
||||
|
||||
def bytespec(s): Msg(str(parse_nbytes(s)))
|
||||
def Bytespec(s): Msg(str(parse_nbytes(s)))
|
||||
|
|
|
|||
234
mmgen/tw.py
234
mmgen/tw.py
|
|
@ -34,22 +34,25 @@ def parse_tw_acct_label(s):
|
|||
a2 = ret[1] if len(ret) == 2 else None
|
||||
return a1,a2
|
||||
|
||||
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):
|
||||
|
||||
class MMGenTwOutputList(list,MMGenObject): pass
|
||||
|
||||
class MMGenTwOutput(MMGenListItem):
|
||||
twmmid = MMGenListItemAttr('twmmid','TwMMGenID')
|
||||
txid = MMGenListItemAttr('txid','BitcoinTxID')
|
||||
attrs_reassign = 'label','skip'
|
||||
attrs = 'txid','vout','amt','label','twmmid','addr','confs','scriptPubKey','days','skip'
|
||||
|
||||
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)
|
||||
""".strip().format(g.proj_name.lower())
|
||||
}
|
||||
sort_keys = 'addr','age','amt','txid','mmid'
|
||||
|
||||
def __init__(self,minconf=1):
|
||||
self.unspent = []
|
||||
self.unspent = self.MMGenTwOutputList()
|
||||
self.fmt_display = ''
|
||||
self.fmt_print = ''
|
||||
self.cols = None
|
||||
|
|
@ -58,57 +61,62 @@ watch-only wallet using '{}-addrimport' and then re-run this program.
|
|||
self.show_days = True
|
||||
self.show_mmid = True
|
||||
self.minconf = minconf
|
||||
self.get_data()
|
||||
self.get_unspent_data()
|
||||
self.sort_key = 'age'
|
||||
self.do_sort()
|
||||
self.total = self.get_total_btc()
|
||||
|
||||
def get_total_btc(self):
|
||||
return sum([i.amt for i in self.unspent])
|
||||
return sum(i.amt for i in self.unspent)
|
||||
|
||||
def get_data(self):
|
||||
def get_unspent_data(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(self.minconf)
|
||||
# write_data_to_file('bogus_unspent.json', repr(us), 'bogus unspent data')
|
||||
# sys.exit()
|
||||
# sys.exit(0)
|
||||
|
||||
if not us_rpc: die(0,self.wmsg['no_spendable_outputs'])
|
||||
mm_rpc = self.MMGenTwOutputList()
|
||||
for o in us_rpc:
|
||||
o['mmid'],o['label'] = parse_tw_acct_label(o['account']) if 'account' in o else ('','')
|
||||
o['days'] = int(o['confirmations'] * g.mins_per_block / (60*24))
|
||||
o['amt'] = o['amount'] # TODO
|
||||
o['addr'] = o['address']
|
||||
o['confs'] = o['confirmations']
|
||||
self.unspent = [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([pp_format(i)+'\n' for i in us_rpc]))
|
||||
# die(1,''.join([str(i)+'\n' for i in self.unspent]))
|
||||
if not 'account' in o: continue # coinbase outputs have no account field
|
||||
l = TwLabel(o['account'],on_fail='silent')
|
||||
if l:
|
||||
o.update({
|
||||
'twmmid': l.mmid,
|
||||
'label': l.comment,
|
||||
'days': int(o['confirmations'] * g.mins_per_block / (60*24)),
|
||||
'amt': o['amount'], # TODO
|
||||
'addr': o['address'],
|
||||
'confs': o['confirmations']
|
||||
})
|
||||
mm_rpc.append(o)
|
||||
self.unspent = self.MMGenTwOutputList([self.MMGenTwOutput(**dict([(k,v) for k,v in o.items() if k in self.MMGenTwOutput.attrs])) for o in mm_rpc])
|
||||
for u in self.unspent:
|
||||
if u.label == None: u.label = ''
|
||||
if not self.unspent:
|
||||
die(1,'No tracked unspent outputs in tracking wallet!')
|
||||
|
||||
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=AddrIdx.max_digits)
|
||||
else: return 'G' + (i.label or '')
|
||||
|
||||
def do_sort(self,key=None,reverse=None):
|
||||
if not key: key = self.sort_key
|
||||
assert key
|
||||
def do_sort(self,key=None,reverse=False):
|
||||
sort_funcs = {
|
||||
'addr': lambda i: i.addr,
|
||||
'age': lambda i: 0 - i.confs,
|
||||
'amt': lambda i: i.amt,
|
||||
'txid': lambda i: '%s %03s' % (i.txid,i.vout),
|
||||
'mmid': lambda i: i.twmmid.sort_key
|
||||
}
|
||||
key = key or self.sort_key
|
||||
if key not in sort_funcs:
|
||||
die(1,"'{}': invalid sort key. Valid options: {}".format(key,' '.join(sort_funcs.keys())))
|
||||
self.sort_key = key
|
||||
if key not in self.sort_keys:
|
||||
fs = "'{}': invalid sort key. Valid keys: [{}]"
|
||||
die(2,fs.format(key,' '.join(self.sort_keys)))
|
||||
if reverse == None: reverse = self.reverse
|
||||
self.unspent.sort(key=getattr(self,'s_'+key),reverse=reverse)
|
||||
assert type(reverse) == bool
|
||||
self.unspent.sort(key=sort_funcs[key],reverse=reverse or self.reverse)
|
||||
|
||||
def sort_info(self,include_group=True):
|
||||
ret = ([],['Reverse'])[self.reverse]
|
||||
ret.append(capfirst(self.sort_key).replace('Mmid','MMGenID'))
|
||||
if include_group and self.group and (self.sort_key in ('addr','txid','mmid')):
|
||||
ret.append(capfirst(self.sort_key).replace('Twmmid','MMGenID'))
|
||||
if include_group and self.group and (self.sort_key in ('addr','txid','twmmid')):
|
||||
ret.append('Grouped')
|
||||
return ret
|
||||
|
||||
|
|
@ -126,42 +134,42 @@ watch-only wallet using '{}-addrimport' and then re-run this program.
|
|||
msg(self.format_for_display())
|
||||
|
||||
def format_for_display(self):
|
||||
unsp = [MMGenTWOutput(**i.__dict__) for i in self.unspent]
|
||||
unsp = self.unspent
|
||||
# unsp.pdie()
|
||||
self.set_term_columns()
|
||||
|
||||
for i in unsp:
|
||||
if i.label == None: i.label = ''
|
||||
i.skip = ''
|
||||
|
||||
mmid_w = max(len(i.mmid or '') for i in unsp) or 10
|
||||
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)))
|
||||
# Field widths
|
||||
min_mmid_w = 12 # DEADBEEF:S:1
|
||||
mmid_w = max(len(('',i.twmmid)[i.twmmid.type=='mmgen']) for i in unsp) or min_mmid_w
|
||||
max_acct_w = max(len(i.label) for i in unsp) + mmid_w + 1
|
||||
addr_w = min(35+(0,1+max_acct_w)[self.show_mmid],self.cols-45)
|
||||
acct_w = min(max_acct_w, 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])
|
||||
fs = ' %-4s %-{}s %-2s %s %s %s'.format(tx_w)
|
||||
|
||||
if self.group and (self.sort_key in ('addr','txid','mmid')):
|
||||
for i in unsp: i.skip = None
|
||||
if self.group and (self.sort_key in ('addr','txid','twmmid')):
|
||||
for a,b in [(unsp[i],unsp[i+1]) for i in range(len(unsp)-1)]:
|
||||
for k in ('addr','txid','mmid'):
|
||||
for k in ('addr','txid','twmmid'):
|
||||
if self.sort_key == k and getattr(a,k) == getattr(b,k):
|
||||
b.skip = (k,'addr')[k=='mmid']
|
||||
b.skip = (k,'addr')[k=='twmmid']
|
||||
|
||||
hdr_fmt = 'UNSPENT OUTPUTS (sort order: %s) Total BTC: %s'
|
||||
out = [hdr_fmt % (' '.join(self.sort_info()), self.total.hl()), table_hdr]
|
||||
out = [hdr_fmt % (' '.join(self.sort_info()), self.total.hl())]
|
||||
if g.chain in ('testnet','regtest'):
|
||||
out += [green('Chain: {}'.format(g.chain.upper()))]
|
||||
af = BTCAddr.fmtc('Address',width=addr_w+1)
|
||||
cf = ('Conf.','Age(d)')[self.show_days]
|
||||
out += [fs % ('Num','TX id'.ljust(tx_w - 5) + ' Vout','',af,'Amt(BTC) ',cf)]
|
||||
|
||||
for n,i in enumerate(unsp):
|
||||
addr_dots = '|' + '.'*33
|
||||
mmid_disp = MMGenID.fmtc('.'*mmid_w if i.skip=='addr'
|
||||
else i.mmid or 'Non-{}'.format(g.proj_name),width=mmid_w,color=True)
|
||||
else i.twmmid if i.twmmid.type=='mmgen'
|
||||
else 'Non-{}'.format(g.proj_name),width=mmid_w,color=True)
|
||||
if self.show_mmid:
|
||||
addr_out = '%s %s' % (
|
||||
type(i.addr).fmtc(addr_dots,width=btaddr_w,color=True) if i.skip == 'addr' \
|
||||
|
|
@ -180,6 +188,7 @@ watch-only wallet using '{}-addrimport' and then re-run this program.
|
|||
i.days if self.show_days else i.confs))
|
||||
|
||||
self.fmt_display = '\n'.join(out) + '\n'
|
||||
# unsp.pdie()
|
||||
return self.fmt_display
|
||||
|
||||
def format_for_printing(self,color=False):
|
||||
|
|
@ -190,13 +199,14 @@ watch-only wallet using '{}-addrimport' and then re-run this program.
|
|||
|
||||
max_lbl_len = max([len(i.label) for i in self.unspent if i.label] or [1])
|
||||
for n,i in enumerate(self.unspent):
|
||||
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)
|
||||
addr = '|'+'.' * 34 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,
|
||||
MMGenID.fmtc(i.mmid or 'Non-{}'.format(g.proj_name),width=14,color=color),
|
||||
MMGenID.fmtc(i.twmmid if i.twmmid.type=='mmgen'
|
||||
else 'Non-{}'.format(g.proj_name),width=14,color=color),
|
||||
i.amt.fmt(color=color),i.confs,i.days,
|
||||
i.label.hl(color=color) if i.label else
|
||||
MMGenAddrLabel.fmtc('',color=color,nullrepl='-',width=max_lbl_len))
|
||||
TwComment.fmtc('',color=color,nullrepl='-',width=max_lbl_len))
|
||||
out.append(s.rstrip())
|
||||
|
||||
fs = 'Unspent outputs ({} UTC)\nSort order: {}\n\n{}\n\nTotal BTC: {}\n'
|
||||
|
|
@ -232,14 +242,15 @@ watch-only wallet using '{}-addrimport' and then re-run this program.
|
|||
"Removing label for address #%s. Is this what you want?" % n):
|
||||
return n,s
|
||||
elif s:
|
||||
if MMGenAddrLabel(s,on_fail='return'):
|
||||
if TwComment(s,on_fail='return'):
|
||||
return n,s
|
||||
|
||||
def view_and_sort(self):
|
||||
def view_and_sort(self,tx):
|
||||
txos = 'Total to spend, excluding fees: {} BTC\n\n'.format(tx.sum_outputs().hl()) if tx.outputs else ''
|
||||
prompt = """
|
||||
Sort options: [t]xid, [a]mount, a[d]dress, [A]ge, [r]everse, [M]mgen addr
|
||||
{}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()
|
||||
""".format(txos).strip()
|
||||
self.display()
|
||||
msg(prompt)
|
||||
|
||||
|
|
@ -257,8 +268,8 @@ Display options: show [D]ays, [g]roup, show [m]mgen addr, r[e]draw screen
|
|||
idx,lbl = self.get_idx_and_label_from_user()
|
||||
if idx:
|
||||
e = self.unspent[idx-1]
|
||||
if type(self).add_label(e.mmid,lbl,addr=e.addr):
|
||||
self.get_data()
|
||||
if type(self).add_label(e.twmmid,lbl,addr=e.addr):
|
||||
self.get_unspent_data()
|
||||
self.do_sort()
|
||||
msg('%s\n%s\n%s' % (self.fmt_display,prompt,p))
|
||||
else:
|
||||
|
|
@ -291,36 +302,71 @@ Display options: show [D]ays, [g]roup, show [m]mgen addr, r[e]draw screen
|
|||
|
||||
# returns on failure
|
||||
@classmethod
|
||||
def add_label(cls,arg1,label='',addr=None):
|
||||
def add_label(cls,arg1,label='',addr=None,silent=False):
|
||||
from mmgen.tx import is_mmgen_id,is_btc_addr
|
||||
mmaddr,btcaddr = None,None
|
||||
if is_btc_addr(addr or arg1):
|
||||
btcaddr = BTCAddr(addr or arg1,on_fail='return')
|
||||
if is_mmgen_id(arg1):
|
||||
mmaddr = MMGenID(arg1)
|
||||
elif is_btc_addr(arg1): # called from 'mmgen-tool add_label'
|
||||
addr = arg1
|
||||
mmaddr = 'btc:'+arg1
|
||||
elif not arg1 and is_btc_addr(addr): # called from view_and_sort(), non-MMGen addr
|
||||
mmaddr = 'btc:'+addr
|
||||
else:
|
||||
die(3,'{}: not a BTC address or {} ID'.format(arg1,g.proj_name))
|
||||
mmaddr = TwMMGenID(arg1)
|
||||
|
||||
if addr:
|
||||
if not BTCAddr(addr,on_fail='return'): return False
|
||||
else:
|
||||
if not btcaddr and not mmaddr:
|
||||
msg("Address '{}' invalid or not found in tracking wallet".format(addr or arg1))
|
||||
return False
|
||||
|
||||
if not btcaddr:
|
||||
from mmgen.addr import AddrData
|
||||
addr = AddrData(source='tw').mmaddr2btcaddr(mmaddr)
|
||||
if not addr:
|
||||
msg('{} address {} not found in tracking wallet'.format(g.proj_name,mmaddr))
|
||||
return False
|
||||
btcaddr = AddrData(source='tw').mmaddr2btcaddr(mmaddr)
|
||||
|
||||
label = MMGenAddrLabel(label,on_fail='return')
|
||||
if not label and label != '': return False
|
||||
if not btcaddr:
|
||||
msg("{} address '{}' not found in tracking wallet".format(g.proj_name,mmaddr))
|
||||
return False
|
||||
|
||||
# Checked that the user isn't importing a random address
|
||||
if not btcaddr.is_in_tracking_wallet():
|
||||
msg("Address '{}' not in tracking wallet".format(btcaddr))
|
||||
return False
|
||||
|
||||
c = bitcoin_connection()
|
||||
if not btcaddr.is_for_current_chain():
|
||||
msg("Address '{}' not valid for chain {}".format(btcaddr,g.chain.upper()))
|
||||
return False
|
||||
|
||||
# Allow for the possibility that BTC addr of MMGen addr was entered.
|
||||
# Do reverse lookup, so that MMGen addr will not be marked as non-MMGen.
|
||||
if not mmaddr:
|
||||
from mmgen.addr import AddrData
|
||||
ad = AddrData(source='tw')
|
||||
mmaddr = ad.btcaddr2mmaddr(btcaddr)
|
||||
|
||||
if not mmaddr: mmaddr = 'btc:'+btcaddr
|
||||
|
||||
mmaddr = TwMMGenID(mmaddr)
|
||||
|
||||
cmt = TwComment(label,on_fail='return')
|
||||
if cmt in (False,None): return False
|
||||
|
||||
lbl = TwLabel(mmaddr + ('',' '+cmt)[bool(cmt)]) # label is ASCII for now
|
||||
|
||||
# NOTE: this works because importaddress() removes the old account before
|
||||
# associating the new account with the address.
|
||||
# Will be replaced by setlabel() with new RPC label API
|
||||
# RPC args: addr,label,rescan[=true],p2sh[=none]
|
||||
ret = c.importaddress(btcaddr,lbl,False,on_fail='return')
|
||||
|
||||
acct = mmaddr + (' ' + label if label else '') # label is ASCII for now
|
||||
# return on failure - args: addr,label,rescan,p2sh
|
||||
ret = bitcoin_connection().importaddress(addr,acct,False,on_fail='return')
|
||||
from mmgen.rpc import rpc_error,rpc_errmsg
|
||||
if rpc_error(ret): msg('From bitcoind: ' + rpc_errmsg(ret))
|
||||
return not rpc_error(ret)
|
||||
if rpc_error(ret):
|
||||
msg('From bitcoind: ' + rpc_errmsg(ret))
|
||||
if not silent:
|
||||
msg('Label could not be {}'.format(('removed','added')[bool(label)]))
|
||||
return False
|
||||
else:
|
||||
m = mmaddr.type.replace('mmg','MMG')
|
||||
a = mmaddr.replace('btc:','')
|
||||
s = '{} address {} in tracking wallet'.format(m,a)
|
||||
if label: msg("Added label '{}' to {}".format(label,s))
|
||||
else: msg('Removed label from {}'.format(s))
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def remove_label(cls,mmaddr): cls.add_label(mmaddr,'')
|
||||
|
|
|
|||
410
mmgen/tx.py
410
mmgen/tx.py
|
|
@ -17,7 +17,7 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
tx.py: Bitcoin transaction routines
|
||||
tx.py: Transaction routines for the MMGen suite
|
||||
"""
|
||||
|
||||
import sys,os
|
||||
|
|
@ -30,24 +30,97 @@ 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_addrlist_id(s): return AddrListID(s,on_fail='silent')
|
||||
def is_tw_label(s): return TwLabel(s,on_fail='silent')
|
||||
|
||||
def is_wif(s):
|
||||
if s == '': return False
|
||||
from mmgen.bitcoin import wif2hex
|
||||
return bool(wif2hex(s))
|
||||
|
||||
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 segwit_is_active(exit_on_error=False):
|
||||
d = bitcoin_connection().getblockchaininfo()
|
||||
if d['chain'] == 'regtest' or d['bip9_softforks']['segwit']['status'] == 'active':
|
||||
return True
|
||||
if g.skip_segwit_active_check: return True
|
||||
if exit_on_error:
|
||||
die(2,'Segwit not active on this chain. Exiting')
|
||||
else:
|
||||
return False
|
||||
|
||||
class MMGenTxInput(MMGenListItem):
|
||||
attrs = 'txid','vout','amt','label','mmid','addr','confs','scriptPubKey','have_wif','sequence'
|
||||
label = MMGenListItemAttr('label','MMGenAddrLabel')
|
||||
def bytes2int(hex_bytes):
|
||||
r = hexlify(unhexlify(hex_bytes)[::-1])
|
||||
if r[0] in '89abcdef':
|
||||
die(3,"{}: Negative values not permitted in transaction!".format(hex_bytes))
|
||||
return int(r,16)
|
||||
|
||||
class MMGenTxOutput(MMGenListItem):
|
||||
attrs = 'txid','vout','amt','label','mmid','addr','have_wif','is_chg'
|
||||
label = MMGenListItemAttr('label','MMGenAddrLabel')
|
||||
def bytes2btc(hex_bytes):
|
||||
return bytes2int(hex_bytes) * g.satoshi
|
||||
|
||||
from collections import OrderedDict
|
||||
class DeserializedTX(OrderedDict,MMGenObject): # need to add MMGen types
|
||||
def __init__(self,txhex):
|
||||
tx = list(unhexlify(txhex))
|
||||
tx_copy = tx[:]
|
||||
|
||||
def hshift(l,n,reverse=False):
|
||||
ret = l[:n]
|
||||
del l[:n]
|
||||
return hexlify(''.join(ret[::-1] if reverse else ret))
|
||||
|
||||
# https://bitcoin.org/en/developer-reference#compactsize-unsigned-integers
|
||||
# For example, the number 515 is encoded as 0xfd0302.
|
||||
def readVInt(l):
|
||||
s = int(hexlify(l[0]),16)
|
||||
bytes_len = 1 if s < 0xfd else 2 if s == 0xfd else 4 if s == 0xfe else 8
|
||||
if bytes_len != 1: del l[0]
|
||||
ret = int(hexlify(''.join(l[:bytes_len][::-1])),16)
|
||||
del l[:bytes_len]
|
||||
return ret
|
||||
|
||||
d = { 'version': bytes2int(hshift(tx,4)) }
|
||||
has_witness = (False,True)[hexlify(tx[0])=='00']
|
||||
if has_witness:
|
||||
u = hshift(tx,2)[2:]
|
||||
if u != '01':
|
||||
die(2,"'{}': Illegal value for flag in transaction!".format(u))
|
||||
del tx_copy[-len(tx)-2:-len(tx)]
|
||||
|
||||
d['num_txins'] = readVInt(tx)
|
||||
d['txins'] = MMGenList([OrderedDict((
|
||||
('txid', hshift(tx,32,reverse=True)),
|
||||
('vout', bytes2int(hshift(tx,4))),
|
||||
('scriptSig', hshift(tx,readVInt(tx))),
|
||||
('nSeq', hshift(tx,4,reverse=True))
|
||||
)) for i in range(d['num_txins'])])
|
||||
|
||||
d['num_txouts'] = readVInt(tx)
|
||||
d['txouts'] = MMGenList([OrderedDict((
|
||||
('amount', bytes2btc(hshift(tx,8))),
|
||||
('scriptPubKey', hshift(tx,readVInt(tx)))
|
||||
)) for i in range(d['num_txouts'])])
|
||||
|
||||
d['witness_size'] = 0
|
||||
if has_witness:
|
||||
# https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki
|
||||
# A non-witness program (defined hereinafter) txin MUST be associated with an empty
|
||||
# witness field, represented by a 0x00.
|
||||
del tx_copy[-len(tx):-4]
|
||||
wd,tx = tx[:-4],tx[-4:]
|
||||
d['witness_size'] = len(wd) + 2 # add marker and flag
|
||||
for i in range(len(d['txins'])):
|
||||
if hexlify(wd[0]) == '00':
|
||||
hshift(wd,1)
|
||||
continue
|
||||
d['txins'][i]['witness'] = [hshift(wd,readVInt(wd)) for item in range(readVInt(wd))]
|
||||
if wd:
|
||||
die(3,'More witness data than inputs with witnesses!')
|
||||
|
||||
d['lock_time'] = bytes2int(hshift(tx,4))
|
||||
d['txid'] = hexlify(sha256(sha256(''.join(tx_copy)).digest()).digest()[::-1])
|
||||
|
||||
keys = 'txid','version','lock_time','witness_size','num_txins','txins','num_txouts','txouts'
|
||||
return OrderedDict.__init__(self, ((k,d[k]) for k in keys))
|
||||
|
||||
class MMGenTX(MMGenObject):
|
||||
ext = 'rawtx'
|
||||
|
|
@ -56,11 +129,25 @@ class MMGenTX(MMGenObject):
|
|||
txid_ext = 'txid'
|
||||
desc = 'transaction'
|
||||
|
||||
class MMGenTxInput(MMGenListItem):
|
||||
attrs = 'txid','vout','amt','label','mmid','addr','confs','scriptPubKey','have_wif','sequence'
|
||||
txid = MMGenListItemAttr('txid','BitcoinTxID')
|
||||
scriptPubKey = MMGenListItemAttr('scriptPubKey','HexStr')
|
||||
|
||||
class MMGenTxOutput(MMGenListItem):
|
||||
attrs = 'txid','vout','amt','label','mmid','addr','have_wif','is_chg'
|
||||
|
||||
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',
|
||||
|
||||
class MMGenTxInputList(list,MMGenObject): pass
|
||||
class MMGenTxOutputList(list,MMGenObject): pass
|
||||
|
||||
def __init__(self,filename=None):
|
||||
self.inputs = []
|
||||
self.inputs_enc = []
|
||||
self.outputs = []
|
||||
self.outputs_enc = []
|
||||
self.inputs = self.MMGenTxInputList()
|
||||
self.outputs = self.MMGenTxOutputList()
|
||||
self.send_amt = BTCAmt('0') # total amt minus change
|
||||
self.hex = '' # raw serialized hex transaction
|
||||
self.label = MMGenTXLabel('')
|
||||
|
|
@ -70,13 +157,21 @@ class MMGenTX(MMGenObject):
|
|||
self.chksum = ''
|
||||
self.fmt_data = ''
|
||||
self.blockcount = 0
|
||||
self.chain = None
|
||||
|
||||
if filename:
|
||||
if get_extension(filename) == self.sig_ext:
|
||||
self.mark_signed()
|
||||
self.parse_tx_file(filename)
|
||||
self.check_sigs() # marks the tx as signed
|
||||
|
||||
# repeat with sign and send, because bitcoind could be restarted
|
||||
self.die_if_incorrect_chain()
|
||||
|
||||
def die_if_incorrect_chain(self):
|
||||
if self.chain and g.chain and self.chain != g.chain:
|
||||
die(2,'Transaction is for {}, but current chain is {}!'.format(self.chain,g.chain))
|
||||
|
||||
def add_output(self,btcaddr,amt,is_chg=None):
|
||||
self.outputs.append(MMGenTxOutput(addr=btcaddr,amt=amt,is_chg=is_chg))
|
||||
self.outputs.append(self.MMGenTxOutput(addr=btcaddr,amt=amt,is_chg=is_chg))
|
||||
|
||||
def get_chg_output_idx(self):
|
||||
for i in range(len(self.outputs)):
|
||||
|
|
@ -87,7 +182,7 @@ class MMGenTX(MMGenObject):
|
|||
def update_output_amt(self,idx,amt):
|
||||
o = self.outputs[idx].__dict__
|
||||
o['amt'] = amt
|
||||
self.outputs[idx] = MMGenTxOutput(**o)
|
||||
self.outputs[idx] = self.MMGenTxOutput(**o)
|
||||
|
||||
def del_output(self,idx):
|
||||
self.outputs.pop(idx)
|
||||
|
|
@ -95,7 +190,7 @@ class MMGenTX(MMGenObject):
|
|||
def sum_outputs(self,exclude=None):
|
||||
olist = self.outputs if exclude == None else \
|
||||
self.outputs[:exclude] + self.outputs[exclude+1:]
|
||||
return BTCAmt(sum([e.amt for e in olist]))
|
||||
return BTCAmt(sum(e.amt for e in olist))
|
||||
|
||||
def add_mmaddrs_to_outputs(self,ad_w,ad_f):
|
||||
a = [e.addr for e in self.outputs]
|
||||
|
|
@ -107,7 +202,7 @@ class MMGenTX(MMGenObject):
|
|||
if f: e.label = f
|
||||
|
||||
# def encode_io(self,desc):
|
||||
# tr = getattr((MMGenTxOutput,MMGenTxInput)[desc=='inputs'],'tr')
|
||||
# tr = getattr((self.MMGenTxOutput,self.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)]
|
||||
|
|
@ -140,24 +235,89 @@ class MMGenTX(MMGenObject):
|
|||
def edit_comment(self):
|
||||
return self.add_comment(self)
|
||||
|
||||
# https://bitcoin.stackexchange.com/questions/1195/
|
||||
# how-to-calculate-transaction-size-before-sending
|
||||
def has_segwit_inputs(self):
|
||||
return any(i.mmid and i.mmid.mmtype == 'S' for i in self.inputs)
|
||||
|
||||
# https://bitcoin.stackexchange.com/questions/1195/how-to-calculate-transaction-size-before-sending
|
||||
# 180: uncompressed, 148: compressed
|
||||
def get_size(self):
|
||||
def estimate_size_old(self):
|
||||
if not self.inputs or not self.outputs: return None
|
||||
return len(self.inputs)*180 + len(self.outputs)*34 + 10
|
||||
|
||||
# https://bitcoincore.org/en/segwit_wallet_dev/
|
||||
# vsize: 3 times of the size with original serialization, plus the size with new
|
||||
# serialization, divide the result by 4 and round up to the next integer.
|
||||
|
||||
# TODO: results differ slightly from actual transaction size
|
||||
def estimate_vsize(self):
|
||||
if not self.inputs or not self.outputs: return None
|
||||
|
||||
sig_size = 72 # sig in DER format
|
||||
pubkey_size = { 'compressed':33, 'uncompressed':65 }
|
||||
outpoint_size = 36 # txid + vout
|
||||
|
||||
def get_inputs_size():
|
||||
segwit_isize = outpoint_size + 1 + 23 + 4 # (txid,vout) [scriptSig size] scriptSig nSeq # = 64
|
||||
# txid vout [scriptSig size] scriptSig (<sig> <pubkey>) nSeq
|
||||
legacy_isize = outpoint_size + 1 + 2 + sig_size + pubkey_size['uncompressed'] + 4 # = 180
|
||||
compressed_isize = outpoint_size + 1 + 2 + sig_size + pubkey_size['compressed'] + 4 # = 148
|
||||
ret = sum((legacy_isize,segwit_isize)[i.mmid.mmtype=='S'] for i in self.inputs if i.mmid)
|
||||
# assume all non-MMGen pubkeys are compressed (we have no way of knowing
|
||||
# until we see the key). TODO: add user option to specify this?
|
||||
return ret + sum(compressed_isize for i in self.inputs if not i.mmid)
|
||||
|
||||
def get_outputs_size():
|
||||
return sum((34,32)[o.addr.addr_fmt=='p2sh'] for o in self.outputs)
|
||||
|
||||
# https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki
|
||||
# The witness is a serialization of all witness data of the transaction. Each txin is
|
||||
# associated with a witness field. A witness field starts with a var_int to indicate the
|
||||
# number of stack items for the txin. It is followed by stack items, with each item starts
|
||||
# with a var_int to indicate the length. Witness data is NOT script.
|
||||
|
||||
# A non-witness program txin MUST be associated with an empty witness field, represented
|
||||
# by a 0x00. If all txins are not witness program, a transaction's wtxid is equal to its txid.
|
||||
def get_witness_size():
|
||||
if not self.has_segwit_inputs(): return 0
|
||||
wf_size = 1 + 1 + sig_size + 1 + pubkey_size['compressed'] # vInt vInt sig vInt pubkey = 108
|
||||
return sum((1,wf_size)[bool(i.mmid) and i.mmid.mmtype=='S'] for i in self.inputs)
|
||||
|
||||
isize = get_inputs_size()
|
||||
osize = get_outputs_size()
|
||||
wsize = get_witness_size()
|
||||
# pmsg([i.mmid and i.mmid.mmtype == 'S' for i in self.inputs])
|
||||
# pmsg([i.mmid for i in self.inputs])
|
||||
# pmsg([i.mmid for i in self.outputs])
|
||||
# pmsg('isize',isize)
|
||||
# pmsg('osize',osize)
|
||||
# pmsg('wsize',wsize)
|
||||
|
||||
# TODO: compute real varInt sizes instead of assuming 1 byte
|
||||
# old serialization: [nVersion] [vInt][txins][vInt][txouts] [nLockTime]
|
||||
old_size = 4 + 1 + isize + 1 + osize + 4
|
||||
# new serialization: [nVersion][marker][flag][vInt][txins][vInt][txouts][witness][nLockTime]
|
||||
new_size = 4 + 1 + 1 + 1 + isize + 1 + osize + wsize + 4 \
|
||||
if wsize else old_size
|
||||
|
||||
ret = (old_size * 3 + new_size) / 4
|
||||
# pmsg('old_size',old_size) # This should be equal to the size of serialized signed tx
|
||||
# pmsg('ret',ret)
|
||||
# pmsg('estimate_size_old',self.estimate_size_old())
|
||||
return ret
|
||||
|
||||
estimate_size = estimate_vsize
|
||||
|
||||
def get_fee(self):
|
||||
return self.sum_inputs() - self.sum_outputs()
|
||||
|
||||
def btc2spb(self,btc_fee):
|
||||
return int(btc_fee/g.satoshi/self.get_size())
|
||||
return int(btc_fee/g.satoshi/self.estimate_size())
|
||||
|
||||
def get_relay_fee(self):
|
||||
assert self.get_size()
|
||||
kb_fee = BTCAmt(bitcoin_connection().getinfo()['relayfee'])
|
||||
assert self.estimate_size()
|
||||
kb_fee = BTCAmt(bitcoin_connection().getnetworkinfo()['relayfee'])
|
||||
vmsg('Relay fee: {} BTC/kB'.format(kb_fee))
|
||||
return kb_fee * self.get_size() / 1024
|
||||
return kb_fee * self.estimate_size() / 1024
|
||||
|
||||
def convert_fee_spec(self,tx_fee,tx_size,on_fail='throw'):
|
||||
if BTCAmt(tx_fee,on_fail='silent'):
|
||||
|
|
@ -174,7 +334,7 @@ class MMGenTX(MMGenObject):
|
|||
assert False, "'{}': invalid tx-fee argument".format(tx_fee)
|
||||
|
||||
def get_usr_fee(self,tx_fee,desc='Missing description'):
|
||||
btc_fee = self.convert_fee_spec(tx_fee,self.get_size(),on_fail='return')
|
||||
btc_fee = self.convert_fee_spec(tx_fee,self.estimate_size(),on_fail='return')
|
||||
if btc_fee == None:
|
||||
msg("'{}': cannot convert satoshis-per-byte to BTC because transaction size is unknown".format(tx_fee))
|
||||
assert False # because we shouldn't be calling this if tx size is unknown
|
||||
|
|
@ -215,28 +375,35 @@ class MMGenTX(MMGenObject):
|
|||
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]
|
||||
io,il = (
|
||||
(self.MMGenTxOutput,self.MMGenTxOutputList),
|
||||
(self.MMGenTxInput,self.MMGenTxInputList)
|
||||
)[desc=='inputs']
|
||||
return il([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
|
||||
io = self.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 copy_inputs_from_tw(self,tw_unspent_data):
|
||||
txi,self.inputs = self.MMGenTxInput,self.MMGenTxInputList()
|
||||
for d in tw_unspent_data:
|
||||
t = txi(**dict([(attr,getattr(d,attr)) for attr in d.__dict__ if attr in txi.attrs]))
|
||||
if d.twmmid.type == 'mmgen': t.mmid = d.twmmid # twmmid -> mmid
|
||||
self.inputs.append(t)
|
||||
|
||||
def get_input_sids(self):
|
||||
return set([e.mmid[:8] for e in self.inputs if e.mmid])
|
||||
return set(e.mmid.sid for e in self.inputs if e.mmid)
|
||||
|
||||
def get_output_sids(self):
|
||||
return set([e.mmid[:8] for e in self.outputs if e.mmid])
|
||||
return set(e.mmid.sid for e in self.outputs if e.mmid)
|
||||
|
||||
def sum_inputs(self):
|
||||
return sum([e.amt for e in self.inputs])
|
||||
return sum(e.amt for e in self.inputs)
|
||||
|
||||
def add_timestamp(self):
|
||||
self.timestamp = make_timestamp()
|
||||
|
|
@ -247,7 +414,8 @@ class MMGenTX(MMGenObject):
|
|||
def format(self):
|
||||
from mmgen.bitcoin import b58encode
|
||||
lines = [
|
||||
'{} {} {} {}'.format(
|
||||
'{} {} {} {} {}'.format(
|
||||
self.chain.upper() if self.chain else 'Unknown',
|
||||
self.txid,
|
||||
self.send_amt,
|
||||
self.timestamp,
|
||||
|
|
@ -266,68 +434,136 @@ class MMGenTX(MMGenObject):
|
|||
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 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):
|
||||
|
||||
self.die_if_incorrect_chain()
|
||||
|
||||
if not keys:
|
||||
msg('No keys. Cannot sign!')
|
||||
return False
|
||||
|
||||
qmsg('Passing %s key%s to bitcoind' % (len(keys),suf(keys,'k')))
|
||||
dmsg('Keys:\n %s' % '\n '.join(keys))
|
||||
qmsg('Passing %s key%s to bitcoind' % (len(keys),suf(keys,'s')))
|
||||
|
||||
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)
|
||||
sig_data = []
|
||||
for d in self.inputs:
|
||||
e = dict([(k,getattr(d,k)) for k in 'txid','vout','scriptPubKey','amt'])
|
||||
e['amount'] = e['amt']
|
||||
del e['amt']
|
||||
wif = keys[d.addr]
|
||||
if d.mmid and d.mmid.mmtype == 'S':
|
||||
from mmgen.bitcoin import pubhex2redeem_script
|
||||
from mmgen.addr import keygen_wif2pubhex,keygen_selector
|
||||
pubhex = keygen_wif2pubhex(wif,keygen_selector())
|
||||
e['redeemScript'] = pubhex2redeem_script(pubhex)
|
||||
sig_data.append(e)
|
||||
|
||||
from mmgen.bitcoin import hash256
|
||||
msg_r('Signing transaction{}...'.format(tx_num_str))
|
||||
sig_tx = c.signrawtransaction(self.hex,sig_data,keys)
|
||||
# sighashtype defaults to 'ALL'
|
||||
sig_tx = c.signrawtransaction(self.hex,sig_data,keys.values())
|
||||
|
||||
if sig_tx['complete']:
|
||||
msg('OK')
|
||||
self.hex = sig_tx['hex']
|
||||
self.mark_signed()
|
||||
vmsg('Signed transaction size: {}'.format(len(self.hex)/2))
|
||||
dt = DeserializedTX(self.hex)
|
||||
txid = dt['txid']
|
||||
self.check_sigs(dt)
|
||||
assert txid == c.decoderawtransaction(self.hex)['txid'], 'txid mismatch (after signing)'
|
||||
self.btc_txid = BitcoinTxID(txid,on_fail='return')
|
||||
msg('OK')
|
||||
return True
|
||||
else:
|
||||
msg('failed\nBitcoind returned the following errors:')
|
||||
pp_msg(sig_tx['errors'])
|
||||
pmsg(sig_tx['errors'])
|
||||
return False
|
||||
|
||||
def mark_raw(self):
|
||||
self.desc = 'transaction'
|
||||
self.ext = self.raw_ext
|
||||
|
||||
def mark_signed(self):
|
||||
def mark_signed(self): # called ONLY by check_sigs()
|
||||
self.desc = 'signed transaction'
|
||||
self.ext = self.sig_ext
|
||||
|
||||
def is_signed(self,color=False):
|
||||
def marked_signed(self,color=False):
|
||||
ret = self.desc == 'signed transaction'
|
||||
return (red,green)[ret](str(ret)) if color else ret
|
||||
|
||||
def check_signed(self,c):
|
||||
d = c.decoderawtransaction(self.hex)
|
||||
ret = bool(d['vin'][0]['scriptSig']['hex'])
|
||||
if ret: self.mark_signed()
|
||||
return ret
|
||||
def check_sigs(self,deserial_tx=None): # return False if no sigs, die on error
|
||||
txins = (deserial_tx or DeserializedTX(self.hex))['txins']
|
||||
has_ss = any(ti['scriptSig'] for ti in txins)
|
||||
has_witness = any('witness' in ti and ti['witness'] for ti in txins)
|
||||
if not (has_ss or has_witness):
|
||||
return False
|
||||
for ti in txins:
|
||||
if ti['scriptSig'][:6] == '160014' and len(ti['scriptSig']) == 46: # P2SH-P2WPKH
|
||||
assert 'witness' in ti, 'missing witness'
|
||||
assert type(ti['witness']) == list and len(ti['witness']) == 2, 'malformed witness'
|
||||
assert len(ti['witness'][1]) == 66, 'incorrect witness pubkey length'
|
||||
elif ti['scriptSig'] == '': # native P2WPKH
|
||||
die(3,('TX has missing signature','Native P2WPKH not implemented')['witness' in ti])
|
||||
else: # non-witness
|
||||
assert not 'witness' in ti, 'non-witness input has witness'
|
||||
# sig_size 72 (DER format), pubkey_size 'compressed':33, 'uncompressed':65
|
||||
assert (200 < len(ti['scriptSig']) < 300), 'malformed scriptSig' # VERY rough check
|
||||
self.mark_signed()
|
||||
return True
|
||||
|
||||
def has_segwit_outputs(self):
|
||||
return any(o.mmid and o.mmid.mmtype == 'S' for o in self.outputs)
|
||||
|
||||
def is_in_mempool(self,c):
|
||||
return 'size' in c.getmempoolentry(self.btc_txid,on_fail='silent')
|
||||
|
||||
def is_in_wallet(self,c):
|
||||
ret = c.gettransaction(self.btc_txid,on_fail='silent')
|
||||
return 'confirmations' in ret and ret['confirmations'] > 0
|
||||
|
||||
def is_replaced(self,c):
|
||||
if self.is_in_mempool(c): return False
|
||||
ret = c.gettransaction(self.btc_txid,on_fail='silent')
|
||||
if not 'bip125-replaceable' in ret or not 'confirmations' in ret or ret['confirmations'] > 0:
|
||||
return False
|
||||
return -ret['confirmations'] + 1 # 1: replacement in mempool, 2: replacement confirmed
|
||||
|
||||
def is_in_utxos(self,c):
|
||||
return 'txid' in c.getrawtransaction(self.btc_txid,True,on_fail='silent')
|
||||
|
||||
def send(self,c,prompt_user=True):
|
||||
|
||||
self.die_if_incorrect_chain()
|
||||
|
||||
bogus_send = os.getenv('MMGEN_BOGUS_SEND')
|
||||
|
||||
if self.has_segwit_outputs() and not segwit_is_active() and not bogus_send:
|
||||
m = 'Transaction has MMGen Segwit outputs, but this blockchain does not support Segwit'
|
||||
die(2,m+' at the current height')
|
||||
|
||||
if self.get_fee() > g.max_tx_fee:
|
||||
die(2,'Transaction fee ({}) greater than max_tx_fee ({})!'.format(self.get_fee(),g.max_tx_fee))
|
||||
|
||||
if self.is_in_mempool(c):
|
||||
msg('Warning: transaction is in mempool!')
|
||||
elif self.is_in_wallet(c):
|
||||
die(1,'Transaction has been confirmed!')
|
||||
elif self.is_in_utxos(c):
|
||||
die(2,red('ERROR: transaction is in the blockchain (but not in the tracking wallet)!'))
|
||||
|
||||
ret = self.is_replaced(c) # 1: replacement in mempool, 2: replacement confirmed
|
||||
if ret:
|
||||
die(1,'Transaction has been replaced'+('',', and the replacement TX is confirmed')[ret==2]+'!')
|
||||
|
||||
if prompt_user:
|
||||
m1 = ("Once this transaction is sent, there's no taking it back!",'')[bool(opt.quiet)]
|
||||
m2 = 'broadcast this transaction to the network'
|
||||
m2 = 'broadcast this transaction to the {} network'.format(g.chain.upper())
|
||||
m3 = ('YES, I REALLY WANT TO DO THIS','YES')[bool(opt.quiet or opt.yes)]
|
||||
confirm_or_exit(m1,m2,m3)
|
||||
|
||||
msg('Sending transaction')
|
||||
if os.getenv('MMGEN_BOGUS_SEND'):
|
||||
if bogus_send:
|
||||
ret = 'deadbeef' * 8
|
||||
m = 'BOGUS transaction NOT sent: %s'
|
||||
else:
|
||||
|
|
@ -335,15 +571,15 @@ class MMGenTX(MMGenObject):
|
|||
m = 'Transaction sent: %s'
|
||||
|
||||
if ret:
|
||||
self.btc_txid = BitcoinTxID(ret,on_fail='return')
|
||||
if self.btc_txid:
|
||||
self.desc = 'sent transaction'
|
||||
msg(m % self.btc_txid.hl())
|
||||
self.add_timestamp()
|
||||
self.add_blockcount(c)
|
||||
return True
|
||||
if not bogus_send:
|
||||
assert ret == self.btc_txid, 'txid mismatch (after sending)'
|
||||
self.desc = 'sent transaction'
|
||||
msg(m % self.btc_txid.hl())
|
||||
self.add_timestamp()
|
||||
self.add_blockcount(c)
|
||||
return True
|
||||
|
||||
# rpc implementation exits on failure, so we won't get here
|
||||
# rpc call exits on failure, so we won't get here
|
||||
msg('Sending of transaction {} failed'.format(self.txid))
|
||||
return False
|
||||
|
||||
|
|
@ -375,7 +611,7 @@ class MMGenTX(MMGenObject):
|
|||
o = self.format_view(terse=terse).encode('utf8')
|
||||
if pager: do_pager(o)
|
||||
else:
|
||||
sys.stdout.write(o)
|
||||
Msg_r(o)
|
||||
from mmgen.term import get_char
|
||||
if pause:
|
||||
get_char('Press any key to continue: ')
|
||||
|
|
@ -397,6 +633,7 @@ class MMGenTX(MMGenObject):
|
|||
self.inputs[0].sequence = g.max_int - 2
|
||||
|
||||
def format_view(self,terse=False):
|
||||
# self.pdie()
|
||||
try:
|
||||
blockcount = bitcoin_connection().getblockcount()
|
||||
except:
|
||||
|
|
@ -408,9 +645,10 @@ class MMGenTX(MMGenObject):
|
|||
)[bool(terse)]
|
||||
|
||||
out = hdr_fs.format(self.txid.hl(),self.send_amt.hl(),self.timestamp,
|
||||
self.is_rbf(color=True),self.is_signed(color=True))
|
||||
self.is_rbf(color=True),self.marked_signed(color=True))
|
||||
|
||||
enl = ('\n','')[bool(terse)]
|
||||
if self.chain in ('testnet','regtest'): out += green('Chain: {}\n'.format(self.chain.upper()))
|
||||
if self.btc_txid: out += 'Bitcoin TxID: {}\n'.format(self.btc_txid.hl())
|
||||
out += enl
|
||||
|
||||
|
|
@ -418,9 +656,8 @@ class MMGenTX(MMGenObject):
|
|||
out += 'Comment: %s\n%s' % (self.label.hl(),enl)
|
||||
out += 'Inputs:\n' + enl
|
||||
|
||||
nonmm_str = '(non-{pnm} address){s}'.format(pnm=g.proj_name,s=('',' ')[terse])
|
||||
# for i in self.inputs: print i #DEBUG
|
||||
for n,e in enumerate(self.inputs):
|
||||
nonmm_str = '(non-{pnm} address){s} '.format(pnm=g.proj_name,s=('',' ')[terse])
|
||||
for n,e in enumerate(sorted(self.inputs,key=lambda o: o.mmid.sort_key if o.mmid else o.addr)):
|
||||
if blockcount:
|
||||
confs = e.confs + blockcount - self.blockcount
|
||||
days = int(confs * g.mins_per_block / (60*24))
|
||||
|
|
@ -440,7 +677,7 @@ class MMGenTX(MMGenObject):
|
|||
out += '\n'
|
||||
|
||||
out += 'Outputs:\n' + enl
|
||||
for n,e in enumerate(self.outputs):
|
||||
for n,e in enumerate(sorted(self.outputs,key=lambda o: o.mmid.sort_key if o.mmid else o.addr)):
|
||||
if e.mmid:
|
||||
app=('',' (chg)')[bool(e.is_chg and terse)]
|
||||
mmid_fmt = e.mmid.fmt(width=len(nonmm_str),encl='()',color=True,
|
||||
|
|
@ -474,7 +711,11 @@ class MMGenTX(MMGenObject):
|
|||
)
|
||||
if opt.verbose:
|
||||
ts = len(self.hex)/2 if self.hex else 'unknown'
|
||||
out += 'Transaction size: estimated - {}, actual - {}\n'.format(self.get_size(),ts)
|
||||
out += 'Transaction size: Vsize={} Actual={}'.format(self.estimate_size(),ts)
|
||||
if self.marked_signed():
|
||||
ws = DeserializedTX(self.hex)['witness_size']
|
||||
out += ' Base={} Witness={}'.format(ts-ws,ws)
|
||||
out += '\n'
|
||||
|
||||
# TX label might contain non-ascii chars
|
||||
return out
|
||||
|
|
@ -489,7 +730,7 @@ class MMGenTX(MMGenObject):
|
|||
|
||||
if len(tx_data) < 5: do_err('number of lines')
|
||||
|
||||
self.chksum = tx_data.pop(0)
|
||||
self.chksum = HexStr(tx_data.pop(0))
|
||||
if self.chksum != make_chksum_6(' '.join(tx_data)):
|
||||
do_err('checksum')
|
||||
|
||||
|
|
@ -517,12 +758,17 @@ class MMGenTX(MMGenObject):
|
|||
else:
|
||||
do_err('number of lines')
|
||||
|
||||
if len(metadata.split()) != 4: do_err('metadata')
|
||||
metadata = metadata.split()
|
||||
if len(metadata) not in (4,5): do_err('metadata')
|
||||
if len(metadata) == 5:
|
||||
t = metadata.pop(0)
|
||||
self.chain = (t.lower(),None)[t=='Unknown']
|
||||
|
||||
self.txid,send_amt,self.timestamp,blockcount = metadata.split()
|
||||
self.txid,send_amt,self.timestamp,blockcount = metadata
|
||||
self.txid = MMGenTxID(self.txid)
|
||||
self.send_amt = BTCAmt(send_amt)
|
||||
self.blockcount = int(blockcount)
|
||||
self.hex = HexStr(self.hex)
|
||||
|
||||
try: unhexlify(self.hex)
|
||||
except: do_err('hex data')
|
||||
|
|
@ -530,6 +776,9 @@ class MMGenTX(MMGenObject):
|
|||
try: self.inputs = self.decode_io('inputs',eval(inputs_data))
|
||||
except: do_err('inputs data')
|
||||
|
||||
if not self.chain and not self.inputs[0].addr.testnet:
|
||||
self.chain = 'mainnet'
|
||||
|
||||
try: self.outputs = self.decode_io('outputs',eval(outputs_data))
|
||||
except: do_err('btc-to-mmgen address map data')
|
||||
|
||||
|
|
@ -545,9 +794,9 @@ class MMGenBumpTX(MMGenTX):
|
|||
if not self.is_rbf():
|
||||
die(1,"Transaction '{}' is not replaceable (RBF)".format(self.txid))
|
||||
|
||||
# If sending, require tx to have been signed and broadcast
|
||||
# If sending, require tx to have been signed
|
||||
if send:
|
||||
if not self.is_signed():
|
||||
if not self.marked_signed():
|
||||
die(1,"File '{}' is not a signed {} transaction file".format(filename,g.proj_name))
|
||||
if not self.btc_txid:
|
||||
die(1,"Transaction '{}' was not broadcast to the network".format(self.txid,g.proj_name))
|
||||
|
|
@ -560,7 +809,8 @@ class MMGenBumpTX(MMGenTX):
|
|||
init_reply = opt.output_to_reduce
|
||||
while True:
|
||||
if init_reply == None:
|
||||
reply = my_raw_input('Which output do you wish to deduct the fee from? ')
|
||||
m = 'Choose an output to deduct the fee from (Hit ENTER for the change output): '
|
||||
reply = my_raw_input(m) or 'c'
|
||||
else:
|
||||
reply,init_reply = init_reply,None
|
||||
if chg_idx == None and not is_int(reply):
|
||||
|
|
|
|||
|
|
@ -121,7 +121,10 @@ def mmaddr2baddr(c,mmaddr,ad_w,ad_f):
|
|||
|
||||
return BTCAddr(btc_addr)
|
||||
|
||||
def get_fee_from_estimate_or_usr(tx,c,estimate_fail_msg_shown=[]):
|
||||
def get_fee_from_estimate_or_user(tx,estimate_fail_msg_shown=[]):
|
||||
|
||||
c = bitcoin_connection()
|
||||
|
||||
if opt.tx_fee:
|
||||
desc = 'User-selected'
|
||||
start_fee = opt.tx_fee
|
||||
|
|
@ -134,14 +137,86 @@ def get_fee_from_estimate_or_usr(tx,c,estimate_fail_msg_shown=[]):
|
|||
estimate_fail_msg_shown.append(True)
|
||||
start_fee = None
|
||||
else:
|
||||
start_fee = BTCAmt(ret) * opt.tx_fee_adj * tx.get_size() / 1024
|
||||
start_fee = BTCAmt(ret) * opt.tx_fee_adj * tx.estimate_size() / 1024
|
||||
if opt.verbose:
|
||||
msg('{} fee ({} confs): {} BTC/kB'.format(desc,opt.tx_confs,ret))
|
||||
msg('TX size (estimated): {}'.format(tx.get_size()))
|
||||
msg('TX size (estimated): {}'.format(tx.estimate_size()))
|
||||
|
||||
return tx.get_usr_fee_interactive(start_fee,desc=desc)
|
||||
|
||||
def txcreate(opt,cmd_args,do_info=False,caller='txcreate'):
|
||||
def get_outputs_from_cmdline(cmd_args,tx):
|
||||
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)
|
||||
|
||||
ad_f = AddrData()
|
||||
for a in addrfiles:
|
||||
check_infile(a)
|
||||
ad_f.add(AddrList(a))
|
||||
|
||||
ad_w = AddrData(source='tw')
|
||||
|
||||
for a in cmd_args:
|
||||
if ',' in a:
|
||||
a1,a2 = a.split(',',1)
|
||||
if is_mmgen_id(a1) or is_btc_addr(a1):
|
||||
btc_addr = mmaddr2baddr(c,a1,ad_w,ad_f) if is_mmgen_id(a1) else BTCAddr(a1)
|
||||
tx.add_output(btc_addr,BTCAmt(a2))
|
||||
else:
|
||||
die(2,"%s: unrecognized subargument in argument '%s'" % (a1,a))
|
||||
elif is_mmgen_id(a) or is_btc_addr(a):
|
||||
if tx.get_chg_output_idx() != None:
|
||||
die(2,'ERROR: More than one change address listed on command line')
|
||||
btc_addr = mmaddr2baddr(c,a,ad_w,ad_f) if is_mmgen_id(a) else BTCAddr(a)
|
||||
tx.add_output(btc_addr,BTCAmt('0'),is_chg=True)
|
||||
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 tx.get_chg_output_idx() == None:
|
||||
die(2,('ERROR: No change output specified',wmsg['no_change_output'])[len(tx.outputs) == 1])
|
||||
|
||||
tx.add_mmaddrs_to_outputs(ad_w,ad_f)
|
||||
|
||||
if not segwit_is_active() and tx.has_segwit_outputs():
|
||||
fs = '{} Segwit address requested on the command line, but Segwit is not active on this chain'
|
||||
rdie(2,fs.format(g.proj_name))
|
||||
|
||||
def get_inputs_from_user(tw,tx,caller):
|
||||
|
||||
while True:
|
||||
m = 'Enter a range or space-separated list of outputs to spend: '
|
||||
sel_nums = select_unspent(tw.unspent,m)
|
||||
msg('Selected output%s: %s' % (suf(sel_nums,'s'),' '.join(str(i) for i in sel_nums)))
|
||||
|
||||
sel_unspent = tw.MMGenTwOutputList([tw.unspent[i-1] for i in sel_nums])
|
||||
|
||||
t_inputs = sum(s.amt for s in sel_unspent)
|
||||
if t_inputs < tx.send_amt:
|
||||
msg(wmsg['not_enough_btc'] % (tx.send_amt - t_inputs))
|
||||
continue
|
||||
|
||||
non_mmaddrs = [i for i in sel_unspent if i.twmmid.type == 'non-mmgen']
|
||||
if non_mmaddrs and caller != 'txdo':
|
||||
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_from_tw(sel_unspent) # makes tx.inputs
|
||||
|
||||
change_amt = tx.sum_inputs() - tx.send_amt - get_fee_from_estimate_or_user(tx)
|
||||
|
||||
if change_amt >= 0:
|
||||
p = 'Transaction produces %s BTC in change' % change_amt.hl()
|
||||
if opt.yes or keypress_confirm(p+'. OK?',default_yes=True):
|
||||
if opt.yes: msg(p)
|
||||
return change_amt
|
||||
else:
|
||||
msg(wmsg['not_enough_btc'] % abs(change_amt))
|
||||
|
||||
def txcreate(cmd_args,do_info=False,caller='txcreate'):
|
||||
|
||||
tx = MMGenTX()
|
||||
|
||||
|
|
@ -149,92 +224,29 @@ def txcreate(opt,cmd_args,do_info=False,caller='txcreate'):
|
|||
|
||||
c = bitcoin_connection()
|
||||
|
||||
if not do_info:
|
||||
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)
|
||||
|
||||
ad_f = AddrData()
|
||||
for a in addrfiles:
|
||||
check_infile(a)
|
||||
ad_f.add(AddrList(a))
|
||||
|
||||
ad_w = AddrData(source='tw')
|
||||
|
||||
for a in cmd_args:
|
||||
if ',' in a:
|
||||
a1,a2 = a.split(',',1)
|
||||
if is_mmgen_id(a1) or is_btc_addr(a1):
|
||||
btc_addr = mmaddr2baddr(c,a1,ad_w,ad_f) if is_mmgen_id(a1) else BTCAddr(a1)
|
||||
tx.add_output(btc_addr,BTCAmt(a2))
|
||||
else:
|
||||
die(2,"%s: unrecognized subargument in argument '%s'" % (a1,a))
|
||||
elif is_mmgen_id(a) or is_btc_addr(a):
|
||||
if tx.get_chg_output_idx() != None:
|
||||
die(2,'ERROR: More than one change address listed on command line')
|
||||
btc_addr = mmaddr2baddr(c,a,ad_w,ad_f) if is_mmgen_id(a) else BTCAddr(a)
|
||||
tx.add_output(btc_addr,BTCAmt('0'),is_chg=True)
|
||||
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 tx.get_chg_output_idx() == None:
|
||||
die(2,('ERROR: No change output specified',wmsg['no_change_output'])[len(tx.outputs) == 1])
|
||||
|
||||
if not do_info: get_outputs_from_cmdline(cmd_args,tx)
|
||||
|
||||
tw = MMGenTrackingWallet(minconf=opt.minconf)
|
||||
tw.view_and_sort()
|
||||
tw.view_and_sort(tx)
|
||||
tw.display_total()
|
||||
|
||||
if do_info: sys.exit()
|
||||
if do_info: sys.exit(0)
|
||||
|
||||
tx.send_amt = tx.sum_outputs()
|
||||
|
||||
msg('Total amount to spend: %s' % ('Unknown','%s BTC'%tx.send_amt.hl())[bool(tx.send_amt)])
|
||||
|
||||
while True:
|
||||
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],
|
||||
' '.join(str(i) for i in sel_nums)
|
||||
))
|
||||
change_amt = get_inputs_from_user(tw,tx,caller)
|
||||
|
||||
sel_unspent = [tw.unspent[i-1] for i in sel_nums]
|
||||
|
||||
t_inputs = sum(s.amt for s in sel_unspent)
|
||||
if t_inputs < tx.send_amt:
|
||||
msg(wmsg['not_enough_btc'] % (tx.send_amt - t_inputs))
|
||||
continue
|
||||
|
||||
non_mmaddrs = [i for i in sel_unspent if i.mmid == None]
|
||||
if non_mmaddrs and caller != 'txdo':
|
||||
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_from_tw(sel_unspent) # makes tx.inputs
|
||||
|
||||
if opt.rbf: tx.signal_for_rbf() # only after we have inputs
|
||||
|
||||
change_amt = tx.sum_inputs() - tx.send_amt - get_fee_from_estimate_or_usr(tx,c)
|
||||
|
||||
if change_amt >= 0:
|
||||
p = 'Transaction produces %s BTC in change' % change_amt.hl()
|
||||
if opt.yes or keypress_confirm(p+'. OK?',default_yes=True):
|
||||
if opt.yes: msg(p)
|
||||
break
|
||||
else:
|
||||
msg(wmsg['not_enough_btc'] % abs(change_amt))
|
||||
if opt.rbf: tx.signal_for_rbf() # only after we have inputs
|
||||
|
||||
chg_idx = tx.get_chg_output_idx()
|
||||
if change_amt > 0:
|
||||
tx.update_output_amt(chg_idx,BTCAmt(change_amt))
|
||||
else:
|
||||
|
||||
if change_amt == 0:
|
||||
msg('Warning: Change address will be deleted as transaction produces no change')
|
||||
tx.del_output(chg_idx)
|
||||
else:
|
||||
tx.update_output_amt(chg_idx,BTCAmt(change_amt))
|
||||
|
||||
if not tx.send_amt:
|
||||
tx.send_amt = change_amt
|
||||
|
|
@ -244,11 +256,12 @@ def txcreate(opt,cmd_args,do_info=False,caller='txcreate'):
|
|||
if not opt.yes:
|
||||
tx.add_comment() # edits an existing comment
|
||||
tx.create_raw(c) # creates tx.hex, tx.txid
|
||||
tx.add_mmaddrs_to_outputs(ad_w,ad_f)
|
||||
|
||||
tx.add_timestamp()
|
||||
tx.add_blockcount(c)
|
||||
tx.chain = g.chain
|
||||
|
||||
assert tx.get_fee() <= g.max_tx_fee
|
||||
assert tx.sum_inputs() - tx.sum_outputs() <= g.max_tx_fee
|
||||
|
||||
qmsg('Transaction successfully created')
|
||||
|
||||
|
|
|
|||
|
|
@ -68,34 +68,37 @@ ERROR: a key file must be supplied for the following non-{pnm} address%s:\n %
|
|||
|
||||
saved_seeds = {}
|
||||
|
||||
def get_seed_for_seed_id(seed_id,infiles,saved_seeds):
|
||||
def get_seed_for_seed_id(sid,infiles,saved_seeds):
|
||||
|
||||
if seed_id in saved_seeds:
|
||||
return saved_seeds[seed_id]
|
||||
if sid in saved_seeds:
|
||||
return saved_seeds[sid]
|
||||
|
||||
while True:
|
||||
if infiles:
|
||||
ss = SeedSource(infiles.pop(0),ignore_in_fmt=True)
|
||||
elif opt.in_fmt:
|
||||
qmsg('Need seed data for Seed ID %s' % seed_id)
|
||||
qmsg('Need seed data for Seed ID %s' % sid)
|
||||
ss = SeedSource()
|
||||
msg('User input produced Seed ID %s' % ss.seed.sid)
|
||||
else:
|
||||
die(2,'ERROR: No seed source found for Seed ID: %s' % seed_id)
|
||||
die(2,'ERROR: No seed source found for Seed ID: %s' % sid)
|
||||
|
||||
saved_seeds[ss.seed.sid] = ss.seed
|
||||
if ss.seed.sid == seed_id: return ss.seed
|
||||
if ss.seed.sid == sid: 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 = []
|
||||
sids = set(i.sid for i in mmgen_addrs)
|
||||
vmsg('Need seed%s: %s' % (suf(sids,'s'),' '.join(sids)))
|
||||
d = AddrListList()
|
||||
from mmgen.addr import KeyAddrList
|
||||
for seed_id in seed_ids:
|
||||
for sid in sids:
|
||||
# Returns only if seed is found
|
||||
seed = get_seed_for_seed_id(seed_id,infiles,saved_seeds)
|
||||
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()
|
||||
seed = get_seed_for_seed_id(sid,infiles,saved_seeds)
|
||||
for t in MMGenAddrType.mmtypes:
|
||||
idx_list = [i.idx for i in mmgen_addrs if i.sid == sid and i.mmtype == t]
|
||||
if idx_list:
|
||||
addr_idxs = AddrIdxList(idx_list=idx_list)
|
||||
d += KeyAddrList(seed=seed,addr_idxs=addr_idxs,do_chksum=False,mmtype=MMGenAddrType(t)).flat_list()
|
||||
return d
|
||||
|
||||
def add_keys(tx,src,infiles=None,saved_seeds=None,keyaddr_list=None):
|
||||
|
|
@ -113,54 +116,57 @@ def add_keys(tx,src,infiles=None,saved_seeds=None,keyaddr_list=None):
|
|||
if f.addr == e.addr:
|
||||
e.have_wif = True
|
||||
if src == 'inputs':
|
||||
new_keys.append(f.wif)
|
||||
new_keys.append((f.addr,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))
|
||||
vmsg('Added %s wif key%s from %s' % (len(new_keys),suf(new_keys,'s'),desc))
|
||||
return new_keys
|
||||
|
||||
def get_tx_files(opt,args): # strips found args
|
||||
def is_tx(i): return get_extension(i) == MMGenTX.raw_ext
|
||||
ret = [args.pop(i) for i in range(len(args)-1,-1,-1) if is_tx(args[i])]
|
||||
if not ret:
|
||||
die(1,'You must specify a raw transaction file!')
|
||||
return list(reversed(ret))
|
||||
def _pop_and_return(args,cmplist): # strips found args
|
||||
return list(reversed([args.pop(args.index(a)) for a in reversed(args) if get_extension(a) in cmplist]))
|
||||
|
||||
def get_seed_files(opt,args): # strips found args
|
||||
def is_seed(i): return get_extension(i) in SeedSource.get_extensions()
|
||||
ret = [args.pop(i) for i in range(len(args)-1,-1,-1) if is_seed(args[i])]
|
||||
from mmgen.filename import find_file_in_dir
|
||||
wf = find_file_in_dir(Wallet,g.data_dir)
|
||||
if wf: ret.append(wf)
|
||||
if not (ret 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!')
|
||||
return list(reversed(ret))
|
||||
|
||||
def get_keyaddrlist(opt):
|
||||
ret = None
|
||||
if opt.mmgen_keys_from_file:
|
||||
ret = KeyAddrList(opt.mmgen_keys_from_file)
|
||||
def get_tx_files(opt,args):
|
||||
ret = _pop_and_return(args,[MMGenTX.raw_ext])
|
||||
if not ret: die(1,'You must specify a raw transaction file!')
|
||||
return ret
|
||||
|
||||
def get_seed_files(opt,args):
|
||||
# favor unencrypted seed sources first, as they don't require passwords
|
||||
u,e = SeedSourceUnenc,SeedSourceEnc
|
||||
ret = _pop_and_return(args,u.get_extensions())
|
||||
from mmgen.filename import find_file_in_dir
|
||||
wf = find_file_in_dir(Wallet,g.data_dir) # Make this the first encrypted ss in the list
|
||||
if wf: ret.append(wf)
|
||||
ret += _pop_and_return(args,e.get_extensions())
|
||||
if not (ret 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!')
|
||||
return ret
|
||||
|
||||
def get_keyaddrlist(opt):
|
||||
if opt.mmgen_keys_from_file:
|
||||
return KeyAddrList(opt.mmgen_keys_from_file)
|
||||
return None
|
||||
|
||||
def get_keylist(opt):
|
||||
ret = None
|
||||
if opt.keys_from_file:
|
||||
l = get_lines_from_file(opt.keys_from_file,'key-address data',trim_comments=True)
|
||||
ret = KeyAddrList(keylist=[m.split()[0] for m in l]) # accept bitcoind wallet dumps
|
||||
ret.generate_addrs()
|
||||
return ret
|
||||
ret.generate_addrs_from_keylist()
|
||||
return ret
|
||||
return None
|
||||
|
||||
def txsign(opt,c,tx,seed_files,kl,kal,tx_num_str=''):
|
||||
# Start
|
||||
keys = []
|
||||
# tx.pmsg()
|
||||
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()
|
||||
keys += tmp.get_addr_wif_pairs()
|
||||
|
||||
if opt.mmgen_keys_from_file:
|
||||
keys += add_keys(tx,'inputs',keyaddr_list=kal)
|
||||
|
|
@ -174,10 +180,9 @@ def txsign(opt,c,tx,seed_files,kl,kal,tx_num_str=''):
|
|||
|
||||
extra_sids = set(saved_seeds) - tx.get_input_sids() - tx.get_output_sids()
|
||||
if extra_sids:
|
||||
msg('Unused Seed ID%s: %s' %
|
||||
(suf(extra_sids,'k'),' '.join(extra_sids)))
|
||||
msg('Unused Seed ID{}: {}'.format(suf(extra_sids,'s'),' '.join(extra_sids)))
|
||||
|
||||
if tx.sign(c,tx_num_str,keys):
|
||||
if tx.sign(c,tx_num_str,dict(keys)):
|
||||
return tx
|
||||
else:
|
||||
die(3,'failed\nSome keys were missing. Transaction %scould not be signed.' % tx_num_str)
|
||||
die(3,'failed\nSome keys were missing. Transaction {}could not be signed.'.format(tx_num_str))
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ def msgred(s): msg(red(s))
|
|||
def mmsg(*args):
|
||||
for d in args: Msg(repr(d))
|
||||
def mdie(*args):
|
||||
mmsg(*args); sys.exit()
|
||||
mmsg(*args); sys.exit(0)
|
||||
|
||||
def die_wait(delay,ev=0,s=''):
|
||||
assert type(delay) == int
|
||||
|
|
@ -57,11 +57,18 @@ def Die(ev=0,s=''):
|
|||
if s: Msg(s)
|
||||
sys.exit(ev)
|
||||
|
||||
def pp_format(d):
|
||||
def rdie(ev=0,s=''): die(ev,red(s))
|
||||
def ydie(ev=0,s=''): die(ev,yellow(s))
|
||||
|
||||
def pformat(d):
|
||||
import pprint
|
||||
return pprint.PrettyPrinter(indent=4).pformat(d)
|
||||
def pp_die(d): die(1,pp_format(d))
|
||||
def pp_msg(d): msg(pp_format(d))
|
||||
def pmsg(*args):
|
||||
if not args: return
|
||||
Msg(pformat(args if len(args) > 1 else args[0]))
|
||||
def pdie(*args):
|
||||
if not args: sys.exit(1)
|
||||
Die(1,(pformat(args if len(args) > 1 else args[0])))
|
||||
|
||||
def set_for_type(val,refval,desc,invert_bool=False,src=None):
|
||||
src_str = (''," in '{}'".format(src))[bool(src)]
|
||||
|
|
@ -132,17 +139,16 @@ def dmsg(s):
|
|||
if opt.debug: msg(s)
|
||||
|
||||
def suf(arg,suf_type):
|
||||
suf_types = { 's': ('s',''), 'es': ('es','') }
|
||||
assert suf_type in suf_types
|
||||
t = type(arg)
|
||||
if t == int:
|
||||
n = arg
|
||||
elif t in (list,tuple,set,dict):
|
||||
elif any(issubclass(t,c) for c in (list,tuple,set,dict)):
|
||||
n = len(arg)
|
||||
else:
|
||||
msg('%s: invalid parameter' % arg)
|
||||
return ''
|
||||
|
||||
if suf_type in ('a','es'): return ('es','')[n == 1]
|
||||
if suf_type in ('k','s'): return ('s','')[n == 1]
|
||||
die(2,'%s: invalid parameter for suf()' % arg)
|
||||
return suf_types[suf_type][n==1]
|
||||
|
||||
def get_extension(f):
|
||||
a,b = os.path.splitext(f)
|
||||
|
|
@ -159,9 +165,12 @@ def make_chksum_N(s,nchars,sep=False):
|
|||
return sep.join([s[i*4:i*4+4] for i in range(nchars/4)])
|
||||
|
||||
def make_chksum_8(s,sep=False):
|
||||
s = sha256(sha256(s).digest()).hexdigest()[:8].upper()
|
||||
from mmgen.obj import HexStr
|
||||
s = HexStr(sha256(sha256(s).digest()).hexdigest()[:8].upper(),case='upper')
|
||||
return '{} {}'.format(s[:4],s[4:]) if sep else s
|
||||
def make_chksum_6(s): return sha256(s).hexdigest()[:6]
|
||||
def make_chksum_6(s):
|
||||
from mmgen.obj import HexStr
|
||||
return HexStr(sha256(s).hexdigest()[:6])
|
||||
def is_chksum_6(s): return len(s) == 6 and is_hex_str_lc(s)
|
||||
|
||||
def make_iv_chksum(s): return sha256(s).hexdigest()[:8].upper()
|
||||
|
|
@ -700,12 +709,14 @@ def do_pager(text):
|
|||
|
||||
pagers,shell = ['less','more'],False
|
||||
# --- Non-MSYS Windows code deleted ---
|
||||
# raw, chop, scroll right 1 char, disable buggy line chopping for Windows
|
||||
os.environ['LESS'] = (('-RS -#1'),('-cR -#1'))[g.platform=='win']
|
||||
# raw, chop, horiz scroll 8 chars, disable buggy line chopping in MSYS
|
||||
os.environ['LESS'] = (('--shift 8 -RS'),('-cR -#1'))[g.platform=='win']
|
||||
|
||||
if 'PAGER' in os.environ and os.environ['PAGER'] != pagers[0]:
|
||||
pagers = [os.environ['PAGER']] + pagers
|
||||
|
||||
text = text.encode('utf8')
|
||||
|
||||
for pager in pagers:
|
||||
end = ('\n(end of text)\n','')[pager=='less']
|
||||
try:
|
||||
|
|
@ -771,5 +782,19 @@ def bitcoin_connection():
|
|||
g.rpc_password or cfg['rpcpassword'],
|
||||
auth_cookie=get_bitcoind_auth_cookie())
|
||||
# do an RPC call so we exit immediately if we can't connect
|
||||
c.client_version = int(c.getinfo()['version'])
|
||||
if not g.bitcoind_version:
|
||||
g.bitcoind_version = int(c.getnetworkinfo()['version'])
|
||||
g.chain = c.getblockchaininfo()['chain']
|
||||
if g.chain != 'regtest': g.chain += 'net'
|
||||
assert g.chain in g.chains
|
||||
err = None
|
||||
if g.regtest and g.chain != 'regtest':
|
||||
err = '--regtest option'
|
||||
elif g.testnet and g.chain == 'mainnet':
|
||||
err = '--testnet option'
|
||||
# we won't actually get here, as connect will fail first
|
||||
elif (not g.testnet) and g.chain != 'mainnet':
|
||||
err = 'mainnet'
|
||||
if err:
|
||||
die(1,'{} selected but chain is {}'.format(err,g.chain))
|
||||
return c
|
||||
|
|
|
|||
20
scripts/traceback.py
Executable file
20
scripts/traceback.py
Executable file
|
|
@ -0,0 +1,20 @@
|
|||
#!/usr/bin/env python
|
||||
import sys,traceback,os
|
||||
sys.path.insert(0,'.')
|
||||
|
||||
if 'TMUX' in os.environ: del os.environ['TMUX']
|
||||
|
||||
f = open('my.err','w')
|
||||
|
||||
try:
|
||||
sys.argv.pop(0)
|
||||
execfile(sys.argv[0])
|
||||
except SystemExit:
|
||||
sys.exit(int(str(sys.exc_info()[1])))
|
||||
except:
|
||||
l = traceback.format_exception(*sys.exc_info())
|
||||
exc = l.pop()
|
||||
def red(s): return '{e}[31;1m{}{e}[0m'.format(s,e='\033')
|
||||
def yellow(s): return '{e}[33;1m{}{e}[0m'.format(s,e='\033')
|
||||
sys.stdout.write('{}{} {}'.format(yellow(''.join(l)),red(exc),str(e[1])))
|
||||
traceback.print_exc(file=f)
|
||||
|
|
@ -29,7 +29,7 @@ from binascii import hexlify
|
|||
|
||||
# Import these _after_ local path's been added to sys.path
|
||||
from mmgen.common import *
|
||||
from mmgen.bitcoin import hex2wif,privnum2addr
|
||||
from mmgen.bitcoin import hex2wif
|
||||
|
||||
rounds = 100
|
||||
opts_data = {
|
||||
|
|
@ -39,6 +39,7 @@ opts_data = {
|
|||
-h, --help Print this help message
|
||||
--, --longhelp Print help message for long options (common options)
|
||||
-q, --quiet Produce quieter output
|
||||
-s, --segwit Generate Segwit (P2SH-P2WPKH) addresses
|
||||
-v, --verbose Produce more verbose output
|
||||
""",
|
||||
'notes': """
|
||||
|
|
@ -48,15 +49,14 @@ opts_data = {
|
|||
Compare: {prog} a <dump file> (compare output of a key generator against wallet dump)
|
||||
where a and b are one of:
|
||||
'1' - native Python ecdsa library (very slow)
|
||||
'2' - 'keyconv' utility from the 'vanitygen' package (old default)
|
||||
'3' - bitcoincore.org's secp256k1 library (default from v0.8.6)
|
||||
'2' - bitcoincore.org's secp256k1 library (default from v0.8.6)
|
||||
|
||||
EXAMPLES:
|
||||
{prog} 2:3 1000
|
||||
(compare output of 'keyconv' with secp256k1 library, 1000 rounds)
|
||||
{prog} 3 1000
|
||||
{prog} 1:2 100
|
||||
(compare output of native Python ECDSA with secp256k1 library, 100 rounds)
|
||||
{prog} 2 1000
|
||||
(test speed of secp256k1 library address generation, 1000 rounds)
|
||||
{prog} 3 my.dump
|
||||
{prog} 2 my.dump
|
||||
(compare addrs generated with secp256k1 library to bitcoind wallet dump)
|
||||
""".format(prog='gentest.py',pnm=g.proj_name,snum=rounds)
|
||||
}
|
||||
|
|
@ -105,7 +105,7 @@ else:
|
|||
die(1,"%s: invalid generator IDs" % cmd_args[0])
|
||||
|
||||
def match_error(sec,wif,a_addr,b_addr,a,b):
|
||||
m = ['','py-ecdsa','keyconv','secp256k1','dump']
|
||||
m = ['','py-ecdsa','secp256k1','dump']
|
||||
qmsg_r(red('\nERROR: Addresses do not match!'))
|
||||
die(3,"""
|
||||
sec key : {}
|
||||
|
|
@ -114,13 +114,16 @@ def match_error(sec,wif,a_addr,b_addr,a,b):
|
|||
{b:10}: {}
|
||||
""".format(sec,wif,a_addr,b_addr,pnm=g.proj_name,a=m[a],b=m[b]).rstrip())
|
||||
|
||||
# Begin execution
|
||||
mmtype = ('L','S')[bool(opt.segwit)]
|
||||
compressed = True
|
||||
|
||||
if a and b:
|
||||
m = "Comparing address generators '{}' and '{}'"
|
||||
qmsg(green(m.format(g.key_generators[a-1],g.key_generators[b-1])))
|
||||
from mmgen.addr import get_privhex2addr_f
|
||||
gen_a = get_privhex2addr_f(generator=a)
|
||||
gen_b = get_privhex2addr_f(generator=b)
|
||||
compressed = False
|
||||
last_t = time.time()
|
||||
|
||||
for i in range(rounds):
|
||||
|
|
@ -129,12 +132,12 @@ if a and b:
|
|||
last_t = time.time()
|
||||
sec = hexlify(os.urandom(32))
|
||||
wif = hex2wif(sec,compressed=compressed)
|
||||
a_addr = gen_a(sec,compressed)
|
||||
b_addr = gen_b(sec,compressed)
|
||||
a_addr = gen_a(sec,compressed,mmtype=mmtype)
|
||||
b_addr = gen_b(sec,compressed,mmtype=mmtype)
|
||||
vmsg('\nkey: %s\naddr: %s\n' % (wif,a_addr))
|
||||
if a_addr != b_addr:
|
||||
match_error(sec,wif,a_addr,b_addr,a,b)
|
||||
if a != 2 and b != 2:
|
||||
if not opt.segwit:
|
||||
compressed = not compressed
|
||||
qmsg_r('\rRound %s/%s ' % (i+1,rounds))
|
||||
|
||||
|
|
@ -143,12 +146,11 @@ elif a and not fh:
|
|||
m = "Testing speed of address generator '{}'"
|
||||
qmsg(green(m.format(g.key_generators[a-1])))
|
||||
from mmgen.addr import get_privhex2addr_f
|
||||
gen_a = get_privhex2addr_f(generator=a)
|
||||
gen = get_privhex2addr_f(generator=a)
|
||||
from struct import pack,unpack
|
||||
seed = os.urandom(28)
|
||||
print 'Incrementing key with each round'
|
||||
print 'Starting key:', hexlify(seed+pack('I',0))
|
||||
compressed = False
|
||||
import time
|
||||
start = last_t = time.time()
|
||||
|
||||
|
|
@ -158,9 +160,9 @@ elif a and not fh:
|
|||
last_t = time.time()
|
||||
sec = hexlify(seed+pack('I',i))
|
||||
wif = hex2wif(sec,compressed=compressed)
|
||||
a_addr = gen_a(sec,compressed)
|
||||
a_addr = gen(sec,compressed,mmtype=mmtype)
|
||||
vmsg('\nkey: %s\naddr: %s\n' % (wif,a_addr))
|
||||
if a != 2:
|
||||
if not opt.segwit:
|
||||
compressed = not compressed
|
||||
qmsg_r('\rRound %s/%s ' % (i+1,rounds))
|
||||
|
||||
|
|
@ -179,7 +181,7 @@ elif a and dump:
|
|||
if sec == False:
|
||||
die(2,'\nInvalid {}net WIF address in dump file: {}'.format(('main','test')[g.testnet],wif))
|
||||
compressed = wif[0] != ('5','9')[g.testnet]
|
||||
b_addr = gen_a(sec,compressed)
|
||||
b_addr = gen_a(sec,compressed,'L')
|
||||
if a_addr != b_addr:
|
||||
match_error(sec,wif,a_addr,b_addr,1 if compressed and a==2 else a,4)
|
||||
qmsg(green(('\n','')[bool(opt.verbose)] + 'OK'))
|
||||
|
|
|
|||
19
test/ref/98831F3A-S[1,31-33,500-501,1010-1011].addrs
Normal file
19
test/ref/98831F3A-S[1,31-33,500-501,1010-1011].addrs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# MMGen address file
|
||||
#
|
||||
# This file is editable.
|
||||
# Everything following a hash symbol '#' is a comment and ignored by MMGen.
|
||||
# A text label of 32 characters or less may be added to the right of each
|
||||
# address, and it will be appended to the bitcoind wallet label upon import.
|
||||
# The label may contain any printable ASCII symbol.
|
||||
# Address data checksum for 98831F3A-S[1,31-33,500-501,1010-1011]: 06C1 9C87 F25C 4EE6
|
||||
# Record this value to a secure location.
|
||||
98831F3A SEGWIT {
|
||||
1 36TvVzU5mxSjJ3D9qKAmYzCV7iUqtTDezF
|
||||
31 33BhCyZJeXt2EUitVQUCKSN8hutAL3jnDh
|
||||
32 37GSqHBi7yKUYN1y5WGwcVeKgxAkhNTJBs
|
||||
33 3CWwH3GZrWh2BfbVjaMChHnuamSc4uxLxK
|
||||
500 35QxzdbJTErNtxYaQJQvfhv3qiG876bhHA
|
||||
501 3P1r25Ch3rp7CUTw9uoBxoZsDQ9smGQ8iZ
|
||||
1010 37h2StmReijH9j3kd1AVMU1vfXe5HwMowX
|
||||
1011 37UkuGYko84PeTE46qXLDWKKvGhMM82tpu
|
||||
}
|
||||
19
test/ref/98831F3A-S[1,31-33,500-501,1010-1011].testnet.addrs
Normal file
19
test/ref/98831F3A-S[1,31-33,500-501,1010-1011].testnet.addrs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# MMGen address file
|
||||
#
|
||||
# This file is editable.
|
||||
# Everything following a hash symbol '#' is a comment and ignored by MMGen.
|
||||
# A text label of 32 characters or less may be added to the right of each
|
||||
# address, and it will be appended to the bitcoind wallet label upon import.
|
||||
# The label may contain any printable ASCII symbol.
|
||||
# Address data checksum for 98831F3A-S[1,31-33,500-501,1010-1011]: 58D1 7B6C E9F9 9C14
|
||||
# Record this value to a secure location.
|
||||
98831F3A SEGWIT {
|
||||
1 2Mx28ZjQ7PQx5VpqhWSneAwBkL4h1dsq6cY
|
||||
31 2MtjuGiVLFzPNSGMSAY64wPMPvG6L8zYx4e
|
||||
32 2Mxpeu27jjRppk9eWkdtpESdauJNvQnxHw6
|
||||
33 2N459LnCbTyCNPTE3Qhy5KEnAo7emrkhMpq
|
||||
500 2MvyB4NXL4hMj6kB85S2oHeuK44UHtYTjtF
|
||||
501 2NEa45p8ifKKTQG6Uq3R4akZ8RkN3arccQY
|
||||
1010 2MyFEWdhTGBEdMWgJJ8nMyR1BssrF8oR48c
|
||||
1011 2My2xy1UnQaZjrErbmy9CqTJb8cuXDzyKiH
|
||||
}
|
||||
244
test/test.py
244
test/test.py
|
|
@ -20,25 +20,8 @@
|
|||
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))
|
||||
|
|
@ -138,6 +121,8 @@ opts_data = {
|
|||
-D, --direct-exec Bypass pexpect and execute a command directly (for
|
||||
debugging only)
|
||||
-e, --exact-output Show the exact output of the MMGen script(s) being run
|
||||
-g, --segwit Generate and use Segwit addresses
|
||||
-G, --segwit-random Generate and use a random mix of Segwit and Legacy addrs
|
||||
-l, --list-cmds List and describe the commands in the test suite
|
||||
-L, --log Log commands to file {lf}
|
||||
-n, --names Display command names instead of descriptions
|
||||
|
|
@ -167,6 +152,11 @@ cmd_args = opts.init(opts_data)
|
|||
|
||||
tn_desc = ('','.testnet')[g.testnet]
|
||||
|
||||
def randbool():
|
||||
return hexlify(os.urandom(1))[1] in '12345678'
|
||||
def get_segwit_val():
|
||||
return randbool() if opt.segwit_random else True if opt.segwit else False
|
||||
|
||||
cfgs = {
|
||||
'15': {
|
||||
'tmpdir': os.path.join('test','tmp15'),
|
||||
|
|
@ -181,6 +171,7 @@ cfgs = {
|
|||
'mmseed': 'export_seed_dfl_wallet',
|
||||
'del_dw_run': 'delete_dfl_wallet',
|
||||
},
|
||||
'segwit': get_segwit_val()
|
||||
},
|
||||
'16': {
|
||||
'tmpdir': os.path.join('test','tmp16'),
|
||||
|
|
@ -189,6 +180,7 @@ cfgs = {
|
|||
'dep_generators': {
|
||||
pwfile: 'passchg_dfl_wallet',
|
||||
},
|
||||
'segwit': get_segwit_val()
|
||||
},
|
||||
'1': {
|
||||
'tmpdir': os.path.join('test','tmp1'),
|
||||
|
|
@ -211,6 +203,7 @@ cfgs = {
|
|||
incog_id_fn: 'export_incog_hidden',
|
||||
'akeys.mmenc': 'keyaddrgen'
|
||||
},
|
||||
'segwit': get_segwit_val()
|
||||
},
|
||||
'2': {
|
||||
'tmpdir': os.path.join('test','tmp2'),
|
||||
|
|
@ -224,6 +217,7 @@ cfgs = {
|
|||
'sigtx': 'txsign2',
|
||||
'mmwords': 'export_mnemonic2',
|
||||
},
|
||||
'segwit': get_segwit_val()
|
||||
},
|
||||
'3': {
|
||||
'tmpdir': os.path.join('test','tmp3'),
|
||||
|
|
@ -235,6 +229,7 @@ cfgs = {
|
|||
'rawtx': 'txcreate3',
|
||||
'sigtx': 'txsign3'
|
||||
},
|
||||
'segwit': get_segwit_val()
|
||||
},
|
||||
'4': {
|
||||
'tmpdir': os.path.join('test','tmp4'),
|
||||
|
|
@ -251,6 +246,7 @@ cfgs = {
|
|||
},
|
||||
'bw_filename': 'brainwallet.mmbrain',
|
||||
'bw_params': '192,1',
|
||||
'segwit': get_segwit_val()
|
||||
},
|
||||
'14': {
|
||||
'kapasswd': 'Maxwell',
|
||||
|
|
@ -263,6 +259,7 @@ cfgs = {
|
|||
'addrs': 'addrgen14',
|
||||
'akeys.mmenc': 'keyaddrgen14',
|
||||
},
|
||||
'segwit': get_segwit_val()
|
||||
},
|
||||
'5': {
|
||||
'tmpdir': os.path.join('test','tmp5'),
|
||||
|
|
@ -272,14 +269,15 @@ cfgs = {
|
|||
'mmdat': 'passchg',
|
||||
pwfile: 'passchg',
|
||||
},
|
||||
'segwit': get_segwit_val()
|
||||
},
|
||||
'6': {
|
||||
'name': 'reference wallet check (128-bit)',
|
||||
'seed_len': 128,
|
||||
'seed_id': 'FE3C6545',
|
||||
'ref_bw_seed_id': '33F10310',
|
||||
'addrfile_chk': ('B230 7526 638F 38CB','B64D 7327 EF2A 60FE')[g.testnet],
|
||||
'keyaddrfile_chk': ('CF83 32FB 8A8B 08E2','FEBF 7878 97BB CC35')[g.testnet],
|
||||
'addrfile_chk': ('B230 7526 638F 38CB','B64D 7327 EF2A 60FE','9914 6D10 2307 F348','7DBF 441F E188 8B37'),
|
||||
'keyaddrfile_chk':('CF83 32FB 8A8B 08E2','FEBF 7878 97BB CC35','C13B F717 D4E8 CF59','4DB5 BAF0 45B7 6E81'),
|
||||
'passfile_chk': 'EB29 DC4F 924B 289F',
|
||||
'passfile32_chk': '37B6 C218 2ABC 7508',
|
||||
'wpasswd': 'reference password',
|
||||
|
|
@ -300,15 +298,15 @@ cfgs = {
|
|||
'addrs': 'refaddrgen1',
|
||||
'akeys.mmenc': 'refkeyaddrgen1'
|
||||
},
|
||||
|
||||
'segwit': get_segwit_val()
|
||||
},
|
||||
'7': {
|
||||
'name': 'reference wallet check (192-bit)',
|
||||
'seed_len': 192,
|
||||
'seed_id': '1378FC64',
|
||||
'ref_bw_seed_id': 'CE918388',
|
||||
'addrfile_chk': ('8C17 A5FA 0470 6E89','0A59 C8CD 9439 8B81')[g.testnet],
|
||||
'keyaddrfile_chk': ('9648 5132 B98E 3AD9','2F72 C83F 44C5 0FAC')[g.testnet],
|
||||
'addrfile_chk': ('8C17 A5FA 0470 6E89','0A59 C8CD 9439 8B81','91C4 0414 89E4 2089','3BA6 7494 8E2B 858D'),
|
||||
'keyaddrfile_chk': ('9648 5132 B98E 3AD9','2F72 C83F 44C5 0FAC','C98B DF08 A3D5 204B','25F2 AEB6 AAAC 8BBE'),
|
||||
'passfile_chk': 'ADEA 0083 094D 489A',
|
||||
'passfile32_chk': '2A28 C5C7 36EC 217A',
|
||||
'wpasswd': 'reference password',
|
||||
|
|
@ -329,23 +327,25 @@ cfgs = {
|
|||
'addrs': 'refaddrgen2',
|
||||
'akeys.mmenc': 'refkeyaddrgen2'
|
||||
},
|
||||
|
||||
'segwit': get_segwit_val()
|
||||
},
|
||||
'8': {
|
||||
'name': 'reference wallet check (256-bit)',
|
||||
'seed_len': 256,
|
||||
'seed_id': '98831F3A',
|
||||
'ref_bw_seed_id': 'B48CD7FC',
|
||||
'addrfile_chk': ('6FEF 6FB9 7B13 5D91','3C2C 8558 BB54 079E')[g.testnet],
|
||||
'keyaddrfile_chk': ('9F2D D781 1812 8BAD','7410 8F95 4B33 B4B2')[g.testnet],
|
||||
'addrfile_chk': ('6FEF 6FB9 7B13 5D91','3C2C 8558 BB54 079E','06C1 9C87 F25C 4EE6','58D1 7B6C E9F9 9C14'),
|
||||
'keyaddrfile_chk': ('9F2D D781 1812 8BAD','7410 8F95 4B33 B4B2','A447 12C2 DD14 5A9B','0690 460D A600 D315'),
|
||||
'passfile_chk': '2D6D 8FBA 422E 1315',
|
||||
'passfile32_chk': 'F6C1 CDFB 97D9 FCAE',
|
||||
'wpasswd': 'reference password',
|
||||
'ref_wallet': '98831F3A-{}[256,1].mmdat'.format(('27F2BF93','E2687906')[g.testnet]),
|
||||
'ref_addrfile': '98831F3A[1,31-33,500-501,1010-1011]{}.addrs'.format(tn_desc),
|
||||
'ref_segwitaddrfile':'98831F3A-S[1,31-33,500-501,1010-1011]{}.addrs'.format(tn_desc),
|
||||
'ref_keyaddrfile': '98831F3A[1,31-33,500-501,1010-1011]{}.akeys.mmenc'.format(tn_desc),
|
||||
'ref_passwdfile': '98831F3A-фубар@crypto.org-b58-20[1,4,9-11,1100].pws',
|
||||
'ref_addrfile_chksum': ('6FEF 6FB9 7B13 5D91','3C2C 8558 BB54 079E')[g.testnet],
|
||||
'ref_segwitaddrfile_chksum':('06C1 9C87 F25C 4EE6','58D1 7B6C E9F9 9C14')[g.testnet],
|
||||
'ref_keyaddrfile_chksum': ('9F2D D781 1812 8BAD','7410 8F95 4B33 B4B2')[g.testnet],
|
||||
'ref_passwdfile_chksum': 'A983 DAB9 5514 27FB',
|
||||
# 'ref_fake_unspent_data':'98831F3A_unspent.json',
|
||||
|
|
@ -367,6 +367,7 @@ cfgs = {
|
|||
'addrs': 'refaddrgen3',
|
||||
'akeys.mmenc': 'refkeyaddrgen3'
|
||||
},
|
||||
'segwit': get_segwit_val()
|
||||
},
|
||||
'9': {
|
||||
'tmpdir': os.path.join('test','tmp9'),
|
||||
|
|
@ -489,6 +490,7 @@ cmd_group['ref'] = (
|
|||
# misc. saved reference data
|
||||
cmd_group['ref_other'] = (
|
||||
('ref_addrfile_chk', 'saved reference address file'),
|
||||
('ref_segwitaddrfile_chk','saved reference address file (segwit)'),
|
||||
('ref_keyaddrfile_chk','saved reference key-address file'),
|
||||
('ref_passwdfile_chk', 'saved reference password file'),
|
||||
# Create the fake inputs:
|
||||
|
|
@ -609,7 +611,7 @@ del cmd_group
|
|||
add_spawn_args = ' '.join(['{} {}'.format(
|
||||
'--'+k.replace('_','-'),
|
||||
getattr(opt,k) if getattr(opt,k) != True else ''
|
||||
) for k in 'testnet','rpc_host' if getattr(opt,k)]).split()
|
||||
) for k in 'testnet','rpc_host','rpc_port','regtest' if getattr(opt,k)]).split()
|
||||
add_spawn_args += ['--data-dir',data_dir]
|
||||
|
||||
if opt.profile: opt.names = True
|
||||
|
|
@ -631,6 +633,8 @@ os.environ['MMGEN_NO_LICENSE'] = '1'
|
|||
os.environ['MMGEN_MIN_URANDCHARS'] = '3'
|
||||
os.environ['MMGEN_BOGUS_SEND'] = '1'
|
||||
|
||||
def get_segwit_arg(cfg): return ([],['--type','segwit'])[cfg['segwit']]
|
||||
|
||||
# Tell spawned programs they're running in the test suite
|
||||
os.environ['MMGEN_TEST_SUITE'] = '1'
|
||||
|
||||
|
|
@ -692,7 +696,7 @@ if opt.list_cmds:
|
|||
w = max([len(i) for i in utils])
|
||||
for cmd in sorted(utils):
|
||||
Msg(fs.format(cmd,utils[cmd],w=w))
|
||||
sys.exit()
|
||||
sys.exit(0)
|
||||
|
||||
import time,re
|
||||
if g.platform == 'linux':
|
||||
|
|
@ -718,7 +722,7 @@ else: # Windows
|
|||
m3 = '.\nControl values should be checked against the program output.\nContinue?'
|
||||
if not keypress_confirm(green(m1)+grnbg(m2)+green(m3),default_yes=True):
|
||||
errmsg('Exiting at user request')
|
||||
sys.exit()
|
||||
sys.exit(0)
|
||||
|
||||
def my_send(p,t,delay=send_delay,s=False):
|
||||
if delay: time.sleep(delay)
|
||||
|
|
@ -770,8 +774,10 @@ def get_file_with_ext(ext,mydir,delete=True,no_dot=False):
|
|||
if len(flist) > 1:
|
||||
if delete:
|
||||
if not opt.quiet:
|
||||
msg("Multiple *.%s files in '%s' - deleting" % (ext,mydir))
|
||||
for f in flist: os.unlink(f)
|
||||
msg("Multiple *.{} files in '{}' - deleting".format(ext,mydir))
|
||||
for f in flist:
|
||||
msg(f)
|
||||
os.unlink(f)
|
||||
return False
|
||||
else:
|
||||
return flist[0]
|
||||
|
|
@ -845,6 +851,7 @@ class MMGenExpect(object):
|
|||
else:
|
||||
if opt.traceback:
|
||||
cmd,args = tb_cmd,[cmd]+args
|
||||
cmd_str = tb_cmd + ' ' + cmd_str
|
||||
if use_popen_spawn:
|
||||
self.p = PopenSpawn(cmd_str)
|
||||
else:
|
||||
|
|
@ -854,7 +861,7 @@ class MMGenExpect(object):
|
|||
def ok(self,exit_val=0):
|
||||
ret = self.p.wait()
|
||||
if ret != exit_val:
|
||||
die(1,red('Program exited with value {}'.format(ret)))
|
||||
die(1,red('test.py: spawned program exited with value {}'.format(ret)))
|
||||
if opt.profile: return
|
||||
if opt.verbose or opt.exact_output:
|
||||
sys.stderr.write(green('OK\n'))
|
||||
|
|
@ -863,7 +870,7 @@ class MMGenExpect(object):
|
|||
def cmp_or_die(self,s,t,skip_ok=False,exit_val=0):
|
||||
ret = self.p.wait()
|
||||
if ret != exit_val:
|
||||
die(1,red('Program exited with value {}'.format(ret)))
|
||||
die(1,red('test.py: spawned program exited with value {}'.format(ret)))
|
||||
if s == t:
|
||||
if not skip_ok: ok()
|
||||
else:
|
||||
|
|
@ -977,16 +984,17 @@ class MMGenExpect(object):
|
|||
from mmgen.obj import BTCAmt
|
||||
from mmgen.bitcoin import verify_addr
|
||||
|
||||
def create_fake_unspent_entry(address,sid=None,idx=None,lbl=None,non_mmgen=None):
|
||||
def create_fake_unspent_entry(btcaddr,al_id=None,idx=None,lbl=None,non_mmgen=False,segwit=False):
|
||||
if lbl: lbl = ' ' + lbl
|
||||
spk1,spk2 = (('76a914','88ac'),('a914','87'))[segwit and btcaddr.addr_fmt=='p2sh']
|
||||
return {
|
||||
'account': (non_mmgen or ('%s:%s%s' % (sid,idx,lbl))).decode('utf8'),
|
||||
'account': 'btc:{}'.format(btcaddr) if non_mmgen else (u'{}:{}{}'.format(al_id,idx,lbl.decode('utf8'))),
|
||||
'vout': int(getrandnum(4) % 8),
|
||||
'txid': hexlify(os.urandom(32)).decode('utf8'),
|
||||
'amount': BTCAmt('%s.%s' % (10+(getrandnum(4) % 40), getrandnum(4) % 100000000)),
|
||||
'address': address,
|
||||
'address': btcaddr,
|
||||
'spendable': False,
|
||||
'scriptPubKey': ('76a914'+verify_addr(address,return_hex=True)+'88ac'),
|
||||
'scriptPubKey': (spk1+verify_addr(btcaddr,return_hex=True)+spk2),
|
||||
'confirmations': getrandnum(4) % 50000
|
||||
}
|
||||
|
||||
|
|
@ -1013,35 +1021,82 @@ labels = [
|
|||
"Carl's capital",
|
||||
]
|
||||
label_iter = None
|
||||
def create_fake_unspent_data(adata,unspent_data_file,tx_data,non_mmgen_input=''):
|
||||
|
||||
def create_fake_unspent_data(adata,tx_data,non_mmgen_input=''):
|
||||
|
||||
out = []
|
||||
for s in tx_data:
|
||||
sid = tx_data[s]['sid']
|
||||
a = adata.addrlist(sid)
|
||||
for n,(idx,btcaddr) in enumerate(a.addrpairs(),1):
|
||||
for d in tx_data.values():
|
||||
al = adata.addrlist(d['al_id'])
|
||||
for n,(idx,btcaddr) in enumerate(al.addrpairs()):
|
||||
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))
|
||||
out.append(create_fake_unspent_entry(btcaddr,d['al_id'],idx,lbl,segwit=d['segwit']))
|
||||
if n == 0: # create a duplicate address. This means addrs_per_wallet += 1
|
||||
out.append(create_fake_unspent_entry(btcaddr,d['al_id'],idx,lbl,segwit=d['segwit']))
|
||||
|
||||
if non_mmgen_input:
|
||||
from mmgen.bitcoin import privnum2addr,hex2wif
|
||||
privnum = getrandnum(32)
|
||||
btcaddr = privnum2addr(privnum,compressed=True)
|
||||
from mmgen.bitcoin import privnum2addr,hex2wif
|
||||
from mmgen.obj import BTCAddr
|
||||
btcaddr = BTCAddr(privnum2addr(privnum,compressed=True))
|
||||
of = os.path.join(cfgs[non_mmgen_input]['tmpdir'],non_mmgen_fn)
|
||||
wif = hex2wif('{:064x}'.format(privnum),compressed=True)
|
||||
# Msg(yellow(wif + ' ' + btcaddr))
|
||||
write_data_to_file(of,wif+'\n','compressed bitcoin key',silent=True)
|
||||
out.append(create_fake_unspent_entry(btcaddr,non_mmgen=True,segwit=False))
|
||||
|
||||
out.append(create_fake_unspent_entry(btcaddr,non_mmgen='Non-MMGen address'))
|
||||
# msg('\n'.join([repr(o) for o in out])); sys.exit(0)
|
||||
return out
|
||||
|
||||
# msg('\n'.join([repr(o) for o in out])); sys.exit()
|
||||
write_data_to_file(unspent_data_file,repr(out),'Unspent outputs',silent=True)
|
||||
def write_fake_data_to_file(d):
|
||||
unspent_data_file = os.path.join(cfg['tmpdir'],'unspent.json')
|
||||
write_data_to_file(unspent_data_file,d,'Unspent outputs',silent=True)
|
||||
os.environ['MMGEN_BOGUS_WALLET_DATA'] = unspent_data_file
|
||||
bwd_msg = 'MMGEN_BOGUS_WALLET_DATA=%s' % unspent_data_file
|
||||
if opt.print_cmdline: msg(bwd_msg)
|
||||
if opt.log: log_fd.write(bwd_msg + ' ')
|
||||
if opt.verbose or opt.exact_output:
|
||||
sys.stderr.write("Fake transaction wallet data written to file '%s'\n" % unspent_data_file)
|
||||
|
||||
def create_tx_data(sources):
|
||||
from mmgen.addr import AddrList,AddrData,AddrIdxList
|
||||
tx_data,ad = {},AddrData()
|
||||
for s in sources:
|
||||
afile = get_file_with_ext('addrs',cfgs[s]['tmpdir'])
|
||||
al = AddrList(afile)
|
||||
ad.add(al)
|
||||
aix = AddrIdxList(fmt_str=cfgs[s]['addr_idx_list'])
|
||||
if len(aix) != addrs_per_wallet:
|
||||
errmsg(red('Address index list length != %s: %s' %
|
||||
(addrs_per_wallet,repr(aix))))
|
||||
sys.exit(0)
|
||||
tx_data[s] = {
|
||||
'addrfile': afile,
|
||||
'chk': al.chksum,
|
||||
'al_id': al.al_id,
|
||||
'addr_idxs': aix[-2:],
|
||||
'segwit': cfgs[s]['segwit']
|
||||
}
|
||||
return ad,tx_data
|
||||
|
||||
def make_txcreate_cmdline(tx_data):
|
||||
from mmgen.bitcoin import privnum2addr
|
||||
btcaddr = privnum2addr(getrandnum(32),compressed=True)
|
||||
|
||||
cmd_args = ['-d',cfg['tmpdir']]
|
||||
for num in tx_data:
|
||||
s = tx_data[num]
|
||||
cmd_args += [
|
||||
'{}:{},{}'.format(s['al_id'],s['addr_idxs'][0],cfgs[num]['amts'][0]),
|
||||
]
|
||||
# + one change address and one BTC address
|
||||
if num is tx_data.keys()[-1]:
|
||||
cmd_args += ['{}:{}'.format(s['al_id'],s['addr_idxs'][1])]
|
||||
cmd_args += ['{},{}'.format(btcaddr,cfgs[num]['amts'][1])]
|
||||
|
||||
return cmd_args + [tx_data[num]['addrfile'] for num in tx_data]
|
||||
|
||||
def add_comments_to_addr_file(addrfile,outfile):
|
||||
silence()
|
||||
|
|
@ -1073,7 +1128,7 @@ def do_between():
|
|||
if opt.verbose or opt.exact_output: sys.stderr.write('\n')
|
||||
else:
|
||||
errmsg('Exiting at user request')
|
||||
sys.exit()
|
||||
sys.exit(0)
|
||||
elif opt.verbose or opt.exact_output:
|
||||
sys.stderr.write('\n')
|
||||
|
||||
|
|
@ -1370,7 +1425,7 @@ class MMGenTestSuite(object):
|
|||
|
||||
def addrgen(self,name,wf,pf=None,check_ref=False,ftype='addr',id_str=None,extra_args=[]):
|
||||
ftype,chkfile = ((ftype,'{}file_chk'.format(ftype)),('pass','passfile32_chk'))[ftype=='pass32']
|
||||
add_args = extra_args + ([],['-q'] + ([],['-P',pf])[bool(pf)])[ia]
|
||||
add_args = extra_args + ([],['-q'] + ([],['-P',pf])[bool(pf)])[ia] + (get_segwit_arg(cfg),[])[ftype[:4]=='pass']
|
||||
dlist = [id_str] if id_str else []
|
||||
t = MMGenExpect(name,'mmgen-{}gen'.format(ftype), add_args +
|
||||
['-d',cfg['tmpdir']] + ([],[wf])[bool(wf)] + dlist + [cfg['{}_idx_list'.format(ftype)]])
|
||||
|
|
@ -1381,7 +1436,8 @@ class MMGenTestSuite(object):
|
|||
desc = ('address','password')[ftype=='pass']
|
||||
chk = t.expect_getend(r'Checksum for {} data .*?: '.format(desc),regex=True)
|
||||
if check_ref:
|
||||
refcheck('address data checksum',chk,cfg[chkfile])
|
||||
c = (cfg[chkfile][g.testnet + 2*cfg['segwit']],cfg[chkfile])[ftype=='pass']
|
||||
refcheck('address data checksum',chk,c)
|
||||
return
|
||||
t.written_to_file('Addresses',oo=True)
|
||||
t.ok()
|
||||
|
|
@ -1400,7 +1456,6 @@ class MMGenTestSuite(object):
|
|||
t = MMGenExpect(name,'mmgen-addrimport', add_args + [outfile])
|
||||
if ia: return
|
||||
t.expect_getend(r'Checksum for address data .*\[.*\]: ',regex=True)
|
||||
t.expect_getend('Validating addresses...OK. ')
|
||||
t.expect("Type uppercase 'YES' to confirm: ",'\n')
|
||||
vmsg('This is a simulation, so no addresses were actually imported into the tracking\nwallet')
|
||||
t.ok(exit_val=1)
|
||||
|
|
@ -1408,50 +1463,14 @@ class MMGenTestSuite(object):
|
|||
def txcreate_common(self,name,sources=['1'],non_mmgen_input='',do_label=False,txdo_args=[],add_args=[]):
|
||||
if opt.verbose or opt.exact_output:
|
||||
sys.stderr.write(green('Generating fake tracking wallet info\n'))
|
||||
|
||||
silence()
|
||||
from mmgen.addr import AddrList,AddrData,AddrIdxList
|
||||
tx_data,ad = {},AddrData()
|
||||
for s in sources:
|
||||
afile = get_file_with_ext('addrs',cfgs[s]['tmpdir'])
|
||||
ai = AddrList(afile)
|
||||
ad.add(ai)
|
||||
aix = AddrIdxList(fmt_str=cfgs[s]['addr_idx_list'])
|
||||
if len(aix) != addrs_per_wallet:
|
||||
errmsg(red('Address index list length != %s: %s' %
|
||||
(addrs_per_wallet,repr(aix))))
|
||||
sys.exit()
|
||||
tx_data[s] = {
|
||||
'addrfile': afile,
|
||||
'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(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
|
||||
btcaddr = privnum2addr(getrandnum(32),compressed=True)
|
||||
|
||||
cmd_args = ['-d',cfg['tmpdir']]
|
||||
for num in tx_data:
|
||||
s = tx_data[num]
|
||||
cmd_args += [
|
||||
'%s:%s,%s' % (s['sid'],s['addr_idxs'][0],cfgs[num]['amts'][0]),
|
||||
]
|
||||
# + one BTC address
|
||||
# + one change address and one BTC address
|
||||
if num is tx_data.keys()[-1]:
|
||||
cmd_args += ['%s:%s' % (s['sid'],s['addr_idxs'][1])]
|
||||
cmd_args += ['%s,%s' % (btcaddr,cfgs[num]['amts'][1])]
|
||||
|
||||
for num in tx_data: cmd_args += [tx_data[num]['addrfile']]
|
||||
|
||||
os.environ['MMGEN_BOGUS_WALLET_DATA'] = unspent_data_file
|
||||
ad,tx_data = create_tx_data(sources)
|
||||
dfake = create_fake_unspent_data(ad,tx_data,non_mmgen_input)
|
||||
write_fake_data_to_file(repr(dfake))
|
||||
cmd_args = make_txcreate_cmdline(tx_data)
|
||||
end_silence()
|
||||
|
||||
if opt.verbose or opt.exact_output: sys.stderr.write('\n')
|
||||
|
||||
if ia:
|
||||
|
|
@ -1459,9 +1478,6 @@ class MMGenTestSuite(object):
|
|||
m = '\nAnswer the interactive prompts as follows:\n' + \
|
||||
" 'y', 'y', 'q', '1-9'<ENTER>, ENTER, ENTER, ENTER, ENTER, 'y'"
|
||||
msg(grnbg(m))
|
||||
bwd_msg = 'MMGEN_BOGUS_WALLET_DATA=%s' % unspent_data_file
|
||||
if opt.print_cmdline: msg(bwd_msg)
|
||||
if opt.log: log_fd.write(bwd_msg + ' ')
|
||||
t = MMGenExpect(name,'mmgen-'+('txcreate','txdo')[bool(txdo_args)],['--rbf','-f',tx_fee] + add_args + cmd_args + txdo_args)
|
||||
if ia: return
|
||||
t.license()
|
||||
|
|
@ -1516,7 +1532,7 @@ class MMGenTestSuite(object):
|
|||
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.expect('Which output do you wish to deduct the fee from? ','1\n')
|
||||
t.expect('deduct the fee from (Hit ENTER for the change output): ','1\n')
|
||||
# Fee must be > tx_fee + network relay fee (currently 0.00001)
|
||||
t.expect('OK? (Y/n): ','\n')
|
||||
t.expect('Enter transaction fee: ','124s\n')
|
||||
|
|
@ -1567,13 +1583,12 @@ class MMGenTestSuite(object):
|
|||
if txdo_handle: return
|
||||
if save:
|
||||
self.txsign_end(t,has_label=has_label)
|
||||
exit_val = 0
|
||||
t.ok()
|
||||
else:
|
||||
cprompt = ('Add a comment to transaction','Edit transaction comment')[has_label]
|
||||
t.expect('%s? (y/N): ' % cprompt,'\n')
|
||||
t.expect('Save signed transaction? (Y/n): ','n')
|
||||
exit_val = 1
|
||||
t.ok(exit_val=exit_val)
|
||||
t.ok(exit_val=1)
|
||||
|
||||
def txsign_dfl_wallet(self,name,txfile,pf='',save=True,has_label=False):
|
||||
return self.txsign(name,txfile,wf=None,pf=pf,save=save,has_label=has_label)
|
||||
|
|
@ -1586,7 +1601,7 @@ class MMGenTestSuite(object):
|
|||
t.license()
|
||||
t.tx_view()
|
||||
t.expect('Add a comment to transaction? (y/N): ','\n')
|
||||
t.expect('broadcast this transaction to the network?')
|
||||
t.expect('Are you sure you want to broadcast this')
|
||||
m = 'YES, I REALLY WANT TO DO THIS'
|
||||
t.expect("'%s' to confirm: " % m,m+'\n')
|
||||
t.expect('BOGUS transaction NOT sent')
|
||||
|
|
@ -1655,8 +1670,8 @@ class MMGenTestSuite(object):
|
|||
|
||||
def addrgen_seed(self,name,wf,foo,desc='seed data',in_fmt='seed'):
|
||||
stdout = (False,True)[desc=='seed data'] #capture output to screen once
|
||||
add_arg = ([],['-S'])[bool(stdout)]
|
||||
t = MMGenExpect(name,'mmgen-addrgen', add_arg +
|
||||
add_args = ([],['-S'])[bool(stdout)] + get_segwit_arg(cfg)
|
||||
t = MMGenExpect(name,'mmgen-addrgen', add_args +
|
||||
['-i'+in_fmt,'-d',cfg['tmpdir'],wf,cfg['addr_idx_list']])
|
||||
t.license()
|
||||
t.expect_getend('Valid %s for Seed ID ' % desc)
|
||||
|
|
@ -1677,7 +1692,7 @@ class MMGenTestSuite(object):
|
|||
self.addrgen_seed(name,wf,foo,desc='mnemonic data',in_fmt='words')
|
||||
|
||||
def addrgen_incog(self,name,wf=[],foo='',in_fmt='i',desc='incognito data',args=[]):
|
||||
t = MMGenExpect(name,'mmgen-addrgen', args+['-i'+in_fmt,'-d',cfg['tmpdir']]+
|
||||
t = MMGenExpect(name,'mmgen-addrgen', args + get_segwit_arg(cfg) + ['-i'+in_fmt,'-d',cfg['tmpdir']]+
|
||||
([],[wf])[bool(wf)] + [cfg['addr_idx_list']])
|
||||
t.license()
|
||||
t.expect_getend('Incog Wallet ID: ')
|
||||
|
|
@ -1698,7 +1713,7 @@ class MMGenTestSuite(object):
|
|||
args=['-H','%s,%s'%(rf,hincog_offset),'-l',str(hincog_seedlen)])
|
||||
|
||||
def keyaddrgen(self,name,wf,pf=None,check_ref=False):
|
||||
args = ['-d',cfg['tmpdir'],usr_rand_arg,wf,cfg['addr_idx_list']]
|
||||
args = get_segwit_arg(cfg) + ['-d',cfg['tmpdir'],usr_rand_arg,wf,cfg['addr_idx_list']]
|
||||
if ia:
|
||||
m = "\nAnswer 'n' at the interactive prompt"
|
||||
msg(grnbg(m))
|
||||
|
|
@ -1709,7 +1724,7 @@ class MMGenTestSuite(object):
|
|||
t.passphrase('MMGen wallet',cfg['wpasswd'])
|
||||
chk = t.expect_getend(r'Checksum for key-address data .*?: ',regex=True)
|
||||
if check_ref:
|
||||
refcheck('key-address data checksum',chk,cfg['keyaddrfile_chk'])
|
||||
refcheck('key-address data checksum',chk,cfg['keyaddrfile_chk'][g.testnet + 2*cfg['segwit']])
|
||||
return
|
||||
t.expect('Encrypt key list? (y/N): ','y')
|
||||
t.usr_rand(usr_rand_chars)
|
||||
|
|
@ -2025,7 +2040,7 @@ class MMGenTestSuite(object):
|
|||
aa = ['-P',get_tmpfile_fn(cfg,pfn)]
|
||||
else:
|
||||
aa = []
|
||||
t = MMGenExpect(name,'mmgen-tool',aa+[ftype+'file_chksum',wf])
|
||||
t = MMGenExpect(name,'mmgen-tool',aa+[ftype.replace('segwit','')+'file_chksum',wf])
|
||||
if ia:
|
||||
k = 'ref_%saddrfile_chksum' % ('','key')[ftype == 'keyaddr']
|
||||
m = grnbg('Checksum should be:')
|
||||
|
|
@ -2046,6 +2061,9 @@ class MMGenTestSuite(object):
|
|||
def ref_passwdfile_chk(self,name):
|
||||
self.ref_addrfile_chk(name,ftype='passwd')
|
||||
|
||||
def ref_segwitaddrfile_chk(self,name):
|
||||
self.ref_addrfile_chk(name,ftype='segwitaddr')
|
||||
|
||||
# def txcreate8(self,name,addrfile):
|
||||
# self.txcreate_common(name,sources=['8'])
|
||||
|
||||
|
|
@ -2253,7 +2271,7 @@ try:
|
|||
for arg in cmd_args:
|
||||
if arg in utils:
|
||||
globals()[arg](cmd_args[cmd_args.index(arg)+1:])
|
||||
sys.exit()
|
||||
sys.exit(0)
|
||||
elif 'info_'+arg in cmd_data:
|
||||
dirs = cmd_data['info_'+arg][1]
|
||||
if dirs: clean(dirs)
|
||||
|
|
@ -2276,7 +2294,11 @@ try:
|
|||
if cmd is not cmd_data.keys()[-1]: do_between()
|
||||
except KeyboardInterrupt:
|
||||
die(1,'\nExiting at user request')
|
||||
raise
|
||||
except opt.traceback and Exception:
|
||||
with open('my.err') as f:
|
||||
t = f.readlines()
|
||||
if t: msg_r('\n'+yellow(''.join(t[:-1]))+red(t[-1]))
|
||||
die(1,blue('Test script exited with error'))
|
||||
except:
|
||||
sys.stderr = stderr_save
|
||||
raise
|
||||
|
|
|
|||
328
test/tooltest.py
328
test/tooltest.py
|
|
@ -20,7 +20,7 @@
|
|||
test/tooltest.py: Tests for the 'mmgen-tool' utility
|
||||
"""
|
||||
|
||||
import sys,os
|
||||
import sys,os,subprocess
|
||||
pn = os.path.dirname(sys.argv[0])
|
||||
os.chdir(os.path.join(pn,os.pardir))
|
||||
sys.path.__setitem__(0,os.path.abspath(os.curdir))
|
||||
|
|
@ -33,52 +33,60 @@ cmd_data = OrderedDict([
|
|||
('util', {
|
||||
'desc': 'base conversion, hashing and file utilities',
|
||||
'cmd_data': OrderedDict([
|
||||
('strtob58', ()),
|
||||
('b58tostr', ('strtob58','io')),
|
||||
('hextob58', ()),
|
||||
('b58tohex', ('hextob58','io')),
|
||||
('b58randenc', ()),
|
||||
('hextob32', ()),
|
||||
('b32tohex', ('hextob32','io')),
|
||||
('randhex', ()),
|
||||
('id8', ()),
|
||||
('id6', ()),
|
||||
('str2id6', ()),
|
||||
('sha256x2', ()),
|
||||
('hexreverse', ()),
|
||||
('hexlify', ()),
|
||||
('hexdump', ()),
|
||||
('unhexdump', ('hexdump','io')),
|
||||
('rand2file', ()),
|
||||
('Strtob58', ()),
|
||||
('B58tostr', ('Strtob58','io')),
|
||||
('Hextob58', ()),
|
||||
('B58tohex', ('Hextob58','io')),
|
||||
('B58randenc', ()),
|
||||
('Hextob32', ()),
|
||||
('B32tohex', ('Hextob32','io')),
|
||||
('Randhex', ()),
|
||||
('Id6', ()),
|
||||
('Id8', ()),
|
||||
('Str2id6', ()),
|
||||
('Hash160', ()),
|
||||
('Hash256', ()),
|
||||
('Hexreverse', ()),
|
||||
('Hexlify', ()),
|
||||
('Hexdump', ()),
|
||||
('Unhexdump', ('Hexdump','io')),
|
||||
('Rand2file', ()),
|
||||
])
|
||||
}
|
||||
),
|
||||
('bitcoin', {
|
||||
'desc': 'Bitcoin address/key commands',
|
||||
'cmd_data': OrderedDict([
|
||||
('randwif', ()),
|
||||
('randpair', ()),
|
||||
('wif2addr', ('randpair','o2')),
|
||||
('wif2hex', ('randpair','o2')),
|
||||
('privhex2addr', ('wif2hex','o2')), # wif from randpair o2
|
||||
('hex2wif', ('wif2hex','io2')),
|
||||
('addr2hexaddr', ('randpair','o2')),
|
||||
('hexaddr2addr', ('addr2hexaddr','io2')),
|
||||
# ('pubkey2addr', ['<public key in hex format> [str]']),
|
||||
# ('pubkey2hexaddr', ['<public key in hex format> [str]']),
|
||||
('Randwif', ()),
|
||||
('Randpair', ()), # create 3 pairs: uncomp,comp,segwit
|
||||
('Wif2addr', ('Randpair','o3')),
|
||||
('Wif2hex', ('Randpair','o3')),
|
||||
|
||||
('Privhex2pubhex', ('Wif2hex','o3')),
|
||||
('Pubhex2addr', ('Privhex2pubhex','o3')),
|
||||
('Pubhex2redeem_script', ('Privhex2pubhex','o3')),
|
||||
('Wif2redeem_script', ('Randpair','o3')),
|
||||
('Wif2segwit_pair', ('Randpair','o2')),
|
||||
|
||||
('Privhex2addr', ('Wif2hex','o3')), # compare with output of Randpair
|
||||
('Hex2wif', ('Wif2hex','io2')),
|
||||
('Addr2hexaddr', ('Randpair','o2')),
|
||||
('Hexaddr2addr', ('Addr2hexaddr','io2')),
|
||||
|
||||
('Pipetest', ('Randpair','o3')),
|
||||
])
|
||||
}
|
||||
),
|
||||
('mnemonic', {
|
||||
'desc': 'mnemonic commands',
|
||||
'cmd_data': OrderedDict([
|
||||
('hex2mn', ()),
|
||||
('mn2hex', ('hex2mn','io3')),
|
||||
('mn_rand128', ()),
|
||||
('mn_rand192', ()),
|
||||
('mn_rand256', ()),
|
||||
('mn_stats', ()),
|
||||
('mn_printlist', ()),
|
||||
('Hex2mn', ()),
|
||||
('Mn2hex', ('Hex2mn','io3')),
|
||||
('Mn_rand128', ()),
|
||||
('Mn_rand192', ()),
|
||||
('Mn_rand256', ()),
|
||||
('Mn_stats', ()),
|
||||
('Mn_printlist', ()),
|
||||
])
|
||||
}
|
||||
),
|
||||
|
|
@ -86,11 +94,11 @@ cmd_data = OrderedDict([
|
|||
'desc': 'Bitcoind RPC commands',
|
||||
'cmd_data': OrderedDict([
|
||||
# ('keyaddrfile_chksum', ()), # interactive
|
||||
('addrfile_chksum', ()),
|
||||
('getbalance', ()),
|
||||
('listaddresses', ()),
|
||||
('twview', ()),
|
||||
('txview', ()),
|
||||
('Addrfile_chksum', ()),
|
||||
('Getbalance', ()),
|
||||
('Listaddresses', ()),
|
||||
('Twview', ()),
|
||||
('Txview', ()),
|
||||
])
|
||||
}
|
||||
),
|
||||
|
|
@ -113,7 +121,8 @@ opts_data = {
|
|||
'options': """
|
||||
-h, --help Print this help message
|
||||
--, --longhelp Print help message for long options (common options)
|
||||
-l, --list-cmds List and describe the tests and commands in the test suite
|
||||
-l, --list-cmds List and describe the tests and commands in this test suite
|
||||
-L, --list-names List the names of all tested 'mmgen-tool' commands
|
||||
-s, --system Test scripts and modules installed on system rather than
|
||||
those in the repo root
|
||||
-v, --verbose Produce more verbose output
|
||||
|
|
@ -127,10 +136,11 @@ If no command is given, the whole suite of tests is run.
|
|||
sys.argv = [sys.argv[0]] + ['--skip-cfg-file'] + sys.argv[1:]
|
||||
|
||||
cmd_args = opts.init(opts_data,add_opts=['exact_output','profile'])
|
||||
spawn_cmd = ['python',os.path.join(os.curdir,'mmgen-tool') if not opt.system else 'mmgen-tool']
|
||||
add_spawn_args = ' '.join(['{} {}'.format(
|
||||
'--'+k.replace('_','-'),
|
||||
getattr(opt,k) if getattr(opt,k) != True else ''
|
||||
) for k in 'testnet','rpc_host' if getattr(opt,k)]).split()
|
||||
) for k in 'testnet','rpc_host','regtest' if getattr(opt,k)]).split()
|
||||
add_spawn_args += [ '--data-dir', cfg['tmpdir']] # ignore ~/.mmgen
|
||||
|
||||
if opt.system: sys.path.pop(0)
|
||||
|
|
@ -143,12 +153,29 @@ if opt.list_cmds:
|
|||
Msg(fs.format(cmd,cmd_data[cmd]['desc'],w=w))
|
||||
Msg('\nAvailable utilities:')
|
||||
Msg(fs.format('clean','Clean the tmp directory',w=w))
|
||||
sys.exit()
|
||||
sys.exit(0)
|
||||
if opt.list_names:
|
||||
acc = []
|
||||
for v in cmd_data.values():
|
||||
acc += v['cmd_data'].keys()
|
||||
tc = sorted(c.lower() for c in acc)
|
||||
msg('{}\n{}'.format(green('Tested commands:'),'\n'.join(tc)))
|
||||
import mmgen.tool
|
||||
tested_in_tool = ('Encrypt','Decrypt','Find_incog_data','Keyaddrfile_chksum','Passwdfile_chksum')
|
||||
ignore = ('Help','Usage')
|
||||
uc = sorted(c.lower() for c in set(mmgen.tool.cmd_data.keys()) - set(acc) - set(ignore) - set(tested_in_tool))
|
||||
msg('\n{}\n{}'.format(yellow('Untested commands:'),'\n'.join(uc)))
|
||||
die()
|
||||
|
||||
import binascii
|
||||
from mmgen.test import *
|
||||
from mmgen.tx import is_wif,is_btc_addr
|
||||
|
||||
msg_w = 35
|
||||
def test_msg(m):
|
||||
m2 = 'Testing {}'.format(m)
|
||||
msg_r(green(m2+'\n') if opt.verbose else '{:{w}}'.format(m2,w=msg_w+8))
|
||||
|
||||
class MMGenToolTestSuite(object):
|
||||
|
||||
def __init__(self):
|
||||
|
|
@ -179,35 +206,28 @@ class MMGenToolTestSuite(object):
|
|||
for cmd in cdata: self.do_cmd(cmd,cdata[cmd])
|
||||
|
||||
def do_cmd(self,cmd,cdata):
|
||||
|
||||
fns = self.gen_deps_for_cmd(cmd,cdata)
|
||||
|
||||
file_list = [os.path.join(cfg['tmpdir'],fn) for fn in fns]
|
||||
|
||||
self.__class__.__dict__[cmd](*([self,cmd] + file_list))
|
||||
|
||||
|
||||
def run_cmd(self,name,tool_args,kwargs='',extra_msg='',silent=False,strip=True):
|
||||
mmgen_tool = 'mmgen-tool'
|
||||
if not opt.system:
|
||||
mmgen_tool = os.path.join(os.curdir,mmgen_tool)
|
||||
|
||||
sys_cmd = ['python',mmgen_tool] + add_spawn_args + ['-r0','-d',cfg['tmpdir'],name] + tool_args + kwargs.split()
|
||||
if extra_msg: extra_msg = '(%s)' % extra_msg
|
||||
full_name = ' '.join([name]+kwargs.split()+extra_msg.split())
|
||||
sys_cmd = (
|
||||
spawn_cmd +
|
||||
add_spawn_args +
|
||||
['-r0','-d',cfg['tmpdir'],name.lower()] +
|
||||
tool_args +
|
||||
kwargs.split()
|
||||
)
|
||||
if extra_msg: extra_msg = '({})'.format(extra_msg)
|
||||
full_name = ' '.join([name.lower()]+kwargs.split()+extra_msg.split())
|
||||
if not silent:
|
||||
if opt.verbose:
|
||||
sys.stderr.write(green('Testing %s\nExecuting ' % full_name))
|
||||
sys.stderr.write('%s\n' % cyan(repr(sys_cmd)))
|
||||
sys.stderr.write(green('Testing {}\nExecuting '.format(full_name)))
|
||||
sys.stderr.write(cyan(' '.join(sys_cmd)+'\n'))
|
||||
else:
|
||||
msg_r('Testing %-31s%s' % (full_name+':',''))
|
||||
msg_r('Testing {:{w}}'.format(full_name+':',w=msg_w))
|
||||
|
||||
import subprocess
|
||||
p = subprocess.Popen(
|
||||
sys_cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
)
|
||||
p = subprocess.Popen(sys_cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
|
||||
a,b = p.communicate()
|
||||
retcode = p.wait()
|
||||
if retcode != 0:
|
||||
|
|
@ -234,18 +254,19 @@ class MMGenToolTestSuite(object):
|
|||
vmsg('Out: ' + repr(ret))
|
||||
return ret
|
||||
|
||||
def run_cmd_out(self,name,carg=None,Return=False,kwargs='',fn_idx='',extra_msg='',literal=False,chkdata=''):
|
||||
def run_cmd_out(self,name,carg=None,Return=False,kwargs='',fn_idx='',extra_msg='',literal=False,chkdata='',hush=False):
|
||||
if carg: write_to_tmpfile(cfg,'%s%s.in' % (name,fn_idx),carg+'\n')
|
||||
ret = self.run_cmd(name,([],[carg])[bool(carg)],kwargs=kwargs,extra_msg=extra_msg)
|
||||
if carg: vmsg('In: ' + repr(carg))
|
||||
vmsg('Out: ' + (repr(ret),ret)[literal])
|
||||
vmsg('Out: ' + (repr(ret),ret.decode('utf8'))[literal])
|
||||
if ret or ret == '':
|
||||
write_to_tmpfile(cfg,'%s%s.out' % (name,fn_idx),ret+'\n')
|
||||
if chkdata:
|
||||
cmp_or_die(ret,chkdata)
|
||||
return
|
||||
if Return: return ret
|
||||
else: ok()
|
||||
else:
|
||||
if not hush: ok()
|
||||
else:
|
||||
die(3,red("Error for command '%s'" % name))
|
||||
|
||||
|
|
@ -259,115 +280,141 @@ class MMGenToolTestSuite(object):
|
|||
ok()
|
||||
vmsg('Returned: %s' % ret)
|
||||
|
||||
def str2id6(self,name):
|
||||
# Util
|
||||
def Strtob58(self,name): self.run_cmd_out(name,getrandstr(16))
|
||||
def B58tostr(self,name,f1,f2): self.run_cmd_chk(name,f1,f2)
|
||||
def Hextob58(self,name): self.run_cmd_out(name,getrandhex(32))
|
||||
def B58tohex(self,name,f1,f2): self.run_cmd_chk(name,f1,f2)
|
||||
def B58randenc(self,name):
|
||||
ret = self.run_cmd_out(name,Return=True)
|
||||
ok_or_die(ret,is_b58_str,'base 58 string')
|
||||
def Hextob32(self,name): self.run_cmd_out(name,getrandhex(24))
|
||||
def B32tohex(self,name,f1,f2): self.run_cmd_chk(name,f1,f2)
|
||||
def Randhex(self,name):
|
||||
ret = self.run_cmd_out(name,Return=True)
|
||||
ok_or_die(ret,binascii.unhexlify,'hex string')
|
||||
def Id6(self,name): self.run_cmd_randinput(name)
|
||||
def Id8(self,name): self.run_cmd_randinput(name)
|
||||
def Str2id6(self,name):
|
||||
s = getrandstr(120,no_space=True)
|
||||
s2 = ' %s %s %s %s %s ' % (s[:3],s[3:9],s[9:29],s[29:50],s[50:120])
|
||||
ret1 = self.run_cmd(name,[s],extra_msg='unspaced input'); ok()
|
||||
ret2 = self.run_cmd(name,[s2],extra_msg='spaced input')
|
||||
cmp_or_die(ret1,ret2)
|
||||
vmsg('Returned: %s' % ret1)
|
||||
|
||||
def mn_rand128(self,name):
|
||||
self.run_cmd_out(name)
|
||||
|
||||
def mn_rand192(self,name):
|
||||
self.run_cmd_out(name)
|
||||
|
||||
def mn_rand256(self,name):
|
||||
self.run_cmd_out(name)
|
||||
|
||||
def mn_stats(self,name):
|
||||
self.run_cmd_out(name)
|
||||
|
||||
def mn_printlist(self,name):
|
||||
self.run_cmd(name,[])
|
||||
ok()
|
||||
|
||||
def id6(self,name): self.run_cmd_randinput(name)
|
||||
def id8(self,name): self.run_cmd_randinput(name)
|
||||
def hexdump(self,name): self.run_cmd_randinput(name,strip=False)
|
||||
|
||||
def unhexdump(self,name,fn1,fn2):
|
||||
def Hash160(self,name): self.run_cmd_out(name,getrandhex(16))
|
||||
def Hash256(self,name): self.run_cmd_out(name,getrandstr(16))
|
||||
def Hexreverse(self,name): self.run_cmd_out(name,getrandhex(24))
|
||||
def Hexlify(self,name): self.run_cmd_out(name,getrandstr(24))
|
||||
def Hexdump(self,name): self.run_cmd_randinput(name,strip=False)
|
||||
def Unhexdump(self,name,fn1,fn2):
|
||||
ret = self.run_cmd(name,[fn2],strip=False)
|
||||
orig = read_from_file(fn1,binary=True)
|
||||
cmp_or_die(orig,ret)
|
||||
|
||||
def rand2file(self,name):
|
||||
def Rand2file(self,name):
|
||||
of = name + '.out'
|
||||
dlen = 1024
|
||||
self.run_cmd(name,[of,str(1024),'threads=4','silent=1'],strip=False)
|
||||
d = read_from_tmpfile(cfg,of,binary=True)
|
||||
cmp_or_die(dlen,len(d))
|
||||
|
||||
def strtob58(self,name): self.run_cmd_out(name,getrandstr(16))
|
||||
def sha256x2(self,name): self.run_cmd_out(name,getrandstr(16))
|
||||
def hexreverse(self,name): self.run_cmd_out(name,getrandhex(24))
|
||||
def hexlify(self,name): self.run_cmd_out(name,getrandstr(24))
|
||||
def b58tostr(self,name,f1,f2): self.run_cmd_chk(name,f1,f2)
|
||||
def hextob58(self,name): self.run_cmd_out(name,getrandhex(32))
|
||||
def b58tohex(self,name,f1,f2): self.run_cmd_chk(name,f1,f2)
|
||||
def hextob32(self,name): self.run_cmd_out(name,getrandhex(24))
|
||||
def b32tohex(self,name,f1,f2): self.run_cmd_chk(name,f1,f2)
|
||||
def b58randenc(self,name):
|
||||
ret = self.run_cmd_out(name,Return=True)
|
||||
ok_or_die(ret,is_b58_str,'base 58 string')
|
||||
def randhex(self,name):
|
||||
ret = self.run_cmd_out(name,Return=True)
|
||||
ok_or_die(ret,binascii.unhexlify,'hex string')
|
||||
def randwif(self,name):
|
||||
# Bitcoin
|
||||
def Randwif(self,name):
|
||||
for n,k in enumerate(['','compressed=1']):
|
||||
ret = self.run_cmd_out(name,kwargs=k,Return=True,fn_idx=n+1)
|
||||
ok_or_die(ret,is_wif,'WIF key')
|
||||
def randpair(self,name):
|
||||
for n,k in enumerate(['','compressed=1']):
|
||||
def Randpair(self,name):
|
||||
for n,k in enumerate(['','compressed=1','segwit=1 compressed=1']):
|
||||
wif,addr = self.run_cmd_out(name,kwargs=k,Return=True,fn_idx=n+1).split()
|
||||
ok_or_die(wif,is_wif,'WIF key',skip_ok=True)
|
||||
ok_or_die(addr,is_btc_addr,'Bitcoin address')
|
||||
def hex2wif(self,name,f1,f2,f3,f4):
|
||||
def Wif2addr(self,name,f1,f2,f3):
|
||||
for n,f,k,m in (1,f1,'',''),(2,f2,'','compressed'),(3,f3,'segwit=1','compressed'):
|
||||
wif = read_from_file(f).split()[0]
|
||||
self.run_cmd_out(name,wif,kwargs=k,fn_idx=n,extra_msg=m)
|
||||
def Wif2hex(self,name,f1,f2,f3):
|
||||
for n,f,m in (1,f1,''),(2,f2,'compressed'),(3,f3,'compressed for segwit'):
|
||||
wif = read_from_file(f).split()[0]
|
||||
self.run_cmd_out(name,wif,fn_idx=n,extra_msg=m)
|
||||
def Privhex2addr(self,name,f1,f2,f3):
|
||||
keys = [read_from_file(f).rstrip() for f in f1,f2,f3]
|
||||
for n,k in enumerate(('','compressed=1','compressed=1 segwit=1')):
|
||||
ret = self.run_cmd(name,[keys[n]],kwargs=k).rstrip()
|
||||
iaddr = read_from_tmpfile(cfg,'Randpair{}.out'.format(n+1)).split()[-1]
|
||||
cmp_or_die(iaddr,ret)
|
||||
def Hex2wif(self,name,f1,f2,f3,f4):
|
||||
for n,fi,fo,k in (1,f1,f2,''),(2,f3,f4,'compressed=1'):
|
||||
ret = self.run_cmd_chk(name,fi,fo,kwargs=k)
|
||||
def wif2hex(self,name,f1,f2):
|
||||
for n,f,k in (1,f1,''),(2,f2,'compressed=1'):
|
||||
wif = read_from_file(f).split()[0]
|
||||
self.run_cmd_out(name,wif,kwargs=k,fn_idx=n)
|
||||
def wif2addr(self,name,f1,f2):
|
||||
for n,f,k in (1,f1,''),(2,f2,'compressed=1'):
|
||||
wif = read_from_file(f).split()[0]
|
||||
self.run_cmd_out(name,wif,kwargs=k,fn_idx=n)
|
||||
def addr2hexaddr(self,name,f1,f2):
|
||||
def Addr2hexaddr(self,name,f1,f2):
|
||||
for n,f,m in (1,f1,''),(2,f2,'from compressed'):
|
||||
addr = read_from_file(f).split()[-1]
|
||||
self.run_cmd_out(name,addr,fn_idx=n,extra_msg=m)
|
||||
def hexaddr2addr(self,name,f1,f2,f3,f4):
|
||||
def Hexaddr2addr(self,name,f1,f2,f3,f4):
|
||||
for n,fi,fo,m in (1,f1,f2,''),(2,f3,f4,'from compressed'):
|
||||
self.run_cmd_chk(name,fi,fo,extra_msg=m)
|
||||
def privhex2addr(self,name,f1,f2):
|
||||
key1 = read_from_file(f1).rstrip()
|
||||
key2 = read_from_file(f2).rstrip()
|
||||
for n,args in enumerate([[key1],[key2,'compressed=1']]):
|
||||
ret = self.run_cmd(name,args).rstrip()
|
||||
iaddr = read_from_tmpfile(cfg,'randpair%s.out' % (n+1)).split()[-1]
|
||||
cmp_or_die(iaddr,ret)
|
||||
def hex2mn(self,name):
|
||||
def Privhex2pubhex(self,name,f1,f2,f3): # from hex2wif
|
||||
addr = read_from_file(f3).strip()
|
||||
self.run_cmd_out(name,addr,kwargs='compressed=1',fn_idx=3)
|
||||
def Pubhex2redeem_script(self,name,f1,f2,f3): # from above
|
||||
addr = read_from_file(f3).strip()
|
||||
self.run_cmd_out(name,addr,fn_idx=3)
|
||||
rs = read_from_tmpfile(cfg,name+'3.out').strip()
|
||||
self.run_cmd_out('pubhex2addr',rs,kwargs='p2sh=1',fn_idx=3,hush=True)
|
||||
addr1 = read_from_tmpfile(cfg,'pubhex2addr3.out').strip()
|
||||
addr2 = read_from_tmpfile(cfg,'Randpair3.out').split()[1]
|
||||
cmp_or_die(addr1,addr2)
|
||||
def Wif2redeem_script(self,name,f1,f2,f3): # compare output with above
|
||||
wif = read_from_file(f3).split()[0]
|
||||
ret1 = self.run_cmd_out(name,wif,fn_idx=3,Return=True)
|
||||
ret2 = read_from_tmpfile(cfg,'Pubhex2redeem_script3.out').strip()
|
||||
cmp_or_die(ret1,ret2)
|
||||
def Wif2segwit_pair(self,name,f1,f2): # does its own checking, so just run
|
||||
wif = read_from_file(f2).split()[0]
|
||||
self.run_cmd_out(name,wif,fn_idx=2)
|
||||
|
||||
def Pubhex2addr(self,name,f1,f2,f3):
|
||||
addr = read_from_file(f3).strip()
|
||||
self.run_cmd_out(name,addr,fn_idx=3)
|
||||
|
||||
def Pipetest(self,name,f1,f2,f3):
|
||||
test_msg('command piping')
|
||||
wif = read_from_file(f3).split()[0]
|
||||
cmd = '{tc} {sa} wif2hex {wif} | {tc} privhex2pubhex - compressed=1 | {tc} pubhex2redeem_script - | {tc} {sa} pubhex2addr - p2sh=1'.format(wif=wif,sa=' '.join(add_spawn_args),tc=' '.join(spawn_cmd))
|
||||
p = subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE,shell=True)
|
||||
res = p.stdout.read().strip()
|
||||
addr = read_from_tmpfile(cfg,'Wif2addr3.out').strip()
|
||||
cmp_or_die(res,addr)
|
||||
|
||||
|
||||
# Mnemonic
|
||||
def Hex2mn(self,name):
|
||||
for n,size,m in(1,16,'128-bit'),(2,24,'192-bit'),(3,32,'256-bit'):
|
||||
hexnum = getrandhex(size)
|
||||
self.run_cmd_out(name,hexnum,fn_idx=n,extra_msg=m)
|
||||
def mn2hex(self,name,f1,f2,f3,f4,f5,f6):
|
||||
def Mn2hex(self,name,f1,f2,f3,f4,f5,f6):
|
||||
for f_i,f_o,m in (f1,f2,'128-bit'),(f3,f4,'192-bit'),(f5,f6,'256-bit'):
|
||||
self.run_cmd_chk(name,f_i,f_o,extra_msg=m)
|
||||
def Mn_rand128(self,name): self.run_cmd_out(name)
|
||||
def Mn_rand192(self,name): self.run_cmd_out(name)
|
||||
def Mn_rand256(self,name): self.run_cmd_out(name)
|
||||
def Mn_stats(self,name): self.run_cmd_out(name)
|
||||
def Mn_printlist(self,name):
|
||||
self.run_cmd(name,[])
|
||||
ok()
|
||||
|
||||
def getbalance(self,name):
|
||||
self.run_cmd_out(name,literal=True)
|
||||
def listaddresses(self,name):
|
||||
self.run_cmd_out(name,literal=True)
|
||||
def twview(self,name):
|
||||
self.run_cmd_out(name,literal=True)
|
||||
def txview(self,name):
|
||||
fn = os.path.join(cfg['refdir'],cfg['txfile'])
|
||||
self.run_cmd_out(name,fn,literal=True)
|
||||
def addrfile_chksum(self,name):
|
||||
# RPC
|
||||
def Addrfile_chksum(self,name):
|
||||
fn = os.path.join(cfg['refdir'],cfg['addrfile'])
|
||||
self.run_cmd_out(name,fn,literal=True,chkdata=cfg['addrfile_chk'])
|
||||
def Getbalance(self,name):
|
||||
self.run_cmd_out(name,literal=True)
|
||||
def Listaddresses(self,name):
|
||||
self.run_cmd_out(name,literal=True)
|
||||
def Twview(self,name):
|
||||
self.run_cmd_out(name,literal=True)
|
||||
def Txview(self,name):
|
||||
fn = os.path.join(cfg['refdir'],cfg['txfile'])
|
||||
self.run_cmd_out(name,fn,literal=True)
|
||||
|
||||
# main()
|
||||
import time
|
||||
|
|
@ -378,14 +425,13 @@ mk_tmpdir(cfg['tmpdir'])
|
|||
if cmd_args:
|
||||
if len(cmd_args) != 1:
|
||||
die(1,'Only one command may be specified')
|
||||
|
||||
cmd = cmd_args[0]
|
||||
if cmd in cmd_data:
|
||||
msg('Running tests for %s:' % cmd_data[cmd]['desc'])
|
||||
ts.do_cmds(cmd)
|
||||
elif cmd == 'clean':
|
||||
cleandir(cfg['tmpdir'])
|
||||
sys.exit()
|
||||
sys.exit(0)
|
||||
else:
|
||||
die(1,"'%s': unrecognized command" % cmd)
|
||||
else:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue