Example #1
0
def write_input_script_prefixed(
    w: Writer,
    script_type: InputScriptType,
    multisig: MultisigRedeemScriptType | None,
    coin: CoinInfo,
    sighash_type: SigHashType,
    pubkey: bytes,
    signature: bytes,
) -> None:
    if script_type == InputScriptType.SPENDADDRESS:
        # p2pkh or p2sh
        write_input_script_p2pkh_or_p2sh_prefixed(w, pubkey, signature,
                                                  sighash_type)
    elif script_type == InputScriptType.SPENDP2SHWITNESS:
        # p2wpkh or p2wsh using p2sh

        if multisig is not None:
            # p2wsh in p2sh
            pubkeys = multisig_get_pubkeys(multisig)
            witness_script_h = utils.HashWriter(sha256())
            write_output_script_multisig(witness_script_h, pubkeys, multisig.m)
            write_input_script_p2wsh_in_p2sh(w,
                                             witness_script_h.get_digest(),
                                             prefixed=True)
        else:
            # p2wpkh in p2sh
            write_input_script_p2wpkh_in_p2sh(w,
                                              common.ecdsa_hash_pubkey(
                                                  pubkey, coin),
                                              prefixed=True)
    elif script_type in (InputScriptType.SPENDWITNESS,
                         InputScriptType.SPENDTAPROOT):
        # native p2wpkh or p2wsh or p2tr
        script_sig = input_script_native_segwit()
        write_bytes_prefixed(w, script_sig)
    elif script_type == InputScriptType.SPENDMULTISIG:
        # p2sh multisig
        assert multisig is not None  # checked in sanitize_tx_input
        signature_index = multisig_pubkey_index(multisig, pubkey)
        write_input_script_multisig_prefixed(w, multisig, signature,
                                             signature_index, sighash_type,
                                             coin)
    else:
        raise wire.ProcessError("Invalid script type")
Example #2
0
def _validate_structure(certificate: messages.CardanoTxCertificate) -> None:
    pool = certificate.pool
    pool_parameters = certificate.pool_parameters

    fields_to_be_empty: dict[CardanoCertificateType, tuple[Any, ...]] = {
        CardanoCertificateType.STAKE_REGISTRATION: (pool, pool_parameters),
        CardanoCertificateType.STAKE_DELEGATION: (pool_parameters, ),
        CardanoCertificateType.STAKE_DEREGISTRATION: (pool, pool_parameters),
        CardanoCertificateType.STAKE_POOL_REGISTRATION: (
            certificate.path,
            certificate.script_hash,
            certificate.key_hash,
            pool,
        ),
    }

    if certificate.type not in fields_to_be_empty or any(
            fields_to_be_empty[certificate.type]):
        raise wire.ProcessError("Invalid certificate")
Example #3
0
    async def dispatch_DebugLinkEraseSdCard(
        ctx: wire.Context, msg: DebugLinkEraseSdCard
    ) -> Success:
        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()
Example #4
0
async def sign_tx(ctx, msg):
    mnemonic = storage.get_mnemonic()
    root_node = bip32.from_mnemonic_cardano(mnemonic)

    progress.init(msg.transactions_count, "Loading data")

    try:
        # request transactions
        transactions = []
        tx_req = CardanoTxRequest()
        for index in range(msg.transactions_count):
            progress.advance()
            tx_ack = await request_transaction(ctx, tx_req, index)
            transactions.append(tx_ack.transaction)

        # clear progress bar
        display_homescreen()

        # sign the transaction bundle and prepare the result
        transaction = Transaction(msg.inputs, msg.outputs, transactions,
                                  root_node, msg.network)
        tx_body, tx_hash = transaction.serialise_tx()
        tx = CardanoSignedTx(tx_body=tx_body, tx_hash=tx_hash)

    except ValueError as e:
        if __debug__:
            log.exception(__name__, e)
        raise wire.ProcessError("Signing failed")

    # display the transaction in UI
    if not await show_tx(
            ctx,
            transaction.output_addresses,
            transaction.outgoing_coins,
            transaction.change_derivation_paths,
            transaction.change_coins,
            transaction.fee,
            len(tx_body),
            transaction.network_name,
    ):
        raise wire.ActionCancelled("Signing cancelled")

    return tx
