1313import PySimpleGUI as sg
1414
1515from ..api import Account , create , group_parser , random_secret , cryptopaths_parser , paper_wallet_available
16- from ..recovery import recover , recover_bip39 , produce_bip39
16+ from ..recovery import recover , recover_bip39 , produce_bip39 , analyze_entropy
1717from ..util import log_level , log_cfg , ordinal , chunker , hue_shift
1818from ..layout import write_pdfs , printers_available
1919from ..defaults import (
@@ -455,21 +455,11 @@ def update_seed_data( event, window, values ):
455455 Stores the last known state of the -SD-... radio buttons, and saves/restores the user data being
456456 supplied on for BIP/SLIP/FIX. If event indicates one of our radio-buttons is re-selectedd, then
457457 also re-generate the random data. If a system event occurs (eg. after a Controls change),
458- restores our last-known radio button and data.
458+ restores our last-known radio button and data. Since we cannot know if/when our main window is
459+ going to disappear and be replaced, we constantly save the current state.
459460
460461 """
461- changed = False
462- if not event .startswith ( '-' ) and update_seed_data .src and window [update_seed_data .src ].visible :
463- # Some system event; restore where we left off (if known, and if the remembered control is visible).
464- data , pswd = update_seed_data .was .get ( update_seed_data .src , ('' ,'' ) )
465- seed = None
466- values [update_seed_data .src ] = True
467- window [update_seed_data .src ].update ( True )
468- update_seed_data .src = None
469- else :
470- data , pswd = values ['-SD-DATA-' ], values ['-SD-PASS-' ]
471- seed = window ['-SD-SEED-' ].get ()
472- for src in [
462+ SD_CONTROLS = [
473463 '-SD-128-RND-' ,
474464 '-SD-256-RND-' ,
475465 '-SD-512-RND-' ,
@@ -479,15 +469,34 @@ def update_seed_data( event, window, values ):
479469 '-SD-BIP-' , # Recover 128- to 256-bit Mnemonic Seed Entropy
480470 '-SD-BIP-SEED-' , # Recover 512-bit Generated Seed w/ passphrase
481471 '-SD-SLIP-' ,
482- ]:
483- # See what we got for -SD-DATA-, for this -SD-... radio button selection
484- if values [src ] and update_seed_data .src != src :
485- # If selected radio-button for Seed Data source changed, save last source's working data
486- # and restore what was, last time we were working on this source. If the triggering
487- # event is not one of ours (eg. __TIMEOUT__), then don't overwrite memory.
488- if update_seed_data .src and event .startswith ( '-' ):
489- #log.warning( f"Switching Seed Source w/ event {event} to {src}: saving {update_seed_data.src}: {data!r:.10}..., {pswd!r}" )
490- update_seed_data .was [update_seed_data .src ] = data , pswd
472+ ]
473+ changed = False
474+ # Some system event (eg. __TIMEOUT__ immediately after a new main window is created, or due to
475+ # scheduled callback); restore where we left off if known, and if the remembered control is
476+ # visible. This allows continuation between major screen changes w/ full controls regeneration.
477+ # As long as the new screen contains the same visible controls, we'll continue editing our Seed
478+ # Data wherever we left off. If the new screen has different controls, we'll get whatever is
479+ # there, instead.
480+ if not event .startswith ( '-' ) and update_seed_data .src and window [update_seed_data .src ].visible :
481+ log .warning ( f"Restoring Seed Source w/ event { event } from saved { update_seed_data .src } "
482+ f" data/password{ ' (none saved)' if update_seed_data .src not in update_seed_data .was else '' } " )
483+ data , pswd = update_seed_data .was .get ( update_seed_data .src , ('' ,'' ) )
484+ seed = None
485+ for src in SD_CONTROLS :
486+ values [src ] = False
487+ values [update_seed_data .src ] = True
488+ window [update_seed_data .src ].update ( True )
489+ update_seed_data .src = None # and force controls' visibility update
490+ else :
491+ data , pswd = values ['-SD-DATA-' ], values ['-SD-PASS-' ]
492+ seed = window ['-SD-SEED-' ].get ()
493+ # Now that we've recovered which Seed Data control is in play, See what we got for -SD-DATA-,
494+ # and update other controls' visibility for this -SD-... radio button selection.
495+ for src in SD_CONTROLS :
496+ if values [src ] and ( update_seed_data .src != src ):
497+ # If selected radio-button for Seed Data source changed, restore what data/pswd was
498+ # there, last time we were working on this source.
499+ log .warning ( f"Seed Data source changed from { update_seed_data .src !r} to { src !r} due to event: { event !r} " )
491500 changed = True
492501 update_seed_data .src = src
493502 data , pswd = update_seed_data .was .get ( src , ('' ,'' ) )
@@ -530,6 +539,7 @@ def update_seed_data( event, window, values ):
530539 window ['-SD-PASS-F-' ].update ( visible = False )
531540 elif event == update_seed_data .src == src :
532541 # Same radio-button re-selected; just force an update (eg. re-generate random)
542+ log .info ( f"Seed Data update forced due to event: { event !r} " )
533543 changed = True
534544 if not seed :
535545 seed = '-' * ( BITS_DEFAULT // 4 )
@@ -585,7 +595,7 @@ def update_seed_data( event, window, values ):
585595 seed = random_secret ( bits // 8 )
586596
587597 # Compute any newly computed/recovered binary Seed Data bytes as hex. Must be 128-, 256- or
588- # 512-bit hex data.
598+ # 512-bit hex data. Do a final comparison against current -SD-SEED- to detect changes.
589599 try :
590600 if type (seed ) is not str :
591601 seed = codecs .encode ( seed , 'hex_codec' ).decode ( 'ascii' )
@@ -600,11 +610,30 @@ def update_seed_data( event, window, values ):
600610 if not bits :
601611 bits = BITS_DEFAULT
602612 seed = '-' * (bits // 4 )
603-
613+ if window ['-SD-SEED-' ].get () != seed :
614+ changed = True
615+
616+ # Analyze the seed for Signal harmonic or Shannon entropy failures, if we're in a __TIMEOUT__
617+ # (between keystrokes or after a major controls change). Otherwise, if the seed's changed,
618+ # request a __TIMEOUT__; when it, perform the entropy analysis.
619+ values ['-SD-SIG-' ] = ''
620+ if status is None and event == '__TIMEOUT__' :
621+ seed_bytes = codecs .decode ( seed , 'hex_codec' )
622+ analysis = analyze_entropy ( seed_bytes , what = f"{ len (seed_bytes )* 8 } -bit Seed Source" , show_details = False )
623+ if analysis :
624+ values ['-SD-SIG-' ] = analysis
625+ status = analysis .split ( '\n ' )[0 ]
626+ elif changed :
627+ log .info ( f"Seed Data requests __TIMEOUT__ w/ current source: { update_seed_data .src !r} " )
628+ values ['__TIMEOUT__' ] = .5
629+
604630 # Since a window[...].update() doesn't show up to a .get() 'til the next cycle of the display,
605631 # we'll communicate updates to successive functions via values.
606632 values ['-SD-SEED-' ] = seed
607633 window ['-SD-SEED-' ].update ( seed )
634+
635+ # Finally, remember what data/pswd we were working on, in case we get a major controls change.
636+ update_seed_data .was [update_seed_data .src ] = values ['-SD-DATA-' ], values ['-SD-PASS-' ]
608637 return status
609638
610639update_seed_data .src = None # noqa: E305
@@ -902,9 +931,10 @@ def app(
902931 master_secret = None # default to produce randomly
903932 details = None # The SLIP-39 details produced from groups; make None to force SLIP-39 Mnemonic update
904933 cryptopaths = None
905- timeout = 0 # First time thru; refresh immediately
934+ timeout = 0 # First time thru; refresh immediately; functions req. refresh may adjust via values['__TIMEOUT__']
906935 instructions = '' # The last instructions .txt payload found
907936 instructions_kwds = dict () # .. and its colors
937+ values = dict ()
908938 while event not in events_termination :
909939 # A Controls layout selection, eg. '-LO-2-'; closes and re-generates window layout
910940 if event and event .startswith ( '-LO-' ):
@@ -951,12 +981,15 @@ def app(
951981 no_titlebar = no_titlebar ,
952982 scaling = scaling ,
953983 )
954- timeout = 0 # First time through w/ new window, refresh immediately
984+ values [ '__TIMEOUT__' ] = 0 # First time through w/ new window, refresh immediately
955985
956- # Block (except for first loop) and obtain current event and input values. Until we get a
957- # new event, retain the current status
986+ # Block (except for first loop, or if someone requested a __TIMEOUT__) and obtain current
987+ # event and input values. Until we get a new event, retain the current status
988+ timeout = values .get ( '__TIMEOUT__' , None ) # Subsequently, default; block indefinitely (functions may adjust...)
989+ if timeout :
990+ logging .debug ( f"A __TIMEOUT__ was requested: { timeout !r} " )
958991 event , values = window .read ( timeout = timeout )
959- timeout = None # Subsequently, default; block indefinitely
992+ assert '__TIMEOUT__' not in values
960993 logging .debug ( f"{ event } , { values } " )
961994 if not values or event in events_termination or event in events_ignored :
962995 continue
0 commit comments