From bdd815a8bfe7eb51b04aeccc297b7f64c84a0dc7 Mon Sep 17 00:00:00 2001 From: j-berman Date: Fri, 3 Sep 2021 19:04:17 -0700 Subject: [PATCH 1/4] Match Monero 0.17.2.3 decoy selection algo --- src/util/gamma_picker.cpp | 27 +++++++++++++++++++++------ src/util/gamma_picker.h | 2 +- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/util/gamma_picker.cpp b/src/util/gamma_picker.cpp index 29273cd..cb24c97 100644 --- a/src/util/gamma_picker.cpp +++ b/src/util/gamma_picker.cpp @@ -40,6 +40,8 @@ namespace lws constexpr const double gamma_shape = 19.28; constexpr const double gamma_scale = 1 / double(1.61); constexpr const std::size_t blocks_in_a_year = (86400 * 365) / DIFFICULTY_TARGET_V2; + constexpr const std::size_t defaut_unlock_time = CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE * DIFFICULTY_TARGET_V2; + constexpr const std::size_t recent_spend_window = 50 * DIFFICULTY_TARGET_V2; } gamma_picker::gamma_picker(std::vector rct_offsets) @@ -49,7 +51,7 @@ namespace lws gamma_picker::gamma_picker(std::vector offsets_in, double shape, double scale) : rct_offsets(std::move(offsets_in)), gamma(shape, scale), - outputs_per_second(0) + seconds_per_output(0) { if (!rct_offsets.empty()) { @@ -59,8 +61,11 @@ namespace lws const std::size_t outputs_to_consider = rct_offsets.back() - initial; static_assert(0 < DIFFICULTY_TARGET_V2, "block target time cannot be zero"); + // match wallet2's integer truncation implementation to avoid creating distinct anonymity puddles // this assumes constant target over the whole rct range - outputs_per_second = outputs_to_consider / double(DIFFICULTY_TARGET_V2 * blocks_to_consider); + seconds_per_output = DIFFICULTY_TARGET_V2 * blocks_to_consider / outputs_to_consider; + if (seconds_per_output == 0) + seconds_per_output = double(DIFFICULTY_TARGET_V2 * blocks_to_consider) / outputs_to_consider; } } @@ -84,17 +89,27 @@ namespace lws static_assert(std::is_empty(), "random_device is no longer cheap to construct"); static constexpr const crypto::random_device engine{}; const auto end = offsets().end() - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE; + const uint64_t num_rct_outputs = spendable_upper_bound(); for (unsigned tries = 0; tries < 100; ++tries) { - std::uint64_t output_index = std::exp(gamma(engine)) * outputs_per_second; - if (offsets().back() <= output_index) + double output_age_in_seconds = std::exp(gamma(engine)); + + // match wallet2 implementation to avoid creating distinct anonymity pools + // shift output back by unlock time to apply distribution from chain tip + if (output_age_in_seconds > defaut_unlock_time) + output_age_in_seconds -= defaut_unlock_time; + else + output_age_in_seconds = crypto::rand_idx(recent_spend_window); + + std::uint64_t output_index = output_age_in_seconds / seconds_per_output; + if (num_rct_outputs <= output_index) continue; // gamma selected older than blockchain height (rare) - output_index = offsets().back() - 1 - output_index; + output_index = num_rct_outputs - 1 - output_index; const auto selection = std::lower_bound(offsets().begin(), end, output_index); if (selection == end) - continue; // gamma selected within locked/non-spendable range (rare) + throw std::runtime_error{"Unable to select random output - output not found"}; const std::uint64_t first_rct = offsets().begin() == selection ? 0 : *(selection - 1); const std::uint64_t n_rct = *selection - first_rct; diff --git a/src/util/gamma_picker.h b/src/util/gamma_picker.h index 851d504..9a66ccb 100644 --- a/src/util/gamma_picker.h +++ b/src/util/gamma_picker.h @@ -36,7 +36,7 @@ namespace lws { std::vector rct_offsets; std::gamma_distribution gamma; - double outputs_per_second; + double seconds_per_output; gamma_picker(const gamma_picker&) = default; // force explicit usage of `clone()` to copy. public: From 31b7c7ce43238135b1e0df13af77526ca5a93cf3 Mon Sep 17 00:00:00 2001 From: j-berman Date: Thu, 30 Sep 2021 01:29:29 -0700 Subject: [PATCH 2/4] Revert back to outputs_per_second + use logic error on output miss --- src/util/gamma_picker.cpp | 14 +++++--------- src/util/gamma_picker.h | 2 +- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/util/gamma_picker.cpp b/src/util/gamma_picker.cpp index cb24c97..cccffcc 100644 --- a/src/util/gamma_picker.cpp +++ b/src/util/gamma_picker.cpp @@ -51,7 +51,7 @@ namespace lws gamma_picker::gamma_picker(std::vector offsets_in, double shape, double scale) : rct_offsets(std::move(offsets_in)), gamma(shape, scale), - seconds_per_output(0) + outputs_per_second(0) { if (!rct_offsets.empty()) { @@ -61,11 +61,8 @@ namespace lws const std::size_t outputs_to_consider = rct_offsets.back() - initial; static_assert(0 < DIFFICULTY_TARGET_V2, "block target time cannot be zero"); - // match wallet2's integer truncation implementation to avoid creating distinct anonymity puddles // this assumes constant target over the whole rct range - seconds_per_output = DIFFICULTY_TARGET_V2 * blocks_to_consider / outputs_to_consider; - if (seconds_per_output == 0) - seconds_per_output = double(DIFFICULTY_TARGET_V2 * blocks_to_consider) / outputs_to_consider; + outputs_per_second = outputs_to_consider / double(DIFFICULTY_TARGET_V2 * blocks_to_consider); } } @@ -95,21 +92,20 @@ namespace lws { double output_age_in_seconds = std::exp(gamma(engine)); - // match wallet2 implementation to avoid creating distinct anonymity pools // shift output back by unlock time to apply distribution from chain tip if (output_age_in_seconds > defaut_unlock_time) output_age_in_seconds -= defaut_unlock_time; else output_age_in_seconds = crypto::rand_idx(recent_spend_window); - std::uint64_t output_index = output_age_in_seconds / seconds_per_output; + std::uint64_t output_index = output_age_in_seconds * outputs_per_second; if (num_rct_outputs <= output_index) continue; // gamma selected older than blockchain height (rare) output_index = num_rct_outputs - 1 - output_index; const auto selection = std::lower_bound(offsets().begin(), end, output_index); if (selection == end) - throw std::runtime_error{"Unable to select random output - output not found"}; + throw std::logic_error{"Unable to select random output - output not found in range (should never happen)"}; const std::uint64_t first_rct = offsets().begin() == selection ? 0 : *(selection - 1); const std::uint64_t n_rct = *selection - first_rct; @@ -117,7 +113,7 @@ namespace lws return first_rct + crypto::rand_idx(n_rct); // block had zero outputs (miner didn't collect XMR?) } - throw std::runtime_error{"Unable to select random output in spendable range using gamma distribution after 1,024 attempts"}; + throw std::runtime_error{"Unable to select random output in spendable range using gamma distribution after 100 attempts"}; } std::vector gamma_picker::take_offsets() diff --git a/src/util/gamma_picker.h b/src/util/gamma_picker.h index 9a66ccb..851d504 100644 --- a/src/util/gamma_picker.h +++ b/src/util/gamma_picker.h @@ -36,7 +36,7 @@ namespace lws { std::vector rct_offsets; std::gamma_distribution gamma; - double seconds_per_output; + double outputs_per_second; gamma_picker(const gamma_picker&) = default; // force explicit usage of `clone()` to copy. public: From acacae17c636a8f55b06edd9fdafc1e21ae3470f Mon Sep 17 00:00:00 2001 From: j-berman Date: Thu, 14 Oct 2021 20:10:32 -0700 Subject: [PATCH 3/4] fix variable name typo --- src/util/gamma_picker.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/util/gamma_picker.cpp b/src/util/gamma_picker.cpp index cccffcc..ba6ebb5 100644 --- a/src/util/gamma_picker.cpp +++ b/src/util/gamma_picker.cpp @@ -40,7 +40,7 @@ namespace lws constexpr const double gamma_shape = 19.28; constexpr const double gamma_scale = 1 / double(1.61); constexpr const std::size_t blocks_in_a_year = (86400 * 365) / DIFFICULTY_TARGET_V2; - constexpr const std::size_t defaut_unlock_time = CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE * DIFFICULTY_TARGET_V2; + constexpr const std::size_t default_unlock_time = CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE * DIFFICULTY_TARGET_V2; constexpr const std::size_t recent_spend_window = 50 * DIFFICULTY_TARGET_V2; } @@ -93,8 +93,8 @@ namespace lws double output_age_in_seconds = std::exp(gamma(engine)); // shift output back by unlock time to apply distribution from chain tip - if (output_age_in_seconds > defaut_unlock_time) - output_age_in_seconds -= defaut_unlock_time; + if (output_age_in_seconds > default_unlock_time) + output_age_in_seconds -= default_unlock_time; else output_age_in_seconds = crypto::rand_idx(recent_spend_window); From 2a4db59d2fe48533a532b663f8e4a9613c38f574 Mon Sep 17 00:00:00 2001 From: j-berman Date: Thu, 14 Oct 2021 20:13:17 -0700 Subject: [PATCH 4/4] Decrease "recent_spend_window" to 15 --- src/util/gamma_picker.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/gamma_picker.cpp b/src/util/gamma_picker.cpp index ba6ebb5..5ad1023 100644 --- a/src/util/gamma_picker.cpp +++ b/src/util/gamma_picker.cpp @@ -41,7 +41,7 @@ namespace lws constexpr const double gamma_scale = 1 / double(1.61); constexpr const std::size_t blocks_in_a_year = (86400 * 365) / DIFFICULTY_TARGET_V2; constexpr const std::size_t default_unlock_time = CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE * DIFFICULTY_TARGET_V2; - constexpr const std::size_t recent_spend_window = 50 * DIFFICULTY_TARGET_V2; + constexpr const std::size_t recent_spend_window = 15 * DIFFICULTY_TARGET_V2; } gamma_picker::gamma_picker(std::vector rct_offsets)