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:
MMGen 2017-12-16 09:31:00 +03:00
commit bafea57134
Signed by untrusted user who does not match committer: mmgen
GPG key ID: 62DBE9E5212F05BE
22 changed files with 527 additions and 126 deletions

View file

@ -3,6 +3,10 @@ include doc/wiki/using-mmgen/*
include test/*.py include test/*.py
include test/ref/* include test/ref/*
include test/ref/litecoin/* 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/bitcoind-walletunlock.py
include scripts/compute-file-chksum.py include scripts/compute-file-chksum.py

View file

@ -27,27 +27,32 @@ from mmgen.obj import *
pnm = g.proj_name pnm = g.proj_name
def sc_dmsg(desc,data):
if os.getenv('MMGEN_DEBUG_ADDRLIST'):
Msg('sc_debug_{}: {}'.format(desc,data))
class AddrGenerator(MMGenObject): class AddrGenerator(MMGenObject):
def __new__(cls,atype): def __new__(cls,atype):
d = { d = {
'p2pkh': AddrGeneratorP2PKH, 'p2pkh': AddrGeneratorP2PKH,
'segwit': AddrGeneratorSegwit 'segwit': AddrGeneratorSegwit,
'ethereum': AddrGeneratorEthereum
} }
assert atype in d 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): class AddrGeneratorP2PKH(AddrGenerator):
desc = 'p2pkh'
def to_addr(self,pubhex): def to_addr(self,pubhex):
from mmgen.protocol import hash160 from mmgen.protocol import hash160
assert type(pubhex) == PubKey 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): def to_segwit_redeem_script(self,pubhex):
raise NotImplementedError raise NotImplementedError
class AddrGeneratorSegwit(AddrGenerator): class AddrGeneratorSegwit(AddrGenerator):
desc = 'segwit'
def to_addr(self,pubhex): def to_addr(self,pubhex):
assert pubhex.compressed assert pubhex.compressed
return CoinAddr(g.proto.pubhex2segwitaddr(pubhex)) return CoinAddr(g.proto.pubhex2segwitaddr(pubhex))
@ -56,6 +61,15 @@ class AddrGeneratorSegwit(AddrGenerator):
assert pubhex.compressed assert pubhex.compressed
return HexStr(g.proto.pubhex2redeem_script(pubhex)) 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): class KeyGenerator(MMGenObject):
def __new__(cls,generator=None,silent=False): def __new__(cls,generator=None,silent=False):
@ -161,8 +175,10 @@ class AddrListIDStr(unicode,Hilite):
if fmt_str: if fmt_str:
ret = fmt_str.format(s) ret = fmt_str.format(s)
else: else:
bc,mt = g.proto.base_coin,addrlist.al_id.mmtype bc = (g.proto.base_coin,g.coin)[g.proto.base_coin=='ETH']
ret = '{}{}{}[{}]'.format(addrlist.al_id.sid,('-'+bc,'')[bc=='BTC'],('-'+mt,'')[mt=='L'],s) 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) 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 gen_keys = False
has_keys = False has_keys = False
ext = 'addrs' 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) chksum_rec_f = lambda foo,e: (str(e.idx), e.addr)
def __init__(self,addrfile='',al_id='',adata=[],seed='',addr_idxs='',src='', def __init__(self,addrfile='',al_id='',adata=[],seed='',addr_idxs='',src='',
addrlist='',keylist='',mmtype=None,do_chksum=True,chksum_only=False): addrlist='',keylist='',mmtype=None,do_chksum=True,chksum_only=False):
self.update_msgs() self.update_msgs()
mmtype = mmtype or MMGenAddrType.dfl_mmtype mmtype = mmtype or g.proto.dfl_mmtype
assert mmtype in MMGenAddrType.mmtypes assert mmtype in MMGenAddrType.mmtypes
if seed and addr_idxs: # data from seed + idxs 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 assert type(addrnums) is AddrIdxList
seed = seed.get_data() 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: if self.gen_addrs:
kg = KeyGenerator() 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 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' is_btcfork = g.proto.base_coin == 'BTC'
if is_btcfork and self.al_id.mmtype == 'L': if is_btcfork and self.al_id.mmtype == 'L':
sc_dmsg('str','(none)')
return seed return seed
if g.proto.base_coin == 'ETH':
scramble_key = g.coin.lower()
else: else:
from mmgen.crypto import sha256_rounds scramble_key = (g.coin.lower()+':','')[is_btcfork] + self.al_id.mmtype.name
import hmac sc_dmsg('str',scramble_key)
key = (g.coin.lower()+':','')[is_btcfork] + self.al_id.mmtype.name from mmgen.crypto import scramble_seed
cseed = hmac.new(seed,key,sha256).digest() return scramble_seed(seed,scramble_key,self.scramble_hash_rounds)
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'): def encrypt(self,desc='new key list'):
from mmgen.crypto import mmgen_encrypt 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') out.append('# Record this value to a secure location.\n')
if type(self) == PasswordList: if type(self) == PasswordList:
out.append(u'{} {} {}:{} {{'.format( lbl = u'{} {} {}:{}'.format(self.al_id.sid,self.pw_id_str,self.pw_fmt,self.pw_len)
self.al_id.sid,self.pw_id_str,self.pw_fmt,self.pw_len))
else: else:
bc,mt = g.proto.base_coin,self.al_id.mmtype bc,mt = g.proto.base_coin,self.al_id.mmtype
lbl = ':'.join(([bc],[])[bc=='BTC']+([mt.name.upper()],[])[mt=='L']) l_coin = [] if bc == 'BTC' else [g.coin] if bc == 'ETH' else [bc]
out.append('{} {}{{'.format(self.al_id.sid,('',lbl+' ')[bool(lbl)])) 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)) fs = ' {:<%s} {:<34}{}' % len(str(self.data[-1].idx))
for e in self.data: 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]) return do_error("'%s': invalid Seed ID" % ls[0])
def parse_addrfile_label(lbl): # we must maintain backwards compat, so parse is tricky 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) lbl = lbl.split(':',1)
if len(lbl) == 2: if len(lbl) == 2:
al_base_coin = lbl[0] al_coin,al_mmtype = lbl[0],lbl[1].lower()
al_mmtype = lbl[1].lower()
else: else:
if lbl[0].lower() in MMGenAddrType.get_names(): if lbl[0].lower() in MMGenAddrType.get_names():
al_mmtype = lbl[0].lower() al_mmtype = lbl[0].lower()
else: else:
al_base_coin = lbl[0] al_coin = lbl[0]
# this block fails if al_mmtype is invalid for g.coin # this block fails if al_mmtype is invalid for g.coin
if not al_mmtype: if not al_mmtype:
mmtype = MMGenAddrType('L') mmtype = MMGenAddrType('E' if al_coin in ('ETH','ETC') else 'L')
else: else:
try: try:
mmtype = MMGenAddrType(al_mmtype) 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()]))) mmtype.upper(),' '.join([i['name'].upper() for i in MMGenAddrType.mmtypes.values()])))
from mmgen.protocol import CoinProtocol 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: 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 return base_coin,mmtype
def check_coin_mismatch(base_coin): # die if addrfile coin doesn't match g.coin 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 False
return True return True
def cook_seed(self,seed): def scramble_seed(self,seed):
from mmgen.crypto import sha256_rounds # Changing either pw_fmt, pw_len or scramble_key will cause a different,
# Changing either pw_fmt, pw_len or cook_str will cause a different,
# unrelated set of passwords to be generated: this is what we want. # unrelated set of passwords to be generated: this is what we want.
# NB: In original implementation, pw_id_str was 'baseN', not 'bN' # 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')) scramble_key = '{}:{}:{}'.format(self.pw_fmt,self.pw_len,self.pw_id_str.encode('utf8'))
dmsg(u'Full ID string: {}'.format(cook_str.decode('utf8'))) from mmgen.crypto import scramble_seed
# Original implementation was 'cseed = seed + cook_str'; hmac was not used return scramble_seed(seed,scramble_key,self.scramble_hash_rounds)
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)
class AddrData(MMGenObject): class AddrData(MMGenObject):
msgs = { msgs = {

View file

@ -80,7 +80,7 @@ opts_data = lambda: {
kgs=' '.join(['{}:{}'.format(n,k) for n,k in enumerate(g.key_generators,1)]), kgs=' '.join(['{}:{}'.format(n,k) for n,k in enumerate(g.key_generators,1)]),
kg=g.key_generator, 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]['name']) dmat="'{}' or '{}'".format(g.proto.dfl_mmtype,MAT.mmtypes[g.proto.dfl_mmtype]['name'])
), ),
'notes': """ 'notes': """
@ -118,7 +118,7 @@ FMT CODES:
cmd_args = opts.init(opts_data,add_opts=['b16'],opt_filter=opt_filter) cmd_args = opts.init(opts_data,add_opts=['b16'],opt_filter=opt_filter)
errmsg = "'{}': invalid parameter for --type option".format(opt.type) 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() if len(cmd_args) < 1: opts.usage()
idxs = AddrIdxList(fmt_str=cmd_args.pop()) idxs = AddrIdxList(fmt_str=cmd_args.pop())

View file

@ -32,7 +32,7 @@ cmd_help = """
Cryptocoin address/key operations (compressed public keys supported): Cryptocoin address/key operations (compressed public keys supported):
addr2hexaddr - convert coin address from base58 to hex format addr2hexaddr - convert coin address from base58 to hex format
hex2wif - convert a private key from hex to WIF 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 privhex2addr - generate coin address from private key in hex format
privhex2pubhex - generate a hex public key from a hex private key privhex2pubhex - generate a hex public key from a hex private key
pubhex2addr - convert a hex pubkey to an address pubhex2addr - convert a hex pubkey to an address

View file

@ -355,7 +355,6 @@ class LTCAmt(BTCAmt): max_amt = 84000000
class CoinAddr(str,Hilite,InitErrors,MMGenObject): class CoinAddr(str,Hilite,InitErrors,MMGenObject):
color = 'cyan' color = 'cyan'
width = 35 # max len of testnet p2sh addr
def __new__(cls,s,on_fail='die'): def __new__(cls,s,on_fail='die'):
if type(s) == cls: return s if type(s) == cls: return s
cls.arg_chk(cls,on_fail) cls.arg_chk(cls,on_fail)
@ -367,6 +366,7 @@ class CoinAddr(str,Hilite,InitErrors,MMGenObject):
assert va,'failed verification' assert va,'failed verification'
me.addr_fmt = va['format'] me.addr_fmt = va['format']
me.hex = va['hex'] me.hex = va['hex']
cls.width = va['width']
return me return me
except Exception as e: except Exception as e:
m = "{!r}: value cannot be converted to {} address ({})" m = "{!r}: value cannot be converted to {} address ({})"
@ -385,10 +385,17 @@ class CoinAddr(str,Hilite,InitErrors,MMGenObject):
def is_for_chain(self,chain): def is_for_chain(self,chain):
from mmgen.globalvars import g from mmgen.globalvars import g
vn = g.proto.get_protocol_by_chain(chain).addr_ver_num 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: 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: else:
return self[0] in vn[self.addr_fmt][1] return pfx_ok(vn[self.addr_fmt][1])
def is_in_tracking_wallet(self): def is_in_tracking_wallet(self):
from mmgen.rpc import rpc_init from mmgen.rpc import rpc_init
@ -427,7 +434,7 @@ class MMGenID(str,Hilite,InitErrors,MMGenObject):
try: try:
ss = str(s).split(':') ss = str(s).split(':')
assert len(ss) in (2,3),'not 2 or 3 colon-separated items' 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 = str.__new__(cls,'{}:{}:{}'.format(ss[0],t,ss[-1]))
me.sid = SeedID(sid=ss[0],on_fail='raise') me.sid = SeedID(sid=ss[0],on_fail='raise')
me.idx = AddrIdx(ss[-1],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() me.wif = me.towif()
return me return me
except Exception as e: 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) return cls.init_fail(fs.format(s,compressed,e),on_fail)
def towif(self): def towif(self):
@ -671,18 +678,22 @@ class MMGenAddrType(str,Hilite,InitErrors,MMGenObject):
'gen':'p2pkh', 'gen':'p2pkh',
'fmt':'p2pkh', 'fmt':'p2pkh',
'desc':'Legacy uncompressed address'}, 'desc':'Legacy uncompressed address'},
'C': { 'name':'compressed',
'comp':True,
'gen':'p2pkh',
'fmt':'p2pkh',
'desc':'Compressed P2PKH address'},
'S': { 'name':'segwit', 'S': { 'name':'segwit',
'comp':True, 'comp':True,
'gen':'segwit', 'gen':'segwit',
'fmt':'p2sh', 'fmt':'p2sh',
'desc':'Segwit P2SH-P2WPKH address' }, 'desc':'Segwit P2SH-P2WPKH address' },
'C': { 'name':'compressed', 'E': { 'name':'ethereum',
'comp':True, 'comp':False,
'gen':'p2pkh', 'gen':'ethereum',
'fmt':'p2pkh', 'fmt':'ethereum',
'desc':'Compressed P2PKH address'} 'desc':'Ethereum address' },
} }
dfl_mmtype = 'L'
def __new__(cls,s,on_fail='die',errmsg=None): def __new__(cls,s,on_fail='die',errmsg=None):
if type(s) == cls: return s if type(s) == cls: return s
cls.arg_chk(cls,on_fail) cls.arg_chk(cls,on_fail)

View file

@ -22,7 +22,7 @@ protocol.py: Coin protocol functions, classes and methods
import os,hashlib import os,hashlib
from binascii import unhexlify 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.obj import MMGenObject,BTCAmt,LTCAmt,BCHAmt,B2XAmt
from mmgen.globalvars import g from mmgen.globalvars import g
@ -53,12 +53,14 @@ def _b58tonum(b58num):
if not i in _b58a: return False 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])))
# chainparams.cpp
class BitcoinProtocol(MMGenObject): class BitcoinProtocol(MMGenObject):
name = 'bitcoin' name = 'bitcoin'
daemon_name = 'bitcoind' daemon_name = 'bitcoind'
addr_ver_num = { 'p2pkh': ('00','1'), 'p2sh': ('05','3') } # chainparams.cpp addr_ver_num = { 'p2pkh': ('00','1'), 'p2sh': ('05','3') }
privkey_pfx = '80' wif_ver_num = '80'
mmtypes = ('L','C','S') mmtypes = ('L','C','S')
dfl_mmtype = 'L'
data_subdir = '' data_subdir = ''
rpc_port = 8332 rpc_port = 8332
secs_per_block = 600 secs_per_block = 600
@ -74,7 +76,9 @@ class BitcoinProtocol(MMGenObject):
(None,'','b2x',True) (None,'','b2x',True)
] ]
caps = ('rbf','segwit') caps = ('rbf','segwit')
mmcaps = ('key','addr','rpc')
base_coin = 'BTC' base_coin = 'BTC'
addr_width = 34
@staticmethod @staticmethod
def get_protocol_by_chain(chain): def get_protocol_by_chain(chain):
@ -89,7 +93,7 @@ class BitcoinProtocol(MMGenObject):
@classmethod @classmethod
def hex2wif(cls,hexpriv,compressed=False): 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)) return _numtob58(int(s+hash256(s)[:8],16))
@classmethod @classmethod
@ -101,34 +105,39 @@ class BitcoinProtocol(MMGenObject):
compressed = len(key) == 76 compressed = len(key) == 76
if compressed and key[66:68] != '01': return False if compressed and key[66:68] != '01': return False
klen = (66,68)[compressed] 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 } return { 'hex':key[2:66], 'compressed':compressed }
else: else:
return False return False
@classmethod @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: for addr_fmt in cls.addr_ver_num:
ver_num,ldigit = cls.addr_ver_num[addr_fmt] ver_num,pfx = cls.addr_ver_num[addr_fmt]
if addr[0] not in ldigit: continue if type(pfx) == tuple:
if addr[0] not in pfx: continue
elif addr[:len(pfx)] != pfx: continue
num = _b58tonum(addr) num = _b58tonum(addr)
if num == False: break if num == False:
addr_hex = '{:050x}'.format(num) if g.debug: Msg('Address cannot be converted to base 58')
if addr_hex[:2] != ver_num: continue break
if hash256(addr_hex[:42])[:8] == addr_hex[42:]: 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 { return {
'hex': addr_hex[2:42], 'hex': addr_hex[len(ver_num):-8],
'format': {'p2pkh':'p2pkh','p2sh':'p2sh','p2sh2':'p2sh'}[addr_fmt], 'format': {'p2pkh':'p2pkh','p2sh':'p2sh','p2sh2':'p2sh'}[addr_fmt],
'width': cls.addr_width
} if return_dict else True } if return_dict else True
else: else:
if verbose: Msg("Invalid checksum in address '{}'".format(addr)) if g.debug: Msg('Invalid checksum in address')
break break
if verbose: Msg("Invalid address '{}'".format(addr)) if g.debug: Msg("Invalid address '{}'".format(addr))
return False return False
@classmethod @classmethod
def hexaddr2addr(cls,hexaddr,p2sh): def pubhash2addr(cls,pubkey_hash,p2sh):
s = cls.addr_ver_num[('p2pkh','p2sh')[p2sh]][0] + hexaddr 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) 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)) return ('1' * lzeroes) + _numtob58(int(s+hash256(s)[:8],16))
@ -142,14 +151,15 @@ class BitcoinProtocol(MMGenObject):
@classmethod @classmethod
def pubhex2segwitaddr(cls,pubhex): 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): class BitcoinTestnetProtocol(BitcoinProtocol):
addr_ver_num = { 'p2pkh': ('6f','mn'), 'p2sh': ('c4','2') } addr_ver_num = { 'p2pkh': ('6f',('m','n')), 'p2sh': ('c4','2') }
privkey_pfx = 'ef' wif_ver_num = 'ef'
data_subdir = 'testnet' data_subdir = 'testnet'
daemon_data_subdir = 'testnet3' daemon_data_subdir = 'testnet3'
rpc_port = 18332 rpc_port = 18332
addr_width = 35
class BitcoinCashProtocol(BitcoinProtocol): class BitcoinCashProtocol(BitcoinProtocol):
# TODO: assumes MSWin user installs in custom dir 'Bitcoin_ABC' # TODO: assumes MSWin user installs in custom dir 'Bitcoin_ABC'
@ -173,10 +183,11 @@ class BitcoinCashProtocol(BitcoinProtocol):
class BitcoinCashTestnetProtocol(BitcoinCashProtocol): class BitcoinCashTestnetProtocol(BitcoinCashProtocol):
rpc_port = 18442 rpc_port = 18442
addr_ver_num = { 'p2pkh': ('6f','mn'), 'p2sh': ('c4','2') } addr_ver_num = { 'p2pkh': ('6f',('m','n')), 'p2sh': ('c4','2') }
privkey_pfx = 'ef' wif_ver_num = 'ef'
data_subdir = 'testnet' data_subdir = 'testnet'
daemon_data_subdir = 'testnet3' daemon_data_subdir = 'testnet3'
addr_width = 35
class B2XProtocol(BitcoinProtocol): class B2XProtocol(BitcoinProtocol):
daemon_name = 'bitcoind-2x' daemon_name = 'bitcoind-2x'
@ -190,11 +201,12 @@ class B2XProtocol(BitcoinProtocol):
] ]
class B2XTestnetProtocol(B2XProtocol): class B2XTestnetProtocol(B2XProtocol):
addr_ver_num = { 'p2pkh': ('6f','mn'), 'p2sh': ('c4','2') } addr_ver_num = { 'p2pkh': ('6f',('m','n')), 'p2sh': ('c4','2') }
privkey_pfx = 'ef' wif_ver_num = 'ef'
data_subdir = 'testnet' data_subdir = 'testnet'
daemon_data_subdir = 'testnet5' daemon_data_subdir = 'testnet5'
rpc_port = 18338 rpc_port = 18338
addr_width = 35
class LitecoinProtocol(BitcoinProtocol): class LitecoinProtocol(BitcoinProtocol):
block0 = '12a765e31ffd4059bada1e25190f6e98c99d9714d334efa41a195a7e7e04bfe2' block0 = '12a765e31ffd4059bada1e25190f6e98c99d9714d334efa41a195a7e7e04bfe2'
@ -203,7 +215,7 @@ class LitecoinProtocol(BitcoinProtocol):
daemon_data_dir = os.path.join(os.getenv('APPDATA'),'Litecoin') if g.platform == 'win' \ daemon_data_dir = os.path.join(os.getenv('APPDATA'),'Litecoin') if g.platform == 'win' \
else os.path.join(g.home_dir,'.litecoin') else os.path.join(g.home_dir,'.litecoin')
addr_ver_num = { 'p2pkh': ('30','L'), 'p2sh': ('32','M'), 'p2sh2': ('05','3') } # 'p2sh' is new fmt 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 secs_per_block = 150
rpc_port = 9332 rpc_port = 9332
coin_amt = LTCAmt coin_amt = LTCAmt
@ -213,16 +225,68 @@ class LitecoinProtocol(BitcoinProtocol):
class LitecoinTestnetProtocol(LitecoinProtocol): class LitecoinTestnetProtocol(LitecoinProtocol):
# addr ver nums same as Bitcoin testnet, except for 'p2sh' # addr ver nums same as Bitcoin testnet, except for 'p2sh'
addr_ver_num = { 'p2pkh': ('6f','mn'), 'p2sh': ('3a','Q'), 'p2sh2': ('c4','2') } addr_ver_num = { 'p2pkh': ('6f',('m','n')), 'p2sh': ('3a','Q'), 'p2sh2': ('c4','2') }
privkey_pfx = 'ef' # same as Bitcoin testnet wif_ver_num = 'ef' # same as Bitcoin testnet
data_subdir = 'testnet' data_subdir = 'testnet'
daemon_data_subdir = 'testnet4' daemon_data_subdir = 'testnet4'
rpc_port = 19332 rpc_port = 19332
addr_width = 35
class EthereumProtocol(MMGenObject): class BitcoinProtocolAddrgen(BitcoinProtocol): mmcaps = ('key','addr')
base_coin = 'ETH' 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 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): class CoinProtocol(MMGenObject):
coins = { coins = {
@ -230,7 +294,10 @@ class CoinProtocol(MMGenObject):
'bch': (BitcoinCashProtocol,BitcoinCashTestnetProtocol), 'bch': (BitcoinCashProtocol,BitcoinCashTestnetProtocol),
'b2x': (B2XProtocol,B2XTestnetProtocol), 'b2x': (B2XProtocol,B2XTestnetProtocol),
'ltc': (LitecoinProtocol,LitecoinTestnetProtocol), 'ltc': (LitecoinProtocol,LitecoinTestnetProtocol),
# 'eth': (EthereumProtocol,EthereumTestnetProtocol), 'dash': (DashProtocol,DashTestnetProtocol),
'zec': (ZcashProtocol,ZcashTestnetProtocol),
'eth': (EthereumProtocol,EthereumTestnetProtocol),
'etc': (EthereumClassicProtocol,EthereumClassicTestnetProtocol),
} }
def __new__(cls,coin,testnet): def __new__(cls,coin,testnet):
coin = coin.lower() coin = coin.lower()

View file

@ -61,7 +61,7 @@ cmd_data = OrderedDict([
('Wif2hex', ['<wif> [str-]']), ('Wif2hex', ['<wif> [str-]']),
('Wif2addr', ['<wif> [str-]','segwit [bool=False]']), ('Wif2addr', ['<wif> [str-]','segwit [bool=False]']),
('Wif2segwit_pair',['<wif> [str-]']), ('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-]']), ('Addr2hexaddr', ['<coin address> [str-]']),
('Privhex2addr', ['<private key in hex format> [str-]','compressed [bool=False]','segwit [bool=False]']), ('Privhex2addr', ['<private key in hex format> [str-]','compressed [bool=False]','segwit [bool=False]']),
('Privhex2pubhex',['<private key in hex format> [str-]','compressed [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) rs = ag.to_segwit_redeem_script(pubhex)
Msg('{}\n{}'.format(rs,addr)) Msg('{}\n{}'.format(rs,addr))
def Hexaddr2addr(hexaddr,p2sh=False): Msg(g.proto.hexaddr2addr(hexaddr,p2sh=p2sh)) 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 Addr2hexaddr(addr): Msg(g.proto.verify_addr(addr,return_dict=True)['hex'])
def Hash160(pubkeyhex): Msg(hash160(pubkeyhex)) def Hash160(pubkeyhex): Msg(hash160(pubkeyhex))
def Pubhex2addr(pubkeyhex,p2sh=False): Msg(g.proto.hexaddr2addr(hash160(pubkeyhex),p2sh=p2sh)) def Pubhex2addr(pubkeyhex,p2sh=False): Msg(g.proto.pubhash2addr(hash160(pubkeyhex),p2sh=p2sh))
def Wif2hex(wif): Msg(wif2hex(wif)) def Wif2hex(wif): Msg(wif2hex(wif))
def Hex2wif(hexpriv,compressed=False): def Hex2wif(hexpriv,compressed=False):
Msg(g.proto.hex2wif(hexpriv,compressed)) Msg(g.proto.hex2wif(hexpriv,compressed))
def Privhex2addr(privhex,compressed=False,segwit=False,output_pubhex=False): def Privhex2addr(privhex,compressed=False,segwit=False,output_pubhex=False):

View file

@ -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 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 elif len(s) == 46 and s[:4] == 'a914' and s[-2:] == '87': addr_hex,p2sh = s[4:-2],True
else: raise NotImplementedError,'Unknown scriptPubKey' else: raise NotImplementedError,'Unknown scriptPubKey'
return g.proto.hexaddr2addr(addr_hex,p2sh) return g.proto.pubhash2addr(addr_hex,p2sh)
from collections import OrderedDict from collections import OrderedDict
class DeserializedTX(OrderedDict,MMGenObject): # need to add MMGen types class DeserializedTX(OrderedDict,MMGenObject): # need to add MMGen types

View file

@ -799,6 +799,9 @@ def get_coin_daemon_auth_cookie():
def rpc_init(reinit=False): 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 if g.rpch != None and not reinit: return g.rpch
def check_chainfork_mismatch(conn): def check_chainfork_mismatch(conn):

View file

@ -2,7 +2,7 @@
# Tested on Linux, MinGW-64 # Tested on Linux, MinGW-64
# MinGW's bash 3.1.17 doesn't do ${var^^} # 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) PROGNAME=$(basename $0)
while getopts hinPt OPT while getopts hinPt OPT
do do
@ -16,14 +16,15 @@ do
echo " '-t' Print the tests without running them" echo " '-t' Print the tests without running them"
echo " AVAILABLE TESTS:" echo " AVAILABLE TESTS:"
echo " obj - data objects" echo " obj - data objects"
echo " alts - operations for all supported gen-only altcoins"
echo " misc - miscellaneous operations" echo " misc - miscellaneous operations"
echo " btc - bitcoin" echo " btc - bitcoin"
echo " btc_tn - bitcoin testnet" echo " btc_tn - bitcoin testnet"
echo " btc_rt - bitcoin regtest" echo " btc_rt - bitcoin regtest"
echo " bch - bitcoin cash (BCH)" echo " bch - bitcoin cash (BCH)"
echo " bch_rt - bitcoin cash (BCH) regtest" echo " bch_rt - bitcoin cash (BCH) regtest"
echo " b2x - bitcoin 2x (B2X)" # echo " b2x - bitcoin 2x (B2X)"
echo " b2x_rt - bitcoin 2x (B2X) regtest" # echo " b2x_rt - bitcoin 2x (B2X) regtest"
echo " ltc - litecoin" echo " ltc - litecoin"
echo " ltc_tn - litecoin testnet" echo " ltc_tn - litecoin testnet"
echo " ltc_rt - litecoin regtest" echo " ltc_rt - litecoin regtest"
@ -86,7 +87,7 @@ do_test() {
[ "$TESTING" ] || eval "$i" || { echo -e $RED'Test failed!'$RESET; exit; } [ "$TESTING" ] || eval "$i" || { echo -e $RED'Test failed!'$RESET; exit; }
done done
} }
i_obj='Data objects' i_obj='Data object'
s_obj='Testing data objects' s_obj='Testing data objects'
t_obj=( t_obj=(
'test/objtest.py --coin=btc -S' 'test/objtest.py --coin=btc -S'
@ -95,6 +96,20 @@ t_obj=(
'test/objtest.py --coin=ltc --testnet=1 -S') 'test/objtest.py --coin=ltc --testnet=1 -S')
f_obj='Data object test complete' 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! i_misc='Miscellaneous operations' # includes autosign!
s_misc='The bitcoin, bitcoin-abc and litecoin (mainnet) daemons must be running for the following tests' s_misc='The bitcoin, bitcoin-abc and litecoin (mainnet) daemons must be running for the following tests'
t_misc=( t_misc=(
@ -125,7 +140,7 @@ s_btc_rt="The following tests will test MMGen's regtest (Bob and Alice) mode"
t_btc_rt=( t_btc_rt=(
'test/test.py -On regtest' 'test/test.py -On regtest'
'test/test.py -On regtest_split') '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)' i_bch='Bitcoin cash (BCH)'
s_bch='The bitcoin cash daemon (Bitcoin ABC) must both be running for the following tests' 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' i_bch_rt='Bitcoin cash (BCH) regtest'
s_bch_rt="The following tests will test MMGen's regtest (Bob and Alice) mode" 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') 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)' i_b2x='Bitcoin 2X (B2X)'
s_b2x='The bitcoin 2X daemon (BTC1) must both be running for the following tests' 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' i_b2x_rt='Bitcoin 2X (B2X) regtest'
s_b2x_rt="The following tests will test MMGen's regtest (Bob and Alice) mode" 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') 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' i_ltc='Litecoin'
s_ltc='The litecoin daemon must both be running for the following tests' 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' i_ltc_rt='Litecoin regtest'
s_ltc_rt="The following tests will test MMGen's regtest (Bob and Alice) mode" 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') 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' i_tool='Tooltest'
s_tool='The following tests will run test/tooltest.py for all supported coins' s_tool='The following tests will run test/tooltest.py for all supported coins'
t_tool=( t_tool=(
@ -180,8 +196,14 @@ t_tool=(
'test/tooltest.py --coin=ltc util' 'test/tooltest.py --coin=ltc util'
'test/tooltest.py --coin=ltc cryptocoin' 'test/tooltest.py --coin=ltc cryptocoin'
'test/tooltest.py --coin=ltc mnemonic' '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' i_gen='Gentest'
s_gen='The following tests will run test/gentest.py on mainnet and testnet for all supported coins' 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 1:2 10'
'test/gentest.py -q --coin=ltc --testnet=1 --segwit 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" ] && { [ -d .git -a -z "$NO_INSTALL" -a -z "$TESTING" ] && {
check check

View file

@ -53,10 +53,16 @@ opts_data = lambda: {
EXAMPLES: EXAMPLES:
{prog} 1:2 100 {prog} 1:2 100
(compare output of native Python ECDSA with secp256k1 library, 100 rounds) (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 {prog} 2 1000
(test speed of secp256k1 library address generation, 1000 rounds) (test speed of secp256k1 library address generation, 1000 rounds)
{prog} 2 my.dump {prog} 2 my.dump
(compare addrs generated with secp256k1 library to {dn} wallet 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) """.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() 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 urounds,fh = None,None
dump = [] dump = []
if len(cmd_args) == 2: if len(cmd_args) == 2:
@ -97,8 +120,27 @@ except:
die(1,"First argument must be one or two generator IDs, colon separated") die(1,"First argument must be one or two generator IDs, colon separated")
else: else:
try: try:
a,b = int(a),int(b) a = int(a)
for i in (a,b): assert 1 <= i <= len(g.key_generators) 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 assert a != b
except: except:
die(1,"%s: invalid generator IDs" % cmd_args[0]) 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 : {} WIF key : {}
{a:10}: {} {a:10}: {}
{b: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 # Begin execution
compressed = True compressed = False if g.coin in ('ETH','ETC') else True
from mmgen.addr import KeyGenerator,AddrGenerator from mmgen.addr import KeyGenerator,AddrGenerator
from mmgen.obj import PrivKey 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: if a and b:
m = "Comparing address generators '{}' and '{}'" m = "Comparing address generators '{}' and '{}' for coin {}"
qmsg(green(m.format(g.key_generators[a-1],g.key_generators[b-1]))) 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() last_t = time.time()
kg_a = KeyGenerator(a) kg_a = KeyGenerator(a)
kg_b = KeyGenerator(b) if b != 'ext': kg_b = KeyGenerator(b)
for i in range(rounds): 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)) qmsg_r('\rRound %s/%s ' % (i+1,rounds))
last_t = time.time() last_t = time.time()
sec = PrivKey(os.urandom(32),compressed) sec = PrivKey(os.urandom(32),compressed)
a_addr = ag.to_addr(kg_a.to_pubhex(sec)) 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)) vmsg('\nkey: %s\naddr: %s\n' % (sec.wif,a_addr))
if a_addr != b_addr: if a_addr != b_addr:
match_error(sec,sec.wif,a_addr,b_addr,a,b) match_error(sec,sec.wif,a_addr,b_addr,a,ext_lib if b == 'ext' else b)
if not opt.segwit: if not opt.segwit and 'L' in g.proto.mmtypes:
compressed = not compressed compressed = not compressed
qmsg_r('\rRound %s/%s ' % (i+1,rounds)) qmsg_r('\rRound %s/%s ' % (i+1,rounds))
qmsg(green(('\n','')[bool(opt.verbose)] + 'OK')) qmsg(green(('\n','')[bool(opt.verbose)] + 'OK'))
elif a and not fh: elif a and not fh:
m = "Testing speed of address generator '{}'" m = "Testing speed of address generator '{}' for coin {}"
qmsg(green(m.format(g.key_generators[a-1]))) qmsg(green(m.format(g.key_generators[a-1],g.coin)))
from struct import pack,unpack from struct import pack,unpack
seed = os.urandom(28) seed = os.urandom(28)
print 'Incrementing key with each round' print 'Incrementing key with each round'
@ -160,7 +207,7 @@ elif a and not fh:
sec = PrivKey(seed+pack('I',i),compressed) sec = PrivKey(seed+pack('I',i),compressed)
a_addr = ag.to_addr(kg.to_pubhex(sec)) a_addr = ag.to_addr(kg.to_pubhex(sec))
vmsg('\nkey: %s\naddr: %s\n' % (sec.wif,a_addr)) 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 compressed = not compressed
qmsg_r('\rRound %s/%s ' % (i+1,rounds)) qmsg_r('\rRound %s/%s ' % (i+1,rounds))

View 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 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
}

View 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 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
}

View 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 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
}

View 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 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
}

103
test/scrambletest.py Executable file
View 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)))

View file

@ -431,9 +431,9 @@ cfgs = {
'passfile32_chk': 'F6C1 CDFB 97D9 FCAE', 'passfile32_chk': 'F6C1 CDFB 97D9 FCAE',
'wpasswd': 'reference password', 'wpasswd': 'reference password',
'ref_wallet': '98831F3A-{}[256,1].mmdat'.format(('27F2BF93','E2687906')[g.testnet]), '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_addrfile': '98831F3A{}[1,31-33,500-501,1010-1011]{}.addrs',
'ref_segwitaddrfile':'98831F3A{}-S[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',
'ref_keyaddrfile': '98831F3A{}[1,31-33,500-501,1010-1011]{}.akeys.mmenc'.format(altcoin_pfx,tn_ext), '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_passwdfile': '98831F3A-фубар@crypto.org-b58-20[1,4,9-11,1100].pws',
'ref_addrfile_chksum': { 'ref_addrfile_chksum': {
'btc': ('6FEF 6FB9 7B13 5D91','3C2C 8558 BB54 079E'), 'btc': ('6FEF 6FB9 7B13 5D91','3C2C 8558 BB54 079E'),
@ -447,7 +447,15 @@ cfgs = {
'btc': ('9F2D D781 1812 8BAD','7410 8F95 4B33 B4B2'), 'btc': ('9F2D D781 1812 8BAD','7410 8F95 4B33 B4B2'),
'ltc': ('B804 978A 8796 3ED4','93A6 844C 8ECC BEF4'), '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_fake_unspent_data':'98831F3A_unspent.json',
'ref_tx_file': { 'ref_tx_file': {
'btc': 'FFB367[1.234]{}.rawtx', 'btc': 'FFB367[1.234]{}.rawtx',
@ -700,6 +708,17 @@ cmd_group['misc'] = (
('autosign', 'transaction autosigning (BTC,BCH,LTC)'), ('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 # undocumented admin cmds
cmd_group_admin = OrderedDict() cmd_group_admin = OrderedDict()
cmd_group_admin['create_ref_tx'] = ( cmd_group_admin['create_ref_tx'] = (
@ -778,6 +797,11 @@ for a,b in cmd_group['misc']:
cmd_list['misc'].append(a) cmd_list['misc'].append(a)
cmd_data[a] = (18,b,[[[],18]]) 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 = { utils = {
'check_deps': 'check dependencies for specified command', 'check_deps': 'check dependencies for specified command',
'clean': 'clean specified tmp dir(s) 1,2,3,4,5 or 6 (no arg = all dirs)', '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() t.close()
cmp_or_die(cfg['seed_id'],chk) 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_key = 'ref_{}file'.format(ftype)
af = os.path.join(ref_dir,(ref_subdir,'')[ftype=='passwd'],cfg[af_key]) af_fn = cfg[af_key].format(pfx or altcoin_pfx,'' if coin else tn_ext)
t = MMGenExpect(name,'mmgen-tool',[ftype.replace('segwit','')+'file_chksum',af]) 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': if ftype == 'keyaddr':
w = 'key-address data' w = 'key-address data'
t.hash_preset(w,ref_kafile_hash_preset) t.hash_preset(w,ref_kafile_hash_preset)
t.passphrase(w,ref_kafile_pass) t.passphrase(w,ref_kafile_pass)
t.expect('Check key-to-address validity? (y/N): ','y') t.expect('Check key-to-address validity? (y/N): ','y')
o = t.read().strip().split('\n')[-1] o = t.read().strip().split('\n')[-1]
rc = cfg['ref_'+ftype+'file_chksum'] rc = cfg[ 'ref_' + ftype + 'file_chksum' +
ref_chksum = rc if ftype == 'passwd' else rc[g.proto.base_coin.lower()][g.testnet] ('_'+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) 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): def ref_keyaddrfile_chk(self,name):
self.ref_addrfile_chk(name,ftype='keyaddr') self.ref_addrfile_chk(name,ftype='keyaddr')
@ -2597,7 +2649,7 @@ class MMGenTestSuite(object):
g.proto = psave g.proto = psave
for k in ('in_addrs','out_addrs'): 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 k in ('in','out'):
for i in range(len(tx[k])): for i in range(len(tx[k])):

View file

@ -71,7 +71,7 @@ cmd_data = OrderedDict([
('Privhex2addr', ('Wif2hex','o3')), # compare with output of Randpair ('Privhex2addr', ('Wif2hex','o3')), # compare with output of Randpair
('Hex2wif', ('Wif2hex','io2')), ('Hex2wif', ('Wif2hex','io2')),
('Addr2hexaddr', ('Randpair','o2')), ('Addr2hexaddr', ('Randpair','o2')),
('Hexaddr2addr', ('Addr2hexaddr','io2')), ('Pubhash2addr', ('Addr2hexaddr','io2')),
('Pipetest', ('Randpair','o3')), ('Pipetest', ('Randpair','o3')),
]) ])
@ -366,7 +366,7 @@ class MMGenToolTestSuite(object):
for n,f,m in ((1,f1,''),(2,f2,'from compressed')): for n,f,m in ((1,f1,''),(2,f2,'from compressed')):
addr = read_from_file(f).split()[-1] addr = read_from_file(f).split()[-1]
self.run_cmd_out(name,addr,fn_idx=n,extra_msg=m) 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')): for n,fi,fo,m in ((1,f1,f2,''),(2,f3,f4,'from compressed')):
self.run_cmd_chk(name,fi,fo,extra_msg=m) self.run_cmd_chk(name,fi,fo,extra_msg=m)
def Privhex2pubhex(self,name,f1,f2,f3): # from Hex2wif def Privhex2pubhex(self,name,f1,f2,f3): # from Hex2wif