Browse Source

py3port: make hexadecimal values be strings instead of bytes type

- binascii.hexlify(b'foo') -> b'foo'.hex()
- binascii.unhexlify('aabb') -> bytes.fromhex('aabb')
- replace HexBytes class with HexStr

This change has led to a ≈10% speedup in the full test-release.sh run
MMGen 6 years ago
parent
commit
b5f1ebc640

+ 22 - 22
mmgen/addr.py

@@ -21,7 +21,6 @@ addr.py:  Address generation/display routines for the MMGen suite
 """
 
 from hashlib import sha256,sha512
-from binascii import hexlify,unhexlify
 from mmgen.common import *
 from mmgen.obj import *
 
@@ -67,7 +66,7 @@ class AddrGeneratorSegwit(AddrGenerator):
 
 	def to_segwit_redeem_script(self,pubhex):
 		assert pubhex.compressed,'Uncompressed public keys incompatible with Segwit'
-		return HexBytes(g.proto.pubhex2redeem_script(pubhex))
+		return HexStr(g.proto.pubhex2redeem_script(pubhex))
 
 class AddrGeneratorBech32(AddrGenerator):
 	def to_addr(self,pubhex):
@@ -82,7 +81,7 @@ class AddrGeneratorEthereum(AddrGenerator):
 	def to_addr(self,pubhex):
 		assert type(pubhex) == PubKey
 		import sha3
-		return CoinAddr(hexlify(sha3.keccak_256(unhexlify(pubhex[2:])).digest()[12:]).decode())
+		return CoinAddr(sha3.keccak_256(bytes.fromhex(pubhex[2:])).hexdigest()[24:])
 
 	def to_wallet_passwd(self,sk_hex):
 		from mmgen.protocol import hash256
@@ -105,26 +104,26 @@ class AddrGeneratorZcashZ(AddrGenerator):
 		return Sha256(s,preprocess=False).digest()
 
 	def to_addr(self,pubhex): # pubhex is really privhex
-		key = unhexlify(pubhex)
+		key = bytes.fromhex(pubhex)
 		assert len(key) == 32,'{}: incorrect privkey length'.format(len(key))
 		if g.platform == 'win':
 			ydie(1,'Zcash z-addresses not supported on Windows platform')
 		from nacl.bindings import crypto_scalarmult_base
 		p2 = crypto_scalarmult_base(self.zhash256(key,1))
 		from mmgen.protocol import _b58chk_encode
-		ret = _b58chk_encode(g.proto.addr_ver_num['zcash_z'][0] + hexlify(self.zhash256(key,0)+p2))
+		ret = _b58chk_encode(g.proto.addr_ver_num['zcash_z'][0] + (self.zhash256(key,0)+p2).hex())
 		assert len(ret) == self.addr_width,'Invalid Zcash z-address length'
 		return CoinAddr(ret)
 
 	def to_viewkey(self,pubhex): # pubhex is really privhex
-		key = unhexlify(pubhex)
+		key = bytes.fromhex(pubhex)
 		assert len(key) == 32,'{}: incorrect privkey length'.format(len(key))
 		vk = bytearray(self.zhash256(key,0)+self.zhash256(key,1))
 		vk[32] &= 0xf8
 		vk[63] &= 0x7f
 		vk[63] |= 0x40
 		from mmgen.protocol import _b58chk_encode
-		ret = _b58chk_encode(g.proto.addr_ver_num['viewkey'][0] + hexlify(vk))
+		ret = _b58chk_encode(g.proto.addr_ver_num['viewkey'][0] + vk.hex())
 		assert len(ret) == self.vk_width,'Invalid Zcash view key length'
 		return ZcashViewKey(ret)
 
@@ -133,10 +132,11 @@ class AddrGeneratorZcashZ(AddrGenerator):
 
 class AddrGeneratorMonero(AddrGenerator):
 
-	def b58enc(self,addr_str):
-		enc,l = baseconv.fromhex,len(addr_str)
-		a = ''.join([enc(hexlify(addr_str[i*8:i*8+8]),'b58',pad=11,tostr=True) for i in range(l//8)])
-		b = enc(hexlify(addr_str[l-l%8:]),'b58',pad=7,tostr=True)
+	def b58enc(self,addr_bytes):
+		enc = baseconv.fromhex
+		l = len(addr_bytes)
+		a = ''.join([enc((addr_bytes[i*8:i*8+8]).hex(),'b58',pad=11,tostr=True) for i in range(l//8)])
+		b = enc((addr_bytes[l-l%8:]).hex(),'b58',pad=7,tostr=True)
 		return a + b
 
 	def to_addr(self,sk_hex): # sk_hex instead of pubhex
@@ -159,12 +159,12 @@ class AddrGeneratorMonero(AddrGenerator):
 			return Q
 
 		def hex2int_le(hexstr):
-			return int(hexlify(unhexlify(hexstr)[::-1]),16)
+			return int((bytes.fromhex(hexstr)[::-1]).hex(),16)
 
 		vk_hex = self.to_viewkey(sk_hex)
 		pk_str  = encodepoint(scalarmultbase(hex2int_le(sk_hex)))
 		pvk_str = encodepoint(scalarmultbase(hex2int_le(vk_hex)))
-		addr_p1 = unhexlify(g.proto.addr_ver_num['monero'][0]) + pk_str + pvk_str
+		addr_p1 = bytes.fromhex(g.proto.addr_ver_num['monero'][0]) + pk_str + pvk_str
 
 		import sha3
 		return CoinAddr(self.b58enc(addr_p1 + sha3.keccak_256(addr_p1).digest()[:4]))
@@ -176,7 +176,7 @@ class AddrGeneratorMonero(AddrGenerator):
 	def to_viewkey(self,sk_hex):
 		assert len(sk_hex) == 64,'{}: incorrect privkey length'.format(len(sk_hex))
 		import sha3
-		return MoneroViewKey(g.proto.preprocess_key(sha3.keccak_256(unhexlify(sk_hex)).hexdigest(),None))
+		return MoneroViewKey(g.proto.preprocess_key(sha3.keccak_256(bytes.fromhex(sk_hex)).hexdigest(),None))
 
 	def to_segwit_redeem_script(self,sk_hex):
 		raise NotImplementedError('Monero addresses incompatible with Segwit')
@@ -210,7 +210,7 @@ class KeyGenerator(MMGenObject):
 		try:
 			from mmgen.secp256k1 import priv2pub
 			m = 'Unable to execute priv2pub() from secp256k1 extension module'
-			assert priv2pub(unhexlify('deadbeef'*8),1),m
+			assert priv2pub(bytes.fromhex('deadbeef'*8),1),m
 			return True
 		except:
 			return False
@@ -239,12 +239,12 @@ class KeyGeneratorPython(KeyGenerator):
 	def privnum2pubhex(self,numpriv,compressed=False):
 		pko = ecdsa.SigningKey.from_secret_exponent(numpriv,self.secp256k1)
 		# pubkey = x (32 bytes) + y (32 bytes) (unsigned big-endian)
-		pubkey = hexlify(pko.get_verifying_key().to_string())
+		pubkey = (pko.get_verifying_key().to_string()).hex()
 		if compressed: # discard Y coord, replace with appropriate version byte
 			# even y: <0, odd y: >0 -- https://bitcointalk.org/index.php?topic=129652.0
-			return (b'03',b'02')[pubkey[-1] in b'02468ace'] + pubkey[:64]
+			return ('03','02')[pubkey[-1] in '02468ace'] + pubkey[:64]
 		else:
-			return b'04' + pubkey
+			return '04' + pubkey
 
 	def to_pubhex(self,privhex):
 		assert type(privhex) == PrivKey
@@ -256,13 +256,13 @@ class KeyGeneratorSecp256k1(KeyGenerator):
 	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)
+		return PubKey(priv2pub(bytes.fromhex(privhex),int(privhex.compressed)).hex(),compressed=privhex.compressed)
 
 class KeyGeneratorDummy(KeyGenerator):
 	desc = 'mmgen-dummy'
 	def to_pubhex(self,privhex):
 		assert type(privhex) == PrivKey
-		return PubKey(privhex.decode(),compressed=privhex.compressed)
+		return PubKey(privhex,compressed=privhex.compressed)
 
 class AddrListEntry(MMGenListItem):
 	addr    = MMGenListItemAttr('addr','CoinAddr')
@@ -404,7 +404,7 @@ Removed {{}} duplicate WIF key{{}} from keylist (also in {pnm} key-address file
 
 		seed = seed.get_data()
 		seed = self.scramble_seed(seed)
-		dmsg_sc('seed',hexlify(seed[:8]).decode())
+		dmsg_sc('seed',seed[:8].hex())
 
 		compressed = self.al_id.mmtype.compressed
 		pubkey_type = self.al_id.mmtype.pubkey_type
@@ -838,7 +838,7 @@ Record this checksum: it will be used to verify the password file in the future
 	def make_passwd(self,hex_sec):
 		assert self.pw_fmt in self.pw_info
 		if self.pw_fmt == 'hex':
-			return hex_sec.decode()
+			return hex_sec
 		else:
 			# we take least significant part
 			return baseconv.fromhex(hex_sec,self.pw_fmt,pad=self.pw_len,tostr=True)[-self.pw_len:]

+ 9 - 9
mmgen/altcoins/eth/contract.py

@@ -70,8 +70,8 @@ class Token(MMGenObject): # ERC20
 			except:
 				"RPC call to decimals() failed (returned '{}')".format(ret)
 			return int(b,16) if b else None
-	def name(self):         return self.strip(unhexlify(self.do_call('name()')[2:]))
-	def symbol(self):       return self.strip(unhexlify(self.do_call('symbol()')[2:]))
+	def name(self):         return self.strip(bytes.fromhex(self.do_call('name()')[2:]))
+	def symbol(self):       return self.strip(bytes.fromhex(self.do_call('symbol()')[2:]))
 
 	def info(self):
 		fs = '{:15}{}\n' * 5
@@ -95,12 +95,12 @@ class Token(MMGenObject): # ERC20
 		if nonce is None:
 			nonce = int(g.rpch.parity_nextNonce('0x'+from_addr),16)
 		data = self.create_data(to_addr,amt,method_sig=method_sig,from_addr=from_addr2)
-		return {'to':       unhexlify(self.addr),
+		return {'to':       bytes.fromhex(self.addr),
 				'startgas': start_gas.toWei(),
 				'gasprice': gasPrice.toWei(),
 				'value':    0,
 				'nonce':   nonce,
-				'data':    unhexlify(data) }
+				'data':    bytes.fromhex(data) }
 
 	def txsign(self,tx_in,key,from_addr,chain_id=None):
 		from ethereum.transactions import Transaction
@@ -108,11 +108,11 @@ class Token(MMGenObject): # ERC20
 			chain_id_method = ('parity_chainId','eth_chainId')['eth_chainId' in g.rpch.caps]
 			chain_id = int(g.rpch.request(chain_id_method),16)
 		tx = Transaction(**tx_in).sign(key,chain_id)
-		hex_tx = hexlify(rlp.encode(tx))
-		coin_txid = CoinTxID(hexlify(tx.hash))
-		if hexlify(tx.sender).decode() != from_addr:
+		hex_tx = rlp.encode(tx).hex()
+		coin_txid = CoinTxID(tx.hash.hex())
+		if tx.sender.hex() != from_addr:
 			m = "Sender address '{}' does not match address of key '{}'!"
-			die(3,m.format(from_addr,hexlify(tx.sender).decode()))
+			die(3,m.format(from_addr,tx.sender.hex()))
 		if g.debug:
 			msg('{}'.format('\n  '.join(parse_abi(data))))
 			pmsg(tx.to_dict())
@@ -121,7 +121,7 @@ class Token(MMGenObject): # ERC20
 # The following are used for token deployment only:
 
 	def txsend(self,hex_tx):
-		return g.rpch.eth_sendRawTransaction('0x'+hex_tx.decode()).replace('0x','',1).encode()
+		return g.rpch.eth_sendRawTransaction('0x'+hex_tx).replace('0x','',1)
 
 	def transfer(   self,from_addr,to_addr,amt,key,start_gas,gasPrice,
 					method_sig='transfer(address,uint256)',

+ 24 - 25
mmgen/altcoins/eth/tx.py

@@ -44,7 +44,7 @@ class EthereumMMGenTX(MMGenTX):
 	usr_rel_fee = None # not in MMGenTX
 	disable_fee_check = False
 	txobj  = None # ""
-	data = HexBytes('')
+	data = HexStr('')
 
 	def __init__(self,*args,**kwargs):
 		super(EthereumMMGenTX,self).__init__(*args,**kwargs)
@@ -53,16 +53,16 @@ class EthereumMMGenTX(MMGenTX):
 		if hasattr(opt,'contract_data') and opt.contract_data:
 			m = "'--contract-data' option may not be used with token transaction"
 			assert not 'Token' in type(self).__name__, m
-			self.data = HexBytes(open(opt.contract_data).read().strip())
+			self.data = HexStr(open(opt.contract_data).read().strip())
 			self.disable_fee_check = True
 
 	@classmethod
 	def get_receipt(cls,txid):
-		return g.rpch.eth_getTransactionReceipt('0x'+txid.decode())
+		return g.rpch.eth_getTransactionReceipt('0x'+txid)
 
 	@classmethod
 	def get_exec_status(cls,txid,silent=False):
-		d = g.rpch.eth_getTransactionReceipt('0x'+txid.decode())
+		d = g.rpch.eth_getTransactionReceipt('0x'+txid)
 		if not silent:
 			if 'contractAddress' in d and d['contractAddress']:
 				msg('Contract address: {}'.format(d['contractAddress'].replace('0x','')))
@@ -82,18 +82,17 @@ class EthereumMMGenTX(MMGenTX):
 	def check_pubkey_scripts(self): pass
 
 	def check_sigs(self,deserial_tx=None):
-		if is_hex_bytes(self.hex):
+		if is_hex_str(self.hex):
 			self.mark_signed()
 			return True
 		return False
 
 	# hex data if signed, json if unsigned: see create_raw()
 	def check_txfile_hex_data(self):
-		if type(self.hex) == str: self.hex = self.hex.encode()
 		if self.check_sigs():
 			from ethereum.transactions import Transaction
 			import rlp
-			etx = rlp.decode(unhexlify(self.hex),Transaction)
+			etx = rlp.decode(bytes.fromhex(self.hex),Transaction)
 			d = etx.to_dict() # ==> hex values have '0x' prefix, 0 is '0x'
 			for k in ('sender','to','data'):
 				if k in d: d[k] = d[k].replace('0x','',1)
@@ -103,13 +102,13 @@ class EthereumMMGenTX(MMGenTX):
 					'gasPrice': ETHAmt(d['gasprice'],'wei'),
 					'startGas': ETHAmt(d['startgas'],'wei'),
 					'nonce':    ETHNonce(d['nonce']),
-					'data':     HexBytes(d['data']) }
+					'data':     HexStr(d['data']) }
 			if o['data'] and not o['to']:
-				self.token_addr = TokenAddr(hexlify(etx.creates).decode())
-			txid = CoinTxID(hexlify(etx.hash))
+				self.token_addr = TokenAddr(etx.creates.hex())
+			txid = CoinTxID(etx.hash.hex())
 			assert txid == self.coin_txid,"txid in tx.hex doesn't match value in MMGen transaction file"
 		else:
-			d = json.loads(self.hex.decode())
+			d = json.loads(self.hex)
 			o = {   'from':     CoinAddr(d['from']),
 					'to':       CoinAddr(d['to']) if d['to'] else Str(''),
 					'amt':      ETHAmt(d['amt']),
@@ -117,7 +116,7 @@ class EthereumMMGenTX(MMGenTX):
 					'startGas': ETHAmt(d['startGas']),
 					'nonce':    ETHNonce(d['nonce']),
 					'chainId':  Int(d['chainId']),
-					'data':     HexBytes(d['data']) }
+					'data':     HexStr(d['data']) }
 		self.tx_gas = o['startGas'] # approximate, but better than nothing
 		self.data = o['data']
 		if o['data'] and not o['to']: self.disable_fee_check = True
@@ -151,7 +150,7 @@ class EthereumMMGenTX(MMGenTX):
 		assert o_num in o_ok,'Transaction has invalid number of outputs!'.format(o_num)
 		self.make_txobj()
 		ol = {k: (v.decode() if issubclass(type(v),bytes) else str(v)) for k,v in self.txobj.items()}
-		self.hex = json.dumps(ol).encode()
+		self.hex = json.dumps(ol)
 		self.update_txid()
 
 	def del_output(self,idx): pass
@@ -278,22 +277,22 @@ class EthereumMMGenTX(MMGenTX):
 		return m.format(ETHAmt(chg).hl(),g.coin)
 
 	def do_sign(self,d,wif,tx_num_str):
-		d_in = {'to':       unhexlify(d['to']),
+		d_in = {'to':       bytes.fromhex(d['to']),
 				'startgas': d['startGas'].toWei(),
 				'gasprice': d['gasPrice'].toWei(),
 				'value':    d['amt'].toWei() if d['amt'] else 0,
 				'nonce':    d['nonce'],
-				'data':     unhexlify(d['data'])}
+				'data':     bytes.fromhex(d['data'])}
 
 		from ethereum.transactions import Transaction
 		etx = Transaction(**d_in).sign(wif,d['chainId'])
-		assert hexlify(etx.sender).decode() == d['from'],(
+		assert etx.sender.hex() == d['from'],(
 			'Sender address recovered from signature does not match true sender')
 		import rlp
-		self.hex = hexlify(rlp.encode(etx))
-		self.coin_txid = CoinTxID(hexlify(etx.hash))
+		self.hex = rlp.encode(etx).hex()
+		self.coin_txid = CoinTxID(etx.hash.hex())
 		if d['data']:
-			self.token_addr = TokenAddr(hexlify(etx.creates).decode())
+			self.token_addr = TokenAddr(etx.creates.hex())
 		assert self.check_sigs(),'Signature check failed'
 
 	def sign(self,tx_num_str,keys): # return True or False; don't exit or raise exception
@@ -320,10 +319,10 @@ class EthereumMMGenTX(MMGenTX):
 			return False
 
 	def is_in_mempool(self):
-		return '0x'+self.coin_txid.decode() in [x['hash'] for x in g.rpch.parity_pendingTransactions()]
+		return '0x'+self.coin_txid in [x['hash'] for x in g.rpch.parity_pendingTransactions()]
 
 	def is_in_wallet(self):
-		d = g.rpch.eth_getTransactionReceipt('0x'+self.coin_txid.decode())
+		d = g.rpch.eth_getTransactionReceipt('0x'+self.coin_txid)
 		if d and 'blockNumber' in d and d['blockNumber'] is not None:
 			return 1 + int(g.rpch.eth_blockNumber(),16) - int(d['blockNumber'],16)
 		return False
@@ -365,7 +364,7 @@ class EthereumMMGenTX(MMGenTX):
 
 		if prompt_user: self.confirm_send()
 
-		ret = None if bogus_send else g.rpch.eth_sendRawTransaction('0x'+self.hex.decode(),on_fail='return')
+		ret = None if bogus_send else g.rpch.eth_sendRawTransaction('0x'+self.hex,on_fail='return')
 
 		from mmgen.rpc import rpc_error,rpc_errmsg
 		if rpc_error(ret):
@@ -376,7 +375,7 @@ class EthereumMMGenTX(MMGenTX):
 		else:
 			m = 'BOGUS transaction NOT sent: {}' if bogus_send else 'Transaction sent: {}'
 			if not bogus_send:
-				assert ret == '0x'+self.coin_txid.decode(),'txid mismatch (after sending)'
+				assert ret == '0x'+self.coin_txid,'txid mismatch (after sending)'
 			self.desc = 'sent transaction'
 			msg(m.format(self.coin_txid.hl()))
 			self.add_timestamp()
@@ -418,8 +417,8 @@ class EthereumTokenMMGenTX(EthereumMMGenTX):
 
 	def set_g_token(self):
 		g.dcoin = self.dcoin
-		if is_hex_bytes(self.hex): return # for txsend we can leave g.token uninitialized
-		d = json.loads(self.hex.decode())
+		if is_hex_str(self.hex): return # for txsend we can leave g.token uninitialized
+		d = json.loads(self.hex)
 		if g.token.upper() == self.dcoin:
 			g.token = d['token_addr']
 		elif g.token != d['token_addr']:

+ 5 - 6
mmgen/crypto.py

@@ -20,7 +20,6 @@
 crypto.py:  Cryptographic and related routines for the MMGen suite
 """
 