Example #5
0
async def get_address(ctx, msg, keychain: seed.Keychain):
    await paths.validate_path(ctx, validate_full_path, keychain, msg.address_n,
                              CURVE)

    try:
        address, _ = derive_address_and_node(keychain, msg.address_n)
    except ValueError as e:
        if __debug__:
            log.exception(__name__, e)
        raise wire.ProcessError("Deriving address failed")
    if msg.show_display:
        desc = address_n_to_str(msg.address_n)
        while True:
            if await show_address(ctx, address, desc=desc):
                break
            if await show_qr(ctx, address, desc=desc):
                break

    return CardanoAddress(address=address)
Example #6
0
async def handle_DoPreauthorized(ctx: wire.Context,
                                 msg: DoPreauthorized) -> protobuf.MessageType:
    from trezor.messages.PreauthorizedRequest import PreauthorizedRequest
    from apps.common import authorization

    if not authorization.is_set():
        raise wire.ProcessError("No preauthorized operation")

    wire_types = authorization.get_wire_types()
    utils.ensure(bool(wire_types), "Unsupported preauthorization found")

    req = await ctx.call_any(PreauthorizedRequest(), *wire_types)

    handler = workflow_handlers.find_registered_handler(
        ctx.iface, req.MESSAGE_WIRE_TYPE)
    if handler is None:
        return wire.unexpected_message()

    return await handler(ctx, req, authorization.get())  # type: ignore
Example #7
0
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")
Example #8
0
def _validate_and_get_type(address: str, protocol_magic: int, network_id: int) -> int:
    """
    Validates Cardano address and returns its type
    for the convenience of outward-facing functions.
    """
    assert_cond(address is not None)
    assert_cond(len(address) > 0)

    address_bytes = get_bytes_unsafe(address)
    address_type = get_type(address_bytes)

    if address_type == CardanoAddressType.BYRON:
        byron_addresses.validate(address_bytes, protocol_magic)
    elif address_type in ADDRESS_TYPES_SHELLEY:
        _validate_shelley_address(address, address_bytes, network_id)
    else:
        raise wire.ProcessError("Invalid address")

    return address_type
Example #9
0
def input_derive_script(
    script_type: EnumTypeInputScriptType,
    multisig: MultisigRedeemScriptType | None,
    coin: CoinInfo,
    hash_type: int,
    pubkey: bytes,
    signature: bytes,
) -> bytes:
    if script_type == InputScriptType.SPENDADDRESS:
        # p2pkh or p2sh
        return scripts.input_script_p2pkh_or_p2sh(pubkey, signature, hash_type)
    elif script_type == InputScriptType.SPENDMULTISIG:
        # p2sh multisig
        assert multisig is not None  # checked in sanitize_tx_input
        signature_index = multisig_pubkey_index(multisig, pubkey)
        return input_script_multisig(multisig, signature, signature_index,
                                     hash_type, coin)
    else:
        raise wire.ProcessError("Invalid script type")
Example #10
0
async def _insert_card_dialog(ctx: Optional[wire.Context]) -> None:
    text = Text("SD card protection", ui.ICON_WRONG)
    text.bold("SD card required.")
    text.br_half()
    if SD_CARD_HOT_SWAPPABLE:
        text.normal("Please insert your", "SD card.")
        btn_confirm = "Retry"  # type: Optional[str]
        btn_cancel = "Abort"
    else:
        text.normal("Please unplug the", "device and insert your", "SD card.")
        btn_confirm = None
        btn_cancel = "Close"

    if ctx is None:
        if await Confirm(text, confirm=btn_confirm, cancel=btn_cancel) is not CONFIRMED:
            raise SdProtectCancelled
    else:
        if not await confirm(ctx, text, confirm=btn_confirm, cancel=btn_cancel):
            raise wire.ProcessError("SD card required.")
