def _validate(
    state: State,
    dst_entr: MoneroTransactionDestinationEntry,
    dst_entr_hmac: bytes,
    is_offloaded_bp: bool,
) -> MoneroTransactionDestinationEntry:
    if state.last_step not in (state.STEP_ALL_IN, state.STEP_OUT):
        raise ValueError("Invalid state transition")
    if is_offloaded_bp and (not state.rsig_offload):
        raise ValueError("Extraneous offloaded msg")

    if state.rsig_offload:
        bidx = _get_rsig_batch(state, state.current_output_index)
        last_in_batch = _is_last_in_batch(state, state.current_output_index,
                                          bidx)

        utils.ensure(
            not last_in_batch
            or state.is_processing_offloaded != is_offloaded_bp,
            "Offloaded BP out of order",
        )
        state.is_processing_offloaded = is_offloaded_bp

    if not state.is_processing_offloaded:
        state.current_output_index += 1

    utils.ensure(not dst_entr or dst_entr.amount >= 0,
                 "Destination with negative amount")
    utils.ensure(state.current_input_index + 1 == state.input_count,
                 "Invalid number of inputs")
    utils.ensure(state.current_output_index < state.output_count,
                 "Invalid output index")

    if not state.is_processing_offloaded:
        # HMAC check of the destination
        dst_entr_hmac_computed = offloading_keys.gen_hmac_tsxdest(
            state.key_hmac, dst_entr, state.current_output_index)

        utils.ensure(crypto.ct_equals(dst_entr_hmac, dst_entr_hmac_computed),
                     "HMAC failed")
        del dst_entr_hmac_computed

    else:
        dst_entr = None

    del dst_entr_hmac
    state.mem_trace(3, True)

    return dst_entr
async def init_transaction(
    state: State,
    address_n: list,
    network_type: int,
    tsx_data: MoneroTransactionData,
    keychain,
) -> MoneroTransactionInitAck:
    from apps.monero.signing import offloading_keys
    from apps.common import paths

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

    state.creds = misc.get_creds(keychain, address_n, network_type)
    state.client_version = tsx_data.client_version or 0
    if state.client_version == 0:
        raise ValueError("Client version not supported")

    state.fee = state.fee if state.fee > 0 else 0
    state.tx_priv = crypto.random_scalar()
    state.tx_pub = crypto.scalarmult_base(state.tx_priv)
    state.mem_trace(1)

    state.input_count = tsx_data.num_inputs
    state.output_count = len(tsx_data.outputs)
    state.progress_total = 4 + 3 * state.input_count + state.output_count
    state.progress_cur = 0

    # Ask for confirmation
    await confirms.require_confirm_transaction(state.ctx, state, tsx_data,
                                               state.creds.network_type)
    state.creds.address = None
    state.creds.network_type = None
    gc.collect()
    state.mem_trace(3)

    # Basic transaction parameters
    state.output_change = tsx_data.change_dts
    state.mixin = tsx_data.mixin
    state.fee = tsx_data.fee
    state.account_idx = tsx_data.account
    state.last_step = state.STEP_INIT
    if tsx_data.hard_fork:
        state.hard_fork = tsx_data.hard_fork

    state.tx_type = (signing.RctType.CLSAG if state.hard_fork >= 13 else
                     signing.RctType.Bulletproof2)

    # Ensure change is correct
    _check_change(state, tsx_data.outputs)

    # At least two outpus are required, this applies also for sweep txs
    # where one fake output is added. See _check_change for more info
    if state.output_count < 2:
        raise signing.NotEnoughOutputsError(
            "At least two outputs are required")

    _check_rsig_data(state, tsx_data.rsig_data)
    _check_subaddresses(state, tsx_data.outputs)

    # Extra processing, payment id
    _process_payment_id(state, tsx_data)
    _compute_sec_keys(state, tsx_data)
    gc.collect()

    # Iterative tx_prefix_hash hash computation
    state.tx_prefix_hasher.uvarint(
        2)  # current Monero transaction format (RingCT = 2)
    state.tx_prefix_hasher.uvarint(tsx_data.unlock_time)
    state.tx_prefix_hasher.uvarint(state.input_count)  # ContainerType, size
    state.mem_trace(10, True)

    # Final message hasher
    state.full_message_hasher.init()
    state.full_message_hasher.set_type_fee(state.tx_type, state.fee)

    # Sub address precomputation
    if tsx_data.account is not None and tsx_data.minor_indices:
        _precompute_subaddr(state, tsx_data.account, tsx_data.minor_indices)
    state.mem_trace(5, True)

    # HMACs all outputs to disallow tampering.
    # Each HMAC is then sent alongside the output
    # and trezor validates it.
    hmacs = []
    for idx in range(state.output_count):
        c_hmac = offloading_keys.gen_hmac_tsxdest(state.key_hmac,
                                                  tsx_data.outputs[idx], idx)
        hmacs.append(c_hmac)
        gc.collect()

    state.mem_trace(6)

    from trezor.messages.MoneroTransactionInitAck import MoneroTransactionInitAck
    from trezor.messages.MoneroTransactionRsigData import MoneroTransactionRsigData

    rsig_data = MoneroTransactionRsigData(offload_type=state.rsig_offload)

    return MoneroTransactionInitAck(hmacs=hmacs, rsig_data=rsig_data)