def _get_key_for_payment_id_encryption(destinations: list, change_addr=None): """ Returns destination address public view key to be used for payment id encryption. """ from apps.monero.xmr.addresses import addr_eq from trezor.messages.MoneroAccountPublicAddress import MoneroAccountPublicAddress addr = MoneroAccountPublicAddress(spend_public_key=crypto.NULL_KEY_ENC, view_public_key=crypto.NULL_KEY_ENC) count = 0 for dest in destinations: if dest.amount == 0: continue if change_addr and addr_eq(dest.addr, change_addr): continue if addr_eq(dest.addr, addr): continue if count > 0: raise ValueError( "Destinations have to have exactly one output to support encrypted payment ids" ) addr = dest.addr count += 1 if count == 0 and change_addr: return change_addr.view_public_key if addr.view_public_key == crypto.NULL_KEY_ENC: raise ValueError("Invalid key") return addr.view_public_key
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")
def _set_out_derivation( state: State, dst_entr: MoneroTransactionDestinationEntry, additional_txkey_priv: crypto.Scalar, ) -> crypto.Point: """ Calculates derivation which is then used in the one-time address as `P = H(derivation)*G + B`. For change outputs the derivation equals a*R, because we know the private view key. For others it is either `r*A` for traditional addresses, or `s*C` for subaddresses. Both `r` and `s` are random scalars, `s` is used in the context of subaddresses, but it's basically the same thing. """ from apps.monero.xmr.addresses import addr_eq change_addr = state.change_address() if change_addr and addr_eq(dst_entr.addr, change_addr): # sending change to yourself; derivation = a*R derivation = crypto_helpers.generate_key_derivation( state.tx_pub, state.creds.view_key_private) else: # sending to the recipient; derivation = r*A (or s*C in the subaddress scheme) if dst_entr.is_subaddress and state.need_additional_txkeys: deriv_priv = additional_txkey_priv else: deriv_priv = state.tx_priv derivation = crypto_helpers.generate_key_derivation( crypto_helpers.decodepoint(dst_entr.addr.view_public_key), deriv_priv) return derivation
def _get_key_for_payment_id_encryption( tsx_data: MoneroTransactionData, change_addr: MoneroAccountPublicAddress | None = None, add_dummy_payment_id: bool = False, ) -> bytes: """ Returns destination address public view key to be used for payment id encryption. If no encrypted payment ID is chosen, dummy payment ID is set for better transaction uniformity if possible. """ from apps.monero.xmr.addresses import addr_eq from trezor.messages import MoneroAccountPublicAddress addr = MoneroAccountPublicAddress( spend_public_key=crypto_helpers.NULL_KEY_ENC, view_public_key=crypto_helpers.NULL_KEY_ENC, ) count = 0 for dest in tsx_data.outputs: if dest.amount == 0: continue if change_addr and addr_eq(dest.addr, change_addr): continue if addr_eq(dest.addr, addr): continue if count > 0 and tsx_data.payment_id: raise ValueError( "Destinations have to have exactly one output to support encrypted payment ids" ) addr = dest.addr count += 1 # Insert dummy payment id for transaction uniformity if not tsx_data.payment_id and count <= 1 and add_dummy_payment_id: tsx_data.payment_id = bytearray(8) if count == 0 and change_addr: return change_addr.view_public_key if addr.view_public_key == crypto_helpers.NULL_KEY_ENC: raise ValueError("Invalid key") return addr.view_public_key