Example #11
0
    async def fetch_removed_original_outputs(
        self, orig: OriginalTxInfo, orig_hash: bytes, last_index: int
    ) -> None:
        while orig.index < last_index:
            txo = await helpers.request_tx_output(
                self.tx_req, orig.index, self.coin, orig_hash
            )
            orig.add_output(txo, self.output_derive_script(txo))

            if orig.output_is_change(txo):
                # Removal of change-outputs is allowed.
                self.approver.add_orig_change_output(txo)
            else:
                # Removal of external outputs requires prompting the user. Not implemented.
                raise wire.ProcessError(
                    "Removal of original external outputs is not supported."
                )

            orig.index += 1
Example #12
0
async def _validate_tx_signing_request(ctx: wire.Context,
                                       msg: CardanoSignTxInit) -> bool:
    """Validate the data in the signing request and return whether the provided network id is verifiable."""
    if msg.fee > LOVELACE_MAX_SUPPLY:
        raise wire.ProcessError("Fee is out of range!")
    validate_network_info(msg.network_id, msg.protocol_magic)

    is_network_id_verifiable = _is_network_id_verifiable(msg)
    if msg.signing_mode == CardanoTxSigningMode.ORDINARY_TRANSACTION:
        if not is_network_id_verifiable:
            await show_warning_tx_network_unverifiable(ctx)
    elif msg.signing_mode == CardanoTxSigningMode.POOL_REGISTRATION_AS_OWNER:
        _validate_stake_pool_registration_tx_structure(msg)
    elif msg.signing_mode == CardanoTxSigningMode.MULTISIG_TRANSACTION:
        if not is_network_id_verifiable:
            await show_warning_tx_network_unverifiable(ctx)
    else:
        raise INVALID_TX_SIGNING_REQUEST

    return is_network_id_verifiable
Example #13
0
async def backup_device(ctx: wire.Context, msg: BackupDevice) -> Success:
    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()
    if mnemonic_secret is None:
        raise RuntimeError

    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")
Example #14
0
async def get_public_key(ctx: wire.Context, msg: messages.CardanoGetPublicKey,
                         keychain: seed.Keychain) -> messages.CardanoPublicKey:
    await paths.validate_path(
        ctx,
        keychain,
        msg.address_n,
        # path must match the PUBKEY schema
        SCHEMA_PUBKEY.match(msg.address_n) or SCHEMA_MINT.match(msg.address_n),
    )

    try:
        key = _get_public_key(keychain, msg.address_n)
    except ValueError as e:
        if __debug__:
            log.exception(__name__, e)
        raise wire.ProcessError("Deriving public key failed")

    if msg.show_display:
        await show_pubkey(ctx, hexlify(key.node.public_key).decode())
    return key
Example #15
0
async def cardano_get_public_key(ctx, msg):
    mnemonic = storage.get_mnemonic()
    root_node = bip32.from_mnemonic_cardano(mnemonic)

    try:
        key = _get_public_key(root_node, msg.address_n)
    except ValueError as e:
        if __debug__:
            log.exception(__name__, e)
        raise wire.ProcessError("Deriving public key failed")
    mnemonic = None
    root_node = None

    lines = ["For BIP32 path: ", ""]
    lines.extend(_break_address_n_to_lines(msg.address_n))
    if not await show_swipable_with_confirmation(ctx, lines,
                                                 "Export xpub key"):
        raise wire.ActionCancelled("Exporting cancelled")

    return key
