diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 3363b84ed..698ecced8 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -1690,6 +1690,32 @@ bool simple_wallet::set_auto_low_priority(const std::vector &args/* return true; } +bool simple_wallet::set_segregate_pre_fork_outputs(const std::vector &args/* = std::vector()*/) +{ + const auto pwd_container = get_and_verify_password(); + if (pwd_container) + { + parse_bool_and_use(args[1], [&](bool r) { + m_wallet->segregate_pre_fork_outputs(r); + m_wallet->rewrite(m_wallet_file, pwd_container->password()); + }); + } + return true; +} + +bool simple_wallet::set_key_reuse_mitigation2(const std::vector &args/* = std::vector()*/) +{ + const auto pwd_container = get_and_verify_password(); + if (pwd_container) + { + parse_bool_and_use(args[1], [&](bool r) { + m_wallet->key_reuse_mitigation2(r); + m_wallet->rewrite(m_wallet_file, pwd_container->password()); + }); + } + return true; +} + bool simple_wallet::help(const std::vector &args/* = std::vector()*/) { if(args.empty()) @@ -1864,7 +1890,11 @@ simple_wallet::simple_wallet() "refresh-from-block-height [n]\n " " Set the height before which to ignore blocks.\n " "auto-low-priority <1|0>\n " - " Whether to automatically use the low priority fee level when it's safe to do so.")); + " Whether to automatically use the low priority fee level when it's safe to do so.\n " + "segregate-pre-fork-outputs <1|0>\n " + " Set this if you intend to spend outputs on both Monero AND a key reusing fork.\n " + "key-reuse-mitigation2 <1|0>\n " + " Set this if you are not sure whether you will spend on a key reusing Monero fork later.")); m_cmd_binder.set_handler("encrypted_seed", boost::bind(&simple_wallet::encrypted_seed, this, _1), tr("Display the encrypted Electrum-style mnemonic seed.")); @@ -2040,6 +2070,8 @@ bool simple_wallet::set_variable(const std::vector &args) success_msg_writer() << "confirm-export-overwrite = " << m_wallet->confirm_export_overwrite(); success_msg_writer() << "refresh-from-block-height = " << m_wallet->get_refresh_from_block_height(); success_msg_writer() << "auto-low-priority = " << m_wallet->auto_low_priority(); + success_msg_writer() << "segregate-pre-fork-outputs = " << m_wallet->segregate_pre_fork_outputs(); + success_msg_writer() << "key-reuse-mitigation2 = " << m_wallet->key_reuse_mitigation2(); return true; } else @@ -2090,6 +2122,8 @@ bool simple_wallet::set_variable(const std::vector &args) CHECK_SIMPLE_VARIABLE("confirm-export-overwrite", set_confirm_export_overwrite, tr("0 or 1")); CHECK_SIMPLE_VARIABLE("refresh-from-block-height", set_refresh_from_block_height, tr("block height")); CHECK_SIMPLE_VARIABLE("auto-low-priority", set_auto_low_priority, tr("0 or 1")); + CHECK_SIMPLE_VARIABLE("segregate-pre-fork-outputs", set_segregate_pre_fork_outputs, tr("0 or 1")); + CHECK_SIMPLE_VARIABLE("key-reuse-mitigation2", set_key_reuse_mitigation2, tr("0 or 1")); } fail_msg_writer() << tr("set: unrecognized argument(s)"); return true; diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index 781b74b66..9846c82f3 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -133,6 +133,8 @@ namespace cryptonote bool set_confirm_export_overwrite(const std::vector &args = std::vector()); bool set_refresh_from_block_height(const std::vector &args = std::vector()); bool set_auto_low_priority(const std::vector &args = std::vector()); + bool set_segregate_pre_fork_outputs(const std::vector &args = std::vector()); + bool set_key_reuse_mitigation2(const std::vector &args = std::vector()); bool help(const std::vector &args = std::vector()); bool start_mining(const std::vector &args); bool stop_mining(const std::vector &args); diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 40d4be34c..6c71a81db 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -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 writer(buffer); @@ -5656,6 +5674,20 @@ void wallet2::get_outs(std::vector> 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 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> 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> 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> 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) - { - 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; - } + 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) + { + 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(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(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> // 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> // 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)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)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> // 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> 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) { diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 79686753b..979372851 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -862,6 +862,10 @@ namespace tools void confirm_export_overwrite(bool always) { m_confirm_export_overwrite = always; } bool auto_low_priority() const { return m_auto_low_priority; } void auto_low_priority(bool value) { m_auto_low_priority = value; } + bool segregate_pre_fork_outputs() const { return m_segregate_pre_fork_outputs; } + void segregate_pre_fork_outputs(bool value) { m_segregate_pre_fork_outputs = value; } + bool key_reuse_mitigation2() const { return m_key_reuse_mitigation2; } + void key_reuse_mitigation2(bool value) { m_key_reuse_mitigation2 = value; } bool get_tx_key(const crypto::hash &txid, crypto::secret_key &tx_key, std::vector &additional_tx_keys) const; void check_tx_key(const crypto::hash &txid, const crypto::secret_key &tx_key, const std::vector &additional_tx_keys, const cryptonote::account_public_address &address, uint64_t &received, bool &in_pool, uint64_t &confirmations); @@ -1181,6 +1185,8 @@ namespace tools uint32_t m_confirm_backlog_threshold; bool m_confirm_export_overwrite; bool m_auto_low_priority; + bool m_segregate_pre_fork_outputs; + bool m_key_reuse_mitigation2; bool m_is_initialized; NodeRPCProxy m_node_rpc_proxy; std::unordered_set m_scanned_pool_txs[2];