-from binascii import hexlify
 from hashlib import sha256
 
 from mmgen.common import *
@@ -46,7 +45,7 @@ def scramble_seed(seed,scramble_key,hash_rounds):
 	import hmac
 	scr_seed = hmac.new(seed,scramble_key,sha256).digest()
 	fs = 'Seed:  {}\nScramble key: {}\nScrambled seed: {}'
-	dmsg(fs.format(hexlify(seed),scramble_key.decode('utf8'),hexlify(scr_seed)))
+	dmsg(fs.format(seed.hex(),scramble_key.decode(),scr_seed.hex()))
 	return sha256_rounds(scr_seed,hash_rounds)
 
 def encrypt_seed(seed,key):
@@ -77,7 +76,7 @@ def decrypt_seed(enc_seed,key,seed_id,key_id):
 #	else:
 #		qmsg('Generated IDs (Seed/Key): {}/{}'.format(chk2,chk1))
 
-	dmsg('Decrypted seed: {}'.format(hexlify(dec_seed)))
+	dmsg('Decrypted seed: {}'.format(dec_seed.hex()))
 	return dec_seed
 
 def encrypt_data(data,key,iv=1,desc='data',verify=True):
@@ -120,7 +119,7 @@ def make_key(passwd,salt,hash_preset,desc='encryption key',from_what='passphrase
 		msg_r('Generating {}{}...'.format(desc,from_what))
 	key = scrypt_hash_passphrase(passwd,salt,hash_preset)
 	if opt.verbose or verbose: msg('done')
-	dmsg('Key: {}'.format(hexlify(key)))
+	dmsg('Key: {}'.format(key.hex()))
 	return key
 
 def _get_random_data_from_user(uchars):
@@ -189,7 +188,7 @@ def mmgen_encrypt(data,desc='data',hash_preset=''):
 	qmsg("Using {} hash preset of '{}'".format(m,hp))
 	passwd = get_new_passphrase(desc,{})
 	key    = make_key(passwd,salt,hp)
-	enc_d  = encrypt_data(sha256(nonce+data).digest()+nonce+data,key,int(hexlify(iv),16),desc=desc)
+	enc_d  = encrypt_data(sha256(nonce+data).digest()+nonce+data,key,int(iv.hex(),16),desc=desc)
 	return salt+iv+enc_d
 
 def mmgen_decrypt(data,desc='data',hash_preset=''):
@@ -204,7 +203,7 @@ def mmgen_decrypt(data,desc='data',hash_preset=''):
 	qmsg("Using {} hash preset of '{}'".format(m,hp))
 	passwd = get_mmgen_passphrase(desc)
 	key    = make_key(passwd,salt,hp)
-	dec_d  = decrypt_data(enc_d,key,int(hexlify(iv),16),desc)
+	dec_d  = decrypt_data(enc_d,key,int(iv.hex(),16),desc)
 	if dec_d[:_sha256_len] == sha256(dec_d[_sha256_len:]).digest():
 		vmsg('OK')
 		return dec_d[_sha256_len+_nonce_len:]

+ 2 - 3
mmgen/main_autosign.py

@@ -269,13 +269,12 @@ def wipe_existing_key():
 		subprocess.call(['wipe','-cf',fn])
 
 def create_key():
-	from binascii import hexlify
-	kdata = hexlify(os.urandom(32)).decode()
+	kdata = os.urandom(32).hex()
 	fn = os.path.join(tx_dir,key_fn)
 	desc = 'key file {}'.format(fn)
 	msg('Creating ' + desc)
 	try:
-		with open(fn,'w') as f: f.write(kdata+'\n')
+		open(fn,'w').write(kdata+'\n')
 		os.chmod(fn,0o400)
 		msg('Wrote ' + desc)
 	except:

+ 14 - 32
mmgen/obj.py

@@ -24,7 +24,6 @@ import sys,os,unicodedata
 from decimal import *
 from mmgen.color import *
 from string import hexdigits,ascii_letters,digits
-from binascii import hexlify
 
 def is_mmgen_seed_id(s): return SeedID(sid=s,on_fail='silent')
 def is_mmgen_idx(s):     return AddrIdx(s,on_fail='silent')
@@ -562,20 +561,19 @@ class TwLabel(str,InitErrors,MMGenObject):
 			m = "{}\n{!r}: value cannot be converted to TwLabel"
 			return cls.init_fail(m.format(e.args[0],s),on_fail)
 
-class HexBytes(bytes,Hilite,InitErrors):
+class HexStr(str,Hilite,InitErrors):
 	color = 'red'
 	trunc_ok = False
-	dtype = bytes
+	dtype = str
 	def __new__(cls,s,on_fail='die',case='lower'):
 		if type(s) == cls: return s
 		assert case in ('upper','lower')
 		cls.arg_chk(cls,on_fail)
-		if issubclass(type(s),bytes): s = s.decode()
 		try:
-			assert type(s) == str,'not a string'
+			assert issubclass(type(s),str),'not a string or string subclass'
 			assert set(s) <= set(getattr(hexdigits,case)()),'not {}case hexadecimal symbols'.format(case)
 			assert not len(s) % 2,'odd-length string'
-			return cls.dtype.__new__(cls,s.encode() if cls.dtype == bytes else s)
+			return cls.dtype.__new__(cls,s)
 		except Exception as e:
 			m = "{!r}: value cannot be converted to {} (value is {})"
 			return cls.init_fail(m.format(s,cls.__name__,e.args[0]),on_fail)
@@ -583,11 +581,11 @@ class HexBytes(bytes,Hilite,InitErrors):
 class Str(str,Hilite): pass
 class Int(int,Hilite): pass
 
-class HexBytesWithWidth(HexBytes):
+class HexStrWithWidth(HexStr):
 	color = 'nocolor'
 	hexcase = 'lower'
 	width = None
-	parent_cls = HexBytes
+	parent_cls = HexStr
 	def __new__(cls,s,on_fail='die'):
 		cls.arg_chk(cls,on_fail)
 		try:
@@ -598,23 +596,7 @@ class HexBytesWithWidth(HexBytes):
 			m = "{}\n{!r}: value cannot be converted to {}"
 			return cls.init_fail(m.format(e.args[0],s,cls.__name__),on_fail)
 
-class CoinTxID(HexBytesWithWidth):       color,width,hexcase = 'purple',64,'lower'
-
-class HexStr(str,Hilite,InitErrors):
-	color = 'red'
-	trunc_ok = False
-	dtype = str
-	def __new__(cls,s,on_fail='die',case='lower'):
-		return HexBytes.__new__(cls,s,on_fail=on_fail,case=case)
-
-class HexStrWithWidth(HexStr):
-	color = 'nocolor'
-	hexcase = 'lower'
-	width = None
-	parent_cls = HexStr
-	def __new__(cls,s,on_fail='die'):
-		return HexBytesWithWidth.__new__(cls,s,on_fail=on_fail)
-
+class CoinTxID(HexStrWithWidth):       color,width,hexcase = 'purple',64,'lower'
 class WalletPassword(HexStrWithWidth): color,width,hexcase = 'blue',32,'lower'
 class MoneroViewKey(HexStrWithWidth):  color,width,hexcase = 'cyan',64,'lower'
 class MMGenTxID(HexStrWithWidth):      color,width,hexcase = 'red',6,'upper'
@@ -634,18 +616,18 @@ class WifKey(str,Hilite,InitErrors):
 			m = '{!r}: invalid value for WIF key ({})'.format(s,e.args[0])
 			return cls.init_fail(m,on_fail)
 
-class PubKey(HexBytes,MMGenObject): # TODO: add some real checks
+class PubKey(HexStr,MMGenObject): # TODO: add some real checks
 	def __new__(cls,s,compressed,on_fail='die'):
 		try:
 			assert type(compressed) == bool,"'compressed' must be of type bool"
-			me = HexBytes.__new__(cls,s,case='lower',on_fail='raise')
+			me = HexStr.__new__(cls,s,case='lower',on_fail='raise')
 			me.compressed = compressed
 			return me
 		except Exception as e:
 			m = '{!r}: invalid value for pubkey ({})'.format(s,e.args[0])
 			return cls.init_fail(m,on_fail)
 
-class PrivKey(bytes,Hilite,InitErrors,MMGenObject):
+class PrivKey(str,Hilite,InitErrors,MMGenObject):
 
 	color = 'red'
 	width = 64
@@ -666,7 +648,7 @@ class PrivKey(bytes,Hilite,InitErrors,MMGenObject):
 				assert s == None
 				assert set(wif) <= set(ascii_letters+digits),'not an ascii alphanumeric string'
 				w2h = g.proto.wif2hex(wif) # raises exception on error
-				me = bytes.__new__(cls,w2h['hex'])
+				me = str.__new__(cls,w2h['hex'])
 				me.compressed = w2h['compressed']
 				me.pubkey_type = w2h['pubkey_type']
 				me.wif = str.__new__(WifKey,wif) # check has been done
@@ -680,13 +662,13 @@ class PrivKey(bytes,Hilite,InitErrors,MMGenObject):
 			assert s and type(compressed) == bool and pubkey_type,'Incorrect args for PrivKey()'
 			assert len(s) == cls.width // 2,'Key length must be {}'.format(cls.width//2)
 			if pubkey_type == 'password': # skip WIF creation and pre-processing for passwds
-				me = bytes.__new__(cls,hexlify(s))
+				me = str.__new__(cls,s.hex())
 			else:
-				me = bytes.__new__(cls,g.proto.preprocess_key(hexlify(s),pubkey_type))
+				me = str.__new__(cls,g.proto.preprocess_key(s.hex(),pubkey_type))
 				me.wif = WifKey(g.proto.hex2wif(me,pubkey_type,compressed),on_fail='raise')
 			me.compressed = compressed
 			me.pubkey_type = pubkey_type
-			me.orig_hex = hexlify(s) # save the non-preprocessed key
+			me.orig_hex = s.hex() # save the non-preprocessed key
 			return me
 		except Exception as e:
 			fs = "Key={!r}\nCompressed={}\nValue pair cannot be converted to PrivKey\n({})"

+ 51 - 53
mmgen/protocol.py

@@ -21,17 +21,16 @@ protocol.py: Coin protocol functions, classes and methods
 """
 
 import sys,os,hashlib
-from binascii import hexlify,unhexlify
 from mmgen.util import msg,pmsg,ymsg,Msg,pdie,ydie
 from mmgen.obj import MMGenObject,BTCAmt,LTCAmt,BCHAmt,B2XAmt,ETHAmt
 from mmgen.globalvars import g
 import mmgen.bech32 as bech32
 
 def hash160(hexnum): # take hex, return hex - OP_HASH160
-	return hashlib.new('ripemd160',hashlib.sha256(unhexlify(hexnum)).digest()).hexdigest().encode()
+	return hashlib.new('ripemd160',hashlib.sha256(bytes.fromhex(hexnum)).digest()).hexdigest()
 
 def hash256(hexnum): # take hex, return hex - OP_HASH256
-	return hashlib.sha256(hashlib.sha256(unhexlify(hexnum)).digest()).hexdigest().encode()
+	return hashlib.sha256(hashlib.sha256(bytes.fromhex(hexnum)).digest()).hexdigest()
 
 _b58a='123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
 
@@ -43,7 +42,7 @@ _b58a='123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
 # 1111111111111111111114oLvT2 (pubkeyhash = '\0'*20)
 
 def _b58chk_encode(hexstr):
-	lzeroes = (len(hexstr) - len(hexstr.lstrip(b'0'))) // 2
+	lzeroes = (len(hexstr) - len(hexstr.lstrip('0'))) // 2
 	def b58enc(n):
 		while n:
 			yield _b58a[n % 58]
@@ -53,10 +52,9 @@ def _b58chk_encode(hexstr):
 def _b58chk_decode(s):
 	lzeroes = len(s) - len(s.lstrip('1'))
 	hexstr = '{}{:x}'.format(
-			'00' * lzeroes,
-			sum(_b58a.index(ch) * 58**n for n,ch in enumerate(s[::-1]))
-		).encode()
-	if len(hexstr) % 2: hexstr = b'0' + hexstr
+				'00' * lzeroes,
+				sum(_b58a.index(ch) * 58**n for n,ch in enumerate(s[::-1])) )
+	if len(hexstr) % 2: hexstr = '0' + hexstr
 	if hexstr[-8:] != hash256(hexstr[:-8])[:8]:
 		raise ValueError('_b58chk_decode(): {}: incorrect checksum for {}, expected {}'.format(
 							hexstr[-8:],hexstr[:-8],hash256(hexstr[:-8])[:8]))
@@ -67,8 +65,8 @@ class BitcoinProtocol(MMGenObject):
 	name            = 'bitcoin'
 	daemon_name     = 'bitcoind'
 	daemon_family   = 'bitcoind'
-	addr_ver_num    = { 'p2pkh': (b'00','1'), 'p2sh':  (b'05','3') }
-	wif_ver_num     = { 'std': b'80' }
+	addr_ver_num    = { 'p2pkh': ('00','1'), 'p2sh':  ('05','3') }
+	wif_ver_num     = { 'std': '80' }
 	mmtypes         = ('L','C','S','B')
 	dfl_mmtype      = 'L'
 	data_subdir     = ''
@@ -90,7 +88,7 @@ class BitcoinProtocol(MMGenObject):
 	base_coin          = 'BTC'
 	# From BIP173: witness version 'n' is stored as 'OP_n'. OP_0 is encoded as 0x00,
 	# but OP_1 through OP_16 are encoded as 0x51 though 0x60 (81 to 96 in decimal).
-	witness_vernum_hex = b'00'
+	witness_vernum_hex = '00'
 	witness_vernum     = int(witness_vernum_hex,16)
 	bech32_hrp         = 'bc'
 	sign_mode          = 'daemon'
@@ -126,7 +124,7 @@ class BitcoinProtocol(MMGenObject):
 	@classmethod
 	def hex2wif(cls,hexpriv,pubkey_type,compressed): # PrivKey
 		assert len(hexpriv) == cls.privkey_len*2, '{} bytes: incorrect private key length!'.format(len(hexpriv)//2)
-		return _b58chk_encode(cls.wif_ver_num[pubkey_type] + hexpriv + (b'',b'01')[bool(compressed)])
+		return _b58chk_encode(cls.wif_ver_num[pubkey_type] + hexpriv + ('','01')[bool(compressed)])
 
 	@classmethod
 	def wif2hex(cls,wif):
@@ -138,7 +136,7 @@ class BitcoinProtocol(MMGenObject):
 				key = key[len(v):]
 		assert pubkey_type,'invalid WIF version number'
 		if len(key) == 66:
-			assert key[-2:] == b'01','invalid compressed key suffix'
+			assert key[-2:] == '01','invalid compressed key suffix'
 			compressed = True
 		else:
 			assert len(key) == 64,'invalid key length'
@@ -156,7 +154,7 @@ class BitcoinProtocol(MMGenObject):
 				msg('{}: Invalid witness version number'.format(ret[0]))
 			elif ret[1]:
 				return {
-					'hex': hexlify(bytes(ret[1])),
+					'hex': bytes(ret[1]).hex(),
 					'format': 'bech32'
 				} if return_dict else True
 			return False
@@ -191,7 +189,7 @@ class BitcoinProtocol(MMGenObject):
 		# https://bitcoincore.org/en/segwit_wallet_dev/
 		# The P2SH redeemScript is always 22 bytes. It starts with a OP_0, followed
 		# by a canonical push of the keyhash (i.e. 0x0014{20-byte keyhash})
-		return cls.witness_vernum_hex + b'14' + hash160(pubhex)
+		return cls.witness_vernum_hex + '14' + hash160(pubhex)
 
 	@classmethod
 	def pubhex2segwitaddr(cls,pubhex):
@@ -199,12 +197,12 @@ class BitcoinProtocol(MMGenObject):
 
 	@classmethod
 	def pubhash2bech32addr(cls,pubhash):
-		d = list(unhexlify(pubhash))
+		d = list(bytes.fromhex(pubhash))
 		return bech32.bech32_encode(cls.bech32_hrp,[cls.witness_vernum]+bech32.convertbits(d,8,5))
 
 class BitcoinTestnetProtocol(BitcoinProtocol):
-	addr_ver_num         = { 'p2pkh': (b'6f',('m','n')), 'p2sh':  (b'c4','2') }
-	wif_ver_num          = { 'std': b'ef' }
+	addr_ver_num         = { 'p2pkh': ('6f',('m','n')), 'p2sh':  ('c4','2') }
+	wif_ver_num          = { 'std': 'ef' }
 	data_subdir          = 'testnet'
 	daemon_data_subdir   = 'testnet3'
 	rpc_port             = 18332
@@ -233,8 +231,8 @@ class BitcoinCashProtocol(BitcoinProtocol):
 
 class BitcoinCashTestnetProtocol(BitcoinCashProtocol):
 	rpc_port      = 18442
-	addr_ver_num  = { 'p2pkh': (b'6f',('m','n')), 'p2sh':  (b'c4','2') }
-	wif_ver_num   = { 'std': b'ef' }
+	addr_ver_num  = { 'p2pkh': ('6f',('m','n')), 'p2sh':  ('c4','2') }
+	wif_ver_num   = { 'std': 'ef' }
 	data_subdir   = 'testnet'
 	daemon_data_subdir = 'testnet3'
 
@@ -250,8 +248,8 @@ class B2XProtocol(BitcoinProtocol):
 	]
 
 class B2XTestnetProtocol(B2XProtocol):
-	addr_ver_num       = { 'p2pkh': (b'6f',('m','n')), 'p2sh':  (b'c4','2') }
-	wif_ver_num        = { 'std': b'ef' }
+	addr_ver_num       = { 'p2pkh': ('6f',('m','n')), 'p2sh':  ('c4','2') }
+	wif_ver_num        = { 'std': 'ef' }
 	data_subdir        = 'testnet'
 	daemon_data_subdir = 'testnet5'
 	rpc_port           = 18338
@@ -262,8 +260,8 @@ class LitecoinProtocol(BitcoinProtocol):
 	daemon_name    = 'litecoind'
 	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': (b'30','L'), 'p2sh':  (b'32','M'), 'p2sh2':  (b'05','3') } # 'p2sh' is new fmt
-	wif_ver_num    = { 'std': b'b0' }
+	addr_ver_num   = { 'p2pkh': ('30','L'), 'p2sh':  ('32','M'), 'p2sh2':  ('05','3') } # 'p2sh' is new fmt
+	wif_ver_num    = { 'std': 'b0' }
 	mmtypes         = ('L','C','S','B')
 	secs_per_block = 150
 	rpc_port       = 9332
@@ -275,8 +273,8 @@ class LitecoinProtocol(BitcoinProtocol):
 
 class LitecoinTestnetProtocol(LitecoinProtocol):
 	# addr ver nums same as Bitcoin testnet, except for 'p2sh'
-	addr_ver_num   = { 'p2pkh': (b'6f',('m','n')), 'p2sh':  (b'3a','Q'), 'p2sh2':  (b'c4','2') }
-	wif_ver_num    = { 'std': b'ef' } # same as Bitcoin testnet
+	addr_ver_num   = { 'p2pkh': ('6f',('m','n')), 'p2sh':  ('3a','Q'), 'p2sh2':  ('c4','2') }
+	wif_ver_num    = { 'std': 'ef' } # same as Bitcoin testnet
 	data_subdir    = 'testnet'
 	daemon_data_subdir = 'testnet4'
 	rpc_port       = 19332
@@ -293,11 +291,11 @@ class DummyWIF(object):
 		n = cls.name.capitalize()
 		assert pubkey_type == cls.pubkey_type,'{}: invalid pubkey_type for {}!'.format(pubkey_type,n)
 		assert compressed == False,'{} does not support compressed pubkeys!'.format(n)
-		return hexpriv.decode()
+		return hexpriv
 
 	@classmethod
 	def wif2hex(cls,wif):
-		return { 'hex':wif.encode(), 'pubkey_type':cls.pubkey_type, 'compressed':False }
+		return { 'hex':wif, 'pubkey_type':cls.pubkey_type, 'compressed':False }
 
 class EthereumProtocol(DummyWIF,BitcoinProtocol):
 
@@ -323,7 +321,7 @@ class EthereumProtocol(DummyWIF,BitcoinProtocol):
 	def verify_addr(cls,addr,hex_width,return_dict=False):
 		from mmgen.util import is_hex_str_lc
 		if is_hex_str_lc(addr) and len(addr) == cls.addr_width:
-			return { 'hex': addr.encode(), 'format': 'ethereum' } if return_dict else True
+			return { 'hex': addr, 'format': 'ethereum' } if return_dict else True
 		if g.debug: Msg("Invalid address '{}'".format(addr))
 		return False
 
@@ -331,7 +329,7 @@ class EthereumProtocol(DummyWIF,BitcoinProtocol):
 	def pubhash2addr(cls,pubkey_hash,p2sh):
 		assert len(pubkey_hash) == 40,'{}: invalid length for pubkey hash'.format(len(pubkey_hash))
 		assert not p2sh,'Ethereum has no P2SH address format'
-		return pubkey_hash.decode()
+		return pubkey_hash
 
 class EthereumTestnetProtocol(EthereumProtocol):
 	data_subdir = 'testnet'
@@ -352,18 +350,18 @@ class ZcashProtocol(BitcoinProtocolAddrgen):
 	name         = 'zcash'
 	base_coin    = 'ZEC'
 	addr_ver_num = {
-		'p2pkh':   (b'1cb8','t1'),
-		'p2sh':    (b'1cbd','t3'),
-		'zcash_z': (b'169a','zc'),
-		'viewkey': (b'a8abd3','ZiVK') }
-	wif_ver_num  = { 'std': b'80', 'zcash_z': b'ab36' }
+		'p2pkh':   ('1cb8','t1'),
+		'p2sh':    ('1cbd','t3'),
+		'zcash_z': ('169a','zc'),
+		'viewkey': ('a8abd3','ZiVK') }
+	wif_ver_num  = { 'std': '80', 'zcash_z': 'ab36' }
 	mmtypes      = ('L','C','Z')
 	dfl_mmtype   = 'L'
 
 	@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).encode() + hexpriv[2:]
+			return '{:02x}'.format(int(hexpriv[:2],16) & 0x0f) + hexpriv[2:]
 		else:
 			return hexpriv
 
@@ -378,18 +376,18 @@ class ZcashProtocol(BitcoinProtocolAddrgen):
 			raise ValueError('{}: incorrect pubkey_hash length'.format(hl))
 
 class ZcashTestnetProtocol(ZcashProtocol):
-	wif_ver_num  = { 'std': b'ef', 'zcash_z': b'ac08' }
+	wif_ver_num  = { 'std': 'ef', 'zcash_z': 'ac08' }
 	addr_ver_num = {
-		'p2pkh':   (b'1d25','tm'),
-		'p2sh':    (b'1cba','t2'),
-		'zcash_z': (b'16b6','zt'),
-		'viewkey': (b'a8ac0c','ZiVt') }
+		'p2pkh':   ('1d25','tm'),
+		'p2sh':    ('1cba','t2'),
+		'zcash_z': ('16b6','zt'),
+		'viewkey': ('a8ac0c','ZiVt') }
 
 # https://github.com/monero-project/monero/blob/master/src/cryptonote_config.h
 class MoneroProtocol(DummyWIF,BitcoinProtocolAddrgen):
 	name         = 'monero'
 	base_coin    = 'XMR'
-	addr_ver_num = { 'monero': (b'12','4'), 'monero_sub': (b'2a','8') } # 18,42
+	addr_ver_num = { 'monero': ('12','4'), 'monero_sub': ('2a','8') } # 18,42
 	wif_ver_num  = {}
 	mmtypes      = ('M',)
 	dfl_mmtype   = 'M'
@@ -399,8 +397,8 @@ class MoneroProtocol(DummyWIF,BitcoinProtocolAddrgen):
 	@classmethod
 	def preprocess_key(cls,hexpriv,pubkey_type): # reduce key
 		from mmgen.ed25519 import l
-		n = int(hexlify(unhexlify(hexpriv)[::-1]),16) % l
-		return hexlify(unhexlify('{:064x}'.format(n))[::-1])
+		n = int(bytes.fromhex(hexpriv)[::-1].hex(),16) % l
+		return bytes.fromhex('{:064x}'.format(n))[::-1].hex()
 
 	@classmethod
 	def verify_addr(cls,addr,hex_width,return_dict=False):
@@ -408,7 +406,7 @@ class MoneroProtocol(DummyWIF,BitcoinProtocolAddrgen):
 		def b58dec(addr_str):
 			from mmgen.util import baseconv
 			l = len(addr_str)
-			a = b''.join([baseconv.tohex(addr_str[i*11:i*11+11],'b58',pad=16) for i in range(l//11)])
+			a = ''.join([baseconv.tohex(addr_str[i*11:i*11+11],'b58',pad=16) for i in range(l//11)])
 			b = baseconv.tohex(addr_str[-(l%11):],'b58',pad=10)
 			return a + b
 
@@ -418,13 +416,13 @@ class MoneroProtocol(DummyWIF,BitcoinProtocolAddrgen):
 
 		ret = b58dec(addr)
 		import sha3
-		chk = sha3.keccak_256(unhexlify(ret)[:-4]).hexdigest()[:8]
-		assert chk.encode() == ret[-8:],'Incorrect checksum'
+		chk = sha3.keccak_256(bytes.fromhex(ret)[:-4]).hexdigest()[:8]
+		assert chk == ret[-8:],'{}: incorrect checksum.  Correct value: {}'.format(ret[-8:],chk)
 
 		return { 'hex': ret, 'format': 'monero' } if return_dict else True
 
 class MoneroTestnetProtocol(MoneroProtocol):
-	addr_ver_num = { 'monero': (b'35','4'), 'monero_sub': (b'3f','8') } # 53,63
+	addr_ver_num = { 'monero': ('35','4'), 'monero_sub': ('3f','8') } # 53,63
 
 class CoinProtocol(MMGenObject):
 	coins = {
@@ -484,17 +482,17 @@ def make_init_genonly_altcoins_str(data):
 		if proto in globals(): return ''
 		if coin.lower() in CoinProtocol.coins: return ''
 
-		def num2hexbytes(n):
-			return "b'{:0{}x}'".format(n,(4,2)[n < 256])
+		def num2hexstr(n):
+			return "'{:0{}x}'".format(n,(4,2)[n < 256])
 
 		o  = ['class {}(Bitcoin{}ProtocolAddrgen):'.format(proto,tn_str)]
 		o += ["base_coin = '{}'".format(coin)]
 		o += ["name = '{}'".format(e[0].lower())]
 		o += ["nameCaps = '{}'".format(e[0])]
-		a = "addr_ver_num = {{ 'p2pkh': ({},{!r})".format(num2hexbytes(e[3][0]),e[3][1])
-		b = ", 'p2sh':  ({},{!r})".format(num2hexbytes(e[4][0]),e[4][1]) if e[4] else ''
+		a = "addr_ver_num = {{ 'p2pkh': ({},{!r})".format(num2hexstr(e[3][0]),e[3][1])
+		b = ", 'p2sh':  ({},{!r})".format(num2hexstr(e[4][0]),e[4][1]) if e[4] else ''
 		o += [a+b+' }']
-		o += ["wif_ver_num = {{ 'std': {} }}".format(num2hexbytes(e[2]))]
+		o += ["wif_ver_num = {{ 'std': {} }}".format(num2hexstr(e[2]))]
 		o += ["mmtypes = ('L','C'{})".format(",'S'" if e[5] else '')]
 		o += ["dfl_mmtype = '{}'".format('L')]
 		return '\n\t'.join(o) + '\n'

+ 3 - 3
mmgen/rpc.py

@@ -110,13 +110,13 @@ class CoinDaemonRPCConnection(object):
 		dmsg_rpc('    RPC POST data ==> {}\n'.format(p))
 
 		ca_type = self.coin_amt_type if hasattr(self,'coin_amt_type') else str
-		from mmgen.obj import CoinTxID,HexBytes
+		from mmgen.obj import HexStr
 		class MyJSONEncoder(json.JSONEncoder):
 			def default(self,obj):
 				if isinstance(obj,g.proto.coin_amt):
 					return ca_type(obj)
-				elif isinstance(obj,CoinTxID) or isinstance(obj,HexBytes):
-					return obj.decode()
+				elif isinstance(obj,HexStr):
+					return obj
 				else:
 					return json.JSONEncoder.default(self,obj)
 

+ 7 - 8
mmgen/seed.py

@@ -21,7 +21,6 @@ seed.py:  Seed-related classes and methods for the MMGen suite
 """
 
 import os
-from binascii import hexlify,unhexlify
 
 from mmgen.common import *
 from mmgen.obj import *
@@ -55,7 +54,7 @@ class Seed(MMGenObject):
 			die(3,'{}: invalid seed length'.format(len(seed_bin)))
 
 		self.data      = seed_bin
-		self.hexdata   = hexlify(seed_bin)
+		self.hexdata   = seed_bin.hex()
 		self.sid       = SeedID(seed=self)
 		self.length    = len(seed_bin) * 8
 
@@ -485,7 +484,7 @@ class Mnemonic (SeedSourceUnenc):
 		# Internal error, so just die
 		compare_or_die(' '.join(ret),'recomputed mnemonic',' '.join(mn),'original',e='Internal error')
 
-		self.seed = Seed(unhexlify(hexseed))
+		self.seed = Seed(bytes.fromhex(hexseed))
 		self.ssdata.mnemonic = mn
 
 		check_usr_seed_len(self.seed.length)
@@ -542,7 +541,7 @@ class SeedFile (SeedSourceUnenc):
 
 		return True
 
-class HexSeedFile (SeedSourceUnenc):
+class HexSeedFile(SeedSourceUnenc):
 
 	stdin_ok = True
 	fmt_codes = 'seedhex','hexseed','hex','mmhex'
@@ -553,7 +552,7 @@ class HexSeedFile (SeedSourceUnenc):
 		h = self.seed.hexdata
 		self.ssdata.chksum = make_chksum_6(h)
 		self.ssdata.hexseed = h
-		self.fmt_data = '{} {}\n'.format(self.ssdata.chksum, split_into_cols(4,h.decode()))
+		self.fmt_data = '{} {}\n'.format(self.ssdata.chksum, split_into_cols(4,h))
 
 	def _deformat(self):
 		desc = self.desc
@@ -582,7 +581,7 @@ class HexSeedFile (SeedSourceUnenc):
 		if not compare_chksums(chk,'file',make_chksum_6(hstr),'computed',verbose=True):
 			return False
 
-		self.seed = Seed(unhexlify(hstr))
+		self.seed = Seed(bytes.fromhex(hstr))
 		self.ssdata.chksum = chk
 		self.ssdata.hexseed = hstr
 
@@ -869,7 +868,7 @@ to exit and re-run the program with the '--old-incog-fmt' option.
 		self.fmt_data = d.iv + encrypt_data(
 							d.salt + d.enc_seed,
 							d.wrapper_key,
-							int(hexlify(d.iv),16),
+							int(d.iv.hex(),16),
 							self.desc)
 #		print len(self.fmt_data)
 
@@ -923,7 +922,7 @@ to exit and re-run the program with the '--old-incog-fmt' option.
 		# IV is used BOTH to initialize counter and to salt password!
 		key = make_key(d.passwd, d.iv, d.hash_preset, 'wrapper key')
 		dd = decrypt_data(d.enc_incog_data, key,
-				int(hexlify(d.iv),16), 'incog data')
+				int(d.iv.hex(),16), 'incog data')
 
 		d.salt     = dd[0:g.salt_len]
 		d.enc_seed = dd[g.salt_len:]

