main_msg.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. #!/usr/bin/env python3
  2. #
  3. # mmgen = Multi-Mode GENerator, a command-line cryptocurrency wallet
  4. # Copyright (C)2013-2022 The MMGen Project <mmgen@tuta.io>
  5. # Licensed under the GNU General Public License, Version 3:
  6. # https://www.gnu.org/licenses
  7. # Public project repositories:
  8. # https://github.com/mmgen/mmgen
  9. # https://gitlab.com/mmgen/mmgen
  10. """
  11. mmgen.main_msg: Message signing operations for the MMGen suite
  12. """
  13. from .base_obj import AsyncInit
  14. from .common import *
  15. from .msg import *
  16. class MsgOps:
  17. ops = ('create','sign','verify')
  18. class create:
  19. def __init__(self,msg,addr_specs):
  20. from .protocol import init_proto_from_opts
  21. proto = init_proto_from_opts()
  22. NewMsg(
  23. coin = proto.coin,
  24. network = proto.network,
  25. message = msg,
  26. addrlists = addr_specs ).write_to_file( ask_overwrite=False )
  27. class sign(metaclass=AsyncInit):
  28. async def __init__(self,msgfile,wallet_files):
  29. m = UnsignedMsg( infile=msgfile )
  30. if not wallet_files:
  31. from .filename import find_file_in_dir
  32. from .wallet import get_wallet_cls
  33. wallet_files = [find_file_in_dir( get_wallet_cls('mmgen'), g.data_dir )]
  34. await m.sign(wallet_files)
  35. m = SignedMsg( data=m.__dict__ )
  36. m.write_to_file( ask_overwrite=False )
  37. if m.data.get('failed_sids'):
  38. sys.exit(1)
  39. class verify(sign):
  40. async def __init__(self,msgfile,addr=None):
  41. try:
  42. m = SignedOnlineMsg( infile=msgfile )
  43. except:
  44. m = ExportedMsgSigs( infile=msgfile )
  45. qmsg(m.format(addr) + '\n')
  46. await m.verify(addr,summary=True)
  47. if m.data.get('failed_sids'):
  48. sys.exit(1)
  49. class export(sign):
  50. async def __init__(self,msgfile,addr=None):
  51. from .fileutil import write_data_to_file
  52. write_data_to_file(
  53. outfile = 'signatures.json',
  54. data = SignedOnlineMsg( infile=msgfile ).get_json_for_export( addr ),
  55. desc = 'signature data' )
  56. opts_data = {
  57. 'text': {
  58. 'desc': 'Perform message signing operations for MMGen addresses',
  59. 'usage2': [
  60. '[opts] create MESSAGE_TEXT ADDRESS_SPEC [...]',
  61. '[opts] sign MESSAGE_FILE [WALLET_FILE ...]',
  62. '[opts] verify MESSAGE_FILE [MMGen ID]',
  63. '[opts] verify <exported JSON dump file> [address]',
  64. '[opts] export MESSAGE_FILE [MMGen ID]',
  65. ],
  66. 'options': """
  67. -h, --help Print this help message
  68. --, --longhelp Print help message for long options (common options)
  69. -d, --outdir=d Output file to directory 'd' instead of working dir
  70. -q, --quiet Produce quieter output
  71. """,
  72. 'notes': """
  73. SUPPORTED OPERATIONS
  74. create - create a raw MMGen message file with specified message text for
  75. signing for addresses specified by ADDRESS_SPEC (see ADDRESS
  76. SPECIFIER below)
  77. sign - perform signing operation on an unsigned MMGen message file
  78. verify - verify and display the contents of a signed MMGen message file
  79. export - dump signed MMGen message file to ‘signatures.json’, including only
  80. data relevant for a third-party verifier
  81. ADDRESS SPECIFIER
  82. The `create` operation takes one or more ADDRESS_SPEC arguments with the
  83. following format:
  84. SEED_ID:ADDR_TYPE:ADDR_IDX_SPEC
  85. where ADDR_TYPE is an address type letter from the list below, and
  86. ADDR_IDX_SPEC is a comma-separated list of address indexes or hyphen-
  87. separated address index ranges.
  88. ADDRESS TYPES
  89. {n_at}
  90. NOTES
  91. Message signing operations are supported for Bitcoin, Ethereum and code forks
  92. thereof.
  93. Ethereum signatures conform to the standard defined by the Geth ‘eth_sign’
  94. JSON-RPC call.
  95. Messages signed for Segwit-P2SH addresses cannot be verified directly using
  96. the Bitcoin Core `verifymessage` RPC call, since such addresses are not hashes
  97. of public keys. As a workaround for this limitation, this utility creates for
  98. each Segwit-P2SH address a non-Segwit address with the same public key to be
  99. used for verification purposes. This non-Segwit verifying address should then
  100. be passed on to the verifying party together with the signature. The verifying
  101. party may then use a tool of their choice (e.g. `mmgen-tool addr2pubhash`) to
  102. assure themselves that the verifying address and Segwit address share the same
  103. public key.
  104. Unfortunately, the aforementioned limitation applies to Segwit-P2PKH (Bech32)
  105. addresses as well, despite the fact that Bech32 addresses are hashes of public
  106. keys (we consider this an implementation shortcoming of `verifymessage`).
  107. Therefore, the above procedure must be followed to verify messages for Bech32
  108. addresses too. `mmgen-tool addr2pubhash` or `bitcoin-cli validateaddress`
  109. may then be used to demonstrate that the two addresses share the same public
  110. key.
  111. EXAMPLES
  112. Create a raw message file for the specified message and specified addresses,
  113. where DEADBEEF is the Seed ID of the user’s default wallet and BEEFCAFE one
  114. of its subwallets:
  115. $ mmgen-msg create '16/3/2022 Earthquake strikes Fukushima coast' DEADBEEF:B:1-3,10,98 BEEFCAFE:S:3,9
  116. Sign the raw message file created by the previous step:
  117. $ mmgen-msg sign <raw message file>
  118. Sign the raw message file using an explicitly supplied wallet:
  119. $ mmgen-msg sign <raw message file> DEADBEEF.bip39
  120. Verify and display all signatures in the signed message file:
  121. $ mmgen-msg verify <signed message file>
  122. Verify and display a single signature in the signed message file:
  123. $ mmgen-msg verify <signed message file> DEADBEEF:B:98
  124. Export data relevant for a third-party verifier to ‘signatures.json’:
  125. $ mmgen-msg export <signed message file>
  126. Same as above, but export only one signature:
  127. $ mmgen-msg export <signed message file> DEADBEEF:B:98
  128. Verify and display the exported JSON signature data:
  129. $ mmgen-msg verify signatures.json
  130. """
  131. },
  132. 'code': {
  133. 'notes': lambda help_notes,s: s.format(
  134. n_at=help_notes('address_types'),
  135. )
  136. }
  137. }
  138. cmd_args = opts.init(opts_data)
  139. if len(cmd_args) < 2:
  140. opts.usage()
  141. op = cmd_args.pop(0)
  142. async def main():
  143. if op == 'create':
  144. if len(cmd_args) < 2:
  145. opts.usage()
  146. MsgOps.create( cmd_args[0], ' '.join(cmd_args[1:]) )
  147. elif op == 'sign':
  148. if len(cmd_args) < 1:
  149. opts.usage()
  150. await MsgOps.sign( cmd_args[0], cmd_args[1:] )
  151. elif op in ('verify','export'):
  152. if len(cmd_args) not in (1,2):
  153. opts.usage()
  154. await getattr(MsgOps,op)( cmd_args[0], cmd_args[1] if len(cmd_args) == 2 else None )
  155. else:
  156. die(1,f'{op!r}: unrecognized operation')
  157. run_session(main())