diff options
36 files changed, 639 insertions, 293 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 44268b056..842eea734 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -407,7 +407,7 @@ else() set(WARNINGS "${WARNINGS} -Wno-error=inline-asm") endif() else() - set(WARNINGS "${WARNINGS} -Wlogical-op -Wno-error=maybe-uninitialized") + set(WARNINGS "${WARNINGS} -Wlogical-op -Wno-error=maybe-uninitialized -Wno-error=cpp") endif() if(MINGW) set(WARNINGS "${WARNINGS} -Wno-error=unused-value -Wno-error=unused-but-set-variable") @@ -699,25 +699,19 @@ if(NOT ZMQ_LIB) message(FATAL_ERROR "Could not find require libzmq") endif() -function (treat_warnings_as_errors dirs) - foreach(dir ${ARGV}) - set_property(DIRECTORY ${dir} - APPEND PROPERTY COMPILE_FLAGS "-Werror") - endforeach() -endfunction() - -add_subdirectory(contrib) -add_subdirectory(src) - -treat_warnings_as_errors(contrib src) - option(BUILD_TESTS "Build tests." OFF) if(BUILD_TESTS) add_subdirectory(tests) endif() +# warnings are cleared only for GCC on Linux +if (NOT (MINGW OR APPLE OR FREEBSD OR OPENBSD OR DRAGONFLY)) +add_compile_options("${WARNINGS_AS_ERRORS_FLAG}") # applies only to targets that follow +endif() +add_subdirectory(contrib) +add_subdirectory(src) if(BUILD_DOCUMENTATION) set(DOC_GRAPHS "YES" CACHE STRING "Create dependency graphs (needs graphviz)") @@ -11,6 +11,11 @@ Portions Copyright (c) 2012-2013, The Cryptonote developers - GitHub: [https://github.com/monero-project/monero](https://github.com/monero-project/monero) - IRC: [#monero-dev on Freenode](http://webchat.freenode.net/?randomnick=1&channels=%23monero-dev&prompt=1&uio=d4) +## Vulnerability Response + +- Our [Vulnerability Response Process](https://github.com/monero-project/meta/blob/master/VULNERABILITY_RESPONSE_PROCESS.md) encourages responsible disclosure +- We are also available via [HackerOne](https://hackerone.com/monero) + ## Build | Operating System | Processor | Status | diff --git a/VULNERABILITY_RESPONSE_PROCESS.md b/VULNERABILITY_RESPONSE_PROCESS.md deleted file mode 100644 index eea3a06e7..000000000 --- a/VULNERABILITY_RESPONSE_PROCESS.md +++ /dev/null @@ -1,143 +0,0 @@ -# Monero Vulnerability Response Process - -## Preamble - -Researchers/Hackers: while you research/hack, we ask that you please refrain from committing the following: -- Denial of Service / Active exploiting against the network -- Social Engineering of Monero staff or contractors -- Any physical or electronic attempts against Monero community property and/or data centers - -## I. Point of Contacts for Security Issues - -``` -ric@getmonero.org -BDA6 BD70 42B7 21C4 67A9 759D 7455 C5E3 C0CD CEB9 - -luigi1111@getmonero.org -8777 AB8F 778E E894 87A2 F8E7 F4AC A018 3641 E010 - -moneromooo.monero@gmail.com -48B0 8161 FBDA DFE3 93AD FC3E 686F 0745 4D6C EFC3 -``` - -## II. Security Response Team - -- fluffypony -- luigi1111 -- moneromooo - -## III. Incident Response - -1. Researcher submits report via one or both of two methods: - - a. Email - - b. [HackerOne](https://hackerone.com/monero) - -2. Response Team designates a Response Manager who is in charge of the particular report based on availability and/or knowledge-set - -3. In no more than 3 working days, Response Team should gratefully respond to researcher using only encrypted, secure channels - -4. Response Manager makes inquiries to satisfy any needed information to confirm if submission is indeed a vulnerability - - a. If submission proves to be vulnerable, proceed to next step - - b. If not vulnerable: - - i. Response Manager responds with reasons why submission is not a vulnerability - - ii. Response Manager moves discussion to a new or existing ticket on GitHub if necessary - -5. If over email, Response Manager opens a HackerOne issue for new submission - -6. Establish severity of vulnerability: - - a. HIGH: impacts network as a whole, has potential to break entire network, results in the loss of monero, or is on a scale of great catastrophe - - b. MEDIUM: impacts individual nodes, wallets, or must be carefully exploited - - c. LOW: is not easily exploitable - -7. Respond according to the severity of the vulnerability: - - a. HIGH severities must be notified on website and reddit /r/Monero within 3 working days of classification - - i. The notification should list appropriate steps for users to take, if any - - ii. The notification must not include any details that could suggest an exploitation path - - iii. The latter takes precedence over the former - - b. MEDIUM and HIGH severities will require a Point Release - - c. LOW severities will be addressed in the next Regular Release - -8. Response Team applies appropriate patch(es) - - a. Response Manager designates a PRIVATE git "hotfix branch" to work in - - b. Patches are reviewed with the researcher - - c. Any messages associated with PUBLIC commits during the time of review should not make reference to the security nature of the PRIVATE branch or its commits - - d. Vulnerability announcement is drafted - - i. Include the severity of the vulnerability - - ii. Include all vulnerable systems/apps/code - - iii. Include solutions (if any) if patch cannot be applied - - e. Release date is discussed - -9. At release date, Response Team coordinates with developers to finalize update: - - a. Response Manager propagates the "hotfix branch" to trunk - - b. Response Manager includes vulnerability announcement draft in release notes - - c. Proceed with the Point or Regular Release - -## IV. Post-release Disclosure Process - -1. Response Team has 90 days to fulfill all points within section III - -2. If the Incident Response process in section III is successfully completed: - - a. Response Manager contacts researcher and asks if researcher wishes for credit - - b. Finalize vulnerability announcement draft and include the following: - - i. Project name and URL - - ii. Versions known to be affected - - iii. Versions known to be not affected (for example, the vulnerable code was introduced in a recent version, and older versions are therefore unaffected) - - iv. Versions not checked - - v. Type of vulnerability and its impact - - vi. If already obtained or applicable, a CVE-ID - - vii. The planned, coordinated release date - - viii. Mitigating factors (for example, the vulnerability is only exposed in uncommon, non-default configurations) - - ix. Workarounds (configuration changes users can make to reduce their exposure to the vulnerability) - - x. If applicable, credits to the original reporter - - c. Release finalized vulnerability announcement on website and reddit /r/Monero - - d. For HIGH severities, release finalized vulnerability announcement on well-known mailing lists: - - i. oss-security@lists.openwall.com - - ii. bugtraq@securityfocus.com - - e. If applicable, developers request a CVE-ID - - i. The commit that applied the fix is made reference too in a future commit and includes a CVE-ID - -3. If the Incident Response process in section III is *not* successfully completed: - - a. Response Team and developers organize an IRC meeting to discuss why/what points in section III were not resolved and how the team can resolve them in the future - - b. Any developer meetings immediately following the incident should include points made in section V - - c. If disputes arise about whether or when to disclose information about a vulnerability, the Response Team will publicly discuss the issue via IRC and attempt to reach consensus - - d. If consensus on a timely disclosure is not met (no later than 90 days), the researcher (after 90 days) has every right to expose the vulnerability to the public - -## V. Incident Analysis - -1. Isolate codebase - - a. Response Team and developers should coordinate to work on the following: - - i. Problematic implementation of classes/libraries/functions, etc. - - ii. Focus on apps/distro packaging, etc. - - iii. Operator/config error, etc. - -2. Auditing - - a. Response Team and developers should coordinate to work on the following: - - i. Auditing of problem area(s) as discussed in point 1 - - ii. Generate internal reports and store for future reference - - iii. If results are not sensitive, share with the public via IRC or GitHub - -3. Response Team has 45 days following completion of section III to ensure completion of section V - -## VI. Resolutions - -Any further questions or resolutions regarding the incident(s) between the researcher and response + development team after public disclosure can be addressed via the following: - -- [GitHub](https://github.com/monero-project/monero/issues/) -- [HackerOne](https://hackerone.com/monero) -- [Reddit /r/Monero](https://reddit.com/r/Monero/) -- IRC -- Email - -## VII. Continuous Improvement - -1. Response Team and developers should hold annual meetings to review the previous year's incidents - -2. Response Team or designated person(s) should give a brief presentation, including: - - a. Areas of Monero affected by the incidents - - b. Any network downtime or monetary cost (if any) of the incidents - - c. Ways in which the incidents could have been avoided (if any) - - d. How effective this process was in dealing with the incidents - -3. After the presentation, Response Team and developers should discuss: - - a. Potential changes to development processes to reduce future incidents - - b. Potential changes to this process to improve future responses diff --git a/contrib/epee/include/net/abstract_tcp_server2.h b/contrib/epee/include/net/abstract_tcp_server2.h index ca58d5467..03f143fe4 100644 --- a/contrib/epee/include/net/abstract_tcp_server2.h +++ b/contrib/epee/include/net/abstract_tcp_server2.h @@ -281,8 +281,6 @@ namespace net_utils bool is_thread_worker(); - bool cleanup_connections(); - /// The io_service used to perform asynchronous operations. std::unique_ptr<boost::asio::io_service> m_io_service_local_instance; boost::asio::io_service& io_service_; @@ -309,7 +307,7 @@ namespace net_utils connection_ptr new_connection_; boost::mutex connections_mutex; - std::deque<std::pair<boost::system_time, connection_ptr>> connections_; + std::set<connection_ptr> connections_; }; // class <>boosted_tcp_server diff --git a/contrib/epee/include/net/abstract_tcp_server2.inl b/contrib/epee/include/net/abstract_tcp_server2.inl index 61276e761..76988a26e 100644 --- a/contrib/epee/include/net/abstract_tcp_server2.inl +++ b/contrib/epee/include/net/abstract_tcp_server2.inl @@ -54,8 +54,6 @@ #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "net" -#define CONNECTION_CLEANUP_TIME 30 // seconds - PRAGMA_WARNING_PUSH namespace epee { @@ -808,7 +806,6 @@ POP_WARNINGS m_threads_count = threads_count; m_main_thread_id = boost::this_thread::get_id(); MLOG_SET_THREAD_NAME("[SRV_MAIN]"); - add_idle_handler(boost::bind(&boosted_tcp_server::cleanup_connections, this), 5000); while(!m_stop_signal_sent) { @@ -898,7 +895,7 @@ POP_WARNINGS connections_mutex.lock(); for (auto &c: connections_) { - c.second->cancel(); + c->cancel(); } connections_.clear(); connections_mutex.unlock(); @@ -907,19 +904,6 @@ POP_WARNINGS } //--------------------------------------------------------------------------------- template<class t_protocol_handler> - bool boosted_tcp_server<t_protocol_handler>::cleanup_connections() - { - connections_mutex.lock(); - boost::system_time cutoff = boost::get_system_time() - boost::posix_time::seconds(CONNECTION_CLEANUP_TIME); - while (!connections_.empty() && connections_.front().first < cutoff) - { - connections_.pop_front(); - } - connections_mutex.unlock(); - return true; - } - //--------------------------------------------------------------------------------- - template<class t_protocol_handler> bool boosted_tcp_server<t_protocol_handler>::is_stop_signal_sent() { return m_stop_signal_sent; @@ -958,9 +942,10 @@ POP_WARNINGS connection_ptr new_connection_l(new connection<t_protocol_handler>(io_service_, m_config, m_sock_count, m_sock_number, m_pfilter, m_connection_type) ); connections_mutex.lock(); - connections_.push_back(std::make_pair(boost::get_system_time(), new_connection_l)); + connections_.insert(new_connection_l); MDEBUG("connections_ size now " << connections_.size()); connections_mutex.unlock(); + epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler([&](){ CRITICAL_REGION_LOCAL(connections_mutex); connections_.erase(new_connection_l); }); boost::asio::ip::tcp::socket& sock_ = new_connection_l->socket(); ////////////////////////////////////////////////////////////////////////// @@ -1038,6 +1023,10 @@ POP_WARNINGS _dbg3("Connected success to " << adr << ':' << port); + // start adds the connection to the config object's list, so we don't need to have it locally anymore + connections_mutex.lock(); + connections_.erase(new_connection_l); + connections_mutex.unlock(); bool r = new_connection_l->start(false, 1 < m_threads_count); if (r) { @@ -1062,9 +1051,10 @@ POP_WARNINGS TRY_ENTRY(); connection_ptr new_connection_l(new connection<t_protocol_handler>(io_service_, m_config, m_sock_count, m_sock_number, m_pfilter, m_connection_type) ); connections_mutex.lock(); - connections_.push_back(std::make_pair(boost::get_system_time(), new_connection_l)); + connections_.insert(new_connection_l); MDEBUG("connections_ size now " << connections_.size()); connections_mutex.unlock(); + epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler([&](){ CRITICAL_REGION_LOCAL(connections_mutex); connections_.erase(new_connection_l); }); boost::asio::ip::tcp::socket& sock_ = new_connection_l->socket(); ////////////////////////////////////////////////////////////////////////// @@ -1113,6 +1103,11 @@ POP_WARNINGS { _dbg3("[sock " << new_connection_l->socket().native_handle() << "] Connected success to " << adr << ':' << port << " from " << lep.address().to_string() << ':' << lep.port()); + + // start adds the connection to the config object's list, so we don't need to have it locally anymore + connections_mutex.lock(); + connections_.erase(new_connection_l); + connections_mutex.unlock(); bool r = new_connection_l->start(false, 1 < m_threads_count); if (r) { diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp index b6978bdc4..c6e24ef98 100644 --- a/src/blockchain_db/lmdb/db_lmdb.cpp +++ b/src/blockchain_db/lmdb/db_lmdb.cpp @@ -2420,8 +2420,8 @@ bool BlockchainLMDB::for_blocks_range(const uint64_t& h1, const uint64_t& h2, st MDB_cursor_op op; if (h1) { - MDB_val_set(k, h1); - op = MDB_SET; + k = MDB_val{sizeof(h1), (void*)&h1}; + op = MDB_SET; } else { op = MDB_FIRST; diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 19d90253b..50887e35c 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -47,6 +47,7 @@ endif() set(common_headers) set(common_private_headers + apply_permutation.h base58.h boost_serialization_helper.h command_line.h diff --git a/src/common/apply_permutation.h b/src/common/apply_permutation.h new file mode 100644 index 000000000..4fd952686 --- /dev/null +++ b/src/common/apply_permutation.h @@ -0,0 +1,68 @@ +// Copyright (c) 2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Most of this file is originally copyright (c) 2017 Raymond Chen, Microsoft +// This algorithm is adapted from Raymond Chen's code: +// https://blogs.msdn.microsoft.com/oldnewthing/20170109-00/?p=95145 + +#include <vector> +#include <functional> +#include "misc_log_ex.h" + +namespace tools +{ + +template<typename F> +void apply_permutation(std::vector<size_t> permutation, const F &swap) +{ + //sanity check + for (size_t n = 0; n < permutation.size(); ++n) + CHECK_AND_ASSERT_THROW_MES(std::find(permutation.begin(), permutation.end(), n) != permutation.end(), "Bad permutation"); + + for (size_t i = 0; i < permutation.size(); ++i) + { + size_t current = i; + while (i != permutation[current]) + { + size_t next = permutation[current]; + swap(current, next); + permutation[current] = current; + current = next; + } + permutation[current] = current; + } +} + +template<typename T> +void apply_permutation(const std::vector<size_t> &permutation, std::vector<T> &v) +{ + CHECK_AND_ASSERT_THROW_MES(permutation.size() == v.size(), "Mismatched vector sizes"); + apply_permutation(permutation, [&v](size_t i0, size_t i1){ std::swap(v[i0], v[i1]); }); +} + +} diff --git a/src/common/dns_utils.cpp b/src/common/dns_utils.cpp index e7ff11c5c..9c306505e 100644 --- a/src/common/dns_utils.cpp +++ b/src/common/dns_utils.cpp @@ -27,8 +27,6 @@ // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "common/dns_utils.h" -#include "common/i18n.h" -#include "cryptonote_basic/cryptonote_basic_impl.h" // check local first (in the event of static or in-source compilation of libunbound) #include "unbound.h" @@ -326,8 +324,6 @@ bool DNSResolver::check_address_syntax(const char *addr) const namespace dns_utils { -const char *tr(const char *str) { return i18n_translate(str, "tools::dns_utils"); } - //----------------------------------------------------------------------- // TODO: parse the string in a less stupid way, probably with regex std::string address_from_txt_record(const std::string& s) diff --git a/src/common/util.cpp b/src/common/util.cpp index 046961b06..74a6babf1 100644 --- a/src/common/util.cpp +++ b/src/common/util.cpp @@ -39,11 +39,13 @@ using namespace epee; #include "net/http_client.h" // epee::net_utils::... #ifdef WIN32 -#include <windows.h> -#include <shlobj.h> -#include <strsafe.h> + #include <windows.h> + #include <shlobj.h> + #include <strsafe.h> #else -#include <sys/utsname.h> + #include <sys/file.h> + #include <sys/utsname.h> + #include <sys/stat.h> #endif #include <boost/filesystem.hpp> #include <boost/asio.hpp> @@ -53,7 +55,12 @@ namespace tools { std::function<void(int)> signal_handler::m_handler; - std::unique_ptr<std::FILE, tools::close_file> create_private_file(const std::string& name) + private_file::private_file() noexcept : m_handle(), m_filename() {} + + private_file::private_file(std::FILE* handle, std::string&& filename) noexcept + : m_handle(handle), m_filename(std::move(filename)) {} + + private_file private_file::create(std::string name) { #ifdef WIN32 struct close_handle @@ -70,17 +77,17 @@ namespace tools const bool fail = OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, std::addressof(temp)) == 0; process.reset(temp); if (fail) - return nullptr; + return {}; } DWORD sid_size = 0; GetTokenInformation(process.get(), TokenOwner, nullptr, 0, std::addressof(sid_size)); if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) - return nullptr; + return {}; std::unique_ptr<char[]> sid{new char[sid_size]}; if (!GetTokenInformation(process.get(), TokenOwner, sid.get(), sid_size, std::addressof(sid_size))) - return nullptr; + return {}; const PSID psid = reinterpret_cast<const PTOKEN_OWNER>(sid.get())->Owner; const DWORD daclSize = @@ -88,17 +95,17 @@ namespace tools const std::unique_ptr<char[]> dacl{new char[daclSize]}; if (!InitializeAcl(reinterpret_cast<PACL>(dacl.get()), daclSize, ACL_REVISION)) - return nullptr; + return {}; if (!AddAccessAllowedAce(reinterpret_cast<PACL>(dacl.get()), ACL_REVISION, (READ_CONTROL | FILE_GENERIC_READ | DELETE), psid)) - return nullptr; + return {}; SECURITY_DESCRIPTOR descriptor{}; if (!InitializeSecurityDescriptor(std::addressof(descriptor), SECURITY_DESCRIPTOR_REVISION)) - return nullptr; + return {}; if (!SetSecurityDescriptorDacl(std::addressof(descriptor), true, reinterpret_cast<PACL>(dacl.get()), false)) - return nullptr; + return {}; SECURITY_ATTRIBUTES attributes{sizeof(SECURITY_ATTRIBUTES), std::addressof(descriptor), false}; std::unique_ptr<void, close_handle> file{ @@ -106,7 +113,7 @@ namespace tools name.c_str(), GENERIC_WRITE, FILE_SHARE_READ, std::addressof(attributes), - CREATE_NEW, FILE_ATTRIBUTE_TEMPORARY, + CREATE_NEW, (FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE), nullptr ) }; @@ -121,22 +128,49 @@ namespace tools { _close(fd); } - return {real_file, tools::close_file{}}; + return {real_file, std::move(name)}; } } #else - const int fd = open(name.c_str(), (O_RDWR | O_EXCL | O_CREAT), S_IRUSR); - if (0 <= fd) + const int fdr = open(name.c_str(), (O_RDONLY | O_CREAT), S_IRUSR); + if (0 <= fdr) { - std::FILE* file = fdopen(fd, "w"); - if (!file) + struct stat rstats = {}; + if (fstat(fdr, std::addressof(rstats)) != 0) { - close(fd); + close(fdr); + return {}; + } + fchmod(fdr, (S_IRUSR | S_IWUSR)); + const int fdw = open(name.c_str(), O_RDWR); + fchmod(fdr, rstats.st_mode); + close(fdr); + + if (0 <= fdw) + { + struct stat wstats = {}; + if (fstat(fdw, std::addressof(wstats)) == 0 && + rstats.st_dev == wstats.st_dev && rstats.st_ino == wstats.st_ino && + flock(fdw, (LOCK_EX | LOCK_NB)) == 0 && ftruncate(fdw, 0) == 0) + { + std::FILE* file = fdopen(fdw, "w"); + if (file) return {file, std::move(name)}; + } + close(fdw); } - return {file, tools::close_file{}}; } #endif - return nullptr; + return {}; + } + + private_file::~private_file() noexcept + { + try + { + boost::system::error_code ec{}; + boost::filesystem::remove(filename(), ec); + } + catch (...) {} } #ifdef WIN32 diff --git a/src/common/util.h b/src/common/util.h index 2452bc9d5..48bdbbc28 100644 --- a/src/common/util.h +++ b/src/common/util.h @@ -60,8 +60,30 @@ namespace tools } }; - //! \return File only readable by owner. nullptr if `filename` exists. - std::unique_ptr<std::FILE, close_file> create_private_file(const std::string& filename); + //! A file restricted to process owner AND process. Deletes file on destruction. + class private_file { + std::unique_ptr<std::FILE, close_file> m_handle; + std::string m_filename; + + private_file(std::FILE* handle, std::string&& filename) noexcept; + public: + + //! `handle() == nullptr && filename.empty()`. + private_file() noexcept; + + /*! \return File only readable by owner and only used by this process + OR `private_file{}` on error. */ + static private_file create(std::string filename); + + private_file(private_file&&) = default; + private_file& operator=(private_file&&) = default; + + //! Deletes `filename()` and closes `handle()`. + ~private_file() noexcept; + + std::FILE* handle() const noexcept { return m_handle.get(); } + const std::string& filename() const noexcept { return m_filename; } + }; /*! \brief Returns the default data directory. * diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp index 745dfb72e..e73f5d778 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.cpp +++ b/src/cryptonote_basic/cryptonote_format_utils.cpp @@ -869,4 +869,21 @@ namespace cryptonote block_hashes_calculated = block_hashes_calculated_count; block_hashes_cached = block_hashes_cached_count; } + //--------------------------------------------------------------- + crypto::secret_key encrypt_key(const crypto::secret_key &key, const std::string &passphrase) + { + crypto::hash hash; + crypto::cn_slow_hash(passphrase.data(), passphrase.size(), hash); + sc_add((unsigned char*)key.data, (const unsigned char*)key.data, (const unsigned char*)hash.data); + return key; + } + //--------------------------------------------------------------- + crypto::secret_key decrypt_key(const crypto::secret_key &key, const std::string &passphrase) + { + crypto::hash hash; + crypto::cn_slow_hash(passphrase.data(), passphrase.size(), hash); + sc_sub((unsigned char*)key.data, (const unsigned char*)key.data, (const unsigned char*)hash.data); + return key; + } + } diff --git a/src/cryptonote_basic/cryptonote_format_utils.h b/src/cryptonote_basic/cryptonote_format_utils.h index d8ccf8eec..00080fb98 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.h +++ b/src/cryptonote_basic/cryptonote_format_utils.h @@ -212,6 +212,8 @@ namespace cryptonote bool is_valid_decomposed_amount(uint64_t amount); void get_hash_stats(uint64_t &tx_hashes_calculated, uint64_t &tx_hashes_cached, uint64_t &block_hashes_calculated, uint64_t & block_hashes_cached); + crypto::secret_key encrypt_key(const crypto::secret_key &key, const std::string &passphrase); + crypto::secret_key decrypt_key(const crypto::secret_key &key, const std::string &passphrase); #define CHECKED_GET_SPECIFIC_VARIANT(variant_var, specific_type, variable_name, fail_return_val) \ CHECK_AND_ASSERT_MES(variant_var.type() == typeid(specific_type), fail_return_val, "wrong variant type: " << variant_var.type().name() << ", expected " << typeid(specific_type).name()); \ specific_type& variable_name = boost::get<specific_type>(variant_var); diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 619dbdc07..7ee88e430 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -2384,6 +2384,26 @@ bool Blockchain::check_tx_outputs(const transaction& tx, tx_verification_context } } + // from v7, sorted outs + if (m_hardfork->get_current_version() >= 7) { + const crypto::public_key *last_key = NULL; + for (size_t n = 0; n < tx.vout.size(); ++n) + { + const tx_out &o = tx.vout[n]; + if (o.target.type() == typeid(txout_to_key)) + { + const txout_to_key& out_to_key = boost::get<txout_to_key>(o.target); + if (last_key && memcmp(&out_to_key.key, last_key, sizeof(*last_key)) >= 0) + { + MERROR_VER("transaction has unsorted outputs"); + tvc.m_invalid_output = true; + return false; + } + last_key = &out_to_key.key; + } + } + } + return true; } //------------------------------------------------------------------ @@ -2552,6 +2572,25 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, } } + // from v7, sorted ins + if (hf_version >= 7) { + const crypto::key_image *last_key_image = NULL; + for (size_t n = 0; n < tx.vin.size(); ++n) + { + const txin_v &txin = tx.vin[n]; + if (txin.type() == typeid(txin_to_key)) + { + const txin_to_key& in_to_key = boost::get<txin_to_key>(txin); + if (last_key_image && memcmp(&in_to_key.k_image, last_key_image, sizeof(*last_key_image)) >= 0) + { + MERROR_VER("transaction has unsorted inputs"); + tvc.m_verifivation_failed = true; + return false; + } + last_key_image = &in_to_key.k_image; + } + } + } auto it = m_check_txin_table.find(tx_prefix_hash); if(it == m_check_txin_table.end()) { @@ -3276,7 +3315,7 @@ leave: // XXX old code adds miner tx here - int tx_index = 0; + size_t tx_index = 0; // Iterate over the block's transaction hashes, grabbing each // from the tx_pool and validating them. Each is then added // to txs. Keys spent in each are added to <keys> by the double spend check. @@ -3358,7 +3397,7 @@ leave: { // ND: if fast_check is enabled for blocks, there is no need to check // the transaction inputs, but do some sanity checks anyway. - if (memcmp(&m_blocks_txs_check[tx_index++], &tx_id, sizeof(tx_id)) != 0) + if (tx_index >= m_blocks_txs_check.size() || memcmp(&m_blocks_txs_check[tx_index++], &tx_id, sizeof(tx_id)) != 0) { MERROR_VER("Block with id: " << id << " has at least one transaction (id: " << tx_id << ") with wrong inputs."); //TODO: why is this done? make sure that keeping invalid blocks makes sense. diff --git a/src/cryptonote_core/cryptonote_tx_utils.cpp b/src/cryptonote_core/cryptonote_tx_utils.cpp index 94f069827..9b442029a 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.cpp +++ b/src/cryptonote_core/cryptonote_tx_utils.cpp @@ -31,6 +31,7 @@ #include "include_base_utils.h" using namespace epee; +#include "common/apply_permutation.h" #include "cryptonote_tx_utils.h" #include "cryptonote_config.h" #include "cryptonote_basic/miner.h" @@ -156,7 +157,7 @@ namespace cryptonote return destinations[0].addr.m_view_public_key; } //--------------------------------------------------------------- - bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, bool rct) + bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, std::vector<tx_source_entry> sources, const std::vector<tx_destination_entry>& destinations, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, bool rct) { std::vector<rct::key> amount_keys; tx.set_null(); @@ -263,14 +264,25 @@ namespace cryptonote tx.vin.push_back(input_to_key); } - // "Shuffle" outs - std::vector<tx_destination_entry> shuffled_dsts(destinations); - std::random_shuffle(shuffled_dsts.begin(), shuffled_dsts.end(), [](unsigned int i) { return crypto::rand<unsigned int>() % i; }); + // sort ins by their key image + std::vector<size_t> ins_order(sources.size()); + for (size_t n = 0; n < sources.size(); ++n) + ins_order[n] = n; + std::sort(ins_order.begin(), ins_order.end(), [&](const size_t i0, const size_t i1) { + const txin_to_key &tk0 = boost::get<txin_to_key>(tx.vin[i0]); + const txin_to_key &tk1 = boost::get<txin_to_key>(tx.vin[i1]); + return memcmp(&tk0.k_image, &tk1.k_image, sizeof(tk0.k_image)) < 0; + }); + tools::apply_permutation(ins_order, [&] (size_t i0, size_t i1) { + std::swap(tx.vin[i0], tx.vin[i1]); + std::swap(in_contexts[i0], in_contexts[i1]); + std::swap(sources[i0], sources[i1]); + }); uint64_t summary_outs_money = 0; //fill outputs size_t output_index = 0; - for(const tx_destination_entry& dst_entr: shuffled_dsts) + for(const tx_destination_entry& dst_entr: destinations) { CHECK_AND_ASSERT_MES(dst_entr.amount > 0 || tx.version > 1, false, "Destination with wrong amount: " << dst_entr.amount); crypto::key_derivation derivation; @@ -297,6 +309,20 @@ namespace cryptonote summary_outs_money += dst_entr.amount; } + // sort outs by their public key + std::vector<size_t> outs_order(tx.vout.size()); + for (size_t n = 0; n < tx.vout.size(); ++n) + outs_order[n] = n; + std::sort(outs_order.begin(), outs_order.end(), [&](size_t i0, size_t i1) { + const txout_to_key &tk0 = boost::get<txout_to_key>(tx.vout[i0].target); + const txout_to_key &tk1 = boost::get<txout_to_key>(tx.vout[i1].target); + return memcmp(&tk0.key, &tk1.key, sizeof(tk0.key)) < 0; + }); + tools::apply_permutation(outs_order, [&] (size_t i0, size_t i1) { + std::swap(tx.vout[i0], tx.vout[i1]); + std::swap(amount_keys[i0], amount_keys[i1]); + }); + //check money if(summary_outs_money > summary_inputs_money ) { diff --git a/src/cryptonote_core/cryptonote_tx_utils.h b/src/cryptonote_core/cryptonote_tx_utils.h index 7aa7c280d..69254fb5f 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.h +++ b/src/cryptonote_core/cryptonote_tx_utils.h @@ -71,7 +71,7 @@ namespace cryptonote //--------------------------------------------------------------- crypto::public_key get_destination_view_key_pub(const std::vector<tx_destination_entry> &destinations, const account_keys &sender_keys); bool construct_tx(const account_keys& sender_account_keys, const std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time); - bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, bool rct = false); + bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, std::vector<tx_source_entry> sources, const std::vector<tx_destination_entry>& destinations, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, bool rct = false); bool generate_genesis_block( block& bl diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp index 7de392036..c31441a99 100644 --- a/src/cryptonote_core/tx_pool.cpp +++ b/src/cryptonote_core/tx_pool.cpp @@ -1090,12 +1090,13 @@ namespace cryptonote m_txs_by_fee_and_receive_time.clear(); m_spent_key_images.clear(); - return m_blockchain.for_all_txpool_txes([this](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata *bd) { + std::vector<crypto::hash> remove; + bool r = m_blockchain.for_all_txpool_txes([this, &remove](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata *bd) { cryptonote::transaction tx; if (!parse_and_validate_tx_from_blob(*bd, tx)) { - MERROR("Failed to parse tx from txpool"); - return false; + MWARNING("Failed to parse tx from txpool, removing"); + remove.push_back(txid); } if (!insert_key_images(tx, meta.kept_by_block)) { @@ -1105,6 +1106,25 @@ namespace cryptonote m_txs_by_fee_and_receive_time.emplace(std::pair<double, time_t>(meta.fee / (double)meta.blob_size, meta.receive_time), txid); return true; }, true); + if (!r) + return false; + if (!remove.empty()) + { + LockedTXN lock(m_blockchain); + for (const auto &txid: remove) + { + try + { + m_blockchain.remove_txpool_tx(txid); + } + catch (const std::exception &e) + { + MWARNING("Failed to remove corrupt transaction: " << txid); + // ignore error + } + } + } + return true; } //--------------------------------------------------------------------------------- diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index cda6f3f95..167e24ed3 100644 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -782,7 +782,7 @@ bool t_rpc_command_executor::is_key_image_spent(const crypto::key_image &ki) { if (1 == res.spent_status.size()) { // first as hex - tools::success_msg_writer() << ki << ": " << (res.spent_status.front() ? "spent" : "unspent"); + tools::success_msg_writer() << ki << ": " << (res.spent_status.front() ? "spent" : "unspent") << (res.spent_status.front() == cryptonote::COMMAND_RPC_IS_KEY_IMAGE_SPENT::SPENT_IN_POOL ? " (in pool)" : ""); } else { diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl index 13352c24a..f1ca50f76 100644 --- a/src/p2p/net_node.inl +++ b/src/p2p/net_node.inl @@ -1090,6 +1090,8 @@ namespace nodetool if (use_white_list) { local_peers_count = m_peerlist.get_white_peers_count(); + if (!local_peers_count) + return false; max_random_index = std::min<uint64_t>(local_peers_count -1, 20); random_index = get_random_index_with_fixed_probability(max_random_index); } else { @@ -1914,8 +1916,13 @@ namespace nodetool template<class t_payload_net_handler> bool node_server<t_payload_net_handler>::gray_peerlist_housekeeping() { + if (!m_exclusive_peers.empty()) return true; + peerlist_entry pe = AUTO_VAL_INIT(pe); + if (m_net_server.is_stop_signal_sent()) + return false; + if (!m_peerlist.get_random_gray_peer(pe)) { return false; } diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 1f7f4a1ff..35b76f3d6 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -478,18 +478,30 @@ namespace cryptonote bool r = m_core.get_pool_transactions(pool_txs); if(r) { - for (std::list<transaction>::const_iterator i = pool_txs.begin(); i != pool_txs.end(); ++i) + // sort to match original request + std::list<transaction> sorted_txs; + std::list<cryptonote::transaction>::const_iterator i; + for (const crypto::hash &h: vh) { - crypto::hash tx_hash = get_transaction_hash(*i); - std::list<crypto::hash>::iterator mi = std::find(missed_txs.begin(), missed_txs.end(), tx_hash); - if (mi != missed_txs.end()) + if (std::find(missed_txs.begin(), missed_txs.end(), h) == missed_txs.end()) { - pool_tx_hashes.insert(tx_hash); - missed_txs.erase(mi); - txs.push_back(*i); + // core returns the ones it finds in the right order + if (get_transaction_hash(txs.front()) != h) + { + res.status = "Failed: tx hash mismatch"; + return true; + } + sorted_txs.push_back(std::move(txs.front())); + txs.pop_front(); + } + else if ((i = std::find_if(pool_txs.begin(), pool_txs.end(), [h](cryptonote::transaction &tx) { return h == cryptonote::get_transaction_hash(tx); })) != pool_txs.end()) + { + sorted_txs.push_back(*i); + missed_txs.remove(h); ++found_in_pool; } } + txs = sorted_txs; } LOG_PRINT_L2("Found " << found_in_pool << "/" << vh.size() << " transactions in the pool"); } @@ -510,11 +522,12 @@ namespace cryptonote e.in_pool = pool_tx_hashes.find(tx_hash) != pool_tx_hashes.end(); if (e.in_pool) { - e.block_height = std::numeric_limits<uint64_t>::max(); + e.block_height = e.block_timestamp = std::numeric_limits<uint64_t>::max(); } else { e.block_height = m_core.get_blockchain_storage().get_db().get_tx_block_height(tx_hash); + e.block_timestamp = m_core.get_blockchain_storage().get_db().get_block_timestamp(e.block_height); } // fill up old style responses too, in case an old wallet asks @@ -948,7 +961,7 @@ namespace cryptonote LOG_ERROR("Failed to find tx pub key in blockblob"); return false; } - res.reserved_offset += sizeof(tx_pub_key) + 3; //3 bytes: tag for TX_EXTRA_TAG_PUBKEY(1 byte), tag for TX_EXTRA_NONCE(1 byte), counter in TX_EXTRA_NONCE(1 byte) + res.reserved_offset += sizeof(tx_pub_key) + 2; //2 bytes: tag for TX_EXTRA_NONCE(1 byte), counter in TX_EXTRA_NONCE(1 byte) if(res.reserved_offset + req.reserve_size > block_blob.size()) { error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 88327dd75..99430bf20 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -49,7 +49,7 @@ namespace cryptonote // advance which version they will stop working with // Don't go over 32767 for any of these #define CORE_RPC_VERSION_MAJOR 1 -#define CORE_RPC_VERSION_MINOR 13 +#define CORE_RPC_VERSION_MINOR 14 #define MAKE_CORE_RPC_VERSION(major,minor) (((major)<<16)|(minor)) #define CORE_RPC_VERSION MAKE_CORE_RPC_VERSION(CORE_RPC_VERSION_MAJOR, CORE_RPC_VERSION_MINOR) @@ -215,6 +215,7 @@ namespace cryptonote std::string as_json; bool in_pool; uint64_t block_height; + uint64_t block_timestamp; std::vector<uint64_t> output_indices; BEGIN_KV_SERIALIZE_MAP() @@ -223,6 +224,7 @@ namespace cryptonote KV_SERIALIZE(as_json) KV_SERIALIZE(in_pool) KV_SERIALIZE(block_height) + KV_SERIALIZE(block_timestamp) KV_SERIALIZE(output_indices) END_KV_SERIALIZE_MAP() }; diff --git a/src/serialization/json_object.cpp b/src/serialization/json_object.cpp index ead3fdd58..e35389f9c 100644 --- a/src/serialization/json_object.cpp +++ b/src/serialization/json_object.cpp @@ -29,6 +29,7 @@ #include "json_object.h" #include <limits> +#include <type_traits> #include "string_tools.h" namespace cryptonote @@ -52,8 +53,9 @@ namespace void convert_numeric(Source source, Type& i) { static_assert( + (std::is_same<Type, char>() && std::is_same<Source, int>()) || std::numeric_limits<Source>::is_signed == std::numeric_limits<Type>::is_signed, - "source and destination signs do not match" + "comparisons below may have undefined behavior" ); if (source < std::numeric_limits<Type>::min()) { diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 3fe68046b..481668299 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -290,7 +290,7 @@ bool simple_wallet::spendkey(const std::vector<std::string> &args/* = std::vecto return true; } -bool simple_wallet::seed(const std::vector<std::string> &args/* = std::vector<std::string>()*/) +bool simple_wallet::print_seed(bool encrypted) { bool success = false; std::string electrum_words; @@ -311,7 +311,16 @@ bool simple_wallet::seed(const std::vector<std::string> &args/* = std::vector<st m_wallet->set_seed_language(mnemonic_language); } - success = m_wallet->get_seed(electrum_words); + std::string seed_pass; + if (encrypted) + { + auto pwd_container = tools::password_container::prompt(true, tr("Enter optional seed encryption passphrase, empty to see raw seed")); + if (std::cin.eof() || !pwd_container) + return true; + seed_pass = pwd_container->password(); + } + + success = m_wallet->get_seed(electrum_words, seed_pass); } if (success) @@ -325,6 +334,16 @@ bool simple_wallet::seed(const std::vector<std::string> &args/* = std::vector<st return true; } +bool simple_wallet::seed(const std::vector<std::string> &args/* = std::vector<std::string>()*/) +{ + return print_seed(false); +} + +bool simple_wallet::encrypted_seed(const std::vector<std::string> &args/* = std::vector<std::string>()*/) +{ + return print_seed(true); +} + bool simple_wallet::seed_set_language(const std::vector<std::string> &args/* = std::vector<std::string>()*/) { if (m_wallet->watch_only()) @@ -757,6 +776,7 @@ simple_wallet::simple_wallet() m_cmd_binder.set_handler("spendkey", boost::bind(&simple_wallet::spendkey, this, _1), tr("Display private spend key")); m_cmd_binder.set_handler("seed", boost::bind(&simple_wallet::seed, this, _1), tr("Display Electrum-style mnemonic seed")); m_cmd_binder.set_handler("set", boost::bind(&simple_wallet::set_variable, this, _1), tr("Available options: seed language - set wallet seed language; always-confirm-transfers <1|0> - whether to confirm unsplit txes; print-ring-members <1|0> - whether to print detailed information about ring members during confirmation; store-tx-info <1|0> - whether to store outgoing tx info (destination address, payment ID, tx secret key) for future reference; default-ring-size <n> - set default ring size (default is 5); auto-refresh <1|0> - whether to automatically sync new blocks from the daemon; refresh-type <full|optimize-coinbase|no-coinbase|default> - set wallet refresh behaviour; priority [0|1|2|3|4] - default/unimportant/normal/elevated/priority fee; confirm-missing-payment-id <1|0>; ask-password <1|0>; unit <monero|millinero|micronero|nanonero|piconero> - set default monero (sub-)unit; min-outputs-count [n] - try to keep at least that many outputs of value at least min-outputs-value; min-outputs-value [n] - try to keep at least min-outputs-count outputs of at least that value; merge-destinations <1|0> - whether to merge multiple payments to the same destination address; confirm-backlog <1|0> - whether to warn if there is transaction backlog")); + m_cmd_binder.set_handler("encrypted_seed", boost::bind(&simple_wallet::encrypted_seed, this, _1), tr("Display encrypted Electrum-style mnemonic seed")); m_cmd_binder.set_handler("rescan_spent", boost::bind(&simple_wallet::rescan_spent, this, _1), tr("Rescan blockchain for spent outputs")); m_cmd_binder.set_handler("get_tx_key", boost::bind(&simple_wallet::get_tx_key, this, _1), tr("Get transaction key (r) for a given <txid>")); m_cmd_binder.set_handler("check_tx_key", boost::bind(&simple_wallet::check_tx_key, this, _1), tr("Check amount going to <address> in <txid>")); @@ -1027,6 +1047,13 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) fail_msg_writer() << tr("Electrum-style word list failed verification"); return false; } + + auto pwd_container = tools::password_container::prompt(false, tr("Enter seed encryption passphrase, empty if none")); + if (std::cin.eof() || !pwd_container) + return false; + std::string seed_pass = pwd_container->password(); + if (!seed_pass.empty()) + m_recovery_key = cryptonote::decrypt_key(m_recovery_key, seed_pass); } if (!m_generate_from_view_key.empty()) { diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index 30c428810..3b29e3ca2 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -97,6 +97,7 @@ namespace cryptonote bool viewkey(const std::vector<std::string> &args = std::vector<std::string>()); bool spendkey(const std::vector<std::string> &args = std::vector<std::string>()); bool seed(const std::vector<std::string> &args = std::vector<std::string>()); + bool encrypted_seed(const std::vector<std::string> &args = std::vector<std::string>()); /*! * \brief Sets seed language. @@ -186,6 +187,7 @@ namespace cryptonote bool accept_loaded_tx(const tools::wallet2::signed_tx_set &txs); bool print_ring_members(const std::vector<tools::wallet2::pending_tx>& ptx_vector, std::ostream& ostr); std::string get_prompt() const; + bool print_seed(bool encrypted); /*! * \brief Prints the seed with a nice message diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index 7afc1f449..9cd72b543 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -303,7 +303,7 @@ WalletImpl::~WalletImpl() // Pause refresh thread - prevents refresh from starting again pauseRefresh(); // Close wallet - stores cache and stops ongoing refresh operation - close(); + close(false); // do not store wallet as part of the closing activities // Stop refresh thread stopRefresh(); delete m_wallet2Callback; @@ -566,19 +566,21 @@ bool WalletImpl::recover(const std::string &path, const std::string &seed) return m_status == Status_Ok; } -bool WalletImpl::close() +bool WalletImpl::close(bool store) { bool result = false; LOG_PRINT_L1("closing wallet..."); try { - // Do not store wallet with invalid status - // Status Critical refers to errors on opening or creating wallets. - if (status() != Status_Critical) - m_wallet->store(); - else - LOG_ERROR("Status_Critical - not storing wallet"); - LOG_PRINT_L1("wallet::store done"); + if (store) { + // Do not store wallet with invalid status + // Status Critical refers to errors on opening or creating wallets. + if (status() != Status_Critical) + m_wallet->store(); + else + LOG_ERROR("Status_Critical - not storing wallet"); + LOG_PRINT_L1("wallet::store done"); + } LOG_PRINT_L1("Calling wallet::stop..."); m_wallet->stop(); LOG_PRINT_L1("wallet::stop done"); diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h index 8190c7873..36ffd4fc0 100644 --- a/src/wallet/api/wallet.h +++ b/src/wallet/api/wallet.h @@ -63,7 +63,7 @@ public: const std::string &address_string, const std::string &viewkey_string, const std::string &spendkey_string = ""); - bool close(); + bool close(bool store = true); std::string seed() const; std::string getSeedLanguage() const; void setSeedLanguage(const std::string &arg); diff --git a/src/wallet/api/wallet_manager.cpp b/src/wallet/api/wallet_manager.cpp index a23533530..4b988a417 100644 --- a/src/wallet/api/wallet_manager.cpp +++ b/src/wallet/api/wallet_manager.cpp @@ -102,10 +102,10 @@ Wallet *WalletManagerImpl::createWalletFromKeys(const std::string &path, return wallet; } -bool WalletManagerImpl::closeWallet(Wallet *wallet) +bool WalletManagerImpl::closeWallet(Wallet *wallet, bool store) { WalletImpl * wallet_ = dynamic_cast<WalletImpl*>(wallet); - bool result = wallet_->close(); + bool result = wallet_->close(store); if (!result) { m_errorString = wallet_->errorString(); } else { diff --git a/src/wallet/api/wallet_manager.h b/src/wallet/api/wallet_manager.h index aa6ea439e..8455f0f16 100644 --- a/src/wallet/api/wallet_manager.h +++ b/src/wallet/api/wallet_manager.h @@ -48,7 +48,7 @@ public: const std::string &addressString, const std::string &viewKeyString, const std::string &spendKeyString = ""); - virtual bool closeWallet(Wallet *wallet); + virtual bool closeWallet(Wallet *wallet, bool store = true); bool walletExists(const std::string &path); bool verifyWalletPassword(const std::string &keys_file_name, const std::string &password, bool watch_only) const; std::vector<std::string> findWallets(const std::string &path); diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 805d5c737..805703027 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -289,6 +289,13 @@ std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file, return false; } restore_deterministic_wallet = true; + + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, seed_passphrase, std::string, String, false, std::string()); + if (field_seed_passphrase_found) + { + if (!field_seed_passphrase.empty()) + recovery_key = cryptonote::decrypt_key(recovery_key, field_seed_passphrase); + } } GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, address, std::string, String, false, std::string()); @@ -520,7 +527,7 @@ bool wallet2::is_deterministic() const return keys_deterministic; } //---------------------------------------------------------------------------------------------------- -bool wallet2::get_seed(std::string& electrum_words) const +bool wallet2::get_seed(std::string& electrum_words, const std::string &passphrase) const { bool keys_deterministic = is_deterministic(); if (!keys_deterministic) @@ -534,7 +541,10 @@ bool wallet2::get_seed(std::string& electrum_words) const return false; } - crypto::ElectrumWords::bytes_to_words(get_account().get_keys().m_spend_secret_key, electrum_words, seed_language); + crypto::secret_key key = get_account().get_keys().m_spend_secret_key; + if (!passphrase.empty()) + key = cryptonote::encrypt_key(key, passphrase); + crypto::ElectrumWords::bytes_to_words(key, electrum_words, seed_language); return true; } @@ -1529,23 +1539,22 @@ void wallet2::update_pool_state(bool refreshed) { if (res.txs.size() == txids.size()) { - size_t n = 0; - for (const auto &txid: txids) + for (const auto &tx_entry: res.txs) { - // might have just been put in a block - if (res.txs[n].in_pool) + if (tx_entry.in_pool) { cryptonote::transaction tx; cryptonote::blobdata bd; crypto::hash tx_hash, tx_prefix_hash; - if (epee::string_tools::parse_hexstr_to_binbuff(res.txs[n].as_hex, bd)) + if (epee::string_tools::parse_hexstr_to_binbuff(tx_entry.as_hex, bd)) { if (cryptonote::parse_and_validate_tx_from_blob(bd, tx, tx_hash, tx_prefix_hash)) { - if (tx_hash == txid) + const std::vector<crypto::hash>::const_iterator i = std::find(txids.begin(), txids.end(), tx_hash); + if (i != txids.end()) { - process_new_transaction(txid, tx, std::vector<uint64_t>(), 0, time(NULL), false, true); - m_scanned_pool_txs[0].insert(txid); + process_new_transaction(tx_hash, tx, std::vector<uint64_t>(), 0, time(NULL), false, true); + m_scanned_pool_txs[0].insert(tx_hash); if (m_scanned_pool_txs[0].size() > 5000) { std::swap(m_scanned_pool_txs[0], m_scanned_pool_txs[1]); @@ -1554,7 +1563,7 @@ void wallet2::update_pool_state(bool refreshed) } else { - LOG_PRINT_L0("Mismatched txids when processing unconfimed txes from pool"); + MERROR("Got txid " << tx_hash << " which we did not ask for"); } } else @@ -1564,14 +1573,13 @@ void wallet2::update_pool_state(bool refreshed) } else { - LOG_PRINT_L0("Failed to parse tx " << txid); + LOG_PRINT_L0("Failed to parse transaction from daemon"); } } else { - LOG_PRINT_L1("Tx " << txid << " was in pool, but is no more"); + LOG_PRINT_L1("Transaction from daemon was in pool, but is no more"); } - ++n; } } else @@ -5381,6 +5389,9 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag } spent = 0; unspent = 0; + std::unordered_set<crypto::hash> spent_txids; // For each spent key image, search for a tx in m_transfers that uses it as input. + std::vector<size_t> swept_transfers; // If such a spending tx wasn't found in m_transfers, this means the spending tx + // was created by sweep_all, so we can't know the spent height and other detailed info. for(size_t i = 0; i < m_transfers.size(); ++i) { transfer_details &td = m_transfers[i]; @@ -5391,8 +5402,146 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag unspent += amount; LOG_PRINT_L2("Transfer " << i << ": " << print_money(amount) << " (" << td.m_global_output_index << "): " << (td.m_spent ? "spent" : "unspent") << " (key image " << req.key_images[i] << ")"); + + if (i < daemon_resp.spent_status.size() && daemon_resp.spent_status[i] == COMMAND_RPC_IS_KEY_IMAGE_SPENT::SPENT_IN_BLOCKCHAIN) + { + bool is_spent_tx_found = false; + for (auto it = m_transfers.rbegin(); &(*it) != &td; ++it) + { + bool is_spent_tx = false; + for(const cryptonote::txin_v& in : it->m_tx.vin) + { + if(in.type() == typeid(cryptonote::txin_to_key) && td.m_key_image == boost::get<cryptonote::txin_to_key>(in).k_image) + { + is_spent_tx = true; + break; + } + } + if (is_spent_tx) + { + is_spent_tx_found = true; + spent_txids.insert(it->m_txid); + break; + } + } + + if (!is_spent_tx_found) + swept_transfers.push_back(i); + } } MDEBUG("Total: " << print_money(spent) << " spent, " << print_money(unspent) << " unspent"); + + if (check_spent) + { + // query outgoing txes + COMMAND_RPC_GET_TRANSACTIONS::request gettxs_req; + COMMAND_RPC_GET_TRANSACTIONS::response gettxs_res; + gettxs_req.decode_as_json = false; + for (const crypto::hash& spent_txid : spent_txids) + gettxs_req.txs_hashes.push_back(epee::string_tools::pod_to_hex(spent_txid)); + m_daemon_rpc_mutex.lock(); + bool r = epee::net_utils::invoke_http_json("/gettransactions", gettxs_req, gettxs_res, m_http_client, rpc_timeout); + m_daemon_rpc_mutex.unlock(); + THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "gettransactions"); + THROW_WALLET_EXCEPTION_IF(gettxs_res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "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())); + + // process each outgoing tx + auto spent_txid = spent_txids.begin(); + for (const COMMAND_RPC_GET_TRANSACTIONS::entry& e : gettxs_res.txs) + { + THROW_WALLET_EXCEPTION_IF(e.in_pool, error::wallet_internal_error, "spent tx isn't supposed to be in txpool"); + + // parse tx + cryptonote::blobdata bd; + THROW_WALLET_EXCEPTION_IF(!epee::string_tools::parse_hexstr_to_binbuff(e.as_hex, bd), error::wallet_internal_error, "parse_hexstr_to_binbuff failed"); + cryptonote::transaction spent_tx; + crypto::hash spnet_txid_parsed, spent_txid_prefix; + THROW_WALLET_EXCEPTION_IF(!cryptonote::parse_and_validate_tx_from_blob(bd, spent_tx, spnet_txid_parsed, spent_txid_prefix), error::wallet_internal_error, "parse_and_validate_tx_from_blob failed"); + THROW_WALLET_EXCEPTION_IF(*spent_txid != spnet_txid_parsed, error::wallet_internal_error, "parsed txid mismatch"); + + // get received (change) amount + uint64_t tx_money_got_in_outs = 0; + const cryptonote::account_keys& keys = m_account.get_keys(); + const crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(spent_tx); + crypto::key_derivation derivation; + generate_key_derivation(tx_pub_key, keys.m_view_secret_key, derivation); + size_t output_index = 0; + for (const cryptonote::tx_out& out : spent_tx.vout) + { + uint64_t money_transfered = 0; + bool error = false, received = false; + check_acc_out_precomp(keys.m_account_address.m_spend_public_key, out, derivation, output_index, received, money_transfered, error); + THROW_WALLET_EXCEPTION_IF(error, error::wallet_internal_error, "check_acc_out_precomp failed"); + if (received) + { + if (money_transfered == 0) + { + rct::key mask; + money_transfered = tools::decodeRct(spent_tx.rct_signatures, tx_pub_key, keys.m_view_secret_key, output_index, mask); + } + tx_money_got_in_outs += money_transfered; + } + ++output_index; + } + + // get spent amount + uint64_t tx_money_spent_in_ins = 0; + for (const cryptonote::txin_v& in : spent_tx.vin) + { + if (in.type() != typeid(cryptonote::txin_to_key)) + continue; + auto it = m_key_images.find(boost::get<cryptonote::txin_to_key>(in).k_image); + if (it != m_key_images.end()) + { + const transfer_details& td = m_transfers[it->second]; + uint64_t amount = boost::get<cryptonote::txin_to_key>(in).amount; + if (amount > 0) + { + THROW_WALLET_EXCEPTION_IF(amount != td.amount(), error::wallet_internal_error, + std::string("Inconsistent amount in tx input: got ") + print_money(amount) + + std::string(", expected ") + print_money(td.amount())); + } + amount = td.amount(); + tx_money_spent_in_ins += amount; + + LOG_PRINT_L0("Spent money: " << print_money(amount) << ", with tx: " << *spent_txid); + set_spent(it->second, e.block_height); + if (m_callback) + m_callback->on_money_spent(e.block_height, *spent_txid, spent_tx, amount, spent_tx); + } + } + + // create outgoing payment + process_outgoing(*spent_txid, spent_tx, e.block_height, e.block_timestamp, tx_money_spent_in_ins, tx_money_got_in_outs); + + // erase corresponding incoming payment + for (auto j = m_payments.begin(); j != m_payments.end(); ++j) + { + if (j->second.m_tx_hash == *spent_txid) + { + m_payments.erase(j); + break; + } + } + + ++spent_txid; + } + + for (size_t n : swept_transfers) + { + const transfer_details& td = m_transfers[n]; + confirmed_transfer_details pd; + pd.m_change = (uint64_t)-1; // cahnge is unknown + pd.m_amount_in = pd.m_amount_out = td.amount(); // fee is unknown + std::string err; + pd.m_block_height = get_daemon_blockchain_height(err); // spent block height is unknown, so hypothetically set to the highest + crypto::hash spent_txid = crypto::rand<crypto::hash>(); // spent txid is unknown, so hypothetically set to random + m_confirmed_txs.insert(std::make_pair(spent_txid, pd)); + } + } + return m_transfers[signed_key_images.size() - 1].m_block_height; } wallet2::payment_container wallet2::export_payments() const diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index adf03abcc..971e98351 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -363,7 +363,7 @@ namespace tools * \brief Checks if deterministic wallet */ bool is_deterministic() const; - bool get_seed(std::string& electrum_words) const; + bool get_seed(std::string& electrum_words, const std::string &passphrase = std::string()) const; /*! * \brief Gets the seed language */ diff --git a/src/wallet/wallet2_api.h b/src/wallet/wallet2_api.h index 8da8c62eb..7a5e01af7 100644 --- a/src/wallet/wallet2_api.h +++ b/src/wallet/wallet2_api.h @@ -663,7 +663,7 @@ struct WalletManager * \param wallet previously opened / created wallet instance * \return None */ - virtual bool closeWallet(Wallet *wallet) = 0; + virtual bool closeWallet(Wallet *wallet, bool store = true) = 0; /* * ! checks if wallet with the given name already exists diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index 773d12775..9368b8cb6 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -37,7 +37,6 @@ using namespace epee; #include "wallet/wallet_args.h" #include "common/command_line.h" #include "common/i18n.h" -#include "common/util.h" #include "cryptonote_basic/cryptonote_format_utils.h" #include "cryptonote_basic/account.h" #include "wallet_rpc_server_commands_defs.h" @@ -70,18 +69,12 @@ namespace tools } //------------------------------------------------------------------------------------------------------------------------------ - wallet_rpc_server::wallet_rpc_server():m_wallet(NULL), rpc_login_filename(), m_stop(false), m_trusted_daemon(false) + wallet_rpc_server::wallet_rpc_server():m_wallet(NULL), rpc_login_file(), m_stop(false), m_trusted_daemon(false) { } //------------------------------------------------------------------------------------------------------------------------------ wallet_rpc_server::~wallet_rpc_server() { - try - { - boost::system::error_code ec{}; - boost::filesystem::remove(rpc_login_filename, ec); - } - catch (...) {} } //------------------------------------------------------------------------------------------------------------------------------ void wallet_rpc_server::set_wallet(wallet2 *cr) @@ -182,34 +175,32 @@ namespace tools default_rpc_username, string_encoding::base64_encode(rand_128bit.data(), rand_128bit.size()) ); + + std::string temp = "monero-wallet-rpc." + bind_port + ".login"; + rpc_login_file = tools::private_file::create(temp); + if (!rpc_login_file.handle()) + { + LOG_ERROR(tr("Failed to create file ") << temp << tr(". Check permissions or remove file")); + return false; + } + std::fputs(http_login->username.c_str(), rpc_login_file.handle()); + std::fputc(':', rpc_login_file.handle()); + std::fputs(http_login->password.c_str(), rpc_login_file.handle()); + std::fflush(rpc_login_file.handle()); + if (std::ferror(rpc_login_file.handle())) + { + LOG_ERROR(tr("Error writing to file ") << temp); + return false; + } + LOG_PRINT_L0(tr("RPC username/password is stored in file ") << temp); } - else + else // chosen user/pass { http_login.emplace( std::move(rpc_config->login->username), std::move(rpc_config->login->password).password() ); } assert(bool(http_login)); - - std::string temp = "monero-wallet-rpc." + bind_port + ".login"; - const auto cookie = tools::create_private_file(temp); - if (!cookie) - { - LOG_ERROR(tr("Failed to create file ") << temp << tr(". Check permissions or remove file")); - return false; - } - rpc_login_filename.swap(temp); // nothrow guarantee destructor cleanup - temp = rpc_login_filename; - std::fputs(http_login->username.c_str(), cookie.get()); - std::fputc(':', cookie.get()); - std::fputs(http_login->password.c_str(), cookie.get()); - std::fflush(cookie.get()); - if (std::ferror(cookie.get())) - { - LOG_ERROR(tr("Error writing to file ") << temp); - return false; - } - LOG_PRINT_L0(tr("RPC username/password is stored in file ") << temp); } // end auth enabled m_http_client.set_server(walvars->get_daemon_address(), walvars->get_daemon_login()); diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h index dd54222b0..e5ed0a846 100644 --- a/src/wallet/wallet_rpc_server.h +++ b/src/wallet/wallet_rpc_server.h @@ -33,6 +33,7 @@ #include <boost/program_options/options_description.hpp> #include <boost/program_options/variables_map.hpp> #include <string> +#include "common/util.h" #include "net/http_server_impl_base.h" #include "wallet_rpc_server_commands_defs.h" #include "wallet2.h" @@ -154,7 +155,7 @@ namespace tools wallet2 *m_wallet; std::string m_wallet_dir; - std::string rpc_login_filename; + tools::private_file rpc_login_file; std::atomic<bool> m_stop; bool m_trusted_daemon; epee::net_utils::http::http_simple_client m_http_client; diff --git a/tests/unit_tests/CMakeLists.txt b/tests/unit_tests/CMakeLists.txt index 967c53367..63abe928d 100644 --- a/tests/unit_tests/CMakeLists.txt +++ b/tests/unit_tests/CMakeLists.txt @@ -27,6 +27,7 @@ # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. set(unit_tests_sources + apply_permutation.cpp address_from_url.cpp ban.cpp base58.cpp diff --git a/tests/unit_tests/apply_permutation.cpp b/tests/unit_tests/apply_permutation.cpp new file mode 100644 index 000000000..a008b74ee --- /dev/null +++ b/tests/unit_tests/apply_permutation.cpp @@ -0,0 +1,74 @@ +// Copyright (c) 2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "gtest/gtest.h" +#include "common/apply_permutation.h" + +TEST(apply_permutation, empty) +{ + std::vector<int> v = {}; + tools::apply_permutation({}, v); + ASSERT_EQ(v, std::vector<int>({})); +} + +TEST(apply_permutation, reorder) +{ + // 0 1 2 3 4 5 6 + std::vector<int> v = {8, 4, 6, 1, 7, 2, 4}; + tools::apply_permutation({3, 5, 6, 1, 2, 4, 0}, v); + ASSERT_EQ(v, std::vector<int>({1, 2, 4, 4, 6, 7, 8})); +} + +TEST(apply_permutation, bad_size) +{ + std::vector<int> v_large = {8, 4, 6, 1, 7, 2, 4, 9}; + std::vector<int> v_small = {8, 4, 6, 1, 7, 2}; + try + { + tools::apply_permutation({3, 5, 6, 1, 2, 4, 0}, v_large); + ASSERT_FALSE(true); + } + catch (const std::exception &e) {} + try + { + tools::apply_permutation({3, 5, 6, 1, 2, 4, 0}, v_small); + ASSERT_FALSE(true); + } + catch (const std::exception &e) {} +} + +TEST(apply_permutation, bad_permutation) +{ + std::vector<int> v = {8, 4, 6, 1, 7, 2, 4}; + try + { + tools::apply_permutation({3, 5, 6, 1, 2, 4, 1}, v); + ASSERT_FALSE(true); + } + catch (const std::exception &e) {} +} diff --git a/utils/systemd/monerod.service b/utils/systemd/monerod.service index 12395eb8c..96e88a2d3 100644 --- a/utils/systemd/monerod.service +++ b/utils/systemd/monerod.service @@ -6,12 +6,13 @@ After=network.target User=monero Group=monero WorkingDirectory=~ +RuntimeDirectory=monero Type=forking -PIDFile=/var/run/monerod.pid +PIDFile=/run/monero/monerod.pid ExecStart=/usr/bin/monerod --config-file /etc/monerod.conf \ - --detach --pidfile /var/run/monerod.pid + --detach --pidfile /run/monero/monerod.pid [Install] WantedBy=multi-user.target |