diff --git a/mmgen/addr.py b/mmgen/addr.py index eb5a69b4..b636183d 100755 --- a/mmgen/addr.py +++ b/mmgen/addr.py @@ -32,14 +32,15 @@ def sc_dmsg(desc,data): Msg('sc_debug_{}: {}'.format(desc,data)) class AddrGenerator(MMGenObject): - def __new__(cls,atype): + def __new__(cls,gen_method): d = { 'p2pkh': AddrGeneratorP2PKH, 'segwit': AddrGeneratorSegwit, - 'ethereum': AddrGeneratorEthereum + 'ethereum': AddrGeneratorEthereum, + 'zcash_z': AddrGeneratorZcashZ } - assert atype in d - me = super(cls,cls).__new__(d[atype]) + assert gen_method in d + me = super(cls,cls).__new__(d[gen_method]) me.desc = d return me @@ -70,15 +71,49 @@ class AddrGeneratorEthereum(AddrGenerator): def to_segwit_redeem_script(self,pubhex): raise NotImplementedError +class AddrGeneratorZcashZ(AddrGenerator): + + def zhash256(self,vhex,t): + byte0 = '{:02x}'.format(int(vhex[:2],16) | 0xc0) + byte32 = '{:02x}'.format(t) + vhex_fix = byte0 + vhex[2:64] + byte32 + '00' * 31 + assert len(vhex_fix) == 128 + from mmgen.sha256 import Sha256 + return Sha256(unhexlify(vhex_fix),preprocess=False).hexdigest() + + def to_addr(self,pubhex): # pubhex is really privhex + key = pubhex + assert len(key) == 64,'{}: incorrect privkey length'.format(len(key)) + addr1 = self.zhash256(key,0) + addr2 = self.zhash256(key,1) + from nacl.bindings import crypto_scalarmult_base + addr2 = hexlify(crypto_scalarmult_base(unhexlify(addr2))) + + from mmgen.protocol import _b58chk_encode + ret = _b58chk_encode(g.proto.addr_ver_num['zcash_z'][0] + addr1 + addr2) + assert len(ret) == g.proto.addr_width,'Invalid zaddr length' + return CoinAddr(ret) + + def to_segwit_redeem_script(self,pubhex): + raise NotImplementedError + class KeyGenerator(MMGenObject): - def __new__(cls,generator=None,silent=False): - if cls.test_for_secp256k1(silent=silent) and generator != 1: - if not opt.key_generator or opt.key_generator == 2 or generator == 2: - return super(cls,cls).__new__(KeyGeneratorSecp256k1) + def __new__(cls,pubkey_type,generator=None,silent=False): + if pubkey_type == 'std': + if cls.test_for_secp256k1(silent=silent) and generator != 1: + if not opt.key_generator or opt.key_generator == 2 or generator == 2: + return super(cls,cls).__new__(KeyGeneratorSecp256k1) + else: + msg('Using (slow) native Python ECDSA library for address generation') + return super(cls,cls).__new__(KeyGeneratorPython) + elif pubkey_type == 'zcash_z': + g.proto.addr_width = 95 + me = super(cls,cls).__new__(KeyGeneratorDummy) + me.desc = 'mmgen-'+pubkey_type + return me else: - msg('Using (slow) native Python ECDSA library for address generation') - return super(cls,cls).__new__(KeyGeneratorPython) + raise ValueError,'{}: invalid pubkey_type argument'.format(pubkey_type) @classmethod def test_for_secp256k1(self,silent=False): @@ -91,6 +126,7 @@ class KeyGenerator(MMGenObject): import ecdsa class KeyGeneratorPython(KeyGenerator): + desc = 'mmgen-python-ecdsa' # From electrum: # secp256k1, http://www.oid-info.com/get/1.3.132.0.10 _p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2FL @@ -119,19 +155,24 @@ class KeyGeneratorPython(KeyGenerator): else: return '04'+pubkey - desc = 'python-ecdsa' def to_pubhex(self,privhex): assert type(privhex) == PrivKey return PubKey(self.privnum2pubhex( int(privhex,16),compressed=privhex.compressed),compressed=privhex.compressed) class KeyGeneratorSecp256k1(KeyGenerator): - desc = 'secp256k1' + desc = 'mmgen-secp256k1' def to_pubhex(self,privhex): assert type(privhex) == PrivKey from mmgen.secp256k1 import priv2pub return PubKey(hexlify(priv2pub(unhexlify(privhex),int(privhex.compressed))),compressed=privhex.compressed) +class KeyGeneratorDummy(KeyGenerator): + desc = 'mmgen-dummy' + def to_pubhex(self,privhex): + assert type(privhex) == PrivKey + return PubKey(str(privhex),compressed=privhex.compressed) + class AddrListEntry(MMGenListItem): addr = MMGenListItemAttr('addr','CoinAddr') idx = MMGenListItemAttr('idx','AddrIdx') # not present in flat addrlists @@ -220,7 +261,7 @@ Removed %s duplicate WIF key%s from keylist (also in {pnm} key-address file self.update_msgs() mmtype = mmtype or g.proto.dfl_mmtype - assert mmtype in MMGenAddrType.mmtypes + assert mmtype in MMGenAddrType.mmtypes,'{}: mmtype not in {}'.format(mmtype,repr(MMGenAddrType.mmtypes)) if seed and addr_idxs: # data from seed + idxs self.al_id,src = AddrListID(seed.sid,mmtype),'gen' @@ -274,8 +315,11 @@ Removed %s duplicate WIF key%s from keylist (also in {pnm} key-address file seed = self.scramble_seed(seed) sc_dmsg('seed',seed[:8].encode('hex')) + compressed = self.al_id.mmtype.compressed + pubkey_type = self.al_id.mmtype.pubkey_type + if self.gen_addrs: - kg = KeyGenerator() + kg = KeyGenerator(pubkey_type) ag = AddrGenerator(self.al_id.mmtype.gen_method) t_addrs,num,pos,out = len(addrnums),0,0,AddrListList() @@ -295,7 +339,7 @@ Removed %s duplicate WIF key%s from keylist (also in {pnm} key-address file e = le(idx=num) # Secret key is double sha256 of seed hash round /num/ - e.sec = PrivKey(sha256(sha256(seed).digest()).digest(),self.al_id.mmtype.compressed) + e.sec = PrivKey(sha256(sha256(seed).digest()).digest(),compressed=compressed,pubkey_type=pubkey_type) if self.gen_addrs: e.addr = ag.to_addr(kg.to_pubhex(e.sec)) @@ -305,7 +349,7 @@ Removed %s duplicate WIF key%s from keylist (also in {pnm} key-address file dmsg('Key {:>03}: {}'.format(pos,e.passwd)) out.append(e) - if g.debug: print 'generate():\n', e.pformat() + if g.debug: Msg('generate():\n', e.pformat()) qmsg('\r%s: %s %s%s generated%s' % ( self.al_id.hl(),t_addrs,self.gen_desc,suf(t_addrs,self.gen_desc_pl),' '*15)) @@ -398,7 +442,7 @@ Removed %s duplicate WIF key%s from keylist (also in {pnm} key-address file return [d.addr for d in self.data if not getattr(d,key)] def generate_addrs_from_keys(self): - kg = KeyGenerator() + kg = KeyGenerator('std') ag = AddrGenerator('p2pkh') d = self.data for n,e in enumerate(d,1): @@ -478,7 +522,7 @@ Removed %s duplicate WIF key%s from keylist (also in {pnm} key-address file ret.append(a) if self.has_keys and keypress_confirm('Check key-to-address validity?'): - kg = KeyGenerator() + kg = KeyGenerator(self.al_id.mmtype.pubkey_type) ag = AddrGenerator(self.al_id.mmtype.gen_method) llen = len(ret) for n,e in enumerate(ret): diff --git a/mmgen/globalvars.py b/mmgen/globalvars.py index cae9d8c9..eadc2c9a 100755 --- a/mmgen/globalvars.py +++ b/mmgen/globalvars.py @@ -38,8 +38,8 @@ class g(object): # Constants: - version = '0.9.5' - release_date = 'November 2017' + version = '0.9.599' + release_date = 'December 2017' proj_name = 'MMGen' proj_url = 'https://github.com/mmgen/mmgen' @@ -47,7 +47,7 @@ class g(object): author = 'Philemon' email = '' Cdates = '2013-2017' - keywords = 'Bitcoin, BTC, cryptocurrency, wallet, cold storage, offline, online, spending, open-source, command-line, Python, Linux, Bitcoin Core, bitcoind, hd, deterministic, hierarchical, secure, anonymous, Electrum, seed, mnemonic, brainwallet, Scrypt, utility, script, scriptable, blockchain, raw, transaction, permissionless, console, terminal, curses, ansi, color, tmux, remote, client, daemon, RPC, json, entropy, xterm, rxvt, PowerShell, MSYS, MinGW, mswin, Armbian, Raspbian, Raspberry Pi, Orange Pi, BCash, BCH, Litecoin, LTC, altcoin' + keywords = 'Bitcoin, BTC, cryptocurrency, wallet, cold storage, offline, online, spending, open-source, command-line, Python, Linux, Bitcoin Core, bitcoind, hd, deterministic, hierarchical, secure, anonymous, Electrum, seed, mnemonic, brainwallet, Scrypt, utility, script, scriptable, blockchain, raw, transaction, permissionless, console, terminal, curses, ansi, color, tmux, remote, client, daemon, RPC, json, entropy, xterm, rxvt, PowerShell, MSYS, MinGW, mswin, Armbian, Raspbian, Raspberry Pi, Orange Pi, BCash, BCH, Litecoin, LTC, altcoin, ZEC, Zcash, DASH, ETH, Ethereum, Classic, SHA256Compress' max_int = 0xffffffff stdin_tty = bool(sys.stdin.isatty() or os.getenv('MMGEN_TEST_SUITE')) http_timeout = 60 diff --git a/mmgen/main_tool.py b/mmgen/main_tool.py index c302e7a7..7c007db9 100755 --- a/mmgen/main_tool.py +++ b/mmgen/main_tool.py @@ -31,7 +31,7 @@ supported commands), use '-' as the first argument. 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 + hex2wif - convert a private key from hex to WIF format (use 'pubkey_type=zcash_z' for zcash-z key) 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 diff --git a/mmgen/obj.py b/mmgen/obj.py index 702d381e..975183cf 100755 --- a/mmgen/obj.py +++ b/mmgen/obj.py @@ -538,9 +538,8 @@ class WifKey(str,Hilite,InitErrors): try: assert set(s) <= set(ascii_letters+digits),'not an ascii string' from mmgen.globalvars import g - if g.proto.wif2hex(s): - return str.__new__(cls,s) - raise ValueError,'failed verification' + g.proto.wif2hex(s) # raises exception on error + return str.__new__(cls,s) except Exception as e: m = '{!r}: invalid value for WIF key ({})'.format(s,e[0]) return cls.init_fail(m,on_fail) @@ -566,7 +565,8 @@ class PrivKey(str,Hilite,InitErrors,MMGenObject): wif = MMGenImmutableAttr('wif',WifKey,typeconv=False) # initialize with (priv_bin,compressed), WIF or self - def __new__(cls,s=None,compressed=None,wif=None,on_fail='die'): + def __new__(cls,s=None,compressed=None,wif=None,pubkey_type=None,on_fail='die'): + from mmgen.globalvars import g if type(s) == cls: return s assert wif or (s and type(compressed) == bool),'Incorrect args for PrivKey()' @@ -575,31 +575,28 @@ class PrivKey(str,Hilite,InitErrors,MMGenObject): if wif: try: assert set(wif) <= set(ascii_letters+digits),'not an ascii string' - from mmgen.globalvars import g - w2h = g.proto.wif2hex(wif) - assert w2h,"wif2hex() failed for wif key {!r}".format(wif) + w2h = g.proto.wif2hex(wif) # raises exception on error me = str.__new__(cls,w2h['hex']) me.compressed = w2h['compressed'] - me.wif = str.__new__(WifKey,wif) # check has been done + me.pubkey_type = w2h['pubkey_type'] + me.wif = str.__new__(WifKey,wif) # check has been done return me except Exception as e: fs = "Value {!r} cannot be converted to WIF key ({})" return cls.init_fail(fs.format(wif,e[0]),on_fail) try: - from binascii import hexlify assert len(s) == cls.width / 2,'Key length must be {}'.format(cls.width/2) - me = str.__new__(cls,hexlify(s)) + me = str.__new__(cls,g.proto.preprocess_key(s.encode('hex'),pubkey_type)) me.compressed = compressed - me.wif = me.towif() + me.pubkey_type = pubkey_type + if me.pubkey_type: # skip WIF creation for passwds + me.wif = WifKey(g.proto.hex2wif(me,pubkey_type,compressed),on_fail='raise') return me except Exception as e: 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): - from mmgen.globalvars import g - return WifKey(g.proto.hex2wif(self,compressed=self.compressed),on_fail='raise') class AddrListID(str,Hilite,InitErrors,MMGenObject): width = 10 @@ -674,25 +671,35 @@ class MMGenAddrType(str,Hilite,InitErrors,MMGenObject): color = 'blue' mmtypes = { # 'name' is used to cook the seed, so it must never change! 'L': { 'name':'legacy', - 'comp':False, - 'gen':'p2pkh', - 'fmt':'p2pkh', + 'pubkey_type':'std', + 'compressed':False, + 'gen_method':'p2pkh', + 'addr_fmt':'p2pkh', 'desc':'Legacy uncompressed address'}, 'C': { 'name':'compressed', - 'comp':True, - 'gen':'p2pkh', - 'fmt':'p2pkh', + 'pubkey_type':'std', + 'compressed':True, + 'gen_method':'p2pkh', + 'addr_fmt':'p2pkh', 'desc':'Compressed P2PKH address'}, 'S': { 'name':'segwit', - 'comp':True, - 'gen':'segwit', - 'fmt':'p2sh', + 'pubkey_type':'std', + 'compressed':True, + 'gen_method':'segwit', + 'addr_fmt':'p2sh', 'desc':'Segwit P2SH-P2WPKH address' }, 'E': { 'name':'ethereum', - 'comp':False, - 'gen':'ethereum', - 'fmt':'ethereum', + 'pubkey_type':'std', + 'compressed':False, + 'gen_method':'ethereum', + 'addr_fmt':'ethereum', 'desc':'Ethereum address' }, + 'Z': { 'name':'zcash_z', + 'pubkey_type':'zcash_z', + 'compressed':False, + 'gen_method':'zcash_z', + 'addr_fmt':'zcash_z', + 'desc':'Zcash z-address' }, } def __new__(cls,s,on_fail='die',errmsg=None): if type(s) == cls: return s @@ -705,11 +712,8 @@ class MMGenAddrType(str,Hilite,InitErrors,MMGenObject): me = str.__new__(cls,s) assert me in g.proto.mmtypes + ('P',), ( "'{}': invalid address type for {}".format(me,g.proto.__name__)) - me.name = v['name'] - me.compressed = v['comp'] - me.gen_method = v['gen'] - me.desc = v['desc'] - me.addr_fmt = v['fmt'] + for k in ('name','pubkey_type','compressed','gen_method','addr_fmt','desc'): + setattr(me,k,v[k]) return me raise ValueError,'not found' except Exception as e: @@ -724,8 +728,9 @@ class MMGenAddrType(str,Hilite,InitErrors,MMGenObject): class MMGenPasswordType(MMGenAddrType): mmtypes = { 'P': { 'name':'password', - 'comp':False, - 'gen':None, - 'fmt':None, + 'pubkey_type':None, + 'compressed':False, + 'gen_method':None, + 'addr_fmt':None, 'desc':'Password generated from MMGen seed'} } diff --git a/mmgen/protocol.py b/mmgen/protocol.py index ee21aac9..a2516555 100755 --- a/mmgen/protocol.py +++ b/mmgen/protocol.py @@ -50,15 +50,25 @@ def _numtob58(num): def _b58tonum(b58num): b58num = b58num.strip() for i in b58num: - if not i in _b58a: return False + if not i in _b58a: + raise ValueError,'_b58tonum(): invalid b58 value' return sum(_b58a.index(n) * (58**i) for i,n in enumerate(list(b58num[::-1]))) +def _b58chk_encode(hexstr): + return _numtob58(int(hexstr+hash256(hexstr)[:8],16)) + +def _b58chk_decode(s): + hexstr = '{:x}'.format(_b58tonum(s)) + if hexstr[-8:] == hash256(hexstr[:-8])[:8]: + return hexstr[:-8] + raise ValueError,'_b58chk_decode(): checksum incorrect' + # chainparams.cpp class BitcoinProtocol(MMGenObject): name = 'bitcoin' daemon_name = 'bitcoind' addr_ver_num = { 'p2pkh': ('00','1'), 'p2sh': ('05','3') } - wif_ver_num = '80' + wif_ver_num = { 'std': '80' } mmtypes = ('L','C','S') dfl_mmtype = 'L' data_subdir = '' @@ -92,12 +102,31 @@ class BitcoinProtocol(MMGenObject): def cap(cls,s): return s in cls.caps @classmethod - def hex2wif(cls,hexpriv,compressed=False): - s = cls.wif_ver_num + hexpriv + ('','01')[bool(compressed)] - return _numtob58(int(s+hash256(s)[:8],16)) + def preprocess_key(cls,hexpriv,pubkey_type): return hexpriv + + @classmethod + def hex2wif(cls,hexpriv,pubkey_type,compressed): + return _b58chk_encode(cls.wif_ver_num[pubkey_type] + hexpriv + ('','01')[bool(compressed)]) @classmethod def wif2hex(cls,wif): + key = _b58chk_decode(wif) + pubkey_type = None + for k,v in cls.wif_ver_num.items(): + if key[:len(v)] == v: + pubkey_type = k + key = key[len(v):] + assert pubkey_type,'invalid WIF version number' + if len(key) == 66: + assert key[-2:] == '01','invalid compressed key suffix' + compressed = True + else: + assert len(key) == 64,'invalid key length' + compressed = False + return { 'hex':key[:64], 'pubkey_type':pubkey_type, 'compressed':compressed } + + @classmethod + def wif2hex_old(cls,wif): num = _b58tonum(wif) if num == False: return False key = '{:x}'.format(num) @@ -105,7 +134,7 @@ class BitcoinProtocol(MMGenObject): compressed = len(key) == 76 if compressed and key[66:68] != '01': return False klen = (66,68)[compressed] - if (key[:2] == cls.wif_ver_num and key[klen:] == hash256(key[:klen])[:8]): + if (key[:2] == cls.wif_ver_num['std'] and key[klen:] == hash256(key[:klen])[:8]): return { 'hex':key[2:66], 'compressed':compressed } else: return False @@ -126,7 +155,7 @@ class BitcoinProtocol(MMGenObject): if hash256(addr_hex[:-8])[:8] == addr_hex[-8:]: return { 'hex': addr_hex[len(ver_num):-8], - 'format': {'p2pkh':'p2pkh','p2sh':'p2sh','p2sh2':'p2sh'}[addr_fmt], + 'format': {'p2pkh':'p2pkh','p2sh':'p2sh','p2sh2':'p2sh','zcash_z':'zcash_z'}[addr_fmt], 'width': cls.addr_width } if return_dict else True else: @@ -139,7 +168,7 @@ class BitcoinProtocol(MMGenObject): 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)) + return ('1' * lzeroes) + _b58chk_encode(s) # Segwit: @classmethod @@ -155,7 +184,7 @@ class BitcoinProtocol(MMGenObject): class BitcoinTestnetProtocol(BitcoinProtocol): addr_ver_num = { 'p2pkh': ('6f',('m','n')), 'p2sh': ('c4','2') } - wif_ver_num = 'ef' + wif_ver_num = { 'std': 'ef' } data_subdir = 'testnet' daemon_data_subdir = 'testnet3' rpc_port = 18332 @@ -184,7 +213,7 @@ class BitcoinCashProtocol(BitcoinProtocol): class BitcoinCashTestnetProtocol(BitcoinCashProtocol): rpc_port = 18442 addr_ver_num = { 'p2pkh': ('6f',('m','n')), 'p2sh': ('c4','2') } - wif_ver_num = 'ef' + wif_ver_num = { 'std': 'ef' } data_subdir = 'testnet' daemon_data_subdir = 'testnet3' addr_width = 35 @@ -202,7 +231,7 @@ class B2XProtocol(BitcoinProtocol): class B2XTestnetProtocol(B2XProtocol): addr_ver_num = { 'p2pkh': ('6f',('m','n')), 'p2sh': ('c4','2') } - wif_ver_num = 'ef' + wif_ver_num = { 'std': 'ef' } data_subdir = 'testnet' daemon_data_subdir = 'testnet5' rpc_port = 18338 @@ -215,7 +244,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 - wif_ver_num = 'b0' + wif_ver_num = { 'std': 'b0' } secs_per_block = 150 rpc_port = 9332 coin_amt = LTCAmt @@ -226,7 +255,7 @@ class LitecoinProtocol(BitcoinProtocol): class LitecoinTestnetProtocol(LitecoinProtocol): # addr ver nums same as Bitcoin testnet, except for 'p2sh' addr_ver_num = { 'p2pkh': ('6f',('m','n')), 'p2sh': ('3a','Q'), 'p2sh2': ('c4','2') } - wif_ver_num = 'ef' # same as Bitcoin testnet + wif_ver_num = { 'std': 'ef' } # same as Bitcoin testnet data_subdir = 'testnet' daemon_data_subdir = 'testnet4' rpc_port = 19332 @@ -234,6 +263,8 @@ class LitecoinTestnetProtocol(LitecoinProtocol): class BitcoinProtocolAddrgen(BitcoinProtocol): mmcaps = ('key','addr') class BitcoinProtocolKeygen(BitcoinProtocol): mmcaps = ('key',) +class BitcoinTestnetProtocolAddrgen(BitcoinTestnetProtocol): mmcaps = ('key','addr') +class BitcoinTestnetProtocolKeygen(BitcoinTestnetProtocol): mmcaps = ('key',) class EthereumProtocol(BitcoinProtocolAddrgen): @@ -244,13 +275,13 @@ class EthereumProtocol(BitcoinProtocolAddrgen): base_coin = 'ETH' @classmethod - def hex2wif(cls,hexpriv,compressed=False): + def hex2wif(cls,hexpriv,pubkey_type,compressed): assert compressed == False,'Ethereum does not support compressed pubkeys!' return str(hexpriv) @classmethod def wif2hex(cls,wif): - return { 'hex':str(wif), 'compressed':False } + return { 'hex':str(wif), 'pubkey_type':'std', 'compressed':False } @classmethod def verify_addr(cls,addr,return_dict=False): @@ -268,25 +299,34 @@ 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',) + addr_ver_num = { 'p2pkh': ('1cb8','t1'), 'p2sh': ('1cbd','t3'), 'zcash_z': ('169a','zc') } + wif_ver_num = { 'std': '80', 'zcash_z': 'ab36' } + mmtypes = ('C','Z') dfl_mmtype = 'C' -class ZcashTestnetProtocol(object): pass + @classmethod + def preprocess_key(cls,hexpriv,pubkey_type): # zero the first four bits + if pubkey_type == 'zcash_z': + return '{:02x}'.format(int(hexpriv[:2],16) & 0x0f) + hexpriv[2:] + else: + return hexpriv + +class ZcashTestnetProtocol(ZcashProtocol): + wif_ver_num = { 'std': '??', 'zcash_z': 'ac08' } + addr_ver_num = { 'p2pkh': ('??','t1'), 'p2sh': ('??','t3'), 'zcash_z': ('16b6','??') } class DashProtocol(BitcoinProtocolAddrgen): name = 'dash' base_coin = 'DASH' addr_ver_num = { 'p2pkh': ('4c','X'), 'p2sh': ('10','7') } - wif_ver_num = 'cc' + wif_ver_num = { 'std': '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' + wif_ver_num = { 'std': 'ef' } class CoinProtocol(MMGenObject): coins = { diff --git a/mmgen/sha256.py b/mmgen/sha256.py new file mode 100755 index 00000000..167f06b8 --- /dev/null +++ b/mmgen/sha256.py @@ -0,0 +1,144 @@ +#!/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 . + +""" +sha256.py: custom sha256 implementation for Zcash +""" + +# Sha256 code ported from JavaScript, originally here: +# CryptoJS v3.1.2 code.google.com/p/crypto-js +# (c) 2009-2013 by Jeff Mott. All rights reserved. +# code.google.com/p/crypto-js/wiki/License +# via here: +# https://www.npmjs.com/package/sha256 +# and here: +# https://github.com/howardwu/zaddr + +class Sha256(object): + + def initConstants(): + import math + def isPrime(n): + for factor in range(2,int(math.sqrt(n))+1): + if not (n % factor): return False + return True + + def getFractionalBits(n): + return int((n - int(n)) * 0x100000000) + + def toSigned32(n): return ((n & 0xffffffff) ^ 0x80000000) - 0x80000000 + + k = [0] * 64 + n,nPrime = 2,0 + while nPrime < 64: + if isPrime(n): + k[nPrime] = getFractionalBits(math.pow(n, 1.0 / 3)) + # for testing against signed implementations: +# k[nPrime] = toSigned32(getFractionalBits(math.pow(n, 1.0 / 3))) + nPrime += 1 + n += 1 + + return k + + K = initConstants() + + # preprocess: True=Sha256(), False=Sha256Compress() + def __init__(self,message,preprocess=True): + self.H = [0x6A09E667,0xBB67AE85,0x3C6EF372,0xA54FF53A,0x510E527F,0x9B05688C,0x1F83D9AB,0x5BE0CD19] + self.M = message + self.W = [0] * 64 + (self.bytesToWords,self.preprocessBlock)[preprocess]() +# self.initConstants() + self.compute() + + def digest(self): + self.M = self.H + self.wordsToBytes() + return self.M + + def hexdigest(self): + return self.digest().encode('hex') + + def bytesToWords(self): + assert type(self.M) == str + words = [0] * (len(self.M) / 4 + len(self.M) % 4) + b = 0 + for i in range(len(self.M)): + words[b >> 5] |= ord(self.M[i]) << (24 - b % 32) + b += 8 + self.M = words + + def wordsToBytes(self): + assert type(self.M) == list + self.M = ''.join([chr((self.M[b >> 5] >> (24 - b % 32)) & 0xff) for b in range(0,len(self.M)*32,8)]) + + def preprocessBlock(self): + def lshift(a,b): return (a << b) & 0xffffffff + assert type(self.M) == str + l = len(self.M) * 8 + self.bytesToWords() + last_idx = lshift((l + 64 >> 9),4) + 15 + self.M.extend([0] * (last_idx - len(self.M) + 1)) + self.M[l >> 5] |= lshift(0x80, (24 - l % 32)) + self.M[last_idx] = l + + def compute(self): + assert type(self.M) == list + for i in range(0,len(self.M),16): + self.processBlock(i) + + def processBlock(self,offset): + def lshift(a,b): return (a << b) & 0xffffffff + def sumr(a,b): return (a + b) & 0xffffffff + a,b,c,d,e,f,g,h = [self.H[i] for i in range(8)] # Working variables + + for i in range(64): + if i < 16: + self.W[i] = self.M[offset + i] + else: + gamma0x = self.W[i - 15] + gamma0 = ( (lshift(gamma0x,25) | (gamma0x >> 7)) ^ + (lshift(gamma0x,14) | (gamma0x >> 18)) ^ + (gamma0x >> 3) ) + gamma1x = self.W[i - 2] + gamma1 = ( (lshift(gamma1x,15) | (gamma1x >> 17)) ^ + (lshift(gamma1x,13) | (gamma1x >> 19)) ^ + (gamma1x >> 10) ) + self.W[i] = sumr(sumr(sumr(gamma0,self.W[i - 7]),gamma1),self.W[i - 16]) + + ch = (e & f) ^ (~e & g) + maj = (a & b) ^ (a & c) ^ (b & c) + + sigma0 = (lshift(a,30) | (a >> 2)) ^ (lshift(a,19) | (a >> 13)) ^ (lshift(a,10) | (a >> 22)) + sigma1 = (lshift(e,26) | (e >> 6)) ^ (lshift(e,21) | (e >> 11)) ^ (lshift(e,7) | (e >> 25)) + + t1 = sumr(sumr(sumr(sumr(h,sigma1),ch),self.K[i]),self.W[i]) + t2 = sumr(sigma0,maj) + + h = g + g = f + f = e + e = sumr(d,t1) + d = c + c = b + b = a + a = sumr(t1,t2) + + # Intermediate hash value + for n,v in enumerate([a,b,c,d,e,f,g,h]): + self.H[n] = sumr(self.H[n],v) diff --git a/mmgen/tool.py b/mmgen/tool.py index 8a756184..24257c5f 100755 --- a/mmgen/tool.py +++ b/mmgen/tool.py @@ -55,16 +55,16 @@ cmd_data = OrderedDict([ ('Hexlify', [' [str-]']), ('Rand2file', [' [str]',' [str]','threads [int=4]','silent [bool=False]']), - ('Randwif', ['compressed [bool=False]']), - ('Randpair', ['compressed [bool=False]','segwit [bool=False]']), - ('Hex2wif', [' [str-]','compressed [bool=False]']), + ('Randwif', ["pubkey_type [str='std']",'compressed [bool=False]']), + ('Randpair', ["pubkey_type [str='std']",'compressed [bool=False]','segwit [bool=False]']), + ('Hex2wif', [' [str-]',"pubkey_type [str='std']",'compressed [bool=False]']), ('Wif2hex', [' [str-]']), ('Wif2addr', [' [str-]','segwit [bool=False]']), ('Wif2segwit_pair',[' [str-]']), ('Pubhash2addr', [' [str-]','p2sh [bool=False]']), ('Addr2hexaddr', [' [str-]']), - ('Privhex2addr', [' [str-]','compressed [bool=False]','segwit [bool=False]']), - ('Privhex2pubhex',[' [str-]','compressed [bool=False]']), + ('Privhex2addr', [' [str-]',"pubkey_type [str='std']",'compressed [bool=False]','segwit [bool=False]']), + ('Privhex2pubhex',[' [str-]',"pubkey_type [str='std']",'compressed [bool=False]']), ('Pubhex2addr', [' [str-]','p2sh [bool=False]']), # new ('Pubhex2redeem_script',[' [str-]']), # new ('Wif2redeem_script', [' [str-]']), # new @@ -85,8 +85,8 @@ cmd_data = OrderedDict([ ('Add_label', ['<{} address> [str]'.format(pnm),'