feat(core): implement repeated backup.

Ioan Bizău 4 weeks ago
parent b9424c12fc
commit 164bac20b0

@ -435,7 +435,7 @@ message RecoveryDevice {
// 7 reserved for unused recovery method // 7 reserved for unused recovery method
optional RecoveryDeviceType type = 8; // supported recovery type optional RecoveryDeviceType type = 8; // supported recovery type
optional uint32 u2f_counter = 9; // U2F counter 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., * Type of recovery procedure. These should be used as bitmask, e.g.,
* `RecoveryDeviceType_ScrambledWords | RecoveryDeviceType_Matrix` * `RecoveryDeviceType_ScrambledWords | RecoveryDeviceType_Matrix`
@ -449,6 +449,12 @@ message RecoveryDevice {
RecoveryDeviceType_ScrambledWords = 0; // words in scrambled order RecoveryDeviceType_ScrambledWords = 0; // words in scrambled order
RecoveryDeviceType_Matrix = 1; // matrix recovery type 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
}
} }
/** /**

@ -121,6 +121,8 @@ trezor.enums.PinMatrixRequestType
import trezor.enums.PinMatrixRequestType import trezor.enums.PinMatrixRequestType
trezor.enums.RecoveryDeviceType trezor.enums.RecoveryDeviceType
import trezor.enums.RecoveryDeviceType import trezor.enums.RecoveryDeviceType
trezor.enums.RecoveryKind
import trezor.enums.RecoveryKind
trezor.enums.RequestType trezor.enums.RequestType
import trezor.enums.RequestType import trezor.enums.RequestType
trezor.enums.SafetyCheckLevel trezor.enums.SafetyCheckLevel

@ -5,6 +5,7 @@ if TYPE_CHECKING:
async def backup_device(msg: BackupDevice) -> Success: async def backup_device(msg: BackupDevice) -> Success:
import storage.cache as storage_cache
import storage.device as storage_device import storage.device as storage_device
from trezor import wire from trezor import wire
from trezor.messages import Success from trezor.messages import Success
@ -15,7 +16,7 @@ async def backup_device(msg: BackupDevice) -> Success:
if not storage_device.is_initialized(): if not storage_device.is_initialized():
raise wire.NotInitialized("Device is not 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") raise wire.ProcessError("Seed already backed up")
mnemonic_secret, backup_type = mnemonic.get() 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_unfinished_backup(True)
storage_device.set_backed_up() storage_device.set_backed_up()
# TODO: clear APP_RECOVERY_REPEATED_BACKUP_UNLOCKED - here?
await backup_seed( await backup_seed(
backup_type, backup_type,
mnemonic_secret, mnemonic_secret,

@ -1,12 +1,14 @@
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from trezor.enums import RecoveryKind
if TYPE_CHECKING: if TYPE_CHECKING:
from trezor.messages import RecoveryDevice, Success from trezor.messages import RecoveryDevice, Success
# List of RecoveryDevice fields that can be set when doing dry-run recovery. # 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. # 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. # 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: 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 from .homescreen import recovery_homescreen, recovery_process
dry_run = msg.dry_run # local_cache_attribute recovery_kind = msg.kind # local_cache_attribute
# -------------------------------------------------------- # --------------------------------------------------------
# validate # validate
if not dry_run and storage_device.is_initialized(): if recovery_kind == RecoveryKind.NormalRecovery:
raise wire.UnexpectedMessage("Already initialized") if storage_device.is_initialized():
if dry_run and not storage_device.is_initialized(): raise wire.UnexpectedMessage("Already initialized")
raise wire.NotInitialized("Device is not initialized") elif recovery_kind == RecoveryKind.DryRun or recovery_kind == RecoveryKind.UnlockRepeatedBackup:
if msg.enforce_wordlist is False: if not storage_device.is_initialized():
raise wire.ProcessError( raise wire.NotInitialized("Device is not initialized")
"Value enforce_wordlist must be True, Trezor Core enforces words automatically."
) if recovery_kind == RecoveryKind.DryRun: # TODO: what about UnlockRepeatedBackup?
if dry_run:
# check that only allowed fields are set # check that only allowed fields are set
for key, value in msg.__dict__.items(): for key, value in msg.__dict__.items():
if key not in DRY_RUN_ALLOWED_FIELDS and value is not None: if key not in DRY_RUN_ALLOWED_FIELDS and value is not None:
raise wire.ProcessError(f"Forbidden field set in dry-run: {key}") 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 # END validate
# -------------------------------------------------------- # --------------------------------------------------------
if storage_recovery.is_in_progress(): if storage_recovery.is_in_progress():
return await recovery_process() return await recovery_process()
# -------------------------------------------------------- if recovery_kind == RecoveryKind.NormalRecovery:
# _continue_dialog
if not dry_run:
await confirm_reset_device(TR.recovery__title_recover, recovery=True) 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 # wipe storage to make sure the device is in a clear state
storage.reset() 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 # set up pin if requested
if msg.pin_protection: if msg.pin_protection:
newpin = await request_pin_confirm(allow_cancel=False) newpin = await request_pin_confirm(allow_cancel=False)
config.change_pin("", newpin, None, None) config.change_pin("", newpin, None, None)
storage_device.set_passphrase_enabled(bool(msg.passphrase_protection)) storage_device.set_passphrase_enabled(bool(msg.passphrase_protection))
if msg.u2f_counter is not None: if msg.u2f_counter is not None:
storage_device.set_u2f_counter(msg.u2f_counter) storage_device.set_u2f_counter(msg.u2f_counter)
if msg.label is not None: if msg.label is not None:
storage_device.set_label(msg.label) 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_in_progress(True)
storage_recovery.set_dry_run(bool(dry_run))
storage_recovery.set_kind(int(recovery_kind))
workflow.set_default(recovery_homescreen) workflow.set_default(recovery_homescreen)
return await recovery_process() return await recovery_process()

@ -1,5 +1,6 @@
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
import storage.cache as storage_cache
import storage.device as storage_device import storage.device as storage_device
import storage.recovery as storage_recovery import storage.recovery as storage_recovery
import storage.recovery_shares as storage_recovery_shares import storage.recovery_shares as storage_recovery_shares
@ -21,25 +22,40 @@ async def recovery_homescreen() -> None:
if not storage_recovery.is_in_progress(): if not storage_recovery.is_in_progress():
workflow.set_default(homescreen) workflow.set_default(homescreen)
return return
elif storage_cache.get(storage_cache.APP_RECOVERY_REPEATED_BACKUP_UNLOCKED) == b"\x01":
await recovery_process() await _continue_repeated_backup()
else:
await recovery_process()
async def recovery_process() -> Success: async def recovery_process() -> Success:
import storage import storage
from trezor.enums import MessageType 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: try:
return await _continue_recovery_process() return await _continue_recovery_process()
except recover.RecoveryAborted: except recover.RecoveryAborted:
dry_run = storage_recovery.is_dry_run() if is_special_kind:
if dry_run:
storage_recovery.end_progress() storage_recovery.end_progress()
else: else:
storage.wipe() storage.wipe()
raise wire.ActionCancelled 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: async def _continue_recovery_process() -> Success:
from trezor import utils from trezor import utils
@ -47,6 +63,7 @@ async def _continue_recovery_process() -> Success:
# gather the current recovery state from storage # gather the current recovery state from storage
dry_run = storage_recovery.is_dry_run() dry_run = storage_recovery.is_dry_run()
unlock_repeated_backup = storage_recovery.is_unlock_repeated_backup()
word_count, backup_type = recover.load_slip39_state() word_count, backup_type = recover.load_slip39_state()
# Both word_count and backup_type are derived from the same data. Both will be # 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 assert backup_type is not None
if dry_run: if dry_run:
result = await _finish_recovery_dry_run(secret, backup_type) 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: else:
result = await _finish_recovery(secret, backup_type) result = await _finish_recovery(secret, backup_type)

@ -34,7 +34,7 @@ APP_COMMON_REQUEST_PIN_LAST_UNLOCK = const(3 | _SESSIONLESS_FLAG)
APP_COMMON_BUSY_DEADLINE_MS = const(4 | _SESSIONLESS_FLAG) APP_COMMON_BUSY_DEADLINE_MS = const(4 | _SESSIONLESS_FLAG)
APP_MISC_COSI_NONCE = const(5 | _SESSIONLESS_FLAG) APP_MISC_COSI_NONCE = const(5 | _SESSIONLESS_FLAG)
APP_MISC_COSI_COMMITMENT = const(6 | _SESSIONLESS_FLAG) APP_MISC_COSI_COMMITMENT = const(6 | _SESSIONLESS_FLAG)
APP_RECOVERY_REPEATED_BACKUP_UNLOCKED = const(7 | _SESSIONLESS_FLAG)
# === Homescreen storage === # === Homescreen storage ===
# This does not logically belong to the "cache" functionality, but the cache module is # 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 8, # APP_COMMON_BUSY_DEADLINE_MS
32, # APP_MISC_COSI_NONCE 32, # APP_MISC_COSI_NONCE
32, # APP_MISC_COSI_COMMITMENT 32, # APP_MISC_COSI_COMMITMENT
1, # APP_RECOVERY_REPEATED_BACKUP_UNLOCKED
) )
super().__init__() super().__init__()

@ -1,6 +1,7 @@
from micropython import const from micropython import const
from storage import common from storage import common
from trezor.enums import RecoveryKind
# Namespace: # Namespace:
_NAMESPACE = common.APP_RECOVERY _NAMESPACE = common.APP_RECOVERY
@ -8,13 +9,14 @@ _NAMESPACE = common.APP_RECOVERY
# fmt: off # fmt: off
# Keys: # Keys:
_IN_PROGRESS = const(0x00) # bool _IN_PROGRESS = const(0x00) # bool
_DRY_RUN = const(0x01) # bool _KIND = const(0x01) # int
_SLIP39_IDENTIFIER = const(0x03) # bytes _SLIP39_IDENTIFIER = const(0x03) # bytes
_REMAINING = const(0x05) # int _REMAINING = const(0x05) # int
_SLIP39_ITERATION_EXPONENT = const(0x06) # int _SLIP39_ITERATION_EXPONENT = const(0x06) # int
_SLIP39_GROUP_COUNT = const(0x07) # int _SLIP39_GROUP_COUNT = const(0x07) # int
# Deprecated Keys: # Deprecated Keys:
# _DRY_RUN = const(0x01) # bool (got upgraded to int)
# _WORD_COUNT = const(0x02) # int # _WORD_COUNT = const(0x02) # int
# _SLIP39_THRESHOLD = const(0x04) # int # _SLIP39_THRESHOLD = const(0x04) # int
# fmt: on # fmt: on
@ -36,14 +38,19 @@ def is_in_progress() -> bool:
return common.get_bool(_NAMESPACE, _IN_PROGRESS) return common.get_bool(_NAMESPACE, _IN_PROGRESS)
def set_dry_run(val: bool) -> None: def set_kind(val: int) -> None:
_require_progress() _require_progress()
common.set_bool(_NAMESPACE, _DRY_RUN, val) common.set_uint8(_NAMESPACE, _KIND, val)
def is_dry_run() -> bool: def is_dry_run() -> bool:
_require_progress() _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: def set_slip39_identifier(identifier: int) -> None:
@ -128,7 +135,7 @@ def end_progress() -> None:
_require_progress() _require_progress()
for key in ( for key in (
_IN_PROGRESS, _IN_PROGRESS,
_DRY_RUN, _KIND,
_SLIP39_IDENTIFIER, _SLIP39_IDENTIFIER,
_REMAINING, _REMAINING,
_SLIP39_ITERATION_EXPONENT, _SLIP39_ITERATION_EXPONENT,

@ -0,0 +1,7 @@
# Automatically generated by pb2py
# fmt: off
# isort:skip_file
NormalRecovery = 0
DryRun = 1
UnlockRepeatedBackup = 2

@ -457,6 +457,11 @@ if TYPE_CHECKING:
ScrambledWords = 0 ScrambledWords = 0
Matrix = 1 Matrix = 1
class RecoveryKind(IntEnum):
NormalRecovery = 0
DryRun = 1
UnlockRepeatedBackup = 2
class WordRequestType(IntEnum): class WordRequestType(IntEnum):
Plain = 0 Plain = 0
Matrix9 = 1 Matrix9 = 1

@ -53,6 +53,7 @@ if TYPE_CHECKING:
from trezor.enums import OutputScriptType # noqa: F401 from trezor.enums import OutputScriptType # noqa: F401
from trezor.enums import PinMatrixRequestType # noqa: F401 from trezor.enums import PinMatrixRequestType # noqa: F401
from trezor.enums import RecoveryDeviceType # 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 RequestType # noqa: F401
from trezor.enums import SafetyCheckLevel # noqa: F401 from trezor.enums import SafetyCheckLevel # noqa: F401
from trezor.enums import SdProtectOperationType # noqa: F401 from trezor.enums import SdProtectOperationType # noqa: F401
@ -2569,7 +2570,7 @@ if TYPE_CHECKING:
enforce_wordlist: "bool | None" enforce_wordlist: "bool | None"
type: "RecoveryDeviceType | None" type: "RecoveryDeviceType | None"
u2f_counter: "int | None" u2f_counter: "int | None"
dry_run: "bool | None" kind: "RecoveryKind | None"
def __init__( def __init__(
self, self,
@ -2581,7 +2582,7 @@ if TYPE_CHECKING:
enforce_wordlist: "bool | None" = None, enforce_wordlist: "bool | None" = None,
type: "RecoveryDeviceType | None" = None, type: "RecoveryDeviceType | None" = None,
u2f_counter: "int | None" = None, u2f_counter: "int | None" = None,
dry_run: "bool | None" = None, kind: "RecoveryKind | None" = None,
) -> None: ) -> None:
pass pass

@ -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 computer one by one in random order). The process continues with
optional check of the seed validity and optional setting up the PIN, optional check of the seed validity and optional setting up the PIN,
which has to be confirmed. Finally the recovered wallet is saved into 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 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 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 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 device) or unsuccessful result(The seed is valid but does not match the

@ -151,6 +151,7 @@ def load(
"-t", "--type", "rec_type", type=ChoiceType(RECOVERY_TYPE), default="scrambled" "-t", "--type", "rec_type", type=ChoiceType(RECOVERY_TYPE), default="scrambled"
) )
@click.option("-d", "--dry-run", is_flag=True) @click.option("-d", "--dry-run", is_flag=True)
@click.option("-b", "--unlock-repeated-backup", is_flag=True)
@with_client @with_client
def recover( def recover(
client: "TrezorClient", client: "TrezorClient",
@ -162,6 +163,7 @@ def recover(
u2f_counter: int, u2f_counter: int,
rec_type: messages.RecoveryDeviceType, rec_type: messages.RecoveryDeviceType,
dry_run: bool, dry_run: bool,
unlock_repeated_backup: bool,
) -> "MessageType": ) -> "MessageType":
"""Start safe recovery workflow.""" """Start safe recovery workflow."""
if rec_type == messages.RecoveryDeviceType.ScrambledWords: if rec_type == messages.RecoveryDeviceType.ScrambledWords:
@ -180,6 +182,7 @@ def recover(
input_callback=input_callback, input_callback=input_callback,
type=rec_type, type=rec_type,
dry_run=dry_run, dry_run=dry_run,
unlock_repeated_backup=unlock_repeated_backup,
) )

@ -159,6 +159,7 @@ def recover(
input_callback: Optional[Callable] = None, input_callback: Optional[Callable] = None,
type: messages.RecoveryDeviceType = messages.RecoveryDeviceType.ScrambledWords, type: messages.RecoveryDeviceType = messages.RecoveryDeviceType.ScrambledWords,
dry_run: bool = False, dry_run: bool = False,
unlock_repeated_backup: bool = False,
u2f_counter: Optional[int] = None, u2f_counter: Optional[int] = None,
) -> "MessageType": ) -> "MessageType":
if language is not None: if language is not None:
@ -173,7 +174,7 @@ def recover(
if word_count not in (12, 18, 24): if word_count not in (12, 18, 24):
raise ValueError("Invalid word count. Use 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( raise RuntimeError(
"Device already initialized. Call device.wipe() and try again." "Device already initialized. Call device.wipe() and try again."
) )
@ -181,11 +182,22 @@ def recover(
if u2f_counter is None: if u2f_counter is None:
u2f_counter = int(time.time()) 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( 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 # set additional parameters
msg.passphrase_protection = passphrase_protection msg.passphrase_protection = passphrase_protection
msg.pin_protection = pin_protection msg.pin_protection = pin_protection

@ -491,6 +491,12 @@ class RecoveryDeviceType(IntEnum):
Matrix = 1 Matrix = 1
class RecoveryKind(IntEnum):
NormalRecovery = 0
DryRun = 1
UnlockRepeatedBackup = 2
class WordRequestType(IntEnum): class WordRequestType(IntEnum):
Plain = 0 Plain = 0
Matrix9 = 1 Matrix9 = 1
@ -3729,7 +3735,7 @@ class RecoveryDevice(protobuf.MessageType):
6: protobuf.Field("enforce_wordlist", "bool", repeated=False, required=False, default=None), 6: protobuf.Field("enforce_wordlist", "bool", repeated=False, required=False, default=None),
8: protobuf.Field("type", "RecoveryDeviceType", 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), 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__( def __init__(
@ -3743,7 +3749,7 @@ class RecoveryDevice(protobuf.MessageType):
enforce_wordlist: Optional["bool"] = None, enforce_wordlist: Optional["bool"] = None,
type: Optional["RecoveryDeviceType"] = None, type: Optional["RecoveryDeviceType"] = None,
u2f_counter: Optional["int"] = None, u2f_counter: Optional["int"] = None,
dry_run: Optional["bool"] = None, kind: Optional["RecoveryKind"] = None,
) -> None: ) -> None:
self.word_count = word_count self.word_count = word_count
self.passphrase_protection = passphrase_protection self.passphrase_protection = passphrase_protection
@ -3753,7 +3759,7 @@ class RecoveryDevice(protobuf.MessageType):
self.enforce_wordlist = enforce_wordlist self.enforce_wordlist = enforce_wordlist
self.type = type self.type = type
self.u2f_counter = u2f_counter self.u2f_counter = u2f_counter
self.dry_run = dry_run self.kind = kind
class WordRequest(protobuf.MessageType): class WordRequest(protobuf.MessageType):

@ -7645,8 +7645,8 @@ pub struct RecoveryDevice {
pub type_: ::std::option::Option<::protobuf::EnumOrUnknown<recovery_device::RecoveryDeviceType>>, pub type_: ::std::option::Option<::protobuf::EnumOrUnknown<recovery_device::RecoveryDeviceType>>,
// @@protoc_insertion_point(field:hw.trezor.messages.management.RecoveryDevice.u2f_counter) // @@protoc_insertion_point(field:hw.trezor.messages.management.RecoveryDevice.u2f_counter)
pub u2f_counter: ::std::option::Option<u32>, pub u2f_counter: ::std::option::Option<u32>,
// @@protoc_insertion_point(field:hw.trezor.messages.management.RecoveryDevice.dry_run) // @@protoc_insertion_point(field:hw.trezor.messages.management.RecoveryDevice.kind)
pub dry_run: ::std::option::Option<bool>, pub kind: ::std::option::Option<::protobuf::EnumOrUnknown<recovery_device::RecoveryKind>>,
// special fields // special fields
// @@protoc_insertion_point(special_field:hw.trezor.messages.management.RecoveryDevice.special_fields) // @@protoc_insertion_point(special_field:hw.trezor.messages.management.RecoveryDevice.special_fields)
pub special_fields: ::protobuf::SpecialFields, pub special_fields: ::protobuf::SpecialFields,
@ -7852,23 +7852,26 @@ impl RecoveryDevice {
self.u2f_counter = ::std::option::Option::Some(v); 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 { pub fn kind(&self) -> recovery_device::RecoveryKind {
self.dry_run.unwrap_or(false) 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) { pub fn clear_kind(&mut self) {
self.dry_run = ::std::option::Option::None; self.kind = ::std::option::Option::None;
} }
pub fn has_dry_run(&self) -> bool { pub fn has_kind(&self) -> bool {
self.dry_run.is_some() self.kind.is_some()
} }
// Param is passed by value, moved // Param is passed by value, moved
pub fn set_dry_run(&mut self, v: bool) { pub fn set_kind(&mut self, v: recovery_device::RecoveryKind) {
self.dry_run = ::std::option::Option::Some(v); self.kind = ::std::option::Option::Some(::protobuf::EnumOrUnknown::new(v));
} }
fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData {
@ -7915,9 +7918,9 @@ impl RecoveryDevice {
|m: &mut RecoveryDevice| { &mut m.u2f_counter }, |m: &mut RecoveryDevice| { &mut m.u2f_counter },
)); ));
fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>(
"dry_run", "kind",
|m: &RecoveryDevice| { &m.dry_run }, |m: &RecoveryDevice| { &m.kind },
|m: &mut RecoveryDevice| { &mut m.dry_run }, |m: &mut RecoveryDevice| { &mut m.kind },
)); ));
::protobuf::reflect::GeneratedMessageDescriptorData::new_2::<RecoveryDevice>( ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::<RecoveryDevice>(
"RecoveryDevice", "RecoveryDevice",
@ -7962,7 +7965,7 @@ impl ::protobuf::Message for RecoveryDevice {
self.u2f_counter = ::std::option::Option::Some(is.read_uint32()?); self.u2f_counter = ::std::option::Option::Some(is.read_uint32()?);
}, },
80 => { 80 => {
self.dry_run = ::std::option::Option::Some(is.read_bool()?); self.kind = ::std::option::Option::Some(is.read_enum_or_unknown()?);
}, },
tag => { tag => {
::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; ::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 { if let Some(v) = self.u2f_counter {
my_size += ::protobuf::rt::uint32_size(9, v); my_size += ::protobuf::rt::uint32_size(9, v);
} }
if let Some(v) = self.dry_run { if let Some(v) = self.kind {
my_size += 1 + 1; my_size += ::protobuf::rt::int32_size(10, v.value());
} }
my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields());
self.special_fields.cached_size().set(my_size as u32); 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 { if let Some(v) = self.u2f_counter {
os.write_uint32(9, v)?; os.write_uint32(9, v)?;
} }
if let Some(v) = self.dry_run { if let Some(v) = self.kind {
os.write_bool(10, v)?; os.write_enum(10, ::protobuf::EnumOrUnknown::value(&v))?;
} }
os.write_unknown_fields(self.special_fields.unknown_fields())?; os.write_unknown_fields(self.special_fields.unknown_fields())?;
::std::result::Result::Ok(()) ::std::result::Result::Ok(())
@ -8061,7 +8064,7 @@ impl ::protobuf::Message for RecoveryDevice {
self.enforce_wordlist = ::std::option::Option::None; self.enforce_wordlist = ::std::option::Option::None;
self.type_ = ::std::option::Option::None; self.type_ = ::std::option::Option::None;
self.u2f_counter = ::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(); self.special_fields.clear();
} }
@ -8075,7 +8078,7 @@ impl ::protobuf::Message for RecoveryDevice {
enforce_wordlist: ::std::option::Option::None, enforce_wordlist: ::std::option::Option::None,
type_: ::std::option::Option::None, type_: ::std::option::Option::None,
u2f_counter: ::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(), special_fields: ::protobuf::SpecialFields::new(),
}; };
&instance &instance
@ -8162,6 +8165,73 @@ pub mod recovery_device {
::protobuf::reflect::GeneratedEnumDescriptorData::new::<RecoveryDeviceType>("RecoveryDevice.RecoveryDeviceType") ::protobuf::reflect::GeneratedEnumDescriptorData::new::<RecoveryDeviceType>("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<RecoveryKind> {
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<RecoveryKind> {
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::<RecoveryKind>("RecoveryDevice.RecoveryKind")
}
}
} }
// @@protoc_insertion_point(message:hw.trezor.messages.management.WordRequest) // @@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\ \n\x10member_threshold\x18\x01\x20\x02(\rR\x0fmemberThreshold\x12!\n\x0c\
member_count\x18\x02\x20\x02(\rR\x0bmemberCount\"\x10\n\x0eEntropyReques\ member_count\x18\x02\x20\x02(\rR\x0bmemberCount\"\x10\n\x0eEntropyReques\
t\"&\n\nEntropyAck\x12\x18\n\x07entropy\x18\x01\x20\x02(\x0cR\x07entropy\ 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\ \twordCount\x123\n\x15passphrase_protection\x18\x02\x20\x01(\x08R\x14pas\
sphraseProtection\x12%\n\x0epin_protection\x18\x03\x20\x01(\x08R\rpinPro\ sphraseProtection\x12%\n\x0epin_protection\x18\x03\x20\x01(\x08R\rpinPro\
tection\x12\x1e\n\x08language\x18\x04\x20\x01(\tR\x08languageB\x02\x18\ 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\ ordlist\x18\x06\x20\x01(\x08R\x0fenforceWordlist\x12T\n\x04type\x18\x08\
\x20\x01(\x0e2@.hw.trezor.messages.management.RecoveryDevice.RecoveryDev\ \x20\x01(\x0e2@.hw.trezor.messages.management.RecoveryDevice.RecoveryDev\
iceTypeR\x04type\x12\x1f\n\x0bu2f_counter\x18\t\x20\x01(\rR\nu2fCounter\ 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\ \x12N\n\x04kind\x18\n\x20\x01(\x0e2:.hw.trezor.messages.management.Recov\
ceType\x12%\n!RecoveryDeviceType_ScrambledWords\x10\0\x12\x1d\n\x19Recov\ eryDevice.RecoveryKindR\x04kind\"Z\n\x12RecoveryDeviceType\x12%\n!Recove\
eryDeviceType_Matrix\x10\x01\"\xc5\x01\n\x0bWordRequest\x12N\n\x04type\ ryDeviceType_ScrambledWords\x10\0\x12\x1d\n\x19RecoveryDeviceType_Matrix\
\x18\x01\x20\x02(\x0e2:.hw.trezor.messages.management.WordRequest.WordRe\ \x10\x01\"o\n\x0cRecoveryKind\x12\x1f\n\x1bRecoveryKind_NormalRecovery\
questTypeR\x04type\"f\n\x0fWordRequestType\x12\x19\n\x15WordRequestType_\ \x10\0\x12\x17\n\x13RecoveryKind_DryRun\x10\x01\x12%\n!RecoveryKind_Unlo\
Plain\x10\0\x12\x1b\n\x17WordRequestType_Matrix9\x10\x01\x12\x1b\n\x17Wo\ ckRepeatedBackup\x10\x02\"\xc5\x01\n\x0bWordRequest\x12N\n\x04type\x18\
rdRequestType_Matrix6\x10\x02\"\x1d\n\x07WordAck\x12\x12\n\x04word\x18\ \x01\x20\x02(\x0e2:.hw.trezor.messages.management.WordRequest.WordReques\
\x01\x20\x02(\tR\x04word\"0\n\rSetU2FCounter\x12\x1f\n\x0bu2f_counter\ tTypeR\x04type\"f\n\x0fWordRequestType\x12\x19\n\x15WordRequestType_Plai\
\x18\x01\x20\x02(\rR\nu2fCounter\"\x13\n\x11GetNextU2FCounter\"1\n\x0eNe\ n\x10\0\x12\x1b\n\x17WordRequestType_Matrix9\x10\x01\x12\x1b\n\x17WordRe\
xtU2FCounter\x12\x1f\n\x0bu2f_counter\x18\x01\x20\x02(\rR\nu2fCounter\"\ questType_Matrix6\x10\x02\"\x1d\n\x07WordAck\x12\x12\n\x04word\x18\x01\
\x11\n\x0fDoPreauthorized\"\x16\n\x14PreauthorizedRequest\"\x15\n\x13Can\ \x20\x02(\tR\x04word\"0\n\rSetU2FCounter\x12\x1f\n\x0bu2f_counter\x18\
celAuthorization\"\x9a\x02\n\x12RebootToBootloader\x12o\n\x0cboot_comman\ \x01\x20\x02(\rR\nu2fCounter\"\x13\n\x11GetNextU2FCounter\"1\n\x0eNextU2\
d\x18\x01\x20\x01(\x0e2=.hw.trezor.messages.management.RebootToBootloade\ FCounter\x12\x1f\n\x0bu2f_counter\x18\x01\x20\x02(\rR\nu2fCounter\"\x11\
r.BootCommand:\rSTOP_AND_WAITR\x0bbootCommand\x12'\n\x0ffirmware_header\ \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\x02\x20\x01(\x0cR\x0efirmwareHeader\x123\n\x14language_data_length\
\x18\x03\x20\x01(\r:\x010R\x12languageDataLength\"5\n\x0bBootCommand\x12\ \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\ \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(ShowDeviceTutorial::generated_message_descriptor_data());
messages.push(UnlockBootloader::generated_message_descriptor_data()); messages.push(UnlockBootloader::generated_message_descriptor_data());
messages.push(backup_device::Slip39Group::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(BackupType::generated_enum_descriptor_data());
enums.push(SafetyCheckLevel::generated_enum_descriptor_data()); enums.push(SafetyCheckLevel::generated_enum_descriptor_data());
enums.push(HomescreenFormat::generated_enum_descriptor_data()); enums.push(HomescreenFormat::generated_enum_descriptor_data());
enums.push(features::Capability::generated_enum_descriptor_data()); enums.push(features::Capability::generated_enum_descriptor_data());
enums.push(sd_protect::SdProtectOperationType::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::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(word_request::WordRequestType::generated_enum_descriptor_data());
enums.push(reboot_to_bootloader::BootCommand::generated_enum_descriptor_data()); enums.push(reboot_to_bootloader::BootCommand::generated_enum_descriptor_data());
::protobuf::reflect::GeneratedFileDescriptor::new_generated( ::protobuf::reflect::GeneratedFileDescriptor::new_generated(

@ -131,6 +131,8 @@ def _make_bad_params():
yield field.name, True yield field.name, True
elif field.type == "string": elif field.type == "string":
yield field.name, "test" yield field.name, "test"
elif field.type == "RecoveryKind":
yield field.name, 1
else: else:
# Someone added a field to RecoveryDevice of a type that has no assigned # Someone added a field to RecoveryDevice of a type that has no assigned
# default value. This test must be fixed. # default value. This test must be fixed.

Loading…
Cancel
Save