+ 1 - 2
mmgen/sha2.py

@@ -22,7 +22,6 @@ sha2.py: A non-optimized but very compact implementation of the SHA2 hash
          SHA256Compress (unpadded SHA256, required for Zcash addresses)
 """
 
-from binascii import hexlify
 from struct import pack,unpack
 
 class Sha2(object):
@@ -103,7 +102,7 @@ class Sha2(object):
 		return b''.join((pack(self.word_fmt,w) for w in self.H))
 
 	def hexdigest(self):
-		return hexlify(self.digest())
+		return self.digest().hex()
 
 	def compute(self):
 		for i in range(0,len(self.M),16):

+ 20 - 22
mmgen/tool.py

@@ -20,8 +20,6 @@
 tool.py:  Routines for the 'mmgen-tool' utility
 """
 
-from binascii import hexlify,unhexlify
-
 from mmgen.protocol import hash160
 from mmgen.common import *
 from mmgen.crypto import *
@@ -248,20 +246,20 @@ class MMGenToolCmdUtil(MMGenToolCmdBase):
 
 	def randhex(self,nbytes='32'):
 		"print 'n' bytes (default 32) of random data in hex format"
-		return hexlify(get_random(int(nbytes)))
+		return get_random(int(nbytes)).hex()
 
 	def hexreverse(self,hexstr:'sstr'):
 		"reverse bytes of a hexadecimal string"
