196 lines
10 KiB
C++
196 lines
10 KiB
C++
// Copyright (c) 2025, 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.
|
|
|
|
#pragma once
|
|
|
|
//local headers
|
|
#include "tx_proposal_utils.h"
|
|
|
|
//third party headers
|
|
|
|
//standard headers
|
|
#include <set>
|
|
|
|
//forward declarations
|
|
|
|
namespace carrot
|
|
{
|
|
struct InputCandidate
|
|
{
|
|
CarrotSelectedInput core;
|
|
|
|
bool is_pre_carrot;
|
|
bool is_external;
|
|
uint64_t block_index;
|
|
};
|
|
|
|
namespace InputSelectionFlags
|
|
{
|
|
// Quantum forward secrecy (ON = unsafe)
|
|
static constexpr std::uint32_t ALLOW_EXTERNAL_INPUTS_IN_NORMAL_TRANSFERS = 1 << 0;
|
|
static constexpr std::uint32_t ALLOW_PRE_CARROT_INPUTS_IN_NORMAL_TRANSFERS = 1 << 1;
|
|
static constexpr std::uint32_t ALLOW_MIXED_INTERNAL_EXTERNAL = 1 << 2;
|
|
static constexpr std::uint32_t ALLOW_MIXED_CARROT_PRE_CARROT = 1 << 3;
|
|
|
|
// Amount handling
|
|
static constexpr std::uint32_t IS_KNOWN_FEE_SUBTRACTABLE = 1 << 4;
|
|
static constexpr std::uint32_t ALLOW_DUST = 1 << 5;
|
|
}
|
|
|
|
/**
|
|
* brief: input_selection_policy_t - a functor which implements N-input selection on a subset of candidates
|
|
* param: input_candidates -
|
|
* param: selectable_input_indices - subset of indices in `input_candidates` allowed to select from
|
|
* param: n_inputs - exact number of selected inputs should return
|
|
* param: required_money - sum of amounts of selected inputs should be greater than or equal to this value
|
|
* outparam: selected_inputs_indices_out - `n_inputs` subset of `selectable_input_indices`, of selected input indices
|
|
*
|
|
* To signify selection failure, `selected_inputs_indices_out` should be empty after end of call.
|
|
*/
|
|
using input_selection_policy_t = std::function<void(
|
|
epee::span<const InputCandidate> input_candidates,
|
|
const std::set<std::size_t> &selectable_input_indices,
|
|
std::size_t n_inputs,
|
|
const boost::multiprecision::uint128_t &required_money,
|
|
std::set<std::size_t> &selected_inputs_indices_out
|
|
)>;
|
|
|
|
/**
|
|
* brief: compare_input_candidate_same_ki - compare two input candidates who share a key image; we can only choose one!
|
|
* param: lhs -
|
|
* param: rhs -
|
|
* return: 1 if lhs is better, -1 if rhs is better, 0 if neutral
|
|
* throw: component_out_of_order - iff rhs.core.key_image != lhs.core.key_image
|
|
*
|
|
* The better candidate is determined by criteria in descending order of importance as follows:
|
|
* 1. Amount (higher is better, duh)
|
|
* 2. Age (older is better for protection against double spend attacks)
|
|
* 3. Is pre-Carrot enote? (`false` is better for spending QFS)
|
|
* 4. Is external enote? (`false` is better for spending QFS)
|
|
*/
|
|
int compare_input_candidate_same_ki(const InputCandidate &lhs, const InputCandidate &rhs);
|
|
/**
|
|
* brief: form_preferred_input_candidate_subsets - make subsets of input candidates to try selection in preferred order
|
|
* param: input_candidates - slice to user-provided input candidates
|
|
* param: flags - see InputSelectionFlags namespace
|
|
* param: is_normal_transfer - true iff num normal non-dummy payments in tx to perform selection for is >= 1
|
|
* return: ordered list of subsets (represented by 0-based indices) of input_candidates to try selection on
|
|
*
|
|
* This function also performs a burning bug check; no indices returned in any subset will reference
|
|
* an input candidate when another input candidate shares the same key image but is "better" as
|
|
* determined by compare_input_candidate_same_ki.
|
|
*
|
|
* Purpose: Mainly due to quantum forward secrecy properties of spending different types of Monero
|
|
* enotes, it isn't always preferable to be able to select all usable input candidates together in
|
|
* the same transaction. For example, spending internal Carrot enotes is always quantum forward
|
|
* secret, even when one's public Monero address is known. By contrast, spending a pre-Carrot enote
|
|
* is never quantum forward secret, even with no public address knowledge. As such, a spender should
|
|
* prefer not to spend these enotes in the same transaction, since the pre-Carrot enote will "taint"
|
|
* the quantum forward spending secrecy of the internal Carrot enote. Based on certain heuristics
|
|
* and user-provided flags, this function creates a list of subsets of input_candidates, in
|
|
* preferred order, to perform input selection on.
|
|
*
|
|
* Flags:
|
|
* * ALLOW_EXTERNAL_INPUTS_IN_NORMAL_TRANSFERS - external inputs in normal txs are allowed iff=1
|
|
* * ALLOW_PRE_CARROT_INPUTS_IN_NORMAL_TRANSFERS - pre-carrot inputs in normal txs are allowed iff=1
|
|
* * ALLOW_MIXED_INTERNAL_EXTERNAL - mixing internal/external inputs in any txs is allowed iff=1
|
|
* * ALLOW_MIXED_CARROT_PRE_CARROT - mixing pre-carrot/carrot inputs in any txs is allowed iff=1
|
|
*
|
|
* General rules (not in any specific order):
|
|
* * It should be preferred NOT to use external inputs in normal transfers
|
|
* * It should be preferred NOT to use pre-carrot inputs in normal transfers
|
|
* * It should be preferred NOT to mix internal/external inputs
|
|
* * It should be preferred NOT to mix pre-carrot/carrot inputs
|
|
* * It should be preferred YES to use external & pre-carrots inputs in self-send transactions
|
|
*
|
|
* In scenarios where a user-provided flag allows a non-preferred subset of input candidates, this
|
|
* function will FIRST add the subset of input candidates as if the user didn't provide that
|
|
* flag, and THEN add the non-preferred subset. For example, let's say that you pass input
|
|
* candidates span {A, B}, flags=ALLOW_EXTERNAL_INPUTS_IN_NORMAL_TRANSFERS, and
|
|
* is_normal_transfer=true. In this example, A is internal and B is external. The return
|
|
* value of this function will be {{0}, {1}, {0, 1}}. This means: "try input selection on 0 (A)
|
|
* first, then 1 (B), and then both {0, 1} (A & B)". In this example, although external inputs are
|
|
* *allowed* in normal transfers by the flag provided by the user, they are not *preferred*, so
|
|
* {A} comes before {B} in the subset list. Mixing is the least preferred, so {A, B} is the last
|
|
* subset.
|
|
*
|
|
* If unsure which flags to use, flags=0 is the "safest" option for input selection. Note that this
|
|
* completely disallows normal transfers for legacy key hierarchies, since inputs will never be
|
|
* internal due to the lack of the view-balance secret s_vb in legacy key hierarchies.
|
|
*/
|
|
std::vector<std::set<std::size_t>> form_preferred_input_candidate_subsets(
|
|
const epee::span<const InputCandidate> input_candidates,
|
|
const std::uint32_t flags,
|
|
const bool is_normal_transfer);
|
|
/**
|
|
* brief: get_input_counts_in_preferred_order - return list of tx input counts in order that we should prefer to select
|
|
*
|
|
* Transaction input counts are trivially observable on-chain, so picking a wrong input count when
|
|
* given the chance between multiple choices can have privacy consequences. The purpose of this
|
|
* function is to determine the order in which we should try to select a certain number of inputs.
|
|
*/
|
|
std::vector<std::size_t> get_input_counts_in_preferred_order();
|
|
/**
|
|
* brief: make_single_transfer_input_selector - a customizable input selector for single (i.e. not batched) transfers
|
|
* param: input_candidates -
|
|
* param: policies - slice of ISPs to attempt selection on, in order of user's preference
|
|
* param: flags - see InputSelectionFlags namespace
|
|
* outparam: selected_input_indices_out - selected indices into `input_candidates` (optional)
|
|
* return: input selector functor
|
|
*
|
|
* The returned input selector considers provided input candidates, and creates subsets of the
|
|
* candidates as according to `form_preferred_input_candidate_subsets`. Then, in the order of input
|
|
* counts as according to `get_input_counts_in_preferred_order`, finds the first pair (subset, input
|
|
* count) that contains enough "usable" money: a sum of money great enough to pay the nominal output
|
|
* sum, plus any required fees as according to the input count. Once that is found, ISPs are
|
|
* dispatched in provided user order until one succeeds. If none succeed for that pair, then the
|
|
* whole of input selection fails, we do not move onto the next (subset, input count) pair.
|
|
*
|
|
* SAFETY: The lifetime of objects referenced by `input_candidates` and `selected_input_indices_out`
|
|
* (if not null) must be valid at least as long as as the last call to the returned functor.
|
|
*/
|
|
select_inputs_func_t make_single_transfer_input_selector(
|
|
const epee::span<const InputCandidate> input_candidates,
|
|
const epee::span<const input_selection_policy_t> policies,
|
|
const std::uint32_t flags,
|
|
std::set<size_t> *selected_input_indices_out);
|
|
|
|
namespace ispolicy
|
|
{
|
|
/**
|
|
* brief: select_greedy_aging - an ISP which generally attempts to select old outputs, but isn't necessarily optimal
|
|
*/
|
|
void select_greedy_aging(const epee::span<const InputCandidate>,
|
|
const std::set<std::size_t>&,
|
|
std::size_t,
|
|
const boost::multiprecision::uint128_t&,
|
|
std::set<std::size_t>&);
|
|
|
|
} //namespace ispolicy
|
|
} //namespace carrot
|