diff --git a/MANIFEST.in b/MANIFEST.in index a6aaabd2..96e68f70 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -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 diff --git a/mmgen/addr.py b/mmgen/addr.py index ff0ec8cb..eb5a69b4 100755 --- a/mmgen/addr.py +++ b/mmgen/addr.py @@ -27,27 +27,32 @@ from mmgen.obj import * pnm = g.proj_name +def sc_dmsg(desc,data): + if os.getenv('MMGEN_DEBUG_ADDRLIST'): + Msg('sc_debug_{}: {}'.format(desc,data)) + class AddrGenerator(MMGenObject): def __new__(cls,atype): d = { 'p2pkh': AddrGeneratorP2PKH, - 'segwit': AddrGeneratorSegwit + 'segwit': AddrGeneratorSegwit, + 'ethereum': AddrGeneratorEthereum } assert atype in d - return super(cls,cls).__new__(d[atype]) + me = super(cls,cls).__new__(d[atype]) + me.desc = d + return me class AddrGeneratorP2PKH(AddrGenerator): - desc = 'p2pkh' def to_addr(self,pubhex): from mmgen.protocol import hash160 assert type(pubhex) == PubKey - return CoinAddr(g.proto.hexaddr2addr(hash160(pubhex),p2sh=False)) + return CoinAddr(g.proto.pubhash2addr(hash160(pubhex),p2sh=False)) def to_segwit_redeem_script(self,pubhex): raise NotImplementedError class AddrGeneratorSegwit(AddrGenerator): - desc = 'segwit' def to_addr(self,pubhex): assert pubhex.compressed return CoinAddr(g.proto.pubhex2segwitaddr(pubhex)) @@ -56,6 +61,15 @@ class AddrGeneratorSegwit(AddrGenerator): assert pubhex.compressed return HexStr(g.proto.pubhex2redeem_script(pubhex)) +class AddrGeneratorEthereum(AddrGenerator): + def to_addr(self,pubhex): + assert type(pubhex) == PubKey + import sha3 + return CoinAddr(sha3.keccak_256(pubhex[2:].decode('hex')).digest()[12:].encode('hex')) + + def to_segwit_redeem_script(self,pubhex): + raise NotImplementedError + class KeyGenerator(MMGenObject): def __new__(cls,generator=None,silent=False): @@ -161,8 +175,10 @@ class AddrListIDStr(unicode,Hilite): if fmt_str: ret = fmt_str.format(s) else: - bc,mt = g.proto.base_coin,addrlist.al_id.mmtype - ret = '{}{}{}[{}]'.format(addrlist.al_id.sid,('-'+bc,'')[bc=='BTC'],('-'+mt,'')[mt=='L'],s) + bc = (g.proto.base_coin,g.coin)[g.proto.base_coin=='ETH'] + mt = addrlist.al_id.mmtype + ret = '{}{}{}[{}]'.format(addrlist.al_id.sid,('-'+bc,'')[bc=='BTC'],('-'+mt,'')[mt in ('L','E')],s) + sc_dmsg('id_str',ret[8:].split('[')[0]) return unicode.__new__(cls,ret) @@ -196,14 +212,14 @@ Removed %s duplicate WIF key%s from keylist (also in {pnm} key-address file gen_keys = False has_keys = False ext = 'addrs' - cook_hash_rounds = 10 # not too many rounds, so hand decoding can still be feasible + scramble_hash_rounds = 10 # not too many rounds, so hand decoding can still be feasible chksum_rec_f = lambda foo,e: (str(e.idx), e.addr) def __init__(self,addrfile='',al_id='',adata=[],seed='',addr_idxs='',src='', addrlist='',keylist='',mmtype=None,do_chksum=True,chksum_only=False): self.update_msgs() - mmtype = mmtype or MMGenAddrType.dfl_mmtype + mmtype = mmtype or g.proto.dfl_mmtype assert mmtype in MMGenAddrType.mmtypes if seed and addr_idxs: # data from seed + idxs @@ -255,7 +271,8 @@ Removed %s duplicate WIF key%s from keylist (also in {pnm} key-address file assert type(addrnums) is AddrIdxList seed = seed.get_data() - seed = self.cook_seed(seed) + seed = self.scramble_seed(seed) + sc_dmsg('seed',seed[:8].encode('hex')) if self.gen_addrs: kg = KeyGenerator() @@ -296,17 +313,18 @@ Removed %s duplicate WIF key%s from keylist (also in {pnm} key-address file def check_format(self,addr): return True # format is checked when added to list entry object - def cook_seed(self,seed): + def scramble_seed(self,seed): is_btcfork = g.proto.base_coin == 'BTC' if is_btcfork and self.al_id.mmtype == 'L': + sc_dmsg('str','(none)') return seed + if g.proto.base_coin == 'ETH': + scramble_key = g.coin.lower() else: - from mmgen.crypto import sha256_rounds - import hmac - key = (g.coin.lower()+':','')[is_btcfork] + self.al_id.mmtype.name - cseed = hmac.new(seed,key,sha256).digest() - dmsg('Seed: {}\nKey: {}\nCseed: {}\nCseed len: {}'.format(hexlify(seed),key,hexlify(cseed),len(cseed))) - return sha256_rounds(cseed,self.cook_hash_rounds) + scramble_key = (g.coin.lower()+':','')[is_btcfork] + self.al_id.mmtype.name + sc_dmsg('str',scramble_key) + from mmgen.crypto import scramble_seed + return scramble_seed(seed,scramble_key,self.scramble_hash_rounds) def encrypt(self,desc='new key list'): from mmgen.crypto import mmgen_encrypt @@ -397,12 +415,16 @@ Removed %s duplicate WIF key%s from keylist (also in {pnm} key-address file out.append('# Record this value to a secure location.\n') if type(self) == PasswordList: - out.append(u'{} {} {}:{} {{'.format( - self.al_id.sid,self.pw_id_str,self.pw_fmt,self.pw_len)) + lbl = u'{} {} {}:{}'.format(self.al_id.sid,self.pw_id_str,self.pw_fmt,self.pw_len) else: bc,mt = g.proto.base_coin,self.al_id.mmtype - lbl = ':'.join(([bc],[])[bc=='BTC']+([mt.name.upper()],[])[mt=='L']) - out.append('{} {}{{'.format(self.al_id.sid,('',lbl+' ')[bool(lbl)])) + l_coin = [] if bc == 'BTC' else [g.coin] if bc == 'ETH' else [bc] + l_type = [] if mt in ('L','E') else [mt.name.upper()] + lbl_p2 = ':'.join(l_coin+l_type) + lbl = self.al_id.sid + ('',' ')[bool(lbl_p2)] + lbl_p2 + + sc_dmsg('lbl',lbl[9:]) + out.append(u'{} {{'.format(lbl)) fs = ' {:<%s} {:<34}{}' % len(str(self.data[-1].idx)) for e in self.data: @@ -492,20 +514,19 @@ Removed %s duplicate WIF key%s from keylist (also in {pnm} key-address file return do_error("'%s': invalid Seed ID" % ls[0]) def parse_addrfile_label(lbl): # we must maintain backwards compat, so parse is tricky - al_base_coin,al_mmtype = None,None + al_coin,al_mmtype = None,None lbl = lbl.split(':',1) if len(lbl) == 2: - al_base_coin = lbl[0] - al_mmtype = lbl[1].lower() + al_coin,al_mmtype = lbl[0],lbl[1].lower() else: if lbl[0].lower() in MMGenAddrType.get_names(): al_mmtype = lbl[0].lower() else: - al_base_coin = lbl[0] + al_coin = lbl[0] # this block fails if al_mmtype is invalid for g.coin if not al_mmtype: - mmtype = MMGenAddrType('L') + mmtype = MMGenAddrType('E' if al_coin in ('ETH','ETC') else 'L') else: try: mmtype = MMGenAddrType(al_mmtype) @@ -514,9 +535,9 @@ Removed %s duplicate WIF key%s from keylist (also in {pnm} key-address file mmtype.upper(),' '.join([i['name'].upper() for i in MMGenAddrType.mmtypes.values()]))) from mmgen.protocol import CoinProtocol - base_coin = CoinProtocol(al_base_coin or 'BTC',testnet=False).base_coin + base_coin = CoinProtocol(al_coin or 'BTC',testnet=False).base_coin if not base_coin: - die(2,"'{}': unknown base coin in address file label!".format(al_base_coin)) + die(2,"'{}': unknown base coin in address file label!".format(al_coin)) return base_coin,mmtype def check_coin_mismatch(base_coin): # die if addrfile coin doesn't match g.coin @@ -686,18 +707,13 @@ Record this checksum: it will be used to verify the password file in the future return False return True - def cook_seed(self,seed): - from mmgen.crypto import sha256_rounds - # Changing either pw_fmt, pw_len or cook_str will cause a different, + def scramble_seed(self,seed): + # Changing either pw_fmt, pw_len or scramble_key will cause a different, # unrelated set of passwords to be generated: this is what we want. # NB: In original implementation, pw_id_str was 'baseN', not 'bN' - cook_str = '{}:{}:{}'.format(self.pw_fmt,self.pw_len,self.pw_id_str.encode('utf8')) - dmsg(u'Full ID string: {}'.format(cook_str.decode('utf8'))) - # Original implementation was 'cseed = seed + cook_str'; hmac was not used - import hmac - cseed = hmac.new(seed,cook_str,sha256).digest() - dmsg('Seed: {}\nCooked seed: {}\nCooked seed len: {}'.format(hexlify(seed),hexlify(cseed),len(cseed))) - return sha256_rounds(cseed,self.cook_hash_rounds) + scramble_key = '{}:{}:{}'.format(self.pw_fmt,self.pw_len,self.pw_id_str.encode('utf8')) + from mmgen.crypto import scramble_seed + return scramble_seed(seed,scramble_key,self.scramble_hash_rounds) class AddrData(MMGenObject): msgs = { diff --git a/mmgen/main_addrgen.py b/mmgen/main_addrgen.py index 9827ff8d..71c5e7a4 100755 --- a/mmgen/main_addrgen.py +++ b/mmgen/main_addrgen.py @@ -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()) diff --git a/mmgen/main_tool.py b/mmgen/main_tool.py index 76614fde..c302e7a7 100755 --- a/mmgen/main_tool.py +++ b/mmgen/main_tool.py @@ -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 diff --git a/mmgen/obj.py b/mmgen/obj.py index 17e578f6..702d381e 100755 --- a/mmgen/obj.py +++ b/mmgen/obj.py @@ -355,7 +355,6 @@ class LTCAmt(BTCAmt): max_amt = 84000000 class CoinAddr(str,Hilite,InitErrors,MMGenObject): color = 'cyan' - width = 35 # max len of testnet p2sh addr def __new__(cls,s,on_fail='die'): if type(s) == cls: return s cls.arg_chk(cls,on_fail) @@ -367,6 +366,7 @@ class CoinAddr(str,Hilite,InitErrors,MMGenObject): assert va,'failed verification' me.addr_fmt = va['format'] me.hex = va['hex'] + cls.width = va['width'] return me except Exception as e: m = "{!r}: value cannot be converted to {} address ({})" @@ -385,10 +385,17 @@ class CoinAddr(str,Hilite,InitErrors,MMGenObject): def is_for_chain(self,chain): from mmgen.globalvars import g vn = g.proto.get_protocol_by_chain(chain).addr_ver_num + + def pfx_ok(pfx): + if type(pfx) == tuple: + if self[0] in pfx: return True + elif self[:len(pfx)] == pfx: return True + return False + if self.addr_fmt == 'p2sh' and 'p2sh2' in vn: - return self[0] in vn['p2sh'][1] or self[0] in vn['p2sh2'][1] + return pfx_ok(vn['p2sh'][1]) or pfx_ok(vn['p2sh2'][1]) else: - return self[0] in vn[self.addr_fmt][1] + return pfx_ok(vn[self.addr_fmt][1]) def is_in_tracking_wallet(self): from mmgen.rpc import rpc_init @@ -427,7 +434,7 @@ class MMGenID(str,Hilite,InitErrors,MMGenObject): try: ss = str(s).split(':') assert len(ss) in (2,3),'not 2 or 3 colon-separated items' - t = MMGenAddrType((ss[1],MMGenAddrType.dfl_mmtype)[len(ss)==2],on_fail='raise') + t = MMGenAddrType((ss[1],g.proto.dfl_mmtype)[len(ss)==2],on_fail='raise') me = str.__new__(cls,'{}:{}:{}'.format(ss[0],t,ss[-1])) me.sid = SeedID(sid=ss[0],on_fail='raise') me.idx = AddrIdx(ss[-1],on_fail='raise') @@ -587,7 +594,7 @@ class PrivKey(str,Hilite,InitErrors,MMGenObject): me.wif = me.towif() return me except Exception as e: - fs = "Key={!r}\nCompressed={}\nValue pair cannot be converted to PrivKey ({!r})" + fs = "Key={!r}\nCompressed={}\nValue pair cannot be converted to PrivKey\n({})" return cls.init_fail(fs.format(s,compressed,e),on_fail) def towif(self): @@ -671,18 +678,22 @@ class MMGenAddrType(str,Hilite,InitErrors,MMGenObject): 'gen':'p2pkh', 'fmt':'p2pkh', 'desc':'Legacy uncompressed address'}, + 'C': { 'name':'compressed', + 'comp':True, + 'gen':'p2pkh', + 'fmt':'p2pkh', + 'desc':'Compressed P2PKH address'}, 'S': { 'name':'segwit', 'comp':True, 'gen':'segwit', 'fmt':'p2sh', 'desc':'Segwit P2SH-P2WPKH address' }, - 'C': { 'name':'compressed', - 'comp':True, - 'gen':'p2pkh', - 'fmt':'p2pkh', - 'desc':'Compressed P2PKH address'} + 'E': { 'name':'ethereum', + 'comp':False, + 'gen':'ethereum', + 'fmt':'ethereum', + 'desc':'Ethereum address' }, } - dfl_mmtype = 'L' def __new__(cls,s,on_fail='die',errmsg=None): if type(s) == cls: return s cls.arg_chk(cls,on_fail) diff --git a/mmgen/protocol.py b/mmgen/protocol.py index 56222675..ee21aac9 100755 --- a/mmgen/protocol.py +++ b/mmgen/protocol.py @@ -22,7 +22,7 @@ protocol.py: Coin protocol functions, classes and methods import os,hashlib from binascii import unhexlify -from mmgen.util import msg,pmsg +from mmgen.util import msg,pmsg,Msg from mmgen.obj import MMGenObject,BTCAmt,LTCAmt,BCHAmt,B2XAmt from mmgen.globalvars import g @@ -53,12 +53,14 @@ def _b58tonum(b58num): if not i in _b58a: return False return sum(_b58a.index(n) * (58**i) for i,n in enumerate(list(b58num[::-1]))) +# chainparams.cpp class BitcoinProtocol(MMGenObject): name = 'bitcoin' daemon_name = 'bitcoind' - addr_ver_num = { 'p2pkh': ('00','1'), 'p2sh': ('05','3') } # chainparams.cpp - privkey_pfx = '80' + addr_ver_num = { 'p2pkh': ('00','1'), 'p2sh': ('05','3') } + wif_ver_num = '80' mmtypes = ('L','C','S') + dfl_mmtype = 'L' data_subdir = '' rpc_port = 8332 secs_per_block = 600 @@ -74,7 +76,9 @@ class BitcoinProtocol(MMGenObject): (None,'','b2x',True) ] caps = ('rbf','segwit') + mmcaps = ('key','addr','rpc') base_coin = 'BTC' + addr_width = 34 @staticmethod def get_protocol_by_chain(chain): @@ -89,7 +93,7 @@ class BitcoinProtocol(MMGenObject): @classmethod def hex2wif(cls,hexpriv,compressed=False): - s = cls.privkey_pfx + hexpriv + ('','01')[bool(compressed)] + s = cls.wif_ver_num + hexpriv + ('','01')[bool(compressed)] return _numtob58(int(s+hash256(s)[:8],16)) @classmethod @@ -101,34 +105,39 @@ class BitcoinProtocol(MMGenObject): compressed = len(key) == 76 if compressed and key[66:68] != '01': return False klen = (66,68)[compressed] - if (key[:2] == cls.privkey_pfx and key[klen:] == hash256(key[:klen])[:8]): + if (key[:2] == cls.wif_ver_num and key[klen:] == hash256(key[:klen])[:8]): return { 'hex':key[2:66], 'compressed':compressed } else: return False @classmethod - def verify_addr(cls,addr,verbose=False,return_dict=False): + def verify_addr(cls,addr,return_dict=False): for addr_fmt in cls.addr_ver_num: - ver_num,ldigit = cls.addr_ver_num[addr_fmt] - if addr[0] not in ldigit: continue + ver_num,pfx = cls.addr_ver_num[addr_fmt] + if type(pfx) == tuple: + if addr[0] not in pfx: continue + elif addr[:len(pfx)] != pfx: continue num = _b58tonum(addr) - if num == False: break - addr_hex = '{:050x}'.format(num) - if addr_hex[:2] != ver_num: continue - if hash256(addr_hex[:42])[:8] == addr_hex[42:]: + if num == False: + if g.debug: Msg('Address cannot be converted to base 58') + break + addr_hex = '{:0{}x}'.format(num,48+len(ver_num)) + if addr_hex[:len(ver_num)] != ver_num: continue + if hash256(addr_hex[:-8])[:8] == addr_hex[-8:]: return { - 'hex': addr_hex[2:42], + 'hex': addr_hex[len(ver_num):-8], 'format': {'p2pkh':'p2pkh','p2sh':'p2sh','p2sh2':'p2sh'}[addr_fmt], + 'width': cls.addr_width } if return_dict else True else: - if verbose: Msg("Invalid checksum in address '{}'".format(addr)) + if g.debug: Msg('Invalid checksum in address') break - if verbose: Msg("Invalid address '{}'".format(addr)) + if g.debug: Msg("Invalid address '{}'".format(addr)) return False @classmethod - def hexaddr2addr(cls,hexaddr,p2sh): - s = cls.addr_ver_num[('p2pkh','p2sh')[p2sh]][0] + hexaddr + def pubhash2addr(cls,pubkey_hash,p2sh): + s = cls.addr_ver_num[('p2pkh','p2sh')[p2sh]][0] + pubkey_hash lzeroes = (len(s) - len(s.lstrip('0'))) / 2 # non-zero only for ver num '00' (BTC p2pkh) return ('1' * lzeroes) + _numtob58(int(s+hash256(s)[:8],16)) @@ -142,14 +151,15 @@ class BitcoinProtocol(MMGenObject): @classmethod def pubhex2segwitaddr(cls,pubhex): - return cls.hexaddr2addr(hash160(cls.pubhex2redeem_script(pubhex)),p2sh=True) + return cls.pubhash2addr(hash160(cls.pubhex2redeem_script(pubhex)),p2sh=True) class BitcoinTestnetProtocol(BitcoinProtocol): - addr_ver_num = { 'p2pkh': ('6f','mn'), 'p2sh': ('c4','2') } - privkey_pfx = 'ef' + addr_ver_num = { 'p2pkh': ('6f',('m','n')), 'p2sh': ('c4','2') } + wif_ver_num = 'ef' data_subdir = 'testnet' daemon_data_subdir = 'testnet3' rpc_port = 18332 + addr_width = 35 class BitcoinCashProtocol(BitcoinProtocol): # TODO: assumes MSWin user installs in custom dir 'Bitcoin_ABC' @@ -173,10 +183,11 @@ class BitcoinCashProtocol(BitcoinProtocol): class BitcoinCashTestnetProtocol(BitcoinCashProtocol): rpc_port = 18442 - addr_ver_num = { 'p2pkh': ('6f','mn'), 'p2sh': ('c4','2') } - privkey_pfx = 'ef' + addr_ver_num = { 'p2pkh': ('6f',('m','n')), 'p2sh': ('c4','2') } + wif_ver_num = 'ef' data_subdir = 'testnet' daemon_data_subdir = 'testnet3' + addr_width = 35 class B2XProtocol(BitcoinProtocol): daemon_name = 'bitcoind-2x' @@ -190,11 +201,12 @@ class B2XProtocol(BitcoinProtocol): ] class B2XTestnetProtocol(B2XProtocol): - addr_ver_num = { 'p2pkh': ('6f','mn'), 'p2sh': ('c4','2') } - privkey_pfx = 'ef' + addr_ver_num = { 'p2pkh': ('6f',('m','n')), 'p2sh': ('c4','2') } + wif_ver_num = 'ef' data_subdir = 'testnet' daemon_data_subdir = 'testnet5' rpc_port = 18338 + addr_width = 35 class LitecoinProtocol(BitcoinProtocol): block0 = '12a765e31ffd4059bada1e25190f6e98c99d9714d334efa41a195a7e7e04bfe2' @@ -203,7 +215,7 @@ class LitecoinProtocol(BitcoinProtocol): daemon_data_dir = os.path.join(os.getenv('APPDATA'),'Litecoin') if g.platform == 'win' \ else os.path.join(g.home_dir,'.litecoin') addr_ver_num = { 'p2pkh': ('30','L'), 'p2sh': ('32','M'), 'p2sh2': ('05','3') } # 'p2sh' is new fmt - privkey_pfx = 'b0' + wif_ver_num = 'b0' secs_per_block = 150 rpc_port = 9332 coin_amt = LTCAmt @@ -213,16 +225,68 @@ class LitecoinProtocol(BitcoinProtocol): class LitecoinTestnetProtocol(LitecoinProtocol): # addr ver nums same as Bitcoin testnet, except for 'p2sh' - addr_ver_num = { 'p2pkh': ('6f','mn'), 'p2sh': ('3a','Q'), 'p2sh2': ('c4','2') } - privkey_pfx = 'ef' # same as Bitcoin testnet + addr_ver_num = { 'p2pkh': ('6f',('m','n')), 'p2sh': ('3a','Q'), 'p2sh2': ('c4','2') } + wif_ver_num = 'ef' # same as Bitcoin testnet data_subdir = 'testnet' daemon_data_subdir = 'testnet4' rpc_port = 19332 + addr_width = 35 -class EthereumProtocol(MMGenObject): - base_coin = 'ETH' +class BitcoinProtocolAddrgen(BitcoinProtocol): mmcaps = ('key','addr') +class BitcoinProtocolKeygen(BitcoinProtocol): mmcaps = ('key',) + +class EthereumProtocol(BitcoinProtocolAddrgen): + + addr_width = 40 + mmtypes = ('E',) + dfl_mmtype = 'E' + name = 'ethereum' + base_coin = 'ETH' + + @classmethod + def hex2wif(cls,hexpriv,compressed=False): + assert compressed == False,'Ethereum does not support compressed pubkeys!' + return str(hexpriv) + + @classmethod + def wif2hex(cls,wif): + return { 'hex':str(wif), 'compressed':False } + + @classmethod + def verify_addr(cls,addr,return_dict=False): + from mmgen.util import is_hex_str_lc + if is_hex_str_lc(addr) and len(addr) == 40: + return { 'hex': addr, 'format': 'ethereum', 'width': cls.addr_width } if return_dict else True + if g.debug: Msg("Invalid address '{}'".format(addr)) + return False class EthereumTestnetProtocol(EthereumProtocol): pass +class EthereumClassicProtocol(EthereumProtocol): + name = 'ethereum_classic' +class EthereumClassicTestnetProtocol(EthereumClassicProtocol): pass + +class ZcashProtocol(BitcoinProtocolAddrgen): + name = 'zcash' + base_coin = 'ZEC' + addr_ver_num = { 'p2pkh': ('1cb8','t1'), 'p2sh': ('1cbd','t3') } + wif_ver_num = '80' + mmtypes = ('C',) + dfl_mmtype = 'C' + +class ZcashTestnetProtocol(object): pass + +class DashProtocol(BitcoinProtocolAddrgen): + name = 'dash' + base_coin = 'DASH' + addr_ver_num = { 'p2pkh': ('4c','X'), 'p2sh': ('10','7') } + wif_ver_num = 'cc' + mmtypes = ('C',) + dfl_mmtype = 'C' + +class DashTestnetProtocol(DashProtocol): + # "Dash", "testnet", "tDASH", b'\xef', b'\x8c', b'\x13' + addr_ver_num = { 'p2pkh': ('8c','y'), 'p2sh': ('13','?') } + wif_ver_num = 'ef' class CoinProtocol(MMGenObject): coins = { @@ -230,7 +294,10 @@ class CoinProtocol(MMGenObject): 'bch': (BitcoinCashProtocol,BitcoinCashTestnetProtocol), 'b2x': (B2XProtocol,B2XTestnetProtocol), 'ltc': (LitecoinProtocol,LitecoinTestnetProtocol), -# 'eth': (EthereumProtocol,EthereumTestnetProtocol), + 'dash': (DashProtocol,DashTestnetProtocol), + 'zec': (ZcashProtocol,ZcashTestnetProtocol), + 'eth': (EthereumProtocol,EthereumTestnetProtocol), + 'etc': (EthereumClassicProtocol,EthereumClassicTestnetProtocol), } def __new__(cls,coin,testnet): coin = coin.lower() diff --git a/mmgen/tool.py b/mmgen/tool.py index 3702c63e..8a756184 100755 --- a/mmgen/tool.py +++ b/mmgen/tool.py @@ -61,7 +61,7 @@ cmd_data = OrderedDict([ ('Wif2hex', [' [str-]']), ('Wif2addr', [' [str-]','segwit [bool=False]']), ('Wif2segwit_pair',[' [str-]']), - ('Hexaddr2addr', [' [str-]','p2sh [bool=False]']), + ('Pubhash2addr', [' [str-]','p2sh [bool=False]']), ('Addr2hexaddr', [' [str-]']), ('Privhex2addr', [' [str-]','compressed [bool=False]','segwit [bool=False]']), ('Privhex2pubhex',[' [str-]','compressed [bool=False]']), @@ -279,11 +279,11 @@ def Wif2segwit_pair(wif): rs = ag.to_segwit_redeem_script(pubhex) Msg('{}\n{}'.format(rs,addr)) -def Hexaddr2addr(hexaddr,p2sh=False): Msg(g.proto.hexaddr2addr(hexaddr,p2sh=p2sh)) -def Addr2hexaddr(addr): Msg(g.proto.verify_addr(addr,return_dict=True)['hex']) -def Hash160(pubkeyhex): Msg(hash160(pubkeyhex)) -def Pubhex2addr(pubkeyhex,p2sh=False): Msg(g.proto.hexaddr2addr(hash160(pubkeyhex),p2sh=p2sh)) -def Wif2hex(wif): Msg(wif2hex(wif)) +def Pubhash2addr(pubhash,p2sh=False): Msg(g.proto.pubhash2addr(pubhash,p2sh=p2sh)) +def Addr2hexaddr(addr): Msg(g.proto.verify_addr(addr,return_dict=True)['hex']) +def Hash160(pubkeyhex): Msg(hash160(pubkeyhex)) +def Pubhex2addr(pubkeyhex,p2sh=False): Msg(g.proto.pubhash2addr(hash160(pubkeyhex),p2sh=p2sh)) +def Wif2hex(wif): Msg(wif2hex(wif)) def Hex2wif(hexpriv,compressed=False): Msg(g.proto.hex2wif(hexpriv,compressed)) def Privhex2addr(privhex,compressed=False,segwit=False,output_pubhex=False): diff --git a/mmgen/tx.py b/mmgen/tx.py index a3f87647..23d6d309 100755 --- a/mmgen/tx.py +++ b/mmgen/tx.py @@ -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 diff --git a/mmgen/util.py b/mmgen/util.py index 14c0883a..ac47a077 100755 --- a/mmgen/util.py +++ b/mmgen/util.py @@ -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): diff --git a/scripts/test-release.sh b/scripts/test-release.sh index 1ea91188..7543e31f 100755 --- a/scripts/test-release.sh +++ b/scripts/test-release.sh @@ -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 diff --git a/test/gentest.py b/test/gentest.py index b065973e..afd861e4 100755 --- a/test/gentest.py +++ b/test/gentest.py @@ -53,10 +53,16 @@ opts_data = lambda: { EXAMPLES: {prog} 1:2 100 (compare output of native Python ECDSA with secp256k1 library, 100 rounds) + {prog} 2:ext 100 + (compare output of secp256k1 library with external library (see below), 100 rounds) {prog} 2 1000 (test speed of secp256k1 library address generation, 1000 rounds) {prog} 2 my.dump (compare addrs generated with secp256k1 library to {dn} wallet dump) + + External libraries required for the 'ext' generator: + + pyethereum (for ETH,ETC) https://github.com/ethereum/pyethereum + + pycoin (for all other coins) https://github.com/richardkiss/pycoin """.format(prog='gentest.py',pnm=g.proj_name,snum=rounds,dn=g.proto.daemon_name) } @@ -66,6 +72,23 @@ cmd_args = opts.init(opts_data,add_opts=['exact_output']) if not 1 <= len(cmd_args) <= 2: opts.usage() +def pyethereum_sec2addr(sec): + return sec,eth.privtoaddr(sec).encode('hex') + +def pycoin_sec2addr(sec): + if g.testnet: # pycoin/networks/all.py pycoin/networks/legacy_networks.py + coin = { 'BTC':'XTN', 'LTC':'XLT', 'DASH':'tDASH' }[g.coin] + else: + coin = g.coin + key = pcku.parse_key(sec,PREFIX_TRANSFORMS,coin) + if key is None: die(1,"can't parse {}".format(sec)) + o = pcku.create_output(sec,key)[0] +# pmsg(o) + suf = ('_uncompressed','')[compressed] + wif = o['wif{}'.format(suf)] + addr = o['{}_address{}'.format(coin,suf)] + return wif,addr + urounds,fh = None,None dump = [] if len(cmd_args) == 2: @@ -97,8 +120,27 @@ except: die(1,"First argument must be one or two generator IDs, colon separated") else: try: - a,b = int(a),int(b) - for i in (a,b): assert 1 <= i <= len(g.key_generators) + a = int(a) + assert 1 <= a <= len(g.key_generators) + if b == 'ext': + if g.coin in ('ETH','ETC'): + try: + import ethereum.utils as eth + except: + die(1,"Unable to import 'pyethereum' module. Is pyethereum installed?") + sec2addr = pyethereum_sec2addr + ext_lib = 'pyethereum' + else: + try: + import pycoin.cmds.ku as pcku + except: + die(1,"Unable to import module 'ku'. Is pycoin installed?") + PREFIX_TRANSFORMS = pcku.prefix_transforms_for_network(g.coin) + sec2addr = pycoin_sec2addr + ext_lib = 'pycoin' + else: + b = int(b) + assert 1 <= b <= len(g.key_generators) assert a != b except: die(1,"%s: invalid generator IDs" % cmd_args[0]) @@ -111,40 +153,45 @@ def match_error(sec,wif,a_addr,b_addr,a,b): WIF key : {} {a:10}: {} {b:10}: {} -""".format(sec,wif,a_addr,b_addr,pnm=g.proj_name,a=m[a],b=m[b]).rstrip()) +""".format(sec,wif,a_addr,b_addr,pnm=g.proj_name,a=m[a],b=m[b] if b in m else b).rstrip()) # Begin execution -compressed = True +compressed = False if g.coin in ('ETH','ETC') else True from mmgen.addr import KeyGenerator,AddrGenerator from mmgen.obj import PrivKey -ag = AddrGenerator(('p2pkh','segwit')[bool(opt.segwit)]) +ag = AddrGenerator('ethereum' if g.coin in ('ETH','ETC') else ('p2pkh','segwit')[bool(opt.segwit)]) if a and b: - m = "Comparing address generators '{}' and '{}'" - qmsg(green(m.format(g.key_generators[a-1],g.key_generators[b-1]))) + m = "Comparing address generators '{}' and '{}' for coin {}" + qmsg(green(m.format(g.key_generators[a-1],(ext_lib if b == 'ext' else g.key_generators[b-1]),g.coin))) last_t = time.time() kg_a = KeyGenerator(a) - kg_b = KeyGenerator(b) + if b != 'ext': kg_b = KeyGenerator(b) for i in range(rounds): - if time.time() - last_t >= 0.1: + if opt.verbose or time.time() - last_t >= 0.1: qmsg_r('\rRound %s/%s ' % (i+1,rounds)) last_t = time.time() sec = PrivKey(os.urandom(32),compressed) a_addr = ag.to_addr(kg_a.to_pubhex(sec)) - b_addr = ag.to_addr(kg_b.to_pubhex(sec)) + if b == 'ext': + b_wif,b_addr = sec2addr(sec) + if b_wif != sec.wif: + match_error(sec,sec.wif,sec.wif,b_wif,a,b) + else: + b_addr = ag.to_addr(kg_b.to_pubhex(sec)) vmsg('\nkey: %s\naddr: %s\n' % (sec.wif,a_addr)) if a_addr != b_addr: - match_error(sec,sec.wif,a_addr,b_addr,a,b) - if not opt.segwit: + match_error(sec,sec.wif,a_addr,b_addr,a,ext_lib if b == 'ext' else b) + if not opt.segwit and 'L' in g.proto.mmtypes: compressed = not compressed qmsg_r('\rRound %s/%s ' % (i+1,rounds)) qmsg(green(('\n','')[bool(opt.verbose)] + 'OK')) elif a and not fh: - m = "Testing speed of address generator '{}'" - qmsg(green(m.format(g.key_generators[a-1]))) + m = "Testing speed of address generator '{}' for coin {}" + qmsg(green(m.format(g.key_generators[a-1],g.coin))) from struct import pack,unpack seed = os.urandom(28) print 'Incrementing key with each round' @@ -160,7 +207,7 @@ elif a and not fh: sec = PrivKey(seed+pack('I',i),compressed) a_addr = ag.to_addr(kg.to_pubhex(sec)) vmsg('\nkey: %s\naddr: %s\n' % (sec.wif,a_addr)) - if not opt.segwit: + if not opt.segwit and g.coin not in ('ETC','ETC'): compressed = not compressed qmsg_r('\rRound %s/%s ' % (i+1,rounds)) diff --git a/test/ref/dash/98831F3A-DASH-C[1,31-33,500-501,1010-1011].addrs b/test/ref/dash/98831F3A-DASH-C[1,31-33,500-501,1010-1011].addrs new file mode 100644 index 00000000..96a0f792 --- /dev/null +++ b/test/ref/dash/98831F3A-DASH-C[1,31-33,500-501,1010-1011].addrs @@ -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 +} diff --git a/test/ref/dash/98831F3A-DASH-C[1,31-33,500-501,1010-1011].akeys.mmenc b/test/ref/dash/98831F3A-DASH-C[1,31-33,500-501,1010-1011].akeys.mmenc new file mode 100644 index 00000000..b064b6ce Binary files /dev/null and b/test/ref/dash/98831F3A-DASH-C[1,31-33,500-501,1010-1011].akeys.mmenc differ diff --git a/test/ref/ethereum/98831F3A-ETH[1,31-33,500-501,1010-1011].addrs b/test/ref/ethereum/98831F3A-ETH[1,31-33,500-501,1010-1011].addrs new file mode 100644 index 00000000..79675b94 --- /dev/null +++ b/test/ref/ethereum/98831F3A-ETH[1,31-33,500-501,1010-1011].addrs @@ -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 +} diff --git a/test/ref/ethereum/98831F3A-ETH[1,31-33,500-501,1010-1011].akeys.mmenc b/test/ref/ethereum/98831F3A-ETH[1,31-33,500-501,1010-1011].akeys.mmenc new file mode 100644 index 00000000..62a6a5ae Binary files /dev/null and b/test/ref/ethereum/98831F3A-ETH[1,31-33,500-501,1010-1011].akeys.mmenc differ diff --git a/test/ref/ethereum_classic/98831F3A-ETC[1,31-33,500-501,1010-1011].addrs b/test/ref/ethereum_classic/98831F3A-ETC[1,31-33,500-501,1010-1011].addrs new file mode 100644 index 00000000..b2d68210 --- /dev/null +++ b/test/ref/ethereum_classic/98831F3A-ETC[1,31-33,500-501,1010-1011].addrs @@ -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 +} diff --git a/test/ref/ethereum_classic/98831F3A-ETC[1,31-33,500-501,1010-1011].akeys.mmenc b/test/ref/ethereum_classic/98831F3A-ETC[1,31-33,500-501,1010-1011].akeys.mmenc new file mode 100644 index 00000000..cd07f6d4 Binary files /dev/null and b/test/ref/ethereum_classic/98831F3A-ETC[1,31-33,500-501,1010-1011].akeys.mmenc differ diff --git a/test/ref/zcash/98831F3A-ZEC-C[1,31-33,500-501,1010-1011].addrs b/test/ref/zcash/98831F3A-ZEC-C[1,31-33,500-501,1010-1011].addrs new file mode 100644 index 00000000..5999f224 --- /dev/null +++ b/test/ref/zcash/98831F3A-ZEC-C[1,31-33,500-501,1010-1011].addrs @@ -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 +} diff --git a/test/ref/zcash/98831F3A-ZEC-C[1,31-33,500-501,1010-1011].akeys.mmenc b/test/ref/zcash/98831F3A-ZEC-C[1,31-33,500-501,1010-1011].akeys.mmenc new file mode 100644 index 00000000..9d0661b1 Binary files /dev/null and b/test/ref/zcash/98831F3A-ZEC-C[1,31-33,500-501,1010-1011].akeys.mmenc differ diff --git a/test/scrambletest.py b/test/scrambletest.py new file mode 100755 index 00000000..1cbb699d --- /dev/null +++ b/test/scrambletest.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python +# +# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution +# Copyright (C)2013-2017 Philemon +# +# 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 . + +""" +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))) diff --git a/test/test.py b/test/test.py index a538a627..cab5a4ac 100755 --- a/test/test.py +++ b/test/test.py @@ -431,9 +431,9 @@ cfgs = { 'passfile32_chk': 'F6C1 CDFB 97D9 FCAE', 'wpasswd': 'reference password', 'ref_wallet': '98831F3A-{}[256,1].mmdat'.format(('27F2BF93','E2687906')[g.testnet]), - 'ref_addrfile': '98831F3A{}[1,31-33,500-501,1010-1011]{}.addrs'.format(altcoin_pfx,tn_ext), - 'ref_segwitaddrfile':'98831F3A{}-S[1,31-33,500-501,1010-1011]{}.addrs'.format(altcoin_pfx,tn_ext), - 'ref_keyaddrfile': '98831F3A{}[1,31-33,500-501,1010-1011]{}.akeys.mmenc'.format(altcoin_pfx,tn_ext), + 'ref_addrfile': '98831F3A{}[1,31-33,500-501,1010-1011]{}.addrs', + 'ref_segwitaddrfile':'98831F3A{}-S[1,31-33,500-501,1010-1011]{}.addrs', + 'ref_keyaddrfile': '98831F3A{}[1,31-33,500-501,1010-1011]{}.akeys.mmenc', 'ref_passwdfile': '98831F3A-фубар@crypto.org-b58-20[1,4,9-11,1100].pws', 'ref_addrfile_chksum': { 'btc': ('6FEF 6FB9 7B13 5D91','3C2C 8558 BB54 079E'), @@ -447,7 +447,15 @@ cfgs = { 'btc': ('9F2D D781 1812 8BAD','7410 8F95 4B33 B4B2'), 'ltc': ('B804 978A 8796 3ED4','93A6 844C 8ECC BEF4'), }, - 'ref_passwdfile_chksum': 'A983 DAB9 5514 27FB', + 'ref_addrfile_chksum_zec': '903E 7225 DD86 6E01', + 'ref_addrfile_chksum_dash':'FBC1 6B6A 0988 4403', + 'ref_addrfile_chksum_eth': 'E554 076E 7AF6 66A3', + 'ref_addrfile_chksum_etc': 'E97A D796 B495 E8BC', + 'ref_keyaddrfile_chksum_zec': 'F05A 5A5C 0C8E 2617', + 'ref_keyaddrfile_chksum_dash': 'E83D 2C63 FEA2 4142', + 'ref_keyaddrfile_chksum_eth': '3635 4DCF B752 8772', + 'ref_keyaddrfile_chksum_etc': '9BAC 38E7 5C8E 42E0', + 'ref_passwdfile_chksum': 'A983 DAB9 5514 27FB', # 'ref_fake_unspent_data':'98831F3A_unspent.json', 'ref_tx_file': { 'btc': 'FFB367[1.234]{}.rawtx', @@ -700,6 +708,17 @@ cmd_group['misc'] = ( ('autosign', 'transaction autosigning (BTC,BCH,LTC)'), ) +cmd_group['altcoin_ref'] = ( + ('ref_addrfile_chk_zec', 'reference address file (ZEC)'), + ('ref_addrfile_chk_dash','reference address file (DASH)'), + ('ref_addrfile_chk_eth', 'reference address file (ETH)'), + ('ref_addrfile_chk_etc', 'reference address file (ETC)'), + ('ref_keyaddrfile_chk_zec', 'reference key-address file (ZEC)'), + ('ref_keyaddrfile_chk_dash','reference key-address file (DASH)'), + ('ref_keyaddrfile_chk_eth', 'reference key-address file (ETH)'), + ('ref_keyaddrfile_chk_etc', 'reference key-address file (ETC)'), +) + # undocumented admin cmds cmd_group_admin = OrderedDict() cmd_group_admin['create_ref_tx'] = ( @@ -778,6 +797,11 @@ for a,b in cmd_group['misc']: cmd_list['misc'].append(a) cmd_data[a] = (18,b,[[[],18]]) +cmd_data['info_altcoin_ref'] = 'altcoin reference files',[8] +for a,b in cmd_group['altcoin_ref']: + cmd_list['altcoin_ref'].append(a) + cmd_data[a] = (8,b,[[[],8]]) + utils = { 'check_deps': 'check dependencies for specified command', 'clean': 'clean specified tmp dir(s) 1,2,3,4,5 or 6 (no arg = all dirs)', @@ -2006,20 +2030,48 @@ class MMGenTestSuite(object): t.close() cmp_or_die(cfg['seed_id'],chk) - def ref_addrfile_chk(self,name,ftype='addr'): + def ref_addrfile_chk(self,name,ftype='addr',coin=None,subdir=None,pfx=None,mmtype=None): af_key = 'ref_{}file'.format(ftype) - af = os.path.join(ref_dir,(ref_subdir,'')[ftype=='passwd'],cfg[af_key]) - t = MMGenExpect(name,'mmgen-tool',[ftype.replace('segwit','')+'file_chksum',af]) + af_fn = cfg[af_key].format(pfx or altcoin_pfx,'' if coin else tn_ext) + af = os.path.join(ref_dir,(subdir or ref_subdir,'')[ftype=='passwd'],af_fn) + coin_arg = [] if coin == None else ['--coin='+coin] + t = MMGenExpect(name,'mmgen-tool',coin_arg+[ftype.replace('segwit','')+'file_chksum',af]) if ftype == 'keyaddr': w = 'key-address data' t.hash_preset(w,ref_kafile_hash_preset) t.passphrase(w,ref_kafile_pass) t.expect('Check key-to-address validity? (y/N): ','y') o = t.read().strip().split('\n')[-1] - rc = cfg['ref_'+ftype+'file_chksum'] - ref_chksum = rc if ftype == 'passwd' else rc[g.proto.base_coin.lower()][g.testnet] + rc = cfg[ 'ref_' + ftype + 'file_chksum' + + ('_'+coin.lower() if coin else '') + + ('_'+mmtype if mmtype else '')] + ref_chksum = rc if (ftype == 'passwd' or coin) else rc[g.proto.base_coin.lower()][g.testnet] cmp_or_die(ref_chksum,o) + def ref_addrfile_chk_zec(self,name): + self.ref_addrfile_chk(name,ftype='addr',coin='ZEC',subdir='zcash',pfx='-ZEC-C') + + def ref_addrfile_chk_dash(self,name): + self.ref_addrfile_chk(name,ftype='addr',coin='DASH',subdir='dash',pfx='-DASH-C') + + def ref_addrfile_chk_eth(self,name): + self.ref_addrfile_chk(name,ftype='addr',coin='ETH',subdir='ethereum',pfx='-ETH') + + def ref_addrfile_chk_etc(self,name): + self.ref_addrfile_chk(name,ftype='addr',coin='ETC',subdir='ethereum_classic',pfx='-ETC') + + def ref_keyaddrfile_chk_zec(self,name): + self.ref_addrfile_chk(name,ftype='keyaddr',coin='ZEC',subdir='zcash',pfx='-ZEC-C') + + def ref_keyaddrfile_chk_dash(self,name): + self.ref_addrfile_chk(name,ftype='keyaddr',coin='DASH',subdir='dash',pfx='-DASH-C') + + def ref_keyaddrfile_chk_eth(self,name): + self.ref_addrfile_chk(name,ftype='keyaddr',coin='ETH',subdir='ethereum',pfx='-ETH') + + def ref_keyaddrfile_chk_etc(self,name): + self.ref_addrfile_chk(name,ftype='keyaddr',coin='ETC',subdir='ethereum_classic',pfx='-ETC') + def ref_keyaddrfile_chk(self,name): self.ref_addrfile_chk(name,ftype='keyaddr') @@ -2597,7 +2649,7 @@ class MMGenTestSuite(object): g.proto = psave for k in ('in_addrs','out_addrs'): - tx[k+'_conv'] = [g.proto.hexaddr2addr(h,(False,True)[f=='p2sh']) for h,f in tx[k+'_hex']] + tx[k+'_conv'] = [g.proto.pubhash2addr(h,(False,True)[f=='p2sh']) for h,f in tx[k+'_hex']] for k in ('in','out'): for i in range(len(tx[k])): diff --git a/test/tooltest.py b/test/tooltest.py index ceb83352..3aaa9553 100755 --- a/test/tooltest.py +++ b/test/tooltest.py @@ -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