Key/address generation for Ethereum, Eth Classic, DASH, and Zcash
- supported commands: - mmgen-addrgen - mmgen-keygen (decrypt encrypted key-addr files with `mmgen-tool decrypt`) - mmgen-tool addrfile_chksum, keyaddrfile_chksum - ETH and ETC keys are distinct, so users needn't worry about key reuse - Only Zcash-t addresses supported for now (but z-addresses coming soon) - Test the new functionality with `scripts/test-release.sh -Pn master alts`
This commit is contained in:
parent
71058479a1
commit
bafea57134
22 changed files with 527 additions and 126 deletions
|
|
@ -3,6 +3,10 @@ include doc/wiki/using-mmgen/*
|
|||
include test/*.py
|
||||
include test/ref/*
|
||||
include test/ref/litecoin/*
|
||||
include test/ref/zcash/*
|
||||
include test/ref/dash/*
|
||||
include test/ref/ethereum/*
|
||||
include test/ref/ethereum_classic/*
|
||||
|
||||
include scripts/bitcoind-walletunlock.py
|
||||
include scripts/compute-file-chksum.py
|
||||
|
|
|
|||
|
|
@ -27,27 +27,32 @@ from mmgen.obj import *
|
|||
|
||||
pnm = g.proj_name
|
||||
|
||||
def sc_dmsg(desc,data):
|
||||
if os.getenv('MMGEN_DEBUG_ADDRLIST'):
|
||||
Msg('sc_debug_{}: {}'.format(desc,data))
|
||||
|
||||
class AddrGenerator(MMGenObject):
|
||||
def __new__(cls,atype):
|
||||
d = {
|
||||
'p2pkh': AddrGeneratorP2PKH,
|
||||
'segwit': AddrGeneratorSegwit
|
||||
'segwit': AddrGeneratorSegwit,
|
||||
'ethereum': AddrGeneratorEthereum
|
||||
}
|
||||
assert atype in d
|
||||
return super(cls,cls).__new__(d[atype])
|
||||
me = super(cls,cls).__new__(d[atype])
|
||||
me.desc = d
|
||||
return me
|
||||
|
||||
class AddrGeneratorP2PKH(AddrGenerator):
|
||||
desc = 'p2pkh'
|
||||
def to_addr(self,pubhex):
|
||||
from mmgen.protocol import hash160
|
||||
assert type(pubhex) == PubKey
|
||||
return CoinAddr(g.proto.hexaddr2addr(hash160(pubhex),p2sh=False))
|
||||
return CoinAddr(g.proto.pubhash2addr(hash160(pubhex),p2sh=False))
|
||||
|
||||
def to_segwit_redeem_script(self,pubhex):
|
||||
raise NotImplementedError
|
||||
|
||||
class AddrGeneratorSegwit(AddrGenerator):
|
||||
desc = 'segwit'
|
||||
def to_addr(self,pubhex):
|
||||
assert pubhex.compressed
|
||||
return CoinAddr(g.proto.pubhex2segwitaddr(pubhex))
|
||||
|
|
@ -56,6 +61,15 @@ class AddrGeneratorSegwit(AddrGenerator):
|
|||
assert pubhex.compressed
|
||||
return HexStr(g.proto.pubhex2redeem_script(pubhex))
|
||||
|
||||
class AddrGeneratorEthereum(AddrGenerator):
|
||||
def to_addr(self,pubhex):
|
||||
assert type(pubhex) == PubKey
|
||||
import sha3
|
||||
return CoinAddr(sha3.keccak_256(pubhex[2:].decode('hex')).digest()[12:].encode('hex'))
|
||||
|
||||
def to_segwit_redeem_script(self,pubhex):
|
||||
raise NotImplementedError
|
||||
|
||||
class KeyGenerator(MMGenObject):
|
||||
|
||||
def __new__(cls,generator=None,silent=False):
|
||||
|
|
@ -161,8 +175,10 @@ class AddrListIDStr(unicode,Hilite):
|
|||
if fmt_str:
|
||||
ret = fmt_str.format(s)
|
||||
else:
|
||||
bc,mt = g.proto.base_coin,addrlist.al_id.mmtype
|
||||
ret = '{}{}{}[{}]'.format(addrlist.al_id.sid,('-'+bc,'')[bc=='BTC'],('-'+mt,'')[mt=='L'],s)
|
||||
bc = (g.proto.base_coin,g.coin)[g.proto.base_coin=='ETH']
|
||||
mt = addrlist.al_id.mmtype
|
||||
ret = '{}{}{}[{}]'.format(addrlist.al_id.sid,('-'+bc,'')[bc=='BTC'],('-'+mt,'')[mt in ('L','E')],s)
|
||||
sc_dmsg('id_str',ret[8:].split('[')[0])
|
||||
|
||||
return unicode.__new__(cls,ret)
|
||||
|
||||
|
|
@ -196,14 +212,14 @@ Removed %s duplicate WIF key%s from keylist (also in {pnm} key-address file
|
|||
gen_keys = False
|
||||
has_keys = False
|
||||
ext = 'addrs'
|
||||
cook_hash_rounds = 10 # not too many rounds, so hand decoding can still be feasible
|
||||
scramble_hash_rounds = 10 # not too many rounds, so hand decoding can still be feasible
|
||||
chksum_rec_f = lambda foo,e: (str(e.idx), e.addr)
|
||||
|
||||
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 MMGenAddrType.dfl_mmtype
|
||||
mmtype = mmtype or g.proto.dfl_mmtype
|
||||
assert mmtype in MMGenAddrType.mmtypes
|
||||
|
||||
if seed and addr_idxs: # data from seed + idxs
|
||||
|
|
@ -255,7 +271,8 @@ Removed %s duplicate WIF key%s from keylist (also in {pnm} key-address file
|
|||
assert type(addrnums) is AddrIdxList
|
||||
|
||||
seed = seed.get_data()
|
||||
seed = self.cook_seed(seed)
|
||||
seed = self.scramble_seed(seed)
|
||||
sc_dmsg('seed',seed[:8].encode('hex'))
|
||||
|
||||
if self.gen_addrs:
|
||||
kg = KeyGenerator()
|
||||
|
|
@ -296,17 +313,18 @@ Removed %s duplicate WIF key%s from keylist (also in {pnm} key-address file
|
|||
|
||||
def check_format(self,addr): return True # format is checked when added to list entry object
|
||||
|
||||
def cook_seed(self,seed):
|
||||
def scramble_seed(self,seed):
|
||||
is_btcfork = g.proto.base_coin == 'BTC'
|
||||
if is_btcfork and self.al_id.mmtype == 'L':
|
||||
sc_dmsg('str','(none)')
|
||||
return seed
|
||||
if g.proto.base_coin == 'ETH':
|
||||
scramble_key = g.coin.lower()
|
||||
else:
|
||||
from mmgen.crypto import sha256_rounds
|
||||
import hmac
|
||||
key = (g.coin.lower()+':','')[is_btcfork] + 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)
|
||||
scramble_key = (g.coin.lower()+':','')[is_btcfork] + self.al_id.mmtype.name
|
||||
sc_dmsg('str',scramble_key)
|
||||
from mmgen.crypto import scramble_seed
|
||||
return scramble_seed(seed,scramble_key,self.scramble_hash_rounds)
|
||||
|
||||
def encrypt(self,desc='new key list'):
|
||||
from mmgen.crypto import mmgen_encrypt
|
||||
|
|
@ -397,12 +415,16 @@ Removed %s duplicate WIF key%s from keylist (also in {pnm} key-address file
|
|||
out.append('# Record this value to a secure location.\n')
|
||||
|
||||
if type(self) == PasswordList:
|
||||
out.append(u'{} {} {}:{} {{'.format(
|
||||
self.al_id.sid,self.pw_id_str,self.pw_fmt,self.pw_len))
|
||||
lbl = u'{} {} {}:{}'.format(self.al_id.sid,self.pw_id_str,self.pw_fmt,self.pw_len)
|
||||
else:
|
||||
bc,mt = g.proto.base_coin,self.al_id.mmtype
|
||||
lbl = ':'.join(([bc],[])[bc=='BTC']+([mt.name.upper()],[])[mt=='L'])
|
||||
out.append('{} {}{{'.format(self.al_id.sid,('',lbl+' ')[bool(lbl)]))
|
||||
l_coin = [] if bc == 'BTC' else [g.coin] if bc == 'ETH' else [bc]
|
||||
l_type = [] if mt in ('L','E') else [mt.name.upper()]
|
||||
lbl_p2 = ':'.join(l_coin+l_type)
|
||||
lbl = self.al_id.sid + ('',' ')[bool(lbl_p2)] + lbl_p2
|
||||
|
||||
sc_dmsg('lbl',lbl[9:])
|
||||
out.append(u'{} {{'.format(lbl))
|
||||
|
||||
fs = ' {:<%s} {:<34}{}' % len(str(self.data[-1].idx))
|
||||
for e in self.data:
|
||||
|
|
@ -492,20 +514,19 @@ Removed %s duplicate WIF key%s from keylist (also in {pnm} key-address file
|
|||
return do_error("'%s': invalid Seed ID" % ls[0])
|
||||
|
||||
def parse_addrfile_label(lbl): # we must maintain backwards compat, so parse is tricky
|
||||
al_base_coin,al_mmtype = None,None
|
||||
al_coin,al_mmtype = None,None
|
||||
lbl = lbl.split(':',1)
|
||||
if len(lbl) == 2:
|
||||
al_base_coin = lbl[0]
|
||||
al_mmtype = lbl[1].lower()
|
||||
al_coin,al_mmtype = lbl[0],lbl[1].lower()
|
||||
else:
|
||||
if lbl[0].lower() in MMGenAddrType.get_names():
|
||||
al_mmtype = lbl[0].lower()
|
||||
else:
|
||||
al_base_coin = lbl[0]
|
||||
al_coin = lbl[0]
|
||||
|
||||
# this block fails if al_mmtype is invalid for g.coin
|
||||
if not al_mmtype:
|
||||
mmtype = MMGenAddrType('L')
|
||||
mmtype = MMGenAddrType('E' if al_coin in ('ETH','ETC') else 'L')
|
||||
else:
|
||||
try:
|
||||
mmtype = MMGenAddrType(al_mmtype)
|
||||
|
|
@ -514,9 +535,9 @@ Removed %s duplicate WIF key%s from keylist (also in {pnm} key-address file
|
|||
mmtype.upper(),' '.join([i['name'].upper() for i in MMGenAddrType.mmtypes.values()])))
|
||||
|
||||
from mmgen.protocol import CoinProtocol
|
||||
base_coin = CoinProtocol(al_base_coin or 'BTC',testnet=False).base_coin
|
||||
base_coin = CoinProtocol(al_coin or 'BTC',testnet=False).base_coin
|
||||
if not base_coin:
|
||||
die(2,"'{}': unknown base coin in address file label!".format(al_base_coin))
|
||||
die(2,"'{}': unknown base coin in address file label!".format(al_coin))
|
||||
return base_coin,mmtype
|
||||
|
||||
def check_coin_mismatch(base_coin): # die if addrfile coin doesn't match g.coin
|
||||
|
|
@ -686,18 +707,13 @@ Record this checksum: it will be used to verify the password file in the future
|
|||
return False
|
||||
return True
|
||||
|
||||
def cook_seed(self,seed):
|
||||
from mmgen.crypto import sha256_rounds
|
||||
# Changing either pw_fmt, pw_len or cook_str will cause a different,
|
||||
def scramble_seed(self,seed):
|
||||
# Changing either pw_fmt, pw_len or scramble_key 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'
|
||||
cook_str = '{}:{}:{}'.format(self.pw_fmt,self.pw_len,self.pw_id_str.encode('utf8'))
|
||||
dmsg(u'Full ID string: {}'.format(cook_str.decode('utf8')))
|
||||
# Original implementation was 'cseed = seed + cook_str'; hmac was not used
|
||||
import hmac
|
||||
cseed = hmac.new(seed,cook_str,sha256).digest()
|
||||
dmsg('Seed: {}\nCooked seed: {}\nCooked seed len: {}'.format(hexlify(seed),hexlify(cseed),len(cseed)))
|
||||
return sha256_rounds(cseed,self.cook_hash_rounds)
|
||||
scramble_key = '{}:{}:{}'.format(self.pw_fmt,self.pw_len,self.pw_id_str.encode('utf8'))
|
||||
from mmgen.crypto import scramble_seed
|
||||
return scramble_seed(seed,scramble_key,self.scramble_hash_rounds)
|
||||
|
||||
class AddrData(MMGenObject):
|
||||
msgs = {
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ opts_data = lambda: {
|
|||
kgs=' '.join(['{}:{}'.format(n,k) for n,k in enumerate(g.key_generators,1)]),
|
||||
kg=g.key_generator,
|
||||
what=gen_what,g=g,
|
||||
dmat="'{}' or '{}'".format(MAT.dfl_mmtype,MAT.mmtypes[MAT.dfl_mmtype]['name'])
|
||||
dmat="'{}' or '{}'".format(g.proto.dfl_mmtype,MAT.mmtypes[g.proto.dfl_mmtype]['name'])
|
||||
),
|
||||
'notes': """
|
||||
|
||||
|
|
@ -118,7 +118,7 @@ FMT CODES:
|
|||
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)
|
||||
addr_type = MAT(opt.type or g.proto.dfl_mmtype,errmsg=errmsg)
|
||||
|
||||
if len(cmd_args) < 1: opts.usage()
|
||||
idxs = AddrIdxList(fmt_str=cmd_args.pop())
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ cmd_help = """
|
|||
Cryptocoin address/key operations (compressed public keys supported):
|
||||
addr2hexaddr - convert coin address from base58 to hex format
|
||||
hex2wif - convert a private key from hex to WIF format
|
||||
hexaddr2addr - convert coin address from hex to base58 format
|
||||
pubhash2addr - convert public key hash to address
|
||||
privhex2addr - generate coin 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
|
||||
|
|
|
|||
33
mmgen/obj.py
33
mmgen/obj.py
|
|
@ -355,7 +355,6 @@ class LTCAmt(BTCAmt): max_amt = 84000000
|
|||
|
||||
class CoinAddr(str,Hilite,InitErrors,MMGenObject):
|
||||
color = 'cyan'
|
||||
width = 35 # max len of testnet p2sh addr
|
||||
def __new__(cls,s,on_fail='die'):
|
||||
if type(s) == cls: return s
|
||||
cls.arg_chk(cls,on_fail)
|
||||
|
|
@ -367,6 +366,7 @@ class CoinAddr(str,Hilite,InitErrors,MMGenObject):
|
|||
assert va,'failed verification'
|
||||
me.addr_fmt = va['format']
|
||||
me.hex = va['hex']
|
||||
cls.width = va['width']
|
||||
return me
|
||||
except Exception as e:
|
||||
m = "{!r}: value cannot be converted to {} address ({})"
|
||||
|
|
@ -385,10 +385,17 @@ class CoinAddr(str,Hilite,InitErrors,MMGenObject):
|
|||
def is_for_chain(self,chain):
|
||||
from mmgen.globalvars import g
|
||||
vn = g.proto.get_protocol_by_chain(chain).addr_ver_num
|
||||
|
||||
def pfx_ok(pfx):
|
||||
if type(pfx) == tuple:
|
||||
if self[0] in pfx: return True
|
||||
elif self[:len(pfx)] == pfx: return True
|
||||
return False
|
||||
|
||||
if self.addr_fmt == 'p2sh' and 'p2sh2' in vn:
|
||||
return self[0] in vn['p2sh'][1] or self[0] in vn['p2sh2'][1]
|
||||
return pfx_ok(vn['p2sh'][1]) or pfx_ok(vn['p2sh2'][1])
|
||||
else:
|
||||
return self[0] in vn[self.addr_fmt][1]
|
||||
return pfx_ok(vn[self.addr_fmt][1])
|
||||
|
||||
def is_in_tracking_wallet(self):
|
||||
from mmgen.rpc import rpc_init
|
||||
|
|
@ -427,7 +434,7 @@ class MMGenID(str,Hilite,InitErrors,MMGenObject):
|
|||
try:
|
||||
ss = str(s).split(':')
|
||||
assert len(ss) in (2,3),'not 2 or 3 colon-separated items'
|
||||
t = MMGenAddrType((ss[1],MMGenAddrType.dfl_mmtype)[len(ss)==2],on_fail='raise')
|
||||
t = MMGenAddrType((ss[1],g.proto.dfl_mmtype)[len(ss)==2],on_fail='raise')
|
||||
me = str.__new__(cls,'{}:{}:{}'.format(ss[0],t,ss[-1]))
|
||||
me.sid = SeedID(sid=ss[0],on_fail='raise')
|
||||
me.idx = AddrIdx(ss[-1],on_fail='raise')
|
||||
|
|
@ -587,7 +594,7 @@ class PrivKey(str,Hilite,InitErrors,MMGenObject):
|
|||
me.wif = me.towif()
|
||||
return me
|
||||
except Exception as e:
|
||||
fs = "Key={!r}\nCompressed={}\nValue pair cannot be converted to PrivKey ({!r})"
|
||||
fs = "Key={!r}\nCompressed={}\nValue pair cannot be converted to PrivKey\n({})"
|
||||
return cls.init_fail(fs.format(s,compressed,e),on_fail)
|
||||
|
||||
def towif(self):
|
||||
|
|
@ -671,18 +678,22 @@ class MMGenAddrType(str,Hilite,InitErrors,MMGenObject):
|
|||
'gen':'p2pkh',
|
||||
'fmt':'p2pkh',
|
||||
'desc':'Legacy uncompressed address'},
|
||||
'C': { 'name':'compressed',
|
||||
'comp':True,
|
||||
'gen':'p2pkh',
|
||||
'fmt':'p2pkh',
|
||||
'desc':'Compressed P2PKH address'},
|
||||
'S': { 'name':'segwit',
|
||||
'comp':True,
|
||||
'gen':'segwit',
|
||||
'fmt':'p2sh',
|
||||
'desc':'Segwit P2SH-P2WPKH address' },
|
||||
'C': { 'name':'compressed',
|
||||
'comp':True,
|
||||
'gen':'p2pkh',
|
||||
'fmt':'p2pkh',
|
||||
'desc':'Compressed P2PKH address'}
|
||||
'E': { 'name':'ethereum',
|
||||
'comp':False,
|
||||
'gen':'ethereum',
|
||||
'fmt':'ethereum',
|
||||
'desc':'Ethereum address' },
|
||||
}
|
||||
dfl_mmtype = 'L'
|
||||
def __new__(cls,s,on_fail='die',errmsg=None):
|
||||
if type(s) == cls: return s
|
||||
cls.arg_chk(cls,on_fail)
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ protocol.py: Coin protocol functions, classes and methods
|
|||
|
||||
import os,hashlib
|
||||
from binascii import unhexlify
|
||||
from mmgen.util import msg,pmsg
|
||||
from mmgen.util import msg,pmsg,Msg
|
||||
from mmgen.obj import MMGenObject,BTCAmt,LTCAmt,BCHAmt,B2XAmt
|
||||
from mmgen.globalvars import g
|
||||
|
||||
|
|
@ -53,12 +53,14 @@ def _b58tonum(b58num):
|
|||
if not i in _b58a: return False
|
||||
return sum(_b58a.index(n) * (58**i) for i,n in enumerate(list(b58num[::-1])))
|
||||
|
||||
# chainparams.cpp
|
||||
class BitcoinProtocol(MMGenObject):
|
||||
name = 'bitcoin'
|
||||
daemon_name = 'bitcoind'
|
||||
addr_ver_num = { 'p2pkh': ('00','1'), 'p2sh': ('05','3') } # chainparams.cpp
|
||||
privkey_pfx = '80'
|
||||
addr_ver_num = { 'p2pkh': ('00','1'), 'p2sh': ('05','3') }
|
||||
wif_ver_num = '80'
|
||||
mmtypes = ('L','C','S')
|
||||
dfl_mmtype = 'L'
|
||||
data_subdir = ''
|
||||
rpc_port = 8332
|
||||
secs_per_block = 600
|
||||
|
|
@ -74,7 +76,9 @@ class BitcoinProtocol(MMGenObject):
|
|||
(None,'','b2x',True)
|
||||
]
|
||||
caps = ('rbf','segwit')
|
||||
mmcaps = ('key','addr','rpc')
|
||||
base_coin = 'BTC'
|
||||
addr_width = 34
|
||||
|
||||
@staticmethod
|
||||
def get_protocol_by_chain(chain):
|
||||
|
|
@ -89,7 +93,7 @@ class BitcoinProtocol(MMGenObject):
|
|||
|
||||
@classmethod
|
||||
def hex2wif(cls,hexpriv,compressed=False):
|
||||
s = cls.privkey_pfx + hexpriv + ('','01')[bool(compressed)]
|
||||
s = cls.wif_ver_num + hexpriv + ('','01')[bool(compressed)]
|
||||
return _numtob58(int(s+hash256(s)[:8],16))
|
||||
|
||||
@classmethod
|
||||
|
|
@ -101,34 +105,39 @@ class BitcoinProtocol(MMGenObject):
|
|||
compressed = len(key) == 76
|
||||
if compressed and key[66:68] != '01': return False
|
||||
klen = (66,68)[compressed]
|
||||
if (key[:2] == cls.privkey_pfx and key[klen:] == hash256(key[:klen])[:8]):
|
||||
if (key[:2] == cls.wif_ver_num and key[klen:] == hash256(key[:klen])[:8]):
|
||||
return { 'hex':key[2:66], 'compressed':compressed }
|
||||
else:
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def verify_addr(cls,addr,verbose=False,return_dict=False):
|
||||
def verify_addr(cls,addr,return_dict=False):
|
||||
for addr_fmt in cls.addr_ver_num:
|
||||
ver_num,ldigit = cls.addr_ver_num[addr_fmt]
|
||||
if addr[0] not in ldigit: continue
|
||||
ver_num,pfx = cls.addr_ver_num[addr_fmt]
|
||||
if type(pfx) == tuple:
|
||||
if addr[0] not in pfx: continue
|
||||
elif addr[:len(pfx)] != pfx: 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:]:
|
||||
if num == False:
|
||||
if g.debug: Msg('Address cannot be converted to base 58')
|
||||
break
|
||||
addr_hex = '{:0{}x}'.format(num,48+len(ver_num))
|
||||
if addr_hex[:len(ver_num)] != ver_num: continue
|
||||
if hash256(addr_hex[:-8])[:8] == addr_hex[-8:]:
|
||||
return {
|
||||
'hex': addr_hex[2:42],
|
||||
'hex': addr_hex[len(ver_num):-8],
|
||||
'format': {'p2pkh':'p2pkh','p2sh':'p2sh','p2sh2':'p2sh'}[addr_fmt],
|
||||
'width': cls.addr_width
|
||||
} if return_dict else True
|
||||
else:
|
||||
if verbose: Msg("Invalid checksum in address '{}'".format(addr))
|
||||
if g.debug: Msg('Invalid checksum in address')
|
||||
break
|
||||
if verbose: Msg("Invalid address '{}'".format(addr))
|
||||
if g.debug: Msg("Invalid address '{}'".format(addr))
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def hexaddr2addr(cls,hexaddr,p2sh):
|
||||
s = cls.addr_ver_num[('p2pkh','p2sh')[p2sh]][0] + hexaddr
|
||||
def pubhash2addr(cls,pubkey_hash,p2sh):
|
||||
s = cls.addr_ver_num[('p2pkh','p2sh')[p2sh]][0] + pubkey_hash
|
||||
lzeroes = (len(s) - len(s.lstrip('0'))) / 2 # non-zero only for ver num '00' (BTC p2pkh)
|
||||
return ('1' * lzeroes) + _numtob58(int(s+hash256(s)[:8],16))
|
||||
|
||||
|
|
@ -142,14 +151,15 @@ class BitcoinProtocol(MMGenObject):
|
|||
|
||||
@classmethod
|
||||
def pubhex2segwitaddr(cls,pubhex):
|
||||
return cls.hexaddr2addr(hash160(cls.pubhex2redeem_script(pubhex)),p2sh=True)
|
||||
return cls.pubhash2addr(hash160(cls.pubhex2redeem_script(pubhex)),p2sh=True)
|
||||
|
||||
class BitcoinTestnetProtocol(BitcoinProtocol):
|
||||
addr_ver_num = { 'p2pkh': ('6f','mn'), 'p2sh': ('c4','2') }
|
||||
privkey_pfx = 'ef'
|
||||
addr_ver_num = { 'p2pkh': ('6f',('m','n')), 'p2sh': ('c4','2') }
|
||||
wif_ver_num = 'ef'
|
||||
data_subdir = 'testnet'
|
||||
daemon_data_subdir = 'testnet3'
|
||||
rpc_port = 18332
|
||||
addr_width = 35
|
||||
|
||||
class BitcoinCashProtocol(BitcoinProtocol):
|
||||
# TODO: assumes MSWin user installs in custom dir 'Bitcoin_ABC'
|
||||
|
|
@ -173,10 +183,11 @@ class BitcoinCashProtocol(BitcoinProtocol):
|
|||
|
||||
class BitcoinCashTestnetProtocol(BitcoinCashProtocol):
|
||||
rpc_port = 18442
|
||||
addr_ver_num = { 'p2pkh': ('6f','mn'), 'p2sh': ('c4','2') }
|
||||
privkey_pfx = 'ef'
|
||||
addr_ver_num = { 'p2pkh': ('6f',('m','n')), 'p2sh': ('c4','2') }
|
||||
wif_ver_num = 'ef'
|
||||
data_subdir = 'testnet'
|
||||
daemon_data_subdir = 'testnet3'
|
||||
addr_width = 35
|
||||
|
||||
class B2XProtocol(BitcoinProtocol):
|
||||
daemon_name = 'bitcoind-2x'
|
||||
|
|
@ -190,11 +201,12 @@ class B2XProtocol(BitcoinProtocol):
|
|||
]
|
||||
|
||||
class B2XTestnetProtocol(B2XProtocol):
|
||||
addr_ver_num = { 'p2pkh': ('6f','mn'), 'p2sh': ('c4','2') }
|
||||
privkey_pfx = 'ef'
|
||||
addr_ver_num = { 'p2pkh': ('6f',('m','n')), 'p2sh': ('c4','2') }
|
||||
wif_ver_num = 'ef'
|
||||
data_subdir = 'testnet'
|
||||
daemon_data_subdir = 'testnet5'
|
||||
rpc_port = 18338
|
||||
addr_width = 35
|
||||
|
||||
class LitecoinProtocol(BitcoinProtocol):
|
||||
block0 = '12a765e31ffd4059bada1e25190f6e98c99d9714d334efa41a195a7e7e04bfe2'
|
||||
|
|
@ -203,7 +215,7 @@ class LitecoinProtocol(BitcoinProtocol):
|
|||
daemon_data_dir = os.path.join(os.getenv('APPDATA'),'Litecoin') if g.platform == 'win' \
|
||||
else os.path.join(g.home_dir,'.litecoin')
|
||||
addr_ver_num = { 'p2pkh': ('30','L'), 'p2sh': ('32','M'), 'p2sh2': ('05','3') } # 'p2sh' is new fmt
|
||||
privkey_pfx = 'b0'
|
||||
wif_ver_num = 'b0'
|
||||
secs_per_block = 150
|
||||
rpc_port = 9332
|
||||
coin_amt = LTCAmt
|
||||
|
|
@ -213,16 +225,68 @@ class LitecoinProtocol(BitcoinProtocol):
|
|||
|
||||
class LitecoinTestnetProtocol(LitecoinProtocol):
|
||||
# addr ver nums same as Bitcoin testnet, except for 'p2sh'
|
||||
addr_ver_num = { 'p2pkh': ('6f','mn'), 'p2sh': ('3a','Q'), 'p2sh2': ('c4','2') }
|
||||
privkey_pfx = 'ef' # same as Bitcoin testnet
|
||||
addr_ver_num = { 'p2pkh': ('6f',('m','n')), 'p2sh': ('3a','Q'), 'p2sh2': ('c4','2') }
|
||||
wif_ver_num = 'ef' # same as Bitcoin testnet
|
||||
data_subdir = 'testnet'
|
||||
daemon_data_subdir = 'testnet4'
|
||||
rpc_port = 19332
|
||||
addr_width = 35
|
||||
|
||||
class EthereumProtocol(MMGenObject):
|
||||
base_coin = 'ETH'
|
||||
class BitcoinProtocolAddrgen(BitcoinProtocol): mmcaps = ('key','addr')
|
||||
class BitcoinProtocolKeygen(BitcoinProtocol): mmcaps = ('key',)
|
||||
|
||||
class EthereumProtocol(BitcoinProtocolAddrgen):
|
||||
|
||||
addr_width = 40
|
||||
mmtypes = ('E',)
|
||||
dfl_mmtype = 'E'
|
||||
name = 'ethereum'
|
||||
base_coin = 'ETH'
|
||||
|
||||
@classmethod
|
||||
def hex2wif(cls,hexpriv,compressed=False):
|
||||
assert compressed == False,'Ethereum does not support compressed pubkeys!'
|
||||
return str(hexpriv)
|
||||
|
||||
@classmethod
|
||||
def wif2hex(cls,wif):
|
||||
return { 'hex':str(wif), 'compressed':False }
|
||||
|
||||
@classmethod
|
||||
def verify_addr(cls,addr,return_dict=False):
|
||||
from mmgen.util import is_hex_str_lc
|
||||
if is_hex_str_lc(addr) and len(addr) == 40:
|
||||
return { 'hex': addr, 'format': 'ethereum', 'width': cls.addr_width } if return_dict else True
|
||||
if g.debug: Msg("Invalid address '{}'".format(addr))
|
||||
return False
|
||||
|
||||
class EthereumTestnetProtocol(EthereumProtocol): pass
|
||||
class EthereumClassicProtocol(EthereumProtocol):
|
||||
name = 'ethereum_classic'
|
||||
class EthereumClassicTestnetProtocol(EthereumClassicProtocol): pass
|
||||
|
||||
class ZcashProtocol(BitcoinProtocolAddrgen):
|
||||
name = 'zcash'
|
||||
base_coin = 'ZEC'
|
||||
addr_ver_num = { 'p2pkh': ('1cb8','t1'), 'p2sh': ('1cbd','t3') }
|
||||
wif_ver_num = '80'
|
||||
mmtypes = ('C',)
|
||||
dfl_mmtype = 'C'
|
||||
|
||||
class ZcashTestnetProtocol(object): pass
|
||||
|
||||
class DashProtocol(BitcoinProtocolAddrgen):
|
||||
name = 'dash'
|
||||
base_coin = 'DASH'
|
||||
addr_ver_num = { 'p2pkh': ('4c','X'), 'p2sh': ('10','7') }
|
||||
wif_ver_num = 'cc'
|
||||
mmtypes = ('C',)
|
||||
dfl_mmtype = 'C'
|
||||
|
||||
class DashTestnetProtocol(DashProtocol):
|
||||
# "Dash", "testnet", "tDASH", b'\xef', b'\x8c', b'\x13'
|
||||
addr_ver_num = { 'p2pkh': ('8c','y'), 'p2sh': ('13','?') }
|
||||
wif_ver_num = 'ef'
|
||||
|
||||
class CoinProtocol(MMGenObject):
|
||||
coins = {
|
||||
|
|
@ -230,7 +294,10 @@ class CoinProtocol(MMGenObject):
|
|||
'bch': (BitcoinCashProtocol,BitcoinCashTestnetProtocol),
|
||||
'b2x': (B2XProtocol,B2XTestnetProtocol),
|
||||
'ltc': (LitecoinProtocol,LitecoinTestnetProtocol),
|
||||
# 'eth': (EthereumProtocol,EthereumTestnetProtocol),
|
||||
'dash': (DashProtocol,DashTestnetProtocol),
|
||||
'zec': (ZcashProtocol,ZcashTestnetProtocol),
|
||||
'eth': (EthereumProtocol,EthereumTestnetProtocol),
|
||||
'etc': (EthereumClassicProtocol,EthereumClassicTestnetProtocol),
|
||||
}
|
||||
def __new__(cls,coin,testnet):
|
||||
coin = coin.lower()
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ cmd_data = OrderedDict([
|
|||
('Wif2hex', ['<wif> [str-]']),
|
||||
('Wif2addr', ['<wif> [str-]','segwit [bool=False]']),
|
||||
('Wif2segwit_pair',['<wif> [str-]']),
|
||||
('Hexaddr2addr', ['<coin address in hex format> [str-]','p2sh [bool=False]']),
|
||||
('Pubhash2addr', ['<coin address in hex format> [str-]','p2sh [bool=False]']),
|
||||
('Addr2hexaddr', ['<coin 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]']),
|
||||
|
|
@ -279,11 +279,11 @@ def Wif2segwit_pair(wif):
|
|||
rs = ag.to_segwit_redeem_script(pubhex)
|
||||
Msg('{}\n{}'.format(rs,addr))
|
||||
|
||||
def Hexaddr2addr(hexaddr,p2sh=False): Msg(g.proto.hexaddr2addr(hexaddr,p2sh=p2sh))
|
||||
def Addr2hexaddr(addr): Msg(g.proto.verify_addr(addr,return_dict=True)['hex'])
|
||||
def Hash160(pubkeyhex): Msg(hash160(pubkeyhex))
|
||||
def Pubhex2addr(pubkeyhex,p2sh=False): Msg(g.proto.hexaddr2addr(hash160(pubkeyhex),p2sh=p2sh))
|
||||
def Wif2hex(wif): Msg(wif2hex(wif))
|
||||
def Pubhash2addr(pubhash,p2sh=False): Msg(g.proto.pubhash2addr(pubhash,p2sh=p2sh))
|
||||
def Addr2hexaddr(addr): Msg(g.proto.verify_addr(addr,return_dict=True)['hex'])
|
||||
def Hash160(pubkeyhex): Msg(hash160(pubkeyhex))
|
||||
def Pubhex2addr(pubkeyhex,p2sh=False): Msg(g.proto.pubhash2addr(hash160(pubkeyhex),p2sh=p2sh))
|
||||
def Wif2hex(wif): Msg(wif2hex(wif))
|
||||
def Hex2wif(hexpriv,compressed=False):
|
||||
Msg(g.proto.hex2wif(hexpriv,compressed))
|
||||
def Privhex2addr(privhex,compressed=False,segwit=False,output_pubhex=False):
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ def scriptPubKey2addr(s):
|
|||
if len(s) == 50 and s[:6] == '76a914' and s[-4:] == '88ac': addr_hex,p2sh = s[6:-4],False
|
||||
elif len(s) == 46 and s[:4] == 'a914' and s[-2:] == '87': addr_hex,p2sh = s[4:-2],True
|
||||
else: raise NotImplementedError,'Unknown scriptPubKey'
|
||||
return g.proto.hexaddr2addr(addr_hex,p2sh)
|
||||
return g.proto.pubhash2addr(addr_hex,p2sh)
|
||||
|
||||
from collections import OrderedDict
|
||||
class DeserializedTX(OrderedDict,MMGenObject): # need to add MMGen types
|
||||
|
|
|
|||
|
|
@ -799,6 +799,9 @@ def get_coin_daemon_auth_cookie():
|
|||
|
||||
def rpc_init(reinit=False):
|
||||
|
||||
if not 'rpc' in g.proto.mmcaps:
|
||||
die(1,'Coin daemon operations not supported for coin {}!'.format(g.coin))
|
||||
|
||||
if g.rpch != None and not reinit: return g.rpch
|
||||
|
||||
def check_chainfork_mismatch(conn):
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
# Tested on Linux, MinGW-64
|
||||
# MinGW's bash 3.1.17 doesn't do ${var^^}
|
||||
|
||||
dfl_tests='obj misc btc btc_tn btc_rt bch bch_rt b2x b2x_rt ltc ltc_tn ltc_rt tool gen'
|
||||
dfl_tests='obj alts misc btc btc_tn btc_rt bch bch_rt ltc ltc_tn ltc_rt tool gen'
|
||||
PROGNAME=$(basename $0)
|
||||
while getopts hinPt OPT
|
||||
do
|
||||
|
|
@ -16,14 +16,15 @@ do
|
|||
echo " '-t' Print the tests without running them"
|
||||
echo " AVAILABLE TESTS:"
|
||||
echo " obj - data objects"
|
||||
echo " alts - operations for all supported gen-only altcoins"
|
||||
echo " misc - miscellaneous operations"
|
||||
echo " btc - bitcoin"
|
||||
echo " btc_tn - bitcoin testnet"
|
||||
echo " btc_rt - bitcoin regtest"
|
||||
echo " bch - bitcoin cash (BCH)"
|
||||
echo " bch_rt - bitcoin cash (BCH) regtest"
|
||||
echo " b2x - bitcoin 2x (B2X)"
|
||||
echo " b2x_rt - bitcoin 2x (B2X) regtest"
|
||||
# echo " b2x - bitcoin 2x (B2X)"
|
||||
# echo " b2x_rt - bitcoin 2x (B2X) regtest"
|
||||
echo " ltc - litecoin"
|
||||
echo " ltc_tn - litecoin testnet"
|
||||
echo " ltc_rt - litecoin regtest"
|
||||
|
|
@ -86,7 +87,7 @@ do_test() {
|
|||
[ "$TESTING" ] || eval "$i" || { echo -e $RED'Test failed!'$RESET; exit; }
|
||||
done
|
||||
}
|
||||
i_obj='Data objects'
|
||||
i_obj='Data object'
|
||||
s_obj='Testing data objects'
|
||||
t_obj=(
|
||||
'test/objtest.py --coin=btc -S'
|
||||
|
|
@ -95,6 +96,20 @@ t_obj=(
|
|||
'test/objtest.py --coin=ltc --testnet=1 -S')
|
||||
f_obj='Data object test complete'
|
||||
|
||||
i_alts='Gen-only altcoin'
|
||||
s_alts='The following tests will test generation operations for all supported altcoins'
|
||||
t_alts=(
|
||||
'test/test.py -n altcoin_ref'
|
||||
'test/gentest.py --coin=btc 2:ext 100'
|
||||
'test/gentest.py --coin=ltc 2:ext 100'
|
||||
'test/gentest.py --coin=zec 2:ext 100'
|
||||
'test/gentest.py --coin=dash 2:ext 100'
|
||||
'test/gentest.py --coin=etc 2:ext 100'
|
||||
'test/gentest.py --coin=eth 2:ext 100'
|
||||
'test/scrambletest.py'
|
||||
)
|
||||
f_alts='Gen-only altcoin tests completed'
|
||||
|
||||
i_misc='Miscellaneous operations' # includes autosign!
|
||||
s_misc='The bitcoin, bitcoin-abc and litecoin (mainnet) daemons must be running for the following tests'
|
||||
t_misc=(
|
||||
|
|
@ -125,7 +140,7 @@ s_btc_rt="The following tests will test MMGen's regtest (Bob and Alice) mode"
|
|||
t_btc_rt=(
|
||||
'test/test.py -On regtest'
|
||||
'test/test.py -On regtest_split')
|
||||
f_btc_rt="Regtest (Bob and Alice) mode tests for BTC completed"
|
||||
f_btc_rt='Regtest (Bob and Alice) mode tests for BTC completed'
|
||||
|
||||
i_bch='Bitcoin cash (BCH)'
|
||||
s_bch='The bitcoin cash daemon (Bitcoin ABC) must both be running for the following tests'
|
||||
|
|
@ -135,7 +150,7 @@ f_bch='You may stop the Bitcoin ABC daemon if you wish'
|
|||
i_bch_rt='Bitcoin cash (BCH) regtest'
|
||||
s_bch_rt="The following tests will test MMGen's regtest (Bob and Alice) mode"
|
||||
t_bch_rt=('test/test.py --coin=bch -On regtest')
|
||||
f_bch_rt="Regtest (Bob and Alice) mode tests for BCH completed"
|
||||
f_bch_rt='Regtest (Bob and Alice) mode tests for BCH completed'
|
||||
|
||||
i_b2x='Bitcoin 2X (B2X)'
|
||||
s_b2x='The bitcoin 2X daemon (BTC1) must both be running for the following tests'
|
||||
|
|
@ -145,7 +160,7 @@ f_b2x='You may stop the Bitcoin 2X daemon if you wish'
|
|||
i_b2x_rt='Bitcoin 2X (B2X) regtest'
|
||||
s_b2x_rt="The following tests will test MMGen's regtest (Bob and Alice) mode"
|
||||
t_b2x_rt=('test/test.py --coin=b2x -On regtest')
|
||||
f_b2x_rt="Regtest (Bob and Alice) mode tests for B2X completed"
|
||||
f_b2x_rt='Regtest (Bob and Alice) mode tests for B2X completed'
|
||||
|
||||
i_ltc='Litecoin'
|
||||
s_ltc='The litecoin daemon must both be running for the following tests'
|
||||
|
|
@ -169,8 +184,9 @@ f_ltc_tn='You may stop the litecoin testnet daemon if you wish'
|
|||
i_ltc_rt='Litecoin regtest'
|
||||
s_ltc_rt="The following tests will test MMGen's regtest (Bob and Alice) mode"
|
||||
t_ltc_rt=('test/test.py --coin=ltc -On regtest')
|
||||
f_ltc_rt="Regtest (Bob and Alice) mode tests for LTC completed"
|
||||
f_ltc_rt='Regtest (Bob and Alice) mode tests for LTC completed'
|
||||
|
||||
# TODO: ethereum support for tooltest
|
||||
i_tool='Tooltest'
|
||||
s_tool='The following tests will run test/tooltest.py for all supported coins'
|
||||
t_tool=(
|
||||
|
|
@ -180,8 +196,14 @@ t_tool=(
|
|||
'test/tooltest.py --coin=ltc util'
|
||||
'test/tooltest.py --coin=ltc cryptocoin'
|
||||
'test/tooltest.py --coin=ltc mnemonic'
|
||||
'test/tooltest.py --coin=zec util'
|
||||
'test/tooltest.py --coin=zec cryptocoin'
|
||||
'test/tooltest.py --coin=zec mnemonic'
|
||||
'test/tooltest.py --coin=dash util'
|
||||
'test/tooltest.py --coin=dash cryptocoin'
|
||||
'test/tooltest.py --coin=dash mnemonic'
|
||||
)
|
||||
f_tool="tooltest tests completed"
|
||||
f_tool='tooltest tests completed'
|
||||
|
||||
i_gen='Gentest'
|
||||
s_gen='The following tests will run test/gentest.py on mainnet and testnet for all supported coins'
|
||||
|
|
@ -199,7 +221,7 @@ t_gen=(
|
|||
'test/gentest.py -q --coin=ltc --testnet=1 1:2 10'
|
||||
'test/gentest.py -q --coin=ltc --testnet=1 --segwit 1:2 10'
|
||||
)
|
||||
f_gen="gentest tests completed"
|
||||
f_gen='gentest tests completed'
|
||||
|
||||
[ -d .git -a -z "$NO_INSTALL" -a -z "$TESTING" ] && {
|
||||
check
|
||||
|
|
|
|||
|
|
@ -53,10 +53,16 @@ opts_data = lambda: {
|
|||
EXAMPLES:
|
||||
{prog} 1:2 100
|
||||
(compare output of native Python ECDSA with secp256k1 library, 100 rounds)
|
||||
{prog} 2:ext 100
|
||||
(compare output of secp256k1 library with external library (see below), 100 rounds)
|
||||
{prog} 2 1000
|
||||
(test speed of secp256k1 library address generation, 1000 rounds)
|
||||
{prog} 2 my.dump
|
||||
(compare addrs generated with secp256k1 library to {dn} wallet dump)
|
||||
|
||||
External libraries required for the 'ext' generator:
|
||||
+ pyethereum (for ETH,ETC) https://github.com/ethereum/pyethereum
|
||||
+ pycoin (for all other coins) https://github.com/richardkiss/pycoin
|
||||
""".format(prog='gentest.py',pnm=g.proj_name,snum=rounds,dn=g.proto.daemon_name)
|
||||
}
|
||||
|
||||
|
|
@ -66,6 +72,23 @@ cmd_args = opts.init(opts_data,add_opts=['exact_output'])
|
|||
|
||||
if not 1 <= len(cmd_args) <= 2: opts.usage()
|
||||
|
||||
def pyethereum_sec2addr(sec):
|
||||
return sec,eth.privtoaddr(sec).encode('hex')
|
||||
|
||||
def pycoin_sec2addr(sec):
|
||||
if g.testnet: # pycoin/networks/all.py pycoin/networks/legacy_networks.py
|
||||
coin = { 'BTC':'XTN', 'LTC':'XLT', 'DASH':'tDASH' }[g.coin]
|
||||
else:
|
||||
coin = g.coin
|
||||
key = pcku.parse_key(sec,PREFIX_TRANSFORMS,coin)
|
||||
if key is None: die(1,"can't parse {}".format(sec))
|
||||
o = pcku.create_output(sec,key)[0]
|
||||
# pmsg(o)
|
||||
suf = ('_uncompressed','')[compressed]
|
||||
wif = o['wif{}'.format(suf)]
|
||||
addr = o['{}_address{}'.format(coin,suf)]
|
||||
return wif,addr
|
||||
|
||||
urounds,fh = None,None
|
||||
dump = []
|
||||
if len(cmd_args) == 2:
|
||||
|
|
@ -97,8 +120,27 @@ except:
|
|||
die(1,"First argument must be one or two generator IDs, colon separated")
|
||||
else:
|
||||
try:
|
||||
a,b = int(a),int(b)
|
||||
for i in (a,b): assert 1 <= i <= len(g.key_generators)
|
||||
a = int(a)
|
||||
assert 1 <= a <= len(g.key_generators)
|
||||
if b == 'ext':
|
||||
if g.coin in ('ETH','ETC'):
|
||||
try:
|
||||
import ethereum.utils as eth
|
||||
except:
|
||||
die(1,"Unable to import 'pyethereum' module. Is pyethereum installed?")
|
||||
sec2addr = pyethereum_sec2addr
|
||||
ext_lib = 'pyethereum'
|
||||
else:
|
||||
try:
|
||||
import pycoin.cmds.ku as pcku
|
||||
except:
|
||||
die(1,"Unable to import module 'ku'. Is pycoin installed?")
|
||||
PREFIX_TRANSFORMS = pcku.prefix_transforms_for_network(g.coin)
|
||||
sec2addr = pycoin_sec2addr
|
||||
ext_lib = 'pycoin'
|
||||
else:
|
||||
b = int(b)
|
||||
assert 1 <= b <= len(g.key_generators)
|
||||
assert a != b
|
||||
except:
|
||||
die(1,"%s: invalid generator IDs" % cmd_args[0])
|
||||
|
|
@ -111,40 +153,45 @@ def match_error(sec,wif,a_addr,b_addr,a,b):
|
|||
WIF key : {}
|
||||
{a:10}: {}
|
||||
{b:10}: {}
|
||||
""".format(sec,wif,a_addr,b_addr,pnm=g.proj_name,a=m[a],b=m[b]).rstrip())
|
||||
""".format(sec,wif,a_addr,b_addr,pnm=g.proj_name,a=m[a],b=m[b] if b in m else b).rstrip())
|
||||
|
||||
# Begin execution
|
||||
compressed = True
|
||||
compressed = False if g.coin in ('ETH','ETC') else True
|
||||
|
||||
from mmgen.addr import KeyGenerator,AddrGenerator
|
||||
from mmgen.obj import PrivKey
|
||||
ag = AddrGenerator(('p2pkh','segwit')[bool(opt.segwit)])
|
||||
ag = AddrGenerator('ethereum' if g.coin in ('ETH','ETC') else ('p2pkh','segwit')[bool(opt.segwit)])
|
||||
|
||||
if a and b:
|
||||
m = "Comparing address generators '{}' and '{}'"
|
||||
qmsg(green(m.format(g.key_generators[a-1],g.key_generators[b-1])))
|
||||
m = "Comparing address generators '{}' and '{}' for coin {}"
|
||||
qmsg(green(m.format(g.key_generators[a-1],(ext_lib if b == 'ext' else g.key_generators[b-1]),g.coin)))
|
||||
last_t = time.time()
|
||||
kg_a = KeyGenerator(a)
|
||||
kg_b = KeyGenerator(b)
|
||||
if b != 'ext': kg_b = KeyGenerator(b)
|
||||
|
||||
for i in range(rounds):
|
||||
if time.time() - last_t >= 0.1:
|
||||
if opt.verbose or time.time() - last_t >= 0.1:
|
||||
qmsg_r('\rRound %s/%s ' % (i+1,rounds))
|
||||
last_t = time.time()
|
||||
sec = PrivKey(os.urandom(32),compressed)
|
||||
a_addr = ag.to_addr(kg_a.to_pubhex(sec))
|
||||
b_addr = ag.to_addr(kg_b.to_pubhex(sec))
|
||||
if b == 'ext':
|
||||
b_wif,b_addr = sec2addr(sec)
|
||||
if b_wif != sec.wif:
|
||||
match_error(sec,sec.wif,sec.wif,b_wif,a,b)
|
||||
else:
|
||||
b_addr = ag.to_addr(kg_b.to_pubhex(sec))
|
||||
vmsg('\nkey: %s\naddr: %s\n' % (sec.wif,a_addr))
|
||||
if a_addr != b_addr:
|
||||
match_error(sec,sec.wif,a_addr,b_addr,a,b)
|
||||
if not opt.segwit:
|
||||
match_error(sec,sec.wif,a_addr,b_addr,a,ext_lib if b == 'ext' else b)
|
||||
if not opt.segwit and 'L' in g.proto.mmtypes:
|
||||
compressed = not compressed
|
||||
qmsg_r('\rRound %s/%s ' % (i+1,rounds))
|
||||
|
||||
qmsg(green(('\n','')[bool(opt.verbose)] + 'OK'))
|
||||
elif a and not fh:
|
||||
m = "Testing speed of address generator '{}'"
|
||||
qmsg(green(m.format(g.key_generators[a-1])))
|
||||
m = "Testing speed of address generator '{}' for coin {}"
|
||||
qmsg(green(m.format(g.key_generators[a-1],g.coin)))
|
||||
from struct import pack,unpack
|
||||
seed = os.urandom(28)
|
||||
print 'Incrementing key with each round'
|
||||
|
|
@ -160,7 +207,7 @@ elif a and not fh:
|
|||
sec = PrivKey(seed+pack('I',i),compressed)
|
||||
a_addr = ag.to_addr(kg.to_pubhex(sec))
|
||||
vmsg('\nkey: %s\naddr: %s\n' % (sec.wif,a_addr))
|
||||
if not opt.segwit:
|
||||
if not opt.segwit and g.coin not in ('ETC','ETC'):
|
||||
compressed = not compressed
|
||||
qmsg_r('\rRound %s/%s ' % (i+1,rounds))
|
||||
|
||||
|
|
|
|||
|
|
@ -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 tracking wallet label upon import.
|
||||
# The label may contain any printable ASCII symbol.
|
||||
# Address data checksum for 98831F3A-DASH-C[1,31-33,500-501,1010-1011]: FBC1 6B6A 0988 4403
|
||||
# Record this value to a secure location.
|
||||
98831F3A DASH:COMPRESSED {
|
||||
1 XsjAJvCxkxYh55ZvCZMFEv2eJUVo5xxbwi
|
||||
31 XdxyGv5KDFCqQH8N82gyqQ6vGVu9M1eERy
|
||||
32 XwQSEW9ut1c2itk9bFZCEFcQjMwczLaiJZ
|
||||
33 XaqBXjNHm484ansvkbBzweDG79LNXY9qXT
|
||||
500 Xne3CdDVaH5gyf751pWKpTq7kJR5zuvkYi
|
||||
501 Xqjr9qPHBoUR3kY4D5ZF3RGyw7JMxBLD5Y
|
||||
1010 XoQ5xaSPTKVENQ5adyDBuaf44ySYMfkWwk
|
||||
1011 XhSrc1yoNLJvk9uvr2xZM8LUwwLyYTWrLo
|
||||
}
|
||||
Binary file not shown.
|
|
@ -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 tracking wallet label upon import.
|
||||
# The label may contain any printable ASCII symbol.
|
||||
# Address data checksum for 98831F3A-ETH[1,31-33,500-501,1010-1011]: E554 076E 7AF6 66A3
|
||||
# Record this value to a secure location.
|
||||
98831F3A ETH {
|
||||
1 e704b6cfd9f0edb2e6cfbd0c913438d37ede7b35
|
||||
31 62ff8e4dbd251b98102e3fb5e4b14119e24cadde
|
||||
32 5fb2f71cd3a2fadeb00e4ac5327cc48941655a74
|
||||
33 130e4118ed2badb5938e55ab2ff0c1e05072cd6c
|
||||
500 92594035614a1e10a2add25690a120d537f9ccbf
|
||||
501 84d763e98a24063f92f004f242d7c9282a44f09b
|
||||
1010 04125d2de2355b5b21980ca5c51c33caa4865d2a
|
||||
1011 b01ea3045d4b2cbb3e801822d4173c772cfc48d9
|
||||
}
|
||||
Binary file not shown.
|
|
@ -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 tracking wallet label upon import.
|
||||
# The label may contain any printable ASCII symbol.
|
||||
# Address data checksum for 98831F3A-ETC[1,31-33,500-501,1010-1011]: E97A D796 B495 E8BC
|
||||
# Record this value to a secure location.
|
||||
98831F3A ETC {
|
||||
1 1a6acbef8c38f52f20d04ecded2992b04d8608d7
|
||||
31 91a3da377e7b454ccb6135a35126ec81e93d2a43
|
||||
32 8b6b7368d896d0b8a128aa73b4d4a197ce74fbf5
|
||||
33 36583a66cf8cc74571f7c267b6361e8951f71869
|
||||
500 93c04597594058499735fcfb6da07cb0187df30b
|
||||
501 084f53a77ced2ea7ee9f96e941d95d581efcc3b2
|
||||
1010 e985efcc82520bd203af430000322f1346c7719a
|
||||
1011 61adf2969ea8fd387a474fefbb4c1df7bca851ce
|
||||
}
|
||||
Binary file not shown.
|
|
@ -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 tracking wallet label upon import.
|
||||
# The label may contain any printable ASCII symbol.
|
||||
# Address data checksum for 98831F3A-ZEC-C[1,31-33,500-501,1010-1011]: 903E 7225 DD86 6E01
|
||||
# Record this value to a secure location.
|
||||
98831F3A ZEC:COMPRESSED {
|
||||
1 t1d47QeTehQye4Mms1Lmx7dPjKVoTtHXKmu
|
||||
31 t1ZVJyRRwKcpHn4s6ddqDMgXn9v81GddzFv
|
||||
32 t1bgBCPE2qXtGNoBY9LZWs6JL16eJ5tKLhT
|
||||
33 t1dzBjbV66hymb9KHUuax1FtMh9X8xaMhhf
|
||||
500 t1KiqudpmQPhnKx33w5kLittD9PihAKe5hU
|
||||
501 t1JEQh88x4TgqNqMkUmZbzJCh89KAuqPKGQ
|
||||
1010 t1W5cD3RWq7BJQte5WAbhe67S8ogYbthCnv
|
||||
1011 t1WKKvpybUSndW3KHuMcU5XRTK7XRmtpude
|
||||
}
|
||||
Binary file not shown.
103
test/scrambletest.py
Executable file
103
test/scrambletest.py
Executable file
|
|
@ -0,0 +1,103 @@
|
|||
#!/usr/bin/env python
|
||||
#
|
||||
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
|
||||
# Copyright (C)2013-2017 Philemon <mmgen-py@yandex.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
test/scrambletest.py: seed scrambling and addrlist metadata generation tests for all supported altcoins
|
||||
"""
|
||||
|
||||
import sys,os,subprocess
|
||||
repo_root = os.path.normpath(os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]),os.pardir)))
|
||||
os.chdir(repo_root)
|
||||
sys.path.__setitem__(0,repo_root)
|
||||
|
||||
# Import this _after_ local path's been added to sys.path
|
||||
from mmgen.common import *
|
||||
|
||||
opts_data = lambda: {
|
||||
'desc': 'Test seed scrambling and addrlist metadata generation for all supported altcoins',
|
||||
'usage':'[options] [command]',
|
||||
'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 this test suite
|
||||
-s, --system Test scripts and modules installed on system rather than
|
||||
those in the repo root
|
||||
-v, --verbose Produce more verbose output
|
||||
""",
|
||||
'notes': """
|
||||
|
||||
If no command is given, the whole suite of tests is run.
|
||||
"""
|
||||
}
|
||||
|
||||
cmd_args = opts.init(opts_data)
|
||||
|
||||
os.environ['MMGEN_DEBUG_ADDRLIST'] = '1'
|
||||
if not opt.system:
|
||||
os.environ['PYTHONPATH'] = repo_root
|
||||
|
||||
from collections import OrderedDict
|
||||
test_data = OrderedDict([
|
||||
# SCRAMBLED_SEED[:8] SCRAMBLE_KEY ID_STR LBL
|
||||
('btc', ('456d7f5f1c4bfe3b', '(none)', '', '')),
|
||||
('btc_compressed', ('bf98a4af5464a4ef', 'compressed', '-C', 'COMPRESSED')),
|
||||
('btc_segwit', ('b56962d829ffc678', 'segwit', '-S', 'SEGWIT')),
|
||||
('bch', ('456d7f5f1c4bfe3b', '(none)', '', '')),
|
||||
('bch_compressed', ('bf98a4af5464a4ef', 'compressed', '-C', 'COMPRESSED')),
|
||||
('ltc', ('b11f16632e63ba92', 'ltc:legacy', '-LTC', 'LTC')),
|
||||
('ltc_compressed', ('7ccf465d466ee7d3', 'ltc:compressed', '-LTC-C', 'LTC:COMPRESSED')),
|
||||
('ltc_segwit', ('9460f5ba15e82768', 'ltc:segwit', '-LTC-S', 'LTC:SEGWIT')),
|
||||
('dash', ('bb21cf88c198ab8c', 'dash:compressed','-DASH-C', 'DASH:COMPRESSED')),
|
||||
('zec', ('637f7b8117b524ed', 'zec:compressed', '-ZEC-C', 'ZEC:COMPRESSED')),
|
||||
('eth', ('213ed116869b19f2', 'eth', '-ETH', 'ETH')),
|
||||
('etc', ('909def37096f5ab8', 'etc', '-ETC', 'ETC')),
|
||||
])
|
||||
|
||||
def run_tests():
|
||||
for test in test_data:
|
||||
try: coin,mmtype = test.split('_')
|
||||
except: coin,mmtype = test,None
|
||||
cmd_name = 'cmds/mmgen-addrgen'
|
||||
wf = 'test/ref/98831F3A.mmwords'
|
||||
type_arg = ['--type='+mmtype] if mmtype else []
|
||||
cmd = [cmd_name,'-qS','--coin='+coin] + type_arg + [wf,'1']
|
||||
vmsg(green('Executing: {}'.format(' '.join(cmd))))
|
||||
msg_r('Testing: --coin {:4} {:22}'.format(coin.upper(),type_arg[0] if type_arg else ''))
|
||||
p = subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
|
||||
o = p.stdout.read().splitlines()
|
||||
# pmsg(o)
|
||||
d = [e for e in o if len(e) > 4 and e[:9] == 'sc_debug_']
|
||||
# pmsg(d)
|
||||
for n,k in enumerate(['seed','str','id_str','lbl']):
|
||||
kk = 'sc_debug_'+k
|
||||
a = test_data[test][n]
|
||||
b = [e for e in d if e[:len(kk)] == kk][0][len(kk)+2:]
|
||||
# pmsg(b); continue
|
||||
if b == a:
|
||||
vmsg('sc_{}: {}'.format(k,a))
|
||||
else:
|
||||
rdie(1,'\nError: sc_{} value {} does not match reference value {}'.format(k,b,a))
|
||||
msg('OK')
|
||||
|
||||
start_time = int(time.time())
|
||||
|
||||
run_tests()
|
||||
|
||||
t = int(time.time()) - start_time
|
||||
m = '\nAll requested tests finished OK, elapsed time: {:02}:{:02}'
|
||||
msg(green(m.format(t/60,t%60)))
|
||||
72
test/test.py
72
test/test.py
|
|
@ -431,9 +431,9 @@ cfgs = {
|
|||
'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(altcoin_pfx,tn_ext),
|
||||
'ref_segwitaddrfile':'98831F3A{}-S[1,31-33,500-501,1010-1011]{}.addrs'.format(altcoin_pfx,tn_ext),
|
||||
'ref_keyaddrfile': '98831F3A{}[1,31-33,500-501,1010-1011]{}.akeys.mmenc'.format(altcoin_pfx,tn_ext),
|
||||
'ref_addrfile': '98831F3A{}[1,31-33,500-501,1010-1011]{}.addrs',
|
||||
'ref_segwitaddrfile':'98831F3A{}-S[1,31-33,500-501,1010-1011]{}.addrs',
|
||||
'ref_keyaddrfile': '98831F3A{}[1,31-33,500-501,1010-1011]{}.akeys.mmenc',
|
||||
'ref_passwdfile': '98831F3A-фубар@crypto.org-b58-20[1,4,9-11,1100].pws',
|
||||
'ref_addrfile_chksum': {
|
||||
'btc': ('6FEF 6FB9 7B13 5D91','3C2C 8558 BB54 079E'),
|
||||
|
|
@ -447,7 +447,15 @@ cfgs = {
|
|||
'btc': ('9F2D D781 1812 8BAD','7410 8F95 4B33 B4B2'),
|
||||
'ltc': ('B804 978A 8796 3ED4','93A6 844C 8ECC BEF4'),
|
||||
},
|
||||
'ref_passwdfile_chksum': 'A983 DAB9 5514 27FB',
|
||||
'ref_addrfile_chksum_zec': '903E 7225 DD86 6E01',
|
||||
'ref_addrfile_chksum_dash':'FBC1 6B6A 0988 4403',
|
||||
'ref_addrfile_chksum_eth': 'E554 076E 7AF6 66A3',
|
||||
'ref_addrfile_chksum_etc': 'E97A D796 B495 E8BC',
|
||||
'ref_keyaddrfile_chksum_zec': 'F05A 5A5C 0C8E 2617',
|
||||
'ref_keyaddrfile_chksum_dash': 'E83D 2C63 FEA2 4142',
|
||||
'ref_keyaddrfile_chksum_eth': '3635 4DCF B752 8772',
|
||||
'ref_keyaddrfile_chksum_etc': '9BAC 38E7 5C8E 42E0',
|
||||
'ref_passwdfile_chksum': 'A983 DAB9 5514 27FB',
|
||||
# 'ref_fake_unspent_data':'98831F3A_unspent.json',
|
||||
'ref_tx_file': {
|
||||
'btc': 'FFB367[1.234]{}.rawtx',
|
||||
|
|
@ -700,6 +708,17 @@ cmd_group['misc'] = (
|
|||
('autosign', 'transaction autosigning (BTC,BCH,LTC)'),
|
||||
)
|
||||
|
||||
cmd_group['altcoin_ref'] = (
|
||||
('ref_addrfile_chk_zec', 'reference address file (ZEC)'),
|
||||
('ref_addrfile_chk_dash','reference address file (DASH)'),
|
||||
('ref_addrfile_chk_eth', 'reference address file (ETH)'),
|
||||
('ref_addrfile_chk_etc', 'reference address file (ETC)'),
|
||||
('ref_keyaddrfile_chk_zec', 'reference key-address file (ZEC)'),
|
||||
('ref_keyaddrfile_chk_dash','reference key-address file (DASH)'),
|
||||
('ref_keyaddrfile_chk_eth', 'reference key-address file (ETH)'),
|
||||
('ref_keyaddrfile_chk_etc', 'reference key-address file (ETC)'),
|
||||
)
|
||||
|
||||
# undocumented admin cmds
|
||||
cmd_group_admin = OrderedDict()
|
||||
cmd_group_admin['create_ref_tx'] = (
|
||||
|
|
@ -778,6 +797,11 @@ for a,b in cmd_group['misc']:
|
|||
cmd_list['misc'].append(a)
|
||||
cmd_data[a] = (18,b,[[[],18]])
|
||||
|
||||
cmd_data['info_altcoin_ref'] = 'altcoin reference files',[8]
|
||||
for a,b in cmd_group['altcoin_ref']:
|
||||
cmd_list['altcoin_ref'].append(a)
|
||||
cmd_data[a] = (8,b,[[[],8]])
|
||||
|
||||
utils = {
|
||||
'check_deps': 'check dependencies for specified command',
|
||||
'clean': 'clean specified tmp dir(s) 1,2,3,4,5 or 6 (no arg = all dirs)',
|
||||
|
|
@ -2006,20 +2030,48 @@ class MMGenTestSuite(object):
|
|||
t.close()
|
||||
cmp_or_die(cfg['seed_id'],chk)
|
||||
|
||||
def ref_addrfile_chk(self,name,ftype='addr'):
|
||||
def ref_addrfile_chk(self,name,ftype='addr',coin=None,subdir=None,pfx=None,mmtype=None):
|
||||
af_key = 'ref_{}file'.format(ftype)
|
||||
af = os.path.join(ref_dir,(ref_subdir,'')[ftype=='passwd'],cfg[af_key])
|
||||
t = MMGenExpect(name,'mmgen-tool',[ftype.replace('segwit','')+'file_chksum',af])
|
||||
af_fn = cfg[af_key].format(pfx or altcoin_pfx,'' if coin else tn_ext)
|
||||
af = os.path.join(ref_dir,(subdir or ref_subdir,'')[ftype=='passwd'],af_fn)
|
||||
coin_arg = [] if coin == None else ['--coin='+coin]
|
||||
t = MMGenExpect(name,'mmgen-tool',coin_arg+[ftype.replace('segwit','')+'file_chksum',af])
|
||||
if ftype == 'keyaddr':
|
||||
w = 'key-address data'
|
||||
t.hash_preset(w,ref_kafile_hash_preset)
|
||||
t.passphrase(w,ref_kafile_pass)
|
||||
t.expect('Check key-to-address validity? (y/N): ','y')
|
||||
o = t.read().strip().split('\n')[-1]
|
||||
rc = cfg['ref_'+ftype+'file_chksum']
|
||||
ref_chksum = rc if ftype == 'passwd' else rc[g.proto.base_coin.lower()][g.testnet]
|
||||
rc = cfg[ 'ref_' + ftype + 'file_chksum' +
|
||||
('_'+coin.lower() if coin else '') +
|
||||
('_'+mmtype if mmtype else '')]
|
||||
ref_chksum = rc if (ftype == 'passwd' or coin) else rc[g.proto.base_coin.lower()][g.testnet]
|
||||
cmp_or_die(ref_chksum,o)
|
||||
|
||||
def ref_addrfile_chk_zec(self,name):
|
||||
self.ref_addrfile_chk(name,ftype='addr',coin='ZEC',subdir='zcash',pfx='-ZEC-C')
|
||||
|
||||
def ref_addrfile_chk_dash(self,name):
|
||||
self.ref_addrfile_chk(name,ftype='addr',coin='DASH',subdir='dash',pfx='-DASH-C')
|
||||
|
||||
def ref_addrfile_chk_eth(self,name):
|
||||
self.ref_addrfile_chk(name,ftype='addr',coin='ETH',subdir='ethereum',pfx='-ETH')
|
||||
|
||||
def ref_addrfile_chk_etc(self,name):
|
||||
self.ref_addrfile_chk(name,ftype='addr',coin='ETC',subdir='ethereum_classic',pfx='-ETC')
|
||||
|
||||
def ref_keyaddrfile_chk_zec(self,name):
|
||||
self.ref_addrfile_chk(name,ftype='keyaddr',coin='ZEC',subdir='zcash',pfx='-ZEC-C')
|
||||
|
||||
def ref_keyaddrfile_chk_dash(self,name):
|
||||
self.ref_addrfile_chk(name,ftype='keyaddr',coin='DASH',subdir='dash',pfx='-DASH-C')
|
||||
|
||||
def ref_keyaddrfile_chk_eth(self,name):
|
||||
self.ref_addrfile_chk(name,ftype='keyaddr',coin='ETH',subdir='ethereum',pfx='-ETH')
|
||||
|
||||
def ref_keyaddrfile_chk_etc(self,name):
|
||||
self.ref_addrfile_chk(name,ftype='keyaddr',coin='ETC',subdir='ethereum_classic',pfx='-ETC')
|
||||
|
||||
def ref_keyaddrfile_chk(self,name):
|
||||
self.ref_addrfile_chk(name,ftype='keyaddr')
|
||||
|
||||
|
|
@ -2597,7 +2649,7 @@ class MMGenTestSuite(object):
|
|||
g.proto = psave
|
||||
|
||||
for k in ('in_addrs','out_addrs'):
|
||||
tx[k+'_conv'] = [g.proto.hexaddr2addr(h,(False,True)[f=='p2sh']) for h,f in tx[k+'_hex']]
|
||||
tx[k+'_conv'] = [g.proto.pubhash2addr(h,(False,True)[f=='p2sh']) for h,f in tx[k+'_hex']]
|
||||
|
||||
for k in ('in','out'):
|
||||
for i in range(len(tx[k])):
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ cmd_data = OrderedDict([
|
|||
('Privhex2addr', ('Wif2hex','o3')), # compare with output of Randpair
|
||||
('Hex2wif', ('Wif2hex','io2')),
|
||||
('Addr2hexaddr', ('Randpair','o2')),
|
||||
('Hexaddr2addr', ('Addr2hexaddr','io2')),
|
||||
('Pubhash2addr', ('Addr2hexaddr','io2')),
|
||||
|
||||
('Pipetest', ('Randpair','o3')),
|
||||
])
|
||||
|
|
@ -366,7 +366,7 @@ class MMGenToolTestSuite(object):
|
|||
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 Pubhash2addr(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 Privhex2pubhex(self,name,f1,f2,f3): # from Hex2wif
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue