async def _finish_recovery_dry_run(ctx: wire.GenericContext, secret: bytes, backup_type: BackupType) -> Success: if backup_type is None: raise RuntimeError digest_input = sha256(secret).digest() stored = mnemonic.get_secret() digest_stored = sha256(stored).digest() result = utils.consteq(digest_stored, digest_input) is_slip39 = backup_types.is_slip39_backup_type(backup_type) # Check that the identifier and iteration exponent match as well if is_slip39: result &= (storage.device.get_slip39_identifier() == storage.recovery.get_slip39_identifier()) result &= (storage.device.get_slip39_iteration_exponent() == storage.recovery.get_slip39_iteration_exponent()) storage.recovery.end_progress() await layout.show_dry_run_result(ctx, result, is_slip39) if result: return Success( message="The seed is valid and matches the one in the device") else: raise wire.ProcessError( "The seed does not match the one in the device")
async def get_firmware(ctx: Context, _msg: GetFirmware) -> Success: await confirm_action( ctx, "dump_firmware", title="Extract firmware", action="Do you want to extract device firmware?", description="Your seed will not be revealed.", ) sector_buffer = bytearray(CHUNK_SIZE) packet = FirmwareChunk(chunk=sector_buffer) workflow.close_others() draw_simple_text("Please wait") progress = 0 _render_progress(progress, PROGRESS_TOTAL) for i in range(utils.FIRMWARE_SECTORS_COUNT): size = utils.firmware_sector_size(i) try: for ofs in range(0, size, CHUNK_SIZE): utils.get_firmware_chunk(i, ofs, sector_buffer) await ctx.call(packet, FirmwareChunkAck) progress += CHUNK_SIZE _render_progress(progress, PROGRESS_TOTAL) # reset progress to known point, in case some sectors are not 128 kB progress = (i + 1) * 128 * 1024 _render_progress(progress, PROGRESS_TOTAL) except ValueError: raise wire.DataError("Failed to dump firmware.") return Success(message="Firmware dumped.")
async def handle_CancelAuthorization( ctx: wire.Context, msg: CancelAuthorization ) -> protobuf.MessageType: from apps.common import authorization authorization.clear() return Success(message="Authorization cancelled")
async def sd_protect_disable(ctx: wire.Context, msg: SdProtect) -> Success: if not storage.sd_salt.is_enabled(): raise wire.ProcessError("SD card protection not enabled") # Note that the SD card doesn't need to be present in order to disable SD # protection. The cleanup will not happen in such case, but that does not matter. # Confirm that user wants to proceed with the operation. await require_confirm_sd_protect(ctx, msg) # Get the current PIN and salt from the SD card. pin, salt = await request_pin_and_sd_salt(ctx, "Enter PIN") # Check PIN and remove salt. if not config.change_pin(pin, pin, salt, None): await error_pin_invalid(ctx) storage.device.set_sd_salt_auth_key(None) try: # Clean up. storage.sd_salt.remove_sd_salt() except Exception: # The cleanup is not necessary for the correct functioning of # SD-protection. If it fails for any reason, we suppress the exception, # because overall SD-protection was successfully disabled. pass await show_success(ctx, "success_sd", "You have successfully disabled SD protection.") return Success(message="SD card protection disabled")
async def add_resident_credential( ctx: wire.Context, msg: WebAuthnAddResidentCredential) -> Success: if not storage.device.is_initialized(): raise wire.NotInitialized("Device is not initialized") if not msg.credential_id: raise wire.ProcessError("Missing credential ID parameter.") try: cred = Fido2Credential.from_cred_id(bytes(msg.credential_id), None) except Exception: await show_error_and_raise( ctx, "warning_credential", header="Import credential", button="Close", content= "The credential you are trying to import does\nnot belong to this authenticator.", red=True, ) if not await confirm_webauthn(ctx, ConfirmAddCredential(cred)): raise wire.ActionCancelled if store_resident_credential(cred): return Success(message="Credential added") else: raise wire.ProcessError("Internal credential storage is full.")
async def dispatch_DebugLinkReseedRandom( ctx: wire.Context, msg: DebugLinkReseedRandom) -> Success: if msg.value is not None: from trezor.crypto import random random.reseed(msg.value) return Success()
async def sd_protect_refresh(ctx: wire.Context, msg: SdProtect) -> Success: if not storage.sd_salt.is_enabled(): raise wire.ProcessError("SD card protection not enabled") # Confirm that user wants to proceed with the operation. await require_confirm_sd_protect(ctx, msg) # Make sure SD card is present. await ensure_sdcard(ctx) # Get the current PIN and salt from the SD card. pin, old_salt = await request_pin_and_sd_salt(ctx, "Enter PIN") # Check PIN and change salt. new_salt, new_auth_key, new_salt_tag = _make_salt() await _set_salt(ctx, new_salt, new_salt_tag, stage=True) if not config.change_pin(pin, pin, old_salt, new_salt): await error_pin_invalid(ctx) storage.device.set_sd_salt_auth_key(new_auth_key) try: # Clean up. storage.sd_salt.commit_sd_salt() except Exception: # If the cleanup fails, then request_sd_salt() will bring the SD card # into a consistent state. We suppress the exception, because overall # SD-protection was successfully refreshed. pass await show_success(ctx, "success_sd", "You have successfully refreshed SD protection.") return Success(message="SD card protection refreshed")
async def verify_message(ctx: Context, msg: EthereumVerifyMessage) -> Success: digest = message_digest(msg.message) if len(msg.signature) != 65: raise wire.DataError("Invalid signature") sig = bytearray([msg.signature[64]]) + msg.signature[:64] pubkey = secp256k1.verify_recover(sig, digest) if not pubkey: raise wire.DataError("Invalid signature") pkh = sha3_256(pubkey[1:], keccak=True).digest()[-20:] address_bytes = bytes_from_address(msg.address) if address_bytes != pkh: raise wire.DataError("Invalid signature") address = address_from_bytes(address_bytes) await confirm_signverify(ctx, "ETH", decode_message(msg.message), address=address, verify=True) return Success(message="Message verified")
async def handle_Ping(ctx: wire.Context, msg: Ping) -> Success: if msg.button_protection: from trezor.ui.layouts import confirm_action from trezor.enums import ButtonRequestType as B await confirm_action(ctx, "ping", "Confirm", "ping", br_code=B.ProtectCall) return Success(message=msg.message)
async def authorize_coinjoin(ctx: wire.Context, msg: AuthorizeCoinJoin, keychain: Keychain, coin: CoinInfo) -> Success: if len(msg.coordinator) > _MAX_COORDINATOR_LEN or not all( 32 <= ord(x) <= 126 for x in msg.coordinator): raise wire.DataError("Invalid coordinator name.") if msg.max_rounds > _MAX_ROUNDS and safety_checks.is_strict(): raise wire.DataError("The number of rounds is unexpectedly large.") if (msg.max_coordinator_fee_rate > _MAX_COORDINATOR_FEE_RATE and safety_checks.is_strict()): raise wire.DataError( "The coordination fee rate is unexpectedly large.") if msg.max_fee_per_kvbyte > 10 * coin.maxfee_kb and safety_checks.is_strict( ): raise wire.DataError("The fee per vbyte is unexpectedly large.") if not msg.address_n: raise wire.DataError("Empty path not allowed.") await confirm_action( ctx, "coinjoin_coordinator", title="Authorize CoinJoin", description= "Do you really want to take part in a CoinJoin transaction at:\n{}", description_param=msg.coordinator, description_param_font=ui.MONO, icon=ui.ICON_RECOVERY, ) max_fee_per_vbyte = format_amount(msg.max_fee_per_kvbyte, 3) await confirm_coinjoin(ctx, coin.coin_name, msg.max_rounds, max_fee_per_vbyte) validation_path = msg.address_n + [0] * BIP32_WALLET_DEPTH await validate_path( ctx, keychain, validation_path, validate_path_against_script_type(coin, address_n=validation_path, script_type=msg.script_type), ) if msg.max_fee_per_kvbyte > coin.maxfee_kb: await confirm_metadata( ctx, "fee_over_threshold", "High mining fee", "The mining fee of\n{} sats/vbyte\nis unexpectedly high.", max_fee_per_vbyte, ButtonRequestType.FeeOverThreshold, ) authorization.set(msg) return Success(message="CoinJoin authorized")
async def dispatch_DebugLinkWatchLayout( ctx: wire.Context, msg: DebugLinkWatchLayout ) -> Success: from trezor import ui layout_change_chan.putters.clear() await ui.wait_until_layout_is_running() storage.watch_layout_changes = bool(msg.watch) log.debug(__name__, "Watch layout changes: %s", storage.watch_layout_changes) return Success()
async def dispatch_DebugLinkRecordScreen( ctx: wire.Context, msg: DebugLinkRecordScreen) -> Success: if msg.target_directory: storage.save_screen_directory = msg.target_directory storage.save_screen = True else: storage.save_screen = False display.clear_save() # clear C buffers return Success()
async def verify_message(ctx: wire.Context, msg: VerifyMessage) -> Success: message = msg.message address = msg.address signature = msg.signature coin_name = msg.coin_name or "Bitcoin" coin = coins.by_name(coin_name) digest = message_digest(coin, message) recid = signature[0] if 27 <= recid <= 34: # p2pkh script_type = InputScriptType.SPENDADDRESS elif 35 <= recid <= 38: # segwit-in-p2sh script_type = InputScriptType.SPENDP2SHWITNESS signature = bytes([signature[0] - 4]) + signature[1:] elif 39 <= recid <= 42: # native segwit script_type = InputScriptType.SPENDWITNESS signature = bytes([signature[0] - 8]) + signature[1:] else: raise wire.ProcessError("Invalid signature") pubkey = secp256k1.verify_recover(signature, digest) if not pubkey: raise wire.ProcessError("Invalid signature") if script_type == InputScriptType.SPENDADDRESS: addr = address_pkh(pubkey, coin) if coin.cashaddr_prefix is not None: addr = address_to_cashaddr(addr, coin) elif script_type == InputScriptType.SPENDP2SHWITNESS: addr = address_p2wpkh_in_p2sh(pubkey, coin) elif script_type == InputScriptType.SPENDWITNESS: addr = address_p2wpkh(pubkey, coin) else: raise wire.ProcessError("Invalid signature") if addr != address: raise wire.ProcessError("Invalid signature") await confirm_signverify( ctx, coin.coin_shortcut, decode_message(message), address=address_short(coin, address), verify=True, ) return Success(message="Message verified")
async def verify_message(ctx, msg): digest = message_digest(msg.message) verified = ed25519.verify(msg.public_key, msg.signature, digest) if not verified: raise wire.ProcessError("Invalid signature") address = get_address_from_public_key(msg.public_key) await confirm_signverify(ctx, "Lisk", decode_message(msg.message), address=address) return Success(message="Message verified")
async def set_u2f_counter(ctx: wire.Context, msg: SetU2FCounter) -> Success: if not storage.device.is_initialized(): raise wire.NotInitialized("Device is not initialized") if msg.u2f_counter is None: raise wire.ProcessError("No value provided") text = Text("Set U2F counter", ui.ICON_CONFIG) text.normal("Do you really want to", "set the U2F counter") text.bold("to %d?" % msg.u2f_counter) await require_confirm(ctx, text, code=ButtonRequestType.ProtectCall) storage.device.set_u2f_counter(msg.u2f_counter) return Success(message="U2F counter set")
async def remove_resident_credential( ctx: wire.Context, msg: WebAuthnRemoveResidentCredential) -> Success: if not storage.device.is_initialized(): raise wire.NotInitialized("Device is not initialized") if msg.index is None: raise wire.ProcessError("Missing credential index parameter.") cred = get_resident_credential(msg.index) if cred is None: raise wire.ProcessError("Invalid credential index.") if not await confirm_webauthn(ctx, ConfirmRemoveCredential(cred)): raise wire.ActionCancelled assert cred.index is not None storage.resident_credentials.delete(cred.index) return Success(message="Credential removed")
async def authorize_coinjoin(ctx: wire.Context, msg: AuthorizeCoinJoin, keychain: Keychain, coin: CoinInfo) -> Success: if len(msg.coordinator) > _MAX_COORDINATOR_LEN or not all( 32 <= ord(x) <= 126 for x in msg.coordinator): raise wire.DataError("Invalid coordinator name.") if not msg.address_n: raise wire.DataError("Empty path not allowed.") validation_path = msg.address_n + [0] * BIP32_WALLET_DEPTH await validate_path( ctx, keychain, validation_path, validate_path_against_script_type(coin, address_n=validation_path, script_type=msg.script_type), ) await confirm_action( ctx, "coinjoin_coordinator", title="Authorize CoinJoin", description= "Do you really want to take part in a CoinJoin transaction at:\n{}", description_param=msg.coordinator, description_param_font=ui.MONO, icon=ui.ICON_RECOVERY, ) if msg.fee_per_anonymity: fee_per_anonymity: str | None = format_amount( msg.fee_per_anonymity, FEE_PER_ANONYMITY_DECIMALS) else: fee_per_anonymity = None await confirm_coinjoin( ctx, fee_per_anonymity, format_coin_amount(msg.max_total_fee, coin, msg.amount_unit), ) authorization.set(msg) return Success(message="CoinJoin authorized")
async def reboot_to_bootloader(ctx: wire.Context, msg: RebootToBootloader) -> NoReturn: if not storage.device.get_experimental_features(): raise wire.UnexpectedMessage("Experimental features are not enabled") await confirm_action( ctx, "reboot", "Go to bootloader", "Do you want to restart Trezor in bootloader mode?", hold_danger=True, verb="Restart", ) await ctx.write(Success(message="Rebooting")) # make sure the outgoing USB buffer is flushed await loop.wait(ctx.iface.iface_num() | io.POLL_WRITE) utils.reboot_to_bootloader() raise RuntimeError
async def backup_device(ctx, msg): if not storage.device.is_initialized(): raise wire.NotInitialized("Device is not initialized") if not storage.device.needs_backup(): raise wire.ProcessError("Seed already backed up") mnemonic_secret, mnemonic_type = mnemonic.get() storage.device.set_unfinished_backup(True) storage.device.set_backed_up() await backup_seed(ctx, mnemonic_type, mnemonic_secret) storage.device.set_unfinished_backup(False) await layout.show_backup_success(ctx) return Success(message="Seed successfully backed up")
async def set_u2f_counter(ctx: wire.Context, msg: SetU2FCounter) -> Success: if not storage.device.is_initialized(): raise wire.NotInitialized("Device is not initialized") if msg.u2f_counter is None: raise wire.ProcessError("No value provided") await confirm_action( ctx, "set_u2f_counter", title="Set U2F counter", description="Do you really want to\nset the U2F counter\nto {}?", description_param=str(msg.u2f_counter), icon=ui.ICON_CONFIG, br_code=ButtonRequestType.ProtectCall, ) storage.device.set_u2f_counter(msg.u2f_counter) return Success(message="U2F counter set")
async def wipe_device(ctx, msg): await confirm_action( ctx, "confirm_wipe", title="Wipe device", description="Do you really want to\nwipe the device?\n", action="All data will be lost.", reverse=True, verb="Hold to confirm", hold=True, hold_danger=True, icon=ui.ICON_WIPE, icon_color=ui.RED, br_code=ButtonRequestType.WipeDevice, ) storage.wipe() reload_settings_from_storage() return Success(message="Device wiped")
async def dispatch_DebugLinkEraseSdCard( ctx: wire.Context, msg: DebugLinkEraseSdCard) -> Success: from trezor import io try: io.sdcard.power_on() if msg.format: io.fatfs.mkfs() else: # trash first 1 MB of data to destroy the FAT filesystem assert io.sdcard.capacity() >= 1024 * 1024 empty_block = bytes([0xFF] * io.sdcard.BLOCK_SIZE) for i in range(1024 * 1024 // io.sdcard.BLOCK_SIZE): io.sdcard.write(i, empty_block) except OSError: raise wire.ProcessError("SD card operation failed") finally: io.sdcard.power_off() return Success()
async def load_device(ctx, msg): word_count = _validate(msg) is_slip39 = backup_types.is_slip39_word_count(word_count) if not is_slip39 and not msg.skip_checksum and not bip39.check( msg.mnemonics[0]): raise wire.ProcessError("Mnemonic is not valid") await _warn(ctx) if not is_slip39: # BIP-39 secret = msg.mnemonics[0].encode() backup_type = BackupType.Bip39 else: identifier, iteration_exponent, secret = slip39.recover_ems( msg.mnemonics) # this must succeed if the recover_ems call succeeded share = slip39.decode_mnemonic(msg.mnemonics[0]) if share.group_count == 1: backup_type = BackupType.Slip39_Basic elif share.group_count > 1: backup_type = BackupType.Slip39_Advanced else: raise wire.ProcessError("Invalid group count") storage.device.set_slip39_identifier(identifier) storage.device.set_slip39_iteration_exponent(iteration_exponent) storage.device.store_mnemonic_secret( secret, backup_type, needs_backup=msg.needs_backup is True, no_backup=msg.no_backup is True, ) storage.device.set_passphrase_enabled(msg.passphrase_protection) storage.device.set_label(msg.label or "") if msg.pin: config.change_pin("", msg.pin, None, None) return Success(message="Device loaded")
async def change_pin(ctx: wire.Context, msg: ChangePin) -> Success: if not is_initialized(): raise wire.NotInitialized("Device is not initialized") # confirm that user wants to change the pin await require_confirm_change_pin(ctx, msg) # get old pin curpin, salt = await request_pin_and_sd_salt(ctx, "Enter old PIN") # if changing pin, pre-check the entered pin before getting new pin if curpin and not msg.remove: if not config.check_pin(curpin, salt): await error_pin_invalid(ctx) # get new pin if not msg.remove: newpin = await request_pin_confirm(ctx) else: newpin = "" # write into storage if not config.change_pin(curpin, newpin, salt, salt): if newpin: await error_pin_matches_wipe_code(ctx) else: await error_pin_invalid(ctx) if newpin: if curpin: msg_screen = "You have successfully changed your PIN." msg_wire = "PIN changed" else: msg_screen = "You have successfully enabled PIN protection." msg_wire = "PIN enabled" else: msg_screen = "You have successfully disabled PIN protection." msg_wire = "PIN removed" await show_success(ctx, "success_pin", msg_screen) return Success(message=msg_wire)
async def change_wipe_code(ctx: wire.Context, msg: ChangeWipeCode) -> Success: if not is_initialized(): raise wire.NotInitialized("Device is not initialized") # Confirm that user wants to set or remove the wipe code. has_wipe_code = config.has_wipe_code() await _require_confirm_action(ctx, msg, has_wipe_code) # Get the unlocking PIN. pin, salt = await request_pin_and_sd_salt(ctx) if not msg.remove: # Pre-check the entered PIN. if config.has_pin() and not config.check_pin(pin, salt): await error_pin_invalid(ctx) # Get new wipe code. wipe_code = await _request_wipe_code_confirm(ctx, pin) else: wipe_code = "" # Write into storage. if not config.change_wipe_code(pin, salt, wipe_code): await error_pin_invalid(ctx) if wipe_code: if has_wipe_code: msg_screen = "You have successfully changed the wipe code." msg_wire = "Wipe code changed" else: msg_screen = "You have successfully set the wipe code." msg_wire = "Wipe code set" else: msg_screen = "You have successfully disabled the wipe code." msg_wire = "Wipe code removed" await show_success(ctx, "success_wipe_code", msg_screen) return Success(message=msg_wire)
async def _finish_recovery(ctx: wire.GenericContext, secret: bytes, backup_type: BackupType) -> Success: if backup_type is None: raise RuntimeError storage.device.store_mnemonic_secret(secret, backup_type, needs_backup=False, no_backup=False) if backup_type in (BackupType.Slip39_Basic, BackupType.Slip39_Advanced): identifier = storage.recovery.get_slip39_identifier() exponent = storage.recovery.get_slip39_iteration_exponent() if identifier is None or exponent is None: # Identifier and exponent need to be stored in storage at this point raise RuntimeError storage.device.set_slip39_identifier(identifier) storage.device.set_slip39_iteration_exponent(exponent) storage.recovery.end_progress() await show_success(ctx, "success_recovery", "You have successfully recovered your wallet.") return Success(message="Device recovered")
async def sd_protect_enable(ctx: wire.Context, msg: SdProtect) -> Success: if storage.sd_salt.is_enabled(): raise wire.ProcessError("SD card protection already enabled") # Confirm that user wants to proceed with the operation. await require_confirm_sd_protect(ctx, msg) # Make sure SD card is present. await ensure_sdcard(ctx) # Get the current PIN. if config.has_pin(): pin = await request_pin(ctx, "Enter PIN", config.get_pin_rem()) else: pin = "" # Check PIN and prepare salt file. salt, salt_auth_key, salt_tag = _make_salt() await _set_salt(ctx, salt, salt_tag) if not config.change_pin(pin, pin, None, salt): # Wrong PIN. Clean up the prepared salt file. try: storage.sd_salt.remove_sd_salt() except Exception: # The cleanup is not necessary for the correct functioning of # SD-protection. If it fails for any reason, we suppress the # exception, because primarily we need to raise wire.PinInvalid. pass await error_pin_invalid(ctx) storage.device.set_sd_salt_auth_key(salt_auth_key) await show_success(ctx, "success_sd", "You have successfully enabled SD protection.") return Success(message="SD card protection enabled")
async def handle_EndSession(ctx: wire.Context, msg: EndSession) -> Success: storage.cache.end_current_session() return Success()
async def reset_device(ctx: wire.Context, msg: ResetDevice) -> Success: # validate parameters and device state _validate_reset_device(msg) # make sure user knows they're setting up a new wallet if msg.backup_type == BackupType.Slip39_Basic: prompt = "Create a new wallet\nwith Shamir Backup?" elif msg.backup_type == BackupType.Slip39_Advanced: prompt = "Create a new wallet\nwith Super Shamir?" else: prompt = "Do you want to create\na new wallet?" await confirm_reset_device(ctx, prompt) await LoadingAnimation() # wipe storage to make sure the device is in a clear state storage.reset() # request and set new PIN if msg.pin_protection: newpin = await request_pin_confirm(ctx) if not config.change_pin("", newpin, None, None): raise wire.ProcessError("Failed to set PIN") # generate and display internal entropy int_entropy = random.bytes(32) if __debug__: storage.debug.reset_internal_entropy = int_entropy if msg.display_random: await layout.show_internal_entropy(ctx, int_entropy) # request external entropy and compute the master secret entropy_ack = await ctx.call(EntropyRequest(), EntropyAck) ext_entropy = entropy_ack.entropy # For SLIP-39 this is the Encrypted Master Secret secret = _compute_secret_from_entropy(int_entropy, ext_entropy, msg.strength) # Check backup type, perform type-specific handling if msg.backup_type == BackupType.Bip39: # in BIP-39 we store mnemonic string instead of the secret secret = bip39.from_data(secret).encode() elif msg.backup_type in (BackupType.Slip39_Basic, BackupType.Slip39_Advanced): # generate and set SLIP39 parameters storage.device.set_slip39_identifier( slip39.generate_random_identifier()) storage.device.set_slip39_iteration_exponent( slip39.DEFAULT_ITERATION_EXPONENT) else: # Unknown backup type. raise RuntimeError # If either of skip_backup or no_backup is specified, we are not doing backup now. # Otherwise, we try to do it. perform_backup = not msg.no_backup and not msg.skip_backup # If doing backup, ask the user to confirm. if perform_backup: perform_backup = await confirm_backup(ctx) # generate and display backup information for the master secret if perform_backup: await backup_seed(ctx, msg.backup_type, secret) # write settings and master secret into storage if msg.label is not None: storage.device.set_label(msg.label) storage.device.set_passphrase_enabled(bool(msg.passphrase_protection)) storage.device.store_mnemonic_secret( secret, # for SLIP-39, this is the EMS msg.backup_type, needs_backup=not perform_backup, no_backup=bool(msg.no_backup), ) # if we backed up the wallet, show success message if perform_backup: await layout.show_backup_success(ctx) return Success(message="Initialized")
async def apply_settings(ctx: wire.Context, msg: ApplySettings) -> Success: if not storage.device.is_initialized(): raise wire.NotInitialized("Device is not initialized") if ( msg.homescreen is None and msg.label is None and msg.use_passphrase is None and msg.passphrase_always_on_device is None and msg.display_rotation is None and msg.auto_lock_delay_ms is None and msg.safety_checks is None and msg.experimental_features is None ): raise wire.ProcessError("No setting provided") if msg.homescreen is not None: validate_homescreen(msg.homescreen) await require_confirm_change_homescreen(ctx) try: storage.device.set_homescreen(msg.homescreen) except ValueError: raise wire.DataError("Invalid homescreen") if msg.label is not None: if len(msg.label) > storage.device.LABEL_MAXLENGTH: raise wire.DataError("Label too long") await require_confirm_change_label(ctx, msg.label) storage.device.set_label(msg.label) if msg.use_passphrase is not None: await require_confirm_change_passphrase(ctx, msg.use_passphrase) storage.device.set_passphrase_enabled(msg.use_passphrase) if msg.passphrase_always_on_device is not None: if not storage.device.is_passphrase_enabled(): raise wire.DataError("Passphrase is not enabled") await require_confirm_change_passphrase_source( ctx, msg.passphrase_always_on_device ) storage.device.set_passphrase_always_on_device(msg.passphrase_always_on_device) if msg.auto_lock_delay_ms is not None: if msg.auto_lock_delay_ms < storage.device.AUTOLOCK_DELAY_MINIMUM: raise wire.ProcessError("Auto-lock delay too short") if msg.auto_lock_delay_ms > storage.device.AUTOLOCK_DELAY_MAXIMUM: raise wire.ProcessError("Auto-lock delay too long") await require_confirm_change_autolock_delay(ctx, msg.auto_lock_delay_ms) storage.device.set_autolock_delay_ms(msg.auto_lock_delay_ms) if msg.safety_checks is not None: await require_confirm_safety_checks(ctx, msg.safety_checks) safety_checks.apply_setting(msg.safety_checks) if msg.display_rotation is not None: await require_confirm_change_display_rotation(ctx, msg.display_rotation) storage.device.set_rotation(msg.display_rotation) if msg.experimental_features is not None: await require_confirm_experimental_features(ctx, msg.experimental_features) storage.device.set_experimental_features(msg.experimental_features) reload_settings_from_storage() return Success(message="Settings applied")