-		return hexlify(unhexlify(hexstr.strip())[::-1])
+		return bytes.fromhex(hexstr.strip())[::-1].hex()
 
 	def hexlify(self,infile:str):
 		"convert bytes in file to hexadecimal (use '-' for stdin)"
 		data = get_data_from_file(infile,dash=True,silent=True,binary=True)
-		return hexlify(data)
+		return data.hex()
 
 	def unhexlify(self,hexstr:'sstr'):
 		"convert hexadecimal value to bytes (warning: outputs binary data)"
-		return unhexlify(hexstr.encode())
+		return bytes.fromhex(hexstr)
 
 	def hexdump(self,infile:str,cols=8,line_nums=True):
 		"create hexdump of data from file (use '-' for stdin)"
@@ -286,7 +284,7 @@ class MMGenToolCmdUtil(MMGenToolCmdBase):
 		if file_input:  b = get_data_from_file(string_or_bytes,binary=True)
 		elif hex_input: b = decode_pretty_hexdump(string_or_bytes)
 		else:           b = string_or_bytes
-		return sha256(sha256(b.encode()).digest()).hexdigest().encode()
+		return sha256(sha256(b.encode()).digest()).hexdigest()
 
 	def id6(self,infile:str):
 		"generate 6-character MMGen ID for a file (use '-' for stdin)"
