diff options
Diffstat (limited to 'src/wallet')
-rw-r--r-- | src/wallet/api/wallet.cpp | 26 | ||||
-rw-r--r-- | src/wallet/api/wallet.h | 4 | ||||
-rw-r--r-- | src/wallet/api/wallet2_api.h | 18 | ||||
-rw-r--r-- | src/wallet/api/wallet_manager.cpp | 25 | ||||
-rw-r--r-- | src/wallet/api/wallet_manager.h | 3 | ||||
-rw-r--r-- | src/wallet/message_store.cpp | 2 | ||||
-rw-r--r-- | src/wallet/message_store.h | 4 | ||||
-rw-r--r-- | src/wallet/message_transporter.cpp | 8 | ||||
-rw-r--r-- | src/wallet/message_transporter.h | 6 | ||||
-rw-r--r-- | src/wallet/node_rpc_proxy.cpp | 2 | ||||
-rw-r--r-- | src/wallet/node_rpc_proxy.h | 6 | ||||
-rw-r--r-- | src/wallet/ringdb.cpp | 60 | ||||
-rw-r--r-- | src/wallet/wallet2.cpp | 503 | ||||
-rw-r--r-- | src/wallet/wallet2.h | 54 | ||||
-rw-r--r-- | src/wallet/wallet_rpc_payments.cpp | 2 | ||||
-rw-r--r-- | src/wallet/wallet_rpc_server.cpp | 18 | ||||
-rw-r--r-- | src/wallet/wallet_rpc_server_commands_defs.h | 2 |
17 files changed, 505 insertions, 238 deletions
diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index 6200c7a1f..4612b0397 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -725,7 +725,7 @@ bool WalletImpl::recover(const std::string &path, const std::string &seed) return recover(path, "", seed); } -bool WalletImpl::recover(const std::string &path, const std::string &password, const std::string &seed) +bool WalletImpl::recover(const std::string &path, const std::string &password, const std::string &seed, const std::string &seed_offset/* = {}*/) { clearStatus(); m_errorString.clear(); @@ -743,6 +743,10 @@ bool WalletImpl::recover(const std::string &path, const std::string &password, c setStatusError(tr("Electrum-style word list failed verification")); return false; } + if (!seed_offset.empty()) + { + recovery_key = cryptonote::decrypt_key(recovery_key, seed_offset); + } if (old_language == crypto::ElectrumWords::old_language_name) old_language = Language::English().get_language_name(); @@ -1671,6 +1675,26 @@ void WalletImpl::disposeTransaction(PendingTransaction *t) delete t; } +uint64_t WalletImpl::estimateTransactionFee(const std::vector<std::pair<std::string, uint64_t>> &destinations, + PendingTransaction::Priority priority) const +{ + const size_t pubkey_size = 33; + const size_t encrypted_paymentid_size = 11; + const size_t extra_size = pubkey_size + encrypted_paymentid_size; + + return m_wallet->estimate_fee( + m_wallet->use_fork_rules(HF_VERSION_PER_BYTE_FEE, 0), + m_wallet->use_fork_rules(4, 0), + 1, + m_wallet->get_min_ring_size() - 1, + destinations.size() + 1, + extra_size, + m_wallet->use_fork_rules(8, 0), + m_wallet->get_base_fee(), + m_wallet->get_fee_multiplier(m_wallet->adjust_priority(static_cast<uint32_t>(priority))), + m_wallet->get_fee_quantization_mask()); +} + TransactionHistory *WalletImpl::history() { return m_history.get(); diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h index 331bf4b38..66eeb0e73 100644 --- a/src/wallet/api/wallet.h +++ b/src/wallet/api/wallet.h @@ -60,7 +60,7 @@ public: const std::string &language) const override; bool open(const std::string &path, const std::string &password); bool recover(const std::string &path,const std::string &password, - const std::string &seed); + const std::string &seed, const std::string &seed_offset = {}); bool recoverFromKeysWithPassword(const std::string &path, const std::string &password, const std::string &language, @@ -166,6 +166,8 @@ public: bool importKeyImages(const std::string &filename) override; virtual void disposeTransaction(PendingTransaction * t) override; + virtual uint64_t estimateTransactionFee(const std::vector<std::pair<std::string, uint64_t>> &destinations, + PendingTransaction::Priority priority) const override; virtual TransactionHistory * history() override; virtual AddressBook * addressBook() override; virtual Subaddress * subaddress() override; diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h index e543a115b..6309724a4 100644 --- a/src/wallet/api/wallet2_api.h +++ b/src/wallet/api/wallet2_api.h @@ -879,6 +879,14 @@ struct Wallet */ virtual void disposeTransaction(PendingTransaction * t) = 0; + /*! + * \brief Estimates transaction fee. + * \param destinations Vector consisting of <address, amount> pairs. + * \return Estimated fee. + */ + virtual uint64_t estimateTransactionFee(const std::vector<std::pair<std::string, uint64_t>> &destinations, + PendingTransaction::Priority priority) const = 0; + /*! * \brief exportKeyImages - exports key images to file * \param filename @@ -1085,10 +1093,12 @@ struct WalletManager * \param nettype Network type * \param restoreHeight restore from start height * \param kdf_rounds Number of rounds for key derivation function + * \param seed_offset Seed offset passphrase (optional) * \return Wallet instance (Wallet::status() needs to be called to check if recovered successfully) */ virtual Wallet * recoveryWallet(const std::string &path, const std::string &password, const std::string &mnemonic, - NetworkType nettype = MAINNET, uint64_t restoreHeight = 0, uint64_t kdf_rounds = 1) = 0; + NetworkType nettype = MAINNET, uint64_t restoreHeight = 0, uint64_t kdf_rounds = 1, + const std::string &seed_offset = {}) = 0; Wallet * recoveryWallet(const std::string &path, const std::string &password, const std::string &mnemonic, bool testnet = false, uint64_t restoreHeight = 0) // deprecated { @@ -1283,7 +1293,11 @@ struct WalletManager virtual std::string resolveOpenAlias(const std::string &address, bool &dnssec_valid) const = 0; //! checks for an update and returns version, hash and url - static std::tuple<bool, std::string, std::string, std::string, std::string> checkUpdates(const std::string &software, std::string subdir); + static std::tuple<bool, std::string, std::string, std::string, std::string> checkUpdates( + const std::string &software, + std::string subdir, + const char *buildtag = nullptr, + const char *current_version = nullptr); }; diff --git a/src/wallet/api/wallet_manager.cpp b/src/wallet/api/wallet_manager.cpp index d589dcc75..8d7541cea 100644 --- a/src/wallet/api/wallet_manager.cpp +++ b/src/wallet/api/wallet_manager.cpp @@ -93,13 +93,14 @@ Wallet *WalletManagerImpl::recoveryWallet(const std::string &path, const std::string &mnemonic, NetworkType nettype, uint64_t restoreHeight, - uint64_t kdf_rounds) + uint64_t kdf_rounds, + const std::string &seed_offset/* = {}*/) { WalletImpl * wallet = new WalletImpl(nettype, kdf_rounds); if(restoreHeight > 0){ wallet->setRefreshFromBlockHeight(restoreHeight); } - wallet->recover(path, password, mnemonic); + wallet->recover(path, password, mnemonic, seed_offset); return wallet; } @@ -341,22 +342,30 @@ std::string WalletManagerImpl::resolveOpenAlias(const std::string &address, bool return addresses.front(); } -std::tuple<bool, std::string, std::string, std::string, std::string> WalletManager::checkUpdates(const std::string &software, std::string subdir) +std::tuple<bool, std::string, std::string, std::string, std::string> WalletManager::checkUpdates( + const std::string &software, + std::string subdir, + const char *buildtag/* = nullptr*/, + const char *current_version/* = nullptr*/) { + if (buildtag == nullptr) + { #ifdef BUILD_TAG - static const char buildtag[] = BOOST_PP_STRINGIZE(BUILD_TAG); + static const char buildtag_default[] = BOOST_PP_STRINGIZE(BUILD_TAG); #else - static const char buildtag[] = "source"; - // Override the subdir string when built from source - subdir = "source"; + static const char buildtag_default[] = "source"; + // Override the subdir string when built from source + subdir = "source"; #endif + buildtag = buildtag_default; + } std::string version, hash; MDEBUG("Checking for a new " << software << " version for " << buildtag); if (!tools::check_updates(software, buildtag, version, hash)) return std::make_tuple(false, "", "", "", ""); - if (tools::vercmp(version.c_str(), MONERO_VERSION) > 0) + if (tools::vercmp(version.c_str(), current_version != nullptr ? current_version : MONERO_VERSION) > 0) { std::string user_url = tools::get_update_url(software, subdir, buildtag, version, true); std::string auto_url = tools::get_update_url(software, subdir, buildtag, version, false); diff --git a/src/wallet/api/wallet_manager.h b/src/wallet/api/wallet_manager.h index 537fc5ba6..0595b8327 100644 --- a/src/wallet/api/wallet_manager.h +++ b/src/wallet/api/wallet_manager.h @@ -46,7 +46,8 @@ public: const std::string &mnemonic, NetworkType nettype, uint64_t restoreHeight, - uint64_t kdf_rounds = 1) override; + uint64_t kdf_rounds = 1, + const std::string &seed_offset = {}) override; virtual Wallet * createWalletFromKeys(const std::string &path, const std::string &password, const std::string &language, diff --git a/src/wallet/message_store.cpp b/src/wallet/message_store.cpp index 6e2cb933f..1bd462ef5 100644 --- a/src/wallet/message_store.cpp +++ b/src/wallet/message_store.cpp @@ -48,7 +48,7 @@ namespace mms { -message_store::message_store() +message_store::message_store(std::unique_ptr<epee::net_utils::http::abstract_http_client> http_client) : m_transporter(std::move(http_client)) { m_active = false; m_auto_send = false; diff --git a/src/wallet/message_store.h b/src/wallet/message_store.h index 637bd29a1..d40daf186 100644 --- a/src/wallet/message_store.h +++ b/src/wallet/message_store.h @@ -43,6 +43,7 @@ #include "common/i18n.h" #include "common/command_line.h" #include "wipeable_string.h" +#include "net/abstract_http_client.h" #include "message_transporter.h" #undef MONERO_DEFAULT_LOG_CATEGORY @@ -202,7 +203,8 @@ namespace mms class message_store { public: - message_store(); + message_store(std::unique_ptr<epee::net_utils::http::abstract_http_client> http_client); + // Initialize and start to use the MMS, set the first signer, this wallet itself // Filename, if not null and not empty, is used to create the ".mms" file // reset it if already used, with deletion of all signers and messages diff --git a/src/wallet/message_transporter.cpp b/src/wallet/message_transporter.cpp index cf9b45b37..4dd4b8f01 100644 --- a/src/wallet/message_transporter.cpp +++ b/src/wallet/message_transporter.cpp @@ -80,7 +80,7 @@ namespace bitmessage_rpc } -message_transporter::message_transporter() +message_transporter::message_transporter(std::unique_ptr<epee::net_utils::http::abstract_http_client> http_client) : m_http_client(std::move(http_client)) { m_run = true; } @@ -96,7 +96,7 @@ void message_transporter::set_options(const std::string &bitmessage_address, con } m_bitmessage_login = bitmessage_login; - m_http_client.set_server(address_parts.host, std::to_string(address_parts.port), boost::none); + m_http_client->set_server(address_parts.host, std::to_string(address_parts.port), boost::none); } bool message_transporter::receive_messages(const std::vector<std::string> &destination_transport_addresses, @@ -256,7 +256,7 @@ bool message_transporter::post_request(const std::string &request, std::string & additional_params.push_back(std::make_pair("Content-Type", "application/xml; charset=utf-8")); const epee::net_utils::http::http_response_info* response = NULL; std::chrono::milliseconds timeout = std::chrono::seconds(15); - bool r = m_http_client.invoke("/", "POST", request, timeout, std::addressof(response), std::move(additional_params)); + bool r = m_http_client->invoke("/", "POST", request, timeout, std::addressof(response), std::move(additional_params)); if (r) { answer = response->m_body; @@ -266,7 +266,7 @@ bool message_transporter::post_request(const std::string &request, std::string & LOG_ERROR("POST request to Bitmessage failed: " << request.substr(0, 300)); THROW_WALLET_EXCEPTION(tools::error::no_connection_to_bitmessage, m_bitmessage_url); } - m_http_client.disconnect(); // see comment above + m_http_client->disconnect(); // see comment above std::string string_value = get_str_between_tags(answer, "<string>", "</string>"); if ((string_value.find("API Error") == 0) || (string_value.find("RPC ") == 0)) { diff --git a/src/wallet/message_transporter.h b/src/wallet/message_transporter.h index 28c099d87..84a2e9bae 100644 --- a/src/wallet/message_transporter.h +++ b/src/wallet/message_transporter.h @@ -34,9 +34,9 @@ #include "cryptonote_basic/cryptonote_basic.h" #include "net/http_server_impl_base.h" #include "net/http_client.h" +#include "net/abstract_http_client.h" #include "common/util.h" #include "wipeable_string.h" -#include "serialization/keyvalue_serialization.h" #include <vector> namespace mms @@ -83,7 +83,7 @@ typedef epee::misc_utils::struct_init<transport_message_t> transport_message; class message_transporter { public: - message_transporter(); + message_transporter(std::unique_ptr<epee::net_utils::http::abstract_http_client> http_client); void set_options(const std::string &bitmessage_address, const epee::wipeable_string &bitmessage_login); bool send_message(const transport_message &message); bool receive_messages(const std::vector<std::string> &destination_transport_addresses, @@ -94,7 +94,7 @@ public: bool delete_transport_address(const std::string &transport_address); private: - epee::net_utils::http::http_simple_client m_http_client; + const std::unique_ptr<epee::net_utils::http::abstract_http_client> m_http_client; std::string m_bitmessage_url; epee::wipeable_string m_bitmessage_login; std::atomic<bool> m_run; diff --git a/src/wallet/node_rpc_proxy.cpp b/src/wallet/node_rpc_proxy.cpp index f3698b599..873c2ee51 100644 --- a/src/wallet/node_rpc_proxy.cpp +++ b/src/wallet/node_rpc_proxy.cpp @@ -51,7 +51,7 @@ namespace tools static const std::chrono::seconds rpc_timeout = std::chrono::minutes(3) + std::chrono::seconds(30); -NodeRPCProxy::NodeRPCProxy(epee::net_utils::http::http_simple_client &http_client, rpc_payment_state_t &rpc_payment_state, boost::recursive_mutex &mutex) +NodeRPCProxy::NodeRPCProxy(epee::net_utils::http::abstract_http_client &http_client, rpc_payment_state_t &rpc_payment_state, boost::recursive_mutex &mutex) : m_http_client(http_client) , m_rpc_payment_state(rpc_payment_state) , m_daemon_rpc_mutex(mutex) diff --git a/src/wallet/node_rpc_proxy.h b/src/wallet/node_rpc_proxy.h index 65ca40640..b053659e9 100644 --- a/src/wallet/node_rpc_proxy.h +++ b/src/wallet/node_rpc_proxy.h @@ -31,7 +31,7 @@ #include <string> #include <boost/thread/mutex.hpp> #include "include_base_utils.h" -#include "net/http_client.h" +#include "net/abstract_http_client.h" #include "rpc/core_rpc_server_commands_defs.h" #include "wallet_rpc_helpers.h" @@ -41,7 +41,7 @@ namespace tools class NodeRPCProxy { public: - NodeRPCProxy(epee::net_utils::http::http_simple_client &http_client, rpc_payment_state_t &rpc_payment_state, boost::recursive_mutex &mutex); + NodeRPCProxy(epee::net_utils::http::abstract_http_client &http_client, rpc_payment_state_t &rpc_payment_state, boost::recursive_mutex &mutex); void set_client_secret_key(const crypto::secret_key &skey) { m_client_id_secret_key = skey; } void invalidate(); @@ -72,7 +72,7 @@ private: private: boost::optional<std::string> get_info(); - epee::net_utils::http::http_simple_client &m_http_client; + epee::net_utils::http::abstract_http_client &m_http_client; rpc_payment_state_t &m_rpc_payment_state; boost::recursive_mutex &m_daemon_rpc_mutex; crypto::secret_key m_client_id_secret_key; diff --git a/src/wallet/ringdb.cpp b/src/wallet/ringdb.cpp index b7efdd75c..dfeb987ca 100644 --- a/src/wallet/ringdb.cpp +++ b/src/wallet/ringdb.cpp @@ -35,10 +35,13 @@ #include "misc_language.h" #include "wallet_errors.h" #include "ringdb.h" +#include "cryptonote_config.h" #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "wallet.ringdb" +#define V1TAG ((uint64_t)798237759845202) + static const char zerokey[8] = {0}; static const MDB_val zerokeyval = { sizeof(zerokey), (void *)zerokey }; @@ -63,15 +66,16 @@ static int compare_uint64(const MDB_val *a, const MDB_val *b) return va < vb ? -1 : va > vb; } -static std::string compress_ring(const std::vector<uint64_t> &ring) +static std::string compress_ring(const std::vector<uint64_t> &ring, uint64_t tag) { std::string s; + s += tools::get_varint_data(tag); for (uint64_t out: ring) s += tools::get_varint_data(out); return s; } -static std::vector<uint64_t> decompress_ring(const std::string &s) +static std::vector<uint64_t> decompress_ring(const std::string &s, uint64_t tag) { std::vector<uint64_t> ring; int read = 0; @@ -81,6 +85,13 @@ static std::vector<uint64_t> decompress_ring(const std::string &s) std::string tmp(i, s.cend()); read = tools::read_varint(tmp.begin(), tmp.end(), out); THROW_WALLET_EXCEPTION_IF(read <= 0 || read > 256, tools::error::wallet_internal_error, "Internal error decompressing ring"); + if (tag) + { + if (tag != out) + return {}; + tag = 0; + continue; + } ring.push_back(out); } return ring; @@ -93,25 +104,25 @@ std::string get_rings_filename(boost::filesystem::path filename) return filename.string(); } -static crypto::chacha_iv make_iv(const crypto::key_image &key_image, const crypto::chacha_key &key) +static crypto::chacha_iv make_iv(const crypto::key_image &key_image, const crypto::chacha_key &key, uint8_t field) { - static const char salt[] = "ringdsb"; - - uint8_t buffer[sizeof(key_image) + sizeof(key) + sizeof(salt)]; + uint8_t buffer[sizeof(key_image) + sizeof(key) + sizeof(config::HASH_KEY_RINGDB) + sizeof(field)]; memcpy(buffer, &key_image, sizeof(key_image)); memcpy(buffer + sizeof(key_image), &key, sizeof(key)); - memcpy(buffer + sizeof(key_image) + sizeof(key), salt, sizeof(salt)); + memcpy(buffer + sizeof(key_image) + sizeof(key), config::HASH_KEY_RINGDB, sizeof(config::HASH_KEY_RINGDB)); + memcpy(buffer + sizeof(key_image) + sizeof(key) + sizeof(config::HASH_KEY_RINGDB), &field, sizeof(field)); crypto::hash hash; - crypto::cn_fast_hash(buffer, sizeof(buffer), hash.data); + // if field is 0, backward compat mode: hash without the field + crypto::cn_fast_hash(buffer, sizeof(buffer) - !field, hash.data); static_assert(sizeof(hash) >= CHACHA_IV_SIZE, "Incompatible hash and chacha IV sizes"); crypto::chacha_iv iv; memcpy(&iv, &hash, CHACHA_IV_SIZE); return iv; } -static std::string encrypt(const std::string &plaintext, const crypto::key_image &key_image, const crypto::chacha_key &key) +static std::string encrypt(const std::string &plaintext, const crypto::key_image &key_image, const crypto::chacha_key &key, uint8_t field) { - const crypto::chacha_iv iv = make_iv(key_image, key); + const crypto::chacha_iv iv = make_iv(key_image, key, field); std::string ciphertext; ciphertext.resize(plaintext.size() + sizeof(iv)); crypto::chacha20(plaintext.data(), plaintext.size(), key, iv, &ciphertext[sizeof(iv)]); @@ -119,14 +130,14 @@ static std::string encrypt(const std::string &plaintext, const crypto::key_image return ciphertext; } -static std::string encrypt(const crypto::key_image &key_image, const crypto::chacha_key &key) +static std::string encrypt(const crypto::key_image &key_image, const crypto::chacha_key &key, uint8_t field) { - return encrypt(std::string((const char*)&key_image, sizeof(key_image)), key_image, key); + return encrypt(std::string((const char*)&key_image, sizeof(key_image)), key_image, key, field); } -static std::string decrypt(const std::string &ciphertext, const crypto::key_image &key_image, const crypto::chacha_key &key) +static std::string decrypt(const std::string &ciphertext, const crypto::key_image &key_image, const crypto::chacha_key &key, uint8_t field) { - const crypto::chacha_iv iv = make_iv(key_image, key); + const crypto::chacha_iv iv = make_iv(key_image, key, field); std::string plaintext; THROW_WALLET_EXCEPTION_IF(ciphertext.size() < sizeof(iv), tools::error::wallet_internal_error, "Bad ciphertext text"); plaintext.resize(ciphertext.size() - sizeof(iv)); @@ -137,11 +148,11 @@ static std::string decrypt(const std::string &ciphertext, const crypto::key_imag static void store_relative_ring(MDB_txn *txn, MDB_dbi &dbi, const crypto::key_image &key_image, const std::vector<uint64_t> &relative_ring, const crypto::chacha_key &chacha_key) { MDB_val key, data; - std::string key_ciphertext = encrypt(key_image, chacha_key); + std::string key_ciphertext = encrypt(key_image, chacha_key, 0); key.mv_data = (void*)key_ciphertext.data(); key.mv_size = key_ciphertext.size(); - std::string compressed_ring = compress_ring(relative_ring); - std::string data_ciphertext = encrypt(compressed_ring, key_image, chacha_key); + std::string compressed_ring = compress_ring(relative_ring, V1TAG); + std::string data_ciphertext = encrypt(compressed_ring, key_image, chacha_key, 1); data.mv_size = data_ciphertext.size(); data.mv_data = (void*)data_ciphertext.c_str(); int dbr = mdb_put(txn, dbi, &key, &data, 0); @@ -297,7 +308,7 @@ bool ringdb::remove_rings(const crypto::chacha_key &chacha_key, const std::vecto for (const crypto::key_image &key_image: key_images) { MDB_val key, data; - std::string key_ciphertext = encrypt(key_image, chacha_key); + std::string key_ciphertext = encrypt(key_image, chacha_key, 0); key.mv_data = (void*)key_ciphertext.data(); key.mv_size = key_ciphertext.size(); @@ -349,7 +360,7 @@ bool ringdb::get_ring(const crypto::chacha_key &chacha_key, const crypto::key_im tx_active = true; MDB_val key, data; - std::string key_ciphertext = encrypt(key_image, chacha_key); + std::string key_ciphertext = encrypt(key_image, chacha_key, 0); key.mv_data = (void*)key_ciphertext.data(); key.mv_size = key_ciphertext.size(); dbr = mdb_get(txn, dbi_rings, &key, &data); @@ -358,8 +369,15 @@ bool ringdb::get_ring(const crypto::chacha_key &chacha_key, const crypto::key_im return false; THROW_WALLET_EXCEPTION_IF(data.mv_size <= 0, tools::error::wallet_internal_error, "Invalid ring data size"); - std::string data_plaintext = decrypt(std::string((const char*)data.mv_data, data.mv_size), key_image, chacha_key); - outs = decompress_ring(data_plaintext); + bool try_v0 = false; + std::string data_plaintext = decrypt(std::string((const char*)data.mv_data, data.mv_size), key_image, chacha_key, 1); + try { outs = decompress_ring(data_plaintext, V1TAG); if (outs.empty()) try_v0 = true; } + catch(...) { try_v0 = true; } + if (try_v0) + { + data_plaintext = decrypt(std::string((const char*)data.mv_data, data.mv_size), key_image, chacha_key, 0); + outs = decompress_ring(data_plaintext, 0); + } MDEBUG("Found ring for key image " << key_image << ":"); MDEBUG("Relative: " << boost::join(outs | boost::adaptors::transformed([](uint64_t out){return std::to_string(out);}), " ")); outs = cryptonote::relative_output_offsets_to_absolute(outs); diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 6a622d953..4220f18be 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -44,6 +44,7 @@ using namespace epee; #include "cryptonote_config.h" +#include "cryptonote_core/tx_sanity_check.h" #include "wallet_rpc_helpers.h" #include "wallet2.h" #include "cryptonote_basic/cryptonote_format_utils.h" @@ -101,10 +102,6 @@ using namespace cryptonote; // used to target a given block weight (additional outputs may be added on top to build fee) #define TX_WEIGHT_TARGET(bytes) (bytes*2/3) -// arbitrary, used to generate different hashes from the same input -#define CHACHA8_KEY_TAIL 0x8c -#define CACHE_KEY_TAIL 0x8d - #define UNSIGNED_TX_PREFIX "Monero unsigned tx set\004" #define SIGNED_TX_PREFIX "Monero signed tx set\004" #define MULTISIG_UNSIGNED_TX_PREFIX "Monero multisig unsigned tx set\001" @@ -149,6 +146,9 @@ static const std::string MULTISIG_EXTRA_INFO_MAGIC = "MultisigxV1"; static const std::string ASCII_OUTPUT_MAGIC = "MoneroAsciiDataV1"; +boost::mutex tools::wallet2::default_daemon_address_lock; +std::string tools::wallet2::default_daemon_address = ""; + namespace { std::string get_default_ringdb_path() @@ -236,8 +236,6 @@ namespace add_reason(reason, "overspend"); if (res.fee_too_low) add_reason(reason, "fee too low"); - if (res.not_rct) - add_reason(reason, "tx is not ringct"); if (res.sanity_check_failed) add_reason(reason, "tx sanity check failed"); if (res.not_relayed) @@ -357,7 +355,7 @@ std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variabl else if (!daemon_ssl_ca_file.empty() || !daemon_ssl_allowed_fingerprints.empty()) { std::vector<std::vector<uint8_t>> ssl_allowed_fingerprints{ daemon_ssl_allowed_fingerprints.size() }; - std::transform(daemon_ssl_allowed_fingerprints.begin(), daemon_ssl_allowed_fingerprints.end(), ssl_allowed_fingerprints.begin(), epee::from_hex::vector); + std::transform(daemon_ssl_allowed_fingerprints.begin(), daemon_ssl_allowed_fingerprints.end(), ssl_allowed_fingerprints.begin(), epee::from_hex_locale::to_vector); for (const auto &fpr: ssl_allowed_fingerprints) { THROW_WALLET_EXCEPTION_IF(fpr.size() != SSL_FINGERPRINT_SIZE, tools::error::wallet_internal_error, @@ -412,6 +410,15 @@ std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variabl daemon_port = get_config(nettype).RPC_DEFAULT_PORT; } + // if no daemon settings are given and we have a previous one, reuse that one + if (command_line::is_arg_defaulted(vm, opts.daemon_host) && command_line::is_arg_defaulted(vm, opts.daemon_port) && command_line::is_arg_defaulted(vm, opts.daemon_address)) + { + // not a bug: taking a const ref to a temporary in this way is actually ok in a recent C++ standard + const std::string &def = tools::wallet2::get_default_daemon_address(); + if (!def.empty()) + daemon_address = def; + } + if (daemon_address.empty()) daemon_address = std::string("http://") + daemon_host + ":" + std::to_string(daemon_port); @@ -428,6 +435,7 @@ std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variabl verification_required && !ssl_options.has_strong_verification(real_daemon), tools::error::wallet_internal_error, tools::wallet2::tr("Enabling --") + std::string{use_proxy ? opts.proxy.name : opts.daemon_ssl.name} + tools::wallet2::tr(" requires --") + + opts.daemon_ssl_allow_any_cert.name + tools::wallet2::tr(" or --") + opts.daemon_ssl_ca_certificates.name + tools::wallet2::tr(" or --") + opts.daemon_ssl_allowed_fingerprints.name + tools::wallet2::tr(" or use of a .onion/.i2p domain") ); } @@ -591,6 +599,8 @@ std::pair<std::unique_ptr<tools::wallet2>, tools::password_container> generate_f } viewkey = *reinterpret_cast<const crypto::secret_key*>(viewkey_data.data()); crypto::public_key pkey; + if (viewkey == crypto::null_skey) + THROW_WALLET_EXCEPTION(tools::error::wallet_internal_error, tools::wallet2::tr("view secret key may not be all zeroes")); if (!crypto::secret_key_to_public_key(viewkey, pkey)) { THROW_WALLET_EXCEPTION(tools::error::wallet_internal_error, tools::wallet2::tr("failed to verify view key secret key")); } @@ -607,6 +617,8 @@ std::pair<std::unique_ptr<tools::wallet2>, tools::password_container> generate_f } spendkey = *reinterpret_cast<const crypto::secret_key*>(spendkey_data.data()); crypto::public_key pkey; + if (spendkey == crypto::null_skey) + THROW_WALLET_EXCEPTION(tools::error::wallet_internal_error, tools::wallet2::tr("spend secret key may not be all zeroes")); if (!crypto::secret_key_to_public_key(spendkey, pkey)) { THROW_WALLET_EXCEPTION(tools::error::wallet_internal_error, tools::wallet2::tr("failed to verify spend key secret key")); } @@ -879,20 +891,6 @@ uint8_t get_bulletproof_fork() return 8; } -uint64_t estimate_fee(bool use_per_byte_fee, bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, uint64_t base_fee, uint64_t fee_multiplier, uint64_t fee_quantization_mask) -{ - if (use_per_byte_fee) - { - const size_t estimated_tx_weight = estimate_tx_weight(use_rct, n_inputs, mixin, n_outputs, extra_size, bulletproof); - return calculate_fee_from_weight(base_fee, estimated_tx_weight, fee_multiplier, fee_quantization_mask); - } - else - { - const size_t estimated_tx_size = estimate_tx_size(use_rct, n_inputs, mixin, n_outputs, extra_size, bulletproof); - return calculate_fee(base_fee, estimated_tx_size, fee_multiplier); - } -} - uint64_t calculate_fee(bool use_per_byte_fee, const cryptonote::transaction &tx, size_t blob_size, uint64_t base_fee, uint64_t fee_multiplier, uint64_t fee_quantization_mask) { if (use_per_byte_fee) @@ -1124,7 +1122,8 @@ void wallet_device_callback::on_progress(const hw::device_progress& event) wallet->on_device_progress(event); } -wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended): +wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended, std::unique_ptr<epee::net_utils::http::http_client_factory> http_client_factory): + m_http_client(std::move(http_client_factory->create())), m_multisig_rescan_info(NULL), m_multisig_rescan_k(NULL), m_upper_transaction_weight_limit(0), @@ -1169,7 +1168,7 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended): m_watch_only(false), m_multisig(false), m_multisig_threshold(0), - m_node_rpc_proxy(m_http_client, m_rpc_payment_state, m_daemon_rpc_mutex), + m_node_rpc_proxy(*m_http_client, m_rpc_payment_state, m_daemon_rpc_mutex), m_account_public_address{crypto::null_pkey, crypto::null_pkey}, m_subaddress_lookahead_major(SUBADDRESS_LOOKAHEAD_MAJOR), m_subaddress_lookahead_minor(SUBADDRESS_LOOKAHEAD_MINOR), @@ -1180,12 +1179,13 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended): m_light_wallet_balance(0), m_light_wallet_unlocked_balance(0), m_original_keys_available(false), - m_message_store(), + m_message_store(http_client_factory->create()), m_key_device_type(hw::device::device_type::SOFTWARE), m_ring_history_saved(false), m_ringdb(), m_last_block_reward(0), m_encrypt_keys_after_refresh(boost::none), + m_decrypt_keys_lockers(0), m_unattended(unattended), m_devices_registered(false), m_device_last_key_image_sync(0), @@ -1299,8 +1299,8 @@ bool wallet2::set_daemon(std::string daemon_address, boost::optional<epee::net_u { boost::lock_guard<boost::recursive_mutex> lock(m_daemon_rpc_mutex); - if(m_http_client.is_connected()) - m_http_client.disconnect(); + if(m_http_client->is_connected()) + m_http_client->disconnect(); const bool changed = m_daemon_address != daemon_address; m_daemon_address = std::move(daemon_address); m_daemon_login = std::move(daemon_login); @@ -1312,8 +1312,15 @@ bool wallet2::set_daemon(std::string daemon_address, boost::optional<epee::net_u m_node_rpc_proxy.invalidate(); } - MINFO("setting daemon to " << get_daemon_address()); - return m_http_client.set_server(get_daemon_address(), get_daemon_login(), std::move(ssl_options)); + const std::string address = get_daemon_address(); + MINFO("setting daemon to " << address); + bool ret = m_http_client->set_server(address, get_daemon_login(), std::move(ssl_options)); + if (ret) + { + CRITICAL_REGION_LOCAL(default_daemon_address_lock); + default_daemon_address = address; + } + return ret; } //---------------------------------------------------------------------------------------------------- bool wallet2::init(std::string daemon_address, boost::optional<epee::net_utils::http::login> daemon_login, boost::asio::ip::tcp::endpoint proxy, uint64_t upper_transaction_weight_limit, bool trusted_daemon, epee::net_utils::ssl_options_t ssl_options) @@ -1322,7 +1329,12 @@ bool wallet2::init(std::string daemon_address, boost::optional<epee::net_utils:: m_is_initialized = true; m_upper_transaction_weight_limit = upper_transaction_weight_limit; if (proxy != boost::asio::ip::tcp::endpoint{}) - m_http_client.set_connector(net::socks::connector{std::move(proxy)}); + { + epee::net_utils::http::abstract_http_client* abstract_http_client = m_http_client.get(); + epee::net_utils::http::http_simple_client* http_simple_client = dynamic_cast<epee::net_utils::http::http_simple_client*>(abstract_http_client); + CHECK_AND_ASSERT_MES(http_simple_client != nullptr, false, "http_simple_client must be used to set proxy"); + http_simple_client->set_connector(net::socks::connector{std::move(proxy)}); + } return set_daemon(daemon_address, daemon_login, trusted_daemon, std::move(ssl_options)); } //---------------------------------------------------------------------------------------------------- @@ -1547,6 +1559,12 @@ void wallet2::expand_subaddresses(const cryptonote::subaddress_index& index) } } //---------------------------------------------------------------------------------------------------- +void wallet2::create_one_off_subaddress(const cryptonote::subaddress_index& index) +{ + const crypto::public_key pkey = get_subaddress_spend_public_key(index); + m_subaddresses[pkey] = index; +} +//---------------------------------------------------------------------------------------------------- std::string wallet2::get_subaddress_label(const cryptonote::subaddress_index& index) const { if (index.major >= m_subaddress_labels.size() || index.minor >= m_subaddress_labels[index.major].size()) @@ -2088,7 +2106,8 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote td.m_amount = amount; td.m_pk_index = pk_index - 1; td.m_subaddr_index = tx_scan_info[o].received->index; - expand_subaddresses(tx_scan_info[o].received->index); + if (tx_scan_info[o].received->index.major < m_subaddress_labels.size() && tx_scan_info[o].received->index.minor < m_subaddress_labels[tx_scan_info[o].received->index.major].size()) + expand_subaddresses(tx_scan_info[o].received->index); if (tx.vout[o].amount == 0) { td.m_mask = tx_scan_info[o].mask; @@ -2166,7 +2185,8 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote td.m_amount = amount; td.m_pk_index = pk_index - 1; td.m_subaddr_index = tx_scan_info[o].received->index; - expand_subaddresses(tx_scan_info[o].received->index); + if (tx_scan_info[o].received->index.major < m_subaddress_labels.size() && tx_scan_info[o].received->index.minor < m_subaddress_labels[tx_scan_info[o].received->index.major].size()) + expand_subaddresses(tx_scan_info[o].received->index); if (tx.vout[o].amount == 0) { td.m_mask = tx_scan_info[o].mask; @@ -2579,7 +2599,7 @@ void wallet2::pull_blocks(uint64_t start_height, uint64_t &blocks_start_height, const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; uint64_t pre_call_credits = m_rpc_payment_state.credits; req.client = get_client_signature(); - bool r = net_utils::invoke_http_bin("/getblocks.bin", req, res, m_http_client, rpc_timeout); + bool r = net_utils::invoke_http_bin("/getblocks.bin", req, res, *m_http_client, rpc_timeout); THROW_ON_RPC_RESPONSE_ERROR(r, {}, res, "getblocks.bin", error::get_blocks_error, get_rpc_status(res.status)); THROW_WALLET_EXCEPTION_IF(res.blocks.size() != res.output_indices.size(), error::wallet_internal_error, "mismatched blocks (" + boost::lexical_cast<std::string>(res.blocks.size()) + ") and output_indices (" + @@ -2608,7 +2628,7 @@ void wallet2::pull_hashes(uint64_t start_height, uint64_t &blocks_start_height, const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; req.client = get_client_signature(); uint64_t pre_call_credits = m_rpc_payment_state.credits; - bool r = net_utils::invoke_http_bin("/gethashes.bin", req, res, m_http_client, rpc_timeout); + bool r = net_utils::invoke_http_bin("/gethashes.bin", req, res, *m_http_client, rpc_timeout); THROW_ON_RPC_RESPONSE_ERROR(r, {}, res, "gethashes.bin", error::get_hashes_error, get_rpc_status(res.status)); check_rpc_cost("/gethashes.bin", res.credits, pre_call_credits, 1 + res.m_block_ids.size() * COST_PER_BLOCK_HASH); } @@ -2873,7 +2893,7 @@ void wallet2::remove_obsolete_pool_txs(const std::vector<crypto::hash> &tx_hashe } //---------------------------------------------------------------------------------------------------- -void wallet2::update_pool_state(std::vector<std::pair<cryptonote::transaction, bool>> &process_txs, bool refreshed) +void wallet2::update_pool_state(std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> &process_txs, bool refreshed) { MTRACE("update_pool_state start"); @@ -2893,7 +2913,7 @@ void wallet2::update_pool_state(std::vector<std::pair<cryptonote::transaction, b const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; uint64_t pre_call_credits = m_rpc_payment_state.credits; req.client = get_client_signature(); - bool r = epee::net_utils::invoke_http_json("/get_transaction_pool_hashes.bin", req, res, m_http_client, rpc_timeout); + bool r = epee::net_utils::invoke_http_json("/get_transaction_pool_hashes.bin", req, res, *m_http_client, rpc_timeout); THROW_ON_RPC_RESPONSE_ERROR(r, {}, res, "get_transaction_pool_hashes.bin", error::get_tx_pool_error); check_rpc_cost("/get_transaction_pool_hashes.bin", res.credits, pre_call_credits, 1 + res.tx_hashes.size() * COST_PER_POOL_HASH); } @@ -2933,7 +2953,6 @@ void wallet2::update_pool_state(std::vector<std::pair<cryptonote::transaction, b pit->second.m_state = wallet2::unconfirmed_transfer_details::failed; // the inputs aren't spent anymore, since the tx failed - remove_rings(pit->second.m_tx); for (size_t vini = 0; vini < pit->second.m_tx.vin.size(); ++vini) { if (pit->second.m_tx.vin[vini].type() == typeid(txin_to_key)) @@ -3039,7 +3058,7 @@ void wallet2::update_pool_state(std::vector<std::pair<cryptonote::transaction, b const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; uint64_t pre_call_credits = m_rpc_payment_state.credits; req.client = get_client_signature(); - r = epee::net_utils::invoke_http_json("/gettransactions", req, res, m_http_client, rpc_timeout); + r = epee::net_utils::invoke_http_json("/gettransactions", req, res, *m_http_client, rpc_timeout); if (r && res.status == CORE_RPC_STATUS_OK) check_rpc_cost("/gettransactions", res.credits, pre_call_credits, res.txs.size() * COST_PER_TX); } @@ -3063,7 +3082,7 @@ void wallet2::update_pool_state(std::vector<std::pair<cryptonote::transaction, b [tx_hash](const std::pair<crypto::hash, bool> &e) { return e.first == tx_hash; }); if (i != txids.end()) { - process_txs.push_back(std::make_pair(tx, tx_entry.double_spend_seen)); + process_txs.push_back(std::make_tuple(tx, tx_hash, tx_entry.double_spend_seen)); } else { @@ -3094,14 +3113,14 @@ void wallet2::update_pool_state(std::vector<std::pair<cryptonote::transaction, b MTRACE("update_pool_state end"); } //---------------------------------------------------------------------------------------------------- -void wallet2::process_pool_state(const std::vector<std::pair<cryptonote::transaction, bool>> &txs) +void wallet2::process_pool_state(const std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> &txs) { const time_t now = time(NULL); for (const auto &e: txs) { - const cryptonote::transaction &tx = e.first; - const bool double_spend_seen = e.second; - const crypto::hash tx_hash = get_transaction_hash(tx); + const cryptonote::transaction &tx = std::get<0>(e); + const crypto::hash &tx_hash = std::get<1>(e); + const bool double_spend_seen = std::get<2>(e); process_new_transaction(tx_hash, tx, std::vector<uint64_t>(), 0, 0, now, false, true, double_spend_seen, {}); m_scanned_pool_txs[0].insert(tx_hash); if (m_scanned_pool_txs[0].size() > 5000) @@ -3140,6 +3159,7 @@ void wallet2::fast_refresh(uint64_t stop_height, uint64_t &blocks_start_height, MERROR("Blocks start before blockchain offset: " << blocks_start_height << " " << m_blockchain.offset()); return; } + current_index = blocks_start_height; if (hashes.size() + current_index < stop_height) { drop_from_short_history(short_chain_history, 3); std::vector<crypto::hash>::iterator right = hashes.end(); @@ -3149,7 +3169,6 @@ void wallet2::fast_refresh(uint64_t stop_height, uint64_t &blocks_start_height, short_chain_history.push_front(*right); } } - current_index = blocks_start_height; for(auto& bl_id: hashes) { if(current_index >= m_blockchain.size()) @@ -3322,7 +3341,7 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo // since that might cause a password prompt, which would introduce a data // leak allowing a passive adversary with traffic analysis capability to // infer when we get an incoming output - std::vector<std::pair<cryptonote::transaction, bool>> process_pool_txs; + std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> process_pool_txs; update_pool_state(process_pool_txs, true); bool first = true, last = false; @@ -3525,7 +3544,7 @@ bool wallet2::get_rct_distribution(uint64_t &start_height, std::vector<uint64_t> const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; uint64_t pre_call_credits = m_rpc_payment_state.credits; req.client = get_client_signature(); - r = net_utils::invoke_http_bin("/get_output_distribution.bin", req, res, m_http_client, rpc_timeout); + r = net_utils::invoke_http_bin("/get_output_distribution.bin", req, res, *m_http_client, rpc_timeout); THROW_ON_RPC_RESPONSE_ERROR_GENERIC(r, {}, res, "/get_output_distribution.bin"); check_rpc_cost("/get_output_distribution.bin", res.credits, pre_call_credits, COST_PER_OUTPUT_DISTRIBUTION_0); } @@ -3684,6 +3703,30 @@ void wallet2::clear_soft(bool keep_key_images) */ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable_string& password, bool watch_only) { + boost::optional<wallet2::keys_file_data> 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"); + + std::string tmp_file_name = keys_file_name + ".new"; + std::string buf; + bool r = ::serialization::dump_binary(keys_file_data.get(), buf); + r = r && save_to_file(tmp_file_name, buf); + CHECK_AND_ASSERT_MES(r, false, "failed to generate wallet keys file " << tmp_file_name); + + unlock_keys_file(); + std::error_code e = tools::replace_file(tmp_file_name, keys_file_name); + lock_keys_file(); + + if (e) { + boost::filesystem::remove(tmp_file_name); + LOG_ERROR("failed to update wallet keys file " << keys_file_name); + return false; + } + + return true; +} +//---------------------------------------------------------------------------------------------------- +boost::optional<wallet2::keys_file_data> wallet2::get_keys_file_data(const epee::wipeable_string& password, bool watch_only) +{ std::string account_data; std::string multisig_signers; std::string multisig_derivations; @@ -3704,8 +3747,8 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable account.encrypt_keys(key); bool r = epee::serialization::store_t_to_binary(account, account_data); - CHECK_AND_ASSERT_MES(r, false, "failed to serialize wallet keys"); - wallet2::keys_file_data keys_file_data = {}; + CHECK_AND_ASSERT_MES(r, boost::none, "failed to serialize wallet keys"); + boost::optional<wallet2::keys_file_data> keys_file_data = (wallet2::keys_file_data) {}; // Create a JSON object with "key_data" and "seed_language" as keys. rapidjson::Document json; @@ -3736,12 +3779,12 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable if (m_multisig) { bool r = ::serialization::dump_binary(m_multisig_signers, multisig_signers); - CHECK_AND_ASSERT_MES(r, false, "failed to serialize wallet multisig signers"); + CHECK_AND_ASSERT_MES(r, boost::none, "failed to serialize wallet multisig signers"); value.SetString(multisig_signers.c_str(), multisig_signers.length()); json.AddMember("multisig_signers", value, json.GetAllocator()); r = ::serialization::dump_binary(m_multisig_derivations, multisig_derivations); - CHECK_AND_ASSERT_MES(r, false, "failed to serialize wallet multisig derivations"); + CHECK_AND_ASSERT_MES(r, boost::none, "failed to serialize wallet multisig derivations"); value.SetString(multisig_derivations.c_str(), multisig_derivations.length()); json.AddMember("multisig_derivations", value, json.GetAllocator()); @@ -3884,27 +3927,10 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable // Encrypt the entire JSON object. std::string cipher; cipher.resize(account_data.size()); - keys_file_data.iv = crypto::rand<crypto::chacha_iv>(); - crypto::chacha20(account_data.data(), account_data.size(), key, keys_file_data.iv, &cipher[0]); - keys_file_data.account_data = cipher; - - std::string tmp_file_name = keys_file_name + ".new"; - std::string buf; - 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(); - std::error_code e = tools::replace_file(tmp_file_name, keys_file_name); - lock_keys_file(); - - if (e) { - boost::filesystem::remove(tmp_file_name); - LOG_ERROR("failed to update wallet keys file " << keys_file_name); - return false; - } - - return true; + keys_file_data.get().iv = crypto::rand<crypto::chacha_iv>(); + crypto::chacha20(account_data.data(), account_data.size(), key, keys_file_data.get().iv, &cipher[0]); + keys_file_data.get().account_data = cipher; + return keys_file_data; } //---------------------------------------------------------------------------------------------------- void wallet2::setup_keys(const epee::wipeable_string &password) @@ -3922,7 +3948,7 @@ void wallet2::setup_keys(const epee::wipeable_string &password) static_assert(HASH_SIZE == sizeof(crypto::chacha_key), "Mismatched sizes of hash and chacha key"); epee::mlocked<tools::scrubbed_arr<char, HASH_SIZE+1>> cache_key_data; memcpy(cache_key_data.data(), &key, HASH_SIZE); - cache_key_data[HASH_SIZE] = CACHE_KEY_TAIL; + cache_key_data[HASH_SIZE] = config::HASH_KEY_WALLET_CACHE; cn_fast_hash(cache_key_data.data(), HASH_SIZE+1, (crypto::hash&)m_cache_key); get_ringdb_key(); } @@ -3944,16 +3970,51 @@ void wallet2::change_password(const std::string &filename, const epee::wipeable_ */ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_string& password) { - rapidjson::Document json; - wallet2::keys_file_data keys_file_data; - std::string buf; - bool encrypted_secret_keys = false; - bool r = load_from_file(keys_file_name, buf); + std::string keys_file_buf; + bool r = load_from_file(keys_file_name, keys_file_buf); THROW_WALLET_EXCEPTION_IF(!r, error::file_read_error, keys_file_name); + // Load keys from buffer + boost::optional<crypto::chacha_key> keys_to_encrypt; + try { + r = wallet2::load_keys_buf(keys_file_buf, password, keys_to_encrypt); + } catch (const std::exception& e) { + std::size_t found = string(e.what()).find("failed to deserialize keys buffer"); + THROW_WALLET_EXCEPTION_IF(found != std::string::npos, error::wallet_internal_error, "internal error: failed to deserialize \"" + keys_file_name + '\"'); + throw e; + } + + // Rewrite with encrypted keys if unencrypted, ignore errors + if (r && keys_to_encrypt != boost::none) + { + if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only) + encrypt_keys(keys_to_encrypt.get()); + bool saved_ret = store_keys(keys_file_name, password, m_watch_only); + if (!saved_ret) + { + // just moan a bit, but not fatal + MERROR("Error saving keys file with encrypted keys, not fatal"); + } + if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only) + decrypt_keys(keys_to_encrypt.get()); + m_keys_file_locker.reset(); + } + return r; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_string& password) { + boost::optional<crypto::chacha_key> keys_to_encrypt; + return wallet2::load_keys_buf(keys_buf, password, keys_to_encrypt); +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_string& password, boost::optional<crypto::chacha_key>& keys_to_encrypt) { + // Decrypt the contents - r = ::serialization::parse_binary(buf, keys_file_data); - THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "internal error: failed to deserialize \"" + keys_file_name + '\"'); + rapidjson::Document json; + wallet2::keys_file_data keys_file_data; + bool encrypted_secret_keys = false; + bool r = ::serialization::parse_binary(keys_buf, keys_file_data); + THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "internal error: failed to deserialize keys buffer"); crypto::chacha_key key; crypto::generate_chacha_key(password.data(), password.size(), key, m_kdf_rounds); std::string account_data; @@ -4087,9 +4148,18 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ m_always_confirm_transfers = field_always_confirm_transfers; GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, print_ring_members, int, Int, false, true); m_print_ring_members = field_print_ring_members; - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, store_tx_keys, int, Int, false, true); - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, store_tx_info, int, Int, false, true); - m_store_tx_info = ((field_store_tx_keys != 0) || (field_store_tx_info != 0)); + if (json.HasMember("store_tx_info")) + { + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, store_tx_info, int, Int, true, true); + m_store_tx_info = field_store_tx_info; + } + else if (json.HasMember("store_tx_keys")) // backward compatibility + { + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, store_tx_keys, int, Int, true, true); + m_store_tx_info = field_store_tx_keys; + } + else + m_store_tx_info = true; GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, default_mixin, unsigned int, Uint, false, 0); m_default_mixin = field_default_mixin; GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, default_priority, unsigned int, Uint, false, 0); @@ -4228,8 +4298,8 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ } else { - THROW_WALLET_EXCEPTION(error::wallet_internal_error, "invalid password"); - return false; + THROW_WALLET_EXCEPTION(error::wallet_internal_error, "invalid password"); + return false; } r = epee::serialization::load_t_from_binary(m_account, account_data); @@ -4263,24 +4333,13 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ } else { - // rewrite with encrypted keys, ignore errors - if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only) - encrypt_keys(key); - bool saved_ret = store_keys(keys_file_name, password, m_watch_only); - if (!saved_ret) - { - // just moan a bit, but not fatal - MERROR("Error saving keys file with encrypted keys, not fatal"); - } - if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only) - decrypt_keys(key); - m_keys_file_locker.reset(); + keys_to_encrypt = key; } } const cryptonote::account_keys& keys = m_account.get_keys(); hw::device &hwdev = m_account.get_device(); r = r && hwdev.verify_keys(keys.m_view_secret_key, keys.m_account_address.m_view_public_key); - if(!m_watch_only && !m_multisig && hwdev.device_protocol() != hw::device::PROTOCOL_COLD) + if (!m_watch_only && !m_multisig && hwdev.device_protocol() != hw::device::PROTOCOL_COLD) r = r && hwdev.verify_keys(keys.m_spend_secret_key, keys.m_account_address.m_spend_public_key); THROW_WALLET_EXCEPTION_IF(!r, error::wallet_files_doesnt_correspond, m_keys_file, m_wallet_file); @@ -4371,12 +4430,18 @@ bool wallet2::verify_password(const std::string& keys_file_name, const epee::wip void wallet2::encrypt_keys(const crypto::chacha_key &key) { + boost::lock_guard<boost::mutex> lock(m_decrypt_keys_lock); + if (--m_decrypt_keys_lockers) // another lock left ? + return; m_account.encrypt_keys(key); m_account.decrypt_viewkey(key); } void wallet2::decrypt_keys(const crypto::chacha_key &key) { + boost::lock_guard<boost::mutex> lock(m_decrypt_keys_lock); + if (m_decrypt_keys_lockers++) // already unlocked ? + return; m_account.encrypt_viewkey(key); m_account.decrypt_keys(key); } @@ -4893,7 +4958,8 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password, // re-encrypt keys keys_reencryptor = epee::misc_utils::auto_scope_leave_caller(); - create_keys_file(m_wallet_file, false, password, boost::filesystem::exists(m_wallet_file + ".address.txt")); + if (!m_wallet_file.empty()) + create_keys_file(m_wallet_file, false, password, boost::filesystem::exists(m_wallet_file + ".address.txt")); setup_new_blockchain(); @@ -5033,7 +5099,9 @@ std::string wallet2::exchange_multisig_keys(const epee::wipeable_string &passwor ++m_multisig_rounds_passed; - create_keys_file(m_wallet_file, false, password, boost::filesystem::exists(m_wallet_file + ".address.txt")); + if (!m_wallet_file.empty()) + create_keys_file(m_wallet_file, false, password, boost::filesystem::exists(m_wallet_file + ".address.txt")); + return extra_multisig_info; } @@ -5407,13 +5475,13 @@ bool wallet2::check_connection(uint32_t *version, bool *ssl, uint32_t timeout) { boost::lock_guard<boost::recursive_mutex> lock(m_daemon_rpc_mutex); - if(!m_http_client.is_connected(ssl)) + if(!m_http_client->is_connected(ssl)) { m_rpc_version = 0; m_node_rpc_proxy.invalidate(); - if (!m_http_client.connect(std::chrono::milliseconds(timeout))) + if (!m_http_client->connect(std::chrono::milliseconds(timeout))) return false; - if(!m_http_client.is_connected(ssl)) + if(!m_http_client->is_connected(ssl)) return false; } } @@ -5441,12 +5509,12 @@ void wallet2::set_offline(bool offline) { m_offline = offline; m_node_rpc_proxy.set_offline(offline); - m_http_client.set_auto_connect(!offline); + m_http_client->set_auto_connect(!offline); if (offline) { boost::lock_guard<boost::recursive_mutex> lock(m_daemon_rpc_mutex); - if(m_http_client.is_connected()) - m_http_client.disconnect(); + if(m_http_client->is_connected()) + m_http_client->disconnect(); } } //---------------------------------------------------------------------------------------------------- @@ -5461,48 +5529,63 @@ void wallet2::generate_chacha_key_from_password(const epee::wipeable_string &pas crypto::generate_chacha_key(pass.data(), pass.size(), key, m_kdf_rounds); } //---------------------------------------------------------------------------------------------------- -void wallet2::load(const std::string& wallet_, const epee::wipeable_string& password) +void wallet2::load(const std::string& wallet_, const epee::wipeable_string& password, const std::string& keys_buf, const std::string& cache_buf) { clear(); prepare_file_names(wallet_); + // determine if loading from file system or string buffer + bool use_fs = !wallet_.empty(); + THROW_WALLET_EXCEPTION_IF((use_fs && !keys_buf.empty()) || (!use_fs && keys_buf.empty()), error::file_read_error, "must load keys either from file system or from buffer");\ + boost::system::error_code e; - bool exists = boost::filesystem::exists(m_keys_file, e); - THROW_WALLET_EXCEPTION_IF(e || !exists, error::file_not_found, m_keys_file); - lock_keys_file(); - THROW_WALLET_EXCEPTION_IF(!is_keys_file_locked(), error::wallet_internal_error, "internal error: \"" + m_keys_file + "\" is opened by another wallet program"); + if (use_fs) + { + bool exists = boost::filesystem::exists(m_keys_file, e); + THROW_WALLET_EXCEPTION_IF(e || !exists, error::file_not_found, m_keys_file); + lock_keys_file(); + THROW_WALLET_EXCEPTION_IF(!is_keys_file_locked(), error::wallet_internal_error, "internal error: \"" + m_keys_file + "\" is opened by another wallet program"); - // this temporary unlocking is necessary for Windows (otherwise the file couldn't be loaded). - unlock_keys_file(); - if (!load_keys(m_keys_file, password)) + // this temporary unlocking is necessary for Windows (otherwise the file couldn't be loaded). + unlock_keys_file(); + if (!load_keys(m_keys_file, password)) + { + THROW_WALLET_EXCEPTION_IF(true, error::file_read_error, m_keys_file); + } + LOG_PRINT_L0("Loaded wallet keys file, with public address: " << m_account.get_public_address_str(m_nettype)); + lock_keys_file(); + } + else if (!load_keys_buf(keys_buf, password)) { - THROW_WALLET_EXCEPTION_IF(true, error::file_read_error, m_keys_file); + THROW_WALLET_EXCEPTION_IF(true, error::file_read_error, "failed to load keys from buffer"); } - LOG_PRINT_L0("Loaded wallet keys file, with public address: " << m_account.get_public_address_str(m_nettype)); - lock_keys_file(); wallet_keys_unlocker unlocker(*this, m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only, password); //keys loaded ok! //try to load wallet file. but even if we failed, it is not big problem - if(!boost::filesystem::exists(m_wallet_file, e) || e) + if (use_fs && (!boost::filesystem::exists(m_wallet_file, e) || e)) { LOG_PRINT_L0("file not found: " << m_wallet_file << ", starting with empty blockchain"); m_account_public_address = m_account.get_keys().m_account_address; } - else + else if (use_fs || !cache_buf.empty()) { wallet2::cache_file_data cache_file_data; - std::string buf; - bool r = load_from_file(m_wallet_file, buf, std::numeric_limits<size_t>::max()); - THROW_WALLET_EXCEPTION_IF(!r, error::file_read_error, m_wallet_file); + std::string cache_file_buf; + bool r = true; + if (use_fs) + { + load_from_file(m_wallet_file, cache_file_buf, std::numeric_limits<size_t>::max()); + THROW_WALLET_EXCEPTION_IF(!r, error::file_read_error, m_wallet_file); + } // try to read it as an encrypted cache try { LOG_PRINT_L1("Trying to decrypt cache data"); - r = ::serialization::parse_binary(buf, cache_file_data); + r = ::serialization::parse_binary(use_fs ? cache_file_buf : cache_buf, cache_file_data); THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "internal error: failed to deserialize \"" + m_wallet_file + '\"'); std::string cache_data; cache_data.resize(cache_file_data.cache_data.size()); @@ -5539,7 +5622,7 @@ void wallet2::load(const std::string& wallet_, const epee::wipeable_string& pass catch (...) { LOG_PRINT_L0("Failed to open portable binary, trying unportable"); - boost::filesystem::copy_file(m_wallet_file, m_wallet_file + ".unportable", boost::filesystem::copy_option::overwrite_if_exists); + if (use_fs) boost::filesystem::copy_file(m_wallet_file, m_wallet_file + ".unportable", boost::filesystem::copy_option::overwrite_if_exists); std::stringstream iss; iss.str(""); iss << cache_data; @@ -5554,17 +5637,17 @@ void wallet2::load(const std::string& wallet_, const epee::wipeable_string& pass LOG_PRINT_L1("Failed to load encrypted cache, trying unencrypted"); try { std::stringstream iss; - iss << buf; + iss << cache_file_buf; boost::archive::portable_binary_iarchive ar(iss); ar >> *this; } catch (...) { LOG_PRINT_L0("Failed to open portable binary, trying unportable"); - boost::filesystem::copy_file(m_wallet_file, m_wallet_file + ".unportable", boost::filesystem::copy_option::overwrite_if_exists); + if (use_fs) boost::filesystem::copy_file(m_wallet_file, m_wallet_file + ".unportable", boost::filesystem::copy_option::overwrite_if_exists); std::stringstream iss; iss.str(""); - iss << buf; + iss << cache_file_buf; boost::archive::binary_iarchive ar(iss); ar >> *this; } @@ -5608,7 +5691,8 @@ void wallet2::load(const std::string& wallet_, const epee::wipeable_string& pass try { - m_message_store.read_from_file(get_multisig_wallet_state(), m_mms_file); + if (use_fs) + m_message_store.read_from_file(get_multisig_wallet_state(), m_mms_file); } catch (const std::exception &e) { @@ -5636,7 +5720,7 @@ void wallet2::trim_hashchain() req.height = m_blockchain.size() - 1; uint64_t pre_call_credits = m_rpc_payment_state.credits; req.client = get_client_signature(); - r = net_utils::invoke_http_json_rpc("/json_rpc", "getblockheaderbyheight", req, res, m_http_client, rpc_timeout); + r = net_utils::invoke_http_json_rpc("/json_rpc", "getblockheaderbyheight", req, res, *m_http_client, rpc_timeout); if (r && res.status == CORE_RPC_STATUS_OK) check_rpc_cost("getblockheaderbyheight", res.credits, pre_call_credits, COST_PER_BLOCK_HEADER); } @@ -5711,18 +5795,10 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas } } } - // preparing wallet data - std::stringstream oss; - boost::archive::portable_binary_oarchive ar(oss); - ar << *this; - wallet2::cache_file_data cache_file_data = {}; - cache_file_data.cache_data = oss.str(); - std::string cipher; - cipher.resize(cache_file_data.cache_data.size()); - cache_file_data.iv = crypto::rand<crypto::chacha_iv>(); - crypto::chacha20(cache_file_data.cache_data.data(), cache_file_data.cache_data.size(), m_cache_key, cache_file_data.iv, &cipher[0]); - cache_file_data.cache_data = cipher; + // get wallet cache data + boost::optional<wallet2::cache_file_data> cache_file_data = get_cache_file_data(password); + THROW_WALLET_EXCEPTION_IF(cache_file_data == boost::none, error::wallet_internal_error, "failed to generate wallet cache data"); const std::string new_file = same_file ? m_wallet_file + ".new" : path; const std::string old_file = m_wallet_file; @@ -5773,7 +5849,7 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas // The price to pay is temporary higher memory consumption for string stream + binary archive std::ostringstream oss; binary_archive<true> oar(oss); - bool success = ::serialization::serialize(oar, cache_file_data); + bool success = ::serialization::serialize(oar, cache_file_data.get()); if (success) { success = save_to_file(new_file, oss.str()); } @@ -5782,7 +5858,7 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas std::ofstream ostr; ostr.open(new_file, std::ios_base::binary | std::ios_base::out | std::ios_base::trunc); binary_archive<true> oar(ostr); - bool success = ::serialization::serialize(oar, cache_file_data); + bool success = ::serialization::serialize(oar, cache_file_data.get()); ostr.close(); THROW_WALLET_EXCEPTION_IF(!success || !ostr.good(), error::file_save_error, new_file); #endif @@ -5798,7 +5874,30 @@ 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); } - +} +//---------------------------------------------------------------------------------------------------- +boost::optional<wallet2::cache_file_data> wallet2::get_cache_file_data(const epee::wipeable_string &passwords) +{ + trim_hashchain(); + try + { + std::stringstream oss; + boost::archive::portable_binary_oarchive ar(oss); + ar << *this; + + boost::optional<wallet2::cache_file_data> cache_file_data = (wallet2::cache_file_data) {}; + cache_file_data.get().cache_data = oss.str(); + std::string cipher; + cipher.resize(cache_file_data.get().cache_data.size()); + cache_file_data.get().iv = crypto::rand<crypto::chacha_iv>(); + crypto::chacha20(cache_file_data.get().cache_data.data(), cache_file_data.get().cache_data.size(), m_cache_key, cache_file_data.get().iv, &cipher[0]); + cache_file_data.get().cache_data = cipher; + return cache_file_data; + } + catch(...) + { + return boost::none; + } } //---------------------------------------------------------------------------------------------------- uint64_t wallet2::balance(uint32_t index_major, bool strict) const @@ -6002,7 +6101,7 @@ void wallet2::rescan_spent() const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; uint64_t pre_call_credits = m_rpc_payment_state.credits; req.client = get_client_signature(); - bool r = epee::net_utils::invoke_http_json("/is_key_image_spent", req, daemon_resp, m_http_client, rpc_timeout); + bool r = epee::net_utils::invoke_http_json("/is_key_image_spent", req, daemon_resp, *m_http_client, rpc_timeout); THROW_ON_RPC_RESPONSE_ERROR(r, {}, daemon_resp, "is_key_image_spent", error::is_key_image_spent_error, get_rpc_status(daemon_resp.status)); THROW_WALLET_EXCEPTION_IF(daemon_resp.spent_status.size() != n_outputs, error::wallet_internal_error, "daemon returned wrong response for is_key_image_spent, wrong amounts count = " + @@ -6331,7 +6430,7 @@ void wallet2::commit_tx(pending_tx& ptx) oreq.tx = epee::string_tools::buff_to_hex_nodelimer(tx_to_blob(ptx.tx)); { const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; - bool r = epee::net_utils::invoke_http_json("/submit_raw_tx", oreq, ores, m_http_client, rpc_timeout, "POST"); + bool r = epee::net_utils::invoke_http_json("/submit_raw_tx", oreq, ores, *m_http_client, rpc_timeout, "POST"); THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "submit_raw_tx"); // MyMonero and OpenMonero use different status strings THROW_WALLET_EXCEPTION_IF(ores.status != "OK" && ores.status != "success" , error::tx_rejected, ptx.tx, get_rpc_status(ores.status), ores.error); @@ -6350,7 +6449,7 @@ void wallet2::commit_tx(pending_tx& ptx) const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; uint64_t pre_call_credits = m_rpc_payment_state.credits; req.client = get_client_signature(); - bool r = epee::net_utils::invoke_http_json("/sendrawtransaction", req, daemon_send_resp, m_http_client, rpc_timeout); + bool r = epee::net_utils::invoke_http_json("/sendrawtransaction", req, daemon_send_resp, *m_http_client, rpc_timeout); THROW_ON_RPC_RESPONSE_ERROR(r, {}, daemon_send_resp, "sendrawtransaction", error::tx_rejected, ptx.tx, get_rpc_status(daemon_send_resp.status), get_text_reason(daemon_send_resp)); check_rpc_cost("/sendrawtransaction", daemon_send_resp.credits, pre_call_credits, COST_PER_TX_RELAY); } @@ -7127,6 +7226,20 @@ bool wallet2::sign_multisig_tx_from_file(const std::string &filename, std::vecto return sign_multisig_tx_to_file(exported_txs, filename, txids); } //---------------------------------------------------------------------------------------------------- +uint64_t wallet2::estimate_fee(bool use_per_byte_fee, bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, uint64_t base_fee, uint64_t fee_multiplier, uint64_t fee_quantization_mask) const +{ + if (use_per_byte_fee) + { + const size_t estimated_tx_weight = estimate_tx_weight(use_rct, n_inputs, mixin, n_outputs, extra_size, bulletproof); + return calculate_fee_from_weight(base_fee, estimated_tx_weight, fee_multiplier, fee_quantization_mask); + } + else + { + const size_t estimated_tx_size = estimate_tx_size(use_rct, n_inputs, mixin, n_outputs, extra_size, bulletproof); + return calculate_fee(base_fee, estimated_tx_size, fee_multiplier); + } +} + uint64_t wallet2::get_fee_multiplier(uint32_t priority, int fee_algorithm) { static const struct @@ -7308,7 +7421,7 @@ uint32_t wallet2::adjust_priority(uint32_t priority) const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; uint64_t pre_call_credits = m_rpc_payment_state.credits; getbh_req.client = get_client_signature(); - bool r = net_utils::invoke_http_json_rpc("/json_rpc", "getblockheadersrange", getbh_req, getbh_res, m_http_client, rpc_timeout); + bool r = net_utils::invoke_http_json_rpc("/json_rpc", "getblockheadersrange", getbh_req, getbh_res, *m_http_client, rpc_timeout); THROW_ON_RPC_RESPONSE_ERROR(r, {}, getbh_res, "getblockheadersrange", error::get_blocks_error, get_rpc_status(getbh_res.status)); check_rpc_cost("/sendrawtransaction", getbh_res.credits, pre_call_credits, N * COST_PER_BLOCK_HEADER); } @@ -7534,7 +7647,7 @@ bool wallet2::find_and_save_rings(bool force) const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; uint64_t pre_call_credits = m_rpc_payment_state.credits; req.client = get_client_signature(); - bool r = epee::net_utils::invoke_http_json("/gettransactions", req, res, m_http_client, rpc_timeout); + bool r = epee::net_utils::invoke_http_json("/gettransactions", req, res, *m_http_client, rpc_timeout); THROW_ON_RPC_RESPONSE_ERROR_GENERIC(r, {}, res, "/gettransactions"); THROW_WALLET_EXCEPTION_IF(res.txs.size() != req.txs_hashes.size(), error::wallet_internal_error, "daemon returned wrong response for gettransactions, wrong txs count = " + @@ -7682,7 +7795,7 @@ void wallet2::light_wallet_get_outs(std::vector<std::vector<tools::wallet2::get_ { const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; - bool r = epee::net_utils::invoke_http_json("/get_random_outs", oreq, ores, m_http_client, rpc_timeout, "POST"); + bool r = epee::net_utils::invoke_http_json("/get_random_outs", oreq, ores, *m_http_client, rpc_timeout, "POST"); m_daemon_rpc_mutex.unlock(); THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_random_outs"); THROW_WALLET_EXCEPTION_IF(ores.amount_outs.empty() , error::wallet_internal_error, "No outputs received from light wallet node. Error: " + ores.Error); @@ -7768,8 +7881,50 @@ void wallet2::light_wallet_get_outs(std::vector<std::vector<tools::wallet2::get_ } } +std::pair<std::set<uint64_t>, size_t> outs_unique(const std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs) +{ + std::set<uint64_t> unique; + size_t total = 0; + + for (const auto &it : outs) + { + for (const auto &out : it) + { + const uint64_t global_index = std::get<0>(out); + unique.insert(global_index); + } + total += it.size(); + } + + return std::make_pair(std::move(unique), total); +} + void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, const std::vector<size_t> &selected_transfers, size_t fake_outputs_count) { + std::vector<uint64_t> rct_offsets; + for (size_t attempts = 3; attempts > 0; --attempts) + { + get_outs(outs, selected_transfers, fake_outputs_count, rct_offsets); + + const auto unique = outs_unique(outs); + if (tx_sanity_check(unique.first, unique.second, rct_offsets.empty() ? 0 : rct_offsets.back())) + { + return; + } + + std::vector<crypto::key_image> key_images; + key_images.reserve(selected_transfers.size()); + std::for_each(selected_transfers.begin(), selected_transfers.end(), [this, &key_images](size_t index) { + key_images.push_back(m_transfers[index].m_key_image); + }); + unset_ring(key_images); + } + + THROW_WALLET_EXCEPTION(error::wallet_internal_error, tr("Transaction sanity check failed")); +} + +void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, const std::vector<size_t> &selected_transfers, size_t fake_outputs_count, std::vector<uint64_t> &rct_offsets) +{ LOG_PRINT_L2("fake_outputs_count: " << fake_outputs_count); outs.clear(); @@ -7790,7 +7945,6 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> // if we have at least one rct out, get the distribution, or fall back to the previous system uint64_t rct_start_height; - std::vector<uint64_t> rct_offsets; bool has_rct = false; uint64_t max_rct_index = 0; for (size_t idx: selected_transfers) @@ -7799,7 +7953,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> has_rct = true; max_rct_index = std::max(max_rct_index, m_transfers[idx].m_global_output_index); } - const bool has_rct_distribution = has_rct && get_rct_distribution(rct_start_height, rct_offsets); + const bool has_rct_distribution = has_rct && (!rct_offsets.empty() || get_rct_distribution(rct_start_height, rct_offsets)); if (has_rct_distribution) { // check we're clear enough of rct start, to avoid corner cases below @@ -7828,7 +7982,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; uint64_t pre_call_credits = m_rpc_payment_state.credits; req_t.client = get_client_signature(); - bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_output_histogram", req_t, resp_t, m_http_client, rpc_timeout); + bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_output_histogram", req_t, resp_t, *m_http_client, rpc_timeout); THROW_ON_RPC_RESPONSE_ERROR(r, {}, resp_t, "get_output_histogram", error::get_histogram_error, get_rpc_status(resp_t.status)); check_rpc_cost("get_output_histogram", resp_t.credits, pre_call_credits, COST_PER_OUTPUT_HISTOGRAM * req_t.amounts.size()); } @@ -7854,7 +8008,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; uint64_t pre_call_credits = m_rpc_payment_state.credits; req_t.client = get_client_signature(); - bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_output_distribution", req_t, resp_t, m_http_client, rpc_timeout * 1000); + bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_output_distribution", req_t, resp_t, *m_http_client, rpc_timeout * 1000); THROW_ON_RPC_RESPONSE_ERROR(r, {}, resp_t, "get_output_distribution", error::get_output_distribution, get_rpc_status(resp_t.status)); uint64_t expected_cost = 0; for (uint64_t amount: req_t.amounts) expected_cost += (amount ? COST_PER_OUTPUT_DISTRIBUTION : COST_PER_OUTPUT_DISTRIBUTION_0); @@ -8208,7 +8362,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; uint64_t pre_call_credits = m_rpc_payment_state.credits; req.client = get_client_signature(); - bool r = epee::net_utils::invoke_http_bin("/get_outs.bin", req, daemon_resp, m_http_client, rpc_timeout); + bool r = epee::net_utils::invoke_http_bin("/get_outs.bin", req, daemon_resp, *m_http_client, rpc_timeout); THROW_ON_RPC_RESPONSE_ERROR(r, {}, daemon_resp, "get_outs.bin", error::get_outs_error, get_rpc_status(daemon_resp.status)); THROW_WALLET_EXCEPTION_IF(daemon_resp.outs.size() != req.outputs.size(), error::wallet_internal_error, "daemon returned wrong response for get_outs.bin, wrong amounts count = " + @@ -10417,7 +10571,7 @@ uint8_t wallet2::get_current_hard_fork() m_daemon_rpc_mutex.lock(); req_t.version = 0; - bool r = net_utils::invoke_http_json_rpc("/json_rpc", "hard_fork_info", req_t, resp_t, m_http_client, rpc_timeout); + bool r = net_utils::invoke_http_json_rpc("/json_rpc", "hard_fork_info", req_t, resp_t, *m_http_client, rpc_timeout); m_daemon_rpc_mutex.unlock(); THROW_WALLET_EXCEPTION_IF(!r, tools::error::no_connection_to_daemon, "hard_fork_info"); THROW_WALLET_EXCEPTION_IF(resp_t.status == CORE_RPC_STATUS_BUSY, tools::error::daemon_busy, "hard_fork_info"); @@ -10512,7 +10666,7 @@ std::vector<size_t> wallet2::select_available_outputs_from_histogram(uint64_t co const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; uint64_t pre_call_credits = m_rpc_payment_state.credits; req_t.client = get_client_signature(); - bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_output_histogram", req_t, resp_t, m_http_client, rpc_timeout); + bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_output_histogram", req_t, resp_t, *m_http_client, rpc_timeout); THROW_ON_RPC_RESPONSE_ERROR(r, {}, resp_t, "get_output_histogram", error::get_histogram_error, resp_t.status); uint64_t cost = req_t.amounts.empty() ? COST_PER_FULL_OUTPUT_HISTOGRAM : (COST_PER_OUTPUT_HISTOGRAM * req_t.amounts.size()); check_rpc_cost("get_output_histogram", resp_t.credits, pre_call_credits, cost); @@ -10554,7 +10708,7 @@ uint64_t wallet2::get_num_rct_outputs() const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; uint64_t pre_call_credits = m_rpc_payment_state.credits; req_t.client = get_client_signature(); - bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_output_histogram", req_t, resp_t, m_http_client, rpc_timeout); + bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_output_histogram", req_t, resp_t, *m_http_client, rpc_timeout); THROW_ON_RPC_RESPONSE_ERROR(r, {}, resp_t, "get_output_histogram", error::get_histogram_error, resp_t.status); THROW_WALLET_EXCEPTION_IF(resp_t.histogram.size() != 1, error::get_histogram_error, "Expected exactly one response"); THROW_WALLET_EXCEPTION_IF(resp_t.histogram[0].amount != 0, error::get_histogram_error, "Expected 0 amount"); @@ -10685,7 +10839,7 @@ bool wallet2::get_tx_key(const crypto::hash &txid, crypto::secret_key &tx_key, s const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; req.client = get_client_signature(); uint64_t pre_call_credits = m_rpc_payment_state.credits; - bool ok = epee::net_utils::invoke_http_json("/gettransactions", req, res, m_http_client); + bool ok = epee::net_utils::invoke_http_json("/gettransactions", req, res, *m_http_client); THROW_WALLET_EXCEPTION_IF(!ok || (res.txs.size() != 1 && res.txs_as_hex.size() != 1), error::wallet_internal_error, "Failed to get transaction from daemon"); check_rpc_cost("/gettransactions", res.credits, pre_call_credits, res.txs.size() * COST_PER_TX); @@ -10738,7 +10892,7 @@ void wallet2::set_tx_key(const crypto::hash &txid, const crypto::secret_key &tx_ const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; pre_call_credits = m_rpc_payment_state.credits; req.client = get_client_signature(); - r = epee::net_utils::invoke_http_json("/gettransactions", req, res, m_http_client, rpc_timeout); + r = epee::net_utils::invoke_http_json("/gettransactions", req, res, *m_http_client, rpc_timeout); THROW_ON_RPC_RESPONSE_ERROR_GENERIC(r, {}, res, "/gettransactions"); THROW_WALLET_EXCEPTION_IF(res.txs.size() != 1, error::wallet_internal_error, "daemon returned wrong response for gettransactions, wrong txs count = " + @@ -10791,7 +10945,7 @@ std::string wallet2::get_spend_proof(const crypto::hash &txid, const std::string const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; pre_call_credits = m_rpc_payment_state.credits; req.client = get_client_signature(); - r = epee::net_utils::invoke_http_json("/gettransactions", req, res, m_http_client, rpc_timeout); + r = epee::net_utils::invoke_http_json("/gettransactions", req, res, *m_http_client, rpc_timeout); THROW_ON_RPC_RESPONSE_ERROR_GENERIC(r, {}, res, "gettransactions"); THROW_WALLET_EXCEPTION_IF(res.txs.size() != 1, error::wallet_internal_error, "daemon returned wrong response for gettransactions, wrong txs count = " + @@ -10855,7 +11009,7 @@ std::string wallet2::get_spend_proof(const crypto::hash &txid, const std::string const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; pre_call_credits = m_rpc_payment_state.credits; req.client = get_client_signature(); - r = epee::net_utils::invoke_http_bin("/get_outs.bin", req, res, m_http_client, rpc_timeout); + r = epee::net_utils::invoke_http_bin("/get_outs.bin", req, res, *m_http_client, rpc_timeout); THROW_ON_RPC_RESPONSE_ERROR(r, {}, res, "get_outs.bin", error::get_outs_error, res.status); THROW_WALLET_EXCEPTION_IF(res.outs.size() != ring_size, error::wallet_internal_error, "daemon returned wrong response for get_outs.bin, wrong amounts count = " + @@ -10913,7 +11067,7 @@ bool wallet2::check_spend_proof(const crypto::hash &txid, const std::string &mes const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; pre_call_credits = m_rpc_payment_state.credits; req.client = get_client_signature(); - r = epee::net_utils::invoke_http_json("/gettransactions", req, res, m_http_client, rpc_timeout); + r = epee::net_utils::invoke_http_json("/gettransactions", req, res, *m_http_client, rpc_timeout); THROW_ON_RPC_RESPONSE_ERROR_GENERIC(r, {}, res, "gettransactions"); THROW_WALLET_EXCEPTION_IF(res.txs.size() != 1, error::wallet_internal_error, "daemon returned wrong response for gettransactions, wrong txs count = " + @@ -10988,7 +11142,7 @@ bool wallet2::check_spend_proof(const crypto::hash &txid, const std::string &mes const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; pre_call_credits = m_rpc_payment_state.credits; req.client = get_client_signature(); - r = epee::net_utils::invoke_http_bin("/get_outs.bin", req, res, m_http_client, rpc_timeout); + r = epee::net_utils::invoke_http_bin("/get_outs.bin", req, res, *m_http_client, rpc_timeout); THROW_ON_RPC_RESPONSE_ERROR(r, {}, res, "get_outs.bin", error::get_outs_error, res.status); THROW_WALLET_EXCEPTION_IF(res.outs.size() != req.outputs.size(), error::wallet_internal_error, "daemon returned wrong response for get_outs.bin, wrong amounts count = " + @@ -11090,7 +11244,7 @@ void wallet2::check_tx_key_helper(const crypto::hash &txid, const crypto::key_de const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; uint64_t pre_call_credits = m_rpc_payment_state.credits; req.client = get_client_signature(); - ok = epee::net_utils::invoke_http_json("/gettransactions", req, res, m_http_client); + ok = epee::net_utils::invoke_http_json("/gettransactions", req, res, *m_http_client); THROW_WALLET_EXCEPTION_IF(!ok || (res.txs.size() != 1 && res.txs_as_hex.size() != 1), error::wallet_internal_error, "Failed to get transaction from daemon"); check_rpc_cost("/gettransactions", res.credits, pre_call_credits, COST_PER_TX); @@ -11145,7 +11299,7 @@ std::string wallet2::get_tx_proof(const crypto::hash &txid, const cryptonote::ac const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; uint64_t pre_call_credits = m_rpc_payment_state.credits; req.client = get_client_signature(); - ok = net_utils::invoke_http_json("/gettransactions", req, res, m_http_client); + ok = net_utils::invoke_http_json("/gettransactions", req, res, *m_http_client); THROW_WALLET_EXCEPTION_IF(!ok || (res.txs.size() != 1 && res.txs_as_hex.size() != 1), error::wallet_internal_error, "Failed to get transaction from daemon"); check_rpc_cost("/gettransactions", res.credits, pre_call_credits, COST_PER_TX); @@ -11306,7 +11460,7 @@ bool wallet2::check_tx_proof(const crypto::hash &txid, const cryptonote::account const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; uint64_t pre_call_credits = m_rpc_payment_state.credits; req.client = get_client_signature(); - ok = net_utils::invoke_http_json("/gettransactions", req, res, m_http_client); + ok = net_utils::invoke_http_json("/gettransactions", req, res, *m_http_client); THROW_WALLET_EXCEPTION_IF(!ok || (res.txs.size() != 1 && res.txs_as_hex.size() != 1), error::wallet_internal_error, "Failed to get transaction from daemon"); check_rpc_cost("/gettransactions", res.credits, pre_call_credits, COST_PER_TX); @@ -11603,7 +11757,7 @@ bool wallet2::check_reserve_proof(const cryptonote::account_public_address &addr const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; uint64_t pre_call_credits = m_rpc_payment_state.credits; gettx_req.client = get_client_signature(); - bool ok = net_utils::invoke_http_json("/gettransactions", gettx_req, gettx_res, m_http_client); + bool ok = net_utils::invoke_http_json("/gettransactions", gettx_req, gettx_res, *m_http_client); THROW_WALLET_EXCEPTION_IF(!ok || gettx_res.txs.size() != proofs.size(), error::wallet_internal_error, "Failed to get transaction from daemon"); check_rpc_cost("/gettransactions", gettx_res.credits, pre_call_credits, gettx_res.txs.size() * COST_PER_TX); @@ -11620,7 +11774,7 @@ bool wallet2::check_reserve_proof(const cryptonote::account_public_address &addr const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; uint64_t pre_call_credits = m_rpc_payment_state.credits; kispent_req.client = get_client_signature(); - ok = epee::net_utils::invoke_http_json("/is_key_image_spent", kispent_req, kispent_res, m_http_client, rpc_timeout); + ok = epee::net_utils::invoke_http_json("/is_key_image_spent", kispent_req, kispent_res, *m_http_client, rpc_timeout); THROW_WALLET_EXCEPTION_IF(!ok || kispent_res.spent_status.size() != proofs.size(), error::wallet_internal_error, "Failed to get key image spent status from daemon"); check_rpc_cost("/is_key_image_spent", kispent_res.credits, pre_call_credits, kispent_res.spent_status.size() * COST_PER_KEY_IMAGE); @@ -12194,7 +12348,7 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; uint64_t pre_call_credits = m_rpc_payment_state.credits; req.client = get_client_signature(); - bool r = epee::net_utils::invoke_http_json("/is_key_image_spent", req, daemon_resp, m_http_client, rpc_timeout); + bool r = epee::net_utils::invoke_http_json("/is_key_image_spent", req, daemon_resp, *m_http_client, rpc_timeout); THROW_ON_RPC_RESPONSE_ERROR_GENERIC(r, {}, daemon_resp, "is_key_image_spent"); THROW_WALLET_EXCEPTION_IF(daemon_resp.spent_status.size() != signed_key_images.size(), error::wallet_internal_error, "daemon returned wrong response for is_key_image_spent, wrong amounts count = " + @@ -12283,7 +12437,7 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; gettxs_req.client = get_client_signature(); uint64_t pre_call_credits = m_rpc_payment_state.credits; - bool r = epee::net_utils::invoke_http_json("/gettransactions", gettxs_req, gettxs_res, m_http_client, rpc_timeout); + bool r = epee::net_utils::invoke_http_json("/gettransactions", gettxs_req, gettxs_res, *m_http_client, rpc_timeout); THROW_ON_RPC_RESPONSE_ERROR_GENERIC(r, {}, gettxs_res, "gettransactions"); THROW_WALLET_EXCEPTION_IF(gettxs_res.txs.size() != spent_txids.size(), error::wallet_internal_error, "daemon returned wrong response for gettransactions, wrong count = " + std::to_string(gettxs_res.txs.size()) + ", expected " + std::to_string(spent_txids.size())); @@ -12601,7 +12755,8 @@ process: const crypto::public_key& out_key = boost::get<cryptonote::txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key; bool r = cryptonote::generate_key_image_helper(m_account.get_keys(), m_subaddresses, out_key, tx_pub_key, additional_tx_pub_keys, td.m_internal_output_index, in_ephemeral, td.m_key_image, m_account.get_device()); THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key image"); - expand_subaddresses(td.m_subaddr_index); + if (td.m_subaddr_index.major < m_subaddress_labels.size() && td.m_subaddr_index.minor < m_subaddress_labels[td.m_subaddr_index.major].size()) + expand_subaddresses(td.m_subaddr_index); td.m_key_image_known = true; td.m_key_image_request = true; td.m_key_image_partial = false; @@ -13223,7 +13378,7 @@ uint64_t wallet2::get_blockchain_height_by_date(uint16_t year, uint8_t month, ui const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; uint64_t pre_call_credits = m_rpc_payment_state.credits; req.client = get_client_signature(); - r = net_utils::invoke_http_bin("/getblocks_by_height.bin", req, res, m_http_client, rpc_timeout); + r = net_utils::invoke_http_bin("/getblocks_by_height.bin", req, res, *m_http_client, rpc_timeout); if (r && res.status == CORE_RPC_STATUS_OK) check_rpc_cost("/getblocks_by_height.bin", res.credits, pre_call_credits, 3 * COST_PER_BLOCK); } @@ -13301,7 +13456,7 @@ std::vector<std::pair<uint64_t, uint64_t>> wallet2::estimate_backlog(const std:: const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; uint64_t pre_call_credits = m_rpc_payment_state.credits; req.client = get_client_signature(); - bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_txpool_backlog", req, res, m_http_client, rpc_timeout); + bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_txpool_backlog", req, res, *m_http_client, rpc_timeout); THROW_ON_RPC_RESPONSE_ERROR(r, {}, res, "get_txpool_backlog", error::get_tx_pool_error); check_rpc_cost("get_txpool_backlog", res.credits, pre_call_credits, COST_PER_TX_POOL_STATS * res.backlog.size()); } @@ -13640,12 +13795,12 @@ void wallet2::finish_rescan_bc_keep_key_images(uint64_t transfer_height, const c //---------------------------------------------------------------------------------------------------- uint64_t wallet2::get_bytes_sent() const { - return m_http_client.get_bytes_sent(); + return m_http_client->get_bytes_sent(); } //---------------------------------------------------------------------------------------------------- uint64_t wallet2::get_bytes_received() const { - return m_http_client.get_bytes_received(); + return m_http_client->get_bytes_received(); } //---------------------------------------------------------------------------------------------------- std::vector<cryptonote::public_node> wallet2::get_public_nodes(bool white_only) @@ -13658,7 +13813,7 @@ std::vector<cryptonote::public_node> wallet2::get_public_nodes(bool white_only) { const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; - bool r = epee::net_utils::invoke_http_json("/get_public_nodes", req, res, m_http_client, rpc_timeout); + bool r = epee::net_utils::invoke_http_json("/get_public_nodes", req, res, *m_http_client, rpc_timeout); THROW_ON_RPC_RESPONSE_ERROR_GENERIC(r, {}, res, "/get_public_nodes"); } diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 810c002fe..1c3c00152 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -269,7 +269,7 @@ private: 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 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); + wallet2(cryptonote::network_type nettype = cryptonote::MAINNET, uint64_t kdf_rounds = 1, bool unattended = false, std::unique_ptr<epee::net_utils::http::http_client_factory> http_client_factory = std::unique_ptr<epee::net_utils::http::http_simple_client_factory>(new epee::net_utils::http::http_simple_client_factory())); ~wallet2(); struct multisig_info @@ -708,7 +708,7 @@ private: */ void rewrite(const std::string& wallet_name, const epee::wipeable_string& password); void write_watch_only_wallet(const std::string& wallet_name, const epee::wipeable_string& password, std::string &new_keys_filename); - void load(const std::string& wallet, const epee::wipeable_string& password); + void load(const std::string& wallet, const epee::wipeable_string& password, const std::string& keys_buf = "", const std::string& cache_buf = ""); void store(); /*! * \brief store_to Stores wallet to another file(s), deleting old ones @@ -716,6 +716,19 @@ private: * \param password Password to protect new wallet (TODO: probably better save the password in the wallet object?) */ void store_to(const std::string &path, const epee::wipeable_string &password); + /*! + * \brief get_keys_file_data Get wallet keys data which can be stored to a wallet file. + * \param password Password of the encrypted wallet buffer (TODO: probably better save the password in the wallet object?) + * \param watch_only true to include only view key, false to include both spend and view keys + * \return Encrypted wallet keys data which can be stored to a wallet file + */ + boost::optional<wallet2::keys_file_data> get_keys_file_data(const epee::wipeable_string& password, bool watch_only); + /*! + * \brief get_cache_file_data Get wallet cache data which can be stored to a wallet file. + * \param password Password to protect the wallet cache data (TODO: probably better save the password in the wallet object?) + * \return Encrypted wallet cache data which can be stored to a wallet file + */ + boost::optional<wallet2::cache_file_data> get_cache_file_data(const epee::wipeable_string& password); std::string path() const; @@ -793,6 +806,7 @@ private: size_t get_num_subaddresses(uint32_t index_major) const { return index_major < m_subaddress_labels.size() ? m_subaddress_labels[index_major].size() : 0; } void add_subaddress(uint32_t index_major, const std::string& label); // throws when index is out of bound void expand_subaddresses(const cryptonote::subaddress_index& index); + void create_one_off_subaddress(const cryptonote::subaddress_index& index); std::string get_subaddress_label(const cryptonote::subaddress_index& index) const; void set_subaddress_label(const cryptonote::subaddress_index &index, const std::string &label); void set_subaddress_lookahead(size_t major, size_t minor); @@ -1224,8 +1238,8 @@ private: 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; - void update_pool_state(std::vector<std::pair<cryptonote::transaction, bool>> &process_txs, bool refreshed = false); - void process_pool_state(const std::vector<std::pair<cryptonote::transaction, bool>> &txs); + void update_pool_state(std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> &process_txs, bool refreshed = false); + void process_pool_state(const std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> &txs); void remove_obsolete_pool_txs(const std::vector<crypto::hash> &tx_hashes); std::string encrypt(const char *plaintext, size_t len, const crypto::secret_key &skey, bool authenticated = true) const; @@ -1246,6 +1260,7 @@ private: std::vector<std::pair<uint64_t, uint64_t>> estimate_backlog(const std::vector<std::pair<double, double>> &fee_levels); std::vector<std::pair<uint64_t, uint64_t>> estimate_backlog(uint64_t min_tx_weight, uint64_t max_tx_weight, const std::vector<uint64_t> &fees); + uint64_t estimate_fee(bool use_per_byte_fee, bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, uint64_t base_fee, uint64_t fee_multiplier, uint64_t fee_quantization_mask) const; uint64_t get_fee_multiplier(uint32_t priority, int fee_algorithm = -1); uint64_t get_base_fee(); uint64_t get_fee_quantization_mask(); @@ -1317,25 +1332,25 @@ private: crypto::public_key get_multisig_signing_public_key(const crypto::secret_key &skey) const; template<class t_request, class t_response> - inline bool invoke_http_json(const boost::string_ref uri, const t_request& req, t_response& res, std::chrono::milliseconds timeout = std::chrono::seconds(15), const boost::string_ref http_method = "GET") + inline bool invoke_http_json(const boost::string_ref uri, const t_request& req, t_response& res, std::chrono::milliseconds timeout = std::chrono::seconds(15), const boost::string_ref http_method = "POST") { if (m_offline) return false; boost::lock_guard<boost::recursive_mutex> lock(m_daemon_rpc_mutex); - return epee::net_utils::invoke_http_json(uri, req, res, m_http_client, timeout, http_method); + return epee::net_utils::invoke_http_json(uri, req, res, *m_http_client, timeout, http_method); } template<class t_request, class t_response> - inline bool invoke_http_bin(const boost::string_ref uri, const t_request& req, t_response& res, std::chrono::milliseconds timeout = std::chrono::seconds(15), const boost::string_ref http_method = "GET") + inline bool invoke_http_bin(const boost::string_ref uri, const t_request& req, t_response& res, std::chrono::milliseconds timeout = std::chrono::seconds(15), const boost::string_ref http_method = "POST") { if (m_offline) return false; boost::lock_guard<boost::recursive_mutex> lock(m_daemon_rpc_mutex); - return epee::net_utils::invoke_http_bin(uri, req, res, m_http_client, timeout, http_method); + return epee::net_utils::invoke_http_bin(uri, req, res, *m_http_client, timeout, http_method); } template<class t_request, class t_response> - inline bool invoke_http_json_rpc(const boost::string_ref uri, const std::string& method_name, const t_request& req, t_response& res, std::chrono::milliseconds timeout = std::chrono::seconds(15), const boost::string_ref http_method = "GET", const std::string& req_id = "0") + inline bool invoke_http_json_rpc(const boost::string_ref uri, const std::string& method_name, const t_request& req, t_response& res, std::chrono::milliseconds timeout = std::chrono::seconds(15), const boost::string_ref http_method = "POST", const std::string& req_id = "0") { if (m_offline) return false; boost::lock_guard<boost::recursive_mutex> lock(m_daemon_rpc_mutex); - return epee::net_utils::invoke_http_json_rpc(uri, method_name, req, res, m_http_client, timeout, http_method, req_id); + return epee::net_utils::invoke_http_json_rpc(uri, method_name, req, res, *m_http_client, timeout, http_method, req_id); } bool set_ring_database(const std::string &filename); @@ -1389,6 +1404,8 @@ private: uint64_t credits() const { return m_rpc_payment_state.credits; } void credit_report(uint64_t &expected_spent, uint64_t &discrepancy) const { expected_spent = m_rpc_payment_state.expected_spent; discrepancy = m_rpc_payment_state.discrepancy; } + static std::string get_default_daemon_address() { CRITICAL_REGION_LOCAL(default_daemon_address_lock); return default_daemon_address; } + private: /*! * \brief Stores wallet information to wallet file. @@ -1399,11 +1416,18 @@ private: */ bool store_keys(const std::string& keys_file_name, const epee::wipeable_string& password, bool watch_only = false); /*! - * \brief Load wallet information from wallet file. + * \brief Load wallet keys information from wallet file. * \param keys_file_name Name of wallet file * \param password Password of wallet file */ bool load_keys(const std::string& keys_file_name, const epee::wipeable_string& password); + /*! + * \brief Load wallet keys information from a string buffer. + * \param keys_buf Keys buffer to load + * \param password Password of keys buffer + */ + 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<crypto::chacha_key>& keys_to_encrypt); void process_new_transaction(const crypto::hash &txid, const cryptonote::transaction& tx, const std::vector<uint64_t> &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<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache = NULL); 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> &tx_cache_data, size_t tx_cache_data_offset, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache = NULL); @@ -1440,6 +1464,7 @@ private: bool is_spent(const transfer_details &td, bool strict = true) const; bool is_spent(size_t idx, bool strict = true) const; void get_outs(std::vector<std::vector<get_outs_entry>> &outs, const std::vector<size_t> &selected_transfers, size_t fake_outputs_count); + void get_outs(std::vector<std::vector<get_outs_entry>> &outs, const std::vector<size_t> &selected_transfers, size_t fake_outputs_count, std::vector<uint64_t> &rct_offsets); bool tx_add_fake_output(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, uint64_t global_index, const crypto::public_key& tx_public_key, const rct::key& mask, uint64_t real_index, bool unlocked) const; bool should_pick_a_second_output(bool use_rct, size_t n_transfers, const std::vector<size_t> &unused_transfers_indices, const std::vector<size_t> &unused_dust_indices) const; std::vector<size_t> get_only_rct(const std::vector<size_t> &unused_dust_indices, const std::vector<size_t> &unused_transfers_indices) const; @@ -1497,7 +1522,7 @@ private: std::string m_wallet_file; std::string m_keys_file; std::string m_mms_file; - epee::net_utils::http::http_simple_client m_http_client; + const std::unique_ptr<epee::net_utils::http::abstract_http_client> m_http_client; hashchain m_blockchain; std::unordered_map<crypto::hash, unconfirmed_transfer_details> m_unconfirmed_txs; std::unordered_map<crypto::hash, confirmed_transfer_details> m_confirmed_txs; @@ -1618,6 +1643,8 @@ private: crypto::chacha_key m_cache_key; boost::optional<epee::wipeable_string> m_encrypt_keys_after_refresh; + boost::mutex m_decrypt_keys_lock; + unsigned int m_decrypt_keys_lockers; bool m_unattended; bool m_devices_registered; @@ -1626,6 +1653,9 @@ private: std::unique_ptr<wallet_device_callback> m_device_callback; ExportFormat m_export_format; + + static boost::mutex default_daemon_address_lock; + static std::string default_daemon_address; }; } BOOST_CLASS_VERSION(tools::wallet2, 29) diff --git a/src/wallet/wallet_rpc_payments.cpp b/src/wallet/wallet_rpc_payments.cpp index 41696d13b..4f5364269 100644 --- a/src/wallet/wallet_rpc_payments.cpp +++ b/src/wallet/wallet_rpc_payments.cpp @@ -85,7 +85,7 @@ bool wallet2::make_rpc_payment(uint32_t nonce, uint32_t cookie, uint64_t &credit uint64_t pre_call_credits = m_rpc_payment_state.credits; req.client = get_client_signature(); epee::json_rpc::error error; - bool r = epee::net_utils::invoke_http_json_rpc("/json_rpc", "rpc_access_submit_nonce", req, res, error, m_http_client, rpc_timeout); + bool r = epee::net_utils::invoke_http_json_rpc("/json_rpc", "rpc_access_submit_nonce", req, res, error, *m_http_client, rpc_timeout); m_daemon_rpc_mutex.unlock(); THROW_ON_RPC_RESPONSE_ERROR_GENERIC(r, error, res, "rpc_access_submit_nonce"); THROW_WALLET_EXCEPTION_IF(res.credits < pre_call_credits, error::wallet_internal_error, "RPC payment did not increase balance"); diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index 2a051553b..db2e2344b 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -1180,7 +1180,6 @@ namespace tools } } - std::vector<tools::wallet2::pending_tx> ptx; try { // gather info to ask the user @@ -1417,11 +1416,22 @@ namespace tools return false; } + std::set<uint32_t> subaddr_indices; + if (req.subaddr_indices_all) + { + for (uint32_t i = 0; i < m_wallet->get_num_subaddresses(req.account_index); ++i) + subaddr_indices.insert(i); + } + else + { + subaddr_indices= req.subaddr_indices; + } + try { 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<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_all(req.below_amount, dsts[0].addr, dsts[0].is_subaddress, req.outputs, mixin, req.unlock_time, priority, extra, req.account_index, req.subaddr_indices); + std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_all(req.below_amount, dsts[0].addr, dsts[0].is_subaddress, req.outputs, mixin, req.unlock_time, priority, extra, req.account_index, subaddr_indices); return fill_response(ptx_vector, req.get_tx_keys, res.tx_key_list, res.amount_list, res.fee_list, res.weight_list, res.multisig_txset, res.unsigned_txset, req.do_not_relay, res.tx_hash_list, req.get_tx_hex, res.tx_blob_list, req.get_tx_metadata, res.tx_metadata_list, er); @@ -2461,7 +2471,7 @@ namespace tools if (req.pool) { - std::vector<std::pair<cryptonote::transaction, bool>> process_txs; + std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> process_txs; m_wallet->update_pool_state(process_txs); if (!process_txs.empty()) m_wallet->process_pool_state(process_txs); @@ -2544,7 +2554,7 @@ namespace tools } } - std::vector<std::pair<cryptonote::transaction, bool>> process_txs; + std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> process_txs; m_wallet->update_pool_state(process_txs); if (!process_txs.empty()) m_wallet->process_pool_state(process_txs); diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h index 614e7af08..a212b79e6 100644 --- a/src/wallet/wallet_rpc_server_commands_defs.h +++ b/src/wallet/wallet_rpc_server_commands_defs.h @@ -761,6 +761,7 @@ namespace wallet_rpc std::string address; uint32_t account_index; std::set<uint32_t> subaddr_indices; + bool subaddr_indices_all; uint32_t priority; uint64_t ring_size; uint64_t outputs; @@ -776,6 +777,7 @@ namespace wallet_rpc KV_SERIALIZE(address) KV_SERIALIZE(account_index) KV_SERIALIZE(subaddr_indices) + KV_SERIALIZE_OPT(subaddr_indices_all, false) KV_SERIALIZE(priority) KV_SERIALIZE_OPT(ring_size, (uint64_t)0) KV_SERIALIZE_OPT(outputs, (uint64_t)1) |