incog_hidden.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  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. # 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-wallet
  9. # https://gitlab.com/mmgen/mmgen-wallet
  10. """
  11. wallet.incog_hidden: hidden incognito wallet class
  12. """
  13. import sys, os
  14. from ..seed import Seed
  15. from ..util import msg, die, capfirst
  16. from ..util2 import parse_bytespec
  17. from .incog_base import wallet
  18. class wallet(wallet):
  19. desc = 'hidden incognito data'
  20. file_mode = 'binary'
  21. no_tty = True
  22. _msg = {
  23. 'choose_file_size': """
  24. You must choose a size for your new hidden incog data. The minimum size
  25. is {} bytes, which puts the incog data right at the end of the file.
  26. Since you probably want to hide your data somewhere in the middle of the
  27. file where it's harder to find, you're advised to choose a much larger file
  28. size than this.
  29. """,
  30. 'check_incog_id': """
  31. Check generated Incog ID above against your records. If it doesn't match,
  32. then your incognito data is incorrect or corrupted, or you may have speci-
  33. fied an incorrect offset.
  34. """,
  35. 'record_incog_id': """
  36. Make a record of the Incog ID but keep it secret. You will used it to
  37. identify the incog wallet data in the future and to locate the offset
  38. where the data is hidden in the event you forget it.
  39. """,
  40. 'decrypt_params': ', hash preset, offset {} seed length'}
  41. def _get_hincog_params(self, wtype):
  42. # permit comma in filename:
  43. fn, offset = getattr(self.cfg, f'hidden_incog_{wtype}_params').rsplit(',', 1)
  44. return fn, int(offset)
  45. def _check_valid_offset(self, fn, action):
  46. d = self.ssdata
  47. m = ('Input', 'Destination')[action=='write']
  48. if fn.size < d.hincog_offset + d.target_data_len:
  49. die(1, '{a} file {b!r} has length {c}, too short to {d} {e} bytes of data at offset {f}'.format(
  50. a = m,
  51. b = fn.name,
  52. c = fn.size,
  53. d = action,
  54. e = d.target_data_len,
  55. f = d.hincog_offset))
  56. def _get_data(self):
  57. d = self.ssdata
  58. d.hincog_offset = self._get_hincog_params('input')[1]
  59. self.cfg._util.qmsg(f'Getting hidden incog data from file {self.infile.name!r}')
  60. # Already sanity-checked:
  61. d.target_data_len = self._get_incog_data_len(self.cfg.seed_len or Seed.dfl_len)
  62. self._check_valid_offset(self.infile, 'read')
  63. flgs = os.O_RDONLY|os.O_BINARY if sys.platform == 'win32' else os.O_RDONLY
  64. fh = os.open(self.infile.name, flgs)
  65. os.lseek(fh, int(d.hincog_offset), os.SEEK_SET)
  66. self.fmt_data = os.read(fh, d.target_data_len)
  67. os.close(fh)
  68. self.cfg._util.qmsg(f'Data read from file {self.infile.name!r} at offset {d.hincog_offset}')
  69. # overrides method in Wallet
  70. def write_to_file(self, **kwargs):
  71. d = self.ssdata
  72. self._format()
  73. self.cfg._util.compare_or_die(
  74. val1 = d.target_data_len,
  75. desc1 = 'target data length',
  76. val2 = len(self.fmt_data),
  77. desc2 = 'length of formatted ' + self.desc)
  78. k = ('output', 'input')[self.op=='pwchg_new']
  79. fn, d.hincog_offset = self._get_hincog_params(k)
  80. if self.cfg.outdir and not os.path.dirname(fn):
  81. fn = os.path.join(self.cfg.outdir, fn)
  82. check_offset = True
  83. try:
  84. os.stat(fn)
  85. except:
  86. from ..ui import keypress_confirm, line_input
  87. keypress_confirm(
  88. self.cfg,
  89. f'Requested file {fn!r} does not exist. Create?',
  90. default_yes = True,
  91. do_exit = True)
  92. min_fsize = d.target_data_len + d.hincog_offset
  93. msg('\n ' + self.msg['choose_file_size'].strip().format(min_fsize)+'\n')
  94. while True:
  95. fsize = parse_bytespec(line_input(self.cfg, 'Enter file size: '))
  96. if fsize >= min_fsize:
  97. break
  98. msg(f'File size must be an integer no less than {min_fsize}')
  99. from ..tool.fileutil import tool_cmd
  100. tool_cmd(self.cfg).rand2file(fn, str(fsize))
  101. check_offset = False
  102. from ..filename import MMGenFile
  103. f = MMGenFile(fn, subclass=type(self), write=True)
  104. self.cfg._util.dmsg('{} data len {}, offset {}'.format(
  105. capfirst(self.desc),
  106. d.target_data_len,
  107. d.hincog_offset))
  108. if check_offset:
  109. self._check_valid_offset(f, 'write')
  110. if not self.cfg.quiet:
  111. from ..ui import confirm_or_raise
  112. confirm_or_raise(
  113. self.cfg,
  114. message = '',
  115. action = f'alter file {f.name!r}')
  116. flgs = os.O_RDWR|os.O_BINARY if sys.platform == 'win32' else os.O_RDWR
  117. fh = os.open(f.name, flgs)
  118. os.lseek(fh, int(d.hincog_offset), os.SEEK_SET)
  119. os.write(fh, self.fmt_data)
  120. os.close(fh)
  121. msg('{} written to file {!r} at offset {}'.format(
  122. capfirst(self.desc),
  123. f.name,
  124. d.hincog_offset))