From 656bb6958761f7c077f053370ae3a28eaec17a65 Mon Sep 17 00:00:00 2001 From: The MMGen Project Date: Sun, 15 Mar 2020 19:45:32 +0000 Subject: [PATCH] terminal-related fixes and cleanups --- mmgen/crypto.py | 27 ++++++++++++++++++--------- mmgen/globalvars.py | 1 + mmgen/tw.py | 6 ++++-- mmgen/tx.py | 19 ++++++++++++++----- mmgen/util.py | 12 +----------- test/misc/term.py | 44 ++++++++++++++++++++++++++------------------ 6 files changed, 64 insertions(+), 45 deletions(-) diff --git a/mmgen/crypto.py b/mmgen/crypto.py index 9591d414..36939b92 100755 --- a/mmgen/crypto.py +++ b/mmgen/crypto.py @@ -152,8 +152,8 @@ def make_key(passwd,salt,hash_preset,desc='encryption key',from_what='passphrase dmsg('Key: {}'.format(key.hex())) return key -def _get_random_data_from_user(uchars,desc): - m = 'Enter {r} random symbols' if opt.quiet else crmsg['usr_rand_notice'] +def _get_random_data_from_user(uchars,desc,test_suite=False): + m = 'Enter {r} random symbols' if opt.quiet or test_suite else crmsg['usr_rand_notice'] msg(m.format(r=uchars,d=desc)) prompt = 'You may begin typing. {} symbols left: ' @@ -165,15 +165,24 @@ def _get_random_data_from_user(uchars,desc): key_data += get_char_raw('\r'+prompt.format(uchars-i)) time_data.append(time.time()) - if opt.quiet: msg_r('\r') - else: msg_r("\rThank you. That's enough.{}\n\n".format(' '*18)) + msg_r('\r' if opt.quiet else "\rThank you. That's enough.{}\n\n".format(' '*18)) - fmt_time_data = list(map('{:.22f}'.format,time_data)) - dmsg('\nUser input:\n{!r}\nKeystroke time values:\n{}\n'.format(key_data,'\n'.join(fmt_time_data))) - prompt = 'User random data successfully acquired. Press ENTER to continue' - prompt_and_get_char(prompt,'',enter_ok=True) + time_data = ['{:.22f}'.format(t).rstrip('0') for t in time_data] - return key_data.encode() + ''.join(fmt_time_data).encode() + avg_prec = sum(len(t.split('.')[1]) for t in time_data) // len(time_data) + if avg_prec < g.min_time_precision: + m = 'WARNING: Avg. time precision of only {} decimal points. User entropy quality is degraded!' + ymsg(m.format(avg_prec)) + + ret = key_data + '\n' + '\n'.join(time_data) + + if g.debug: + msg('USER ENTROPY (user input + keystroke timings):\n{}'.format(ret)) + + if not test_suite: + my_raw_input('User random data successfully acquired. Press ENTER to continue: ') + + return ret.encode() def get_random(length): return add_user_random(os.urandom(length),'OS random data') diff --git a/mmgen/globalvars.py b/mmgen/globalvars.py index 65e0b46f..634d572a 100755 --- a/mmgen/globalvars.py +++ b/mmgen/globalvars.py @@ -58,6 +58,7 @@ class g(object): http_timeout = 60 err_disp_timeout = 0.7 short_disp_timeout = 0.3 + min_time_precision = 18 # Variables - these might be altered at runtime: diff --git a/mmgen/tw.py b/mmgen/tw.py index 8fcff7ab..43a7e50a 100755 --- a/mmgen/tw.py +++ b/mmgen/tw.py @@ -344,8 +344,10 @@ watch-only wallet using '{}-addrimport' and then re-run this program. no_output,oneshot_msg = False,None while True: msg_r('' if no_output else '\n\n' if opt.no_blank else CUR_HOME+ERASE_ALL) - reply = get_char('' if no_output else self.format_for_display()+'\n'+(oneshot_msg or '')+prompt, - immed_chars=''.join(self.key_mappings.keys())) + reply = get_char( + '' if no_output else self.format_for_display()+'\n'+(oneshot_msg or '')+prompt, + immed_chars=''.join(self.key_mappings.keys()) + ) no_output = False oneshot_msg = '' if oneshot_msg else None # tristate, saves previous state if reply not in self.key_mappings: diff --git a/mmgen/tx.py b/mmgen/tx.py index d1b91d99..2cc0dfaf 100755 --- a/mmgen/tx.py +++ b/mmgen/tx.py @@ -1030,11 +1030,20 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam ask_tty=ask_tty, ask_write_default_yes=ask_write_default_yes) - def view_with_prompt(self,prompt=''): - prompt += ' (y)es, (N)o, pager (v)iew, (t)erse view' - reply = prompt_and_get_char(prompt,'YyNnVvTt',enter_ok=True) - if reply and reply in 'YyVvTt': - self.view(pager=reply in 'Vv',terse=reply in 'Tt') + def view_with_prompt(self,prompt='',pause=True): + prompt += ' (y)es, (N)o, pager (v)iew, (t)erse view: ' + from mmgen.term import get_char + ok_chars = 'YyNnVvTt' + while True: + reply = get_char(prompt,immed_chars=ok_chars).strip('\n\r') + msg('') + if reply == '' or reply in 'Nn': + break + elif reply in 'YyVvTt': + self.view(pager=reply in 'Vv',terse=reply in 'Tt',pause=pause) + break + else: + msg('Invalid reply') def view(self,pager=False,pause=True,terse=False): o = self.format_view(terse=terse) diff --git a/mmgen/util.py b/mmgen/util.py index 1a7a155f..f159d7ae 100755 --- a/mmgen/util.py +++ b/mmgen/util.py @@ -760,7 +760,7 @@ def keypress_confirm(prompt,default_yes=False,verbose=False,no_nl=False,complete from mmgen.term import get_char while True: - reply = get_char(p).strip('\n\r') + reply = get_char(p,immed_chars='yYnN').strip('\n\r') if not reply: msg_r(nl) return True if default_yes else False @@ -770,16 +770,6 @@ def keypress_confirm(prompt,default_yes=False,verbose=False,no_nl=False,complete else: msg_r('\nInvalid reply\n' if verbose else '\r') -def prompt_and_get_char(prompt,chars,enter_ok=False,verbose=False): - - from mmgen.term import get_char - while True: - reply = get_char('{}: '.format(prompt)).strip('\n\r') - if reply in chars or (enter_ok and not reply): - msg('') - return reply - msg_r('\nInvalid reply\n' if verbose else '\r') - def do_pager(text): pagers = ['less','more'] diff --git a/test/misc/term.py b/test/misc/term.py index 88a06f9f..5e21840b 100755 --- a/test/misc/term.py +++ b/test/misc/term.py @@ -67,22 +67,6 @@ def tt_my_raw_input(): reply = my_raw_input('\nEnter text: ') confirm('Did you enter the text {!r}?'.format(reply)) -def tt_prompt_and_get_char(): - cmsg('Testing prompt_and_get_char():') - m = 'Type some letters besides "x" or "z", then "x" or "z"' - reply = prompt_and_get_char(m,'xz') - confirm('Did you enter the letter {!r}?'.format(reply)) - -def tt_prompt_and_get_char_enter_ok(): - cmsg('Testing prompt_and_get_char() with blank choices and enter_ok=True:') - for m in ( - 'Type ENTER', - 'Type any letter followed by a pause, followed by ENTER', - ): - reply = prompt_and_get_char(m,'',enter_ok=True) - assert reply == '' - msg('OK') - def tt_get_char(raw=False,one_char=False,sleep=0,immed_chars=''): fname = ('get_char','get_char_raw')[raw] fs = fmt(""" @@ -130,6 +114,30 @@ def tt_get_char(raw=False,one_char=False,sleep=0,immed_chars=''): except KeyboardInterrupt: msg('\nDone') +def tt_urand(): + cmsg('Testing _get_random_data_from_user():') + from mmgen.crypto import _get_random_data_from_user + ret = _get_random_data_from_user(10,desc='data',test_suite=True).decode() + msg('USER ENTROPY (user input + keystroke timings):\n\n{}'.format(fmt(ret,' '))) + times = ret.splitlines()[1:] + avg_prec = sum(len(t.split('.')[1]) for t in times) // len(times) + if avg_prec < g.min_time_precision: + m = 'WARNING: Avg. time precision of only {} decimal points. User entropy quality is degraded!' + ymsg(m.format(avg_prec)) + else: + msg('Average time precision: {} decimal points - OK'.format(avg_prec)) + my_raw_input('Press ENTER to continue: ') + +def tt_txview(): + cmsg('Testing tx.view_with_prompt() (try each viewing option)') + from mmgen.tx import MMGenTX + fn = 'test/ref/0B8D5A[15.31789,14,tl=1320969600].rawtx' + tx = MMGenTX(fn,offline=True) + while True: + tx.view_with_prompt('View data for transaction?',pause=False) + if not keypress_confirm('Continue testing transaction view?',default_yes=True): + break + if g.platform == 'linux': import termios,atexit fd = sys.stdin.fileno() @@ -142,8 +150,8 @@ tt_get_terminal_size() tt_color() tt_license() tt_my_raw_input() -tt_prompt_and_get_char() -tt_prompt_and_get_char_enter_ok() +tt_urand() +tt_txview() tt_get_char(one_char=True) tt_get_char(one_char=True,sleep=1)