diff --git a/src/carrot_core/CMakeLists.txt b/src/carrot_core/CMakeLists.txt index 9f6da606d..0d7f7dc5c 100644 --- a/src/carrot_core/CMakeLists.txt +++ b/src/carrot_core/CMakeLists.txt @@ -29,6 +29,7 @@ set(carrot_core_sources account_secrets.cpp address_utils.cpp + account.cpp carrot_enote_types.cpp core_types.cpp destination.cpp diff --git a/src/carrot_impl/account.cpp b/src/carrot_core/account.cpp similarity index 100% rename from src/carrot_impl/account.cpp rename to src/carrot_core/account.cpp diff --git a/src/carrot_impl/account.h b/src/carrot_core/account.h similarity index 97% rename from src/carrot_impl/account.h rename to src/carrot_core/account.h index 3bb5c807f..3d85e71c0 100644 --- a/src/carrot_impl/account.h +++ b/src/carrot_core/account.h @@ -28,11 +28,11 @@ #pragma once -#include "carrot_core/account_secrets.h" -#include "carrot_core/address_utils.h" -#include "carrot_core/destination.h" -#include "carrot_core/device_ram_borrowed.h" -#include "carrot_core/enote_utils.h" +#include "account_secrets.h" +#include "address_utils.h" +#include "destination.h" +#include "device_ram_borrowed.h" +#include "enote_utils.h" #include "carrot_impl/subaddress_index.h" #include "cryptonote_basic/account.h" #include "cryptonote_basic/subaddress_index.h" diff --git a/src/carrot_core/enote_utils.cpp b/src/carrot_core/enote_utils.cpp index aade33f1b..adcfbc451 100644 --- a/src/carrot_core/enote_utils.cpp +++ b/src/carrot_core/enote_utils.cpp @@ -223,7 +223,7 @@ void make_sparc_return_pubkey_encryption_mask(const unsigned char s_sender_recei //------------------------------------------------------------------------------------------------------------------- void make_sparc_return_pubkey(const unsigned char s_sender_receiver_unctx[32], const input_context_t &input_context, - const view_incoming_key_device *k_view_incoming_dev, + const view_balance_secret_device *s_view_balance_dev, const crypto::public_key &onetime_address, encrypted_return_pubkey_t &return_pubkey_out) { @@ -232,7 +232,7 @@ void make_sparc_return_pubkey(const unsigned char s_sender_receiver_unctx[32], crypto::public_key return_pub; encrypted_return_pubkey_t K_return; encrypted_return_pubkey_t m_return; - k_view_incoming_dev->make_internal_return_privkey(input_context, onetime_address, k_return); + s_view_balance_dev->make_internal_return_privkey(input_context, onetime_address, k_return); crypto::secret_key_to_public_key(k_return, return_pub); static_assert(sizeof(K_return.bytes) == sizeof(return_pub.data), "Size mismatch"); memcpy(K_return.bytes, return_pub.data, sizeof(encrypted_return_pubkey_t)); diff --git a/src/carrot_core/enote_utils.h b/src/carrot_core/enote_utils.h index 4f9f72c0c..03e6844eb 100644 --- a/src/carrot_core/enote_utils.h +++ b/src/carrot_core/enote_utils.h @@ -160,7 +160,7 @@ void make_sparc_return_pubkey_encryption_mask(const unsigned char s_sender_recei */ void make_sparc_return_pubkey(const unsigned char s_sender_receiver_unctx[32], const input_context_t &input_context, - const view_incoming_key_device *k_view_incoming_dev, + const view_balance_secret_device *s_view_balance_dev, const crypto::public_key &onetime_address, encrypted_return_pubkey_t &return_pubkey_out); /** diff --git a/src/carrot_core/output_set_finalization.cpp b/src/carrot_core/output_set_finalization.cpp index 0b4a0973d..3c0b4b64a 100644 --- a/src/carrot_core/output_set_finalization.cpp +++ b/src/carrot_core/output_set_finalization.cpp @@ -223,13 +223,13 @@ void get_output_enote_proposals(const std::vector &norm if (tx_type == cryptonote::transaction_type::RETURN) { get_output_proposal_return_v1(normal_payment_proposals[i], tx_first_key_image, - k_view_dev, + s_view_balance_dev, output_entry.first, encrypted_payment_id); } else { get_output_proposal_normal_v1(normal_payment_proposals[i], tx_first_key_image, - k_view_dev, + s_view_balance_dev, output_entry.first, encrypted_payment_id); } @@ -279,6 +279,8 @@ void get_output_enote_proposals(const std::vector &norm *s_view_balance_dev, tx_first_key_image, other_enote_ephemeral_pubkey, + tx_type, + return_enote_out, output_entry.first); } else if (k_view_dev != nullptr) @@ -286,9 +288,7 @@ void get_output_enote_proposals(const std::vector &norm get_output_proposal_special_v1(selfsend_payment_proposal, *k_view_dev, tx_first_key_image, - tx_type, other_enote_ephemeral_pubkey, - return_enote_out, output_entry.first); } else // neither k_v nor s_vb device passed diff --git a/src/carrot_core/payment_proposal.cpp b/src/carrot_core/payment_proposal.cpp index 808bc96db..a012b3fc0 100644 --- a/src/carrot_core/payment_proposal.cpp +++ b/src/carrot_core/payment_proposal.cpp @@ -170,7 +170,7 @@ static void get_external_output_proposal_parts(const mx25519_pubkey &s_sender_re const CarrotEnoteType enote_type, const mx25519_pubkey &enote_ephemeral_pubkey, const input_context_t &input_context, - const view_incoming_key_device *k_view_incoming_dev, + const view_balance_secret_device *s_view_balance_dev, const bool coinbase_amount_commitment, crypto::hash &s_sender_receiver_out, crypto::secret_key &amount_blinding_factor_out, @@ -206,10 +206,10 @@ static void get_external_output_proposal_parts(const mx25519_pubkey &s_sender_re make_carrot_view_tag(s_sender_receiver_unctx.data, input_context, onetime_address_out, view_tag_out); // 4. construct the return pubkey - if (k_view_incoming_dev != nullptr) + if (s_view_balance_dev != nullptr) make_sparc_return_pubkey(s_sender_receiver_unctx.data, input_context, - k_view_incoming_dev, + s_view_balance_dev, onetime_address_out, return_pubkey_out); } @@ -303,7 +303,7 @@ void get_coinbase_output_proposal_v1(const CarrotPaymentProposalV1 &proposal, //------------------------------------------------------------------------------------------------------------------- void get_output_proposal_normal_v1(const CarrotPaymentProposalV1 &proposal, const crypto::key_image &tx_first_key_image, - const view_incoming_key_device *k_view_incoming_dev, + const view_balance_secret_device *s_view_balance_dev, RCTOutputEnoteProposal &output_enote_out, encrypted_payment_id_t &encrypted_payment_id_out) { @@ -331,7 +331,7 @@ void get_output_proposal_normal_v1(const CarrotPaymentProposalV1 &proposal, CarrotEnoteType::PAYMENT, output_enote_out.enote.enote_ephemeral_pubkey, input_context, - k_view_incoming_dev, + s_view_balance_dev, // we need it to calculate the return pubkey false, // coinbase_amount_commitment s_sender_receiver, output_enote_out.amount_blinding_factor, @@ -357,9 +357,7 @@ void get_output_proposal_normal_v1(const CarrotPaymentProposalV1 &proposal, void get_output_proposal_special_v1(const CarrotPaymentProposalSelfSendV1 &proposal, const view_incoming_key_device &k_view_dev, const crypto::key_image &tx_first_key_image, - const cryptonote::transaction_type tx_type, const std::optional &other_enote_ephemeral_pubkey, - RCTOutputEnoteProposal &return_enote_out, RCTOutputEnoteProposal &output_enote_out) { // 1. sanity checks @@ -426,42 +424,11 @@ void get_output_proposal_special_v1(const CarrotPaymentProposalSelfSendV1 &propo output_enote_out.enote.asset_type = "SAL1"; output_enote_out.enote.return_enc = crypto::rand(); output_enote_out.amount = proposal.amount; - - // 10. construct the stake return enote - if (tx_type == cryptonote::transaction_type::STAKE) { - // make k_return - crypto::secret_key k_return; - k_view_dev.make_internal_return_privkey(input_context, output_enote_out.enote.onetime_address, k_return); - - // compute K_return = k_return * G - crypto::public_key return_pub; - crypto::secret_key_to_public_key(k_return, return_pub); - - // Make a destination address for the return - CarrotDestinationV1 return_destination; - make_carrot_main_address_v1(output_enote_out.enote.onetime_address, return_pub, return_destination); - - // Create the return proposal, using the return address and the amount - const CarrotPaymentProposalV1 proposal_return = CarrotPaymentProposalV1{ - .destination = return_destination, - .amount = 0, - .randomness = gen_janus_anchor() - }; - - encrypted_payment_id_t encrypted_payment_id_return; - get_output_proposal_return_v1( - proposal_return, - tx_first_key_image, - nullptr, // k_view_dev - return_enote_out, - encrypted_payment_id_return - ); - } } //------------------------------------------------------------------------------------------------------------------- void get_output_proposal_return_v1(const CarrotPaymentProposalV1 &proposal, const crypto::key_image &tx_first_key_image, - const view_incoming_key_device *k_view_incoming_dev, + const view_balance_secret_device *s_view_balance_dev, RCTOutputEnoteProposal &output_enote_out, encrypted_payment_id_t &encrypted_payment_id_out) { @@ -490,7 +457,7 @@ void get_output_proposal_return_v1(const CarrotPaymentProposalV1 &proposal, CarrotEnoteType::PAYMENT, output_enote_out.enote.enote_ephemeral_pubkey, input_context, - k_view_incoming_dev, + s_view_balance_dev, false, // coinbase_amount_commitment s_sender_receiver, output_enote_out.amount_blinding_factor, @@ -532,6 +499,8 @@ void get_output_proposal_internal_v1(const CarrotPaymentProposalSelfSendV1 &prop const view_balance_secret_device &s_view_balance_dev, const crypto::key_image &tx_first_key_image, const std::optional &other_enote_ephemeral_pubkey, + const cryptonote::transaction_type tx_type, + RCTOutputEnoteProposal &return_enote_out, RCTOutputEnoteProposal &output_enote_out) { // 1. sanity checks @@ -593,6 +562,37 @@ void get_output_proposal_internal_v1(const CarrotPaymentProposalSelfSendV1 &prop output_enote_out.enote.asset_type = "SAL1"; output_enote_out.enote.return_enc = crypto::rand(); output_enote_out.amount = proposal.amount; + + // 10. construct the stake return enote + if (tx_type == cryptonote::transaction_type::STAKE) { + // make k_return + crypto::secret_key k_return; + s_view_balance_dev.make_internal_return_privkey(input_context, output_enote_out.enote.onetime_address, k_return); + + // compute K_return = k_return * G + crypto::public_key return_pub; + crypto::secret_key_to_public_key(k_return, return_pub); + + // Make a destination address for the return + CarrotDestinationV1 return_destination; + make_carrot_main_address_v1(output_enote_out.enote.onetime_address, return_pub, return_destination); + + // Create the return proposal, using the return address and the amount + const CarrotPaymentProposalV1 proposal_return = CarrotPaymentProposalV1{ + .destination = return_destination, + .amount = 0, + .randomness = gen_janus_anchor() + }; + + encrypted_payment_id_t encrypted_payment_id_return; + get_output_proposal_return_v1( + proposal_return, + tx_first_key_image, + nullptr, // s_view_balance_dev + return_enote_out, + encrypted_payment_id_return + ); + } } //------------------------------------------------------------------------------------------------------------------- CarrotPaymentProposalV1 gen_carrot_payment_proposal_v1(const bool is_subaddress, diff --git a/src/carrot_core/payment_proposal.h b/src/carrot_core/payment_proposal.h index 01e1fba88..4c45cc1e9 100644 --- a/src/carrot_core/payment_proposal.h +++ b/src/carrot_core/payment_proposal.h @@ -133,7 +133,7 @@ void get_coinbase_output_proposal_v1(const CarrotPaymentProposalV1 &proposal, */ void get_output_proposal_normal_v1(const CarrotPaymentProposalV1 &proposal, const crypto::key_image &tx_first_key_image, - const view_incoming_key_device *k_view_dev, + const view_balance_secret_device *s_view_balance_dev, RCTOutputEnoteProposal &output_enote_out, encrypted_payment_id_t &encrypted_payment_id_out); /** @@ -146,7 +146,7 @@ void get_output_proposal_normal_v1(const CarrotPaymentProposalV1 &proposal, */ void get_output_proposal_return_v1(const CarrotPaymentProposalV1 &proposal, const crypto::key_image &tx_first_key_image, - const view_incoming_key_device *k_view_dev, + const view_balance_secret_device *s_view_balance_dev, RCTOutputEnoteProposal &output_enote_out, encrypted_payment_id_t &encrypted_payment_id_out); /** @@ -160,9 +160,7 @@ void get_output_proposal_return_v1(const CarrotPaymentProposalV1 &proposal, void get_output_proposal_special_v1(const CarrotPaymentProposalSelfSendV1 &proposal, const view_incoming_key_device &k_view_dev, const crypto::key_image &tx_first_key_image, - const cryptonote::transaction_type tx_type, const std::optional &other_enote_ephemeral_pubkey, - RCTOutputEnoteProposal &return_enote_out, RCTOutputEnoteProposal &output_enote_out); /** * brief: get_output_proposal_internal_v1 - convert the carrot proposal to an output proposal (internal) @@ -177,6 +175,8 @@ void get_output_proposal_internal_v1(const CarrotPaymentProposalSelfSendV1 &prop const view_balance_secret_device &s_view_balance_dev, const crypto::key_image &tx_first_key_image, const std::optional &other_enote_ephemeral_pubkey, + const cryptonote::transaction_type tx_type, + RCTOutputEnoteProposal &return_enote_out, RCTOutputEnoteProposal &output_enote_out); /** * brief: gen_jamtis_payment_proposal_v1 - generate a random proposal diff --git a/src/carrot_core/scan.cpp b/src/carrot_core/scan.cpp index c12b48dea..939c9fbae 100644 --- a/src/carrot_core/scan.cpp +++ b/src/carrot_core/scan.cpp @@ -70,6 +70,104 @@ static crypto::secret_key make_enote_ephemeral_privkey_sender(const janus_anchor return enote_ephemeral_privkey; } //------------------------------------------------------------------------------------------------------------------- +bool scan_return_output( + const crypto::public_key &return_onetime_address, + const mx25519_pubkey &return_ephemeral_pubkey, + const carrot::view_tag_t &return_view_tag, + const carrot::encrypted_janus_anchor_t &return_anchor_enc, + const carrot::encrypted_amount_t &return_amount_enc, + const std::optional amount_commitment, + const carrot::input_context_t &return_input_context, + carrot::carrot_and_legacy_account &account, + crypto::public_key &address_spend_pubkey_out, + rct::xmr_amount &amount_out, + crypto::secret_key &amount_blinding_factor_out +) { + const auto &return_output_map = account.get_return_output_map_ref(); + CHECK_AND_ASSERT_MES(return_output_map.count(return_onetime_address), false, "return output not found"); + const auto &origin_tx = return_output_map.at(return_onetime_address); + + // 1. make k_return + crypto::secret_key k_return; + account.s_view_balance_dev.make_internal_return_privkey(origin_tx.input_context, origin_tx.K_o, k_return); + + // 2. compute K_return' = k_return * G + crypto::public_key K_return; + crypto::secret_key_to_public_key(k_return, K_return); + + // 3. ssr + mx25519_pubkey shared_secret_return_unctx; + crypto::hash shared_secret_return; + carrot::make_carrot_uncontextualized_shared_key_receiver(k_return, return_ephemeral_pubkey, shared_secret_return_unctx); + carrot::make_carrot_sender_receiver_secret( + shared_secret_return_unctx.data, + return_ephemeral_pubkey, + return_input_context, + shared_secret_return + ); + + // 4. verify the view_tag + CHECK_AND_ASSERT_MES( + carrot::test_carrot_view_tag( + shared_secret_return_unctx.data, + return_input_context, + return_onetime_address, + return_view_tag + ), + false, + "view tag verification failed for carrot coinbase enote" + ); + + // 5. compute anchor_return + carrot::janus_anchor_t recovered_anchor_return = + carrot::decrypt_carrot_anchor(return_anchor_enc, shared_secret_return, return_onetime_address); + + // 6. compute d_e' + crypto::secret_key recovered_ephemeral_privkey_return; + carrot::make_carrot_enote_ephemeral_privkey( + recovered_anchor_return, + return_input_context, + origin_tx.K_change, + carrot::null_payment_id, + recovered_ephemeral_privkey_return + ); + + // 7. compute D_e' + mx25519_pubkey recovered_ephemeral_pubkey_return; + carrot::make_carrot_enote_ephemeral_pubkey( + recovered_ephemeral_privkey_return, + origin_tx.K_change, + false, + recovered_ephemeral_pubkey_return + ); + + // 8. verify the enote ephemeral pubkey + CHECK_AND_ASSERT_MES( + memcmp(recovered_ephemeral_pubkey_return.data, return_ephemeral_pubkey.data, sizeof(mx25519_pubkey)) == 0, + false, + "carrot coinbase enote protection verification failed" + ); + + amount_out = carrot::decrypt_carrot_amount(return_amount_enc, shared_secret_return, return_onetime_address); + address_spend_pubkey_out = origin_tx.K_change; + + if (amount_commitment) + { + CHECK_AND_ASSERT_MES( + carrot::try_recompute_carrot_amount_commitment(shared_secret_return, + amount_out, + address_spend_pubkey_out, + carrot::CarrotEnoteType::PAYMENT, + amount_commitment.value(), + amount_blinding_factor_out + ), + false, + "failed to recompute carrot amount commitment for return output" + ); + } + return true; +} +//------------------------------------------------------------------------------------------------------------------- static bool try_scan_carrot_coinbase_enote_checked( const CarrotCoinbaseEnoteV1 &enote, const mx25519_pubkey &s_sender_receiver_unctx, @@ -361,43 +459,113 @@ bool try_scan_carrot_enote_external_receiver(const CarrotEnoteV1 &enote, } //------------------------------------------------------------------------------------------------------------------- bool try_scan_carrot_enote_internal_receiver(const CarrotEnoteV1 &enote, - const view_balance_secret_device &s_view_balance_dev, + carrot::carrot_and_legacy_account &account, crypto::secret_key &sender_extension_g_out, crypto::secret_key &sender_extension_t_out, crypto::public_key &address_spend_pubkey_out, rct::xmr_amount &amount_out, crypto::secret_key &amount_blinding_factor_out, CarrotEnoteType &enote_type_out, - janus_anchor_t &internal_message_out) + janus_anchor_t &internal_message_out, + crypto::public_key &return_address_out, + bool &is_return_out) { // input_context const input_context_t input_context = make_carrot_input_context(enote.tx_first_key_image); + // assume that the enote is not a return output + is_return_out = false; + // vt = H_3(s_sr || input_context || Ko) view_tag_t nominal_view_tag; - s_view_balance_dev.make_internal_view_tag(input_context, enote.onetime_address, nominal_view_tag); + account.s_view_balance_dev.make_internal_view_tag(input_context, enote.onetime_address, nominal_view_tag); // test view tag - if (nominal_view_tag != enote.view_tag) + if (nominal_view_tag == enote.view_tag) { + // s^ctx_sr = H_32(s_vb, D_e, input_context) + crypto::hash s_sender_receiver; + account.s_view_balance_dev.make_internal_sender_receiver_secret(enote.enote_ephemeral_pubkey, + input_context, + s_sender_receiver); + + bool normal_change_found = true; + if (!try_scan_carrot_enote_internal_burnt(enote, + s_sender_receiver, + sender_extension_g_out, + sender_extension_t_out, + address_spend_pubkey_out, + amount_out, + amount_blinding_factor_out, + enote_type_out, + internal_message_out)) + return false; + + // we received a change output + // save the Kr = K_change + K_return to out subaddress map + for (const auto &output_key : enote.tx_output_keys) { + // make k_return + crypto::secret_key k_return; + const carrot::input_context_t input_context = carrot::make_carrot_input_context(enote.tx_first_key_image); + account.s_view_balance_dev.make_internal_return_privkey(input_context, output_key, k_return); + + // compute K_return = k_return * G + crypto::public_key K_return; + crypto::secret_key_to_public_key(k_return, K_return); + + // compute K_r = K_return + K_o + crypto::public_key K_r = rct::rct2pk(rct::addKeys(rct::pk2rct(K_return), rct::pk2rct(enote.onetime_address))); + + // calculate the key image for the return output + crypto::secret_key sum_g; + sc_add(to_bytes(sum_g), to_bytes(sender_extension_g_out), to_bytes(k_return)); + crypto::key_image key_image = account.derive_key_image( + account.get_keys().m_carrot_account_address.m_spend_public_key, + sum_g, + sender_extension_t_out, + K_r + ); + + crypto::secret_key x, y; + account.try_searching_for_opening_for_onetime_address( + account.get_keys().m_carrot_account_address.m_spend_public_key, + sum_g, + sender_extension_t_out, + x, + y + ); + + // save the input context & change output key + account.insert_return_output_info({{K_r, {input_context, output_key, enote.onetime_address, key_image, x, y}}}); + } + + // janus protection checks are not needed for internal scans + return true; + } + + // check for known return addresses + const auto &return_map = account.get_return_output_map_ref(); + if (return_map.find(enote.onetime_address) == return_map.end()) return false; - // s^ctx_sr = H_32(s_vb, D_e, input_context) - crypto::hash s_sender_receiver; - s_view_balance_dev.make_internal_sender_receiver_secret(enote.enote_ephemeral_pubkey, - input_context, - s_sender_receiver); + // scan the return output + if (!scan_return_output( + enote.onetime_address, + enote.enote_ephemeral_pubkey, + enote.view_tag, + enote.anchor_enc, + enote.amount_enc, + enote.amount_commitment, + carrot::make_carrot_input_context(enote.tx_first_key_image), + account, + address_spend_pubkey_out, + amount_out, + amount_blinding_factor_out)) + return false; - return try_scan_carrot_enote_internal_burnt(enote, - s_sender_receiver, - sender_extension_g_out, - sender_extension_t_out, - address_spend_pubkey_out, - amount_out, - amount_blinding_factor_out, - enote_type_out, - internal_message_out); - - // janus protection checks are not needed for internal scans + // if we come here, we have a return output + is_return_out = true; + return_address_out = enote.onetime_address; + return true; } //------------------------------------------------------------------------------------------------------------------- } //namespace carrot diff --git a/src/carrot_core/scan.h b/src/carrot_core/scan.h index 7de62e007..8966df2cb 100644 --- a/src/carrot_core/scan.h +++ b/src/carrot_core/scan.h @@ -39,6 +39,7 @@ #include "carrot_enote_types.h" #include "device.h" #include "span.h" +#include "account.h" //third party headers @@ -178,14 +179,30 @@ bool try_scan_carrot_enote_external_receiver(const CarrotEnoteV1 &enote, * return: true iff the scan process succeeded */ bool try_scan_carrot_enote_internal_receiver(const CarrotEnoteV1 &enote, - const view_balance_secret_device &s_view_balance_dev, + carrot::carrot_and_legacy_account &account, crypto::secret_key &sender_extension_g_out, crypto::secret_key &sender_extension_t_out, crypto::public_key &address_spend_pubkey_out, rct::xmr_amount &amount_out, crypto::secret_key &amount_blinding_factor_out, CarrotEnoteType &enote_type_out, - janus_anchor_t &internal_message_out); + janus_anchor_t &internal_message_out, + crypto::public_key &return_address_out, + bool &is_return_out); //! @TODO: try_scan_carrot_enote_internal_sender(): can't validate burning w/o passing s_sr = s_vb +bool scan_return_output( + const crypto::public_key &return_onetime_address, + const mx25519_pubkey &return_ephemeral_pubkey, + const carrot::view_tag_t &return_view_tag, + const carrot::encrypted_janus_anchor_t &return_anchor_enc, + const carrot::encrypted_amount_t &return_amount_enc, + const std::optional amount_commitment, + const carrot::input_context_t &return_input_context, + carrot::carrot_and_legacy_account &account, + crypto::public_key &address_spend_pubkey_out, + rct::xmr_amount &amount_out, + crypto::secret_key &amount_blinding_factor_out +); + } //namespace carrot diff --git a/src/carrot_impl/CMakeLists.txt b/src/carrot_impl/CMakeLists.txt index 4c8b92b02..93dd26203 100644 --- a/src/carrot_impl/CMakeLists.txt +++ b/src/carrot_impl/CMakeLists.txt @@ -27,7 +27,6 @@ # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. set(carrot_impl_sources - account.cpp address_device_ram_borrowed.cpp address_utils_compat.cpp format_utils.cpp diff --git a/src/wallet/scanning_tools.cpp b/src/wallet/scanning_tools.cpp index 2446d213f..beed81e12 100644 --- a/src/wallet/scanning_tools.cpp +++ b/src/wallet/scanning_tools.cpp @@ -260,104 +260,6 @@ static std::optional view_incoming_scan_pre_car }; } //------------------------------------------------------------------------------------------------------------------- -static bool scan_return_output( - const crypto::public_key &return_onetime_address, - const mx25519_pubkey &return_ephemeral_pubkey, - const carrot::view_tag_t &return_view_tag, - const carrot::encrypted_janus_anchor_t &return_anchor_enc, - const carrot::encrypted_amount_t &return_amount_enc, - const std::optional amount_commitment, - const carrot::input_context_t &return_input_context, - carrot::carrot_and_legacy_account &account, - crypto::public_key &address_spend_pubkey_out, - rct::xmr_amount &amount_out, - crypto::secret_key &amount_blinding_factor_out -) { - const auto &return_output_map = account.get_return_output_map_ref(); - CHECK_AND_ASSERT_MES(return_output_map.count(return_onetime_address), false, "return output not found"); - const auto &origin_tx = return_output_map.at(return_onetime_address); - - // 1. make k_return - crypto::secret_key k_return; - account.k_view_incoming_dev.make_internal_return_privkey(origin_tx.input_context, origin_tx.K_o, k_return); - - // 2. compute K_return' = k_return * G - crypto::public_key K_return; - crypto::secret_key_to_public_key(k_return, K_return); - - // 3. ssr - mx25519_pubkey shared_secret_return_unctx; - crypto::hash shared_secret_return; - carrot::make_carrot_uncontextualized_shared_key_receiver(k_return, return_ephemeral_pubkey, shared_secret_return_unctx); - carrot::make_carrot_sender_receiver_secret( - shared_secret_return_unctx.data, - return_ephemeral_pubkey, - return_input_context, - shared_secret_return - ); - - // 4. verify the view_tag - CHECK_AND_ASSERT_MES( - carrot::test_carrot_view_tag( - shared_secret_return_unctx.data, - return_input_context, - return_onetime_address, - return_view_tag - ), - false, - "view tag verification failed for carrot coinbase enote" - ); - - // 5. compute anchor_return - carrot::janus_anchor_t recovered_anchor_return = - carrot::decrypt_carrot_anchor(return_anchor_enc, shared_secret_return, return_onetime_address); - - // 6. compute d_e' - crypto::secret_key recovered_ephemeral_privkey_return; - carrot::make_carrot_enote_ephemeral_privkey( - recovered_anchor_return, - return_input_context, - origin_tx.K_change, - carrot::null_payment_id, - recovered_ephemeral_privkey_return - ); - - // 7. compute D_e' - mx25519_pubkey recovered_ephemeral_pubkey_return; - carrot::make_carrot_enote_ephemeral_pubkey( - recovered_ephemeral_privkey_return, - origin_tx.K_change, - false, - recovered_ephemeral_pubkey_return - ); - - // 8. verify the enote ephemeral pubkey - CHECK_AND_ASSERT_MES( - memcmp(recovered_ephemeral_pubkey_return.data, return_ephemeral_pubkey.data, sizeof(mx25519_pubkey)) == 0, - false, - "carrot coinbase enote protection verification failed" - ); - - amount_out = carrot::decrypt_carrot_amount(return_amount_enc, shared_secret_return, return_onetime_address); - address_spend_pubkey_out = origin_tx.K_change; - - if (amount_commitment) - { - CHECK_AND_ASSERT_MES( - carrot::try_recompute_carrot_amount_commitment(shared_secret_return, - amount_out, - address_spend_pubkey_out, - carrot::CarrotEnoteType::PAYMENT, - amount_commitment.value(), - amount_blinding_factor_out - ), - false, - "failed to recompute carrot amount commitment for return output" - ); - } - return true; -} -//------------------------------------------------------------------------------------------------------------------- static std::optional view_incoming_scan_carrot_coinbase_enote( const carrot::CarrotCoinbaseEnoteV1 &enote, const mx25519_pubkey &s_sender_receiver_unctx, @@ -374,19 +276,14 @@ static std::optional view_incoming_scan_carrot_ res.sender_extension_t)) { // check for known return addresses - const auto &subaddress_map = account.get_subaddress_map_ref(); - if (subaddress_map.find(enote.onetime_address) == subaddress_map.end()) + const auto &return_map = account.get_return_output_map_ref(); + if (return_map.find(enote.onetime_address) == return_map.end()) return std::nullopt; found_in_return = true; } if (found_in_return) { - CHECK_AND_ASSERT_MES( - account.get_return_output_map_ref().count(enote.onetime_address), - std::nullopt, - "return output not found" - ); // scan the return output crypto::public_key address_spend_pubkey; carrot::encrypted_amount_t amount_enc; @@ -404,7 +301,7 @@ static std::optional view_incoming_scan_carrot_ account, address_spend_pubkey, amount, - amount_blinding_factor) + amount_blinding_factor) ) { return std::nullopt; } @@ -417,7 +314,7 @@ static std::optional view_incoming_scan_carrot_ res.address_spend_pubkey = main_address_spend_pubkey; res.is_return = false; } - + res.payment_id = crypto::null_hash; res.subaddr_index = carrot::subaddress_index_extended{{0, 0}}; res.amount = enote.amount; @@ -488,10 +385,13 @@ static std::optional view_incoming_scan_carrot_ { enote_view_incoming_scan_info_t res; + // assume not a return output + res.is_return = false; + crypto::secret_key amount_blinding_factor_sk; carrot::payment_id_t payment_id; carrot::CarrotEnoteType dummy_enote_type; - bool found_in_return = false; + carrot::janus_anchor_t internal_message; if (!carrot::try_scan_carrot_enote_external_receiver(enote, encrypted_payment_id, s_sender_receiver_unctx, @@ -505,95 +405,35 @@ static std::optional view_incoming_scan_carrot_ payment_id, dummy_enote_type)) { - // check for known return addresses - const auto &subaddress_map = account.get_subaddress_map_ref(); - if (subaddress_map.find(enote.onetime_address) == subaddress_map.end()) - return std::nullopt; - - found_in_return = true; - } - - if (found_in_return) { - // scan the return output - crypto::public_key address_spend_pubkey; - if (!scan_return_output( - enote.onetime_address, - enote.enote_ephemeral_pubkey, - enote.view_tag, - enote.anchor_enc, - enote.amount_enc, - enote.amount_commitment, - carrot::make_carrot_input_context(enote.tx_first_key_image), - account, - res.address_spend_pubkey, - res.amount, - amount_blinding_factor_sk) - ) { + if (!carrot::try_scan_carrot_enote_internal_receiver(enote, + account, + res.sender_extension_g, + res.sender_extension_t, + res.address_spend_pubkey, + res.amount, + amount_blinding_factor_sk, + dummy_enote_type, + internal_message, + res.return_address, + res.is_return)) + { return std::nullopt; } - - res.address_spend_pubkey = address_spend_pubkey; - res.return_address = enote.onetime_address; - res.is_return = true; - } else { - // we received a normal enote - res.address_spend_pubkey = main_address_spend_pubkey; - res.is_return = false; } - if (!found_in_return) { + if (!res.is_return) { const auto subaddr_it = account.get_subaddress_map_ref().find(res.address_spend_pubkey); CHECK_AND_ASSERT_MES(subaddr_it != account.get_subaddress_map_ref().cend(), - std::nullopt, - "view_incoming_scan_carrot_enote: carrot enote scanned successfully, " - "but the recovered address spend pubkey was not found in the subaddress map"); + std::nullopt, + "view_incoming_scan_carrot_enote: carrot enote scanned successfully, " + "but the recovered address spend pubkey was not found in the subaddress map"); + const carrot::subaddress_index_extended subaddr_index = subaddr_it->second; - memset(&res.payment_id, 0, sizeof(res.payment_id)); memcpy(&res.payment_id, &payment_id, sizeof(carrot::payment_id_t)); - - // we received and output - // save the Kr = K_change + K_return to out subaddress map - for (const auto &output_key : enote.tx_output_keys) { - // make k_return - crypto::secret_key k_return; - const carrot::input_context_t input_context = carrot::make_carrot_input_context(enote.tx_first_key_image); - k_view_dev.make_internal_return_privkey(input_context, output_key, k_return); - - // compute K_return = k_return * G - crypto::public_key K_return; - crypto::secret_key_to_public_key(k_return, K_return); - - // compute K_r = K_return + K_o - crypto::public_key K_r = rct::rct2pk(rct::addKeys(rct::pk2rct(K_return), rct::pk2rct(enote.onetime_address))); - account.insert_subaddresses({{K_r, {{subaddr_index.index.major, subaddr_index.index.minor}, - carrot::AddressDeriveType::Carrot, true}}}); - - // calculate the key image for the return output - crypto::secret_key sum_g; - sc_add(to_bytes(sum_g), to_bytes(res.sender_extension_g), to_bytes(k_return)); - crypto::key_image key_image = account.derive_key_image( - account.get_keys().m_carrot_account_address.m_spend_public_key, - sum_g, - res.sender_extension_t, - K_r - ); - - crypto::secret_key x, y; - account.try_searching_for_opening_for_onetime_address( - account.get_keys().m_carrot_account_address.m_spend_public_key, - sum_g, - res.sender_extension_t, - x, - y - ); - - // save the input context & change output key - account.insert_return_output_info({{K_r, {input_context, output_key, enote.onetime_address, key_image, x, y}}}); - } res.subaddr_index = subaddr_index; } else { - res.subaddr_index = carrot::subaddress_index_extended{{0, 0}}; + res.subaddr_index = {{0, 0}, carrot::AddressDeriveType::Carrot, true}; } res.amount_blinding_factor = rct::sk2rct(amount_blinding_factor_sk); diff --git a/src/wallet/scanning_tools.h b/src/wallet/scanning_tools.h index 5e4615188..b33e641ce 100644 --- a/src/wallet/scanning_tools.h +++ b/src/wallet/scanning_tools.h @@ -31,7 +31,7 @@ //local headers #include "carrot_core/carrot_enote_types.h" #include "carrot_core/device.h" -#include "carrot_impl/account.h" +#include "carrot_core/account.h" #include "carrot_impl/subaddress_index.h" #include "crypto/crypto.h" #include "cryptonote_basic/account.h" diff --git a/src/wallet/tx_builder.cpp b/src/wallet/tx_builder.cpp index 2069b97f2..ec2c884b8 100644 --- a/src/wallet/tx_builder.cpp +++ b/src/wallet/tx_builder.cpp @@ -823,86 +823,99 @@ bool get_address_openings_x_y( return true; } - const std::vector v_pubkeys{src.real_out_tx_key}; const std::vector v_pubkeys_empty{}; const epee::span main_tx_ephemeral_pubkeys = (src.real_out_tx_key == crypto::null_pkey) ? epee::to_span(v_pubkeys_empty) : epee::to_span(v_pubkeys); const epee::span additional_tx_ephemeral_pubkeys = epee::to_span(src.real_out_additional_tx_keys); - // 2. perform ECDH derivations - std::vector main_derivations; - std::vector additional_derivations; - bool is_carrot = carrot::is_carrot_transaction_v1(tx); - wallet::perform_ecdh_derivations( - main_tx_ephemeral_pubkeys, - additional_tx_ephemeral_pubkeys, - is_carrot ? w.get_account().get_keys().k_view_incoming : w.get_account().get_keys().m_view_secret_key, - w.get_account().get_keys().get_device(), - is_carrot, - main_derivations, - additional_derivations - ); + // we have to try both internal and external derivations + bool r = false; + for (size_t i = 0; i < 2; ++i) { + // perform ECDH derivations + std::vector main_derivations; + std::vector additional_derivations; + if (i == 0) { + wallet::perform_ecdh_derivations( + main_tx_ephemeral_pubkeys, + additional_tx_ephemeral_pubkeys, + w.get_account().get_keys().k_view_incoming, + w.get_account().get_keys().get_device(), + src.carrot, + main_derivations, + additional_derivations + ); + } else { + crypto::key_derivation main_derivation; + memcpy(main_derivation.data, w.get_account().get_keys().s_view_balance.data, sizeof(crypto::secret_key)); + main_derivations.push_back(main_derivation); + } - crypto::hash s_sender_receiver; - const crypto::key_derivation &kd = main_derivations.size() - ? main_derivations[0] - : additional_derivations[src.real_output_in_tx_index]; - const mx25519_pubkey s_sender_receiver_unctx = carrot::raw_byte_convert(kd); + crypto::hash s_sender_receiver; + const crypto::key_derivation &kd = main_derivations.size() + ? main_derivations[0] + : additional_derivations[src.real_output_in_tx_index]; + const mx25519_pubkey s_sender_receiver_unctx = carrot::raw_byte_convert(kd); - // ephemeral pubkeys - const epee::span enote_ephemeral_pubkeys_pk = - main_tx_ephemeral_pubkeys.empty() ? additional_tx_ephemeral_pubkeys : main_tx_ephemeral_pubkeys; - const epee::span enote_ephemeral_pubkeys = { - reinterpret_cast(enote_ephemeral_pubkeys_pk.data()), - enote_ephemeral_pubkeys_pk.size() - }; + // ephemeral pubkeys + const epee::span enote_ephemeral_pubkeys_pk = + main_tx_ephemeral_pubkeys.empty() ? additional_tx_ephemeral_pubkeys : main_tx_ephemeral_pubkeys; + const epee::span enote_ephemeral_pubkeys = { + reinterpret_cast(enote_ephemeral_pubkeys_pk.data()), + enote_ephemeral_pubkeys_pk.size() + }; - const bool shared_ephemeral_pubkey = enote_ephemeral_pubkeys.size() == 1; - const size_t ephemeral_pubkey_index = shared_ephemeral_pubkey ? 0 : src.real_output_in_tx_index; + const bool shared_ephemeral_pubkey = enote_ephemeral_pubkeys.size() == 1; + const size_t ephemeral_pubkey_index = shared_ephemeral_pubkey ? 0 : src.real_output_in_tx_index; - // input_context - carrot::input_context_t input_context; - if (src.coinbase) { - input_context = carrot::make_carrot_input_context_coinbase(src.block_index); - } else { - input_context = carrot::make_carrot_input_context(src.first_rct_key_image); + // input_context + carrot::input_context_t input_context; + if (src.coinbase) { + input_context = carrot::make_carrot_input_context_coinbase(src.block_index); + } else { + input_context = carrot::make_carrot_input_context(src.first_rct_key_image); + } + + // s^ctx_sr = H_32(s_sr, D_e, input_context) + make_carrot_sender_receiver_secret(s_sender_receiver_unctx.data, + enote_ephemeral_pubkeys[ephemeral_pubkey_index], + input_context, + s_sender_receiver); + + // get the k_og and k_ot + crypto::secret_key sender_extension_g_out; + crypto::secret_key sender_extension_t_out; + crypto::public_key address_spend_pubkey_out; + carrot::payment_id_t nominal_payment_id_out; + carrot::janus_anchor_t nominal_janus_anchor_out; + carrot::encrypted_janus_anchor_t encrypted_janus_anchor; + carrot::encrypted_payment_id_t encrypted_payment_id; + carrot::scan_carrot_dest_info( + rct::rct2pk(src.outputs[src.real_output].second.dest), + src.outputs[src.real_output].second.mask, + encrypted_janus_anchor, + encrypted_payment_id, + s_sender_receiver, + sender_extension_g_out, + sender_extension_t_out, + address_spend_pubkey_out, + nominal_payment_id_out, + nominal_janus_anchor_out + ); + r = w.get_account().try_searching_for_opening_for_onetime_address( + address_spend_pubkey_out, + sender_extension_g_out, + sender_extension_t_out, + x_out, + y_out + ); + + // If we found the opening, we can stop here + if (r) { + break; + } } - // s^ctx_sr = H_32(s_sr, D_e, input_context) - make_carrot_sender_receiver_secret(s_sender_receiver_unctx.data, - enote_ephemeral_pubkeys[ephemeral_pubkey_index], - input_context, - s_sender_receiver); - - // get the k_og and k_ot - crypto::secret_key sender_extension_g_out; - crypto::secret_key sender_extension_t_out; - crypto::public_key address_spend_pubkey_out; - carrot::payment_id_t nominal_payment_id_out; - carrot::janus_anchor_t nominal_janus_anchor_out; - carrot::encrypted_janus_anchor_t encrypted_janus_anchor; - carrot::encrypted_payment_id_t encrypted_payment_id; - carrot::scan_carrot_dest_info( - rct::rct2pk(src.outputs[src.real_output].second.dest), - src.outputs[src.real_output].second.mask, - encrypted_janus_anchor, - encrypted_payment_id, - s_sender_receiver, - sender_extension_g_out, - sender_extension_t_out, - address_spend_pubkey_out, - nominal_payment_id_out, - nominal_janus_anchor_out - ); - bool r = w.get_account().try_searching_for_opening_for_onetime_address( - address_spend_pubkey_out, - sender_extension_g_out, - sender_extension_t_out, - x_out, - y_out - ); CHECK_AND_ASSERT_THROW_MES(r, "Failed to obtain openings for onetime address"); - return true; } //------------------------------------------------------------------------------------------------------------------- @@ -1020,7 +1033,7 @@ cryptonote::transaction finalize_all_proofs_from_transfer_details( carrot::get_output_enote_proposals(tx_proposal.normal_payment_proposals, selfsend_payment_proposal_cores, tx_proposal.dummy_encrypted_payment_id, - nullptr, + &w.get_account().s_view_balance_dev, &addr_dev, tx_proposal.key_images_sorted.at(0), output_enote_proposals, diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 474d134eb..ed97144a0 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -47,7 +47,7 @@ #include #include "include_base_utils.h" -#include "carrot_impl/account.h" +#include "carrot_core/account.h" #include "carrot_impl/carrot_offchain_serialization.h" #include "cryptonote_basic/account.h" #include "cryptonote_basic/account_boost_serialization.h"