main_autosign.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. #!/usr/bin/env python3
  2. #
  3. # MMGen Wallet, a terminal-based cryptocurrency wallet
  4. # Copyright (C)2013-2025 The MMGen Project <mmgen@tuta.io>
  5. #
  6. # This program is free software: you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation, either version 3 of the License, or
  9. # (at your option) any later version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. """
  19. autosign: Auto-sign MMGen transactions, message files and XMR wallet output files
  20. """
  21. import sys
  22. from .util import msg, die, fmt_list, exit_if_mswin, async_run
  23. exit_if_mswin('autosigning')
  24. opts_data = {
  25. 'sets': [('stealth_led', True, 'led', True)],
  26. 'text': {
  27. 'desc': 'Auto-sign MMGen transactions, message files and XMR wallet output files',
  28. 'usage':'[opts] [operation]',
  29. 'options': """
  30. -h, --help Print this help message
  31. --, --longhelp Print help message for long (global) options
  32. -c, --coins=c Coins to sign for (comma-separated list)
  33. -I, --no-insert-check Don’t check for device insertion
  34. -k, --keys-from-file=F Use wif keys listed in file ‘F’ for signing non-MMGen
  35. inputs. The file may be MMGen encrypted if desired. The
  36. ‘setup’ operation creates a temporary encrypted copy of
  37. the file in volatile memory for use during the signing
  38. session, thus permitting the deletion of the original
  39. file for increased security.
  40. -l, --seed-len=N Specify wallet seed length of ‘N’ bits (for setup only)
  41. -L, --led Use status LED to signal standby, busy and error
  42. -m, --mountpoint=M Specify an alternate mountpoint 'M'
  43. (default: {asi.dfl_mountpoint!r})
  44. -M, --mnemonic-fmt=F During setup, prompt for mnemonic seed phrase of format
  45. 'F' (choices: {mn_fmts}; default: {asi.dfl_mn_fmt!r})
  46. -n, --no-summary Don’t print a transaction summary
  47. -r, --macos-ramdisk-size=S Set the size (in MB) of the ramdisk used to store
  48. the offline signing wallet(s) on macOS machines. By
  49. default, a runtime-calculated value will be used. This
  50. option is of interest only for setups with unusually
  51. large Monero wallets
  52. -s, --stealth-led Stealth LED mode - signal busy and error only, and only
  53. after successful authorization.
  54. -S, --full-summary Print a full summary of each signed transaction after
  55. each autosign run. The default list of non-MMGen outputs
  56. will not be printed.
  57. -q, --quiet Produce quieter output
  58. -v, --verbose Produce more verbose output
  59. -w, --wallet-dir=D Specify an alternate wallet dir
  60. (default: {asi.dfl_wallet_dir!r})
  61. -W, --allow-non-wallet-swap Allow signing of swap transactions that send funds
  62. to non-wallet addresses
  63. -x, --xmrwallets=L Range or list of wallets to be used for XMR autosigning
  64. """,
  65. 'notes': """
  66. OPERATIONS
  67. clean - clean the removable device of unneeded files, removing only non-
  68. essential data
  69. gen_key - generate the wallet encryption key and copy it to the removable
  70. device mounted at mountpoint ‘{asi.mountpoint}’ (as currently
  71. configured)
  72. setup - full setup: run ‘gen_key’ and create temporary signing wallet(s)
  73. for all configured coins
  74. xmr_setup - set up Monero temporary signing wallet(s). Not required during
  75. normal operation: use ‘setup’ with --xmrwallets instead
  76. macos_ramdisk_setup - set up the ramdisk used for storing the temporary signing
  77. wallet(s) (macOS only). Required only when creating the wallet(s)
  78. manually, without ‘setup’
  79. macos_ramdisk_delete - delete the macOS ramdisk
  80. disable_swap - disable disk swap to prevent potentially sensitive data in
  81. volatile memory from being swapped to disk. Applicable only when
  82. creating temporary signing wallet(s) manually, without ‘setup’
  83. enable_swap - reenable disk swap. For testing only, should not be invoked in
  84. a production environment
  85. wait - start in loop mode: wait-mount-sign-unmount-wait
  86. wipe_key - wipe the wallet encryption key on the removable device, making
  87. signing transactions or stealing the user’s seed impossible.
  88. The operation is intended as a ‘kill switch’ and thus performed
  89. without prompting
  90. USAGE NOTES
  91. If no operation is specified, this program mounts a removable device
  92. (typically a USB flash drive) containing unsigned MMGen transactions, message
  93. files, and/or XMR wallet output files, signs them, unmounts the removable
  94. device and exits.
  95. If invoked with ‘wait’, the program waits in a loop, mounting the removable
  96. device, performing signing operations and unmounting the device every time it
  97. is inserted.
  98. On supported platforms (currently Orange Pi, Rock Pi and Raspberry Pi boards),
  99. the status LED indicates whether the program is busy or in standby mode, i.e.
  100. ready for device insertion or removal.
  101. The removable device must have a partition with a filesystem labeled MMGEN_TX
  102. and a user-writable root directory. For interoperability between OS-es, it’s
  103. recommended to use the exFAT file system.
  104. On both the signing and online machines the mountpoint ‘{asi.mountpoint}’
  105. (as currently configured) must exist. Linux (not macOS) machines must have
  106. an ‘/etc/fstab’ with the following entry:
  107. LABEL=MMGEN_TX {asi.mountpoint} auto noauto,user 0 0
  108. Signing is performed with a temporary wallet created in volatile memory in
  109. the directory ‘{asi.wallet_dir}’ (as currently configured). The wallet is
  110. encrypted with a 32-byte password saved in the file ‘autosign.key’ in the
  111. root of the removable device’s filesystem.
  112. The password and temporary wallet may be created in one operation by invoking
  113. ‘mmgen-autosign setup’ with the removable device inserted. In this case, the
  114. temporary wallet is created from the user’s default wallet, if it exists and
  115. the user so desires. If not, the user is prompted to enter a seed phrase.
  116. Alternatively, the password and temporary wallet may be created separately by
  117. first invoking ‘mmgen-autosign gen_key’ and then creating and encrypting the
  118. wallet using the -P (--passwd-file) option:
  119. $ mmgen-walletconv -iwords -d{asi.wallet_dir} -p1 -N -P{asi.mountpoint}/autosign.key -Lfoo
  120. Note that the hash preset must be ‘1’. To use a wallet file as the source
  121. instead of an MMGen seed phrase, omit the ‘-i’ option and add the wallet
  122. file path to the end of the command line. Multiple temporary wallets may
  123. be created in this way and used for signing (note, however, that for XMR
  124. operations only one wallet is supported).
  125. Autosigning is currently supported on Linux and macOS only.
  126. SECURITY NOTE
  127. By placing wallet and password on separate devices, this program creates
  128. a two-factor authentication setup whereby an attacker must gain physical
  129. control of both the removable device and signing machine in order to sign
  130. transactions. It’s therefore recommended to always keep the removable device
  131. secure, separated from the signing machine and hidden (in your pocket, for
  132. example) when not transacting. In addition, since login access on the
  133. signing machine is required to steal the user’s seed, it’s good practice
  134. to lock the signing machine’s screen once the setup process is complete.
  135. As a last resort, cutting power to the signing machine will destroy the
  136. volatile memory where the temporary wallet resides and foil any attack,
  137. even if you’ve lost control of the removable device.
  138. Always remember to power off the signing machine when your signing session
  139. is over.
  140. """
  141. },
  142. 'code': {
  143. 'options': lambda s: s.format(
  144. asi = asi,
  145. mn_fmts = fmt_list(asi.mn_fmts, fmt='no_spc'),
  146. ),
  147. 'notes': lambda s: s.format(asi=asi)
  148. }
  149. }
  150. def main(do_loop):
  151. asi.init_led()
  152. asi.init_exit_handler()
  153. async def do():
  154. await asi.check_daemons_running()
  155. if do_loop:
  156. await asi.main_loop()
  157. else:
  158. ret = await asi.do_sign()
  159. asi.at_exit(not ret)
  160. async_run(cfg, do)
  161. from .cfg import Config
  162. from .autosign import Autosign
  163. cfg = Config(
  164. opts_data = opts_data,
  165. init_opts = {
  166. 'out_fmt': 'wallet',
  167. 'usr_randchars': 0,
  168. 'hash_preset': '1',
  169. 'label': 'Autosign Wallet'},
  170. caller_post_init = True)
  171. cmd = cfg._args[0] if len(cfg._args) == 1 else 'sign' if not cfg._args else cfg._usage()
  172. if cmd not in Autosign.cmds + Autosign.util_cmds:
  173. die(1, f'‘{cmd}’: unrecognized command')
  174. if cmd != 'setup':
  175. for opt in ('seed_len', 'mnemonic_fmt', 'keys_from_file'):
  176. if getattr(cfg, opt):
  177. die(1, f'--{opt.replace("_", "-")} is valid only for the ‘setup’ operation')
  178. if cmd not in ('sign', 'wait'):
  179. for opt in ('no_summary', 'led', 'stealth_led', 'full_summary'):
  180. if getattr(cfg, opt):
  181. die(1, f'--{opt.replace("_", "-")} is not valid for the ‘{cmd}’ operation')
  182. asi = Autosign(cfg, cmd=cmd)
  183. cfg._post_init()
  184. match cmd:
  185. case 'gen_key':
  186. asi.gen_key()
  187. case 'setup':
  188. asi.setup()
  189. from .ui import keypress_confirm
  190. if cfg.xmrwallets and keypress_confirm(cfg, '\nContinue with Monero setup?', default_yes=True):
  191. msg('')
  192. asi.xmr_setup()
  193. asi.do_umount()
  194. case 'xmr_setup':
  195. if not cfg.xmrwallets:
  196. die(1, 'Please specify a wallet or range of wallets with the --xmrwallets option')
  197. asi.do_mount()
  198. asi.xmr_setup()
  199. asi.do_umount()
  200. case 'macos_ramdisk_setup' | 'macos_ramdisk_delete':
  201. if sys.platform != 'darwin':
  202. die(1, f'The ‘{cmd}’ operation is for the macOS platform only')
  203. getattr(asi, cmd)()
  204. case 'enable_swap':
  205. asi.swap.enable()
  206. case 'disable_swap':
  207. asi.swap.disable()
  208. case 'sign':
  209. main(do_loop=False)
  210. case 'wait':
  211. main(do_loop=True)
  212. case 'clean':
  213. asi.do_mount()
  214. asi.clean_old_files()
  215. asi.do_umount()
  216. case 'wipe_key':
  217. asi.do_mount()
  218. asi.wipe_encryption_key()
  219. asi.do_umount()