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"); + } +}