diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/wallet/wallet2.cpp | 40 |
1 files changed, 39 insertions, 1 deletions
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 217a22027..6e8d585dc 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -144,6 +144,9 @@ using namespace cryptonote; #define IGNORE_LONG_PAYMENT_ID_FROM_BLOCK_VERSION 12 +#define DEFAULT_UNLOCK_TIME (CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE * DIFFICULTY_TARGET_V2) +#define RECENT_SPEND_WINDOW (50 * DIFFICULTY_TARGET_V2) + static const std::string MULTISIG_SIGNATURE_MAGIC = "SigMultisigPkV1"; static const std::string MULTISIG_EXTRA_INFO_MAGIC = "MultisigxV1"; @@ -1019,7 +1022,13 @@ gamma_picker::gamma_picker(const std::vector<uint64_t> &rct_offsets, double shap end = rct_offsets.data() + rct_offsets.size() - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE; num_rct_outputs = *(end - 1); THROW_WALLET_EXCEPTION_IF(num_rct_outputs == 0, error::wallet_internal_error, "No rct outputs"); + THROW_WALLET_EXCEPTION_IF(outputs_to_consider == 0, error::wallet_internal_error, "No rct outputs to consider"); average_output_time = DIFFICULTY_TARGET_V2 * blocks_to_consider / outputs_to_consider; // this assumes constant target over the whole rct range + if (average_output_time == 0) { + // TODO: apply this to all cases; do so alongside a hard fork, where all clients will update at the same time, preventing anonymity puddle formation + average_output_time = DIFFICULTY_TARGET_V2 * blocks_to_consider / static_cast<double>(outputs_to_consider); + } + THROW_WALLET_EXCEPTION_IF(average_output_time == 0, error::wallet_internal_error, "Average seconds per output cannot be 0."); }; gamma_picker::gamma_picker(const std::vector<uint64_t> &rct_offsets): gamma_picker(rct_offsets, GAMMA_SHAPE, GAMMA_SCALE) {} @@ -1028,6 +1037,34 @@ uint64_t gamma_picker::pick() { double x = gamma(engine); x = exp(x); + + if (x > DEFAULT_UNLOCK_TIME) + { + // We are trying to select an output from the chain that appeared 'x' seconds before the + // current chain tip, where 'x' is selected from the gamma distribution recommended in Miller et al. + // (https://arxiv.org/pdf/1704.04299/). + // Our method is to get the average time delta between outputs in the recent past, estimate the number of + // outputs 'n' that would have appeared between 'chain_tip - x' and 'chain_tip', select the real output at + // 'current_num_outputs - n', then randomly select an output from the block where that output appears. + // Source code to paper: https://github.com/maltemoeser/moneropaper + // + // Due to the 'default spendable age' mechanic in Monero, 'current_num_outputs' only contains + // currently *unlocked* outputs, which means the earliest output that can be selected is not at the chain tip! + // Therefore, we must offset 'x' so it matches up with the timing of the outputs being considered. We do + // this by saying if 'x` equals the expected age of the first unlocked output (compared to the current + // chain tip - i.e. DEFAULT_UNLOCK_TIME), then select the first unlocked output. + x -= DEFAULT_UNLOCK_TIME; + } + else + { + // If the spent time suggested by the gamma is less than the unlock time, that means the gamma is suggesting an output + // that is no longer feasible to be spent (possible since the gamma was constructed when consensus rules did not enforce the + // lock time). The assumption made in this code is that an output expected spent quicker than the unlock time would likely + // be spent within RECENT_SPEND_WINDOW after allowed. So it returns an output that falls between 0 and the RECENT_SPEND_WINDOW. + // The RECENT_SPEND_WINDOW was determined with empirical analysis of observed data. + x = crypto::rand_idx(static_cast<uint64_t>(RECENT_SPEND_WINDOW)); + } + uint64_t output_index = x / average_output_time; if (output_index >= num_rct_outputs) return std::numeric_limits<uint64_t>::max(); // bad pick @@ -8659,7 +8696,8 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> if (req.outputs[i].index == td.m_global_output_index) if (daemon_resp.outs[i].key == boost::get<txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key) if (daemon_resp.outs[i].mask == mask) - real_out_found = true; + if (daemon_resp.outs[i].unlocked) + real_out_found = true; } THROW_WALLET_EXCEPTION_IF(!real_out_found, error::wallet_internal_error, "Daemon response did not include the requested real output"); |