dieroll.py 3.3 KB

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