From 7be9bcac796fb2115c47317d856db7d225ebaad7 Mon Sep 17 00:00:00 2001 From: matejcik Date: Tue, 10 Sep 2019 11:24:57 +0200 Subject: [PATCH] tests: extract more common functionality --- tests/device_tests/common.py | 61 ++++++++++ .../test_msg_resetdevice_shamir.py | 73 +++--------- .../test_msg_resetdevice_supershamir.py | 108 ++++-------------- tests/device_tests/test_msg_resetdevice_t2.py | 66 ++++------- .../test_shamir_reset_recovery.py | 75 +++--------- .../test_shamir_reset_recovery_groups.py | 108 ++++-------------- 6 files changed, 159 insertions(+), 332 deletions(-) diff --git a/tests/device_tests/common.py b/tests/device_tests/common.py index 02ca61a21..27be144cb 100644 --- a/tests/device_tests/common.py +++ b/tests/device_tests/common.py @@ -82,6 +82,16 @@ def generate_entropy(strength, internal_entropy, external_entropy): def recovery_enter_shares(debug, shares, groups=False): + """Perform the recovery flow for a set of Shamir shares. + + For use in an input flow function. + Example: + + def input_flow(): + yield # start recovery + client.debug.press_yes() + yield from recovery_enter_shares(client.debug, SOME_SHARES) + """ word_count = len(shares[0].split(" ")) # Homescreen - proceed to word number selection @@ -118,3 +128,54 @@ def recovery_enter_shares(debug, shares, groups=False): # or Homescreen - confirm success yield debug.press_yes() + + +def click_through(debug, screens, code=None): + """Click through N dialog screens. + + For use in an input flow function. + Example: + + def input_flow(): + # 1. Confirm reset + # 2. Backup your seed + # 3. Confirm warning + # 4. Shares info + yield from click_through(client.debug, screens=4, code=B.ResetDevice) + """ + for _ in range(screens): + received = yield + if code is not None: + assert received == code + debug.press_yes() + + +def read_and_confirm_mnemonic(debug, words): + """Read a given number of mnemonic words from Trezor T screen and correctly + answer confirmation questions. Return the full mnemonic. + + For use in an input flow function. + Example: + + def input_flow(): + yield from click_through(client.debug, screens=3) + + yield # confirm mnemonic entry + mnemonic = read_and_confirm_mnemonic(client.debug, words=20) + """ + mnemonic = [] + while True: + mnemonic.extend(debug.read_reset_word().split()) + if len(mnemonic) < words: + debug.swipe_down() + else: + # last page is confirmation + debug.press_yes() + break + + # check share + for _ in range(3): + index = debug.read_reset_word_pos() + debug.input(mnemonic[index]) + + return " ".join(mnemonic) diff --git a/tests/device_tests/test_msg_resetdevice_shamir.py b/tests/device_tests/test_msg_resetdevice_shamir.py index 573c5077c..373433561 100644 --- a/tests/device_tests/test_msg_resetdevice_shamir.py +++ b/tests/device_tests/test_msg_resetdevice_shamir.py @@ -8,13 +8,13 @@ from shamir_mnemonic import MnemonicError from trezorlib import device, messages as proto from trezorlib.messages import ButtonRequestType as B, ResetDeviceBackupType -from .common import TrezorTest, generate_entropy +from .common import click_through, generate_entropy, read_and_confirm_mnemonic EXTERNAL_ENTROPY = b"zlutoucky kun upel divoke ody" * 2 @pytest.mark.skip_t1 -class TestMsgResetDeviceT2(TrezorTest): +class TestMsgResetDeviceT2: # TODO: test with different options @pytest.mark.setup_client(uninitialized=True) def test_reset_device_shamir(self, client): @@ -23,68 +23,23 @@ class TestMsgResetDeviceT2(TrezorTest): all_mnemonics = [] def input_flow(): - # Confirm Reset - btn_code = yield - assert btn_code == B.ResetDevice - client.debug.press_yes() - - # Backup your seed - btn_code = yield - assert btn_code == B.ResetDevice - client.debug.press_yes() - - # Confirm warning - btn_code = yield - assert btn_code == B.ResetDevice - client.debug.press_yes() - - # shares info - btn_code = yield - assert btn_code == B.ResetDevice - client.debug.press_yes() - - # Set & Confirm number of shares - btn_code = yield - assert btn_code == B.ResetDevice - client.debug.press_yes() - - # threshold info - btn_code = yield - assert btn_code == B.ResetDevice - client.debug.press_yes() - - # Set & confirm threshold value - btn_code = yield - assert btn_code == B.ResetDevice - client.debug.press_yes() - - # Confirm show seeds - btn_code = yield - assert btn_code == B.ResetDevice - client.debug.press_yes() + # 1. Confirm Reset + # 2. Backup your seed + # 3. Confirm warning + # 4. shares info + # 5. Set & Confirm number of shares + # 6. threshold info + # 7. Set & confirm threshold value + # 8. Confirm show seeds + yield from click_through(client.debug, screens=8, code=B.ResetDevice) # show & confirm shares for h in range(5): - words = [] + # mnemonic phrases btn_code = yield assert btn_code == B.Other - - # mnemonic phrases - # 20 word over 6 pages for strength 128, 33 words over 9 pages for strength 256 - for i in range(6): - words.extend(client.debug.read_reset_word().split()) - if i < 5: - client.debug.swipe_down() - else: - # last page is confirmation - client.debug.press_yes() - - # check share - for _ in range(3): - index = client.debug.read_reset_word_pos() - client.debug.input(words[index]) - - all_mnemonics.extend([" ".join(words)]) + mnemonic = read_and_confirm_mnemonic(client.debug, words=20) + all_mnemonics.append(mnemonic) # Confirm continue to next share btn_code = yield diff --git a/tests/device_tests/test_msg_resetdevice_supershamir.py b/tests/device_tests/test_msg_resetdevice_supershamir.py index ea493aa75..481ff147b 100644 --- a/tests/device_tests/test_msg_resetdevice_supershamir.py +++ b/tests/device_tests/test_msg_resetdevice_supershamir.py @@ -6,95 +6,43 @@ import shamir_mnemonic as shamir from trezorlib import device, messages as proto from trezorlib.messages import ButtonRequestType as B, ResetDeviceBackupType -from .common import TrezorTest, generate_entropy +from .common import click_through, generate_entropy, read_and_confirm_mnemonic EXTERNAL_ENTROPY = b"zlutoucky kun upel divoke ody" * 2 @pytest.mark.skip_t1 -class TestMsgResetDeviceT2(TrezorTest): +class TestMsgResetDeviceT2: # TODO: test with different options @pytest.mark.setup_client(uninitialized=True) def test_reset_device_supershamir(self, client): strength = 128 + word_count = 20 member_threshold = 3 all_mnemonics = [] def input_flow(): - # Confirm Reset - btn_code = yield - assert btn_code == B.ResetDevice - client.debug.press_yes() - - # Backup your seed - btn_code = yield - assert btn_code == B.ResetDevice - client.debug.press_yes() - - # Confirm warning - btn_code = yield - assert btn_code == B.ResetDevice - client.debug.press_yes() - - # shares info - btn_code = yield - assert btn_code == B.ResetDevice - client.debug.press_yes() - - # Set & Confirm number of groups - btn_code = yield - assert btn_code == B.ResetDevice - client.debug.press_yes() - - # threshold info - btn_code = yield - assert btn_code == B.ResetDevice - client.debug.press_yes() - - # Set & confirm group threshold value - btn_code = yield - assert btn_code == B.ResetDevice - client.debug.press_yes() - - for _ in range(5): - # Set & Confirm number of share - btn_code = yield - assert btn_code == B.ResetDevice - client.debug.press_yes() - - # Set & confirm share threshold value - btn_code = yield - assert btn_code == B.ResetDevice - client.debug.press_yes() - - # Confirm show seeds - btn_code = yield - assert btn_code == B.ResetDevice - client.debug.press_yes() + # 1. Confirm Reset + # 2. Backup your seed + # 3. Confirm warning + # 4. shares info + # 5. Set & Confirm number of groups + # 6. threshold info + # 7. Set & confirm group threshold value + # 8-17: for each of 5 groups: + # 1. Set & Confirm number of shares + # 2. Set & confirm share threshold value + # 18. Confirm show seeds + yield from click_through(client.debug, screens=18, code=B.ResetDevice) # show & confirm shares for all groups for g in range(5): for h in range(5): - words = [] + # mnemonic phrases btn_code = yield assert btn_code == B.Other - - # mnemonic phrases - # 20 word over 6 pages for strength 128, 33 words over 9 pages for strength 256 - for i in range(6): - words.extend(client.debug.read_reset_word().split()) - if i < 5: - client.debug.swipe_down() - else: - # last page is confirmation - client.debug.press_yes() - - # check share - for _ in range(3): - index = client.debug.read_reset_word_pos() - client.debug.input(words[index]) - - all_mnemonics.extend([" ".join(words)]) + mnemonic = read_and_confirm_mnemonic(client.debug, words=word_count) + all_mnemonics.append(mnemonic) # Confirm continue to next share btn_code = yield @@ -119,25 +67,15 @@ class TestMsgResetDeviceT2(TrezorTest): proto.ButtonRequest(code=B.ResetDevice), proto.ButtonRequest(code=B.ResetDevice), proto.ButtonRequest(code=B.ResetDevice), - proto.ButtonRequest( - code=B.ResetDevice - ), # group #1 shares& thresholds + proto.ButtonRequest(code=B.ResetDevice), # group #1 counts proto.ButtonRequest(code=B.ResetDevice), - proto.ButtonRequest( - code=B.ResetDevice - ), # group #2 shares& thresholds + proto.ButtonRequest(code=B.ResetDevice), # group #2 counts proto.ButtonRequest(code=B.ResetDevice), - proto.ButtonRequest( - code=B.ResetDevice - ), # group #3 shares& thresholds + proto.ButtonRequest(code=B.ResetDevice), # group #3 counts proto.ButtonRequest(code=B.ResetDevice), - proto.ButtonRequest( - code=B.ResetDevice - ), # group #4 shares& thresholds + proto.ButtonRequest(code=B.ResetDevice), # group #4 counts proto.ButtonRequest(code=B.ResetDevice), - proto.ButtonRequest( - code=B.ResetDevice - ), # group #5 shares& thresholds + proto.ButtonRequest(code=B.ResetDevice), # group #5 counts proto.ButtonRequest(code=B.ResetDevice), proto.ButtonRequest(code=B.Other), # show seeds proto.ButtonRequest(code=B.Success), diff --git a/tests/device_tests/test_msg_resetdevice_t2.py b/tests/device_tests/test_msg_resetdevice_t2.py index beeb2b0b5..8e5f361b4 100644 --- a/tests/device_tests/test_msg_resetdevice_t2.py +++ b/tests/device_tests/test_msg_resetdevice_t2.py @@ -22,50 +22,34 @@ from mnemonic import Mnemonic from trezorlib import device, messages as proto from trezorlib.messages import ButtonRequestType as B -from .common import MNEMONIC12, TrezorTest, generate_entropy +from .common import ( + MNEMONIC12, + click_through, + generate_entropy, + read_and_confirm_mnemonic, +) EXTERNAL_ENTROPY = b"zlutoucky kun upel divoke ody" * 2 @pytest.mark.skip_t1 -class TestMsgResetDeviceT2(TrezorTest): +class TestMsgResetDeviceT2: @pytest.mark.setup_client(uninitialized=True) def test_reset_device(self, client): - words = [] + mnemonic = None strength = 128 def input_flow(): - # Confirm Reset - btn_code = yield - assert btn_code == B.ResetDevice - client.debug.press_yes() - - # Backup your seed - btn_code = yield - assert btn_code == B.ResetDevice - client.debug.press_yes() - - # Confirm warning - btn_code = yield - assert btn_code == B.ResetDevice - client.debug.press_yes() + nonlocal mnemonic + # 1. Confirm Reset + # 2. Backup your seed + # 3. Confirm warning + yield from click_through(client.debug, screens=3, code=B.ResetDevice) # mnemonic phrases btn_code = yield assert btn_code == B.ResetDevice - # 12 words, 3 pages - for i in range(3): - words.extend(client.debug.read_reset_word().split()) - if i < 2: - client.debug.swipe_down() - else: - # last page is confirmation - client.debug.press_yes() - - # check backup words - for _ in range(3): - index = client.debug.read_reset_word_pos() - client.debug.input(words[index]) + mnemonic = read_and_confirm_mnemonic(client.debug, words=12) # confirm recovery seed check btn_code = yield @@ -111,7 +95,7 @@ class TestMsgResetDeviceT2(TrezorTest): expected_mnemonic = Mnemonic("english").to_mnemonic(entropy) # Compare that device generated proper mnemonic for given entropies - assert " ".join(words) == expected_mnemonic + assert mnemonic == expected_mnemonic # Check if device is properly initialized resp = client.call_raw(proto.Initialize()) @@ -122,10 +106,12 @@ class TestMsgResetDeviceT2(TrezorTest): @pytest.mark.setup_client(uninitialized=True) def test_reset_device_pin(self, client): - words = [] + mnemonic = None strength = 128 def input_flow(): + nonlocal mnemonic + # Confirm Reset btn_code = yield assert btn_code == B.ResetDevice @@ -157,19 +143,7 @@ class TestMsgResetDeviceT2(TrezorTest): # mnemonic phrases btn_code = yield assert btn_code == B.ResetDevice - # 12 words, 3 pages - for i in range(3): - words.extend(client.debug.read_reset_word().split()) - if i < 2: - client.debug.swipe_down() - else: - # last page is confirmation - client.debug.press_yes() - - # check backup words - for _ in range(3): - index = client.debug.read_reset_word_pos() - client.debug.input(words[index]) + mnemonic = read_and_confirm_mnemonic(client.debug, words=12) # confirm recovery seed check btn_code = yield @@ -218,7 +192,7 @@ class TestMsgResetDeviceT2(TrezorTest): expected_mnemonic = Mnemonic("english").to_mnemonic(entropy) # Compare that device generated proper mnemonic for given entropies - assert " ".join(words) == expected_mnemonic + assert mnemonic == expected_mnemonic # Check if device is properly initialized resp = client.call_raw(proto.Initialize()) diff --git a/tests/device_tests/test_shamir_reset_recovery.py b/tests/device_tests/test_shamir_reset_recovery.py index 427c85bbe..3d5195a62 100644 --- a/tests/device_tests/test_shamir_reset_recovery.py +++ b/tests/device_tests/test_shamir_reset_recovery.py @@ -4,7 +4,7 @@ from trezorlib import btc, device, messages from trezorlib.messages import ButtonRequestType as B, ResetDeviceBackupType from trezorlib.tools import parse_path -from .common import recovery_enter_shares +from .common import click_through, read_and_confirm_mnemonic, recovery_enter_shares @pytest.mark.skip_t1 @@ -24,70 +24,28 @@ def test_reset_recovery(client): def reset(client, strength=128): all_mnemonics = [] + # per SLIP-39: strength in bits, rounded up to nearest multiple of 10, plus 70 bits + # of metadata, split into 10-bit words + word_count = ((strength + 9) // 10) + 7 def input_flow(): - # Confirm Reset - btn_code = yield - assert btn_code == B.ResetDevice - client.debug.press_yes() - - # Backup your seed - btn_code = yield - assert btn_code == B.ResetDevice - client.debug.press_yes() - - # Confirm warning - btn_code = yield - assert btn_code == B.ResetDevice - client.debug.press_yes() - - # shares info - btn_code = yield - assert btn_code == B.ResetDevice - client.debug.press_yes() - - # Set & Confirm number of shares - btn_code = yield - assert btn_code == B.ResetDevice - client.debug.press_yes() - - # threshold info - btn_code = yield - assert btn_code == B.ResetDevice - client.debug.press_yes() - - # Set & confirm threshold value - btn_code = yield - assert btn_code == B.ResetDevice - client.debug.press_yes() - - # Confirm show seeds - btn_code = yield - assert btn_code == B.ResetDevice - client.debug.press_yes() + # 1. Confirm Reset + # 2. Backup your seed + # 3. Confirm warning + # 4. shares info + # 5. Set & Confirm number of shares + # 6. threshold info + # 7. Set & confirm threshold value + # 8. Confirm show seeds + yield from click_through(client.debug, screens=8, code=B.ResetDevice) # show & confirm shares for h in range(5): - words = [] + # mnemonic phrases btn_code = yield assert btn_code == B.Other - - # mnemonic phrases - # 20 word over 6 pages for strength 128, 33 words over 9 pages for strength 256 - for i in range(6): - words.extend(client.debug.read_reset_word().split()) - if i < 5: - client.debug.swipe_down() - else: - # last page is confirmation - client.debug.press_yes() - - # check share - for _ in range(3): - index = client.debug.read_reset_word_pos() - client.debug.input(words[index]) - - all_mnemonics.extend([" ".join(words)]) + mnemonic = read_and_confirm_mnemonic(client.debug, words=word_count) + all_mnemonics.append(mnemonic) # Confirm continue to next share btn_code = yield @@ -139,6 +97,7 @@ def reset(client, strength=128): language="english", backup_type=ResetDeviceBackupType.Slip39_Single_Group, ) + client.set_input_flow(None) # Check if device is properly initialized diff --git a/tests/device_tests/test_shamir_reset_recovery_groups.py b/tests/device_tests/test_shamir_reset_recovery_groups.py index aab1c9766..9387b6d00 100644 --- a/tests/device_tests/test_shamir_reset_recovery_groups.py +++ b/tests/device_tests/test_shamir_reset_recovery_groups.py @@ -4,7 +4,7 @@ from trezorlib import btc, device, messages from trezorlib.messages import ButtonRequestType as B, ResetDeviceBackupType from trezorlib.tools import parse_path -from .common import recovery_enter_shares +from .common import click_through, read_and_confirm_mnemonic, recovery_enter_shares @pytest.mark.skip_t1 @@ -32,82 +32,32 @@ def test_reset_recovery(client): def reset(client, strength=128): all_mnemonics = [] + # per SLIP-39: strength in bits, rounded up to nearest multiple of 10, plus 70 bits + # of metadata, split into 10-bit words + word_count = ((strength + 9) // 10) + 7 def input_flow(): - # Confirm Reset - btn_code = yield - assert btn_code == B.ResetDevice - client.debug.press_yes() - - # Backup your seed - btn_code = yield - assert btn_code == B.ResetDevice - client.debug.press_yes() - - # Confirm warning - btn_code = yield - assert btn_code == B.ResetDevice - client.debug.press_yes() - - # shares info - btn_code = yield - assert btn_code == B.ResetDevice - client.debug.press_yes() - - # Set & Confirm number of groups - btn_code = yield - assert btn_code == B.ResetDevice - client.debug.press_yes() - - # threshold info - btn_code = yield - assert btn_code == B.ResetDevice - client.debug.press_yes() - - # Set & confirm group threshold value - btn_code = yield - assert btn_code == B.ResetDevice - client.debug.press_yes() - - for _ in range(5): - # Set & Confirm number of share - btn_code = yield - assert btn_code == B.ResetDevice - client.debug.press_yes() - - # Set & confirm share threshold value - btn_code = yield - assert btn_code == B.ResetDevice - client.debug.press_yes() - - # Confirm show seeds - btn_code = yield - assert btn_code == B.ResetDevice - client.debug.press_yes() + # 1. Confirm Reset + # 2. Backup your seed + # 3. Confirm warning + # 4. shares info + # 5. Set & Confirm number of groups + # 6. threshold info + # 7. Set & confirm group threshold value + # 8-17: for each of 5 groups: + # 1. Set & Confirm number of shares + # 2. Set & confirm share threshold value + # 18. Confirm show seeds + yield from click_through(client.debug, screens=18, code=B.ResetDevice) # show & confirm shares for all groups for g in range(5): for h in range(5): - words = [] + # mnemonic phrases btn_code = yield assert btn_code == B.Other - - # mnemonic phrases - # 20 word over 6 pages for strength 128, 33 words over 9 pages for strength 256 - for i in range(6): - words.extend(client.debug.read_reset_word().split()) - if i < 5: - client.debug.swipe_down() - else: - # last page is confirmation - client.debug.press_yes() - - # check share - for _ in range(3): - index = client.debug.read_reset_word_pos() - client.debug.input(words[index]) - - all_mnemonics.extend([" ".join(words)]) + mnemonic = read_and_confirm_mnemonic(client.debug, words=word_count) + all_mnemonics.append(mnemonic) # Confirm continue to next share btn_code = yield @@ -131,25 +81,15 @@ def reset(client, strength=128): messages.ButtonRequest(code=B.ResetDevice), messages.ButtonRequest(code=B.ResetDevice), messages.ButtonRequest(code=B.ResetDevice), - messages.ButtonRequest( - code=B.ResetDevice - ), # group #1 shares& thresholds + messages.ButtonRequest(code=B.ResetDevice), # group #1 counts messages.ButtonRequest(code=B.ResetDevice), - messages.ButtonRequest( - code=B.ResetDevice - ), # group #2 shares& thresholds + messages.ButtonRequest(code=B.ResetDevice), # group #2 counts messages.ButtonRequest(code=B.ResetDevice), - messages.ButtonRequest( - code=B.ResetDevice - ), # group #3 shares& thresholds + messages.ButtonRequest(code=B.ResetDevice), # group #3 counts messages.ButtonRequest(code=B.ResetDevice), - messages.ButtonRequest( - code=B.ResetDevice - ), # group #4 shares& thresholds + messages.ButtonRequest(code=B.ResetDevice), # group #4 counts messages.ButtonRequest(code=B.ResetDevice), - messages.ButtonRequest( - code=B.ResetDevice - ), # group #5 shares& thresholds + messages.ButtonRequest(code=B.ResetDevice), # group #5 counts messages.ButtonRequest(code=B.ResetDevice), messages.ButtonRequest(code=B.Other), # show seeds messages.ButtonRequest(code=B.Success),