incog_base.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  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_base: incognito wallet base class
  12. """
  13. from ..seed import Seed
  14. from ..util import msg, make_chksum_8, die
  15. from .enc import wallet
  16. class wallet(wallet):
  17. _msg = {
  18. 'check_incog_id': """
  19. Check the generated Incog ID above against your records. If it doesn't
  20. match, then your incognito data is incorrect or corrupted.
  21. """,
  22. 'record_incog_id': """
  23. Make a record of the Incog ID but keep it secret. You will use it to
  24. identify your incog wallet data in the future.
  25. """,
  26. 'decrypt_params': " {} hash preset"
  27. }
  28. def _make_iv_chksum(self, s):
  29. from hashlib import sha256
  30. return sha256(s).hexdigest()[:8].upper()
  31. def _get_incog_data_len(self, seed_len):
  32. return (
  33. self.crypto.aesctr_iv_len
  34. + self.crypto.salt_len
  35. + (0 if self.cfg.old_incog_fmt else self.crypto.hincog_chk_len)
  36. + seed_len//8)
  37. def _incog_data_size_chk(self):
  38. # valid sizes: 56, 64, 72
  39. dlen = len(self.fmt_data)
  40. seed_len = self.cfg.seed_len or Seed.dfl_len
  41. valid_dlen = self._get_incog_data_len(seed_len)
  42. if dlen == valid_dlen:
  43. return True
  44. else:
  45. if self.cfg.old_incog_fmt:
  46. msg('WARNING: old-style incognito format requested. Are you sure this is correct?')
  47. msg(f'Invalid incognito data size ({dlen} bytes) for this seed length ({seed_len} bits)')
  48. msg(f'Valid data size for this seed length: {valid_dlen} bytes')
  49. for sl in Seed.lens:
  50. if dlen == self._get_incog_data_len(sl):
  51. die(1, f'Valid seed length for this data size: {sl} bits')
  52. msg(f'This data size ({dlen} bytes) is invalid for all available seed lengths')
  53. return False
  54. def _encrypt (self):
  55. self._get_first_pw_and_hp_and_encrypt_seed()
  56. if self.cfg.old_incog_fmt:
  57. die(1, 'Writing old-format incognito wallets is unsupported')
  58. d = self.ssdata
  59. crypto = self.crypto
  60. d.iv = crypto.get_random(crypto.aesctr_iv_len)
  61. d.iv_id = self._make_iv_chksum(d.iv)
  62. msg(f'New Incog Wallet ID: {d.iv_id}')
  63. self.cfg._util.qmsg('Make a record of this value')
  64. self.cfg._util.vmsg('\n ' + self.msg['record_incog_id'].strip()+'\n')
  65. d.salt = crypto.get_random(crypto.salt_len)
  66. seed_key = crypto.make_key(
  67. passwd = d.passwd,
  68. salt = d.salt,
  69. hash_preset = d.hash_preset,
  70. desc = 'incog wallet key')
  71. from hashlib import sha256
  72. chk = sha256(self.seed.data).digest()[:8]
  73. d.enc_seed = crypto.encrypt_seed(
  74. data = chk + self.seed.data,
  75. key = seed_key)
  76. # IV is used BOTH to initialize counter and to salt password!
  77. d.wrapper_key = crypto.make_key(
  78. passwd = d.passwd,
  79. salt = d.iv,
  80. hash_preset = d.hash_preset,
  81. desc = 'incog wrapper key')
  82. d.key_id = make_chksum_8(d.wrapper_key)
  83. self.cfg._util.vmsg(f'Key ID: {d.key_id}')
  84. d.target_data_len = self._get_incog_data_len(self.seed.bitlen)
  85. def _format(self):
  86. d = self.ssdata
  87. self.fmt_data = d.iv + self.crypto.encrypt_data(
  88. data = d.salt + d.enc_seed,
  89. key = d.wrapper_key,
  90. iv = d.iv,
  91. desc = self.desc)
  92. def _filename(self):
  93. s = self.seed
  94. d = self.ssdata
  95. return '{}-{}-{}[{},{}].{}'.format(
  96. s.fn_stem,
  97. d.key_id,
  98. d.iv_id,
  99. s.bitlen,
  100. d.hash_preset,
  101. self.ext)
  102. def _deformat(self):
  103. if not self._incog_data_size_chk():
  104. return False
  105. d = self.ssdata
  106. d.iv = self.fmt_data[0:self.crypto.aesctr_iv_len]
  107. d.incog_id = self._make_iv_chksum(d.iv)
  108. d.enc_incog_data = self.fmt_data[self.crypto.aesctr_iv_len:]
  109. msg(f'Incog Wallet ID: {d.incog_id}')
  110. self.cfg._util.qmsg('Check this value against your records')
  111. self.cfg._util.vmsg('\n ' + self.msg['check_incog_id'].strip()+'\n')
  112. return True
  113. def _verify_seed_newfmt(self, data):
  114. chk, seed = data[:8], data[8:]
  115. from hashlib import sha256
  116. if sha256(seed).digest()[:8] == chk:
  117. self.cfg._util.qmsg('Passphrase{} are correct'.format(self.msg['decrypt_params'].format('and')))
  118. return seed
  119. else:
  120. msg('Incorrect passphrase{}'.format(self.msg['decrypt_params'].format('or')))
  121. return False
  122. def _verify_seed_oldfmt(self, seed):
  123. prompt = f'Seed ID: {make_chksum_8(seed)}. Is the Seed ID correct?'
  124. from ..ui import keypress_confirm
  125. if keypress_confirm(self.cfg, prompt, default_yes=True):
  126. return seed
  127. else:
  128. return False
  129. def _decrypt(self):
  130. d = self.ssdata
  131. self._get_hash_preset(add_desc=d.incog_id)
  132. d.passwd = self._get_passphrase(add_desc=d.incog_id)
  133. crypto = self.crypto
  134. # IV is used BOTH to initialize counter and to salt password!
  135. wrapper_key = crypto.make_key(
  136. passwd = d.passwd,
  137. salt = d.iv,
  138. hash_preset = d.hash_preset,
  139. desc = 'wrapper key')
  140. dd = crypto.decrypt_data(
  141. enc_data = d.enc_incog_data,
  142. key = wrapper_key,
  143. iv = d.iv,
  144. desc = 'incog data')
  145. d.salt = dd[0:crypto.salt_len]
  146. d.enc_seed = dd[crypto.salt_len:]
  147. seed_key = crypto.make_key(
  148. passwd = d.passwd,
  149. salt = d.salt,
  150. hash_preset = d.hash_preset,
  151. desc = 'main key')
  152. self.cfg._util.qmsg(f'Key ID: {make_chksum_8(seed_key)}')
  153. verify_seed_func = getattr(self, '_verify_seed_'+ ('oldfmt' if self.cfg.old_incog_fmt else 'newfmt'))
  154. seed = verify_seed_func(
  155. crypto.decrypt_seed(
  156. enc_seed = d.enc_seed,
  157. key = seed_key,
  158. seed_id = '',
  159. key_id = ''))
  160. if seed:
  161. self.seed = Seed(self.cfg, seed_bin=seed)
  162. msg(f'Seed ID: {self.seed.sid}')
  163. return True
  164. else:
  165. return False