Browse Source

Zcash z-address generation with custom sha256 implementation

- Tests have been updated, test with `scripts/test-release.sh -Pn master alts`
MMGen 7 years ago
parent
commit
1cc64e1eb9

+ 62 - 18
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):

+ 3 - 3
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     = '<mmgen@tuta.io>'
 	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

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

+ 39 - 34
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'}
 	}

+ 61 - 21
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 = {

+ 144 - 0
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 <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/>.
+
+"""
+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)

+ 26 - 27
mmgen/tool.py

@@ -55,16 +55,16 @@ cmd_data = OrderedDict([
 	('Hexlify',      ['<string> [str-]']),
 	('Rand2file',    ['<outfile> [str]','<nbytes> [str]','threads [int=4]','silent [bool=False]']),
 
-	('Randwif',    ['compressed [bool=False]']),
-	('Randpair',   ['compressed [bool=False]','segwit [bool=False]']),
-	('Hex2wif',    ['<private key in hex format> [str-]','compressed [bool=False]']),
+	('Randwif',    ["pubkey_type [str='std']",'compressed [bool=False]']),
+	('Randpair',   ["pubkey_type [str='std']",'compressed [bool=False]','segwit [bool=False]']),
+	('Hex2wif',    ['<private key in hex format> [str-]',"pubkey_type [str='std']",'compressed [bool=False]']),
 	('Wif2hex',    ['<wif> [str-]']),
 	('Wif2addr',   ['<wif> [str-]','segwit [bool=False]']),
 	('Wif2segwit_pair',['<wif> [str-]']),
 	('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]']),
+	('Privhex2addr', ['<private key in hex format> [str-]',"pubkey_type [str='std']",'compressed [bool=False]','segwit [bool=False]']),
+	('Privhex2pubhex',['<private key in hex format> [str-]',"pubkey_type [str='std']",'compressed [bool=False]']),
 	('Pubhex2addr',  ['<public key in hex format> [str-]','p2sh [bool=False]']), # new
 	('Pubhex2redeem_script',['<public key in hex format> [str-]']), # new
 	('Wif2redeem_script', ['<private key in WIF format> [str-]']), # new
@@ -85,8 +85,8 @@ cmd_data = OrderedDict([
 
 	('Add_label',       ['<{} address> [str]'.format(pnm),'<label> [str]']),
 	('Remove_label',    ['<{} address> [str]'.format(pnm)]),
-	('Addrfile_chksum', ['<{} addr file> [str]'.format(pnm)]),
-	('Keyaddrfile_chksum', ['<{} addr file> [str]'.format(pnm)]),
+	('Addrfile_chksum', ['<{} addr file> [str]'.format(pnm),"mmtype [str='']"]),
+	('Keyaddrfile_chksum', ['<{} addr file> [str]'.format(pnm),"mmtype [str='']"]),
 	('Passwdfile_chksum', ['<{} password file> [str]'.format(pnm)]),
 	('Find_incog_data', ['<file or device name> [str]','<Incog ID> [str]','keep_searching [bool=False]']),
 
@@ -226,7 +226,7 @@ def print_convert_results(indata,enc,dec,dtype):
 	if error:
 		die(3,"Error! Recoded data doesn't match input!")
 
-kg = KeyGenerator()
+kg = KeyGenerator('std')
 
 def Hexdump(infile, cols=8, line_nums=True):
 	Msg(pretty_hexdump(
@@ -249,13 +249,13 @@ def B58randenc():
 def Randhex(nbytes='32'):
 	Msg(binascii.hexlify(get_random(int(nbytes))))
 
-def Randwif(compressed=False):
-	Msg(PrivKey(get_random(32),compressed).wif)
+def Randwif(pubkey_type='std',compressed=False):
+	Msg(PrivKey(get_random(32),compressed=compressed,pubkey_type=pubkey_type).wif)
 
-def Randpair(compressed=False,segwit=False):
+def Randpair(pubkey_type='std',compressed=False,segwit=False):
 	if segwit: compressed = True
 	ag = AddrGenerator(('p2pkh','segwit')[bool(segwit)])
-	privhex = PrivKey(get_random(32),compressed)
+	privhex = PrivKey(get_random(32),compressed=compressed,pubkey_type=pubkey_type)
 	addr = ag.to_addr(kg.to_pubhex(privhex))
 	Vmsg('Key (hex):  %s' % privhex)
 	Vmsg_r('Key (WIF):  '); Msg(privhex.wif)
@@ -283,18 +283,19 @@ def Pubhash2addr(pubhash,p2sh=False):   Msg(g.proto.pubhash2addr(pubhash,p2sh=p2
 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):
+def Wif2hex(wif):                       Msg(PrivKey(wif=wif))
+
+def Hex2wif(hexpriv,pubkey_type='std',compressed=False):
+	Msg(g.proto.hex2wif(hexpriv,pubkey_type=pubkey_type,compressed=compressed))
+def Privhex2addr(privhex,pubkey_type='std',compressed=False,segwit=False,output_pubhex=False):
 	if segwit and not compressed:
 		die(1,'Segwit address can be generated only from a compressed pubkey')
-	pk = PrivKey(binascii.unhexlify(privhex),compressed=compressed)
+	pk = PrivKey(binascii.unhexlify(privhex),compressed=compressed,pubkey_type=pubkey_type)
 	ph = kg.to_pubhex(pk)
 	ag = AddrGenerator(('p2pkh','segwit')[bool(segwit)])
 	Msg(ph if output_pubhex else ag.to_addr(ph))
-def Privhex2pubhex(privhex,compressed=False): # new
-	return Privhex2addr(privhex,compressed=compressed,output_pubhex=True)
+def Privhex2pubhex(privhex,pubkey_type='std',compressed=False): # new
+	return Privhex2addr(privhex,pubkey_type=pubkey_type,compressed=compressed,output_pubhex=True)
 def Pubhex2redeem_script(pubhex): # new
 	Msg(g.proto.pubhex2redeem_script(pubhex))
 def Wif2redeem_script(wif): # new
@@ -304,10 +305,6 @@ def Wif2redeem_script(wif): # new
 	ag = AddrGenerator('segwit')
 	Msg(ag.to_segwit_redeem_script(kg.to_pubhex(privhex)))
 
-def wif2hex(wif): # wrapper
-	ret = PrivKey(wif=wif)
-	return ret or die(1,'{}: Invalid WIF'.format(wif))
-
 wordlists = 'electrum','tirosh'
 dfl_wl_id = 'electrum'
 
@@ -353,13 +350,15 @@ def Id6(infile):
 def Str2id6(s): # retain ignoring of space for backwards compat
 	Msg(make_chksum_6(''.join(s.split())))
 
-def Addrfile_chksum(infile):
+def Addrfile_chksum(infile,mmtype=''):
 	from mmgen.addr import AddrList
-	AddrList(infile,chksum_only=True)
+	mmtype = None if not mmtype else MMGenAddrType(mmtype)
+	AddrList(infile,chksum_only=True,mmtype=mmtype)
 
-def Keyaddrfile_chksum(infile):
+def Keyaddrfile_chksum(infile,mmtype=''):
 	from mmgen.addr import KeyAddrList
-	KeyAddrList(infile,chksum_only=True)
+	mmtype = None if not mmtype else MMGenAddrType(mmtype)
+	KeyAddrList(infile,chksum_only=True,mmtype=mmtype)
 
 def Passwdfile_chksum(infile):
 	from mmgen.addr import PasswordList

+ 1 - 1
mmgen/tx.py

@@ -597,7 +597,7 @@ class MMGenTX(MMGenObject):
 
 		if self.has_segwit_inputs():
 			from mmgen.addr import KeyGenerator,AddrGenerator
-			kg = KeyGenerator()
+			kg = KeyGenerator('std')
 			ag = AddrGenerator('segwit')
 			keydict = MMGenDict([(d.addr,d.sec) for d in keys])
 

+ 3 - 3
scripts/test-release.sh

@@ -99,15 +99,15 @@ 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/scrambletest.py'
 	'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=zec 2:ext 100'
 	'test/gentest.py --coin=etc 2:ext 100'
 	'test/gentest.py --coin=eth 2:ext 100'
-	'test/scrambletest.py'
-	)
+	'test/gentest.py --coin=zec --type=zcash_z 2:ext 1000')
 f_alts='Gen-only altcoin tests completed'
 
 i_misc='Miscellaneous operations' # includes autosign!

+ 1 - 0
setup.py

@@ -123,6 +123,7 @@ setup(
 			'mmgen.regtest',
 			'mmgen.rpc',
 			'mmgen.seed',
+			'mmgen.sha256',
 			'mmgen.term',
 			'mmgen.test',
 			'mmgen.tool',

+ 35 - 17
test/gentest.py

@@ -38,6 +38,7 @@ opts_data = lambda: {
 -h, --help       Print this help message
 --, --longhelp   Print help message for long options (common options)
 -q, --quiet      Produce quieter output
+-a, --type=      Specify address type (options: 'std','zcash_z')
 -s, --segwit     Generate Segwit (P2SH-P2WPKH) addresses
 -v, --verbose    Produce more verbose output
 """,
@@ -72,9 +73,16 @@ cmd_args = opts.init(opts_data,add_opts=['exact_output'])
 
 if not 1 <= len(cmd_args) <= 2: opts.usage()
 
+addr_type = opt.type or 'std'
+
 def pyethereum_sec2addr(sec):
 	return sec,eth.privtoaddr(sec).encode('hex')
 
+def zcash_mini_sec2addr(sec):
+	p = sp.Popen(['zcash-mini','-key','-simple'],stderr=sp.PIPE,stdin=sp.PIPE,stdout=sp.PIPE)
+	p.stdin.write(sec.wif+'\n')
+	return sec.wif,p.stdout.read().split()[0]
+
 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]
