diff options
Diffstat (limited to 'src/simplewallet/simplewallet.cpp')
-rw-r--r-- | src/simplewallet/simplewallet.cpp | 854 |
1 files changed, 456 insertions, 398 deletions
diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 37db00bc1..a307f9d3d 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -80,6 +80,8 @@ typedef cryptonote::simple_wallet sw; #define DEFAULT_MIX 4 +#define MIN_RING_SIZE 5 // Used to inform user about min ring size -- does not track actual protocol + #define OUTPUT_EXPORT_FILE_MAGIC "Monero output export\003" #define LOCK_IDLE_SCOPE() \ @@ -363,6 +365,27 @@ std::string simple_wallet::get_commands_str() return ss.str(); } +std::string simple_wallet::get_command_usage(const std::vector<std::string> &args) +{ + std::pair<std::string, std::string> 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<std::string> &args/* = std::vector<std::string>()*/) { if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } @@ -619,27 +642,27 @@ bool simple_wallet::set_default_ring_size(const std::vector<std::string> &args/* { if (strchr(args[1].c_str(), '-')) { - fail_msg_writer() << tr("ring size must be an integer >= 3"); + fail_msg_writer() << tr("ring size must be an integer >= ") << MIN_RING_SIZE; return true; } uint32_t ring_size = boost::lexical_cast<uint32_t>(args[1]); - if (ring_size < 3 && ring_size != 0) + if (ring_size < MIN_RING_SIZE && ring_size != 0) { - fail_msg_writer() << tr("ring size must be an integer >= 3"); + fail_msg_writer() << tr("ring size must be an integer >= ") << MIN_RING_SIZE; return true; } const auto pwd_container = get_and_verify_password(); if (pwd_container) { - m_wallet->default_mixin(ring_size - 1); + 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 >= 3"); + fail_msg_writer() << tr("ring size must be an integer >= ") << MIN_RING_SIZE; return true; } catch(...) @@ -883,7 +906,14 @@ bool simple_wallet::set_refresh_from_block_height(const std::vector<std::string> bool simple_wallet::help(const std::vector<std::string> &args/* = std::vector<std::string>()*/) { - success_msg_writer() << get_commands_str(); + if(args.empty()) + { + success_msg_writer() << get_commands_str(); + } + else + { + success_msg_writer() << get_command_usage(args); + } return true; } @@ -896,61 +926,244 @@ simple_wallet::simple_wallet() , 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 [<number_of_threads>] [bg_mining] [ignore_battery] - Start mining in 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 daemon")); - m_cmd_binder.set_handler("save_bc", boost::bind(&simple_wallet::save_bc, this, _1), tr("Save current blockchain data")); - m_cmd_binder.set_handler("refresh", boost::bind(&simple_wallet::refresh, this, _1), tr("Synchronize transactions and balance")); - m_cmd_binder.set_handler("balance", boost::bind(&simple_wallet::show_balance, this, _1), tr("balance [detail] - Show wallet balance of 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=<N1>[,<N2>,...]] - Show 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 <PID_1> [<PID_2> ... <PID_N>] - Show payments for given payment ID[s]")); - m_cmd_binder.set_handler("bc_height", boost::bind(&simple_wallet::show_blockchain_height, this, _1), tr("Show blockchain height")); - m_cmd_binder.set_handler("transfer_original", boost::bind(&simple_wallet::transfer, this, _1), tr("Same as transfer, but using an older transaction building algorithm")); - m_cmd_binder.set_handler("transfer", boost::bind(&simple_wallet::transfer_new, this, _1), tr("transfer [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <address> <amount> [<payment_id>] - Transfer <amount> to <address>. If the parameter \"index=<N1>[,<N2>,...]\" 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. <priority> is the priority of the transaction. The higher the priority, the higher the fee of the transaction. 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. <ring_size> is the number of inputs to include for untraceability. Multiple payments can be made at once by adding <address_2> <amount_2> 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=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <addr> <amount> <lockblocks> [<payment_id>] - Same as transfer, but with number of blocks to lock the transaction for, max 1000000")); - 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=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <address> [<payment_id>] - Send all unlocked balance to an address. If the parameter \"index<N1>[,<N2>,...]\" is specified, the wallet sweeps outputs received by those address indices. If omitted, the wallet randomly chooses an address index to be used.")); - m_cmd_binder.set_handler("sweep_below", boost::bind(&simple_wallet::sweep_below, this, _1), tr("sweep_below <amount_threshold> [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <address> [<payment_id>] - 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 [<priority>] [<ring_size>] <key_image> <address> [<payment_id>] - 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=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <amount> [<payment_id>] - Donate <amount> to the development team (donate.getmonero.org)")); - m_cmd_binder.set_handler("sign_transfer", boost::bind(&simple_wallet::sign_transfer, this, _1), tr("Sign a transaction from a file")); - 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 <level>|{+,-,}<categories> - Change current log detail (level must be <0-4>)")); - m_cmd_binder.set_handler("account", boost::bind(&simple_wallet::account, this, _1), tr("account [new <label text with white spaces allowed> | switch <index> | label <index> <label text with white spaces allowed>] - If no argments are specified, the wallet shows all the existing accounts along with their balances. If the \"new\" argument is specified, the wallet creates a new account with its label initialized by the provided label text (which can be empty). If the \"switch\" argument is specified, the wallet switches to the account specified by <index>. If the \"label\" argument is specified, the wallet sets the label of the account specified by <index> to the provided label text.")); - m_cmd_binder.set_handler("address", boost::bind(&simple_wallet::print_address, this, _1), tr("address [ new <label text with white spaces allowed> | all | <index_min> [<index_max>] | label <index> <label text with white spaces allowed> ] - If no arguments are specified or <index> is specified, the wallet shows the default or specified address. If \"all\" is specified, the walllet shows all the existing addresses in the currently selected account. If \"new \" is specified, the wallet creates a new address with the provided label text (which can be empty). If \"label\" is specified, the wallet sets the label of the address specified by <index> to the provided label text.")); - m_cmd_binder.set_handler("integrated_address", boost::bind(&simple_wallet::print_integrated_address, this, _1), tr("integrated_address [PID] - Encode a payment ID into an integrated address for the current wallet public address (no argument uses a random payment ID), or decode an integrated address to standard address and payment ID")); - m_cmd_binder.set_handler("address_book", boost::bind(&simple_wallet::address_book, this, _1), tr("address_book [(add (<address> [pid <long or short payment id>])|<integrated address> [<description possibly with whitespaces>])|(delete <index>)] - Print all entries in the address book, optionally adding/deleting an entry to/from it")); - m_cmd_binder.set_handler("save", boost::bind(&simple_wallet::save, this, _1), tr("Save wallet data")); - m_cmd_binder.set_handler("save_watch_only", boost::bind(&simple_wallet::save_watch_only, this, _1), tr("Save a watch-only keys file")); - m_cmd_binder.set_handler("viewkey", boost::bind(&simple_wallet::viewkey, this, _1), tr("Display private view key")); - m_cmd_binder.set_handler("spendkey", boost::bind(&simple_wallet::spendkey, this, _1), tr("Display private spend key")); - m_cmd_binder.set_handler("seed", boost::bind(&simple_wallet::seed, this, _1), tr("Display Electrum-style mnemonic seed")); - m_cmd_binder.set_handler("set", boost::bind(&simple_wallet::set_variable, this, _1), tr("Available options: seed language - set wallet seed language; always-confirm-transfers <1|0> - whether to confirm unsplit txes; print-ring-members <1|0> - whether to print detailed information about ring members during confirmation; store-tx-info <1|0> - whether to store outgoing tx info (destination address, payment ID, tx secret key) for future reference; default-ring-size <n> - set default ring size (default is 5); auto-refresh <1|0> - whether to automatically sync new blocks from the daemon; refresh-type <full|optimize-coinbase|no-coinbase|default> - set wallet refresh behaviour; priority [0|1|2|3|4] - default/unimportant/normal/elevated/priority fee; confirm-missing-payment-id <1|0>; ask-password <1|0>; unit <monero|millinero|micronero|nanonero|piconero> - set default monero (sub-)unit; min-outputs-count [n] - try to keep at least that many outputs of value at least min-outputs-value; min-outputs-value [n] - try to keep at least min-outputs-count outputs of at least that value; merge-destinations <1|0> - whether to merge multiple payments to the same destination address; confirm-backlog <1|0> - whether to warn if there is transaction backlog; confirm-backlog-threshold [n] - sets a threshold for confirm-backlog to only warn if the transaction backlog is greater than n blocks; refresh-from-block-height [n] - set height before which to ignore blocks")); - m_cmd_binder.set_handler("encrypted_seed", boost::bind(&simple_wallet::encrypted_seed, this, _1), tr("Display encrypted Electrum-style mnemonic seed")); - m_cmd_binder.set_handler("rescan_spent", boost::bind(&simple_wallet::rescan_spent, this, _1), tr("Rescan blockchain for spent outputs")); - m_cmd_binder.set_handler("get_tx_key", boost::bind(&simple_wallet::get_tx_key, this, _1), tr("Get transaction key (r) for a given <txid>")); - m_cmd_binder.set_handler("check_tx_key", boost::bind(&simple_wallet::check_tx_key, this, _1), tr("Check amount going to <address> in <txid>")); - m_cmd_binder.set_handler("get_tx_proof", boost::bind(&simple_wallet::get_tx_proof, this, _1), tr("Generate a signature to prove payment to <address> in <txid> using the transaction secret key (r) without revealing it")); - m_cmd_binder.set_handler("check_tx_proof", boost::bind(&simple_wallet::check_tx_proof, this, _1), tr("Check tx proof for payment going to <address> in <txid>")); - m_cmd_binder.set_handler("show_transfers", boost::bind(&simple_wallet::show_transfers, this, _1), tr("show_transfers [in|out|pending|failed|pool] [index=<N1>[,<N2>,...]] [<min_height> [<max_height>]] - Show incoming/outgoing transfers within an optional height range")); - m_cmd_binder.set_handler("unspent_outputs", boost::bind(&simple_wallet::unspent_outputs, this, _1), tr("unspent_outputs [index=<N1>[,<N2>,...]] [<min_amount> [<max_amount>]] - Show unspent outputs of a specified address within an optional amount range")); - m_cmd_binder.set_handler("rescan_bc", boost::bind(&simple_wallet::rescan_blockchain, this, _1), tr("Rescan blockchain from scratch")); - m_cmd_binder.set_handler("set_tx_note", boost::bind(&simple_wallet::set_tx_note, this, _1), tr("Set an arbitrary string note for a txid")); - m_cmd_binder.set_handler("get_tx_note", boost::bind(&simple_wallet::get_tx_note, this, _1), tr("Get a string note for a txid")); - m_cmd_binder.set_handler("set_description", boost::bind(&simple_wallet::set_description, this, _1), tr("Set an arbitrary description for the wallet")); - m_cmd_binder.set_handler("get_description", boost::bind(&simple_wallet::get_description, this, _1), tr("Get the description of the wallet ")); - m_cmd_binder.set_handler("status", boost::bind(&simple_wallet::status, this, _1), tr("Show wallet status information")); - m_cmd_binder.set_handler("wallet_info", boost::bind(&simple_wallet::wallet_info, this, _1), tr("Show wallet information")); - m_cmd_binder.set_handler("sign", boost::bind(&simple_wallet::sign, this, _1), tr("Sign the contents of a file")); - m_cmd_binder.set_handler("verify", boost::bind(&simple_wallet::verify, this, _1), tr("Verify a signature on the contents of a file")); - m_cmd_binder.set_handler("export_key_images", boost::bind(&simple_wallet::export_key_images, this, _1), tr("Export a signed set of key images")); - m_cmd_binder.set_handler("import_key_images", boost::bind(&simple_wallet::import_key_images, this, _1), tr("Import signed key images list and verify their spent status")); - m_cmd_binder.set_handler("export_outputs", boost::bind(&simple_wallet::export_outputs, this, _1), tr("Export a set of outputs owned by this wallet")); - m_cmd_binder.set_handler("import_outputs", boost::bind(&simple_wallet::import_outputs, this, _1), tr("Import set of outputs owned by this wallet")); - m_cmd_binder.set_handler("show_transfer", boost::bind(&simple_wallet::show_transfer, this, _1), tr("Show information about a transfer to/from this address")); - m_cmd_binder.set_handler("password", boost::bind(&simple_wallet::change_password, this, _1), tr("Change wallet password")); - m_cmd_binder.set_handler("payment_id", boost::bind(&simple_wallet::payment_id, this, _1), tr("Generate a new random full size payment id - these will be unencrypted on the blockchain, see integrated_address for encrypted short payment ids")); - m_cmd_binder.set_handler("fee", boost::bind(&simple_wallet::print_fee_info, this, _1), tr("Print information about fee and current transaction backlog")); - m_cmd_binder.set_handler("help", boost::bind(&simple_wallet::help, this, _1), tr("Show this help")); + m_cmd_binder.set_handler("start_mining", + boost::bind(&simple_wallet::start_mining, this, _1), + tr("start_mining [<number_of_threads>] [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("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=<N1>[,<N2>[,...]]]"), + 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 <PID_1> [<PID_2> ... <PID_N>]"), + 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_original", + boost::bind(&simple_wallet::transfer, this, _1), + tr("transfer_original [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <address> <amount> [<payment_id>]"), + tr("Transfer <amount> to <address> using an older transaction building algorithm. If the parameter \"index=<N1>[,<N2>,...]\" 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. <priority> is the priority of the transaction. The higher the priority, the higher the fee of the transaction. 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. <ring_size> is the number of inputs to include for untraceability. Multiple payments can be made at once by adding <address_2> <amount_2> etcetera (before the payment ID, if it's included)")); + m_cmd_binder.set_handler("transfer", boost::bind(&simple_wallet::transfer_new, this, _1), + tr("transfer [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <address> <amount> [<payment_id>]"), + tr("Transfer <amount> to <address>. If the parameter \"index=<N1>[,<N2>,...]\" 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. <priority> is the priority of the transaction. The higher the priority, the higher the fee of the transaction. 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. <ring_size> is the number of inputs to include for untraceability. Multiple payments can be made at once by adding <address_2> <amount_2> 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=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <addr> <amount> <lockblocks> [<payment_id>]"), + tr("Transfer <amount> to <address> and lock it for <lockblocks> (max. 1000000). If the parameter \"index=<N1>[,<N2>,...]\" 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. <priority> is the priority of the transaction. The higher the priority, the higher the fee of the transaction. 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. <ring_size> is the number of inputs to include for untraceability. Multiple payments can be made at once by adding <address_2> <amount_2> etcetera (before the payment ID, if it's included)")); + 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=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <address> [<payment_id>]"), + tr("Send all unlocked balance to an address. If the parameter \"index<N1>[,<N2>,...]\" is specified, the wallet sweeps outputs received by those address indices. If omitted, the wallet randomly chooses an address index to be used.")); + m_cmd_binder.set_handler("sweep_below", + boost::bind(&simple_wallet::sweep_below, this, _1), + tr("sweep_below <amount_threshold> [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <address> [<payment_id>]"), + 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 [<priority>] [<ring_size>] <key_image> <address> [<payment_id>]"), + 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=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <amount> [<payment_id>]"), + tr("Donate <amount> 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 <file>"), + tr("Sign a transaction from a <file>.")); + 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 <level>|{+,-,}<categories>"), + 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 [new <label text with white spaces allowed> | switch <index> | label <index> <label text with white spaces allowed>]"), + tr("If no arguments are specified, the wallet shows all the existing accounts along with their balances. If the \"new\" argument is specified, the wallet creates a new account with its label initialized by the provided label text (which can be empty). If the \"switch\" argument is specified, the wallet switches to the account specified by <index>. If the \"label\" argument is specified, the wallet sets the label of the account specified by <index> to the provided label text.")); + m_cmd_binder.set_handler("address", + boost::bind(&simple_wallet::print_address, this, _1), + tr("address [ new <label text with white spaces allowed> | all | <index_min> [<index_max>] | label <index> <label text with white spaces allowed>]"), + tr("If no arguments are specified or <index> is specified, the wallet shows the default or specified address. If \"all\" is specified, the walllet shows all the existing addresses in the currently selected account. If \"new \" is specified, the wallet creates a new address with the provided label text (which can be empty). If \"label\" is specified, the wallet sets the label of the address specified by <index> to the provided label text.")); + m_cmd_binder.set_handler("integrated_address", + boost::bind(&simple_wallet::print_integrated_address, this, _1), + tr("integrated_address [<payment_id> | <address>]"), + tr("Encode a payment ID into an integrated address for the current wallet public address (no argument uses a random payment ID), or decode an integrated address to standard address and payment ID")); + m_cmd_binder.set_handler("address_book", + boost::bind(&simple_wallet::address_book, this, _1), + tr("address_book [(add ((<address> [pid <id>])|<integrated address>) [<description possibly with whitespaces>])|(delete <index>)]"), + tr("Print all entries in the address book, optionally adding/deleting an entry to/from it.")); + m_cmd_binder.set_handler("save", + boost::bind(&simple_wallet::save, this, _1), + tr("Save the wallet data.")); + m_cmd_binder.set_handler("save_watch_only", + boost::bind(&simple_wallet::save_watch_only, this, _1), + tr("Save a watch-only keys file.")); + m_cmd_binder.set_handler("viewkey", + boost::bind(&simple_wallet::viewkey, this, _1), + tr("Display the private view key.")); + m_cmd_binder.set_handler("spendkey", + boost::bind(&simple_wallet::spendkey, this, _1), + tr("Display the private spend key.")); + m_cmd_binder.set_handler("seed", + boost::bind(&simple_wallet::seed, this, _1), + tr("Display the Electrum-style mnemonic seed")); + m_cmd_binder.set_handler("set", + boost::bind(&simple_wallet::set_variable, this, _1), + tr("set <option> [<value>]"), + tr("Available options:\n " + "seed language\n " + " Set the wallet's seed language.\n " + "always-confirm-transfers <1|0>\n " + " Whether to confirm unsplit txes.\n " + "print-ring-members <1|0>\n " + " Whether to print detailed information about ring members during confirmation.\n " + "store-tx-info <1|0>\n " + " Whether to store outgoing tx info (destination address, payment ID, tx secret key) for future reference.\n " + "default-ring-size <n>\n " + " Set the default ring size (default and minimum is 5).\n " + "auto-refresh <1|0>\n " + " Whether to automatically synchronize new blocks from the daemon.\n " + "refresh-type <full|optimize-coinbase|no-coinbase|default>\n " + " Set the wallet's refresh behaviour.\n " + "priority [0|1|2|3|4]\n " + " Set the fee too default/unimportant/normal/elevated/priority.\n " + "confirm-missing-payment-id <1|0>\n " + "ask-password <1|0>\n " + "unit <monero|millinero|micronero|nanonero|piconero>\n " + " Set the default monero (sub-)unit.\n " + "min-outputs-count [n]\n " + " Try to keep at least that many outputs of value at least min-outputs-value.\n " + "min-outputs-value [n]\n " + " Try to keep at least min-outputs-count outputs of at least that value.\n " + "merge-destinations <1|0>\n " + " Whether to merge multiple payments to the same destination address.\n " + "confirm-backlog <1|0>\n " + " Whether to warn if there is transaction backlog.\n " + "confirm-backlog-threshold [n]\n " + " Set a threshold for confirm-backlog to only warn if the transaction backlog is greater than n blocks.\n " + "refresh-from-block-height [n]\n " + " Set the height before which to ignore blocks.")); + m_cmd_binder.set_handler("encrypted_seed", + boost::bind(&simple_wallet::encrypted_seed, this, _1), + tr("Display the encrypted Electrum-style mnemonic seed.")); + m_cmd_binder.set_handler("rescan_spent", + boost::bind(&simple_wallet::rescan_spent, this, _1), + tr("Rescan the blockchain for spent outputs.")); + m_cmd_binder.set_handler("get_tx_key", + boost::bind(&simple_wallet::get_tx_key, this, _1), + tr("get_tx_key <txid>"), + tr("Get the transaction key (r) for a given <txid>.")); + m_cmd_binder.set_handler("check_tx_key", + boost::bind(&simple_wallet::check_tx_key, this, _1), + tr("check_tx_key <txid> <txkey> <address>"), + tr("Check the amount going to <address> in <txid>.")); + m_cmd_binder.set_handler("get_tx_proof_out", + boost::bind(&simple_wallet::get_tx_proof, this, _1), + tr("get_tx_proof_out <txid> <address> [<message>]"), + tr("Generate a signature proving funds sent to <address> in <txid>, optionally with a challenge string <message>, using either the transaction secret key (when <address> is not your wallet's address) or the view secret key (otherwise), which does not disclose the secret key.")); + m_cmd_binder.set_handler("check_tx_proof", + boost::bind(&simple_wallet::check_tx_proof, this, _1), + tr("check_tx_proof <txid> <address> <signature_file> [<message>]"), + tr("Check the proof for funds going to <address> in <txid> with the challenge string <message> if any.")); + m_cmd_binder.set_handler("get_spend_proof", + boost::bind(&simple_wallet::get_spend_proof, this, _1), + tr("get_spend_proof <txid> [<message>]"), + tr("Generate a signature proving that you generated <txid> using the spend secret key, optionally with a challenge string <message>.")); + m_cmd_binder.set_handler("check_spend_proof", + boost::bind(&simple_wallet::check_spend_proof, this, _1), + tr("check_spend_proof <txid> <signature_file> [<message>]"), + tr("Check a signature proving that the signer generated <txid>, optionally with a challenge string <message>.")); + m_cmd_binder.set_handler("show_transfers", + boost::bind(&simple_wallet::show_transfers, this, _1), + tr("show_transfers [in|out|pending|failed|pool] [index=<N1>[,<N2>,...]] [<min_height> [<max_height>]]"), + tr("Show the incoming/outgoing transfers within an optional height range.")); + m_cmd_binder.set_handler("unspent_outputs", + boost::bind(&simple_wallet::unspent_outputs, this, _1), + tr("unspent_outputs [index=<N1>[,<N2>,...]] [<min_amount> [<max_amount>]]"), + tr("Show the unspent outputs of a specified address within an optional amount range.")); + m_cmd_binder.set_handler("rescan_bc", + boost::bind(&simple_wallet::rescan_blockchain, this, _1), + tr("Rescan the blockchain from scratch.")); + m_cmd_binder.set_handler("set_tx_note", + boost::bind(&simple_wallet::set_tx_note, this, _1), + tr("set_tx_note <txid> [free text note]"), + tr("Set an arbitrary string note for a <txid>.")); + m_cmd_binder.set_handler("get_tx_note", + boost::bind(&simple_wallet::get_tx_note, this, _1), + tr("get_tx_note <txid>"), + tr("Get a string note for a txid.")); + m_cmd_binder.set_handler("set_description", + boost::bind(&simple_wallet::set_description, this, _1), + tr("set_description [free text note]"), + tr("Set an arbitrary description for the wallet.")); + m_cmd_binder.set_handler("get_description", + boost::bind(&simple_wallet::get_description, this, _1), + tr("Get the description of the wallet.")); + m_cmd_binder.set_handler("status", + boost::bind(&simple_wallet::status, this, _1), + tr("Show the wallet's status.")); + m_cmd_binder.set_handler("wallet_info", + boost::bind(&simple_wallet::wallet_info, this, _1), + tr("Show the wallet's information.")); + m_cmd_binder.set_handler("sign", + boost::bind(&simple_wallet::sign, this, _1), + tr("sign <file>"), + tr("Sign the contents of a file.")); + m_cmd_binder.set_handler("verify", + boost::bind(&simple_wallet::verify, this, _1), + tr("verify <filename> <address> <signature>"), + tr("Verify a signature on the contents of a file.")); + m_cmd_binder.set_handler("export_key_images", + boost::bind(&simple_wallet::export_key_images, this, _1), + tr("export_key_images <file>"), + tr("Export a signed set of key images to a <file>.")); + m_cmd_binder.set_handler("import_key_images", + boost::bind(&simple_wallet::import_key_images, this, _1), + tr("import_key_images <file>"), + tr("Import a signed key images list and verify their spent status.")); + m_cmd_binder.set_handler("export_outputs", + boost::bind(&simple_wallet::export_outputs, this, _1), + tr("export_outputs <file>"), + tr("Export a set of outputs owned by this wallet.")); + m_cmd_binder.set_handler("import_outputs", + boost::bind(&simple_wallet::import_outputs, this, _1), + tr("import_outputs <file>"), + tr("Import a set of outputs owned by this wallet.")); + m_cmd_binder.set_handler("show_transfer", + boost::bind(&simple_wallet::show_transfer, this, _1), + tr("show_transfer <txid>"), + tr("Show information about a transfer to/from this address.")); + m_cmd_binder.set_handler("password", + boost::bind(&simple_wallet::change_password, this, _1), + tr("Change the wallet's password.")); + m_cmd_binder.set_handler("payment_id", + boost::bind(&simple_wallet::payment_id, this, _1), + tr("Generate a new random full size payment id. These will be unencrypted on the blockchain, see integrated_address for encrypted short payment ids.")); + m_cmd_binder.set_handler("fee", + boost::bind(&simple_wallet::print_fee_info, this, _1), + tr("Print the information about the current fee and transaction backlog.")); + m_cmd_binder.set_handler("help", + boost::bind(&simple_wallet::help, this, _1), + tr("help [<command>]"), + tr("Show the help section or the documentation about a <command>.")); } //---------------------------------------------------------------------------------------------------- bool simple_wallet::set_variable(const std::vector<std::string> &args) @@ -1009,7 +1222,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args) CHECK_SIMPLE_VARIABLE("always-confirm-transfers", set_always_confirm_transfers, tr("0 or 1")); CHECK_SIMPLE_VARIABLE("print-ring-members", set_print_ring_members, tr("0 or 1")); CHECK_SIMPLE_VARIABLE("store-tx-info", set_store_tx_info, tr("0 or 1")); - CHECK_SIMPLE_VARIABLE("default-ring-size", set_default_ring_size, tr("integer >= 3")); + CHECK_SIMPLE_VARIABLE("default-ring-size", set_default_ring_size, tr("integer >= ") << MIN_RING_SIZE); CHECK_SIMPLE_VARIABLE("auto-refresh", set_auto_refresh, tr("0 or 1")); CHECK_SIMPLE_VARIABLE("refresh-type", set_refresh_type, tr("full (slowest, no assumptions); optimize-coinbase (fast, assumes the whole coinbase is paid to a single address); no-coinbase (fastest, assumes we receive no coinbase transaction), default (same as optimize-coinbase)")); CHECK_SIMPLE_VARIABLE("priority", set_default_priority, tr("0, 1, 2, 3, or 4")); @@ -1588,7 +1801,17 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) } } if (m_restoring) + { + uint64_t estimate_height = m_wallet->estimate_blockchain_height(); + if (m_restore_height >= estimate_height) + { + success_msg_writer() << tr("Restore height ") << m_restore_height << (" is not yet reached. The current estimated height is ") << estimate_height; + std::string confirm = input_line(tr("Still apply restore height? (Y/Yes/N/No): ")); + if (std::cin.eof() || command_line::is_no(confirm)) + m_restore_height = 0; + } m_wallet->set_refresh_from_block_height(m_restore_height); + } } else { @@ -1801,6 +2024,7 @@ bool simple_wallet::new_wallet(const boost::program_options::variables_map& vm, tr("Your wallet has been generated!\n" "To start synchronizing with the daemon, use \"refresh\" command.\n" "Use \"help\" command to see the list of available commands.\n" + "Use \"help <command>\" to see a command's documentation.\n" "Always use \"exit\" command when closing monero-wallet-cli to save your\n" "current session's state. Otherwise, you might need to synchronize \n" "your wallet again (your wallet keys are NOT at risk in any case).\n") @@ -1920,6 +2144,7 @@ bool simple_wallet::open_wallet(const boost::program_options::variables_map& vm) success_msg_writer() << "**********************************************************************\n" << tr("Use \"help\" command to see the list of available commands.\n") << + tr("Use \"help <command>\" to see a command's documentation.\n") << "**********************************************************************"; return true; } @@ -2102,7 +2327,7 @@ void simple_wallet::on_money_received(uint64_t height, const crypto::hash &txid, message_writer(console_color_green, false) << "\r" << tr("Height ") << height << ", " << tr("txid ") << txid << ", " << - print_money(amount) << + print_money(amount) << ", " << tr("idx ") << subaddr_index; if (m_auto_refresh_refreshing) m_cmd_binder.print_prompt(); @@ -3911,22 +4136,24 @@ bool simple_wallet::get_tx_key(const std::vector<std::string> &args_) } if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } - cryptonote::blobdata txid_data; - if(!epee::string_tools::parse_hexstr_to_binbuff(local_args.front(), txid_data) || txid_data.size() != sizeof(crypto::hash)) + crypto::hash txid; + if (!epee::string_tools::hex_to_pod(local_args[0], txid)) { fail_msg_writer() << tr("failed to parse txid"); return true; } - crypto::hash txid = *reinterpret_cast<const crypto::hash*>(txid_data.data()); LOCK_IDLE_SCOPE(); crypto::secret_key tx_key; std::vector<crypto::secret_key> additional_tx_keys; - std::vector<crypto::secret_key> amount_keys; if (m_wallet->get_tx_key(txid, tx_key, additional_tx_keys)) { - success_msg_writer() << tr("Tx key: ") << epee::string_tools::pod_to_hex(tx_key); + ostringstream oss; + oss << epee::string_tools::pod_to_hex(tx_key); + for (size_t i = 0; i < additional_tx_keys.size(); ++i) + oss << epee::string_tools::pod_to_hex(additional_tx_keys[i]); + success_msg_writer() << tr("Tx key: ") << oss.str(); return true; } else @@ -3938,19 +4165,18 @@ bool simple_wallet::get_tx_key(const std::vector<std::string> &args_) //---------------------------------------------------------------------------------------------------- bool simple_wallet::get_tx_proof(const std::vector<std::string> &args) { - if(args.size() != 2) { - fail_msg_writer() << tr("usage: get_tx_proof <txid> <dest_address>"); + if (args.size() != 2 && args.size() != 3) + { + fail_msg_writer() << tr("usage: get_tx_proof_out <txid> <address> [<message>]"); return true; } - if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } - cryptonote::blobdata txid_data; - if(!epee::string_tools::parse_hexstr_to_binbuff(args[0], txid_data) || txid_data.size() != sizeof(crypto::hash)) + crypto::hash txid; + if(!epee::string_tools::hex_to_pod(args[0], txid)) { fail_msg_writer() << tr("failed to parse txid"); return true; } - crypto::hash txid = *reinterpret_cast<const crypto::hash*>(txid_data.data()); cryptonote::address_parse_info info; if(!cryptonote::get_account_address_from_str_or_url(info, m_wallet->testnet(), args[1], oa_prompter)) @@ -3959,140 +4185,21 @@ bool simple_wallet::get_tx_proof(const std::vector<std::string> &args) return true; } - LOCK_IDLE_SCOPE(); - - crypto::secret_key tx_key; - std::vector<crypto::secret_key> additional_tx_keys; - if (!m_wallet->get_tx_key(txid, tx_key, additional_tx_keys)) - { - fail_msg_writer() << tr("Tx secret key wasn't found in the wallet file."); - return true; - } - - // fetch tx prefix either from the daemon or from the wallet cache if it's still pending - cryptonote::transaction_prefix tx; - // first, look up the wallet cache - std::list<std::pair<crypto::hash, tools::wallet2::unconfirmed_transfer_details>> unconfirmed_payments_out; - m_wallet->get_unconfirmed_payments_out(unconfirmed_payments_out, m_current_subaddress_account); - auto found = std::find_if(unconfirmed_payments_out.begin(), unconfirmed_payments_out.end(), [&](const std::pair<crypto::hash, tools::wallet2::unconfirmed_transfer_details>& p) - { - return p.first == txid; - }); - // if not found, query the daemon - if (found == unconfirmed_payments_out.end()) - { - COMMAND_RPC_GET_TRANSACTIONS::request req; - COMMAND_RPC_GET_TRANSACTIONS::response res; - req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); - if (!net_utils::invoke_http_json("/gettransactions", req, res, m_http_client) || - (res.txs.size() != 1 && res.txs_as_hex.size() != 1)) - { - fail_msg_writer() << tr("failed to get transaction from daemon"); - return true; - } - cryptonote::blobdata tx_data; - bool ok; - if (res.txs.size() == 1) - ok = string_tools::parse_hexstr_to_binbuff(res.txs.front().as_hex, tx_data); - else - ok = string_tools::parse_hexstr_to_binbuff(res.txs_as_hex.front(), tx_data); - if (!ok) - { - fail_msg_writer() << tr("failed to parse transaction from daemon"); - return true; - } - crypto::hash tx_hash, tx_prefix_hash; - cryptonote::transaction tx_full; - if (!cryptonote::parse_and_validate_tx_from_blob(tx_data, tx_full, tx_hash, tx_prefix_hash)) - { - fail_msg_writer() << tr("failed to validate transaction from daemon"); - return true; - } - if (tx_hash != txid) - { - fail_msg_writer() << tr("failed to get the right transaction from daemon"); - return true; - } - tx = tx_full; - } - else - { - tx = found->second.m_tx; - } - - // fetch tx pubkey - crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(tx); - if (tx_pub_key == crypto::null_pkey) - { - fail_msg_writer() << tr("Tx pubkey was not found"); - return true; - } - const std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(tx); - if (additional_tx_keys.size() != additional_tx_pub_keys.size()) - { - fail_msg_writer() << tr("The set of additional tx secret/public keys doesn't match"); - return true; - } - - // find the correct R: - // R = r*G for standard addresses - // R = r*B for subaddresses (where B is the recipient's spend pubkey) - crypto::public_key R; - crypto::secret_key r; - if (info.is_subaddress) - { - auto i = additional_tx_keys.begin(); - auto j = additional_tx_pub_keys.begin(); - for (; ; ++i, ++j) { - if (i == additional_tx_keys.end()) - { - r = tx_key; - R = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(info.address.m_spend_public_key), rct::sk2rct(r))); - if (R == tx_pub_key) - break; - fail_msg_writer() << tr("The matching tx secret/pubkey pair wasn't found for this subaddress"); - return true; - } - else - { - r = *i; - R = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(info.address.m_spend_public_key), rct::sk2rct(r))); - if (R == *j) - break; - } - } - } - else - { - r = tx_key; - crypto::secret_key_to_public_key(r, R); - if (R != tx_pub_key) - { - fail_msg_writer() << tr("The destinations of this tx don't include a standard address"); - return true; - } - } + if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } - crypto::public_key rA = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(info.address.m_view_public_key), rct::sk2rct(r))); - crypto::signature sig; try { - if (info.is_subaddress) - crypto::generate_tx_proof(txid, R, info.address.m_view_public_key, info.address.m_spend_public_key, rA, r, sig); + std::string sig_str = m_wallet->get_tx_proof(txid, info.address, info.is_subaddress, args.size() == 3 ? args[2] : ""); + const std::string filename = "monero_tx_proof"; + if (epee::file_io_utils::save_string_to_file(filename, sig_str)) + success_msg_writer() << tr("signature file saved to: ") << filename; else - crypto::generate_tx_proof(txid, R, info.address.m_view_public_key, boost::none, rA, r, sig); + fail_msg_writer() << tr("failed to save signature file"); } - catch (const std::runtime_error &e) + catch (const std::exception &e) { - fail_msg_writer() << e.what(); - return true; + fail_msg_writer() << tr("error: ") << e.what(); } - - std::string sig_str = std::string("ProofV1") + - tools::base58::encode(std::string((const char *)&rA, sizeof(crypto::public_key))) + - tools::base58::encode(std::string((const char *)&sig, sizeof(crypto::signature))); - - success_msg_writer() << tr("Signature: ") << sig_str; return true; } //---------------------------------------------------------------------------------------------------- @@ -4113,27 +4220,31 @@ bool simple_wallet::check_tx_key(const std::vector<std::string> &args_) fail_msg_writer() << tr("wallet is null"); return true; } - cryptonote::blobdata txid_data; - if(!epee::string_tools::parse_hexstr_to_binbuff(local_args[0], txid_data) || txid_data.size() != sizeof(crypto::hash)) + crypto::hash txid; + if(!epee::string_tools::hex_to_pod(local_args[0], txid)) { fail_msg_writer() << tr("failed to parse txid"); return true; } - crypto::hash txid = *reinterpret_cast<const crypto::hash*>(txid_data.data()); - if (local_args[1].size() < 64 || local_args[1].size() % 64) + crypto::secret_key tx_key; + std::vector<crypto::secret_key> additional_tx_keys; + if(!epee::string_tools::hex_to_pod(local_args[1].substr(0, 64), tx_key)) { fail_msg_writer() << tr("failed to parse tx key"); return true; } - crypto::secret_key tx_key; - cryptonote::blobdata tx_key_data; - if(!epee::string_tools::parse_hexstr_to_binbuff(local_args[1], tx_key_data) || tx_key_data.size() != sizeof(crypto::secret_key)) + local_args[1] = local_args[1].substr(64); + while (!local_args[1].empty()) { - fail_msg_writer() << tr("failed to parse tx key"); - return true; + additional_tx_keys.resize(additional_tx_keys.size() + 1); + if(!epee::string_tools::hex_to_pod(local_args[1].substr(0, 64), additional_tx_keys.back())) + { + fail_msg_writer() << tr("failed to parse tx key"); + return true; + } + local_args[1] = local_args[1].substr(64); } - tx_key = *reinterpret_cast<const crypto::secret_key*>(tx_key_data.data()); cryptonote::address_parse_info info; if(!cryptonote::get_account_address_from_str_or_url(info, m_wallet->testnet(), local_args[2], oa_prompter)) @@ -4142,134 +4253,48 @@ bool simple_wallet::check_tx_key(const std::vector<std::string> &args_) return true; } - crypto::key_derivation derivation; - if (!crypto::generate_key_derivation(info.address.m_view_public_key, tx_key, derivation)) - { - fail_msg_writer() << tr("failed to generate key derivation from supplied parameters"); - return true; - } - - return check_tx_key_helper(txid, info.address, info.is_subaddress, derivation); -} -//---------------------------------------------------------------------------------------------------- -bool simple_wallet::check_tx_key_helper(const crypto::hash &txid, const cryptonote::account_public_address &address, bool is_subaddress, const crypto::key_derivation &derivation) -{ - COMMAND_RPC_GET_TRANSACTIONS::request req; - COMMAND_RPC_GET_TRANSACTIONS::response res; - req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); - if (!net_utils::invoke_http_json("/gettransactions", req, res, m_http_client) || - (res.txs.size() != 1 && res.txs_as_hex.size() != 1)) - { - fail_msg_writer() << tr("failed to get transaction from daemon"); - return true; - } - cryptonote::blobdata tx_data; - bool ok; - if (res.txs.size() == 1) - ok = string_tools::parse_hexstr_to_binbuff(res.txs.front().as_hex, tx_data); - else - ok = string_tools::parse_hexstr_to_binbuff(res.txs_as_hex.front(), tx_data); - if (!ok) - { - fail_msg_writer() << tr("failed to parse transaction from daemon"); - return true; - } - crypto::hash tx_hash, tx_prefix_hash; - cryptonote::transaction tx; - if (!cryptonote::parse_and_validate_tx_from_blob(tx_data, tx, tx_hash, tx_prefix_hash)) - { - fail_msg_writer() << tr("failed to validate transaction from daemon"); - return true; - } - if (tx_hash != txid) + try { - fail_msg_writer() << tr("failed to get the right transaction from daemon"); - return true; - } + uint64_t received; + bool in_pool; + uint64_t confirmations; + m_wallet->check_tx_key(txid, tx_key, additional_tx_keys, info.address, received, in_pool, confirmations); - uint64_t received = 0; - try { - for (size_t n = 0; n < tx.vout.size(); ++n) + if (received > 0) { - if (typeid(txout_to_key) != tx.vout[n].target.type()) - continue; - const txout_to_key tx_out_to_key = boost::get<txout_to_key>(tx.vout[n].target); - crypto::public_key pubkey; - derive_public_key(derivation, n, address.m_spend_public_key, pubkey); - if (pubkey == tx_out_to_key.key) + success_msg_writer() << get_account_address_as_str(m_wallet->testnet(), info.is_subaddress, info.address) << " " << tr("received") << " " << print_money(received) << " " << tr("in txid") << " " << txid; + if (in_pool) + { + success_msg_writer() << tr("WARNING: this transaction is not yet included in the blockchain!"); + } + else { - uint64_t amount; - if (tx.version == 1) + if (confirmations != (uint64_t)-1) { - amount = tx.vout[n].amount; + success_msg_writer() << boost::format(tr("This transaction has %u confirmations")) % confirmations; } else { - try - { - rct::key Ctmp; - //rct::key amount_key = rct::hash_to_scalar(rct::scalarmultKey(rct::pk2rct(address.m_view_public_key), rct::sk2rct(tx_key))); - { - crypto::secret_key scalar1; - crypto::derivation_to_scalar(derivation, n, scalar1); - rct::ecdhTuple ecdh_info = tx.rct_signatures.ecdhInfo[n]; - rct::ecdhDecode(ecdh_info, rct::sk2rct(scalar1)); - rct::key C = tx.rct_signatures.outPk[n].mask; - rct::addKeys2(Ctmp, ecdh_info.mask, ecdh_info.amount, rct::H); - if (rct::equalKeys(C, Ctmp)) - amount = rct::h2d(ecdh_info.amount); - else - amount = 0; - } - } - catch (...) { amount = 0; } + success_msg_writer() << tr("WARNING: failed to determine number of confirmations!"); } - received += amount; } } - } - catch(const std::exception &e) - { - LOG_ERROR("error: " << e.what()); - fail_msg_writer() << tr("error: ") << e.what(); - return true; - } - - std::string address_str = get_account_address_as_str(m_wallet->testnet(), is_subaddress, address); - if (received > 0) - { - success_msg_writer() << address_str << " " << tr("received") << " " << print_money(received) << " " << tr("in txid") << " " << txid; - } - else - { - fail_msg_writer() << address_str << " " << tr("received nothing in txid") << " " << txid; - } - if (res.txs.front().in_pool) - { - success_msg_writer() << tr("WARNING: this transaction is not yet included in the blockchain!"); - } - else - { - std::string err; - uint64_t bc_height = get_daemon_blockchain_height(err); - if (err.empty()) - { - uint64_t confirmations = bc_height - (res.txs.front().block_height + 1); - success_msg_writer() << boost::format(tr("This transaction has %u confirmations")) % confirmations; - } else { - success_msg_writer() << tr("WARNING: failed to determine number of confirmations!"); + fail_msg_writer() << get_account_address_as_str(m_wallet->testnet(), info.is_subaddress, info.address) << " " << tr("received nothing in txid") << " " << txid; } } - + catch (const std::exception &e) + { + fail_msg_writer() << tr("error: ") << e.what(); + } return true; } //---------------------------------------------------------------------------------------------------- bool simple_wallet::check_tx_proof(const std::vector<std::string> &args) { - if(args.size() != 3) { - fail_msg_writer() << tr("usage: check_tx_proof <txid> <address> <signature>"); + if(args.size() != 3 && args.size() != 4) { + fail_msg_writer() << tr("usage: check_tx_proof <txid> <address> <signature_file> [<message>]"); return true; } @@ -4277,13 +4302,12 @@ bool simple_wallet::check_tx_proof(const std::vector<std::string> &args) return true; // parse txid - cryptonote::blobdata txid_data; - if(!epee::string_tools::parse_hexstr_to_binbuff(args[0], txid_data) || txid_data.size() != sizeof(crypto::hash)) + crypto::hash txid; + if(!epee::string_tools::hex_to_pod(args[0], txid)) { fail_msg_writer() << tr("failed to parse txid"); return true; } - crypto::hash txid = *reinterpret_cast<const crypto::hash*>(txid_data.data()); // parse address cryptonote::address_parse_info info; @@ -4293,117 +4317,141 @@ bool simple_wallet::check_tx_proof(const std::vector<std::string> &args) return true; } - // parse pubkey r*A & signature - std::string sig_str = args[2]; - const size_t header_len = strlen("ProofV1"); - if (sig_str.size() < header_len || sig_str.substr(0, header_len) != "ProofV1") + // read signature file + std::string sig_str; + if (!epee::file_io_utils::load_file_to_string(args[2], sig_str)) { - fail_msg_writer() << tr("Signature header check error"); + fail_msg_writer() << tr("failed to load signature file"); return true; } - crypto::public_key rA; - crypto::signature sig; - const size_t rA_len = tools::base58::encode(std::string((const char *)&rA, sizeof(crypto::public_key))).size(); - const size_t sig_len = tools::base58::encode(std::string((const char *)&sig, sizeof(crypto::signature))).size(); - std::string rA_decoded; - std::string sig_decoded; - if (!tools::base58::decode(sig_str.substr(header_len, rA_len), rA_decoded)) + + try { - fail_msg_writer() << tr("Signature decoding error"); - return true; + uint64_t received; + bool in_pool; + uint64_t confirmations; + if (m_wallet->check_tx_proof(txid, info.address, info.is_subaddress, args.size() == 4 ? args[3] : "", sig_str, received, in_pool, confirmations)) + { + success_msg_writer() << tr("Good signature"); + if (received > 0) + { + success_msg_writer() << get_account_address_as_str(m_wallet->testnet(), info.is_subaddress, info.address) << " " << tr("received") << " " << print_money(received) << " " << tr("in txid") << " " << txid; + if (in_pool) + { + success_msg_writer() << tr("WARNING: this transaction is not yet included in the blockchain!"); + } + else + { + if (confirmations != (uint64_t)-1) + { + success_msg_writer() << boost::format(tr("This transaction has %u confirmations")) % confirmations; + } + else + { + success_msg_writer() << tr("WARNING: failed to determine number of confirmations!"); + } + } + } + else + { + fail_msg_writer() << get_account_address_as_str(m_wallet->testnet(), info.is_subaddress, info.address) << " " << tr("received nothing in txid") << " " << txid; + } + } + else + { + fail_msg_writer() << tr("Bad signature"); + } } - if (!tools::base58::decode(sig_str.substr(header_len + rA_len, sig_len), sig_decoded)) + catch (const std::exception &e) { - fail_msg_writer() << tr("Signature decoding error"); - return true; + fail_msg_writer() << tr("error: ") << e.what(); } - if (sizeof(crypto::public_key) != rA_decoded.size() || sizeof(crypto::signature) != sig_decoded.size()) - { - fail_msg_writer() << tr("Signature decoding error"); + return true; +} +//---------------------------------------------------------------------------------------------------- +bool simple_wallet::get_spend_proof(const std::vector<std::string> &args) +{ + if(args.size() != 1 && args.size() != 2) { + fail_msg_writer() << tr("usage: get_spend_proof <txid> [<message>]"); return true; } - memcpy(&rA, rA_decoded.data(), sizeof(crypto::public_key)); - memcpy(&sig, sig_decoded.data(), sizeof(crypto::signature)); - // fetch tx pubkey from the daemon - COMMAND_RPC_GET_TRANSACTIONS::request req; - COMMAND_RPC_GET_TRANSACTIONS::response res; - req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); - if (!net_utils::invoke_http_json("/gettransactions", req, res, m_http_client) || - (res.txs.size() != 1 && res.txs_as_hex.size() != 1)) + if (m_wallet->watch_only()) { - fail_msg_writer() << tr("failed to get transaction from daemon"); + fail_msg_writer() << tr("wallet is watch-only and cannot generate the proof"); return true; } - cryptonote::blobdata tx_data; - bool ok; - if (res.txs.size() == 1) - ok = string_tools::parse_hexstr_to_binbuff(res.txs.front().as_hex, tx_data); - else - ok = string_tools::parse_hexstr_to_binbuff(res.txs_as_hex.front(), tx_data); - if (!ok) + + crypto::hash txid; + if (!epee::string_tools::hex_to_pod(args[0], txid)) { - fail_msg_writer() << tr("failed to parse transaction from daemon"); + fail_msg_writer() << tr("failed to parse txid"); return true; } - crypto::hash tx_hash, tx_prefix_hash; - cryptonote::transaction tx; - if (!cryptonote::parse_and_validate_tx_from_blob(tx_data, tx, tx_hash, tx_prefix_hash)) + + if (!try_connect_to_daemon()) { - fail_msg_writer() << tr("failed to validate transaction from daemon"); + fail_msg_writer() << tr("failed to connect to the daemon"); return true; } - if (tx_hash != txid) + + if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } + + try { - fail_msg_writer() << tr("failed to get the right transaction from daemon"); - return true; + const std::string sig_str = m_wallet->get_spend_proof(txid, args.size() == 2 ? args[1] : ""); + const std::string filename = "monero_spend_proof"; + if (epee::file_io_utils::save_string_to_file(filename, sig_str)) + success_msg_writer() << tr("signature file saved to: ") << filename; + else + fail_msg_writer() << tr("failed to save signature file"); } - crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(tx); - if (tx_pub_key == crypto::null_pkey) + catch (const std::exception &e) { - fail_msg_writer() << tr("Tx pubkey was not found"); + fail_msg_writer() << e.what(); + } + return true; +} +//---------------------------------------------------------------------------------------------------- +bool simple_wallet::check_spend_proof(const std::vector<std::string> &args) +{ + if(args.size() != 2 && args.size() != 3) { + fail_msg_writer() << tr("usage: check_spend_proof <txid> <signature_file> [<message>]"); return true; } - const std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(tx); - // check signature - bool good_signature = false; - if (info.is_subaddress) - { - good_signature = crypto::check_tx_proof(txid, tx_pub_key, info.address.m_view_public_key, info.address.m_spend_public_key, rA, sig); - if (!good_signature) - { - for (size_t i = 0; i < additional_tx_pub_keys.size(); ++i) - { - good_signature = crypto::check_tx_proof(txid, additional_tx_pub_keys[i], info.address.m_view_public_key, info.address.m_spend_public_key, rA, sig); - if (good_signature) - break; - } - } - } - else - { - good_signature = crypto::check_tx_proof(txid, tx_pub_key, info.address.m_view_public_key, boost::none, rA, sig); - } - if (good_signature) + crypto::hash txid; + if (!epee::string_tools::hex_to_pod(args[0], txid)) { - success_msg_writer() << tr("Good signature"); + fail_msg_writer() << tr("failed to parse txid"); + return true; } - else + + if (!try_connect_to_daemon()) { - fail_msg_writer() << tr("Bad signature"); + fail_msg_writer() << tr("failed to connect to the daemon"); return true; } - // obtain key derivation by multiplying scalar 1 to the pubkey r*A included in the signature - crypto::key_derivation derivation; - if (!crypto::generate_key_derivation(rA, rct::rct2sk(rct::I), derivation)) + std::string sig_str; + if (!epee::file_io_utils::load_file_to_string(args[1], sig_str)) { - fail_msg_writer() << tr("failed to generate key derivation"); + fail_msg_writer() << tr("failed to load signature file"); return true; } - return check_tx_key_helper(txid, info.address, info.is_subaddress, derivation); + try + { + if (m_wallet->check_spend_proof(txid, args.size() == 3 ? args[2] : "", sig_str)) + success_msg_writer() << tr("Good signature"); + else + fail_msg_writer() << tr("Bad signature"); + } + catch (const std::exception& e) + { + fail_msg_writer() << e.what(); + } + return true; } //---------------------------------------------------------------------------------------------------- static std::string get_human_readable_timestamp(uint64_t ts) @@ -5039,6 +5087,11 @@ bool simple_wallet::print_integrated_address(const std::vector<std::string> &arg } if (args.size() == 0) { + if (m_current_subaddress_account != 0) + { + fail_msg_writer() << tr("Integrated addresses can only be created for account 0"); + return true; + } payment_id = crypto::rand<crypto::hash8>(); success_msg_writer() << tr("Random payment ID: ") << payment_id; success_msg_writer() << tr("Matching integrated address: ") << m_wallet->get_account().get_public_integrated_address_str(payment_id, m_wallet->testnet()); @@ -5046,6 +5099,11 @@ bool simple_wallet::print_integrated_address(const std::vector<std::string> &arg } if(tools::wallet2::parse_short_payment_id(args.back(), payment_id)) { + if (m_current_subaddress_account != 0) + { + fail_msg_writer() << tr("Integrated addresses can only be created for account 0"); + return true; + } success_msg_writer() << m_wallet->get_account().get_public_integrated_address_str(payment_id, m_wallet->testnet()); return true; } |