aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/simplewallet/simplewallet.cpp36
-rw-r--r--src/simplewallet/simplewallet.h2
-rw-r--r--src/wallet/wallet2.cpp181
-rw-r--r--src/wallet/wallet2.h6
4 files changed, 209 insertions, 16 deletions
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<std::string> &args/*
return true;
}
+bool simple_wallet::set_segregate_pre_fork_outputs(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
+{
+ 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<std::string> &args/* = std::vector<std::string>()*/)
+{
+ 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<std::string> &args/* = std::vector<std::string>()*/)
{
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<std::string> &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<std::string> &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<std::string> &args = std::vector<std::string>());
bool set_refresh_from_block_height(const std::vector<std::string> &args = std::vector<std::string>());
bool set_auto_low_priority(const std::vector<std::string> &args = std::vector<std::string>());
+ bool set_segregate_pre_fork_outputs(const std::vector<std::string> &args = std::vector<std::string>());
+ bool set_key_reuse_mitigation2(const std::vector<std::string> &args = std::vector<std::string>());
bool help(const std::vector<std::string> &args = std::vector<std::string>());
bool start_mining(const std::vector<std::string> &args);
bool stop_mining(const std::vector<std::string> &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<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)
{
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<crypto::secret_key> &additional_tx_keys) const;
void check_tx_key(const crypto::hash &txid, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &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<crypto::hash> m_scanned_pool_txs[2];