Browse Source

mmgen-msg: add `export` command

- export signature data relevant for a third-party verifier to JSON
The MMGen Project 3 years ago
parent
commit
4821fc203c
6 changed files with 72 additions and 21 deletions
  1. 1 1
      mmgen/data/release_date
  2. 1 1
      mmgen/data/version
  3. 19 2
      mmgen/main_msg.py
  4. 32 15
      mmgen/msg.py
  5. 13 2
      test/test_py_d/ts_regtest.py
  6. 6 0
      test/unit_tests_d/ut_msg.py

+ 1 - 1
mmgen/data/release_date

@@ -1 +1 @@
-March 2022
+April 2022

+ 1 - 1
mmgen/data/version

@@ -1 +1 @@
-13.1.dev23
+13.1.dev24

+ 19 - 2
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 <signed message file>
 
 Verify and display a single signature in the signed message file:
 $ mmgen-msg verify <signed message file> DEADBEEF:B:98
+
+Export data relevant for a third-party verifier to ‘signatures.json’:
+$ mmgen-msg export <signed message file>
 """
 	},
 	'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')
 

+ 32 - 15
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,22 +266,39 @@ class coin_msg:
 			else:
 				sigs = self.sigs
 
-			if sigs:
-				from .rpc import rpc_init
-				self.rpc = await rpc_init(self.proto)
+			if not sigs:
+				die(1,'No signatures')
 
-				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"]})')
+			return sigs
 
-				if summary:
-					msg('{} signature{} verified'.format( len(sigs), suf(sigs) ))
-			else:
-				die(1,'No signatures')
+		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):
 

+ 13 - 2
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)

+ 6 - 0
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')