dieroll.py 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. #!/usr/bin/env python3
  2. #
  3. # mmgen = Multi-Mode GENerator, a command-line cryptocurrency wallet
  4. # Copyright (C)2013-2023 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.dieroll: dieroll wallet class
  12. """
  13. import time
  14. from ..util import msg,msg_r,die,fmt,remove_whitespace
  15. from ..util2 import block_format
  16. from ..seed import Seed
  17. from ..baseconv import baseconv
  18. from .unenc import wallet
  19. class wallet(wallet):
  20. stdin_ok = True
  21. desc = 'base6d die roll seed data'
  22. conv_cls = baseconv
  23. wl_id = 'b6d'
  24. mn_type = 'base6d'
  25. choose_seedlen_prompt = 'Choose a seed length: 1) 128 bits, 2) 192 bits, 3) 256 bits: '
  26. choose_seedlen_confirm = 'Seed length of {} bits chosen. OK?'
  27. user_entropy_prompt = 'Would you like to provide some additional entropy from the keyboard?'
  28. interactive_input = False
  29. def _format(self):
  30. d = baseconv('b6d').frombytes(self.seed.data,pad='seed',tostr=True) + '\n'
  31. self.fmt_data = block_format(d,gw=5,cols=5)
  32. def _deformat(self):
  33. d = remove_whitespace(self.fmt_data)
  34. bc = baseconv('b6d')
  35. rmap = bc.seedlen_map_rev
  36. if not len(d) in rmap:
  37. die( 'SeedLengthError', '{!r}: invalid length for {} (must be one of {})'.format(
  38. len(d),
  39. self.desc,
  40. list(rmap) ))
  41. # truncate seed to correct length, discarding high bits
  42. seed_len = rmap[len(d)]
  43. seed_bytes = bc.tobytes( d, pad='seed' )[-seed_len:]
  44. if self.interactive_input and self.cfg.usr_randchars:
  45. from ..ui import keypress_confirm
  46. if keypress_confirm( self.cfg, self.user_entropy_prompt ):
  47. from ..crypto import Crypto
  48. seed_bytes = Crypto(self.cfg).add_user_random(
  49. rand_bytes = seed_bytes,
  50. desc = 'gathered from your die rolls' )
  51. self.desc += ' plus user-supplied entropy'
  52. self.seed = Seed( self.cfg, seed_bytes )
  53. self.ssdata.hexseed = seed_bytes.hex()
  54. self.check_usr_seed_len()
  55. return True
  56. def _get_data_from_user(self,desc):
  57. if not self.cfg.stdin_tty:
  58. from ..ui import get_data_from_user
  59. return get_data_from_user( self.cfg, desc )
  60. bc = baseconv('b6d')
  61. seed_bitlen = self._choose_seedlen([ n*8 for n in sorted(bc.seedlen_map) ])
  62. nDierolls = bc.seedlen_map[seed_bitlen // 8]
  63. message = """
  64. For a {sb}-bit seed you must roll the die {nd} times. After each die roll,
  65. enter the result on the keyboard as a digit. If you make an invalid entry,
  66. you'll be prompted to re-enter it.
  67. """
  68. msg('\n'+fmt(message.strip()).format(sb=seed_bitlen,nd=nDierolls)+'\n')
  69. CUR_HIDE = '\033[?25l'
  70. CUR_SHOW = '\033[?25h'
  71. cr = '\n' if self.cfg.test_suite else '\r'
  72. prompt_fs = f'\b\b\b {cr}Enter die roll #{{}}: {CUR_SHOW}'
  73. clear_line = '' if self.cfg.test_suite else '\r' + ' ' * 25
  74. invalid_msg = CUR_HIDE + cr + 'Invalid entry' + ' ' * 11
  75. from ..term import get_char
  76. def get_digit(n):
  77. p = prompt_fs
  78. while True:
  79. time.sleep(self.cfg.short_disp_timeout)
  80. ch = get_char(p.format(n),num_bytes=1)
  81. if ch in bc.digits:
  82. msg_r(CUR_HIDE + ' OK')
  83. return ch
  84. else:
  85. msg_r(invalid_msg)
  86. p = clear_line + prompt_fs
  87. dierolls,n = [],1
  88. while len(dierolls) < nDierolls:
  89. dierolls.append(get_digit(n))
  90. n += 1
  91. msg('Die rolls successfully entered' + CUR_SHOW)
  92. self.interactive_input = True
  93. return ''.join(dierolls)