@@ -309,15 +307,15 @@ class MMGenToolCmdUtil(MMGenToolCmdBase):
 	def bytestob58(self,infile:str,pad=0):
 		"convert bytes to base 58 (supply data via STDIN)"
 		data = get_data_from_file(infile,dash=True,silent=True,binary=True)
-		return baseconv.fromhex(hexlify(data),'b58',pad=pad,tostr=True)
+		return baseconv.fromhex(data.hex(),'b58',pad=pad,tostr=True)
 
 	def b58tobytes(self,b58num:'sstr',pad=0):
 		"convert a base 58 number to bytes (warning: outputs binary data)"
-		return unhexlify(baseconv.tohex(b58num,'b58',pad=pad))
+		return bytes.fromhex(baseconv.tohex(b58num,'b58',pad=pad))
 
 	def hextob58(self,hexstr:'sstr',pad=0):
 		"convert a hexadecimal number to base 58"
-		return baseconv.fromhex(hexstr.encode(),'b58',pad=pad,tostr=True)
+		return baseconv.fromhex(hexstr,'b58',pad=pad,tostr=True)
 
 	def b58tohex(self,b58num:'sstr',pad=0):
 		"convert a base 58 number to hexadecimal"
@@ -326,7 +324,7 @@ class MMGenToolCmdUtil(MMGenToolCmdBase):
 	def hextob58chk(self,hexstr:'sstr'):
 		"convert a hexadecimal number to base58-check encoding"
 		from mmgen.protocol import _b58chk_encode
-		return _b58chk_encode(hexstr.encode())
+		return _b58chk_encode(hexstr)
 
 	def b58chktohex(self,b58chk_num:'sstr'):
 		"convert a base58-check encoded number to hexadecimal"
@@ -335,7 +333,7 @@ class MMGenToolCmdUtil(MMGenToolCmdBase):
 
 	def hextob32(self,hexstr:'sstr',pad=0):
 		"convert a hexadecimal number to MMGen's flavor of base 32"
