// Copyright (c) 2014-2018, The Monero Project // // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are // permitted provided that the following conditions are met: // // 1. Redistributions of source code must retain the above copyright notice, this list of // conditions and the following disclaimer. // // 2. Redistributions in binary form must reproduce the above copyright notice, this list // of conditions and the following disclaimer in the documentation and/or other // materials provided with the distribution. // // 3. Neither the name of the copyright holder nor the names of its contributors may be // used to endorse or promote products derived from this software without specific // prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL // THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers /*! * \file simplewallet.cpp * * \brief Source file that defines simple_wallet class. */ #include #include #include #include #include #include #include #include #include #include #include "include_base_utils.h" #include "common/i18n.h" #include "common/command_line.h" #include "common/util.h" #include "common/dns_utils.h" #include "common/base58.h" #include "common/scoped_message_writer.h" #include "cryptonote_protocol/cryptonote_protocol_handler.h" #include "simplewallet.h" #include "cryptonote_basic/cryptonote_format_utils.h" #include "storages/http_abstract_invoke.h" #include "rpc/core_rpc_server_commands_defs.h" #include "crypto/crypto.h" // for crypto::secret_key definition #include "mnemonics/electrum-words.h" #include "rapidjson/document.h" #include "common/json_util.h" #include "ringct/rctSigs.h" #include "multisig/multisig.h" #include "wallet/wallet_args.h" #include "version.h" #include #ifdef WIN32 #include #include #endif #ifdef HAVE_READLINE #include "readline_buffer.h" #endif using namespace std; using namespace epee; using namespace cryptonote; using boost::lexical_cast; namespace po = boost::program_options; typedef cryptonote::simple_wallet sw; #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "wallet.simplewallet" #define EXTENDED_LOGS_FILE "wallet_details.log" #define DEFAULT_MIX 10 #define MIN_RING_SIZE 11 // Used to inform user about min ring size -- does not track actual protocol #define LOCK_IDLE_SCOPE() \ bool auto_refresh_enabled = m_auto_refresh_enabled.load(std::memory_order_relaxed); \ m_auto_refresh_enabled.store(false, std::memory_order_relaxed); \ /* stop any background refresh, and take over */ \ m_wallet->stop(); \ boost::unique_lock lock(m_idle_mutex); \ m_idle_cond.notify_all(); \ epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler([&](){ \ m_auto_refresh_enabled.store(auto_refresh_enabled, std::memory_order_relaxed); \ }) #define SCOPED_WALLET_UNLOCK() \ LOCK_IDLE_SCOPE(); \ boost::optional pwd_container = boost::none; \ if (m_wallet->ask_password() && !m_wallet->watch_only() && !(pwd_container = get_and_verify_password())) { return true; } \ tools::wallet_keys_unlocker unlocker(*m_wallet, pwd_container); enum TransferType { Transfer, TransferLocked, }; namespace { const std::array allowed_priority_strings = {{"default", "unimportant", "normal", "elevated", "priority"}}; const auto arg_wallet_file = wallet_args::arg_wallet_file(); const command_line::arg_descriptor arg_generate_new_wallet = {"generate-new-wallet", sw::tr("Generate new wallet and save it to "), ""}; const command_line::arg_descriptor arg_generate_from_device = {"generate-from-device", sw::tr("Generate new wallet from device and save it to "), ""}; const command_line::arg_descriptor arg_generate_from_view_key = {"generate-from-view-key", sw::tr("Generate incoming-only wallet from view key"), ""}; const command_line::arg_descriptor arg_generate_from_spend_key = {"generate-from-spend-key", sw::tr("Generate deterministic wallet from spend key"), ""}; const command_line::arg_descriptor arg_generate_from_keys = {"generate-from-keys", sw::tr("Generate wallet from private keys"), ""}; const command_line::arg_descriptor arg_generate_from_multisig_keys = {"generate-from-multisig-keys", sw::tr("Generate a master wallet from multisig wallet keys"), ""}; const auto arg_generate_from_json = wallet_args::arg_generate_from_json(); const command_line::arg_descriptor arg_mnemonic_language = {"mnemonic-language", sw::tr("Language for mnemonic"), ""}; const command_line::arg_descriptor arg_electrum_seed = {"electrum-seed", sw::tr("Specify Electrum seed for wallet recovery/creation"), ""}; const command_line::arg_descriptor arg_restore_deterministic_wallet = {"restore-deterministic-wallet", sw::tr("Recover wallet using Electrum-style mnemonic seed"), false}; const command_line::arg_descriptor arg_restore_multisig_wallet = {"restore-multisig-wallet", sw::tr("Recover multisig wallet using Electrum-style mnemonic seed"), false}; const command_line::arg_descriptor arg_non_deterministic = {"non-deterministic", sw::tr("Generate non-deterministic view and spend keys"), false}; const command_line::arg_descriptor arg_allow_mismatched_daemon_version = {"allow-mismatched-daemon-version", sw::tr("Allow communicating with a daemon that uses a different RPC version"), false}; const command_line::arg_descriptor arg_restore_height = {"restore-height", sw::tr("Restore from specific blockchain height"), 0}; const command_line::arg_descriptor arg_do_not_relay = {"do-not-relay", sw::tr("The newly created transaction will not be relayed to the monero network"), false}; const command_line::arg_descriptor arg_create_address_file = {"create-address-file", sw::tr("Create an address file for new wallets"), false}; const command_line::arg_descriptor arg_subaddress_lookahead = {"subaddress-lookahead", tools::wallet2::tr("Set subaddress lookahead sizes to :"), ""}; const command_line::arg_descriptor arg_use_english_language_names = {"use-english-language-names", sw::tr("Display English language names"), false}; const command_line::arg_descriptor< std::vector > arg_command = {"command", ""}; std::string input_line(const std::string& prompt) { #ifdef HAVE_READLINE rdln::suspend_readline pause_readline; #endif std::cout << prompt; std::string buf; #ifdef _WIN32 buf = tools::input_line_win(); #else std::getline(std::cin, buf); #endif return epee::string_tools::trim(buf); } epee::wipeable_string input_secure_line(const std::string& prompt) { #ifdef HAVE_READLINE rdln::suspend_readline pause_readline; #endif auto pwd_container = tools::password_container::prompt(false, prompt.c_str(), false); if (!pwd_container) { MERROR("Failed to read secure line"); return ""; } epee::wipeable_string buf = pwd_container->password(); buf.trim(); return buf; } boost::optional password_prompter(const char *prompt, bool verify) { #ifdef HAVE_READLINE rdln::suspend_readline pause_readline; #endif auto pwd_container = tools::password_container::prompt(verify, prompt); if (!pwd_container) { tools::fail_msg_writer() << sw::tr("failed to read wallet password"); } return pwd_container; } boost::optional default_password_prompter(bool verify) { return password_prompter(verify ? sw::tr("Enter a new password for the wallet") : sw::tr("Wallet password"), verify); } inline std::string interpret_rpc_response(bool ok, const std::string& status) { std::string err; if (ok) { if (status == CORE_RPC_STATUS_BUSY) { err = sw::tr("daemon is busy. Please try again later."); } else if (status != CORE_RPC_STATUS_OK) { err = status; } } else { err = sw::tr("possibly lost connection to daemon"); } return err; } tools::scoped_message_writer success_msg_writer(bool color = false) { return tools::scoped_message_writer(color ? console_color_green : console_color_default, false, std::string(), el::Level::Info); } tools::scoped_message_writer message_writer(epee::console_colors color = epee::console_color_default, bool bright = false) { return tools::scoped_message_writer(color, bright); } tools::scoped_message_writer fail_msg_writer() { return tools::scoped_message_writer(console_color_red, true, sw::tr("Error: "), el::Level::Error); } bool parse_bool(const std::string& s, bool& result) { if (s == "1" || command_line::is_yes(s)) { result = true; return true; } if (s == "0" || command_line::is_no(s)) { result = false; return true; } boost::algorithm::is_iequal ignore_case{}; if (boost::algorithm::equals("true", s, ignore_case) || boost::algorithm::equals(simple_wallet::tr("true"), s, ignore_case)) { result = true; return true; } if (boost::algorithm::equals("false", s, ignore_case) || boost::algorithm::equals(simple_wallet::tr("false"), s, ignore_case)) { result = false; return true; } return false; } template bool parse_bool_and_use(const std::string& s, F func) { bool r; if (parse_bool(s, r)) { func(r); return true; } else { fail_msg_writer() << sw::tr("invalid argument: must be either 0/1, true/false, y/n, yes/no"); return false; } } const struct { const char *name; tools::wallet2::RefreshType refresh_type; } refresh_type_names[] = { { "full", tools::wallet2::RefreshFull }, { "optimize-coinbase", tools::wallet2::RefreshOptimizeCoinbase }, { "optimized-coinbase", tools::wallet2::RefreshOptimizeCoinbase }, { "no-coinbase", tools::wallet2::RefreshNoCoinbase }, { "default", tools::wallet2::RefreshDefault }, }; bool parse_refresh_type(const std::string &s, tools::wallet2::RefreshType &refresh_type) { for (size_t n = 0; n < sizeof(refresh_type_names) / sizeof(refresh_type_names[0]); ++n) { if (s == refresh_type_names[n].name) { refresh_type = refresh_type_names[n].refresh_type; return true; } } fail_msg_writer() << cryptonote::simple_wallet::tr("failed to parse refresh type"); return false; } std::string get_refresh_type_name(tools::wallet2::RefreshType type) { for (size_t n = 0; n < sizeof(refresh_type_names) / sizeof(refresh_type_names[0]); ++n) { if (type == refresh_type_names[n].refresh_type) return refresh_type_names[n].name; } return "invalid"; } std::string get_version_string(uint32_t version) { return boost::lexical_cast(version >> 16) + "." + boost::lexical_cast(version & 0xffff); } std::string oa_prompter(const std::string &url, const std::vector &addresses, bool dnssec_valid) { if (addresses.empty()) return {}; // prompt user for confirmation. // inform user of DNSSEC validation status as well. std::string dnssec_str; if (dnssec_valid) { dnssec_str = sw::tr("DNSSEC validation passed"); } else { dnssec_str = sw::tr("WARNING: DNSSEC validation was unsuccessful, this address may not be correct!"); } std::stringstream prompt; prompt << sw::tr("For URL: ") << url << ", " << dnssec_str << std::endl << sw::tr(" Monero Address = ") << addresses[0] << std::endl << sw::tr("Is this OK? (Y/n) ") ; // prompt the user for confirmation given the dns query and dnssec status std::string confirm_dns_ok = input_line(prompt.str()); if (std::cin.eof()) { return {}; } if (!command_line::is_yes(confirm_dns_ok)) { std::cout << sw::tr("you have cancelled the transfer request") << std::endl; return {}; } return addresses[0]; } bool parse_subaddress_indices(const std::string& arg, std::set& subaddr_indices) { subaddr_indices.clear(); if (arg.substr(0, 6) != "index=") return false; std::string subaddr_indices_str_unsplit = arg.substr(6, arg.size() - 6); std::vector subaddr_indices_str; boost::split(subaddr_indices_str, subaddr_indices_str_unsplit, boost::is_any_of(",")); for (const auto& subaddr_index_str : subaddr_indices_str) { uint32_t subaddr_index; if(!epee::string_tools::get_xtype_from_string(subaddr_index, subaddr_index_str)) { fail_msg_writer() << sw::tr("failed to parse index: ") << subaddr_index_str; subaddr_indices.clear(); return false; } subaddr_indices.insert(subaddr_index); } return true; } boost::optional> parse_subaddress_lookahead(const std::string& str) { auto r = tools::parse_subaddress_lookahead(str); if (!r) fail_msg_writer() << sw::tr("invalid format for subaddress lookahead; must be :"); return r; } void handle_transfer_exception(const std::exception_ptr &e, bool trusted_daemon) { bool warn_of_possible_attack = !trusted_daemon; try { std::rethrow_exception(e); } catch (const tools::error::daemon_busy&) { fail_msg_writer() << sw::tr("daemon is busy. Please try again later."); } catch (const tools::error::no_connection_to_daemon&) { fail_msg_writer() << sw::tr("no connection to daemon. Please make sure daemon is running."); } catch (const tools::error::wallet_rpc_error& e) { LOG_ERROR("RPC error: " << e.to_string()); fail_msg_writer() << sw::tr("RPC error: ") << e.what(); } catch (const tools::error::get_outs_error &e) { fail_msg_writer() << sw::tr("failed to get random outputs to mix: ") << e.what(); } catch (const tools::error::not_enough_unlocked_money& e) { LOG_PRINT_L0(boost::format("not enough money to transfer, available only %s, sent amount %s") % print_money(e.available()) % print_money(e.tx_amount())); fail_msg_writer() << sw::tr("Not enough money in unlocked balance"); warn_of_possible_attack = false; } catch (const tools::error::not_enough_money& e) { LOG_PRINT_L0(boost::format("not enough money to transfer, available only %s, sent amount %s") % print_money(e.available()) % print_money(e.tx_amount())); fail_msg_writer() << sw::tr("Not enough money in unlocked balance"); warn_of_possible_attack = false; } catch (const tools::error::tx_not_possible& e) { LOG_PRINT_L0(boost::format("not enough money to transfer, available only %s, transaction amount %s = %s + %s (fee)") % print_money(e.available()) % print_money(e.tx_amount() + e.fee()) % print_money(e.tx_amount()) % print_money(e.fee())); fail_msg_writer() << sw::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) { auto writer = fail_msg_writer(); writer << sw::tr("not enough outputs for specified ring size") << " = " << (e.mixin_count() + 1) << ":"; for (std::pair outs_for_amount : e.scanty_outs()) { writer << "\n" << sw::tr("output amount") << " = " << print_money(outs_for_amount.first) << ", " << sw::tr("found outputs to use") << " = " << outs_for_amount.second; } writer << sw::tr("Please use sweep_unmixable."); } catch (const tools::error::tx_not_constructed&) { fail_msg_writer() << sw::tr("transaction was not constructed"); warn_of_possible_attack = false; } catch (const tools::error::tx_rejected& e) { fail_msg_writer() << (boost::format(sw::tr("transaction %s was rejected by daemon with status: ")) % get_transaction_hash(e.tx())) << e.status(); std::string reason = e.reason(); if (!reason.empty()) fail_msg_writer() << sw::tr("Reason: ") << reason; } 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() << sw::tr("one of destinations is zero"); warn_of_possible_attack = false; } catch (const tools::error::tx_too_big& e) { fail_msg_writer() << sw::tr("failed to find a suitable way to split transactions"); warn_of_possible_attack = false; } catch (const tools::error::transfer_error& e) { LOG_ERROR("unknown transfer error: " << e.to_string()); fail_msg_writer() << sw::tr("unknown transfer error: ") << e.what(); } catch (const tools::error::multisig_export_needed& e) { LOG_ERROR("Multisig error: " << e.to_string()); fail_msg_writer() << sw::tr("Multisig error: ") << e.what(); warn_of_possible_attack = false; } catch (const tools::error::wallet_internal_error& e) { LOG_ERROR("internal error: " << e.to_string()); fail_msg_writer() << sw::tr("internal error: ") << e.what(); } catch (const std::exception& e) { LOG_ERROR("unexpected error: " << e.what()); fail_msg_writer() << sw::tr("unexpected error: ") << e.what(); } if (warn_of_possible_attack) fail_msg_writer() << sw::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 transaction immediately. Alternatively, connect to another node so the original node cannot correlate information."); } bool check_file_overwrite(const std::string &filename) { boost::system::error_code errcode; if (boost::filesystem::exists(filename, errcode)) { if (boost::ends_with(filename, ".keys")) { fail_msg_writer() << boost::format(sw::tr("File %s likely stores wallet private keys! Use a different file name.")) % filename; return false; } return command_line::is_yes(input_line((boost::format(sw::tr("File %s already exists. Are you sure to overwrite it? (Y/Yes/N/No): ")) % filename).str())); } return true; } void print_secret_key(const crypto::secret_key &k) { static constexpr const char hex[] = u8"0123456789abcdef"; const uint8_t *ptr = (const uint8_t*)k.data; for (size_t i = 0, sz = sizeof(k); i < sz; ++i) { putchar(hex[*ptr >> 4]); putchar(hex[*ptr & 15]); ++ptr; } } } bool parse_priority(const std::string& arg, uint32_t& priority) { auto priority_pos = std::find( allowed_priority_strings.begin(), allowed_priority_strings.end(), arg); if(priority_pos != allowed_priority_strings.end()) { priority = std::distance(allowed_priority_strings.begin(), priority_pos); return true; } return false; } std::string join_priority_strings(const char *delimiter) { std::string s; for (size_t n = 0; n < allowed_priority_strings.size(); ++n) { if (!s.empty()) s += delimiter; s += allowed_priority_strings[n]; } return s; } std::string simple_wallet::get_commands_str() { std::stringstream ss; ss << tr("Commands: ") << ENDL; std::string usage = m_cmd_binder.get_usage(); boost::replace_all(usage, "\n", "\n "); usage.insert(0, " "); ss << usage << ENDL; return ss.str(); } std::string simple_wallet::get_command_usage(const std::vector &args) { std::pair documentation = m_cmd_binder.get_documentation(args); std::stringstream ss; if(documentation.first.empty()) { ss << tr("Unknown command: ") << args.front(); } else { std::string usage = documentation.second.empty() ? args.front() : documentation.first; std::string description = documentation.second.empty() ? documentation.first : documentation.second; usage.insert(0, " "); ss << tr("Command usage: ") << ENDL << usage << ENDL << ENDL; boost::replace_all(description, "\n", "\n "); description.insert(0, " "); ss << tr("Command description: ") << ENDL << description << ENDL; } return ss.str(); } bool simple_wallet::viewkey(const std::vector &args/* = std::vector()*/) { // don't log PAUSE_READLINE(); if (m_wallet->key_on_device()) { std::cout << "secret: On device. Not available" << std::endl; } else { SCOPED_WALLET_UNLOCK(); printf("secret: "); print_secret_key(m_wallet->get_account().get_keys().m_view_secret_key); putchar('\n'); } std::cout << "public: " << string_tools::pod_to_hex(m_wallet->get_account().get_keys().m_account_address.m_view_public_key) << std::endl; return true; } bool simple_wallet::spendkey(const std::vector &args/* = std::vector()*/) { if (m_wallet->watch_only()) { fail_msg_writer() << tr("wallet is watch-only and has no spend key"); return true; } // don't log PAUSE_READLINE(); if (m_wallet->key_on_device()) { std::cout << "secret: On device. Not available" << std::endl; } else { SCOPED_WALLET_UNLOCK(); printf("secret: "); print_secret_key(m_wallet->get_account().get_keys().m_spend_secret_key); putchar('\n'); } std::cout << "public: " << string_tools::pod_to_hex(m_wallet->get_account().get_keys().m_account_address.m_spend_public_key) << std::endl; return true; } bool simple_wallet::print_seed(bool encrypted) { bool success = false; epee::wipeable_string seed; bool ready, multisig; if (m_wallet->key_on_device()) { fail_msg_writer() << tr("command not supported by HW wallet"); return true; } if (m_wallet->watch_only()) { fail_msg_writer() << tr("wallet is watch-only and has no seed"); return true; } multisig = m_wallet->multisig(&ready); if (multisig) { if (!ready) { fail_msg_writer() << tr("wallet is multisig but not yet finalized"); return true; } } SCOPED_WALLET_UNLOCK(); if (!multisig && !m_wallet->is_deterministic()) { fail_msg_writer() << tr("wallet is non-deterministic and has no seed"); return true; } epee::wipeable_string seed_pass; if (encrypted) { auto pwd_container = password_prompter(tr("Enter optional seed offset passphrase, empty to see raw seed"), true); if (std::cin.eof() || !pwd_container) return true; seed_pass = pwd_container->password(); } if (multisig) success = m_wallet->get_multisig_seed(seed, seed_pass); else if (m_wallet->is_deterministic()) success = m_wallet->get_seed(seed, seed_pass); if (success) { print_seed(seed); } else { fail_msg_writer() << tr("Failed to retrieve seed"); } return true; } bool simple_wallet::seed(const std::vector &args/* = std::vector()*/) { return print_seed(false); } bool simple_wallet::encrypted_seed(const std::vector &args/* = std::vector()*/) { return print_seed(true); } bool simple_wallet::seed_set_language(const std::vector &args/* = std::vector()*/) { if (m_wallet->key_on_device()) { fail_msg_writer() << tr("command not supported by HW wallet"); return true; } if (m_wallet->multisig()) { fail_msg_writer() << tr("wallet is multisig and has no seed"); return true; } if (m_wallet->watch_only()) { fail_msg_writer() << tr("wallet is watch-only and has no seed"); return true; } epee::wipeable_string password; { SCOPED_WALLET_UNLOCK(); if (!m_wallet->is_deterministic()) { fail_msg_writer() << tr("wallet is non-deterministic and has no seed"); return true; } // we need the password, even if ask-password is unset if (!pwd_container) { pwd_container = get_and_verify_password(); if (pwd_container == boost::none) { fail_msg_writer() << tr("Incorrect password"); return true; } } password = pwd_container->password(); } std::string mnemonic_language = get_mnemonic_language(); if (mnemonic_language.empty()) return true; m_wallet->set_seed_language(std::move(mnemonic_language)); m_wallet->rewrite(m_wallet_file, password); return true; } bool simple_wallet::change_password(const std::vector &args) { const auto orig_pwd_container = get_and_verify_password(); if(orig_pwd_container == boost::none) { fail_msg_writer() << tr("Your original password was incorrect."); return true; } // prompts for a new password, pass true to verify the password const auto pwd_container = default_password_prompter(true); if(!pwd_container) return true; try { m_wallet->change_password(m_wallet_file, orig_pwd_container->password(), pwd_container->password()); } catch (const tools::error::wallet_logic_error& e) { fail_msg_writer() << tr("Error with wallet rewrite: ") << e.what(); return true; } return true; } bool simple_wallet::payment_id(const std::vector &args/* = std::vector()*/) { crypto::hash payment_id; if (args.size() > 0) { fail_msg_writer() << tr("usage: payment_id"); return true; } payment_id = crypto::rand(); success_msg_writer() << tr("Random payment ID: ") << payment_id; return true; } bool simple_wallet::print_fee_info(const std::vector &args/* = std::vector()*/) { if (!try_connect_to_daemon()) { fail_msg_writer() << tr("Cannot connect to daemon"); return true; } const bool per_byte = m_wallet->use_fork_rules(HF_VERSION_PER_BYTE_FEE); const uint64_t base_fee = m_wallet->get_base_fee(); const char *base = per_byte ? "byte" : "kB"; const uint64_t typical_size = per_byte ? 2500 : 13; const uint64_t size_granularity = per_byte ? 1 : 1024; message_writer() << (boost::format(tr("Current fee is %s %s per %s")) % print_money(base_fee) % cryptonote::get_unit(cryptonote::get_default_decimal_point()) % base).str(); std::vector fees; for (uint32_t priority = 1; priority <= 4; ++priority) { uint64_t mult = m_wallet->get_fee_multiplier(priority); fees.push_back(base_fee * typical_size * mult); } std::vector> blocks; try { uint64_t base_size = typical_size * size_granularity; blocks = m_wallet->estimate_backlog(base_size, base_size + size_granularity - 1, fees); } catch (const std::exception &e) { fail_msg_writer() << tr("Error: failed to estimate backlog array size: ") << e.what(); return true; } if (blocks.size() != 4) { fail_msg_writer() << tr("Error: bad estimated backlog array size"); return true; } for (uint32_t priority = 1; priority <= 4; ++priority) { uint64_t nblocks_low = blocks[priority - 1].first; uint64_t nblocks_high = blocks[priority - 1].second; if (nblocks_low > 0) { std::string msg; if (priority == m_wallet->get_default_priority() || (m_wallet->get_default_priority() == 0 && priority == 2)) msg = tr(" (current)"); uint64_t minutes_low = nblocks_low * DIFFICULTY_TARGET_V2 / 60, minutes_high = nblocks_high * DIFFICULTY_TARGET_V2 / 60; if (nblocks_high == nblocks_low) message_writer() << (boost::format(tr("%u block (%u minutes) backlog at priority %u%s")) % nblocks_low % minutes_low % priority % msg).str(); else message_writer() << (boost::format(tr("%u to %u block (%u to %u minutes) backlog at priority %u")) % nblocks_low % nblocks_high % minutes_low % minutes_high % priority).str(); } else message_writer() << tr("No backlog at priority ") << priority; } return true; } bool simple_wallet::prepare_multisig(const std::vector &args) { if (m_wallet->key_on_device()) { fail_msg_writer() << tr("command not supported by HW wallet"); return true; } if (m_wallet->multisig()) { fail_msg_writer() << tr("This wallet is already multisig"); return true; } if (m_wallet->watch_only()) { fail_msg_writer() << tr("wallet is watch-only and cannot be made multisig"); return true; } if(m_wallet->get_num_transfer_details()) { fail_msg_writer() << tr("This wallet has been used before, please use a new wallet to create a multisig wallet"); return true; } SCOPED_WALLET_UNLOCK(); std::string multisig_info = m_wallet->get_multisig_info(); success_msg_writer() << multisig_info; success_msg_writer() << tr("Send this multisig info to all other participants, then use make_multisig [...] with others' multisig info"); success_msg_writer() << tr("This includes the PRIVATE view key, so needs to be disclosed only to that multisig wallet's participants "); return true; } bool simple_wallet::make_multisig(const std::vector &args) { if (m_wallet->key_on_device()) { fail_msg_writer() << tr("command not supported by HW wallet"); return true; } if (m_wallet->multisig()) { fail_msg_writer() << tr("This wallet is already multisig"); return true; } if (m_wallet->watch_only()) { fail_msg_writer() << tr("wallet is watch-only and cannot be made multisig"); return true; } if(m_wallet->get_num_transfer_details()) { fail_msg_writer() << tr("This wallet has been used before, please use a new wallet to create a multisig wallet"); return true; } if (args.size() < 2) { fail_msg_writer() << tr("usage: make_multisig [...]"); return true; } // parse threshold uint32_t threshold; if (!string_tools::get_xtype_from_string(threshold, args[0])) { fail_msg_writer() << tr("Invalid threshold"); return true; } const auto orig_pwd_container = get_and_verify_password(); if(orig_pwd_container == boost::none) { fail_msg_writer() << tr("Your original password was incorrect."); return true; } LOCK_IDLE_SCOPE(); try { auto local_args = args; local_args.erase(local_args.begin()); std::string multisig_extra_info = m_wallet->make_multisig(orig_pwd_container->password(), local_args, threshold); if (!multisig_extra_info.empty()) { success_msg_writer() << tr("Another step is needed"); success_msg_writer() << multisig_extra_info; success_msg_writer() << tr("Send this multisig info to all other participants, then use exchange_multisig_keys [...] with others' multisig info"); return true; } } catch (const std::exception &e) { fail_msg_writer() << tr("Error creating multisig: ") << e.what(); return true; } uint32_t total; if (!m_wallet->multisig(NULL, &threshold, &total)) { fail_msg_writer() << tr("Error creating multisig: new wallet is not multisig"); return true; } success_msg_writer() << std::to_string(threshold) << "/" << total << tr(" multisig address: ") << m_wallet->get_account().get_public_address_str(m_wallet->nettype()); return true; } bool simple_wallet::finalize_multisig(const std::vector &args) { bool ready; if (m_wallet->key_on_device()) { fail_msg_writer() << tr("command not supported by HW wallet"); return true; } const auto pwd_container = get_and_verify_password(); if(pwd_container == boost::none) { fail_msg_writer() << tr("Your original password was incorrect."); return true; } if (!m_wallet->multisig(&ready)) { fail_msg_writer() << tr("This wallet is not multisig"); return true; } if (ready) { fail_msg_writer() << tr("This wallet is already finalized"); return true; } LOCK_IDLE_SCOPE(); if (args.size() < 2) { fail_msg_writer() << tr("usage: finalize_multisig [...]"); return true; } try { if (!m_wallet->finalize_multisig(pwd_container->password(), args)) { fail_msg_writer() << tr("Failed to finalize multisig"); return true; } } catch (const std::exception &e) { fail_msg_writer() << tr("Failed to finalize multisig: ") << e.what(); return true; } return true; } bool simple_wallet::exchange_multisig_keys(const std::vector &args) { bool ready; if (m_wallet->key_on_device()) { fail_msg_writer() << tr("command not supported by HW wallet"); return true; } if (!m_wallet->multisig(&ready)) { fail_msg_writer() << tr("This wallet is not multisig"); return true; } if (ready) { fail_msg_writer() << tr("This wallet is already finalized"); return true; } const auto orig_pwd_container = get_and_verify_password(); if(orig_pwd_container == boost::none) { fail_msg_writer() << tr("Your original password was incorrect."); return true; } if (args.size() < 2) { fail_msg_writer() << tr("usage: exchange_multisig_keys [...]"); return true; } try { std::string multisig_extra_info = m_wallet->exchange_multisig_keys(orig_pwd_container->password(), args); if (!multisig_extra_info.empty()) { message_writer() << tr("Another step is needed"); message_writer() << multisig_extra_info; message_writer() << tr("Send this multisig info to all other participants, then use exchange_multisig_keys [...] with others' multisig info"); return true; } else { uint32_t threshold, total; m_wallet->multisig(NULL, &threshold, &total); success_msg_writer() << tr("Multisig wallet has been successfully created. Current wallet type: ") << threshold << "/" << total; } } catch (const std::exception &e) { fail_msg_writer() << tr("Failed to perform multisig keys exchange: ") << e.what(); return true; } return true; } bool simple_wallet::export_multisig(const std::vector &args) { bool ready; if (m_wallet->key_on_device()) { fail_msg_writer() << tr("command not supported by HW wallet"); return true; } if (!m_wallet->multisig(&ready)) { fail_msg_writer() << tr("This wallet is not multisig"); return true; } if (!ready) { fail_msg_writer() << tr("This multisig wallet is not yet finalized"); return true; } if (args.size() != 1) { fail_msg_writer() << tr("usage: export_multisig_info "); return true; } const std::string filename = args[0]; if (m_wallet->confirm_export_overwrite() && !check_file_overwrite(filename)) return true; SCOPED_WALLET_UNLOCK(); try { cryptonote::blobdata ciphertext = m_wallet->export_multisig(); bool r = epee::file_io_utils::save_string_to_file(filename, ciphertext); if (!r) { fail_msg_writer() << tr("failed to save file ") << filename; return true; } } catch (const std::exception &e) { LOG_ERROR("Error exporting multisig info: " << e.what()); fail_msg_writer() << tr("Error exporting multisig info: ") << e.what(); return true; } success_msg_writer() << tr("Multisig info exported to ") << filename; return true; } bool simple_wallet::import_multisig(const std::vector &args) { bool ready; uint32_t threshold, total; if (m_wallet->key_on_device()) { fail_msg_writer() << tr("command not supported by HW wallet"); return true; } if (!m_wallet->multisig(&ready, &threshold, &total)) { fail_msg_writer() << tr("This wallet is not multisig"); return true; } if (!ready) { fail_msg_writer() << tr("This multisig wallet is not yet finalized"); return true; } if (args.size() < threshold - 1) { fail_msg_writer() << tr("usage: import_multisig_info [...] - one for each other participant"); return true; } std::vector info; for (size_t n = 0; n < args.size(); ++n) { const std::string filename = args[n]; std::string data; bool r = epee::file_io_utils::load_file_to_string(filename, data); if (!r) { fail_msg_writer() << tr("failed to read file ") << filename; return true; } info.push_back(std::move(data)); } SCOPED_WALLET_UNLOCK(); // all read and parsed, actually import try { m_in_manual_refresh.store(true, std::memory_order_relaxed); epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler([&](){m_in_manual_refresh.store(false, std::memory_order_relaxed);}); size_t n_outputs = m_wallet->import_multisig(info); // Clear line "Height xxx of xxx" std::cout << "\r \r"; success_msg_writer() << tr("Multisig info imported"); } catch (const std::exception &e) { fail_msg_writer() << tr("Failed to import multisig info: ") << e.what(); return true; } if (m_wallet->is_trusted_daemon()) { try { m_wallet->rescan_spent(); } catch (const std::exception &e) { message_writer() << tr("Failed to update spent status after importing multisig info: ") << e.what(); } } else { message_writer() << tr("Untrusted daemon, spent status may be incorrect. Use a trusted daemon and run \"rescan_spent\""); } return true; } bool simple_wallet::accept_loaded_tx(const tools::wallet2::multisig_tx_set &txs) { std::string extra_message; return accept_loaded_tx([&txs](){return txs.m_ptx.size();}, [&txs](size_t n)->const tools::wallet2::tx_construction_data&{return txs.m_ptx[n].construction_data;}, extra_message); } bool simple_wallet::sign_multisig(const std::vector &args) { bool ready; if (m_wallet->key_on_device()) { fail_msg_writer() << tr("command not supported by HW wallet"); return true; } if(!m_wallet->multisig(&ready)) { fail_msg_writer() << tr("This is not a multisig wallet"); return true; } if (!ready) { fail_msg_writer() << tr("This multisig wallet is not yet finalized"); return true; } if (args.size() != 1) { fail_msg_writer() << tr("usage: sign_multisig "); return true; } SCOPED_WALLET_UNLOCK(); std::string filename = args[0]; std::vector txids; uint32_t signers = 0; try { bool r = m_wallet->sign_multisig_tx_from_file(filename, txids, [&](const tools::wallet2::multisig_tx_set &tx){ signers = tx.m_signers.size(); return accept_loaded_tx(tx); }); if (!r) { fail_msg_writer() << tr("Failed to sign multisig transaction"); return true; } } catch (const tools::error::multisig_export_needed& e) { fail_msg_writer() << tr("Multisig error: ") << e.what(); return true; } catch (const std::exception &e) { fail_msg_writer() << tr("Failed to sign multisig transaction: ") << e.what(); return true; } if (txids.empty()) { uint32_t threshold; m_wallet->multisig(NULL, &threshold); uint32_t signers_needed = threshold - signers - 1; success_msg_writer(true) << tr("Transaction successfully signed to file ") << filename << ", " << signers_needed << " more signer(s) needed"; return true; } else { std::string txids_as_text; for (const auto &txid: txids) { if (!txids_as_text.empty()) txids_as_text += (", "); txids_as_text += epee::string_tools::pod_to_hex(txid); } success_msg_writer(true) << tr("Transaction successfully signed to file ") << filename << ", txid " << txids_as_text; success_msg_writer(true) << tr("It may be relayed to the network with submit_multisig"); } return true; } bool simple_wallet::submit_multisig(const std::vector &args) { bool ready; uint32_t threshold; if (m_wallet->key_on_device()) { fail_msg_writer() << tr("command not supported by HW wallet"); return true; } if (!m_wallet->multisig(&ready, &threshold)) { fail_msg_writer() << tr("This is not a multisig wallet"); return true; } if (!ready) { fail_msg_writer() << tr("This multisig wallet is not yet finalized"); return true; } if (args.size() != 1) { fail_msg_writer() << tr("usage: submit_multisig "); return true; } if (!try_connect_to_daemon()) return true; SCOPED_WALLET_UNLOCK(); std::string filename = args[0]; try { tools::wallet2::multisig_tx_set txs; bool r = m_wallet->load_multisig_tx_from_file(filename, txs, [&](const tools::wallet2::multisig_tx_set &tx){ return accept_loaded_tx(tx); }); if (!r) { fail_msg_writer() << tr("Failed to load multisig transaction from file"); return true; } if (txs.m_signers.size() < threshold) { fail_msg_writer() << (boost::format(tr("Multisig transaction signed by only %u signers, needs %u more signatures")) % txs.m_signers.size() % (threshold - txs.m_signers.size())).str(); return true; } // actually commit the transactions for (auto &ptx: txs.m_ptx) { m_wallet->commit_tx(ptx); success_msg_writer(true) << tr("Transaction successfully submitted, transaction ") << get_transaction_hash(ptx.tx) << ENDL << tr("You can check its status by using the `show_transfers` command."); } } catch (const std::exception &e) { handle_transfer_exception(std::current_exception(), m_wallet->is_trusted_daemon()); } catch (...) { LOG_ERROR("unknown error"); fail_msg_writer() << tr("unknown error"); } return true; } bool simple_wallet::export_raw_multisig(const std::vector &args) { bool ready; uint32_t threshold; if (m_wallet->key_on_device()) { fail_msg_writer() << tr("command not supported by HW wallet"); return true; } if (!m_wallet->multisig(&ready, &threshold)) { fail_msg_writer() << tr("This is not a multisig wallet"); return true; } if (!ready) { fail_msg_writer() << tr("This multisig wallet is not yet finalized"); return true; } if (args.size() != 1) { fail_msg_writer() << tr("usage: export_raw_multisig "); return true; } std::string filename = args[0]; if (m_wallet->confirm_export_overwrite() && !check_file_overwrite(filename)) return true; SCOPED_WALLET_UNLOCK(); try { tools::wallet2::multisig_tx_set txs; bool r = m_wallet->load_multisig_tx_from_file(filename, txs, [&](const tools::wallet2::multisig_tx_set &tx){ return accept_loaded_tx(tx); }); if (!r) { fail_msg_writer() << tr("Failed to load multisig transaction from file"); return true; } if (txs.m_signers.size() < threshold) { fail_msg_writer() << (boost::format(tr("Multisig transaction signed by only %u signers, needs %u more signatures")) % txs.m_signers.size() % (threshold - txs.m_signers.size())).str(); return true; } // save the transactions std::string filenames; for (auto &ptx: txs.m_ptx) { const crypto::hash txid = cryptonote::get_transaction_hash(ptx.tx); const std::string filename = std::string("raw_multisig_monero_tx_") + epee::string_tools::pod_to_hex(txid); if (!filenames.empty()) filenames += ", "; filenames += filename; if (!epee::file_io_utils::save_string_to_file(filename, cryptonote::tx_to_blob(ptx.tx))) { fail_msg_writer() << tr("Failed to export multisig transaction to file ") << filename; return true; } } success_msg_writer() << tr("Saved exported multisig transaction file(s): ") << filenames; } catch (const std::exception& e) { LOG_ERROR("unexpected error: " << e.what()); fail_msg_writer() << tr("unexpected error: ") << e.what(); } catch (...) { LOG_ERROR("Unknown error"); fail_msg_writer() << tr("unknown error"); } return true; } bool simple_wallet::print_ring(const std::vector &args) { crypto::key_image key_image; crypto::hash txid; if (args.size() != 1) { fail_msg_writer() << tr("usage: print_ring "); 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 ring; std::vector>> 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 &args) { crypto::key_image key_image; // try filename first if (args.size() == 1) { if (!epee::file_io_utils::is_file_exist(args[0])) { fail_msg_writer() << tr("File doesn't exist"); return true; } char str[4096]; std::unique_ptr 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; char key_image_str[65], type_str[9]; int read_after_key_image = 0, read = 0; int fields = sscanf(str, "%64[abcdefABCDEF0123456789] %n%8s %n", key_image_str, &read_after_key_image, type_str, &read); if (fields != 2) { fail_msg_writer() << tr("Invalid ring specification: ") << str; continue; } key_image_str[64] = 0; type_str[8] = 0; crypto::key_image key_image; if (read_after_key_image == 0 || !epee::string_tools::hex_to_pod(key_image_str, key_image)) { fail_msg_writer() << tr("Invalid key image: ") << str; continue; } if (read == read_after_key_image+8 || (strcmp(type_str, "absolute") && strcmp(type_str, "relative"))) { fail_msg_writer() << tr("Invalid ring type, expected relative or abosolute: ") << str; continue; } bool relative = !strcmp(type_str, "relative"); if (read < 0 || (size_t)read > strlen(str)) { fail_msg_writer() << tr("Error reading line: ") << str; continue; } bool valid = true; std::vector ring; const char *ptr = str + read; while (*ptr) { unsigned long offset; int elements = sscanf(ptr, "%lu %n", &offset, &read); if (elements == 0 || read <= 0 || (size_t)read > strlen(str)) { fail_msg_writer() << tr("Error reading line: ") << str; valid = false; break; } ring.push_back(offset); ptr += read; } if (!valid) continue; if (ring.empty()) { fail_msg_writer() << tr("Invalid ring: ") << str; continue; } if (relative) { for (size_t n = 1; n < ring.size(); ++n) { if (ring[n] <= 0) { fail_msg_writer() << tr("Invalid relative ring: ") << str; valid = false; break; } } } else { for (size_t n = 1; n < ring.size(); ++n) { if (ring[n] <= ring[n-1]) { fail_msg_writer() << tr("Invalid absolute ring: ") << str; valid = false; break; } } } if (!valid) continue; if (!m_wallet->set_ring(key_image, ring, relative)) fail_msg_writer() << tr("Failed to set ring for key image: ") << key_image << ". " << tr("Continuing."); } f.reset(); } return true; } if (args.size() < 3) { fail_msg_writer() << tr("usage: set_ring | ( absolute|relative [...] )"); 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 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::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 &args) { uint64_t amount = std::numeric_limits::max(), offset, num_offsets; if (args.size() == 0) { fail_msg_writer() << tr("usage: mark_output_spent / | [add]"); return true; } try { if (sscanf(args[0].c_str(), "%" PRIu64 "/%" PRIu64, &amount, &offset) == 2) { m_wallet->blackball_output(std::make_pair(amount, offset)); } else if (epee::file_io_utils::is_file_exist(args[0])) { std::vector> outputs; char str[256]; std::unique_ptr 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; if (sscanf(str, "@%" PRIu64, &amount) == 1) { continue; } if (amount == std::numeric_limits::max()) { fail_msg_writer() << tr("First line is not an amount"); return true; } if (sscanf(str, "%" PRIu64 "*%" PRIu64, &offset, &num_offsets) == 2 && num_offsets <= std::numeric_limits::max() - offset) { while (num_offsets--) outputs.push_back(std::make_pair(amount, offset++)); } else if (sscanf(str, "%" PRIu64, &offset) == 1) { outputs.push_back(std::make_pair(amount, offset)); } else { fail_msg_writer() << tr("Invalid output: ") << 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 output key, and file doesn't exist"); return true; } } catch (const std::exception &e) { fail_msg_writer() << tr("Failed to mark output spent: ") << e.what(); } return true; } bool simple_wallet::unblackball(const std::vector &args) { std::pair output; if (args.size() != 1) { fail_msg_writer() << tr("usage: mark_output_unspent /"); return true; } if (sscanf(args[0].c_str(), "%" PRIu64 "/%" PRIu64, &output.first, &output.second) != 2) { fail_msg_writer() << tr("Invalid output"); return true; } try { m_wallet->unblackball_output(output); } catch (const std::exception &e) { fail_msg_writer() << tr("Failed to mark output unspent: ") << e.what(); } return true; } bool simple_wallet::blackballed(const std::vector &args) { std::pair output; if (args.size() != 1) { fail_msg_writer() << tr("usage: is_output_spent /"); return true; } if (sscanf(args[0].c_str(), "%" PRIu64 "/%" PRIu64, &output.first, &output.second) != 2) { fail_msg_writer() << tr("Invalid output"); return true; } try { if (m_wallet->is_output_blackballed(output)) message_writer() << tr("Spent: ") << output.first << "/" << output.second; else message_writer() << tr("Not spent: ") << output.first << "/" << output.second; } catch (const std::exception &e) { fail_msg_writer() << tr("Failed to check whether output is spent: ") << e.what(); } return true; } bool simple_wallet::save_known_rings(const std::vector &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::version(const std::vector &args) { message_writer() << "Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")"; return true; } bool simple_wallet::set_always_confirm_transfers(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->always_confirm_transfers(r); m_wallet->rewrite(m_wallet_file, pwd_container->password()); }); } return true; } bool simple_wallet::set_print_ring_members(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->print_ring_members(r); m_wallet->rewrite(m_wallet_file, pwd_container->password()); }); } return true; } bool simple_wallet::set_store_tx_info(const std::vector &args/* = std::vector()*/) { if (m_wallet->watch_only()) { fail_msg_writer() << tr("wallet is watch-only and cannot transfer"); return true; } const auto pwd_container = get_and_verify_password(); if (pwd_container) { parse_bool_and_use(args[1], [&](bool r) { m_wallet->store_tx_info(r); m_wallet->rewrite(m_wallet_file, pwd_container->password()); }); } return true; } bool simple_wallet::set_default_ring_size(const std::vector &args/* = std::vector()*/) { if (m_wallet->watch_only()) { fail_msg_writer() << tr("wallet is watch-only and cannot transfer"); return true; } try { if (strchr(args[1].c_str(), '-')) { fail_msg_writer() << tr("ring size must be an integer >= ") << MIN_RING_SIZE; return true; } uint32_t ring_size = boost::lexical_cast(args[1]); if (ring_size < MIN_RING_SIZE && ring_size != 0) { fail_msg_writer() << tr("ring size must be an integer >= ") << MIN_RING_SIZE; return true; } if (ring_size != 0 && ring_size != DEFAULT_MIX+1) message_writer() << tr("WARNING: this is a non default ring size, which may harm your privacy. Default is recommended."); else if (ring_size == DEFAULT_MIX) message_writer() << tr("WARNING: from v8, ring size will be fixed and this setting will be ignored."); const auto pwd_container = get_and_verify_password(); if (pwd_container) { m_wallet->default_mixin(ring_size > 0 ? ring_size - 1 : 0); m_wallet->rewrite(m_wallet_file, pwd_container->password()); } return true; } catch(const boost::bad_lexical_cast &) { fail_msg_writer() << tr("ring size must be an integer >= ") << MIN_RING_SIZE; return true; } catch(...) { fail_msg_writer() << tr("could not change default ring size"); return true; } } bool simple_wallet::set_default_priority(const std::vector &args/* = std::vector()*/) { uint32_t priority = 0; try { if (strchr(args[1].c_str(), '-')) { fail_msg_writer() << tr("priority must be either 0, 1, 2, 3, or 4, or one of: ") << join_priority_strings(", "); return true; } if (args[1] == "0") { priority = 0; } else { bool found = false; for (size_t n = 0; n < allowed_priority_strings.size(); ++n) { if (allowed_priority_strings[n] == args[1]) { found = true; priority = n; } } if (!found) { priority = boost::lexical_cast(args[1]); if (priority < 1 || priority > 4) { fail_msg_writer() << tr("priority must be either 0, 1, 2, 3, or 4, or one of: ") << join_priority_strings(", "); return true; } } } const auto pwd_container = get_and_verify_password(); if (pwd_container) { m_wallet->set_default_priority(priority); m_wallet->rewrite(m_wallet_file, pwd_container->password()); } return true; } catch(const boost::bad_lexical_cast &) { fail_msg_writer() << tr("priority must be either 0, 1, 2, 3, or 4, or one of: ") << join_priority_strings(", "); return true; } catch(...) { fail_msg_writer() << tr("could not change default priority"); return true; } } bool simple_wallet::set_auto_refresh(const std::vector &args/* = std::vector()*/) { const auto pwd_container = get_and_verify_password(); if (pwd_container) { parse_bool_and_use(args[1], [&](bool auto_refresh) { m_auto_refresh_enabled.store(false, std::memory_order_relaxed); m_wallet->auto_refresh(auto_refresh); m_idle_mutex.lock(); m_auto_refresh_enabled.store(auto_refresh, std::memory_order_relaxed); m_idle_cond.notify_one(); m_idle_mutex.unlock(); m_wallet->rewrite(m_wallet_file, pwd_container->password()); }); } return true; } bool simple_wallet::set_refresh_type(const std::vector &args/* = std::vector()*/) { tools::wallet2::RefreshType refresh_type; if (!parse_refresh_type(args[1], refresh_type)) { return true; } const auto pwd_container = get_and_verify_password(); if (pwd_container) { m_wallet->set_refresh_type(refresh_type); m_wallet->rewrite(m_wallet_file, pwd_container->password()); } return true; } bool simple_wallet::set_confirm_missing_payment_id(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->confirm_missing_payment_id(r); m_wallet->rewrite(m_wallet_file, pwd_container->password()); }); } return true; } bool simple_wallet::set_ask_password(const std::vector &args/* = std::vector()*/) { const auto pwd_container = get_and_verify_password(); if (pwd_container) { tools::wallet2::AskPasswordType ask = tools::wallet2::AskPasswordToDecrypt; if (args[1] == "never" || args[1] == "0") ask = tools::wallet2::AskPasswordNever; else if (args[1] == "action" || args[1] == "1") ask = tools::wallet2::AskPasswordOnAction; else if (args[1] == "encrypt" || args[1] == "decrypt" || args[1] == "2") ask = tools::wallet2::AskPasswordToDecrypt; else { fail_msg_writer() << tr("invalid argument: must be either 0/never, 1/action, or 2/encrypt/decrypt"); return true; } const tools::wallet2::AskPasswordType cur_ask = m_wallet->ask_password(); if (!m_wallet->watch_only()) { if (cur_ask == tools::wallet2::AskPasswordToDecrypt && ask != tools::wallet2::AskPasswordToDecrypt) m_wallet->decrypt_keys(pwd_container->password()); else if (cur_ask != tools::wallet2::AskPasswordToDecrypt && ask == tools::wallet2::AskPasswordToDecrypt) m_wallet->encrypt_keys(pwd_container->password()); } m_wallet->ask_password(ask); m_wallet->rewrite(m_wallet_file, pwd_container->password()); } return true; } bool simple_wallet::set_unit(const std::vector &args/* = std::vector()*/) { const std::string &unit = args[1]; unsigned int decimal_point = CRYPTONOTE_DISPLAY_DECIMAL_POINT; if (unit == "monero") decimal_point = CRYPTONOTE_DISPLAY_DECIMAL_POINT; else if (unit == "millinero") decimal_point = CRYPTONOTE_DISPLAY_DECIMAL_POINT - 3; else if (unit == "micronero") decimal_point = CRYPTONOTE_DISPLAY_DECIMAL_POINT - 6; else if (unit == "nanonero") decimal_point = CRYPTONOTE_DISPLAY_DECIMAL_POINT - 9; else if (unit == "piconero") decimal_point = 0; else { fail_msg_writer() << tr("invalid unit"); return true; } const auto pwd_container = get_and_verify_password(); if (pwd_container) { cryptonote::set_default_decimal_point(decimal_point); m_wallet->rewrite(m_wallet_file, pwd_container->password()); } return true; } bool simple_wallet::set_min_output_count(const std::vector &args/* = std::vector()*/) { uint32_t count; if (!string_tools::get_xtype_from_string(count, args[1])) { fail_msg_writer() << tr("invalid count: must be an unsigned integer"); return true; } const auto pwd_container = get_and_verify_password(); if (pwd_container) { m_wallet->set_min_output_count(count); m_wallet->rewrite(m_wallet_file, pwd_container->password()); } return true; } bool simple_wallet::set_min_output_value(const std::vector &args/* = std::vector()*/) { uint64_t value; if (!cryptonote::parse_amount(value, args[1])) { fail_msg_writer() << tr("invalid value"); return true; } const auto pwd_container = get_and_verify_password(); if (pwd_container) { m_wallet->set_min_output_value(value); m_wallet->rewrite(m_wallet_file, pwd_container->password()); } return true; } bool simple_wallet::set_merge_destinations(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->merge_destinations(r); m_wallet->rewrite(m_wallet_file, pwd_container->password()); }); } return true; } bool simple_wallet::set_confirm_backlog(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->confirm_backlog(r); m_wallet->rewrite(m_wallet_file, pwd_container->password()); }); } return true; } bool simple_wallet::set_confirm_backlog_threshold(const std::vector &args/* = std::vector()*/) { uint32_t threshold; if (!string_tools::get_xtype_from_string(threshold, args[1])) { fail_msg_writer() << tr("invalid count: must be an unsigned integer"); return true; } const auto pwd_container = get_and_verify_password(); if (pwd_container) { m_wallet->set_confirm_backlog_threshold(threshold); m_wallet->rewrite(m_wallet_file, pwd_container->password()); } return true; } bool simple_wallet::set_confirm_export_overwrite(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->confirm_export_overwrite(r); m_wallet->rewrite(m_wallet_file, pwd_container->password()); }); } return true; } bool simple_wallet::set_refresh_from_block_height(const std::vector &args/* = std::vector()*/) { const auto pwd_container = get_and_verify_password(); if (pwd_container) { uint64_t height; if (!epee::string_tools::get_xtype_from_string(height, args[1])) { fail_msg_writer() << tr("Invalid height"); return true; } m_wallet->set_refresh_from_block_height(height); m_wallet->rewrite(m_wallet_file, pwd_container->password()); } return true; } bool simple_wallet::set_auto_low_priority(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->auto_low_priority(r); m_wallet->rewrite(m_wallet_file, pwd_container->password()); }); } 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::set_subaddress_lookahead(const std::vector &args/* = std::vector()*/) { const auto pwd_container = get_and_verify_password(); if (pwd_container) { auto lookahead = parse_subaddress_lookahead(args[1]); if (lookahead) { m_wallet->set_subaddress_lookahead(lookahead->first, lookahead->second); m_wallet->rewrite(m_wallet_file, pwd_container->password()); } } return true; } bool simple_wallet::set_segregation_height(const std::vector &args/* = std::vector()*/) { const auto pwd_container = get_and_verify_password(); if (pwd_container) { uint64_t height; if (!epee::string_tools::get_xtype_from_string(height, args[1])) { fail_msg_writer() << tr("Invalid height"); return true; } m_wallet->segregation_height(height); m_wallet->rewrite(m_wallet_file, pwd_container->password()); } return true; } bool simple_wallet::set_ignore_fractional_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->ignore_fractional_outputs(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()) { success_msg_writer() << get_commands_str(); } else { success_msg_writer() << get_command_usage(args); } return true; } simple_wallet::simple_wallet() : m_allow_mismatched_daemon_version(false) , m_refresh_progress_reporter(*this) , m_idle_run(true) , m_auto_refresh_enabled(false) , m_auto_refresh_refreshing(false) , m_in_manual_refresh(false) , m_current_subaddress_account(0) { m_cmd_binder.set_handler("start_mining", boost::bind(&simple_wallet::start_mining, this, _1), tr("start_mining [] [bg_mining] [ignore_battery]"), tr("Start mining in the daemon (bg_mining and ignore_battery are optional booleans).")); m_cmd_binder.set_handler("stop_mining", boost::bind(&simple_wallet::stop_mining, this, _1), tr("Stop mining in the daemon.")); m_cmd_binder.set_handler("set_daemon", boost::bind(&simple_wallet::set_daemon, this, _1), tr("set_daemon [:] [trusted|untrusted]"), tr("Set another daemon to connect to.")); m_cmd_binder.set_handler("save_bc", boost::bind(&simple_wallet::save_bc, this, _1), tr("Save the current blockchain data.")); m_cmd_binder.set_handler("refresh", boost::bind(&simple_wallet::refresh, this, _1), tr("Synchronize the transactions and balance.")); m_cmd_binder.set_handler("balance", boost::bind(&simple_wallet::show_balance, this, _1), tr("balance [detail]"), tr("Show the wallet's balance of the currently selected account.")); m_cmd_binder.set_handler("incoming_transfers", boost::bind(&simple_wallet::show_incoming_transfers, this, _1), tr("incoming_transfers [available|unavailable] [verbose] [index=[,[,...]]]"), tr("Show the incoming transfers, all or filtered by availability and address index.")); m_cmd_binder.set_handler("payments", boost::bind(&simple_wallet::show_payments, this, _1), tr("payments [ ... ]"), tr("Show the payments for the given payment IDs.")); m_cmd_binder.set_handler("bc_height", boost::bind(&simple_wallet::show_blockchain_height, this, _1), tr("Show the blockchain height.")); m_cmd_binder.set_handler("transfer", boost::bind(&simple_wallet::transfer, this, _1), tr("transfer [index=[,,...]] [] [] ( |
) []"), tr("Transfer to
. If the parameter \"index=[,,...]\" is specified, the wallet uses outputs received by addresses of those indices. If omitted, the wallet randomly chooses address indices to be used. In any case, it tries its best not to combine outputs across multiple addresses. is the priority of the transaction. The higher the priority, the higher the transaction fee. Valid values in priority order (from lowest to highest) are: unimportant, normal, elevated, priority. If omitted, the default value (see the command \"set priority\") is used. is the number of inputs to include for untraceability. Multiple payments can be made at once by adding URI_2 or etcetera (before the payment ID, if it's included)")); m_cmd_binder.set_handler("locked_transfer", boost::bind(&simple_wallet::locked_transfer, this, _1), tr("locked_transfer [index=[,,...]] [] [] ( | ) []"), tr("Transfer to
and lock it for (max. 1000000). If the parameter \"index=[,,...]\" is specified, the wallet uses outputs received by addresses of those indices. If omitted, the wallet randomly chooses address indices to be used. In any case, it tries its best not to combine outputs across multiple addresses. is the priority of the transaction. The higher the priority, the higher the transaction fee. Valid values in priority order (from lowest to highest) are: unimportant, normal, elevated, priority. If omitted, the default value (see the command \"set priority\") is used. is the number of inputs to include for untraceability. Multiple payments can be made at once by adding URI_2 or etcetera (before the payment ID, if it's included)")); m_cmd_binder.set_handler("locked_sweep_all", boost::bind(&simple_wallet::locked_sweep_all, this, _1), tr("locked_sweep_all [index=[,,...]] [] []
[]"), tr("Send all unlocked balance to an address and lock it for (max. 1000000). If the parameter \"index[,,...]\" is specified, the wallet sweeps outputs received by those address indices. If omitted, the wallet randomly chooses an address index to be used. is the priority of the sweep. The higher the priority, the higher the transaction fee. Valid values in priority order (from lowest to highest) are: unimportant, normal, elevated, priority. If omitted, the default value (see the command \"set priority\") is used. is the number of inputs to include for untraceability.")); m_cmd_binder.set_handler("sweep_unmixable", boost::bind(&simple_wallet::sweep_unmixable, this, _1), tr("Send all unmixable outputs to yourself with ring_size 1")); m_cmd_binder.set_handler("sweep_all", boost::bind(&simple_wallet::sweep_all, this, _1), tr("sweep_all [index=[,,...]] [] [] [outputs=]
[]"), tr("Send all unlocked balance to an address. If the parameter \"index[,,...]\" is specified, the wallet sweeps outputs received by those address indices. If omitted, the wallet randomly chooses an address index to be used. If the parameter \"outputs=\" is specified and N > 0, wallet splits the transaction into N even outputs.")); m_cmd_binder.set_handler("sweep_below", boost::bind(&simple_wallet::sweep_below, this, _1), tr("sweep_below [index=[,,...]] [] []
[]"), tr("Send all unlocked outputs below the threshold to an address.")); m_cmd_binder.set_handler("sweep_single", boost::bind(&simple_wallet::sweep_single, this, _1), tr("sweep_single [] [] [outputs=]
[]"), tr("Send a single output of the given key image to an address without change.")); m_cmd_binder.set_handler("donate", boost::bind(&simple_wallet::donate, this, _1), tr("donate [index=[,,...]] [] [] []"), tr("Donate to the development team (donate.getmonero.org).")); m_cmd_binder.set_handler("sign_transfer", boost::bind(&simple_wallet::sign_transfer, this, _1), tr("sign_transfer [export_raw]"), tr("Sign a transaction from a file. If the parameter \"export_raw\" is specified, transaction raw hex data suitable for the daemon RPC /sendrawtransaction is exported.")); m_cmd_binder.set_handler("submit_transfer", boost::bind(&simple_wallet::submit_transfer, this, _1), tr("Submit a signed transaction from a file.")); m_cmd_binder.set_handler("set_log", boost::bind(&simple_wallet::set_log, this, _1), tr("set_log |{+,-,}"), tr("Change the current log detail (level must be <0-4>).")); m_cmd_binder.set_handler("account", boost::bind(&simple_wallet::account, this, _1), tr("account\n" " account new