Example #16
0
async def cardano_verify_message(ctx, msg):
    try:
        res = _verify_message(msg.public_key, msg.signature, msg.message)
    except ValueError as e:
        if __debug__:
            log.exception(__name__, e)
        raise wire.ProcessError("Verifying failed")

    if not res:
        return Failure(message="Invalid signature")

    if not await show_swipable_with_confirmation(
            ctx, msg.message, "Verifying message", ui.ICON_RECEIVE, ui.GREEN):
        raise wire.ActionCancelled("Verifying cancelled")

    if not await show_swipable_with_confirmation(ctx, hexlify(
            msg.public_key), "With public key", ui.ICON_RECEIVE, ui.GREEN):
        raise wire.ActionCancelled("Verifying cancelled")

    return Success(message="Message verified")
Example #17
0
async def get_address(ctx, msg):
    mnemonic = storage.get_mnemonic()
    root_node = bip32.from_mnemonic_cardano(mnemonic)

    try:
        address, _ = derive_address_and_node(root_node, msg.address_n)
    except ValueError as e:
        if __debug__:
            log.exception(__name__, e)
        raise wire.ProcessError("Deriving address failed")
    mnemonic = None
    root_node = None

    if msg.show_display:
        if not await confirm_with_pagination(
            ctx, address, "Export address", icon=ui.ICON_SEND, icon_color=ui.GREEN
        ):
            raise wire.ActionCancelled("Exporting cancelled")

    return CardanoAddress(address=address)
Example #18
0
    async def step3_verify_inputs(self) -> None:
        # should come out the same as h_inputs, checked before continuing
        h_check = self.create_hash_writer()

        for i in range(self.tx.inputs_count):
            progress.advance()
            txi = await helpers.request_tx_input(self.tx_req, i, self.coin)

            writers.write_tx_input_check(h_check, txi)
            prev_amount, script_pubkey = await self.get_prevtx_output(
                txi.prev_hash, txi.prev_index)
            if prev_amount != txi.amount:
                raise wire.DataError("Invalid amount specified")

            if i in self.external:
                await self.verify_external_input(i, txi, script_pubkey)

        # check that the inputs were the same as those streamed for approval
        if h_check.get_digest() != self.h_inputs:
            raise wire.ProcessError("Transaction has changed during signing")
Example #19
0
async def get_address(ctx: wire.Context, msg: messages.CardanoGetAddress,
                      keychain: seed.Keychain) -> messages.CardanoAddress:
    validate_network_info(msg.network_id, msg.protocol_magic)
    addresses.validate_address_parameters(msg.address_parameters)

    try:
        address = addresses.derive_human_readable(keychain,
                                                  msg.address_parameters,
                                                  msg.protocol_magic,
                                                  msg.network_id)
    except ValueError as e:
        if __debug__:
            log.exception(__name__, e)
        raise wire.ProcessError("Deriving address failed")

    if msg.show_display:
        await _display_address(ctx, msg.address_parameters, address,
                               msg.protocol_magic)

    return messages.CardanoAddress(address=address)
async def get_public_key(ctx, msg, keychain: seed.Keychain):
    await paths.validate_path(
        ctx,
        paths.validate_path_for_get_public_key,
        keychain,
        msg.address_n,
        CURVE,
        slip44_id=1815,
    )

    try:
        key = _get_public_key(keychain, msg.address_n)
    except ValueError as e:
        if __debug__:
            log.exception(__name__, e)
        raise wire.ProcessError("Deriving public key failed")

    if msg.show_display:
        await layout.show_pubkey(ctx, key.node.public_key)
    return key
Example #21
0
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 RuntimeError("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.load_settings(use_passphrase=msg.passphrase_protection,
                                 label=msg.label)
    if msg.pin:
        config.change_pin(pin_to_int(""), pin_to_int(msg.pin), None, None)

    return Success(message="Device loaded")
