py3port: new HexBytes class

This commit is contained in:
The MMGen Project 2018-10-31 16:32:03 +00:00
commit af2bec1220
Signed by: mmgen
GPG key ID: 3F8B1861E32B7DA2
8 changed files with 49 additions and 25 deletions

View file

@ -68,7 +68,7 @@ class AddrGeneratorSegwit(AddrGenerator):
def to_segwit_redeem_script(self,pubhex):
assert pubhex.compressed,'Uncompressed public keys incompatible with Segwit'
return HexStr(g.proto.pubhex2redeem_script(pubhex))
return HexBytes(g.proto.pubhex2redeem_script(pubhex))
class AddrGeneratorBech32(AddrGenerator):
def to_addr(self,pubhex):

View file

@ -46,7 +46,7 @@ class EthereumMMGenTX(MMGenTX):
usr_rel_fee = None # not in MMGenTX
disable_fee_check = False
txobj = None # ""
data = HexStr('')
data = HexBytes('')
def __init__(self,*args,**kwargs):
super(EthereumMMGenTX,self).__init__(*args,**kwargs)
@ -55,7 +55,7 @@ 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 = HexStr(open(opt.contract_data).read().strip())
self.data = HexBytes(open(opt.contract_data).read().strip())
self.disable_fee_check = True
@classmethod
@ -84,7 +84,7 @@ class EthereumMMGenTX(MMGenTX):
def check_pubkey_scripts(self): pass
def check_sigs(self,deserial_tx=None):
if is_hex_str(self.hex):
if is_hex_bytes(self.hex):
self.mark_signed()
return True
return False
@ -105,7 +105,7 @@ class EthereumMMGenTX(MMGenTX):
'gasPrice': ETHAmt(d['gasprice'],'wei'),
'startGas': ETHAmt(d['startgas'],'wei'),
'nonce': ETHNonce(d['nonce']),
'data': HexStr(d['data']) }
'data': HexBytes(d['data']) }
if o['data'] and not o['to']:
self.token_addr = TokenAddr(hexlify(etx.creates).decode())
txid = CoinTxID(hexlify(etx.hash))
@ -119,7 +119,7 @@ class EthereumMMGenTX(MMGenTX):
'startGas': ETHAmt(d['startGas']),
'nonce': ETHNonce(d['nonce']),
'chainId': Int(d['chainId']),
'data': HexStr(d['data']) }
'data': HexBytes(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
@ -413,7 +413,7 @@ class EthereumTokenMMGenTX(EthereumMMGenTX):
def set_g_token(self):
g.dcoin = self.dcoin
if is_hex_str(self.hex): return # for txsend we can leave g.token uninitialized
if is_hex_bytes(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']

View file

@ -559,18 +559,20 @@ 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 HexStr(str,Hilite,InitErrors):
class HexBytes(bytes,Hilite,InitErrors):
color = 'red'
trunc_ok = False
dtype = bytes
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) in (str,str,bytes),'not a string'
assert type(s) == str,'not a string'
assert set(s) <= set(getattr(hexdigits,case)()),'not {}case hexadecimal symbols'.format(case)
assert not len(s) % 2,'odd-length string'
return str.__new__(cls,s)
return cls.dtype.__new__(cls,s.encode() if cls.dtype == bytes else 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)
@ -578,25 +580,41 @@ class HexStr(str,Hilite,InitErrors):
class Str(str,Hilite): pass
class Int(int,Hilite): pass
class HexStrWithWidth(HexStr):
class HexBytesWithWidth(HexBytes):
color = 'nocolor'
trunc_ok = False
hexcase = 'lower'
width = None
parent_cls = HexBytes
def __new__(cls,s,on_fail='die'):
cls.arg_chk(cls,on_fail)
try:
ret = HexStr.__new__(cls,s,case=cls.hexcase,on_fail='raise')
ret = cls.parent_cls.__new__(cls,s,case=cls.hexcase,on_fail='raise')
assert len(s) == cls.width,'Value is not {} characters wide'.format(cls.width)
return ret
except Exception as e:
m = "{}\n{!r}: value cannot be converted to {}"
return cls.init_fail(m.format(e.args[0],s,cls.__name__),on_fail)
class MMGenTxID(HexStrWithWidth): color,width,hexcase = 'red',6,'upper'
class MoneroViewKey(HexStrWithWidth): color,width,hexcase = 'cyan',64,'lower'
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 WalletPassword(HexStrWithWidth): color,width,hexcase = 'blue',32,'lower'
class CoinTxID(HexStrWithWidth): color,width,hexcase = 'purple',64,'lower'
class MoneroViewKey(HexStrWithWidth): color,width,hexcase = 'cyan',64,'lower'
class MMGenTxID(HexStrWithWidth): color,width,hexcase = 'red',6,'upper'
class WifKey(str,Hilite,InitErrors):
width = 53
@ -613,11 +631,11 @@ 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(HexStr,MMGenObject): # TODO: add some real checks
class PubKey(HexBytes,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 = HexStr.__new__(cls,s,case='lower',on_fail='raise')
me = HexBytes.__new__(cls,s,case='lower',on_fail='raise')
me.compressed = compressed
return me
except Exception as e:

View file

@ -110,11 +110,15 @@ 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
class MyJSONEncoder(json.JSONEncoder):
def default(self,obj):
if isinstance(obj,g.proto.coin_amt):
return ca_type(obj)
return json.JSONEncoder.default(self,obj)
elif isinstance(obj,CoinTxID) or isinstance(obj,HexBytes):
return obj.decode()
else:
return json.JSONEncoder.default(self,obj)
http_hdr = { 'Content-Type': 'application/json' }
if self.auth:

View file

@ -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','HexStr')
scriptPubKey = MMGenImmutableAttr('scriptPubKey','HexBytes')
days = MMGenListItemAttr('days',int,typeconv=False)
skip = MMGenListItemAttr('skip',str,typeconv=False,reassign_ok=True)

View file

@ -238,7 +238,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','HexStr')
scriptPubKey = MMGenListItemAttr('scriptPubKey','HexBytes')
sequence = MMGenListItemAttr('sequence',(int,int)[g.platform=='win'],typeconv=False)
class MMGenTxOutput(MMGenListItem):
@ -368,7 +368,7 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam
if self.inputs[0].sequence:
i[0]['sequence'] = self.inputs[0].sequence
o = dict([(e.addr,e.amt) for e in self.outputs])
self.hex = g.rpch.createrawtransaction(i,o)
self.hex = HexBytes(g.rpch.createrawtransaction(i,o))
self.update_txid()
# returns true if comment added or changed
@ -729,7 +729,7 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam
return False
try:
self.hex = ret['hex']
self.hex = HexBytes(ret['hex'])
self.compare_size_and_estimated_size()
dt = DeserializedTX(self.hex)
self.check_hex_tx_matches_mmgen_tx(dt)
@ -1113,7 +1113,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 = HexStr(self.hex,on_fail='raise')
self.hex = HexBytes(self.hex,on_fail='raise')
def parse_tx_file(self,infile,metadata_only=False,silent_open=False):

View file

@ -241,6 +241,8 @@ 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()))

View file

@ -159,7 +159,7 @@ tests = OrderedDict([
'F00BAA12:S:9999999 comment',
tw_pfx+'x comment')
}),
('HexStr', {
('HexBytes', {
'bad': (1,[],'\0','\1','я','g','gg','FF','f00'),
'good': ('deadbeef','f00baa12')
}),