mirror of
https://github.com/wownero/wownero.git
synced 2026-10-17 23:51:37 +02:00
+3









j-berman
jeffro256
Luke Parker
Lee *!* Clagett
SyntheticBird45
jeetrex17
UkoeHB
MrCyjaneK
tobtoht
selsta
Thomas
navidR
Boog900
a9145f9d38
Co-authored-by: jeffro256 <jeffro256@tutanota.com> Co-authored-by: Luke Parker <lukeparker5132@gmail.com> Co-authored-by: Lee *!* Clagett <code@leeclagett.com> Co-authored-by: SyntheticBird45 <someoneelse.is_on.github.rio7x@simplelogin.com> Co-authored-by: jeetrex17 <jeetrex@tutamail.com> Co-authored-by: UkoeHB <ukoehb@users.noreply.github.com> Co-authored-by: MrCyjaneK Co-authored-by: tobtoht Co-authored-by: selsta Co-authored-by: Thomas <thomas.giudici@proton.me> Co-authored-by: navidR Co-authored-by: Boog900 <boog900@tutanota.com>
1948 lines
80 KiB
C++
1948 lines
80 KiB
C++
// Copyright (c) 2024-2026, The Monero Project
|
|
//
|
|
// All rights reserved.
|
|
//
|
|
// Redistribution and use in source and binary forms, with or without modification, are
|
|
// permitted provided that the following conditions are met:
|
|
//
|
|
// 1. Redistributions of source code must retain the above copyright notice, this list of
|
|
// conditions and the following disclaimer.
|
|
//
|
|
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
|
// of conditions and the following disclaimer in the documentation and/or other
|
|
// materials provided with the distribution.
|
|
//
|
|
// 3. Neither the name of the copyright holder nor the names of its contributors may be
|
|
// used to endorse or promote products derived from this software without specific
|
|
// prior written permission.
|
|
//
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
|
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
|
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
|
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
|
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
#include "gtest/gtest.h"
|
|
|
|
#include <boost/multiprecision/cpp_int.hpp>
|
|
#include <boost/numeric/conversion/cast.hpp>
|
|
|
|
#include "carrot_core/exceptions.h"
|
|
#include "carrot_core/payment_proposal.h"
|
|
#include "carrot_impl/format_utils.h"
|
|
#include "carrot_impl/multi_tx_proposal_utils.h"
|
|
#include "carrot_impl/spend_device_ram_borrowed.h"
|
|
#include "carrot_impl/tx_builder_inputs.h"
|
|
#include "carrot_impl/tx_builder_outputs.h"
|
|
#include "carrot_impl/tx_proposal_utils.h"
|
|
#include "carrot_mock_helpers.h"
|
|
#include "fcmp_pp/prove.h"
|
|
#include "ringct/rctOps.h"
|
|
|
|
using namespace carrot;
|
|
using namespace carrot::mock::people;
|
|
|
|
namespace
|
|
{
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
static carrot::OutputOpeningHintVariant gen_output_opening_hint()
|
|
{
|
|
return carrot::CarrotOutputOpeningHintV1{
|
|
.source_enote = carrot::mock::gen_carrot_enote_v1(),
|
|
.encrypted_payment_id = carrot::gen_encrypted_payment_id(),
|
|
.subaddr_index = carrot::mock::gen_subaddress_index_extended()
|
|
};
|
|
}
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
static void unittest_scan_enote_set_multi_account(const std::vector<CarrotEnoteV1> &enotes,
|
|
const encrypted_payment_id_t encrypted_payment_id,
|
|
const epee::span<const mock::mock_carrot_and_legacy_keys * const> accounts,
|
|
std::vector<std::vector<mock::mock_scan_result_t>> &res)
|
|
{
|
|
res.clear();
|
|
res.reserve(accounts.size());
|
|
|
|
for (const auto *account : accounts)
|
|
mock_scan_enote_set(enotes, encrypted_payment_id, *account, res.emplace_back());
|
|
}
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
struct unittest_transaction_preproposal
|
|
{
|
|
using per_payment_proposal = std::pair<CarrotPaymentProposalV1, /*is subtractble?*/bool>;
|
|
using per_ss_payment_proposal = std::pair<CarrotPaymentProposalVerifiableSelfSendV1, /*is subtractble?*/bool>;
|
|
using per_account = std::pair<mock::mock_carrot_and_legacy_keys, std::vector<per_payment_proposal>>;
|
|
using per_input = std::pair<crypto::key_image, rct::xmr_amount>;
|
|
|
|
std::vector<per_account> per_account_payments;
|
|
std::vector<per_ss_payment_proposal> explicit_selfsend_proposals;
|
|
size_t self_sender_index{0};
|
|
rct::xmr_amount fee_per_weight;
|
|
std::vector<std::uint8_t> extra_extra;
|
|
|
|
void get_flattened_payment_proposals(std::vector<CarrotPaymentProposalV1> &normal_payment_proposals_out,
|
|
std::vector<CarrotPaymentProposalVerifiableSelfSendV1> &selfsend_payment_proposals_out,
|
|
std::set<size_t> &subtractable_normal_payment_proposals,
|
|
std::set<size_t> &subtractable_selfsend_payment_proposals) const
|
|
{
|
|
size_t norm_idx = 0;
|
|
for (const per_account &pa : per_account_payments)
|
|
{
|
|
for (const per_payment_proposal &ppp : pa.second)
|
|
{
|
|
normal_payment_proposals_out.push_back(ppp.first);
|
|
if (ppp.second)
|
|
subtractable_normal_payment_proposals.insert(norm_idx);
|
|
|
|
norm_idx++;
|
|
}
|
|
}
|
|
|
|
for (size_t ss_idx = 0; ss_idx < explicit_selfsend_proposals.size(); ++ss_idx)
|
|
{
|
|
const per_ss_payment_proposal &pspp = explicit_selfsend_proposals.at(ss_idx);
|
|
selfsend_payment_proposals_out.push_back(pspp.first);
|
|
if (pspp.second)
|
|
subtractable_selfsend_payment_proposals.insert(ss_idx);
|
|
}
|
|
}
|
|
};
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
select_inputs_func_t make_fake_input_selection_callback(size_t num_ins = 0)
|
|
{
|
|
return [num_ins](const boost::multiprecision::uint128_t &nominal_output_sum,
|
|
const std::map<std::size_t, rct::xmr_amount> &fee_per_input_count,
|
|
size_t,
|
|
size_t,
|
|
std::vector<CarrotSelectedInput> &selected_inputs)
|
|
{
|
|
const size_t nins = num_ins ? num_ins : 1;
|
|
selected_inputs.clear();
|
|
selected_inputs.reserve(nins);
|
|
|
|
const rct::xmr_amount fee = fee_per_input_count.at(nins);
|
|
rct::xmr_amount in_amount_sum_64 = boost::numeric_cast<rct::xmr_amount>(nominal_output_sum + fee);
|
|
|
|
for (size_t i = 0; i < nins - 1; ++i)
|
|
{
|
|
const rct::xmr_amount current_in_amount = in_amount_sum_64 ? crypto::rand_idx(in_amount_sum_64) : 0;
|
|
selected_inputs.push_back({current_in_amount, gen_output_opening_hint()});
|
|
in_amount_sum_64 -= current_in_amount;
|
|
}
|
|
|
|
selected_inputs.push_back({in_amount_sum_64, gen_output_opening_hint()});
|
|
};
|
|
}
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
static void subtest_multi_account_transfer_over_transaction(const unittest_transaction_preproposal &tx_preproposal)
|
|
{
|
|
// get payment proposals
|
|
std::vector<CarrotPaymentProposalV1> normal_payment_proposals;
|
|
std::vector<CarrotPaymentProposalVerifiableSelfSendV1> selfsend_payment_proposals;
|
|
std::set<size_t> subtractable_normal_payment_proposals;
|
|
std::set<size_t> subtractable_selfsend_payment_proposals;
|
|
tx_preproposal.get_flattened_payment_proposals(normal_payment_proposals,
|
|
selfsend_payment_proposals,
|
|
subtractable_normal_payment_proposals,
|
|
subtractable_selfsend_payment_proposals);
|
|
|
|
// get self-sender account
|
|
const mock::mock_carrot_and_legacy_keys &ss_keys =
|
|
tx_preproposal.per_account_payments.at(tx_preproposal.self_sender_index).first;
|
|
|
|
// make transaction proposal
|
|
CarrotTransactionProposalV1 tx_proposal;
|
|
make_carrot_transaction_proposal_v1_transfer(normal_payment_proposals,
|
|
selfsend_payment_proposals,
|
|
tx_preproposal.fee_per_weight,
|
|
tx_preproposal.extra_extra,
|
|
make_fake_input_selection_callback(),
|
|
ss_keys.carrot_account_spend_pubkey,
|
|
{{0, 0}, AddressDeriveType::Carrot},
|
|
{},
|
|
{},
|
|
tx_proposal);
|
|
|
|
// make unsigned transaction
|
|
cryptonote::transaction tx;
|
|
make_pruned_transaction_from_proposal_v1(tx_proposal,
|
|
&ss_keys.s_view_balance_dev,
|
|
&ss_keys.k_view_incoming_dev,
|
|
mock::dummy_key_image_device{},
|
|
tx);
|
|
|
|
// calculate acceptable fee margin between proposed amount and actual amount for subtractable outputs
|
|
const size_t num_subtractable = subtractable_normal_payment_proposals.size() +
|
|
subtractable_selfsend_payment_proposals.size();
|
|
const rct::xmr_amount acceptable_fee_margin = num_subtractable
|
|
? (tx.rct_signatures.txnFee / num_subtractable) + 1
|
|
: 0;
|
|
|
|
// load carrot stuff from tx
|
|
std::vector<CarrotEnoteV1> parsed_enotes;
|
|
std::vector<crypto::key_image> parsed_key_images;
|
|
rct::xmr_amount parsed_fee;
|
|
std::optional<encrypted_payment_id_t> parsed_encrypted_payment_id;
|
|
ASSERT_TRUE(try_load_carrot_from_transaction_v1(tx,
|
|
parsed_enotes,
|
|
parsed_key_images,
|
|
parsed_fee,
|
|
parsed_encrypted_payment_id));
|
|
ASSERT_TRUE(parsed_encrypted_payment_id);
|
|
|
|
// sanity check that the enotes and pid_enc loaded from the transaction are equal to the enotes
|
|
// and pic_enc returned from get_output_enote_proposals() when called with the modified payment
|
|
// proposals. we do this so that the modified payment proposals from make_unsigned_transaction()
|
|
// can be passed to a hardware device for deterministic verification of the signable tx hash
|
|
std::vector<RCTOutputEnoteProposal> rederived_output_enote_proposals;
|
|
encrypted_payment_id_t rederived_encrypted_payment_id;
|
|
get_output_enote_proposals_from_proposal_v1(tx_proposal,
|
|
&ss_keys.s_view_balance_dev,
|
|
&ss_keys.k_view_incoming_dev,
|
|
parsed_key_images.at(0),
|
|
rederived_output_enote_proposals,
|
|
rederived_encrypted_payment_id);
|
|
EXPECT_EQ(*parsed_encrypted_payment_id, rederived_encrypted_payment_id);
|
|
ASSERT_EQ(parsed_enotes.size(), rederived_output_enote_proposals.size());
|
|
for (size_t enote_idx = 0; enote_idx < parsed_enotes.size(); ++enote_idx)
|
|
{
|
|
EXPECT_EQ(parsed_enotes.at(enote_idx), rederived_output_enote_proposals.at(enote_idx).enote);
|
|
}
|
|
|
|
// collect accounts
|
|
std::vector<const mock::mock_carrot_and_legacy_keys *> accounts;
|
|
for (const auto &pa : tx_preproposal.per_account_payments)
|
|
accounts.push_back(&pa.first);
|
|
|
|
// do scanning of all accounts on every enotes
|
|
std::vector<std::vector<mock::mock_scan_result_t>> scan_results;
|
|
unittest_scan_enote_set_multi_account(parsed_enotes,
|
|
*parsed_encrypted_payment_id,
|
|
epee::to_span(accounts),
|
|
scan_results);
|
|
|
|
// check that the scan results for each *normal* account match the corresponding payment
|
|
// proposals for each account. also check that the accounts can each open their corresponding
|
|
// onetime outut pubkeys
|
|
ASSERT_EQ(scan_results.size(), accounts.size());
|
|
// for each normal account...
|
|
for (size_t account_idx = 0; account_idx < accounts.size(); ++account_idx)
|
|
{
|
|
// skip self-sender account
|
|
if (account_idx == tx_preproposal.self_sender_index)
|
|
continue;
|
|
|
|
const std::vector<mock::mock_scan_result_t> &account_scan_results = scan_results.at(account_idx);
|
|
const auto &account_payment_proposals = tx_preproposal.per_account_payments.at(account_idx).second;
|
|
ASSERT_EQ(account_payment_proposals.size(), account_scan_results.size());
|
|
std::set<size_t> matched_payment_proposals;
|
|
|
|
// for each scan result assigned to this account...
|
|
for (const mock::mock_scan_result_t &single_scan_res : account_scan_results)
|
|
{
|
|
// for each normal payment proposal to this account...
|
|
for (size_t norm_prop_idx = 0; norm_prop_idx < account_payment_proposals.size(); ++norm_prop_idx)
|
|
{
|
|
// calculate acceptable loss from fee subtraction
|
|
const CarrotPaymentProposalV1 &account_payment_proposal = account_payment_proposals.at(norm_prop_idx).first;
|
|
const bool is_subtractable = subtractable_normal_payment_proposals.count(norm_prop_idx);
|
|
const rct::xmr_amount acceptable_fee_margin_for_proposal = is_subtractable ? acceptable_fee_margin : 0;
|
|
|
|
// if the scan result matches the payment proposal...
|
|
if (compare_scan_result(single_scan_res, account_payment_proposal, acceptable_fee_margin_for_proposal))
|
|
{
|
|
// try opening Ko
|
|
const CarrotEnoteV1 &enote = parsed_enotes.at(single_scan_res.output_index);
|
|
EXPECT_TRUE(accounts.at(account_idx)->can_open_fcmp_onetime_address(single_scan_res.address_spend_pubkey,
|
|
single_scan_res.sender_extension_g,
|
|
single_scan_res.sender_extension_t,
|
|
enote.onetime_address));
|
|
|
|
// if this payment proposal isn't already marked as scanned, mark as scanned
|
|
if (!matched_payment_proposals.count(norm_prop_idx))
|
|
{
|
|
matched_payment_proposals.insert(norm_prop_idx);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// check that the number of matched payment proposals is equal to the original number of them
|
|
// doing it this way checks that the same payment proposal isn't marked twice and another left out
|
|
EXPECT_EQ(account_payment_proposals.size(), matched_payment_proposals.size());
|
|
}
|
|
|
|
// check that the scan results for the selfsend account match the corresponding payment
|
|
// proposals. also check that the accounts can each open their corresponding onetime outut pubkeys
|
|
const std::vector<mock::mock_scan_result_t> &account_scan_results = scan_results.at(tx_preproposal.self_sender_index);
|
|
ASSERT_EQ(selfsend_payment_proposals.size() + 1, account_scan_results.size());
|
|
std::set<size_t> matched_payment_proposals;
|
|
const mock::mock_scan_result_t* implicit_change_scan_res = nullptr;
|
|
// for each scan result assigned to the self-sender account...
|
|
for (const mock::mock_scan_result_t &single_scan_res : account_scan_results)
|
|
{
|
|
bool matched_payment = false;
|
|
// for each self-send payment proposal...
|
|
for (size_t ss_prop_idx = 0; ss_prop_idx < selfsend_payment_proposals.size(); ++ss_prop_idx)
|
|
{
|
|
// calculate acceptable loss from fee subtraction
|
|
const CarrotPaymentProposalSelfSendV1 &account_payment_proposal = verifiable_selfsend_to_core_v1(
|
|
selfsend_payment_proposals.at(ss_prop_idx));
|
|
const bool is_subtractable = subtractable_selfsend_payment_proposals.count(ss_prop_idx);
|
|
const rct::xmr_amount acceptable_fee_margin_for_proposal = is_subtractable ? acceptable_fee_margin : 0;
|
|
|
|
// if the scan result matches the payment proposal...
|
|
if (compare_scan_result(single_scan_res, account_payment_proposal, acceptable_fee_margin_for_proposal))
|
|
{
|
|
// try opening Ko
|
|
const CarrotEnoteV1 &enote = parsed_enotes.at(single_scan_res.output_index);
|
|
EXPECT_TRUE(ss_keys.can_open_fcmp_onetime_address(single_scan_res.address_spend_pubkey,
|
|
single_scan_res.sender_extension_g,
|
|
single_scan_res.sender_extension_t,
|
|
enote.onetime_address));
|
|
|
|
// if this payment proposal isn't already marked as scanned, mark as scanned
|
|
if (!matched_payment_proposals.count(ss_prop_idx))
|
|
{
|
|
matched_payment = true;
|
|
matched_payment_proposals.insert(ss_prop_idx);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// if this scan result has no matching payment...
|
|
if (!matched_payment)
|
|
{
|
|
EXPECT_EQ(nullptr, implicit_change_scan_res); // only one non-matched scan result is allowed
|
|
implicit_change_scan_res = &single_scan_res; // save the implicit change scan result for later
|
|
}
|
|
}
|
|
EXPECT_EQ(selfsend_payment_proposals.size(), matched_payment_proposals.size());
|
|
EXPECT_NE(nullptr, implicit_change_scan_res);
|
|
// @TODO: assert properties of `implicit_change_scan_res`
|
|
}
|
|
} //namespace
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
TEST(carrot_impl, multi_account_transfer_over_transaction_1)
|
|
{
|
|
// two accounts, both carrot
|
|
// 1/2 tx
|
|
// 1 normal payment to main address
|
|
// 0 explicit selfsend payments
|
|
|
|
unittest_transaction_preproposal tx_proposal;
|
|
tx_proposal.per_account_payments = std::vector<unittest_transaction_preproposal::per_account>(2);
|
|
auto &acc0 = tx_proposal.per_account_payments[0].first;
|
|
auto &acc1 = tx_proposal.per_account_payments[1].first;
|
|
acc0.generate();
|
|
acc1.generate();
|
|
|
|
// 1 normal payment
|
|
CarrotPaymentProposalV1 &normal_payment_proposal = tx_proposal.per_account_payments[0].second.emplace_back().first;
|
|
normal_payment_proposal = CarrotPaymentProposalV1{
|
|
.destination = acc0.cryptonote_address(),
|
|
.amount = crypto::rand_idx((rct::xmr_amount) 1ull << 63),
|
|
.randomness = gen_janus_anchor()
|
|
};
|
|
|
|
// specify self-sender
|
|
tx_proposal.self_sender_index = 1;
|
|
|
|
// specify fee per weight
|
|
tx_proposal.fee_per_weight = 20250510;
|
|
|
|
// test
|
|
subtest_multi_account_transfer_over_transaction(tx_proposal);
|
|
}
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
TEST(carrot_impl, multi_account_transfer_over_transaction_2)
|
|
{
|
|
// four accounts, all carrot
|
|
// 1/4 tx
|
|
// 1 normal payment to main address, integrated address, and subaddress each
|
|
// 0 explicit selfsend payments
|
|
|
|
unittest_transaction_preproposal tx_proposal;
|
|
tx_proposal.per_account_payments = std::vector<unittest_transaction_preproposal::per_account>(4);
|
|
auto &acc0 = tx_proposal.per_account_payments[0];
|
|
auto &acc1 = tx_proposal.per_account_payments[1];
|
|
auto &acc2 = tx_proposal.per_account_payments[2];
|
|
auto &acc3 = tx_proposal.per_account_payments[3];
|
|
acc0.first.generate();
|
|
acc1.first.generate();
|
|
acc2.first.generate();
|
|
acc3.first.generate();
|
|
|
|
// specify self-sender
|
|
tx_proposal.self_sender_index = 2;
|
|
|
|
// 1 subaddress payment
|
|
acc0.second.emplace_back().first = CarrotPaymentProposalV1{
|
|
.destination = acc0.first.subaddress({{2, 3}}),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
};
|
|
|
|
// 1 main address payment
|
|
acc1.second.emplace_back().first = CarrotPaymentProposalV1{
|
|
.destination = acc1.first.cryptonote_address(),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
};
|
|
|
|
// 1 integrated address payment
|
|
acc3.second.emplace_back().first = CarrotPaymentProposalV1{
|
|
.destination = acc3.first.cryptonote_address(gen_payment_id()),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
};
|
|
|
|
// specify fee per weight
|
|
tx_proposal.fee_per_weight = 314159;
|
|
|
|
// test
|
|
subtest_multi_account_transfer_over_transaction(tx_proposal);
|
|
}
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
TEST(carrot_impl, multi_account_transfer_over_transaction_3)
|
|
{
|
|
// four accounts, all carrot
|
|
// 1/6 tx
|
|
// 2 normal payment to main address, 1 integrated address, and 2 subaddress, each copied except integrated
|
|
// 0 explicit selfsend payments
|
|
|
|
unittest_transaction_preproposal tx_proposal;
|
|
tx_proposal.per_account_payments = std::vector<unittest_transaction_preproposal::per_account>(4);
|
|
auto &acc0 = tx_proposal.per_account_payments[0];
|
|
auto &acc1 = tx_proposal.per_account_payments[1];
|
|
auto &acc2 = tx_proposal.per_account_payments[2];
|
|
auto &acc3 = tx_proposal.per_account_payments[3];
|
|
acc0.first.generate();
|
|
acc1.first.generate();
|
|
acc2.first.generate();
|
|
acc3.first.generate();
|
|
|
|
// specify self-sender
|
|
tx_proposal.self_sender_index = 2;
|
|
|
|
// 2 subaddress payment
|
|
acc0.second.emplace_back().first = CarrotPaymentProposalV1{
|
|
.destination = acc0.first.subaddress({{2, 3}}),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
};
|
|
acc0.second.push_back(acc0.second.front());
|
|
acc0.second.back().first.randomness = gen_janus_anchor(); //mangle anchor_norm
|
|
|
|
// 2 main address payment
|
|
acc1.second.emplace_back().first = CarrotPaymentProposalV1{
|
|
.destination = acc1.first.cryptonote_address(),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
};
|
|
acc1.second.push_back(acc1.second.front());
|
|
acc1.second.back().first.randomness = gen_janus_anchor(); //mangle anchor_norm
|
|
|
|
// 1 integrated address payment
|
|
acc3.second.emplace_back().first = CarrotPaymentProposalV1{
|
|
.destination = acc3.first.cryptonote_address(gen_payment_id()),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
};
|
|
|
|
// specify fee per weight
|
|
tx_proposal.fee_per_weight = 314159;
|
|
|
|
// test
|
|
subtest_multi_account_transfer_over_transaction(tx_proposal);
|
|
}
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
TEST(carrot_impl, multi_account_transfer_over_transaction_4)
|
|
{
|
|
// four accounts, all carrot
|
|
// 1/8 tx
|
|
// 2 normal payment to main address, 1 integrated address, and 2 subaddress, each copied except integrated
|
|
// 2 explicit selfsend payments: 1 main address destination, 1 subaddress destination
|
|
|
|
unittest_transaction_preproposal tx_proposal;
|
|
tx_proposal.per_account_payments = std::vector<unittest_transaction_preproposal::per_account>(4);
|
|
auto &acc0 = tx_proposal.per_account_payments[0];
|
|
auto &acc1 = tx_proposal.per_account_payments[1];
|
|
auto &acc2 = tx_proposal.per_account_payments[2];
|
|
auto &acc3 = tx_proposal.per_account_payments[3];
|
|
acc0.first.generate();
|
|
acc1.first.generate();
|
|
acc2.first.generate();
|
|
acc3.first.generate();
|
|
|
|
// specify self-sender
|
|
tx_proposal.self_sender_index = 2;
|
|
|
|
// 2 subaddress payment
|
|
acc0.second.emplace_back().first = CarrotPaymentProposalV1{
|
|
.destination = acc0.first.subaddress({{2, 3}}),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
};
|
|
acc0.second.push_back(acc0.second.front());
|
|
acc0.second.back().first.randomness = gen_janus_anchor(); //mangle anchor_norm
|
|
|
|
// 2 main address payment
|
|
acc1.second.emplace_back().first = CarrotPaymentProposalV1{
|
|
.destination = acc1.first.cryptonote_address(),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
};
|
|
acc1.second.push_back(acc1.second.front());
|
|
acc1.second.back().first.randomness = gen_janus_anchor(); //mangle anchor_norm
|
|
|
|
// 1 integrated address payment
|
|
acc3.second.emplace_back().first = CarrotPaymentProposalV1{
|
|
.destination = acc3.first.cryptonote_address(gen_payment_id()),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
};
|
|
|
|
// 1 main address selfsend
|
|
tx_proposal.explicit_selfsend_proposals.emplace_back().first = CarrotPaymentProposalVerifiableSelfSendV1{
|
|
.destination_address_spend_pubkey = acc2.first.carrot_account_spend_pubkey,
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.enote_type = CarrotEnoteType::PAYMENT,
|
|
.internal_message = gen_janus_anchor()
|
|
};
|
|
|
|
// 1 subaddress selfsend
|
|
tx_proposal.explicit_selfsend_proposals.emplace_back().first = CarrotPaymentProposalVerifiableSelfSendV1{
|
|
.destination_address_spend_pubkey = acc2.first.subaddress({{4, 19}}).address_spend_pubkey,
|
|
.subaddr_index = {{4, 19}},
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.enote_type = CarrotEnoteType::CHANGE
|
|
};
|
|
|
|
// specify fee per weight
|
|
tx_proposal.fee_per_weight = 314159;
|
|
|
|
// test
|
|
subtest_multi_account_transfer_over_transaction(tx_proposal);
|
|
}
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
TEST(carrot_impl, multi_account_transfer_over_transaction_5)
|
|
{
|
|
// two accounts, both legacy
|
|
// 1/2 tx
|
|
// 1 normal payment to main address
|
|
// 0 explicit selfsend payments
|
|
|
|
unittest_transaction_preproposal tx_proposal;
|
|
tx_proposal.per_account_payments = std::vector<unittest_transaction_preproposal::per_account>(2);
|
|
auto &acc0 = tx_proposal.per_account_payments[0].first;
|
|
auto &acc1 = tx_proposal.per_account_payments[1].first;
|
|
acc0.generate(AddressDeriveType::PreCarrot);
|
|
acc1.generate(AddressDeriveType::PreCarrot);
|
|
|
|
// 1 normal payment
|
|
CarrotPaymentProposalV1 &normal_payment_proposal = tx_proposal.per_account_payments[0].second.emplace_back().first;
|
|
normal_payment_proposal = CarrotPaymentProposalV1{
|
|
.destination = acc0.cryptonote_address(),
|
|
.amount = crypto::rand_idx((rct::xmr_amount) 1ull << 63),
|
|
.randomness = gen_janus_anchor()
|
|
};
|
|
|
|
// specify self-sender
|
|
tx_proposal.self_sender_index = 1;
|
|
|
|
// specify fee per weight
|
|
tx_proposal.fee_per_weight = 20250510;
|
|
|
|
// test
|
|
subtest_multi_account_transfer_over_transaction(tx_proposal);
|
|
}
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
TEST(carrot_impl, multi_account_transfer_over_transaction_6)
|
|
{
|
|
// four accounts, all legacy
|
|
// 1/4 tx
|
|
// 1 normal payment to main address, integrated address, and subaddress each
|
|
// 0 explicit selfsend payments
|
|
|
|
unittest_transaction_preproposal tx_proposal;
|
|
tx_proposal.per_account_payments = std::vector<unittest_transaction_preproposal::per_account>(4);
|
|
auto &acc0 = tx_proposal.per_account_payments[0];
|
|
auto &acc1 = tx_proposal.per_account_payments[1];
|
|
auto &acc2 = tx_proposal.per_account_payments[2];
|
|
auto &acc3 = tx_proposal.per_account_payments[3];
|
|
acc0.first.generate();
|
|
acc1.first.generate();
|
|
acc2.first.generate();
|
|
acc3.first.generate();
|
|
|
|
// specify self-sender
|
|
tx_proposal.self_sender_index = 2;
|
|
|
|
// 1 subaddress payment
|
|
acc0.second.emplace_back().first = CarrotPaymentProposalV1{
|
|
.destination = acc0.first.subaddress({{2, 3}}),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
};
|
|
|
|
// 1 main address payment
|
|
acc1.second.emplace_back().first = CarrotPaymentProposalV1{
|
|
.destination = acc1.first.cryptonote_address(),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
};
|
|
|
|
// 1 integrated address payment
|
|
acc3.second.emplace_back().first = CarrotPaymentProposalV1{
|
|
.destination = acc3.first.cryptonote_address(gen_payment_id()),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
};
|
|
|
|
// specify fee per weight
|
|
tx_proposal.fee_per_weight = 314159;
|
|
|
|
// test
|
|
subtest_multi_account_transfer_over_transaction(tx_proposal);
|
|
}
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
TEST(carrot_impl, multi_account_transfer_over_transaction_7)
|
|
{
|
|
// four accounts, all legacy
|
|
// 1/6 tx
|
|
// 2 normal payment to main address, 1 integrated address, and 2 subaddress, each copied except integrated
|
|
// 0 explicit selfsend payments
|
|
|
|
unittest_transaction_preproposal tx_proposal;
|
|
tx_proposal.per_account_payments = std::vector<unittest_transaction_preproposal::per_account>(4);
|
|
auto &acc0 = tx_proposal.per_account_payments[0];
|
|
auto &acc1 = tx_proposal.per_account_payments[1];
|
|
auto &acc2 = tx_proposal.per_account_payments[2];
|
|
auto &acc3 = tx_proposal.per_account_payments[3];
|
|
acc0.first.generate(AddressDeriveType::PreCarrot);
|
|
acc1.first.generate(AddressDeriveType::PreCarrot);
|
|
acc2.first.generate(AddressDeriveType::PreCarrot);
|
|
acc3.first.generate(AddressDeriveType::PreCarrot);
|
|
|
|
// specify self-sender
|
|
tx_proposal.self_sender_index = 2;
|
|
|
|
// 2 subaddress payment
|
|
acc0.second.emplace_back().first = CarrotPaymentProposalV1{
|
|
.destination = acc0.first.subaddress({{2, 3}}),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
};
|
|
acc0.second.push_back(acc0.second.front());
|
|
acc0.second.back().first.randomness = gen_janus_anchor(); //mangle anchor_norm
|
|
|
|
// 2 main address payment
|
|
acc1.second.emplace_back().first = CarrotPaymentProposalV1{
|
|
.destination = acc1.first.cryptonote_address(),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
};
|
|
acc1.second.push_back(acc1.second.front());
|
|
acc1.second.back().first.randomness = gen_janus_anchor(); //mangle anchor_norm
|
|
|
|
// 1 integrated address payment
|
|
acc3.second.emplace_back().first = CarrotPaymentProposalV1{
|
|
.destination = acc3.first.cryptonote_address(gen_payment_id()),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
};
|
|
|
|
// specify fee per weight
|
|
tx_proposal.fee_per_weight = 314159;
|
|
|
|
// test
|
|
subtest_multi_account_transfer_over_transaction(tx_proposal);
|
|
}
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
TEST(carrot_impl, multi_account_transfer_over_transaction_8)
|
|
{
|
|
// four accounts, all legacy
|
|
// 1/8 tx
|
|
// 2 normal payment to main address, 1 integrated address, and 2 subaddress, each copied except integrated
|
|
// 2 explicit selfsend payments: 1 main address destination, 1 subaddress destination
|
|
|
|
unittest_transaction_preproposal tx_proposal;
|
|
tx_proposal.per_account_payments = std::vector<unittest_transaction_preproposal::per_account>(4);
|
|
auto &acc0 = tx_proposal.per_account_payments[0];
|
|
auto &acc1 = tx_proposal.per_account_payments[1];
|
|
auto &acc2 = tx_proposal.per_account_payments[2];
|
|
auto &acc3 = tx_proposal.per_account_payments[3];
|
|
acc0.first.generate(AddressDeriveType::PreCarrot);
|
|
acc1.first.generate(AddressDeriveType::PreCarrot);
|
|
acc2.first.generate(AddressDeriveType::PreCarrot);
|
|
acc3.first.generate(AddressDeriveType::PreCarrot);
|
|
|
|
// specify self-sender
|
|
tx_proposal.self_sender_index = 2;
|
|
|
|
// 2 subaddress payment
|
|
acc0.second.emplace_back().first = CarrotPaymentProposalV1{
|
|
.destination = acc0.first.subaddress({{2, 3}}),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
};
|
|
acc0.second.push_back(acc0.second.front());
|
|
acc0.second.back().first.randomness = gen_janus_anchor(); //mangle anchor_norm
|
|
|
|
// 2 main address payment
|
|
acc1.second.emplace_back().first = CarrotPaymentProposalV1{
|
|
.destination = acc1.first.cryptonote_address(),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
};
|
|
acc1.second.push_back(acc1.second.front());
|
|
acc1.second.back().first.randomness = gen_janus_anchor(); //mangle anchor_norm
|
|
|
|
// 1 integrated address payment
|
|
acc3.second.emplace_back().first = CarrotPaymentProposalV1{
|
|
.destination = acc3.first.cryptonote_address(gen_payment_id()),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
};
|
|
|
|
// 1 main address selfsend
|
|
tx_proposal.explicit_selfsend_proposals.emplace_back().first = CarrotPaymentProposalVerifiableSelfSendV1{
|
|
.destination_address_spend_pubkey = acc2.first.carrot_account_spend_pubkey,
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.enote_type = CarrotEnoteType::PAYMENT,
|
|
// no internal messages for legacy self-sends
|
|
};
|
|
|
|
// 1 subaddress selfsend
|
|
tx_proposal.explicit_selfsend_proposals.emplace_back().first = CarrotPaymentProposalVerifiableSelfSendV1{
|
|
.destination_address_spend_pubkey = acc2.first.subaddress({{4, 19}}).address_spend_pubkey,
|
|
.subaddr_index = {{4, 19}},
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.enote_type = CarrotEnoteType::CHANGE
|
|
};
|
|
|
|
// specify fee per weight
|
|
tx_proposal.fee_per_weight = 314159;
|
|
|
|
// test
|
|
subtest_multi_account_transfer_over_transaction(tx_proposal);
|
|
}
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
TEST(carrot_impl, multi_account_transfer_over_transaction_9)
|
|
{
|
|
// two accounts, both carrot
|
|
// 1/2 tx
|
|
// 1 normal payment to main address
|
|
// 0 explicit selfsend payments
|
|
// subtractable
|
|
|
|
unittest_transaction_preproposal tx_proposal;
|
|
tx_proposal.per_account_payments = std::vector<unittest_transaction_preproposal::per_account>(2);
|
|
auto &acc0 = tx_proposal.per_account_payments[0].first;
|
|
auto &acc1 = tx_proposal.per_account_payments[1].first;
|
|
acc0.generate();
|
|
acc1.generate();
|
|
|
|
// 1 normal payment (subtractable)
|
|
CarrotPaymentProposalV1 &normal_payment_proposal = tx_proposal.per_account_payments[0].second.emplace_back().first;
|
|
normal_payment_proposal = CarrotPaymentProposalV1{
|
|
.destination = acc0.cryptonote_address(),
|
|
.amount = crypto::rand_idx((rct::xmr_amount) 1ull << 63),
|
|
.randomness = gen_janus_anchor()
|
|
};
|
|
tx_proposal.per_account_payments[0].second.back().second = true;
|
|
|
|
// specify self-sender
|
|
tx_proposal.self_sender_index = 1;
|
|
|
|
// specify fee per weight
|
|
tx_proposal.fee_per_weight = 20250510;
|
|
|
|
// test
|
|
subtest_multi_account_transfer_over_transaction(tx_proposal);
|
|
}
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
TEST(carrot_impl, multi_account_transfer_over_transaction_10)
|
|
{
|
|
// four accounts, all carrot
|
|
// 1/4 tx
|
|
// 1 normal payment to main address, integrated address, and subaddress each
|
|
// 0 explicit selfsend payments
|
|
// subaddress and integrated address are subtractable
|
|
|
|
unittest_transaction_preproposal tx_proposal;
|
|
tx_proposal.per_account_payments = std::vector<unittest_transaction_preproposal::per_account>(4);
|
|
auto &acc0 = tx_proposal.per_account_payments[0];
|
|
auto &acc1 = tx_proposal.per_account_payments[1];
|
|
auto &acc2 = tx_proposal.per_account_payments[2];
|
|
auto &acc3 = tx_proposal.per_account_payments[3];
|
|
acc0.first.generate();
|
|
acc1.first.generate();
|
|
acc2.first.generate();
|
|
acc3.first.generate();
|
|
|
|
// specify self-sender
|
|
tx_proposal.self_sender_index = 2;
|
|
|
|
// 1 subaddress payment (subtractable)
|
|
acc0.second.emplace_back() = {CarrotPaymentProposalV1{
|
|
.destination = acc0.first.subaddress({{2, 3}}),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
}, true};
|
|
|
|
// 1 main address payment
|
|
acc1.second.emplace_back().first = CarrotPaymentProposalV1{
|
|
.destination = acc1.first.cryptonote_address(),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
};
|
|
|
|
// 1 integrated address payment
|
|
acc3.second.emplace_back() = {CarrotPaymentProposalV1{
|
|
.destination = acc3.first.cryptonote_address(gen_payment_id()),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
}, true};
|
|
|
|
// specify fee per weight
|
|
tx_proposal.fee_per_weight = 314159;
|
|
|
|
// test
|
|
subtest_multi_account_transfer_over_transaction(tx_proposal);
|
|
}
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
TEST(carrot_impl, multi_account_transfer_over_transaction_11)
|
|
{
|
|
// four accounts, all carrot
|
|
// 1/6 tx
|
|
// 2 normal payment to main address, 1 integrated address, and 2 subaddress, each copied except integrated
|
|
// 0 explicit selfsend payments
|
|
// 1 main and 1 subaddress is subtractable
|
|
|
|
unittest_transaction_preproposal tx_proposal;
|
|
tx_proposal.per_account_payments = std::vector<unittest_transaction_preproposal::per_account>(4);
|
|
auto &acc0 = tx_proposal.per_account_payments[0];
|
|
auto &acc1 = tx_proposal.per_account_payments[1];
|
|
auto &acc2 = tx_proposal.per_account_payments[2];
|
|
auto &acc3 = tx_proposal.per_account_payments[3];
|
|
acc0.first.generate();
|
|
acc1.first.generate();
|
|
acc2.first.generate();
|
|
acc3.first.generate();
|
|
|
|
// specify self-sender
|
|
tx_proposal.self_sender_index = 2;
|
|
|
|
// 2 subaddress payment
|
|
acc0.second.emplace_back().first = CarrotPaymentProposalV1{
|
|
.destination = acc0.first.subaddress({{2, 3}}),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
};
|
|
acc0.second.push_back(acc0.second.front());
|
|
acc0.second.back().first.randomness = gen_janus_anchor(); //mangle anchor_norm
|
|
acc0.second.back().second = true; //set copy as subtractable
|
|
|
|
// 2 main address payment
|
|
acc1.second.emplace_back().first = CarrotPaymentProposalV1{
|
|
.destination = acc1.first.cryptonote_address(),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
};
|
|
acc1.second.push_back(acc1.second.front());
|
|
acc1.second.back().first.randomness = gen_janus_anchor(); //mangle anchor_norm
|
|
acc1.second.back().second = true; //set copy as subtractable
|
|
|
|
// 1 integrated address payment
|
|
acc3.second.emplace_back().first = CarrotPaymentProposalV1{
|
|
.destination = acc3.first.cryptonote_address(gen_payment_id()),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
};
|
|
|
|
// specify fee per weight
|
|
tx_proposal.fee_per_weight = 314159;
|
|
|
|
// test
|
|
subtest_multi_account_transfer_over_transaction(tx_proposal);
|
|
}
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
TEST(carrot_impl, multi_account_transfer_over_transaction_12)
|
|
{
|
|
// four accounts, all carrot
|
|
// 1/8 tx
|
|
// 2 normal payment to main address, 1 integrated address, and 2 subaddress, each copied except integrated
|
|
// 2 explicit selfsend payments: 1 main address destination, 1 subaddress destination
|
|
// 1 normal main address, 1 integrated, and 1 self-send subaddress is subtractable
|
|
|
|
unittest_transaction_preproposal tx_proposal;
|
|
tx_proposal.per_account_payments = std::vector<unittest_transaction_preproposal::per_account>(4);
|
|
auto &acc0 = tx_proposal.per_account_payments[0];
|
|
auto &acc1 = tx_proposal.per_account_payments[1];
|
|
auto &acc2 = tx_proposal.per_account_payments[2];
|
|
auto &acc3 = tx_proposal.per_account_payments[3];
|
|
acc0.first.generate();
|
|
acc1.first.generate();
|
|
acc2.first.generate();
|
|
acc3.first.generate();
|
|
|
|
// specify self-sender
|
|
tx_proposal.self_sender_index = 2;
|
|
|
|
// 2 subaddress payment (1 subtractable)
|
|
acc0.second.emplace_back() = {CarrotPaymentProposalV1{
|
|
.destination = acc0.first.subaddress({{2, 3}}),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
}, true};
|
|
acc0.second.push_back(acc0.second.front());
|
|
acc0.second.back().first.randomness = gen_janus_anchor(); //mangle anchor_norm
|
|
acc0.second.back().second = false; //set not subtractable, first already is
|
|
|
|
// 2 main address payment
|
|
acc1.second.emplace_back().first = CarrotPaymentProposalV1{
|
|
.destination = acc1.first.cryptonote_address(),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
};
|
|
acc1.second.push_back(acc1.second.front());
|
|
acc1.second.back().first.randomness = gen_janus_anchor(); //mangle anchor_norm
|
|
|
|
// 1 integrated address payment (subtractable)
|
|
acc3.second.emplace_back() = {CarrotPaymentProposalV1{
|
|
.destination = acc3.first.cryptonote_address(gen_payment_id()),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
}, true};
|
|
|
|
// 1 main address selfsend
|
|
tx_proposal.explicit_selfsend_proposals.emplace_back().first = CarrotPaymentProposalVerifiableSelfSendV1{
|
|
.destination_address_spend_pubkey = acc2.first.carrot_account_spend_pubkey,
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.enote_type = CarrotEnoteType::PAYMENT,
|
|
.internal_message = gen_janus_anchor()
|
|
};
|
|
|
|
// 1 subaddress selfsend (subtractable)
|
|
tx_proposal.explicit_selfsend_proposals.emplace_back() = {CarrotPaymentProposalVerifiableSelfSendV1{
|
|
.destination_address_spend_pubkey = acc2.first.subaddress({{4, 19}}).address_spend_pubkey,
|
|
.subaddr_index = {{4, 19}},
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.enote_type = CarrotEnoteType::CHANGE
|
|
}, true};
|
|
|
|
// specify fee per weight
|
|
tx_proposal.fee_per_weight = 314159;
|
|
|
|
// test
|
|
subtest_multi_account_transfer_over_transaction(tx_proposal);
|
|
}
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
TEST(carrot_impl, multi_account_transfer_over_transaction_13)
|
|
{
|
|
// two accounts, both legacy
|
|
// 1/2 tx
|
|
// 1 normal payment to main address
|
|
// 0 explicit selfsend payments
|
|
// subtractable
|
|
|
|
unittest_transaction_preproposal tx_proposal;
|
|
tx_proposal.per_account_payments = std::vector<unittest_transaction_preproposal::per_account>(2);
|
|
auto &acc0 = tx_proposal.per_account_payments[0].first;
|
|
auto &acc1 = tx_proposal.per_account_payments[1].first;
|
|
acc0.generate(AddressDeriveType::PreCarrot);
|
|
acc1.generate(AddressDeriveType::PreCarrot);
|
|
|
|
// 1 normal payment (subtractable)
|
|
CarrotPaymentProposalV1 &normal_payment_proposal = tx_proposal.per_account_payments[0].second.emplace_back().first;
|
|
normal_payment_proposal = CarrotPaymentProposalV1{
|
|
.destination = acc0.cryptonote_address(),
|
|
.amount = crypto::rand_idx((rct::xmr_amount) 1ull << 63),
|
|
.randomness = gen_janus_anchor()
|
|
};
|
|
tx_proposal.per_account_payments[0].second.back().second = true;
|
|
|
|
// specify self-sender
|
|
tx_proposal.self_sender_index = 1;
|
|
|
|
// specify fee per weight
|
|
tx_proposal.fee_per_weight = 20250510;
|
|
|
|
// test
|
|
subtest_multi_account_transfer_over_transaction(tx_proposal);
|
|
}
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
TEST(carrot_impl, multi_account_transfer_over_transaction_14)
|
|
{
|
|
// four accounts, all legacy
|
|
// 1/4 tx
|
|
// 1 normal payment to main address, integrated address, and subaddress each
|
|
// 0 explicit selfsend payments
|
|
// 1 integrated and 1 subaddress subtractable
|
|
|
|
unittest_transaction_preproposal tx_proposal;
|
|
tx_proposal.per_account_payments = std::vector<unittest_transaction_preproposal::per_account>(4);
|
|
auto &acc0 = tx_proposal.per_account_payments[0];
|
|
auto &acc1 = tx_proposal.per_account_payments[1];
|
|
auto &acc2 = tx_proposal.per_account_payments[2];
|
|
auto &acc3 = tx_proposal.per_account_payments[3];
|
|
acc0.first.generate(AddressDeriveType::PreCarrot);
|
|
acc1.first.generate(AddressDeriveType::PreCarrot);
|
|
acc2.first.generate(AddressDeriveType::PreCarrot);
|
|
acc3.first.generate(AddressDeriveType::PreCarrot);
|
|
|
|
// specify self-sender
|
|
tx_proposal.self_sender_index = 2;
|
|
|
|
// 1 subaddress payment (subtractable)
|
|
acc0.second.emplace_back() = {CarrotPaymentProposalV1{
|
|
.destination = acc0.first.subaddress({{2, 3}}),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
}, true};
|
|
|
|
// 1 main address payment
|
|
acc1.second.emplace_back().first = CarrotPaymentProposalV1{
|
|
.destination = acc1.first.cryptonote_address(),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
};
|
|
|
|
// 1 integrated address payment (subtractable)
|
|
acc3.second.emplace_back() = {CarrotPaymentProposalV1{
|
|
.destination = acc3.first.cryptonote_address(gen_payment_id()),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
}, true};
|
|
|
|
// specify fee per weight
|
|
tx_proposal.fee_per_weight = 314159;
|
|
|
|
// test
|
|
subtest_multi_account_transfer_over_transaction(tx_proposal);
|
|
}
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
TEST(carrot_impl, multi_account_transfer_over_transaction_15)
|
|
{
|
|
// four accounts, all legacy
|
|
// 1/6 tx
|
|
// 2 normal payment to main address, 1 integrated address, and 2 subaddress, each copied except integrated
|
|
// 0 explicit selfsend payments
|
|
// all subtractable
|
|
|
|
unittest_transaction_preproposal tx_proposal;
|
|
tx_proposal.per_account_payments = std::vector<unittest_transaction_preproposal::per_account>(4);
|
|
auto &acc0 = tx_proposal.per_account_payments[0];
|
|
auto &acc1 = tx_proposal.per_account_payments[1];
|
|
auto &acc2 = tx_proposal.per_account_payments[2];
|
|
auto &acc3 = tx_proposal.per_account_payments[3];
|
|
acc0.first.generate(AddressDeriveType::PreCarrot);
|
|
acc1.first.generate(AddressDeriveType::PreCarrot);
|
|
acc2.first.generate(AddressDeriveType::PreCarrot);
|
|
acc3.first.generate(AddressDeriveType::PreCarrot);
|
|
|
|
// specify self-sender
|
|
tx_proposal.self_sender_index = 2;
|
|
|
|
// 2 subaddress payment (subtractable)
|
|
acc0.second.emplace_back() = {CarrotPaymentProposalV1{
|
|
.destination = acc0.first.subaddress({{2, 3}}),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
}, true};
|
|
acc0.second.push_back(acc0.second.front());
|
|
acc0.second.back().first.randomness = gen_janus_anchor(); //mangle anchor_norm
|
|
|
|
// 2 main address payment (subtractable)
|
|
acc1.second.emplace_back() = {CarrotPaymentProposalV1{
|
|
.destination = acc1.first.cryptonote_address(),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
}, true};
|
|
acc1.second.push_back(acc1.second.front());
|
|
acc1.second.back().first.randomness = gen_janus_anchor(); //mangle anchor_norm
|
|
|
|
// 1 integrated address payment (subtractable)
|
|
acc3.second.emplace_back() = {CarrotPaymentProposalV1{
|
|
.destination = acc3.first.cryptonote_address(gen_payment_id()),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
}, true};
|
|
|
|
// specify fee per weight
|
|
tx_proposal.fee_per_weight = 314159;
|
|
|
|
// test
|
|
subtest_multi_account_transfer_over_transaction(tx_proposal);
|
|
}
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
TEST(carrot_impl, multi_account_transfer_over_transaction_16)
|
|
{
|
|
// four accounts, all legacy
|
|
// 1/8 tx
|
|
// 2 normal payment to main address, 1 integrated address, and 2 subaddress, each copied except integrated
|
|
// 2 explicit selfsend payments: 1 main address destination, 1 subaddress destination
|
|
// all subtractable
|
|
|
|
unittest_transaction_preproposal tx_proposal;
|
|
tx_proposal.per_account_payments = std::vector<unittest_transaction_preproposal::per_account>(4);
|
|
auto &acc0 = tx_proposal.per_account_payments[0];
|
|
auto &acc1 = tx_proposal.per_account_payments[1];
|
|
auto &acc2 = tx_proposal.per_account_payments[2];
|
|
auto &acc3 = tx_proposal.per_account_payments[3];
|
|
acc0.first.generate(AddressDeriveType::PreCarrot);
|
|
acc1.first.generate(AddressDeriveType::PreCarrot);
|
|
acc2.first.generate(AddressDeriveType::PreCarrot);
|
|
acc3.first.generate(AddressDeriveType::PreCarrot);
|
|
|
|
// specify self-sender
|
|
tx_proposal.self_sender_index = 2;
|
|
|
|
// 2 subaddress payment (subtractable)
|
|
acc0.second.emplace_back() = {CarrotPaymentProposalV1{
|
|
.destination = acc0.first.subaddress({{2, 3}}),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
}, true};
|
|
acc0.second.push_back(acc0.second.front());
|
|
acc0.second.back().first.randomness = gen_janus_anchor(); //mangle anchor_norm
|
|
|
|
// 2 main address payment (subtractable)
|
|
acc1.second.emplace_back() = {CarrotPaymentProposalV1{
|
|
.destination = acc1.first.cryptonote_address(),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
}, true};
|
|
acc1.second.push_back(acc1.second.front());
|
|
acc1.second.back().first.randomness = gen_janus_anchor(); //mangle anchor_norm
|
|
|
|
// 1 integrated address payment (subtractable)
|
|
acc3.second.emplace_back() = {CarrotPaymentProposalV1{
|
|
.destination = acc3.first.cryptonote_address(gen_payment_id()),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
}, true};
|
|
|
|
// 1 main address selfsend (subtractable)
|
|
tx_proposal.explicit_selfsend_proposals.emplace_back() = {{CarrotPaymentProposalVerifiableSelfSendV1{
|
|
.destination_address_spend_pubkey = acc2.first.carrot_account_spend_pubkey,
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.enote_type = CarrotEnoteType::PAYMENT,
|
|
// no internal messages for legacy self-sends
|
|
}}, true};
|
|
|
|
// 1 subaddress selfsend (subtractable)
|
|
tx_proposal.explicit_selfsend_proposals.emplace_back() = {CarrotPaymentProposalVerifiableSelfSendV1{
|
|
.destination_address_spend_pubkey = acc2.first.subaddress({{4, 19}}).address_spend_pubkey,
|
|
.subaddr_index = {{4, 19}},
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.enote_type = CarrotEnoteType::CHANGE
|
|
}, true};
|
|
|
|
// specify fee per weight
|
|
tx_proposal.fee_per_weight = 314159;
|
|
|
|
// test
|
|
subtest_multi_account_transfer_over_transaction(tx_proposal);
|
|
}
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
TEST(carrot_impl, get_input_count_for_max_usable_money_1)
|
|
{
|
|
// Input selection should only select amounts > 10 (the marginal fee increase for including an input)
|
|
const std::vector<rct::xmr_amount> amounts{17, 7, 11, 8, 9};
|
|
|
|
const std::map<std::size_t, rct::xmr_amount> fee_by_input_count{
|
|
{1, 10},
|
|
{2, 20},
|
|
{3, 30},
|
|
{4, 40},
|
|
{5, 50},
|
|
{6, 60},
|
|
{7, 70},
|
|
{8, 80}
|
|
};
|
|
|
|
const auto input_count_for_max_usable_money = carrot::get_input_count_for_max_usable_money(
|
|
amounts.cbegin(),
|
|
amounts.cend(),
|
|
fee_by_input_count.size(),
|
|
fee_by_input_count);
|
|
|
|
const std::pair<std::size_t, boost::multiprecision::uint128_t> expected_input_count_for_max_usable_money{
|
|
2, 17 + 11
|
|
};
|
|
|
|
EXPECT_EQ(expected_input_count_for_max_usable_money, input_count_for_max_usable_money);
|
|
}
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
TEST(carrot_impl, make_single_transfer_input_selector_not_enough_money_1)
|
|
{
|
|
// no input candidates, should throw `not_enough_money`
|
|
|
|
const std::vector<InputCandidate> input_candidates = {
|
|
};
|
|
|
|
const std::vector<input_selection_policy_t> policies = { input_selection_policy_t{} };
|
|
|
|
const uint32_t flags = 0;
|
|
|
|
std::set<size_t> selected_input_indices;
|
|
select_inputs_func_t input_selector = make_single_transfer_input_selector(epee::to_span(input_candidates),
|
|
epee::to_span(policies),
|
|
flags,
|
|
0,
|
|
&selected_input_indices);
|
|
|
|
const boost::multiprecision::uint128_t nominal_output_sum = 369;
|
|
|
|
const std::map<size_t, rct::xmr_amount> fee_by_input_count = {
|
|
{1, 50},
|
|
{2, 75}
|
|
};
|
|
|
|
const size_t num_normal_payment_proposals = 1;
|
|
const size_t num_selfsend_payment_proposals = 1;
|
|
|
|
std::vector<CarrotSelectedInput> selected_inputs;
|
|
EXPECT_THROW(input_selector(nominal_output_sum,
|
|
fee_by_input_count,
|
|
num_normal_payment_proposals,
|
|
num_selfsend_payment_proposals,
|
|
selected_inputs),
|
|
not_enough_money);
|
|
}
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
TEST(carrot_impl, make_single_transfer_input_selector_not_enough_money_2)
|
|
{
|
|
// 1 input candidates w/ strictly less than nominal output sum, should throw `not_enough_money`
|
|
|
|
const std::vector<InputCandidate> input_candidates = {
|
|
InputCandidate {
|
|
.core = CarrotSelectedInput {
|
|
.amount = 222,
|
|
.input = gen_output_opening_hint(),
|
|
},
|
|
.is_external = false,
|
|
.block_index = 23
|
|
},
|
|
};
|
|
|
|
const std::vector<input_selection_policy_t> policies = { input_selection_policy_t{} };
|
|
|
|
const uint32_t flags = 0;
|
|
|
|
std::set<size_t> selected_input_indices;
|
|
select_inputs_func_t input_selector = make_single_transfer_input_selector(epee::to_span(input_candidates),
|
|
epee::to_span(policies),
|
|
flags,
|
|
0,
|
|
&selected_input_indices);
|
|
|
|
const boost::multiprecision::uint128_t nominal_output_sum = 369;
|
|
|
|
const std::map<size_t, rct::xmr_amount> fee_by_input_count = {
|
|
{1, 50},
|
|
{2, 75}
|
|
};
|
|
|
|
const size_t num_normal_payment_proposals = 1;
|
|
const size_t num_selfsend_payment_proposals = 1;
|
|
|
|
std::vector<CarrotSelectedInput> selected_inputs;
|
|
EXPECT_THROW(input_selector(nominal_output_sum,
|
|
fee_by_input_count,
|
|
num_normal_payment_proposals,
|
|
num_selfsend_payment_proposals,
|
|
selected_inputs),
|
|
not_enough_money);
|
|
}
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
TEST(carrot_impl, make_single_transfer_input_selector_not_enough_money_3)
|
|
{
|
|
// 2 input candidates who sum is strictly greater than the required money for a 1-in tx, but
|
|
// strictly less than the required money for a 2-out tx, should throw `not_enough_usable_money`
|
|
|
|
const rct::xmr_amount nominal_output_sum = 369;
|
|
|
|
const std::map<size_t, rct::xmr_amount> fee_by_input_count = {
|
|
{1, 50},
|
|
{2, 75}
|
|
};
|
|
|
|
const rct::xmr_amount required_1in = fee_by_input_count.at(1) + nominal_output_sum;
|
|
const rct::xmr_amount required_2in = fee_by_input_count.at(2) + nominal_output_sum;
|
|
ASSERT_GT(required_1in, 0);
|
|
ASSERT_GT(required_2in, required_1in + 1);
|
|
|
|
const rct::xmr_amount input_sum_target = (required_2in + required_1in) / 2;
|
|
const rct::xmr_amount min_inamount = input_sum_target - required_1in + 1;
|
|
ASSERT_LT(min_inamount, required_1in);
|
|
const rct::xmr_amount inamount_0 = min_inamount + rct::randXmrAmount(required_1in - min_inamount);
|
|
const rct::xmr_amount inamount_1 = input_sum_target - inamount_0;
|
|
|
|
const std::vector<InputCandidate> input_candidates = {
|
|
InputCandidate {
|
|
.core = CarrotSelectedInput {
|
|
.amount = inamount_0,
|
|
.input = gen_output_opening_hint(),
|
|
},
|
|
.is_external = false,
|
|
.block_index = 3407684
|
|
},
|
|
InputCandidate {
|
|
.core = CarrotSelectedInput {
|
|
.amount = inamount_1,
|
|
.input = gen_output_opening_hint(),
|
|
},
|
|
.is_external = false,
|
|
.block_index = 4867043
|
|
},
|
|
};
|
|
|
|
const std::vector<input_selection_policy_t> policies = { input_selection_policy_t{} };
|
|
|
|
const uint32_t flags = 0;
|
|
|
|
std::set<size_t> selected_input_indices;
|
|
select_inputs_func_t input_selector = make_single_transfer_input_selector(epee::to_span(input_candidates),
|
|
epee::to_span(policies),
|
|
flags,
|
|
0,
|
|
&selected_input_indices);
|
|
|
|
const size_t num_normal_payment_proposals = 1;
|
|
const size_t num_selfsend_payment_proposals = 1;
|
|
|
|
std::vector<CarrotSelectedInput> selected_inputs;
|
|
EXPECT_THROW(input_selector(nominal_output_sum,
|
|
fee_by_input_count,
|
|
num_normal_payment_proposals,
|
|
num_selfsend_payment_proposals,
|
|
selected_inputs),
|
|
not_enough_usable_money);
|
|
}
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
TEST(carrot_impl, make_single_transfer_input_selector_greedy_aging_1)
|
|
{
|
|
const std::vector<InputCandidate> input_candidates = {
|
|
InputCandidate {
|
|
.core = CarrotSelectedInput {
|
|
.amount = 500,
|
|
.input = gen_output_opening_hint(),
|
|
},
|
|
.is_external = false,
|
|
.block_index = 72
|
|
},
|
|
InputCandidate {
|
|
.core = CarrotSelectedInput {
|
|
.amount = 200,
|
|
.input = gen_output_opening_hint(),
|
|
},
|
|
.is_external = false,
|
|
.block_index = 34
|
|
}
|
|
};
|
|
|
|
const std::vector<input_selection_policy_t> policies = { &carrot::ispolicy::select_greedy_aging };
|
|
|
|
const uint32_t flags = 0;
|
|
|
|
std::set<size_t> selected_input_indices;
|
|
select_inputs_func_t input_selector = make_single_transfer_input_selector(epee::to_span(input_candidates),
|
|
epee::to_span(policies),
|
|
flags,
|
|
0,
|
|
&selected_input_indices);
|
|
|
|
const boost::multiprecision::uint128_t nominal_output_sum = 369;
|
|
|
|
const std::map<size_t, rct::xmr_amount> fee_by_input_count = {
|
|
{1, 50},
|
|
{2, 75}
|
|
};
|
|
|
|
const size_t num_normal_payment_proposals = 1;
|
|
const size_t num_selfsend_payment_proposals = 1;
|
|
|
|
ASSERT_GT(input_candidates[0].core.amount, nominal_output_sum + fee_by_input_count.crbegin()->second);
|
|
|
|
std::vector<CarrotSelectedInput> selected_inputs;
|
|
input_selector(nominal_output_sum,
|
|
fee_by_input_count,
|
|
num_normal_payment_proposals,
|
|
num_selfsend_payment_proposals,
|
|
selected_inputs);
|
|
|
|
// sometimes we choose 2 for default, sometimes 1 for default
|
|
ASSERT_EQ(2, input_candidates.size());
|
|
ASSERT_EQ(selected_inputs.size(), selected_input_indices.size());
|
|
if (selected_inputs.size() == 2)
|
|
{
|
|
EXPECT_NE(input_candidates.at(0).core, input_candidates.at(1).core);
|
|
EXPECT_NE(selected_inputs.at(0), selected_inputs.at(1));
|
|
EXPECT_TRUE((selected_inputs.at(0) == input_candidates.at(0).core) ^ (selected_inputs.at(0) == input_candidates[1].core));
|
|
EXPECT_TRUE((selected_inputs.at(1) == input_candidates.at(0).core) ^ (selected_inputs.at(1) == input_candidates.at(1).core));
|
|
}
|
|
else
|
|
{
|
|
ASSERT_EQ(std::set<size_t>{0}, selected_input_indices);
|
|
ASSERT_EQ(input_candidates.at(0).core, selected_inputs.at(0));
|
|
}
|
|
}
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
TEST(carrot_impl, make_single_transfer_input_selector_greedy_aging_2)
|
|
{
|
|
const std::vector<InputCandidate> input_candidates = {
|
|
InputCandidate {
|
|
.core = CarrotSelectedInput {
|
|
.amount = 500,
|
|
.input = gen_output_opening_hint(),
|
|
},
|
|
.is_external = false,
|
|
.block_index = 72
|
|
}
|
|
};
|
|
|
|
const std::vector<input_selection_policy_t> policies = { &carrot::ispolicy::select_greedy_aging };
|
|
|
|
const uint32_t flags = 0;
|
|
|
|
std::set<size_t> selected_input_indices;
|
|
select_inputs_func_t input_selector = make_single_transfer_input_selector(epee::to_span(input_candidates),
|
|
epee::to_span(policies),
|
|
flags,
|
|
0,
|
|
&selected_input_indices);
|
|
|
|
const boost::multiprecision::uint128_t nominal_output_sum = 369;
|
|
|
|
const std::map<size_t, rct::xmr_amount> fee_by_input_count = {
|
|
{1, 50},
|
|
{2, 75}
|
|
};
|
|
|
|
const size_t num_normal_payment_proposals = 1;
|
|
const size_t num_selfsend_payment_proposals = 1;
|
|
|
|
ASSERT_GT(input_candidates[0].core.amount, nominal_output_sum + fee_by_input_count.crbegin()->second);
|
|
|
|
std::vector<CarrotSelectedInput> selected_inputs;
|
|
input_selector(nominal_output_sum,
|
|
fee_by_input_count,
|
|
num_normal_payment_proposals,
|
|
num_selfsend_payment_proposals,
|
|
selected_inputs);
|
|
|
|
ASSERT_EQ(1, input_candidates.size());
|
|
ASSERT_EQ(std::set<size_t>{0}, selected_input_indices);
|
|
ASSERT_EQ(std::vector<CarrotSelectedInput>{input_candidates.at(0).core}, selected_inputs);
|
|
}
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
TEST(carrot_impl, make_single_transfer_input_selector_greedy_aging_3)
|
|
{
|
|
// Give 5 input candidates, only 3 are needed to fill order. 4 should be used since it's the next power of 2
|
|
// They should be the four oldest
|
|
|
|
const std::vector<InputCandidate> input_candidates = {
|
|
InputCandidate {
|
|
.core = CarrotSelectedInput {
|
|
.amount = 100,
|
|
.input = gen_output_opening_hint(),
|
|
},
|
|
.is_pre_carrot = false,
|
|
.is_external = false,
|
|
.block_index = 55
|
|
},
|
|
InputCandidate {
|
|
.core = CarrotSelectedInput {
|
|
.amount = 100,
|
|
.input = gen_output_opening_hint(),
|
|
},
|
|
.is_pre_carrot = false,
|
|
.is_external = false,
|
|
.block_index = 22
|
|
},
|
|
InputCandidate {
|
|
.core = CarrotSelectedInput {
|
|
.amount = 100,
|
|
.input = gen_output_opening_hint(),
|
|
},
|
|
.is_pre_carrot = false,
|
|
.is_external = false,
|
|
.block_index = 11
|
|
},
|
|
InputCandidate {
|
|
.core = CarrotSelectedInput {
|
|
.amount = 100,
|
|
.input = gen_output_opening_hint(),
|
|
},
|
|
.is_pre_carrot = false,
|
|
.is_external = false,
|
|
.block_index = 88
|
|
},
|
|
InputCandidate {
|
|
.core = CarrotSelectedInput {
|
|
.amount = 100,
|
|
.input = gen_output_opening_hint(),
|
|
},
|
|
.is_pre_carrot = false,
|
|
.is_external = false,
|
|
.block_index = 72
|
|
}
|
|
};
|
|
|
|
const std::vector<input_selection_policy_t> policies = { &carrot::ispolicy::select_greedy_aging };
|
|
|
|
const uint32_t flags = 0;
|
|
|
|
std::set<size_t> selected_input_indices;
|
|
select_inputs_func_t input_selector = make_single_transfer_input_selector(epee::to_span(input_candidates),
|
|
epee::to_span(policies),
|
|
flags,
|
|
0,
|
|
&selected_input_indices);
|
|
|
|
const boost::multiprecision::uint128_t nominal_output_sum = 223;
|
|
|
|
const std::map<size_t, rct::xmr_amount> fee_by_input_count = {
|
|
{1, 10},
|
|
{2, 20},
|
|
{3, 29},
|
|
{4, 37},
|
|
{5, 44},
|
|
{6, 50},
|
|
{7, 55},
|
|
{8, 59},
|
|
};
|
|
|
|
const size_t num_normal_payment_proposals = 1;
|
|
const size_t num_selfsend_payment_proposals = 1;
|
|
|
|
std::vector<CarrotSelectedInput> selected_inputs;
|
|
input_selector(nominal_output_sum,
|
|
fee_by_input_count,
|
|
num_normal_payment_proposals,
|
|
num_selfsend_payment_proposals,
|
|
selected_inputs);
|
|
|
|
ASSERT_EQ(4, selected_input_indices.size()); // discretization yay
|
|
ASSERT_EQ(4, selected_inputs.size());
|
|
std::set<crypto::public_key> selected_otas;
|
|
for (const CarrotSelectedInput &selected_input : selected_inputs)
|
|
selected_otas.insert(onetime_address_ref(selected_input.input));
|
|
ASSERT_EQ(4, selected_otas.size());
|
|
|
|
// this particular set of 4 indices is the indices of the 4 oldest inputs
|
|
ASSERT_EQ(std::set<size_t>({0, 1, 2, 4}), selected_input_indices);
|
|
}
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
TEST(carrot_impl, make_single_transfer_input_selector_greedy_aging_4)
|
|
{
|
|
// Give 10 input candidates, greater than 4, less than or equal to 7, are
|
|
// needed to fund the output amount. Transaction input count should be 8
|
|
// without limiting input count. After limiting input count to 7,
|
|
// transaction input count should be less than or equal to 7.
|
|
|
|
const std::vector<InputCandidate> input_candidates = {
|
|
InputCandidate {
|
|
.core = CarrotSelectedInput {
|
|
.amount = 100,
|
|
.input = gen_output_opening_hint(),
|
|
},
|
|
.is_pre_carrot = false,
|
|
.is_external = false,
|
|
.block_index = 55
|
|
},
|
|
InputCandidate {
|
|
.core = CarrotSelectedInput {
|
|
.amount = 100,
|
|
.input = gen_output_opening_hint(),
|
|
},
|
|
.is_pre_carrot = false,
|
|
.is_external = false,
|
|
.block_index = 22
|
|
},
|
|
InputCandidate {
|
|
.core = CarrotSelectedInput {
|
|
.amount = 100,
|
|
.input = gen_output_opening_hint(),
|
|
},
|
|
.is_pre_carrot = false,
|
|
.is_external = false,
|
|
.block_index = 11
|
|
},
|
|
InputCandidate {
|
|
.core = CarrotSelectedInput {
|
|
.amount = 100,
|
|
.input = gen_output_opening_hint(),
|
|
},
|
|
.is_pre_carrot = false,
|
|
.is_external = false,
|
|
.block_index = 88
|
|
},
|
|
InputCandidate {
|
|
.core = CarrotSelectedInput {
|
|
.amount = 100,
|
|
.input = gen_output_opening_hint(),
|
|
},
|
|
.is_pre_carrot = false,
|
|
.is_external = false,
|
|
.block_index = 72
|
|
},
|
|
InputCandidate {
|
|
.core = CarrotSelectedInput {
|
|
.amount = 100,
|
|
.input = gen_output_opening_hint(),
|
|
},
|
|
.is_pre_carrot = false,
|
|
.is_external = false,
|
|
.block_index = 52
|
|
},
|
|
InputCandidate {
|
|
.core = CarrotSelectedInput {
|
|
.amount = 100,
|
|
.input = gen_output_opening_hint(),
|
|
},
|
|
.is_pre_carrot = false,
|
|
.is_external = false,
|
|
.block_index = 39
|
|
},
|
|
InputCandidate {
|
|
.core = CarrotSelectedInput {
|
|
.amount = 100,
|
|
.input = gen_output_opening_hint(),
|
|
},
|
|
.is_pre_carrot = false,
|
|
.is_external = false,
|
|
.block_index = 12
|
|
},
|
|
InputCandidate {
|
|
.core = CarrotSelectedInput {
|
|
.amount = 100,
|
|
.input = gen_output_opening_hint(),
|
|
},
|
|
.is_pre_carrot = false,
|
|
.is_external = false,
|
|
.block_index = 71
|
|
},
|
|
InputCandidate {
|
|
.core = CarrotSelectedInput {
|
|
.amount = 100,
|
|
.input = gen_output_opening_hint(),
|
|
},
|
|
.is_pre_carrot = false,
|
|
.is_external = false,
|
|
.block_index = 85
|
|
}
|
|
};
|
|
|
|
const std::vector<input_selection_policy_t> policies = { &carrot::ispolicy::select_greedy_aging };
|
|
|
|
const uint32_t flags = 0;
|
|
|
|
std::set<size_t> selected_input_indices;
|
|
select_inputs_func_t input_selector = make_single_transfer_input_selector(epee::to_span(input_candidates),
|
|
epee::to_span(policies),
|
|
flags,
|
|
/*max_n_inputs=*/0,
|
|
&selected_input_indices);
|
|
|
|
const boost::multiprecision::uint128_t nominal_output_sum = 577;
|
|
|
|
const std::map<size_t, rct::xmr_amount> fee_by_input_count = {
|
|
{ 1, 10},
|
|
{ 2, 20},
|
|
{ 3, 29},
|
|
{ 4, 37},
|
|
{ 5, 44},
|
|
{ 6, 50},
|
|
{ 7, 55},
|
|
{ 8, 59},
|
|
{ 9, 62},
|
|
{10, 64}
|
|
};
|
|
|
|
const size_t num_normal_payment_proposals = 1;
|
|
const size_t num_selfsend_payment_proposals = 1;
|
|
|
|
std::vector<CarrotSelectedInput> selected_inputs;
|
|
input_selector(nominal_output_sum,
|
|
fee_by_input_count,
|
|
num_normal_payment_proposals,
|
|
num_selfsend_payment_proposals,
|
|
selected_inputs);
|
|
|
|
std::size_t expected_n_inputs = 8;
|
|
ASSERT_EQ(expected_n_inputs, selected_input_indices.size()); // discretization yay
|
|
ASSERT_EQ(expected_n_inputs, selected_inputs.size());
|
|
std::set<crypto::public_key> selected_otas;
|
|
for (const CarrotSelectedInput &selected_input : selected_inputs)
|
|
selected_otas.insert(onetime_address_ref(selected_input.input));
|
|
ASSERT_EQ(expected_n_inputs, selected_otas.size());
|
|
|
|
// this particular set of 8 indices is the indices of the 8 oldest inputs
|
|
ASSERT_EQ(std::set<size_t>({0, 1, 2, 4, 5, 6, 7, 8}), selected_input_indices);
|
|
|
|
//*********
|
|
// now limited to 7
|
|
|
|
static constexpr std::size_t max_n_inputs = 7;
|
|
static_assert(max_n_inputs <= FCMP_PLUS_PLUS_MAX_INPUTS);
|
|
|
|
input_selector = make_single_transfer_input_selector(epee::to_span(input_candidates),
|
|
epee::to_span(policies),
|
|
flags,
|
|
max_n_inputs,
|
|
&selected_input_indices);
|
|
|
|
selected_input_indices.clear();
|
|
selected_inputs.clear();
|
|
selected_otas.clear();
|
|
input_selector(nominal_output_sum,
|
|
fee_by_input_count,
|
|
num_normal_payment_proposals,
|
|
num_selfsend_payment_proposals,
|
|
selected_inputs);
|
|
|
|
expected_n_inputs = 7;
|
|
ASSERT_EQ(expected_n_inputs, selected_input_indices.size());
|
|
ASSERT_EQ(expected_n_inputs, selected_inputs.size());
|
|
for (const CarrotSelectedInput &selected_input : selected_inputs)
|
|
selected_otas.insert(onetime_address_ref(selected_input.input));
|
|
ASSERT_EQ(expected_n_inputs, selected_otas.size());
|
|
|
|
// this particular set of 7 indices is the indices of the 7 oldest inputs
|
|
ASSERT_EQ(std::set<size_t>({0, 1, 2, 5, 6, 7, 8}), selected_input_indices);
|
|
}
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
TEST(carrot_impl, make_multiple_carrot_transaction_proposals_sweep_1)
|
|
{
|
|
// Taken from real function test
|
|
const rct::xmr_amount input_enote_amounts[] = {
|
|
35183466129983, 35183399022847, 35183331915839, 35183197702207, 35183130595583, 35183063489087,
|
|
35182996382719, 35182929276479, 35182862170367, 35182795064383, 35182727958527, 35182660852799,
|
|
35182593747199, 35182526641727, 35182459536383, 35182392431167, 35182325326078, 35182258221118,
|
|
35182191116286, 35182124011582, 35182056907006, 35181989802558, 35181922698238, 35181855594046,
|
|
35181788489981, 35181721386045, 35181654282237, 35181587178557, 35181520075005, 35181452971580,
|
|
35181385868284, 35181318765116, 35181251662076, 35181184559163, 35181117456379, 35181050353723,
|
|
35180983251195, 35180916148794, 35180849046522, 35180781944378, 35180714842361, 35180647740473,
|
|
35180580638713, 35180513537080, 35180446435576, 35180379334199, 35180312232951, 35180245131830,
|
|
35180178030838, 35180110929974, 35180043829237, 35179976728629, 35179909628148, 35179842527796,
|
|
35179775427571, 35179708327474, 35179641227506, 35179574127665, 35179507027953, 35179439928368,
|
|
35179372828911, 35179305729583, 35179238630382, 35179171531309, 35179104432365, 35179037333548,
|
|
35178970234859, 35178903136298, 35178836037865, 35178768939561, 35178701841384, 35178634743335,
|
|
35178567645414, 35178500547621, 35178433449956, 35178366352419, 35178299255010, 35178232157729,
|
|
35178165060576, 35178097963551, 35178030866654, 35177963769885, 35177896673244, 35177829576731,
|
|
35177762480346, 35177695384089, 35192149487959, 1000000000000, 69354088760000, 35192082391958,
|
|
69353820325310, 35192984896085, 1000000000000, 67052582291646, 35177427000340, 35191881104722,
|
|
69353283459006, 35177292809233, 35177225713872, 35177158618638, 35177091523533,
|
|
};
|
|
static constexpr std::size_t n_input_enote_amounts = sizeof(input_enote_amounts) / sizeof(input_enote_amounts[0]);
|
|
|
|
std::vector<carrot::CarrotSelectedInput> selected_inputs;
|
|
selected_inputs.reserve(n_input_enote_amounts);
|
|
for (std::size_t i = 0; i < n_input_enote_amounts; ++i)
|
|
{
|
|
selected_inputs.push_back(carrot::CarrotSelectedInput{
|
|
.amount = input_enote_amounts[i],
|
|
.input = gen_output_opening_hint()
|
|
});
|
|
}
|
|
|
|
std::unordered_map<crypto::public_key, std::size_t> expected_otas;
|
|
for (std::size_t i = 0; i < n_input_enote_amounts; ++i)
|
|
{
|
|
const carrot::CarrotSelectedInput &selected_input = selected_inputs.at(i);
|
|
expected_otas.emplace(onetime_address_ref(selected_input.input), i);
|
|
}
|
|
|
|
const carrot::CarrotPaymentProposalV1 normal_payment_proposal{
|
|
.destination = carrot::gen_carrot_main_address_v1(),
|
|
.amount = 0,
|
|
.randomness = carrot::gen_janus_anchor()
|
|
};
|
|
|
|
for (int ignore_dust = 0; ignore_dust <= 1; ++ignore_dust)
|
|
{
|
|
MDEBUG(typeid(*this).name() << ": ignore_dust=" << ignore_dust);
|
|
|
|
std::vector<carrot::CarrotTransactionProposalV1> tx_proposals;
|
|
make_multiple_carrot_transaction_proposals_sweep({normal_payment_proposal},
|
|
/*selfsend_payment_proposals=*/{},
|
|
/*fee_per_weight=*/20000,
|
|
/*extra=*/{},
|
|
std::vector<carrot::CarrotSelectedInput>(selected_inputs),
|
|
/*change_address_spend_pubkey=*/carrot::mock::gen_public_key(),
|
|
/*change_address_index=*/{},
|
|
ignore_dust,
|
|
tx_proposals);
|
|
|
|
const std::size_t n_expected_tx_proposals = (n_input_enote_amounts + FCMP_PLUS_PLUS_MAX_INPUTS - 1)
|
|
/ FCMP_PLUS_PLUS_MAX_INPUTS;
|
|
ASSERT_EQ(n_expected_tx_proposals, tx_proposals.size());
|
|
|
|
std::unordered_set<crypto::public_key> seen_ota;
|
|
for (const carrot::CarrotTransactionProposalV1 &tx_proposal : tx_proposals)
|
|
{
|
|
ASSERT_EQ(1, tx_proposal.normal_payment_proposals.size());
|
|
ASSERT_EQ(1, tx_proposal.selfsend_payment_proposals.size());
|
|
ASSERT_TRUE(tx_proposal.extra.empty());
|
|
|
|
const carrot::CarrotPaymentProposalVerifiableSelfSendV1 &selfsend_payment_proposal
|
|
= tx_proposal.selfsend_payment_proposals.at(0);
|
|
ASSERT_EQ(0, selfsend_payment_proposal.amount);
|
|
ASSERT_EQ(0, selfsend_payment_proposal.subaddr_index.index.major);
|
|
ASSERT_EQ(0, selfsend_payment_proposal.subaddr_index.index.minor);
|
|
ASSERT_EQ(carrot::AddressDeriveType::Auto, selfsend_payment_proposal.subaddr_index.derive_type);
|
|
|
|
carrot::CarrotPaymentProposalV1 modified_normal_payment_proposal = normal_payment_proposal;
|
|
modified_normal_payment_proposal.amount = tx_proposal.normal_payment_proposals.at(0).amount;
|
|
ASSERT_EQ(modified_normal_payment_proposal, tx_proposal.normal_payment_proposals.at(0));
|
|
|
|
boost::multiprecision::uint128_t input_amount_sum = 0;
|
|
for (const carrot::InputProposalV1 &input_proposal : tx_proposal.input_proposals)
|
|
{
|
|
const crypto::public_key ota = onetime_address_ref(input_proposal);
|
|
ASSERT_TRUE(expected_otas.count(ota));
|
|
ASSERT_FALSE(seen_ota.count(ota));
|
|
seen_ota.insert(ota);
|
|
input_amount_sum += input_enote_amounts[expected_otas.at(ota)];
|
|
}
|
|
|
|
ASSERT_EQ(input_amount_sum, tx_proposal.normal_payment_proposals.at(0).amount + tx_proposal.fee);
|
|
}
|
|
EXPECT_EQ(n_input_enote_amounts, seen_ota.size());
|
|
}
|
|
}
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
TEST(carrot_impl, new_hierarchy_spend_device_ram_1in_2out)
|
|
{
|
|
// check alice uses a s_vb
|
|
ASSERT_TRUE(alice.default_derive_type == AddressDeriveType::Carrot);
|
|
|
|
// fund alice
|
|
CarrotCoinbaseEnoteV1 alice_enote;
|
|
get_coinbase_enote_v1(
|
|
CarrotPaymentProposalV1{
|
|
.destination = alice.cryptonote_address(null_payment_id, AddressDeriveType::Carrot),
|
|
.amount = rct::randXmrAmount(25000 + 2 * COIN),
|
|
.randomness = gen_janus_anchor()
|
|
},
|
|
mock::gen_block_index(),
|
|
alice_enote);
|
|
|
|
// make a tx proposal from Alice -> Bob
|
|
const xmr_amount fee = 25000;
|
|
const xmr_amount bob_amount = rct::randXmrAmount(alice_enote.amount - fee);
|
|
const CarrotTransactionProposalV1 tx_proposal{
|
|
.input_proposals = {CarrotCoinbaseOutputOpeningHintV1{
|
|
.source_enote = alice_enote,
|
|
.derive_type = AddressDeriveType::Carrot
|
|
}},
|
|
.normal_payment_proposals = {CarrotPaymentProposalV1{
|
|
.destination = bob.cryptonote_address(),
|
|
.amount = bob_amount,
|
|
.randomness = gen_janus_anchor()
|
|
}},
|
|
.selfsend_payment_proposals = {CarrotPaymentProposalVerifiableSelfSendV1{
|
|
.destination_address_spend_pubkey = alice.carrot_account_spend_pubkey,
|
|
.subaddr_index = {{0, 0}, AddressDeriveType::Carrot},
|
|
.amount = alice_enote.amount - fee - bob_amount,
|
|
.enote_type = CarrotEnoteType::CHANGE
|
|
}},
|
|
.dummy_encrypted_payment_id = gen_encrypted_payment_id(),
|
|
.fee = fee,
|
|
.extra = {}
|
|
};
|
|
|
|
// derive key images and output enotes
|
|
std::vector<crypto::key_image> sorted_tx_key_images;
|
|
get_sorted_input_key_images_from_proposal_v1(tx_proposal, *alice.key_image_dev, sorted_tx_key_images);
|
|
|
|
std::vector<RCTOutputEnoteProposal> output_enote_proposals;
|
|
encrypted_payment_id_t encrypted_payment_id;
|
|
std::vector<std::pair<bool, std::size_t>> payment_proposal_order;
|
|
get_output_enote_proposals_from_proposal_v1(tx_proposal,
|
|
&alice.s_view_balance_dev,
|
|
&alice.k_view_incoming_dev,
|
|
sorted_tx_key_images.at(0),
|
|
output_enote_proposals,
|
|
encrypted_payment_id,
|
|
&payment_proposal_order);
|
|
|
|
// test that change is internal-scannable
|
|
const CarrotEnoteV1 &change_enote = payment_proposal_order.at(0).first
|
|
? output_enote_proposals.at(0).enote : output_enote_proposals.at(1).enote;
|
|
crypto::secret_key k_g_o, k_t_o, k_a;
|
|
crypto::public_key K_j_s;
|
|
xmr_amount a;
|
|
CarrotEnoteType enote_type;
|
|
janus_anchor_t internal_message;
|
|
const bool change_scanned = try_scan_carrot_enote_internal_receiver(change_enote,
|
|
alice.s_view_balance_dev, k_g_o, k_t_o, K_j_s, a, k_a, enote_type, internal_message);
|
|
ASSERT_TRUE(change_scanned);
|
|
const CarrotPaymentProposalVerifiableSelfSendV1 &selfsend_proposal = tx_proposal.selfsend_payment_proposals.at(0);
|
|
ASSERT_EQ(selfsend_proposal.amount, a);
|
|
ASSERT_EQ(alice.carrot_account_spend_pubkey, K_j_s);
|
|
ASSERT_EQ(selfsend_proposal.enote_type, enote_type);
|
|
ASSERT_EQ(selfsend_proposal.internal_message.value_or(janus_anchor_t{}), internal_message);
|
|
|
|
// make signable tx hash
|
|
crypto::hash signable_tx_hash;
|
|
make_signable_tx_hash_from_proposal_v1(tx_proposal,
|
|
&alice.s_view_balance_dev,
|
|
&alice.k_view_incoming_dev,
|
|
sorted_tx_key_images,
|
|
signable_tx_hash);
|
|
|
|
// rerandomize input
|
|
const std::vector<FcmpRerandomizedOutputCompressed> rerandomized = generate_rerandomized_inputs_nonrefundable(
|
|
epee::to_span(output_enote_proposals),
|
|
epee::to_span(tx_proposal.input_proposals),
|
|
*alice.addr_dev,
|
|
&alice.s_view_balance_dev,
|
|
alice.k_view_incoming_dev);
|
|
|
|
// compose compound signing device
|
|
const spend_device_ram_borrowed spend_dev(
|
|
std::make_shared<view_incoming_key_ram_borrowed_device>(alice.legacy_acb.get_keys().m_view_secret_key),
|
|
std::make_shared<view_balance_secret_ram_borrowed_device>(alice.s_view_balance),
|
|
alice.addr_dev,
|
|
alice.k_generate_image,
|
|
alice.k_prove_spend);
|
|
|
|
// sign thru device
|
|
spend_device::signed_input_set_t signed_inputs;
|
|
crypto::hash signable_tx_hash_device;
|
|
const bool did_sign = spend_dev.try_sign_carrot_transaction_proposal_v1(tx_proposal,
|
|
{{alice_enote.onetime_address, rerandomized.at(0)}},
|
|
signable_tx_hash_device,
|
|
signed_inputs);
|
|
ASSERT_TRUE(did_sign);
|
|
|
|
// check nominal returned values (signable tx hash, OTA, KI)
|
|
ASSERT_EQ(signable_tx_hash, signable_tx_hash_device);
|
|
ASSERT_EQ(1, signed_inputs.size());
|
|
ASSERT_EQ(alice_enote.onetime_address, signed_inputs.begin()->second.first);
|
|
ASSERT_EQ(sorted_tx_key_images.at(0), signed_inputs.begin()->first);
|
|
|
|
// verify SA/L
|
|
const bool sal_verified = fcmp_pp::verify_sal(signable_tx_hash,
|
|
rerandomized.at(0).input,
|
|
sorted_tx_key_images.at(0),
|
|
signed_inputs.begin()->second.second);
|
|
EXPECT_TRUE(sal_verified);
|
|
}
|
|
//----------------------------------------------------------------------------------------------------------------------
|