-		return baseconv.fromhex(hexstr.encode(),'b32',pad,tostr=True)
+		return baseconv.fromhex(hexstr,'b32',pad,tostr=True)
 
 	def b32tohex(self,b32num:'sstr',pad=0):
 		"convert an MMGen-flavor base 32 number to hexadecimal"
@@ -370,7 +368,7 @@ class MMGenToolCmdCoin(MMGenToolCmdBase):
 	def hex2wif(self,privhex:'sstr'):
 		"convert a private key from hex to WIF format"
 		init_generators('at')
-		return g.proto.hex2wif(privhex.encode(),pubkey_type=at.pubkey_type,compressed=at.compressed)
+		return g.proto.hex2wif(privhex,pubkey_type=at.pubkey_type,compressed=at.compressed)
 
 	def wif2addr(self,wifkey:'sstr'):
 		"generate a coin address from a key in WIF format"
@@ -398,7 +396,7 @@ class MMGenToolCmdCoin(MMGenToolCmdBase):
 	def privhex2addr(self,privhex:'sstr',output_pubhex=False):
 		"generate coin address from private key in hex format"
 		init_generators()
-		pk = PrivKey(unhexlify(privhex),compressed=at.compressed,pubkey_type=at.pubkey_type)
+		pk = PrivKey(bytes.fromhex(privhex),compressed=at.compressed,pubkey_type=at.pubkey_type)
 		ph = kg.to_pubhex(pk)
 		return ph if output_pubhex else ag.to_addr(ph)
 
@@ -409,9 +407,9 @@ class MMGenToolCmdCoin(MMGenToolCmdBase):
 	def pubhex2addr(self,pubkeyhex:'sstr'):
 		"convert a hex pubkey to an address"
 		if opt.type == 'segwit':
-			return g.proto.pubhex2segwitaddr(pubkeyhex.encode())
+			return g.proto.pubhex2segwitaddr(pubkeyhex)
 		else:
-			return self.pubhash2addr(hash160(pubkeyhex.encode()).decode())
+			return self.pubhash2addr(hash160(pubkeyhex))
 
 	def pubhex2redeem_script(self,pubkeyhex:'sstr'): # new
 		"convert a hex pubkey to a Segwit P2SH-P2WPKH redeem script"
