diff --git a/src/carrot_impl/carrot_tx_builder_utils.cpp b/src/carrot_impl/carrot_tx_builder_utils.cpp index 609a545ef..848a18a66 100644 --- a/src/carrot_impl/carrot_tx_builder_utils.cpp +++ b/src/carrot_impl/carrot_tx_builder_utils.cpp @@ -128,7 +128,8 @@ void make_carrot_transaction_proposal_v1(const std::vector &extra, select_inputs_func_t &&select_inputs, carve_fees_and_balance_func_t &&carve_fees_and_balance, - const crypto::public_key &account_spend_pubkey, + const crypto::public_key &change_address_spend_pubkey, + const subaddress_index_extended &change_address_index, CarrotTransactionProposalV1 &tx_proposal_out) { tx_proposal_out.extra = extra; @@ -143,8 +144,8 @@ void make_carrot_transaction_proposal_v1(const std::vector= CARROT_MIN_TX_OUTPUTS, @@ -242,7 +243,8 @@ void make_carrot_transaction_proposal_v1_transfer( const rct::xmr_amount fee_per_weight, const std::vector &extra, select_inputs_func_t &&select_inputs, - const crypto::public_key &account_spend_pubkey, + const crypto::public_key &change_address_spend_pubkey, + const subaddress_index_extended &change_address_index, const std::set &subtractable_normal_payment_proposals, const std::set &subtractable_selfsend_payment_proposals, CarrotTransactionProposalV1 &tx_proposal_out) @@ -261,13 +263,13 @@ void make_carrot_transaction_proposal_v1_transfer( selfsend_payment_proposals.size() == 1 && selfsend_payment_proposals.at(0).proposal.enote_type == CarrotEnoteType::CHANGE; - selfsend_payment_proposals.push_back(CarrotPaymentProposalVerifiableSelfSendV1{ + selfsend_payment_proposals.push_back(CarrotPaymentProposalVerifiableSelfSendV1{ .proposal = CarrotPaymentProposalSelfSendV1{ - .destination_address_spend_pubkey = account_spend_pubkey, + .destination_address_spend_pubkey = change_address_spend_pubkey, .amount = 0, .enote_type = add_payment_type_selfsend ? CarrotEnoteType::PAYMENT : CarrotEnoteType::CHANGE }, - .subaddr_index = {{0, 0}, AddressDeriveType::Auto} // @TODO: let callers pass AddressDeriveType as an argument + .subaddr_index = change_address_index }); // define carves fees and balance callback @@ -392,7 +394,8 @@ void make_carrot_transaction_proposal_v1_transfer( extra, std::forward(select_inputs), std::move(carve_fees_and_balance), - account_spend_pubkey, + change_address_spend_pubkey, + change_address_index, tx_proposal_out); } //------------------------------------------------------------------------------------------------------------------- @@ -402,7 +405,8 @@ void make_carrot_transaction_proposal_v1_sweep( const rct::xmr_amount fee_per_weight, const std::vector &extra, std::vector &&selected_inputs, - const crypto::public_key &account_spend_pubkey, + const crypto::public_key &change_address_spend_pubkey, + const subaddress_index_extended &change_address_index, CarrotTransactionProposalV1 &tx_proposal_out) { // sanity check payment proposals are provided @@ -480,7 +484,8 @@ void make_carrot_transaction_proposal_v1_sweep( extra, std::move(select_inputs), std::move(carve_fees_and_balance), - account_spend_pubkey, + change_address_spend_pubkey, + change_address_index, tx_proposal_out); } //------------------------------------------------------------------------------------------------------------------- diff --git a/src/carrot_impl/carrot_tx_builder_utils.h b/src/carrot_impl/carrot_tx_builder_utils.h index cf43272d9..9b6a5dd18 100644 --- a/src/carrot_impl/carrot_tx_builder_utils.h +++ b/src/carrot_impl/carrot_tx_builder_utils.h @@ -58,7 +58,8 @@ void make_carrot_transaction_proposal_v1(const std::vector &extra, select_inputs_func_t &&select_inputs, carve_fees_and_balance_func_t &&carve_fees_and_balance, - const crypto::public_key &account_spend_pubkey, + const crypto::public_key &change_address_spend_pubkey, + const subaddress_index_extended &change_address_index, CarrotTransactionProposalV1 &tx_proposal_out); void make_carrot_transaction_proposal_v1_transfer( @@ -67,7 +68,8 @@ void make_carrot_transaction_proposal_v1_transfer( const rct::xmr_amount fee_per_weight, const std::vector &extra, select_inputs_func_t &&select_inputs, - const crypto::public_key &account_spend_pubkey, + const crypto::public_key &change_address_spend_pubkey, + const subaddress_index_extended &change_address_index, const std::set &subtractable_normal_payment_proposals, const std::set &subtractable_selfsend_payment_proposals, CarrotTransactionProposalV1 &tx_proposal_out); @@ -78,7 +80,8 @@ void make_carrot_transaction_proposal_v1_sweep( const rct::xmr_amount fee_per_weight, const std::vector &extra, std::vector &&selected_inputs, - const crypto::public_key &account_spend_pubkey, + const crypto::public_key &change_address_spend_pubkey, + const subaddress_index_extended &change_address_index, CarrotTransactionProposalV1 &tx_proposal_out); void get_output_enote_proposals_from_proposal_v1(const CarrotTransactionProposalV1 &tx_proposal, diff --git a/src/wallet/tx_builder.cpp b/src/wallet/tx_builder.cpp index 34504b97b..98fa65267 100644 --- a/src/wallet/tx_builder.cpp +++ b/src/wallet/tx_builder.cpp @@ -126,6 +126,51 @@ static bool build_payment_proposals(std::vector } //------------------------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------------------------- +static cryptonote::tx_destination_entry make_tx_destination_entry( + const carrot::CarrotPaymentProposalV1 &payment_proposal) +{ + cryptonote::tx_destination_entry dest = cryptonote::tx_destination_entry(payment_proposal.amount, + {payment_proposal.destination.address_spend_pubkey, payment_proposal.destination.address_view_pubkey}, + payment_proposal.destination.is_subaddress); + dest.is_integrated = payment_proposal.destination.payment_id != carrot::null_payment_id; + return dest; +} +//------------------------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------------------------- +static cryptonote::tx_destination_entry make_tx_destination_entry( + const carrot::CarrotPaymentProposalVerifiableSelfSendV1 &payment_proposal, + const carrot::view_incoming_key_device &k_view_dev) +{ + crypto::public_key address_view_pubkey; + CHECK_AND_ASSERT_THROW_MES(k_view_dev.view_key_scalar_mult_ed25519( + payment_proposal.proposal.destination_address_spend_pubkey, + address_view_pubkey), + "make_tx_destination_entry: view-key multiplication failed"); + + return cryptonote::tx_destination_entry(payment_proposal.proposal.amount, + {payment_proposal.proposal.destination_address_spend_pubkey, address_view_pubkey}, + payment_proposal.subaddr_index.index.is_subaddress()); +} +//------------------------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------------------------- +static crypto::public_key find_change_address_spend_pubkey( + const std::unordered_map &subaddress_map, + const std::uint32_t subaddr_account) +{ + const auto change_it = std::find_if(subaddress_map.cbegin(), subaddress_map.cend(), + [subaddr_account](const auto &p) { return p.second.major == subaddr_account && p.second.minor == 0; }); + CHECK_AND_ASSERT_THROW_MES(change_it != subaddress_map.cend(), + "find_change_address_spend_pubkey: missing change address (index " + << subaddr_account << ",0) in subaddress map"); + const auto change_it_2 = std::find_if(std::next(change_it), subaddress_map.cend(), + [subaddr_account](const auto &p) { return p.second.major == subaddr_account && p.second.minor == 0; }); + CHECK_AND_ASSERT_THROW_MES(change_it_2 == subaddress_map.cend(), + "find_change_address_spend_pubkey: provided subaddress map is malformed!!! At least two spend pubkeys map to " + "index " << subaddr_account << ",0 in the subaddress map!"); + return change_it->first; +} +//------------------------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------------------------- std::unordered_map collect_non_burned_transfers_by_key_image( const wallet2::transfer_container &transfers) { @@ -258,6 +303,9 @@ std::vector make_carrot_transaction_proposa std::vector tx_proposals; tx_proposals.reserve(dsts.size() / (FCMP_PLUS_PLUS_MAX_OUTPUTS - 1) + 1); + const crypto::public_key change_address_spend_pubkey + = find_change_address_spend_pubkey(subaddress_map, subaddr_account); + while (!dsts.empty()) { const std::size_t num_dsts_to_complete = std::min(dsts.size(), FCMP_PLUS_PLUS_MAX_OUTPUTS - 1); @@ -305,7 +353,8 @@ std::vector make_carrot_transaction_proposa fee_per_weight, extra, std::move(select_inputs), - acc_keys.m_account_address.m_spend_public_key, + change_address_spend_pubkey, + {{subaddr_account, 0}, carrot::AddressDeriveType::PreCarrot}, //! @TODO: handle Carrot keys subtractable_normal_payment_proposals, subtractable_selfsend_payment_proposals, tx_proposal); @@ -383,9 +432,10 @@ std::vector make_carrot_transaction_proposa CHECK_AND_ASSERT_THROW_MES(n_dests, __func__ << ": n_dests is zero"); - // Check that the key image is available and isn't spent, and collect amounts + // Check that the key image is usable and isn't spent, collect amounts, and get subaddress account index std::vector input_amounts; input_amounts.reserve(input_key_images.size()); + std::uint32_t subaddr_account = std::numeric_limits::max(); const auto best_transfers_by_ki = collect_non_burned_transfers_by_key_image(transfers); for (const crypto::key_image &ki : input_key_images) { @@ -401,8 +451,12 @@ std::vector make_carrot_transaction_proposa top_block_index), __func__ << ": transfer not usable as an input"); input_amounts.push_back(td.amount()); + subaddr_account = std::min(subaddr_account, td.m_subaddr_index.major); } + const crypto::public_key change_address_spend_pubkey + = find_change_address_spend_pubkey(subaddress_map, subaddr_account); + // get 1 payment proposal corresponding to (address, is_subaddres) std::vector normal_payment_proposal; std::vector selfsend_payment_proposal; @@ -477,7 +531,8 @@ std::vector make_carrot_transaction_proposa fee_per_weight, extra, std::move(sweep_outlay.selected_inputs), - acc_keys.m_account_address.m_spend_public_key, + change_address_spend_pubkey, + {{subaddr_account, 0}, carrot::AddressDeriveType::PreCarrot}, //! @TODO: handle Carrot keys tx_proposal); tx_proposals.push_back(std::move(tx_proposal)); @@ -608,130 +663,5 @@ std::vector make_carrot_transaction_proposa top_block_index, w.get_account().get_keys()); } -//------------------------------------------------------------------------------------------------------------------- -wallet2::pending_tx make_pending_carrot_tx(const carrot::CarrotTransactionProposalV1 &tx_proposal, - const wallet2::transfer_container &transfers, - const cryptonote::account_keys &acc_keys) -{ - const std::size_t n_inputs = tx_proposal.key_images_sorted.size(); - const std::size_t n_outputs = tx_proposal.normal_payment_proposals.size() + - tx_proposal.selfsend_payment_proposals.size(); - const bool shared_ephemeral_pubkey = n_outputs == 2; - - const crypto::key_image &tx_first_key_image = tx_proposal.key_images_sorted.at(0); - - // collect non-burned transfers - const std::unordered_map unburned_transfers_by_key_image = - collect_non_burned_transfers_by_key_image(transfers); - - // collect selected_transfers and key_images string - std::vector selected_transfers; - selected_transfers.reserve(n_inputs); - std::stringstream key_images_string; - for (size_t i = 0; i < n_inputs; ++i) - { - const crypto::key_image &ki = tx_proposal.key_images_sorted.at(i); - const auto ki_it = unburned_transfers_by_key_image.find(ki); - CHECK_AND_ASSERT_THROW_MES(ki_it != unburned_transfers_by_key_image.cend(), - "make_pending_carrot_tx: unrecognized key image in transfers list"); - selected_transfers.push_back(ki_it->second); - if (i) - key_images_string << ' '; - key_images_string << ki; - } - - //! @TODO: HW device - carrot::view_incoming_key_ram_borrowed_device k_view_dev(acc_keys.m_view_secret_key); - - // get order of payment proposals - std::vector output_enote_proposals; - carrot::encrypted_payment_id_t encrypted_payment_id; - std::vector> sorted_payment_proposal_indices; - carrot::get_output_enote_proposals_from_proposal_v1(tx_proposal, - /*s_view_balance_dev=*/nullptr, - &k_view_dev, - output_enote_proposals, - encrypted_payment_id, - &sorted_payment_proposal_indices); - - // calculate change_dst index based whether 2-out tx has a dummy output - // change_dst is set to dummy in 2-out self-send, otherwise last self-send - const bool has_2out_dummy = n_outputs == 2 - && tx_proposal.normal_payment_proposals.size() == 1 - && tx_proposal.normal_payment_proposals.at(0).amount == 0; - CHECK_AND_ASSERT_THROW_MES(!tx_proposal.selfsend_payment_proposals.empty(), - "make_pending_carrot_tx: carrot tx proposal missing a self-send proposal"); - const std::pair change_dst_index{!has_2out_dummy, - has_2out_dummy ? 0 : tx_proposal.selfsend_payment_proposals.size()-1}; - - // collect destinations and private tx keys for normal enotes - //! @TODO: payment proofs for special self-send, perhaps generate d_e deterministically - cryptonote::tx_destination_entry change_dts; - std::vector dests; - std::vector ephemeral_privkeys; - dests.reserve(n_outputs); - ephemeral_privkeys.reserve(n_outputs); - for (const std::pair &payment_idx : sorted_payment_proposal_indices) - { - cryptonote::tx_destination_entry dest; - - const bool is_selfsend = payment_idx.first; - if (is_selfsend) - { - dest = make_tx_destination_entry(tx_proposal.selfsend_payment_proposals.at(payment_idx.second), - k_view_dev); - ephemeral_privkeys.push_back(crypto::null_skey); - } - else // !is_selfsend - { - const carrot::CarrotPaymentProposalV1 &normal_payment_proposal = - tx_proposal.normal_payment_proposals.at(payment_idx.second); - dest = make_tx_destination_entry(normal_payment_proposal); - ephemeral_privkeys.push_back(carrot::get_enote_ephemeral_privkey(normal_payment_proposal, - carrot::make_carrot_input_context(tx_first_key_image))); - } - - if (payment_idx == change_dst_index) - change_dts = dest; - else - dests.push_back(dest); - } - - // collect subaddr account and minor indices - const std::uint32_t subaddr_account = transfers.at(selected_transfers.at(0)).m_subaddr_index.major; - std::set subaddr_indices; - for (const size_t selected_transfer : selected_transfers) - { - const wallet2::transfer_details &td = transfers.at(selected_transfer); - const std::uint32_t other_subaddr_account = td.m_subaddr_index.major; - if (other_subaddr_account != subaddr_account) - { - MWARNING("make_pending_carrot_tx: conflicting account indices: " << subaddr_account << " vs " - << other_subaddr_account); - } - subaddr_indices.insert(td.m_subaddr_index.minor); - } - - wallet2::pending_tx ptx; - ptx.tx.set_null(); - ptx.dust = 0; - ptx.fee = tx_proposal.fee; - ptx.dust_added_to_fee = false; - ptx.change_dts = change_dts; - ptx.selected_transfers = std::move(selected_transfers); - ptx.key_images = key_images_string.str(); - ptx.tx_key = shared_ephemeral_pubkey ? ephemeral_privkeys.at(0) : crypto::null_skey; - if (shared_ephemeral_pubkey) - ptx.additional_tx_keys = std::move(ephemeral_privkeys); - else - ptx.additional_tx_keys.clear(); - ptx.dests = std::move(dests); - ptx.multisig_sigs = {}; - ptx.multisig_tx_key_entropy = {}; - ptx.subaddr_account = subaddr_account; - ptx.subaddr_indices = std::move(subaddr_indices); - ptx.construction_data = tx_proposal; - return ptx; -} } //namespace wallet } //namespace tools diff --git a/tests/unit_tests/carrot_fcmp.cpp b/tests/unit_tests/carrot_fcmp.cpp index 03babd525..45af3774a 100644 --- a/tests/unit_tests/carrot_fcmp.cpp +++ b/tests/unit_tests/carrot_fcmp.cpp @@ -358,6 +358,7 @@ TEST(carrot_fcmp, receive_scan_spend_and_verify_serialized_carrot_tx) } }, alice.carrot_account_spend_pubkey, + {{0, 0}, AddressDeriveType::Carrot}, {}, {}, tx_proposal); diff --git a/tests/unit_tests/carrot_impl.cpp b/tests/unit_tests/carrot_impl.cpp index 4e3013f8a..9f840c52d 100644 --- a/tests/unit_tests/carrot_impl.cpp +++ b/tests/unit_tests/carrot_impl.cpp @@ -159,6 +159,7 @@ static void subtest_multi_account_transfer_over_transaction(const unittest_trans tx_preproposal.extra_extra, make_fake_input_selection_callback(), ss_keys.carrot_account_spend_pubkey, + {{0, 0}, AddressDeriveType::Carrot}, {}, {}, tx_proposal); diff --git a/tests/unit_tests/carrot_mock_helpers.cpp b/tests/unit_tests/carrot_mock_helpers.cpp index 64b41ff1e..ff2a7dacf 100644 --- a/tests/unit_tests/carrot_mock_helpers.cpp +++ b/tests/unit_tests/carrot_mock_helpers.cpp @@ -103,6 +103,17 @@ CarrotDestinationV1 mock_carrot_and_legacy_keys::subaddress(const subaddress_ind return addr; } //---------------------------------------------------------------------------------------------------------------------- +std::unordered_map mock_carrot_and_legacy_keys::subaddress_map_cn() const +{ + std::unordered_map res; + for (const auto &p : subaddress_map) + if (p.second.derive_type == AddressDeriveType::PreCarrot) + res.emplace(p.first, cryptonote::subaddress_index{p.second.index.major, p.second.index.minor}); + CHECK_AND_ASSERT_THROW_MES(!res.empty(), + "mock_carrot_and_legacy_keys::subaddress_map_cn: subaddress map does not contain pre-carrot subaddresses"); + return res; +} +//---------------------------------------------------------------------------------------------------------------------- void mock_carrot_and_legacy_keys::opening_for_subaddress(const subaddress_index_extended &subaddress_index, crypto::secret_key &address_privkey_g_out, crypto::secret_key &address_privkey_t_out, @@ -506,12 +517,20 @@ CarrotDestinationV1 convert_destination_v1(const cryptonote::tx_destination_entr }; } //---------------------------------------------------------------------------------------------------------------------- -CarrotPaymentProposalV1 convert_normal_payment_proposal_v1(const cryptonote::tx_destination_entry &cn_dst) +cryptonote::tx_destination_entry convert_destination_v1(const CarrotDestinationV1 &dst, const rct::xmr_amount amount) +{ + return cryptonote::tx_destination_entry(amount, + {dst.address_spend_pubkey, dst.address_view_pubkey}, + dst.is_subaddress); +} +//---------------------------------------------------------------------------------------------------------------------- +CarrotPaymentProposalV1 convert_normal_payment_proposal_v1(const cryptonote::tx_destination_entry &cn_dst, + const janus_anchor_t randomness) { return CarrotPaymentProposalV1{ .destination = convert_destination_v1(cn_dst), .amount = cn_dst.amount, - .randomness = gen_janus_anchor() + .randomness = randomness }; } //---------------------------------------------------------------------------------------------------------------------- diff --git a/tests/unit_tests/carrot_mock_helpers.h b/tests/unit_tests/carrot_mock_helpers.h index 84dd7be42..52e6b0186 100644 --- a/tests/unit_tests/carrot_mock_helpers.h +++ b/tests/unit_tests/carrot_mock_helpers.h @@ -94,6 +94,8 @@ struct mock_carrot_and_legacy_keys CarrotDestinationV1 subaddress(const subaddress_index_extended &subaddress_index) const; + std::unordered_map subaddress_map_cn() const; + // brief: opening_for_subaddress - return (k^g_a, k^t_a) for j s.t. K^j_s = (k^g_a * G + k^t_a * T) void opening_for_subaddress(const subaddress_index_extended &subaddress_index, crypto::secret_key &address_privkey_g_out, @@ -177,7 +179,10 @@ std::uint64_t gen_block_index(); //---------------------------------------------------------------------------------------------------------------------- CarrotDestinationV1 convert_destination_v1(const cryptonote::tx_destination_entry &cn_dst); //---------------------------------------------------------------------------------------------------------------------- -CarrotPaymentProposalV1 convert_normal_payment_proposal_v1(const cryptonote::tx_destination_entry &cn_dst); +cryptonote::tx_destination_entry convert_destination_v1(const CarrotDestinationV1 &dst, const rct::xmr_amount amount); +//---------------------------------------------------------------------------------------------------------------------- +CarrotPaymentProposalV1 convert_normal_payment_proposal_v1(const cryptonote::tx_destination_entry &cn_dst, + const janus_anchor_t randomness = gen_janus_anchor()); //---------------------------------------------------------------------------------------------------------------------- CarrotPaymentProposalSelfSendV1 convert_selfsend_payment_proposal_v1(const cryptonote::tx_destination_entry &cn_dst); //---------------------------------------------------------------------------------------------------------------------- diff --git a/tests/unit_tests/tx_construction_helpers.cpp b/tests/unit_tests/tx_construction_helpers.cpp index 60469b1dc..29de60ee6 100644 --- a/tests/unit_tests/tx_construction_helpers.cpp +++ b/tests/unit_tests/tx_construction_helpers.cpp @@ -427,6 +427,7 @@ cryptonote::transaction construct_carrot_pruned_transaction_fake_inputs( /*extra=*/{}, std::move(select_inputs), acc_keys.m_account_address.m_spend_public_key, + {{0, 0}, carrot::AddressDeriveType::PreCarrot}, {}, {}, tx_proposal); diff --git a/tests/unit_tests/wallet_tx_builder.cpp b/tests/unit_tests/wallet_tx_builder.cpp index 4b25fe36a..1d46314d8 100644 --- a/tests/unit_tests/wallet_tx_builder.cpp +++ b/tests/unit_tests/wallet_tx_builder.cpp @@ -49,7 +49,7 @@ static tools::wallet2::transfer_details gen_transfer_details() .m_spent_height = 0, .m_key_image = crypto::key_image{rct::rct2pk(rct::pkGen())}, .m_mask = rct::skGen(), - .m_amount = crypto::rand_range(0, COIN), // [0, 1] XMR i.e. [0, 1e12] pXMR + .m_amount = crypto::rand_range(COIN, 2 * COIN), // [1, 2] XMR i.e. [1e12, 2e12] pXMR .m_rct = true, .m_key_image_known = true, .m_key_image_request = false, @@ -158,7 +158,7 @@ TEST(wallet_tx_builder, make_carrot_transaction_proposals_wallet2_transfer_1) const std::vector tx_proposals = tools::wallet::make_carrot_transaction_proposals_wallet2_transfer( transfers, - /*subaddress_map=*/{}, + {{alice.get_keys().m_account_address.m_spend_public_key, {}}}, dsts, /*fee_per_weight=*/1, /*extra=*/{}, @@ -188,6 +188,97 @@ TEST(wallet_tx_builder, make_carrot_transaction_proposals_wallet2_transfer_1) transfers.front().amount()); } //---------------------------------------------------------------------------------------------------------------------- +TEST(wallet_tx_builder, make_carrot_transaction_proposals_wallet2_transfer_2) +{ + carrot::mock::mock_carrot_and_legacy_keys alice; + alice.generate(); + carrot::mock::mock_carrot_and_legacy_keys bob; + bob.generate(); + + static constexpr uint32_t spending_subaddr_account = 2; + static_assert(spending_subaddr_account); + + tools::wallet2::transfer_container transfers; + std::uint64_t top_block_index = 0; + std::unordered_map allowed_transfers; + for (size_t i = 0; i < FCMP_PLUS_PLUS_MAX_INPUTS + 2; ++i) + { + tools::wallet2::transfer_details &td = tools::add_element(transfers); + td = gen_transfer_details(); + td.m_subaddr_index.major = (i % 2 == 0) ? spending_subaddr_account : (spending_subaddr_account - 1); + td.m_subaddr_index.minor = crypto::rand_range(0, carrot::mock::MAX_SUBADDRESS_MINOR_INDEX); + top_block_index = std::max(top_block_index, td.m_block_height); + + if (td.m_subaddr_index.major == spending_subaddr_account) + allowed_transfers.emplace(td.m_key_image, i); + } + top_block_index += CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE; + + const rct::xmr_amount out_amount = COIN * 3 / 4; + + const std::vector dsts{ + carrot::mock::convert_destination_v1(bob.cryptonote_address(), out_amount) + }; + + const std::vector tx_proposals = tools::wallet::make_carrot_transaction_proposals_wallet2_transfer( + transfers, + alice.subaddress_map_cn(), + dsts, + /*fee_per_weight=*/1, + /*extra=*/{}, + /*subaddr_account=*/spending_subaddr_account, + /*subaddr_indices=*/{}, + /*ignore_above=*/MONEY_SUPPLY, + /*ignore_below=*/0, + {}, + top_block_index, + alice.legacy_acb.get_keys()); + + ASSERT_EQ(1, tx_proposals.size()); + const carrot::CarrotTransactionProposalV1 &tx_proposal = tx_proposals.at(0); + + // Assert basic length facts about tx proposal + ASSERT_LE(tx_proposal.key_images_sorted.size(), 2); + ASSERT_EQ(1, tx_proposal.normal_payment_proposals.size()); + ASSERT_EQ(1, tx_proposal.selfsend_payment_proposals.size()); + EXPECT_EQ(0, tx_proposal.extra.size()); + + const carrot::CarrotPaymentProposalV1 &normal_payment_proposal = tx_proposal.normal_payment_proposals.at(0); + const carrot::CarrotPaymentProposalVerifiableSelfSendV1 &selfsend_payment_proposal = tx_proposal.selfsend_payment_proposals.at(0); + + // Assert that selected transfers have spending_subaddr_account subaddr major index + boost::multiprecision::uint128_t in_sum = 0; + for (std::size_t in_idx = 0; in_idx < tx_proposal.key_images_sorted.size(); ++in_idx) + { + const crypto::key_image &ki = tx_proposal.key_images_sorted.at(in_idx); + if (in_idx > 0) + { + ASSERT_LT(ki, tx_proposal.key_images_sorted.at(in_idx - 1)); + } + ASSERT_EQ(1, allowed_transfers.count(ki)); + const tools::wallet2::transfer_details &td = transfers.at(allowed_transfers.at(ki)); + ASSERT_EQ(spending_subaddr_account, td.m_subaddr_index.major); + in_sum += td.amount(); + } + + // Assert balanced amounts + boost::multiprecision::uint128_t out_sum = tx_proposal.fee; + out_sum += normal_payment_proposal.amount; + out_sum += selfsend_payment_proposal.proposal.amount; + ASSERT_EQ(in_sum, out_sum); + + // Assert pubkeys/subaddr indices/amounts of payment proposals + EXPECT_EQ(carrot::mock::convert_normal_payment_proposal_v1(dsts.at(0), normal_payment_proposal.randomness), normal_payment_proposal); + EXPECT_NE(normal_payment_proposal.randomness, carrot::janus_anchor_t{}); + EXPECT_EQ(spending_subaddr_account, selfsend_payment_proposal.subaddr_index.index.major); + EXPECT_EQ(0, selfsend_payment_proposal.subaddr_index.index.minor); + EXPECT_EQ(alice.subaddress({{spending_subaddr_account, 0}, carrot::AddressDeriveType::PreCarrot}).address_spend_pubkey, + selfsend_payment_proposal.proposal.destination_address_spend_pubkey); + EXPECT_EQ(carrot::CarrotEnoteType::CHANGE, selfsend_payment_proposal.proposal.enote_type); + EXPECT_FALSE(selfsend_payment_proposal.proposal.internal_message); + EXPECT_FALSE(selfsend_payment_proposal.proposal.enote_ephemeral_pubkey); +} +//---------------------------------------------------------------------------------------------------------------------- TEST(wallet_tx_builder, make_carrot_transaction_proposals_wallet2_sweep_1) { cryptonote::account_base alice; @@ -199,7 +290,7 @@ TEST(wallet_tx_builder, make_carrot_transaction_proposals_wallet2_sweep_1) const std::vector tx_proposals = tools::wallet::make_carrot_transaction_proposals_wallet2_sweep( transfers, - /*subaddress_map=*/{}, + {{alice.get_keys().m_account_address.m_spend_public_key, {}}}, {transfers.front().m_key_image}, bob.get_keys().m_account_address, /*is_subaddress=*/false, @@ -234,7 +325,7 @@ TEST(wallet_tx_builder, make_carrot_transaction_proposals_wallet2_sweep_2) const std::vector tx_proposals = tools::wallet::make_carrot_transaction_proposals_wallet2_sweep( transfers, - /*subaddress_map=*/{}, + {{alice.get_keys().m_account_address.m_spend_public_key, {}}}, {transfers.front().m_key_image}, bob.get_keys().m_account_address, /*is_subaddress=*/false, @@ -277,7 +368,7 @@ TEST(wallet_tx_builder, make_carrot_transaction_proposals_wallet2_sweep_3) const std::vector tx_proposals = tools::wallet::make_carrot_transaction_proposals_wallet2_sweep( transfers, - /*subaddress_map=*/{{alice.get_keys().m_account_address.m_spend_public_key, {}}}, + {{alice.get_keys().m_account_address.m_spend_public_key, {}}}, {transfers.front().m_key_image}, alice.get_keys().m_account_address, /*is_subaddress=*/false, @@ -353,7 +444,7 @@ TEST(wallet_tx_builder, make_carrot_transaction_proposals_wallet2_sweep_4) // make tx proposals const std::vector tx_proposals = tools::wallet::make_carrot_transaction_proposals_wallet2_sweep( transfers, - /*subaddress_map=*/{}, + {{alice.get_keys().m_account_address.m_spend_public_key, {}}}, selected_key_images, bob.get_keys().m_account_address, /*is_subaddress=*/false, @@ -436,7 +527,7 @@ TEST(wallet_tx_builder, make_carrot_transaction_proposals_wallet2_sweep_5) // make tx proposals const std::vector tx_proposals = tools::wallet::make_carrot_transaction_proposals_wallet2_sweep( transfers, - /*subaddress_map=*/{{alice.get_keys().m_account_address.m_spend_public_key, {}}}, + {{alice.get_keys().m_account_address.m_spend_public_key, {}}}, selected_key_images, alice.get_keys().m_account_address, /*is_subaddress=*/false, @@ -519,7 +610,7 @@ TEST(wallet_tx_builder, make_carrot_transaction_proposals_wallet2_sweep_6) // make tx proposals const std::vector tx_proposals = tools::wallet::make_carrot_transaction_proposals_wallet2_sweep( transfers, - /*subaddress_map=*/{{alice.get_keys().m_account_address.m_spend_public_key, {}}}, + {{alice.get_keys().m_account_address.m_spend_public_key, {}}}, selected_key_images, alice.get_keys().m_account_address, /*is_subaddress=*/false,