From 09ab734ab32905d224ed6fd62473b237fdcade6e Mon Sep 17 00:00:00 2001 From: The MMGen Project Date: Thu, 28 Apr 2022 11:00:52 +0000 Subject: [PATCH] mmgen-msg: support verification and display of exported JSON data Usage: $ mmgen-msg verify signatures.json Testing: $ test/unit_tests.py -v msg --- mmgen/base_proto/bitcoin/msg.py | 2 ++ mmgen/main_msg.py | 8 +++++--- mmgen/msg.py | 29 +++++++++++++++++++++++++---- test/test_py_d/ts_regtest.py | 24 +++++++++++++++++++++--- test/unit_tests_d/ut_msg.py | 26 +++++++++++++++++++++++++- 5 files changed, 78 insertions(+), 11 deletions(-) diff --git a/mmgen/base_proto/bitcoin/msg.py b/mmgen/base_proto/bitcoin/msg.py index 0829f975..0b0a2332 100755 --- a/mmgen/base_proto/bitcoin/msg.py +++ b/mmgen/base_proto/bitcoin/msg.py @@ -33,3 +33,5 @@ class coin_msg(coin_msg): async def do_verify(self,addr,sig,message): return await self.rpc.call( 'verifymessage', addr, sig, message ) + + class exported_sigs(coin_msg.exported_sigs,signed_online): pass diff --git a/mmgen/main_msg.py b/mmgen/main_msg.py index 4fc56ec9..6b0f9947 100755 --- a/mmgen/main_msg.py +++ b/mmgen/main_msg.py @@ -55,8 +55,10 @@ class MsgOps: class verify(sign): async def __init__(self,msgfile,addr=None): - - m = SignedOnlineMsg( infile=msgfile ) + try: + m = SignedOnlineMsg( infile=msgfile ) + except: + m = ExportedMsgSigs( infile=msgfile ) qmsg(m.format(addr) + '\n') @@ -81,7 +83,7 @@ opts_data = { 'usage2': [ '[opts] create MESSAGE_TEXT ADDRESS_SPEC [...]', '[opts] sign MESSAGE_FILE [WALLET_FILE ...]', - '[opts] verify MESSAGE_FILE', + '[opts] verify MESSAGE_FILE or exported JSON dump', '[opts] export MESSAGE_FILE', ], 'options': """ diff --git a/mmgen/msg.py b/mmgen/msg.py index 69f25cd8..e9c65898 100755 --- a/mmgen/msg.py +++ b/mmgen/msg.py @@ -19,7 +19,7 @@ from .util import msg,vmsg,die,suf,make_chksum_6,fmt_list,remove_dups from .color import red,orange,grnbg from .protocol import init_proto from .fileutil import get_data_from_file,write_data_to_file -from .addr import MMGenID +from .addr import MMGenID,CoinAddr class MMGenIDRange(str,Hilite,InitErrors,MMGenObject): """ @@ -87,7 +87,7 @@ class coin_msg: def get_proto_from_file(self,filename): data = json.loads(get_data_from_file(filename)) - network_id = data['metadata']['network'] + network_id = data['metadata']['network'] if 'metadata' in data else data['network'].lower() coin,network = network_id.split('_') return init_proto( coin=coin, network=network ) @@ -162,7 +162,9 @@ class coin_msg: yield fs1.format( v[0], v[1](self.data[k]) ) if self.sigs: yield 'Signature data:' - k = MMGenID(self.proto,req_addr) + k = ( + CoinAddr(self.proto,req_addr) if type(self).__name__ == 'exported_sigs' else + MMGenID(self.proto,req_addr) ) if k not in self.sigs: die(1,f'{k}: address not found in signature data') for res in gen_entry(self.sigs[k]): @@ -271,7 +273,9 @@ class coin_msg: def get_sigs(self,addr): if addr: - req_addr = MMGenID(self.proto,addr) + req_addr = ( + CoinAddr(self.proto,addr) if type(self).__name__ == 'exported_sigs' else + MMGenID(self.proto,addr) ) sigs = {k:v for k,v in self.sigs.items() if k == req_addr} else: sigs = self.sigs @@ -312,6 +316,22 @@ class coin_msg: indent = 4 ) + class exported_sigs(signed_online): + + def __init__(self,infile,*args,**kwargs): + + self.data = json.loads( + get_data_from_file( + infile = infile, + desc = self.desc ) + ) + + def gen_sigs(): + for e in self.data['signatures']: + yield e + + self.sigs = {e['addr']:e for e in gen_sigs()} + def _get_obj(clsname,coin=None,network='mainnet',infile=None,data=None,*args,**kwargs): assert not args, 'msg:_get_obj(): only keyword args allowed' @@ -348,3 +368,4 @@ CompletedMsg = _get('completed') UnsignedMsg = _get('unsigned') SignedMsg = _get('signed') SignedOnlineMsg = _get('signed_online') +ExportedMsgSigs = _get('exported_sigs') diff --git a/test/test_py_d/ts_regtest.py b/test/test_py_d/ts_regtest.py index 1c0a2734..369f3396 100755 --- a/test/test_py_d/ts_regtest.py +++ b/test/test_py_d/ts_regtest.py @@ -266,8 +266,10 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared): ('bob_msgverify', 'verifying the message file (all addresses)'), ('bob_msgverify_raw', 'verifying the raw message file (all addresses)'), ('bob_msgverify_single', 'verifying the message file (single address)'), - ('bob_msgexport', 'exporting the message file'), ('bob_msgexport_single', 'exporting the message file (single address)'), + ('bob_msgexport', 'exporting the message file (all addresses)'), + ('bob_msgverify_export', 'verifying the exported JSON data (all addresses)'), + ('bob_msgverify_export_single','verifying the exported JSON data (single address)'), ('stop', 'stopping regtest daemon'), ) @@ -1087,13 +1089,13 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared): fn2 = get_file_with_ext(self.tmpdir,'bip39') return self.bob_msgsign([fn2,fn1]) - def bob_msgverify(self,addr=None,ext='sigmsg.json',cmd='verify'): + def bob_msgverify(self,addr=None,ext='sigmsg.json',cmd='verify',msgfile=None): return self.spawn( 'mmgen-msg', [ '--bob', f'--outdir={self.tmpdir}', cmd, - get_file_with_ext(self.tmpdir,ext), + msgfile or get_file_with_ext(self.tmpdir,ext), ] + ([addr] if addr else []) ) def bob_msgverify_raw(self): @@ -1114,6 +1116,22 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared): sid = self._user_sid('bob') return self.bob_msgexport(addr=f'{sid}:{self.dfl_mmtype}:1') + def bob_msgverify_export(self): + return self.bob_msgverify( + msgfile = os.path.join(self.tmpdir,'signatures.json') + ) + + def bob_msgverify_export_single(self): + sid = self._user_sid('bob') + mmid = f'{sid}:{self.dfl_mmtype}:1' + t = self.spawn('mmgen-tool', [ '--bob', '--color=0', 'listaddress', mmid ], no_msg=True) + addr = t.expect_getend(mmid).split()[0] + t.close() + return self.bob_msgverify( + addr = addr, + msgfile = os.path.join(self.tmpdir,'signatures.json') + ) + def stop(self): if opt.no_daemon_stop: self.spawn('',msg_only=True) diff --git a/test/unit_tests_d/ut_msg.py b/test/unit_tests_d/ut_msg.py index 3cf521ed..a7bcb5ea 100755 --- a/test/unit_tests_d/ut_msg.py +++ b/test/unit_tests_d/ut_msg.py @@ -9,7 +9,8 @@ from test.include.common import silence,end_silence,restart_test_daemons,stop_te from mmgen.opts import opt from mmgen.util import msg,bmsg,pumsg from mmgen.protocol import CoinProtocol -from mmgen.msg import NewMsg,UnsignedMsg,SignedMsg,SignedOnlineMsg +from mmgen.msg import NewMsg,UnsignedMsg,SignedMsg,SignedOnlineMsg,ExportedMsgSigs +from mmgen.addr import MMGenID def get_obj(coin,network): @@ -65,6 +66,7 @@ async def run_test(network_id): msg(m.format()) single_addr = 'A091ABAA:111' + single_addr_coin = m.sigs[MMGenID(m.proto,single_addr)]['addr'] pumsg('\nTesting single address display:\n') msg(m.format(single_addr)) @@ -81,6 +83,28 @@ async def run_test(network_id): pumsg('\nTesting single address JSON dump for export:\n') msg( m.get_json_for_export(single_addr) ) + from mmgen.fileutil import write_data_to_file + exported_sigs = os.path.join(tmpdir,'signatures.json') + write_data_to_file( + outfile = exported_sigs, + data = m.get_json_for_export(), + desc = 'signature data', + ask_overwrite = False ) + + m = ExportedMsgSigs( infile=exported_sigs ) + + pumsg('\nTesting verification (exported data):\n') + await m.verify(summary=opt.verbose) + + pumsg('\nTesting single address verification (exported data):\n') + await m.verify(single_addr_coin,summary=opt.verbose) + + pumsg('\nTesting display (exported data):\n') + msg(m.format()) + + pumsg('\nTesting single address display (exported data):\n') + msg(m.format(single_addr_coin)) + stop_test_daemons(network_id) msg('\n')