diff options
Diffstat (limited to 'src/wallet/wallet2.cpp')
-rw-r--r-- | src/wallet/wallet2.cpp | 142 |
1 files changed, 107 insertions, 35 deletions
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 42e2f1d41..0218c8292 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -1003,6 +1003,24 @@ uint64_t num_priv_multisig_keys_post_setup(uint64_t threshold, uint64_t total) return n_multisig_keys; } +/** + * @brief Derives the chacha key to encrypt wallet cache files given the chacha key to encrypt the wallet keys files + * + * @param keys_data_key the chacha key that encrypts wallet keys files + * @return crypto::chacha_key the chacha key that encrypts the wallet cache files + */ +crypto::chacha_key derive_cache_key(const crypto::chacha_key& keys_data_key) +{ + static_assert(HASH_SIZE == sizeof(crypto::chacha_key), "Mismatched sizes of hash and chacha key"); + + crypto::chacha_key cache_key; + epee::mlocked<tools::scrubbed_arr<char, HASH_SIZE+1>> cache_key_data; + memcpy(cache_key_data.data(), &keys_data_key, HASH_SIZE); + cache_key_data[HASH_SIZE] = config::HASH_KEY_WALLET_CACHE; + cn_fast_hash(cache_key_data.data(), HASH_SIZE+1, (crypto::hash&) cache_key); + + return cache_key; +} //----------------------------------------------------------------- } //namespace @@ -3901,7 +3919,7 @@ std::shared_ptr<std::map<std::pair<uint64_t, uint64_t>, size_t>> wallet2::create return cache; } //---------------------------------------------------------------------------------------------------- -void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blocks_fetched, bool& received_money, bool check_pool, bool try_incremental) +void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blocks_fetched, bool& received_money, bool check_pool, bool try_incremental, uint64_t max_blocks) { if (m_offline) { @@ -3996,7 +4014,7 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo // infer when we get an incoming output bool first = true, last = false; - while(m_run.load(std::memory_order_relaxed)) + while(m_run.load(std::memory_order_relaxed) && blocks_fetched < max_blocks) { uint64_t next_blocks_start_height; std::vector<cryptonote::block_complete_entry> next_blocks; @@ -4406,6 +4424,10 @@ boost::optional<wallet2::keys_file_data> wallet2::get_keys_file_data(const epee: crypto::chacha_key key; crypto::generate_chacha_key(password.data(), password.size(), key, m_kdf_rounds); + // We use m_cache_key as a deterministic test to see if given key corresponds to original password + const crypto::chacha_key cache_key = derive_cache_key(key); + THROW_WALLET_EXCEPTION_IF(cache_key != m_cache_key, error::invalid_password); + if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only) { account.encrypt_viewkey(key); @@ -4438,7 +4460,7 @@ boost::optional<wallet2::keys_file_data> wallet2::get_keys_file_data(const epee: value2.SetInt(m_key_device_type); json.AddMember("key_on_device", value2, json.GetAllocator()); - value2.SetInt(watch_only ? 1 :0); // WTF ? JSON has different true and false types, and not boolean ?? + value2.SetInt((watch_only || m_watch_only) ? 1 :0); // WTF ? JSON has different true and false types, and not boolean ?? json.AddMember("watch_only", value2, json.GetAllocator()); value2.SetInt(m_multisig ? 1 :0); @@ -4630,11 +4652,8 @@ void wallet2::setup_keys(const epee::wipeable_string &password) m_account.decrypt_viewkey(key); } - static_assert(HASH_SIZE == sizeof(crypto::chacha_key), "Mismatched sizes of hash and chacha key"); - epee::mlocked<tools::scrubbed_arr<char, HASH_SIZE+1>> cache_key_data; - memcpy(cache_key_data.data(), &key, HASH_SIZE); - cache_key_data[HASH_SIZE] = config::HASH_KEY_WALLET_CACHE; - cn_fast_hash(cache_key_data.data(), HASH_SIZE+1, (crypto::hash&)m_cache_key); + m_cache_key = derive_cache_key(key); + get_ringdb_key(); } //---------------------------------------------------------------------------------------------------- @@ -4643,9 +4662,8 @@ void wallet2::change_password(const std::string &filename, const epee::wipeable_ if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only) decrypt_keys(original_password); setup_keys(new_password); - rewrite(filename, new_password); if (!filename.empty()) - store(); + store_to(filename, new_password, true); // force rewrite keys file to possible new location } //---------------------------------------------------------------------------------------------------- /*! @@ -5151,6 +5169,10 @@ void wallet2::encrypt_keys(const crypto::chacha_key &key) void wallet2::decrypt_keys(const crypto::chacha_key &key) { + // We use m_cache_key as a deterministic test to see if given key corresponds to original password + const crypto::chacha_key cache_key = derive_cache_key(key); + THROW_WALLET_EXCEPTION_IF(cache_key != m_cache_key, error::invalid_password); + m_account.encrypt_viewkey(key); m_account.decrypt_keys(key); } @@ -6222,6 +6244,20 @@ void wallet2::load(const std::string& wallet_, const epee::wipeable_string& pass if (!m_persistent_rpc_client_id) set_rpc_client_secret_key(rct::rct2sk(rct::skGen())); + // Wallets used to wipe, but not erase, old unused multisig key info, which lead to huge memory leaks. + // Here we erase these multisig keys if they're zero'd out to free up space. + for (auto &td : m_transfers) + { + auto mk_it = td.m_multisig_k.begin(); + while (mk_it != td.m_multisig_k.end()) + { + if (*mk_it == rct::zero()) + mk_it = td.m_multisig_k.erase(mk_it); + else + ++mk_it; + } + } + cryptonote::block genesis; generate_genesis(genesis); crypto::hash genesis_hash = get_block_hash(genesis); @@ -6311,22 +6347,32 @@ void wallet2::store() store_to("", epee::wipeable_string()); } //---------------------------------------------------------------------------------------------------- -void wallet2::store_to(const std::string &path, const epee::wipeable_string &password) +void wallet2::store_to(const std::string &path, const epee::wipeable_string &password, bool force_rewrite_keys) { trim_hashchain(); + const bool had_old_wallet_files = !m_wallet_file.empty(); + THROW_WALLET_EXCEPTION_IF(!had_old_wallet_files && path.empty(), error::wallet_internal_error, + "Cannot resave wallet to current file since wallet was not loaded from file to begin with"); + // if file is the same, we do: - // 1. save wallet to the *.new file - // 2. remove old wallet file - // 3. rename *.new to wallet_name + // 1. overwrite the keys file iff force_rewrite_keys is specified + // 2. save cache to the *.new file + // 3. rename *.new to wallet_name, replacing old cache file + // else we do: + // 1. prepare new file names with "path" variable + // 2. store new keys files + // 3. remove old keys file + // 4. store new cache file + // 5. remove old cache file // handle if we want just store wallet state to current files (ex store() replacement); - bool same_file = true; - if (!path.empty()) + bool same_file = had_old_wallet_files && path.empty(); + if (had_old_wallet_files && !path.empty()) { - std::string canonical_path = boost::filesystem::canonical(m_wallet_file).string(); - size_t pos = canonical_path.find(path); - same_file = pos != std::string::npos; + const std::string canonical_old_path = boost::filesystem::canonical(m_wallet_file).string(); + const std::string canonical_new_path = boost::filesystem::weakly_canonical(path).string(); + same_file = canonical_old_path == canonical_new_path; } @@ -6347,7 +6393,7 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas } // get wallet cache data - boost::optional<wallet2::cache_file_data> cache_file_data = get_cache_file_data(password); + boost::optional<wallet2::cache_file_data> cache_file_data = get_cache_file_data(); THROW_WALLET_EXCEPTION_IF(cache_file_data == boost::none, error::wallet_internal_error, "failed to generate wallet cache data"); const std::string new_file = same_file ? m_wallet_file + ".new" : path; @@ -6356,12 +6402,20 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas const std::string old_address_file = m_wallet_file + ".address.txt"; const std::string old_mms_file = m_mms_file; - // save keys to the new file - // if we here, main wallet file is saved and we only need to save keys and address files - if (!same_file) { + if (!same_file) + { prepare_file_names(path); - bool r = store_keys(m_keys_file, password, false); + } + + if (!same_file || force_rewrite_keys) + { + bool r = store_keys(m_keys_file, password, m_watch_only); THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); + } + + if (!same_file && had_old_wallet_files) + { + bool r = false; if (boost::filesystem::exists(old_address_file)) { // save address to the new file @@ -6374,11 +6428,6 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas LOG_ERROR("error removing file: " << old_address_file); } } - // remove old wallet file - r = boost::filesystem::remove(old_file); - if (!r) { - LOG_ERROR("error removing file: " << old_file); - } // remove old keys file r = boost::filesystem::remove(old_keys_file); if (!r) { @@ -6392,8 +6441,9 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas LOG_ERROR("error removing file: " << old_mms_file); } } - } else { - // save to new file + } + + // Save cache to new file. If storing to the same file, the temp path has the ".new" extension #ifdef WIN32 // On Windows avoid using std::ofstream which does not work with UTF-8 filenames // The price to pay is temporary higher memory consumption for string stream + binary archive @@ -6413,10 +6463,20 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas THROW_WALLET_EXCEPTION_IF(!success || !ostr.good(), error::file_save_error, new_file); #endif + if (same_file) + { // here we have "*.new" file, we need to rename it to be without ".new" std::error_code e = tools::replace_file(new_file, m_wallet_file); THROW_WALLET_EXCEPTION_IF(e, error::file_save_error, m_wallet_file, e); } + else if (!same_file && had_old_wallet_files) + { + // remove old wallet file + bool r = boost::filesystem::remove(old_file); + if (!r) { + LOG_ERROR("error removing file: " << old_file); + } + } if (m_message_store.get_active()) { @@ -6426,7 +6486,7 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas } } //---------------------------------------------------------------------------------------------------- -boost::optional<wallet2::cache_file_data> wallet2::get_cache_file_data(const epee::wipeable_string &passwords) +boost::optional<wallet2::cache_file_data> wallet2::get_cache_file_data() { trim_hashchain(); try @@ -7077,7 +7137,10 @@ void wallet2::commit_tx(pending_tx& ptx) // tx generated, get rid of used k values for (size_t idx: ptx.selected_transfers) + { memwipe(m_transfers[idx].m_multisig_k.data(), m_transfers[idx].m_multisig_k.size() * sizeof(m_transfers[idx].m_multisig_k[0])); + m_transfers[idx].m_multisig_k.clear(); + } //fee includes dust if dust policy specified it. LOG_PRINT_L1("Transaction successfully sent. <" << txid << ">" << ENDL @@ -7581,7 +7644,10 @@ std::string wallet2::save_multisig_tx(multisig_tx_set txs) // txes generated, get rid of used k values for (size_t n = 0; n < txs.m_ptx.size(); ++n) for (size_t idx: txs.m_ptx[n].construction_data.selected_transfers) + { memwipe(m_transfers[idx].m_multisig_k.data(), m_transfers[idx].m_multisig_k.size() * sizeof(m_transfers[idx].m_multisig_k[0])); + m_transfers[idx].m_multisig_k.clear(); + } // zero out some data we don't want to share for (auto &ptx: txs.m_ptx) @@ -7905,7 +7971,10 @@ bool wallet2::sign_multisig_tx(multisig_tx_set &exported_txs, std::vector<crypto // inputs in the transactions worked on here) for (size_t n = 0; n < exported_txs.m_ptx.size(); ++n) for (size_t idx: exported_txs.m_ptx[n].construction_data.selected_transfers) + { memwipe(m_transfers[idx].m_multisig_k.data(), m_transfers[idx].m_multisig_k.size() * sizeof(m_transfers[idx].m_multisig_k[0])); + m_transfers[idx].m_multisig_k.clear(); + } exported_txs.m_signers.insert(get_multisig_signer_public_key()); @@ -10928,7 +10997,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp else { LOG_PRINT_L2("We made a tx, adjusting fee and saving it, we need " << print_money(needed_fee) << " and we have " << print_money(test_ptx.fee)); - while (needed_fee > test_ptx.fee) { + do { if (use_rct) transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, unlock_time, needed_fee, extra, test_tx, test_ptx, rct_config, use_view_tags); @@ -10939,7 +11008,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp needed_fee = calculate_fee(use_per_byte_fee, test_ptx.tx, txBlob.size(), base_fee, fee_quantization_mask); LOG_PRINT_L2("Made an attempt at a final " << get_weight_string(test_ptx.tx, txBlob.size()) << " tx, with " << print_money(test_ptx.fee) << " fee and " << print_money(test_ptx.change_dts.amount) << " change"); - } + } while (needed_fee > test_ptx.fee); LOG_PRINT_L2("Made a final " << get_weight_string(test_ptx.tx, txBlob.size()) << " tx, with " << print_money(test_ptx.fee) << " fee and " << print_money(test_ptx.change_dts.amount) << " change"); @@ -11335,7 +11404,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton THROW_WALLET_EXCEPTION_IF(needed_fee > available_for_fee, error::wallet_internal_error, "Transaction cannot pay for itself"); do { - LOG_PRINT_L2("We made a tx, adjusting fee and saving it"); + LOG_PRINT_L2("We made a tx, adjusting fee and saving it, we need " << print_money(needed_fee) << " and we have " << print_money(test_ptx.fee)); // distribute total transferred amount between outputs uint64_t amount_transferred = available_for_fee - needed_fee; uint64_t dt_amount = amount_transferred / outputs; @@ -14220,7 +14289,10 @@ cryptonote::blobdata wallet2::export_multisig() transfer_details &td = m_transfers[n]; crypto::key_image ki; if (td.m_multisig_k.size()) + { memwipe(td.m_multisig_k.data(), td.m_multisig_k.size() * sizeof(td.m_multisig_k[0])); + td.m_multisig_k.clear(); + } info[n].m_LR.clear(); info[n].m_partial_key_images.clear(); |