async def require_confirm_transaction( ctx, state: State, tsx_data: MoneroTransactionData, network_type: int ): """ Ask for confirmation from user. """ from apps.monero.xmr.addresses import get_change_addr_idx outputs = tsx_data.outputs change_idx = get_change_addr_idx(outputs, tsx_data.change_dts) has_integrated = bool(tsx_data.integrated_indices) has_payment = bool(tsx_data.payment_id) if tsx_data.unlock_time != 0: await _require_confirm_unlock_time(ctx, tsx_data.unlock_time) for idx, dst in enumerate(outputs): is_change = change_idx is not None and idx == change_idx if is_change: continue # Change output does not need confirmation is_dummy = change_idx is None and dst.amount == 0 and len(outputs) == 2 if is_dummy: continue # Dummy output does not need confirmation if has_integrated and idx in tsx_data.integrated_indices: cur_payment = tsx_data.payment_id else: cur_payment = None await _require_confirm_output(ctx, dst, network_type, cur_payment) if has_payment and not has_integrated and tsx_data.payment_id != DUMMY_PAYMENT_ID: await _require_confirm_payment_id(ctx, tsx_data.payment_id) await _require_confirm_fee(ctx, tsx_data.fee) await transaction_step(state, 0)
async def require_confirm_transaction(ctx, tsx_data, network_type): """ Ask for confirmation from user. """ from apps.monero.xmr.addresses import get_change_addr_idx outputs = tsx_data.outputs change_idx = get_change_addr_idx(outputs, tsx_data.change_dts) has_integrated = bool(tsx_data.integrated_indices) has_payment = bool(tsx_data.payment_id) for idx, dst in enumerate(outputs): is_change = change_idx is not None and idx == change_idx if is_change: continue # Change output does not need confirmation is_dummy = change_idx is None and dst.amount == 0 and len(outputs) == 2 if is_dummy: continue # Dummy output does not need confirmation if has_integrated and idx in tsx_data.integrated_indices: cur_payment = tsx_data.payment_id else: cur_payment = None await _require_confirm_output(ctx, dst, network_type, cur_payment) if has_payment and not has_integrated: await _require_confirm_payment_id(ctx, tsx_data.payment_id) await _require_confirm_fee(ctx, tsx_data.fee) text = Text("Signing transaction", ui.ICON_SEND, icon_color=ui.BLUE) text.normal("Signing...") text.render()
def _check_change(state: State, outputs: List[MoneroTransactionDestinationEntry]): """ Check if the change address in state.output_change (from `tsx_data.outputs`) is a) among tx outputs b) is equal to our address The change output is in `tsx_data.change_dts`, but also has to be in `tsx_data.outputs`. This is what Monero does in its cold wallet signing protocol. In other words, these structures are built by Monero when generating unsigned transaction set and we do not want to modify this logic. We just translate the unsigned tx to the protobuf message. So, although we could probably optimize this by having the change output in `change_dts` only, we intentionally do not do so. """ from apps.monero.xmr.addresses import addr_eq, get_change_addr_idx change_index = get_change_addr_idx(outputs, state.output_change) change_addr = state.change_address() # if there is no change, there is nothing to check if change_addr is None: state.mem_trace("No change" if __debug__ else None) return """ Sweep tx is just one output and no change. To prevent recognition of such transactions another fake output is added that spends exactly 0 coins to a random address. See https://github.com/monero-project/monero/pull/1415 """ if change_index is None and state.output_change.amount == 0 and len( outputs) == 2: state.mem_trace("Sweep tsx" if __debug__ else None) return found = False for out in outputs: if addr_eq(out.addr, change_addr): found = True break if not found: raise signing.ChangeAddressError("Change address not found in outputs") my_addr = _get_primary_change_address(state) if not addr_eq(my_addr, change_addr): raise signing.ChangeAddressError("Change address differs from ours")