From af2bec1220e986ce4679e5623a969eed730aaf3b Mon Sep 17 00:00:00 2001 From: MMGen Date: Wed, 31 Oct 2018 16:32:03 +0000 Subject: [PATCH] py3port: new HexBytes class --- mmgen/addr.py | 2 +- mmgen/altcoins/eth/tx.py | 12 ++++++------ mmgen/obj.py | 40 +++++++++++++++++++++++++++++----------- mmgen/rpc.py | 6 +++++- mmgen/tw.py | 2 +- mmgen/tx.py | 8 ++++---- mmgen/util.py | 2 ++ test/objtest.py | 2 +- 8 files changed, 49 insertions(+), 25 deletions(-) diff --git a/mmgen/addr.py b/mmgen/addr.py index d0a0b4ca..0f3ae9d9 100755 --- a/mmgen/addr.py +++ b/mmgen/addr.py @@ -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): diff --git a/mmgen/altcoins/eth/tx.py b/mmgen/altcoins/eth/tx.py index 2ae555f8..80a6c534 100755 --- a/mmgen/altcoins/eth/tx.py +++ b/mmgen/altcoins/eth/tx.py @@ -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'] diff --git a/mmgen/obj.py b/mmgen/obj.py index 2b46c0b0..18172f5b 100755 --- a/mmgen/obj.py +++ b/mmgen/obj.py @@ -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: diff --git a/mmgen/rpc.py b/mmgen/rpc.py index d2ceaa0a..637fee03 100755 --- a/mmgen/rpc.py +++ b/mmgen/rpc.py @@ -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: diff --git a/mmgen/tw.py b/mmgen/tw.py index c8bb544e..1354d468 100755 --- a/mmgen/tw.py +++ b/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','HexStr') + scriptPubKey = MMGenImmutableAttr('scriptPubKey','HexBytes') days = MMGenListItemAttr('days',int,typeconv=False) skip = MMGenListItemAttr('skip',str,typeconv=False,reassign_ok=True) diff --git a/mmgen/tx.py b/mmgen/tx.py index db423471..107c6c03 100755 --- a/mmgen/tx.py +++ b/mmgen/tx.py @@ -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): diff --git a/mmgen/util.py b/mmgen/util.py index f953b5be..2f427be4 100755 --- a/mmgen/util.py +++ b/mmgen/util.py @@ -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())) diff --git a/test/objtest.py b/test/objtest.py index b285197b..e707aa3d 100755 --- a/test/objtest.py +++ b/test/objtest.py @@ -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') }),