diff --git a/src/carrot_core/enote_utils.cpp b/src/carrot_core/enote_utils.cpp index 278500ce043..95748581c45 100644 --- a/src/carrot_core/enote_utils.cpp +++ b/src/carrot_core/enote_utils.cpp @@ -159,12 +159,13 @@ static void make_carrot_sender_extension_pubkey(const crypto::hash &s_sender_rec void make_carrot_enote_ephemeral_privkey(const janus_anchor_t &anchor_norm, const input_context_t &input_context, const crypto::public_key &address_spend_pubkey, + const crypto::public_key &address_view_pubkey, const payment_id_t payment_id, crypto::secret_key &enote_ephemeral_privkey_out) { - // d_e = (H_64(anchor_norm, input_context, K^j_s, pid)) mod l + // d_e = (H_64(anchor_norm, input_context, K^j_s, K^j_v, pid)) mod l const auto transcript = make_fixed_transcript( - anchor_norm, input_context, address_spend_pubkey, payment_id); + anchor_norm, input_context, address_spend_pubkey, address_view_pubkey, payment_id); derive_scalar(transcript.data(), transcript.size(), nullptr, &enote_ephemeral_privkey_out); } //------------------------------------------------------------------------------------------------------------------- @@ -625,15 +626,17 @@ bool try_get_carrot_amount(const crypto::hash &s_sender_receiver_ctx, bool verify_carrot_normal_janus_protection(const janus_anchor_t &nominal_anchor, const input_context_t &input_context, const crypto::public_key &nominal_address_spend_pubkey, + const crypto::public_key &nominal_address_view_pubkey, const bool is_subaddress, const payment_id_t nominal_payment_id, const mx25519_pubkey &enote_ephemeral_pubkey) { - // d_e' = H_n(anchor_norm, input_context, K^j_s, pid) + // d_e' = H_n(anchor_norm, input_context, K^j_s, K^j_v, pid) crypto::secret_key nominal_enote_ephemeral_privkey; make_carrot_enote_ephemeral_privkey(nominal_anchor, input_context, nominal_address_spend_pubkey, + nominal_address_view_pubkey, nominal_payment_id, nominal_enote_ephemeral_privkey); diff --git a/src/carrot_core/enote_utils.h b/src/carrot_core/enote_utils.h index e480b4f8b5a..0756cd1ccb1 100644 --- a/src/carrot_core/enote_utils.h +++ b/src/carrot_core/enote_utils.h @@ -46,16 +46,18 @@ namespace carrot /** * @brief Derive enote ephemeral privkey d_e for Carrot enotes - * d_e = H_n(anchor_norm, input_context, K^j_s, pid) + * d_e = H_n(anchor_norm, input_context, K^j_s, K^j_v, pid) * @param anchor_norm normal Janus anchor * @param input_context input_context * @param address_spend_pubkey K^j_s + * @param address_view_pubkey K^j_v * @param payment_id pid * @param[out] enote_ephemeral_privkey_out k_e */ void make_carrot_enote_ephemeral_privkey(const janus_anchor_t &anchor_norm, const input_context_t &input_context, const crypto::public_key &address_spend_pubkey, + const crypto::public_key &address_view_pubkey, const payment_id_t payment_id, crypto::secret_key &enote_ephemeral_privkey_out); /** @@ -419,6 +421,7 @@ bool try_get_carrot_amount(const crypto::hash &s_sender_receiver_ctx, * @param nominal_anchor anchor' * @param input_context - * @param nominal_address_spend_pubkey K^j_s' + * @param nominal_address_view_pubkey K^j_v' * @param is_subaddress - * @param nominal_payment_id pid' * @param enote_ephemeral_pubkey D_e @@ -427,6 +430,7 @@ bool try_get_carrot_amount(const crypto::hash &s_sender_receiver_ctx, bool verify_carrot_normal_janus_protection(const janus_anchor_t &nominal_anchor, const input_context_t &input_context, const crypto::public_key &nominal_address_spend_pubkey, + const crypto::public_key &nominal_address_view_pubkey, const bool is_subaddress, const payment_id_t nominal_payment_id, const mx25519_pubkey &enote_ephemeral_pubkey); diff --git a/src/carrot_core/payment_proposal.cpp b/src/carrot_core/payment_proposal.cpp index 336f3f1966b..92f76f0cb08 100644 --- a/src/carrot_core/payment_proposal.cpp +++ b/src/carrot_core/payment_proposal.cpp @@ -64,7 +64,7 @@ static void get_normal_proposal_ecdh_parts(const CarrotPaymentProposalV1 &propos mx25519_pubkey &enote_ephemeral_pubkey_out, mx25519_pubkey &s_sender_receiver_out) { - // 1. d_e = H_n(anchor_norm, input_context, K^j_s, pid) + // 1. d_e = H_n(anchor_norm, input_context, K^j_s, K^j_v, pid) const crypto::secret_key enote_ephemeral_privkey = get_enote_ephemeral_privkey(proposal, input_context); // 2. make D_e @@ -193,11 +193,12 @@ bool operator==(const CarrotPaymentProposalSelfSendV1 &a, const CarrotPaymentPro crypto::secret_key get_enote_ephemeral_privkey(const CarrotPaymentProposalV1 &proposal, const input_context_t &input_context) { - // d_e = H_n(anchor_norm, input_context, K^j_s, pid) + // d_e = H_n(anchor_norm, input_context, K^j_s, K^j_v, pid) crypto::secret_key enote_ephemeral_privkey; make_carrot_enote_ephemeral_privkey(proposal.randomness, input_context, proposal.destination.address_spend_pubkey, + proposal.destination.address_view_pubkey, proposal.destination.payment_id, enote_ephemeral_privkey); @@ -240,7 +241,7 @@ crypto::secret_key get_enote_ephemeral_privkey(const CarrotPaymentProposalSelfSe mx25519_pubkey get_enote_ephemeral_pubkey(const CarrotPaymentProposalV1 &proposal, const input_context_t &input_context) { - // d_e = H_n(anchor_norm, input_context, K^j_s, pid) + // d_e = H_n(anchor_norm, input_context, K^j_s, K^j_v, pid) const crypto::secret_key enote_ephemeral_privkey{get_enote_ephemeral_privkey(proposal, input_context)}; // D_e = d_e * ... diff --git a/src/carrot_core/scan.cpp b/src/carrot_core/scan.cpp index dfc3dfa72df..6c53740558b 100644 --- a/src/carrot_core/scan.cpp +++ b/src/carrot_core/scan.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2025, The Monero Project +// Copyright (c) 2025-2026, The Monero Project // // All rights reserved. // @@ -32,15 +32,18 @@ #include "scan.h" //local headers +#include "crypto/generators.h" #include "destination.h" #include "enote_utils.h" -#include "ringct/rctOps.h" #include "scan_unsafe.h" //third party headers //standard headers +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "carrot.scan" + namespace carrot { @@ -56,15 +59,31 @@ static bool is_main_address_spend_pubkey(const crypto::public_key &address_spend } //------------------------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------------------------- +static bool recover_address_view_pubkey(const crypto::public_key &address_spend_pubkey, + const epee::span main_address_spend_pubkeys, + const view_incoming_key_device &k_view_dev, + crypto::public_key &address_view_pubkey_out, + bool &is_subaddress_out) +{ + // K^j_v = k_v K_base, where: + // [subaddress] K_base = K^j_s + // [main address] K_base = G + is_subaddress_out = !is_main_address_spend_pubkey(address_spend_pubkey, main_address_spend_pubkeys); + address_view_pubkey_out = is_subaddress_out ? address_spend_pubkey : crypto::get_G(); + return k_view_dev.view_key_scalar_mult_ed25519(address_view_pubkey_out, address_view_pubkey_out); +} +//------------------------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------------------------- static crypto::secret_key get_enote_ephemeral_privkey_sender(const janus_anchor_t &anchor_norm, const CarrotDestinationV1 &destination, const input_context_t &input_context) { - // d_e = H_n(anchor_norm, input_context, K^j_s, pid) + // d_e = H_n(anchor_norm, input_context, K^j_s, K^j_v, pid) crypto::secret_key enote_ephemeral_privkey; make_carrot_enote_ephemeral_privkey(anchor_norm, input_context, destination.address_spend_pubkey, + destination.address_view_pubkey, destination.payment_id, enote_ephemeral_privkey); return enote_ephemeral_privkey; @@ -74,6 +93,7 @@ static bool try_scan_carrot_coinbase_enote_checked( const CarrotCoinbaseEnoteV1 &enote, const mx25519_pubkey &s_sender_receiver, const epee::span main_address_spend_pubkeys, + const crypto::public_key &main_address_view_pubkey, crypto::secret_key &sender_extension_g_out, crypto::secret_key &sender_extension_t_out, crypto::public_key &address_spend_pubkey_out) @@ -92,49 +112,12 @@ static bool try_scan_carrot_coinbase_enote_checked( return verify_carrot_normal_janus_protection(nominal_janus_anchor, make_carrot_input_context_coinbase(enote.block_index), address_spend_pubkey_out, + main_address_view_pubkey, /*is_subaddress=*/false, null_payment_id, enote.enote_ephemeral_pubkey); } //------------------------------------------------------------------------------------------------------------------- -static bool try_scan_carrot_enote_external_normal_checked(const CarrotEnoteV1 &enote, - const std::optional &encrypted_payment_id, - const mx25519_pubkey &s_sender_receiver, - const epee::span main_address_spend_pubkeys, - crypto::secret_key &sender_extension_g_out, - crypto::secret_key &sender_extension_t_out, - crypto::public_key &address_spend_pubkey_out, - xmr_amount &amount_out, - crypto::secret_key &amount_blinding_factor_out, - payment_id_t &payment_id_out, - CarrotEnoteType &enote_type_out, - janus_anchor_t &nominal_janus_anchor_out, - bool &verified_normal_janus) -{ - if (!try_scan_carrot_enote_external_no_janus(enote, - encrypted_payment_id, - s_sender_receiver, - sender_extension_g_out, - sender_extension_t_out, - address_spend_pubkey_out, - amount_out, - amount_blinding_factor_out, - payment_id_out, - enote_type_out, - nominal_janus_anchor_out)) - return false; - - verified_normal_janus = verify_carrot_normal_janus_protection( - make_carrot_input_context(enote.tx_first_key_image), - address_spend_pubkey_out, - !is_main_address_spend_pubkey(address_spend_pubkey_out, main_address_spend_pubkeys), - enote.enote_ephemeral_pubkey, - nominal_janus_anchor_out, - payment_id_out); - - return true; -} -//------------------------------------------------------------------------------------------------------------------- bool try_make_carrot_shared_key_receiver( const view_incoming_key_device &k_view_dev, const mx25519_pubkey &enote_ephemeral_pubkey, @@ -179,6 +162,7 @@ bool try_scan_carrot_coinbase_enote_sender( if (!try_scan_carrot_coinbase_enote_checked(enote, s_sender_receiver, {&destination.address_spend_pubkey, 1}, + destination.address_view_pubkey, sender_extension_g_out, sender_extension_t_out, dummy_main_address_spend_pubkey)) @@ -192,6 +176,7 @@ bool try_scan_carrot_coinbase_enote_receiver( const CarrotCoinbaseEnoteV1 &enote, const mx25519_pubkey &s_sender_receiver, const epee::span main_address_spend_pubkeys, + const crypto::public_key &main_address_view_pubkey, crypto::secret_key &sender_extension_g_out, crypto::secret_key &sender_extension_t_out, crypto::public_key &main_address_spend_pubkey_out) @@ -199,6 +184,7 @@ bool try_scan_carrot_coinbase_enote_receiver( return try_scan_carrot_coinbase_enote_checked(enote, s_sender_receiver, main_address_spend_pubkeys, + main_address_view_pubkey, sender_extension_g_out, sender_extension_t_out, main_address_spend_pubkey_out); @@ -208,6 +194,7 @@ bool try_scan_carrot_coinbase_enote_receiver( const CarrotCoinbaseEnoteV1 &enote, const mx25519_pubkey &s_sender_receiver, const crypto::public_key &main_address_spend_pubkey, + const crypto::public_key &main_address_view_pubkey, crypto::secret_key &sender_extension_g_out, crypto::secret_key &sender_extension_t_out) { @@ -216,6 +203,7 @@ bool try_scan_carrot_coinbase_enote_receiver( enote, s_sender_receiver, {&main_address_spend_pubkey, 1}, + main_address_view_pubkey, sender_extension_g_out, sender_extension_t_out, dummy_main_address_spend_pubkey); @@ -291,31 +279,34 @@ bool try_scan_carrot_enote_external_sender(const CarrotEnoteV1 &enote, { crypto::public_key recovered_address_spend_pubkey; payment_id_t recovered_payment_id; - CarrotEnoteType recovered_enote_type; - janus_anchor_t dummy_janus_anchor; - bool verified_normal_janus = false; - if (!try_scan_carrot_enote_external_normal_checked(enote, + janus_anchor_t recovered_janus_anchor; + if (!try_scan_carrot_enote_external_no_janus(enote, encrypted_payment_id, s_sender_receiver, - {&destination.address_spend_pubkey, 1}, sender_extension_g_out, sender_extension_t_out, recovered_address_spend_pubkey, amount_out, amount_blinding_factor_out, recovered_payment_id, - recovered_enote_type, - dummy_janus_anchor, - verified_normal_janus)) + enote_type_out, + recovered_janus_anchor)) return false; - else if (!verified_normal_janus) + + if (recovered_address_spend_pubkey != destination.address_spend_pubkey) return false; - else if (recovered_address_spend_pubkey != destination.address_spend_pubkey) + + if (!verify_carrot_normal_janus_protection( + make_carrot_input_context(enote.tx_first_key_image), + destination.address_spend_pubkey, + destination.address_view_pubkey, + destination.is_subaddress, + enote.enote_ephemeral_pubkey, + recovered_janus_anchor, + recovered_payment_id)) return false; else if (check_pid && recovered_payment_id != destination.payment_id) return false; - else if (recovered_enote_type != CarrotEnoteType::PAYMENT) - return false; return true; } @@ -334,11 +325,9 @@ bool try_scan_carrot_enote_external_receiver(const CarrotEnoteV1 &enote, CarrotEnoteType &enote_type_out) { janus_anchor_t nominal_janus_anchor; - bool verified_normal_janus = false; - if (!try_scan_carrot_enote_external_normal_checked(enote, + if (!try_scan_carrot_enote_external_no_janus(enote, encrypted_payment_id, s_sender_receiver, - main_address_spend_pubkeys, sender_extension_g_out, sender_extension_t_out, address_spend_pubkey_out, @@ -346,10 +335,28 @@ bool try_scan_carrot_enote_external_receiver(const CarrotEnoteV1 &enote, amount_blinding_factor_out, payment_id_out, enote_type_out, - nominal_janus_anchor, - verified_normal_janus)) + nominal_janus_anchor)) + return false; + + // K^j_v from K^j_s + crypto::public_key address_view_pubkey; + bool is_subaddress{}; + if (!recover_address_view_pubkey(address_spend_pubkey_out, + main_address_spend_pubkeys, + k_view_dev, + address_view_pubkey, + is_subaddress)) return false; + const bool verified_normal_janus = verify_carrot_normal_janus_protection( + make_carrot_input_context(enote.tx_first_key_image), + address_spend_pubkey_out, + address_view_pubkey, + is_subaddress, + enote.enote_ephemeral_pubkey, + nominal_janus_anchor, + payment_id_out); + if (!verified_normal_janus && !verify_carrot_special_janus_protection(enote.tx_first_key_image, enote.enote_ephemeral_pubkey, enote.onetime_address, diff --git a/src/carrot_core/scan.h b/src/carrot_core/scan.h index 0278fb62d9c..5abb1b65e71 100644 --- a/src/carrot_core/scan.h +++ b/src/carrot_core/scan.h @@ -71,6 +71,7 @@ bool try_make_carrot_shared_key_receiver( * @param enote_ephemeral_privkey d_e * @param s_sender_receiver s_sr * @param main_address_spend_pubkeys {K^0_s, ...} + * @param main_address_view_pubkey K^0_v * @param[out] sender_extension_g_out k^g_o * @param[out] sender_extension_t_out k^t_o * @param[out] main_address_spend_pubkey_out K^0_s, which will be one of main_address_spend_pubkeys @@ -92,6 +93,7 @@ bool try_scan_carrot_coinbase_enote_receiver( const CarrotCoinbaseEnoteV1 &enote, const mx25519_pubkey &s_sender_receiver, const epee::span main_address_spend_pubkeys, + const crypto::public_key &main_address_view_pubkey, crypto::secret_key &sender_extension_g_out, crypto::secret_key &sender_extension_t_out, crypto::public_key &main_address_spend_pubkey_out); @@ -99,6 +101,7 @@ bool try_scan_carrot_coinbase_enote_receiver( const CarrotCoinbaseEnoteV1 &enote, const mx25519_pubkey &s_sender_receiver, const crypto::public_key &main_address_spend_pubkey, + const crypto::public_key &main_address_view_pubkey, crypto::secret_key &sender_extension_g_out, crypto::secret_key &sender_extension_t_out); /** diff --git a/src/carrot_core/scan_unsafe.cpp b/src/carrot_core/scan_unsafe.cpp index b640ce1f08a..79ca5cf8f63 100644 --- a/src/carrot_core/scan_unsafe.cpp +++ b/src/carrot_core/scan_unsafe.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2025, The Monero Project +// Copyright (c) 2025-2026, The Monero Project // // All rights reserved. // @@ -226,6 +226,7 @@ bool try_scan_carrot_enote_internal_burnt(const CarrotEnoteV1 &enote, //------------------------------------------------------------------------------------------------------------------- bool verify_carrot_normal_janus_protection(const input_context_t &input_context, const crypto::public_key &nominal_address_spend_pubkey, + const crypto::public_key &nominal_address_view_pubkey, const bool is_subaddress, const mx25519_pubkey &enote_ephemeral_pubkey, const janus_anchor_t &nominal_janus_anchor, @@ -235,6 +236,7 @@ bool verify_carrot_normal_janus_protection(const input_context_t &input_context, if (verify_carrot_normal_janus_protection(nominal_janus_anchor, input_context, nominal_address_spend_pubkey, + nominal_address_view_pubkey, is_subaddress, nominal_payment_id_inout, enote_ephemeral_pubkey)) @@ -245,6 +247,7 @@ bool verify_carrot_normal_janus_protection(const input_context_t &input_context, return verify_carrot_normal_janus_protection(nominal_janus_anchor, input_context, nominal_address_spend_pubkey, + nominal_address_view_pubkey, is_subaddress, nominal_payment_id_inout, enote_ephemeral_pubkey); diff --git a/src/carrot_core/scan_unsafe.h b/src/carrot_core/scan_unsafe.h index b5722032adc..10ef1c86bcb 100644 --- a/src/carrot_core/scan_unsafe.h +++ b/src/carrot_core/scan_unsafe.h @@ -126,6 +126,7 @@ bool try_scan_carrot_enote_internal_burnt(const CarrotEnoteV1 &enote, * @brief Verify that scanned normal enote is not attempting a Janus attack, and check pid * @param input_context input_context * @param nominal_address_spend_pubkey K^j_s' + * @param nominal_address_view_pubkey K^j_v' * @param is_subaddress true iff K^j_s' corresponds to a subaddress * @param enote_ephemeral_pubkey D_e * @param nominal_janus_anchor anchor' @@ -134,6 +135,7 @@ bool try_scan_carrot_enote_internal_burnt(const CarrotEnoteV1 &enote, */ bool verify_carrot_normal_janus_protection(const input_context_t &input_context, const crypto::public_key &nominal_address_spend_pubkey, + const crypto::public_key &nominal_address_view_pubkey, const bool is_subaddress, const mx25519_pubkey &enote_ephemeral_pubkey, const janus_anchor_t &nominal_janus_anchor, diff --git a/src/carrot_impl/address_device_hierarchies.cpp b/src/carrot_impl/address_device_hierarchies.cpp index 90e9c3130bc..057061b72ef 100644 --- a/src/carrot_impl/address_device_hierarchies.cpp +++ b/src/carrot_impl/address_device_hierarchies.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2025, The Monero Project +// Copyright (c) 2025-2026, The Monero Project // // All rights reserved. // @@ -107,7 +107,9 @@ void cryptonote_hierarchy_address_device::get_address_spend_pubkey(const subaddr // decompress K_S ge_p3 account_spend_pubkey_p3; - ge_frombytes_vartime(&account_spend_pubkey_p3, to_bytes(this->m_cryptonote_account_spend_pubkey)); // discard result + [[maybe_unused]] int r = 1; + r = ge_frombytes_vartime(&account_spend_pubkey_p3, to_bytes(this->m_cryptonote_account_spend_pubkey)); + assert(0 == r); ge_cached account_spend_pubkey_cached; ge_p3_to_cached(&account_spend_pubkey_cached, &account_spend_pubkey_p3); @@ -204,11 +206,13 @@ void cryptonote_hierarchy_address_device::assert_derive_type(const subaddress_in carrot_hierarchy_address_device::carrot_hierarchy_address_device( std::shared_ptr s_generate_address_dev, const crypto::public_key &carrot_account_spend_pubkey, - const crypto::public_key &carrot_account_view_pubkey) + const crypto::public_key &carrot_account_view_pubkey, + const crypto::public_key &carrot_main_address_view_pubkey) : m_s_generate_address_dev(std::move(s_generate_address_dev)), m_carrot_account_spend_pubkey(carrot_account_spend_pubkey), - m_carrot_account_view_pubkey(carrot_account_view_pubkey) + m_carrot_account_view_pubkey(carrot_account_view_pubkey), + m_carrot_main_address_view_pubkey(carrot_main_address_view_pubkey) { assert(this->m_s_generate_address_dev); } @@ -228,7 +232,9 @@ void carrot_hierarchy_address_device::get_address_spend_pubkey(const subaddress_ // decompress K_s ge_p3 account_spend_pubkey_p3; - ge_frombytes_vartime(&account_spend_pubkey_p3, to_bytes(this->m_carrot_account_spend_pubkey)); + [[maybe_unused]] int r = 1; + r = ge_frombytes_vartime(&account_spend_pubkey_p3, to_bytes(this->m_carrot_account_spend_pubkey)); + assert(0 == r); // K^j_s = k^j_subscal K_s ge_p2 tmp_p2; @@ -245,7 +251,7 @@ void carrot_hierarchy_address_device::get_address_pubkeys(const subaddress_index if (!subaddr_index.index.is_subaddress()) { address_spend_pubkey_out = this->m_carrot_account_spend_pubkey; - address_view_pubkey_out = this->m_carrot_account_view_pubkey; + address_view_pubkey_out = this->m_carrot_main_address_view_pubkey; return; } @@ -253,7 +259,8 @@ void carrot_hierarchy_address_device::get_address_pubkeys(const subaddress_index // decompress K_s ge_p3 tmp_p3; - ge_frombytes_vartime(&tmp_p3, to_bytes(this->m_carrot_account_spend_pubkey)); + [[maybe_unused]] int r = ge_frombytes_vartime(&tmp_p3, to_bytes(this->m_carrot_account_spend_pubkey)); + assert(0 == r); // K^j_s = k^j_subscal K_s ge_p2 tmp_p2; @@ -261,7 +268,8 @@ void carrot_hierarchy_address_device::get_address_pubkeys(const subaddress_index ge_tobytes(to_bytes(address_spend_pubkey_out), &tmp_p2); // decompress K_v - ge_frombytes_vartime(&tmp_p3, to_bytes(this->m_carrot_account_view_pubkey)); + r = ge_frombytes_vartime(&tmp_p3, to_bytes(this->m_carrot_account_view_pubkey)); + assert(0 == r); // K^j_v = k^j_subscal K_v ge_scalarmult(&tmp_p2, to_bytes(subaddress_scalar), &tmp_p3); diff --git a/src/carrot_impl/address_device_hierarchies.h b/src/carrot_impl/address_device_hierarchies.h index 3877219c8f8..6dc720568dc 100644 --- a/src/carrot_impl/address_device_hierarchies.h +++ b/src/carrot_impl/address_device_hierarchies.h @@ -117,7 +117,8 @@ class carrot_hierarchy_address_device final: public address_device carrot_hierarchy_address_device( std::shared_ptr s_generate_address_dev, const crypto::public_key &carrot_account_spend_pubkey, - const crypto::public_key &carrot_account_view_pubkey); + const crypto::public_key &carrot_account_view_pubkey, + const crypto::public_key &carrot_main_address_view_pubkey); //address_device void get_address_spend_pubkey(const subaddress_index_extended &subaddr_index, @@ -138,6 +139,7 @@ class carrot_hierarchy_address_device final: public address_device std::shared_ptr m_s_generate_address_dev; crypto::public_key m_carrot_account_spend_pubkey; crypto::public_key m_carrot_account_view_pubkey; + crypto::public_key m_carrot_main_address_view_pubkey; //member functions crypto::secret_key get_subaddress_scalar(const subaddress_index &subaddr_index) const; diff --git a/src/carrot_impl/output_opening_types.cpp b/src/carrot_impl/output_opening_types.cpp index 288e0cd6a39..3d6a24f73d5 100644 --- a/src/carrot_impl/output_opening_types.cpp +++ b/src/carrot_impl/output_opening_types.cpp @@ -253,9 +253,13 @@ static bool try_scan_opening_hint(const OutputOpeningHintVariant &opening_hint, s_sender_receiver)) { crypto::public_key dummy_address_spend_pubkey; + crypto::public_key main_address_view_pubkey; + addr_dev.get_address_pubkeys({}, dummy_address_spend_pubkey, main_address_view_pubkey); + return try_scan_carrot_coinbase_enote_receiver(hint.source_enote, s_sender_receiver, main_address_spend_pubkeys, + main_address_view_pubkey, sender_extension_g_out, sender_extension_t_out, dummy_address_spend_pubkey); diff --git a/src/carrot_impl/tx_builder_outputs.cpp b/src/carrot_impl/tx_builder_outputs.cpp index 1fd8c2de8ff..b9c4f35da47 100644 --- a/src/carrot_impl/tx_builder_outputs.cpp +++ b/src/carrot_impl/tx_builder_outputs.cpp @@ -209,7 +209,7 @@ void get_enote_ephemeral_privkeys_from_proposal_v1( } else { - // d_e = H_n(anchor_norm, input_context, K^j_s, pid) + // d_e = H_n(anchor_norm, input_context, K^j_s, K^j_v, pid) enote_ephemeral_privkey = get_enote_ephemeral_privkey( normal_payment_proposals.at(payment_proposal_idx.second), make_carrot_input_context(tx_first_key_image)); diff --git a/src/wallet/hot_cold.cpp b/src/wallet/hot_cold.cpp index 65606c679f5..8d9a0f5203c 100644 --- a/src/wallet/hot_cold.cpp +++ b/src/wallet/hot_cold.cpp @@ -32,7 +32,6 @@ //local headers #include "carrot_core/device_ram_borrowed.h" #include "carrot_core/enote_utils.h" -#include "carrot_core/output_set_finalization.h" #include "carrot_core/scan.h" #include "carrot_core/scan_unsafe.h" #include "carrot_impl/address_device_ram_borrowed.h" @@ -180,9 +179,10 @@ exported_carrot_transfer_details export_cold_carrot_output(const wallet2_basic:: // 10. C_a = k_a G + a H const rct::key amount_commitment = rct::commit(td.amount(), td.m_mask); - // 11. derive K^j_s from j + // 11. derive (K^j_s, K^j_v) from j crypto::public_key address_spend_pubkey; - addr_dev.get_address_spend_pubkey({etd.subaddr_index}, address_spend_pubkey); //! @TODO: Carrot/hybrid hierarchy + crypto::public_key address_view_pubkey; + addr_dev.get_address_pubkeys({etd.subaddr_index}, address_spend_pubkey, address_view_pubkey); const bool is_subaddress = !td.m_subaddr_index.is_zero(); // 12. calc k_a assuming enote_type="change": k_a' = H_n(s^ctx_sr, a, K^j_s, "change") @@ -208,6 +208,7 @@ exported_carrot_transfer_details export_cold_carrot_output(const wallet2_basic:: CHECK_AND_ASSERT_THROW_MES( carrot::verify_carrot_normal_janus_protection(input_context, address_spend_pubkey, + address_view_pubkey, is_subaddress, enote_ephemeral_pubkey, etd.janus_anchor, diff --git a/src/wallet/scanning_tools.cpp b/src/wallet/scanning_tools.cpp index a134a9ec0af..98fbdbc5faa 100644 --- a/src/wallet/scanning_tools.cpp +++ b/src/wallet/scanning_tools.cpp @@ -286,6 +286,7 @@ static std::optional view_incoming_scan_carrot_ const carrot::CarrotCoinbaseEnoteV1 &enote, const mx25519_pubkey &s_sender_receiver, const epee::span main_address_spend_pubkeys, + const crypto::public_key &main_address_view_pubkey, const carrot::subaddress_map &subaddress_map) { enote_view_incoming_scan_info_t res; @@ -293,6 +294,7 @@ static std::optional view_incoming_scan_carrot_ if (!carrot::try_scan_carrot_coinbase_enote_receiver(enote, s_sender_receiver, main_address_spend_pubkeys, + main_address_view_pubkey, res.sender_extension_g, res.sender_extension_t, res.address_spend_pubkey)) @@ -482,7 +484,7 @@ bool try_load_pre_carrot_enote_from_transaction_prefix(const cryptonote::transac return true; } //------------------------------------------------------------------------------------------------------------------- -std::optional view_incoming_scan_enote( +static std::optional view_incoming_scan_enote( const MoneroEnoteVariant &enote, const std::size_t local_output_index, const cryptonote::blobdata &tx_extra_nonce, @@ -491,6 +493,7 @@ std::optional view_incoming_scan_enote( const cryptonote::account_public_address &address, const carrot::view_incoming_key_device *k_view_incoming_dev, const epee::span main_address_spend_pubkeys, + const crypto::public_key &main_address_view_pubkey, const carrot::subaddress_map &subaddress_map) { CHECK_AND_ASSERT_MES(!main_derivations.empty() || !additional_derivations.empty(), @@ -539,6 +542,7 @@ std::optional view_incoming_scan_enote( scan_as_sender ? epee::span(&address.m_spend_public_key, 1) : main_address_spend_pubkeys, + scan_as_sender ? address.m_view_public_key : main_address_view_pubkey, subaddress_map); } @@ -576,6 +580,7 @@ std::optional view_incoming_scan_enote( const cryptonote::account_public_address &address; const carrot::view_incoming_key_device *k_view_incoming_dev; const epee::span main_address_spend_pubkeys; + const crypto::public_key &main_address_view_pubkey; const carrot::subaddress_map &subaddress_map; }; @@ -588,12 +593,13 @@ std::optional view_incoming_scan_enote( address, k_view_incoming_dev, main_address_spend_pubkeys, + main_address_view_pubkey, subaddress_map }, enote); } //------------------------------------------------------------------------------------------------------------------- -std::optional view_incoming_scan_enote( +static std::optional view_incoming_scan_enote( const cryptonote::transaction &tx, const std::size_t local_output_index, const epee::span main_tx_ephemeral_pubkeys, @@ -604,6 +610,7 @@ std::optional view_incoming_scan_enote( const cryptonote::account_public_address &address, const carrot::view_incoming_key_device *k_view_incoming_dev, const epee::span main_address_spend_pubkeys, + const crypto::public_key &main_address_view_pubkey, const carrot::subaddress_map &subaddress_map) { MoneroEnoteVariant enote; @@ -661,6 +668,7 @@ std::optional view_incoming_scan_enote( address, k_view_incoming_dev, main_address_spend_pubkeys, + main_address_view_pubkey, subaddress_map); } //------------------------------------------------------------------------------------------------------------------- @@ -671,6 +679,7 @@ std::optional view_incoming_scan_enote_from_pre const std::size_t local_output_index, const carrot::view_incoming_key_device &k_view_incoming_dev, const epee::span main_address_spend_pubkeys, + const crypto::public_key &main_address_view_pubkey, const carrot::subaddress_map &subaddress_map) { const bool is_carrot = carrot::is_carrot_transaction_v1(tx_prefix); @@ -722,6 +731,7 @@ std::optional view_incoming_scan_enote_from_pre /*address=*/{}, &k_view_incoming_dev, main_address_spend_pubkeys, + main_address_view_pubkey, subaddress_map); } //------------------------------------------------------------------------------------------------------------------- @@ -734,6 +744,7 @@ void view_incoming_scan_transaction( const epee::span additional_derivations, const carrot::view_incoming_key_device &k_view_incoming_dev, const epee::span main_address_spend_pubkeys, + const crypto::public_key &main_address_view_pubkey, const carrot::subaddress_map &subaddress_map, const epee::span> enote_scan_infos_out) { @@ -755,6 +766,7 @@ void view_incoming_scan_transaction( /*address=*/{}, &k_view_incoming_dev, main_address_spend_pubkeys, + main_address_view_pubkey, subaddress_map); } } @@ -763,6 +775,7 @@ void view_incoming_scan_transaction( const cryptonote::transaction &tx, const carrot::view_incoming_key_device &k_view_incoming_dev, const epee::span main_address_spend_pubkeys, + const crypto::public_key &main_address_view_pubkey, const carrot::subaddress_map &subaddress_map, const epee::span> enote_scan_infos_out) { @@ -805,6 +818,7 @@ void view_incoming_scan_transaction( epee::to_span(additional_derivations), k_view_incoming_dev, main_address_spend_pubkeys, + main_address_view_pubkey, subaddress_map, enote_scan_infos_out); } @@ -816,10 +830,12 @@ std::vector> view_incoming_scan_t const carrot::subaddress_map &subaddress_map) { crypto::public_key main_address_spend_pubkeys[2]; + crypto::public_key main_address_view_pubkey; + addr_dev.get_address_pubkeys({}, main_address_spend_pubkeys[0], main_address_view_pubkey); std::vector> res(tx.vout.size()); view_incoming_scan_transaction(tx, k_view_incoming_dev, carrot::get_all_main_address_spend_pubkeys_span(addr_dev, main_address_spend_pubkeys), - subaddress_map, epee::to_mut_span(res)); + main_address_view_pubkey, subaddress_map, epee::to_mut_span(res)); return res; } //------------------------------------------------------------------------------------------------------------------- @@ -859,6 +875,7 @@ std::vector> view_incoming_scan_t address, /*k_view_incoming_dev=*/nullptr, /*main_address_spend_pubkeys=*/{}, + address.m_view_public_key, carrot::subaddress_map_legacy{{{address.m_spend_public_key, {}}}}); } diff --git a/src/wallet/scanning_tools.h b/src/wallet/scanning_tools.h index 3ac91e901a9..7d35d67c881 100644 --- a/src/wallet/scanning_tools.h +++ b/src/wallet/scanning_tools.h @@ -103,6 +103,7 @@ using MoneroEnoteVariant = std::variant view_incoming_scan_enote_from_pre const std::size_t local_output_index, const carrot::view_incoming_key_device &k_view_incoming_dev, const epee::span main_address_spend_pubkeys, + const crypto::public_key &main_address_view_pubkey, const carrot::subaddress_map &subaddress_map); /** @@ -125,6 +127,7 @@ std::optional view_incoming_scan_enote_from_pre * @param additional_derivations sender-receiver ECDHs against additional ephemeral pubkeys * @param k_view_incoming_dev device for k_v * @param main_address_spend_pubkeys [K_s, ...] + * @param main_address_view_pubkey K^0_v * @param subaddress_map - * @param[out] enote_scan_infos_out - mutable span to enote scan infos of size N, where N is number of `tx`'s outputs */ @@ -137,12 +140,14 @@ void view_incoming_scan_transaction( const epee::span additional_derivations, const carrot::view_incoming_key_device &k_view_incoming_dev, const epee::span main_address_spend_pubkeys, + const crypto::public_key &main_address_view_pubkey, const carrot::subaddress_map &subaddress_map, const epee::span> enote_scan_infos_out); void view_incoming_scan_transaction( const cryptonote::transaction &tx, const carrot::view_incoming_key_device &k_view_incoming_dev, const epee::span main_address_spend_pubkeys, + const crypto::public_key &main_address_view_pubkey, const carrot::subaddress_map &subaddress_map, const epee::span> enote_scan_infos_out); std::vector> view_incoming_scan_transaction( diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index e818afe530b..9cc4b9e2995 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -3649,6 +3649,7 @@ void wallet2::process_parsed_blocks(const uint64_t start_height, const uint64_t wallet::view_incoming_scan_transaction(tx, *k_view_incoming_dev, {&m_account.get_keys().m_account_address.m_spend_public_key, 1}, //! @TODO: Carrot + m_account.get_keys().m_account_address.m_view_public_key, carrot::subaddress_map_legacy{this->m_subaddresses}, {&enote_scan_infos[0] + tx_output_idx, tx.vout.size()}); @@ -13475,6 +13476,7 @@ crypto::public_key wallet2::get_tx_pub_key_from_received_outs(const tools::walle td.m_internal_output_index, *k_view_incoming_dev, {&m_account.get_keys().m_account_address.m_spend_public_key, 1}, //! @TODO: Carrot + m_account.get_keys().m_account_address.m_view_public_key, carrot::subaddress_map_legacy{m_subaddresses}); const size_t main_tx_pubkey_index = enote_scan_info ? enote_scan_info->main_tx_pubkey_index : 0; diff --git a/tests/unit_tests/carrot_convergence.cpp b/tests/unit_tests/carrot_convergence.cpp index 295da70fb4f..6319a958bb5 100644 --- a/tests/unit_tests/carrot_convergence.cpp +++ b/tests/unit_tests/carrot_convergence.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2025, The Monero Project +// Copyright (c) 2025-2026, The Monero Project // // All rights reserved. // @@ -117,24 +117,24 @@ static const hex_value_t subaddress_spend_pubkey("8f2f38e702 static const hex_value_t subaddress_view_pubkey("369bdcf4f434f42eb09f4372cb6be30de7b17d21e4f98e244459a90b58cd0610"); static const hex_value_t anchor_norm("caee1381775487a0982557f0d2680b55"); -static const hex_value_t anchor_special("70fe9b941fe1ef3b2345c87485f70a6e"); +static const hex_value_t anchor_special("de618a9c5ace05174b4815f5d0968c03"); static const hex_value_t input_context("9423f74f3e869dc8427d8b35bb24c917480409c3f4750bff3c742f8e4d5af7bef7"); static const hex_value_t payment_id("4321734f56621440"); -static const hex_value_t enote_ephemeral_privkey("6aea0ed0c34ad3483415377658841a75e0da8b462e637d8bf783b9bcd320b303"); -static const hex_value_t enote_ephemeral_pubkey_cryptonote("8df2a40a42ecc10348a461310c1afc2c2b1be7b29fd27a3921a1aefba5efa27b"); -static const hex_value_t enote_ephemeral_pubkey_subaddress("a3c3cdf84fd301cfc4675096f1c896543f2efc1001d899bbab3a0fd137f6a630"); -static const hex_value_t s_sender_receiver("1f848f8384e7a9f217dc9dc2691703cf392eaf6c92931acd0fc840c900d3ed49"); -static const hex_value_t s_sender_receiver_ctx("6e99852ed7b3744177bb669e73fd1c544d88555ea6fffe3787ca6af48d2fe9f6"); +static const hex_value_t enote_ephemeral_privkey("168d6c01b80caa083b2eac1cfd5702f665209ecd09f356679386f79868c7b301"); +static const hex_value_t enote_ephemeral_pubkey_cryptonote("71eacbebd15c7f851aecf10ae092cb8be72c512326df88df1dc6211a6473e67d"); +static const hex_value_t enote_ephemeral_pubkey_subaddress("11e21a65cd526e376b9f9b39acbf979908e581901a52bf59328c825720f8e54f"); +static const hex_value_t s_sender_receiver("a87e53a316b1fb1c3fdaa3c896e52a289087781d90fe690878525721dd072632"); +static const hex_value_t s_sender_receiver_ctx("49049817b9b61f7a8ade58274ed57802a659603a1d4a771b05659c5f797d4b82"); static const rct::xmr_amount amount = 67000000000000; -static const hex_value_t amount_blinding_factor_payment("5a01cc9f8ca9556c429d623d848fe036c76593005c63a62df57afc4b51d3c20b"); -static const hex_value_t amount_blinding_factor_change("f69587a2e01d039758b5dd61999e4d60f226eb7b8027be2ff2656ecbb584d103"); -static const hex_value_t amount_commitment("f5df40aeba877e8ccadd9dff363d90ec28efbfd1201573897cd70c61c026edb9"); -static const hex_value_t onetime_address_coinbase("0c4ee83d079ebd77882f894b2e0a43e3d572af9c330871f1dfbcc62f5c64e4ae"); -static const hex_value_t onetime_address("522347147e41f22ebe155abc32b9def985b2e454045c6edd8921ee4253cd4516"); -static const hex_value_t view_tag("5f58e1"); -static const hex_value_t anchor_encryption_mask("6ba7e188fb315ad2158ac6b6652408d4"); -static const hex_value_t amount_encryption_mask("2b739fdb6d1d5e50"); -static const hex_value_t payment_id_encryption_mask("043d7e9ed13a3484"); +static const hex_value_t amount_blinding_factor_payment("3ef935ee428b33a3cd46aad05a9fb6ac9dfc8ce1cec0f13a01a3c2d79c1a690e"); +static const hex_value_t amount_blinding_factor_change("64c4e4c363b112fe08b74e8e7dd5d589fa89fa22afd8ba558534abe91ffabc0c"); +static const hex_value_t amount_commitment("03a0a93dfd865143ee1086607a94d04352077f20e51c2e36a7b6ad3452fa237b"); +static const hex_value_t onetime_address_coinbase("d30463d8e1b355adc3e5c3ca6a682133acc0367064c1c8441c7a95a8648c7744"); +static const hex_value_t onetime_address("2b5685aeb279883ffc29c6d97d9784464ec4c71dfa0b9a97d2632242cc265983"); +static const hex_value_t view_tag("0b644d"); +static const hex_value_t anchor_encryption_mask("cd2c983ade4cb295e0f310f929c2b288"); +static const hex_value_t amount_encryption_mask("4759901f6a954bc0"); +static const hex_value_t payment_id_encryption_mask("70b52c8d28c0e97d"); } //anonymous namespace //--------------------------------------------------------------------------------------------------------------------- @@ -247,6 +247,7 @@ TEST(carrot_convergence, make_carrot_enote_ephemeral_privkey) make_carrot_enote_ephemeral_privkey(anchor_norm.value, input_context.value, subaddress_spend_pubkey.value, + subaddress_view_pubkey.value, payment_id.value, enote_ephemeral_privkey_rc); EXPECT_TRUE(enote_ephemeral_privkey.matches(enote_ephemeral_privkey_rc)); diff --git a/tests/unit_tests/carrot_core.cpp b/tests/unit_tests/carrot_core.cpp index 27909fddf8d..a8fc9e3ac9a 100644 --- a/tests/unit_tests/carrot_core.cpp +++ b/tests/unit_tests/carrot_core.cpp @@ -49,6 +49,29 @@ using namespace carrot::mock::people; #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "unit_tests.carrot" +namespace +{ +static bool verify_carrot_normal_janus_protection_to_mock_keys(const input_context_t &input_context, + const crypto::public_key &nominal_address_spend_pubkey, + const mock::mock_carrot_and_legacy_keys &keys, + const mx25519_pubkey &enote_ephemeral_pubkey, + const janus_anchor_t &nominal_janus_anchor, + const payment_id_t &nominal_payment_id) +{ + const subaddress_index_extended subaddr_index = keys.subaddress_map.get_index_for_address_spend_pubkey( + nominal_address_spend_pubkey).value(); + const CarrotDestinationV1 address = keys.subaddress(subaddr_index); + CHECK_AND_ASSERT_THROW_MES(address.address_spend_pubkey == nominal_address_spend_pubkey, + "Bad subaddress index lookup: mismatched address spend pubkeys"); + return verify_carrot_normal_janus_protection(nominal_janus_anchor, + input_context, + nominal_address_spend_pubkey, + address.address_view_pubkey, + subaddr_index.index.is_subaddress(), + nominal_payment_id, + enote_ephemeral_pubkey); +} +} //anonymous namespace //--------------------------------------------------------------------------------------------------------------------- TEST(carrot_core, ECDH_cryptonote_completeness) { @@ -680,6 +703,7 @@ TEST(carrot_core, main_address_coinbase_scan_completeness) const bool scan_success = try_scan_carrot_coinbase_enote_receiver(enote, s_sender_receiver, keys.carrot_account_spend_pubkey, + keys.cryptonote_address().address_view_pubkey, recovered_sender_extension_g, recovered_sender_extension_t); @@ -906,15 +930,15 @@ TEST(carrot_core, get_enote_output_proposals_external_ss_sub2integ_completeness) struct JanusAttackProposalV1 { /// The address provided in the normal proposal is used to scale the enote ephemeral pubkey and derive s_sr. - /// The `readjusted_opening_subaddress_spend_pubkey` is what is added to the sender extensions to derive K_o. - /// If `use_readjusted_spend_pubkey_in_ephemeral_privkey_hash=true`, then d_e is calculated as - /// d_e = H_n(anchor_norm, input_context, K^i_s, pid)), else it is normally calculated as - /// d_e = H_n(anchor_norm, input_context, K^j_s, pid)). It shouldn't make a difference to the attack either + /// The `readjusted_opening_address` is what is added to the sender extensions to derive K_o. + /// If `use_readjusted_address_in_ephemeral_privkey_hash=true`, then d_e is calculated as + /// d_e = H_n(anchor_norm, input_context, K^i_s, K^i_v, pid)), else it is normally calculated as + /// d_e = H_n(anchor_norm, input_context, K^j_s, K^j_v, pid)). It shouldn't make a difference to the attack either /// way, but we will test that below. carrot::CarrotPaymentProposalV1 normal; // contains address (is_subaddress, K^j_s, K^j_v, pid) - crypto::public_key readjusted_opening_subaddress_spend_pubkey; // K^i_s - bool use_readjusted_spend_pubkey_in_ephemeral_privkey_hash; + carrot::CarrotDestinationV1 readjusted_opening_address; // (K^i_s, K^i_v) + bool use_readjusted_address_in_ephemeral_privkey_hash; }; //---------------------------------------------------------------------------------------------------------------------- static void get_output_proposal_janus_attack_v1(const JanusAttackProposalV1 &proposal, @@ -932,16 +956,17 @@ static void get_output_proposal_janus_attack_v1(const JanusAttackProposalV1 &pro const input_context_t input_context = make_carrot_input_context(tx_first_key_image); // 3. decide if K^x_s = K^j_s XOR K^x_s = K^i_s - const crypto::public_key &spend_pubkey_used_in_ephemeral_privkey_hash = - proposal.use_readjusted_spend_pubkey_in_ephemeral_privkey_hash - ? proposal.readjusted_opening_subaddress_spend_pubkey - : destination.address_spend_pubkey; + const carrot::CarrotDestinationV1 &destination_used_in_ephemeral_privkey_hash = + proposal.use_readjusted_address_in_ephemeral_privkey_hash + ? proposal.readjusted_opening_address + : destination; - // 4. d_e = H_n(anchor_norm, input_context, K^x_s, pid) + // 4. d_e = H_n(anchor_norm, input_context, K^x_s, K^x_v, pid) crypto::secret_key enote_ephemeral_privkey; carrot::make_carrot_enote_ephemeral_privkey(proposal.normal.randomness, input_context, - spend_pubkey_used_in_ephemeral_privkey_hash, + destination_used_in_ephemeral_privkey_hash.address_spend_pubkey, + destination_used_in_ephemeral_privkey_hash.address_view_pubkey, destination.payment_id, enote_ephemeral_privkey); @@ -967,7 +992,7 @@ static void get_output_proposal_janus_attack_v1(const JanusAttackProposalV1 &pro // 8. k_a = H_n(s^ctx_sr, a, K^i_s, enote_type) make_carrot_amount_blinding_factor(s_sender_receiver_ctx, proposal.normal.amount, - proposal.readjusted_opening_subaddress_spend_pubkey, + proposal.readjusted_opening_address.address_spend_pubkey, carrot::CarrotEnoteType::PAYMENT, output_enote_out.amount_blinding_factor); @@ -977,7 +1002,7 @@ static void get_output_proposal_janus_attack_v1(const JanusAttackProposalV1 &pro // 10. Ko = K^i_s + K^o_ext = K^i_s + (k^o_g G + k^o_t T) CHECK_AND_ASSERT_THROW_MES(try_make_carrot_onetime_address( - proposal.readjusted_opening_subaddress_spend_pubkey, + proposal.readjusted_opening_address.address_spend_pubkey, s_sender_receiver_ctx, output_enote_out.enote.amount_commitment, output_enote_out.enote.onetime_address), @@ -1008,7 +1033,7 @@ static void get_output_proposal_janus_attack_v1(const JanusAttackProposalV1 &pro output_enote_out.amount = proposal.normal.amount; output_enote_out.enote.tx_first_key_image = tx_first_key_image; - // Notice steps 8 & 10 specifically for where `readjusted_opening_subaddress_spend_pubkey` is + // Notice steps 8 & 10 specifically for where `readjusted_opening_address` is // substituted instead of the actual address spend pubkey used for deriving D_e and s_sr. } //---------------------------------------------------------------------------------------------------------------------- @@ -1027,8 +1052,8 @@ TEST(carrot_core, janus_protection_non_coinbase_main_sub_readjust_NOT_in_d_e) .amount = amount, .randomness = carrot::gen_janus_anchor() }, - .readjusted_opening_subaddress_spend_pubkey = bob_subaddr.address_spend_pubkey, - .use_readjusted_spend_pubkey_in_ephemeral_privkey_hash = false, + .readjusted_opening_address = bob_subaddr, + .use_readjusted_address_in_ephemeral_privkey_hash = false, }; carrot::RCTOutputEnoteProposal output_enote; @@ -1061,7 +1086,7 @@ TEST(carrot_core, janus_protection_non_coinbase_main_sub_readjust_NOT_in_d_e) recovered_enote_type, nominal_janus_anchor); ASSERT_TRUE(scanned); - ASSERT_EQ(proposal.readjusted_opening_subaddress_spend_pubkey, nominal_address_spend_pubkey); + ASSERT_EQ(proposal.readjusted_opening_address.address_spend_pubkey, nominal_address_spend_pubkey); ASSERT_EQ(amount, recovered_amount); ASSERT_EQ(carrot::CarrotEnoteType::PAYMENT, recovered_enote_type); ASSERT_EQ(output_enote.enote.amount_commitment, commit_carrot_amount(recovered_amount, recovered_amount_blinding_factor)); @@ -1070,10 +1095,10 @@ TEST(carrot_core, janus_protection_non_coinbase_main_sub_readjust_NOT_in_d_e) sender_extension_t, output_enote.enote.onetime_address)); - EXPECT_FALSE(verify_carrot_normal_janus_protection( + EXPECT_FALSE(verify_carrot_normal_janus_protection_to_mock_keys( carrot::make_carrot_input_context(tx_first_key_image), nominal_address_spend_pubkey, - bob.cryptonote_address().address_spend_pubkey != nominal_address_spend_pubkey, + bob, output_enote.enote.enote_ephemeral_pubkey, nominal_janus_anchor, nominal_payment_id)); @@ -1117,8 +1142,8 @@ TEST(carrot_core, janus_protection_non_coinbase_main_sub_readjust_in_d_e) .amount = amount, .randomness = carrot::gen_janus_anchor() }, - .readjusted_opening_subaddress_spend_pubkey = bob_subaddr.address_spend_pubkey, - .use_readjusted_spend_pubkey_in_ephemeral_privkey_hash = true, + .readjusted_opening_address = bob_subaddr, + .use_readjusted_address_in_ephemeral_privkey_hash = true, }; carrot::RCTOutputEnoteProposal output_enote; @@ -1151,7 +1176,7 @@ TEST(carrot_core, janus_protection_non_coinbase_main_sub_readjust_in_d_e) recovered_enote_type, nominal_janus_anchor); ASSERT_TRUE(scanned); - ASSERT_EQ(proposal.readjusted_opening_subaddress_spend_pubkey, nominal_address_spend_pubkey); + ASSERT_EQ(proposal.readjusted_opening_address.address_spend_pubkey, nominal_address_spend_pubkey); ASSERT_EQ(amount, recovered_amount); ASSERT_EQ(carrot::CarrotEnoteType::PAYMENT, recovered_enote_type); ASSERT_EQ(output_enote.enote.amount_commitment, commit_carrot_amount(recovered_amount, recovered_amount_blinding_factor)); @@ -1160,10 +1185,10 @@ TEST(carrot_core, janus_protection_non_coinbase_main_sub_readjust_in_d_e) sender_extension_t, output_enote.enote.onetime_address)); - EXPECT_FALSE(verify_carrot_normal_janus_protection( + EXPECT_FALSE(verify_carrot_normal_janus_protection_to_mock_keys( carrot::make_carrot_input_context(tx_first_key_image), nominal_address_spend_pubkey, - bob.cryptonote_address().address_spend_pubkey != nominal_address_spend_pubkey, + bob, output_enote.enote.enote_ephemeral_pubkey, nominal_janus_anchor, nominal_payment_id)); @@ -1207,8 +1232,8 @@ TEST(carrot_core, janus_protection_non_coinbase_sub_main_readjust_NOT_in_d_e) .amount = amount, .randomness = carrot::gen_janus_anchor() }, - .readjusted_opening_subaddress_spend_pubkey = bob_main.address_spend_pubkey, - .use_readjusted_spend_pubkey_in_ephemeral_privkey_hash = false, + .readjusted_opening_address = bob_main, + .use_readjusted_address_in_ephemeral_privkey_hash = false, }; carrot::RCTOutputEnoteProposal output_enote; @@ -1241,7 +1266,7 @@ TEST(carrot_core, janus_protection_non_coinbase_sub_main_readjust_NOT_in_d_e) recovered_enote_type, nominal_janus_anchor); ASSERT_TRUE(scanned); - ASSERT_EQ(proposal.readjusted_opening_subaddress_spend_pubkey, nominal_address_spend_pubkey); + ASSERT_EQ(proposal.readjusted_opening_address.address_spend_pubkey, nominal_address_spend_pubkey); ASSERT_EQ(amount, recovered_amount); ASSERT_EQ(carrot::CarrotEnoteType::PAYMENT, recovered_enote_type); ASSERT_EQ(output_enote.enote.amount_commitment, commit_carrot_amount(recovered_amount, recovered_amount_blinding_factor)); @@ -1250,10 +1275,10 @@ TEST(carrot_core, janus_protection_non_coinbase_sub_main_readjust_NOT_in_d_e) sender_extension_t, output_enote.enote.onetime_address)); - EXPECT_FALSE(verify_carrot_normal_janus_protection( + EXPECT_FALSE(verify_carrot_normal_janus_protection_to_mock_keys( carrot::make_carrot_input_context(tx_first_key_image), nominal_address_spend_pubkey, - bob.cryptonote_address().address_spend_pubkey != nominal_address_spend_pubkey, + bob, output_enote.enote.enote_ephemeral_pubkey, nominal_janus_anchor, nominal_payment_id)); @@ -1297,8 +1322,8 @@ TEST(carrot_core, janus_protection_non_coinbase_sub_main_readjust_in_d_e) .amount = amount, .randomness = carrot::gen_janus_anchor() }, - .readjusted_opening_subaddress_spend_pubkey = bob_main.address_spend_pubkey, - .use_readjusted_spend_pubkey_in_ephemeral_privkey_hash = true, + .readjusted_opening_address = bob_main, + .use_readjusted_address_in_ephemeral_privkey_hash = true, }; carrot::RCTOutputEnoteProposal output_enote; @@ -1331,7 +1356,7 @@ TEST(carrot_core, janus_protection_non_coinbase_sub_main_readjust_in_d_e) recovered_enote_type, nominal_janus_anchor); ASSERT_TRUE(scanned); - ASSERT_EQ(proposal.readjusted_opening_subaddress_spend_pubkey, nominal_address_spend_pubkey); + ASSERT_EQ(proposal.readjusted_opening_address.address_spend_pubkey, nominal_address_spend_pubkey); ASSERT_EQ(amount, recovered_amount); ASSERT_EQ(carrot::CarrotEnoteType::PAYMENT, recovered_enote_type); ASSERT_EQ(output_enote.enote.amount_commitment, commit_carrot_amount(recovered_amount, recovered_amount_blinding_factor)); @@ -1340,10 +1365,10 @@ TEST(carrot_core, janus_protection_non_coinbase_sub_main_readjust_in_d_e) sender_extension_t, output_enote.enote.onetime_address)); - EXPECT_FALSE(verify_carrot_normal_janus_protection( + EXPECT_FALSE(verify_carrot_normal_janus_protection_to_mock_keys( carrot::make_carrot_input_context(tx_first_key_image), nominal_address_spend_pubkey, - bob.cryptonote_address().address_spend_pubkey != nominal_address_spend_pubkey, + bob, output_enote.enote.enote_ephemeral_pubkey, nominal_janus_anchor, nominal_payment_id)); @@ -1387,8 +1412,8 @@ TEST(carrot_core, janus_protection_non_coinbase_sub_sub_readjust_NOT_in_d_e) .amount = amount, .randomness = carrot::gen_janus_anchor() }, - .readjusted_opening_subaddress_spend_pubkey = bob_subaddr2.address_spend_pubkey, - .use_readjusted_spend_pubkey_in_ephemeral_privkey_hash = false, + .readjusted_opening_address = bob_subaddr2, + .use_readjusted_address_in_ephemeral_privkey_hash = false, }; carrot::RCTOutputEnoteProposal output_enote; @@ -1421,7 +1446,7 @@ TEST(carrot_core, janus_protection_non_coinbase_sub_sub_readjust_NOT_in_d_e) recovered_enote_type, nominal_janus_anchor); ASSERT_TRUE(scanned); - ASSERT_EQ(proposal.readjusted_opening_subaddress_spend_pubkey, nominal_address_spend_pubkey); + ASSERT_EQ(proposal.readjusted_opening_address.address_spend_pubkey, nominal_address_spend_pubkey); ASSERT_EQ(amount, recovered_amount); ASSERT_EQ(carrot::CarrotEnoteType::PAYMENT, recovered_enote_type); ASSERT_EQ(output_enote.enote.amount_commitment, commit_carrot_amount(recovered_amount, recovered_amount_blinding_factor)); @@ -1430,10 +1455,10 @@ TEST(carrot_core, janus_protection_non_coinbase_sub_sub_readjust_NOT_in_d_e) sender_extension_t, output_enote.enote.onetime_address)); - EXPECT_FALSE(verify_carrot_normal_janus_protection( + EXPECT_FALSE(verify_carrot_normal_janus_protection_to_mock_keys( carrot::make_carrot_input_context(tx_first_key_image), nominal_address_spend_pubkey, - bob.cryptonote_address().address_spend_pubkey != nominal_address_spend_pubkey, + bob, output_enote.enote.enote_ephemeral_pubkey, nominal_janus_anchor, nominal_payment_id)); @@ -1477,8 +1502,8 @@ TEST(carrot_core, janus_protection_non_coinbase_sub_sub_readjust_in_d_e) .amount = amount, .randomness = carrot::gen_janus_anchor() }, - .readjusted_opening_subaddress_spend_pubkey = bob_subaddr2.address_spend_pubkey, - .use_readjusted_spend_pubkey_in_ephemeral_privkey_hash = true, + .readjusted_opening_address = bob_subaddr2, + .use_readjusted_address_in_ephemeral_privkey_hash = true, }; carrot::RCTOutputEnoteProposal output_enote; @@ -1511,7 +1536,7 @@ TEST(carrot_core, janus_protection_non_coinbase_sub_sub_readjust_in_d_e) recovered_enote_type, nominal_janus_anchor); ASSERT_TRUE(scanned); - ASSERT_EQ(proposal.readjusted_opening_subaddress_spend_pubkey, nominal_address_spend_pubkey); + ASSERT_EQ(proposal.readjusted_opening_address.address_spend_pubkey, nominal_address_spend_pubkey); ASSERT_EQ(amount, recovered_amount); ASSERT_EQ(carrot::CarrotEnoteType::PAYMENT, recovered_enote_type); ASSERT_EQ(output_enote.enote.amount_commitment, commit_carrot_amount(recovered_amount, recovered_amount_blinding_factor)); @@ -1520,10 +1545,10 @@ TEST(carrot_core, janus_protection_non_coinbase_sub_sub_readjust_in_d_e) sender_extension_t, output_enote.enote.onetime_address)); - EXPECT_FALSE(verify_carrot_normal_janus_protection( + EXPECT_FALSE(verify_carrot_normal_janus_protection_to_mock_keys( carrot::make_carrot_input_context(tx_first_key_image), nominal_address_spend_pubkey, - bob.cryptonote_address().address_spend_pubkey != nominal_address_spend_pubkey, + bob, output_enote.enote.enote_ephemeral_pubkey, nominal_janus_anchor, nominal_payment_id)); @@ -1568,16 +1593,17 @@ static void get_coinbase_output_proposal_janus_attack_v1(const JanusAttackPropos const input_context_t input_context = make_carrot_input_context_coinbase(block_index); // 3. decide if K^x_s = K^j_s XOR K^x_s = K^i_s - const crypto::public_key &spend_pubkey_used_in_ephemeral_privkey_hash = - proposal.use_readjusted_spend_pubkey_in_ephemeral_privkey_hash - ? proposal.readjusted_opening_subaddress_spend_pubkey - : destination.address_spend_pubkey; + const carrot::CarrotDestinationV1 &destination_used_in_ephemeral_privkey_hash = + proposal.use_readjusted_address_in_ephemeral_privkey_hash + ? proposal.readjusted_opening_address + : destination; // 4. d_e = H_n(anchor_norm, input_context, K^x_s, pid) crypto::secret_key enote_ephemeral_privkey; carrot::make_carrot_enote_ephemeral_privkey(proposal.normal.randomness, input_context, - spend_pubkey_used_in_ephemeral_privkey_hash, + destination_used_in_ephemeral_privkey_hash.address_spend_pubkey, + destination_used_in_ephemeral_privkey_hash.address_view_pubkey, destination.payment_id, enote_ephemeral_privkey); @@ -1602,7 +1628,7 @@ static void get_coinbase_output_proposal_janus_attack_v1(const JanusAttackPropos // 8. Ko = K^i_s + K^o_ext = K^i_s + (k^o_g G + k^o_t T) CHECK_AND_ASSERT_THROW_MES(try_make_carrot_onetime_address_coinbase( - proposal.readjusted_opening_subaddress_spend_pubkey, + proposal.readjusted_opening_address.address_spend_pubkey, s_sender_receiver_ctx, proposal.normal.amount, output_enote_out.onetime_address), @@ -1623,7 +1649,7 @@ static void get_coinbase_output_proposal_janus_attack_v1(const JanusAttackPropos output_enote_out.amount = proposal.normal.amount; output_enote_out.block_index = block_index; - // Notice step 8 specifically for where `readjusted_opening_subaddress_spend_pubkey` is + // Notice step 8 specifically for where `readjusted_opening_address` is // substituted instead of the actual address spend pubkey used for deriving D_e and s_sr. } //---------------------------------------------------------------------------------------------------------------------- @@ -1642,8 +1668,8 @@ TEST(carrot_core, janus_protection_coinbase_main_sub_readjust_NOT_in_d_e) .amount = amount, .randomness = carrot::gen_janus_anchor() }, - .readjusted_opening_subaddress_spend_pubkey = bob_main.address_spend_pubkey, - .use_readjusted_spend_pubkey_in_ephemeral_privkey_hash = false, + .readjusted_opening_address = bob_main, + .use_readjusted_address_in_ephemeral_privkey_hash = false, }; carrot::CarrotCoinbaseEnoteV1 output_enote; @@ -1667,22 +1693,24 @@ TEST(carrot_core, janus_protection_coinbase_main_sub_readjust_NOT_in_d_e) nominal_address_spend_pubkey, nominal_janus_anchor); ASSERT_TRUE(scanned); - ASSERT_EQ(proposal.readjusted_opening_subaddress_spend_pubkey, nominal_address_spend_pubkey); + ASSERT_EQ(proposal.readjusted_opening_address.address_spend_pubkey, nominal_address_spend_pubkey); ASSERT_TRUE(bob.can_open_fcmp_onetime_address(nominal_address_spend_pubkey, sender_extension_g, sender_extension_t, output_enote.onetime_address)); - EXPECT_FALSE(carrot::verify_carrot_normal_janus_protection(nominal_janus_anchor, + EXPECT_FALSE(verify_carrot_normal_janus_protection_to_mock_keys( make_carrot_input_context_coinbase(block_index), nominal_address_spend_pubkey, - bob.cryptonote_address().address_spend_pubkey != nominal_address_spend_pubkey, - null_payment_id, - output_enote.enote_ephemeral_pubkey)); + bob, + output_enote.enote_ephemeral_pubkey, + nominal_janus_anchor, + null_payment_id)); EXPECT_FALSE(try_scan_carrot_coinbase_enote_receiver(output_enote, s_sender_receiver, {&bob.carrot_account_spend_pubkey, 1}, + bob.cryptonote_address().address_view_pubkey, sender_extension_g, sender_extension_t, nominal_address_spend_pubkey)); @@ -1709,8 +1737,8 @@ TEST(carrot_core, janus_protection_coinbase_main_sub_readjust_in_d_e) .amount = amount, .randomness = carrot::gen_janus_anchor() }, - .readjusted_opening_subaddress_spend_pubkey = bob_main.address_spend_pubkey, - .use_readjusted_spend_pubkey_in_ephemeral_privkey_hash = true, + .readjusted_opening_address = bob_main, + .use_readjusted_address_in_ephemeral_privkey_hash = true, }; carrot::CarrotCoinbaseEnoteV1 output_enote; @@ -1734,22 +1762,23 @@ TEST(carrot_core, janus_protection_coinbase_main_sub_readjust_in_d_e) nominal_address_spend_pubkey, nominal_janus_anchor); ASSERT_TRUE(scanned); - ASSERT_EQ(proposal.readjusted_opening_subaddress_spend_pubkey, nominal_address_spend_pubkey); + ASSERT_EQ(proposal.readjusted_opening_address.address_spend_pubkey, nominal_address_spend_pubkey); ASSERT_TRUE(bob.can_open_fcmp_onetime_address(nominal_address_spend_pubkey, sender_extension_g, sender_extension_t, output_enote.onetime_address)); - EXPECT_FALSE(carrot::verify_carrot_normal_janus_protection(nominal_janus_anchor, - make_carrot_input_context_coinbase(block_index), + EXPECT_FALSE(verify_carrot_normal_janus_protection_to_mock_keys(make_carrot_input_context_coinbase(block_index), nominal_address_spend_pubkey, - bob.cryptonote_address().address_spend_pubkey != nominal_address_spend_pubkey, - null_payment_id, - output_enote.enote_ephemeral_pubkey)); + bob, + output_enote.enote_ephemeral_pubkey, + nominal_janus_anchor, + null_payment_id)); EXPECT_FALSE(try_scan_carrot_coinbase_enote_receiver(output_enote, s_sender_receiver, {&bob.carrot_account_spend_pubkey, 1}, + bob.cryptonote_address().address_view_pubkey, sender_extension_g, sender_extension_t, nominal_address_spend_pubkey)); @@ -1761,7 +1790,7 @@ TEST(carrot_core, janus_protection_coinbase_main_sub_readjust_in_d_e) sender_extension_t)); } //---------------------------------------------------------------------------------------------------------------------- -TEST(carrot_core, janus_protection_use_readjusted_spend_pubkey_in_ephemeral_privkey_hash) +TEST(carrot_core, janus_protection_use_readjusted_address_in_ephemeral_privkey_hash) { const std::uint64_t block_index = mock::gen_block_index(); @@ -1776,12 +1805,12 @@ TEST(carrot_core, janus_protection_use_readjusted_spend_pubkey_in_ephemeral_priv .amount = amount, .randomness = carrot::gen_janus_anchor() }, - .readjusted_opening_subaddress_spend_pubkey = bob_main.address_spend_pubkey, - .use_readjusted_spend_pubkey_in_ephemeral_privkey_hash = false, + .readjusted_opening_address = bob_main, + .use_readjusted_address_in_ephemeral_privkey_hash = false, }; JanusAttackProposalV1 proposal2 = proposal1; - proposal2.use_readjusted_spend_pubkey_in_ephemeral_privkey_hash = true; + proposal2.use_readjusted_address_in_ephemeral_privkey_hash = true; carrot::CarrotCoinbaseEnoteV1 output_enote1; get_coinbase_output_proposal_janus_attack_v1(proposal1, block_index, output_enote1); @@ -2299,6 +2328,7 @@ TEST(carrot_core, pq_turnstile_completeness_coinbase) const bool scan_success = try_scan_carrot_coinbase_enote_receiver(output_enote, s_sender_receiver, {&bob.carrot_account_spend_pubkey, 1}, + bob.cryptonote_address().address_view_pubkey, recovered_sender_extension_g, recovered_sender_extension_t, recovered_address_spend_pubkey); diff --git a/tests/unit_tests/carrot_mock_helpers.cpp b/tests/unit_tests/carrot_mock_helpers.cpp index db97bd182e6..51e453f9a39 100644 --- a/tests/unit_tests/carrot_mock_helpers.cpp +++ b/tests/unit_tests/carrot_mock_helpers.cpp @@ -62,7 +62,8 @@ static void reassign_non_borrowed_devices(carrot::mock::mock_carrot_and_legacy_k std::make_shared( std::make_shared(keys.s_generate_address_dev), keys.carrot_account_spend_pubkey, - keys.carrot_account_view_pubkey), + keys.carrot_account_view_pubkey, + keys.legacy_acb.get_keys().m_account_address.m_view_public_key), std::make_shared( k_view_incoming_dev, keys.legacy_acb.get_keys().m_account_address.m_spend_public_key))); @@ -564,6 +565,7 @@ void mock_scan_coinbase_enote_set(const std::vector &coin if (try_scan_carrot_coinbase_enote_receiver(enote, s_sender_receiver, {main_address_spend_pubkeys, 2}, + keys.cryptonote_address().address_view_pubkey, scan_result.sender_extension_g, scan_result.sender_extension_t, scan_result.address_spend_pubkey))