diff options
Diffstat (limited to 'src')
44 files changed, 1660 insertions, 313 deletions
diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp index 9e22e2e4b..b0f3ca5f0 100644 --- a/src/blockchain_db/lmdb/db_lmdb.cpp +++ b/src/blockchain_db/lmdb/db_lmdb.cpp @@ -3391,8 +3391,10 @@ bool BlockchainLMDB::get_output_distribution(uint64_t amount, uint64_t from_heig break; } + distribution[0] += base; for (size_t n = 1; n < distribution.size(); ++n) distribution[n] += distribution[n - 1]; + base = 0; TXN_POSTFIX_RDONLY(); diff --git a/src/blocks/checkpoints.dat b/src/blocks/checkpoints.dat Binary files differindex 085558504..03e2cd2a8 100644 --- a/src/blocks/checkpoints.dat +++ b/src/blocks/checkpoints.dat diff --git a/src/checkpoints/checkpoints.cpp b/src/checkpoints/checkpoints.cpp index ef1ee171d..6251fcc91 100644 --- a/src/checkpoints/checkpoints.cpp +++ b/src/checkpoints/checkpoints.cpp @@ -165,6 +165,7 @@ namespace cryptonote { ADD_CHECKPOINT(0, "48ca7cd3c8de5b6a4d53d2861fbdaedca141553559f9be9520068053cda8430b"); ADD_CHECKPOINT(1000000, "46b690b710a07ea051bc4a6b6842ac37be691089c0f7758cfeec4d5fc0b4a258"); + ADD_CHECKPOINT(1058600, "12904f6b4d9e60fd875674e07147d2c83d6716253f046af7b894c3e81da7e1bd"); return true; } if (nettype == STAGENET) @@ -208,7 +209,7 @@ namespace cryptonote ADD_CHECKPOINT(1450000, "ac94e8860093bc7c83e4e91215cba1d663421ecf4067a0ae609c3a8b52bcfac2"); ADD_CHECKPOINT(1530000, "01759bce497ec38e63c78b1038892169203bb78f87e488172f6b854fcd63ba7e"); ADD_CHECKPOINT(1579000, "7d0d7a2346373afd41ed1e744a939fc5d474a7dbaa257be5c6fff4009e789241"); - + ADD_CHECKPOINT(1668900, "ac2dcaf3d2f58ffcf8391639f0f1ebafcb8eac43c49479c7c37f611868d07568"); return true; } diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index c6bac2199..e89dbbc24 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -33,6 +33,8 @@ set(common_sources command_line.cpp dns_utils.cpp download.cpp + error.cpp + expect.cpp util.cpp i18n.cpp password.cpp @@ -55,6 +57,8 @@ set(common_private_headers common_fwd.h dns_utils.h download.h + error.h + expect.h http_connection.h int-util.h pod-class.h diff --git a/src/common/error.cpp b/src/common/error.cpp new file mode 100644 index 000000000..e091e4478 --- /dev/null +++ b/src/common/error.cpp @@ -0,0 +1,75 @@ +// 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 "error.h" + +#include <string> + +namespace +{ + struct category final : std::error_category + { + virtual const char* name() const noexcept override final + { + return "common_category()"; + } + + virtual std::string message(int value) const override final + { + switch (common_error(value)) + { + case common_error::kInvalidArgument: + return make_error_code(std::errc::invalid_argument).message(); + case common_error::kInvalidErrorCode: + return "expect<T> was given an error value of zero"; + default: + break; + } + return "Unknown basic_category() value"; + } + + virtual std::error_condition default_error_condition(int value) const noexcept override final + { + // maps specific errors to generic `std::errc` cases. + switch (common_error(value)) + { + case common_error::kInvalidArgument: + case common_error::kInvalidErrorCode: + return std::errc::invalid_argument; + default: + break; + } + return std::error_condition{value, *this}; + } + }; +} + +std::error_category const& common_category() noexcept +{ + static const category instance{}; + return instance; +} + diff --git a/src/common/error.h b/src/common/error.h new file mode 100644 index 000000000..6fef3eb4b --- /dev/null +++ b/src/common/error.h @@ -0,0 +1,52 @@ +// 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 <system_error> +#include <type_traits> + +enum class common_error : int +{ + // 0 is reserved for no error, as per expect<T> + kInvalidArgument = 1, //!< A function argument is invalid + kInvalidErrorCode //!< Default `std::error_code` given to `expect<T>` +}; + +std::error_category const& common_category() noexcept; + +inline std::error_code make_error_code(::common_error value) noexcept +{ + return std::error_code{int(value), common_category()}; +} + +namespace std +{ + template<> + struct is_error_code_enum<::common_error> + : true_type + {}; +} diff --git a/src/common/expect.cpp b/src/common/expect.cpp new file mode 100644 index 000000000..c86e23e95 --- /dev/null +++ b/src/common/expect.cpp @@ -0,0 +1,70 @@ +// +// 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 "expect.h" + +#include <easylogging++.h> +#include <string> + +namespace detail +{ + namespace + { + std::string generate_error(const char* msg, const char* file, unsigned line) + { + std::string error_msg{}; + if (msg) + { + error_msg.append(msg); + if (file) + error_msg.append(" ("); + } + if (file) + { + error_msg.append("thrown at "); + + // remove path, get just filename + extension + char buff[256] = {0}; + el::base::utils::File::buildBaseFilename(file, buff, sizeof(buff) - 1); + error_msg.append(buff); + + error_msg.push_back(':'); + error_msg.append(std::to_string(line)); + } + if (msg && file) + error_msg.push_back(')'); + return error_msg; + } + } + + void expect::throw_(std::error_code ec, const char* msg, const char* file, unsigned line) + { + if (msg || file) + throw std::system_error{ec, generate_error(msg, file, line)}; + throw std::system_error{ec}; + } +} // detail diff --git a/src/common/expect.h b/src/common/expect.h new file mode 100644 index 000000000..326242502 --- /dev/null +++ b/src/common/expect.h @@ -0,0 +1,447 @@ +// 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 <cassert> +#include <system_error> +#include <type_traits> +#include <utility> + +#include "common/error.h" + +//! If precondition fails, return `::error::kInvalidArgument` in current scope. +#define MONERO_PRECOND(...) \ + do \ + { \ + if (!( __VA_ARGS__ )) \ + return {::common_error::kInvalidArgument}; \ + } while (0) + +//! Check `expect<void>` and return errors in current scope. +#define MONERO_CHECK(...) \ + do \ + { \ + const ::expect<void> result = __VA_ARGS__ ; \ + if (!result) \ + return result.error(); \ + } while (0) + +/*! Get `T` from `expect<T>` by `std::move` as-if by function call. + `expect<void>` returns nothing. + + \throw std::system_error with `expect<T>::error()`, filename and line + number when `expect<T>::has_error() == true`.*/ +#define MONERO_UNWRAP(...) \ + ::detail::expect::unwrap( __VA_ARGS__ , nullptr, __FILE__ , __LINE__ ) + +/* \throw std::system_error with `code` and `msg` as part of the details. The +filename and line number will automatically be injected into the explanation +string. `code` can be any enum convertible to `std::error_code`. */ +#define MONERO_THROW(code, msg) \ + ::detail::expect::throw_( code , msg , __FILE__ , __LINE__ ) + + +template<typename> class expect; + +namespace detail +{ + // Shortens the characters in the places that `enable_if` is used below. + template<bool C> + using enable_if = typename std::enable_if<C>::type; + + struct expect + { + //! \throw std::system_error with `ec`, optional `msg` and/or optional `file` + `line`. + static void throw_(std::error_code ec, const char* msg, const char* file, unsigned line); + + //! If `result.has_error()` call `throw_`. Otherwise, \return `*result` by move. + template<typename T> + static T unwrap(::expect<T>&& result, const char* error_msg, const char* file, unsigned line) + { + if (!result) + throw_(result.error(), error_msg, file, line); + return std::move(*result); + } + + //! If `result.has_error()` call `throw_`. + static void unwrap(::expect<void>&& result, const char* error_msg, const char* file, unsigned line); + }; +} + +/*! + `expect<T>` is a value or error implementation, similar to Rust std::result + or various C++ proposals (boost::expected, boost::outcome). This + implementation currently has a strict error type, `std::error_code`, and a + templated value type `T`. `expect<T>` is implicitly convertible from `T` + or `std::error_code`, and one `expect<T>` object type is implicitly + convertible to another `expect<U>` object iff the destination value type + can be implicitly constructed from the source value type (i.e. + `struct U { ... U(T src) { ...} ... };`). + + `operator==` and `operator!=` are the only comparison operators provided; + comparison between different value types is allowed provided the two values + types have a `operator==` defined between them (i.e. + `assert(expect<int>{100} == expect<short>{100});`). Comparisons can also be + done against `std::error_code` objects or error code enums directly (i.e. + `assert(expect<int>{make_error_code(common_error::kInvalidArgument)} == error::kInvalidArgument)`). + Comparison of default constructed `std::error_code` will always fail. + "Generic" comparisons can be done with `std::error_condition` via the `matches` + method only (i.e. + `assert(expect<int>{make_error_code{common_error::kInvalidErrorCode}.matches(std::errc::invalid_argument))`), + `operator==` and `operator!=` will not work with `std::errc` or + `std::error_condition`. A comparison with `matches` is more expensive + because an equivalency between error categories is computed, but is + recommended when an error can be one of several categories (this is going + to be the case in nearly every situation when calling a function from + another C++ struct/class). + + `expect<void>` is a special case with no stored value. It is used by + functions that can fail, but otherwise would return `void`. It is useful + for consistency; all macros, standalone functions, and comparison operators + work with `expect<void>`. + + \note See `src/common/error.h` for creating a custom error enum. + */ +template<typename T> +class expect +{ + static_assert(std::is_nothrow_destructible<T>(), "T must have a nothrow destructor"); + + template<typename U> + static constexpr bool is_convertible() noexcept + { + return std::is_constructible<T, U>() && + std::is_convertible<U, T>(); + } + + // MEMBERS + std::error_code code_; + typename std::aligned_storage<sizeof(T), alignof(T)>::type storage_; + // MEMBERS + + T& get() noexcept + { + assert(has_value()); + return *reinterpret_cast<T*>(std::addressof(storage_)); + } + + T const& get() const noexcept + { + assert(has_value()); + return *reinterpret_cast<T const*>(std::addressof(storage_)); + } + + template<typename U> + void store(U&& value) noexcept(std::is_nothrow_constructible<T, U>()) + { + new (std::addressof(storage_)) T{std::forward<U>(value)}; + code_ = std::error_code{}; + } + + void maybe_throw() const + { + if (has_error()) + ::detail::expect::throw_(error(), nullptr, nullptr, 0); + } + +public: + using value_type = T; + using error_type = std::error_code; + + expect() = delete; + + /*! Store an error, `code`, in the `expect` object. If `code` creates a + `std::error_code` object whose `.value() == 0`, then `error()` will be set + to `::common_error::kInvalidErrorCode`. */ + expect(std::error_code const& code) noexcept + : code_(code), storage_() + { + if (!has_error()) + code_ = ::common_error::kInvalidErrorCode; + } + + //! Store a value, `val`, in the `expect` object. + expect(T val) noexcept(std::is_nothrow_move_constructible<T>()) + : code_(), storage_() + { + store(std::move(val)); + } + + expect(expect const& src) noexcept(std::is_nothrow_copy_constructible<T>()) + : code_(src.error()), storage_() + { + if (src.has_value()) + store(src.get()); + } + + //! Copy conversion from `U` to `T`. + template<typename U, typename = detail::enable_if<is_convertible<U const&>()>> + expect(expect<U> const& src) noexcept(std::is_nothrow_constructible<T, U const&>()) + : code_(src.error()), storage_() + { + if (src.has_value()) + store(*src); + } + + expect(expect&& src) noexcept(std::is_nothrow_move_constructible<T>()) + : code_(src.error()), storage_() + { + if (src.has_value()) + store(std::move(src.get())); + } + + //! Move conversion from `U` to `T`. + template<typename U, typename = detail::enable_if<is_convertible<U>()>> + expect(expect<U>&& src) noexcept(std::is_nothrow_constructible<T, U>()) + : code_(src.error()), storage_() + { + if (src.has_value()) + store(std::move(*src)); + } + + ~expect() noexcept + { + if (has_value()) + get().~T(); + } + + expect& operator=(expect const& src) noexcept(std::is_nothrow_copy_constructible<T>() && std::is_nothrow_copy_assignable<T>()) + { + if (this != std::addressof(src)) + { + if (has_value() && src.has_value()) + get() = src.get(); + else if (has_value()) + get().~T(); + else if (src.has_value()) + store(src.get()); + code_ = src.error(); + } + return *this; + } + + /*! Move `src` into `this`. If `src.has_value() && addressof(src) != this` + then `src.value() will be in a "moved from state". */ + expect& operator=(expect&& src) noexcept(std::is_nothrow_move_constructible<T>() && std::is_nothrow_move_assignable<T>()) + { + if (this != std::addressof(src)) + { + if (has_value() && src.has_value()) + get() = std::move(src.get()); + else if (has_value()) + get().~T(); + else if (src.has_value()) + store(std::move(src.get())); + code_ = src.error(); + } + return *this; + } + + //! \return True if `this` is storing a value instead of an error. + explicit operator bool() const noexcept { return has_value(); } + + //! \return True if `this` is storing an error instead of a value. + bool has_error() const noexcept { return bool(code_); } + + //! \return True if `this` is storing a value instead of an error. + bool has_value() const noexcept { return !has_error(); } + + //! \return Error - always safe to call. Empty when `!has_error()`. + std::error_code error() const noexcept { return code_; } + + //! \return Value if `has_value()` otherwise \throw `std::system_error{error()}`. + T& value() & + { + maybe_throw(); + return get(); + } + + //! \return Value if `has_value()` otherwise \throw `std::system_error{error()}`. + T const& value() const & + { + maybe_throw(); + return get(); + } + + /*! Same as other overloads, but expressions such as `foo(bar().value())` + will automatically perform moves with no copies. */ + T&& value() && + { + maybe_throw(); + return std::move(get()); + } + + //! \return Value, \pre `has_value()`. + T* operator->() noexcept { return std::addressof(get()); } + //! \return Value, \pre `has_value()`. + T const* operator->() const noexcept { return std::addressof(get()); } + //! \return Value, \pre `has_value()`. + T& operator*() noexcept { return get(); } + //! \return Value, \pre `has_value()`. + T const& operator*() const noexcept { return get(); } + + /*! + \note This function is `noexcept` when `U == T` is `noexcept`. + \return True if `has_value() == rhs.has_value()` and if values or errors are equal. + */ + template<typename U> + bool equal(expect<U> const& rhs) const noexcept(noexcept(*std::declval<expect<T>>() == *rhs)) + { + return has_value() && rhs.has_value() ? + get() == *rhs : error() == rhs.error(); + } + + //! \return False if `has_value()`, otherwise `error() == rhs`. + bool equal(std::error_code const& rhs) const noexcept + { + return has_error() && error() == rhs; + } + + /*! + \note This function is `noexcept` when `U == T` is `noexcept`. + \return False if `has_error()`, otherwise `value() == rhs`. + */ + template<typename U, typename = detail::enable_if<!std::is_constructible<std::error_code, U>::value>> + bool equal(U const& rhs) const noexcept(noexcept(*std::declval<expect<T>>() == rhs)) + { + return has_value() && get() == rhs; + } + + //! \return False if `has_value()`, otherwise `error() == rhs`. + bool matches(std::error_condition const& rhs) const noexcept + { + return has_error() && error() == rhs; + } +}; + +template<> +class expect<void> +{ + std::error_code code_; + +public: + using value_type = void; + using error_type = std::error_code; + + //! Create a successful object. + expect() = default; + + expect(std::error_code const& code) noexcept + : code_(code) + { + if (!has_error()) + code_ = ::common_error::kInvalidErrorCode; + } + + expect(expect const&) = default; + ~expect() = default; + expect& operator=(expect const&) = default; + + //! \return True if `this` is storing a value instead of an error. + explicit operator bool() const noexcept { return !has_error(); } + + //! \return True if `this` is storing an error instead of a value. + bool has_error() const noexcept { return bool(code_); } + + //! \return Error - alway + std::error_code error() const noexcept { return code_; } + + //! \return `error() == rhs.error()`. + bool equal(expect const& rhs) const noexcept + { + return error() == rhs.error(); + } + + //! \return `has_error() && error() == rhs`. + bool equal(std::error_code const& rhs) const noexcept + { + return has_error() && error() == rhs; + } + + //! \return False if `has_value()`, otherwise `error() == rhs`. + bool matches(std::error_condition const& rhs) const noexcept + { + return has_error() && error() == rhs; + } +}; + +//! \return An `expect<void>` object with `!has_error()`. +inline expect<void> success() noexcept { return expect<void>{}; } + +template<typename T, typename U> +inline +bool operator==(expect<T> const& lhs, expect<U> const& rhs) noexcept(noexcept(lhs.equal(rhs))) +{ + return lhs.equal(rhs); +} + +template<typename T, typename U> +inline +bool operator==(expect<T> const& lhs, U const& rhs) noexcept(noexcept(lhs.equal(rhs))) +{ + return lhs.equal(rhs); +} + +template<typename T, typename U> +inline +bool operator==(T const& lhs, expect<U> const& rhs) noexcept(noexcept(rhs.equal(lhs))) +{ + return rhs.equal(lhs); +} + +template<typename T, typename U> +inline +bool operator!=(expect<T> const& lhs, expect<U> const& rhs) noexcept(noexcept(lhs.equal(rhs))) +{ + return !lhs.equal(rhs); +} + +template<typename T, typename U> +inline +bool operator!=(expect<T> const& lhs, U const& rhs) noexcept(noexcept(lhs.equal(rhs))) +{ + return !lhs.equal(rhs); +} + +template<typename T, typename U> +inline +bool operator!=(T const& lhs, expect<U> const& rhs) noexcept(noexcept(rhs.equal(lhs))) +{ + return !rhs.equal(lhs); +} + +namespace detail +{ + inline void expect::unwrap(::expect<void>&& result, const char* error_msg, const char* file, unsigned line) + { + if (!result) + throw_(result.error(), error_msg, file, line); + } +} + diff --git a/src/crypto/keccak.c b/src/crypto/keccak.c index 8fcd2138e..b5946036e 100644 --- a/src/crypto/keccak.c +++ b/src/crypto/keccak.c @@ -5,6 +5,7 @@ #include <stdio.h> #include <stdlib.h> #include <unistd.h> +#include "common/int-util.h" #include "hash-ops.h" #include "keccak.h" @@ -105,7 +106,7 @@ void keccak(const uint8_t *in, size_t inlen, uint8_t *md, int mdlen) for ( ; inlen >= rsiz; inlen -= rsiz, in += rsiz) { for (i = 0; i < rsizw; i++) - st[i] ^= ((uint64_t *) in)[i]; + st[i] ^= swap64le(((uint64_t *) in)[i]); keccakf(st, KECCAK_ROUNDS); } @@ -121,11 +122,15 @@ void keccak(const uint8_t *in, size_t inlen, uint8_t *md, int mdlen) temp[rsiz - 1] |= 0x80; for (i = 0; i < rsizw; i++) - st[i] ^= ((uint64_t *) temp)[i]; + st[i] ^= swap64le(((uint64_t *) temp)[i]); keccakf(st, KECCAK_ROUNDS); - memcpy(md, st, mdlen); + if (((size_t)mdlen % sizeof(uint64_t)) != 0) + { + local_abort("Bad keccak use"); + } + memcpy_swap64le(md, st, mdlen/sizeof(uint64_t)); } void keccak1600(const uint8_t *in, size_t inlen, uint8_t *md) diff --git a/src/crypto/slow-hash.c b/src/crypto/slow-hash.c index 914ba6dc0..80bf4e06f 100644 --- a/src/crypto/slow-hash.c +++ b/src/crypto/slow-hash.c @@ -198,6 +198,22 @@ extern int aesb_pseudo_round(const uint8_t *in, uint8_t *out, const uint8_t *exp } while (0) #endif +#define VARIANT2_2_PORTABLE() \ + if (variant >= 2) { \ + xor_blocks(long_state + (j ^ 0x10), d); \ + xor_blocks(d, long_state + (j ^ 0x20)); \ + } + +#define VARIANT2_2() \ + do if (variant >= 2) \ + { \ + *U64(hp_state + (j ^ 0x10)) ^= hi; \ + *(U64(hp_state + (j ^ 0x10)) + 1) ^= lo; \ + hi ^= *U64(hp_state + (j ^ 0x20)); \ + lo ^= *(U64(hp_state + (j ^ 0x20)) + 1); \ + } while (0) + + #if !defined NO_AES && (defined(__x86_64__) || (defined(_MSC_VER) && defined(_WIN64))) // Optimised code below, uses x86-specific intrinsics, SSE2, AES-NI // Fall back to more portable code is down at the bottom @@ -282,6 +298,7 @@ extern int aesb_pseudo_round(const uint8_t *in, uint8_t *out, const uint8_t *exp b[0] = p[0]; b[1] = p[1]; \ VARIANT2_INTEGER_MATH_SSE2(b, c); \ __mul(); \ + VARIANT2_2(); \ VARIANT2_SHUFFLE_ADD_SSE2(hp_state, j); \ a[0] += hi; a[1] += lo; \ p = U64(&hp_state[j]); \ @@ -884,6 +901,7 @@ union cn_slow_hash_state b[0] = p[0]; b[1] = p[1]; \ VARIANT2_PORTABLE_INTEGER_MATH(b, c); \ __mul(); \ + VARIANT2_2(); \ VARIANT2_SHUFFLE_ADD_NEON(hp_state, j); \ a[0] += hi; a[1] += lo; \ p = U64(&hp_state[j]); \ @@ -1305,6 +1323,7 @@ void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int VARIANT2_PORTABLE_INTEGER_MATH(c, c1); mul(c1, c, d); + VARIANT2_2_PORTABLE(); VARIANT2_PORTABLE_SHUFFLE_ADD(long_state, j); sum_half_blocks(a, d); swap_blocks(a, c); @@ -1486,6 +1505,7 @@ void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int copy_block(c2, &long_state[j]); VARIANT2_PORTABLE_INTEGER_MATH(c2, c1); mul(c1, c2, d); + VARIANT2_2_PORTABLE(); VARIANT2_PORTABLE_SHUFFLE_ADD(long_state, j); swap_blocks(a, c1); sum_half_blocks(c1, d); diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 3751d6473..0e175cdbe 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -158,6 +158,8 @@ static const struct { { 5, 35000, 0, 1521360000 }, { 6, 36000, 0, 1521480000 }, { 7, 37000, 0, 1521600000 }, + { 8, 176456, 0, 1537821770 }, + { 9, 177176, 0, 1537821771 }, }; //------------------------------------------------------------------ @@ -1818,15 +1820,10 @@ bool Blockchain::get_output_distribution(uint64_t amount, uint64_t from_height, { std::vector<uint64_t> heights; heights.reserve(to_height + 1 - start_height); - uint64_t real_start_height = start_height > 0 ? start_height-1 : start_height; - for (uint64_t h = real_start_height; h <= to_height; ++h) + for (uint64_t h = start_height; h <= to_height; ++h) heights.push_back(h); distribution = m_db->get_block_cumulative_rct_outputs(heights); - if (start_height > 0) - { - base = distribution[0]; - distribution.erase(distribution.begin()); - } + base = 0; return true; } else @@ -4401,7 +4398,7 @@ void Blockchain::cancel() } #if defined(PER_BLOCK_CHECKPOINT) -static const char expected_block_hashes_hash[] = "0924bc1c47aae448321fde949554be192878dd800e6489379865218f84eacbca"; +static const char expected_block_hashes_hash[] = "954cb2bbfa2fe6f74b2cdd22a1a4c767aea249ad47ad4f7c9445f0f03260f511"; void Blockchain::load_compiled_in_block_hashes() { const bool testnet = m_nettype == TESTNET; diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 7cbf414b7..3e3d3c19c 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -181,7 +181,8 @@ namespace cryptonote m_last_json_checkpoints_update(0), m_disable_dns_checkpoints(false), m_update_download(0), - m_nettype(UNDEFINED) + m_nettype(UNDEFINED), + m_update_available(false) { m_checkpoints_updating.clear(); set_cryptonote_protocol(pprotocol); @@ -1541,10 +1542,14 @@ namespace cryptonote return false; if (tools::vercmp(version.c_str(), MONERO_VERSION) <= 0) + { + m_update_available = false; return true; + } std::string url = tools::get_update_url(software, subdir, buildtag, version, true); MCLOG_CYAN(el::Level::Info, "global", "Version " << version << " of " << software << " for " << buildtag << " is available: " << url << ", SHA256 hash " << hash); + m_update_available = true; if (check_updates_level == UPDATES_NOTIFY) return true; diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index b40575ae9..58fe5b7b5 100644 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -739,6 +739,16 @@ namespace cryptonote network_type get_nettype() const { return m_nettype; }; /** + * @brief check whether an update is known to be available or not + * + * This does not actually trigger a check, but returns the result + * of the last check + * + * @return whether an update is known to be available or not + */ + bool is_update_available() const { return m_update_available; } + + /** * @brief get whether fluffy blocks are enabled * * @return whether fluffy blocks are enabled @@ -966,6 +976,8 @@ namespace cryptonote network_type m_nettype; //!< which network are we on? + std::atomic<bool> m_update_available; + std::string m_checkpoints_path; //!< path to json checkpoints file time_t m_last_dns_checkpoints_update; //!< time when dns checkpoints were last updated time_t m_last_json_checkpoints_update; //!< time when json checkpoints were last updated diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp index a725eac6e..c31a2073b 100644 --- a/src/cryptonote_core/tx_pool.cpp +++ b/src/cryptonote_core/tx_pool.cpp @@ -1349,6 +1349,7 @@ namespace cryptonote { MWARNING("Failed to parse tx from txpool, removing"); remove.push_back(txid); + return true; } if (!insert_key_images(tx, meta.kept_by_block)) { diff --git a/src/device/CMakeLists.txt b/src/device/CMakeLists.txt index cc2d20a54..8f446f42a 100644 --- a/src/device/CMakeLists.txt +++ b/src/device/CMakeLists.txt @@ -32,18 +32,27 @@ set(device_sources log.cpp ) -if(PCSC_FOUND) - set(device_sources ${device_sources} device_ledger.cpp) +if(HIDAPI_FOUND) + set(device_sources + ${device_sources} + device_ledger.cpp + device_io_hid.cpp + ) endif() set(device_headers device.hpp + device_io.hpp device_default.hpp log.hpp ) -if(PCSC_FOUND) - set(device_headers ${device_headers} device_ledger.hpp) +if(HIDAPI_FOUND) + set(device_headers + ${device_headers} + device_ledger.hpp + device_io_hid.hpp + ) endif() set(device_private_headers) @@ -65,7 +74,7 @@ monero_add_library(device target_link_libraries(device PUBLIC - ${PCSC_LIBRARIES} + ${HIDAPI_LIBRARIES} cncrypto ringct_basic ${OPENSSL_CRYPTO_LIBRARIES} diff --git a/src/device/device.cpp b/src/device/device.cpp index 8a8b40061..50041baef 100644 --- a/src/device/device.cpp +++ b/src/device/device.cpp @@ -29,7 +29,7 @@ #include "device.hpp" #include "device_default.hpp" -#ifdef HAVE_PCSC +#ifdef WITH_DEVICE_LEDGER #include "device_ledger.hpp" #endif #include "misc_log_ex.h" @@ -45,7 +45,7 @@ namespace hw { device_registry::device_registry(){ hw::core::register_all(registry); - #ifdef HAVE_PCSC + #ifdef WITH_DEVICE_LEDGER hw::ledger::register_all(registry); #endif } diff --git a/src/device/device.hpp b/src/device/device.hpp index d14b8848c..cb9117650 100644 --- a/src/device/device.hpp +++ b/src/device/device.hpp @@ -48,11 +48,12 @@ #include "crypto/chacha.h" #include "ringct/rctTypes.h" + #ifndef USE_DEVICE_LEDGER #define USE_DEVICE_LEDGER 1 #endif -#if !defined(HAVE_PCSC) +#if !defined(HAVE_HIDAPI) #undef USE_DEVICE_LEDGER #define USE_DEVICE_LEDGER 0 #endif @@ -78,7 +79,6 @@ namespace hw { return false; } - class device { protected: std::string name; @@ -96,6 +96,12 @@ namespace hw { TRANSACTION_CREATE_FAKE, TRANSACTION_PARSE }; + enum device_type + { + SOFTWARE = 0, + LEDGER = 1 + }; + /* ======================================================================= */ /* SETUP/TEARDOWN */ @@ -109,7 +115,9 @@ namespace hw { virtual bool connect(void) = 0; virtual bool disconnect(void) = 0; - virtual bool set_mode(device_mode mode) = 0; + virtual bool set_mode(device_mode mode) = 0; + + virtual device_type get_type() const = 0; /* ======================================================================= */ diff --git a/src/device/device_default.hpp b/src/device/device_default.hpp index 8d841d9de..5c59a9066 100644 --- a/src/device/device_default.hpp +++ b/src/device/device_default.hpp @@ -61,6 +61,8 @@ namespace hw { bool set_mode(device_mode mode) override; + device_type get_type() const override {return device_type::SOFTWARE;}; + /* ======================================================================= */ /* LOCKER */ /* ======================================================================= */ diff --git a/src/device/device_io.hpp b/src/device/device_io.hpp new file mode 100644 index 000000000..96163a211 --- /dev/null +++ b/src/device/device_io.hpp @@ -0,0 +1,56 @@ +// Copyright (c) 2017-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 hw { + namespace io { + + class device_io { + + public: + + device_io() {}; + ~device_io() {}; + + virtual void init() = 0; + virtual void release() = 0; + + virtual void connect(void *parms) = 0; + virtual void disconnect() = 0; + virtual bool connected() const = 0; + + virtual int exchange(unsigned char *command, unsigned int cmd_len, unsigned char *response, unsigned int max_resp_len) = 0; + }; + }; +}; diff --git a/src/device/device_io_hid.cpp b/src/device/device_io_hid.cpp new file mode 100644 index 000000000..562aca8b8 --- /dev/null +++ b/src/device/device_io_hid.cpp @@ -0,0 +1,288 @@ +// 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(HAVE_HIDAPI) + +#include "log.hpp" +#include "device_io_hid.hpp" + +namespace hw { + namespace io { + + #undef MONERO_DEFAULT_LOG_CATEGORY + #define MONERO_DEFAULT_LOG_CATEGORY "device.io" + + #define ASSERT_X(exp,msg) CHECK_AND_ASSERT_THROW_MES(exp, msg); + + #define MAX_BLOCK 64 + + static std::string safe_hid_error(hid_device *hwdev) { + if (hwdev) { + return std::string((char*)hid_error(hwdev)); + } + return std::string("NULL device"); + } + + static std::string safe_hid_path(const hid_device_info *hwdev_info) { + if (hwdev_info && hwdev_info->path) { + return std::string(hwdev_info->path); + } + return std::string("NULL path"); + } + + device_io_hid::device_io_hid(unsigned short c, unsigned char t, unsigned int ps, unsigned int to) : + channel(c), + tag(t), + packet_size(ps), + timeout(to), + usb_vid(0), + usb_pid(0), + usb_device(NULL) { + } + + device_io_hid::device_io_hid() : device_io_hid(DEFAULT_CHANNEL, DEFAULT_TAG, DEFAULT_PACKET_SIZE, DEFAULT_TIMEOUT) { + } + + void device_io_hid::io_hid_log(int read, unsigned char* buffer, int block_len) { + if (hid_verbose) { + char strbuffer[1024]; + hw::buffer_to_str(strbuffer, sizeof(strbuffer), (char*)buffer, block_len); + MDEBUG( "HID " << (read?"<":">") <<" : "<<strbuffer); + } + } + + void device_io_hid::init() { + int r; + r = hid_init(); + ASSERT_X(r>=0, "Unable to init hidapi library. Error "+std::to_string(r)+": "+safe_hid_error(this->usb_device)); + } + + void device_io_hid::connect(void *params) { + hid_conn_params *p = (struct hid_conn_params*)params; + this->connect(p->vid, p->pid, p->interface_number, p->usage_page, p->interface_OR_page); + } + + void device_io_hid::connect(unsigned int vid, unsigned int pid, unsigned int interface_number, unsigned int usage_page, bool interface_OR_page ) { + hid_device_info *hwdev_info, *hwdev_info_list; + hid_device *hwdev; + + this->disconnect(); + + hwdev_info_list = hid_enumerate(vid, pid); + ASSERT_X(hwdev_info_list, "Unable to enumerate device "+std::to_string(vid)+":"+std::to_string(vid)+ ": "+ safe_hid_error(this->usb_device)); + hwdev = NULL; + hwdev_info = hwdev_info_list; + while (hwdev_info) { + if ((interface_OR_page && ((usage_page == 0xffa0) || (interface_number == 0))) || + ((usage_page == 0xffa0) && (interface_number == 0)) ) { + MDEBUG("HID Device found: " << safe_hid_path(hwdev_info)); + hwdev = hid_open_path(hwdev_info->path); + break; + } else { + MDEBUG("HID Device discard: " << safe_hid_path(hwdev_info) << "("+std::to_string(hwdev_info->usage_page) << "," << std::to_string(hwdev_info->interface_number) << ")"); + } + hwdev_info = hwdev_info->next; + } + hid_free_enumeration(hwdev_info_list); + ASSERT_X(hwdev, "Unable to open device "+std::to_string(pid)+":"+std::to_string(vid)); + this->usb_vid = vid; + this->usb_pid = pid; + this->usb_device = hwdev; + } + + + bool device_io_hid::connected() const { + return this->usb_device != NULL; + } + + int device_io_hid::exchange(unsigned char *command, unsigned int cmd_len, unsigned char *response, unsigned int max_resp_len) { + unsigned char buffer[400]; + unsigned char padding_buffer[MAX_BLOCK+1]; + unsigned int result; + int hid_ret; + unsigned int sw_offset; + unsigned int remaining; + unsigned int offset = 0; + + ASSERT_X(this->usb_device,"No device opened"); + + //Split command in several HID packet + memset(buffer, 0, sizeof(buffer)); + result = this->wrapCommand(command, cmd_len, buffer, sizeof(buffer)); + remaining = result; + + while (remaining > 0) { + int block_size = (remaining > MAX_BLOCK ? MAX_BLOCK : remaining); + memset(padding_buffer, 0, sizeof(padding_buffer)); + memcpy(padding_buffer+1, buffer + offset, block_size); + io_hid_log(0, padding_buffer, block_size+1); + hid_ret = hid_write(this->usb_device, padding_buffer, block_size+1); + ASSERT_X(hid_ret>=0, "Unable to send hidapi command. Error "+std::to_string(result)+": "+ safe_hid_error(this->usb_device)); + offset += block_size; + remaining -= block_size; + } + + //get first response + memset(buffer, 0, sizeof(buffer)); + hid_ret = hid_read_timeout(this->usb_device, buffer, MAX_BLOCK, this->timeout); + ASSERT_X(hid_ret>=0, "Unable to read hidapi response. Error "+std::to_string(result)+": "+ safe_hid_error(this->usb_device)); + result = (unsigned int)hid_ret; + io_hid_log(1, buffer, result); + offset = MAX_BLOCK; + //parse first response and get others if any + for (;;) { + result = this->unwrapReponse(buffer, offset, response, max_resp_len); + if (result != 0) { + break; + } + hid_ret = hid_read_timeout(this->usb_device, buffer + offset, MAX_BLOCK, this->timeout); + ASSERT_X(hid_ret>=0, "Unable to receive hidapi response. Error "+std::to_string(result)+": "+ safe_hid_error(this->usb_device)); + result = (unsigned int)hid_ret; + io_hid_log(1, buffer + offset, result); + offset += MAX_BLOCK; + } + return result; + } + + void device_io_hid::disconnect(void) { + if (this->usb_device) { + hid_close(this->usb_device); + } + this->usb_vid = 0; + this->usb_pid = 0; + this->usb_device = NULL; + } + + void device_io_hid::release() { + /* Do not exit, as the lib context is global*/ + //hid_exit(); + } + + unsigned int device_io_hid::wrapCommand(const unsigned char *command, size_t command_len, unsigned char *out, size_t out_len) { + unsigned int sequence_idx = 0; + unsigned int offset = 0; + unsigned int offset_out = 0; + unsigned int block_size; + + ASSERT_X(this->packet_size >= 3, "Invalid Packet size: "+std::to_string(this->packet_size)) ; + ASSERT_X(out_len >= 7, "out_len too short: "+std::to_string(out_len)); + + out_len -= 7; + out[offset_out++] = ((this->channel >> 8) & 0xff); + out[offset_out++] = (this->channel & 0xff); + out[offset_out++] = this->tag; + out[offset_out++] = ((sequence_idx >> 8) & 0xff); + out[offset_out++] = (sequence_idx & 0xff); + sequence_idx++; + out[offset_out++] = ((command_len >> 8) & 0xff); + out[offset_out++] = (command_len & 0xff); + block_size = (command_len > this->packet_size - 7 ? this->packet_size - 7 : command_len); + ASSERT_X(out_len >= block_size, "out_len too short: "+std::to_string(out_len)); + out_len -= block_size; + memcpy(out + offset_out, command + offset, block_size); + offset_out += block_size; + offset += block_size; + while (offset != command_len) { + ASSERT_X(out_len >= 5, "out_len too short: "+std::to_string(out_len)); + out_len -= 5; + out[offset_out++] = ((this->channel >> 8) & 0xff); + out[offset_out++] = (this->channel & 0xff); + out[offset_out++] = this->tag; + out[offset_out++] = ((sequence_idx >> 8) & 0xff); + out[offset_out++] = (sequence_idx & 0xff); + sequence_idx++; + block_size = ((command_len - offset) > this->packet_size - 5 ? this->packet_size - 5 : command_len - offset); + ASSERT_X(out_len >= block_size, "out_len too short: "+std::to_string(out_len)); + out_len -= block_size; + memcpy(out + offset_out, command + offset, block_size); + offset_out += block_size; + offset += block_size; + } + while ((offset_out % this->packet_size) != 0) { + ASSERT_X(out_len >= 1, "out_len too short: "+std::to_string(out_len)); + out_len--; + out[offset_out++] = 0; + } + return offset_out; + } + + /* + * return 0 if more data are needed + * >0 if response is fully available + */ + unsigned int device_io_hid::unwrapReponse(const unsigned char *data, size_t data_len, unsigned char *out, size_t out_len) { + unsigned int sequence_idx = 0; + unsigned int offset = 0; + unsigned int offset_out = 0; + unsigned int response_len; + unsigned int block_size; + unsigned int val; + + //end? + if ((data == NULL) || (data_len < 7 + 5)) { + return 0; + } + + //check hid header + val = (data[offset]<<8) + data[offset+1]; + offset += 2; + ASSERT_X(val == this->channel, "Wrong Channel"); + val = data[offset]; + offset++; + ASSERT_X(val == this->tag, "Wrong TAG"); + val = (data[offset]<<8) + data[offset+1]; + offset += 2; + ASSERT_X(val == sequence_idx, "Wrong sequence_idx"); + + //fetch + response_len = (data[offset++] << 8); + response_len |= data[offset++]; + ASSERT_X(out_len >= response_len, "Out Buffer too short"); + if (data_len < (7 + response_len)) { + return 0; + } + block_size = (response_len > (this->packet_size - 7) ? this->packet_size - 7 : response_len); + memcpy(out + offset_out, data + offset, block_size); + offset += block_size; + offset_out += block_size; + while (offset_out != response_len) { + sequence_idx++; + if (offset == data_len) { + return 0; + } + val = (data[offset]<<8) + data[offset+1]; + offset += 2; + ASSERT_X(val == this->channel, "Wrong Channel"); + val = data[offset]; + offset++; + ASSERT_X(val == this->tag, "Wrong TAG"); + val = (data[offset]<<8) + data[offset+1]; + offset += 2; + ASSERT_X(val == sequence_idx, "Wrong sequence_idx"); + + block_size = ((response_len - offset_out) > this->packet_size - 5 ? this->packet_size - 5 : response_len - offset_out); + if (block_size > (data_len - offset)) { + return 0; + } + memcpy(out + offset_out, data + offset, block_size); + offset += block_size; + offset_out += block_size; + } + return offset_out; + } + + + } +} + +#endif //#if defined(HAVE_HIDAPI) diff --git a/src/device/device_io_hid.hpp b/src/device/device_io_hid.hpp new file mode 100644 index 000000000..560208c77 --- /dev/null +++ b/src/device/device_io_hid.hpp @@ -0,0 +1,112 @@ +// Copyright (c) 2017-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(HAVE_HIDAPI) + +#include <hidapi/hidapi.h> +#include "device_io.hpp" + +#pragma once + +namespace hw { + namespace io { + + + + /** HID class base. Commands are formated as follow: + * + * |----------------------------------------------------------| + * | 2 bytes | 1 byte | 2 bytes | 2 bytes | len bytes | + * |-----------|----------|-----------|----------|------------| + * | channel | tag | sequence | len | payload | + * |----------------------------------------------------------| + */ + + + struct hid_conn_params { + unsigned int vid; + unsigned int pid; + unsigned int interface_number; + unsigned int usage_page; + bool interface_OR_page ; + }; + + + class device_io_hid: device_io { + + + private: + + + unsigned short channel; + unsigned char tag; + unsigned int packet_size; + unsigned int timeout; + + unsigned int usb_vid; + unsigned int usb_pid; + hid_device *usb_device; + + void io_hid_log(int read, unsigned char* buf, int buf_len); + void io_hid_init(); + void io_hid_exit() ; + void io_hid_open(int vid, int pid, int mode); + void io_hid_close (void); + + unsigned int wrapCommand(const unsigned char *command, size_t command_len, unsigned char *out, size_t out_len); + unsigned int unwrapReponse(const unsigned char *data, size_t data_len, unsigned char *out, size_t out_len); + + + public: + bool hid_verbose = false; + + const unsigned int OR_SELECT = 1; + const unsigned int AND_SELECT = 2; + + const unsigned char DEFAULT_CHANNEL = 0x0001; + const unsigned char DEFAULT_TAG = 0x01; + const unsigned int DEFAULT_PACKET_SIZE = 64; + const unsigned int DEFAULT_TIMEOUT = 120000; + + device_io_hid(unsigned short channel, unsigned char tag, unsigned int packet_zize, unsigned int timeout); + device_io_hid(); + ~device_io_hid() {}; + + void init(); + void connect(void *params); + void connect(unsigned int vid, unsigned int pid, unsigned int interface_number, unsigned int usage_page, bool interface_OR_page ); + bool connected() const; + int exchange(unsigned char *command, unsigned int cmd_len, unsigned char *response, unsigned int max_resp_len); + void disconnect(); + void release(); + }; + }; +}; + +#endif //#if defined(HAVE_HIDAPI) diff --git a/src/device/device_ledger.cpp b/src/device/device_ledger.cpp index 9b5ea0fd7..456eda739 100644 --- a/src/device/device_ledger.cpp +++ b/src/device/device_ledger.cpp @@ -185,41 +185,8 @@ namespace hw { #define INS_GET_RESPONSE 0xc0 - void device_ledger::logCMD() { - if (apdu_verbose) { - char strbuffer[1024]; - snprintf(strbuffer, sizeof(strbuffer), "%.02x %.02x %.02x %.02x %.02x ", - this->buffer_send[0], - this->buffer_send[1], - this->buffer_send[2], - this->buffer_send[3], - this->buffer_send[4] - ); - const size_t len = strlen(strbuffer); - buffer_to_str(strbuffer+len, sizeof(strbuffer)-len, (char*)(this->buffer_send+5), this->length_send-5); - MDEBUG( "CMD :" << strbuffer); - } - } - - void device_ledger::logRESP() { - if (apdu_verbose) { - char strbuffer[1024]; - snprintf(strbuffer, sizeof(strbuffer), "%.02x%.02x ", - this->buffer_recv[this->length_recv-2], - this->buffer_recv[this->length_recv-1] - ); - const size_t len = strlen(strbuffer); - buffer_to_str(strbuffer+len, sizeof(strbuffer)-len, (char*)(this->buffer_recv), this->length_recv-2); - MDEBUG( "RESP :" << strbuffer); - - } - } - - /* -------------------------------------------------------------- */ - device_ledger::device_ledger() { + device_ledger::device_ledger(): hw_device(0x0101, 0x05, 64, 10000) { this->id = device_id++; - this->hCard = 0; - this->hContext = 0; this->reset_buffer(); this->mode = NONE; this->has_view_key = false; @@ -272,10 +239,39 @@ namespace hw { MDEBUG( "Device "<<this->name << " UNLOCKed"); } + /* ======================================================================= */ - /* MISC */ + /* IO */ /* ======================================================================= */ - int device_ledger::set_command_header(BYTE ins, BYTE p1, BYTE p2) { + + void device_ledger::logCMD() { + if (apdu_verbose) { + char strbuffer[1024]; + snprintf(strbuffer, sizeof(strbuffer), "%.02x %.02x %.02x %.02x %.02x ", + this->buffer_send[0], + this->buffer_send[1], + this->buffer_send[2], + this->buffer_send[3], + this->buffer_send[4] + ); + const size_t len = strlen(strbuffer); + buffer_to_str(strbuffer+len, sizeof(strbuffer)-len, (char*)(this->buffer_send+5), this->length_send-5); + MDEBUG( "CMD : " << strbuffer); + } + } + + void device_ledger::logRESP() { + if (apdu_verbose) { + char strbuffer[1024]; + snprintf(strbuffer, sizeof(strbuffer), "%.04x ", this->sw); + const size_t len = strlen(strbuffer); + buffer_to_str(strbuffer+len, sizeof(strbuffer)-len, (char*)(this->buffer_recv), this->length_recv); + MDEBUG( "RESP : " << strbuffer); + + } + } + + int device_ledger::set_command_header(unsigned char ins, unsigned char p1, unsigned char p2) { reset_buffer(); int offset = 0; this->buffer_send[0] = 0x00; @@ -286,7 +282,7 @@ namespace hw { return 5; } - int device_ledger::set_command_header_noopt(BYTE ins, BYTE p1, BYTE p2) { + int device_ledger::set_command_header_noopt(unsigned char ins, unsigned char p1, unsigned char p2) { int offset = set_command_header(ins, p1, p2); //options this->buffer_send[offset++] = 0; @@ -294,7 +290,7 @@ namespace hw { return offset; } - void device_ledger::send_simple(BYTE ins, BYTE p1) { + void device_ledger::send_simple(unsigned char ins, unsigned char p1) { this->length_send = set_command_header_noopt(ins, p1); this->exchange(); } @@ -305,23 +301,17 @@ namespace hw { } unsigned int device_ledger::exchange(unsigned int ok, unsigned int mask) { - LONG rv; - unsigned int sw; - - ASSERT_T0(this->length_send <= BUFFER_SEND_SIZE); logCMD(); - this->length_recv = BUFFER_RECV_SIZE; - rv = SCardTransmit(this->hCard, - SCARD_PCI_T0, this->buffer_send, this->length_send, - NULL, this->buffer_recv, &this->length_recv); - ASSERT_RV(rv); - ASSERT_T0(this->length_recv >= 2); - ASSERT_T0(this->length_recv <= BUFFER_RECV_SIZE); - logRESP(); - sw = (this->buffer_recv[this->length_recv-2]<<8) | this->buffer_recv[this->length_recv-1]; - ASSERT_SW(sw,ok,msk); - return sw; + this->length_recv = hw_device.exchange(this->buffer_send, this->length_send, this->buffer_recv, BUFFER_SEND_SIZE); + ASSERT_X(this->length_recv>=2, "Communication error, less than tow bytes received"); + + this->length_recv -= 2; + this->sw = (this->buffer_recv[length_recv]<<8) | this->buffer_recv[length_recv+1]; + ASSERT_SW(this->sw,ok,msk); + + logRESP(); + return this->sw; } void device_ledger::reset_buffer() { @@ -341,101 +331,25 @@ namespace hw { } const std::string device_ledger::get_name() const { - if (this->full_name.empty() || (this->hCard == 0)) { + if (this->full_name.empty() || !this->connected()) { return std::string("<disconnected:").append(this->name).append(">"); } - return this->full_name; + return this->name; } bool device_ledger::init(void) { #ifdef DEBUG_HWDEVICE this->controle_device = &hw::get_device("default"); #endif - LONG rv; this->release(); - rv = SCardEstablishContext(SCARD_SCOPE_SYSTEM,0,0, &this->hContext); - ASSERT_RV(rv); - MDEBUG( "Device "<<this->id <<" SCardContext created: hContext="<<this->hContext); - this->hCard = 0; - return true; - } - - bool device_ledger::release() { - this->disconnect(); - if (this->hContext) { - SCardReleaseContext(this->hContext); - MDEBUG( "Device "<<this->id <<" SCardContext released: hContext="<<this->hContext); - this->hContext = 0; - this->full_name.clear(); - } + hw_device.init(); + MDEBUG( "Device "<<this->id <<" HIDUSB inited"); return true; } bool device_ledger::connect(void) { - BYTE pbAtr[MAX_ATR_SIZE]; - LPSTR mszReaders; - DWORD dwReaders; - LONG rv; - DWORD dwState, dwProtocol, dwAtrLen, dwReaderLen; - this->disconnect(); -#ifdef SCARD_AUTOALLOCATE - dwReaders = SCARD_AUTOALLOCATE; - rv = SCardListReaders(this->hContext, NULL, (LPSTR)&mszReaders, &dwReaders); -#else - dwReaders = 0; - rv = SCardListReaders(this->hContext, NULL, NULL, &dwReaders); - if (rv != SCARD_S_SUCCESS) - return false; - mszReaders = (LPSTR)calloc(dwReaders, sizeof(char)); - rv = SCardListReaders(this->hContext, NULL, mszReaders, &dwReaders); -#endif - if (rv == SCARD_S_SUCCESS) { - char* p; - const char* prefix = this->name.c_str(); - - p = mszReaders; - MDEBUG( "Looking for " << std::string(prefix)); - while (*p) { - MDEBUG( "Device Found: " << std::string(p)); - if ((strncmp(prefix, p, strlen(prefix))==0)) { - MDEBUG( "Device Match: " << std::string(p)); - if ((rv = SCardConnect(this->hContext, - p, SCARD_SHARE_EXCLUSIVE, SCARD_PROTOCOL_T0, - &this->hCard, &dwProtocol))!=SCARD_S_SUCCESS) { - break; - } - MDEBUG( "Device "<<this->id <<" Connected: hCard="<<this->hCard); - dwAtrLen = sizeof(pbAtr); - if ((rv = SCardStatus(this->hCard, NULL, &dwReaderLen, &dwState, &dwProtocol, pbAtr, &dwAtrLen))!=SCARD_S_SUCCESS) { - break; - } - MDEBUG( "Device "<<this->id <<" Status OK"); - rv = SCARD_S_SUCCESS ; - this->full_name = std::string(p); - break; - } - p += strlen(p) +1; - } - } - - if (rv == SCARD_S_SUCCESS && mszReaders) { - #ifdef SCARD_AUTOALLOCATE - SCardFreeMemory(this->hContext, mszReaders); - #else - free(mszReaders); - #endif - mszReaders = NULL; - } - if (rv != SCARD_S_SUCCESS) { - if ( hCard) { - SCardDisconnect(this->hCard, SCARD_UNPOWER_CARD); - MDEBUG( "Device "<<this->id <<" disconnected: hCard="<<this->hCard); - this->hCard = 0; - } - } - ASSERT_RV(rv); - + hw_device.connect(0x2c97,0x0001, 0, 0xffa0, hw_device.OR_SELECT); this->reset(); #ifdef DEBUG_HWDEVICE cryptonote::account_public_address pubkey; @@ -445,15 +359,21 @@ namespace hw { crypto::secret_key skey; this->get_secret_keys(vkey,skey); - return rv==SCARD_S_SUCCESS; + return true; + } + + bool device_ledger::connected(void) const { + return hw_device.connected(); } bool device_ledger::disconnect() { - if (this->hCard) { - SCardDisconnect(this->hCard, SCARD_UNPOWER_CARD); - MDEBUG( "Device "<<this->id <<" disconnected: hCard="<<this->hCard); - this->hCard = 0; - } + hw_device.disconnect(); + return true; + } + + bool device_ledger::release() { + this->disconnect(); + hw_device.release(); return true; } diff --git a/src/device/device_ledger.hpp b/src/device/device_ledger.hpp index e6c6e5b52..dde69fbfd 100644 --- a/src/device/device_ledger.hpp +++ b/src/device/device_ledger.hpp @@ -33,13 +33,7 @@ #include <cstddef> #include <string> #include "device.hpp" -#ifdef WIN32 -#include <winscard.h> -#define MAX_ATR_SIZE 33 -#else -#include <PCSC/winscard.h> -#include <PCSC/wintypes.h> -#endif +#include "device_io_hid.hpp" #include <boost/thread/mutex.hpp> #include <boost/thread/recursive_mutex.hpp> @@ -89,22 +83,23 @@ namespace hw { mutable boost::recursive_mutex device_locker; mutable boost::mutex command_locker; - //PCSC management - std::string full_name; - SCARDCONTEXT hContext; - SCARDHANDLE hCard; - DWORD length_send; - BYTE buffer_send[BUFFER_SEND_SIZE]; - DWORD length_recv; - BYTE buffer_recv[BUFFER_RECV_SIZE]; - unsigned int id; + //IO + hw::io::device_io_hid hw_device; + std::string full_name; + unsigned int length_send; + unsigned char buffer_send[BUFFER_SEND_SIZE]; + unsigned int length_recv; + unsigned char buffer_recv[BUFFER_RECV_SIZE]; + unsigned int sw; + unsigned int id; void logCMD(void); void logRESP(void); - unsigned int exchange(unsigned int ok=0x9000, unsigned int mask=0xFFFF); + unsigned int exchange(unsigned int ok=0x9000, unsigned int mask=0xFFFF); void reset_buffer(void); - int set_command_header(BYTE ins, BYTE p1 = 0x00, BYTE p2 = 0x00); - int set_command_header_noopt(BYTE ins, BYTE p1 = 0x00, BYTE p2 = 0x00); - void send_simple(BYTE ins, BYTE p1 = 0x00); + int set_command_header(unsigned char ins, unsigned char p1 = 0x00, unsigned char p2 = 0x00); + int set_command_header_noopt(unsigned char ins, unsigned char p1 = 0x00, unsigned char p2 = 0x00); + void send_simple(unsigned char ins, unsigned char p1 = 0x00); + // hw running mode device_mode mode; @@ -127,7 +122,7 @@ namespace hw { device_ledger(const device_ledger &device) = delete ; device_ledger& operator=(const device_ledger &device) = delete; - explicit operator bool() const override {return this->hContext != 0;} + explicit operator bool() const override {return this->connected(); } bool reset(void); @@ -141,8 +136,11 @@ namespace hw { bool release() override; bool connect(void) override; bool disconnect() override; + bool connected(void) const; + + bool set_mode(device_mode mode) override; - bool set_mode(device_mode mode) override; + device_type get_type() const override {return device_type::LEDGER;}; /* ======================================================================= */ /* LOCKER */ diff --git a/src/device/log.cpp b/src/device/log.cpp index 1707524fb..c9d3b551b 100644 --- a/src/device/log.cpp +++ b/src/device/log.cpp @@ -32,29 +32,34 @@ namespace hw { - #ifdef WITH_DEVICE_LEDGER - namespace ledger { - - #undef MONERO_DEFAULT_LOG_CATEGORY - #define MONERO_DEFAULT_LOG_CATEGORY "device.ledger" + #undef MONERO_DEFAULT_LOG_CATEGORY + #define MONERO_DEFAULT_LOG_CATEGORY "device" - void buffer_to_str(char *to_buff, size_t to_len, const char *buff, size_t len) { - CHECK_AND_ASSERT_THROW_MES(to_len > (len*2), "destination buffer too short. At least" << (len*2+1) << " bytes required"); - for (size_t i=0; i<len; i++) { - sprintf(to_buff+2*i, "%.02x", (unsigned char)buff[i]); - } + void buffer_to_str(char *to_buff, size_t to_len, const char *buff, size_t len) { + CHECK_AND_ASSERT_THROW_MES(to_len > (len*2), "destination buffer too short. At least" << (len*2+1) << " bytes required"); + for (size_t i=0; i<len; i++) { + sprintf(to_buff+2*i, "%.02x", (unsigned char)buff[i]); } + } - void log_hexbuffer(const std::string &msg, const char* buff, size_t len) { - char logstr[1025]; - buffer_to_str(logstr, sizeof(logstr), buff, len); - MDEBUG(msg<< ": " << logstr); - } + void log_hexbuffer(const std::string &msg, const char* buff, size_t len) { + char logstr[1025]; + buffer_to_str(logstr, sizeof(logstr), buff, len); + MDEBUG(msg<< ": " << logstr); + } - void log_message(const std::string &msg, const std::string &info ) { - MDEBUG(msg << ": " << info); - } + void log_message(const std::string &msg, const std::string &info ) { + MDEBUG(msg << ": " << info); + } + + + #ifdef WITH_DEVICE_LEDGER + namespace ledger { + + #undef MONERO_DEFAULT_LOG_CATEGORY + #define MONERO_DEFAULT_LOG_CATEGORY "device.ledger" + #ifdef DEBUG_HWDEVICE extern crypto::secret_key dbg_viewkey; extern crypto::secret_key dbg_spendkey; diff --git a/src/device/log.hpp b/src/device/log.hpp index 1d1635dc1..25a214a6c 100644 --- a/src/device/log.hpp +++ b/src/device/log.hpp @@ -40,12 +40,13 @@ namespace hw { + void buffer_to_str(char *to_buff, size_t to_len, const char *buff, size_t len) ; + void log_hexbuffer(const std::string &msg, const char* buff, size_t len); + void log_message(const std::string &msg, const std::string &info ); + #ifdef WITH_DEVICE_LEDGER namespace ledger { - void buffer_to_str(char *to_buff, size_t to_len, const char *buff, size_t len) ; - void log_hexbuffer(const std::string &msg, const char* buff, size_t len); - void log_message(const std::string &msg, const std::string &info ); #ifdef DEBUG_HWDEVICE #define TRACK printf("file %s:%d\n",__FILE__, __LINE__) //#define TRACK MCDEBUG("ledger"," At file " << __FILE__ << ":" << __LINE__) diff --git a/src/ringct/bulletproofs.cc b/src/ringct/bulletproofs.cc index abe4ef18d..381f50872 100644 --- a/src/ringct/bulletproofs.cc +++ b/src/ringct/bulletproofs.cc @@ -1068,6 +1068,14 @@ bool bulletproof_VERIFY(const std::vector<const Bulletproof*> &proofs) CHECK_AND_ASSERT_MES(!(x_ip == rct::zero()), false, "x_ip == 0"); PERF_TIMER_STOP(VERIFY_start); + // pre-multiply some points by 8 + rct::keyV proof8_V = proof.V; for (rct::key &k: proof8_V) k = rct::scalarmult8(k); + rct::keyV proof8_L = proof.L; for (rct::key &k: proof8_L) k = rct::scalarmult8(k); + rct::keyV proof8_R = proof.R; for (rct::key &k: proof8_R) k = rct::scalarmult8(k); + rct::key proof8_T1 = rct::scalarmult8(proof.T1); + rct::key proof8_T2 = rct::scalarmult8(proof.T2); + rct::key proof8_S = rct::scalarmult8(proof.S); + PERF_TIMER_START_BP(VERIFY_line_61); // PAPER LINE 61 sc_muladd(y0.bytes, proof.taux.bytes, weight.bytes, y0.bytes); @@ -1090,26 +1098,22 @@ bool bulletproof_VERIFY(const std::vector<const Bulletproof*> &proofs) multiexp_data.reserve(proof.V.size()); sc_sub(tmp.bytes, proof.t.bytes, tmp.bytes); sc_muladd(y1.bytes, tmp.bytes, weight.bytes, y1.bytes); - for (size_t j = 0; j < proof.V.size(); j++) + for (size_t j = 0; j < proof8_V.size(); j++) { - sc_mul(tmp.bytes, zpow[j+2].bytes, EIGHT.bytes); - multiexp_data.emplace_back(tmp, proof.V[j]); + multiexp_data.emplace_back(zpow[j+2], proof8_V[j]); } rct::addKeys(Y2, Y2, rct::scalarmultKey(multiexp(multiexp_data, false), weight)); - rct::key weight8; - sc_mul(weight8.bytes, weight.bytes, EIGHT.bytes); - sc_mul(tmp.bytes, x.bytes, weight8.bytes); - rct::addKeys(Y3, Y3, rct::scalarmultKey(proof.T1, tmp)); + sc_mul(tmp.bytes, x.bytes, weight.bytes); + rct::addKeys(Y3, Y3, rct::scalarmultKey(proof8_T1, tmp)); rct::key xsq; sc_mul(xsq.bytes, x.bytes, x.bytes); - sc_mul(tmp.bytes, xsq.bytes, weight8.bytes); - rct::addKeys(Y4, Y4, rct::scalarmultKey(proof.T2, tmp)); + sc_mul(tmp.bytes, xsq.bytes, weight.bytes); + rct::addKeys(Y4, Y4, rct::scalarmultKey(proof8_T2, tmp)); PERF_TIMER_STOP(VERIFY_line_61rl_new); PERF_TIMER_START_BP(VERIFY_line_62); // PAPER LINE 62 - sc_mul(tmp.bytes, x.bytes, EIGHT.bytes); - rct::addKeys(Z0, Z0, rct::scalarmultKey(rct::addKeys(rct::scalarmult8(proof.A), rct::scalarmultKey(proof.S, tmp)), weight)); + rct::addKeys(Z0, Z0, rct::scalarmultKey(rct::addKeys(rct::scalarmult8(proof.A), rct::scalarmultKey(proof8_S, x)), weight)); PERF_TIMER_STOP(VERIFY_line_62); // Compute the number of rounds for the inner product @@ -1192,11 +1196,9 @@ bool bulletproof_VERIFY(const std::vector<const Bulletproof*> &proofs) for (size_t i = 0; i < rounds; ++i) { sc_mul(tmp.bytes, w[i].bytes, w[i].bytes); - sc_mul(tmp.bytes, tmp.bytes, EIGHT.bytes); - multiexp_data.emplace_back(tmp, proof.L[i]); + multiexp_data.emplace_back(tmp, proof8_L[i]); sc_mul(tmp.bytes, winv[i].bytes, winv[i].bytes); - sc_mul(tmp.bytes, tmp.bytes, EIGHT.bytes); - multiexp_data.emplace_back(tmp, proof.R[i]); + multiexp_data.emplace_back(tmp, proof8_R[i]); } rct::key acc = multiexp(multiexp_data, false); rct::addKeys(Z2, Z2, rct::scalarmultKey(acc, weight)); diff --git a/src/ringct/rctOps.cpp b/src/ringct/rctOps.cpp index 6c3c4500e..41bbf6ca3 100644 --- a/src/ringct/rctOps.cpp +++ b/src/ringct/rctOps.cpp @@ -252,6 +252,25 @@ namespace rct { return k; } + rct::key addKeys(const keyV &A) { + if (A.empty()) + return rct::identity(); + ge_p3 p3, tmp; + CHECK_AND_ASSERT_THROW_MES_L1(ge_frombytes_vartime(&p3, A[0].bytes) == 0, "ge_frombytes_vartime failed at "+boost::lexical_cast<std::string>(__LINE__)); + for (size_t i = 1; i < A.size(); ++i) + { + CHECK_AND_ASSERT_THROW_MES_L1(ge_frombytes_vartime(&tmp, A[i].bytes) == 0, "ge_frombytes_vartime failed at "+boost::lexical_cast<std::string>(__LINE__)); + ge_cached p2; + ge_p3_to_cached(&p2, &tmp); + ge_p1p1 p1; + ge_add(&p1, &p3, &p2); + ge_p1p1_to_p3(&p3, &p1); + } + rct::key res; + ge_p3_tobytes(res.bytes, &p3); + return res; + } + //addKeys1 //aGB = aG + B where a is a scalar, G is the basepoint, and B is a point void addKeys1(key &aGB, const key &a, const key & B) { diff --git a/src/ringct/rctOps.h b/src/ringct/rctOps.h index 50645821c..60e920b3a 100644 --- a/src/ringct/rctOps.h +++ b/src/ringct/rctOps.h @@ -132,6 +132,7 @@ namespace rct { //for curve points: AB = A + B void addKeys(key &AB, const key &A, const key &B); rct::key addKeys(const key &A, const key &B); + rct::key addKeys(const keyV &A); //aGB = aG + B where a is a scalar, G is the basepoint, and B is a point void addKeys1(key &aGB, const key &a, const key & B); //aGbB = aG + bB where a, b are scalars, G is the basepoint and B is a point diff --git a/src/ringct/rctSigs.cpp b/src/ringct/rctSigs.cpp index fe0cd9c57..0d1789a38 100644 --- a/src/ringct/rctSigs.cpp +++ b/src/ringct/rctSigs.cpp @@ -963,18 +963,16 @@ namespace rct { const bool bulletproof = is_rct_bulletproof(rv.type); const keyV &pseudoOuts = bulletproof ? rv.p.pseudoOuts : rv.pseudoOuts; - key sumOutpks = identity(); + rct::keyV masks(rv.outPk.size()); for (size_t i = 0; i < rv.outPk.size(); i++) { - addKeys(sumOutpks, sumOutpks, rv.outPk[i].mask); + masks[i] = rv.outPk[i].mask; } + key sumOutpks = addKeys(masks); DP(sumOutpks); - key txnFeeKey = scalarmultH(d2h(rv.txnFee)); + const key txnFeeKey = scalarmultH(d2h(rv.txnFee)); addKeys(sumOutpks, txnFeeKey, sumOutpks); - key sumPseudoOuts = identity(); - for (size_t i = 0 ; i < pseudoOuts.size() ; i++) { - addKeys(sumPseudoOuts, sumPseudoOuts, pseudoOuts[i]); - } + key sumPseudoOuts = addKeys(pseudoOuts); DP(sumPseudoOuts); //check pseudoOuts vs Outs.. diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index a85b94541..be511a2d2 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -208,6 +208,7 @@ namespace cryptonote res.was_bootstrap_ever_used = m_was_bootstrap_ever_used; } res.database_size = m_core.get_blockchain_storage().get_db().get_database_size(); + res.update_available = m_core.is_update_available(); return true; } //------------------------------------------------------------------------------------------------------------------------------ @@ -220,6 +221,15 @@ namespace cryptonote return ss.str(); } //------------------------------------------------------------------------------------------------------------------------------ + static cryptonote::blobdata get_pruned_tx_json(cryptonote::transaction &tx) + { + std::stringstream ss; + json_archive<true> ar(ss); + bool r = tx.serialize_base(ar); + CHECK_AND_ASSERT_MES(r, cryptonote::blobdata(), "Failed to serialize rct signatures base"); + return ss.str(); + } + //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_blocks(const COMMAND_RPC_GET_BLOCKS_FAST::request& req, COMMAND_RPC_GET_BLOCKS_FAST::response& res) { PERF_TIMER(on_get_blocks); @@ -557,7 +567,7 @@ namespace cryptonote blobdata blob = req.prune ? get_pruned_tx_blob(tx) : t_serializable_object_to_blob(tx); e.as_hex = string_tools::buff_to_hex_nodelimer(blob); if (req.decode_as_json) - e.as_json = obj_to_json_str(tx); + e.as_json = req.prune ? get_pruned_tx_json(tx) : obj_to_json_str(tx); e.in_pool = pool_tx_hashes.find(tx_hash) != pool_tx_hashes.end(); if (e.in_pool) { @@ -1247,6 +1257,7 @@ namespace cryptonote response.depth = m_core.get_current_blockchain_height() - height - 1; response.hash = string_tools::pod_to_hex(hash); response.difficulty = m_core.get_blockchain_storage().block_difficulty(height); + response.cumulative_difficulty = response.block_weight = m_core.get_blockchain_storage().get_db().get_block_cumulative_difficulty(height); response.reward = get_block_reward(blk); response.block_size = response.block_weight = m_core.get_blockchain_storage().get_db().get_block_weight(height); response.num_txes = blk.tx_hashes.size(); @@ -1594,6 +1605,7 @@ namespace cryptonote res.was_bootstrap_ever_used = m_was_bootstrap_ever_used; } res.database_size = m_core.get_blockchain_storage().get_db().get_database_size(); + res.update_available = m_core.is_update_available(); return true; } //------------------------------------------------------------------------------------------------------------------------------ diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 1a84ee614..3b654d4cb 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -49,7 +49,7 @@ namespace cryptonote // advance which version they will stop working with // Don't go over 32767 for any of these #define CORE_RPC_VERSION_MAJOR 2 -#define CORE_RPC_VERSION_MINOR 0 +#define CORE_RPC_VERSION_MINOR 1 #define MAKE_CORE_RPC_VERSION(major,minor) (((major)<<16)|(minor)) #define CORE_RPC_VERSION MAKE_CORE_RPC_VERSION(CORE_RPC_VERSION_MAJOR, CORE_RPC_VERSION_MINOR) @@ -893,6 +893,7 @@ namespace cryptonote uint64_t height_without_bootstrap; bool was_bootstrap_ever_used; uint64_t database_size; + bool update_available; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(status) @@ -926,6 +927,7 @@ namespace cryptonote KV_SERIALIZE(height_without_bootstrap) KV_SERIALIZE(was_bootstrap_ever_used) KV_SERIALIZE(database_size) + KV_SERIALIZE(update_available) END_KV_SERIALIZE_MAP() }; }; @@ -1120,6 +1122,7 @@ namespace cryptonote uint64_t depth; std::string hash; difficulty_type difficulty; + difficulty_type cumulative_difficulty; uint64_t reward; uint64_t block_size; uint64_t block_weight; @@ -1137,6 +1140,7 @@ namespace cryptonote KV_SERIALIZE(depth) KV_SERIALIZE(hash) KV_SERIALIZE(difficulty) + KV_SERIALIZE(cumulative_difficulty) KV_SERIALIZE(reward) KV_SERIALIZE(block_size) KV_SERIALIZE_OPT(block_weight, (uint64_t)0) diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 391d1f03e..302d2a999 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -108,8 +108,7 @@ typedef cryptonote::simple_wallet sw; tools::wallet_keys_unlocker unlocker(*m_wallet, pwd_container); enum TransferType { - TransferOriginal, - TransferNew, + Transfer, TransferLocked, }; @@ -2297,11 +2296,7 @@ simple_wallet::simple_wallet() m_cmd_binder.set_handler("bc_height", boost::bind(&simple_wallet::show_blockchain_height, this, _1), tr("Show the blockchain height.")); - m_cmd_binder.set_handler("transfer_original", - boost::bind(&simple_wallet::transfer, this, _1), - tr("transfer_original [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] (<URI> | <address> <amount>) [<payment_id>]"), - tr("Transfer <amount> to <address> using an older transaction building algorithm. If the parameter \"index=<N1>[,<N2>,...]\" is specified, the wallet uses outputs received by addresses of those indices. If omitted, the wallet randomly chooses address indices to be used. In any case, it tries its best not to combine outputs across multiple addresses. <priority> is the priority of the transaction. The higher the priority, the higher the transaction fee. Valid values in priority order (from lowest to highest) are: unimportant, normal, elevated, priority. If omitted, the default value (see the command \"set priority\") is used. <ring_size> is the number of inputs to include for untraceability. Multiple payments can be made at once by adding URI_2 or <address_2> <amount_2> etcetera (before the payment ID, if it's included)")); - m_cmd_binder.set_handler("transfer", boost::bind(&simple_wallet::transfer_new, this, _1), + m_cmd_binder.set_handler("transfer", boost::bind(&simple_wallet::transfer, this, _1), tr("transfer [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] (<URI> | <address> <amount>) [<payment_id>]"), tr("Transfer <amount> to <address>. If the parameter \"index=<N1>[,<N2>,...]\" is specified, the wallet uses outputs received by addresses of those indices. If omitted, the wallet randomly chooses address indices to be used. In any case, it tries its best not to combine outputs across multiple addresses. <priority> is the priority of the transaction. The higher the priority, the higher the transaction fee. Valid values in priority order (from lowest to highest) are: unimportant, normal, elevated, priority. If omitted, the default value (see the command \"set priority\") is used. <ring_size> is the number of inputs to include for untraceability. Multiple payments can be made at once by adding URI_2 or <address_2> <amount_2> etcetera (before the payment ID, if it's included)")); m_cmd_binder.set_handler("locked_transfer", @@ -2316,15 +2311,15 @@ simple_wallet::simple_wallet() boost::bind(&simple_wallet::sweep_unmixable, this, _1), tr("Send all unmixable outputs to yourself with ring_size 1")); m_cmd_binder.set_handler("sweep_all", boost::bind(&simple_wallet::sweep_all, this, _1), - tr("sweep_all [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <address> [<payment_id>]"), - tr("Send all unlocked balance to an address. If the parameter \"index<N1>[,<N2>,...]\" is specified, the wallet sweeps outputs received by those address indices. If omitted, the wallet randomly chooses an address index to be used.")); + tr("sweep_all [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] [outputs=<N>] <address> [<payment_id>]"), + tr("Send all unlocked balance to an address. If the parameter \"index<N1>[,<N2>,...]\" is specified, the wallet sweeps outputs received by those address indices. If omitted, the wallet randomly chooses an address index to be used. If the parameter \"outputs=<N>\" is specified and N > 0, wallet splits the transaction into N even outputs.")); m_cmd_binder.set_handler("sweep_below", boost::bind(&simple_wallet::sweep_below, this, _1), tr("sweep_below <amount_threshold> [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <address> [<payment_id>]"), tr("Send all unlocked outputs below the threshold to an address.")); m_cmd_binder.set_handler("sweep_single", boost::bind(&simple_wallet::sweep_single, this, _1), - tr("sweep_single [<priority>] [<ring_size>] <key_image> <address> [<payment_id>]"), + tr("sweep_single [<priority>] [<ring_size>] [outputs=<N>] <key_image> <address> [<payment_id>]"), tr("Send a single output of the given key image to an address without change.")); m_cmd_binder.set_handler("donate", boost::bind(&simple_wallet::donate, this, _1), @@ -3278,10 +3273,10 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) else if (!m_generate_from_json.empty()) { - m_wallet_file = m_generate_from_json; try { - m_wallet = tools::wallet2::make_from_json(vm, false, m_wallet_file, password_prompter); + m_wallet = tools::wallet2::make_from_json(vm, false, m_generate_from_json, password_prompter); + m_wallet_file = m_wallet->path(); } catch (const std::exception &e) { @@ -4203,7 +4198,7 @@ boost::optional<epee::wipeable_string> simple_wallet::on_get_password(const char // 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"); + message_writer(console_color_red, false) << boost::format(tr("Password needed (%s) - use the refresh command")) % reason; m_cmd_binder.print_prompt(); return boost::none; } @@ -4944,7 +4939,7 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri default: LOG_ERROR("Unknown transfer method, using default"); /* FALLTHRU */ - case TransferNew: + case Transfer: ptx_vector = m_wallet->create_transactions_2(dsts, fake_outs_count, 0 /* unlock_time */, priority, extra, m_current_subaddress_account, subaddr_indices); break; } @@ -5134,12 +5129,7 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri //---------------------------------------------------------------------------------------------------- bool simple_wallet::transfer(const std::vector<std::string> &args_) { - return transfer_main(TransferOriginal, args_); -} -//---------------------------------------------------------------------------------------------------- -bool simple_wallet::transfer_new(const std::vector<std::string> &args_) -{ - return transfer_main(TransferNew, args_); + return transfer_main(Transfer, args_); } //---------------------------------------------------------------------------------------------------- bool simple_wallet::locked_transfer(const std::vector<std::string> &args_) @@ -5263,7 +5253,7 @@ bool simple_wallet::sweep_main(uint64_t below, bool locked, const std::vector<st { auto print_usage = [below]() { - fail_msg_writer() << boost::format(tr("usage: %s [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <address> [<payment_id>]")) % (below ? "sweep_below" : "sweep_all"); + fail_msg_writer() << boost::format(tr("usage: %s [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] [outputs=<N>] <address> [<payment_id>]")) % (below ? "sweep_below" : "sweep_all"); }; if (args_.size() == 0) { @@ -5361,6 +5351,25 @@ bool simple_wallet::sweep_main(uint64_t below, bool locked, const std::vector<st local_args.erase(local_args.begin() + 1); } + size_t outputs = 1; + if (local_args.size() > 0 && local_args[0].substr(0, 8) == "outputs=") + { + if (!epee::string_tools::get_xtype_from_string(outputs, local_args[0].substr(8))) + { + fail_msg_writer() << tr("Failed to parse number of outputs"); + return true; + } + else if (outputs < 1) + { + fail_msg_writer() << tr("Amount of outputs should be greater than 0"); + return true; + } + else + { + local_args.erase(local_args.begin()); + } + } + std::vector<uint8_t> extra; bool payment_id_seen = false; if (local_args.size() >= 2) @@ -5445,7 +5454,7 @@ bool simple_wallet::sweep_main(uint64_t below, bool locked, const std::vector<st 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); + auto ptx_vector = m_wallet->create_transactions_all(below, info.address, info.is_subaddress, outputs, fake_outs_count, unlock_block /* unlock_time */, priority, extra, m_current_subaddress_account, subaddr_indices); if (ptx_vector.empty()) { @@ -5586,6 +5595,25 @@ bool simple_wallet::sweep_single(const std::vector<std::string> &args_) return true; } + size_t outputs = 1; + if (local_args.size() > 0 && local_args[0].substr(0, 8) == "outputs=") + { + if (!epee::string_tools::get_xtype_from_string(outputs, local_args[0].substr(8))) + { + fail_msg_writer() << tr("Failed to parse number of outputs"); + return true; + } + else if (outputs < 1) + { + fail_msg_writer() << tr("Amount of outputs should be greater than 0"); + return true; + } + else + { + local_args.erase(local_args.begin()); + } + } + std::vector<uint8_t> extra; bool payment_id_seen = false; if (local_args.size() == 3) @@ -5619,7 +5647,7 @@ bool simple_wallet::sweep_single(const std::vector<std::string> &args_) if (local_args.size() != 2) { - fail_msg_writer() << tr("usage: sweep_single [<priority>] [<ring_size>] <key_image> <address> [<payment_id>]"); + fail_msg_writer() << tr("usage: sweep_single [<priority>] [<ring_size>] [outputs=<N>] <key_image> <address> [<payment_id>]"); return true; } @@ -5674,7 +5702,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); + auto ptx_vector = m_wallet->create_transactions_single(ki, info.address, info.is_subaddress, outputs, fake_outs_count, 0 /* unlock_time */, priority, extra); if (ptx_vector.empty()) { @@ -5810,7 +5838,7 @@ bool simple_wallet::donate(const std::vector<std::string> &args_) if (!payment_id_str.empty()) local_args.push_back(payment_id_str); message_writer() << (boost::format(tr("Donating %s %s to The Monero Project (donate.getmonero.org or %s).")) % amount_str % cryptonote::get_unit(cryptonote::get_default_decimal_point()) % MONERO_DONATION_ADDR).str(); - transfer_new(local_args); + transfer(local_args); return true; } //---------------------------------------------------------------------------------------------------- diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index d50e4ce04..2d23d6248 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -152,7 +152,6 @@ namespace cryptonote bool show_blockchain_height(const std::vector<std::string> &args); bool transfer_main(int transfer_type, const std::vector<std::string> &args); bool transfer(const std::vector<std::string> &args); - bool transfer_new(const std::vector<std::string> &args); bool locked_transfer(const std::vector<std::string> &args); bool locked_sweep_all(const std::vector<std::string> &args); bool sweep_main(uint64_t below, bool locked, const std::vector<std::string> &args); diff --git a/src/version.cpp.in b/src/version.cpp.in index 55ba51f50..55075a064 100644 --- a/src/version.cpp.in +++ b/src/version.cpp.in @@ -1,6 +1,6 @@ #define DEF_MONERO_VERSION_TAG "@VERSIONTAG@" -#define DEF_MONERO_VERSION "0.12.3.0-master" -#define DEF_MONERO_RELEASE_NAME "Lithium Luna" +#define DEF_MONERO_VERSION "0.13.0.1-rc" +#define DEF_MONERO_RELEASE_NAME "Beryllium Bullet" #define DEF_MONERO_VERSION_FULL DEF_MONERO_VERSION "-" DEF_MONERO_VERSION_TAG #include "version.h" diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index 463185284..5827e4d1a 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -639,6 +639,11 @@ bool WalletImpl::recoverFromDevice(const std::string &path, const std::string &p return true; } +Wallet::Device WalletImpl::getDeviceType() const +{ + return static_cast<Wallet::Device>(m_wallet->get_device_type()); +} + bool WalletImpl::open(const std::string &path, const std::string &password) { clearStatus(); @@ -1380,7 +1385,7 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const for (uint32_t index = 0; index < m_wallet->get_num_subaddresses(subaddr_account); ++index) subaddr_indices.insert(index); } - transaction->m_pending_tx = m_wallet->create_transactions_all(0, info.address, info.is_subaddress, fake_outs_count, 0 /* unlock_time */, + transaction->m_pending_tx = m_wallet->create_transactions_all(0, info.address, info.is_subaddress, 1, fake_outs_count, 0 /* unlock_time */, adjusted_priority, extra, subaddr_account, subaddr_indices); } diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h index 2cbfa30d0..64350cee7 100644 --- a/src/wallet/api/wallet.h +++ b/src/wallet/api/wallet.h @@ -79,6 +79,7 @@ public: bool recoverFromDevice(const std::string &path, const std::string &password, const std::string &device_name); + Device getDeviceType() const override; bool close(bool store = true); std::string seed() const override; std::string getSeedLanguage() const override; diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h index 933916076..e0d491705 100644 --- a/src/wallet/api/wallet2_api.h +++ b/src/wallet/api/wallet2_api.h @@ -373,6 +373,10 @@ struct WalletListener */ struct Wallet { + enum Device { + Device_Software = 0, + Device_Ledger = 1 + }; enum Status { Status_Ok, @@ -911,6 +915,12 @@ struct Wallet virtual bool unlockKeysFile() = 0; //! returns true if the keys file is locked virtual bool isKeysFileLocked() = 0; + + /*! + * \brief Queries backing device for wallet keys + * \return Device they are on + */ + virtual Device getDeviceType() const = 0; }; /** @@ -1097,6 +1107,18 @@ struct WalletManager virtual bool verifyWalletPassword(const std::string &keys_file_name, const std::string &password, bool no_spend_key, uint64_t kdf_rounds = 1) const = 0; /*! + * \brief determine the key storage for the specified wallet file + * \param device_type (OUT) wallet backend as enumerated in Wallet::Device + * \param keys_file_name Keys file to verify password for + * \param password Password to verify + * \return true if password correct, else false + * + * for verification only - determines key storage hardware + * + */ + virtual bool queryWalletDevice(Wallet::Device& device_type, const std::string &keys_file_name, const std::string &password, uint64_t kdf_rounds = 1) const = 0; + + /*! * \brief findWallets - searches for the wallet files by given path name recursively * \param path - starting point to search * \return - list of strings with found wallets (absolute paths); diff --git a/src/wallet/api/wallet_manager.cpp b/src/wallet/api/wallet_manager.cpp index 3851ca9cc..5b262f1b7 100644 --- a/src/wallet/api/wallet_manager.cpp +++ b/src/wallet/api/wallet_manager.cpp @@ -167,6 +167,14 @@ bool WalletManagerImpl::verifyWalletPassword(const std::string &keys_file_name, return tools::wallet2::verify_password(keys_file_name, password, no_spend_key, hw::get_device("default"), kdf_rounds); } +bool WalletManagerImpl::queryWalletDevice(Wallet::Device& device_type, const std::string &keys_file_name, const std::string &password, uint64_t kdf_rounds) const +{ + hw::device::device_type type; + bool r = tools::wallet2::query_device(type, keys_file_name, password, kdf_rounds); + device_type = static_cast<Wallet::Device>(type); + return r; +} + std::vector<std::string> WalletManagerImpl::findWallets(const std::string &path) { std::vector<std::string> result; diff --git a/src/wallet/api/wallet_manager.h b/src/wallet/api/wallet_manager.h index 8b1c8be7f..b3c0d6c00 100644 --- a/src/wallet/api/wallet_manager.h +++ b/src/wallet/api/wallet_manager.h @@ -76,6 +76,7 @@ public: virtual bool closeWallet(Wallet *wallet, bool store = true) override; bool walletExists(const std::string &path) override; bool verifyWalletPassword(const std::string &keys_file_name, const std::string &password, bool no_spend_key, uint64_t kdf_rounds = 1) const override; + bool queryWalletDevice(Wallet::Device& device_type, const std::string &keys_file_name, const std::string &password, uint64_t kdf_rounds = 1) const override; std::vector<std::string> findWallets(const std::string &path) override; std::string errorString() const override; void setDaemonAddress(const std::string &address) override; diff --git a/src/wallet/node_rpc_proxy.cpp b/src/wallet/node_rpc_proxy.cpp index 21f75371b..346c052b5 100644 --- a/src/wallet/node_rpc_proxy.cpp +++ b/src/wallet/node_rpc_proxy.cpp @@ -69,7 +69,7 @@ boost::optional<std::string> NodeRPCProxy::get_rpc_version(uint32_t &rpc_version m_daemon_rpc_mutex.lock(); bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_version", req_t, resp_t, m_http_client, rpc_timeout); m_daemon_rpc_mutex.unlock(); - CHECK_AND_ASSERT_MES(r, std::string(), "Failed to connect to daemon"); + CHECK_AND_ASSERT_MES(r, std::string("Failed to connect to daemon"), "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 daemon RPC version"); m_rpc_version = resp_t.version; @@ -95,7 +95,7 @@ boost::optional<std::string> NodeRPCProxy::get_info() const bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_info", req_t, resp_t, m_http_client, rpc_timeout); m_daemon_rpc_mutex.unlock(); - CHECK_AND_ASSERT_MES(r, std::string(), "Failed to connect to daemon"); + CHECK_AND_ASSERT_MES(r, std::string("Failed to connect to daemon"), "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 target blockchain height"); m_height = resp_t.height; @@ -144,7 +144,7 @@ boost::optional<std::string> NodeRPCProxy::get_earliest_height(uint8_t version, req_t.version = version; bool r = net_utils::invoke_http_json_rpc("/json_rpc", "hard_fork_info", req_t, resp_t, m_http_client, rpc_timeout); m_daemon_rpc_mutex.unlock(); - CHECK_AND_ASSERT_MES(r, std::string(), "Failed to connect to daemon"); + CHECK_AND_ASSERT_MES(r, std::string("Failed to connect to daemon"), "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.earliest_height; @@ -171,7 +171,7 @@ boost::optional<std::string> NodeRPCProxy::get_dynamic_base_fee_estimate(uint64_ req_t.grace_blocks = grace_blocks; bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_fee_estimate", req_t, resp_t, m_http_client, rpc_timeout); m_daemon_rpc_mutex.unlock(); - CHECK_AND_ASSERT_MES(r, std::string(), "Failed to connect to daemon"); + CHECK_AND_ASSERT_MES(r, std::string("Failed to connect to daemon"), "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 fee estimate"); m_dynamic_base_fee_estimate = resp_t.fee; @@ -201,7 +201,7 @@ boost::optional<std::string> NodeRPCProxy::get_fee_quantization_mask(uint64_t &f req_t.grace_blocks = m_dynamic_base_fee_estimate_grace_blocks; bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_fee_estimate", req_t, resp_t, m_http_client, rpc_timeout); m_daemon_rpc_mutex.unlock(); - CHECK_AND_ASSERT_MES(r, std::string(), "Failed to connect to daemon"); + CHECK_AND_ASSERT_MES(r, std::string("Failed to connect to daemon"), "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 fee estimate"); m_dynamic_base_fee_estimate = resp_t.fee; diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 6502463e2..33699cb79 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -794,7 +794,7 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended): m_light_wallet_connected(false), m_light_wallet_balance(0), m_light_wallet_unlocked_balance(0), - m_key_on_device(false), + m_key_device_type(hw::device::device_type::SOFTWARE), m_ring_history_saved(false), m_ringdb(), m_last_block_reward(0), @@ -1344,6 +1344,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote size_t pk_index = 0; std::vector<tx_scan_info_t> tx_scan_info(tx.vout.size()); std::deque<bool> output_found(tx.vout.size(), false); + uint64_t total_received_1 = 0; while (!tx.vout.empty()) { // if tx.vout is not empty, we loop through all tx pubkeys @@ -1518,6 +1519,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote + ", m_transfers.size() is " + boost::lexical_cast<std::string>(m_transfers.size())); if (kit == m_pub_keys.end()) { + uint64_t amount = tx.vout[o].amount ? tx.vout[o].amount : tx_scan_info[o].amount; if (!pool) { m_transfers.push_back(boost::value_initialized<transfer_details>()); @@ -1530,14 +1532,13 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote td.m_key_image = tx_scan_info[o].ki; td.m_key_image_known = !m_watch_only && !m_multisig; td.m_key_image_partial = m_multisig; - td.m_amount = tx.vout[o].amount; + td.m_amount = amount; td.m_pk_index = pk_index - 1; td.m_subaddr_index = tx_scan_info[o].received->index; expand_subaddresses(tx_scan_info[o].received->index); - if (td.m_amount == 0) + if (tx.vout[o].amount == 0) { td.m_mask = tx_scan_info[o].mask; - td.m_amount = tx_scan_info[o].amount; td.m_rct = true; } else if (miner_tx && tx.version == 2) @@ -1565,6 +1566,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote if (0 != m_callback) m_callback->on_money_received(height, txid, tx, td.m_amount, td.m_subaddr_index); } + total_received_1 += amount; } else if (m_transfers[kit->second].m_spent || m_transfers[kit->second].amount() >= tx_scan_info[o].amount) { @@ -1572,6 +1574,9 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote << " from received " << print_money(tx_scan_info[o].amount) << " output already exists with " << (m_transfers[kit->second].m_spent ? "spent" : "unspent") << " " << print_money(m_transfers[kit->second].amount()) << " in tx " << m_transfers[kit->second].m_txid << ", received output ignored"); + THROW_WALLET_EXCEPTION_IF(tx_money_got_in_outs[tx_scan_info[o].received->index] < tx_scan_info[o].amount, + error::wallet_internal_error, "Unexpected values of new and old outputs"); + tx_money_got_in_outs[tx_scan_info[o].received->index] -= tx_scan_info[o].amount; } else { @@ -1579,8 +1584,14 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote << " from received " << print_money(tx_scan_info[o].amount) << " output already exists with " << print_money(m_transfers[kit->second].amount()) << ", replacing with new output"); // The new larger output replaced a previous smaller one - tx_money_got_in_outs[tx_scan_info[o].received->index] -= tx_scan_info[o].amount; - + THROW_WALLET_EXCEPTION_IF(tx_money_got_in_outs[tx_scan_info[o].received->index] < tx_scan_info[o].amount, + error::wallet_internal_error, "Unexpected values of new and old outputs"); + THROW_WALLET_EXCEPTION_IF(m_transfers[kit->second].amount() > tx_scan_info[o].amount, + error::wallet_internal_error, "Unexpected values of new and old outputs"); + tx_money_got_in_outs[tx_scan_info[o].received->index] -= m_transfers[kit->second].amount(); + + uint64_t amount = tx.vout[o].amount ? tx.vout[o].amount : tx_scan_info[o].amount; + uint64_t extra_amount = amount - m_transfers[kit->second].amount(); if (!pool) { transfer_details &td = m_transfers[kit->second]; @@ -1589,14 +1600,13 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote td.m_global_output_index = o_indices[o]; td.m_tx = (const cryptonote::transaction_prefix&)tx; td.m_txid = txid; - td.m_amount = tx.vout[o].amount; + td.m_amount = amount; td.m_pk_index = pk_index - 1; td.m_subaddr_index = tx_scan_info[o].received->index; expand_subaddresses(tx_scan_info[o].received->index); - if (td.m_amount == 0) + if (tx.vout[o].amount == 0) { td.m_mask = tx_scan_info[o].mask; - td.m_amount = tx_scan_info[o].amount; td.m_rct = true; } else if (miner_tx && tx.version == 2) @@ -1623,6 +1633,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote if (0 != m_callback) m_callback->on_money_received(height, txid, tx, td.m_amount, td.m_subaddr_index); } + total_received_1 += extra_amount; } } } @@ -1744,6 +1755,20 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote } } + uint64_t total_received_2 = 0; + for (const auto& i : tx_money_got_in_outs) + total_received_2 += i.second; + if (total_received_1 != total_received_2) + { + const el::Level level = el::Level::Warning; + MCLOG_RED(level, "global", "**********************************************************************"); + MCLOG_RED(level, "global", "Consistency failure in amounts received"); + MCLOG_RED(level, "global", "Check transaction " << txid); + MCLOG_RED(level, "global", "**********************************************************************"); + exit(1); + return; + } + for (const auto& i : tx_money_got_in_outs) { payment_details payment; @@ -2908,7 +2933,7 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable rapidjson::Value value2(rapidjson::kNumberType); - value2.SetInt(m_key_on_device?1:0); + value2.SetInt(m_key_device_type); json.AddMember("key_on_device", value2, json.GetAllocator()); value2.SetInt(watch_only ? 1 :0); // WTF ? JSON has different true and false types, and not boolean ?? @@ -3121,7 +3146,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_device_name = ""; - m_key_on_device = false; + m_key_device_type = hw::device::device_type::SOFTWARE; encrypted_secret_keys = false; } else if(json.IsObject()) @@ -3141,8 +3166,8 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ if (json.HasMember("key_on_device")) { - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, key_on_device, int, Int, false, false); - m_key_on_device = field_key_on_device; + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, key_on_device, int, Int, false, hw::device::device_type::SOFTWARE); + m_key_device_type = static_cast<hw::device::device_type>(field_key_on_device); } GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, seed_language, std::string, String, false, std::string()); @@ -3269,7 +3294,8 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ } r = epee::serialization::load_t_from_binary(m_account, account_data); - if (r && m_key_on_device) { + THROW_WALLET_EXCEPTION_IF(!r, error::invalid_password); + if (m_key_device_type == hw::device::device_type::LEDGER) { LOG_PRINT_L0("Account on device. Initing device..."); hw::device &hwdev = hw::get_device(m_device_name); hwdev.set_name(m_device_name); @@ -3277,6 +3303,8 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ hwdev.connect(); m_account.set_device(hwdev); LOG_PRINT_L0("Device inited..."); + } else if (key_on_device()) { + THROW_WALLET_EXCEPTION(error::wallet_internal_error, "hardware device not supported"); } if (r) @@ -3445,6 +3473,59 @@ void wallet2::create_keys_file(const std::string &wallet_, bool watch_only, cons /*! + * \brief determine the key storage for the specified wallet file + * \param device_type (OUT) wallet backend as enumerated in hw::device::device_type + * \param keys_file_name Keys file to verify password for + * \param password Password to verify + * \return true if password correct, else false + * + * for verification only - determines key storage hardware + * + */ +bool wallet2::query_device(hw::device::device_type& device_type, const std::string& keys_file_name, const epee::wipeable_string& password, uint64_t kdf_rounds) +{ + rapidjson::Document json; + wallet2::keys_file_data keys_file_data; + std::string buf; + bool r = epee::file_io_utils::load_file_to_string(keys_file_name, buf); + THROW_WALLET_EXCEPTION_IF(!r, error::file_read_error, keys_file_name); + + // Decrypt the contents + r = ::serialization::parse_binary(buf, keys_file_data); + THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "internal error: failed to deserialize \"" + keys_file_name + '\"'); + crypto::chacha_key key; + crypto::generate_chacha_key(password.data(), password.size(), key, kdf_rounds); + std::string account_data; + account_data.resize(keys_file_data.account_data.size()); + crypto::chacha20(keys_file_data.account_data.data(), keys_file_data.account_data.size(), key, keys_file_data.iv, &account_data[0]); + if (json.Parse(account_data.c_str()).HasParseError() || !json.IsObject()) + crypto::chacha8(keys_file_data.account_data.data(), keys_file_data.account_data.size(), key, keys_file_data.iv, &account_data[0]); + + // The contents should be JSON if the wallet follows the new format. + if (json.Parse(account_data.c_str()).HasParseError()) + { + // old format before JSON wallet key file format + } + else + { + account_data = std::string(json["key_data"].GetString(), json["key_data"].GetString() + + json["key_data"].GetStringLength()); + + if (json.HasMember("key_on_device")) + { + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, key_on_device, int, Int, false, hw::device::device_type::SOFTWARE); + device_type = static_cast<hw::device::device_type>(field_key_on_device); + } + } + + cryptonote::account_base account_data_check; + + r = epee::serialization::load_t_from_binary(account_data_check, account_data); + if (!r) return false; + return true; +} + +/*! * \brief Generates a wallet or restores one. * \param wallet_ Name of wallet file * \param password Password of wallet file @@ -3518,7 +3599,7 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& m_multisig = true; m_multisig_threshold = threshold; m_multisig_signers = multisig_signers; - m_key_on_device = false; + m_key_device_type = hw::device::device_type::SOFTWARE; setup_keys(password); create_keys_file(wallet_, false, password, m_nettype != MAINNET || create_address_file); @@ -3558,7 +3639,7 @@ crypto::secret_key wallet2::generate(const std::string& wallet_, const epee::wip m_multisig = false; m_multisig_threshold = 0; m_multisig_signers.clear(); - m_key_on_device = false; + m_key_device_type = hw::device::device_type::SOFTWARE; setup_keys(password); // calculate a starting refresh height @@ -3646,7 +3727,7 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& m_multisig = false; m_multisig_threshold = 0; m_multisig_signers.clear(); - m_key_on_device = false; + m_key_device_type = hw::device::device_type::SOFTWARE; setup_keys(password); create_keys_file(wallet_, true, password, m_nettype != MAINNET || create_address_file); @@ -3686,7 +3767,7 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& m_multisig = false; m_multisig_threshold = 0; m_multisig_signers.clear(); - m_key_on_device = false; + m_key_device_type = hw::device::device_type::SOFTWARE; setup_keys(password); create_keys_file(wallet_, false, password, create_address_file); @@ -3713,12 +3794,12 @@ void wallet2::restore(const std::string& wallet_, const epee::wipeable_string& p THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_wallet_file, ignored_ec), error::file_exists, m_wallet_file); THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_keys_file, ignored_ec), error::file_exists, m_keys_file); } - m_key_on_device = true; auto &hwdev = hw::get_device(device_name); hwdev.set_name(device_name); m_account.create_from_device(hwdev); + m_key_device_type = m_account.get_device().get_type(); m_account_public_address = m_account.get_keys().m_account_address; m_watch_only = false; m_multisig = false; @@ -3815,7 +3896,7 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password, m_watch_only = false; m_multisig = true; m_multisig_threshold = threshold; - m_key_on_device = false; + m_key_device_type = hw::device::device_type::SOFTWARE; if (threshold == spend_keys.size() + 1) { @@ -6642,11 +6723,23 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> } // while we still need more mixins + uint64_t num_usable_outs = num_outs; + bool allow_blackballed = false; while (num_found < requested_outputs_count) { // if we've gone through every possible output, we've gotten all we can - if (seen_indices.size() == num_outs) - break; + if (seen_indices.size() == num_usable_outs) + { + // there is a first pass which rejects blackballed outputs, then a second pass + // which allows them if we don't have enough non blackballed outputs to reach + // the required amount of outputs (since consensus does not care about blackballed + // outputs, we still need to reach the minimum ring size) + if (allow_blackballed) + break; + MINFO("Not enough non blackballed outputs, we'll allow blackballed ones"); + allow_blackballed = true; + num_usable_outs = num_outs; + } // get a random output index from the DB. If we've already seen it, // return to the top of the loop and try again, otherwise add it to the @@ -6720,14 +6813,26 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> if (seen_indices.count(i)) continue; - if (is_output_blackballed(std::make_pair(amount, i))) // don't add blackballed outputs + if (!allow_blackballed && is_output_blackballed(std::make_pair(amount, i))) // don't add blackballed outputs + { + --num_usable_outs; continue; + } seen_indices.emplace(i); LOG_PRINT_L2("picking " << i << " as " << type); req.outputs.push_back({amount, i}); ++num_found; } + + // if we had enough unusable outputs, we might fall off here and still + // have too few outputs, so we stuff with one to keep counts good, and + // we'll error out later + while (num_found < requested_outputs_count) + { + req.outputs.push_back({amount, 0}); + ++num_found; + } } // sort the subsection, to ensure the daemon doesn't know which output is ours @@ -8408,7 +8513,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) +std::vector<wallet2::pending_tx> wallet2::create_transactions_all(uint64_t below, const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, const size_t fake_outs_count, 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; @@ -8459,10 +8564,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); + return create_transactions_from(address, is_subaddress, outputs, 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) +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 outputs, 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; @@ -8480,10 +8585,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); + return create_transactions_from(address, is_subaddress, outputs, 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) +std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, 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(); @@ -8578,7 +8683,9 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton needed_fee = estimate_fee(use_per_byte_fee, use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, base_fee, fee_multiplier, fee_quantization_mask); - tx.dsts.push_back(tx_destination_entry(1, address, is_subaddress)); + // add N - 1 outputs for correct initial fee estimation + for (size_t i = 0; i < ((outputs > 1) ? outputs - 1 : outputs); ++i) + tx.dsts.push_back(tx_destination_entry(1, address, is_subaddress)); LOG_PRINT_L2("Trying to create a tx now, with " << tx.dsts.size() << " destinations and " << tx.selected_transfers.size() << " outputs"); @@ -8590,15 +8697,35 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx); auto txBlob = t_serializable_object_to_blob(test_ptx.tx); needed_fee = calculate_fee(use_per_byte_fee, test_ptx.tx, txBlob.size(), base_fee, fee_multiplier, fee_quantization_mask); - available_for_fee = test_ptx.fee + test_ptx.dests[0].amount + test_ptx.change_dts.amount; + available_for_fee = test_ptx.fee + test_ptx.change_dts.amount; + for (auto &dt: test_ptx.dests) + available_for_fee += dt.amount; LOG_PRINT_L2("Made a " << get_weight_string(test_ptx.tx, txBlob.size()) << " tx, with " << print_money(available_for_fee) << " available for fee (" << print_money(needed_fee) << " needed)"); + // add last output, missed for fee estimation + if (outputs > 1) + tx.dsts.push_back(tx_destination_entry(1, address, is_subaddress)); + THROW_WALLET_EXCEPTION_IF(needed_fee > available_for_fee, error::wallet_internal_error, "Transaction cannot pay for itself"); do { LOG_PRINT_L2("We made a tx, adjusting fee and saving it"); - tx.dsts[0].amount = available_for_fee - needed_fee; + // distribute total transferred amount between outputs + uint64_t amount_transferred = available_for_fee - needed_fee; + uint64_t dt_amount = amount_transferred / outputs; + // residue is distributed as one atomic unit per output until it reaches zero + uint64_t residue = amount_transferred % outputs; + for (auto &dt: tx.dsts) + { + uint64_t dt_residue = 0; + if (residue > 0) + { + dt_residue = 1; + residue -= 1; + } + dt.amount = dt_amount + dt_residue; + } if (use_rct) transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra, test_tx, test_ptx, range_proof_type); @@ -8845,7 +8972,7 @@ std::vector<wallet2::pending_tx> wallet2::create_unmixable_sweep_transactions() 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>()); + return create_transactions_from(m_account_public_address, false, 1, unmixable_transfer_outputs, unmixable_dust_outputs, 0 /*fake_outs_count */, 0 /* unlock_time */, 1 /*priority */, std::vector<uint8_t>()); } //---------------------------------------------------------------------------------------------------- void wallet2::discard_unmixable_outputs() diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index c30ca1d85..acbc09c36 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -189,6 +189,7 @@ namespace tools static std::unique_ptr<wallet2> make_dummy(const boost::program_options::variables_map& vm, bool unattended, 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); + static bool query_device(hw::device::device_type& device_type, const std::string& keys_file_name, const epee::wipeable_string& password, uint64_t kdf_rounds = 1); wallet2(cryptonote::network_type nettype = cryptonote::MAINNET, uint64_t kdf_rounds = 1, bool unattended = false); ~wallet2(); @@ -709,7 +710,8 @@ namespace tools bool has_multisig_partial_key_images() const; bool has_unknown_key_images() const; bool get_multisig_seed(epee::wipeable_string& seed, const epee::wipeable_string &passphrase = std::string(), bool raw = true) const; - bool key_on_device() const { return m_key_on_device; } + bool key_on_device() const { return get_device_type() != hw::device::device_type::SOFTWARE; } + hw::device::device_type get_device_type() const { return m_key_device_type; } bool reconnect_device(); // locked & unlocked balance of given or current subaddress account @@ -750,9 +752,9 @@ namespace tools 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<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); + std::vector<wallet2::pending_tx> create_transactions_all(uint64_t below, const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, const size_t fake_outs_count, 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 outputs, 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, const size_t outputs, 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); @@ -1284,7 +1286,7 @@ namespace tools bool m_trusted_daemon; i_wallet2_callback* m_callback; - bool m_key_on_device; + hw::device::device_type m_key_device_type; cryptonote::network_type m_nettype; uint64_t m_kdf_rounds; std::string seed_language; /*!< Language of the mnemonics (seed). */ diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index 86b46b173..e6eb64d12 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -1102,6 +1102,13 @@ namespace tools return false; } + if (req.outputs < 1) + { + er.code = WALLET_RPC_ERROR_CODE_TX_NOT_POSSIBLE; + er.message = "Amount of outputs should be greater than 0."; + return false; + } + try { uint64_t mixin; @@ -1114,7 +1121,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); + std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_all(req.below_amount, dsts[0].addr, dsts[0].is_subaddress, req.outputs, mixin, req.unlock_time, priority, extra, req.account_index, req.subaddr_indices); 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); @@ -1140,6 +1147,13 @@ namespace tools return false; } + if (req.outputs < 1) + { + er.code = WALLET_RPC_ERROR_CODE_TX_NOT_POSSIBLE; + er.message = "Amount of outputs should be greater than 0."; + return false; + } + // validate the transfer requested and populate dsts & extra std::list<wallet_rpc::transfer_destination> destination; destination.push_back(wallet_rpc::transfer_destination()); @@ -1170,7 +1184,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); + std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_single(ki, dsts[0].addr, dsts[0].is_subaddress, req.outputs, mixin, req.unlock_time, priority, extra); if (ptx_vector.empty()) { @@ -1539,7 +1553,7 @@ namespace tools rpc_transfers.spent = td.m_spent; rpc_transfers.global_index = td.m_global_output_index; rpc_transfers.tx_hash = epee::string_tools::pod_to_hex(td.m_txid); - rpc_transfers.subaddr_index = td.m_subaddr_index.minor; + rpc_transfers.subaddr_index = {td.m_subaddr_index.major, td.m_subaddr_index.minor}; rpc_transfers.key_image = req.verbose && td.m_key_image_known ? epee::string_tools::pod_to_hex(td.m_key_image) : ""; res.transfers.push_back(rpc_transfers); } @@ -3280,9 +3294,12 @@ class t_daemon private: const boost::program_options::variables_map& vm; + std::unique_ptr<tools::wallet_rpc_server> wrpc; + public: t_daemon(boost::program_options::variables_map const & _vm) : vm(_vm) + , wrpc(new tools::wallet_rpc_server) { } @@ -3372,17 +3389,16 @@ public: return false; } just_dir: - tools::wallet_rpc_server wrpc; - if (wal) wrpc.set_wallet(wal.release()); - bool r = wrpc.init(&vm); + if (wal) wrpc->set_wallet(wal.release()); + bool r = wrpc->init(&vm); CHECK_AND_ASSERT_MES(r, false, tools::wallet_rpc_server::tr("Failed to initialize wallet RPC server")); - tools::signal_handler::install([&wrpc](int) { - wrpc.send_stop_signal(); + tools::signal_handler::install([this](int) { + wrpc->send_stop_signal(); }); LOG_PRINT_L0(tools::wallet_rpc_server::tr("Starting wallet RPC server")); try { - wrpc.run(); + wrpc->run(); } catch (const std::exception &e) { @@ -3393,7 +3409,7 @@ public: try { LOG_PRINT_L0(tools::wallet_rpc_server::tr("Saving wallet...")); - wrpc.stop(); + wrpc->stop(); LOG_PRINT_L0(tools::wallet_rpc_server::tr("Successfully saved")); } catch (const std::exception& e) @@ -3403,6 +3419,11 @@ public: } return true; } + + void stop() + { + wrpc->send_stop_signal(); + } }; class t_executor final @@ -3410,7 +3431,9 @@ class t_executor final public: static std::string const NAME; - std::string const & name() + typedef ::t_daemon t_daemon; + + std::string const & name() const { return NAME; } @@ -3453,6 +3476,7 @@ int main(int argc, char** argv) { command_line::add_arg(desc_params, arg_prompt_for_password); daemonizer::init_options(hidden_options, desc_params); + desc_params.add(hidden_options); boost::optional<po::variables_map> vm; bool should_terminate = false; diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h index 4501cf575..2168e0f71 100644 --- a/src/wallet/wallet_rpc_server_commands_defs.h +++ b/src/wallet/wallet_rpc_server_commands_defs.h @@ -639,6 +639,7 @@ namespace wallet_rpc uint32_t priority; uint64_t mixin; uint64_t ring_size; + uint64_t outputs; uint64_t unlock_time; std::string payment_id; bool get_tx_keys; @@ -654,6 +655,7 @@ namespace wallet_rpc KV_SERIALIZE(priority) KV_SERIALIZE_OPT(mixin, (uint64_t)0) KV_SERIALIZE_OPT(ring_size, (uint64_t)0) + KV_SERIALIZE_OPT(outputs, (uint64_t)1) KV_SERIALIZE(unlock_time) KV_SERIALIZE(payment_id) KV_SERIALIZE(get_tx_keys) @@ -705,6 +707,7 @@ namespace wallet_rpc uint32_t priority; uint64_t mixin; uint64_t ring_size; + uint64_t outputs; uint64_t unlock_time; std::string payment_id; bool get_tx_key; @@ -718,6 +721,7 @@ namespace wallet_rpc KV_SERIALIZE(priority) KV_SERIALIZE_OPT(mixin, (uint64_t)0) KV_SERIALIZE_OPT(ring_size, (uint64_t)0) + KV_SERIALIZE_OPT(outputs, (uint64_t)1) KV_SERIALIZE(unlock_time) KV_SERIALIZE(payment_id) KV_SERIALIZE(get_tx_key) @@ -859,7 +863,7 @@ namespace wallet_rpc bool spent; uint64_t global_index; std::string tx_hash; - uint32_t subaddr_index; + cryptonote::subaddress_index subaddr_index; std::string key_image; BEGIN_KV_SERIALIZE_MAP() |