incog_base.py 5.6 KB

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