@@ -123,12 +131,16 @@ else:
 		a = int(a)
 		assert 1 <= a <= len(g.key_generators)
 		if b == 'ext':
-			if g.coin in ('ETH','ETC'):
+			if addr_type == 'zcash_z':
+				import subprocess as sp
+				ext_sec2addr = zcash_mini_sec2addr
+				ext_lib = 'zcash_mini'
+			elif 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_sec2addr = pyethereum_sec2addr
 				ext_lib = 'pyethereum'
 			else:
 				try:
@@ -136,7 +148,7 @@ else:
 				except:
 					die(1,"Unable to import module 'ku'. Is pycoin installed?")
 				PREFIX_TRANSFORMS = pcku.prefix_transforms_for_network(g.coin)
-				sec2addr = pycoin_sec2addr
+				ext_sec2addr = pycoin_sec2addr
 				ext_lib = 'pycoin'
 		else:
 			b = int(b)
@@ -156,27 +168,33 @@ def match_error(sec,wif,a_addr,b_addr,a,b):
 """.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 = False if g.coin in ('ETH','ETC') else True
+no_compressed     = g.coin in ('ETH','ETC') or addr_type == 'zcash_z'
+no_uncompressed   = opt.segwit or g.coin == 'DASH' or (g.coin=='ZEC' and addr_type == 'std')
+switch_compressed = not no_compressed and not no_uncompressed
+compressed        = not no_compressed
 
 from mmgen.addr import KeyGenerator,AddrGenerator
 from mmgen.obj import PrivKey
-ag = AddrGenerator('ethereum' if g.coin in ('ETH','ETC') else ('p2pkh','segwit')[bool(opt.segwit)])
+ag = AddrGenerator(
+	'ethereum' if g.coin in ('ETH','ETC')
+	else 'zcash_z' if addr_type == 'zcash_z'
+	else ('p2pkh','segwit')[bool(opt.segwit)])
 
 if a and b:
 	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)
-	if b != 'ext': kg_b = KeyGenerator(b)
+	kg_a = KeyGenerator(addr_type,a)
+	if b != 'ext': kg_b = KeyGenerator(addr_type,b)
+	qmsg(green(m.format(kg_a.desc,(ext_lib if b == 'ext' else kg_b.desc),g.coin)))
 
 	for i in range(rounds):
 		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)
+		sec = PrivKey(os.urandom(32),compressed=compressed,pubkey_type=addr_type)
 		a_addr = ag.to_addr(kg_a.to_pubhex(sec))
 		if b == 'ext':
-			b_wif,b_addr = sec2addr(sec)
+			b_wif,b_addr = ext_sec2addr(sec)
 			if b_wif != sec.wif:
 				match_error(sec,sec.wif,sec.wif,b_wif,a,b)
 		else:
@@ -184,38 +202,38 @@ if a and b:
 		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,ext_lib if b == 'ext' else b)
-		if not opt.segwit and 'L' in g.proto.mmtypes:
+		if switch_compressed:
 			compressed = not compressed
 	qmsg_r('\rRound %s/%s ' % (i+1,rounds))
 
 	qmsg(green(('\n','')[bool(opt.verbose)] + 'OK'))
 elif a and not fh:
+	kg = KeyGenerator(addr_type,a)
 	m = "Testing speed of address generator '{}' for coin {}"
-	qmsg(green(m.format(g.key_generators[a-1],g.coin)))
+	qmsg(green(m.format(kg.desc,g.coin)))
 	from struct import pack,unpack
 	seed = os.urandom(28)
 	print 'Incrementing key with each round'
 	print 'Starting key:', hexlify(seed+pack('I',0))
 	import time
 	start = last_t = time.time()
-	kg = KeyGenerator(a)
 
 	for i in range(rounds):
 		if time.time() - last_t >= 0.1:
 			qmsg_r('\rRound %s/%s ' % (i+1,rounds))
 			last_t = time.time()
-		sec = PrivKey(seed+pack('I',i),compressed)
+		sec = PrivKey(seed+pack('I',i),compressed=compressed,pubkey_type=addr_type)
 		a_addr = ag.to_addr(kg.to_pubhex(sec))
 		vmsg('\nkey:  %s\naddr: %s\n' % (sec.wif,a_addr))
-		if not opt.segwit and g.coin not in ('ETC','ETC'):
+		if switch_compressed:
 			compressed = not compressed
 	qmsg_r('\rRound %s/%s ' % (i+1,rounds))
 
 	qmsg('\n{} addresses generated in {:.2f} seconds'.format(rounds,time.time()-start))
 elif a and dump:
+	kg = KeyGenerator(addr_type,a)
 	m = "Comparing output of address generator '{}' against wallet dump '{}'"
-	qmsg(green(m.format(g.key_generators[a-1],cmd_args[1])))
-	kg = KeyGenerator(a)
+	qmsg(green(m.format(kg.desc,cmd_args[1])))
 	for n,[wif,a_addr] in enumerate(dump,1):
 		qmsg_r('\rKey %s/%s ' % (n,len(dump)))
 		try:

+ 19 - 0
test/ref/zcash/98831F3A-ZEC-Z[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-Z[1,31-33,500-501,1010-1011]: 9C7A 72DC 3D4A B3AF
+# Record this value to a secure location.
+98831F3A ZEC:ZCASH_Z {
+  1     zcLMMsnzfFYZWU4avRBnuc83yh4jTtJXbtP32uWrs3ickzu1krMU4ppZCQPTwwfE9hLnRuFDSYF8VFW13aT9eeQK8aov3Ge
+  31    zcYNDA2gcYTU3fUGNRoyBS7KDN3gqGY3STRGeJViwkwmqFqAFPuw3UzXnbmtNPVvp5EQn4H6opjF8nd3touN2J9QK6HrDVW
+  32    zcG4tWh4WmrD2RUhXKE3PU8R7oPnPaSgsKmf2gCCGbQPL5RG1tjcZojHwhVjCxbsUqGgV4hy6gfYsYAgsvFCsLTKpqbENuP
+  33    zcfWPcKVvKAVRuY3rG8m7RgrCSB7pWYiXfQpr3ppWaHwQ6rWGrXp8YfeZMq53ZYZ4E7RASm8DvcnCW3bVWTsC5vgEkRpmYS
+  500   zcdnkn6chKxCVuZ1wstUdGUHx4hYNsDeErqwc8zXuZnGekTVqYLTWzNWT5LgJSZYaVnJ8wiL1mWJuMV6ACh3yaC68p39nVT
+  501   zcQctEy3rwKuKSXPFBzanTVJGGbWrSCfymQt18rcRC41pdb8g2A2GbRdxkdBRs9WysDwGWySweYZzwQWL2LvoqrGeuTaZAh
+  1010  zcFeZC94UYSRfUg4MsxsPzmczj93WCuSRAqBsFwCQ3sqpg1LE1eksyRRSis511fBsse1GpJdqW35VAcAtR2rswxEAtwW6jt
+  1011  zcanQ9hr8FpxGyBG1HYYBLCkftFf9tyqGpzYUe6eVM5fHuZJUMv22Vpb29B8iHjYxXq1WQHHGNmZrzHW5btwAMrUAom584A
+}

BIN
test/ref/zcash/98831F3A-ZEC-Z[1,31-33,500-501,1010-1011].akeys.mmenc


+ 22 - 21
test/scrambletest.py

@@ -17,7 +17,7 @@
 # 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
+test/scrambletest.py: seed scrambling and addrlist data generation tests for all supported altcoins
 """
 
 import sys,os,subprocess
