diff options
84 files changed, 2954 insertions, 819 deletions
diff --git a/Dockerfile b/Dockerfile index 0decb4fde..5fde5a477 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,9 +23,9 @@ RUN set -ex && \ WORKDIR /usr/local #Cmake -ARG CMAKE_VERSION=3.11.4 -ARG CMAKE_VERSION_DOT=v3.11 -ARG CMAKE_HASH=8f864e9f78917de3e1483e256270daabc4a321741592c5b36af028e72bff87f5 +ARG CMAKE_VERSION=3.12.0 +ARG CMAKE_VERSION_DOT=v3.12 +ARG CMAKE_HASH=d0781a90f6cdb9049d104ac16a150f9350b693498b9dea8a0331e799db6b9d69 RUN set -ex \ && curl -s -O https://cmake.org/files/${CMAKE_VERSION_DOT}/cmake-${CMAKE_VERSION}.tar.gz \ && echo "${CMAKE_HASH} cmake-${CMAKE_VERSION}.tar.gz" | sha256sum -c \ diff --git a/README.i18n.md b/README.i18n.md index b0379bfb3..504fc9e75 100644 --- a/README.i18n.md +++ b/README.i18n.md @@ -7,7 +7,7 @@ In order to use the same translation workflow as the [Monero Core GUI](https://g ### Tools for translators -In order to create, update or build translations files, you need to have Qt tools installed. For translating, you need either the **Qt Linguist GUI** ([part of QT Creator](https://www.qt.io/download-open-source/#allDownloadsDiv-9) or a [3rd-party standalone version](https://github.com/lelegard/qtlinguist-installers/releases)), or another tool that supports Qt ts files, such as Transifex. The files are XML, so they can be edited in any plain text editor if needed. +In order to create, update or build translations files, you need to have Qt tools installed. For translating, you need either the **Qt Linguist GUI** ([part of Qt Creator](https://www.qt.io/download) or a [3rd-party standalone version](https://github.com/lelegard/qtlinguist-installers/releases)), or another tool that supports Qt ts files, such as Transifex. The files are XML, so they can be edited in any plain text editor if needed. ### Creating / modifying translations @@ -107,7 +107,7 @@ Dates are provided in the format YYYY-MM-DD. | 1220516 | 2017-01-05 | v4 | v0.10.1 | v0.10.2.1 | Allow normal and RingCT transactions | | 1288616 | 2017-04-15 | v5 | v0.10.3.0 | v0.10.3.1 | Adjusted minimum blocksize and fee algorithm | | 1400000 | 2017-09-16 | v6 | v0.11.0.0 | v0.11.0.0 | Allow only RingCT transactions, allow only >= ringsize 5 | -| 1546000 | 2018-04-06 | v7 | v0.12.0.0 | v0.12.2.0 | Cryptonight variant 1, ringsize >= 7, sorted inputs +| 1546000 | 2018-04-06 | v7 | v0.12.0.0 | v0.12.3.0 | Cryptonight variant 1, ringsize >= 7, sorted inputs | XXXXXXX | 2018-10-XX | XX | XXXXXXXXX | XXXXXXXXX | X X's indicate that these details have not been determined as of commit date. @@ -137,8 +137,9 @@ library archives (`.a`). | Boost | 1.58 | NO | `libboost-all-dev` | `boost` | `boost-devel` | NO | C++ libraries | | OpenSSL | basically any | NO | `libssl-dev` | `openssl` | `openssl-devel` | NO | sha256 sum | | libzmq | 3.0.0 | NO | `libzmq3-dev` | `zeromq` | `cppzmq-devel` | NO | ZeroMQ library | +| OpenPGM | ? | NO | `libpgm-dev` | `libpgm` | `openpgm-devel` | NO | For ZeroMQ | | libunbound | 1.4.16 | YES | `libunbound-dev` | `unbound` | `unbound-devel` | NO | DNS resolver | -| libsodium | ? | NO | `libsodium-dev` | ? | `libsodium-devel` | NO | libsodium | +| libsodium | ? | NO | `libsodium-dev` | ? | `libsodium-devel` | NO | cryptography | | libunwind | any | NO | `libunwind8-dev` | `libunwind` | `libunwind-devel` | YES | Stack traces | | liblzma | any | NO | `liblzma-dev` | `xz` | `xz-devel` | YES | For libunwind | | libreadline | 6.3.0 | NO | `libreadline6-dev` | `readline` | `readline-devel` | YES | Input editing | @@ -154,7 +155,7 @@ library archives (`.a`). build the library binary manually. This can be done with the following command ```sudo apt-get install libgtest-dev && cd /usr/src/gtest && sudo cmake . && sudo make && sudo mv libg* /usr/lib/ ``` Debian / Ubuntu one liner for all dependencies -``` sudo apt update && sudo apt install build-essential cmake pkg-config libboost-all-dev libssl-dev libzmq3-dev libunbound-dev libsodium-dev libunwind8-dev liblzma-dev libreadline6-dev libldns-dev libexpat1-dev doxygen graphviz libpcsclite-dev ``` +``` sudo apt update && sudo apt install build-essential cmake pkg-config libboost-all-dev libssl-dev libzmq3-dev libunbound-dev libsodium-dev libunwind8-dev liblzma-dev libreadline6-dev libldns-dev libexpat1-dev doxygen graphviz libpcsclite-dev libpgm-dev``` ### Cloning the repository @@ -177,7 +178,7 @@ invokes cmake commands as needed. * Change to the root of the source code directory, change to the most recent release branch, and build: cd monero - git checkout v0.12.2.0 + git checkout v0.12.3.0 make *Optional*: If your machine has several cores and enough memory, enable @@ -239,7 +240,7 @@ Tested on a Raspberry Pi Zero with a clean install of minimal Raspbian Stretch ( ``` git clone https://github.com/monero-project/monero.git cd monero - git checkout tags/v0.12.2.0 + git checkout tags/v0.12.3.0 ``` * Build: ``` @@ -336,9 +337,9 @@ application. cd monero -* If you would like a specific [version/tag](https://github.com/monero-project/monero/tags), do a git checkout for that version. eg. 'v0.12.1.0'. If you dont care about the version and just want binaries from master, skip this step: +* If you would like a specific [version/tag](https://github.com/monero-project/monero/tags), do a git checkout for that version. eg. 'v0.12.3.0'. If you dont care about the version and just want binaries from master, skip this step: - git checkout v0.12.1.0 + git checkout v0.12.3.0 * If you are on a 64-bit system, run: diff --git a/contrib/codefresh/codefresh.yml b/contrib/codefresh/codefresh.yml new file mode 100644 index 000000000..a22debfa1 --- /dev/null +++ b/contrib/codefresh/codefresh.yml @@ -0,0 +1,18 @@ +version: '1.0' +steps: + init_submodules: + title: Init Submodules + commands: + - git submodule update --init --recursive + image: codefreshio/git-image:latest + working_directory: ${{main_clone}} + + BuildingDockerImage: + title: Building Docker Image + type: build + image_name: monero + working_directory: ./ + tag: '${{CF_BRANCH_TAG_NORMALIZED}}' + dockerfile: Dockerfile + build_arguments: + - NPROC=1 diff --git a/contrib/epee/include/fnv1.h b/contrib/epee/include/fnv1.h new file mode 100644 index 000000000..c04389bca --- /dev/null +++ b/contrib/epee/include/fnv1.h @@ -0,0 +1,45 @@ +// Copyright (c) 2018, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +namespace epee +{ + +namespace fnv +{ + inline uint64_t FNV1a(const char *ptr, size_t sz) + { + uint64_t h = 0xcbf29ce484222325; + for (size_t i = 0; i < sz; ++i) + h = (h ^ *(const uint8_t*)ptr++) * 0x100000001b3; + return h; + } +} + +} diff --git a/contrib/epee/include/hex.h b/contrib/epee/include/hex.h index e960da1d2..02600c320 100644 --- a/contrib/epee/include/hex.h +++ b/contrib/epee/include/hex.h @@ -33,6 +33,7 @@ #include <iosfwd> #include <string> +#include "wipeable_string.h" #include "span.h" namespace epee @@ -41,6 +42,8 @@ namespace epee { //! \return A std::string containing hex of `src`. static std::string string(const span<const std::uint8_t> src); + //! \return A epee::wipeable_string containing hex of `src`. + static epee::wipeable_string wipeable_string(const span<const std::uint8_t> src); //! \return An array containing hex of `src`. template<std::size_t N> @@ -59,6 +62,8 @@ namespace epee static void formatted(std::ostream& out, const span<const std::uint8_t> src); private: + template<typename T> T static convert(const span<const std::uint8_t> src); + //! Write `src` bytes as hex to `out`. `out` must be twice the length static void buffer_unchecked(char* out, const span<const std::uint8_t> src) noexcept; }; diff --git a/contrib/epee/include/mlocker.h b/contrib/epee/include/mlocker.h new file mode 100644 index 000000000..d2fc2ed58 --- /dev/null +++ b/contrib/epee/include/mlocker.h @@ -0,0 +1,87 @@ +// Copyright (c) 2018, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include <map> +#include <boost/thread/mutex.hpp> + +namespace epee +{ + class mlocker + { + public: + mlocker(void *ptr, size_t len); + ~mlocker(); + + static size_t get_page_size(); + static size_t get_num_locked_pages(); + static size_t get_num_locked_objects(); + + static void lock(void *ptr, size_t len); + static void unlock(void *ptr, size_t len); + + private: + static size_t page_size; + static size_t num_locked_objects; + + static boost::mutex &mutex(); + static std::map<size_t, unsigned int> &map(); + static void lock_page(size_t page); + static void unlock_page(size_t page); + + void *ptr; + size_t len; + }; + + /// Locks memory while in scope + /// + /// Primarily useful for making sure that private keys don't get swapped out + // to disk + template <class T> + struct mlocked : public T { + using type = T; + + mlocked(): T() { mlocker::lock(this, sizeof(T)); } + mlocked(const T &t): T(t) { mlocker::lock(this, sizeof(T)); } + mlocked(const mlocked<T> &mt): T(mt) { mlocker::lock(this, sizeof(T)); } + mlocked(const T &&t): T(t) { mlocker::lock(this, sizeof(T)); } + mlocked(const mlocked<T> &&mt): T(mt) { mlocker::lock(this, sizeof(T)); } + mlocked<T> &operator=(const mlocked<T> &mt) { T::operator=(mt); return *this; } + ~mlocked() { mlocker::unlock(this, sizeof(T)); } + }; + + template<typename T> + T& unwrap(mlocked<T>& src) { return src; } + + template<typename T> + const T& unwrap(mlocked<T> const& src) { return src; } + + template <class T, size_t N> + using mlocked_arr = mlocked<std::array<T, N>>; +} diff --git a/contrib/epee/include/serialization/keyvalue_serialization.h b/contrib/epee/include/serialization/keyvalue_serialization.h index 5791e1998..fc5a21851 100644 --- a/contrib/epee/include/serialization/keyvalue_serialization.h +++ b/contrib/epee/include/serialization/keyvalue_serialization.h @@ -85,6 +85,14 @@ public: \ static_assert(std::is_pod<decltype(this_ref.varialble)>::value, "t_type must be a POD type."); \ KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE_N(varialble, val_name) +#define KV_SERIALIZE_VAL_POD_AS_BLOB_OPT_N(varialble, val_name, default_value) \ + do { \ + static_assert(std::is_pod<decltype(this_ref.varialble)>::value, "t_type must be a POD type."); \ + bool ret = KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE_N(varialble, val_name); \ + if (!ret) \ + epee::serialize_default(this_ref.varialble, default_value); \ + } while(0); + #define KV_SERIALIZE_CONTAINER_POD_AS_BLOB_N(varialble, val_name) \ epee::serialization::selector<is_store>::serialize_stl_container_pod_val_as_blob(this_ref.varialble, stg, hparent_section, val_name); @@ -92,6 +100,7 @@ public: \ #define KV_SERIALIZE(varialble) KV_SERIALIZE_N(varialble, #varialble) #define KV_SERIALIZE_VAL_POD_AS_BLOB(varialble) KV_SERIALIZE_VAL_POD_AS_BLOB_N(varialble, #varialble) +#define KV_SERIALIZE_VAL_POD_AS_BLOB_OPT(varialble, def) KV_SERIALIZE_VAL_POD_AS_BLOB_OPT_N(varialble, #varialble, def) #define KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE(varialble) KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE_N(varialble, #varialble) //skip is_pod compile time check #define KV_SERIALIZE_CONTAINER_POD_AS_BLOB(varialble) KV_SERIALIZE_CONTAINER_POD_AS_BLOB_N(varialble, #varialble) #define KV_SERIALIZE_OPT(variable,default_value) KV_SERIALIZE_OPT_N(variable, #variable, default_value) diff --git a/contrib/epee/include/serialization/keyvalue_serialization_overloads.h b/contrib/epee/include/serialization/keyvalue_serialization_overloads.h index 09087f785..15c95f07a 100644 --- a/contrib/epee/include/serialization/keyvalue_serialization_overloads.h +++ b/contrib/epee/include/serialization/keyvalue_serialization_overloads.h @@ -35,6 +35,11 @@ namespace epee { + namespace + { + template<class C> void hint_resize(C &container, size_t size) {} + template<class C> void hint_resize(std::vector<C> &container, size_t size) { container.reserve(size); } + } namespace serialization { @@ -158,6 +163,7 @@ namespace epee false, "size in blob " << loaded_size << " not have not zero modulo for sizeof(value_type) = " << sizeof(typename stl_container::value_type) << ", type " << typeid(typename stl_container::value_type).name()); size_t count = (loaded_size/sizeof(typename stl_container::value_type)); + hint_resize(container, count); for(size_t i = 0; i < count; i++) container.insert(container.end(), *(pelem++)); } diff --git a/contrib/epee/include/storages/parserse_base_utils.h b/contrib/epee/include/storages/parserse_base_utils.h index c809392f4..8c6c1a64d 100644 --- a/contrib/epee/include/storages/parserse_base_utils.h +++ b/contrib/epee/include/storages/parserse_base_utils.h @@ -28,6 +28,8 @@ #pragma once +#include <algorithm> + namespace epee { namespace misc_utils @@ -36,8 +38,12 @@ namespace misc_utils { inline std::string transform_to_escape_sequence(const std::string& src) { - //std::stringstream res; + static const char escaped[] = "\b\f\n\r\t\v\"\\/"; + if (std::find_first_of(src.begin(), src.end(), escaped, escaped + sizeof(escaped)) == src.end()) + return src; + std::string res; + res.reserve(2 * src.size()); for(std::string::const_iterator it = src.begin(); it!=src.end(); ++it) { switch(*it) @@ -84,6 +90,7 @@ namespace misc_utils inline void match_string2(std::string::const_iterator& star_end_string, std::string::const_iterator buf_end, std::string& val) { val.clear(); + val.reserve(std::distance(star_end_string, buf_end)); bool escape_mode = false; std::string::const_iterator it = star_end_string; ++it; diff --git a/contrib/epee/include/string_tools.h b/contrib/epee/include/string_tools.h index 8d8603076..aba065cc7 100644 --- a/contrib/epee/include/string_tools.h +++ b/contrib/epee/include/string_tools.h @@ -46,6 +46,7 @@ #include <boost/algorithm/string/predicate.hpp> #include "hex.h" #include "memwipe.h" +#include "mlocker.h" #include "span.h" #include "warnings.h" @@ -358,6 +359,12 @@ POP_WARNINGS return hex_to_pod(hex_str, unwrap(s)); } //---------------------------------------------------------------------------- + template<class t_pod_type> + bool hex_to_pod(const std::string& hex_str, epee::mlocked<t_pod_type>& s) + { + return hex_to_pod(hex_str, unwrap(s)); + } + //---------------------------------------------------------------------------- bool validate_hex(uint64_t length, const std::string& str); //---------------------------------------------------------------------------- inline std::string get_extension(const std::string& str) diff --git a/contrib/epee/include/wipeable_string.h b/contrib/epee/include/wipeable_string.h index 70d1a9586..4cebe5fdf 100644 --- a/contrib/epee/include/wipeable_string.h +++ b/contrib/epee/include/wipeable_string.h @@ -28,28 +28,43 @@ #pragma once +#include <boost/optional/optional_fwd.hpp> #include <stddef.h> #include <vector> #include <string> +#include "fnv1.h" namespace epee { class wipeable_string { public: + typedef char value_type; + wipeable_string() {} wipeable_string(const wipeable_string &other); wipeable_string(wipeable_string &&other); wipeable_string(const std::string &other); wipeable_string(std::string &&other); wipeable_string(const char *s); + wipeable_string(const char *s, size_t len); ~wipeable_string(); void wipe(); void push_back(char c); - void pop_back(); + void operator+=(char c); + void operator+=(const std::string &s); + void operator+=(const epee::wipeable_string &s); + void operator+=(const char *s); + void append(const char *ptr, size_t len); + char pop_back(); const char *data() const noexcept { return buffer.data(); } + char *data() noexcept { return buffer.data(); } size_t size() const noexcept { return buffer.size(); } + size_t length() const noexcept { return buffer.size(); } bool empty() const noexcept { return buffer.empty(); } + void trim(); + void split(std::vector<wipeable_string> &fields) const; + boost::optional<wipeable_string> parse_hexstr() const; void resize(size_t sz); void reserve(size_t sz); void clear(); @@ -65,3 +80,14 @@ namespace epee std::vector<char> buffer; }; } + +namespace std +{ + template<> struct hash<epee::wipeable_string> + { + size_t operator()(const epee::wipeable_string &s) const + { + return epee::fnv::FNV1a(s.data(), s.size()); + } + }; +} diff --git a/contrib/epee/src/CMakeLists.txt b/contrib/epee/src/CMakeLists.txt index c4750cea0..0b5e7ae6c 100644 --- a/contrib/epee/src/CMakeLists.txt +++ b/contrib/epee/src/CMakeLists.txt @@ -27,7 +27,7 @@ # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. add_library(epee STATIC hex.cpp http_auth.cpp mlog.cpp net_utils_base.cpp string_tools.cpp wipeable_string.cpp memwipe.c - connection_basic.cpp network_throttle.cpp network_throttle-detail.cpp) + connection_basic.cpp network_throttle.cpp network_throttle-detail.cpp mlocker.cpp) if (USE_READLINE AND GNU_READLINE_FOUND) add_library(epee_readline STATIC readline_buffer.cpp) endif() diff --git a/contrib/epee/src/hex.cpp b/contrib/epee/src/hex.cpp index c143b2dc2..5c8acc8be 100644 --- a/contrib/epee/src/hex.cpp +++ b/contrib/epee/src/hex.cpp @@ -52,17 +52,21 @@ namespace epee } } - std::string to_hex::string(const span<const std::uint8_t> src) + template<typename T> + T to_hex::convert(const span<const std::uint8_t> src) { if (std::numeric_limits<std::size_t>::max() / 2 < src.size()) throw std::range_error("hex_view::to_string exceeded maximum size"); - std::string out{}; + T out{}; out.resize(src.size() * 2); - buffer_unchecked(std::addressof(out[0]), src); + to_hex::buffer_unchecked((char*)out.data(), src); // can't see the non const version in wipeable_string?? return out; } + std::string to_hex::string(const span<const std::uint8_t> src) { return convert<std::string>(src); } + epee::wipeable_string to_hex::wipeable_string(const span<const std::uint8_t> src) { return convert<epee::wipeable_string>(src); } + void to_hex::buffer(std::ostream& out, const span<const std::uint8_t> src) { write_hex(std::ostreambuf_iterator<char>{out}, src); diff --git a/contrib/epee/src/mlocker.cpp b/contrib/epee/src/mlocker.cpp new file mode 100644 index 000000000..5573d591a --- /dev/null +++ b/contrib/epee/src/mlocker.cpp @@ -0,0 +1,182 @@ +// Copyright (c) 2018, 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. + +#if defined __GNUC__ && !defined _WIN32 +#define HAVE_MLOCK 1 +#endif + +#include <unistd.h> +#if defined HAVE_MLOCK +#include <sys/mman.h> +#endif +#include "misc_log_ex.h" +#include "syncobj.h" +#include "mlocker.h" + +static size_t query_page_size() +{ +#if defined HAVE_MLOCK + long ret = sysconf(_SC_PAGESIZE); + if (ret <= 0) + { + MERROR("Failed to determine page size"); + return 0; + } + MINFO("Page size: " << ret); + return ret; +#else +#warning Missing query_page_size implementation +#endif + return 0; +} + +static void do_lock(void *ptr, size_t len) +{ +#if defined HAVE_MLOCK + int ret = mlock(ptr, len); + if (ret < 0) + MERROR("Error locking page at " << ptr << ": " << strerror(errno)); +#else +#warning Missing do_lock implementation +#endif +} + +static void do_unlock(void *ptr, size_t len) +{ +#if defined HAVE_MLOCK + int ret = munlock(ptr, len); + if (ret < 0) + MERROR("Error unlocking page at " << ptr << ": " << strerror(errno)); +#else +#warning Missing implementation of page size detection +#endif +} + +namespace epee +{ + size_t mlocker::page_size = 0; + size_t mlocker::num_locked_objects = 0; + + boost::mutex &mlocker::mutex() + { + static boost::mutex vmutex; + return vmutex; + } + std::map<size_t, unsigned int> &mlocker::map() + { + static std::map<size_t, unsigned int> vmap; + return vmap; + } + + size_t mlocker::get_page_size() + { + CRITICAL_REGION_LOCAL(mutex()); + if (page_size == 0) + page_size = query_page_size(); + return page_size; + } + + mlocker::mlocker(void *ptr, size_t len): ptr(ptr), len(len) + { + lock(ptr, len); + } + + mlocker::~mlocker() + { + unlock(ptr, len); + } + + void mlocker::lock(void *ptr, size_t len) + { + size_t page_size = get_page_size(); + if (page_size == 0) + return; + + CRITICAL_REGION_LOCAL(mutex()); + const size_t first = ((uintptr_t)ptr) / page_size; + const size_t last = (((uintptr_t)ptr) + len - 1) / page_size; + for (size_t page = first; page <= last; ++page) + lock_page(page); + ++num_locked_objects; + } + + void mlocker::unlock(void *ptr, size_t len) + { + size_t page_size = get_page_size(); + if (page_size == 0) + return; + CRITICAL_REGION_LOCAL(mutex()); + const size_t first = ((uintptr_t)ptr) / page_size; + const size_t last = (((uintptr_t)ptr) + len - 1) / page_size; + for (size_t page = first; page <= last; ++page) + unlock_page(page); + --num_locked_objects; + } + + size_t mlocker::get_num_locked_pages() + { + CRITICAL_REGION_LOCAL(mutex()); + return map().size(); + } + + size_t mlocker::get_num_locked_objects() + { + CRITICAL_REGION_LOCAL(mutex()); + return num_locked_objects; + } + + void mlocker::lock_page(size_t page) + { + std::pair<std::map<size_t, unsigned int>::iterator, bool> p = map().insert(std::make_pair(page, 1)); + if (p.second) + { + do_lock((void*)(page * page_size), page_size); + } + else + { + ++p.first->second; + } + } + + void mlocker::unlock_page(size_t page) + { + std::map<size_t, unsigned int>::iterator i = map().find(page); + if (i == map().end()) + { + MERROR("Attempt to unlock unlocked page at " << (void*)(page * page_size)); + } + else + { + if (!--i->second) + { + map().erase(i); + do_unlock((void*)(page * page_size), page_size); + } + } + } +} diff --git a/contrib/epee/src/mlog.cpp b/contrib/epee/src/mlog.cpp index e8248c958..818fc0a69 100644 --- a/contrib/epee/src/mlog.cpp +++ b/contrib/epee/src/mlog.cpp @@ -142,7 +142,9 @@ void mlog_configure(const std::string &filename_base, bool console, const std::s { std::vector<boost::filesystem::path> found_files; const boost::filesystem::directory_iterator end_itr; - for (boost::filesystem::directory_iterator iter(boost::filesystem::path(filename_base).parent_path()); iter != end_itr; ++iter) + const boost::filesystem::path filename_base_path(filename_base); + const boost::filesystem::path parent_path = filename_base_path.has_parent_path() ? filename_base_path.parent_path() : "."; + for (boost::filesystem::directory_iterator iter(parent_path); iter != end_itr; ++iter) { const std::string filename = iter->path().string(); if (filename.size() >= filename_base.size() && std::memcmp(filename.data(), filename_base.data(), filename_base.size()) == 0) diff --git a/contrib/epee/src/wipeable_string.cpp b/contrib/epee/src/wipeable_string.cpp index 6ed4ee8a2..7c9722765 100644 --- a/contrib/epee/src/wipeable_string.cpp +++ b/contrib/epee/src/wipeable_string.cpp @@ -26,11 +26,22 @@ // 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 <boost/optional/optional.hpp> #include <string.h> #include "memwipe.h" #include "misc_log_ex.h" #include "wipeable_string.h" +namespace +{ + int atolower(int c) + { + if (c >= 'A' && c <= 'Z') + c |= 32; + return c; + } +} + namespace epee { @@ -69,6 +80,12 @@ wipeable_string::wipeable_string(const char *s) memcpy(buffer.data(), s, size()); } +wipeable_string::wipeable_string(const char *s, size_t len) +{ + grow(len); + memcpy(buffer.data(), s, len); +} + wipeable_string::~wipeable_string() { wipe(); @@ -109,9 +126,100 @@ void wipeable_string::push_back(char c) buffer.back() = c; } -void wipeable_string::pop_back() +void wipeable_string::operator+=(char c) +{ + push_back(c); +} + +void wipeable_string::append(const char *ptr, size_t len) +{ + const size_t orgsz = size(); + CHECK_AND_ASSERT_THROW_MES(orgsz < std::numeric_limits<size_t>::max() - len, "Appended data too large"); + grow(orgsz + len); + if (len > 0) + memcpy(data() + orgsz, ptr, len); +} + +void wipeable_string::operator+=(const char *s) +{ + append(s, strlen(s)); +} + +void wipeable_string::operator+=(const epee::wipeable_string &s) +{ + append(s.data(), s.size()); +} + +void wipeable_string::operator+=(const std::string &s) +{ + append(s.c_str(), s.size()); +} + +void wipeable_string::trim() +{ + size_t prefix = 0; + while (prefix < size() && data()[prefix] == ' ') + ++prefix; + if (prefix > 0) + memmove(buffer.data(), buffer.data() + prefix, size() - prefix); + + size_t suffix = 0; + while (suffix < size()-prefix && data()[size() - 1 - prefix - suffix] == ' ') + ++suffix; + + resize(size() - prefix - suffix); +} + +void wipeable_string::split(std::vector<wipeable_string> &fields) const +{ + fields.clear(); + size_t len = size(); + const char *ptr = data(); + bool space = true; + while (len--) + { + const char c = *ptr++; + if (c != ' ') + { + if (space) + fields.push_back({}); + fields.back().push_back(c); + } + space = c == ' '; + } +} + +boost::optional<epee::wipeable_string> wipeable_string::parse_hexstr() const +{ + if (size() % 2 != 0) + return boost::none; + boost::optional<epee::wipeable_string> res = epee::wipeable_string(""); + const size_t len = size(); + const char *d = data(); + res->grow(0, len / 2); + static constexpr const char hex[] = u8"0123456789abcdef"; + for (size_t i = 0; i < len; i += 2) + { + char c = atolower(d[i]); + const char *ptr0 = strchr(hex, c); + if (!ptr0) + return boost::none; + c = atolower(d[i+1]); + const char *ptr1 = strchr(hex, c); + if (!ptr1) + return boost::none; + res->push_back(((ptr0-hex)<<4) | (ptr1-hex)); + } + return res; +} + +char wipeable_string::pop_back() { - resize(size() - 1); + const size_t sz = size(); + CHECK_AND_ASSERT_THROW_MES(sz > 0, "Popping from an empty string"); + const char c = buffer.back(); + resize(sz - 1); + return c; } void wipeable_string::resize(size_t sz) diff --git a/external/miniupnp b/external/miniupnp -Subproject 6a63f9954959119568fbc4af57d7b491b9428d8 +Subproject 6b9b73a567e351b844f96c077f7b752ea92e298 diff --git a/external/rapidjson b/external/rapidjson -Subproject af223d44f4e8d3772cb1ac0ce8bc2a132b51717 +Subproject 129d19ba7f496df5e33658527a7158c79b99c21 diff --git a/external/unbound b/external/unbound -Subproject 193bdc4ee3fe2b0d17e547e86512528c2614483 +Subproject d3724dfa553429d368c27aef160f02f5e8b8075 diff --git a/src/blockchain_db/berkeleydb/db_bdb.h b/src/blockchain_db/berkeleydb/db_bdb.h index c90d030a2..8c99ab255 100644 --- a/src/blockchain_db/berkeleydb/db_bdb.h +++ b/src/blockchain_db/berkeleydb/db_bdb.h @@ -303,7 +303,6 @@ public: virtual uint64_t get_indexing_base() const { return 1; } virtual output_data_t get_output_key(const uint64_t& amount, const uint64_t& index); - virtual output_data_t get_output_key(const uint64_t& global_index) const; virtual void get_output_key(const uint64_t &amount, const std::vector<uint64_t> &offsets, std::vector<output_data_t> &outputs); virtual tx_out_index get_output_tx_and_index_from_global(const uint64_t& index) const; @@ -419,6 +418,7 @@ private: * @return the global index of the desired output */ uint64_t get_output_global_index(const uint64_t& amount, const uint64_t& index); + output_data_t get_output_key(const uint64_t& global_index) const; void checkpoint_worker() const; void check_open() const; diff --git a/src/blockchain_db/blockchain_db.cpp b/src/blockchain_db/blockchain_db.cpp index 8544cc3f0..78d63fdcf 100644 --- a/src/blockchain_db/blockchain_db.cpp +++ b/src/blockchain_db/blockchain_db.cpp @@ -87,8 +87,8 @@ const command_line::arg_descriptor<std::string> arg_db_type = { }; const command_line::arg_descriptor<std::string> arg_db_sync_mode = { "db-sync-mode" -, "Specify sync option, using format [safe|fast|fastest]:[sync|async]:[nblocks_per_sync]." -, "fast:async:1000" +, "Specify sync option, using format [safe|fast|fastest]:[sync|async]:[<nblocks_per_sync>[blocks]|<nbytes_per_sync>[bytes]]." +, "fast:async:250000000bytes" }; const command_line::arg_descriptor<bool> arg_db_salvage = { "db-salvage" diff --git a/src/blockchain_db/blockchain_db.h b/src/blockchain_db/blockchain_db.h index 6851e2404..4431ca44c 100644 --- a/src/blockchain_db/blockchain_db.h +++ b/src/blockchain_db/blockchain_db.h @@ -1262,23 +1262,6 @@ public: virtual output_data_t get_output_key(const uint64_t& amount, const uint64_t& index) = 0; /** - * @brief get some of an output's data - * - * The subclass should return the public key, unlock time, and block height - * for the output with the given global index, collected in a struct. - * - * If the output cannot be found, the subclass should throw OUTPUT_DNE. - * - * If any of these parts cannot be found, but some are, the subclass - * should throw DB_ERROR with a message stating as much. - * - * @param global_index the output's index (global) - * - * @return the requested output data - */ - virtual output_data_t get_output_key(const uint64_t& global_index) const = 0; - - /** * @brief gets an output's tx hash and index * * The subclass should return the hash of the transaction which created the diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp index 627038ca7..32ed0e9a2 100644 --- a/src/blockchain_db/lmdb/db_lmdb.cpp +++ b/src/blockchain_db/lmdb/db_lmdb.cpp @@ -1971,14 +1971,25 @@ std::vector<uint64_t> BlockchainLMDB::get_block_cumulative_rct_outputs(const std MDB_val v; + uint64_t prev_height = heights[0]; for (uint64_t height: heights) { - MDB_val_set(v, height); - result = mdb_cursor_get(m_cur_block_info, (MDB_val *)&zerokval, &v, MDB_GET_BOTH); + if (height == prev_height + 1) + { + MDB_val k2; + result = mdb_cursor_get(m_cur_block_info, &k2, &v, MDB_NEXT); + } + else + { + v.mv_size = sizeof(uint64_t); + v.mv_data = (void*)&height; + result = mdb_cursor_get(m_cur_block_info, (MDB_val *)&zerokval, &v, MDB_GET_BOTH); + } if (result) throw0(DB_ERROR(lmdb_error("Error attempting to retrieve rct distribution from the db: ", result).c_str())); const mdb_block_info *bi = (const mdb_block_info *)v.mv_data; res.push_back(bi->bi_cum_rct); + prev_height = height; } TXN_POSTFIX_RDONLY(); @@ -2454,55 +2465,6 @@ uint64_t BlockchainLMDB::get_num_outputs(const uint64_t& amount) const return num_elems; } -// This is a lot harder now that we've removed the output_keys index -output_data_t BlockchainLMDB::get_output_key(const uint64_t &global_index) const -{ - LOG_PRINT_L3("BlockchainLMDB::" << __func__ << " (unused version - does nothing)"); - check_open(); - TXN_PREFIX_RDONLY(); - RCURSOR(output_txs); - RCURSOR(tx_indices); - - output_data_t od; - MDB_val_set(v, global_index); - auto get_result = mdb_cursor_get(m_cur_output_txs, (MDB_val *)&zerokval, &v, MDB_GET_BOTH); - if (get_result == MDB_NOTFOUND) - throw1(OUTPUT_DNE("output with given index not in db")); - else if (get_result) - throw0(DB_ERROR("DB error attempting to fetch output tx hash")); - - outtx *ot = (outtx *)v.mv_data; - - MDB_val_set(val_h, ot->tx_hash); - get_result = mdb_cursor_get(m_cur_tx_indices, (MDB_val *)&zerokval, &val_h, MDB_GET_BOTH); - if (get_result) - throw0(DB_ERROR(lmdb_error(std::string("DB error attempting to fetch transaction index from hash ") + epee::string_tools::pod_to_hex(ot->tx_hash) + ": ", get_result).c_str())); - - txindex *tip = (txindex *)val_h.mv_data; - MDB_val_set(val_tx_id, tip->data.tx_id); - MDB_val result; - get_result = mdb_cursor_get(m_cur_txs_pruned, &val_tx_id, &result, MDB_SET); - if (get_result == MDB_NOTFOUND) - throw1(TX_DNE(std::string("tx with hash ").append(epee::string_tools::pod_to_hex(ot->tx_hash)).append(" not found in db").c_str())); - else if (get_result) - throw0(DB_ERROR(lmdb_error("DB error attempting to fetch tx from hash", get_result).c_str())); - - blobdata bd; - bd.assign(reinterpret_cast<char*>(result.mv_data), result.mv_size); - - transaction tx; - if (!parse_and_validate_tx_base_from_blob(bd, tx)) - throw0(DB_ERROR("Failed to parse tx from blob retrieved from the db")); - - const tx_out tx_output = tx.vout[ot->local_index]; - od.unlock_time = tip->data.unlock_time; - od.height = tip->data.block_id; - od.pubkey = boost::get<txout_to_key>(tx_output.target).key; - - TXN_POSTFIX_RDONLY(); - return od; -} - output_data_t BlockchainLMDB::get_output_key(const uint64_t& amount, const uint64_t& index) { LOG_PRINT_L3("BlockchainLMDB::" << __func__); diff --git a/src/blockchain_db/lmdb/db_lmdb.h b/src/blockchain_db/lmdb/db_lmdb.h index 8b214d2df..8ff2073da 100644 --- a/src/blockchain_db/lmdb/db_lmdb.h +++ b/src/blockchain_db/lmdb/db_lmdb.h @@ -243,7 +243,6 @@ public: virtual uint64_t get_num_outputs(const uint64_t& amount) const; virtual output_data_t get_output_key(const uint64_t& amount, const uint64_t& index); - virtual output_data_t get_output_key(const uint64_t& global_index) const; virtual void get_output_key(const uint64_t &amount, const std::vector<uint64_t> &offsets, std::vector<output_data_t> &outputs, bool allow_partial = false); virtual tx_out_index get_output_tx_and_index_from_global(const uint64_t& index) const; diff --git a/src/common/password.cpp b/src/common/password.cpp index 3ce2ba42a..5671c4a4e 100644 --- a/src/common/password.cpp +++ b/src/common/password.cpp @@ -54,7 +54,7 @@ namespace return 0 != _isatty(_fileno(stdin)); } - bool read_from_tty(epee::wipeable_string& pass) + bool read_from_tty(epee::wipeable_string& pass, bool hide_input) { static constexpr const char BACKSPACE = 8; @@ -62,7 +62,7 @@ namespace DWORD mode_old; ::GetConsoleMode(h_cin, &mode_old); - DWORD mode_new = mode_old & ~(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT); + DWORD mode_new = mode_old & ~((hide_input ? ENABLE_ECHO_INPUT : 0) | ENABLE_LINE_INPUT); ::SetConsoleMode(h_cin, mode_new); bool r = true; @@ -107,14 +107,14 @@ namespace return 0 != isatty(fileno(stdin)); } - int getch() noexcept + int getch(bool hide_input) noexcept { struct termios tty_old; tcgetattr(STDIN_FILENO, &tty_old); struct termios tty_new; tty_new = tty_old; - tty_new.c_lflag &= ~(ICANON | ECHO); + tty_new.c_lflag &= ~(ICANON | (hide_input ? ECHO : 0)); tcsetattr(STDIN_FILENO, TCSANOW, &tty_new); int ch = getchar(); @@ -124,14 +124,14 @@ namespace return ch; } - bool read_from_tty(epee::wipeable_string& aPass) + bool read_from_tty(epee::wipeable_string& aPass, bool hide_input) { static constexpr const char BACKSPACE = 127; aPass.reserve(tools::password_container::max_password_size); while (aPass.size() < tools::password_container::max_password_size) { - int ch = getch(); + int ch = getch(hide_input); if (EOF == ch || ch == EOT) { return false; @@ -159,18 +159,18 @@ namespace #endif // end !WIN32 - bool read_from_tty(const bool verify, const char *message, epee::wipeable_string& pass1, epee::wipeable_string& pass2) + bool read_from_tty(const bool verify, const char *message, bool hide_input, epee::wipeable_string& pass1, epee::wipeable_string& pass2) { while (true) { if (message) std::cout << message <<": " << std::flush; - if (!read_from_tty(pass1)) + if (!read_from_tty(pass1, hide_input)) return false; if (verify) { std::cout << "Confirm password: "; - if (!read_from_tty(pass2)) + if (!read_from_tty(pass2, hide_input)) return false; if(pass1!=pass2) { @@ -229,12 +229,12 @@ namespace tools std::atomic<bool> password_container::is_prompting(false); - boost::optional<password_container> password_container::prompt(const bool verify, const char *message) + boost::optional<password_container> password_container::prompt(const bool verify, const char *message, bool hide_input) { is_prompting = true; password_container pass1{}; password_container pass2{}; - if (is_cin_tty() ? read_from_tty(verify, message, pass1.m_password, pass2.m_password) : read_from_file(pass1.m_password)) + if (is_cin_tty() ? read_from_tty(verify, message, hide_input, pass1.m_password, pass2.m_password) : read_from_file(pass1.m_password)) { is_prompting = false; return {std::move(pass1)}; diff --git a/src/common/password.h b/src/common/password.h index 61937b93a..529881e40 100644 --- a/src/common/password.h +++ b/src/common/password.h @@ -49,7 +49,7 @@ namespace tools password_container(std::string&& password) noexcept; //! \return A password from stdin TTY prompt or `std::cin` pipe. - static boost::optional<password_container> prompt(bool verify, const char *mesage = "Password"); + static boost::optional<password_container> prompt(bool verify, const char *mesage = "Password", bool hide_input = true); static std::atomic<bool> is_prompting; password_container(const password_container&) = delete; diff --git a/src/common/updates.cpp b/src/common/updates.cpp index 9eb402e0b..9f12f8dbc 100644 --- a/src/common/updates.cpp +++ b/src/common/updates.cpp @@ -69,12 +69,12 @@ namespace tools continue; bool alnum = true; - for (auto c: hash) + for (auto c: fields[3]) if (!isalnum(c)) alnum = false; - if (hash.size() != 64 && !alnum) + if (fields[3].size() != 64 && !alnum) { - MWARNING("Invalid hash: " << hash); + MWARNING("Invalid hash: " << fields[3]); continue; } diff --git a/src/crypto/CMakeLists.txt b/src/crypto/CMakeLists.txt index 71dcedcab..0c635e7cb 100644 --- a/src/crypto/CMakeLists.txt +++ b/src/crypto/CMakeLists.txt @@ -78,6 +78,7 @@ target_link_libraries(cncrypto PUBLIC epee ${Boost_SYSTEM_LIBRARY} + ${SODIUM_LIBRARY} PRIVATE ${EXTRA_LIBRARIES}) diff --git a/src/crypto/chacha.h b/src/crypto/chacha.h index 1dc270faf..6e85ad0e9 100644 --- a/src/crypto/chacha.h +++ b/src/crypto/chacha.h @@ -40,6 +40,7 @@ #include <memory.h> #include "memwipe.h" +#include "mlocker.h" #include "hash.h" namespace crypto { @@ -50,7 +51,7 @@ namespace crypto { #if defined(__cplusplus) } - using chacha_key = tools::scrubbed_arr<uint8_t, CHACHA_KEY_SIZE>; + using chacha_key = epee::mlocked<tools::scrubbed_arr<uint8_t, CHACHA_KEY_SIZE>>; #pragma pack(push, 1) // MS VC 2012 doesn't interpret `class chacha_iv` as POD in spite of [9.0.10], so it is a struct @@ -71,20 +72,20 @@ namespace crypto { inline void generate_chacha_key(const void *data, size_t size, chacha_key& key, uint64_t kdf_rounds) { static_assert(sizeof(chacha_key) <= sizeof(hash), "Size of hash must be at least that of chacha_key"); - tools::scrubbed_arr<char, HASH_SIZE> pwd_hash; + epee::mlocked<tools::scrubbed_arr<char, HASH_SIZE>> pwd_hash; crypto::cn_slow_hash(data, size, pwd_hash.data(), 0/*variant*/, 0/*prehashed*/); for (uint64_t n = 1; n < kdf_rounds; ++n) crypto::cn_slow_hash(pwd_hash.data(), pwd_hash.size(), pwd_hash.data(), 0/*variant*/, 0/*prehashed*/); - memcpy(&unwrap(key), pwd_hash.data(), sizeof(key)); + memcpy(&unwrap(unwrap(key)), pwd_hash.data(), sizeof(key)); } inline void generate_chacha_key_prehashed(const void *data, size_t size, chacha_key& key, uint64_t kdf_rounds) { static_assert(sizeof(chacha_key) <= sizeof(hash), "Size of hash must be at least that of chacha_key"); - tools::scrubbed_arr<char, HASH_SIZE> pwd_hash; + epee::mlocked<tools::scrubbed_arr<char, HASH_SIZE>> pwd_hash; crypto::cn_slow_hash(data, size, pwd_hash.data(), 0/*variant*/, 1/*prehashed*/); for (uint64_t n = 1; n < kdf_rounds; ++n) crypto::cn_slow_hash(pwd_hash.data(), pwd_hash.size(), pwd_hash.data(), 0/*variant*/, 0/*prehashed*/); - memcpy(&unwrap(key), pwd_hash.data(), sizeof(key)); + memcpy(&unwrap(unwrap(key)), pwd_hash.data(), sizeof(key)); } inline void generate_chacha_key(std::string password, chacha_key& key, uint64_t kdf_rounds) { diff --git a/src/crypto/crypto.h b/src/crypto/crypto.h index a2d61b04e..33cc0a25a 100644 --- a/src/crypto/crypto.h +++ b/src/crypto/crypto.h @@ -41,6 +41,7 @@ #include "common/pod-class.h" #include "common/util.h" #include "memwipe.h" +#include "mlocker.h" #include "generic-ops.h" #include "hex.h" #include "span.h" @@ -65,7 +66,7 @@ namespace crypto { friend class crypto_ops; }; - using secret_key = tools::scrubbed<ec_scalar>; + using secret_key = epee::mlocked<tools::scrubbed<ec_scalar>>; POD_CLASS public_keyV { std::vector<public_key> keys; @@ -282,6 +283,6 @@ namespace crypto { } CRYPTO_MAKE_HASHABLE(public_key) -CRYPTO_MAKE_HASHABLE(secret_key) +CRYPTO_MAKE_HASHABLE_CONSTANT_TIME(secret_key) CRYPTO_MAKE_HASHABLE(key_image) CRYPTO_MAKE_COMPARABLE(signature) diff --git a/src/crypto/generic-ops.h b/src/crypto/generic-ops.h index 62bc758c9..42b98706e 100644 --- a/src/crypto/generic-ops.h +++ b/src/crypto/generic-ops.h @@ -33,19 +33,30 @@ #include <cstddef> #include <cstring> #include <functional> +#include <sodium/crypto_verify_32.h> #define CRYPTO_MAKE_COMPARABLE(type) \ namespace crypto { \ inline bool operator==(const type &_v1, const type &_v2) { \ - return std::memcmp(&_v1, &_v2, sizeof(type)) == 0; \ + return !memcmp(&_v1, &_v2, sizeof(_v1)); \ } \ inline bool operator!=(const type &_v1, const type &_v2) { \ - return std::memcmp(&_v1, &_v2, sizeof(type)) != 0; \ + return !operator==(_v1, _v2); \ } \ } -#define CRYPTO_MAKE_HASHABLE(type) \ -CRYPTO_MAKE_COMPARABLE(type) \ +#define CRYPTO_MAKE_COMPARABLE_CONSTANT_TIME(type) \ +namespace crypto { \ + inline bool operator==(const type &_v1, const type &_v2) { \ + static_assert(sizeof(_v1) == 32, "constant time comparison is only implenmted for 32 bytes"); \ + return crypto_verify_32((const unsigned char*)&_v1, (const unsigned char*)&_v2) == 0; \ + } \ + inline bool operator!=(const type &_v1, const type &_v2) { \ + return !operator==(_v1, _v2); \ + } \ +} + +#define CRYPTO_DEFINE_HASH_FUNCTIONS(type) \ namespace crypto { \ static_assert(sizeof(std::size_t) <= sizeof(type), "Size of " #type " must be at least that of size_t"); \ inline std::size_t hash_value(const type &_v) { \ @@ -60,3 +71,12 @@ namespace std { \ } \ }; \ } + +#define CRYPTO_MAKE_HASHABLE(type) \ +CRYPTO_MAKE_COMPARABLE(type) \ +CRYPTO_DEFINE_HASH_FUNCTIONS(type) + +#define CRYPTO_MAKE_HASHABLE_CONSTANT_TIME(type) \ +CRYPTO_MAKE_COMPARABLE_CONSTANT_TIME(type) \ +CRYPTO_DEFINE_HASH_FUNCTIONS(type) + diff --git a/src/crypto/keccak.c b/src/crypto/keccak.c index de8e2a5b3..8fcd2138e 100644 --- a/src/crypto/keccak.c +++ b/src/crypto/keccak.c @@ -132,3 +132,77 @@ void keccak1600(const uint8_t *in, size_t inlen, uint8_t *md) { keccak(in, inlen, md, sizeof(state_t)); } + +#define KECCAK_FINALIZED 0x80000000 +#define KECCAK_BLOCKLEN 136 +#define KECCAK_WORDS 17 +#define KECCAK_DIGESTSIZE 32 +#define IS_ALIGNED_64(p) (0 == (7 & ((const char*)(p) - (const char*)0))) +#define KECCAK_PROCESS_BLOCK(st, block) { \ + for (int i_ = 0; i_ < KECCAK_WORDS; i_++){ \ + ((st))[i_] ^= ((block))[i_]; \ + }; \ + keccakf(st, KECCAK_ROUNDS); } + + +void keccak_init(KECCAK_CTX * ctx){ + memset(ctx, 0, sizeof(KECCAK_CTX)); +} + +void keccak_update(KECCAK_CTX * ctx, const uint8_t *in, size_t inlen){ + if (ctx->rest & KECCAK_FINALIZED) { + local_abort("Bad keccak use"); + } + + const size_t idx = ctx->rest; + ctx->rest = (ctx->rest + inlen) % KECCAK_BLOCKLEN; + + // fill partial block + if (idx) { + size_t left = KECCAK_BLOCKLEN - idx; + memcpy((char*)ctx->message + idx, in, (inlen < left ? inlen : left)); + if (inlen < left) return; + + KECCAK_PROCESS_BLOCK(ctx->hash, ctx->message); + + in += left; + inlen -= left; + } + + const bool is_aligned = IS_ALIGNED_64(in); + while (inlen >= KECCAK_BLOCKLEN) { + const uint64_t* aligned_message_block; + if (is_aligned) { + aligned_message_block = (uint64_t*)in; + } else { + memcpy(ctx->message, in, KECCAK_BLOCKLEN); + aligned_message_block = ctx->message; + } + + KECCAK_PROCESS_BLOCK(ctx->hash, aligned_message_block); + in += KECCAK_BLOCKLEN; + inlen -= KECCAK_BLOCKLEN; + } + if (inlen) { + memcpy(ctx->message, in, inlen); + } +} + +void keccak_finish(KECCAK_CTX * ctx, uint8_t *md){ + if (!(ctx->rest & KECCAK_FINALIZED)) + { + // clear the rest of the data queue + memset((char*)ctx->message + ctx->rest, 0, KECCAK_BLOCKLEN - ctx->rest); + ((char*)ctx->message)[ctx->rest] |= 0x01; + ((char*)ctx->message)[KECCAK_BLOCKLEN - 1] |= 0x80; + + // process final block + KECCAK_PROCESS_BLOCK(ctx->hash, ctx->message); + ctx->rest = KECCAK_FINALIZED; // mark context as finalized + } + + static_assert(KECCAK_BLOCKLEN > KECCAK_DIGESTSIZE, ""); + if (md) { + memcpy(md, ctx->hash, KECCAK_DIGESTSIZE); + } +} diff --git a/src/crypto/keccak.h b/src/crypto/keccak.h index fb9d8bd04..9123c7a3b 100644 --- a/src/crypto/keccak.h +++ b/src/crypto/keccak.h @@ -15,6 +15,17 @@ #define ROTL64(x, y) (((x) << (y)) | ((x) >> (64 - (y)))) #endif +// SHA3 Algorithm context. +typedef struct KECCAK_CTX +{ + // 1600 bits algorithm hashing state + uint64_t hash[25]; + // 1088-bit buffer for leftovers, block size = 136 B for 256-bit keccak + uint64_t message[17]; + // count of bytes in the message[] buffer + size_t rest; +} KECCAK_CTX; + // compute a keccak hash (md) of given byte length from "in" void keccak(const uint8_t *in, size_t inlen, uint8_t *md, int mdlen); @@ -23,4 +34,7 @@ void keccakf(uint64_t st[25], int norounds); void keccak1600(const uint8_t *in, size_t inlen, uint8_t *md); +void keccak_init(KECCAK_CTX * ctx); +void keccak_update(KECCAK_CTX * ctx, const uint8_t *in, size_t inlen); +void keccak_finish(KECCAK_CTX * ctx, uint8_t *md); #endif diff --git a/src/cryptonote_basic/account.cpp b/src/cryptonote_basic/account.cpp index aac6ec22b..4cbfa8142 100644 --- a/src/cryptonote_basic/account.cpp +++ b/src/cryptonote_basic/account.cpp @@ -44,6 +44,9 @@ extern "C" #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "account" +#define KEYS_ENCRYPTION_SALT 'k' + + using namespace std; DISABLE_VS_WARNINGS(4244 4345) @@ -60,7 +63,70 @@ DISABLE_VS_WARNINGS(4244 4345) m_device = &hwdev; MCDEBUG("device", "account_keys::set_device device type: "<<typeid(hwdev).name()); } + //----------------------------------------------------------------- + static void derive_key(const crypto::chacha_key &base_key, crypto::chacha_key &key) + { + static_assert(sizeof(base_key) == sizeof(crypto::hash), "chacha key and hash should be the same size"); + epee::mlocked<tools::scrubbed_arr<char, sizeof(base_key)+1>> data; + memcpy(data.data(), &base_key, sizeof(base_key)); + data[sizeof(base_key)] = KEYS_ENCRYPTION_SALT; + crypto::generate_chacha_key(data.data(), sizeof(data), key, 1); + } + //----------------------------------------------------------------- + static epee::wipeable_string get_key_stream(const crypto::chacha_key &base_key, const crypto::chacha_iv &iv, size_t bytes) + { + // derive a new key + crypto::chacha_key key; + derive_key(base_key, key); + // chacha + epee::wipeable_string buffer0(std::string(bytes, '\0')); + epee::wipeable_string buffer1 = buffer0; + crypto::chacha20(buffer0.data(), buffer0.size(), key, iv, buffer1.data()); + return buffer1; + } + //----------------------------------------------------------------- + void account_keys::xor_with_key_stream(const crypto::chacha_key &key) + { + // encrypt a large enough byte stream with chacha20 + epee::wipeable_string key_stream = get_key_stream(key, m_encryption_iv, sizeof(crypto::secret_key) * (2 + m_multisig_keys.size())); + const char *ptr = key_stream.data(); + for (size_t i = 0; i < sizeof(crypto::secret_key); ++i) + m_spend_secret_key.data[i] ^= *ptr++; + for (size_t i = 0; i < sizeof(crypto::secret_key); ++i) + m_view_secret_key.data[i] ^= *ptr++; + for (crypto::secret_key &k: m_multisig_keys) + { + for (size_t i = 0; i < sizeof(crypto::secret_key); ++i) + k.data[i] ^= *ptr++; + } + } + //----------------------------------------------------------------- + void account_keys::encrypt(const crypto::chacha_key &key) + { + m_encryption_iv = crypto::rand<crypto::chacha_iv>(); + xor_with_key_stream(key); + } + //----------------------------------------------------------------- + void account_keys::decrypt(const crypto::chacha_key &key) + { + xor_with_key_stream(key); + } + //----------------------------------------------------------------- + void account_keys::encrypt_viewkey(const crypto::chacha_key &key) + { + // encrypt a large enough byte stream with chacha20 + epee::wipeable_string key_stream = get_key_stream(key, m_encryption_iv, sizeof(crypto::secret_key) * 2); + const char *ptr = key_stream.data(); + ptr += sizeof(crypto::secret_key); + for (size_t i = 0; i < sizeof(crypto::secret_key); ++i) + m_view_secret_key.data[i] ^= *ptr++; + } + //----------------------------------------------------------------- + void account_keys::decrypt_viewkey(const crypto::chacha_key &key) + { + encrypt_viewkey(key); + } //----------------------------------------------------------------- account_base::account_base() { @@ -157,7 +223,7 @@ DISABLE_VS_WARNINGS(4244 4345) void account_base::create_from_viewkey(const cryptonote::account_public_address& address, const crypto::secret_key& viewkey) { crypto::secret_key fake; - memset(&unwrap(fake), 0, sizeof(fake)); + memset(&unwrap(unwrap(fake)), 0, sizeof(fake)); create_from_keys(address, fake, viewkey); } //----------------------------------------------------------------- diff --git a/src/cryptonote_basic/account.h b/src/cryptonote_basic/account.h index b5d119c46..dac66ff1a 100644 --- a/src/cryptonote_basic/account.h +++ b/src/cryptonote_basic/account.h @@ -44,18 +44,29 @@ namespace cryptonote crypto::secret_key m_view_secret_key; std::vector<crypto::secret_key> m_multisig_keys; hw::device *m_device = &hw::get_device("default"); + crypto::chacha_iv m_encryption_iv; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(m_account_address) KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE(m_spend_secret_key) KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE(m_view_secret_key) KV_SERIALIZE_CONTAINER_POD_AS_BLOB(m_multisig_keys) + const crypto::chacha_iv default_iv{{0, 0, 0, 0, 0, 0, 0, 0}}; + KV_SERIALIZE_VAL_POD_AS_BLOB_OPT(m_encryption_iv, default_iv) END_KV_SERIALIZE_MAP() account_keys& operator=(account_keys const&) = default; + void encrypt(const crypto::chacha_key &key); + void decrypt(const crypto::chacha_key &key); + void encrypt_viewkey(const crypto::chacha_key &key); + void decrypt_viewkey(const crypto::chacha_key &key); + hw::device& get_device() const ; void set_device( hw::device &hwdev) ; + + private: + void xor_with_key_stream(const crypto::chacha_key &key); }; /************************************************************************/ @@ -87,6 +98,11 @@ namespace cryptonote void forget_spend_key(); const std::vector<crypto::secret_key> &get_multisig_keys() const { return m_keys.m_multisig_keys; } + void encrypt_keys(const crypto::chacha_key &key) { m_keys.encrypt(key); } + void decrypt_keys(const crypto::chacha_key &key) { m_keys.decrypt(key); } + void encrypt_viewkey(const crypto::chacha_key &key) { m_keys.encrypt_viewkey(key); } + void decrypt_viewkey(const crypto::chacha_key &key) { m_keys.decrypt_viewkey(key); } + template <class t_archive> inline void serialize(t_archive &a, const unsigned int /*ver*/) { diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 87ef47c11..e96dc6bb6 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -156,9 +156,10 @@ static const struct { //------------------------------------------------------------------ Blockchain::Blockchain(tx_memory_pool& tx_pool) : m_db(), m_tx_pool(tx_pool), m_hardfork(NULL), m_timestamps_and_difficulties_height(0), m_current_block_cumul_sz_limit(0), m_current_block_cumul_sz_median(0), - m_enforce_dns_checkpoints(false), m_max_prepare_blocks_threads(4), m_db_blocks_per_sync(1), m_db_sync_mode(db_async), m_db_default_sync(false), m_fast_sync(true), m_show_time_stats(false), m_sync_counter(0), m_cancel(false), + m_enforce_dns_checkpoints(false), m_max_prepare_blocks_threads(4), m_db_sync_on_blocks(true), m_db_sync_threshold(1), m_db_sync_mode(db_async), m_db_default_sync(false), m_fast_sync(true), m_show_time_stats(false), m_sync_counter(0), m_bytes_to_sync(0), m_cancel(false), m_difficulty_for_next_block_top_hash(crypto::null_hash), - m_difficulty_for_next_block(1) + m_difficulty_for_next_block(1), + m_btc_valid(false) { LOG_PRINT_L3("Blockchain::" << __func__); } @@ -632,6 +633,7 @@ block Blockchain::pop_block_from_blockchain() update_next_cumulative_size_limit(); m_tx_pool.on_blockchain_dec(m_db->height()-1, get_tail_id()); + invalidate_block_template_cache(); return popped_block; } @@ -642,6 +644,7 @@ bool Blockchain::reset_and_set_genesis_block(const block& b) CRITICAL_REGION_LOCAL(m_blockchain_lock); m_timestamps_and_difficulties_height = 0; m_alternative_chains.clear(); + invalidate_block_template_cache(); m_db->reset(); m_hardfork->init(); @@ -1212,9 +1215,26 @@ bool Blockchain::create_block_template(block& b, const account_public_address& m LOG_PRINT_L3("Blockchain::" << __func__); size_t median_size; uint64_t already_generated_coins; + uint64_t pool_cookie; CRITICAL_REGION_BEGIN(m_blockchain_lock); height = m_db->height(); + if (m_btc_valid) { + // The pool cookie is atomic. The lack of locking is OK, as if it changes + // just as we compare it, we'll just use a slightly old template, but + // this would be the case anyway if we'd lock, and the change happened + // just after the block template was created + if (!memcmp(&miner_address, &m_btc_address, sizeof(cryptonote::account_public_address)) && m_btc_nonce == ex_nonce && m_btc_pool_cookie == m_tx_pool.cookie()) { + MDEBUG("Using cached template"); + m_btc.timestamp = time(NULL); // update timestamp unconditionally + b = m_btc; + diffic = m_btc_difficulty; + expected_reward = m_btc_expected_reward; + return true; + } + MDEBUG("Not using cached template: address " << (!memcmp(&miner_address, &m_btc_address, sizeof(cryptonote::account_public_address))) << ", nonce " << (m_btc_nonce == ex_nonce) << ", cookie " << (m_btc_pool_cookie == m_tx_pool.cookie())); + invalidate_block_template_cache(); + } b.major_version = m_hardfork->get_current_version(); b.minor_version = m_hardfork->get_ideal_version(); @@ -1241,6 +1261,7 @@ bool Blockchain::create_block_template(block& b, const account_public_address& m { return false; } + pool_cookie = m_tx_pool.cookie(); #if defined(DEBUG_CREATE_BLOCK_TEMPLATE) size_t real_txs_size = 0; uint64_t real_fee = 0; @@ -1355,6 +1376,8 @@ bool Blockchain::create_block_template(block& b, const account_public_address& m MDEBUG("Creating block template: miner tx size " << coinbase_blob_size << ", cumulative size " << cumulative_size << " is now good"); #endif + + cache_block_template(b, miner_address, ex_nonce, diffic, expected_reward, pool_cookie); return true; } LOG_ERROR("Failed to create_block_template with " << 10 << " tries"); @@ -2294,7 +2317,7 @@ bool Blockchain::find_blockchain_supplement(const uint64_t req_start_block, cons total_height = get_current_blockchain_height(); size_t count = 0, size = 0; blocks.reserve(std::min(std::min(max_count, (size_t)10000), (size_t)(total_height - start_height))); - for(size_t i = start_height; i < total_height && count < max_count && (size < FIND_BLOCKCHAIN_SUPPLEMENT_MAX_SIZE || count < 3); i++, count++) + for(uint64_t i = start_height; i < total_height && count < max_count && (size < FIND_BLOCKCHAIN_SUPPLEMENT_MAX_SIZE || count < 3); i++, count++) { blocks.resize(blocks.size()+1); blocks.back().first.first = m_db->get_block_blob_from_height(i); @@ -3697,6 +3720,7 @@ leave: // appears to be a NOP *and* is called elsewhere. wat? m_tx_pool.on_blockchain_inc(new_height, id); get_difficulty_for_next_block(); // just to cache it + invalidate_block_template_cache(); return true; } @@ -3877,11 +3901,13 @@ bool Blockchain::cleanup_handle_incoming_blocks(bool force_sync) store_blockchain(); m_sync_counter = 0; } - else if (m_db_blocks_per_sync && m_sync_counter >= m_db_blocks_per_sync) + else if (m_db_sync_threshold && ((m_db_sync_on_blocks && m_sync_counter >= m_db_sync_threshold) || (!m_db_sync_on_blocks && m_bytes_to_sync >= m_db_sync_threshold))) { + MDEBUG("Sync threshold met, syncing"); if(m_db_sync_mode == db_async) { m_sync_counter = 0; + m_bytes_to_sync = 0; m_async_service.dispatch(boost::bind(&Blockchain::store_blockchain, this)); } else if(m_db_sync_mode == db_sync) @@ -4073,6 +4099,7 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::vector<block_complete } total_txs += entry.txs.size(); } + m_bytes_to_sync += bytes; while (!(stop_batch = m_db->batch_start(blocks_entry.size(), bytes))) { m_blockchain_lock.unlock(); m_tx_pool.unlock(); @@ -4423,7 +4450,7 @@ bool Blockchain::for_all_txpool_txes(std::function<bool(const crypto::hash&, con return m_db->for_all_txpool_txes(f, include_blob, include_unrelayed_txes); } -void Blockchain::set_user_options(uint64_t maxthreads, uint64_t blocks_per_sync, blockchain_db_sync_mode sync_mode, bool fast_sync) +void Blockchain::set_user_options(uint64_t maxthreads, bool sync_on_blocks, uint64_t sync_threshold, blockchain_db_sync_mode sync_mode, bool fast_sync) { if (sync_mode == db_defaultsync) { @@ -4432,7 +4459,8 @@ void Blockchain::set_user_options(uint64_t maxthreads, uint64_t blocks_per_sync, } m_db_sync_mode = sync_mode; m_fast_sync = fast_sync; - m_db_blocks_per_sync = blocks_per_sync; + m_db_sync_on_blocks = sync_on_blocks; + m_db_sync_threshold = sync_threshold; m_max_prepare_blocks_threads = maxthreads; } @@ -4666,6 +4694,24 @@ bool Blockchain::for_all_outputs(uint64_t amount, std::function<bool(uint64_t he return m_db->for_all_outputs(amount, f);; } +void Blockchain::invalidate_block_template_cache() +{ + MDEBUG("Invalidating block template cache"); + m_btc_valid = false; +} + +void Blockchain::cache_block_template(const block &b, const cryptonote::account_public_address &address, const blobdata &nonce, const difficulty_type &diff, uint64_t expected_reward, uint64_t pool_cookie) +{ + MDEBUG("Setting block template cache"); + m_btc = b; + m_btc_address = address; + m_btc_nonce = nonce; + m_btc_difficulty = diff; + m_btc_expected_reward = expected_reward; + m_btc_pool_cookie = pool_cookie; + m_btc_valid = true; +} + namespace cryptonote { template bool Blockchain::get_transactions(const std::vector<crypto::hash>&, std::vector<transaction>&, std::vector<crypto::hash>&) const; template bool Blockchain::get_transactions_blobs(const std::vector<crypto::hash>&, std::vector<cryptonote::blobdata>&, std::vector<crypto::hash>&, bool) const; diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index d95c8ed15..2292ffbf3 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -729,11 +729,12 @@ namespace cryptonote * @brief sets various performance options * * @param maxthreads max number of threads when preparing blocks for addition - * @param blocks_per_sync number of blocks to cache before syncing to database + * @param sync_on_blocks whether to sync based on blocks or bytes + * @param sync_threshold number of blocks/bytes to cache before syncing to database * @param sync_mode the ::blockchain_db_sync_mode to use * @param fast_sync sync using built-in block hashes as trusted */ - void set_user_options(uint64_t maxthreads, uint64_t blocks_per_sync, + void set_user_options(uint64_t maxthreads, bool sync_on_blocks, uint64_t sync_threshold, blockchain_db_sync_mode sync_mode, bool fast_sync); /** @@ -1017,11 +1018,13 @@ namespace cryptonote bool m_fast_sync; bool m_show_time_stats; bool m_db_default_sync; - uint64_t m_db_blocks_per_sync; + bool m_db_sync_on_blocks; + uint64_t m_db_sync_threshold; uint64_t m_max_prepare_blocks_threads; uint64_t m_fake_pow_calc_time; uint64_t m_fake_scan_time; uint64_t m_sync_counter; + uint64_t m_bytes_to_sync; std::vector<uint64_t> m_timestamps; std::vector<difficulty_type> m_difficulties; uint64_t m_timestamps_and_difficulties_height; @@ -1052,6 +1055,15 @@ namespace cryptonote std::atomic<bool> m_cancel; + // block template cache + block m_btc; + account_public_address m_btc_address; + blobdata m_btc_nonce; + difficulty_type m_btc_difficulty; + uint64_t m_btc_pool_cookie; + uint64_t m_btc_expected_reward; + bool m_btc_valid; + /** * @brief collects the keys for all outputs being "spent" as an input * @@ -1407,5 +1419,17 @@ namespace cryptonote * that implicit data. */ bool expand_transaction_2(transaction &tx, const crypto::hash &tx_prefix_hash, const std::vector<std::vector<rct::ctkey>> &pubkeys); + + /** + * @brief invalidates any cached block template + */ + void invalidate_block_template_cache(); + + /** + * @brief stores a new cached block template + * + * At some point, may be used to push an update to miners + */ + void cache_block_template(const block &b, const cryptonote::account_public_address &address, const blobdata &nonce, const difficulty_type &diff, uint64_t expected_reward, uint64_t pool_cookie); }; } // namespace cryptonote diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 18490c65e..d0db38799 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -439,9 +439,10 @@ namespace cryptonote MGINFO("Loading blockchain from folder " << folder.string() << " ..."); const std::string filename = folder.string(); - // default to fast:async:1 + // default to fast:async:1 if overridden blockchain_db_sync_mode sync_mode = db_defaultsync; - uint64_t blocks_per_sync = 1; + bool sync_on_blocks = true; + uint64_t sync_threshold = 1; if (m_nettype == FAKECHAIN) { @@ -491,7 +492,7 @@ namespace cryptonote else if(options[0] == "fastest") { db_flags = DBF_FASTEST; - blocks_per_sync = 1000; // default to fastest:async:1000 + sync_threshold = 1000; // default to fastest:async:1000 sync_mode = db_sync_mode_is_default ? db_defaultsync : db_async; } else @@ -509,9 +510,22 @@ namespace cryptonote if(options.size() >= 3 && !safemode) { char *endptr; - uint64_t bps = strtoull(options[2].c_str(), &endptr, 0); - if (*endptr == '\0') - blocks_per_sync = bps; + uint64_t threshold = strtoull(options[2].c_str(), &endptr, 0); + if (*endptr == '\0' || !strcmp(endptr, "blocks")) + { + sync_on_blocks = true; + sync_threshold = threshold; + } + else if (!strcmp(endptr, "bytes")) + { + sync_on_blocks = false; + sync_threshold = threshold; + } + else + { + LOG_ERROR("Invalid db sync mode: " << options[2]); + return false; + } } if (db_salvage) @@ -528,7 +542,7 @@ namespace cryptonote } m_blockchain_storage.set_user_options(blocks_threads, - blocks_per_sync, sync_mode, fast_sync); + sync_on_blocks, sync_threshold, sync_mode, fast_sync); const std::pair<uint8_t, uint64_t> regtest_hard_forks[3] = {std::make_pair(1, 0), std::make_pair(Blockchain::get_hard_fork_heights(MAINNET).back().version, 1), std::make_pair(0, 0)}; const cryptonote::test_options regtest_test_options = { diff --git a/src/cryptonote_core/cryptonote_tx_utils.cpp b/src/cryptonote_core/cryptonote_tx_utils.cpp index 071ce591e..1581f3088 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.cpp +++ b/src/cryptonote_core/cryptonote_tx_utils.cpp @@ -516,6 +516,7 @@ namespace cryptonote uint64_t amount_in = 0, amount_out = 0; rct::ctkeyV inSk; + inSk.reserve(sources.size()); // mixRing indexing is done the other way round for simple rct::ctkeyM mixRing(use_simple_rct ? sources.size() : n_total_outs); rct::keyV destinations; @@ -532,6 +533,7 @@ namespace cryptonote ctkey.dest = rct::sk2rct(in_contexts[i].in_ephemeral.sec); ctkey.mask = sources[i].mask; inSk.push_back(ctkey); + memwipe(&ctkey, sizeof(rct::ctkey)); // inPk: (public key, commitment) // will be done when filling in mixRing if (msout) @@ -590,6 +592,7 @@ namespace cryptonote tx.rct_signatures = rct::genRctSimple(rct::hash2rct(tx_prefix_hash), inSk, destinations, inamounts, outamounts, amount_in - amount_out, mixRing, amount_keys, msout ? &kLRki : NULL, msout, index, outSk, bulletproof, hwdev); else tx.rct_signatures = rct::genRct(rct::hash2rct(tx_prefix_hash), inSk, destinations, outamounts, mixRing, amount_keys, msout ? &kLRki[0] : NULL, msout, sources[0].real_output, outSk, bulletproof, hwdev); // same index assumption + memwipe(inSk.data(), inSk.size() * sizeof(rct::ctkey)); CHECK_AND_ASSERT_MES(tx.vout.size() == outSk.size(), false, "outSk size does not match vout"); diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp index eac0f1f57..5807867d9 100644 --- a/src/cryptonote_core/tx_pool.cpp +++ b/src/cryptonote_core/tx_pool.cpp @@ -102,7 +102,7 @@ namespace cryptonote } //--------------------------------------------------------------------------------- //--------------------------------------------------------------------------------- - tx_memory_pool::tx_memory_pool(Blockchain& bchs): m_blockchain(bchs), m_txpool_max_size(DEFAULT_TXPOOL_MAX_SIZE), m_txpool_size(0) + tx_memory_pool::tx_memory_pool(Blockchain& bchs): m_blockchain(bchs), m_txpool_max_size(DEFAULT_TXPOOL_MAX_SIZE), m_txpool_size(0), m_cookie(0) { } @@ -306,6 +306,8 @@ namespace cryptonote tvc.m_verifivation_failed = false; m_txpool_size += blob_size; + ++m_cookie; + MINFO("Transaction added to pool: txid " << id << " bytes: " << blob_size << " fee/byte: " << (fee / (double)blob_size)); prune(m_txpool_max_size); @@ -341,6 +343,7 @@ namespace cryptonote bytes = m_txpool_max_size; CRITICAL_REGION_LOCAL1(m_blockchain); LockedTXN lock(m_blockchain); + bool changed = false; // this will never remove the first one, but we don't care auto it = --m_txs_by_fee_and_receive_time.end(); @@ -377,6 +380,7 @@ namespace cryptonote remove_transaction_keyimages(tx); MINFO("Pruned tx " << txid << " from txpool: size: " << it->first.second << ", fee/byte: " << it->first.first); m_txs_by_fee_and_receive_time.erase(it--); + changed = true; } catch (const std::exception &e) { @@ -384,6 +388,8 @@ namespace cryptonote return; } } + if (changed) + ++m_cookie; if (m_txpool_size > bytes) MINFO("Pool size after pruning is larger than limit: " << m_txpool_size << "/" << bytes); } @@ -401,6 +407,7 @@ namespace cryptonote auto ins_res = kei_image_set.insert(id); CHECK_AND_ASSERT_MES(ins_res.second, false, "internal error: try to insert duplicate iterator in key_image set"); } + ++m_cookie; return true; } //--------------------------------------------------------------------------------- @@ -435,6 +442,7 @@ namespace cryptonote } } + ++m_cookie; return true; } //--------------------------------------------------------------------------------- @@ -480,6 +488,7 @@ namespace cryptonote } m_txs_by_fee_and_receive_time.erase(sorted_it); + ++m_cookie; return true; } //--------------------------------------------------------------------------------- @@ -553,6 +562,7 @@ namespace cryptonote // ignore error } } + ++m_cookie; } return true; } @@ -1051,6 +1061,7 @@ namespace cryptonote { CRITICAL_REGION_LOCAL(m_transactions_lock); CRITICAL_REGION_LOCAL1(m_blockchain); + bool changed = false; LockedTXN lock(m_blockchain); for(size_t i = 0; i!= tx.vin.size(); i++) { @@ -1071,6 +1082,7 @@ namespace cryptonote { MDEBUG("Marking " << txid << " as double spending " << itk.k_image); meta.double_spend_seen = true; + changed = true; try { m_blockchain.update_txpool_tx(txid, meta); @@ -1084,6 +1096,8 @@ namespace cryptonote } } } + if (changed) + ++m_cookie; } //--------------------------------------------------------------------------------- std::string tx_memory_pool::print_pool(bool short_format) const @@ -1305,6 +1319,8 @@ namespace cryptonote } } } + if (n_removed > 0) + ++m_cookie; return n_removed; } //--------------------------------------------------------------------------------- @@ -1361,6 +1377,10 @@ namespace cryptonote } } } + + m_cookie = 0; + + // Ignore deserialization error return true; } diff --git a/src/cryptonote_core/tx_pool.h b/src/cryptonote_core/tx_pool.h index 4ade7ddbe..4abfef85c 100644 --- a/src/cryptonote_core/tx_pool.h +++ b/src/cryptonote_core/tx_pool.h @@ -362,6 +362,13 @@ namespace cryptonote */ size_t validate(uint8_t version); + /** + * @brief return the cookie + * + * @return the cookie + */ + uint64_t cookie() const { return m_cookie; } + /** * @brief get the cumulative txpool size in bytes * @@ -549,6 +556,8 @@ private: //!< container for transactions organized by fee per size and receive time sorted_tx_container m_txs_by_fee_and_receive_time; + std::atomic<uint64_t> m_cookie; //!< incremented at each change + /** * @brief get an iterator to a transaction in the sorted container * diff --git a/src/cryptonote_protocol/block_queue.cpp b/src/cryptonote_protocol/block_queue.cpp index c39d67ceb..05f4189fb 100644 --- a/src/cryptonote_protocol/block_queue.cpp +++ b/src/cryptonote_protocol/block_queue.cpp @@ -57,7 +57,11 @@ void block_queue::add_blocks(uint64_t height, std::vector<cryptonote::block_comp bool has_hashes = remove_span(height, &hashes); blocks.insert(span(height, std::move(bcel), connection_id, rate, size)); if (has_hashes) + { + for (const crypto::hash &h: hashes) + requested_hashes.insert(h); set_span_hashes(height, connection_id, hashes); + } } void block_queue::add_blocks(uint64_t height, uint64_t nblocks, const boost::uuids::uuid &connection_id, boost::posix_time::ptime time) @@ -76,11 +80,19 @@ void block_queue::flush_spans(const boost::uuids::uuid &connection_id, bool all) block_map::iterator j = i++; if (j->connection_id == connection_id && (all || j->blocks.size() == 0)) { - blocks.erase(j); + erase_block(j); } } } +void block_queue::erase_block(block_map::iterator j) +{ + CHECK_AND_ASSERT_THROW_MES(j != blocks.end(), "Invalid iterator"); + for (const crypto::hash &h: j->hashes) + requested_hashes.erase(h); + blocks.erase(j); +} + void block_queue::flush_stale_spans(const std::set<boost::uuids::uuid> &live_connections) { boost::unique_lock<boost::recursive_mutex> lock(mutex); @@ -92,7 +104,7 @@ void block_queue::flush_stale_spans(const std::set<boost::uuids::uuid> &live_con block_map::iterator j = i++; if (live_connections.find(j->connection_id) == live_connections.end() && j->blocks.size() == 0) { - blocks.erase(j); + erase_block(j); } } } @@ -106,7 +118,7 @@ bool block_queue::remove_span(uint64_t start_block_height, std::vector<crypto::h { if (hashes) *hashes = std::move(i->hashes); - blocks.erase(i); + erase_block(i); return true; } } @@ -121,7 +133,7 @@ void block_queue::remove_spans(const boost::uuids::uuid &connection_id, uint64_t block_map::iterator j = i++; if (j->connection_id == connection_id && j->start_block_height <= start_block_height) { - blocks.erase(j); + erase_block(j); } } } @@ -160,16 +172,15 @@ std::string block_queue::get_overview() const return s; } +inline bool block_queue::requested_internal(const crypto::hash &hash) const +{ + return requested_hashes.find(hash) != requested_hashes.end(); +} + bool block_queue::requested(const crypto::hash &hash) const { boost::unique_lock<boost::recursive_mutex> lock(mutex); - for (const auto &span: blocks) - { - for (const auto &h: span.hashes) - if (h == hash) - return true; - } - return false; + return requested_internal(hash); } std::pair<uint64_t, uint64_t> block_queue::reserve_span(uint64_t first_block_height, uint64_t last_block_height, uint64_t max_blocks, const boost::uuids::uuid &connection_id, const std::vector<crypto::hash> &block_hashes, boost::posix_time::ptime time) @@ -184,7 +195,7 @@ std::pair<uint64_t, uint64_t> block_queue::reserve_span(uint64_t first_block_hei uint64_t span_start_height = last_block_height - block_hashes.size() + 1; std::vector<crypto::hash>::const_iterator i = block_hashes.begin(); - while (i != block_hashes.end() && requested(*i)) + while (i != block_hashes.end() && requested_internal(*i)) { ++i; ++span_start_height; @@ -256,8 +267,10 @@ void block_queue::set_span_hashes(uint64_t start_height, const boost::uuids::uui if (i->start_block_height == start_height && i->connection_id == connection_id) { span s = *i; - blocks.erase(i); + erase_block(i); s.hashes = std::move(hashes); + for (const crypto::hash &h: s.hashes) + requested_hashes.insert(h); blocks.insert(s); return; } diff --git a/src/cryptonote_protocol/block_queue.h b/src/cryptonote_protocol/block_queue.h index 9059e89ac..9cce95075 100644 --- a/src/cryptonote_protocol/block_queue.h +++ b/src/cryptonote_protocol/block_queue.h @@ -33,6 +33,7 @@ #include <string> #include <vector> #include <set> +#include <unordered_set> #include <boost/thread/recursive_mutex.hpp> #include <boost/uuid/uuid.hpp> @@ -93,7 +94,12 @@ namespace cryptonote bool requested(const crypto::hash &hash) const; private: + void erase_block(block_map::iterator j); + inline bool requested_internal(const crypto::hash &hash) const; + + private: block_map blocks; mutable boost::recursive_mutex mutex; + std::unordered_set<crypto::hash> requested_hashes; }; } diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.inl b/src/cryptonote_protocol/cryptonote_protocol_handler.inl index a931d3b57..c2c660e8c 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.inl +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.inl @@ -1683,11 +1683,6 @@ skip: fluffy_arg.b = arg.b; fluffy_arg.b.txs = fluffy_txs; - // pre-serialize them - std::string fullBlob, fluffyBlob; - epee::serialization::store_t_to_binary(arg, fullBlob); - epee::serialization::store_t_to_binary(fluffy_arg, fluffyBlob); - // sort peers between fluffy ones and others std::list<boost::uuids::uuid> fullConnections, fluffyConnections; m_p2p->for_each_connection([this, &exclude_context, &fullConnections, &fluffyConnections](connection_context& context, nodetool::peerid_type peer_id, uint32_t support_flags) @@ -1709,8 +1704,18 @@ skip: }); // send fluffy ones first, we want to encourage people to run that - m_p2p->relay_notify_to_list(NOTIFY_NEW_FLUFFY_BLOCK::ID, fluffyBlob, fluffyConnections); - m_p2p->relay_notify_to_list(NOTIFY_NEW_BLOCK::ID, fullBlob, fullConnections); + if (!fluffyConnections.empty()) + { + std::string fluffyBlob; + epee::serialization::store_t_to_binary(fluffy_arg, fluffyBlob); + m_p2p->relay_notify_to_list(NOTIFY_NEW_FLUFFY_BLOCK::ID, fluffyBlob, fluffyConnections); + } + if (!fullConnections.empty()) + { + std::string fullBlob; + epee::serialization::store_t_to_binary(arg, fullBlob); + m_p2p->relay_notify_to_list(NOTIFY_NEW_BLOCK::ID, fullBlob, fullConnections); + } return true; } diff --git a/src/device/device_default.cpp b/src/device/device_default.cpp index bf14813ea..a4f40e041 100644 --- a/src/device/device_default.cpp +++ b/src/device/device_default.cpp @@ -103,7 +103,7 @@ namespace hw { bool device_default::generate_chacha_key(const cryptonote::account_keys &keys, crypto::chacha_key &key, uint64_t kdf_rounds) { const crypto::secret_key &view_key = keys.m_view_secret_key; const crypto::secret_key &spend_key = keys.m_spend_secret_key; - tools::scrubbed_arr<char, sizeof(view_key) + sizeof(spend_key) + 1> data; + epee::mlocked<tools::scrubbed_arr<char, sizeof(view_key) + sizeof(spend_key) + 1>> data; memcpy(data.data(), &view_key, sizeof(view_key)); memcpy(data.data() + sizeof(view_key), &spend_key, sizeof(spend_key)); data[sizeof(data) - 1] = CHACHA8_KEY_TAIL; diff --git a/src/device/device_ledger.cpp b/src/device/device_ledger.cpp index 7a34dad5e..c4e9e40b7 100644 --- a/src/device/device_ledger.cpp +++ b/src/device/device_ledger.cpp @@ -136,7 +136,8 @@ namespace hw { } bool operator==(const crypto::key_derivation &d0, const crypto::key_derivation &d1) { - return !memcmp(&d0, &d1, sizeof(d0)); + static_assert(sizeof(crypto::key_derivation) == 32, "key_derivation must be 32 bytes"); + return !crypto_verify_32((const unsigned char*)&d0, (const unsigned char*)&d1); } /* ===================================================================== */ diff --git a/src/gen_multisig/gen_multisig.cpp b/src/gen_multisig/gen_multisig.cpp index 03e0a7946..e680a8157 100644 --- a/src/gen_multisig/gen_multisig.cpp +++ b/src/gen_multisig/gen_multisig.cpp @@ -92,7 +92,7 @@ static bool generate_multisig(uint32_t threshold, uint32_t total, const std::str { std::string name = basename + "-" + std::to_string(n + 1); wallets[n].reset(new tools::wallet2(nettype)); - wallets[n]->init(""); + wallets[n]->init(false, ""); wallets[n]->generate(name, pwd_container->password(), rct::rct2sk(rct::skGen()), false, false, create_address_file); } @@ -101,11 +101,13 @@ static bool generate_multisig(uint32_t threshold, uint32_t total, const std::str std::vector<crypto::public_key> pk(total); for (size_t n = 0; n < total; ++n) { + wallets[n]->decrypt_keys(pwd_container->password()); if (!tools::wallet2::verify_multisig_info(wallets[n]->get_multisig_info(), sk[n], pk[n])) { tools::fail_msg_writer() << tr("Failed to verify multisig info"); return false; } + wallets[n]->encrypt_keys(pwd_container->password()); } // make the wallets multisig diff --git a/src/mnemonics/electrum-words.cpp b/src/mnemonics/electrum-words.cpp index 19a9c26bb..3d6338856 100644 --- a/src/mnemonics/electrum-words.cpp +++ b/src/mnemonics/electrum-words.cpp @@ -43,8 +43,11 @@ #include <vector> #include <unordered_map> #include <boost/algorithm/string.hpp> +#include "wipeable_string.h" +#include "misc_language.h" #include "crypto/crypto.h" // for declaration of crypto::secret_key #include <fstream> +#include "common/int-util.h" #include "mnemonics/electrum-words.h" #include <stdexcept> #include <boost/filesystem.hpp> @@ -80,9 +83,9 @@ namespace crypto namespace { - uint32_t create_checksum_index(const std::vector<std::string> &word_list, + uint32_t create_checksum_index(const std::vector<epee::wipeable_string> &word_list, uint32_t unique_prefix_length); - bool checksum_test(std::vector<std::string> seed, uint32_t unique_prefix_length); + bool checksum_test(std::vector<epee::wipeable_string> seed, uint32_t unique_prefix_length); /*! * \brief Finds the word list that contains the seed words and puts the indices @@ -93,7 +96,7 @@ namespace * \param language Language instance pointer to write to after it is found. * \return true if all the words were present in some language false if not. */ - bool find_seed_language(const std::vector<std::string> &seed, + bool find_seed_language(const std::vector<epee::wipeable_string> &seed, bool has_checksum, std::vector<uint32_t> &matched_indices, Language::Base **language) { // If there's a new language added, add an instance of it here. @@ -114,17 +117,19 @@ namespace }); Language::Base *fallback = NULL; + std::vector<epee::wipeable_string>::const_iterator it2; + matched_indices.reserve(seed.size()); + // Iterate through all the languages and find a match for (std::vector<Language::Base*>::iterator it1 = language_instances.begin(); it1 != language_instances.end(); it1++) { - const std::unordered_map<std::string, uint32_t> &word_map = (*it1)->get_word_map(); - const std::unordered_map<std::string, uint32_t> &trimmed_word_map = (*it1)->get_trimmed_word_map(); + const std::unordered_map<epee::wipeable_string, uint32_t> &word_map = (*it1)->get_word_map(); + const std::unordered_map<epee::wipeable_string, uint32_t> &trimmed_word_map = (*it1)->get_trimmed_word_map(); // To iterate through seed words - std::vector<std::string>::const_iterator it2; bool full_match = true; - std::string trimmed_word; + epee::wipeable_string trimmed_word; // Iterate through all the words and see if they're all present for (it2 = seed.begin(); it2 != seed.end(); it2++) { @@ -167,6 +172,7 @@ namespace return true; } // Some didn't match. Clear the index array. + memwipe(matched_indices.data(), matched_indices.size() * sizeof(matched_indices[0])); matched_indices.clear(); } @@ -181,6 +187,7 @@ namespace } MINFO("No match found"); + memwipe(matched_indices.data(), matched_indices.size() * sizeof(matched_indices[0])); return false; } @@ -190,12 +197,12 @@ namespace * \param unique_prefix_length the prefix length of each word to use for checksum * \return Checksum index */ - uint32_t create_checksum_index(const std::vector<std::string> &word_list, + uint32_t create_checksum_index(const std::vector<epee::wipeable_string> &word_list, uint32_t unique_prefix_length) { - std::string trimmed_words = ""; + epee::wipeable_string trimmed_words = ""; - for (std::vector<std::string>::const_iterator it = word_list.begin(); it != word_list.end(); it++) + for (std::vector<epee::wipeable_string>::const_iterator it = word_list.begin(); it != word_list.end(); it++) { if (it->length() > unique_prefix_length) { @@ -217,22 +224,22 @@ namespace * \param unique_prefix_length the prefix length of each word to use for checksum * \return True if the test passed false if not. */ - bool checksum_test(std::vector<std::string> seed, uint32_t unique_prefix_length) + bool checksum_test(std::vector<epee::wipeable_string> seed, uint32_t unique_prefix_length) { if (seed.empty()) return false; // The last word is the checksum. - std::string last_word = seed.back(); + epee::wipeable_string last_word = seed.back(); seed.pop_back(); - std::string checksum = seed[create_checksum_index(seed, unique_prefix_length)]; + epee::wipeable_string checksum = seed[create_checksum_index(seed, unique_prefix_length)]; - std::string trimmed_checksum = checksum.length() > unique_prefix_length ? Language::utf8prefix(checksum, unique_prefix_length) : + epee::wipeable_string trimmed_checksum = checksum.length() > unique_prefix_length ? Language::utf8prefix(checksum, unique_prefix_length) : checksum; - std::string trimmed_last_word = last_word.length() > unique_prefix_length ? Language::utf8prefix(last_word, unique_prefix_length) : + epee::wipeable_string trimmed_last_word = last_word.length() > unique_prefix_length ? Language::utf8prefix(last_word, unique_prefix_length) : last_word; bool ret = trimmed_checksum == trimmed_last_word; - MINFO("Checksum is %s" << (ret ? "valid" : "invalid")); + MINFO("Checksum is " << (ret ? "valid" : "invalid")); return ret; } } @@ -260,13 +267,12 @@ namespace crypto * \param language_name Language of the seed as found gets written here. * \return false if not a multiple of 3 words, or if word is not in the words list */ - bool words_to_bytes(std::string words, std::string& dst, size_t len, bool duplicate, + bool words_to_bytes(const epee::wipeable_string &words, epee::wipeable_string& dst, size_t len, bool duplicate, std::string &language_name) { - std::vector<std::string> seed; + std::vector<epee::wipeable_string> seed; - boost::algorithm::trim(words); - boost::split(seed, words, boost::is_any_of(" "), boost::token_compress_on); + words.split(seed); if (len % 4) { @@ -291,6 +297,7 @@ namespace crypto } std::vector<uint32_t> matched_indices; + auto wiper = epee::misc_utils::create_scope_leave_handler([&](){memwipe(matched_indices.data(), matched_indices.size() * sizeof(matched_indices[0]));}); Language::Base *language; if (!find_seed_language(seed, has_checksum, matched_indices, &language)) { @@ -313,33 +320,33 @@ namespace crypto for (unsigned int i=0; i < seed.size() / 3; i++) { - uint32_t val; - uint32_t w1, w2, w3; - w1 = matched_indices[i*3]; - w2 = matched_indices[i*3 + 1]; - w3 = matched_indices[i*3 + 2]; + uint32_t w[4]; + w[1] = matched_indices[i*3]; + w[2] = matched_indices[i*3 + 1]; + w[3] = matched_indices[i*3 + 2]; - val = w1 + word_list_length * (((word_list_length - w1) + w2) % word_list_length) + - word_list_length * word_list_length * (((word_list_length - w2) + w3) % word_list_length); + w[0]= w[1] + word_list_length * (((word_list_length - w[1]) + w[2]) % word_list_length) + + word_list_length * word_list_length * (((word_list_length - w[2]) + w[3]) % word_list_length); - if (!(val % word_list_length == w1)) + if (!(w[0]% word_list_length == w[1])) { + memwipe(w, sizeof(w)); MERROR("Invalid seed: mumble mumble"); return false; } - dst.append((const char*)&val, 4); // copy 4 bytes to position + dst.append((const char*)&w[0], 4); // copy 4 bytes to position + memwipe(w, sizeof(w)); } if (len > 0 && duplicate) { const size_t expected = len * 3 / 32; - std::string wlist_copy = words; if (seed.size() == expected/2) { - dst.append(dst); // if electrum 12-word seed, duplicate - wlist_copy += ' '; - wlist_copy += words; + dst += ' '; // if electrum 12-word seed, duplicate + dst += dst; // if electrum 12-word seed, duplicate + dst.pop_back(); // trailing space } } @@ -353,10 +360,10 @@ namespace crypto * \param language_name Language of the seed as found gets written here. * \return false if not a multiple of 3 words, or if word is not in the words list */ - bool words_to_bytes(std::string words, crypto::secret_key& dst, + bool words_to_bytes(const epee::wipeable_string &words, crypto::secret_key& dst, std::string &language_name) { - std::string s; + epee::wipeable_string s; if (!words_to_bytes(words, s, sizeof(dst), true, language_name)) { MERROR("Invalid seed: failed to convert words to bytes"); @@ -378,7 +385,7 @@ namespace crypto * \param language_name Seed language name * \return true if successful false if not. Unsuccessful if wrong key size. */ - bool bytes_to_words(const char *src, size_t len, std::string& words, + bool bytes_to_words(const char *src, size_t len, epee::wipeable_string& words, const std::string &language_name) { @@ -397,39 +404,38 @@ namespace crypto } const std::vector<std::string> &word_list = language->get_word_list(); // To store the words for random access to add the checksum word later. - std::vector<std::string> words_store; + std::vector<epee::wipeable_string> words_store; uint32_t word_list_length = word_list.size(); // 4 bytes -> 3 words. 8 digits base 16 -> 3 digits base 1626 - for (unsigned int i=0; i < len/4; i++, words += ' ') + for (unsigned int i=0; i < len/4; i++, words.push_back(' ')) { - uint32_t w1, w2, w3; - - uint32_t val; + uint32_t w[4]; - memcpy(&val, src + (i * 4), 4); + w[0] = SWAP32LE(*(const uint32_t*)(src + (i * 4))); - w1 = val % word_list_length; - w2 = ((val / word_list_length) + w1) % word_list_length; - w3 = (((val / word_list_length) / word_list_length) + w2) % word_list_length; + w[1] = w[0] % word_list_length; + w[2] = ((w[0] / word_list_length) + w[1]) % word_list_length; + w[3] = (((w[0] / word_list_length) / word_list_length) + w[2]) % word_list_length; - words += word_list[w1]; + words += word_list[w[1]]; words += ' '; - words += word_list[w2]; + words += word_list[w[2]]; words += ' '; - words += word_list[w3]; + words += word_list[w[3]]; + + words_store.push_back(word_list[w[1]]); + words_store.push_back(word_list[w[2]]); + words_store.push_back(word_list[w[3]]); - words_store.push_back(word_list[w1]); - words_store.push_back(word_list[w2]); - words_store.push_back(word_list[w3]); + memwipe(w, sizeof(w)); } - words.pop_back(); - words += (' ' + words_store[create_checksum_index(words_store, language->get_unique_prefix_length())]); + words += words_store[create_checksum_index(words_store, language->get_unique_prefix_length())]; return true; } - bool bytes_to_words(const crypto::secret_key& src, std::string& words, + bool bytes_to_words(const crypto::secret_key& src, epee::wipeable_string& words, const std::string &language_name) { return bytes_to_words(src.data, sizeof(src), words, language_name); @@ -473,11 +479,10 @@ namespace crypto * \param seed The seed to check (a space delimited concatenated word list) * \return true if the seed passed is a old style seed false if not. */ - bool get_is_old_style_seed(std::string seed) + bool get_is_old_style_seed(const epee::wipeable_string &seed) { - std::vector<std::string> word_list; - boost::algorithm::trim(seed); - boost::split(word_list, seed, boost::is_any_of(" "), boost::token_compress_on); + std::vector<epee::wipeable_string> word_list; + seed.split(word_list); return word_list.size() != (seed_length + 1); } diff --git a/src/mnemonics/electrum-words.h b/src/mnemonics/electrum-words.h index 856edb92a..5401b9779 100644 --- a/src/mnemonics/electrum-words.h +++ b/src/mnemonics/electrum-words.h @@ -44,6 +44,8 @@ #include <map> #include "crypto/crypto.h" // for declaration of crypto::secret_key +namespace epee { class wipeable_string; } + /*! * \namespace crypto * @@ -70,7 +72,7 @@ namespace crypto * \param language_name Language of the seed as found gets written here. * \return false if not a multiple of 3 words, or if word is not in the words list */ - bool words_to_bytes(std::string words, std::string& dst, size_t len, bool duplicate, + bool words_to_bytes(const epee::wipeable_string &words, epee::wipeable_string& dst, size_t len, bool duplicate, std::string &language_name); /*! * \brief Converts seed words to bytes (secret key). @@ -79,7 +81,7 @@ namespace crypto * \param language_name Language of the seed as found gets written here. * \return false if not a multiple of 3 words, or if word is not in the words list */ - bool words_to_bytes(std::string words, crypto::secret_key& dst, + bool words_to_bytes(const epee::wipeable_string &words, crypto::secret_key& dst, std::string &language_name); /*! @@ -90,7 +92,7 @@ namespace crypto * \param language_name Seed language name * \return true if successful false if not. Unsuccessful if wrong key size. */ - bool bytes_to_words(const char *src, size_t len, std::string& words, + bool bytes_to_words(const char *src, size_t len, epee::wipeable_string& words, const std::string &language_name); /*! @@ -100,7 +102,7 @@ namespace crypto * \param language_name Seed language name * \return true if successful false if not. Unsuccessful if wrong key size. */ - bool bytes_to_words(const crypto::secret_key& src, std::string& words, + bool bytes_to_words(const crypto::secret_key& src, epee::wipeable_string& words, const std::string &language_name); /*! @@ -115,7 +117,7 @@ namespace crypto * \param seed The seed to check (a space delimited concatenated word list) * \return true if the seed passed is a old style seed false if not. */ - bool get_is_old_style_seed(std::string seed); + bool get_is_old_style_seed(const epee::wipeable_string &seed); /*! * \brief Returns the name of a language in English diff --git a/src/mnemonics/language_base.h b/src/mnemonics/language_base.h index 2b0c37c6b..cf518ab2a 100644 --- a/src/mnemonics/language_base.h +++ b/src/mnemonics/language_base.h @@ -53,15 +53,20 @@ namespace Language * \param count How many characters to return.
* \return A string consisting of the first count characters in s.
*/
- inline std::string utf8prefix(const std::string &s, size_t count)
+ template<typename T>
+ inline T utf8prefix(const T &s, size_t count)
{
- std::string prefix = "";
- const char *ptr = s.c_str();
- while (count-- && *ptr)
+ T prefix = "";
+ size_t avail = s.size();
+ const char *ptr = s.data();
+ while (count-- && avail--)
{
prefix += *ptr++;
- while (((*ptr) & 0xc0) == 0x80)
+ while (avail && ((*ptr) & 0xc0) == 0x80)
+ {
prefix += *ptr++;
+ --avail;
+ }
}
return prefix;
}
@@ -79,8 +84,8 @@ namespace Language ALLOW_DUPLICATE_PREFIXES = 1<<1,
};
const std::vector<std::string> word_list; /*!< A pointer to the array of words */
- std::unordered_map<std::string, uint32_t> word_map; /*!< hash table to find word's index */
- std::unordered_map<std::string, uint32_t> trimmed_word_map; /*!< hash table to find word's trimmed index */
+ std::unordered_map<epee::wipeable_string, uint32_t> word_map; /*!< hash table to find word's index */
+ std::unordered_map<epee::wipeable_string, uint32_t> trimmed_word_map; /*!< hash table to find word's trimmed index */
std::string language_name; /*!< Name of language */
std::string english_language_name; /*!< Name of language */
uint32_t unique_prefix_length; /*!< Number of unique starting characters to trim the wordlist to when matching */
@@ -103,7 +108,7 @@ namespace Language else
throw std::runtime_error("Too short word in " + language_name + " word list: " + *it);
}
- std::string trimmed;
+ epee::wipeable_string trimmed;
if (it->length() > unique_prefix_length)
{
trimmed = utf8prefix(*it, unique_prefix_length);
@@ -115,9 +120,9 @@ namespace Language if (trimmed_word_map.find(trimmed) != trimmed_word_map.end())
{
if (flags & ALLOW_DUPLICATE_PREFIXES)
- MWARNING("Duplicate prefix in " << language_name << " word list: " << trimmed);
+ MWARNING("Duplicate prefix in " << language_name << " word list: " << std::string(trimmed.data(), trimmed.size()));
else
- throw std::runtime_error("Duplicate prefix in " + language_name + " word list: " + trimmed);
+ throw std::runtime_error("Duplicate prefix in " + language_name + " word list: " + std::string(trimmed.data(), trimmed.size()));
}
trimmed_word_map[trimmed] = ii;
}
@@ -145,7 +150,7 @@ namespace Language * \brief Returns a pointer to the word map.
* \return A pointer to the word map.
*/
- const std::unordered_map<std::string, uint32_t>& get_word_map() const
+ const std::unordered_map<epee::wipeable_string, uint32_t>& get_word_map() const
{
return word_map;
}
@@ -153,7 +158,7 @@ namespace Language * \brief Returns a pointer to the trimmed word map.
* \return A pointer to the trimmed word map.
*/
- const std::unordered_map<std::string, uint32_t>& get_trimmed_word_map() const
+ const std::unordered_map<epee::wipeable_string, uint32_t>& get_trimmed_word_map() const
{
return trimmed_word_map;
}
diff --git a/src/multisig/multisig.cpp b/src/multisig/multisig.cpp index d85c47772..a0a788b7d 100644 --- a/src/multisig/multisig.cpp +++ b/src/multisig/multisig.cpp @@ -47,9 +47,12 @@ namespace cryptonote crypto::secret_key get_multisig_blinded_secret_key(const crypto::secret_key &key) { rct::keyV data; + data.reserve(2); data.push_back(rct::sk2rct(key)); data.push_back(multisig_salt); - return rct::rct2sk(rct::hash_to_scalar(data)); + crypto::secret_key result = rct::rct2sk(rct::hash_to_scalar(data)); + memwipe(&data[0], sizeof(rct::key)); + return result; } //----------------------------------------------------------------- void generate_multisig_N_N(const account_keys &keys, const std::vector<crypto::public_key> &spend_keys, std::vector<crypto::secret_key> &multisig_keys, rct::key &spend_skey, rct::key &spend_pkey) diff --git a/src/ringct/rctSigs.cpp b/src/ringct/rctSigs.cpp index f74216ed4..cc966c44b 100644 --- a/src/ringct/rctSigs.cpp +++ b/src/ringct/rctSigs.cpp @@ -492,7 +492,9 @@ namespace rct { for (size_t j = 0; j < outPk.size(); j++) { sc_sub(sk[rows].bytes, sk[rows].bytes, outSk[j].mask.bytes); //subtract output masks in last row.. } - return MLSAG_Gen(message, M, sk, kLRki, mscout, index, rows, hwdev); + mgSig result = MLSAG_Gen(message, M, sk, kLRki, mscout, index, rows, hwdev); + memwipe(sk.data(), sk.size() * sizeof(key)); + return result; } @@ -521,7 +523,9 @@ namespace rct { M[i][0] = pubs[i].dest; subKeys(M[i][1], pubs[i].mask, Cout); } - return MLSAG_Gen(message, M, sk, kLRki, mscout, index, rows, hwdev); + mgSig result = MLSAG_Gen(message, M, sk, kLRki, mscout, index, rows, hwdev); + memwipe(&sk[0], sizeof(key)); + return result; } diff --git a/src/ringct/rctTypes.h b/src/ringct/rctTypes.h index 844291d0c..452a68eb2 100644 --- a/src/ringct/rctTypes.h +++ b/src/ringct/rctTypes.h @@ -36,6 +36,7 @@ #include <vector> #include <iostream> #include <cinttypes> +#include <sodium/crypto_verify_32.h> extern "C" { #include "crypto/crypto-ops.h" @@ -81,7 +82,7 @@ namespace rct { unsigned char operator[](int i) const { return bytes[i]; } - bool operator==(const key &k) const { return !memcmp(bytes, k.bytes, sizeof(bytes)); } + bool operator==(const key &k) const { return !crypto_verify_32(bytes, k.bytes); } unsigned char bytes[32]; }; typedef std::vector<key> keyV; //vector of keys @@ -403,6 +404,16 @@ namespace rct { }; struct rctSig: public rctSigBase { rctSigPrunable p; + + keyV& get_pseudo_outs() + { + return type == RCTTypeSimpleBulletproof ? p.pseudoOuts : pseudoOuts; + } + + keyV const& get_pseudo_outs() const + { + return type == RCTTypeSimpleBulletproof ? p.pseudoOuts : pseudoOuts; + } }; //other basepoint H = toPoint(cn_fast_hash(G)), G the basepoint @@ -514,16 +525,16 @@ namespace rct { static inline const crypto::secret_key rct2sk(const rct::key &k) { return (const crypto::secret_key&)k; } static inline const crypto::key_image rct2ki(const rct::key &k) { return (const crypto::key_image&)k; } static inline const crypto::hash rct2hash(const rct::key &k) { return (const crypto::hash&)k; } - static inline bool operator==(const rct::key &k0, const crypto::public_key &k1) { return !memcmp(&k0, &k1, 32); } - static inline bool operator!=(const rct::key &k0, const crypto::public_key &k1) { return memcmp(&k0, &k1, 32); } + static inline bool operator==(const rct::key &k0, const crypto::public_key &k1) { return !crypto_verify_32(k0.bytes, (const unsigned char*)&k1); } + static inline bool operator!=(const rct::key &k0, const crypto::public_key &k1) { return crypto_verify_32(k0.bytes, (const unsigned char*)&k1); } } namespace cryptonote { - static inline bool operator==(const crypto::public_key &k0, const rct::key &k1) { return !memcmp(&k0, &k1, 32); } - static inline bool operator!=(const crypto::public_key &k0, const rct::key &k1) { return memcmp(&k0, &k1, 32); } - static inline bool operator==(const crypto::secret_key &k0, const rct::key &k1) { return !memcmp(&k0, &k1, 32); } - static inline bool operator!=(const crypto::secret_key &k0, const rct::key &k1) { return memcmp(&k0, &k1, 32); } + static inline bool operator==(const crypto::public_key &k0, const rct::key &k1) { return !crypto_verify_32((const unsigned char*)&k0, k1.bytes); } + static inline bool operator!=(const crypto::public_key &k0, const rct::key &k1) { return crypto_verify_32((const unsigned char*)&k0, k1.bytes); } + static inline bool operator==(const crypto::secret_key &k0, const rct::key &k1) { return !crypto_verify_32((const unsigned char*)&k0, k1.bytes); } + static inline bool operator!=(const crypto::secret_key &k0, const rct::key &k1) { return crypto_verify_32((const unsigned char*)&k0, k1.bytes); } } namespace rct { diff --git a/src/rpc/daemon_handler.cpp b/src/rpc/daemon_handler.cpp index 55858cc2a..25abe4825 100644 --- a/src/rpc/daemon_handler.cpp +++ b/src/rpc/daemon_handler.cpp @@ -86,53 +86,45 @@ namespace rpc res.error_details = "incorrect number of transactions retrieved for block"; return; } - std::vector<transaction> txs; - for (const auto& p : it->second) - { - txs.resize(txs.size() + 1); - if (!parse_and_validate_tx_from_blob(p.second, txs.back())) - { - res.blocks.clear(); - res.output_indices.clear(); - res.status = Message::STATUS_FAILED; - res.error_details = "failed retrieving a requested transaction"; - return; - } - } cryptonote::rpc::block_output_indices& indices = res.output_indices[block_count]; // miner tx output indices { cryptonote::rpc::tx_output_indices tx_indices; - bool r = m_core.get_tx_outputs_gindexs(get_transaction_hash(bwt.block.miner_tx), tx_indices); - if (!r) + if (!m_core.get_tx_outputs_gindexs(get_transaction_hash(bwt.block.miner_tx), tx_indices)) { res.status = Message::STATUS_FAILED; res.error_details = "core::get_tx_outputs_gindexs() returned false"; return; } - indices.push_back(tx_indices); + indices.push_back(std::move(tx_indices)); } - // assume each block returned is returned with all its transactions - // in the correct order. - auto tx_it = txs.begin(); - for (const crypto::hash& h : bwt.block.tx_hashes) + auto hash_it = bwt.block.tx_hashes.begin(); + bwt.transactions.reserve(it->second.size()); + for (const auto& blob : it->second) { - bwt.transactions.emplace(h, *tx_it); - tx_it++; + bwt.transactions.emplace_back(); + if (!parse_and_validate_tx_from_blob(blob.second, bwt.transactions.back())) + { + res.blocks.clear(); + res.output_indices.clear(); + res.status = Message::STATUS_FAILED; + res.error_details = "failed retrieving a requested transaction"; + return; + } cryptonote::rpc::tx_output_indices tx_indices; - bool r = m_core.get_tx_outputs_gindexs(h, tx_indices); - if (!r) + if (!m_core.get_tx_outputs_gindexs(*hash_it, tx_indices)) { res.status = Message::STATUS_FAILED; res.error_details = "core::get_tx_outputs_gindexs() returned false"; return; } - indices.push_back(tx_indices); + indices.push_back(std::move(tx_indices)); + ++hash_it; } it++; diff --git a/src/rpc/message_data_structs.h b/src/rpc/message_data_structs.h index fc1b2329d..20390aee8 100644 --- a/src/rpc/message_data_structs.h +++ b/src/rpc/message_data_structs.h @@ -44,7 +44,7 @@ namespace rpc struct block_with_transactions { cryptonote::block block; - std::unordered_map<crypto::hash, cryptonote::transaction> transactions; + std::vector<cryptonote::transaction> transactions; }; typedef std::vector<uint64_t> tx_output_indices; diff --git a/src/serialization/json_object.cpp b/src/serialization/json_object.cpp index c2467b863..67fe709dc 100644 --- a/src/serialization/json_object.cpp +++ b/src/serialization/json_object.cpp @@ -28,6 +28,8 @@ #include "json_object.h" +#include <boost/range/adaptor/transformed.hpp> +#include <boost/variant/apply_visitor.hpp> #include <limits> #include <type_traits> #include "string_tools.h" @@ -219,11 +221,11 @@ void toJsonValue(rapidjson::Document& doc, const cryptonote::transaction& tx, ra INSERT_INTO_JSON_OBJECT(val, doc, version, tx.version); INSERT_INTO_JSON_OBJECT(val, doc, unlock_time, tx.unlock_time); - INSERT_INTO_JSON_OBJECT(val, doc, vin, tx.vin); - INSERT_INTO_JSON_OBJECT(val, doc, vout, tx.vout); + INSERT_INTO_JSON_OBJECT(val, doc, inputs, tx.vin); + INSERT_INTO_JSON_OBJECT(val, doc, outputs, tx.vout); INSERT_INTO_JSON_OBJECT(val, doc, extra, tx.extra); INSERT_INTO_JSON_OBJECT(val, doc, signatures, tx.signatures); - INSERT_INTO_JSON_OBJECT(val, doc, rct_signatures, tx.rct_signatures); + INSERT_INTO_JSON_OBJECT(val, doc, ringct, tx.rct_signatures); } @@ -236,11 +238,11 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::transaction& tx) GET_FROM_JSON_OBJECT(val, tx.version, version); GET_FROM_JSON_OBJECT(val, tx.unlock_time, unlock_time); - GET_FROM_JSON_OBJECT(val, tx.vin, vin); - GET_FROM_JSON_OBJECT(val, tx.vout, vout); + GET_FROM_JSON_OBJECT(val, tx.vin, inputs); + GET_FROM_JSON_OBJECT(val, tx.vout, outputs); GET_FROM_JSON_OBJECT(val, tx.extra, extra); GET_FROM_JSON_OBJECT(val, tx.signatures, signatures); - GET_FROM_JSON_OBJECT(val, tx.rct_signatures, rct_signatures); + GET_FROM_JSON_OBJECT(val, tx.rct_signatures, ringct); } void toJsonValue(rapidjson::Document& doc, const cryptonote::block& b, rapidjson::Value& val) @@ -277,26 +279,31 @@ void toJsonValue(rapidjson::Document& doc, const cryptonote::txin_v& txin, rapid { val.SetObject(); - if (txin.type() == typeid(cryptonote::txin_gen)) + struct add_input { - val.AddMember("type", "txin_gen", doc.GetAllocator()); - INSERT_INTO_JSON_OBJECT(val, doc, value, boost::get<cryptonote::txin_gen>(txin)); - } - else if (txin.type() == typeid(cryptonote::txin_to_script)) - { - val.AddMember("type", "txin_to_script", doc.GetAllocator()); - INSERT_INTO_JSON_OBJECT(val, doc, value, boost::get<cryptonote::txin_to_script>(txin)); - } - else if (txin.type() == typeid(cryptonote::txin_to_scripthash)) - { - val.AddMember("type", "txin_to_scripthash", doc.GetAllocator()); - INSERT_INTO_JSON_OBJECT(val, doc, value, boost::get<cryptonote::txin_to_scripthash>(txin)); - } - else if (txin.type() == typeid(cryptonote::txin_to_key)) - { - val.AddMember("type", "txin_to_key", doc.GetAllocator()); - INSERT_INTO_JSON_OBJECT(val, doc, value, boost::get<cryptonote::txin_to_key>(txin)); - } + using result_type = void; + + rapidjson::Document& doc; + rapidjson::Value& val; + + void operator()(cryptonote::txin_to_key const& input) const + { + INSERT_INTO_JSON_OBJECT(val, doc, to_key, input); + } + void operator()(cryptonote::txin_gen const& input) const + { + INSERT_INTO_JSON_OBJECT(val, doc, gen, input); + } + void operator()(cryptonote::txin_to_script const& input) const + { + INSERT_INTO_JSON_OBJECT(val, doc, to_script, input); + } + void operator()(cryptonote::txin_to_scripthash const& input) const + { + INSERT_INTO_JSON_OBJECT(val, doc, to_scripthash, input); + } + }; + boost::apply_visitor(add_input{doc, val}, txin); } @@ -307,31 +314,37 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::txin_v& txin) throw WRONG_TYPE("json object"); } - OBJECT_HAS_MEMBER_OR_THROW(val, "type") - OBJECT_HAS_MEMBER_OR_THROW(val, "value") - if (val["type"]== "txin_gen") - { - cryptonote::txin_gen tmpVal; - fromJsonValue(val["value"], tmpVal); - txin = tmpVal; - } - else if (val["type"]== "txin_to_script") + if (val.MemberCount() != 1) { - cryptonote::txin_to_script tmpVal; - fromJsonValue(val["value"], tmpVal); - txin = tmpVal; + throw MISSING_KEY("Invalid input object"); } - else if (val["type"] == "txin_to_scripthash") - { - cryptonote::txin_to_scripthash tmpVal; - fromJsonValue(val["value"], tmpVal); - txin = tmpVal; - } - else if (val["type"] == "txin_to_key") + + for (auto const& elem : val.GetObject()) { - cryptonote::txin_to_key tmpVal; - fromJsonValue(val["value"], tmpVal); - txin = tmpVal; + if (elem.name == "to_key") + { + cryptonote::txin_to_key tmpVal; + fromJsonValue(elem.value, tmpVal); + txin = std::move(tmpVal); + } + else if (elem.name == "gen") + { + cryptonote::txin_gen tmpVal; + fromJsonValue(elem.value, tmpVal); + txin = std::move(tmpVal); + } + else if (elem.name == "to_script") + { + cryptonote::txin_to_script tmpVal; + fromJsonValue(elem.value, tmpVal); + txin = std::move(tmpVal); + } + else if (elem.name == "to_scripthash") + { + cryptonote::txin_to_scripthash tmpVal; + fromJsonValue(elem.value, tmpVal); + txin = std::move(tmpVal); + } } } @@ -405,7 +418,7 @@ void toJsonValue(rapidjson::Document& doc, const cryptonote::txin_to_key& txin, INSERT_INTO_JSON_OBJECT(val, doc, amount, txin.amount); INSERT_INTO_JSON_OBJECT(val, doc, key_offsets, txin.key_offsets); - INSERT_INTO_JSON_OBJECT(val, doc, k_image, txin.k_image); + INSERT_INTO_JSON_OBJECT(val, doc, key_image, txin.k_image); } @@ -418,58 +431,7 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::txin_to_key& txin) GET_FROM_JSON_OBJECT(val, txin.amount, amount); GET_FROM_JSON_OBJECT(val, txin.key_offsets, key_offsets); - GET_FROM_JSON_OBJECT(val, txin.k_image, k_image); -} - -void toJsonValue(rapidjson::Document& doc, const cryptonote::txout_target_v& txout, rapidjson::Value& val) -{ - val.SetObject(); - - if (txout.type() == typeid(cryptonote::txout_to_script)) - { - val.AddMember("type", "txout_to_script", doc.GetAllocator()); - INSERT_INTO_JSON_OBJECT(val, doc, value, boost::get<cryptonote::txout_to_script>(txout)); - } - else if (txout.type() == typeid(cryptonote::txout_to_scripthash)) - { - val.AddMember("type", "txout_to_scripthash", doc.GetAllocator()); - INSERT_INTO_JSON_OBJECT(val, doc, value, boost::get<cryptonote::txout_to_scripthash>(txout)); - } - else if (txout.type() == typeid(cryptonote::txout_to_key)) - { - val.AddMember("type", "txout_to_key", doc.GetAllocator()); - INSERT_INTO_JSON_OBJECT(val, doc, value, boost::get<cryptonote::txout_to_key>(txout)); - } -} - - -void fromJsonValue(const rapidjson::Value& val, cryptonote::txout_target_v& txout) -{ - if (!val.IsObject()) - { - throw WRONG_TYPE("json object"); - } - - OBJECT_HAS_MEMBER_OR_THROW(val, "type") - OBJECT_HAS_MEMBER_OR_THROW(val, "value") - if (val["type"]== "txout_to_script") - { - cryptonote::txout_to_script tmpVal; - fromJsonValue(val["value"], tmpVal); - txout = tmpVal; - } - else if (val["type"] == "txout_to_scripthash") - { - cryptonote::txout_to_scripthash tmpVal; - fromJsonValue(val["value"], tmpVal); - txout = tmpVal; - } - else if (val["type"] == "txout_to_key") - { - cryptonote::txout_to_key tmpVal; - fromJsonValue(val["value"], tmpVal); - txout = tmpVal; - } + GET_FROM_JSON_OBJECT(val, txin.k_image, key_image); } void toJsonValue(rapidjson::Document& doc, const cryptonote::txout_to_script& txout, rapidjson::Value& val) @@ -533,7 +495,28 @@ void toJsonValue(rapidjson::Document& doc, const cryptonote::tx_out& txout, rapi val.SetObject(); INSERT_INTO_JSON_OBJECT(val, doc, amount, txout.amount); - INSERT_INTO_JSON_OBJECT(val, doc, target, txout.target); + + struct add_output + { + using result_type = void; + + rapidjson::Document& doc; + rapidjson::Value& val; + + void operator()(cryptonote::txout_to_key const& output) const + { + INSERT_INTO_JSON_OBJECT(val, doc, to_key, output); + } + void operator()(cryptonote::txout_to_script const& output) const + { + INSERT_INTO_JSON_OBJECT(val, doc, to_script, output); + } + void operator()(cryptonote::txout_to_scripthash const& output) const + { + INSERT_INTO_JSON_OBJECT(val, doc, to_scripthash, output); + } + }; + boost::apply_visitor(add_output{doc, val}, txout.target); } void fromJsonValue(const rapidjson::Value& val, cryptonote::tx_out& txout) @@ -543,8 +526,37 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::tx_out& txout) throw WRONG_TYPE("json object"); } - GET_FROM_JSON_OBJECT(val, txout.amount, amount); - GET_FROM_JSON_OBJECT(val, txout.target, target); + if (val.MemberCount() != 2) + { + throw MISSING_KEY("Invalid input object"); + } + + for (auto const& elem : val.GetObject()) + { + if (elem.name == "amount") + { + fromJsonValue(elem.value, txout.amount); + } + + if (elem.name == "to_key") + { + cryptonote::txout_to_key tmpVal; + fromJsonValue(elem.value, tmpVal); + txout.target = std::move(tmpVal); + } + else if (elem.name == "to_script") + { + cryptonote::txout_to_script tmpVal; + fromJsonValue(elem.value, tmpVal); + txout.target = std::move(tmpVal); + } + else if (elem.name == "to_scripthash") + { + cryptonote::txout_to_scripthash tmpVal; + fromJsonValue(elem.value, tmpVal); + txout.target = std::move(tmpVal); + } + } } void toJsonValue(rapidjson::Document& doc, const cryptonote::connection_info& info, rapidjson::Value& val) @@ -617,7 +629,7 @@ void toJsonValue(rapidjson::Document& doc, const cryptonote::block_complete_entr val.SetObject(); INSERT_INTO_JSON_OBJECT(val, doc, block, blk.block); - INSERT_INTO_JSON_OBJECT(val, doc, txs, blk.txs); + INSERT_INTO_JSON_OBJECT(val, doc, transactions, blk.txs); } @@ -629,7 +641,7 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::block_complete_entry } GET_FROM_JSON_OBJECT(val, blk.block, block); - GET_FROM_JSON_OBJECT(val, blk.txs, txs); + GET_FROM_JSON_OBJECT(val, blk.txs, transactions); } void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::block_with_transactions& blk, rapidjson::Value& val) @@ -936,51 +948,70 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::BlockHeaderResp void toJsonValue(rapidjson::Document& doc, const rct::rctSig& sig, rapidjson::Value& val) { + using boost::adaptors::transform; + val.SetObject(); + const auto just_mask = [] (rct::ctkey const& key) -> rct::key const& + { + return key.mask; + }; + INSERT_INTO_JSON_OBJECT(val, doc, type, sig.type); - INSERT_INTO_JSON_OBJECT(val, doc, message, sig.message); - INSERT_INTO_JSON_OBJECT(val, doc, mixRing, sig.mixRing); - INSERT_INTO_JSON_OBJECT(val, doc, pseudoOuts, sig.pseudoOuts); - INSERT_INTO_JSON_OBJECT(val, doc, ecdhInfo, sig.ecdhInfo); - INSERT_INTO_JSON_OBJECT(val, doc, outPk, sig.outPk); - INSERT_INTO_JSON_OBJECT(val, doc, txnFee, sig.txnFee); - INSERT_INTO_JSON_OBJECT(val, doc, p, sig.p); + INSERT_INTO_JSON_OBJECT(val, doc, encrypted, sig.ecdhInfo); + INSERT_INTO_JSON_OBJECT(val, doc, commitments, transform(sig.outPk, just_mask)); + INSERT_INTO_JSON_OBJECT(val, doc, fee, sig.txnFee); + + // prunable + { + rapidjson::Value prunable; + prunable.SetObject(); + + INSERT_INTO_JSON_OBJECT(prunable, doc, range_proofs, sig.p.rangeSigs); + INSERT_INTO_JSON_OBJECT(prunable, doc, bulletproofs, sig.p.bulletproofs); + INSERT_INTO_JSON_OBJECT(prunable, doc, mlsags, sig.p.MGs); + INSERT_INTO_JSON_OBJECT(prunable, doc, pseudo_outs, sig.get_pseudo_outs()); + + val.AddMember("prunable", prunable, doc.GetAllocator()); + } } void fromJsonValue(const rapidjson::Value& val, rct::rctSig& sig) { + using boost::adaptors::transform; + if (!val.IsObject()) { throw WRONG_TYPE("json object"); } + std::vector<rct::key> commitments; + GET_FROM_JSON_OBJECT(val, sig.type, type); - GET_FROM_JSON_OBJECT(val, sig.message, message); - GET_FROM_JSON_OBJECT(val, sig.mixRing, mixRing); - GET_FROM_JSON_OBJECT(val, sig.pseudoOuts, pseudoOuts); - GET_FROM_JSON_OBJECT(val, sig.ecdhInfo, ecdhInfo); - GET_FROM_JSON_OBJECT(val, sig.outPk, outPk); - GET_FROM_JSON_OBJECT(val, sig.txnFee, txnFee); - GET_FROM_JSON_OBJECT(val, sig.p, p); -} + GET_FROM_JSON_OBJECT(val, sig.ecdhInfo, encrypted); + GET_FROM_JSON_OBJECT(val, commitments, commitments); + GET_FROM_JSON_OBJECT(val, sig.txnFee, fee); -void toJsonValue(rapidjson::Document& doc, const rct::ctkey& key, rapidjson::Value& val) -{ - val.SetObject(); + // prunable + { + OBJECT_HAS_MEMBER_OR_THROW(val, "prunable"); + const auto& prunable = val["prunable"]; - INSERT_INTO_JSON_OBJECT(val, doc, dest, key.dest); - INSERT_INTO_JSON_OBJECT(val, doc, mask, key.mask); -} + rct::keyV pseudo_outs; -void fromJsonValue(const rapidjson::Value& val, rct::ctkey& key) -{ - if (!val.IsObject()) + GET_FROM_JSON_OBJECT(prunable, sig.p.rangeSigs, range_proofs); + GET_FROM_JSON_OBJECT(prunable, sig.p.bulletproofs, bulletproofs); + GET_FROM_JSON_OBJECT(prunable, sig.p.MGs, mlsags); + GET_FROM_JSON_OBJECT(prunable, pseudo_outs, pseudo_outs); + + sig.get_pseudo_outs() = std::move(pseudo_outs); + } + + sig.outPk.reserve(commitments.size()); + for (rct::key const& commitment : commitments) { - throw WRONG_TYPE("json object"); + sig.outPk.push_back({{}, commitment}); } - GET_FROM_JSON_OBJECT(val, key.dest, dest); - GET_FROM_JSON_OBJECT(val, key.mask, mask); } void toJsonValue(rapidjson::Document& doc, const rct::ecdhTuple& tuple, rapidjson::Value& val) @@ -1002,27 +1033,6 @@ void fromJsonValue(const rapidjson::Value& val, rct::ecdhTuple& tuple) GET_FROM_JSON_OBJECT(val, tuple.amount, amount); } -void toJsonValue(rapidjson::Document& doc, const rct::rctSigPrunable& sig, rapidjson::Value& val) -{ - val.SetObject(); - - INSERT_INTO_JSON_OBJECT(val, doc, rangeSigs, sig.rangeSigs); - INSERT_INTO_JSON_OBJECT(val, doc, bulletproofs, sig.bulletproofs); - INSERT_INTO_JSON_OBJECT(val, doc, MGs, sig.MGs); -} - -void fromJsonValue(const rapidjson::Value& val, rct::rctSigPrunable& sig) -{ - if (!val.IsObject()) - { - throw WRONG_TYPE("json object"); - } - - GET_FROM_JSON_OBJECT(val, sig.rangeSigs, rangeSigs); - GET_FROM_JSON_OBJECT(val, sig.bulletproofs, bulletproofs); - GET_FROM_JSON_OBJECT(val, sig.MGs, MGs); -} - void toJsonValue(rapidjson::Document& doc, const rct::rangeSig& sig, rapidjson::Value& val) { val.SetObject(); @@ -1040,10 +1050,16 @@ void fromJsonValue(const rapidjson::Value& val, rct::rangeSig& sig) throw WRONG_TYPE("json object"); } + const auto ci = val.FindMember("Ci"); + if (ci == val.MemberEnd()) + { + throw MISSING_KEY("Ci"); + } + GET_FROM_JSON_OBJECT(val, sig.asig, asig); std::vector<rct::key> keyVector; - cryptonote::json::fromJsonValue(val["Ci"], keyVector); + cryptonote::json::fromJsonValue(ci->value, keyVector); if (!(keyVector.size() == 64)) { throw WRONG_TYPE("key64 (rct::key[64])"); @@ -1098,10 +1114,10 @@ void toJsonValue(rapidjson::Document& doc, const rct::boroSig& sig, rapidjson::V val.SetObject(); std::vector<rct::key> keyVector(sig.s0, std::end(sig.s0)); - INSERT_INTO_JSON_OBJECT(val, doc, s0, sig.s0); + INSERT_INTO_JSON_OBJECT(val, doc, s0, keyVector); keyVector.assign(sig.s1, std::end(sig.s1)); - INSERT_INTO_JSON_OBJECT(val, doc, s1, sig.s1); + INSERT_INTO_JSON_OBJECT(val, doc, s1, keyVector); INSERT_INTO_JSON_OBJECT(val, doc, ee, sig.ee); } diff --git a/src/serialization/json_object.h b/src/serialization/json_object.h index de21ace66..da3351fe3 100644 --- a/src/serialization/json_object.h +++ b/src/serialization/json_object.h @@ -263,15 +263,9 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::BlockHeaderResp void toJsonValue(rapidjson::Document& doc, const rct::rctSig& i, rapidjson::Value& val); void fromJsonValue(const rapidjson::Value& i, rct::rctSig& sig); -void toJsonValue(rapidjson::Document& doc, const rct::ctkey& key, rapidjson::Value& val); -void fromJsonValue(const rapidjson::Value& val, rct::ctkey& key); - void toJsonValue(rapidjson::Document& doc, const rct::ecdhTuple& tuple, rapidjson::Value& val); void fromJsonValue(const rapidjson::Value& val, rct::ecdhTuple& tuple); -void toJsonValue(rapidjson::Document& doc, const rct::rctSigPrunable& sig, rapidjson::Value& val); -void fromJsonValue(const rapidjson::Value& val, rct::rctSigPrunable& sig); - void toJsonValue(rapidjson::Document& doc, const rct::rangeSig& sig, rapidjson::Value& val); void fromJsonValue(const rapidjson::Value& val, rct::rangeSig& sig); diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index e07c7e49b..f5aeabded 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -95,17 +95,18 @@ typedef cryptonote::simple_wallet sw; m_auto_refresh_enabled.store(false, std::memory_order_relaxed); \ /* stop any background refresh, and take over */ \ m_wallet->stop(); \ - m_idle_mutex.lock(); \ - while (m_auto_refresh_refreshing) \ - m_idle_cond.notify_one(); \ - m_idle_mutex.unlock(); \ -/* if (auto_refresh_run)*/ \ - /*m_auto_refresh_thread.join();*/ \ boost::unique_lock<boost::mutex> lock(m_idle_mutex); \ + m_idle_cond.notify_all(); \ epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler([&](){ \ m_auto_refresh_enabled.store(auto_refresh_enabled, std::memory_order_relaxed); \ }) +#define SCOPED_WALLET_UNLOCK() \ + LOCK_IDLE_SCOPE(); \ + boost::optional<tools::password_container> pwd_container = boost::none; \ + if (m_wallet->ask_password() && !m_wallet->watch_only() && !(pwd_container = get_and_verify_password())) { return true; } \ + tools::wallet_keys_unlocker unlocker(*m_wallet, pwd_container); + enum TransferType { TransferOriginal, TransferNew, @@ -128,8 +129,6 @@ namespace const command_line::arg_descriptor<bool> arg_restore_deterministic_wallet = {"restore-deterministic-wallet", sw::tr("Recover wallet using Electrum-style mnemonic seed"), false}; const command_line::arg_descriptor<bool> arg_restore_multisig_wallet = {"restore-multisig-wallet", sw::tr("Recover multisig wallet using Electrum-style mnemonic seed"), false}; const command_line::arg_descriptor<bool> arg_non_deterministic = {"non-deterministic", sw::tr("Generate non-deterministic view and spend keys"), false}; - const command_line::arg_descriptor<bool> arg_trusted_daemon = {"trusted-daemon", sw::tr("Enable commands which rely on a trusted daemon"), false}; - const command_line::arg_descriptor<bool> arg_untrusted_daemon = {"untrusted-daemon", sw::tr("Disable commands which rely on a trusted daemon"), false}; const command_line::arg_descriptor<bool> arg_allow_mismatched_daemon_version = {"allow-mismatched-daemon-version", sw::tr("Allow communicating with a daemon that uses a different RPC version"), false}; const command_line::arg_descriptor<uint64_t> arg_restore_height = {"restore-height", sw::tr("Restore from specific blockchain height"), 0}; const command_line::arg_descriptor<bool> arg_do_not_relay = {"do-not-relay", sw::tr("The newly created transaction will not be relayed to the monero network"), false}; @@ -152,12 +151,31 @@ namespace // // Note also that input for passwords is NOT translated, to remain compatible with any // passwords containing special characters that predate this switch to UTF-8 support. - static std::string cp850_to_utf8(const std::string &cp850_str) + template<typename T> + static T cp850_to_utf8(const T &cp850_str) { boost::locale::generator gen; gen.locale_cache_enabled(true); std::locale loc = gen("en_US.CP850"); - return boost::locale::conv::to_utf<char>(cp850_str, loc); + const boost::locale::conv::method_type how = boost::locale::conv::default_method; + T result; + const char *begin = cp850_str.data(); + const char *end = begin + cp850_str.size(); + result.reserve(end-begin); + typedef std::back_insert_iterator<T> inserter_type; + inserter_type inserter(result); + boost::locale::utf::code_point c; + while(begin!=end) { + c=boost::locale::utf::utf_traits<char>::template decode<char const *>(begin,end); + if(c==boost::locale::utf::illegal || c==boost::locale::utf::incomplete) { + if(how==boost::locale::conv::stop) + throw boost::locale::conv::conversion_error(); + } + else { + boost::locale::utf::utf_traits<char>::template encode<inserter_type>(c,inserter); + } + } + return result; } #endif @@ -177,6 +195,28 @@ namespace return epee::string_tools::trim(buf); } + epee::wipeable_string input_secure_line(const std::string& prompt) + { +#ifdef HAVE_READLINE + rdln::suspend_readline pause_readline; +#endif + auto pwd_container = tools::password_container::prompt(false, prompt.c_str(), false); + if (!pwd_container) + { + MERROR("Failed to read secure line"); + return ""; + } + + epee::wipeable_string buf = pwd_container->password(); + +#ifdef WIN32 + buf = cp850_to_utf8(buf); +#endif + + buf.trim(); + return buf; + } + boost::optional<tools::password_container> password_prompter(const char *prompt, bool verify) { #ifdef HAVE_READLINE @@ -512,6 +552,18 @@ namespace } return true; } + + void print_secret_key(const crypto::secret_key &k) + { + static constexpr const char hex[] = u8"0123456789abcdef"; + const uint8_t *ptr = (const uint8_t*)k.data; + for (size_t i = 0, sz = sizeof(k); i < sz; ++i) + { + putchar(hex[*ptr >> 4]); + putchar(hex[*ptr & 15]); + ++ptr; + } + } } bool parse_priority(const std::string& arg, uint32_t& priority) @@ -527,6 +579,18 @@ bool parse_priority(const std::string& arg, uint32_t& priority) return false; } +std::string join_priority_strings(const char *delimiter) +{ + std::string s; + for (size_t n = 0; n < allowed_priority_strings.size(); ++n) + { + if (!s.empty()) + s += delimiter; + s += allowed_priority_strings[n]; + } + return s; +} + std::string simple_wallet::get_commands_str() { std::stringstream ss; @@ -561,12 +625,15 @@ std::string simple_wallet::get_command_usage(const std::vector<std::string> &arg bool simple_wallet::viewkey(const std::vector<std::string> &args/* = std::vector<std::string>()*/) { - if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } + SCOPED_WALLET_UNLOCK(); // don't log + PAUSE_READLINE(); if (m_wallet->key_on_device()) { std::cout << "secret: On device. Not available" << std::endl; } else { - std::cout << "secret: " << string_tools::pod_to_hex(m_wallet->get_account().get_keys().m_view_secret_key) << std::endl; + printf("secret: "); + print_secret_key(m_wallet->get_account().get_keys().m_view_secret_key); + putchar('\n'); } std::cout << "public: " << string_tools::pod_to_hex(m_wallet->get_account().get_keys().m_account_address.m_view_public_key) << std::endl; @@ -580,12 +647,15 @@ bool simple_wallet::spendkey(const std::vector<std::string> &args/* = std::vecto fail_msg_writer() << tr("wallet is watch-only and has no spend key"); return true; } - if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } + SCOPED_WALLET_UNLOCK(); // don't log + PAUSE_READLINE(); if (m_wallet->key_on_device()) { std::cout << "secret: On device. Not available" << std::endl; } else { - std::cout << "secret: " << string_tools::pod_to_hex(m_wallet->get_account().get_keys().m_spend_secret_key) << std::endl; + printf("secret: "); + print_secret_key(m_wallet->get_account().get_keys().m_spend_secret_key); + putchar('\n'); } std::cout << "public: " << string_tools::pod_to_hex(m_wallet->get_account().get_keys().m_account_address.m_spend_public_key) << std::endl; @@ -595,7 +665,7 @@ bool simple_wallet::spendkey(const std::vector<std::string> &args/* = std::vecto bool simple_wallet::print_seed(bool encrypted) { bool success = false; - std::string seed; + epee::wipeable_string seed; bool ready, multisig; if (m_wallet->key_on_device()) @@ -608,7 +678,8 @@ bool simple_wallet::print_seed(bool encrypted) fail_msg_writer() << tr("wallet is watch-only and has no seed"); return true; } - if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } + + SCOPED_WALLET_UNLOCK(); multisig = m_wallet->multisig(&ready); if (multisig) @@ -677,22 +748,36 @@ bool simple_wallet::seed_set_language(const std::vector<std::string> &args/* = s fail_msg_writer() << tr("wallet is watch-only and has no seed"); return true; } - if (!m_wallet->is_deterministic()) - { - fail_msg_writer() << tr("wallet is non-deterministic and has no seed"); - return true; - } - - const auto pwd_container = get_and_verify_password(); - if (pwd_container) + + epee::wipeable_string password; { - std::string mnemonic_language = get_mnemonic_language(); - if (mnemonic_language.empty()) + SCOPED_WALLET_UNLOCK(); + + if (!m_wallet->is_deterministic()) + { + fail_msg_writer() << tr("wallet is non-deterministic and has no seed"); return true; + } - m_wallet->set_seed_language(std::move(mnemonic_language)); - m_wallet->rewrite(m_wallet_file, pwd_container->password()); + // we need the password, even if ask-password is unset + if (!pwd_container) + { + pwd_container = get_and_verify_password(); + if (pwd_container == boost::none) + { + fail_msg_writer() << tr("Incorrect password"); + return true; + } + } + password = pwd_container->password(); } + + std::string mnemonic_language = get_mnemonic_language(); + if (mnemonic_language.empty()) + return true; + + m_wallet->set_seed_language(std::move(mnemonic_language)); + m_wallet->rewrite(m_wallet_file, password); return true; } @@ -713,8 +798,7 @@ bool simple_wallet::change_password(const std::vector<std::string> &args) try { - m_wallet->rewrite(m_wallet_file, pwd_container->password()); - m_wallet->store(); + m_wallet->change_password(m_wallet_file, orig_pwd_container->password(), pwd_container->password()); } catch (const tools::error::wallet_logic_error& e) { @@ -817,12 +901,7 @@ bool simple_wallet::prepare_multisig(const std::vector<std::string> &args) return true; } - const auto orig_pwd_container = get_and_verify_password(); - if(orig_pwd_container == boost::none) - { - fail_msg_writer() << tr("Your password is incorrect."); - return true; - } + SCOPED_WALLET_UNLOCK(); std::string multisig_info = m_wallet->get_multisig_info(); success_msg_writer() << multisig_info; @@ -855,13 +934,6 @@ bool simple_wallet::make_multisig(const std::vector<std::string> &args) return true; } - const auto orig_pwd_container = get_and_verify_password(); - if(orig_pwd_container == boost::none) - { - fail_msg_writer() << tr("Your original password was incorrect."); - return true; - } - if (args.size() < 2) { fail_msg_writer() << tr("usage: make_multisig <threshold> <multisiginfo1> [<multisiginfo2>...]"); @@ -876,6 +948,13 @@ bool simple_wallet::make_multisig(const std::vector<std::string> &args) return true; } + const auto orig_pwd_container = get_and_verify_password(); + if(orig_pwd_container == boost::none) + { + fail_msg_writer() << tr("Your original password was incorrect."); + return true; + } + LOCK_IDLE_SCOPE(); try @@ -917,6 +996,14 @@ bool simple_wallet::finalize_multisig(const std::vector<std::string> &args) fail_msg_writer() << tr("command not supported by HW wallet"); return true; } + + const auto pwd_container = get_and_verify_password(); + if(pwd_container == boost::none) + { + fail_msg_writer() << tr("Your original password was incorrect."); + return true; + } + if (!m_wallet->multisig(&ready)) { fail_msg_writer() << tr("This wallet is not multisig"); @@ -928,12 +1015,7 @@ bool simple_wallet::finalize_multisig(const std::vector<std::string> &args) return true; } - const auto orig_pwd_container = get_and_verify_password(); - if(orig_pwd_container == boost::none) - { - fail_msg_writer() << tr("Your original password was incorrect."); - return true; - } + LOCK_IDLE_SCOPE(); if (args.size() < 2) { @@ -943,7 +1025,7 @@ bool simple_wallet::finalize_multisig(const std::vector<std::string> &args) try { - if (!m_wallet->finalize_multisig(orig_pwd_container->password(), args)) + if (!m_wallet->finalize_multisig(pwd_container->password(), args)) { fail_msg_writer() << tr("Failed to finalize multisig"); return true; @@ -981,8 +1063,8 @@ bool simple_wallet::export_multisig(const std::vector<std::string> &args) fail_msg_writer() << tr("usage: export_multisig_info <filename>"); return true; } - if (m_wallet->ask_password() && !get_and_verify_password()) - return true; + + SCOPED_WALLET_UNLOCK(); const std::string filename = args[0]; if (m_wallet->confirm_export_overwrite() && !check_file_overwrite(filename)) @@ -1033,8 +1115,8 @@ bool simple_wallet::import_multisig(const std::vector<std::string> &args) fail_msg_writer() << tr("usage: import_multisig_info <filename1> [<filename2>...] - one for each other participant"); return true; } - if (m_wallet->ask_password() && !get_and_verify_password()) - return true; + + SCOPED_WALLET_UNLOCK(); std::vector<cryptonote::blobdata> info; for (size_t n = 0; n < args.size(); ++n) @@ -1050,11 +1132,11 @@ bool simple_wallet::import_multisig(const std::vector<std::string> &args) info.push_back(std::move(data)); } - LOCK_IDLE_SCOPE(); - // all read and parsed, actually import try { + m_in_manual_refresh.store(true, std::memory_order_relaxed); + epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler([&](){m_in_manual_refresh.store(false, std::memory_order_relaxed);}); size_t n_outputs = m_wallet->import_multisig(info); // Clear line "Height xxx of xxx" std::cout << "\r \r"; @@ -1065,7 +1147,7 @@ bool simple_wallet::import_multisig(const std::vector<std::string> &args) fail_msg_writer() << tr("Failed to import multisig info: ") << e.what(); return true; } - if (is_daemon_trusted()) + if (m_wallet->is_trusted_daemon()) { try { @@ -1112,7 +1194,8 @@ bool simple_wallet::sign_multisig(const std::vector<std::string> &args) fail_msg_writer() << tr("usage: sign_multisig <filename>"); return true; } - if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } + + SCOPED_WALLET_UNLOCK(); std::string filename = args[0]; std::vector<crypto::hash> txids; @@ -1185,7 +1268,8 @@ bool simple_wallet::submit_multisig(const std::vector<std::string> &args) fail_msg_writer() << tr("usage: submit_multisig <filename>"); return true; } - if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } + + SCOPED_WALLET_UNLOCK(); if (!try_connect_to_daemon()) return true; @@ -1217,7 +1301,7 @@ bool simple_wallet::submit_multisig(const std::vector<std::string> &args) } catch (const std::exception &e) { - handle_transfer_exception(std::current_exception(), is_daemon_trusted()); + handle_transfer_exception(std::current_exception(), m_wallet->is_trusted_daemon()); } catch (...) { @@ -1252,7 +1336,8 @@ bool simple_wallet::export_raw_multisig(const std::vector<std::string> &args) fail_msg_writer() << tr("usage: export_raw_multisig <filename>"); return true; } - if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } + + SCOPED_WALLET_UNLOCK(); std::string filename = args[0]; if (m_wallet->confirm_export_overwrite() && !check_file_overwrite(filename)) @@ -1777,12 +1862,12 @@ bool simple_wallet::set_default_ring_size(const std::vector<std::string> &args/* bool simple_wallet::set_default_priority(const std::vector<std::string> &args/* = std::vector<std::string>()*/) { - int priority = 0; + uint32_t priority = 0; try { if (strchr(args[1].c_str(), '-')) { - fail_msg_writer() << tr("priority must be 0, 1, 2, 3, or 4 "); + fail_msg_writer() << tr("priority must be either 0, 1, 2, 3, or 4, or one of: ") << join_priority_strings(", "); return true; } if (args[1] == "0") @@ -1791,11 +1876,23 @@ bool simple_wallet::set_default_priority(const std::vector<std::string> &args/* } else { - priority = boost::lexical_cast<int>(args[1]); - if (priority < 1 || priority > 4) + bool found = false; + for (size_t n = 0; n < allowed_priority_strings.size(); ++n) { - fail_msg_writer() << tr("priority must be 0, 1, 2, 3, or 4"); - return true; + if (allowed_priority_strings[n] == args[1]) + { + found = true; + priority = n; + } + } + if (!found) + { + priority = boost::lexical_cast<int>(args[1]); + if (priority < 1 || priority > 4) + { + fail_msg_writer() << tr("priority must be either 0, 1, 2, 3, or 4, or one of: ") << join_priority_strings(", "); + return true; + } } } @@ -1809,7 +1906,7 @@ bool simple_wallet::set_default_priority(const std::vector<std::string> &args/* } catch(const boost::bad_lexical_cast &) { - fail_msg_writer() << tr("priority must be 0, 1, 2, 3, or 4"); + fail_msg_writer() << tr("priority must be either 0, 1, 2, 3, or 4, or one of: ") << join_priority_strings(", "); return true; } catch(...) @@ -1874,6 +1971,14 @@ bool simple_wallet::set_ask_password(const std::vector<std::string> &args/* = st if (pwd_container) { parse_bool_and_use(args[1], [&](bool r) { + const bool cur_r = m_wallet->ask_password(); + if (!m_wallet->watch_only()) + { + if (cur_r && !r) + m_wallet->decrypt_keys(pwd_container->password()); + else if (!cur_r && r) + m_wallet->encrypt_keys(pwd_container->password()); + } m_wallet->ask_password(r); m_wallet->rewrite(m_wallet_file, pwd_container->password()); }); @@ -2347,7 +2452,7 @@ simple_wallet::simple_wallet() tr("Show the unspent outputs of a specified address within an optional amount range.")); m_cmd_binder.set_handler("rescan_bc", boost::bind(&simple_wallet::rescan_blockchain, this, _1), - tr("Rescan the blockchain from scratch.")); + tr("Rescan the blockchain from scratch, losing any information which can not be recovered from the blockchain itself.")); m_cmd_binder.set_handler("set_tx_note", boost::bind(&simple_wallet::set_tx_note, this, _1), tr("set_tx_note <txid> [free text note]"), @@ -2476,6 +2581,10 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args) std::string seed_language = m_wallet->get_seed_language(); if (m_use_english_language_names) seed_language = crypto::ElectrumWords::get_english_name_for(seed_language); + std::string priority_string = "invalid"; + uint32_t priority = m_wallet->get_default_priority(); + if (priority < allowed_priority_strings.size()) + priority_string = allowed_priority_strings[priority]; success_msg_writer() << "seed = " << seed_language; success_msg_writer() << "always-confirm-transfers = " << m_wallet->always_confirm_transfers(); success_msg_writer() << "print-ring-members = " << m_wallet->print_ring_members(); @@ -2483,7 +2592,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args) success_msg_writer() << "default-ring-size = " << (m_wallet->default_mixin() ? m_wallet->default_mixin() + 1 : 0); success_msg_writer() << "auto-refresh = " << m_wallet->auto_refresh(); success_msg_writer() << "refresh-type = " << get_refresh_type_name(m_wallet->get_refresh_type()); - success_msg_writer() << "priority = " << m_wallet->get_default_priority(); + success_msg_writer() << "priority = " << priority<< " (" << priority_string << ")"; success_msg_writer() << "confirm-missing-payment-id = " << m_wallet->confirm_missing_payment_id(); success_msg_writer() << "ask-password = " << m_wallet->ask_password(); success_msg_writer() << "unit = " << cryptonote::get_unit(cryptonote::get_default_decimal_point()); @@ -2539,7 +2648,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args) CHECK_SIMPLE_VARIABLE("default-ring-size", set_default_ring_size, tr("integer >= ") << MIN_RING_SIZE); CHECK_SIMPLE_VARIABLE("auto-refresh", set_auto_refresh, tr("0 or 1")); CHECK_SIMPLE_VARIABLE("refresh-type", set_refresh_type, tr("full (slowest, no assumptions); optimize-coinbase (fast, assumes the whole coinbase is paid to a single address); no-coinbase (fastest, assumes we receive no coinbase transaction), default (same as optimize-coinbase)")); - CHECK_SIMPLE_VARIABLE("priority", set_default_priority, tr("0, 1, 2, 3, or 4")); + CHECK_SIMPLE_VARIABLE("priority", set_default_priority, tr("0, 1, 2, 3, or 4, or one of ") << join_priority_strings(", ")); CHECK_SIMPLE_VARIABLE("confirm-missing-payment-id", set_confirm_missing_payment_id, tr("0 or 1")); CHECK_SIMPLE_VARIABLE("ask-password", set_ask_password, tr("0 or 1")); CHECK_SIMPLE_VARIABLE("unit", set_unit, tr("monero, millinero, micronero, nanonero, piconero")); @@ -2679,28 +2788,45 @@ bool simple_wallet::ask_wallet_create_if_needed() * \brief Prints the seed with a nice message * \param seed seed to print */ -void simple_wallet::print_seed(std::string seed) +void simple_wallet::print_seed(const epee::wipeable_string &seed) { success_msg_writer(true) << "\n" << tr("NOTE: the following 25 words can be used to recover access to your wallet. " "Write them down and store them somewhere safe and secure. Please do not store them in " "your email or on file storage services outside of your immediate control.\n"); - boost::replace_nth(seed, " ", 15, "\n"); - boost::replace_nth(seed, " ", 7, "\n"); // don't log - std::cout << seed << std::endl; + int space_index = 0; + size_t len = seed.size(); + for (const char *ptr = seed.data(); len--; ++ptr) + { + if (*ptr == ' ') + { + if (space_index == 15 || space_index == 7) + putchar('\n'); + else + putchar(*ptr); + ++space_index; + } + else + putchar(*ptr); + } + putchar('\n'); + fflush(stdout); } //---------------------------------------------------------------------------------------------------- -static bool might_be_partial_seed(std::string words) +static bool might_be_partial_seed(const epee::wipeable_string &words) { - std::vector<std::string> seed; + std::vector<epee::wipeable_string> seed; - boost::algorithm::trim(words); - boost::split(seed, words, boost::is_any_of(" "), boost::token_compress_on); + words.split(seed); return seed.size() < 24; } //---------------------------------------------------------------------------------------------------- bool simple_wallet::init(const boost::program_options::variables_map& vm) { + epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler([&](){ + m_electrum_seed.wipe(); + }); + const bool testnet = tools::wallet2::has_testnet_option(vm); const bool stagenet = tools::wallet2::has_stagenet_option(vm); if (testnet && stagenet) @@ -2710,7 +2836,7 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) } const network_type nettype = testnet ? TESTNET : stagenet ? STAGENET : MAINNET; - std::string multisig_keys; + epee::wipeable_string multisig_keys; if (!handle_command_line(vm)) return false; @@ -2752,8 +2878,8 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) { if (m_restore_multisig_wallet) { - const char *prompt = "Specify multisig seed: "; - m_electrum_seed = input_line(prompt); + const char *prompt = "Specify multisig seed"; + m_electrum_seed = input_secure_line(prompt); if (std::cin.eof()) return false; if (m_electrum_seed.empty()) @@ -2767,8 +2893,8 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) m_electrum_seed = ""; do { - const char *prompt = m_electrum_seed.empty() ? "Specify Electrum seed: " : "Electrum seed continued: "; - std::string electrum_seed = input_line(prompt); + const char *prompt = m_electrum_seed.empty() ? "Specify Electrum seed" : "Electrum seed continued"; + epee::wipeable_string electrum_seed = input_secure_line(prompt); if (std::cin.eof()) return false; if (electrum_seed.empty()) @@ -2776,18 +2902,21 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) fail_msg_writer() << tr("specify a recovery parameter with the --electrum-seed=\"words list here\""); return false; } - m_electrum_seed += electrum_seed + " "; + m_electrum_seed += electrum_seed; + m_electrum_seed += ' '; } while (might_be_partial_seed(m_electrum_seed)); } } if (m_restore_multisig_wallet) { - if (!epee::string_tools::parse_hexstr_to_binbuff(m_electrum_seed, multisig_keys)) + const boost::optional<epee::wipeable_string> parsed = m_electrum_seed.parse_hexstr(); + if (!parsed) { fail_msg_writer() << tr("Multisig seed failed verification"); return false; } + multisig_keys = *parsed; } else { @@ -2809,7 +2938,7 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) crypto::secret_key key; crypto::cn_slow_hash(seed_pass.data(), seed_pass.size(), (crypto::hash&)key); sc_reduce32((unsigned char*)key.data); - multisig_keys = m_wallet->decrypt(multisig_keys, key, true); + multisig_keys = m_wallet->decrypt<epee::wipeable_string>(std::string(multisig_keys.data(), multisig_keys.size()), key, true); } else m_recovery_key = cryptonote::decrypt_key(m_recovery_key, seed_pass); @@ -3112,7 +3241,7 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) m_wallet_file = m_generate_from_json; try { - m_wallet = tools::wallet2::make_from_json(vm, m_wallet_file, password_prompter); + m_wallet = tools::wallet2::make_from_json(vm, false, m_wallet_file, password_prompter); } catch (const std::exception &e) { @@ -3260,22 +3389,7 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) return false; } - // set --trusted-daemon if local and not overridden - if (!m_trusted_daemon) - { - try - { - m_trusted_daemon = false; - if (tools::is_local_address(m_wallet->get_daemon_address())) - { - MINFO(tr("Daemon is local, assuming trusted")); - m_trusted_daemon = true; - } - } - catch (const std::exception &e) { } - } - - if (!is_daemon_trusted()) + if (!m_wallet->is_trusted_daemon()) message_writer() << (boost::format(tr("Warning: using an untrusted daemon at %s, privacy will be lessened")) % m_wallet->get_daemon_address()).str(); if (m_wallet->get_ring_database().empty()) @@ -3309,10 +3423,6 @@ bool simple_wallet::handle_command_line(const boost::program_options::variables_ m_restore_deterministic_wallet = command_line::get_arg(vm, arg_restore_deterministic_wallet); m_restore_multisig_wallet = command_line::get_arg(vm, arg_restore_multisig_wallet); m_non_deterministic = command_line::get_arg(vm, arg_non_deterministic); - if (!command_line::is_arg_defaulted(vm, arg_trusted_daemon) || !command_line::is_arg_defaulted(vm, arg_untrusted_daemon)) - m_trusted_daemon = command_line::get_arg(vm, arg_trusted_daemon) && !command_line::get_arg(vm, arg_untrusted_daemon); - if (!command_line::is_arg_defaulted(vm, arg_trusted_daemon) && !command_line::is_arg_defaulted(vm, arg_untrusted_daemon)) - message_writer() << tr("--trusted-daemon and --untrusted-daemon are both seen, assuming untrusted"); m_allow_mismatched_daemon_version = command_line::get_arg(vm, arg_allow_mismatched_daemon_version); m_restore_height = command_line::get_arg(vm, arg_restore_height); m_do_not_relay = command_line::get_arg(vm, arg_do_not_relay); @@ -3414,7 +3524,7 @@ boost::optional<tools::password_container> simple_wallet::get_and_verify_passwor boost::optional<epee::wipeable_string> simple_wallet::new_wallet(const boost::program_options::variables_map& vm, const crypto::secret_key& recovery_key, bool recover, bool two_random, const std::string &old_language) { - auto rc = tools::wallet2::make_new(vm, password_prompter); + auto rc = tools::wallet2::make_new(vm, false, password_prompter); m_wallet = std::move(rc.first); if (!m_wallet) { @@ -3469,7 +3579,10 @@ boost::optional<epee::wipeable_string> simple_wallet::new_wallet(const boost::pr recovery_val = m_wallet->generate(m_wallet_file, std::move(rc.second).password(), recovery_key, recover, two_random, create_address_file); message_writer(console_color_white, true) << tr("Generated new wallet: ") << m_wallet->get_account().get_public_address_str(m_wallet->nettype()); - std::cout << tr("View key: ") << string_tools::pod_to_hex(m_wallet->get_account().get_keys().m_view_secret_key) << ENDL; + PAUSE_READLINE(); + std::cout << tr("View key: "); + print_secret_key(m_wallet->get_account().get_keys().m_view_secret_key); + putchar('\n'); } catch (const std::exception& e) { @@ -3478,7 +3591,7 @@ boost::optional<epee::wipeable_string> simple_wallet::new_wallet(const boost::pr } // convert rng value to electrum-style word list - std::string electrum_words; + epee::wipeable_string electrum_words; crypto::ElectrumWords::bytes_to_words(recovery_val, electrum_words, mnemonic_language); @@ -3506,7 +3619,7 @@ boost::optional<epee::wipeable_string> simple_wallet::new_wallet(const boost::pr const cryptonote::account_public_address& address, const boost::optional<crypto::secret_key>& spendkey, const crypto::secret_key& viewkey) { - auto rc = tools::wallet2::make_new(vm, password_prompter); + auto rc = tools::wallet2::make_new(vm, false, password_prompter); m_wallet = std::move(rc.first); if (!m_wallet) { @@ -3552,7 +3665,7 @@ boost::optional<epee::wipeable_string> simple_wallet::new_wallet(const boost::pr //---------------------------------------------------------------------------------------------------- boost::optional<epee::wipeable_string> simple_wallet::new_wallet(const boost::program_options::variables_map& vm, const std::string &device_name) { - auto rc = tools::wallet2::make_new(vm, password_prompter); + auto rc = tools::wallet2::make_new(vm, false, password_prompter); m_wallet = std::move(rc.first); if (!m_wallet) { @@ -3586,9 +3699,9 @@ boost::optional<epee::wipeable_string> simple_wallet::new_wallet(const boost::pr } //---------------------------------------------------------------------------------------------------- boost::optional<epee::wipeable_string> simple_wallet::new_wallet(const boost::program_options::variables_map& vm, - const std::string &multisig_keys, const std::string &old_language) + const epee::wipeable_string &multisig_keys, const std::string &old_language) { - auto rc = tools::wallet2::make_new(vm, password_prompter); + auto rc = tools::wallet2::make_new(vm, false, password_prompter); m_wallet = std::move(rc.first); if (!m_wallet) { @@ -3659,7 +3772,7 @@ bool simple_wallet::open_wallet(const boost::program_options::variables_map& vm) epee::wipeable_string password; try { - auto rc = tools::wallet2::make_from_file(vm, m_wallet_file, password_prompter); + auto rc = tools::wallet2::make_from_file(vm, false, m_wallet_file, password_prompter); m_wallet = std::move(rc.first); password = std::move(std::move(rc.second).password()); if (!m_wallet) @@ -3686,7 +3799,12 @@ bool simple_wallet::open_wallet(const boost::program_options::variables_map& vm) // NOTE: this is_deprecated() refers to the wallet file format before becoming JSON. It does not refer to the "old english" seed words form of "deprecated" used elsewhere. if (m_wallet->is_deprecated()) { - if (m_wallet->is_deterministic()) + bool is_deterministic; + { + SCOPED_WALLET_UNLOCK(); + is_deterministic = m_wallet->is_deterministic(); + } + if (is_deterministic) { message_writer(console_color_green, false) << "\n" << tr("You had been using " "a deprecated version of the wallet. Please proceed to upgrade your wallet.\n"); @@ -3697,7 +3815,7 @@ bool simple_wallet::open_wallet(const boost::program_options::variables_map& vm) m_wallet->rewrite(m_wallet_file, password); // Display the seed - std::string seed; + epee::wipeable_string seed; m_wallet->get_seed(seed); print_seed(seed); } @@ -3816,7 +3934,7 @@ bool simple_wallet::save_watch_only(const std::vector<std::string> &args/* = std //---------------------------------------------------------------------------------------------------- bool simple_wallet::start_mining(const std::vector<std::string>& args) { - if (!is_daemon_trusted()) + if (!m_wallet->is_trusted_daemon()) { fail_msg_writer() << tr("this command requires a trusted daemon. Enable with --trusted-daemon"); return true; @@ -3924,34 +4042,34 @@ bool simple_wallet::set_daemon(const std::vector<std::string>& args) daemon_url = args[0]; } LOCK_IDLE_SCOPE(); - m_wallet->init(daemon_url); + m_wallet->init(false, daemon_url); if (args.size() == 2) { if (args[1] == "trusted") - m_trusted_daemon = true; + m_wallet->set_trusted_daemon(true); else if (args[1] == "untrusted") - m_trusted_daemon = false; + m_wallet->set_trusted_daemon(false); else { fail_msg_writer() << tr("Expected trusted or untrusted, got ") << args[1] << ": assuming untrusted"; - m_trusted_daemon = false; + m_wallet->set_trusted_daemon(false); } } else { - m_trusted_daemon = false; + m_wallet->set_trusted_daemon(false); try { if (tools::is_local_address(m_wallet->get_daemon_address())) { MINFO(tr("Daemon is local, assuming trusted")); - m_trusted_daemon = true; + m_wallet->set_trusted_daemon(true); } } catch (const std::exception &e) { } } - success_msg_writer() << boost::format("Daemon set to %s, %s") % daemon_url % (*m_trusted_daemon ? tr("trusted") : tr("untrusted")); + success_msg_writer() << boost::format("Daemon set to %s, %s") % daemon_url % (m_wallet->is_trusted_daemon() ? tr("trusted") : tr("untrusted")); } else { fail_msg_writer() << tr("This does not seem to be a valid daemon URL."); } @@ -4038,6 +4156,32 @@ void simple_wallet::on_skip_transaction(uint64_t height, const crypto::hash &txi { } //---------------------------------------------------------------------------------------------------- +boost::optional<epee::wipeable_string> simple_wallet::on_get_password(const char *reason) +{ + // can't ask for password from a background thread + if (!m_in_manual_refresh.load(std::memory_order_relaxed)) + { + message_writer(console_color_red, false) << tr("Password needed - use the refresh command"); + m_cmd_binder.print_prompt(); + return boost::none; + } + +#ifdef HAVE_READLINE + rdln::suspend_readline pause_readline; +#endif + std::string msg = tr("Enter password"); + if (reason && *reason) + msg += std::string(" (") + reason + ")"; + auto pwd_container = tools::password_container::prompt(false, msg.c_str()); + if (!pwd_container) + { + MERROR("Failed to read password"); + return boost::none; + } + + return pwd_container->password(); +} +//---------------------------------------------------------------------------------------------------- bool simple_wallet::refresh_main(uint64_t start_height, bool reset, bool is_init) { if (!try_connect_to_daemon(is_init)) @@ -4061,7 +4205,7 @@ bool simple_wallet::refresh_main(uint64_t start_height, bool reset, bool is_init { m_in_manual_refresh.store(true, std::memory_order_relaxed); epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler([&](){m_in_manual_refresh.store(false, std::memory_order_relaxed);}); - m_wallet->refresh(is_daemon_trusted(), start_height, fetched_blocks); + m_wallet->refresh(m_wallet->is_trusted_daemon(), start_height, fetched_blocks); ok = true; // Clear line "Height xxx of xxx" std::cout << "\r \r"; @@ -4352,7 +4496,7 @@ bool simple_wallet::show_blockchain_height(const std::vector<std::string>& args) //---------------------------------------------------------------------------------------------------- bool simple_wallet::rescan_spent(const std::vector<std::string> &args) { - if (!is_daemon_trusted()) + if (!m_wallet->is_trusted_daemon()) { fail_msg_writer() << tr("this command requires a trusted daemon. Enable with --trusted-daemon"); return true; @@ -4513,11 +4657,10 @@ bool simple_wallet::print_ring_members(const std::vector<tools::wallet2::pending bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::string> &args_) { // "transfer [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <address> <amount> [<payment_id>]" - if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } if (!try_connect_to_daemon()) return true; - LOCK_IDLE_SCOPE(); + SCOPED_WALLET_UNLOCK(); std::vector<std::string> local_args = args_; @@ -4701,16 +4844,16 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri return true; } unlock_block = bc_height + locked_blocks; - ptx_vector = m_wallet->create_transactions_2(dsts, fake_outs_count, unlock_block /* unlock_time */, priority, extra, m_current_subaddress_account, subaddr_indices, is_daemon_trusted()); + ptx_vector = m_wallet->create_transactions_2(dsts, fake_outs_count, unlock_block /* unlock_time */, priority, extra, m_current_subaddress_account, subaddr_indices); break; case TransferNew: - ptx_vector = m_wallet->create_transactions_2(dsts, fake_outs_count, 0 /* unlock_time */, priority, extra, m_current_subaddress_account, subaddr_indices, is_daemon_trusted()); + ptx_vector = m_wallet->create_transactions_2(dsts, fake_outs_count, 0 /* unlock_time */, priority, extra, m_current_subaddress_account, subaddr_indices); break; default: LOG_ERROR("Unknown transfer method, using original"); /* FALLTHRU */ case TransferOriginal: - ptx_vector = m_wallet->create_transactions(dsts, fake_outs_count, 0 /* unlock_time */, priority, extra, is_daemon_trusted()); + ptx_vector = m_wallet->create_transactions(dsts, fake_outs_count, 0 /* unlock_time */, priority, extra); break; } @@ -4886,7 +5029,7 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri } catch (const std::exception &e) { - handle_transfer_exception(std::current_exception(), is_daemon_trusted()); + handle_transfer_exception(std::current_exception(), m_wallet->is_trusted_daemon()); } catch (...) { @@ -4920,15 +5063,15 @@ bool simple_wallet::locked_sweep_all(const std::vector<std::string> &args_) bool simple_wallet::sweep_unmixable(const std::vector<std::string> &args_) { - if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } if (!try_connect_to_daemon()) return true; - LOCK_IDLE_SCOPE(); + SCOPED_WALLET_UNLOCK(); + try { // figure out what tx will be necessary - auto ptx_vector = m_wallet->create_unmixable_sweep_transactions(is_daemon_trusted()); + auto ptx_vector = m_wallet->create_unmixable_sweep_transactions(); if (ptx_vector.empty()) { @@ -5007,13 +5150,13 @@ bool simple_wallet::sweep_unmixable(const std::vector<std::string> &args_) { try { - m_wallet->discard_unmixable_outputs(is_daemon_trusted()); + m_wallet->discard_unmixable_outputs(); } catch (...) {} } } catch (const std::exception &e) { - handle_transfer_exception(std::current_exception(), is_daemon_trusted()); + handle_transfer_exception(std::current_exception(), m_wallet->is_trusted_daemon()); } catch (...) { @@ -5037,7 +5180,6 @@ bool simple_wallet::sweep_main(uint64_t below, bool locked, const std::vector<st return true; } - if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } if (!try_connect_to_daemon()) return true; @@ -5201,12 +5343,12 @@ bool simple_wallet::sweep_main(uint64_t below, bool locked, const std::vector<st } } - LOCK_IDLE_SCOPE(); + SCOPED_WALLET_UNLOCK(); try { // figure out what tx will be necessary - auto ptx_vector = m_wallet->create_transactions_all(below, info.address, info.is_subaddress, fake_outs_count, unlock_block /* unlock_time */, priority, extra, m_current_subaddress_account, subaddr_indices, is_daemon_trusted()); + auto ptx_vector = m_wallet->create_transactions_all(below, info.address, info.is_subaddress, fake_outs_count, unlock_block /* unlock_time */, priority, extra, m_current_subaddress_account, subaddr_indices); if (ptx_vector.empty()) { @@ -5290,7 +5432,7 @@ bool simple_wallet::sweep_main(uint64_t below, bool locked, const std::vector<st } catch (const std::exception& e) { - handle_transfer_exception(std::current_exception(), is_daemon_trusted()); + handle_transfer_exception(std::current_exception(), m_wallet->is_trusted_daemon()); } catch (...) { @@ -5303,7 +5445,7 @@ bool simple_wallet::sweep_main(uint64_t below, bool locked, const std::vector<st //---------------------------------------------------------------------------------------------------- bool simple_wallet::sweep_single(const std::vector<std::string> &args_) { - if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } + SCOPED_WALLET_UNLOCK(); if (!try_connect_to_daemon()) return true; @@ -5419,7 +5561,7 @@ bool simple_wallet::sweep_single(const std::vector<std::string> &args_) try { // figure out what tx will be necessary - auto ptx_vector = m_wallet->create_transactions_single(ki, info.address, info.is_subaddress, fake_outs_count, 0 /* unlock_time */, priority, extra, is_daemon_trusted()); + auto ptx_vector = m_wallet->create_transactions_single(ki, info.address, info.is_subaddress, fake_outs_count, 0 /* unlock_time */, priority, extra); if (ptx_vector.empty()) { @@ -5489,7 +5631,7 @@ bool simple_wallet::sweep_single(const std::vector<std::string> &args_) } catch (const std::exception& e) { - handle_transfer_exception(std::current_exception(), is_daemon_trusted()); + handle_transfer_exception(std::current_exception(), m_wallet->is_trusted_daemon()); } catch (...) { @@ -5729,7 +5871,8 @@ bool simple_wallet::sign_transfer(const std::vector<std::string> &args_) fail_msg_writer() << tr("usage: sign_transfer [export_raw]"); return true; } - if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } + + SCOPED_WALLET_UNLOCK(); const bool export_raw = args_.size() == 1; std::vector<tools::wallet2::pending_tx> ptx; @@ -5794,7 +5937,7 @@ bool simple_wallet::submit_transfer(const std::vector<std::string> &args_) } catch (const std::exception& e) { - handle_transfer_exception(std::current_exception(), is_daemon_trusted()); + handle_transfer_exception(std::current_exception(), m_wallet->is_trusted_daemon()); } catch (...) { @@ -5818,7 +5961,6 @@ bool simple_wallet::get_tx_key(const std::vector<std::string> &args_) fail_msg_writer() << tr("usage: get_tx_key <txid>"); return true; } - if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } crypto::hash txid; if (!epee::string_tools::hex_to_pod(local_args[0], txid)) @@ -5827,7 +5969,7 @@ bool simple_wallet::get_tx_key(const std::vector<std::string> &args_) return true; } - LOCK_IDLE_SCOPE(); + SCOPED_WALLET_UNLOCK(); crypto::secret_key tx_key; std::vector<crypto::secret_key> additional_tx_keys; @@ -5932,7 +6074,7 @@ bool simple_wallet::get_tx_proof(const std::vector<std::string> &args) return true; } - if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } + SCOPED_WALLET_UNLOCK(); try { @@ -6147,7 +6289,7 @@ bool simple_wallet::get_spend_proof(const std::vector<std::string> &args) return true; } - if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } + SCOPED_WALLET_UNLOCK(); try { @@ -6242,9 +6384,7 @@ bool simple_wallet::get_reserve_proof(const std::vector<std::string> &args) return true; } - if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } - - LOCK_IDLE_SCOPE(); + SCOPED_WALLET_UNLOCK(); try { @@ -6345,7 +6485,7 @@ static std::string get_human_readable_timespan(std::chrono::seconds seconds) if (ts < 3600 * 24 * 30.5) return std::to_string((uint64_t)(ts / (3600 * 24))) + tr(" days"); if (ts < 3600 * 24 * 365.25) - return std::to_string((uint64_t)(ts / (3600 * 24 * 365.25))) + tr(" months"); + return std::to_string((uint64_t)(ts / (3600 * 24 * 30.5))) + tr(" months"); return tr("a long time"); } //---------------------------------------------------------------------------------------------------- @@ -6497,6 +6637,9 @@ bool simple_wallet::show_transfers(const std::vector<std::string> &args_) if (pool) { try { + m_in_manual_refresh.store(true, std::memory_order_relaxed); + epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler([&](){m_in_manual_refresh.store(false, std::memory_order_relaxed);}); + m_wallet->update_pool_state(); std::list<std::pair<crypto::hash, tools::wallet2::pool_payment_details>> payments; m_wallet->get_unconfirmed_payments(payments, m_current_subaddress_account, subaddr_indices); @@ -6680,6 +6823,14 @@ bool simple_wallet::unspent_outputs(const std::vector<std::string> &args_) //---------------------------------------------------------------------------------------------------- bool simple_wallet::rescan_blockchain(const std::vector<std::string> &args_) { + message_writer() << tr("Warning: this will lose any information which can not be recovered from the blockchain."); + message_writer() << tr("This includes destination addresses, tx secret keys, tx notes, etc"); + std::string confirm = input_line(tr("Rescan anyway ? (Y/Yes/N/No): ")); + if(!std::cin.eof()) + { + if (!command_line::is_yes(confirm)) + return true; + } return refresh_main(0, true); } //---------------------------------------------------------------------------------------------------- @@ -6699,7 +6850,7 @@ void simple_wallet::wallet_idle_thread() { uint64_t fetched_blocks; if (try_connect_to_daemon(true)) - m_wallet->refresh(is_daemon_trusted(), 0, fetched_blocks); + m_wallet->refresh(m_wallet->is_trusted_daemon(), 0, fetched_blocks); } catch(...) {} m_auto_refresh_refreshing = false; @@ -7345,7 +7496,8 @@ bool simple_wallet::sign(const std::vector<std::string> &args) fail_msg_writer() << tr("This wallet is multisig and cannot sign"); return true; } - if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } + + SCOPED_WALLET_UNLOCK(); std::string filename = args[0]; std::string data; bool r = epee::file_io_utils::load_file_to_string(filename, data); @@ -7414,14 +7566,14 @@ bool simple_wallet::export_key_images(const std::vector<std::string> &args) fail_msg_writer() << tr("wallet is watch-only and cannot export key images"); return true; } - if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } + + SCOPED_WALLET_UNLOCK(); std::string filename = args[0]; if (m_wallet->confirm_export_overwrite() && !check_file_overwrite(filename)) return true; try { - LOCK_IDLE_SCOPE(); if (!m_wallet->export_key_images(filename)) { fail_msg_writer() << tr("failed to save file ") << filename; @@ -7446,7 +7598,7 @@ bool simple_wallet::import_key_images(const std::vector<std::string> &args) fail_msg_writer() << tr("command not supported by HW wallet"); return true; } - if (!is_daemon_trusted()) + if (!m_wallet->is_trusted_daemon()) { fail_msg_writer() << tr("this command requires a trusted daemon. Enable with --trusted-daemon"); return true; @@ -7493,12 +7645,12 @@ bool simple_wallet::export_outputs(const std::vector<std::string> &args) fail_msg_writer() << tr("usage: export_outputs <filename>"); return true; } - if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } + + SCOPED_WALLET_UNLOCK(); std::string filename = args[0]; if (m_wallet->confirm_export_overwrite() && !check_file_overwrite(filename)) return true; - LOCK_IDLE_SCOPE(); try { std::string data = m_wallet->export_outputs_to_str(); @@ -7544,7 +7696,7 @@ bool simple_wallet::import_outputs(const std::vector<std::string> &args) try { - LOCK_IDLE_SCOPE(); + SCOPED_WALLET_UNLOCK(); size_t n_outputs = m_wallet->import_outputs_from_str(data); success_msg_writer() << boost::lexical_cast<std::string>(n_outputs) << " outputs imported"; } @@ -7777,8 +7929,6 @@ int main(int argc, char* argv[]) command_line::add_arg(desc_params, arg_restore_multisig_wallet ); command_line::add_arg(desc_params, arg_non_deterministic ); command_line::add_arg(desc_params, arg_electrum_seed ); - command_line::add_arg(desc_params, arg_trusted_daemon); - command_line::add_arg(desc_params, arg_untrusted_daemon); command_line::add_arg(desc_params, arg_allow_mismatched_daemon_version); command_line::add_arg(desc_params, arg_restore_height); command_line::add_arg(desc_params, arg_do_not_relay); diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index 472ad0c00..bfbe633ac 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -44,6 +44,7 @@ #include "cryptonote_basic/cryptonote_basic_impl.h" #include "wallet/wallet2.h" #include "console_handler.h" +#include "wipeable_string.h" #include "common/i18n.h" #include "common/password.h" #include "crypto/crypto.h" // for definition of crypto::secret_key @@ -96,7 +97,7 @@ namespace cryptonote boost::optional<epee::wipeable_string> new_wallet(const boost::program_options::variables_map& vm, const cryptonote::account_public_address& address, const boost::optional<crypto::secret_key>& spendkey, const crypto::secret_key& viewkey); boost::optional<epee::wipeable_string> new_wallet(const boost::program_options::variables_map& vm, - const std::string &multisig_keys, const std::string &old_language); + const epee::wipeable_string &multisig_keys, const std::string &old_language); boost::optional<epee::wipeable_string> new_wallet(const boost::program_options::variables_map& vm, const std::string& device_name); bool open_wallet(const boost::program_options::variables_map& vm); bool close_wallet(); @@ -232,13 +233,12 @@ namespace cryptonote 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); - bool is_daemon_trusted() const { return *m_trusted_daemon; } /*! * \brief Prints the seed with a nice message * \param seed seed to print */ - void print_seed(std::string seed); + void print_seed(const epee::wipeable_string &seed); /*! * \brief Gets the word seed language from the user. @@ -261,6 +261,7 @@ namespace cryptonote virtual void on_unconfirmed_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index); virtual void on_money_spent(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& in_tx, uint64_t amount, const cryptonote::transaction& spend_tx, const cryptonote::subaddress_index& subaddr_index); virtual void on_skip_transaction(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx); + virtual boost::optional<epee::wipeable_string> on_get_password(const char *reason); //---------------------------------------------------------- friend class refresh_progress_reporter_t; @@ -329,13 +330,12 @@ namespace cryptonote std::string m_import_path; std::string m_subaddress_lookahead; - std::string m_electrum_seed; // electrum-style seed parameter + epee::wipeable_string m_electrum_seed; // electrum-style seed parameter crypto::secret_key m_recovery_key; // recovery key (used as random for wallet gen) bool m_restore_deterministic_wallet; // recover flag bool m_restore_multisig_wallet; // recover flag bool m_non_deterministic; // old 2-random generation - boost::optional<bool> m_trusted_daemon; bool m_allow_mismatched_daemon_version; bool m_restoring; // are we restoring, by whatever method? uint64_t m_restore_height; // optional diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index b48bf07e0..7b4ad27e4 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -369,7 +369,6 @@ void Wallet::error(const std::string &category, const std::string &str) { WalletImpl::WalletImpl(NetworkType nettype, uint64_t kdf_rounds) :m_wallet(nullptr) , m_status(Wallet::Status_Ok) - , m_trustedDaemon(false) , m_wallet2Callback(nullptr) , m_recoveringFromSeed(false) , m_recoveringFromDevice(false) @@ -733,10 +732,10 @@ bool WalletImpl::close(bool store) std::string WalletImpl::seed() const { - std::string seed; + epee::wipeable_string seed; if (m_wallet) m_wallet->get_seed(seed); - return seed; + return std::string(seed.data(), seed.size()); // TODO } std::string WalletImpl::getSeedLanguage() const @@ -1358,7 +1357,7 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const dsts.push_back(de); transaction->m_pending_tx = m_wallet->create_transactions_2(dsts, fake_outs_count, 0 /* unlock_time */, adjusted_priority, - extra, subaddr_account, subaddr_indices, m_trustedDaemon); + extra, subaddr_account, subaddr_indices); } else { // for the GUI, sweep_all (i.e. amount set as "(all)") will always sweep all the funds in all the addresses if (subaddr_indices.empty()) @@ -1368,7 +1367,7 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const } transaction->m_pending_tx = m_wallet->create_transactions_all(0, info.address, info.is_subaddress, fake_outs_count, 0 /* unlock_time */, adjusted_priority, - extra, subaddr_account, subaddr_indices, m_trustedDaemon); + extra, subaddr_account, subaddr_indices); } if (multisig().isMultisig) { @@ -1454,7 +1453,7 @@ PendingTransaction *WalletImpl::createSweepUnmixableTransaction() do { try { - transaction->m_pending_tx = m_wallet->create_unmixable_sweep_transactions(m_trustedDaemon); + transaction->m_pending_tx = m_wallet->create_unmixable_sweep_transactions(); } catch (const tools::error::daemon_busy&) { // TODO: make it translatable with "tr"? @@ -1891,12 +1890,12 @@ Wallet::ConnectionStatus WalletImpl::connected() const void WalletImpl::setTrustedDaemon(bool arg) { - m_trustedDaemon = arg; + m_wallet->set_trusted_daemon(arg); } bool WalletImpl::trustedDaemon() const { - return m_trustedDaemon; + return m_wallet->is_trusted_daemon(); } bool WalletImpl::watchOnly() const @@ -2032,7 +2031,8 @@ bool WalletImpl::isNewWallet() const bool WalletImpl::doInit(const string &daemon_address, uint64_t upper_transaction_size_limit, bool ssl) { - if (!m_wallet->init(daemon_address, m_daemon_login, upper_transaction_size_limit, ssl)) + // claim RPC so there's no in-memory encryption for now + if (!m_wallet->init(true, daemon_address, m_daemon_login, upper_transaction_size_limit, ssl)) return false; // in case new wallet, this will force fast-refresh (pulling hashes instead of blocks) diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h index 9218d3ad5..0f3b1ce04 100644 --- a/src/wallet/api/wallet.h +++ b/src/wallet/api/wallet.h @@ -219,7 +219,6 @@ private: mutable std::string m_errorString; std::string m_password; TransactionHistoryImpl * m_history; - bool m_trustedDaemon; Wallet2CallbackImpl * m_wallet2Callback; AddressBookImpl * m_addressBook; SubaddressImpl * m_subaddress; diff --git a/src/wallet/node_rpc_proxy.cpp b/src/wallet/node_rpc_proxy.cpp index 401ada61b..2072495cd 100644 --- a/src/wallet/node_rpc_proxy.cpp +++ b/src/wallet/node_rpc_proxy.cpp @@ -146,7 +146,7 @@ boost::optional<std::string> NodeRPCProxy::get_earliest_height(uint8_t version, CHECK_AND_ASSERT_MES(r, std::string(), "Failed to connect to daemon"); CHECK_AND_ASSERT_MES(resp_t.status != CORE_RPC_STATUS_BUSY, resp_t.status, "Failed to connect to daemon"); CHECK_AND_ASSERT_MES(resp_t.status == CORE_RPC_STATUS_OK, resp_t.status, "Failed to get hard fork status"); - m_earliest_height[version] = resp_t.enabled ? resp_t.earliest_height : std::numeric_limits<uint64_t>::max(); + m_earliest_height[version] = resp_t.earliest_height; } earliest_height = m_earliest_height[version]; diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 9deaad09b..a2e36706e 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -89,6 +89,7 @@ using namespace cryptonote; // 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" @@ -121,8 +122,6 @@ using namespace cryptonote; static const std::string MULTISIG_SIGNATURE_MAGIC = "SigMultisigPkV1"; -std::atomic<unsigned int> tools::wallet2::key_ref::refs(0); - namespace { std::string get_default_ringdb_path() @@ -141,6 +140,8 @@ namespace struct options { const command_line::arg_descriptor<std::string> daemon_address = {"daemon-address", tools::wallet2::tr("Use daemon instance at <host>:<port>"), ""}; const command_line::arg_descriptor<std::string> daemon_host = {"daemon-host", tools::wallet2::tr("Use daemon instance at host <arg> instead of localhost"), ""}; + const command_line::arg_descriptor<bool> trusted_daemon = {"trusted-daemon", tools::wallet2::tr("Enable commands which rely on a trusted daemon"), false}; + const command_line::arg_descriptor<bool> untrusted_daemon = {"untrusted-daemon", tools::wallet2::tr("Disable commands which rely on a trusted daemon"), false}; const command_line::arg_descriptor<std::string> password = {"password", tools::wallet2::tr("Wallet password (escape/quote as needed)"), "", true}; const command_line::arg_descriptor<std::string> password_file = {"password-file", tools::wallet2::tr("Wallet password file"), "", true}; const command_line::arg_descriptor<int> daemon_port = {"daemon-port", tools::wallet2::tr("Use daemon instance at port <arg> instead of 18081"), 0}; @@ -197,7 +198,7 @@ std::string get_size_string(const cryptonote::blobdata &tx) return get_size_string(tx.size()); } -std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variables_map& vm, const options& opts, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter) +std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variables_map& vm, bool rpc, const options& opts, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter) { const bool testnet = command_line::get_arg(vm, opts.testnet); const bool stagenet = command_line::get_arg(vm, opts.stagenet); @@ -237,8 +238,29 @@ std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variabl if (daemon_address.empty()) daemon_address = std::string("http://") + daemon_host + ":" + std::to_string(daemon_port); + boost::optional<bool> trusted_daemon; + if (!command_line::is_arg_defaulted(vm, opts.trusted_daemon) || !command_line::is_arg_defaulted(vm, opts.untrusted_daemon)) + trusted_daemon = command_line::get_arg(vm, opts.trusted_daemon) && !command_line::get_arg(vm, opts.untrusted_daemon); + THROW_WALLET_EXCEPTION_IF(!command_line::is_arg_defaulted(vm, opts.trusted_daemon) && !command_line::is_arg_defaulted(vm, opts.untrusted_daemon), + tools::error::wallet_internal_error, tools::wallet2::tr("--trusted-daemon and --untrusted-daemon are both seen, assuming untrusted")); + + // set --trusted-daemon if local and not overridden + if (!trusted_daemon) + { + try + { + trusted_daemon = false; + if (tools::is_local_address(daemon_address)) + { + MINFO(tr("Daemon is local, assuming trusted")); + trusted_daemon = true; + } + } + catch (const std::exception &e) { } + } + std::unique_ptr<tools::wallet2> wallet(new tools::wallet2(nettype, kdf_rounds)); - wallet->init(std::move(daemon_address), std::move(login)); + wallet->init(rpc, std::move(daemon_address), std::move(login), 0, false, *trusted_daemon); boost::filesystem::path ringdb_path = command_line::get_arg(vm, opts.shared_ringdb_dir); wallet->set_ring_database(ringdb_path.string()); return wallet; @@ -273,7 +295,7 @@ boost::optional<tools::password_container> get_password(const boost::program_opt return password_prompter(verify ? tr("Enter a new password for the wallet") : tr("Wallet password"), verify); } -std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file, const boost::program_options::variables_map& vm, const options& opts, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter) +std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file, const boost::program_options::variables_map& vm, bool rpc, const options& opts, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter) { const bool testnet = command_line::get_arg(vm, opts.testnet); const bool stagenet = command_line::get_arg(vm, opts.stagenet); @@ -411,7 +433,7 @@ std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file, THROW_WALLET_EXCEPTION_IF(deprecated_wallet, tools::error::wallet_internal_error, tools::wallet2::tr("Cannot generate deprecated wallets from JSON")); - wallet.reset(make_basic(vm, opts, password_prompter).release()); + wallet.reset(make_basic(vm, rpc, opts, password_prompter).release()); wallet->set_refresh_from_block_height(field_scan_from_height); wallet->explicit_refresh_from_block_height(field_scan_from_height_found); @@ -648,11 +670,40 @@ const size_t MAX_SPLIT_ATTEMPTS = 30; constexpr const std::chrono::seconds wallet2::rpc_timeout; const char* wallet2::tr(const char* str) { return i18n_translate(str, "tools::wallet2"); } +wallet_keys_unlocker::wallet_keys_unlocker(wallet2 &w, const boost::optional<tools::password_container> &password): + w(w), + locked(password != boost::none) +{ + if (!locked || w.is_rpc()) + return; + const epee::wipeable_string pass = password->password(); + w.generate_chacha_key_from_password(pass, key); + w.decrypt_keys(key); +} + +wallet_keys_unlocker::wallet_keys_unlocker(wallet2 &w, bool locked, const epee::wipeable_string &password): + w(w), + locked(locked) +{ + if (!locked) + return; + w.generate_chacha_key_from_password(password, key); + w.decrypt_keys(key); +} + +wallet_keys_unlocker::~wallet_keys_unlocker() +{ + if (!locked) + return; + w.encrypt_keys(key); +} + wallet2::wallet2(network_type nettype, uint64_t kdf_rounds): m_multisig_rescan_info(NULL), m_multisig_rescan_k(NULL), m_run(true), m_callback(0), + m_trusted_daemon(false), m_nettype(nettype), m_always_confirm_transfers(true), m_print_ring_members(false), @@ -693,7 +744,9 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds): m_key_on_device(false), m_ring_history_saved(false), m_ringdb(), - m_last_block_reward(0) + m_last_block_reward(0), + m_encrypt_keys_after_refresh(boost::none), + m_rpc(false) { } @@ -716,6 +769,8 @@ void wallet2::init_options(boost::program_options::options_description& desc_par const options opts{}; command_line::add_arg(desc_params, opts.daemon_address); command_line::add_arg(desc_params, opts.daemon_host); + command_line::add_arg(desc_params, opts.trusted_daemon); + command_line::add_arg(desc_params, opts.untrusted_daemon); command_line::add_arg(desc_params, opts.password); command_line::add_arg(desc_params, opts.password_file); command_line::add_arg(desc_params, opts.daemon_port); @@ -726,14 +781,14 @@ void wallet2::init_options(boost::program_options::options_description& desc_par command_line::add_arg(desc_params, opts.kdf_rounds); } -std::unique_ptr<wallet2> wallet2::make_from_json(const boost::program_options::variables_map& vm, const std::string& json_file, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter) +std::unique_ptr<wallet2> wallet2::make_from_json(const boost::program_options::variables_map& vm, bool rpc, const std::string& json_file, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter) { const options opts{}; - return generate_from_json(json_file, vm, opts, password_prompter); + return generate_from_json(json_file, vm, rpc, opts, password_prompter); } std::pair<std::unique_ptr<wallet2>, password_container> wallet2::make_from_file( - const boost::program_options::variables_map& vm, const std::string& wallet_file, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter) + const boost::program_options::variables_map& vm, bool rpc, const std::string& wallet_file, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter) { const options opts{}; auto pwd = get_password(vm, opts, password_prompter, false); @@ -741,7 +796,7 @@ std::pair<std::unique_ptr<wallet2>, password_container> wallet2::make_from_file( { return {nullptr, password_container{}}; } - auto wallet = make_basic(vm, opts, password_prompter); + auto wallet = make_basic(vm, rpc, opts, password_prompter); if (wallet) { wallet->load(wallet_file, pwd->password()); @@ -749,7 +804,7 @@ std::pair<std::unique_ptr<wallet2>, password_container> wallet2::make_from_file( return {std::move(wallet), std::move(*pwd)}; } -std::pair<std::unique_ptr<wallet2>, password_container> wallet2::make_new(const boost::program_options::variables_map& vm, const std::function<boost::optional<password_container>(const char *, bool)> &password_prompter) +std::pair<std::unique_ptr<wallet2>, password_container> wallet2::make_new(const boost::program_options::variables_map& vm, bool rpc, const std::function<boost::optional<password_container>(const char *, bool)> &password_prompter) { const options opts{}; auto pwd = get_password(vm, opts, password_prompter, true); @@ -757,18 +812,19 @@ std::pair<std::unique_ptr<wallet2>, password_container> wallet2::make_new(const { return {nullptr, password_container{}}; } - return {make_basic(vm, opts, password_prompter), std::move(*pwd)}; + return {make_basic(vm, rpc, opts, password_prompter), std::move(*pwd)}; } -std::unique_ptr<wallet2> wallet2::make_dummy(const boost::program_options::variables_map& vm, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter) +std::unique_ptr<wallet2> wallet2::make_dummy(const boost::program_options::variables_map& vm, bool rpc, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter) { const options opts{}; - return make_basic(vm, opts, password_prompter); + return make_basic(vm, rpc, opts, password_prompter); } //---------------------------------------------------------------------------------------------------- -bool wallet2::init(std::string daemon_address, boost::optional<epee::net_utils::http::login> daemon_login, uint64_t upper_transaction_size_limit, bool ssl) +bool wallet2::init(bool rpc, std::string daemon_address, boost::optional<epee::net_utils::http::login> daemon_login, uint64_t upper_transaction_size_limit, bool ssl, bool trusted_daemon) { + m_rpc = rpc; m_checkpoints.init_default_checkpoints(m_nettype); if(m_http_client.is_connected()) m_http_client.disconnect(); @@ -776,6 +832,7 @@ bool wallet2::init(std::string daemon_address, boost::optional<epee::net_utils:: m_upper_transaction_size_limit = upper_transaction_size_limit; m_daemon_address = std::move(daemon_address); m_daemon_login = std::move(daemon_login); + m_trusted_daemon = trusted_daemon; // When switching from light wallet to full wallet, we need to reset the height we got from lw node. return m_http_client.set_server(get_daemon_address(), get_daemon_login(), ssl); } @@ -785,11 +842,10 @@ bool wallet2::is_deterministic() const crypto::secret_key second; keccak((uint8_t *)&get_account().get_keys().m_spend_secret_key, sizeof(crypto::secret_key), (uint8_t *)&second, sizeof(crypto::secret_key)); sc_reduce32((uint8_t *)&second); - bool keys_deterministic = memcmp(second.data,get_account().get_keys().m_view_secret_key.data, sizeof(crypto::secret_key)) == 0; - return keys_deterministic; + return memcmp(second.data,get_account().get_keys().m_view_secret_key.data, sizeof(crypto::secret_key)) == 0; } //---------------------------------------------------------------------------------------------------- -bool wallet2::get_seed(std::string& electrum_words, const epee::wipeable_string &passphrase) const +bool wallet2::get_seed(epee::wipeable_string& electrum_words, const epee::wipeable_string &passphrase) const { bool keys_deterministic = is_deterministic(); if (!keys_deterministic) @@ -815,7 +871,7 @@ bool wallet2::get_seed(std::string& electrum_words, const epee::wipeable_string return true; } //---------------------------------------------------------------------------------------------------- -bool wallet2::get_multisig_seed(std::string& seed, const epee::wipeable_string &passphrase, bool raw) const +bool wallet2::get_multisig_seed(epee::wipeable_string& seed, const epee::wipeable_string &passphrase, bool raw) const { bool ready; uint32_t threshold, total; @@ -838,7 +894,7 @@ bool wallet2::get_multisig_seed(std::string& seed, const epee::wipeable_string & crypto::secret_key skey; crypto::public_key pkey; const account_keys &keys = get_account().get_keys(); - std::string data; + epee::wipeable_string data; data.append((const char*)&threshold, sizeof(uint32_t)); data.append((const char*)&total, sizeof(uint32_t)); skey = keys.m_spend_secret_key; @@ -864,7 +920,7 @@ bool wallet2::get_multisig_seed(std::string& seed, const epee::wipeable_string & if (raw) { - seed = epee::string_tools::buff_to_hex_nodelimer(data); + seed = epee::to_hex::wipeable_string({(const unsigned char*)data.data(), data.size()}); } else { @@ -1103,9 +1159,25 @@ static uint64_t decodeRct(const rct::rctSig & rv, const crypto::key_derivation & } } //---------------------------------------------------------------------------------------------------- -void wallet2::scan_output(const cryptonote::transaction &tx, const crypto::public_key &tx_pub_key, size_t i, tx_scan_info_t &tx_scan_info, int &num_vouts_received, std::unordered_map<cryptonote::subaddress_index, uint64_t> &tx_money_got_in_outs, std::vector<size_t> &outs) const +void wallet2::scan_output(const cryptonote::transaction &tx, const crypto::public_key &tx_pub_key, size_t i, tx_scan_info_t &tx_scan_info, int &num_vouts_received, std::unordered_map<cryptonote::subaddress_index, uint64_t> &tx_money_got_in_outs, std::vector<size_t> &outs) { THROW_WALLET_EXCEPTION_IF(i >= tx.vout.size(), error::wallet_internal_error, "Invalid vout index"); + + // if keys are encrypted, ask for password + if (m_ask_password && !m_rpc && !m_watch_only && !m_multisig_rescan_k) + { + static critical_section password_lock; + CRITICAL_REGION_LOCAL(password_lock); + if (!m_encrypt_keys_after_refresh) + { + boost::optional<epee::wipeable_string> pwd = m_callback->on_get_password("output received"); + THROW_WALLET_EXCEPTION_IF(!pwd, error::password_needed, tr("Password is needed to compute key image for incoming monero")); + THROW_WALLET_EXCEPTION_IF(!verify_password(*pwd), error::password_needed, tr("Invalid password: password is needed to compute key image for incoming monero")); + decrypt_keys(*pwd); + m_encrypt_keys_after_refresh = *pwd; + } + } + if (m_multisig) { tx_scan_info.in_ephemeral.pub = boost::get<cryptonote::txout_to_key>(tx.vout[i].target).key; @@ -1763,34 +1835,7 @@ void wallet2::pull_blocks(uint64_t start_height, uint64_t &blocks_start_height, cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::response res = AUTO_VAL_INIT(res); req.block_ids = short_chain_history; - uint32_t rpc_version; - boost::optional<std::string> result = m_node_rpc_proxy.get_rpc_version(rpc_version); - // no error - if (!!result) - { - // empty string -> not connection - THROW_WALLET_EXCEPTION_IF(result->empty(), tools::error::no_connection_to_daemon, "getversion"); - THROW_WALLET_EXCEPTION_IF(*result == CORE_RPC_STATUS_BUSY, tools::error::daemon_busy, "getversion"); - if (*result != CORE_RPC_STATUS_OK) - { - MDEBUG("Cannot determine daemon RPC version, not asking for pruned blocks"); - req.prune = false; // old daemon - } - } - else - { - if (rpc_version >= MAKE_CORE_RPC_VERSION(1, 7)) - { - MDEBUG("Daemon is recent enough, asking for pruned blocks"); - req.prune = true; - } - else - { - MDEBUG("Daemon is too old, not asking for pruned blocks"); - req.prune = false; - } - } - + req.prune = true; req.start_height = start_height; req.no_miner_tx = m_refresh_type == RefreshNoCoinbase; m_daemon_rpc_mutex.lock(); @@ -2063,6 +2108,14 @@ void wallet2::update_pool_state(bool refreshed) { MDEBUG("update_pool_state start"); + auto keys_reencryptor = epee::misc_utils::create_scope_leave_handler([&, this]() { + if (m_encrypt_keys_after_refresh) + { + encrypt_keys(*m_encrypt_keys_after_refresh); + m_encrypt_keys_after_refresh = boost::none; + } + }); + // get the pool state cryptonote::COMMAND_RPC_GET_TRANSACTION_POOL_HASHES_BIN::request req; cryptonote::COMMAND_RPC_GET_TRANSACTION_POOL_HASHES_BIN::response res; @@ -2369,8 +2422,6 @@ bool wallet2::delete_address_book_row(std::size_t row_id) { //---------------------------------------------------------------------------------------------------- void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blocks_fetched, bool& received_money) { - key_ref kref(*this); - if(m_light_wallet) { // MyMonero get_address_info needs to be called occasionally to trigger wallet sync. @@ -2438,6 +2489,14 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo // subsequent pulls in this refresh. start_height = 0; + auto keys_reencryptor = epee::misc_utils::create_scope_leave_handler([&, this]() { + if (m_encrypt_keys_after_refresh) + { + encrypt_keys(*m_encrypt_keys_after_refresh); + m_encrypt_keys_after_refresh = boost::none; + } + }); + bool first = true; while(m_run.load(std::memory_order_relaxed)) { @@ -2507,6 +2566,12 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo throw std::runtime_error("proxy exception in refresh thread"); } } + catch (const tools::error::password_needed&) + { + blocks_fetched += added_blocks; + waiter.wait(&tpool); + throw; + } catch (const std::exception&) { blocks_fetched += added_blocks; @@ -2591,6 +2656,7 @@ bool wallet2::get_rct_distribution(uint64_t &start_height, std::vector<uint64_t> req.amounts.push_back(0); req.from_height = 0; req.cumulative = true; + req.binary = true; m_daemon_rpc_mutex.lock(); bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_output_distribution", req, res, m_http_client, rpc_timeout); m_daemon_rpc_mutex.unlock(); @@ -2728,8 +2794,20 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable std::string multisig_signers; cryptonote::account_base account = m_account; + crypto::chacha_key key; + crypto::generate_chacha_key(password.data(), password.size(), key, m_kdf_rounds); + + if (m_ask_password && !m_rpc && !m_watch_only) + { + account.encrypt_viewkey(key); + account.decrypt_keys(key); + } + if (watch_only) account.forget_spend_key(); + + account.encrypt_keys(key); + bool r = epee::serialization::store_t_to_binary(account, account_data); CHECK_AND_ASSERT_MES(r, false, "failed to serialize wallet keys"); wallet2::keys_file_data keys_file_data = boost::value_initialized<wallet2::keys_file_data>(); @@ -2846,6 +2924,9 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable value2.SetUint(m_subaddress_lookahead_minor); json.AddMember("subaddress_lookahead_minor", value2, json.GetAllocator()); + value2.SetUint(1); + json.AddMember("encrypted_secret_keys", value2, json.GetAllocator()); + // Serialize the JSON object rapidjson::StringBuffer buffer; rapidjson::Writer<rapidjson::StringBuffer> writer(buffer); @@ -2853,7 +2934,6 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable account_data = buffer.GetString(); // Encrypt the entire JSON object. - crypto::chacha_key key; crypto::generate_chacha_key(password.data(), password.size(), key, m_kdf_rounds); std::string cipher; cipher.resize(account_data.size()); @@ -2871,6 +2951,35 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable return true; } //---------------------------------------------------------------------------------------------------- +void wallet2::setup_keys(const epee::wipeable_string &password) +{ + crypto::chacha_key key; + crypto::generate_chacha_key(password.data(), password.size(), key, m_kdf_rounds); + + // re-encrypt, but keep viewkey unencrypted + if (m_ask_password && !m_rpc && !m_watch_only) + { + m_account.encrypt_keys(key); + m_account.decrypt_viewkey(key); + } + + 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; + cn_fast_hash(cache_key_data.data(), HASH_SIZE+1, (crypto::hash&)m_cache_key); + get_ringdb_key(); +} +//---------------------------------------------------------------------------------------------------- +void wallet2::change_password(const std::string &filename, const epee::wipeable_string &original_password, const epee::wipeable_string &new_password) +{ + if (m_ask_password && !m_rpc && !m_watch_only) + decrypt_keys(original_password); + setup_keys(new_password); + rewrite(filename, new_password); + store(); +} +//---------------------------------------------------------------------------------------------------- /*! * \brief Load wallet information from wallet file. * \param keys_file_name Name of wallet file @@ -2881,6 +2990,7 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ rapidjson::Document json; wallet2::keys_file_data keys_file_data; std::string buf; + bool encrypted_secret_keys = false; bool r = epee::file_io_utils::load_file_to_string(keys_file_name, buf); THROW_WALLET_EXCEPTION_IF(!r, error::file_read_error, keys_file_name); @@ -2926,6 +3036,7 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ m_subaddress_lookahead_major = SUBADDRESS_LOOKAHEAD_MAJOR; m_subaddress_lookahead_minor = SUBADDRESS_LOOKAHEAD_MINOR; m_key_on_device = false; + encrypted_secret_keys = false; } else if(json.IsObject()) { @@ -3055,6 +3166,8 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ m_subaddress_lookahead_major = field_subaddress_lookahead_major; GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, subaddress_lookahead_minor, uint32_t, Uint, false, SUBADDRESS_LOOKAHEAD_MINOR); m_subaddress_lookahead_minor = field_subaddress_lookahead_minor; + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, encrypted_secret_keys, uint32_t, Uint, false, false); + encrypted_secret_keys = field_encrypted_secret_keys; } else { @@ -3071,12 +3184,39 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ m_account.set_device(hwdev); LOG_PRINT_L0("Device inited..."); } + + if (r) + { + if (encrypted_secret_keys) + { + m_account.decrypt_keys(key); + } + else + { + // rewrite with encrypted keys, ignore errors + if (m_ask_password && !m_rpc && !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 && !m_rpc && !m_watch_only) + decrypt_keys(key); + m_keys_file_locker.reset(); + } + } 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) r = r && hwdev.verify_keys(keys.m_spend_secret_key, keys.m_account_address.m_spend_public_key); THROW_WALLET_EXCEPTION_IF(!r, error::invalid_password); + + if (r) + setup_keys(password); + return true; } @@ -3117,6 +3257,7 @@ bool wallet2::verify_password(const std::string& keys_file_name, const epee::wip rapidjson::Document json; wallet2::keys_file_data keys_file_data; std::string buf; + bool encrypted_secret_keys = false; bool r = epee::file_io_utils::load_file_to_string(keys_file_name, buf); THROW_WALLET_EXCEPTION_IF(!r, error::file_read_error, keys_file_name); @@ -3140,19 +3281,50 @@ bool wallet2::verify_password(const std::string& keys_file_name, const epee::wip { account_data = std::string(json["key_data"].GetString(), json["key_data"].GetString() + json["key_data"].GetStringLength()); + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, encrypted_secret_keys, uint32_t, Uint, false, false); + encrypted_secret_keys = field_encrypted_secret_keys; } cryptonote::account_base account_data_check; r = epee::serialization::load_t_from_binary(account_data_check, account_data); - const cryptonote::account_keys& keys = account_data_check.get_keys(); + if (encrypted_secret_keys) + account_data_check.decrypt_keys(key); + + const cryptonote::account_keys& keys = account_data_check.get_keys(); r = r && hwdev.verify_keys(keys.m_view_secret_key, keys.m_account_address.m_view_public_key); if(!no_spend_key) r = r && hwdev.verify_keys(keys.m_spend_secret_key, keys.m_account_address.m_spend_public_key); return r; } +void wallet2::encrypt_keys(const crypto::chacha_key &key) +{ + m_account.encrypt_keys(key); + m_account.decrypt_viewkey(key); +} + +void wallet2::decrypt_keys(const crypto::chacha_key &key) +{ + m_account.encrypt_viewkey(key); + m_account.decrypt_keys(key); +} + +void wallet2::encrypt_keys(const epee::wipeable_string &password) +{ + crypto::chacha_key key; + crypto::generate_chacha_key(password.data(), password.size(), key, m_kdf_rounds); + encrypt_keys(key); +} + +void wallet2::decrypt_keys(const epee::wipeable_string &password) +{ + crypto::chacha_key key; + crypto::generate_chacha_key(password.data(), password.size(), key, m_kdf_rounds); + decrypt_keys(key); +} + /*! * \brief Generates a wallet or restores one. * \param wallet_ Name of wallet file @@ -3161,7 +3333,7 @@ bool wallet2::verify_password(const std::string& keys_file_name, const epee::wip * \param create_address_file Whether to create an address file */ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& password, - const std::string& multisig_data, bool create_address_file) + const epee::wipeable_string& multisig_data, bool create_address_file) { clear(); prepare_file_names(wallet_); @@ -3217,6 +3389,7 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& for (const auto &msk: multisig_keys) sc_add(skey.bytes, skey.bytes, rct::sk2rct(msk).bytes); THROW_WALLET_EXCEPTION_IF(!(rct::rct2sk(skey) == spend_secret_key), error::invalid_multisig_seed); + memwipe(&skey, sizeof(rct::key)); m_account.make_multisig(view_secret_key, spend_secret_key, spend_public_key, multisig_keys); m_account.finalize_multisig(spend_public_key); @@ -3227,6 +3400,7 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& m_multisig_threshold = threshold; m_multisig_signers = multisig_signers; m_key_on_device = false; + setup_keys(password); if (!wallet_.empty()) { @@ -3281,6 +3455,7 @@ crypto::secret_key wallet2::generate(const std::string& wallet_, const epee::wip m_multisig_threshold = 0; m_multisig_signers.clear(); m_key_on_device = false; + setup_keys(password); // calculate a starting refresh height if(m_refresh_from_block_height == 0 && !recover){ @@ -3382,6 +3557,7 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& m_multisig_threshold = 0; m_multisig_signers.clear(); m_key_on_device = false; + setup_keys(password); if (!wallet_.empty()) { @@ -3435,6 +3611,7 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& m_multisig_threshold = 0; m_multisig_signers.clear(); m_key_on_device = false; + setup_keys(password); if (!wallet_.empty()) { @@ -3481,6 +3658,7 @@ void wallet2::restore(const std::string& wallet_, const epee::wipeable_string& p m_multisig = false; m_multisig_threshold = 0; m_multisig_signers.clear(); + setup_keys(password); if (!wallet_.empty()) { bool r = store_keys(m_keys_file, password, false); @@ -3520,6 +3698,17 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password, clear(); + // decrypt keys + epee::misc_utils::auto_scope_leave_caller keys_reencryptor; + if (m_ask_password && !m_rpc && !m_watch_only) + { + crypto::chacha_key chacha_key; + crypto::generate_chacha_key(password.data(), password.size(), chacha_key, m_kdf_rounds); + m_account.encrypt_viewkey(chacha_key); + m_account.decrypt_keys(chacha_key); + keys_reencryptor = epee::misc_utils::create_scope_leave_handler([&, this, chacha_key]() { m_account.encrypt_keys(chacha_key); m_account.decrypt_viewkey(chacha_key); }); + } + MINFO("Creating spend key..."); std::vector<crypto::secret_key> multisig_keys; rct::key spend_pkey, spend_skey; @@ -3563,6 +3752,7 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password, MINFO("Creating multisig address..."); CHECK_AND_ASSERT_THROW_MES(m_account.make_multisig(view_skey, rct::rct2sk(spend_skey), rct::rct2pk(spend_pkey), multisig_keys), "Failed to create multisig wallet due to bad keys"); + memwipe(&spend_skey, sizeof(rct::key)); m_account_public_address = m_account.get_keys().m_account_address; m_watch_only = false; @@ -3580,6 +3770,9 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password, m_multisig_signers = std::vector<crypto::public_key>(spend_keys.size() + 1, crypto::null_pkey); } + // re-encrypt keys + keys_reencryptor = epee::misc_utils::auto_scope_leave_caller(); + if (!m_wallet_file.empty()) { bool r = store_keys(m_keys_file, password, false); @@ -3662,6 +3855,17 @@ bool wallet2::finalize_multisig(const epee::wipeable_string &password, std::unor { CHECK_AND_ASSERT_THROW_MES(!pkeys.empty(), "empty pkeys"); + // keys are decrypted + epee::misc_utils::auto_scope_leave_caller keys_reencryptor; + if (m_ask_password && !m_rpc && !m_watch_only) + { + crypto::chacha_key chacha_key; + crypto::generate_chacha_key(password.data(), password.size(), chacha_key, m_kdf_rounds); + m_account.encrypt_viewkey(chacha_key); + m_account.decrypt_keys(chacha_key); + keys_reencryptor = epee::misc_utils::create_scope_leave_handler([&, this, chacha_key]() { m_account.encrypt_keys(chacha_key); m_account.decrypt_viewkey(chacha_key); }); + } + // add ours if not included crypto::public_key local_signer; CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(get_account().get_keys().m_spend_secret_key, local_signer), @@ -3684,6 +3888,9 @@ bool wallet2::finalize_multisig(const epee::wipeable_string &password, std::unor m_multisig_signers = signers; std::sort(m_multisig_signers.begin(), m_multisig_signers.end(), [](const crypto::public_key &e0, const crypto::public_key &e1){ return memcmp(&e0, &e1, sizeof(e0)); }); + // keys are encrypted again + keys_reencryptor = epee::misc_utils::auto_scope_leave_caller(); + if (!m_wallet_file.empty()) { bool r = store_keys(m_keys_file, password, false); @@ -3995,6 +4202,11 @@ bool wallet2::generate_chacha_key_from_secret_keys(crypto::chacha_key &key) cons return hwdev.generate_chacha_key(m_account.get_keys(), key, m_kdf_rounds); } //---------------------------------------------------------------------------------------------------- +void wallet2::generate_chacha_key_from_password(const epee::wipeable_string &pass, crypto::chacha_key &key) const +{ + crypto::generate_chacha_key(pass.data(), pass.size(), key, m_kdf_rounds); +} +//---------------------------------------------------------------------------------------------------- void wallet2::load(const std::string& wallet_, const epee::wipeable_string& password) { clear(); @@ -4015,6 +4227,8 @@ void wallet2::load(const std::string& wallet_, const epee::wipeable_string& pass 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 && !m_rpc && !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) @@ -4036,11 +4250,9 @@ void wallet2::load(const std::string& wallet_, const epee::wipeable_string& pass r = ::serialization::parse_binary(buf, cache_file_data); THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "internal error: failed to deserialize \"" + m_wallet_file + '\"'); - crypto::chacha_key key; - generate_chacha_key_from_secret_keys(key); std::string cache_data; cache_data.resize(cache_file_data.cache_data.size()); - crypto::chacha20(cache_file_data.cache_data.data(), cache_file_data.cache_data.size(), key, cache_file_data.iv, &cache_data[0]); + crypto::chacha20(cache_file_data.cache_data.data(), cache_file_data.cache_data.size(), m_cache_key, cache_file_data.iv, &cache_data[0]); try { std::stringstream iss; @@ -4048,11 +4260,13 @@ void wallet2::load(const std::string& wallet_, const epee::wipeable_string& pass boost::archive::portable_binary_iarchive ar(iss); ar >> *this; } - catch (...) + catch(...) { - crypto::chacha8(cache_file_data.cache_data.data(), cache_file_data.cache_data.size(), key, cache_file_data.iv, &cache_data[0]); - try - { + // try with previous scheme: direct from keys + crypto::chacha_key key; + generate_chacha_key_from_secret_keys(key); + crypto::chacha20(cache_file_data.cache_data.data(), cache_file_data.cache_data.size(), key, cache_file_data.iv, &cache_data[0]); + try { std::stringstream iss; iss << cache_data; boost::archive::portable_binary_iarchive ar(iss); @@ -4060,13 +4274,24 @@ 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); - std::stringstream iss; - iss.str(""); - iss << cache_data; - boost::archive::binary_iarchive ar(iss); - ar >> *this; + crypto::chacha8(cache_file_data.cache_data.data(), cache_file_data.cache_data.size(), key, cache_file_data.iv, &cache_data[0]); + try + { + std::stringstream iss; + iss << cache_data; + boost::archive::portable_binary_iarchive ar(iss); + ar >> *this; + } + catch (...) + { + LOG_PRINT_L0("Failed to open portable binary, trying unportable"); + boost::filesystem::copy_file(m_wallet_file, m_wallet_file + ".unportable", boost::filesystem::copy_option::overwrite_if_exists); + std::stringstream iss; + iss.str(""); + iss << cache_data; + boost::archive::binary_iarchive ar(iss); + ar >> *this; + } } } } @@ -4218,12 +4443,10 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas wallet2::cache_file_data cache_file_data = boost::value_initialized<wallet2::cache_file_data>(); cache_file_data.cache_data = oss.str(); - crypto::chacha_key key; - generate_chacha_key_from_secret_keys(key); 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(), key, cache_file_data.iv, &cipher[0]); + 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; const std::string new_file = same_file ? m_wallet_file + ".new" : path; @@ -4681,7 +4904,7 @@ size_t wallet2::pop_best_value(std::vector<size_t> &unused_indices, const std::v // returns: // direct return: amount of money found // modified reference: selected_transfers, a list of iterators/indices of input sources -uint64_t wallet2::select_transfers(uint64_t needed_money, std::vector<size_t> unused_transfers_indices, std::vector<size_t>& selected_transfers, bool trusted_daemon) const +uint64_t wallet2::select_transfers(uint64_t needed_money, std::vector<size_t> unused_transfers_indices, std::vector<size_t>& selected_transfers) const { uint64_t found_money = 0; selected_transfers.reserve(unused_transfers_indices.size()); @@ -4725,17 +4948,17 @@ void wallet2::add_unconfirmed_tx(const cryptonote::transaction& tx, uint64_t amo //---------------------------------------------------------------------------------------------------- void wallet2::transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, const size_t fake_outs_count, const std::vector<size_t> &unused_transfers_indices, - uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx& ptx, bool trusted_daemon) + uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx& ptx) { - transfer(dsts, fake_outs_count, unused_transfers_indices, unlock_time, fee, extra, detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), tx, ptx, trusted_daemon); + transfer(dsts, fake_outs_count, unused_transfers_indices, unlock_time, fee, extra, detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), tx, ptx); } //---------------------------------------------------------------------------------------------------- void wallet2::transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, const size_t fake_outs_count, const std::vector<size_t> &unused_transfers_indices, - uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, bool trusted_daemon) + uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra) { cryptonote::transaction tx; pending_tx ptx; - transfer(dsts, fake_outs_count, unused_transfers_indices, unlock_time, fee, extra, tx, ptx, trusted_daemon); + transfer(dsts, fake_outs_count, unused_transfers_indices, unlock_time, fee, extra, tx, ptx); } namespace { @@ -5721,9 +5944,9 @@ uint32_t wallet2::adjust_priority(uint32_t priority) // // this function will make multiple calls to wallet2::transfer if multiple // transactions will be required -std::vector<wallet2::pending_tx> wallet2::create_transactions(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, bool trusted_daemon) +std::vector<wallet2::pending_tx> wallet2::create_transactions(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra) { - const std::vector<size_t> unused_transfers_indices = select_available_outputs_from_histogram(fake_outs_count + 1, true, true, true, trusted_daemon); + const std::vector<size_t> unused_transfers_indices = select_available_outputs_from_histogram(fake_outs_count + 1, true, true, true); const uint64_t fee_per_kb = get_per_kb_fee(); const uint64_t fee_multiplier = get_fee_multiplier(priority, get_fee_algorithm()); @@ -5757,7 +5980,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions(std::vector<crypto uint64_t needed_fee = calculate_fee(fee_per_kb, estimated_tx_size, fee_multiplier); do { - transfer(dst_vector, fake_outs_count, unused_transfers_indices, unlock_time, needed_fee, extra, tx, ptx, trusted_daemon); + transfer(dst_vector, fake_outs_count, unused_transfers_indices, unlock_time, needed_fee, extra, tx, ptx); auto txBlob = t_serializable_object_to_blob(ptx.tx); needed_fee = calculate_fee(fee_per_kb, txBlob, fee_multiplier); } while (ptx.fee < needed_fee); @@ -5861,12 +6084,6 @@ crypto::chacha_key wallet2::get_ringdb_key() return *m_ringdb_key; } -void wallet2::clear_ringdb_key() -{ - MINFO("clearing ringdb key"); - m_ringdb_key = boost::none; -} - bool wallet2::add_rings(const crypto::chacha_key &key, const cryptonote::transaction_prefix &tx) { if (!m_ringdb) @@ -5877,7 +6094,6 @@ bool wallet2::add_rings(const crypto::chacha_key &key, const cryptonote::transac bool wallet2::add_rings(const cryptonote::transaction_prefix &tx) { - key_ref kref(*this); try { return add_rings(get_ringdb_key(), tx); } catch (const std::exception &e) { return false; } } @@ -5886,7 +6102,6 @@ bool wallet2::remove_rings(const cryptonote::transaction_prefix &tx) { if (!m_ringdb) return false; - key_ref kref(*this); try { return m_ringdb->remove_rings(get_ringdb_key(), tx); } catch (const std::exception &e) { return false; } } @@ -5924,7 +6139,6 @@ bool wallet2::get_rings(const crypto::hash &txid, std::vector<std::pair<crypto:: bool wallet2::get_ring(const crypto::key_image &key_image, std::vector<uint64_t> &outs) { - key_ref kref(*this); try { return get_ring(get_ringdb_key(), key_image, outs); } catch (const std::exception &e) { return false; } } @@ -5934,7 +6148,6 @@ bool wallet2::set_ring(const crypto::key_image &key_image, const std::vector<uin if (!m_ringdb) return false; - key_ref kref(*this); try { return m_ringdb->set_ring(get_ringdb_key(), key_image, outs, relative); } catch (const std::exception &e) { return false; } } @@ -5946,7 +6159,6 @@ bool wallet2::find_and_save_rings(bool force) if (!m_ringdb) return false; - key_ref kref(*this); COMMAND_RPC_GET_TRANSACTIONS::request req = AUTO_VAL_INIT(req); COMMAND_RPC_GET_TRANSACTIONS::response res = AUTO_VAL_INIT(res); @@ -6267,6 +6479,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> req_t.from_height = std::max<uint64_t>(segregation_fork_height, RECENT_OUTPUT_BLOCKS) - RECENT_OUTPUT_BLOCKS; req_t.to_height = segregation_fork_height + 1; req_t.cumulative = true; + req_t.binary = true; m_daemon_rpc_mutex.lock(); bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_output_distribution", req_t, resp_t, m_http_client, rpc_timeout * 1000); m_daemon_rpc_mutex.unlock(); @@ -7742,7 +7955,7 @@ bool wallet2::light_wallet_key_image_is_ours(const crypto::key_image& key_image, // This system allows for sending (almost) the entire balance, since it does // not generate spurious change in all txes, thus decreasing the instantaneous // usable balance. -std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices, bool trusted_daemon) +std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices) { //ensure device is let in NONE mode in any case hw::device &hwdev = m_account.get_device(); @@ -8267,7 +8480,7 @@ skip_tx: return ptx_vector; } -std::vector<wallet2::pending_tx> wallet2::create_transactions_all(uint64_t below, const cryptonote::account_public_address &address, bool is_subaddress, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices, bool trusted_daemon) +std::vector<wallet2::pending_tx> wallet2::create_transactions_all(uint64_t below, const cryptonote::account_public_address &address, bool is_subaddress, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices) { std::vector<size_t> unused_transfers_indices; std::vector<size_t> unused_dust_indices; @@ -8318,10 +8531,10 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_all(uint64_t below } } - return create_transactions_from(address, is_subaddress, unused_transfers_indices, unused_dust_indices, fake_outs_count, unlock_time, priority, extra, trusted_daemon); + return create_transactions_from(address, is_subaddress, unused_transfers_indices, unused_dust_indices, fake_outs_count, unlock_time, priority, extra); } -std::vector<wallet2::pending_tx> wallet2::create_transactions_single(const crypto::key_image &ki, const cryptonote::account_public_address &address, bool is_subaddress, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, bool trusted_daemon) +std::vector<wallet2::pending_tx> wallet2::create_transactions_single(const crypto::key_image &ki, const cryptonote::account_public_address &address, bool is_subaddress, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra) { std::vector<size_t> unused_transfers_indices; std::vector<size_t> unused_dust_indices; @@ -8339,10 +8552,10 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_single(const crypt break; } } - return create_transactions_from(address, is_subaddress, unused_transfers_indices, unused_dust_indices, fake_outs_count, unlock_time, priority, extra, trusted_daemon); + return create_transactions_from(address, is_subaddress, unused_transfers_indices, unused_dust_indices, fake_outs_count, unlock_time, priority, extra); } -std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const cryptonote::account_public_address &address, bool is_subaddress, std::vector<size_t> unused_transfers_indices, std::vector<size_t> unused_dust_indices, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, bool trusted_daemon) +std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const cryptonote::account_public_address &address, bool is_subaddress, std::vector<size_t> unused_transfers_indices, std::vector<size_t> unused_dust_indices, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra) { //ensure device is let in NONE mode in any case hw::device &hwdev = m_account.get_device(); @@ -8525,7 +8738,7 @@ bool wallet2::use_fork_rules(uint8_t version, int64_t early_blocks) const result = m_node_rpc_proxy.get_earliest_height(version, earliest_height); throw_on_rpc_response_error(result, "get_hard_fork_info"); - bool close_enough = height >= earliest_height - early_blocks && earliest_height != std::numeric_limits<uint64_t>::max(); // start using the rules that many blocks beforehand + bool close_enough = height >= earliest_height - early_blocks; // start using the rules that many blocks beforehand if (close_enough) LOG_PRINT_L2("Using v" << (unsigned)version << " rules"); else @@ -8576,12 +8789,12 @@ std::vector<uint64_t> wallet2::get_unspent_amounts_vector() const return vector; } //---------------------------------------------------------------------------------------------------- -std::vector<size_t> wallet2::select_available_outputs_from_histogram(uint64_t count, bool atleast, bool unlocked, bool allow_rct, bool trusted_daemon) +std::vector<size_t> wallet2::select_available_outputs_from_histogram(uint64_t count, bool atleast, bool unlocked, bool allow_rct) { cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::request req_t = AUTO_VAL_INIT(req_t); cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::response resp_t = AUTO_VAL_INIT(resp_t); m_daemon_rpc_mutex.lock(); - if (trusted_daemon) + if (is_trusted_daemon()) req_t.amounts = get_unspent_amounts_vector(); req_t.min_count = count; req_t.max_count = 0; @@ -8642,21 +8855,21 @@ const wallet2::transfer_details &wallet2::get_transfer_details(size_t idx) const return m_transfers[idx]; } //---------------------------------------------------------------------------------------------------- -std::vector<size_t> wallet2::select_available_unmixable_outputs(bool trusted_daemon) +std::vector<size_t> wallet2::select_available_unmixable_outputs() { // request all outputs with less than 3 instances const size_t min_mixin = use_fork_rules(7, 10) ? 6 : use_fork_rules(6, 10) ? 4 : 2; // v6 increases min mixin from 2 to 4, v7 to 6 - return select_available_outputs_from_histogram(min_mixin + 1, false, true, false, trusted_daemon); + return select_available_outputs_from_histogram(min_mixin + 1, false, true, false); } //---------------------------------------------------------------------------------------------------- -std::vector<size_t> wallet2::select_available_mixable_outputs(bool trusted_daemon) +std::vector<size_t> wallet2::select_available_mixable_outputs() { // request all outputs with at least 3 instances, so we can use mixin 2 with const size_t min_mixin = use_fork_rules(7, 10) ? 6 : use_fork_rules(6, 10) ? 4 : 2; // v6 increases min mixin from 2 to 4, v7 to 6 - return select_available_outputs_from_histogram(min_mixin + 1, true, true, true, trusted_daemon); + return select_available_outputs_from_histogram(min_mixin + 1, true, true, true); } //---------------------------------------------------------------------------------------------------- -std::vector<wallet2::pending_tx> wallet2::create_unmixable_sweep_transactions(bool trusted_daemon) +std::vector<wallet2::pending_tx> wallet2::create_unmixable_sweep_transactions() { // From hard fork 1, we don't consider small amounts to be dust anymore const bool hf1_rules = use_fork_rules(2, 10); // first hard fork has version 2 @@ -8665,7 +8878,7 @@ std::vector<wallet2::pending_tx> wallet2::create_unmixable_sweep_transactions(bo const uint64_t fee_per_kb = get_per_kb_fee(); // may throw - std::vector<size_t> unmixable_outputs = select_available_unmixable_outputs(trusted_daemon); + std::vector<size_t> unmixable_outputs = select_available_unmixable_outputs(); size_t num_dust_outputs = unmixable_outputs.size(); if (num_dust_outputs == 0) @@ -8683,13 +8896,13 @@ std::vector<wallet2::pending_tx> wallet2::create_unmixable_sweep_transactions(bo unmixable_transfer_outputs.push_back(n); } - return create_transactions_from(m_account_public_address, false, unmixable_transfer_outputs, unmixable_dust_outputs, 0 /*fake_outs_count */, 0 /* unlock_time */, 1 /*priority */, std::vector<uint8_t>(), trusted_daemon); + return create_transactions_from(m_account_public_address, false, unmixable_transfer_outputs, unmixable_dust_outputs, 0 /*fake_outs_count */, 0 /* unlock_time */, 1 /*priority */, std::vector<uint8_t>()); } //---------------------------------------------------------------------------------------------------- -void wallet2::discard_unmixable_outputs(bool trusted_daemon) +void wallet2::discard_unmixable_outputs() { // may throw - std::vector<size_t> unmixable_outputs = select_available_unmixable_outputs(trusted_daemon); + std::vector<size_t> unmixable_outputs = select_available_unmixable_outputs(); for (size_t idx : unmixable_outputs) { m_transfers[idx].m_spent = true; @@ -10662,6 +10875,7 @@ size_t wallet2::import_multisig(std::vector<cryptonote::blobdata> blobs) m_multisig_rescan_info = &info; try { + refresh(false); } catch (...) {} @@ -10671,14 +10885,14 @@ size_t wallet2::import_multisig(std::vector<cryptonote::blobdata> blobs) return n_outputs; } //---------------------------------------------------------------------------------------------------- -std::string wallet2::encrypt(const std::string &plaintext, const crypto::secret_key &skey, bool authenticated) const +std::string wallet2::encrypt(const char *plaintext, size_t len, const crypto::secret_key &skey, bool authenticated) const { crypto::chacha_key key; crypto::generate_chacha_key(&skey, sizeof(skey), key, m_kdf_rounds); std::string ciphertext; crypto::chacha_iv iv = crypto::rand<crypto::chacha_iv>(); - ciphertext.resize(plaintext.size() + sizeof(iv) + (authenticated ? sizeof(crypto::signature) : 0)); - crypto::chacha20(plaintext.data(), plaintext.size(), key, iv, &ciphertext[sizeof(iv)]); + ciphertext.resize(len + sizeof(iv) + (authenticated ? sizeof(crypto::signature) : 0)); + crypto::chacha20(plaintext, len, key, iv, &ciphertext[sizeof(iv)]); memcpy(&ciphertext[0], &iv, sizeof(iv)); if (authenticated) { @@ -10692,12 +10906,28 @@ std::string wallet2::encrypt(const std::string &plaintext, const crypto::secret_ return ciphertext; } //---------------------------------------------------------------------------------------------------- +std::string wallet2::encrypt(const epee::span<char> &plaintext, const crypto::secret_key &skey, bool authenticated) const +{ + return encrypt(plaintext.data(), plaintext.size(), skey, authenticated); +} +//---------------------------------------------------------------------------------------------------- +std::string wallet2::encrypt(const std::string &plaintext, const crypto::secret_key &skey, bool authenticated) const +{ + return encrypt(plaintext.data(), plaintext.size(), skey, authenticated); +} +//---------------------------------------------------------------------------------------------------- +std::string wallet2::encrypt(const epee::wipeable_string &plaintext, const crypto::secret_key &skey, bool authenticated) const +{ + return encrypt(plaintext.data(), plaintext.size(), skey, authenticated); +} +//---------------------------------------------------------------------------------------------------- std::string wallet2::encrypt_with_view_secret_key(const std::string &plaintext, bool authenticated) const { return encrypt(plaintext, get_account().get_keys().m_view_secret_key, authenticated); } //---------------------------------------------------------------------------------------------------- -std::string wallet2::decrypt(const std::string &ciphertext, const crypto::secret_key &skey, bool authenticated) const +template<typename T> +T wallet2::decrypt(const std::string &ciphertext, const crypto::secret_key &skey, bool authenticated) const { const size_t prefix_size = sizeof(chacha_iv) + (authenticated ? sizeof(crypto::signature) : 0); THROW_WALLET_EXCEPTION_IF(ciphertext.size() < prefix_size, @@ -10706,8 +10936,6 @@ std::string wallet2::decrypt(const std::string &ciphertext, const crypto::secret crypto::chacha_key key; crypto::generate_chacha_key(&skey, sizeof(skey), key, m_kdf_rounds); const crypto::chacha_iv &iv = *(const crypto::chacha_iv*)&ciphertext[0]; - std::string plaintext; - plaintext.resize(ciphertext.size() - prefix_size); if (authenticated) { crypto::hash hash; @@ -10718,10 +10946,14 @@ std::string wallet2::decrypt(const std::string &ciphertext, const crypto::secret THROW_WALLET_EXCEPTION_IF(!crypto::check_signature(hash, pkey, signature), error::wallet_internal_error, "Failed to authenticate ciphertext"); } - crypto::chacha20(ciphertext.data() + sizeof(iv), ciphertext.size() - prefix_size, key, iv, &plaintext[0]); - return plaintext; + std::unique_ptr<char[]> buffer{new char[ciphertext.size() - prefix_size]}; + auto wiper = epee::misc_utils::create_scope_leave_handler([&]() { memwipe(buffer.get(), ciphertext.size() - prefix_size); }); + crypto::chacha20(ciphertext.data() + sizeof(iv), ciphertext.size() - prefix_size, key, iv, buffer.get()); + return T(buffer.get(), ciphertext.size() - prefix_size); } //---------------------------------------------------------------------------------------------------- +template epee::wipeable_string wallet2::decrypt(const std::string &ciphertext, const crypto::secret_key &skey, bool authenticated) const; +//---------------------------------------------------------------------------------------------------- std::string wallet2::decrypt_with_view_secret_key(const std::string &ciphertext, bool authenticated) const { return decrypt(ciphertext, get_account().get_keys().m_view_secret_key, authenticated); @@ -10983,6 +11215,7 @@ std::vector<std::pair<uint64_t, uint64_t>> wallet2::estimate_backlog(const std:: const auto result = m_node_rpc_proxy.get_block_size_limit(block_size_limit); throw_on_rpc_response_error(result, "get_info"); uint64_t full_reward_zone = block_size_limit / 2; + THROW_WALLET_EXCEPTION_IF(full_reward_zone == 0, error::wallet_internal_error, "Invalid block size limit from daemon"); std::vector<std::pair<uint64_t, uint64_t>> blocks; for (const auto &fee_level: fee_levels) diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index d04156461..556679f51 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -67,6 +67,19 @@ class Serialization_portability_wallet_Test; namespace tools { class ringdb; + class wallet2; + + class wallet_keys_unlocker + { + public: + wallet_keys_unlocker(wallet2 &w, const boost::optional<tools::password_container> &password); + wallet_keys_unlocker(wallet2 &w, bool locked, const epee::wipeable_string &password); + ~wallet_keys_unlocker(); + private: + wallet2 &w; + bool locked; + crypto::chacha_key key; + }; class i_wallet2_callback { @@ -77,6 +90,7 @@ namespace tools virtual void on_unconfirmed_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index) {} virtual void on_money_spent(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& in_tx, uint64_t amount, const cryptonote::transaction& spend_tx, const cryptonote::subaddress_index& subaddr_index) {} virtual void on_skip_transaction(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx) {} + virtual boost::optional<epee::wipeable_string> on_get_password(const char *reason) { return boost::none; } // Light wallet callbacks virtual void on_lw_new_block(uint64_t height) {} virtual void on_lw_money_received(uint64_t height, const crypto::hash &txid, uint64_t amount) {} @@ -133,9 +147,11 @@ namespace tools std::deque<crypto::hash> m_blockchain; }; + class wallet_keys_unlocker; class wallet2 { friend class ::Serialization_portability_wallet_Test; + friend class wallet_keys_unlocker; public: static constexpr const std::chrono::seconds rpc_timeout = std::chrono::minutes(3) + std::chrono::seconds(30); @@ -153,17 +169,17 @@ namespace tools static void init_options(boost::program_options::options_description& desc_params); //! Uses stdin and stdout. Returns a wallet2 if no errors. - static std::unique_ptr<wallet2> make_from_json(const boost::program_options::variables_map& vm, const std::string& json_file, const std::function<boost::optional<password_container>(const char *, bool)> &password_prompter); + static std::unique_ptr<wallet2> make_from_json(const boost::program_options::variables_map& vm, bool rpc, const std::string& json_file, const std::function<boost::optional<password_container>(const char *, bool)> &password_prompter); //! Uses stdin and stdout. Returns a wallet2 and password for `wallet_file` if no errors. static std::pair<std::unique_ptr<wallet2>, password_container> - make_from_file(const boost::program_options::variables_map& vm, const std::string& wallet_file, const std::function<boost::optional<password_container>(const char *, bool)> &password_prompter); + make_from_file(const boost::program_options::variables_map& vm, bool rpc, const std::string& wallet_file, const std::function<boost::optional<password_container>(const char *, bool)> &password_prompter); //! Uses stdin and stdout. Returns a wallet2 and password for wallet with no file if no errors. - static std::pair<std::unique_ptr<wallet2>, password_container> make_new(const boost::program_options::variables_map& vm, const std::function<boost::optional<password_container>(const char *, bool)> &password_prompter); + static std::pair<std::unique_ptr<wallet2>, password_container> make_new(const boost::program_options::variables_map& vm, bool rpc, const std::function<boost::optional<password_container>(const char *, bool)> &password_prompter); //! Just parses variables. - static std::unique_ptr<wallet2> make_dummy(const boost::program_options::variables_map& vm, const std::function<boost::optional<password_container>(const char *, bool)> &password_prompter); + static std::unique_ptr<wallet2> make_dummy(const boost::program_options::variables_map& vm, bool rpc, const std::function<boost::optional<password_container>(const char *, bool)> &password_prompter); static bool verify_password(const std::string& keys_file_name, const epee::wipeable_string& password, bool no_spend_key, hw::device &hwdev, uint64_t kdf_rounds); @@ -477,16 +493,6 @@ namespace tools std::vector<is_out_data> additional; }; - struct key_ref - { - key_ref(tools::wallet2 &w): wallet(w) { ++refs; } - ~key_ref() { if (!--refs) wallet.clear_ringdb_key(); } - - private: - tools::wallet2 &wallet; - static std::atomic<unsigned int> refs; - }; - /*! * \brief Generates a wallet or restores one. * \param wallet_ Name of wallet file @@ -495,7 +501,7 @@ namespace tools * \param create_address_file Whether to create an address file */ void generate(const std::string& wallet_, const epee::wipeable_string& password, - const std::string& multisig_data, bool create_address_file = false); + const epee::wipeable_string& multisig_data, bool create_address_file = false); /*! * \brief Generates a wallet or restores one. @@ -613,6 +619,11 @@ namespace tools cryptonote::account_base& get_account(){return m_account;} const cryptonote::account_base& get_account()const{return m_account;} + void encrypt_keys(const crypto::chacha_key &key); + void encrypt_keys(const epee::wipeable_string &password); + void decrypt_keys(const crypto::chacha_key &key); + void decrypt_keys(const epee::wipeable_string &password); + void set_refresh_from_block_height(uint64_t height) {m_refresh_from_block_height = height;} uint64_t get_refresh_from_block_height() const {return m_refresh_from_block_height;} @@ -625,19 +636,22 @@ namespace tools // into account the current median block size rather than // the minimum block size. bool deinit(); - bool init(std::string daemon_address = "http://localhost:8080", - boost::optional<epee::net_utils::http::login> daemon_login = boost::none, uint64_t upper_transaction_size_limit = 0, bool ssl = false); + bool init(bool rpc, std::string daemon_address = "http://localhost:8080", + boost::optional<epee::net_utils::http::login> daemon_login = boost::none, uint64_t upper_transaction_size_limit = 0, bool ssl = false, bool trusted_daemon = false); void stop() { m_run.store(false, std::memory_order_relaxed); } i_wallet2_callback* callback() const { return m_callback; } void callback(i_wallet2_callback* callback) { m_callback = callback; } + bool is_trusted_daemon() const { return m_trusted_daemon; } + void set_trusted_daemon(bool trusted) { m_trusted_daemon = trusted; } + /*! * \brief Checks if deterministic wallet */ bool is_deterministic() const; - bool get_seed(std::string& electrum_words, const epee::wipeable_string &passphrase = epee::wipeable_string()) const; + bool get_seed(epee::wipeable_string& electrum_words, const epee::wipeable_string &passphrase = epee::wipeable_string()) const; /*! * \brief Checks if light wallet. A light wallet sends view key to a server where the blockchain is scanned. @@ -691,7 +705,7 @@ namespace tools bool multisig(bool *ready = NULL, uint32_t *threshold = NULL, uint32_t *total = NULL) const; bool has_multisig_partial_key_images() const; bool has_unknown_key_images() const; - bool get_multisig_seed(std::string& seed, const epee::wipeable_string &passphrase = std::string(), bool raw = true) const; + bool get_multisig_seed(epee::wipeable_string& seed, const epee::wipeable_string &passphrase = std::string(), bool raw = true) const; bool key_on_device() const { return m_key_on_device; } // locked & unlocked balance of given or current subaddress account @@ -704,11 +718,11 @@ namespace tools uint64_t balance_all() const; uint64_t unlocked_balance_all() const; template<typename T> - void transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, const size_t fake_outputs_count, const std::vector<size_t> &unused_transfers_indices, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, bool trusted_daemon); + void transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, const size_t fake_outputs_count, const std::vector<size_t> &unused_transfers_indices, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy); template<typename T> - void transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, const size_t fake_outputs_count, const std::vector<size_t> &unused_transfers_indices, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction& tx, pending_tx& ptx, bool trusted_daemon); - void transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, const size_t fake_outputs_count, const std::vector<size_t> &unused_transfers_indices, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, bool trusted_daemon); - void transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, const size_t fake_outputs_count, const std::vector<size_t> &unused_transfers_indices, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx& ptx, bool trusted_daemon); + void transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, const size_t fake_outputs_count, const std::vector<size_t> &unused_transfers_indices, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction& tx, pending_tx& ptx); + void transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, const size_t fake_outputs_count, const std::vector<size_t> &unused_transfers_indices, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra); + void transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, const size_t fake_outputs_count, const std::vector<size_t> &unused_transfers_indices, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx& ptx); template<typename T> void transfer_selected(const std::vector<cryptonote::tx_destination_entry>& dsts, const std::vector<size_t>& selected_transfers, size_t fake_outputs_count, std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, @@ -737,18 +751,18 @@ namespace tools bool parse_unsigned_tx_from_str(const std::string &unsigned_tx_st, unsigned_tx_set &exported_txs) const; bool load_tx(const std::string &signed_filename, std::vector<tools::wallet2::pending_tx> &ptx, std::function<bool(const signed_tx_set&)> accept_func = NULL); bool parse_tx_from_str(const std::string &signed_tx_st, std::vector<tools::wallet2::pending_tx> &ptx, std::function<bool(const signed_tx_set &)> accept_func); - std::vector<pending_tx> create_transactions(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, bool trusted_daemon); - std::vector<wallet2::pending_tx> create_transactions_2(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices, bool trusted_daemon); // pass subaddr_indices by value on purpose - std::vector<wallet2::pending_tx> create_transactions_all(uint64_t below, const cryptonote::account_public_address &address, bool is_subaddress, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices, bool trusted_daemon); - std::vector<wallet2::pending_tx> create_transactions_single(const crypto::key_image &ki, const cryptonote::account_public_address &address, bool is_subaddress, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, bool trusted_daemon); - std::vector<wallet2::pending_tx> create_transactions_from(const cryptonote::account_public_address &address, bool is_subaddress, std::vector<size_t> unused_transfers_indices, std::vector<size_t> unused_dust_indices, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, bool trusted_daemon); + std::vector<pending_tx> create_transactions(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra); + std::vector<wallet2::pending_tx> create_transactions_2(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices); // pass subaddr_indices by value on purpose + std::vector<wallet2::pending_tx> create_transactions_all(uint64_t below, const cryptonote::account_public_address &address, bool is_subaddress, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices); + std::vector<wallet2::pending_tx> create_transactions_single(const crypto::key_image &ki, const cryptonote::account_public_address &address, bool is_subaddress, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra); + std::vector<wallet2::pending_tx> create_transactions_from(const cryptonote::account_public_address &address, bool is_subaddress, std::vector<size_t> unused_transfers_indices, std::vector<size_t> unused_dust_indices, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra); bool load_multisig_tx(cryptonote::blobdata blob, multisig_tx_set &exported_txs, std::function<bool(const multisig_tx_set&)> accept_func = NULL); bool load_multisig_tx_from_file(const std::string &filename, multisig_tx_set &exported_txs, std::function<bool(const multisig_tx_set&)> accept_func = NULL); bool sign_multisig_tx_from_file(const std::string &filename, std::vector<crypto::hash> &txids, std::function<bool(const multisig_tx_set&)> accept_func); bool sign_multisig_tx(multisig_tx_set &exported_txs, std::vector<crypto::hash> &txids); bool sign_multisig_tx_to_file(multisig_tx_set &exported_txs, const std::string &filename, std::vector<crypto::hash> &txids); - std::vector<pending_tx> create_unmixable_sweep_transactions(bool trusted_daemon); - void discard_unmixable_outputs(bool trusted_daemon); + std::vector<pending_tx> create_unmixable_sweep_transactions(); + void discard_unmixable_outputs(); bool check_connection(uint32_t *version = NULL, uint32_t timeout = 200000); void get_transfers(wallet2::transfer_container& incoming_transfers) const; void get_payments(const crypto::hash& payment_id, std::list<wallet2::payment_details>& payments, uint64_t min_height = 0, const boost::optional<uint32_t>& subaddr_account = boost::none, const std::set<uint32_t>& subaddr_indices = {}) const; @@ -985,10 +999,10 @@ namespace tools */ uint64_t get_approximate_blockchain_height() const; uint64_t estimate_blockchain_height(); - std::vector<size_t> select_available_outputs_from_histogram(uint64_t count, bool atleast, bool unlocked, bool allow_rct, bool trusted_daemon); + std::vector<size_t> select_available_outputs_from_histogram(uint64_t count, bool atleast, bool unlocked, bool allow_rct); std::vector<size_t> select_available_outputs(const std::function<bool(const transfer_details &td)> &f) const; - std::vector<size_t> select_available_unmixable_outputs(bool trusted_daemon); - std::vector<size_t> select_available_mixable_outputs(bool trusted_daemon); + std::vector<size_t> select_available_unmixable_outputs(); + std::vector<size_t> select_available_mixable_outputs(); size_t pop_best_value_from(const transfer_container &transfers, std::vector<size_t> &unused_dust_indices, const std::vector<size_t>& selected_transfers, bool smallest = false) const; size_t pop_best_value(std::vector<size_t> &unused_dust_indices, const std::vector<size_t>& selected_transfers, bool smallest = false) const; @@ -1054,9 +1068,12 @@ namespace tools void update_pool_state(bool refreshed = false); 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; + std::string encrypt(const epee::span<char> &span, const crypto::secret_key &skey, bool authenticated = true) const; std::string encrypt(const std::string &plaintext, const crypto::secret_key &skey, bool authenticated = true) const; + std::string encrypt(const epee::wipeable_string &plaintext, const crypto::secret_key &skey, bool authenticated = true) const; std::string encrypt_with_view_secret_key(const std::string &plaintext, bool authenticated = true) const; - std::string decrypt(const std::string &ciphertext, const crypto::secret_key &skey, bool authenticated = true) const; + template<typename T=std::string> T decrypt(const std::string &ciphertext, const crypto::secret_key &skey, bool authenticated = true) const; std::string decrypt_with_view_secret_key(const std::string &ciphertext, bool authenticated = true) const; std::string make_uri(const std::string &address, const std::string &payment_id, uint64_t amount, const std::string &tx_description, const std::string &recipient_name, std::string &error) const; @@ -1074,6 +1091,8 @@ namespace tools uint64_t adjust_mixin(uint64_t mixin) const; uint32_t adjust_priority(uint32_t priority); + bool is_rpc() const { return m_rpc; } + // Light wallet specific functions // fetch unspent outs from lw node and store in m_transfers void light_wallet_get_unspent_outs(); @@ -1150,6 +1169,9 @@ namespace tools bool lock_keys_file(); bool unlock_keys_file(); bool is_keys_file_locked() const; + + void change_password(const std::string &filename, const epee::wipeable_string &original_password, const epee::wipeable_string &new_password); + private: /*! * \brief Stores wallet information to wallet file. @@ -1176,7 +1198,7 @@ namespace tools void fast_refresh(uint64_t stop_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history, bool force = false); void pull_and_parse_next_blocks(uint64_t start_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history, const std::vector<cryptonote::block_complete_entry> &prev_blocks, const std::vector<parsed_block> &prev_parsed_blocks, std::vector<cryptonote::block_complete_entry> &blocks, std::vector<parsed_block> &parsed_blocks, bool &error); void process_parsed_blocks(uint64_t start_height, const std::vector<cryptonote::block_complete_entry> &blocks, const std::vector<parsed_block> &parsed_blocks, uint64_t& blocks_added); - uint64_t select_transfers(uint64_t needed_money, std::vector<size_t> unused_transfers_indices, std::vector<size_t>& selected_transfers, bool trusted_daemon) const; + uint64_t select_transfers(uint64_t needed_money, std::vector<size_t> unused_transfers_indices, std::vector<size_t>& selected_transfers) const; bool prepare_file_names(const std::string& file_path); void process_unconfirmed(const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t height); void process_outgoing(const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t height, uint64_t ts, uint64_t spent, uint64_t received, uint32_t subaddr_account, const std::set<uint32_t>& subaddr_indices); @@ -1184,6 +1206,7 @@ namespace tools void generate_genesis(cryptonote::block& b) const; void check_genesis(const crypto::hash& genesis_hash) const; //throws bool generate_chacha_key_from_secret_keys(crypto::chacha_key &key) const; + void generate_chacha_key_from_password(const epee::wipeable_string &pass, crypto::chacha_key &key) const; crypto::hash get_payment_id(const pending_tx &ptx) const; void check_acc_out_precomp(const cryptonote::tx_out &o, const crypto::key_derivation &derivation, const std::vector<crypto::key_derivation> &additional_derivations, size_t i, tx_scan_info_t &tx_scan_info) const; void check_acc_out_precomp(const cryptonote::tx_out &o, const crypto::key_derivation &derivation, const std::vector<crypto::key_derivation> &additional_derivations, size_t i, const is_out_data *is_out_data, tx_scan_info_t &tx_scan_info) const; @@ -1201,7 +1224,7 @@ namespace tools crypto::public_key get_tx_pub_key_from_received_outs(const tools::wallet2::transfer_details &td) 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; - void scan_output(const cryptonote::transaction &tx, const crypto::public_key &tx_pub_key, size_t i, tx_scan_info_t &tx_scan_info, int &num_vouts_received, std::unordered_map<cryptonote::subaddress_index, uint64_t> &tx_money_got_in_outs, std::vector<size_t> &outs) const; + void scan_output(const cryptonote::transaction &tx, const crypto::public_key &tx_pub_key, size_t i, tx_scan_info_t &tx_scan_info, int &num_vouts_received, std::unordered_map<cryptonote::subaddress_index, uint64_t> &tx_money_got_in_outs, std::vector<size_t> &outs); void trim_hashchain(); crypto::key_image get_multisig_composite_key_image(size_t n) const; rct::multisig_kLRki get_multisig_composite_kLRki(size_t n, const crypto::public_key &ignore, std::unordered_set<rct::key> &used_L, std::unordered_set<rct::key> &new_used_L) const; @@ -1213,8 +1236,7 @@ namespace tools bool remove_rings(const cryptonote::transaction_prefix &tx); bool get_ring(const crypto::chacha_key &key, const crypto::key_image &key_image, std::vector<uint64_t> &outs); crypto::chacha_key get_ringdb_key(); - void cache_ringdb_key(); - void clear_ringdb_key(); + void setup_keys(const epee::wipeable_string &password); bool get_rct_distribution(uint64_t &start_height, std::vector<uint64_t> &distribution); @@ -1255,6 +1277,7 @@ namespace tools boost::mutex m_daemon_rpc_mutex; + bool m_trusted_daemon; i_wallet2_callback* m_callback; bool m_key_on_device; cryptonote::network_type m_nettype; @@ -1317,6 +1340,11 @@ namespace tools uint64_t m_last_block_reward; std::unique_ptr<tools::file_locker> m_keys_file_locker; + + crypto::chacha_key m_cache_key; + boost::optional<epee::wipeable_string> m_encrypt_keys_after_refresh; + + bool m_rpc; }; } BOOST_CLASS_VERSION(tools::wallet2, 25) @@ -1793,16 +1821,16 @@ namespace tools //---------------------------------------------------------------------------------------------------- template<typename T> void wallet2::transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, const size_t fake_outs_count, const std::vector<size_t> &unused_transfers_indices, - uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, bool trusted_daemon) + uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy) { pending_tx ptx; cryptonote::transaction tx; - transfer(dsts, fake_outs_count, unused_transfers_indices, unlock_time, fee, extra, destination_split_strategy, dust_policy, tx, ptx, trusted_daemon); + transfer(dsts, fake_outs_count, unused_transfers_indices, unlock_time, fee, extra, destination_split_strategy, dust_policy, tx, ptx); } template<typename T> void wallet2::transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, const size_t fake_outputs_count, const std::vector<size_t> &unused_transfers_indices, - uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction& tx, pending_tx &ptx, bool trusted_daemon) + uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction& tx, pending_tx &ptx) { using namespace cryptonote; // throw if attempting a transaction with no destinations @@ -1825,7 +1853,7 @@ namespace tools // randomly select inputs for transaction // throw if requested send amount is greater than (unlocked) amount available to send std::vector<size_t> selected_transfers; - uint64_t found_money = select_transfers(needed_money, unused_transfers_indices, selected_transfers, trusted_daemon); + uint64_t found_money = select_transfers(needed_money, unused_transfers_indices, selected_transfers); THROW_WALLET_EXCEPTION_IF(found_money < needed_money, error::not_enough_unlocked_money, found_money, needed_money - fee, fee); uint32_t subaddr_account = m_transfers[*selected_transfers.begin()].m_subaddr_index.major; diff --git a/src/wallet/wallet_errors.h b/src/wallet/wallet_errors.h index e80652750..243953280 100644 --- a/src/wallet/wallet_errors.h +++ b/src/wallet/wallet_errors.h @@ -53,6 +53,7 @@ namespace tools // wallet_not_initialized // multisig_export_needed // multisig_import_needed + // password_needed // std::logic_error // wallet_logic_error * // file_exists @@ -209,6 +210,14 @@ namespace tools } }; //---------------------------------------------------------------------------------------------------- + struct password_needed : public wallet_runtime_error + { + explicit password_needed(std::string&& loc, const std::string &msg = "Password needed") + : wallet_runtime_error(std::move(loc), msg) + { + } + }; + //---------------------------------------------------------------------------------------------------- const char* const file_error_messages[] = { "file already exists", "file not found", diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index 510cb3e58..67f26c7a7 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -59,7 +59,6 @@ namespace { const command_line::arg_descriptor<std::string, true> arg_rpc_bind_port = {"rpc-bind-port", "Sets bind port for server"}; const command_line::arg_descriptor<bool> arg_disable_rpc_login = {"disable-rpc-login", "Disable HTTP authentication for RPC connections served by this process"}; - const command_line::arg_descriptor<bool> arg_trusted_daemon = {"trusted-daemon", "Enable commands which rely on a trusted daemon", false}; const command_line::arg_descriptor<bool> arg_restricted = {"restricted-rpc", "Restricts to view-only commands", false}; const command_line::arg_descriptor<std::string> arg_wallet_dir = {"wallet-dir", "Directory for newly created wallets"}; const command_line::arg_descriptor<bool> arg_prompt_for_password = {"prompt-for-password", "Prompts for password when not provided", false}; @@ -100,7 +99,7 @@ namespace tools } //------------------------------------------------------------------------------------------------------------------------------ - wallet_rpc_server::wallet_rpc_server():m_wallet(NULL), rpc_login_file(), m_stop(false), m_trusted_daemon(false), m_restricted(false), m_vm(NULL) + wallet_rpc_server::wallet_rpc_server():m_wallet(NULL), rpc_login_file(), m_stop(false), m_restricted(false), m_vm(NULL) { } //------------------------------------------------------------------------------------------------------------------------------ @@ -120,7 +119,7 @@ namespace tools m_stop = false; m_net_server.add_idle_handler([this](){ try { - if (m_wallet) m_wallet->refresh(m_trusted_daemon); + if (m_wallet) m_wallet->refresh(m_wallet->is_trusted_daemon()); } catch (const std::exception& ex) { LOG_ERROR("Exception at while refreshing, what=" << ex.what()); } @@ -163,21 +162,12 @@ namespace tools walvars = m_wallet; else { - tmpwal = tools::wallet2::make_dummy(*m_vm, password_prompter); + tmpwal = tools::wallet2::make_dummy(*m_vm, true, password_prompter); walvars = tmpwal.get(); } boost::optional<epee::net_utils::http::login> http_login{}; std::string bind_port = command_line::get_arg(*m_vm, arg_rpc_bind_port); const bool disable_auth = command_line::get_arg(*m_vm, arg_disable_rpc_login); - m_trusted_daemon = command_line::get_arg(*m_vm, arg_trusted_daemon); - if (!command_line::has_arg(*m_vm, arg_trusted_daemon)) - { - if (tools::is_local_address(walvars->get_daemon_address())) - { - MINFO(tr("Daemon is local, assuming trusted")); - m_trusted_daemon = true; - } - } m_restricted = command_line::get_arg(*m_vm, arg_restricted); if (command_line::has_arg(*m_vm, arg_wallet_dir)) { @@ -857,7 +847,7 @@ namespace tools mixin = m_wallet->adjust_mixin(req.mixin); } uint32_t priority = m_wallet->adjust_priority(req.priority); - std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_2(dsts, mixin, req.unlock_time, priority, extra, req.account_index, req.subaddr_indices, m_trusted_daemon); + std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_2(dsts, mixin, req.unlock_time, priority, extra, req.account_index, req.subaddr_indices); if (ptx_vector.empty()) { @@ -918,7 +908,7 @@ namespace tools } uint32_t priority = m_wallet->adjust_priority(req.priority); LOG_PRINT_L2("on_transfer_split calling create_transactions_2"); - std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_2(dsts, mixin, req.unlock_time, priority, extra, req.account_index, req.subaddr_indices, m_trusted_daemon); + std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_2(dsts, mixin, req.unlock_time, priority, extra, req.account_index, req.subaddr_indices); LOG_PRINT_L2("on_transfer_split called create_transactions_2"); return fill_response(ptx_vector, req.get_tx_keys, res.tx_key_list, res.amount_list, res.fee_list, res.multisig_txset, res.unsigned_txset, req.do_not_relay, @@ -1079,7 +1069,7 @@ namespace tools try { - std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_unmixable_sweep_transactions(m_trusted_daemon); + std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_unmixable_sweep_transactions(); return fill_response(ptx_vector, req.get_tx_keys, res.tx_key_list, res.amount_list, res.fee_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); @@ -1127,7 +1117,7 @@ namespace tools mixin = m_wallet->adjust_mixin(req.mixin); } 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, mixin, req.unlock_time, priority, extra, req.account_index, req.subaddr_indices, m_trusted_daemon); + std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_all(req.below_amount, dsts[0].addr, dsts[0].is_subaddress, mixin, req.unlock_time, priority, extra, req.account_index, req.subaddr_indices); return fill_response(ptx_vector, req.get_tx_keys, res.tx_key_list, res.amount_list, res.fee_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); @@ -1183,7 +1173,7 @@ namespace tools mixin = m_wallet->adjust_mixin(req.mixin); } uint32_t priority = m_wallet->adjust_priority(req.priority); - std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_single(ki, dsts[0].addr, dsts[0].is_subaddress, mixin, req.unlock_time, priority, extra, m_trusted_daemon); + std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_single(ki, dsts[0].addr, dsts[0].is_subaddress, mixin, req.unlock_time, priority, extra); if (ptx_vector.empty()) { @@ -1574,11 +1564,13 @@ namespace tools if (req.key_type.compare("mnemonic") == 0) { - if (!m_wallet->get_seed(res.key)) + epee::wipeable_string seed; + if (!m_wallet->get_seed(seed)) { er.message = "The wallet is non-deterministic. Cannot display seed."; return false; } + res.key = std::string(seed.data(), seed.size()); // send to the network, then wipe RAM :D } else if(req.key_type.compare("view_key") == 0) { @@ -2302,7 +2294,7 @@ namespace tools er.message = "Command unavailable in restricted mode."; return false; } - if (!m_trusted_daemon) + if (!m_wallet->is_trusted_daemon()) { er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; er.message = "This command requires a trusted daemon."; @@ -2505,6 +2497,28 @@ namespace tools return true; } //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_refresh(const wallet_rpc::COMMAND_RPC_REFRESH::request& req, wallet_rpc::COMMAND_RPC_REFRESH::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + if (m_restricted) + { + er.code = WALLET_RPC_ERROR_CODE_DENIED; + er.message = "Command unavailable in restricted mode."; + return false; + } + try + { + m_wallet->refresh(m_wallet->is_trusted_daemon(), req.start_height, res.blocks_fetched, res.received_money); + return true; + } + catch (const std::exception& e) + { + handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR); + return false; + } + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_rescan_spent(const wallet_rpc::COMMAND_RPC_RESCAN_SPENT::request& req, wallet_rpc::COMMAND_RPC_RESCAN_SPENT::response& res, epee::json_rpc::error& er) { if (!m_wallet) return not_open(er); @@ -2530,7 +2544,7 @@ namespace tools bool wallet_rpc_server::on_start_mining(const wallet_rpc::COMMAND_RPC_START_MINING::request& req, wallet_rpc::COMMAND_RPC_START_MINING::response& res, epee::json_rpc::error& er) { if (!m_wallet) return not_open(er); - if (!m_trusted_daemon) + if (!m_wallet->is_trusted_daemon()) { er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; er.message = "This command requires a trusted daemon."; @@ -2636,7 +2650,7 @@ namespace tools command_line::add_arg(desc, arg_password); po::store(po::parse_command_line(argc, argv, desc), vm2); } - std::unique_ptr<tools::wallet2> wal = tools::wallet2::make_new(vm2, nullptr).first; + std::unique_ptr<tools::wallet2> wal = tools::wallet2::make_new(vm2, true, nullptr).first; if (!wal) { er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; @@ -2710,7 +2724,7 @@ namespace tools } std::unique_ptr<tools::wallet2> wal = nullptr; try { - wal = tools::wallet2::make_from_file(vm2, wallet_file, nullptr).first; + wal = tools::wallet2::make_from_file(vm2, true, wallet_file, nullptr).first; } catch (const std::exception& e) { @@ -2728,6 +2742,38 @@ namespace tools return true; } //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_change_wallet_password(const wallet_rpc::COMMAND_RPC_CHANGE_WALLET_PASSWORD::request& req, wallet_rpc::COMMAND_RPC_CHANGE_WALLET_PASSWORD::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + if (m_restricted) + { + er.code = WALLET_RPC_ERROR_CODE_DENIED; + er.message = "Command unavailable in restricted mode."; + return false; + } + if (m_wallet->verify_password(req.old_password)) + { + try + { + m_wallet->rewrite(m_wallet->get_wallet_file(), req.new_password); + m_wallet->store(); + LOG_PRINT_L0("Wallet password changed."); + } + catch (const std::exception& e) + { + handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR); + return false; + } + } + else + { + er.code = WALLET_RPC_ERROR_CODE_INVALID_PASSWORD; + er.message = "Invalid original password."; + return false; + } + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ void wallet_rpc_server::handle_rpc_exception(const std::exception_ptr& e, epee::json_rpc::error& er, int default_error_code) { try { @@ -2969,7 +3015,7 @@ namespace tools return false; } - if (m_trusted_daemon) + if (m_wallet->is_trusted_daemon()) { try { @@ -3192,7 +3238,6 @@ int main(int argc, char** argv) { tools::wallet2::init_options(desc_params); command_line::add_arg(desc_params, arg_rpc_bind_port); command_line::add_arg(desc_params, arg_disable_rpc_login); - command_line::add_arg(desc_params, arg_trusted_daemon); command_line::add_arg(desc_params, arg_restricted); cryptonote::rpc_args::init_options(desc_params); command_line::add_arg(desc_params, arg_wallet_file); @@ -3259,13 +3304,13 @@ int main(int argc, char** argv) { LOG_PRINT_L0(tools::wallet_rpc_server::tr("Loading wallet...")); if(!wallet_file.empty()) { - wal = tools::wallet2::make_from_file(*vm, wallet_file, password_prompt).first; + wal = tools::wallet2::make_from_file(*vm, true, wallet_file, password_prompt).first; } else { try { - wal = tools::wallet2::make_from_json(*vm, from_json, password_prompt); + wal = tools::wallet2::make_from_json(*vm, true, from_json, password_prompt); } catch (const std::exception &e) { @@ -3285,7 +3330,7 @@ int main(int argc, char** argv) { wal->stop(); }); - wal->refresh(command_line::get_arg(*vm, arg_trusted_daemon)); + wal->refresh(wal->is_trusted_daemon()); // if we ^C during potentially length load/refresh, there's no server loop yet if (quit) { diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h index 25eb01ba9..7525b9dd0 100644 --- a/src/wallet/wallet_rpc_server.h +++ b/src/wallet/wallet_rpc_server.h @@ -126,12 +126,14 @@ namespace tools MAP_JON_RPC_WE("get_address_book", on_get_address_book, wallet_rpc::COMMAND_RPC_GET_ADDRESS_BOOK_ENTRY) MAP_JON_RPC_WE("add_address_book", on_add_address_book, wallet_rpc::COMMAND_RPC_ADD_ADDRESS_BOOK_ENTRY) MAP_JON_RPC_WE("delete_address_book",on_delete_address_book,wallet_rpc::COMMAND_RPC_DELETE_ADDRESS_BOOK_ENTRY) + MAP_JON_RPC_WE("refresh", on_refresh, wallet_rpc::COMMAND_RPC_REFRESH) MAP_JON_RPC_WE("rescan_spent", on_rescan_spent, wallet_rpc::COMMAND_RPC_RESCAN_SPENT) MAP_JON_RPC_WE("start_mining", on_start_mining, wallet_rpc::COMMAND_RPC_START_MINING) MAP_JON_RPC_WE("stop_mining", on_stop_mining, wallet_rpc::COMMAND_RPC_STOP_MINING) MAP_JON_RPC_WE("get_languages", on_get_languages, wallet_rpc::COMMAND_RPC_GET_LANGUAGES) MAP_JON_RPC_WE("create_wallet", on_create_wallet, wallet_rpc::COMMAND_RPC_CREATE_WALLET) MAP_JON_RPC_WE("open_wallet", on_open_wallet, wallet_rpc::COMMAND_RPC_OPEN_WALLET) + MAP_JON_RPC_WE("change_wallet_password", on_change_wallet_password, wallet_rpc::COMMAND_RPC_CHANGE_WALLET_PASSWORD) MAP_JON_RPC_WE("is_multisig", on_is_multisig, wallet_rpc::COMMAND_RPC_IS_MULTISIG) MAP_JON_RPC_WE("prepare_multisig", on_prepare_multisig, wallet_rpc::COMMAND_RPC_PREPARE_MULTISIG) MAP_JON_RPC_WE("make_multisig", on_make_multisig, wallet_rpc::COMMAND_RPC_MAKE_MULTISIG) @@ -200,12 +202,14 @@ namespace tools bool on_get_address_book(const wallet_rpc::COMMAND_RPC_GET_ADDRESS_BOOK_ENTRY::request& req, wallet_rpc::COMMAND_RPC_GET_ADDRESS_BOOK_ENTRY::response& res, epee::json_rpc::error& er); bool on_add_address_book(const wallet_rpc::COMMAND_RPC_ADD_ADDRESS_BOOK_ENTRY::request& req, wallet_rpc::COMMAND_RPC_ADD_ADDRESS_BOOK_ENTRY::response& res, epee::json_rpc::error& er); bool on_delete_address_book(const wallet_rpc::COMMAND_RPC_DELETE_ADDRESS_BOOK_ENTRY::request& req, wallet_rpc::COMMAND_RPC_DELETE_ADDRESS_BOOK_ENTRY::response& res, epee::json_rpc::error& er); + bool on_refresh(const wallet_rpc::COMMAND_RPC_REFRESH::request& req, wallet_rpc::COMMAND_RPC_REFRESH::response& res, epee::json_rpc::error& er); bool on_rescan_spent(const wallet_rpc::COMMAND_RPC_RESCAN_SPENT::request& req, wallet_rpc::COMMAND_RPC_RESCAN_SPENT::response& res, epee::json_rpc::error& er); bool on_start_mining(const wallet_rpc::COMMAND_RPC_START_MINING::request& req, wallet_rpc::COMMAND_RPC_START_MINING::response& res, epee::json_rpc::error& er); bool on_stop_mining(const wallet_rpc::COMMAND_RPC_STOP_MINING::request& req, wallet_rpc::COMMAND_RPC_STOP_MINING::response& res, epee::json_rpc::error& er); bool on_get_languages(const wallet_rpc::COMMAND_RPC_GET_LANGUAGES::request& req, wallet_rpc::COMMAND_RPC_GET_LANGUAGES::response& res, epee::json_rpc::error& er); bool on_create_wallet(const wallet_rpc::COMMAND_RPC_CREATE_WALLET::request& req, wallet_rpc::COMMAND_RPC_CREATE_WALLET::response& res, epee::json_rpc::error& er); bool on_open_wallet(const wallet_rpc::COMMAND_RPC_OPEN_WALLET::request& req, wallet_rpc::COMMAND_RPC_OPEN_WALLET::response& res, epee::json_rpc::error& er); + bool on_change_wallet_password(const wallet_rpc::COMMAND_RPC_CHANGE_WALLET_PASSWORD::request& req, wallet_rpc::COMMAND_RPC_CHANGE_WALLET_PASSWORD::response& res, epee::json_rpc::error& er); bool on_is_multisig(const wallet_rpc::COMMAND_RPC_IS_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_IS_MULTISIG::response& res, epee::json_rpc::error& er); bool on_prepare_multisig(const wallet_rpc::COMMAND_RPC_PREPARE_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_PREPARE_MULTISIG::response& res, epee::json_rpc::error& er); bool on_make_multisig(const wallet_rpc::COMMAND_RPC_MAKE_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_MAKE_MULTISIG::response& res, epee::json_rpc::error& er); @@ -236,7 +240,6 @@ namespace tools std::string m_wallet_dir; tools::private_file rpc_login_file; std::atomic<bool> m_stop; - bool m_trusted_daemon; bool m_restricted; const boost::program_options::variables_map *m_vm; }; diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h index 48d881c4c..62fb98ebb 100644 --- a/src/wallet/wallet_rpc_server_commands_defs.h +++ b/src/wallet/wallet_rpc_server_commands_defs.h @@ -47,7 +47,7 @@ // advance which version they will stop working with // Don't go over 32767 for any of these #define WALLET_RPC_VERSION_MAJOR 1 -#define WALLET_RPC_VERSION_MINOR 2 +#define WALLET_RPC_VERSION_MINOR 3 #define MAKE_WALLET_RPC_VERSION(major,minor) (((major)<<16)|(minor)) #define WALLET_RPC_VERSION MAKE_WALLET_RPC_VERSION(WALLET_RPC_VERSION_MAJOR, WALLET_RPC_VERSION_MINOR) namespace tools @@ -445,7 +445,6 @@ namespace wallet_rpc { std::string tx_hash; std::string tx_key; - std::list<std::string> amount_keys; uint64_t amount; uint64_t fee; std::string tx_blob; @@ -456,7 +455,6 @@ namespace wallet_rpc BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(tx_hash) KV_SERIALIZE(tx_key) - KV_SERIALIZE(amount_keys) KV_SERIALIZE(amount) KV_SERIALIZE(fee) KV_SERIALIZE(tx_blob) @@ -1714,6 +1712,29 @@ namespace wallet_rpc }; }; + struct COMMAND_RPC_REFRESH + { + struct request + { + uint64_t start_height; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_OPT(start_height, (uint64_t) 0) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + uint64_t blocks_fetched; + bool received_money; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(blocks_fetched); + KV_SERIALIZE(received_money); + END_KV_SERIALIZE_MAP() + }; + }; + struct COMMAND_RPC_START_MINING { struct request @@ -1808,6 +1829,25 @@ namespace wallet_rpc }; }; + struct COMMAND_RPC_CHANGE_WALLET_PASSWORD + { + struct request + { + std::string old_password; + std::string new_password; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(old_password) + KV_SERIALIZE(new_password) + END_KV_SERIALIZE_MAP() + }; + struct response + { + BEGIN_KV_SERIALIZE_MAP() + END_KV_SERIALIZE_MAP() + }; + }; + struct COMMAND_RPC_IS_MULTISIG { struct request diff --git a/tests/functional_tests/transactions_flow_test.cpp b/tests/functional_tests/transactions_flow_test.cpp index c36c53b89..7248efade 100644 --- a/tests/functional_tests/transactions_flow_test.cpp +++ b/tests/functional_tests/transactions_flow_test.cpp @@ -86,7 +86,7 @@ bool do_send_money(tools::wallet2& w1, tools::wallet2& w2, size_t mix_in_factor, { tools::wallet2::pending_tx ptx; std::vector<size_t> indices = w1.select_available_outputs([](const tools::wallet2::transfer_details&) { return true; }); - w1.transfer(dsts, mix_in_factor, indices, 0, TEST_FEE, std::vector<uint8_t>(), tools::detail::null_split_strategy, tools::tx_dust_policy(TEST_DUST_THRESHOLD), tx, ptx, true); + w1.transfer(dsts, mix_in_factor, indices, 0, TEST_FEE, std::vector<uint8_t>(), tools::detail::null_split_strategy, tools::tx_dust_policy(TEST_DUST_THRESHOLD), tx, ptx); w1.commit_tx(ptx); return true; } @@ -138,7 +138,7 @@ bool transactions_flow_test(std::string& working_folder, return false; } - w1.init(daemon_addr_a); + w1.init(true, daemon_addr_a); uint64_t blocks_fetched = 0; bool received_money; @@ -149,7 +149,7 @@ bool transactions_flow_test(std::string& working_folder, return false; } - w2.init(daemon_addr_b); + w2.init(true, daemon_addr_b); MGINFO_GREEN("Using wallets: " << ENDL << "Source: " << w1.get_account().get_public_address_str(MAINNET) << ENDL << "Path: " << working_folder + "/" + path_source_wallet << ENDL diff --git a/tests/fuzz/signature.cpp b/tests/fuzz/signature.cpp index 7f22757b2..6dadf960d 100644 --- a/tests/fuzz/signature.cpp +++ b/tests/fuzz/signature.cpp @@ -64,6 +64,7 @@ int SignatureFuzzer::init() std::cerr << "failed to parse address" << std::endl; return 1; } + address = info.address; } catch (const std::exception &e) { diff --git a/tests/performance_tests/equality.h b/tests/performance_tests/equality.h new file mode 100644 index 000000000..8d24d7da7 --- /dev/null +++ b/tests/performance_tests/equality.h @@ -0,0 +1,72 @@ +// Copyright (c) 2014-2018, 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. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#pragma once + +#include <string.h> +#include <sodium/crypto_verify_32.h> + +struct memcmp32 +{ + static const size_t loop_count = 1000000000; + static int call(const unsigned char *k0, const unsigned char *k1){ return memcmp(k0, k1, 32); } +}; + +struct verify32 +{ + static const size_t loop_count = 10000000; + static int call(const unsigned char *k0, const unsigned char *k1){ return crypto_verify_32(k0, k1); } +}; + +template<typename f, bool equal> +class test_equality +{ +public: + static const size_t loop_count = f::loop_count; + + bool init() + { + for (int n = 0; n < 32; ++n) + k0[n] = n; + for (int n = 0; n < 32; ++n) + k1[n] = equal ? n : n + 1; + return true; + } + + bool test() + { + return equal == !f::call(k0, k1); + } + +private: + unsigned char k0[32]; + unsigned char k1[32]; +}; + diff --git a/tests/performance_tests/main.cpp b/tests/performance_tests/main.cpp index bc3622ea8..1733e3409 100644 --- a/tests/performance_tests/main.cpp +++ b/tests/performance_tests/main.cpp @@ -51,6 +51,7 @@ #include "sc_reduce32.h" #include "cn_fast_hash.h" #include "rct_mlsag.h" +#include "equality.h" namespace po = boost::program_options; @@ -151,6 +152,11 @@ int main(int argc, char** argv) TEST_PERFORMANCE3(filter, test_ringct_mlsag, 1, 10, true); TEST_PERFORMANCE3(filter, test_ringct_mlsag, 1, 100, true); + TEST_PERFORMANCE2(filter, test_equality, memcmp32, true); + TEST_PERFORMANCE2(filter, test_equality, memcmp32, false); + TEST_PERFORMANCE2(filter, test_equality, verify32, false); + TEST_PERFORMANCE2(filter, test_equality, verify32, false); + std::cout << "Tests finished. Elapsed time: " << timer.elapsed_ms() / 1000 << " sec" << std::endl; return 0; diff --git a/tests/unit_tests/CMakeLists.txt b/tests/unit_tests/CMakeLists.txt index 3c7414640..145e3820e 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 + account.cpp apply_permutation.cpp address_from_url.cpp ban.cpp @@ -47,11 +48,13 @@ set(unit_tests_sources epee_levin_protocol_handler_async.cpp epee_utils.cpp fee.cpp + json_serialization.cpp get_xtype_from_string.cpp hashchain.cpp http.cpp main.cpp memwipe.cpp + mlocker.cpp mnemonics.cpp mul_div.cpp multisig.cpp @@ -72,7 +75,8 @@ set(unit_tests_sources ringct.cpp output_selection.cpp vercmp.cpp - ringdb.cpp) + ringdb.cpp + wipeable_string.cpp) set(unit_tests_headers unit_tests_utils.h) @@ -87,6 +91,7 @@ target_link_libraries(unit_tests cryptonote_core blockchain_db rpc + serialization wallet p2p version diff --git a/tests/unit_tests/account.cpp b/tests/unit_tests/account.cpp new file mode 100644 index 000000000..113622b5e --- /dev/null +++ b/tests/unit_tests/account.cpp @@ -0,0 +1,71 @@ +// Copyright (c) 2014-2018, 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 "cryptonote_basic/account.h" + +TEST(account, encrypt_keys) +{ + cryptonote::keypair recovery_key = cryptonote::keypair::generate(hw::get_device("default")); + cryptonote::account_base account; + crypto::secret_key key = account.generate(recovery_key.sec); + const cryptonote::account_keys keys = account.get_keys(); + + ASSERT_EQ(account.get_keys().m_account_address, keys.m_account_address); + ASSERT_EQ(account.get_keys().m_spend_secret_key, keys.m_spend_secret_key); + ASSERT_EQ(account.get_keys().m_view_secret_key, keys.m_view_secret_key); + ASSERT_EQ(account.get_keys().m_multisig_keys, keys.m_multisig_keys); + + crypto::chacha_key chacha_key; + crypto::generate_chacha_key(&recovery_key, sizeof(recovery_key), chacha_key, 1); + + account.encrypt_keys(chacha_key); + + ASSERT_EQ(account.get_keys().m_account_address, keys.m_account_address); + ASSERT_NE(account.get_keys().m_spend_secret_key, keys.m_spend_secret_key); + ASSERT_NE(account.get_keys().m_view_secret_key, keys.m_view_secret_key); + + account.decrypt_viewkey(chacha_key); + + ASSERT_EQ(account.get_keys().m_account_address, keys.m_account_address); + ASSERT_NE(account.get_keys().m_spend_secret_key, keys.m_spend_secret_key); + ASSERT_EQ(account.get_keys().m_view_secret_key, keys.m_view_secret_key); + + account.encrypt_viewkey(chacha_key); + + ASSERT_EQ(account.get_keys().m_account_address, keys.m_account_address); + ASSERT_NE(account.get_keys().m_spend_secret_key, keys.m_spend_secret_key); + ASSERT_NE(account.get_keys().m_view_secret_key, keys.m_view_secret_key); + + account.decrypt_keys(chacha_key); + + ASSERT_EQ(account.get_keys().m_account_address, keys.m_account_address); + ASSERT_EQ(account.get_keys().m_spend_secret_key, keys.m_spend_secret_key); + ASSERT_EQ(account.get_keys().m_view_secret_key, keys.m_view_secret_key); +} diff --git a/tests/unit_tests/crypto.cpp b/tests/unit_tests/crypto.cpp index 9e1680568..29fa88f9d 100644 --- a/tests/unit_tests/crypto.cpp +++ b/tests/unit_tests/crypto.cpp @@ -81,3 +81,18 @@ TEST(Crypto, null_keys) ASSERT_EQ(memcmp(crypto::null_skey.data, zero, 32), 0); ASSERT_EQ(memcmp(crypto::null_pkey.data, zero, 32), 0); } + +TEST(Crypto, verify_32) +{ + // all bytes are treated the same, so we can brute force just one byte + unsigned char k0[32] = {0}, k1[32] = {0}; + for (unsigned int i0 = 0; i0 < 256; ++i0) + { + k0[0] = i0; + for (unsigned int i1 = 0; i1 < 256; ++i1) + { + k1[0] = i1; + ASSERT_EQ(!crypto_verify_32(k0, k1), i0 == i1); + } + } +} diff --git a/tests/unit_tests/hardfork.cpp b/tests/unit_tests/hardfork.cpp index 930aeb782..8155c65f9 100644 --- a/tests/unit_tests/hardfork.cpp +++ b/tests/unit_tests/hardfork.cpp @@ -93,7 +93,6 @@ public: virtual uint64_t get_num_outputs(const uint64_t& amount) const { return 1; } virtual uint64_t get_indexing_base() const { return 0; } virtual output_data_t get_output_key(const uint64_t& amount, const uint64_t& index) { return output_data_t(); } - virtual output_data_t get_output_key(const uint64_t& global_index) const { return output_data_t(); } virtual tx_out_index get_output_tx_and_index_from_global(const uint64_t& index) const { return tx_out_index(); } virtual tx_out_index get_output_tx_and_index(const uint64_t& amount, const uint64_t& index) const { return tx_out_index(); } virtual void get_output_tx_and_index(const uint64_t& amount, const std::vector<uint64_t> &offsets, std::vector<tx_out_index> &indices) const {} diff --git a/tests/unit_tests/json_serialization.cpp b/tests/unit_tests/json_serialization.cpp new file mode 100644 index 000000000..ac6b60846 --- /dev/null +++ b/tests/unit_tests/json_serialization.cpp @@ -0,0 +1,217 @@ + +#include <boost/optional/optional.hpp> +#include <boost/range/adaptor/indexed.hpp> +#include <gtest/gtest.h> +#include <rapidjson/document.h> +#include <vector> + +#include "crypto/hash.h" +#include "cryptonote_basic/account.h" +#include "cryptonote_basic/cryptonote_basic.h" +#include "cryptonote_basic/cryptonote_format_utils.h" +#include "cryptonote_core/cryptonote_tx_utils.h" +#include "serialization/json_object.h" + + +namespace +{ + cryptonote::transaction + make_miner_transaction(cryptonote::account_public_address const& to) + { + cryptonote::transaction tx{}; + if (!cryptonote::construct_miner_tx(0, 0, 5000, 500, 500, to, tx)) + throw std::runtime_error{"transaction construction error"}; + + crypto::hash id{0}; + if (!cryptonote::get_transaction_hash(tx, id)) + throw std::runtime_error{"could not get transaction hash"}; + + return tx; + } + + cryptonote::transaction + make_transaction( + cryptonote::account_keys const& from, + std::vector<cryptonote::transaction> const& sources, + std::vector<cryptonote::account_public_address> const& destinations, + bool rct, + bool bulletproof) + { + std::uint64_t source_amount = 0; + std::vector<cryptonote::tx_source_entry> actual_sources; + for (auto const& source : sources) + { + std::vector<cryptonote::tx_extra_field> extra_fields; + if (!cryptonote::parse_tx_extra(source.extra, extra_fields)) + throw std::runtime_error{"invalid transaction"}; + + cryptonote::tx_extra_pub_key key_field{}; + if (!cryptonote::find_tx_extra_field_by_type(extra_fields, key_field)) + throw std::runtime_error{"invalid transaction"}; + + for (auto const& input : boost::adaptors::index(source.vout)) + { + source_amount += input.value().amount; + auto const& key = boost::get<cryptonote::txout_to_key>(input.value().target); + + actual_sources.push_back( + {{}, 0, key_field.pub_key, {}, std::size_t(input.index()), input.value().amount, rct, rct::identity()} + ); + + for (unsigned ring = 0; ring < 10; ++ring) + actual_sources.back().push_output(input.index(), key.key, input.value().amount); + } + } + + std::vector<cryptonote::tx_destination_entry> to; + for (auto const& destination : destinations) + to.push_back({(source_amount / destinations.size()), destination, false}); + + cryptonote::transaction tx{}; + + crypto::secret_key tx_key{}; + std::vector<crypto::secret_key> extra_keys{}; + + std::unordered_map<crypto::public_key, cryptonote::subaddress_index> subaddresses; + subaddresses[from.m_account_address.m_spend_public_key] = {0,0}; + + if (!cryptonote::construct_tx_and_get_tx_key(from, subaddresses, actual_sources, to, boost::none, {}, tx, 0, tx_key, extra_keys, rct, bulletproof)) + throw std::runtime_error{"transaction construction error"}; + + return tx; + } +} // anonymous + +TEST(JsonSerialization, MinerTransaction) +{ + cryptonote::account_base acct; + acct.generate(); + const auto miner_tx = make_miner_transaction(acct.get_keys().m_account_address); + + crypto::hash tx_hash{}; + ASSERT_TRUE(cryptonote::get_transaction_hash(miner_tx, tx_hash)); + + rapidjson::Document doc; + cryptonote::json::toJsonValue(doc, miner_tx, doc); + + cryptonote::transaction miner_tx_copy; + cryptonote::json::fromJsonValue(doc, miner_tx_copy); + + crypto::hash tx_copy_hash{}; + ASSERT_TRUE(cryptonote::get_transaction_hash(miner_tx_copy, tx_copy_hash)); + EXPECT_EQ(tx_hash, tx_copy_hash); + + cryptonote::blobdata tx_bytes{}; + cryptonote::blobdata tx_copy_bytes{}; + + ASSERT_TRUE(cryptonote::t_serializable_object_to_blob(miner_tx, tx_bytes)); + ASSERT_TRUE(cryptonote::t_serializable_object_to_blob(miner_tx_copy, tx_copy_bytes)); + + EXPECT_EQ(tx_bytes, tx_copy_bytes); +} + +TEST(JsonSerialization, RegularTransaction) +{ + cryptonote::account_base acct1; + acct1.generate(); + + cryptonote::account_base acct2; + acct2.generate(); + + const auto miner_tx = make_miner_transaction(acct1.get_keys().m_account_address); + const auto tx = make_transaction( + acct1.get_keys(), {miner_tx}, {acct2.get_keys().m_account_address}, false, false + ); + + crypto::hash tx_hash{}; + ASSERT_TRUE(cryptonote::get_transaction_hash(tx, tx_hash)); + + rapidjson::Document doc; + cryptonote::json::toJsonValue(doc, tx, doc); + + cryptonote::transaction tx_copy; + cryptonote::json::fromJsonValue(doc, tx_copy); + + crypto::hash tx_copy_hash{}; + ASSERT_TRUE(cryptonote::get_transaction_hash(tx_copy, tx_copy_hash)); + EXPECT_EQ(tx_hash, tx_copy_hash); + + cryptonote::blobdata tx_bytes{}; + cryptonote::blobdata tx_copy_bytes{}; + + ASSERT_TRUE(cryptonote::t_serializable_object_to_blob(tx, tx_bytes)); + ASSERT_TRUE(cryptonote::t_serializable_object_to_blob(tx_copy, tx_copy_bytes)); + + EXPECT_EQ(tx_bytes, tx_copy_bytes); +} + +TEST(JsonSerialization, RingctTransaction) +{ + cryptonote::account_base acct1; + acct1.generate(); + + cryptonote::account_base acct2; + acct2.generate(); + + const auto miner_tx = make_miner_transaction(acct1.get_keys().m_account_address); + const auto tx = make_transaction( + acct1.get_keys(), {miner_tx}, {acct2.get_keys().m_account_address}, true, false + ); + + crypto::hash tx_hash{}; + ASSERT_TRUE(cryptonote::get_transaction_hash(tx, tx_hash)); + + rapidjson::Document doc; + cryptonote::json::toJsonValue(doc, tx, doc); + + cryptonote::transaction tx_copy; + cryptonote::json::fromJsonValue(doc, tx_copy); + + crypto::hash tx_copy_hash{}; + ASSERT_TRUE(cryptonote::get_transaction_hash(tx_copy, tx_copy_hash)); + EXPECT_EQ(tx_hash, tx_copy_hash); + + cryptonote::blobdata tx_bytes{}; + cryptonote::blobdata tx_copy_bytes{}; + + ASSERT_TRUE(cryptonote::t_serializable_object_to_blob(tx, tx_bytes)); + ASSERT_TRUE(cryptonote::t_serializable_object_to_blob(tx_copy, tx_copy_bytes)); + + EXPECT_EQ(tx_bytes, tx_copy_bytes); +} + +TEST(JsonSerialization, BulletproofTransaction) +{ + cryptonote::account_base acct1; + acct1.generate(); + + cryptonote::account_base acct2; + acct2.generate(); + + const auto miner_tx = make_miner_transaction(acct1.get_keys().m_account_address); + const auto tx = make_transaction( + acct1.get_keys(), {miner_tx}, {acct2.get_keys().m_account_address}, true, true + ); + + crypto::hash tx_hash{}; + ASSERT_TRUE(cryptonote::get_transaction_hash(tx, tx_hash)); + + rapidjson::Document doc; + cryptonote::json::toJsonValue(doc, tx, doc); + + cryptonote::transaction tx_copy; + cryptonote::json::fromJsonValue(doc, tx_copy); + + crypto::hash tx_copy_hash{}; + ASSERT_TRUE(cryptonote::get_transaction_hash(tx_copy, tx_copy_hash)); + EXPECT_EQ(tx_hash, tx_copy_hash); + + cryptonote::blobdata tx_bytes{}; + cryptonote::blobdata tx_copy_bytes{}; + + ASSERT_TRUE(cryptonote::t_serializable_object_to_blob(tx, tx_bytes)); + ASSERT_TRUE(cryptonote::t_serializable_object_to_blob(tx_copy, tx_copy_bytes)); + + EXPECT_EQ(tx_bytes, tx_copy_bytes); +} + diff --git a/tests/unit_tests/mlocker.cpp b/tests/unit_tests/mlocker.cpp new file mode 100644 index 000000000..6e6048c6c --- /dev/null +++ b/tests/unit_tests/mlocker.cpp @@ -0,0 +1,186 @@ +// Copyright (c) 2018, 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 "misc_log_ex.h" +#include "mlocker.h" + +#define BASE(data) (char*)(((uintptr_t)(data.get() + page_size - 1)) / page_size * page_size) + +TEST(mlocker, distinct_1) +{ + const size_t page_size = epee::mlocker::get_page_size(); + ASSERT_TRUE(page_size > 0); + const size_t base_pages = epee::mlocker::get_num_locked_pages(); + const size_t base_objects = epee::mlocker::get_num_locked_objects(); + std::unique_ptr<char[]> data{new char[8 * page_size]}; + epee::mlocker *m0 = new epee::mlocker(BASE(data), 1); + epee::mlocker *m1 = new epee::mlocker(BASE(data) + 2 * page_size, 1); + epee::mlocker *m2 = new epee::mlocker(BASE(data) + 3 * page_size, 1); + ASSERT_EQ(epee::mlocker::get_num_locked_pages(), base_pages + 3); + ASSERT_EQ(epee::mlocker::get_num_locked_objects(), base_objects + 3); + delete m0; + delete m1; + delete m2; + ASSERT_EQ(epee::mlocker::get_num_locked_pages(), base_pages + 0); + ASSERT_EQ(epee::mlocker::get_num_locked_objects(), base_objects + 0); +} + +TEST(mlocker, distinct_full_page) +{ + const size_t page_size = epee::mlocker::get_page_size(); + ASSERT_TRUE(page_size > 0); + const size_t base_pages = epee::mlocker::get_num_locked_pages(); + const size_t base_objects = epee::mlocker::get_num_locked_objects(); + std::unique_ptr<char[]> data{new char[8 * page_size]}; + epee::mlocker *m0 = new epee::mlocker(BASE(data), page_size); + epee::mlocker *m1 = new epee::mlocker(BASE(data) + 2 * page_size, page_size); + epee::mlocker *m2 = new epee::mlocker(BASE(data) + 3 * page_size, page_size); + ASSERT_EQ(epee::mlocker::get_num_locked_pages(), base_pages + 3); + ASSERT_EQ(epee::mlocker::get_num_locked_objects(), base_objects + 3); + delete m0; + delete m1; + delete m2; + ASSERT_EQ(epee::mlocker::get_num_locked_pages(), base_pages + 0); + ASSERT_EQ(epee::mlocker::get_num_locked_objects(), base_objects + 0); +} + +TEST(mlocker, identical) +{ + const size_t page_size = epee::mlocker::get_page_size(); + ASSERT_TRUE(page_size >= 32); + const size_t base_pages = epee::mlocker::get_num_locked_pages(); + const size_t base_objects = epee::mlocker::get_num_locked_objects(); + std::unique_ptr<char[]> data{new char[8 * page_size]}; + epee::mlocker *m0 = new epee::mlocker(BASE(data) + page_size, 32); + epee::mlocker *m1 = new epee::mlocker(BASE(data) + page_size, 32); + epee::mlocker *m2 = new epee::mlocker(BASE(data) + page_size, 32); + ASSERT_EQ(epee::mlocker::get_num_locked_pages(), base_pages + 1); + ASSERT_EQ(epee::mlocker::get_num_locked_objects(), base_objects + 3); + delete m1; + ASSERT_EQ(epee::mlocker::get_num_locked_pages(), base_pages + 1); + ASSERT_EQ(epee::mlocker::get_num_locked_objects(), base_objects + 2); + delete m0; + delete m2; + ASSERT_EQ(epee::mlocker::get_num_locked_pages(), base_pages + 0); + ASSERT_EQ(epee::mlocker::get_num_locked_objects(), base_objects + 0); +} + +TEST(mlocker, overlapping_small) +{ + const size_t page_size = epee::mlocker::get_page_size(); + ASSERT_TRUE(page_size >= 64); + const size_t base_pages = epee::mlocker::get_num_locked_pages(); + const size_t base_objects = epee::mlocker::get_num_locked_objects(); + std::unique_ptr<char[]> data{new char[8 * page_size]}; + epee::mlocker *m0 = new epee::mlocker(BASE(data), 32); + epee::mlocker *m1 = new epee::mlocker(BASE(data) + 16, 32); + epee::mlocker *m2 = new epee::mlocker(BASE(data) + 8, 32); + ASSERT_EQ(epee::mlocker::get_num_locked_pages(), base_pages + 1); + ASSERT_EQ(epee::mlocker::get_num_locked_objects(), base_objects + 3); + delete m1; + ASSERT_EQ(epee::mlocker::get_num_locked_pages(), base_pages + 1); + ASSERT_EQ(epee::mlocker::get_num_locked_objects(), base_objects + 2); + delete m2; + delete m0; + ASSERT_EQ(epee::mlocker::get_num_locked_pages(), base_pages + 0); + ASSERT_EQ(epee::mlocker::get_num_locked_objects(), base_objects + 0); +} + +TEST(mlocker, multi_page) +{ + const size_t page_size = epee::mlocker::get_page_size(); + ASSERT_TRUE(page_size > 0); + const size_t base_pages = epee::mlocker::get_num_locked_pages(); + const size_t base_objects = epee::mlocker::get_num_locked_objects(); + std::unique_ptr<char[]> data{new char[8 * page_size]}; + epee::mlocker *m0 = new epee::mlocker(BASE(data) + page_size, page_size * 3); + ASSERT_EQ(epee::mlocker::get_num_locked_pages(), base_pages + 3); + ASSERT_EQ(epee::mlocker::get_num_locked_objects(), base_objects + 1); + epee::mlocker *m1 = new epee::mlocker(BASE(data) + page_size * 7, page_size); + ASSERT_EQ(epee::mlocker::get_num_locked_pages(), base_pages + 4); + ASSERT_EQ(epee::mlocker::get_num_locked_objects(), base_objects + 2); + delete m0; + ASSERT_EQ(epee::mlocker::get_num_locked_pages(), base_pages + 1); + ASSERT_EQ(epee::mlocker::get_num_locked_objects(), base_objects + 1); + delete m1; + ASSERT_EQ(epee::mlocker::get_num_locked_pages(), base_pages + 0); + ASSERT_EQ(epee::mlocker::get_num_locked_objects(), base_objects + 0); +} + +TEST(mlocker, cross_page) +{ + const size_t page_size = epee::mlocker::get_page_size(); + ASSERT_TRUE(page_size > 32); + const size_t base_pages = epee::mlocker::get_num_locked_pages(); + const size_t base_objects = epee::mlocker::get_num_locked_objects(); + std::unique_ptr<char[]> data{new char[2 * page_size]}; + epee::mlocker *m0 = new epee::mlocker(BASE(data) + page_size - 1, 2); + ASSERT_EQ(epee::mlocker::get_num_locked_pages(), base_pages + 2); + ASSERT_EQ(epee::mlocker::get_num_locked_objects(), base_objects + 1); + delete m0; + ASSERT_EQ(epee::mlocker::get_num_locked_pages(), base_pages + 0); + ASSERT_EQ(epee::mlocker::get_num_locked_objects(), base_objects + 0); +} + +TEST(mlocker, redundant) +{ + const size_t page_size = epee::mlocker::get_page_size(); + const size_t base_pages = epee::mlocker::get_num_locked_pages(); + const size_t base_objects = epee::mlocker::get_num_locked_objects(); + std::unique_ptr<char[]> data{new char[2 * page_size]}; + ASSERT_EQ(epee::mlocker::get_num_locked_pages(), base_pages + 0); + ASSERT_EQ(epee::mlocker::get_num_locked_objects(), base_objects + 0); + epee::mlocker *m0 = new epee::mlocker(BASE(data), 32); + ASSERT_EQ(epee::mlocker::get_num_locked_pages(), base_pages + 1); + ASSERT_EQ(epee::mlocker::get_num_locked_objects(), base_objects + 1); + epee::mlocker *m1 = new epee::mlocker(BASE(data), 32); + ASSERT_EQ(epee::mlocker::get_num_locked_pages(), base_pages + 1); + ASSERT_EQ(epee::mlocker::get_num_locked_objects(), base_objects + 2); + delete m1; + ASSERT_EQ(epee::mlocker::get_num_locked_pages(), base_pages + 1); + ASSERT_EQ(epee::mlocker::get_num_locked_objects(), base_objects + 1); + delete m0; + ASSERT_EQ(epee::mlocker::get_num_locked_pages(), base_pages + 0); + ASSERT_EQ(epee::mlocker::get_num_locked_objects(), base_objects + 0); +} + +TEST(mlocker, mlocked) +{ + const size_t base_pages = epee::mlocker::get_num_locked_pages(); + const size_t base_objects = epee::mlocker::get_num_locked_objects(); + { + struct Foo { uint64_t u; }; + epee::mlocked<Foo> l; + ASSERT_EQ(epee::mlocker::get_num_locked_pages(), base_pages + 1); + ASSERT_EQ(epee::mlocker::get_num_locked_objects(), base_objects + 1); + } + ASSERT_EQ(epee::mlocker::get_num_locked_pages(), base_pages + 0); + ASSERT_EQ(epee::mlocker::get_num_locked_objects(), base_objects + 0); +} diff --git a/tests/unit_tests/mnemonics.cpp b/tests/unit_tests/mnemonics.cpp index 8fa3192b9..0b74a6b94 100644 --- a/tests/unit_tests/mnemonics.cpp +++ b/tests/unit_tests/mnemonics.cpp @@ -27,6 +27,8 @@ // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "gtest/gtest.h" +#include "wipeable_string.h" +#include "mnemonics/language_base.h" #include "mnemonics/electrum-words.h" #include "crypto/crypto.h" #include <stdlib.h> @@ -74,14 +76,16 @@ namespace void test_language(const Language::Base &language) { const std::vector<std::string> &word_list = language.get_word_list(); - std::string seed = "", return_seed = ""; + epee::wipeable_string w_seed = "", w_return_seed = ""; + std::string seed, return_seed; // Generate a random seed without checksum crypto::secret_key randkey; for (size_t ii = 0; ii < sizeof(randkey); ++ii) { randkey.data[ii] = rand(); } - crypto::ElectrumWords::bytes_to_words(randkey, seed, language.get_language_name()); + crypto::ElectrumWords::bytes_to_words(randkey, w_seed, language.get_language_name()); + seed = std::string(w_seed.data(), w_seed.size()); // remove the checksum word const char *space = strrchr(seed.c_str(), ' '); ASSERT_TRUE(space != NULL); @@ -103,7 +107,8 @@ namespace ASSERT_STREQ(language.get_language_name().c_str(), language_name.c_str()); // Convert the secret key back to seed - crypto::ElectrumWords::bytes_to_words(key, return_seed, language.get_language_name()); + crypto::ElectrumWords::bytes_to_words(key, w_return_seed, language.get_language_name()); + return_seed = std::string(w_return_seed.data(), w_return_seed.size()); ASSERT_EQ(true, res); std::cout << "Returned seed:\n"; std::cout << return_seed << std::endl; @@ -126,8 +131,9 @@ namespace std::cout << "Detected language: " << language_name << std::endl; ASSERT_STREQ(language.get_language_name().c_str(), language_name.c_str()); - return_seed = ""; - crypto::ElectrumWords::bytes_to_words(key, return_seed, language.get_language_name()); + w_return_seed = ""; + crypto::ElectrumWords::bytes_to_words(key, w_return_seed, language.get_language_name()); + return_seed = std::string(w_return_seed.data(), w_return_seed.size()); ASSERT_EQ(true, res); std::cout << "Returned seed:\n"; std::cout << return_seed << std::endl; @@ -202,3 +208,17 @@ TEST(mnemonics, language_detection_with_bad_checksum) ASSERT_EQ(true, res); ASSERT_STREQ(language_name.c_str(), "Português"); } + +TEST(mnemonics, utf8prefix) +{ + ASSERT_TRUE(Language::utf8prefix(epee::wipeable_string("foo"), 0) == ""); + ASSERT_TRUE(Language::utf8prefix(epee::wipeable_string("foo"), 1) == "f"); + ASSERT_TRUE(Language::utf8prefix(epee::wipeable_string("foo"), 2) == "fo"); + ASSERT_TRUE(Language::utf8prefix(epee::wipeable_string("foo"), 3) == "foo"); + ASSERT_TRUE(Language::utf8prefix(epee::wipeable_string("foo"), 4) == "foo"); + ASSERT_TRUE(Language::utf8prefix(epee::wipeable_string("æon"), 0) == ""); + ASSERT_TRUE(Language::utf8prefix(epee::wipeable_string("æon"), 1) == "æ"); + ASSERT_TRUE(Language::utf8prefix(epee::wipeable_string("æon"), 2) == "æo"); + ASSERT_TRUE(Language::utf8prefix(epee::wipeable_string("æon"), 3) == "æon"); + ASSERT_TRUE(Language::utf8prefix(epee::wipeable_string("æon"), 4) == "æon"); +} diff --git a/tests/unit_tests/multisig.cpp b/tests/unit_tests/multisig.cpp index 922299333..eb453b960 100644 --- a/tests/unit_tests/multisig.cpp +++ b/tests/unit_tests/multisig.cpp @@ -61,10 +61,13 @@ static void make_wallet(unsigned int idx, tools::wallet2 &wallet) try { - wallet.init(""); + wallet.init(false, ""); wallet.set_subaddress_lookahead(1, 1); wallet.generate("", "", spendkey, true, false); ASSERT_TRUE(test_addresses[idx].address == wallet.get_account().get_public_address_str(cryptonote::TESTNET)); + wallet.decrypt_keys(""); + ASSERT_TRUE(test_addresses[idx].spendkey == epee::string_tools::pod_to_hex(wallet.get_account().get_keys().m_spend_secret_key)); + wallet.encrypt_keys(""); } catch (const std::exception &e) { @@ -83,8 +86,12 @@ static void make_M_2_wallet(tools::wallet2 &wallet0, tools::wallet2 &wallet1, un std::vector<crypto::secret_key> sk0(1), sk1(1); std::vector<crypto::public_key> pk0(1), pk1(1); + wallet0.decrypt_keys(""); std::string mi0 = wallet0.get_multisig_info(); + wallet0.encrypt_keys(""); + wallet1.decrypt_keys(""); std::string mi1 = wallet1.get_multisig_info(); + wallet1.encrypt_keys(""); ASSERT_TRUE(tools::wallet2::verify_multisig_info(mi1, sk0[0], pk0[0])); ASSERT_TRUE(tools::wallet2::verify_multisig_info(mi0, sk1[0], pk1[0])); @@ -118,9 +125,15 @@ static void make_M_3_wallet(tools::wallet2 &wallet0, tools::wallet2 &wallet1, to std::vector<crypto::secret_key> sk0(2), sk1(2), sk2(2); std::vector<crypto::public_key> pk0(2), pk1(2), pk2(2); + wallet0.decrypt_keys(""); std::string mi0 = wallet0.get_multisig_info(); + wallet0.encrypt_keys(""); + wallet1.decrypt_keys(""); std::string mi1 = wallet1.get_multisig_info(); + wallet1.encrypt_keys(""); + wallet2.decrypt_keys(""); std::string mi2 = wallet2.get_multisig_info(); + wallet2.encrypt_keys(""); ASSERT_TRUE(tools::wallet2::verify_multisig_info(mi1, sk0[0], pk0[0])); ASSERT_TRUE(tools::wallet2::verify_multisig_info(mi2, sk0[1], pk0[1])); diff --git a/tests/unit_tests/ringdb.cpp b/tests/unit_tests/ringdb.cpp index 8b0ea10d4..9b842569a 100644 --- a/tests/unit_tests/ringdb.cpp +++ b/tests/unit_tests/ringdb.cpp @@ -47,7 +47,7 @@ static crypto::chacha_key generate_chacha_key() { crypto::chacha_key chacha_key; uint64_t password = crypto::rand<uint64_t>(); - crypto::generate_chacha_key(std::string((const char*)&password, sizeof(password)), chacha_key); + crypto::generate_chacha_key(std::string((const char*)&password, sizeof(password)), chacha_key, 1); return chacha_key; } diff --git a/tests/unit_tests/serialization.cpp b/tests/unit_tests/serialization.cpp index 5bec280b1..2f7b5aac7 100644 --- a/tests/unit_tests/serialization.cpp +++ b/tests/unit_tests/serialization.cpp @@ -824,7 +824,7 @@ TEST(Serialization, portability_outputs) return {}; } crypto::chacha8(ciphertext.data() + sizeof(iv), ciphertext.size() - prefix_size, key, iv, &plaintext[0]); - return std::move(plaintext); + return plaintext; }; crypto::secret_key view_secret_key; epee::string_tools::hex_to_pod("339673bb1187e2f73ba7841ab6841c5553f96e9f13f8fe6612e69318db4e9d0a", view_secret_key); diff --git a/tests/unit_tests/wipeable_string.cpp b/tests/unit_tests/wipeable_string.cpp new file mode 100644 index 000000000..5ea1c1729 --- /dev/null +++ b/tests/unit_tests/wipeable_string.cpp @@ -0,0 +1,204 @@ +// Copyright (c) 2018, 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 <boost/optional/optional.hpp> +#include <string.h> +#include "gtest/gtest.h" + +#include "misc_log_ex.h" +#include "wipeable_string.h" + +TEST(wipeable_string, ctor) +{ + epee::wipeable_string s0; + ASSERT_EQ(s0.size(), 0); + + epee::wipeable_string s1(std::string("foo")); + ASSERT_EQ(s1.size(), 3); + ASSERT_TRUE(!memcmp(s1.data(), "foo", s1.size())); + + epee::wipeable_string s2(std::string("bar")); + ASSERT_EQ(s2.size(), 3); + ASSERT_TRUE(!memcmp(s2.data(), "bar", s2.size())); + + epee::wipeable_string s3(std::string("quux")); + ASSERT_EQ(s3.size(), 4); + ASSERT_TRUE(!memcmp(s3.data(), "quux", s3.size())); +} + +TEST(wipeable_string, wipe) +{ + epee::wipeable_string s0(std::string("foo")); + ASSERT_EQ(s0.size(), 3); + s0.wipe(); + ASSERT_EQ(s0.size(), 3); + ASSERT_TRUE(!memcmp(s0.data(), "\0\0\0", 3)); +} + +TEST(wipeable_string, clear) +{ + epee::wipeable_string s0(std::string("foo")); + ASSERT_EQ(s0.size(), 3); + s0.clear(); + ASSERT_EQ(s0.size(), 0); +} + +TEST(wipeable_string, push_back) +{ + epee::wipeable_string s0(std::string("fo")); + ASSERT_EQ(s0.size(), 2); + s0.push_back('o'); + ASSERT_EQ(s0.size(), 3); + ASSERT_TRUE(!memcmp(s0.data(), "foo", s0.size())); +} + +TEST(wipeable_string, append_char) +{ + epee::wipeable_string s0(std::string("fo")); + ASSERT_EQ(s0.size(), 2); + s0 += 'o'; + ASSERT_EQ(s0.size(), 3); + ASSERT_TRUE(!memcmp(s0.data(), "foo", s0.size())); +} + +TEST(wipeable_string, append_string) +{ + epee::wipeable_string s0(std::string("foo")); + ASSERT_EQ(s0.size(), 3); + s0 += "bar"; + ASSERT_EQ(s0.size(), 6); + ASSERT_TRUE(!memcmp(s0.data(), "foobar", s0.size())); +} + +TEST(wipeable_string, empty) +{ + epee::wipeable_string s0; + ASSERT_TRUE(s0.empty()); + s0.push_back(' '); + ASSERT_FALSE(s0.empty()); + ASSERT_EQ(s0.pop_back(), ' '); + ASSERT_TRUE(s0.empty()); +} + +TEST(wipeable_string, pop_back) +{ + epee::wipeable_string s = "test"; + ASSERT_EQ(s.size(), 4); + ASSERT_EQ(s.pop_back(), 't'); + ASSERT_EQ(s.size(), 3); + ASSERT_TRUE(!memcmp(s.data(), "tes", s.size())); +} + +TEST(wipeable_string, equal) +{ + epee::wipeable_string s0 = "foo"; + epee::wipeable_string s1 = "bar"; + epee::wipeable_string s0_2 = "foo"; + ASSERT_TRUE(s0 == s0); + ASSERT_TRUE(s0 == s0_2); + ASSERT_TRUE(s1 == s1); + ASSERT_FALSE(s1 == s0); + ASSERT_FALSE(s1 == s0_2); +} + +TEST(wipeable_string, not_equal) +{ + epee::wipeable_string s0 = "foo"; + epee::wipeable_string s1 = "bar"; + epee::wipeable_string s0_2 = "foo"; + ASSERT_FALSE(s0 != s0); + ASSERT_FALSE(s0 != s0_2); + ASSERT_FALSE(s1 != s1); + ASSERT_TRUE(s1 != s0); + ASSERT_TRUE(s1 != s0_2); +} + +static epee::wipeable_string trimmed(const char *s) +{ + epee::wipeable_string str(s); + str.trim(); + return str; +} + +TEST(wipeable_string, trim) +{ + ASSERT_TRUE(trimmed("") == ""); + ASSERT_TRUE(trimmed(" ") == ""); + ASSERT_TRUE(trimmed(" ") == ""); + ASSERT_TRUE(trimmed("a") == "a"); + ASSERT_TRUE(trimmed(" a") == "a"); + ASSERT_TRUE(trimmed(" a") == "a"); + ASSERT_TRUE(trimmed("a ") == "a"); + ASSERT_TRUE(trimmed("a ") == "a"); + ASSERT_TRUE(trimmed(" a ") == "a"); + ASSERT_TRUE(trimmed(" a ") == "a"); + ASSERT_TRUE(trimmed(" ab ") == "ab"); + ASSERT_TRUE(trimmed(" a b ") == "a b"); + ASSERT_TRUE(trimmed(" a b ") == "a b"); +} + +static bool check_split(const char *s, const std::vector<epee::wipeable_string> &v) +{ + epee::wipeable_string str(s); + std::vector<epee::wipeable_string> fields; + str.split(fields); + return v == fields; +} + +TEST(wipeable_string, split) +{ + ASSERT_TRUE(check_split("", {})); + ASSERT_TRUE(check_split("foo", {"foo"})); + ASSERT_TRUE(check_split(" foo ", {"foo"})); + ASSERT_TRUE(check_split("foo bar", {"foo", "bar"})); + ASSERT_TRUE(check_split("foo bar", {"foo", "bar"})); + ASSERT_TRUE(check_split("foo bar baz", {"foo", "bar", "baz"})); + ASSERT_TRUE(check_split(" foo bar baz ", {"foo", "bar", "baz"})); + ASSERT_TRUE(check_split(" foo bar baz", {"foo", "bar", "baz"})); + ASSERT_TRUE(check_split("foo bar baz ", {"foo", "bar", "baz"})); +} + +TEST(wipeable_string, parse_hexstr) +{ + boost::optional<epee::wipeable_string> s; + + ASSERT_EQ(boost::none, epee::wipeable_string("x").parse_hexstr()); + ASSERT_EQ(boost::none, epee::wipeable_string("x0000000000000000").parse_hexstr()); + ASSERT_EQ(boost::none, epee::wipeable_string("0000000000000000x").parse_hexstr()); + ASSERT_EQ(boost::none, epee::wipeable_string("0").parse_hexstr()); + ASSERT_EQ(boost::none, epee::wipeable_string("000").parse_hexstr()); + + ASSERT_TRUE((s = epee::wipeable_string("").parse_hexstr())); + ASSERT_EQ(*s, ""); + ASSERT_TRUE((s = epee::wipeable_string("00").parse_hexstr())); + ASSERT_EQ(*s, epee::wipeable_string("", 1)); + ASSERT_TRUE((s = epee::wipeable_string("41").parse_hexstr())); + ASSERT_EQ(*s, epee::wipeable_string("A")); + ASSERT_TRUE((s = epee::wipeable_string("414243").parse_hexstr())); + ASSERT_EQ(*s, epee::wipeable_string("ABC")); +} |