Files
salvium/src/carrot_impl/tx_proposal_utils.cpp
T

519 lines
27 KiB
C++

// Copyright (c) 2024, 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.
//paired header
#include "tx_proposal_utils.h"
//local headers
#include "carrot_core/exceptions.h"
#include "carrot_core/output_set_finalization.h"
#include "cryptonote_basic/cryptonote_format_utils.h"
#include "misc_log_ex.h"
//third party headers
//standard headers
#undef MONERO_DEFAULT_LOG_CATEGORY
#define MONERO_DEFAULT_LOG_CATEGORY "carrot_impl.tpu"
namespace carrot
{
//-------------------------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------------------------
static void append_additional_payment_proposal_if_necessary(
std::vector<CarrotPaymentProposalV1>& normal_payment_proposals_inout,
std::vector<CarrotPaymentProposalVerifiableSelfSendV1> &selfsend_payment_proposals_inout,
const crypto::public_key &change_address_spend_pubkey,
const subaddress_index_extended &change_subaddr_index)
{
struct append_additional_payment_proposal_if_necessary_visitor
{
void operator()(boost::blank) const {}
void operator()(const CarrotPaymentProposalV1 &p) const { normal_proposals_inout.push_back(p); }
void operator()(const CarrotPaymentProposalSelfSendV1 &p) const
{
selfsend_proposals_inout.push_back(CarrotPaymentProposalVerifiableSelfSendV1{
.proposal = p,
.subaddr_index = change_subaddr_index
});
}
std::vector<CarrotPaymentProposalV1>& normal_proposals_inout;
std::vector<CarrotPaymentProposalVerifiableSelfSendV1> &selfsend_proposals_inout;
const subaddress_index_extended &change_subaddr_index;
};
bool have_payment_type_selfsend = false;
for (const CarrotPaymentProposalVerifiableSelfSendV1 &selfsend_payment_proposal : selfsend_payment_proposals_inout)
if (selfsend_payment_proposal.proposal.enote_type == CarrotEnoteType::PAYMENT)
have_payment_type_selfsend = true;
const auto additional_output_proposal = get_additional_output_proposal(normal_payment_proposals_inout.size(),
selfsend_payment_proposals_inout.size(),
/*needed_change_amount=*/0,
have_payment_type_selfsend,
change_address_spend_pubkey);
additional_output_proposal.visit(append_additional_payment_proposal_if_necessary_visitor{
normal_payment_proposals_inout,
selfsend_payment_proposals_inout,
change_subaddr_index
});
}
//-------------------------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------------------------
std::uint64_t get_carrot_default_tx_extra_size(const std::size_t n_outputs)
{
CHECK_AND_ASSERT_THROW_MES(n_outputs <= CARROT_MAX_TX_OUTPUTS,
"get_carrot_default_tx_extra_size: n_outputs too high: " << n_outputs);
CHECK_AND_ASSERT_THROW_MES(n_outputs >= CARROT_MIN_TX_OUTPUTS,
"get_carrot_default_tx_extra_size: n_outputs too low: " << n_outputs);
static constexpr std::uint64_t enc_pid_extra_field_size =
8 /*pid*/
+ 1 /*TX_EXTRA_NONCE_ENCRYPTED_PAYMENT_ID*/
+ 1 /*nonce.size()*/
+ 1 /*tx_extra_field variant tag*/;
static constexpr std::uint64_t x25519_pubkey_size = 32;
const std::size_t n_ephemeral = (n_outputs == 2) ? 1 : n_outputs;
const bool use_additional = n_ephemeral > 1;
const std::uint64_t ephemeral_pubkeys_field_size =
1 /*tx_extra_field variant tag*/
+ (use_additional ? 1 : 0) /*vector size if applicable*/
+ (n_ephemeral * x25519_pubkey_size) /*actual pubkeys*/;
return enc_pid_extra_field_size + ephemeral_pubkeys_field_size;
}
//-------------------------------------------------------------------------------------------------------------------
void make_carrot_transaction_proposal_v1(const std::vector<CarrotPaymentProposalV1> &normal_payment_proposals_in,
const std::vector<CarrotPaymentProposalVerifiableSelfSendV1> &selfsend_payment_proposals_in,
const rct::xmr_amount fee_per_weight,
const rct::xmr_amount fee_quantization_mask,
const std::vector<uint8_t> &extra,
const cryptonote::transaction_type tx_type,
select_inputs_func_t &&select_inputs,
carve_fees_and_balance_func_t &&carve_fees_and_balance,
const crypto::public_key &change_address_spend_pubkey,
const subaddress_index_extended &change_address_index,
CarrotTransactionProposalV1 &tx_proposal_out)
{
tx_proposal_out.extra = extra;
std::vector<CarrotPaymentProposalV1> &normal_payment_proposals
= tx_proposal_out.normal_payment_proposals
= normal_payment_proposals_in;
std::vector<CarrotPaymentProposalVerifiableSelfSendV1> &selfsend_payment_proposals
= tx_proposal_out.selfsend_payment_proposals
= selfsend_payment_proposals_in;
// add an additional payment proposal to satisfy scanning/consensus rules, if applicable
append_additional_payment_proposal_if_necessary(normal_payment_proposals,
selfsend_payment_proposals,
change_address_spend_pubkey,
change_address_index);
const size_t num_outs = normal_payment_proposals.size() + selfsend_payment_proposals.size();
CHECK_AND_ASSERT_THROW_MES(num_outs >= CARROT_MIN_TX_OUTPUTS,
"make_carrot_transaction_proposal_v1: too few outputs");
// generate random X25519 ephemeral pubkeys for selfsend proposals if:
// a. not explicitly provided in a >2-out tx, OR
// b. not explicitly provided in a 2-out 2-self-send tx and the other is also missing
const bool should_gen_selfsend_ephemeral_pubkeys = num_outs != 2 ||
(normal_payment_proposals.empty()
&& !selfsend_payment_proposals.at(0).proposal.enote_ephemeral_pubkey
&& !selfsend_payment_proposals.at(1).proposal.enote_ephemeral_pubkey);
if (should_gen_selfsend_ephemeral_pubkeys)
{
for (size_t i = 0; i < selfsend_payment_proposals.size(); ++i)
{
// should not provide two different D_e in a 2-out tx, so skip the second D_e in a 2-out
const bool should_skip_generating = num_outs == 2 && i == 1;
if (should_skip_generating)
continue;
CarrotPaymentProposalVerifiableSelfSendV1 &selfsend_payment_proposal = selfsend_payment_proposals[i];
if (!selfsend_payment_proposal.proposal.enote_ephemeral_pubkey)
selfsend_payment_proposal.proposal.enote_ephemeral_pubkey = gen_x25519_pubkey();
}
}
// generate random dummy encrypted payment ID for if none of the normal payment proposals are integrated
tx_proposal_out.dummy_encrypted_payment_id = gen_encrypted_payment_id();
// calculate the final size of tx.extra
const size_t tx_extra_size = get_carrot_default_tx_extra_size(num_outs) + extra.size();
// calculate the concrete fee for this transaction for each possible valid input count
std::map<size_t, rct::xmr_amount> fee_per_input_count;
for (size_t num_ins = CARROT_MIN_TX_INPUTS; num_ins <= CARROT_MAX_TX_INPUTS; ++num_ins)
{
const rct::xmr_amount fee = estimate_fee_carrot(num_ins, 15, num_outs, tx_extra_size, true, true, true, true, fee_per_weight, fee_quantization_mask);
fee_per_input_count.emplace(num_ins, fee);
}
// calculate sum of payment proposal amounts before fee carving
boost::multiprecision::uint128_t nominal_output_amount_sum = 0;
for (const CarrotPaymentProposalV1 &normal_proposal : normal_payment_proposals)
nominal_output_amount_sum += normal_proposal.amount;
for (const CarrotPaymentProposalVerifiableSelfSendV1 &selfsend_proposal : selfsend_payment_proposals)
nominal_output_amount_sum += selfsend_proposal.proposal.amount;
// callback to select inputs given nominal output sum and fee per input count
std::vector<CarrotSelectedInput> selected_inputs;
select_inputs(nominal_output_amount_sum,
fee_per_input_count,
normal_payment_proposals.size(),
selfsend_payment_proposals.size(),
selected_inputs);
// get fee given the number of selected inputs
const std::size_t n_inputs = selected_inputs.size();
CARROT_CHECK_AND_THROW(n_inputs >= CARROT_MIN_TX_INPUTS,
too_few_inputs, "input selection returned too few inputs: " << n_inputs);
CARROT_CHECK_AND_THROW(n_inputs <= CARROT_MAX_TX_INPUTS,
too_few_inputs, "input selection returned too many inputs: " << n_inputs);
CARROT_CHECK_AND_THROW(fee_per_input_count.count(n_inputs),
carrot_logic_error, "BUG: fee_per_input_count populated with holes, missing: " << n_inputs);
tx_proposal_out.fee = fee_per_input_count.at(n_inputs);
// calculate input amount sum
boost::multiprecision::uint128_t input_amount_sum = 0;
for (const CarrotSelectedInput &selected_input : selected_inputs)
input_amount_sum += selected_input.amount;
// callback to balance the outputs with the fee and input sum
carve_fees_and_balance(input_amount_sum, tx_proposal_out.fee, normal_payment_proposals, selfsend_payment_proposals);
// sanity check balance
input_amount_sum -= tx_proposal_out.fee;
for (const CarrotPaymentProposalV1 &normal_payment_proposal : normal_payment_proposals)
input_amount_sum -= normal_payment_proposal.amount;
for (const CarrotPaymentProposalVerifiableSelfSendV1 &selfsend_payment_proposal : selfsend_payment_proposals)
input_amount_sum -= selfsend_payment_proposal.proposal.amount;
if (tx_type != cryptonote::transaction_type::STAKE &&
tx_type != cryptonote::transaction_type::BURN)
{
CHECK_AND_ASSERT_THROW_MES(input_amount_sum == 0,
"make_carrot_transaction_proposal_v1: post-carved transaction does not balance");
} else {
tx_proposal_out.amount_burnt = input_amount_sum.convert_to<uint64_t>();
CHECK_AND_ASSERT_THROW_MES(tx_proposal_out.amount_burnt >= 0,
"make_carrot_transaction_proposal_v1: post-carved transaction burnt amount is negative: "
<< tx_proposal_out.amount_burnt);
input_amount_sum -= tx_proposal_out.amount_burnt;
}
// collect and sort key images
tx_proposal_out.key_images_sorted.reserve(selected_inputs.size());
for (const CarrotSelectedInput &selected_input : selected_inputs)
tx_proposal_out.key_images_sorted.push_back(selected_input.key_image);
std::sort(tx_proposal_out.key_images_sorted.begin(),
tx_proposal_out.key_images_sorted.end(),
std::greater{}); // consensus rules dictate inputs sorted in *reverse* lexicographical order since v7
// set the transaction type
tx_proposal_out.tx_type = tx_type;
}
//-------------------------------------------------------------------------------------------------------------------
void make_carrot_transaction_proposal_v1_transfer(
const std::vector<CarrotPaymentProposalV1> &normal_payment_proposals,
const std::vector<CarrotPaymentProposalVerifiableSelfSendV1> &selfsend_payment_proposals_in,
const rct::xmr_amount fee_per_weight,
const rct::xmr_amount fee_quantization_mask,
const std::vector<uint8_t> &extra,
const cryptonote::transaction_type tx_type,
select_inputs_func_t &&select_inputs,
const crypto::public_key &change_address_spend_pubkey,
const subaddress_index_extended &change_address_index,
const std::set<std::size_t> &subtractable_normal_payment_proposals,
const std::set<std::size_t> &subtractable_selfsend_payment_proposals,
CarrotTransactionProposalV1 &tx_proposal_out)
{
std::vector<CarrotPaymentProposalVerifiableSelfSendV1> selfsend_payment_proposals = selfsend_payment_proposals_in;
// always add implicit selfsend enote, so resultant enotes' amounts mirror given payments set close as possible
// note: we always do this, even if the amount ends up being 0 and we already have a selfsend. this is because if we
// realize later that the change output we added here has a 0 amount, and we try removing it, then the fee
// would go down and then the change amount *wouldn't* be 0, so it must stay. Although technically,
// the scenario could arise where a change in input selection changes the input sum amount and fee exactly
// such that we could remove the implicit change output and it happens to balance. IMO, handling this edge
// case isn't worth the additional code complexity, and may cause unexpected uniformity issues. The calling
// code might expect that transfers to N destinations always produces a transaction with N+1 outputs
const bool add_payment_type_selfsend = normal_payment_proposals.empty() &&
selfsend_payment_proposals.size() == 1 &&
selfsend_payment_proposals.at(0).proposal.enote_type == CarrotEnoteType::CHANGE;
selfsend_payment_proposals.push_back(CarrotPaymentProposalVerifiableSelfSendV1{
.proposal = CarrotPaymentProposalSelfSendV1{
.destination_address_spend_pubkey = change_address_spend_pubkey,
.amount = 0,
.enote_type = add_payment_type_selfsend ? CarrotEnoteType::PAYMENT : CarrotEnoteType::CHANGE,
.asset_type = "SAL1"
},
.subaddr_index = change_address_index
});
// define carves fees and balance callback
carve_fees_and_balance_func_t carve_fees_and_balance =
[
&subtractable_normal_payment_proposals,
&subtractable_selfsend_payment_proposals,
&tx_type
]
(
const boost::multiprecision::uint128_t &input_sum_amount,
const rct::xmr_amount fee,
std::vector<CarrotPaymentProposalV1> &normal_payment_proposals,
std::vector<CarrotPaymentProposalVerifiableSelfSendV1> &selfsend_payment_proposals
)
{
// shadow subtractable_selfsend_payment_proposals and adjust for default case (no subtractable provided)
const auto &subtractable_selfsend_payment_proposals_ref = subtractable_selfsend_payment_proposals;
std::set<std::size_t> subtractable_selfsend_payment_proposals = subtractable_selfsend_payment_proposals_ref;
if (subtractable_selfsend_payment_proposals.empty())
subtractable_selfsend_payment_proposals.insert(selfsend_payment_proposals.size() - 1);
const bool has_subbable_normal = !subtractable_normal_payment_proposals.empty();
const bool has_subbable_selfsend = !subtractable_selfsend_payment_proposals.empty();
const size_t num_normal = normal_payment_proposals.size();
const size_t num_selfsend = selfsend_payment_proposals.size();
// check subbable indices invariants
CHECK_AND_ASSERT_THROW_MES(
!has_subbable_normal || *subtractable_normal_payment_proposals.crbegin() < num_normal,
"make unsigned transaction transfer subtractable: subtractable normal proposal index out of bounds");
CHECK_AND_ASSERT_THROW_MES(
!has_subbable_selfsend || *subtractable_selfsend_payment_proposals.crbegin() < num_selfsend,
"make unsigned transaction transfer subtractable: subtractable selfsend proposal index out of bounds");
CHECK_AND_ASSERT_THROW_MES(has_subbable_normal || has_subbable_selfsend,
"make unsigned transaction transfer subtractable: no subtractable indices");
// check selfsend proposal invariants
CHECK_AND_ASSERT_THROW_MES(!selfsend_payment_proposals.empty(),
"make unsigned transaction transfer subtractable: missing a selfsend proposal");
CHECK_AND_ASSERT_THROW_MES(selfsend_payment_proposals.back().proposal.amount == 0,
"make unsigned transaction transfer subtractable: bug: added implicit change output has non-zero amount");
// start by setting the last selfsend amount equal to (inputs - outputs), before fee
boost::multiprecision::uint128_t implicit_change_amount = input_sum_amount;
for (const CarrotPaymentProposalV1 &normal_payment_proposal : normal_payment_proposals)
implicit_change_amount -= normal_payment_proposal.amount;
for (const CarrotPaymentProposalVerifiableSelfSendV1 &selfsend_payment_proposal : selfsend_payment_proposals)
implicit_change_amount -= selfsend_payment_proposal.proposal.amount;
selfsend_payment_proposals.back().proposal.amount =
boost::numeric_cast<rct::xmr_amount>(implicit_change_amount);
// deduct an even fee amount from all subtractable outputs
const size_t num_subtractble_normal = subtractable_normal_payment_proposals.size();
const size_t num_subtractable_selfsend = subtractable_selfsend_payment_proposals.size();
const size_t num_subtractable = num_subtractble_normal + num_subtractable_selfsend;
const rct::xmr_amount minimum_subtraction = fee / num_subtractable; // no div by 0 since we checked subtractable
for (size_t normal_sub_idx : subtractable_normal_payment_proposals)
{
CarrotPaymentProposalV1 &normal_payment_proposal = normal_payment_proposals[normal_sub_idx];
CHECK_AND_ASSERT_THROW_MES(normal_payment_proposal.amount >= minimum_subtraction,
"make unsigned transaction transfer subtractable: not enough funds in subtractable payment");
normal_payment_proposal.amount -= minimum_subtraction;
}
for (size_t selfsend_sub_idx : subtractable_selfsend_payment_proposals)
{
CarrotPaymentProposalSelfSendV1 &selfsend_payment_proposal =
selfsend_payment_proposals[selfsend_sub_idx].proposal;
CHECK_AND_ASSERT_THROW_MES(selfsend_payment_proposal.amount >= minimum_subtraction,
"make unsigned transaction transfer subtractable: not enough funds in subtractable payment");
selfsend_payment_proposal.amount -= minimum_subtraction;
}
// deduct 1 at a time from selfsend proposals
rct::xmr_amount fee_remainder = fee % num_subtractable;
for (size_t selfsend_sub_idx : subtractable_selfsend_payment_proposals)
{
if (fee_remainder == 0)
break;
CarrotPaymentProposalSelfSendV1 &selfsend_payment_proposal =
selfsend_payment_proposals[selfsend_sub_idx].proposal;
CHECK_AND_ASSERT_THROW_MES(selfsend_payment_proposal.amount >= 1,
"make unsigned transaction transfer subtractable: not enough funds in subtractable payment");
selfsend_payment_proposal.amount -= 1;
fee_remainder -= 1;
}
// now deduct 1 at a time from normal proposals, shuffled
if (fee_remainder != 0)
{
// create vector of shuffled subtractble normal payment indices
// note: we do this to hide the order that the normal payment proposals were described in this call, in case
// the recipients collude
std::vector<size_t> shuffled_normal_subtractable(subtractable_normal_payment_proposals.cbegin(),
subtractable_normal_payment_proposals.cend());
std::shuffle(shuffled_normal_subtractable.begin(),
shuffled_normal_subtractable.end(),
crypto::random_device{});
for (size_t normal_sub_idx : shuffled_normal_subtractable)
{
if (fee_remainder == 0)
break;
CarrotPaymentProposalV1 &normal_payment_proposal = normal_payment_proposals[normal_sub_idx];
CHECK_AND_ASSERT_THROW_MES(normal_payment_proposal.amount >= 1,
"make unsigned transaction transfer subtractable: not enough funds in subtractable payment");
normal_payment_proposal.amount -= 1;
fee_remainder -= 1;
}
}
CHECK_AND_ASSERT_THROW_MES(fee_remainder == 0,
"make unsigned transaction transfer subtractable: bug: fee remainder at end of carve function");
// remove the self send payment we have made to ourself now that we have our change payment.
if (tx_type == cryptonote::transaction_type::STAKE ||
tx_type == cryptonote::transaction_type::BURN)
{
selfsend_payment_proposals.back().proposal.enote_ephemeral_pubkey =
selfsend_payment_proposals.front().proposal.enote_ephemeral_pubkey;
selfsend_payment_proposals.erase(selfsend_payment_proposals.begin());
}
}; //end carve_fees_and_balance
// make unsigned transaction with fee carving callback
make_carrot_transaction_proposal_v1(normal_payment_proposals,
selfsend_payment_proposals,
fee_per_weight,
fee_quantization_mask,
extra,
tx_type,
std::forward<select_inputs_func_t>(select_inputs),
std::move(carve_fees_and_balance),
change_address_spend_pubkey,
change_address_index,
tx_proposal_out);
}
//-------------------------------------------------------------------------------------------------------------------
void make_carrot_transaction_proposal_v1_sweep(
const std::vector<CarrotPaymentProposalV1> &normal_payment_proposals,
const std::vector<CarrotPaymentProposalVerifiableSelfSendV1> &selfsend_payment_proposals,
const rct::xmr_amount fee_per_weight,
const rct::xmr_amount fee_quantization_mask,
const std::vector<uint8_t> &extra,
const cryptonote::transaction_type tx_type,
std::vector<CarrotSelectedInput> &&selected_inputs,
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
CHECK_AND_ASSERT_THROW_MES(normal_payment_proposals.size() || selfsend_payment_proposals.size(),
"make carrot transaction proposal v1 sweep: no payment proposals provided");
// sanity check that all payment proposal amounts are 0
for (const CarrotPaymentProposalV1 &normal_payment_proposal : normal_payment_proposals)
{
CHECK_AND_ASSERT_THROW_MES(normal_payment_proposal.amount == 0,
"make carrot transaction proposal v1 sweep: payment proposal amount not 0");
}
for (const CarrotPaymentProposalVerifiableSelfSendV1 &selfsend_payment_proposal : selfsend_payment_proposals)
{
CHECK_AND_ASSERT_THROW_MES(selfsend_payment_proposal.proposal.amount == 0,
"make carrot transaction proposal v1 sweep: payment proposal amount not 0");
}
// sanity check that either normal payment proposals XOR selfsend are provided, not both
CHECK_AND_ASSERT_THROW_MES(bool(normal_payment_proposals.size()) ^ bool(selfsend_payment_proposals.size()),
"make carrot transaction proposal v1 sweep: both normal and self-send payment proposals are provided");
const bool is_selfsend_sweep = !selfsend_payment_proposals.empty();
// define input selection callback, which is just a shuttle for `selected_inputs`
select_inputs_func_t select_inputs = [&selected_inputs]
(
const boost::multiprecision::uint128_t&,
const std::map<std::size_t, rct::xmr_amount>&,
const std::size_t,
const std::size_t,
std::vector<CarrotSelectedInput> &selected_inputs_out
)
{
selected_inputs_out = std::move(selected_inputs);
}; //end select_inputs
// define carves fees and balance callback
carve_fees_and_balance_func_t carve_fees_and_balance = [is_selfsend_sweep]
(
const boost::multiprecision::uint128_t &input_sum_amount,
const rct::xmr_amount fee,
std::vector<CarrotPaymentProposalV1> &normal_payment_proposals,
std::vector<CarrotPaymentProposalVerifiableSelfSendV1> &selfsend_payment_proposals
)
{
// get pointers to proposal amounts and shuffle, excluding implicit selfsend
const size_t n_outputs = normal_payment_proposals.size() + selfsend_payment_proposals.size();
std::vector<rct::xmr_amount*> amount_ptrs;
amount_ptrs.reserve(n_outputs);
if (is_selfsend_sweep)
for (CarrotPaymentProposalVerifiableSelfSendV1 &selfsend_payment_proposal : selfsend_payment_proposals)
amount_ptrs.push_back(&selfsend_payment_proposal.proposal.amount);
else
for (CarrotPaymentProposalV1 &normal_payment_proposal : normal_payment_proposals)
amount_ptrs.push_back(&normal_payment_proposal.amount);
std::shuffle(amount_ptrs.begin(), amount_ptrs.end(), crypto::random_device{});
// disburse amount equally amongst modifiable amounts
const boost::multiprecision::uint128_t output_sum_amount = input_sum_amount - fee;
const rct::xmr_amount minimum_sweep_amount =
boost::numeric_cast<rct::xmr_amount>(output_sum_amount / amount_ptrs.size());
const size_t num_remaining =
boost::numeric_cast<rct::xmr_amount>(output_sum_amount % amount_ptrs.size());
CHECK_AND_ASSERT_THROW_MES(num_remaining < amount_ptrs.size(),
"make carrot transaction proposal v1 sweep: bug: num_remaining >= n_outputs");
for (size_t i = 0; i < amount_ptrs.size(); ++i)
*amount_ptrs.at(i) = minimum_sweep_amount + (i < num_remaining ? 1 : 0);
}; //end carve_fees_and_balance
// make unsigned transaction with sweep carving callback and selected inputs
make_carrot_transaction_proposal_v1(normal_payment_proposals,
selfsend_payment_proposals,
fee_per_weight,
fee_quantization_mask,
extra,
tx_type,
std::move(select_inputs),
std::move(carve_fees_and_balance),
change_address_spend_pubkey,
change_address_index,
tx_proposal_out);
}
//-------------------------------------------------------------------------------------------------------------------
} //namespace carrot