Browse Source

message signing: cleanups, default to base cls if proto cls missing

The MMGen Project 2 years ago
parent
commit
b84e68d486

+ 5 - 9
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 )

+ 5 - 9
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

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

+ 21 - 18
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')
-
-	cls = getattr(
-		getattr(importlib.import_module(f'mmgen.base_proto.{proto.base_proto.lower()}.msg'),'coin_msg'),
-		clsname )
+	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')
 
-	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)

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

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

+ 14 - 10
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(

+ 10 - 8
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')