Files
salvium/src/wallet/scanning_tools.cpp

732 lines
31 KiB
C++

// Copyright (c) 2025, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//paired header
#include "scanning_tools.h"
//local headers
#include "carrot_core/device_ram_borrowed.h"
#include "carrot_core/enote_utils.h"
#include "carrot_core/lazy_amount_commitment.h"
#include "carrot_core/scan.h"
#include "carrot_impl/carrot_tx_format_utils.h"
#include "common/container_helpers.h"
#include "cryptonote_basic/cryptonote_format_utils.h"
#include "ringct/rctOps.h"
#include "ringct/rctSigs.h"
//third party headers
//standard headers
#undef MONERO_DEFAULT_LOG_CATEGORY
#define MONERO_DEFAULT_LOG_CATEGORY "wallet.scanning_tools"
namespace tools
{
namespace wallet
{
//-------------------------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------------------------
static bool parse_tx_extra_for_scanning(const std::vector<std::uint8_t> &tx_extra,
const std::size_t n_outputs,
std::vector<crypto::public_key> &main_tx_ephemeral_pubkeys_out,
std::vector<crypto::public_key> &additional_tx_ephemeral_pubkeys_out,
cryptonote::blobdata &tx_extra_nonce_out)
{
// 1. parse extra fields
std::vector<cryptonote::tx_extra_field> tx_extra_fields;
bool fully_parsed = cryptonote::parse_tx_extra(tx_extra, tx_extra_fields);
// 2. extract main tx pubkey
cryptonote::tx_extra_pub_key field_main_pubkey;
size_t field_main_pubkey_index = 0;
while (cryptonote::find_tx_extra_field_by_type(tx_extra_fields, field_main_pubkey, field_main_pubkey_index++))
main_tx_ephemeral_pubkeys_out.push_back(field_main_pubkey.pub_key);
// 3. extract additional tx pubkeys
cryptonote::tx_extra_additional_pub_keys field_additional_pubkeys;
if (cryptonote::find_tx_extra_field_by_type(tx_extra_fields, field_additional_pubkeys))
{
if (field_additional_pubkeys.data.size() == n_outputs)
additional_tx_ephemeral_pubkeys_out = std::move(field_additional_pubkeys.data);
else
fully_parsed = false;
}
// 4. extract nonce string
cryptonote::tx_extra_nonce field_extra_nonce;
if (cryptonote::find_tx_extra_field_by_type(tx_extra_fields, field_extra_nonce))
tx_extra_nonce_out = std::move(field_extra_nonce.nonce);
return fully_parsed;
}
//-------------------------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------------------------
static bool parse_tx_extra_for_scanning(const cryptonote::transaction_prefix &tx_prefix,
std::vector<crypto::public_key> &main_tx_ephemeral_pubkeys_out,
std::vector<crypto::public_key> &additional_tx_ephemeral_pubkeys_out,
cryptonote::blobdata &tx_extra_nonce_out)
{
return parse_tx_extra_for_scanning(tx_prefix.extra,
tx_prefix.vout.size(),
main_tx_ephemeral_pubkeys_out,
additional_tx_ephemeral_pubkeys_out,
tx_extra_nonce_out);
}
//-------------------------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------------------------
static void perform_ecdh_derivations(const epee::span<const crypto::public_key> main_tx_ephemeral_pubkeys,
const epee::span<const crypto::public_key> additional_tx_ephemeral_pubkeys,
const cryptonote::account_keys &acc,
const bool is_carrot,
std::vector<crypto::key_derivation> &main_derivations_out,
std::vector<crypto::key_derivation> &additional_derivations_out)
{
main_derivations_out.clear();
additional_derivations_out.clear();
main_derivations_out.reserve(main_tx_ephemeral_pubkeys.size());
additional_derivations_out.reserve(additional_derivations_out.size());
if (is_carrot)
{
//! @TODO: HW device
carrot::view_incoming_key_ram_borrowed_device k_view_dev(acc.m_view_secret_key);
for (const crypto::public_key &main_tx_ephemeral_pubkey : main_tx_ephemeral_pubkeys)
{
mx25519_pubkey s_sender_receiver_unctx;
k_view_dev.view_key_scalar_mult_x25519(
carrot::raw_byte_convert<mx25519_pubkey>(main_tx_ephemeral_pubkey),
s_sender_receiver_unctx);
main_derivations_out.push_back(carrot::raw_byte_convert<crypto::key_derivation>(s_sender_receiver_unctx));
}
for (const crypto::public_key &additional_tx_ephemeral_pubkey : additional_tx_ephemeral_pubkeys)
{
mx25519_pubkey s_sender_receiver_unctx;
k_view_dev.view_key_scalar_mult_x25519(
carrot::raw_byte_convert<mx25519_pubkey>(additional_tx_ephemeral_pubkey),
s_sender_receiver_unctx);
additional_derivations_out.push_back(carrot::raw_byte_convert<crypto::key_derivation>(s_sender_receiver_unctx));
}
}
else // !is_carrot
{
for (const crypto::public_key &main_tx_ephemeral_pubkey : main_tx_ephemeral_pubkeys)
{
acc.get_device().generate_key_derivation(main_tx_ephemeral_pubkey,
acc.m_view_secret_key,
tools::add_element(main_derivations_out));
}
for (const crypto::public_key &additional_tx_ephemeral_pubkey : additional_tx_ephemeral_pubkeys)
{
acc.get_device().generate_key_derivation(additional_tx_ephemeral_pubkey,
acc.m_view_secret_key,
tools::add_element(additional_derivations_out));
}
}
}
//-------------------------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------------------------
std::optional<enote_view_incoming_scan_info_t> try_view_incoming_scan_enote_destination(
const cryptonote::tx_out &enote_destination,
const carrot::lazy_amount_commitment_t &amount_commitment,
const epee::span<const crypto::public_key> main_tx_ephemeral_pubkeys,
const epee::span<const crypto::public_key> additional_tx_ephemeral_pubkeys,
const cryptonote::blobdata &tx_extra_nonce,
const cryptonote::txin_v &first_tx_input,
const std::size_t local_output_index,
const epee::span<const crypto::key_derivation> main_derivations,
const std::vector<crypto::key_derivation> &additional_derivations,
const cryptonote::account_keys &acc,
const std::unordered_map<crypto::public_key, cryptonote::subaddress_index> &subaddress_map)
{
// 1. check that:
// a) has main or additional ephemeral pubkey, and
// b) main derivations match with main ephemeral pubkeys, and
// c) additional derivations match with additional pubkeys, and
// d) output_index isn't out of range for a non-empty additional ephemeral pubkeys, and
// e) main ephemeral pubkeys count is not greater than two, and
// f) we can extract an output public key from the enote destination
enote_view_incoming_scan_info_t res{};
if (main_tx_ephemeral_pubkeys.empty() && additional_tx_ephemeral_pubkeys.empty())
return std::nullopt;
else if (main_derivations.size() != main_tx_ephemeral_pubkeys.size())
return std::nullopt;
else if (additional_derivations.size() != additional_tx_ephemeral_pubkeys.size())
return std::nullopt;
else if (!additional_derivations.empty() && local_output_index >= additional_derivations.size())
return std::nullopt;
else if (main_derivations.size() > 2)
return std::nullopt;
else if (!cryptonote::get_output_public_key(enote_destination, res.onetime_address))
return std::nullopt;
// 2. setup default values
res.amount = enote_destination.amount;
res.amount_blinding_factor = rct::I;
res.local_output_index = local_output_index;
boost::optional<cryptonote::subaddress_receive_info> subaddr_receive_info;
// 3. copy long plaintext payment ID, if applicable
if (!tx_extra_nonce.empty() && !cryptonote::get_payment_id_from_tx_extra_nonce(tx_extra_nonce, res.payment_id))
res.payment_id = crypto::hash{};
// 4. view-incoming scan
const bool is_carrot = enote_destination.target.type() == typeid(cryptonote::txout_to_carrot_v1);
const bool is_coinbase = first_tx_input.type() == typeid(cryptonote::txin_gen);
if (is_carrot)
{
const crypto::public_key &enote_ephemeral_pubkey_pk = main_tx_ephemeral_pubkeys.empty()
? additional_tx_ephemeral_pubkeys[local_output_index] : main_tx_ephemeral_pubkeys[0];
const auto enote_ephemeral_pubkey = carrot::raw_byte_convert<mx25519_pubkey>(enote_ephemeral_pubkey_pk);
const auto &txout_carrot = boost::get<cryptonote::txout_to_carrot_v1>(enote_destination.target);
//! @TODO: HW device
const carrot::view_incoming_key_ram_borrowed_device k_view_dev(acc.m_view_secret_key);
mx25519_pubkey s_sender_receiver_unctx;
if (!k_view_dev.view_key_scalar_mult_x25519(enote_ephemeral_pubkey, s_sender_receiver_unctx))
return std::nullopt;
if (is_coinbase)
{
const carrot::CarrotCoinbaseEnoteV1 enote{
.onetime_address = txout_carrot.key,
.amount = enote_destination.amount,
.anchor_enc = txout_carrot.encrypted_janus_anchor,
.view_tag = txout_carrot.view_tag,
.enote_ephemeral_pubkey = enote_ephemeral_pubkey,
.block_index = boost::get<cryptonote::txin_gen>(first_tx_input).height
};
if (!carrot::try_scan_carrot_coinbase_enote(enote,
s_sender_receiver_unctx,
k_view_dev,
acc.m_account_address.m_spend_public_key,
res.sender_extension_g,
res.sender_extension_t))
return std::nullopt;
res.address_spend_pubkey = acc.m_account_address.m_spend_public_key;
}
else // !is_coinbase
{
const carrot::CarrotEnoteV1 enote{
.onetime_address = txout_carrot.key,
.amount_commitment = carrot::calculate_amount_commitment(amount_commitment),
//.anchor_enc = ... doesn't matter for try_scan_carrot_enote_external_destination_only() ...
.anchor_enc = txout_carrot.encrypted_janus_anchor,
.view_tag = txout_carrot.view_tag,
.enote_ephemeral_pubkey = enote_ephemeral_pubkey,
.tx_first_key_image = boost::get<cryptonote::txin_to_key>(first_tx_input).k_image
};
// convert pid_enc
std::optional<carrot::encrypted_payment_id_t> encrypted_carrot_payment_id;
if (!tx_extra_nonce.empty())
{
crypto::hash8 pid_enc_8;
if (cryptonote::get_encrypted_payment_id_from_tx_extra_nonce(tx_extra_nonce, pid_enc_8))
encrypted_carrot_payment_id = carrot::raw_byte_convert<carrot::encrypted_payment_id_t>(pid_enc_8);
}
// try Carrot view-scan w/o amount commitment recomputation
carrot::payment_id_t carrot_payment_id;
if (!carrot::try_scan_carrot_enote_external_destination_only(enote,
encrypted_carrot_payment_id,
s_sender_receiver_unctx,
k_view_dev,
acc.m_account_address.m_spend_public_key,
res.sender_extension_g,
res.sender_extension_t,
res.address_spend_pubkey,
carrot_payment_id))
return std::nullopt;
memcpy(&res.payment_id, &carrot_payment_id, sizeof(carrot_payment_id));
}
// find K^j_s in subaddress map
const auto subaddr_it = subaddress_map.find(res.address_spend_pubkey);
if (subaddr_it == subaddress_map.cend())
return std::nullopt;
carrot::input_context_t input_context;
if (is_coinbase)
{
// input_context = "C" || IntToBytes256(block_index)
carrot::make_carrot_input_context_coinbase(boost::get<cryptonote::txin_gen>(first_tx_input).height,
input_context);
}
else
{
// input_context = "R" || KI_1
carrot::make_carrot_input_context(boost::get<cryptonote::txin_to_key>(first_tx_input).k_image,
input_context);
}
//! TODO: return s^ctx_sr from scan function
// s^ctx_sr = H_32(s_sr, D_e, input_context)
crypto::hash s_sender_receiver;
carrot::make_carrot_sender_receiver_secret(s_sender_receiver_unctx.data,
enote_ephemeral_pubkey,
input_context,
s_sender_receiver);
// j
subaddr_receive_info = cryptonote::subaddress_receive_info{
.index = subaddr_it->second,
.derivation = carrot::raw_byte_convert<crypto::key_derivation>(s_sender_receiver)
};
// we don't have the extra main tx pubkey bug in Carrot
res.main_tx_pubkey_index = 0;
}
else // !is_carrot
{
const boost::optional<crypto::view_tag> view_tag = cryptonote::get_output_view_tag(enote_destination);
for (size_t i = 0; i < std::max<size_t>(main_derivations.size(), 1); ++i)
{
// try view-scan, testing view tag if applicable
subaddr_receive_info = cryptonote::is_out_to_acc_precomp(subaddress_map,
res.onetime_address,
(i < main_derivations.size()) ? main_derivations[i] : crypto::key_derivation{},
(i == 0) ? additional_derivations : std::vector<crypto::key_derivation>{},
local_output_index,
acc.get_device(),
view_tag);
if (!subaddr_receive_info)
continue;
// derive k^g_o
if (!acc.get_device().derivation_to_scalar(subaddr_receive_info->derivation,
local_output_index,
res.sender_extension_g))
continue;
// k^t_o = 0
res.sender_extension_t = crypto::null_skey;
// K^j_s'
if (!acc.get_device().derive_subaddress_public_key(res.onetime_address,
subaddr_receive_info->derivation,
local_output_index,
res.address_spend_pubkey))
continue;
res.main_tx_pubkey_index = i;
}
// decrypt pid
const bool should_try_decrypt_pid = !tx_extra_nonce.empty()
&& res.main_tx_pubkey_index < main_tx_ephemeral_pubkeys.size();
if (should_try_decrypt_pid)
{
crypto::hash8 pid_8;
if (cryptonote::get_encrypted_payment_id_from_tx_extra_nonce(tx_extra_nonce, pid_8))
{
if (acc.get_device().decrypt_payment_id(pid_8,
main_tx_ephemeral_pubkeys[res.main_tx_pubkey_index],
acc.m_view_secret_key))
memcpy(&res.payment_id, &pid_8, sizeof(pid_8));
}
}
}
if (!subaddr_receive_info)
return std::nullopt;
res.subaddr_index = carrot::subaddress_index_extended{
.index = { subaddr_receive_info->index.major, subaddr_receive_info->index.minor },
.derive_type = carrot::AddressDeriveType::Auto //! @TODO: handle hybrid devices
};
res.derivation = subaddr_receive_info->derivation;
return res;
}
//-------------------------------------------------------------------------------------------------------------------
std::optional<enote_view_incoming_scan_info_t> try_view_incoming_scan_enote_destination(
const cryptonote::transaction_prefix &tx_prefix,
const carrot::lazy_amount_commitment_t &amount_commitment,
const std::size_t local_output_index,
const cryptonote::account_keys &acc,
const std::unordered_map<crypto::public_key, cryptonote::subaddress_index> &subaddress_map)
{
// 1. parse tx extra
std::vector<crypto::public_key> main_tx_ephemeral_pubkeys;
std::vector<crypto::public_key> additional_tx_ephemeral_pubkeys;
cryptonote::blobdata tx_extra_nonce;
parse_tx_extra_for_scanning(tx_prefix, main_tx_ephemeral_pubkeys, additional_tx_ephemeral_pubkeys, tx_extra_nonce);
// 2. perform ECDH derivations
std::vector<crypto::key_derivation> main_derivations;
std::vector<crypto::key_derivation> additional_derivations;
perform_ecdh_derivations(epee::to_span(main_tx_ephemeral_pubkeys),
epee::to_span(additional_tx_ephemeral_pubkeys),
acc,
carrot::is_carrot_transaction_v1(tx_prefix),
main_derivations,
additional_derivations);
// 3. get first transaction input with a default value
const cryptonote::txin_v tx_first_input = !tx_prefix.vin.empty()
? tx_prefix.vin.at(0)
: cryptonote::txin_v(cryptonote::txin_to_key{});
// 4. view-scan enote destination
return try_view_incoming_scan_enote_destination(tx_prefix.vout.at(local_output_index),
amount_commitment,
epee::to_span(main_tx_ephemeral_pubkeys),
epee::to_span(additional_tx_ephemeral_pubkeys),
tx_extra_nonce,
tx_first_input,
local_output_index,
epee::to_span(main_derivations),
additional_derivations,
acc,
subaddress_map);
}
//-------------------------------------------------------------------------------------------------------------------
bool try_decrypt_enote_amount(const crypto::public_key &onetime_address,
const rct::key &enote_amount_commitment,
const std::uint8_t rct_type,
const rct::ecdhTuple &rct_ecdh_tuple,
const crypto::key_derivation &derivation,
const std::size_t local_output_index,
const crypto::public_key &address_spend_pubkey,
hw::device &hwdev,
rct::xmr_amount &amount_out,
rct::key &amount_blinding_factor_out)
{
const bool is_carrot = rct_type >= carrot::carrot_v1_rct_type;
if (is_carrot)
{
//! @TODO: put this into format utils
carrot::encrypted_amount_t encrypted_amount;
memcpy(&encrypted_amount, &rct_ecdh_tuple.amount, sizeof(encrypted_amount));
const crypto::hash s_sender_receiver = carrot::raw_byte_convert<crypto::hash>(derivation);
carrot::CarrotEnoteType dummy_enote_type;
crypto::secret_key amount_blinding_factor_sk;
if (!carrot::try_get_carrot_amount(s_sender_receiver,
encrypted_amount,
onetime_address,
address_spend_pubkey,
enote_amount_commitment,
dummy_enote_type,
amount_out, // a
amount_blinding_factor_sk))
return false;
// z
amount_blinding_factor_out = rct::sk2rct(amount_blinding_factor_sk);
}
else // !is_carrot
{
crypto::ec_scalar amount_key;
if (!hwdev.derivation_to_scalar(derivation,
local_output_index,
amount_key))
return false;
const bool is_short_amount = rct_type >= rct::RCTTypeBulletproof2;
rct::ecdhTuple decoded_ecdh_tuple = rct_ecdh_tuple;
if (!hwdev.ecdhDecode(decoded_ecdh_tuple,
carrot::raw_byte_convert<rct::key>(amount_key),
is_short_amount))
return false;
// a
amount_out = rct::h2d(decoded_ecdh_tuple.amount);
// z
amount_blinding_factor_out = decoded_ecdh_tuple.mask;
// C' = z G + a H
const rct::key recomputed_amount_commitment = rct::commit(amount_out,
amount_blinding_factor_out);
// C' ?= C
if (!rct::equalKeys(enote_amount_commitment, recomputed_amount_commitment))
return false;
}
return true;
}
//-------------------------------------------------------------------------------------------------------------------
std::optional<enote_view_incoming_scan_info_t> try_view_incoming_scan_enote(
const cryptonote::tx_out &enote_destination,
const rct::rctSigBase &rct_sig,
const epee::span<const crypto::public_key> main_tx_ephemeral_pubkeys,
const epee::span<const crypto::public_key> additional_tx_ephemeral_pubkeys,
const cryptonote::blobdata &tx_extra_nonce,
const cryptonote::txin_v &first_tx_input,
const std::size_t local_output_index,
const epee::span<const crypto::key_derivation> main_derivations,
const std::vector<crypto::key_derivation> &additional_derivations,
const cryptonote::account_keys &acc,
const std::unordered_map<crypto::public_key, cryptonote::subaddress_index> &subaddress_map)
{
const bool is_rct = rct_sig.type != rct::RCTTypeNull;
carrot::lazy_amount_commitment_t amount_commitment;
if (is_rct) amount_commitment = rct_sig.outPk.at(local_output_index).mask;
else amount_commitment = enote_destination.amount;
auto res = try_view_incoming_scan_enote_destination(
enote_destination,
amount_commitment,
main_tx_ephemeral_pubkeys,
additional_tx_ephemeral_pubkeys,
tx_extra_nonce,
first_tx_input,
local_output_index,
main_derivations,
additional_derivations,
acc,
subaddress_map);
if (!res)
return res;
// a, z
if (is_rct)
{
const bool decrypted_amount = try_decrypt_enote_amount(
res->onetime_address,
rct_sig.outPk.at(local_output_index).mask,
rct_sig.type,
rct_sig.ecdhInfo.at(local_output_index),
res->derivation,
res->local_output_index,
res->address_spend_pubkey,
acc.get_device(),
res->amount,
res->amount_blinding_factor);
if (!decrypted_amount)
res.reset();
}
else // !is_rct
{
res->amount = enote_destination.amount;
res->amount_blinding_factor = rct::I;
}
return res;
}
//-------------------------------------------------------------------------------------------------------------------
void view_incoming_scan_transaction(
const cryptonote::transaction &tx,
const epee::span<const crypto::public_key> main_tx_ephemeral_pubkeys,
const epee::span<const crypto::public_key> additional_tx_ephemeral_pubkeys,
const cryptonote::blobdata &tx_extra_nonce,
const epee::span<const crypto::key_derivation> main_derivations,
const std::vector<crypto::key_derivation> &additional_derivations,
const cryptonote::account_keys &acc,
const std::unordered_map<crypto::public_key, cryptonote::subaddress_index> &subaddress_map,
const epee::span<std::optional<enote_view_incoming_scan_info_t>> enote_scan_infos_out)
{
const size_t n_outputs = tx.vout.size();
CHECK_AND_ASSERT_THROW_MES(enote_scan_infos_out.size() == n_outputs,
"view_incoming_scan_transaction: enote scan span wrong length");
// do view-incoming scan for each output enotes
for (size_t local_output_index = 0; local_output_index < n_outputs; ++local_output_index)
{
auto &enote_scan_info = const_cast<std::optional<enote_view_incoming_scan_info_t>&>(enote_scan_infos_out[local_output_index]);
const cryptonote::tx_out &enote_destination = tx.vout.at(local_output_index);
// get first transaction input with a default value
const cryptonote::txin_v tx_first_input = !tx.vin.empty()
? tx.vin.at(0)
: cryptonote::txin_v(cryptonote::txin_to_key{});
enote_scan_info = try_view_incoming_scan_enote(enote_destination,
tx.rct_signatures,
main_tx_ephemeral_pubkeys,
additional_tx_ephemeral_pubkeys,
tx_extra_nonce,
tx_first_input,
local_output_index,
main_derivations,
additional_derivations,
acc,
subaddress_map);
}
}
//-------------------------------------------------------------------------------------------------------------------
void view_incoming_scan_transaction(
const cryptonote::transaction &tx,
const epee::span<const crypto::key_derivation> custom_main_derivations,
const std::vector<crypto::key_derivation> &custom_additional_derivations,
const cryptonote::account_keys &acc,
const std::unordered_map<crypto::public_key, cryptonote::subaddress_index> &subaddress_map,
const epee::span<std::optional<enote_view_incoming_scan_info_t>> enote_scan_infos_out)
{
// 1. parse tx extra
std::vector<crypto::public_key> main_tx_ephemeral_pubkeys;
std::vector<crypto::public_key> additional_tx_ephemeral_pubkeys;
cryptonote::blobdata tx_extra_nonce;
if (!parse_tx_extra_for_scanning(tx, main_tx_ephemeral_pubkeys, additional_tx_ephemeral_pubkeys, tx_extra_nonce))
MWARNING("Transaction extra has unsupported format: " << cryptonote::get_transaction_hash(tx));
// 2. view-incoming scan output enotes
view_incoming_scan_transaction(tx,
epee::to_span(main_tx_ephemeral_pubkeys),
epee::to_span(additional_tx_ephemeral_pubkeys),
tx_extra_nonce,
custom_main_derivations,
custom_additional_derivations,
acc,
subaddress_map,
enote_scan_infos_out);
}
//-------------------------------------------------------------------------------------------------------------------
void view_incoming_scan_transaction(
const cryptonote::transaction &tx,
const cryptonote::account_keys &acc,
const std::unordered_map<crypto::public_key, cryptonote::subaddress_index> &subaddress_map,
const epee::span<std::optional<enote_view_incoming_scan_info_t>> enote_scan_infos_out)
{
// 1. parse tx extra
std::vector<crypto::public_key> main_tx_ephemeral_pubkeys;
std::vector<crypto::public_key> additional_tx_ephemeral_pubkeys;
cryptonote::blobdata tx_extra_nonce;
if (!parse_tx_extra_for_scanning(tx, main_tx_ephemeral_pubkeys, additional_tx_ephemeral_pubkeys, tx_extra_nonce))
MWARNING("Transaction extra has unsupported format: " << cryptonote::get_transaction_hash(tx));
// 2. perform ECDH derivations
std::vector<crypto::key_derivation> main_derivations;
std::vector<crypto::key_derivation> additional_derivations;
perform_ecdh_derivations(epee::to_span(main_tx_ephemeral_pubkeys),
epee::to_span(additional_tx_ephemeral_pubkeys),
acc,
carrot::is_carrot_transaction_v1(tx),
main_derivations,
additional_derivations);
// 3. view-incoming scan output enotes
view_incoming_scan_transaction(tx,
epee::to_span(main_tx_ephemeral_pubkeys),
epee::to_span(additional_tx_ephemeral_pubkeys),
tx_extra_nonce,
epee::to_span(main_derivations),
additional_derivations,
acc,
subaddress_map,
enote_scan_infos_out);
}
//-------------------------------------------------------------------------------------------------------------------
std::vector<std::optional<enote_view_incoming_scan_info_t>> view_incoming_scan_transaction(
const cryptonote::transaction &tx,
const cryptonote::account_keys &acc,
const std::unordered_map<crypto::public_key, cryptonote::subaddress_index> &subaddress_map)
{
std::vector<std::optional<enote_view_incoming_scan_info_t>> res(tx.vout.size());
view_incoming_scan_transaction(tx, acc, subaddress_map, epee::to_mut_span(res));
return res;
}
//-------------------------------------------------------------------------------------------------------------------
bool is_long_payment_id(const crypto::hash &pid)
{
static_assert(sizeof(pid.data) / sizeof(pid.data[0]) == 32);
char c = 0;
for (size_t i = 8; i < 32; i++)
c |= pid.data[i];
return c != 0;
}
//-------------------------------------------------------------------------------------------------------------------
std::optional<crypto::key_image> try_derive_enote_key_image(
const enote_view_incoming_scan_info_t &enote_scan_info,
const cryptonote::account_keys &acc)
{
if (!enote_scan_info.subaddr_index)
return std::nullopt;
const cryptonote::subaddress_index subaddr_index_cn{
enote_scan_info.subaddr_index->index.major,
enote_scan_info.subaddr_index->index.minor
};
const bool is_carrot = enote_scan_info.sender_extension_t != crypto::null_skey;
if (is_carrot)
{
//! @TODO: compact helper function like generate_key_image_helper_precomp()
//! @TODO: verify x G + y T = O to prevent downstream debugging issues
// I = Hp(O)
crypto::ec_point ki_generator;
crypto::derive_key_image_generator(enote_scan_info.onetime_address, ki_generator);
//! @TODO: HW devices
rct::key subaddress_extension;
if (subaddr_index_cn.is_zero())
subaddress_extension = rct::Z;
else // !subaddr_index_cn.is_zero()
subaddress_extension = rct::sk2rct(
acc.get_device().get_subaddress_secret_key(acc.m_view_secret_key, subaddr_index_cn));
// x = k_s + k^j_subext + k_o
rct::key x;
sc_add(x.bytes,
to_bytes(acc.m_spend_secret_key),
to_bytes(enote_scan_info.sender_extension_g));
sc_add(x.bytes, x.bytes, subaddress_extension.bytes);
// L = x I = (k_s + k^j_subext + k_o) Hp(O)
return rct::rct2ki(rct::scalarmultKey(rct::pt2rct(ki_generator), x));
}
else // !is_carrot
{
crypto::key_image ki;
cryptonote::keypair ota_kp;
if (!cryptonote::generate_key_image_helper_precomp(acc,
enote_scan_info.onetime_address,
enote_scan_info.derivation,
enote_scan_info.local_output_index,
subaddr_index_cn,
ota_kp,
ki,
acc.get_device()))
return std::nullopt;
return ki;
}
}
//-------------------------------------------------------------------------------------------------------------------
} //namespace wallet
} //namespace tools