From 468daa9d8d9b823b2aafb70671930cc40904fba6 Mon Sep 17 00:00:00 2001 From: jeffro256 Date: Wed, 21 Jun 2023 21:55:14 +0200 Subject: [PATCH] wallet2_basic: robust compat lib for loading/storing legacy wallets This library has no dependency on wallet2.h and gives us a way forward to move away from `wallet2` in the (not-so-distant) future, while still supporting conversions of old wallet files. This lib is also useful if you have an application where you want to extract information directly from the wallet file with or without having to setup accounts and devices. This is now possible because I have split the wallet keys loading into two steps: `load_from_memory` and `setup_account_and_devices`. When one is loading a wallet keys file, the user of the API can choose whether or not to contact external devices during this process with use of the flag `allow_external_devices_setup`. --- CMakeLists.txt | 1 + .../polymorphic_portable_binary_iarchive.h | 56 + .../polymorphic_portable_binary_oarchive.h | 56 + src/wallet/CMakeLists.txt | 3 + src/wallet/wallet2.cpp | 1020 +++------------ src/wallet/wallet2.h | 1112 ++--------------- src/wallet/wallet2_basic/CMakeLists.txt | 72 ++ .../wallet2_boost_serialization.cpp | 39 + .../wallet2_boost_serialization.h | 559 +++++++++ src/wallet/wallet2_basic/wallet2_constants.h | 42 + src/wallet/wallet2_basic/wallet2_storage.cpp | 730 +++++++++++ src/wallet/wallet2_basic/wallet2_storage.h | 297 +++++ src/wallet/wallet2_basic/wallet2_types.h | 335 +++++ src/wallet/wallet_errors.h | 6 +- tests/core_tests/wallet_tools.cpp | 24 +- tests/core_tests/wallet_tools.h | 4 +- .../functional_tests/functional_tests_rpc.py | 3 + tests/functional_tests/wallet.py | 18 + tests/unit_tests/CMakeLists.txt | 1 + tests/unit_tests/serialization.cpp | 51 +- tests/unit_tests/unit_tests_utils.h | 9 + tests/unit_tests/wallet2_storage.cpp | 151 +++ 22 files changed, 2689 insertions(+), 1900 deletions(-) create mode 100644 src/serialization/polymorphic_portable_binary_iarchive.h create mode 100644 src/serialization/polymorphic_portable_binary_oarchive.h create mode 100644 src/wallet/wallet2_basic/CMakeLists.txt create mode 100644 src/wallet/wallet2_basic/wallet2_boost_serialization.cpp create mode 100644 src/wallet/wallet2_basic/wallet2_boost_serialization.h create mode 100644 src/wallet/wallet2_basic/wallet2_constants.h create mode 100644 src/wallet/wallet2_basic/wallet2_storage.cpp create mode 100644 src/wallet/wallet2_basic/wallet2_storage.h create mode 100644 src/wallet/wallet2_basic/wallet2_types.h create mode 100644 tests/unit_tests/wallet2_storage.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 1f634c1149..47654db738 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1094,6 +1094,7 @@ endif() include_directories(SYSTEM ${Boost_INCLUDE_DIRS}) if(MINGW) set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Wa,-mbig-obj") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wa,-mbig-obj") set(EXTRA_LIBRARIES mswsock;ws2_32;iphlpapi;crypt32;bcrypt) if(DEPENDS) set(ICU_LIBRARIES iconv) diff --git a/src/serialization/polymorphic_portable_binary_iarchive.h b/src/serialization/polymorphic_portable_binary_iarchive.h new file mode 100644 index 0000000000..12439f6104 --- /dev/null +++ b/src/serialization/polymorphic_portable_binary_iarchive.h @@ -0,0 +1,56 @@ +// 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. +// + +#pragma once + +#include +#include +#include + +namespace boost +{ +namespace archive +{ +class polymorphic_portable_binary_iarchive : + public detail::polymorphic_iarchive_route +{ +public: + polymorphic_portable_binary_iarchive(std::istream & is, unsigned int flags = 0) : + detail::polymorphic_iarchive_route(is, flags) + {} + ~polymorphic_portable_binary_iarchive() {} +}; + +} // namespace archive +} // namespace boost + +// required by export +BOOST_SERIALIZATION_REGISTER_ARCHIVE( + boost::archive::polymorphic_portable_binary_iarchive +) diff --git a/src/serialization/polymorphic_portable_binary_oarchive.h b/src/serialization/polymorphic_portable_binary_oarchive.h new file mode 100644 index 0000000000..aa6b03738f --- /dev/null +++ b/src/serialization/polymorphic_portable_binary_oarchive.h @@ -0,0 +1,56 @@ +// 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. +// + +#pragma once + +#include +#include +#include + +namespace boost +{ +namespace archive +{ +class polymorphic_portable_binary_oarchive : + public detail::polymorphic_oarchive_route +{ +public: + polymorphic_portable_binary_oarchive(std::ostream & os, unsigned int flags = 0) : + detail::polymorphic_oarchive_route(os, flags) + {} + ~polymorphic_portable_binary_oarchive() {} +}; + +} // namespace archive +} // namespace boost + +// required by export +BOOST_SERIALIZATION_REGISTER_ARCHIVE( + boost::archive::polymorphic_portable_binary_oarchive +) diff --git a/src/wallet/CMakeLists.txt b/src/wallet/CMakeLists.txt index 48c7f1c5b2..6a127a7755 100644 --- a/src/wallet/CMakeLists.txt +++ b/src/wallet/CMakeLists.txt @@ -28,6 +28,8 @@ # include (${PROJECT_SOURCE_DIR}/cmake/libutils.cmake) +add_subdirectory(wallet2_basic) + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) set(wallet_sources @@ -55,6 +57,7 @@ target_link_libraries(wallet mnemonics device_trezor net + wallet2_basic ${LMDB_LIBRARY} ${Boost_CHRONO_LIBRARY} ${Boost_SERIALIZATION_LIBRARY} diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 78c0f63282..fc0273002d 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -39,7 +39,6 @@ #include #include #include -#include #include #include #include @@ -52,6 +51,7 @@ using namespace epee; #include "hardforks/hardforks.h" #include "cryptonote_core/tx_sanity_check.h" #include "wallet2.h" +#include "wallet2_basic/wallet2_constants.h" #include "wallet_args.h" #include "cryptonote_basic/cryptonote_format_utils.h" #include "net/parse.h" @@ -99,6 +99,7 @@ extern "C" using namespace std; using namespace crypto; using namespace cryptonote; +using namespace wallet2_basic; #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "wallet.wallet2" @@ -122,9 +123,6 @@ using namespace cryptonote; #define SECOND_OUTPUT_RELATEDNESS_THRESHOLD 0.0f -#define SUBADDRESS_LOOKAHEAD_MAJOR 50 -#define SUBADDRESS_LOOKAHEAD_MINOR 200 - #define KEY_IMAGE_EXPORT_FILE_MAGIC "Monero key image export\003" #define MULTISIG_EXPORT_FILE_MAGIC "Monero multisig export\001" @@ -144,13 +142,88 @@ using namespace cryptonote; #define DEFAULT_MIN_OUTPUT_COUNT 5 #define DEFAULT_MIN_OUTPUT_VALUE (2*COIN) -#define DEFAULT_INACTIVITY_LOCK_TIMEOUT 90 // a minute and a half - #define IGNORE_LONG_PAYMENT_ID_FROM_BLOCK_VERSION 12 #define DEFAULT_UNLOCK_TIME (CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE * DIFFICULTY_TARGET_V2) #define RECENT_SPEND_WINDOW (15 * DIFFICULTY_TARGET_V2) +// Map legacy wallet2 cache field names to new m_cache object +#define m_blockchain m_cache.m_blockchain +#define m_transfers m_cache.m_transfers +#define m_account_public_address m_cache.m_account_public_address +#define m_key_images m_cache.m_key_images +#define m_unconfirmed_txs m_cache.m_unconfirmed_txs +#define m_payments m_cache.m_payments +#define m_tx_keys m_cache.m_tx_keys +#define m_confirmed_txs m_cache.m_confirmed_txs +#define m_tx_notes m_cache.m_tx_notes +#define m_unconfirmed_payments m_cache.m_unconfirmed_payments +#define m_pub_keys m_cache.m_pub_keys +#define m_address_book m_cache.m_address_book +#define m_scanned_pool_txs m_cache.m_scanned_pool_txs +#define m_subaddresses m_cache.m_subaddresses +#define m_subaddress_labels m_cache.m_subaddress_labels +#define m_attributes m_cache.m_attributes +#define m_account_tags m_cache.m_account_tags +#define m_ring_history_saved m_cache.m_ring_history_saved +#define m_last_block_reward m_cache.m_last_block_reward +#define m_tx_device m_cache.m_tx_device +#define m_device_last_key_image_sync m_cache.m_device_last_key_image_sync +#define m_cold_key_images m_cache.m_cold_key_images +#define m_has_ever_refreshed_from_node m_cache.m_has_ever_refreshed_from_node + +// Map legacy wallet2 keys field names to new m_keys_data object +#define m_account m_keys_data.m_account +#define is_old_file_format m_keys_data.is_old_file_format +#define m_watch_only m_keys_data.m_watch_only +#define m_multisig m_keys_data.m_multisig +#define seed_language m_keys_data.seed_language +#define m_nettype m_keys_data.m_nettype +#define m_multisig_threshold m_keys_data.m_multisig_threshold +#define m_multisig_signers m_keys_data.m_multisig_signers +#define m_multisig_rounds_passed m_keys_data.m_multisig_rounds_passed +#define m_multisig_derivations m_keys_data.m_multisig_derivations +#define m_always_confirm_transfers m_keys_data.m_always_confirm_transfers +#define m_print_ring_members m_keys_data.m_print_ring_members +#define m_store_tx_info m_keys_data.m_store_tx_info +#define m_default_mixin m_keys_data.m_default_mixin +#define m_default_priority m_keys_data.m_default_priority +#define m_auto_refresh m_keys_data.m_auto_refresh +#define m_refresh_type m_keys_data.m_refresh_type +#define m_refresh_from_block_height m_keys_data.m_refresh_from_block_height +#define m_skip_to_height m_keys_data.m_skip_to_height +#define m_confirm_non_default_ring_size m_keys_data.m_confirm_non_default_ring_size +#define m_ask_password m_keys_data.m_ask_password +#define m_max_reorg_depth m_keys_data.m_max_reorg_depth +#define m_min_output_count m_keys_data.m_min_output_count +#define m_min_output_value m_keys_data.m_min_output_value +#define m_merge_destinations m_keys_data.m_merge_destinations +#define m_confirm_backlog m_keys_data.m_confirm_backlog +#define m_confirm_backlog_threshold m_keys_data.m_confirm_backlog_threshold +#define m_confirm_export_overwrite m_keys_data.m_confirm_export_overwrite +#define m_auto_low_priority m_keys_data.m_auto_low_priority +#define m_segregate_pre_fork_outputs m_keys_data.m_segregate_pre_fork_outputs +#define m_key_reuse_mitigation2 m_keys_data.m_key_reuse_mitigation2 +#define m_segregation_height m_keys_data.m_segregation_height +#define m_ignore_fractional_outputs m_keys_data.m_ignore_fractional_outputs +#define m_ignore_outputs_above m_keys_data.m_ignore_outputs_above +#define m_ignore_outputs_below m_keys_data.m_ignore_outputs_below +#define m_track_uses m_keys_data.m_track_uses +#define m_show_wallet_name_when_locked m_keys_data.m_show_wallet_name_when_locked +#define m_inactivity_lock_timeout m_keys_data.m_inactivity_lock_timeout +#define m_setup_background_mining m_keys_data.m_setup_background_mining +#define m_subaddress_lookahead_major m_keys_data.m_subaddress_lookahead_major +#define m_subaddress_lookahead_minor m_keys_data.m_subaddress_lookahead_minor +#define m_original_keys_available m_keys_data.m_original_keys_available +#define m_original_address m_keys_data.m_original_address +#define m_original_view_secret_key m_keys_data.m_original_view_secret_key +#define m_export_format m_keys_data.m_export_format +#define m_load_deprecated_formats m_keys_data.m_load_deprecated_formats +#define m_device_name m_keys_data.m_device_name +#define m_device_derivation_path m_keys_data.m_device_derivation_path +#define m_enable_multisig m_keys_data.m_enable_multisig +#define m_allow_mismatched_daemon_version m_keys_data.m_allow_mismatched_daemon_version + static const std::string MULTISIG_SIGNATURE_MAGIC = "SigMultisigPkV1"; static const std::string ASCII_OUTPUT_MAGIC = "MoneroAsciiDataV1"; @@ -1156,67 +1229,23 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended, std m_run(true), m_callback(0), m_trusted_daemon(false), - m_nettype(nettype), - m_multisig_rounds_passed(0), - m_always_confirm_transfers(true), - m_print_ring_members(false), - m_store_tx_info(true), - m_default_mixin(0), - m_default_priority(0), - m_refresh_type(RefreshOptimizeCoinbase), - m_auto_refresh(true), m_first_refresh_done(false), - m_refresh_from_block_height(0), m_explicit_refresh_from_block_height(true), - m_skip_to_height(0), - m_ask_password(AskPasswordToDecrypt), - m_max_reorg_depth(ORPHANED_BLOCKS_MAX_COUNT), - m_min_output_count(0), - m_min_output_value(0), - m_merge_destinations(false), - m_confirm_backlog(true), - m_confirm_backlog_threshold(0), - m_confirm_export_overwrite(true), - m_auto_low_priority(true), - m_segregate_pre_fork_outputs(true), - m_key_reuse_mitigation2(true), - m_segregation_height(0), - m_ignore_fractional_outputs(true), - m_ignore_outputs_above(MONEY_SUPPLY), - m_ignore_outputs_below(0), - m_track_uses(false), - m_show_wallet_name_when_locked(false), - m_inactivity_lock_timeout(DEFAULT_INACTIVITY_LOCK_TIMEOUT), - m_setup_background_mining(BackgroundMiningMaybe), m_is_initialized(false), m_kdf_rounds(kdf_rounds), - is_old_file_format(false), - m_watch_only(false), - m_multisig(false), - m_multisig_threshold(0), m_node_rpc_proxy(*m_http_client, m_daemon_rpc_mutex), - m_account_public_address{crypto::null_pkey, crypto::null_pkey}, - m_subaddress_lookahead_major(SUBADDRESS_LOOKAHEAD_MAJOR), - m_subaddress_lookahead_minor(SUBADDRESS_LOOKAHEAD_MINOR), - m_original_keys_available(false), m_message_store(http_client_factory->create()), - m_key_device_type(hw::device::device_type::SOFTWARE), - m_ring_history_saved(false), m_ringdb(), - m_last_block_reward(0), m_unattended(unattended), m_devices_registered(false), - m_device_last_key_image_sync(0), m_use_dns(true), m_offline(false), m_rpc_version(0), - m_export_format(ExportFormat::Binary), - m_load_deprecated_formats(false), - m_enable_multisig(false), m_pool_info_query_time(0), - m_has_ever_refreshed_from_node(false), - m_allow_mismatched_daemon_version(false) + m_cache(), + m_keys_data() { + m_nettype = nettype; } wallet2::~wallet2() @@ -4262,7 +4291,7 @@ bool wallet2::clear() m_unconfirmed_txs.clear(); m_payments.clear(); m_tx_keys.clear(); - m_additional_tx_keys.clear(); + m_cache.m_additional_tx_keys.clear(); m_confirmed_txs.clear(); m_unconfirmed_payments.clear(); m_scanned_pool_txs[0].clear(); @@ -4308,13 +4337,26 @@ void wallet2::clear_soft(bool keep_key_images) */ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable_string& password, bool watch_only) { - boost::optional keys_file_data = get_keys_file_data(password, watch_only); - CHECK_AND_ASSERT_MES(keys_file_data != boost::none, false, "failed to generate wallet keys data"); + const crypto::chacha_key keys_key = keys_data::pwd_to_keys_data_key(password.data(), password.size(), m_kdf_rounds); + + const bool just_spend_key_is_encrypted = m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only; + cryptonote::account_base old_account; + if (just_spend_key_is_encrypted) + { + old_account = m_account; + m_account.encrypt_viewkey(keys_key); + m_account.decrypt_keys(keys_key); + } + + const std::string keys_buf = m_keys_data.store_to_memory(keys_key, watch_only); + + if (just_spend_key_is_encrypted) + { + m_account = old_account; + } std::string tmp_file_name = keys_file_name + ".new"; - std::string buf; - bool r = ::serialization::dump_binary(keys_file_data.get(), buf); - r = r && save_to_file(tmp_file_name, buf); + bool r = save_to_file(tmp_file_name, keys_buf); CHECK_AND_ASSERT_MES(r, false, "failed to generate wallet keys file " << tmp_file_name); unlock_keys_file(); @@ -4330,235 +4372,10 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable return true; } //---------------------------------------------------------------------------------------------------- -boost::optional wallet2::get_keys_file_data(const epee::wipeable_string& password, bool watch_only) -{ - epee::byte_slice account_data; - std::string multisig_signers; - std::string multisig_derivations; - cryptonote::account_base account = m_account; - - crypto::chacha_key key; - crypto::generate_chacha_key(password.data(), password.size(), key, m_kdf_rounds); - - if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only) - { - account.encrypt_viewkey(key); - account.decrypt_keys(key); - } - - if (watch_only) - account.forget_spend_key(); - - account.encrypt_keys(key); - - bool r = epee::serialization::store_t_to_binary(account, account_data); - CHECK_AND_ASSERT_MES(r, boost::none, "failed to serialize wallet keys"); - boost::optional keys_file_data = (wallet2::keys_file_data) {}; - - // Create a JSON object with "key_data" and "seed_language" as keys. - rapidjson::Document json; - json.SetObject(); - rapidjson::Value value(rapidjson::kStringType); - value.SetString(reinterpret_cast(account_data.data()), account_data.size()); - json.AddMember("key_data", value, json.GetAllocator()); - if (!seed_language.empty()) - { - value.SetString(seed_language.c_str(), seed_language.length()); - json.AddMember("seed_language", value, json.GetAllocator()); - } - - rapidjson::Value value2(rapidjson::kNumberType); - - 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 ?? - json.AddMember("watch_only", value2, json.GetAllocator()); - - value2.SetInt(m_multisig ? 1 :0); - json.AddMember("multisig", value2, json.GetAllocator()); - - value2.SetUint(m_multisig_threshold); - json.AddMember("multisig_threshold", value2, json.GetAllocator()); - - if (m_multisig) - { - bool r = ::serialization::dump_binary(m_multisig_signers, multisig_signers); - CHECK_AND_ASSERT_MES(r, boost::none, "failed to serialize wallet multisig signers"); - value.SetString(multisig_signers.c_str(), multisig_signers.length()); - json.AddMember("multisig_signers", value, json.GetAllocator()); - - r = ::serialization::dump_binary(m_multisig_derivations, multisig_derivations); - CHECK_AND_ASSERT_MES(r, boost::none, "failed to serialize wallet multisig derivations"); - value.SetString(multisig_derivations.c_str(), multisig_derivations.length()); - json.AddMember("multisig_derivations", value, json.GetAllocator()); - - value2.SetUint(m_multisig_rounds_passed); - json.AddMember("multisig_rounds_passed", value2, json.GetAllocator()); - } - - value2.SetInt(m_always_confirm_transfers ? 1 :0); - json.AddMember("always_confirm_transfers", value2, json.GetAllocator()); - - value2.SetInt(m_print_ring_members ? 1 :0); - json.AddMember("print_ring_members", value2, json.GetAllocator()); - - value2.SetInt(m_store_tx_info ? 1 :0); - json.AddMember("store_tx_info", value2, json.GetAllocator()); - - value2.SetUint(m_default_mixin); - json.AddMember("default_mixin", value2, json.GetAllocator()); - - value2.SetUint(m_default_priority); - json.AddMember("default_priority", value2, json.GetAllocator()); - - value2.SetInt(m_auto_refresh ? 1 :0); - json.AddMember("auto_refresh", value2, json.GetAllocator()); - - value2.SetInt(m_refresh_type); - json.AddMember("refresh_type", value2, json.GetAllocator()); - - value2.SetUint64(m_refresh_from_block_height); - json.AddMember("refresh_height", value2, json.GetAllocator()); - - value2.SetUint64(m_skip_to_height); - json.AddMember("skip_to_height", value2, json.GetAllocator()); - - value2.SetInt(1); // exists for deserialization backwards compatibility - json.AddMember("confirm_non_default_ring_size", value2, json.GetAllocator()); - - value2.SetInt(m_ask_password); - json.AddMember("ask_password", value2, json.GetAllocator()); - - value2.SetUint64(m_max_reorg_depth); - json.AddMember("max_reorg_depth", value2, json.GetAllocator()); - - value2.SetUint(m_min_output_count); - json.AddMember("min_output_count", value2, json.GetAllocator()); - - value2.SetUint64(m_min_output_value); - json.AddMember("min_output_value", value2, json.GetAllocator()); - - value2.SetInt(cryptonote::get_default_decimal_point()); - json.AddMember("default_decimal_point", value2, json.GetAllocator()); - - value2.SetInt(m_merge_destinations ? 1 :0); - json.AddMember("merge_destinations", value2, json.GetAllocator()); - - value2.SetInt(m_confirm_backlog ? 1 :0); - json.AddMember("confirm_backlog", value2, json.GetAllocator()); - - value2.SetUint(m_confirm_backlog_threshold); - json.AddMember("confirm_backlog_threshold", value2, json.GetAllocator()); - - value2.SetInt(m_confirm_export_overwrite ? 1 :0); - json.AddMember("confirm_export_overwrite", value2, json.GetAllocator()); - - value2.SetInt(m_auto_low_priority ? 1 : 0); - json.AddMember("auto_low_priority", value2, json.GetAllocator()); - - value2.SetUint(m_nettype); - json.AddMember("nettype", value2, json.GetAllocator()); - - value2.SetInt(m_segregate_pre_fork_outputs ? 1 : 0); - json.AddMember("segregate_pre_fork_outputs", value2, json.GetAllocator()); - - value2.SetInt(m_key_reuse_mitigation2 ? 1 : 0); - json.AddMember("key_reuse_mitigation2", value2, json.GetAllocator()); - - value2.SetUint(m_segregation_height); - json.AddMember("segregation_height", value2, json.GetAllocator()); - - value2.SetInt(m_ignore_fractional_outputs ? 1 : 0); - json.AddMember("ignore_fractional_outputs", value2, json.GetAllocator()); - - value2.SetUint64(m_ignore_outputs_above); - json.AddMember("ignore_outputs_above", value2, json.GetAllocator()); - - value2.SetUint64(m_ignore_outputs_below); - json.AddMember("ignore_outputs_below", value2, json.GetAllocator()); - - value2.SetInt(m_track_uses ? 1 : 0); - json.AddMember("track_uses", value2, json.GetAllocator()); - - value2.SetInt(m_show_wallet_name_when_locked ? 1 : 0); - json.AddMember("show_wallet_name_when_locked", value2, json.GetAllocator()); - - value2.SetInt(m_inactivity_lock_timeout); - json.AddMember("inactivity_lock_timeout", value2, json.GetAllocator()); - - value2.SetInt(m_setup_background_mining); - json.AddMember("setup_background_mining", value2, json.GetAllocator()); - - value2.SetUint(m_subaddress_lookahead_major); - json.AddMember("subaddress_lookahead_major", value2, json.GetAllocator()); - - value2.SetUint(m_subaddress_lookahead_minor); - json.AddMember("subaddress_lookahead_minor", value2, json.GetAllocator()); - - value2.SetInt(m_original_keys_available ? 1 : 0); - json.AddMember("original_keys_available", value2, json.GetAllocator()); - - value2.SetInt(m_export_format); - json.AddMember("export_format", value2, json.GetAllocator()); - - value2.SetInt(m_load_deprecated_formats); - json.AddMember("load_deprecated_formats", value2, json.GetAllocator()); - - value2.SetUint(1); - json.AddMember("encrypted_secret_keys", value2, json.GetAllocator()); - - value.SetString(m_device_name.c_str(), m_device_name.size()); - json.AddMember("device_name", value, json.GetAllocator()); - - value.SetString(m_device_derivation_path.c_str(), m_device_derivation_path.size()); - json.AddMember("device_derivation_path", value, json.GetAllocator()); - - std::string original_address; - std::string original_view_secret_key; - if (m_original_keys_available) - { - original_address = get_account_address_as_str(m_nettype, false, m_original_address); - value.SetString(original_address.c_str(), original_address.length()); - json.AddMember("original_address", value, json.GetAllocator()); - original_view_secret_key = epee::string_tools::pod_to_hex(m_original_view_secret_key); - value.SetString(original_view_secret_key.c_str(), original_view_secret_key.length()); - json.AddMember("original_view_secret_key", value, json.GetAllocator()); - } - - // This value is serialized for compatibility with wallets which support the pay-to-use RPC system - value2.SetInt(0); - json.AddMember("persistent_rpc_client_id", value2, json.GetAllocator()); - - // This value is serialized for compatibility with wallets which support the pay-to-use RPC system - value2.SetFloat(0.0f); - json.AddMember("auto_mine_for_rpc_payment", value2, json.GetAllocator()); - - // This value is serialized for compatibility with wallets which support the pay-to-use RPC system - value2.SetUint64(0); - json.AddMember("credits_target", value2, json.GetAllocator()); - - value2.SetInt(m_enable_multisig ? 1 : 0); - json.AddMember("enable_multisig", value2, json.GetAllocator()); - - // Serialize the JSON object - rapidjson::StringBuffer buffer; - rapidjson::Writer writer(buffer); - json.Accept(writer); - - // Encrypt the entire JSON object. - std::string cipher; - cipher.resize(buffer.GetSize()); - keys_file_data.get().iv = crypto::rand(); - crypto::chacha20(buffer.GetString(), buffer.GetSize(), key, keys_file_data.get().iv, &cipher[0]); - keys_file_data.get().account_data = cipher; - return keys_file_data; -} -//---------------------------------------------------------------------------------------------------- void wallet2::setup_keys(const epee::wipeable_string &password) { - crypto::chacha_key key; - crypto::generate_chacha_key(password.data(), password.size(), key, m_kdf_rounds); + const crypto::chacha_key key = keys_data::pwd_to_keys_data_key( + password.data(), password.size(), m_kdf_rounds); // re-encrypt, but keep viewkey unencrypted if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only) @@ -4567,11 +4384,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> 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 = cache::pwd_to_cache_key(password.data(), password.size(), m_kdf_rounds); + get_ringdb_key(); } //---------------------------------------------------------------------------------------------------- @@ -4624,368 +4438,26 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st } //---------------------------------------------------------------------------------------------------- bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_string& password, boost::optional& keys_to_encrypt) { + try + { + const crypto::chacha_key keys_key = keys_data::pwd_to_keys_data_key(password.data(), password.size(), m_kdf_rounds); + m_keys_data = keys_data::load_from_memory(keys_buf, keys_key, m_nettype); + m_keys_data.setup_account_keys_and_devices(keys_key, get_device_callback()); - // Decrypt the contents - rapidjson::Document json; - wallet2::keys_file_data keys_file_data; - bool encrypted_secret_keys = false; - bool r = ::serialization::parse_binary(keys_buf, keys_file_data); - THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "internal error: failed to deserialize keys buffer"); - crypto::chacha_key key; - crypto::generate_chacha_key(password.data(), password.size(), key, m_kdf_rounds); - std::string account_data; - account_data.resize(keys_file_data.account_data.size()); - crypto::chacha20(keys_file_data.account_data.data(), keys_file_data.account_data.size(), key, keys_file_data.iv, &account_data[0]); - if (json.Parse(account_data.c_str()).HasParseError() || !json.IsObject()) - crypto::chacha8(keys_file_data.account_data.data(), keys_file_data.account_data.size(), key, keys_file_data.iv, &account_data[0]); - // The contents should be JSON if the wallet follows the new format. - if (json.Parse(account_data.c_str()).HasParseError()) - { - is_old_file_format = true; - m_watch_only = false; - m_multisig = false; - m_multisig_threshold = 0; - m_multisig_signers.clear(); - m_multisig_rounds_passed = 0; - m_multisig_derivations.clear(); - m_always_confirm_transfers = false; - m_print_ring_members = false; - m_store_tx_info = true; - m_default_mixin = 0; - m_default_priority = 0; - m_auto_refresh = true; - m_refresh_type = RefreshType::RefreshDefault; - m_refresh_from_block_height = 0; - m_skip_to_height = 0; - m_ask_password = AskPasswordToDecrypt; - cryptonote::set_default_decimal_point(CRYPTONOTE_DISPLAY_DECIMAL_POINT); - m_max_reorg_depth = ORPHANED_BLOCKS_MAX_COUNT; - m_min_output_count = 0; - m_min_output_value = 0; - m_merge_destinations = false; - m_confirm_backlog = true; - m_confirm_backlog_threshold = 0; - m_confirm_export_overwrite = true; - m_auto_low_priority = true; - m_segregate_pre_fork_outputs = true; - m_key_reuse_mitigation2 = true; - m_segregation_height = 0; - m_ignore_fractional_outputs = true; - m_ignore_outputs_above = MONEY_SUPPLY; - m_ignore_outputs_below = 0; - m_track_uses = false; - m_show_wallet_name_when_locked = false; - m_inactivity_lock_timeout = DEFAULT_INACTIVITY_LOCK_TIMEOUT; - m_setup_background_mining = BackgroundMiningMaybe; - m_subaddress_lookahead_major = SUBADDRESS_LOOKAHEAD_MAJOR; - m_subaddress_lookahead_minor = SUBADDRESS_LOOKAHEAD_MINOR; - m_original_keys_available = false; - m_export_format = ExportFormat::Binary; - m_load_deprecated_formats = false; - m_device_name = ""; - m_device_derivation_path = ""; - m_key_device_type = hw::device::device_type::SOFTWARE; - encrypted_secret_keys = false; - m_enable_multisig = false; - m_allow_mismatched_daemon_version = false; - } - else if(json.IsObject()) - { - if (!json.HasMember("key_data")) - { - LOG_ERROR("Field key_data not found in JSON"); - return false; - } - if (!json["key_data"].IsString()) - { - LOG_ERROR("Field key_data found in JSON, but not String"); - return false; - } - const char *field_key_data = json["key_data"].GetString(); - account_data = std::string(field_key_data, field_key_data + json["key_data"].GetStringLength()); - - if (json.HasMember("key_on_device")) - { - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, key_on_device, int, Int, false, hw::device::device_type::SOFTWARE); - m_key_device_type = static_cast(field_key_on_device); - } - - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, seed_language, std::string, String, false, std::string()); - if (field_seed_language_found) - { - set_seed_language(field_seed_language); - } - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, watch_only, int, Int, false, false); - m_watch_only = field_watch_only; - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, multisig, int, Int, false, false); - m_multisig = field_multisig; - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, multisig_threshold, unsigned int, Uint, m_multisig, 0); - m_multisig_threshold = field_multisig_threshold; - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, multisig_rounds_passed, unsigned int, Uint, false, 0); - m_multisig_rounds_passed = field_multisig_rounds_passed; - if (m_multisig) - { - if (!json.HasMember("multisig_signers")) - { - LOG_ERROR("Field multisig_signers not found in JSON"); - return false; - } - if (!json["multisig_signers"].IsString()) - { - LOG_ERROR("Field multisig_signers found in JSON, but not String"); - return false; - } - const char *field_multisig_signers = json["multisig_signers"].GetString(); - std::string multisig_signers = std::string(field_multisig_signers, field_multisig_signers + json["multisig_signers"].GetStringLength()); - r = ::serialization::parse_binary(multisig_signers, m_multisig_signers); - if (!r) - { - LOG_ERROR("Field multisig_signers found in JSON, but failed to parse"); - return false; - } - - //previous version of multisig does not have this field - if (json.HasMember("multisig_derivations")) - { - if (!json["multisig_derivations"].IsString()) - { - LOG_ERROR("Field multisig_derivations found in JSON, but not String"); - return false; - } - const char *field_multisig_derivations = json["multisig_derivations"].GetString(); - std::string multisig_derivations = std::string(field_multisig_derivations, field_multisig_derivations + json["multisig_derivations"].GetStringLength()); - r = ::serialization::parse_binary(multisig_derivations, m_multisig_derivations); - if (!r) - { - LOG_ERROR("Field multisig_derivations found in JSON, but failed to parse"); - return false; - } - } - } - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, always_confirm_transfers, int, Int, false, true); - m_always_confirm_transfers = field_always_confirm_transfers; - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, print_ring_members, int, Int, false, true); - m_print_ring_members = field_print_ring_members; - if (json.HasMember("store_tx_info")) - { - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, store_tx_info, int, Int, true, true); - m_store_tx_info = field_store_tx_info; - } - else if (json.HasMember("store_tx_keys")) // backward compatibility - { - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, store_tx_keys, int, Int, true, true); - m_store_tx_info = field_store_tx_keys; - } - else - m_store_tx_info = true; - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, default_mixin, unsigned int, Uint, false, 0); - m_default_mixin = field_default_mixin; - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, default_priority, unsigned int, Uint, false, 0); - if (field_default_priority_found) + if (!m_keys_data.keys_were_encrypted_on_load()) { - m_default_priority = field_default_priority; - } - else - { - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, default_fee_multiplier, unsigned int, Uint, false, 0); - if (field_default_fee_multiplier_found) - m_default_priority = field_default_fee_multiplier; - else - m_default_priority = 0; - } - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, auto_refresh, int, Int, false, true); - m_auto_refresh = field_auto_refresh; - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, refresh_type, int, Int, false, RefreshType::RefreshDefault); - m_refresh_type = RefreshType::RefreshDefault; - if (field_refresh_type_found) - { - if (field_refresh_type == RefreshFull || field_refresh_type == RefreshOptimizeCoinbase || field_refresh_type == RefreshNoCoinbase) - m_refresh_type = (RefreshType)field_refresh_type; - else - LOG_PRINT_L0("Unknown refresh-type value (" << field_refresh_type << "), using default"); - } - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, refresh_height, uint64_t, Uint64, false, 0); - m_refresh_from_block_height = field_refresh_height; - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, skip_to_height, uint64_t, Uint64, false, 0); - m_skip_to_height = field_skip_to_height; - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, ask_password, AskPasswordType, Int, false, AskPasswordToDecrypt); - m_ask_password = field_ask_password; - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, default_decimal_point, int, Int, false, CRYPTONOTE_DISPLAY_DECIMAL_POINT); - cryptonote::set_default_decimal_point(field_default_decimal_point); - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, max_reorg_depth, uint64_t, Uint64, false, ORPHANED_BLOCKS_MAX_COUNT); - m_max_reorg_depth = field_max_reorg_depth; - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, min_output_count, uint32_t, Uint, false, 0); - m_min_output_count = field_min_output_count; - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, min_output_value, uint64_t, Uint64, false, 0); - m_min_output_value = field_min_output_value; - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, merge_destinations, int, Int, false, false); - m_merge_destinations = field_merge_destinations; - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, confirm_backlog, int, Int, false, true); - m_confirm_backlog = field_confirm_backlog; - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, confirm_backlog_threshold, uint32_t, Uint, false, 0); - m_confirm_backlog_threshold = field_confirm_backlog_threshold; - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, confirm_export_overwrite, int, Int, false, true); - m_confirm_export_overwrite = field_confirm_export_overwrite; - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, auto_low_priority, int, Int, false, true); - m_auto_low_priority = field_auto_low_priority; - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, nettype, uint8_t, Uint, false, static_cast(m_nettype)); - // The network type given in the program argument is inconsistent with the network type saved in the wallet - THROW_WALLET_EXCEPTION_IF(static_cast(m_nettype) != field_nettype, error::wallet_internal_error, - (boost::format("%s wallet cannot be opened as %s wallet") - % (field_nettype == 0 ? "Mainnet" : field_nettype == 1 ? "Testnet" : "Stagenet") - % (m_nettype == MAINNET ? "mainnet" : m_nettype == TESTNET ? "testnet" : "stagenet")).str()); - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, segregate_pre_fork_outputs, int, Int, false, true); - m_segregate_pre_fork_outputs = field_segregate_pre_fork_outputs; - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, key_reuse_mitigation2, int, Int, false, true); - m_key_reuse_mitigation2 = field_key_reuse_mitigation2; - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, segregation_height, int, Uint, false, 0); - m_segregation_height = field_segregation_height; - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, ignore_fractional_outputs, int, Int, false, true); - m_ignore_fractional_outputs = field_ignore_fractional_outputs; - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, ignore_outputs_above, uint64_t, Uint64, false, MONEY_SUPPLY); - m_ignore_outputs_above = field_ignore_outputs_above; - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, ignore_outputs_below, uint64_t, Uint64, false, 0); - m_ignore_outputs_below = field_ignore_outputs_below; - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, track_uses, int, Int, false, false); - m_track_uses = field_track_uses; - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, show_wallet_name_when_locked, int, Int, false, false); - m_show_wallet_name_when_locked = field_show_wallet_name_when_locked; - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, inactivity_lock_timeout, uint32_t, Uint, false, DEFAULT_INACTIVITY_LOCK_TIMEOUT); - m_inactivity_lock_timeout = field_inactivity_lock_timeout; - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, setup_background_mining, BackgroundMiningSetupType, Int, false, BackgroundMiningMaybe); - m_setup_background_mining = field_setup_background_mining; - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, subaddress_lookahead_major, uint32_t, Uint, false, SUBADDRESS_LOOKAHEAD_MAJOR); - m_subaddress_lookahead_major = field_subaddress_lookahead_major; - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, subaddress_lookahead_minor, uint32_t, Uint, false, SUBADDRESS_LOOKAHEAD_MINOR); - m_subaddress_lookahead_minor = field_subaddress_lookahead_minor; - - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, encrypted_secret_keys, uint32_t, Uint, false, false); - encrypted_secret_keys = field_encrypted_secret_keys; - - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, export_format, ExportFormat, Int, false, Binary); - m_export_format = field_export_format; - - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, load_deprecated_formats, int, Int, false, false); - m_load_deprecated_formats = field_load_deprecated_formats; - - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, device_name, std::string, String, false, std::string()); - if (m_device_name.empty()) - { - if (field_device_name_found) - { - m_device_name = field_device_name; - } - else - { - m_device_name = m_key_device_type == hw::device::device_type::LEDGER ? "Ledger" : "default"; - } + keys_to_encrypt = keys_key; } - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, device_derivation_path, std::string, String, false, std::string()); - m_device_derivation_path = field_device_derivation_path; - - if (json.HasMember("original_keys_available")) - { - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, original_keys_available, int, Int, false, false); - m_original_keys_available = field_original_keys_available; - if (m_original_keys_available) - { - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, original_address, std::string, String, true, std::string()); - address_parse_info info; - bool ok = get_account_address_from_str(info, m_nettype, field_original_address); - if (!ok) - { - LOG_ERROR("Failed to parse original_address from JSON"); - return false; - } - m_original_address = info.address; - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, original_view_secret_key, std::string, String, true, std::string()); - ok = epee::string_tools::hex_to_pod(field_original_view_secret_key, m_original_view_secret_key); - if (!ok) - { - LOG_ERROR("Failed to parse original_view_secret_key from JSON"); - return false; - } - } - } - else - { - m_original_keys_available = false; - } + setup_keys(password); - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, enable_multisig, int, Int, false, false); - m_enable_multisig = field_enable_multisig; + return true; } - else + catch (const std::exception& e) { - THROW_WALLET_EXCEPTION(error::wallet_internal_error, "invalid password"); + MERROR("Error while trying to load keys from buffer: " << e.what()); return false; } - - r = epee::serialization::load_t_from_binary(m_account, account_data); - THROW_WALLET_EXCEPTION_IF(!r, error::invalid_password); - if (m_key_device_type == hw::device::device_type::LEDGER || m_key_device_type == hw::device::device_type::TREZOR) { - LOG_PRINT_L0("Account on device. Initing device..."); - hw::device &hwdev = lookup_device(m_device_name); - THROW_WALLET_EXCEPTION_IF(!hwdev.set_name(m_device_name), error::wallet_internal_error, "Could not set device name " + m_device_name); - hwdev.set_network_type(m_nettype); - hwdev.set_derivation_path(m_device_derivation_path); - hwdev.set_callback(get_device_callback()); - THROW_WALLET_EXCEPTION_IF(!hwdev.init(), error::wallet_internal_error, "Could not initialize the device " + m_device_name); - THROW_WALLET_EXCEPTION_IF(!hwdev.connect(), error::wallet_internal_error, "Could not connect to the device " + m_device_name); - m_account.set_device(hwdev); - - account_public_address device_account_public_address; - bool fetch_device_address = true; - - ::hw::device_cold* dev_cold = nullptr; - if (m_key_device_type == hw::device::device_type::TREZOR && (dev_cold = dynamic_cast<::hw::device_cold*>(&hwdev)) != nullptr) { - THROW_WALLET_EXCEPTION_IF(!dev_cold->get_public_address_with_no_passphrase(device_account_public_address), error::wallet_internal_error, "Cannot get a device address"); - if (device_account_public_address == m_account.get_keys().m_account_address) { - LOG_PRINT_L0("Wallet opened with an empty passphrase"); - fetch_device_address = false; - dev_cold->set_use_empty_passphrase(true); - } else { - fetch_device_address = true; - LOG_PRINT_L0("Wallet opening with an empty passphrase failed. Retry again: " << fetch_device_address); - dev_cold->reset_session(); - } - } - - if (fetch_device_address) { - THROW_WALLET_EXCEPTION_IF(!hwdev.get_public_address(device_account_public_address), error::wallet_internal_error, "Cannot get a device address"); - } - - THROW_WALLET_EXCEPTION_IF(device_account_public_address != m_account.get_keys().m_account_address, error::wallet_internal_error, "Device wallet does not match wallet address. If the device uses the passphrase feature, please check whether the passphrase was entered correctly (it may have been misspelled - different passphrases generate different wallets, passphrase is case-sensitive). " - "Device address: " + cryptonote::get_account_address_as_str(m_nettype, false, device_account_public_address) + - ", wallet address: " + m_account.get_public_address_str(m_nettype)); - LOG_PRINT_L0("Device inited..."); - } else if (key_on_device()) { - THROW_WALLET_EXCEPTION(error::wallet_internal_error, "hardware device not supported"); - } - - if (r) - { - if (encrypted_secret_keys) - { - m_account.decrypt_keys(key); - } - else - { - keys_to_encrypt = key; - } - } - const cryptonote::account_keys& keys = m_account.get_keys(); - hw::device &hwdev = m_account.get_device(); - r = r && hwdev.verify_keys(keys.m_view_secret_key, keys.m_account_address.m_view_public_key); - if (!m_watch_only && !m_multisig && hwdev.device_protocol() != hw::device::PROTOCOL_COLD) - r = r && hwdev.verify_keys(keys.m_spend_secret_key, keys.m_account_address.m_spend_public_key); - THROW_WALLET_EXCEPTION_IF(!r, error::wallet_files_doesnt_correspond, m_keys_file, m_wallet_file); - - if (r) - setup_keys(password); - - return true; } /*! @@ -5022,49 +4494,19 @@ bool wallet2::verify_password(const epee::wipeable_string& password) */ bool wallet2::verify_password(const std::string& keys_file_name, const epee::wipeable_string& password, bool no_spend_key, hw::device &hwdev, uint64_t kdf_rounds) { - rapidjson::Document json; - wallet2::keys_file_data keys_file_data; std::string buf; - bool encrypted_secret_keys = false; bool r = load_from_file(keys_file_name, buf); THROW_WALLET_EXCEPTION_IF(!r, error::file_read_error, keys_file_name); - // Decrypt the contents - r = ::serialization::parse_binary(buf, keys_file_data); - THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "internal error: failed to deserialize \"" + keys_file_name + '\"'); - crypto::chacha_key key; - crypto::generate_chacha_key(password.data(), password.size(), key, kdf_rounds); - std::string account_data; - account_data.resize(keys_file_data.account_data.size()); - crypto::chacha20(keys_file_data.account_data.data(), keys_file_data.account_data.size(), key, keys_file_data.iv, &account_data[0]); - if (json.Parse(account_data.c_str()).HasParseError() || !json.IsObject()) - crypto::chacha8(keys_file_data.account_data.data(), keys_file_data.account_data.size(), key, keys_file_data.iv, &account_data[0]); - - // The contents should be JSON if the wallet follows the new format. - if (json.Parse(account_data.c_str()).HasParseError()) + try { - // old format before JSON wallet key file format + const keys_data kd = keys_data::load_from_memory(buf, password, cryptonote::UNDEFINED, kdf_rounds); + return kd.verify_account_keys(no_spend_key, std::addressof(hwdev)); } - else + catch (...) { - account_data = std::string(json["key_data"].GetString(), json["key_data"].GetString() + - json["key_data"].GetStringLength()); - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, encrypted_secret_keys, uint32_t, Uint, false, false); - encrypted_secret_keys = field_encrypted_secret_keys; + return false; } - - cryptonote::account_base account_data_check; - - r = epee::serialization::load_t_from_binary(account_data_check, account_data); - - if (encrypted_secret_keys) - account_data_check.decrypt_keys(key); - - const cryptonote::account_keys& keys = account_data_check.get_keys(); - r = r && hwdev.verify_keys(keys.m_view_secret_key, keys.m_account_address.m_view_public_key); - if(!no_spend_key) - r = r && hwdev.verify_keys(keys.m_spend_secret_key, keys.m_account_address.m_spend_public_key); - return r; } void wallet2::encrypt_keys(const crypto::chacha_key &key) @@ -5130,46 +4572,20 @@ void wallet2::create_keys_file(const std::string &wallet_, bool watch_only, cons */ bool wallet2::query_device(hw::device::device_type& device_type, const std::string& keys_file_name, const epee::wipeable_string& password, uint64_t kdf_rounds) { - rapidjson::Document json; - wallet2::keys_file_data keys_file_data; - std::string buf; - bool r = load_from_file(keys_file_name, buf); + std::string keys_file_buf; + bool r = load_from_file(keys_file_name, keys_file_buf); THROW_WALLET_EXCEPTION_IF(!r, error::file_read_error, keys_file_name); - // Decrypt the contents - r = ::serialization::parse_binary(buf, keys_file_data); - THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "internal error: failed to deserialize \"" + keys_file_name + '\"'); - crypto::chacha_key key; - crypto::generate_chacha_key(password.data(), password.size(), key, kdf_rounds); - std::string account_data; - account_data.resize(keys_file_data.account_data.size()); - crypto::chacha20(keys_file_data.account_data.data(), keys_file_data.account_data.size(), key, keys_file_data.iv, &account_data[0]); - if (json.Parse(account_data.c_str()).HasParseError() || !json.IsObject()) - crypto::chacha8(keys_file_data.account_data.data(), keys_file_data.account_data.size(), key, keys_file_data.iv, &account_data[0]); - - device_type = hw::device::device_type::SOFTWARE; - // The contents should be JSON if the wallet follows the new format. - if (json.Parse(account_data.c_str()).HasParseError()) + try { - // old format before JSON wallet key file format + const keys_data kd = keys_data::load_from_memory(keys_file_buf, password, cryptonote::UNDEFINED, kdf_rounds); + device_type = kd.m_key_device_type; + return true; } - else + catch (...) { - account_data = std::string(json["key_data"].GetString(), json["key_data"].GetString() + - json["key_data"].GetStringLength()); - - if (json.HasMember("key_on_device")) - { - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, key_on_device, int, Int, false, hw::device::device_type::SOFTWARE); - device_type = static_cast(field_key_on_device); - } + return false; } - - cryptonote::account_base account_data_check; - - r = epee::serialization::load_t_from_binary(account_data_check, account_data); - if (!r) return false; - return true; } void wallet2::init_type(hw::device::device_type device_type) @@ -5180,7 +4596,7 @@ void wallet2::init_type(hw::device::device_type device_type) m_multisig_threshold = 0; m_multisig_signers.clear(); m_original_keys_available = false; - m_key_device_type = device_type; + m_keys_data.m_key_device_type = device_type; } /*! @@ -6022,7 +5438,6 @@ void wallet2::load(const std::string& wallet_, const epee::wipeable_string& pass } else { - wallet2::cache_file_data cache_file_data; std::string cache_file_buf; bool r = true; if (use_fs) @@ -6031,100 +5446,9 @@ void wallet2::load(const std::string& wallet_, const epee::wipeable_string& pass THROW_WALLET_EXCEPTION_IF(!r, error::file_read_error, m_wallet_file); } - // try to read it as an encrypted cache - try - { - LOG_PRINT_L1("Trying to decrypt cache data"); - - r = ::serialization::parse_binary(use_fs ? cache_file_buf : cache_buf, cache_file_data); - THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "internal error: failed to deserialize \"" + m_wallet_file + '\"'); - std::string cache_data; - cache_data.resize(cache_file_data.cache_data.size()); - crypto::chacha20(cache_file_data.cache_data.data(), cache_file_data.cache_data.size(), m_cache_key, cache_file_data.iv, &cache_data[0]); - - try { - bool loaded = false; - - try - { - binary_archive ar{epee::strspan(cache_data)}; - if (::serialization::serialize(ar, *this)) - if (::serialization::check_stream_state(ar)) - loaded = true; - if (!loaded) - { - binary_archive ar{epee::strspan(cache_data)}; - ar.enable_varint_bug_backward_compatibility(); - if (::serialization::serialize(ar, *this)) - if (::serialization::check_stream_state(ar)) - loaded = true; - } - } - catch(...) { } + const std::string& cache_buf_to_use = use_fs ? cache_file_buf : cache_buf; + m_cache = cache::load_from_memory(cache_buf_to_use, password, m_account, m_kdf_rounds); - if (!loaded) - { - std::stringstream iss; - iss << cache_data; - boost::archive::portable_binary_iarchive ar(iss); - ar >> *this; - } - } - catch(...) - { - // try with previous scheme: direct from keys - crypto::chacha_key key; - generate_chacha_key_from_secret_keys(key); - crypto::chacha20(cache_file_data.cache_data.data(), cache_file_data.cache_data.size(), key, cache_file_data.iv, &cache_data[0]); - try { - std::stringstream iss; - iss << cache_data; - boost::archive::portable_binary_iarchive ar(iss); - ar >> *this; - } - catch (...) - { - crypto::chacha8(cache_file_data.cache_data.data(), cache_file_data.cache_data.size(), key, cache_file_data.iv, &cache_data[0]); - try - { - std::stringstream iss; - iss << cache_data; - boost::archive::portable_binary_iarchive ar(iss); - ar >> *this; - } - catch (...) - { - LOG_PRINT_L0("Failed to open portable binary, trying unportable"); - if (use_fs) boost::filesystem::copy_file(m_wallet_file, m_wallet_file + ".unportable", boost::filesystem::copy_option::overwrite_if_exists); - std::stringstream iss; - iss.str(""); - iss << cache_data; - boost::archive::binary_iarchive ar(iss); - ar >> *this; - } - } - } - } - catch (...) - { - LOG_PRINT_L1("Failed to load encrypted cache, trying unencrypted"); - try { - std::stringstream iss; - iss << cache_file_buf; - boost::archive::portable_binary_iarchive ar(iss); - ar >> *this; - } - catch (...) - { - LOG_PRINT_L0("Failed to open portable binary, trying unportable"); - if (use_fs) boost::filesystem::copy_file(m_wallet_file, m_wallet_file + ".unportable", boost::filesystem::copy_option::overwrite_if_exists); - std::stringstream iss; - iss.str(""); - iss << cache_file_buf; - boost::archive::binary_iarchive ar(iss); - ar >> *this; - } - } THROW_WALLET_EXCEPTION_IF( m_account_public_address.m_spend_public_key != m_account.get_keys().m_account_address.m_spend_public_key || m_account_public_address.m_view_public_key != m_account.get_keys().m_account_address.m_view_public_key, @@ -6255,10 +5579,6 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas } } - // get wallet cache data - boost::optional cache_file_data = get_cache_file_data(password); - 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; const std::string old_file = m_wallet_file; const std::string old_keys_file = m_keys_file; @@ -6303,24 +5623,9 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas } } else { // save to new file -#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 - std::ostringstream oss; - binary_archive oar(oss); - bool success = ::serialization::serialize(oar, cache_file_data.get()); - if (success) { - success = save_to_file(new_file, oss.str()); - } - THROW_WALLET_EXCEPTION_IF(!success, error::file_save_error, new_file); -#else - std::ofstream ostr; - ostr.open(new_file, std::ios_base::binary | std::ios_base::out | std::ios_base::trunc); - binary_archive oar(ostr); - bool success = ::serialization::serialize(oar, cache_file_data.get()); - ostr.close(); - THROW_WALLET_EXCEPTION_IF(!success || !ostr.good(), error::file_save_error, new_file); -#endif + const std::string cache_buf = m_cache.store_to_memory(m_cache_key); + const bool save_success = epee::file_io_utils::save_string_to_file(new_file, cache_buf); + THROW_WALLET_EXCEPTION_IF(!save_success, error::file_save_error, new_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); @@ -6335,31 +5640,6 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas } } //---------------------------------------------------------------------------------------------------- -boost::optional wallet2::get_cache_file_data(const epee::wipeable_string &passwords) -{ - trim_hashchain(); - try - { - std::stringstream oss; - binary_archive ar(oss); - if (!::serialization::serialize(ar, *this)) - return boost::none; - - boost::optional cache_file_data = (wallet2::cache_file_data) {}; - cache_file_data.get().cache_data = oss.str(); - std::string cipher; - cipher.resize(cache_file_data.get().cache_data.size()); - cache_file_data.get().iv = crypto::rand(); - crypto::chacha20(cache_file_data.get().cache_data.data(), cache_file_data.get().cache_data.size(), m_cache_key, cache_file_data.get().iv, &cipher[0]); - cache_file_data.get().cache_data = cipher; - return cache_file_data; - } - catch(...) - { - return boost::none; - } -} -//---------------------------------------------------------------------------------------------------- uint64_t wallet2::balance(uint32_t index_major, bool strict) const { uint64_t amount = 0; @@ -6968,7 +6248,7 @@ void wallet2::commit_tx(pending_tx& ptx) if (store_tx_info() && ptx.tx_key != crypto::null_skey) { m_tx_keys[txid] = ptx.tx_key; - m_additional_tx_keys[txid] = ptx.additional_tx_keys; + m_cache.m_additional_tx_keys[txid] = ptx.additional_tx_keys; } LOG_PRINT_L2("transaction " << txid << " generated ok and sent to daemon, key_images: [" << ptx.key_images << "]"); @@ -7078,7 +6358,7 @@ bool wallet2::parse_unsigned_tx_from_str(const std::string &unsigned_tx_st, unsi try { std::istringstream iss(s); - boost::archive::portable_binary_iarchive ar(iss); + boost::archive::polymorphic_portable_binary_iarchive ar(iss); ar >> exported_txs; } catch (...) @@ -7100,7 +6380,7 @@ bool wallet2::parse_unsigned_tx_from_str(const std::string &unsigned_tx_st, unsi try { std::istringstream iss(s); - boost::archive::portable_binary_iarchive ar(iss); + boost::archive::polymorphic_portable_binary_iarchive ar(iss); ar >> exported_txs; } catch (...) @@ -7190,7 +6470,7 @@ bool wallet2::sign_tx(unsigned_tx_set &exported_txs, std::vector> signed_txs; } catch (...) @@ -7416,7 +6696,7 @@ bool wallet2::parse_tx_from_str(const std::string &signed_tx_st, std::vector> signed_txs; } catch (...) @@ -7583,7 +6863,7 @@ bool wallet2::parse_multisig_tx_from_str(std::string multisig_tx_st, multisig_tx if (!loaded && m_load_deprecated_formats) { std::istringstream iss(multisig_tx_st); - boost::archive::portable_binary_iarchive ar(iss); + boost::archive::polymorphic_portable_binary_iarchive ar(iss); ar >> exported_txs; loaded = true; } @@ -7637,7 +6917,7 @@ bool wallet2::load_multisig_tx(cryptonote::blobdata s, multisig_tx_set &exported if (store_tx_info()) { m_tx_keys[txid] = ptx.tx_key; - m_additional_tx_keys[txid] = ptx.additional_tx_keys; + m_cache.m_additional_tx_keys[txid] = ptx.additional_tx_keys; } } } @@ -7798,7 +7078,7 @@ bool wallet2::sign_multisig_tx(multisig_tx_set &exported_txs, std::vectorsecond; if (tx_key == crypto::null_skey) return false; - const auto j = m_additional_tx_keys.find(txid); - if (j != m_additional_tx_keys.end()) + const auto j = m_cache.m_additional_tx_keys.find(txid); + if (j != m_cache.m_additional_tx_keys.end()) additional_tx_keys = j->second; return true; } @@ -11150,7 +10430,7 @@ void wallet2::set_tx_key(const crypto::hash &txid, const crypto::secret_key &tx_ find_tx_extra_field_by_type(tx_extra_fields, additional_tx_pub_keys); THROW_WALLET_EXCEPTION_IF(additional_tx_keys.size() != additional_tx_pub_keys.data.size(), error::wallet_internal_error, "The number of additional tx secret keys doesn't agree with the number of additional tx public keys in the blockchain" ); m_tx_keys[txid] = tx_key; - m_additional_tx_keys[txid] = additional_tx_keys; + m_cache.m_additional_tx_keys[txid] = additional_tx_keys; } //---------------------------------------------------------------------------------------------------- std::string wallet2::get_spend_proof(const crypto::hash &txid, const std::string &message) @@ -11987,7 +11267,7 @@ bool wallet2::check_reserve_proof(const cryptonote::account_public_address &addr if (!loaded && m_load_deprecated_formats) { std::istringstream iss(sig_decoded); - boost::archive::portable_binary_iarchive ar(iss); + boost::archive::polymorphic_portable_binary_iarchive ar(iss); ar >> proofs >> subaddr_spendkeys.parent(); } @@ -13311,7 +12591,7 @@ size_t wallet2::import_outputs_from_str(const std::string &outputs_st) { std::stringstream iss; iss << body; - boost::archive::portable_binary_iarchive ar(iss); + boost::archive::polymorphic_portable_binary_iarchive ar(iss); ar >> outputs; loaded = true; } @@ -13577,7 +12857,7 @@ size_t wallet2::import_multisig(std::vector blobs) if (!loaded && m_load_deprecated_formats) { std::istringstream iss(body); - boost::archive::portable_binary_iarchive ar(iss); + boost::archive::polymorphic_portable_binary_iarchive ar(iss); ar >> i; loaded = true; } diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 9c310f6924..75ca6ad7b1 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -39,14 +39,12 @@ #endif #include #include -#include #include #include #include #include "include_base_utils.h" #include "cryptonote_basic/account.h" -#include "cryptonote_basic/account_boost_serialization.h" #include "cryptonote_basic/cryptonote_basic_impl.h" #include "net/http.h" #include "storages/http_abstract_invoke.h" @@ -66,7 +64,7 @@ #include "serialization/tuple.h" #include "serialization/containers.h" -#include "wallet_errors.h" +#include "wallet2_basic/wallet2_boost_serialization.h" #include "common/password.h" #include "node_rpc_proxy.h" #include "message_store.h" @@ -180,44 +178,7 @@ namespace tools } }; - class hashchain - { - public: - hashchain(): m_genesis(crypto::null_hash), m_offset(0) {} - - size_t size() const { return m_blockchain.size() + m_offset; } - size_t offset() const { return m_offset; } - const crypto::hash &genesis() const { return m_genesis; } - void push_back(const crypto::hash &hash) { if (m_offset == 0 && m_blockchain.empty()) m_genesis = hash; m_blockchain.push_back(hash); } - bool is_in_bounds(size_t idx) const { return idx >= m_offset && idx < size(); } - const crypto::hash &operator[](size_t idx) const { return m_blockchain[idx - m_offset]; } - crypto::hash &operator[](size_t idx) { return m_blockchain[idx - m_offset]; } - void crop(size_t height) { m_blockchain.resize(height - m_offset); } - void clear() { m_offset = 0; m_blockchain.clear(); } - bool empty() const { return m_blockchain.empty() && m_offset == 0; } - void trim(size_t height) { while (height > m_offset && m_blockchain.size() > 1) { m_blockchain.pop_front(); ++m_offset; } m_blockchain.shrink_to_fit(); } - void refill(const crypto::hash &hash) { m_blockchain.push_back(hash); --m_offset; } - - template - inline void serialize(t_archive &a, const unsigned int ver) - { - a & m_offset; - a & m_genesis; - a & m_blockchain; - } - - BEGIN_SERIALIZE_OBJECT() - VERSION_FIELD(0) - VARINT_FIELD(m_offset) - FIELD(m_genesis) - FIELD(m_blockchain) - END_SERIALIZE() - - private: - size_t m_offset; - crypto::hash m_genesis; - std::deque m_blockchain; - }; + typedef wallet2_basic::hashchain hashchain; class wallet_keys_unlocker; class wallet2 @@ -229,30 +190,6 @@ namespace tools public: static constexpr const std::chrono::seconds rpc_timeout = std::chrono::minutes(3) + std::chrono::seconds(30); - enum RefreshType { - RefreshFull, - RefreshOptimizeCoinbase, - RefreshNoCoinbase, - RefreshDefault = RefreshOptimizeCoinbase, - }; - - enum AskPasswordType { - AskPasswordNever = 0, - AskPasswordOnAction = 1, - AskPasswordToDecrypt = 2, - }; - - enum BackgroundMiningSetupType { - BackgroundMiningMaybe = 0, - BackgroundMiningYes = 1, - BackgroundMiningNo = 2, - }; - - enum ExportFormat { - Binary = 0, - Ascii, - }; - static const char* tr(const char* str); static bool has_testnet_option(const boost::program_options::variables_map& vm); @@ -278,32 +215,12 @@ namespace tools static bool query_device(hw::device::device_type& device_type, const std::string& keys_file_name, const epee::wipeable_string& password, uint64_t kdf_rounds = 1); wallet2(cryptonote::network_type nettype = cryptonote::MAINNET, uint64_t kdf_rounds = 1, bool unattended = false, std::unique_ptr http_client_factory = std::unique_ptr(new net::http::client_factory())); + wallet2(const wallet2&) = delete; + wallet2(wallet2&&) = delete; + wallet2& operator=(const wallet2&) = delete; + wallet2& operator=(wallet2&&) = delete; ~wallet2(); - struct multisig_info - { - struct LR - { - rct::key m_L; - rct::key m_R; - - BEGIN_SERIALIZE_OBJECT() - FIELD(m_L) - FIELD(m_R) - END_SERIALIZE() - }; - - crypto::public_key m_signer; - std::vector m_LR; - std::vector m_partial_key_images; // one per key the participant has - - BEGIN_SERIALIZE_OBJECT() - FIELD(m_signer) - FIELD(m_LR) - FIELD(m_partial_key_images) - END_SERIALIZE() - }; - struct tx_scan_info_t { cryptonote::keypair in_ephemeral; @@ -317,64 +234,6 @@ namespace tools tx_scan_info_t(): amount(0), money_transfered(0), error(true) {} }; - struct transfer_details - { - uint64_t m_block_height; - cryptonote::transaction_prefix m_tx; - crypto::hash m_txid; - uint64_t m_internal_output_index; - uint64_t m_global_output_index; - bool m_spent; - bool m_frozen; - uint64_t m_spent_height; - crypto::key_image m_key_image; //TODO: key_image stored twice :( - rct::key m_mask; - uint64_t m_amount; - bool m_rct; - bool m_key_image_known; - bool m_key_image_request; // view wallets: we want to request it; cold wallets: it was requested - uint64_t m_pk_index; - cryptonote::subaddress_index m_subaddr_index; - bool m_key_image_partial; - std::vector m_multisig_k; - std::vector m_multisig_info; // one per other participant - std::vector> m_uses; - - bool is_rct() const { return m_rct; } - uint64_t amount() const { return m_amount; } - const crypto::public_key get_public_key() const { - crypto::public_key output_public_key; - THROW_WALLET_EXCEPTION_IF(m_tx.vout.size() <= m_internal_output_index, - error::wallet_internal_error, "Too few outputs, outputs may be corrupted"); - THROW_WALLET_EXCEPTION_IF(!get_output_public_key(m_tx.vout[m_internal_output_index], output_public_key), - error::wallet_internal_error, "Unable to get output public key from output"); - return output_public_key; - }; - - BEGIN_SERIALIZE_OBJECT() - FIELD(m_block_height) - FIELD(m_tx) - FIELD(m_txid) - FIELD(m_internal_output_index) - FIELD(m_global_output_index) - FIELD(m_spent) - FIELD(m_frozen) - FIELD(m_spent_height) - FIELD(m_key_image) - FIELD(m_mask) - FIELD(m_amount) - FIELD(m_rct) - FIELD(m_key_image_known) - FIELD(m_key_image_request) - FIELD(m_pk_index) - FIELD(m_subaddr_index) - FIELD(m_key_image_partial) - FIELD(m_multisig_k) - FIELD(m_multisig_info) - FIELD(m_uses) - END_SERIALIZE() - }; - struct exported_transfer_details { crypto::public_key m_pubkey; @@ -415,32 +274,7 @@ namespace tools END_SERIALIZE() }; - typedef std::vector amounts_container; - struct payment_details - { - crypto::hash m_tx_hash; - uint64_t m_amount; - amounts_container m_amounts; - uint64_t m_fee; - uint64_t m_block_height; - uint64_t m_unlock_time; - uint64_t m_timestamp; - bool m_coinbase; - cryptonote::subaddress_index m_subaddr_index; - - BEGIN_SERIALIZE_OBJECT() - VERSION_FIELD(0) - FIELD(m_tx_hash) - VARINT_FIELD(m_amount) - FIELD(m_amounts) - VARINT_FIELD(m_fee) - VARINT_FIELD(m_block_height) - VARINT_FIELD(m_unlock_time) - VARINT_FIELD(m_timestamp) - FIELD(m_coinbase) - FIELD(m_subaddr_index) - END_SERIALIZE() - }; + typedef wallet2_basic::payment_details payment_details; struct address_tx : payment_details { @@ -448,88 +282,6 @@ namespace tools bool m_incoming; }; - struct pool_payment_details - { - payment_details m_pd; - bool m_double_spend_seen; - - BEGIN_SERIALIZE_OBJECT() - VERSION_FIELD(0) - FIELD(m_pd) - FIELD(m_double_spend_seen) - END_SERIALIZE() - }; - - struct unconfirmed_transfer_details - { - cryptonote::transaction_prefix m_tx; - uint64_t m_amount_in; - uint64_t m_amount_out; - uint64_t m_change; - time_t m_sent_time; - std::vector m_dests; - crypto::hash m_payment_id; - enum { pending, pending_in_pool, failed } m_state; - uint64_t m_timestamp; - uint32_t m_subaddr_account; // subaddress account of your wallet to be used in this transfer - std::set m_subaddr_indices; // set of address indices used as inputs in this transfer - std::vector>> m_rings; // relative - - BEGIN_SERIALIZE_OBJECT() - VERSION_FIELD(1) - FIELD(m_tx) - VARINT_FIELD(m_amount_in) - VARINT_FIELD(m_amount_out) - VARINT_FIELD(m_change) - VARINT_FIELD(m_sent_time) - FIELD(m_dests) - FIELD(m_payment_id) - if (version >= 1) - VARINT_FIELD(m_state) - VARINT_FIELD(m_timestamp) - VARINT_FIELD(m_subaddr_account) - FIELD(m_subaddr_indices) - FIELD(m_rings) - END_SERIALIZE() - }; - - struct confirmed_transfer_details - { - cryptonote::transaction_prefix m_tx; - uint64_t m_amount_in; - uint64_t m_amount_out; - uint64_t m_change; - uint64_t m_block_height; - std::vector m_dests; - crypto::hash m_payment_id; - uint64_t m_timestamp; - uint64_t m_unlock_time; - uint32_t m_subaddr_account; // subaddress account of your wallet to be used in this transfer - std::set m_subaddr_indices; // set of address indices used as inputs in this transfer - std::vector>> m_rings; // relative - - confirmed_transfer_details(): m_amount_in(0), m_amount_out(0), m_change((uint64_t)-1), m_block_height(0), m_payment_id(crypto::null_hash), m_timestamp(0), m_unlock_time(0), m_subaddr_account((uint32_t)-1) {} - confirmed_transfer_details(const unconfirmed_transfer_details &utd, uint64_t height): - m_tx(utd.m_tx), m_amount_in(utd.m_amount_in), m_amount_out(utd.m_amount_out), m_change(utd.m_change), m_block_height(height), m_dests(utd.m_dests), m_payment_id(utd.m_payment_id), m_timestamp(utd.m_timestamp), m_unlock_time(utd.m_tx.unlock_time), m_subaddr_account(utd.m_subaddr_account), m_subaddr_indices(utd.m_subaddr_indices), m_rings(utd.m_rings) {} - - BEGIN_SERIALIZE_OBJECT() - VERSION_FIELD(1) - if (version >= 1) - FIELD(m_tx) - VARINT_FIELD(m_amount_in) - VARINT_FIELD(m_amount_out) - VARINT_FIELD(m_change) - VARINT_FIELD(m_block_height) - FIELD(m_dests) - FIELD(m_payment_id) - VARINT_FIELD(m_timestamp) - VARINT_FIELD(m_unlock_time) - VARINT_FIELD(m_subaddr_account) - FIELD(m_subaddr_indices) - FIELD(m_rings) - END_SERIALIZE() - }; - struct tx_construction_data { std::vector sources; @@ -589,9 +341,6 @@ namespace tools END_SERIALIZE() }; - typedef std::vector transfer_container; - typedef serializable_unordered_multimap payment_container; - struct multisig_sig { rct::rctSig sigs; @@ -663,6 +412,8 @@ namespace tools END_SERIALIZE() }; + typedef wallet2_basic::transfer_container transfer_container; + // The term "Unsigned tx" is not really a tx since it's not signed yet. // It doesnt have tx hash, key and the integrated address is not separated into addr + payment id. struct unsigned_tx_set @@ -722,47 +473,6 @@ namespace tools END_SERIALIZE() }; - struct keys_file_data - { - crypto::chacha_iv iv; - std::string account_data; - - BEGIN_SERIALIZE_OBJECT() - FIELD(iv) - FIELD(account_data) - END_SERIALIZE() - }; - - struct cache_file_data - { - crypto::chacha_iv iv; - std::string cache_data; - - BEGIN_SERIALIZE_OBJECT() - FIELD(iv) - FIELD(cache_data) - END_SERIALIZE() - }; - - // GUI Address book - struct address_book_row - { - cryptonote::account_public_address m_address; - crypto::hash8 m_payment_id; - std::string m_description; - bool m_is_subaddress; - bool m_has_payment_id; - - BEGIN_SERIALIZE_OBJECT() - VERSION_FIELD(0) - FIELD(m_address) - FIELD(m_payment_id) - FIELD(m_description) - FIELD(m_is_subaddress) - FIELD(m_has_payment_id) - END_SERIALIZE() - }; - struct reserve_proof_entry { crypto::hash txid; @@ -783,6 +493,34 @@ namespace tools END_SERIALIZE() }; + typedef wallet2_basic::multisig_info multisig_info; + typedef wallet2_basic::pool_payment_details pool_payment_details; + typedef wallet2_basic::transfer_details transfer_details; + typedef wallet2_basic::unconfirmed_transfer_details unconfirmed_transfer_details; + typedef wallet2_basic::confirmed_transfer_details confirmed_transfer_details; + typedef wallet2_basic::payment_container payment_container; + typedef wallet2_basic::address_book_row address_book_row; + + typedef wallet2_basic::RefreshType RefreshType; + static constexpr RefreshType RefreshFull = wallet2_basic::RefreshFull; + static constexpr RefreshType RefreshOptimizeCoinbase = wallet2_basic::RefreshOptimizeCoinbase; + static constexpr RefreshType RefreshNoCoinbase = wallet2_basic::RefreshNoCoinbase; + static constexpr RefreshType RefreshDefault = wallet2_basic::RefreshDefault; + + typedef wallet2_basic::AskPasswordType AskPasswordType; + static constexpr AskPasswordType AskPasswordNever = wallet2_basic::AskPasswordNever; + static constexpr AskPasswordType AskPasswordOnAction = wallet2_basic::AskPasswordOnAction; + static constexpr AskPasswordType AskPasswordToDecrypt = wallet2_basic::AskPasswordToDecrypt; + + typedef wallet2_basic::BackgroundMiningSetupType BackgroundMiningSetupType; + static constexpr BackgroundMiningSetupType BackgroundMiningMaybe = wallet2_basic::BackgroundMiningMaybe; + static constexpr BackgroundMiningSetupType BackgroundMiningYes = wallet2_basic::BackgroundMiningYes; + static constexpr BackgroundMiningSetupType BackgroundMiningNo = wallet2_basic::BackgroundMiningNo; + + typedef wallet2_basic::ExportFormat ExportFormat; + static constexpr ExportFormat Binary = wallet2_basic::Binary; + static constexpr ExportFormat Ascii = wallet2_basic::Ascii; + typedef std::tuple get_outs_entry; struct parsed_block @@ -936,19 +674,6 @@ namespace tools * \param password Password to protect new wallet (TODO: probably better save the password in the wallet object?) */ void store_to(const std::string &path, const epee::wipeable_string &password); - /*! - * \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 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 - */ - boost::optional 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 - */ - boost::optional get_cache_file_data(const epee::wipeable_string& password); std::string path() const; @@ -956,22 +681,22 @@ namespace tools * \brief verifies given password is correct for default wallet keys file */ bool verify_password(const epee::wipeable_string& password); - cryptonote::account_base& get_account(){return m_account;} - const cryptonote::account_base& get_account()const{return m_account;} + cryptonote::account_base& get_account(){return m_keys_data.m_account;} + const cryptonote::account_base& get_account()const{return m_keys_data.m_account;} void encrypt_keys(const crypto::chacha_key &key); void encrypt_keys(const epee::wipeable_string &password); void decrypt_keys(const crypto::chacha_key &key); void decrypt_keys(const epee::wipeable_string &password); - void set_refresh_from_block_height(uint64_t height) {m_refresh_from_block_height = height;} - uint64_t get_refresh_from_block_height() const {return m_refresh_from_block_height;} + void set_refresh_from_block_height(uint64_t height) {m_keys_data.m_refresh_from_block_height = height;} + uint64_t get_refresh_from_block_height() const {return m_keys_data.m_refresh_from_block_height;} void explicit_refresh_from_block_height(bool expl) {m_explicit_refresh_from_block_height = expl;} bool explicit_refresh_from_block_height() const {return m_explicit_refresh_from_block_height;} - void max_reorg_depth(uint64_t depth) {m_max_reorg_depth = depth;} - uint64_t max_reorg_depth() const {return m_max_reorg_depth;} + void max_reorg_depth(uint64_t depth) {m_keys_data.m_max_reorg_depth = depth;} + uint64_t max_reorg_depth() const {return m_keys_data.m_max_reorg_depth;} bool deinit(); bool init(std::string daemon_address = "http://localhost:8080", @@ -1018,15 +743,15 @@ namespace tools std::string get_address_as_str() const { return get_subaddress_as_str({0, 0}); } std::string get_integrated_address_as_str(const crypto::hash8& payment_id) const; void add_subaddress_account(const std::string& label); - size_t get_num_subaddress_accounts() const { return m_subaddress_labels.size(); } - size_t get_num_subaddresses(uint32_t index_major) const { return index_major < m_subaddress_labels.size() ? m_subaddress_labels[index_major].size() : 0; } + size_t get_num_subaddress_accounts() const { return m_cache.m_subaddress_labels.size(); } + size_t get_num_subaddresses(uint32_t index_major) const { return index_major < m_cache.m_subaddress_labels.size() ? m_cache.m_subaddress_labels[index_major].size() : 0; } void add_subaddress(uint32_t index_major, const std::string& label); // throws when index is out of bound void expand_subaddresses(const cryptonote::subaddress_index& index); void create_one_off_subaddress(const cryptonote::subaddress_index& index); std::string get_subaddress_label(const cryptonote::subaddress_index& index) const; void set_subaddress_label(const cryptonote::subaddress_index &index, const std::string &label); void set_subaddress_lookahead(size_t major, size_t minor); - std::pair get_subaddress_lookahead() const { return {m_subaddress_lookahead_major, m_subaddress_lookahead_minor}; } + std::pair get_subaddress_lookahead() const { return {m_keys_data.m_subaddress_lookahead_major, m_keys_data.m_subaddress_lookahead_minor}; } /*! * \brief Tells if the wallet file is deprecated. */ @@ -1036,17 +761,17 @@ namespace tools void refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blocks_fetched, bool& received_money, bool check_pool = true, bool try_incremental = true, uint64_t max_blocks = std::numeric_limits::max()); 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; } - RefreshType get_refresh_type() const { return m_refresh_type; } + void set_refresh_type(RefreshType refresh_type) { m_keys_data.m_refresh_type = refresh_type; } + RefreshType get_refresh_type() const { return m_keys_data.m_refresh_type; } - cryptonote::network_type nettype() const { return m_nettype; } - bool watch_only() const { return m_watch_only; } + cryptonote::network_type nettype() const { return m_keys_data.m_nettype; } + bool watch_only() const { return m_keys_data.m_watch_only; } bool multisig(bool *ready = NULL, uint32_t *threshold = NULL, uint32_t *total = NULL) const; bool has_multisig_partial_key_images() const; bool has_unknown_key_images() const; bool get_multisig_seed(epee::wipeable_string& seed, const epee::wipeable_string &passphrase = std::string(), bool raw = true) const; bool key_on_device() const { return get_device_type() != hw::device::device_type::SOFTWARE; } - hw::device::device_type get_device_type() const { return m_key_device_type; } + hw::device::device_type get_device_type() const { return m_keys_data.m_key_device_type; } bool reconnect_device(); // locked & unlocked balance of given or current subaddress account @@ -1114,181 +839,17 @@ namespace tools void get_unconfirmed_payments_out(std::list>& unconfirmed_payments, const boost::optional& subaddr_account = boost::none, const std::set& subaddr_indices = {}) const; void get_unconfirmed_payments(std::list>& unconfirmed_payments, const boost::optional& subaddr_account = boost::none, const std::set& subaddr_indices = {}) const; - uint64_t get_blockchain_current_height() const { return m_blockchain.size(); } + uint64_t get_blockchain_current_height() const { return m_cache.m_blockchain.size(); } void rescan_spent(); void rescan_blockchain(bool hard, bool refresh = true, bool keep_key_images = false); bool is_transfer_unlocked(const transfer_details& td); bool is_transfer_unlocked(uint64_t unlock_time, uint64_t block_height); - uint64_t get_last_block_reward() const { return m_last_block_reward; } - uint64_t get_device_last_key_image_sync() const { return m_device_last_key_image_sync; } + uint64_t get_last_block_reward() const { return m_cache.m_last_block_reward; } + uint64_t get_device_last_key_image_sync() const { return m_cache.m_device_last_key_image_sync; } std::vector get_public_nodes(bool white_only = true); - template - inline void serialize(t_archive &a, const unsigned int ver) - { - uint64_t dummy_refresh_height = 0; // moved to keys file - if(ver < 5) - return; - if (ver < 19) - { - std::vector blockchain; - a & blockchain; - m_blockchain.clear(); - for (const auto &b: blockchain) - { - m_blockchain.push_back(b); - } - } - else - { - a & m_blockchain; - } - a & m_transfers; - a & m_account_public_address; - a & m_key_images.parent(); - if(ver < 6) - return; - a & m_unconfirmed_txs.parent(); - if(ver < 7) - return; - a & m_payments.parent(); - if(ver < 8) - return; - a & m_tx_keys.parent(); - if(ver < 9) - return; - a & m_confirmed_txs.parent(); - if(ver < 11) - return; - a & dummy_refresh_height; - if(ver < 12) - return; - a & m_tx_notes.parent(); - if(ver < 13) - return; - if (ver < 17) - { - // we're loading an old version, where m_unconfirmed_payments was a std::map - std::unordered_map m; - a & m; - m_unconfirmed_payments.clear(); - for (std::unordered_map::const_iterator i = m.begin(); i != m.end(); ++i) - m_unconfirmed_payments.insert(std::make_pair(i->first, pool_payment_details{i->second, false})); - } - if(ver < 14) - return; - if(ver < 15) - { - // we're loading an older wallet without a pubkey map, rebuild it - m_pub_keys.clear(); - for (size_t i = 0; i < m_transfers.size(); ++i) - { - const transfer_details &td = m_transfers[i]; - m_pub_keys.emplace(td.get_public_key(), i); - } - return; - } - a & m_pub_keys.parent(); - if(ver < 16) - return; - a & m_address_book; - if(ver < 17) - return; - if (ver < 22) - { - // we're loading an old version, where m_unconfirmed_payments payload was payment_details - std::unordered_multimap m; - a & m; - m_unconfirmed_payments.clear(); - for (const auto &i: m) - m_unconfirmed_payments.insert(std::make_pair(i.first, pool_payment_details{i.second, false})); - } - if(ver < 18) - return; - a & m_scanned_pool_txs[0]; - a & m_scanned_pool_txs[1]; - if (ver < 20) - return; - a & m_subaddresses.parent(); - std::unordered_map dummy_subaddresses_inv; - a & dummy_subaddresses_inv; - a & m_subaddress_labels; - a & m_additional_tx_keys.parent(); - if(ver < 21) - return; - a & m_attributes.parent(); - if(ver < 22) - return; - a & m_unconfirmed_payments.parent(); - if(ver < 23) - return; - a & (std::pair, std::vector>&)m_account_tags; - if(ver < 24) - return; - a & m_ring_history_saved; - if(ver < 25) - return; - a & m_last_block_reward; - if(ver < 26) - return; - a & m_tx_device.parent(); - if(ver < 27) - return; - a & m_device_last_key_image_sync; - if(ver < 28) - return; - a & m_cold_key_images.parent(); - if(ver < 29) - return; - crypto::secret_key dummy_rpc_client_secret_key; // Compatibility for old RPC payment system - a & dummy_rpc_client_secret_key; - if(ver < 30) - { - m_has_ever_refreshed_from_node = false; - return; - } - a & m_has_ever_refreshed_from_node; - } - - BEGIN_SERIALIZE_OBJECT() - MAGIC_FIELD("monero wallet cache") - VERSION_FIELD(1) - FIELD(m_blockchain) - FIELD(m_transfers) - FIELD(m_account_public_address) - FIELD(m_key_images) - FIELD(m_unconfirmed_txs) - FIELD(m_payments) - FIELD(m_tx_keys) - FIELD(m_confirmed_txs) - FIELD(m_tx_notes) - FIELD(m_unconfirmed_payments) - FIELD(m_pub_keys) - FIELD(m_address_book) - FIELD(m_scanned_pool_txs[0]) - FIELD(m_scanned_pool_txs[1]) - FIELD(m_subaddresses) - FIELD(m_subaddress_labels) - FIELD(m_additional_tx_keys) - FIELD(m_attributes) - FIELD(m_account_tags) - FIELD(m_ring_history_saved) - FIELD(m_last_block_reward) - FIELD(m_tx_device) - FIELD(m_device_last_key_image_sync) - FIELD(m_cold_key_images) - crypto::secret_key dummy_rpc_client_secret_key; // Compatibility for old RPC payment system - FIELD_N("m_rpc_client_secret_key", dummy_rpc_client_secret_key) - if (version < 1) - { - m_has_ever_refreshed_from_node = false; - return true; - } - FIELD(m_has_ever_refreshed_from_node) - END_SERIALIZE() - /*! * \brief Check if wallet keys and bin files exist * \param file_path Wallet file path @@ -1306,66 +867,68 @@ namespace tools static bool parse_short_payment_id(const std::string& payment_id_str, crypto::hash8& payment_id); static bool parse_payment_id(const std::string& payment_id_str, crypto::hash& payment_id); - bool always_confirm_transfers() const { return m_always_confirm_transfers; } - void always_confirm_transfers(bool always) { m_always_confirm_transfers = always; } - bool print_ring_members() const { return m_print_ring_members; } - void print_ring_members(bool value) { m_print_ring_members = value; } - bool store_tx_info() const { return m_store_tx_info; } - void store_tx_info(bool store) { m_store_tx_info = store; } - uint32_t default_mixin() const { return m_default_mixin; } - void default_mixin(uint32_t m) { m_default_mixin = m; } - uint32_t get_default_priority() const { return m_default_priority; } - void set_default_priority(uint32_t p) { m_default_priority = p; } - bool auto_refresh() const { return m_auto_refresh; } - void auto_refresh(bool r) { m_auto_refresh = r; } - AskPasswordType ask_password() const { return m_ask_password; } - void ask_password(AskPasswordType ask) { m_ask_password = ask; } - void set_min_output_count(uint32_t count) { m_min_output_count = count; } - uint32_t get_min_output_count() const { return m_min_output_count; } - void set_min_output_value(uint64_t value) { m_min_output_value = value; } - uint64_t get_min_output_value() const { return m_min_output_value; } - void merge_destinations(bool merge) { m_merge_destinations = merge; } - bool merge_destinations() const { return m_merge_destinations; } - bool confirm_backlog() const { return m_confirm_backlog; } - void confirm_backlog(bool always) { m_confirm_backlog = always; } - void set_confirm_backlog_threshold(uint32_t threshold) { m_confirm_backlog_threshold = threshold; }; - uint32_t get_confirm_backlog_threshold() const { return m_confirm_backlog_threshold; }; - bool confirm_export_overwrite() const { return m_confirm_export_overwrite; } - void confirm_export_overwrite(bool always) { m_confirm_export_overwrite = always; } - bool auto_low_priority() const { return m_auto_low_priority; } - void auto_low_priority(bool value) { m_auto_low_priority = value; } - bool segregate_pre_fork_outputs() const { return m_segregate_pre_fork_outputs; } - void segregate_pre_fork_outputs(bool value) { m_segregate_pre_fork_outputs = value; } - bool key_reuse_mitigation2() const { return m_key_reuse_mitigation2; } - void key_reuse_mitigation2(bool value) { m_key_reuse_mitigation2 = value; } - uint64_t segregation_height() const { return m_segregation_height; } - void segregation_height(uint64_t height) { m_segregation_height = height; } - bool ignore_fractional_outputs() const { return m_ignore_fractional_outputs; } - void ignore_fractional_outputs(bool value) { m_ignore_fractional_outputs = value; } - uint64_t ignore_outputs_above() const { return m_ignore_outputs_above; } - void ignore_outputs_above(uint64_t value) { m_ignore_outputs_above = value; } - uint64_t ignore_outputs_below() const { return m_ignore_outputs_below; } - void ignore_outputs_below(uint64_t value) { m_ignore_outputs_below = value; } - bool track_uses() const { return m_track_uses; } - void track_uses(bool value) { m_track_uses = value; } - bool show_wallet_name_when_locked() const { return m_show_wallet_name_when_locked; } - void show_wallet_name_when_locked(bool value) { m_show_wallet_name_when_locked = value; } - BackgroundMiningSetupType setup_background_mining() const { return m_setup_background_mining; } - void setup_background_mining(BackgroundMiningSetupType value) { m_setup_background_mining = value; } - uint32_t inactivity_lock_timeout() const { return m_inactivity_lock_timeout; } - void inactivity_lock_timeout(uint32_t seconds) { m_inactivity_lock_timeout = seconds; } - const std::string & device_name() const { return m_device_name; } - void device_name(const std::string & device_name) { m_device_name = device_name; } - const std::string & device_derivation_path() const { return m_device_derivation_path; } - void device_derivation_path(const std::string &device_derivation_path) { m_device_derivation_path = device_derivation_path; } - const ExportFormat & export_format() const { return m_export_format; } - inline void set_export_format(const ExportFormat& export_format) { m_export_format = export_format; } - bool load_deprecated_formats() const { return m_load_deprecated_formats; } - void load_deprecated_formats(bool load) { m_load_deprecated_formats = load; } - bool is_multisig_enabled() const { return m_enable_multisig; } - void enable_multisig(bool enable) { m_enable_multisig = enable; } - bool is_mismatched_daemon_version_allowed() const { return m_allow_mismatched_daemon_version; } - void allow_mismatched_daemon_version(bool allow_mismatch) { m_allow_mismatched_daemon_version = allow_mismatch; } + bool always_confirm_transfers() const { return m_keys_data.m_always_confirm_transfers; } + void always_confirm_transfers(bool always) { m_keys_data.m_always_confirm_transfers = always; } + bool print_ring_members() const { return m_keys_data.m_print_ring_members; } + void print_ring_members(bool value) { m_keys_data.m_print_ring_members = value; } + bool store_tx_info() const { return m_keys_data.m_store_tx_info; } + void store_tx_info(bool store) { m_keys_data.m_store_tx_info = store; } + uint32_t default_mixin() const { return m_keys_data.m_default_mixin; } + void default_mixin(uint32_t m) { m_keys_data.m_default_mixin = m; } + uint32_t get_default_priority() const { return m_keys_data.m_default_priority; } + void set_default_priority(uint32_t p) { m_keys_data.m_default_priority = p; } + bool auto_refresh() const { return m_keys_data.m_auto_refresh; } + void auto_refresh(bool r) { m_keys_data.m_auto_refresh = r; } + AskPasswordType ask_password() const { return m_keys_data.m_ask_password; } + void ask_password(AskPasswordType ask) { m_keys_data.m_ask_password = ask; } + void set_min_output_count(uint32_t count) { m_keys_data.m_min_output_count = count; } + uint32_t get_min_output_count() const { return m_keys_data.m_min_output_count; } + void set_min_output_value(uint64_t value) { m_keys_data.m_min_output_value = value; } + uint64_t get_min_output_value() const { return m_keys_data.m_min_output_value; } + void merge_destinations(bool merge) { m_keys_data.m_merge_destinations = merge; } + bool merge_destinations() const { return m_keys_data.m_merge_destinations; } + bool confirm_backlog() const { return m_keys_data.m_confirm_backlog; } + void confirm_backlog(bool always) { m_keys_data.m_confirm_backlog = always; } + void set_confirm_backlog_threshold(uint32_t threshold) { m_keys_data.m_confirm_backlog_threshold = threshold; }; + uint32_t get_confirm_backlog_threshold() const { return m_keys_data.m_confirm_backlog_threshold; }; + bool confirm_export_overwrite() const { return m_keys_data.m_confirm_export_overwrite; } + void confirm_export_overwrite(bool always) { m_keys_data.m_confirm_export_overwrite = always; } + bool auto_low_priority() const { return m_keys_data.m_auto_low_priority; } + void auto_low_priority(bool value) { m_keys_data.m_auto_low_priority = value; } + bool segregate_pre_fork_outputs() const { return m_keys_data.m_segregate_pre_fork_outputs; } + void segregate_pre_fork_outputs(bool value) { m_keys_data.m_segregate_pre_fork_outputs = value; } + bool key_reuse_mitigation2() const { return m_keys_data.m_key_reuse_mitigation2; } + void key_reuse_mitigation2(bool value) { m_keys_data.m_key_reuse_mitigation2 = value; } + uint64_t segregation_height() const { return m_keys_data.m_segregation_height; } + void segregation_height(uint64_t height) { m_keys_data.m_segregation_height = height; } + bool ignore_fractional_outputs() const { return m_keys_data.m_ignore_fractional_outputs; } + void ignore_fractional_outputs(bool value) { m_keys_data.m_ignore_fractional_outputs = value; } + bool confirm_non_default_ring_size() const { return m_keys_data.m_confirm_non_default_ring_size; } + void confirm_non_default_ring_size(bool always) { m_keys_data.m_confirm_non_default_ring_size = always; } + uint64_t ignore_outputs_above() const { return m_keys_data.m_ignore_outputs_above; } + void ignore_outputs_above(uint64_t value) { m_keys_data.m_ignore_outputs_above = value; } + uint64_t ignore_outputs_below() const { return m_keys_data.m_ignore_outputs_below; } + void ignore_outputs_below(uint64_t value) { m_keys_data.m_ignore_outputs_below = value; } + bool track_uses() const { return m_keys_data.m_track_uses; } + void track_uses(bool value) { m_keys_data.m_track_uses = value; } + bool show_wallet_name_when_locked() const { return m_keys_data.m_show_wallet_name_when_locked; } + void show_wallet_name_when_locked(bool value) { m_keys_data.m_show_wallet_name_when_locked = value; } + BackgroundMiningSetupType setup_background_mining() const { return m_keys_data.m_setup_background_mining; } + void setup_background_mining(BackgroundMiningSetupType value) { m_keys_data.m_setup_background_mining = value; } + uint32_t inactivity_lock_timeout() const { return m_keys_data.m_inactivity_lock_timeout; } + void inactivity_lock_timeout(uint32_t seconds) { m_keys_data.m_inactivity_lock_timeout = seconds; } + const std::string & device_name() const { return m_keys_data.m_device_name; } + void device_name(const std::string & device_name) { m_keys_data.m_device_name = device_name; } + const std::string & device_derivation_path() const { return m_keys_data.m_device_derivation_path; } + void device_derivation_path(const std::string &device_derivation_path) { m_keys_data.m_device_derivation_path = device_derivation_path; } + const ExportFormat & export_format() const { return m_keys_data.m_export_format; } + inline void set_export_format(const ExportFormat& export_format) { m_keys_data.m_export_format = export_format; } + bool load_deprecated_formats() const { return m_keys_data.m_load_deprecated_formats; } + void load_deprecated_formats(bool load) { m_keys_data.m_load_deprecated_formats = load; } + bool is_multisig_enabled() const { return m_keys_data.m_enable_multisig; } + void enable_multisig(bool enable) { m_keys_data.m_enable_multisig = enable; } + bool is_mismatched_daemon_version_allowed() const { return m_keys_data.m_allow_mismatched_daemon_version; } + void allow_mismatched_daemon_version(bool allow_mismatch) { m_keys_data.m_allow_mismatched_daemon_version = allow_mismatch; } bool get_tx_key_cached(const crypto::hash &txid, crypto::secret_key &tx_key, std::vector &additional_tx_keys) const; void set_tx_key(const crypto::hash &txid, const crypto::secret_key &tx_key, const std::vector &additional_tx_keys, const boost::optional &single_destination_subaddress = boost::none); @@ -1406,13 +969,13 @@ namespace tools /*! * \brief GUI Address book get/store */ - std::vector get_address_book() const { return m_address_book; } + std::vector get_address_book() const { return m_cache.m_address_book; } bool add_address_book_row(const cryptonote::account_public_address &address, const crypto::hash8 *payment_id, const std::string &description, bool is_subaddress); bool set_address_book_row(size_t row_id, const cryptonote::account_public_address &address, const crypto::hash8 *payment_id, const std::string &description, bool is_subaddress); bool delete_address_book_row(std::size_t row_id); uint64_t get_num_rct_outputs(); - size_t get_num_transfer_details() const { return m_transfers.size(); } + size_t get_num_transfer_details() const { return m_cache.m_transfers.size(); } const transfer_details &get_transfer_details(size_t idx) const; uint8_t get_current_hard_fork(); @@ -1758,120 +1321,47 @@ namespace tools bool should_expand(const cryptonote::subaddress_index &index) const; bool spends_one_of_ours(const cryptonote::transaction &tx) const; - cryptonote::account_base m_account; boost::optional m_daemon_login; std::string m_daemon_address; std::string m_wallet_file; std::string m_keys_file; std::string m_mms_file; const std::unique_ptr m_http_client; - hashchain m_blockchain; - serializable_unordered_map m_unconfirmed_txs; - serializable_unordered_map m_confirmed_txs; - serializable_unordered_multimap m_unconfirmed_payments; - serializable_unordered_map m_tx_keys; cryptonote::checkpoints m_checkpoints; - serializable_unordered_map> m_additional_tx_keys; - - transfer_container m_transfers; - payment_container m_payments; - serializable_unordered_map m_key_images; - serializable_unordered_map m_pub_keys; - cryptonote::account_public_address m_account_public_address; - serializable_unordered_map m_subaddresses; - std::vector> m_subaddress_labels; - serializable_unordered_map m_tx_notes; - serializable_unordered_map m_attributes; - std::vector m_address_book; - std::pair, std::vector> m_account_tags; + uint64_t m_upper_transaction_weight_limit; //TODO: auto-calc this value or request from daemon, now use some fixed value const std::vector> *m_multisig_rescan_info; const std::vector> *m_multisig_rescan_k; - serializable_unordered_map m_cold_key_images; std::atomic m_run; boost::recursive_mutex m_daemon_rpc_mutex; + // These two fields contain all the data that is stored persistently + wallet2_basic::cache m_cache; + wallet2_basic::keys_data m_keys_data; + bool m_trusted_daemon; i_wallet2_callback* m_callback; - hw::device::device_type m_key_device_type; - cryptonote::network_type m_nettype; - uint64_t m_kdf_rounds; - std::string seed_language; /*!< Language of the mnemonics (seed). */ - bool is_old_file_format; /*!< Whether the wallet file is of an old file format */ - bool m_watch_only; /*!< no spend key */ - bool m_multisig; /*!< if > 1 spend secret key will not match spend public key */ - uint32_t m_multisig_threshold; - std::vector m_multisig_signers; - //in case of general M/N multisig wallet we should perform N - M + 1 key exchange rounds and remember how many rounds are passed. - uint32_t m_multisig_rounds_passed; - std::vector m_multisig_derivations; - bool m_always_confirm_transfers; - bool m_print_ring_members; - bool m_store_tx_info; /*!< request txkey to be returned in RPC, and store in the wallet cache file */ - uint32_t m_default_mixin; - uint32_t m_default_priority; - RefreshType m_refresh_type; - bool m_auto_refresh; + uint64_t m_kdf_rounds; /*!< num of KDF rounds used to gen chacha keys encrypting the files */ bool m_first_refresh_done; - uint64_t m_refresh_from_block_height; // If m_refresh_from_block_height is explicitly set to zero we need this to differentiate it from the case that - // m_refresh_from_block_height was defaulted to zero.*/ + // m_keys_data.m_refresh_from_block_height was defaulted to zero.*/ bool m_explicit_refresh_from_block_height; uint64_t m_pool_info_query_time; - uint64_t m_skip_to_height; - // m_skip_to_height is useful when we don't want to modify the wallet's restore height. - // m_refresh_from_block_height is also a wallet's restore height which should remain constant unless explicitly modified by the user. - bool m_confirm_non_default_ring_size; - AskPasswordType m_ask_password; - uint64_t m_max_reorg_depth; - uint32_t m_min_output_count; - uint64_t m_min_output_value; - bool m_merge_destinations; - bool m_confirm_backlog; - uint32_t m_confirm_backlog_threshold; - bool m_confirm_export_overwrite; - bool m_auto_low_priority; - bool m_segregate_pre_fork_outputs; - bool m_key_reuse_mitigation2; - uint64_t m_segregation_height; - bool m_ignore_fractional_outputs; - uint64_t m_ignore_outputs_above; - uint64_t m_ignore_outputs_below; - bool m_track_uses; - bool m_show_wallet_name_when_locked; - uint32_t m_inactivity_lock_timeout; - BackgroundMiningSetupType m_setup_background_mining; - float m_auto_mine_for_rpc_payment_threshold; bool m_is_initialized; NodeRPCProxy m_node_rpc_proxy; - std::unordered_set m_scanned_pool_txs[2]; - size_t m_subaddress_lookahead_major, m_subaddress_lookahead_minor; - std::string m_device_name; - std::string m_device_derivation_path; - uint64_t m_device_last_key_image_sync; bool m_use_dns; bool m_offline; uint32_t m_rpc_version; - bool m_enable_multisig; - bool m_allow_mismatched_daemon_version; - - // Aux transaction data from device - serializable_unordered_map m_tx_device; std::string m_ring_database; - bool m_ring_history_saved; std::unique_ptr m_ringdb; boost::optional m_ringdb_key; - uint64_t m_last_block_reward; std::unique_ptr m_keys_file_locker; mms::message_store m_message_store; - bool m_original_keys_available; - cryptonote::account_public_address m_original_address; - crypto::secret_key m_original_view_secret_key; crypto::chacha_key m_cache_key; std::shared_ptr m_encrypt_keys_after_refresh; @@ -1882,25 +1372,11 @@ namespace tools std::shared_ptr m_tx_notify; std::unique_ptr m_device_callback; - ExportFormat m_export_format; - bool m_load_deprecated_formats; - - bool m_has_ever_refreshed_from_node; - static boost::mutex default_daemon_address_lock; static std::string default_daemon_address; }; } -BOOST_CLASS_VERSION(tools::wallet2, 30) -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) -BOOST_CLASS_VERSION(tools::wallet2::payment_details, 5) -BOOST_CLASS_VERSION(tools::wallet2::pool_payment_details, 1) -BOOST_CLASS_VERSION(tools::wallet2::unconfirmed_transfer_details, 8) -BOOST_CLASS_VERSION(tools::wallet2::confirmed_transfer_details, 6) -BOOST_CLASS_VERSION(tools::wallet2::address_book_row, 18) BOOST_CLASS_VERSION(tools::wallet2::reserve_proof_entry, 0) BOOST_CLASS_VERSION(tools::wallet2::unsigned_tx_set, 1) BOOST_CLASS_VERSION(tools::wallet2::signed_tx_set, 1) @@ -1923,168 +1399,6 @@ namespace boost ar & boost::serialization::make_nvp("t", std::get<2>(t)); } - template - inline typename std::enable_if::type initialize_transfer_details(Archive &a, tools::wallet2::transfer_details &x, const boost::serialization::version_type ver) - { - } - template - inline typename std::enable_if::type initialize_transfer_details(Archive &a, tools::wallet2::transfer_details &x, const boost::serialization::version_type ver) - { - if (ver < 1) - { - x.m_mask = rct::identity(); - x.m_amount = x.m_tx.vout[x.m_internal_output_index].amount; - } - if (ver < 2) - { - x.m_spent_height = 0; - } - if (ver < 4) - { - x.m_rct = x.m_tx.vout[x.m_internal_output_index].amount == 0; - } - if (ver < 6) - { - x.m_key_image_known = true; - } - if (ver < 7) - { - x.m_pk_index = 0; - } - if (ver < 8) - { - x.m_subaddr_index = {}; - } - if (ver < 9) - { - x.m_key_image_partial = false; - x.m_multisig_k.clear(); - x.m_multisig_info.clear(); - } - if (ver < 10) - { - x.m_key_image_request = false; - } - if (ver < 12) - { - x.m_frozen = false; - } - } - - template - inline void serialize(Archive &a, tools::wallet2::transfer_details &x, const boost::serialization::version_type ver) - { - a & x.m_block_height; - a & x.m_global_output_index; - a & x.m_internal_output_index; - if (ver < 3) - { - cryptonote::transaction tx; - a & tx; - x.m_tx = (const cryptonote::transaction_prefix&)tx; - x.m_txid = cryptonote::get_transaction_hash(tx); - } - else - { - a & x.m_tx; - } - a & x.m_spent; - a & x.m_key_image; - if (ver < 1) - { - // ensure mask and amount are set - initialize_transfer_details(a, x, ver); - return; - } - a & x.m_mask; - a & x.m_amount; - if (ver < 2) - { - initialize_transfer_details(a, x, ver); - return; - } - a & x.m_spent_height; - if (ver < 3) - { - initialize_transfer_details(a, x, ver); - return; - } - a & x.m_txid; - if (ver < 4) - { - initialize_transfer_details(a, x, ver); - return; - } - a & x.m_rct; - if (ver < 5) - { - initialize_transfer_details(a, x, ver); - return; - } - if (ver < 6) - { - // v5 did not properly initialize - uint8_t u; - a & u; - x.m_key_image_known = true; - return; - } - a & x.m_key_image_known; - if (ver < 7) - { - initialize_transfer_details(a, x, ver); - return; - } - a & x.m_pk_index; - if (ver < 8) - { - initialize_transfer_details(a, x, ver); - return; - } - a & x.m_subaddr_index; - if (ver < 9) - { - initialize_transfer_details(a, x, ver); - return; - } - a & x.m_multisig_info; - a & x.m_multisig_k; - a & x.m_key_image_partial; - if (ver < 10) - { - initialize_transfer_details(a, x, ver); - return; - } - 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 - inline void serialize(Archive &a, tools::wallet2::multisig_info::LR &x, const boost::serialization::version_type ver) - { - a & x.m_L; - a & x.m_R; - } - - template - inline void serialize(Archive &a, tools::wallet2::multisig_info &x, const boost::serialization::version_type ver) - { - a & x.m_signer; - a & x.m_LR; - a & x.m_partial_key_images; - } - template inline void serialize(Archive &a, tools::wallet2::multisig_tx_set &x, const boost::serialization::version_type ver) { @@ -2092,182 +1406,6 @@ namespace boost a & x.m_signers; } - template - inline void serialize(Archive &a, tools::wallet2::unconfirmed_transfer_details &x, const boost::serialization::version_type ver) - { - a & x.m_change; - a & x.m_sent_time; - if (ver < 5) - { - cryptonote::transaction tx; - a & tx; - x.m_tx = (const cryptonote::transaction_prefix&)tx; - } - else - { - a & x.m_tx; - } - if (ver < 1) - return; - a & x.m_dests; - a & x.m_payment_id; - if (ver < 2) - return; - a & x.m_state; - if (ver < 3) - return; - a & x.m_timestamp; - if (ver < 4) - return; - a & x.m_amount_in; - a & x.m_amount_out; - if (ver < 6) - { - // v<6 may not have change accumulated in m_amount_out, which is a pain, - // as it's readily understood to be sum of outputs. - // We convert it to include change from v6 - if (!typename Archive::is_saving() && x.m_change != (uint64_t)-1) - x.m_amount_out += x.m_change; - } - if (ver < 7) - { - x.m_subaddr_account = 0; - return; - } - a & x.m_subaddr_account; - a & x.m_subaddr_indices; - if (ver < 8) - return; - a & x.m_rings; - } - - template - inline void serialize(Archive &a, tools::wallet2::confirmed_transfer_details &x, const boost::serialization::version_type ver) - { - a & x.m_amount_in; - a & x.m_amount_out; - a & x.m_change; - a & x.m_block_height; - if (ver < 1) - return; - a & x.m_dests; - a & x.m_payment_id; - if (ver < 2) - return; - a & x.m_timestamp; - if (ver < 3) - { - // v<3 may not have change accumulated in m_amount_out, which is a pain, - // as it's readily understood to be sum of outputs. Whether it got added - // or not depends on whether it came from a unconfirmed_transfer_details - // (not included) or not (included). We can't reliably tell here, so we - // check whether either yields a "negative" fee, or use the other if so. - // We convert it to include change from v3 - if (!typename Archive::is_saving() && x.m_change != (uint64_t)-1) - { - if (x.m_amount_in > (x.m_amount_out + x.m_change)) - x.m_amount_out += x.m_change; - } - } - if (ver < 4) - { - if (!typename Archive::is_saving()) - x.m_unlock_time = 0; - return; - } - a & x.m_unlock_time; - if (ver < 5) - { - x.m_subaddr_account = 0; - return; - } - a & x.m_subaddr_account; - a & x.m_subaddr_indices; - if (ver < 6) - return; - a & x.m_rings; - } - - template - inline void serialize(Archive& a, tools::wallet2::payment_details& x, const boost::serialization::version_type ver) - { - a & x.m_tx_hash; - a & x.m_amount; - a & x.m_block_height; - a & x.m_unlock_time; - if (ver < 1) - return; - a & x.m_timestamp; - if (ver < 2) - { - x.m_coinbase = false; - x.m_subaddr_index = {}; - return; - } - a & x.m_subaddr_index; - if (ver < 3) - { - x.m_coinbase = false; - x.m_fee = 0; - return; - } - a & x.m_fee; - if (ver < 4) - { - x.m_coinbase = false; - return; - } - a & x.m_coinbase; - if (ver < 5) - return; - a & x.m_amounts; - } - - template - inline void serialize(Archive& a, tools::wallet2::pool_payment_details& x, const boost::serialization::version_type ver) - { - a & x.m_pd; - a & x.m_double_spend_seen; - } - - template - inline void serialize(Archive& a, tools::wallet2::address_book_row& x, const boost::serialization::version_type ver) - { - a & x.m_address; - if (ver < 18) - { - crypto::hash payment_id; - a & payment_id; - x.m_has_payment_id = !(payment_id == crypto::null_hash); - if (x.m_has_payment_id) - { - bool is_long = false; - for (int i = 8; i < 32; ++i) - is_long |= payment_id.data[i]; - if (is_long) - { - MWARNING("Long payment ID ignored on address book load"); - x.m_payment_id = crypto::null_hash8; - x.m_has_payment_id = false; - } - else - memcpy(x.m_payment_id.data, payment_id.data, 8); - } - } - a & x.m_description; - if (ver < 17) - { - x.m_is_subaddress = false; - return; - } - a & x.m_is_subaddress; - if (ver < 18) - return; - a & x.m_has_payment_id; - if (x.m_has_payment_id) - a & x.m_payment_id; - } - template inline void serialize(Archive& a, tools::wallet2::reserve_proof_entry& x, const boost::serialization::version_type ver) { diff --git a/src/wallet/wallet2_basic/CMakeLists.txt b/src/wallet/wallet2_basic/CMakeLists.txt new file mode 100644 index 0000000000..744cb9912e --- /dev/null +++ b/src/wallet/wallet2_basic/CMakeLists.txt @@ -0,0 +1,72 @@ +# 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. + +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + +set(wallet2_basic_sources + wallet2_boost_serialization.cpp + wallet2_storage.cpp +) + +set(wallet2_basic_headers + wallet2_constants.h + wallet2_storage.h + wallet2_types.h +) + +set(wallet2_basic_private_headers +) + +monero_private_headers(wallet2_basic + ${wallet2_basic_private_headers}) +monero_add_library(wallet2_basic + ${wallet2_basic_sources} + ${wallet2_basic_headers} + ${wallet2_basic_private_headers}) +target_link_libraries(wallet2_basic + PUBLIC + common + cryptonote_core + ${Boost_LOCALE_LIBRARY} + ${ICU_LIBRARIES} + ${Boost_SERIALIZATION_LIBRARY} + ${Boost_FILESYSTEM_LIBRARY} + ${Boost_SYSTEM_LIBRARY} + PRIVATE + ${EXTRA_LIBRARIES}) + +set_property(TARGET wallet2_basic PROPERTY EXCLUDE_FROM_ALL TRUE) + +if(IOS) + set(lib_folder lib-${ARCH}) +else() + set(lib_folder lib) +endif() + +install(FILES ${wallet2_basic_headers} + DESTINATION include/wallet/wallet2_basic) diff --git a/src/wallet/wallet2_basic/wallet2_boost_serialization.cpp b/src/wallet/wallet2_basic/wallet2_boost_serialization.cpp new file mode 100644 index 0000000000..008f84776b --- /dev/null +++ b/src/wallet/wallet2_basic/wallet2_boost_serialization.cpp @@ -0,0 +1,39 @@ +// 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 "wallet2_boost_serialization.h" + +BOOST_CLASS_EXPORT_IMPLEMENT(::wallet2_basic::hashchain) +BOOST_CLASS_EXPORT_IMPLEMENT(::wallet2_basic::transfer_details) +BOOST_CLASS_EXPORT_IMPLEMENT(::wallet2_basic::multisig_info) +BOOST_CLASS_EXPORT_IMPLEMENT(::wallet2_basic::unconfirmed_transfer_details) +BOOST_CLASS_EXPORT_IMPLEMENT(::wallet2_basic::confirmed_transfer_details) +BOOST_CLASS_EXPORT_IMPLEMENT(::wallet2_basic::payment_details) +BOOST_CLASS_EXPORT_IMPLEMENT(::wallet2_basic::pool_payment_details) +BOOST_CLASS_EXPORT_IMPLEMENT(::wallet2_basic::address_book_row) +BOOST_CLASS_EXPORT_IMPLEMENT(::wallet2_basic::cache) diff --git a/src/wallet/wallet2_basic/wallet2_boost_serialization.h b/src/wallet/wallet2_basic/wallet2_boost_serialization.h new file mode 100644 index 0000000000..f0e77d7055 --- /dev/null +++ b/src/wallet/wallet2_basic/wallet2_boost_serialization.h @@ -0,0 +1,559 @@ +// 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. + +#pragma once + +#include +#include +#include +#include + +#include "cryptonote_basic/cryptonote_boost_serialization.h" +#include "cryptonote_basic/account_boost_serialization.h" +#include "serialization/polymorphic_portable_binary_iarchive.h" +#include "serialization/polymorphic_portable_binary_oarchive.h" +#include "wallet2_storage.h" +#include "wallet2_types.h" + +namespace wallet2_basic +{ +struct HashchainAccessor +{ + hashchain& hc; + HashchainAccessor(hashchain& hc): hc(hc) {} + inline std::size_t& m_offset() { return hc.m_offset; } + inline crypto::hash& m_genesis() { return hc.m_genesis; } + inline std::deque& m_blockchain() { return hc.m_blockchain; } +}; +} // namespace wallet2_basic + +namespace boost +{ +namespace serialization +{ +template +void serialize(Archive &a, wallet2_basic::multisig_info::LR &x, const version_type ver) +{ + a & x.m_L; + a & x.m_R; +} + +template +void serialize(Archive &a, wallet2_basic::multisig_info &x, const version_type ver) +{ + a & x.m_signer; + a & x.m_LR; + a & x.m_partial_key_images; +} + +template +std::enable_if_t +initialize_transfer_details(Archive &a, wallet2_basic::transfer_details &x, const version_type ver) +{} + +template +std::enable_if_t +initialize_transfer_details(Archive &a, wallet2_basic::transfer_details &x, const version_type ver) +{ + if (ver < 1) + { + x.m_mask = rct::identity(); + x.m_amount = x.m_tx.vout[x.m_internal_output_index].amount; + } + if (ver < 2) + { + x.m_spent_height = 0; + } + if (ver < 4) + { + x.m_rct = x.m_tx.vout[x.m_internal_output_index].amount == 0; + } + if (ver < 6) + { + x.m_key_image_known = true; + } + if (ver < 7) + { + x.m_pk_index = 0; + } + if (ver < 8) + { + x.m_subaddr_index = {}; + } + if (ver < 9) + { + x.m_key_image_partial = false; + x.m_multisig_k.clear(); + x.m_multisig_info.clear(); + } + if (ver < 10) + { + x.m_key_image_request = false; + } + if (ver < 12) + { + x.m_frozen = false; + } +} + +template +void serialize(Archive &a, wallet2_basic::hashchain &x, const version_type ver) +{ + wallet2_basic::HashchainAccessor xaccess(x); + a & xaccess.m_offset(); + a & xaccess.m_genesis(); + a & xaccess.m_blockchain(); +} + +template +void serialize(Archive &a, wallet2_basic::transfer_details &x, const version_type ver) +{ + a & x.m_block_height; + a & x.m_global_output_index; + a & x.m_internal_output_index; + if (ver < 3) + { + cryptonote::transaction tx; + a & tx; + x.m_tx = (const cryptonote::transaction_prefix&)tx; + x.m_txid = cryptonote::get_transaction_hash(tx); + } + else + { + a & x.m_tx; + } + a & x.m_spent; + a & x.m_key_image; + if (ver < 1) + { + // ensure mask and amount are set + initialize_transfer_details(a, x, ver); + return; + } + a & x.m_mask; + a & x.m_amount; + if (ver < 2) + { + initialize_transfer_details(a, x, ver); + return; + } + a & x.m_spent_height; + if (ver < 3) + { + initialize_transfer_details(a, x, ver); + return; + } + a & x.m_txid; + if (ver < 4) + { + initialize_transfer_details(a, x, ver); + return; + } + a & x.m_rct; + if (ver < 5) + { + initialize_transfer_details(a, x, ver); + return; + } + if (ver < 6) + { + // v5 did not properly initialize + uint8_t u = 0; + a & u; + x.m_key_image_known = true; + return; + } + a & x.m_key_image_known; + if (ver < 7) + { + initialize_transfer_details(a, x, ver); + return; + } + a & x.m_pk_index; + if (ver < 8) + { + initialize_transfer_details(a, x, ver); + return; + } + a & x.m_subaddr_index; + if (ver < 9) + { + initialize_transfer_details(a, x, ver); + return; + } + a & x.m_multisig_info; + a & x.m_multisig_k; + a & x.m_key_image_partial; + if (ver < 10) + { + initialize_transfer_details(a, x, ver); + return; + } + 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 +void serialize(Archive &a, wallet2_basic::unconfirmed_transfer_details &x, const version_type ver) +{ + a & x.m_change; + a & x.m_sent_time; + if (ver < 5) + { + cryptonote::transaction tx; + a & tx; + x.m_tx = (const cryptonote::transaction_prefix&)tx; + } + else + { + a & x.m_tx; + } + if (ver < 1) + return; + a & x.m_dests; + a & x.m_payment_id; + if (ver < 2) + return; + a & x.m_state; + if (ver < 3) + return; + a & x.m_timestamp; + if (ver < 4) + return; + a & x.m_amount_in; + a & x.m_amount_out; + if (ver < 6) + { + // v<6 may not have change accumulated in m_amount_out, which is a pain, + // as it's readily understood to be sum of outputs. + // We convert it to include change from v6 + if (!typename Archive::is_saving() && x.m_change != (uint64_t)1) + x.m_amount_out += x.m_change; + } + if (ver < 7) + { + x.m_subaddr_account = 0; + return; + } + a & x.m_subaddr_account; + a & x.m_subaddr_indices; + if (ver < 8) + return; + a & x.m_rings; +} + +template +void serialize(Archive &a, wallet2_basic::confirmed_transfer_details &x, const version_type ver) +{ + a & x.m_amount_in; + a & x.m_amount_out; + a & x.m_change; + a & x.m_block_height; + if (ver < 1) + return; + a & x.m_dests; + a & x.m_payment_id; + if (ver < 2) + return; + a & x.m_timestamp; + if (ver < 3) + { + // v<3 may not have change accumulated in m_amount_out, which is a pain, + // as it's readily understood to be sum of outputs. Whether it got added + // or not depends on whether it came from a unconfirmed_transfer_details + // (not included) or not (included). We can't reliably tell here, so we + // check whether either yields a "negative" fee, or use the other if so. + // We convert it to include change from v3 + if (!typename Archive::is_saving() && x.m_change != (uint64_t)1) + { + if (x.m_amount_in > (x.m_amount_out + x.m_change)) + x.m_amount_out += x.m_change; + } + } + if (ver < 4) + { + if (!typename Archive::is_saving()) + x.m_unlock_time = 0; + return; + } + a & x.m_unlock_time; + if (ver < 5) + { + x.m_subaddr_account = 0; + return; + } + a & x.m_subaddr_account; + a & x.m_subaddr_indices; + if (ver < 6) + return; + a & x.m_rings; +} + +template +void serialize(Archive& a, wallet2_basic::payment_details& x, const version_type ver) +{ + a & x.m_tx_hash; + a & x.m_amount; + a & x.m_block_height; + a & x.m_unlock_time; + if (ver < 1) + return; + a & x.m_timestamp; + if (ver < 2) + { + x.m_coinbase = false; + x.m_subaddr_index = {}; + return; + } + a & x.m_subaddr_index; + if (ver < 3) + { + x.m_coinbase = false; + x.m_fee = 0; + return; + } + a & x.m_fee; + if (ver < 4) + { + x.m_coinbase = false; + return; + } + a & x.m_coinbase; + if (ver < 5) + return; + a & x.m_amounts; +} + +template +void serialize(Archive& a, wallet2_basic::pool_payment_details& x, const version_type ver) +{ + a & x.m_pd; + a & x.m_double_spend_seen; +} + +template +void serialize(Archive& a, wallet2_basic::address_book_row& x, const version_type ver) +{ + a & x.m_address; + if (ver < 18) + { + crypto::hash payment_id; + a & payment_id; + x.m_has_payment_id = !(payment_id == crypto::null_hash); + if (x.m_has_payment_id) + { + bool is_long = false; + for (int i = 8; i < 32; ++i) + is_long |= payment_id.data[i]; + if (is_long) + { + MWARNING("Long payment ID ignored on address book load"); + x.m_payment_id = crypto::null_hash8; + x.m_has_payment_id = false; + } + else + memcpy(x.m_payment_id.data, payment_id.data, 8); + } + } + a & x.m_description; + if (ver < 17) + { + x.m_is_subaddress = false; + return; + } + a & x.m_is_subaddress; + if (ver < 18) + return; + a & x.m_has_payment_id; + if (x.m_has_payment_id) + a & x.m_payment_id; +} + +template +void serialize(Archive& a, wallet2_basic::cache& x, const version_type ver) +{ + using namespace wallet2_basic; + + uint64_t dummy_refresh_height = 0; // moved to keys file + if(ver < 5) + return; + if (ver < 19) + { + std::vector blockchain; + a & blockchain; + x.m_blockchain.clear(); + for (const auto &b: blockchain) + { + x.m_blockchain.push_back(b); + } + } + else + { + a & x.m_blockchain; + } + a & x.m_transfers; + a & x.m_account_public_address; + a & x.m_key_images.parent(); + if(ver < 6) + return; + a & x.m_unconfirmed_txs.parent(); + if(ver < 7) + return; + a & x.m_payments.parent(); + if(ver < 8) + return; + a & x.m_tx_keys.parent(); + if(ver < 9) + return; + a & x.m_confirmed_txs.parent(); + if(ver < 11) + return; + a & dummy_refresh_height; + if(ver < 12) + return; + a & x.m_tx_notes.parent(); + if(ver < 13) + return; + if (ver < 17) + { + // we're loading an old version, where m_unconfirmed_payments was a std::map + std::unordered_map m; + a & m; + x.m_unconfirmed_payments.clear(); + for (const auto& i : m) + x.m_unconfirmed_payments.insert({i.first, pool_payment_details{i.second, false}}); + } + if(ver < 14) + return; + if(ver < 15) + { + // we're loading an older wallet without a pubkey map, rebuild it + x.m_pub_keys.clear(); + for (size_t i = 0; i < x.m_transfers.size(); ++i) + { + const transfer_details &td = x.m_transfers[i]; + x.m_pub_keys.emplace(td.get_public_key(), i); + } + return; + } + a & x.m_pub_keys.parent(); + if(ver < 16) + return; + a & x.m_address_book; + if(ver < 17) + return; + if (ver < 22) + { + // we're loading an old version, where m_unconfirmed_payments payload was payment_details + std::unordered_multimap m; + a & m; + x.m_unconfirmed_payments.clear(); + for (const auto &i: m) + x.m_unconfirmed_payments.insert({i.first, pool_payment_details{i.second, false}}); + } + if(ver < 18) + return; + a & x.m_scanned_pool_txs[0]; + a & x.m_scanned_pool_txs[1]; + if (ver < 20) + return; + a & x.m_subaddresses.parent(); + std::unordered_map dummy_subaddresses_inv; + a & dummy_subaddresses_inv; + a & x.m_subaddress_labels; + a & x.m_additional_tx_keys.parent(); + if(ver < 21) + return; + a & x.m_attributes.parent(); + if(ver < 22) + return; + a & x.m_unconfirmed_payments.parent(); + if(ver < 23) + return; + a & (std::pair, std::vector>&) x.m_account_tags; + if(ver < 24) + return; + a & x.m_ring_history_saved; + if(ver < 25) + return; + a & x.m_last_block_reward; + if(ver < 26) + return; + a & x.m_tx_device.parent(); + if(ver < 27) + return; + a & x.m_device_last_key_image_sync; + if(ver < 28) + return; + a & x.m_cold_key_images.parent(); + if(ver < 29) + return; + crypto::secret_key dummy_rpc_client_secret_key; // Compatibility for old RPC payment system + a & dummy_rpc_client_secret_key; + if(ver < 30) + { + x.m_has_ever_refreshed_from_node = false; + return; + } + a & x.m_has_ever_refreshed_from_node; +} +} // namespace serialization +} // namespace boost + +BOOST_CLASS_VERSION(wallet2_basic::hashchain, 0) +BOOST_CLASS_VERSION(wallet2_basic::transfer_details, 12) +BOOST_CLASS_VERSION(wallet2_basic::multisig_info::LR, 0) +BOOST_CLASS_VERSION(wallet2_basic::multisig_info, 1) +BOOST_CLASS_VERSION(wallet2_basic::unconfirmed_transfer_details, 8) +BOOST_CLASS_VERSION(wallet2_basic::confirmed_transfer_details, 6) +BOOST_CLASS_VERSION(wallet2_basic::payment_details, 5) +BOOST_CLASS_VERSION(wallet2_basic::pool_payment_details, 1) +BOOST_CLASS_VERSION(wallet2_basic::address_book_row, 18) +BOOST_CLASS_VERSION(wallet2_basic::cache, 30) + +BOOST_CLASS_EXPORT_KEY(::wallet2_basic::hashchain) +BOOST_CLASS_EXPORT_KEY(::wallet2_basic::transfer_details) +BOOST_CLASS_EXPORT_KEY(::wallet2_basic::multisig_info::LR) +BOOST_CLASS_EXPORT_KEY(::wallet2_basic::multisig_info) +BOOST_CLASS_EXPORT_KEY(::wallet2_basic::unconfirmed_transfer_details) +BOOST_CLASS_EXPORT_KEY(::wallet2_basic::confirmed_transfer_details) +BOOST_CLASS_EXPORT_KEY(::wallet2_basic::payment_details) +BOOST_CLASS_EXPORT_KEY(::wallet2_basic::pool_payment_details) +BOOST_CLASS_EXPORT_KEY(::wallet2_basic::address_book_row) +BOOST_CLASS_EXPORT_KEY(::wallet2_basic::cache) diff --git a/src/wallet/wallet2_basic/wallet2_constants.h b/src/wallet/wallet2_basic/wallet2_constants.h new file mode 100644 index 0000000000..82623bca80 --- /dev/null +++ b/src/wallet/wallet2_basic/wallet2_constants.h @@ -0,0 +1,42 @@ +// 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. + +#pragma once + +#include +#include + +namespace wallet2_basic +{ + // a minute and a half + constexpr uint32_t DEFAULT_INACTIVITY_LOCK_TIMEOUT = 90; + + constexpr size_t SUBADDRESS_LOOKAHEAD_MAJOR = 50; + constexpr size_t SUBADDRESS_LOOKAHEAD_MINOR = 200; + +} // namespace wallet2_basic diff --git a/src/wallet/wallet2_basic/wallet2_storage.cpp b/src/wallet/wallet2_basic/wallet2_storage.cpp new file mode 100644 index 0000000000..229531b4ea --- /dev/null +++ b/src/wallet/wallet2_basic/wallet2_storage.cpp @@ -0,0 +1,730 @@ +// 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 +#include +#include + +#include "cryptonote_basic/account.h" +#include "device/device_cold.hpp" +#include "device_trezor/device_trezor.hpp" +#include "file_io_utils.h" +#include "serialization/binary_utils.h" +#include "storages/portable_storage_template_helper.h" +#include "wallet2_boost_serialization.h" +#include "wallet2_storage.h" + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "wallet.wallet2_basic.storage" + +using namespace boost::archive; +using rapidjson::Document; + +#define TRY_NOFAIL(stmt) try { stmt; } catch (...) {} + +namespace +{ +static hw::i_device_callback noop_device_cb; + +template +wallet2_basic::cache boost_deserialize_cache(const std::string& cache_data) +{ + wallet2_basic::cache c; + std::istringstream iss(cache_data); + Archive ar(iss); + ar >> c; + return c; +} + +/*************************************************************************************************** +********************************JSON ADAPTER HELPER FUNCTIONS*************************************** +***************************************************************************************************/ + +template +void adapt_json_field_intlike(T& out, const Document& json, const char* name, bool mand, std::false_type /*IsSaving*/) +{ + const rapidjson::Value::ConstMemberIterator memb_it = json.FindMember(name); + if (memb_it != json.MemberEnd()) + { + if (memb_it->value.IsInt()) + out = static_cast(memb_it->value.GetInt()); + else if (memb_it->value.IsUint()) + out = static_cast(memb_it->value.GetUint()); + else if (memb_it->value.IsUint64()) + out = static_cast(memb_it->value.GetUint64()); + else + ASSERT_MES_AND_THROW("Field " << name << " found in JSON, but not an int-like number"); + } + else if (mand) + ASSERT_MES_AND_THROW("Field " << name << " not found in JSON"); +} + +void adapt_json_field_String(std::string& out, const Document& json, const char* name, bool mand, std::false_type /*IsSaving*/) +{ + const rapidjson::Value::ConstMemberIterator memb_it = json.FindMember(name); + if (memb_it != json.MemberEnd()) + { + if (memb_it->value.IsString()) + out = std::string(memb_it->value.GetString(), memb_it->value.GetStringLength()); + else + ASSERT_MES_AND_THROW("Field " << name << " found in JSON, but not " << "String"); + } + else if (mand) + ASSERT_MES_AND_THROW("Field " << name << " not found in JSON"); +} + +// Load arbitrary typesfrom JSON string fields represented in binary_archive format +template +void adapt_json_field_binary_archive(T& out, const Document& json, const char* name, bool mand, std::false_type /*IsSaving*/) +{ + std::string binary_repr; + adapt_json_field_String(binary_repr, json, name, mand, std::false_type{}); + const bool r = serialization::parse_binary(binary_repr, out); + CHECK_AND_ASSERT_THROW_MES(r, "Could not parse object from binary archive in JSON field"); +} + +template +void adapt_json_field_intlike(const T& in, Document& json, const char* name, bool, std::true_type /*IsSaving*/) +{ + rapidjson::Value k(name, json.GetAllocator()); + rapidjson::Value v; + if (in < T{}) // Is negative? + v.SetInt(static_cast(in)); + else // Is positive + v.SetUint64(static_cast(in)); + json.AddMember(k, v, json.GetAllocator()); +} + +void adapt_json_field_String(const std::string& in, Document& json, const char* name, bool, std::true_type /*IsSaving*/) +{ + rapidjson::Value k(name, json.GetAllocator()); + rapidjson::Value v(in.data(), in.size(), json.GetAllocator()); + json.AddMember(k, v, json.GetAllocator()); +} + +// Store arbitrary types to JSON string fields represented in binary_archive format +template +void adapt_json_field_binary_archive(const T& in, Document& json, const char* name, bool, std::true_type /*IsSaving*/) +{ + std::string binary_repr; + const bool r = serialization::dump_binary(const_cast(in), binary_repr); + CHECK_AND_ASSERT_THROW_MES(r, "Could not represent object in binary archive"); + adapt_json_field_String(binary_repr, json, name, true, std::true_type{}); +} + +template +void adapt_json_field_Int(T& inout, Document& json, const char* name, bool mand, IsSaving) +{ adapt_json_field_intlike(inout, json, name, mand, IsSaving{}); } +template +void adapt_json_field_Uint(T& inout, Document& json, const char* name, bool mand, IsSaving) +{ adapt_json_field_intlike(inout, json, name, mand, IsSaving{}); } +template +void adapt_json_field_Uint64(T& inout, Document& json, const char* name, bool mand, IsSaving) +{ adapt_json_field_intlike(inout, json, name, mand, IsSaving{}); } +} // anonymous namespace + +namespace wallet2_basic +{ +/*************************************************************************************************** +*************************************CACHE STORAGE************************************************** +***************************************************************************************************/ +crypto::chacha_key cache::pwd_to_cache_key(const char* pwd, size_t len, uint64_t kdf_rounds) +{ + static_assert(crypto::HASH_SIZE == sizeof(crypto::chacha_key), "Mismatched sizes of hash and chacha key"); + + crypto::chacha_key key; + crypto::generate_chacha_key(pwd, len, key, kdf_rounds); + + epee::mlocked> cache_key_data; + memcpy(cache_key_data.data(), &key, crypto::HASH_SIZE); + cache_key_data[crypto::HASH_SIZE] = config::HASH_KEY_WALLET_CACHE; + crypto::cn_fast_hash(cache_key_data.data(), crypto::HASH_SIZE+1, reinterpret_cast(key)); + + return key; +} + +crypto::chacha_key cache::account_to_old_cache_key(const cryptonote::account_base& account, uint64_t kdf_rounds) +{ + crypto::chacha_key key; + hw::device &hwdev = account.get_device(); + const bool r = hwdev.generate_chacha_key(account.get_keys(), key, kdf_rounds); + THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "device failed to generate chacha key"); + return key; +} + +cache cache::load_from_memory +( + const std::string& cache_file_buf, + const epee::wipeable_string& password, + const cryptonote::account_base& wallet_account, + uint64_t kdf_rounds +) +{ + // Try to deserialize cache file buf into `cache_file_data` type. If success, + // then we are dealing with encrypted cache + cache_file_data cfd; + const bool encrypted_cache = ::serialization::parse_binary(cache_file_buf, cfd); + + if (encrypted_cache) + { + LOG_PRINT_L1("Taking encrypted wallet cache load path..."); + + // Decrypt cache contents into buffer + crypto::chacha_key cache_key = pwd_to_cache_key(password.data(), password.size(), kdf_rounds); + std::string cache_data; + cache_data.resize(cfd.cache_data.size()); + crypto::chacha20(cfd.cache_data.data(), cfd.cache_data.size(), cache_key, cfd.iv, &cache_data[0]); + + LOG_PRINT_L1("Trying to read from recent binary archive"); + try + { + cache c; + binary_archive ar{epee::strspan(cache_data)}; + if (::serialization::serialize(ar, c)) + if (::serialization::check_stream_state(ar)) + return c; + } + catch (...) {} + + LOG_PRINT_L1("Trying to read from binary archive with varint incompatibility"); + try + { + cache c; + binary_archive ar{epee::strspan(cache_data)}; + ar.enable_varint_bug_backward_compatibility(); + if (::serialization::serialize(ar, c)) + if (::serialization::check_stream_state(ar)) + return c; + } + catch (...) {} + + LOG_PRINT_L1("Trying to read from boost portable binary archive"); + TRY_NOFAIL(return boost_deserialize_cache(cache_data)); + + LOG_PRINT_L1("Switching to decryption key derived from account keys..."); + cache_key = account_to_old_cache_key(wallet_account, kdf_rounds); + crypto::chacha20(cfd.cache_data.data(), cfd.cache_data.size(), cache_key, cfd.iv, &cache_data[0]); + + LOG_PRINT_L1("Trying to read from boost portable binary archive encrypted with account keys"); + TRY_NOFAIL(return boost_deserialize_cache(cache_data)); + + LOG_PRINT_L1("Switching to old chacha8 encryption..."); + crypto::chacha8(cfd.cache_data.data(), cfd.cache_data.size(), cache_key, cfd.iv, &cache_data[0]); + + LOG_PRINT_L1("Trying to read from boost portable binary archive encrypted with account keys & chacha8"); + TRY_NOFAIL(return boost_deserialize_cache(cache_data)); + + LOG_PRINT_L1("Trying to read from boost UNportable binary archive encrypted with account keys & chacha8"); + TRY_NOFAIL(return boost_deserialize_cache(cache_data)); + } + else // not encrypted cache + { + LOG_PRINT_L1("Taking unencrypted wallet cache load path..."); + + LOG_PRINT_L1("Trying to read from boost portable binary archive unencrypted"); + TRY_NOFAIL(return boost_deserialize_cache(cache_file_buf)); + + LOG_PRINT_L1("Trying to read from boost UNportable binary archive unencrypted"); + TRY_NOFAIL(return boost_deserialize_cache(cache_file_buf)); + } + + LOG_ERROR("THROW EXCEPTION: " << "error::wallet_internal_error"); + tools::error::throw_wallet_ex( + std::string(__FILE__ ":" STRINGIZE(__LINE__)), "failed to load wallet cache"); +} + +std::string cache::store_to_memory(const epee::wipeable_string& password, uint64_t kdf_rounds) +{ + return store_to_memory(pwd_to_cache_key(password.data(), password.size(), kdf_rounds)); +} + +std::string cache::store_to_memory(const crypto::chacha_key& encryption_key) +{ + // Serialize cache + std::stringstream oss; + binary_archive ar1(oss); + THROW_WALLET_EXCEPTION_IF(!::serialization::serialize(ar1, *this), + error::wallet_internal_error, "Failed to serialize cache"); + + // Prepare outer cache_file_data data structure + std::string cache_pt = oss.str(); + cache_file_data cfd; + cfd.iv = crypto::rand(); + cfd.cache_data.resize(cache_pt.size()); + + // Encrypt cache + crypto::chacha20(cache_pt.data(), cache_pt.size(), encryption_key, cfd.iv, &cfd.cache_data[0]); + + // Serialize cache_file_data structure + oss.str(""); + binary_archive ar2(oss); + THROW_WALLET_EXCEPTION_IF(!::serialization::serialize(ar2, cfd), + error::wallet_internal_error, "Failed to serialize outer cache file data"); + + return oss.str(); +} + +/*************************************************************************************************** +*********************************WALLET KEYS STORAGE************************************************ +***************************************************************************************************/ + +crypto::chacha_key keys_data::pwd_to_keys_data_key(const char* pwd, size_t len, uint64_t kdf_rounds) +{ + crypto::chacha_key key; + crypto::generate_chacha_key(pwd, len, key, kdf_rounds); + return key; +} + +keys_data keys_data::load_from_memory +( + const std::string& keys_file_buf, + const epee::wipeable_string& password, + cryptonote::network_type nettype, + uint64_t kdf_rounds +) +{ + const crypto::chacha_key encryption_key = pwd_to_keys_data_key(password.data(), password.size(), kdf_rounds); + return load_from_memory(keys_file_buf, encryption_key, nettype); +} + +keys_data keys_data::load_from_memory +( + const std::string& keys_file_buf, + const crypto::chacha_key& encryption_key, + cryptonote::network_type nettype +) +{ + // Deserialize encrypted data and IV into `keys_file_data` structure + keys_file_data kfd; + bool r = ::serialization::parse_binary(keys_file_buf, kfd); + THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "internal error: failed to deserialize keys buffer"); + + // Derive chacha decryption key from password and decrypt key buffer + std::string decrypted_keys_data; + decrypted_keys_data.resize(kfd.account_data.size()); + crypto::chacha20(kfd.account_data.data(), kfd.account_data.size(), encryption_key, kfd.iv, &decrypted_keys_data[0]); + + mlog_set_log_level(4); + MINFO("Hex keys data JSON: " << epee::to_hex::string(epee::strspan(decrypted_keys_data))); + + rapidjson::Document json; + if (json.Parse(decrypted_keys_data.c_str()).HasParseError() || !json.IsObject()) + crypto::chacha8(kfd.account_data.data(), kfd.account_data.size(), encryption_key, kfd.iv, &decrypted_keys_data[0]); + + keys_data kd; + kd.m_nettype = nettype; + + if (json.Parse(decrypted_keys_data.c_str()).HasParseError()) + { + CHECK_AND_ASSERT_THROW_MES(nettype != cryptonote::UNDEFINED, + "No network type was provided and we can't deduce nettype from old wallet keys files"); + kd.is_old_file_format = true; + r = epee::serialization::load_t_from_binary(kd.m_account, decrypted_keys_data); + THROW_WALLET_EXCEPTION_IF(!r, error::invalid_password); + } + else if (json.IsObject()) // The contents should be JSON if the wallet follows the new format. + { + kd.adapt_tofrom_json_object(json, encryption_key); + } + else + { + THROW_WALLET_EXCEPTION(error::wallet_internal_error, + "malformed wallet keys JSON: Document root is not an object"); + } + + return kd; +} + +std::string keys_data::store_to_memory +( + const epee::wipeable_string& password, + bool downgrade_to_watch_only, + uint64_t kdf_rounds +) +{ + const crypto::chacha_key encryption_key = pwd_to_keys_data_key(password.data(), password.size(), kdf_rounds); + return store_to_memory(encryption_key); +} + +std::string keys_data::store_to_memory +( + const crypto::chacha_key& encryption_key, + bool downgrade_to_watch_only +) +{ + // Create JSON object containing all the information we need about our keys data + rapidjson::Document json; + json.SetObject(); + adapt_tofrom_json_object(json, encryption_key); + + // Serialize the JSON object + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + json.Accept(writer); + + // Encrypt the JSON buffer into a keys_file_data structure + keys_file_data kfd; + kfd.account_data.resize(buffer.GetSize()); + kfd.iv = crypto::rand(); + crypto::chacha20(buffer.GetString(), buffer.GetSize(), encryption_key, kfd.iv, &kfd.account_data[0]); + + // Serialize the keys_file_data structure as a binary archive + std::string final_buf; + const bool r = ::serialization::dump_binary(kfd, final_buf); + CHECK_AND_ASSERT_THROW_MES(r, "Failed to serialize keys_file_data into binary archive"); + + return final_buf; +} + +void keys_data::setup_account_keys_and_devices +( + const epee::wipeable_string& password, + hw::i_device_callback* device_cb, + uint64_t kdf_rounds +) +{ + const crypto::chacha_key encryption_key = pwd_to_keys_data_key(password.data(), password.size(), kdf_rounds); + setup_account_keys_and_devices(encryption_key, device_cb); +} + +void keys_data::setup_account_keys_and_devices +( + const crypto::chacha_key& encryption_key, + hw::i_device_callback* device_cb +) +{ + if (m_key_device_type == hw::device::device_type::LEDGER || m_key_device_type == hw::device::device_type::TREZOR) + { + LOG_PRINT_L0("Account on device. Initing device..."); + hw::device &hwdev = reconnect_device(device_cb); + + cryptonote::account_public_address device_account_public_address; + bool fetch_device_address = true; + + ::hw::device_cold* dev_cold = nullptr; + if (m_key_device_type == hw::device::device_type::TREZOR && (dev_cold = dynamic_cast<::hw::device_cold*>(&hwdev)) != nullptr) + { + THROW_WALLET_EXCEPTION_IF( + !dev_cold->get_public_address_with_no_passphrase(device_account_public_address), + error::wallet_internal_error, "Cannot get a device address"); + if (device_account_public_address == m_account.get_keys().m_account_address) + { + LOG_PRINT_L0("Wallet opened with an empty passphrase"); + fetch_device_address = false; + dev_cold->set_use_empty_passphrase(true); + } + else + { + fetch_device_address = true; + LOG_PRINT_L0("Wallet opening with an empty passphrase failed. Retry again: " << fetch_device_address); + dev_cold->reset_session(); + } + } + + if (fetch_device_address) + { + THROW_WALLET_EXCEPTION_IF(!hwdev.get_public_address(device_account_public_address), + error::wallet_internal_error, "Cannot get a device address"); + } + + THROW_WALLET_EXCEPTION_IF(device_account_public_address != m_account.get_keys().m_account_address, + error::wallet_internal_error, + "Device wallet does not match wallet address. If the device uses the passphrase feature, " + "please check whether the passphrase was entered correctly (it may have been misspelled - " + "different passphrases generate different wallets, passphrase is case-sensitive). " + "Device address: " + cryptonote::get_account_address_as_str(m_nettype, false, device_account_public_address) + + ", wallet address: " + m_account.get_public_address_str(m_nettype)); + LOG_PRINT_L0("Device inited..."); + } + else if (requires_external_device()) + { + THROW_WALLET_EXCEPTION(error::wallet_internal_error, "hardware device not supported"); + } + + hw::device& hwdev = m_account.get_keys().get_device(); + const bool view_only = m_watch_only || m_multisig || hwdev.device_protocol() == hw::device::PROTOCOL_COLD; + const bool keys_verified = verify_account_keys(view_only); + CHECK_AND_ASSERT_THROW_MES(keys_verified, "Device does not appear to correspond to this wallet file"); +} + +bool keys_data::verify_account_keys +( + bool view_only, + hw::device* alt_device +) const +{ + return wallet2_basic::verify_account_keys(m_account.get_keys(), view_only, alt_device); +} + +hw::device& keys_data::reconnect_device(hw::i_device_callback* device_cb) +{ + hw::trezor::register_all(); + hw::device& hwdev = hw::get_device(m_device_name); + + THROW_WALLET_EXCEPTION_IF(!hwdev.set_name(m_device_name), error::wallet_internal_error, + "Could not set device name " + m_device_name); + hwdev.set_network_type(m_nettype); + hwdev.set_derivation_path(m_device_derivation_path); + hwdev.set_callback(device_cb ? device_cb : &noop_device_cb); + THROW_WALLET_EXCEPTION_IF(!hwdev.init(), error::wallet_internal_error, + "Could not initialize the device " + m_device_name); + THROW_WALLET_EXCEPTION_IF(!hwdev.connect(), error::wallet_internal_error, + "Could not connect to the device " + m_device_name); + m_account.set_device(hwdev); + + return hwdev; +} + +#define ADAPT_JSON_FIELD_N(name, jtype, mandatory, var) \ + adapt_json_field_##jtype(var, obj, #name, mandatory, std::integral_constant()) \ + +#define ADAPT_JSON_FIELD(name, jtype, mandatory) \ + ADAPT_JSON_FIELD_N(name, jtype, mandatory, m_##name) \ + +template +void keys_data::adapt_tofrom_json_object +( + Document& obj, + const crypto::chacha_key& keys_key, + bool downgrade_to_watch_only +) +{ + // Important prereq: we assume we already know obj is an object and not an array, number, etc + + // We always encrypt the account when storing now, but very old wallets didn't + bool account_keys_are_encrypted = IsSaving; + ADAPT_JSON_FIELD_N(encrypted_secret_keys, Int, false, account_keys_are_encrypted); + if (!IsSaving) + m_keys_were_encrypted_on_load = account_keys_are_encrypted; + + if (IsSaving) // Saving account to JSON + { + cryptonote::account_base encrypted_account = m_account; + if (downgrade_to_watch_only) + encrypted_account.forget_spend_key(); + encrypted_account.encrypt_keys(keys_key); + const epee::byte_slice account_data = epee::serialization::store_t_to_binary(encrypted_account); + rapidjson::Value v(reinterpret_cast(account_data.data()), account_data.size(), obj.GetAllocator()); + obj.AddMember("key_data", v, obj.GetAllocator()); + } + else // Loading account from JSON + { + std::string account_data; + ADAPT_JSON_FIELD_N(key_data, String, true, account_data); + CHECK_AND_ASSERT_THROW_MES( + epee::serialization::load_t_from_binary(m_account, account_data), + "Could not parse account keys from EPEE binary"); + if (account_keys_are_encrypted) + m_account.decrypt_keys(keys_key); + } + + ADAPT_JSON_FIELD(nettype, Uint, m_nettype == cryptonote::UNDEFINED); + CHECK_AND_ASSERT_THROW_MES( + m_nettype == cryptonote::MAINNET || + m_nettype == cryptonote::TESTNET || + m_nettype == cryptonote::STAGENET || + m_nettype == cryptonote::FAKECHAIN, + "unrecognized network type for keys_data"); + + ADAPT_JSON_FIELD(multisig, Int, false); + ADAPT_JSON_FIELD(multisig_threshold, Uint, m_multisig); + ADAPT_JSON_FIELD(multisig_rounds_passed, Uint, false); + ADAPT_JSON_FIELD(enable_multisig, Int, false); + if (m_multisig) + { + ADAPT_JSON_FIELD(multisig_signers, binary_archive, true); + ADAPT_JSON_FIELD(multisig_derivations, binary_archive, true); + } + + ADAPT_JSON_FIELD(watch_only, Int, false); + ADAPT_JSON_FIELD(confirm_non_default_ring_size, Int, false); + ADAPT_JSON_FIELD(ask_password, Int, false); // @TODO: Check AskPasswordType + ADAPT_JSON_FIELD(refresh_type, Int, false); // @TODO: Check RefreshType + ADAPT_JSON_FIELD(skip_to_height, Uint64, false); + ADAPT_JSON_FIELD(max_reorg_depth, Uint64, false); + ADAPT_JSON_FIELD(min_output_count, Uint, false); + ADAPT_JSON_FIELD(min_output_value, Uint64, false); + ADAPT_JSON_FIELD(merge_destinations, Int, false); + ADAPT_JSON_FIELD(confirm_backlog, Int, false); + ADAPT_JSON_FIELD(confirm_backlog_threshold, Uint, false); + ADAPT_JSON_FIELD(confirm_export_overwrite, Int, false); + ADAPT_JSON_FIELD(auto_low_priority, Int, false); + ADAPT_JSON_FIELD(confirm_export_overwrite, Int, false); + ADAPT_JSON_FIELD(segregate_pre_fork_outputs, Int, false); + ADAPT_JSON_FIELD(key_reuse_mitigation2, Int, false); + ADAPT_JSON_FIELD(segregation_height, Uint, false); + ADAPT_JSON_FIELD(ignore_fractional_outputs, Int, false); + ADAPT_JSON_FIELD(ignore_outputs_above, Uint64, false); + ADAPT_JSON_FIELD(ignore_outputs_below, Uint64, false); + ADAPT_JSON_FIELD(track_uses, Int, false); + ADAPT_JSON_FIELD(show_wallet_name_when_locked, Int, false); + ADAPT_JSON_FIELD(inactivity_lock_timeout, Uint, false); + ADAPT_JSON_FIELD(setup_background_mining, Int, false); + ADAPT_JSON_FIELD(subaddress_lookahead_major, Uint, false); + ADAPT_JSON_FIELD(subaddress_lookahead_minor, Uint, false); + ADAPT_JSON_FIELD(always_confirm_transfers, Int, false); + ADAPT_JSON_FIELD(print_ring_members, Int, false); + ADAPT_JSON_FIELD(store_tx_info, Int, false); + ADAPT_JSON_FIELD(default_mixin, Uint, false); + ADAPT_JSON_FIELD(export_format, Int, false); // @TODO Check ExportFormat + ADAPT_JSON_FIELD(load_deprecated_formats, Int, false); + ADAPT_JSON_FIELD(default_priority, Uint, false); + ADAPT_JSON_FIELD(auto_refresh, Int, false); + ADAPT_JSON_FIELD(device_derivation_path, String, false); + + ADAPT_JSON_FIELD_N(store_tx_keys, Int, false, m_store_tx_info); // backward compat + ADAPT_JSON_FIELD_N(default_fee_multiplier, Uint, false, m_default_priority); // backward compat + ADAPT_JSON_FIELD_N(refresh_height, Uint64, false, m_refresh_from_block_height); + ADAPT_JSON_FIELD_N(key_on_device, Int, false, m_key_device_type); + ADAPT_JSON_FIELD_N(seed_language, String, false, seed_language); + + if (!IsSaving) + m_device_name = m_key_device_type == hw::device::device_type::LEDGER ? "Ledger" : "default"; + ADAPT_JSON_FIELD(device_name, String, false); + + ADAPT_JSON_FIELD(original_keys_available, Int, false); + if (m_original_keys_available) + { + std::string original_address, original_view_secret_key; + if (IsSaving) + { + original_address = get_account_address_as_str(m_nettype, false, m_original_address); + ADAPT_JSON_FIELD_N(original_address, String, true, original_address); + original_view_secret_key = epee::string_tools::pod_to_hex(m_original_view_secret_key); + ADAPT_JSON_FIELD_N(original_view_secret_key, String, true, original_view_secret_key); + } + else // loading original address + { + ADAPT_JSON_FIELD_N(original_address, String, true, original_address); + cryptonote::address_parse_info info; + CHECK_AND_ASSERT_THROW_MES(get_account_address_from_str(info, m_nettype, original_address), + "Failed to parse original_address from JSON"); + m_original_address = info.address; + + ADAPT_JSON_FIELD_N(original_view_secret_key, String, true, original_view_secret_key); + CHECK_AND_ASSERT_THROW_MES( + epee::string_tools::hex_to_pod(original_view_secret_key, m_original_view_secret_key), + "Failed to parse original_view_secret_key from JSON"); + } + } +} + +/*************************************************************************************************** +********************************** MISC ACCOUNT UTILS ********************************************* +***************************************************************************************************/ + +bool verify_account_keys +( + const cryptonote::account_keys& keys, + bool view_only, + hw::device* hwdev +) +{ + if (nullptr == hwdev) + { + hwdev = std::addressof(keys.get_device()); + CHECK_AND_ASSERT_THROW_MES(hwdev, "Account device is NULL and no alternate was provided"); + } + + if (!hwdev->verify_keys(keys.m_view_secret_key, keys.m_account_address.m_view_public_key)) + return false; + + if (!view_only) + if (!hwdev->verify_keys(keys.m_spend_secret_key, keys.m_account_address.m_spend_public_key)) + return false; + + return true; +} + +/*************************************************************************************************** +************************* WALLET KEYS/CACHE COMBINATION LOADING ************************************ +***************************************************************************************************/ + +void load_keys_and_cache_from_memory +( + const std::string& cache_file_buf, + const std::string& keys_file_buf, + const epee::wipeable_string& password, + cache& c, + keys_data& k, + cryptonote::network_type nettype, + bool allow_external_devices_setup, + hw::i_device_callback* device_cb, + uint64_t kdf_rounds +) +{ + k = keys_data::load_from_memory(keys_file_buf, password, nettype, kdf_rounds); + if (!k.requires_external_device() || allow_external_devices_setup) + { + k.setup_account_keys_and_devices(password, device_cb, kdf_rounds); + } + c = cache::load_from_memory(cache_file_buf, password, k.m_account, kdf_rounds); +} + +void load_keys_and_cache_from_file +( + const std::string& cache_path, + const epee::wipeable_string& password, + cache& c, + keys_data& k, + cryptonote::network_type nettype, + std::string keys_path, + bool allow_external_devices_setup, + hw::i_device_callback* device_cb, + uint64_t kdf_rounds +) +{ + if (keys_path.empty()) + { + keys_path = cache_path + ".keys"; + } + + std::string keys_file_buf; + CHECK_AND_ASSERT_THROW_MES(epee::file_io_utils::load_file_to_string(keys_path, keys_file_buf), + "Could not load keys wallet file"); + + k = keys_data::load_from_memory(keys_file_buf, password, nettype, kdf_rounds); + if (!k.requires_external_device() || allow_external_devices_setup) + { + k.setup_account_keys_and_devices(password, device_cb, kdf_rounds); + } + + std::string cache_file_buf; + const bool loaded_cache = epee::file_io_utils::load_file_to_string(cache_path, cache_file_buf); + + if (loaded_cache) + { + c = cache::load_from_memory(cache_file_buf, password, k.m_account, kdf_rounds); + } + else + { + MWARNING("Could not load cache from filesystem, returning default cache"); + c = cache(); + } +} +} // namespace wallet2_basic diff --git a/src/wallet/wallet2_basic/wallet2_storage.h b/src/wallet/wallet2_basic/wallet2_storage.h new file mode 100644 index 0000000000..7475d24053 --- /dev/null +++ b/src/wallet/wallet2_basic/wallet2_storage.h @@ -0,0 +1,297 @@ +// 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. + +#pragma once + +#include + +#include "wallet2_constants.h" +#include "wallet2_types.h" + +namespace wallet2_basic +{ +struct cache +{ + hashchain m_blockchain; + transfer_container m_transfers; + cryptonote::account_public_address m_account_public_address; + serializable_unordered_map m_key_images; + serializable_unordered_map m_unconfirmed_txs; + payment_container m_payments; + serializable_unordered_map m_tx_keys; + serializable_unordered_map m_confirmed_txs; + serializable_unordered_map m_tx_notes; + serializable_unordered_multimap m_unconfirmed_payments; + serializable_unordered_map m_pub_keys; + std::vector m_address_book; + std::unordered_set m_scanned_pool_txs[2]; + serializable_unordered_map m_subaddresses; + std::vector> m_subaddress_labels; + serializable_unordered_map> m_additional_tx_keys; + serializable_unordered_map m_attributes; + std::pair, std::vector> m_account_tags; + bool m_ring_history_saved; + uint64_t m_last_block_reward; + // Aux transaction data from device + serializable_unordered_map m_tx_device; + uint64_t m_device_last_key_image_sync; + serializable_unordered_map m_cold_key_images; + bool m_has_ever_refreshed_from_node; + + // There's special key derivations for specifically wallet cache files for some reason + static crypto::chacha_key pwd_to_cache_key(const char* pwd, size_t len, uint64_t kdf_rounds = 1); + static crypto::chacha_key account_to_old_cache_key(const cryptonote::account_base& account, uint64_t kdf_rounds = 1); + + static cache load_from_memory + ( + const std::string& cache_file_buf, + const epee::wipeable_string& password, + const cryptonote::account_base& wallet_account, + uint64_t kdf_rounds = 1 + ); + + std::string store_to_memory(const epee::wipeable_string& password, uint64_t kdf_rounds = 1); + std::string store_to_memory(const crypto::chacha_key& encryption_key); + + BEGIN_SERIALIZE_OBJECT() + MAGIC_FIELD("monero wallet cache") + VERSION_FIELD(1) + FIELD(m_blockchain) + FIELD(m_transfers) + FIELD(m_account_public_address) + FIELD(m_key_images) + FIELD(m_unconfirmed_txs) + FIELD(m_payments) + FIELD(m_tx_keys) + FIELD(m_confirmed_txs) + FIELD(m_tx_notes) + FIELD(m_unconfirmed_payments) + FIELD(m_pub_keys) + FIELD(m_address_book) + FIELD(m_scanned_pool_txs[0]) + FIELD(m_scanned_pool_txs[1]) + FIELD(m_subaddresses) + FIELD(m_subaddress_labels) + FIELD(m_additional_tx_keys) + FIELD(m_attributes) + FIELD(m_account_tags) + FIELD(m_ring_history_saved) + FIELD(m_last_block_reward) + FIELD(m_tx_device) + FIELD(m_device_last_key_image_sync) + FIELD(m_cold_key_images) + crypto::secret_key dummy_rpc_client_secret_key; // Compatibility for old RPC payment system + FIELD_N("m_rpc_client_secret_key", dummy_rpc_client_secret_key) + if (version < 1) + { + m_has_ever_refreshed_from_node = false; + return true; + } + FIELD(m_has_ever_refreshed_from_node) + END_SERIALIZE() +}; + +struct keys_data +{ + cryptonote::account_base m_account; + + bool is_old_file_format = false; + bool m_watch_only = false; /*!< no spend key */ + bool m_multisig = false; /*!< if > 1 spend secret key will not match spend public key */ + std::string seed_language = ""; /*!< Language of the mnemonics (seed). */ + cryptonote::network_type m_nettype = cryptonote::UNDEFINED; + uint32_t m_multisig_threshold = 0; + std::vector m_multisig_signers; + //in case of general M/N multisig wallet we should perform N - M + 1 key exchange rounds and remember how many rounds are passed. + uint32_t m_multisig_rounds_passed = 0; + std::vector m_multisig_derivations; + bool m_always_confirm_transfers = true; + bool m_print_ring_members = false; + bool m_store_tx_info = true; /*!< request txkey to be returned in RPC, and store in the wallet cache file */ + uint32_t m_default_mixin = 0; + uint32_t m_default_priority = 0; + bool m_auto_refresh = true; + RefreshType m_refresh_type = RefreshDefault; + uint64_t m_refresh_from_block_height = 0; + uint64_t m_skip_to_height = 0; + // m_skip_to_height is useful when we don't want to modify the wallet's restore height. + // m_refresh_from_block_height is also a wallet's restore height which should remain constant unless explicitly modified by the user. + bool m_confirm_non_default_ring_size = true; + AskPasswordType m_ask_password = AskPasswordToDecrypt; + uint64_t m_max_reorg_depth = ORPHANED_BLOCKS_MAX_COUNT; + uint32_t m_min_output_count = 0; + uint64_t m_min_output_value = 0; + bool m_merge_destinations = false; + bool m_confirm_backlog = true; + uint32_t m_confirm_backlog_threshold = 0; + bool m_confirm_export_overwrite = true; + bool m_auto_low_priority = true; + bool m_segregate_pre_fork_outputs = true; + bool m_key_reuse_mitigation2 = true; + uint64_t m_segregation_height = 0; + bool m_ignore_fractional_outputs = true; + uint64_t m_ignore_outputs_above = MONEY_SUPPLY; + uint64_t m_ignore_outputs_below = 0; + bool m_track_uses = false; + bool m_show_wallet_name_when_locked = false; + uint32_t m_inactivity_lock_timeout = DEFAULT_INACTIVITY_LOCK_TIMEOUT; + BackgroundMiningSetupType m_setup_background_mining = BackgroundMiningMaybe; + size_t m_subaddress_lookahead_major = SUBADDRESS_LOOKAHEAD_MAJOR; + size_t m_subaddress_lookahead_minor = SUBADDRESS_LOOKAHEAD_MINOR; + bool m_original_keys_available = false; + cryptonote::account_public_address m_original_address; + crypto::secret_key m_original_view_secret_key; + ExportFormat m_export_format = ExportFormat::Binary; + bool m_load_deprecated_formats = false; + std::string m_device_name = ""; + std::string m_device_derivation_path = ""; + hw::device::device_type m_key_device_type = hw::device::device_type::SOFTWARE; + bool m_enable_multisig = false; + bool m_allow_mismatched_daemon_version = false; + + static crypto::chacha_key pwd_to_keys_data_key(const char* pwd, size_t len, uint64_t kdf_rounds = 1); + + static keys_data load_from_memory + ( + const std::string& keys_file_buf, + const epee::wipeable_string& password, + cryptonote::network_type nettype = cryptonote::UNDEFINED, + uint64_t kdf_rounds = 1 + ); + static keys_data load_from_memory + ( + const std::string& keys_file_buf, + const crypto::chacha_key& encryption_key, + cryptonote::network_type nettype = cryptonote::UNDEFINED + ); + + std::string store_to_memory + ( + const epee::wipeable_string& password, + bool downgrade_to_watch_only = false, + uint64_t kdf_rounds = 1 + ); + std::string store_to_memory + ( + const crypto::chacha_key& encryption_key, + bool downgrade_to_watch_only = false + ); + + bool requires_external_device() const + { return m_key_device_type != hw::device::device_type::SOFTWARE; } + + bool keys_were_encrypted_on_load() const { return m_keys_were_encrypted_on_load; } + + void setup_account_keys_and_devices + ( + const epee::wipeable_string& password, + hw::i_device_callback* device_cb = nullptr, + uint64_t kdf_rounds = 1 + ); + + void setup_account_keys_and_devices + ( + const crypto::chacha_key& encryption_key, + hw::i_device_callback* device_cb = nullptr + ); + + bool verify_account_keys + ( + bool view_only = false, + hw::device* alt_device = nullptr + ) const; + + hw::device& reconnect_device(hw::i_device_callback* device_cb = nullptr); + +private: + bool m_keys_were_encrypted_on_load = false; + + template + void adapt_tofrom_json_object + ( + rapidjson::Document& obj, + const crypto::chacha_key& keys_key, + bool downgrade_to_watch_only = false + ); +}; // struct keys_data + +bool verify_account_keys +( + const cryptonote::account_keys& keys, + bool view_only = false, + hw::device* alt_device = nullptr +); + +void load_keys_and_cache_from_memory +( + const std::string& cache_file_buf, + const std::string& keys_file_buf, + const epee::wipeable_string& password, + cache& c, + keys_data& k, + cryptonote::network_type nettype = cryptonote::UNDEFINED, + bool allow_external_devices_setup = true, + hw::i_device_callback* device_cb = nullptr, + uint64_t kdf_rounds = 1 +); + +void load_keys_and_cache_from_file +( + const std::string& cache_path, + const epee::wipeable_string& password, + cache& c, + keys_data& k, + cryptonote::network_type nettype = cryptonote::UNDEFINED, + std::string keys_path = "", + bool allow_external_devices_setup = true, + hw::i_device_callback* device_cb = nullptr, + uint64_t kdf_rounds = 1 +); + +struct cache_file_data +{ + crypto::chacha_iv iv; + std::string cache_data; + + BEGIN_SERIALIZE_OBJECT() + FIELD(iv) + FIELD(cache_data) + END_SERIALIZE() +}; + +struct keys_file_data +{ + crypto::chacha_iv iv; + std::string account_data; + + BEGIN_SERIALIZE_OBJECT() + FIELD(iv) + FIELD(account_data) + END_SERIALIZE() +}; +} // namespace wallet2_basic diff --git a/src/wallet/wallet2_basic/wallet2_types.h b/src/wallet/wallet2_basic/wallet2_types.h new file mode 100644 index 0000000000..9bbf2f8f82 --- /dev/null +++ b/src/wallet/wallet2_basic/wallet2_types.h @@ -0,0 +1,335 @@ +// 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. + +#pragma once + +#include "cryptonote_basic/cryptonote_basic.h" +#include "wallet/wallet_errors.h" + +namespace wallet2_basic +{ +namespace error = tools::error; + +struct HashchainAccessor; +class hashchain +{ +public: + hashchain(): m_genesis(crypto::null_hash), m_offset(0) {} + + size_t size() const { return m_blockchain.size() + m_offset; } + size_t offset() const { return m_offset; } + const crypto::hash &genesis() const { return m_genesis; } + void push_back(const crypto::hash &hash) { if (m_offset == 0 && m_blockchain.empty()) m_genesis = hash; m_blockchain.push_back(hash); } + bool is_in_bounds(size_t idx) const { return idx >= m_offset && idx < size(); } + const crypto::hash &operator[](size_t idx) const { return m_blockchain[idx - m_offset]; } + crypto::hash &operator[](size_t idx) { return m_blockchain[idx - m_offset]; } + void crop(size_t height) { m_blockchain.resize(height - m_offset); } + void clear() { m_offset = 0; m_blockchain.clear(); } + bool empty() const { return m_blockchain.empty() && m_offset == 0; } + void trim(size_t height) { while (height > m_offset && m_blockchain.size() > 1) { m_blockchain.pop_front(); ++m_offset; } m_blockchain.shrink_to_fit(); } + void refill(const crypto::hash &hash) { m_blockchain.push_back(hash); --m_offset; } + + BEGIN_SERIALIZE_OBJECT() + VERSION_FIELD(0) + VARINT_FIELD(m_offset) + FIELD(m_genesis) + FIELD(m_blockchain) + END_SERIALIZE() + +private: + size_t m_offset; + crypto::hash m_genesis; + std::deque m_blockchain; + + friend struct HashchainAccessor; +}; + +struct multisig_info +{ + struct LR + { + rct::key m_L; + rct::key m_R; + + BEGIN_SERIALIZE_OBJECT() + FIELD(m_L) + FIELD(m_R) + END_SERIALIZE() + }; + + crypto::public_key m_signer; + std::vector m_LR; + std::vector m_partial_key_images; // one per key the participant has + + BEGIN_SERIALIZE_OBJECT() + FIELD(m_signer) + FIELD(m_LR) + FIELD(m_partial_key_images) + END_SERIALIZE() +}; + +struct transfer_details +{ + uint64_t m_block_height; + cryptonote::transaction_prefix m_tx; + crypto::hash m_txid; + uint64_t m_internal_output_index; + uint64_t m_global_output_index; + bool m_spent; + bool m_frozen; + uint64_t m_spent_height; + crypto::key_image m_key_image; //TODO: key_image stored twice :( + rct::key m_mask; + uint64_t m_amount; + bool m_rct; + bool m_key_image_known; + bool m_key_image_request; // view wallets: we want to request it; cold wallets: it was requested + uint64_t m_pk_index; + cryptonote::subaddress_index m_subaddr_index; + bool m_key_image_partial; + std::vector m_multisig_k; + std::vector m_multisig_info; // one per other participant + std::vector> m_uses; + + bool is_rct() const { return m_rct; } + uint64_t amount() const { return m_amount; } + const crypto::public_key get_public_key() const { + crypto::public_key output_public_key; + THROW_WALLET_EXCEPTION_IF(m_tx.vout.size() <= m_internal_output_index, + error::wallet_internal_error, "Too few outputs, outputs may be corrupted"); + THROW_WALLET_EXCEPTION_IF(!get_output_public_key(m_tx.vout[m_internal_output_index], output_public_key), + error::wallet_internal_error, "Unable to get output public key from output"); + return output_public_key; + }; + + BEGIN_SERIALIZE_OBJECT() + FIELD(m_block_height) + FIELD(m_tx) + FIELD(m_txid) + FIELD(m_internal_output_index) + FIELD(m_global_output_index) + FIELD(m_spent) + FIELD(m_frozen) + FIELD(m_spent_height) + FIELD(m_key_image) + FIELD(m_mask) + FIELD(m_amount) + FIELD(m_rct) + FIELD(m_key_image_known) + FIELD(m_key_image_request) + FIELD(m_pk_index) + FIELD(m_subaddr_index) + FIELD(m_key_image_partial) + FIELD(m_multisig_k) + FIELD(m_multisig_info) + FIELD(m_uses) + END_SERIALIZE() +}; + +typedef std::vector transfer_container; + +struct unconfirmed_transfer_details +{ + cryptonote::transaction_prefix m_tx; + uint64_t m_amount_in; + uint64_t m_amount_out; + uint64_t m_change; + time_t m_sent_time; + std::vector m_dests; + crypto::hash m_payment_id; + enum { pending, pending_in_pool, failed } m_state; + uint64_t m_timestamp; + uint32_t m_subaddr_account; // subaddress account of your wallet to be used in this transfer + std::set m_subaddr_indices; // set of address indices used as inputs in this transfer + std::vector>> m_rings; // relative + + BEGIN_SERIALIZE_OBJECT() + VERSION_FIELD(1) + FIELD(m_tx) + VARINT_FIELD(m_amount_in) + VARINT_FIELD(m_amount_out) + VARINT_FIELD(m_change) + VARINT_FIELD(m_sent_time) + FIELD(m_dests) + FIELD(m_payment_id) + if (version >= 1) + VARINT_FIELD(m_state) + VARINT_FIELD(m_timestamp) + VARINT_FIELD(m_subaddr_account) + FIELD(m_subaddr_indices) + FIELD(m_rings) + END_SERIALIZE() +}; + +struct confirmed_transfer_details +{ + cryptonote::transaction_prefix m_tx; + uint64_t m_amount_in; + uint64_t m_amount_out; + uint64_t m_change; + uint64_t m_block_height; + std::vector m_dests; + crypto::hash m_payment_id; + uint64_t m_timestamp; + uint64_t m_unlock_time; + uint32_t m_subaddr_account; // subaddress account of your wallet to be used in this transfer + std::set m_subaddr_indices; // set of address indices used as inputs in this transfer + std::vector>> m_rings; // relative + + confirmed_transfer_details() + : m_amount_in(0) + , m_amount_out(0) + , m_change((uint64_t)-1) + , m_block_height(0) + , m_payment_id(crypto::null_hash) + , m_timestamp(0) + , m_unlock_time(0) + , m_subaddr_account((uint32_t)-1) + {} + + confirmed_transfer_details(const unconfirmed_transfer_details &utd, uint64_t height) + : m_tx(utd.m_tx) + , m_amount_in(utd.m_amount_in) + , m_amount_out(utd.m_amount_out) + , m_change(utd.m_change) + , m_block_height(height) + , m_dests(utd.m_dests) + , m_payment_id(utd.m_payment_id) + , m_timestamp(utd.m_timestamp) + , m_unlock_time(utd.m_tx.unlock_time) + , m_subaddr_account(utd.m_subaddr_account) + , m_subaddr_indices(utd.m_subaddr_indices) + , m_rings(utd.m_rings) + {} + + BEGIN_SERIALIZE_OBJECT() + VERSION_FIELD(1) + if (version >= 1) + FIELD(m_tx) + VARINT_FIELD(m_amount_in) + VARINT_FIELD(m_amount_out) + VARINT_FIELD(m_change) + VARINT_FIELD(m_block_height) + FIELD(m_dests) + FIELD(m_payment_id) + VARINT_FIELD(m_timestamp) + VARINT_FIELD(m_unlock_time) + VARINT_FIELD(m_subaddr_account) + FIELD(m_subaddr_indices) + FIELD(m_rings) + END_SERIALIZE() +}; + +typedef std::vector amounts_container; +struct payment_details +{ + crypto::hash m_tx_hash; + uint64_t m_amount; + amounts_container m_amounts; + uint64_t m_fee; + uint64_t m_block_height; + uint64_t m_unlock_time; + uint64_t m_timestamp; + bool m_coinbase; + cryptonote::subaddress_index m_subaddr_index; + + BEGIN_SERIALIZE_OBJECT() + VERSION_FIELD(0) + FIELD(m_tx_hash) + VARINT_FIELD(m_amount) + FIELD(m_amounts) + VARINT_FIELD(m_fee) + VARINT_FIELD(m_block_height) + VARINT_FIELD(m_unlock_time) + VARINT_FIELD(m_timestamp) + FIELD(m_coinbase) + FIELD(m_subaddr_index) + END_SERIALIZE() +}; + +typedef serializable_unordered_multimap payment_container; + +struct pool_payment_details +{ + payment_details m_pd; + bool m_double_spend_seen; + + BEGIN_SERIALIZE_OBJECT() + VERSION_FIELD(0) + FIELD(m_pd) + FIELD(m_double_spend_seen) + END_SERIALIZE() +}; + +// GUI Address book +struct address_book_row +{ + cryptonote::account_public_address m_address; + crypto::hash8 m_payment_id; + std::string m_description; + bool m_is_subaddress; + bool m_has_payment_id; + + BEGIN_SERIALIZE_OBJECT() + VERSION_FIELD(0) + FIELD(m_address) + FIELD(m_payment_id) + FIELD(m_description) + FIELD(m_is_subaddress) + FIELD(m_has_payment_id) + END_SERIALIZE() +}; + +enum RefreshType +{ + RefreshFull, + RefreshOptimizeCoinbase, + RefreshNoCoinbase, + RefreshDefault = RefreshOptimizeCoinbase, +}; + +enum AskPasswordType +{ + AskPasswordNever = 0, + AskPasswordOnAction = 1, + AskPasswordToDecrypt = 2, +}; + +enum BackgroundMiningSetupType +{ + BackgroundMiningMaybe = 0, + BackgroundMiningYes = 1, + BackgroundMiningNo = 2, +}; + +enum ExportFormat +{ + Binary = 0, + Ascii, +}; +} // namespace wallet2_basic diff --git a/src/wallet/wallet_errors.h b/src/wallet/wallet_errors.h index 6706e77ff5..e2a64f79be 100644 --- a/src/wallet/wallet_errors.h +++ b/src/wallet/wallet_errors.h @@ -939,7 +939,7 @@ namespace tools #if !defined(_MSC_VER) template - void throw_wallet_ex(std::string&& loc, const TArgs&... args) + [[noreturn]] void throw_wallet_ex(std::string&& loc, const TArgs&... args) { TException e(std::move(loc), args...); LOG_PRINT_L0(e.to_string()); @@ -952,7 +952,7 @@ namespace tools #include template - void throw_wallet_ex(std::string&& loc) + [[noreturn]] void throw_wallet_ex(std::string&& loc) { TException e(std::move(loc)); LOG_PRINT_L0(e.to_string()); @@ -961,7 +961,7 @@ namespace tools #define GEN_throw_wallet_ex(z, n, data) \ template \ - void throw_wallet_ex(std::string&& loc, BOOST_PP_ENUM_BINARY_PARAMS(n, const TArg, &arg)) \ + [[noreturn]] void throw_wallet_ex(std::string&& loc, BOOST_PP_ENUM_BINARY_PARAMS(n, const TArg, &arg)) \ { \ TException e(std::move(loc), BOOST_PP_ENUM_PARAMS(n, arg)); \ LOG_PRINT_L0(e.to_string()); \ diff --git a/tests/core_tests/wallet_tools.cpp b/tests/core_tests/wallet_tools.cpp index a3b66e835d..a83d360cef 100644 --- a/tests/core_tests/wallet_tools.cpp +++ b/tests/core_tests/wallet_tools.cpp @@ -13,18 +13,18 @@ using namespace cryptonote; void wallet_accessor_test::set_account(tools::wallet2 * wallet, cryptonote::account_base& account) { wallet->clear(); - wallet->m_account = account; - - wallet->m_key_device_type = account.get_device().get_type(); - wallet->m_account_public_address = account.get_keys().m_account_address; - wallet->m_watch_only = false; - wallet->m_multisig = false; - wallet->m_multisig_threshold = 0; - wallet->m_multisig_signers.clear(); - wallet->m_device_name = account.get_device().get_name(); - - wallet->m_subaddress_lookahead_major = 5; - wallet->m_subaddress_lookahead_minor = 20; + wallet->m_keys_data.m_account = account; + + wallet->m_keys_data.m_key_device_type = account.get_device().get_type(); + wallet->m_cache.m_account_public_address = account.get_keys().m_account_address; + wallet->m_keys_data.m_watch_only = false; + wallet->m_keys_data.m_multisig = false; + wallet->m_keys_data.m_multisig_threshold = 0; + wallet->m_keys_data.m_multisig_signers.clear(); + wallet->m_keys_data.m_device_name = account.get_device().get_name(); + + wallet->m_keys_data.m_subaddress_lookahead_major = 5; + wallet->m_keys_data.m_subaddress_lookahead_minor = 20; wallet->setup_new_blockchain(); // generates also subadress register } diff --git a/tests/core_tests/wallet_tools.h b/tests/core_tests/wallet_tools.h index 1fb8017bff..01f064d284 100644 --- a/tests/core_tests/wallet_tools.h +++ b/tests/core_tests/wallet_tools.h @@ -53,8 +53,8 @@ class wallet_accessor_test { public: static void set_account(tools::wallet2 * wallet, cryptonote::account_base& account); - static tools::wallet2::transfer_container & get_transfers(tools::wallet2 * wallet) { return wallet->m_transfers; } - static subaddresses_t & get_subaddresses(tools::wallet2 * wallet) { return wallet->m_subaddresses; } + static tools::wallet2::transfer_container & get_transfers(tools::wallet2 * wallet) { return wallet->m_cache.m_transfers; } + static subaddresses_t & get_subaddresses(tools::wallet2 * wallet) { return wallet->m_cache.m_subaddresses; } static void process_parsed_blocks(tools::wallet2 * wallet, uint64_t start_height, const std::vector &blocks, const std::vector &parsed_blocks, uint64_t& blocks_added); }; diff --git a/tests/functional_tests/functional_tests_rpc.py b/tests/functional_tests/functional_tests_rpc.py index 9975cdfa23..1be5fea3c2 100755 --- a/tests/functional_tests/functional_tests_rpc.py +++ b/tests/functional_tests/functional_tests_rpc.py @@ -86,6 +86,9 @@ print('Starting servers...') try: + # Setup directories + subprocess.Popen(['rm', '-rf', WALLET_DIRECTORY]) + PYTHONPATH = os.environ['PYTHONPATH'] if 'PYTHONPATH' in os.environ else '' if len(PYTHONPATH) > 0: PYTHONPATH += ':' diff --git a/tests/functional_tests/wallet.py b/tests/functional_tests/wallet.py index 3bb4459d6b..c0f4676f1b 100755 --- a/tests/functional_tests/wallet.py +++ b/tests/functional_tests/wallet.py @@ -301,6 +301,24 @@ def open_close(self): except: ok = True assert ok + #################################################################################################################### + # This create->close->open pattern reveals if you have code which performs loading correctly but storing incorrectly + + wallet.create_wallet('createcloseopen', password='NIST SP 800-90A -- Dual_EC_DRBG') + cco_addr = wallet.get_address().address + assert cco_addr != '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW' # what are the chances haha + + wallet.close_wallet() + ok = False + try: wallet.get_address() + except: ok = True + assert ok + + wallet.open_wallet('createcloseopen', password='NIST SP 800-90A -- Dual_EC_DRBG') + res = wallet.get_address() + assert res.address == cco_addr + #################################################################################################################### + wallet.restore_deterministic_wallet(seed = 'velvet lymph giddy number token physics poetry unquoted nibs useful sabotage limits benches lifestyle eden nitrogen anvil fewest avoid batch vials washing fences goat unquoted') res = wallet.get_address() assert res.address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm' diff --git a/tests/unit_tests/CMakeLists.txt b/tests/unit_tests/CMakeLists.txt index 57bd568fc3..4e09d03bed 100644 --- a/tests/unit_tests/CMakeLists.txt +++ b/tests/unit_tests/CMakeLists.txt @@ -98,6 +98,7 @@ set(unit_tests_sources output_selection.cpp vercmp.cpp ringdb.cpp + wallet2_storage.cpp wipeable_string.cpp is_hdd.cpp aligned.cpp diff --git a/tests/unit_tests/serialization.cpp b/tests/unit_tests/serialization.cpp index 49f1dc3100..ef64353e5d 100644 --- a/tests/unit_tests/serialization.cpp +++ b/tests/unit_tests/serialization.cpp @@ -34,7 +34,6 @@ #include #include #include -#include #include "cryptonote_basic/cryptonote_basic.h" #include "cryptonote_basic/cryptonote_basic_impl.h" #include "ringct/rctSigs.h" @@ -667,30 +666,30 @@ TEST(Serialization, portability_wallet) std::vector m_address_book */ // blockchain - ASSERT_TRUE(w.m_blockchain.size() == 1); - ASSERT_TRUE(epee::string_tools::pod_to_hex(w.m_blockchain[0]) == "48ca7cd3c8de5b6a4d53d2861fbdaedca141553559f9be9520068053cda8430b"); + ASSERT_TRUE(w.m_cache.m_blockchain.size() == 1); + ASSERT_TRUE(epee::string_tools::pod_to_hex(w.m_cache.m_blockchain[0]) == "48ca7cd3c8de5b6a4d53d2861fbdaedca141553559f9be9520068053cda8430b"); // transfers (TODO) - ASSERT_TRUE(w.m_transfers.size() == 3); + ASSERT_TRUE(w.m_cache.m_transfers.size() == 3); // account public address - ASSERT_TRUE(epee::string_tools::pod_to_hex(w.m_account_public_address.m_view_public_key) == "e47d4b6df6ab7339539148c2a03ad3e2f3434e5ab2046848e1f21369a3937cad"); - ASSERT_TRUE(epee::string_tools::pod_to_hex(w.m_account_public_address.m_spend_public_key) == "13daa2af00ad26a372d317195de0bdd716f7a05d33bc4d7aff1664b6ee93c060"); + ASSERT_TRUE(epee::string_tools::pod_to_hex(w.m_cache.m_account_public_address.m_view_public_key) == "e47d4b6df6ab7339539148c2a03ad3e2f3434e5ab2046848e1f21369a3937cad"); + ASSERT_TRUE(epee::string_tools::pod_to_hex(w.m_cache.m_account_public_address.m_spend_public_key) == "13daa2af00ad26a372d317195de0bdd716f7a05d33bc4d7aff1664b6ee93c060"); // key images - ASSERT_TRUE(w.m_key_images.size() == 3); + ASSERT_TRUE(w.m_cache.m_key_images.size() == 3); { crypto::key_image ki[3]; epee::string_tools::hex_to_pod("c5680d3735b90871ca5e3d90cd82d6483eed1151b9ab75c2c8c3a7d89e00a5a8", ki[0]); epee::string_tools::hex_to_pod("d54cbd435a8d636ad9b01b8d4f3eb13bd0cf1ce98eddf53ab1617f9b763e66c0", ki[1]); epee::string_tools::hex_to_pod("6c3cd6af97c4070a7aef9b1344e7463e29c7cd245076fdb65da447a34da3ca76", ki[2]); - ASSERT_EQ_MAP(0, w.m_key_images, ki[0]); - ASSERT_EQ_MAP(1, w.m_key_images, ki[1]); - ASSERT_EQ_MAP(2, w.m_key_images, ki[2]); + ASSERT_EQ_MAP(0, w.m_cache.m_key_images, ki[0]); + ASSERT_EQ_MAP(1, w.m_cache.m_key_images, ki[1]); + ASSERT_EQ_MAP(2, w.m_cache.m_key_images, ki[2]); } // unconfirmed txs - ASSERT_TRUE(w.m_unconfirmed_txs.size() == 0); + ASSERT_TRUE(w.m_cache.m_unconfirmed_txs.size() == 0); // payments - ASSERT_TRUE(w.m_payments.size() == 2); + ASSERT_TRUE(w.m_cache.m_payments.size() == 2); { - auto pd0 = w.m_payments.begin(); + auto pd0 = w.m_cache.m_payments.begin(); auto pd1 = pd0; ++pd1; ASSERT_TRUE(epee::string_tools::pod_to_hex(pd0->first) == "0000000000000000000000000000000000000000000000000000000000000000"); @@ -709,7 +708,7 @@ TEST(Serialization, portability_wallet) ASSERT_TRUE(pd1->second.m_timestamp == 1483272963); } // tx keys - ASSERT_TRUE(w.m_tx_keys.size() == 2); + ASSERT_TRUE(w.m_cache.m_tx_keys.size() == 2); { const std::vector> txid_txkey = { @@ -722,37 +721,37 @@ TEST(Serialization, portability_wallet) crypto::secret_key txkey; epee::string_tools::hex_to_pod(txid_txkey[i].first, txid); epee::string_tools::hex_to_pod(txid_txkey[i].second, txkey); - ASSERT_EQ_MAP(txkey, w.m_tx_keys, txid); + ASSERT_EQ_MAP(txkey, w.m_cache.m_tx_keys, txid); } } // confirmed txs - ASSERT_TRUE(w.m_confirmed_txs.size() == 1); + ASSERT_TRUE(w.m_cache.m_confirmed_txs.size() == 1); // tx notes - ASSERT_TRUE(w.m_tx_notes.size() == 2); + ASSERT_TRUE(w.m_cache.m_tx_notes.size() == 2); { crypto::hash h[2]; epee::string_tools::hex_to_pod("15024343b38e77a1a9860dfed29921fa17e833fec837191a6b04fa7cb9605b8e", h[0]); epee::string_tools::hex_to_pod("6e7013684d35820f66c6679197ded9329bfe0e495effa47e7b25258799858dba", h[1]); - ASSERT_EQ_MAP("sample note", w.m_tx_notes, h[0]); - ASSERT_EQ_MAP("sample note 2", w.m_tx_notes, h[1]); + ASSERT_EQ_MAP("sample note", w.m_cache.m_tx_notes, h[0]); + ASSERT_EQ_MAP("sample note 2", w.m_cache.m_tx_notes, h[1]); } // unconfirmed payments - ASSERT_TRUE(w.m_unconfirmed_payments.size() == 0); + ASSERT_TRUE(w.m_cache.m_unconfirmed_payments.size() == 0); // pub keys - ASSERT_TRUE(w.m_pub_keys.size() == 3); + ASSERT_TRUE(w.m_cache.m_pub_keys.size() == 3); { crypto::public_key pubkey[3]; epee::string_tools::hex_to_pod("33f75f264574cb3a9ea5b24220a5312e183d36dc321c9091dfbb720922a4f7b0", pubkey[0]); epee::string_tools::hex_to_pod("5066ff2ce9861b1d131cf16eeaa01264933a49f28242b97b153e922ec7b4b3cb", pubkey[1]); epee::string_tools::hex_to_pod("0d8467e16e73d16510452b78823e082e05ee3a63788d40de577cf31eb555f0c8", pubkey[2]); - ASSERT_EQ_MAP(0, w.m_pub_keys, pubkey[0]); - ASSERT_EQ_MAP(1, w.m_pub_keys, pubkey[1]); - ASSERT_EQ_MAP(2, w.m_pub_keys, pubkey[2]); + ASSERT_EQ_MAP(0, w.m_cache.m_pub_keys, pubkey[0]); + ASSERT_EQ_MAP(1, w.m_cache.m_pub_keys, pubkey[1]); + ASSERT_EQ_MAP(2, w.m_cache.m_pub_keys, pubkey[2]); } // address book - ASSERT_TRUE(w.m_address_book.size() == 1); + ASSERT_TRUE(w.m_cache.m_address_book.size() == 1); { - auto address_book_row = w.m_address_book.begin(); + auto address_book_row = w.m_cache.m_address_book.begin(); ASSERT_TRUE(epee::string_tools::pod_to_hex(address_book_row->m_address.m_spend_public_key) == "9bc53a6ff7b0831c9470f71b6b972dbe5ad1e8606f72682868b1dda64e119fb3"); ASSERT_TRUE(epee::string_tools::pod_to_hex(address_book_row->m_address.m_view_public_key) == "49fece1ef97dc0c0f7a5e2106e75e96edd910f7e86b56e1e308cd0cf734df191"); ASSERT_TRUE(address_book_row->m_description == "testnet wallet 9y52S6"); diff --git a/tests/unit_tests/unit_tests_utils.h b/tests/unit_tests/unit_tests_utils.h index e3c6c2521f..2a452d21f8 100644 --- a/tests/unit_tests/unit_tests_utils.h +++ b/tests/unit_tests/unit_tests_utils.h @@ -72,3 +72,12 @@ namespace unit_test ASSERT_TRUE(found != map.end()); \ ASSERT_EQ(val, found->second); \ } while (false) + +#define EXPECT_EQ_MAP(val, map, key) \ + do { \ + auto found = map.find(key); \ + EXPECT_TRUE(found != map.end()); \ + if (found == map.end()) break; \ + EXPECT_EQ(val, found->second); \ + } while (false) \ + diff --git a/tests/unit_tests/wallet2_storage.cpp b/tests/unit_tests/wallet2_storage.cpp new file mode 100644 index 0000000000..de77881b97 --- /dev/null +++ b/tests/unit_tests/wallet2_storage.cpp @@ -0,0 +1,151 @@ +// 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 "gtest/gtest.h" + +#include "unit_tests_utils.h" +#include "wallet/wallet2_basic/wallet2_storage.h" + +TEST(wallet2_storage, read_old_wallet) +{ + const boost::filesystem::path wallet_file = unit_test::data_dir / "wallet_9svHk1"; + const epee::wipeable_string password = "test"; + + wallet2_basic::cache c; + wallet2_basic::keys_data k; + wallet2_basic::load_keys_and_cache_from_file(wallet_file.string(), password, c, k); + + /* + This test suite is adapated from unit test Serialization.portability_wallet + Cache fields to be checked: + std::vector m_blockchain + std::vector m_transfers + cryptonote::account_public_address m_account_public_address + std::unordered_map m_key_images + std::unordered_map m_unconfirmed_txs + std::unordered_multimap m_payments + std::unordered_map m_tx_keys + std::unordered_map m_confirmed_txs + std::unordered_map m_tx_notes + std::unordered_map m_unconfirmed_payments + std::unordered_map m_pub_keys + std::vector m_address_book + */ + + // blockchain + EXPECT_TRUE(c.m_blockchain.size() == 1); + EXPECT_TRUE(epee::string_tools::pod_to_hex(c.m_blockchain[0]) == "48ca7cd3c8de5b6a4d53d2861fbdaedca141553559f9be9520068053cda8430b"); + // transfers (TODO) + EXPECT_TRUE(c.m_transfers.size() == 3); + // account public address + EXPECT_TRUE(epee::string_tools::pod_to_hex(c.m_account_public_address.m_view_public_key) == "e47d4b6df6ab7339539148c2a03ad3e2f3434e5ab2046848e1f21369a3937cad"); + EXPECT_TRUE(epee::string_tools::pod_to_hex(c.m_account_public_address.m_spend_public_key) == "13daa2af00ad26a372d317195de0bdd716f7a05d33bc4d7aff1664b6ee93c060"); + // key images + ASSERT_TRUE(c.m_key_images.size() == 3); + { + crypto::key_image ki[3]; + epee::string_tools::hex_to_pod("c5680d3735b90871ca5e3d90cd82d6483eed1151b9ab75c2c8c3a7d89e00a5a8", ki[0]); + epee::string_tools::hex_to_pod("d54cbd435a8d636ad9b01b8d4f3eb13bd0cf1ce98eddf53ab1617f9b763e66c0", ki[1]); + epee::string_tools::hex_to_pod("6c3cd6af97c4070a7aef9b1344e7463e29c7cd245076fdb65da447a34da3ca76", ki[2]); + EXPECT_EQ_MAP(0, c.m_key_images, ki[0]); + EXPECT_EQ_MAP(1, c.m_key_images, ki[1]); + EXPECT_EQ_MAP(2, c.m_key_images, ki[2]); + } + // unconfirmed txs + EXPECT_TRUE(c.m_unconfirmed_txs.size() == 0); + // payments + ASSERT_TRUE(c.m_payments.size() == 2); + { + auto pd0 = c.m_payments.begin(); + auto pd1 = pd0; + ++pd1; + EXPECT_TRUE(epee::string_tools::pod_to_hex(pd0->first) == "0000000000000000000000000000000000000000000000000000000000000000"); + EXPECT_TRUE(epee::string_tools::pod_to_hex(pd1->first) == "0000000000000000000000000000000000000000000000000000000000000000"); + if (epee::string_tools::pod_to_hex(pd0->second.m_tx_hash) == "ec34c9bb12b99af33d49691384eee5bed9171498ff04e59516505f35d1fc5efc") + swap(pd0, pd1); + EXPECT_TRUE(epee::string_tools::pod_to_hex(pd0->second.m_tx_hash) == "15024343b38e77a1a9860dfed29921fa17e833fec837191a6b04fa7cb9605b8e"); + EXPECT_TRUE(epee::string_tools::pod_to_hex(pd1->second.m_tx_hash) == "ec34c9bb12b99af33d49691384eee5bed9171498ff04e59516505f35d1fc5efc"); + EXPECT_TRUE(pd0->second.m_amount == 13400845012231); + EXPECT_TRUE(pd1->second.m_amount == 1200000000000); + EXPECT_TRUE(pd0->second.m_block_height == 818424); + EXPECT_TRUE(pd1->second.m_block_height == 818522); + EXPECT_TRUE(pd0->second.m_unlock_time == 818484); + EXPECT_TRUE(pd1->second.m_unlock_time == 0); + EXPECT_TRUE(pd0->second.m_timestamp == 1483263366); + EXPECT_TRUE(pd1->second.m_timestamp == 1483272963); + } + // tx keys + ASSERT_TRUE(c.m_tx_keys.size() == 2); + { + const std::vector> txid_txkey = + { + {"b9aac8c020ab33859e0c0b6331f46a8780d349e7ac17b067116e2d87bf48daad", "bf3614c6de1d06c09add5d92a5265d8c76af706f7bc6ac830d6b0d109aa87701"}, + {"6e7013684d35820f66c6679197ded9329bfe0e495effa47e7b25258799858dba", "e556884246df5a787def6732c6ea38f1e092fa13e5ea98f732b99c07a6332003"}, + }; + for (size_t i = 0; i < txid_txkey.size(); ++i) + { + crypto::hash txid; + crypto::secret_key txkey; + epee::string_tools::hex_to_pod(txid_txkey[i].first, txid); + epee::string_tools::hex_to_pod(txid_txkey[i].second, txkey); + EXPECT_EQ_MAP(txkey, c.m_tx_keys, txid); + } + } + // confirmed txs + EXPECT_TRUE(c.m_confirmed_txs.size() == 1); + // tx notes + ASSERT_TRUE(c.m_tx_notes.size() == 2); + { + crypto::hash h[2]; + epee::string_tools::hex_to_pod("15024343b38e77a1a9860dfed29921fa17e833fec837191a6b04fa7cb9605b8e", h[0]); + epee::string_tools::hex_to_pod("6e7013684d35820f66c6679197ded9329bfe0e495effa47e7b25258799858dba", h[1]); + EXPECT_EQ_MAP("sample note", c.m_tx_notes, h[0]); + EXPECT_EQ_MAP("sample note 2", c.m_tx_notes, h[1]); + } + // unconfirmed payments + EXPECT_TRUE(c.m_unconfirmed_payments.size() == 0); + // pub keys + ASSERT_TRUE(c.m_pub_keys.size() == 3); + { + crypto::public_key pubkey[3]; + epee::string_tools::hex_to_pod("33f75f264574cb3a9ea5b24220a5312e183d36dc321c9091dfbb720922a4f7b0", pubkey[0]); + epee::string_tools::hex_to_pod("5066ff2ce9861b1d131cf16eeaa01264933a49f28242b97b153e922ec7b4b3cb", pubkey[1]); + epee::string_tools::hex_to_pod("0d8467e16e73d16510452b78823e082e05ee3a63788d40de577cf31eb555f0c8", pubkey[2]); + EXPECT_EQ_MAP(0, c.m_pub_keys, pubkey[0]); + EXPECT_EQ_MAP(1, c.m_pub_keys, pubkey[1]); + EXPECT_EQ_MAP(2, c.m_pub_keys, pubkey[2]); + } + // address book + ASSERT_TRUE(c.m_address_book.size() == 1); + { + auto address_book_row = c.m_address_book.begin(); + EXPECT_TRUE(epee::string_tools::pod_to_hex(address_book_row->m_address.m_spend_public_key) == "9bc53a6ff7b0831c9470f71b6b972dbe5ad1e8606f72682868b1dda64e119fb3"); + EXPECT_TRUE(epee::string_tools::pod_to_hex(address_book_row->m_address.m_view_public_key) == "49fece1ef97dc0c0f7a5e2106e75e96edd910f7e86b56e1e308cd0cf734df191"); + EXPECT_TRUE(address_book_row->m_description == "testnet wallet 9y52S6"); + } +}