From 5829d5f594b1e1fc5a8b78be17afe5bb0878ee91 Mon Sep 17 00:00:00 2001 From: Czarek Nakamoto Date: Fri, 6 Mar 2026 12:06:40 +0100 Subject: wip --- builder/Dockerfile | 3 +- contrib/depends/Makefile | 4 +- contrib/depends/gen_toolchain.cmake.sh | 14 +- ...issing-___clear_cache-when-targetting-iOS.patch | 2 +- ...ude-locale-only-when-targeting-WIN32 copy.patch | 44 + patches/monero/0022-fix-remove-flaky-test.patch | 27 + ...issing-___clear_cache-when-targetting-iOS.patch | 19 + ...et-background-sync-with-just-the-view-key.patch | 4309 -------------------- .../wownero/0002-fix-is_trivially_copyable.patch | 32 + ...issing-___clear_cache-when-targetting-iOS.patch | 19 - .../wownero/0003-fix-is_trivially_copyable.patch | 32 - patches/wownero/0003-store-crash-fix.patch | 212 + patches/wownero/0004-store-crash-fix.patch | 210 - .../0004-uint64_t-missing-definition-fix.patch | 25 + .../0005-uint64_t-missing-definition-fix.patch | 25 - ...005-use-proper-error-handling-in-get_seed.patch | 71 + patches/wownero/0006-UR-functions.patch | 1027 +++++ ...006-use-proper-error-handling-in-get_seed.patch | 71 - patches/wownero/0007-UR-functions.patch | 1036 ----- .../wownero/0007-add-dummy-device-for-ledger.patch | 619 +++ .../wownero/0008-add-dummy-device-for-ledger.patch | 619 --- patches/wownero/0008-polyseed.patch | 1446 +++++++ patches/wownero/0009-coin-control.patch | 979 +++++ patches/wownero/0009-polyseed.patch | 1464 ------- ...oding-and-tx-key-getter-for-PendingTransc.patch | 68 + patches/wownero/0010-coin-control.patch | 979 ----- ...oding-and-tx-key-getter-for-PendingTransc.patch | 68 - ...dd-recoverDeterministicWalletFromSpendKey.patch | 153 + ...dd-recoverDeterministicWalletFromSpendKey.patch | 153 - .../0012-add-monero-submodule-support.patch | 79 + .../0013-add-monero-submodule-support.patch | 65 - patches/wownero/0013-fix-iOS-depends-build.patch | 104 + patches/wownero/0014-fix-iOS-depends-build.patch | 104 - ...-include-locale-only-when-targeting-WIN32.patch | 43 + .../0015-change-earliest-fork-height-message.patch | 25 + ...-include-locale-only-when-targeting-WIN32.patch | 43 - .../0016-change-earliest-fork-height-message.patch | 25 - ...enerate_translations_header.c-requirement.patch | 132 + patches/wownero/0017-pr-9880.patch | 25 - .../0018-fix-unary_function-__unary_function.patch | 28 - wownero | 2 +- 41 files changed, 5116 insertions(+), 9289 deletions(-) create mode 100644 patches/monero/0013-include-locale-only-when-targeting-WIN32 copy.patch create mode 100644 patches/monero/0022-fix-remove-flaky-test.patch create mode 100644 patches/wownero/0001-fix-missing-___clear_cache-when-targetting-iOS.patch delete mode 100644 patches/wownero/0001-wallet-background-sync-with-just-the-view-key.patch create mode 100644 patches/wownero/0002-fix-is_trivially_copyable.patch delete mode 100644 patches/wownero/0002-fix-missing-___clear_cache-when-targetting-iOS.patch delete mode 100644 patches/wownero/0003-fix-is_trivially_copyable.patch create mode 100644 patches/wownero/0003-store-crash-fix.patch delete mode 100644 patches/wownero/0004-store-crash-fix.patch create mode 100644 patches/wownero/0004-uint64_t-missing-definition-fix.patch delete mode 100644 patches/wownero/0005-uint64_t-missing-definition-fix.patch create mode 100644 patches/wownero/0005-use-proper-error-handling-in-get_seed.patch create mode 100644 patches/wownero/0006-UR-functions.patch delete mode 100644 patches/wownero/0006-use-proper-error-handling-in-get_seed.patch delete mode 100644 patches/wownero/0007-UR-functions.patch create mode 100644 patches/wownero/0007-add-dummy-device-for-ledger.patch delete mode 100644 patches/wownero/0008-add-dummy-device-for-ledger.patch create mode 100644 patches/wownero/0008-polyseed.patch create mode 100644 patches/wownero/0009-coin-control.patch delete mode 100644 patches/wownero/0009-polyseed.patch create mode 100644 patches/wownero/0010-Add-hex-encoding-and-tx-key-getter-for-PendingTransc.patch delete mode 100644 patches/wownero/0010-coin-control.patch delete mode 100644 patches/wownero/0011-Add-hex-encoding-and-tx-key-getter-for-PendingTransc.patch create mode 100644 patches/wownero/0011-Add-recoverDeterministicWalletFromSpendKey.patch delete mode 100644 patches/wownero/0012-Add-recoverDeterministicWalletFromSpendKey.patch create mode 100644 patches/wownero/0012-add-monero-submodule-support.patch delete mode 100644 patches/wownero/0013-add-monero-submodule-support.patch create mode 100644 patches/wownero/0013-fix-iOS-depends-build.patch delete mode 100644 patches/wownero/0014-fix-iOS-depends-build.patch create mode 100644 patches/wownero/0014-include-locale-only-when-targeting-WIN32.patch create mode 100644 patches/wownero/0015-change-earliest-fork-height-message.patch delete mode 100644 patches/wownero/0015-include-locale-only-when-targeting-WIN32.patch delete mode 100644 patches/wownero/0016-change-earliest-fork-height-message.patch create mode 100644 patches/wownero/0016-drop-generate_translations_header.c-requirement.patch delete mode 100644 patches/wownero/0017-pr-9880.patch delete mode 100644 patches/wownero/0018-fix-unary_function-__unary_function.patch diff --git a/builder/Dockerfile b/builder/Dockerfile index 44b7413..e72c308 100644 --- a/builder/Dockerfile +++ b/builder/Dockerfile @@ -9,7 +9,8 @@ RUN apt update \ USER user RUN git config --global user.email "monero_c@mrcyjanek.net" \ - && git config --global user.name "MoneroC CI" + && git config --global user.name "MoneroC CI" \ + && git config --global --add safe.directory '*' FROM builder_base AS contrib_simplybs COPY contrib/depends /w/contrib/depends diff --git a/contrib/depends/Makefile b/contrib/depends/Makefile index 32cca84..0667624 100644 --- a/contrib/depends/Makefile +++ b/contrib/depends/Makefile @@ -3,7 +3,7 @@ HOST ?= PACKAGES = native/cmake,native/python@3.14,native/git,native/_,native/make,native/cmake-toolchain,icu4c,boost@1_90_0,zeromq,unbound,sodium,openssl,libiconv,zlib -SIMPLYBS_HASH = 0414efe7204404aa50911eaaeab885910c1ef313 +SIMPLYBS_HASH = f6eb673d26451799dfc9082761ae5c635b8878a0 all: simplybs $(if $(HOST),sbs-build-$(HOST),) @if [ -z "$(HOST)" ]; then \ @@ -11,7 +11,7 @@ all: simplybs $(if $(HOST),sbs-build-$(HOST),) exit 1; \ fi -.PHONY: simplybs sbs-build-% sps-cleanup +.PHONY: simplybs sbs-build-% sbs-cleanup sbs-cleanup: @rm -rf simplybs/_ diff --git a/contrib/depends/gen_toolchain.cmake.sh b/contrib/depends/gen_toolchain.cmake.sh index df0dd73..153c9ed 100755 --- a/contrib/depends/gen_toolchain.cmake.sh +++ b/contrib/depends/gen_toolchain.cmake.sh @@ -48,6 +48,7 @@ sed -e "s|@HOST@|$TARGET|g" \ -e "s|@build_tests@|OFF|g" \ -e "s|@cmake_system_name@|$CMAKE_SYSTEM_NAME|g" \ -e "s|@prefix@|$PREFIX|g" \ + -e "s|@nativeprefix@|$NATIVEPREFIX|g" \ -e "s|@arch@|$ARCH|g" > $outfile < +Date: Mon, 18 Nov 2024 10:57:37 -0500 +Subject: [PATCH 13/17] include locale only when targeting WIN32 + +--- + CMakeLists.txt | 6 +++++- + src/wallet/api/wallet.cpp | 2 ++ + 2 files changed, 7 insertions(+), 1 deletion(-) + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 1eac121db..5938be622 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -1089,7 +1089,11 @@ if(NOT Boost_FOUND) + elseif(Boost_FOUND) + message(STATUS "Found Boost Version: ${Boost_VERSION_STRING}") + +- set(BOOST_COMPONENTS filesystem thread date_time chrono serialization program_options locale) ++ set(BOOST_COMPONENTS filesystem thread date_time chrono serialization program_options) ++ ++ if(WIN32) ++ list(APPEND BOOST_COMPONENTS locale) ++ endif() + + # Boost System is header-only since 1.69 + if (Boost_VERSION_STRING VERSION_LESS 1.69.0) +diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp +index 20ccbfb35..c43803033 100644 +--- a/src/wallet/api/wallet.cpp ++++ b/src/wallet/api/wallet.cpp +@@ -46,7 +46,9 @@ + #include + #include + ++#ifdef WIN32 + #include ++#endif + #include + #include "bc-ur/src/bc-ur.hpp" + #if defined(HIDAPI_DUMMY) && !defined(HAVE_HIDAPI) +-- +2.49.0 + diff --git a/patches/monero/0022-fix-remove-flaky-test.patch b/patches/monero/0022-fix-remove-flaky-test.patch new file mode 100644 index 0000000..4c70ca3 --- /dev/null +++ b/patches/monero/0022-fix-remove-flaky-test.patch @@ -0,0 +1,27 @@ +From 3855cc39761321650484f5fba70c48dd8eb18444 Mon Sep 17 00:00:00 2001 +From: Czarek Nakamoto +Date: Thu, 5 Mar 2026 18:12:53 +0100 +Subject: [PATCH] fix: remove flaky test + +--- + CMakeLists.txt | 4 +--- + 1 file changed, 1 insertion(+), 3 deletions(-) + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 2525cf8ba..42189c0fe 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -235,9 +235,7 @@ add_library(l3 OBJECT incorrect_source.cpp) + "-DCMAKE_MODULE_LINKER_FLAGS=${CMAKE_MODULE_LINKER_FLAGS}" + "-DEXPECT_SUCCESS=${EXPECT}" + ) +- if (NOT ${SUCCESS} STREQUAL ${EXPECT}) +- message(FATAL_ERROR "Undefined symbols test failure: expect(${EXPECT}), success(${SUCCESS})") +- endif() ++ + file(REMOVE_RECURSE "${TEST_PROJECT}") + endforeach() + endfunction() +-- +2.51.0 + diff --git a/patches/wownero/0001-fix-missing-___clear_cache-when-targetting-iOS.patch b/patches/wownero/0001-fix-missing-___clear_cache-when-targetting-iOS.patch new file mode 100644 index 0000000..6b5dbe0 --- /dev/null +++ b/patches/wownero/0001-fix-missing-___clear_cache-when-targetting-iOS.patch @@ -0,0 +1,19 @@ +From 8de3cd7566eb3a6f3dd88d1e4126d2b16cfdea14 Mon Sep 17 00:00:00 2001 +From: Czarek Nakamoto +Date: Wed, 23 Oct 2024 15:18:21 +0200 +Subject: [PATCH 01/16] fix missing ___clear_cache when targetting iOS + +--- + external/randomwow | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/external/randomwow b/external/randomwow +index 27b099b6d..6f30d4b92 160000 +--- a/external/randomwow ++++ b/external/randomwow +@@ -1 +1 @@ +-Subproject commit 27b099b6dd6fef6e17f58c6dfe00009e9c5df587 ++Subproject commit 6f30d4b924fecb231e5b683915cc75d18b3b5866 +-- +2.51.0 + diff --git a/patches/wownero/0001-wallet-background-sync-with-just-the-view-key.patch b/patches/wownero/0001-wallet-background-sync-with-just-the-view-key.patch deleted file mode 100644 index e45deb1..0000000 --- a/patches/wownero/0001-wallet-background-sync-with-just-the-view-key.patch +++ /dev/null @@ -1,4309 +0,0 @@ -From 6ebd4546355d3d6ed82e3d30a45ecb06310b958e Mon Sep 17 00:00:00 2001 -From: j-berman -Date: Thu, 13 Oct 2022 18:33:33 -0700 -Subject: [PATCH 01/15] wallet: background sync with just the view key - -- When background syncing, the wallet wipes the spend key -from memory and processes all new transactions. The wallet saves -all receives, spends, and "plausible" spends of receives the -wallet does not know key images for. -- When background sync disabled, the wallet processes all -background synced txs and then clears the background sync cache. -- Adding "plausible" spends to the background sync cache ensures -that the wallet does not need to query the daemon to see if any -received outputs were spent while background sync was enabled. -This would harm privacy especially for users of 3rd party daemons. -- To enable the feature in the CLI wallet, the user can set -background-sync to reuse-wallet-password or -custom-background-password and the wallet automatically syncs in -the background when the wallet locks, then processes all -background synced txs when the wallet is unlocked. -- The custom-background-password option enables the user to -open a distinct background wallet that only has a view key saved -and can be opened/closed/synced separately from the main wallet. -When the main wallet opens, it processes the background wallet's -cache. -- To enable the feature in the RPC wallet, there is a new -`/setup_background_sync` endpoint. -- HW, multsig and view-only wallets cannot background sync. ---- - src/cryptonote_basic/account.cpp | 11 + - src/cryptonote_basic/account.h | 1 + - src/cryptonote_config.h | 2 + - src/simplewallet/simplewallet.cpp | 204 +++- - src/simplewallet/simplewallet.h | 1 + - src/wallet/api/wallet.cpp | 213 +++- - src/wallet/api/wallet.h | 12 + - src/wallet/api/wallet2_api.h | 42 + - src/wallet/wallet2.cpp | 1028 ++++++++++++++++-- - src/wallet/wallet2.h | 156 ++- - src/wallet/wallet_errors.h | 39 + - src/wallet/wallet_rpc_server.cpp | 162 +++ - src/wallet/wallet_rpc_server.h | 6 + - src/wallet/wallet_rpc_server_commands_defs.h | 64 ++ - src/wallet/wallet_rpc_server_error_codes.h | 2 + - tests/functional_tests/transfer.py | 400 ++++++- - tests/functional_tests/util_resources.py | 25 + - tests/functional_tests/wallet.py | 43 +- - tests/unit_tests/wipeable_string.cpp | 12 + - utils/python-rpc/framework/wallet.py | 42 + - 20 files changed, 2336 insertions(+), 129 deletions(-) - -diff --git a/src/cryptonote_basic/account.cpp b/src/cryptonote_basic/account.cpp -index 2ac455fda..4e87d4477 100644 ---- a/src/cryptonote_basic/account.cpp -+++ b/src/cryptonote_basic/account.cpp -@@ -152,6 +152,17 @@ DISABLE_VS_WARNINGS(4244 4345) - m_keys.m_multisig_keys.clear(); - } - //----------------------------------------------------------------- -+ void account_base::set_spend_key(const crypto::secret_key& spend_secret_key) -+ { -+ // make sure derived spend public key matches saved public spend key -+ crypto::public_key spend_public_key; -+ crypto::secret_key_to_public_key(spend_secret_key, spend_public_key); -+ CHECK_AND_ASSERT_THROW_MES(m_keys.m_account_address.m_spend_public_key == spend_public_key, -+ "Unexpected derived public spend key"); -+ -+ m_keys.m_spend_secret_key = spend_secret_key; -+ } -+ //----------------------------------------------------------------- - crypto::secret_key account_base::generate(const crypto::secret_key& recovery_key, bool recover, bool two_random) - { - crypto::secret_key first = generate_keys(m_keys.m_account_address.m_spend_public_key, m_keys.m_spend_secret_key, recovery_key, recover); -diff --git a/src/cryptonote_basic/account.h b/src/cryptonote_basic/account.h -index 2ee9545d4..93d1d28f0 100644 ---- a/src/cryptonote_basic/account.h -+++ b/src/cryptonote_basic/account.h -@@ -95,6 +95,7 @@ namespace cryptonote - bool store(const std::string& file_path); - - void forget_spend_key(); -+ void set_spend_key(const crypto::secret_key& spend_secret_key); - const std::vector &get_multisig_keys() const { return m_keys.m_multisig_keys; } - - void encrypt_keys(const crypto::chacha_key &key) { m_keys.encrypt(key); } -diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h -index 8c0d3ce20..8b5091a46 100644 ---- a/src/cryptonote_config.h -+++ b/src/cryptonote_config.h -@@ -253,6 +253,8 @@ namespace config - const unsigned char HASH_KEY_ENCRYPTED_PAYMENT_ID = 0x8d; - const unsigned char HASH_KEY_WALLET = 0x8c; - const unsigned char HASH_KEY_WALLET_CACHE = 0x8d; -+ const unsigned char HASH_KEY_BACKGROUND_CACHE = 0x8e; -+ const unsigned char HASH_KEY_BACKGROUND_KEYS_FILE = 0x8f; - const unsigned char HASH_KEY_RPC_PAYMENT_NONCE = 0x58; - const unsigned char HASH_KEY_MEMORY = 'k'; - const unsigned char HASH_KEY_MULTISIG[] = {'M', 'u', 'l', 't' , 'i', 's', 'i', 'g', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; -diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp -index e0a08eec0..83b56c3f4 100644 ---- a/src/simplewallet/simplewallet.cpp -+++ b/src/simplewallet/simplewallet.cpp -@@ -155,6 +155,17 @@ typedef cryptonote::simple_wallet sw; - } \ - } while(0) - -+#define CHECK_IF_BACKGROUND_SYNCING(msg) \ -+ do \ -+ { \ -+ if (m_wallet->is_background_wallet() || m_wallet->is_background_syncing()) \ -+ { \ -+ std::string type = m_wallet->is_background_wallet() ? "background wallet" : "background syncing wallet"; \ -+ fail_msg_writer() << boost::format(tr("%s %s")) % type % msg; \ -+ return false; \ -+ } \ -+ } while (0) -+ - static std::string get_human_readable_timespan(std::chrono::seconds seconds); - static std::string get_human_readable_timespan(uint64_t seconds); - -@@ -325,7 +336,7 @@ namespace - auto pwd_container = tools::password_container::prompt(verify, prompt); - if (!pwd_container) - { -- tools::fail_msg_writer() << sw::tr("failed to read wallet password"); -+ tools::fail_msg_writer() << sw::tr("failed to read password"); - } - return pwd_container; - } -@@ -335,6 +346,11 @@ namespace - return password_prompter(verify ? sw::tr("Enter a new password for the wallet") : sw::tr("Wallet password"), verify); - } - -+ boost::optional background_sync_cache_password_prompter(bool verify) -+ { -+ return password_prompter(verify ? sw::tr("Enter a custom password for the background sync cache") : sw::tr("Background sync cache password"), verify); -+ } -+ - inline std::string interpret_rpc_response(bool ok, const std::string& status) - { - std::string err; -@@ -452,6 +468,41 @@ namespace - return "invalid"; - } - -+ const struct -+ { -+ const char *name; -+ tools::wallet2::BackgroundSyncType background_sync_type; -+ } background_sync_type_names[] = -+ { -+ { "off", tools::wallet2::BackgroundSyncOff }, -+ { "reuse-wallet-password", tools::wallet2::BackgroundSyncReusePassword }, -+ { "custom-background-password", tools::wallet2::BackgroundSyncCustomPassword }, -+ }; -+ -+ bool parse_background_sync_type(const std::string &s, tools::wallet2::BackgroundSyncType &background_sync_type) -+ { -+ for (size_t n = 0; n < sizeof(background_sync_type_names) / sizeof(background_sync_type_names[0]); ++n) -+ { -+ if (s == background_sync_type_names[n].name) -+ { -+ background_sync_type = background_sync_type_names[n].background_sync_type; -+ return true; -+ } -+ } -+ fail_msg_writer() << cryptonote::simple_wallet::tr("failed to parse background sync type"); -+ return false; -+ } -+ -+ std::string get_background_sync_type_name(tools::wallet2::BackgroundSyncType type) -+ { -+ for (size_t n = 0; n < sizeof(background_sync_type_names) / sizeof(background_sync_type_names[0]); ++n) -+ { -+ if (type == background_sync_type_names[n].background_sync_type) -+ return background_sync_type_names[n].name; -+ } -+ return "invalid"; -+ } -+ - std::string get_version_string(uint32_t version) - { - return boost::lexical_cast(version >> 16) + "." + boost::lexical_cast(version & 0xffff); -@@ -805,6 +856,7 @@ bool simple_wallet::spendkey(const std::vector &args/* = std::vecto - fail_msg_writer() << tr("wallet is watch-only and has no spend key"); - return true; - } -+ CHECK_IF_BACKGROUND_SYNCING("has no spend key"); - // don't log - PAUSE_READLINE(); - if (m_wallet->key_on_device()) { -@@ -836,6 +888,7 @@ bool simple_wallet::print_seed(bool encrypted) - fail_msg_writer() << tr("wallet is watch-only and has no seed"); - return true; - } -+ CHECK_IF_BACKGROUND_SYNCING("has no seed"); - - multisig = m_wallet->multisig(&ready); - if (multisig) -@@ -913,6 +966,7 @@ bool simple_wallet::seed_set_language(const std::vector &args/* = s - fail_msg_writer() << tr("wallet is watch-only and has no seed"); - return true; - } -+ CHECK_IF_BACKGROUND_SYNCING("has no seed"); - - epee::wipeable_string password; - { -@@ -1059,6 +1113,7 @@ bool simple_wallet::prepare_multisig_main(const std::vector &args, - fail_msg_writer() << tr("wallet is watch-only and cannot be made multisig"); - return false; - } -+ CHECK_IF_BACKGROUND_SYNCING("cannot be made multisig"); - - if(m_wallet->get_num_transfer_details()) - { -@@ -2195,6 +2250,7 @@ bool simple_wallet::save_known_rings(const std::vector &args) - - bool simple_wallet::freeze_thaw(const std::vector &args, bool freeze) - { -+ CHECK_IF_BACKGROUND_SYNCING("cannot freeze/thaw"); - if (args.empty()) - { - fail_msg_writer() << boost::format(tr("usage: %s |")) % (freeze ? "freeze" : "thaw"); -@@ -2234,6 +2290,7 @@ bool simple_wallet::thaw(const std::vector &args) - - bool simple_wallet::frozen(const std::vector &args) - { -+ CHECK_IF_BACKGROUND_SYNCING("cannot see frozen key images"); - if (args.empty()) - { - size_t ntd = m_wallet->get_num_transfer_details(); -@@ -3005,6 +3062,57 @@ bool simple_wallet::set_track_uses(const std::vector &args/* = std: - return true; - } - -+bool simple_wallet::setup_background_sync(const std::vector &args/* = std::vector()*/) -+{ -+ if (m_wallet->multisig()) -+ { -+ fail_msg_writer() << tr("background sync not implemented for multisig wallet"); -+ return true; -+ } -+ if (m_wallet->watch_only()) -+ { -+ fail_msg_writer() << tr("background sync not implemented for watch only wallet"); -+ return true; -+ } -+ if (m_wallet->key_on_device()) -+ { -+ fail_msg_writer() << tr("command not supported by HW wallet"); -+ return true; -+ } -+ -+ tools::wallet2::BackgroundSyncType background_sync_type; -+ if (!parse_background_sync_type(args[1], background_sync_type)) -+ { -+ fail_msg_writer() << tr("invalid option"); -+ return true; -+ } -+ -+ const auto pwd_container = get_and_verify_password(); -+ if (!pwd_container) -+ return true; -+ -+ try -+ { -+ boost::optional background_cache_password = boost::none; -+ if (background_sync_type == tools::wallet2::BackgroundSyncCustomPassword) -+ { -+ const auto background_pwd_container = background_sync_cache_password_prompter(true); -+ if (!background_pwd_container) -+ return true; -+ background_cache_password = background_pwd_container->password(); -+ } -+ -+ LOCK_IDLE_SCOPE(); -+ m_wallet->setup_background_sync(background_sync_type, pwd_container->password(), background_cache_password); -+ } -+ catch (const std::exception &e) -+ { -+ fail_msg_writer() << tr("Error setting background sync type: ") << e.what(); -+ } -+ -+ return true; -+} -+ - bool simple_wallet::set_show_wallet_name_when_locked(const std::vector &args/* = std::vector()*/) - { - const auto pwd_container = get_and_verify_password(); -@@ -3237,6 +3345,7 @@ bool simple_wallet::apropos(const std::vector &args) - - bool simple_wallet::scan_tx(const std::vector &args) - { -+ CHECK_IF_BACKGROUND_SYNCING("cannot scan tx"); - if (args.empty()) - { - PRINT_USAGE(USAGE_SCAN_TX); -@@ -3459,6 +3568,8 @@ simple_wallet::simple_wallet() - " Ignore outputs of amount above this threshold when spending. Value 0 is translated to the maximum value (18 million) which disables this filter.\n " - "ignore-outputs-below \n " - " Ignore outputs of amount below this threshold when spending.\n " -+ "background-sync \n " -+ " Set this to enable scanning in the background with just the view key while the wallet is locked.\n " - "track-uses <1|0>\n " - " Whether to keep track of owned outputs uses.\n " - "setup-background-mining <1|0>\n " -@@ -3879,6 +3990,7 @@ bool simple_wallet::set_variable(const std::vector &args) - success_msg_writer() << "ignore-outputs-above = " << cryptonote::print_money(m_wallet->ignore_outputs_above()); - success_msg_writer() << "ignore-outputs-below = " << cryptonote::print_money(m_wallet->ignore_outputs_below()); - success_msg_writer() << "track-uses = " << m_wallet->track_uses(); -+ success_msg_writer() << "background-sync = " << get_background_sync_type_name(m_wallet->background_sync_type()); - success_msg_writer() << "setup-background-mining = " << setup_background_mining_string; - success_msg_writer() << "device-name = " << m_wallet->device_name(); - success_msg_writer() << "export-format = " << (m_wallet->export_format() == tools::wallet2::ExportFormat::Ascii ? "ascii" : "binary"); -@@ -3897,6 +4009,7 @@ bool simple_wallet::set_variable(const std::vector &args) - } - else - { -+ CHECK_IF_BACKGROUND_SYNCING("cannot change wallet settings"); - - #define CHECK_SIMPLE_VARIABLE(name, f, help) do \ - if (args[0] == name) { \ -@@ -3950,6 +4063,7 @@ bool simple_wallet::set_variable(const std::vector &args) - CHECK_SIMPLE_VARIABLE("ignore-outputs-above", set_ignore_outputs_above, tr("amount")); - CHECK_SIMPLE_VARIABLE("ignore-outputs-below", set_ignore_outputs_below, tr("amount")); - CHECK_SIMPLE_VARIABLE("track-uses", set_track_uses, tr("0 or 1")); -+ CHECK_SIMPLE_VARIABLE("background-sync", setup_background_sync, tr("off (default); reuse-wallet-password (reuse the wallet password to encrypt the background cache); custom-background-password (use a custom background password to encrypt the background cache)")); - CHECK_SIMPLE_VARIABLE("show-wallet-name-when-locked", set_show_wallet_name_when_locked, tr("1 or 0")); - CHECK_SIMPLE_VARIABLE("inactivity-lock-timeout", set_inactivity_lock_timeout, tr("unsigned integer (seconds, 0 to disable)")); - CHECK_SIMPLE_VARIABLE("setup-background-mining", set_setup_background_mining, tr("1/yes or 0/no")); -@@ -4904,7 +5018,10 @@ std::string simple_wallet::get_mnemonic_language() - //---------------------------------------------------------------------------------------------------- - boost::optional simple_wallet::get_and_verify_password() const - { -- auto pwd_container = default_password_prompter(m_wallet_file.empty()); -+ const bool verify = m_wallet_file.empty(); -+ auto pwd_container = (m_wallet->is_background_wallet() && m_wallet->background_sync_type() == tools::wallet2::BackgroundSyncCustomPassword) -+ ? background_sync_cache_password_prompter(verify) -+ : default_password_prompter(verify); - if (!pwd_container) - return boost::none; - -@@ -5208,6 +5325,8 @@ boost::optional simple_wallet::open_wallet(const boost::p - prefix = tr("Opened watch-only wallet"); - else if (m_wallet->multisig(&ready, &threshold, &total)) - prefix = (boost::format(tr("Opened %u/%u multisig wallet%s")) % threshold % total % (ready ? "" : " (not yet finalized)")).str(); -+ else if (m_wallet->is_background_wallet()) -+ prefix = tr("Opened background wallet"); - else - prefix = tr("Opened wallet"); - message_writer(console_color_white, true) << -@@ -5415,6 +5534,10 @@ void simple_wallet::stop_background_mining() - //---------------------------------------------------------------------------------------------------- - void simple_wallet::check_background_mining(const epee::wipeable_string &password) - { -+ // Background mining can be toggled from the main wallet -+ if (m_wallet->is_background_wallet() || m_wallet->is_background_syncing()) -+ return; -+ - tools::wallet2::BackgroundMiningSetupType setup = m_wallet->setup_background_mining(); - if (setup == tools::wallet2::BackgroundMiningNo) - { -@@ -6287,6 +6410,7 @@ bool simple_wallet::show_blockchain_height(const std::vector& args) - //---------------------------------------------------------------------------------------------------- - bool simple_wallet::rescan_spent(const std::vector &args) - { -+ CHECK_IF_BACKGROUND_SYNCING("cannot rescan spent"); - if (!m_wallet->is_trusted_daemon()) - { - fail_msg_writer() << tr("this command requires a trusted daemon. Enable with --trusted-daemon"); -@@ -6584,11 +6708,27 @@ void simple_wallet::check_for_inactivity_lock(bool user) - " B B " << std::endl << - "" << std::endl; - } -+ -+ bool started_background_sync = false; -+ if (!m_wallet->is_background_wallet() && -+ m_wallet->background_sync_type() != tools::wallet2::BackgroundSyncOff) -+ { -+ LOCK_IDLE_SCOPE(); -+ m_wallet->start_background_sync(); -+ started_background_sync = true; -+ } -+ - while (1) - { - const char *inactivity_msg = user ? "" : tr("Locked due to inactivity."); -- tools::msg_writer() << inactivity_msg << (inactivity_msg[0] ? " " : "") << tr("The wallet password is required to unlock the console."); -+ tools::msg_writer() << inactivity_msg << (inactivity_msg[0] ? " " : "") << ( -+ (m_wallet->is_background_wallet() && m_wallet->background_sync_type() == tools::wallet2::BackgroundSyncCustomPassword) -+ ? tr("The background password is required to unlock the console.") -+ : tr("The wallet password is required to unlock the console.") -+ ); - -+ if (m_wallet->is_background_syncing()) -+ tools::msg_writer() << tr("\nSyncing in the background while locked...") << std::endl; - const bool show_wallet_name = m_wallet->show_wallet_name_when_locked(); - if (show_wallet_name) - { -@@ -6600,8 +6740,16 @@ void simple_wallet::check_for_inactivity_lock(bool user) - } - try - { -- if (get_and_verify_password()) -+ const auto pwd_container = get_and_verify_password(); -+ if (pwd_container) -+ { -+ if (started_background_sync) -+ { -+ LOCK_IDLE_SCOPE(); -+ m_wallet->stop_background_sync(pwd_container->password()); -+ } - break; -+ } - } - catch (...) { /* do nothing, just let the loop loop */ } - } -@@ -6628,6 +6776,7 @@ bool simple_wallet::on_command(bool (simple_wallet::*cmd)(const std::vector &args_, bool called_by_mms) - { - // "transfer [index=[,,...]] [] []
[]" -+ CHECK_IF_BACKGROUND_SYNCING("cannot transfer"); - if (!try_connect_to_daemon()) - return false; - -@@ -7056,6 +7205,7 @@ bool simple_wallet::transfer_main(const std::vector &args_, bool ca - //---------------------------------------------------------------------------------------------------- - bool simple_wallet::transfer(const std::vector &args_) - { -+ CHECK_IF_BACKGROUND_SYNCING("cannot transfer"); - if (args_.size() < 1) - { - PRINT_USAGE(USAGE_TRANSFER); -@@ -7068,6 +7218,7 @@ bool simple_wallet::transfer(const std::vector &args_) - - bool simple_wallet::sweep_unmixable(const std::vector &args_) - { -+ CHECK_IF_BACKGROUND_SYNCING("cannot sweep"); - if (!try_connect_to_daemon()) - return true; - -@@ -7175,6 +7326,7 @@ bool simple_wallet::sweep_unmixable(const std::vector &args_) - //---------------------------------------------------------------------------------------------------- - bool simple_wallet::sweep_main(uint32_t account, uint64_t below, const std::vector &args_) - { -+ CHECK_IF_BACKGROUND_SYNCING("cannot sweep"); - auto print_usage = [this, account, below]() - { - if (below) -@@ -7456,6 +7608,7 @@ bool simple_wallet::sweep_main(uint32_t account, uint64_t below, const std::vect - //---------------------------------------------------------------------------------------------------- - bool simple_wallet::sweep_single(const std::vector &args_) - { -+ CHECK_IF_BACKGROUND_SYNCING("cannot sweep"); - if (!try_connect_to_daemon()) - return true; - -@@ -7694,12 +7847,14 @@ bool simple_wallet::sweep_single(const std::vector &args_) - //---------------------------------------------------------------------------------------------------- - bool simple_wallet::sweep_all(const std::vector &args_) - { -+ CHECK_IF_BACKGROUND_SYNCING("cannot sweep"); - sweep_main(m_current_subaddress_account, 0, args_); - return true; - } - //---------------------------------------------------------------------------------------------------- - bool simple_wallet::sweep_account(const std::vector &args_) - { -+ CHECK_IF_BACKGROUND_SYNCING("cannot sweep"); - auto local_args = args_; - if (local_args.empty()) - { -@@ -7720,6 +7875,7 @@ bool simple_wallet::sweep_account(const std::vector &args_) - //---------------------------------------------------------------------------------------------------- - bool simple_wallet::sweep_below(const std::vector &args_) - { -+ CHECK_IF_BACKGROUND_SYNCING("cannot sweep"); - uint64_t below = 0; - if (args_.size() < 1) - { -@@ -7738,6 +7894,7 @@ bool simple_wallet::sweep_below(const std::vector &args_) - //---------------------------------------------------------------------------------------------------- - bool simple_wallet::donate(const std::vector &args_) - { -+ CHECK_IF_BACKGROUND_SYNCING("cannot donate"); - std::vector local_args = args_; - if(local_args.empty() || local_args.size() > 5) - { -@@ -7799,6 +7956,7 @@ bool simple_wallet::donate(const std::vector &args_) - //---------------------------------------------------------------------------------------------------- - bool simple_wallet::accept_loaded_tx(const std::function get_num_txes, const std::function &get_tx, const std::string &extra_message) - { -+ CHECK_IF_BACKGROUND_SYNCING("cannot load tx"); - // gather info to ask the user - uint64_t amount = 0, amount_to_dests = 0, change = 0; - size_t min_ring_size = ~0; -@@ -7980,6 +8138,8 @@ bool simple_wallet::sign_transfer(const std::vector &args_) - return true; - } - -+ CHECK_IF_BACKGROUND_SYNCING("cannot sign transfer"); -+ - bool export_raw = false; - std::string unsigned_filename = "unsigned_wownero_tx"; - if (args_.size() > 2 || (args_.size() == 2 && args_[0] != "export_raw")) -@@ -8086,6 +8246,8 @@ std::string get_tx_key_stream(crypto::secret_key tx_key, std::vector &args_) - { -+ CHECK_IF_BACKGROUND_SYNCING("cannot get tx key"); -+ - std::vector local_args = args_; - - if (m_wallet->key_on_device() && m_wallet->get_account().get_device().get_type() != hw::device::TREZOR) -@@ -8126,6 +8288,8 @@ bool simple_wallet::get_tx_key(const std::vector &args_) - //---------------------------------------------------------------------------------------------------- - bool simple_wallet::set_tx_key(const std::vector &args_) - { -+ CHECK_IF_BACKGROUND_SYNCING("cannot set tx key"); -+ - std::vector local_args = args_; - - if(local_args.size() != 2 && local_args.size() != 3) { -@@ -8202,6 +8366,8 @@ bool simple_wallet::set_tx_key(const std::vector &args_) - //---------------------------------------------------------------------------------------------------- - bool simple_wallet::get_tx_proof(const std::vector &args) - { -+ CHECK_IF_BACKGROUND_SYNCING("cannot get tx proof"); -+ - if (args.size() != 2 && args.size() != 3) - { - PRINT_USAGE(USAGE_GET_TX_PROOF); -@@ -8408,6 +8574,7 @@ bool simple_wallet::check_tx_proof(const std::vector &args) - //---------------------------------------------------------------------------------------------------- - bool simple_wallet::get_spend_proof(const std::vector &args) - { -+ CHECK_IF_BACKGROUND_SYNCING("cannot get spend proof"); - if (m_wallet->key_on_device()) - { - fail_msg_writer() << tr("command not supported by HW wallet"); -@@ -8492,6 +8659,7 @@ bool simple_wallet::check_spend_proof(const std::vector &args) - //---------------------------------------------------------------------------------------------------- - bool simple_wallet::get_reserve_proof(const std::vector &args) - { -+ CHECK_IF_BACKGROUND_SYNCING("cannot get reserve proof"); - if (m_wallet->key_on_device()) - { - fail_msg_writer() << tr("command not supported by HW wallet"); -@@ -9192,6 +9360,8 @@ bool simple_wallet::unspent_outputs(const std::vector &args_) - //---------------------------------------------------------------------------------------------------- - bool simple_wallet::rescan_blockchain(const std::vector &args_) - { -+ CHECK_IF_BACKGROUND_SYNCING("cannot rescan"); -+ - uint64_t start_height = 0; - ResetType reset_type = ResetSoft; - -@@ -9489,6 +9659,7 @@ bool simple_wallet::account(const std::vector &args/* = std::vector - if (command == "new") - { - // create a new account and switch to it -+ CHECK_IF_BACKGROUND_SYNCING("cannot create new account"); - std::string label = boost::join(local_args, " "); - if (label.empty()) - label = tr("(Untitled account)"); -@@ -9519,6 +9690,7 @@ bool simple_wallet::account(const std::vector &args/* = std::vector - else if (command == "label" && local_args.size() >= 1) - { - // set label of the specified account -+ CHECK_IF_BACKGROUND_SYNCING("cannot modify account"); - uint32_t index_major; - if (!epee::string_tools::get_xtype_from_string(index_major, local_args[0])) - { -@@ -9540,6 +9712,7 @@ bool simple_wallet::account(const std::vector &args/* = std::vector - } - else if (command == "tag" && local_args.size() >= 2) - { -+ CHECK_IF_BACKGROUND_SYNCING("cannot modify account"); - const std::string tag = local_args[0]; - std::set account_indices; - for (size_t i = 1; i < local_args.size(); ++i) -@@ -9564,6 +9737,7 @@ bool simple_wallet::account(const std::vector &args/* = std::vector - } - else if (command == "untag" && local_args.size() >= 1) - { -+ CHECK_IF_BACKGROUND_SYNCING("cannot modify account"); - std::set account_indices; - for (size_t i = 0; i < local_args.size(); ++i) - { -@@ -9587,6 +9761,7 @@ bool simple_wallet::account(const std::vector &args/* = std::vector - } - else if (command == "tag_description" && local_args.size() >= 1) - { -+ CHECK_IF_BACKGROUND_SYNCING("cannot modify account"); - const std::string tag = local_args[0]; - std::string description; - if (local_args.size() > 1) -@@ -9704,6 +9879,7 @@ bool simple_wallet::print_address(const std::vector &args/* = std:: - } - else if (local_args[0] == "new") - { -+ CHECK_IF_BACKGROUND_SYNCING("cannot add address"); - local_args.erase(local_args.begin()); - std::string label; - if (local_args.size() > 0) -@@ -9716,6 +9892,7 @@ bool simple_wallet::print_address(const std::vector &args/* = std:: - } - else if (local_args[0] == "mnew") - { -+ CHECK_IF_BACKGROUND_SYNCING("cannot add addresses"); - local_args.erase(local_args.begin()); - if (local_args.size() != 1) - { -@@ -9741,6 +9918,7 @@ bool simple_wallet::print_address(const std::vector &args/* = std:: - } - else if (local_args[0] == "one-off") - { -+ CHECK_IF_BACKGROUND_SYNCING("cannot add address"); - local_args.erase(local_args.begin()); - std::string label; - if (local_args.size() != 2) -@@ -9759,6 +9937,7 @@ bool simple_wallet::print_address(const std::vector &args/* = std:: - } - else if (local_args.size() >= 2 && local_args[0] == "label") - { -+ CHECK_IF_BACKGROUND_SYNCING("cannot modify address"); - if (!epee::string_tools::get_xtype_from_string(index, local_args[1])) - { - fail_msg_writer() << tr("failed to parse index: ") << local_args[1]; -@@ -9905,6 +10084,8 @@ bool simple_wallet::print_integrated_address(const std::vector &arg - //---------------------------------------------------------------------------------------------------- - bool simple_wallet::address_book(const std::vector &args/* = std::vector()*/) - { -+ CHECK_IF_BACKGROUND_SYNCING("cannot get address book"); -+ - if (args.size() == 0) - { - } -@@ -9965,6 +10146,8 @@ bool simple_wallet::address_book(const std::vector &args/* = std::v - //---------------------------------------------------------------------------------------------------- - bool simple_wallet::set_tx_note(const std::vector &args) - { -+ CHECK_IF_BACKGROUND_SYNCING("cannot set tx note"); -+ - if (args.size() == 0) - { - PRINT_USAGE(USAGE_SET_TX_NOTE); -@@ -9993,6 +10176,8 @@ bool simple_wallet::set_tx_note(const std::vector &args) - //---------------------------------------------------------------------------------------------------- - bool simple_wallet::get_tx_note(const std::vector &args) - { -+ CHECK_IF_BACKGROUND_SYNCING("cannot get tx note"); -+ - if (args.size() != 1) - { - PRINT_USAGE(USAGE_GET_TX_NOTE); -@@ -10018,6 +10203,8 @@ bool simple_wallet::get_tx_note(const std::vector &args) - //---------------------------------------------------------------------------------------------------- - bool simple_wallet::set_description(const std::vector &args) - { -+ CHECK_IF_BACKGROUND_SYNCING("cannot set description"); -+ - // 0 arguments allowed, for setting the description to empty string - - std::string description = ""; -@@ -10034,6 +10221,8 @@ bool simple_wallet::set_description(const std::vector &args) - //---------------------------------------------------------------------------------------------------- - bool simple_wallet::get_description(const std::vector &args) - { -+ CHECK_IF_BACKGROUND_SYNCING("cannot get description"); -+ - if (args.size() != 0) - { - PRINT_USAGE(USAGE_GET_DESCRIPTION); -@@ -10092,6 +10281,8 @@ bool simple_wallet::wallet_info(const std::vector &args) - type = tr("Watch only"); - else if (m_wallet->multisig(&ready, &threshold, &total)) - type = (boost::format(tr("%u/%u multisig%s")) % threshold % total % (ready ? "" : " (not yet finalized)")).str(); -+ else if (m_wallet->is_background_wallet()) -+ type = tr("Background wallet"); - else - type = tr("Normal"); - message_writer() << tr("Type: ") << type; -@@ -10103,6 +10294,7 @@ bool simple_wallet::wallet_info(const std::vector &args) - //---------------------------------------------------------------------------------------------------- - bool simple_wallet::sign(const std::vector &args) - { -+ CHECK_IF_BACKGROUND_SYNCING("cannot sign"); - if (m_wallet->key_on_device()) - { - fail_msg_writer() << tr("command not supported by HW wallet"); -@@ -10210,6 +10402,7 @@ bool simple_wallet::export_key_images(const std::vector &args_) - fail_msg_writer() << tr("command not supported by HW wallet"); - return true; - } -+ CHECK_IF_BACKGROUND_SYNCING("cannot export key images"); - auto args = args_; - - if (m_wallet->watch_only()) -@@ -10263,6 +10456,7 @@ bool simple_wallet::import_key_images(const std::vector &args) - fail_msg_writer() << tr("command not supported by HW wallet"); - return true; - } -+ CHECK_IF_BACKGROUND_SYNCING("cannot import key images"); - if (!m_wallet->is_trusted_daemon()) - { - fail_msg_writer() << tr("this command requires a trusted daemon. Enable with --trusted-daemon"); -@@ -10371,6 +10565,7 @@ bool simple_wallet::export_outputs(const std::vector &args_) - fail_msg_writer() << tr("command not supported by HW wallet"); - return true; - } -+ CHECK_IF_BACKGROUND_SYNCING("cannot export outputs"); - auto args = args_; - - bool all = false; -@@ -10420,6 +10615,7 @@ bool simple_wallet::import_outputs(const std::vector &args) - fail_msg_writer() << tr("command not supported by HW wallet"); - return true; - } -+ CHECK_IF_BACKGROUND_SYNCING("cannot import outputs"); - if (args.size() != 1) - { - PRINT_USAGE(USAGE_IMPORT_OUTPUTS); -diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h -index d641dca1b..b98a40713 100644 ---- a/src/simplewallet/simplewallet.h -+++ b/src/simplewallet/simplewallet.h -@@ -147,6 +147,7 @@ namespace cryptonote - bool set_ignore_outputs_above(const std::vector &args = std::vector()); - bool set_ignore_outputs_below(const std::vector &args = std::vector()); - bool set_track_uses(const std::vector &args = std::vector()); -+ bool setup_background_sync(const std::vector &args = std::vector()); - bool set_show_wallet_name_when_locked(const std::vector &args = std::vector()); - bool set_inactivity_lock_timeout(const std::vector &args = std::vector()); - bool set_setup_background_mining(const std::vector &args = std::vector()); -diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp -index e81265ad3..e868fa039 100644 ---- a/src/wallet/api/wallet.cpp -+++ b/src/wallet/api/wallet.cpp -@@ -54,6 +54,40 @@ using namespace cryptonote; - #undef MONERO_DEFAULT_LOG_CATEGORY - #define MONERO_DEFAULT_LOG_CATEGORY "WalletAPI" - -+#define LOCK_REFRESH() \ -+ bool refresh_enabled = m_refreshEnabled; \ -+ m_refreshEnabled = false; \ -+ m_wallet->stop(); \ -+ m_refreshCV.notify_one(); \ -+ boost::mutex::scoped_lock lock(m_refreshMutex); \ -+ boost::mutex::scoped_lock lock2(m_refreshMutex2); \ -+ epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler([&](){ \ -+ /* m_refreshMutex's still locked here */ \ -+ if (refresh_enabled) \ -+ startRefresh(); \ -+ }) -+ -+#define PRE_VALIDATE_BACKGROUND_SYNC() \ -+ do \ -+ { \ -+ clearStatus(); \ -+ if (m_wallet->key_on_device()) \ -+ { \ -+ setStatusError(tr("HW wallet cannot use background sync")); \ -+ return false; \ -+ } \ -+ if (m_wallet->watch_only()) \ -+ { \ -+ setStatusError(tr("View only wallet cannot use background sync")); \ -+ return false; \ -+ } \ -+ if (m_wallet->multisig()) \ -+ { \ -+ setStatusError(tr("Multisig wallet cannot use background sync")); \ -+ return false; \ -+ } \ -+ } while (0) -+ - namespace Monero { - - namespace { -@@ -792,6 +826,8 @@ bool WalletImpl::close(bool store) - - std::string WalletImpl::seed(const std::string& seed_offset) const - { -+ if (checkBackgroundSync("cannot get seed")) -+ return std::string(); - epee::wipeable_string seed; - if (m_wallet) - m_wallet->get_seed(seed, seed_offset); -@@ -805,6 +841,8 @@ std::string WalletImpl::getSeedLanguage() const - - void WalletImpl::setSeedLanguage(const std::string &arg) - { -+ if (checkBackgroundSync("cannot set seed language")) -+ return; - m_wallet->set_seed_language(arg); - } - -@@ -828,6 +866,8 @@ void WalletImpl::statusWithErrorString(int& status, std::string& errorString) co - - bool WalletImpl::setPassword(const std::string &password) - { -+ if (checkBackgroundSync("cannot change password")) -+ return false; - clearStatus(); - try { - m_wallet->change_password(m_wallet->get_wallet_file(), m_password, password); -@@ -988,6 +1028,8 @@ bool WalletImpl::lightWalletImportWalletRequest(std::string &payment_id, uint64_ - - void WalletImpl::setRefreshFromBlockHeight(uint64_t refresh_from_block_height) - { -+ if (checkBackgroundSync("cannot change refresh height")) -+ return; - m_wallet->set_refresh_from_block_height(refresh_from_block_height); - } - -@@ -1105,6 +1147,8 @@ void WalletImpl::refreshAsync() - - bool WalletImpl::rescanBlockchain() - { -+ if (checkBackgroundSync("cannot rescan blockchain")) -+ return false; - clearStatus(); - m_refreshShouldRescan = true; - doRefresh(); -@@ -1113,6 +1157,8 @@ bool WalletImpl::rescanBlockchain() - - void WalletImpl::rescanBlockchainAsync() - { -+ if (checkBackgroundSync("cannot rescan blockchain")) -+ return; - m_refreshShouldRescan = true; - refreshAsync(); - } -@@ -1136,7 +1182,7 @@ int WalletImpl::autoRefreshInterval() const - UnsignedTransaction *WalletImpl::loadUnsignedTx(const std::string &unsigned_filename) { - clearStatus(); - UnsignedTransactionImpl * transaction = new UnsignedTransactionImpl(*this); -- if (!m_wallet->load_unsigned_tx(unsigned_filename, transaction->m_unsigned_tx_set)){ -+ if (checkBackgroundSync("cannot load tx") || !m_wallet->load_unsigned_tx(unsigned_filename, transaction->m_unsigned_tx_set)){ - setStatusError(tr("Failed to load unsigned transactions")); - transaction->m_status = UnsignedTransaction::Status::Status_Error; - transaction->m_errorString = errorString(); -@@ -1156,6 +1202,8 @@ UnsignedTransaction *WalletImpl::loadUnsignedTx(const std::string &unsigned_file - - bool WalletImpl::submitTransaction(const string &fileName) { - clearStatus(); -+ if (checkBackgroundSync("cannot submit tx")) -+ return false; - std::unique_ptr transaction(new PendingTransactionImpl(*this)); - - bool r = m_wallet->load_tx(fileName, transaction->m_pending_tx); -@@ -1179,6 +1227,8 @@ bool WalletImpl::exportKeyImages(const string &filename, bool all) - setStatusError(tr("Wallet is view only")); - return false; - } -+ if (checkBackgroundSync("cannot export key images")) -+ return false; - - try - { -@@ -1199,6 +1249,8 @@ bool WalletImpl::exportKeyImages(const string &filename, bool all) - - bool WalletImpl::importKeyImages(const string &filename) - { -+ if (checkBackgroundSync("cannot import key images")) -+ return false; - if (!trustedDaemon()) { - setStatusError(tr("Key images can only be imported with a trusted daemon")); - return false; -@@ -1222,6 +1274,8 @@ bool WalletImpl::importKeyImages(const string &filename) - - bool WalletImpl::exportOutputs(const string &filename, bool all) - { -+ if (checkBackgroundSync("cannot export outputs")) -+ return false; - if (m_wallet->key_on_device()) - { - setStatusError(string(tr("Not supported on HW wallets.")) + filename); -@@ -1252,6 +1306,8 @@ bool WalletImpl::exportOutputs(const string &filename, bool all) - - bool WalletImpl::importOutputs(const string &filename) - { -+ if (checkBackgroundSync("cannot import outputs")) -+ return false; - if (m_wallet->key_on_device()) - { - setStatusError(string(tr("Not supported on HW wallets.")) + filename); -@@ -1284,6 +1340,8 @@ bool WalletImpl::importOutputs(const string &filename) - - bool WalletImpl::scanTransactions(const std::vector &txids) - { -+ if (checkBackgroundSync("cannot scan transactions")) -+ return false; - if (txids.empty()) - { - setStatusError(string(tr("Failed to scan transactions: no transaction ids provided."))); -@@ -1322,8 +1380,86 @@ bool WalletImpl::scanTransactions(const std::vector &txids) - return true; - } - -+bool WalletImpl::setupBackgroundSync(const Wallet::BackgroundSyncType background_sync_type, const std::string &wallet_password, const optional &background_cache_password) -+{ -+ try -+ { -+ PRE_VALIDATE_BACKGROUND_SYNC(); -+ -+ tools::wallet2::BackgroundSyncType bgs_type; -+ switch (background_sync_type) -+ { -+ case Wallet::BackgroundSync_Off: bgs_type = tools::wallet2::BackgroundSyncOff; break; -+ case Wallet::BackgroundSync_ReusePassword: bgs_type = tools::wallet2::BackgroundSyncReusePassword; break; -+ case Wallet::BackgroundSync_CustomPassword: bgs_type = tools::wallet2::BackgroundSyncCustomPassword; break; -+ default: setStatusError(tr("Unknown background sync type")); return false; -+ } -+ -+ boost::optional bgc_password = background_cache_password -+ ? boost::optional(*background_cache_password) -+ : boost::none; -+ -+ LOCK_REFRESH(); -+ m_wallet->setup_background_sync(bgs_type, wallet_password, bgc_password); -+ } -+ catch (const std::exception &e) -+ { -+ LOG_ERROR("Failed to setup background sync: " << e.what()); -+ setStatusError(string(tr("Failed to setup background sync: ")) + e.what()); -+ return false; -+ } -+ return true; -+} -+ -+Wallet::BackgroundSyncType WalletImpl::getBackgroundSyncType() const -+{ -+ switch (m_wallet->background_sync_type()) -+ { -+ case tools::wallet2::BackgroundSyncOff: return Wallet::BackgroundSync_Off; -+ case tools::wallet2::BackgroundSyncReusePassword: return Wallet::BackgroundSync_ReusePassword; -+ case tools::wallet2::BackgroundSyncCustomPassword: return Wallet::BackgroundSync_CustomPassword; -+ default: setStatusError(tr("Unknown background sync type")); return Wallet::BackgroundSync_Off; -+ } -+} -+ -+bool WalletImpl::startBackgroundSync() -+{ -+ try -+ { -+ PRE_VALIDATE_BACKGROUND_SYNC(); -+ LOCK_REFRESH(); -+ m_wallet->start_background_sync(); -+ } -+ catch (const std::exception &e) -+ { -+ LOG_ERROR("Failed to start background sync: " << e.what()); -+ setStatusError(string(tr("Failed to start background sync: ")) + e.what()); -+ return false; -+ } -+ return true; -+} -+ -+bool WalletImpl::stopBackgroundSync(const std::string &wallet_password) -+{ -+ try -+ { -+ PRE_VALIDATE_BACKGROUND_SYNC(); -+ LOCK_REFRESH(); -+ m_wallet->stop_background_sync(epee::wipeable_string(wallet_password)); -+ } -+ catch (const std::exception &e) -+ { -+ LOG_ERROR("Failed to stop background sync: " << e.what()); -+ setStatusError(string(tr("Failed to stop background sync: ")) + e.what()); -+ return false; -+ } -+ return true; -+} -+ - void WalletImpl::addSubaddressAccount(const std::string& label) - { -+ if (checkBackgroundSync("cannot add account")) -+ return; - m_wallet->add_subaddress_account(label); - } - size_t WalletImpl::numSubaddressAccounts() const -@@ -1336,10 +1472,14 @@ size_t WalletImpl::numSubaddresses(uint32_t accountIndex) const - } - void WalletImpl::addSubaddress(uint32_t accountIndex, const std::string& label) - { -+ if (checkBackgroundSync("cannot add subbaddress")) -+ return; - m_wallet->add_subaddress(accountIndex, label); - } - std::string WalletImpl::getSubaddressLabel(uint32_t accountIndex, uint32_t addressIndex) const - { -+ if (checkBackgroundSync("cannot get subbaddress label")) -+ return ""; - try - { - return m_wallet->get_subaddress_label({accountIndex, addressIndex}); -@@ -1353,6 +1493,8 @@ std::string WalletImpl::getSubaddressLabel(uint32_t accountIndex, uint32_t addre - } - void WalletImpl::setSubaddressLabel(uint32_t accountIndex, uint32_t addressIndex, const std::string &label) - { -+ if (checkBackgroundSync("cannot set subbaddress label")) -+ return; - try - { - return m_wallet->set_subaddress_label({accountIndex, addressIndex}, label); -@@ -1366,12 +1508,16 @@ void WalletImpl::setSubaddressLabel(uint32_t accountIndex, uint32_t addressIndex - - MultisigState WalletImpl::multisig() const { - MultisigState state; -+ if (checkBackgroundSync("cannot use multisig")) -+ return state; - state.isMultisig = m_wallet->multisig(&state.isReady, &state.threshold, &state.total); - - return state; - } - - string WalletImpl::getMultisigInfo() const { -+ if (checkBackgroundSync("cannot use multisig")) -+ return string(); - try { - clearStatus(); - return m_wallet->get_multisig_first_kex_msg(); -@@ -1384,6 +1530,8 @@ string WalletImpl::getMultisigInfo() const { - } - - string WalletImpl::makeMultisig(const vector& info, const uint32_t threshold) { -+ if (checkBackgroundSync("cannot make multisig")) -+ return string(); - try { - clearStatus(); - -@@ -1524,6 +1672,9 @@ PendingTransaction *WalletImpl::createTransactionMultDest(const std::vector extra; - std::string extra_nonce; - vector dsts; -@@ -1690,6 +1841,9 @@ PendingTransaction *WalletImpl::createSweepUnmixableTransaction() - PendingTransactionImpl * transaction = new PendingTransactionImpl(*this); - - do { -+ if (checkBackgroundSync("cannot sweep")) -+ break; -+ - try { - transaction->m_pending_tx = m_wallet->create_unmixable_sweep_transactions(); - pendingTxPostProcess(transaction); -@@ -1823,11 +1977,15 @@ uint32_t WalletImpl::defaultMixin() const - - void WalletImpl::setDefaultMixin(uint32_t arg) - { -+ if (checkBackgroundSync("cannot set default mixin")) -+ return; - m_wallet->default_mixin(arg); - } - - bool WalletImpl::setCacheAttribute(const std::string &key, const std::string &val) - { -+ if (checkBackgroundSync("cannot set cache attribute")) -+ return false; - m_wallet->set_attribute(key, val); - return true; - } -@@ -1841,6 +1999,8 @@ std::string WalletImpl::getCacheAttribute(const std::string &key) const - - bool WalletImpl::setUserNote(const std::string &txid, const std::string ¬e) - { -+ if (checkBackgroundSync("cannot set user note")) -+ return false; - cryptonote::blobdata txid_data; - if(!epee::string_tools::parse_hexstr_to_binbuff(txid, txid_data) || txid_data.size() != sizeof(crypto::hash)) - return false; -@@ -1852,6 +2012,8 @@ bool WalletImpl::setUserNote(const std::string &txid, const std::string ¬e) - - std::string WalletImpl::getUserNote(const std::string &txid) const - { -+ if (checkBackgroundSync("cannot get user note")) -+ return ""; - cryptonote::blobdata txid_data; - if(!epee::string_tools::parse_hexstr_to_binbuff(txid, txid_data) || txid_data.size() != sizeof(crypto::hash)) - return ""; -@@ -1862,6 +2024,9 @@ std::string WalletImpl::getUserNote(const std::string &txid) const - - std::string WalletImpl::getTxKey(const std::string &txid_str) const - { -+ if (checkBackgroundSync("cannot get tx key")) -+ return ""; -+ - crypto::hash txid; - if(!epee::string_tools::hex_to_pod(txid_str, txid)) - { -@@ -1946,6 +2111,9 @@ bool WalletImpl::checkTxKey(const std::string &txid_str, std::string tx_key_str, - - std::string WalletImpl::getTxProof(const std::string &txid_str, const std::string &address_str, const std::string &message) const - { -+ if (checkBackgroundSync("cannot get tx proof")) -+ return ""; -+ - crypto::hash txid; - if (!epee::string_tools::hex_to_pod(txid_str, txid)) - { -@@ -2002,6 +2170,9 @@ bool WalletImpl::checkTxProof(const std::string &txid_str, const std::string &ad - } - - std::string WalletImpl::getSpendProof(const std::string &txid_str, const std::string &message) const { -+ if (checkBackgroundSync("cannot get spend proof")) -+ return ""; -+ - crypto::hash txid; - if(!epee::string_tools::hex_to_pod(txid_str, txid)) - { -@@ -2044,6 +2215,9 @@ bool WalletImpl::checkSpendProof(const std::string &txid_str, const std::string - } - - std::string WalletImpl::getReserveProof(bool all, uint32_t account_index, uint64_t amount, const std::string &message) const { -+ if (checkBackgroundSync("cannot get reserve proof")) -+ return ""; -+ - try - { - clearStatus(); -@@ -2090,6 +2264,9 @@ bool WalletImpl::checkReserveProof(const std::string &address, const std::string - - std::string WalletImpl::signMessage(const std::string &message, const std::string &address) - { -+ if (checkBackgroundSync("cannot sign message")) -+ return ""; -+ - if (address.empty()) { - return m_wallet->sign(message, tools::wallet2::sign_with_spend_key); - } -@@ -2217,6 +2394,16 @@ bool WalletImpl::isDeterministic() const - return m_wallet->is_deterministic(); - } - -+bool WalletImpl::isBackgroundSyncing() const -+{ -+ return m_wallet->is_background_syncing(); -+} -+ -+bool WalletImpl::isBackgroundWallet() const -+{ -+ return m_wallet->is_background_wallet(); -+} -+ - void WalletImpl::clearStatus() const - { - boost::lock_guard l(m_statusMutex); -@@ -2285,9 +2472,7 @@ void WalletImpl::doRefresh() - if(rescan) - m_wallet->rescan_blockchain(false); - m_wallet->refresh(trustedDaemon()); -- if (!m_synchronized) { -- m_synchronized = true; -- } -+ m_synchronized = m_wallet->is_synced(); - // assuming if we have empty history, it wasn't initialized yet - // for further history changes client need to update history in - // "on_money_received" and "on_money_sent" callbacks -@@ -2391,6 +2576,24 @@ bool WalletImpl::doInit(const string &daemon_address, const std::string &proxy_a - return true; - } - -+bool WalletImpl::checkBackgroundSync(const std::string &message) const -+{ -+ clearStatus(); -+ if (m_wallet->is_background_wallet()) -+ { -+ LOG_ERROR("Background wallets " + message); -+ setStatusError(tr("Background wallets ") + message); -+ return true; -+ } -+ if (m_wallet->is_background_syncing()) -+ { -+ LOG_ERROR(message + " while background syncing"); -+ setStatusError(message + tr(" while background syncing. Stop background syncing first.")); -+ return true; -+ } -+ return false; -+} -+ - bool WalletImpl::parse_uri(const std::string &uri, std::string &address, std::string &payment_id, uint64_t &amount, std::string &tx_description, std::string &recipient_name, std::vector &unknown_parameters, std::string &error) - { - return m_wallet->parse_uri(uri, address, payment_id, amount, tx_description, recipient_name, unknown_parameters, error); -@@ -2409,6 +2612,8 @@ std::string WalletImpl::getDefaultDataDir() const - bool WalletImpl::rescanSpent() - { - clearStatus(); -+ if (checkBackgroundSync("cannot rescan spent")) -+ return false; - if (!trustedDaemon()) { - setStatusError(tr("Rescan spent can only be used with a trusted daemon")); - return false; -diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h -index ec2d7e9b3..1f199a72c 100644 ---- a/src/wallet/api/wallet.h -+++ b/src/wallet/api/wallet.h -@@ -171,6 +171,13 @@ public: - bool importOutputs(const std::string &filename) override; - bool scanTransactions(const std::vector &txids) override; - -+ bool setupBackgroundSync(const BackgroundSyncType background_sync_type, const std::string &wallet_password, const optional &background_cache_password = optional()) override; -+ BackgroundSyncType getBackgroundSyncType() const override; -+ bool startBackgroundSync() override; -+ bool stopBackgroundSync(const std::string &wallet_password) override; -+ bool isBackgroundSyncing() const override; -+ bool isBackgroundWallet() const override; -+ - virtual void disposeTransaction(PendingTransaction * t) override; - virtual uint64_t estimateTransactionFee(const std::vector> &destinations, - PendingTransaction::Priority priority) const override; -@@ -239,6 +246,7 @@ private: - bool isNewWallet() const; - void pendingTxPostProcess(PendingTransactionImpl * pending); - bool doInit(const std::string &daemon_address, const std::string &proxy_address, uint64_t upper_transaction_size_limit = 0, bool ssl = false); -+ bool checkBackgroundSync(const std::string &message) const; - - private: - friend class PendingTransactionImpl; -@@ -253,6 +261,10 @@ private: - mutable boost::mutex m_statusMutex; - mutable int m_status; - mutable std::string m_errorString; -+ // TODO: harden password handling in the wallet API, see relevant discussion -+ // https://github.com/monero-project/monero-gui/issues/1537 -+ // https://github.com/feather-wallet/feather/issues/72#issuecomment-1405602142 -+ // https://github.com/monero-project/monero/pull/8619#issuecomment-1632951461 - std::string m_password; - std::unique_ptr m_history; - std::unique_ptr m_wallet2Callback; -diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h -index 71991df0d..e349df176 100644 ---- a/src/wallet/api/wallet2_api.h -+++ b/src/wallet/api/wallet2_api.h -@@ -445,6 +445,12 @@ struct Wallet - ConnectionStatus_WrongVersion - }; - -+ enum BackgroundSyncType { -+ BackgroundSync_Off = 0, -+ BackgroundSync_ReusePassword = 1, -+ BackgroundSync_CustomPassword = 2 -+ }; -+ - virtual ~Wallet() = 0; - virtual std::string seed(const std::string& seed_offset = "") const = 0; - virtual std::string getSeedLanguage() const = 0; -@@ -936,6 +942,42 @@ struct Wallet - */ - virtual bool scanTransactions(const std::vector &txids) = 0; - -+ /*! -+ * \brief setupBackgroundSync - setup background sync mode with just a view key -+ * \param background_sync_type - the mode the wallet background syncs in -+ * \param wallet_password -+ * \param background_cache_password - custom password to encrypt background cache, only needed for custom password background sync type -+ * \return - true on success -+ */ -+ virtual bool setupBackgroundSync(const BackgroundSyncType background_sync_type, const std::string &wallet_password, const optional &background_cache_password) = 0; -+ -+ /*! -+ * \brief getBackgroundSyncType - get mode the wallet background syncs in -+ * \return - the type, or off if type is unknown -+ */ -+ virtual BackgroundSyncType getBackgroundSyncType() const = 0; -+ -+ /** -+ * @brief startBackgroundSync - sync the chain in the background with just view key -+ */ -+ virtual bool startBackgroundSync() = 0; -+ -+ /** -+ * @brief stopBackgroundSync - bring back spend key and process background synced txs -+ * \param wallet_password -+ */ -+ virtual bool stopBackgroundSync(const std::string &wallet_password) = 0; -+ -+ /** -+ * @brief isBackgroundSyncing - returns true if the wallet is background syncing -+ */ -+ virtual bool isBackgroundSyncing() const = 0; -+ -+ /** -+ * @brief isBackgroundWallet - returns true if the wallet is a background wallet -+ */ -+ virtual bool isBackgroundWallet() const = 0; -+ - virtual TransactionHistory * history() = 0; - virtual AddressBook * addressBook() = 0; - virtual Subaddress * subaddress() = 0; -diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp -index 478101af2..535005ab1 100644 ---- a/src/wallet/wallet2.cpp -+++ b/src/wallet/wallet2.cpp -@@ -157,6 +157,8 @@ static const std::string MULTISIG_SIGNATURE_MAGIC = "SigMultisigPkV1"; - - static const std::string ASCII_OUTPUT_MAGIC = "WowneroAsciiDataV1"; - -+static const std::string BACKGROUND_WALLET_SUFFIX = ".background"; -+ - boost::mutex tools::wallet2::default_daemon_address_lock; - std::string tools::wallet2::default_daemon_address = ""; - -@@ -1009,14 +1011,14 @@ uint64_t num_priv_multisig_keys_post_setup(uint64_t threshold, uint64_t total) - * @param keys_data_key the chacha key that encrypts wallet keys files - * @return crypto::chacha_key the chacha key that encrypts the wallet cache files - */ --crypto::chacha_key derive_cache_key(const crypto::chacha_key& keys_data_key) -+crypto::chacha_key derive_cache_key(const crypto::chacha_key& keys_data_key, const unsigned char domain_separator) - { - static_assert(HASH_SIZE == sizeof(crypto::chacha_key), "Mismatched sizes of hash and chacha key"); - - crypto::chacha_key cache_key; - epee::mlocked> cache_key_data; - memcpy(cache_key_data.data(), &keys_data_key, HASH_SIZE); -- cache_key_data[HASH_SIZE] = config::HASH_KEY_WALLET_CACHE; -+ cache_key_data[HASH_SIZE] = domain_separator; - cn_fast_hash(cache_key_data.data(), HASH_SIZE+1, (crypto::hash&) cache_key); - - return cache_key; -@@ -1104,7 +1106,7 @@ wallet_keys_unlocker::wallet_keys_unlocker(wallet2 &w, const boost::optional lock(lockers_lock); - if (lockers++ > 0) - locked = false; -- if (!locked || w.is_unattended() || w.ask_password() != tools::wallet2::AskPasswordToDecrypt || w.watch_only()) -+ if (!locked || w.is_unattended() || w.ask_password() != tools::wallet2::AskPasswordToDecrypt || w.watch_only() || w.is_background_syncing()) - { - locked = false; - return; -@@ -1221,6 +1223,11 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended, std - m_ignore_outputs_above(MONEY_SUPPLY), - m_ignore_outputs_below(0), - m_track_uses(false), -+ m_is_background_wallet(false), -+ m_background_sync_type(BackgroundSyncOff), -+ m_background_syncing(false), -+ m_processing_background_cache(false), -+ m_custom_background_key(boost::none), - m_show_wallet_name_when_locked(false), - m_inactivity_lock_timeout(DEFAULT_INACTIVITY_LOCK_TIMEOUT), - m_setup_background_mining(BackgroundMiningNo), -@@ -1869,6 +1876,9 @@ bool has_nonrequested_tx_at_height_or_above_requested(uint64_t height, const std - //---------------------------------------------------------------------------------------------------- - void wallet2::scan_tx(const std::unordered_set &txids) - { -+ THROW_WALLET_EXCEPTION_IF(m_background_syncing || m_is_background_wallet, error::wallet_internal_error, -+ "cannot scan tx from background wallet"); -+ - // Get the transactions from daemon in batches sorted lowest height to highest - tx_entry_data txs_to_scan = get_tx_entries(txids); - if (txs_to_scan.tx_entries.empty()) -@@ -2178,11 +2188,11 @@ void wallet2::scan_output(const cryptonote::transaction &tx, bool miner_tx, cons - THROW_WALLET_EXCEPTION_IF(i >= tx.vout.size(), error::wallet_internal_error, "Invalid vout index"); - - // if keys are encrypted, ask for password -- if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only && !m_multisig_rescan_k) -+ if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only && !m_multisig_rescan_k && !m_background_syncing) - { - static critical_section password_lock; - CRITICAL_REGION_LOCAL(password_lock); -- if (!m_encrypt_keys_after_refresh) -+ if (!m_encrypt_keys_after_refresh && !m_processing_background_cache) - { - boost::optional pwd = m_callback->on_get_password(pool ? "output found in pool" : "output received"); - THROW_WALLET_EXCEPTION_IF(!pwd, error::password_needed, tr("Password is needed to compute key image for incoming wownero")); -@@ -2194,7 +2204,7 @@ void wallet2::scan_output(const cryptonote::transaction &tx, bool miner_tx, cons - crypto::public_key output_public_key; - THROW_WALLET_EXCEPTION_IF(!get_output_public_key(tx.vout[i], output_public_key), error::wallet_internal_error, "Failed to get output public key"); - -- if (m_multisig) -+ if (m_multisig || m_background_syncing/*no spend key*/) - { - tx_scan_info.in_ephemeral.pub = output_public_key; - tx_scan_info.in_ephemeral.sec = crypto::null_skey; -@@ -2451,6 +2461,22 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote - THROW_WALLET_EXCEPTION_IF(tx.vout.size() != o_indices.size(), error::wallet_internal_error, - "transactions outputs size=" + std::to_string(tx.vout.size()) + - " not match with daemon response size=" + std::to_string(o_indices.size())); -+ -+ // we're going to re-process this receive when background sync is disabled -+ if (m_background_syncing && m_background_sync_data.txs.find(txid) == m_background_sync_data.txs.end()) -+ { -+ size_t bgs_idx = m_background_sync_data.txs.size(); -+ background_synced_tx_t bgs_tx = { -+ .index_in_background_sync_data = bgs_idx, -+ .tx = tx, -+ .output_indices = o_indices, -+ .height = height, -+ .block_timestamp = ts, -+ .double_spend_seen = double_spend_seen -+ }; -+ LOG_PRINT_L2("Adding received tx " << txid << " to background sync data (idx=" << bgs_idx << ")"); -+ m_background_sync_data.txs.insert({txid, std::move(bgs_tx)}); -+ } - } - - for(size_t o: outs) -@@ -2476,7 +2502,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote - td.m_tx = (const cryptonote::transaction_prefix&)tx; - td.m_txid = txid; - td.m_key_image = tx_scan_info[o].ki; -- td.m_key_image_known = !m_watch_only && !m_multisig; -+ td.m_key_image_known = !m_watch_only && !m_multisig && !m_background_syncing; - if (!td.m_key_image_known) - { - // we might have cold signed, and have a mapping to key images -@@ -2666,10 +2692,25 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote - set_spent(it->second, height); - if (!ignore_callbacks && 0 != m_callback) - m_callback->on_money_spent(height, txid, tx, amount, tx, td.m_subaddr_index); -+ -+ if (m_background_syncing && m_background_sync_data.txs.find(txid) == m_background_sync_data.txs.end()) -+ { -+ size_t bgs_idx = m_background_sync_data.txs.size(); -+ background_synced_tx_t bgs_tx = { -+ .index_in_background_sync_data = bgs_idx, -+ .tx = tx, -+ .output_indices = o_indices, -+ .height = height, -+ .block_timestamp = ts, -+ .double_spend_seen = double_spend_seen -+ }; -+ LOG_PRINT_L2("Adding spent tx " << txid << " to background sync data (idx=" << bgs_idx << ")"); -+ m_background_sync_data.txs.insert({txid, std::move(bgs_tx)}); -+ } - } - } - -- if (!pool && m_track_uses) -+ if (!pool && (m_track_uses || (m_background_syncing && it == m_key_images.end()))) - { - PERF_TIMER(track_uses); - const uint64_t amount = in_to_key.amount; -@@ -2683,7 +2724,27 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote - { - size_t idx = i->second; - THROW_WALLET_EXCEPTION_IF(idx >= m_transfers.size(), error::wallet_internal_error, "Output tracker cache index out of range"); -- m_transfers[idx].m_uses.push_back(std::make_pair(height, txid)); -+ -+ if (m_track_uses) -+ m_transfers[idx].m_uses.push_back(std::make_pair(height, txid)); -+ -+ // We'll re-process all txs which *might* be spends when we disable -+ // background sync and retrieve the spend key. We don't know if an -+ // output is a spend in this tx if we don't know its key image. -+ if (m_background_syncing && !m_transfers[idx].m_key_image_known && m_background_sync_data.txs.find(txid) == m_background_sync_data.txs.end()) -+ { -+ size_t bgs_idx = m_background_sync_data.txs.size(); -+ background_synced_tx_t bgs_tx = { -+ .index_in_background_sync_data = bgs_idx, -+ .tx = tx, -+ .output_indices = o_indices, -+ .height = height, -+ .block_timestamp = ts, -+ .double_spend_seen = double_spend_seen -+ }; -+ LOG_PRINT_L2("Adding plausible spent tx " << txid << " to background sync data (idx=" << bgs_idx << ")"); -+ m_background_sync_data.txs.insert({txid, std::move(bgs_tx)}); -+ } - } - } - } -@@ -2693,7 +2754,24 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote - continue; - for (uint64_t offset: offsets) - if (offset == td.m_global_output_index) -- td.m_uses.push_back(std::make_pair(height, txid)); -+ { -+ if (m_track_uses) -+ td.m_uses.push_back(std::make_pair(height, txid)); -+ if (m_background_syncing && !td.m_key_image_known && m_background_sync_data.txs.find(txid) == m_background_sync_data.txs.end()) -+ { -+ size_t bgs_idx = m_background_sync_data.txs.size(); -+ background_synced_tx_t bgs_tx = { -+ .index_in_background_sync_data = bgs_idx, -+ .tx = tx, -+ .output_indices = o_indices, -+ .height = height, -+ .block_timestamp = ts, -+ .double_spend_seen = double_spend_seen -+ }; -+ LOG_PRINT_L2("Adding plausible spent tx " << txid << " to background sync data (idx=" << bgs_idx << ")"); -+ m_background_sync_data.txs.insert({txid, std::move(bgs_tx)}); -+ } -+ } - } - } - } -@@ -3066,8 +3144,8 @@ void wallet2::pull_blocks(bool first, bool try_incremental, uint64_t start_heigh - req.start_height = start_height; - req.no_miner_tx = m_refresh_type == RefreshNoCoinbase; - -- req.requested_info = first ? COMMAND_RPC_GET_BLOCKS_FAST::BLOCKS_AND_POOL : COMMAND_RPC_GET_BLOCKS_FAST::BLOCKS_ONLY; -- if (try_incremental) -+ req.requested_info = (first && !m_background_syncing) ? COMMAND_RPC_GET_BLOCKS_FAST::BLOCKS_AND_POOL : COMMAND_RPC_GET_BLOCKS_FAST::BLOCKS_ONLY; -+ if (try_incremental && !m_background_syncing) - req.pool_info_since = m_pool_info_query_time; - - { -@@ -3094,7 +3172,7 @@ void wallet2::pull_blocks(bool first, bool try_incremental, uint64_t start_heigh - << ", height " << blocks_start_height + blocks.size() << ", node height " << res.current_height - << ", pool info " << static_cast(res.pool_info_extent)); - -- if (first) -+ if (first && !m_background_syncing) - { - if (res.pool_info_extent != COMMAND_RPC_GET_BLOCKS_FAST::NONE) - { -@@ -3606,6 +3684,9 @@ void wallet2::process_unconfirmed_transfer(bool incremental, const crypto::hash - // incremental update anymore, because with that we might miss some txs altogether. - void wallet2::update_pool_state(std::vector> &process_txs, bool refreshed, bool try_incremental) - { -+ process_txs.clear(); -+ if (m_background_syncing) -+ return; - bool updated = false; - if (m_pool_info_query_time != 0 && try_incremental) - { -@@ -4177,6 +4258,8 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo - } - - m_first_refresh_done = true; -+ if (m_background_syncing || m_is_background_wallet) -+ m_background_sync_data.first_refresh_done = true; - - LOG_PRINT_L1("Refresh done, blocks received: " << blocks_fetched << ", balance (all accounts): " << print_money(balance_all(false)) << ", unlocked: " << print_money(unlocked_balance_all(false))); - } -@@ -4262,6 +4345,14 @@ wallet2::detached_blockchain_data wallet2::detach_blockchain(uint64_t height, st - td.m_uses.pop_back(); - } - -+ for (auto it = m_background_sync_data.txs.begin(); it != m_background_sync_data.txs.end(); ) -+ { -+ if(height <= it->second.height) -+ it = m_background_sync_data.txs.erase(it); -+ else -+ ++it; -+ } -+ - if (output_tracker_cache) - output_tracker_cache->clear(); - -@@ -4336,8 +4427,12 @@ void wallet2::handle_reorg(uint64_t height, std::map m_blockchain.offset(), - error::wallet_internal_error, "Daemon claims reorg below last checkpoint"); -+ - detached_blockchain_data dbd = detach_blockchain(height, output_tracker_cache); - -+ if (m_background_syncing && height < m_background_sync_data.start_height) -+ m_background_sync_data.start_height = height; -+ - if (m_callback) - m_callback->on_reorg(height, dbd.detached_blockchain.size(), dbd.detached_tx_hashes.size()); - } -@@ -4347,6 +4442,7 @@ bool wallet2::deinit() - if(m_is_initialized) { - m_is_initialized = false; - unlock_keys_file(); -+ unlock_background_keys_file(); - m_account.deinit(); - } - return true; -@@ -4373,6 +4469,7 @@ bool wallet2::clear() - m_device_last_key_image_sync = 0; - m_pool_info_query_time = 0; - m_skip_to_height = 0; -+ m_background_sync_data = background_sync_data_t{}; - return true; - } - //---------------------------------------------------------------------------------------------------- -@@ -4391,13 +4488,30 @@ void wallet2::clear_soft(bool keep_key_images) - m_scanned_pool_txs[1].clear(); - m_pool_info_query_time = 0; - m_skip_to_height = 0; -+ m_background_sync_data = background_sync_data_t{}; - - cryptonote::block b; - generate_genesis(b); - m_blockchain.push_back(get_block_hash(b)); - m_last_block_reward = cryptonote::get_outs_money_amount(b.miner_tx); - } -- -+//---------------------------------------------------------------------------------------------------- -+void wallet2::clear_user_data() -+{ -+ for (auto i = m_confirmed_txs.begin(); i != m_confirmed_txs.end(); ++i) -+ i->second.m_dests.clear(); -+ for (auto i = m_unconfirmed_txs.begin(); i != m_unconfirmed_txs.end(); ++i) -+ i->second.m_dests.clear(); -+ for (auto i = m_transfers.begin(); i != m_transfers.end(); ++i) -+ i->m_frozen = false; -+ m_tx_keys.clear(); -+ m_tx_notes.clear(); -+ m_address_book.clear(); -+ m_subaddress_labels.clear(); -+ m_attributes.clear(); -+ m_account_tags = std::pair, std::vector>(); -+} -+//---------------------------------------------------------------------------------------------------- - /*! - * \brief Stores wallet information to wallet file. - * \param keys_file_name Name of wallet file -@@ -4409,16 +4523,35 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable - { - 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"); -- -+ return store_keys_file_data(keys_file_name, keys_file_data.get()); -+} -+//---------------------------------------------------------------------------------------------------- -+bool wallet2::store_keys(const std::string& keys_file_name, const crypto::chacha_key& key, bool watch_only, bool background_keys_file) -+{ -+ boost::optional keys_file_data = get_keys_file_data(key, watch_only, background_keys_file); -+ CHECK_AND_ASSERT_MES(keys_file_data != boost::none, false, "failed to generate wallet keys data"); -+ return store_keys_file_data(keys_file_name, keys_file_data.get(), background_keys_file); -+} -+//---------------------------------------------------------------------------------------------------- -+bool wallet2::store_keys_file_data(const std::string& keys_file_name, wallet2::keys_file_data &keys_file_data, bool background_keys_file) -+{ - std::string tmp_file_name = keys_file_name + ".new"; - std::string buf; -- bool r = ::serialization::dump_binary(keys_file_data.get(), buf); -+ bool r = ::serialization::dump_binary(keys_file_data, buf); - r = r && save_to_file(tmp_file_name, buf); - CHECK_AND_ASSERT_MES(r, false, "failed to generate wallet keys file " << tmp_file_name); - -- unlock_keys_file(); -+ if (!background_keys_file) -+ unlock_keys_file(); -+ else -+ unlock_background_keys_file(); -+ - std::error_code e = tools::replace_file(tmp_file_name, keys_file_name); -- lock_keys_file(); -+ -+ if (!background_keys_file) -+ lock_keys_file(); -+ else -+ lock_background_keys_file(keys_file_name); - - if (e) { - boost::filesystem::remove(tmp_file_name); -@@ -4430,26 +4563,27 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable - } - //---------------------------------------------------------------------------------------------------- - boost::optional wallet2::get_keys_file_data(const epee::wipeable_string& password, bool watch_only) -+{ -+ crypto::chacha_key key; -+ crypto::generate_chacha_key(password.data(), password.size(), key, m_kdf_rounds); -+ verify_password_with_cached_key(key); -+ return get_keys_file_data(key, watch_only); -+} -+//---------------------------------------------------------------------------------------------------- -+boost::optional wallet2::get_keys_file_data(const crypto::chacha_key& key, bool watch_only, bool background_keys_file) - { - 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); -- -- // We use m_cache_key as a deterministic test to see if given key corresponds to original password -- const crypto::chacha_key cache_key = derive_cache_key(key); -- THROW_WALLET_EXCEPTION_IF(cache_key != m_cache_key, error::invalid_password); -- - if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only) - { - account.encrypt_viewkey(key); - account.decrypt_keys(key); - } - -- if (watch_only) -+ if (watch_only || background_keys_file) - account.forget_spend_key(); - - account.encrypt_keys(key); -@@ -4584,6 +4718,9 @@ boost::optional wallet2::get_keys_file_data(const epee: - value2.SetInt(m_track_uses ? 1 : 0); - json.AddMember("track_uses", value2, json.GetAllocator()); - -+ value2.SetInt(m_background_sync_type); -+ json.AddMember("background_sync_type", value2, json.GetAllocator()); -+ - value2.SetInt(m_show_wallet_name_when_locked ? 1 : 0); - json.AddMember("show_wallet_name_when_locked", value2, json.GetAllocator()); - -@@ -4641,6 +4778,12 @@ boost::optional wallet2::get_keys_file_data(const epee: - value2.SetInt(m_enable_multisig ? 1 : 0); - json.AddMember("enable_multisig", value2, json.GetAllocator()); - -+ if (m_background_sync_type == BackgroundSyncCustomPassword && !background_keys_file && m_custom_background_key) -+ { -+ value.SetString(reinterpret_cast(m_custom_background_key.get().data()), m_custom_background_key.get().size()); -+ json.AddMember("custom_background_key", value, json.GetAllocator()); -+ } -+ - // Serialize the JSON object - rapidjson::StringBuffer buffer; - rapidjson::Writer writer(buffer); -@@ -4667,13 +4810,81 @@ void wallet2::setup_keys(const epee::wipeable_string &password) - m_account.decrypt_viewkey(key); - } - -- m_cache_key = derive_cache_key(key); -+ m_cache_key = derive_cache_key(key, config::HASH_KEY_WALLET_CACHE); - - get_ringdb_key(); - } - //---------------------------------------------------------------------------------------------------- -+void validate_background_cache_password_usage(const tools::wallet2::BackgroundSyncType background_sync_type, const boost::optional &background_cache_password, const bool multisig, const bool watch_only, const bool key_on_device) -+{ -+ THROW_WALLET_EXCEPTION_IF(multisig || watch_only || key_on_device, error::wallet_internal_error, multisig -+ ? "Background sync not implemented for multisig wallets" : watch_only -+ ? "Background sync not implemented for view only wallets" -+ : "Background sync not implemented for HW wallets"); -+ -+ switch (background_sync_type) -+ { -+ case tools::wallet2::BackgroundSyncOff: -+ { -+ THROW_WALLET_EXCEPTION(error::wallet_internal_error, "background sync is not enabled"); -+ break; -+ } -+ case tools::wallet2::BackgroundSyncReusePassword: -+ { -+ THROW_WALLET_EXCEPTION_IF(background_cache_password, error::wallet_internal_error, -+ "unexpected custom background cache password"); -+ break; -+ } -+ case tools::wallet2::BackgroundSyncCustomPassword: -+ { -+ THROW_WALLET_EXCEPTION_IF(!background_cache_password, error::wallet_internal_error, -+ "expected custom background cache password"); -+ break; -+ } -+ default: THROW_WALLET_EXCEPTION(error::wallet_internal_error, "unknown background sync type"); -+ } -+} -+//---------------------------------------------------------------------------------------------------- -+void get_custom_background_key(const epee::wipeable_string &password, crypto::chacha_key &custom_background_key, const uint64_t kdf_rounds) -+{ -+ crypto::chacha_key key; -+ crypto::generate_chacha_key(password.data(), password.size(), key, kdf_rounds); -+ custom_background_key = derive_cache_key(key, config::HASH_KEY_BACKGROUND_KEYS_FILE); -+} -+//---------------------------------------------------------------------------------------------------- -+const crypto::chacha_key wallet2::get_cache_key() -+{ -+ if (m_background_sync_type == BackgroundSyncCustomPassword && m_background_syncing) -+ { -+ THROW_WALLET_EXCEPTION_IF(!m_custom_background_key, error::wallet_internal_error, "Custom background key not set"); -+ // Domain separate keys used to encrypt background keys file and cache -+ return derive_cache_key(m_custom_background_key.get(), config::HASH_KEY_BACKGROUND_CACHE); -+ } -+ else -+ { -+ return m_cache_key; -+ } -+} -+//---------------------------------------------------------------------------------------------------- -+void wallet2::verify_password_with_cached_key(const epee::wipeable_string &password) -+{ -+ crypto::chacha_key key; -+ crypto::generate_chacha_key(password.data(), password.size(), key, m_kdf_rounds); -+ verify_password_with_cached_key(key); -+} -+//---------------------------------------------------------------------------------------------------- -+void wallet2::verify_password_with_cached_key(const crypto::chacha_key &key) -+{ -+ // We use m_cache_key as a deterministic test to see if given key corresponds to original password -+ const crypto::chacha_key cache_key = derive_cache_key(key, config::HASH_KEY_WALLET_CACHE); -+ THROW_WALLET_EXCEPTION_IF(cache_key != m_cache_key, error::invalid_password); -+} -+//---------------------------------------------------------------------------------------------------- - void wallet2::change_password(const std::string &filename, const epee::wipeable_string &original_password, const epee::wipeable_string &new_password) - { -+ THROW_WALLET_EXCEPTION_IF(m_background_syncing || m_is_background_wallet, error::wallet_internal_error, -+ "cannot change password from background wallet"); -+ - if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only) - decrypt_keys(original_password); - setup_keys(new_password); -@@ -4732,8 +4943,24 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st - 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()) -+ const bool try_v0_format = json.Parse(account_data.c_str()).HasParseError() || !json.IsObject(); -+ if (try_v0_format) - crypto::chacha8(keys_file_data.account_data.data(), keys_file_data.account_data.size(), key, keys_file_data.iv, &account_data[0]); -+ -+ // Check if it's a background keys file if both of the above formats fail -+ { -+ m_is_background_wallet = false; -+ m_background_syncing = false; -+ cryptonote::account_base account_data_check; -+ if (try_v0_format && !epee::serialization::load_t_from_binary(account_data_check, account_data)) -+ { -+ get_custom_background_key(password, key, m_kdf_rounds); -+ crypto::chacha20(keys_file_data.account_data.data(), keys_file_data.account_data.size(), key, keys_file_data.iv, &account_data[0]); -+ m_is_background_wallet = !json.Parse(account_data.c_str()).HasParseError() && json.IsObject(); -+ m_background_syncing = m_is_background_wallet; // start a background wallet background syncing -+ } -+ } -+ - // The contents should be JSON if the wallet follows the new format. - if (json.Parse(account_data.c_str()).HasParseError()) - { -@@ -4771,6 +4998,7 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st - m_ignore_outputs_above = MONEY_SUPPLY; - m_ignore_outputs_below = 0; - m_track_uses = false; -+ m_background_sync_type = BackgroundSyncOff; - m_show_wallet_name_when_locked = false; - m_inactivity_lock_timeout = DEFAULT_INACTIVITY_LOCK_TIMEOUT; - m_setup_background_mining = BackgroundMiningNo; -@@ -4788,6 +5016,7 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st - m_credits_target = 0; - m_enable_multisig = false; - m_allow_mismatched_daemon_version = true; -+ m_custom_background_key = boost::none; - } - else if(json.IsObject()) - { -@@ -5024,6 +5253,39 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st - m_credits_target = field_credits_target; - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, enable_multisig, int, Int, false, false); - m_enable_multisig = field_enable_multisig; -+ -+ GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, background_sync_type, BackgroundSyncType, Int, false, BackgroundSyncOff); -+ m_background_sync_type = field_background_sync_type; -+ -+ // Load encryption key used to encrypt background cache -+ crypto::chacha_key custom_background_key; -+ m_custom_background_key = boost::none; -+ if (m_background_sync_type == BackgroundSyncCustomPassword && !m_is_background_wallet) -+ { -+ if (!json.HasMember("custom_background_key")) -+ { -+ LOG_ERROR("Field custom_background_key not found in JSON"); -+ return false; -+ } -+ else if (!json["custom_background_key"].IsString()) -+ { -+ LOG_ERROR("Field custom_background_key found in JSON, but not String"); -+ return false; -+ } -+ else if (json["custom_background_key"].GetStringLength() != sizeof(crypto::chacha_key)) -+ { -+ LOG_ERROR("Field custom_background_key found in JSON, but not correct length"); -+ return false; -+ } -+ const char *field_custom_background_key = json["custom_background_key"].GetString(); -+ memcpy(custom_background_key.data(), field_custom_background_key, sizeof(crypto::chacha_key)); -+ m_custom_background_key = boost::optional(custom_background_key); -+ LOG_PRINT_L1("Loaded custom background key derived from custom password"); -+ } -+ else if (json.HasMember("custom_background_key")) -+ { -+ LOG_ERROR("Unexpected field custom_background_key found in JSON"); -+ } - } - else - { -@@ -5087,12 +5349,17 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st - 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) -+ if (!m_watch_only && !m_multisig && hwdev.device_protocol() != hw::device::PROTOCOL_COLD && !m_is_background_wallet) - 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); -+ { -+ if (!m_is_background_wallet) -+ setup_keys(password); -+ else -+ m_custom_background_key = boost::optional(key); -+ } - - return true; - } -@@ -5107,11 +5374,12 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st - * can be used prior to rewriting wallet keys file, to ensure user has entered the correct password - * - */ --bool wallet2::verify_password(const epee::wipeable_string& password) -+bool wallet2::verify_password(const epee::wipeable_string& password, crypto::secret_key &spend_key_out) - { - // this temporary unlocking is necessary for Windows (otherwise the file couldn't be loaded). - unlock_keys_file(); -- bool r = verify_password(m_keys_file, password, m_account.get_device().device_protocol() == hw::device::PROTOCOL_COLD || m_watch_only || m_multisig, m_account.get_device(), m_kdf_rounds); -+ const bool no_spend_key = m_account.get_device().device_protocol() == hw::device::PROTOCOL_COLD || m_watch_only || m_multisig || m_is_background_wallet; -+ bool r = verify_password(m_keys_file, password, no_spend_key, m_account.get_device(), m_kdf_rounds, spend_key_out); - lock_keys_file(); - return r; - } -@@ -5129,7 +5397,7 @@ bool wallet2::verify_password(const epee::wipeable_string& password) - * can be used prior to rewriting wallet keys file, to ensure user has entered the correct 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) -+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, crypto::secret_key &spend_key_out) - { - rapidjson::Document json; - wallet2::keys_file_data keys_file_data; -@@ -5146,9 +5414,22 @@ bool wallet2::verify_password(const std::string& keys_file_name, const epee::wip - 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()) -+ const bool try_v0_format = json.Parse(account_data.c_str()).HasParseError() || !json.IsObject(); -+ if (try_v0_format) - crypto::chacha8(keys_file_data.account_data.data(), keys_file_data.account_data.size(), key, keys_file_data.iv, &account_data[0]); - -+ // Check if it's a background keys file if both of the above formats fail -+ { -+ cryptonote::account_base account_data_check; -+ if (try_v0_format && !epee::serialization::load_t_from_binary(account_data_check, account_data)) -+ { -+ get_custom_background_key(password, key, kdf_rounds); -+ crypto::chacha20(keys_file_data.account_data.data(), keys_file_data.account_data.size(), key, keys_file_data.iv, &account_data[0]); -+ const bool is_background_wallet = json.Parse(account_data.c_str()).HasParseError() && json.IsObject(); -+ no_spend_key = no_spend_key || is_background_wallet; -+ } -+ } -+ - // The contents should be JSON if the wallet follows the new format. - if (json.Parse(account_data.c_str()).HasParseError()) - { -@@ -5173,6 +5454,7 @@ bool wallet2::verify_password(const std::string& keys_file_name, const epee::wip - 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); -+ spend_key_out = (!no_spend_key && r) ? keys.m_spend_secret_key : crypto::null_skey; - return r; - } - -@@ -5184,9 +5466,7 @@ void wallet2::encrypt_keys(const crypto::chacha_key &key) - - void wallet2::decrypt_keys(const crypto::chacha_key &key) - { -- // We use m_cache_key as a deterministic test to see if given key corresponds to original password -- const crypto::chacha_key cache_key = derive_cache_key(key); -- THROW_WALLET_EXCEPTION_IF(cache_key != m_cache_key, error::invalid_password); -+ verify_password_with_cached_key(key); - - m_account.encrypt_viewkey(key); - m_account.decrypt_keys(key); -@@ -5862,11 +6142,30 @@ void wallet2::rewrite(const std::string& wallet_name, const epee::wipeable_strin - { - if (wallet_name.empty()) - return; -+ THROW_WALLET_EXCEPTION_IF(m_background_syncing || m_is_background_wallet, error::wallet_internal_error, -+ "cannot change wallet settings from background wallet"); - prepare_file_names(wallet_name); - boost::system::error_code ignored_ec; - THROW_WALLET_EXCEPTION_IF(!boost::filesystem::exists(m_keys_file, ignored_ec), error::file_not_found, m_keys_file); - bool r = store_keys(m_keys_file, password, m_watch_only); - THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); -+ -+ // Update the background keys file when we rewrite the main wallet keys file -+ if (m_background_sync_type == BackgroundSyncCustomPassword && m_custom_background_key) -+ { -+ const std::string background_keys_filename = make_background_keys_file_name(wallet_name); -+ if (!lock_background_keys_file(background_keys_filename)) -+ { -+ LOG_ERROR("Background keys file " << background_keys_filename << " is opened by another wallet program and cannot be rewritten"); -+ return; // not fatal, background keys file will just have different wallet settings -+ } -+ store_background_keys(m_custom_background_key.get()); -+ store_background_cache(m_custom_background_key.get(), true/*do_reset_background_sync_data*/); -+ } -+ else if (m_background_sync_type == BackgroundSyncReusePassword) -+ { -+ reset_background_sync_data(m_background_sync_data); -+ } - } - /*! - * \brief Writes to a file named based on the normal wallet (doesn't generate key, assumes it's already there) -@@ -5900,6 +6199,16 @@ bool wallet2::wallet_valid_path_format(const std::string& file_path) - return !file_path.empty(); - } - //---------------------------------------------------------------------------------------------------- -+std::string wallet2::make_background_wallet_file_name(const std::string &wallet_file) -+{ -+ return wallet_file + BACKGROUND_WALLET_SUFFIX; -+} -+//---------------------------------------------------------------------------------------------------- -+std::string wallet2::make_background_keys_file_name(const std::string &wallet_file) -+{ -+ return make_background_wallet_file_name(wallet_file) + ".keys"; -+} -+//---------------------------------------------------------------------------------------------------- - bool wallet2::parse_long_payment_id(const std::string& payment_id_str, crypto::hash& payment_id) - { - cryptonote::blobdata payment_id_data; -@@ -6135,10 +6444,81 @@ void wallet2::load(const std::string& wallet_, const epee::wipeable_string& pass - THROW_WALLET_EXCEPTION_IF(true, error::file_read_error, "failed to load keys from buffer"); - } - -- wallet_keys_unlocker unlocker(*this, m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only, password); -+ wallet_keys_unlocker unlocker(*this, m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only && !m_is_background_wallet, password); - - //keys loaded ok! - //try to load wallet cache. but even if we failed, it is not big problem -+ load_wallet_cache(use_fs, cache_buf); -+ -+ if (!m_persistent_rpc_client_id) -+ set_rpc_client_secret_key(rct::rct2sk(rct::skGen())); -+ -+ // Wallets used to wipe, but not erase, old unused multisig key info, which lead to huge memory leaks. -+ // Here we erase these multisig keys if they're zero'd out to free up space. -+ for (auto &td : m_transfers) -+ { -+ auto mk_it = td.m_multisig_k.begin(); -+ while (mk_it != td.m_multisig_k.end()) -+ { -+ if (*mk_it == rct::zero()) -+ mk_it = td.m_multisig_k.erase(mk_it); -+ else -+ ++mk_it; -+ } -+ } -+ -+ cryptonote::block genesis; -+ generate_genesis(genesis); -+ crypto::hash genesis_hash = get_block_hash(genesis); -+ -+ if (m_blockchain.empty()) -+ { -+ m_blockchain.push_back(genesis_hash); -+ m_last_block_reward = cryptonote::get_outs_money_amount(genesis.miner_tx); -+ } -+ else -+ { -+ check_genesis(genesis_hash); -+ } -+ -+ trim_hashchain(); -+ -+ if (get_num_subaddress_accounts() == 0) -+ add_subaddress_account(tr("Primary account")); -+ -+ try -+ { -+ find_and_save_rings(false); -+ } -+ catch (const std::exception &e) -+ { -+ MERROR("Failed to save rings, will try again next time"); -+ } -+ -+ try -+ { -+ if (use_fs) -+ m_message_store.read_from_file(get_multisig_wallet_state(), m_mms_file, m_load_deprecated_formats); -+ } -+ catch (const std::exception &e) -+ { -+ MERROR("Failed to initialize MMS, it will be unusable"); -+ } -+ -+ try -+ { -+ if (use_fs) -+ process_background_cache_on_open(); -+ } -+ catch (const std::exception &e) -+ { -+ MERROR("Failed to process background cache on open: " << e.what()); -+ } -+} -+//---------------------------------------------------------------------------------------------------- -+void wallet2::load_wallet_cache(const bool use_fs, const std::string& cache_buf) -+{ -+ boost::system::error_code e; - bool cache_missing = use_fs ? (!boost::filesystem::exists(m_wallet_file, e) || e) : cache_buf.empty(); - if (cache_missing) - { -@@ -6165,7 +6545,7 @@ void wallet2::load(const std::string& wallet_, const epee::wipeable_string& pass - 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]); -+ crypto::chacha20(cache_file_data.cache_data.data(), cache_file_data.cache_data.size(), get_cache_key(), cache_file_data.iv, &cache_data[0]); - - try { - bool loaded = false; -@@ -6255,60 +6635,76 @@ void wallet2::load(const std::string& wallet_, const epee::wipeable_string& pass - m_account_public_address.m_view_public_key != m_account.get_keys().m_account_address.m_view_public_key, - error::wallet_files_doesnt_correspond, m_keys_file, m_wallet_file); - } -+} -+//---------------------------------------------------------------------------------------------------- -+void wallet2::process_background_cache_on_open() -+{ -+ if (m_wallet_file.empty()) -+ return; -+ if (m_background_syncing || m_is_background_wallet) -+ return; -+ if (m_background_sync_type == BackgroundSyncOff) -+ return; - -- if (!m_persistent_rpc_client_id) -- set_rpc_client_secret_key(rct::rct2sk(rct::skGen())); -+ if (m_background_sync_type == BackgroundSyncReusePassword) -+ { -+ const background_sync_data_t background_sync_data = m_background_sync_data; -+ const hashchain blockchain = m_blockchain; -+ process_background_cache(background_sync_data, blockchain, m_last_block_reward); - -- // Wallets used to wipe, but not erase, old unused multisig key info, which lead to huge memory leaks. -- // Here we erase these multisig keys if they're zero'd out to free up space. -- for (auto &td : m_transfers) -+ // Reset the background cache after processing -+ reset_background_sync_data(m_background_sync_data); -+ } -+ else if (m_background_sync_type == BackgroundSyncCustomPassword) - { -- auto mk_it = td.m_multisig_k.begin(); -- while (mk_it != td.m_multisig_k.end()) -+ // If the background wallet files don't exist, recreate them -+ const std::string background_keys_file = make_background_keys_file_name(m_wallet_file); -+ const std::string background_wallet_file = make_background_wallet_file_name(m_wallet_file); -+ const bool background_keys_file_exists = boost::filesystem::exists(background_keys_file); -+ const bool background_wallet_exists = boost::filesystem::exists(background_wallet_file); -+ -+ THROW_WALLET_EXCEPTION_IF(!lock_background_keys_file(background_keys_file), error::background_wallet_already_open, background_wallet_file); -+ THROW_WALLET_EXCEPTION_IF(!m_custom_background_key, error::wallet_internal_error, "Custom background key not set"); -+ -+ if (!background_keys_file_exists) - { -- if (*mk_it == rct::zero()) -- mk_it = td.m_multisig_k.erase(mk_it); -- else -- ++mk_it; -+ MDEBUG("Background keys file not found, restoring"); -+ store_background_keys(m_custom_background_key.get()); - } -- } - -- cryptonote::block genesis; -- generate_genesis(genesis); -- crypto::hash genesis_hash = get_block_hash(genesis); -+ if (!background_wallet_exists) -+ { -+ MDEBUG("Background cache not found, restoring"); -+ store_background_cache(m_custom_background_key.get(), true/*do_reset_background_sync_data*/); -+ return; -+ } - -- if (m_blockchain.empty()) -- { -- m_blockchain.push_back(genesis_hash); -- m_last_block_reward = cryptonote::get_outs_money_amount(genesis.miner_tx); -- } -- else -- { -- check_genesis(genesis_hash); -- } -+ MDEBUG("Loading background cache"); - -- trim_hashchain(); -+ // Set up a minimal background wallet2 instance -+ std::unique_ptr background_w2(new wallet2(m_nettype)); -+ background_w2->m_is_background_wallet = true; -+ background_w2->m_background_syncing = true; -+ background_w2->m_background_sync_type = m_background_sync_type; -+ background_w2->m_custom_background_key = m_custom_background_key; - -- if (get_num_subaddress_accounts() == 0) -- add_subaddress_account(tr("Primary account")); -+ cryptonote::account_base account = m_account; -+ account.forget_spend_key(); -+ background_w2->m_account = account; - -- try -- { -- find_and_save_rings(false); -- } -- catch (const std::exception &e) -- { -- MERROR("Failed to save rings, will try again next time"); -- } -- -- try -- { -- if (use_fs) -- m_message_store.read_from_file(get_multisig_wallet_state(), m_mms_file, m_load_deprecated_formats); -+ // Load background cache from file -+ background_w2->clear(); -+ background_w2->prepare_file_names(background_wallet_file); -+ background_w2->load_wallet_cache(true/*use_fs*/); -+ -+ process_background_cache(background_w2->m_background_sync_data, background_w2->m_blockchain, background_w2->m_last_block_reward); -+ -+ // Reset the background cache after processing -+ store_background_cache(m_custom_background_key.get(), true/*do_reset_background_sync_data*/); - } -- catch (const std::exception &e) -+ else - { -- MERROR("Failed to initialize MMS, it will be unusable"); -+ THROW_WALLET_EXCEPTION(error::wallet_internal_error, "unknown background sync type"); - } - } - //---------------------------------------------------------------------------------------------------- -@@ -6390,6 +6786,8 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas - same_file = canonical_old_path == canonical_new_path; - } - -+ THROW_WALLET_EXCEPTION_IF(m_is_background_wallet && !same_file, error::wallet_internal_error, -+ "Cannot save background wallet files to a different location"); - - if (!same_file) - { -@@ -6406,6 +6804,21 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas - } - } - } -+ else if (m_background_sync_type == BackgroundSyncCustomPassword && m_background_syncing && !m_is_background_wallet) -+ { -+ // We're background syncing, so store the wallet cache as a background cache -+ // keeping the background sync data -+ try -+ { -+ THROW_WALLET_EXCEPTION_IF(!m_custom_background_key, error::wallet_internal_error, "Custom background key not set"); -+ store_background_cache(m_custom_background_key.get(), false/*do_reset_background_sync_data*/); -+ } -+ catch (const std::exception &e) -+ { -+ MERROR("Failed to store background cache while background syncing: " << e.what()); -+ } -+ return; -+ } - - // get wallet cache data - boost::optional cache_file_data = get_cache_file_data(); -@@ -6499,6 +6912,22 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas - // store should only exist if the MMS is really active - m_message_store.write_to_file(get_multisig_wallet_state(), m_mms_file); - } -+ -+ if (m_background_sync_type == BackgroundSyncCustomPassword && !m_background_syncing && !m_is_background_wallet) -+ { -+ // Update the background wallet cache when we store the main wallet cache -+ // Note: if background syncing when this is called, it means the background -+ // wallet is open and was already stored above -+ try -+ { -+ THROW_WALLET_EXCEPTION_IF(!m_custom_background_key, error::wallet_internal_error, "Custom background key not set"); -+ store_background_cache(m_custom_background_key.get(), true/*do_reset_background_sync_data*/); -+ } -+ catch (const std::exception &e) -+ { -+ MERROR("Failed to update background cache: " << e.what()); -+ } -+ } - } - //---------------------------------------------------------------------------------------------------- - boost::optional wallet2::get_cache_file_data() -@@ -6516,7 +6945,7 @@ boost::optional wallet2::get_cache_file_data() - 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]); -+ crypto::chacha20(cache_file_data.get().cache_data.data(), cache_file_data.get().cache_data.size(), get_cache_key(), cache_file_data.get().iv, &cipher[0]); - cache_file_data.get().cache_data = cipher; - return cache_file_data; - } -@@ -8586,6 +9015,34 @@ bool wallet2::is_keys_file_locked() const - return m_keys_file_locker->locked(); - } - -+bool wallet2::lock_background_keys_file(const std::string &background_keys_file) -+{ -+ if (background_keys_file.empty() || !boost::filesystem::exists(background_keys_file)) -+ return true; -+ if (m_background_keys_file_locker && m_background_keys_file_locker->locked()) -+ return true; -+ m_background_keys_file_locker.reset(new tools::file_locker(background_keys_file)); -+ return m_background_keys_file_locker->locked(); -+} -+ -+bool wallet2::unlock_background_keys_file() -+{ -+ if (!m_background_keys_file_locker) -+ { -+ MDEBUG("background keys file locker is not set"); -+ return false; -+ } -+ m_background_keys_file_locker.reset(); -+ return true; -+} -+ -+bool wallet2::is_background_keys_file_locked() const -+{ -+ if (!m_background_keys_file_locker) -+ return false; -+ return m_background_keys_file_locker->locked(); -+} -+ - bool wallet2::tx_add_fake_output(std::vector> &outs, uint64_t global_index, const crypto::public_key& output_public_key, const rct::key& mask, uint64_t real_index, bool unlocked, std::unordered_set &valid_public_keys_cache) const - { - if (!unlocked) // don't add locked outs -@@ -13909,6 +14366,413 @@ bool wallet2::import_key_images(signed_tx_set & signed_tx, size_t offset, bool o - return import_key_images(signed_tx.key_images, offset, only_selected_transfers ? boost::make_optional(selected_transfers) : boost::none); - } - -+/* -+ In background sync mode, we use just the view key when the wallet is scanning -+ to identify all txs where: -+ -+ 1. We received an output. -+ 2. We spent an output. -+ 3. We *may* have spent a received output but we didn't know for sure because -+ the spend key was not loaded while background sync was enabled. -+ -+ When the user is ready to use the spend key again, we call this function to -+ process all those background synced transactions with the spend key loaded, -+ so that we can properly generate key images for the transactions which we -+ we were not able to do so for while background sync was enabled. This allows -+ us to determine *all* receives and spends the user completed while the wallet -+ had background sync enabled. Once this function completes, we can continue -+ scanning from where the background sync left off. -+ -+ Txs of type 3 (txs which we *may* have spent received output(s)) are txs where -+ 1+ rings contain an output that the user received and the wallet does not know -+ the associated key image for that output. We don't know if the user spent in -+ this type of tx or not. This function will generate key images for all outputs -+ we don't know key images for, and then check if those outputs were spent in -+ the txs of type 3. -+ -+ By storing this type of "plausible spend tx" when scanning in background sync -+ mode, we avoid the need to query the daemon with key images when background -+ sync mode is disabled to see if those key images were spent. This would -+ reveal key images to 3rd party nodes for users who don't run their own. -+ Although this is not a perfect solution to avoid revealing key images to a 3rd -+ party node (since tx submission trivially reveals key images to a node), it's -+ probably better than revealing *unused* key images to a 3rd party node, which -+ would enable the 3rd party to deduce that a tx is spending an output at least -+ X old when the key image is included in the chain. -+*/ -+void wallet2::process_background_cache(const background_sync_data_t &background_sync_data, const hashchain &background_synced_chain, uint64_t last_block_reward) -+{ -+ // We expect the spend key to be in a decrypted state while -+ // m_processing_background_cache is true -+ m_processing_background_cache = true; -+ auto done_processing = epee::misc_utils::create_scope_leave_handler([&, this]() { -+ m_processing_background_cache = false; -+ }); -+ -+ if (m_background_syncing || m_multisig || m_watch_only || key_on_device()) -+ return; -+ -+ if (!background_sync_data.first_refresh_done) -+ { -+ MDEBUG("Skipping processing background cache, background cache has not synced yet"); -+ return; -+ } -+ -+ // Skip processing if wallet cache is synced higher than background cache -+ const uint64_t current_height = m_blockchain.size(); -+ const uint64_t background_height = background_synced_chain.size(); -+ MDEBUG("Background cache height " << background_height << " , wallet height " << current_height); -+ if (current_height > background_height) -+ { -+ MWARNING("Skipping processing background cache, synced height is higher than background cache"); -+ return; -+ } -+ -+ if (m_refresh_from_block_height < background_sync_data.wallet_refresh_from_block_height || -+ m_subaddress_lookahead_major > background_sync_data.subaddress_lookahead_major || -+ m_subaddress_lookahead_minor > background_sync_data.subaddress_lookahead_minor || -+ m_refresh_type < background_sync_data.wallet_refresh_type) -+ { -+ MWARNING("Skipping processing background cache, background wallet sync settings did not match main wallet's"); -+ MDEBUG("Wallet settings: " << -+ ", m_refresh_from_block_height: " << m_refresh_from_block_height << " vs " << background_sync_data.wallet_refresh_from_block_height << -+ ", m_subaddress_lookahead_major: " << m_subaddress_lookahead_major << " vs " << background_sync_data.subaddress_lookahead_major << -+ ", m_subaddress_lookahead_minor: " << m_subaddress_lookahead_minor << " vs " << background_sync_data.subaddress_lookahead_minor << -+ ", m_refresh_type: " << m_refresh_type << " vs " << background_sync_data.wallet_refresh_type); -+ return; -+ } -+ -+ // Sort background synced txs in the order they appeared in the cache so that -+ // we process them in the order they appeared in the chain. Thus if tx2 spends -+ // from tx1, we will know because tx1 is processed before tx2. -+ std::vector> sorted_bgs_cache(background_sync_data.txs.begin(), background_sync_data.txs.end()); -+ std::sort(sorted_bgs_cache.begin(), sorted_bgs_cache.end(), -+ [](const std::pair& l, const std::pair& r) -+ { -+ uint64_t left_index = l.second.index_in_background_sync_data; -+ uint64_t right_index = r.second.index_in_background_sync_data; -+ THROW_WALLET_EXCEPTION_IF( -+ (left_index < right_index && l.second.height > r.second.height) || -+ (left_index > right_index && l.second.height < r.second.height), -+ error::wallet_internal_error, "Unexpected background sync data order"); -+ return left_index < right_index; -+ }); -+ -+ // All txs in the background cache should have height >= sync start height, -+ // but not fatal if not -+ if (!sorted_bgs_cache.empty() && sorted_bgs_cache[0].second.height < background_sync_data.start_height) -+ MWARNING("First tx in background cache has height (" << sorted_bgs_cache[0].second.height << ") lower than sync start height (" << background_sync_data.start_height << ")"); -+ -+ // We want to process all background synced txs in order to make sure -+ // the wallet state updates correctly. First we remove all txs from the wallet -+ // from before the background sync start height, then re-process them in -+ // chronological order. The background cache should contain a superset of -+ // *all* the wallet's txs from after the background sync start height. -+ MDEBUG("Processing " << background_sync_data.txs.size() << " background synced txs starting from height " << background_sync_data.start_height); -+ detached_blockchain_data dbd = detach_blockchain(background_sync_data.start_height); -+ -+ for (const auto &bgs_tx : sorted_bgs_cache) -+ { -+ MDEBUG("Processing background synced tx " << bgs_tx.first); -+ -+ process_new_transaction(bgs_tx.first, bgs_tx.second.tx, bgs_tx.second.output_indices, bgs_tx.second.height, 0, bgs_tx.second.block_timestamp, -+ cryptonote::is_coinbase(bgs_tx.second.tx), false/*pool*/, bgs_tx.second.double_spend_seen, {}, {}, true/*ignore_callbacks*/); -+ -+ // Re-set destination addresses if they were previously set -+ if (m_confirmed_txs.find(bgs_tx.first) != m_confirmed_txs.end() && -+ dbd.detached_confirmed_txs_dests.find(bgs_tx.first) != dbd.detached_confirmed_txs_dests.end()) -+ { -+ m_confirmed_txs[bgs_tx.first].m_dests = std::move(dbd.detached_confirmed_txs_dests[bgs_tx.first]); -+ } -+ } -+ -+ m_blockchain = background_synced_chain; -+ m_last_block_reward = last_block_reward; -+ -+ MDEBUG("Finished processing background sync data"); -+} -+//---------------------------------------------------------------------------------------------------- -+void wallet2::reset_background_sync_data(background_sync_data_t &background_sync_data) -+{ -+ background_sync_data.first_refresh_done = false; -+ background_sync_data.start_height = get_blockchain_current_height(); -+ background_sync_data.txs.clear(); -+ -+ background_sync_data.wallet_refresh_from_block_height = m_refresh_from_block_height; -+ background_sync_data.subaddress_lookahead_major = m_subaddress_lookahead_major; -+ background_sync_data.subaddress_lookahead_minor = m_subaddress_lookahead_minor; -+ background_sync_data.wallet_refresh_type = m_refresh_type; -+} -+//---------------------------------------------------------------------------------------------------- -+void wallet2::store_background_cache(const crypto::chacha_key &custom_background_key, const bool do_reset_background_sync_data) -+{ -+ MDEBUG("Storing background cache (do_reset_background_sync_data=" << do_reset_background_sync_data << ")"); -+ -+ THROW_WALLET_EXCEPTION_IF(m_background_sync_type != BackgroundSyncCustomPassword, error::wallet_internal_error, -+ "Can only write a background cache when using a custom background password"); -+ THROW_WALLET_EXCEPTION_IF(m_wallet_file.empty(), error::wallet_internal_error, -+ "No wallet file known, can't store background cache"); -+ -+ std::unique_ptr background_w2(new wallet2(m_nettype)); -+ background_w2->prepare_file_names(make_background_wallet_file_name(m_wallet_file)); -+ -+ // Make sure background wallet is opened by this wallet -+ THROW_WALLET_EXCEPTION_IF(!lock_background_keys_file(background_w2->m_keys_file), -+ error::background_wallet_already_open, background_w2->m_wallet_file); -+ -+ // Load a background wallet2 instance using this wallet2 instance -+ std::string this_wallet2; -+ bool r = ::serialization::dump_binary(*this, this_wallet2); -+ THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to serialize wallet cache"); -+ -+ background_w2->clear(); -+ r = ::serialization::parse_binary(this_wallet2, *background_w2); -+ THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to deserialize wallet cache"); -+ -+ // Clear sensitive data from background cache not needed to sync -+ background_w2->clear_user_data(); -+ -+ background_w2->m_is_background_wallet = true; -+ if (do_reset_background_sync_data) -+ reset_background_sync_data(background_w2->m_background_sync_data); -+ else -+ background_w2->m_background_sync_data = m_background_sync_data; -+ background_w2->m_background_syncing = true; -+ -+ background_w2->m_custom_background_key = boost::optional(custom_background_key); -+ background_w2->m_background_sync_type = m_background_sync_type; -+ background_w2->store(); -+ -+ MDEBUG("Background cache stored (" << background_w2->m_transfers.size() << " transfers, " << background_w2->m_background_sync_data.txs.size() << " background synced txs)"); -+} -+//---------------------------------------------------------------------------------------------------- -+void wallet2::store_background_keys(const crypto::chacha_key &custom_background_key) -+{ -+ MDEBUG("Storing background keys"); -+ -+ THROW_WALLET_EXCEPTION_IF(m_wallet_file.empty(), error::wallet_internal_error, -+ "No wallet file known, can't store background keys"); -+ -+ const std::string background_keys_file = make_background_keys_file_name(m_wallet_file); -+ bool r = store_keys(background_keys_file, custom_background_key, false/*watch_only*/, true/*background_keys_file*/); -+ THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, background_keys_file); -+ THROW_WALLET_EXCEPTION_IF(!is_background_keys_file_locked(), error::wallet_internal_error, background_keys_file + "\" should be locked"); -+ -+ // GUI uses the address file to differentiate non-mainnet wallets in the UI -+ const std::string background_address_file = make_background_wallet_file_name(m_wallet_file) + ".address.txt"; -+ if (m_nettype != MAINNET && !boost::filesystem::exists(background_address_file)) -+ { -+ r = save_to_file(background_address_file, m_account.get_public_address_str(m_nettype), true); -+ if (!r) MERROR("String with address text not saved"); -+ } -+ -+ MDEBUG("Background keys stored"); -+} -+//---------------------------------------------------------------------------------------------------- -+void wallet2::write_background_sync_wallet(const epee::wipeable_string &wallet_password, const epee::wipeable_string &background_cache_password) -+{ -+ MDEBUG("Storing background sync wallet"); -+ THROW_WALLET_EXCEPTION_IF(m_background_sync_type != BackgroundSyncCustomPassword, error::wallet_internal_error, -+ "Can only write a background sync wallet when using a custom background password"); -+ THROW_WALLET_EXCEPTION_IF(m_background_syncing || m_is_background_wallet, error::wallet_internal_error, -+ "Can't write background sync wallet from an existing background cache"); -+ THROW_WALLET_EXCEPTION_IF(wallet_password == background_cache_password, -+ error::background_custom_password_same_as_wallet_password); -+ -+ // Set the background encryption key -+ crypto::chacha_key custom_background_key; -+ get_custom_background_key(background_cache_password, custom_background_key, m_kdf_rounds); -+ -+ // Keep the background encryption key in memory so the main wallet can update -+ // the background cache when it stores the main wallet cache -+ m_custom_background_key = boost::optional(custom_background_key); -+ -+ if (m_wallet_file.empty() || m_keys_file.empty()) -+ return; -+ -+ // Save background keys file, then background cache, then update main wallet settings -+ store_background_keys(custom_background_key); -+ store_background_cache(custom_background_key, true/*do_reset_background_sync_data*/); -+ bool r = store_keys(m_keys_file, wallet_password, false/*watch_only*/); -+ THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); -+ -+ MDEBUG("Background sync wallet saved successfully"); -+} -+//---------------------------------------------------------------------------------------------------- -+void wallet2::setup_background_sync(BackgroundSyncType background_sync_type, const epee::wipeable_string &wallet_password, const boost::optional &background_cache_password) -+{ -+ MDEBUG("Setting background sync to type " << background_sync_type); -+ THROW_WALLET_EXCEPTION_IF(m_background_syncing || m_is_background_wallet, error::wallet_internal_error, -+ "Can't set background sync type from an existing background cache"); -+ verify_password_with_cached_key(wallet_password); -+ -+ if (background_sync_type != BackgroundSyncOff) -+ validate_background_cache_password_usage(background_sync_type, background_cache_password, m_multisig, m_watch_only, key_on_device()); -+ -+ THROW_WALLET_EXCEPTION_IF(background_sync_type == BackgroundSyncCustomPassword && wallet_password == background_cache_password, -+ error::background_custom_password_same_as_wallet_password); -+ -+ if (m_background_sync_type == background_sync_type && background_sync_type != BackgroundSyncCustomPassword) -+ return; // No need to make any changes -+ -+ if (!m_wallet_file.empty()) -+ { -+ // Delete existing background files if they already exist -+ const std::string old_background_wallet_file = make_background_wallet_file_name(m_wallet_file); -+ const std::string old_background_keys_file = make_background_keys_file_name(m_wallet_file); -+ const std::string old_background_address_file = old_background_wallet_file + ".address.txt"; -+ -+ // Make sure no other program is using the background wallet -+ THROW_WALLET_EXCEPTION_IF(!lock_background_keys_file(old_background_keys_file), -+ error::background_wallet_already_open, old_background_wallet_file); -+ -+ if (boost::filesystem::exists(old_background_wallet_file)) -+ if (!boost::filesystem::remove(old_background_wallet_file)) -+ LOG_ERROR("Error deleting background wallet file: " << old_background_wallet_file); -+ -+ if (boost::filesystem::exists(old_background_keys_file)) -+ if (!boost::filesystem::remove(old_background_keys_file)) -+ LOG_ERROR("Error deleting background keys file: " << old_background_keys_file); -+ -+ if (boost::filesystem::exists(old_background_address_file)) -+ if (!boost::filesystem::remove(old_background_address_file)) -+ LOG_ERROR("Error deleting background address file: " << old_background_address_file); -+ } -+ -+ m_background_sync_type = background_sync_type; -+ m_custom_background_key = boost::none; -+ -+ // Write the new files -+ switch (background_sync_type) -+ { -+ case BackgroundSyncOff: -+ case BackgroundSyncReusePassword: rewrite(m_wallet_file, wallet_password); break; -+ case BackgroundSyncCustomPassword: write_background_sync_wallet(wallet_password, background_cache_password.get()); break; -+ default: THROW_WALLET_EXCEPTION(error::wallet_internal_error, "unknown background sync type"); -+ } -+ -+ MDEBUG("Done setting background sync type"); -+} -+//---------------------------------------------------------------------------------------------------- -+/* -+ When background syncing, the wallet scans using just the view key, without -+ keeping the spend key in decrypted state. When a user returns to the wallet -+ and decrypts the spend key, the wallet processes the background synced txs, -+ then the wallet picks up scanning normally right where the background sync -+ left off. -+*/ -+void wallet2::start_background_sync() -+{ -+ THROW_WALLET_EXCEPTION_IF(m_background_sync_type == BackgroundSyncOff, error::wallet_internal_error, -+ "must setup background sync first before using background sync"); -+ THROW_WALLET_EXCEPTION_IF(m_is_background_wallet, error::wallet_internal_error, -+ "Can't start background syncing from a background wallet (it is always background syncing)"); -+ -+ MDEBUG("Starting background sync"); -+ -+ if (m_background_syncing) -+ { -+ MDEBUG("Already background syncing"); -+ return; -+ } -+ -+ if (m_background_sync_type == BackgroundSyncCustomPassword && !m_wallet_file.empty()) -+ { -+ // Save the current state of the wallet cache. Only necessary when using a -+ // custom background password which uses distinct background wallet to sync. -+ // When reusing wallet password to sync we reuse the main wallet cache. -+ store(); -+ -+ // Wipe user data from the background wallet cache not needed to sync. -+ // Only wipe user data from background cache if wallet cache is stored -+ // on disk; otherwise we could lose the data. -+ clear_user_data(); -+ -+ // Wipe m_cache_key since it can be used to decrypt main wallet cache -+ m_cache_key.scrub(); -+ } -+ -+ reset_background_sync_data(m_background_sync_data); -+ m_background_syncing = true; -+ -+ // Wipe the spend key from memory -+ m_account.forget_spend_key(); -+ -+ MDEBUG("Background sync started at height " << m_background_sync_data.start_height); -+} -+//---------------------------------------------------------------------------------------------------- -+void wallet2::stop_background_sync(const epee::wipeable_string &wallet_password, const crypto::secret_key &spend_secret_key) -+{ -+ MDEBUG("Stopping background sync"); -+ -+ // Verify provided password and spend secret key. If no spend secret key is -+ // provided, recover it from the wallet keys file -+ crypto::secret_key recovered_spend_key = crypto::null_skey; -+ if (!m_wallet_file.empty()) -+ { -+ THROW_WALLET_EXCEPTION_IF(!verify_password(wallet_password, recovered_spend_key), error::invalid_password); -+ } -+ else -+ { -+ verify_password_with_cached_key(wallet_password); -+ } -+ -+ if (spend_secret_key != crypto::null_skey) -+ { -+ THROW_WALLET_EXCEPTION_IF(!m_wallet_file.empty() && spend_secret_key != recovered_spend_key, -+ error::invalid_spend_key); -+ MDEBUG("Setting spend secret key with the provided key"); -+ recovered_spend_key = spend_secret_key; -+ } -+ -+ // Verify private spend key derives to wallet's public spend key -+ const auto verify_spend_key = [this](crypto::secret_key &recovered_spend_key) -> bool -+ { -+ crypto::public_key spend_public_key; -+ return recovered_spend_key != crypto::null_skey && -+ crypto::secret_key_to_public_key(recovered_spend_key, spend_public_key) && -+ m_account.get_keys().m_account_address.m_spend_public_key == spend_public_key; -+ }; -+ THROW_WALLET_EXCEPTION_IF(!verify_spend_key(recovered_spend_key), error::invalid_spend_key); -+ -+ THROW_WALLET_EXCEPTION_IF(m_background_sync_type == BackgroundSyncOff, error::wallet_internal_error, -+ "must setup background sync first before using background sync"); -+ THROW_WALLET_EXCEPTION_IF(m_is_background_wallet, error::wallet_internal_error, -+ "Can't stop background syncing from a background wallet"); -+ -+ if (!m_background_syncing) -+ return; -+ -+ // Copy background cache, we're about to overwrite it -+ const background_sync_data_t background_sync_data = m_background_sync_data; -+ const hashchain background_synced_chain = m_blockchain; -+ const uint64_t last_block_reward = m_last_block_reward; -+ -+ if (m_background_sync_type == BackgroundSyncCustomPassword && !m_wallet_file.empty()) -+ { -+ // Reload the wallet from disk -+ load(m_wallet_file, wallet_password); -+ THROW_WALLET_EXCEPTION_IF(!verify_spend_key(recovered_spend_key), error::invalid_spend_key); -+ } -+ m_background_syncing = false; -+ -+ // Set the plaintext spend key -+ m_account.set_spend_key(recovered_spend_key); -+ -+ // Encrypt the spend key when done if needed -+ epee::misc_utils::auto_scope_leave_caller keys_reencryptor; -+ if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only) -+ keys_reencryptor = epee::misc_utils::create_scope_leave_handler([&, this]{encrypt_keys(wallet_password);}); -+ -+ // Now we can use the decrypted spend key to process background cache -+ process_background_cache(background_sync_data, background_synced_chain, last_block_reward); -+ -+ // Reset the background cache after processing -+ reset_background_sync_data(m_background_sync_data); -+ -+ MDEBUG("Background sync stopped"); -+} -+//---------------------------------------------------------------------------------------------------- - wallet2::payment_container wallet2::export_payments() const - { - payment_container payments; -diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h -index b620ea265..632298726 100644 ---- a/src/wallet/wallet2.h -+++ b/src/wallet/wallet2.h -@@ -256,6 +256,20 @@ private: - BackgroundMiningNo = 2, - }; - -+ enum BackgroundSyncType { -+ BackgroundSyncOff = 0, -+ BackgroundSyncReusePassword = 1, -+ BackgroundSyncCustomPassword = 2, -+ }; -+ -+ static BackgroundSyncType background_sync_type_from_str(const std::string &background_sync_type_str) -+ { -+ if (background_sync_type_str == "off") return BackgroundSyncOff; -+ if (background_sync_type_str == "reuse-wallet-password") return BackgroundSyncReusePassword; -+ if (background_sync_type_str == "custom-background-password") return BackgroundSyncCustomPassword; -+ throw std::logic_error("Unknown background sync type"); -+ }; -+ - enum ExportFormat { - Binary = 0, - Ascii, -@@ -282,7 +296,12 @@ private: - //! Just parses variables. - static std::unique_ptr make_dummy(const boost::program_options::variables_map& vm, bool unattended, const std::function(const char *, bool)> &password_prompter); - -- static bool verify_password(const std::string& keys_file_name, const epee::wipeable_string& password, bool no_spend_key, hw::device &hwdev, uint64_t kdf_rounds); -+ static bool verify_password(const std::string& keys_file_name, const epee::wipeable_string& password, bool no_spend_key, hw::device &hwdev, uint64_t kdf_rounds) -+ { -+ crypto::secret_key spend_key = crypto::null_skey; -+ return verify_password(keys_file_name, password, no_spend_key, hwdev, kdf_rounds, spend_key); -+ }; -+ static bool verify_password(const std::string& keys_file_name, const epee::wipeable_string& password, bool no_spend_key, hw::device &hwdev, uint64_t kdf_rounds, crypto::secret_key &spend_key_out); - 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())); -@@ -792,6 +811,54 @@ private: - END_SERIALIZE() - }; - -+ struct background_synced_tx_t -+ { -+ uint64_t index_in_background_sync_data; -+ cryptonote::transaction tx; -+ std::vector output_indices; -+ uint64_t height; -+ uint64_t block_timestamp; -+ bool double_spend_seen; -+ -+ BEGIN_SERIALIZE_OBJECT() -+ VERSION_FIELD(0) -+ VARINT_FIELD(index_in_background_sync_data) -+ -+ // prune tx; don't need to keep signature data -+ if (!tx.serialize_base(ar)) -+ return false; -+ -+ FIELD(output_indices) -+ VARINT_FIELD(height) -+ VARINT_FIELD(block_timestamp) -+ FIELD(double_spend_seen) -+ END_SERIALIZE() -+ }; -+ -+ struct background_sync_data_t -+ { -+ bool first_refresh_done = false; -+ uint64_t start_height = 0; -+ serializable_unordered_map txs; -+ -+ // Relevant wallet settings -+ uint64_t wallet_refresh_from_block_height; -+ size_t subaddress_lookahead_major; -+ size_t subaddress_lookahead_minor; -+ RefreshType wallet_refresh_type; -+ -+ BEGIN_SERIALIZE_OBJECT() -+ VERSION_FIELD(0) -+ FIELD(first_refresh_done) -+ FIELD(start_height) -+ FIELD(txs) -+ FIELD(wallet_refresh_from_block_height) -+ VARINT_FIELD(subaddress_lookahead_major) -+ VARINT_FIELD(subaddress_lookahead_minor) -+ VARINT_FIELD(wallet_refresh_type) -+ END_SERIALIZE() -+ }; -+ - typedef std::tuple get_outs_entry; - - struct parsed_block -@@ -980,7 +1047,8 @@ private: - /*! - * \brief verifies given password is correct for default wallet keys file - */ -- bool verify_password(const epee::wipeable_string& password); -+ bool verify_password(const epee::wipeable_string& password) {crypto::secret_key key = crypto::null_skey; return verify_password(password, key);}; -+ bool verify_password(const epee::wipeable_string& password, crypto::secret_key &spend_key_out); - cryptonote::account_base& get_account(){return m_account;} - const cryptonote::account_base& get_account()const{return m_account;} - -@@ -1076,6 +1144,7 @@ private: - cryptonote::network_type nettype() const { return m_nettype; } - bool watch_only() const { return m_watch_only; } - bool multisig(bool *ready = NULL, uint32_t *threshold = NULL, uint32_t *total = NULL) const; -+ bool is_background_wallet() const { return m_is_background_wallet; } - 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()) const; -@@ -1283,11 +1352,17 @@ private: - return; - } - a & m_has_ever_refreshed_from_node; -+ if(ver < 31) -+ { -+ m_background_sync_data = background_sync_data_t{}; -+ return; -+ } -+ a & m_background_sync_data; - } - - BEGIN_SERIALIZE_OBJECT() - MAGIC_FIELD("wownero wallet cache") -- VERSION_FIELD(1) -+ VERSION_FIELD(2) - FIELD(m_blockchain) - FIELD(m_transfers) - FIELD(m_account_public_address) -@@ -1319,6 +1394,12 @@ private: - return true; - } - FIELD(m_has_ever_refreshed_from_node) -+ if (version < 2) -+ { -+ m_background_sync_data = background_sync_data_t{}; -+ return true; -+ } -+ FIELD(m_background_sync_data) - END_SERIALIZE() - - /*! -@@ -1334,6 +1415,8 @@ private: - * \return Whether path is valid format - */ - static bool wallet_valid_path_format(const std::string& file_path); -+ static std::string make_background_wallet_file_name(const std::string &wallet_file); -+ static std::string make_background_keys_file_name(const std::string &wallet_file); - static bool parse_long_payment_id(const std::string& payment_id_str, crypto::hash& payment_id); - 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); -@@ -1382,6 +1465,9 @@ private: - 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; } -+ BackgroundSyncType background_sync_type() const { return m_background_sync_type; } -+ void setup_background_sync(BackgroundSyncType background_sync_type, const epee::wipeable_string &wallet_password, const boost::optional &background_cache_password); -+ bool is_background_syncing() const { return m_background_syncing; } - 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; } -@@ -1696,6 +1782,9 @@ private: - uint64_t get_bytes_sent() const; - uint64_t get_bytes_received() const; - -+ void start_background_sync(); -+ void stop_background_sync(const epee::wipeable_string &wallet_password, const crypto::secret_key &spend_secret_key = crypto::null_skey); -+ - // MMS ------------------------------------------------------------------------------------------------- - mms::message_store& get_message_store() { return m_message_store; }; - const mms::message_store& get_message_store() const { return m_message_store; }; -@@ -1731,6 +1820,9 @@ private: - * \return Whether it was successful. - */ - bool store_keys(const std::string& keys_file_name, const epee::wipeable_string& password, bool watch_only = false); -+ bool store_keys(const std::string& keys_file_name, const crypto::chacha_key& key, bool watch_only = false, bool background_keys_file = false); -+ boost::optional get_keys_file_data(const crypto::chacha_key& key, bool watch_only = false, bool background_keys_file = false); -+ bool store_keys_file_data(const std::string& keys_file_name, wallet2::keys_file_data &keys_file_data, bool background_keys_file = false); - /*! - * \brief Load wallet keys information from wallet file. - * \param keys_file_name Name of wallet file -@@ -1744,6 +1836,7 @@ private: - */ - bool load_keys_buf(const std::string& keys_buf, const epee::wipeable_string& password); - bool load_keys_buf(const std::string& keys_buf, const epee::wipeable_string& password, boost::optional& keys_to_encrypt); -+ void load_wallet_cache(const bool use_fs, const std::string& cache_buf = ""); - void process_new_transaction(const crypto::hash &txid, const cryptonote::transaction& tx, const std::vector &o_indices, uint64_t height, uint8_t block_version, uint64_t ts, bool miner_tx, bool pool, bool double_spend_seen, const tx_cache_data &tx_cache_data, std::map, size_t> *output_tracker_cache = NULL, bool ignore_callbacks = false); - bool should_skip_block(const cryptonote::block &b, uint64_t height) const; - void process_new_blockchain_entry(const cryptonote::block& b, const cryptonote::block_complete_entry& bche, const parsed_block &parsed_block, const crypto::hash& bl_id, uint64_t height, const std::vector &tx_cache_data, size_t tx_cache_data_offset, std::map, size_t> *output_tracker_cache = NULL); -@@ -1752,6 +1845,15 @@ private: - void get_short_chain_history(std::list& ids, uint64_t granularity = 1) const; - bool clear(); - void clear_soft(bool keep_key_images=false); -+ /* -+ * clear_user_data clears data created by the user, which is mostly data -+ * that a view key cannot identify on chain. This function was initially -+ * added to ensure that a "background" wallet (a wallet that syncs with just -+ * a view key hot in memory) does not have any sensitive data loaded that it -+ * does not need in order to sync. Future devs should take care to ensure -+ * that this function deletes data that is not useful for background syncing -+ */ -+ void clear_user_data(); - void pull_blocks(bool first, bool try_incremental, uint64_t start_height, uint64_t& blocks_start_height, const std::list &short_chain_history, std::vector &blocks, std::vector &o_indices, uint64_t ¤t_height); - void pull_hashes(uint64_t start_height, uint64_t& blocks_start_height, const std::list &short_chain_history, std::vector &hashes); - void fast_refresh(uint64_t stop_height, uint64_t &blocks_start_height, std::list &short_chain_history, bool force = false); -@@ -1803,10 +1905,23 @@ private: - bool get_ring(const crypto::chacha_key &key, const crypto::key_image &key_image, std::vector &outs); - crypto::chacha_key get_ringdb_key(); - void setup_keys(const epee::wipeable_string &password); -+ const crypto::chacha_key get_cache_key(); -+ void verify_password_with_cached_key(const epee::wipeable_string &password); -+ void verify_password_with_cached_key(const crypto::chacha_key &key); - size_t get_transfer_details(const crypto::key_image &ki) const; - tx_entry_data get_tx_entries(const std::unordered_set &txids); - void sort_scan_tx_entries(std::vector &unsorted_tx_entries); - void process_scan_txs(const tx_entry_data &txs_to_scan, const tx_entry_data &txs_to_reprocess, const std::unordered_set &tx_hashes_to_reprocess, detached_blockchain_data &dbd); -+ void write_background_sync_wallet(const epee::wipeable_string &wallet_password, const epee::wipeable_string &background_cache_password); -+ void process_background_cache_on_open(); -+ void process_background_cache(const background_sync_data_t &background_sync_data, const hashchain &background_chain, uint64_t last_block_reward); -+ void reset_background_sync_data(background_sync_data_t &background_sync_data); -+ void store_background_cache(const crypto::chacha_key &custom_background_key, const bool do_reset_background_sync_data = true); -+ void store_background_keys(const crypto::chacha_key &custom_background_key); -+ -+ bool lock_background_keys_file(const std::string &background_keys_file); -+ bool unlock_background_keys_file(); -+ bool is_background_keys_file_locked() const; - - void register_devices(); - hw::device& lookup_device(const std::string & device_descriptor); -@@ -1922,6 +2037,8 @@ private: - uint64_t m_ignore_outputs_above; - uint64_t m_ignore_outputs_below; - bool m_track_uses; -+ bool m_is_background_wallet; -+ BackgroundSyncType m_background_sync_type; - bool m_show_wallet_name_when_locked; - uint32_t m_inactivity_lock_timeout; - BackgroundMiningSetupType m_setup_background_mining; -@@ -1967,6 +2084,7 @@ private: - - uint64_t m_last_block_reward; - std::unique_ptr m_keys_file_locker; -+ std::unique_ptr m_background_keys_file_locker; - - mms::message_store m_message_store; - bool m_original_keys_available; -@@ -1974,6 +2092,7 @@ private: - crypto::secret_key m_original_view_secret_key; - - crypto::chacha_key m_cache_key; -+ boost::optional m_custom_background_key = boost::none; - std::shared_ptr m_encrypt_keys_after_refresh; - - bool m_unattended; -@@ -1989,9 +2108,13 @@ private: - - static boost::mutex default_daemon_address_lock; - static std::string default_daemon_address; -+ -+ bool m_background_syncing; -+ bool m_processing_background_cache; -+ background_sync_data_t m_background_sync_data; - }; - } --BOOST_CLASS_VERSION(tools::wallet2, 30) -+BOOST_CLASS_VERSION(tools::wallet2, 31) - 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) -@@ -2007,6 +2130,8 @@ BOOST_CLASS_VERSION(tools::wallet2::signed_tx_set, 1) - BOOST_CLASS_VERSION(tools::wallet2::tx_construction_data, 4) - BOOST_CLASS_VERSION(tools::wallet2::pending_tx, 3) - BOOST_CLASS_VERSION(tools::wallet2::multisig_sig, 1) -+BOOST_CLASS_VERSION(tools::wallet2::background_synced_tx_t, 0) -+BOOST_CLASS_VERSION(tools::wallet2::background_sync_data_t, 0) - - namespace boost - { -@@ -2505,6 +2630,29 @@ namespace boost - return; - a & x.multisig_sigs; - } -+ -+ template -+ inline void serialize(Archive& a, tools::wallet2::background_synced_tx_t &x, const boost::serialization::version_type ver) -+ { -+ a & x.index_in_background_sync_data; -+ a & x.tx; -+ a & x.output_indices; -+ a & x.height; -+ a & x.block_timestamp; -+ a & x.double_spend_seen; -+ } -+ -+ template -+ inline void serialize(Archive& a, tools::wallet2::background_sync_data_t &x, const boost::serialization::version_type ver) -+ { -+ a & x.first_refresh_done; -+ a & x.start_height; -+ a & x.txs.parent(); -+ a & x.wallet_refresh_from_block_height; -+ a & x.subaddress_lookahead_major; -+ a & x.subaddress_lookahead_minor; -+ a & x.wallet_refresh_type; -+ } - } - } - -diff --git a/src/wallet/wallet_errors.h b/src/wallet/wallet_errors.h -index c077313d4..c54cd3499 100644 ---- a/src/wallet/wallet_errors.h -+++ b/src/wallet/wallet_errors.h -@@ -63,6 +63,7 @@ namespace tools - // invalid_password - // invalid_priority - // invalid_multisig_seed -+ // invalid_spend_key - // refresh_error * - // acc_outs_lookup_error - // block_parse_error -@@ -97,6 +98,9 @@ namespace tools - // wallet_files_doesnt_correspond - // scan_tx_error * - // wont_reprocess_recent_txs_via_untrusted_daemon -+ // background_sync_error * -+ // background_wallet_already_open -+ // background_custom_password_same_as_wallet_password - // - // * - class with protected ctor - -@@ -304,6 +308,16 @@ namespace tools - std::string to_string() const { return wallet_logic_error::to_string(); } - }; - -+ struct invalid_spend_key : public wallet_logic_error -+ { -+ explicit invalid_spend_key(std::string&& loc) -+ : wallet_logic_error(std::move(loc), "invalid spend key") -+ { -+ } -+ -+ std::string to_string() const { return wallet_logic_error::to_string(); } -+ }; -+ - //---------------------------------------------------------------------------------------------------- - struct invalid_pregenerated_random : public wallet_logic_error - { -@@ -947,6 +961,31 @@ namespace tools - } - }; - //---------------------------------------------------------------------------------------------------- -+ struct background_sync_error : public wallet_logic_error -+ { -+ protected: -+ explicit background_sync_error(std::string&& loc, const std::string& message) -+ : wallet_logic_error(std::move(loc), message) -+ { -+ } -+ }; -+ //---------------------------------------------------------------------------------------------------- -+ struct background_wallet_already_open : public background_sync_error -+ { -+ explicit background_wallet_already_open(std::string&& loc, const std::string& background_wallet_file) -+ : background_sync_error(std::move(loc), "background wallet " + background_wallet_file + " is already opened by another wallet program") -+ { -+ } -+ }; -+ //---------------------------------------------------------------------------------------------------- -+ struct background_custom_password_same_as_wallet_password : public background_sync_error -+ { -+ explicit background_custom_password_same_as_wallet_password(std::string&& loc) -+ : background_sync_error(std::move(loc), "custom background password must be different than wallet password") -+ { -+ } -+ }; -+ //---------------------------------------------------------------------------------------------------- - - #if !defined(_MSC_VER) - -diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp -index 1c7c45034..21e69ea0e 100644 ---- a/src/wallet/wallet_rpc_server.cpp -+++ b/src/wallet/wallet_rpc_server.cpp -@@ -73,6 +73,54 @@ using namespace epee; - } \ - } while(0) - -+#define CHECK_IF_BACKGROUND_SYNCING() \ -+ do \ -+ { \ -+ if (!m_wallet) { return not_open(er); } \ -+ if (m_wallet->is_background_wallet()) \ -+ { \ -+ er.code = WALLET_RPC_ERROR_CODE_IS_BACKGROUND_WALLET; \ -+ er.message = "This command is disabled for background wallets."; \ -+ return false; \ -+ } \ -+ if (m_wallet->is_background_syncing()) \ -+ { \ -+ er.code = WALLET_RPC_ERROR_CODE_IS_BACKGROUND_SYNCING; \ -+ er.message = "This command is disabled while background syncing. Stop background syncing to use this command."; \ -+ return false; \ -+ } \ -+ } while(0) -+ -+#define PRE_VALIDATE_BACKGROUND_SYNC() \ -+ do \ -+ { \ -+ if (!m_wallet) { return not_open(er); } \ -+ if (m_restricted) \ -+ { \ -+ er.code = WALLET_RPC_ERROR_CODE_DENIED; \ -+ er.message = "Command unavailable in restricted mode."; \ -+ return false; \ -+ } \ -+ if (m_wallet->key_on_device()) \ -+ { \ -+ er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; \ -+ er.message = "Command not supported by HW wallet"; \ -+ return false; \ -+ } \ -+ if (m_wallet->multisig()) \ -+ { \ -+ er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; \ -+ er.message = "Multisig wallet cannot enable background sync"; \ -+ return false; \ -+ } \ -+ if (m_wallet->watch_only()) \ -+ { \ -+ er.code = WALLET_RPC_ERROR_CODE_WATCH_ONLY; \ -+ er.message = "Watch-only wallet cannot enable background sync"; \ -+ return false; \ -+ } \ -+ } while (0) -+ - namespace - { - const command_line::arg_descriptor arg_rpc_bind_port = {"rpc-bind-port", "Sets bind port for server"}; -@@ -291,6 +339,9 @@ namespace tools - { - if (!m_wallet) - return; -+ // Background mining can be toggled from the main wallet -+ if (m_wallet->is_background_wallet() || m_wallet->is_background_syncing()) -+ return; - - tools::wallet2::BackgroundMiningSetupType setup = m_wallet->setup_background_mining(); - if (setup == tools::wallet2::BackgroundMiningNo) -@@ -581,6 +632,7 @@ namespace tools - bool wallet_rpc_server::on_create_address(const wallet_rpc::COMMAND_RPC_CREATE_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_CREATE_ADDRESS::response& res, epee::json_rpc::error& er, const connection_context *ctx) - { - if (!m_wallet) return not_open(er); -+ CHECK_IF_BACKGROUND_SYNCING(); - try - { - if (req.count < 1 || req.count > 64) { -@@ -618,6 +670,7 @@ namespace tools - bool wallet_rpc_server::on_label_address(const wallet_rpc::COMMAND_RPC_LABEL_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_LABEL_ADDRESS::response& res, epee::json_rpc::error& er, const connection_context *ctx) - { - if (!m_wallet) return not_open(er); -+ CHECK_IF_BACKGROUND_SYNCING(); - try - { - m_wallet->set_subaddress_label(req.index, req.label); -@@ -680,6 +733,7 @@ namespace tools - bool wallet_rpc_server::on_create_account(const wallet_rpc::COMMAND_RPC_CREATE_ACCOUNT::request& req, wallet_rpc::COMMAND_RPC_CREATE_ACCOUNT::response& res, epee::json_rpc::error& er, const connection_context *ctx) - { - if (!m_wallet) return not_open(er); -+ CHECK_IF_BACKGROUND_SYNCING(); - try - { - m_wallet->add_subaddress_account(req.label); -@@ -697,6 +751,7 @@ namespace tools - bool wallet_rpc_server::on_label_account(const wallet_rpc::COMMAND_RPC_LABEL_ACCOUNT::request& req, wallet_rpc::COMMAND_RPC_LABEL_ACCOUNT::response& res, epee::json_rpc::error& er, const connection_context *ctx) - { - if (!m_wallet) return not_open(er); -+ CHECK_IF_BACKGROUND_SYNCING(); - try - { - m_wallet->set_subaddress_label({req.account_index, 0}, req.label); -@@ -712,6 +767,7 @@ namespace tools - bool wallet_rpc_server::on_get_account_tags(const wallet_rpc::COMMAND_RPC_GET_ACCOUNT_TAGS::request& req, wallet_rpc::COMMAND_RPC_GET_ACCOUNT_TAGS::response& res, epee::json_rpc::error& er, const connection_context *ctx) - { - if (!m_wallet) return not_open(er); -+ CHECK_IF_BACKGROUND_SYNCING(); - const std::pair, std::vector> account_tags = m_wallet->get_account_tags(); - for (const std::pair& p : account_tags.first) - { -@@ -731,6 +787,7 @@ namespace tools - bool wallet_rpc_server::on_tag_accounts(const wallet_rpc::COMMAND_RPC_TAG_ACCOUNTS::request& req, wallet_rpc::COMMAND_RPC_TAG_ACCOUNTS::response& res, epee::json_rpc::error& er, const connection_context *ctx) - { - if (!m_wallet) return not_open(er); -+ CHECK_IF_BACKGROUND_SYNCING(); - try - { - m_wallet->set_account_tag(req.accounts, req.tag); -@@ -746,6 +803,7 @@ namespace tools - bool wallet_rpc_server::on_untag_accounts(const wallet_rpc::COMMAND_RPC_UNTAG_ACCOUNTS::request& req, wallet_rpc::COMMAND_RPC_UNTAG_ACCOUNTS::response& res, epee::json_rpc::error& er, const connection_context *ctx) - { - if (!m_wallet) return not_open(er); -+ CHECK_IF_BACKGROUND_SYNCING(); - try - { - m_wallet->set_account_tag(req.accounts, ""); -@@ -761,6 +819,7 @@ namespace tools - bool wallet_rpc_server::on_set_account_tag_description(const wallet_rpc::COMMAND_RPC_SET_ACCOUNT_TAG_DESCRIPTION::request& req, wallet_rpc::COMMAND_RPC_SET_ACCOUNT_TAG_DESCRIPTION::response& res, epee::json_rpc::error& er, const connection_context *ctx) - { - if (!m_wallet) return not_open(er); -+ CHECK_IF_BACKGROUND_SYNCING(); - try - { - m_wallet->set_account_tag_description(req.tag, req.description); -@@ -791,6 +850,7 @@ namespace tools - bool wallet_rpc_server::on_freeze(const wallet_rpc::COMMAND_RPC_FREEZE::request& req, wallet_rpc::COMMAND_RPC_FREEZE::response& res, epee::json_rpc::error& er, const connection_context *ctx) - { - if (!m_wallet) return not_open(er); -+ CHECK_IF_BACKGROUND_SYNCING(); - try - { - if (req.key_image.empty()) -@@ -819,6 +879,7 @@ namespace tools - bool wallet_rpc_server::on_thaw(const wallet_rpc::COMMAND_RPC_THAW::request& req, wallet_rpc::COMMAND_RPC_THAW::response& res, epee::json_rpc::error& er, const connection_context *ctx) - { - if (!m_wallet) return not_open(er); -+ CHECK_IF_BACKGROUND_SYNCING(); - try - { - if (req.key_image.empty()) -@@ -847,6 +908,7 @@ namespace tools - bool wallet_rpc_server::on_frozen(const wallet_rpc::COMMAND_RPC_FROZEN::request& req, wallet_rpc::COMMAND_RPC_FROZEN::response& res, epee::json_rpc::error& er, const connection_context *ctx) - { - if (!m_wallet) return not_open(er); -+ CHECK_IF_BACKGROUND_SYNCING(); - try - { - if (req.key_image.empty()) -@@ -874,6 +936,8 @@ namespace tools - //------------------------------------------------------------------------------------------------------------------------------ - bool wallet_rpc_server::validate_transfer(const std::list& destinations, const std::string& payment_id, std::vector& dsts, std::vector& extra, bool at_least_one_destination, epee::json_rpc::error& er) - { -+ CHECK_IF_BACKGROUND_SYNCING(); -+ - crypto::hash8 integrated_payment_id = crypto::null_hash8; - std::string extra_nonce; - for (auto it = destinations.begin(); it != destinations.end(); it++) -@@ -1203,6 +1267,7 @@ namespace tools - } - - CHECK_MULTISIG_ENABLED(); -+ CHECK_IF_BACKGROUND_SYNCING(); - - cryptonote::blobdata blob; - if (!epee::string_tools::parse_hexstr_to_binbuff(req.unsigned_txset, blob)) -@@ -1284,6 +1349,7 @@ namespace tools - er.message = "command not supported by watch-only wallet"; - return false; - } -+ CHECK_IF_BACKGROUND_SYNCING(); - if(req.unsigned_txset.empty() && req.multisig_txset.empty()) - { - er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; -@@ -1553,6 +1619,7 @@ namespace tools - } - - CHECK_MULTISIG_ENABLED(); -+ CHECK_IF_BACKGROUND_SYNCING(); - - try - { -@@ -2114,6 +2181,7 @@ namespace tools - er.message = "The wallet is watch-only. Cannot retrieve seed."; - return false; - } -+ CHECK_IF_BACKGROUND_SYNCING(); - if (!m_wallet->is_deterministic()) - { - er.code = WALLET_RPC_ERROR_CODE_NON_DETERMINISTIC; -@@ -2142,6 +2210,7 @@ namespace tools - er.message = "The wallet is watch-only. Cannot retrieve spend key."; - return false; - } -+ CHECK_IF_BACKGROUND_SYNCING(); - epee::wipeable_string key = epee::to_hex::wipeable_string(m_wallet->get_account().get_keys().m_spend_secret_key); - res.key = std::string(key.data(), key.size()); - } -@@ -2163,6 +2232,7 @@ namespace tools - er.message = "Command unavailable in restricted mode."; - return false; - } -+ CHECK_IF_BACKGROUND_SYNCING(); - - try - { -@@ -2176,6 +2246,79 @@ namespace tools - return true; - } - //------------------------------------------------------------------------------------------------------------------------------ -+ bool wallet_rpc_server::on_setup_background_sync(const wallet_rpc::COMMAND_RPC_SETUP_BACKGROUND_SYNC::request& req, wallet_rpc::COMMAND_RPC_SETUP_BACKGROUND_SYNC::response& res, epee::json_rpc::error& er, const connection_context *ctx) -+ { -+ try -+ { -+ PRE_VALIDATE_BACKGROUND_SYNC(); -+ const tools::wallet2::BackgroundSyncType background_sync_type = tools::wallet2::background_sync_type_from_str(req.background_sync_type); -+ boost::optional background_cache_password = boost::none; -+ if (background_sync_type == tools::wallet2::BackgroundSyncCustomPassword) -+ background_cache_password = boost::optional(req.background_cache_password); -+ m_wallet->setup_background_sync(background_sync_type, req.wallet_password, background_cache_password); -+ } -+ catch (...) -+ { -+ handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR); -+ return false; -+ } -+ return true; -+ } -+ //------------------------------------------------------------------------------------------------------------------------------ -+ bool wallet_rpc_server::on_start_background_sync(const wallet_rpc::COMMAND_RPC_START_BACKGROUND_SYNC::request& req, wallet_rpc::COMMAND_RPC_START_BACKGROUND_SYNC::response& res, epee::json_rpc::error& er, const connection_context *ctx) -+ { -+ try -+ { -+ PRE_VALIDATE_BACKGROUND_SYNC(); -+ m_wallet->start_background_sync(); -+ } -+ catch (...) -+ { -+ handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR); -+ return false; -+ } -+ return true; -+ } -+ //------------------------------------------------------------------------------------------------------------------------------ -+ bool wallet_rpc_server::on_stop_background_sync(const wallet_rpc::COMMAND_RPC_STOP_BACKGROUND_SYNC::request& req, wallet_rpc::COMMAND_RPC_STOP_BACKGROUND_SYNC::response& res, epee::json_rpc::error& er, const connection_context *ctx) -+ { -+ try -+ { -+ PRE_VALIDATE_BACKGROUND_SYNC(); -+ crypto::secret_key spend_secret_key = crypto::null_skey; -+ -+ // Load the spend key from seed if seed is provided -+ if (!req.seed.empty()) -+ { -+ crypto::secret_key recovery_key; -+ std::string language; -+ -+ if (!crypto::ElectrumWords::words_to_bytes(req.seed, recovery_key, language)) -+ { -+ er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; -+ er.message = "Electrum-style word list failed verification"; -+ return false; -+ } -+ -+ if (!req.seed_offset.empty()) -+ recovery_key = cryptonote::decrypt_key(recovery_key, req.seed_offset); -+ -+ // generate spend key -+ cryptonote::account_base account; -+ account.generate(recovery_key, true, false); -+ spend_secret_key = account.get_keys().m_spend_secret_key; -+ } -+ -+ m_wallet->stop_background_sync(req.wallet_password, spend_secret_key); -+ } -+ catch (...) -+ { -+ handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR); -+ return false; -+ } -+ return true; -+ } -+ //------------------------------------------------------------------------------------------------------------------------------ - bool wallet_rpc_server::on_sign(const wallet_rpc::COMMAND_RPC_SIGN::request& req, wallet_rpc::COMMAND_RPC_SIGN::response& res, epee::json_rpc::error& er, const connection_context *ctx) - { - if (!m_wallet) return not_open(er); -@@ -2185,6 +2328,7 @@ namespace tools - er.message = "Command unavailable in restricted mode."; - return false; - } -+ CHECK_IF_BACKGROUND_SYNCING(); - - tools::wallet2::message_signature_type_t signature_type = tools::wallet2::sign_with_spend_key; - if (req.signature_type == "spend" || req.signature_type == "") -@@ -2277,6 +2421,7 @@ namespace tools - er.message = "Command unavailable in restricted mode."; - return false; - } -+ CHECK_IF_BACKGROUND_SYNCING(); - - if (req.txids.size() != req.notes.size()) - { -@@ -2349,6 +2494,7 @@ namespace tools - er.message = "Command unavailable in restricted mode."; - return false; - } -+ CHECK_IF_BACKGROUND_SYNCING(); - - m_wallet->set_attribute(req.key, req.value); - -@@ -2376,6 +2522,7 @@ namespace tools - bool wallet_rpc_server::on_get_tx_key(const wallet_rpc::COMMAND_RPC_GET_TX_KEY::request& req, wallet_rpc::COMMAND_RPC_GET_TX_KEY::response& res, epee::json_rpc::error& er, const connection_context *ctx) - { - if (!m_wallet) return not_open(er); -+ CHECK_IF_BACKGROUND_SYNCING(); - - crypto::hash txid; - if (!epee::string_tools::hex_to_pod(req.txid, txid)) -@@ -2467,6 +2614,7 @@ namespace tools - bool wallet_rpc_server::on_get_tx_proof(const wallet_rpc::COMMAND_RPC_GET_TX_PROOF::request& req, wallet_rpc::COMMAND_RPC_GET_TX_PROOF::response& res, epee::json_rpc::error& er, const connection_context *ctx) - { - if (!m_wallet) return not_open(er); -+ CHECK_IF_BACKGROUND_SYNCING(); - - crypto::hash txid; - if (!epee::string_tools::hex_to_pod(req.txid, txid)) -@@ -2583,6 +2731,7 @@ namespace tools - bool wallet_rpc_server::on_get_reserve_proof(const wallet_rpc::COMMAND_RPC_GET_RESERVE_PROOF::request& req, wallet_rpc::COMMAND_RPC_GET_RESERVE_PROOF::response& res, epee::json_rpc::error& er, const connection_context *ctx) - { - if (!m_wallet) return not_open(er); -+ CHECK_IF_BACKGROUND_SYNCING(); - - boost::optional> account_minreserve; - if (!req.all) -@@ -2825,6 +2974,7 @@ namespace tools - er.message = "command not supported by HW wallet"; - return false; - } -+ CHECK_IF_BACKGROUND_SYNCING(); - - try - { -@@ -2854,6 +3004,7 @@ namespace tools - er.message = "command not supported by HW wallet"; - return false; - } -+ CHECK_IF_BACKGROUND_SYNCING(); - - cryptonote::blobdata blob; - if (!epee::string_tools::parse_hexstr_to_binbuff(req.outputs_data_hex, blob)) -@@ -2879,6 +3030,7 @@ namespace tools - bool wallet_rpc_server::on_export_key_images(const wallet_rpc::COMMAND_RPC_EXPORT_KEY_IMAGES::request& req, wallet_rpc::COMMAND_RPC_EXPORT_KEY_IMAGES::response& res, epee::json_rpc::error& er, const connection_context *ctx) - { - if (!m_wallet) return not_open(er); -+ CHECK_IF_BACKGROUND_SYNCING(); - try - { - std::pair>> ski = m_wallet->export_key_images(req.all); -@@ -2915,6 +3067,7 @@ namespace tools - er.message = "This command requires a trusted daemon."; - return false; - } -+ CHECK_IF_BACKGROUND_SYNCING(); - try - { - std::vector> ski; -@@ -2983,6 +3136,7 @@ namespace tools - bool wallet_rpc_server::on_get_address_book(const wallet_rpc::COMMAND_RPC_GET_ADDRESS_BOOK_ENTRY::request& req, wallet_rpc::COMMAND_RPC_GET_ADDRESS_BOOK_ENTRY::response& res, epee::json_rpc::error& er, const connection_context *ctx) - { - if (!m_wallet) return not_open(er); -+ CHECK_IF_BACKGROUND_SYNCING(); - const auto ab = m_wallet->get_address_book(); - if (req.entries.empty()) - { -@@ -3028,6 +3182,7 @@ namespace tools - er.message = "Command unavailable in restricted mode."; - return false; - } -+ CHECK_IF_BACKGROUND_SYNCING(); - - cryptonote::address_parse_info info; - er.message = ""; -@@ -3070,6 +3225,7 @@ namespace tools - er.message = "Command unavailable in restricted mode."; - return false; - } -+ CHECK_IF_BACKGROUND_SYNCING(); - - const auto ab = m_wallet->get_address_book(); - if (req.index >= ab.size()) -@@ -3132,6 +3288,7 @@ namespace tools - er.message = "Command unavailable in restricted mode."; - return false; - } -+ CHECK_IF_BACKGROUND_SYNCING(); - - const auto ab = m_wallet->get_address_book(); - if (req.index >= ab.size()) -@@ -3202,6 +3359,7 @@ namespace tools - er.message = "Command unavailable in restricted mode."; - return false; - } -+ CHECK_IF_BACKGROUND_SYNCING(); - - std::unordered_set txids; - std::list::const_iterator i = req.txids.begin(); -@@ -3241,6 +3399,7 @@ namespace tools - er.message = "Command unavailable in restricted mode."; - return false; - } -+ CHECK_IF_BACKGROUND_SYNCING(); - try - { - m_wallet->rescan_spent(); -@@ -3505,6 +3664,7 @@ namespace tools - er.message = "Command unavailable in restricted mode."; - return false; - } -+ CHECK_IF_BACKGROUND_SYNCING(); - if (m_wallet->verify_password(req.old_password)) - { - try -@@ -4032,6 +4192,7 @@ namespace tools - er.message = "wallet is watch-only and cannot be made multisig"; - return false; - } -+ CHECK_IF_BACKGROUND_SYNCING(); - - res.multisig_info = m_wallet->get_multisig_first_kex_msg(); - return true; -@@ -4059,6 +4220,7 @@ namespace tools - er.message = "wallet is watch-only and cannot be made multisig"; - return false; - } -+ CHECK_IF_BACKGROUND_SYNCING(); - - try - { -diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h -index 3308d1751..c2329aafe 100644 ---- a/src/wallet/wallet_rpc_server.h -+++ b/src/wallet/wallet_rpc_server.h -@@ -160,6 +160,9 @@ namespace tools - MAP_JON_RPC_WE("set_log_categories", on_set_log_categories, wallet_rpc::COMMAND_RPC_SET_LOG_CATEGORIES) - MAP_JON_RPC_WE("estimate_tx_size_and_weight", on_estimate_tx_size_and_weight, wallet_rpc::COMMAND_RPC_ESTIMATE_TX_SIZE_AND_WEIGHT) - MAP_JON_RPC_WE("get_version", on_get_version, wallet_rpc::COMMAND_RPC_GET_VERSION) -+ MAP_JON_RPC_WE("setup_background_sync", on_setup_background_sync, wallet_rpc::COMMAND_RPC_SETUP_BACKGROUND_SYNC) -+ MAP_JON_RPC_WE("start_background_sync", on_start_background_sync, wallet_rpc::COMMAND_RPC_START_BACKGROUND_SYNC) -+ MAP_JON_RPC_WE("stop_background_sync", on_stop_background_sync, wallet_rpc::COMMAND_RPC_STOP_BACKGROUND_SYNC) - END_JSON_RPC_MAP() - END_URI_MAP2() - -@@ -251,6 +254,9 @@ namespace tools - bool on_set_log_categories(const wallet_rpc::COMMAND_RPC_SET_LOG_CATEGORIES::request& req, wallet_rpc::COMMAND_RPC_SET_LOG_CATEGORIES::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL); - bool on_estimate_tx_size_and_weight(const wallet_rpc::COMMAND_RPC_ESTIMATE_TX_SIZE_AND_WEIGHT::request& req, wallet_rpc::COMMAND_RPC_ESTIMATE_TX_SIZE_AND_WEIGHT::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL); - bool on_get_version(const wallet_rpc::COMMAND_RPC_GET_VERSION::request& req, wallet_rpc::COMMAND_RPC_GET_VERSION::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL); -+ bool on_setup_background_sync(const wallet_rpc::COMMAND_RPC_SETUP_BACKGROUND_SYNC::request& req, wallet_rpc::COMMAND_RPC_SETUP_BACKGROUND_SYNC::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL); -+ bool on_start_background_sync(const wallet_rpc::COMMAND_RPC_START_BACKGROUND_SYNC::request& req, wallet_rpc::COMMAND_RPC_START_BACKGROUND_SYNC::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL); -+ bool on_stop_background_sync(const wallet_rpc::COMMAND_RPC_STOP_BACKGROUND_SYNC::request& req, wallet_rpc::COMMAND_RPC_STOP_BACKGROUND_SYNC::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL); - - //json rpc v2 - bool on_query_key(const wallet_rpc::COMMAND_RPC_QUERY_KEY::request& req, wallet_rpc::COMMAND_RPC_QUERY_KEY::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL); -diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h -index 2ce39f667..72a35eb73 100644 ---- a/src/wallet/wallet_rpc_server_commands_defs.h -+++ b/src/wallet/wallet_rpc_server_commands_defs.h -@@ -2698,5 +2698,69 @@ namespace wallet_rpc - typedef epee::misc_utils::struct_init response; - }; - -+ struct COMMAND_RPC_SETUP_BACKGROUND_SYNC -+ { -+ struct request_t -+ { -+ std::string background_sync_type; -+ std::string wallet_password; -+ std::string background_cache_password; -+ -+ BEGIN_KV_SERIALIZE_MAP() -+ KV_SERIALIZE(background_sync_type) -+ KV_SERIALIZE(wallet_password) -+ KV_SERIALIZE_OPT(background_cache_password, (std::string)"") -+ END_KV_SERIALIZE_MAP() -+ }; -+ typedef epee::misc_utils::struct_init request; -+ -+ struct response_t -+ { -+ BEGIN_KV_SERIALIZE_MAP() -+ END_KV_SERIALIZE_MAP() -+ }; -+ typedef epee::misc_utils::struct_init response; -+ }; -+ -+ struct COMMAND_RPC_START_BACKGROUND_SYNC -+ { -+ struct request_t -+ { -+ BEGIN_KV_SERIALIZE_MAP() -+ END_KV_SERIALIZE_MAP() -+ }; -+ typedef epee::misc_utils::struct_init request; -+ -+ struct response_t -+ { -+ BEGIN_KV_SERIALIZE_MAP() -+ END_KV_SERIALIZE_MAP() -+ }; -+ typedef epee::misc_utils::struct_init response; -+ }; -+ -+ struct COMMAND_RPC_STOP_BACKGROUND_SYNC -+ { -+ struct request_t -+ { -+ std::string wallet_password; -+ std::string seed; -+ std::string seed_offset; -+ -+ BEGIN_KV_SERIALIZE_MAP() -+ KV_SERIALIZE(wallet_password) -+ KV_SERIALIZE_OPT(seed, (std::string)"") -+ KV_SERIALIZE_OPT(seed_offset, (std::string)"") -+ END_KV_SERIALIZE_MAP() -+ }; -+ typedef epee::misc_utils::struct_init request; -+ -+ struct response_t -+ { -+ BEGIN_KV_SERIALIZE_MAP() -+ END_KV_SERIALIZE_MAP() -+ }; -+ typedef epee::misc_utils::struct_init response; -+ }; - } - } -diff --git a/src/wallet/wallet_rpc_server_error_codes.h b/src/wallet/wallet_rpc_server_error_codes.h -index 541d29f86..4756c191c 100644 ---- a/src/wallet/wallet_rpc_server_error_codes.h -+++ b/src/wallet/wallet_rpc_server_error_codes.h -@@ -81,3 +81,5 @@ - #define WALLET_RPC_ERROR_CODE_DISABLED -48 - #define WALLET_RPC_ERROR_CODE_PROXY_ALREADY_DEFINED -49 - #define WALLET_RPC_ERROR_CODE_NONZERO_UNLOCK_TIME -50 -+#define WALLET_RPC_ERROR_CODE_IS_BACKGROUND_WALLET -51 -+#define WALLET_RPC_ERROR_CODE_IS_BACKGROUND_SYNCING -52 -diff --git a/tests/functional_tests/transfer.py b/tests/functional_tests/transfer.py -index 4063911f4..60eb09a10 100755 ---- a/tests/functional_tests/transfer.py -+++ b/tests/functional_tests/transfer.py -@@ -30,6 +30,7 @@ - - from __future__ import print_function - import json -+import util_resources - import pprint - from deepdiff import DeepDiff - pp = pprint.PrettyPrinter(indent=2) -@@ -46,6 +47,17 @@ seeds = [ - 'dilute gutter certain antics pamphlet macro enjoy left slid guarded bogeys upload nineteen bomb jubilee enhanced irritate turnip eggs swung jukebox loudly reduce sedan slid', - ] - -+def diff_transfers(actual_transfers, expected_transfers, ignore_order = True): -+ # The payments containers aren't ordered; re-scanning can lead to diff orders -+ diff = DeepDiff(actual_transfers, expected_transfers, ignore_order = ignore_order) -+ if diff != {}: -+ pp.pprint(diff) -+ assert diff == {} -+ -+def diff_incoming_transfers(actual_transfers, expected_transfers): -+ # wallet2 m_transfers container is ordered and order should be the same across rescans -+ diff_transfers(actual_transfers, expected_transfers, ignore_order = False) -+ - class TransferTest(): - def run_test(self): - self.reset() -@@ -63,6 +75,8 @@ class TransferTest(): - self.check_is_key_image_spent() - self.check_scan_tx() - self.check_subtract_fee_from_outputs() -+ self.check_background_sync() -+ self.check_background_sync_reorg_recovery() - - def reset(self): - print('Resetting blockchain') -@@ -840,12 +854,6 @@ class TransferTest(): - - print('Testing scan_tx') - -- def diff_transfers(actual_transfers, expected_transfers): -- diff = DeepDiff(actual_transfers, expected_transfers) -- if diff != {}: -- pp.pprint(diff) -- assert diff == {} -- - # set up sender_wallet - sender_wallet = self.wallet[0] - try: sender_wallet.close_wallet() -@@ -1127,5 +1135,385 @@ class TransferTest(): - except AssertionError: - pass - -+ def check_background_sync(self): -+ daemon = Daemon() -+ -+ print('Testing background sync') -+ -+ # Some helper functions -+ def stop_with_wrong_inputs(wallet, wallet_password, seed = ''): -+ invalid = False -+ try: wallet.stop_background_sync(wallet_password = wallet_password, seed = seed) -+ except: invalid = True -+ assert invalid -+ -+ def open_with_wrong_password(wallet, filename, password): -+ invalid_password = False -+ try: wallet.open_wallet(filename, password = password) -+ except: invalid_password = True -+ assert invalid_password -+ -+ def restore_wallet(wallet, seed, filename = '', password = ''): -+ wallet.close_wallet() -+ if filename != '': -+ util_resources.remove_wallet_files(filename) -+ wallet.restore_deterministic_wallet(seed = seed, filename = filename, password = password) -+ wallet.auto_refresh(enable = False) -+ assert wallet.get_transfers() == {} -+ -+ def assert_correct_transfers(wallet, expected_transfers, expected_inc_transfers, expected_balance): -+ diff_transfers(wallet.get_transfers(), expected_transfers) -+ diff_incoming_transfers(wallet.incoming_transfers(transfer_type = 'all'), expected_inc_transfers) -+ assert wallet.get_balance().balance == expected_balance -+ -+ # Set up sender_wallet. Prepare to sweep single output to receiver. -+ # We're testing a sweep because it makes sure background sync can -+ # properly pick up txs which do not have a change output back to sender. -+ sender_wallet = self.wallet[0] -+ try: sender_wallet.close_wallet() -+ except: pass -+ sender_wallet.restore_deterministic_wallet(seed = seeds[0]) -+ sender_wallet.auto_refresh(enable = False) -+ sender_wallet.refresh() -+ res = sender_wallet.incoming_transfers(transfer_type = 'available') -+ unlocked = [x for x in res.transfers if x.unlocked and x.amount > 0] -+ assert len(unlocked) > 0 -+ ki = unlocked[0].key_image -+ amount = unlocked[0].amount -+ spent_txid = unlocked[0].tx_hash -+ sender_wallet.refresh() -+ res = sender_wallet.get_transfers() -+ out_len = 0 if 'out' not in res else len(res.out) -+ sender_starting_balance = sender_wallet.get_balance().balance -+ -+ # Background sync type options -+ reuse_password = sender_wallet.background_sync_options.reuse_password -+ custom_password = sender_wallet.background_sync_options.custom_password -+ -+ # set up receiver_wallet -+ receiver_wallet = self.wallet[1] -+ try: receiver_wallet.close_wallet() -+ except: pass -+ receiver_wallet.restore_deterministic_wallet(seed = seeds[1]) -+ receiver_wallet.auto_refresh(enable = False) -+ receiver_wallet.refresh() -+ res = receiver_wallet.get_transfers() -+ in_len = 0 if 'in' not in res else len(res['in']) -+ receiver_starting_balance = receiver_wallet.get_balance().balance -+ -+ # transfer from sender_wallet to receiver_wallet -+ dst = '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW' -+ res = sender_wallet.sweep_single(dst, key_image = ki) -+ assert len(res.tx_hash) == 32*2 -+ txid = res.tx_hash -+ assert res.fee > 0 -+ fee = res.fee -+ assert res.amount == amount - fee -+ -+ expected_sender_balance = sender_starting_balance - amount -+ expected_receiver_balance = receiver_starting_balance + (amount - fee) -+ -+ print('Checking background sync on outgoing wallet') -+ sender_wallet.setup_background_sync(background_sync_type = reuse_password) -+ sender_wallet.start_background_sync() -+ # Mine block to an uninvolved wallet -+ daemon.generateblocks('46r4nYSevkfBUMhuykdK3gQ98XDqDTYW1hNLaXNvjpsJaSbNtdXh1sKMsdVgqkaihChAzEy29zEDPMR3NHQvGoZCLGwTerK', 1) -+ # sender should still be able to scan the transfer normally because we -+ # spent an output that had a known key image -+ sender_wallet.refresh() -+ transfers = sender_wallet.get_transfers() -+ assert 'pending' not in transfers or len(transfers.pending) == 0 -+ assert 'pool' not in transfers or len (transfers.pool) == 0 -+ assert len(transfers.out) == out_len + 1 -+ tx = [x for x in transfers.out if x.txid == txid] -+ assert len(tx) == 1 -+ tx = tx[0] -+ assert tx.amount == amount - fee -+ assert tx.fee == fee -+ assert len(tx.destinations) == 1 -+ assert tx.destinations[0].amount == amount - fee -+ assert tx.destinations[0].address == dst -+ incoming_transfers = sender_wallet.incoming_transfers(transfer_type = 'all') -+ assert len([x for x in incoming_transfers.transfers if x.tx_hash == spent_txid and x.key_image == ki and x.spent]) == 1 -+ assert sender_wallet.get_balance().balance == expected_sender_balance -+ -+ # Restore and check background syncing outgoing wallet -+ restore_wallet(sender_wallet, seeds[0]) -+ sender_wallet.setup_background_sync(background_sync_type = reuse_password) -+ sender_wallet.start_background_sync() -+ sender_wallet.refresh() -+ for i, out_tx in enumerate(transfers.out): -+ if 'destinations' in out_tx: -+ del transfers.out[i]['destinations'] # destinations are not expected after wallet restore -+ # sender's balance should be higher because can't detect spends while -+ # background sync enabled, only receives -+ background_bal = sender_wallet.get_balance().balance -+ assert background_bal > expected_sender_balance -+ background_transfers = sender_wallet.get_transfers() -+ assert 'out' not in background_transfers or len(background_transfers.out) == 0 -+ assert 'in' in background_transfers and len(background_transfers['in']) > 0 -+ background_incoming_transfers = sender_wallet.incoming_transfers(transfer_type = 'all') -+ assert len(background_incoming_transfers) == len(incoming_transfers) -+ assert len([x for x in background_incoming_transfers.transfers if x.spent or x.key_image != '']) == 0 -+ assert len([x for x in background_incoming_transfers.transfers if x.tx_hash == spent_txid]) == 1 -+ -+ # Try to stop background sync with the wrong seed -+ stop_with_wrong_inputs(sender_wallet, wallet_password = '', seed = seeds[1]) -+ -+ # Stop background sync and check transfers update correctly -+ sender_wallet.stop_background_sync(wallet_password = '', seed = seeds[0]) -+ assert_correct_transfers(sender_wallet, transfers, incoming_transfers, expected_sender_balance) -+ -+ # Check stopping a wallet with wallet files saved to disk -+ for background_sync_type in [reuse_password, custom_password]: -+ restore_wallet(sender_wallet, seeds[0], 'test1', 'test_password') -+ background_cache_password = None if background_sync_type == reuse_password else 'background_password' -+ sender_wallet.setup_background_sync(background_sync_type = background_sync_type, wallet_password = 'test_password', background_cache_password = background_cache_password) -+ sender_wallet.start_background_sync() -+ sender_wallet.refresh() -+ assert_correct_transfers(sender_wallet, background_transfers, background_incoming_transfers, background_bal) -+ stop_with_wrong_inputs(sender_wallet, 'wrong_password') -+ sender_wallet.stop_background_sync(wallet_password = 'test_password') -+ assert_correct_transfers(sender_wallet, transfers, incoming_transfers, expected_sender_balance) -+ -+ # Close wallet while background syncing, then reopen -+ for background_sync_type in [reuse_password, custom_password]: -+ restore_wallet(sender_wallet, seeds[0], 'test1', 'test_password') -+ background_cache_password = None if background_sync_type == reuse_password else 'background_password' -+ sender_wallet.setup_background_sync(background_sync_type = background_sync_type, wallet_password = 'test_password', background_cache_password = background_cache_password) -+ sender_wallet.start_background_sync() -+ sender_wallet.refresh() -+ assert_correct_transfers(sender_wallet, background_transfers, background_incoming_transfers, background_bal) -+ sender_wallet.close_wallet() -+ open_with_wrong_password(sender_wallet, 'test1', 'wrong_password') -+ sender_wallet.open_wallet('test1', password = 'test_password') -+ # It should reopen with spend key loaded and correctly scan all transfers -+ assert_correct_transfers(sender_wallet, transfers, incoming_transfers, expected_sender_balance) -+ -+ # Close wallet while syncing normally, then reopen -+ for background_sync_type in [reuse_password, custom_password]: -+ restore_wallet(sender_wallet, seeds[0], 'test1', 'test_password') -+ background_cache_password = None if background_sync_type == reuse_password else 'background_password' -+ sender_wallet.setup_background_sync(background_sync_type = background_sync_type, wallet_password = 'test_password', background_cache_password = background_cache_password) -+ sender_wallet.refresh() -+ assert_correct_transfers(sender_wallet, transfers, incoming_transfers, expected_sender_balance) -+ sender_wallet.close_wallet() -+ open_with_wrong_password(sender_wallet, 'test1', 'wrong_password') -+ sender_wallet.open_wallet('test1', password = 'test_password') -+ assert_correct_transfers(sender_wallet, transfers, incoming_transfers, expected_sender_balance) -+ -+ # Create background cache using custom password, then use it to sync, then reopen main wallet -+ for background_cache_password in ['background_password', '']: -+ restore_wallet(sender_wallet, seeds[0], 'test1', 'test_password') -+ assert not util_resources.file_exists('test1.background') -+ assert not util_resources.file_exists('test1.background.keys') -+ sender_wallet.setup_background_sync(background_sync_type = custom_password, wallet_password = 'test_password', background_cache_password = background_cache_password) -+ assert util_resources.file_exists('test1.background') -+ assert util_resources.file_exists('test1.background.keys') -+ sender_wallet.close_wallet() -+ open_with_wrong_password(sender_wallet, 'test1.background', 'test_password') -+ sender_wallet.open_wallet('test1.background', password = background_cache_password) -+ sender_wallet.refresh() -+ assert_correct_transfers(sender_wallet, background_transfers, background_incoming_transfers, background_bal) -+ sender_wallet.close_wallet() -+ sender_wallet.open_wallet('test1', password = 'test_password') -+ assert_correct_transfers(sender_wallet, transfers, incoming_transfers, expected_sender_balance) -+ -+ # Check that main wallet keeps background cache encrypted with custom password in sync -+ restore_wallet(sender_wallet, seeds[0], 'test1', 'test_password') -+ sender_wallet.setup_background_sync(background_sync_type = background_sync_type, wallet_password = 'test_password', background_cache_password = 'background_password') -+ sender_wallet.refresh() -+ assert_correct_transfers(sender_wallet, transfers, incoming_transfers, expected_sender_balance) -+ sender_wallet.close_wallet() -+ sender_wallet.open_wallet('test1.background', password = 'background_password') -+ assert_correct_transfers(sender_wallet, transfers, incoming_transfers, expected_sender_balance) -+ -+ # Try using wallet password as custom background password -+ restore_wallet(sender_wallet, seeds[0], 'test1', 'test_password') -+ assert not util_resources.file_exists('test1.background') -+ assert not util_resources.file_exists('test1.background.keys') -+ same_password = False -+ try: sender_wallet.setup_background_sync(background_sync_type = custom_password, wallet_password = 'test_password', background_cache_password = 'test_password') -+ except: same_password = True -+ assert same_password -+ assert not util_resources.file_exists('test1.background') -+ assert not util_resources.file_exists('test1.background.keys') -+ -+ # Turn off background sync -+ for background_sync_type in [reuse_password, custom_password]: -+ restore_wallet(sender_wallet, seeds[0], 'test1', 'test_password') -+ background_cache_password = None if background_sync_type == reuse_password else 'background_password' -+ sender_wallet.setup_background_sync(background_sync_type = background_sync_type, wallet_password = 'test_password', background_cache_password = background_cache_password) -+ if background_sync_type == custom_password: -+ assert util_resources.file_exists('test1.background') -+ assert util_resources.file_exists('test1.background.keys') -+ sender_wallet.close_wallet() -+ assert util_resources.file_exists('test1.background') -+ assert util_resources.file_exists('test1.background.keys') -+ else: -+ assert not util_resources.file_exists('test1.background') -+ assert not util_resources.file_exists('test1.background.keys') -+ sender_wallet.close_wallet() -+ assert not util_resources.file_exists('test1.background') -+ assert not util_resources.file_exists('test1.background.keys') -+ sender_wallet.open_wallet('test1', password = 'test_password') -+ sender_wallet.setup_background_sync(background_sync_type = sender_wallet.background_sync_options.off, wallet_password = 'test_password') -+ assert not util_resources.file_exists('test1.background') -+ assert not util_resources.file_exists('test1.background.keys') -+ sender_wallet.close_wallet() -+ assert not util_resources.file_exists('test1.background') -+ assert not util_resources.file_exists('test1.background.keys') -+ sender_wallet.open_wallet('test1', password = 'test_password') -+ -+ # Sanity check against outgoing wallet restored at height 0 -+ sender_wallet.close_wallet() -+ sender_wallet.restore_deterministic_wallet(seed = seeds[0], restore_height = 0) -+ sender_wallet.refresh() -+ assert_correct_transfers(sender_wallet, transfers, incoming_transfers, expected_sender_balance) -+ -+ print('Checking background sync on incoming wallet') -+ receiver_wallet.setup_background_sync(background_sync_type = reuse_password) -+ receiver_wallet.start_background_sync() -+ receiver_wallet.refresh() -+ transfers = receiver_wallet.get_transfers() -+ assert 'pending' not in transfers or len(transfers.pending) == 0 -+ assert 'pool' not in transfers or len (transfers.pool) == 0 -+ assert len(transfers['in']) == in_len + 1 -+ tx = [x for x in transfers['in'] if x.txid == txid] -+ assert len(tx) == 1 -+ tx = tx[0] -+ assert tx.amount == amount - fee -+ assert tx.fee == fee -+ incoming_transfers = receiver_wallet.incoming_transfers(transfer_type = 'all') -+ assert len([x for x in incoming_transfers.transfers if x.tx_hash == txid and x.key_image == '' and not x.spent]) == 1 -+ assert receiver_wallet.get_balance().balance == expected_receiver_balance -+ -+ # Restore and check background syncing incoming wallet -+ restore_wallet(receiver_wallet, seeds[1]) -+ receiver_wallet.setup_background_sync(background_sync_type = reuse_password) -+ receiver_wallet.start_background_sync() -+ receiver_wallet.refresh() -+ if 'out' in transfers: -+ for i, out_tx in enumerate(transfers.out): -+ if 'destinations' in out_tx: -+ del transfers.out[i]['destinations'] # destinations are not expected after wallet restore -+ background_bal = receiver_wallet.get_balance().balance -+ assert background_bal >= expected_receiver_balance -+ background_transfers = receiver_wallet.get_transfers() -+ assert 'out' not in background_transfers or len(background_transfers.out) == 0 -+ assert 'in' in background_transfers and len(background_transfers['in']) > 0 -+ background_incoming_transfers = receiver_wallet.incoming_transfers(transfer_type = 'all') -+ assert len(background_incoming_transfers) == len(incoming_transfers) -+ assert len([x for x in background_incoming_transfers.transfers if x.spent or x.key_image != '']) == 0 -+ assert len([x for x in background_incoming_transfers.transfers if x.tx_hash == txid]) == 1 -+ -+ # Stop background sync and check transfers update correctly -+ receiver_wallet.stop_background_sync(wallet_password = '', seed = seeds[1]) -+ diff_transfers(receiver_wallet.get_transfers(), transfers) -+ incoming_transfers = receiver_wallet.incoming_transfers(transfer_type = 'all') -+ assert len(background_incoming_transfers) == len(incoming_transfers) -+ assert len([x for x in incoming_transfers.transfers if x.tx_hash == txid and x.key_image != '' and not x.spent]) == 1 -+ assert receiver_wallet.get_balance().balance == expected_receiver_balance -+ -+ # Check a fresh incoming wallet with wallet files saved to disk and encrypted with password -+ restore_wallet(receiver_wallet, seeds[1], 'test2', 'test_password') -+ receiver_wallet.setup_background_sync(background_sync_type = reuse_password, wallet_password = 'test_password') -+ receiver_wallet.start_background_sync() -+ receiver_wallet.refresh() -+ assert_correct_transfers(receiver_wallet, background_transfers, background_incoming_transfers, background_bal) -+ stop_with_wrong_inputs(receiver_wallet, 'wrong_password') -+ receiver_wallet.stop_background_sync(wallet_password = 'test_password') -+ assert_correct_transfers(receiver_wallet, transfers, incoming_transfers, expected_receiver_balance) -+ -+ # Close receiver's wallet while background sync is enabled then reopen -+ restore_wallet(receiver_wallet, seeds[1], 'test2', 'test_password') -+ receiver_wallet.setup_background_sync(background_sync_type = reuse_password, wallet_password = 'test_password') -+ receiver_wallet.start_background_sync() -+ receiver_wallet.refresh() -+ diff_transfers(receiver_wallet.get_transfers(), background_transfers) -+ diff_incoming_transfers(receiver_wallet.incoming_transfers(transfer_type = 'all'), background_incoming_transfers) -+ assert receiver_wallet.get_balance().balance == background_bal -+ receiver_wallet.close_wallet() -+ receiver_wallet.open_wallet('test2', password = 'test_password') -+ # It should reopen with spend key loaded and correctly scan all transfers -+ assert_correct_transfers(receiver_wallet, transfers, incoming_transfers, expected_receiver_balance) -+ -+ # Sanity check against incoming wallet restored at height 0 -+ receiver_wallet.close_wallet() -+ receiver_wallet.restore_deterministic_wallet(seed = seeds[1], restore_height = 0) -+ receiver_wallet.refresh() -+ assert_correct_transfers(receiver_wallet, transfers, incoming_transfers, expected_receiver_balance) -+ -+ # Clean up -+ util_resources.remove_wallet_files('test1') -+ util_resources.remove_wallet_files('test2') -+ for i in range(2): -+ self.wallet[i].close_wallet() -+ self.wallet[i].restore_deterministic_wallet(seed = seeds[i]) -+ -+ def check_background_sync_reorg_recovery(self): -+ daemon = Daemon() -+ -+ print('Testing background sync reorg recovery') -+ -+ # Disconnect daemon from peers -+ daemon.out_peers(0) -+ -+ # Background sync type options -+ sender_wallet = self.wallet[0] -+ reuse_password = sender_wallet.background_sync_options.reuse_password -+ custom_password = sender_wallet.background_sync_options.custom_password -+ -+ for background_sync_type in [reuse_password, custom_password]: -+ # Set up wallet saved to disk -+ sender_wallet.close_wallet() -+ util_resources.remove_wallet_files('test1') -+ sender_wallet.restore_deterministic_wallet(seed = seeds[0], filename = 'test1', password = '') -+ sender_wallet.auto_refresh(enable = False) -+ sender_wallet.refresh() -+ sender_starting_balance = sender_wallet.get_balance().balance -+ -+ # Send tx and mine a block -+ amount = 1000000000000 -+ assert sender_starting_balance > amount -+ dst = {'address': '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW', 'amount': amount} -+ res = sender_wallet.transfer([dst]) -+ assert len(res.tx_hash) == 32*2 -+ txid = res.tx_hash -+ -+ daemon.generateblocks('46r4nYSevkfBUMhuykdK3gQ98XDqDTYW1hNLaXNvjpsJaSbNtdXh1sKMsdVgqkaihChAzEy29zEDPMR3NHQvGoZCLGwTerK', 1) -+ -+ # Make sure the wallet can see the tx -+ sender_wallet.refresh() -+ transfers = sender_wallet.get_transfers() -+ assert 'pool' not in transfers or len (transfers.pool) == 0 -+ tx = [x for x in transfers.out if x.txid == txid] -+ assert len(tx) == 1 -+ tx = tx[0] -+ assert sender_wallet.get_balance().balance < (sender_starting_balance - amount) -+ -+ # Pop the block while background syncing -+ background_cache_password = None if background_sync_type == reuse_password else 'background_password' -+ sender_wallet.setup_background_sync(background_sync_type = background_sync_type, wallet_password = '', background_cache_password = background_cache_password) -+ sender_wallet.start_background_sync() -+ daemon.pop_blocks(1) -+ daemon.flush_txpool() -+ -+ daemon.generateblocks('46r4nYSevkfBUMhuykdK3gQ98XDqDTYW1hNLaXNvjpsJaSbNtdXh1sKMsdVgqkaihChAzEy29zEDPMR3NHQvGoZCLGwTerK', 1) -+ -+ # Make sure the wallet can no longer see the tx -+ sender_wallet.refresh() -+ sender_wallet.stop_background_sync(wallet_password = '', seed = seeds[0]) -+ transfers = sender_wallet.get_transfers() -+ no_tx = [x for x in transfers.out if x.txid == txid] -+ assert len(no_tx) == 0 -+ assert sender_wallet.get_balance().balance == sender_starting_balance -+ -+ # Clean up -+ daemon.out_peers(12) -+ util_resources.remove_wallet_files('test1') -+ self.wallet[0].close_wallet() -+ self.wallet[0].restore_deterministic_wallet(seed = seeds[0]) -+ - if __name__ == '__main__': - TransferTest().run_test() -diff --git a/tests/functional_tests/util_resources.py b/tests/functional_tests/util_resources.py -index e030312da..3ca6fdb86 100755 ---- a/tests/functional_tests/util_resources.py -+++ b/tests/functional_tests/util_resources.py -@@ -37,6 +37,8 @@ - from __future__ import print_function - import subprocess - import psutil -+import os -+import errno - - def available_ram_gb(): - ram_bytes = psutil.virtual_memory().available -@@ -51,3 +53,26 @@ def get_time_pi_seconds(cores, app_dir='.'): - miliseconds = int(decoded) - - return miliseconds / 1000.0 -+ -+def remove_file(name): -+ WALLET_DIRECTORY = os.environ['WALLET_DIRECTORY'] -+ assert WALLET_DIRECTORY != '' -+ try: -+ os.unlink(WALLET_DIRECTORY + '/' + name) -+ except OSError as e: -+ if e.errno != errno.ENOENT: -+ raise -+ -+def get_file_path(name): -+ WALLET_DIRECTORY = os.environ['WALLET_DIRECTORY'] -+ assert WALLET_DIRECTORY != '' -+ return WALLET_DIRECTORY + '/' + name -+ -+def remove_wallet_files(name): -+ for suffix in ['', '.keys', '.background', '.background.keys', '.address.txt']: -+ remove_file(name + suffix) -+ -+def file_exists(name): -+ WALLET_DIRECTORY = os.environ['WALLET_DIRECTORY'] -+ assert WALLET_DIRECTORY != '' -+ return os.path.isfile(WALLET_DIRECTORY + '/' + name) -diff --git a/tests/functional_tests/wallet.py b/tests/functional_tests/wallet.py -index 1ad05c98f..8182cecb2 100755 ---- a/tests/functional_tests/wallet.py -+++ b/tests/functional_tests/wallet.py -@@ -34,8 +34,7 @@ - - from __future__ import print_function - import sys --import os --import errno -+import util_resources - - from framework.wallet import Wallet - from framework.daemon import Daemon -@@ -54,24 +53,6 @@ class WalletTest(): - self.change_password() - self.store() - -- def remove_file(self, name): -- WALLET_DIRECTORY = os.environ['WALLET_DIRECTORY'] -- assert WALLET_DIRECTORY != '' -- try: -- os.unlink(WALLET_DIRECTORY + '/' + name) -- except OSError as e: -- if e.errno != errno.ENOENT: -- raise -- -- def remove_wallet_files(self, name): -- for suffix in ['', '.keys']: -- self.remove_file(name + suffix) -- -- def file_exists(self, name): -- WALLET_DIRECTORY = os.environ['WALLET_DIRECTORY'] -- assert WALLET_DIRECTORY != '' -- return os.path.isfile(WALLET_DIRECTORY + '/' + name) -- - def reset(self): - print('Resetting blockchain') - daemon = Daemon() -@@ -333,7 +314,7 @@ class WalletTest(): - try: wallet.close_wallet() - except: pass - -- self.remove_wallet_files('test1') -+ util_resources.remove_wallet_files('test1') - - 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.restore_deterministic_wallet(seed = seed, filename = 'test1') -@@ -359,7 +340,7 @@ class WalletTest(): - - wallet.close_wallet() - -- self.remove_wallet_files('test1') -+ util_resources.remove_wallet_files('test1') - - def store(self): - print('Testing store') -@@ -369,22 +350,26 @@ class WalletTest(): - try: wallet.close_wallet() - except: pass - -- self.remove_wallet_files('test1') -+ util_resources.remove_wallet_files('test1') - - 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.restore_deterministic_wallet(seed = seed, filename = 'test1') - assert res.address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm' - assert res.seed == seed - -- self.remove_file('test1') -- assert self.file_exists('test1.keys') -- assert not self.file_exists('test1') -+ util_resources.remove_file('test1') -+ assert util_resources.file_exists('test1.keys') -+ assert not util_resources.file_exists('test1') - wallet.store() -- assert self.file_exists('test1.keys') -- assert self.file_exists('test1') -+ assert util_resources.file_exists('test1.keys') -+ assert util_resources.file_exists('test1') - - wallet.close_wallet() -- self.remove_wallet_files('test1') -+ -+ wallet.open_wallet(filename = 'test1', password = '') -+ wallet.close_wallet() -+ -+ util_resources.remove_wallet_files('test1') - - - if __name__ == '__main__': -diff --git a/tests/unit_tests/wipeable_string.cpp b/tests/unit_tests/wipeable_string.cpp -index ef6964f9e..25121a02e 100644 ---- a/tests/unit_tests/wipeable_string.cpp -+++ b/tests/unit_tests/wipeable_string.cpp -@@ -211,3 +211,15 @@ TEST(wipeable_string, to_hex) - ASSERT_TRUE(epee::to_hex::wipeable_string(epee::span((const uint8_t*)"", 0)) == epee::wipeable_string("")); - ASSERT_TRUE(epee::to_hex::wipeable_string(epee::span((const uint8_t*)"abc", 3)) == epee::wipeable_string("616263")); - } -+ -+TEST(wipeable_string, to_string) -+{ -+ // Converting a wipeable_string to a string defeats the purpose of wipeable_string, -+ // but nice to know this works -+ std::string str; -+ { -+ epee::wipeable_string wipeable_str("foo"); -+ str = std::string(wipeable_str.data(), wipeable_str.size()); -+ } -+ ASSERT_TRUE(str == std::string("foo")); -+} -diff --git a/utils/python-rpc/framework/wallet.py b/utils/python-rpc/framework/wallet.py -index 1e10e1f86..bff33a561 100644 ---- a/utils/python-rpc/framework/wallet.py -+++ b/utils/python-rpc/framework/wallet.py -@@ -1138,3 +1138,45 @@ class Wallet(object): - 'id': '0' - } - return self.rpc.send_json_rpc_request(frozen) -+ -+ class BackgroundSyncOptions(object): -+ def __init__(self): -+ self.off = 'off' -+ self.reuse_password = 'reuse-wallet-password' -+ self.custom_password = 'custom-background-password' -+ background_sync_options = BackgroundSyncOptions() -+ -+ def setup_background_sync(self, background_sync_type = background_sync_options.off, wallet_password = '', background_cache_password = ''): -+ setup_background_sync = { -+ 'method': 'setup_background_sync', -+ 'jsonrpc': '2.0', -+ 'params' : { -+ 'background_sync_type': background_sync_type, -+ 'wallet_password': wallet_password, -+ 'background_cache_password': background_cache_password, -+ }, -+ 'id': '0' -+ } -+ return self.rpc.send_json_rpc_request(setup_background_sync) -+ -+ def start_background_sync(self): -+ start_background_sync = { -+ 'method': 'start_background_sync', -+ 'jsonrpc': '2.0', -+ 'params' : {}, -+ 'id': '0' -+ } -+ return self.rpc.send_json_rpc_request(start_background_sync) -+ -+ def stop_background_sync(self, wallet_password = '', seed = '', seed_offset = ''): -+ stop_background_sync = { -+ 'method': 'stop_background_sync', -+ 'jsonrpc': '2.0', -+ 'params' : { -+ 'wallet_password': wallet_password, -+ 'seed': seed, -+ 'seed_offset': seed_offset, -+ }, -+ 'id': '0' -+ } -+ return self.rpc.send_json_rpc_request(stop_background_sync) --- -2.48.0 - diff --git a/patches/wownero/0002-fix-is_trivially_copyable.patch b/patches/wownero/0002-fix-is_trivially_copyable.patch new file mode 100644 index 0000000..e6da812 --- /dev/null +++ b/patches/wownero/0002-fix-is_trivially_copyable.patch @@ -0,0 +1,32 @@ +From ab667a9b129984abbbc252963cf4f471c8fe29ba Mon Sep 17 00:00:00 2001 +From: cyan +Date: Tue, 22 Oct 2024 10:23:18 +0000 +Subject: [PATCH 02/16] fix is_trivially_copyable + +--- + contrib/epee/include/span.h | 2 -- + 1 file changed, 2 deletions(-) + +diff --git a/contrib/epee/include/span.h b/contrib/epee/include/span.h +index 01dc387d6..5e3af4d11 100644 +--- a/contrib/epee/include/span.h ++++ b/contrib/epee/include/span.h +@@ -151,7 +151,6 @@ namespace epee + using value_type = typename T::value_type; + static_assert(!std::is_empty(), "empty value types will not work -> sizeof == 1"); + static_assert(std::is_standard_layout(), "value type must have standard layout"); +- static_assert(std::is_trivially_copyable(), "value type must be trivially copyable"); + static_assert(alignof(value_type) == 1, "value type may have padding"); + return {reinterpret_cast(src.data()), src.size() * sizeof(value_type)}; + } +@@ -162,7 +161,6 @@ namespace epee + { + static_assert(!std::is_empty(), "empty types will not work -> sizeof == 1"); + static_assert(std::is_standard_layout(), "type must have standard layout"); +- static_assert(std::is_trivially_copyable(), "type must be trivially copyable"); + static_assert(alignof(T) == 1, "type may have padding"); + return {reinterpret_cast(std::addressof(src)), sizeof(T)}; + } +-- +2.51.0 + diff --git a/patches/wownero/0002-fix-missing-___clear_cache-when-targetting-iOS.patch b/patches/wownero/0002-fix-missing-___clear_cache-when-targetting-iOS.patch deleted file mode 100644 index ca9ccfd..0000000 --- a/patches/wownero/0002-fix-missing-___clear_cache-when-targetting-iOS.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 4828befb3843764eaaa5e5ea489cde6d101d71ce Mon Sep 17 00:00:00 2001 -From: Czarek Nakamoto -Date: Wed, 23 Oct 2024 15:18:21 +0200 -Subject: [PATCH 02/15] fix missing ___clear_cache when targetting iOS - ---- - external/randomwow | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/external/randomwow b/external/randomwow -index 27b099b6d..6f30d4b92 160000 ---- a/external/randomwow -+++ b/external/randomwow -@@ -1 +1 @@ --Subproject commit 27b099b6dd6fef6e17f58c6dfe00009e9c5df587 -+Subproject commit 6f30d4b924fecb231e5b683915cc75d18b3b5866 --- -2.48.0 - diff --git a/patches/wownero/0003-fix-is_trivially_copyable.patch b/patches/wownero/0003-fix-is_trivially_copyable.patch deleted file mode 100644 index 5b81f2d..0000000 --- a/patches/wownero/0003-fix-is_trivially_copyable.patch +++ /dev/null @@ -1,32 +0,0 @@ -From d4d406961073b5f9d3cc46a6cedaf73a378e1ea6 Mon Sep 17 00:00:00 2001 -From: cyan -Date: Tue, 22 Oct 2024 10:23:18 +0000 -Subject: [PATCH 03/15] fix is_trivially_copyable - ---- - contrib/epee/include/span.h | 2 -- - 1 file changed, 2 deletions(-) - -diff --git a/contrib/epee/include/span.h b/contrib/epee/include/span.h -index 01dc387d6..5e3af4d11 100644 ---- a/contrib/epee/include/span.h -+++ b/contrib/epee/include/span.h -@@ -151,7 +151,6 @@ namespace epee - using value_type = typename T::value_type; - static_assert(!std::is_empty(), "empty value types will not work -> sizeof == 1"); - static_assert(std::is_standard_layout(), "value type must have standard layout"); -- static_assert(std::is_trivially_copyable(), "value type must be trivially copyable"); - static_assert(alignof(value_type) == 1, "value type may have padding"); - return {reinterpret_cast(src.data()), src.size() * sizeof(value_type)}; - } -@@ -162,7 +161,6 @@ namespace epee - { - static_assert(!std::is_empty(), "empty types will not work -> sizeof == 1"); - static_assert(std::is_standard_layout(), "type must have standard layout"); -- static_assert(std::is_trivially_copyable(), "type must be trivially copyable"); - static_assert(alignof(T) == 1, "type may have padding"); - return {reinterpret_cast(std::addressof(src)), sizeof(T)}; - } --- -2.48.0 - diff --git a/patches/wownero/0003-store-crash-fix.patch b/patches/wownero/0003-store-crash-fix.patch new file mode 100644 index 0000000..942dd4b --- /dev/null +++ b/patches/wownero/0003-store-crash-fix.patch @@ -0,0 +1,212 @@ +From 45a4148e08f61fe6d85b7245e7ee445b8db97273 Mon Sep 17 00:00:00 2001 +From: Czarek Nakamoto +Date: Sat, 11 May 2024 16:25:10 +0200 +Subject: [PATCH 03/16] store crash fix + +Monero wallet crashes (sometimes) when it is syncing, +while the proper solution (that can be seen in feather) +is to not store wallet while it is being synced, this is not +acceptable for mobile wallets where OS can just come +and kill the wallet because it felt like it. + +This patch depends on the background-sync patch, but +to use it as a standalone fix grabbing the definition for the +LOCK_REFRESH macro should be enough. + +tobtoht suggested: +_say you want to store every 15 minutes during background sync. you stop the refresh every 15 minutes. then do something like this in the callback:_ + +``` +// Make sure this doesn't run in the refresh thread +onRefreshed() { + if (hasItBeen15MinutesSinceWeStored()) { + store(); + } + + if (shouldWeContinueRefreshing()) { + startRefresh(); + } +} +``` + +which works for crashes after the wallet is initially synced +but doesn't solve the issue for wallet that are syncing (it +would just wait for it to finish before actually storing). + +Also imo store() functin should store the wallet, no matter +the current state. +--- + external/randomwow | 2 +- + src/wallet/api/wallet.cpp | 23 +++++++++++------------ + src/wallet/api/wallet.h | 1 - + src/wallet/wallet2.cpp | 12 +++++++++++- + src/wallet/wallet2.h | 3 +++ + 5 files changed, 26 insertions(+), 15 deletions(-) + +diff --git a/external/randomwow b/external/randomwow +index 6f30d4b92..27b099b6d 160000 +--- a/external/randomwow ++++ b/external/randomwow +@@ -1 +1 @@ +-Subproject commit 6f30d4b924fecb231e5b683915cc75d18b3b5866 ++Subproject commit 27b099b6dd6fef6e17f58c6dfe00009e9c5df587 +diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp +index af420569c..d53372fc9 100644 +--- a/src/wallet/api/wallet.cpp ++++ b/src/wallet/api/wallet.cpp +@@ -55,8 +55,8 @@ using namespace cryptonote; + #define MONERO_DEFAULT_LOG_CATEGORY "WalletAPI" + + #define LOCK_REFRESH() \ +- bool refresh_enabled = m_refreshEnabled; \ +- m_refreshEnabled = false; \ ++ bool refresh_enabled = m_wallet->get_refresh_enabled(); \ ++ m_wallet->set_refresh_enabled(false); \ + m_wallet->stop(); \ + m_refreshCV.notify_one(); \ + boost::mutex::scoped_lock lock(m_refreshMutex); \ +@@ -466,7 +466,7 @@ WalletImpl::WalletImpl(NetworkType nettype, uint64_t kdf_rounds) + m_wallet2Callback.reset(new Wallet2CallbackImpl(this)); + m_wallet->callback(m_wallet2Callback.get()); + m_refreshThreadDone = false; +- m_refreshEnabled = false; ++ m_wallet->set_refresh_enabled(false); + m_addressBook.reset(new AddressBookImpl(this)); + m_subaddress.reset(new SubaddressImpl(this)); + m_subaddressAccount.reset(new SubaddressAccountImpl(this)); +@@ -962,6 +962,7 @@ void WalletImpl::stop() + bool WalletImpl::store(const std::string &path) + { + clearStatus(); ++ LOCK_REFRESH(); + try { + if (path.empty()) { + m_wallet->store(); +@@ -2449,10 +2450,10 @@ void WalletImpl::refreshThreadFunc() + } + + LOG_PRINT_L3(__FUNCTION__ << ": refresh lock acquired..."); +- LOG_PRINT_L3(__FUNCTION__ << ": m_refreshEnabled: " << m_refreshEnabled); ++ LOG_PRINT_L3(__FUNCTION__ << ": m_refreshEnabled: " << m_wallet->get_refresh_enabled()); + LOG_PRINT_L3(__FUNCTION__ << ": m_status: " << status()); + LOG_PRINT_L3(__FUNCTION__ << ": m_refreshShouldRescan: " << m_refreshShouldRescan); +- if (m_refreshEnabled) { ++ if (m_wallet->get_refresh_enabled()) { + LOG_PRINT_L3(__FUNCTION__ << ": refreshing..."); + doRefresh(); + } +@@ -2486,7 +2487,7 @@ void WalletImpl::doRefresh() + } catch (const std::exception &e) { + setStatusError(e.what()); + break; +- }while(!rescan && (rescan=m_refreshShouldRescan.exchange(false))); // repeat if not rescanned and rescan was requested ++ }while(m_wallet->get_refresh_enabled() && !rescan && (rescan=m_refreshShouldRescan.exchange(false))); // repeat if not rescanned and rescan was requested + + if (m_wallet2Callback->getListener()) { + m_wallet2Callback->getListener()->refreshed(); +@@ -2496,9 +2497,9 @@ void WalletImpl::doRefresh() + + void WalletImpl::startRefresh() + { +- if (!m_refreshEnabled) { ++ if (!m_wallet->get_refresh_enabled()) { + LOG_PRINT_L2(__FUNCTION__ << ": refresh started/resumed..."); +- m_refreshEnabled = true; ++ m_wallet->set_refresh_enabled(true); + m_refreshCV.notify_one(); + } + } +@@ -2508,7 +2509,7 @@ void WalletImpl::startRefresh() + void WalletImpl::stopRefresh() + { + if (!m_refreshThreadDone) { +- m_refreshEnabled = false; ++ m_wallet->set_refresh_enabled(false); + m_refreshThreadDone = true; + m_refreshCV.notify_one(); + m_refreshThread.join(); +@@ -2519,9 +2520,7 @@ void WalletImpl::pauseRefresh() + { + LOG_PRINT_L2(__FUNCTION__ << ": refresh paused..."); + // TODO synchronize access +- if (!m_refreshThreadDone) { +- m_refreshEnabled = false; +- } ++ m_wallet->set_refresh_enabled(false); + } + + +diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h +index 1f199a72c..ac7ce2f6a 100644 +--- a/src/wallet/api/wallet.h ++++ b/src/wallet/api/wallet.h +@@ -273,7 +273,6 @@ private: + std::unique_ptr m_subaddressAccount; + + // multi-threaded refresh stuff +- std::atomic m_refreshEnabled; + std::atomic m_refreshThreadDone; + std::atomic m_refreshIntervalMillis; + std::atomic m_refreshShouldRescan; +diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp +index f111ad0d5..8d8ba33c5 100644 +--- a/src/wallet/wallet2.cpp ++++ b/src/wallet/wallet2.cpp +@@ -1195,6 +1195,7 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended, std + m_upper_transaction_weight_limit(0), + m_run(true), + m_callback(0), ++ m_refreshEnabled(false), + m_trusted_daemon(false), + m_nettype(nettype), + m_multisig_rounds_passed(0), +@@ -1415,6 +1416,14 @@ bool wallet2::set_daemon(std::string daemon_address, boost::optionalset_proxy(address); +@@ -4151,8 +4160,9 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo + // infer when we get an incoming output + + bool first = true, last = false; +- while(m_run.load(std::memory_order_relaxed) && blocks_fetched < max_blocks) ++ while(m_run.load(std::memory_order_relaxed) && blocks_fetched < max_blocks && m_refreshEnabled) + { ++ LOG_ERROR("SYNCING"); + uint64_t next_blocks_start_height; + std::vector next_blocks; + std::vector next_parsed_blocks; +diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h +index 8ffad5675..a1d939a5a 100644 +--- a/src/wallet/wallet2.h ++++ b/src/wallet/wallet2.h +@@ -1078,6 +1078,8 @@ private: + epee::net_utils::ssl_options_t ssl_options = epee::net_utils::ssl_support_t::e_ssl_support_autodetect, + const std::string &proxy = ""); + bool set_proxy(const std::string &address); ++ bool get_refresh_enabled(); ++ void set_refresh_enabled(bool val); + + void stop() { m_run.store(false, std::memory_order_relaxed); m_message_store.stop(); } + +@@ -1996,6 +1998,7 @@ private: + + boost::recursive_mutex m_daemon_rpc_mutex; + ++ bool m_refreshEnabled; + bool m_trusted_daemon; + i_wallet2_callback* m_callback; + hw::device::device_type m_key_device_type; +-- +2.51.0 + diff --git a/patches/wownero/0004-store-crash-fix.patch b/patches/wownero/0004-store-crash-fix.patch deleted file mode 100644 index 52104dc..0000000 --- a/patches/wownero/0004-store-crash-fix.patch +++ /dev/null @@ -1,210 +0,0 @@ -From 799d1825696483da743ff2f92a773fcfbef08557 Mon Sep 17 00:00:00 2001 -From: Czarek Nakamoto -Date: Sat, 11 May 2024 16:25:10 +0200 -Subject: [PATCH 04/15] store crash fix - -Monero wallet crashes (sometimes) when it is syncing, -while the proper solution (that can be seen in feather) -is to not store wallet while it is being synced, this is not -acceptable for mobile wallets where OS can just come -and kill the wallet because it felt like it. - -This patch depends on the background-sync patch, but -to use it as a standalone fix grabbing the definition for the -LOCK_REFRESH macro should be enough. - -tobtoht suggested: -_say you want to store every 15 minutes during background sync. you stop the refresh every 15 minutes. then do something like this in the callback:_ - -``` -// Make sure this doesn't run in the refresh thread -onRefreshed() { - if (hasItBeen15MinutesSinceWeStored()) { - store(); - } - - if (shouldWeContinueRefreshing()) { - startRefresh(); - } -} -``` - -which works for crashes after the wallet is initially synced -but doesn't solve the issue for wallet that are syncing (it -would just wait for it to finish before actually storing). - -Also imo store() functin should store the wallet, no matter -the current state. ---- - src/wallet/api/wallet.cpp | 25 ++++++++++++------------- - src/wallet/api/wallet.h | 1 - - src/wallet/wallet2.cpp | 12 +++++++++++- - src/wallet/wallet2.h | 3 +++ - 4 files changed, 26 insertions(+), 15 deletions(-) - -diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp -index e868fa039..899ef044a 100644 ---- a/src/wallet/api/wallet.cpp -+++ b/src/wallet/api/wallet.cpp -@@ -55,8 +55,8 @@ using namespace cryptonote; - #define MONERO_DEFAULT_LOG_CATEGORY "WalletAPI" - - #define LOCK_REFRESH() \ -- bool refresh_enabled = m_refreshEnabled; \ -- m_refreshEnabled = false; \ -+ bool refresh_enabled = m_wallet->get_refresh_enabled(); \ -+ m_wallet->set_refresh_enabled(false); \ - m_wallet->stop(); \ - m_refreshCV.notify_one(); \ - boost::mutex::scoped_lock lock(m_refreshMutex); \ -@@ -466,7 +466,7 @@ WalletImpl::WalletImpl(NetworkType nettype, uint64_t kdf_rounds) - m_wallet2Callback.reset(new Wallet2CallbackImpl(this)); - m_wallet->callback(m_wallet2Callback.get()); - m_refreshThreadDone = false; -- m_refreshEnabled = false; -+ m_wallet->set_refresh_enabled(false); - m_addressBook.reset(new AddressBookImpl(this)); - m_subaddress.reset(new SubaddressImpl(this)); - m_subaddressAccount.reset(new SubaddressAccountImpl(this)); -@@ -962,6 +962,7 @@ void WalletImpl::stop() - bool WalletImpl::store(const std::string &path) - { - clearStatus(); -+ LOCK_REFRESH(); - try { - if (path.empty()) { - m_wallet->store(); -@@ -2448,10 +2449,10 @@ void WalletImpl::refreshThreadFunc() - } - - LOG_PRINT_L3(__FUNCTION__ << ": refresh lock acquired..."); -- LOG_PRINT_L3(__FUNCTION__ << ": m_refreshEnabled: " << m_refreshEnabled); -+ LOG_PRINT_L3(__FUNCTION__ << ": m_refreshEnabled: " << m_wallet->get_refresh_enabled()); - LOG_PRINT_L3(__FUNCTION__ << ": m_status: " << status()); - LOG_PRINT_L3(__FUNCTION__ << ": m_refreshShouldRescan: " << m_refreshShouldRescan); -- if (m_refreshEnabled) { -+ if (m_wallet->get_refresh_enabled()) { - LOG_PRINT_L3(__FUNCTION__ << ": refreshing..."); - doRefresh(); - } -@@ -2481,12 +2482,12 @@ void WalletImpl::doRefresh() - } - m_wallet->find_and_save_rings(false); - } else { -- LOG_PRINT_L3(__FUNCTION__ << ": skipping refresh - daemon is not synced"); -+ LOG_PRINT_L3(__FUNCTION__ << ": skipping refresh - daemon is not synced"); - } - } catch (const std::exception &e) { - setStatusError(e.what()); - break; -- }while(!rescan && (rescan=m_refreshShouldRescan.exchange(false))); // repeat if not rescanned and rescan was requested -+ }while(m_wallet->get_refresh_enabled() && !rescan && (rescan=m_refreshShouldRescan.exchange(false))); // repeat if not rescanned and rescan was requested - - if (m_wallet2Callback->getListener()) { - m_wallet2Callback->getListener()->refreshed(); -@@ -2496,9 +2497,9 @@ void WalletImpl::doRefresh() - - void WalletImpl::startRefresh() - { -- if (!m_refreshEnabled) { -+ if (!m_wallet->get_refresh_enabled()) { - LOG_PRINT_L2(__FUNCTION__ << ": refresh started/resumed..."); -- m_refreshEnabled = true; -+ m_wallet->set_refresh_enabled(true); - m_refreshCV.notify_one(); - } - } -@@ -2508,7 +2509,7 @@ void WalletImpl::startRefresh() - void WalletImpl::stopRefresh() - { - if (!m_refreshThreadDone) { -- m_refreshEnabled = false; -+ m_wallet->set_refresh_enabled(false); - m_refreshThreadDone = true; - m_refreshCV.notify_one(); - m_refreshThread.join(); -@@ -2519,9 +2520,7 @@ void WalletImpl::pauseRefresh() - { - LOG_PRINT_L2(__FUNCTION__ << ": refresh paused..."); - // TODO synchronize access -- if (!m_refreshThreadDone) { -- m_refreshEnabled = false; -- } -+ m_wallet->set_refresh_enabled(false); - } - - -diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h -index 1f199a72c..ac7ce2f6a 100644 ---- a/src/wallet/api/wallet.h -+++ b/src/wallet/api/wallet.h -@@ -273,7 +273,6 @@ private: - std::unique_ptr m_subaddressAccount; - - // multi-threaded refresh stuff -- std::atomic m_refreshEnabled; - std::atomic m_refreshThreadDone; - std::atomic m_refreshIntervalMillis; - std::atomic m_refreshShouldRescan; -diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp -index 535005ab1..4e66cdeae 100644 ---- a/src/wallet/wallet2.cpp -+++ b/src/wallet/wallet2.cpp -@@ -1192,6 +1192,7 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended, std - m_upper_transaction_weight_limit(0), - m_run(true), - m_callback(0), -+ m_refreshEnabled(false), - m_trusted_daemon(false), - m_nettype(nettype), - m_multisig_rounds_passed(0), -@@ -1412,6 +1413,14 @@ bool wallet2::set_daemon(std::string daemon_address, boost::optionalset_proxy(address); -@@ -4107,8 +4116,9 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo - // infer when we get an incoming output - - bool first = true, last = false; -- while(m_run.load(std::memory_order_relaxed) && blocks_fetched < max_blocks) -+ while(m_run.load(std::memory_order_relaxed) && blocks_fetched < max_blocks && m_refreshEnabled) - { -+ LOG_ERROR("SYNCING"); - uint64_t next_blocks_start_height; - std::vector next_blocks; - std::vector next_parsed_blocks; -diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h -index 632298726..022d0696f 100644 ---- a/src/wallet/wallet2.h -+++ b/src/wallet/wallet2.h -@@ -1078,6 +1078,8 @@ private: - epee::net_utils::ssl_options_t ssl_options = epee::net_utils::ssl_support_t::e_ssl_support_autodetect, - const std::string &proxy = ""); - bool set_proxy(const std::string &address); -+ bool get_refresh_enabled(); -+ void set_refresh_enabled(bool val); - - void stop() { m_run.store(false, std::memory_order_relaxed); m_message_store.stop(); } - -@@ -1989,6 +1991,7 @@ private: - - boost::recursive_mutex m_daemon_rpc_mutex; - -+ bool m_refreshEnabled; - bool m_trusted_daemon; - i_wallet2_callback* m_callback; - hw::device::device_type m_key_device_type; --- -2.48.0 - diff --git a/patches/wownero/0004-uint64_t-missing-definition-fix.patch b/patches/wownero/0004-uint64_t-missing-definition-fix.patch new file mode 100644 index 0000000..8bfa59a --- /dev/null +++ b/patches/wownero/0004-uint64_t-missing-definition-fix.patch @@ -0,0 +1,25 @@ +From faa6501f23d74a59f345a8542fdbb65f27540e74 Mon Sep 17 00:00:00 2001 +From: Czarek Nakamoto +Date: Mon, 2 Sep 2024 16:40:31 +0200 +Subject: [PATCH 04/16] uint64_t missing definition fix + +--- + contrib/epee/include/net/http_base.h | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/contrib/epee/include/net/http_base.h b/contrib/epee/include/net/http_base.h +index 4af4da790..ae4c0d05e 100644 +--- a/contrib/epee/include/net/http_base.h ++++ b/contrib/epee/include/net/http_base.h +@@ -28,7 +28,7 @@ + + #pragma once + #include "memwipe.h" +- ++#include + #include + + #include +-- +2.51.0 + diff --git a/patches/wownero/0005-uint64_t-missing-definition-fix.patch b/patches/wownero/0005-uint64_t-missing-definition-fix.patch deleted file mode 100644 index 5035df2..0000000 --- a/patches/wownero/0005-uint64_t-missing-definition-fix.patch +++ /dev/null @@ -1,25 +0,0 @@ -From 5a9148c10ea29a42d8aa08d69e2f4559c8611655 Mon Sep 17 00:00:00 2001 -From: Czarek Nakamoto -Date: Mon, 2 Sep 2024 16:40:31 +0200 -Subject: [PATCH 05/15] uint64_t missing definition fix - ---- - contrib/epee/include/net/http_base.h | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/contrib/epee/include/net/http_base.h b/contrib/epee/include/net/http_base.h -index 4af4da790..ae4c0d05e 100644 ---- a/contrib/epee/include/net/http_base.h -+++ b/contrib/epee/include/net/http_base.h -@@ -28,7 +28,7 @@ - - #pragma once - #include "memwipe.h" -- -+#include - #include - - #include --- -2.48.0 - diff --git a/patches/wownero/0005-use-proper-error-handling-in-get_seed.patch b/patches/wownero/0005-use-proper-error-handling-in-get_seed.patch new file mode 100644 index 0000000..d581ebf --- /dev/null +++ b/patches/wownero/0005-use-proper-error-handling-in-get_seed.patch @@ -0,0 +1,71 @@ +From 04899e3877b7f801b21057f7e527b5fb846fb7bd Mon Sep 17 00:00:00 2001 +From: Czarek Nakamoto +Date: Mon, 24 Jun 2024 10:49:12 +0200 +Subject: [PATCH 05/16] use proper error handling in get_seed + +--- + src/wallet/api/wallet.cpp | 17 ++++++++++++----- + src/wallet/wallet2.cpp | 5 ++++- + 2 files changed, 16 insertions(+), 6 deletions(-) + +diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp +index d53372fc9..ec02a129d 100644 +--- a/src/wallet/api/wallet.cpp ++++ b/src/wallet/api/wallet.cpp +@@ -826,12 +826,19 @@ bool WalletImpl::close(bool store) + + std::string WalletImpl::seed(const std::string& seed_offset) const + { +- if (checkBackgroundSync("cannot get seed")) ++ if (checkBackgroundSync("cannot get seed")) { ++ setStatusError("cannot get seed"); + return std::string(); +- epee::wipeable_string seed; +- if (m_wallet) +- m_wallet->get_seed(seed, seed_offset); +- return std::string(seed.data(), seed.size()); // TODO ++ } ++ try { ++ epee::wipeable_string seed; ++ if (m_wallet) ++ m_wallet->get_seed(seed, seed_offset); ++ return std::string(seed.data(), seed.size()); // TODO ++ } catch (const std::exception &e) { ++ setStatusError(e.what()); ++ return std::string(); ++ } + } + + std::string WalletImpl::getSeedLanguage() const +diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp +index 8d8ba33c5..856645b6b 100644 +--- a/src/wallet/wallet2.cpp ++++ b/src/wallet/wallet2.cpp +@@ -1452,11 +1452,13 @@ bool wallet2::get_seed(epee::wipeable_string& electrum_words, const epee::wipeab + bool keys_deterministic = is_deterministic(); + if (!keys_deterministic) + { ++ THROW_WALLET_EXCEPTION(error::wallet_internal_error, "This is not a deterministic wallet"); + std::cout << "This is not a deterministic wallet" << std::endl; + return false; + } + if (seed_language.empty()) + { ++ THROW_WALLET_EXCEPTION(error::wallet_internal_error, "seed_language not set"); + std::cout << "seed_language not set" << std::endl; + return false; + } +@@ -1466,8 +1468,9 @@ bool wallet2::get_seed(epee::wipeable_string& electrum_words, const epee::wipeab + key = cryptonote::encrypt_key(key, passphrase); + if (!crypto::ElectrumWords::bytes_to_words(key, electrum_words, seed_language)) + { ++ THROW_WALLET_EXCEPTION(error::wallet_internal_error, "Failed to create seed from key for language: "+seed_language+", falling back to English."); + std::cout << "Failed to create seed from key for language: " << seed_language << std::endl; +- return false; ++ crypto::ElectrumWords::bytes_to_words(key, electrum_words, "English"); + } + + return true; +-- +2.51.0 + diff --git a/patches/wownero/0006-UR-functions.patch b/patches/wownero/0006-UR-functions.patch new file mode 100644 index 0000000..7a4d779 --- /dev/null +++ b/patches/wownero/0006-UR-functions.patch @@ -0,0 +1,1027 @@ +From 59084c084836343354f2e5b5dccafc26b5f7f02a Mon Sep 17 00:00:00 2001 +From: tobtoht +Date: Tue, 12 Mar 2024 10:09:50 +0100 +Subject: [PATCH 06/16] UR functions + +This commit adds UR functions for UR tasks, +I believe that the right place to get +UR strings is the wallet code itself, +especially because it allows us to +skip the part when we have to store +things to file to encode them later. +Now we are fully in memory + +Things broken in the commit +- ledger support. + AUTO_LOCK_CMD macro causes compile time + issues with this patch. I don't know why + just yet, this is a issue that I'll fix + later. However (considering the purpose + of this patch) it is not a dealbreaker. +--- + .gitmodules | 5 +- + CMakeLists.txt | 4 +- + contrib/depends/hosts/darwin.mk | 2 +- + contrib/depends/toolchain.cmake.in | 2 +- + external/CMakeLists.txt | 1 + + src/device/device_ledger.cpp | 5 +- + src/wallet/CMakeLists.txt | 1 + + src/wallet/api/pending_transaction.cpp | 33 +++ + src/wallet/api/pending_transaction.h | 1 + + src/wallet/api/unsigned_transaction.cpp | 42 ++++ + src/wallet/api/unsigned_transaction.h | 1 + + src/wallet/api/wallet.cpp | 309 +++++++++++++++++++++++- + src/wallet/api/wallet.h | 8 + + src/wallet/api/wallet2_api.h | 22 +- + src/wallet/wallet2.cpp | 141 +++++++---- + src/wallet/wallet2.h | 3 + + 16 files changed, 520 insertions(+), 60 deletions(-) + +diff --git a/.gitmodules b/.gitmodules +index 98a0af1ab..c1c0d385d 100644 +--- a/.gitmodules ++++ b/.gitmodules +@@ -15,4 +15,7 @@ + path = external/randomwow + url = https://codeberg.org/wownero/RandomWOW + branch = 1.2.1-wow +- ++[submodule "external/bc-ur"] ++ path = external/bc-ur ++ url = https://github.com/MrCyjaneK/bc-ur ++ branch = misc +diff --git a/CMakeLists.txt b/CMakeLists.txt +index c8edf7a45..c439e5300 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -96,7 +96,8 @@ enable_language(C ASM) + set(CMAKE_C_STANDARD 11) + set(CMAKE_C_STANDARD_REQUIRED ON) + set(CMAKE_C_EXTENSIONS OFF) +-set(CMAKE_CXX_STANDARD 14) ++set(CMAKE_CXX_STANDARD 17) ++add_definitions(-D_LIBCPP_ENABLE_CXX17_REMOVED_FEATURES) # boost: no template named 'unary_function' in namespace 'std'; did you mean '__unary_function'? + set(CMAKE_CXX_STANDARD_REQUIRED ON) + set(CMAKE_CXX_EXTENSIONS OFF) + +@@ -365,6 +366,7 @@ if(NOT MANUAL_SUBMODULES) + endfunction () + + message(STATUS "Checking submodules") ++ #check_submodule(external/bc-ur) + check_submodule(external/miniupnp) + check_submodule(external/rapidjson) + check_submodule(external/trezor-common) +diff --git a/contrib/depends/hosts/darwin.mk b/contrib/depends/hosts/darwin.mk +index 79d449054..83d83036b 100644 +--- a/contrib/depends/hosts/darwin.mk ++++ b/contrib/depends/hosts/darwin.mk +@@ -1,4 +1,4 @@ +-OSX_MIN_VERSION=10.8 ++OSX_MIN_VERSION=10.14 + LD64_VERSION=609 + ifeq (aarch64, $(host_arch)) + CC_target=arm64-apple-$(host_os) +diff --git a/contrib/depends/toolchain.cmake.in b/contrib/depends/toolchain.cmake.in +index f118c754e..f26655d68 100644 +--- a/contrib/depends/toolchain.cmake.in ++++ b/contrib/depends/toolchain.cmake.in +@@ -94,7 +94,7 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + SET(BREW OFF) + SET(PORT OFF) + SET(CMAKE_OSX_SYSROOT "@prefix@/native/SDK/") +- SET(CMAKE_OSX_DEPLOYMENT_TARGET "10.08") ++ SET(CMAKE_OSX_DEPLOYMENT_TARGET "10.14") + SET(CMAKE_CXX_STANDARD 14) + SET(LLVM_ENABLE_PIC OFF) + SET(LLVM_ENABLE_PIE OFF) +diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt +index 649383aa2..d0b6d9b14 100644 +--- a/external/CMakeLists.txt ++++ b/external/CMakeLists.txt +@@ -70,4 +70,5 @@ endif() + add_subdirectory(db_drivers) + add_subdirectory(easylogging++) + add_subdirectory(qrcodegen) ++add_subdirectory(bc-ur) + add_subdirectory(randomwow EXCLUDE_FROM_ALL) +diff --git a/src/device/device_ledger.cpp b/src/device/device_ledger.cpp +index 56a3a45c4..e1bfa7041 100644 +--- a/src/device/device_ledger.cpp ++++ b/src/device/device_ledger.cpp +@@ -313,12 +313,13 @@ namespace hw { + + /* ======================================================================= */ + /* LOCKER */ +- /* ======================================================================= */ ++ /* ======================================================================= */ + + //automatic lock one more level on device ensuring the current thread is allowed to use it ++ #pragma message ("Warning AUTO_LOCK_CMD is intentionally left broken. This is yet to be fixed.") + #define AUTO_LOCK_CMD() \ + /* lock both mutexes without deadlock*/ \ +- boost::lock(device_locker, command_locker); \ ++ /* boost::lock(device_locker, command_locker); */ \ + /* make sure both already-locked mutexes are unlocked at the end of scope */ \ + boost::lock_guard lock1(device_locker, boost::adopt_lock); \ + boost::lock_guard lock2(command_locker, boost::adopt_lock) +diff --git a/src/wallet/CMakeLists.txt b/src/wallet/CMakeLists.txt +index fdf3f2f5d..66384fe31 100644 +--- a/src/wallet/CMakeLists.txt ++++ b/src/wallet/CMakeLists.txt +@@ -50,6 +50,7 @@ monero_add_library(wallet + target_link_libraries(wallet + PUBLIC + rpc_base ++ bc-ur + multisig + common + cryptonote_core +diff --git a/src/wallet/api/pending_transaction.cpp b/src/wallet/api/pending_transaction.cpp +index 70a702796..9c3c26ee5 100644 +--- a/src/wallet/api/pending_transaction.cpp ++++ b/src/wallet/api/pending_transaction.cpp +@@ -42,6 +42,8 @@ + #include + #include + ++#include "bc-ur/src/bc-ur.hpp" ++ + using namespace std; + + namespace Monero { +@@ -162,6 +164,37 @@ bool PendingTransactionImpl::commit(const std::string &filename, bool overwrite) + return m_status == Status_Ok; + } + ++std::string PendingTransactionImpl::commitUR(int max_fragment_length) { ++ ++ LOG_PRINT_L3("m_pending_tx size: " << m_pending_tx.size()); ++ ++ try { ++ std::string ptx = m_wallet.m_wallet->dump_tx_to_str(m_pending_tx); ++ m_status = Status_Ok; ++ auto urMessage = ur::string_to_bytes(ptx); ++ ur::ByteVector cbor; ++ ur::CborLite::encodeBytes(cbor, urMessage); ++ std::string type; ++ if (m_wallet.watchOnly()) { ++ type = "xmr-txunsigned"; ++ } else { ++ type = "xmr-txsigned"; ++ } ++ ur::UR urData = ur::UR(type, cbor); ++ auto encoder = ur::UREncoder(urData, max_fragment_length); ++ std::string output; ++ for(size_t i = 0; i < encoder.seq_len(); i++) { ++ output.append("\n"+encoder.next_part()); ++ } ++ return output; ++ } catch (const std::exception &e) { ++ m_errorString = string(tr("Unknown exception: ")) + e.what(); ++ m_status = Status_Error; ++ return ""; ++ } ++} ++ ++ + uint64_t PendingTransactionImpl::amount() const + { + uint64_t result = 0; +diff --git a/src/wallet/api/pending_transaction.h b/src/wallet/api/pending_transaction.h +index 0a9779c07..403bfe281 100644 +--- a/src/wallet/api/pending_transaction.h ++++ b/src/wallet/api/pending_transaction.h +@@ -46,6 +46,7 @@ public: + int status() const override; + std::string errorString() const override; + bool commit(const std::string &filename = "", bool overwrite = false) override; ++ std::string commitUR(int max_fragment_length = 130) override; + uint64_t amount() const override; + uint64_t dust() const override; + uint64_t fee() const override; +diff --git a/src/wallet/api/unsigned_transaction.cpp b/src/wallet/api/unsigned_transaction.cpp +index 6165a2240..fd03e959d 100644 +--- a/src/wallet/api/unsigned_transaction.cpp ++++ b/src/wallet/api/unsigned_transaction.cpp +@@ -40,6 +40,8 @@ + #include + #include + ++#include "bc-ur/src/bc-ur.hpp" ++ + using namespace std; + + namespace Monero { +@@ -96,6 +98,46 @@ bool UnsignedTransactionImpl::sign(const std::string &signedFileName) + return true; + } + ++std::string UnsignedTransactionImpl::signUR(int max_fragment_length) ++{ ++ if(m_wallet.watchOnly()) ++ { ++ m_errorString = tr("This is a watch only wallet"); ++ m_status = Status_Error; ++ return ""; ++ } ++ std::vector ptx; ++ try ++ { ++ tools::wallet2::signed_tx_set signed_txes; ++ std::string signedTx = m_wallet.m_wallet->sign_tx_dump_to_str(m_unsigned_tx_set, ptx, signed_txes); ++ if (signedTx.empty()) ++ { ++ m_errorString = tr("Failed to sign transaction"); ++ m_status = Status_Error; ++ return ""; ++ } ++ auto urMessage = ur::string_to_bytes(signedTx); ++ ur::ByteVector cbor; ++ ur::CborLite::encodeBytes(cbor, urMessage); ++ std::string type = "xmr-txsigned"; ++ ur::UR urData = ur::UR(type, cbor); ++ auto encoder = ur::UREncoder(urData, max_fragment_length); ++ std::string output; ++ for(size_t i = 0; i < encoder.seq_len(); i++) { ++ output.append("\n"+encoder.next_part()); ++ } ++ return output; ++ } ++ catch (const std::exception &e) ++ { ++ m_errorString = string(tr("Failed to sign transaction")) + e.what(); ++ m_status = Status_Error; ++ return ""; ++ } ++ return ""; ++} ++ + //---------------------------------------------------------------------------------------------------- + bool UnsignedTransactionImpl::checkLoadedTx(const std::function get_num_txes, const std::function &get_tx, const std::string &extra_message) + { +diff --git a/src/wallet/api/unsigned_transaction.h b/src/wallet/api/unsigned_transaction.h +index 30065a7fa..a94b23f75 100644 +--- a/src/wallet/api/unsigned_transaction.h ++++ b/src/wallet/api/unsigned_transaction.h +@@ -53,6 +53,7 @@ public: + uint64_t txCount() const override; + // sign txs and save to file + bool sign(const std::string &signedFileName) override; ++ std::string signUR(int max_fragment_length = 130) override; + std::string confirmationMessage() const override {return m_confirmationMessage;} + uint64_t minMixinCount() const override; + +diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp +index ec02a129d..e04205e48 100644 +--- a/src/wallet/api/wallet.cpp ++++ b/src/wallet/api/wallet.cpp +@@ -47,6 +47,7 @@ + + #include + #include ++#include "bc-ur/src/bc-ur.hpp" + + using namespace std; + using namespace cryptonote; +@@ -1066,6 +1067,24 @@ uint64_t WalletImpl::unlockedBalance(uint32_t accountIndex) const + return m_wallet->unlocked_balance(accountIndex, false); + } + ++uint64_t WalletImpl::viewOnlyBalance(uint32_t accountIndex, const std::vector &key_images) const ++{ ++ clearStatus(); ++ ++ std::vector kis; ++ for (const auto &key_image : key_images) { ++ crypto::key_image ki; ++ if (!epee::string_tools::hex_to_pod(key_image, ki)) ++ { ++ setStatusError(tr("failed to parse key image")); ++ return 0; ++ } ++ kis.push_back(ki); ++ } ++ ++ return m_wallet->view_only_balance(accountIndex, kis); ++} ++ + uint64_t WalletImpl::blockChainHeight() const + { + if(m_wallet->light_wallet()) { +@@ -1208,6 +1227,61 @@ UnsignedTransaction *WalletImpl::loadUnsignedTx(const std::string &unsigned_file + return transaction; + } + ++ ++UnsignedTransaction *WalletImpl::loadUnsignedTxUR(const std::string &input) { ++ clearStatus(); ++ UnsignedTransactionImpl * transaction = new UnsignedTransactionImpl(*this); ++ auto decoder = ur::URDecoder(); ++ ++ std::string delimiter = "\n"; ++ std::string inp = input; ++ size_t pos = 0; ++ std::string token; ++ while ((pos = inp.find(delimiter)) != std::string::npos) { ++ token = inp.substr(0, pos); ++ decoder.receive_part(token); ++ inp.erase(0, pos + delimiter.length()); ++ } ++ decoder.receive_part(inp); ++ ++ if (decoder.is_failure()) { ++ setStatusError(decoder.result_error().what()); ++ transaction->m_status = UnsignedTransaction::Status::Status_Error; ++ transaction->m_errorString = errorString(); ++ return transaction; ++ } ++ ++ if (!decoder.is_complete()) { ++ setStatusError("file ended but ur didn't complete"); ++ transaction->m_status = UnsignedTransaction::Status::Status_Error; ++ transaction->m_errorString = errorString(); ++ return transaction; ++ } ++ ++ std::string data; ++ auto cbor = decoder.result_ur().cbor(); ++ auto i = cbor.begin(); ++ auto end = cbor.end(); ++ ur::CborLite::decodeBytes(i, end, data); ++ ++ if (checkBackgroundSync("cannot load tx") || !m_wallet->parse_unsigned_tx_from_str(data, transaction->m_unsigned_tx_set)){ ++ setStatusError(tr("Failed to load unsigned transactions")); ++ transaction->m_status = UnsignedTransaction::Status::Status_Error; ++ transaction->m_errorString = errorString(); ++ ++ return transaction; ++ } ++ ++ // Check tx data and construct confirmation message ++ std::string extra_message; ++ if (!std::get<2>(transaction->m_unsigned_tx_set.transfers).empty()) ++ extra_message = (boost::format("%u outputs to import. ") % (unsigned)std::get<2>(transaction->m_unsigned_tx_set.transfers).size()).str(); ++ transaction->checkLoadedTx([&transaction](){return transaction->m_unsigned_tx_set.txes.size();}, [&transaction](size_t n)->const tools::wallet2::tx_construction_data&{return transaction->m_unsigned_tx_set.txes[n];}, extra_message); ++ setStatus(transaction->status(), transaction->errorString()); ++ ++ return transaction; ++} ++ + bool WalletImpl::submitTransaction(const string &fileName) { + clearStatus(); + if (checkBackgroundSync("cannot submit tx")) +@@ -1219,7 +1293,7 @@ bool WalletImpl::submitTransaction(const string &fileName) { + setStatus(Status_Ok, tr("Failed to load transaction from file")); + return false; + } +- ++ + if(!transaction->commit()) { + setStatusError(transaction->m_errorString); + return false; +@@ -1228,6 +1302,61 @@ bool WalletImpl::submitTransaction(const string &fileName) { + return true; + } + ++ ++bool WalletImpl::submitTransactionUR(const string &input) { ++ clearStatus(); ++ auto decoder = ur::URDecoder(); ++ ++ std::string delimiter = "\n"; ++ std::string inp = input; ++ size_t pos = 0; ++ std::string token; ++ while ((pos = inp.find(delimiter)) != std::string::npos) { ++ token = inp.substr(0, pos); ++ decoder.receive_part(token); ++ inp.erase(0, pos + delimiter.length()); ++ } ++ decoder.receive_part(inp); ++ ++ if (decoder.is_failure()) { ++ setStatusError(decoder.result_error().what()); ++ return false; ++ } ++ ++ if (!decoder.is_complete()) { ++ setStatusError("file ended but ur didn't complete"); ++ return false; ++ } ++ ++ std::string data; ++ auto cbor = decoder.result_ur().cbor(); ++ auto i = cbor.begin(); ++ auto end = cbor.end(); ++ ur::CborLite::decodeBytes(i, end, data); ++ if (checkBackgroundSync("cannot submit tx")) ++ return false; ++ std::unique_ptr transaction(new PendingTransactionImpl(*this)); ++ ++ bool r = m_wallet->parse_tx_from_str(data, transaction->m_pending_tx, NULL); ++ if (!r) { ++ setStatus(Status_Ok, tr("Failed to load transaction from file")); ++ return false; ++ } ++ ++ if(!transaction->commit()) { ++ setStatusError(transaction->m_errorString); ++ return false; ++ } ++ ++ return true; ++} ++ ++ ++bool WalletImpl::hasUnknownKeyImages() const ++{ ++ return m_wallet->has_unknown_key_images(); ++} ++ + bool WalletImpl::exportKeyImages(const string &filename, bool all) + { + if (m_wallet->watch_only()) +@@ -1255,6 +1384,39 @@ bool WalletImpl::exportKeyImages(const string &filename, bool all) + return true; + } + ++std::string WalletImpl::exportKeyImagesUR(size_t max_fragment_length, bool all) ++{ ++ if (m_wallet->watch_only()) ++ { ++ setStatusError(tr("Wallet is view only")); ++ return ""; ++ } ++ if (checkBackgroundSync("cannot export key images")) ++ return ""; ++ ++ try ++ { ++ std::string keyImages = m_wallet->export_key_images_str(all); ++ auto urMessage = ur::string_to_bytes(keyImages); ++ ur::ByteVector cbor; ++ ur::CborLite::encodeBytes(cbor, urMessage); ++ ur::UR urData = ur::UR("xmr-keyimage", cbor); ++ auto encoder = ur::UREncoder(urData, max_fragment_length); ++ std::string output; ++ for(size_t i = 0; i < encoder.seq_len(); i++) { ++ output.append("\n"+encoder.next_part()); ++ } ++ return output; ++ } ++ catch (const std::exception &e) ++ { ++ LOG_ERROR("Error exporting key images: " << e.what()); ++ setStatusError(e.what()); ++ return ""; ++ } ++ return ""; ++} ++ + bool WalletImpl::importKeyImages(const string &filename) + { + if (checkBackgroundSync("cannot import key images")) +@@ -1280,6 +1442,62 @@ bool WalletImpl::importKeyImages(const string &filename) + return true; + } + ++ ++bool WalletImpl::importKeyImagesUR(const string &input) ++{ ++ if (checkBackgroundSync("cannot import key images")) ++ return false; ++ if (!trustedDaemon()) { ++ setStatusError(tr("Key images can only be imported with a trusted daemon")); ++ return false; ++ } ++ try ++ { ++ auto decoder = ur::URDecoder(); ++ std::string delimiter = "\n"; ++ std::string inp = input; ++ size_t pos = 0; ++ std::string token; ++ while ((pos = inp.find(delimiter)) != std::string::npos) { ++ token = inp.substr(0, pos); ++ decoder.receive_part(token); ++ inp.erase(0, pos + delimiter.length()); ++ } ++ decoder.receive_part(inp); ++ ++ if (decoder.is_failure()) { ++ setStatusError(decoder.result_error().what()); ++ return false; ++ } ++ ++ if (!decoder.is_complete()) { ++ setStatusError("file ended but ur didn't complete"); ++ return false; ++ } ++ ++ std::string data; ++ auto cbor = decoder.result_ur().cbor(); ++ auto i = cbor.begin(); ++ auto end = cbor.end(); ++ ur::CborLite::decodeBytes(i, end, data); ++ ++ uint64_t spent = 0, unspent = 0; ++ ++ uint64_t height = m_wallet->import_key_images_str(data, spent, unspent); ++ LOG_PRINT_L2("Signed key images imported to height " << height << ", " ++ << print_money(spent) << " spent, " << print_money(unspent) << " unspent"); ++ } ++ catch (const std::exception &e) ++ { ++ LOG_ERROR("Error exporting key images: " << e.what()); ++ setStatusError(string(tr("Failed to import key images: ")) + e.what()); ++ return false; ++ } ++ ++ return true; ++} ++ ++ + bool WalletImpl::exportOutputs(const string &filename, bool all) + { + if (checkBackgroundSync("cannot export outputs")) +@@ -1312,6 +1530,40 @@ bool WalletImpl::exportOutputs(const string &filename, bool all) + return true; + } + ++std::string WalletImpl::exportOutputsUR(size_t max_fragment_length, bool all) ++{ ++ ++ if (checkBackgroundSync("cannot export outputs")) ++ return ""; ++ if (m_wallet->key_on_device()) ++ { ++ setStatusError(string(tr("Not supported on HW wallets."))); ++ return ""; ++ } ++ ++ try ++ { ++ std::string data = m_wallet->export_outputs_to_str(all); ++ auto urMessage = ur::string_to_bytes(data); ++ ur::ByteVector cbor; ++ ur::CborLite::encodeBytes(cbor, urMessage); ++ ur::UR urData = ur::UR("xmr-output", cbor); ++ auto encoder = ur::UREncoder(urData, max_fragment_length); ++ std::string output; ++ for(size_t i = 0; i < encoder.seq_len(); i++) { ++ output.append("\n"+encoder.next_part()); ++ } ++ return output; ++ } ++ catch (const std::exception &e) ++ { ++ LOG_ERROR("Error exporting outputs: " << e.what()); ++ setStatusError(string(tr("Error exporting outputs: ")) + e.what()); ++ return ""; ++ } ++} ++ ++ + bool WalletImpl::importOutputs(const string &filename) + { + if (checkBackgroundSync("cannot import outputs")) +@@ -1346,6 +1598,61 @@ bool WalletImpl::importOutputs(const string &filename) + return true; + } + ++ ++bool WalletImpl::importOutputsUR(const string &input) ++{ ++ if (checkBackgroundSync("cannot import outputs")) ++ return false; ++ if (m_wallet->key_on_device()) ++ { ++ setStatusError(string(tr("Not supported on HW wallets."))); ++ return false; ++ } ++ ++ try ++ { ++ auto decoder = ur::URDecoder(); ++ ++ std::string delimiter = "\n"; ++ std::string inp = input; ++ size_t pos = 0; ++ std::string token; ++ while ((pos = inp.find(delimiter)) != std::string::npos) { ++ token = inp.substr(0, pos); ++ decoder.receive_part(token); ++ inp.erase(0, pos + delimiter.length()); ++ } ++ decoder.receive_part(inp); ++ ++ if (decoder.is_failure()) { ++ setStatusError(decoder.result_error().what()); ++ return false; ++ } ++ ++ if (!decoder.is_complete()) { ++ setStatusError("file ended but ur didn't complete"); ++ return false; ++ } ++ ++ std::string data; ++ auto cbor = decoder.result_ur().cbor(); ++ auto i = cbor.begin(); ++ auto end = cbor.end(); ++ ur::CborLite::decodeBytes(i, end, data); ++ size_t n_outputs = m_wallet->import_outputs_from_str(std::string(data)); ++ LOG_PRINT_L2(std::to_string(n_outputs) << " outputs imported"); ++ } ++ catch (const std::exception &e) ++ { ++ LOG_ERROR("Failed to import outputs: " << e.what()); ++ setStatusError(string(tr("Failed to import outputs: ")) + e.what()); ++ return false; ++ } ++ ++ return true; ++} ++ ++ + bool WalletImpl::scanTransactions(const std::vector &txids) + { + if (checkBackgroundSync("cannot scan transactions")) +diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h +index ac7ce2f6a..edf8bb8ce 100644 +--- a/src/wallet/api/wallet.h ++++ b/src/wallet/api/wallet.h +@@ -112,6 +112,7 @@ public: + bool setProxy(const std::string &address) override; + uint64_t balance(uint32_t accountIndex = 0) const override; + uint64_t unlockedBalance(uint32_t accountIndex = 0) const override; ++ uint64_t viewOnlyBalance(uint32_t accountIndex, const std::vector &key_images) const override; + uint64_t blockChainHeight() const override; + uint64_t approximateBlockChainHeight() const override; + uint64_t estimateBlockChainHeight() const override; +@@ -164,11 +165,18 @@ public: + std::set subaddr_indices = {}) override; + virtual PendingTransaction * createSweepUnmixableTransaction() override; + bool submitTransaction(const std::string &fileName) override; ++ bool submitTransactionUR(const std::string &input) override; + virtual UnsignedTransaction * loadUnsignedTx(const std::string &unsigned_filename) override; ++ virtual UnsignedTransaction * loadUnsignedTxUR(const std::string &input) override; ++ bool hasUnknownKeyImages() const override; + bool exportKeyImages(const std::string &filename, bool all = false) override; ++ std::string exportKeyImagesUR(size_t max_fragment_length, bool all = false) override; + bool importKeyImages(const std::string &filename) override; ++ bool importKeyImagesUR(const std::string &input) override; + bool exportOutputs(const std::string &filename, bool all = false) override; ++ std::string exportOutputsUR(size_t max_fragment_length, bool all) override; + bool importOutputs(const std::string &filename) override; ++ bool importOutputsUR(const std::string &filename) override; + bool scanTransactions(const std::vector &txids) override; + + bool setupBackgroundSync(const BackgroundSyncType background_sync_type, const std::string &wallet_password, const optional &background_cache_password = optional()) override; +diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h +index e349df176..764adbfbf 100644 +--- a/src/wallet/api/wallet2_api.h ++++ b/src/wallet/api/wallet2_api.h +@@ -91,6 +91,7 @@ struct PendingTransaction + virtual std::string errorString() const = 0; + // commit transaction or save to file if filename is provided. + virtual bool commit(const std::string &filename = "", bool overwrite = false) = 0; ++ virtual std::string commitUR(int max_fragment_length = 130) = 0; + virtual uint64_t amount() const = 0; + virtual uint64_t dust() const = 0; + virtual uint64_t fee() const = 0; +@@ -160,7 +161,8 @@ struct UnsignedTransaction + * @param signedFileName + * return - true on success + */ +- virtual bool sign(const std::string &signedFileName) = 0; ++ virtual bool sign(const std::string &signedFileName) = 0; ++ virtual std::string signUR(int max_fragment_length = 130) = 0; + }; + + /** +@@ -626,6 +628,7 @@ struct Wallet + result += unlockedBalance(i); + return result; + } ++ virtual uint64_t viewOnlyBalance(uint32_t accountIndex, const std::vector &key_images = {}) const = 0; + + /** + * @brief watchOnly - checks if wallet is watch only +@@ -884,13 +887,15 @@ struct Wallet + * after object returned + */ + virtual UnsignedTransaction * loadUnsignedTx(const std::string &unsigned_filename) = 0; +- +- /*! ++ virtual UnsignedTransaction * loadUnsignedTxUR(const std::string &input) = 0; ++ ++ /*! + * \brief submitTransaction - submits transaction in signed tx file + * \return - true on success + */ + virtual bool submitTransaction(const std::string &fileName) = 0; +- ++ virtual bool submitTransactionUR(const std::string &input) = 0; ++ + + /*! + * \brief disposeTransaction - destroys transaction object +@@ -906,6 +911,8 @@ struct Wallet + virtual uint64_t estimateTransactionFee(const std::vector> &destinations, + PendingTransaction::Priority priority) const = 0; + ++ virtual bool hasUnknownKeyImages() const = 0; ++ + /*! + * \brief exportKeyImages - exports key images to file + * \param filename +@@ -913,20 +920,22 @@ struct Wallet + * \return - true on success + */ + virtual bool exportKeyImages(const std::string &filename, bool all = false) = 0; +- ++ virtual std::string exportKeyImagesUR(size_t max_fragment_length, bool all = false) = 0; + /*! + * \brief importKeyImages - imports key images from file + * \param filename + * \return - true on success + */ + virtual bool importKeyImages(const std::string &filename) = 0; ++ virtual bool importKeyImagesUR(const std::string &input) = 0; + + /*! +- * \brief importOutputs - exports outputs to file ++ * \brief exportOutputs - exports outputs to file + * \param filename + * \return - true on success + */ + virtual bool exportOutputs(const std::string &filename, bool all = false) = 0; ++ virtual std::string exportOutputsUR(size_t max_fragment_length, bool all = false) = 0; + + /*! + * \brief importOutputs - imports outputs from file +@@ -934,6 +943,7 @@ struct Wallet + * \return - true on success + */ + virtual bool importOutputs(const std::string &filename) = 0; ++ virtual bool importOutputsUR(const std::string &filename) = 0; + + /*! + * \brief scanTransactions - scan a list of transaction ids, this operation may reveal the txids to the remote node and affect your privacy +diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp +index 856645b6b..d89595cf8 100644 +--- a/src/wallet/wallet2.cpp ++++ b/src/wallet/wallet2.cpp +@@ -953,6 +953,16 @@ uint32_t get_subaddress_clamped_sum(uint32_t idx, uint32_t extra) + return idx + extra; + } + ++bool is_preferred_input(const std::vector& preferred_input_list, const crypto::key_image& input) { ++ if (!preferred_input_list.empty()) { ++ auto it = std::find(preferred_input_list.begin(), preferred_input_list.end(), input); ++ if (it == preferred_input_list.end()) { ++ return false; ++ } ++ } ++ return true; ++} ++ + static void setup_shim(hw::wallet_shim * shim, tools::wallet2 * wallet) + { + shim->get_tx_pub_key_from_received_outs = std::bind(&tools::wallet2::get_tx_pub_key_from_received_outs, wallet, std::placeholders::_1); +@@ -7039,6 +7049,25 @@ uint64_t wallet2::unlocked_balance(uint32_t index_major, bool strict, uint64_t * + return amount; + } + //---------------------------------------------------------------------------------------------------- ++uint64_t wallet2::view_only_balance(uint32_t index_major, const std::vector& selected_inputs) ++{ ++ uint64_t amount = 0; ++ for (const auto &td : m_transfers) { ++ if (is_preferred_input(selected_inputs, td.m_key_image) && ++ !is_spent(td, false) && ++ !td.m_frozen && ++ !td.m_key_image_partial && ++ td.m_key_image_known && ++ td.is_rct() && ++ is_transfer_unlocked(td) && ++ td.m_subaddr_index.major == index_major) ++ { ++ amount += td.m_amount; ++ } ++ } ++ return amount; ++} ++//---------------------------------------------------------------------------------------------------- + std::map wallet2::balance_per_subaddress(uint32_t index_major, bool strict) const + { + std::map amount_per_subaddr; +@@ -7890,9 +7919,7 @@ bool wallet2::sign_tx(unsigned_tx_set &exported_txs, std::vector additional_derivations; + +- // compute public keys from out secret keys +- crypto::public_key tx_pub_key; +- crypto::secret_key_to_public_key(txs[n].tx_key, tx_pub_key); ++ crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(tx); + std::vector additional_tx_pub_keys; + for (const crypto::secret_key &skey: txs[n].additional_tx_keys) + { +@@ -11230,7 +11257,7 @@ std::vector wallet2::create_transactions_2(std::vector m_ignore_outputs_above || td.amount() < m_ignore_outputs_below) + { +@@ -11280,9 +11307,15 @@ std::vector wallet2::create_transactions_2(std::vector(); ++ // use tobotoht's code path on view-only wallet, otherwise default to upstream ++ bool throwOnNoEnotes = m_account.get_device().device_protocol() == hw::device::PROTOCOL_COLD || m_watch_only || m_multisig || m_is_background_wallet; + ++ if (throwOnNoEnotes) { ++ THROW_WALLET_EXCEPTION_IF(unused_dust_indices_per_subaddr.empty() && unused_transfers_indices_per_subaddr.empty(), error::wallet_internal_error, "No enotes available to spend") ++ } else { ++ if (unused_dust_indices_per_subaddr.empty() && unused_transfers_indices_per_subaddr.empty()) ++ return std::vector(); ++ } + // if empty, put dummy entry so that the front can be referenced later in the loop + if (unused_dust_indices_per_subaddr.empty()) + unused_dust_indices_per_subaddr.push_back({}); +@@ -13911,33 +13944,40 @@ crypto::public_key wallet2::get_tx_pub_key_from_received_outs(const tools::walle + + bool wallet2::export_key_images(const std::string &filename, bool all) const + { +- PERF_TIMER(export_key_images); +- std::pair>> ski = export_key_images(all); +- std::string magic(KEY_IMAGE_EXPORT_FILE_MAGIC, strlen(KEY_IMAGE_EXPORT_FILE_MAGIC)); +- const cryptonote::account_public_address &keys = get_account().get_keys().m_account_address; +- const uint32_t offset = ski.first; ++ std::string data = export_key_images_str(all); ++ return save_to_file(filename, data); ++} + +- std::string data; +- data.reserve(4 + ski.second.size() * (sizeof(crypto::key_image) + sizeof(crypto::signature)) + 2 * sizeof(crypto::public_key)); +- data.resize(4); +- data[0] = offset & 0xff; +- data[1] = (offset >> 8) & 0xff; +- data[2] = (offset >> 16) & 0xff; +- data[3] = (offset >> 24) & 0xff; +- data += std::string((const char *)&keys.m_spend_public_key, sizeof(crypto::public_key)); +- data += std::string((const char *)&keys.m_view_public_key, sizeof(crypto::public_key)); +- for (const auto &i: ski.second) +- { +- data += std::string((const char *)&i.first, sizeof(crypto::key_image)); +- data += std::string((const char *)&i.second, sizeof(crypto::signature)); +- } ++std::string wallet2::export_key_images_str(bool all) const ++{ ++ PERF_TIMER(export_key_images); ++ std::pair>> ski = export_key_images(all); ++ std::string magic(KEY_IMAGE_EXPORT_FILE_MAGIC, strlen(KEY_IMAGE_EXPORT_FILE_MAGIC)); ++ const cryptonote::account_public_address &keys = get_account().get_keys().m_account_address; ++ const uint32_t offset = ski.first; + +- // encrypt data, keep magic plaintext +- PERF_TIMER(export_key_images_encrypt); +- std::string ciphertext = encrypt_with_view_secret_key(data); +- return save_to_file(filename, magic + ciphertext); ++ std::string data; ++ data.reserve(4 + ski.second.size() * (sizeof(crypto::key_image) + sizeof(crypto::signature)) + 2 * sizeof(crypto::public_key)); ++ data.resize(4); ++ data[0] = offset & 0xff; ++ data[1] = (offset >> 8) & 0xff; ++ data[2] = (offset >> 16) & 0xff; ++ data[3] = (offset >> 24) & 0xff; ++ data += std::string((const char *)&keys.m_spend_public_key, sizeof(crypto::public_key)); ++ data += std::string((const char *)&keys.m_view_public_key, sizeof(crypto::public_key)); ++ for (const auto &i: ski.second) ++ { ++ data += std::string((const char *)&i.first, sizeof(crypto::key_image)); ++ data += std::string((const char *)&i.second, sizeof(crypto::signature)); ++ } ++ ++ // encrypt data, keep magic plaintext ++ PERF_TIMER(export_key_images_encrypt); ++ std::string ciphertext = encrypt_with_view_secret_key(data); ++ return magic + ciphertext; + } + ++ + //---------------------------------------------------------------------------------------------------- + std::pair>> wallet2::export_key_images(bool all) const + { +@@ -13992,53 +14032,60 @@ std::pair> + return std::make_pair(offset, ski); + } + +-uint64_t wallet2::import_key_images(const std::string &filename, uint64_t &spent, uint64_t &unspent) ++uint64_t wallet2::import_key_images(const std::string &filename, uint64_t &spent, uint64_t &unspent) { ++ std::string data; ++ ++ bool r = load_from_file(filename, data); ++ ++ THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, std::string(tr("failed to read file ")) + filename); ++ ++ return import_key_images_str(data, spent, unspent); ++} ++ ++uint64_t wallet2::import_key_images_str(const std::string &data, uint64_t &spent, uint64_t &unspent) + { + PERF_TIMER(import_key_images_fsu); +- std::string data; +- bool r = load_from_file(filename, data); +- +- THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, std::string(tr("failed to read file ")) + filename); ++ std::string data_local = data; + + const size_t magiclen = strlen(KEY_IMAGE_EXPORT_FILE_MAGIC); + if (data.size() < magiclen || memcmp(data.data(), KEY_IMAGE_EXPORT_FILE_MAGIC, magiclen)) + { +- THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string("Bad key image export file magic in ") + filename); ++ THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string("Bad key image export file magic")); + } + + try + { + PERF_TIMER(import_key_images_decrypt); +- data = decrypt_with_view_secret_key(std::string(data, magiclen)); ++ data_local = decrypt_with_view_secret_key(std::string(data, magiclen)); + } + catch (const std::exception &e) + { +- THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string("Failed to decrypt ") + filename + ": " + e.what()); ++ THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string("Failed to decrypt ") + ": " + e.what()); + } + + const size_t headerlen = 4 + 2 * sizeof(crypto::public_key); +- THROW_WALLET_EXCEPTION_IF(data.size() < headerlen, error::wallet_internal_error, std::string("Bad data size from file ") + filename); +- const uint32_t offset = (uint8_t)data[0] | (((uint8_t)data[1]) << 8) | (((uint8_t)data[2]) << 16) | (((uint8_t)data[3]) << 24); +- const crypto::public_key &public_spend_key = *(const crypto::public_key*)&data[4]; +- const crypto::public_key &public_view_key = *(const crypto::public_key*)&data[4 + sizeof(crypto::public_key)]; ++ THROW_WALLET_EXCEPTION_IF(data_local.size() < headerlen, error::wallet_internal_error, std::string("Bad data size from file ")); ++ const uint32_t offset = (uint8_t)data_local[0] | (((uint8_t)data_local[1]) << 8) | (((uint8_t)data_local[2]) << 16) | (((uint8_t)data_local[3]) << 24); ++ const crypto::public_key &public_spend_key = *(const crypto::public_key*)&data_local[4]; ++ const crypto::public_key &public_view_key = *(const crypto::public_key*)&data_local[4 + sizeof(crypto::public_key)]; + const cryptonote::account_public_address &keys = get_account().get_keys().m_account_address; + if (public_spend_key != keys.m_spend_public_key || public_view_key != keys.m_view_public_key) + { +- THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string( "Key images from ") + filename + " are for a different account"); ++ THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string( "Key images from ") + " are for a different account"); + } + THROW_WALLET_EXCEPTION_IF(offset > m_transfers.size(), error::wallet_internal_error, "Offset larger than known outputs"); + + const size_t record_size = sizeof(crypto::key_image) + sizeof(crypto::signature); +- THROW_WALLET_EXCEPTION_IF((data.size() - headerlen) % record_size, +- error::wallet_internal_error, std::string("Bad data size from file ") + filename); +- size_t nki = (data.size() - headerlen) / record_size; ++ THROW_WALLET_EXCEPTION_IF((data_local.size() - headerlen) % record_size, ++ error::wallet_internal_error, std::string("Bad data size from file ")); ++ size_t nki = (data_local.size() - headerlen) / record_size; + + std::vector> ski; + ski.reserve(nki); + for (size_t n = 0; n < nki; ++n) + { +- crypto::key_image key_image = *reinterpret_cast(&data[headerlen + n * record_size]); +- crypto::signature signature = *reinterpret_cast(&data[headerlen + n * record_size + sizeof(crypto::key_image)]); ++ crypto::key_image key_image = *reinterpret_cast(&data_local[headerlen + n * record_size]); ++ crypto::signature signature = *reinterpret_cast(&data_local[headerlen + n * record_size + sizeof(crypto::key_image)]); + + ski.push_back(std::make_pair(key_image, signature)); + } +diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h +index a1d939a5a..e309cec5e 100644 +--- a/src/wallet/wallet2.h ++++ b/src/wallet/wallet2.h +@@ -1164,6 +1164,7 @@ private: + // locked & unlocked balance of given or current subaddress account + uint64_t balance(uint32_t subaddr_index_major, bool strict) const; + uint64_t unlocked_balance(uint32_t subaddr_index_major, bool strict, uint64_t *blocks_to_unlock = NULL, uint64_t *time_to_unlock = NULL); ++ uint64_t view_only_balance(uint32_t index_major, const std::vector& selected_inputs = {}); + // locked & unlocked balance per subaddress of given or current subaddress account + std::map balance_per_subaddress(uint32_t subaddr_index_major, bool strict) const; + std::map>> unlocked_balance_per_subaddress(uint32_t subaddr_index_major, bool strict); +@@ -1638,9 +1639,11 @@ private: + std::tuple> export_blockchain() const; + void import_blockchain(const std::tuple> &bc); + bool export_key_images(const std::string &filename, bool all = false) const; ++ std::string export_key_images_str(bool all) const; + std::pair>> export_key_images(bool all = false) const; + uint64_t import_key_images(const std::vector> &signed_key_images, size_t offset, uint64_t &spent, uint64_t &unspent, bool check_spent = true); + uint64_t import_key_images(const std::string &filename, uint64_t &spent, uint64_t &unspent); ++ uint64_t import_key_images_str(const std::string &data, uint64_t &spent, uint64_t &unspent); + bool import_key_images(std::vector key_images, size_t offset=0, boost::optional> selected_transfers=boost::none); + bool import_key_images(signed_tx_set & signed_tx, size_t offset=0, bool only_selected_transfers=false); + crypto::public_key get_tx_pub_key_from_received_outs(const tools::wallet2::transfer_details &td) const; +-- +2.51.0 + diff --git a/patches/wownero/0006-use-proper-error-handling-in-get_seed.patch b/patches/wownero/0006-use-proper-error-handling-in-get_seed.patch deleted file mode 100644 index b9680fc..0000000 --- a/patches/wownero/0006-use-proper-error-handling-in-get_seed.patch +++ /dev/null @@ -1,71 +0,0 @@ -From ee6aa49179833c930c1ec10ae2e877aeb87eb8c8 Mon Sep 17 00:00:00 2001 -From: Czarek Nakamoto -Date: Mon, 24 Jun 2024 10:49:12 +0200 -Subject: [PATCH 06/15] use proper error handling in get_seed - ---- - src/wallet/api/wallet.cpp | 17 ++++++++++++----- - src/wallet/wallet2.cpp | 5 ++++- - 2 files changed, 16 insertions(+), 6 deletions(-) - -diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp -index 899ef044a..e16d8f83f 100644 ---- a/src/wallet/api/wallet.cpp -+++ b/src/wallet/api/wallet.cpp -@@ -826,12 +826,19 @@ bool WalletImpl::close(bool store) - - std::string WalletImpl::seed(const std::string& seed_offset) const - { -- if (checkBackgroundSync("cannot get seed")) -+ if (checkBackgroundSync("cannot get seed")) { -+ setStatusError("cannot get seed"); - return std::string(); -- epee::wipeable_string seed; -- if (m_wallet) -- m_wallet->get_seed(seed, seed_offset); -- return std::string(seed.data(), seed.size()); // TODO -+ } -+ try { -+ epee::wipeable_string seed; -+ if (m_wallet) -+ m_wallet->get_seed(seed, seed_offset); -+ return std::string(seed.data(), seed.size()); // TODO -+ } catch (const std::exception &e) { -+ setStatusError(e.what()); -+ return std::string(); -+ } - } - - std::string WalletImpl::getSeedLanguage() const -diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp -index 4e66cdeae..48267d647 100644 ---- a/src/wallet/wallet2.cpp -+++ b/src/wallet/wallet2.cpp -@@ -1449,11 +1449,13 @@ bool wallet2::get_seed(epee::wipeable_string& electrum_words, const epee::wipeab - bool keys_deterministic = is_deterministic(); - if (!keys_deterministic) - { -+ THROW_WALLET_EXCEPTION(error::wallet_internal_error, "This is not a deterministic wallet"); - std::cout << "This is not a deterministic wallet" << std::endl; - return false; - } - if (seed_language.empty()) - { -+ THROW_WALLET_EXCEPTION(error::wallet_internal_error, "seed_language not set"); - std::cout << "seed_language not set" << std::endl; - return false; - } -@@ -1463,8 +1465,9 @@ bool wallet2::get_seed(epee::wipeable_string& electrum_words, const epee::wipeab - key = cryptonote::encrypt_key(key, passphrase); - if (!crypto::ElectrumWords::bytes_to_words(key, electrum_words, seed_language)) - { -+ THROW_WALLET_EXCEPTION(error::wallet_internal_error, "Failed to create seed from key for language: "+seed_language+", falling back to English."); - std::cout << "Failed to create seed from key for language: " << seed_language << std::endl; -- return false; -+ crypto::ElectrumWords::bytes_to_words(key, electrum_words, "English"); - } - - return true; --- -2.48.0 - diff --git a/patches/wownero/0007-UR-functions.patch b/patches/wownero/0007-UR-functions.patch deleted file mode 100644 index 0bdc695..0000000 --- a/patches/wownero/0007-UR-functions.patch +++ /dev/null @@ -1,1036 +0,0 @@ -From 031df7de0d75d93f78be732d5cac702b0ab193f0 Mon Sep 17 00:00:00 2001 -From: tobtoht -Date: Tue, 12 Mar 2024 10:09:50 +0100 -Subject: [PATCH 07/15] UR functions - -This commit adds UR functions for UR tasks, -I believe that the right place to get -UR strings is the wallet code itself, -especially because it allows us to -skip the part when we have to store -things to file to encode them later. -Now we are fully in memory - -Things broken in the commit -- ledger support. - AUTO_LOCK_CMD macro causes compile time - issues with this patch. I don't know why - just yet, this is a issue that I'll fix - later. However (considering the purpose - of this patch) it is not a dealbreaker. ---- - .gitmodules | 5 +- - CMakeLists.txt | 4 +- - contrib/depends/hosts/darwin.mk | 2 +- - contrib/depends/toolchain.cmake.in | 2 +- - external/CMakeLists.txt | 1 + - external/bc-ur | 1 + - src/device/device_ledger.cpp | 5 +- - src/wallet/CMakeLists.txt | 1 + - src/wallet/api/pending_transaction.cpp | 33 +++ - src/wallet/api/pending_transaction.h | 1 + - src/wallet/api/unsigned_transaction.cpp | 42 ++++ - src/wallet/api/unsigned_transaction.h | 1 + - src/wallet/api/wallet.cpp | 309 +++++++++++++++++++++++- - src/wallet/api/wallet.h | 8 + - src/wallet/api/wallet2_api.h | 22 +- - src/wallet/wallet2.cpp | 141 +++++++---- - src/wallet/wallet2.h | 3 + - 17 files changed, 521 insertions(+), 60 deletions(-) - create mode 160000 external/bc-ur - -diff --git a/.gitmodules b/.gitmodules -index 991071fbe..b24855d9b 100644 ---- a/.gitmodules -+++ b/.gitmodules -@@ -16,4 +16,7 @@ - path = external/randomwow - url = https://codeberg.org/wownero/RandomWOW - branch = 1.2.1-wow -- -+[submodule "external/bc-ur"] -+ path = external/bc-ur -+ url = https://github.com/MrCyjaneK/bc-ur -+ branch = misc -diff --git a/CMakeLists.txt b/CMakeLists.txt -index b4b8c8089..88335ee9d 100644 ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -96,7 +96,8 @@ enable_language(C ASM) - set(CMAKE_C_STANDARD 11) - set(CMAKE_C_STANDARD_REQUIRED ON) - set(CMAKE_C_EXTENSIONS OFF) --set(CMAKE_CXX_STANDARD 14) -+set(CMAKE_CXX_STANDARD 17) -+add_definitions(-D_LIBCPP_ENABLE_CXX17_REMOVED_FEATURES) # boost: no template named 'unary_function' in namespace 'std'; did you mean '__unary_function'? - set(CMAKE_CXX_STANDARD_REQUIRED ON) - set(CMAKE_CXX_EXTENSIONS OFF) - -@@ -365,6 +366,7 @@ if(NOT MANUAL_SUBMODULES) - endfunction () - - message(STATUS "Checking submodules") -+ #check_submodule(external/bc-ur) - check_submodule(external/miniupnp) - check_submodule(external/rapidjson) - #check_submodule(external/trezor-common) -diff --git a/contrib/depends/hosts/darwin.mk b/contrib/depends/hosts/darwin.mk -index 79d449054..83d83036b 100644 ---- a/contrib/depends/hosts/darwin.mk -+++ b/contrib/depends/hosts/darwin.mk -@@ -1,4 +1,4 @@ --OSX_MIN_VERSION=10.8 -+OSX_MIN_VERSION=10.14 - LD64_VERSION=609 - ifeq (aarch64, $(host_arch)) - CC_target=arm64-apple-$(host_os) -diff --git a/contrib/depends/toolchain.cmake.in b/contrib/depends/toolchain.cmake.in -index f118c754e..f26655d68 100644 ---- a/contrib/depends/toolchain.cmake.in -+++ b/contrib/depends/toolchain.cmake.in -@@ -94,7 +94,7 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") - SET(BREW OFF) - SET(PORT OFF) - SET(CMAKE_OSX_SYSROOT "@prefix@/native/SDK/") -- SET(CMAKE_OSX_DEPLOYMENT_TARGET "10.08") -+ SET(CMAKE_OSX_DEPLOYMENT_TARGET "10.14") - SET(CMAKE_CXX_STANDARD 14) - SET(LLVM_ENABLE_PIC OFF) - SET(LLVM_ENABLE_PIE OFF) -diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt -index 29aed0cc6..dfdffe67c 100644 ---- a/external/CMakeLists.txt -+++ b/external/CMakeLists.txt -@@ -69,4 +69,5 @@ endif() - add_subdirectory(db_drivers) - add_subdirectory(easylogging++) - add_subdirectory(qrcodegen) -+add_subdirectory(bc-ur) - add_subdirectory(randomwow EXCLUDE_FROM_ALL) -diff --git a/external/bc-ur b/external/bc-ur -new file mode 160000 -index 000000000..d82e7c753 ---- /dev/null -+++ b/external/bc-ur -@@ -0,0 +1 @@ -+Subproject commit d82e7c753e710b8000706dc3383b498438795208 -diff --git a/src/device/device_ledger.cpp b/src/device/device_ledger.cpp -index 9961d13e7..8403d76e8 100644 ---- a/src/device/device_ledger.cpp -+++ b/src/device/device_ledger.cpp -@@ -313,12 +313,13 @@ namespace hw { - - /* ======================================================================= */ - /* LOCKER */ -- /* ======================================================================= */ -+ /* ======================================================================= */ - - //automatic lock one more level on device ensuring the current thread is allowed to use it -+ #pragma message ("Warning AUTO_LOCK_CMD is intentionally left broken. This is yet to be fixed.") - #define AUTO_LOCK_CMD() \ - /* lock both mutexes without deadlock*/ \ -- boost::lock(device_locker, command_locker); \ -+ /* boost::lock(device_locker, command_locker); */ \ - /* make sure both already-locked mutexes are unlocked at the end of scope */ \ - boost::lock_guard lock1(device_locker, boost::adopt_lock); \ - boost::lock_guard lock2(command_locker, boost::adopt_lock) -diff --git a/src/wallet/CMakeLists.txt b/src/wallet/CMakeLists.txt -index fdf3f2f5d..66384fe31 100644 ---- a/src/wallet/CMakeLists.txt -+++ b/src/wallet/CMakeLists.txt -@@ -50,6 +50,7 @@ monero_add_library(wallet - target_link_libraries(wallet - PUBLIC - rpc_base -+ bc-ur - multisig - common - cryptonote_core -diff --git a/src/wallet/api/pending_transaction.cpp b/src/wallet/api/pending_transaction.cpp -index 70a702796..9c3c26ee5 100644 ---- a/src/wallet/api/pending_transaction.cpp -+++ b/src/wallet/api/pending_transaction.cpp -@@ -42,6 +42,8 @@ - #include - #include - -+#include "bc-ur/src/bc-ur.hpp" -+ - using namespace std; - - namespace Monero { -@@ -162,6 +164,37 @@ bool PendingTransactionImpl::commit(const std::string &filename, bool overwrite) - return m_status == Status_Ok; - } - -+std::string PendingTransactionImpl::commitUR(int max_fragment_length) { -+ -+ LOG_PRINT_L3("m_pending_tx size: " << m_pending_tx.size()); -+ -+ try { -+ std::string ptx = m_wallet.m_wallet->dump_tx_to_str(m_pending_tx); -+ m_status = Status_Ok; -+ auto urMessage = ur::string_to_bytes(ptx); -+ ur::ByteVector cbor; -+ ur::CborLite::encodeBytes(cbor, urMessage); -+ std::string type; -+ if (m_wallet.watchOnly()) { -+ type = "xmr-txunsigned"; -+ } else { -+ type = "xmr-txsigned"; -+ } -+ ur::UR urData = ur::UR(type, cbor); -+ auto encoder = ur::UREncoder(urData, max_fragment_length); -+ std::string output; -+ for(size_t i = 0; i < encoder.seq_len(); i++) { -+ output.append("\n"+encoder.next_part()); -+ } -+ return output; -+ } catch (const std::exception &e) { -+ m_errorString = string(tr("Unknown exception: ")) + e.what(); -+ m_status = Status_Error; -+ return ""; -+ } -+} -+ -+ - uint64_t PendingTransactionImpl::amount() const - { - uint64_t result = 0; -diff --git a/src/wallet/api/pending_transaction.h b/src/wallet/api/pending_transaction.h -index 0a9779c07..403bfe281 100644 ---- a/src/wallet/api/pending_transaction.h -+++ b/src/wallet/api/pending_transaction.h -@@ -46,6 +46,7 @@ public: - int status() const override; - std::string errorString() const override; - bool commit(const std::string &filename = "", bool overwrite = false) override; -+ std::string commitUR(int max_fragment_length = 130) override; - uint64_t amount() const override; - uint64_t dust() const override; - uint64_t fee() const override; -diff --git a/src/wallet/api/unsigned_transaction.cpp b/src/wallet/api/unsigned_transaction.cpp -index 6165a2240..fd03e959d 100644 ---- a/src/wallet/api/unsigned_transaction.cpp -+++ b/src/wallet/api/unsigned_transaction.cpp -@@ -40,6 +40,8 @@ - #include - #include - -+#include "bc-ur/src/bc-ur.hpp" -+ - using namespace std; - - namespace Monero { -@@ -96,6 +98,46 @@ bool UnsignedTransactionImpl::sign(const std::string &signedFileName) - return true; - } - -+std::string UnsignedTransactionImpl::signUR(int max_fragment_length) -+{ -+ if(m_wallet.watchOnly()) -+ { -+ m_errorString = tr("This is a watch only wallet"); -+ m_status = Status_Error; -+ return ""; -+ } -+ std::vector ptx; -+ try -+ { -+ tools::wallet2::signed_tx_set signed_txes; -+ std::string signedTx = m_wallet.m_wallet->sign_tx_dump_to_str(m_unsigned_tx_set, ptx, signed_txes); -+ if (signedTx.empty()) -+ { -+ m_errorString = tr("Failed to sign transaction"); -+ m_status = Status_Error; -+ return ""; -+ } -+ auto urMessage = ur::string_to_bytes(signedTx); -+ ur::ByteVector cbor; -+ ur::CborLite::encodeBytes(cbor, urMessage); -+ std::string type = "xmr-txsigned"; -+ ur::UR urData = ur::UR(type, cbor); -+ auto encoder = ur::UREncoder(urData, max_fragment_length); -+ std::string output; -+ for(size_t i = 0; i < encoder.seq_len(); i++) { -+ output.append("\n"+encoder.next_part()); -+ } -+ return output; -+ } -+ catch (const std::exception &e) -+ { -+ m_errorString = string(tr("Failed to sign transaction")) + e.what(); -+ m_status = Status_Error; -+ return ""; -+ } -+ return ""; -+} -+ - //---------------------------------------------------------------------------------------------------- - bool UnsignedTransactionImpl::checkLoadedTx(const std::function get_num_txes, const std::function &get_tx, const std::string &extra_message) - { -diff --git a/src/wallet/api/unsigned_transaction.h b/src/wallet/api/unsigned_transaction.h -index 30065a7fa..a94b23f75 100644 ---- a/src/wallet/api/unsigned_transaction.h -+++ b/src/wallet/api/unsigned_transaction.h -@@ -53,6 +53,7 @@ public: - uint64_t txCount() const override; - // sign txs and save to file - bool sign(const std::string &signedFileName) override; -+ std::string signUR(int max_fragment_length = 130) override; - std::string confirmationMessage() const override {return m_confirmationMessage;} - uint64_t minMixinCount() const override; - -diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp -index e16d8f83f..ee000e7ab 100644 ---- a/src/wallet/api/wallet.cpp -+++ b/src/wallet/api/wallet.cpp -@@ -47,6 +47,7 @@ - - #include - #include -+#include "bc-ur/src/bc-ur.hpp" - - using namespace std; - using namespace cryptonote; -@@ -1066,6 +1067,24 @@ uint64_t WalletImpl::unlockedBalance(uint32_t accountIndex) const - return m_wallet->unlocked_balance(accountIndex, false); - } - -+uint64_t WalletImpl::viewOnlyBalance(uint32_t accountIndex, const std::vector &key_images) const -+{ -+ clearStatus(); -+ -+ std::vector kis; -+ for (const auto &key_image : key_images) { -+ crypto::key_image ki; -+ if (!epee::string_tools::hex_to_pod(key_image, ki)) -+ { -+ setStatusError(tr("failed to parse key image")); -+ return 0; -+ } -+ kis.push_back(ki); -+ } -+ -+ return m_wallet->view_only_balance(accountIndex, kis); -+} -+ - uint64_t WalletImpl::blockChainHeight() const - { - if(m_wallet->light_wallet()) { -@@ -1208,6 +1227,61 @@ UnsignedTransaction *WalletImpl::loadUnsignedTx(const std::string &unsigned_file - return transaction; - } - -+ -+UnsignedTransaction *WalletImpl::loadUnsignedTxUR(const std::string &input) { -+ clearStatus(); -+ UnsignedTransactionImpl * transaction = new UnsignedTransactionImpl(*this); -+ auto decoder = ur::URDecoder(); -+ -+ std::string delimiter = "\n"; -+ std::string inp = input; -+ size_t pos = 0; -+ std::string token; -+ while ((pos = inp.find(delimiter)) != std::string::npos) { -+ token = inp.substr(0, pos); -+ decoder.receive_part(token); -+ inp.erase(0, pos + delimiter.length()); -+ } -+ decoder.receive_part(inp); -+ -+ if (decoder.is_failure()) { -+ setStatusError(decoder.result_error().what()); -+ transaction->m_status = UnsignedTransaction::Status::Status_Error; -+ transaction->m_errorString = errorString(); -+ return transaction; -+ } -+ -+ if (!decoder.is_complete()) { -+ setStatusError("file ended but ur didn't complete"); -+ transaction->m_status = UnsignedTransaction::Status::Status_Error; -+ transaction->m_errorString = errorString(); -+ return transaction; -+ } -+ -+ std::string data; -+ auto cbor = decoder.result_ur().cbor(); -+ auto i = cbor.begin(); -+ auto end = cbor.end(); -+ ur::CborLite::decodeBytes(i, end, data); -+ -+ if (checkBackgroundSync("cannot load tx") || !m_wallet->parse_unsigned_tx_from_str(data, transaction->m_unsigned_tx_set)){ -+ setStatusError(tr("Failed to load unsigned transactions")); -+ transaction->m_status = UnsignedTransaction::Status::Status_Error; -+ transaction->m_errorString = errorString(); -+ -+ return transaction; -+ } -+ -+ // Check tx data and construct confirmation message -+ std::string extra_message; -+ if (!std::get<2>(transaction->m_unsigned_tx_set.transfers).empty()) -+ extra_message = (boost::format("%u outputs to import. ") % (unsigned)std::get<2>(transaction->m_unsigned_tx_set.transfers).size()).str(); -+ transaction->checkLoadedTx([&transaction](){return transaction->m_unsigned_tx_set.txes.size();}, [&transaction](size_t n)->const tools::wallet2::tx_construction_data&{return transaction->m_unsigned_tx_set.txes[n];}, extra_message); -+ setStatus(transaction->status(), transaction->errorString()); -+ -+ return transaction; -+} -+ - bool WalletImpl::submitTransaction(const string &fileName) { - clearStatus(); - if (checkBackgroundSync("cannot submit tx")) -@@ -1219,7 +1293,7 @@ bool WalletImpl::submitTransaction(const string &fileName) { - setStatus(Status_Ok, tr("Failed to load transaction from file")); - return false; - } -- -+ - if(!transaction->commit()) { - setStatusError(transaction->m_errorString); - return false; -@@ -1228,6 +1302,61 @@ bool WalletImpl::submitTransaction(const string &fileName) { - return true; - } - -+ -+bool WalletImpl::submitTransactionUR(const string &input) { -+ clearStatus(); -+ auto decoder = ur::URDecoder(); -+ -+ std::string delimiter = "\n"; -+ std::string inp = input; -+ size_t pos = 0; -+ std::string token; -+ while ((pos = inp.find(delimiter)) != std::string::npos) { -+ token = inp.substr(0, pos); -+ decoder.receive_part(token); -+ inp.erase(0, pos + delimiter.length()); -+ } -+ decoder.receive_part(inp); -+ -+ if (decoder.is_failure()) { -+ setStatusError(decoder.result_error().what()); -+ return false; -+ } -+ -+ if (!decoder.is_complete()) { -+ setStatusError("file ended but ur didn't complete"); -+ return false; -+ } -+ -+ std::string data; -+ auto cbor = decoder.result_ur().cbor(); -+ auto i = cbor.begin(); -+ auto end = cbor.end(); -+ ur::CborLite::decodeBytes(i, end, data); -+ if (checkBackgroundSync("cannot submit tx")) -+ return false; -+ std::unique_ptr transaction(new PendingTransactionImpl(*this)); -+ -+ bool r = m_wallet->parse_tx_from_str(data, transaction->m_pending_tx, NULL); -+ if (!r) { -+ setStatus(Status_Ok, tr("Failed to load transaction from file")); -+ return false; -+ } -+ -+ if(!transaction->commit()) { -+ setStatusError(transaction->m_errorString); -+ return false; -+ } -+ -+ return true; -+} -+ -+ -+bool WalletImpl::hasUnknownKeyImages() const -+{ -+ return m_wallet->has_unknown_key_images(); -+} -+ - bool WalletImpl::exportKeyImages(const string &filename, bool all) - { - if (m_wallet->watch_only()) -@@ -1255,6 +1384,39 @@ bool WalletImpl::exportKeyImages(const string &filename, bool all) - return true; - } - -+std::string WalletImpl::exportKeyImagesUR(size_t max_fragment_length, bool all) -+{ -+ if (m_wallet->watch_only()) -+ { -+ setStatusError(tr("Wallet is view only")); -+ return ""; -+ } -+ if (checkBackgroundSync("cannot export key images")) -+ return ""; -+ -+ try -+ { -+ std::string keyImages = m_wallet->export_key_images_str(all); -+ auto urMessage = ur::string_to_bytes(keyImages); -+ ur::ByteVector cbor; -+ ur::CborLite::encodeBytes(cbor, urMessage); -+ ur::UR urData = ur::UR("xmr-keyimage", cbor); -+ auto encoder = ur::UREncoder(urData, max_fragment_length); -+ std::string output; -+ for(size_t i = 0; i < encoder.seq_len(); i++) { -+ output.append("\n"+encoder.next_part()); -+ } -+ return output; -+ } -+ catch (const std::exception &e) -+ { -+ LOG_ERROR("Error exporting key images: " << e.what()); -+ setStatusError(e.what()); -+ return ""; -+ } -+ return ""; -+} -+ - bool WalletImpl::importKeyImages(const string &filename) - { - if (checkBackgroundSync("cannot import key images")) -@@ -1280,6 +1442,62 @@ bool WalletImpl::importKeyImages(const string &filename) - return true; - } - -+ -+bool WalletImpl::importKeyImagesUR(const string &input) -+{ -+ if (checkBackgroundSync("cannot import key images")) -+ return false; -+ if (!trustedDaemon()) { -+ setStatusError(tr("Key images can only be imported with a trusted daemon")); -+ return false; -+ } -+ try -+ { -+ auto decoder = ur::URDecoder(); -+ std::string delimiter = "\n"; -+ std::string inp = input; -+ size_t pos = 0; -+ std::string token; -+ while ((pos = inp.find(delimiter)) != std::string::npos) { -+ token = inp.substr(0, pos); -+ decoder.receive_part(token); -+ inp.erase(0, pos + delimiter.length()); -+ } -+ decoder.receive_part(inp); -+ -+ if (decoder.is_failure()) { -+ setStatusError(decoder.result_error().what()); -+ return false; -+ } -+ -+ if (!decoder.is_complete()) { -+ setStatusError("file ended but ur didn't complete"); -+ return false; -+ } -+ -+ std::string data; -+ auto cbor = decoder.result_ur().cbor(); -+ auto i = cbor.begin(); -+ auto end = cbor.end(); -+ ur::CborLite::decodeBytes(i, end, data); -+ -+ uint64_t spent = 0, unspent = 0; -+ -+ uint64_t height = m_wallet->import_key_images_str(data, spent, unspent); -+ LOG_PRINT_L2("Signed key images imported to height " << height << ", " -+ << print_money(spent) << " spent, " << print_money(unspent) << " unspent"); -+ } -+ catch (const std::exception &e) -+ { -+ LOG_ERROR("Error exporting key images: " << e.what()); -+ setStatusError(string(tr("Failed to import key images: ")) + e.what()); -+ return false; -+ } -+ -+ return true; -+} -+ -+ - bool WalletImpl::exportOutputs(const string &filename, bool all) - { - if (checkBackgroundSync("cannot export outputs")) -@@ -1312,6 +1530,40 @@ bool WalletImpl::exportOutputs(const string &filename, bool all) - return true; - } - -+std::string WalletImpl::exportOutputsUR(size_t max_fragment_length, bool all) -+{ -+ -+ if (checkBackgroundSync("cannot export outputs")) -+ return ""; -+ if (m_wallet->key_on_device()) -+ { -+ setStatusError(string(tr("Not supported on HW wallets."))); -+ return ""; -+ } -+ -+ try -+ { -+ std::string data = m_wallet->export_outputs_to_str(all); -+ auto urMessage = ur::string_to_bytes(data); -+ ur::ByteVector cbor; -+ ur::CborLite::encodeBytes(cbor, urMessage); -+ ur::UR urData = ur::UR("xmr-output", cbor); -+ auto encoder = ur::UREncoder(urData, max_fragment_length); -+ std::string output; -+ for(size_t i = 0; i < encoder.seq_len(); i++) { -+ output.append("\n"+encoder.next_part()); -+ } -+ return output; -+ } -+ catch (const std::exception &e) -+ { -+ LOG_ERROR("Error exporting outputs: " << e.what()); -+ setStatusError(string(tr("Error exporting outputs: ")) + e.what()); -+ return ""; -+ } -+} -+ -+ - bool WalletImpl::importOutputs(const string &filename) - { - if (checkBackgroundSync("cannot import outputs")) -@@ -1346,6 +1598,61 @@ bool WalletImpl::importOutputs(const string &filename) - return true; - } - -+ -+bool WalletImpl::importOutputsUR(const string &input) -+{ -+ if (checkBackgroundSync("cannot import outputs")) -+ return false; -+ if (m_wallet->key_on_device()) -+ { -+ setStatusError(string(tr("Not supported on HW wallets."))); -+ return false; -+ } -+ -+ try -+ { -+ auto decoder = ur::URDecoder(); -+ -+ std::string delimiter = "\n"; -+ std::string inp = input; -+ size_t pos = 0; -+ std::string token; -+ while ((pos = inp.find(delimiter)) != std::string::npos) { -+ token = inp.substr(0, pos); -+ decoder.receive_part(token); -+ inp.erase(0, pos + delimiter.length()); -+ } -+ decoder.receive_part(inp); -+ -+ if (decoder.is_failure()) { -+ setStatusError(decoder.result_error().what()); -+ return false; -+ } -+ -+ if (!decoder.is_complete()) { -+ setStatusError("file ended but ur didn't complete"); -+ return false; -+ } -+ -+ std::string data; -+ auto cbor = decoder.result_ur().cbor(); -+ auto i = cbor.begin(); -+ auto end = cbor.end(); -+ ur::CborLite::decodeBytes(i, end, data); -+ size_t n_outputs = m_wallet->import_outputs_from_str(std::string(data)); -+ LOG_PRINT_L2(std::to_string(n_outputs) << " outputs imported"); -+ } -+ catch (const std::exception &e) -+ { -+ LOG_ERROR("Failed to import outputs: " << e.what()); -+ setStatusError(string(tr("Failed to import outputs: ")) + e.what()); -+ return false; -+ } -+ -+ return true; -+} -+ -+ - bool WalletImpl::scanTransactions(const std::vector &txids) - { - if (checkBackgroundSync("cannot scan transactions")) -diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h -index ac7ce2f6a..edf8bb8ce 100644 ---- a/src/wallet/api/wallet.h -+++ b/src/wallet/api/wallet.h -@@ -112,6 +112,7 @@ public: - bool setProxy(const std::string &address) override; - uint64_t balance(uint32_t accountIndex = 0) const override; - uint64_t unlockedBalance(uint32_t accountIndex = 0) const override; -+ uint64_t viewOnlyBalance(uint32_t accountIndex, const std::vector &key_images) const override; - uint64_t blockChainHeight() const override; - uint64_t approximateBlockChainHeight() const override; - uint64_t estimateBlockChainHeight() const override; -@@ -164,11 +165,18 @@ public: - std::set subaddr_indices = {}) override; - virtual PendingTransaction * createSweepUnmixableTransaction() override; - bool submitTransaction(const std::string &fileName) override; -+ bool submitTransactionUR(const std::string &input) override; - virtual UnsignedTransaction * loadUnsignedTx(const std::string &unsigned_filename) override; -+ virtual UnsignedTransaction * loadUnsignedTxUR(const std::string &input) override; -+ bool hasUnknownKeyImages() const override; - bool exportKeyImages(const std::string &filename, bool all = false) override; -+ std::string exportKeyImagesUR(size_t max_fragment_length, bool all = false) override; - bool importKeyImages(const std::string &filename) override; -+ bool importKeyImagesUR(const std::string &input) override; - bool exportOutputs(const std::string &filename, bool all = false) override; -+ std::string exportOutputsUR(size_t max_fragment_length, bool all) override; - bool importOutputs(const std::string &filename) override; -+ bool importOutputsUR(const std::string &filename) override; - bool scanTransactions(const std::vector &txids) override; - - bool setupBackgroundSync(const BackgroundSyncType background_sync_type, const std::string &wallet_password, const optional &background_cache_password = optional()) override; -diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h -index e349df176..764adbfbf 100644 ---- a/src/wallet/api/wallet2_api.h -+++ b/src/wallet/api/wallet2_api.h -@@ -91,6 +91,7 @@ struct PendingTransaction - virtual std::string errorString() const = 0; - // commit transaction or save to file if filename is provided. - virtual bool commit(const std::string &filename = "", bool overwrite = false) = 0; -+ virtual std::string commitUR(int max_fragment_length = 130) = 0; - virtual uint64_t amount() const = 0; - virtual uint64_t dust() const = 0; - virtual uint64_t fee() const = 0; -@@ -160,7 +161,8 @@ struct UnsignedTransaction - * @param signedFileName - * return - true on success - */ -- virtual bool sign(const std::string &signedFileName) = 0; -+ virtual bool sign(const std::string &signedFileName) = 0; -+ virtual std::string signUR(int max_fragment_length = 130) = 0; - }; - - /** -@@ -626,6 +628,7 @@ struct Wallet - result += unlockedBalance(i); - return result; - } -+ virtual uint64_t viewOnlyBalance(uint32_t accountIndex, const std::vector &key_images = {}) const = 0; - - /** - * @brief watchOnly - checks if wallet is watch only -@@ -884,13 +887,15 @@ struct Wallet - * after object returned - */ - virtual UnsignedTransaction * loadUnsignedTx(const std::string &unsigned_filename) = 0; -- -- /*! -+ virtual UnsignedTransaction * loadUnsignedTxUR(const std::string &input) = 0; -+ -+ /*! - * \brief submitTransaction - submits transaction in signed tx file - * \return - true on success - */ - virtual bool submitTransaction(const std::string &fileName) = 0; -- -+ virtual bool submitTransactionUR(const std::string &input) = 0; -+ - - /*! - * \brief disposeTransaction - destroys transaction object -@@ -906,6 +911,8 @@ struct Wallet - virtual uint64_t estimateTransactionFee(const std::vector> &destinations, - PendingTransaction::Priority priority) const = 0; - -+ virtual bool hasUnknownKeyImages() const = 0; -+ - /*! - * \brief exportKeyImages - exports key images to file - * \param filename -@@ -913,20 +920,22 @@ struct Wallet - * \return - true on success - */ - virtual bool exportKeyImages(const std::string &filename, bool all = false) = 0; -- -+ virtual std::string exportKeyImagesUR(size_t max_fragment_length, bool all = false) = 0; - /*! - * \brief importKeyImages - imports key images from file - * \param filename - * \return - true on success - */ - virtual bool importKeyImages(const std::string &filename) = 0; -+ virtual bool importKeyImagesUR(const std::string &input) = 0; - - /*! -- * \brief importOutputs - exports outputs to file -+ * \brief exportOutputs - exports outputs to file - * \param filename - * \return - true on success - */ - virtual bool exportOutputs(const std::string &filename, bool all = false) = 0; -+ virtual std::string exportOutputsUR(size_t max_fragment_length, bool all = false) = 0; - - /*! - * \brief importOutputs - imports outputs from file -@@ -934,6 +943,7 @@ struct Wallet - * \return - true on success - */ - virtual bool importOutputs(const std::string &filename) = 0; -+ virtual bool importOutputsUR(const std::string &filename) = 0; - - /*! - * \brief scanTransactions - scan a list of transaction ids, this operation may reveal the txids to the remote node and affect your privacy -diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp -index 48267d647..e14d4d2fc 100644 ---- a/src/wallet/wallet2.cpp -+++ b/src/wallet/wallet2.cpp -@@ -948,6 +948,16 @@ uint32_t get_subaddress_clamped_sum(uint32_t idx, uint32_t extra) - return idx + extra; - } - -+bool is_preferred_input(const std::vector& preferred_input_list, const crypto::key_image& input) { -+ if (!preferred_input_list.empty()) { -+ auto it = std::find(preferred_input_list.begin(), preferred_input_list.end(), input); -+ if (it == preferred_input_list.end()) { -+ return false; -+ } -+ } -+ return true; -+} -+ - static void setup_shim(hw::wallet_shim * shim, tools::wallet2 * wallet) - { - shim->get_tx_pub_key_from_received_outs = std::bind(&tools::wallet2::get_tx_pub_key_from_received_outs, wallet, std::placeholders::_1); -@@ -6998,6 +7008,25 @@ uint64_t wallet2::unlocked_balance(uint32_t index_major, bool strict, uint64_t * - return amount; - } - //---------------------------------------------------------------------------------------------------- -+uint64_t wallet2::view_only_balance(uint32_t index_major, const std::vector& selected_inputs) -+{ -+ uint64_t amount = 0; -+ for (const auto &td : m_transfers) { -+ if (is_preferred_input(selected_inputs, td.m_key_image) && -+ !is_spent(td, false) && -+ !td.m_frozen && -+ !td.m_key_image_partial && -+ td.m_key_image_known && -+ td.is_rct() && -+ is_transfer_unlocked(td) && -+ td.m_subaddr_index.major == index_major) -+ { -+ amount += td.m_amount; -+ } -+ } -+ return amount; -+} -+//---------------------------------------------------------------------------------------------------- - std::map wallet2::balance_per_subaddress(uint32_t index_major, bool strict) const - { - std::map amount_per_subaddr; -@@ -7849,9 +7878,7 @@ bool wallet2::sign_tx(unsigned_tx_set &exported_txs, std::vector additional_derivations; - -- // compute public keys from out secret keys -- crypto::public_key tx_pub_key; -- crypto::secret_key_to_public_key(txs[n].tx_key, tx_pub_key); -+ crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(tx); - std::vector additional_tx_pub_keys; - for (const crypto::secret_key &skey: txs[n].additional_tx_keys) - { -@@ -11241,7 +11268,7 @@ std::vector wallet2::create_transactions_2(std::vector m_ignore_outputs_above || td.amount() < m_ignore_outputs_below) - { -@@ -11291,9 +11318,15 @@ std::vector wallet2::create_transactions_2(std::vector(); -+ // use tobotoht's code path on view-only wallet, otherwise default to upstream -+ bool throwOnNoEnotes = m_account.get_device().device_protocol() == hw::device::PROTOCOL_COLD || m_watch_only || m_multisig || m_is_background_wallet; - -+ if (throwOnNoEnotes) { -+ THROW_WALLET_EXCEPTION_IF(unused_dust_indices_per_subaddr.empty() && unused_transfers_indices_per_subaddr.empty(), error::wallet_internal_error, "No enotes available to spend") -+ } else { -+ if (unused_dust_indices_per_subaddr.empty() && unused_transfers_indices_per_subaddr.empty()) -+ return std::vector(); -+ } - // if empty, put dummy entry so that the front can be referenced later in the loop - if (unused_dust_indices_per_subaddr.empty()) - unused_dust_indices_per_subaddr.push_back({}); -@@ -13920,33 +13953,40 @@ crypto::public_key wallet2::get_tx_pub_key_from_received_outs(const tools::walle - - bool wallet2::export_key_images(const std::string &filename, bool all) const - { -- PERF_TIMER(export_key_images); -- std::pair>> ski = export_key_images(all); -- std::string magic(KEY_IMAGE_EXPORT_FILE_MAGIC, strlen(KEY_IMAGE_EXPORT_FILE_MAGIC)); -- const cryptonote::account_public_address &keys = get_account().get_keys().m_account_address; -- const uint32_t offset = ski.first; -+ std::string data = export_key_images_str(all); -+ return save_to_file(filename, data); -+} - -- std::string data; -- data.reserve(4 + ski.second.size() * (sizeof(crypto::key_image) + sizeof(crypto::signature)) + 2 * sizeof(crypto::public_key)); -- data.resize(4); -- data[0] = offset & 0xff; -- data[1] = (offset >> 8) & 0xff; -- data[2] = (offset >> 16) & 0xff; -- data[3] = (offset >> 24) & 0xff; -- data += std::string((const char *)&keys.m_spend_public_key, sizeof(crypto::public_key)); -- data += std::string((const char *)&keys.m_view_public_key, sizeof(crypto::public_key)); -- for (const auto &i: ski.second) -- { -- data += std::string((const char *)&i.first, sizeof(crypto::key_image)); -- data += std::string((const char *)&i.second, sizeof(crypto::signature)); -- } -+std::string wallet2::export_key_images_str(bool all) const -+{ -+ PERF_TIMER(export_key_images); -+ std::pair>> ski = export_key_images(all); -+ std::string magic(KEY_IMAGE_EXPORT_FILE_MAGIC, strlen(KEY_IMAGE_EXPORT_FILE_MAGIC)); -+ const cryptonote::account_public_address &keys = get_account().get_keys().m_account_address; -+ const uint32_t offset = ski.first; - -- // encrypt data, keep magic plaintext -- PERF_TIMER(export_key_images_encrypt); -- std::string ciphertext = encrypt_with_view_secret_key(data); -- return save_to_file(filename, magic + ciphertext); -+ std::string data; -+ data.reserve(4 + ski.second.size() * (sizeof(crypto::key_image) + sizeof(crypto::signature)) + 2 * sizeof(crypto::public_key)); -+ data.resize(4); -+ data[0] = offset & 0xff; -+ data[1] = (offset >> 8) & 0xff; -+ data[2] = (offset >> 16) & 0xff; -+ data[3] = (offset >> 24) & 0xff; -+ data += std::string((const char *)&keys.m_spend_public_key, sizeof(crypto::public_key)); -+ data += std::string((const char *)&keys.m_view_public_key, sizeof(crypto::public_key)); -+ for (const auto &i: ski.second) -+ { -+ data += std::string((const char *)&i.first, sizeof(crypto::key_image)); -+ data += std::string((const char *)&i.second, sizeof(crypto::signature)); -+ } -+ -+ // encrypt data, keep magic plaintext -+ PERF_TIMER(export_key_images_encrypt); -+ std::string ciphertext = encrypt_with_view_secret_key(data); -+ return magic + ciphertext; - } - -+ - //---------------------------------------------------------------------------------------------------- - std::pair>> wallet2::export_key_images(bool all) const - { -@@ -14001,53 +14041,60 @@ std::pair> - return std::make_pair(offset, ski); - } - --uint64_t wallet2::import_key_images(const std::string &filename, uint64_t &spent, uint64_t &unspent) -+uint64_t wallet2::import_key_images(const std::string &filename, uint64_t &spent, uint64_t &unspent) { -+ std::string data; -+ -+ bool r = load_from_file(filename, data); -+ -+ THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, std::string(tr("failed to read file ")) + filename); -+ -+ return import_key_images_str(data, spent, unspent); -+} -+ -+uint64_t wallet2::import_key_images_str(const std::string &data, uint64_t &spent, uint64_t &unspent) - { - PERF_TIMER(import_key_images_fsu); -- std::string data; -- bool r = load_from_file(filename, data); -- -- THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, std::string(tr("failed to read file ")) + filename); -+ std::string data_local = data; - - const size_t magiclen = strlen(KEY_IMAGE_EXPORT_FILE_MAGIC); - if (data.size() < magiclen || memcmp(data.data(), KEY_IMAGE_EXPORT_FILE_MAGIC, magiclen)) - { -- THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string("Bad key image export file magic in ") + filename); -+ THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string("Bad key image export file magic")); - } - - try - { - PERF_TIMER(import_key_images_decrypt); -- data = decrypt_with_view_secret_key(std::string(data, magiclen)); -+ data_local = decrypt_with_view_secret_key(std::string(data, magiclen)); - } - catch (const std::exception &e) - { -- THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string("Failed to decrypt ") + filename + ": " + e.what()); -+ THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string("Failed to decrypt ") + ": " + e.what()); - } - - const size_t headerlen = 4 + 2 * sizeof(crypto::public_key); -- THROW_WALLET_EXCEPTION_IF(data.size() < headerlen, error::wallet_internal_error, std::string("Bad data size from file ") + filename); -- const uint32_t offset = (uint8_t)data[0] | (((uint8_t)data[1]) << 8) | (((uint8_t)data[2]) << 16) | (((uint8_t)data[3]) << 24); -- const crypto::public_key &public_spend_key = *(const crypto::public_key*)&data[4]; -- const crypto::public_key &public_view_key = *(const crypto::public_key*)&data[4 + sizeof(crypto::public_key)]; -+ THROW_WALLET_EXCEPTION_IF(data_local.size() < headerlen, error::wallet_internal_error, std::string("Bad data size from file ")); -+ const uint32_t offset = (uint8_t)data_local[0] | (((uint8_t)data_local[1]) << 8) | (((uint8_t)data_local[2]) << 16) | (((uint8_t)data_local[3]) << 24); -+ const crypto::public_key &public_spend_key = *(const crypto::public_key*)&data_local[4]; -+ const crypto::public_key &public_view_key = *(const crypto::public_key*)&data_local[4 + sizeof(crypto::public_key)]; - const cryptonote::account_public_address &keys = get_account().get_keys().m_account_address; - if (public_spend_key != keys.m_spend_public_key || public_view_key != keys.m_view_public_key) - { -- THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string( "Key images from ") + filename + " are for a different account"); -+ THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string( "Key images from ") + " are for a different account"); - } - THROW_WALLET_EXCEPTION_IF(offset > m_transfers.size(), error::wallet_internal_error, "Offset larger than known outputs"); - - const size_t record_size = sizeof(crypto::key_image) + sizeof(crypto::signature); -- THROW_WALLET_EXCEPTION_IF((data.size() - headerlen) % record_size, -- error::wallet_internal_error, std::string("Bad data size from file ") + filename); -- size_t nki = (data.size() - headerlen) / record_size; -+ THROW_WALLET_EXCEPTION_IF((data_local.size() - headerlen) % record_size, -+ error::wallet_internal_error, std::string("Bad data size from file ")); -+ size_t nki = (data_local.size() - headerlen) / record_size; - - std::vector> ski; - ski.reserve(nki); - for (size_t n = 0; n < nki; ++n) - { -- crypto::key_image key_image = *reinterpret_cast(&data[headerlen + n * record_size]); -- crypto::signature signature = *reinterpret_cast(&data[headerlen + n * record_size + sizeof(crypto::key_image)]); -+ crypto::key_image key_image = *reinterpret_cast(&data_local[headerlen + n * record_size]); -+ crypto::signature signature = *reinterpret_cast(&data_local[headerlen + n * record_size + sizeof(crypto::key_image)]); - - ski.push_back(std::make_pair(key_image, signature)); - } -diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h -index 022d0696f..746e2aeff 100644 ---- a/src/wallet/wallet2.h -+++ b/src/wallet/wallet2.h -@@ -1157,6 +1157,7 @@ private: - // locked & unlocked balance of given or current subaddress account - uint64_t balance(uint32_t subaddr_index_major, bool strict) const; - uint64_t unlocked_balance(uint32_t subaddr_index_major, bool strict, uint64_t *blocks_to_unlock = NULL, uint64_t *time_to_unlock = NULL); -+ uint64_t view_only_balance(uint32_t index_major, const std::vector& selected_inputs = {}); - // locked & unlocked balance per subaddress of given or current subaddress account - std::map balance_per_subaddress(uint32_t subaddr_index_major, bool strict) const; - std::map>> unlocked_balance_per_subaddress(uint32_t subaddr_index_major, bool strict); -@@ -1631,9 +1632,11 @@ private: - std::tuple> export_blockchain() const; - void import_blockchain(const std::tuple> &bc); - bool export_key_images(const std::string &filename, bool all = false) const; -+ std::string export_key_images_str(bool all) const; - std::pair>> export_key_images(bool all = false) const; - uint64_t import_key_images(const std::vector> &signed_key_images, size_t offset, uint64_t &spent, uint64_t &unspent, bool check_spent = true); - uint64_t import_key_images(const std::string &filename, uint64_t &spent, uint64_t &unspent); -+ uint64_t import_key_images_str(const std::string &data, uint64_t &spent, uint64_t &unspent); - bool import_key_images(std::vector key_images, size_t offset=0, boost::optional> selected_transfers=boost::none); - bool import_key_images(signed_tx_set & signed_tx, size_t offset=0, bool only_selected_transfers=false); - crypto::public_key get_tx_pub_key_from_received_outs(const tools::wallet2::transfer_details &td) const; --- -2.48.0 - diff --git a/patches/wownero/0007-add-dummy-device-for-ledger.patch b/patches/wownero/0007-add-dummy-device-for-ledger.patch new file mode 100644 index 0000000..7fd3100 --- /dev/null +++ b/patches/wownero/0007-add-dummy-device-for-ledger.patch @@ -0,0 +1,619 @@ +From be6bbe3958304e17566b2bc601255b0d6cec119f Mon Sep 17 00:00:00 2001 +From: Czarek Nakamoto +Date: Wed, 26 Jun 2024 15:04:38 +0200 +Subject: [PATCH 07/16] add dummy device for ledger + +--- + CMakeLists.txt | 19 +++-- + src/device/CMakeLists.txt | 6 +- + src/device/device.cpp | 10 ++- + src/device/device.hpp | 12 +-- + src/device/device_io_dummy.cpp | 133 ++++++++++++++++++++++++++++++ + src/device/device_io_dummy.hpp | 74 +++++++++++++++++ + src/device/device_ledger.cpp | 6 +- + src/device/device_ledger.hpp | 7 +- + src/wallet/api/wallet.cpp | 94 +++++++++++++++++++++ + src/wallet/api/wallet.h | 18 ++++ + src/wallet/api/wallet2_api.h | 12 +++ + src/wallet/api/wallet_manager.cpp | 12 ++- + 12 files changed, 372 insertions(+), 31 deletions(-) + create mode 100644 src/device/device_io_dummy.cpp + create mode 100644 src/device/device_io_dummy.hpp + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index c439e5300..86af78f10 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -693,16 +693,21 @@ include_directories(${LMDB_INCLUDE}) + include_directories(${LIBUNWIND_INCLUDE}) + link_directories(${LIBUNWIND_LIBRARY_DIRS}) + +-# Final setup for hid +-if (HIDAPI_FOUND) +- message(STATUS "Using HIDAPI include dir at ${HIDAPI_INCLUDE_DIR}") +- add_definitions(-DHAVE_HIDAPI) +- include_directories(${HIDAPI_INCLUDE_DIR}) +- link_directories(${LIBHIDAPI_LIBRARY_DIRS}) ++if (HIDAPI_DUMMY) ++ add_definitions(-DHIDAPI_DUMMY) + else() +- message(STATUS "Could not find HIDAPI") ++ # Final setup for hid ++ if (HIDAPI_FOUND) ++ message(STATUS "Using HIDAPI include dir at ${HIDAPI_INCLUDE_DIR}") ++ add_definitions(-DHAVE_HIDAPI) ++ include_directories(${HIDAPI_INCLUDE_DIR}) ++ link_directories(${LIBHIDAPI_LIBRARY_DIRS}) ++ else() ++ message(STATUS "Could not find HIDAPI") ++ endif() + endif() + ++ + # Trezor support check + include(CheckTrezor) + +diff --git a/src/device/CMakeLists.txt b/src/device/CMakeLists.txt +index e4f1159b5..14d398f87 100644 +--- a/src/device/CMakeLists.txt ++++ b/src/device/CMakeLists.txt +@@ -29,10 +29,11 @@ + set(device_sources + device.cpp + device_default.cpp ++ device_io_dummy.cpp + log.cpp + ) + +-if(HIDAPI_FOUND) ++if(HIDAPI_FOUND OR HIDAPI_DUMMY) + set(device_sources + ${device_sources} + device_ledger.cpp +@@ -45,10 +46,11 @@ set(device_headers + device_io.hpp + device_default.hpp + device_cold.hpp ++ device_io_dummy.hpp + log.hpp + ) + +-if(HIDAPI_FOUND) ++if(HIDAPI_FOUND OR HIDAPI_DUMMY) + set(device_headers + ${device_headers} + device_ledger.hpp +diff --git a/src/device/device.cpp b/src/device/device.cpp +index e6cd358b6..dd0701e0c 100644 +--- a/src/device/device.cpp ++++ b/src/device/device.cpp +@@ -29,7 +29,7 @@ + + #include "device.hpp" + #include "device_default.hpp" +-#ifdef WITH_DEVICE_LEDGER ++#if defined(WITH_DEVICE_LEDGER) || defined(HIDAPI_DUMMY) + #include "device_ledger.hpp" + #endif + #include "misc_log_ex.h" +@@ -57,7 +57,7 @@ namespace hw { + + device_registry::device_registry(){ + hw::core::register_all(registry); +- #ifdef WITH_DEVICE_LEDGER ++ #if defined(WITH_DEVICE_LEDGER) || defined(HIDAPI_DUMMY) + hw::ledger::register_all(registry); + #endif + atexit(clear_device_registry); +@@ -83,11 +83,13 @@ namespace hw { + + auto device = registry.find(device_descriptor_lookup); + if (device == registry.end()) { +- MERROR("Device not found in registry: '" << device_descriptor << "'. Known devices: "); ++ std::stringstream ss("Device not found in registry: '" + device_descriptor + "'. Known devices: "); ++ MERROR("Device not found in registry: '" << device_descriptor << "'. Known devices: \n"); + for( const auto& sm_pair : registry ) { ++ ss << "\n- " + sm_pair.first; + MERROR(" - " << sm_pair.first); + } +- throw std::runtime_error("device not found: " + device_descriptor); ++ throw std::runtime_error("device not found: " + device_descriptor + "\n" + ss.str()); + } + return *device->second; + } +diff --git a/src/device/device.hpp b/src/device/device.hpp +index 392703a24..ffd419779 100644 +--- a/src/device/device.hpp ++++ b/src/device/device.hpp +@@ -34,17 +34,7 @@ + #include "ringct/rctTypes.h" + #include "cryptonote_config.h" + +- +-#ifndef USE_DEVICE_LEDGER +-#define USE_DEVICE_LEDGER 1 +-#endif +- +-#if !defined(HAVE_HIDAPI) +-#undef USE_DEVICE_LEDGER +-#define USE_DEVICE_LEDGER 0 +-#endif +- +-#if USE_DEVICE_LEDGER ++#if defined(HAVE_HIDAPI) || defined(HIDAPI_DUMMY) + #define WITH_DEVICE_LEDGER + #endif + +diff --git a/src/device/device_io_dummy.cpp b/src/device/device_io_dummy.cpp +new file mode 100644 +index 000000000..f91e10651 +--- /dev/null ++++ b/src/device/device_io_dummy.cpp +@@ -0,0 +1,133 @@ ++// Copyright (c) 2017-2022, 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. ++// ++ ++// device_io_dummy ++// Main goal of device_io_dummy is to emulate a hw::io::device_io without the need to actually ++// connect a device. ++// Many operating systems do not support giving raw USB access to a process (android), or don't ++// support that at all (hi iOS), therefore other means of connection can be used, either USB ++// abstraction provided by the OS (monerujo), or BLE (also monerujo). ++// Monerujo implementation is written in Java, which makes it a nice fit for iOS, but makes the ++// code extremely unportable, so for this reason the code in here is written in CPP. ++// Data transport is made available in wallet2_api.h, so wallet developers can easily plug their ++// own USB/BLE/other transport layer. ++ ++#if defined(HIDAPI_DUMMY) && !defined(HAVE_HIDAPI) ++#include ++#include "log.hpp" ++#include "device_io_dummy.hpp" ++#include "device_ledger.hpp" ++ ++ ++bool hw::io::device_io_dummy::stateIsConnected = false; ++unsigned char* hw::io::device_io_dummy::sendToDevice = {}; ++size_t hw::io::device_io_dummy::sendToDeviceLength = 0; ++unsigned char* hw::io::device_io_dummy::receivedFromDevice = {}; ++size_t hw::io::device_io_dummy::receivedFromDeviceLength = 0; ++bool hw::io::device_io_dummy::waitsForDeviceSend = false; ++bool hw::io::device_io_dummy::waitsForDeviceReceive = false; ++ ++namespace hw { ++ namespace io { ++ ++#undef MONERO_DEFAULT_LOG_CATEGORY ++#define MONERO_DEFAULT_LOG_CATEGORY "device.io_dummy" ++ device_io_dummy::device_io_dummy(int a, int b, int c, int d) { ++ MDEBUG("device_io_dummy(a: " << a << ", b: " << b << ", c: " << c << ", d: " << d <<")"); ++ } ++ ++ void device_io_dummy::init() { ++ MDEBUG("init()"); ++ } ++ ++ void device_io_dummy::connect(void *params) { ++ MDEBUG("connect(" << params << ")"); ++ stateIsConnected = true; ++ } ++ ++ void device_io_dummy::connect(const std::vector& known_devices) { ++ MDEBUG("connect(["); ++ for (const auto &item: known_devices) { ++ MDEBUG("{ interface_number: " << item.interface_number); ++ MDEBUG(" pid : " << item.pid); ++ MDEBUG(" usage_page : " << item.usage_page); ++ MDEBUG(" vid : " << item.vid << " },"); ++ } ++ MDEBUG("])"); ++ stateIsConnected = true; ++ } ++ ++ bool device_io_dummy::connected() const { ++ MDEBUG("connected()"); ++ return stateIsConnected; ++ } ++ ++ int device_io_dummy::exchange(unsigned char *command, unsigned int cmd_len, unsigned char *response, unsigned int max_resp_len, bool user_input) { ++ MDEBUG("exchange(): locking mutex"); ++ boost::unique_lock lock(mutex); ++ sendToDevice = command; ++ sendToDeviceLength = cmd_len; ++ waitsForDeviceSend = true; ++ waitsForDeviceReceive = true; ++ MDEBUG("exchange(): waitsForDeviceSend"); ++ // NOTE: waitsForDeviceSend should be changed by external code ++ while (waitsForDeviceSend) { ++ std::this_thread::sleep_for(std::chrono::microseconds(1000)); ++ MDEBUG("exchange(): waitsForDeviceSend (still)"); ++ } ++ ++ MDEBUG("exchange(): waitsForDeviceReceive"); ++ while (waitsForDeviceReceive) { ++ std::this_thread::sleep_for(std::chrono::microseconds(1000)); ++ MDEBUG("exchange(): waitsForDeviceReceive (still)"); ++ } ++ ++ if (receivedFromDeviceLength > max_resp_len) { ++ MDEBUG("exchange(): receivedFromDeviceLength ("<& known_devices); ++ void disconnect(); ++ bool connected() const; ++ ++ int exchange(unsigned char *command, unsigned int cmd_len, unsigned char *response, unsigned int max_resp_len, bool user_input); ++ }; ++ }; ++}; ++ ++#endif // HAVE_HIDAPI +diff --git a/src/device/device_ledger.cpp b/src/device/device_ledger.cpp +index e1bfa7041..0139f1577 100644 +--- a/src/device/device_ledger.cpp ++++ b/src/device/device_ledger.cpp +@@ -41,7 +41,7 @@ namespace hw { + + namespace ledger { + +- #ifdef WITH_DEVICE_LEDGER ++ #if defined(WITH_DEVICE_LEDGER) || defined(HIDAPI_DUMMY) + + #undef MONERO_DEFAULT_LOG_CATEGORY + #define MONERO_DEFAULT_LOG_CATEGORY "device.ledger" +@@ -299,7 +299,7 @@ namespace hw { + + device_ledger::device_ledger(): hw_device(0x0101, 0x05, 64, 2000) { + this->id = device_id++; +- this->reset_buffer(); ++ this->reset_buffer(); + this->mode = NONE; + this->has_view_key = false; + this->tx_in_progress = false; +@@ -533,7 +533,9 @@ namespace hw { + + bool device_ledger::connect(void) { + this->disconnect(); ++ #if !(defined(HIDAPI_DUMMY) && !defined(HAVE_HIDAPI)) + hw_device.connect(known_devices); ++ #endif + this->reset(); + #ifdef DEBUG_HWDEVICE + cryptonote::account_public_address pubkey; +diff --git a/src/device/device_ledger.hpp b/src/device/device_ledger.hpp +index 03058c4f1..39454ca6d 100644 +--- a/src/device/device_ledger.hpp ++++ b/src/device/device_ledger.hpp +@@ -35,6 +35,7 @@ + #include "device.hpp" + #include "log.hpp" + #include "device_io_hid.hpp" ++#include "device_io_dummy.hpp" + #include + #include + +@@ -56,7 +57,7 @@ namespace hw { + + void register_all(std::map> ®istry); + +- #ifdef WITH_DEVICE_LEDGER ++ #if defined(WITH_DEVICE_LEDGER) || defined(HIDAPI_DUMMY) + + // Origin: https://github.com/LedgerHQ/ledger-app-monero/blob/master/src/monero_types.h + #define SW_OK 0x9000 +@@ -148,7 +149,11 @@ namespace hw { + mutable boost::mutex command_locker; + + //IO ++#if defined(HIDAPI_DUMMY) && !defined(HAVE_HIDAPI) ++ hw::io::device_io_dummy hw_device; ++#else + hw::io::device_io_hid hw_device; ++#endif + unsigned int length_send; + unsigned char buffer_send[BUFFER_SEND_SIZE]; + unsigned int length_recv; +diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp +index e04205e48..aec76ffc0 100644 +--- a/src/wallet/api/wallet.cpp ++++ b/src/wallet/api/wallet.cpp +@@ -48,6 +48,9 @@ + #include + #include + #include "bc-ur/src/bc-ur.hpp" ++#if defined(HIDAPI_DUMMY) && !defined(HAVE_HIDAPI) ++#include "device/device_io_dummy.hpp" ++#endif + + using namespace std; + using namespace cryptonote; +@@ -3178,4 +3181,95 @@ uint64_t WalletImpl::getBytesSent() + return m_wallet->get_bytes_sent(); + } + ++ ++// HIDAPI_DUMMY ++bool WalletImpl::getStateIsConnected() { ++ #if !(defined(HIDAPI_DUMMY) && !defined(HAVE_HIDAPI)) ++ setStatusError("MONERO compiled with #if !(defined(HIDAPI_DUMMY) && !defined(HAVE_HIDAPI))"); ++ return false; ++ #else ++ return hw::io::device_io_dummy::stateIsConnected; ++ #endif ++} ++ ++unsigned char* WalletImpl::getSendToDevice() { ++ #if !(defined(HIDAPI_DUMMY) && !defined(HAVE_HIDAPI)) ++ setStatusError("MONERO compiled with #if !(defined(HIDAPI_DUMMY) && !defined(HAVE_HIDAPI))"); ++ return {}; ++ #else ++ return hw::io::device_io_dummy::sendToDevice; ++ #endif ++} ++ ++size_t WalletImpl::getSendToDeviceLength() { ++ #if !(defined(HIDAPI_DUMMY) && !defined(HAVE_HIDAPI)) ++ setStatusError("MONERO compiled with #if !(defined(HIDAPI_DUMMY) && !defined(HAVE_HIDAPI))"); ++ return -1; ++ #else ++ return hw::io::device_io_dummy::sendToDeviceLength; ++ #endif ++} ++ ++unsigned char* WalletImpl::getReceivedFromDevice() { ++ #if !(defined(HIDAPI_DUMMY) && !defined(HAVE_HIDAPI)) ++ setStatusError("MONERO compiled with #if !(defined(HIDAPI_DUMMY) && !defined(HAVE_HIDAPI))"); ++ return {}; ++ #else ++ return hw::io::device_io_dummy::receivedFromDevice; ++ #endif ++} ++ ++size_t WalletImpl::getReceivedFromDeviceLength() { ++ #if !(defined(HIDAPI_DUMMY) && !defined(HAVE_HIDAPI)) ++ setStatusError("MONERO compiled with #if !(defined(HIDAPI_DUMMY) && !defined(HAVE_HIDAPI))"); ++ return -1; ++ #else ++ return hw::io::device_io_dummy::receivedFromDeviceLength; ++ #endif ++} ++ ++bool WalletImpl::getWaitsForDeviceSend() { ++ #if !(defined(HIDAPI_DUMMY) && !defined(HAVE_HIDAPI)) ++ setStatusError("MONERO compiled with #if !(defined(HIDAPI_DUMMY) && !defined(HAVE_HIDAPI))"); ++ return false; ++ #else ++ return hw::io::device_io_dummy::receivedFromDeviceLength; ++ #endif ++} ++ ++bool WalletImpl::getWaitsForDeviceReceive() { ++ #if !(defined(HIDAPI_DUMMY) && !defined(HAVE_HIDAPI)) ++ setStatusError("MONERO compiled with #if !(defined(HIDAPI_DUMMY) && !defined(HAVE_HIDAPI))"); ++ return false; ++ #else ++ return hw::io::device_io_dummy::waitsForDeviceReceive; ++ #endif ++} ++ ++void WalletImpl::setDeviceReceivedData(unsigned char* data, size_t len) { ++ #if !(defined(HIDAPI_DUMMY) && !defined(HAVE_HIDAPI)) ++ setStatusError("MONERO compiled with #if !(defined(HIDAPI_DUMMY) && !defined(HAVE_HIDAPI))"); ++ return; ++ #else ++ hw::io::device_io_dummy::receivedFromDevice = static_cast(malloc(len)); ++ hw::io::device_io_dummy::receivedFromDeviceLength = len; ++ memset(hw::io::device_io_dummy::receivedFromDevice, 0, len); ++ memcpy(hw::io::device_io_dummy::receivedFromDevice, data, len); ++ hw::io::device_io_dummy::waitsForDeviceReceive = false; ++ #endif ++} ++ ++void WalletImpl::setDeviceSendData(unsigned char* data, size_t len) { ++ #if !(defined(HIDAPI_DUMMY) && !defined(HAVE_HIDAPI)) ++ setStatusError("MONERO compiled with #if !(defined(HIDAPI_DUMMY) && !defined(HAVE_HIDAPI))"); ++ return; ++ #else ++ hw::io::device_io_dummy::sendToDevice = static_cast(malloc(len)); ++ hw::io::device_io_dummy::sendToDeviceLength = len; ++ memset(hw::io::device_io_dummy::sendToDevice, 0, len); ++ memcpy(hw::io::device_io_dummy::sendToDevice, data, len); ++ hw::io::device_io_dummy::waitsForDeviceSend = false; ++ #endif ++} ++ + } // namespace +diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h +index edf8bb8ce..4e9c21ecb 100644 +--- a/src/wallet/api/wallet.h ++++ b/src/wallet/api/wallet.h +@@ -301,6 +301,24 @@ private: + // cache connection status to avoid unnecessary RPC calls + mutable std::atomic m_is_connected; + boost::optional m_daemon_login{}; ++ ++ bool getStateIsConnected(); ++ ++ unsigned char *getSendToDevice(); ++ ++ size_t getSendToDeviceLength(); ++ ++ unsigned char *getReceivedFromDevice(); ++ ++ size_t getReceivedFromDeviceLength(); ++ ++ bool getWaitsForDeviceSend(); ++ ++ bool getWaitsForDeviceReceive(); ++ ++ void setDeviceReceivedData(unsigned char *data, size_t len); ++ ++ void setDeviceSendData(unsigned char *data, size_t len); + }; + + +diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h +index 764adbfbf..53ec4abfc 100644 +--- a/src/wallet/api/wallet2_api.h ++++ b/src/wallet/api/wallet2_api.h +@@ -1150,6 +1150,18 @@ struct Wallet + + //! get bytes sent + virtual uint64_t getBytesSent() = 0; ++ ++ // HIDAPI_DUMMY ++ virtual bool getStateIsConnected() = 0; ++ virtual unsigned char* getSendToDevice() = 0; ++ virtual size_t getSendToDeviceLength() = 0; ++ virtual unsigned char* getReceivedFromDevice() = 0; ++ virtual size_t getReceivedFromDeviceLength() = 0; ++ virtual bool getWaitsForDeviceSend() = 0; ++ virtual bool getWaitsForDeviceReceive() = 0; ++ ++ virtual void setDeviceReceivedData(unsigned char* data, size_t len) = 0; ++ virtual void setDeviceSendData(unsigned char* data, size_t len) = 0; + }; + + /** +diff --git a/src/wallet/api/wallet_manager.cpp b/src/wallet/api/wallet_manager.cpp +index e81b8f83a..277be6ac9 100644 +--- a/src/wallet/api/wallet_manager.cpp ++++ b/src/wallet/api/wallet_manager.cpp +@@ -188,10 +188,14 @@ bool WalletManagerImpl::verifyWalletPassword(const std::string &keys_file_name, + + bool WalletManagerImpl::queryWalletDevice(Wallet::Device& device_type, const std::string &keys_file_name, const std::string &password, uint64_t kdf_rounds) const + { +- hw::device::device_type type; +- bool r = tools::wallet2::query_device(type, keys_file_name, password, kdf_rounds); +- device_type = static_cast(type); +- return r; ++ try { ++ hw::device::device_type type; ++ bool r = tools::wallet2::query_device(type, keys_file_name, password, kdf_rounds); ++ device_type = static_cast(type); ++ return r; ++ } catch (...) { ++ return false; ++ } + } + + std::vector WalletManagerImpl::findWallets(const std::string &path) +-- +2.51.0 + diff --git a/patches/wownero/0008-add-dummy-device-for-ledger.patch b/patches/wownero/0008-add-dummy-device-for-ledger.patch deleted file mode 100644 index 47b0cc7..0000000 --- a/patches/wownero/0008-add-dummy-device-for-ledger.patch +++ /dev/null @@ -1,619 +0,0 @@ -From 221fb7ed81aff1830b4bc2d7f9b73c94eb36924a Mon Sep 17 00:00:00 2001 -From: Czarek Nakamoto -Date: Wed, 26 Jun 2024 15:04:38 +0200 -Subject: [PATCH 08/15] add dummy device for ledger - ---- - CMakeLists.txt | 6 +- - src/device/CMakeLists.txt | 6 +- - src/device/device.cpp | 10 ++- - src/device/device.hpp | 12 +-- - src/device/device_io_dummy.cpp | 133 ++++++++++++++++++++++++++++++ - src/device/device_io_dummy.hpp | 74 +++++++++++++++++ - src/device/device_ledger.cpp | 6 +- - src/device/device_ledger.hpp | 7 +- - src/wallet/api/wallet.cpp | 94 +++++++++++++++++++++ - src/wallet/api/wallet.h | 18 ++++ - src/wallet/api/wallet2_api.h | 12 +++ - src/wallet/api/wallet_manager.cpp | 12 ++- - 12 files changed, 365 insertions(+), 25 deletions(-) - create mode 100644 src/device/device_io_dummy.cpp - create mode 100644 src/device/device_io_dummy.hpp - -diff --git a/CMakeLists.txt b/CMakeLists.txt -index c73b813d8..ce5ef4bab 100644 ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -692,16 +692,21 @@ include_directories(${LMDB_INCLUDE}) - include_directories(${LIBUNWIND_INCLUDE}) - link_directories(${LIBUNWIND_LIBRARY_DIRS}) - --# Final setup for hid --if (HIDAPI_FOUND) -- message(STATUS "Using HIDAPI include dir at ${HIDAPI_INCLUDE_DIR}") -- add_definitions(-DHAVE_HIDAPI) -- include_directories(${HIDAPI_INCLUDE_DIR}) -- link_directories(${LIBHIDAPI_LIBRARY_DIRS}) -+if (HIDAPI_DUMMY) -+ add_definitions(-DHIDAPI_DUMMY) - else() -- message(STATUS "Could not find HIDAPI") -+ # Final setup for hid -+ if (HIDAPI_FOUND) -+ message(STATUS "Using HIDAPI include dir at ${HIDAPI_INCLUDE_DIR}") -+ add_definitions(-DHAVE_HIDAPI) -+ include_directories(${HIDAPI_INCLUDE_DIR}) -+ link_directories(${LIBHIDAPI_LIBRARY_DIRS}) -+ else() -+ message(STATUS "Could not find HIDAPI") -+ endif() - endif() - -+ - # Trezor support check - include(CheckTrezor) - -diff --git a/src/device/CMakeLists.txt b/src/device/CMakeLists.txt -index e4f1159b5..14d398f87 100644 ---- a/src/device/CMakeLists.txt -+++ b/src/device/CMakeLists.txt -@@ -29,10 +29,11 @@ - set(device_sources - device.cpp - device_default.cpp -+ device_io_dummy.cpp - log.cpp - ) - --if(HIDAPI_FOUND) -+if(HIDAPI_FOUND OR HIDAPI_DUMMY) - set(device_sources - ${device_sources} - device_ledger.cpp -@@ -45,10 +46,11 @@ set(device_headers - device_io.hpp - device_default.hpp - device_cold.hpp -+ device_io_dummy.hpp - log.hpp - ) - --if(HIDAPI_FOUND) -+if(HIDAPI_FOUND OR HIDAPI_DUMMY) - set(device_headers - ${device_headers} - device_ledger.hpp -diff --git a/src/device/device.cpp b/src/device/device.cpp -index e6cd358b6..777584c01 100644 ---- a/src/device/device.cpp -+++ b/src/device/device.cpp -@@ -29,7 +29,7 @@ - - #include "device.hpp" - #include "device_default.hpp" --#ifdef WITH_DEVICE_LEDGER -+#if defined(WITH_DEVICE_LEDGER) || defined(HIDAPI_DUMMY) - #include "device_ledger.hpp" - #endif - #include "misc_log_ex.h" -@@ -57,7 +57,7 @@ namespace hw { - - device_registry::device_registry(){ - hw::core::register_all(registry); -- #ifdef WITH_DEVICE_LEDGER -+ #if defined(WITH_DEVICE_LEDGER) || defined(HIDAPI_DUMMY) - hw::ledger::register_all(registry); - #endif - atexit(clear_device_registry); -@@ -83,11 +83,13 @@ namespace hw { - - auto device = registry.find(device_descriptor_lookup); - if (device == registry.end()) { -- MERROR("Device not found in registry: '" << device_descriptor << "'. Known devices: "); -+ std::stringstream ss("Device not found in registry: '" + device_descriptor + "'. Known devices: "); -+ MERROR("Device not found in registry: '" << device_descriptor << "'. Known devices: \n"); - for( const auto& sm_pair : registry ) { -+ ss << "\n- " + sm_pair.first; - MERROR(" - " << sm_pair.first); - } -- throw std::runtime_error("device not found: " + device_descriptor); -+ throw std::runtime_error("device not found: " + device_descriptor + "\n" + ss.str()); - } - return *device->second; - } -diff --git a/src/device/device.hpp b/src/device/device.hpp -index 392703a24..ffd419779 100644 ---- a/src/device/device.hpp -+++ b/src/device/device.hpp -@@ -34,17 +34,7 @@ - #include "ringct/rctTypes.h" - #include "cryptonote_config.h" - -- --#ifndef USE_DEVICE_LEDGER --#define USE_DEVICE_LEDGER 1 --#endif -- --#if !defined(HAVE_HIDAPI) --#undef USE_DEVICE_LEDGER --#define USE_DEVICE_LEDGER 0 --#endif -- --#if USE_DEVICE_LEDGER -+#if defined(HAVE_HIDAPI) || defined(HIDAPI_DUMMY) - #define WITH_DEVICE_LEDGER - #endif - -diff --git a/src/device/device_io_dummy.cpp b/src/device/device_io_dummy.cpp -new file mode 100644 -index 000000000..edb4beea3 ---- /dev/null -+++ b/src/device/device_io_dummy.cpp -@@ -0,0 +1,133 @@ -+// Copyright (c) 2017-2022, 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. -+// -+ -+// device_io_dummy -+// Main goal of device_io_dummy is to emulate a hw::io::device_io without the need to actually -+// connect a device. -+// Many operating systems do not support giving raw USB access to a process (android), or don't -+// support that at all (hi iOS), therefore other means of connection can be used, either USB -+// abstraction provided by the OS (monerujo), or BLE (also monerujo). -+// Monerujo implementation is written in Java, which makes it a nice fit for iOS, but makes the -+// code extremely unportable, so for this reason the code in here is written in CPP. -+// Data transport is made available in wallet2_api.h, so wallet developers can easily plug their -+// own USB/BLE/other transport layer. -+ -+#if defined(HIDAPI_DUMMY) && !defined(HAVE_HIDAPI) -+#include -+#include "log.hpp" -+#include "device_io_dummy.hpp" -+#include "device_ledger.hpp" -+ -+ -+bool hw::io::device_io_dummy::stateIsConnected = false; -+unsigned char* hw::io::device_io_dummy::sendToDevice = {}; -+size_t hw::io::device_io_dummy::sendToDeviceLength = 0; -+unsigned char* hw::io::device_io_dummy::receivedFromDevice = {}; -+size_t hw::io::device_io_dummy::receivedFromDeviceLength = 0; -+bool hw::io::device_io_dummy::waitsForDeviceSend = false; -+bool hw::io::device_io_dummy::waitsForDeviceReceive = false; -+ -+namespace hw { -+ namespace io { -+ -+#undef MONERO_DEFAULT_LOG_CATEGORY -+#define MONERO_DEFAULT_LOG_CATEGORY "device.io_dummy" -+ device_io_dummy::device_io_dummy(int a, int b, int c, int d) { -+ MDEBUG("device_io_dummy(a: " << a << ", b: " << b << ", c: " << c << ", d: " << d <<")"); -+ } -+ -+ void device_io_dummy::init() { -+ MDEBUG("init()"); -+ } -+ -+ void device_io_dummy::connect(void *params) { -+ MDEBUG("connect(" << params << ")"); -+ stateIsConnected = true; -+ } -+ -+ void device_io_dummy::connect(const std::vector& known_devices) { -+ MDEBUG("connect(["); -+ for (const auto &item: known_devices) { -+ MDEBUG("{ interface_number: " << item.interface_number); -+ MDEBUG(" pid : " << item.pid); -+ MDEBUG(" usage_page : " << item.usage_page); -+ MDEBUG(" vid : " << item.vid << " },"); -+ } -+ MDEBUG("])"); -+ stateIsConnected = true; -+ } -+ -+ bool device_io_dummy::connected() const { -+ MDEBUG("connected()"); -+ return stateIsConnected; -+ } -+ -+ int device_io_dummy::exchange(unsigned char *command, unsigned int cmd_len, unsigned char *response, unsigned int max_resp_len, bool user_input) { -+ MDEBUG("exchange(): locking mutex"); -+ boost::unique_lock lock(mutex); -+ sendToDevice = command; -+ sendToDeviceLength = cmd_len; -+ waitsForDeviceSend = true; -+ waitsForDeviceReceive = true; -+ MDEBUG("exchange(): waitsForDeviceSend"); -+ // NOTE: waitsForDeviceSend should be changed by external code -+ while (waitsForDeviceSend) { -+ std::this_thread::sleep_for(std::chrono::microseconds(1000)); -+ MDEBUG("exchange(): waitsForDeviceSend (still)"); -+ } -+ -+ MDEBUG("exchange(): waitsForDeviceReceive"); -+ while (waitsForDeviceReceive) { -+ std::this_thread::sleep_for(std::chrono::microseconds(1000)); -+ MDEBUG("exchange(): waitsForDeviceReceive (still)"); -+ } -+ -+ if (receivedFromDeviceLength > max_resp_len) { -+ MDEBUG("exchange(): receivedFromDeviceLength ("<& known_devices); -+ void disconnect(); -+ bool connected() const; -+ -+ int exchange(unsigned char *command, unsigned int cmd_len, unsigned char *response, unsigned int max_resp_len, bool user_input); -+ }; -+ }; -+}; -+ -+#endif // HAVE_HIDAPI -diff --git a/src/device/device_ledger.cpp b/src/device/device_ledger.cpp -index 8403d76e8..0587eb7d3 100644 ---- a/src/device/device_ledger.cpp -+++ b/src/device/device_ledger.cpp -@@ -41,7 +41,7 @@ namespace hw { - - namespace ledger { - -- #ifdef WITH_DEVICE_LEDGER -+ #if defined(WITH_DEVICE_LEDGER) || defined(HIDAPI_DUMMY) - - #undef MONERO_DEFAULT_LOG_CATEGORY - #define MONERO_DEFAULT_LOG_CATEGORY "device.ledger" -@@ -299,7 +299,7 @@ namespace hw { - - device_ledger::device_ledger(): hw_device(0x0101, 0x05, 64, 2000) { - this->id = device_id++; -- this->reset_buffer(); -+ this->reset_buffer(); - this->mode = NONE; - this->has_view_key = false; - this->tx_in_progress = false; -@@ -533,7 +533,9 @@ namespace hw { - - bool device_ledger::connect(void) { - this->disconnect(); -+ #if !(defined(HIDAPI_DUMMY) && !defined(HAVE_HIDAPI)) - hw_device.connect(known_devices); -+ #endif - this->reset(); - #ifdef DEBUG_HWDEVICE - cryptonote::account_public_address pubkey; -diff --git a/src/device/device_ledger.hpp b/src/device/device_ledger.hpp -index 03058c4f1..39454ca6d 100644 ---- a/src/device/device_ledger.hpp -+++ b/src/device/device_ledger.hpp -@@ -35,6 +35,7 @@ - #include "device.hpp" - #include "log.hpp" - #include "device_io_hid.hpp" -+#include "device_io_dummy.hpp" - #include - #include - -@@ -56,7 +57,7 @@ namespace hw { - - void register_all(std::map> ®istry); - -- #ifdef WITH_DEVICE_LEDGER -+ #if defined(WITH_DEVICE_LEDGER) || defined(HIDAPI_DUMMY) - - // Origin: https://github.com/LedgerHQ/ledger-app-monero/blob/master/src/monero_types.h - #define SW_OK 0x9000 -@@ -148,7 +149,11 @@ namespace hw { - mutable boost::mutex command_locker; - - //IO -+#if defined(HIDAPI_DUMMY) && !defined(HAVE_HIDAPI) -+ hw::io::device_io_dummy hw_device; -+#else - hw::io::device_io_hid hw_device; -+#endif - unsigned int length_send; - unsigned char buffer_send[BUFFER_SEND_SIZE]; - unsigned int length_recv; -diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp -index ee000e7ab..556e2a8ce 100644 ---- a/src/wallet/api/wallet.cpp -+++ b/src/wallet/api/wallet.cpp -@@ -48,6 +48,9 @@ - #include - #include - #include "bc-ur/src/bc-ur.hpp" -+#if defined(HIDAPI_DUMMY) && !defined(HAVE_HIDAPI) -+#include "device/device_io_dummy.hpp" -+#endif - - using namespace std; - using namespace cryptonote; -@@ -3178,4 +3181,95 @@ uint64_t WalletImpl::getBytesSent() - return m_wallet->get_bytes_sent(); - } - -+ -+// HIDAPI_DUMMY -+bool WalletImpl::getStateIsConnected() { -+ #if !(defined(HIDAPI_DUMMY) && !defined(HAVE_HIDAPI)) -+ setStatusError("MONERO compiled with #if !(defined(HIDAPI_DUMMY) && !defined(HAVE_HIDAPI))"); -+ return false; -+ #else -+ return hw::io::device_io_dummy::stateIsConnected; -+ #endif -+} -+ -+unsigned char* WalletImpl::getSendToDevice() { -+ #if !(defined(HIDAPI_DUMMY) && !defined(HAVE_HIDAPI)) -+ setStatusError("MONERO compiled with #if !(defined(HIDAPI_DUMMY) && !defined(HAVE_HIDAPI))"); -+ return {}; -+ #else -+ return hw::io::device_io_dummy::sendToDevice; -+ #endif -+} -+ -+size_t WalletImpl::getSendToDeviceLength() { -+ #if !(defined(HIDAPI_DUMMY) && !defined(HAVE_HIDAPI)) -+ setStatusError("MONERO compiled with #if !(defined(HIDAPI_DUMMY) && !defined(HAVE_HIDAPI))"); -+ return -1; -+ #else -+ return hw::io::device_io_dummy::sendToDeviceLength; -+ #endif -+} -+ -+unsigned char* WalletImpl::getReceivedFromDevice() { -+ #if !(defined(HIDAPI_DUMMY) && !defined(HAVE_HIDAPI)) -+ setStatusError("MONERO compiled with #if !(defined(HIDAPI_DUMMY) && !defined(HAVE_HIDAPI))"); -+ return {}; -+ #else -+ return hw::io::device_io_dummy::receivedFromDevice; -+ #endif -+} -+ -+size_t WalletImpl::getReceivedFromDeviceLength() { -+ #if !(defined(HIDAPI_DUMMY) && !defined(HAVE_HIDAPI)) -+ setStatusError("MONERO compiled with #if !(defined(HIDAPI_DUMMY) && !defined(HAVE_HIDAPI))"); -+ return -1; -+ #else -+ return hw::io::device_io_dummy::receivedFromDeviceLength; -+ #endif -+} -+ -+bool WalletImpl::getWaitsForDeviceSend() { -+ #if !(defined(HIDAPI_DUMMY) && !defined(HAVE_HIDAPI)) -+ setStatusError("MONERO compiled with #if !(defined(HIDAPI_DUMMY) && !defined(HAVE_HIDAPI))"); -+ return false; -+ #else -+ return hw::io::device_io_dummy::receivedFromDeviceLength; -+ #endif -+} -+ -+bool WalletImpl::getWaitsForDeviceReceive() { -+ #if !(defined(HIDAPI_DUMMY) && !defined(HAVE_HIDAPI)) -+ setStatusError("MONERO compiled with #if !(defined(HIDAPI_DUMMY) && !defined(HAVE_HIDAPI))"); -+ return false; -+ #else -+ return hw::io::device_io_dummy::waitsForDeviceReceive; -+ #endif -+} -+ -+void WalletImpl::setDeviceReceivedData(unsigned char* data, size_t len) { -+ #if !(defined(HIDAPI_DUMMY) && !defined(HAVE_HIDAPI)) -+ setStatusError("MONERO compiled with #if !(defined(HIDAPI_DUMMY) && !defined(HAVE_HIDAPI))"); -+ return; -+ #else -+ hw::io::device_io_dummy::receivedFromDevice = static_cast(malloc(len)); -+ hw::io::device_io_dummy::receivedFromDeviceLength = len; -+ memset(hw::io::device_io_dummy::receivedFromDevice, 0, len); -+ memcpy(hw::io::device_io_dummy::receivedFromDevice, data, len); -+ hw::io::device_io_dummy::waitsForDeviceReceive = false; -+ #endif -+} -+ -+void WalletImpl::setDeviceSendData(unsigned char* data, size_t len) { -+ #if !(defined(HIDAPI_DUMMY) && !defined(HAVE_HIDAPI)) -+ setStatusError("MONERO compiled with #if !(defined(HIDAPI_DUMMY) && !defined(HAVE_HIDAPI))"); -+ return; -+ #else -+ hw::io::device_io_dummy::sendToDevice = static_cast(malloc(len)); -+ hw::io::device_io_dummy::sendToDeviceLength = len; -+ memset(hw::io::device_io_dummy::sendToDevice, 0, len); -+ memcpy(hw::io::device_io_dummy::sendToDevice, data, len); -+ hw::io::device_io_dummy::waitsForDeviceSend = false; -+ #endif -+} -+ - } // namespace -diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h -index edf8bb8ce..4e9c21ecb 100644 ---- a/src/wallet/api/wallet.h -+++ b/src/wallet/api/wallet.h -@@ -301,6 +301,24 @@ private: - // cache connection status to avoid unnecessary RPC calls - mutable std::atomic m_is_connected; - boost::optional m_daemon_login{}; -+ -+ bool getStateIsConnected(); -+ -+ unsigned char *getSendToDevice(); -+ -+ size_t getSendToDeviceLength(); -+ -+ unsigned char *getReceivedFromDevice(); -+ -+ size_t getReceivedFromDeviceLength(); -+ -+ bool getWaitsForDeviceSend(); -+ -+ bool getWaitsForDeviceReceive(); -+ -+ void setDeviceReceivedData(unsigned char *data, size_t len); -+ -+ void setDeviceSendData(unsigned char *data, size_t len); - }; - - -diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h -index 764adbfbf..53ec4abfc 100644 ---- a/src/wallet/api/wallet2_api.h -+++ b/src/wallet/api/wallet2_api.h -@@ -1150,6 +1150,18 @@ struct Wallet - - //! get bytes sent - virtual uint64_t getBytesSent() = 0; -+ -+ // HIDAPI_DUMMY -+ virtual bool getStateIsConnected() = 0; -+ virtual unsigned char* getSendToDevice() = 0; -+ virtual size_t getSendToDeviceLength() = 0; -+ virtual unsigned char* getReceivedFromDevice() = 0; -+ virtual size_t getReceivedFromDeviceLength() = 0; -+ virtual bool getWaitsForDeviceSend() = 0; -+ virtual bool getWaitsForDeviceReceive() = 0; -+ -+ virtual void setDeviceReceivedData(unsigned char* data, size_t len) = 0; -+ virtual void setDeviceSendData(unsigned char* data, size_t len) = 0; - }; - - /** -diff --git a/src/wallet/api/wallet_manager.cpp b/src/wallet/api/wallet_manager.cpp -index e81b8f83a..277be6ac9 100644 ---- a/src/wallet/api/wallet_manager.cpp -+++ b/src/wallet/api/wallet_manager.cpp -@@ -188,10 +188,14 @@ bool WalletManagerImpl::verifyWalletPassword(const std::string &keys_file_name, - - bool WalletManagerImpl::queryWalletDevice(Wallet::Device& device_type, const std::string &keys_file_name, const std::string &password, uint64_t kdf_rounds) const - { -- hw::device::device_type type; -- bool r = tools::wallet2::query_device(type, keys_file_name, password, kdf_rounds); -- device_type = static_cast(type); -- return r; -+ try { -+ hw::device::device_type type; -+ bool r = tools::wallet2::query_device(type, keys_file_name, password, kdf_rounds); -+ device_type = static_cast(type); -+ return r; -+ } catch (...) { -+ return false; -+ } - } - - std::vector WalletManagerImpl::findWallets(const std::string &path) --- -2.48.0 - diff --git a/patches/wownero/0008-polyseed.patch b/patches/wownero/0008-polyseed.patch new file mode 100644 index 0000000..69df1d5 --- /dev/null +++ b/patches/wownero/0008-polyseed.patch @@ -0,0 +1,1446 @@ +From 2aec74a418dda0560e51ef2bdbfbc87ea8765bf4 Mon Sep 17 00:00:00 2001 +From: tobtoht +Date: Tue, 12 Mar 2024 09:42:37 +0100 +Subject: [PATCH 08/16] polyseed + +Co-authored-by: Czarek Nakamoto +--- + .gitmodules | 6 + + CMakeLists.txt | 4 +- + contrib/depends/hosts/darwin.mk | 2 + + contrib/depends/hosts/linux.mk | 8 +- + contrib/depends/packages/packages.mk | 2 +- + contrib/depends/packages/polyseed.mk | 28 +++ + contrib/depends/packages/sodium.mk | 2 +- + .../polyseed/0001-disable-soname.patch | 48 +++++ + .../patches/polyseed/force-static-mingw.patch | 23 +++ + contrib/epee/include/wipeable_string.h | 7 + + contrib/epee/src/wipeable_string.cpp | 10 + + external/CMakeLists.txt | 2 + + src/CMakeLists.txt | 1 + + src/cryptonote_basic/CMakeLists.txt | 1 + + src/cryptonote_basic/account.cpp | 23 ++- + src/cryptonote_basic/account.h | 6 + + src/cryptonote_config.h | 2 + + src/polyseed/CMakeLists.txt | 25 +++ + src/polyseed/pbkdf2.c | 85 ++++++++ + src/polyseed/pbkdf2.h | 46 +++++ + src/polyseed/polyseed.cpp | 182 ++++++++++++++++++ + src/polyseed/polyseed.hpp | 167 ++++++++++++++++ + src/wallet/api/wallet.cpp | 70 +++++++ + src/wallet/api/wallet.h | 10 + + src/wallet/api/wallet2_api.h | 25 +++ + src/wallet/api/wallet_manager.cpp | 9 + + src/wallet/api/wallet_manager.h | 10 + + src/wallet/wallet2.cpp | 99 ++++++++-- + src/wallet/wallet2.h | 30 ++- + 29 files changed, 910 insertions(+), 23 deletions(-) + create mode 100644 contrib/depends/packages/polyseed.mk + create mode 100644 contrib/depends/patches/polyseed/0001-disable-soname.patch + create mode 100644 contrib/depends/patches/polyseed/force-static-mingw.patch + create mode 100644 src/polyseed/CMakeLists.txt + create mode 100644 src/polyseed/pbkdf2.c + create mode 100644 src/polyseed/pbkdf2.h + create mode 100644 src/polyseed/polyseed.cpp + create mode 100644 src/polyseed/polyseed.hpp + +diff --git a/.gitmodules b/.gitmodules +index c1c0d385d..9edead0ee 100644 +--- a/.gitmodules ++++ b/.gitmodules +@@ -19,3 +19,9 @@ + path = external/bc-ur + url = https://github.com/MrCyjaneK/bc-ur + branch = misc ++[submodule "external/utf8proc"] ++ path = external/utf8proc ++ url = https://github.com/JuliaStrings/utf8proc.git ++[submodule "external/polyseed"] ++ path = external/polyseed ++ url = https://github.com/tevador/polyseed.git +\ No newline at end of file +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 86af78f10..0007b5ea9 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -372,6 +372,8 @@ if(NOT MANUAL_SUBMODULES) + check_submodule(external/trezor-common) + check_submodule(external/randomwow) + check_submodule(external/supercop) ++ check_submodule(external/polyseed) ++ check_submodule(external/utf8proc) + endif() + endif() + +@@ -461,7 +463,7 @@ endif() + # elseif(CMAKE_SYSTEM_NAME MATCHES ".*BSDI.*") + # set(BSDI TRUE) + +-include_directories(external/rapidjson/include external/easylogging++ src contrib/epee/include external external/supercop/include) ++include_directories(external/rapidjson/include external/easylogging++ src contrib/epee/include external external/supercop/include external/polyseed/include external/utf8proc) + + if(APPLE) + cmake_policy(SET CMP0042 NEW) +diff --git a/contrib/depends/hosts/darwin.mk b/contrib/depends/hosts/darwin.mk +index 83d83036b..b14ee5c5b 100644 +--- a/contrib/depends/hosts/darwin.mk ++++ b/contrib/depends/hosts/darwin.mk +@@ -8,6 +8,8 @@ endif + darwin_CC=clang -target $(CC_target) -mmacosx-version-min=$(OSX_MIN_VERSION) --sysroot $(host_prefix)/native/SDK/ -mlinker-version=$(LD64_VERSION) -B$(host_prefix)/native/bin/$(host)- + darwin_CXX=clang++ -target $(CC_target) -mmacosx-version-min=$(OSX_MIN_VERSION) --sysroot $(host_prefix)/native/SDK/ -mlinker-version=$(LD64_VERSION) -stdlib=libc++ -B$(host_prefix)/native/bin/$(host)- + ++darwin_RANLIB=$(host_prefix)/native/bin/$(host)-ranlib ++ + darwin_CFLAGS=-pipe + darwin_CXXFLAGS=$(darwin_CFLAGS) + darwin_ARFLAGS=cr +diff --git a/contrib/depends/hosts/linux.mk b/contrib/depends/hosts/linux.mk +index 912fdb03c..b79799f30 100644 +--- a/contrib/depends/hosts/linux.mk ++++ b/contrib/depends/hosts/linux.mk +@@ -11,15 +11,15 @@ linux_debug_CXXFLAGS=$(linux_debug_CFLAGS) + linux_debug_CPPFLAGS=-D_GLIBCXX_DEBUG -D_GLIBCXX_DEBUG_PEDANTIC + + ifeq (86,$(findstring 86,$(build_arch))) +-i686_linux_CC=gcc -m32 +-i686_linux_CXX=g++ -m32 ++i686_linux_CC=i686-linux-gnu-gcc ++i686_linux_CXX=i686-linux-gnu-g++ + i686_linux_AR=ar + i686_linux_RANLIB=ranlib + i686_linux_NM=nm + i686_linux_STRIP=strip + +-x86_64_linux_CC=gcc -m64 +-x86_64_linux_CXX=g++ -m64 ++x86_64_linux_CC=x86_64-linux-gnu-gcc ++x86_64_linux_CXX=x86_64-linux-gnu-g++ + x86_64_linux_AR=ar + x86_64_linux_RANLIB=ranlib + x86_64_linux_NM=nm +diff --git a/contrib/depends/packages/packages.mk b/contrib/depends/packages/packages.mk +index d2d1eca85..8783d4955 100644 +--- a/contrib/depends/packages/packages.mk ++++ b/contrib/depends/packages/packages.mk +@@ -1,4 +1,4 @@ +-packages:=boost openssl zeromq libiconv expat unbound ++packages:=boost openssl zeromq libiconv expat unbound polyseed + + # ccache is useless in gitian builds + ifneq ($(GITIAN),1) +diff --git a/contrib/depends/packages/polyseed.mk b/contrib/depends/packages/polyseed.mk +new file mode 100644 +index 000000000..0071b20f3 +--- /dev/null ++++ b/contrib/depends/packages/polyseed.mk +@@ -0,0 +1,28 @@ ++package=polyseed ++$(package)_version=2.0.0 ++$(package)_download_path=https://github.com/tevador/$(package)/archive/refs/tags/ ++$(package)_download_file=v$($(package)_version).tar.gz ++$(package)_file_name=$(package)-$($(package)_version).tar.gz ++$(package)_sha256_hash=f36282fcbcd68d32461b8230c89e1a40661bd46b91109681cec637433004135a ++$(package)_patches=force-static-mingw.patch 0001-disable-soname.patch ++ ++define $(package)_preprocess_cmds ++ patch -p1 < $($(package)_patch_dir)/force-static-mingw.patch &&\ ++ patch -p1 < $($(package)_patch_dir)/0001-disable-soname.patch ++endef ++ ++define $(package)_config_cmds ++ CC="$($(package)_cc)" cmake -DCMAKE_INSTALL_PREFIX="$(host_prefix)" . ++endef ++ ++define $(package)_set_vars ++ $(package)_build_opts=CC="$($(package)_cc)" ++endef ++ ++define $(package)_build_cmds ++ CC="$($(package)_cc)" $(MAKE) ++endef ++ ++define $(package)_stage_cmds ++ $(MAKE) DESTDIR=$($(package)_staging_dir) install ++endef +diff --git a/contrib/depends/packages/sodium.mk b/contrib/depends/packages/sodium.mk +index 87b34599e..68a5b48ba 100644 +--- a/contrib/depends/packages/sodium.mk ++++ b/contrib/depends/packages/sodium.mk +@@ -6,7 +6,7 @@ $(package)_sha256_hash=6f504490b342a4f8a4c4a02fc9b866cbef8622d5df4e5452b46be121e + $(package)_patches=disable-glibc-getrandom-getentropy.patch fix-whitespace.patch + + define $(package)_set_vars +-$(package)_config_opts=--enable-static --disable-shared ++$(package)_config_opts=--enable-static --disable-shared --with-pic + $(package)_config_opts+=--prefix=$(host_prefix) + endef + +diff --git a/contrib/depends/patches/polyseed/0001-disable-soname.patch b/contrib/depends/patches/polyseed/0001-disable-soname.patch +new file mode 100644 +index 000000000..bd97dd394 +--- /dev/null ++++ b/contrib/depends/patches/polyseed/0001-disable-soname.patch +@@ -0,0 +1,48 @@ ++From aabafcfc0572651436d024a635483c49042fad7f Mon Sep 17 00:00:00 2001 ++From: Czarek Nakamoto ++Date: Thu, 28 Mar 2024 00:32:51 +0100 ++Subject: [PATCH] disable soname ++ ++--- ++ CMakeLists.txt | 16 +++++++++------- ++ 1 file changed, 9 insertions(+), 7 deletions(-) ++ ++diff --git a/CMakeLists.txt b/CMakeLists.txt ++index 8a8e7c2..5301353 100644 ++--- a/CMakeLists.txt +++++ b/CMakeLists.txt ++@@ -36,6 +36,7 @@ include_directories(polyseed ++ target_compile_definitions(polyseed PRIVATE POLYSEED_SHARED) ++ set_target_properties(polyseed PROPERTIES VERSION 2.0.0 ++ SOVERSION 2 +++ NO_SONAME 1 ++ C_STANDARD 11 ++ C_STANDARD_REQUIRED ON) ++ ++@@ -45,16 +46,17 @@ include_directories(polyseed_static ++ include/) ++ target_compile_definitions(polyseed_static PRIVATE POLYSEED_STATIC) ++ set_target_properties(polyseed_static PROPERTIES OUTPUT_NAME polyseed +++ NO_SONAME 1 ++ C_STANDARD 11 ++ C_STANDARD_REQUIRED ON) ++ ++-add_executable(polyseed-tests ++- tests/tests.c) ++-include_directories(polyseed-tests ++- include/) ++-target_compile_definitions(polyseed-tests PRIVATE POLYSEED_STATIC) ++-target_link_libraries(polyseed-tests ++- PRIVATE polyseed_static) +++# add_executable(polyseed-tests +++# tests/tests.c) +++# include_directories(polyseed-tests +++# include/) +++# target_compile_definitions(polyseed-tests PRIVATE POLYSEED_STATIC) +++# target_link_libraries(polyseed-tests +++# PRIVATE polyseed_static) ++ ++ include(GNUInstallDirs) ++ install(TARGETS polyseed polyseed_static ++-- ++2.39.2 +diff --git a/contrib/depends/patches/polyseed/force-static-mingw.patch b/contrib/depends/patches/polyseed/force-static-mingw.patch +new file mode 100644 +index 000000000..f05cb2b6a +--- /dev/null ++++ b/contrib/depends/patches/polyseed/force-static-mingw.patch +@@ -0,0 +1,23 @@ ++--- a/include/polyseed.h +++++ b/include/polyseed.h ++@@ -93,13 +93,13 @@ Shared/static library definitions ++ - define POLYSEED_STATIC when linking to the static library ++ */ ++ #if defined(_WIN32) || defined(__CYGWIN__) ++- #ifdef POLYSEED_SHARED ++- #define POLYSEED_API __declspec(dllexport) ++- #elif !defined(POLYSEED_STATIC) ++- #define POLYSEED_API __declspec(dllimport) ++- #else ++- #define POLYSEED_API ++- #endif +++// #ifdef POLYSEED_SHARED +++// #define POLYSEED_API __declspec(dllexport) +++// #elif !defined(POLYSEED_STATIC) +++// #define POLYSEED_API __declspec(dllimport) +++// #else +++ #define POLYSEED_API +++// #endif ++ #define POLYSEED_PRIVATE ++ #else ++ #ifdef POLYSEED_SHARED +diff --git a/contrib/epee/include/wipeable_string.h b/contrib/epee/include/wipeable_string.h +index 65977cd97..594e15de4 100644 +--- a/contrib/epee/include/wipeable_string.h ++++ b/contrib/epee/include/wipeable_string.h +@@ -34,6 +34,7 @@ + #include + #include "memwipe.h" + #include "fnv1.h" ++#include "serialization/keyvalue_serialization.h" + + namespace epee + { +@@ -75,6 +76,12 @@ namespace epee + bool operator!=(const wipeable_string &other) const noexcept { return buffer != other.buffer; } + wipeable_string &operator=(wipeable_string &&other); + wipeable_string &operator=(const wipeable_string &other); ++ char& operator[](size_t idx); ++ const char& operator[](size_t idx) const; ++ ++ BEGIN_KV_SERIALIZE_MAP() ++ KV_SERIALIZE_CONTAINER_POD_AS_BLOB(buffer) ++ END_KV_SERIALIZE_MAP() + + private: + void grow(size_t sz, size_t reserved = 0); +diff --git a/contrib/epee/src/wipeable_string.cpp b/contrib/epee/src/wipeable_string.cpp +index b016f2f48..f2f365b1b 100644 +--- a/contrib/epee/src/wipeable_string.cpp ++++ b/contrib/epee/src/wipeable_string.cpp +@@ -261,4 +261,14 @@ wipeable_string &wipeable_string::operator=(const wipeable_string &other) + return *this; + } + ++char& wipeable_string::operator[](size_t idx) { ++ CHECK_AND_ASSERT_THROW_MES(idx < buffer.size(), "Index out of bounds"); ++ return buffer[idx]; ++} ++ ++const char& wipeable_string::operator[](size_t idx) const { ++ CHECK_AND_ASSERT_THROW_MES(idx < buffer.size(), "Index out of bounds"); ++ return buffer[idx]; ++} ++ + } +diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt +index d0b6d9b14..ad30abc1f 100644 +--- a/external/CMakeLists.txt ++++ b/external/CMakeLists.txt +@@ -71,4 +71,6 @@ add_subdirectory(db_drivers) + add_subdirectory(easylogging++) + add_subdirectory(qrcodegen) + add_subdirectory(bc-ur) ++add_subdirectory(polyseed EXCLUDE_FROM_ALL) ++add_subdirectory(utf8proc EXCLUDE_FROM_ALL) + add_subdirectory(randomwow EXCLUDE_FROM_ALL) +diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt +index 3335d3c21..06b708cf0 100644 +--- a/src/CMakeLists.txt ++++ b/src/CMakeLists.txt +@@ -95,6 +95,7 @@ add_subdirectory(net) + add_subdirectory(hardforks) + add_subdirectory(blockchain_db) + add_subdirectory(mnemonics) ++add_subdirectory(polyseed) + add_subdirectory(rpc) + if(NOT IOS) + add_subdirectory(serialization) +diff --git a/src/cryptonote_basic/CMakeLists.txt b/src/cryptonote_basic/CMakeLists.txt +index 1414be1b2..414936a05 100644 +--- a/src/cryptonote_basic/CMakeLists.txt ++++ b/src/cryptonote_basic/CMakeLists.txt +@@ -71,6 +71,7 @@ target_link_libraries(cryptonote_basic + checkpoints + cryptonote_format_utils_basic + device ++ polyseed_wrapper + ${Boost_DATE_TIME_LIBRARY} + ${Boost_PROGRAM_OPTIONS_LIBRARY} + ${Boost_SERIALIZATION_LIBRARY} +diff --git a/src/cryptonote_basic/account.cpp b/src/cryptonote_basic/account.cpp +index 4e87d4477..2d556f285 100644 +--- a/src/cryptonote_basic/account.cpp ++++ b/src/cryptonote_basic/account.cpp +@@ -87,12 +87,16 @@ DISABLE_VS_WARNINGS(4244 4345) + void account_keys::xor_with_key_stream(const crypto::chacha_key &key) + { + // encrypt a large enough byte stream with chacha20 +- epee::wipeable_string key_stream = get_key_stream(key, m_encryption_iv, sizeof(crypto::secret_key) * (2 + m_multisig_keys.size())); ++ epee::wipeable_string key_stream = get_key_stream(key, m_encryption_iv, sizeof(crypto::secret_key) * (3 + m_multisig_keys.size()) + m_passphrase.size()); + const char *ptr = key_stream.data(); + for (size_t i = 0; i < sizeof(crypto::secret_key); ++i) + m_spend_secret_key.data[i] ^= *ptr++; + for (size_t i = 0; i < sizeof(crypto::secret_key); ++i) + m_view_secret_key.data[i] ^= *ptr++; ++ for (size_t i = 0; i < sizeof(crypto::secret_key); ++i) ++ m_polyseed.data[i] ^= *ptr++; ++ for (size_t i = 0; i < m_passphrase.size(); ++i) ++ m_passphrase.data()[i] ^= *ptr++; + for (crypto::secret_key &k: m_multisig_keys) + { + for (size_t i = 0; i < sizeof(crypto::secret_key); ++i) +@@ -150,6 +154,8 @@ DISABLE_VS_WARNINGS(4244 4345) + { + m_keys.m_spend_secret_key = crypto::secret_key(); + m_keys.m_multisig_keys.clear(); ++ m_keys.m_polyseed = crypto::secret_key(); ++ m_keys.m_passphrase.wipe(); + } + //----------------------------------------------------------------- + void account_base::set_spend_key(const crypto::secret_key& spend_secret_key) +@@ -255,6 +261,21 @@ DISABLE_VS_WARNINGS(4244 4345) + create_from_keys(address, fake, viewkey); + } + //----------------------------------------------------------------- ++ void account_base::create_from_polyseed(const polyseed::data& seed, const epee::wipeable_string &passphrase) ++ { ++ crypto::secret_key secret_key; ++ seed.keygen(&secret_key, sizeof(secret_key)); ++ ++ if (!passphrase.empty()) { ++ secret_key = cryptonote::decrypt_key(secret_key, passphrase); ++ } ++ ++ generate(secret_key, true, false); ++ ++ seed.save(m_keys.m_polyseed.data); ++ m_keys.m_passphrase = passphrase; ++ } ++ //----------------------------------------------------------------- + bool account_base::make_multisig(const crypto::secret_key &view_secret_key, const crypto::secret_key &spend_secret_key, const crypto::public_key &spend_public_key, const std::vector &multisig_keys) + { + m_keys.m_account_address.m_spend_public_key = spend_public_key; +diff --git a/src/cryptonote_basic/account.h b/src/cryptonote_basic/account.h +index 93d1d28f0..1f76febce 100644 +--- a/src/cryptonote_basic/account.h ++++ b/src/cryptonote_basic/account.h +@@ -33,6 +33,7 @@ + #include "cryptonote_basic.h" + #include "crypto/crypto.h" + #include "serialization/keyvalue_serialization.h" ++#include "polyseed/polyseed.hpp" + + namespace cryptonote + { +@@ -45,6 +46,8 @@ namespace cryptonote + std::vector m_multisig_keys; + hw::device *m_device = &hw::get_device("default"); + crypto::chacha_iv m_encryption_iv; ++ crypto::secret_key m_polyseed; ++ epee::wipeable_string m_passphrase; // Only used with polyseed + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(m_account_address) +@@ -53,6 +56,8 @@ namespace cryptonote + KV_SERIALIZE_CONTAINER_POD_AS_BLOB(m_multisig_keys) + const crypto::chacha_iv default_iv{{0, 0, 0, 0, 0, 0, 0, 0}}; + KV_SERIALIZE_VAL_POD_AS_BLOB_OPT(m_encryption_iv, default_iv) ++ KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE(m_polyseed) ++ KV_SERIALIZE(m_passphrase) + END_KV_SERIALIZE_MAP() + + void encrypt(const crypto::chacha_key &key); +@@ -79,6 +84,7 @@ namespace cryptonote + void create_from_device(hw::device &hwdev); + void create_from_keys(const cryptonote::account_public_address& address, const crypto::secret_key& spendkey, const crypto::secret_key& viewkey); + void create_from_viewkey(const cryptonote::account_public_address& address, const crypto::secret_key& viewkey); ++ void create_from_polyseed(const polyseed::data &polyseed, const epee::wipeable_string &passphrase); + bool make_multisig(const crypto::secret_key &view_secret_key, const crypto::secret_key &spend_secret_key, const crypto::public_key &spend_public_key, const std::vector &multisig_keys); + const account_keys& get_keys() const; + std::string get_public_address_str(network_type nettype) const; +diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h +index 42861a8ff..8973d9fb8 100644 +--- a/src/cryptonote_config.h ++++ b/src/cryptonote_config.h +@@ -223,6 +223,8 @@ + + #define DNS_BLOCKLIST_LIFETIME (86400 * 8) + ++#define POLYSEED_COIN POLYSEED_WOWNERO ++ + //The limit is enough for the mandatory transaction content with 16 outputs (547 bytes), + //a custom tag (1 byte) and up to 32 bytes of custom data for each recipient. + // (1+32) + (1+1+16*32) + (1+16*32) = 1060 +diff --git a/src/polyseed/CMakeLists.txt b/src/polyseed/CMakeLists.txt +new file mode 100644 +index 000000000..cca4eb746 +--- /dev/null ++++ b/src/polyseed/CMakeLists.txt +@@ -0,0 +1,25 @@ ++set(polyseed_sources ++ pbkdf2.c ++ polyseed.cpp ++) ++ ++monero_find_all_headers(polyseed_private_headers "${CMAKE_CURRENT_SOURCE_DIR}") ++ ++monero_private_headers(polyseed_wrapper ++ ${polyseed_private_headers} ++) ++ ++monero_add_library(polyseed_wrapper ++ ${polyseed_sources} ++ ${polyseed_headers} ++ ${polyseed_private_headers} ++) ++ ++target_link_libraries(polyseed_wrapper ++PUBLIC ++ polyseed ++ utf8proc ++ ${SODIUM_LIBRARY} ++ PRIVATE ++ ${EXTRA_LIBRARIES} ++) +diff --git a/src/polyseed/pbkdf2.c b/src/polyseed/pbkdf2.c +new file mode 100644 +index 000000000..1c45f4708 +--- /dev/null ++++ b/src/polyseed/pbkdf2.c +@@ -0,0 +1,85 @@ ++// Copyright (c) 2023, The Monero Project ++// Copyright (c) 2021, tevador ++// Copyright (c) 2005,2007,2009 Colin Percival ++// ++// 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. ++// ++// THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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 ++ ++static inline void ++store32_be(uint8_t dst[4], uint32_t w) ++{ ++ dst[3] = (uint8_t) w; w >>= 8; ++ dst[2] = (uint8_t) w; w >>= 8; ++ dst[1] = (uint8_t) w; w >>= 8; ++ dst[0] = (uint8_t) w; ++} ++ ++void ++crypto_pbkdf2_sha256(const uint8_t* passwd, size_t passwdlen, ++ const uint8_t* salt, size_t saltlen, uint64_t c, ++ uint8_t* buf, size_t dkLen) ++{ ++ crypto_auth_hmacsha256_state Phctx, PShctx, hctx; ++ size_t i; ++ uint8_t ivec[4]; ++ uint8_t U[32]; ++ uint8_t T[32]; ++ uint64_t j; ++ int k; ++ size_t clen; ++ ++ crypto_auth_hmacsha256_init(&Phctx, passwd, passwdlen); ++ PShctx = Phctx; ++ crypto_auth_hmacsha256_update(&PShctx, salt, saltlen); ++ ++ for (i = 0; i * 32 < dkLen; i++) { ++ store32_be(ivec, (uint32_t)(i + 1)); ++ hctx = PShctx; ++ crypto_auth_hmacsha256_update(&hctx, ivec, 4); ++ crypto_auth_hmacsha256_final(&hctx, U); ++ ++ memcpy(T, U, 32); ++ for (j = 2; j <= c; j++) { ++ hctx = Phctx; ++ crypto_auth_hmacsha256_update(&hctx, U, 32); ++ crypto_auth_hmacsha256_final(&hctx, U); ++ ++ for (k = 0; k < 32; k++) { ++ T[k] ^= U[k]; ++ } ++ } ++ ++ clen = dkLen - i * 32; ++ if (clen > 32) { ++ clen = 32; ++ } ++ memcpy(&buf[i * 32], T, clen); ++ } ++ sodium_memzero((void*)&Phctx, sizeof Phctx); ++ sodium_memzero((void*)&PShctx, sizeof PShctx); ++} +\ No newline at end of file +diff --git a/src/polyseed/pbkdf2.h b/src/polyseed/pbkdf2.h +new file mode 100644 +index 000000000..f6253b9d7 +--- /dev/null ++++ b/src/polyseed/pbkdf2.h +@@ -0,0 +1,46 @@ ++// Copyright (c) 2023, The Monero Project ++// Copyright (c) 2021, tevador ++// ++// 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. ++// ++// THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. ++ ++#ifndef PBKDF2_H ++#define PBKDF2_H ++ ++#include ++#include ++ ++#ifdef __cplusplus ++extern "C" { ++#endif ++ ++void ++crypto_pbkdf2_sha256(const uint8_t* passwd, size_t passwdlen, ++ const uint8_t* salt, size_t saltlen, uint64_t c, ++ uint8_t* buf, size_t dkLen); ++ ++#ifdef __cplusplus ++} ++#endif ++ ++#endif +\ No newline at end of file +diff --git a/src/polyseed/polyseed.cpp b/src/polyseed/polyseed.cpp +new file mode 100644 +index 000000000..231a48a94 +--- /dev/null ++++ b/src/polyseed/polyseed.cpp +@@ -0,0 +1,182 @@ ++// Copyright (c) 2023, The Monero Project ++// Copyright (c) 2021, tevador ++// ++// 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. ++// ++// THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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 "polyseed.hpp" ++#include "pbkdf2.h" ++ ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++ ++namespace polyseed { ++ ++ inline size_t utf8_norm(const char* str, polyseed_str norm, utf8proc_option_t options) { ++ utf8proc_int32_t buffer[POLYSEED_STR_SIZE]; ++ utf8proc_ssize_t result; ++ ++ result = utf8proc_decompose(reinterpret_cast(str), 0, buffer, POLYSEED_STR_SIZE, options); ++ if (result < 0 || result > (POLYSEED_STR_SIZE - 1)) { ++ throw std::runtime_error("Unicode normalization failed"); ++ } ++ ++ result = utf8proc_reencode(buffer, result, options); ++ if (result < 0 || result > POLYSEED_STR_SIZE) { ++ throw std::runtime_error("Unicode normalization failed"); ++ } ++ ++ strcpy(norm, reinterpret_cast(buffer)); ++ sodium_memzero(buffer, POLYSEED_STR_SIZE); ++ return result; ++ } ++ ++ static size_t utf8_nfc(const char* str, polyseed_str norm) { ++ // Note: UTF8PROC_LUMP is used here to replace the ideographic space with a regular space for Japanese phrases ++ // to allow wallets to split on ' '. ++ return utf8_norm(str, norm, (utf8proc_option_t)(UTF8PROC_NULLTERM | UTF8PROC_STABLE | UTF8PROC_COMPOSE | UTF8PROC_STRIPNA)); ++ } ++ ++ static size_t utf8_nfkd(const char* str, polyseed_str norm) { ++ return utf8_norm(str, norm, (utf8proc_option_t)(UTF8PROC_NULLTERM | UTF8PROC_STABLE | UTF8PROC_DECOMPOSE | UTF8PROC_COMPAT | UTF8PROC_STRIPNA)); ++ } ++ ++ struct dependency { ++ dependency(); ++ std::vector languages; ++ }; ++ ++ static dependency deps; ++ ++ dependency::dependency() { ++ if (sodium_init() == -1) { ++ throw std::runtime_error("sodium_init failed"); ++ } ++ ++ polyseed_dependency pd; ++ pd.randbytes = &randombytes_buf; ++ pd.pbkdf2_sha256 = &crypto_pbkdf2_sha256; ++ pd.memzero = &sodium_memzero; ++ pd.u8_nfc = &utf8_nfc; ++ pd.u8_nfkd = &utf8_nfkd; ++ pd.time = nullptr; ++ pd.alloc = nullptr; ++ pd.free = nullptr; ++ ++ polyseed_inject(&pd); ++ ++ for (int i = 0; i < polyseed_get_num_langs(); ++i) { ++ languages.push_back(language(polyseed_get_lang(i))); ++ } ++ } ++ ++ static language invalid_lang; ++ ++ const std::vector& get_langs() { ++ return deps.languages; ++ } ++ ++ const language& get_lang_by_name(const std::string& name) { ++ for (auto& lang : deps.languages) { ++ if (name == lang.name_en()) { ++ return lang; ++ } ++ if (name == lang.name()) { ++ return lang; ++ } ++ } ++ return invalid_lang; ++ } ++ ++ inline void data::check_init() const { ++ if (valid()) { ++ throw std::runtime_error("already initialized"); ++ } ++ } ++ ++ static std::array error_desc = { ++ "Success", ++ "Wrong number of words in the phrase", ++ "Unknown language or unsupported words", ++ "Checksum mismatch", ++ "Unsupported seed features", ++ "Invalid seed format", ++ "Memory allocation failure", ++ "Unicode normalization failed" ++ }; ++ ++ static error get_error(polyseed_status status) { ++ if (status > 0 && status < sizeof(error_desc) / sizeof(const char*)) { ++ return error(error_desc[(int)status], status); ++ } ++ return error("Unknown error", status); ++ } ++ ++ void data::create(feature_type features) { ++ check_init(); ++ auto status = polyseed_create(features, &m_data); ++ if (status != POLYSEED_OK) { ++ throw get_error(status); ++ } ++ } ++ ++ void data::split(const language& lang, polyseed_phrase& words) { ++ check_init(); ++ if (!lang.valid()) { ++ throw std::runtime_error("invalid language"); ++ } ++ } ++ ++ void data::load(polyseed_storage storage) { ++ check_init(); ++ auto status = polyseed_load(storage, &m_data); ++ if (status != POLYSEED_OK) { ++ throw get_error(status); ++ } ++ } ++ ++ void data::load(const crypto::secret_key &key) { ++ polyseed_storage d; ++ memcpy(&d, &key.data, 32); ++ auto status = polyseed_load(d, &m_data); ++ if (status != POLYSEED_OK) { ++ throw get_error(status); ++ } ++ } ++ ++ language data::decode(const char* phrase) { ++ check_init(); ++ const polyseed_lang* lang; ++ auto status = polyseed_decode(phrase, m_coin, &lang, &m_data); ++ if (status != POLYSEED_OK) { ++ throw get_error(status); ++ } ++ return language(lang); ++ } ++} +diff --git a/src/polyseed/polyseed.hpp b/src/polyseed/polyseed.hpp +new file mode 100644 +index 000000000..2c8c777a7 +--- /dev/null ++++ b/src/polyseed/polyseed.hpp +@@ -0,0 +1,167 @@ ++// Copyright (c) 2023, The Monero Project ++// Copyright (c) 2021, tevador ++// ++// 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. ++// ++// THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. ++ ++#ifndef POLYSEED_HPP ++#define POLYSEED_HPP ++ ++#include ++#include ++#include ++#include ++#include ++#include "crypto/crypto.h" ++ ++namespace polyseed { ++ ++ class data; ++ ++ class language { ++ public: ++ language() : m_lang(nullptr) {} ++ language(const language&) = default; ++ language(const polyseed_lang* lang) : m_lang(lang) {} ++ const char* name() const { ++ return polyseed_get_lang_name(m_lang); ++ } ++ const char* name_en() const { ++ return polyseed_get_lang_name_en(m_lang); ++ } ++ const char* separator() const { ++ return m_lang->separator; ++ } ++ bool valid() const { ++ return m_lang != nullptr; ++ } ++ ++ const polyseed_lang* m_lang; ++ private: ++ ++ friend class data; ++ }; ++ ++ const std::vector& get_langs(); ++ const language& get_lang_by_name(const std::string& name); ++ ++ class error : public std::runtime_error { ++ public: ++ error(const char* msg, polyseed_status status) ++ : std::runtime_error(msg), m_status(status) ++ { ++ } ++ polyseed_status status() const { ++ return m_status; ++ } ++ private: ++ polyseed_status m_status; ++ }; ++ ++ using feature_type = unsigned int; ++ ++ inline int enable_features(feature_type features) { ++ return polyseed_enable_features(features); ++ } ++ ++ class data { ++ public: ++ data(const data&) = delete; ++ data(polyseed_coin coin) : m_data(nullptr), m_coin(coin) {} ++ ~data() { ++ polyseed_free(m_data); ++ } ++ ++ void create(feature_type features); ++ ++ void load(polyseed_storage storage); ++ ++ void load(const crypto::secret_key &key); ++ ++ language decode(const char* phrase); ++ ++ template ++ void encode(const language& lang, str_type& str) const { ++ check_valid(); ++ if (!lang.valid()) { ++ throw std::runtime_error("invalid language"); ++ } ++ str.resize(POLYSEED_STR_SIZE); ++ auto size = polyseed_encode(m_data, lang.m_lang, m_coin, &str[0]); ++ str.resize(size); ++ } ++ ++ void split(const language& lang, polyseed_phrase& words); ++ ++ void save(polyseed_storage storage) const { ++ check_valid(); ++ polyseed_store(m_data, storage); ++ } ++ ++ void save(void *storage) const { ++ check_valid(); ++ polyseed_store(m_data, (uint8_t*)storage); ++ } ++ ++ void crypt(const char* password) { ++ check_valid(); ++ polyseed_crypt(m_data, password); ++ } ++ ++ void keygen(void* ptr, size_t key_size) const { ++ check_valid(); ++ polyseed_keygen(m_data, m_coin, key_size, (uint8_t*)ptr); ++ } ++ ++ bool valid() const { ++ return m_data != nullptr; ++ } ++ ++ bool encrypted() const { ++ check_valid(); ++ return polyseed_is_encrypted(m_data); ++ } ++ ++ uint64_t birthday() const { ++ check_valid(); ++ return polyseed_get_birthday(m_data); ++ } ++ ++ bool has_feature(feature_type feature) const { ++ check_valid(); ++ return polyseed_get_feature(m_data, feature) != 0; ++ } ++ private: ++ void check_valid() const { ++ if (m_data == nullptr) { ++ throw std::runtime_error("invalid object"); ++ } ++ } ++ void check_init() const; ++ ++ polyseed_data* m_data; ++ polyseed_coin m_coin; ++ }; ++} ++ ++#endif //POLYSEED_HPP +\ No newline at end of file +diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp +index aec76ffc0..f315b7ed6 100644 +--- a/src/wallet/api/wallet.cpp ++++ b/src/wallet/api/wallet.cpp +@@ -728,6 +728,28 @@ bool WalletImpl::recoverFromDevice(const std::string &path, const std::string &p + return true; + } + ++bool WalletImpl::createFromPolyseed(const std::string &path, const std::string &password, const std::string &seed, ++ const std::string &passphrase, bool newWallet, uint64_t restoreHeight) ++{ ++ clearStatus(); ++ m_recoveringFromSeed = !newWallet; ++ m_recoveringFromDevice = false; ++ ++ polyseed::data polyseed(POLYSEED_COIN); ++ ++ try { ++ auto lang = polyseed.decode(seed.data()); ++ m_wallet->set_seed_language(lang.name()); ++ m_wallet->generate(path, password, polyseed, passphrase, !newWallet); ++ } ++ catch (const std::exception &e) { ++ setStatusError(e.what()); ++ return false; ++ } ++ ++ return true; ++} ++ + Wallet::Device WalletImpl::getDeviceType() const + { + return static_cast(m_wallet->get_device_type()); +@@ -845,6 +867,54 @@ std::string WalletImpl::seed(const std::string& seed_offset) const + } + } + ++bool WalletImpl::getPolyseed(std::string &seed_words, std::string &passphrase) const ++{ ++ epee::wipeable_string seed_words_epee(seed_words.c_str(), seed_words.size()); ++ epee::wipeable_string passphrase_epee(passphrase.c_str(), passphrase.size()); ++ clearStatus(); ++ ++ if (!m_wallet) { ++ return false; ++ } ++ ++ bool result = m_wallet->get_polyseed(seed_words_epee, passphrase_epee); ++ ++ seed_words.assign(seed_words_epee.data(), seed_words_epee.size()); ++ passphrase.assign(passphrase_epee.data(), passphrase_epee.size()); ++ ++ return result; ++} ++ ++std::vector> Wallet::getPolyseedLanguages() ++ { ++ std::vector> languages; ++ ++ auto langs = polyseed::get_langs(); ++ for (const auto &lang : langs) { ++ languages.emplace_back(std::pair(lang.name_en(), lang.name())); ++ } ++ ++ return languages; ++} ++ ++bool Wallet::createPolyseed(std::string &seed_words, std::string &err, const std::string &language) ++{ ++ epee::wipeable_string seed_words_epee(seed_words.c_str(), seed_words.size()); ++ ++ try { ++ polyseed::data polyseed(POLYSEED_COIN); ++ polyseed.create(0); ++ polyseed.encode(polyseed::get_lang_by_name(language), seed_words_epee); ++ ++ seed_words.assign(seed_words_epee.data(), seed_words_epee.size()); ++ } ++ catch (const std::exception &e) { ++ err = e.what(); ++ return false; ++ } ++ ++ return true; ++} + std::string WalletImpl::getSeedLanguage() const + { + return m_wallet->get_seed_language(); +diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h +index 4e9c21ecb..32e12284b 100644 +--- a/src/wallet/api/wallet.h ++++ b/src/wallet/api/wallet.h +@@ -79,9 +79,19 @@ public: + bool recoverFromDevice(const std::string &path, + const std::string &password, + const std::string &device_name); ++ ++ bool createFromPolyseed(const std::string &path, ++ const std::string &password, ++ const std::string &seed, ++ const std::string &passphrase = "", ++ bool newWallet = true, ++ uint64_t restoreHeight = 0); ++ + Device getDeviceType() const override; + bool close(bool store = true); + std::string seed(const std::string& seed_offset = "") const override; ++ bool getPolyseed(std::string &seed_words, std::string &passphrase) const override; ++ + std::string getSeedLanguage() const override; + void setSeedLanguage(const std::string &arg) override; + // void setListener(Listener *) {} +diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h +index 53ec4abfc..be1c3704e 100644 +--- a/src/wallet/api/wallet2_api.h ++++ b/src/wallet/api/wallet2_api.h +@@ -709,6 +709,10 @@ struct Wallet + static void warning(const std::string &category, const std::string &str); + static void error(const std::string &category, const std::string &str); + ++ virtual bool getPolyseed(std::string &seed, std::string &passphrase) const = 0; ++ static bool createPolyseed(std::string &seed_words, std::string &err, const std::string &language = "English"); ++ static std::vector> getPolyseedLanguages(); ++ + /** + * @brief StartRefresh - Start/resume refresh thread (refresh every 10 seconds) + */ +@@ -1320,6 +1324,27 @@ struct WalletManager + uint64_t kdf_rounds = 1, + WalletListener * listener = nullptr) = 0; + ++ /*! ++ * \brief creates a wallet from a polyseed mnemonic phrase ++ * \param path Name of the wallet file to be created ++ * \param password Password of wallet file ++ * \param nettype Network type ++ * \param mnemonic Polyseed mnemonic ++ * \param passphrase Optional seed offset passphrase ++ * \param newWallet Whether it is a new wallet ++ * \param restoreHeight Override the embedded restore height ++ * \param kdf_rounds Number of rounds for key derivation function ++ * @return ++ */ ++ virtual Wallet * createWalletFromPolyseed(const std::string &path, ++ const std::string &password, ++ NetworkType nettype, ++ const std::string &mnemonic, ++ const std::string &passphrase = "", ++ bool newWallet = true, ++ uint64_t restore_height = 0, ++ uint64_t kdf_rounds = 1) = 0; ++ + /*! + * \brief Closes wallet. In case operation succeeded, wallet object deleted. in case operation failed, wallet object not deleted + * \param wallet previously opened / created wallet instance +diff --git a/src/wallet/api/wallet_manager.cpp b/src/wallet/api/wallet_manager.cpp +index 277be6ac9..da2056d8a 100644 +--- a/src/wallet/api/wallet_manager.cpp ++++ b/src/wallet/api/wallet_manager.cpp +@@ -156,6 +156,15 @@ Wallet *WalletManagerImpl::createWalletFromDevice(const std::string &path, + return wallet; + } + ++Wallet *WalletManagerImpl::createWalletFromPolyseed(const std::string &path, const std::string &password, NetworkType nettype, ++ const std::string &mnemonic, const std::string &passphrase, ++ bool newWallet, uint64_t restoreHeight, uint64_t kdf_rounds) ++{ ++ WalletImpl * wallet = new WalletImpl(nettype, kdf_rounds); ++ wallet->createFromPolyseed(path, password, mnemonic, passphrase, newWallet, restoreHeight); ++ return wallet; ++} ++ + bool WalletManagerImpl::closeWallet(Wallet *wallet, bool store) + { + WalletImpl * wallet_ = dynamic_cast(wallet); +diff --git a/src/wallet/api/wallet_manager.h b/src/wallet/api/wallet_manager.h +index a223e1df9..28fcd36c9 100644 +--- a/src/wallet/api/wallet_manager.h ++++ b/src/wallet/api/wallet_manager.h +@@ -75,6 +75,16 @@ public: + const std::string &subaddressLookahead = "", + uint64_t kdf_rounds = 1, + WalletListener * listener = nullptr) override; ++ ++ virtual Wallet * createWalletFromPolyseed(const std::string &path, ++ const std::string &password, ++ NetworkType nettype, ++ const std::string &mnemonic, ++ const std::string &passphrase, ++ bool newWallet = true, ++ uint64_t restore_height = 0, ++ uint64_t kdf_rounds = 1) override; ++ + virtual bool closeWallet(Wallet *wallet, bool store = true) override; + bool walletExists(const std::string &path) override; + bool verifyWalletPassword(const std::string &keys_file_name, const std::string &password, bool no_spend_key, uint64_t kdf_rounds = 1) const override; +diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp +index d89595cf8..849128ca5 100644 +--- a/src/wallet/wallet2.cpp ++++ b/src/wallet/wallet2.cpp +@@ -92,6 +92,7 @@ using namespace epee; + #include "device/device_cold.hpp" + #include "device_trezor/device_trezor.hpp" + #include "net/socks_connect.h" ++#include "polyseed/include/polyseed.h" + + extern "C" + { +@@ -1281,7 +1282,8 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended, std + m_enable_multisig(false), + m_pool_info_query_time(0), + m_has_ever_refreshed_from_node(false), +- m_allow_mismatched_daemon_version(false) ++ m_allow_mismatched_daemon_version(false), ++ m_polyseed(false) + { + set_rpc_client_secret_key(rct::rct2sk(rct::skGen())); + } +@@ -1486,6 +1488,20 @@ bool wallet2::get_seed(epee::wipeable_string& electrum_words, const epee::wipeab + return true; + } + //---------------------------------------------------------------------------------------------------- ++ ++bool wallet2::get_polyseed(epee::wipeable_string& polyseed, epee::wipeable_string& passphrase) const ++{ ++ if (!m_polyseed) { ++ return false; ++ } ++ ++ polyseed::data data(POLYSEED_COIN); ++ data.load(get_account().get_keys().m_polyseed); ++ data.encode(polyseed::get_lang_by_name(seed_language), polyseed); ++ passphrase = get_account().get_keys().m_passphrase; ++ return true; ++} ++//---------------------------------------------------------------------------------------------------- + bool wallet2::get_multisig_seed(epee::wipeable_string& seed, const epee::wipeable_string &passphrase) const + { + bool ready; +@@ -4851,6 +4867,9 @@ boost::optional wallet2::get_keys_file_data(const crypt + value2.SetInt(m_enable_multisig ? 1 : 0); + json.AddMember("enable_multisig", value2, json.GetAllocator()); + ++ value2.SetInt(m_polyseed ? 1 : 0); ++ json.AddMember("polyseed", value2, json.GetAllocator()); ++ + if (m_background_sync_type == BackgroundSyncCustomPassword && !background_keys_file && m_custom_background_key) + { + value.SetString(reinterpret_cast(m_custom_background_key.get().data()), m_custom_background_key.get().size()); +@@ -5090,6 +5109,7 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st + m_enable_multisig = false; + m_allow_mismatched_daemon_version = false; + m_custom_background_key = boost::none; ++ m_polyseed = false; + } + else if(json.IsObject()) + { +@@ -5330,6 +5350,9 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, background_sync_type, BackgroundSyncType, Int, false, BackgroundSyncOff); + m_background_sync_type = field_background_sync_type; + ++ GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, polyseed, int, Int, false, false); ++ m_polyseed = field_polyseed; ++ + // Load encryption key used to encrypt background cache + crypto::chacha_key custom_background_key; + m_custom_background_key = boost::none; +@@ -5649,6 +5672,48 @@ void wallet2::init_type(hw::device::device_type device_type) + m_key_device_type = device_type; + } + ++/*! ++ * \brief Generates a polyseed wallet or restores one. ++ * \param wallet_ Name of wallet file ++ * \param password Password of wallet file ++ * \param passphrase Seed offset passphrase ++ * \param recover Whether it is a restore ++ * \param seed_words If it is a restore, the polyseed ++ * \param create_address_file Whether to create an address file ++ * \return The secret key of the generated wallet ++ */ ++void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& password, ++ const polyseed::data &seed, const epee::wipeable_string& passphrase, bool recover, uint64_t restoreHeight, bool create_address_file) ++{ ++ clear(); ++ prepare_file_names(wallet_); ++ ++ if (!wallet_.empty()) { ++ boost::system::error_code ignored_ec; ++ THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_wallet_file, ignored_ec), error::file_exists, m_wallet_file); ++ THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_keys_file, ignored_ec), error::file_exists, m_keys_file); ++ } ++ ++ m_account.create_from_polyseed(seed, passphrase); ++ ++ init_type(hw::device::device_type::SOFTWARE); ++ m_polyseed = true; ++ setup_keys(password); ++ ++ if (recover) { ++ m_refresh_from_block_height = estimate_blockchain_height(restoreHeight > 0 ? restoreHeight : seed.birthday()); ++ } else { ++ m_refresh_from_block_height = estimate_blockchain_height(); ++ } ++ ++ create_keys_file(wallet_, false, password, m_nettype != MAINNET || create_address_file); ++ ++ setup_new_blockchain(); ++ ++ if (!wallet_.empty()) ++ store(); ++} ++ + /*! + * \brief Generates a wallet or restores one. Assumes the multisig setup + * has already completed for the provided multisig info. +@@ -5776,7 +5841,7 @@ crypto::secret_key wallet2::generate(const std::string& wallet_, const epee::wip + return retval; + } + +- uint64_t wallet2::estimate_blockchain_height() ++ uint64_t wallet2::estimate_blockchain_height(uint64_t time) + { + // -1 month for fluctuations in block time and machine date/time setup. + // avg seconds per block +@@ -5800,7 +5865,7 @@ crypto::secret_key wallet2::generate(const std::string& wallet_, const epee::wip + // the daemon is currently syncing. + // If we use the approximate height we subtract one month as + // a safety margin. +- height = get_approximate_blockchain_height(); ++ height = get_approximate_blockchain_height(time); + uint64_t target_height = get_daemon_blockchain_target_height(err); + if (err.empty()) { + if (target_height < height) +@@ -13634,9 +13699,10 @@ uint64_t wallet2::get_daemon_blockchain_target_height(string &err) + return target_height; + } + +-uint64_t wallet2::get_approximate_blockchain_height() const ++uint64_t wallet2::get_approximate_blockchain_height(uint64_t t) const + { + uint64_t approx_blockchain_height = m_nettype == TESTNET ? 0 : (time(NULL) - 1522624244)/307; ++ // uint64_t approx_blockchain_height = fork_block + ((t > 0 ? t : time(NULL)) - fork_time)/seconds_per_block; + LOG_PRINT_L2("Calculated blockchain height: " << approx_blockchain_height); + return approx_blockchain_height; + } +@@ -15758,15 +15824,6 @@ bool wallet2::parse_uri(const std::string &uri, std::string &address, std::strin + //---------------------------------------------------------------------------------------------------- + uint64_t wallet2::get_blockchain_height_by_date(uint16_t year, uint8_t month, uint8_t day) + { +- uint32_t version; +- if (!check_connection(&version)) +- { +- throw std::runtime_error("failed to connect to daemon: " + get_daemon_address()); +- } +- if (version < MAKE_CORE_RPC_VERSION(1, 6)) +- { +- throw std::runtime_error("this function requires RPC version 1.6 or higher"); +- } + std::tm date = { 0, 0, 0, 0, 0, 0, 0, 0 }; + date.tm_year = year - 1900; + date.tm_mon = month - 1; +@@ -15775,7 +15832,23 @@ uint64_t wallet2::get_blockchain_height_by_date(uint16_t year, uint8_t month, ui + { + throw std::runtime_error("month or day out of range"); + } ++ + uint64_t timestamp_target = std::mktime(&date); ++ ++ return get_blockchain_height_by_timestamp(timestamp_target); ++} ++ ++uint64_t wallet2::get_blockchain_height_by_timestamp(uint64_t timestamp_target) { ++ uint32_t version; ++ if (!check_connection(&version)) ++ { ++ throw std::runtime_error("failed to connect to daemon: " + get_daemon_address()); ++ } ++ if (version < MAKE_CORE_RPC_VERSION(1, 6)) ++ { ++ throw std::runtime_error("this function requires RPC version 1.6 or higher"); ++ } ++ + std::string err; + uint64_t height_min = 0; + uint64_t height_max = get_daemon_blockchain_height(err) - 1; +diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h +index e309cec5e..9c520fa99 100644 +--- a/src/wallet/wallet2.h ++++ b/src/wallet/wallet2.h +@@ -72,6 +72,7 @@ + #include "message_store.h" + #include "wallet_light_rpc.h" + #include "wallet_rpc_helpers.h" ++#include "polyseed/polyseed.hpp" + + #undef MONERO_DEFAULT_LOG_CATEGORY + #define MONERO_DEFAULT_LOG_CATEGORY "wallet.wallet2" +@@ -921,6 +922,20 @@ private: + void generate(const std::string& wallet_, const epee::wipeable_string& password, + const epee::wipeable_string& multisig_data, bool create_address_file = false); + ++ /*! ++ * \brief Generates a wallet from a polyseed. ++ * @param wallet_ Name of wallet file ++ * @param password Password of wallet file ++ * @param seed Polyseed data ++ * @param passphrase Optional seed offset passphrase ++ * @param recover Whether it is a restore ++ * @param restoreHeight Override the embedded restore height ++ * @param create_address_file Whether to create an address file ++ */ ++ void generate(const std::string& wallet_, const epee::wipeable_string& password, ++ const polyseed::data &seed, const epee::wipeable_string& passphrase = "", ++ bool recover = false, uint64_t restoreHeight = 0, bool create_address_file = false); ++ + /*! + * \brief Generates a wallet or restores one. + * \param wallet_ Name of wallet file +@@ -1095,6 +1110,15 @@ private: + bool is_deterministic() const; + bool get_seed(epee::wipeable_string& electrum_words, const epee::wipeable_string &passphrase = epee::wipeable_string()) const; + ++ /*! ++ * \brief get_polyseed Gets the polyseed (if available) and passphrase (if set) needed to recover the wallet. ++ * @param seed Polyseed mnemonic phrase ++ * @param passphrase Seed offset passphrase that was used to restore the wallet ++ * @return Returns true if the wallet has a polyseed. ++ * Note: both the mnemonic phrase and the passphrase are needed to recover the wallet ++ */ ++ bool get_polyseed(epee::wipeable_string& seed, epee::wipeable_string &passphrase) const; ++ + /*! + * \brief Checks if light wallet. A light wallet sends view key to a server where the blockchain is scanned. + */ +@@ -1569,8 +1593,8 @@ private: + /*! + * \brief Calculates the approximate blockchain height from current date/time. + */ +- uint64_t get_approximate_blockchain_height() const; +- uint64_t estimate_blockchain_height(); ++ uint64_t get_approximate_blockchain_height(uint64_t time = 0) const; ++ uint64_t estimate_blockchain_height(uint64_t time = 0); + std::vector select_available_outputs_from_histogram(uint64_t count, bool atleast, bool unlocked, bool allow_rct); + std::vector select_available_outputs(const std::function &f); + std::vector select_available_unmixable_outputs(); +@@ -1664,6 +1688,7 @@ private: + bool parse_uri(const std::string &uri, std::string &address, std::string &payment_id, uint64_t &amount, std::string &tx_description, std::string &recipient_name, std::vector &unknown_parameters, std::string &error); + + uint64_t get_blockchain_height_by_date(uint16_t year, uint8_t month, uint8_t day); // 1<=month<=12, 1<=day<=31 ++ uint64_t get_blockchain_height_by_timestamp(uint64_t timestamp); + + bool is_synced(); + +@@ -2010,6 +2035,7 @@ private: + 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_polyseed; + bool m_multisig; /*!< if > 1 spend secret key will not match spend public key */ + uint32_t m_multisig_threshold; + std::vector m_multisig_signers; +-- +2.51.0 + diff --git a/patches/wownero/0009-coin-control.patch b/patches/wownero/0009-coin-control.patch new file mode 100644 index 0000000..d3dd5e0 --- /dev/null +++ b/patches/wownero/0009-coin-control.patch @@ -0,0 +1,979 @@ +From 8b680843825e374cb21d2ea7895442d5436cd12d Mon Sep 17 00:00:00 2001 +From: tobtoht +Date: Tue, 12 Mar 2024 11:07:57 +0100 +Subject: [PATCH 09/16] coin control + +--- + src/simplewallet/simplewallet.cpp | 2 +- + src/wallet/api/CMakeLists.txt | 8 +- + src/wallet/api/coins.cpp | 186 ++++++++++++++++++++++++++++++ + src/wallet/api/coins.h | 40 +++++++ + src/wallet/api/coins_info.cpp | 122 ++++++++++++++++++++ + src/wallet/api/coins_info.h | 71 ++++++++++++ + src/wallet/api/wallet.cpp | 64 +++++++++- + src/wallet/api/wallet.h | 10 +- + src/wallet/api/wallet2_api.h | 52 ++++++++- + src/wallet/wallet2.cpp | 46 +++++++- + src/wallet/wallet2.h | 11 +- + 11 files changed, 593 insertions(+), 19 deletions(-) + create mode 100644 src/wallet/api/coins.cpp + create mode 100644 src/wallet/api/coins.h + create mode 100644 src/wallet/api/coins_info.cpp + create mode 100644 src/wallet/api/coins_info.h + +diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp +index 42e45054d..58f60783e 100644 +--- a/src/simplewallet/simplewallet.cpp ++++ b/src/simplewallet/simplewallet.cpp +@@ -6971,7 +6971,7 @@ bool simple_wallet::transfer_main(const std::vector &args_, bool ca + { + // figure out what tx will be necessary + auto ptx_vector = m_wallet->create_transactions_2(dsts, fake_outs_count, priority, extra, +- m_current_subaddress_account, subaddr_indices, subtract_fee_from_outputs); ++ m_current_subaddress_account, subaddr_indices, {}, subtract_fee_from_outputs); + + if (ptx_vector.empty()) + { +diff --git a/src/wallet/api/CMakeLists.txt b/src/wallet/api/CMakeLists.txt +index af7948d8a..bb740e2ac 100644 +--- a/src/wallet/api/CMakeLists.txt ++++ b/src/wallet/api/CMakeLists.txt +@@ -40,7 +40,9 @@ set(wallet_api_sources + address_book.cpp + subaddress.cpp + subaddress_account.cpp +- unsigned_transaction.cpp) ++ unsigned_transaction.cpp ++ coins.cpp ++ coins_info.cpp) + + set(wallet_api_headers + wallet2_api.h) +@@ -55,7 +57,9 @@ set(wallet_api_private_headers + address_book.h + subaddress.h + subaddress_account.h +- unsigned_transaction.h) ++ unsigned_transaction.h ++ coins.h ++ coins_info.h) + + monero_private_headers(wallet_api + ${wallet_api_private_headers}) +diff --git a/src/wallet/api/coins.cpp b/src/wallet/api/coins.cpp +new file mode 100644 +index 000000000..ef12141cf +--- /dev/null ++++ b/src/wallet/api/coins.cpp +@@ -0,0 +1,186 @@ ++#include "coins.h" ++#include "coins_info.h" ++#include "wallet.h" ++#include "crypto/hash.h" ++#include "wallet/wallet2.h" ++#include "common_defines.h" ++ ++#include ++#include ++ ++using namespace epee; ++ ++namespace Monero { ++ ++Coins::~Coins() = default; ++ ++CoinsImpl::CoinsImpl(WalletImpl *wallet) ++ : m_wallet(wallet) {} ++ ++CoinsImpl::~CoinsImpl() ++{ ++ for (auto t : m_rows) ++ delete t; ++} ++ ++int CoinsImpl::count() const ++{ ++ boost::shared_lock lock(m_rowsMutex); ++ int result = m_rows.size(); ++ return result; ++} ++ ++CoinsInfo *CoinsImpl::coin(int index) const ++{ ++ boost::shared_lock lock(m_rowsMutex); ++ // sanity check ++ if (index < 0) ++ return nullptr; ++ auto index_ = static_cast(index); ++ return index_ < m_rows.size() ? m_rows[index_] : nullptr; ++} ++ ++std::vector CoinsImpl::getAll() const ++{ ++ boost::shared_lock lock(m_rowsMutex); ++ return m_rows; ++} ++ ++ ++void CoinsImpl::refresh() ++{ ++ LOG_PRINT_L2("Refreshing coins"); ++ ++ boost::unique_lock lock(m_rowsMutex); ++ boost::shared_lock transfers_lock(m_wallet->m_wallet->m_transfers_mutex); ++ ++ // delete old outputs; ++ for (auto t : m_rows) ++ delete t; ++ m_rows.clear(); ++ ++ for (size_t i = 0; i < m_wallet->m_wallet->get_num_transfer_details(); ++i) ++ { ++ const tools::wallet2::transfer_details &td = m_wallet->m_wallet->get_transfer_details(i); ++ ++ auto ci = new CoinsInfoImpl(); ++ ci->m_blockHeight = td.m_block_height; ++ ci->m_hash = string_tools::pod_to_hex(td.m_txid); ++ ci->m_internalOutputIndex = td.m_internal_output_index; ++ ci->m_globalOutputIndex = td.m_global_output_index; ++ ci->m_spent = td.m_spent; ++ ci->m_frozen = td.m_frozen; ++ ci->m_spentHeight = td.m_spent_height; ++ ci->m_amount = td.m_amount; ++ ci->m_rct = td.m_rct; ++ ci->m_keyImageKnown = td.m_key_image_known; ++ ci->m_pkIndex = td.m_pk_index; ++ ci->m_subaddrIndex = td.m_subaddr_index.minor; ++ ci->m_subaddrAccount = td.m_subaddr_index.major; ++ ci->m_address = m_wallet->m_wallet->get_subaddress_as_str(td.m_subaddr_index); // todo: this is expensive, cache maybe? ++ ci->m_addressLabel = m_wallet->m_wallet->get_subaddress_label(td.m_subaddr_index); ++ ci->m_keyImage = string_tools::pod_to_hex(td.m_key_image); ++ ci->m_unlockTime = td.m_tx.unlock_time; ++ ci->m_unlocked = m_wallet->m_wallet->is_transfer_unlocked(td); ++ ci->m_pubKey = string_tools::pod_to_hex(td.get_public_key()); ++ ci->m_coinbase = td.m_tx.vin.size() == 1 && td.m_tx.vin[0].type() == typeid(cryptonote::txin_gen); ++ ci->m_description = m_wallet->m_wallet->get_tx_note(td.m_txid); ++ ++ m_rows.push_back(ci); ++ } ++} ++ ++void CoinsImpl::setFrozen(std::string public_key) ++{ ++ crypto::public_key pk; ++ if (!epee::string_tools::hex_to_pod(public_key, pk)) ++ { ++ LOG_ERROR("Invalid public key: " << public_key); ++ return; ++ } ++ ++ try ++ { ++ m_wallet->m_wallet->freeze(pk); ++ refresh(); ++ } ++ catch (const std::exception& e) ++ { ++ LOG_ERROR("setFrozen: " << e.what()); ++ } ++} ++ ++void CoinsImpl::setFrozen(int index) ++{ ++ try ++ { ++ LOG_ERROR("Freezing coin: " << index); ++ m_wallet->m_wallet->freeze(index); ++ refresh(); ++ } ++ catch (const std::exception& e) ++ { ++ LOG_ERROR("setLabel: " << e.what()); ++ } ++} ++ ++void CoinsImpl::thaw(std::string public_key) ++{ ++ crypto::public_key pk; ++ if (!epee::string_tools::hex_to_pod(public_key, pk)) ++ { ++ LOG_ERROR("Invalid public key: " << public_key); ++ return; ++ } ++ ++ try ++ { ++ m_wallet->m_wallet->thaw(pk); ++ refresh(); ++ } ++ catch (const std::exception& e) ++ { ++ LOG_ERROR("thaw: " << e.what()); ++ } ++} ++ ++void CoinsImpl::thaw(int index) ++{ ++ try ++ { ++ m_wallet->m_wallet->thaw(index); ++ refresh(); ++ } ++ catch (const std::exception& e) ++ { ++ LOG_ERROR("thaw: " << e.what()); ++ } ++} ++ ++bool CoinsImpl::isTransferUnlocked(uint64_t unlockTime, uint64_t blockHeight) { ++ return m_wallet->m_wallet->is_transfer_unlocked(unlockTime, blockHeight); ++} ++ ++void CoinsImpl::setDescription(const std::string &public_key, const std::string &description) ++{ ++ crypto::public_key pk; ++ if (!epee::string_tools::hex_to_pod(public_key, pk)) ++ { ++ LOG_ERROR("Invalid public key: " << public_key); ++ return; ++ } ++ ++ try ++ { ++ const size_t index = m_wallet->m_wallet->get_transfer_details(pk); ++ const tools::wallet2::transfer_details &td = m_wallet->m_wallet->get_transfer_details(index); ++ m_wallet->m_wallet->set_tx_note(td.m_txid, description); ++ refresh(); ++ } ++ catch (const std::exception& e) ++ { ++ LOG_ERROR("setDescription: " << e.what()); ++ } ++} ++ ++} // namespace +diff --git a/src/wallet/api/coins.h b/src/wallet/api/coins.h +new file mode 100644 +index 000000000..b7a0a8642 +--- /dev/null ++++ b/src/wallet/api/coins.h +@@ -0,0 +1,40 @@ ++#ifndef FEATHER_COINS_H ++#define FEATHER_COINS_H ++ ++#include "wallet/api/wallet2_api.h" ++#include "wallet/wallet2.h" ++ ++namespace Monero { ++ ++class WalletImpl; ++ ++class CoinsImpl : public Coins ++{ ++public: ++ explicit CoinsImpl(WalletImpl * wallet); ++ ~CoinsImpl() override; ++ int count() const override; ++ CoinsInfo * coin(int index) const override; ++ std::vector getAll() const override; ++ void refresh() override; ++ ++ void setFrozen(std::string public_key) override; ++ void setFrozen(int index) override; ++ void thaw(std::string public_key) override; ++ void thaw(int index) override; ++ ++ bool isTransferUnlocked(uint64_t unlockTime, uint64_t blockHeight) override; ++ ++ void setDescription(const std::string &public_key, const std::string &description) override; ++ ++private: ++ WalletImpl *m_wallet; ++ std::vector m_rows; ++ mutable boost::shared_mutex m_rowsMutex; ++}; ++ ++} ++ ++namespace Bitmonero = Monero; ++ ++#endif //FEATHER_COINS_H +diff --git a/src/wallet/api/coins_info.cpp b/src/wallet/api/coins_info.cpp +new file mode 100644 +index 000000000..5f2c4e1e4 +--- /dev/null ++++ b/src/wallet/api/coins_info.cpp +@@ -0,0 +1,122 @@ ++#include "coins_info.h" ++ ++using namespace std; ++ ++namespace Monero { ++ ++CoinsInfo::~CoinsInfo() = default; ++ ++CoinsInfoImpl::CoinsInfoImpl() ++ : m_blockHeight(0) ++ , m_internalOutputIndex(0) ++ , m_globalOutputIndex(0) ++ , m_spent(false) ++ , m_frozen(false) ++ , m_spentHeight(0) ++ , m_amount(0) ++ , m_rct(false) ++ , m_keyImageKnown(false) ++ , m_pkIndex(0) ++ , m_subaddrAccount(0) ++ , m_subaddrIndex(0) ++ , m_unlockTime(0) ++ , m_unlocked(false) ++{ ++ ++} ++ ++CoinsInfoImpl::~CoinsInfoImpl() = default; ++ ++uint64_t CoinsInfoImpl::blockHeight() const ++{ ++ return m_blockHeight; ++} ++ ++string CoinsInfoImpl::hash() const ++{ ++ return m_hash; ++} ++ ++size_t CoinsInfoImpl::internalOutputIndex() const { ++ return m_internalOutputIndex; ++} ++ ++uint64_t CoinsInfoImpl::globalOutputIndex() const ++{ ++ return m_globalOutputIndex; ++} ++ ++bool CoinsInfoImpl::spent() const ++{ ++ return m_spent; ++} ++ ++bool CoinsInfoImpl::frozen() const ++{ ++ return m_frozen; ++} ++ ++uint64_t CoinsInfoImpl::spentHeight() const ++{ ++ return m_spentHeight; ++} ++ ++uint64_t CoinsInfoImpl::amount() const ++{ ++ return m_amount; ++} ++ ++bool CoinsInfoImpl::rct() const { ++ return m_rct; ++} ++ ++bool CoinsInfoImpl::keyImageKnown() const { ++ return m_keyImageKnown; ++} ++ ++size_t CoinsInfoImpl::pkIndex() const { ++ return m_pkIndex; ++} ++ ++uint32_t CoinsInfoImpl::subaddrIndex() const { ++ return m_subaddrIndex; ++} ++ ++uint32_t CoinsInfoImpl::subaddrAccount() const { ++ return m_subaddrAccount; ++} ++ ++string CoinsInfoImpl::address() const { ++ return m_address; ++} ++ ++string CoinsInfoImpl::addressLabel() const { ++ return m_addressLabel; ++} ++ ++string CoinsInfoImpl::keyImage() const { ++ return m_keyImage; ++} ++ ++uint64_t CoinsInfoImpl::unlockTime() const { ++ return m_unlockTime; ++} ++ ++bool CoinsInfoImpl::unlocked() const { ++ return m_unlocked; ++} ++ ++string CoinsInfoImpl::pubKey() const { ++ return m_pubKey; ++} ++ ++bool CoinsInfoImpl::coinbase() const { ++ return m_coinbase; ++} ++ ++string CoinsInfoImpl::description() const { ++ return m_description; ++} ++} // namespace ++ ++namespace Bitmonero = Monero; +diff --git a/src/wallet/api/coins_info.h b/src/wallet/api/coins_info.h +new file mode 100644 +index 000000000..c43e45abd +--- /dev/null ++++ b/src/wallet/api/coins_info.h +@@ -0,0 +1,71 @@ ++#ifndef FEATHER_COINS_INFO_H ++#define FEATHER_COINS_INFO_H ++ ++#include "wallet/api/wallet2_api.h" ++#include ++#include ++ ++namespace Monero { ++ ++class CoinsImpl; ++ ++class CoinsInfoImpl : public CoinsInfo ++{ ++public: ++ CoinsInfoImpl(); ++ ~CoinsInfoImpl(); ++ ++ virtual uint64_t blockHeight() const override; ++ virtual std::string hash() const override; ++ virtual size_t internalOutputIndex() const override; ++ virtual uint64_t globalOutputIndex() const override; ++ virtual bool spent() const override; ++ virtual bool frozen() const override; ++ virtual uint64_t spentHeight() const override; ++ virtual uint64_t amount() const override; ++ virtual bool rct() const override; ++ virtual bool keyImageKnown() const override; ++ virtual size_t pkIndex() const override; ++ virtual uint32_t subaddrIndex() const override; ++ virtual uint32_t subaddrAccount() const override; ++ virtual std::string address() const override; ++ virtual std::string addressLabel() const override; ++ virtual std::string keyImage() const override; ++ virtual uint64_t unlockTime() const override; ++ virtual bool unlocked() const override; ++ virtual std::string pubKey() const override; ++ virtual bool coinbase() const override; ++ virtual std::string description() const override; ++ ++private: ++ uint64_t m_blockHeight; ++ std::string m_hash; ++ size_t m_internalOutputIndex; ++ uint64_t m_globalOutputIndex; ++ bool m_spent; ++ bool m_frozen; ++ uint64_t m_spentHeight; ++ uint64_t m_amount; ++ bool m_rct; ++ bool m_keyImageKnown; ++ size_t m_pkIndex; ++ uint32_t m_subaddrIndex; ++ uint32_t m_subaddrAccount; ++ std::string m_address; ++ std::string m_addressLabel; ++ std::string m_keyImage; ++ uint64_t m_unlockTime; ++ bool m_unlocked; ++ std::string m_pubKey; ++ bool m_coinbase; ++ std::string m_description; ++ ++ friend class CoinsImpl; ++ ++}; ++ ++} // namespace ++ ++namespace Bitmonero = Monero; ++ ++#endif //FEATHER_COINS_INFO_H +diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp +index f315b7ed6..7066fc3d8 100644 +--- a/src/wallet/api/wallet.cpp ++++ b/src/wallet/api/wallet.cpp +@@ -35,6 +35,7 @@ + #include "transaction_history.h" + #include "address_book.h" + #include "subaddress.h" ++#include "coins.h" + #include "subaddress_account.h" + #include "common_defines.h" + #include "common/util.h" +@@ -473,6 +474,7 @@ WalletImpl::WalletImpl(NetworkType nettype, uint64_t kdf_rounds) + m_wallet->set_refresh_enabled(false); + m_addressBook.reset(new AddressBookImpl(this)); + m_subaddress.reset(new SubaddressImpl(this)); ++ m_coins.reset(new CoinsImpl(this)); + m_subaddressAccount.reset(new SubaddressAccountImpl(this)); + + +@@ -2046,7 +2048,7 @@ PendingTransaction* WalletImpl::restoreMultisigTransaction(const string& signDat + // - unconfirmed_transfer_details; + // - confirmed_transfer_details) + +-PendingTransaction *WalletImpl::createTransactionMultDest(const std::vector &dst_addr, const string &payment_id, optional> amount, uint32_t mixin_count, PendingTransaction::Priority priority, uint32_t subaddr_account, std::set subaddr_indices) ++PendingTransaction *WalletImpl::createTransactionMultDest(const std::vector &dst_addr, const string &payment_id, optional> amount, uint32_t mixin_count, PendingTransaction::Priority priority, uint32_t subaddr_account, std::set subaddr_indices, const std::set &preferred_inputs) + + { + clearStatus(); +@@ -2084,6 +2086,7 @@ PendingTransaction *WalletImpl::createTransactionMultDest(const std::vectornettype(), dst_addr[i])) { + // TODO: copy-paste 'if treating as an address fails, try as url' from simplewallet.cpp:1982 +@@ -2105,6 +2108,7 @@ PendingTransaction *WalletImpl::createTransactionMultDest(const std::vectorunlocked_balance(subaddr_account, true); ++ // if (maxAllowedSpend < amountSum) { ++ // error = true; ++ // setStatusError(tr("Amount you are trying to spend is larger than unlocked amount")); ++ // break; ++ // } ++ std::vector preferred_input_list; ++ if (!preferred_inputs.empty()) { ++ LOG_ERROR("empty"); ++ ++ for (const auto &public_key : preferred_inputs) { ++ crypto::key_image keyImage; ++ bool r = epee::string_tools::hex_to_pod(public_key, keyImage); ++ if (!r) { ++ error = true; ++ setStatusError(tr("failed to parse key image")); ++ break; ++ } ++ if (m_wallet->frozen(keyImage)) { ++ error = true; ++ setStatusError(tr("refusing to spend frozen coin")); ++ break; ++ } ++ ++ preferred_input_list.push_back(keyImage); ++ } ++ } else { ++ LOG_ERROR("not empty"); ++ ++ boost::shared_lock transfers_lock(m_wallet->m_transfers_mutex); ++ for (size_t i = 0; i < m_wallet->get_num_transfer_details(); ++i) { ++ const tools::wallet2::transfer_details &td = m_wallet->get_transfer_details(i); ++ LOG_ERROR("COIN: " << i << ": " << td.amount() << "; "<frozen(td)); ++ if (td.m_spent) continue; ++ LOG_ERROR("is frozen"); ++ if (!td.m_frozen) { ++ LOG_ERROR("isn't:"); ++ LOG_ERROR("hash: " << td.m_key_image << "; " << td.amount()); ++ preferred_input_list.push_back(td.m_key_image); ++ } ++ } ++ } ++ for (const auto &de : preferred_input_list) { ++ LOG_ERROR("preferred input: " << de); ++ } + if (error) { + break; + } +@@ -2129,11 +2178,11 @@ PendingTransaction *WalletImpl::createTransactionMultDest(const std::vectorm_pending_tx = m_wallet->create_transactions_2(dsts, fake_outs_count, + adjusted_priority, +- extra, subaddr_account, subaddr_indices); ++ extra, subaddr_account, subaddr_indices, preferred_input_list); + } else { + transaction->m_pending_tx = m_wallet->create_transactions_all(0, info.address, info.is_subaddress, 1, fake_outs_count, + adjusted_priority, +- extra, subaddr_account, subaddr_indices); ++ extra, subaddr_account, subaddr_indices, preferred_input_list); + } + pendingTxPostProcess(transaction); + +@@ -2214,10 +2263,10 @@ PendingTransaction *WalletImpl::createTransactionMultDest(const std::vector amount, uint32_t mixin_count, +- PendingTransaction::Priority priority, uint32_t subaddr_account, std::set subaddr_indices) ++ PendingTransaction::Priority priority, uint32_t subaddr_account, std::set subaddr_indices, const std::set &preferred_inputs) + + { +- return createTransactionMultDest(std::vector {dst_addr}, payment_id, amount ? (std::vector {*amount}) : (optional>()), mixin_count, priority, subaddr_account, subaddr_indices); ++ return createTransactionMultDest(std::vector {dst_addr}, payment_id, amount ? (std::vector {*amount}) : (optional>()), mixin_count, priority, subaddr_account, subaddr_indices, preferred_inputs); + } + + PendingTransaction *WalletImpl::createSweepUnmixableTransaction() +@@ -2343,6 +2392,11 @@ AddressBook *WalletImpl::addressBook() + return m_addressBook.get(); + } + ++Coins *WalletImpl::coins() ++{ ++ return m_coins.get(); ++} ++ + Subaddress *WalletImpl::subaddress() + { + return m_subaddress.get(); +diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h +index 32e12284b..a82f270e4 100644 +--- a/src/wallet/api/wallet.h ++++ b/src/wallet/api/wallet.h +@@ -46,6 +46,7 @@ class PendingTransactionImpl; + class UnsignedTransactionImpl; + class AddressBookImpl; + class SubaddressImpl; ++class CoinsImpl; + class SubaddressAccountImpl; + struct Wallet2CallbackImpl; + +@@ -167,12 +168,14 @@ public: + optional> amount, uint32_t mixin_count, + PendingTransaction::Priority priority = PendingTransaction::Priority_Low, + uint32_t subaddr_account = 0, +- std::set subaddr_indices = {}) override; ++ std::set subaddr_indices = {}, ++ const std::set &preferred_inputs = {}) override; + PendingTransaction * createTransaction(const std::string &dst_addr, const std::string &payment_id, + optional amount, uint32_t mixin_count, + PendingTransaction::Priority priority = PendingTransaction::Priority_Low, + uint32_t subaddr_account = 0, +- std::set subaddr_indices = {}) override; ++ std::set subaddr_indices = {}, ++ const std::set &preferred_inputs = {}) override; + virtual PendingTransaction * createSweepUnmixableTransaction() override; + bool submitTransaction(const std::string &fileName) override; + bool submitTransactionUR(const std::string &input) override; +@@ -201,6 +204,7 @@ public: + PendingTransaction::Priority priority) const override; + virtual TransactionHistory * history() override; + virtual AddressBook * addressBook() override; ++ virtual Coins * coins() override; + virtual Subaddress * subaddress() override; + virtual SubaddressAccount * subaddressAccount() override; + virtual void setListener(WalletListener * l) override; +@@ -272,6 +276,7 @@ private: + friend class TransactionHistoryImpl; + friend struct Wallet2CallbackImpl; + friend class AddressBookImpl; ++ friend class CoinsImpl; + friend class SubaddressImpl; + friend class SubaddressAccountImpl; + +@@ -288,6 +293,7 @@ private: + std::unique_ptr m_wallet2Callback; + std::unique_ptr m_addressBook; + std::unique_ptr m_subaddress; ++ std::unique_ptr m_coins; + std::unique_ptr m_subaddressAccount; + + // multi-threaded refresh stuff +diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h +index be1c3704e..013b5bcba 100644 +--- a/src/wallet/api/wallet2_api.h ++++ b/src/wallet/api/wallet2_api.h +@@ -263,6 +263,51 @@ struct AddressBook + virtual int lookupPaymentID(const std::string &payment_id) const = 0; + }; + ++/** ++ * @brief The CoinsInfo - interface for displaying coins information ++ */ ++struct CoinsInfo ++{ ++ virtual ~CoinsInfo() = 0; ++ ++ virtual uint64_t blockHeight() const = 0; ++ virtual std::string hash() const = 0; ++ virtual size_t internalOutputIndex() const = 0; ++ virtual uint64_t globalOutputIndex() const = 0; ++ virtual bool spent() const = 0; ++ virtual bool frozen() const = 0; ++ virtual uint64_t spentHeight() const = 0; ++ virtual uint64_t amount() const = 0; ++ virtual bool rct() const = 0; ++ virtual bool keyImageKnown() const = 0; ++ virtual size_t pkIndex() const = 0; ++ virtual uint32_t subaddrIndex() const = 0; ++ virtual uint32_t subaddrAccount() const = 0; ++ virtual std::string address() const = 0; ++ virtual std::string addressLabel() const = 0; ++ virtual std::string keyImage() const = 0; ++ virtual uint64_t unlockTime() const = 0; ++ virtual bool unlocked() const = 0; ++ virtual std::string pubKey() const = 0; ++ virtual bool coinbase() const = 0; ++ virtual std::string description() const = 0; ++}; ++ ++struct Coins ++{ ++ virtual ~Coins() = 0; ++ virtual int count() const = 0; ++ virtual CoinsInfo * coin(int index) const = 0; ++ virtual std::vector getAll() const = 0; ++ virtual void refresh() = 0; ++ virtual void setFrozen(std::string public_key) = 0; ++ virtual void setFrozen(int index) = 0; ++ virtual void thaw(std::string public_key) = 0; ++ virtual void thaw(int index) = 0; ++ virtual bool isTransferUnlocked(uint64_t unlockTime, uint64_t blockHeight) = 0; ++ virtual void setDescription(const std::string &public_key, const std::string &description) = 0; ++}; ++ + struct SubaddressRow { + public: + SubaddressRow(std::size_t _rowId, const std::string &_address, const std::string &_label): +@@ -856,7 +901,8 @@ struct Wallet + optional> amount, uint32_t mixin_count, + PendingTransaction::Priority = PendingTransaction::Priority_Low, + uint32_t subaddr_account = 0, +- std::set subaddr_indices = {}) = 0; ++ std::set subaddr_indices = {}, ++ const std::set &preferred_inputs = {}) = 0; + + /*! + * \brief createTransaction creates transaction. if dst_addr is an integrated address, payment_id is ignored +@@ -875,7 +921,8 @@ struct Wallet + optional amount, uint32_t mixin_count, + PendingTransaction::Priority = PendingTransaction::Priority_Low, + uint32_t subaddr_account = 0, +- std::set subaddr_indices = {}) = 0; ++ std::set subaddr_indices = {}, ++ const std::set &preferred_inputs = {}) = 0; + + /*! + * \brief createSweepUnmixableTransaction creates transaction with unmixable outputs. +@@ -994,6 +1041,7 @@ struct Wallet + + virtual TransactionHistory * history() = 0; + virtual AddressBook * addressBook() = 0; ++ virtual Coins * coins() = 0; + virtual Subaddress * subaddress() = 0; + virtual SubaddressAccount * subaddressAccount() = 0; + virtual void setListener(WalletListener *) = 0; +diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp +index 849128ca5..f96de960a 100644 +--- a/src/wallet/wallet2.cpp ++++ b/src/wallet/wallet2.cpp +@@ -2136,12 +2136,21 @@ bool wallet2::frozen(const multisig_tx_set& txs) const + + return false; + } ++void wallet2::freeze(const crypto::public_key &pk) ++{ ++ freeze(get_transfer_details(pk)); ++} + //---------------------------------------------------------------------------------------------------- + void wallet2::freeze(const crypto::key_image &ki) + { + freeze(get_transfer_details(ki)); + } + //---------------------------------------------------------------------------------------------------- ++void wallet2::thaw(const crypto::public_key &pk) ++{ ++ thaw(get_transfer_details(pk)); ++} ++//---------------------------------------------------------------------------------------------------- + void wallet2::thaw(const crypto::key_image &ki) + { + thaw(get_transfer_details(ki)); +@@ -2152,6 +2161,18 @@ bool wallet2::frozen(const crypto::key_image &ki) const + return frozen(get_transfer_details(ki)); + } + //---------------------------------------------------------------------------------------------------- ++size_t wallet2::get_transfer_details(const crypto::public_key &pk) const ++{ ++ for (size_t idx = 0; idx < m_transfers.size(); ++idx) ++ { ++ const transfer_details &td = m_transfers[idx]; ++ if (td.get_public_key() == pk) { ++ return idx; ++ } ++ } ++ CHECK_AND_ASSERT_THROW_MES(false, "Public key not found"); ++} ++//---------------------------------------------------------------------------------------------------- + size_t wallet2::get_transfer_details(const crypto::key_image &ki) const + { + for (size_t idx = 0; idx < m_transfers.size(); ++idx) +@@ -2566,6 +2587,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote + uint64_t amount = tx.vout[o].amount ? tx.vout[o].amount : tx_scan_info[o].amount; + if (!pool) + { ++ boost::unique_lock lock(m_transfers_mutex); + m_transfers.push_back(transfer_details{}); + transfer_details& td = m_transfers.back(); + td.m_block_height = height; +@@ -2669,6 +2691,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote + uint64_t extra_amount = amount - burnt; + if (!pool) + { ++ boost::unique_lock lock(m_transfers_mutex); + transfer_details &td = m_transfers[kit->second]; + td.m_block_height = height; + td.m_internal_output_index = o; +@@ -10494,7 +10517,7 @@ void wallet2::transfer_selected_rct(std::vector wallet2::pick_preferred_rct_inputs(uint64_t needed_money, uint32_t subaddr_account, const std::set &subaddr_indices) ++std::vector wallet2::pick_preferred_rct_inputs(uint64_t needed_money, uint32_t subaddr_account, const std::set &subaddr_indices, const std::vector& preferred_input_list) + { + std::vector picks; + float current_output_relatdness = 1.0f; +@@ -10505,6 +10528,9 @@ std::vector wallet2::pick_preferred_rct_inputs(uint64_t needed_money, ui + for (size_t i = 0; i < m_transfers.size(); ++i) + { + const transfer_details& td = m_transfers[i]; ++ if (!is_preferred_input(preferred_input_list, td.m_key_image)) { ++ continue; ++ } + if (!is_spent(td, false) && !td.m_frozen && td.is_rct() && td.amount() >= needed_money && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1) + { + if (td.amount() > m_ignore_outputs_above || td.amount() < m_ignore_outputs_below) +@@ -10525,6 +10551,9 @@ std::vector wallet2::pick_preferred_rct_inputs(uint64_t needed_money, ui + for (size_t i = 0; i < m_transfers.size(); ++i) + { + const transfer_details& td = m_transfers[i]; ++ if (!is_preferred_input(preferred_input_list, td.m_key_image)) { ++ continue; ++ } + if (!is_spent(td, false) && !td.m_frozen && !td.m_key_image_partial && td.is_rct() && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1) + { + if (td.amount() > m_ignore_outputs_above || td.amount() < m_ignore_outputs_below) +@@ -10536,6 +10565,9 @@ std::vector wallet2::pick_preferred_rct_inputs(uint64_t needed_money, ui + for (size_t j = i + 1; j < m_transfers.size(); ++j) + { + const transfer_details& td2 = m_transfers[j]; ++ if (!is_preferred_input(preferred_input_list, td2.m_key_image)) { ++ continue; ++ } + if (td2.amount() > m_ignore_outputs_above || td2.amount() < m_ignore_outputs_below) + { + MDEBUG("Ignoring output " << j << " of amount " << print_money(td2.amount()) << " which is outside prescribed range [" << print_money(m_ignore_outputs_below) << ", " << print_money(m_ignore_outputs_above) << "]"); +@@ -11108,7 +11140,7 @@ bool wallet2::light_wallet_key_image_is_ours(const crypto::key_image& key_image, + // This system allows for sending (almost) the entire balance, since it does + // not generate spurious change in all txes, thus decreasing the instantaneous + // usable balance. +-std::vector wallet2::create_transactions_2(std::vector dsts, const size_t fake_outs_count, uint32_t priority, const std::vector& extra, uint32_t subaddr_account, std::set subaddr_indices, const unique_index_container& subtract_fee_from_outputs) ++std::vector wallet2::create_transactions_2(std::vector dsts, const size_t fake_outs_count, uint32_t priority, const std::vector& extra, uint32_t subaddr_account, std::set subaddr_indices, const std::vector& preferred_input_list, const unique_index_container& subtract_fee_from_outputs) + { + //ensure device is let in NONE mode in any case + hw::device &hwdev = m_account.get_device(); +@@ -11317,6 +11349,9 @@ std::vector wallet2::create_transactions_2(std::vector wallet2::create_transactions_2(std::vector &ptx_vector, c + return true; + } + +-std::vector wallet2::create_transactions_all(uint64_t below, const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, const size_t fake_outs_count, uint32_t priority, const std::vector& extra, uint32_t subaddr_account, std::set subaddr_indices) ++std::vector wallet2::create_transactions_all(uint64_t below, const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, const size_t fake_outs_count, uint32_t priority, const std::vector& extra, uint32_t subaddr_account, std::set subaddr_indices, const std::vector& preferred_input_list) + { + std::vector unused_transfers_indices; + std::vector unused_dust_indices; +@@ -11917,6 +11952,9 @@ std::vector wallet2::create_transactions_all(uint64_t below + for (size_t i = 0; i < m_transfers.size(); ++i) + { + const transfer_details& td = m_transfers[i]; ++ if (!is_preferred_input(preferred_input_list, td.m_key_image)) { ++ continue; ++ } + if (m_ignore_fractional_outputs && td.amount() < fractional_threshold) + { + MDEBUG("Ignoring output " << i << " of amount " << print_money(td.amount()) << " which is below threshold " << print_money(fractional_threshold)); +diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h +index 9c520fa99..66eea3ec9 100644 +--- a/src/wallet/wallet2.h ++++ b/src/wallet/wallet2.h +@@ -1223,8 +1223,8 @@ private: + bool parse_unsigned_tx_from_str(const std::string &unsigned_tx_st, unsigned_tx_set &exported_txs) const; + bool load_tx(const std::string &signed_filename, std::vector &ptx, std::function accept_func = NULL); + bool parse_tx_from_str(const std::string &signed_tx_st, std::vector &ptx, std::function accept_func); +- std::vector create_transactions_2(std::vector dsts, const size_t fake_outs_count, uint32_t priority, const std::vector& extra, uint32_t subaddr_account, std::set subaddr_indices, const unique_index_container& subtract_fee_from_outputs = {}); // pass subaddr_indices by value on purpose +- std::vector create_transactions_all(uint64_t below, const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, const size_t fake_outs_count, uint32_t priority, const std::vector& extra, uint32_t subaddr_account, std::set subaddr_indices); ++ std::vector create_transactions_2(std::vector dsts, const size_t fake_outs_count, uint32_t priority, const std::vector& extra, uint32_t subaddr_account, std::set subaddr_indices, const std::vector& preferred_input_list = {}, const unique_index_container& subtract_fee_from_outputs = {}); // pass subaddr_indices by value on purpose ++ std::vector create_transactions_all(uint64_t below, const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, const size_t fake_outs_count, uint32_t priority, const std::vector& extra, uint32_t subaddr_account, std::set subaddr_indices, const std::vector& preferred_input_list = {}); + std::vector create_transactions_single(const crypto::key_image &ki, const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, const size_t fake_outs_count, uint32_t priority, const std::vector& extra); + std::vector create_transactions_from(const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, std::vector unused_transfers_indices, std::vector unused_dust_indices, const size_t fake_outs_count, uint32_t priority, const std::vector& extra); + bool sanity_check(const std::vector &ptx_vector, const std::vector& dsts, const unique_index_container& subtract_fee_from_outputs = {}) const; +@@ -1576,6 +1576,7 @@ private: + uint64_t get_num_rct_outputs(); + size_t get_num_transfer_details() const { return m_transfers.size(); } + const transfer_details &get_transfer_details(size_t idx) const; ++ size_t get_transfer_details(const crypto::public_key &pk) const; + + uint8_t get_current_hard_fork(); + void get_hard_fork_info(uint8_t version, uint64_t &earliest_height); +@@ -1807,7 +1808,9 @@ private: + void freeze(size_t idx); + void thaw(size_t idx); + bool frozen(size_t idx) const; ++ void freeze(const crypto::public_key &pk); + void freeze(const crypto::key_image &ki); ++ void thaw(const crypto::public_key &pk); + void thaw(const crypto::key_image &ki); + bool frozen(const crypto::key_image &ki) const; + bool frozen(const transfer_details &td) const; +@@ -1848,6 +1851,8 @@ private: + + static std::string get_default_daemon_address() { CRITICAL_REGION_LOCAL(default_daemon_address_lock); return default_daemon_address; } + ++ boost::shared_mutex m_transfers_mutex; ++ + private: + /*! + * \brief Stores wallet information to wallet file. +@@ -1919,7 +1924,7 @@ private: + std::vector get_unspent_amounts_vector(bool strict); + uint64_t get_dynamic_base_fee_estimate(); + float get_output_relatedness(const transfer_details &td0, const transfer_details &td1) const; +- std::vector pick_preferred_rct_inputs(uint64_t needed_money, uint32_t subaddr_account, const std::set &subaddr_indices); ++ std::vector pick_preferred_rct_inputs(uint64_t needed_money, uint32_t subaddr_account, const std::set &subaddr_indices, const std::vector& preferred_input_list); + void set_spent(size_t idx, uint64_t height); + void set_unspent(size_t idx); + bool is_spent(const transfer_details &td, bool strict = true) const; +-- +2.51.0 + diff --git a/patches/wownero/0009-polyseed.patch b/patches/wownero/0009-polyseed.patch deleted file mode 100644 index 6f5d5d3..0000000 --- a/patches/wownero/0009-polyseed.patch +++ /dev/null @@ -1,1464 +0,0 @@ -From d146c6c1e23e1bf3762449e72a3aab847b3c0412 Mon Sep 17 00:00:00 2001 -From: tobtoht -Date: Tue, 12 Mar 2024 09:42:37 +0100 -Subject: [PATCH 09/15] polyseed - -Co-authored-by: Czarek Nakamoto ---- - .gitmodules | 6 + - CMakeLists.txt | 4 +- - contrib/depends/hosts/darwin.mk | 2 + - contrib/depends/hosts/linux.mk | 8 +- - contrib/depends/packages/packages.mk | 2 +- - contrib/depends/packages/polyseed.mk | 28 +++ - contrib/depends/packages/sodium.mk | 2 +- - .../polyseed/0001-disable-soname.patch | 48 +++++ - .../patches/polyseed/force-static-mingw.patch | 23 +++ - contrib/epee/include/wipeable_string.h | 7 + - contrib/epee/src/wipeable_string.cpp | 10 + - external/CMakeLists.txt | 2 + - external/polyseed | 1 + - external/utf8proc | 1 + - src/CMakeLists.txt | 1 + - src/cryptonote_basic/CMakeLists.txt | 1 + - src/cryptonote_basic/account.cpp | 23 ++- - src/cryptonote_basic/account.h | 6 + - src/cryptonote_config.h | 2 + - src/polyseed/CMakeLists.txt | 25 +++ - src/polyseed/pbkdf2.c | 85 ++++++++ - src/polyseed/pbkdf2.h | 46 +++++ - src/polyseed/polyseed.cpp | 182 ++++++++++++++++++ - src/polyseed/polyseed.hpp | 167 ++++++++++++++++ - src/wallet/api/wallet.cpp | 70 +++++++ - src/wallet/api/wallet.h | 10 + - src/wallet/api/wallet2_api.h | 25 +++ - src/wallet/api/wallet_manager.cpp | 9 + - src/wallet/api/wallet_manager.h | 10 + - src/wallet/wallet2.cpp | 99 ++++++++-- - src/wallet/wallet2.h | 30 ++- - 31 files changed, 912 insertions(+), 23 deletions(-) - create mode 100644 contrib/depends/packages/polyseed.mk - create mode 100644 contrib/depends/patches/polyseed/0001-disable-soname.patch - create mode 100644 contrib/depends/patches/polyseed/force-static-mingw.patch - create mode 160000 external/polyseed - create mode 160000 external/utf8proc - create mode 100644 src/polyseed/CMakeLists.txt - create mode 100644 src/polyseed/pbkdf2.c - create mode 100644 src/polyseed/pbkdf2.h - create mode 100644 src/polyseed/polyseed.cpp - create mode 100644 src/polyseed/polyseed.hpp - -diff --git a/.gitmodules b/.gitmodules -index b24855d9b..589676649 100644 ---- a/.gitmodules -+++ b/.gitmodules -@@ -20,3 +20,9 @@ - path = external/bc-ur - url = https://github.com/MrCyjaneK/bc-ur - branch = misc -+[submodule "external/utf8proc"] -+ path = external/utf8proc -+ url = https://github.com/JuliaStrings/utf8proc.git -+[submodule "external/polyseed"] -+ path = external/polyseed -+ url = https://github.com/tevador/polyseed.git -\ No newline at end of file -diff --git a/CMakeLists.txt b/CMakeLists.txt -index 86000f811..96056e324 100644 ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -372,6 +372,8 @@ if(NOT MANUAL_SUBMODULES) - #check_submodule(external/trezor-common) - check_submodule(external/randomwow) - check_submodule(external/supercop) -+ check_submodule(external/polyseed) -+ check_submodule(external/utf8proc) - endif() - endif() - -@@ -461,7 +463,7 @@ endif() - # elseif(CMAKE_SYSTEM_NAME MATCHES ".*BSDI.*") - # set(BSDI TRUE) - --include_directories(external/rapidjson/include external/easylogging++ src contrib/epee/include external external/supercop/include) -+include_directories(external/rapidjson/include external/easylogging++ src contrib/epee/include external external/supercop/include external/polyseed/include external/utf8proc) - - if(APPLE) - cmake_policy(SET CMP0042 NEW) -diff --git a/contrib/depends/hosts/darwin.mk b/contrib/depends/hosts/darwin.mk -index 83d83036b..b14ee5c5b 100644 ---- a/contrib/depends/hosts/darwin.mk -+++ b/contrib/depends/hosts/darwin.mk -@@ -8,6 +8,8 @@ endif - darwin_CC=clang -target $(CC_target) -mmacosx-version-min=$(OSX_MIN_VERSION) --sysroot $(host_prefix)/native/SDK/ -mlinker-version=$(LD64_VERSION) -B$(host_prefix)/native/bin/$(host)- - darwin_CXX=clang++ -target $(CC_target) -mmacosx-version-min=$(OSX_MIN_VERSION) --sysroot $(host_prefix)/native/SDK/ -mlinker-version=$(LD64_VERSION) -stdlib=libc++ -B$(host_prefix)/native/bin/$(host)- - -+darwin_RANLIB=$(host_prefix)/native/bin/$(host)-ranlib -+ - darwin_CFLAGS=-pipe - darwin_CXXFLAGS=$(darwin_CFLAGS) - darwin_ARFLAGS=cr -diff --git a/contrib/depends/hosts/linux.mk b/contrib/depends/hosts/linux.mk -index 912fdb03c..b79799f30 100644 ---- a/contrib/depends/hosts/linux.mk -+++ b/contrib/depends/hosts/linux.mk -@@ -11,15 +11,15 @@ linux_debug_CXXFLAGS=$(linux_debug_CFLAGS) - linux_debug_CPPFLAGS=-D_GLIBCXX_DEBUG -D_GLIBCXX_DEBUG_PEDANTIC - - ifeq (86,$(findstring 86,$(build_arch))) --i686_linux_CC=gcc -m32 --i686_linux_CXX=g++ -m32 -+i686_linux_CC=i686-linux-gnu-gcc -+i686_linux_CXX=i686-linux-gnu-g++ - i686_linux_AR=ar - i686_linux_RANLIB=ranlib - i686_linux_NM=nm - i686_linux_STRIP=strip - --x86_64_linux_CC=gcc -m64 --x86_64_linux_CXX=g++ -m64 -+x86_64_linux_CC=x86_64-linux-gnu-gcc -+x86_64_linux_CXX=x86_64-linux-gnu-g++ - x86_64_linux_AR=ar - x86_64_linux_RANLIB=ranlib - x86_64_linux_NM=nm -diff --git a/contrib/depends/packages/packages.mk b/contrib/depends/packages/packages.mk -index d2d1eca85..8783d4955 100644 ---- a/contrib/depends/packages/packages.mk -+++ b/contrib/depends/packages/packages.mk -@@ -1,4 +1,4 @@ --packages:=boost openssl zeromq libiconv expat unbound -+packages:=boost openssl zeromq libiconv expat unbound polyseed - - # ccache is useless in gitian builds - ifneq ($(GITIAN),1) -diff --git a/contrib/depends/packages/polyseed.mk b/contrib/depends/packages/polyseed.mk -new file mode 100644 -index 000000000..0071b20f3 ---- /dev/null -+++ b/contrib/depends/packages/polyseed.mk -@@ -0,0 +1,28 @@ -+package=polyseed -+$(package)_version=2.0.0 -+$(package)_download_path=https://github.com/tevador/$(package)/archive/refs/tags/ -+$(package)_download_file=v$($(package)_version).tar.gz -+$(package)_file_name=$(package)-$($(package)_version).tar.gz -+$(package)_sha256_hash=f36282fcbcd68d32461b8230c89e1a40661bd46b91109681cec637433004135a -+$(package)_patches=force-static-mingw.patch 0001-disable-soname.patch -+ -+define $(package)_preprocess_cmds -+ patch -p1 < $($(package)_patch_dir)/force-static-mingw.patch &&\ -+ patch -p1 < $($(package)_patch_dir)/0001-disable-soname.patch -+endef -+ -+define $(package)_config_cmds -+ CC="$($(package)_cc)" cmake -DCMAKE_INSTALL_PREFIX="$(host_prefix)" . -+endef -+ -+define $(package)_set_vars -+ $(package)_build_opts=CC="$($(package)_cc)" -+endef -+ -+define $(package)_build_cmds -+ CC="$($(package)_cc)" $(MAKE) -+endef -+ -+define $(package)_stage_cmds -+ $(MAKE) DESTDIR=$($(package)_staging_dir) install -+endef -diff --git a/contrib/depends/packages/sodium.mk b/contrib/depends/packages/sodium.mk -index 87b34599e..68a5b48ba 100644 ---- a/contrib/depends/packages/sodium.mk -+++ b/contrib/depends/packages/sodium.mk -@@ -6,7 +6,7 @@ $(package)_sha256_hash=6f504490b342a4f8a4c4a02fc9b866cbef8622d5df4e5452b46be121e - $(package)_patches=disable-glibc-getrandom-getentropy.patch fix-whitespace.patch - - define $(package)_set_vars --$(package)_config_opts=--enable-static --disable-shared -+$(package)_config_opts=--enable-static --disable-shared --with-pic - $(package)_config_opts+=--prefix=$(host_prefix) - endef - -diff --git a/contrib/depends/patches/polyseed/0001-disable-soname.patch b/contrib/depends/patches/polyseed/0001-disable-soname.patch -new file mode 100644 -index 000000000..bd97dd394 ---- /dev/null -+++ b/contrib/depends/patches/polyseed/0001-disable-soname.patch -@@ -0,0 +1,48 @@ -+From aabafcfc0572651436d024a635483c49042fad7f Mon Sep 17 00:00:00 2001 -+From: Czarek Nakamoto -+Date: Thu, 28 Mar 2024 00:32:51 +0100 -+Subject: [PATCH] disable soname -+ -+--- -+ CMakeLists.txt | 16 +++++++++------- -+ 1 file changed, 9 insertions(+), 7 deletions(-) -+ -+diff --git a/CMakeLists.txt b/CMakeLists.txt -+index 8a8e7c2..5301353 100644 -+--- a/CMakeLists.txt -++++ b/CMakeLists.txt -+@@ -36,6 +36,7 @@ include_directories(polyseed -+ target_compile_definitions(polyseed PRIVATE POLYSEED_SHARED) -+ set_target_properties(polyseed PROPERTIES VERSION 2.0.0 -+ SOVERSION 2 -++ NO_SONAME 1 -+ C_STANDARD 11 -+ C_STANDARD_REQUIRED ON) -+ -+@@ -45,16 +46,17 @@ include_directories(polyseed_static -+ include/) -+ target_compile_definitions(polyseed_static PRIVATE POLYSEED_STATIC) -+ set_target_properties(polyseed_static PROPERTIES OUTPUT_NAME polyseed -++ NO_SONAME 1 -+ C_STANDARD 11 -+ C_STANDARD_REQUIRED ON) -+ -+-add_executable(polyseed-tests -+- tests/tests.c) -+-include_directories(polyseed-tests -+- include/) -+-target_compile_definitions(polyseed-tests PRIVATE POLYSEED_STATIC) -+-target_link_libraries(polyseed-tests -+- PRIVATE polyseed_static) -++# add_executable(polyseed-tests -++# tests/tests.c) -++# include_directories(polyseed-tests -++# include/) -++# target_compile_definitions(polyseed-tests PRIVATE POLYSEED_STATIC) -++# target_link_libraries(polyseed-tests -++# PRIVATE polyseed_static) -+ -+ include(GNUInstallDirs) -+ install(TARGETS polyseed polyseed_static -+-- -+2.39.2 -diff --git a/contrib/depends/patches/polyseed/force-static-mingw.patch b/contrib/depends/patches/polyseed/force-static-mingw.patch -new file mode 100644 -index 000000000..f05cb2b6a ---- /dev/null -+++ b/contrib/depends/patches/polyseed/force-static-mingw.patch -@@ -0,0 +1,23 @@ -+--- a/include/polyseed.h -++++ b/include/polyseed.h -+@@ -93,13 +93,13 @@ Shared/static library definitions -+ - define POLYSEED_STATIC when linking to the static library -+ */ -+ #if defined(_WIN32) || defined(__CYGWIN__) -+- #ifdef POLYSEED_SHARED -+- #define POLYSEED_API __declspec(dllexport) -+- #elif !defined(POLYSEED_STATIC) -+- #define POLYSEED_API __declspec(dllimport) -+- #else -+- #define POLYSEED_API -+- #endif -++// #ifdef POLYSEED_SHARED -++// #define POLYSEED_API __declspec(dllexport) -++// #elif !defined(POLYSEED_STATIC) -++// #define POLYSEED_API __declspec(dllimport) -++// #else -++ #define POLYSEED_API -++// #endif -+ #define POLYSEED_PRIVATE -+ #else -+ #ifdef POLYSEED_SHARED -diff --git a/contrib/epee/include/wipeable_string.h b/contrib/epee/include/wipeable_string.h -index 65977cd97..594e15de4 100644 ---- a/contrib/epee/include/wipeable_string.h -+++ b/contrib/epee/include/wipeable_string.h -@@ -34,6 +34,7 @@ - #include - #include "memwipe.h" - #include "fnv1.h" -+#include "serialization/keyvalue_serialization.h" - - namespace epee - { -@@ -75,6 +76,12 @@ namespace epee - bool operator!=(const wipeable_string &other) const noexcept { return buffer != other.buffer; } - wipeable_string &operator=(wipeable_string &&other); - wipeable_string &operator=(const wipeable_string &other); -+ char& operator[](size_t idx); -+ const char& operator[](size_t idx) const; -+ -+ BEGIN_KV_SERIALIZE_MAP() -+ KV_SERIALIZE_CONTAINER_POD_AS_BLOB(buffer) -+ END_KV_SERIALIZE_MAP() - - private: - void grow(size_t sz, size_t reserved = 0); -diff --git a/contrib/epee/src/wipeable_string.cpp b/contrib/epee/src/wipeable_string.cpp -index b016f2f48..f2f365b1b 100644 ---- a/contrib/epee/src/wipeable_string.cpp -+++ b/contrib/epee/src/wipeable_string.cpp -@@ -261,4 +261,14 @@ wipeable_string &wipeable_string::operator=(const wipeable_string &other) - return *this; - } - -+char& wipeable_string::operator[](size_t idx) { -+ CHECK_AND_ASSERT_THROW_MES(idx < buffer.size(), "Index out of bounds"); -+ return buffer[idx]; -+} -+ -+const char& wipeable_string::operator[](size_t idx) const { -+ CHECK_AND_ASSERT_THROW_MES(idx < buffer.size(), "Index out of bounds"); -+ return buffer[idx]; -+} -+ - } -diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt -index dfdffe67c..fa98f61b5 100644 ---- a/external/CMakeLists.txt -+++ b/external/CMakeLists.txt -@@ -70,4 +70,6 @@ add_subdirectory(db_drivers) - add_subdirectory(easylogging++) - add_subdirectory(qrcodegen) - add_subdirectory(bc-ur) -+add_subdirectory(polyseed EXCLUDE_FROM_ALL) -+add_subdirectory(utf8proc EXCLUDE_FROM_ALL) - add_subdirectory(randomwow EXCLUDE_FROM_ALL) -diff --git a/external/polyseed b/external/polyseed -new file mode 160000 -index 000000000..bd79f5014 ---- /dev/null -+++ b/external/polyseed -@@ -0,0 +1 @@ -+Subproject commit bd79f5014c331273357277ed8a3d756fb61b9fa1 -diff --git a/external/utf8proc b/external/utf8proc -new file mode 160000 -index 000000000..3de4596fb ---- /dev/null -+++ b/external/utf8proc -@@ -0,0 +1 @@ -+Subproject commit 3de4596fbe28956855df2ecb3c11c0bbc3535838 -diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt -index 9216bcaa5..c043ba150 100644 ---- a/src/CMakeLists.txt -+++ b/src/CMakeLists.txt -@@ -95,6 +95,7 @@ add_subdirectory(net) - add_subdirectory(hardforks) - add_subdirectory(blockchain_db) - add_subdirectory(mnemonics) -+add_subdirectory(polyseed) - add_subdirectory(rpc) - if(NOT IOS) - add_subdirectory(serialization) -diff --git a/src/cryptonote_basic/CMakeLists.txt b/src/cryptonote_basic/CMakeLists.txt -index 1414be1b2..414936a05 100644 ---- a/src/cryptonote_basic/CMakeLists.txt -+++ b/src/cryptonote_basic/CMakeLists.txt -@@ -71,6 +71,7 @@ target_link_libraries(cryptonote_basic - checkpoints - cryptonote_format_utils_basic - device -+ polyseed_wrapper - ${Boost_DATE_TIME_LIBRARY} - ${Boost_PROGRAM_OPTIONS_LIBRARY} - ${Boost_SERIALIZATION_LIBRARY} -diff --git a/src/cryptonote_basic/account.cpp b/src/cryptonote_basic/account.cpp -index 4e87d4477..2d556f285 100644 ---- a/src/cryptonote_basic/account.cpp -+++ b/src/cryptonote_basic/account.cpp -@@ -87,12 +87,16 @@ DISABLE_VS_WARNINGS(4244 4345) - void account_keys::xor_with_key_stream(const crypto::chacha_key &key) - { - // encrypt a large enough byte stream with chacha20 -- epee::wipeable_string key_stream = get_key_stream(key, m_encryption_iv, sizeof(crypto::secret_key) * (2 + m_multisig_keys.size())); -+ epee::wipeable_string key_stream = get_key_stream(key, m_encryption_iv, sizeof(crypto::secret_key) * (3 + m_multisig_keys.size()) + m_passphrase.size()); - const char *ptr = key_stream.data(); - for (size_t i = 0; i < sizeof(crypto::secret_key); ++i) - m_spend_secret_key.data[i] ^= *ptr++; - for (size_t i = 0; i < sizeof(crypto::secret_key); ++i) - m_view_secret_key.data[i] ^= *ptr++; -+ for (size_t i = 0; i < sizeof(crypto::secret_key); ++i) -+ m_polyseed.data[i] ^= *ptr++; -+ for (size_t i = 0; i < m_passphrase.size(); ++i) -+ m_passphrase.data()[i] ^= *ptr++; - for (crypto::secret_key &k: m_multisig_keys) - { - for (size_t i = 0; i < sizeof(crypto::secret_key); ++i) -@@ -150,6 +154,8 @@ DISABLE_VS_WARNINGS(4244 4345) - { - m_keys.m_spend_secret_key = crypto::secret_key(); - m_keys.m_multisig_keys.clear(); -+ m_keys.m_polyseed = crypto::secret_key(); -+ m_keys.m_passphrase.wipe(); - } - //----------------------------------------------------------------- - void account_base::set_spend_key(const crypto::secret_key& spend_secret_key) -@@ -255,6 +261,21 @@ DISABLE_VS_WARNINGS(4244 4345) - create_from_keys(address, fake, viewkey); - } - //----------------------------------------------------------------- -+ void account_base::create_from_polyseed(const polyseed::data& seed, const epee::wipeable_string &passphrase) -+ { -+ crypto::secret_key secret_key; -+ seed.keygen(&secret_key, sizeof(secret_key)); -+ -+ if (!passphrase.empty()) { -+ secret_key = cryptonote::decrypt_key(secret_key, passphrase); -+ } -+ -+ generate(secret_key, true, false); -+ -+ seed.save(m_keys.m_polyseed.data); -+ m_keys.m_passphrase = passphrase; -+ } -+ //----------------------------------------------------------------- - bool account_base::make_multisig(const crypto::secret_key &view_secret_key, const crypto::secret_key &spend_secret_key, const crypto::public_key &spend_public_key, const std::vector &multisig_keys) - { - m_keys.m_account_address.m_spend_public_key = spend_public_key; -diff --git a/src/cryptonote_basic/account.h b/src/cryptonote_basic/account.h -index 93d1d28f0..1f76febce 100644 ---- a/src/cryptonote_basic/account.h -+++ b/src/cryptonote_basic/account.h -@@ -33,6 +33,7 @@ - #include "cryptonote_basic.h" - #include "crypto/crypto.h" - #include "serialization/keyvalue_serialization.h" -+#include "polyseed/polyseed.hpp" - - namespace cryptonote - { -@@ -45,6 +46,8 @@ namespace cryptonote - std::vector m_multisig_keys; - hw::device *m_device = &hw::get_device("default"); - crypto::chacha_iv m_encryption_iv; -+ crypto::secret_key m_polyseed; -+ epee::wipeable_string m_passphrase; // Only used with polyseed - - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(m_account_address) -@@ -53,6 +56,8 @@ namespace cryptonote - KV_SERIALIZE_CONTAINER_POD_AS_BLOB(m_multisig_keys) - const crypto::chacha_iv default_iv{{0, 0, 0, 0, 0, 0, 0, 0}}; - KV_SERIALIZE_VAL_POD_AS_BLOB_OPT(m_encryption_iv, default_iv) -+ KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE(m_polyseed) -+ KV_SERIALIZE(m_passphrase) - END_KV_SERIALIZE_MAP() - - void encrypt(const crypto::chacha_key &key); -@@ -79,6 +84,7 @@ namespace cryptonote - void create_from_device(hw::device &hwdev); - void create_from_keys(const cryptonote::account_public_address& address, const crypto::secret_key& spendkey, const crypto::secret_key& viewkey); - void create_from_viewkey(const cryptonote::account_public_address& address, const crypto::secret_key& viewkey); -+ void create_from_polyseed(const polyseed::data &polyseed, const epee::wipeable_string &passphrase); - bool make_multisig(const crypto::secret_key &view_secret_key, const crypto::secret_key &spend_secret_key, const crypto::public_key &spend_public_key, const std::vector &multisig_keys); - const account_keys& get_keys() const; - std::string get_public_address_str(network_type nettype) const; -diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h -index 8b5091a46..d9151e8d2 100644 ---- a/src/cryptonote_config.h -+++ b/src/cryptonote_config.h -@@ -219,6 +219,8 @@ - - #define DNS_BLOCKLIST_LIFETIME (86400 * 8) - -+#define POLYSEED_COIN POLYSEED_WOWNERO -+ - //The limit is enough for the mandatory transaction content with 16 outputs (547 bytes), - //a custom tag (1 byte) and up to 32 bytes of custom data for each recipient. - // (1+32) + (1+1+16*32) + (1+16*32) = 1060 -diff --git a/src/polyseed/CMakeLists.txt b/src/polyseed/CMakeLists.txt -new file mode 100644 -index 000000000..cca4eb746 ---- /dev/null -+++ b/src/polyseed/CMakeLists.txt -@@ -0,0 +1,25 @@ -+set(polyseed_sources -+ pbkdf2.c -+ polyseed.cpp -+) -+ -+monero_find_all_headers(polyseed_private_headers "${CMAKE_CURRENT_SOURCE_DIR}") -+ -+monero_private_headers(polyseed_wrapper -+ ${polyseed_private_headers} -+) -+ -+monero_add_library(polyseed_wrapper -+ ${polyseed_sources} -+ ${polyseed_headers} -+ ${polyseed_private_headers} -+) -+ -+target_link_libraries(polyseed_wrapper -+PUBLIC -+ polyseed -+ utf8proc -+ ${SODIUM_LIBRARY} -+ PRIVATE -+ ${EXTRA_LIBRARIES} -+) -diff --git a/src/polyseed/pbkdf2.c b/src/polyseed/pbkdf2.c -new file mode 100644 -index 000000000..1c45f4708 ---- /dev/null -+++ b/src/polyseed/pbkdf2.c -@@ -0,0 +1,85 @@ -+// Copyright (c) 2023, The Monero Project -+// Copyright (c) 2021, tevador -+// Copyright (c) 2005,2007,2009 Colin Percival -+// -+// 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. -+// -+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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 -+ -+static inline void -+store32_be(uint8_t dst[4], uint32_t w) -+{ -+ dst[3] = (uint8_t) w; w >>= 8; -+ dst[2] = (uint8_t) w; w >>= 8; -+ dst[1] = (uint8_t) w; w >>= 8; -+ dst[0] = (uint8_t) w; -+} -+ -+void -+crypto_pbkdf2_sha256(const uint8_t* passwd, size_t passwdlen, -+ const uint8_t* salt, size_t saltlen, uint64_t c, -+ uint8_t* buf, size_t dkLen) -+{ -+ crypto_auth_hmacsha256_state Phctx, PShctx, hctx; -+ size_t i; -+ uint8_t ivec[4]; -+ uint8_t U[32]; -+ uint8_t T[32]; -+ uint64_t j; -+ int k; -+ size_t clen; -+ -+ crypto_auth_hmacsha256_init(&Phctx, passwd, passwdlen); -+ PShctx = Phctx; -+ crypto_auth_hmacsha256_update(&PShctx, salt, saltlen); -+ -+ for (i = 0; i * 32 < dkLen; i++) { -+ store32_be(ivec, (uint32_t)(i + 1)); -+ hctx = PShctx; -+ crypto_auth_hmacsha256_update(&hctx, ivec, 4); -+ crypto_auth_hmacsha256_final(&hctx, U); -+ -+ memcpy(T, U, 32); -+ for (j = 2; j <= c; j++) { -+ hctx = Phctx; -+ crypto_auth_hmacsha256_update(&hctx, U, 32); -+ crypto_auth_hmacsha256_final(&hctx, U); -+ -+ for (k = 0; k < 32; k++) { -+ T[k] ^= U[k]; -+ } -+ } -+ -+ clen = dkLen - i * 32; -+ if (clen > 32) { -+ clen = 32; -+ } -+ memcpy(&buf[i * 32], T, clen); -+ } -+ sodium_memzero((void*)&Phctx, sizeof Phctx); -+ sodium_memzero((void*)&PShctx, sizeof PShctx); -+} -\ No newline at end of file -diff --git a/src/polyseed/pbkdf2.h b/src/polyseed/pbkdf2.h -new file mode 100644 -index 000000000..f6253b9d7 ---- /dev/null -+++ b/src/polyseed/pbkdf2.h -@@ -0,0 +1,46 @@ -+// Copyright (c) 2023, The Monero Project -+// Copyright (c) 2021, tevador -+// -+// 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. -+// -+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. -+ -+#ifndef PBKDF2_H -+#define PBKDF2_H -+ -+#include -+#include -+ -+#ifdef __cplusplus -+extern "C" { -+#endif -+ -+void -+crypto_pbkdf2_sha256(const uint8_t* passwd, size_t passwdlen, -+ const uint8_t* salt, size_t saltlen, uint64_t c, -+ uint8_t* buf, size_t dkLen); -+ -+#ifdef __cplusplus -+} -+#endif -+ -+#endif -\ No newline at end of file -diff --git a/src/polyseed/polyseed.cpp b/src/polyseed/polyseed.cpp -new file mode 100644 -index 000000000..231a48a94 ---- /dev/null -+++ b/src/polyseed/polyseed.cpp -@@ -0,0 +1,182 @@ -+// Copyright (c) 2023, The Monero Project -+// Copyright (c) 2021, tevador -+// -+// 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. -+// -+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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 "polyseed.hpp" -+#include "pbkdf2.h" -+ -+#include -+#include -+#include -+#include -+ -+#include -+#include -+#include -+ -+namespace polyseed { -+ -+ inline size_t utf8_norm(const char* str, polyseed_str norm, utf8proc_option_t options) { -+ utf8proc_int32_t buffer[POLYSEED_STR_SIZE]; -+ utf8proc_ssize_t result; -+ -+ result = utf8proc_decompose(reinterpret_cast(str), 0, buffer, POLYSEED_STR_SIZE, options); -+ if (result < 0 || result > (POLYSEED_STR_SIZE - 1)) { -+ throw std::runtime_error("Unicode normalization failed"); -+ } -+ -+ result = utf8proc_reencode(buffer, result, options); -+ if (result < 0 || result > POLYSEED_STR_SIZE) { -+ throw std::runtime_error("Unicode normalization failed"); -+ } -+ -+ strcpy(norm, reinterpret_cast(buffer)); -+ sodium_memzero(buffer, POLYSEED_STR_SIZE); -+ return result; -+ } -+ -+ static size_t utf8_nfc(const char* str, polyseed_str norm) { -+ // Note: UTF8PROC_LUMP is used here to replace the ideographic space with a regular space for Japanese phrases -+ // to allow wallets to split on ' '. -+ return utf8_norm(str, norm, (utf8proc_option_t)(UTF8PROC_NULLTERM | UTF8PROC_STABLE | UTF8PROC_COMPOSE | UTF8PROC_STRIPNA)); -+ } -+ -+ static size_t utf8_nfkd(const char* str, polyseed_str norm) { -+ return utf8_norm(str, norm, (utf8proc_option_t)(UTF8PROC_NULLTERM | UTF8PROC_STABLE | UTF8PROC_DECOMPOSE | UTF8PROC_COMPAT | UTF8PROC_STRIPNA)); -+ } -+ -+ struct dependency { -+ dependency(); -+ std::vector languages; -+ }; -+ -+ static dependency deps; -+ -+ dependency::dependency() { -+ if (sodium_init() == -1) { -+ throw std::runtime_error("sodium_init failed"); -+ } -+ -+ polyseed_dependency pd; -+ pd.randbytes = &randombytes_buf; -+ pd.pbkdf2_sha256 = &crypto_pbkdf2_sha256; -+ pd.memzero = &sodium_memzero; -+ pd.u8_nfc = &utf8_nfc; -+ pd.u8_nfkd = &utf8_nfkd; -+ pd.time = nullptr; -+ pd.alloc = nullptr; -+ pd.free = nullptr; -+ -+ polyseed_inject(&pd); -+ -+ for (int i = 0; i < polyseed_get_num_langs(); ++i) { -+ languages.push_back(language(polyseed_get_lang(i))); -+ } -+ } -+ -+ static language invalid_lang; -+ -+ const std::vector& get_langs() { -+ return deps.languages; -+ } -+ -+ const language& get_lang_by_name(const std::string& name) { -+ for (auto& lang : deps.languages) { -+ if (name == lang.name_en()) { -+ return lang; -+ } -+ if (name == lang.name()) { -+ return lang; -+ } -+ } -+ return invalid_lang; -+ } -+ -+ inline void data::check_init() const { -+ if (valid()) { -+ throw std::runtime_error("already initialized"); -+ } -+ } -+ -+ static std::array error_desc = { -+ "Success", -+ "Wrong number of words in the phrase", -+ "Unknown language or unsupported words", -+ "Checksum mismatch", -+ "Unsupported seed features", -+ "Invalid seed format", -+ "Memory allocation failure", -+ "Unicode normalization failed" -+ }; -+ -+ static error get_error(polyseed_status status) { -+ if (status > 0 && status < sizeof(error_desc) / sizeof(const char*)) { -+ return error(error_desc[(int)status], status); -+ } -+ return error("Unknown error", status); -+ } -+ -+ void data::create(feature_type features) { -+ check_init(); -+ auto status = polyseed_create(features, &m_data); -+ if (status != POLYSEED_OK) { -+ throw get_error(status); -+ } -+ } -+ -+ void data::split(const language& lang, polyseed_phrase& words) { -+ check_init(); -+ if (!lang.valid()) { -+ throw std::runtime_error("invalid language"); -+ } -+ } -+ -+ void data::load(polyseed_storage storage) { -+ check_init(); -+ auto status = polyseed_load(storage, &m_data); -+ if (status != POLYSEED_OK) { -+ throw get_error(status); -+ } -+ } -+ -+ void data::load(const crypto::secret_key &key) { -+ polyseed_storage d; -+ memcpy(&d, &key.data, 32); -+ auto status = polyseed_load(d, &m_data); -+ if (status != POLYSEED_OK) { -+ throw get_error(status); -+ } -+ } -+ -+ language data::decode(const char* phrase) { -+ check_init(); -+ const polyseed_lang* lang; -+ auto status = polyseed_decode(phrase, m_coin, &lang, &m_data); -+ if (status != POLYSEED_OK) { -+ throw get_error(status); -+ } -+ return language(lang); -+ } -+} -diff --git a/src/polyseed/polyseed.hpp b/src/polyseed/polyseed.hpp -new file mode 100644 -index 000000000..2c8c777a7 ---- /dev/null -+++ b/src/polyseed/polyseed.hpp -@@ -0,0 +1,167 @@ -+// Copyright (c) 2023, The Monero Project -+// Copyright (c) 2021, tevador -+// -+// 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. -+// -+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. -+ -+#ifndef POLYSEED_HPP -+#define POLYSEED_HPP -+ -+#include -+#include -+#include -+#include -+#include -+#include "crypto/crypto.h" -+ -+namespace polyseed { -+ -+ class data; -+ -+ class language { -+ public: -+ language() : m_lang(nullptr) {} -+ language(const language&) = default; -+ language(const polyseed_lang* lang) : m_lang(lang) {} -+ const char* name() const { -+ return polyseed_get_lang_name(m_lang); -+ } -+ const char* name_en() const { -+ return polyseed_get_lang_name_en(m_lang); -+ } -+ const char* separator() const { -+ return m_lang->separator; -+ } -+ bool valid() const { -+ return m_lang != nullptr; -+ } -+ -+ const polyseed_lang* m_lang; -+ private: -+ -+ friend class data; -+ }; -+ -+ const std::vector& get_langs(); -+ const language& get_lang_by_name(const std::string& name); -+ -+ class error : public std::runtime_error { -+ public: -+ error(const char* msg, polyseed_status status) -+ : std::runtime_error(msg), m_status(status) -+ { -+ } -+ polyseed_status status() const { -+ return m_status; -+ } -+ private: -+ polyseed_status m_status; -+ }; -+ -+ using feature_type = unsigned int; -+ -+ inline int enable_features(feature_type features) { -+ return polyseed_enable_features(features); -+ } -+ -+ class data { -+ public: -+ data(const data&) = delete; -+ data(polyseed_coin coin) : m_data(nullptr), m_coin(coin) {} -+ ~data() { -+ polyseed_free(m_data); -+ } -+ -+ void create(feature_type features); -+ -+ void load(polyseed_storage storage); -+ -+ void load(const crypto::secret_key &key); -+ -+ language decode(const char* phrase); -+ -+ template -+ void encode(const language& lang, str_type& str) const { -+ check_valid(); -+ if (!lang.valid()) { -+ throw std::runtime_error("invalid language"); -+ } -+ str.resize(POLYSEED_STR_SIZE); -+ auto size = polyseed_encode(m_data, lang.m_lang, m_coin, &str[0]); -+ str.resize(size); -+ } -+ -+ void split(const language& lang, polyseed_phrase& words); -+ -+ void save(polyseed_storage storage) const { -+ check_valid(); -+ polyseed_store(m_data, storage); -+ } -+ -+ void save(void *storage) const { -+ check_valid(); -+ polyseed_store(m_data, (uint8_t*)storage); -+ } -+ -+ void crypt(const char* password) { -+ check_valid(); -+ polyseed_crypt(m_data, password); -+ } -+ -+ void keygen(void* ptr, size_t key_size) const { -+ check_valid(); -+ polyseed_keygen(m_data, m_coin, key_size, (uint8_t*)ptr); -+ } -+ -+ bool valid() const { -+ return m_data != nullptr; -+ } -+ -+ bool encrypted() const { -+ check_valid(); -+ return polyseed_is_encrypted(m_data); -+ } -+ -+ uint64_t birthday() const { -+ check_valid(); -+ return polyseed_get_birthday(m_data); -+ } -+ -+ bool has_feature(feature_type feature) const { -+ check_valid(); -+ return polyseed_get_feature(m_data, feature) != 0; -+ } -+ private: -+ void check_valid() const { -+ if (m_data == nullptr) { -+ throw std::runtime_error("invalid object"); -+ } -+ } -+ void check_init() const; -+ -+ polyseed_data* m_data; -+ polyseed_coin m_coin; -+ }; -+} -+ -+#endif //POLYSEED_HPP -\ No newline at end of file -diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp -index 556e2a8ce..704e5e148 100644 ---- a/src/wallet/api/wallet.cpp -+++ b/src/wallet/api/wallet.cpp -@@ -728,6 +728,28 @@ bool WalletImpl::recoverFromDevice(const std::string &path, const std::string &p - return true; - } - -+bool WalletImpl::createFromPolyseed(const std::string &path, const std::string &password, const std::string &seed, -+ const std::string &passphrase, bool newWallet, uint64_t restoreHeight) -+{ -+ clearStatus(); -+ m_recoveringFromSeed = !newWallet; -+ m_recoveringFromDevice = false; -+ -+ polyseed::data polyseed(POLYSEED_COIN); -+ -+ try { -+ auto lang = polyseed.decode(seed.data()); -+ m_wallet->set_seed_language(lang.name()); -+ m_wallet->generate(path, password, polyseed, passphrase, !newWallet); -+ } -+ catch (const std::exception &e) { -+ setStatusError(e.what()); -+ return false; -+ } -+ -+ return true; -+} -+ - Wallet::Device WalletImpl::getDeviceType() const - { - return static_cast(m_wallet->get_device_type()); -@@ -845,6 +867,54 @@ std::string WalletImpl::seed(const std::string& seed_offset) const - } - } - -+bool WalletImpl::getPolyseed(std::string &seed_words, std::string &passphrase) const -+{ -+ epee::wipeable_string seed_words_epee(seed_words.c_str(), seed_words.size()); -+ epee::wipeable_string passphrase_epee(passphrase.c_str(), passphrase.size()); -+ clearStatus(); -+ -+ if (!m_wallet) { -+ return false; -+ } -+ -+ bool result = m_wallet->get_polyseed(seed_words_epee, passphrase_epee); -+ -+ seed_words.assign(seed_words_epee.data(), seed_words_epee.size()); -+ passphrase.assign(passphrase_epee.data(), passphrase_epee.size()); -+ -+ return result; -+} -+ -+std::vector> Wallet::getPolyseedLanguages() -+ { -+ std::vector> languages; -+ -+ auto langs = polyseed::get_langs(); -+ for (const auto &lang : langs) { -+ languages.emplace_back(std::pair(lang.name_en(), lang.name())); -+ } -+ -+ return languages; -+} -+ -+bool Wallet::createPolyseed(std::string &seed_words, std::string &err, const std::string &language) -+{ -+ epee::wipeable_string seed_words_epee(seed_words.c_str(), seed_words.size()); -+ -+ try { -+ polyseed::data polyseed(POLYSEED_COIN); -+ polyseed.create(0); -+ polyseed.encode(polyseed::get_lang_by_name(language), seed_words_epee); -+ -+ seed_words.assign(seed_words_epee.data(), seed_words_epee.size()); -+ } -+ catch (const std::exception &e) { -+ err = e.what(); -+ return false; -+ } -+ -+ return true; -+} - std::string WalletImpl::getSeedLanguage() const - { - return m_wallet->get_seed_language(); -diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h -index 4e9c21ecb..32e12284b 100644 ---- a/src/wallet/api/wallet.h -+++ b/src/wallet/api/wallet.h -@@ -79,9 +79,19 @@ public: - bool recoverFromDevice(const std::string &path, - const std::string &password, - const std::string &device_name); -+ -+ bool createFromPolyseed(const std::string &path, -+ const std::string &password, -+ const std::string &seed, -+ const std::string &passphrase = "", -+ bool newWallet = true, -+ uint64_t restoreHeight = 0); -+ - Device getDeviceType() const override; - bool close(bool store = true); - std::string seed(const std::string& seed_offset = "") const override; -+ bool getPolyseed(std::string &seed_words, std::string &passphrase) const override; -+ - std::string getSeedLanguage() const override; - void setSeedLanguage(const std::string &arg) override; - // void setListener(Listener *) {} -diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h -index 53ec4abfc..be1c3704e 100644 ---- a/src/wallet/api/wallet2_api.h -+++ b/src/wallet/api/wallet2_api.h -@@ -709,6 +709,10 @@ struct Wallet - static void warning(const std::string &category, const std::string &str); - static void error(const std::string &category, const std::string &str); - -+ virtual bool getPolyseed(std::string &seed, std::string &passphrase) const = 0; -+ static bool createPolyseed(std::string &seed_words, std::string &err, const std::string &language = "English"); -+ static std::vector> getPolyseedLanguages(); -+ - /** - * @brief StartRefresh - Start/resume refresh thread (refresh every 10 seconds) - */ -@@ -1320,6 +1324,27 @@ struct WalletManager - uint64_t kdf_rounds = 1, - WalletListener * listener = nullptr) = 0; - -+ /*! -+ * \brief creates a wallet from a polyseed mnemonic phrase -+ * \param path Name of the wallet file to be created -+ * \param password Password of wallet file -+ * \param nettype Network type -+ * \param mnemonic Polyseed mnemonic -+ * \param passphrase Optional seed offset passphrase -+ * \param newWallet Whether it is a new wallet -+ * \param restoreHeight Override the embedded restore height -+ * \param kdf_rounds Number of rounds for key derivation function -+ * @return -+ */ -+ virtual Wallet * createWalletFromPolyseed(const std::string &path, -+ const std::string &password, -+ NetworkType nettype, -+ const std::string &mnemonic, -+ const std::string &passphrase = "", -+ bool newWallet = true, -+ uint64_t restore_height = 0, -+ uint64_t kdf_rounds = 1) = 0; -+ - /*! - * \brief Closes wallet. In case operation succeeded, wallet object deleted. in case operation failed, wallet object not deleted - * \param wallet previously opened / created wallet instance -diff --git a/src/wallet/api/wallet_manager.cpp b/src/wallet/api/wallet_manager.cpp -index 277be6ac9..da2056d8a 100644 ---- a/src/wallet/api/wallet_manager.cpp -+++ b/src/wallet/api/wallet_manager.cpp -@@ -156,6 +156,15 @@ Wallet *WalletManagerImpl::createWalletFromDevice(const std::string &path, - return wallet; - } - -+Wallet *WalletManagerImpl::createWalletFromPolyseed(const std::string &path, const std::string &password, NetworkType nettype, -+ const std::string &mnemonic, const std::string &passphrase, -+ bool newWallet, uint64_t restoreHeight, uint64_t kdf_rounds) -+{ -+ WalletImpl * wallet = new WalletImpl(nettype, kdf_rounds); -+ wallet->createFromPolyseed(path, password, mnemonic, passphrase, newWallet, restoreHeight); -+ return wallet; -+} -+ - bool WalletManagerImpl::closeWallet(Wallet *wallet, bool store) - { - WalletImpl * wallet_ = dynamic_cast(wallet); -diff --git a/src/wallet/api/wallet_manager.h b/src/wallet/api/wallet_manager.h -index a223e1df9..28fcd36c9 100644 ---- a/src/wallet/api/wallet_manager.h -+++ b/src/wallet/api/wallet_manager.h -@@ -75,6 +75,16 @@ public: - const std::string &subaddressLookahead = "", - uint64_t kdf_rounds = 1, - WalletListener * listener = nullptr) override; -+ -+ virtual Wallet * createWalletFromPolyseed(const std::string &path, -+ const std::string &password, -+ NetworkType nettype, -+ const std::string &mnemonic, -+ const std::string &passphrase, -+ bool newWallet = true, -+ uint64_t restore_height = 0, -+ uint64_t kdf_rounds = 1) override; -+ - virtual bool closeWallet(Wallet *wallet, bool store = true) override; - bool walletExists(const std::string &path) override; - bool verifyWalletPassword(const std::string &keys_file_name, const std::string &password, bool no_spend_key, uint64_t kdf_rounds = 1) const override; -diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp -index e14d4d2fc..fd4094360 100644 ---- a/src/wallet/wallet2.cpp -+++ b/src/wallet/wallet2.cpp -@@ -92,6 +92,7 @@ using namespace epee; - #include "device/device_cold.hpp" - #include "device_trezor/device_trezor.hpp" - #include "net/socks_connect.h" -+#include "polyseed/include/polyseed.h" - - extern "C" - { -@@ -1278,7 +1279,8 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended, std - m_enable_multisig(false), - m_pool_info_query_time(0), - m_has_ever_refreshed_from_node(false), -- m_allow_mismatched_daemon_version(true) -+ m_allow_mismatched_daemon_version(true), -+ m_polyseed(false) - { - set_rpc_client_secret_key(rct::rct2sk(rct::skGen())); - } -@@ -1483,6 +1485,20 @@ bool wallet2::get_seed(epee::wipeable_string& electrum_words, const epee::wipeab - return true; - } - //---------------------------------------------------------------------------------------------------- -+ -+bool wallet2::get_polyseed(epee::wipeable_string& polyseed, epee::wipeable_string& passphrase) const -+{ -+ if (!m_polyseed) { -+ return false; -+ } -+ -+ polyseed::data data(POLYSEED_COIN); -+ data.load(get_account().get_keys().m_polyseed); -+ data.encode(polyseed::get_lang_by_name(seed_language), polyseed); -+ passphrase = get_account().get_keys().m_passphrase; -+ return true; -+} -+//---------------------------------------------------------------------------------------------------- - bool wallet2::get_multisig_seed(epee::wipeable_string& seed, const epee::wipeable_string &passphrase) const - { - bool ready; -@@ -4801,6 +4817,9 @@ boost::optional wallet2::get_keys_file_data(const crypt - value2.SetInt(m_enable_multisig ? 1 : 0); - json.AddMember("enable_multisig", value2, json.GetAllocator()); - -+ value2.SetInt(m_polyseed ? 1 : 0); -+ json.AddMember("polyseed", value2, json.GetAllocator()); -+ - if (m_background_sync_type == BackgroundSyncCustomPassword && !background_keys_file && m_custom_background_key) - { - value.SetString(reinterpret_cast(m_custom_background_key.get().data()), m_custom_background_key.get().size()); -@@ -5040,6 +5059,7 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st - m_enable_multisig = false; - m_allow_mismatched_daemon_version = true; - m_custom_background_key = boost::none; -+ m_polyseed = false; - } - else if(json.IsObject()) - { -@@ -5280,6 +5300,9 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, background_sync_type, BackgroundSyncType, Int, false, BackgroundSyncOff); - m_background_sync_type = field_background_sync_type; - -+ GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, polyseed, int, Int, false, false); -+ m_polyseed = field_polyseed; -+ - // Load encryption key used to encrypt background cache - crypto::chacha_key custom_background_key; - m_custom_background_key = boost::none; -@@ -5599,6 +5622,48 @@ void wallet2::init_type(hw::device::device_type device_type) - m_key_device_type = device_type; - } - -+/*! -+ * \brief Generates a polyseed wallet or restores one. -+ * \param wallet_ Name of wallet file -+ * \param password Password of wallet file -+ * \param passphrase Seed offset passphrase -+ * \param recover Whether it is a restore -+ * \param seed_words If it is a restore, the polyseed -+ * \param create_address_file Whether to create an address file -+ * \return The secret key of the generated wallet -+ */ -+void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& password, -+ const polyseed::data &seed, const epee::wipeable_string& passphrase, bool recover, uint64_t restoreHeight, bool create_address_file) -+{ -+ clear(); -+ prepare_file_names(wallet_); -+ -+ if (!wallet_.empty()) { -+ boost::system::error_code ignored_ec; -+ THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_wallet_file, ignored_ec), error::file_exists, m_wallet_file); -+ THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_keys_file, ignored_ec), error::file_exists, m_keys_file); -+ } -+ -+ m_account.create_from_polyseed(seed, passphrase); -+ -+ init_type(hw::device::device_type::SOFTWARE); -+ m_polyseed = true; -+ setup_keys(password); -+ -+ if (recover) { -+ m_refresh_from_block_height = estimate_blockchain_height(restoreHeight > 0 ? restoreHeight : seed.birthday()); -+ } else { -+ m_refresh_from_block_height = estimate_blockchain_height(); -+ } -+ -+ create_keys_file(wallet_, false, password, m_nettype != MAINNET || create_address_file); -+ -+ setup_new_blockchain(); -+ -+ if (!wallet_.empty()) -+ store(); -+} -+ - /*! - * \brief Generates a wallet or restores one. Assumes the multisig setup - * has already completed for the provided multisig info. -@@ -5726,7 +5791,7 @@ crypto::secret_key wallet2::generate(const std::string& wallet_, const epee::wip - return retval; - } - -- uint64_t wallet2::estimate_blockchain_height() -+ uint64_t wallet2::estimate_blockchain_height(uint64_t time) - { - // -1 month for fluctuations in block time and machine date/time setup. - // avg seconds per block -@@ -5750,7 +5815,7 @@ crypto::secret_key wallet2::generate(const std::string& wallet_, const epee::wip - // the daemon is currently syncing. - // If we use the approximate height we subtract one month as - // a safety margin. -- height = get_approximate_blockchain_height(); -+ height = get_approximate_blockchain_height(time); - uint64_t target_height = get_daemon_blockchain_target_height(err); - if (err.empty()) { - if (target_height < height) -@@ -13643,9 +13708,10 @@ uint64_t wallet2::get_daemon_blockchain_target_height(string &err) - return target_height; - } - --uint64_t wallet2::get_approximate_blockchain_height() const -+uint64_t wallet2::get_approximate_blockchain_height(uint64_t t) const - { - uint64_t approx_blockchain_height = m_nettype == TESTNET ? 0 : (time(NULL) - 1522624244)/307; -+ // uint64_t approx_blockchain_height = fork_block + ((t > 0 ? t : time(NULL)) - fork_time)/seconds_per_block; - LOG_PRINT_L2("Calculated blockchain height: " << approx_blockchain_height); - return approx_blockchain_height; - } -@@ -15780,15 +15846,6 @@ bool wallet2::parse_uri(const std::string &uri, std::string &address, std::strin - //---------------------------------------------------------------------------------------------------- - uint64_t wallet2::get_blockchain_height_by_date(uint16_t year, uint8_t month, uint8_t day) - { -- uint32_t version; -- if (!check_connection(&version)) -- { -- throw std::runtime_error("failed to connect to daemon: " + get_daemon_address()); -- } -- if (version < MAKE_CORE_RPC_VERSION(1, 6)) -- { -- throw std::runtime_error("this function requires RPC version 1.6 or higher"); -- } - std::tm date = { 0, 0, 0, 0, 0, 0, 0, 0 }; - date.tm_year = year - 1900; - date.tm_mon = month - 1; -@@ -15797,7 +15854,23 @@ uint64_t wallet2::get_blockchain_height_by_date(uint16_t year, uint8_t month, ui - { - throw std::runtime_error("month or day out of range"); - } -+ - uint64_t timestamp_target = std::mktime(&date); -+ -+ return get_blockchain_height_by_timestamp(timestamp_target); -+} -+ -+uint64_t wallet2::get_blockchain_height_by_timestamp(uint64_t timestamp_target) { -+ uint32_t version; -+ if (!check_connection(&version)) -+ { -+ throw std::runtime_error("failed to connect to daemon: " + get_daemon_address()); -+ } -+ if (version < MAKE_CORE_RPC_VERSION(1, 6)) -+ { -+ throw std::runtime_error("this function requires RPC version 1.6 or higher"); -+ } -+ - std::string err; - uint64_t height_min = 0; - uint64_t height_max = get_daemon_blockchain_height(err) - 1; -diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h -index 746e2aeff..c165acb9d 100644 ---- a/src/wallet/wallet2.h -+++ b/src/wallet/wallet2.h -@@ -72,6 +72,7 @@ - #include "message_store.h" - #include "wallet_light_rpc.h" - #include "wallet_rpc_helpers.h" -+#include "polyseed/polyseed.hpp" - - #undef MONERO_DEFAULT_LOG_CATEGORY - #define MONERO_DEFAULT_LOG_CATEGORY "wallet.wallet2" -@@ -921,6 +922,20 @@ private: - void generate(const std::string& wallet_, const epee::wipeable_string& password, - const epee::wipeable_string& multisig_data, bool create_address_file = false); - -+ /*! -+ * \brief Generates a wallet from a polyseed. -+ * @param wallet_ Name of wallet file -+ * @param password Password of wallet file -+ * @param seed Polyseed data -+ * @param passphrase Optional seed offset passphrase -+ * @param recover Whether it is a restore -+ * @param restoreHeight Override the embedded restore height -+ * @param create_address_file Whether to create an address file -+ */ -+ void generate(const std::string& wallet_, const epee::wipeable_string& password, -+ const polyseed::data &seed, const epee::wipeable_string& passphrase = "", -+ bool recover = false, uint64_t restoreHeight = 0, bool create_address_file = false); -+ - /*! - * \brief Generates a wallet or restores one. - * \param wallet_ Name of wallet file -@@ -1095,6 +1110,15 @@ private: - bool is_deterministic() const; - bool get_seed(epee::wipeable_string& electrum_words, const epee::wipeable_string &passphrase = epee::wipeable_string()) const; - -+ /*! -+ * \brief get_polyseed Gets the polyseed (if available) and passphrase (if set) needed to recover the wallet. -+ * @param seed Polyseed mnemonic phrase -+ * @param passphrase Seed offset passphrase that was used to restore the wallet -+ * @return Returns true if the wallet has a polyseed. -+ * Note: both the mnemonic phrase and the passphrase are needed to recover the wallet -+ */ -+ bool get_polyseed(epee::wipeable_string& seed, epee::wipeable_string &passphrase) const; -+ - /*! - * \brief Checks if light wallet. A light wallet sends view key to a server where the blockchain is scanned. - */ -@@ -1562,8 +1586,8 @@ private: - /*! - * \brief Calculates the approximate blockchain height from current date/time. - */ -- uint64_t get_approximate_blockchain_height() const; -- uint64_t estimate_blockchain_height(); -+ uint64_t get_approximate_blockchain_height(uint64_t time = 0) const; -+ uint64_t estimate_blockchain_height(uint64_t time = 0); - std::vector select_available_outputs_from_histogram(uint64_t count, bool atleast, bool unlocked, bool allow_rct); - std::vector select_available_outputs(const std::function &f); - std::vector select_available_unmixable_outputs(); -@@ -1657,6 +1681,7 @@ private: - bool parse_uri(const std::string &uri, std::string &address, std::string &payment_id, uint64_t &amount, std::string &tx_description, std::string &recipient_name, std::vector &unknown_parameters, std::string &error); - - uint64_t get_blockchain_height_by_date(uint16_t year, uint8_t month, uint8_t day); // 1<=month<=12, 1<=day<=31 -+ uint64_t get_blockchain_height_by_timestamp(uint64_t timestamp); - - bool is_synced(); - -@@ -2003,6 +2028,7 @@ private: - 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_polyseed; - bool m_multisig; /*!< if > 1 spend secret key will not match spend public key */ - uint32_t m_multisig_threshold; - std::vector m_multisig_signers; --- -2.48.0 - diff --git a/patches/wownero/0010-Add-hex-encoding-and-tx-key-getter-for-PendingTransc.patch b/patches/wownero/0010-Add-hex-encoding-and-tx-key-getter-for-PendingTransc.patch new file mode 100644 index 0000000..62fc6ef --- /dev/null +++ b/patches/wownero/0010-Add-hex-encoding-and-tx-key-getter-for-PendingTransc.patch @@ -0,0 +1,68 @@ +From 32d3c34073563f34ce90a8afdbe8e554e98d38bc Mon Sep 17 00:00:00 2001 +From: M +Date: Fri, 21 Apr 2023 15:43:47 -0400 +Subject: [PATCH 10/16] Add hex encoding and tx key getter for + PendingTransction in wallet api. + +--- + src/wallet/api/pending_transaction.cpp | 16 ++++++++++++++++ + src/wallet/api/pending_transaction.h | 2 ++ + src/wallet/api/wallet2_api.h | 2 ++ + 3 files changed, 20 insertions(+) + +diff --git a/src/wallet/api/pending_transaction.cpp b/src/wallet/api/pending_transaction.cpp +index 9c3c26ee5..1f714d229 100644 +--- a/src/wallet/api/pending_transaction.cpp ++++ b/src/wallet/api/pending_transaction.cpp +@@ -80,6 +80,22 @@ std::vector PendingTransactionImpl::txid() const + return txid; + } + ++std::vector PendingTransactionImpl::hex() const ++{ ++ std::vector hexs; ++ for (const auto &pt: m_pending_tx) ++ hexs.push_back(epee::string_tools::buff_to_hex_nodelimer(cryptonote::tx_to_blob(pt.tx))); ++ return hexs; ++} ++ ++std::vector PendingTransactionImpl::txKey() const ++{ ++ std::vector keys; ++ for (const auto& pt: m_pending_tx) ++ keys.push_back(epee::string_tools::pod_to_hex(pt.tx_key)); ++ return keys; ++} ++ + bool PendingTransactionImpl::commit(const std::string &filename, bool overwrite) + { + +diff --git a/src/wallet/api/pending_transaction.h b/src/wallet/api/pending_transaction.h +index 403bfe281..0cc6c58e9 100644 +--- a/src/wallet/api/pending_transaction.h ++++ b/src/wallet/api/pending_transaction.h +@@ -59,6 +59,8 @@ public: + std::string multisigSignData() override; + void signMultisigTx() override; + std::vector signersKeys() const override; ++ std::vector hex() const override; ++ std::vector txKey() const override; + + private: + friend class WalletImpl; +diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h +index 013b5bcba..f421fdc05 100644 +--- a/src/wallet/api/wallet2_api.h ++++ b/src/wallet/api/wallet2_api.h +@@ -127,6 +127,8 @@ struct PendingTransaction + * @return vector of base58-encoded signers' public keys + */ + virtual std::vector signersKeys() const = 0; ++ virtual std::vector hex() const = 0; ++ virtual std::vector txKey() const = 0; + }; + + /** +-- +2.51.0 + diff --git a/patches/wownero/0010-coin-control.patch b/patches/wownero/0010-coin-control.patch deleted file mode 100644 index 94f8485..0000000 --- a/patches/wownero/0010-coin-control.patch +++ /dev/null @@ -1,979 +0,0 @@ -From edc33fa98da3bc9e8e746a59f5e62b9001afb230 Mon Sep 17 00:00:00 2001 -From: tobtoht -Date: Tue, 12 Mar 2024 11:07:57 +0100 -Subject: [PATCH 10/15] coin control - ---- - src/simplewallet/simplewallet.cpp | 2 +- - src/wallet/api/CMakeLists.txt | 8 +- - src/wallet/api/coins.cpp | 186 ++++++++++++++++++++++++++++++ - src/wallet/api/coins.h | 40 +++++++ - src/wallet/api/coins_info.cpp | 122 ++++++++++++++++++++ - src/wallet/api/coins_info.h | 71 ++++++++++++ - src/wallet/api/wallet.cpp | 64 +++++++++- - src/wallet/api/wallet.h | 10 +- - src/wallet/api/wallet2_api.h | 52 ++++++++- - src/wallet/wallet2.cpp | 46 +++++++- - src/wallet/wallet2.h | 11 +- - 11 files changed, 593 insertions(+), 19 deletions(-) - create mode 100644 src/wallet/api/coins.cpp - create mode 100644 src/wallet/api/coins.h - create mode 100644 src/wallet/api/coins_info.cpp - create mode 100644 src/wallet/api/coins_info.h - -diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp -index 83b56c3f4..12c38b8e1 100644 ---- a/src/simplewallet/simplewallet.cpp -+++ b/src/simplewallet/simplewallet.cpp -@@ -6981,7 +6981,7 @@ bool simple_wallet::transfer_main(const std::vector &args_, bool ca - { - // figure out what tx will be necessary - auto ptx_vector = m_wallet->create_transactions_2(dsts, fake_outs_count, priority, extra, -- m_current_subaddress_account, subaddr_indices, subtract_fee_from_outputs); -+ m_current_subaddress_account, subaddr_indices, {}, subtract_fee_from_outputs); - - if (ptx_vector.empty()) - { -diff --git a/src/wallet/api/CMakeLists.txt b/src/wallet/api/CMakeLists.txt -index af7948d8a..bb740e2ac 100644 ---- a/src/wallet/api/CMakeLists.txt -+++ b/src/wallet/api/CMakeLists.txt -@@ -40,7 +40,9 @@ set(wallet_api_sources - address_book.cpp - subaddress.cpp - subaddress_account.cpp -- unsigned_transaction.cpp) -+ unsigned_transaction.cpp -+ coins.cpp -+ coins_info.cpp) - - set(wallet_api_headers - wallet2_api.h) -@@ -55,7 +57,9 @@ set(wallet_api_private_headers - address_book.h - subaddress.h - subaddress_account.h -- unsigned_transaction.h) -+ unsigned_transaction.h -+ coins.h -+ coins_info.h) - - monero_private_headers(wallet_api - ${wallet_api_private_headers}) -diff --git a/src/wallet/api/coins.cpp b/src/wallet/api/coins.cpp -new file mode 100644 -index 000000000..ef12141cf ---- /dev/null -+++ b/src/wallet/api/coins.cpp -@@ -0,0 +1,186 @@ -+#include "coins.h" -+#include "coins_info.h" -+#include "wallet.h" -+#include "crypto/hash.h" -+#include "wallet/wallet2.h" -+#include "common_defines.h" -+ -+#include -+#include -+ -+using namespace epee; -+ -+namespace Monero { -+ -+Coins::~Coins() = default; -+ -+CoinsImpl::CoinsImpl(WalletImpl *wallet) -+ : m_wallet(wallet) {} -+ -+CoinsImpl::~CoinsImpl() -+{ -+ for (auto t : m_rows) -+ delete t; -+} -+ -+int CoinsImpl::count() const -+{ -+ boost::shared_lock lock(m_rowsMutex); -+ int result = m_rows.size(); -+ return result; -+} -+ -+CoinsInfo *CoinsImpl::coin(int index) const -+{ -+ boost::shared_lock lock(m_rowsMutex); -+ // sanity check -+ if (index < 0) -+ return nullptr; -+ auto index_ = static_cast(index); -+ return index_ < m_rows.size() ? m_rows[index_] : nullptr; -+} -+ -+std::vector CoinsImpl::getAll() const -+{ -+ boost::shared_lock lock(m_rowsMutex); -+ return m_rows; -+} -+ -+ -+void CoinsImpl::refresh() -+{ -+ LOG_PRINT_L2("Refreshing coins"); -+ -+ boost::unique_lock lock(m_rowsMutex); -+ boost::shared_lock transfers_lock(m_wallet->m_wallet->m_transfers_mutex); -+ -+ // delete old outputs; -+ for (auto t : m_rows) -+ delete t; -+ m_rows.clear(); -+ -+ for (size_t i = 0; i < m_wallet->m_wallet->get_num_transfer_details(); ++i) -+ { -+ const tools::wallet2::transfer_details &td = m_wallet->m_wallet->get_transfer_details(i); -+ -+ auto ci = new CoinsInfoImpl(); -+ ci->m_blockHeight = td.m_block_height; -+ ci->m_hash = string_tools::pod_to_hex(td.m_txid); -+ ci->m_internalOutputIndex = td.m_internal_output_index; -+ ci->m_globalOutputIndex = td.m_global_output_index; -+ ci->m_spent = td.m_spent; -+ ci->m_frozen = td.m_frozen; -+ ci->m_spentHeight = td.m_spent_height; -+ ci->m_amount = td.m_amount; -+ ci->m_rct = td.m_rct; -+ ci->m_keyImageKnown = td.m_key_image_known; -+ ci->m_pkIndex = td.m_pk_index; -+ ci->m_subaddrIndex = td.m_subaddr_index.minor; -+ ci->m_subaddrAccount = td.m_subaddr_index.major; -+ ci->m_address = m_wallet->m_wallet->get_subaddress_as_str(td.m_subaddr_index); // todo: this is expensive, cache maybe? -+ ci->m_addressLabel = m_wallet->m_wallet->get_subaddress_label(td.m_subaddr_index); -+ ci->m_keyImage = string_tools::pod_to_hex(td.m_key_image); -+ ci->m_unlockTime = td.m_tx.unlock_time; -+ ci->m_unlocked = m_wallet->m_wallet->is_transfer_unlocked(td); -+ ci->m_pubKey = string_tools::pod_to_hex(td.get_public_key()); -+ ci->m_coinbase = td.m_tx.vin.size() == 1 && td.m_tx.vin[0].type() == typeid(cryptonote::txin_gen); -+ ci->m_description = m_wallet->m_wallet->get_tx_note(td.m_txid); -+ -+ m_rows.push_back(ci); -+ } -+} -+ -+void CoinsImpl::setFrozen(std::string public_key) -+{ -+ crypto::public_key pk; -+ if (!epee::string_tools::hex_to_pod(public_key, pk)) -+ { -+ LOG_ERROR("Invalid public key: " << public_key); -+ return; -+ } -+ -+ try -+ { -+ m_wallet->m_wallet->freeze(pk); -+ refresh(); -+ } -+ catch (const std::exception& e) -+ { -+ LOG_ERROR("setFrozen: " << e.what()); -+ } -+} -+ -+void CoinsImpl::setFrozen(int index) -+{ -+ try -+ { -+ LOG_ERROR("Freezing coin: " << index); -+ m_wallet->m_wallet->freeze(index); -+ refresh(); -+ } -+ catch (const std::exception& e) -+ { -+ LOG_ERROR("setLabel: " << e.what()); -+ } -+} -+ -+void CoinsImpl::thaw(std::string public_key) -+{ -+ crypto::public_key pk; -+ if (!epee::string_tools::hex_to_pod(public_key, pk)) -+ { -+ LOG_ERROR("Invalid public key: " << public_key); -+ return; -+ } -+ -+ try -+ { -+ m_wallet->m_wallet->thaw(pk); -+ refresh(); -+ } -+ catch (const std::exception& e) -+ { -+ LOG_ERROR("thaw: " << e.what()); -+ } -+} -+ -+void CoinsImpl::thaw(int index) -+{ -+ try -+ { -+ m_wallet->m_wallet->thaw(index); -+ refresh(); -+ } -+ catch (const std::exception& e) -+ { -+ LOG_ERROR("thaw: " << e.what()); -+ } -+} -+ -+bool CoinsImpl::isTransferUnlocked(uint64_t unlockTime, uint64_t blockHeight) { -+ return m_wallet->m_wallet->is_transfer_unlocked(unlockTime, blockHeight); -+} -+ -+void CoinsImpl::setDescription(const std::string &public_key, const std::string &description) -+{ -+ crypto::public_key pk; -+ if (!epee::string_tools::hex_to_pod(public_key, pk)) -+ { -+ LOG_ERROR("Invalid public key: " << public_key); -+ return; -+ } -+ -+ try -+ { -+ const size_t index = m_wallet->m_wallet->get_transfer_details(pk); -+ const tools::wallet2::transfer_details &td = m_wallet->m_wallet->get_transfer_details(index); -+ m_wallet->m_wallet->set_tx_note(td.m_txid, description); -+ refresh(); -+ } -+ catch (const std::exception& e) -+ { -+ LOG_ERROR("setDescription: " << e.what()); -+ } -+} -+ -+} // namespace -diff --git a/src/wallet/api/coins.h b/src/wallet/api/coins.h -new file mode 100644 -index 000000000..b7a0a8642 ---- /dev/null -+++ b/src/wallet/api/coins.h -@@ -0,0 +1,40 @@ -+#ifndef FEATHER_COINS_H -+#define FEATHER_COINS_H -+ -+#include "wallet/api/wallet2_api.h" -+#include "wallet/wallet2.h" -+ -+namespace Monero { -+ -+class WalletImpl; -+ -+class CoinsImpl : public Coins -+{ -+public: -+ explicit CoinsImpl(WalletImpl * wallet); -+ ~CoinsImpl() override; -+ int count() const override; -+ CoinsInfo * coin(int index) const override; -+ std::vector getAll() const override; -+ void refresh() override; -+ -+ void setFrozen(std::string public_key) override; -+ void setFrozen(int index) override; -+ void thaw(std::string public_key) override; -+ void thaw(int index) override; -+ -+ bool isTransferUnlocked(uint64_t unlockTime, uint64_t blockHeight) override; -+ -+ void setDescription(const std::string &public_key, const std::string &description) override; -+ -+private: -+ WalletImpl *m_wallet; -+ std::vector m_rows; -+ mutable boost::shared_mutex m_rowsMutex; -+}; -+ -+} -+ -+namespace Bitmonero = Monero; -+ -+#endif //FEATHER_COINS_H -diff --git a/src/wallet/api/coins_info.cpp b/src/wallet/api/coins_info.cpp -new file mode 100644 -index 000000000..5f2c4e1e4 ---- /dev/null -+++ b/src/wallet/api/coins_info.cpp -@@ -0,0 +1,122 @@ -+#include "coins_info.h" -+ -+using namespace std; -+ -+namespace Monero { -+ -+CoinsInfo::~CoinsInfo() = default; -+ -+CoinsInfoImpl::CoinsInfoImpl() -+ : m_blockHeight(0) -+ , m_internalOutputIndex(0) -+ , m_globalOutputIndex(0) -+ , m_spent(false) -+ , m_frozen(false) -+ , m_spentHeight(0) -+ , m_amount(0) -+ , m_rct(false) -+ , m_keyImageKnown(false) -+ , m_pkIndex(0) -+ , m_subaddrAccount(0) -+ , m_subaddrIndex(0) -+ , m_unlockTime(0) -+ , m_unlocked(false) -+{ -+ -+} -+ -+CoinsInfoImpl::~CoinsInfoImpl() = default; -+ -+uint64_t CoinsInfoImpl::blockHeight() const -+{ -+ return m_blockHeight; -+} -+ -+string CoinsInfoImpl::hash() const -+{ -+ return m_hash; -+} -+ -+size_t CoinsInfoImpl::internalOutputIndex() const { -+ return m_internalOutputIndex; -+} -+ -+uint64_t CoinsInfoImpl::globalOutputIndex() const -+{ -+ return m_globalOutputIndex; -+} -+ -+bool CoinsInfoImpl::spent() const -+{ -+ return m_spent; -+} -+ -+bool CoinsInfoImpl::frozen() const -+{ -+ return m_frozen; -+} -+ -+uint64_t CoinsInfoImpl::spentHeight() const -+{ -+ return m_spentHeight; -+} -+ -+uint64_t CoinsInfoImpl::amount() const -+{ -+ return m_amount; -+} -+ -+bool CoinsInfoImpl::rct() const { -+ return m_rct; -+} -+ -+bool CoinsInfoImpl::keyImageKnown() const { -+ return m_keyImageKnown; -+} -+ -+size_t CoinsInfoImpl::pkIndex() const { -+ return m_pkIndex; -+} -+ -+uint32_t CoinsInfoImpl::subaddrIndex() const { -+ return m_subaddrIndex; -+} -+ -+uint32_t CoinsInfoImpl::subaddrAccount() const { -+ return m_subaddrAccount; -+} -+ -+string CoinsInfoImpl::address() const { -+ return m_address; -+} -+ -+string CoinsInfoImpl::addressLabel() const { -+ return m_addressLabel; -+} -+ -+string CoinsInfoImpl::keyImage() const { -+ return m_keyImage; -+} -+ -+uint64_t CoinsInfoImpl::unlockTime() const { -+ return m_unlockTime; -+} -+ -+bool CoinsInfoImpl::unlocked() const { -+ return m_unlocked; -+} -+ -+string CoinsInfoImpl::pubKey() const { -+ return m_pubKey; -+} -+ -+bool CoinsInfoImpl::coinbase() const { -+ return m_coinbase; -+} -+ -+string CoinsInfoImpl::description() const { -+ return m_description; -+} -+} // namespace -+ -+namespace Bitmonero = Monero; -diff --git a/src/wallet/api/coins_info.h b/src/wallet/api/coins_info.h -new file mode 100644 -index 000000000..c43e45abd ---- /dev/null -+++ b/src/wallet/api/coins_info.h -@@ -0,0 +1,71 @@ -+#ifndef FEATHER_COINS_INFO_H -+#define FEATHER_COINS_INFO_H -+ -+#include "wallet/api/wallet2_api.h" -+#include -+#include -+ -+namespace Monero { -+ -+class CoinsImpl; -+ -+class CoinsInfoImpl : public CoinsInfo -+{ -+public: -+ CoinsInfoImpl(); -+ ~CoinsInfoImpl(); -+ -+ virtual uint64_t blockHeight() const override; -+ virtual std::string hash() const override; -+ virtual size_t internalOutputIndex() const override; -+ virtual uint64_t globalOutputIndex() const override; -+ virtual bool spent() const override; -+ virtual bool frozen() const override; -+ virtual uint64_t spentHeight() const override; -+ virtual uint64_t amount() const override; -+ virtual bool rct() const override; -+ virtual bool keyImageKnown() const override; -+ virtual size_t pkIndex() const override; -+ virtual uint32_t subaddrIndex() const override; -+ virtual uint32_t subaddrAccount() const override; -+ virtual std::string address() const override; -+ virtual std::string addressLabel() const override; -+ virtual std::string keyImage() const override; -+ virtual uint64_t unlockTime() const override; -+ virtual bool unlocked() const override; -+ virtual std::string pubKey() const override; -+ virtual bool coinbase() const override; -+ virtual std::string description() const override; -+ -+private: -+ uint64_t m_blockHeight; -+ std::string m_hash; -+ size_t m_internalOutputIndex; -+ uint64_t m_globalOutputIndex; -+ bool m_spent; -+ bool m_frozen; -+ uint64_t m_spentHeight; -+ uint64_t m_amount; -+ bool m_rct; -+ bool m_keyImageKnown; -+ size_t m_pkIndex; -+ uint32_t m_subaddrIndex; -+ uint32_t m_subaddrAccount; -+ std::string m_address; -+ std::string m_addressLabel; -+ std::string m_keyImage; -+ uint64_t m_unlockTime; -+ bool m_unlocked; -+ std::string m_pubKey; -+ bool m_coinbase; -+ std::string m_description; -+ -+ friend class CoinsImpl; -+ -+}; -+ -+} // namespace -+ -+namespace Bitmonero = Monero; -+ -+#endif //FEATHER_COINS_INFO_H -diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp -index 704e5e148..e69910e69 100644 ---- a/src/wallet/api/wallet.cpp -+++ b/src/wallet/api/wallet.cpp -@@ -35,6 +35,7 @@ - #include "transaction_history.h" - #include "address_book.h" - #include "subaddress.h" -+#include "coins.h" - #include "subaddress_account.h" - #include "common_defines.h" - #include "common/util.h" -@@ -473,6 +474,7 @@ WalletImpl::WalletImpl(NetworkType nettype, uint64_t kdf_rounds) - m_wallet->set_refresh_enabled(false); - m_addressBook.reset(new AddressBookImpl(this)); - m_subaddress.reset(new SubaddressImpl(this)); -+ m_coins.reset(new CoinsImpl(this)); - m_subaddressAccount.reset(new SubaddressAccountImpl(this)); - - -@@ -2046,7 +2048,7 @@ PendingTransaction* WalletImpl::restoreMultisigTransaction(const string& signDat - // - unconfirmed_transfer_details; - // - confirmed_transfer_details) - --PendingTransaction *WalletImpl::createTransactionMultDest(const std::vector &dst_addr, const string &payment_id, optional> amount, uint32_t mixin_count, PendingTransaction::Priority priority, uint32_t subaddr_account, std::set subaddr_indices) -+PendingTransaction *WalletImpl::createTransactionMultDest(const std::vector &dst_addr, const string &payment_id, optional> amount, uint32_t mixin_count, PendingTransaction::Priority priority, uint32_t subaddr_account, std::set subaddr_indices, const std::set &preferred_inputs) - - { - clearStatus(); -@@ -2084,6 +2086,7 @@ PendingTransaction *WalletImpl::createTransactionMultDest(const std::vectornettype(), dst_addr[i])) { - // TODO: copy-paste 'if treating as an address fails, try as url' from simplewallet.cpp:1982 -@@ -2105,6 +2108,7 @@ PendingTransaction *WalletImpl::createTransactionMultDest(const std::vectorunlocked_balance(subaddr_account, true); -+ // if (maxAllowedSpend < amountSum) { -+ // error = true; -+ // setStatusError(tr("Amount you are trying to spend is larger than unlocked amount")); -+ // break; -+ // } -+ std::vector preferred_input_list; -+ if (!preferred_inputs.empty()) { -+ LOG_ERROR("empty"); -+ -+ for (const auto &public_key : preferred_inputs) { -+ crypto::key_image keyImage; -+ bool r = epee::string_tools::hex_to_pod(public_key, keyImage); -+ if (!r) { -+ error = true; -+ setStatusError(tr("failed to parse key image")); -+ break; -+ } -+ if (m_wallet->frozen(keyImage)) { -+ error = true; -+ setStatusError(tr("refusing to spend frozen coin")); -+ break; -+ } -+ -+ preferred_input_list.push_back(keyImage); -+ } -+ } else { -+ LOG_ERROR("not empty"); -+ -+ boost::shared_lock transfers_lock(m_wallet->m_transfers_mutex); -+ for (size_t i = 0; i < m_wallet->get_num_transfer_details(); ++i) { -+ const tools::wallet2::transfer_details &td = m_wallet->get_transfer_details(i); -+ LOG_ERROR("COIN: " << i << ": " << td.amount() << "; "<frozen(td)); -+ if (td.m_spent) continue; -+ LOG_ERROR("is frozen"); -+ if (!td.m_frozen) { -+ LOG_ERROR("isn't:"); -+ LOG_ERROR("hash: " << td.m_key_image << "; " << td.amount()); -+ preferred_input_list.push_back(td.m_key_image); -+ } -+ } -+ } -+ for (const auto &de : preferred_input_list) { -+ LOG_ERROR("preferred input: " << de); -+ } - if (error) { - break; - } -@@ -2129,11 +2178,11 @@ PendingTransaction *WalletImpl::createTransactionMultDest(const std::vectorm_pending_tx = m_wallet->create_transactions_2(dsts, fake_outs_count, - adjusted_priority, -- extra, subaddr_account, subaddr_indices); -+ extra, subaddr_account, subaddr_indices, preferred_input_list); - } else { - transaction->m_pending_tx = m_wallet->create_transactions_all(0, info.address, info.is_subaddress, 1, fake_outs_count, - adjusted_priority, -- extra, subaddr_account, subaddr_indices); -+ extra, subaddr_account, subaddr_indices, preferred_input_list); - } - pendingTxPostProcess(transaction); - -@@ -2214,10 +2263,10 @@ PendingTransaction *WalletImpl::createTransactionMultDest(const std::vector amount, uint32_t mixin_count, -- PendingTransaction::Priority priority, uint32_t subaddr_account, std::set subaddr_indices) -+ PendingTransaction::Priority priority, uint32_t subaddr_account, std::set subaddr_indices, const std::set &preferred_inputs) - - { -- return createTransactionMultDest(std::vector {dst_addr}, payment_id, amount ? (std::vector {*amount}) : (optional>()), mixin_count, priority, subaddr_account, subaddr_indices); -+ return createTransactionMultDest(std::vector {dst_addr}, payment_id, amount ? (std::vector {*amount}) : (optional>()), mixin_count, priority, subaddr_account, subaddr_indices, preferred_inputs); - } - - PendingTransaction *WalletImpl::createSweepUnmixableTransaction() -@@ -2342,6 +2391,11 @@ AddressBook *WalletImpl::addressBook() - return m_addressBook.get(); - } - -+Coins *WalletImpl::coins() -+{ -+ return m_coins.get(); -+} -+ - Subaddress *WalletImpl::subaddress() - { - return m_subaddress.get(); -diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h -index 32e12284b..a82f270e4 100644 ---- a/src/wallet/api/wallet.h -+++ b/src/wallet/api/wallet.h -@@ -46,6 +46,7 @@ class PendingTransactionImpl; - class UnsignedTransactionImpl; - class AddressBookImpl; - class SubaddressImpl; -+class CoinsImpl; - class SubaddressAccountImpl; - struct Wallet2CallbackImpl; - -@@ -167,12 +168,14 @@ public: - optional> amount, uint32_t mixin_count, - PendingTransaction::Priority priority = PendingTransaction::Priority_Low, - uint32_t subaddr_account = 0, -- std::set subaddr_indices = {}) override; -+ std::set subaddr_indices = {}, -+ const std::set &preferred_inputs = {}) override; - PendingTransaction * createTransaction(const std::string &dst_addr, const std::string &payment_id, - optional amount, uint32_t mixin_count, - PendingTransaction::Priority priority = PendingTransaction::Priority_Low, - uint32_t subaddr_account = 0, -- std::set subaddr_indices = {}) override; -+ std::set subaddr_indices = {}, -+ const std::set &preferred_inputs = {}) override; - virtual PendingTransaction * createSweepUnmixableTransaction() override; - bool submitTransaction(const std::string &fileName) override; - bool submitTransactionUR(const std::string &input) override; -@@ -201,6 +204,7 @@ public: - PendingTransaction::Priority priority) const override; - virtual TransactionHistory * history() override; - virtual AddressBook * addressBook() override; -+ virtual Coins * coins() override; - virtual Subaddress * subaddress() override; - virtual SubaddressAccount * subaddressAccount() override; - virtual void setListener(WalletListener * l) override; -@@ -272,6 +276,7 @@ private: - friend class TransactionHistoryImpl; - friend struct Wallet2CallbackImpl; - friend class AddressBookImpl; -+ friend class CoinsImpl; - friend class SubaddressImpl; - friend class SubaddressAccountImpl; - -@@ -288,6 +293,7 @@ private: - std::unique_ptr m_wallet2Callback; - std::unique_ptr m_addressBook; - std::unique_ptr m_subaddress; -+ std::unique_ptr m_coins; - std::unique_ptr m_subaddressAccount; - - // multi-threaded refresh stuff -diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h -index be1c3704e..013b5bcba 100644 ---- a/src/wallet/api/wallet2_api.h -+++ b/src/wallet/api/wallet2_api.h -@@ -263,6 +263,51 @@ struct AddressBook - virtual int lookupPaymentID(const std::string &payment_id) const = 0; - }; - -+/** -+ * @brief The CoinsInfo - interface for displaying coins information -+ */ -+struct CoinsInfo -+{ -+ virtual ~CoinsInfo() = 0; -+ -+ virtual uint64_t blockHeight() const = 0; -+ virtual std::string hash() const = 0; -+ virtual size_t internalOutputIndex() const = 0; -+ virtual uint64_t globalOutputIndex() const = 0; -+ virtual bool spent() const = 0; -+ virtual bool frozen() const = 0; -+ virtual uint64_t spentHeight() const = 0; -+ virtual uint64_t amount() const = 0; -+ virtual bool rct() const = 0; -+ virtual bool keyImageKnown() const = 0; -+ virtual size_t pkIndex() const = 0; -+ virtual uint32_t subaddrIndex() const = 0; -+ virtual uint32_t subaddrAccount() const = 0; -+ virtual std::string address() const = 0; -+ virtual std::string addressLabel() const = 0; -+ virtual std::string keyImage() const = 0; -+ virtual uint64_t unlockTime() const = 0; -+ virtual bool unlocked() const = 0; -+ virtual std::string pubKey() const = 0; -+ virtual bool coinbase() const = 0; -+ virtual std::string description() const = 0; -+}; -+ -+struct Coins -+{ -+ virtual ~Coins() = 0; -+ virtual int count() const = 0; -+ virtual CoinsInfo * coin(int index) const = 0; -+ virtual std::vector getAll() const = 0; -+ virtual void refresh() = 0; -+ virtual void setFrozen(std::string public_key) = 0; -+ virtual void setFrozen(int index) = 0; -+ virtual void thaw(std::string public_key) = 0; -+ virtual void thaw(int index) = 0; -+ virtual bool isTransferUnlocked(uint64_t unlockTime, uint64_t blockHeight) = 0; -+ virtual void setDescription(const std::string &public_key, const std::string &description) = 0; -+}; -+ - struct SubaddressRow { - public: - SubaddressRow(std::size_t _rowId, const std::string &_address, const std::string &_label): -@@ -856,7 +901,8 @@ struct Wallet - optional> amount, uint32_t mixin_count, - PendingTransaction::Priority = PendingTransaction::Priority_Low, - uint32_t subaddr_account = 0, -- std::set subaddr_indices = {}) = 0; -+ std::set subaddr_indices = {}, -+ const std::set &preferred_inputs = {}) = 0; - - /*! - * \brief createTransaction creates transaction. if dst_addr is an integrated address, payment_id is ignored -@@ -875,7 +921,8 @@ struct Wallet - optional amount, uint32_t mixin_count, - PendingTransaction::Priority = PendingTransaction::Priority_Low, - uint32_t subaddr_account = 0, -- std::set subaddr_indices = {}) = 0; -+ std::set subaddr_indices = {}, -+ const std::set &preferred_inputs = {}) = 0; - - /*! - * \brief createSweepUnmixableTransaction creates transaction with unmixable outputs. -@@ -994,6 +1041,7 @@ struct Wallet - - virtual TransactionHistory * history() = 0; - virtual AddressBook * addressBook() = 0; -+ virtual Coins * coins() = 0; - virtual Subaddress * subaddress() = 0; - virtual SubaddressAccount * subaddressAccount() = 0; - virtual void setListener(WalletListener *) = 0; -diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp -index fd4094360..be3096675 100644 ---- a/src/wallet/wallet2.cpp -+++ b/src/wallet/wallet2.cpp -@@ -2103,12 +2103,21 @@ bool wallet2::frozen(const multisig_tx_set& txs) const - - return false; - } -+void wallet2::freeze(const crypto::public_key &pk) -+{ -+ freeze(get_transfer_details(pk)); -+} - //---------------------------------------------------------------------------------------------------- - void wallet2::freeze(const crypto::key_image &ki) - { - freeze(get_transfer_details(ki)); - } - //---------------------------------------------------------------------------------------------------- -+void wallet2::thaw(const crypto::public_key &pk) -+{ -+ thaw(get_transfer_details(pk)); -+} -+//---------------------------------------------------------------------------------------------------- - void wallet2::thaw(const crypto::key_image &ki) - { - thaw(get_transfer_details(ki)); -@@ -2119,6 +2128,18 @@ bool wallet2::frozen(const crypto::key_image &ki) const - return frozen(get_transfer_details(ki)); - } - //---------------------------------------------------------------------------------------------------- -+size_t wallet2::get_transfer_details(const crypto::public_key &pk) const -+{ -+ for (size_t idx = 0; idx < m_transfers.size(); ++idx) -+ { -+ const transfer_details &td = m_transfers[idx]; -+ if (td.get_public_key() == pk) { -+ return idx; -+ } -+ } -+ CHECK_AND_ASSERT_THROW_MES(false, "Public key not found"); -+} -+//---------------------------------------------------------------------------------------------------- - size_t wallet2::get_transfer_details(const crypto::key_image &ki) const - { - for (size_t idx = 0; idx < m_transfers.size(); ++idx) -@@ -2532,6 +2553,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote - uint64_t amount = tx.vout[o].amount ? tx.vout[o].amount : tx_scan_info[o].amount; - if (!pool) - { -+ boost::unique_lock lock(m_transfers_mutex); - m_transfers.push_back(transfer_details{}); - transfer_details& td = m_transfers.back(); - td.m_block_height = height; -@@ -2635,6 +2657,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote - uint64_t extra_amount = amount - burnt; - if (!pool) - { -+ boost::unique_lock lock(m_transfers_mutex); - transfer_details &td = m_transfers[kit->second]; - td.m_block_height = height; - td.m_internal_output_index = o; -@@ -10506,7 +10529,7 @@ void wallet2::transfer_selected_rct(std::vector wallet2::pick_preferred_rct_inputs(uint64_t needed_money, uint32_t subaddr_account, const std::set &subaddr_indices) -+std::vector wallet2::pick_preferred_rct_inputs(uint64_t needed_money, uint32_t subaddr_account, const std::set &subaddr_indices, const std::vector& preferred_input_list) - { - std::vector picks; - float current_output_relatdness = 1.0f; -@@ -10517,6 +10540,9 @@ std::vector wallet2::pick_preferred_rct_inputs(uint64_t needed_money, ui - for (size_t i = 0; i < m_transfers.size(); ++i) - { - const transfer_details& td = m_transfers[i]; -+ if (!is_preferred_input(preferred_input_list, td.m_key_image)) { -+ continue; -+ } - if (!is_spent(td, false) && !td.m_frozen && td.is_rct() && td.amount() >= needed_money && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1) - { - if (td.amount() > m_ignore_outputs_above || td.amount() < m_ignore_outputs_below) -@@ -10537,6 +10563,9 @@ std::vector wallet2::pick_preferred_rct_inputs(uint64_t needed_money, ui - for (size_t i = 0; i < m_transfers.size(); ++i) - { - const transfer_details& td = m_transfers[i]; -+ if (!is_preferred_input(preferred_input_list, td.m_key_image)) { -+ continue; -+ } - if (!is_spent(td, false) && !td.m_frozen && !td.m_key_image_partial && td.is_rct() && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1) - { - if (td.amount() > m_ignore_outputs_above || td.amount() < m_ignore_outputs_below) -@@ -10548,6 +10577,9 @@ std::vector wallet2::pick_preferred_rct_inputs(uint64_t needed_money, ui - for (size_t j = i + 1; j < m_transfers.size(); ++j) - { - const transfer_details& td2 = m_transfers[j]; -+ if (!is_preferred_input(preferred_input_list, td2.m_key_image)) { -+ continue; -+ } - if (td2.amount() > m_ignore_outputs_above || td2.amount() < m_ignore_outputs_below) - { - MDEBUG("Ignoring output " << j << " of amount " << print_money(td2.amount()) << " which is outside prescribed range [" << print_money(m_ignore_outputs_below) << ", " << print_money(m_ignore_outputs_above) << "]"); -@@ -11120,7 +11152,7 @@ bool wallet2::light_wallet_key_image_is_ours(const crypto::key_image& key_image, - // This system allows for sending (almost) the entire balance, since it does - // not generate spurious change in all txes, thus decreasing the instantaneous - // usable balance. --std::vector wallet2::create_transactions_2(std::vector dsts, const size_t fake_outs_count, uint32_t priority, const std::vector& extra, uint32_t subaddr_account, std::set subaddr_indices, const unique_index_container& subtract_fee_from_outputs) -+std::vector wallet2::create_transactions_2(std::vector dsts, const size_t fake_outs_count, uint32_t priority, const std::vector& extra, uint32_t subaddr_account, std::set subaddr_indices, const std::vector& preferred_input_list, const unique_index_container& subtract_fee_from_outputs) - { - //ensure device is let in NONE mode in any case - hw::device &hwdev = m_account.get_device(); -@@ -11328,6 +11360,9 @@ std::vector wallet2::create_transactions_2(std::vector wallet2::create_transactions_2(std::vector &ptx_vector, c - return true; - } - --std::vector wallet2::create_transactions_all(uint64_t below, const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, const size_t fake_outs_count, uint32_t priority, const std::vector& extra, uint32_t subaddr_account, std::set subaddr_indices) -+std::vector wallet2::create_transactions_all(uint64_t below, const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, const size_t fake_outs_count, uint32_t priority, const std::vector& extra, uint32_t subaddr_account, std::set subaddr_indices, const std::vector& preferred_input_list) - { - std::vector unused_transfers_indices; - std::vector unused_dust_indices; -@@ -11927,6 +11962,9 @@ std::vector wallet2::create_transactions_all(uint64_t below - for (size_t i = 0; i < m_transfers.size(); ++i) - { - const transfer_details& td = m_transfers[i]; -+ if (!is_preferred_input(preferred_input_list, td.m_key_image)) { -+ continue; -+ } - if (m_ignore_fractional_outputs && td.amount() < fractional_threshold) - { - MDEBUG("Ignoring output " << i << " of amount " << print_money(td.amount()) << " which is below threshold " << print_money(fractional_threshold)); -diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h -index c165acb9d..6b103d9c2 100644 ---- a/src/wallet/wallet2.h -+++ b/src/wallet/wallet2.h -@@ -1216,8 +1216,8 @@ private: - bool parse_unsigned_tx_from_str(const std::string &unsigned_tx_st, unsigned_tx_set &exported_txs) const; - bool load_tx(const std::string &signed_filename, std::vector &ptx, std::function accept_func = NULL); - bool parse_tx_from_str(const std::string &signed_tx_st, std::vector &ptx, std::function accept_func); -- std::vector create_transactions_2(std::vector dsts, const size_t fake_outs_count, uint32_t priority, const std::vector& extra, uint32_t subaddr_account, std::set subaddr_indices, const unique_index_container& subtract_fee_from_outputs = {}); // pass subaddr_indices by value on purpose -- std::vector create_transactions_all(uint64_t below, const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, const size_t fake_outs_count, uint32_t priority, const std::vector& extra, uint32_t subaddr_account, std::set subaddr_indices); -+ std::vector create_transactions_2(std::vector dsts, const size_t fake_outs_count, uint32_t priority, const std::vector& extra, uint32_t subaddr_account, std::set subaddr_indices, const std::vector& preferred_input_list = {}, const unique_index_container& subtract_fee_from_outputs = {}); // pass subaddr_indices by value on purpose -+ std::vector create_transactions_all(uint64_t below, const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, const size_t fake_outs_count, uint32_t priority, const std::vector& extra, uint32_t subaddr_account, std::set subaddr_indices, const std::vector& preferred_input_list = {}); - std::vector create_transactions_single(const crypto::key_image &ki, const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, const size_t fake_outs_count, uint32_t priority, const std::vector& extra); - std::vector create_transactions_from(const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, std::vector unused_transfers_indices, std::vector unused_dust_indices, const size_t fake_outs_count, uint32_t priority, const std::vector& extra); - bool sanity_check(const std::vector &ptx_vector, const std::vector& dsts, const unique_index_container& subtract_fee_from_outputs = {}) const; -@@ -1569,6 +1569,7 @@ private: - uint64_t get_num_rct_outputs(); - size_t get_num_transfer_details() const { return m_transfers.size(); } - const transfer_details &get_transfer_details(size_t idx) const; -+ size_t get_transfer_details(const crypto::public_key &pk) const; - - uint8_t get_current_hard_fork(); - void get_hard_fork_info(uint8_t version, uint64_t &earliest_height); -@@ -1800,7 +1801,9 @@ private: - void freeze(size_t idx); - void thaw(size_t idx); - bool frozen(size_t idx) const; -+ void freeze(const crypto::public_key &pk); - void freeze(const crypto::key_image &ki); -+ void thaw(const crypto::public_key &pk); - void thaw(const crypto::key_image &ki); - bool frozen(const crypto::key_image &ki) const; - bool frozen(const transfer_details &td) const; -@@ -1841,6 +1844,8 @@ private: - - static std::string get_default_daemon_address() { CRITICAL_REGION_LOCAL(default_daemon_address_lock); return default_daemon_address; } - -+ boost::shared_mutex m_transfers_mutex; -+ - private: - /*! - * \brief Stores wallet information to wallet file. -@@ -1912,7 +1917,7 @@ private: - std::vector get_unspent_amounts_vector(bool strict); - uint64_t get_dynamic_base_fee_estimate(); - float get_output_relatedness(const transfer_details &td0, const transfer_details &td1) const; -- std::vector pick_preferred_rct_inputs(uint64_t needed_money, uint32_t subaddr_account, const std::set &subaddr_indices); -+ std::vector pick_preferred_rct_inputs(uint64_t needed_money, uint32_t subaddr_account, const std::set &subaddr_indices, const std::vector& preferred_input_list); - void set_spent(size_t idx, uint64_t height); - void set_unspent(size_t idx); - bool is_spent(const transfer_details &td, bool strict = true) const; --- -2.48.0 - diff --git a/patches/wownero/0011-Add-hex-encoding-and-tx-key-getter-for-PendingTransc.patch b/patches/wownero/0011-Add-hex-encoding-and-tx-key-getter-for-PendingTransc.patch deleted file mode 100644 index de19446..0000000 --- a/patches/wownero/0011-Add-hex-encoding-and-tx-key-getter-for-PendingTransc.patch +++ /dev/null @@ -1,68 +0,0 @@ -From 96911b156ad9cfef2ebf2809a393492e87ab7607 Mon Sep 17 00:00:00 2001 -From: M -Date: Fri, 21 Apr 2023 15:43:47 -0400 -Subject: [PATCH 11/15] Add hex encoding and tx key getter for - PendingTransction in wallet api. - ---- - src/wallet/api/pending_transaction.cpp | 16 ++++++++++++++++ - src/wallet/api/pending_transaction.h | 2 ++ - src/wallet/api/wallet2_api.h | 2 ++ - 3 files changed, 20 insertions(+) - -diff --git a/src/wallet/api/pending_transaction.cpp b/src/wallet/api/pending_transaction.cpp -index 9c3c26ee5..1f714d229 100644 ---- a/src/wallet/api/pending_transaction.cpp -+++ b/src/wallet/api/pending_transaction.cpp -@@ -80,6 +80,22 @@ std::vector PendingTransactionImpl::txid() const - return txid; - } - -+std::vector PendingTransactionImpl::hex() const -+{ -+ std::vector hexs; -+ for (const auto &pt: m_pending_tx) -+ hexs.push_back(epee::string_tools::buff_to_hex_nodelimer(cryptonote::tx_to_blob(pt.tx))); -+ return hexs; -+} -+ -+std::vector PendingTransactionImpl::txKey() const -+{ -+ std::vector keys; -+ for (const auto& pt: m_pending_tx) -+ keys.push_back(epee::string_tools::pod_to_hex(pt.tx_key)); -+ return keys; -+} -+ - bool PendingTransactionImpl::commit(const std::string &filename, bool overwrite) - { - -diff --git a/src/wallet/api/pending_transaction.h b/src/wallet/api/pending_transaction.h -index 403bfe281..0cc6c58e9 100644 ---- a/src/wallet/api/pending_transaction.h -+++ b/src/wallet/api/pending_transaction.h -@@ -59,6 +59,8 @@ public: - std::string multisigSignData() override; - void signMultisigTx() override; - std::vector signersKeys() const override; -+ std::vector hex() const override; -+ std::vector txKey() const override; - - private: - friend class WalletImpl; -diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h -index 013b5bcba..f421fdc05 100644 ---- a/src/wallet/api/wallet2_api.h -+++ b/src/wallet/api/wallet2_api.h -@@ -127,6 +127,8 @@ struct PendingTransaction - * @return vector of base58-encoded signers' public keys - */ - virtual std::vector signersKeys() const = 0; -+ virtual std::vector hex() const = 0; -+ virtual std::vector txKey() const = 0; - }; - - /** --- -2.48.0 - diff --git a/patches/wownero/0011-Add-recoverDeterministicWalletFromSpendKey.patch b/patches/wownero/0011-Add-recoverDeterministicWalletFromSpendKey.patch new file mode 100644 index 0000000..a6cf6f5 --- /dev/null +++ b/patches/wownero/0011-Add-recoverDeterministicWalletFromSpendKey.patch @@ -0,0 +1,153 @@ +From 9f1a4324d0346d3fc9baf055f0d86f796d1dcecd Mon Sep 17 00:00:00 2001 +From: Konstantin Ullrich +Date: Wed, 11 Oct 2023 16:47:59 +0200 +Subject: [PATCH 11/16] Add recoverDeterministicWalletFromSpendKey + +This function is used by Cake Wallet to enable polyseed (dart implementation) +support. + +Sourced from the following commit: +https://github.com/cake-tech/monero/commit/cb6fb5ab218878702ed151c0e3d5d68eb2732788 + +Co-authored-by: Godwin Asuquo +--- + src/wallet/api/wallet.cpp | 29 +++++++++++++++++++++++++++++ + src/wallet/api/wallet.h | 4 ++++ + src/wallet/api/wallet2_api.h | 19 +++++++++++++++++++ + src/wallet/api/wallet_manager.cpp | 16 ++++++++++++++++ + src/wallet/api/wallet_manager.h | 7 +++++++ + 5 files changed, 75 insertions(+) + +diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp +index 7066fc3d8..690fbdce1 100644 +--- a/src/wallet/api/wallet.cpp ++++ b/src/wallet/api/wallet.cpp +@@ -824,6 +824,35 @@ bool WalletImpl::recover(const std::string &path, const std::string &password, c + return status() == Status_Ok; + } + ++bool WalletImpl::recoverDeterministicWalletFromSpendKey(const std::string &path, const std::string &password, const std::string &language, const std::string &spendkey_string) ++{ ++ clearStatus(); ++ m_errorString.clear(); ++ ++ m_recoveringFromSeed = true; ++ m_recoveringFromDevice = false; ++ ++ // parse spend key ++ crypto::secret_key spendkey; ++ if (!spendkey_string.empty()) { ++ cryptonote::blobdata spendkey_data; ++ if(!epee::string_tools::parse_hexstr_to_binbuff(spendkey_string, spendkey_data) || spendkey_data.size() != sizeof(crypto::secret_key)) ++ { ++ setStatusError(tr("failed to parse secret spend key")); ++ return false; ++ } ++ spendkey = *reinterpret_cast(spendkey_data.data()); ++ } ++ ++ try { ++ m_wallet->generate(path, password, spendkey, true, false); ++ setSeedLanguage(language); ++ } catch (const std::exception &e) { ++ setStatusCritical(e.what()); ++ } ++ return status() == Status_Ok; ++} ++ + bool WalletImpl::close(bool store) + { + +diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h +index a82f270e4..9e1fbb40b 100644 +--- a/src/wallet/api/wallet.h ++++ b/src/wallet/api/wallet.h +@@ -77,6 +77,10 @@ public: + const std::string &address_string, + const std::string &viewkey_string, + const std::string &spendkey_string = ""); ++ bool recoverDeterministicWalletFromSpendKey(const std::string &path, ++ const std::string &password, ++ const std::string &language, ++ const std::string &spendkey_string); + bool recoverFromDevice(const std::string &path, + const std::string &password, + const std::string &device_name); +diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h +index f421fdc05..c8d6bb179 100644 +--- a/src/wallet/api/wallet2_api.h ++++ b/src/wallet/api/wallet2_api.h +@@ -1323,6 +1323,25 @@ struct WalletManager + return createWalletFromKeys(path, password, language, testnet ? TESTNET : MAINNET, restoreHeight, addressString, viewKeyString, spendKeyString); + } + ++ /*! ++ * \brief recover deterministic wallet from spend key. ++ * \param path Name of wallet file to be created ++ * \param password Password of wallet file ++ * \param language language ++ * \param nettype Network type ++ * \param restoreHeight restore from start height ++ * \param spendKeyString spend key ++ * \param kdf_rounds Number of rounds for key derivation function ++ * \return Wallet instance (Wallet::status() needs to be called to check if recovered successfully) ++ */ ++ virtual Wallet * createDeterministicWalletFromSpendKey(const std::string &path, ++ const std::string &password, ++ const std::string &language, ++ NetworkType nettype, ++ uint64_t restoreHeight, ++ const std::string &spendKeyString, ++ uint64_t kdf_rounds = 1) = 0; ++ + /*! + * \deprecated this method creates a wallet WITHOUT a passphrase, use createWalletFromKeys(..., password, ...) instead + * \brief recovers existing wallet using keys. Creates a view only wallet if spend key is omitted +diff --git a/src/wallet/api/wallet_manager.cpp b/src/wallet/api/wallet_manager.cpp +index da2056d8a..c200f52ae 100644 +--- a/src/wallet/api/wallet_manager.cpp ++++ b/src/wallet/api/wallet_manager.cpp +@@ -127,6 +127,22 @@ Wallet *WalletManagerImpl::createWalletFromKeys(const std::string &path, + return wallet; + } + ++Wallet *WalletManagerImpl::createDeterministicWalletFromSpendKey(const std::string &path, ++ const std::string &password, ++ const std::string &language, ++ NetworkType nettype, ++ uint64_t restoreHeight, ++ const std::string &spendkey_string, ++ uint64_t kdf_rounds) ++{ ++ WalletImpl * wallet = new WalletImpl(nettype, kdf_rounds); ++ if(restoreHeight > 0){ ++ wallet->setRefreshFromBlockHeight(restoreHeight); ++ } ++ wallet->recoverDeterministicWalletFromSpendKey(path, password, language, spendkey_string); ++ return wallet; ++} ++ + Wallet *WalletManagerImpl::createWalletFromDevice(const std::string &path, + const std::string &password, + NetworkType nettype, +diff --git a/src/wallet/api/wallet_manager.h b/src/wallet/api/wallet_manager.h +index 28fcd36c9..be3ff8184 100644 +--- a/src/wallet/api/wallet_manager.h ++++ b/src/wallet/api/wallet_manager.h +@@ -67,6 +67,13 @@ public: + const std::string &addressString, + const std::string &viewKeyString, + const std::string &spendKeyString = "") override; ++ virtual Wallet * createDeterministicWalletFromSpendKey(const std::string &path, ++ const std::string &password, ++ const std::string &language, ++ NetworkType nettype, ++ uint64_t restoreHeight, ++ const std::string &spendkey_string, ++ uint64_t kdf_rounds) override; + virtual Wallet * createWalletFromDevice(const std::string &path, + const std::string &password, + NetworkType nettype, +-- +2.51.0 + diff --git a/patches/wownero/0012-Add-recoverDeterministicWalletFromSpendKey.patch b/patches/wownero/0012-Add-recoverDeterministicWalletFromSpendKey.patch deleted file mode 100644 index bbb5547..0000000 --- a/patches/wownero/0012-Add-recoverDeterministicWalletFromSpendKey.patch +++ /dev/null @@ -1,153 +0,0 @@ -From ad28016bf3cfd11242dc14472389f7a006c73dcf Mon Sep 17 00:00:00 2001 -From: Konstantin Ullrich -Date: Wed, 11 Oct 2023 16:47:59 +0200 -Subject: [PATCH 12/15] Add recoverDeterministicWalletFromSpendKey - -This function is used by Cake Wallet to enable polyseed (dart implementation) -support. - -Sourced from the following commit: -https://github.com/cake-tech/monero/commit/cb6fb5ab218878702ed151c0e3d5d68eb2732788 - -Co-authored-by: Godwin Asuquo ---- - src/wallet/api/wallet.cpp | 29 +++++++++++++++++++++++++++++ - src/wallet/api/wallet.h | 4 ++++ - src/wallet/api/wallet2_api.h | 19 +++++++++++++++++++ - src/wallet/api/wallet_manager.cpp | 16 ++++++++++++++++ - src/wallet/api/wallet_manager.h | 7 +++++++ - 5 files changed, 75 insertions(+) - -diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp -index e69910e69..e650e6044 100644 ---- a/src/wallet/api/wallet.cpp -+++ b/src/wallet/api/wallet.cpp -@@ -824,6 +824,35 @@ bool WalletImpl::recover(const std::string &path, const std::string &password, c - return status() == Status_Ok; - } - -+bool WalletImpl::recoverDeterministicWalletFromSpendKey(const std::string &path, const std::string &password, const std::string &language, const std::string &spendkey_string) -+{ -+ clearStatus(); -+ m_errorString.clear(); -+ -+ m_recoveringFromSeed = true; -+ m_recoveringFromDevice = false; -+ -+ // parse spend key -+ crypto::secret_key spendkey; -+ if (!spendkey_string.empty()) { -+ cryptonote::blobdata spendkey_data; -+ if(!epee::string_tools::parse_hexstr_to_binbuff(spendkey_string, spendkey_data) || spendkey_data.size() != sizeof(crypto::secret_key)) -+ { -+ setStatusError(tr("failed to parse secret spend key")); -+ return false; -+ } -+ spendkey = *reinterpret_cast(spendkey_data.data()); -+ } -+ -+ try { -+ m_wallet->generate(path, password, spendkey, true, false); -+ setSeedLanguage(language); -+ } catch (const std::exception &e) { -+ setStatusCritical(e.what()); -+ } -+ return status() == Status_Ok; -+} -+ - bool WalletImpl::close(bool store) - { - -diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h -index a82f270e4..9e1fbb40b 100644 ---- a/src/wallet/api/wallet.h -+++ b/src/wallet/api/wallet.h -@@ -77,6 +77,10 @@ public: - const std::string &address_string, - const std::string &viewkey_string, - const std::string &spendkey_string = ""); -+ bool recoverDeterministicWalletFromSpendKey(const std::string &path, -+ const std::string &password, -+ const std::string &language, -+ const std::string &spendkey_string); - bool recoverFromDevice(const std::string &path, - const std::string &password, - const std::string &device_name); -diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h -index f421fdc05..c8d6bb179 100644 ---- a/src/wallet/api/wallet2_api.h -+++ b/src/wallet/api/wallet2_api.h -@@ -1323,6 +1323,25 @@ struct WalletManager - return createWalletFromKeys(path, password, language, testnet ? TESTNET : MAINNET, restoreHeight, addressString, viewKeyString, spendKeyString); - } - -+ /*! -+ * \brief recover deterministic wallet from spend key. -+ * \param path Name of wallet file to be created -+ * \param password Password of wallet file -+ * \param language language -+ * \param nettype Network type -+ * \param restoreHeight restore from start height -+ * \param spendKeyString spend key -+ * \param kdf_rounds Number of rounds for key derivation function -+ * \return Wallet instance (Wallet::status() needs to be called to check if recovered successfully) -+ */ -+ virtual Wallet * createDeterministicWalletFromSpendKey(const std::string &path, -+ const std::string &password, -+ const std::string &language, -+ NetworkType nettype, -+ uint64_t restoreHeight, -+ const std::string &spendKeyString, -+ uint64_t kdf_rounds = 1) = 0; -+ - /*! - * \deprecated this method creates a wallet WITHOUT a passphrase, use createWalletFromKeys(..., password, ...) instead - * \brief recovers existing wallet using keys. Creates a view only wallet if spend key is omitted -diff --git a/src/wallet/api/wallet_manager.cpp b/src/wallet/api/wallet_manager.cpp -index da2056d8a..c200f52ae 100644 ---- a/src/wallet/api/wallet_manager.cpp -+++ b/src/wallet/api/wallet_manager.cpp -@@ -127,6 +127,22 @@ Wallet *WalletManagerImpl::createWalletFromKeys(const std::string &path, - return wallet; - } - -+Wallet *WalletManagerImpl::createDeterministicWalletFromSpendKey(const std::string &path, -+ const std::string &password, -+ const std::string &language, -+ NetworkType nettype, -+ uint64_t restoreHeight, -+ const std::string &spendkey_string, -+ uint64_t kdf_rounds) -+{ -+ WalletImpl * wallet = new WalletImpl(nettype, kdf_rounds); -+ if(restoreHeight > 0){ -+ wallet->setRefreshFromBlockHeight(restoreHeight); -+ } -+ wallet->recoverDeterministicWalletFromSpendKey(path, password, language, spendkey_string); -+ return wallet; -+} -+ - Wallet *WalletManagerImpl::createWalletFromDevice(const std::string &path, - const std::string &password, - NetworkType nettype, -diff --git a/src/wallet/api/wallet_manager.h b/src/wallet/api/wallet_manager.h -index 28fcd36c9..be3ff8184 100644 ---- a/src/wallet/api/wallet_manager.h -+++ b/src/wallet/api/wallet_manager.h -@@ -67,6 +67,13 @@ public: - const std::string &addressString, - const std::string &viewKeyString, - const std::string &spendKeyString = "") override; -+ virtual Wallet * createDeterministicWalletFromSpendKey(const std::string &path, -+ const std::string &password, -+ const std::string &language, -+ NetworkType nettype, -+ uint64_t restoreHeight, -+ const std::string &spendkey_string, -+ uint64_t kdf_rounds) override; - virtual Wallet * createWalletFromDevice(const std::string &path, - const std::string &password, - NetworkType nettype, --- -2.48.0 - diff --git a/patches/wownero/0012-add-monero-submodule-support.patch b/patches/wownero/0012-add-monero-submodule-support.patch new file mode 100644 index 0000000..6380cd2 --- /dev/null +++ b/patches/wownero/0012-add-monero-submodule-support.patch @@ -0,0 +1,79 @@ +From 733d8fa11a455ff144d29a6a5fad384212d1931a Mon Sep 17 00:00:00 2001 +From: cyan +Date: Thu, 7 Nov 2024 16:46:24 +0000 +Subject: [PATCH 12/16] add monero submodule support + +--- + CMakeLists.txt | 6 +++--- + cmake/CheckLinkerFlag.cmake | 2 +- + cmake/FindCcache.cmake | 2 +- + src/wallet/wallet_rpc_server.cpp | 2 +- + 4 files changed, 6 insertions(+), 6 deletions(-) + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 0007b5ea9..caa1d5e33 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -223,9 +223,9 @@ function(forbid_undefined_symbols) + cmake_minimum_required(VERSION 3.5) + project(test) + option(EXPECT_SUCCESS "" ON) +-file(WRITE "${CMAKE_SOURCE_DIR}/incorrect_source.cpp" "void undefined_symbol(); void symbol() { undefined_symbol(); }") ++file(WRITE "${CMAKE_CURRENT_SOURCE_DIR}/incorrect_source.cpp" "void undefined_symbol(); void symbol() { undefined_symbol(); }") + if (EXPECT_SUCCESS) +- file(APPEND "${CMAKE_SOURCE_DIR}/incorrect_source.cpp" " void undefined_symbol() {}; ") ++ file(APPEND "${CMAKE_CURRENT_SOURCE_DIR}/incorrect_source.cpp" " void undefined_symbol() {}; ") + endif() + add_library(l0 SHARED incorrect_source.cpp) + add_library(l1 MODULE incorrect_source.cpp) +@@ -391,7 +391,7 @@ else() + endif() + + list(INSERT CMAKE_MODULE_PATH 0 +- "${CMAKE_SOURCE_DIR}/cmake") ++ "${CMAKE_CURRENT_SOURCE_DIR}/cmake") + + if (NOT DEFINED ENV{DEVELOPER_LOCAL_TOOLS}) + message(STATUS "Could not find DEVELOPER_LOCAL_TOOLS in env (not required)") +diff --git a/cmake/CheckLinkerFlag.cmake b/cmake/CheckLinkerFlag.cmake +index 69eefe810..89fb9d167 100644 +--- a/cmake/CheckLinkerFlag.cmake ++++ b/cmake/CheckLinkerFlag.cmake +@@ -6,7 +6,7 @@ macro(CHECK_LINKER_FLAG flag VARIABLE) + message(STATUS "Looking for ${flag} linker flag") + endif() + +- set(_cle_source ${monero_SOURCE_DIR}/cmake/CheckLinkerFlag.c) ++ set(_cle_source ${CMAKE_CURRENT_SOURCE_DIR}/cmake/CheckLinkerFlag.c) + + set(saved_CMAKE_C_FLAGS ${CMAKE_C_FLAGS}) + set(CMAKE_C_FLAGS "${flag}") +diff --git a/cmake/FindCcache.cmake b/cmake/FindCcache.cmake +index d3f5f829b..68e4851cb 100644 +--- a/cmake/FindCcache.cmake ++++ b/cmake/FindCcache.cmake +@@ -47,7 +47,7 @@ if (CCACHE_FOUND) + cmake_minimum_required(VERSION 3.5) + project(test) + option (CCACHE "") +-file(WRITE "${CMAKE_SOURCE_DIR}/test.cpp" "int main() { return 0; }") ++file(WRITE "${CMAKE_CURRENT_SOURCE_DIR}/test.cpp" "int main() { return 0; }") + set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CCACHE}") + set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK "${CCACHE}") + add_executable(main test.cpp) +diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp +index d4167e8cb..f7a07ca26 100644 +--- a/src/wallet/wallet_rpc_server.cpp ++++ b/src/wallet/wallet_rpc_server.cpp +@@ -1261,7 +1261,7 @@ namespace tools + { + uint64_t mixin = m_wallet->adjust_mixin(req.ring_size ? req.ring_size - 1 : 0); + uint32_t priority = m_wallet->adjust_priority(req.priority); +- std::vector ptx_vector = m_wallet->create_transactions_2(dsts, mixin, priority, extra, req.account_index, req.subaddr_indices, req.subtract_fee_from_outputs); ++ std::vector ptx_vector = m_wallet->create_transactions_2(dsts, mixin, priority, extra, req.account_index, req.subaddr_indices, {}, req.subtract_fee_from_outputs); + + if (ptx_vector.empty()) + { +-- +2.51.0 + diff --git a/patches/wownero/0013-add-monero-submodule-support.patch b/patches/wownero/0013-add-monero-submodule-support.patch deleted file mode 100644 index 6b1e987..0000000 --- a/patches/wownero/0013-add-monero-submodule-support.patch +++ /dev/null @@ -1,65 +0,0 @@ -From f8b3055a79c929dc167d20d4874ab20b0a51d49d Mon Sep 17 00:00:00 2001 -From: cyan -Date: Thu, 7 Nov 2024 16:46:24 +0000 -Subject: [PATCH 13/15] add monero submodule support - ---- - CMakeLists.txt | 6 +++--- - cmake/CheckLinkerFlag.cmake | 2 +- - src/wallet/wallet_rpc_server.cpp | 2 +- - 3 files changed, 5 insertions(+), 5 deletions(-) - -diff --git a/CMakeLists.txt b/CMakeLists.txt -index 96056e324..1383e630a 100644 ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -223,9 +223,9 @@ function(forbid_undefined_symbols) - cmake_minimum_required(VERSION 3.1) - project(test) - option(EXPECT_SUCCESS "" ON) --file(WRITE "${CMAKE_SOURCE_DIR}/incorrect_source.cpp" "void undefined_symbol(); void symbol() { undefined_symbol(); }") -+file(WRITE "${CMAKE_CURRENT_SOURCE_DIR}/incorrect_source.cpp" "void undefined_symbol(); void symbol() { undefined_symbol(); }") - if (EXPECT_SUCCESS) -- file(APPEND "${CMAKE_SOURCE_DIR}/incorrect_source.cpp" " void undefined_symbol() {}; ") -+ file(APPEND "${CMAKE_CURRENT_SOURCE_DIR}/incorrect_source.cpp" " void undefined_symbol() {}; ") - endif() - add_library(l0 SHARED incorrect_source.cpp) - add_library(l1 MODULE incorrect_source.cpp) -@@ -391,7 +391,7 @@ else() - endif() - - list(INSERT CMAKE_MODULE_PATH 0 -- "${CMAKE_SOURCE_DIR}/cmake") -+ "${CMAKE_CURRENT_SOURCE_DIR}/cmake") - - if (NOT DEFINED ENV{DEVELOPER_LOCAL_TOOLS}) - message(STATUS "Could not find DEVELOPER_LOCAL_TOOLS in env (not required)") -diff --git a/cmake/CheckLinkerFlag.cmake b/cmake/CheckLinkerFlag.cmake -index 7ecf5f610..89fb9d167 100644 ---- a/cmake/CheckLinkerFlag.cmake -+++ b/cmake/CheckLinkerFlag.cmake -@@ -6,7 +6,7 @@ macro(CHECK_LINKER_FLAG flag VARIABLE) - message(STATUS "Looking for ${flag} linker flag") - endif() - -- set(_cle_source ${CMAKE_SOURCE_DIR}/cmake/CheckLinkerFlag.c) -+ set(_cle_source ${CMAKE_CURRENT_SOURCE_DIR}/cmake/CheckLinkerFlag.c) - - set(saved_CMAKE_C_FLAGS ${CMAKE_C_FLAGS}) - set(CMAKE_C_FLAGS "${flag}") -diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp -index 21e69ea0e..d9bc5dda3 100644 ---- a/src/wallet/wallet_rpc_server.cpp -+++ b/src/wallet/wallet_rpc_server.cpp -@@ -1162,7 +1162,7 @@ namespace tools - { - uint64_t mixin = m_wallet->adjust_mixin(req.ring_size ? req.ring_size - 1 : 0); - uint32_t priority = m_wallet->adjust_priority(req.priority); -- std::vector ptx_vector = m_wallet->create_transactions_2(dsts, mixin, priority, extra, req.account_index, req.subaddr_indices, req.subtract_fee_from_outputs); -+ std::vector ptx_vector = m_wallet->create_transactions_2(dsts, mixin, priority, extra, req.account_index, req.subaddr_indices, {}, req.subtract_fee_from_outputs); - - if (ptx_vector.empty()) - { --- -2.48.0 - diff --git a/patches/wownero/0013-fix-iOS-depends-build.patch b/patches/wownero/0013-fix-iOS-depends-build.patch new file mode 100644 index 0000000..78464b7 --- /dev/null +++ b/patches/wownero/0013-fix-iOS-depends-build.patch @@ -0,0 +1,104 @@ +From 23507763ad4a3b2461b97ef0a993d6a06158312d Mon Sep 17 00:00:00 2001 +From: Czarek Nakamoto +Date: Thu, 21 Nov 2024 06:05:03 -0500 +Subject: [PATCH 13/16] fix iOS depends build + +--- + CMakeLists.txt | 4 ---- + src/checkpoints/CMakeLists.txt | 6 +++++- + src/cryptonote_basic/CMakeLists.txt | 6 +++++- + src/cryptonote_basic/miner.cpp | 8 ++++---- + 4 files changed, 14 insertions(+), 10 deletions(-) + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index caa1d5e33..59d598edb 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -39,10 +39,6 @@ include(CheckLibraryExists) + include(CheckFunctionExists) + include(FindPythonInterp) + +-if (IOS) +- INCLUDE(CmakeLists_IOS.txt) +-endif() +- + cmake_minimum_required(VERSION 3.5) + message(STATUS "CMake version ${CMAKE_VERSION}") + +diff --git a/src/checkpoints/CMakeLists.txt b/src/checkpoints/CMakeLists.txt +index 665441f62..841df3256 100644 +--- a/src/checkpoints/CMakeLists.txt ++++ b/src/checkpoints/CMakeLists.txt +@@ -28,7 +28,11 @@ + + if(APPLE) + if(DEPENDS) +- list(APPEND EXTRA_LIBRARIES "-framework Foundation -framework ApplicationServices -framework AppKit -framework IOKit") ++ if(${CMAKE_SYSTEM_NAME} STREQUAL "iOS") ++ list(APPEND EXTRA_LIBRARIES "-framework Foundation -framework IOKit") ++ else() ++ list(APPEND EXTRA_LIBRARIES "-framework Foundation -framework ApplicationServices -framework AppKit -framework IOKit") ++ endif() + else() + find_library(IOKIT_LIBRARY IOKit) + mark_as_advanced(IOKIT_LIBRARY) +diff --git a/src/cryptonote_basic/CMakeLists.txt b/src/cryptonote_basic/CMakeLists.txt +index 414936a05..81c81767f 100644 +--- a/src/cryptonote_basic/CMakeLists.txt ++++ b/src/cryptonote_basic/CMakeLists.txt +@@ -28,7 +28,11 @@ + + if(APPLE) + if(DEPENDS) +- list(APPEND EXTRA_LIBRARIES "-framework Foundation -framework ApplicationServices -framework AppKit -framework IOKit") ++ if(${CMAKE_SYSTEM_NAME} STREQUAL "iOS") ++ list(APPEND EXTRA_LIBRARIES "-framework Foundation -framework IOKit") ++ else() ++ list(APPEND EXTRA_LIBRARIES "-framework Foundation -framework ApplicationServices -framework AppKit -framework IOKit") ++ endif() + else() + find_library(IOKIT_LIBRARY IOKit) + mark_as_advanced(IOKIT_LIBRARY) +diff --git a/src/cryptonote_basic/miner.cpp b/src/cryptonote_basic/miner.cpp +index 70e95d398..18563c3b3 100644 +--- a/src/cryptonote_basic/miner.cpp ++++ b/src/cryptonote_basic/miner.cpp +@@ -45,7 +45,7 @@ + #include "boost/logic/tribool.hpp" + #include + +-#ifdef __APPLE__ ++#if defined(__APPLE__) && !defined(TARGET_OS_IPHONE) + #include + #include + #include +@@ -973,7 +973,7 @@ namespace cryptonote + + return true; + +- #elif defined(__APPLE__) ++ #elif defined(__APPLE__) && !defined(TARGET_OS_IPHONE) + + mach_msg_type_number_t count; + kern_return_t status; +@@ -1039,7 +1039,7 @@ namespace cryptonote + return true; + } + +- #elif (defined(__linux__) && defined(_SC_CLK_TCK)) || defined(__APPLE__) || defined(__FreeBSD__) ++ #elif (defined(__linux__) && defined(_SC_CLK_TCK)) || (defined(__APPLE__) && !defined(TARGET_OS_IPHONE)) || defined(__FreeBSD__) + + struct tms tms; + if ( times(&tms) != (clock_t)-1 ) +@@ -1068,7 +1068,7 @@ namespace cryptonote + return boost::logic::tribool(power_status.ACLineStatus != 1); + } + +- #elif defined(__APPLE__) ++ #elif defined(__APPLE__) && !defined(TARGET_OS_IPHONE) + + #if TARGET_OS_MAC && (!defined(MAC_OS_X_VERSION_MIN_REQUIRED) || MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_7) + return boost::logic::tribool(IOPSGetTimeRemainingEstimate() != kIOPSTimeRemainingUnlimited); +-- +2.51.0 + diff --git a/patches/wownero/0014-fix-iOS-depends-build.patch b/patches/wownero/0014-fix-iOS-depends-build.patch deleted file mode 100644 index 5cb1c79..0000000 --- a/patches/wownero/0014-fix-iOS-depends-build.patch +++ /dev/null @@ -1,104 +0,0 @@ -From a707d5fc2aa9d387857381e7ebc4a68c1d245a00 Mon Sep 17 00:00:00 2001 -From: Czarek Nakamoto -Date: Thu, 21 Nov 2024 06:05:03 -0500 -Subject: [PATCH 14/15] fix iOS depends build - ---- - CMakeLists.txt | 4 ---- - src/checkpoints/CMakeLists.txt | 6 +++++- - src/cryptonote_basic/CMakeLists.txt | 6 +++++- - src/cryptonote_basic/miner.cpp | 8 ++++---- - 4 files changed, 14 insertions(+), 10 deletions(-) - -diff --git a/CMakeLists.txt b/CMakeLists.txt -index 1383e630a..b8782570d 100644 ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -39,10 +39,6 @@ include(CheckLibraryExists) - include(CheckFunctionExists) - include(FindPythonInterp) - --if (IOS) -- INCLUDE(CmakeLists_IOS.txt) --endif() -- - cmake_minimum_required(VERSION 3.5) - message(STATUS "CMake version ${CMAKE_VERSION}") - -diff --git a/src/checkpoints/CMakeLists.txt b/src/checkpoints/CMakeLists.txt -index 665441f62..841df3256 100644 ---- a/src/checkpoints/CMakeLists.txt -+++ b/src/checkpoints/CMakeLists.txt -@@ -28,7 +28,11 @@ - - if(APPLE) - if(DEPENDS) -- list(APPEND EXTRA_LIBRARIES "-framework Foundation -framework ApplicationServices -framework AppKit -framework IOKit") -+ if(${CMAKE_SYSTEM_NAME} STREQUAL "iOS") -+ list(APPEND EXTRA_LIBRARIES "-framework Foundation -framework IOKit") -+ else() -+ list(APPEND EXTRA_LIBRARIES "-framework Foundation -framework ApplicationServices -framework AppKit -framework IOKit") -+ endif() - else() - find_library(IOKIT_LIBRARY IOKit) - mark_as_advanced(IOKIT_LIBRARY) -diff --git a/src/cryptonote_basic/CMakeLists.txt b/src/cryptonote_basic/CMakeLists.txt -index 414936a05..81c81767f 100644 ---- a/src/cryptonote_basic/CMakeLists.txt -+++ b/src/cryptonote_basic/CMakeLists.txt -@@ -28,7 +28,11 @@ - - if(APPLE) - if(DEPENDS) -- list(APPEND EXTRA_LIBRARIES "-framework Foundation -framework ApplicationServices -framework AppKit -framework IOKit") -+ if(${CMAKE_SYSTEM_NAME} STREQUAL "iOS") -+ list(APPEND EXTRA_LIBRARIES "-framework Foundation -framework IOKit") -+ else() -+ list(APPEND EXTRA_LIBRARIES "-framework Foundation -framework ApplicationServices -framework AppKit -framework IOKit") -+ endif() - else() - find_library(IOKIT_LIBRARY IOKit) - mark_as_advanced(IOKIT_LIBRARY) -diff --git a/src/cryptonote_basic/miner.cpp b/src/cryptonote_basic/miner.cpp -index 83bea8b5b..dfb4b5c5a 100644 ---- a/src/cryptonote_basic/miner.cpp -+++ b/src/cryptonote_basic/miner.cpp -@@ -45,7 +45,7 @@ - #include "boost/logic/tribool.hpp" - #include - --#ifdef __APPLE__ -+#if defined(__APPLE__) && !defined(TARGET_OS_IPHONE) - #include - #include - #include -@@ -971,7 +971,7 @@ namespace cryptonote - - return true; - -- #elif defined(__APPLE__) -+ #elif defined(__APPLE__) && !defined(TARGET_OS_IPHONE) - - mach_msg_type_number_t count; - kern_return_t status; -@@ -1037,7 +1037,7 @@ namespace cryptonote - return true; - } - -- #elif (defined(__linux__) && defined(_SC_CLK_TCK)) || defined(__APPLE__) || defined(__FreeBSD__) -+ #elif (defined(__linux__) && defined(_SC_CLK_TCK)) || (defined(__APPLE__) && !defined(TARGET_OS_IPHONE)) || defined(__FreeBSD__) - - struct tms tms; - if ( times(&tms) != (clock_t)-1 ) -@@ -1066,7 +1066,7 @@ namespace cryptonote - return boost::logic::tribool(power_status.ACLineStatus != 1); - } - -- #elif defined(__APPLE__) -+ #elif defined(__APPLE__) && !defined(TARGET_OS_IPHONE) - - #if TARGET_OS_MAC && (!defined(MAC_OS_X_VERSION_MIN_REQUIRED) || MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_7) - return boost::logic::tribool(IOPSGetTimeRemainingEstimate() != kIOPSTimeRemainingUnlimited); --- -2.48.0 - diff --git a/patches/wownero/0014-include-locale-only-when-targeting-WIN32.patch b/patches/wownero/0014-include-locale-only-when-targeting-WIN32.patch new file mode 100644 index 0000000..913ee93 --- /dev/null +++ b/patches/wownero/0014-include-locale-only-when-targeting-WIN32.patch @@ -0,0 +1,43 @@ +From 54c9d7a6a6feb0196e638511dabc50e119038ca7 Mon Sep 17 00:00:00 2001 +From: Czarek Nakamoto +Date: Mon, 18 Nov 2024 10:57:37 -0500 +Subject: [PATCH 14/16] include locale only when targeting WIN32 + +--- + CMakeLists.txt | 5 ++++- + src/wallet/api/wallet.cpp | 2 ++ + 2 files changed, 6 insertions(+), 1 deletion(-) + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 59d598edb..a620e8a37 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -1091,7 +1091,10 @@ if(NOT Boost_FOUND) + elseif(Boost_FOUND) + message(STATUS "Found Boost Version: ${Boost_VERSION_STRING}") + +- set(BOOST_COMPONENTS filesystem thread date_time chrono serialization program_options locale) ++ if(WIN32) ++ set(BOOST_LOCALE locale) ++ endif() ++ set(BOOST_COMPONENTS filesystem thread date_time chrono serialization program_options ${BOOST_LOCALE}) + + # Boost System is header-only since 1.69 + if (Boost_VERSION_STRING VERSION_LESS 1.69.0) +diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp +index 690fbdce1..161bd33ef 100644 +--- a/src/wallet/api/wallet.cpp ++++ b/src/wallet/api/wallet.cpp +@@ -46,7 +46,9 @@ + #include + #include + ++#ifdef WIN32 + #include ++#endif + #include + #include "bc-ur/src/bc-ur.hpp" + #if defined(HIDAPI_DUMMY) && !defined(HAVE_HIDAPI) +-- +2.51.0 + diff --git a/patches/wownero/0015-change-earliest-fork-height-message.patch b/patches/wownero/0015-change-earliest-fork-height-message.patch new file mode 100644 index 0000000..a8daff4 --- /dev/null +++ b/patches/wownero/0015-change-earliest-fork-height-message.patch @@ -0,0 +1,25 @@ +From 1d52a04342d63795de45958d41477047e15f0ac2 Mon Sep 17 00:00:00 2001 +From: Czarek Nakamoto +Date: Wed, 29 Jan 2025 16:13:28 +0100 +Subject: [PATCH 15/16] change earliest fork height message + +--- + src/wallet/wallet2.cpp | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp +index f96de960a..888f862f7 100644 +--- a/src/wallet/wallet2.cpp ++++ b/src/wallet/wallet2.cpp +@@ -12351,7 +12351,7 @@ bool wallet2::use_fork_rules(uint8_t version, int64_t early_blocks) + boost::optional result = m_node_rpc_proxy.get_height(height); + THROW_WALLET_EXCEPTION_IF(result, error::wallet_internal_error, "Failed to get height"); + result = m_node_rpc_proxy.get_earliest_height(version, earliest_height); +- THROW_WALLET_EXCEPTION_IF(result, error::wallet_internal_error, "Failed to get earliest fork height"); ++ THROW_WALLET_EXCEPTION_IF(result, error::wallet_internal_error, "Failed to get earliest fork height. Please check your connection and/or switch node you are connected to currently."); + + bool close_enough = (int64_t)height >= (int64_t)earliest_height - early_blocks && earliest_height != std::numeric_limits::max(); // start using the rules that many blocks beforehand + if (close_enough) +-- +2.51.0 + diff --git a/patches/wownero/0015-include-locale-only-when-targeting-WIN32.patch b/patches/wownero/0015-include-locale-only-when-targeting-WIN32.patch deleted file mode 100644 index 27ec9bc..0000000 --- a/patches/wownero/0015-include-locale-only-when-targeting-WIN32.patch +++ /dev/null @@ -1,43 +0,0 @@ -From eeb059ab8bff7e8614531565259ca66f6e6a8c7d Mon Sep 17 00:00:00 2001 -From: Czarek Nakamoto -Date: Mon, 18 Nov 2024 10:57:37 -0500 -Subject: [PATCH 15/15] include locale only when targeting WIN32 - ---- - CMakeLists.txt | 5 ++++- - src/wallet/api/wallet.cpp | 2 ++ - 2 files changed, 6 insertions(+), 1 deletion(-) - -diff --git a/CMakeLists.txt b/CMakeLists.txt -index b8782570d..d1ed1de7b 100644 ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -1090,7 +1090,10 @@ if(NOT Boost_FOUND) - elseif(Boost_FOUND) - message(STATUS "Found Boost Version: ${Boost_VERSION_STRING}") - -- set(BOOST_COMPONENTS filesystem thread date_time chrono serialization program_options locale) -+ if(WIN32) -+ set(BOOST_LOCALE locale) -+ endif() -+ set(BOOST_COMPONENTS filesystem thread date_time chrono serialization program_options ${BOOST_LOCALE}) - - # Boost System is header-only since 1.69 - if (Boost_VERSION_STRING VERSION_LESS 1.69.0) -diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp -index e650e6044..6d7553a1d 100644 ---- a/src/wallet/api/wallet.cpp -+++ b/src/wallet/api/wallet.cpp -@@ -46,7 +46,9 @@ - #include - #include - -+#ifdef WIN32 - #include -+#endif - #include - #include "bc-ur/src/bc-ur.hpp" - #if defined(HIDAPI_DUMMY) && !defined(HAVE_HIDAPI) --- -2.48.0 - diff --git a/patches/wownero/0016-change-earliest-fork-height-message.patch b/patches/wownero/0016-change-earliest-fork-height-message.patch deleted file mode 100644 index 0a0bd2c..0000000 --- a/patches/wownero/0016-change-earliest-fork-height-message.patch +++ /dev/null @@ -1,25 +0,0 @@ -From d47284456194c6d99698b28908d753acf1a64010 Mon Sep 17 00:00:00 2001 -From: Czarek Nakamoto -Date: Wed, 29 Jan 2025 16:13:28 +0100 -Subject: [PATCH] change earliest fork height message - ---- - src/wallet/wallet2.cpp | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp -index d060bf95b..9c1574c02 100644 ---- a/src/wallet/wallet2.cpp -+++ b/src/wallet/wallet2.cpp -@@ -12354,7 +12354,7 @@ bool wallet2::use_fork_rules(uint8_t version, int64_t early_blocks) - boost::optional result = m_node_rpc_proxy.get_height(height); - THROW_WALLET_EXCEPTION_IF(result, error::wallet_internal_error, "Failed to get height"); - result = m_node_rpc_proxy.get_earliest_height(version, earliest_height); -- THROW_WALLET_EXCEPTION_IF(result, error::wallet_internal_error, "Failed to get earliest fork height"); -+ THROW_WALLET_EXCEPTION_IF(result, error::wallet_internal_error, "Failed to get earliest fork height. Please check your connection and/or switch node you are connected to currently."); - - bool close_enough = (int64_t)height >= (int64_t)earliest_height - early_blocks && earliest_height != std::numeric_limits::max(); // start using the rules that many blocks beforehand - if (close_enough) --- -2.48.1 - diff --git a/patches/wownero/0016-drop-generate_translations_header.c-requirement.patch b/patches/wownero/0016-drop-generate_translations_header.c-requirement.patch new file mode 100644 index 0000000..5b14436 --- /dev/null +++ b/patches/wownero/0016-drop-generate_translations_header.c-requirement.patch @@ -0,0 +1,132 @@ +From f7ccb8ea7471edb2c5eed8d29b39f7112da7a450 Mon Sep 17 00:00:00 2001 +From: Czarek Nakamoto +Date: Fri, 20 Feb 2026 08:03:01 +0100 +Subject: [PATCH 16/16] drop generate_translations_header.c requirement + +--- + CMakeLists.txt | 11 ++---- + translations/CMakeLists.txt | 79 +++++++++++++------------------------ + 2 files changed, 31 insertions(+), 59 deletions(-) + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index a620e8a37..6343a3066 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -71,7 +71,7 @@ if (${CMAKE_VERSION} VERSION_GREATER "3.0.0" AND CMAKE_MAKE_PROGRAM MATCHES "nin + set(CMAKE_JOB_POOL_LINK link_job_pool) + endif () + endif () +- ++ + option (USE_CLANG_TIDY_C "Lint the code with clang-tidy - variant C" OFF) + option (USE_CLANG_TIDY_CXX "Lint the code with clang-tidy - variant C++" OFF) + if (USE_CLANG_TIDY_C AND USE_CLANG_TIDY_CXX) +@@ -667,16 +667,11 @@ endfunction () + # Generate header for embedded translations + # Generate header for embedded translations, use target toolchain if depends, otherwise use the + # lrelease and lupdate binaries from the host +-include(ExternalProject) +-ExternalProject_Add(generate_translations_header +- SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/translations" +- BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/translations" +- STAMP_DIR ${LRELEASE_PATH} +- CMAKE_ARGS -DLRELEASE_PATH=${LRELEASE_PATH} +- INSTALL_COMMAND ${CMAKE_COMMAND} -E echo "") ++add_subdirectory(translations) + include_directories("${CMAKE_CURRENT_BINARY_DIR}/translations") + add_subdirectory(external) + ++ + # Final setup for libunbound + include_directories(${UNBOUND_INCLUDE_DIR}) + +diff --git a/translations/CMakeLists.txt b/translations/CMakeLists.txt +index 3b43360f8..d88a78ced 100644 +--- a/translations/CMakeLists.txt ++++ b/translations/CMakeLists.txt +@@ -30,54 +30,31 @@ cmake_minimum_required(VERSION 3.5) + + project(translations) + +-# when crosscompiling import the executable targets from a file +-IF(CMAKE_CROSSCOMPILING) +- message(WARNING "CrossCompiling") +- SET(IMPORT_EXECUTABLES "${CMAKE_CURRENT_BINARY_DIR}/ImportExecutables.cmake" CACHE FILEPATH "Point it to the export file from a native build") +- INCLUDE(${IMPORT_EXECUTABLES}) +-ENDIF(CMAKE_CROSSCOMPILING) +- +-# only build the generator if not crosscompiling +-IF(NOT CMAKE_CROSSCOMPILING) +- add_executable(generate_translations_header generate_translations_header.c) +-ENDIF(NOT CMAKE_CROSSCOMPILING) +- +-if(LRELEASE_PATH STREQUAL "") +- find_program(LRELEASE lrelease) +-else() +- set(LRELEASE ${LRELEASE_PATH}/lrelease) +-endif() +- +-if(LRELEASE STREQUAL "LRELEASE-NOTFOUND") +- set(ts_files "") +- message(WARNING "lrelease program not found, translation files not built") +-else() +- execute_process(COMMAND ${LRELEASE} -version +- RESULT_VARIABLE lrelease_ret) +- if(NOT lrelease_ret EQUAL "0") +- set(ts_files "") +- message(WARNING "lrelease program not working, translation files not built") +- else() +- file(GLOB ts_files RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" *.ts) +- foreach(ts_file ${ts_files}) +- string(REPLACE ".ts" ".qm" qm_file "${ts_file}") +- add_custom_command(TARGET generate_translations_header +- PRE_BUILD +- COMMAND ${LRELEASE} "${CMAKE_CURRENT_SOURCE_DIR}/${ts_file}" -qm "${qm_file}" +- WORKING_DIRECTORY "${CMAKE_CURRENT_BIN_DIR}") +- endforeach() +- endif() +-endif() +- +-string(REPLACE ".ts" ".qm" qm_files "${ts_files}") +- +-add_custom_command(TARGET generate_translations_header +- POST_BUILD +- COMMAND $ ${qm_files} +- WORKING_DIRECTORY "${CMAKE_CURRENT_BIN_DIR}" +- COMMENT "Generating embedded translations header") +- +-# export the generator target to a file, so it can be imported (see above) by another build +-IF(NOT CMAKE_CROSSCOMPILING) +- EXPORT(TARGETS generate_translations_header FILE ${CMAKE_CURRENT_BINARY_DIR}/ImportExecutables.cmake ) +-ENDIF(NOT CMAKE_CROSSCOMPILING) ++add_custom_target(generate_translations_header) ++ ++file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/translation_files.h" ++"#ifndef TRANSLATION_FILES_H ++#define TRANSLATION_FILES_H ++ ++#include ++ ++static const struct embedded_file { ++ const std::string *name; ++ const std::string *data; ++} embedded_files[] = { ++ {NULL, NULL} ++}; ++ ++static bool find_embedded_file(const std::string &name, std::string &data) { ++ const struct embedded_file *p; ++ for (p = embedded_files; p->name != NULL; p++) { ++ if (*p->name == name) { ++ data = *p->data; ++ return true; ++ } ++ } ++ return false; ++} ++ ++#endif /* TRANSLATION_FILES_H */ ++") +-- +2.51.0 + diff --git a/patches/wownero/0017-pr-9880.patch b/patches/wownero/0017-pr-9880.patch deleted file mode 100644 index dbf604f..0000000 --- a/patches/wownero/0017-pr-9880.patch +++ /dev/null @@ -1,25 +0,0 @@ -From 31ef09596a9d8d547905577823ff52d33e10a3d2 Mon Sep 17 00:00:00 2001 -From: Czarek Nakamoto -Date: Tue, 1 Apr 2025 11:30:45 +0200 -Subject: [PATCH] pr-9880 - ---- - CMakeLists.txt | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/CMakeLists.txt b/CMakeLists.txt -index 5938be6..1c47285 100644 ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -216,7 +216,7 @@ function(forbid_undefined_symbols) - file(MAKE_DIRECTORY "${TEST_PROJECT}") - file(WRITE "${TEST_PROJECT}/CMakeLists.txt" - [=[ --cmake_minimum_required(VERSION 3.1) -+cmake_minimum_required(VERSION 3.5) - project(test) - option(EXPECT_SUCCESS "" ON) - file(WRITE "${CMAKE_CURRENT_SOURCE_DIR}/incorrect_source.cpp" "void undefined_symbol(); void symbol() { undefined_symbol(); }") --- -2.49.0 - diff --git a/patches/wownero/0018-fix-unary_function-__unary_function.patch b/patches/wownero/0018-fix-unary_function-__unary_function.patch deleted file mode 100644 index 5fc385c..0000000 --- a/patches/wownero/0018-fix-unary_function-__unary_function.patch +++ /dev/null @@ -1,28 +0,0 @@ -From e488eaa7397d388cee6e914e10d23790f005f6f7 Mon Sep 17 00:00:00 2001 -From: Czarek Nakamoto -Date: Thu, 10 Apr 2025 13:28:06 +0200 -Subject: [PATCH] fix: unary_function -> __unary_function - ---- - src/cryptonote_basic/cryptonote_basic_impl.h | 4 ++++ - 1 file changed, 4 insertions(+) - -diff --git a/src/cryptonote_basic/cryptonote_basic_impl.h b/src/cryptonote_basic/cryptonote_basic_impl.h -index b423573..a9aef7a 100644 ---- a/src/cryptonote_basic/cryptonote_basic_impl.h -+++ b/src/cryptonote_basic/cryptonote_basic_impl.h -@@ -40,7 +40,11 @@ namespace cryptonote { - /* */ - /************************************************************************/ - template -+#ifdef __APPLE__ -+ struct array_hasher: std::__unary_function -+#else - struct array_hasher: std::unary_function -+#endif - { - std::size_t operator()(const t_array& val) const - { --- -2.49.0 - diff --git a/wownero b/wownero index 3e302be..cfd2a69 160000 --- a/wownero +++ b/wownero @@ -1 +1 @@ -Subproject commit 3e302be710f4e6b4f58642989c8e47711362fa56 +Subproject commit cfd2a696245be112a01936d1f2f41b6eb7558376 -- cgit v1.2.3