@@ -423,15 +421,15 @@ class MMGenToolCmdCoin(MMGenToolCmdBase):
 		assert opt.type == 'segwit','This command is meaningful only for --type=segwit'
 		assert redeem_scripthex[:4] == '0014','{!r}: invalid redeem script'.format(redeem_scripthex)
 		assert len(redeem_scripthex) == 44,'{} bytes: invalid redeem script length'.format(len(redeem_scripthex)//2)
-		return self.pubhash2addr(self.hash160(redeem_scripthex).decode())
+		return self.pubhash2addr(self.hash160(redeem_scripthex))
 
 	def pubhash2addr(self,pubhashhex:'sstr'):
 		"convert public key hash to address"
 		if opt.type == 'bech32':
-			return g.proto.pubhash2bech32addr(pubhashhex.encode())
+			return g.proto.pubhash2bech32addr(pubhashhex)
 		else:
 			init_generators('at')
-			return g.proto.pubhash2addr(pubhashhex.encode(),at.addr_fmt=='p2sh')
+			return g.proto.pubhash2addr(pubhashhex,at.addr_fmt=='p2sh')
 
 	def addr2pubhash(self,addr:'sstr'):
 		"convert coin address to public key hash"
@@ -446,7 +444,7 @@ class MMGenToolCmdMnemonic(MMGenToolCmdBase):
 	"""
 	def _do_random_mn(self,nbytes:int,wordlist_id:str):
 		assert nbytes in (16,24,32), 'nbytes must be 16, 24 or 32'
-		hexrand = hexlify(get_random(nbytes))
+		hexrand = get_random(nbytes).hex()
 		Vmsg('Seed: {}'.format(hexrand))
 		return self.hex2mn(hexrand,wordlist_id=wordlist_id)
 
@@ -466,7 +464,7 @@ class MMGenToolCmdMnemonic(MMGenToolCmdBase):
 		"convert a 16, 24 or 32-byte hexadecimal number to a mnemonic"
 		opt.out_fmt = 'words'
 		from mmgen.seed import SeedSource
-		s = SeedSource(seed=unhexlify(hexstr))
+		s = SeedSource(seed=bytes.fromhex(hexstr))
 		s._format()
 		return ' '.join(s.ssdata.mnemonic)
 
@@ -820,7 +818,7 @@ class MMGenToolCmdMonero(MMGenToolCmdBase):
 			p = pexpect.spawn('monero-wallet-cli --generate-from-spend-key {}'.format(fn))
 			if g.debug: p.logfile = sys.stdout
 			my_expect(p,'Awaiting initial prompt','Secret spend key: ')
-			my_sendline(p,'',d.sec.decode(),65)
+			my_sendline(p,'',d.sec,65)
 			my_expect(p,'','Enter.* new.* password.*: ',regex=True)
 			my_sendline(p,'Sending password',d.wallet_passwd,33)
 			my_expect(p,'','Confirm password: ')

+ 2 - 2
mmgen/tw.py

@@ -63,7 +63,7 @@ Actions: [q]uit view, [p]rint to file, pager [v]iew, [w]ide view, add [l]abel:
 		twmmid   = MMGenImmutableAttr('twmmid','TwMMGenID')
 		addr     = MMGenImmutableAttr('addr','CoinAddr')
 		confs    = MMGenImmutableAttr('confs',int,typeconv=False)
-		scriptPubKey = MMGenImmutableAttr('scriptPubKey','HexBytes')
+		scriptPubKey = MMGenImmutableAttr('scriptPubKey','HexStr')
 		days    = MMGenListItemAttr('days',int,typeconv=False)
 		skip    = MMGenListItemAttr('skip',str,typeconv=False,reassign_ok=True)
 
@@ -231,7 +231,7 @@ watch-only wallet using '{}-addrimport' and then re-run this program.
 			out.append(fs.format(   n=str(n+1)+')',
 									t='' if not i.txid else \
 										' ' * (tx_w-4) + '|...' if i.skip == 'txid' \
-											else i.txid.decode()[:tx_w-len(txdots)]+txdots,
+											else i.txid[:tx_w-len(txdots)] + txdots,
 									v=i.vout,
 									a=addr_out,
 									A=i.amt.fmt(color=True,prec=self.disp_prec),

+ 29 - 30
mmgen/tx.py

@@ -22,7 +22,6 @@ tx.py:  Transaction routines for the MMGen suite
 
 import sys,os,json
 from stat import *
-from binascii import unhexlify
 from mmgen.common import *
 from mmgen.obj import *
 
@@ -98,8 +97,8 @@ def segwit_is_active(exit_on_error=False):
 		return False
 
 def bytes2int(hex_bytes):
-	r = hexlify(unhexlify(hex_bytes)[::-1])
-	if hexlify(bytes([r[0]])) in b'89abcdef':
+	r = bytes.fromhex(hex_bytes)[::-1].hex()
+	if r[0] in '89abcdef': # sign bit is set
 		die(3,"{}: Negative values not permitted in transaction!".format(hex_bytes))
 	return int(r,16)
 
@@ -107,11 +106,11 @@ def bytes2coin_amt(hex_bytes):
 	return g.proto.coin_amt(bytes2int(hex_bytes) * g.proto.coin_amt.min_coin_unit)
 
 def scriptPubKey2addr(s):
-	if len(s) == 50 and s[:6] == b'76a914' and s[-4:] == b'88ac':
+	if len(s) == 50 and s[:6] == '76a914' and s[-4:] == '88ac':
 		return g.proto.pubhash2addr(s[6:-4],p2sh=False),'p2pkh'
-	elif len(s) == 46 and s[:4] == b'a914' and s[-2:] == b'87':
+	elif len(s) == 46 and s[:4] == 'a914' and s[-2:] == '87':
 		return g.proto.pubhash2addr(s[4:-2],p2sh=True),'p2sh'
-	elif len(s) == 44 and s[:4] == g.proto.witness_vernum_hex + b'14':
+	elif len(s) == 44 and s[:4] == g.proto.witness_vernum_hex + '14':
 		return g.proto.pubhash2bech32addr(s[4:]),'bech32'
 	else:
 		raise NotImplementedError('Unknown scriptPubKey ({})'.format(s))
@@ -119,7 +118,7 @@ def scriptPubKey2addr(s):
 from collections import OrderedDict
 class DeserializedTX(OrderedDict,MMGenObject): # need to add MMGen types
 	def __init__(self,txhex):
-		tx = list(unhexlify(txhex))
+		tx = list(bytes.fromhex(txhex))
 		tx_copy = tx[:]
 		d = { 'raw_tx': [] }
 
@@ -127,7 +126,7 @@ class DeserializedTX(OrderedDict,MMGenObject): # need to add MMGen types
 			ret = l[:n]
 			if not skip: d['raw_tx'] += ret
 			del l[:n]
-			return hexlify(bytes(ret[::-1] if reverse else ret))
+			return bytes(ret[::-1] if reverse else ret).hex()
 
 		# https://bitcoin.org/en/developer-reference#compactsize-unsigned-integers
 		# For example, the number 515 is encoded as 0xfd0302.
@@ -135,8 +134,8 @@ class DeserializedTX(OrderedDict,MMGenObject): # need to add MMGen types
 			s = l[0]
 			bytes_len = 1 if s < 0xfd else 2 if s == 0xfd else 4 if s == 0xfe else 8
 			if bytes_len != 1: del l[0]
-			ret = int(hexlify(bytes(l[:bytes_len][::-1])),16)
-			if sub_null: d['raw_tx'] += b'\0'
+			ret = int(bytes(l[:bytes_len][::-1]).hex(),16)
+			if sub_null: d['raw_tx'] += b'\x00'
 			elif not skip: d['raw_tx'] += l[:bytes_len]
 			del l[:bytes_len]
 			return ret
@@ -145,7 +144,7 @@ class DeserializedTX(OrderedDict,MMGenObject): # need to add MMGen types
 		has_witness = tx[0] == 0
 		if has_witness:
 			u = hshift(tx,2,skip=True)
-			if u != b'0001':
+			if u != '0001':
 				raise IllegalWitnessFlagValue("'{}': Illegal value for flag in transaction!".format(u))
 			del tx_copy[-len(tx)-2:-len(tx)]
 
@@ -186,8 +185,8 @@ class DeserializedTX(OrderedDict,MMGenObject): # need to add MMGen types
 				raise WitnessSizeMismatch('More witness data than inputs with witnesses!')
 
 		d['lock_time'] = bytes2int(hshift(tx,4))
-		d['txid'] = hexlify(sha256(sha256(bytes(tx_copy)).digest()).digest()[::-1])
-		d['unsigned_hex'] = hexlify(bytes(d['raw_tx']))
+		d['txid'] = sha256(sha256(bytes(tx_copy)).digest()).digest()[::-1].hex()
+		d['unsigned_hex'] = bytes(d['raw_tx']).hex()
 		del d['raw_tx']
 
 		keys = 'txid','version','lock_time','witness_size','num_txins','txins','num_txouts','txouts','unsigned_hex'
@@ -240,7 +239,7 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam
 
 	class MMGenTxInput(MMGenListItem):
 		for k in txio_attrs: locals()[k] = txio_attrs[k] # in lieu of inheritance
-		scriptPubKey = MMGenListItemAttr('scriptPubKey','HexBytes')
+		scriptPubKey = MMGenListItemAttr('scriptPubKey','HexStr')
 		sequence = MMGenListItemAttr('sequence',int,typeconv=False)
 
 	class MMGenTxOutput(MMGenListItem):
@@ -279,7 +278,7 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam
 		self.outputs     = self.MMGenTxOutputList()
 		self.send_amt    = g.proto.coin_amt('0')  # total amt minus change
 		self.fee         = g.proto.coin_amt('0')
-		self.hex         = b''          # raw serialized hex transaction
+		self.hex         = ''          # raw serialized hex transaction
 		self.label       = MMGenTXLabel('')
 		self.txid        = ''
 		self.coin_txid    = ''
@@ -362,14 +361,14 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam
 			die(2,'{}: duplicate address in transaction {}'.format(attr,io_str))
 
 	def update_txid(self):
-		self.txid = MMGenTxID(make_chksum_6(unhexlify(self.hex)).upper())
+		self.txid = MMGenTxID(make_chksum_6(bytes.fromhex(self.hex)).upper())
 
 	def create_raw(self):
 		i = [{'txid':e.txid,'vout':e.vout} for e in self.inputs]
 		if self.inputs[0].sequence:
 			i[0]['sequence'] = self.inputs[0].sequence
 		o = {e.addr:e.amt for e in self.outputs}
-		self.hex = HexBytes(g.rpch.createrawtransaction(i,o))
+		self.hex = HexStr(g.rpch.createrawtransaction(i,o))
 		self.update_txid()
 
 	# returns true if comment added or changed
@@ -625,11 +624,11 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam
 		self.timestamp = make_timestamp()
 
 	def get_hex_locktime(self):
-		return int(hexlify(unhexlify(self.hex[-8:])[::-1]),16)
+		return int(bytes.fromhex(self.hex[-8:])[::-1].hex(),16)
 
 	def set_hex_locktime(self,val):
 		assert type(val) == int,'locktime value not an integer'
-		self.hex = self.hex[:-8] + hexlify(unhexlify('{:08x}'.format(val))[::-1])
+		self.hex = self.hex[:-8] + bytes.fromhex('{:08x}'.format(val))[::-1].hex()
 
 	def get_blockcount(self):
 		return int(g.rpch.getblockcount())
@@ -653,7 +652,7 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam
 				self.blockcount,
 				('',' LT={}'.format(self.locktime))[bool(self.locktime)]
 			),
-			self.hex.decode(),
+			self.hex,
 			repr([amt_to_str(e.__dict__) for e in self.inputs]),
 			repr([amt_to_str(e.__dict__) for e in self.outputs])
 		]
@@ -661,7 +660,7 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam
 			lines.append(baseconv.b58encode(self.label.encode()))
 		if self.coin_txid:
 			if not self.label: lines.append('-') # keep old tx files backwards compatible
-			lines.append(self.coin_txid.decode())
+			lines.append(self.coin_txid)
 		self.chksum = make_chksum_6(' '.join(lines))
 		self.fmt_data = '\n'.join([self.chksum] + lines)+'\n'
 
@@ -721,13 +720,13 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam
 			return False
 
 		try:
-			self.hex = HexBytes(ret['hex'])
+			self.hex = HexStr(ret['hex'])
 			self.compare_size_and_estimated_size()
 			dt = DeserializedTX(self.hex)
 			self.check_hex_tx_matches_mmgen_tx(dt)
 			self.coin_txid = CoinTxID(dt['txid'],on_fail='raise')
 			self.check_sigs(dt)
-			if not self.coin_txid.decode() == g.rpch.decoderawtransaction(ret['hex'])['txid']:
+			if not self.coin_txid == g.rpch.decoderawtransaction(ret['hex'])['txid']:
 				raise BadMMGenTxID('txid mismatch (after signing)')
 			msg('OK')
 			return True
@@ -782,7 +781,7 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam
 		check_equal('outputs',d_hex,d_mmgen)
 
 		uh = deserial_tx['unsigned_hex']
-		if str(self.txid) != make_chksum_6(unhexlify(uh)).upper():
+		if str(self.txid) != make_chksum_6(bytes.fromhex(uh)).upper():
 			raise TxHexMismatch('MMGen TxID ({}) does not match hex transaction data!\n{}'.format(self.txid,m))
 
 	def check_pubkey_scripts(self):
@@ -807,13 +806,13 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam
 		fs = "Hex TX has {} scriptSig but input is of type '{}'!"
 		for n in range(len(txins)):
 			ti,mmti = txins[n],self.inputs[n]
-			if ti['scriptSig'] == b'' or ( len(ti['scriptSig']) == 46 and # native P2WPKH or P2SH-P2WPKH
-					ti['scriptSig'][:6] == b'16' + g.proto.witness_vernum_hex + b'14' ):
+			if ti['scriptSig'] == '' or ( len(ti['scriptSig']) == 46 and # native P2WPKH or P2SH-P2WPKH
+					ti['scriptSig'][:6] == '16' + g.proto.witness_vernum_hex + '14' ):
 				assert 'witness' in ti, 'missing witness'
 				assert type(ti['witness']) == list and len(ti['witness']) == 2, 'malformed witness'
 				assert len(ti['witness'][1]) == 66, 'incorrect witness pubkey length'
 				assert mmti.mmid, fs.format('witness-type','non-MMGen')
-				assert mmti.mmid.mmtype == ('S','B')[ti['scriptSig']==b''],(
+				assert mmti.mmid.mmtype == ('S','B')[ti['scriptSig']==''],(
 							fs.format('witness-type',mmti.mmid.mmtype))
 			else: # non-witness
 				if mmti.mmid:
@@ -936,7 +935,7 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam
 			if bogus_send:
 				m = 'BOGUS transaction NOT sent: {}'
 			else:
-				assert ret.encode() == self.coin_txid, 'txid mismatch (after sending)'
+				assert ret == self.coin_txid, 'txid mismatch (after sending)'
 				m = 'Transaction sent: {}'
 			self.desc = 'sent transaction'
 			msg(m.format(self.coin_txid.hl()))
@@ -1034,7 +1033,7 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam
 						((n+1,'')[ip],'address:',e.addr.fmt(color=True,width=addr_w) + ' '+mmid_fmt),
 						('','comment:',e.label.hl() if e.label else ''),
 						('','amount:','{} {}'.format(e.amt.hl(),g.dcoin))]
-					items = [(n+1, 'tx,vout:','{},{}'.format(e.txid.decode(),e.vout))] + icommon + [
+					items = [(n+1, 'tx,vout:','{},{}'.format(e.txid,e.vout))] + icommon + [
 						('','confirmations:','{} (around {} days)'.format(confs,days) if blockcount else '')
 					] if ip else icommon + [
 						('','change:',green('True') if e.is_chg else '')]
@@ -1110,7 +1109,7 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam
 		return out # TX label might contain non-ascii chars
 
 	def check_txfile_hex_data(self):
-		self.hex = HexBytes(self.hex,on_fail='raise')
+		self.hex = HexStr(self.hex,on_fail='raise')
 
 	def parse_tx_file(self,infile,metadata_only=False,silent_open=False):
 

+ 7 - 10
mmgen/util.py

@@ -22,7 +22,6 @@ util.py:  Low-level routines imported by other modules in the MMGen suite
 
 import sys,os,time,stat,re,unicodedata
 from hashlib import sha256
-from binascii import hexlify,unhexlify
 from string import hexdigits,digits
 from mmgen.color import *
 from mmgen.exception import *
@@ -260,8 +259,6 @@ def is_int(s):
 
 # https://en.wikipedia.org/wiki/Base32#RFC_4648_Base32_alphabet
 # https://tools.ietf.org/html/rfc4648
-def is_hex_bytes(s):  return set(list(s.lower())) <= set(list(hexdigits.lower().encode()))
-
 def is_hex_str(s):    return set(list(s.lower())) <= set(list(hexdigits.lower()))
 def is_hex_str_lc(s): return set(list(s))         <= set(list(hexdigits.lower()))
 def is_hex_str_uc(s): return set(list(s))         <= set(list(hexdigits.upper()))
@@ -298,12 +295,12 @@ class baseconv(object):
 	@classmethod
 	def b58encode(cls,s,pad=None):
 		pad = cls.get_pad(s,pad,'en',cls.b58pad_lens,[bytes])
-		return cls.fromhex(hexlify(s),'b58',pad=pad,tostr=True)
+		return cls.fromhex(s.hex(),'b58',pad=pad,tostr=True)
 
 	@classmethod
 	def b58decode(cls,s,pad=None):
 		pad = cls.get_pad(s,pad,'de',cls.b58pad_lens_rev,[bytes,str])
-		return unhexlify(cls.tohex(s,'b58',pad=pad*2 if pad else None))
+		return bytes.fromhex(cls.tohex(s,'b58',pad=pad*2 if pad else None))
 
 	@staticmethod
 	def get_pad(s,pad,op,pad_map,ok_types):
@@ -353,13 +350,13 @@ class baseconv(object):
 
 		deconv =  [wl.index(words[::-1][i])*(base**i) for i in range(len(words))]
 		ret = ('{:0{w}x}'.format(sum(deconv),w=pad or 0))
-		return (('','0')[len(ret) % 2] + ret).encode() # return bytes, for consistency with hexlify()
+		return (('','0')[len(ret) % 2] + ret)
 
 	@classmethod
 	def fromhex(cls,hexnum,wl_id,pad=None,tostr=False):
 
-		if not is_hex_str(hexnum.decode()):
-			die(2,"'{}': not a hexadecimal number".format(hexnum.decode()))
+		if not is_hex_str(hexnum):
+			die(2,"{!r}: not a hexadecimal number".format(hexnum))
 
 		wl = cls.digits[wl_id]
 		base = len(wl)
@@ -411,7 +408,7 @@ def pretty_hexdump(data,gw=2,cols=8,line_nums=False):
 	return ''.join(
 		[
 			('' if (line_nums == False or i % cols) else '{:06x}: '.format(i*gw)) +
-				hexlify(data[i*gw:i*gw+gw]).decode() + ('\n',' ')[bool((i+1) % cols)]
+				data[i*gw:i*gw+gw].hex() + ('\n',' ')[bool((i+1) % cols)]
 					for i in range(len(data)//gw + r)
 		]
 	).rstrip() + '\n'
@@ -421,7 +418,7 @@ def decode_pretty_hexdump(data):
 	pat = r'^[{}]+:\s+'.format(hexdigits)
 	lines = [re.sub(pat,'',l) for l in data.splitlines()]
 	try:
-		return unhexlify(''.join((''.join(lines).split())))
+		return bytes.fromhex(''.join((''.join(lines).split())))
 	except:
 		msg('Data not in hexdump format')
 		return False

+ 3 - 4
test/common.py

@@ -29,14 +29,13 @@ class TestSuiteException(Exception): pass
 class TestSuiteFatalException(Exception): pass
 
 import os
-from binascii import hexlify
 from mmgen.common import *
 
-def getrandnum(n): return int(hexlify(os.urandom(n)),16)
-def getrandhex(n): return hexlify(os.urandom(n)).decode()
+def getrandnum(n): return int(os.urandom(n).hex(),16)
+def getrandhex(n): return os.urandom(n).hex()
 def getrandnum_range(nbytes,rn_max):
 	while True:
-		rn = int(hexlify(os.urandom(nbytes)),16)
+		rn = int(os.urandom(nbytes).hex(),16)
 		if rn < rn_max: return rn
 
 def getrandstr(num_chars,no_space=False):

+ 3 - 5
test/gentest.py

@@ -26,8 +26,6 @@ os.chdir(os.path.join(pn,os.pardir))
 sys.path.__setitem__(0,os.path.abspath(os.curdir))
 os.environ['MMGEN_TEST_SUITE'] = '1'
 
-from binascii import hexlify
-
 # Import these _after_ local path's been added to sys.path
 from mmgen.common import *
 from mmgen.obj import MMGenAddrType
@@ -81,7 +79,7 @@ if not 1 <= len(cmd_args) <= 2: opts.usage()
 addr_type = MMGenAddrType(opt.type or g.proto.dfl_mmtype)
 
 def pyethereum_sec2addr(sec):
-	return sec.decode(),hexlify(eth.privtoaddr(sec.decode())).decode()
+	return sec,eth.privtoaddr(sec).hex()
 
 def keyconv_sec2addr(sec):
 	p = sp.Popen(['keyconv','-C',g.coin,sec.wif],stderr=sp.PIPE,stdout=sp.PIPE)
@@ -95,7 +93,7 @@ def zcash_mini_sec2addr(sec):
 
 def pycoin_sec2addr(sec):
 	coin = ci.external_tests['testnet']['pycoin'][g.coin] if g.testnet else g.coin
-	key = pcku.parse_key(sec.decode(),[network_for_netcode(coin)])[1]
+	key = pcku.parse_key(sec,[network_for_netcode(coin)])[1]
 	if key is None: die(1,"can't parse {}".format(sec))
 	d = {
 		'legacy':     ('wif_uncompressed','address_uncompressed'),
@@ -205,7 +203,7 @@ def speed_test():
 	from struct import pack,unpack
 	seed = os.urandom(28)
 	print('Incrementing key with each round')
-	print('Starting key:', hexlify(seed+pack('I',0)))
+	print('Starting key:', (seed + pack('I',0)).hex())
 	import time
 	start = last_t = time.time()
 

+ 5 - 11
test/objtest.py

@@ -25,8 +25,6 @@ pn = os.path.dirname(sys.argv[0])
 os.chdir(os.path.join(pn,os.pardir))
 sys.path.__setitem__(0,os.path.abspath(os.curdir))
 
-from binascii import hexlify
-
 # Import these _after_ local path's been added to sys.path
 from mmgen.common import *
 from mmgen.obj import *
@@ -162,20 +160,16 @@ tests = OrderedDict([
 			'F00BAA12:S:9999999 comment',
 			tw_pfx+'x comment')
 	}),
-	('HexBytes', {
-		'bad':  (1,[],'\0','\1','я','g','gg','FF','f00'),
-		'good': ('deadbeef','f00baa12')
-	}),
 	('MMGenTxID', {
 		'bad':  (1,[],'\0','\1','я','g','gg','FF','f00','F00F0012'),
 		'good': ('DEADBE','F00BAA')
 	}),
 	('CoinTxID',{
-		'bad':  (1,[],'\0','\1','я','g','gg','FF','f00','F00F0012',hexlify(r16),hexlify(r32)+b'ee'),
-		'good': (hexlify(r32),)
+		'bad':  (1,[],'\0','\1','я','g','gg','FF','f00','F00F0012',r16.hex(),r32.hex()+'ee'),
+		'good': (r32.hex(),)
 	}),
 	('WifKey', {
-		'bad':  (1,[],'\0','\1','я','g','gg','FF','f00',hexlify(r16),'2MspvWFjBbkv2wzQGqhxJUYPCk3Y2jMaxLN'),
+		'bad':  (1,[],'\0','\1','я','g','gg','FF','f00',r16.hex(),'2MspvWFjBbkv2wzQGqhxJUYPCk3Y2jMaxLN'),
 		'good': {
 			'btc': (('5KXEpVzjWreTcQoG5hX357s1969MUKNLuSfcszF6yu84kpsNZKb',
 					'KwWr9rDh8KK5TtDa3HLChEvQXNYcUXpwhRFUPc5uSNnMtqNKLFhk'),
@@ -211,8 +205,8 @@ tests = OrderedDict([
 					{'wif':'cSaJAXBAm9ooHpVJgoxqjDG3AcareFy29Cz8mhnNTRijjv2HLgta',
 					'ret':'94fa8b90c11fea8fb907c9376b919534b0a75b9a9621edf71a78753544b4101c'})),
 				}[g.coin.lower()][bool(g.testnet)],
-			{'s':r32,'compressed':False,'pubkey_type':'std','ret':hexlify(r32)},
-			{'s':r32,'compressed':True,'pubkey_type':'std','ret':hexlify(r32)}
+			{'s':r32,'compressed':False,'pubkey_type':'std','ret':r32.hex()},
+			{'s':r32,'compressed':True,'pubkey_type':'std','ret':r32.hex()}
 		)
 	}),
 	('AddrListID', { # a rather pointless test, but do it anyway

+ 6 - 6
test/sha2test.py

@@ -20,7 +20,6 @@ test/sha2test.py: Test MMGen's SHA256 and SHA512 implementations
 """
 
 import sys,os
-from binascii import hexlify
 from mmgen.sha2 import Sha256,Sha512
 from mmgen.util import die
 
@@ -47,10 +46,11 @@ class TestSha2(object):
 
 	def compare_hashes(self,dlen,data):
 		import hashlib
-		sha2 = getattr(hashlib,self.desc)(data).hexdigest().encode()
-		my_sha2 = self.t_cls(data).hexdigest()
-		if my_sha2 != sha2:
-			die(3,'Hashes do not match!\nmy_sha2: {}\nsha2: {}\n'.format(my_sha2,sha2))
+		sha2_ref = getattr(hashlib,self.desc)(data).hexdigest()
+		ret = self.t_cls(data).hexdigest()
+		if ret != sha2_ref:
+			m ='\nHashes do not match!\nReference {d}: {}\nMMGen {d}:     {}'
+			die(3,m.format(sha2_ref,ret,d=self.desc.upper()))
 
 	def test_ref(self,input_n=None):
 
@@ -77,7 +77,7 @@ class TestSha2(object):
 		for i in range(rounds):
 			if i+1 in (1,rounds) or not (i+1) % 10:
 				msg('\rTesting random input data:    {:4}/{} '.format(i+1,rounds))
-			dlen = int(hexlify(os.urandom(4)),16) >> 18
+			dlen = int(os.urandom(4).hex(),16) >> 18
 			self.compare_hashes(dlen,os.urandom(dlen))
 		msg('OK\n')
 

+ 1 - 2
test/test.py

@@ -360,7 +360,7 @@ def list_cmds():
 def do_between():
 	if opt.pause:
 		confirm_continue()
-	elif opt.verbose or opt.exact_output:
+	elif (opt.verbose or opt.exact_output) and not opt.skip_deps:
 		sys.stderr.write('\n')
 
 def list_tmpdirs():
@@ -747,7 +747,6 @@ class TestSuiteRunner(object):
 		# delete files depended on by this cmd
 		arg_list = [get_file_with_ext(cfgs[num]['tmpdir'],ext) for num,ext in d]
 
-
 		if opt.resume:
 			if cmd == opt.resume:
 				bmsg('Resuming at {!r}'.format(cmd))

+ 1 - 1
test/test_py_d/common.py

@@ -104,7 +104,7 @@ def end_silence():
 		g.stdout_fileno = 1
 
 def randbool():
-	return hexlify(os.urandom(1))[1] in b'12345678'
+	return os.urandom(1).hex()[0] in '13579ace'
 
 def disable_debug():
 	global save_debug

+ 1 - 1
test/test_py_d/ts_autosign.py

@@ -73,7 +73,7 @@ class TestSuiteAutosign(TestSuiteBase):
 			def get_pad_chars(n):
 				ret = ''
 				for i in range(n):
-					m = int(hexlify(os.urandom(1)),16) % 32
+					m = int(os.urandom(1).hex(),16) % 32
 					ret += r'123579!@#$%^&*()_+-=[]{}"?/,.<>|'[m]
 				return ret
 

+ 1 - 1
test/test_py_d/ts_ethdev.py

@@ -524,7 +524,7 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared):
 		txid = self.txsend_ui_common(t,caller=mmgen_cmd,quiet=True,bogus_send=False)
 		addr = t.expect_getend('Contract address: ')
 		from mmgen.altcoins.eth.tx import EthereumMMGenTX as etx