Example #22
0
def cborize_native_script(
    keychain: seed.Keychain, script: messages.CardanoNativeScript
) -> CborSequence:
    script_content: CborSequence
    if script.type == CardanoNativeScriptType.PUB_KEY:
        if script.key_hash:
            script_content = (script.key_hash,)
        elif script.key_path:
            script_content = (get_public_key_hash(keychain, script.key_path),)
        else:
            raise wire.ProcessError("Invalid native script")
    elif script.type == CardanoNativeScriptType.ALL:
        script_content = (
            tuple(
                cborize_native_script(keychain, sub_script)
                for sub_script in script.scripts
            ),
        )
    elif script.type == CardanoNativeScriptType.ANY:
        script_content = (
            tuple(
                cborize_native_script(keychain, sub_script)
                for sub_script in script.scripts
            ),
        )
    elif script.type == CardanoNativeScriptType.N_OF_K:
        script_content = (
            script.required_signatures_count,
            tuple(
                cborize_native_script(keychain, sub_script)
                for sub_script in script.scripts
            ),
        )
    elif script.type == CardanoNativeScriptType.INVALID_BEFORE:
        script_content = (script.invalid_before,)
    elif script.type == CardanoNativeScriptType.INVALID_HEREAFTER:
        script_content = (script.invalid_hereafter,)
    else:
        raise RuntimeError  # should be unreachable

    return (script.type,) + script_content
Example #23
0
async def _get_keychain_bip39(
        ctx: wire.Context, derivation_type: CardanoDerivationType) -> Keychain:
    if not device.is_initialized():
        raise wire.NotInitialized("Device is not initialized")

    if derivation_type == CardanoDerivationType.LEDGER:
        seed = await get_seed(ctx)
        return Keychain(cardano.from_seed_ledger(seed))

    if not cache.get(cache.APP_COMMON_DERIVE_CARDANO):
        raise wire.ProcessError(
            "Cardano derivation is not enabled for this session")

    if derivation_type == CardanoDerivationType.ICARUS:
        cache_entry = cache.APP_CARDANO_ICARUS_SECRET
    else:
        cache_entry = cache.APP_CARDANO_ICARUS_TREZOR_SECRET

    secret = await _get_secret(ctx, cache_entry)
    root = cardano.from_secret(secret)
    return Keychain(root)
Example #24
0
def write_input_script_prefixed(
    w: Writer,
    script_type: InputScriptType,
    multisig: MultisigRedeemScriptType | None,
    coin: CoinInfo,
    hash_type: int,
    pubkey: bytes,
    signature: bytes,
) -> None:
    if script_type == InputScriptType.SPENDADDRESS:
        # p2pkh or p2sh
        scripts.write_input_script_p2pkh_or_p2sh_prefixed(
            w, pubkey, signature, hash_type)
    elif script_type == InputScriptType.SPENDMULTISIG:
        # p2sh multisig
        assert multisig is not None  # checked in sanitize_tx_input
        signature_index = multisig_pubkey_index(multisig, pubkey)
        write_input_script_multisig_prefixed(w, multisig, signature,
                                             signature_index, hash_type, coin)
    else:
        raise wire.ProcessError("Invalid script type")
Example #25
0
def _validate_max_tx_output_size(
    keychain: seed.Keychain,
    output: CardanoTxOutputType,
    protocol_magic: int,
    network_id: int,
) -> None:
    """
    This limitation is a mitigation measure to prevent sending
    large (especially change) outputs containing many tokens that Trezor
    would not be able to spend reliably given that
    currently the full Cardano transaction is held in-memory.
    Once Cardano-transaction signing is refactored to be streamed, this
    limit can be lifted
    """
    cborized_output = _cborize_output(keychain, output, protocol_magic,
                                      network_id)
    serialized_output = cbor.encode(cborized_output)

    if len(serialized_output) > MAX_TX_OUTPUT_SIZE:
        raise wire.ProcessError("Maximum tx output size (%s bytes) exceeded!" %
                                MAX_TX_OUTPUT_SIZE)
