From 905a405cb33d177d48135ae67da6d761b8d18728 Mon Sep 17 00:00:00 2001 From: ciny Date: Mon, 4 Nov 2019 15:45:54 +0100 Subject: [PATCH] tests: click tests for basic and advanced slip39 --- tests/buttons.py | 10 +- tests/click_tests/reset.py | 71 +++++++ .../click_tests/test_reset_slip39_advanced.py | 193 ++++++++++++++++++ tests/click_tests/test_reset_slip39_basic.py | 174 ++++++++++++++++ 4 files changed, 446 insertions(+), 2 deletions(-) create mode 100644 tests/click_tests/reset.py create mode 100644 tests/click_tests/test_reset_slip39_advanced.py create mode 100644 tests/click_tests/test_reset_slip39_basic.py diff --git a/tests/buttons.py b/tests/buttons.py index fe8169644..e65371e16 100644 --- a/tests/buttons.py +++ b/tests/buttons.py @@ -21,8 +21,14 @@ INFO = (MID, BOTTOM) CONFIRM_WORD = (MID, TOP) -MINUS = (LEFT, grid(DISPLAY_HEIGHT, 5, 2)) -PLUS = (RIGHT, grid(DISPLAY_HEIGHT, 5, 2)) +RESET_MINUS = (LEFT, grid(DISPLAY_HEIGHT, 5, 1)) +RESET_PLUS = (RIGHT, grid(DISPLAY_HEIGHT, 5, 1)) + +RESET_WORD_CHECK = [ + (MID, grid(DISPLAY_HEIGHT, 6, 3)), + (MID, grid(DISPLAY_HEIGHT, 6, 4)), + (MID, grid(DISPLAY_HEIGHT, 6, 5)), +] BUTTON_LETTERS = ("ab", "cd", "ef", "ghij", "klm", "nopq", "rs", "tuv", "wxyz") diff --git a/tests/click_tests/reset.py b/tests/click_tests/reset.py new file mode 100644 index 000000000..75cbb97af --- /dev/null +++ b/tests/click_tests/reset.py @@ -0,0 +1,71 @@ +import shamir_mnemonic as shamir + +from trezorlib import messages + +from .. import buttons + + +def confirm_wait(debug, startswith): + layout = debug.wait_layout() + assert layout.text.startswith(startswith) + debug.click(buttons.OK, wait=True) + + +def confirm_read(debug, startswith): + layout = debug.read_layout() + assert layout.text.startswith(startswith) + debug.click(buttons.OK, wait=True) + + +def set_selection(debug, button, diff): + layout = debug.read_layout() + assert layout.text.startswith("Slip39NumInput") + for _ in range(diff): + debug.click(button, wait=False) + debug.click(buttons.OK, wait=True) + + +def read_words(debug, is_advanced=False): + words = [] + layout = debug.read_layout() + if is_advanced: + assert layout.text.startswith("Group") + else: + assert layout.text.startswith("Recovery share") + for i in range(6): + lines = debug.read_layout().lines + if i == 0: + words.append(lines[3].split()[1]) + words.append(lines[4].split()[1]) + debug.input(swipe=messages.DebugSwipeDirection.UP, wait=True) + elif i == 5: + words.append(lines[1].split()[1]) + words.append(lines[2].split()[1]) + else: + words.append(lines[1].split()[1]) + words.append(lines[2].split()[1]) + words.append(lines[3].split()[1]) + words.append(lines[4].split()[1]) + debug.input(swipe=messages.DebugSwipeDirection.UP, wait=True) + debug.press_yes() + + return words + + +def confirm_words(debug, words): + # confirm words + layout = debug.wait_layout() + layout.text.startswith("Check share") + for _ in range(3): + word_pos = int(debug.state().layout_lines[1].split()[2]) + button_pos = debug.state().layout_lines.index(words[word_pos - 1]) - 2 + debug.click(buttons.RESET_WORD_CHECK[button_pos], wait=True) + + +def validate_mnemonics(mnemonics, expected_ems): + # We expect these combinations to recreate the secret properly + # In case of click tests the mnemonics are always XofX so no need for combinations + ms = shamir.combine_mnemonics(mnemonics) + identifier, iteration_exponent, _, _, _ = shamir._decode_mnemonics(mnemonics) + ems = shamir._encrypt(ms, b"", iteration_exponent, identifier) + assert ems == expected_ems diff --git a/tests/click_tests/test_reset_slip39_advanced.py b/tests/click_tests/test_reset_slip39_advanced.py new file mode 100644 index 000000000..feea93e99 --- /dev/null +++ b/tests/click_tests/test_reset_slip39_advanced.py @@ -0,0 +1,193 @@ +# This file is part of the Trezor project. +# +# Copyright (C) 2012-2019 SatoshiLabs and contributors +# +# This library is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License version 3 +# as published by the Free Software Foundation. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the License along with this library. +# If not, see . + +from unittest import mock + +import pytest + +from trezorlib import device, messages + +from .. import buttons +from ..common import generate_entropy +from . import reset + +EXTERNAL_ENTROPY = b"zlutoucky kun upel divoke ody" * 2 + + +@pytest.mark.skip_t1 +@pytest.mark.setup_client(uninitialized=True) +def test_reset_slip39_advanced_2of2groups_2of2shares(device_handler): + features = device_handler.features() + debug = device_handler.debuglink() + + assert features.initialized is False + + os_urandom = mock.Mock(return_value=EXTERNAL_ENTROPY) + with mock.patch("os.urandom", os_urandom), device_handler: + device_handler.run( + device.reset, + strength=128, + backup_type=messages.BackupType.Slip39_Advanced, + pin_protection=False, + ) + + # confirm new wallet + reset.confirm_wait(debug, "Create new wallet") + + # confirm back up + reset.confirm_wait(debug, "Success") + + # confirm checklist + reset.confirm_read(debug, "Checklist") + + # set num of groups + reset.set_selection(debug, buttons.RESET_MINUS, 3) + + # confirm checklist + reset.confirm_read(debug, "Checklist") + + # set group threshold + reset.set_selection(debug, buttons.RESET_MINUS, 0) + + # confirm checklist + reset.confirm_read(debug, "Checklist") + + # set share num and threshold for groups + for _ in range(2): + # set num of shares + reset.set_selection(debug, buttons.RESET_MINUS, 3) + + # set share threshold + reset.set_selection(debug, buttons.RESET_MINUS, 0) + + # confirm backup warning + reset.confirm_read(debug, "Caution") + + all_words = [] + for _ in range(2): + for _ in range(2): + # read words + words = reset.read_words(debug, True) + + # confirm words + reset.confirm_words(debug, words) + + # confirm share checked + reset.confirm_read(debug, "Success") + + all_words.append(" ".join(words)) + + # confirm backup done + reset.confirm_read(debug, "Success") + + # generate secret locally + internal_entropy = debug.state().reset_entropy + secret = generate_entropy(128, internal_entropy, EXTERNAL_ENTROPY) + + # validate that all combinations will result in the correct master secret + reset.validate_mnemonics(all_words, secret) + + assert device_handler.result() == "Initialized" + + features = device_handler.features() + assert features.initialized is True + assert features.needs_backup is False + assert features.pin_protection is False + assert features.passphrase_protection is False + assert features.backup_type is messages.BackupType.Slip39_Advanced + + +@pytest.mark.skip_t1 +@pytest.mark.setup_client(uninitialized=True) +def test_reset_slip39_advanced_16of16groups_16of16shares(device_handler): + features = device_handler.features() + debug = device_handler.debuglink() + + assert features.initialized is False + + os_urandom = mock.Mock(return_value=EXTERNAL_ENTROPY) + with mock.patch("os.urandom", os_urandom), device_handler: + device_handler.run( + device.reset, + strength=128, + backup_type=messages.BackupType.Slip39_Advanced, + pin_protection=False, + ) + + # confirm new wallet + reset.confirm_wait(debug, "Create new wallet") + + # confirm back up + reset.confirm_wait(debug, "Success") + + # confirm checklist + reset.confirm_read(debug, "Checklist") + + # set num of groups + reset.set_selection(debug, buttons.RESET_PLUS, 11) + + # confirm checklist + reset.confirm_read(debug, "Checklist") + + # set group threshold + reset.set_selection(debug, buttons.RESET_PLUS, 11) + + # confirm checklist + reset.confirm_read(debug, "Checklist") + + # set share num and threshold for groups + for _ in range(16): + # set num of shares + reset.set_selection(debug, buttons.RESET_PLUS, 11) + + # set share threshold + reset.set_selection(debug, buttons.RESET_PLUS, 11) + + # confirm backup warning + reset.confirm_read(debug, "Caution") + + all_words = [] + for _ in range(16): + for _ in range(16): + # read words + words = reset.read_words(debug, True) + + # confirm words + reset.confirm_words(debug, words) + + # confirm share checked + reset.confirm_read(debug, "Success") + + all_words.append(" ".join(words)) + + # confirm backup done + reset.confirm_read(debug, "Success") + + # generate secret locally + internal_entropy = debug.state().reset_entropy + secret = generate_entropy(128, internal_entropy, EXTERNAL_ENTROPY) + + # validate that all combinations will result in the correct master secret + reset.validate_mnemonics(all_words, secret) + + assert device_handler.result() == "Initialized" + + features = device_handler.features() + assert features.initialized is True + assert features.needs_backup is False + assert features.pin_protection is False + assert features.passphrase_protection is False + assert features.backup_type is messages.BackupType.Slip39_Advanced diff --git a/tests/click_tests/test_reset_slip39_basic.py b/tests/click_tests/test_reset_slip39_basic.py new file mode 100644 index 000000000..92993078f --- /dev/null +++ b/tests/click_tests/test_reset_slip39_basic.py @@ -0,0 +1,174 @@ +# This file is part of the Trezor project. +# +# Copyright (C) 2012-2019 SatoshiLabs and contributors +# +# This library is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License version 3 +# as published by the Free Software Foundation. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the License along with this library. +# If not, see . + +from unittest import mock + +import pytest + +from trezorlib import device, messages + +from .. import buttons +from ..common import generate_entropy +from . import reset + +EXTERNAL_ENTROPY = b"zlutoucky kun upel divoke ody" * 2 + + +@pytest.mark.skip_t1 +@pytest.mark.setup_client(uninitialized=True) +def test_reset_slip39_basic_1of1(device_handler): + features = device_handler.features() + debug = device_handler.debuglink() + + assert features.initialized is False + + os_urandom = mock.Mock(return_value=EXTERNAL_ENTROPY) + with mock.patch("os.urandom", os_urandom), device_handler: + device_handler.run( + device.reset, + strength=128, + backup_type=messages.BackupType.Slip39_Basic, + pin_protection=False, + ) + + # confirm new wallet + reset.confirm_wait(debug, "Create new wallet") + + # confirm back up + reset.confirm_wait(debug, "Success") + + # confirm checklist + reset.confirm_read(debug, "Checklist") + + # set num of shares + # default is 5 so we press RESET_MINUS 4 times + reset.set_selection(debug, buttons.RESET_MINUS, 4) + + # confirm checklist + reset.confirm_read(debug, "Checklist") + + # set threshold + # threshold will default to 1 + reset.set_selection(debug, buttons.RESET_MINUS, 0) + + # confirm checklist + reset.confirm_read(debug, "Checklist") + + # confirm backup warning + reset.confirm_read(debug, "Caution") + + # read words + words = reset.read_words(debug) + + # confirm words + reset.confirm_words(debug, words) + + # confirm share checked + reset.confirm_read(debug, "Success") + + # confirm backup done + reset.confirm_read(debug, "Success") + + # generate secret locally + internal_entropy = debug.state().reset_entropy + secret = generate_entropy(128, internal_entropy, EXTERNAL_ENTROPY) + + # validate that all combinations will result in the correct master secret + validate = [" ".join(words)] + reset.validate_mnemonics(validate, secret) + + assert device_handler.result() == "Initialized" + features = device_handler.features() + assert features.initialized is True + assert features.needs_backup is False + assert features.pin_protection is False + assert features.passphrase_protection is False + assert features.backup_type is messages.BackupType.Slip39_Basic + + +@pytest.mark.skip_t1 +@pytest.mark.setup_client(uninitialized=True) +def test_reset_slip39_basic_16of16(device_handler): + features = device_handler.features() + debug = device_handler.debuglink() + + assert features.initialized is False + + os_urandom = mock.Mock(return_value=EXTERNAL_ENTROPY) + with mock.patch("os.urandom", os_urandom), device_handler: + device_handler.run( + device.reset, + strength=128, + backup_type=messages.BackupType.Slip39_Basic, + pin_protection=False, + ) + + # confirm new wallet + reset.confirm_wait(debug, "Create new wallet") + + # confirm back up + reset.confirm_wait(debug, "Success") + + # confirm checklist + reset.confirm_read(debug, "Checklist") + + # set num of shares + # default is 5 so we add 11 + reset.set_selection(debug, buttons.RESET_PLUS, 11) + + # confirm checklist + reset.confirm_read(debug, "Checklist") + + # set threshold + # default is 5 so we add 11 + reset.set_selection(debug, buttons.RESET_PLUS, 11) + + # confirm checklist + reset.confirm_read(debug, "Checklist") + + # confirm backup warning + reset.confirm_read(debug, "Caution") + + all_words = [] + for _ in range(16): + # read words + words = reset.read_words(debug) + + # confirm words + reset.confirm_words(debug, words) + + # confirm share checked + reset.confirm_read(debug, "Success") + + all_words.append(" ".join(words)) + + # confirm backup done + reset.confirm_read(debug, "Success") + + # generate secret locally + internal_entropy = debug.state().reset_entropy + secret = generate_entropy(128, internal_entropy, EXTERNAL_ENTROPY) + + # validate that all combinations will result in the correct master secret + reset.validate_mnemonics(all_words, secret) + + assert device_handler.result() == "Initialized" + features = device_handler.features() + assert features.initialized is True + assert features.needs_backup is False + assert features.pin_protection is False + assert features.passphrase_protection is False + assert features.backup_type is messages.BackupType.Slip39_Basic