From 164bac20b04a1a84a98dc754ef152afde4ff1f4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ioan=20Biz=C4=83u?= Date: Tue, 23 Apr 2024 12:26:46 +0200 Subject: [PATCH] feat(core): implement repeated backup. --- common/protob/messages-management.proto | 8 +- core/src/all_modules.py | 2 + core/src/apps/management/backup_device.py | 5 +- .../management/recovery_device/__init__.py | 68 ++++---- .../management/recovery_device/homescreen.py | 32 +++- core/src/storage/cache.py | 3 +- core/src/storage/recovery.py | 17 +- core/src/trezor/enums/RecoveryKind.py | 7 + core/src/trezor/enums/__init__.py | 5 + core/src/trezor/messages.py | 5 +- docs/common/message-workflows.md | 9 +- python/src/trezorlib/cli/device.py | 3 + python/src/trezorlib/device.py | 18 ++- python/src/trezorlib/messages.py | 12 +- .../protos/generated/messages_management.rs | 148 +++++++++++++----- .../test_recovery_bip39_dryrun.py | 2 + 16 files changed, 251 insertions(+), 93 deletions(-) create mode 100644 core/src/trezor/enums/RecoveryKind.py diff --git a/common/protob/messages-management.proto b/common/protob/messages-management.proto index a8f574036..ffd97cb1f 100644 --- a/common/protob/messages-management.proto +++ b/common/protob/messages-management.proto @@ -435,7 +435,7 @@ message RecoveryDevice { // 7 reserved for unused recovery method optional RecoveryDeviceType type = 8; // supported recovery type optional uint32 u2f_counter = 9; // U2F counter - optional bool dry_run = 10; // perform dry-run recovery workflow (for safe mnemonic validation) + optional RecoveryKind kind = 10; // the kind of recovery to perform /** * Type of recovery procedure. These should be used as bitmask, e.g., * `RecoveryDeviceType_ScrambledWords | RecoveryDeviceType_Matrix` @@ -449,6 +449,12 @@ message RecoveryDevice { RecoveryDeviceType_ScrambledWords = 0; // words in scrambled order RecoveryDeviceType_Matrix = 1; // matrix recovery type } + + enum RecoveryKind { + RecoveryKind_NormalRecovery = 0; // recovery from seedphrase on an uninitialized device + RecoveryKind_DryRun = 1; // mnemonic validation + RecoveryKind_UnlockRepeatedBackup = 2; // unlock SLIP-39 repeated backup + } } /** diff --git a/core/src/all_modules.py b/core/src/all_modules.py index 571e7cda5..cd3b499a4 100644 --- a/core/src/all_modules.py +++ b/core/src/all_modules.py @@ -121,6 +121,8 @@ trezor.enums.PinMatrixRequestType import trezor.enums.PinMatrixRequestType trezor.enums.RecoveryDeviceType import trezor.enums.RecoveryDeviceType +trezor.enums.RecoveryKind +import trezor.enums.RecoveryKind trezor.enums.RequestType import trezor.enums.RequestType trezor.enums.SafetyCheckLevel diff --git a/core/src/apps/management/backup_device.py b/core/src/apps/management/backup_device.py index c4bb30c70..13f64b5d6 100644 --- a/core/src/apps/management/backup_device.py +++ b/core/src/apps/management/backup_device.py @@ -5,6 +5,7 @@ if TYPE_CHECKING: async def backup_device(msg: BackupDevice) -> Success: + import storage.cache as storage_cache import storage.device as storage_device from trezor import wire from trezor.messages import Success @@ -15,7 +16,7 @@ async def backup_device(msg: BackupDevice) -> Success: if not storage_device.is_initialized(): raise wire.NotInitialized("Device is not initialized") - if not storage_device.needs_backup(): + if not storage_device.needs_backup() and storage_cache.get(storage_cache.APP_RECOVERY_REPEATED_BACKUP_UNLOCKED) != b"\x01": raise wire.ProcessError("Seed already backed up") mnemonic_secret, backup_type = mnemonic.get() @@ -25,6 +26,8 @@ async def backup_device(msg: BackupDevice) -> Success: storage_device.set_unfinished_backup(True) storage_device.set_backed_up() + # TODO: clear APP_RECOVERY_REPEATED_BACKUP_UNLOCKED - here? + await backup_seed( backup_type, mnemonic_secret, diff --git a/core/src/apps/management/recovery_device/__init__.py b/core/src/apps/management/recovery_device/__init__.py index a4f349ba8..8f385b26c 100644 --- a/core/src/apps/management/recovery_device/__init__.py +++ b/core/src/apps/management/recovery_device/__init__.py @@ -1,12 +1,14 @@ from typing import TYPE_CHECKING +from trezor.enums import RecoveryKind + if TYPE_CHECKING: from trezor.messages import RecoveryDevice, Success # List of RecoveryDevice fields that can be set when doing dry-run recovery. # All except `dry_run` are allowed for T1 compatibility, but their values are ignored. # If set, `enforce_wordlist` must be True, because we do not support non-enforcing. -DRY_RUN_ALLOWED_FIELDS = ("dry_run", "word_count", "enforce_wordlist", "type") +DRY_RUN_ALLOWED_FIELDS = ("kind", "word_count", "enforce_wordlist", "type") async def recovery_device(msg: RecoveryDevice) -> Success: @@ -31,68 +33,68 @@ async def recovery_device(msg: RecoveryDevice) -> Success: from .homescreen import recovery_homescreen, recovery_process - dry_run = msg.dry_run # local_cache_attribute + recovery_kind = msg.kind # local_cache_attribute # -------------------------------------------------------- # validate - if not dry_run and storage_device.is_initialized(): - raise wire.UnexpectedMessage("Already initialized") - if dry_run and not storage_device.is_initialized(): - raise wire.NotInitialized("Device is not initialized") - if msg.enforce_wordlist is False: - raise wire.ProcessError( - "Value enforce_wordlist must be True, Trezor Core enforces words automatically." - ) - if dry_run: + if recovery_kind == RecoveryKind.NormalRecovery: + if storage_device.is_initialized(): + raise wire.UnexpectedMessage("Already initialized") + elif recovery_kind == RecoveryKind.DryRun or recovery_kind == RecoveryKind.UnlockRepeatedBackup: + if not storage_device.is_initialized(): + raise wire.NotInitialized("Device is not initialized") + + if recovery_kind == RecoveryKind.DryRun: # TODO: what about UnlockRepeatedBackup? # check that only allowed fields are set for key, value in msg.__dict__.items(): if key not in DRY_RUN_ALLOWED_FIELDS and value is not None: raise wire.ProcessError(f"Forbidden field set in dry-run: {key}") + + if msg.enforce_wordlist is False: + raise wire.ProcessError( + "Value enforce_wordlist must be True, Trezor Core enforces words automatically." + ) # END validate # -------------------------------------------------------- if storage_recovery.is_in_progress(): return await recovery_process() - # -------------------------------------------------------- - # _continue_dialog - if not dry_run: + if recovery_kind == RecoveryKind.NormalRecovery: await confirm_reset_device(TR.recovery__title_recover, recovery=True) - else: - await confirm_action( - "confirm_seedcheck", - TR.recovery__title_dry_run, - description=TR.recovery__check_dry_run, - br_code=ButtonRequestType.ProtectCall, - verb=TR.buttons__check, - ) - # END _continue_dialog - # -------------------------------------------------------- - if not dry_run: # wipe storage to make sure the device is in a clear state storage.reset() - # for dry run pin needs to be entered - if dry_run: - curpin, salt = await request_pin_and_sd_salt(TR.pin__enter) - if not config.check_pin(curpin, salt): - await error_pin_invalid() - - if not dry_run: # set up pin if requested if msg.pin_protection: newpin = await request_pin_confirm(allow_cancel=False) config.change_pin("", newpin, None, None) storage_device.set_passphrase_enabled(bool(msg.passphrase_protection)) + if msg.u2f_counter is not None: storage_device.set_u2f_counter(msg.u2f_counter) + if msg.label is not None: storage_device.set_label(msg.label) + elif recovery_kind == RecoveryKind.DryRun or recovery_kind == RecoveryKind.UnlockRepeatedBackup: + await confirm_action( + "confirm_seedcheck", + TR.recovery__title_dry_run, # TODO: separate title for UnlockRepeatedBackup? + description=TR.recovery__check_dry_run, + br_code=ButtonRequestType.ProtectCall, + verb=TR.buttons__check, + ) + + curpin, salt = await request_pin_and_sd_salt(TR.pin__enter) + if not config.check_pin(curpin, salt): + await error_pin_invalid() storage_recovery.set_in_progress(True) - storage_recovery.set_dry_run(bool(dry_run)) + + storage_recovery.set_kind(int(recovery_kind)) workflow.set_default(recovery_homescreen) + return await recovery_process() diff --git a/core/src/apps/management/recovery_device/homescreen.py b/core/src/apps/management/recovery_device/homescreen.py index 2691c6cbb..a55749401 100644 --- a/core/src/apps/management/recovery_device/homescreen.py +++ b/core/src/apps/management/recovery_device/homescreen.py @@ -1,5 +1,6 @@ from typing import TYPE_CHECKING +import storage.cache as storage_cache import storage.device as storage_device import storage.recovery as storage_recovery import storage.recovery_shares as storage_recovery_shares @@ -21,25 +22,40 @@ async def recovery_homescreen() -> None: if not storage_recovery.is_in_progress(): workflow.set_default(homescreen) return - - await recovery_process() + elif storage_cache.get(storage_cache.APP_RECOVERY_REPEATED_BACKUP_UNLOCKED) == b"\x01": + await _continue_repeated_backup() + else: + await recovery_process() async def recovery_process() -> Success: import storage from trezor.enums import MessageType - wire.AVOID_RESTARTING_FOR = (MessageType.Initialize, MessageType.GetFeatures) + is_special_kind = storage_recovery.is_dry_run() or storage_recovery.is_unlock_repeated_backup() + + wire.AVOID_RESTARTING_FOR = (MessageType.Initialize, MessageType.GetFeatures, MessageType.GetPublicKey) try: return await _continue_recovery_process() except recover.RecoveryAborted: - dry_run = storage_recovery.is_dry_run() - if dry_run: + if is_special_kind: storage_recovery.end_progress() else: storage.wipe() raise wire.ActionCancelled +async def _continue_repeated_backup() -> Success: + from trezor.enums import ButtonRequestType + from trezor.ui.layouts import confirm_action + + await confirm_action( # TODO + "confirm_repeated_backup", + "BACKUP?", + description="New backup?", + br_code=ButtonRequestType.ProtectCall, + verb="Backup!", + ) + async def _continue_recovery_process() -> Success: from trezor import utils @@ -47,6 +63,7 @@ async def _continue_recovery_process() -> Success: # gather the current recovery state from storage dry_run = storage_recovery.is_dry_run() + unlock_repeated_backup = storage_recovery.is_unlock_repeated_backup() word_count, backup_type = recover.load_slip39_state() # Both word_count and backup_type are derived from the same data. Both will be @@ -95,6 +112,11 @@ async def _continue_recovery_process() -> Success: assert backup_type is not None if dry_run: result = await _finish_recovery_dry_run(secret, backup_type) + elif unlock_repeated_backup: + import storage.cache + + storage.cache.set(storage.cache.APP_RECOVERY_REPEATED_BACKUP_UNLOCKED, b"\x01") + result = Success(message="Backup unlocked") else: result = await _finish_recovery(secret, backup_type) diff --git a/core/src/storage/cache.py b/core/src/storage/cache.py index 1e1afdd84..0989d7653 100644 --- a/core/src/storage/cache.py +++ b/core/src/storage/cache.py @@ -34,7 +34,7 @@ APP_COMMON_REQUEST_PIN_LAST_UNLOCK = const(3 | _SESSIONLESS_FLAG) APP_COMMON_BUSY_DEADLINE_MS = const(4 | _SESSIONLESS_FLAG) APP_MISC_COSI_NONCE = const(5 | _SESSIONLESS_FLAG) APP_MISC_COSI_COMMITMENT = const(6 | _SESSIONLESS_FLAG) - +APP_RECOVERY_REPEATED_BACKUP_UNLOCKED = const(7 | _SESSIONLESS_FLAG) # === Homescreen storage === # This does not logically belong to the "cache" functionality, but the cache module is @@ -145,6 +145,7 @@ class SessionlessCache(DataCache): 8, # APP_COMMON_BUSY_DEADLINE_MS 32, # APP_MISC_COSI_NONCE 32, # APP_MISC_COSI_COMMITMENT + 1, # APP_RECOVERY_REPEATED_BACKUP_UNLOCKED ) super().__init__() diff --git a/core/src/storage/recovery.py b/core/src/storage/recovery.py index 5e7324aa2..fa1604c61 100644 --- a/core/src/storage/recovery.py +++ b/core/src/storage/recovery.py @@ -1,6 +1,7 @@ from micropython import const from storage import common +from trezor.enums import RecoveryKind # Namespace: _NAMESPACE = common.APP_RECOVERY @@ -8,13 +9,14 @@ _NAMESPACE = common.APP_RECOVERY # fmt: off # Keys: _IN_PROGRESS = const(0x00) # bool -_DRY_RUN = const(0x01) # bool +_KIND = const(0x01) # int _SLIP39_IDENTIFIER = const(0x03) # bytes _REMAINING = const(0x05) # int _SLIP39_ITERATION_EXPONENT = const(0x06) # int _SLIP39_GROUP_COUNT = const(0x07) # int # Deprecated Keys: +# _DRY_RUN = const(0x01) # bool (got upgraded to int) # _WORD_COUNT = const(0x02) # int # _SLIP39_THRESHOLD = const(0x04) # int # fmt: on @@ -36,14 +38,19 @@ def is_in_progress() -> bool: return common.get_bool(_NAMESPACE, _IN_PROGRESS) -def set_dry_run(val: bool) -> None: +def set_kind(val: int) -> None: _require_progress() - common.set_bool(_NAMESPACE, _DRY_RUN, val) + common.set_uint8(_NAMESPACE, _KIND, val) def is_dry_run() -> bool: _require_progress() - return common.get_bool(_NAMESPACE, _DRY_RUN) + return common.get_uint8(_NAMESPACE, _KIND) == RecoveryKind.DryRun + + +def is_unlock_repeated_backup() -> bool: + _require_progress() + return common.get_uint8(_NAMESPACE, _KIND) == RecoveryKind.UnlockRepeatedBackup def set_slip39_identifier(identifier: int) -> None: @@ -128,7 +135,7 @@ def end_progress() -> None: _require_progress() for key in ( _IN_PROGRESS, - _DRY_RUN, + _KIND, _SLIP39_IDENTIFIER, _REMAINING, _SLIP39_ITERATION_EXPONENT, diff --git a/core/src/trezor/enums/RecoveryKind.py b/core/src/trezor/enums/RecoveryKind.py new file mode 100644 index 000000000..8806a3063 --- /dev/null +++ b/core/src/trezor/enums/RecoveryKind.py @@ -0,0 +1,7 @@ +# Automatically generated by pb2py +# fmt: off +# isort:skip_file + +NormalRecovery = 0 +DryRun = 1 +UnlockRepeatedBackup = 2 diff --git a/core/src/trezor/enums/__init__.py b/core/src/trezor/enums/__init__.py index 3335f1b27..e74bd11a4 100644 --- a/core/src/trezor/enums/__init__.py +++ b/core/src/trezor/enums/__init__.py @@ -457,6 +457,11 @@ if TYPE_CHECKING: ScrambledWords = 0 Matrix = 1 + class RecoveryKind(IntEnum): + NormalRecovery = 0 + DryRun = 1 + UnlockRepeatedBackup = 2 + class WordRequestType(IntEnum): Plain = 0 Matrix9 = 1 diff --git a/core/src/trezor/messages.py b/core/src/trezor/messages.py index 189acb1d8..5623f17db 100644 --- a/core/src/trezor/messages.py +++ b/core/src/trezor/messages.py @@ -53,6 +53,7 @@ if TYPE_CHECKING: from trezor.enums import OutputScriptType # noqa: F401 from trezor.enums import PinMatrixRequestType # noqa: F401 from trezor.enums import RecoveryDeviceType # noqa: F401 + from trezor.enums import RecoveryKind # noqa: F401 from trezor.enums import RequestType # noqa: F401 from trezor.enums import SafetyCheckLevel # noqa: F401 from trezor.enums import SdProtectOperationType # noqa: F401 @@ -2569,7 +2570,7 @@ if TYPE_CHECKING: enforce_wordlist: "bool | None" type: "RecoveryDeviceType | None" u2f_counter: "int | None" - dry_run: "bool | None" + kind: "RecoveryKind | None" def __init__( self, @@ -2581,7 +2582,7 @@ if TYPE_CHECKING: enforce_wordlist: "bool | None" = None, type: "RecoveryDeviceType | None" = None, u2f_counter: "int | None" = None, - dry_run: "bool | None" = None, + kind: "RecoveryKind | None" = None, ) -> None: pass diff --git a/docs/common/message-workflows.md b/docs/common/message-workflows.md index 7a72f9be4..8832297e9 100644 --- a/docs/common/message-workflows.md +++ b/docs/common/message-workflows.md @@ -279,9 +279,14 @@ entering PIN) or standard recovery (with entering the seed to the host computer one by one in random order). The process continues with optional check of the seed validity and optional setting up the PIN, which has to be confirmed. Finally the recovered wallet is saved into -device storage. The same process is used with the dry run recovery, the +device storage. + + +TODO! + +The same process is used with the dry run recovery, the differences are that this process can be done only with already -initialized deviice and that the mnemonic is not saved into the device +initialized device and that the mnemonic is not saved into the device but it is only compared to the mnemonic already loaded into the device with the successful result (The seed is valid and matches the one in the device) or unsuccessful result(The seed is valid but does not match the diff --git a/python/src/trezorlib/cli/device.py b/python/src/trezorlib/cli/device.py index c09d49453..391967c22 100644 --- a/python/src/trezorlib/cli/device.py +++ b/python/src/trezorlib/cli/device.py @@ -151,6 +151,7 @@ def load( "-t", "--type", "rec_type", type=ChoiceType(RECOVERY_TYPE), default="scrambled" ) @click.option("-d", "--dry-run", is_flag=True) +@click.option("-b", "--unlock-repeated-backup", is_flag=True) @with_client def recover( client: "TrezorClient", @@ -162,6 +163,7 @@ def recover( u2f_counter: int, rec_type: messages.RecoveryDeviceType, dry_run: bool, + unlock_repeated_backup: bool, ) -> "MessageType": """Start safe recovery workflow.""" if rec_type == messages.RecoveryDeviceType.ScrambledWords: @@ -180,6 +182,7 @@ def recover( input_callback=input_callback, type=rec_type, dry_run=dry_run, + unlock_repeated_backup=unlock_repeated_backup, ) diff --git a/python/src/trezorlib/device.py b/python/src/trezorlib/device.py index bdebdc06b..edf08a0f8 100644 --- a/python/src/trezorlib/device.py +++ b/python/src/trezorlib/device.py @@ -159,6 +159,7 @@ def recover( input_callback: Optional[Callable] = None, type: messages.RecoveryDeviceType = messages.RecoveryDeviceType.ScrambledWords, dry_run: bool = False, + unlock_repeated_backup: bool = False, u2f_counter: Optional[int] = None, ) -> "MessageType": if language is not None: @@ -173,7 +174,7 @@ def recover( if word_count not in (12, 18, 24): raise ValueError("Invalid word count. Use 12/18/24") - if client.features.initialized and not dry_run: + if client.features.initialized and not (dry_run or unlock_repeated_backup): raise RuntimeError( "Device already initialized. Call device.wipe() and try again." ) @@ -181,11 +182,22 @@ def recover( if u2f_counter is None: u2f_counter = int(time.time()) + if not dry_run and not unlock_repeated_backup: + kind = messages.RecoveryKind.NormalRecovery + elif dry_run and not unlock_repeated_backup: + kind = messages.RecoveryKind.DryRun + elif unlock_repeated_backup and not dry_run: + kind = messages.RecoveryKind.UnlockRepeatedBackup + else: + raise RuntimeError( + "Only one of dry_run and unlock_repeated_backup can be requested at the same time." + ) + msg = messages.RecoveryDevice( - word_count=word_count, enforce_wordlist=True, type=type, dry_run=dry_run + word_count=word_count, enforce_wordlist=True, type=type, kind=kind ) - if not dry_run: + if kind == messages.RecoveryKind.NormalRecovery: # set additional parameters msg.passphrase_protection = passphrase_protection msg.pin_protection = pin_protection diff --git a/python/src/trezorlib/messages.py b/python/src/trezorlib/messages.py index 662fb3679..fdad9516a 100644 --- a/python/src/trezorlib/messages.py +++ b/python/src/trezorlib/messages.py @@ -491,6 +491,12 @@ class RecoveryDeviceType(IntEnum): Matrix = 1 +class RecoveryKind(IntEnum): + NormalRecovery = 0 + DryRun = 1 + UnlockRepeatedBackup = 2 + + class WordRequestType(IntEnum): Plain = 0 Matrix9 = 1 @@ -3729,7 +3735,7 @@ class RecoveryDevice(protobuf.MessageType): 6: protobuf.Field("enforce_wordlist", "bool", repeated=False, required=False, default=None), 8: protobuf.Field("type", "RecoveryDeviceType", repeated=False, required=False, default=None), 9: protobuf.Field("u2f_counter", "uint32", repeated=False, required=False, default=None), - 10: protobuf.Field("dry_run", "bool", repeated=False, required=False, default=None), + 10: protobuf.Field("kind", "RecoveryKind", repeated=False, required=False, default=None), } def __init__( @@ -3743,7 +3749,7 @@ class RecoveryDevice(protobuf.MessageType): enforce_wordlist: Optional["bool"] = None, type: Optional["RecoveryDeviceType"] = None, u2f_counter: Optional["int"] = None, - dry_run: Optional["bool"] = None, + kind: Optional["RecoveryKind"] = None, ) -> None: self.word_count = word_count self.passphrase_protection = passphrase_protection @@ -3753,7 +3759,7 @@ class RecoveryDevice(protobuf.MessageType): self.enforce_wordlist = enforce_wordlist self.type = type self.u2f_counter = u2f_counter - self.dry_run = dry_run + self.kind = kind class WordRequest(protobuf.MessageType): diff --git a/rust/trezor-client/src/protos/generated/messages_management.rs b/rust/trezor-client/src/protos/generated/messages_management.rs index d1257069c..58b4c66fe 100644 --- a/rust/trezor-client/src/protos/generated/messages_management.rs +++ b/rust/trezor-client/src/protos/generated/messages_management.rs @@ -7645,8 +7645,8 @@ pub struct RecoveryDevice { pub type_: ::std::option::Option<::protobuf::EnumOrUnknown>, // @@protoc_insertion_point(field:hw.trezor.messages.management.RecoveryDevice.u2f_counter) pub u2f_counter: ::std::option::Option, - // @@protoc_insertion_point(field:hw.trezor.messages.management.RecoveryDevice.dry_run) - pub dry_run: ::std::option::Option, + // @@protoc_insertion_point(field:hw.trezor.messages.management.RecoveryDevice.kind) + pub kind: ::std::option::Option<::protobuf::EnumOrUnknown>, // special fields // @@protoc_insertion_point(special_field:hw.trezor.messages.management.RecoveryDevice.special_fields) pub special_fields: ::protobuf::SpecialFields, @@ -7852,23 +7852,26 @@ impl RecoveryDevice { self.u2f_counter = ::std::option::Option::Some(v); } - // optional bool dry_run = 10; + // optional .hw.trezor.messages.management.RecoveryDevice.RecoveryKind kind = 10; - pub fn dry_run(&self) -> bool { - self.dry_run.unwrap_or(false) + pub fn kind(&self) -> recovery_device::RecoveryKind { + match self.kind { + Some(e) => e.enum_value_or(recovery_device::RecoveryKind::RecoveryKind_NormalRecovery), + None => recovery_device::RecoveryKind::RecoveryKind_NormalRecovery, + } } - pub fn clear_dry_run(&mut self) { - self.dry_run = ::std::option::Option::None; + pub fn clear_kind(&mut self) { + self.kind = ::std::option::Option::None; } - pub fn has_dry_run(&self) -> bool { - self.dry_run.is_some() + pub fn has_kind(&self) -> bool { + self.kind.is_some() } // Param is passed by value, moved - pub fn set_dry_run(&mut self, v: bool) { - self.dry_run = ::std::option::Option::Some(v); + pub fn set_kind(&mut self, v: recovery_device::RecoveryKind) { + self.kind = ::std::option::Option::Some(::protobuf::EnumOrUnknown::new(v)); } fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { @@ -7915,9 +7918,9 @@ impl RecoveryDevice { |m: &mut RecoveryDevice| { &mut m.u2f_counter }, )); fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( - "dry_run", - |m: &RecoveryDevice| { &m.dry_run }, - |m: &mut RecoveryDevice| { &mut m.dry_run }, + "kind", + |m: &RecoveryDevice| { &m.kind }, + |m: &mut RecoveryDevice| { &mut m.kind }, )); ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( "RecoveryDevice", @@ -7962,7 +7965,7 @@ impl ::protobuf::Message for RecoveryDevice { self.u2f_counter = ::std::option::Option::Some(is.read_uint32()?); }, 80 => { - self.dry_run = ::std::option::Option::Some(is.read_bool()?); + self.kind = ::std::option::Option::Some(is.read_enum_or_unknown()?); }, tag => { ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; @@ -8000,8 +8003,8 @@ impl ::protobuf::Message for RecoveryDevice { if let Some(v) = self.u2f_counter { my_size += ::protobuf::rt::uint32_size(9, v); } - if let Some(v) = self.dry_run { - my_size += 1 + 1; + if let Some(v) = self.kind { + my_size += ::protobuf::rt::int32_size(10, v.value()); } my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); self.special_fields.cached_size().set(my_size as u32); @@ -8033,8 +8036,8 @@ impl ::protobuf::Message for RecoveryDevice { if let Some(v) = self.u2f_counter { os.write_uint32(9, v)?; } - if let Some(v) = self.dry_run { - os.write_bool(10, v)?; + if let Some(v) = self.kind { + os.write_enum(10, ::protobuf::EnumOrUnknown::value(&v))?; } os.write_unknown_fields(self.special_fields.unknown_fields())?; ::std::result::Result::Ok(()) @@ -8061,7 +8064,7 @@ impl ::protobuf::Message for RecoveryDevice { self.enforce_wordlist = ::std::option::Option::None; self.type_ = ::std::option::Option::None; self.u2f_counter = ::std::option::Option::None; - self.dry_run = ::std::option::Option::None; + self.kind = ::std::option::Option::None; self.special_fields.clear(); } @@ -8075,7 +8078,7 @@ impl ::protobuf::Message for RecoveryDevice { enforce_wordlist: ::std::option::Option::None, type_: ::std::option::Option::None, u2f_counter: ::std::option::Option::None, - dry_run: ::std::option::Option::None, + kind: ::std::option::Option::None, special_fields: ::protobuf::SpecialFields::new(), }; &instance @@ -8162,6 +8165,73 @@ pub mod recovery_device { ::protobuf::reflect::GeneratedEnumDescriptorData::new::("RecoveryDevice.RecoveryDeviceType") } } + + #[derive(Clone,Copy,PartialEq,Eq,Debug,Hash)] + // @@protoc_insertion_point(enum:hw.trezor.messages.management.RecoveryDevice.RecoveryKind) + pub enum RecoveryKind { + // @@protoc_insertion_point(enum_value:hw.trezor.messages.management.RecoveryDevice.RecoveryKind.RecoveryKind_NormalRecovery) + RecoveryKind_NormalRecovery = 0, + // @@protoc_insertion_point(enum_value:hw.trezor.messages.management.RecoveryDevice.RecoveryKind.RecoveryKind_DryRun) + RecoveryKind_DryRun = 1, + // @@protoc_insertion_point(enum_value:hw.trezor.messages.management.RecoveryDevice.RecoveryKind.RecoveryKind_UnlockRepeatedBackup) + RecoveryKind_UnlockRepeatedBackup = 2, + } + + impl ::protobuf::Enum for RecoveryKind { + const NAME: &'static str = "RecoveryKind"; + + fn value(&self) -> i32 { + *self as i32 + } + + fn from_i32(value: i32) -> ::std::option::Option { + match value { + 0 => ::std::option::Option::Some(RecoveryKind::RecoveryKind_NormalRecovery), + 1 => ::std::option::Option::Some(RecoveryKind::RecoveryKind_DryRun), + 2 => ::std::option::Option::Some(RecoveryKind::RecoveryKind_UnlockRepeatedBackup), + _ => ::std::option::Option::None + } + } + + fn from_str(str: &str) -> ::std::option::Option { + match str { + "RecoveryKind_NormalRecovery" => ::std::option::Option::Some(RecoveryKind::RecoveryKind_NormalRecovery), + "RecoveryKind_DryRun" => ::std::option::Option::Some(RecoveryKind::RecoveryKind_DryRun), + "RecoveryKind_UnlockRepeatedBackup" => ::std::option::Option::Some(RecoveryKind::RecoveryKind_UnlockRepeatedBackup), + _ => ::std::option::Option::None + } + } + + const VALUES: &'static [RecoveryKind] = &[ + RecoveryKind::RecoveryKind_NormalRecovery, + RecoveryKind::RecoveryKind_DryRun, + RecoveryKind::RecoveryKind_UnlockRepeatedBackup, + ]; + } + + impl ::protobuf::EnumFull for RecoveryKind { + fn enum_descriptor() -> ::protobuf::reflect::EnumDescriptor { + static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::EnumDescriptor> = ::protobuf::rt::Lazy::new(); + descriptor.get(|| super::file_descriptor().enum_by_package_relative_name("RecoveryDevice.RecoveryKind").unwrap()).clone() + } + + fn descriptor(&self) -> ::protobuf::reflect::EnumValueDescriptor { + let index = *self as usize; + Self::enum_descriptor().value_by_index(index) + } + } + + impl ::std::default::Default for RecoveryKind { + fn default() -> Self { + RecoveryKind::RecoveryKind_NormalRecovery + } + } + + impl RecoveryKind { + pub(in super) fn generated_enum_descriptor_data() -> ::protobuf::reflect::GeneratedEnumDescriptorData { + ::protobuf::reflect::GeneratedEnumDescriptorData::new::("RecoveryDevice.RecoveryKind") + } + } } // @@protoc_insertion_point(message:hw.trezor.messages.management.WordRequest) @@ -10676,7 +10746,7 @@ static file_descriptor_proto_data: &'static [u8] = b"\ \n\x10member_threshold\x18\x01\x20\x02(\rR\x0fmemberThreshold\x12!\n\x0c\ member_count\x18\x02\x20\x02(\rR\x0bmemberCount\"\x10\n\x0eEntropyReques\ t\"&\n\nEntropyAck\x12\x18\n\x07entropy\x18\x01\x20\x02(\x0cR\x07entropy\ - \"\xd8\x03\n\x0eRecoveryDevice\x12\x1d\n\nword_count\x18\x01\x20\x01(\rR\ + \"\x80\x05\n\x0eRecoveryDevice\x12\x1d\n\nword_count\x18\x01\x20\x01(\rR\ \twordCount\x123\n\x15passphrase_protection\x18\x02\x20\x01(\x08R\x14pas\ sphraseProtection\x12%\n\x0epin_protection\x18\x03\x20\x01(\x08R\rpinPro\ tection\x12\x1e\n\x08language\x18\x04\x20\x01(\tR\x08languageB\x02\x18\ @@ -10684,20 +10754,23 @@ static file_descriptor_proto_data: &'static [u8] = b"\ ordlist\x18\x06\x20\x01(\x08R\x0fenforceWordlist\x12T\n\x04type\x18\x08\ \x20\x01(\x0e2@.hw.trezor.messages.management.RecoveryDevice.RecoveryDev\ iceTypeR\x04type\x12\x1f\n\x0bu2f_counter\x18\t\x20\x01(\rR\nu2fCounter\ - \x12\x17\n\x07dry_run\x18\n\x20\x01(\x08R\x06dryRun\"Z\n\x12RecoveryDevi\ - ceType\x12%\n!RecoveryDeviceType_ScrambledWords\x10\0\x12\x1d\n\x19Recov\ - eryDeviceType_Matrix\x10\x01\"\xc5\x01\n\x0bWordRequest\x12N\n\x04type\ - \x18\x01\x20\x02(\x0e2:.hw.trezor.messages.management.WordRequest.WordRe\ - questTypeR\x04type\"f\n\x0fWordRequestType\x12\x19\n\x15WordRequestType_\ - Plain\x10\0\x12\x1b\n\x17WordRequestType_Matrix9\x10\x01\x12\x1b\n\x17Wo\ - rdRequestType_Matrix6\x10\x02\"\x1d\n\x07WordAck\x12\x12\n\x04word\x18\ - \x01\x20\x02(\tR\x04word\"0\n\rSetU2FCounter\x12\x1f\n\x0bu2f_counter\ - \x18\x01\x20\x02(\rR\nu2fCounter\"\x13\n\x11GetNextU2FCounter\"1\n\x0eNe\ - xtU2FCounter\x12\x1f\n\x0bu2f_counter\x18\x01\x20\x02(\rR\nu2fCounter\"\ - \x11\n\x0fDoPreauthorized\"\x16\n\x14PreauthorizedRequest\"\x15\n\x13Can\ - celAuthorization\"\x9a\x02\n\x12RebootToBootloader\x12o\n\x0cboot_comman\ - d\x18\x01\x20\x01(\x0e2=.hw.trezor.messages.management.RebootToBootloade\ - r.BootCommand:\rSTOP_AND_WAITR\x0bbootCommand\x12'\n\x0ffirmware_header\ + \x12N\n\x04kind\x18\n\x20\x01(\x0e2:.hw.trezor.messages.management.Recov\ + eryDevice.RecoveryKindR\x04kind\"Z\n\x12RecoveryDeviceType\x12%\n!Recove\ + ryDeviceType_ScrambledWords\x10\0\x12\x1d\n\x19RecoveryDeviceType_Matrix\ + \x10\x01\"o\n\x0cRecoveryKind\x12\x1f\n\x1bRecoveryKind_NormalRecovery\ + \x10\0\x12\x17\n\x13RecoveryKind_DryRun\x10\x01\x12%\n!RecoveryKind_Unlo\ + ckRepeatedBackup\x10\x02\"\xc5\x01\n\x0bWordRequest\x12N\n\x04type\x18\ + \x01\x20\x02(\x0e2:.hw.trezor.messages.management.WordRequest.WordReques\ + tTypeR\x04type\"f\n\x0fWordRequestType\x12\x19\n\x15WordRequestType_Plai\ + n\x10\0\x12\x1b\n\x17WordRequestType_Matrix9\x10\x01\x12\x1b\n\x17WordRe\ + questType_Matrix6\x10\x02\"\x1d\n\x07WordAck\x12\x12\n\x04word\x18\x01\ + \x20\x02(\tR\x04word\"0\n\rSetU2FCounter\x12\x1f\n\x0bu2f_counter\x18\ + \x01\x20\x02(\rR\nu2fCounter\"\x13\n\x11GetNextU2FCounter\"1\n\x0eNextU2\ + FCounter\x12\x1f\n\x0bu2f_counter\x18\x01\x20\x02(\rR\nu2fCounter\"\x11\ + \n\x0fDoPreauthorized\"\x16\n\x14PreauthorizedRequest\"\x15\n\x13CancelA\ + uthorization\"\x9a\x02\n\x12RebootToBootloader\x12o\n\x0cboot_command\ + \x18\x01\x20\x01(\x0e2=.hw.trezor.messages.management.RebootToBootloader\ + .BootCommand:\rSTOP_AND_WAITR\x0bbootCommand\x12'\n\x0ffirmware_header\ \x18\x02\x20\x01(\x0cR\x0efirmwareHeader\x123\n\x14language_data_length\ \x18\x03\x20\x01(\r:\x010R\x12languageDataLength\"5\n\x0bBootCommand\x12\ \x11\n\rSTOP_AND_WAIT\x10\0\x12\x13\n\x0fINSTALL_UPGRADE\x10\x01\"\x10\n\ @@ -10777,13 +10850,14 @@ pub fn file_descriptor() -> &'static ::protobuf::reflect::FileDescriptor { messages.push(ShowDeviceTutorial::generated_message_descriptor_data()); messages.push(UnlockBootloader::generated_message_descriptor_data()); messages.push(backup_device::Slip39Group::generated_message_descriptor_data()); - let mut enums = ::std::vec::Vec::with_capacity(8); + let mut enums = ::std::vec::Vec::with_capacity(9); enums.push(BackupType::generated_enum_descriptor_data()); enums.push(SafetyCheckLevel::generated_enum_descriptor_data()); enums.push(HomescreenFormat::generated_enum_descriptor_data()); enums.push(features::Capability::generated_enum_descriptor_data()); enums.push(sd_protect::SdProtectOperationType::generated_enum_descriptor_data()); enums.push(recovery_device::RecoveryDeviceType::generated_enum_descriptor_data()); + enums.push(recovery_device::RecoveryKind::generated_enum_descriptor_data()); enums.push(word_request::WordRequestType::generated_enum_descriptor_data()); enums.push(reboot_to_bootloader::BootCommand::generated_enum_descriptor_data()); ::protobuf::reflect::GeneratedFileDescriptor::new_generated( diff --git a/tests/device_tests/reset_recovery/test_recovery_bip39_dryrun.py b/tests/device_tests/reset_recovery/test_recovery_bip39_dryrun.py index 5e6494a76..9c834e843 100644 --- a/tests/device_tests/reset_recovery/test_recovery_bip39_dryrun.py +++ b/tests/device_tests/reset_recovery/test_recovery_bip39_dryrun.py @@ -131,6 +131,8 @@ def _make_bad_params(): yield field.name, True elif field.type == "string": yield field.name, "test" + elif field.type == "RecoveryKind": + yield field.name, 1 else: # Someone added a field to RecoveryDevice of a type that has no assigned # default value. This test must be fixed.