From 18faa6da0c868720d78d985e64cd60b30932b5c7 Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Wed, 13 Mar 2019 21:51:41 +0000 Subject: [PATCH] wallet: add freeze/thaw/frozen commands These commands let one freeze outputs by key image, so they do not appear in balance, nor are considered when creating a transaction, etc This is helpful when receiving an output from a suspected spy, who might try to track your other outputs by seeing with what other outputs it gets spent. The frozen command may be used without parameters to list all currently frozen outputs. --- src/simplewallet/simplewallet.cpp | 87 ++++++++++++++++++++++++++++++- src/simplewallet/simplewallet.h | 6 ++- src/wallet/wallet2.cpp | 85 +++++++++++++++++++++++++----- src/wallet/wallet2.h | 26 ++++++++- 4 files changed, 188 insertions(+), 16 deletions(-) diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index f390b7675..c839a186a 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -237,6 +237,9 @@ namespace const char* USAGE_MARK_OUTPUT_SPENT("mark_output_spent / | [add]"); const char* USAGE_MARK_OUTPUT_UNSPENT("mark_output_unspent /"); const char* USAGE_IS_OUTPUT_SPENT("is_output_spent /"); + const char* USAGE_FREEZE("freeze "); + const char* USAGE_THAW("thaw "); + const char* USAGE_FROZEN("frozen "); const char* USAGE_VERSION("version"); const char* USAGE_HELP("help []"); @@ -2025,6 +2028,74 @@ bool simple_wallet::save_known_rings(const std::vector &args) return true; } +bool simple_wallet::freeze_thaw(const std::vector &args, bool freeze) +{ + if (args.empty()) + { + fail_msg_writer() << boost::format(tr("usage: %s |")) % (freeze ? "freeze" : "thaw"); + return true; + } + crypto::key_image ki; + if (!epee::string_tools::hex_to_pod(args[0], ki)) + { + fail_msg_writer() << tr("failed to parse key image"); + return true; + } + try + { + if (freeze) + m_wallet->freeze(ki); + else + m_wallet->thaw(ki); + } + catch (const std::exception &e) + { + fail_msg_writer() << e.what(); + return true; + } + + return true; +} + +bool simple_wallet::freeze(const std::vector &args) +{ + return freeze_thaw(args, true); +} + +bool simple_wallet::thaw(const std::vector &args) +{ + return freeze_thaw(args, false); +} + +bool simple_wallet::frozen(const std::vector &args) +{ + if (args.empty()) + { + size_t ntd = m_wallet->get_num_transfer_details(); + for (size_t i = 0; i < ntd; ++i) + { + if (!m_wallet->frozen(i)) + continue; + const tools::wallet2::transfer_details &td = m_wallet->get_transfer_details(i); + message_writer() << tr("Frozen: ") << td.m_key_image << " " << cryptonote::print_money(td.amount()); + } + } + else + { + crypto::key_image ki; + if (!epee::string_tools::hex_to_pod(args[0], ki)) + { + fail_msg_writer() << tr("failed to parse key image"); + return true; + } + if (m_wallet->frozen(ki)) + message_writer() << tr("Frozen: ") << ki; + else + message_writer() << tr("Not frozen: ") << ki; + } + return true; +} + bool simple_wallet::version(const std::vector &args) { message_writer() << "Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")"; @@ -2600,7 +2671,7 @@ simple_wallet::simple_wallet() tr(USAGE_INCOMING_TRANSFERS), tr("Show the incoming transfers, all or filtered by availability and address index.\n\n" "Output format:\n" - "Amount, Spent(\"T\"|\"F\"), \"locked\"|\"unlocked\", RingCT, Global Index, Transaction Hash, Address Index, [Public Key, Key Image] ")); + "Amount, Spent(\"T\"|\"F\"), \"frozen\"|\"locked\"|\"unlocked\", RingCT, Global Index, Transaction Hash, Address Index, [Public Key, Key Image] ")); m_cmd_binder.set_handler("payments", boost::bind(&simple_wallet::show_payments, this, _1), tr(USAGE_PAYMENTS), @@ -3012,6 +3083,18 @@ simple_wallet::simple_wallet() boost::bind(&simple_wallet::blackballed, this, _1), tr(USAGE_IS_OUTPUT_SPENT), tr("Checks whether an output is marked as spent")); + m_cmd_binder.set_handler("freeze", + boost::bind(&simple_wallet::freeze, this, _1), + tr(USAGE_FREEZE), + tr("Freeze a single output by key image so it will not be used")); + m_cmd_binder.set_handler("thaw", + boost::bind(&simple_wallet::thaw, this, _1), + tr(USAGE_THAW), + tr("Thaw a single output by key image so it may be used again")); + m_cmd_binder.set_handler("frozen", + boost::bind(&simple_wallet::frozen, this, _1), + tr(USAGE_FROZEN), + tr("Checks whether a given output is currently frozen by key image")); m_cmd_binder.set_handler("version", boost::bind(&simple_wallet::version, this, _1), tr(USAGE_VERSION), @@ -4987,7 +5070,7 @@ bool simple_wallet::show_incoming_transfers(const std::vector& args boost::format("%21s%8s%12s%8s%16u%68s%16u%s") % print_money(td.amount()) % (td.m_spent ? tr("T") : tr("F")) % - (m_wallet->is_transfer_unlocked(td) ? tr("unlocked") : tr("locked")) % + (m_wallet->frozen(td) ? tr("[frozen]") : m_wallet->is_transfer_unlocked(td) ? tr("unlocked") : tr("locked")) % (td.is_rct() ? tr("RingCT") : tr("-")) % td.m_global_output_index % td.m_txid % diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index 7bcb92190..c9a5c55e8 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -238,9 +238,12 @@ namespace cryptonote bool blackball(const std::vector& args); bool unblackball(const std::vector& args); bool blackballed(const std::vector& args); + bool freeze(const std::vector& args); + bool thaw(const std::vector& args); + bool frozen(const std::vector& args); bool version(const std::vector& args); - bool cold_sign_tx(const std::vector& ptx_vector, tools::wallet2::signed_tx_set &exported_txs, std::vector &dsts_info, std::function accept_func); + bool cold_sign_tx(const std::vector& ptx_vector, tools::wallet2::signed_tx_set &exported_txs, std::vector &dsts_info, std::function accept_func); uint64_t get_daemon_blockchain_height(std::string& err); bool try_connect_to_daemon(bool silent = false, uint32_t* version = nullptr); bool ask_wallet_create_if_needed(); @@ -253,6 +256,7 @@ namespace cryptonote void key_images_sync_intern(); void on_refresh_finished(uint64_t start_height, uint64_t fetched_blocks, bool is_init, bool received_money); std::pair show_outputs_line(const std::vector &heights, uint64_t blockchain_height, uint64_t highlight_height = std::numeric_limits::max()) const; + bool freeze_thaw(const std::vector& args, bool freeze); struct transfer_view { diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index a46da42db..47f9b6de8 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -1392,6 +1392,58 @@ void wallet2::set_unspent(size_t idx) td.m_spent_height = 0; } //---------------------------------------------------------------------------------------------------- +void wallet2::freeze(size_t idx) +{ + CHECK_AND_ASSERT_THROW_MES(idx < m_transfers.size(), "Invalid transfer_details index"); + transfer_details &td = m_transfers[idx]; + td.m_frozen = true; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::thaw(size_t idx) +{ + CHECK_AND_ASSERT_THROW_MES(idx < m_transfers.size(), "Invalid transfer_details index"); + transfer_details &td = m_transfers[idx]; + td.m_frozen = false; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::frozen(size_t idx) const +{ + CHECK_AND_ASSERT_THROW_MES(idx < m_transfers.size(), "Invalid transfer_details index"); + const transfer_details &td = m_transfers[idx]; + return td.m_frozen; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::freeze(const crypto::key_image &ki) +{ + freeze(get_transfer_details(ki)); +} +//---------------------------------------------------------------------------------------------------- +void wallet2::thaw(const crypto::key_image &ki) +{ + thaw(get_transfer_details(ki)); +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::frozen(const crypto::key_image &ki) const +{ + return frozen(get_transfer_details(ki)); +} +//---------------------------------------------------------------------------------------------------- +size_t wallet2::get_transfer_details(const crypto::key_image &ki) const +{ + for (size_t idx = 0; idx < m_transfers.size(); ++idx) + { + const transfer_details &td = m_transfers[idx]; + if (td.m_key_image_known && td.m_key_image == ki) + return idx; + } + CHECK_AND_ASSERT_THROW_MES(false, "Key image not found"); +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::frozen(const transfer_details &td) const +{ + return td.m_frozen; +} +//---------------------------------------------------------------------------------------------------- void wallet2::check_acc_out_precomp(const tx_out &o, const crypto::key_derivation &derivation, const std::vector &additional_derivations, size_t i, tx_scan_info_t &tx_scan_info) const { hw::device &hwdev = m_account.get_device(); @@ -1809,6 +1861,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote td.m_mask = rct::identity(); td.m_rct = false; } + td.m_frozen = false; set_unspent(m_transfers.size()-1); if (td.m_key_image_known) m_key_images[td.m_key_image] = m_transfers.size()-1; @@ -3156,8 +3209,9 @@ void wallet2::detach_blockchain(uint64_t height) wallet2::transfer_details &td = m_transfers[i]; if (td.m_spent && td.m_spent_height >= height) { - LOG_PRINT_L1("Resetting spent status for output " << i << ": " << td.m_key_image); + LOG_PRINT_L1("Resetting spent/frozen status for output " << i << ": " << td.m_key_image); set_unspent(i); + thaw(i); } } @@ -5331,7 +5385,7 @@ std::map wallet2::balance_per_subaddress(uint32_t index_majo std::map amount_per_subaddr; for (const auto& td: m_transfers) { - if (td.m_subaddr_index.major == index_major && !td.m_spent) + if (td.m_subaddr_index.major == index_major && !td.m_spent && !td.m_frozen) { auto found = amount_per_subaddr.find(td.m_subaddr_index.minor); if (found == amount_per_subaddr.end()) @@ -5360,7 +5414,7 @@ std::map wallet2::unlocked_balance_per_subaddress(uint32_t i std::map amount_per_subaddr; for(const transfer_details& td: m_transfers) { - if(td.m_subaddr_index.major == index_major && !td.m_spent && is_transfer_unlocked(td)) + if(td.m_subaddr_index.major == index_major && !td.m_spent && !td.m_frozen && is_transfer_unlocked(td)) { auto found = amount_per_subaddr.find(td.m_subaddr_index.minor); if (found == amount_per_subaddr.end()) @@ -8264,7 +8318,7 @@ std::vector wallet2::pick_preferred_rct_inputs(uint64_t needed_money, ui for (size_t i = 0; i < m_transfers.size(); ++i) { const transfer_details& td = m_transfers[i]; - if (!td.m_spent && td.is_rct() && td.amount() >= needed_money && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1) + if (!td.m_spent && !td.m_frozen && td.is_rct() && td.amount() >= needed_money && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1) { LOG_PRINT_L2("We can use " << i << " alone: " << print_money(td.amount())); picks.push_back(i); @@ -8279,13 +8333,13 @@ std::vector wallet2::pick_preferred_rct_inputs(uint64_t needed_money, ui for (size_t i = 0; i < m_transfers.size(); ++i) { const transfer_details& td = m_transfers[i]; - if (!td.m_spent && !td.m_key_image_partial && td.is_rct() && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1) + if (!td.m_spent && !td.m_frozen && !td.m_key_image_partial && td.is_rct() && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1) { LOG_PRINT_L2("Considering input " << i << ", " << print_money(td.amount())); for (size_t j = i + 1; j < m_transfers.size(); ++j) { const transfer_details& td2 = m_transfers[j]; - if (!td2.m_spent && !td.m_key_image_partial && td2.is_rct() && td.amount() + td2.amount() >= needed_money && is_transfer_unlocked(td2) && td2.m_subaddr_index == td.m_subaddr_index) + if (!td2.m_spent && !td2.m_frozen && !td.m_key_image_partial && td2.is_rct() && td.amount() + td2.amount() >= needed_money && is_transfer_unlocked(td2) && td2.m_subaddr_index == td.m_subaddr_index) { // update our picks if those outputs are less related than any we // already found. If the same, don't update, and oldest suitable outputs @@ -8512,6 +8566,7 @@ void wallet2::light_wallet_get_unspent_outs() td.m_pk_index = 0; td.m_internal_output_index = o.index; td.m_spent = spent; + td.m_frozen = false; tx_out txout; txout.target = txout_to_key(public_key); @@ -8986,7 +9041,7 @@ std::vector wallet2::create_transactions_2(std::vector>& x) { return x.first == index_minor; }; @@ -9466,7 +9521,7 @@ std::vector wallet2::create_transactions_all(uint64_t below for (size_t i = 0; i < m_transfers.size(); ++i) { const transfer_details& td = m_transfers[i]; - if (!td.m_spent && !td.m_key_image_partial && (use_rct ? true : !td.is_rct()) && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && (subaddr_indices.empty() || subaddr_indices.count(td.m_subaddr_index.minor) == 1)) + if (!td.m_spent && !td.m_frozen && !td.m_key_image_partial && (use_rct ? true : !td.is_rct()) && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && (subaddr_indices.empty() || subaddr_indices.count(td.m_subaddr_index.minor) == 1)) { fund_found = true; if (below == 0 || td.amount() < below) @@ -9514,7 +9569,7 @@ std::vector wallet2::create_transactions_single(const crypt for (size_t i = 0; i < m_transfers.size(); ++i) { const transfer_details& td = m_transfers[i]; - if (td.m_key_image_known && td.m_key_image == ki && !td.m_spent && (use_rct ? true : !td.is_rct()) && is_transfer_unlocked(td)) + if (td.m_key_image_known && td.m_key_image == ki && !td.m_spent && !td.m_frozen && (use_rct ? true : !td.is_rct()) && is_transfer_unlocked(td)) { if (td.is_rct() || is_valid_decomposed_amount(td.amount())) unused_transfers_indices.push_back(i); @@ -9846,6 +9901,8 @@ std::vector wallet2::select_available_outputs(const std::functionm_spent) continue; + if (i->m_frozen) + continue; if (i->m_key_image_partial) continue; if (!is_transfer_unlocked(*i)) @@ -9861,7 +9918,7 @@ std::vector wallet2::get_unspent_amounts_vector() const std::set set; for (const auto &td: m_transfers) { - if (!td.m_spent) + if (!td.m_spent && !td.m_frozen) set.insert(td.is_rct() ? 0 : td.amount()); } std::vector vector; @@ -9987,7 +10044,7 @@ void wallet2::discard_unmixable_outputs() std::vector unmixable_outputs = select_available_unmixable_outputs(); for (size_t idx : unmixable_outputs) { - m_transfers[idx].m_spent = true; + freeze(idx); } } @@ -10770,7 +10827,7 @@ std::string wallet2::get_reserve_proof(const boost::optionalfirst == td.m_subaddr_index.major)) + if (!td.m_spent && !td.m_frozen && (!account_minreserve || account_minreserve->first == td.m_subaddr_index.major)) selected_transfers.push_back(i); } @@ -11517,6 +11574,8 @@ uint64_t wallet2::import_key_images(const std::vector &output); bool is_output_blackballed(const std::pair &output) const; + void freeze(size_t idx); + void thaw(size_t idx); + bool frozen(size_t idx) const; + void freeze(const crypto::key_image &ki); + void thaw(const crypto::key_image &ki); + bool frozen(const crypto::key_image &ki) const; + bool frozen(const transfer_details &td) const; + // MMS ------------------------------------------------------------------------------------------------- mms::message_store& get_message_store() { return m_message_store; }; const mms::message_store& get_message_store() const { return m_message_store; }; @@ -1325,6 +1335,7 @@ namespace tools bool get_ring(const crypto::chacha_key &key, const crypto::key_image &key_image, std::vector &outs); crypto::chacha_key get_ringdb_key(); void setup_keys(const epee::wipeable_string &password); + size_t get_transfer_details(const crypto::key_image &ki) const; void register_devices(); hw::device& lookup_device(const std::string & device_descriptor); @@ -1479,7 +1490,7 @@ namespace tools }; } BOOST_CLASS_VERSION(tools::wallet2, 28) -BOOST_CLASS_VERSION(tools::wallet2::transfer_details, 11) +BOOST_CLASS_VERSION(tools::wallet2::transfer_details, 12) BOOST_CLASS_VERSION(tools::wallet2::multisig_info, 1) BOOST_CLASS_VERSION(tools::wallet2::multisig_info::LR, 0) BOOST_CLASS_VERSION(tools::wallet2::multisig_tx_set, 1) @@ -1541,6 +1552,10 @@ namespace boost { x.m_key_image_request = false; } + if (ver < 12) + { + x.m_frozen = false; + } } template @@ -1629,8 +1644,17 @@ namespace boost } a & x.m_key_image_request; if (ver < 11) + { + initialize_transfer_details(a, x, ver); return; + } a & x.m_uses; + if (ver < 12) + { + initialize_transfer_details(a, x, ver); + return; + } + a & x.m_frozen; } template