From b84e68d48611c0f5643edc9fc0ac2d43175b6e62 Mon Sep 17 00:00:00 2001 From: The MMGen Project Date: Tue, 3 May 2022 21:01:04 +0000 Subject: [PATCH] message signing: cleanups, default to base cls if proto cls missing --- mmgen/base_proto/bitcoin/msg.py | 14 ++++-------- mmgen/base_proto/ethereum/msg.py | 14 ++++-------- mmgen/main_msg.py | 2 +- mmgen/msg.py | 39 +++++++++++++++++--------------- mmgen/proto/btc.py | 1 - mmgen/proto/eth.py | 1 - test/test_py_d/ts_ethdev.py | 24 ++++++++++++-------- test/unit_tests_d/ut_msg.py | 18 ++++++++------- 8 files changed, 56 insertions(+), 57 deletions(-) diff --git a/mmgen/base_proto/bitcoin/msg.py b/mmgen/base_proto/bitcoin/msg.py index b8e4fc17..4e9dba95 100755 --- a/mmgen/base_proto/bitcoin/msg.py +++ b/mmgen/base_proto/bitcoin/msg.py @@ -16,20 +16,16 @@ from ...msg import coin_msg class coin_msg(coin_msg): - class base(coin_msg.base): pass + include_pubhash = True + sigdata_pfx = None + msghash_types = ('raw',) # first-listed is the default - class new(base,coin_msg.new): pass - - class completed(base,coin_msg.completed): pass - - class unsigned(completed,coin_msg.unsigned): + class unsigned(coin_msg.unsigned): async def do_sign(self,wif,message,msghash_type): return await self.rpc.call( 'signmessagewithprivkey', wif, message ) - class signed(completed,coin_msg.signed): pass - - class signed_online(signed,coin_msg.signed_online): + class signed_online(coin_msg.signed_online): async def do_verify(self,addr,sig,message,msghash_type): return await self.rpc.call( 'verifymessage', addr, sig, message ) diff --git a/mmgen/base_proto/ethereum/msg.py b/mmgen/base_proto/ethereum/msg.py index 29e1533a..b3039ed8 100755 --- a/mmgen/base_proto/ethereum/msg.py +++ b/mmgen/base_proto/ethereum/msg.py @@ -16,21 +16,17 @@ from ...msg import coin_msg class coin_msg(coin_msg): - class base(coin_msg.base): pass + include_pubhash = False + sigdata_pfx = '0x' + msghash_types = ('eth_sign','raw') # first-listed is the default - class new(base,coin_msg.new): pass - - class completed(base,coin_msg.completed): pass - - class unsigned(completed,coin_msg.unsigned): + class unsigned(coin_msg.unsigned): async def do_sign(self,wif,message,msghash_type): from .misc import ec_sign_message_with_privkey return ec_sign_message_with_privkey( message, bytes.fromhex(wif), msghash_type ) - class signed(completed,coin_msg.signed): pass - - class signed_online(signed,coin_msg.signed_online): + class signed_online(coin_msg.signed_online): async def do_verify(self,addr,sig,message,msghash_type): from ...tool.coin import tool_cmd diff --git a/mmgen/main_msg.py b/mmgen/main_msg.py index 1a23ea5b..b0eefb84 100755 --- a/mmgen/main_msg.py +++ b/mmgen/main_msg.py @@ -29,7 +29,7 @@ class MsgOps: network = proto.network, message = msg, addrlists = addr_specs, - msghash_type = opt.msghash_type or proto.msghash_types[0] + msghash_type = opt.msghash_type ).write_to_file( ask_overwrite=False ) class sign(metaclass=AsyncInit): diff --git a/mmgen/msg.py b/mmgen/msg.py index d952ddcc..7867d7e4 100755 --- a/mmgen/msg.py +++ b/mmgen/msg.py @@ -49,12 +49,11 @@ class MMGenIDRange(str,Hilite,InitErrors,MMGenObject): class coin_msg: - supported_base_protos = ('Bitcoin','Ethereum') - class base(MMGenObject): ext = 'rawmsg.json' signed = False + chksum_keys = ('addrlists','message','msghash_type','network') @property def desc(self): @@ -64,7 +63,7 @@ class coin_msg: def chksum(self): return make_chksum_6( json.dumps( - {k:v for k,v in self.data.items() if k in ('addrlists','message','network','msghash_type')}, + {k:self.data[k] for k in self.chksum_keys}, sort_keys = True, separators = (',', ':') )) @@ -107,7 +106,8 @@ class coin_msg: class new(base): def __init__(self,message,addrlists,msghash_type,*args,**kwargs): - if msghash_type not in self.proto.msghash_types: + msghash_type = msghash_type or self.msg_cls.msghash_types[0] + if msghash_type not in self.msg_cls.msghash_types: die(2,f'msghash_type {msghash_type!r} not supported for {self.proto.base_proto} protocol') self.data = { 'network': '{}_{}'.format( self.proto.coin.lower(), self.proto.network ), @@ -181,7 +181,7 @@ class coin_msg: 'failed_sids': ('Failed Seed IDs:', lambda v: red(fmt_list(v,fmt='bare')) ), } - if len(self.proto.msghash_types) == 1: + if len(self.msg_cls.msghash_types) == 1: del hdr_data['msghash_type'] if req_addr or type(self).__name__ == 'exported_sigs': @@ -193,7 +193,7 @@ class coin_msg: fs1 = '{:%s} {}' % max(len(v[0]) for v in hdr_data.values()) fs2 = '{:%s} %s{}' % ( max(len(labels[k]) for v in self.sigs.values() for k in v.keys()), - '0x' if self.proto.base_proto == 'Ethereum' else '' + self.msg_cls.sigdata_pfx or '' ) if req_addr: @@ -229,7 +229,7 @@ class coin_msg: 'addr': e.addr, 'sig': sig, } - if self.proto.base_proto != 'Ethereum': + if self.msg_cls.include_pubhash: data.update({ 'pubhash': self.proto.parse_addr(e.addr_p2pkh or e.addr).bytes.hex() }) if e.addr_p2pkh: @@ -319,8 +319,9 @@ class coin_msg: def get_json_for_export(self,addr=None): sigs = list( self.get_sigs(addr).values() ) - if self.proto.base_proto == 'Ethereum': - sigs = [{k:'0x'+v for k,v in e.items()} for e in sigs] + pfx = self.msg_cls.sigdata_pfx + if pfx: + sigs = [{k:pfx+v for k,v in e.items()} for e in sigs] return json.dumps( { 'message': self.data['message'], @@ -342,9 +343,10 @@ class coin_msg: desc = self.desc ) ) + pfx = self.msg_cls.sigdata_pfx self.sigs = {sig_data['addr']:sig_data for sig_data in ( - [{k:v[2:] for k,v in e.items()} for e in self.data['signatures']] # remove '0x' prefix - if self.proto.base_proto == 'Ethereum' else + [{k:v[len(pfx):] for k,v in e.items()} for e in self.data['signatures']] + if pfx else self.data['signatures'] )} @@ -362,14 +364,15 @@ def _get_obj(clsname,coin=None,network='mainnet',infile=None,data=None,*args,**k init_proto( coin=coin, network=network ) if coin else coin_msg.base().get_proto_from_file(infile) ) - if proto.base_proto not in coin_msg.supported_base_protos: - die(f'Message signing operations not supported for {proto.base_proto} protocol') + try: + msg_cls = getattr( + importlib.import_module(f'mmgen.base_proto.{proto.base_proto.lower()}.msg'), + 'coin_msg' ) + except: + die(1,f'Message signing operations not supported for {proto.base_proto} protocol') - cls = getattr( - getattr(importlib.import_module(f'mmgen.base_proto.{proto.base_proto.lower()}.msg'),'coin_msg'), - clsname ) - - me = MMGenObject.__new__(cls) + me = MMGenObject.__new__(getattr( msg_cls, clsname, getattr(coin_msg,clsname) )) + me.msg_cls = msg_cls me.proto = proto me.__init__(infile=infile,data=data,*args,**kwargs) diff --git a/mmgen/proto/btc.py b/mmgen/proto/btc.py index f94eafd0..e370a578 100755 --- a/mmgen/proto/btc.py +++ b/mmgen/proto/btc.py @@ -43,7 +43,6 @@ class mainnet(CoinProtocol.Secp256k1): # chainparams.cpp witness_vernum = int(witness_vernum_hex,16) bech32_hrp = 'bc' sign_mode = 'daemon' - msghash_types = ('raw',) # first-listed is the default avg_bdi = int(9.7 * 60) # average block discovery interval (historical) halving_interval = 210000 max_halvings = 64 diff --git a/mmgen/proto/eth.py b/mmgen/proto/eth.py index dc46fa53..c4798b71 100755 --- a/mmgen/proto/eth.py +++ b/mmgen/proto/eth.py @@ -30,7 +30,6 @@ class mainnet(CoinProtocol.DummyWIF,CoinProtocol.Secp256k1): max_tx_fee = '0.005' chain_names = ['ethereum','foundation'] sign_mode = 'standalone' - msghash_types = ('eth_sign','raw') # first-listed is the default caps = ('token',) mmcaps = ('key','addr','rpc','tx') base_proto = 'Ethereum' diff --git a/test/test_py_d/ts_ethdev.py b/test/test_py_d/ts_ethdev.py index 971c5c60..5802d169 100755 --- a/test/test_py_d/ts_ethdev.py +++ b/test/test_py_d/ts_ethdev.py @@ -712,9 +712,10 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared): t.written_to_file('Signed message data') return t - def msgverify(self,fn=None): + def msgverify(self,fn=None,msghash_type='eth_sign'): fn = fn or get_file_with_ext(self.tmpdir,'sigmsg.json') t = self.spawn('mmgen-msg', self.eth_args_noquiet + [ 'verify', fn ]) + t.expect(msghash_type) t.expect('1 signature verified') return t @@ -725,25 +726,28 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared): return t def msgverify_export(self): - return self.msgverify( fn=os.path.join(self.tmpdir,'signatures.json') ) + return self.msgverify( + fn = os.path.join(self.tmpdir,'signatures.json') ) def msgcreate_raw(self): get_file_with_ext(self.tmpdir,'rawmsg.json',delete_all=True) return self.msgcreate(add_args=['--msghash-type=raw']) - def msgsign_raw(self,*args,**kwargs): + def msgsign_raw(self): get_file_with_ext(self.tmpdir,'sigmsg.json',delete_all=True) - return self.msgsign(*args,**kwargs) + return self.msgsign() - def msgverify_raw(self,*args,**kwargs): - return self.msgverify(*args,**kwargs) + def msgverify_raw(self): + return self.msgverify(msghash_type='raw') - def msgexport_raw(self,*args,**kwargs): + def msgexport_raw(self): get_file_with_ext(self.tmpdir,'signatures.json',no_dot=True,delete_all=True) - return self.msgexport(*args,**kwargs) + return self.msgexport() - def msgverify_export_raw(self,*args,**kwargs): - return self.msgverify_export(*args,**kwargs) + def msgverify_export_raw(self): + return self.msgverify( + fn = os.path.join(self.tmpdir,'signatures.json'), + msghash_type = 'raw' ) def txcreate4(self): return self.txcreate( diff --git a/test/unit_tests_d/ut_msg.py b/test/unit_tests_d/ut_msg.py index aed65d15..991631be 100755 --- a/test/unit_tests_d/ut_msg.py +++ b/test/unit_tests_d/ut_msg.py @@ -29,7 +29,7 @@ def get_obj(coin,network,msghash_type): addrlists = addrlists, msghash_type = msghash_type ) -async def run_test(network_id,msghash_type='raw'): +async def run_test(network_id,chksum,msghash_type='raw'): coin,network = CoinProtocol.Base.parse_network_id(network_id) @@ -48,6 +48,8 @@ async def run_test(network_id,msghash_type='raw'): os.makedirs(tmpdir,exist_ok=True) + assert m.chksum.upper() == chksum, f'{m.chksum.upper()} != {chksum}' + m.write_to_file( outdir = tmpdir, ask_overwrite = False ) @@ -122,22 +124,22 @@ class unit_tests: altcoin_deps = ('ltc','bch','eth','eth_raw') def btc(self,name,ut): - return run_test('btc') + return run_test('btc','AA0DB5') def btc_tn(self,name,ut): - return run_test('btc_tn') + return run_test('btc_tn','A88E1D') def btc_rt(self,name,ut): - return run_test('btc_rt') + return run_test('btc_rt','578018') def ltc(self,name,ut): - return run_test('ltc') + return run_test('ltc','BA7549') def bch(self,name,ut): - return run_test('bch') + return run_test('bch','1B8065') def eth(self,name,ut): - return run_test('eth',msghash_type='eth_sign') + return run_test('eth','35BAD9',msghash_type='eth_sign') def eth_raw(self,name,ut): - return run_test('eth') + return run_test('eth','9D900C')