ts_input.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. #!/usr/bin/env python3
  2. #
  3. # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
  4. # Copyright (C)2013-2020 The MMGen Project <mmgen@tuta.io>
  5. #
  6. # Project source code repository: https://github.com/mmgen/mmgen
  7. # Licensed according to the terms of GPL Version 3. See LICENSE for details.
  8. """
  9. ts_input.py: user input tests for the MMGen test.py test suite
  10. """
  11. from ..include.common import *
  12. from .ts_base import *
  13. from .input import *
  14. from mmgen.seed import SeedSource
  15. class TestSuiteInput(TestSuiteBase):
  16. 'user input'
  17. networks = ('btc',)
  18. tmpdir_nums = []
  19. cmd_group = (
  20. ('password_entry_noecho', (1,"utf8 password entry", [])),
  21. ('password_entry_echo', (1,"utf8 password entry (echoed)", [])),
  22. ('mnemonic_entry_mmgen', (1,"stealth mnemonic entry (mmgen)", [])),
  23. ('mnemonic_entry_mmgen_minimal', (1,"stealth mnemonic entry (mmgen - minimal entry mode)", [])),
  24. ('mnemonic_entry_bip39', (1,"stealth mnemonic entry (bip39)", [])),
  25. ('mnemonic_entry_bip39_short', (1,"stealth mnemonic entry (bip39 - short entry mode)", [])),
  26. ('mn2hex_interactive_mmgen', (1,"mn2hex_interactive (mmgen)", [])),
  27. ('mn2hex_interactive_mmgen_fixed',(1,"mn2hex_interactive (mmgen - fixed (10-letter) entry mode)", [])),
  28. ('mn2hex_interactive_bip39', (1,"mn2hex_interactive (bip39)", [])),
  29. ('mn2hex_interactive_bip39_short',(1,"mn2hex_interactive (bip39 - short entry mode (+pad entry))", [])),
  30. ('mn2hex_interactive_bip39_fixed',(1,"mn2hex_interactive (bip39 - fixed (4-letter) entry mode)", [])),
  31. ('mn2hex_interactive_xmr', (1,"mn2hex_interactive (xmrseed)", [])),
  32. ('mn2hex_interactive_xmr_short', (1,"mn2hex_interactive (xmrseed - short entry mode)", [])),
  33. ('dieroll_entry', (1,"dieroll entry (base6d)", [])),
  34. ('dieroll_entry_usrrand', (1,"dieroll entry (base6d) with added user entropy", [])),
  35. )
  36. def password_entry(self,prompt,cmd_args):
  37. t = self.spawn('test/misc/password_entry.py',cmd_args,cmd_dir='.')
  38. pw = 'abc-α'
  39. t.expect(prompt,pw)
  40. ret = t.expect_getend('Entered: ')
  41. assert ret == pw,'Password mismatch! {} != {}'.format(ret,pw)
  42. return t
  43. def password_entry_noecho(self):
  44. if self.skip_for_win():
  45. m = "getpass() doesn't work with pexpect.popen_spawn!\n"
  46. m += 'Perform the following test by hand with non-ASCII password abc-α:\n'
  47. m += ' test/misc/password_entry.py'
  48. return ('skip_warn',m)
  49. return self.password_entry('Enter passphrase: ',[])
  50. def password_entry_echo(self):
  51. if self.skip_for_win():
  52. m = "getpass() doesn't work with pexpect.popen_spawn!\n"
  53. m += 'Perform the following test by hand with non-ASCII password abc-α:\n'
  54. m += ' test/misc/password_entry.py --echo-passphrase'
  55. return ('skip_warn',m)
  56. return self.password_entry('Enter passphrase (echoed): ',['--echo-passphrase'])
  57. def _mn2hex(self,fmt,entry_mode='full',mn=None,pad_entry=False):
  58. mn = mn or sample_mn[fmt]['mn'].split()
  59. t = self.spawn('mmgen-tool',['mn2hex_interactive','fmt='+fmt,'mn_len=12','print_mn=1'])
  60. from mmgen.mn_entry import mn_entry
  61. mne = mn_entry(fmt,entry_mode)
  62. t.expect('Entry mode: ',str(mne.entry_modes.index(entry_mode)+1))
  63. t.expect('Using (.+) entry mode',regex=True)
  64. mode = t.p.match.group(1).lower()
  65. assert mode == mne.em.name.lower(), '{} != {}'.format(mode,mne.em.name.lower())
  66. stealth_mnemonic_entry(t,mne,mn,entry_mode=entry_mode,pad_entry=pad_entry)
  67. t.expect(sample_mn[fmt]['hex'])
  68. t.read()
  69. return t
  70. def _user_seed_entry(self,fmt,usr_rand=False,out_fmt=None,entry_mode='full',mn=None):
  71. wcls = SeedSource.fmt_code_to_type(fmt)
  72. wf = os.path.join(ref_dir,'FE3C6545.{}'.format(wcls.ext))
  73. if wcls.wclass == 'mnemonic':
  74. mn = mn or read_from_file(wf).strip().split()
  75. elif wcls.wclass == 'dieroll':
  76. mn = mn or list(read_from_file(wf).strip().translate(dict((ord(ws),None) for ws in '\t\n ')))
  77. for idx,val in ((5,'x'),(18,'0'),(30,'7'),(44,'9')):
  78. mn.insert(idx,val)
  79. t = self.spawn('mmgen-walletconv',['-r10','-S','-i',fmt,'-o',out_fmt or fmt])
  80. t.expect('{} type: {}'.format(capfirst(wcls.wclass),wcls.mn_type))
  81. t.expect(wcls.choose_seedlen_prompt,'1')
  82. t.expect('(Y/n): ','y')
  83. if wcls.wclass == 'mnemonic':
  84. t.expect('Entry mode: ','6')
  85. t.expect('invalid')
  86. from mmgen.mn_entry import mn_entry
  87. mne = mn_entry(fmt,entry_mode)
  88. t.expect('Entry mode: ',str(mne.entry_modes.index(entry_mode)+1))
  89. t.expect('Using (.+) entry mode',regex=True)
  90. mode = t.p.match.group(1).lower()
  91. assert mode == mne.em.name.lower(), '{} != {}'.format(mode,mne.em.name.lower())
  92. stealth_mnemonic_entry(t,mne,mn,entry_mode=entry_mode)
  93. elif wcls.wclass == 'dieroll':
  94. user_dieroll_entry(t,mn)
  95. if usr_rand:
  96. t.expect(wcls.user_entropy_prompt,'y')
  97. t.usr_rand(10)
  98. else:
  99. t.expect(wcls.user_entropy_prompt,'n')
  100. if not usr_rand:
  101. sid_chk = 'FE3C6545'
  102. sid = t.expect_getend('Valid {} for Seed ID '.format(wcls.desc))[:8]
  103. assert sid == sid_chk,'Seed ID mismatch! {} != {}'.format(sid,sid_chk)
  104. t.expect('to confirm: ','YES\n')
  105. t.read()
  106. return t
  107. def mnemonic_entry_mmgen_minimal(self):
  108. from mmgen.mn_entry import mn_entry
  109. # erase_chars: '\b\x7f'
  110. m = mn_entry('mmgen','minimal')
  111. np = 2
  112. mn = (
  113. 'z',
  114. 'aa',
  115. '1d2ud',
  116. 'fo{}ot{}#'.format('1' * np, '2' * (m.em.pad_max - np)), # substring of 'football'
  117. 'des1p)%erate\n', # substring of 'desperately'
  118. '#t!(ie',
  119. '!)sto8o',
  120. 'the123m8!%s',
  121. '349t(5)rip',
  122. 'di\b\bdesce',
  123. 'cea',
  124. 'bu\x7f\x7fsuic',
  125. 'app\bpl',
  126. 'wd',
  127. 'busy')
  128. return self._user_seed_entry('words',entry_mode='minimal',mn=mn)
  129. def mnemonic_entry_mmgen(self): return self._user_seed_entry('words',entry_mode='full')
  130. def mnemonic_entry_bip39(self): return self._user_seed_entry('bip39',entry_mode='full')
  131. def mnemonic_entry_bip39_short(self): return self._user_seed_entry('bip39',entry_mode='short')
  132. def mn2hex_interactive_mmgen(self): return self._mn2hex('mmgen',entry_mode='full')
  133. def mn2hex_interactive_mmgen_fixed(self): return self._mn2hex('mmgen',entry_mode='fixed')
  134. def mn2hex_interactive_bip39(self): return self._mn2hex('bip39',entry_mode='full')
  135. def mn2hex_interactive_bip39_short(self): return self._mn2hex('bip39',entry_mode='short',pad_entry=True)
  136. def mn2hex_interactive_bip39_fixed(self): return self._mn2hex('bip39',entry_mode='fixed')
  137. def mn2hex_interactive_xmr(self): return self._mn2hex('xmrseed',entry_mode='full')
  138. def mn2hex_interactive_xmr_short(self): return self._mn2hex('xmrseed',entry_mode='short')
  139. def dieroll_entry(self): return self._user_seed_entry('dieroll')
  140. def dieroll_entry_usrrand(self): return self._user_seed_entry('dieroll',usr_rand=True,out_fmt='bip39')