@@ -29,7 +29,7 @@ sys.path.__setitem__(0,repo_root)
 from mmgen.common import *
 
 opts_data = lambda: {
-	'desc': 'Test seed scrambling and addrlist metadata generation for all supported altcoins',
+	'desc': 'Test seed scrambling and addrlist data generation for all supported altcoins',
 	'usage':'[options] [command]',
 	'options': """
 -h, --help          Print this help message
@@ -53,24 +53,25 @@ if not opt.system:
 
 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')),
+#                  SCRAMBLED_SEED[:8] SCRAMBLE_KEY      ID_STR LBL          FIRST ADDR
+('btc',           ('456d7f5f1c4bfe3b','(none)',         '',    '',          '1MU7EdgqYy9JX35L25hR6CmXXcSEBDAwyv')),
+('btc_compressed',('bf98a4af5464a4ef','compressed',     '-C',  'COMPRESSED','1F97Jd89wwmu4ELadesAdGDzg3d8Y6j5iP')),
+('btc_segwit',    ('b56962d829ffc678','segwit',         '-S',  'SEGWIT',    '36TvVzU5mxSjJ3D9qKAmYzCV7iUqtTDezF')),
+('bch',           ('456d7f5f1c4bfe3b','(none)',         '',    '',          '1MU7EdgqYy9JX35L25hR6CmXXcSEBDAwyv')),
+('bch_compressed',('bf98a4af5464a4ef','compressed',     '-C',  'COMPRESSED','1F97Jd89wwmu4ELadesAdGDzg3d8Y6j5iP')),
+('ltc',           ('b11f16632e63ba92','ltc:legacy',     '-LTC','LTC',       'LMxB474SVfxeYdqxNrM1WZDZMnifteSMv1')),
+('ltc_compressed',('7ccf465d466ee7d3','ltc:compressed', '-LTC-C', 'LTC:COMPRESSED', 'LdkebBKVXSs6NNoPJWGM8KciDnL8LhXXjb')),
+('ltc_segwit',    ('9460f5ba15e82768','ltc:segwit',     '-LTC-S', 'LTC:SEGWIT',     'MQrY3vEbqKMBgegXrSaR93R2HoTDE5bKrY')),
+('eth',           ('213ed116869b19f2','eth',            '-ETH', 'ETH','e704b6cfd9f0edb2e6cfbd0c913438d37ede7b35')),
+('etc',           ('909def37096f5ab8','etc',            '-ETC', 'ETC','1a6acbef8c38f52f20d04ecded2992b04d8608d7')),
+('dash',          ('bb21cf88c198ab8c','dash:compressed','-DASH-C','DASH:COMPRESSED','XsjAJvCxkxYh55ZvCZMFEv2eJUVo5xxbwi')),
+('zec',           ('637f7b8117b524ed','zec:compressed', '-ZEC-C', 'ZEC:COMPRESSED', 't1d47QeTehQye4Mms1Lmx7dPjKVoTtHXKmu')),
+('zec_zcash_z',   ('b15570d033df9b1a','zec:zcash_z',    '-ZEC-Z', 'ZEC:ZCASH_Z',    'zcLMMsnzfFYZWU4avRBnuc83yh4jTtJXbtP32uWrs3ickzu1krMU4ppZCQPTwwfE9hLnRuFDSYF8VFW13aT9eeQK8aov3Ge')),
 ])
 
 def run_tests():
 	for test in test_data:
-		try:    coin,mmtype = test.split('_')
+		try:    coin,mmtype = test.split('_',1)
 		except: coin,mmtype = test,None
 		cmd_name = 'cmds/mmgen-addrgen'
 		wf = 'test/ref/98831F3A.mmwords'
@@ -79,15 +80,15 @@ def run_tests():
 		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)
+		o = p.stdout.read()
+		vmsg(o)
+		o = o.splitlines()
 		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']):
+		d.append('sc_debug_addr: ' + o[-2].split()[-1])
+		for n,k in enumerate(['seed','str','id_str','lbl','addr']):
 			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:

+ 22 - 10
test/test.py

@@ -448,10 +448,12 @@ cfgs = {
 			'ltc': ('B804 978A 8796 3ED4','93A6 844C 8ECC BEF4'),
 		},
 		'ref_addrfile_chksum_zec': '903E 7225 DD86 6E01',
+		'ref_addrfile_chksum_zec_z': '9C7A 72DC 3D4A B3AF',
 		'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_zec_z': '220F 5F23 CC9B EC1F',
 		'ref_keyaddrfile_chksum_dash': 'E83D 2C63 FEA2 4142',
 		'ref_keyaddrfile_chksum_eth': '3635 4DCF B752 8772',
 		'ref_keyaddrfile_chksum_etc': '9BAC 38E7 5C8E 42E0',
@@ -709,14 +711,16 @@ cmd_group['misc'] = (
 )
 
 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_addrfile_chk_dash','reference address file (DASH)'),
+	('ref_addrfile_chk_zec', 'reference address file (ZEC-T)'),
+	('ref_addrfile_chk_zec_z','reference address file (ZEC-Z)'),
 	('ref_keyaddrfile_chk_eth', 'reference key-address file (ETH)'),
 	('ref_keyaddrfile_chk_etc', 'reference key-address file (ETC)'),
+	('ref_keyaddrfile_chk_dash','reference key-address file (DASH)'),
+	('ref_keyaddrfile_chk_zec', 'reference key-address file (ZEC-T)'),
+	('ref_keyaddrfile_chk_zec_z','reference key-address file (ZEC-Z)'),
 )
 
 # undocumented admin cmds
@@ -1041,8 +1045,8 @@ def create_fake_unspent_data(adata,tx_data,non_mmgen_input=''):
 				out.append(create_fake_unspent_entry(coinaddr,d['al_id'],idx,lbl,segwit=d['segwit']))
 
 	if non_mmgen_input:
-		privkey = PrivKey(os.urandom(32),compressed=True)
-		coinaddr = AddrGenerator('p2pkh').to_addr(KeyGenerator().to_pubhex(privkey))
+		privkey = PrivKey(os.urandom(32),compressed=True,pubkey_type='std')
+		coinaddr = AddrGenerator('p2pkh').to_addr(KeyGenerator('std').to_pubhex(privkey))
 		of = os.path.join(cfgs[non_mmgen_input]['tmpdir'],non_mmgen_fn)
 		write_data_to_file(of,privkey.wif+'\n','compressed {} key'.format(g.proto.name),silent=True)
 		out.append(create_fake_unspent_entry(coinaddr,non_mmgen=True,segwit=False))
@@ -1083,7 +1087,7 @@ def create_tx_data(sources):
 def make_txcreate_cmdline(tx_data):
 	privkey = PrivKey(os.urandom(32),compressed=True)
 	t = ('p2pkh','segwit')['S' in g.proto.mmtypes]
-	coinaddr = AddrGenerator(t).to_addr(KeyGenerator().to_pubhex(privkey))
+	coinaddr = AddrGenerator(t).to_addr(KeyGenerator('std').to_pubhex(privkey))
 
 	# total of two outputs must be < 10 BTC (<1000 LTC)
 	mods = {'btc':(6,4),'bch':(6,4),'ltc':(600,400)}[coin_sel]
@@ -2030,12 +2034,12 @@ class MMGenTestSuite(object):
 			t.close()
 			cmp_or_die(cfg['seed_id'],chk)
 
-	def ref_addrfile_chk(self,name,ftype='addr',coin=None,subdir=None,pfx=None,mmtype=None):
+	def ref_addrfile_chk(self,name,ftype='addr',coin=None,subdir=None,pfx=None,mmtype=None,add_args=[]):
 		af_key = 'ref_{}file'.format(ftype)
 		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])
+		t = MMGenExpect(name,'mmgen-tool',coin_arg+[ftype.replace('segwit','')+'file_chksum',af]+add_args)
 		if ftype == 'keyaddr':
 			w = 'key-address data'
 			t.hash_preset(w,ref_kafile_hash_preset)
@@ -2051,6 +2055,10 @@ class MMGenTestSuite(object):
 	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_zec_z(self,name):
+		self.ref_addrfile_chk(name,ftype='addr',coin='ZEC',subdir='zcash',pfx='-ZEC-Z',
+								mmtype='z',add_args=['mmtype=zcash_z'])
+
 	def ref_addrfile_chk_dash(self,name):
 		self.ref_addrfile_chk(name,ftype='addr',coin='DASH',subdir='dash',pfx='-DASH-C')
 
@@ -2063,6 +2071,10 @@ class MMGenTestSuite(object):
 	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_zec_z(self,name):
+		self.ref_addrfile_chk(name,ftype='keyaddr',coin='ZEC',subdir='zcash',pfx='-ZEC-Z',
+								mmtype='z',add_args=['mmtype=zcash_z'])
+
 	def ref_keyaddrfile_chk_dash(self,name):
 		self.ref_addrfile_chk(name,ftype='keyaddr',coin='DASH',subdir='dash',pfx='-DASH-C')
 
@@ -2604,7 +2616,7 @@ class MMGenTestSuite(object):
 		psave = g.proto
 		g.proto = CoinProtocol(g.coin,True)
 		privhex = PrivKey(os.urandom(32),compressed=True)
-		addr = AddrGenerator('p2pkh').to_addr(KeyGenerator().to_pubhex(privhex))
+		addr = AddrGenerator('p2pkh').to_addr(KeyGenerator('std').to_pubhex(privhex))
 		g.proto = psave
 		outputs_cl = [sid+':{}:3,1.1234'.format(g.proto.mmtypes[-1]), sid+':C:5,5.5555',sid+':L:4',addr+',100']
 		pw = cfg['wpasswd']