use internal notes instead of special ones for change and return enotes (#36)

This commit is contained in:
akildemir
2025-08-01 15:54:59 +03:00
committed by GitHub
parent a8ee0a90e0
commit cbe84499fb
15 changed files with 374 additions and 336 deletions
+26 -186
View File
@@ -260,104 +260,6 @@ static std::optional<enote_view_incoming_scan_info_t> view_incoming_scan_pre_car
};
}
//-------------------------------------------------------------------------------------------------------------------
static bool scan_return_output(
const crypto::public_key &return_onetime_address,
const mx25519_pubkey &return_ephemeral_pubkey,
const carrot::view_tag_t &return_view_tag,
const carrot::encrypted_janus_anchor_t &return_anchor_enc,
const carrot::encrypted_amount_t &return_amount_enc,
const std::optional<rct::key> amount_commitment,
const carrot::input_context_t &return_input_context,
carrot::carrot_and_legacy_account &account,
crypto::public_key &address_spend_pubkey_out,
rct::xmr_amount &amount_out,
crypto::secret_key &amount_blinding_factor_out
) {
const auto &return_output_map = account.get_return_output_map_ref();
CHECK_AND_ASSERT_MES(return_output_map.count(return_onetime_address), false, "return output not found");
const auto &origin_tx = return_output_map.at(return_onetime_address);
// 1. make k_return
crypto::secret_key k_return;
account.k_view_incoming_dev.make_internal_return_privkey(origin_tx.input_context, origin_tx.K_o, k_return);
// 2. compute K_return' = k_return * G
crypto::public_key K_return;
crypto::secret_key_to_public_key(k_return, K_return);
// 3. ssr
mx25519_pubkey shared_secret_return_unctx;
crypto::hash shared_secret_return;
carrot::make_carrot_uncontextualized_shared_key_receiver(k_return, return_ephemeral_pubkey, shared_secret_return_unctx);
carrot::make_carrot_sender_receiver_secret(
shared_secret_return_unctx.data,
return_ephemeral_pubkey,
return_input_context,
shared_secret_return
);
// 4. verify the view_tag
CHECK_AND_ASSERT_MES(
carrot::test_carrot_view_tag(
shared_secret_return_unctx.data,
return_input_context,
return_onetime_address,
return_view_tag
),
false,
"view tag verification failed for carrot coinbase enote"
);
// 5. compute anchor_return
carrot::janus_anchor_t recovered_anchor_return =
carrot::decrypt_carrot_anchor(return_anchor_enc, shared_secret_return, return_onetime_address);
// 6. compute d_e'
crypto::secret_key recovered_ephemeral_privkey_return;
carrot::make_carrot_enote_ephemeral_privkey(
recovered_anchor_return,
return_input_context,
origin_tx.K_change,
carrot::null_payment_id,
recovered_ephemeral_privkey_return
);
// 7. compute D_e'
mx25519_pubkey recovered_ephemeral_pubkey_return;
carrot::make_carrot_enote_ephemeral_pubkey(
recovered_ephemeral_privkey_return,
origin_tx.K_change,
false,
recovered_ephemeral_pubkey_return
);
// 8. verify the enote ephemeral pubkey
CHECK_AND_ASSERT_MES(
memcmp(recovered_ephemeral_pubkey_return.data, return_ephemeral_pubkey.data, sizeof(mx25519_pubkey)) == 0,
false,
"carrot coinbase enote protection verification failed"
);
amount_out = carrot::decrypt_carrot_amount(return_amount_enc, shared_secret_return, return_onetime_address);
address_spend_pubkey_out = origin_tx.K_change;
if (amount_commitment)
{
CHECK_AND_ASSERT_MES(
carrot::try_recompute_carrot_amount_commitment(shared_secret_return,
amount_out,
address_spend_pubkey_out,
carrot::CarrotEnoteType::PAYMENT,
amount_commitment.value(),
amount_blinding_factor_out
),
false,
"failed to recompute carrot amount commitment for return output"
);
}
return true;
}
//-------------------------------------------------------------------------------------------------------------------
static std::optional<enote_view_incoming_scan_info_t> view_incoming_scan_carrot_coinbase_enote(
const carrot::CarrotCoinbaseEnoteV1 &enote,
const mx25519_pubkey &s_sender_receiver_unctx,
@@ -374,19 +276,14 @@ static std::optional<enote_view_incoming_scan_info_t> view_incoming_scan_carrot_
res.sender_extension_t))
{
// check for known return addresses
const auto &subaddress_map = account.get_subaddress_map_ref();
if (subaddress_map.find(enote.onetime_address) == subaddress_map.end())
const auto &return_map = account.get_return_output_map_ref();
if (return_map.find(enote.onetime_address) == return_map.end())
return std::nullopt;
found_in_return = true;
}
if (found_in_return) {
CHECK_AND_ASSERT_MES(
account.get_return_output_map_ref().count(enote.onetime_address),
std::nullopt,
"return output not found"
);
// scan the return output
crypto::public_key address_spend_pubkey;
carrot::encrypted_amount_t amount_enc;
@@ -404,7 +301,7 @@ static std::optional<enote_view_incoming_scan_info_t> view_incoming_scan_carrot_
account,
address_spend_pubkey,
amount,
amount_blinding_factor)
amount_blinding_factor)
) {
return std::nullopt;
}
@@ -417,7 +314,7 @@ static std::optional<enote_view_incoming_scan_info_t> view_incoming_scan_carrot_
res.address_spend_pubkey = main_address_spend_pubkey;
res.is_return = false;
}
res.payment_id = crypto::null_hash;
res.subaddr_index = carrot::subaddress_index_extended{{0, 0}};
res.amount = enote.amount;
@@ -488,10 +385,13 @@ static std::optional<enote_view_incoming_scan_info_t> view_incoming_scan_carrot_
{
enote_view_incoming_scan_info_t res;
// assume not a return output
res.is_return = false;
crypto::secret_key amount_blinding_factor_sk;
carrot::payment_id_t payment_id;
carrot::CarrotEnoteType dummy_enote_type;
bool found_in_return = false;
carrot::janus_anchor_t internal_message;
if (!carrot::try_scan_carrot_enote_external_receiver(enote,
encrypted_payment_id,
s_sender_receiver_unctx,
@@ -505,95 +405,35 @@ static std::optional<enote_view_incoming_scan_info_t> view_incoming_scan_carrot_
payment_id,
dummy_enote_type))
{
// check for known return addresses
const auto &subaddress_map = account.get_subaddress_map_ref();
if (subaddress_map.find(enote.onetime_address) == subaddress_map.end())
return std::nullopt;
found_in_return = true;
}
if (found_in_return) {
// scan the return output
crypto::public_key address_spend_pubkey;
if (!scan_return_output(
enote.onetime_address,
enote.enote_ephemeral_pubkey,
enote.view_tag,
enote.anchor_enc,
enote.amount_enc,
enote.amount_commitment,
carrot::make_carrot_input_context(enote.tx_first_key_image),
account,
res.address_spend_pubkey,
res.amount,
amount_blinding_factor_sk)
) {
if (!carrot::try_scan_carrot_enote_internal_receiver(enote,
account,
res.sender_extension_g,
res.sender_extension_t,
res.address_spend_pubkey,
res.amount,
amount_blinding_factor_sk,
dummy_enote_type,
internal_message,
res.return_address,
res.is_return))
{
return std::nullopt;
}
res.address_spend_pubkey = address_spend_pubkey;
res.return_address = enote.onetime_address;
res.is_return = true;
} else {
// we received a normal enote
res.address_spend_pubkey = main_address_spend_pubkey;
res.is_return = false;
}
if (!found_in_return) {
if (!res.is_return) {
const auto subaddr_it = account.get_subaddress_map_ref().find(res.address_spend_pubkey);
CHECK_AND_ASSERT_MES(subaddr_it != account.get_subaddress_map_ref().cend(),
std::nullopt,
"view_incoming_scan_carrot_enote: carrot enote scanned successfully, "
"but the recovered address spend pubkey was not found in the subaddress map");
std::nullopt,
"view_incoming_scan_carrot_enote: carrot enote scanned successfully, "
"but the recovered address spend pubkey was not found in the subaddress map");
const carrot::subaddress_index_extended subaddr_index = subaddr_it->second;
memset(&res.payment_id, 0, sizeof(res.payment_id));
memcpy(&res.payment_id, &payment_id, sizeof(carrot::payment_id_t));
// we received and output
// save the Kr = K_change + K_return to out subaddress map
for (const auto &output_key : enote.tx_output_keys) {
// make k_return
crypto::secret_key k_return;
const carrot::input_context_t input_context = carrot::make_carrot_input_context(enote.tx_first_key_image);
k_view_dev.make_internal_return_privkey(input_context, output_key, k_return);
// compute K_return = k_return * G
crypto::public_key K_return;
crypto::secret_key_to_public_key(k_return, K_return);
// compute K_r = K_return + K_o
crypto::public_key K_r = rct::rct2pk(rct::addKeys(rct::pk2rct(K_return), rct::pk2rct(enote.onetime_address)));
account.insert_subaddresses({{K_r, {{subaddr_index.index.major, subaddr_index.index.minor},
carrot::AddressDeriveType::Carrot, true}}});
// calculate the key image for the return output
crypto::secret_key sum_g;
sc_add(to_bytes(sum_g), to_bytes(res.sender_extension_g), to_bytes(k_return));
crypto::key_image key_image = account.derive_key_image(
account.get_keys().m_carrot_account_address.m_spend_public_key,
sum_g,
res.sender_extension_t,
K_r
);
crypto::secret_key x, y;
account.try_searching_for_opening_for_onetime_address(
account.get_keys().m_carrot_account_address.m_spend_public_key,
sum_g,
res.sender_extension_t,
x,
y
);
// save the input context & change output key
account.insert_return_output_info({{K_r, {input_context, output_key, enote.onetime_address, key_image, x, y}}});
}
res.subaddr_index = subaddr_index;
} else {
res.subaddr_index = carrot::subaddress_index_extended{{0, 0}};
res.subaddr_index = {{0, 0}, carrot::AddressDeriveType::Carrot, true};
}
res.amount_blinding_factor = rct::sk2rct(amount_blinding_factor_sk);
+1 -1
View File
@@ -31,7 +31,7 @@
//local headers
#include "carrot_core/carrot_enote_types.h"
#include "carrot_core/device.h"
#include "carrot_impl/account.h"
#include "carrot_core/account.h"
#include "carrot_impl/subaddress_index.h"
#include "crypto/crypto.h"
#include "cryptonote_basic/account.h"
+82 -69
View File
@@ -823,86 +823,99 @@ bool get_address_openings_x_y(
return true;
}
const std::vector<crypto::public_key> v_pubkeys{src.real_out_tx_key};
const std::vector<crypto::public_key> v_pubkeys_empty{};
const epee::span<const crypto::public_key> main_tx_ephemeral_pubkeys = (src.real_out_tx_key == crypto::null_pkey) ? epee::to_span(v_pubkeys_empty) : epee::to_span(v_pubkeys);
const epee::span<const crypto::public_key> additional_tx_ephemeral_pubkeys = epee::to_span(src.real_out_additional_tx_keys);
// 2. perform ECDH derivations
std::vector<crypto::key_derivation> main_derivations;
std::vector<crypto::key_derivation> additional_derivations;
bool is_carrot = carrot::is_carrot_transaction_v1(tx);
wallet::perform_ecdh_derivations(
main_tx_ephemeral_pubkeys,
additional_tx_ephemeral_pubkeys,
is_carrot ? w.get_account().get_keys().k_view_incoming : w.get_account().get_keys().m_view_secret_key,
w.get_account().get_keys().get_device(),
is_carrot,
main_derivations,
additional_derivations
);
// we have to try both internal and external derivations
bool r = false;
for (size_t i = 0; i < 2; ++i) {
// perform ECDH derivations
std::vector<crypto::key_derivation> main_derivations;
std::vector<crypto::key_derivation> additional_derivations;
if (i == 0) {
wallet::perform_ecdh_derivations(
main_tx_ephemeral_pubkeys,
additional_tx_ephemeral_pubkeys,
w.get_account().get_keys().k_view_incoming,
w.get_account().get_keys().get_device(),
src.carrot,
main_derivations,
additional_derivations
);
} else {
crypto::key_derivation main_derivation;
memcpy(main_derivation.data, w.get_account().get_keys().s_view_balance.data, sizeof(crypto::secret_key));
main_derivations.push_back(main_derivation);
}
crypto::hash s_sender_receiver;
const crypto::key_derivation &kd = main_derivations.size()
? main_derivations[0]
: additional_derivations[src.real_output_in_tx_index];
const mx25519_pubkey s_sender_receiver_unctx = carrot::raw_byte_convert<mx25519_pubkey>(kd);
crypto::hash s_sender_receiver;
const crypto::key_derivation &kd = main_derivations.size()
? main_derivations[0]
: additional_derivations[src.real_output_in_tx_index];
const mx25519_pubkey s_sender_receiver_unctx = carrot::raw_byte_convert<mx25519_pubkey>(kd);
// ephemeral pubkeys
const epee::span<const crypto::public_key> enote_ephemeral_pubkeys_pk =
main_tx_ephemeral_pubkeys.empty() ? additional_tx_ephemeral_pubkeys : main_tx_ephemeral_pubkeys;
const epee::span<const mx25519_pubkey> enote_ephemeral_pubkeys = {
reinterpret_cast<const mx25519_pubkey*>(enote_ephemeral_pubkeys_pk.data()),
enote_ephemeral_pubkeys_pk.size()
};
// ephemeral pubkeys
const epee::span<const crypto::public_key> enote_ephemeral_pubkeys_pk =
main_tx_ephemeral_pubkeys.empty() ? additional_tx_ephemeral_pubkeys : main_tx_ephemeral_pubkeys;
const epee::span<const mx25519_pubkey> enote_ephemeral_pubkeys = {
reinterpret_cast<const mx25519_pubkey*>(enote_ephemeral_pubkeys_pk.data()),
enote_ephemeral_pubkeys_pk.size()
};
const bool shared_ephemeral_pubkey = enote_ephemeral_pubkeys.size() == 1;
const size_t ephemeral_pubkey_index = shared_ephemeral_pubkey ? 0 : src.real_output_in_tx_index;
const bool shared_ephemeral_pubkey = enote_ephemeral_pubkeys.size() == 1;
const size_t ephemeral_pubkey_index = shared_ephemeral_pubkey ? 0 : src.real_output_in_tx_index;
// input_context
carrot::input_context_t input_context;
if (src.coinbase) {
input_context = carrot::make_carrot_input_context_coinbase(src.block_index);
} else {
input_context = carrot::make_carrot_input_context(src.first_rct_key_image);
// input_context
carrot::input_context_t input_context;
if (src.coinbase) {
input_context = carrot::make_carrot_input_context_coinbase(src.block_index);
} else {
input_context = carrot::make_carrot_input_context(src.first_rct_key_image);
}
// s^ctx_sr = H_32(s_sr, D_e, input_context)
make_carrot_sender_receiver_secret(s_sender_receiver_unctx.data,
enote_ephemeral_pubkeys[ephemeral_pubkey_index],
input_context,
s_sender_receiver);
// get the k_og and k_ot
crypto::secret_key sender_extension_g_out;
crypto::secret_key sender_extension_t_out;
crypto::public_key address_spend_pubkey_out;
carrot::payment_id_t nominal_payment_id_out;
carrot::janus_anchor_t nominal_janus_anchor_out;
carrot::encrypted_janus_anchor_t encrypted_janus_anchor;
carrot::encrypted_payment_id_t encrypted_payment_id;
carrot::scan_carrot_dest_info(
rct::rct2pk(src.outputs[src.real_output].second.dest),
src.outputs[src.real_output].second.mask,
encrypted_janus_anchor,
encrypted_payment_id,
s_sender_receiver,
sender_extension_g_out,
sender_extension_t_out,
address_spend_pubkey_out,
nominal_payment_id_out,
nominal_janus_anchor_out
);
r = w.get_account().try_searching_for_opening_for_onetime_address(
address_spend_pubkey_out,
sender_extension_g_out,
sender_extension_t_out,
x_out,
y_out
);
// If we found the opening, we can stop here
if (r) {
break;
}
}
// s^ctx_sr = H_32(s_sr, D_e, input_context)
make_carrot_sender_receiver_secret(s_sender_receiver_unctx.data,
enote_ephemeral_pubkeys[ephemeral_pubkey_index],
input_context,
s_sender_receiver);
// get the k_og and k_ot
crypto::secret_key sender_extension_g_out;
crypto::secret_key sender_extension_t_out;
crypto::public_key address_spend_pubkey_out;
carrot::payment_id_t nominal_payment_id_out;
carrot::janus_anchor_t nominal_janus_anchor_out;
carrot::encrypted_janus_anchor_t encrypted_janus_anchor;
carrot::encrypted_payment_id_t encrypted_payment_id;
carrot::scan_carrot_dest_info(
rct::rct2pk(src.outputs[src.real_output].second.dest),
src.outputs[src.real_output].second.mask,
encrypted_janus_anchor,
encrypted_payment_id,
s_sender_receiver,
sender_extension_g_out,
sender_extension_t_out,
address_spend_pubkey_out,
nominal_payment_id_out,
nominal_janus_anchor_out
);
bool r = w.get_account().try_searching_for_opening_for_onetime_address(
address_spend_pubkey_out,
sender_extension_g_out,
sender_extension_t_out,
x_out,
y_out
);
CHECK_AND_ASSERT_THROW_MES(r, "Failed to obtain openings for onetime address");
return true;
}
//-------------------------------------------------------------------------------------------------------------------
@@ -1020,7 +1033,7 @@ cryptonote::transaction finalize_all_proofs_from_transfer_details(
carrot::get_output_enote_proposals(tx_proposal.normal_payment_proposals,
selfsend_payment_proposal_cores,
tx_proposal.dummy_encrypted_payment_id,
nullptr,
&w.get_account().s_view_balance_dev,
&addr_dev,
tx_proposal.key_images_sorted.at(0),
output_enote_proposals,
+1 -1
View File
@@ -47,7 +47,7 @@
#include <random>
#include "include_base_utils.h"
#include "carrot_impl/account.h"
#include "carrot_core/account.h"
#include "carrot_impl/carrot_offchain_serialization.h"
#include "cryptonote_basic/account.h"
#include "cryptonote_basic/account_boost_serialization.h"