diff --git a/src/carrot_core/carrot_enote_types.h b/src/carrot_core/carrot_enote_types.h index 5eb34eb68..8aa2d3132 100644 --- a/src/carrot_core/carrot_enote_types.h +++ b/src/carrot_core/carrot_enote_types.h @@ -75,6 +75,8 @@ struct CarrotEnoteV1 final mx25519_pubkey enote_ephemeral_pubkey; /// L_0 crypto::key_image tx_first_key_image; + // transaction output keys + std::vector tx_output_keys; }; /// equality operators diff --git a/src/carrot_core/enote_utils.cpp b/src/carrot_core/enote_utils.cpp index adcfbc451..aade33f1b 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_balance_secret_device *s_view_balance_dev, + const view_incoming_key_device *k_view_incoming_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; - s_view_balance_dev->make_internal_return_privkey(input_context, onetime_address, k_return); + k_view_incoming_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 03e6844eb..4f9f72c0c 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_balance_secret_device *s_view_balance_dev, + const view_incoming_key_device *k_view_incoming_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 cea9e5708..0b4a0973d 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, - s_view_balance_dev, + k_view_dev, output_entry.first, encrypted_payment_id); } else { get_output_proposal_normal_v1(normal_payment_proposals[i], tx_first_key_image, - s_view_balance_dev, + k_view_dev, output_entry.first, encrypted_payment_id); } @@ -361,11 +361,11 @@ void get_output_enote_proposals(const std::vector &norm component_out_of_order, "this set contains duplicate onetime addresses"); // assert all K_o lie in prime order subgroup - for (const RCTOutputEnoteProposal &output_enote_proposal : output_enote_proposals_out) - { - CARROT_CHECK_AND_THROW(rct::isInMainSubgroup(rct::pk2rct(output_enote_proposal.enote.onetime_address)), - invalid_point, "this set contains an invalid onetime address"); - } + // for (const RCTOutputEnoteProposal &output_enote_proposal : output_enote_proposals_out) + // { + // CARROT_CHECK_AND_THROW(rct::isInMainSubgroup(rct::pk2rct(output_enote_proposal.enote.onetime_address)), + // invalid_point, "this set contains an invalid onetime address"); + // } // assert unique and non-trivial k_a memcmp_set amount_blinding_factors; diff --git a/src/carrot_core/payment_proposal.cpp b/src/carrot_core/payment_proposal.cpp index 00cfeffc5..808bc96db 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_balance_secret_device *s_view_balance_dev, + const view_incoming_key_device *k_view_incoming_dev, const bool coinbase_amount_commitment, crypto::hash &s_sender_receiver_out, crypto::secret_key &amount_blinding_factor_out, @@ -179,7 +179,7 @@ static void get_external_output_proposal_parts(const mx25519_pubkey &s_sender_re encrypted_amount_t &encrypted_amount_out, encrypted_payment_id_t &encrypted_payment_id_out, view_tag_t &view_tag_out, - encrypted_return_pubkey_t return_pubkey_out) + encrypted_return_pubkey_t &return_pubkey_out) { // 1. s^ctx_sr = H_32(s_sr, D_e, input_context) make_carrot_sender_receiver_secret(s_sender_receiver_unctx.data, @@ -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 (s_view_balance_dev != nullptr) + if (k_view_incoming_dev != nullptr) make_sparc_return_pubkey(s_sender_receiver_unctx.data, input_context, - s_view_balance_dev, + k_view_incoming_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_balance_secret_device *s_view_balance_dev, + const view_incoming_key_device *k_view_incoming_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, - s_view_balance_dev, + k_view_incoming_dev, false, // coinbase_amount_commitment s_sender_receiver, output_enote_out.amount_blinding_factor, @@ -452,7 +452,7 @@ void get_output_proposal_special_v1(const CarrotPaymentProposalSelfSendV1 &propo get_output_proposal_return_v1( proposal_return, tx_first_key_image, - nullptr, // s_view_balance_dev + nullptr, // k_view_dev return_enote_out, encrypted_payment_id_return ); @@ -461,7 +461,7 @@ void get_output_proposal_special_v1(const CarrotPaymentProposalSelfSendV1 &propo //------------------------------------------------------------------------------------------------------------------- void get_output_proposal_return_v1(const CarrotPaymentProposalV1 &proposal, const crypto::key_image &tx_first_key_image, - const view_balance_secret_device *s_view_balance_dev, + const view_incoming_key_device *k_view_incoming_dev, RCTOutputEnoteProposal &output_enote_out, encrypted_payment_id_t &encrypted_payment_id_out) { @@ -490,7 +490,7 @@ void get_output_proposal_return_v1(const CarrotPaymentProposalV1 &proposal, CarrotEnoteType::PAYMENT, output_enote_out.enote.enote_ephemeral_pubkey, input_context, - s_view_balance_dev, + k_view_incoming_dev, false, // coinbase_amount_commitment s_sender_receiver, output_enote_out.amount_blinding_factor, diff --git a/src/carrot_core/payment_proposal.h b/src/carrot_core/payment_proposal.h index 2d2f4a1f2..01e1fba88 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_balance_secret_device *s_view_balance_dev, + const view_incoming_key_device *k_view_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_balance_secret_device *s_view_balance_dev, + const view_incoming_key_device *k_view_dev, RCTOutputEnoteProposal &output_enote_out, encrypted_payment_id_t &encrypted_payment_id_out); /** diff --git a/src/carrot_impl/account.h b/src/carrot_impl/account.h index 01e280303..3bb5c807f 100644 --- a/src/carrot_impl/account.h +++ b/src/carrot_impl/account.h @@ -46,19 +46,22 @@ namespace carrot struct return_output_info_t { input_context_t input_context; - crypto::public_key address_spend_pubkey; + crypto::public_key K_o; // output onetime address + crypto::public_key K_change; // change output onetime address crypto::key_image key_image; crypto::secret_key x; crypto::secret_key y; return_output_info_t( const input_context_t &input_context, - const crypto::public_key &address_spend_pubkey, + const crypto::public_key &K_o, + const crypto::public_key &K_change, const crypto::key_image &key_image, const crypto::secret_key &x, const crypto::secret_key &y): input_context(input_context), - address_spend_pubkey(address_spend_pubkey), + K_o(K_o), + K_change(K_change), key_image(key_image), x(x), y(y) {} diff --git a/src/carrot_impl/format_utils.cpp b/src/carrot_impl/format_utils.cpp index c5d342cda..ab980b8bd 100644 --- a/src/carrot_impl/format_utils.cpp +++ b/src/carrot_impl/format_utils.cpp @@ -355,6 +355,14 @@ bool try_load_carrot_enote_from_transaction_v1(const cryptonote::transaction &tx //D_e enote_out.enote_ephemeral_pubkey = enote_ephemeral_pubkeys[ephemeral_pubkey_index]; + + // save all output keys in order to calculate Kr values. + for (const auto& out: tx.vout) { + const cryptonote::txout_to_carrot_v1 * const carrot_out = boost::strict_get(&out.target); + if (carrot_out) { + enote_out.tx_output_keys.push_back(carrot_out->key); + } + } return true; } diff --git a/src/wallet/scanning_tools.cpp b/src/wallet/scanning_tools.cpp index b3aafb384..2446d213f 100644 --- a/src/wallet/scanning_tools.cpp +++ b/src/wallet/scanning_tools.cpp @@ -261,17 +261,25 @@ static std::optional view_incoming_scan_pre_car } //------------------------------------------------------------------------------------------------------------------- static bool scan_return_output( - const carrot::CarrotCoinbaseEnoteV1 &enote, + 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 + 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(enote.onetime_address), false, "return output not found"); - const auto &return_output = return_output_map.at(enote.onetime_address); + 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(return_output.input_context, return_output.address_spend_pubkey, 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; @@ -280,11 +288,11 @@ static bool scan_return_output( // 3. ssr mx25519_pubkey shared_secret_return_unctx; crypto::hash shared_secret_return; - carrot::make_carrot_uncontextualized_shared_key_receiver(k_return, enote.enote_ephemeral_pubkey, shared_secret_return_unctx); + 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, - enote.enote_ephemeral_pubkey, - return_output.input_context, + return_ephemeral_pubkey, + return_input_context, shared_secret_return ); @@ -292,9 +300,9 @@ static bool scan_return_output( CHECK_AND_ASSERT_MES( carrot::test_carrot_view_tag( shared_secret_return_unctx.data, - return_output.input_context, - enote.onetime_address, - enote.view_tag + return_input_context, + return_onetime_address, + return_view_tag ), false, "view tag verification failed for carrot coinbase enote" @@ -302,14 +310,14 @@ static bool scan_return_output( // 5. compute anchor_return carrot::janus_anchor_t recovered_anchor_return = - carrot::decrypt_carrot_anchor(enote.anchor_enc, shared_secret_return, enote.onetime_address); + 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_output.input_context, - return_output.address_spend_pubkey, + return_input_context, + origin_tx.K_change, carrot::null_payment_id, recovered_ephemeral_privkey_return ); @@ -318,19 +326,35 @@ static bool scan_return_output( mx25519_pubkey recovered_ephemeral_pubkey_return; carrot::make_carrot_enote_ephemeral_pubkey( recovered_ephemeral_privkey_return, - return_output.address_spend_pubkey, + 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, enote.enote_ephemeral_pubkey.data, sizeof(mx25519_pubkey)) == 0, + memcmp(recovered_ephemeral_pubkey_return.data, return_ephemeral_pubkey.data, sizeof(mx25519_pubkey)) == 0, false, "carrot coinbase enote protection verification failed" ); - address_spend_pubkey_out = return_output.address_spend_pubkey; + 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; } //------------------------------------------------------------------------------------------------------------------- @@ -358,10 +382,32 @@ static std::optional view_incoming_scan_carrot_ } 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; - if (!scan_return_output(enote, account, address_spend_pubkey)) + carrot::encrypted_amount_t amount_enc; + crypto::secret_key amount_blinding_factor; + rct::xmr_amount amount; + if (!scan_return_output( + enote.onetime_address, + enote.enote_ephemeral_pubkey, + enote.view_tag, + enote.anchor_enc, + amount_enc, + std::nullopt, // no amount commitment for coinbase enotes + account.get_return_output_map_ref() + .at(enote.onetime_address).input_context, + account, + address_spend_pubkey, + amount, + amount_blinding_factor) + ) { return std::nullopt; + } res.address_spend_pubkey = address_spend_pubkey; res.return_address = enote.onetime_address; @@ -445,6 +491,7 @@ static std::optional view_incoming_scan_carrot_ crypto::secret_key amount_blinding_factor_sk; carrot::payment_id_t payment_id; carrot::CarrotEnoteType dummy_enote_type; + bool found_in_return = false; if (!carrot::try_scan_carrot_enote_external_receiver(enote, encrypted_payment_id, s_sender_receiver_unctx, @@ -456,64 +503,103 @@ static std::optional view_incoming_scan_carrot_ res.amount, amount_blinding_factor_sk, payment_id, - dummy_enote_type)) - return std::nullopt; + 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; - 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(), + 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) + ) { + 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) { + 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"); + const carrot::subaddress_index_extended subaddr_index = subaddr_it->second; - 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)); - 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); - // we received and output - // save the Kr = K_change + K_return to out subaddress map - // 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, enote.onetime_address, 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_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}}}); - // 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 + ); - // 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 + ); - 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}}; + } - // save the input context & change output key - account.insert_return_output_info({{K_r, {input_context, enote.onetime_address, key_image, x, y}}}); - - res.subaddr_index = subaddr_index; res.amount_blinding_factor = rct::sk2rct(amount_blinding_factor_sk); res.main_tx_pubkey_index = 0; res.asset_type = enote.asset_type; res.is_carrot = true; - res.is_return = false; return res; } diff --git a/src/wallet/tx_builder.cpp b/src/wallet/tx_builder.cpp index 5f2d43607..2069b97f2 100644 --- a/src/wallet/tx_builder.cpp +++ b/src/wallet/tx_builder.cpp @@ -664,7 +664,7 @@ std::vector make_carrot_transaction_proposa tx_type, std::move(selected_inputs), change_address_spend_pubkey, - {{subaddr_account, 0}, carrot::AddressDeriveType::PreCarrot}, //! @TODO: handle Carrot keys + {{subaddr_account, 0}, carrot::AddressDeriveType::Carrot}, tx_proposal); // populate the sources diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index f1fc99c7d..430dba500 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -11699,7 +11699,7 @@ std::vector wallet2::create_transactions_return(std::vector THROW_WALLET_EXCEPTION_IF(idx >= get_num_transfer_details(), error::wallet_internal_error, tr("cannot locate return_payment origin index in m_transfers")); const transfer_details& td_origin = get_transfer_details(idx); const std::string asset_type = td_origin.m_tx.source_asset_type; - bool is_subaddress = true; + bool is_subaddress = false; size_t outputs = 1; std::vector unused_dust_indices = {}; size_t fake_outs_count = get_min_ring_size() - 1; // Use the default ring size @@ -11816,6 +11816,9 @@ std::vector wallet2::create_transactions_return(std::vector return create_transactions_from(address, cryptonote::transaction_type::RETURN, asset_type, is_subaddress, outputs, transfers_indices, unused_dust_indices, fake_outs_count, unlock_time, priority, extra); } + + // pre-carrot return payment construction uses subaddress true + is_subaddress = true; // To return a payment, we need to know the y value to process the F value // ...but the y value is calculated differently depending on the original TX