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/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

View file

@ -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 = {

View file

@ -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())

View file

@ -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

View file

@ -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)

View file

@ -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):
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()

View file

@ -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,10 +279,10 @@ 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 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.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 Hex2wif(hexpriv,compressed=False):
Msg(g.proto.hex2wif(hexpriv,compressed))

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

View file

@ -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):

View file

@ -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

View file

@ -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))
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))

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',
'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,6 +447,14 @@ cfgs = {
'btc': ('9F2D D781 1812 8BAD','7410 8F95 4B33 B4B2'),
'ltc': ('B804 978A 8796 3ED4','93A6 844C 8ECC BEF4'),
},
'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': {
@ -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])):

View file

@ -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