From 4821fc203c387453d482478060008a75d43ba5be Mon Sep 17 00:00:00 2001 From: The MMGen Project Date: Sat, 23 Apr 2022 14:36:10 +0000 Subject: [PATCH] mmgen-msg: add `export` command - export signature data relevant for a third-party verifier to JSON --- mmgen/data/release_date | 2 +- mmgen/data/version | 2 +- mmgen/main_msg.py | 21 ++++++++++++++-- mmgen/msg.py | 49 ++++++++++++++++++++++++------------ test/test_py_d/ts_regtest.py | 15 +++++++++-- test/unit_tests_d/ut_msg.py | 6 +++++ 6 files changed, 73 insertions(+), 22 deletions(-) diff --git a/mmgen/data/release_date b/mmgen/data/release_date index fbf89c0d..41108380 100644 --- a/mmgen/data/release_date +++ b/mmgen/data/release_date @@ -1 +1 @@ -March 2022 +April 2022 diff --git a/mmgen/data/version b/mmgen/data/version index 8567e1d6..1d8890b3 100644 --- a/mmgen/data/version +++ b/mmgen/data/version @@ -1 +1 @@ -13.1.dev23 +13.1.dev24 diff --git a/mmgen/main_msg.py b/mmgen/main_msg.py index 77cea14e..5a8606e8 100755 --- a/mmgen/main_msg.py +++ b/mmgen/main_msg.py @@ -65,6 +65,17 @@ class MsgOps: if getattr(m,'failed_sids',None): sys.exit(1) + class export(sign): + + async def __init__(self,msgfile,addr=None): + + from .fileutil import write_data_to_file + + write_data_to_file( + outfile = 'signatures.json', + data = SignedOnlineMsg( infile=msgfile ).get_json_for_export( addr ), + desc = 'signature data' ) + opts_data = { 'text': { 'desc': 'Perform message signing operations for MMGen addresses', @@ -72,6 +83,7 @@ opts_data = { '[opts] create MESSAGE_TEXT ADDRESS_SPEC [...]', '[opts] sign MESSAGE_FILE [WALLET_FILE ...]', '[opts] verify MESSAGE_FILE', + '[opts] export MESSAGE_FILE', ], 'options': """ -h, --help Print this help message @@ -88,6 +100,8 @@ create - create a raw MMGen message file with specified message text for SPECIFIER below) sign - perform signing operation on an unsigned MMGen message file verify - verify and display the contents of a signed MMGen message file +export - dump signed MMGen message file to ‘signatures.json’, including only + data relevant for a third-party verifier ADDRESS SPECIFIER @@ -149,6 +163,9 @@ $ mmgen-msg verify Verify and display a single signature in the signed message file: $ mmgen-msg verify DEADBEEF:B:98 + +Export data relevant for a third-party verifier to ‘signatures.json’: +$ mmgen-msg export """ }, 'code': { @@ -174,10 +191,10 @@ async def main(): if len(cmd_args) < 1: opts.usage() await MsgOps.sign( cmd_args[0], cmd_args[1:] ) - elif op == 'verify': + elif op in ('verify','export'): if len(cmd_args) not in (1,2): opts.usage() - await MsgOps.verify( cmd_args[0], cmd_args[1] if len(cmd_args) == 2 else None ) + await getattr(MsgOps,op)( cmd_args[0], cmd_args[1] if len(cmd_args) == 2 else None ) else: die(1,f'{op!r}: unrecognized operation') diff --git a/mmgen/msg.py b/mmgen/msg.py index f0c6fe08..d58ca5cc 100755 --- a/mmgen/msg.py +++ b/mmgen/msg.py @@ -258,7 +258,7 @@ class coin_msg: class signed_online(signed): - async def verify(self,addr=None,summary=False): + def get_sigs(self,addr): if addr: mmaddr = MMGenID(self.proto,addr) @@ -266,23 +266,40 @@ class coin_msg: else: sigs = self.sigs - if sigs: - from .rpc import rpc_init - self.rpc = await rpc_init(self.proto) - - for k,v in sigs.items(): - ret = await self.do_verify( - addr = v.get('addr_p2pkh') or v['addr'], - sig = v['sig'], - message = self.data['message'] ) - if not ret: - die(3,f'Invalid signature for address {k} ({v["addr"]})') - - if summary: - msg('{} signature{} verified'.format( len(sigs), suf(sigs) )) - else: + if not sigs: die(1,'No signatures') + return sigs + + async def verify(self,addr=None,summary=False): + + sigs = self.get_sigs(addr) + + from .rpc import rpc_init + self.rpc = await rpc_init(self.proto) + + for k,v in sigs.items(): + ret = await self.do_verify( + addr = v.get('addr_p2pkh') or v['addr'], + sig = v['sig'], + message = self.data['message'] ) + if not ret: + die(3,f'Invalid signature for address {k} ({v["addr"]})') + + if summary: + msg('{} signature{} verified'.format( len(sigs), suf(sigs) )) + + def get_json_for_export(self,addr=None): + return json.dumps( + { + 'message': self.data['message'], + 'network': self.data['network'].upper(), + 'signatures': tuple( self.get_sigs(addr).values() ), + }, + sort_keys = True, + indent = 4 + ) + def _get_obj(clsname,coin=None,network='mainnet',infile=None,data=None,*args,**kwargs): assert not args, 'msg:_get_obj(): only keyword args allowed' diff --git a/test/test_py_d/ts_regtest.py b/test/test_py_d/ts_regtest.py index 1ba3f1f7..7adb1d03 100755 --- a/test/test_py_d/ts_regtest.py +++ b/test/test_py_d/ts_regtest.py @@ -266,6 +266,8 @@ 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)'), ('stop', 'stopping regtest daemon'), ) @@ -1083,12 +1085,12 @@ 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'): + def bob_msgverify(self,addr=None,ext='sigmsg.json',cmd='verify'): return self.spawn( 'mmgen-msg', [ '--bob', f'--outdir={self.tmpdir}', - 'verify', + cmd, get_file_with_ext(self.tmpdir,ext), ] + ([addr] if addr else []) ) @@ -1101,6 +1103,15 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared): sid = self._user_sid('bob') return self.bob_msgverify(addr=f'{sid}:{self.dfl_mmtype}:1') + def bob_msgexport(self,addr=None): + t = self.bob_msgverify( addr=addr, cmd='export' ) + t.written_to_file('data') + return t + + def bob_msgexport_single(self): + sid = self._user_sid('bob') + return self.bob_msgexport(addr=f'{sid}:{self.dfl_mmtype}:1') + 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 83952895..a4a7aeb8 100755 --- a/test/unit_tests_d/ut_msg.py +++ b/test/unit_tests_d/ut_msg.py @@ -73,6 +73,12 @@ async def run_test(network_id): pumsg('\nTesting single address verification:\n') await m.verify('A091ABAA:111',summary=opt.verbose) + pumsg('\nTesting JSON dump for export:\n') + msg( m.get_json_for_export() ) + + pumsg('\nTesting single address JSON dump for export:\n') + msg( m.get_json_for_export('A091ABAA:111') ) + stop_test_daemons(network_id) msg('\n')