diff --git a/shared/xor_seed.py b/shared/xor_seed.py index 4f1d949b..a89bde11 100644 --- a/shared/xor_seed.py +++ b/shared/xor_seed.py @@ -120,13 +120,15 @@ Quiz Passed!\n You have confirmed the details of the new split.''') # list of seed phrases -# stores encoded secret bytes (not word lists) +# - stores encoded secret bytes (not word lists) import_xor_parts = [] async def xor_all_done(data): # So we have another part, might be done or not. global import_xor_parts + chk_words = None + if data is None: # special case, needs something already in import_xor_parts target_words = len_to_numwords(len(import_xor_parts[0])) @@ -144,7 +146,7 @@ async def xor_all_done(data): if num_parts >= 2: chk_words = bip39.b2a_words(seed).split(' ') chk_word = chk_words[-1] - msg += "If you stop now, the %dth word of the XOR-combined seed phrase\nwill be:\n\n" % target_words + msg += "If you stop now, the %dth word of the XOR-combined seed phrase will be:\n\n" % target_words msg += "%d: %s\n\n" % (target_words, chk_word) if all((not x) for x in seed): @@ -236,12 +238,12 @@ async def xor_restore_start(*a): # shown on import menu when no seed of any kind yet # - or operational system ch = await ux_show_story('''\ -To import a seed split using XOR, you must import all the parts. -It does not matter the order (A/B/C or C/A/B) and the Coldcard -cannot determine when you have all the parts. You may stop at -any time and you will have a valid wallet. Combined seed parts -have to be equal length. No way to combine seed parts of different -length. Press %s for 24 words XOR, press (1) for 12 words XOR, +To import a seed split using XOR, you must import all the parts. \ +It does not matter the order (A/B/C or C/A/B) and the Coldcard \ +cannot determine when you have all the parts. You may stop at \ +any time and you will have a valid wallet. Combined seed parts \ +have to be equal length.\n +Press %s for 24 words XOR, press (1) for 12 words XOR, \ or press (2) for 18 words XOR.''' % OK, escape="12") if ch == 'x': return @@ -251,8 +253,6 @@ or press (2) for 18 words XOR.''' % OK, escape="12") elif ch == "2": desired_num_words = 18 - curr_num_words = settings.get('words', desired_num_words) - global import_xor_parts import_xor_parts.clear() @@ -264,15 +264,20 @@ or press (2) for 18 words XOR.''' % OK, escape="12") msg = ("Since you have a seed already on this Coldcard, the reconstructed XOR seed will be " "temporary and not saved. Wipe the seed first if you want to commit the new value " "into the secure element.") - if curr_num_words == desired_num_words: + + curr_num_words = settings.get('words', desired_num_words) + if (curr_num_words == desired_num_words) and not pa.hobbled_mode: escape += "1" - msg += ("\nPress (1) to include this Coldcard's seed words into the XOR seed set, " + msg += ("\n\nPress (1) to include this Coldcard's seed words into the XOR seed set, " "or %s to continue without." % OK) ch = await ux_show_story(msg, escape=escape) - if ch == 'x': return + if ch == 'x': + return + if ch == '1': + assert not pa.hobbled_mode dis.fullscreen("Wait...") with SensitiveValues(enforce_delta=True) as sv: if sv.mode == 'words': diff --git a/testing/test_hobble.py b/testing/test_hobble.py index 9d8e5fa4..9e83f82b 100644 --- a/testing/test_hobble.py +++ b/testing/test_hobble.py @@ -273,7 +273,8 @@ def test_h_tempseeds(mode, set_hobble, pick_menu_item, cap_menu, settings_set, i m = cap_menu() assert 'Generate Words' not in m - assert all(i.startswith("Import ") or i.endswith(' Backup') for i in m), m + assert all((i.startswith("Import ") or i.endswith(' Backup') or i == 'Restore Seed XOR') + for i in m), m words, expect_xfp = WORDLISTS[12] @@ -451,5 +452,29 @@ def test_h_qrscan(en_okeys, set_hobble, scan_a_qr, need_keypress, press_cancel, else: scr = cap_screen() # stays in scanning mode assert 'KT Blocked' in scr + +def test_h_seedxor(set_hobble, need_keypress, press_cancel, cap_screen, only_q1, + cap_story, press_select, pick_menu_item, settings_set): + # can start import via seed XOR, but cannot include master seed phrase + # as part of it. + + settings_set('seedvault', True) + settings_set('seeds', []) + set_hobble(True, {'okeys'}) + + pick_menu_item("Advanced/Tools") + pick_menu_item('Temporary Seed') + pick_menu_item('Restore Seed XOR') + + title, story = cap_story() + assert 'A/B/C' in story + press_select() # select 24 words + + title, story = cap_story() + assert 'Since you have' in story + assert "include this Coldcard's seed" not in story # WEAK: fragile if UX changes + + press_cancel() + # EOF