Example #26
0
def produce_json_for_signing(envelope: BinanceSignTx, msg: MessageType) -> str:
    if BinanceTransferMsg.is_type_of(msg):
        json_msg = produce_transfer_json(msg)
    elif BinanceOrderMsg.is_type_of(msg):
        json_msg = produce_neworder_json(msg)
    elif BinanceCancelMsg.is_type_of(msg):
        json_msg = produce_cancel_json(msg)
    else:
        raise wire.ProcessError("input message unrecognized")

    if envelope.source < 0:
        raise wire.DataError("Source is invalid")

    return ENVELOPE_BLUEPRINT.format(
        account_number=envelope.account_number,
        chain_id=envelope.chain_id,
        memo=envelope.memo,
        msgs=json_msg,
        sequence=envelope.sequence,
        source=envelope.source,
    )
async def get_address(ctx: wire.Context, msg: CardanoGetAddress,
                      keychain: seed.Keychain) -> CardanoAddress:
    address_parameters = msg.address_parameters

    await paths.validate_path(ctx, validate_full_path, keychain,
                              address_parameters.address_n, CURVE)

    try:
        address = derive_human_readable_address(keychain, address_parameters,
                                                msg.protocol_magic,
                                                msg.network_id)
    except ValueError as e:
        if __debug__:
            log.exception(__name__, e)
        raise wire.ProcessError("Deriving address failed")

    if msg.show_display:
        await _display_address(ctx, keychain, address_parameters, address,
                               msg.protocol_magic)

    return CardanoAddress(address=address)
Example #28
0
async def get_address(ctx, msg):
    keychain = await seed.get_keychain(ctx)

    await paths.validate_path(ctx, validate_full_path, path=msg.address_n)

    try:
        address, _ = derive_address_and_node(keychain, msg.address_n)
    except ValueError as e:
        if __debug__:
            log.exception(__name__, e)
        raise wire.ProcessError("Deriving address failed")

    if msg.show_display:
        if not await confirm_with_pagination(ctx,
                                             address,
                                             "Export address",
                                             icon=ui.ICON_SEND,
                                             icon_color=ui.GREEN):
            raise wire.ActionCancelled("Exporting cancelled")

    return CardanoAddress(address=address)
Example #29
0
    async def sign_segwit_input(self, i: int) -> None:
        # STAGE_REQUEST_SEGWIT_WITNESS in legacy
        txi = await helpers.request_tx_input(self.tx_req, i, self.coin)

        if not input_is_segwit(txi):
            raise wire.ProcessError("Transaction has changed during signing")

        public_key, signature = self.sign_bip143_input(txi)

        self.set_serialized_signature(i, signature)
        if txi.multisig:
            # find out place of our signature based on the pubkey
            signature_index = multisig.multisig_pubkey_index(
                txi.multisig, public_key)
            self.serialized_tx.extend(
                scripts.witness_p2wsh(txi.multisig, signature, signature_index,
                                      self.get_hash_type()))
        else:
            self.serialized_tx.extend(
                scripts.witness_p2wpkh(signature, public_key,
                                       self.get_hash_type()))
Example #30
0
def require_confirm_sd_protect(ctx: wire.Context, msg: SdProtect) -> None:
    if msg.operation == SdProtectOperationType.ENABLE:
        text = Text("SD card protection", ui.ICON_CONFIG)
        text.normal("Do you really want to", "secure your device with",
                    "SD card protection?")
    elif msg.operation == SdProtectOperationType.DISABLE:
        text = Text("SD card protection", ui.ICON_CONFIG)
        text.normal("Do you really want to", "remove SD card",
                    "protection from your", "device?")
    elif msg.operation == SdProtectOperationType.REFRESH:
        text = Text("SD card protection", ui.ICON_CONFIG)
        text.normal(
            "Do you really want to",
            "replace the current",
            "SD card secret with a",
            "newly generated one?",
        )
    else:
        raise wire.ProcessError("Unknown operation")

    return require_confirm(ctx, text)