diff --git a/tests/upgrade_tests/__init__.py b/tests/upgrade_tests/__init__.py index 49844ce0c..be9fb9fe6 100644 --- a/tests/upgrade_tests/__init__.py +++ b/tests/upgrade_tests/__init__.py @@ -46,6 +46,17 @@ core_only = pytest.mark.skipif( ) +def version_from_tag(tag: str | None) -> tuple | None: + if tag is None or not tag.startswith("v"): + return None + + # Strip initial "v" and optional revision from the tag string. + tag_version = tag[1:].partition("-")[0] + + # Translate string to an integer tuple. + return tuple(int(n) for n in tag_version.split(".")) + + def for_all( *args: str, legacy_minimum_version: Tuple[int, int, int] = (1, 0, 0), @@ -84,13 +95,9 @@ def for_all( continue try: for tag in ALL_TAGS[gen]: - if tag.startswith("v"): - tag_version = tag[1:] - if "-" in tag: # contains revision - tag_version = tag[1:-9] - tag_version = tuple(int(n) for n in tag_version.split(".")) - if tag_version < minimum_version: - continue + tag_version = version_from_tag(tag) + if tag_version is not None and tag_version < minimum_version: + continue all_params.append((gen, tag)) # at end, add None tag, which is the current master diff --git a/tests/upgrade_tests/test_firmware_upgrades.py b/tests/upgrade_tests/test_firmware_upgrades.py index 7c138f2be..c536296f5 100644 --- a/tests/upgrade_tests/test_firmware_upgrades.py +++ b/tests/upgrade_tests/test_firmware_upgrades.py @@ -18,22 +18,26 @@ import dataclasses from typing import TYPE_CHECKING, List, Optional import pytest +from shamir_mnemonic import shamir from trezorlib import btc, debuglink, device, exceptions, fido, models -from trezorlib.messages import BackupType +from trezorlib.messages import ApplySettings, BackupType, Success from trezorlib.tools import H_ from ..common import MNEMONIC_SLIP39_BASIC_20_3of6, MNEMONIC_SLIP39_BASIC_20_3of6_SECRET from ..device_handler import BackgroundDeviceHandler from ..emulators import ALL_TAGS, EmulatorWrapper -from . import for_all, for_tags, recovery_old +from ..input_flows import InputFlowSlip39BasicBackup +from . import for_all, for_tags, recovery_old, version_from_tag if TYPE_CHECKING: from trezorlib.debuglink import TrezorClientDebugLink as Client -models.TREZOR_ONE = dataclasses.replace(models.TREZOR_ONE, minimum_version=(1, 0, 0)) -models.TREZOR_T = dataclasses.replace(models.TREZOR_T, minimum_version=(2, 0, 0)) -models.TREZORS = {models.TREZOR_ONE, models.TREZOR_T} +models.T1B1 = dataclasses.replace(models.T1B1, minimum_version=(1, 0, 0)) +models.T2T1 = dataclasses.replace(models.T2T1, minimum_version=(2, 0, 0)) +models.TREZOR_ONE = models.T1B1 +models.TREZOR_T = models.T2T1 +models.TREZORS = {models.T1B1, models.T2T1} # **** COMMON DEFINITIONS **** @@ -333,6 +337,66 @@ def test_upgrade_shamir_recovery(gen: str, tag: Optional[str]): assert state.mnemonic_type == BackupType.Slip39_Basic +@for_all("core", core_minimum_version=(2, 1, 9)) +def test_upgrade_shamir_backup(gen: str, tag: Optional[str]): + with EmulatorWrapper(gen, tag) as emu: + # Generate a new encrypted master secret and record it. + device.reset( + emu.client, + pin_protection=False, + skip_backup=True, + backup_type=BackupType.Slip39_Basic, + ) + device_id = emu.client.features.device_id + backup_type = emu.client.features.backup_type + mnemonic_secret = emu.client.debug.state().mnemonic_secret + + # Set passphrase_source = HOST. + resp = emu.client.call(ApplySettings(_passphrase_source=2, use_passphrase=True)) + assert isinstance(resp, Success) + + # Get a passphrase-less and a passphrased address. + address = btc.get_address(emu.client, "Bitcoin", PATH) + emu.client.init_device(new_session=True) + emu.client.use_passphrase("TREZOR") + address_passphrase = btc.get_address(emu.client, "Bitcoin", PATH) + + assert emu.client.features.needs_backup + storage = emu.get_storage() + + with EmulatorWrapper(gen, storage=storage) as emu: + assert emu.client.features.device_id == device_id + + # Create a backup of the encrypted master secret. + assert emu.client.features.needs_backup + with emu.client: + IF = InputFlowSlip39BasicBackup(emu.client, False) + emu.client.set_input_flow(IF.get()) + device.backup(emu.client) + assert not emu.client.features.needs_backup + + # Check the backup type. + assert emu.client.features.backup_type == backup_type + tag_version = version_from_tag(tag) + if tag_version is not None: + assert ( + backup_type == BackupType.Slip39_Basic + if tag_version < (2, 7, 1) + else BackupType.Slip39_Basic_Extendable + ) + + # Check that the backup contains the originally generated encrypted master secret. + groups = shamir.decode_mnemonics(IF.mnemonics[:3]) + ems = shamir.recover_ems(groups) + assert ems.ciphertext == mnemonic_secret + + # Check that addresses are the same after firmware upgrade and backup. + assert btc.get_address(emu.client, "Bitcoin", PATH) == address + emu.client.init_device(new_session=True) + emu.client.use_passphrase("TREZOR") + assert btc.get_address(emu.client, "Bitcoin", PATH) == address_passphrase + + @for_all(legacy_minimum_version=(1, 8, 4), core_minimum_version=(2, 1, 9)) def test_upgrade_u2f(gen: str, tag: str): """Check U2F counter stayed the same after an upgrade."""