aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/wallet/wallet2.cpp106
-rw-r--r--src/wallet/wallet2.h24
-rw-r--r--src/wallet/wallet_rpc_server.cpp14
-rw-r--r--tests/CMakeLists.txt10
-rw-r--r--tests/data/wallet_00fd416abin0 -> 2329810 bytes
-rw-r--r--tests/data/wallet_00fd416a.keysbin0 -> 1679 bytes
-rw-r--r--tests/unit_tests/CMakeLists.txt1
-rw-r--r--tests/unit_tests/wallet_storage.cpp266
8 files changed, 373 insertions, 48 deletions
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp
index 42e2f1d41..c5c66d4e5 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, uint64_t max_blocks, bool try_incremental)
{
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);
@@ -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);
}
@@ -6311,22 +6333,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 +6379,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 +6388,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);
+ }
+
+ if (!same_file || force_rewrite_keys)
+ {
bool r = store_keys(m_keys_file, password, false);
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 +6414,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 +6427,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 +6449,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 +6472,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
diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h
index baeffe096..11f55d6d4 100644
--- a/src/wallet/wallet2.h
+++ b/src/wallet/wallet2.h
@@ -940,22 +940,32 @@ private:
/*!
* \brief store_to Stores wallet to another file(s), deleting old ones
* \param path Path to the wallet file (keys and address filenames will be generated based on this filename)
- * \param password Password to protect new wallet (TODO: probably better save the password in the wallet object?)
+ * \param password Password that currently locks the wallet
+ * \param force_rewrite_keys if true, always rewrite keys file
+ *
+ * Leave both "path" and "password" blank to restore the cache file to the current position in the disk
+ * (which is the same as calling `store()`). If you want to store the wallet with a new password,
+ * use the method `change_password()`.
+ *
+ * Normally the keys file is not overwritten when storing, except when force_rewrite_keys is true
+ * or when `path` is a new wallet file.
+ *
+ * \throw error::invalid_password If storing keys file and old password is incorrect
*/
- void store_to(const std::string &path, const epee::wipeable_string &password);
+ void store_to(const std::string &path, const epee::wipeable_string &password, bool force_rewrite_keys = false);
/*!
* \brief get_keys_file_data Get wallet keys data which can be stored to a wallet file.
- * \param password Password of the encrypted wallet buffer (TODO: probably better save the password in the wallet object?)
+ * \param password Password that currently locks the wallet
* \param watch_only true to include only view key, false to include both spend and view keys
* \return Encrypted wallet keys data which can be stored to a wallet file
+ * \throw error::invalid_password if password does not match current wallet
*/
boost::optional<wallet2::keys_file_data> get_keys_file_data(const epee::wipeable_string& password, bool watch_only);
/*!
* \brief get_cache_file_data Get wallet cache data which can be stored to a wallet file.
- * \param password Password to protect the wallet cache data (TODO: probably better save the password in the wallet object?)
- * \return Encrypted wallet cache data which can be stored to a wallet file
+ * \return Encrypted wallet cache data which can be stored to a wallet file (using current password)
*/
- boost::optional<wallet2::cache_file_data> get_cache_file_data(const epee::wipeable_string& password);
+ boost::optional<wallet2::cache_file_data> get_cache_file_data();
std::string path() const;
@@ -1048,7 +1058,7 @@ private:
bool is_deprecated() const;
void refresh(bool trusted_daemon);
void refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blocks_fetched);
- void refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blocks_fetched, bool& received_money, bool check_pool = true, bool try_incremental = true);
+ void refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blocks_fetched, bool& received_money, bool check_pool = true, uint64_t max_blocks = std::numeric_limits<uint64_t>::max(), bool try_incremental = true);
bool refresh(bool trusted_daemon, uint64_t & blocks_fetched, bool& received_money, bool& ok);
void set_refresh_type(RefreshType refresh_type) { m_refresh_type = refresh_type; }
diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp
index 8119d9d09..f08ae82d6 100644
--- a/src/wallet/wallet_rpc_server.cpp
+++ b/src/wallet/wallet_rpc_server.cpp
@@ -60,6 +60,7 @@ using namespace epee;
#define MONERO_DEFAULT_LOG_CATEGORY "wallet.rpc"
#define DEFAULT_AUTO_REFRESH_PERIOD 20 // seconds
+#define REFRESH_INFICATIVE_BLOCK_CHUNK_SIZE 256 // just to split refresh in separate calls to play nicer with other threads
#define CHECK_MULTISIG_ENABLED() \
do \
@@ -79,6 +80,7 @@ namespace
const command_line::arg_descriptor<bool> arg_restricted = {"restricted-rpc", "Restricts to view-only commands", false};
const command_line::arg_descriptor<std::string> arg_wallet_dir = {"wallet-dir", "Directory for newly created wallets"};
const command_line::arg_descriptor<bool> arg_prompt_for_password = {"prompt-for-password", "Prompts for password when not provided", false};
+ const command_line::arg_descriptor<bool> arg_no_initial_sync = {"no-initial-sync", "Skips the initial sync before listening for connections", false};
constexpr const char default_rpc_username[] = "monero";
@@ -152,11 +154,14 @@ namespace tools
uint64_t blocks_fetched = 0;
try {
bool received_money = false;
- if (m_wallet) m_wallet->refresh(m_wallet->is_trusted_daemon(), 0, blocks_fetched, received_money, true, true);
+ if (m_wallet) m_wallet->refresh(m_wallet->is_trusted_daemon(), 0, blocks_fetched, received_money, true, REFRESH_INFICATIVE_BLOCK_CHUNK_SIZE, true);
} catch (const std::exception& ex) {
LOG_ERROR("Exception at while refreshing, what=" << ex.what());
}
- m_last_auto_refresh_time = boost::posix_time::microsec_clock::universal_time();
+ // if we got the max amount of blocks, do not set the last refresh time, we did only part of the refresh and will
+ // continue asap, and only set the last refresh time once the refresh is actually finished
+ if (blocks_fetched < REFRESH_INFICATIVE_BLOCK_CHUNK_SIZE)
+ m_last_auto_refresh_time = boost::posix_time::microsec_clock::universal_time();
return true;
}, 1000);
m_net_server.add_idle_handler([this](){
@@ -4554,6 +4559,7 @@ public:
const auto password_file = command_line::get_arg(vm, arg_password_file);
const auto prompt_for_password = command_line::get_arg(vm, arg_prompt_for_password);
const auto password_prompt = prompt_for_password ? password_prompter : nullptr;
+ const auto no_initial_sync = command_line::get_arg(vm, arg_no_initial_sync);
if(!wallet_file.empty() && !from_json.empty())
{
@@ -4622,7 +4628,8 @@ public:
try
{
- wal->refresh(wal->is_trusted_daemon());
+ if (!no_initial_sync)
+ wal->refresh(wal->is_trusted_daemon());
}
catch (const std::exception& e)
{
@@ -4733,6 +4740,7 @@ int main(int argc, char** argv) {
command_line::add_arg(desc_params, arg_wallet_dir);
command_line::add_arg(desc_params, arg_prompt_for_password);
command_line::add_arg(desc_params, arg_rpc_client_secret_key);
+ command_line::add_arg(desc_params, arg_no_initial_sync);
daemonizer::init_options(hidden_options, desc_params);
desc_params.add(hidden_options);
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 2cabb1ba5..e074ceed6 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -72,14 +72,8 @@ else ()
include_directories(SYSTEM "${CMAKE_CURRENT_SOURCE_DIR}/gtest/include")
endif (GTest_FOUND)
-file(COPY
- data/wallet_9svHk1.keys
- data/wallet_9svHk1
- data/outputs
- data/unsigned_monero_tx
- data/signed_monero_tx
- data/sha256sum
- DESTINATION data)
+message(STATUS "Copying test data directory...")
+file(COPY data DESTINATION .) # Copy data directory from source root to build root
if (CMAKE_BUILD_TYPE STREQUAL "fuzz" OR OSSFUZZ)
add_subdirectory(fuzz)
diff --git a/tests/data/wallet_00fd416a b/tests/data/wallet_00fd416a
new file mode 100644
index 000000000..a1b7898e6
--- /dev/null
+++ b/tests/data/wallet_00fd416a
Binary files differ
diff --git a/tests/data/wallet_00fd416a.keys b/tests/data/wallet_00fd416a.keys
new file mode 100644
index 000000000..6908cce1b
--- /dev/null
+++ b/tests/data/wallet_00fd416a.keys
Binary files differ
diff --git a/tests/unit_tests/CMakeLists.txt b/tests/unit_tests/CMakeLists.txt
index 147b38dd4..fec36803e 100644
--- a/tests/unit_tests/CMakeLists.txt
+++ b/tests/unit_tests/CMakeLists.txt
@@ -97,6 +97,7 @@ set(unit_tests_sources
output_selection.cpp
vercmp.cpp
ringdb.cpp
+ wallet_storage.cpp
wipeable_string.cpp
is_hdd.cpp
aligned.cpp
diff --git a/tests/unit_tests/wallet_storage.cpp b/tests/unit_tests/wallet_storage.cpp
new file mode 100644
index 000000000..dacaff960
--- /dev/null
+++ b/tests/unit_tests/wallet_storage.cpp
@@ -0,0 +1,266 @@
+// Copyright (c) 2023, 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.
+
+#include "unit_tests_utils.h"
+#include "gtest/gtest.h"
+
+#include "file_io_utils.h"
+#include "wallet/wallet2.h"
+
+using namespace boost::filesystem;
+using namespace epee::file_io_utils;
+
+static constexpr const char WALLET_00fd416a_PRIMARY_ADDRESS[] =
+ "45p2SngJAPSJbqSiUvYfS3BfhEdxZmv8pDt25oW1LzxrZv9Uq6ARagiFViMGUE3gJk5VPWingCXVf1p2tyAy6SUeSHPhbve";
+
+TEST(wallet_storage, store_to_file2file)
+{
+ const path source_wallet_file = unit_test::data_dir / "wallet_00fd416a";
+ const path interm_wallet_file = unit_test::data_dir / "wallet_00fd416a_copy_file2file";
+ const path target_wallet_file = unit_test::data_dir / "wallet_00fd416a_new_file2file";
+
+ ASSERT_TRUE(is_file_exist(source_wallet_file.string()));
+ ASSERT_TRUE(is_file_exist(source_wallet_file.string() + ".keys"));
+
+ copy_file(source_wallet_file, interm_wallet_file, copy_option::overwrite_if_exists);
+ copy_file(source_wallet_file.string() + ".keys", interm_wallet_file.string() + ".keys", copy_option::overwrite_if_exists);
+
+ ASSERT_TRUE(is_file_exist(interm_wallet_file.string()));
+ ASSERT_TRUE(is_file_exist(interm_wallet_file.string() + ".keys"));
+
+ if (is_file_exist(target_wallet_file.string()))
+ remove(target_wallet_file);
+ if (is_file_exist(target_wallet_file.string() + ".keys"))
+ remove(target_wallet_file.string() + ".keys");
+ ASSERT_FALSE(is_file_exist(target_wallet_file.string()));
+ ASSERT_FALSE(is_file_exist(target_wallet_file.string() + ".keys"));
+
+ epee::wipeable_string password("beepbeep");
+
+ const auto files_are_expected = [&]()
+ {
+ EXPECT_FALSE(is_file_exist(interm_wallet_file.string()));
+ EXPECT_FALSE(is_file_exist(interm_wallet_file.string() + ".keys"));
+ EXPECT_TRUE(is_file_exist(target_wallet_file.string()));
+ EXPECT_TRUE(is_file_exist(target_wallet_file.string() + ".keys"));
+ };
+
+ {
+ tools::wallet2 w;
+ w.load(interm_wallet_file.string(), password);
+ const std::string primary_address = w.get_address_as_str();
+ EXPECT_EQ(WALLET_00fd416a_PRIMARY_ADDRESS, primary_address);
+ w.store_to(target_wallet_file.string(), password);
+ files_are_expected();
+ }
+
+ files_are_expected();
+
+ {
+ tools::wallet2 w;
+ w.load(target_wallet_file.string(), password);
+ const std::string primary_address = w.get_address_as_str();
+ EXPECT_EQ(WALLET_00fd416a_PRIMARY_ADDRESS, primary_address);
+ w.store_to("", "");
+ files_are_expected();
+ }
+
+ files_are_expected();
+}
+
+TEST(wallet_storage, store_to_mem2file)
+{
+ const path target_wallet_file = unit_test::data_dir / "wallet_mem2file";
+
+ if (is_file_exist(target_wallet_file.string()))
+ remove(target_wallet_file);
+ if (is_file_exist(target_wallet_file.string() + ".keys"))
+ remove(target_wallet_file.string() + ".keys");
+ ASSERT_FALSE(is_file_exist(target_wallet_file.string()));
+ ASSERT_FALSE(is_file_exist(target_wallet_file.string() + ".keys"));
+
+ epee::wipeable_string password("beepbeep2");
+
+ {
+ tools::wallet2 w;
+ w.generate("", password);
+ w.store_to(target_wallet_file.string(), password);
+
+ EXPECT_TRUE(is_file_exist(target_wallet_file.string()));
+ EXPECT_TRUE(is_file_exist(target_wallet_file.string() + ".keys"));
+ }
+
+ EXPECT_TRUE(is_file_exist(target_wallet_file.string()));
+ EXPECT_TRUE(is_file_exist(target_wallet_file.string() + ".keys"));
+
+ {
+ tools::wallet2 w;
+ w.load(target_wallet_file.string(), password);
+
+ EXPECT_TRUE(is_file_exist(target_wallet_file.string()));
+ EXPECT_TRUE(is_file_exist(target_wallet_file.string() + ".keys"));
+ }
+
+ EXPECT_TRUE(is_file_exist(target_wallet_file.string()));
+ EXPECT_TRUE(is_file_exist(target_wallet_file.string() + ".keys"));
+}
+
+TEST(wallet_storage, change_password_same_file)
+{
+ const path source_wallet_file = unit_test::data_dir / "wallet_00fd416a";
+ const path interm_wallet_file = unit_test::data_dir / "wallet_00fd416a_copy_change_password_same";
+
+ ASSERT_TRUE(is_file_exist(source_wallet_file.string()));
+ ASSERT_TRUE(is_file_exist(source_wallet_file.string() + ".keys"));
+
+ copy_file(source_wallet_file, interm_wallet_file, copy_option::overwrite_if_exists);
+ copy_file(source_wallet_file.string() + ".keys", interm_wallet_file.string() + ".keys", copy_option::overwrite_if_exists);
+
+ ASSERT_TRUE(is_file_exist(interm_wallet_file.string()));
+ ASSERT_TRUE(is_file_exist(interm_wallet_file.string() + ".keys"));
+
+ epee::wipeable_string old_password("beepbeep");
+ epee::wipeable_string new_password("meepmeep");
+
+ {
+ tools::wallet2 w;
+ w.load(interm_wallet_file.string(), old_password);
+ const std::string primary_address = w.get_address_as_str();
+ EXPECT_EQ(WALLET_00fd416a_PRIMARY_ADDRESS, primary_address);
+ w.change_password(w.get_wallet_file(), old_password, new_password);
+ }
+
+ {
+ tools::wallet2 w;
+ w.load(interm_wallet_file.string(), new_password);
+ const std::string primary_address = w.get_address_as_str();
+ EXPECT_EQ(WALLET_00fd416a_PRIMARY_ADDRESS, primary_address);
+ }
+
+ {
+ tools::wallet2 w;
+ EXPECT_THROW(w.load(interm_wallet_file.string(), old_password), tools::error::invalid_password);
+ }
+}
+
+TEST(wallet_storage, change_password_different_file)
+{
+ const path source_wallet_file = unit_test::data_dir / "wallet_00fd416a";
+ const path interm_wallet_file = unit_test::data_dir / "wallet_00fd416a_copy_change_password_diff";
+ const path target_wallet_file = unit_test::data_dir / "wallet_00fd416a_new_change_password_diff";
+
+ ASSERT_TRUE(is_file_exist(source_wallet_file.string()));
+ ASSERT_TRUE(is_file_exist(source_wallet_file.string() + ".keys"));
+
+ copy_file(source_wallet_file, interm_wallet_file, copy_option::overwrite_if_exists);
+ copy_file(source_wallet_file.string() + ".keys", interm_wallet_file.string() + ".keys", copy_option::overwrite_if_exists);
+
+ ASSERT_TRUE(is_file_exist(interm_wallet_file.string()));
+ ASSERT_TRUE(is_file_exist(interm_wallet_file.string() + ".keys"));
+
+ if (is_file_exist(target_wallet_file.string()))
+ remove(target_wallet_file);
+ if (is_file_exist(target_wallet_file.string() + ".keys"))
+ remove(target_wallet_file.string() + ".keys");
+ ASSERT_FALSE(is_file_exist(target_wallet_file.string()));
+ ASSERT_FALSE(is_file_exist(target_wallet_file.string() + ".keys"));
+
+ epee::wipeable_string old_password("beepbeep");
+ epee::wipeable_string new_password("meepmeep");
+
+ {
+ tools::wallet2 w;
+ w.load(interm_wallet_file.string(), old_password);
+ const std::string primary_address = w.get_address_as_str();
+ EXPECT_EQ(WALLET_00fd416a_PRIMARY_ADDRESS, primary_address);
+ w.change_password(target_wallet_file.string(), old_password, new_password);
+ }
+
+ EXPECT_FALSE(is_file_exist(interm_wallet_file.string()));
+ EXPECT_FALSE(is_file_exist(interm_wallet_file.string() + ".keys"));
+ EXPECT_TRUE(is_file_exist(target_wallet_file.string()));
+ EXPECT_TRUE(is_file_exist(target_wallet_file.string() + ".keys"));
+
+ {
+ tools::wallet2 w;
+ w.load(target_wallet_file.string(), new_password);
+ const std::string primary_address = w.get_address_as_str();
+ EXPECT_EQ(WALLET_00fd416a_PRIMARY_ADDRESS, primary_address);
+ }
+}
+
+TEST(wallet_storage, change_password_in_memory)
+{
+ const epee::wipeable_string password1("monero");
+ const epee::wipeable_string password2("means money");
+ const epee::wipeable_string password_wrong("is traceable");
+
+ tools::wallet2 w;
+ w.generate("", password1);
+ const std::string primary_address_1 = w.get_address_as_str();
+ w.change_password("", password1, password2);
+ const std::string primary_address_2 = w.get_address_as_str();
+ EXPECT_EQ(primary_address_1, primary_address_2);
+
+ EXPECT_THROW(w.change_password("", password_wrong, password1), tools::error::invalid_password);
+}
+
+TEST(wallet_storage, change_password_mem2file)
+{
+ const path target_wallet_file = unit_test::data_dir / "wallet_change_password_mem2file";
+
+ if (is_file_exist(target_wallet_file.string()))
+ remove(target_wallet_file);
+ if (is_file_exist(target_wallet_file.string() + ".keys"))
+ remove(target_wallet_file.string() + ".keys");
+ ASSERT_FALSE(is_file_exist(target_wallet_file.string()));
+ ASSERT_FALSE(is_file_exist(target_wallet_file.string() + ".keys"));
+
+ const epee::wipeable_string password1("https://safecurves.cr.yp.to/rigid.html");
+ const epee::wipeable_string password2(
+ "https://csrc.nist.gov/csrc/media/projects/crypto-standards-development-process/documents/dualec_in_x982_and_sp800-90.pdf");
+
+ std::string primary_address_1, primary_address_2;
+ {
+ tools::wallet2 w;
+ w.generate("", password1);
+ primary_address_1 = w.get_address_as_str();
+ w.change_password(target_wallet_file.string(), password1, password2);
+ }
+
+ EXPECT_TRUE(is_file_exist(target_wallet_file.string()));
+ EXPECT_TRUE(is_file_exist(target_wallet_file.string() + ".keys"));
+
+ {
+ tools::wallet2 w;
+ w.load(target_wallet_file.string(), password2);
+ primary_address_2 = w.get_address_as_str();
+ }
+
+ EXPECT_EQ(primary_address_1, primary_address_2);
+}