@ -94,7 +94,9 @@ using namespace cryptonote;
# define MULTISIG_UNSIGNED_TX_PREFIX "Monero multisig unsigned tx set\001"
# define RECENT_OUTPUT_RATIO (0.5) // 50% of outputs are from the recent zone
# define RECENT_OUTPUT_ZONE ((time_t)(1.8 * 86400)) // last 1.8 day makes up the recent zone (taken from monerolink.pdf, Miller et al)
# define RECENT_OUTPUT_DAYS (1.8) // last 1.8 day makes up the recent zone (taken from monerolink.pdf, Miller et al)
# define RECENT_OUTPUT_ZONE ((time_t)(RECENT_OUTPUT_DAYS * 86400))
# define RECENT_OUTPUT_BLOCKS (RECENT_OUTPUT_DAYS * 720)
# define FEE_ESTIMATE_GRACE_BLOCKS 10 // estimate fee valid for that many blocks
@ -107,6 +109,12 @@ using namespace cryptonote;
# define MULTISIG_EXPORT_FILE_MAGIC "Monero multisig export\001"
# define SEGREGATION_FORK_HEIGHT 1564965
# define TESTNET_SEGREGATION_FORK_HEIGHT 1000000
# define STAGENET_SEGREGATION_FORK_HEIGHT 1000000
# define SEGREGATION_FORK_VICINITY 1500 /* blocks */
namespace
{
std : : string get_default_ringdb_path ( )
@ -652,6 +660,8 @@ wallet2::wallet2(network_type nettype, bool restricted):
m_confirm_backlog_threshold ( 0 ) ,
m_confirm_export_overwrite ( true ) ,
m_auto_low_priority ( true ) ,
m_segregate_pre_fork_outputs ( true ) ,
m_key_reuse_mitigation2 ( true ) ,
m_is_initialized ( false ) ,
m_restricted ( restricted ) ,
is_old_file_format ( false ) ,
@ -2328,6 +2338,8 @@ bool wallet2::get_output_distribution(uint64_t &start_height, std::vector<uint64
cryptonote : : COMMAND_RPC_GET_OUTPUT_DISTRIBUTION : : request req = AUTO_VAL_INIT ( req ) ;
cryptonote : : COMMAND_RPC_GET_OUTPUT_DISTRIBUTION : : response res = AUTO_VAL_INIT ( res ) ;
req . amounts . push_back ( 0 ) ;
req . from_height = 0 ;
req . cumulative = true ;
m_daemon_rpc_mutex . lock ( ) ;
bool r = net_utils : : invoke_http_json_rpc ( " /json_rpc " , " get_output_distribution " , req , res , m_http_client , rpc_timeout ) ;
m_daemon_rpc_mutex . unlock ( ) ;
@ -2563,6 +2575,12 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable
value2 . SetUint ( m_nettype ) ;
json . AddMember ( " nettype " , value2 , json . GetAllocator ( ) ) ;
value2 . SetInt ( m_segregate_pre_fork_outputs ? 1 : 0 ) ;
json . AddMember ( " segregate_pre_fork_outputs " , value2 , json . GetAllocator ( ) ) ;
value2 . SetInt ( m_key_reuse_mitigation2 ? 1 : 0 ) ;
json . AddMember ( " key_reuse_mitigation2 " , value2 , json . GetAllocator ( ) ) ;
// Serialize the JSON object
rapidjson : : StringBuffer buffer ;
rapidjson : : Writer < rapidjson : : StringBuffer > writer ( buffer ) ;
@ -5656,6 +5674,20 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
if ( fake_outputs_count > 0 )
{
uint64_t segregation_fork_height ;
switch ( m_nettype )
{
case TESTNET : segregation_fork_height = TESTNET_SEGREGATION_FORK_HEIGHT ; break ;
case STAGENET : segregation_fork_height = STAGENET_SEGREGATION_FORK_HEIGHT ; break ;
case MAINNET : segregation_fork_height = SEGREGATION_FORK_HEIGHT ; break ;
default : THROW_WALLET_EXCEPTION ( error : : wallet_internal_error , " Invalid network type " ) ;
}
// check whether we're shortly after the fork
uint64_t height ;
boost : : optional < std : : string > result = m_node_rpc_proxy . get_height ( height ) ;
throw_on_rpc_response_error ( result , " get_info " ) ;
bool is_shortly_after_segregation_fork = height > = segregation_fork_height & & height < segregation_fork_height + SEGREGATION_FORK_VICINITY ;
// get histogram for the amounts we need
cryptonote : : COMMAND_RPC_GET_OUTPUT_HISTOGRAM : : request req_t = AUTO_VAL_INIT ( req_t ) ;
cryptonote : : COMMAND_RPC_GET_OUTPUT_HISTOGRAM : : response resp_t = AUTO_VAL_INIT ( resp_t ) ;
@ -5673,6 +5705,50 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
THROW_WALLET_EXCEPTION_IF ( resp_t . status = = CORE_RPC_STATUS_BUSY , error : : daemon_busy , " get_output_histogram " ) ;
THROW_WALLET_EXCEPTION_IF ( resp_t . status ! = CORE_RPC_STATUS_OK , error : : get_histogram_error , resp_t . status ) ;
// if we want to segregate fake outs pre or post fork, get distribution
std : : unordered_map < uint64_t , std : : pair < uint64_t , uint64_t > > segregation_limit ;
if ( m_segregate_pre_fork_outputs | | m_key_reuse_mitigation2 )
{
cryptonote : : COMMAND_RPC_GET_OUTPUT_DISTRIBUTION : : request req_t = AUTO_VAL_INIT ( req_t ) ;
cryptonote : : COMMAND_RPC_GET_OUTPUT_DISTRIBUTION : : response resp_t = AUTO_VAL_INIT ( resp_t ) ;
for ( size_t idx : selected_transfers )
req_t . amounts . push_back ( m_transfers [ idx ] . is_rct ( ) ? 0 : m_transfers [ idx ] . amount ( ) ) ;
std : : sort ( req_t . amounts . begin ( ) , req_t . amounts . end ( ) ) ;
auto end = std : : unique ( req_t . amounts . begin ( ) , req_t . amounts . end ( ) ) ;
req_t . amounts . resize ( std : : distance ( req_t . amounts . begin ( ) , end ) ) ;
req_t . from_height = segregation_fork_height > = RECENT_OUTPUT_ZONE ? height > = ( segregation_fork_height ? segregation_fork_height : height ) - RECENT_OUTPUT_BLOCKS : 0 ;
req_t . cumulative = true ;
m_daemon_rpc_mutex . lock ( ) ;
bool r = net_utils : : invoke_http_json_rpc ( " /json_rpc " , " get_output_distribution " , req_t , resp_t , m_http_client , rpc_timeout ) ;
m_daemon_rpc_mutex . unlock ( ) ;
THROW_WALLET_EXCEPTION_IF ( ! r , error : : no_connection_to_daemon , " transfer_selected " ) ;
THROW_WALLET_EXCEPTION_IF ( resp_t . status = = CORE_RPC_STATUS_BUSY , error : : daemon_busy , " get_output_distribution " ) ;
THROW_WALLET_EXCEPTION_IF ( resp_t . status ! = CORE_RPC_STATUS_OK , error : : get_output_distribution , resp_t . status ) ;
// check we got all data
for ( size_t idx : selected_transfers )
{
const uint64_t amount = m_transfers [ idx ] . is_rct ( ) ? 0 : m_transfers [ idx ] . amount ( ) ;
bool found = false ;
for ( const auto & d : resp_t . distributions )
{
if ( d . amount = = amount )
{
THROW_WALLET_EXCEPTION_IF ( d . start_height > segregation_fork_height , error : : get_output_distribution , " Distribution start_height too high " ) ;
THROW_WALLET_EXCEPTION_IF ( segregation_fork_height - d . start_height > = d . distribution . size ( ) , error : : get_output_distribution , " Distribution size too small " ) ;
THROW_WALLET_EXCEPTION_IF ( segregation_fork_height < = RECENT_OUTPUT_BLOCKS , error : : wallet_internal_error , " Fork height too low " ) ;
THROW_WALLET_EXCEPTION_IF ( segregation_fork_height - RECENT_OUTPUT_BLOCKS < d . start_height , error : : get_output_distribution , " Bad start height " ) ;
uint64_t till_fork = d . distribution [ segregation_fork_height - d . start_height ] ;
uint64_t recent = till_fork - d . distribution [ segregation_fork_height - RECENT_OUTPUT_BLOCKS - d . start_height ] ;
segregation_limit [ amount ] = std : : make_pair ( till_fork , recent ) ;
found = true ;
break ;
}
}
THROW_WALLET_EXCEPTION_IF ( ! found , error : : get_output_distribution , " Requested amount not found in response " ) ;
}
}
// we ask for more, to have spares if some outputs are still locked
size_t base_requested_outputs_count = ( size_t ) ( ( fake_outputs_count + 1 ) * 1.5 + 1 ) ;
LOG_PRINT_L2 ( " base_requested_outputs_count: " < < base_requested_outputs_count ) ;
@ -5692,35 +5768,83 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
size_t requested_outputs_count = base_requested_outputs_count + ( td . is_rct ( ) ? CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE : 0 ) ;
size_t start = req . outputs . size ( ) ;
// if there are just enough outputs to mix with, use all of them.
// Eventually this should become impossible.
const bool output_is_pre_fork = td . m_block_height < segregation_fork_height ;
uint64_t num_outs = 0 , num_recent_outs = 0 ;
for ( const auto & he : resp_t . histogram )
uint64_t num_post_fork_outs = 0 ;
float pre_fork_num_out_ratio = 0.0f ;
float post_fork_num_out_ratio = 0.0f ;
if ( m_segregate_pre_fork_outputs & & output_is_pre_fork )
{
if ( he . amount = = amount )
num_outs = segregation_limit [ amount ] . first ;
num_recent_outs = segregation_limit [ amount ] . second ;
}
else
{
// if there are just enough outputs to mix with, use all of them.
// Eventually this should become impossible.
for ( const auto & he : resp_t . histogram )
{
LOG_PRINT_L2 ( " Found " < < print_money ( amount ) < < " : " < < he . total_instances < < " total, "
< < he . unlocked_instances < < " unlocked, " < < he . recent_instances < < " recent " ) ;
num_outs = he . unlocked_instances ;
num_recent_outs = he . recent_instances ;
break ;
if ( he . amount = = amount )
{
LOG_PRINT_L2 ( " Found " < < print_money ( amount ) < < " : " < < he . total_instances < < " total, "
< < he . unlocked_instances < < " unlocked, " < < he . recent_instances < < " recent " ) ;
num_outs = he . unlocked_instances ;
num_recent_outs = he . recent_instances ;
break ;
}
}
if ( m_key_reuse_mitigation2 )
{
if ( output_is_pre_fork )
{
if ( is_shortly_after_segregation_fork )
{
pre_fork_num_out_ratio = 33.4 / 100.0f * ( 1.0f - RECENT_OUTPUT_RATIO ) ;
}
else
{
pre_fork_num_out_ratio = 33.4 / 100.0f * ( 1.0f - RECENT_OUTPUT_RATIO ) ;
post_fork_num_out_ratio = 33.4 / 100.0f * ( 1.0f - RECENT_OUTPUT_RATIO ) ;
}
}
else
{
if ( is_shortly_after_segregation_fork )
{
}
else
{
post_fork_num_out_ratio = 67.8 / 100.0f * ( 1.0f - RECENT_OUTPUT_RATIO ) ;
}
}
}
num_post_fork_outs = num_outs - segregation_limit [ amount ] . first ;
}
LOG_PRINT_L1 ( " " < < num_outs < < " unlocked outputs of size " < < print_money ( amount ) ) ;
THROW_WALLET_EXCEPTION_IF ( num_outs = = 0 , error : : wallet_internal_error ,
" histogram reports no unlocked outputs for " + boost : : lexical_cast < std : : string > ( amount ) + " , not even ours " ) ;
THROW_WALLET_EXCEPTION_IF ( num_recent_outs > num_outs , error : : wallet_internal_error ,
" histogram reports more recent outs than outs for " + boost : : lexical_cast < std : : string > ( amount ) ) ;
// how many fake outs to draw on a pre-fork triangular distribution
size_t pre_fork_outputs_count = requested_outputs_count * pre_fork_num_out_ratio ;
size_t post_fork_outputs_count = requested_outputs_count * post_fork_num_out_ratio ;
// how many fake outs to draw otherwise
size_t normal_output_count = requested_outputs_count - pre_fork_outputs_count - post_fork_outputs_count ;
// X% of those outs are to be taken from recent outputs
size_t recent_outputs_count = requested_outputs_count * RECENT_OUTPUT_RATIO ;
size_t recent_outputs_count = normal_output _count * RECENT_OUTPUT_RATIO ;
if ( recent_outputs_count = = 0 )
recent_outputs_count = 1 ; // ensure we have at least one, if possible
if ( recent_outputs_count > num_recent_outs )
recent_outputs_count = num_recent_outs ;
if ( td . m_global_output_index > = num_outs - num_recent_outs & & recent_outputs_count > 0 )
- - recent_outputs_count ; // if the real out is recent, pick one less recent fake out
LOG_PRINT_L1 ( " Using " < < recent_outputs_count < < " recent outputs " ) ;
LOG_PRINT_L1 ( " Fake output makeup: " < < requested_outputs_count < < " requested: " < < recent_outputs_count < < " recent, " < <
pre_fork_outputs_count < < " pre-fork, " < < post_fork_outputs_count < < " post-fork, " < <
( requested_outputs_count - recent_outputs_count - pre_fork_outputs_count - post_fork_outputs_count ) < < " full-chain " ) ;
uint64_t num_found = 0 ;
@ -5796,6 +5920,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
// list of output indices we've seen.
uint64_t i ;
const char * type = " " ;
if ( num_found - 1 < recent_outputs_count ) // -1 to account for the real one we seeded with
{
// triangular distribution over [a,b) with a=0, mode c=b=up_index_limit
@ -5805,7 +5930,29 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
// just in case rounding up to 1 occurs after calc
if ( i = = num_outs )
- - i ;
LOG_PRINT_L2 ( " picking " < < i < < " as recent " ) ;
type = " recent " ;
}
else if ( num_found - 1 < recent_outputs_count + pre_fork_outputs_count )
{
// triangular distribution over [a,b) with a=0, mode c=b=up_index_limit
uint64_t r = crypto : : rand < uint64_t > ( ) % ( ( uint64_t ) 1 < < 53 ) ;
double frac = std : : sqrt ( ( double ) r / ( ( uint64_t ) 1 < < 53 ) ) ;
i = ( uint64_t ) ( frac * segregation_limit [ amount ] . first ) ;
// just in case rounding up to 1 occurs after calc
if ( i = = num_outs )
- - i ;
type = " pre-fork " ;
}
else if ( num_found - 1 < recent_outputs_count + pre_fork_outputs_count + post_fork_outputs_count )
{
// triangular distribution over [a,b) with a=0, mode c=b=up_index_limit
uint64_t r = crypto : : rand < uint64_t > ( ) % ( ( uint64_t ) 1 < < 53 ) ;
double frac = std : : sqrt ( ( double ) r / ( ( uint64_t ) 1 < < 53 ) ) ;
i = ( uint64_t ) ( frac * num_post_fork_outs ) + segregation_limit [ amount ] . first ;
// just in case rounding up to 1 occurs after calc
if ( i = = num_post_fork_outs + segregation_limit [ amount ] . first )
- - i ;
type = " post-fork " ;
}
else
{
@ -5816,13 +5963,14 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
// just in case rounding up to 1 occurs after calc
if ( i = = num_outs )
- - i ;
LOG_PRINT_L2( " picking " < < i < < " as triangular " ) ;
type = " triangular " ;
}
if ( seen_indices . count ( i ) )
continue ;
seen_indices . emplace ( i ) ;
LOG_PRINT_L2 ( " picking " < < i < < " as " < < type ) ;
req . outputs . push_back ( { amount , i } ) ;
+ + num_found ;
}
@ -5860,7 +6008,10 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
uint64_t num_outs = 0 ;
const uint64_t amount = td . is_rct ( ) ? 0 : td . amount ( ) ;
for ( const auto & he : resp_t . histogram )
const bool output_is_pre_fork = td . m_block_height < segregation_fork_height ;
if ( m_segregate_pre_fork_outputs & & output_is_pre_fork )
num_outs = segregation_limit [ amount ] . first ;
else for ( const auto & he : resp_t . histogram )
{
if ( he . amount = = amount )
{