-		assert etx.get_exec_status(txid.encode(),True) != 0,(
+		assert etx.get_exec_status(txid,True) != 0,(
 			"Contract '{}:{}' failed to execute. Aborting".format(num,key))
 		if key == 'Token':
 			self.write_to_tmpfile('token_addr{}'.format(num),addr+'\n')

+ 3 - 3
test/test_py_d/ts_main.py

@@ -258,17 +258,17 @@ class TestSuiteMain(TestSuiteBase,TestSuiteShared):
 		if not segwit and k == 'p2sh': k = 'p2pkh'
 		s_beg,s_end = { 'p2pkh':  ('76a914','88ac'),
 						'p2sh':   ('a914','87'),
-						'bech32': (g.proto.witness_vernum_hex.decode()+'14','') }[k]
+						'bech32': (g.proto.witness_vernum_hex + '14','') }[k]
 		amt1,amt2 = {'btc':(10,40),'bch':(10,40),'ltc':(1000,4000)}[g.coin.lower()]
 		ret = {
 			self.lbl_id: '{}:{}'.format(g.proto.base_coin.lower(),coinaddr) if non_mmgen \
 				else ('{}:{}{}'.format(al_id,idx,lbl)),
 			'vout': int(getrandnum(4) % 8),
-			'txid': hexlify(os.urandom(32)),
+			'txid': os.urandom(32).hex(),
 			'amount': g.proto.coin_amt('{}.{}'.format(amt1 + getrandnum(4) % amt2, getrandnum(4) % 100000000)),
 			'address': coinaddr,
 			'spendable': False,
-			'scriptPubKey': '{}{}{}'.format(s_beg,coinaddr.hex.decode(),s_end).encode(),
+			'scriptPubKey': '{}{}{}'.format(s_beg,coinaddr.hex,s_end),
 			'confirmations': getrandnum(3) // 2 # max: 8388608 (7 digits)
 		}
 		return ret

+ 2 - 2
test/test_py_d/ts_regtest.py

@@ -530,7 +530,7 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared):
 		return self.alice_add_label_badaddr(rt_pw,'Invalid coin address for this chain: '+rt_pw)
 
 	def alice_add_label_badaddr2(self):
-		addr = g.proto.pubhash2addr(b'00'*20,False) # mainnet zero address
+		addr = g.proto.pubhash2addr('00'*20,False) # mainnet zero address
 		return self.alice_add_label_badaddr(addr,'Invalid coin address for this chain: '+addr)
 
 	def alice_add_label_badaddr3(self):
@@ -539,7 +539,7 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared):
 			"MMGen address '{}' not found in tracking wallet".format(addr))
 
 	def alice_add_label_badaddr4(self):
-		addr = CoinProtocol(g.coin,True).pubhash2addr(b'00'*20,False) # testnet zero address
+		addr = CoinProtocol(g.coin,True).pubhash2addr('00'*20,False) # testnet zero address
 		return self.alice_add_label_badaddr(addr,
 			"Address '{}' not found in tracking wallet".format(addr))