main_wallet.py 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. #!/usr/bin/env python
  2. #
  3. # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
  4. # Copyright (C)2013-2018 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. mmgen/main_wallet: Entry point for MMGen wallet-related scripts
  20. """
  21. import os
  22. from mmgen.common import *
  23. from mmgen.seed import SeedSource,Wallet
  24. from mmgen.filename import find_file_in_dir
  25. from mmgen.obj import MMGenWalletLabel
  26. usage = '[opts] [infile]'
  27. nargs = 1
  28. iaction = 'convert'
  29. oaction = 'convert'
  30. invoked_as = 'passchg' if g.prog_name == 'mmgen-passchg' else g.prog_name.partition('-wallet')[2]
  31. bw_note = True
  32. # full: defhHiJkKlLmoOpPqrSvz-
  33. if invoked_as == 'gen':
  34. desc = 'Generate an {pnm} wallet from a random seed'
  35. opt_filter = 'ehdoJlLpPqrSvz-'
  36. usage = '[opts]'
  37. oaction = 'output'
  38. nargs = 0
  39. elif invoked_as == 'conv':
  40. desc = 'Convert an {pnm} wallet from one format to another'
  41. opt_filter = 'dehHiJkKlLmoOpPqrSvz-'
  42. elif invoked_as == 'chk':
  43. desc = 'Check validity of an {pnm} wallet'
  44. opt_filter = 'ehiHOlpPqrvz-'
  45. iaction = 'input'
  46. elif invoked_as == 'passchg':
  47. desc = 'Change the passphrase, hash preset or label of an {pnm} wallet'
  48. opt_filter = 'efhdiHkKOlLmpPqrSvz-'
  49. iaction = 'input'
  50. bw_note = False
  51. else:
  52. die(1,"'%s': unrecognized invocation" % g.prog_name)
  53. opts_data = lambda: {
  54. # Can't use: share/Opts doesn't know anything about fmt codes
  55. # 'sets': [('hidden_incog_output_params',bool,'out_fmt','hi')],
  56. 'desc': desc.format(pnm=g.proj_name),
  57. 'usage': usage,
  58. 'options': """
  59. -h, --help Print this help message
  60. --, --longhelp Print help message for long options (common options)
  61. -d, --outdir= d Output files to directory 'd' instead of working dir
  62. -e, --echo-passphrase Echo passphrases and other user input to screen
  63. -f, --force-update Force update of wallet even if nothing has changed
  64. -i, --in-fmt= f {iaction} from wallet format 'f' (see FMT CODES below)
  65. -o, --out-fmt= f {oaction} to wallet format 'f' (see FMT CODES below)
  66. -H, --hidden-incog-input-params=f,o Read hidden incognito data from file
  67. 'f' at offset 'o' (comma-separated)
  68. -J, --hidden-incog-output-params=f,o Write hidden incognito data to file
  69. 'f' at offset 'o' (comma-separated). File 'f' will be
  70. created if necessary and filled with random data.
  71. -O, --old-incog-fmt Specify old-format incognito input
  72. -k, --keep-passphrase Reuse passphrase of input wallet for output wallet
  73. -K, --keep-hash-preset Reuse hash preset of input wallet for output wallet
  74. -l, --seed-len= l Specify wallet seed length of 'l' bits. This option
  75. is required only for brainwallet and incognito inputs
  76. with non-standard (< {g.seed_len}-bit) seed lengths.
  77. -L, --label= l Specify a label 'l' for output wallet
  78. -m, --keep-label Reuse label of input wallet for output wallet
  79. -p, --hash-preset= p Use the scrypt hash parameters defined by preset 'p'
  80. for password hashing (default: '{g.hash_preset}')
  81. -z, --show-hash-presets Show information on available hash presets
  82. -P, --passwd-file= f Get wallet passphrase from file 'f'
  83. -q, --quiet Produce quieter output; suppress some warnings
  84. -r, --usr-randchars=n Get 'n' characters of additional randomness from user
  85. (min={g.min_urandchars}, max={g.max_urandchars}, default={g.usr_randchars})
  86. -S, --stdout Write wallet data to stdout instead of file
  87. -v, --verbose Produce more verbose output
  88. """.format(
  89. g=g,
  90. iaction=capfirst(iaction),
  91. oaction=capfirst(oaction),
  92. ),
  93. 'notes': """
  94. {n_pw}{n_bw}
  95. FMT CODES:
  96. {f}
  97. """.format(
  98. f='\n '.join(SeedSource.format_fmt_codes().splitlines()),
  99. n_pw=help_notes('passwd'),
  100. n_bw=('','\n\n' + help_notes('brainwallet'))[bw_note]
  101. )
  102. }
  103. cmd_args = opts.init(opts_data,opt_filter=opt_filter)
  104. if opt.label:
  105. opt.label = MMGenWalletLabel(opt.label,msg="Error in option '--label'")
  106. sf = get_seed_file(cmd_args,nargs,invoked_as=invoked_as)
  107. if not invoked_as == 'chk': do_license_msg()
  108. dw_msg = ('',yellow(' (default wallet)'))[bool(sf and os.path.dirname(sf)==g.data_dir)]
  109. if invoked_as in ('conv','passchg'):
  110. msg(green('Processing input wallet')+dw_msg)
  111. ss_in = None if invoked_as == 'gen' else SeedSource(sf,passchg=(invoked_as=='passchg'))
  112. if invoked_as == 'chk':
  113. lbl = ss_in.ssdata.label.hl() if hasattr(ss_in.ssdata,'label') else 'NONE'
  114. vmsg('Wallet label: {}'.format(lbl))
  115. # TODO: display creation date
  116. sys.exit(0)
  117. if invoked_as in ('conv','passchg'):
  118. msg(green('Processing output wallet'))
  119. ss_out = SeedSource(ss=ss_in,passchg=invoked_as=='passchg')
  120. if invoked_as == 'gen':
  121. qmsg("This wallet's Seed ID: %s" % ss_out.seed.sid.hl())
  122. if invoked_as == 'passchg':
  123. if not (opt.force_update or [k for k in ('passwd','hash_preset','label')
  124. if getattr(ss_out.ssdata,k) != getattr(ss_in.ssdata,k)]):
  125. die(1,'Password, hash preset and label are unchanged. Taking no action')
  126. m1 = yellow('Confirmation of default wallet update')
  127. m2 = 'update the default wallet'
  128. m3 = 'Make this wallet your default and move it to the data directory?'
  129. if invoked_as == 'passchg' and ss_in.infile.dirname == g.data_dir:
  130. confirm_or_exit(m1,m2,exit_msg='Password not changed')
  131. ss_out.write_to_file(desc='New wallet',outdir=g.data_dir)
  132. msg('Securely deleting old wallet')
  133. from subprocess import check_output,CalledProcessError
  134. sd_cmd = (['wipe','-sf'],['sdelete','-p','20'])[g.platform=='win']
  135. try:
  136. check_output(sd_cmd + [ss_in.infile.name])
  137. except:
  138. msg(yellow("WARNING: '%s' command failed, using regular file delete instead" % sd_cmd[0]))
  139. # msg('Command output: {}\nReturn value {}'.format(e.output,e.returncode))
  140. os.unlink(ss_in.infile.name)
  141. elif invoked_as == 'gen' and not find_file_in_dir(Wallet,g.data_dir) \
  142. and not opt.stdout and keypress_confirm(m3,default_yes=True):
  143. ss_out.write_to_file(outdir=g.data_dir)
  144. else:
  145. ss_out.write_to_file()
  146. if invoked_as == 'passchg':
  147. if ss_out.ssdata.passwd == ss_in.ssdata.passwd:
  148. msg('New and old passphrases are the same')
  149. else:
  150. msg('Wallet passphrase has changed')
  151. if ss_out.ssdata.hash_preset != ss_in.ssdata.hash_preset:
  152. msg("Hash preset has been changed to '{}'".format(ss_out.ssdata.hash_preset))