aboutsummaryrefslogtreecommitdiff
path: root/src/simplewallet/simplewallet.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/simplewallet/simplewallet.cpp')
-rw-r--r--src/simplewallet/simplewallet.cpp366
1 files changed, 352 insertions, 14 deletions
diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp
index e9172b1da..bd1e424d4 100644
--- a/src/simplewallet/simplewallet.cpp
+++ b/src/simplewallet/simplewallet.cpp
@@ -375,8 +375,9 @@ namespace
return true;
}
- void handle_transfer_exception(const std::exception_ptr &e)
+ void handle_transfer_exception(const std::exception_ptr &e, bool trusted_daemon)
{
+ bool warn_of_possible_attack = !trusted_daemon;
try
{
std::rethrow_exception(e);
@@ -404,6 +405,7 @@ namespace
print_money(e.available()) %
print_money(e.tx_amount()));
fail_msg_writer() << tr("Not enough money in unlocked balance");
+ warn_of_possible_attack = false;
}
catch (const tools::error::not_enough_money& e)
{
@@ -411,6 +413,7 @@ namespace
print_money(e.available()) %
print_money(e.tx_amount()));
fail_msg_writer() << tr("Not enough money in unlocked balance");
+ warn_of_possible_attack = false;
}
catch (const tools::error::tx_not_possible& e)
{
@@ -420,6 +423,7 @@ namespace
print_money(e.tx_amount()) %
print_money(e.fee()));
fail_msg_writer() << tr("Failed to find a way to create transactions. This is usually due to dust which is so small it cannot pay for itself in fees, or trying to send more money than the unlocked balance, or not leaving enough for fees");
+ warn_of_possible_attack = false;
}
catch (const tools::error::not_enough_outs_to_mix& e)
{
@@ -434,6 +438,7 @@ namespace
catch (const tools::error::tx_not_constructed&)
{
fail_msg_writer() << tr("transaction was not constructed");
+ warn_of_possible_attack = false;
}
catch (const tools::error::tx_rejected& e)
{
@@ -445,14 +450,17 @@ namespace
catch (const tools::error::tx_sum_overflow& e)
{
fail_msg_writer() << e.what();
+ warn_of_possible_attack = false;
}
catch (const tools::error::zero_destination&)
{
fail_msg_writer() << tr("one of destinations is zero");
+ warn_of_possible_attack = false;
}
catch (const tools::error::tx_too_big& e)
{
fail_msg_writer() << tr("failed to find a suitable way to split transactions");
+ warn_of_possible_attack = false;
}
catch (const tools::error::transfer_error& e)
{
@@ -463,6 +471,7 @@ namespace
{
LOG_ERROR("Multisig error: " << e.to_string());
fail_msg_writer() << tr("Multisig error: ") << e.what();
+ warn_of_possible_attack = false;
}
catch (const tools::error::wallet_internal_error& e)
{
@@ -474,6 +483,9 @@ namespace
LOG_ERROR("unexpected error: " << e.what());
fail_msg_writer() << tr("unexpected error: ") << e.what();
}
+
+ if (warn_of_possible_attack)
+ fail_msg_writer() << tr("There was an error, which could mean the node may be trying to get you to retry creating a transaction, and zero in on which outputs you own. Or it could be a bona fide error. It may be prudent to disconnect from this node, and not try to send a tranasction immediately. Alternatively, connect to another node so the original node cannot correlate information.");
}
bool check_file_overwrite(const std::string &filename)
@@ -1195,7 +1207,7 @@ bool simple_wallet::submit_multisig(const std::vector<std::string> &args)
}
catch (const std::exception &e)
{
- handle_transfer_exception(std::current_exception());
+ handle_transfer_exception(std::current_exception(), m_trusted_daemon);
}
catch (...)
{
@@ -1282,6 +1294,275 @@ bool simple_wallet::export_raw_multisig(const std::vector<std::string> &args)
return true;
}
+bool simple_wallet::print_ring(const std::vector<std::string> &args)
+{
+ crypto::key_image key_image;
+ crypto::hash txid;
+ if (args.size() != 1)
+ {
+ fail_msg_writer() << tr("usage: print_ring <key_image|txid>");
+ return true;
+ }
+
+ if (!epee::string_tools::hex_to_pod(args[0], key_image))
+ {
+ fail_msg_writer() << tr("Invalid key image");
+ return true;
+ }
+ // this one will always work, they're all 32 byte hex
+ if (!epee::string_tools::hex_to_pod(args[0], txid))
+ {
+ fail_msg_writer() << tr("Invalid txid");
+ return true;
+ }
+
+ std::vector<uint64_t> ring;
+ std::vector<std::pair<crypto::key_image, std::vector<uint64_t>>> rings;
+ try
+ {
+ if (m_wallet->get_ring(key_image, ring))
+ rings.push_back({key_image, ring});
+ else if (!m_wallet->get_rings(txid, rings))
+ {
+ fail_msg_writer() << tr("Key image either not spent, or spent with mixin 0");
+ return true;
+ }
+
+ for (const auto &ring: rings)
+ {
+ std::stringstream str;
+ for (const auto &x: ring.second)
+ str << x<< " ";
+ // do NOT translate this "absolute" below, the lin can be used as input to set_ring
+ success_msg_writer() << epee::string_tools::pod_to_hex(ring.first) << " absolute " << str.str();
+ }
+ }
+ catch (const std::exception &e)
+ {
+ fail_msg_writer() << tr("Failed to get key image ring: ") << e.what();
+ }
+
+ return true;
+}
+
+bool simple_wallet::set_ring(const std::vector<std::string> &args)
+{
+ crypto::key_image key_image;
+ if (args.size() < 3)
+ {
+ fail_msg_writer() << tr("usage: set_ring <key_image> absolute|relative <index> [<index>...]");
+ return true;
+ }
+
+ if (!epee::string_tools::hex_to_pod(args[0], key_image))
+ {
+ fail_msg_writer() << tr("Invalid key image");
+ return true;
+ }
+
+ bool relative;
+ if (args[1] == "absolute")
+ {
+ relative = false;
+ }
+ else if (args[1] == "relative")
+ {
+ relative = true;
+ }
+ else
+ {
+ fail_msg_writer() << tr("Missing absolute or relative keyword");
+ return true;
+ }
+
+ std::vector<uint64_t> ring;
+ for (size_t n = 2; n < args.size(); ++n)
+ {
+ ring.resize(ring.size() + 1);
+ if (!string_tools::get_xtype_from_string(ring.back(), args[n]))
+ {
+ fail_msg_writer() << tr("invalid index: must be a strictly positive unsigned integer");
+ return true;
+ }
+ if (relative)
+ {
+ if (ring.size() > 1 && !ring.back())
+ {
+ fail_msg_writer() << tr("invalid index: must be a strictly positive unsigned integer");
+ return true;
+ }
+ uint64_t sum = 0;
+ for (uint64_t out: ring)
+ {
+ if (out > std::numeric_limits<uint64_t>::max() - sum)
+ {
+ fail_msg_writer() << tr("invalid index: indices wrap");
+ return true;
+ }
+ sum += out;
+ }
+ }
+ else
+ {
+ if (ring.size() > 1 && ring[ring.size() - 2] >= ring[ring.size() - 1])
+ {
+ fail_msg_writer() << tr("invalid index: indices should be in strictly ascending order");
+ return true;
+ }
+ }
+ }
+ if (!m_wallet->set_ring(key_image, ring, relative))
+ {
+ fail_msg_writer() << tr("failed to set ring");
+ return true;
+ }
+
+ return true;
+}
+
+bool simple_wallet::blackball(const std::vector<std::string> &args)
+{
+ crypto::public_key output;
+ if (args.size() == 0)
+ {
+ fail_msg_writer() << tr("usage: blackball <output_public_key> | <filename> [add]");
+ return true;
+ }
+
+ try
+ {
+ if (epee::string_tools::hex_to_pod(args[0], output))
+ {
+ m_wallet->blackball_output(output);
+ }
+ else if (epee::file_io_utils::is_file_exist(args[0]))
+ {
+ std::vector<crypto::public_key> outputs;
+ char str[65];
+
+ std::unique_ptr<FILE, tools::close_file> f(fopen(args[0].c_str(), "r"));
+ if (f)
+ {
+ while (!feof(f.get()))
+ {
+ if (!fgets(str, sizeof(str), f.get()))
+ break;
+ const size_t len = strlen(str);
+ if (len > 0 && str[len - 1] == '\n')
+ str[len - 1] = 0;
+ if (!str[0])
+ continue;
+ outputs.push_back(crypto::public_key());
+ if (!epee::string_tools::hex_to_pod(str, outputs.back()))
+ {
+ fail_msg_writer() << tr("Invalid public key: ") << str;
+ return true;
+ }
+ }
+ f.reset();
+ bool add = false;
+ if (args.size() > 1)
+ {
+ if (args[1] != "add")
+ {
+ fail_msg_writer() << tr("Bad argument: ") + args[1] + ": " + tr("should be \"add\"");
+ return true;
+ }
+ add = true;
+ }
+ m_wallet->set_blackballed_outputs(outputs, add);
+ }
+ else
+ {
+ fail_msg_writer() << tr("Failed to open file");
+ return true;
+ }
+ }
+ else
+ {
+ fail_msg_writer() << tr("Invalid public key, and file doesn't exist");
+ return true;
+ }
+ }
+ catch (const std::exception &e)
+ {
+ fail_msg_writer() << tr("Failed to blackball output: ") << e.what();
+ }
+
+ return true;
+}
+
+bool simple_wallet::unblackball(const std::vector<std::string> &args)
+{
+ crypto::public_key output;
+ if (args.size() != 1)
+ {
+ fail_msg_writer() << tr("usage: unblackball <output_public_key>");
+ return true;
+ }
+
+ if (!epee::string_tools::hex_to_pod(args[0], output))
+ {
+ fail_msg_writer() << tr("Invalid public key");
+ return true;
+ }
+
+ try
+ {
+ m_wallet->unblackball_output(output);
+ }
+ catch (const std::exception &e)
+ {
+ fail_msg_writer() << tr("Failed to unblackball output: ") << e.what();
+ }
+
+ return true;
+}
+
+bool simple_wallet::blackballed(const std::vector<std::string> &args)
+{
+ crypto::public_key output;
+ if (args.size() != 1)
+ {
+ fail_msg_writer() << tr("usage: blackballed <output_public_key>");
+ return true;
+ }
+
+ if (!epee::string_tools::hex_to_pod(args[0], output))
+ {
+ fail_msg_writer() << tr("Invalid public key");
+ return true;
+ }
+
+ try
+ {
+ if (m_wallet->is_output_blackballed(output))
+ message_writer() << tr("Blackballed: ") << output;
+ else
+ message_writer() << tr("not blackballed: ") << output;
+ }
+ catch (const std::exception &e)
+ {
+ fail_msg_writer() << tr("Failed to unblackball output: ") << e.what();
+ }
+
+ return true;
+}
+
+bool simple_wallet::save_known_rings(const std::vector<std::string> &args)
+{
+ try
+ {
+ LOCK_IDLE_SCOPE();
+ m_wallet->find_and_save_rings();
+ }
+ catch (const std::exception &e)
+ {
+ fail_msg_writer() << tr("Failed to save known rings: ") << e.what();
+ }
+ return true;
+}
+
bool simple_wallet::set_always_confirm_transfers(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
{
const auto pwd_container = get_and_verify_password();
@@ -1626,6 +1907,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())
@@ -1800,7 +2107,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."));
@@ -1939,6 +2250,30 @@ simple_wallet::simple_wallet()
boost::bind(&simple_wallet::export_raw_multisig, this, _1),
tr("export_raw_multisig_tx <filename>"),
tr("Export a signed multisig transaction to a file"));
+ m_cmd_binder.set_handler("print_ring",
+ boost::bind(&simple_wallet::print_ring, this, _1),
+ tr("print_ring <key_image> | <txid>"),
+ tr("Print the ring(s) used to spend a given key image or transaction (if the ring size is > 1)"));
+ m_cmd_binder.set_handler("set_ring",
+ boost::bind(&simple_wallet::set_ring, this, _1),
+ tr("set_ring <key_image> absolute|relative <index> [<index>...]"),
+ tr("Set the ring used for a given key image, so it can be reused in a fork"));
+ m_cmd_binder.set_handler("save_known_rings",
+ boost::bind(&simple_wallet::save_known_rings, this, _1),
+ tr("save_known_rings"),
+ tr("Save known rings to the shared rings database"));
+ m_cmd_binder.set_handler("blackball",
+ boost::bind(&simple_wallet::blackball, this, _1),
+ tr("blackball <output public key> | <filename> [add]"),
+ tr("Blackball output(s) so they never get selected as fake outputs in a ring"));
+ m_cmd_binder.set_handler("unblackball",
+ boost::bind(&simple_wallet::unblackball, this, _1),
+ tr("unblackball <output public key>"),
+ tr("Unblackballs an output so it may get selected as a fake output in a ring"));
+ m_cmd_binder.set_handler("blackballed",
+ boost::bind(&simple_wallet::blackballed, this, _1),
+ tr("blackballed <output public key>"),
+ tr("Checks whether an output is blackballed"));
m_cmd_binder.set_handler("help",
boost::bind(&simple_wallet::help, this, _1),
tr("help [<command>]"),
@@ -1968,6 +2303,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
@@ -2018,6 +2355,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;
@@ -2683,7 +3022,6 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm)
if (!m_trusted_daemon)
message_writer() << (boost::format(tr("Warning: using an untrusted daemon at %s, privacy will be lessened")) % m_wallet->get_daemon_address()).str();
- m_http_client.set_server(m_wallet->get_daemon_address(), m_wallet->get_daemon_login());
m_wallet->callback(this);
return true;
@@ -3216,7 +3554,7 @@ bool simple_wallet::start_mining(const std::vector<std::string>& args)
}
COMMAND_RPC_START_MINING::response res;
- bool r = net_utils::invoke_http_json("/start_mining", req, res, m_http_client);
+ bool r = m_wallet->invoke_http_json("/start_mining", req, res);
std::string err = interpret_rpc_response(r, res.status);
if (err.empty())
success_msg_writer() << tr("Mining started in daemon");
@@ -3238,7 +3576,7 @@ bool simple_wallet::stop_mining(const std::vector<std::string>& args)
COMMAND_RPC_STOP_MINING::request req;
COMMAND_RPC_STOP_MINING::response res;
- bool r = net_utils::invoke_http_json("/stop_mining", req, res, m_http_client);
+ bool r = m_wallet->invoke_http_json("/stop_mining", req, res);
std::string err = interpret_rpc_response(r, res.status);
if (err.empty())
success_msg_writer() << tr("Mining stopped in daemon");
@@ -3295,7 +3633,7 @@ bool simple_wallet::save_bc(const std::vector<std::string>& args)
}
COMMAND_RPC_SAVE_BC::request req;
COMMAND_RPC_SAVE_BC::response res;
- bool r = net_utils::invoke_http_json("/save_bc", req, res, m_http_client);
+ bool r = m_wallet->invoke_http_json("/save_bc", req, res);
std::string err = interpret_rpc_response(r, res.status);
if (err.empty())
success_msg_writer() << tr("Blockchain saved");
@@ -3641,7 +3979,7 @@ uint64_t simple_wallet::get_daemon_blockchain_height(std::string& err)
COMMAND_RPC_GET_HEIGHT::request req;
COMMAND_RPC_GET_HEIGHT::response res = boost::value_initialized<COMMAND_RPC_GET_HEIGHT::response>();
- bool r = net_utils::invoke_http_json("/getheight", req, res, m_http_client);
+ bool r = m_wallet->invoke_http_json("/getheight", req, res);
err = interpret_rpc_response(r, res.status);
return res.height;
}
@@ -3763,7 +4101,7 @@ bool simple_wallet::print_ring_members(const std::vector<tools::wallet2::pending
req.outputs[j].index = absolute_offsets[j];
}
COMMAND_RPC_GET_OUTPUTS_BIN::response res = AUTO_VAL_INIT(res);
- bool r = net_utils::invoke_http_bin("/get_outs.bin", req, res, m_http_client);
+ bool r = m_wallet->invoke_http_bin("/get_outs.bin", req, res);
err = interpret_rpc_response(r, res.status);
if (!err.empty())
{
@@ -4176,7 +4514,7 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri
}
catch (const std::exception &e)
{
- handle_transfer_exception(std::current_exception());
+ handle_transfer_exception(std::current_exception(), m_trusted_daemon);
}
catch (...)
{
@@ -4284,7 +4622,7 @@ bool simple_wallet::sweep_unmixable(const std::vector<std::string> &args_)
}
catch (const std::exception &e)
{
- handle_transfer_exception(std::current_exception());
+ handle_transfer_exception(std::current_exception(), m_trusted_daemon);
}
catch (...)
{
@@ -4517,7 +4855,7 @@ bool simple_wallet::sweep_main(uint64_t below, const std::vector<std::string> &a
}
catch (const std::exception& e)
{
- handle_transfer_exception(std::current_exception());
+ handle_transfer_exception(std::current_exception(), m_trusted_daemon);
}
catch (...)
{
@@ -4716,7 +5054,7 @@ bool simple_wallet::sweep_single(const std::vector<std::string> &args_)
}
catch (const std::exception& e)
{
- handle_transfer_exception(std::current_exception());
+ handle_transfer_exception(std::current_exception(), m_trusted_daemon);
}
catch (...)
{
@@ -5021,7 +5359,7 @@ bool simple_wallet::submit_transfer(const std::vector<std::string> &args_)
}
catch (const std::exception& e)
{
- handle_transfer_exception(std::current_exception());
+ handle_transfer_exception(std::current_exception(), m_trusted_daemon);
}
catch (...)
{