From d4cfc046908e7c8993f307de5e8797d90e65d075 Mon Sep 17 00:00:00 2001 From: jeffro256 Date: Tue, 18 Aug 2026 00:53:40 -0500 Subject: [PATCH] carrot_core: rebind K^j_v in d_e --- src/carrot_core/enote_utils.cpp | 9 +- src/carrot_core/enote_utils.h | 6 +- src/carrot_core/payment_proposal.cpp | 7 +- src/carrot_core/scan.cpp | 122 +++++++------ src/carrot_core/scan.h | 3 + src/carrot_core/scan_unsafe.cpp | 5 +- src/carrot_core/scan_unsafe.h | 2 + .../address_device_hierarchies.cpp | 24 ++- src/carrot_impl/address_device_hierarchies.h | 4 +- src/carrot_impl/output_opening_types.cpp | 4 + src/carrot_impl/tx_builder_outputs.cpp | 2 +- src/wallet/hot_cold.cpp | 4 +- src/wallet/scanning_tools.cpp | 23 ++- src/wallet/scanning_tools.h | 5 + src/wallet/wallet2.cpp | 2 + tests/unit_tests/carrot_convergence.cpp | 31 ++-- tests/unit_tests/carrot_core.cpp | 172 ++++++++++-------- tests/unit_tests/carrot_mock_helpers.cpp | 4 +- 18 files changed, 263 insertions(+), 166 deletions(-) diff --git a/src/carrot_core/enote_utils.cpp b/src/carrot_core/enote_utils.cpp index aa285d51b..f0faceba3 100644 --- a/src/carrot_core/enote_utils.cpp +++ b/src/carrot_core/enote_utils.cpp @@ -177,12 +177,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); } //------------------------------------------------------------------------------------------------------------------- @@ -662,15 +663,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 4d10887c8..082bf78ae 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); /** @@ -422,6 +424,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 @@ -430,6 +433,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 7ca273542..2f94a9c55 100644 --- a/src/carrot_core/payment_proposal.cpp +++ b/src/carrot_core/payment_proposal.cpp @@ -54,7 +54,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 @@ -183,11 +183,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); @@ -230,7 +231,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 1757f8e36..6c5374055 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,6 +32,7 @@ #include "scan.h" //local headers +#include "crypto/generators.h" #include "destination.h" #include "enote_utils.h" #include "scan_unsafe.h" @@ -40,6 +41,9 @@ //standard headers +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "carrot.scan" + namespace carrot { @@ -55,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; @@ -73,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) @@ -91,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, @@ -178,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)) @@ -191,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) @@ -198,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); @@ -207,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) { @@ -215,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); @@ -290,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; } @@ -333,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, @@ -345,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 0278fb62d..5abb1b65e 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 b640ce1f0..79ca5cf8f 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 b5722032a..10ef1c86b 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 b2f701e52..f94f71414 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. // @@ -108,7 +108,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); @@ -205,11 +207,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); } @@ -229,7 +233,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; @@ -246,7 +252,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; } @@ -254,7 +260,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; @@ -262,7 +269,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 3877219c8..6dc720568 100644 --- a/src/carrot_impl/address_device_hierarchies.h +++ b/src/carrot_impl/address_device_hierarchies.h @@ -117,7 +117,8 @@ public: 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 @@ private: 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 288e0cd6a..78e081854 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 1fd8c2de8..b9c4f35da 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 cf129a1eb..868a32c23 100644 --- a/src/wallet/hot_cold.cpp +++ b/src/wallet/hot_cold.cpp @@ -493,7 +493,8 @@ exported_carrot_transfer_details export_cold_carrot_output(const wallet2_basic:: // 10. K^j_s, enote_type crypto::public_key address_spend_pubkey; - addr_dev.get_address_spend_pubkey({etd.subaddr_index}, address_spend_pubkey); + crypto::public_key address_view_pubkey; + addr_dev.get_address_pubkeys({etd.subaddr_index}, address_spend_pubkey, address_view_pubkey); crypto::secret_key amount_blinding_factor; etd.flags.m_enote_type_change = carrot::try_recompute_carrot_amount_commitment(s_sender_receiver_ctx, td.amount(), address_spend_pubkey, carrot::CarrotEnoteType::CHANGE, @@ -513,6 +514,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 11d4cae19..279be84bc 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 3ac91e901..7d35d67c8 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 1d2982f31..1b5648e89 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -3659,6 +3659,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()}); @@ -13560,6 +13561,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 d343924e9..c64f673e4 100644 --- a/tests/unit_tests/carrot_convergence.cpp +++ b/tests/unit_tests/carrot_convergence.cpp @@ -117,24 +117,24 @@ static const hex_value_t subaddress_spend_pubkey("97d227d0ff static const hex_value_t subaddress_view_pubkey("99a684cd429d88815cb1f90b794522b32812388a9f35120cfc08c7435f7bd51f"); static const hex_value_t anchor_norm("caee1381775487a0982557f0d2680b55"); -static const hex_value_t anchor_special("dea14ab8268ba491238f1c554ab60d83"); +static const hex_value_t anchor_special("ed214f5093ad9c5da37765459ac45837"); static const hex_value_t input_context("9423f74f3e869dc8427d8b35bb24c917480409c3f4750bff3c742f8e4d5af7bef7"); static const hex_value_t payment_id("4321734f56621440"); -static const hex_value_t enote_ephemeral_privkey("6bd72042c79d9532a3b90b3689ee53c22725a11169ac2d251337bc4a69b2340d"); -static const hex_value_t enote_ephemeral_pubkey_cryptonote("65b42ef1ed3bd2ab3e6e86d17a52d832bcb6c820a8987306bedd9f6453693869"); -static const hex_value_t enote_ephemeral_pubkey_subaddress("d8e787047bb21d7dd348524741c78f311f549554b6dffd71c86ecc4a98a15720"); -static const hex_value_t s_sender_receiver("513ee79c0c8d76fdd95665a36d607b618e2f76a4806cfdba340fafe64b7f805f"); -static const hex_value_t s_sender_receiver_ctx("6d4288869ce44ed5c38d4016b33083a1a0200daa2d8afc16625702d2108b62ae"); +static const hex_value_t enote_ephemeral_privkey("ae5802c6757d77bd7032e071f72fc99c31a01b9f1bc3af2671c482c8e458140e"); +static const hex_value_t enote_ephemeral_pubkey_cryptonote("f2296f8bd12d705b63d4d827011dd2b2834f2c3396e3546969110173fd14c11a"); +static const hex_value_t enote_ephemeral_pubkey_subaddress("09b26146c8e458ea0822958af12d5f99de17600849388d9f85eabda24934c346"); +static const hex_value_t s_sender_receiver("1ab57d1f23d12d3f67bb52587dd0c74123cbba6e1c4fd10d0a93067caff19647"); +static const hex_value_t s_sender_receiver_ctx("911a15979c3d448aca2b81cd91d16ba2bf2ca4fa4f6dd16ad596c6304eb52227"); static const rct::xmr_amount amount = 67000000000000; -static const hex_value_t amount_blinding_factor_payment("2943f1f7cdabfcfef4803fe6a36df414065e912089ebcee5dbdad32a9685060a"); -static const hex_value_t amount_blinding_factor_change("b9787d10298d13e34cf21513bce84dd1b13ae8aad6ab79794ac06a4c936e2b09"); -static const hex_value_t amount_commitment("95f818f40a41665950d90db8f790cd8a135403624bac284d1059aeb29652fafe"); -static const hex_value_t onetime_address_coinbase("eb6d42a2cabe4e71a0d61172ed8b528b62c0ab398cde9373dab35cc774ba7c05"); -static const hex_value_t onetime_address("5e8f18d1dd3aba72d6ea2c7cfb217573ff4baa878300bbf97b4e32c5c566050d"); -static const hex_value_t view_tag("98ad1e"); -static const hex_value_t anchor_encryption_mask("8d769d8417759007792f824e83115408"); -static const hex_value_t amount_encryption_mask("ee875c495435d1c3"); -static const hex_value_t payment_id_encryption_mask("736350182e1e0840"); +static const hex_value_t amount_blinding_factor_payment("dd8f4252476020ddab59df4ec0ca506e6d25618bfe465e13302276398b23550f"); +static const hex_value_t amount_blinding_factor_change("dba0496c81efa9e25c55a65682024e6e42f1893d3ba57026d9a96468ea5d680b"); +static const hex_value_t amount_commitment("a44cacd33bdacb1c86262987518885a6c22b569ffc89be13e730d5d175f2dede"); +static const hex_value_t onetime_address_coinbase("4f01c472b41a0d85f3c9bc88ad38b7fd47a24029508f84aea84f55d221a48d29"); +static const hex_value_t onetime_address("cd9610e64abdae97e2f8d8580550dbdd0ed543c99740c9c90f9f2cc854841bfa"); +static const hex_value_t view_tag("13191c"); +static const hex_value_t anchor_encryption_mask("ed6dac7330268e639619551f662b19b5"); +static const hex_value_t amount_encryption_mask("f5ff32293fa31ed6"); +static const hex_value_t payment_id_encryption_mask("59220b6582addd7d"); } //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 d253c4a94..c227b58e8 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); @@ -968,7 +993,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); @@ -978,7 +1003,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), @@ -1009,7 +1034,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. } //---------------------------------------------------------------------------------------------------------------------- @@ -1028,8 +1053,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; @@ -1062,7 +1087,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)); @@ -1071,10 +1096,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)); @@ -1118,8 +1143,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; @@ -1152,7 +1177,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)); @@ -1161,10 +1186,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)); @@ -1208,8 +1233,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; @@ -1242,7 +1267,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)); @@ -1251,10 +1276,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)); @@ -1298,8 +1323,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; @@ -1332,7 +1357,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)); @@ -1341,10 +1366,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)); @@ -1388,8 +1413,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; @@ -1422,7 +1447,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)); @@ -1431,10 +1456,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)); @@ -1478,8 +1503,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; @@ -1512,7 +1537,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)); @@ -1521,10 +1546,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)); @@ -1569,16 +1594,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); @@ -1604,7 +1630,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), @@ -1625,7 +1651,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. } //---------------------------------------------------------------------------------------------------------------------- @@ -1644,8 +1670,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; @@ -1669,22 +1695,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)); @@ -1711,8 +1739,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; @@ -1736,22 +1764,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)); @@ -1763,7 +1792,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(); @@ -1778,12 +1807,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); @@ -2301,6 +2330,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 58ec97f91..84b3e92aa 100644 --- a/tests/unit_tests/carrot_mock_helpers.cpp +++ b/tests/unit_tests/carrot_mock_helpers.cpp @@ -64,7 +64,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))); @@ -570,6 +571,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))