def _check_subaddresses( state: State, outputs: list[MoneroTransactionDestinationEntry]) -> None: """ Using subaddresses leads to a few poorly documented exceptions. Normally we set R=r*G (tx_pub), however for subaddresses this is equal to R=r*D to achieve the nice blockchain scanning property. Remember, that R is per-transaction and not per-input. It's all good if we have a single output or we have a single destination and the second output is our change. This is because although the R=r*D, we can still derive the change using our private view-key. In other words, calculate the one-time address as P = H(x*R)*G + Y (where X,Y is the change). However, this does not work for other outputs than change, because we do not have the recipient's view key, so we cannot use the same formula -- we need a new R. The solution is very straightforward -- we create additional `R`s and use the `extra` field to include them under the `ADDITIONAL_PUBKEYS` tag. See: - https://lab.getmonero.org/pubs/MRL-0006.pdf - https://github.com/monero-project/monero/pull/2056 """ from apps.monero.xmr.addresses import classify_subaddresses # let's first figure out what kind of destinations we have num_stdaddresses, num_subaddresses, single_dest_subaddress = classify_subaddresses( outputs, state.change_address()) # if this is a single-destination transfer to a subaddress, # we set (override) the tx pubkey to R=r*D and no additional # tx keys are needed if num_stdaddresses == 0 and num_subaddresses == 1: state.tx_pub = crypto.scalarmult_into( None, crypto_helpers.decodepoint( single_dest_subaddress.spend_public_key), state.tx_priv, ) # if a subaddress is used and either standard address is as well # or more than one subaddress is used we need to add additional tx keys state.need_additional_txkeys = num_subaddresses > 0 and ( num_stdaddresses > 0 or num_subaddresses > 1) state.mem_trace(4, True)
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 # 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) await _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(signing.RctType.Bulletproof2, 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 = await 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)