diff options
92 files changed, 4507 insertions, 1716 deletions
diff --git a/.github/workflows/depends.yml b/.github/workflows/depends.yml index f20bf949f..32df7dc42 100644 --- a/.github/workflows/depends.yml +++ b/.github/workflows/depends.yml @@ -17,7 +17,7 @@ env: ccache --set-config=compression=true jobs: - build-macos: + build-cross: runs-on: ubuntu-18.04 env: CCACHE_TEMPDIR: /tmp/.ccache-temp diff --git a/.gitignore b/.gitignore index a39168ac5..9f62575e5 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,12 @@ external/miniupnpc/Makefile miniupnpcstrings.h version/ ClangBuildAnalyzerSession.txt + +# gitian +contrib/gitian/builder/ +contrib/gitian/docker/ +contrib/gitian/sigs/ + # Created by https://www.gitignore.io ### C++ ### diff --git a/CMakeLists.txt b/CMakeLists.txt index 3abd0722a..b05c087cf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1076,6 +1076,7 @@ if(STATIC) set(Boost_USE_STATIC_RUNTIME ON) endif() find_package(Boost 1.58 QUIET REQUIRED COMPONENTS system filesystem thread date_time chrono regex serialization program_options locale) +add_definitions(-DBOOST_ASIO_ENABLE_SEQUENTIAL_STRAND_ALLOCATION) set(CMAKE_FIND_LIBRARY_SUFFIXES ${OLD_LIB_SUFFIXES}) if(NOT Boost_FOUND) @@ -754,7 +754,7 @@ WARN_LOGFILE = # spaces. # Note: If this tag is empty the current directory is searched. -INPUT = . +INPUT = contrib/epee external/easylogging++ src # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses @@ -805,7 +805,7 @@ EXCLUDE_SYMLINKS = NO # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories for example use the pattern */test/* -EXCLUDE_PATTERNS = */build/* */contrib/depends/* +EXCLUDE_PATTERNS = */src/crypto/crypto_ops_builder/ref10* # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the @@ -138,6 +138,8 @@ Dates are provided in the format YYYY-MM-DD. | 1978433 | 2019-11-30 | v12 | v0.15.0.0 | v0.16.0.0 | New PoW based on RandomX, only allow >= 2 outputs, change to the block median used to calculate penalty, v1 coinbases are forbidden, rct sigs in coinbase forbidden, 10 block lock time for incoming outputs | 2210000 | 2020-10-17 | v13 | v0.17.0.0 | v0.17.3.2 | New CLSAG transaction format | 2210720 | 2020-10-18 | v14 | v0.17.1.1 | v0.17.3.2 | forbid old MLSAG transaction format +| 2688888 | 2022-08-13 | v15 | v0.18.0.0 | v0.18.0.0 | ringsize = 16, bulletproofs+, view tags, adjusted dynamic block weight algorithm +| 2689608 | 2022-08-14 | v16 | v0.18.0.0 | v0.18.0.0 | forbid old v14 transaction format | XXXXXXX | XXX-XX-XX | XXX | vX.XX.X.X | vX.XX.X.X | XXX | X's indicate that these details have not been determined as of commit date. @@ -264,7 +266,7 @@ invokes cmake commands as needed. ```bash cd monero - git checkout release-v0.17 + git checkout release-v0.18 make ``` @@ -343,7 +345,7 @@ Tested on a Raspberry Pi Zero with a clean install of minimal Raspbian Stretch ( ```bash git clone https://github.com/monero-project/monero.git cd monero - git checkout v0.17.3.2 + git checkout v0.18.0.0 ``` * Build: @@ -462,10 +464,10 @@ application. cd monero ``` -* If you would like a specific [version/tag](https://github.com/monero-project/monero/tags), do a git checkout for that version. eg. 'v0.17.3.2'. If you don't care about the version and just want binaries from master, skip this step: +* If you would like a specific [version/tag](https://github.com/monero-project/monero/tags), do a git checkout for that version. eg. 'v0.18.0.0'. If you don't care about the version and just want binaries from master, skip this step: ```bash - git checkout v0.17.3.2 + git checkout v0.18.0.0 ``` * If you are on a 64-bit system, run: @@ -579,7 +581,7 @@ You can also cross-compile static binaries on Linux for Windows and macOS with t * ```make depends target=aarch64-linux-android``` for 64bit android binaries -The required packages are the names for each toolchain on apt. Depending on your distro, they may have different names. +The required packages are the names for each toolchain on apt. Depending on your distro, they may have different names. The `depends` system has been tested on Ubuntu 18.04 and 20.04. Using `depends` might also be easier to compile Monero on Windows than using MSYS. Activate Windows Subsystem for Linux (WSL) with a distro (for example Ubuntu), install the apt build-essentials and follow the `depends` steps as depicted above. diff --git a/contrib/depends/packages/unbound.mk b/contrib/depends/packages/unbound.mk index a85c47e4e..9336524f3 100644 --- a/contrib/depends/packages/unbound.mk +++ b/contrib/depends/packages/unbound.mk @@ -4,6 +4,8 @@ $(package)_download_path=https://www.nlnetlabs.nl/downloads/$(package)/ $(package)_file_name=$(package)-$($(package)_version).tar.gz $(package)_sha256_hash=a480dc6c8937447b98d161fe911ffc76cfaffa2da18788781314e81339f1126f $(package)_dependencies=openssl expat ldns +$(package)_patches=disable-glibc-reallocarray.patch + define $(package)_set_vars $(package)_config_opts=--disable-shared --enable-static --without-pyunbound --prefix=$(host_prefix) --with-libexpat=$(host_prefix) --with-ssl=$(host_prefix) --with-libevent=no --without-pythonmodule --disable-flto --with-pthreads --with-libunbound-only @@ -12,8 +14,13 @@ define $(package)_set_vars $(package)_build_opts_mingw32=LDFLAGS="$($(package)_ldflags) -lpthread" endef +define $(package)_preprocess_cmds + patch -p1 < $($(package)_patch_dir)/disable-glibc-reallocarray.patch &&\ + autoconf +endef + define $(package)_config_cmds - $($(package)_autoconf) + $($(package)_autoconf) ac_cv_func_getentropy=no endef define $(package)_build_cmds diff --git a/contrib/depends/patches/unbound/disable-glibc-reallocarray.patch b/contrib/depends/patches/unbound/disable-glibc-reallocarray.patch new file mode 100644 index 000000000..d66a821ad --- /dev/null +++ b/contrib/depends/patches/unbound/disable-glibc-reallocarray.patch @@ -0,0 +1,14 @@ +diff --git a/configure.ac b/configure.ac +index 5c7da197..e2b25288 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -1702,6 +1702,9 @@ AC_LINK_IFELSE([AC_LANG_SOURCE(AC_INCLUDES_DEFAULT + #ifndef _OPENBSD_SOURCE + #define _OPENBSD_SOURCE 1 + #endif ++#ifdef __linux__ ++# error reallocarray() is currently disabled on Linux to support glibc < 2.26 ++#endif + #include <stdlib.h> + int main(void) { + void* p = reallocarray(NULL, 10, 100); diff --git a/contrib/epee/include/net/abstract_tcp_server2.h b/contrib/epee/include/net/abstract_tcp_server2.h index 51aa9f275..bc0da66e2 100644 --- a/contrib/epee/include/net/abstract_tcp_server2.h +++ b/contrib/epee/include/net/abstract_tcp_server2.h @@ -44,12 +44,16 @@ #include <cassert> #include <map> #include <memory> +#include <condition_variable> #include <boost/asio.hpp> #include <boost/asio/ssl.hpp> +#include <boost/asio/strand.hpp> +#include <boost/asio/steady_timer.hpp> #include <boost/array.hpp> #include <boost/enable_shared_from_this.hpp> #include <boost/thread/thread.hpp> +#include <boost/optional.hpp> #include "byte_slice.h" #include "net_utils_base.h" #include "syncobj.h" @@ -87,7 +91,172 @@ namespace net_utils { public: typedef typename t_protocol_handler::connection_context t_connection_context; + private: + using connection_t = connection<t_protocol_handler>; + using connection_ptr = boost::shared_ptr<connection_t>; + using ssl_support_t = epee::net_utils::ssl_support_t; + using timer_t = boost::asio::steady_timer; + using duration_t = timer_t::duration; + using ec_t = boost::system::error_code; + using handshake_t = boost::asio::ssl::stream_base::handshake_type; + + using io_context_t = boost::asio::io_service; + using strand_t = boost::asio::io_service::strand; + using socket_t = boost::asio::ip::tcp::socket; + + using network_throttle_t = epee::net_utils::network_throttle; + using network_throttle_manager_t = epee::net_utils::network_throttle_manager; + + unsigned int host_count(int delta = 0); + duration_t get_default_timeout(); + duration_t get_timeout_from_bytes_read(size_t bytes) const; + + void state_status_check(); + + void start_timer(duration_t duration, bool add = {}); + void async_wait_timer(); + void cancel_timer(); + + void start_handshake(); + void start_read(); + void start_write(); + void start_shutdown(); + void cancel_socket(); + + void cancel_handler(); + + void interrupt(); + void on_interrupted(); + + void terminate(); + void on_terminating(); + + bool send(epee::byte_slice message); + bool start_internal( + bool is_income, + bool is_multithreaded, + boost::optional<network_address> real_remote + ); + + enum status_t { + TERMINATED, + RUNNING, + INTERRUPTED, + TERMINATING, + WASTED, + }; + + struct state_t { + struct stat_t { + struct { + network_throttle_t throttle{"speed_in", "throttle_speed_in"}; + } in; + struct { + network_throttle_t throttle{"speed_out", "throttle_speed_out"}; + } out; + }; + + struct data_t { + struct { + std::array<uint8_t, 0x2000> buffer; + } read; + struct { + std::deque<epee::byte_slice> queue; + bool wait_consume; + } write; + }; + + struct ssl_t { + bool enabled; + bool forced; + bool detected; + bool handshaked; + }; + + struct socket_status_t { + bool connected; + + bool wait_handshake; + bool cancel_handshake; + + bool wait_read; + bool handle_read; + bool cancel_read; + + bool wait_write; + bool handle_write; + bool cancel_write; + + bool wait_shutdown; + bool cancel_shutdown; + }; + + struct timer_status_t { + bool wait_expire; + bool cancel_expire; + bool reset_expire; + }; + + struct timers_status_t { + struct throttle_t { + timer_status_t in; + timer_status_t out; + }; + + timer_status_t general; + throttle_t throttle; + }; + + struct protocol_t { + size_t reference_counter; + bool released; + bool initialized; + + bool wait_release; + bool wait_init; + size_t wait_callback; + }; + + std::mutex lock; + std::condition_variable_any condition; + status_t status; + socket_status_t socket; + ssl_t ssl; + timers_status_t timers; + protocol_t protocol; + stat_t stat; + data_t data; + }; + + struct timers_t { + timers_t(io_context_t &io_context): + general(io_context), + throttle(io_context) + {} + struct throttle_t { + throttle_t(io_context_t &io_context): + in(io_context), + out(io_context) + {} + timer_t in; + timer_t out; + }; + + timer_t general; + throttle_t throttle; + }; + io_context_t &m_io_context; + t_connection_type m_connection_type; + t_connection_context m_conn_context{}; + strand_t m_strand; + timers_t m_timers; + connection_ptr self{}; + bool m_local{}; + std::string m_host{}; + state_t m_state{}; + t_protocol_handler m_handler; + public: struct shared_state : connection_basic_shared_state, t_protocol_handler::config_type { shared_state() @@ -119,7 +288,7 @@ namespace net_utils // `real_remote` is the actual endpoint (if connection is to proxy, etc.) bool start(bool is_income, bool is_multithreaded, network_address real_remote); - void get_context(t_connection_context& context_){context_ = context;} + void get_context(t_connection_context& context_){context_ = m_conn_context;} void call_back_starter(); @@ -141,58 +310,6 @@ namespace net_utils virtual bool add_ref(); virtual bool release(); //------------------------------------------------------ - bool do_send_chunk(byte_slice chunk); ///< will send (or queue) a part of data. internal use only - - boost::shared_ptr<connection<t_protocol_handler> > safe_shared_from_this(); - bool shutdown(); - /// Handle completion of a receive operation. - void handle_receive(const boost::system::error_code& e, - std::size_t bytes_transferred); - - /// Handle completion of a read operation. - void handle_read(const boost::system::error_code& e, - std::size_t bytes_transferred); - - /// Handle completion of a write operation. - void handle_write(const boost::system::error_code& e, size_t cb); - - /// reset connection timeout timer and callback - void reset_timer(boost::posix_time::milliseconds ms, bool add); - boost::posix_time::milliseconds get_default_timeout(); - boost::posix_time::milliseconds get_timeout_from_bytes_read(size_t bytes); - - /// host connection count tracking - unsigned int host_count(const std::string &host, int delta = 0); - - /// Buffer for incoming data. - boost::array<char, 8192> buffer_; - size_t buffer_ssl_init_fill; - - t_connection_context context; - - // TODO what do they mean about wait on destructor?? --rfree : - //this should be the last one, because it could be wait on destructor, while other activities possible on other threads - t_protocol_handler m_protocol_handler; - //typename t_protocol_handler::config_type m_dummy_config; - size_t m_reference_count = 0; // reference count managed through add_ref/release support - boost::shared_ptr<connection<t_protocol_handler> > m_self_ref; // the reference to hold - critical_section m_self_refs_lock; - critical_section m_chunking_lock; // held while we add small chunks of the big do_send() to small do_send_chunk() - critical_section m_shutdown_lock; // held while shutting down - - t_connection_type m_connection_type; - - // for calculate speed (last 60 sec) - network_throttle m_throttle_speed_in; - network_throttle m_throttle_speed_out; - boost::mutex m_throttle_speed_in_mutex; - boost::mutex m_throttle_speed_out_mutex; - - boost::asio::deadline_timer m_timer; - bool m_local; - bool m_ready_to_close; - std::string m_host; - public: void setRpcStation(); }; diff --git a/contrib/epee/include/net/abstract_tcp_server2.inl b/contrib/epee/include/net/abstract_tcp_server2.inl index 0c3b457bc..81aa725d1 100644 --- a/contrib/epee/include/net/abstract_tcp_server2.inl +++ b/contrib/epee/include/net/abstract_tcp_server2.inl @@ -76,859 +76,1057 @@ namespace net_utils /************************************************************************/ /* */ /************************************************************************/ - template<class t_protocol_handler> - connection<t_protocol_handler>::connection( boost::asio::io_service& io_service, - std::shared_ptr<shared_state> state, - t_connection_type connection_type, - ssl_support_t ssl_support - ) - : connection(boost::asio::ip::tcp::socket{io_service}, std::move(state), connection_type, ssl_support) + template<typename T> + unsigned int connection<T>::host_count(int delta) { + static std::mutex hosts_mutex; + std::lock_guard<std::mutex> guard(hosts_mutex); + static std::map<std::string, unsigned int> hosts; + unsigned int &val = hosts[m_host]; + if (delta > 0) + MTRACE("New connection from host " << m_host << ": " << val); + else if (delta < 0) + MTRACE("Closed connection from host " << m_host << ": " << val); + CHECK_AND_ASSERT_THROW_MES(delta >= 0 || val >= (unsigned)-delta, "Count would go negative"); + CHECK_AND_ASSERT_THROW_MES(delta <= 0 || val <= std::numeric_limits<unsigned int>::max() - (unsigned)delta, "Count would wrap"); + val += delta; + return val; } - template<class t_protocol_handler> - connection<t_protocol_handler>::connection( boost::asio::ip::tcp::socket&& sock, - std::shared_ptr<shared_state> state, - t_connection_type connection_type, - ssl_support_t ssl_support - ) - : - connection_basic(std::move(sock), state, ssl_support), - m_protocol_handler(this, check_and_get(state), context), - buffer_ssl_init_fill(0), - m_connection_type( connection_type ), - m_throttle_speed_in("speed_in", "throttle_speed_in"), - m_throttle_speed_out("speed_out", "throttle_speed_out"), - m_timer(GET_IO_SERVICE(socket_)), - m_local(false), - m_ready_to_close(false) + template<typename T> + typename connection<T>::duration_t connection<T>::get_default_timeout() { - MDEBUG("test, connection constructor set m_connection_type="<<m_connection_type); + unsigned count{}; + try { count = host_count(); } catch (...) {} + const unsigned shift = ( + connection_basic::get_state().sock_count > AGGRESSIVE_TIMEOUT_THRESHOLD ? + std::min(std::max(count, 1u) - 1, 8u) : + 0 + ); + return ( + m_local ? + std::chrono::milliseconds(DEFAULT_TIMEOUT_MS_LOCAL >> shift) : + std::chrono::milliseconds(DEFAULT_TIMEOUT_MS_REMOTE >> shift) + ); } - //--------------------------------------------------------------------------------- - template<class t_protocol_handler> - connection<t_protocol_handler>::~connection() noexcept(false) + template<typename T> + typename connection<T>::duration_t connection<T>::get_timeout_from_bytes_read(size_t bytes) const { - if(!m_was_shutdown) - { - _dbg3("[sock " << socket().native_handle() << "] Socket destroyed without shutdown."); - shutdown(); - } - - _dbg3("[sock " << socket().native_handle() << "] Socket destroyed"); + return std::chrono::duration_cast<connection<T>::duration_t>( + std::chrono::duration<double, std::chrono::milliseconds::period>( + bytes * TIMEOUT_EXTRA_MS_PER_BYTE + ) + ); } - //--------------------------------------------------------------------------------- - template<class t_protocol_handler> - boost::shared_ptr<connection<t_protocol_handler> > connection<t_protocol_handler>::safe_shared_from_this() + + template<typename T> + void connection<T>::state_status_check() { - try - { - return connection<t_protocol_handler>::shared_from_this(); - } - catch (const boost::bad_weak_ptr&) - { - // It happens when the connection is being deleted - return boost::shared_ptr<connection<t_protocol_handler> >(); + switch (m_state.status) + { + case status_t::RUNNING: + interrupt(); + break; + case status_t::INTERRUPTED: + on_interrupted(); + break; + case status_t::TERMINATING: + on_terminating(); + break; + default: + break; } } - //--------------------------------------------------------------------------------- - template<class t_protocol_handler> - bool connection<t_protocol_handler>::start(bool is_income, bool is_multithreaded) - { - TRY_ENTRY(); - boost::system::error_code ec; - auto remote_ep = socket().remote_endpoint(ec); - CHECK_AND_NO_ASSERT_MES(!ec, false, "Failed to get remote endpoint: " << ec.message() << ':' << ec.value()); - CHECK_AND_NO_ASSERT_MES(remote_ep.address().is_v4() || remote_ep.address().is_v6(), false, "only IPv4 and IPv6 supported here"); - - if (remote_ep.address().is_v4()) - { - const unsigned long ip_ = boost::asio::detail::socket_ops::host_to_network_long(remote_ep.address().to_v4().to_ulong()); - return start(is_income, is_multithreaded, ipv4_network_address{uint32_t(ip_), remote_ep.port()}); - } - else - { - const auto ip_ = remote_ep.address().to_v6(); - return start(is_income, is_multithreaded, ipv6_network_address{ip_, remote_ep.port()}); - } - CATCH_ENTRY_L0("connection<t_protocol_handler>::start()", false); - } - //--------------------------------------------------------------------------------- - template<class t_protocol_handler> - bool connection<t_protocol_handler>::start(bool is_income, bool is_multithreaded, network_address real_remote) + template<typename T> + void connection<T>::start_timer(duration_t duration, bool add) { - TRY_ENTRY(); - - // Use safe_shared_from_this, because of this is public method and it can be called on the object being deleted - auto self = safe_shared_from_this(); - if(!self) - return false; - - m_is_multithreaded = is_multithreaded; - m_local = real_remote.is_loopback() || real_remote.is_local(); - - // create a random uuid, we don't need crypto strength here - const boost::uuids::uuid random_uuid = boost::uuids::random_generator()(); - - context = t_connection_context{}; - bool ssl = m_ssl_support == epee::net_utils::ssl_support_t::e_ssl_support_enabled; - context.set_details(random_uuid, std::move(real_remote), is_income, ssl); - - boost::system::error_code ec; - auto local_ep = socket().local_endpoint(ec); - CHECK_AND_NO_ASSERT_MES(!ec, false, "Failed to get local endpoint: " << ec.message() << ':' << ec.value()); - - _dbg3("[sock " << socket_.native_handle() << "] new connection from " << print_connection_context_short(context) << - " to " << local_ep.address().to_string() << ':' << local_ep.port() << - ", total sockets objects " << get_state().sock_count); - - if(static_cast<shared_state&>(get_state()).pfilter && !static_cast<shared_state&>(get_state()).pfilter->is_remote_host_allowed(context.m_remote_address)) - { - _dbg2("[sock " << socket().native_handle() << "] host denied " << context.m_remote_address.host_str() << ", shutdowning connection"); - close(); - return false; + if (m_state.timers.general.wait_expire) { + m_state.timers.general.cancel_expire = true; + m_state.timers.general.reset_expire = true; + ec_t ec; + m_timers.general.expires_from_now( + std::min( + duration + (add ? m_timers.general.expires_from_now() : duration_t{}), + get_default_timeout() + ), + ec + ); + } + else { + ec_t ec; + m_timers.general.expires_from_now( + std::min( + duration + (add ? m_timers.general.expires_from_now() : duration_t{}), + get_default_timeout() + ), + ec + ); + async_wait_timer(); } - - m_host = context.m_remote_address.host_str(); - try { host_count(m_host, 1); } catch(...) { /* ignore */ } - - m_protocol_handler.after_init_connection(); - - reset_timer(boost::posix_time::milliseconds(m_local ? NEW_CONNECTION_TIMEOUT_LOCAL : NEW_CONNECTION_TIMEOUT_REMOTE), false); - - // first read on the raw socket to detect SSL for the server - buffer_ssl_init_fill = 0; - if (is_income && m_ssl_support != epee::net_utils::ssl_support_t::e_ssl_support_disabled) - socket().async_receive(boost::asio::buffer(buffer_), - strand_.wrap( - std::bind(&connection<t_protocol_handler>::handle_receive, self, - std::placeholders::_1, - std::placeholders::_2))); - else - async_read_some(boost::asio::buffer(buffer_), - strand_.wrap( - std::bind(&connection<t_protocol_handler>::handle_read, self, - std::placeholders::_1, - std::placeholders::_2))); -#if !defined(_WIN32) || !defined(__i686) - // not supported before Windows7, too lazy for runtime check - // Just exclude for 32bit windows builds - //set ToS flag - int tos = get_tos_flag(); - boost::asio::detail::socket_option::integer< IPPROTO_IP, IP_TOS > - optionTos( tos ); - socket().set_option( optionTos ); - //_dbg1("Set ToS flag to " << tos); -#endif - - boost::asio::ip::tcp::no_delay noDelayOption(false); - socket().set_option(noDelayOption); - - return true; - - CATCH_ENTRY_L0("connection<t_protocol_handler>::start()", false); } - //--------------------------------------------------------------------------------- - template<class t_protocol_handler> - bool connection<t_protocol_handler>::request_callback() - { - TRY_ENTRY(); - _dbg2("[" << print_connection_context_short(context) << "] request_callback"); - // Use safe_shared_from_this, because of this is public method and it can be called on the object being deleted - auto self = safe_shared_from_this(); - if(!self) - return false; - strand_.post(boost::bind(&connection<t_protocol_handler>::call_back_starter, self)); - CATCH_ENTRY_L0("connection<t_protocol_handler>::request_callback()", false); - return true; - } - //--------------------------------------------------------------------------------- - template<class t_protocol_handler> - boost::asio::io_service& connection<t_protocol_handler>::get_io_service() + template<typename T> + void connection<T>::async_wait_timer() { - return GET_IO_SERVICE(socket()); + if (m_state.timers.general.wait_expire) + return; + m_state.timers.general.wait_expire = true; + auto self = connection<T>::shared_from_this(); + m_timers.general.async_wait([this, self](const ec_t & ec){ + std::lock_guard<std::mutex> guard(m_state.lock); + m_state.timers.general.wait_expire = false; + if (m_state.timers.general.cancel_expire) { + m_state.timers.general.cancel_expire = false; + if (m_state.timers.general.reset_expire) { + m_state.timers.general.reset_expire = false; + async_wait_timer(); + } + else if (m_state.status == status_t::INTERRUPTED) + on_interrupted(); + else if (m_state.status == status_t::TERMINATING) + on_terminating(); + } + else if (m_state.status == status_t::RUNNING) + interrupt(); + else if (m_state.status == status_t::INTERRUPTED) + terminate(); + }); } - //--------------------------------------------------------------------------------- - template<class t_protocol_handler> - bool connection<t_protocol_handler>::add_ref() - { - TRY_ENTRY(); - // Use safe_shared_from_this, because of this is public method and it can be called on the object being deleted - auto self = safe_shared_from_this(); - if(!self) - return false; - //_dbg3("[sock " << socket().native_handle() << "] add_ref, m_peer_number=" << mI->m_peer_number); - CRITICAL_REGION_LOCAL(self->m_self_refs_lock); - //_dbg3("[sock " << socket().native_handle() << "] add_ref 2, m_peer_number=" << mI->m_peer_number); - ++m_reference_count; - m_self_ref = std::move(self); - return true; - CATCH_ENTRY_L0("connection<t_protocol_handler>::add_ref()", false); - } - //--------------------------------------------------------------------------------- - template<class t_protocol_handler> - bool connection<t_protocol_handler>::release() - { - TRY_ENTRY(); - boost::shared_ptr<connection<t_protocol_handler> > back_connection_copy; - LOG_TRACE_CC(context, "[sock " << socket().native_handle() << "] release"); - CRITICAL_REGION_BEGIN(m_self_refs_lock); - CHECK_AND_ASSERT_MES(m_reference_count, false, "[sock " << socket().native_handle() << "] m_reference_count already at 0 at connection<t_protocol_handler>::release() call"); - // is this the last reference? - if (--m_reference_count == 0) { - // move the held reference to a local variable, keeping the object alive until the function terminates - std::swap(back_connection_copy, m_self_ref); - } - CRITICAL_REGION_END(); - return true; - CATCH_ENTRY_L0("connection<t_protocol_handler>::release()", false); - } - //--------------------------------------------------------------------------------- - template<class t_protocol_handler> - void connection<t_protocol_handler>::call_back_starter() + template<typename T> + void connection<T>::cancel_timer() { - TRY_ENTRY(); - _dbg2("[" << print_connection_context_short(context) << "] fired_callback"); - m_protocol_handler.handle_qued_callback(); - CATCH_ENTRY_L0("connection<t_protocol_handler>::call_back_starter()", void()); + if (!m_state.timers.general.wait_expire) + return; + m_state.timers.general.cancel_expire = true; + m_state.timers.general.reset_expire = false; + ec_t ec; + m_timers.general.cancel(ec); } - //--------------------------------------------------------------------------------- - template<class t_protocol_handler> - void connection<t_protocol_handler>::save_dbg_log() - { - std::string address, port; - boost::system::error_code e; - boost::asio::ip::tcp::endpoint endpoint = socket().remote_endpoint(e); - if (e) - { - address = "<not connected>"; - port = "<not connected>"; - } - else - { - address = endpoint.address().to_string(); - port = boost::lexical_cast<std::string>(endpoint.port()); - } - MDEBUG(" connection type " << to_string( m_connection_type ) << " " - << socket().local_endpoint().address().to_string() << ":" << socket().local_endpoint().port() - << " <--> " << context.m_remote_address.str() << " (via " << address << ":" << port << ")"); - } - //--------------------------------------------------------------------------------- - template<class t_protocol_handler> - void connection<t_protocol_handler>::handle_read(const boost::system::error_code& e, - std::size_t bytes_transferred) + template<typename T> + void connection<T>::start_handshake() { - TRY_ENTRY(); - //_info("[sock " << socket().native_handle() << "] Async read calledback."); - - if (m_was_shutdown) - return; + if (m_state.socket.wait_handshake) + return; + static_assert( + epee::net_utils::get_ssl_magic_size() <= sizeof(m_state.data.read.buffer), + "" + ); + auto self = connection<T>::shared_from_this(); + if (!m_state.ssl.forced && !m_state.ssl.detected) { + m_state.socket.wait_read = true; + boost::asio::async_read( + connection_basic::socket_.next_layer(), + boost::asio::buffer( + m_state.data.read.buffer.data(), + m_state.data.read.buffer.size() + ), + boost::asio::transfer_exactly(epee::net_utils::get_ssl_magic_size()), + m_strand.wrap( + [this, self](const ec_t &ec, size_t bytes_transferred){ + std::lock_guard<std::mutex> guard(m_state.lock); + m_state.socket.wait_read = false; + if (m_state.socket.cancel_read) { + m_state.socket.cancel_read = false; + state_status_check(); + } + else if (ec.value()) { + terminate(); + } + else if ( + !epee::net_utils::is_ssl( + static_cast<const unsigned char *>( + m_state.data.read.buffer.data() + ), + bytes_transferred + ) + ) { + m_state.ssl.enabled = false; + m_state.socket.handle_read = true; + connection_basic::strand_.post( + [this, self, bytes_transferred]{ + bool success = m_handler.handle_recv( + reinterpret_cast<char *>(m_state.data.read.buffer.data()), + bytes_transferred + ); + std::lock_guard<std::mutex> guard(m_state.lock); + m_state.socket.handle_read = false; + if (m_state.status == status_t::INTERRUPTED) + on_interrupted(); + else if (m_state.status == status_t::TERMINATING) + on_terminating(); + else if (!success) + interrupt(); + else { + start_read(); + } + } + ); + } + else { + m_state.ssl.detected = true; + start_handshake(); + } + } + ) + ); + return; + } - if (!e) - { - double current_speed_down; - { - CRITICAL_REGION_LOCAL(m_throttle_speed_in_mutex); - m_throttle_speed_in.handle_trafic_exact(bytes_transferred); - current_speed_down = m_throttle_speed_in.get_current_speed(); - } - context.m_current_speed_down = current_speed_down; - context.m_max_speed_down = std::max(context.m_max_speed_down, current_speed_down); - - { - CRITICAL_REGION_LOCAL( epee::net_utils::network_throttle_manager::network_throttle_manager::m_lock_get_global_throttle_in ); - epee::net_utils::network_throttle_manager::network_throttle_manager::get_global_throttle_in().handle_trafic_exact(bytes_transferred); - } - - double delay=0; // will be calculated - how much we should sleep to obey speed limit etc - - - if (speed_limit_is_enabled()) { - do // keep sleeping if we should sleep - { - { //_scope_dbg1("CRITICAL_REGION_LOCAL"); - CRITICAL_REGION_LOCAL( epee::net_utils::network_throttle_manager::m_lock_get_global_throttle_in ); - delay = epee::net_utils::network_throttle_manager::get_global_throttle_in().get_sleep_time_after_tick( bytes_transferred ); - } - - if (m_was_shutdown) - return; - - delay *= 0.5; - long int ms = (long int)(delay * 100); - if (ms > 0) { - reset_timer(boost::posix_time::milliseconds(ms + 1), true); - boost::this_thread::sleep_for(boost::chrono::milliseconds(ms)); - } - } while(delay > 0); - } // any form of sleeping - - //_info("[sock " << socket().native_handle() << "] RECV " << bytes_transferred); - logger_handle_net_read(bytes_transferred); - context.m_last_recv = time(NULL); - context.m_recv_cnt += bytes_transferred; - m_ready_to_close = false; - bool recv_res = m_protocol_handler.handle_recv(buffer_.data(), bytes_transferred); - if(!recv_res) - { - //_info("[sock " << socket().native_handle() << "] protocol_want_close"); - //some error in protocol, protocol handler ask to close connection - m_want_close_connection = true; - bool do_shutdown = false; - CRITICAL_REGION_BEGIN(m_send_que_lock); - if(!m_send_que.size()) - do_shutdown = true; - CRITICAL_REGION_END(); - if(do_shutdown) - shutdown(); - }else - { - reset_timer(get_timeout_from_bytes_read(bytes_transferred), false); - async_read_some(boost::asio::buffer(buffer_), - strand_.wrap( - boost::bind(&connection<t_protocol_handler>::handle_read, connection<t_protocol_handler>::shared_from_this(), - boost::asio::placeholders::error, - boost::asio::placeholders::bytes_transferred))); - //_info("[sock " << socket().native_handle() << "]Async read requested."); + m_state.socket.wait_handshake = true; + auto on_handshake = [this, self](const ec_t &ec, size_t bytes_transferred){ + std::lock_guard<std::mutex> guard(m_state.lock); + m_state.socket.wait_handshake = false; + if (m_state.socket.cancel_handshake) { + m_state.socket.cancel_handshake = false; + state_status_check(); } - }else - { - _dbg3("[sock " << socket().native_handle() << "] Some not success at read: " << e.message() << ':' << e.value()); - if(e.value() != 2) - { - _dbg3("[sock " << socket().native_handle() << "] Some problems at read: " << e.message() << ':' << e.value()); - shutdown(); + else if (ec.value()) { + ec_t ec; + connection_basic::socket_.next_layer().shutdown( + socket_t::shutdown_both, + ec + ); + connection_basic::socket_.next_layer().close(ec); + m_state.socket.connected = false; + interrupt(); } - else - { - _dbg3("[sock " << socket().native_handle() << "] peer closed connection"); - bool do_shutdown = false; - CRITICAL_REGION_BEGIN(m_send_que_lock); - if(!m_send_que.size()) - do_shutdown = true; - CRITICAL_REGION_END(); - if (m_ready_to_close || do_shutdown) - shutdown(); + else { + m_state.ssl.handshaked = true; + start_write(); + start_read(); } - m_ready_to_close = true; - } - // If an error occurs then no new asynchronous operations are started. This - // means that all shared_ptr references to the connection object will - // disappear and the object will be destroyed automatically after this - // handler returns. The connection class's destructor closes the socket. - CATCH_ENTRY_L0("connection<t_protocol_handler>::handle_read", void()); + }; + const auto handshake = handshake_t::server; + static_cast<shared_state&>( + connection_basic::get_state() + ).ssl_options().configure(connection_basic::socket_, handshake); + m_strand.post( + [this, self, on_handshake]{ + connection_basic::socket_.async_handshake( + handshake, + boost::asio::buffer( + m_state.data.read.buffer.data(), + m_state.ssl.forced ? 0 : + epee::net_utils::get_ssl_magic_size() + ), + m_strand.wrap(on_handshake) + ); + } + ); } - //--------------------------------------------------------------------------------- - template<class t_protocol_handler> - void connection<t_protocol_handler>::handle_receive(const boost::system::error_code& e, - std::size_t bytes_transferred) - { - TRY_ENTRY(); - if (m_was_shutdown) return; - - if (e) - { - // offload the error case - handle_read(e, bytes_transferred); + template<typename T> + void connection<T>::start_read() + { + if (m_state.timers.throttle.in.wait_expire || m_state.socket.wait_read || + m_state.socket.handle_read + ) { return; } + auto self = connection<T>::shared_from_this(); + if (m_connection_type != e_connection_type_RPC) { + auto calc_duration = []{ + CRITICAL_REGION_LOCAL( + network_throttle_manager_t::m_lock_get_global_throttle_in + ); + return std::chrono::duration_cast<connection<T>::duration_t>( + std::chrono::duration<double, std::chrono::seconds::period>( + std::min( + network_throttle_manager_t::get_global_throttle_in( + ).get_sleep_time_after_tick(1), + 1.0 + ) + ) + ); + }; + const auto duration = calc_duration(); + if (duration > duration_t{}) { + ec_t ec; + m_timers.throttle.in.expires_from_now(duration, ec); + m_state.timers.throttle.in.wait_expire = true; + m_timers.throttle.in.async_wait([this, self](const ec_t &ec){ + std::lock_guard<std::mutex> guard(m_state.lock); + m_state.timers.throttle.in.wait_expire = false; + if (m_state.timers.throttle.in.cancel_expire) { + m_state.timers.throttle.in.cancel_expire = false; + state_status_check(); + } + else if (ec.value()) + interrupt(); + else + start_read(); + }); + return; + } + } + m_state.socket.wait_read = true; + auto on_read = [this, self](const ec_t &ec, size_t bytes_transferred){ + std::lock_guard<std::mutex> guard(m_state.lock); + m_state.socket.wait_read = false; + if (m_state.socket.cancel_read) { + m_state.socket.cancel_read = false; + state_status_check(); + } + else if (ec.value()) + terminate(); + else { + { + m_state.stat.in.throttle.handle_trafic_exact(bytes_transferred); + const auto speed = m_state.stat.in.throttle.get_current_speed(); + m_conn_context.m_current_speed_down = speed; + m_conn_context.m_max_speed_down = std::max( + m_conn_context.m_max_speed_down, + speed + ); + { + CRITICAL_REGION_LOCAL( + network_throttle_manager_t::m_lock_get_global_throttle_in + ); + network_throttle_manager_t::get_global_throttle_in( + ).handle_trafic_exact(bytes_transferred); + } + connection_basic::logger_handle_net_read(bytes_transferred); + m_conn_context.m_last_recv = time(NULL); + m_conn_context.m_recv_cnt += bytes_transferred; + start_timer(get_timeout_from_bytes_read(bytes_transferred), true); + } - buffer_ssl_init_fill += bytes_transferred; - MTRACE("we now have " << buffer_ssl_init_fill << "/" << get_ssl_magic_size() << " bytes needed to detect SSL"); - if (buffer_ssl_init_fill < get_ssl_magic_size()) - { - socket().async_receive(boost::asio::buffer(buffer_.data() + buffer_ssl_init_fill, buffer_.size() - buffer_ssl_init_fill), - strand_.wrap( - boost::bind(&connection<t_protocol_handler>::handle_receive, connection<t_protocol_handler>::shared_from_this(), - boost::asio::placeholders::error, - boost::asio::placeholders::bytes_transferred))); + // Post handle_recv to a separate `strand_`, distinct from `m_strand` + // which is listening for reads/writes. This avoids a circular dep. + // handle_recv can queue many writes, and `m_strand` will process those + // writes until the connection terminates without deadlocking waiting + // for handle_recv. + m_state.socket.handle_read = true; + connection_basic::strand_.post( + [this, self, bytes_transferred]{ + bool success = m_handler.handle_recv( + reinterpret_cast<char *>(m_state.data.read.buffer.data()), + bytes_transferred + ); + std::lock_guard<std::mutex> guard(m_state.lock); + m_state.socket.handle_read = false; + if (m_state.status == status_t::INTERRUPTED) + on_interrupted(); + else if (m_state.status == status_t::TERMINATING) + on_terminating(); + else if (!success) + interrupt(); + else { + start_read(); + } + } + ); + } + }; + if (!m_state.ssl.enabled) + connection_basic::socket_.next_layer().async_read_some( + boost::asio::buffer( + m_state.data.read.buffer.data(), + m_state.data.read.buffer.size() + ), + m_strand.wrap(on_read) + ); + else + m_strand.post( + [this, self, on_read]{ + connection_basic::socket_.async_read_some( + boost::asio::buffer( + m_state.data.read.buffer.data(), + m_state.data.read.buffer.size() + ), + m_strand.wrap(on_read) + ); + } + ); + } + + template<typename T> + void connection<T>::start_write() + { + if (m_state.timers.throttle.out.wait_expire || m_state.socket.wait_write || + m_state.data.write.queue.empty() || + (m_state.ssl.enabled && !m_state.ssl.handshaked) + ) { return; } + auto self = connection<T>::shared_from_this(); + if (m_connection_type != e_connection_type_RPC) { + auto calc_duration = [this]{ + CRITICAL_REGION_LOCAL( + network_throttle_manager_t::m_lock_get_global_throttle_out + ); + return std::chrono::duration_cast<connection<T>::duration_t>( + std::chrono::duration<double, std::chrono::seconds::period>( + std::min( + network_throttle_manager_t::get_global_throttle_out( + ).get_sleep_time_after_tick( + m_state.data.write.queue.back().size() + ), + 1.0 + ) + ) + ); + }; + const auto duration = calc_duration(); + if (duration > duration_t{}) { + ec_t ec; + m_timers.throttle.out.expires_from_now(duration, ec); + m_state.timers.throttle.out.wait_expire = true; + m_timers.throttle.out.async_wait([this, self](const ec_t &ec){ + std::lock_guard<std::mutex> guard(m_state.lock); + m_state.timers.throttle.out.wait_expire = false; + if (m_state.timers.throttle.out.cancel_expire) { + m_state.timers.throttle.out.cancel_expire = false; + state_status_check(); + } + else if (ec.value()) + interrupt(); + else + start_write(); + }); + } + } - // detect SSL - if (m_ssl_support == epee::net_utils::ssl_support_t::e_ssl_support_autodetect) - { - if (is_ssl((const unsigned char*)buffer_.data(), buffer_ssl_init_fill)) - { - MDEBUG("That looks like SSL"); - m_ssl_support = epee::net_utils::ssl_support_t::e_ssl_support_enabled; // read/write to the SSL socket + m_state.socket.wait_write = true; + auto on_write = [this, self](const ec_t &ec, size_t bytes_transferred){ + std::lock_guard<std::mutex> guard(m_state.lock); + m_state.socket.wait_write = false; + if (m_state.socket.cancel_write) { + m_state.socket.cancel_write = false; + m_state.data.write.queue.clear(); + state_status_check(); } - else - { - MDEBUG("That does not look like SSL"); - m_ssl_support = epee::net_utils::ssl_support_t::e_ssl_support_disabled; // read/write to the raw socket + else if (ec.value()) { + m_state.data.write.queue.clear(); + interrupt(); } - } + else { + { + m_state.stat.out.throttle.handle_trafic_exact(bytes_transferred); + const auto speed = m_state.stat.out.throttle.get_current_speed(); + m_conn_context.m_current_speed_up = speed; + m_conn_context.m_max_speed_down = std::max( + m_conn_context.m_max_speed_down, + speed + ); + { + CRITICAL_REGION_LOCAL( + network_throttle_manager_t::m_lock_get_global_throttle_out + ); + network_throttle_manager_t::get_global_throttle_out( + ).handle_trafic_exact(bytes_transferred); + } + connection_basic::logger_handle_net_write(bytes_transferred); + m_conn_context.m_last_send = time(NULL); + m_conn_context.m_send_cnt += bytes_transferred; - if (m_ssl_support == epee::net_utils::ssl_support_t::e_ssl_support_enabled) - { - // Handshake - if (!handshake(boost::asio::ssl::stream_base::server, boost::asio::const_buffer(buffer_.data(), buffer_ssl_init_fill))) - { - MERROR("SSL handshake failed"); - m_want_close_connection = true; - m_ready_to_close = true; - bool do_shutdown = false; - CRITICAL_REGION_BEGIN(m_send_que_lock); - if(!m_send_que.size()) - do_shutdown = true; - CRITICAL_REGION_END(); - if(do_shutdown) - shutdown(); - return; + start_timer(get_default_timeout(), true); + } + assert(bytes_transferred == m_state.data.write.queue.back().size()); + m_state.data.write.queue.pop_back(); + m_state.condition.notify_all(); + start_write(); } - } + }; + if (!m_state.ssl.enabled) + boost::asio::async_write( + connection_basic::socket_.next_layer(), + boost::asio::buffer( + m_state.data.write.queue.back().data(), + m_state.data.write.queue.back().size() + ), + m_strand.wrap(on_write) + ); else - { - handle_read(e, buffer_ssl_init_fill); - return; - } + m_strand.post( + [this, self, on_write]{ + boost::asio::async_write( + connection_basic::socket_, + boost::asio::buffer( + m_state.data.write.queue.back().data(), + m_state.data.write.queue.back().size() + ), + m_strand.wrap(on_write) + ); + } + ); + } - async_read_some(boost::asio::buffer(buffer_), - strand_.wrap( - boost::bind(&connection<t_protocol_handler>::handle_read, connection<t_protocol_handler>::shared_from_this(), - boost::asio::placeholders::error, - boost::asio::placeholders::bytes_transferred))); - - // If an error occurs then no new asynchronous operations are started. This - // means that all shared_ptr references to the connection object will - // disappear and the object will be destroyed automatically after this - // handler returns. The connection class's destructor closes the socket. - CATCH_ENTRY_L0("connection<t_protocol_handler>::handle_receive", void()); + template<typename T> + void connection<T>::start_shutdown() + { + if (m_state.socket.wait_shutdown) + return; + auto self = connection<T>::shared_from_this(); + m_state.socket.wait_shutdown = true; + auto on_shutdown = [this, self](const ec_t &ec){ + std::lock_guard<std::mutex> guard(m_state.lock); + m_state.socket.wait_shutdown = false; + if (m_state.socket.cancel_shutdown) { + m_state.socket.cancel_shutdown = false; + switch (m_state.status) + { + case status_t::RUNNING: + interrupt(); + break; + case status_t::INTERRUPTED: + terminate(); + break; + case status_t::TERMINATING: + on_terminating(); + break; + default: + break; + } + } + else if (ec.value()) + terminate(); + else { + cancel_timer(); + on_interrupted(); + } + }; + m_strand.post( + [this, self, on_shutdown]{ + connection_basic::socket_.async_shutdown( + m_strand.wrap(on_shutdown) + ); + } + ); + start_timer(get_default_timeout()); } - //--------------------------------------------------------------------------------- - template<class t_protocol_handler> - bool connection<t_protocol_handler>::call_run_once_service_io() + + template<typename T> + void connection<T>::cancel_socket() { - TRY_ENTRY(); - if(!m_is_multithreaded) - { - //single thread model, we can wait in blocked call - size_t cnt = GET_IO_SERVICE(socket()).run_one(); - if(!cnt)//service is going to quit - return false; - }else - { - //multi thread model, we can't(!) wait in blocked call - //so we make non blocking call and releasing CPU by calling sleep(0); - //if no handlers were called - //TODO: Maybe we need to have have critical section + event + callback to upper protocol to - //ask it inside(!) critical region if we still able to go in event wait... - size_t cnt = GET_IO_SERVICE(socket()).poll_one(); - if(!cnt) - misc_utils::sleep_no_w(1); + bool wait_socket = false; + if (m_state.socket.wait_handshake) + wait_socket = m_state.socket.cancel_handshake = true; + if (m_state.timers.throttle.in.wait_expire) { + m_state.timers.throttle.in.cancel_expire = true; + ec_t ec; + m_timers.throttle.in.cancel(ec); + } + if (m_state.socket.wait_read) + wait_socket = m_state.socket.cancel_read = true; + if (m_state.timers.throttle.out.wait_expire) { + m_state.timers.throttle.out.cancel_expire = true; + ec_t ec; + m_timers.throttle.out.cancel(ec); + } + if (m_state.socket.wait_write) + wait_socket = m_state.socket.cancel_write = true; + if (m_state.socket.wait_shutdown) + wait_socket = m_state.socket.cancel_shutdown = true; + if (wait_socket) { + ec_t ec; + connection_basic::socket_.next_layer().cancel(ec); } - - return true; - CATCH_ENTRY_L0("connection<t_protocol_handler>::call_run_once_service_io", false); } - //--------------------------------------------------------------------------------- - template<class t_protocol_handler> - bool connection<t_protocol_handler>::do_send(byte_slice message) { - TRY_ENTRY(); - - // Use safe_shared_from_this, because of this is public method and it can be called on the object being deleted - auto self = safe_shared_from_this(); - if (!self) return false; - if (m_was_shutdown) return false; - // TODO avoid copy - - std::uint8_t const* const message_data = message.data(); - const std::size_t message_size = message.size(); - - const double factor = 32; // TODO config - typedef long long signed int t_safe; // my t_size to avoid any overunderflow in arithmetic - const t_safe chunksize_good = (t_safe)( 1024 * std::max(1.0,factor) ); - const t_safe chunksize_max = chunksize_good * 2 ; - const bool allow_split = (m_connection_type == e_connection_type_RPC) ? false : true; // do not split RPC data - - CHECK_AND_ASSERT_MES(! (chunksize_max<0), false, "Negative chunksize_max" ); // make sure it is unsigned before removin sign with cast: - long long unsigned int chunksize_max_unsigned = static_cast<long long unsigned int>( chunksize_max ) ; - - if (allow_split && (message_size > chunksize_max_unsigned)) { - { // LOCK: chunking - epee::critical_region_t<decltype(m_chunking_lock)> send_guard(m_chunking_lock); // *** critical *** - - MDEBUG("do_send() will SPLIT into small chunks, from packet="<<message_size<<" B for ptr="<<(const void*)message_data); - // 01234567890 - // ^^^^ (pos=0, len=4) ; pos:=pos+len, pos=4 - // ^^^^ (pos=4, len=4) ; pos:=pos+len, pos=8 - // ^^^ (pos=8, len=4) ; - - // const size_t bufsize = chunksize_good; // TODO safecast - // char* buf = new char[ bufsize ]; - - bool all_ok = true; - while (!message.empty()) { - byte_slice chunk = message.take_slice(chunksize_good); - MDEBUG("chunk_start="<<(void*)chunk.data()<<" ptr="<<(const void*)message_data<<" pos="<<(chunk.data() - message_data)); - MDEBUG("part of " << message.size() << ": pos="<<(chunk.data() - message_data) << " len="<<chunk.size()); - - bool ok = do_send_chunk(std::move(chunk)); // <====== *** - - all_ok = all_ok && ok; - if (!all_ok) { - MDEBUG("do_send() DONE ***FAILED*** from packet="<<message_size<<" B for ptr="<<(const void*)message_data); - MDEBUG("do_send() SEND was aborted in middle of big package - this is mostly harmless " - << " (e.g. peer closed connection) but if it causes trouble tell us at #monero-dev. " << message_size); - return false; // partial failure in sending - } - // (in catch block, or uniq pointer) delete buf; - } // each chunk + template<typename T> + void connection<T>::cancel_handler() + { + if (m_state.protocol.released || m_state.protocol.wait_release) + return; + m_state.protocol.wait_release = true; + m_state.lock.unlock(); + m_handler.release_protocol(); + m_state.lock.lock(); + m_state.protocol.wait_release = false; + m_state.protocol.released = true; + if (m_state.status == status_t::INTERRUPTED) + on_interrupted(); + else if (m_state.status == status_t::TERMINATING) + on_terminating(); + } - MDEBUG("do_send() DONE SPLIT from packet="<<message_size<<" B for ptr="<<(const void*)message_data); + template<typename T> + void connection<T>::interrupt() + { + if (m_state.status != status_t::RUNNING) + return; + m_state.status = status_t::INTERRUPTED; + cancel_timer(); + cancel_socket(); + on_interrupted(); + m_state.condition.notify_all(); + cancel_handler(); + } - MDEBUG("do_send() m_connection_type = " << m_connection_type); + template<typename T> + void connection<T>::on_interrupted() + { + assert(m_state.status == status_t::INTERRUPTED); + if (m_state.timers.general.wait_expire) + return; + if (m_state.socket.wait_handshake) + return; + if (m_state.timers.throttle.in.wait_expire) + return; + if (m_state.socket.wait_read) + return; + if (m_state.socket.handle_read) + return; + if (m_state.timers.throttle.out.wait_expire) + return; + if (m_state.socket.wait_write) + return; + if (m_state.socket.wait_shutdown) + return; + if (m_state.protocol.wait_init) + return; + if (m_state.protocol.wait_callback) + return; + if (m_state.protocol.wait_release) + return; + if (m_state.socket.connected) { + if (!m_state.ssl.enabled) { + ec_t ec; + connection_basic::socket_.next_layer().shutdown( + socket_t::shutdown_both, + ec + ); + connection_basic::socket_.next_layer().close(ec); + m_state.socket.connected = false; + m_state.status = status_t::WASTED; + } + else + start_shutdown(); + } + else + m_state.status = status_t::WASTED; + } - return all_ok; // done - e.g. queued - all the chunks of current do_send call - } // LOCK: chunking - } // a big block (to be chunked) - all chunks - else { // small block - return do_send_chunk(std::move(message)); // just send as 1 big chunk - } + template<typename T> + void connection<T>::terminate() + { + if (m_state.status != status_t::RUNNING && + m_state.status != status_t::INTERRUPTED + ) + return; + m_state.status = status_t::TERMINATING; + cancel_timer(); + cancel_socket(); + on_terminating(); + m_state.condition.notify_all(); + cancel_handler(); + } - CATCH_ENTRY_L0("connection<t_protocol_handler>::do_send", false); - } // do_send() + template<typename T> + void connection<T>::on_terminating() + { + assert(m_state.status == status_t::TERMINATING); + if (m_state.timers.general.wait_expire) + return; + if (m_state.socket.wait_handshake) + return; + if (m_state.timers.throttle.in.wait_expire) + return; + if (m_state.socket.wait_read) + return; + if (m_state.socket.handle_read) + return; + if (m_state.timers.throttle.out.wait_expire) + return; + if (m_state.socket.wait_write) + return; + if (m_state.socket.wait_shutdown) + return; + if (m_state.protocol.wait_init) + return; + if (m_state.protocol.wait_callback) + return; + if (m_state.protocol.wait_release) + return; + if (m_state.socket.connected) { + ec_t ec; + connection_basic::socket_.next_layer().shutdown( + socket_t::shutdown_both, + ec + ); + connection_basic::socket_.next_layer().close(ec); + m_state.socket.connected = false; + } + m_state.status = status_t::WASTED; + } - //--------------------------------------------------------------------------------- - template<class t_protocol_handler> - bool connection<t_protocol_handler>::do_send_chunk(byte_slice chunk) + template<typename T> + bool connection<T>::send(epee::byte_slice message) { - TRY_ENTRY(); - // Use safe_shared_from_this, because of this is public method and it can be called on the object being deleted - auto self = safe_shared_from_this(); - if(!self) - return false; - if(m_was_shutdown) + std::lock_guard<std::mutex> guard(m_state.lock); + if (m_state.status != status_t::RUNNING || m_state.socket.wait_handshake) return false; - double current_speed_up; - { - CRITICAL_REGION_LOCAL(m_throttle_speed_out_mutex); - m_throttle_speed_out.handle_trafic_exact(chunk.size()); - current_speed_up = m_throttle_speed_out.get_current_speed(); - } - context.m_current_speed_up = current_speed_up; - context.m_max_speed_up = std::max(context.m_max_speed_up, current_speed_up); - - //_info("[sock " << socket().native_handle() << "] SEND " << cb); - context.m_last_send = time(NULL); - context.m_send_cnt += chunk.size(); - //some data should be wrote to stream - //request complete - - // No sleeping here; sleeping is done once and for all in "handle_write" - - m_send_que_lock.lock(); // *** critical *** - epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler([&](){m_send_que_lock.unlock();}); - - long int retry=0; - const long int retry_limit = 5*4; - while (m_send_que.size() > ABSTRACT_SERVER_SEND_QUE_MAX_COUNT) - { - retry++; - - /* if ( ::cryptonote::core::get_is_stopping() ) { // TODO re-add fast stop - _fact("ABORT queue wait due to stopping"); - return false; // aborted - }*/ + // Wait for the write queue to fall below the max. If it doesn't after a + // randomized delay, drop the connection. + auto wait_consume = [this] { + auto random_delay = []{ using engine = std::mt19937; - - engine rng; std::random_device dev; - std::seed_seq::result_type rand[engine::state_size]{}; // Use complete bit space - + std::seed_seq::result_type rand[ + engine::state_size // Use complete bit space + ]{}; std::generate_n(rand, engine::state_size, std::ref(dev)); std::seed_seq seed(rand, rand + engine::state_size); - rng.seed(seed); - - long int ms = 250 + (rng() % 50); - MDEBUG("Sleeping because QUEUE is FULL, in " << __FUNCTION__ << " for " << ms << " ms before packet_size="<<chunk.size()); // XXX debug sleep - m_send_que_lock.unlock(); - boost::this_thread::sleep(boost::posix_time::milliseconds( ms ) ); - m_send_que_lock.lock(); - _dbg1("sleep for queue: " << ms); - if (m_was_shutdown) - return false; - - if (retry > retry_limit) { - MWARNING("send que size is more than ABSTRACT_SERVER_SEND_QUE_MAX_COUNT(" << ABSTRACT_SERVER_SEND_QUE_MAX_COUNT << "), shutting down connection"); - shutdown(); - return false; + engine rng(seed); + return std::chrono::milliseconds( + std::uniform_int_distribution<>(5000, 6000)(rng) + ); + }; + if (m_state.data.write.queue.size() <= ABSTRACT_SERVER_SEND_QUE_MAX_COUNT) + return true; + m_state.data.write.wait_consume = true; + bool success = m_state.condition.wait_for( + m_state.lock, + random_delay(), + [this]{ + return ( + m_state.status != status_t::RUNNING || + m_state.data.write.queue.size() <= + ABSTRACT_SERVER_SEND_QUE_MAX_COUNT + ); + } + ); + m_state.data.write.wait_consume = false; + if (!success) { + terminate(); + return false; + } + else + return m_state.status == status_t::RUNNING; + }; + auto wait_sender = [this] { + m_state.condition.wait( + m_state.lock, + [this] { + return ( + m_state.status != status_t::RUNNING || + !m_state.data.write.wait_consume + ); } + ); + return m_state.status == status_t::RUNNING; + }; + if (!wait_sender()) + return false; + constexpr size_t CHUNK_SIZE = 32 * 1024; + if (m_connection_type == e_connection_type_RPC || + message.size() <= 2 * CHUNK_SIZE + ) { + if (!wait_consume()) + return false; + m_state.data.write.queue.emplace_front(std::move(message)); + start_write(); } - - m_send_que.push_back(std::move(chunk)); - - if(m_send_que.size() > 1) - { // active operation should be in progress, nothing to do, just wait last operation callback - auto size_now = m_send_que.back().size(); - MDEBUG("do_send_chunk() NOW just queues: packet="<<size_now<<" B, is added to queue-size="<<m_send_que.size()); - //do_send_handler_delayed( ptr , size_now ); // (((H))) // empty function - - LOG_TRACE_CC(context, "[sock " << socket().native_handle() << "] Async send requested " << m_send_que.front().size()); + else { + while (!message.empty()) { + if (!wait_consume()) + return false; + m_state.data.write.queue.emplace_front( + message.take_slice(CHUNK_SIZE) + ); + start_write(); + } } - else - { // no active operation + m_state.condition.notify_all(); + return true; + } - if(m_send_que.size()!=1) - { - _erro("Looks like no active operations, but send que size != 1!!"); - return false; + template<typename T> + bool connection<T>::start_internal( + bool is_income, + bool is_multithreaded, + boost::optional<network_address> real_remote + ) + { + std::unique_lock<std::mutex> guard(m_state.lock); + if (m_state.status != status_t::TERMINATED) + return false; + if (!real_remote) { + ec_t ec; + auto endpoint = connection_basic::socket_.next_layer().remote_endpoint( + ec + ); + if (ec.value()) + return false; + real_remote = ( + endpoint.address().is_v6() ? + network_address{ + ipv6_network_address{endpoint.address().to_v6(), endpoint.port()} + } : + network_address{ + ipv4_network_address{ + uint32_t{ + boost::asio::detail::socket_ops::host_to_network_long( + endpoint.address().to_v4().to_ulong() + ) + }, + endpoint.port() + } } - - auto size_now = m_send_que.front().size(); - MDEBUG("do_send_chunk() NOW SENSD: packet="<<size_now<<" B"); - if (speed_limit_is_enabled()) - do_send_handler_write( m_send_que.back().data(), m_send_que.back().size() ); // (((H))) - - CHECK_AND_ASSERT_MES( size_now == m_send_que.front().size(), false, "Unexpected queue size"); - reset_timer(get_default_timeout(), false); - async_write(boost::asio::buffer(m_send_que.front().data(), size_now ) , - strand_.wrap( - std::bind(&connection<t_protocol_handler>::handle_write, self, std::placeholders::_1, std::placeholders::_2) - ) - ); - //_dbg3("(chunk): " << size_now); - //logger_handle_net_write(size_now); - //_info("[sock " << socket().native_handle() << "] Async send requested " << m_send_que.front().size()); + ); } - - //do_send_handler_stop( ptr , cb ); // empty function - + auto *filter = static_cast<shared_state&>( + connection_basic::get_state() + ).pfilter; + if (filter && !filter->is_remote_host_allowed(*real_remote)) + return false; + ec_t ec; + #if !defined(_WIN32) || !defined(__i686) + connection_basic::socket_.next_layer().set_option( + boost::asio::detail::socket_option::integer<IPPROTO_IP, IP_TOS>{ + connection_basic::get_tos_flag() + }, + ec + ); + if (ec.value()) + return false; + #endif + connection_basic::socket_.next_layer().set_option( + boost::asio::ip::tcp::no_delay{false}, + ec + ); + if (ec.value()) + return false; + connection_basic::m_is_multithreaded = is_multithreaded; + m_conn_context.set_details( + boost::uuids::random_generator()(), + *real_remote, + is_income, + connection_basic::m_ssl_support == ssl_support_t::e_ssl_support_enabled + ); + m_host = real_remote->host_str(); + try { host_count(1); } catch(...) { /* ignore */ } + m_local = real_remote->is_loopback() || real_remote->is_local(); + m_state.ssl.enabled = ( + connection_basic::m_ssl_support != ssl_support_t::e_ssl_support_disabled + ); + m_state.ssl.forced = ( + connection_basic::m_ssl_support == ssl_support_t::e_ssl_support_enabled + ); + m_state.socket.connected = true; + m_state.status = status_t::RUNNING; + start_timer( + std::chrono::milliseconds( + m_local ? NEW_CONNECTION_TIMEOUT_LOCAL : NEW_CONNECTION_TIMEOUT_REMOTE + ) + ); + m_state.protocol.wait_init = true; + guard.unlock(); + m_handler.after_init_connection(); + guard.lock(); + m_state.protocol.wait_init = false; + m_state.protocol.initialized = true; + if (m_state.status == status_t::INTERRUPTED) + on_interrupted(); + else if (m_state.status == status_t::TERMINATING) + on_terminating(); + else if (!is_income || !m_state.ssl.enabled) + start_read(); + else + start_handshake(); return true; + } - CATCH_ENTRY_L0("connection<t_protocol_handler>::do_send_chunk", false); - } // do_send_chunk - //--------------------------------------------------------------------------------- - template<class t_protocol_handler> - boost::posix_time::milliseconds connection<t_protocol_handler>::get_default_timeout() + template<typename T> + connection<T>::connection( + io_context_t &io_context, + std::shared_ptr<shared_state> shared_state, + t_connection_type connection_type, + ssl_support_t ssl_support + ): + connection( + std::move(socket_t{io_context}), + std::move(shared_state), + connection_type, + ssl_support + ) { - unsigned count; - try { count = host_count(m_host); } catch (...) { count = 0; } - const unsigned shift = get_state().sock_count > AGGRESSIVE_TIMEOUT_THRESHOLD ? std::min(std::max(count, 1u) - 1, 8u) : 0; - boost::posix_time::milliseconds timeout(0); - if (m_local) - timeout = boost::posix_time::milliseconds(DEFAULT_TIMEOUT_MS_LOCAL >> shift); - else - timeout = boost::posix_time::milliseconds(DEFAULT_TIMEOUT_MS_REMOTE >> shift); - return timeout; } - //--------------------------------------------------------------------------------- - template<class t_protocol_handler> - boost::posix_time::milliseconds connection<t_protocol_handler>::get_timeout_from_bytes_read(size_t bytes) + + template<typename T> + connection<T>::connection( + socket_t &&socket, + std::shared_ptr<shared_state> shared_state, + t_connection_type connection_type, + ssl_support_t ssl_support + ): + connection_basic(std::move(socket), shared_state, ssl_support), + m_handler(this, *shared_state, m_conn_context), + m_connection_type(connection_type), + m_io_context{GET_IO_SERVICE(connection_basic::socket_)}, + m_strand{m_io_context}, + m_timers{m_io_context} { - boost::posix_time::milliseconds ms = (boost::posix_time::milliseconds)(unsigned)(bytes * TIMEOUT_EXTRA_MS_PER_BYTE); - const auto cur = m_timer.expires_from_now().total_milliseconds(); - if (cur > 0) - ms += (boost::posix_time::milliseconds)cur; - if (ms > get_default_timeout()) - ms = get_default_timeout(); - return ms; } - //--------------------------------------------------------------------------------- - template<class t_protocol_handler> - unsigned int connection<t_protocol_handler>::host_count(const std::string &host, int delta) + + template<typename T> + connection<T>::~connection() noexcept(false) { - static boost::mutex hosts_mutex; - CRITICAL_REGION_LOCAL(hosts_mutex); - static std::map<std::string, unsigned int> hosts; - unsigned int &val = hosts[host]; - if (delta > 0) - MTRACE("New connection from host " << host << ": " << val); - else if (delta < 0) - MTRACE("Closed connection from host " << host << ": " << val); - CHECK_AND_ASSERT_THROW_MES(delta >= 0 || val >= (unsigned)-delta, "Count would go negative"); - CHECK_AND_ASSERT_THROW_MES(delta <= 0 || val <= std::numeric_limits<unsigned int>::max() - (unsigned)delta, "Count would wrap"); - val += delta; - return val; + std::lock_guard<std::mutex> guard(m_state.lock); + assert(m_state.status == status_t::TERMINATED || + m_state.status == status_t::WASTED || + m_io_context.stopped() + ); + if (m_state.status != status_t::WASTED) + return; + try { host_count(-1); } catch (...) { /* ignore */ } } - //--------------------------------------------------------------------------------- - template<class t_protocol_handler> - void connection<t_protocol_handler>::reset_timer(boost::posix_time::milliseconds ms, bool add) + + template<typename T> + bool connection<T>::start( + bool is_income, + bool is_multithreaded + ) { - const auto tms = ms.total_milliseconds(); - if (tms < 0 || (add && tms == 0)) - { - MWARNING("Ignoring negative timeout " << ms); - return; - } - MTRACE((add ? "Adding" : "Setting") << " " << ms << " expiry"); - auto self = safe_shared_from_this(); - if(!self) - { - MERROR("Resetting timer on a dead object"); - return; - } - if (m_was_shutdown) - { - MERROR("Setting timer on a shut down object"); - return; - } - if (add) - { - const auto cur = m_timer.expires_from_now().total_milliseconds(); - if (cur > 0) - ms += (boost::posix_time::milliseconds)cur; - } - m_timer.expires_from_now(ms); - m_timer.async_wait([=](const boost::system::error_code& ec) - { - if(ec == boost::asio::error::operation_aborted) - return; - MDEBUG(context << "connection timeout, closing"); - self->close(); - }); + return start_internal(is_income, is_multithreaded, {}); } - //--------------------------------------------------------------------------------- - template<class t_protocol_handler> - bool connection<t_protocol_handler>::shutdown() + + template<typename T> + bool connection<T>::start( + bool is_income, + bool is_multithreaded, + network_address real_remote + ) { - CRITICAL_REGION_BEGIN(m_shutdown_lock); - if (m_was_shutdown) - return true; - m_was_shutdown = true; - // Initiate graceful connection closure. - m_timer.cancel(); - boost::system::error_code ignored_ec; - if (m_ssl_support == epee::net_utils::ssl_support_t::e_ssl_support_enabled) - { - const shared_state &state = static_cast<const shared_state&>(get_state()); - if (!state.stop_signal_sent) - socket_.shutdown(ignored_ec); - } - socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ignored_ec); - if (!m_host.empty()) - { - try { host_count(m_host, -1); } catch (...) { /* ignore */ } - m_host = ""; - } - CRITICAL_REGION_END(); - m_protocol_handler.release_protocol(); - return true; + return start_internal(is_income, is_multithreaded, real_remote); } - //--------------------------------------------------------------------------------- - template<class t_protocol_handler> - bool connection<t_protocol_handler>::close() + + template<typename T> + void connection<T>::save_dbg_log() { - TRY_ENTRY(); - auto self = safe_shared_from_this(); - if(!self) - return false; - //_info("[sock " << socket().native_handle() << "] Que Shutdown called."); - m_timer.cancel(); - size_t send_que_size = 0; - CRITICAL_REGION_BEGIN(m_send_que_lock); - send_que_size = m_send_que.size(); - CRITICAL_REGION_END(); - m_want_close_connection = true; - if(!send_que_size) - { - shutdown(); + std::lock_guard<std::mutex> guard(m_state.lock); + std::string address; + std::string port; + ec_t ec; + auto endpoint = connection_basic::socket().remote_endpoint(ec); + if (ec.value()) { + address = "<not connected>"; + port = "<not connected>"; } - - return true; - CATCH_ENTRY_L0("connection<t_protocol_handler>::close", false); + else { + address = endpoint.address().to_string(); + port = std::to_string(endpoint.port()); + } + MDEBUG( + " connection type " << std::to_string(m_connection_type) << + " " << connection_basic::socket().local_endpoint().address().to_string() << + ":" << connection_basic::socket().local_endpoint().port() << + " <--> " << m_conn_context.m_remote_address.str() << + " (via " << address << ":" << port << ")" + ); } - //--------------------------------------------------------------------------------- - template<class t_protocol_handler> - bool connection<t_protocol_handler>::send_done() + + template<typename T> + bool connection<T>::speed_limit_is_enabled() const { - if (m_ready_to_close) - return close(); - m_ready_to_close = true; - return true; + return m_connection_type != e_connection_type_RPC; } - //--------------------------------------------------------------------------------- - template<class t_protocol_handler> - bool connection<t_protocol_handler>::cancel() + + template<typename T> + bool connection<T>::cancel() { return close(); } - //--------------------------------------------------------------------------------- - template<class t_protocol_handler> - void connection<t_protocol_handler>::handle_write(const boost::system::error_code& e, size_t cb) + + template<typename T> + bool connection<T>::do_send(byte_slice message) { - TRY_ENTRY(); - LOG_TRACE_CC(context, "[sock " << socket().native_handle() << "] Async send calledback " << cb); + return send(std::move(message)); + } - if (e) - { - _dbg1("[sock " << socket().native_handle() << "] Some problems at write: " << e.message() << ':' << e.value()); - shutdown(); - return; - } - logger_handle_net_write(cb); + template<typename T> + bool connection<T>::send_done() + { + return true; + } - // The single sleeping that is needed for correctly handling "out" speed throttling - if (speed_limit_is_enabled()) { - sleep_before_packet(cb, 1, 1); - } + template<typename T> + bool connection<T>::close() + { + std::lock_guard<std::mutex> guard(m_state.lock); + if (m_state.status != status_t::RUNNING) + return false; + terminate(); + return true; + } - bool do_shutdown = false; - CRITICAL_REGION_BEGIN(m_send_que_lock); - if(m_send_que.empty()) - { - _erro("[sock " << socket().native_handle() << "] m_send_que.size() == 0 at handle_write!"); - return; + template<typename T> + bool connection<T>::call_run_once_service_io() + { + if(connection_basic::m_is_multithreaded) { + if (!m_io_context.poll_one()) + misc_utils::sleep_no_w(1); } - - m_send_que.pop_front(); - if(m_send_que.empty()) - { - if(m_want_close_connection) - { - do_shutdown = true; - } - }else - { - //have more data to send - reset_timer(get_default_timeout(), false); - auto size_now = m_send_que.front().size(); - MDEBUG("handle_write() NOW SENDS: packet="<<size_now<<" B" <<", from queue size="<<m_send_que.size()); - if (speed_limit_is_enabled()) - do_send_handler_write_from_queue(e, m_send_que.front().size() , m_send_que.size()); // (((H))) - CHECK_AND_ASSERT_MES( size_now == m_send_que.front().size(), void(), "Unexpected queue size"); - async_write(boost::asio::buffer(m_send_que.front().data(), size_now) , - strand_.wrap( - std::bind(&connection<t_protocol_handler>::handle_write, connection<t_protocol_handler>::shared_from_this(), std::placeholders::_1, std::placeholders::_2) - ) - ); - //_dbg3("(normal)" << size_now); + else { + if (!m_io_context.run_one()) + return false; } - CRITICAL_REGION_END(); + return true; + } - if(do_shutdown) - { - shutdown(); - } - CATCH_ENTRY_L0("connection<t_protocol_handler>::handle_write", void()); + template<typename T> + bool connection<T>::request_callback() + { + std::lock_guard<std::mutex> guard(m_state.lock); + if (m_state.status != status_t::RUNNING) + return false; + auto self = connection<T>::shared_from_this(); + ++m_state.protocol.wait_callback; + connection_basic::strand_.post([this, self]{ + m_handler.handle_qued_callback(); + std::lock_guard<std::mutex> guard(m_state.lock); + --m_state.protocol.wait_callback; + if (m_state.status == status_t::INTERRUPTED) + on_interrupted(); + else if (m_state.status == status_t::TERMINATING) + on_terminating(); + }); + return true; } - //--------------------------------------------------------------------------------- - template<class t_protocol_handler> - void connection<t_protocol_handler>::setRpcStation() + template<typename T> + typename connection<T>::io_context_t &connection<T>::get_io_service() { - m_connection_type = e_connection_type_RPC; - MDEBUG("set m_connection_type = RPC "); + return m_io_context; } + template<typename T> + bool connection<T>::add_ref() + { + try { + auto self = connection<T>::shared_from_this(); + std::lock_guard<std::mutex> guard(m_state.lock); + this->self = std::move(self); + ++m_state.protocol.reference_counter; + return true; + } + catch (boost::bad_weak_ptr &exception) { + return false; + } + } - template<class t_protocol_handler> - bool connection<t_protocol_handler>::speed_limit_is_enabled() const { - return m_connection_type != e_connection_type_RPC ; - } + template<typename T> + bool connection<T>::release() + { + connection_ptr self; + std::lock_guard<std::mutex> guard(m_state.lock); + if (!(--m_state.protocol.reference_counter)) + self = std::move(this->self); + return true; + } - /************************************************************************/ - /* */ - /************************************************************************/ + template<typename T> + void connection<T>::setRpcStation() + { + std::lock_guard<std::mutex> guard(m_state.lock); + m_connection_type = e_connection_type_RPC; + } template<class t_protocol_handler> boosted_tcp_server<t_protocol_handler>::boosted_tcp_server( t_connection_type connection_type ) : diff --git a/contrib/epee/include/net/enums.h b/contrib/epee/include/net/enums.h index 8c402da20..b9e05a3eb 100644 --- a/contrib/epee/include/net/enums.h +++ b/contrib/epee/include/net/enums.h @@ -64,3 +64,13 @@ namespace net_utils } // net_utils } // epee +namespace std +{ + template<> struct hash<epee::net_utils::zone> + { + std::size_t operator()(const epee::net_utils::zone _z) const + { + return static_cast<std::size_t>(_z); + } + }; +} // std diff --git a/contrib/epee/include/net/net_ssl.h b/contrib/epee/include/net/net_ssl.h index 108e6771b..c79a3acc1 100644 --- a/contrib/epee/include/net/net_ssl.h +++ b/contrib/epee/include/net/net_ssl.h @@ -110,6 +110,11 @@ namespace net_utils //! Search against internal fingerprints. Always false if `behavior() != user_certificate_check`. bool has_fingerprint(boost::asio::ssl::verify_context &ctx) const; + //! configure ssl_stream handshake verification + void configure( + boost::asio::ssl::stream<boost::asio::ip::tcp::socket> &socket, + boost::asio::ssl::stream_base::handshake_type type, + const std::string& host = {}) const; boost::asio::ssl::context create_context() const; /*! \note If `this->support == autodetect && this->verification != none`, diff --git a/contrib/epee/include/storages/portable_storage_from_json.h b/contrib/epee/include/storages/portable_storage_from_json.h index 3021598f5..69192ca6b 100644 --- a/contrib/epee/include/storages/portable_storage_from_json.h +++ b/contrib/epee/include/storages/portable_storage_from_json.h @@ -51,7 +51,6 @@ namespace epee { CHECK_AND_ASSERT_THROW_MES(recursion < EPEE_JSON_RECURSION_LIMIT_INTERNAL, "Wrong JSON data: recursion limitation (" << EPEE_JSON_RECURSION_LIMIT_INTERNAL << ") exceeded"); - std::string::const_iterator sub_element_start; std::string name; typename t_storage::harray h_array = nullptr; enum match_state diff --git a/contrib/epee/src/net_ssl.cpp b/contrib/epee/src/net_ssl.cpp index 7dfb56068..2d0b7d791 100644 --- a/contrib/epee/src/net_ssl.cpp +++ b/contrib/epee/src/net_ssl.cpp @@ -32,6 +32,8 @@ #include <boost/asio/ssl.hpp> #include <boost/cerrno.hpp> #include <boost/filesystem/operations.hpp> +#include <boost/asio/strand.hpp> +#include <condition_variable> #include <boost/lambda/lambda.hpp> #include <openssl/ssl.h> #include <openssl/pem.h> @@ -488,12 +490,10 @@ bool ssl_options_t::has_fingerprint(boost::asio::ssl::verify_context &ctx) const return false; } -bool ssl_options_t::handshake( +void ssl_options_t::configure( boost::asio::ssl::stream<boost::asio::ip::tcp::socket> &socket, boost::asio::ssl::stream_base::handshake_type type, - boost::asio::const_buffer buffer, - const std::string& host, - std::chrono::milliseconds timeout) const + const std::string& host) const { socket.next_layer().set_option(boost::asio::ip::tcp::no_delay(true)); @@ -538,30 +538,98 @@ bool ssl_options_t::handshake( return true; }); } +} + +bool ssl_options_t::handshake( + boost::asio::ssl::stream<boost::asio::ip::tcp::socket> &socket, + boost::asio::ssl::stream_base::handshake_type type, + boost::asio::const_buffer buffer, + const std::string& host, + std::chrono::milliseconds timeout) const +{ + configure(socket, type, host); + + auto start_handshake = [&]{ + using ec_t = boost::system::error_code; + using timer_t = boost::asio::steady_timer; + using strand_t = boost::asio::io_service::strand; + using socket_t = boost::asio::ip::tcp::socket; + + auto &io_context = GET_IO_SERVICE(socket); + if (io_context.stopped()) + io_context.reset(); + strand_t strand(io_context); + timer_t deadline(io_context, timeout); + + struct state_t { + std::mutex lock; + std::condition_variable_any condition; + ec_t result; + bool wait_timer; + bool wait_handshake; + bool cancel_timer; + bool cancel_handshake; + }; + state_t state{}; + + state.wait_timer = true; + auto on_timer = [&](const ec_t &ec){ + std::lock_guard<std::mutex> guard(state.lock); + state.wait_timer = false; + state.condition.notify_all(); + if (!state.cancel_timer) { + state.cancel_handshake = true; + ec_t ec; + socket.next_layer().cancel(ec); + } + }; + + state.wait_handshake = true; + auto on_handshake = [&](const ec_t &ec, size_t bytes_transferred){ + std::lock_guard<std::mutex> guard(state.lock); + state.wait_handshake = false; + state.condition.notify_all(); + state.result = ec; + if (!state.cancel_handshake) { + state.cancel_timer = true; + ec_t ec; + deadline.cancel(ec); + } + }; + + deadline.async_wait(on_timer); + strand.post( + [&]{ + socket.async_handshake( + type, + boost::asio::buffer(buffer), + strand.wrap(on_handshake) + ); + } + ); - auto& io_service = GET_IO_SERVICE(socket); - boost::asio::steady_timer deadline(io_service, timeout); - deadline.async_wait([&socket](const boost::system::error_code& error) { - if (error != boost::asio::error::operation_aborted) + while (!io_context.stopped()) { - socket.next_layer().close(); + io_context.poll_one(); + std::lock_guard<std::mutex> guard(state.lock); + state.condition.wait_for( + state.lock, + std::chrono::milliseconds(30), + [&]{ + return !state.wait_timer && !state.wait_handshake; + } + ); + if (!state.wait_timer && !state.wait_handshake) + break; } - }); - - boost::system::error_code ec = boost::asio::error::would_block; - socket.async_handshake(type, boost::asio::buffer(buffer), boost::lambda::var(ec) = boost::lambda::_1); - if (io_service.stopped()) - { - io_service.reset(); - } - while (ec == boost::asio::error::would_block && !io_service.stopped()) - { - // should poll_one(), can't run_one() because it can block if there is - // another worker thread executing io_service's tasks - // TODO: once we get Boost 1.66+, replace with run_one_for/run_until - std::this_thread::sleep_for(std::chrono::milliseconds(30)); - io_service.poll_one(); - } + if (state.result.value()) { + ec_t ec; + socket.next_layer().shutdown(socket_t::shutdown_both, ec); + socket.next_layer().close(ec); + } + return state.result; + }; + const auto ec = start_handshake(); if (ec) { diff --git a/contrib/gitian/README.md b/contrib/gitian/README.md index 9852b07ba..5211b8409 100644 --- a/contrib/gitian/README.md +++ b/contrib/gitian/README.md @@ -133,10 +133,11 @@ Common setup part: su - gitianuser GH_USER=YOUR_GITHUB_USER_NAME -VERSION=v0.17.2.0 +VERSION=v0.18.0.0 ``` Where `GH_USER` is your GitHub user name and `VERSION` is the version tag you want to build. +The `gitian-build.py`'s `--setup` switch will also refresh the environment of any stale files and submodules. Setup for LXC: diff --git a/contrib/gitian/gitian-android.yml b/contrib/gitian/gitian-android.yml index 23cb7d0e8..7e9ca8178 100644 --- a/contrib/gitian/gitian-android.yml +++ b/contrib/gitian/gitian-android.yml @@ -1,5 +1,5 @@ --- -name: "monero-android-0.17" +name: "monero-android-0.18" enable_cache: true suites: - "bionic" diff --git a/contrib/gitian/gitian-build.py b/contrib/gitian/gitian-build.py index 859c2c645..859f460a7 100755 --- a/contrib/gitian/gitian-build.py +++ b/contrib/gitian/gitian-build.py @@ -31,8 +31,10 @@ def setup(): subprocess.check_call(['git', 'checkout', 'c0f77ca018cb5332bfd595e0aff0468f77542c23']) os.makedirs('inputs', exist_ok=True) os.chdir('inputs') - if not os.path.isdir('monero'): - subprocess.check_call(['git', 'clone', args.url, 'monero']) + if os.path.isdir('monero'): + # Remove the potentially stale monero dir. Otherwise you might face submodule mismatches. + subprocess.check_call(['rm', 'monero', '-fR']) + subprocess.check_call(['git', 'clone', args.url, 'monero']) os.chdir('..') make_image_prog = ['bin/make-base-vm', '--suite', 'bionic', '--arch', 'amd64'] if args.docker: diff --git a/contrib/gitian/gitian-freebsd.yml b/contrib/gitian/gitian-freebsd.yml index 134823b95..7a17f0750 100644 --- a/contrib/gitian/gitian-freebsd.yml +++ b/contrib/gitian/gitian-freebsd.yml @@ -1,5 +1,5 @@ --- -name: "monero-freebsd-0.17" +name: "monero-freebsd-0.18" enable_cache: true suites: - "bionic" diff --git a/contrib/gitian/gitian-linux.yml b/contrib/gitian/gitian-linux.yml index 7ab628fbc..63d2bc5d2 100644 --- a/contrib/gitian/gitian-linux.yml +++ b/contrib/gitian/gitian-linux.yml @@ -1,5 +1,5 @@ --- -name: "monero-linux-0.17" +name: "monero-linux-0.18" enable_cache: true suites: - "bionic" diff --git a/contrib/gitian/gitian-osx.yml b/contrib/gitian/gitian-osx.yml index b4929e822..648688bcd 100644 --- a/contrib/gitian/gitian-osx.yml +++ b/contrib/gitian/gitian-osx.yml @@ -1,5 +1,5 @@ --- -name: "monero-osx-0.17" +name: "monero-osx-0.18" enable_cache: true suites: - "bionic" diff --git a/contrib/gitian/gitian-win.yml b/contrib/gitian/gitian-win.yml index 7d5a249c8..4c607898e 100644 --- a/contrib/gitian/gitian-win.yml +++ b/contrib/gitian/gitian-win.yml @@ -1,5 +1,5 @@ --- -name: "monero-win-0.17" +name: "monero-win-0.18" enable_cache: true suites: - "bionic" diff --git a/docs/PORTABLE_STORAGE.md b/docs/PORTABLE_STORAGE.md index 675ca818c..70e7ff954 100644 --- a/docs/PORTABLE_STORAGE.md +++ b/docs/PORTABLE_STORAGE.md @@ -158,7 +158,7 @@ that most will be familiar with): ```json { - "short_quote": "Give me liberty or give me death!", + "short_quote": "Give me liberty or give me death", "long_quote": "Monero is more than just a technology. It's also what the technology stands for.", "signed_32bit_int": 20140418, "array_of_bools": [true, false, true, true], @@ -169,9 +169,51 @@ that most will be familiar with): } ``` -This would translate to: +This object would translate into the following bytes when serialized into epee portable storage format. The bytes are represented in hex, with comments and whitespace added for readability. -![Epee binary storage format example](/docs/images/storage_binary_example.png) +``` +01 11 01 01 01 01 02 01 // Signature +01 // Version +14 // Varint number of section entries (5) +0b // Length of next section key (11) +73 68 6f 72 74 5f 71 75 6f 74 65 // Section key ("short_quote") +0a // Type code (STRING) +80 // Varint length of string (32) +47 69 76 65 20 6d 65 20 6c 69 62 65 72 74 79 20 // STRING value ("Give me liberty ") +6f 72 20 67 69 76 65 20 6d 65 20 64 65 61 74 68 // STRING value cont. ("or give me death") +0a // Length of next section key (10) +6c 6f 6e 67 5f 71 75 6f 74 65 // Section key ("long_quote") +0a // Type code (STRING) +41 01 // Varint length of string (80). Note it's 2 bytes +4d 6f 6e 65 72 6f 20 69 73 20 6d 6f 72 65 20 74 // STRING value ("Monero is more t") +68 61 6e 20 6a 75 73 74 20 61 20 74 65 63 68 6e // STRING value cont. ("han just a techn") +6f 6c 6f 67 79 2e 20 49 74 27 73 20 61 6c 73 6f // STRING value cont. ("ology. It's also") +20 77 68 61 74 20 74 68 65 20 74 65 63 68 6e 6f // STRING value cont. (" what the techno") +6c 6f 67 79 20 73 74 61 6e 64 73 20 66 6f 72 2e // STRING value cont. ("logy stands for.") +10 // Length of next section key (16) +73 69 67 6e 65 64 5f 33 32 62 69 74 5f 69 6e 74 // Section key ("signed_32bit_int") +02 // type code (INT32) +82 51 33 01 // INT32 value (20140418) +0e // Length of next section key (14) +61 72 72 61 79 5f 6f 66 5f 62 6f 6f 6c 73 // Section key ("array_of_bools") +8b // Type code (BOOL | FLAG_ARRAY) +10 // Varint number of array elements (4) +01 00 01 01 // Array BOOL values [true, false, true, true] +0e // Length of next section key (14) +6e 65 73 74 65 64 5f 73 65 63 74 69 6f 6e // Section key ("nested_section") +0c // Type code (OBJECT) +08 // Varint number of inner section entries (2) +06 // Length of first inner section key (6) +64 6f 75 62 6c 65 // Section key ("double") +09 // Type code (DOUBLE) +9a 99 99 99 99 99 1b c0 // DOUBLE value (-6.9) +12 // Length of second inner section key (18) +75 6e 73 69 67 6e 65 64 5f 36 34 62 69 74 5f 69 // Section key ("unsigned_64bit_i") +6e 74 // Section key cont ("nt") +05 // Type code (UINT64) +c7 71 ac b5 af 98 32 9a // UINT64 value (11111111111111111111) + +``` ## Monero specifics diff --git a/docs/images/storage_binary_example.png b/docs/images/storage_binary_example.png Binary files differdeleted file mode 100644 index 1baa20cc1..000000000 --- a/docs/images/storage_binary_example.png +++ /dev/null diff --git a/external/boost/archive/portable_binary_archive.hpp b/external/boost/archive/portable_binary_archive.hpp index 7ae01a225..b1d6ae73e 100644 --- a/external/boost/archive/portable_binary_archive.hpp +++ b/external/boost/archive/portable_binary_archive.hpp @@ -44,9 +44,16 @@ reverse_bytes(signed char size, char *address){ char * first = address;
char * last = first + size - 1;
for(;first < last;++first, --last){
+#if defined(__GNUC__) && !defined(__clang__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wstringop-overflow="
+#endif
char x = *last;
*last = *first;
*first = x;
+#if defined(__GNUC__) && !defined(__clang__)
+#pragma GCC diagnostic pop
+#endif
}
}
diff --git a/external/db_drivers/liblmdb/mdb.c b/external/db_drivers/liblmdb/mdb.c index 6314d5775..bf60c7013 100644 --- a/external/db_drivers/liblmdb/mdb.c +++ b/external/db_drivers/liblmdb/mdb.c @@ -1467,6 +1467,8 @@ struct MDB_env { #endif /** Failed to update the meta page. Probably an I/O error. */ #define MDB_FATAL_ERROR 0x80000000U + /** using a raw block device */ +#define MDB_RAWPART 0x40000000U /** Some fields are initialized. */ #define MDB_ENV_ACTIVE 0x20000000U /** me_txkey is set */ @@ -4038,6 +4040,8 @@ fail: return rc; } +static int ESECT mdb_env_map(MDB_env *env, void *addr); + /** Read the environment parameters of a DB environment before * mapping it into memory. * @param[in] env the environment handle @@ -4054,6 +4058,31 @@ mdb_env_read_header(MDB_env *env, int prev, MDB_meta *meta) int i, rc, off; enum { Size = sizeof(pbuf) }; + if (env->me_flags & MDB_RAWPART) { +#define VM_ALIGN 0x200000 + env->me_mapsize += VM_ALIGN-1; + env->me_mapsize &= ~(VM_ALIGN-1); + env->me_psize = env->me_os_psize; + rc = mdb_env_map(env, NULL); + if (rc) { + DPRINTF(("mdb_env_map: %s", mdb_strerror(rc))); + return rc; + } + p = (MDB_page *)env->me_map; + for (i=0; i<NUM_METAS; i++) { + if (!F_ISSET(p->mp_flags, P_META)) + return ENOENT; + if (env->me_metas[i]->mm_magic != MDB_MAGIC) + return MDB_INVALID; + if (env->me_metas[i]->mm_version != MDB_DATA_VERSION) + return MDB_VERSION_MISMATCH; + if (i == 0 || env->me_metas[i]->mm_txnid > meta->mm_txnid) + *meta = *env->me_metas[i]; + p = (MDB_page *)((char *)p + env->me_psize); + } + return 0; + } + /* We don't know the page size yet, so use a minimum value. * Read both meta pages so we can use the latest one. */ @@ -4081,6 +4110,8 @@ mdb_env_read_header(MDB_env *env, int prev, MDB_meta *meta) p = (MDB_page *)&pbuf; if (!F_ISSET(p->mp_flags, P_META)) { + if (env->me_flags & MDB_RAWPART) + return ENOENT; DPRINTF(("page %"Yu" not a meta page", p->mp_pgno)); return MDB_INVALID; } @@ -4148,6 +4179,18 @@ mdb_env_init_meta(MDB_env *env, MDB_meta *meta) psize = env->me_psize; + if ((env->me_flags & (MDB_RAWPART|MDB_WRITEMAP)) == (MDB_RAWPART|MDB_WRITEMAP)) { + p = (MDB_page *)env->me_map; + p->mp_pgno = 0; + p->mp_flags = P_META; + *(MDB_meta *)METADATA(p) = *meta; + q = (MDB_page *)((char *)p + psize); + q->mp_pgno = 1; + q->mp_flags = P_META; + *(MDB_meta *)METADATA(q) = *meta; + return 0; + } + p = calloc(NUM_METAS, psize); if (!p) return ENOMEM; @@ -4410,7 +4453,7 @@ mdb_env_map(MDB_env *env, void *addr) int prot = PROT_READ; if (flags & MDB_WRITEMAP) { prot |= PROT_WRITE; - if (ftruncate(env->me_fd, env->me_mapsize) < 0) + if (!(flags & MDB_RAWPART) && ftruncate(env->me_fd, env->me_mapsize) < 0) return ErrCode(); } env->me_map = mmap(addr, env->me_mapsize, prot, MAP_SHARED, @@ -5449,6 +5492,17 @@ mdb_env_open(MDB_env *env, const char *path, unsigned int flags, mdb_mode_t mode goto leave; #endif #endif +#ifndef _WIN32 + { + struct stat st; + flags &= ~MDB_RAWPART; + if (!stat(path, &st) && (S_ISBLK(st.st_mode) || S_ISCHR(st.st_mode))) { + flags |= MDB_RAWPART | MDB_NOSUBDIR; + if (!env->me_mapsize) + env->me_mapsize = DEFAULT_MAPSIZE; + } + } +#endif flags |= MDB_ENV_ACTIVE; /* tell mdb_env_close0() to clean up */ if (flags & MDB_RDONLY) { @@ -7668,7 +7722,7 @@ more: offset *= 4; /* space for 4 more */ break; } - /* FALLTHRU: Big enough MDB_DUPFIXED sub-page */ + /* FALLTHRU *//* Big enough MDB_DUPFIXED sub-page */ case MDB_CURRENT: fp->mp_flags |= P_DIRTY; COPY_PGNO(fp->mp_pgno, mp->mp_pgno); diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp index e2ac9df0b..db7fa6c7c 100644 --- a/src/blockchain_db/lmdb/db_lmdb.cpp +++ b/src/blockchain_db/lmdb/db_lmdb.cpp @@ -25,13 +25,6 @@ // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -#ifndef _WIN32 -#include <sys/stat.h> -#include <sys/types.h> -#include <sys/mman.h> -#include <fcntl.h> -#endif - #include "db_lmdb.h" #include <boost/filesystem.hpp> @@ -1303,26 +1296,6 @@ BlockchainLMDB::BlockchainLMDB(bool batch_transactions): BlockchainDB() m_hardfork = nullptr; } -void BlockchainLMDB::check_mmap_support() -{ -#ifndef _WIN32 - const boost::filesystem::path mmap_test_file = m_folder / boost::filesystem::unique_path(); - int mmap_test_fd = ::open(mmap_test_file.string().c_str(), O_RDWR | O_CREAT, 0600); - if (mmap_test_fd < 0) - throw0(DB_ERROR((std::string("Failed to check for mmap support: open failed: ") + strerror(errno)).c_str())); - epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler([mmap_test_fd, &mmap_test_file]() { - ::close(mmap_test_fd); - boost::filesystem::remove(mmap_test_file.string()); - }); - if (write(mmap_test_fd, "mmaptest", 8) != 8) - throw0(DB_ERROR((std::string("Failed to check for mmap support: write failed: ") + strerror(errno)).c_str())); - void *mmap_res = mmap(NULL, 8, PROT_READ, MAP_SHARED, mmap_test_fd, 0); - if (mmap_res == MAP_FAILED) - throw0(DB_ERROR("This filesystem does not support mmap: use --data-dir to place the blockchain on a filesystem which does")); - munmap(mmap_res, 8); -#endif -} - void BlockchainLMDB::open(const std::string& filename, const int db_flags) { int result; @@ -1334,14 +1307,8 @@ void BlockchainLMDB::open(const std::string& filename, const int db_flags) throw0(DB_OPEN_FAILURE("Attempted to open db, but it's already open")); boost::filesystem::path direc(filename); - if (boost::filesystem::exists(direc)) - { - if (!boost::filesystem::is_directory(direc)) - throw0(DB_OPEN_FAILURE("LMDB needs a directory path, but a file was passed")); - } - else - { - if (!boost::filesystem::create_directories(direc)) + if (!boost::filesystem::exists(direc) && + !boost::filesystem::create_directories(direc)) { throw0(DB_OPEN_FAILURE(std::string("Failed to create directory ").append(filename).c_str())); } @@ -1364,9 +1331,6 @@ void BlockchainLMDB::open(const std::string& filename, const int db_flags) m_folder = filename; - try { check_mmap_support(); } - catch(...) { MERROR("Failed to check for mmap support, proceeding"); } - #ifdef __OpenBSD__ if ((mdb_flags & MDB_WRITEMAP) == 0) { MCLOG_RED(el::Level::Info, "global", "Running on OpenBSD: forcing WRITEMAP"); diff --git a/src/blockchain_db/lmdb/db_lmdb.h b/src/blockchain_db/lmdb/db_lmdb.h index 20edab2e9..bdae44948 100644 --- a/src/blockchain_db/lmdb/db_lmdb.h +++ b/src/blockchain_db/lmdb/db_lmdb.h @@ -359,7 +359,6 @@ public: static int compare_string(const MDB_val *a, const MDB_val *b); private: - void check_mmap_support(); void do_resize(uint64_t size_increase=0); bool need_resize(uint64_t threshold_size=0) const; diff --git a/src/blocks/checkpoints.dat b/src/blocks/checkpoints.dat Binary files differindex e75e379f2..2ed1d630f 100644 --- a/src/blocks/checkpoints.dat +++ b/src/blocks/checkpoints.dat diff --git a/src/checkpoints/checkpoints.cpp b/src/checkpoints/checkpoints.cpp index 27e77cae8..330e3653c 100644 --- a/src/checkpoints/checkpoints.cpp +++ b/src/checkpoints/checkpoints.cpp @@ -239,6 +239,7 @@ namespace cryptonote ADD_CHECKPOINT2(2046000, "5e867f0b8baefed9244a681df97fc885d8ab36c3dfcd24c7a3abf3b8ac8b8314", "0x9cb8b6ff2978c6"); ADD_CHECKPOINT2(2092500, "c4e00820c9c7989b49153d5e90ae095a18a11d990e82fcc3be54e6ed785472b5", "0xb4e585a31369cb"); ADD_CHECKPOINT2(2182500, "0d22b5f81982eff21d094af9e821dc2007e6342069e3b1a37b15d97646353124", "0xead4a874083492"); + ADD_CHECKPOINT2(2661600, "41c9060e8426012238e8a26da26fcb90797436896cc70886a894c2c560bcccf2", "0x2e0d87526ff161f"); return true; } diff --git a/src/common/util.cpp b/src/common/util.cpp index 89dcf4fef..f0de73a06 100644 --- a/src/common/util.cpp +++ b/src/common/util.cpp @@ -85,7 +85,7 @@ using namespace epee; #include <boost/algorithm/string.hpp> #include <boost/asio.hpp> #include <boost/format.hpp> -#include <openssl/sha.h> +#include <openssl/evp.h> #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "util" @@ -941,14 +941,7 @@ std::string get_nix_version_display_string() bool sha256sum(const uint8_t *data, size_t len, crypto::hash &hash) { - SHA256_CTX ctx; - if (!SHA256_Init(&ctx)) - return false; - if (!SHA256_Update(&ctx, data, len)) - return false; - if (!SHA256_Final((unsigned char*)hash.data, &ctx)) - return false; - return true; + return EVP_Digest(data, len, (unsigned char*) hash.data, NULL, EVP_sha256(), NULL) != 0; } bool sha256sum(const std::string &filename, crypto::hash &hash) @@ -961,8 +954,8 @@ std::string get_nix_version_display_string() if (!f) return false; std::ifstream::pos_type file_size = f.tellg(); - SHA256_CTX ctx; - if (!SHA256_Init(&ctx)) + std::unique_ptr<EVP_MD_CTX, decltype(&EVP_MD_CTX_free)> ctx(EVP_MD_CTX_new(), &EVP_MD_CTX_free); + if (!EVP_DigestInit_ex(ctx.get(), EVP_sha256(), nullptr)) return false; size_t size_left = file_size; f.seekg(0, std::ios::beg); @@ -973,12 +966,12 @@ std::string get_nix_version_display_string() f.read(buf, read_size); if (!f || !f.good()) return false; - if (!SHA256_Update(&ctx, buf, read_size)) + if (!EVP_DigestUpdate(ctx.get(), buf, read_size)) return false; size_left -= read_size; } f.close(); - if (!SHA256_Final((unsigned char*)hash.data, &ctx)) + if (!EVP_DigestFinal_ex(ctx.get(), (unsigned char*)hash.data, nullptr)) return false; return true; } diff --git a/src/common/util.h b/src/common/util.h index 25f5ceb47..f489594e8 100644 --- a/src/common/util.h +++ b/src/common/util.h @@ -231,7 +231,27 @@ namespace tools bool is_privacy_preserving_network(const std::string &address); int vercmp(const char *v0, const char *v1); // returns < 0, 0, > 0, similar to strcmp, but more human friendly than lexical - does not attempt to validate + /** + * \brief Creates a SHA-256 digest of a data buffer + * + * \param[in] data pointer to the buffer + * \param[in] len size of the buffer in bytes + * \param[out] hash where message digest will be written to + * + * \returns true if successful, false otherwise + */ bool sha256sum(const uint8_t *data, size_t len, crypto::hash &hash); + + /** + * \brief Creates a SHA-256 digest of a file's contents, equivalent to the sha256sum command in Linux + * + * \param[in] filename path to target file + * \param[out] hash where message digest will be written to + * + * \returns true if successful, false if the file can not be opened or there is an OpenSSL failure + * + * \throws ios_base::failure if after the file is successfully opened, an error occurs during reading + */ bool sha256sum(const std::string &filename, crypto::hash &hash); boost::optional<bool> is_hdd(const char *path); diff --git a/src/cryptonote_basic/account.h b/src/cryptonote_basic/account.h index 6e887db6d..2ee9545d4 100644 --- a/src/cryptonote_basic/account.h +++ b/src/cryptonote_basic/account.h @@ -55,8 +55,6 @@ namespace cryptonote KV_SERIALIZE_VAL_POD_AS_BLOB_OPT(m_encryption_iv, default_iv) END_KV_SERIALIZE_MAP() - account_keys& operator=(account_keys const&) = default; - void encrypt(const crypto::chacha_key &key); void decrypt(const crypto::chacha_key &key); void encrypt_viewkey(const crypto::chacha_key &key); diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp index f101f10c5..388013f96 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.cpp +++ b/src/cryptonote_basic/cryptonote_format_utils.cpp @@ -1042,7 +1042,7 @@ namespace cryptonote crypto::public_key subaddress_spendkey; if (out_can_be_to_acc(view_tag_opt, derivation, output_index)) { - hwdev.derive_subaddress_public_key(out_key, derivation, output_index, subaddress_spendkey); + CHECK_AND_ASSERT_MES(hwdev.derive_subaddress_public_key(out_key, derivation, output_index, subaddress_spendkey), boost::none, "Failed to derive subaddress public key"); auto found = subaddresses.find(subaddress_spendkey); if (found != subaddresses.end()) return subaddress_receive_info{ found->second, derivation }; @@ -1054,7 +1054,7 @@ namespace cryptonote CHECK_AND_ASSERT_MES(output_index < additional_derivations.size(), boost::none, "wrong number of additional derivations"); if (out_can_be_to_acc(view_tag_opt, additional_derivations[output_index], output_index)) { - hwdev.derive_subaddress_public_key(out_key, additional_derivations[output_index], output_index, subaddress_spendkey); + CHECK_AND_ASSERT_MES(hwdev.derive_subaddress_public_key(out_key, additional_derivations[output_index], output_index, subaddress_spendkey), boost::none, "Failed to derive subaddress public key"); auto found = subaddresses.find(subaddress_spendkey); if (found != subaddresses.end()) return subaddress_receive_info{ found->second, additional_derivations[output_index] }; diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index f2a8e9b79..2ec194ef8 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -239,12 +239,15 @@ namespace config const unsigned char HASH_KEY_MEMORY = 'k'; const unsigned char HASH_KEY_MULTISIG[] = {'M', 'u', 'l', 't' , 'i', 's', 'i', 'g', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; const unsigned char HASH_KEY_MULTISIG_KEY_AGGREGATION[] = "Multisig_key_agg"; + const unsigned char HASH_KEY_CLSAG_ROUND_MULTISIG[] = "CLSAG_round_ms_merge_factor"; const unsigned char HASH_KEY_TXPROOF_V2[] = "TXPROOF_V2"; const unsigned char HASH_KEY_CLSAG_ROUND[] = "CLSAG_round"; const unsigned char HASH_KEY_CLSAG_AGG_0[] = "CLSAG_agg_0"; const unsigned char HASH_KEY_CLSAG_AGG_1[] = "CLSAG_agg_1"; const char HASH_KEY_MESSAGE_SIGNING[] = "MoneroMessageSignature"; const unsigned char HASH_KEY_MM_SLOT = 'm'; + const constexpr char HASH_KEY_MULTISIG_TX_PRIVKEYS_SEED[] = "multisig_tx_privkeys_seed"; + const constexpr char HASH_KEY_MULTISIG_TX_PRIVKEYS[] = "multisig_tx_privkeys"; // Multisig const uint32_t MULTISIG_MAX_SIGNERS{16}; diff --git a/src/cryptonote_core/CMakeLists.txt b/src/cryptonote_core/CMakeLists.txt index e272b94f0..69411e379 100644 --- a/src/cryptonote_core/CMakeLists.txt +++ b/src/cryptonote_core/CMakeLists.txt @@ -49,7 +49,6 @@ target_link_libraries(cryptonote_core common cncrypto blockchain_db - multisig ringct device hardforks diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 5b7b4353d..c37dfe9e7 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -5604,7 +5604,7 @@ void Blockchain::cancel() } #if defined(PER_BLOCK_CHECKPOINT) -static const char expected_block_hashes_hash[] = "8da80ca560793f252d1d4ed449c85d75c74867f3f86b8832c8e3f88b1cbb6ae3"; +static const char expected_block_hashes_hash[] = "e9371004b9f6be59921b27bc81e28b4715845ade1c6d16891d5c455f72e21365"; void Blockchain::load_compiled_in_block_hashes(const GetCheckpointsCallback& get_checkpoints) { if (get_checkpoints == nullptr || !m_fast_sync) diff --git a/src/cryptonote_core/cryptonote_tx_utils.cpp b/src/cryptonote_core/cryptonote_tx_utils.cpp index 1d2024a05..472026217 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.cpp +++ b/src/cryptonote_core/cryptonote_tx_utils.cpp @@ -203,7 +203,7 @@ namespace cryptonote return addr.m_view_public_key; } //--------------------------------------------------------------- - bool construct_tx_with_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, bool rct, const rct::RCTConfig &rct_config, rct::multisig_out *msout, bool shuffle_outs, bool use_view_tags) + bool construct_tx_with_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, bool rct, const rct::RCTConfig &rct_config, bool shuffle_outs, bool use_view_tags) { hw::device &hwdev = sender_account_keys.get_device(); @@ -216,10 +216,6 @@ namespace cryptonote std::vector<rct::key> amount_keys; tx.set_null(); amount_keys.clear(); - if (msout) - { - msout->c.clear(); - } tx.version = rct ? 2 : 1; tx.unlock_time = unlock_time; @@ -333,8 +329,8 @@ namespace cryptonote return false; } - //check that derivated key is equal with real output key (if non multisig) - if(!msout && !(in_ephemeral.pub == src_entr.outputs[src_entr.real_output].second.dest) ) + //check that derivated key is equal with real output key + if(!(in_ephemeral.pub == src_entr.outputs[src_entr.real_output].second.dest) ) { LOG_ERROR("derived public key mismatch with output public key at index " << idx << ", real out " << src_entr.real_output << "! "<< ENDL << "derived_key:" << string_tools::pod_to_hex(in_ephemeral.pub) << ENDL << "real output_public_key:" @@ -347,7 +343,7 @@ namespace cryptonote //put key image into tx input txin_to_key input_to_key; input_to_key.amount = src_entr.amount; - input_to_key.k_image = msout ? rct::rct2ki(src_entr.multisig_kLRki.ki) : img; + input_to_key.k_image = img; //fill outputs array and use relative offsets for(const tx_source_entry::output_entry& out_entry: src_entr.outputs) @@ -529,7 +525,6 @@ namespace cryptonote rct::keyV destinations; std::vector<uint64_t> inamounts, outamounts; std::vector<unsigned int> index; - std::vector<rct::multisig_kLRki> kLRki; for (size_t i = 0; i < sources.size(); ++i) { rct::ctkey ctkey; @@ -543,10 +538,6 @@ namespace cryptonote memwipe(&ctkey, sizeof(rct::ctkey)); // inPk: (public key, commitment) // will be done when filling in mixRing - if (msout) - { - kLRki.push_back(sources[i].multisig_kLRki); - } } for (size_t i = 0; i < tx.vout.size(); ++i) { @@ -598,9 +589,9 @@ namespace cryptonote get_transaction_prefix_hash(tx, tx_prefix_hash, hwdev); rct::ctkeyV outSk; if (use_simple_rct) - tx.rct_signatures = rct::genRctSimple(rct::hash2rct(tx_prefix_hash), inSk, destinations, inamounts, outamounts, amount_in - amount_out, mixRing, amount_keys, msout ? &kLRki : NULL, msout, index, outSk, rct_config, hwdev); + tx.rct_signatures = rct::genRctSimple(rct::hash2rct(tx_prefix_hash), inSk, destinations, inamounts, outamounts, amount_in - amount_out, mixRing, amount_keys, index, outSk, rct_config, hwdev); else - tx.rct_signatures = rct::genRct(rct::hash2rct(tx_prefix_hash), inSk, destinations, outamounts, mixRing, amount_keys, msout ? &kLRki[0] : NULL, msout, sources[0].real_output, outSk, rct_config, hwdev); // same index assumption + tx.rct_signatures = rct::genRct(rct::hash2rct(tx_prefix_hash), inSk, destinations, outamounts, mixRing, amount_keys, sources[0].real_output, outSk, rct_config, hwdev); // same index assumption memwipe(inSk.data(), inSk.size() * sizeof(rct::ctkey)); CHECK_AND_ASSERT_MES(tx.vout.size() == outSk.size(), false, "outSk size does not match vout"); @@ -613,7 +604,7 @@ namespace cryptonote return true; } //--------------------------------------------------------------- - bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys, bool rct, const rct::RCTConfig &rct_config, rct::multisig_out *msout, bool use_view_tags) + bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys, bool rct, const rct::RCTConfig &rct_config, bool use_view_tags) { hw::device &hwdev = sender_account_keys.get_device(); hwdev.open_tx(tx_key); @@ -634,7 +625,7 @@ namespace cryptonote } bool shuffle_outs = true; - bool r = construct_tx_with_tx_key(sender_account_keys, subaddresses, sources, destinations, change_addr, extra, tx, unlock_time, tx_key, additional_tx_keys, rct, rct_config, msout, shuffle_outs, use_view_tags); + bool r = construct_tx_with_tx_key(sender_account_keys, subaddresses, sources, destinations, change_addr, extra, tx, unlock_time, tx_key, additional_tx_keys, rct, rct_config, shuffle_outs, use_view_tags); hwdev.close_tx(); return r; } catch(...) { @@ -650,7 +641,7 @@ namespace cryptonote crypto::secret_key tx_key; std::vector<crypto::secret_key> additional_tx_keys; std::vector<tx_destination_entry> destinations_copy = destinations; - return construct_tx_and_get_tx_key(sender_account_keys, subaddresses, sources, destinations_copy, change_addr, extra, tx, unlock_time, tx_key, additional_tx_keys, false, { rct::RangeProofBorromean, 0}, NULL, false); + return construct_tx_and_get_tx_key(sender_account_keys, subaddresses, sources, destinations_copy, change_addr, extra, tx, unlock_time, tx_key, additional_tx_keys, false, { rct::RangeProofBorromean, 0}); } //--------------------------------------------------------------- bool generate_genesis_block( diff --git a/src/cryptonote_core/cryptonote_tx_utils.h b/src/cryptonote_core/cryptonote_tx_utils.h index f4ffb98ff..12d6b8ce5 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.h +++ b/src/cryptonote_core/cryptonote_tx_utils.h @@ -119,8 +119,8 @@ namespace cryptonote //--------------------------------------------------------------- crypto::public_key get_destination_view_key_pub(const std::vector<tx_destination_entry> &destinations, const boost::optional<cryptonote::account_public_address>& change_addr); bool construct_tx(const account_keys& sender_account_keys, std::vector<tx_source_entry> &sources, const std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time); - bool construct_tx_with_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, bool rct = false, const rct::RCTConfig &rct_config = { rct::RangeProofBorromean, 0 }, rct::multisig_out *msout = NULL, bool shuffle_outs = true, bool use_view_tags = false); - bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys, bool rct = false, const rct::RCTConfig &rct_config = { rct::RangeProofBorromean, 0 }, rct::multisig_out *msout = NULL, bool use_view_tags = false); + bool construct_tx_with_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, bool rct = false, const rct::RCTConfig &rct_config = { rct::RangeProofBorromean, 0 }, bool shuffle_outs = true, bool use_view_tags = false); + bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys, bool rct = false, const rct::RCTConfig &rct_config = { rct::RangeProofBorromean, 0 }, bool use_view_tags = false); bool generate_output_ephemeral_keys(const size_t tx_version, const cryptonote::account_keys &sender_account_keys, const crypto::public_key &txkey_pub, const crypto::secret_key &tx_key, const cryptonote::tx_destination_entry &dst_entr, const boost::optional<cryptonote::account_public_address> &change_addr, const size_t output_index, const bool &need_additional_txkeys, const std::vector<crypto::secret_key> &additional_tx_keys, diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp index c27261860..a68da0e62 100644 --- a/src/cryptonote_core/tx_pool.cpp +++ b/src/cryptonote_core/tx_pool.cpp @@ -97,9 +97,9 @@ namespace cryptonote constexpr const std::chrono::seconds forward_delay_average{CRYPTONOTE_FORWARD_DELAY_AVERAGE}; // a kind of increasing backoff within min/max bounds - uint64_t get_relay_delay(time_t now, time_t received) + uint64_t get_relay_delay(time_t last_relay, time_t received) { - time_t d = (now - received + MIN_RELAY_TIME) / MIN_RELAY_TIME * MIN_RELAY_TIME; + time_t d = (last_relay - received + MIN_RELAY_TIME) / MIN_RELAY_TIME * MIN_RELAY_TIME; if (d > MAX_RELAY_TIME) d = MAX_RELAY_TIME; return d; @@ -779,7 +779,7 @@ namespace cryptonote case relay_method::local: case relay_method::fluff: case relay_method::block: - if (now - meta.last_relayed_time <= get_relay_delay(now, meta.receive_time)) + if (now - meta.last_relayed_time <= get_relay_delay(meta.last_relayed_time, meta.receive_time)) return true; // continue to next tx break; } @@ -812,7 +812,7 @@ namespace cryptonote function is only called every ~2 minutes, so this resetting should be unnecessary, but is primarily a precaution against potential changes to the callback routines. */ - elem.second.last_relayed_time = now + get_relay_delay(now, elem.second.receive_time); + elem.second.last_relayed_time = now + get_relay_delay(elem.second.last_relayed_time, elem.second.receive_time); m_blockchain.update_txpool_tx(elem.first, elem.second); } diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.h b/src/cryptonote_protocol/cryptonote_protocol_handler.h index a1e4df563..515b78c94 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.h +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.h @@ -113,12 +113,23 @@ namespace cryptonote const block_queue &get_block_queue() const { return m_block_queue; } void stop(); void on_connection_close(cryptonote_connection_context &context); - void set_max_out_peers(unsigned int max) { m_max_out_peers = max; } + void set_max_out_peers(epee::net_utils::zone zone, unsigned int max) { CRITICAL_REGION_LOCAL(m_max_out_peers_lock); m_max_out_peers[zone] = max; } + unsigned int get_max_out_peers(epee::net_utils::zone zone) const + { + CRITICAL_REGION_LOCAL(m_max_out_peers_lock); + const auto it = m_max_out_peers.find(zone); + if (it == m_max_out_peers.end()) + { + MWARNING(epee::net_utils::zone_to_string(zone) << " max out peers not set, using default"); + return P2P_DEFAULT_CONNECTIONS_COUNT; + } + return it->second; + } bool no_sync() const { return m_no_sync; } void set_no_sync(bool value) { m_no_sync = value; } std::string get_peers_overview() const; std::pair<uint32_t, uint32_t> get_next_needed_pruning_stripe() const; - bool needs_new_sync_connections() const; + bool needs_new_sync_connections(epee::net_utils::zone zone) const; bool is_busy_syncing(); private: @@ -171,7 +182,8 @@ namespace cryptonote epee::math_helper::once_a_time_milliseconds<100> m_standby_checker; epee::math_helper::once_a_time_seconds<101> m_sync_search_checker; epee::math_helper::once_a_time_seconds<43> m_bad_peer_checker; - std::atomic<unsigned int> m_max_out_peers; + std::unordered_map<epee::net_utils::zone, unsigned int> m_max_out_peers; + mutable epee::critical_section m_max_out_peers_lock; tools::PerformanceTimer m_sync_timer, m_add_timer; uint64_t m_last_add_end_time; uint64_t m_sync_spans_downloaded, m_sync_old_spans_downloaded, m_sync_bad_spans_downloaded; diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.inl b/src/cryptonote_protocol/cryptonote_protocol_handler.inl index 891ee109d..af3031263 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.inl +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.inl @@ -1776,33 +1776,49 @@ skip: return true; MTRACE("Checking for outgoing syncing peers..."); - unsigned n_syncing = 0, n_synced = 0; - boost::uuids::uuid last_synced_peer_id(boost::uuids::nil_uuid()); + std::unordered_map<epee::net_utils::zone, unsigned> n_syncing, n_synced; + std::unordered_map<epee::net_utils::zone, boost::uuids::uuid> last_synced_peer_id; + std::vector<epee::net_utils::zone> zones; m_p2p->for_each_connection([&](cryptonote_connection_context& context, nodetool::peerid_type peer_id, uint32_t support_flags)->bool { if (!peer_id || context.m_is_income) // only consider connected outgoing peers return true; + + const epee::net_utils::zone zone = context.m_remote_address.get_zone(); + if (n_syncing.find(zone) == n_syncing.end()) + { + n_syncing[zone] = 0; + n_synced[zone] = 0; + last_synced_peer_id[zone] = boost::uuids::nil_uuid(); + zones.push_back(zone); + } + if (context.m_state == cryptonote_connection_context::state_synchronizing) - ++n_syncing; + ++n_syncing[zone]; if (context.m_state == cryptonote_connection_context::state_normal) { - ++n_synced; + ++n_synced[zone]; if (!context.m_anchor) - last_synced_peer_id = context.m_connection_id; + last_synced_peer_id[zone] = context.m_connection_id; } return true; }); - MTRACE(n_syncing << " syncing, " << n_synced << " synced"); - // if we're at max out peers, and not enough are syncing - if (n_synced + n_syncing >= m_max_out_peers && n_syncing < P2P_DEFAULT_SYNC_SEARCH_CONNECTIONS_COUNT && last_synced_peer_id != boost::uuids::nil_uuid()) + for (const auto& zone : zones) { - if (!m_p2p->for_connection(last_synced_peer_id, [&](cryptonote_connection_context& ctx, nodetool::peerid_type peer_id, uint32_t f)->bool{ - MINFO(ctx << "dropping synced peer, " << n_syncing << " syncing, " << n_synced << " synced"); - drop_connection(ctx, false, false); - return true; - })) - MDEBUG("Failed to find peer we wanted to drop"); + const unsigned int max_out_peers = get_max_out_peers(zone); + MTRACE("[" << epee::net_utils::zone_to_string(zone) << "] " << n_syncing[zone] << " syncing, " << n_synced[zone] << " synced, " << max_out_peers << " max out peers"); + + // if we're at max out peers, and not enough are syncing, drop the last sync'd non-anchor + if (n_synced[zone] + n_syncing[zone] >= max_out_peers && n_syncing[zone] < P2P_DEFAULT_SYNC_SEARCH_CONNECTIONS_COUNT && last_synced_peer_id[zone] != boost::uuids::nil_uuid()) + { + if (!m_p2p->for_connection(last_synced_peer_id[zone], [&](cryptonote_connection_context& ctx, nodetool::peerid_type peer_id, uint32_t f)->bool{ + MINFO(ctx << "dropping synced peer, " << n_syncing[zone] << " syncing, " << n_synced[zone] << " synced, " << max_out_peers << " max out peers"); + drop_connection(ctx, false, false); + return true; + })) + MDEBUG("Failed to find peer we wanted to drop"); + } } return true; @@ -1987,11 +2003,13 @@ skip: ++n_peers_on_next_stripe; return true; }); + // TODO: investigate tallying by zone and comparing to max out peers by zone + const unsigned int max_out_peers = get_max_out_peers(epee::net_utils::zone::public_); const uint32_t distance = (peer_stripe + (1<<CRYPTONOTE_PRUNING_LOG_STRIPES) - next_stripe) % (1<<CRYPTONOTE_PRUNING_LOG_STRIPES); - if ((n_out_peers >= m_max_out_peers && n_peers_on_next_stripe == 0) || (distance > 1 && n_peers_on_next_stripe <= 2) || distance > 2) + if ((n_out_peers >= max_out_peers && n_peers_on_next_stripe == 0) || (distance > 1 && n_peers_on_next_stripe <= 2) || distance > 2) { MDEBUG(context << "we want seed " << next_stripe << ", and either " << n_out_peers << " is at max out peers (" - << m_max_out_peers << ") or distance " << distance << " from " << next_stripe << " to " << peer_stripe << + << max_out_peers << ") or distance " << distance << " from " << next_stripe << " to " << peer_stripe << " is too large and we have only " << n_peers_on_next_stripe << " peers on next seed, dropping connection to make space"); return true; } @@ -2812,11 +2830,13 @@ skip: } return true; }); - const bool use_next = (n_next > m_max_out_peers / 2 && n_subsequent <= 1) || (n_next > 2 && n_subsequent == 0); + // TODO: investigate tallying by zone and comparing to max out peers by zone + const unsigned int max_out_peers = get_max_out_peers(epee::net_utils::zone::public_); + const bool use_next = (n_next > max_out_peers / 2 && n_subsequent <= 1) || (n_next > 2 && n_subsequent == 0); const uint32_t ret_stripe = use_next ? subsequent_pruning_stripe: next_pruning_stripe; MIDEBUG(const std::string po = get_peers_overview(), "get_next_needed_pruning_stripe: want height " << want_height << " (" << want_height_from_blockchain << " from blockchain, " << want_height_from_block_queue << " from block queue), stripe " << - next_pruning_stripe << " (" << n_next << "/" << m_max_out_peers << " on it and " << n_subsequent << " on " << + next_pruning_stripe << " (" << n_next << "/" << max_out_peers << " on it and " << n_subsequent << " on " << subsequent_pruning_stripe << ", " << n_others << " others) -> " << ret_stripe << " (+" << (ret_stripe - next_pruning_stripe + (1 << CRYPTONOTE_PRUNING_LOG_STRIPES)) % (1 << CRYPTONOTE_PRUNING_LOG_STRIPES) << "), current peers " << po); @@ -2824,7 +2844,7 @@ skip: } //------------------------------------------------------------------------------------------------------------------------ template<class t_core> - bool t_cryptonote_protocol_handler<t_core>::needs_new_sync_connections() const + bool t_cryptonote_protocol_handler<t_core>::needs_new_sync_connections(epee::net_utils::zone zone) const { const uint64_t target = m_core.get_target_blockchain_height(); const uint64_t height = m_core.get_current_blockchain_height(); @@ -2832,11 +2852,11 @@ skip: return false; size_t n_out_peers = 0; m_p2p->for_each_connection([&](cryptonote_connection_context& ctx, nodetool::peerid_type peer_id, uint32_t support_flags)->bool{ - if (!ctx.m_is_income) + if (!ctx.m_is_income && ctx.m_remote_address.get_zone() == zone) ++n_out_peers; return true; }); - if (n_out_peers >= m_max_out_peers) + if (n_out_peers >= get_max_out_peers(zone)) return false; return true; } diff --git a/src/device/device_ledger.cpp b/src/device/device_ledger.cpp index 51e65dfa5..aa73e998c 100644 --- a/src/device/device_ledger.cpp +++ b/src/device/device_ledger.cpp @@ -694,7 +694,8 @@ namespace hw { log_hexbuffer("derive_subaddress_public_key: [[IN]] pub ", pub_x.data, 32); log_hexbuffer("derive_subaddress_public_key: [[IN]] derivation", derivation_x.data, 32); log_message ("derive_subaddress_public_key: [[IN]] index ", std::to_string((int)output_index_x)); - this->controle_device->derive_subaddress_public_key(pub_x, derivation_x,output_index_x,derived_pub_x); + if (!this->controle_device->derive_subaddress_public_key(pub_x, derivation_x,output_index_x,derived_pub_x)) + return false; log_hexbuffer("derive_subaddress_public_key: [[OUT]] derived_pub", derived_pub_x.data, 32); #endif @@ -702,7 +703,8 @@ namespace hw { //If we are in TRANSACTION_PARSE, the given derivation has been retrieved uncrypted (wihtout the help //of the device), so continue that way. MDEBUG( "derive_subaddress_public_key : PARSE mode with known viewkey"); - crypto::derive_subaddress_public_key(pub, derivation, output_index,derived_pub); + if (!crypto::derive_subaddress_public_key(pub, derivation, output_index,derived_pub)) + return false; } else { AUTO_LOCK_CMD(); int offset = set_command_header_noopt(INS_DERIVE_SUBADDRESS_PUBLIC_KEY); @@ -1052,7 +1054,8 @@ namespace hw { crypto::key_derivation derivation_x; log_hexbuffer("generate_key_derivation: [[IN]] pub ", pub_x.data, 32); log_hexbuffer("generate_key_derivation: [[IN]] sec ", sec_x.data, 32); - this->controle_device->generate_key_derivation(pub_x, sec_x, derivation_x); + if (!this->controle_device->generate_key_derivation(pub_x, sec_x, derivation_x)) + return false; log_hexbuffer("generate_key_derivation: [[OUT]] derivation", derivation_x.data, 32); #endif @@ -1207,7 +1210,8 @@ namespace hw { log_hexbuffer("derive_public_key: [[IN]] derivation ", derivation_x.data, 32); log_message ("derive_public_key: [[IN]] output_index", std::to_string(output_index_x)); log_hexbuffer("derive_public_key: [[IN]] pub ", pub_x.data, 32); - this->controle_device->derive_public_key(derivation_x, output_index_x, pub_x, derived_pub_x); + if (!this->controle_device->derive_public_key(derivation_x, output_index_x, pub_x, derived_pub_x)) + return false; log_hexbuffer("derive_public_key: [[OUT]] derived_pub ", derived_pub_x.data, 32); #endif diff --git a/src/device_trezor/device_trezor.cpp b/src/device_trezor/device_trezor.cpp index 6f7ae9a6b..58c36f2c9 100644 --- a/src/device_trezor/device_trezor.cpp +++ b/src/device_trezor/device_trezor.cpp @@ -324,8 +324,8 @@ namespace trezor { std::vector<protocol::ki::MoneroTransferDetails> mtds; std::vector<protocol::ki::MoneroExportedKeyImage> kis; - protocol::ki::key_image_data(wallet, transfers, mtds, client_version() <= 1); - protocol::ki::generate_commitment(mtds, transfers, req, client_version() <= 1); + protocol::ki::key_image_data(wallet, transfers, mtds); + protocol::ki::generate_commitment(mtds, transfers, req); EVENT_PROGRESS(0.); this->set_msg_addr<messages::monero::MoneroKeyImageExportInitRequest>(req.get()); @@ -635,11 +635,7 @@ namespace trezor { } // Step: sort - auto perm_req = signer->step_permutation(); - if (perm_req){ - auto perm_ack = this->client_exchange<messages::monero::MoneroTransactionInputsPermutationAck>(perm_req); - signer->step_permutation_ack(perm_ack); - } + signer->sort_ki(); EVENT_PROGRESS(3, 1, 1); // Step: input_vini @@ -697,13 +693,13 @@ namespace trezor { unsigned device_trezor::client_version() { auto trezor_version = get_version(); - if (trezor_version <= pack_version(2, 0, 10)){ - throw exc::TrezorException("Trezor firmware 2.0.10 and lower are not supported. Please update."); + if (trezor_version < pack_version(2, 4, 3)){ + throw exc::TrezorException("Minimal Trezor firmware version is 2.4.3. Please update."); } - unsigned client_version = 1; - if (trezor_version >= pack_version(2, 3, 1)){ - client_version = 3; + unsigned client_version = 3; + if (trezor_version >= pack_version(2, 5, 2)){ + client_version = 4; } #ifdef WITH_TREZOR_DEBUGGING @@ -739,14 +735,6 @@ namespace trezor { CHECK_AND_ASSERT_THROW_MES(init_msg, "TransactionInitRequest is empty"); CHECK_AND_ASSERT_THROW_MES(init_msg->has_tsx_data(), "TransactionInitRequest has no transaction data"); CHECK_AND_ASSERT_THROW_MES(m_features, "Device state not initialized"); // make sure the caller did not reset features - const bool nonce_required = init_msg->tsx_data().has_payment_id() && init_msg->tsx_data().payment_id().size() > 0; - - if (nonce_required && init_msg->tsx_data().payment_id().size() == 8){ - // Versions 2.0.9 and lower do not support payment ID - if (get_version() <= pack_version(2, 0, 9)) { - throw exc::TrezorException("Trezor firmware 2.0.9 and lower does not support transactions with short payment IDs or integrated addresses. Please update."); - } - } } void device_trezor::transaction_check(const protocol::tx::TData & tdata, const hw::tx_aux_data & aux_data) diff --git a/src/device_trezor/trezor/protocol.cpp b/src/device_trezor/trezor/protocol.cpp index a400e82c7..0e59a16ba 100644 --- a/src/device_trezor/trezor/protocol.cpp +++ b/src/device_trezor/trezor/protocol.cpp @@ -38,6 +38,7 @@ #include <crypto/hmac-keccak.h> #include <ringct/rctSigs.h> #include <ringct/bulletproofs.h> +#include <ringct/bulletproofs_plus.h> #include "cryptonote_config.h" #include <sodium.h> #include <sodium/crypto_verify_32.h> @@ -145,8 +146,7 @@ namespace ki { bool key_image_data(wallet_shim * wallet, const std::vector<tools::wallet2::transfer_details> & transfers, - std::vector<MoneroTransferDetails> & res, - bool need_all_additionals) + std::vector<MoneroTransferDetails> & res) { for(auto & td : transfers){ ::crypto::public_key tx_pub_key = wallet->get_tx_pub_key_from_received_outs(td); @@ -159,11 +159,7 @@ namespace ki { cres.set_internal_output_index(td.m_internal_output_index); cres.set_sub_addr_major(td.m_subaddr_index.major); cres.set_sub_addr_minor(td.m_subaddr_index.minor); - if (need_all_additionals) { - for (auto &aux : additional_tx_pub_keys) { - cres.add_additional_tx_pub_keys(key_to_string(aux)); - } - } else if (!additional_tx_pub_keys.empty() && additional_tx_pub_keys.size() > td.m_internal_output_index) { + if (!additional_tx_pub_keys.empty() && additional_tx_pub_keys.size() > td.m_internal_output_index) { cres.add_additional_tx_pub_keys(key_to_string(additional_tx_pub_keys[td.m_internal_output_index])); } } @@ -194,8 +190,7 @@ namespace ki { void generate_commitment(std::vector<MoneroTransferDetails> & mtds, const std::vector<tools::wallet2::transfer_details> & transfers, - std::shared_ptr<messages::monero::MoneroKeyImageExportInitRequest> & req, - bool need_subaddr_indices) + std::shared_ptr<messages::monero::MoneroKeyImageExportInitRequest> & req) { req = std::make_shared<messages::monero::MoneroKeyImageExportInitRequest>(); @@ -219,16 +214,6 @@ namespace ki { auto & st = search.first->second; st.insert(cur.m_subaddr_index.minor); } - - if (need_subaddr_indices) { - for (auto &x: sub_indices) { - auto subs = req->add_subs(); - subs->set_account(x.first); - for (auto minor : x.second) { - subs->add_minor_indices(minor); - } - } - } } void live_refresh_ack(const ::crypto::secret_key & view_key_priv, @@ -399,7 +384,7 @@ namespace tx { m_tx_idx = tx_idx; m_ct.tx_data = cur_src_tx(); m_multisig = false; - m_client_version = 1; + m_client_version = 3; } void Signer::extract_payment_id(){ @@ -474,25 +459,19 @@ namespace tx { auto & cur = src.outputs[i]; auto out = dst->add_outputs(); - if (i == src.real_output || need_ring_indices || client_version() <= 1) { + if (i == src.real_output || need_ring_indices) { out->set_idx(cur.first); } - if (i == src.real_output || need_ring_keys || client_version() <= 1) { + if (i == src.real_output || need_ring_keys) { translate_rct_key(out->mutable_key(), &(cur.second)); } } dst->set_real_out_tx_key(key_to_string(src.real_out_tx_key)); dst->set_real_output_in_tx_index(src.real_output_in_tx_index); - - if (client_version() <= 1) { - for (auto &cur : src.real_out_additional_tx_keys) { - dst->add_real_out_additional_tx_keys(key_to_string(cur)); - } - } else if (!src.real_out_additional_tx_keys.empty()) { + if (!src.real_out_additional_tx_keys.empty()) { dst->add_real_out_additional_tx_keys(key_to_string(src.real_out_additional_tx_keys.at(src.real_output_in_tx_index))); } - dst->set_amount(src.amount); dst->set_rct(src.rct); dst->set_mask(key_to_string(src.mask)); @@ -532,7 +511,7 @@ namespace tx { m_ct.tx.version = 2; m_ct.tx.unlock_time = tx.unlock_time; - m_client_version = (m_aux_data->client_version ? m_aux_data->client_version.get() : 1); + m_client_version = (m_aux_data->client_version ? m_aux_data->client_version.get() : 3); tsx_data.set_version(1); tsx_data.set_client_version(client_version()); @@ -543,18 +522,13 @@ namespace tx { tsx_data.set_monero_version(std::string(MONERO_VERSION) + "|" + MONERO_VERSION_TAG); tsx_data.set_hard_fork(m_aux_data->hard_fork ? m_aux_data->hard_fork.get() : 0); - if (client_version() <= 1){ - assign_to_repeatable(tsx_data.mutable_minor_indices(), tx.subaddr_indices.begin(), tx.subaddr_indices.end()); - } - // Rsig decision auto rsig_data = tsx_data.mutable_rsig_data(); m_ct.rsig_type = get_rsig_type(tx.rct_config, tx.splitted_dsts.size()); rsig_data->set_rsig_type(m_ct.rsig_type); - if (tx.rct_config.range_proof_type != rct::RangeProofBorromean){ - m_ct.bp_version = (m_aux_data->bp_version ? m_aux_data->bp_version.get() : 1); - rsig_data->set_bp_version((uint32_t) m_ct.bp_version); - } + CHECK_AND_ASSERT_THROW_MES(tx.rct_config.range_proof_type != rct::RangeProofBorromean, "Borromean rsig not supported"); + m_ct.bp_version = (m_aux_data->bp_version ? m_aux_data->bp_version.get() : 1); + rsig_data->set_bp_version((uint32_t) m_ct.bp_version); generate_rsig_batch_sizes(m_ct.grouping_vct, m_ct.rsig_type, tx.splitted_dsts.size()); assign_to_repeatable(rsig_data->mutable_grouping(), m_ct.grouping_vct.begin(), m_ct.grouping_vct.end()); @@ -652,22 +626,6 @@ namespace tx { }); } - std::shared_ptr<messages::monero::MoneroTransactionInputsPermutationRequest> Signer::step_permutation(){ - sort_ki(); - if (client_version() >= 2){ - return nullptr; - } - - auto res = std::make_shared<messages::monero::MoneroTransactionInputsPermutationRequest>(); - assign_to_repeatable(res->mutable_perm(), m_ct.source_permutation.begin(), m_ct.source_permutation.end()); - - return res; - } - - void Signer::step_permutation_ack(std::shared_ptr<const messages::monero::MoneroTransactionInputsPermutationAck> ack){ - - } - std::shared_ptr<messages::monero::MoneroTransactionInputViniRequest> Signer::step_set_vini_input(size_t idx){ CHECK_AND_ASSERT_THROW_MES(idx < m_ct.tx_data.sources.size(), "Invalid transaction index"); CHECK_AND_ASSERT_THROW_MES(idx < m_ct.tx.vin.size(), "Invalid transaction index"); @@ -711,8 +669,10 @@ namespace tx { } void Signer::step_set_output_ack(std::shared_ptr<const messages::monero::MoneroTransactionSetOutputAck> ack){ + CHECK_AND_ASSERT_THROW_MES(is_req_bulletproof(), "Borromean rsig not supported"); cryptonote::tx_out tx_out; rct::Bulletproof bproof{}; + rct::BulletproofPlus bproof_plus{}; rct::ctkey out_pk{}; rct::ecdhTuple ecdh{}; @@ -727,7 +687,7 @@ namespace tx { rsig_buff = rsig_data.rsig(); } - if (client_version() >= 1 && rsig_data.has_mask()){ + if (rsig_data.has_mask()){ rct::key cmask{}; string_to_key(cmask, rsig_data.mask()); m_ct.rsig_gamma.emplace_back(cmask); @@ -751,22 +711,32 @@ namespace tx { memcpy(ecdh.amount.bytes, ack->ecdh_info().data(), 8); } - if (has_rsig && is_req_bulletproof() && !cn_deserialize(rsig_buff, bproof)){ - throw exc::ProtocolException("Cannot deserialize bulletproof rangesig"); - } - m_ct.tx.vout.emplace_back(tx_out); m_ct.tx_out_hmacs.push_back(ack->vouti_hmac()); m_ct.tx_out_pk.emplace_back(out_pk); m_ct.tx_out_ecdh.emplace_back(ecdh); - // ClientV0, if no rsig was generated on Trezor, do not continue. - // ClientV1+ generates BP after all masks in the current batch are generated - if (!has_rsig || (client_version() >= 1 && is_offloading())){ + rsig_v bp_obj{}; + if (has_rsig) { + bool deserialize_success; + if (is_req_bulletproof_plus()) { + deserialize_success = cn_deserialize(rsig_buff, bproof_plus); + bp_obj = bproof_plus; + } else { + deserialize_success = cn_deserialize(rsig_buff, bproof); + bp_obj = bproof; + } + if (!deserialize_success) { + throw exc::ProtocolException("Cannot deserialize bulletproof rangesig"); + } + } + + // Generates BP after all masks in the current batch are generated + if (!has_rsig || is_offloading()){ return; } - process_bproof(bproof); + process_bproof(bp_obj); m_ct.cur_batch_idx += 1; m_ct.cur_output_in_batch_idx = 0; } @@ -791,13 +761,21 @@ namespace tx { masks.push_back(m_ct.rsig_gamma[bidx]); } - auto bp = bulletproof_PROVE(amounts, masks); - auto serRsig = cn_serialize(bp); - m_ct.tx_out_rsigs.emplace_back(bp); + std::string serRsig; + if (is_req_bulletproof_plus()) { + auto bp = bulletproof_plus_PROVE(amounts, masks); + serRsig = cn_serialize(bp); + m_ct.tx_out_rsigs.emplace_back(bp); + } else { + auto bp = bulletproof_PROVE(amounts, masks); + serRsig = cn_serialize(bp); + m_ct.tx_out_rsigs.emplace_back(bp); + } + rsig_data.set_rsig(serRsig); } - void Signer::process_bproof(rct::Bulletproof & bproof){ + void Signer::process_bproof(rsig_v & bproof){ CHECK_AND_ASSERT_THROW_MES(m_ct.cur_batch_idx < m_ct.grouping_vct.size(), "Invalid batch index"); auto batch_size = m_ct.grouping_vct[m_ct.cur_batch_idx]; for (size_t i = 0; i < batch_size; ++i){ @@ -806,12 +784,22 @@ namespace tx { rct::key commitment = m_ct.tx_out_pk[bidx].mask; commitment = rct::scalarmultKey(commitment, rct::INV_EIGHT); - bproof.V.push_back(commitment); + if (is_req_bulletproof_plus()) { + boost::get<rct::BulletproofPlus>(bproof).V.push_back(commitment); + } else { + boost::get<rct::Bulletproof>(bproof).V.push_back(commitment); + } } m_ct.tx_out_rsigs.emplace_back(bproof); - if (!rct::bulletproof_VERIFY(boost::get<rct::Bulletproof>(m_ct.tx_out_rsigs.back()))) { - throw exc::ProtocolException("Returned range signature is invalid"); + if (is_req_bulletproof_plus()) { + if (!rct::bulletproof_plus_VERIFY(boost::get<rct::BulletproofPlus>(m_ct.tx_out_rsigs.back()))) { + throw exc::ProtocolException("Returned range signature is invalid"); + } + } else { + if (!rct::bulletproof_VERIFY(boost::get<rct::Bulletproof>(m_ct.tx_out_rsigs.back()))) { + throw exc::ProtocolException("Returned range signature is invalid"); + } } } @@ -840,6 +828,7 @@ namespace tx { } void Signer::step_all_outs_set_ack(std::shared_ptr<const messages::monero::MoneroTransactionAllOutSetAck> ack, hw::device &hwdev){ + CHECK_AND_ASSERT_THROW_MES(is_req_bulletproof(), "Borromean rsig not supported"); m_ct.rv = std::make_shared<rct::rctSig>(); m_ct.rv->txnFee = ack->rv().txn_fee(); m_ct.rv->type = static_cast<uint8_t>(ack->rv().rv_type()); @@ -864,24 +853,15 @@ namespace tx { // RctSig auto num_sources = m_ct.tx_data.sources.size(); - if (is_simple() || is_req_bulletproof()){ - auto dst = &m_ct.rv->pseudoOuts; - if (is_bulletproof()){ - dst = &m_ct.rv->p.pseudoOuts; - } - - dst->clear(); - for (const auto &pseudo_out : m_ct.pseudo_outs) { - dst->emplace_back(); - string_to_key(dst->back(), pseudo_out); - } - - m_ct.rv->mixRing.resize(num_sources); - } else { - m_ct.rv->mixRing.resize(m_ct.tsx_data.mixin()); - m_ct.rv->mixRing[0].resize(num_sources); + auto dst = &m_ct.rv->p.pseudoOuts; + dst->clear(); + for (const auto &pseudo_out : m_ct.pseudo_outs) { + dst->emplace_back(); + string_to_key(dst->back(), pseudo_out); } + m_ct.rv->mixRing.resize(num_sources); + CHECK_AND_ASSERT_THROW_MES(m_ct.tx_out_pk.size() == m_ct.tx_out_ecdh.size(), "Invalid vector sizes"); for(size_t i = 0; i < m_ct.tx_out_ecdh.size(); ++i){ m_ct.rv->outPk.push_back(m_ct.tx_out_pk[i]); @@ -889,10 +869,10 @@ namespace tx { } for(size_t i = 0; i < m_ct.tx_out_rsigs.size(); ++i){ - if (is_bulletproof()){ - m_ct.rv->p.bulletproofs.push_back(boost::get<rct::Bulletproof>(m_ct.tx_out_rsigs[i])); + if (is_req_bulletproof_plus()) { + m_ct.rv->p.bulletproofs_plus.push_back(boost::get<rct::BulletproofPlus>(m_ct.tx_out_rsigs[i])); } else { - m_ct.rv->p.rangeSigs.push_back(boost::get<rct::rangeSig>(m_ct.tx_out_rsigs[i])); + m_ct.rv->p.bulletproofs.push_back(boost::get<rct::Bulletproof>(m_ct.tx_out_rsigs[i])); } } @@ -936,8 +916,8 @@ namespace tx { void Signer::step_sign_input_ack(std::shared_ptr<const messages::monero::MoneroTransactionSignInputAck> ack){ m_ct.signatures.push_back(ack->signature()); - // Sync updated pseudo_outputs, client_version>=1, HF10+ - if (client_version() >= 1 && ack->has_pseudo_out()){ + // Sync updated pseudo_outputs + if (ack->has_pseudo_out()){ CHECK_AND_ASSERT_THROW_MES(m_ct.cur_input_idx < m_ct.pseudo_outs.size(), "Invalid pseudo-out index"); m_ct.pseudo_outs[m_ct.cur_input_idx] = ack->pseudo_out(); if (is_bulletproof()){ @@ -955,6 +935,8 @@ namespace tx { } void Signer::step_final_ack(std::shared_ptr<const messages::monero::MoneroTransactionFinalAck> ack){ + CHECK_AND_ASSERT_THROW_MES(is_clsag(), "Only CLSAGs signatures are supported"); + if (m_multisig){ auto & cout_key = ack->cout_key(); for(auto & cur : m_ct.couts){ @@ -975,47 +957,34 @@ namespace tx { m_ct.enc_keys = ack->tx_enc_keys(); // Opening the sealed signatures - if (client_version() >= 3){ - if(!ack->has_opening_key()){ - throw exc::ProtocolException("Client version 3+ requires sealed signatures"); - } + if(!ack->has_opening_key()){ + throw exc::ProtocolException("Client version 3+ requires sealed signatures"); + } - for(size_t i = 0; i < m_ct.signatures.size(); ++i){ - CHECK_AND_ASSERT_THROW_MES(m_ct.signatures[i].size() > crypto::chacha::TAG_SIZE, "Invalid signature size"); - std::string nonce = compute_sealing_key(ack->opening_key(), i, true); - std::string key = compute_sealing_key(ack->opening_key(), i, false); - size_t plen = m_ct.signatures[i].size() - crypto::chacha::TAG_SIZE; - std::unique_ptr<uint8_t[]> plaintext(new uint8_t[plen]); - uint8_t * buff = plaintext.get(); - - protocol::crypto::chacha::decrypt( - m_ct.signatures[i].data(), - m_ct.signatures[i].size(), - reinterpret_cast<const uint8_t *>(key.data()), - reinterpret_cast<const uint8_t *>(nonce.data()), - reinterpret_cast<char *>(buff), &plen); - m_ct.signatures[i].assign(reinterpret_cast<const char *>(buff), plen); - } + for(size_t i = 0; i < m_ct.signatures.size(); ++i){ + CHECK_AND_ASSERT_THROW_MES(m_ct.signatures[i].size() > crypto::chacha::TAG_SIZE, "Invalid signature size"); + std::string nonce = compute_sealing_key(ack->opening_key(), i, true); + std::string key = compute_sealing_key(ack->opening_key(), i, false); + size_t plen = m_ct.signatures[i].size() - crypto::chacha::TAG_SIZE; + std::unique_ptr<uint8_t[]> plaintext(new uint8_t[plen]); + uint8_t * buff = plaintext.get(); + + protocol::crypto::chacha::decrypt( + m_ct.signatures[i].data(), + m_ct.signatures[i].size(), + reinterpret_cast<const uint8_t *>(key.data()), + reinterpret_cast<const uint8_t *>(nonce.data()), + reinterpret_cast<char *>(buff), &plen); + m_ct.signatures[i].assign(reinterpret_cast<const char *>(buff), plen); } - if (m_ct.rv->type == rct::RCTTypeCLSAG){ - m_ct.rv->p.CLSAGs.reserve(m_ct.signatures.size()); - for (size_t i = 0; i < m_ct.signatures.size(); ++i) { - rct::clsag clsag; - if (!cn_deserialize(m_ct.signatures[i], clsag)) { - throw exc::ProtocolException("Cannot deserialize clsag[i]"); - } - m_ct.rv->p.CLSAGs.push_back(clsag); - } - } else { - m_ct.rv->p.MGs.reserve(m_ct.signatures.size()); - for (size_t i = 0; i < m_ct.signatures.size(); ++i) { - rct::mgSig mg; - if (!cn_deserialize(m_ct.signatures[i], mg)) { - throw exc::ProtocolException("Cannot deserialize mg[i]"); - } - m_ct.rv->p.MGs.push_back(mg); + m_ct.rv->p.CLSAGs.reserve(m_ct.signatures.size()); + for (size_t i = 0; i < m_ct.signatures.size(); ++i) { + rct::clsag clsag; + if (!cn_deserialize(m_ct.signatures[i], clsag)) { + throw exc::ProtocolException("Cannot deserialize clsag[i]"); } + m_ct.rv->p.CLSAGs.push_back(clsag); } m_ct.tx.rct_signatures = *(m_ct.rv); diff --git a/src/device_trezor/trezor/protocol.hpp b/src/device_trezor/trezor/protocol.hpp index 858db1520..fa8355200 100644 --- a/src/device_trezor/trezor/protocol.hpp +++ b/src/device_trezor/trezor/protocol.hpp @@ -116,8 +116,7 @@ namespace ki { */ bool key_image_data(wallet_shim * wallet, const std::vector<tools::wallet2::transfer_details> & transfers, - std::vector<MoneroTransferDetails> & res, - bool need_all_additionals=false); + std::vector<MoneroTransferDetails> & res); /** * Computes a hash over MoneroTransferDetails. Commitment used in the KI sync. @@ -129,8 +128,7 @@ namespace ki { */ void generate_commitment(std::vector<MoneroTransferDetails> & mtds, const std::vector<tools::wallet2::transfer_details> & transfers, - std::shared_ptr<messages::monero::MoneroKeyImageExportInitRequest> & req, - bool need_subaddr_indices=false); + std::shared_ptr<messages::monero::MoneroKeyImageExportInitRequest> & req); /** * Processes Live refresh step response, parses KI, checks the signature @@ -166,7 +164,7 @@ namespace tx { ::crypto::secret_key compute_enc_key(const ::crypto::secret_key & private_view_key, const std::string & aux, const std::string & salt); std::string compute_sealing_key(const std::string & master_key, size_t idx, bool is_iv=false); - typedef boost::variant<rct::rangeSig, rct::Bulletproof> rsig_v; + typedef boost::variant<rct::Bulletproof, rct::BulletproofPlus> rsig_v; /** * Transaction signer state holder. @@ -247,7 +245,7 @@ namespace tx { void compute_integrated_indices(TsxData * tsx_data); bool should_compute_bp_now() const; void compute_bproof(messages::monero::MoneroTransactionRsigData & rsig_data); - void process_bproof(rct::Bulletproof & bproof); + void process_bproof(rsig_v & bproof); void set_tx_input(MoneroTransactionSourceEntry * dst, size_t idx, bool need_ring_keys=false, bool need_ring_indices=false); public: @@ -260,8 +258,6 @@ namespace tx { void step_set_input_ack(std::shared_ptr<const messages::monero::MoneroTransactionSetInputAck> ack); void sort_ki(); - std::shared_ptr<messages::monero::MoneroTransactionInputsPermutationRequest> step_permutation(); - void step_permutation_ack(std::shared_ptr<const messages::monero::MoneroTransactionInputsPermutationAck> ack); std::shared_ptr<messages::monero::MoneroTransactionInputViniRequest> step_set_vini_input(size_t idx); void step_set_vini_input_ack(std::shared_ptr<const messages::monero::MoneroTransactionInputViniAck> ack); @@ -290,11 +286,15 @@ namespace tx { return m_client_version; } - bool is_simple() const { + uint8_t get_rv_type() const { if (!m_ct.rv){ throw std::invalid_argument("RV not initialized"); } - auto tp = m_ct.rv->type; + return m_ct.rv->type; + } + + bool is_simple() const { + auto tp = get_rv_type(); return tp == rct::RCTTypeSimple; } @@ -302,12 +302,27 @@ namespace tx { return m_ct.tx_data.rct_config.range_proof_type != rct::RangeProofBorromean; } + bool is_req_clsag() const { + return is_req_bulletproof() && m_ct.tx_data.rct_config.bp_version >= 3; + } + + bool is_req_bulletproof_plus() const { + return is_req_bulletproof() && m_ct.tx_data.rct_config.bp_version == 4; // rct::genRctSimple + } + bool is_bulletproof() const { - if (!m_ct.rv){ - throw std::invalid_argument("RV not initialized"); - } - auto tp = m_ct.rv->type; - return tp == rct::RCTTypeBulletproof || tp == rct::RCTTypeBulletproof2 || tp == rct::RCTTypeCLSAG; + auto tp = get_rv_type(); + return rct::is_rct_bulletproof(tp) || rct::is_rct_bulletproof_plus(tp); + } + + bool is_bulletproof_plus() const { + auto tp = get_rv_type(); + return rct::is_rct_bulletproof_plus(tp); + } + + bool is_clsag() const { + auto tp = get_rv_type(); + return rct::is_rct_clsag(tp); } bool is_offloading() const { diff --git a/src/hardforks/hardforks.cpp b/src/hardforks/hardforks.cpp index 0dd0cf5f0..336ef6519 100644 --- a/src/hardforks/hardforks.cpp +++ b/src/hardforks/hardforks.cpp @@ -71,8 +71,8 @@ const hardfork_t mainnet_hard_forks[] = { { 13, 2210000, 0, 1598180817 }, { 14, 2210720, 0, 1598180818 }, - { 15, 8000000, 0, 1608223241 }, // temp so tests test with these consensus rules - { 16, 8000001, 0, 1608223242 }, // temp so tests test with these consensus rules + { 15, 2688888, 0, 1656629117 }, + { 16, 2689608, 0, 1656629118 }, }; const size_t num_mainnet_hard_forks = sizeof(mainnet_hard_forks) / sizeof(mainnet_hard_forks[0]); const uint64_t mainnet_hard_fork_version_1_till = 1009826; @@ -122,5 +122,7 @@ const hardfork_t stagenet_hard_forks[] = { { 12, 454721, 0, 1571419280 }, { 13, 675405, 0, 1598180817 }, { 14, 676125, 0, 1598180818 }, + { 15, 1151000, 0, 1656629117 }, + { 16, 1151720, 0, 1656629118 }, }; const size_t num_stagenet_hard_forks = sizeof(stagenet_hard_forks) / sizeof(stagenet_hard_forks[0]); diff --git a/src/multisig/CMakeLists.txt b/src/multisig/CMakeLists.txt index 294a1721f..61e658a39 100644 --- a/src/multisig/CMakeLists.txt +++ b/src/multisig/CMakeLists.txt @@ -30,7 +30,9 @@ set(multisig_sources multisig.cpp multisig_account.cpp multisig_account_kex_impl.cpp - multisig_kex_msg.cpp) + multisig_clsag_context.cpp + multisig_kex_msg.cpp + multisig_tx_builder_ringct.cpp) set(multisig_headers) @@ -48,6 +50,7 @@ target_link_libraries(multisig PUBLIC ringct cryptonote_basic + cryptonote_core common cncrypto PRIVATE diff --git a/src/multisig/multisig_clsag_context.cpp b/src/multisig/multisig_clsag_context.cpp new file mode 100644 index 000000000..e3417b896 --- /dev/null +++ b/src/multisig/multisig_clsag_context.cpp @@ -0,0 +1,257 @@ +// Copyright (c) 2021, 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 "multisig_clsag_context.h" + +#include "int-util.h" + +#include "crypto/crypto.h" +#include "cryptonote_config.h" +#include "ringct/rctOps.h" +#include "ringct/rctTypes.h" + +#include <cstring> +#include <string> +#include <vector> + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "multisig" + +namespace multisig { + +namespace signing { +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +template<std::size_t N> +static rct::key string_to_key(const unsigned char (&str)[N]) { + rct::key tmp{}; + static_assert(sizeof(tmp.bytes) >= N, ""); + std::memcpy(tmp.bytes, str, N); + return tmp; +} +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +static void encode_int_to_key_le(const unsigned int i, rct::key &k_out) +{ + static_assert(sizeof(unsigned int) <= sizeof(std::uint64_t), "unsigned int max too large"); + static_assert(sizeof(std::uint64_t) <= sizeof(rct::key), ""); + std::uint64_t temp_i{SWAP64LE(i)}; + std::memcpy(k_out.bytes, &temp_i, sizeof(temp_i)); +} +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +bool CLSAG_context_t::init( + const rct::keyV& P, + const rct::keyV& C_nonzero, + const rct::key& C_offset, + const rct::key& message, + const rct::key& I, + const rct::key& D, + const unsigned int l, + const rct::keyV& s, + const std::size_t num_alpha_components +) +{ + initialized = false; + + n = P.size(); + if (n <= 0) + return false; + if (C_nonzero.size() != n) + return false; + if (s.size() != n) + return false; + if (l >= n) + return false; + + c_params.clear(); + c_params.reserve(n * 2 + 5); + b_params.clear(); + b_params.reserve(n * 3 + 2 * num_alpha_components + 7); + + c_params.push_back(string_to_key(config::HASH_KEY_CLSAG_ROUND)); + b_params.push_back(string_to_key(config::HASH_KEY_CLSAG_ROUND_MULTISIG)); + c_params.insert(c_params.end(), P.begin(), P.end()); + b_params.insert(b_params.end(), P.begin(), P.end()); + c_params.insert(c_params.end(), C_nonzero.begin(), C_nonzero.end()); + b_params.insert(b_params.end(), C_nonzero.begin(), C_nonzero.end()); + c_params.emplace_back(C_offset); + b_params.emplace_back(C_offset); + c_params.emplace_back(message); + b_params.emplace_back(message); + c_params_L_offset = c_params.size(); + b_params_L_offset = b_params.size(); + c_params.resize(c_params.size() + 1); //this is where L will be inserted later + b_params.resize(b_params.size() + num_alpha_components); //multisig aggregate public nonces for L will be inserted here later + c_params_R_offset = c_params.size(); + b_params_R_offset = b_params.size(); + c_params.resize(c_params.size() + 1); //this is where R will be inserted later + b_params.resize(b_params.size() + num_alpha_components); //multisig aggregate public nonces for R will be inserted here later + b_params.emplace_back(I); + b_params.emplace_back(D); + b_params.insert(b_params.end(), s.begin(), s.begin() + l); //fake responses before 'l' + b_params.insert(b_params.end(), s.begin() + l + 1, s.end()); //fake responses after 'l' + b_params.emplace_back(); + encode_int_to_key_le(l, b_params.back()); //real signing index 'l' + b_params.emplace_back(); + encode_int_to_key_le(num_alpha_components, b_params.back()); //number of parallel nonces + b_params.emplace_back(); + encode_int_to_key_le(n, b_params.back()); //number of ring members + + rct::keyV mu_P_params; + rct::keyV mu_C_params; + mu_P_params.reserve(n * 2 + 4); + mu_C_params.reserve(n * 2 + 4); + + mu_P_params.push_back(string_to_key(config::HASH_KEY_CLSAG_AGG_0)); + mu_C_params.push_back(string_to_key(config::HASH_KEY_CLSAG_AGG_1)); + mu_P_params.insert(mu_P_params.end(), P.begin(), P.end()); + mu_C_params.insert(mu_C_params.end(), P.begin(), P.end()); + mu_P_params.insert(mu_P_params.end(), C_nonzero.begin(), C_nonzero.end()); + mu_C_params.insert(mu_C_params.end(), C_nonzero.begin(), C_nonzero.end()); + mu_P_params.emplace_back(I); + mu_C_params.emplace_back(I); + mu_P_params.emplace_back(scalarmultKey(D, rct::INV_EIGHT)); + mu_C_params.emplace_back(mu_P_params.back()); + mu_P_params.emplace_back(C_offset); + mu_C_params.emplace_back(C_offset); + mu_P = hash_to_scalar(mu_P_params); + mu_C = hash_to_scalar(mu_C_params); + + rct::geDsmp I_precomp; + rct::geDsmp D_precomp; + rct::precomp(I_precomp.k, I); + rct::precomp(D_precomp.k, D); + rct::key wH_l; + rct::addKeys3(wH_l, mu_P, I_precomp.k, mu_C, D_precomp.k); + rct::precomp(wH_l_precomp.k, wH_l); + W_precomp.resize(n); + H_precomp.resize(n); + for (std::size_t i = 0; i < n; ++i) { + rct::geDsmp P_precomp; + rct::geDsmp C_precomp; + rct::key C; + rct::subKeys(C, C_nonzero[i], C_offset); + rct::precomp(P_precomp.k, P[i]); + rct::precomp(C_precomp.k, C); + rct::key W; + rct::addKeys3(W, mu_P, P_precomp.k, mu_C, C_precomp.k); + rct::precomp(W_precomp[i].k, W); + ge_p3 Hi_p3; + rct::hash_to_p3(Hi_p3, P[i]); + ge_dsm_precomp(H_precomp[i].k, &Hi_p3); + } + rct::precomp(G_precomp.k, rct::G); + this->l = l; + this->s = s; + this->num_alpha_components = num_alpha_components; + + initialized = true; + return true; +} +//---------------------------------------------------------------------------------------------------------------------- +bool CLSAG_context_t::combine_alpha_and_compute_challenge( + const rct::keyV& total_alpha_G, + const rct::keyV& total_alpha_H, + const rct::keyV& alpha, + rct::key& alpha_combined, + rct::key& c_0, + rct::key& c +) +{ + if (not initialized) + return false; + + if (num_alpha_components != total_alpha_G.size()) + return false; + if (num_alpha_components != total_alpha_H.size()) + return false; + if (num_alpha_components != alpha.size()) + return false; + + // insert aggregate public nonces for L and R components + for (std::size_t i = 0; i < num_alpha_components; ++i) { + b_params[b_params_L_offset + i] = total_alpha_G[i]; + b_params[b_params_R_offset + i] = total_alpha_H[i]; + } + + // musig2-style combination factor 'b' + const rct::key b = rct::hash_to_scalar(b_params); + + // 1) store combined public nonces in the 'L' and 'R' slots for computing the initial challenge + // - L = sum_i(b^i total_alpha_G[i]) + // - R = sum_i(b^i total_alpha_H[i]) + // 2) compute the local signer's combined private nonce + // - alpha_combined = sum_i(b^i * alpha[i]) + rct::key& L_l = c_params[c_params_L_offset]; + rct::key& R_l = c_params[c_params_R_offset]; + rct::key b_i = rct::identity(); + L_l = rct::identity(); + R_l = rct::identity(); + alpha_combined = rct::zero(); + for (std::size_t i = 0; i < num_alpha_components; ++i) { + rct::addKeys(L_l, L_l, rct::scalarmultKey(total_alpha_G[i], b_i)); + rct::addKeys(R_l, R_l, rct::scalarmultKey(total_alpha_H[i], b_i)); + sc_muladd(alpha_combined.bytes, alpha[i].bytes, b_i.bytes, alpha_combined.bytes); + sc_mul(b_i.bytes, b_i.bytes, b.bytes); + } + + // compute initial challenge from real spend components + c = rct::hash_to_scalar(c_params); + + // 1) c_0: find the CLSAG's challenge for index '0', which will be stored in the proof + // note: in the CLSAG implementation in ringct/rctSigs, c_0 is denoted 'c1' (a notation error) + // 2) c: find the final challenge for the multisig signers to respond to + for (std::size_t i = (l + 1) % n; i != l; i = (i + 1) % n) { + if (i == 0) + c_0 = c; + rct::addKeys3(c_params[c_params_L_offset], s[i], G_precomp.k, c, W_precomp[i].k); + rct::addKeys3(c_params[c_params_R_offset], s[i], H_precomp[i].k, c, wH_l_precomp.k); + c = rct::hash_to_scalar(c_params); + } + if (l == 0) + c_0 = c; + + return true; +} +//---------------------------------------------------------------------------------------------------------------------- +bool CLSAG_context_t::get_mu( + rct::key& mu_P, + rct::key& mu_C +) const +{ + if (not initialized) + return false; + mu_P = this->mu_P; + mu_C = this->mu_C; + return true; +} +//---------------------------------------------------------------------------------------------------------------------- +} //namespace signing + +} //namespace multisig diff --git a/src/multisig/multisig_clsag_context.h b/src/multisig/multisig_clsag_context.h new file mode 100644 index 000000000..5017e8688 --- /dev/null +++ b/src/multisig/multisig_clsag_context.h @@ -0,0 +1,137 @@ +// Copyright (c) 2021, 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. + +//// +// References +// - CLSAG (base signature scheme): https://eprint.iacr.org/2019/654 +// - MuSig2 (style for multisig signing): https://eprint.iacr.org/2020/1261 +/// + + +#pragma once + +#include "ringct/rctTypes.h" + +#include <vector> + + +namespace multisig { + +namespace signing { + +class CLSAG_context_t final { +private: + // is the CLSAG context initialized? + bool initialized; + // challenge components: c = H(domain-separator, {P}, {C}, C_offset, message, L, R) + rct::keyV c_params; + // indices in c_params where L and R will be + std::size_t c_params_L_offset; + std::size_t c_params_R_offset; + // musig2-style nonce combination factor components for multisig signing + // b = H(domain-separator, {P}, {C}, C_offset, message, {L_combined_alphas}, {R_combined_alphas}, I, D, {s_non_l}, l, k, n) + // - {P} = ring of one-time addresses + // - {C} = ring of amount commitments (1:1 with one-time addresses) + // - C_offset = pseudo-output commitment to offset all amount commitments with + // - message = message the CLSAG will sign + // - {L_combined_alphas} = set of summed-together public nonces from all multisig signers for this CLSAG's L component + // - {R_combined_alphas} = set of summed-together public nonces from all multisig signers for this CLSAG's R component + // - I = key image for one-time address at {P}[l] + // - D = auxiliary key image for the offsetted amount commitment '{C}[l] - C_offset' + // - {s_non_l} = fake responses for this proof + // - l = real signing index in {P} and '{C} - C_offset' + // - k = number of parallel nonces that each participant provides + // - n = number of ring members + rct::keyV b_params; + // indices in b_params where L and R 'alpha' components will be + std::size_t b_params_L_offset; + std::size_t b_params_R_offset; + // CLSAG 'concise' coefficients for {P} and '{C} - C_offset' + // mu_x = H(domain-separator, {P}, {C}, I, (1/8)*D, C_offset) + // - note: 'D' is stored in the form '(1/8)*D' in transaction data + rct::key mu_P; + rct::key mu_C; + // ring size + std::size_t n; + // aggregate key image: mu_P*I + mu_C*D + rct::geDsmp wH_l_precomp; + // aggregate ring members: mu_P*P_i + mu_C*(C_i - C_offset) + std::vector<rct::geDsmp> W_precomp; + // key image component base keys: H_p(P_i) + std::vector<rct::geDsmp> H_precomp; + // cache for later: generator 'G' in 'precomp' representation + rct::geDsmp G_precomp; + // real signing index in this CLSAG + std::size_t l; + // signature responses + rct::keyV s; + // number of signing nonces expected per signer + std::size_t num_alpha_components; +public: + CLSAG_context_t() : initialized{false} {} + + // prepare CLSAG challenge context + bool init( + const rct::keyV& P, + const rct::keyV& C_nonzero, + const rct::key& C_offset, + const rct::key& message, + const rct::key& I, + const rct::key& D, + const unsigned int l, + const rct::keyV& s, + const std::size_t num_alpha_components + ); + + // get the local signer's combined musig2-style private nonce and compute the CLSAG challenge + bool combine_alpha_and_compute_challenge( + // set of summed-together musig2-style public nonces from all multisig signers for this CLSAG's L component + const rct::keyV& total_alpha_G, + // set of summed-together musig2-style public nonces from all multisig signers for this CLSAG's R component + const rct::keyV& total_alpha_H, + // local signer's private musig2-style nonces + const rct::keyV& alpha, + // local signer's final private nonce, using musig2-style combination with factor 'b' + // alpha_combined = sum_i(b^i * alpha[i]) + rct::key& alpha_combined, + // CLSAG challenge to store in the proof + rct::key& c_0, + // final CLSAG challenge to respond to (need this to make multisig partial signatures) + rct::key& c + ); + + // getter for CLSAG 'concise' coefficients + bool get_mu( + rct::key& mu_P, + rct::key& mu_C + ) const; +}; + +} //namespace signing + +} //namespace multisig diff --git a/src/multisig/multisig_tx_builder_ringct.cpp b/src/multisig/multisig_tx_builder_ringct.cpp new file mode 100644 index 000000000..e5c9ac483 --- /dev/null +++ b/src/multisig/multisig_tx_builder_ringct.cpp @@ -0,0 +1,1059 @@ +// Copyright (c) 2021, 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 "multisig_tx_builder_ringct.h" + +#include "int-util.h" +#include "memwipe.h" + +#include "cryptonote_basic/cryptonote_basic.h" +#include "cryptonote_basic/account.h" +#include "cryptonote_basic/cryptonote_format_utils.h" +#include "cryptonote_config.h" +#include "cryptonote_core/cryptonote_tx_utils.h" +#include "device/device.hpp" +#include "multisig_clsag_context.h" +#include "ringct/bulletproofs.h" +#include "ringct/bulletproofs_plus.h" +#include "ringct/rctSigs.h" + +#include <boost/multiprecision/cpp_int.hpp> + +#include <algorithm> +#include <cstring> +#include <limits> +#include <set> +#include <string> +#include <unordered_map> +#include <unordered_set> +#include <vector> + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "multisig" + +namespace multisig { + +namespace signing { +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +bool view_tag_required(const int bp_version) +{ + // view tags were introduced at the same time as BP+, so they are needed after BP+ (v4 and later) + if (bp_version <= 3) + return false; + else + return true; +} +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +static void sort_sources( + std::vector<cryptonote::tx_source_entry>& sources +) +{ + std::sort(sources.begin(), sources.end(), [](const auto& lhs, const auto& rhs){ + const rct::key& ki0 = lhs.multisig_kLRki.ki; + const rct::key& ki1 = rhs.multisig_kLRki.ki; + return memcmp(&ki0, &ki1, sizeof(rct::key)) > 0; + }); +} +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +static bool compute_keys_for_sources( + const cryptonote::account_keys& account_keys, + const std::vector<cryptonote::tx_source_entry>& sources, + const std::uint32_t subaddr_account, + const std::set<std::uint32_t>& subaddr_minor_indices, + rct::keyV& input_secret_keys +) +{ + const std::size_t num_sources = sources.size(); + hw::device& hwdev = account_keys.get_device(); + std::unordered_map<crypto::public_key, cryptonote::subaddress_index> subaddresses; + for (const std::uint32_t minor_index: subaddr_minor_indices) { + subaddresses[hwdev.get_subaddress_spend_public_key( + account_keys, + {subaddr_account, minor_index} + )] = {subaddr_account, minor_index}; + } + input_secret_keys.resize(num_sources); + for (std::size_t i = 0; i < num_sources; ++i) { + const auto& src = sources[i]; + crypto::key_image tmp_key_image; + cryptonote::keypair tmp_keys; + if (src.real_output >= src.outputs.size()) + return false; + if (not cryptonote::generate_key_image_helper( + account_keys, + subaddresses, + rct::rct2pk(src.outputs[src.real_output].second.dest), + src.real_out_tx_key, + src.real_out_additional_tx_keys, + src.real_output_in_tx_index, + tmp_keys, + tmp_key_image, + hwdev + )) { + return false; + } + input_secret_keys[i] = rct::sk2rct(tmp_keys.sec); + } + return true; +} +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +static void shuffle_destinations( + std::vector<cryptonote::tx_destination_entry>& destinations +) +{ + std::shuffle(destinations.begin(), destinations.end(), crypto::random_device{}); +} +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +static bool set_tx_extra( + const cryptonote::account_keys& account_keys, + const std::vector<cryptonote::tx_destination_entry>& destinations, + const cryptonote::tx_destination_entry& change, + const crypto::secret_key& tx_secret_key, + const crypto::public_key& tx_public_key, + const std::vector<crypto::public_key>& tx_aux_public_keys, + const std::vector<std::uint8_t>& extra, + cryptonote::transaction& tx +) +{ + hw::device &hwdev = account_keys.get_device(); + tx.extra = extra; + // if we have a stealth payment id, find it and encrypt it with the tx key now + std::vector<cryptonote::tx_extra_field> tx_extra_fields; + if (cryptonote::parse_tx_extra(tx.extra, tx_extra_fields)) + { + bool add_dummy_payment_id = true; + cryptonote::tx_extra_nonce extra_nonce; + if (cryptonote::find_tx_extra_field_by_type(tx_extra_fields, extra_nonce)) + { + crypto::hash payment_id = crypto::null_hash; + crypto::hash8 payment_id8 = crypto::null_hash8; + if (cryptonote::get_encrypted_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id8)) + { + LOG_PRINT_L2("Encrypting payment id " << payment_id8); + crypto::public_key view_key_pub = cryptonote::get_destination_view_key_pub(destinations, change.addr); + if (view_key_pub == crypto::null_pkey) + { + // valid combinations: + // - 1 output with encrypted payment ID, dummy change output (0 amount) + // - 0 outputs, 1 change output with encrypted payment ID + // - 1 output with encrypted payment ID, 1 change output + LOG_ERROR("Destinations have to have exactly one output to support encrypted payment ids"); + return false; + } + + if (!hwdev.encrypt_payment_id(payment_id8, view_key_pub, tx_secret_key)) + { + LOG_ERROR("Failed to encrypt payment id"); + return false; + } + + std::string extra_nonce_updated; + cryptonote::set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce_updated, payment_id8); + cryptonote::remove_field_from_tx_extra(tx.extra, typeid(cryptonote::tx_extra_nonce)); + if (!cryptonote::add_extra_nonce_to_tx_extra(tx.extra, extra_nonce_updated)) + { + LOG_ERROR("Failed to add encrypted payment id to tx extra"); + return false; + } + LOG_PRINT_L1("Encrypted payment ID: " << payment_id8); + add_dummy_payment_id = false; + } + else if (cryptonote::get_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id)) + { + add_dummy_payment_id = false; + } + } + + // we don't add one if we've got more than the usual 1 destination plus change + if (destinations.size() > 2) + add_dummy_payment_id = false; + + if (add_dummy_payment_id) + { + // if we have neither long nor short payment id, add a dummy short one, + // this should end up being the vast majority of txes as time goes on + std::string extra_nonce_updated; + crypto::hash8 payment_id8 = crypto::null_hash8; + crypto::public_key view_key_pub = cryptonote::get_destination_view_key_pub(destinations, change.addr); + if (view_key_pub == crypto::null_pkey) + { + LOG_ERROR("Failed to get key to encrypt dummy payment id with"); + } + else + { + hwdev.encrypt_payment_id(payment_id8, view_key_pub, tx_secret_key); + cryptonote::set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce_updated, payment_id8); + if (!cryptonote::add_extra_nonce_to_tx_extra(tx.extra, extra_nonce_updated)) + { + LOG_ERROR("Failed to add dummy encrypted payment id to tx extra"); + // continue anyway + } + } + } + } + else + { + MWARNING("Failed to parse tx extra"); + tx_extra_fields.clear(); + } + + cryptonote::remove_field_from_tx_extra(tx.extra, typeid(cryptonote::tx_extra_pub_key)); + cryptonote::add_tx_pub_key_to_extra(tx.extra, tx_public_key); + cryptonote::remove_field_from_tx_extra(tx.extra, typeid(cryptonote::tx_extra_additional_pub_keys)); + LOG_PRINT_L2("tx pubkey: " << tx_public_key); + if (tx_aux_public_keys.size()) + { + LOG_PRINT_L2("additional tx pubkeys: "); + for (size_t i = 0; i < tx_aux_public_keys.size(); ++i) + LOG_PRINT_L2(tx_aux_public_keys[i]); + cryptonote::add_additional_tx_pub_keys_to_extra(tx.extra, tx_aux_public_keys); + } + if (not cryptonote::sort_tx_extra(tx.extra, tx.extra)) + return false; + return true; +} +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +static void make_tx_secret_key_seed(const crypto::secret_key& tx_secret_key_entropy, + const std::vector<cryptonote::tx_source_entry>& sources, + crypto::secret_key& tx_secret_key_seed) +{ + // seed = H(H("domain separator"), entropy, {KI}) + static const std::string domain_separator{config::HASH_KEY_MULTISIG_TX_PRIVKEYS_SEED}; + + rct::keyV hash_context; + hash_context.reserve(2 + sources.size()); + auto hash_context_wiper = epee::misc_utils::create_scope_leave_handler([&]{ + memwipe(hash_context.data(), hash_context.size()); + }); + hash_context.emplace_back(); + rct::cn_fast_hash(hash_context.back(), domain_separator.data(), domain_separator.size()); //domain sep + hash_context.emplace_back(rct::sk2rct(tx_secret_key_entropy)); //entropy + + for (const cryptonote::tx_source_entry& source : sources) + hash_context.emplace_back(source.multisig_kLRki.ki); //{KI} + + // set the seed + tx_secret_key_seed = rct::rct2sk(rct::cn_fast_hash(hash_context)); +} +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +static void make_tx_secret_keys(const crypto::secret_key& tx_secret_key_seed, + const std::size_t num_tx_keys, + std::vector<crypto::secret_key>& tx_secret_keys) +{ + // make tx secret keys as a hash chain of the seed + // h1 = H_n(seed || H("domain separator")) + // h2 = H_n(seed || h1) + // h3 = H_n(seed || h2) + // ... + static const std::string domain_separator{config::HASH_KEY_MULTISIG_TX_PRIVKEYS}; + + rct::keyV hash_context; + hash_context.resize(2); + auto hash_context_wiper = epee::misc_utils::create_scope_leave_handler([&]{ + memwipe(hash_context.data(), hash_context.size()); + }); + hash_context[0] = rct::sk2rct(tx_secret_key_seed); + rct::cn_fast_hash(hash_context[1], domain_separator.data(), domain_separator.size()); + + tx_secret_keys.clear(); + tx_secret_keys.resize(num_tx_keys); + + for (crypto::secret_key& tx_secret_key : tx_secret_keys) + { + // advance the hash chain + hash_context[1] = rct::hash_to_scalar(hash_context); + + // set this key + tx_secret_key = rct::rct2sk(hash_context[1]); + } +} +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +static bool collect_tx_secret_keys(const std::vector<crypto::secret_key>& tx_secret_keys, + crypto::secret_key& tx_secret_key, + std::vector<crypto::secret_key>& tx_aux_secret_keys) +{ + if (tx_secret_keys.size() == 0) + return false; + + tx_secret_key = tx_secret_keys[0]; + tx_aux_secret_keys.clear(); + tx_aux_secret_keys.reserve(tx_secret_keys.size() - 1); + for (std::size_t tx_key_index{1}; tx_key_index < tx_secret_keys.size(); ++tx_key_index) + tx_aux_secret_keys.emplace_back(tx_secret_keys[tx_key_index]); + + return true; +} +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +static bool compute_keys_for_destinations( + const cryptonote::account_keys& account_keys, + const std::uint32_t subaddr_account, + const std::vector<cryptonote::tx_destination_entry>& destinations, + const cryptonote::tx_destination_entry& change, + const std::vector<std::uint8_t>& extra, + const bool use_view_tags, + const bool reconstruction, + const crypto::secret_key& tx_secret_key_seed, + crypto::secret_key& tx_secret_key, + std::vector<crypto::secret_key>& tx_aux_secret_keys, + rct::keyV& output_public_keys, + rct::keyV& output_amount_secret_keys, + std::vector<crypto::view_tag>& view_tags, + cryptonote::transaction& unsigned_tx +) +{ + hw::device &hwdev = account_keys.get_device(); + + // check non-zero change amount case + if (change.amount > 0) + { + // the change output must be directed to the local account + if (change.addr != hwdev.get_subaddress(account_keys, {subaddr_account})) + return false; + + // expect the change destination to be in the destination set + if (std::find_if(destinations.begin(), destinations.end(), + [&change](const auto &destination) -> bool + { + return destination.addr == change.addr; + }) == destinations.end()) + return false; + } + + // collect non-change recipients into normal/subaddress buckets + std::unordered_set<cryptonote::account_public_address> unique_subbaddr_recipients; + std::unordered_set<cryptonote::account_public_address> unique_std_recipients; + for(const auto& dst_entr: destinations) { + if (dst_entr.addr == change.addr) + continue; + if (dst_entr.is_subaddress) + unique_subbaddr_recipients.insert(dst_entr.addr); + else + unique_std_recipients.insert(dst_entr.addr); + } + + // figure out how many tx secret keys are needed + // - tx aux keys: add if there are > 1 non-change recipients, with at least one to a subaddress + const std::size_t num_destinations = destinations.size(); + const bool need_tx_aux_keys = unique_subbaddr_recipients.size() + bool(unique_std_recipients.size()) > 1; + + const std::size_t num_tx_keys = 1 + (need_tx_aux_keys ? num_destinations : 0); + + // make tx secret keys + std::vector<crypto::secret_key> all_tx_secret_keys; + make_tx_secret_keys(tx_secret_key_seed, num_tx_keys, all_tx_secret_keys); + + // split up tx secret keys + crypto::secret_key tx_secret_key_temp; + std::vector<crypto::secret_key> tx_aux_secret_keys_temp; + if (not collect_tx_secret_keys(all_tx_secret_keys, tx_secret_key_temp, tx_aux_secret_keys_temp)) + return false; + + if (reconstruction) + { + // when reconstructing, the tx secret keys should be reproducible from input seed + if (!(tx_secret_key == tx_secret_key_temp)) + return false; + if (!(tx_aux_secret_keys == tx_aux_secret_keys_temp)) + return false; + } + else + { + tx_secret_key = tx_secret_key_temp; + tx_aux_secret_keys = std::move(tx_aux_secret_keys_temp); + } + + // tx pub key: R + crypto::public_key tx_public_key; + if (unique_std_recipients.empty() && unique_subbaddr_recipients.size() == 1) { + // if there is exactly 1 non-change recipient, and it's to a subaddress, then the tx pubkey = r*Ksi_nonchange_recipient + tx_public_key = rct::rct2pk( + hwdev.scalarmultKey( + rct::pk2rct(unique_subbaddr_recipients.begin()->m_spend_public_key), + rct::sk2rct(tx_secret_key) + )); + } + else { + // otherwise, the tx pub key = r*G + // - if there are > 1 non-change recipients, with at least one to a subaddress, then the tx pubkey is not used + // (additional tx keys will be used instead) + // - if all non-change recipients are to normal addresses, then the tx pubkey will be used by all recipients + // (including change recipient, even if change is to a subaddress) + tx_public_key = rct::rct2pk(hwdev.scalarmultBase(rct::sk2rct(tx_secret_key))); + } + + // additional tx pubkeys: R_t + output_public_keys.resize(num_destinations); + view_tags.resize(num_destinations); + std::vector<crypto::public_key> tx_aux_public_keys; + crypto::public_key temp_output_public_key; + + for (std::size_t i = 0; i < num_destinations; ++i) { + if (not hwdev.generate_output_ephemeral_keys( + unsigned_tx.version, + account_keys, + tx_public_key, + tx_secret_key, + destinations[i], + change.addr, + i, + need_tx_aux_keys, + tx_aux_secret_keys, + tx_aux_public_keys, + output_amount_secret_keys, + temp_output_public_key, + use_view_tags, + view_tags[i] //unused variable if use_view_tags is not set + )) { + return false; + } + output_public_keys[i] = rct::pk2rct(temp_output_public_key); + } + + if (num_destinations != output_amount_secret_keys.size()) + return false; + + CHECK_AND_ASSERT_MES( + tx_aux_public_keys.size() == tx_aux_secret_keys.size(), + false, + "Internal error creating additional public keys" + ); + + if (not set_tx_extra(account_keys, destinations, change, tx_secret_key, tx_public_key, tx_aux_public_keys, extra, unsigned_tx)) + return false; + + return true; +} +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +static void set_tx_inputs( + const std::vector<cryptonote::tx_source_entry>& sources, + cryptonote::transaction& unsigned_tx +) +{ + const std::size_t num_sources = sources.size(); + unsigned_tx.vin.resize(num_sources); + for (std::size_t i = 0; i < num_sources; ++i) { + std::vector<std::uint64_t> offsets; + offsets.reserve(sources[i].outputs.size()); + for (const auto& e: sources[i].outputs) + offsets.emplace_back(e.first); + unsigned_tx.vin[i] = cryptonote::txin_to_key{ + .amount = 0, + .key_offsets = cryptonote::absolute_output_offsets_to_relative(offsets), + .k_image = rct::rct2ki(sources[i].multisig_kLRki.ki), + }; + } +} +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +static bool onetime_addresses_are_unique(const rct::keyV& output_public_keys) +{ + for (auto addr_it = output_public_keys.begin(); addr_it != output_public_keys.end(); ++addr_it) + { + if (std::find(output_public_keys.begin(), addr_it, *addr_it) != addr_it) + return false; + } + + return true; +} +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +static bool set_tx_outputs(const rct::keyV& output_public_keys, cryptonote::transaction& unsigned_tx) +{ + // sanity check: all onetime addresses should be unique + if (not onetime_addresses_are_unique(output_public_keys)) + return false; + + // set the tx outputs + const std::size_t num_destinations = output_public_keys.size(); + unsigned_tx.vout.resize(num_destinations); + for (std::size_t i = 0; i < num_destinations; ++i) + cryptonote::set_tx_out(0, rct::rct2pk(output_public_keys[i]), false, crypto::view_tag{}, unsigned_tx.vout[i]); + + return true; +} +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +static bool set_tx_outputs_with_view_tags( + const rct::keyV& output_public_keys, + const std::vector<crypto::view_tag>& view_tags, + cryptonote::transaction& unsigned_tx +) +{ + // sanity check: all onetime addresses should be unique + if (not onetime_addresses_are_unique(output_public_keys)) + return false; + + // set the tx outputs (with view tags) + const std::size_t num_destinations = output_public_keys.size(); + CHECK_AND_ASSERT_MES(view_tags.size() == num_destinations, false, + "multisig signing protocol: internal error, view tag size mismatch."); + unsigned_tx.vout.resize(num_destinations); + for (std::size_t i = 0; i < num_destinations; ++i) + cryptonote::set_tx_out(0, rct::rct2pk(output_public_keys[i]), true, view_tags[i], unsigned_tx.vout[i]); + + return true; +} +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +static void make_new_range_proofs(const int bp_version, + const std::vector<std::uint64_t>& output_amounts, + const rct::keyV& output_amount_masks, + rct::rctSigPrunable& sigs) +{ + sigs.bulletproofs.clear(); + sigs.bulletproofs_plus.clear(); + + if (bp_version == 3) + sigs.bulletproofs.push_back(rct::bulletproof_PROVE(output_amounts, output_amount_masks)); + else if (bp_version == 4) + sigs.bulletproofs_plus.push_back(rct::bulletproof_plus_PROVE(output_amounts, output_amount_masks)); +} +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +static bool try_reconstruct_range_proofs(const int bp_version, + const rct::rctSigPrunable& original_sigs, + const std::size_t num_destinations, + const rct::ctkeyV& output_public_keys, + rct::rctSigPrunable& reconstructed_sigs) +{ + auto try_reconstruct_range_proofs = + [&](const auto &original_range_proofs, auto &new_range_proofs) -> bool + { + if (original_range_proofs.size() != 1) + return false; + + new_range_proofs = original_range_proofs; + new_range_proofs[0].V.resize(num_destinations); + for (std::size_t i = 0; i < num_destinations; ++i) + new_range_proofs[0].V[i] = rct::scalarmultKey(output_public_keys[i].mask, rct::INV_EIGHT); + + return true; + }; + + if (bp_version == 3) + { + if (not try_reconstruct_range_proofs(original_sigs.bulletproofs, reconstructed_sigs.bulletproofs)) + return false; + return rct::bulletproof_VERIFY(reconstructed_sigs.bulletproofs); + } + else if (bp_version == 4) + { + if (not try_reconstruct_range_proofs(original_sigs.bulletproofs_plus, reconstructed_sigs.bulletproofs_plus)) + return false; + return rct::bulletproof_plus_VERIFY(reconstructed_sigs.bulletproofs_plus); + } + + return false; +} +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +static bool set_tx_rct_signatures( + const std::uint64_t fee, + const std::vector<cryptonote::tx_source_entry>& sources, + const std::vector<cryptonote::tx_destination_entry>& destinations, + const rct::keyV& input_secret_keys, + const rct::keyV& output_public_keys, + const rct::keyV& output_amount_secret_keys, + const rct::RCTConfig& rct_config, + const bool reconstruction, + cryptonote::transaction& unsigned_tx, + std::vector<CLSAG_context_t>& CLSAG_contexts, + rct::keyV& cached_w +) +{ + if (rct_config.bp_version != 3 && + rct_config.bp_version != 4) + return false; + if (rct_config.range_proof_type != rct::RangeProofPaddedBulletproof) + return false; + + const std::size_t num_destinations = destinations.size(); + const std::size_t num_sources = sources.size(); + + // rct_signatures component of tx + rct::rctSig rv{}; + + // set misc. fields + if (rct_config.bp_version == 3) + rv.type = rct::RCTTypeCLSAG; + else if (rct_config.bp_version == 4) + rv.type = rct::RCTTypeBulletproofPlus; + else + return false; + rv.txnFee = fee; + rv.message = rct::hash2rct(cryptonote::get_transaction_prefix_hash(unsigned_tx)); + + // define outputs + std::vector<std::uint64_t> output_amounts(num_destinations); + rct::keyV output_amount_masks(num_destinations); + rv.ecdhInfo.resize(num_destinations); + rv.outPk.resize(num_destinations); + for (std::size_t i = 0; i < num_destinations; ++i) { + rv.outPk[i].dest = output_public_keys[i]; + output_amounts[i] = destinations[i].amount; + output_amount_masks[i] = genCommitmentMask(output_amount_secret_keys[i]); + rv.ecdhInfo[i].amount = rct::d2h(output_amounts[i]); + rct::addKeys2( + rv.outPk[i].mask, + output_amount_masks[i], + rv.ecdhInfo[i].amount, + rct::H + ); + rct::ecdhEncode(rv.ecdhInfo[i], output_amount_secret_keys[i], true); + } + + // output range proofs + if (not reconstruction) { + make_new_range_proofs(rct_config.bp_version, output_amounts, output_amount_masks, rv.p); + } + else { + if (not try_reconstruct_range_proofs(rct_config.bp_version, + unsigned_tx.rct_signatures.p, + num_destinations, + rv.outPk, + rv.p)) + return false; + } + + // prepare rings for input CLSAGs + rv.mixRing.resize(num_sources); + for (std::size_t i = 0; i < num_sources; ++i) { + const std::size_t ring_size = sources[i].outputs.size(); + rv.mixRing[i].resize(ring_size); + for (std::size_t j = 0; j < ring_size; ++j) { + rv.mixRing[i][j].dest = sources[i].outputs[j].second.dest; + rv.mixRing[i][j].mask = sources[i].outputs[j].second.mask; + } + } + + // make pseudo-output commitments + rct::keyV a; //pseudo-output commitment blinding factors + auto a_wiper = epee::misc_utils::create_scope_leave_handler([&]{ + memwipe(static_cast<rct::key *>(a.data()), a.size() * sizeof(rct::key)); + }); + if (not reconstruction) { + a.resize(num_sources); + rv.p.pseudoOuts.resize(num_sources); + a[num_sources - 1] = rct::zero(); + for (std::size_t i = 0; i < num_destinations; ++i) { + sc_add( + a[num_sources - 1].bytes, + a[num_sources - 1].bytes, + output_amount_masks[i].bytes + ); + } + for (std::size_t i = 0; i < num_sources - 1; ++i) { + rct::skGen(a[i]); + sc_sub( + a[num_sources - 1].bytes, + a[num_sources - 1].bytes, + a[i].bytes + ); + rct::genC(rv.p.pseudoOuts[i], a[i], sources[i].amount); + } + rct::genC( + rv.p.pseudoOuts[num_sources - 1], + a[num_sources - 1], + sources[num_sources - 1].amount + ); + } + // check balance if reconstructing the tx + else { + rv.p.pseudoOuts = unsigned_tx.rct_signatures.p.pseudoOuts; + if (num_sources != rv.p.pseudoOuts.size()) + return false; + rct::key balance_accumulator = rct::scalarmultH(rct::d2h(fee)); + for (const auto& e: rv.outPk) + rct::addKeys(balance_accumulator, balance_accumulator, e.mask); + for (const auto& pseudoOut: rv.p.pseudoOuts) + rct::subKeys(balance_accumulator, balance_accumulator, pseudoOut); + if (not (balance_accumulator == rct::identity())) + return false; + } + + // prepare input CLSAGs for signing + const rct::key message = get_pre_mlsag_hash(rv, hw::get_device("default")); + + rv.p.CLSAGs.resize(num_sources); + if (reconstruction) { + if (num_sources != unsigned_tx.rct_signatures.p.CLSAGs.size()) + return false; + } + + CLSAG_contexts.resize(num_sources); + if (not reconstruction) + cached_w.resize(num_sources); + + for (std::size_t i = 0; i < num_sources; ++i) { + const std::size_t ring_size = rv.mixRing[i].size(); + const rct::key& I = sources[i].multisig_kLRki.ki; + const std::size_t l = sources[i].real_output; + if (l >= ring_size) + return false; + rct::keyV& s = rv.p.CLSAGs[i].s; + const rct::key& C_offset = rv.p.pseudoOuts[i]; + rct::keyV P(ring_size); + rct::keyV C_nonzero(ring_size); + + if (not reconstruction) { + s.resize(ring_size); + for (std::size_t j = 0; j < ring_size; ++j) { + if (j != l) + s[j] = rct::skGen(); //make fake responses + } + } + else { + if (ring_size != unsigned_tx.rct_signatures.p.CLSAGs[i].s.size()) + return false; + s = unsigned_tx.rct_signatures.p.CLSAGs[i].s; + } + + for (std::size_t j = 0; j < ring_size; ++j) { + P[j] = rv.mixRing[i][j].dest; + C_nonzero[j] = rv.mixRing[i][j].mask; + } + + rct::key D; + rct::key z; + auto z_wiper = epee::misc_utils::create_scope_leave_handler([&]{ + memwipe(static_cast<rct::key *>(&z), sizeof(rct::key)); + }); + if (not reconstruction) { + sc_sub(z.bytes, sources[i].mask.bytes, a[i].bytes); //commitment to zero privkey + ge_p3 H_p3; + rct::hash_to_p3(H_p3, rv.mixRing[i][l].dest); + rct::key H_l; + ge_p3_tobytes(H_l.bytes, &H_p3); + D = rct::scalarmultKey(H_l, z); //auxilliary key image (for commitment to zero) + rv.p.CLSAGs[i].D = rct::scalarmultKey(D, rct::INV_EIGHT); + rv.p.CLSAGs[i].I = I; + } + else { + rv.p.CLSAGs[i].D = unsigned_tx.rct_signatures.p.CLSAGs[i].D; + rv.p.CLSAGs[i].I = I; + D = rct::scalarmultKey(rv.p.CLSAGs[i].D, rct::EIGHT); + } + + if (not CLSAG_contexts[i].init(P, C_nonzero, C_offset, message, I, D, l, s, kAlphaComponents)) + return false; + + if (not reconstruction) { + rct::key mu_P; + rct::key mu_C; + if (not CLSAG_contexts[i].get_mu(mu_P, mu_C)) + return false; + sc_mul(cached_w[i].bytes, mu_P.bytes, input_secret_keys[i].bytes); + sc_muladd(cached_w[i].bytes, mu_C.bytes, z.bytes, cached_w[i].bytes); + } + } + unsigned_tx.rct_signatures = std::move(rv); + return true; +} +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +static bool compute_tx_fee( + const std::vector<cryptonote::tx_source_entry>& sources, + const std::vector<cryptonote::tx_destination_entry>& destinations, + std::uint64_t& fee +) +{ + boost::multiprecision::uint128_t in_amount = 0; + for (const auto& src: sources) + in_amount += src.amount; + + boost::multiprecision::uint128_t out_amount = 0; + for (const auto& dst: destinations) + out_amount += dst.amount; + + if (out_amount > in_amount) + return false; + + if (in_amount - out_amount > std::numeric_limits<std::uint64_t>::max()) + return false; + + fee = static_cast<std::uint64_t>(in_amount - out_amount); + return true; +} +//---------------------------------------------------------------------------------------------------------------------- +tx_builder_ringct_t::tx_builder_ringct_t(): initialized(false) {} +//---------------------------------------------------------------------------------------------------------------------- +tx_builder_ringct_t::~tx_builder_ringct_t() +{ + memwipe(static_cast<rct::key *>(cached_w.data()), cached_w.size() * sizeof(rct::key)); +} +//---------------------------------------------------------------------------------------------------------------------- +bool tx_builder_ringct_t::init( + const cryptonote::account_keys& account_keys, + const std::vector<std::uint8_t>& extra, + const std::uint64_t unlock_time, + const std::uint32_t subaddr_account, + const std::set<std::uint32_t>& subaddr_minor_indices, + std::vector<cryptonote::tx_source_entry>& sources, + std::vector<cryptonote::tx_destination_entry>& destinations, + const cryptonote::tx_destination_entry& change, + const rct::RCTConfig& rct_config, + const bool use_rct, + const bool reconstruction, + crypto::secret_key& tx_secret_key, + std::vector<crypto::secret_key>& tx_aux_secret_keys, + crypto::secret_key& tx_secret_key_entropy, + cryptonote::transaction& unsigned_tx +) +{ + initialized = false; + this->reconstruction = reconstruction; + if (not use_rct) + return false; + if (sources.empty()) + return false; + + if (not reconstruction) + unsigned_tx.set_null(); + + std::uint64_t fee; + if (not compute_tx_fee(sources, destinations, fee)) + return false; + + // decide if view tags are needed + const bool use_view_tags{view_tag_required(rct_config.bp_version)}; + + // misc. fields + unsigned_tx.version = 2; //rct = 2 + unsigned_tx.unlock_time = unlock_time; + + // sort inputs + sort_sources(sources); + + // prepare tx secret key seed (must be AFTER sorting sources) + // - deriving the seed from sources plus entropy ensures uniqueness for every new tx attempt + // - the goal is that two multisig txs added to the chain will never have outputs with the same onetime addresses, + // which would burn funds (embedding the inputs' key images guarantees this) + // - it is acceptable if two tx attempts use the same input set and entropy (only a malicious tx proposer will do + // that, but all it can accomplish is leaking information about the recipients - which a malicious proposer can + // easily do outside the signing ritual anyway) + if (not reconstruction) + tx_secret_key_entropy = rct::rct2sk(rct::skGen()); + + // expect not null (note: wallet serialization code may set this to null if handling an old partial tx) + if (tx_secret_key_entropy == crypto::null_skey) + return false; + + crypto::secret_key tx_secret_key_seed; + make_tx_secret_key_seed(tx_secret_key_entropy, sources, tx_secret_key_seed); + + // get secret keys for signing input CLSAGs (multisig: or for the initial partial signature) + rct::keyV input_secret_keys; + auto input_secret_keys_wiper = epee::misc_utils::create_scope_leave_handler([&]{ + memwipe(static_cast<rct::key *>(input_secret_keys.data()), input_secret_keys.size() * sizeof(rct::key)); + }); + if (not compute_keys_for_sources(account_keys, sources, subaddr_account, subaddr_minor_indices, input_secret_keys)) + return false; + + // randomize output order + if (not reconstruction) + shuffle_destinations(destinations); + + // prepare outputs + rct::keyV output_public_keys; + rct::keyV output_amount_secret_keys; + std::vector<crypto::view_tag> view_tags; + auto output_amount_secret_keys_wiper = epee::misc_utils::create_scope_leave_handler([&]{ + memwipe(static_cast<rct::key *>(output_amount_secret_keys.data()), output_amount_secret_keys.size() * sizeof(rct::key)); + }); + if (not compute_keys_for_destinations(account_keys, + subaddr_account, + destinations, + change, + extra, + use_view_tags, + reconstruction, + tx_secret_key_seed, + tx_secret_key, + tx_aux_secret_keys, + output_public_keys, + output_amount_secret_keys, + view_tags, + unsigned_tx)) + return false; + + // add inputs to tx + set_tx_inputs(sources, unsigned_tx); + + // add output one-time addresses to tx + bool set_tx_outputs_result{false}; + if (use_view_tags) + set_tx_outputs_result = set_tx_outputs_with_view_tags(output_public_keys, view_tags, unsigned_tx); + else + set_tx_outputs_result = set_tx_outputs(output_public_keys, unsigned_tx); + + if (not set_tx_outputs_result) + return false; + + // prepare input signatures + if (not set_tx_rct_signatures(fee, sources, destinations, input_secret_keys, output_public_keys, output_amount_secret_keys, + rct_config, reconstruction, unsigned_tx, CLSAG_contexts, cached_w)) + return false; + + initialized = true; + return true; +} +//---------------------------------------------------------------------------------------------------------------------- +bool tx_builder_ringct_t::first_partial_sign( + const std::size_t source, + const rct::keyV& total_alpha_G, + const rct::keyV& total_alpha_H, + const rct::keyV& alpha, + rct::key& c_0, + rct::key& s +) +{ + if (not initialized or reconstruction) + return false; + const std::size_t num_sources = CLSAG_contexts.size(); + if (source >= num_sources) + return false; + rct::key c; + rct::key alpha_combined; + auto alpha_combined_wiper = epee::misc_utils::create_scope_leave_handler([&]{ + memwipe(static_cast<rct::key *>(&alpha_combined), sizeof(rct::key)); + }); + if (not CLSAG_contexts[source].combine_alpha_and_compute_challenge( + total_alpha_G, + total_alpha_H, + alpha, + alpha_combined, + c_0, + c + )) { + return false; + } + + // initial partial response: + // s = alpha_combined_local - challenge*[mu_P*(local keys and sender-receiver secret and subaddress material) + + // mu_C*(commitment-to-zero secret)] + sc_mulsub(s.bytes, c.bytes, cached_w[source].bytes, alpha_combined.bytes); + return true; +} +//---------------------------------------------------------------------------------------------------------------------- +bool tx_builder_ringct_t::next_partial_sign( + const rct::keyM& total_alpha_G, + const rct::keyM& total_alpha_H, + const rct::keyM& alpha, + const rct::key& x, + rct::keyV& c_0, + rct::keyV& s +) +{ + if (not initialized or not reconstruction) + return false; + const std::size_t num_sources = CLSAG_contexts.size(); + if (num_sources != total_alpha_G.size()) + return false; + if (num_sources != total_alpha_H.size()) + return false; + if (num_sources != alpha.size()) + return false; + if (num_sources != c_0.size()) + return false; + if (num_sources != s.size()) + return false; + for (std::size_t i = 0; i < num_sources; ++i) { + rct::key c; + rct::key alpha_combined; + auto alpha_combined_wiper = epee::misc_utils::create_scope_leave_handler([&]{ + memwipe(static_cast<rct::key *>(&alpha_combined), sizeof(rct::key)); + }); + if (not CLSAG_contexts[i].combine_alpha_and_compute_challenge( + total_alpha_G[i], + total_alpha_H[i], + alpha[i], + alpha_combined, + c_0[i], + c + )) { + return false; + } + rct::key mu_P; + rct::key mu_C; + if (not CLSAG_contexts[i].get_mu(mu_P, mu_C)) + return false; + rct::key w; + auto w_wiper = epee::misc_utils::create_scope_leave_handler([&]{ + memwipe(static_cast<rct::key *>(&w), sizeof(rct::key)); + }); + sc_mul(w.bytes, mu_P.bytes, x.bytes); + + // include local signer's response: + // s += alpha_combined_local - challenge*[mu_P*(local keys)] + sc_add(s[i].bytes, s[i].bytes, alpha_combined.bytes); + sc_mulsub(s[i].bytes, c.bytes, w.bytes, s[i].bytes); + } + return true; +} +//---------------------------------------------------------------------------------------------------------------------- +bool tx_builder_ringct_t::finalize_tx( + const std::vector<cryptonote::tx_source_entry>& sources, + const rct::keyV& c_0, + const rct::keyV& s, + cryptonote::transaction& unsigned_tx +) +{ + // checks + const std::size_t num_sources = sources.size(); + if (num_sources != unsigned_tx.rct_signatures.p.CLSAGs.size()) + return false; + if (num_sources != c_0.size()) + return false; + if (num_sources != s.size()) + return false; + + // finalize tx signatures + for (std::size_t i = 0; i < num_sources; ++i) { + const std::size_t ring_size = unsigned_tx.rct_signatures.p.CLSAGs[i].s.size(); + if (sources[i].real_output >= ring_size) + return false; + unsigned_tx.rct_signatures.p.CLSAGs[i].s[sources[i].real_output] = s[i]; + unsigned_tx.rct_signatures.p.CLSAGs[i].c1 = c_0[i]; + } + + return true; +} +//---------------------------------------------------------------------------------------------------------------------- +} //namespace signing + +} //namespace multisig diff --git a/src/multisig/multisig_tx_builder_ringct.h b/src/multisig/multisig_tx_builder_ringct.h new file mode 100644 index 000000000..853934659 --- /dev/null +++ b/src/multisig/multisig_tx_builder_ringct.h @@ -0,0 +1,120 @@ +// Copyright (c) 2021, 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 "ringct/rctTypes.h" + +#include <set> +#include <vector> + +namespace cryptonote { + +class transaction; +struct tx_source_entry; +struct tx_destination_entry; +struct account_keys; + +} + +namespace multisig { + +namespace signing { + +class CLSAG_context_t; + +// number of parallel signing nonces to use per signer (2 nonces as in musig2 and FROST) +constexpr std::size_t kAlphaComponents = 2; + +class tx_builder_ringct_t final { +private: + // the tx builder has been initialized + bool initialized; + // the tx builder is 'reconstructing' a tx that has already been created using this object + bool reconstruction; + // cached: mu_P*(local keys and sender-receiver secret and subaddress material) + mu_C*(commitment-to-zero secret) + // - these are only used for the initial building of a tx (not reconstructions) + rct::keyV cached_w; + // contexts for making CLSAG challenges with multisig nonces + std::vector<CLSAG_context_t> CLSAG_contexts; +public: + tx_builder_ringct_t(); + ~tx_builder_ringct_t(); + + // prepare an unsigned transaction (and get tx privkeys for outputs) + bool init( + const cryptonote::account_keys& account_keys, + const std::vector<std::uint8_t>& extra, + const std::uint64_t unlock_time, + const std::uint32_t subaddr_account, + const std::set<std::uint32_t>& subaddr_minor_indices, + std::vector<cryptonote::tx_source_entry>& sources, + std::vector<cryptonote::tx_destination_entry>& destinations, + const cryptonote::tx_destination_entry& change, + const rct::RCTConfig& rct_config, + const bool use_rct, + const bool reconstruction, + crypto::secret_key& tx_secret_key, + std::vector<crypto::secret_key>& tx_aux_secret_keys, + crypto::secret_key& tx_secret_key_entropy, + cryptonote::transaction& unsigned_tx + ); + + // get the first partial signature for the specified input ('source') + bool first_partial_sign( + const std::size_t source, + const rct::keyV& total_alpha_G, + const rct::keyV& total_alpha_H, + const rct::keyV& alpha, + rct::key& c_0, + rct::key& s + ); + + // get intermediate partial signatures for all the inputs + bool next_partial_sign( + const rct::keyM& total_alpha_G, + const rct::keyM& total_alpha_H, + const rct::keyM& alpha, + const rct::key& x, + rct::keyV& c_0, + rct::keyV& s + ); + + // finalize an unsigned transaction (add challenges and real responses to incomplete CLSAG signatures) + static bool finalize_tx( + const std::vector<cryptonote::tx_source_entry>& sources, + const rct::keyV& c_0, + const rct::keyV& s, + cryptonote::transaction& unsigned_tx + ); +}; + +} //namespace signing + +} //namespace multisig diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl index a3bc3bf24..f33ce977d 100644 --- a/src/p2p/net_node.inl +++ b/src/p2p/net_node.inl @@ -538,7 +538,7 @@ namespace nodetool if ( !set_max_out_peers(public_zone, command_line::get_arg(vm, arg_out_peers) ) ) return false; else - m_payload_handler.set_max_out_peers(public_zone.m_config.m_net_config.max_out_connection_count); + m_payload_handler.set_max_out_peers(epee::net_utils::zone::public_, public_zone.m_config.m_net_config.max_out_connection_count); if ( !set_max_in_peers(public_zone, command_line::get_arg(vm, arg_in_peers) ) ) @@ -575,6 +575,8 @@ namespace nodetool if (!set_max_out_peers(zone, proxy.max_connections)) return false; + else + m_payload_handler.set_max_out_peers(proxy.zone, proxy.max_connections); epee::byte_slice this_noise = nullptr; if (proxy.noise) @@ -2462,8 +2464,12 @@ namespace nodetool const epee::net_utils::zone zone_type = context.m_remote_address.get_zone(); network_zone& zone = m_network_zones.at(zone_type); + //will add self to peerlist if in same zone as outgoing later in this function + const bool outgoing_to_same_zone = !context.m_is_income && zone.m_our_address.get_zone() == zone_type; + const uint32_t max_peerlist_size = P2P_DEFAULT_PEERS_IN_HANDSHAKE - (outgoing_to_same_zone ? 1 : 0); + std::vector<peerlist_entry> local_peerlist_new; - zone.m_peerlist.get_peerlist_head(local_peerlist_new, true, P2P_DEFAULT_PEERS_IN_HANDSHAKE); + zone.m_peerlist.get_peerlist_head(local_peerlist_new, true, max_peerlist_size); //only include out peers we did not already send rsp.local_peerlist_new.reserve(local_peerlist_new.size()); @@ -2483,7 +2489,7 @@ namespace nodetool etc., because someone could give faulty addresses over Tor/I2P to get the real peer with that identity banned/blacklisted. */ - if(!context.m_is_income && zone.m_our_address.get_zone() == zone_type) + if(outgoing_to_same_zone) rsp.local_peerlist_new.push_back(peerlist_entry{zone.m_our_address, zone.m_config.m_peer_id, std::time(nullptr)}); LOG_DEBUG_CC(context, "COMMAND_TIMED_SYNC"); @@ -2758,7 +2764,7 @@ namespace nodetool public_zone->second.m_config.m_net_config.max_out_connection_count = count; if(current > count) public_zone->second.m_net_server.get_config_object().del_out_connections(current - count); - m_payload_handler.set_max_out_peers(count); + m_payload_handler.set_max_out_peers(epee::net_utils::zone::public_, count); } } @@ -2887,10 +2893,12 @@ namespace nodetool { if (m_offline) return true; if (!m_exclusive_peers.empty()) return true; - if (m_payload_handler.needs_new_sync_connections()) return true; for (auto& zone : m_network_zones) { + if (m_payload_handler.needs_new_sync_connections(zone.first)) + continue; + if (zone.second.m_net_server.is_stop_signal_sent()) return false; diff --git a/src/ringct/rctSigs.cpp b/src/ringct/rctSigs.cpp index bd67778ec..21040317c 100644 --- a/src/ringct/rctSigs.cpp +++ b/src/ringct/rctSigs.cpp @@ -238,14 +238,12 @@ namespace rct { // P[l] == p*G // C[l] == z*G // C[i] == C_nonzero[i] - C_offset (for hashing purposes) for all i - clsag CLSAG_Gen(const key &message, const keyV & P, const key & p, const keyV & C, const key & z, const keyV & C_nonzero, const key & C_offset, const unsigned int l, const multisig_kLRki *kLRki, key *mscout, key *mspout, hw::device &hwdev) { + clsag CLSAG_Gen(const key &message, const keyV & P, const key & p, const keyV & C, const key & z, const keyV & C_nonzero, const key & C_offset, const unsigned int l, hw::device &hwdev) { clsag sig; size_t n = P.size(); // ring size CHECK_AND_ASSERT_THROW_MES(n == C.size(), "Signing and commitment key vector sizes must match!"); CHECK_AND_ASSERT_THROW_MES(n == C_nonzero.size(), "Signing and commitment key vector sizes must match!"); CHECK_AND_ASSERT_THROW_MES(l < n, "Signing index out of range!"); - CHECK_AND_ASSERT_THROW_MES((kLRki && mscout) || (!kLRki && !mscout), "Only one of kLRki/mscout is present"); - CHECK_AND_ASSERT_THROW_MES((mscout && mspout) || !kLRki, "Multisig pointers are not all present"); // Key images ge_p3 H_p3; @@ -260,16 +258,7 @@ namespace rct { key aG; key aH; - // Multisig - if (kLRki) - { - sig.I = kLRki->ki; - scalarmultKey(D,H,z); - } - else - { - hwdev.clsag_prepare(p,z,sig.I,D,H,a,aG,aH); - } + hwdev.clsag_prepare(p,z,sig.I,D,H,a,aG,aH); geDsmp I_precomp; geDsmp D_precomp; @@ -317,18 +306,9 @@ namespace rct { c_to_hash[2*n+1] = C_offset; c_to_hash[2*n+2] = message; - // Multisig data is present - if (kLRki) - { - a = kLRki->k; - c_to_hash[2*n+3] = kLRki->L; - c_to_hash[2*n+4] = kLRki->R; - } - else - { - c_to_hash[2*n+3] = aG; - c_to_hash[2*n+4] = aH; - } + c_to_hash[2*n+3] = aG; + c_to_hash[2*n+4] = aH; + hwdev.clsag_hash(c_to_hash,c); size_t i; @@ -380,16 +360,11 @@ namespace rct { hwdev.clsag_sign(c,a,p,z,mu_P,mu_C,sig.s[l]); memwipe(&a, sizeof(key)); - if (mscout) - *mscout = c; - if (mspout) - *mspout = mu_P; - return sig; } clsag CLSAG_Gen(const key &message, const keyV & P, const key & p, const keyV & C, const key & z, const keyV & C_nonzero, const key & C_offset, const unsigned int l) { - return CLSAG_Gen(message, P, p, C, z, C_nonzero, C_offset, l, NULL, NULL, NULL, hw::get_device("default")); + return CLSAG_Gen(message, P, p, C, z, C_nonzero, C_offset, l, hw::get_device("default")); } // MLSAG signatures @@ -397,7 +372,7 @@ namespace rct { // This generalization allows for some dimensions not to require linkability; // this is used in practice for commitment data within signatures // Note that using more than one linkable dimension is not recommended. - mgSig MLSAG_Gen(const key &message, const keyM & pk, const keyV & xx, const multisig_kLRki *kLRki, key *mscout, const unsigned int index, size_t dsRows, hw::device &hwdev) { + mgSig MLSAG_Gen(const key &message, const keyM & pk, const keyV & xx, const unsigned int index, size_t dsRows, hw::device &hwdev) { mgSig rv; size_t cols = pk.size(); CHECK_AND_ASSERT_THROW_MES(cols >= 2, "Error! What is c if cols = 1!"); @@ -409,8 +384,6 @@ namespace rct { } CHECK_AND_ASSERT_THROW_MES(xx.size() == rows, "Bad xx size"); CHECK_AND_ASSERT_THROW_MES(dsRows <= rows, "Bad dsRows size"); - CHECK_AND_ASSERT_THROW_MES((kLRki && mscout) || (!kLRki && !mscout), "Only one of kLRki/mscout is present"); - CHECK_AND_ASSERT_THROW_MES(!kLRki || dsRows == 1, "Multisig requires exactly 1 dsRows"); size_t i = 0, j = 0, ii = 0; key c, c_old, L, R, Hi; @@ -428,20 +401,11 @@ namespace rct { DP("here1"); for (i = 0; i < dsRows; i++) { toHash[3 * i + 1] = pk[index][i]; - if (kLRki) { - // multisig - alpha[i] = kLRki->k; - toHash[3 * i + 2] = kLRki->L; - toHash[3 * i + 3] = kLRki->R; - rv.II[i] = kLRki->ki; - } - else { - hash_to_p3(Hi_p3, pk[index][i]); - ge_p3_tobytes(Hi.bytes, &Hi_p3); - hwdev.mlsag_prepare(Hi, xx[i], alpha[i] , aG[i] , aHP[i] , rv.II[i]); - toHash[3 * i + 2] = aG[i]; - toHash[3 * i + 3] = aHP[i]; - } + hash_to_p3(Hi_p3, pk[index][i]); + ge_p3_tobytes(Hi.bytes, &Hi_p3); + hwdev.mlsag_prepare(Hi, xx[i], alpha[i] , aG[i] , aHP[i] , rv.II[i]); + toHash[3 * i + 2] = aG[i]; + toHash[3 * i + 3] = aHP[i]; precomp(Ip[i].k, rv.II[i]); } size_t ndsRows = 3 * dsRows; //non Double Spendable Rows (see identity chains paper) @@ -485,8 +449,6 @@ namespace rct { } } hwdev.mlsag_sign(c, xx, alpha, rows, dsRows, rv.ss[index]); - if (mscout) - *mscout = c; return rv; } @@ -722,7 +684,7 @@ namespace rct { // this shows that sum inputs = sum outputs //Ver: // verifies the above sig is created corretly - mgSig proveRctMG(const key &message, const ctkeyM & pubs, const ctkeyV & inSk, const ctkeyV &outSk, const ctkeyV & outPk, const multisig_kLRki *kLRki, key *mscout, unsigned int index, const key &txnFeeKey, hw::device &hwdev) { + mgSig proveRctMG(const key &message, const ctkeyM & pubs, const ctkeyV & inSk, const ctkeyV &outSk, const ctkeyV & outPk, unsigned int index, const key &txnFeeKey, hw::device &hwdev) { //setup vars size_t cols = pubs.size(); CHECK_AND_ASSERT_THROW_MES(cols >= 1, "Empty pubs"); @@ -733,7 +695,6 @@ namespace rct { } CHECK_AND_ASSERT_THROW_MES(inSk.size() == rows, "Bad inSk size"); CHECK_AND_ASSERT_THROW_MES(outSk.size() == outPk.size(), "Bad outSk/outPk size"); - CHECK_AND_ASSERT_THROW_MES((kLRki && mscout) || (!kLRki && !mscout), "Only one of kLRki/mscout is present"); keyV sk(rows + 1); keyV tmp(rows + 1); @@ -766,7 +727,7 @@ namespace rct { for (size_t j = 0; j < outPk.size(); j++) { sc_sub(sk[rows].bytes, sk[rows].bytes, outSk[j].mask.bytes); //subtract output masks in last row.. } - mgSig result = MLSAG_Gen(message, M, sk, kLRki, mscout, index, rows, hwdev); + mgSig result = MLSAG_Gen(message, M, sk, index, rows, hwdev); memwipe(sk.data(), sk.size() * sizeof(key)); return result; } @@ -779,12 +740,11 @@ namespace rct { // inSk is x, a_in corresponding to signing index // a_out, Cout is for the output commitment // index is the signing index.. - mgSig proveRctMGSimple(const key &message, const ctkeyV & pubs, const ctkey & inSk, const key &a , const key &Cout, const multisig_kLRki *kLRki, key *mscout, unsigned int index, hw::device &hwdev) { + mgSig proveRctMGSimple(const key &message, const ctkeyV & pubs, const ctkey & inSk, const key &a , const key &Cout, unsigned int index, hw::device &hwdev) { //setup vars size_t rows = 1; size_t cols = pubs.size(); CHECK_AND_ASSERT_THROW_MES(cols >= 1, "Empty pubs"); - CHECK_AND_ASSERT_THROW_MES((kLRki && mscout) || (!kLRki && !mscout), "Only one of kLRki/mscout is present"); keyV tmp(rows + 1); keyV sk(rows + 1); size_t i; @@ -796,17 +756,16 @@ namespace rct { M[i][0] = pubs[i].dest; subKeys(M[i][1], pubs[i].mask, Cout); } - mgSig result = MLSAG_Gen(message, M, sk, kLRki, mscout, index, rows, hwdev); + mgSig result = MLSAG_Gen(message, M, sk, index, rows, hwdev); memwipe(sk.data(), sk.size() * sizeof(key)); return result; } - clsag proveRctCLSAGSimple(const key &message, const ctkeyV &pubs, const ctkey &inSk, const key &a, const key &Cout, const multisig_kLRki *kLRki, key *mscout, key *mspout, unsigned int index, hw::device &hwdev) { + clsag proveRctCLSAGSimple(const key &message, const ctkeyV &pubs, const ctkey &inSk, const key &a, const key &Cout, unsigned int index, hw::device &hwdev) { //setup vars size_t rows = 1; size_t cols = pubs.size(); CHECK_AND_ASSERT_THROW_MES(cols >= 1, "Empty pubs"); - CHECK_AND_ASSERT_THROW_MES((kLRki && mscout) || (!kLRki && !mscout), "Only one of kLRki/mscout is present"); keyV tmp(rows + 1); keyV sk(rows + 1); keyM M(cols, tmp); @@ -826,7 +785,7 @@ namespace rct { sk[0] = copy(inSk.dest); sc_sub(sk[1].bytes, inSk.mask.bytes, a.bytes); - clsag result = CLSAG_Gen(message, P, sk[0], C, sk[1], C_nonzero, Cout, index, kLRki, mscout, mspout, hwdev); + clsag result = CLSAG_Gen(message, P, sk[0], C, sk[1], C_nonzero, Cout, index, hwdev); memwipe(sk.data(), sk.size() * sizeof(key)); return result; } @@ -1084,14 +1043,13 @@ namespace rct { // must know the destination private key to find the correct amount, else will return a random number // Note: For txn fees, the last index in the amounts vector should contain that // Thus the amounts vector will be "one" longer than the destinations vectort - rctSig genRct(const key &message, const ctkeyV & inSk, const keyV & destinations, const vector<xmr_amount> & amounts, const ctkeyM &mixRing, const keyV &amount_keys, const multisig_kLRki *kLRki, multisig_out *msout, unsigned int index, ctkeyV &outSk, const RCTConfig &rct_config, hw::device &hwdev) { + rctSig genRct(const key &message, const ctkeyV & inSk, const keyV & destinations, const vector<xmr_amount> & amounts, const ctkeyM &mixRing, const keyV &amount_keys, unsigned int index, ctkeyV &outSk, const RCTConfig &rct_config, hw::device &hwdev) { CHECK_AND_ASSERT_THROW_MES(amounts.size() == destinations.size() || amounts.size() == destinations.size() + 1, "Different number of amounts/destinations"); CHECK_AND_ASSERT_THROW_MES(amount_keys.size() == destinations.size(), "Different number of amount_keys/destinations"); CHECK_AND_ASSERT_THROW_MES(index < mixRing.size(), "Bad index into mixRing"); for (size_t n = 0; n < mixRing.size(); ++n) { CHECK_AND_ASSERT_THROW_MES(mixRing[n].size() == inSk.size(), "Bad mixRing size"); } - CHECK_AND_ASSERT_THROW_MES((kLRki && msout) || (!kLRki && !msout), "Only one of kLRki/msout is present"); CHECK_AND_ASSERT_THROW_MES(inSk.size() < 2, "genRct is not suitable for 2+ rings"); rctSig rv; @@ -1130,23 +1088,21 @@ namespace rct { key txnFeeKey = scalarmultH(d2h(rv.txnFee)); rv.mixRing = mixRing; - if (msout) - msout->c.resize(1); - rv.p.MGs.push_back(proveRctMG(get_pre_mlsag_hash(rv, hwdev), rv.mixRing, inSk, outSk, rv.outPk, kLRki, msout ? &msout->c[0] : NULL, index, txnFeeKey,hwdev)); + rv.p.MGs.push_back(proveRctMG(get_pre_mlsag_hash(rv, hwdev), rv.mixRing, inSk, outSk, rv.outPk, index, txnFeeKey,hwdev)); return rv; } - rctSig genRct(const key &message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const vector<xmr_amount> & amounts, const keyV &amount_keys, const multisig_kLRki *kLRki, multisig_out *msout, const int mixin, const RCTConfig &rct_config, hw::device &hwdev) { + rctSig genRct(const key &message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const vector<xmr_amount> & amounts, const keyV &amount_keys, const int mixin, const RCTConfig &rct_config, hw::device &hwdev) { unsigned int index; ctkeyM mixRing; ctkeyV outSk; tie(mixRing, index) = populateFromBlockchain(inPk, mixin); - return genRct(message, inSk, destinations, amounts, mixRing, amount_keys, kLRki, msout, index, outSk, rct_config, hwdev); + return genRct(message, inSk, destinations, amounts, mixRing, amount_keys, index, outSk, rct_config, hwdev); } //RCT simple //for post-rct only - rctSig genRctSimple(const key &message, const ctkeyV & inSk, const keyV & destinations, const vector<xmr_amount> &inamounts, const vector<xmr_amount> &outamounts, xmr_amount txnFee, const ctkeyM & mixRing, const keyV &amount_keys, const std::vector<multisig_kLRki> *kLRki, multisig_out *msout, const std::vector<unsigned int> & index, ctkeyV &outSk, const RCTConfig &rct_config, hw::device &hwdev) { + rctSig genRctSimple(const key &message, const ctkeyV & inSk, const keyV & destinations, const vector<xmr_amount> &inamounts, const vector<xmr_amount> &outamounts, xmr_amount txnFee, const ctkeyM & mixRing, const keyV &amount_keys, const std::vector<unsigned int> & index, ctkeyV &outSk, const RCTConfig &rct_config, hw::device &hwdev) { const bool bulletproof_or_plus = rct_config.range_proof_type > RangeProofBorromean; CHECK_AND_ASSERT_THROW_MES(inamounts.size() > 0, "Empty inamounts"); CHECK_AND_ASSERT_THROW_MES(inamounts.size() == inSk.size(), "Different number of inamounts/inSk"); @@ -1157,10 +1113,6 @@ namespace rct { for (size_t n = 0; n < mixRing.size(); ++n) { CHECK_AND_ASSERT_THROW_MES(index[n] < mixRing[n].size(), "Bad index into mixRing"); } - CHECK_AND_ASSERT_THROW_MES((kLRki && msout) || (!kLRki && !msout), "Only one of kLRki/msout is present"); - if (kLRki && msout) { - CHECK_AND_ASSERT_THROW_MES(kLRki->size() == inamounts.size(), "Mismatched kLRki/inamounts sizes"); - } rctSig rv; if (bulletproof_or_plus) @@ -1322,11 +1274,7 @@ namespace rct { DP(pseudoOuts[i]); key full_message = get_pre_mlsag_hash(rv,hwdev); - if (msout) - { - msout->c.resize(inamounts.size()); - msout->mu_p.resize(is_rct_clsag(rv.type) ? inamounts.size() : 0); - } + for (i = 0 ; i < inamounts.size(); i++) { if (is_rct_clsag(rv.type)) @@ -1334,17 +1282,17 @@ namespace rct { if (hwdev.get_mode() == hw::device::TRANSACTION_CREATE_FAKE) rv.p.CLSAGs[i] = make_dummy_clsag(rv.mixRing[i].size()); else - rv.p.CLSAGs[i] = proveRctCLSAGSimple(full_message, rv.mixRing[i], inSk[i], a[i], pseudoOuts[i], kLRki ? &(*kLRki)[i]: NULL, msout ? &msout->c[i] : NULL, msout ? &msout->mu_p[i] : NULL, index[i], hwdev); + rv.p.CLSAGs[i] = proveRctCLSAGSimple(full_message, rv.mixRing[i], inSk[i], a[i], pseudoOuts[i], index[i], hwdev); } else { - rv.p.MGs[i] = proveRctMGSimple(full_message, rv.mixRing[i], inSk[i], a[i], pseudoOuts[i], kLRki ? &(*kLRki)[i]: NULL, msout ? &msout->c[i] : NULL, index[i], hwdev); + rv.p.MGs[i] = proveRctMGSimple(full_message, rv.mixRing[i], inSk[i], a[i], pseudoOuts[i], index[i], hwdev); } } return rv; } - rctSig genRctSimple(const key &message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const vector<xmr_amount> &inamounts, const vector<xmr_amount> &outamounts, const keyV &amount_keys, const std::vector<multisig_kLRki> *kLRki, multisig_out *msout, xmr_amount txnFee, unsigned int mixin, const RCTConfig &rct_config, hw::device &hwdev) { + rctSig genRctSimple(const key &message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const vector<xmr_amount> &inamounts, const vector<xmr_amount> &outamounts, const keyV &amount_keys, xmr_amount txnFee, unsigned int mixin, const RCTConfig &rct_config, hw::device &hwdev) { std::vector<unsigned int> index; index.resize(inPk.size()); ctkeyM mixRing; @@ -1354,7 +1302,7 @@ namespace rct { mixRing[i].resize(mixin+1); index[i] = populateFromBlockchainSimple(mixRing[i], inPk[i], mixin); } - return genRctSimple(message, inSk, destinations, inamounts, outamounts, txnFee, mixRing, amount_keys, kLRki, msout, index, outSk, rct_config, hwdev); + return genRctSimple(message, inSk, destinations, inamounts, outamounts, txnFee, mixRing, amount_keys, index, outSk, rct_config, hwdev); } //RingCT protocol @@ -1700,60 +1648,4 @@ namespace rct { key mask; return decodeRctSimple(rv, sk, i, mask, hwdev); } - - bool signMultisigMLSAG(rctSig &rv, const std::vector<unsigned int> &indices, const keyV &k, const multisig_out &msout, const key &secret_key) { - CHECK_AND_ASSERT_MES(rv.type == RCTTypeFull || rv.type == RCTTypeSimple || rv.type == RCTTypeBulletproof || rv.type == RCTTypeBulletproof2, - false, "unsupported rct type"); - CHECK_AND_ASSERT_MES(!is_rct_clsag(rv.type), false, "CLSAG signature type in MLSAG signature function"); - CHECK_AND_ASSERT_MES(indices.size() == k.size(), false, "Mismatched k/indices sizes"); - CHECK_AND_ASSERT_MES(k.size() == rv.p.MGs.size(), false, "Mismatched k/MGs size"); - CHECK_AND_ASSERT_MES(k.size() == msout.c.size(), false, "Mismatched k/msout.c size"); - CHECK_AND_ASSERT_MES(rv.p.CLSAGs.empty(), false, "CLSAGs not empty for MLSAGs"); - if (rv.type == RCTTypeFull) - { - CHECK_AND_ASSERT_MES(rv.p.MGs.size() == 1, false, "MGs not a single element"); - } - for (size_t n = 0; n < indices.size(); ++n) { - CHECK_AND_ASSERT_MES(indices[n] < rv.p.MGs[n].ss.size(), false, "Index out of range"); - CHECK_AND_ASSERT_MES(!rv.p.MGs[n].ss[indices[n]].empty(), false, "empty ss line"); - } - - // MLSAG: each player contributes a share to the secret-index ss: k - cc*secret_key_share - // cc: msout.c[n], secret_key_share: secret_key - for (size_t n = 0; n < indices.size(); ++n) { - rct::key diff; - sc_mulsub(diff.bytes, msout.c[n].bytes, secret_key.bytes, k[n].bytes); - sc_add(rv.p.MGs[n].ss[indices[n]][0].bytes, rv.p.MGs[n].ss[indices[n]][0].bytes, diff.bytes); - } - return true; - } - - bool signMultisigCLSAG(rctSig &rv, const std::vector<unsigned int> &indices, const keyV &k, const multisig_out &msout, const key &secret_key) { - CHECK_AND_ASSERT_MES(is_rct_clsag(rv.type), false, "unsupported rct type"); - CHECK_AND_ASSERT_MES(indices.size() == k.size(), false, "Mismatched k/indices sizes"); - CHECK_AND_ASSERT_MES(k.size() == rv.p.CLSAGs.size(), false, "Mismatched k/CLSAGs size"); - CHECK_AND_ASSERT_MES(k.size() == msout.c.size(), false, "Mismatched k/msout.c size"); - CHECK_AND_ASSERT_MES(rv.p.MGs.empty(), false, "MGs not empty for CLSAGs"); - CHECK_AND_ASSERT_MES(msout.c.size() == msout.mu_p.size(), false, "Bad mu_p size"); - for (size_t n = 0; n < indices.size(); ++n) { - CHECK_AND_ASSERT_MES(indices[n] < rv.p.CLSAGs[n].s.size(), false, "Index out of range"); - } - - // CLSAG: each player contributes a share to the secret-index ss: k - cc*mu_p*secret_key_share - // cc: msout.c[n], mu_p, msout.mu_p[n], secret_key_share: secret_key - for (size_t n = 0; n < indices.size(); ++n) { - rct::key diff, sk; - sc_mul(sk.bytes, msout.mu_p[n].bytes, secret_key.bytes); - sc_mulsub(diff.bytes, msout.c[n].bytes, sk.bytes, k[n].bytes); - sc_add(rv.p.CLSAGs[n].s[indices[n]].bytes, rv.p.CLSAGs[n].s[indices[n]].bytes, diff.bytes); - } - return true; - } - - bool signMultisig(rctSig &rv, const std::vector<unsigned int> &indices, const keyV &k, const multisig_out &msout, const key &secret_key) { - if (is_rct_clsag(rv.type)) - return signMultisigCLSAG(rv, indices, k, msout, secret_key); - else - return signMultisigMLSAG(rv, indices, k, msout, secret_key); - } } diff --git a/src/ringct/rctSigs.h b/src/ringct/rctSigs.h index a0346b34e..17cfd77b9 100644 --- a/src/ringct/rctSigs.h +++ b/src/ringct/rctSigs.h @@ -74,12 +74,12 @@ namespace rct { // Gen creates a signature which proves that for some column in the keymatrix "pk" // the signer knows a secret key for each row in that column // Ver verifies that the MG sig was created correctly - mgSig MLSAG_Gen(const key &message, const keyM & pk, const keyV & xx, const multisig_kLRki *kLRki, key *mscout, const unsigned int index, size_t dsRows, hw::device &hwdev); + mgSig MLSAG_Gen(const key &message, const keyM & pk, const keyV & xx, const unsigned int index, size_t dsRows, hw::device &hwdev); bool MLSAG_Ver(const key &message, const keyM &pk, const mgSig &sig, size_t dsRows); - clsag CLSAG_Gen(const key &message, const keyV & P, const key & p, const keyV & C, const key & z, const keyV & C_nonzero, const key & C_offset, const unsigned int l, const multisig_kLRki *kLRki, key *mscout, key *mspout, hw::device &hwdev); + clsag CLSAG_Gen(const key &message, const keyV & P, const key & p, const keyV & C, const key & z, const keyV & C_nonzero, const key & C_offset, const unsigned int l, hw::device &hwdev); clsag CLSAG_Gen(const key &message, const keyV & P, const key & p, const keyV & C, const key & z, const keyV & C_nonzero, const key & C_offset, const unsigned int l); - clsag proveRctCLSAGSimple(const key &, const ctkeyV &, const ctkey &, const key &, const key &, const multisig_kLRki *, key *, key *, unsigned int, hw::device &); + clsag proveRctCLSAGSimple(const key &, const ctkeyV &, const ctkey &, const key &, const key &, unsigned int, hw::device &); bool verRctCLSAGSimple(const key &, const clsag &, const ctkeyV &, const key &); //proveRange and verRange @@ -100,8 +100,8 @@ namespace rct { // this shows that sum inputs = sum outputs //Ver: // verifies the above sig is created corretly - mgSig proveRctMG(const ctkeyM & pubs, const ctkeyV & inSk, const keyV &outMasks, const ctkeyV & outPk, const multisig_kLRki *kLRki, key *mscout, unsigned int index, const key &txnFee, const key &message, hw::device &hwdev); - mgSig proveRctMGSimple(const key & message, const ctkeyV & pubs, const ctkey & inSk, const key &a , const key &Cout, const multisig_kLRki *kLRki, key *mscout, unsigned int index, hw::device &hwdev); + mgSig proveRctMG(const ctkeyM & pubs, const ctkeyV & inSk, const keyV &outMasks, const ctkeyV & outPk, unsigned int index, const key &txnFee, const key &message, hw::device &hwdev); + mgSig proveRctMGSimple(const key & message, const ctkeyV & pubs, const ctkey & inSk, const key &a , const key &Cout, unsigned int index, hw::device &hwdev); bool verRctMG(const mgSig &mg, const ctkeyM & pubs, const ctkeyV & outPk, const key &txnFee, const key &message); bool verRctMGSimple(const key &message, const mgSig &mg, const ctkeyV & pubs, const key & C); @@ -123,10 +123,10 @@ namespace rct { //decodeRct: (c.f. https://eprint.iacr.org/2015/1098 section 5.1.1) // uses the attached ecdh info to find the amounts represented by each output commitment // must know the destination private key to find the correct amount, else will return a random number - rctSig genRct(const key &message, const ctkeyV & inSk, const keyV & destinations, const std::vector<xmr_amount> & amounts, const ctkeyM &mixRing, const keyV &amount_keys, const multisig_kLRki *kLRki, multisig_out *msout, unsigned int index, ctkeyV &outSk, const RCTConfig &rct_config, hw::device &hwdev); - rctSig genRct(const key &message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const std::vector<xmr_amount> & amounts, const keyV &amount_keys, const multisig_kLRki *kLRki, multisig_out *msout, const int mixin, const RCTConfig &rct_config, hw::device &hwdev); - rctSig genRctSimple(const key & message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const std::vector<xmr_amount> & inamounts, const std::vector<xmr_amount> & outamounts, const keyV &amount_keys, const std::vector<multisig_kLRki> *kLRki, multisig_out *msout, xmr_amount txnFee, unsigned int mixin, const RCTConfig &rct_config, hw::device &hwdev); - rctSig genRctSimple(const key & message, const ctkeyV & inSk, const keyV & destinations, const std::vector<xmr_amount> & inamounts, const std::vector<xmr_amount> & outamounts, xmr_amount txnFee, const ctkeyM & mixRing, const keyV &amount_keys, const std::vector<multisig_kLRki> *kLRki, multisig_out *msout, const std::vector<unsigned int> & index, ctkeyV &outSk, const RCTConfig &rct_config, hw::device &hwdev); + rctSig genRct(const key &message, const ctkeyV & inSk, const keyV & destinations, const std::vector<xmr_amount> & amounts, const ctkeyM &mixRing, const keyV &amount_keys, unsigned int index, ctkeyV &outSk, const RCTConfig &rct_config, hw::device &hwdev); + rctSig genRct(const key &message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const std::vector<xmr_amount> & amounts, const keyV &amount_keys, const int mixin, const RCTConfig &rct_config, hw::device &hwdev); + rctSig genRctSimple(const key & message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const std::vector<xmr_amount> & inamounts, const std::vector<xmr_amount> & outamounts, const keyV &amount_keys, xmr_amount txnFee, unsigned int mixin, const RCTConfig &rct_config, hw::device &hwdev); + rctSig genRctSimple(const key & message, const ctkeyV & inSk, const keyV & destinations, const std::vector<xmr_amount> & inamounts, const std::vector<xmr_amount> & outamounts, xmr_amount txnFee, const ctkeyM & mixRing, const keyV &amount_keys, const std::vector<unsigned int> & index, ctkeyV &outSk, const RCTConfig &rct_config, hw::device &hwdev); bool verRct(const rctSig & rv, bool semantics); static inline bool verRct(const rctSig & rv) { return verRct(rv, true) && verRct(rv, false); } bool verRctSemanticsSimple(const rctSig & rv); @@ -138,7 +138,6 @@ namespace rct { xmr_amount decodeRctSimple(const rctSig & rv, const key & sk, unsigned int i, key & mask, hw::device &hwdev); xmr_amount decodeRctSimple(const rctSig & rv, const key & sk, unsigned int i, hw::device &hwdev); key get_pre_mlsag_hash(const rctSig &rv, hw::device &hwdev); - bool signMultisig(rctSig &rv, const std::vector<unsigned int> &indices, const keyV &k, const multisig_out &msout, const key &secret_key); } #endif /* RCTSIGS_H */ diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 0fe28465f..5304333ff 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -492,7 +492,6 @@ namespace cryptonote } CHECK_PAYMENT_MIN1(req, res, COST_PER_GET_INFO, false); - db_rtxn_guard rtxn_guard(&m_core.get_blockchain_storage().get_db()); const bool restricted = m_restricted && ctx; @@ -598,7 +597,6 @@ namespace cryptonote } CHECK_PAYMENT(req, res, 1); - db_rtxn_guard rtxn_guard(&m_core.get_blockchain_storage().get_db()); // quick check for noop if (!req.block_ids.empty()) @@ -609,7 +607,7 @@ namespace cryptonote if (last_block_hash == req.block_ids.front()) { res.start_height = 0; - res.current_height = last_block_height + 1; + res.current_height = m_core.get_current_blockchain_height(); res.status = CORE_RPC_STATUS_OK; return true; } @@ -730,7 +728,6 @@ namespace cryptonote res.blocks.clear(); res.blocks.reserve(req.heights.size()); CHECK_PAYMENT_MIN1(req, res, req.heights.size() * COST_PER_BLOCK, false); - db_rtxn_guard rtxn_guard(&m_core.get_blockchain_storage().get_db()); for (uint64_t height : req.heights) { block blk; @@ -1592,7 +1589,6 @@ namespace cryptonote return r; CHECK_PAYMENT(req, res, 1); - db_rtxn_guard rtxn_guard(&m_core.get_blockchain_storage().get_db()); const bool restricted = m_restricted && ctx; const bool request_has_rpc_origin = ctx != NULL; @@ -1617,7 +1613,6 @@ namespace cryptonote return r; CHECK_PAYMENT(req, res, 1); - db_rtxn_guard rtxn_guard(&m_core.get_blockchain_storage().get_db()); const bool restricted = m_restricted && ctx; const bool request_has_rpc_origin = ctx != NULL; @@ -1720,14 +1715,11 @@ namespace cryptonote error_resp.message = "Wrong parameters, expected height"; return false; } - db_rtxn_guard rtxn_guard(&m_core.get_blockchain_storage().get_db()); uint64_t h = req[0]; - uint64_t blockchain_height = m_core.get_current_blockchain_height(); - if(blockchain_height <= h) + if(m_core.get_current_blockchain_height() <= h) { error_resp.code = CORE_RPC_ERROR_CODE_TOO_BIG_HEIGHT; - error_resp.message = std::string("Requested block height: ") + std::to_string(h) + " greater than current top block height: " + std::to_string(blockchain_height - 1); - return false; + error_resp.message = std::string("Requested block height: ") + std::to_string(h) + " greater than current top block height: " + std::to_string(m_core.get_current_blockchain_height() - 1); } res = string_tools::pod_to_hex(m_core.get_block_id_by_height(h)); return true; @@ -1877,7 +1869,6 @@ namespace cryptonote return false; } } - db_rtxn_guard rtxn_guard(&m_core.get_blockchain_storage().get_db()); crypto::hash seed_hash, next_seed_hash; if (!get_block_template(info.address, req.prev_block.empty() ? NULL : &prev_block, blob_reserve, reserved_offset, wdiff, res.height, res.expected_reward, b, res.seed_height, seed_hash, next_seed_hash, error_resp)) return false; @@ -2351,7 +2342,6 @@ namespace cryptonote CHECK_CORE_READY(); CHECK_PAYMENT_MIN1(req, res, COST_PER_BLOCK_HEADER, false); - db_rtxn_guard rtxn_guard(&m_core.get_blockchain_storage().get_db()); uint64_t last_block_height; crypto::hash last_block_hash; m_core.get_blockchain_top(last_block_height, last_block_hash); @@ -2392,8 +2382,6 @@ namespace cryptonote return false; } - db_rtxn_guard rtxn_guard(&m_core.get_blockchain_storage().get_db()); - auto get = [this](const std::string &hash, bool fill_pow_hash, block_header_response &block_header, bool restricted, epee::json_rpc::error& error_resp) -> bool { crypto::hash block_hash; bool hash_parsed = parse_hash256(hash, block_hash); @@ -2453,6 +2441,13 @@ namespace cryptonote if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_BLOCK_HEADERS_RANGE>(invoke_http_mode::JON_RPC, "getblockheadersrange", req, res, r)) return r; + const uint64_t bc_height = m_core.get_current_blockchain_height(); + if (req.start_height >= bc_height || req.end_height >= bc_height || req.start_height > req.end_height) + { + error_resp.code = CORE_RPC_ERROR_CODE_TOO_BIG_HEIGHT; + error_resp.message = "Invalid start/end heights."; + return false; + } const bool restricted = m_restricted && ctx; if (restricted && req.end_height - req.start_height > RESTRICTED_BLOCK_HEADER_RANGE) { @@ -2462,16 +2457,6 @@ namespace cryptonote } CHECK_PAYMENT_MIN1(req, res, (req.end_height - req.start_height + 1) * COST_PER_BLOCK_HEADER, false); - db_rtxn_guard rtxn_guard(&m_core.get_blockchain_storage().get_db()); - - const uint64_t bc_height = m_core.get_current_blockchain_height(); - if (req.start_height >= bc_height || req.end_height >= bc_height || req.start_height > req.end_height) - { - error_resp.code = CORE_RPC_ERROR_CODE_TOO_BIG_HEIGHT; - error_resp.message = "Invalid start/end heights."; - return false; - } - for (uint64_t h = req.start_height; h <= req.end_height; ++h) { crypto::hash block_hash = m_core.get_block_id_by_height(h); @@ -2516,12 +2501,10 @@ namespace cryptonote if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_BLOCK_HEADER_BY_HEIGHT>(invoke_http_mode::JON_RPC, "getblockheaderbyheight", req, res, r)) return r; - db_rtxn_guard rtxn_guard(&m_core.get_blockchain_storage().get_db()); - uint64_t blockchain_height = m_core.get_current_blockchain_height(); - if(blockchain_height <= req.height) + if(m_core.get_current_blockchain_height() <= req.height) { error_resp.code = CORE_RPC_ERROR_CODE_TOO_BIG_HEIGHT; - error_resp.message = std::string("Requested block height: ") + std::to_string(req.height) + " greater than current top block height: " + std::to_string(blockchain_height - 1); + error_resp.message = std::string("Requested block height: ") + std::to_string(req.height) + " greater than current top block height: " + std::to_string(m_core.get_current_blockchain_height() - 1); return false; } CHECK_PAYMENT_MIN1(req, res, COST_PER_BLOCK_HEADER, false); @@ -2554,7 +2537,6 @@ namespace cryptonote return r; CHECK_PAYMENT_MIN1(req, res, COST_PER_BLOCK, false); - db_rtxn_guard rtxn_guard(&m_core.get_blockchain_storage().get_db()); crypto::hash block_hash; if (!req.hash.empty()) @@ -2569,11 +2551,10 @@ namespace cryptonote } else { - uint64_t blockchain_height = m_core.get_current_blockchain_height(); - if(blockchain_height <= req.height) + if(m_core.get_current_blockchain_height() <= req.height) { error_resp.code = CORE_RPC_ERROR_CODE_TOO_BIG_HEIGHT; - error_resp.message = std::string("Requested block height: ") + std::to_string(req.height) + " greater than current top block height: " + std::to_string(blockchain_height - 1); + error_resp.message = std::string("Requested block height: ") + std::to_string(req.height) + " greater than current top block height: " + std::to_string(m_core.get_current_blockchain_height() - 1); return false; } block_hash = m_core.get_block_id_by_height(req.height); @@ -2881,7 +2862,6 @@ namespace cryptonote bool core_rpc_server::on_get_coinbase_tx_sum(const COMMAND_RPC_GET_COINBASE_TX_SUM::request& req, COMMAND_RPC_GET_COINBASE_TX_SUM::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) { RPC_TRACKER(get_coinbase_tx_sum); - db_rtxn_guard rtxn_guard(&m_core.get_blockchain_storage().get_db()); const uint64_t bc_height = m_core.get_current_blockchain_height(); if (req.height >= bc_height || req.count > bc_height) { @@ -2923,7 +2903,6 @@ namespace cryptonote bool core_rpc_server::on_get_alternate_chains(const COMMAND_RPC_GET_ALTERNATE_CHAINS::request& req, COMMAND_RPC_GET_ALTERNATE_CHAINS::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) { RPC_TRACKER(get_alternate_chains); - db_rtxn_guard rtxn_guard(&m_core.get_blockchain_storage().get_db()); try { std::vector<std::pair<Blockchain::block_extended_info, std::vector<crypto::hash>>> chains = m_core.get_blockchain_storage().get_alternative_chains(); @@ -3226,7 +3205,6 @@ namespace cryptonote bool r; if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_TRANSACTION_POOL_BACKLOG>(invoke_http_mode::JON_RPC, "get_txpool_backlog", req, res, r)) return r; - db_rtxn_guard rtxn_guard(&m_core.get_blockchain_storage().get_db()); size_t n_txes = m_core.get_pool_transactions_count(); CHECK_PAYMENT_MIN1(req, res, COST_PER_TX_POOL_STATS * n_txes, false); diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index d3e40ab74..a8f4e5a07 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -142,6 +142,19 @@ typedef cryptonote::simple_wallet sw; #define MIN_PAYMENT_RATE 0.01f // per hash #define MAX_MNEW_ADDRESSES 1000 +#define CHECK_MULTISIG_ENABLED() \ + do \ + { \ + if (!m_wallet->is_multisig_enabled()) \ + { \ + fail_msg_writer() << tr("Multisig is disabled."); \ + fail_msg_writer() << tr("Multisig is an experimental feature and may have bugs. Things that could go wrong include: funds sent to a multisig wallet can't be spent at all, can only be spent with the participation of a malicious group member, or can be stolen by a malicious group member."); \ + fail_msg_writer() << tr("You can enable it with:"); \ + fail_msg_writer() << tr(" set enable-multisig-experimental 1"); \ + return false; \ + } \ + } while(0) + enum TransferType { Transfer, TransferLocked, @@ -986,12 +999,14 @@ bool simple_wallet::print_fee_info(const std::vector<std::string> &args/* = std: bool simple_wallet::prepare_multisig(const std::vector<std::string> &args) { + CHECK_MULTISIG_ENABLED(); prepare_multisig_main(args, false); return true; } bool simple_wallet::prepare_multisig_main(const std::vector<std::string> &args, bool called_by_mms) { + CHECK_MULTISIG_ENABLED(); if (m_wallet->key_on_device()) { fail_msg_writer() << tr("command not supported by HW wallet"); @@ -1031,12 +1046,14 @@ bool simple_wallet::prepare_multisig_main(const std::vector<std::string> &args, bool simple_wallet::make_multisig(const std::vector<std::string> &args) { + CHECK_MULTISIG_ENABLED(); make_multisig_main(args, false); return true; } bool simple_wallet::make_multisig_main(const std::vector<std::string> &args, bool called_by_mms) { + CHECK_MULTISIG_ENABLED(); if (m_wallet->key_on_device()) { fail_msg_writer() << tr("command not supported by HW wallet"); @@ -1121,11 +1138,13 @@ bool simple_wallet::make_multisig_main(const std::vector<std::string> &args, boo bool simple_wallet::exchange_multisig_keys(const std::vector<std::string> &args) { + CHECK_MULTISIG_ENABLED(); exchange_multisig_keys_main(args, false); return true; } bool simple_wallet::exchange_multisig_keys_main(const std::vector<std::string> &args, bool called_by_mms) { + CHECK_MULTISIG_ENABLED(); bool ready; if (m_wallet->key_on_device()) { @@ -1189,12 +1208,14 @@ bool simple_wallet::exchange_multisig_keys_main(const std::vector<std::string> & bool simple_wallet::export_multisig(const std::vector<std::string> &args) { + CHECK_MULTISIG_ENABLED(); export_multisig_main(args, false); return true; } bool simple_wallet::export_multisig_main(const std::vector<std::string> &args, bool called_by_mms) { + CHECK_MULTISIG_ENABLED(); bool ready; if (m_wallet->key_on_device()) { @@ -1254,12 +1275,14 @@ bool simple_wallet::export_multisig_main(const std::vector<std::string> &args, b bool simple_wallet::import_multisig(const std::vector<std::string> &args) { + CHECK_MULTISIG_ENABLED(); import_multisig_main(args, false); return true; } bool simple_wallet::import_multisig_main(const std::vector<std::string> &args, bool called_by_mms) { + CHECK_MULTISIG_ENABLED(); bool ready; uint32_t threshold, total; if (m_wallet->key_on_device()) @@ -1349,12 +1372,14 @@ bool simple_wallet::accept_loaded_tx(const tools::wallet2::multisig_tx_set &txs) bool simple_wallet::sign_multisig(const std::vector<std::string> &args) { + CHECK_MULTISIG_ENABLED(); sign_multisig_main(args, false); return true; } bool simple_wallet::sign_multisig_main(const std::vector<std::string> &args, bool called_by_mms) { + CHECK_MULTISIG_ENABLED(); bool ready; if (m_wallet->key_on_device()) { @@ -1464,12 +1489,14 @@ bool simple_wallet::sign_multisig_main(const std::vector<std::string> &args, boo bool simple_wallet::submit_multisig(const std::vector<std::string> &args) { + CHECK_MULTISIG_ENABLED(); submit_multisig_main(args, false); return true; } bool simple_wallet::submit_multisig_main(const std::vector<std::string> &args, bool called_by_mms) { + CHECK_MULTISIG_ENABLED(); bool ready; uint32_t threshold; if (m_wallet->key_on_device()) @@ -1551,6 +1578,7 @@ bool simple_wallet::submit_multisig_main(const std::vector<std::string> &args, b bool simple_wallet::export_raw_multisig(const std::vector<std::string> &args) { + CHECK_MULTISIG_ENABLED(); bool ready; uint32_t threshold; if (m_wallet->key_on_device()) @@ -3074,6 +3102,25 @@ bool simple_wallet::set_load_deprecated_formats(const std::vector<std::string> & return true; } +bool simple_wallet::set_enable_multisig(const std::vector<std::string> &args/* = std::vector<std::string>()*/) +{ + if (args.size() < 2) + { + fail_msg_writer() << tr("Value not specified"); + return true; + } + + const auto pwd_container = get_and_verify_password(); + if (pwd_container) + { + parse_bool_and_use(args[1], [&](bool r) { + m_wallet->enable_multisig(r); + m_wallet->rewrite(m_wallet_file, pwd_container->password()); + }); + } + return true; +} + bool simple_wallet::help(const std::vector<std::string> &args/* = std::vector<std::string>()*/) { if(args.empty()) @@ -3391,6 +3438,8 @@ simple_wallet::simple_wallet() " The RPC payment credits balance to target (0 for default).\n " "show-wallet-name-when-locked <1|0>\n " " Set this if you would like to display the wallet name when locked.\n " + "enable-multisig-experimental <1|0>\n " + " Set this to allow multisig commands. Multisig may currently be exploitable if parties do not trust each other.\n " "inactivity-lock-timeout <unsigned int>\n " " How many seconds to wait before locking the wallet (0 to disable).")); m_cmd_binder.set_handler("encrypted_seed", @@ -3806,6 +3855,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args) success_msg_writer() << "auto-mine-for-rpc-payment-threshold = " << m_wallet->auto_mine_for_rpc_payment_threshold(); success_msg_writer() << "credits-target = " << m_wallet->credits_target(); success_msg_writer() << "load-deprecated-formats = " << m_wallet->load_deprecated_formats(); + success_msg_writer() << "enable-multisig-experimental = " << m_wallet->is_multisig_enabled(); return true; } else @@ -3872,6 +3922,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args) CHECK_SIMPLE_VARIABLE("persistent-rpc-client-id", set_persistent_rpc_client_id, tr("0 or 1")); CHECK_SIMPLE_VARIABLE("auto-mine-for-rpc-payment-threshold", set_auto_mine_for_rpc_payment_threshold, tr("floating point >= 0")); CHECK_SIMPLE_VARIABLE("credits-target", set_credits_target, tr("unsigned integer")); + CHECK_SIMPLE_VARIABLE("enable-multisig-experimental", set_enable_multisig, tr("0 or 1")); } fail_msg_writer() << tr("set: unrecognized argument(s)"); return true; @@ -5622,14 +5673,18 @@ void simple_wallet::on_new_block(uint64_t height, const cryptonote::block& block m_refresh_progress_reporter.update(height, false); } //---------------------------------------------------------------------------------------------------- -void simple_wallet::on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index, bool is_change, uint64_t unlock_time) +void simple_wallet::on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, uint64_t burnt, const cryptonote::subaddress_index& subaddr_index, bool is_change, uint64_t unlock_time) { if (m_locked) return; + std::stringstream burn; + if (burnt != 0) { + burn << " (" << print_money(amount) << " yet " << print_money(burnt) << " was burnt)"; + } message_writer(console_color_green, false) << "\r" << tr("Height ") << height << ", " << tr("txid ") << txid << ", " << - print_money(amount) << ", " << + print_money(amount - burnt) << burn.str() << ", " << tr("idx ") << subaddr_index; const uint64_t warn_height = m_wallet->nettype() == TESTNET ? 1000000 : m_wallet->nettype() == STAGENET ? 50000 : 1650000; @@ -6560,7 +6615,6 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri vector<cryptonote::address_parse_info> dsts_info; vector<cryptonote::tx_destination_entry> dsts; - size_t num_subaddresses = 0; for (size_t i = 0; i < local_args.size(); ) { dsts_info.emplace_back(); @@ -6619,7 +6673,6 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri de.addr = info.address; de.is_subaddress = info.is_subaddress; de.is_integrated = info.has_payment_id; - num_subaddresses += info.is_subaddress; if (info.has_payment_id || !payment_id_uri.empty()) { @@ -6911,18 +6964,33 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri //---------------------------------------------------------------------------------------------------- bool simple_wallet::transfer(const std::vector<std::string> &args_) { + if (args_.size() < 1) + { + PRINT_USAGE(USAGE_TRANSFER); + return true; + } transfer_main(Transfer, args_, false); return true; } //---------------------------------------------------------------------------------------------------- bool simple_wallet::locked_transfer(const std::vector<std::string> &args_) { + if (args_.size() < 1) + { + PRINT_USAGE(USAGE_LOCKED_TRANSFER); + return true; + } transfer_main(TransferLocked, args_, false); return true; } //---------------------------------------------------------------------------------------------------- bool simple_wallet::locked_sweep_all(const std::vector<std::string> &args_) { + if (args_.size() < 1) + { + PRINT_USAGE(USAGE_LOCKED_SWEEP_ALL); + return true; + } sweep_main(m_current_subaddress_account, 0, true, args_); return true; } @@ -6980,6 +7048,7 @@ bool simple_wallet::sweep_unmixable(const std::vector<std::string> &args_) // actually commit the transactions if (m_wallet->multisig()) { + CHECK_MULTISIG_ENABLED(); bool r = m_wallet->save_multisig_tx(ptx_vector, "multisig_monero_tx"); if (!r) { @@ -7284,6 +7353,7 @@ bool simple_wallet::sweep_main(uint32_t account, uint64_t below, bool locked, co // actually commit the transactions if (m_wallet->multisig()) { + CHECK_MULTISIG_ENABLED(); bool r = m_wallet->save_multisig_tx(ptx_vector, "multisig_monero_tx"); if (!r) { @@ -7518,6 +7588,7 @@ bool simple_wallet::sweep_single(const std::vector<std::string> &args_) // actually commit the transactions if (m_wallet->multisig()) { + CHECK_MULTISIG_ENABLED(); bool r = m_wallet->save_multisig_tx(ptx_vector, "multisig_monero_tx"); if (!r) { @@ -7618,6 +7689,7 @@ bool simple_wallet::sweep_below(const std::vector<std::string> &args_) if (args_.size() < 1) { fail_msg_writer() << tr("missing threshold amount"); + PRINT_USAGE(USAGE_SWEEP_BELOW); return true; } if (!cryptonote::parse_amount(below, args_[0])) @@ -11549,6 +11621,7 @@ void simple_wallet::mms_auto_config(const std::vector<std::string> &args) bool simple_wallet::mms(const std::vector<std::string> &args) { + CHECK_MULTISIG_ENABLED(); try { m_wallet->get_multisig_wallet_state(); diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index 4c005c53a..6a9fa149d 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -153,6 +153,7 @@ namespace cryptonote bool set_device_name(const std::vector<std::string> &args = std::vector<std::string>()); bool set_export_format(const std::vector<std::string> &args = std::vector<std::string>()); bool set_load_deprecated_formats(const std::vector<std::string> &args = std::vector<std::string>()); + bool set_enable_multisig(const std::vector<std::string> &args = std::vector<std::string>()); bool set_persistent_rpc_client_id(const std::vector<std::string> &args = std::vector<std::string>()); bool set_auto_mine_for_rpc_payment_threshold(const std::vector<std::string> &args = std::vector<std::string>()); bool set_credits_target(const std::vector<std::string> &args = std::vector<std::string>()); @@ -345,7 +346,7 @@ namespace cryptonote //----------------- i_wallet2_callback --------------------- virtual void on_new_block(uint64_t height, const cryptonote::block& block); - virtual void on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index, bool is_change, uint64_t unlock_time); + virtual void on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, uint64_t burnt, const cryptonote::subaddress_index& subaddr_index, bool is_change, uint64_t unlock_time); virtual void on_unconfirmed_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index); virtual void on_money_spent(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& in_tx, uint64_t amount, const cryptonote::transaction& spend_tx, const cryptonote::subaddress_index& subaddr_index); virtual void on_skip_transaction(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx); diff --git a/src/version.cpp.in b/src/version.cpp.in index 9f6ffd97b..c6d473bf9 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.17.0.0" -#define DEF_MONERO_RELEASE_NAME "Oxygen Orion" +#define DEF_MONERO_VERSION "0.18.0.0" +#define DEF_MONERO_RELEASE_NAME "Fluorine Fermi" #define DEF_MONERO_VERSION_FULL DEF_MONERO_VERSION "-" DEF_MONERO_VERSION_TAG #define DEF_MONERO_VERSION_IS_RELEASE @VERSION_IS_RELEASE@ diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index 7cd8656e1..1ee2e20b6 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -154,18 +154,20 @@ struct Wallet2CallbackImpl : public tools::i_wallet2_callback } } - virtual void on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index, bool is_change, uint64_t unlock_time) + virtual void on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, uint64_t burnt, const cryptonote::subaddress_index& subaddr_index, bool is_change, uint64_t unlock_time) { std::string tx_hash = epee::string_tools::pod_to_hex(txid); LOG_PRINT_L3(__FUNCTION__ << ": money received. height: " << height << ", tx: " << tx_hash - << ", amount: " << print_money(amount) + << ", amount: " << print_money(amount - burnt) + << ", burnt: " << print_money(burnt) + << ", raw_output_value: " << print_money(amount) << ", idx: " << subaddr_index); // do not signal on received tx if wallet is not syncronized completely if (m_listener && m_wallet->synchronized()) { - m_listener->moneyReceived(tx_hash, amount); + m_listener->moneyReceived(tx_hash, amount - burnt); m_listener->updated(); } } @@ -1280,6 +1282,42 @@ bool WalletImpl::importOutputs(const string &filename) return true; } +bool WalletImpl::scanTransactions(const std::vector<std::string> &txids) +{ + if (txids.empty()) + { + setStatusError(string(tr("Failed to scan transactions: no transaction ids provided."))); + return false; + } + + // Parse and dedup args + std::unordered_set<crypto::hash> txids_u; + for (const auto &s : txids) + { + crypto::hash txid; + if (!epee::string_tools::hex_to_pod(s, txid)) + { + setStatusError(string(tr("Invalid txid specified: ")) + s); + return false; + } + txids_u.insert(txid); + } + std::vector<crypto::hash> txids_v(txids_u.begin(), txids_u.end()); + + try + { + m_wallet->scan_tx(txids_v); + } + catch (const std::exception &e) + { + LOG_ERROR("Failed to scan transaction: " << e.what()); + setStatusError(string(tr("Failed to scan transaction: ")) + e.what()); + return false; + } + + return true; +} + void WalletImpl::addSubaddressAccount(const std::string& label) { m_wallet->add_subaddress_account(label); diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h index 0e61ee330..018b2a0ed 100644 --- a/src/wallet/api/wallet.h +++ b/src/wallet/api/wallet.h @@ -169,6 +169,7 @@ public: bool importKeyImages(const std::string &filename) override; bool exportOutputs(const std::string &filename, bool all = false) override; bool importOutputs(const std::string &filename) override; + bool scanTransactions(const std::vector<std::string> &txids) override; virtual void disposeTransaction(PendingTransaction * t) override; virtual uint64_t estimateTransactionFee(const std::vector<std::pair<std::string, uint64_t>> &destinations, diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h index c6f81f0e4..b67bce60c 100644 --- a/src/wallet/api/wallet2_api.h +++ b/src/wallet/api/wallet2_api.h @@ -927,6 +927,13 @@ struct Wallet */ virtual bool importOutputs(const std::string &filename) = 0; + /*! + * \brief scanTransactions - scan a list of transaction ids, this operation may reveal the txids to the remote node and affect your privacy + * \param txids - list of transaction ids + * \return - true on success + */ + virtual bool scanTransactions(const std::vector<std::string> &txids) = 0; + virtual TransactionHistory * history() = 0; virtual AddressBook * addressBook() = 0; virtual Subaddress * subaddress() = 0; diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 1eeb893c0..195763949 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -62,6 +62,7 @@ using namespace epee; #include "multisig/multisig.h" #include "multisig/multisig_account.h" #include "multisig/multisig_kex_msg.h" +#include "multisig/multisig_tx_builder_ringct.h" #include "common/boost_serialization_helper.h" #include "common/command_line.h" #include "common/threadpool.h" @@ -1216,7 +1217,8 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended, std m_rpc_version(0), m_export_format(ExportFormat::Binary), m_load_deprecated_formats(false), - m_credits_target(0) + m_credits_target(0), + m_enable_multisig(false) { set_rpc_client_secret_key(rct::rct2sk(rct::skGen())); } @@ -2207,7 +2209,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote } LOG_PRINT_L0("Received money: " << print_money(td.amount()) << ", with tx: " << txid); if (0 != m_callback) - m_callback->on_money_received(height, txid, tx, td.m_amount, td.m_subaddr_index, spends_one_of_ours(tx), td.m_tx.unlock_time); + m_callback->on_money_received(height, txid, tx, td.m_amount, 0, td.m_subaddr_index, spends_one_of_ours(tx), td.m_tx.unlock_time); } total_received_1 += amount; notify = true; @@ -2241,7 +2243,8 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote 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(); + uint64_t burnt = m_transfers[kit->second].amount(); + uint64_t extra_amount = amount - burnt; if (!pool) { transfer_details &td = m_transfers[kit->second]; @@ -2284,7 +2287,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote LOG_PRINT_L0("Received money: " << print_money(td.amount()) << ", with tx: " << txid); if (0 != m_callback) - m_callback->on_money_received(height, txid, tx, td.m_amount, td.m_subaddr_index, spends_one_of_ours(tx), td.m_tx.unlock_time); + m_callback->on_money_received(height, txid, tx, td.m_amount, burnt, td.m_subaddr_index, spends_one_of_ours(tx), td.m_tx.unlock_time); } total_received_1 += extra_amount; notify = true; @@ -2882,6 +2885,11 @@ void wallet2::process_parsed_blocks(uint64_t start_height, const std::vector<cry " (height " + std::to_string(start_height) + "), local block id at this height: " + string_tools::pod_to_hex(m_blockchain[current_index])); + const uint64_t reorg_depth = m_blockchain.size() - current_index; + THROW_WALLET_EXCEPTION_IF(reorg_depth > m_max_reorg_depth, error::reorg_depth_error, + tr("reorg exceeds maximum allowed depth, use 'set max-reorg-depth N' to allow it, reorg depth: ") + + std::to_string(reorg_depth)); + detach_blockchain(current_index, output_tracker_cache); process_new_blockchain_entry(bl, blocks[i], parsed_blocks[i], bl_id, current_index, tx_cache_data, tx_cache_data_offset, output_tracker_cache); } @@ -3153,14 +3161,18 @@ void wallet2::update_pool_state(std::vector<std::tuple<cryptonote::transaction, } } - // get those txes - if (!txids.empty()) + // get_transaction_pool_hashes.bin may return more transactions than we're allowed to request in restricted mode + const size_t SLICE_SIZE = 100; // RESTRICTED_TRANSACTIONS_COUNT as defined in rpc/core_rpc_server.cpp + for (size_t offset = 0; offset < txids.size(); offset += SLICE_SIZE) { cryptonote::COMMAND_RPC_GET_TRANSACTIONS::request req; cryptonote::COMMAND_RPC_GET_TRANSACTIONS::response res; - for (const auto &p: txids) - req.txs_hashes.push_back(epee::string_tools::pod_to_hex(p.first)); - MDEBUG("asking for " << txids.size() << " transactions"); + + const size_t n_txids = std::min<size_t>(SLICE_SIZE, txids.size() - offset); + for (size_t n = offset; n < (offset + n_txids); ++n) { + req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txids.at(n).first)); + } + MDEBUG("asking for " << req.txs_hashes.size() << " transactions"); req.decode_as_json = false; req.prune = true; @@ -3177,7 +3189,7 @@ void wallet2::update_pool_state(std::vector<std::tuple<cryptonote::transaction, MDEBUG("Got " << r << " and " << res.status); if (r && res.status == CORE_RPC_STATUS_OK) { - if (res.txs.size() == txids.size()) + if (res.txs.size() == req.txs_hashes.size()) { for (const auto &tx_entry: res.txs) { @@ -3213,7 +3225,7 @@ void wallet2::update_pool_state(std::vector<std::tuple<cryptonote::transaction, } else { - LOG_PRINT_L0("Expected " << txids.size() << " tx(es), got " << res.txs.size()); + LOG_PRINT_L0("Expected " << n_txids << " out of " << txids.size() << " tx(es), got " << res.txs.size()); } } else @@ -3532,15 +3544,6 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo first = false; - if (!next_blocks.empty()) - { - const uint64_t expected_start_height = std::max(static_cast<uint64_t>(m_blockchain.size()), uint64_t(1)) - 1; - const uint64_t reorg_depth = expected_start_height - std::min(expected_start_height, next_blocks_start_height); - THROW_WALLET_EXCEPTION_IF(reorg_depth > m_max_reorg_depth, error::reorg_depth_error, - tr("reorg exceeds maximum allowed depth, use 'set max-reorg-depth N' to allow it, reorg depth: ") + - std::to_string(reorg_depth)); - } - // if we've got at least 10 blocks to refresh, assume we're starting // a long refresh, and setup a tracking output cache if we need to if (m_track_uses && (!output_tracker_cache || output_tracker_cache->empty()) && next_blocks.size() >= 10) @@ -3625,32 +3628,7 @@ bool wallet2::refresh(bool trusted_daemon, uint64_t & blocks_fetched, bool& rece //---------------------------------------------------------------------------------------------------- bool wallet2::get_rct_distribution(uint64_t &start_height, std::vector<uint64_t> &distribution) { - uint32_t rpc_version; - boost::optional<std::string> result = m_node_rpc_proxy.get_rpc_version(rpc_version); - // no error - if (!!result) - { - // empty string -> not connection - THROW_WALLET_EXCEPTION_IF(result->empty(), tools::error::no_connection_to_daemon, "getversion"); - THROW_WALLET_EXCEPTION_IF(*result == CORE_RPC_STATUS_BUSY, tools::error::daemon_busy, "getversion"); - if (*result != CORE_RPC_STATUS_OK) - { - MDEBUG("Cannot determine daemon RPC version, not requesting rct distribution"); - return false; - } - } - else - { - if (rpc_version >= MAKE_CORE_RPC_VERSION(1, 19)) - { - MDEBUG("Daemon is recent enough, requesting rct distribution"); - } - else - { - MDEBUG("Daemon is too old, not requesting rct distribution"); - return false; - } - } + MDEBUG("Requesting rct distribution"); cryptonote::COMMAND_RPC_GET_OUTPUT_DISTRIBUTION::request req = AUTO_VAL_INIT(req); cryptonote::COMMAND_RPC_GET_OUTPUT_DISTRIBUTION::response res = AUTO_VAL_INIT(res); @@ -4051,6 +4029,9 @@ boost::optional<wallet2::keys_file_data> wallet2::get_keys_file_data(const epee: value2.SetUint64(m_credits_target); json.AddMember("credits_target", value2, json.GetAllocator()); + value2.SetInt(m_enable_multisig ? 1 : 0); + json.AddMember("enable_multisig", value2, json.GetAllocator()); + // Serialize the JSON object rapidjson::StringBuffer buffer; rapidjson::Writer<rapidjson::StringBuffer> writer(buffer); @@ -4199,6 +4180,7 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st m_persistent_rpc_client_id = false; m_auto_mine_for_rpc_payment_threshold = -1.0f; m_credits_target = 0; + m_enable_multisig = false; } else if(json.IsObject()) { @@ -4431,6 +4413,8 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st m_auto_mine_for_rpc_payment_threshold = field_auto_mine_for_rpc_payment; GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, credits_target, uint64_t, Uint64, false, 0); m_credits_target = field_credits_target; + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, enable_multisig, int, Int, false, false); + m_enable_multisig = field_enable_multisig; } else { @@ -5067,7 +5051,6 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password, m_multisig_rounds_passed = 1; // derivations stored (should be empty in last round) - // TODO: make use of the origins map for aggregation-style signing (instead of round-robin) m_multisig_derivations.clear(); m_multisig_derivations.reserve(multisig_account.get_kex_keys_to_origins_map().size()); @@ -5124,7 +5107,6 @@ std::string wallet2::exchange_multisig_keys(const epee::wipeable_string &passwor expanded_msgs.emplace_back(msg); // reconstruct multisig account - crypto::public_key dummy; multisig::multisig_keyset_map_memsafe_t kex_origins_map; for (const auto &derivation : m_multisig_derivations) @@ -5160,7 +5142,6 @@ std::string wallet2::exchange_multisig_keys(const epee::wipeable_string &passwor "Failed to update multisig wallet account due to bad keys"); // derivations stored (should be empty in last round) - // TODO: make use of the origins map for aggregation-style signing (instead of round-robin) m_multisig_derivations.clear(); m_multisig_derivations.reserve(multisig_account.get_kex_keys_to_origins_map().size()); @@ -6639,8 +6620,7 @@ bool wallet2::sign_tx(unsigned_tx_set &exported_txs, std::vector<wallet2::pendin rct::RCTConfig rct_config = sd.rct_config; crypto::secret_key tx_key; std::vector<crypto::secret_key> additional_tx_keys; - rct::multisig_out msout; - bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sd.sources, sd.splitted_dsts, sd.change_dts.addr, sd.extra, ptx.tx, sd.unlock_time, tx_key, additional_tx_keys, sd.use_rct, rct_config, m_multisig ? &msout : NULL, sd.use_view_tags); + bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sd.sources, sd.splitted_dsts, sd.change_dts.addr, sd.extra, ptx.tx, sd.unlock_time, tx_key, additional_tx_keys, sd.use_rct, rct_config, sd.use_view_tags); THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sd.sources, sd.splitted_dsts, sd.unlock_time, m_nettype); // we don't test tx size, because we don't know the current limit, due to not having a blockchain, // and it's a bit pointless to fail there anyway, since it'd be a (good) guess only. We sign anyway, @@ -7148,77 +7128,114 @@ bool wallet2::sign_multisig_tx(multisig_tx_set &exported_txs, std::vector<crypto txids.clear(); - // sign the transactions + // The 'exported_txs' contains a set of different transactions for the multisig group to try to sign. Each of those + // transactions has a set of 'signing attempts' corresponding to all the possible signing groups within the multisig. + // - Here, we will partially sign as many of those signing attempts as possible, for each proposed transaction. for (size_t n = 0; n < exported_txs.m_ptx.size(); ++n) { tools::wallet2::pending_tx &ptx = exported_txs.m_ptx[n]; THROW_WALLET_EXCEPTION_IF(ptx.multisig_sigs.empty(), error::wallet_internal_error, "No signatures found in multisig tx"); - tools::wallet2::tx_construction_data &sd = ptx.construction_data; - LOG_PRINT_L1(" " << (n+1) << ": " << sd.sources.size() << " inputs, mixin " << (sd.sources[0].outputs.size()-1) << + const tools::wallet2::tx_construction_data &sd = ptx.construction_data; + LOG_PRINT_L1(" " << (n+1) << ": " << sd.sources.size() << " inputs, ring size " << (sd.sources[0].outputs.size()) << ", signed by " << exported_txs.m_signers.size() << "/" << m_multisig_threshold); - cryptonote::transaction tx; - rct::multisig_out msout = ptx.multisig_sigs.front().msout; - auto sources = sd.sources; - rct::RCTConfig rct_config = sd.rct_config; - bool shuffle_outs = false; - bool r = cryptonote::construct_tx_with_tx_key(m_account.get_keys(), m_subaddresses, sources, sd.splitted_dsts, ptx.change_dts.addr, sd.extra, tx, sd.unlock_time, ptx.tx_key, ptx.additional_tx_keys, sd.use_rct, rct_config, &msout, shuffle_outs, sd.use_view_tags); - THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sd.sources, sd.splitted_dsts, sd.unlock_time, m_nettype); - - THROW_WALLET_EXCEPTION_IF(get_transaction_prefix_hash (tx) != get_transaction_prefix_hash(ptx.tx), - error::wallet_internal_error, "Transaction prefix does not match data"); - // Tests passed, sign - std::vector<unsigned int> indices; - for (const auto &source: sources) - indices.push_back(source.real_output); + // reconstruct the partially-signed transaction attempt to verify we are signing something that at least looks like a transaction + // note: the caller should further verify that the tx details are acceptable (inputs/outputs/memos/tx type) + multisig::signing::tx_builder_ringct_t multisig_tx_builder; + THROW_WALLET_EXCEPTION_IF( + not multisig_tx_builder.init( + m_account.get_keys(), + ptx.construction_data.extra, + ptx.construction_data.unlock_time, + ptx.construction_data.subaddr_account, + ptx.construction_data.subaddr_indices, + ptx.construction_data.sources, + ptx.construction_data.splitted_dsts, + ptx.construction_data.change_dts, + ptx.construction_data.rct_config, + ptx.construction_data.use_rct, + true, //true = we are reconstructing the tx (it was first constructed by the tx proposer) + ptx.tx_key, + ptx.additional_tx_keys, + ptx.multisig_tx_key_entropy, + ptx.tx + ), + error::wallet_internal_error, + "error: multisig::signing::tx_builder_ringct_t::init" + ); + // go through each signing attempt for this transaction (each signing attempt corresponds to some subgroup of signers + // of size 'threshold') for (auto &sig: ptx.multisig_sigs) { + // skip this partial tx if it's intended for a subgroup of signers that doesn't include the local signer + // note: this check can only weed out signers who provided multisig_infos to the multisig tx proposer's + // (initial author's) last call to import_multisig() before making this tx proposal; all other signers + // will encounter a 'need to export multisig' wallet error in get_multisig_k() below + // note2: the 'need to export multisig' wallet error can also appear if a bad/buggy tx proposer adds duplicate + // 'used_L' to the set of tx attempts, or if two different tx proposals use the same 'used_L' values and the + // local signer calls this function on both of them if (sig.ignore.find(local_signer) == sig.ignore.end()) { - ptx.tx.rct_signatures = sig.sigs; - - rct::keyV k; + rct::keyM local_nonces_k(sd.selected_transfers.size(), rct::keyV(multisig::signing::kAlphaComponents)); rct::key skey = rct::zero(); - auto wiper = epee::misc_utils::create_scope_leave_handler([&](){ memwipe(k.data(), k.size() * sizeof(k[0])); memwipe(&skey, sizeof(skey)); }); - - for (size_t idx: sd.selected_transfers) - k.push_back(get_multisig_k(idx, sig.used_L)); + auto wiper = epee::misc_utils::create_scope_leave_handler([&]{ + for (auto& e: local_nonces_k) + memwipe(e.data(), e.size() * sizeof(rct::key)); + memwipe(&skey, sizeof(rct::key)); + }); + + // get local signer's nonces for this transaction attempt's inputs + // note: whoever created 'exported_txs' has full power to match proposed tx inputs (selected_transfers) + // with the public nonces of the multisig signers who call this function (via 'used_L' as identifiers), however + // the local signer will only use a given nonce exactly once (even if a used_L is repeated) + for (std::size_t i = 0; i < local_nonces_k.size(); ++i) { + for (std::size_t j = 0; j < multisig::signing::kAlphaComponents; ++j) { + get_multisig_k(sd.selected_transfers[i], sig.used_L, local_nonces_k[i][j]); + } + } - for (const auto &msk: get_account().get_multisig_keys()) + // round-robin signing: sign with all local multisig key shares that other signers have not signed with yet + for (const auto &multisig_skey: get_account().get_multisig_keys()) { - crypto::public_key pmsk = get_multisig_signing_public_key(msk); + crypto::public_key multisig_pkey = get_multisig_signing_public_key(multisig_skey); - if (sig.signing_keys.find(pmsk) == sig.signing_keys.end()) + if (sig.signing_keys.find(multisig_pkey) == sig.signing_keys.end()) { - sc_add(skey.bytes, skey.bytes, rct::sk2rct(msk).bytes); - sig.signing_keys.insert(pmsk); + sc_add(skey.bytes, skey.bytes, rct::sk2rct(multisig_skey).bytes); + sig.signing_keys.insert(multisig_pkey); } } - THROW_WALLET_EXCEPTION_IF(!rct::signMultisig(ptx.tx.rct_signatures, indices, k, sig.msout, skey), - error::wallet_internal_error, "Failed signing, transaction likely malformed"); - sig.sigs = ptx.tx.rct_signatures; + THROW_WALLET_EXCEPTION_IF( + not multisig_tx_builder.next_partial_sign(sig.total_alpha_G, sig.total_alpha_H, local_nonces_k, skey, sig.c_0, sig.s), + error::wallet_internal_error, + "error: multisig::signing::tx_builder_ringct_t::next_partial_sign" + ); } } const bool is_last = exported_txs.m_signers.size() + 1 >= m_multisig_threshold; if (is_last) { - // when the last signature on a multisig tx is made, we select the right - // signature to plug into the final tx + // if there are signatures from enough signers (assuming the local signer signed 1+ tx attempts), find the tx + // attempt with a full set of signatures so this tx can be finalized bool found = false; for (const auto &sig: ptx.multisig_sigs) { if (sig.ignore.find(local_signer) == sig.ignore.end() && !keys_intersect(sig.ignore, exported_txs.m_signers)) { THROW_WALLET_EXCEPTION_IF(found, error::wallet_internal_error, "More than one transaction is final"); - ptx.tx.rct_signatures = sig.sigs; + THROW_WALLET_EXCEPTION_IF( + not multisig_tx_builder.finalize_tx(ptx.construction_data.sources, sig.c_0, sig.s, ptx.tx), + error::wallet_internal_error, + "error: multisig::signing::tx_builder_ringct_t::finalize_tx" + ); found = true; } } THROW_WALLET_EXCEPTION_IF(!found, error::wallet_internal_error, - "Final signed transaction not found: this transaction was likely made without our export data, so we cannot sign it"); + "Unable to finalize the transaction: the ignore sets for these tx attempts seem to be malformed."); const crypto::hash txid = get_transaction_hash(ptx.tx); if (store_tx_info()) { @@ -7229,7 +7246,8 @@ bool wallet2::sign_multisig_tx(multisig_tx_set &exported_txs, std::vector<crypto } } - // txes generated, get rid of used k values + // signatures generated, get rid of any unused k values (must do export_multisig() to make more tx attempts with the + // inputs in the transactions worked on here) for (size_t n = 0; n < exported_txs.m_ptx.size(); ++n) for (size_t idx: exported_txs.m_ptx[n].construction_data.selected_transfers) memwipe(m_transfers[idx].m_multisig_k.data(), m_transfers[idx].m_multisig_k.size() * sizeof(m_transfers[idx].m_multisig_k[0])); @@ -8046,8 +8064,13 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> has_rct = true; max_rct_index = std::max(max_rct_index, m_transfers[idx].m_global_output_index); } - const bool has_rct_distribution = has_rct && (!rct_offsets.empty() || get_rct_distribution(rct_start_height, rct_offsets)); - if (has_rct_distribution) + + if (has_rct && rct_offsets.empty()) { + THROW_WALLET_EXCEPTION_IF(!get_rct_distribution(rct_start_height, rct_offsets), + error::get_output_distribution, "Could not obtain output distribution."); + } + + if (has_rct) { // check we're clear enough of rct start, to avoid corner cases below THROW_WALLET_EXCEPTION_IF(rct_offsets.size() <= CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE, @@ -8059,11 +8082,11 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> // get histogram for the amounts we need cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::request req_t = AUTO_VAL_INIT(req_t); cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::response resp_t = AUTO_VAL_INIT(resp_t); - // request histogram for all outputs, except 0 if we have the rct distribution + // request histogram for all pre-rct outputs req_t.amounts.reserve(selected_transfers.size()); for(size_t idx: selected_transfers) - if (!m_transfers[idx].is_rct() || !has_rct_distribution) - req_t.amounts.push_back(m_transfers[idx].is_rct() ? 0 : m_transfers[idx].amount()); + if (!m_transfers[idx].is_rct()) + req_t.amounts.push_back(m_transfers[idx].amount()); if (!req_t.amounts.empty()) { std::sort(req_t.amounts.begin(), req_t.amounts.end()); @@ -8163,7 +8186,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> COMMAND_RPC_GET_OUTPUTS_BIN::response daemon_resp = AUTO_VAL_INIT(daemon_resp); std::unique_ptr<gamma_picker> gamma; - if (has_rct_distribution) + if (has_rct) gamma.reset(new gamma_picker(rct_offsets)); size_t num_selected_transfers = 0; @@ -8178,7 +8201,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> // request more for rct in base recent (locked) coinbases are picked, since they're locked for longer size_t requested_outputs_count = base_requested_outputs_count + (td.is_rct() ? CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE : 0); size_t start = req.outputs.size(); - bool use_histogram = amount != 0 || !has_rct_distribution; + bool use_histogram = amount != 0; const bool output_is_pre_fork = td.m_block_height < segregation_fork_height; uint64_t num_outs = 0, num_recent_outs = 0; @@ -8365,7 +8388,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> uint64_t i; const char *type = ""; - if (amount == 0 && has_rct_distribution) + if (amount == 0) { THROW_WALLET_EXCEPTION_IF(!gamma, error::wallet_internal_error, "No gamma picker"); // gamma distribution @@ -8527,7 +8550,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> break; } } - bool use_histogram = amount != 0 || !has_rct_distribution; + bool use_histogram = amount != 0; if (!use_histogram) num_outs = rct_offsets[rct_offsets.size() - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE]; @@ -8755,9 +8778,8 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent crypto::secret_key tx_key; std::vector<crypto::secret_key> additional_tx_keys; - rct::multisig_out msout; LOG_PRINT_L2("constructing tx"); - bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sources, splitted_dsts, change_dts.addr, extra, tx, unlock_time, tx_key, additional_tx_keys, false, {}, m_multisig ? &msout : NULL, use_view_tags); + bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sources, splitted_dsts, change_dts.addr, extra, tx, unlock_time, tx_key, additional_tx_keys, false, {}, use_view_tags); LOG_PRINT_L2("constructed tx, r="<<r); THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, splitted_dsts, unlock_time, m_nettype); THROW_WALLET_EXCEPTION_IF(upper_transaction_weight_limit <= get_transaction_weight(tx), error::tx_too_big, tx, upper_transaction_weight_limit); @@ -8839,6 +8861,10 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry // At this step we need to define set of participants available for signature, // i.e. those of them who exchanged with multisig info's + // note: The oldest unspent owned output's multisig info (in m_transfers) will contain the most recent result of + // 'import_multisig()', which means only 'fresh' multisig infos (public nonces) will be used to make tx attempts. + // - If a signer's info was missing from the latest call to 'import_multisig()', then they won't be able to participate! + // - If a newly-acquired output doesn't have enouch nonces from multisig infos, then it can't be spent! for (const crypto::public_key &signer: m_multisig_signers) { if (signer == local_signer) @@ -8906,7 +8932,6 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry LOG_PRINT_L2("preparing outputs"); size_t i = 0, out_index = 0; std::vector<cryptonote::tx_source_entry> sources; - std::unordered_set<rct::key> used_L; for(size_t idx: selected_transfers) { sources.resize(sources.size()+1); @@ -8949,10 +8974,8 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry src.real_output_in_tx_index = td.m_internal_output_index; src.mask = td.m_mask; if (m_multisig) - { - auto ignore_set = ignore_sets.empty() ? std::unordered_set<crypto::public_key>() : ignore_sets.front(); - src.multisig_kLRki = get_multisig_composite_kLRki(idx, ignore_set, used_L, used_L); - } + // note: multisig_kLRki is a legacy struct, currently only used as a key image shuttle into the multisig tx builder + src.multisig_kLRki = {.k = {}, .L = {}, .R = {}, .ki = rct::ki2rct(td.m_key_image)}; else src.multisig_kLRki = rct::multisig_kLRki({rct::zero(), rct::zero(), rct::zero(), rct::zero()}); detail::print_source_entry(src); @@ -8989,12 +9012,43 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry crypto::secret_key tx_key; std::vector<crypto::secret_key> additional_tx_keys; - rct::multisig_out msout; + crypto::secret_key multisig_tx_key_entropy; LOG_PRINT_L2("constructing tx"); auto sources_copy = sources; - bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sources, splitted_dsts, change_dts.addr, extra, tx, unlock_time, tx_key, additional_tx_keys, true, rct_config, m_multisig ? &msout : NULL, use_view_tags); - LOG_PRINT_L2("constructed tx, r="<<r); - THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, dsts, unlock_time, m_nettype); + multisig::signing::tx_builder_ringct_t multisig_tx_builder; + if (m_multisig) { + // prepare the core part of a multisig tx (many tx attempts for different signer groups can be spun off this core piece) + std::set<std::uint32_t> subaddr_minor_indices; + for (size_t idx: selected_transfers) { + subaddr_minor_indices.insert(m_transfers[idx].m_subaddr_index.minor); + } + THROW_WALLET_EXCEPTION_IF( + not multisig_tx_builder.init(m_account.get_keys(), + extra, + unlock_time, + subaddr_account, + subaddr_minor_indices, + sources, + splitted_dsts, + change_dts, + rct_config, + true, + false, + tx_key, + additional_tx_keys, + multisig_tx_key_entropy, + tx + ), + error::wallet_internal_error, + "error: multisig::signing::tx_builder_ringct_t::init" + ); + } + else { + // make a normal tx + bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sources, splitted_dsts, change_dts.addr, extra, tx, unlock_time, tx_key, additional_tx_keys, true, rct_config, use_view_tags); + LOG_PRINT_L2("constructed tx, r="<<r); + THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, dsts, unlock_time, m_nettype); + } THROW_WALLET_EXCEPTION_IF(upper_transaction_weight_limit <= get_transaction_weight(tx), error::tx_too_big, tx, upper_transaction_weight_limit); // work out the permutation done on sources @@ -9012,42 +9066,77 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry THROW_WALLET_EXCEPTION_IF(ins_order.size() != sources.size(), error::wallet_internal_error, "Failed to work out sources permutation"); std::vector<tools::wallet2::multisig_sig> multisig_sigs; - if (m_multisig) - { - auto ignore = ignore_sets.empty() ? std::unordered_set<crypto::public_key>() : ignore_sets.front(); - multisig_sigs.push_back({tx.rct_signatures, ignore, used_L, std::unordered_set<crypto::public_key>(), msout}); - - if (m_multisig_threshold < m_multisig_signers.size()) - { - const crypto::hash prefix_hash = cryptonote::get_transaction_prefix_hash(tx); - - // create the other versions, one for every other participant (the first one's already done above) - for (size_t ignore_index = 1; ignore_index < ignore_sets.size(); ++ignore_index) - { - std::unordered_set<rct::key> new_used_L; - size_t src_idx = 0; - THROW_WALLET_EXCEPTION_IF(selected_transfers.size() != sources.size(), error::wallet_internal_error, "mismatched selected_transfers and sources sixes"); - for(size_t idx: selected_transfers) - { - cryptonote::tx_source_entry& src = sources_copy[src_idx]; - src.multisig_kLRki = get_multisig_composite_kLRki(idx, ignore_sets[ignore_index], used_L, new_used_L); - ++src_idx; + if (m_multisig) { + if (ignore_sets.empty()) + ignore_sets.emplace_back(); + const std::size_t num_multisig_attempts = ignore_sets.size(); + multisig_sigs.resize(num_multisig_attempts); + std::unordered_set<rct::key> all_used_L; + std::unordered_set<crypto::public_key> signing_keys; + for (const crypto::secret_key &multisig_skey: get_account().get_multisig_keys()) + signing_keys.insert(get_multisig_signing_public_key(multisig_skey)); + const std::size_t num_sources = sources.size(); + const std::size_t num_alpha_components = multisig::signing::kAlphaComponents; + + // initiate a multisig tx attempt for each unique set of signers that + // a) includes the local signer + // b) includes other signers who most recently sent the local signer LR public nonces via 'export_multisig() -> import_multisig()' + for (std::size_t i = 0; i < num_multisig_attempts; ++i) { + multisig_sig& sig = multisig_sigs[i]; + sig.total_alpha_G.resize(num_sources, rct::keyV(num_alpha_components)); + sig.total_alpha_H.resize(num_sources, rct::keyV(num_alpha_components)); + sig.s.resize(num_sources); + sig.c_0.resize(num_sources); + + // for each tx input, get public musig2-style nonces from + // a) temporary local-generated private nonces (used to make the local partial signatures on each tx attempt) + // b) other signers' public nonces, sent to the local signer via 'export_multisig() -> import_multisig()' + // - WARNING: If two multisig players initiate multisig tx attempts separately, but spend the same funds (and hence rely on the same LR public nonces), + // then if two signers partially sign different tx attempt sets, then all attempts that require both signers will become garbage, + // because LR nonces can only be used for one tx attempt. + for (std::size_t j = 0; j < num_sources; ++j) { + rct::keyV alpha(num_alpha_components); + auto alpha_wiper = epee::misc_utils::create_scope_leave_handler([&]{ + memwipe(static_cast<rct::key *>(alpha.data()), alpha.size() * sizeof(rct::key)); + }); + for (std::size_t m = 0; m < num_alpha_components; ++m) { + const rct::multisig_kLRki kLRki = get_multisig_composite_kLRki( + selected_transfers[ins_order[j]], + ignore_sets[i], + all_used_L, //collect all public L nonces used by this tx proposal (set of tx attempts) to avoid duplicates + sig.used_L //record the public L nonces used by this tx input to this tx attempt, for coordination with other signers + ); + alpha[m] = kLRki.k; + sig.total_alpha_G[j][m] = kLRki.L; + sig.total_alpha_H[j][m] = kLRki.R; } - LOG_PRINT_L2("Creating supplementary multisig transaction"); - cryptonote::transaction ms_tx; - auto sources_copy_copy = sources_copy; - bool shuffle_outs = false; - bool r = cryptonote::construct_tx_with_tx_key(m_account.get_keys(), m_subaddresses, sources_copy_copy, splitted_dsts, change_dts.addr, extra, ms_tx, unlock_time,tx_key, additional_tx_keys, true, rct_config, &msout, shuffle_outs, use_view_tags); - LOG_PRINT_L2("constructed tx, r="<<r); - THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, splitted_dsts, unlock_time, m_nettype); - THROW_WALLET_EXCEPTION_IF(upper_transaction_weight_limit <= get_transaction_weight(tx), error::tx_too_big, tx, upper_transaction_weight_limit); - THROW_WALLET_EXCEPTION_IF(cryptonote::get_transaction_prefix_hash(ms_tx) != prefix_hash, error::wallet_internal_error, "Multisig txes do not share prefix"); - multisig_sigs.push_back({ms_tx.rct_signatures, ignore_sets[ignore_index], new_used_L, std::unordered_set<crypto::public_key>(), msout}); - - ms_tx.rct_signatures = tx.rct_signatures; - THROW_WALLET_EXCEPTION_IF(cryptonote::get_transaction_hash(ms_tx) != cryptonote::get_transaction_hash(tx), error::wallet_internal_error, "Multisig txes differ by more than the signatures"); - } + // local signer: initial partial signature on this tx input for this tx attempt + // note: sign here with sender-receiver secret component, subaddress component, and ALL of the local signer's multisig key shares + // (this ultimately occurs deep in generate_key_image_helper_precomp()) + THROW_WALLET_EXCEPTION_IF( + not multisig_tx_builder.first_partial_sign(j, sig.total_alpha_G[j], sig.total_alpha_H[j], alpha, sig.c_0[j], sig.s[j]), + error::wallet_internal_error, + "error: multisig::signing::tx_builder_ringct_t::first_partial_sign" + ); + } + + // note: record the ignore set so when other signers go to add their signatures (sign_multisig_tx()), they + // can skip this tx attempt if they aren't supposed to sign it; this only works for signers who provided + // multisig_infos to the last 'import_multisig()' call by the local signer, all 'other signers' will encounter + // a 'need to export multisig_info' wallet error if they try to sign this partial tx, which means if they want to sign a tx + // they need to export_multisig() -> send to the local signer -> local signer calls import_multisig() with fresh + // multisig_infos from all signers -> local signer makes completely new tx attempts (or a different signer makes tx attempts) + sig.ignore = ignore_sets[i]; + sig.signing_keys = signing_keys; //the local signer signed with ALL of their multisig key shares, record their pubkeys for reference by other signers + } + if (m_multisig_threshold <= 1) { + // local signer: finish signing the tx inputs if we are the only signer (ignore all but the first 'attempt') + THROW_WALLET_EXCEPTION_IF( + not multisig_tx_builder.finalize_tx(sources, multisig_sigs[0].c_0, multisig_sigs[0].s, tx), + error::wallet_internal_error, + "error: multisig::signing::tx_builder_ringct_t::finalize_tx" + ); } } @@ -9074,6 +9163,7 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry ptx.additional_tx_keys = additional_tx_keys; ptx.dests = dsts; ptx.multisig_sigs = multisig_sigs; + ptx.multisig_tx_key_entropy = multisig_tx_key_entropy; ptx.construction_data.sources = sources_copy; ptx.construction_data.change_dts = change_dts; ptx.construction_data.splitted_dsts = splitted_dsts; @@ -12087,7 +12177,8 @@ bool wallet2::check_reserve_proof(const cryptonote::account_public_address &addr crypto::key_derivation derivation; THROW_WALLET_EXCEPTION_IF(!crypto::generate_key_derivation(proof.shared_secret, rct::rct2sk(rct::I), derivation), error::wallet_internal_error, "Failed to generate key derivation"); crypto::public_key subaddr_spendkey; - crypto::derive_subaddress_public_key(output_public_key, derivation, proof.index_in_tx, subaddr_spendkey); + THROW_WALLET_EXCEPTION_IF(!crypto::derive_subaddress_public_key(output_public_key, derivation, proof.index_in_tx, subaddr_spendkey), + error::wallet_internal_error, "Failed to derive subaddress public key"); THROW_WALLET_EXCEPTION_IF(subaddr_spendkeys.count(subaddr_spendkey) == 0, error::wallet_internal_error, "The address doesn't seem to have received the fund"); @@ -13333,19 +13424,26 @@ crypto::public_key wallet2::get_multisig_signing_public_key(size_t idx) const return get_multisig_signing_public_key(get_account().get_multisig_keys()[idx]); } //---------------------------------------------------------------------------------------------------- -rct::key wallet2::get_multisig_k(size_t idx, const std::unordered_set<rct::key> &used_L) const +void wallet2::get_multisig_k(size_t idx, const std::unordered_set<rct::key> &used_L, rct::key &nonce) { CHECK_AND_ASSERT_THROW_MES(m_multisig, "Wallet is not multisig"); CHECK_AND_ASSERT_THROW_MES(idx < m_transfers.size(), "idx out of range"); - for (const auto &k: m_transfers[idx].m_multisig_k) + for (auto &k: m_transfers[idx].m_multisig_k) { + if (k == rct::zero()) + continue; + + // decide whether or not to return a nonce just based on if its pubkey 'L = k*G' is attached to the transfer 'idx' rct::key L; rct::scalarmultBase(L, k); if (used_L.find(L) != used_L.end()) - return k; + { + nonce = k; + memwipe(static_cast<rct::key *>(&k), sizeof(rct::key)); //CRITICAL: a nonce may only be used once! + return; + } } THROW_WALLET_EXCEPTION(tools::error::multisig_export_needed); - return rct::zero(); } //---------------------------------------------------------------------------------------------------- rct::multisig_kLRki wallet2::get_multisig_kLRki(size_t n, const rct::key &k) const @@ -13411,15 +13509,23 @@ cryptonote::blobdata wallet2::export_multisig() const crypto::public_key signer = get_multisig_signer_public_key(); + // For each transfer (output owned by the multisig wallet): + // 1) Record the output's partial key image (from the local signer), so other signers can assemble the output's full key image. + // 2) Prepare enough signing nonces for one signing attempt with each possible combination of 'threshold' signers + // from the multisig group (only groups that include the local signer). + // - Calling this function will reset any nonces recorded by the previous call to this function. Doing so will + // invalidate any in-progress signing attempts that rely on the previous output of this function. info.resize(m_transfers.size()); for (size_t n = 0; n < m_transfers.size(); ++n) { transfer_details &td = m_transfers[n]; crypto::key_image ki; - memwipe(td.m_multisig_k.data(), td.m_multisig_k.size() * sizeof(td.m_multisig_k[0])); + if (td.m_multisig_k.size()) + memwipe(td.m_multisig_k.data(), td.m_multisig_k.size() * sizeof(td.m_multisig_k[0])); info[n].m_LR.clear(); info[n].m_partial_key_images.clear(); + // record the partial key images for (size_t m = 0; m < get_account().get_multisig_keys().size(); ++m) { // we want to export the partial key image, not the full one, so we can't use td.m_key_image @@ -13432,6 +13538,15 @@ cryptonote::blobdata wallet2::export_multisig() // if we have 2/4 wallet with signers: A, B, C, D and A is a transaction creator it will need to pick up 1 signer from 3 wallets left. // That means counting combinations for excluding 2-of-3 wallets (k = total signers count - threshold, n = total signers count - 1). size_t nlr = tools::combinations_count(m_multisig_signers.size() - m_multisig_threshold, m_multisig_signers.size() - 1); + + // 'td.m_multisig_k' is an expansion of [{alpha_0, alpha_1, ...}, {alpha_0, alpha_1, ...}, {alpha_0, alpha_1, ...}], + // - A '{alpha_0, alpha_1, ...}' tuple contains a set of 'kAlphaComponents' nonces, which can be used for one + // signing attempt. Each output will gain 'nlr' tuples, so that every signing group can make one signing attempt. + // - All tuples are always cleared after 1+ of them is used to sign a tx attempt (in sign_multisig_tx()), so + // in practice, a call to this function only allows _one_ multisig signing cycle for each output (which can + // include signing attempts for multiple signer groups). + nlr *= multisig::signing::kAlphaComponents; + for (size_t m = 0; m < nlr; ++m) { td.m_multisig_k.push_back(rct::skGen()); @@ -14019,43 +14134,6 @@ uint64_t wallet2::get_segregation_fork_height() const if (m_segregation_height > 0) return m_segregation_height; - if (m_use_dns && !m_offline) - { - // All four MoneroPulse domains have DNSSEC on and valid - static const std::vector<std::string> dns_urls = { - "segheights.moneropulse.org", - "segheights.moneropulse.net", - "segheights.moneropulse.co", - "segheights.moneropulse.se" - }; - - const uint64_t current_height = get_blockchain_current_height(); - uint64_t best_diff = std::numeric_limits<uint64_t>::max(), best_height = 0; - std::vector<std::string> records; - if (tools::dns_utils::load_txt_records_from_dns(records, dns_urls)) - { - for (const auto& record : records) - { - std::vector<std::string> fields; - boost::split(fields, record, boost::is_any_of(":")); - if (fields.size() != 2) - continue; - uint64_t height; - if (!string_tools::get_xtype_from_string(height, fields[1])) - continue; - - MINFO("Found segregation height via DNS: " << fields[0] << " fork height at " << height); - uint64_t diff = height > current_height ? height - current_height : current_height - height; - if (diff < best_diff) - { - best_diff = diff; - best_height = height; - } - } - if (best_height) - return best_height; - } - } return SEGREGATION_FORK_HEIGHT; } //---------------------------------------------------------------------------------------------------- diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index e9e5bc95e..16e898ad8 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -137,7 +137,7 @@ private: public: // Full wallet callbacks virtual void on_new_block(uint64_t height, const cryptonote::block& block) {} - virtual void on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index, bool is_change, uint64_t unlock_time) {} + virtual void on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, uint64_t burnt, const cryptonote::subaddress_index& subaddr_index, bool is_change, uint64_t unlock_time) {} virtual void on_unconfirmed_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index) {} virtual void on_money_spent(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& in_tx, uint64_t amount, const cryptonote::transaction& spend_tx, const cryptonote::subaddress_index& subaddr_index) {} virtual void on_skip_transaction(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx) {} @@ -349,6 +349,8 @@ private: uint64_t amount() const { return m_amount; } const crypto::public_key get_public_key() const { crypto::public_key output_public_key; + THROW_WALLET_EXCEPTION_IF(m_tx.vout.size() <= m_internal_output_index, + error::wallet_internal_error, "Too few outputs, outputs may be corrupted"); THROW_WALLET_EXCEPTION_IF(!get_output_public_key(m_tx.vout[m_internal_output_index], output_public_key), error::wallet_internal_error, "Unable to get output public key from output"); return output_public_key; @@ -492,6 +494,7 @@ private: struct confirmed_transfer_details { + cryptonote::transaction_prefix m_tx; uint64_t m_amount_in; uint64_t m_amount_out; uint64_t m_change; @@ -506,10 +509,12 @@ private: confirmed_transfer_details(): m_amount_in(0), m_amount_out(0), m_change((uint64_t)-1), m_block_height(0), m_payment_id(crypto::null_hash), m_timestamp(0), m_unlock_time(0), m_subaddr_account((uint32_t)-1) {} confirmed_transfer_details(const unconfirmed_transfer_details &utd, uint64_t height): - m_amount_in(utd.m_amount_in), m_amount_out(utd.m_amount_out), m_change(utd.m_change), m_block_height(height), m_dests(utd.m_dests), m_payment_id(utd.m_payment_id), m_timestamp(utd.m_timestamp), m_unlock_time(utd.m_tx.unlock_time), m_subaddr_account(utd.m_subaddr_account), m_subaddr_indices(utd.m_subaddr_indices), m_rings(utd.m_rings) {} + m_tx(utd.m_tx), m_amount_in(utd.m_amount_in), m_amount_out(utd.m_amount_out), m_change(utd.m_change), m_block_height(height), m_dests(utd.m_dests), m_payment_id(utd.m_payment_id), m_timestamp(utd.m_timestamp), m_unlock_time(utd.m_tx.unlock_time), m_subaddr_account(utd.m_subaddr_account), m_subaddr_indices(utd.m_subaddr_indices), m_rings(utd.m_rings) {} BEGIN_SERIALIZE_OBJECT() - VERSION_FIELD(0) + VERSION_FIELD(1) + if (version >= 1) + FIELD(m_tx) VARINT_FIELD(m_amount_in) VARINT_FIELD(m_amount_out) VARINT_FIELD(m_change) @@ -594,13 +599,24 @@ private: std::unordered_set<crypto::public_key> signing_keys; rct::multisig_out msout; + rct::keyM total_alpha_G; + rct::keyM total_alpha_H; + rct::keyV c_0; + rct::keyV s; + BEGIN_SERIALIZE_OBJECT() - VERSION_FIELD(0) + VERSION_FIELD(1) + if (version < 1) + return false; FIELD(sigs) FIELD(ignore) FIELD(used_L) FIELD(signing_keys) FIELD(msout) + FIELD(total_alpha_G) + FIELD(total_alpha_H) + FIELD(c_0) + FIELD(s) END_SERIALIZE() }; @@ -619,10 +635,12 @@ private: std::vector<crypto::secret_key> additional_tx_keys; std::vector<cryptonote::tx_destination_entry> dests; std::vector<multisig_sig> multisig_sigs; + crypto::secret_key multisig_tx_key_entropy; tx_construction_data construction_data; BEGIN_SERIALIZE_OBJECT() + VERSION_FIELD(1) FIELD(tx) FIELD(dust) FIELD(fee) @@ -635,6 +653,12 @@ private: FIELD(dests) FIELD(construction_data) FIELD(multisig_sigs) + if (version < 1) + { + multisig_tx_key_entropy = crypto::null_skey; + return true; + } + FIELD(multisig_tx_key_entropy) END_SERIALIZE() }; @@ -1297,6 +1321,8 @@ private: void set_rpc_client_secret_key(const crypto::secret_key &key) { m_rpc_client_secret_key = key; m_node_rpc_proxy.set_client_secret_key(key); } uint64_t credits_target() const { return m_credits_target; } void credits_target(uint64_t threshold) { m_credits_target = threshold; } + bool is_multisig_enabled() const { return m_enable_multisig; } + void enable_multisig(bool enable) { m_enable_multisig = enable; } bool get_tx_key_cached(const crypto::hash &txid, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys) const; void set_tx_key(const crypto::hash &txid, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, const boost::optional<cryptonote::account_public_address> &single_destination_subaddress = boost::none); @@ -1677,7 +1703,7 @@ private: crypto::key_image get_multisig_composite_key_image(size_t n) const; rct::multisig_kLRki get_multisig_composite_kLRki(size_t n, const std::unordered_set<crypto::public_key> &ignore_set, std::unordered_set<rct::key> &used_L, std::unordered_set<rct::key> &new_used_L) const; rct::multisig_kLRki get_multisig_kLRki(size_t n, const rct::key &k) const; - rct::key get_multisig_k(size_t idx, const std::unordered_set<rct::key> &used_L) const; + void get_multisig_k(size_t idx, const std::unordered_set<rct::key> &used_L, rct::key &nonce); void update_multisig_rescan_info(const std::vector<std::vector<rct::key>> &multisig_k, const std::vector<std::vector<tools::wallet2::multisig_info>> &info, size_t n); bool add_rings(const crypto::chacha_key &key, const cryptonote::transaction_prefix &tx); bool add_rings(const cryptonote::transaction_prefix &tx); @@ -1813,6 +1839,7 @@ private: crypto::secret_key m_rpc_client_secret_key; rpc_payment_state_t m_rpc_payment_state; uint64_t m_credits_target; + bool m_enable_multisig; // Aux transaction data from device serializable_unordered_map<crypto::hash, std::string> m_tx_device; @@ -1875,7 +1902,7 @@ BOOST_CLASS_VERSION(tools::wallet2::unsigned_tx_set, 0) BOOST_CLASS_VERSION(tools::wallet2::signed_tx_set, 1) BOOST_CLASS_VERSION(tools::wallet2::tx_construction_data, 4) BOOST_CLASS_VERSION(tools::wallet2::pending_tx, 3) -BOOST_CLASS_VERSION(tools::wallet2::multisig_sig, 0) +BOOST_CLASS_VERSION(tools::wallet2::multisig_sig, 1) namespace boost { @@ -2313,6 +2340,12 @@ namespace boost a & x.used_L; a & x.signing_keys; a & x.msout; + if (ver < 1) + return; + a & x.total_alpha_G; + a & x.total_alpha_H; + a & x.c_0; + a & x.s; } template <class Archive> diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index 57baf428f..7ec5fc7a1 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -61,6 +61,17 @@ using namespace epee; #define DEFAULT_AUTO_REFRESH_PERIOD 20 // seconds +#define CHECK_MULTISIG_ENABLED() \ + do \ + { \ + if (m_wallet->multisig() && !m_wallet->is_multisig_enabled()) \ + { \ + er.code = WALLET_RPC_ERROR_CODE_DISABLED; \ + er.message = "This wallet is multisig, and multisig is disabled. Multisig is an experimental feature and may have bugs. Things that could go wrong include: funds sent to a multisig wallet can't be spent at all, can only be spent with the participation of a malicious group member, or can be stolen by a malicious group member. You can enable it by running this once in monero-wallet-cli: set enable-multisig-experimental 1"; \ + return false; \ + } \ + } while(0) + namespace { const command_line::arg_descriptor<std::string, true> arg_rpc_bind_port = {"rpc-bind-port", "Sets bind port for server"}; @@ -1057,6 +1068,8 @@ namespace tools return false; } + CHECK_MULTISIG_ENABLED(); + // validate the transfer requested and populate dsts & extra if (!validate_transfer(req.destinations, req.payment_id, dsts, extra, true, er)) { @@ -1109,6 +1122,8 @@ namespace tools return false; } + CHECK_MULTISIG_ENABLED(); + // validate the transfer requested and populate dsts & extra; RPC_TRANSFER::request and RPC_TRANSFER_SPLIT::request are identical types. if (!validate_transfer(req.destinations, req.payment_id, dsts, extra, true, er)) { @@ -1163,6 +1178,8 @@ namespace tools return false; } + CHECK_MULTISIG_ENABLED(); + cryptonote::blobdata blob; if (!epee::string_tools::parse_hexstr_to_binbuff(req.unsigned_txset, blob)) { @@ -1511,6 +1528,8 @@ namespace tools return false; } + CHECK_MULTISIG_ENABLED(); + try { std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_unmixable_sweep_transactions(); @@ -1539,6 +1558,8 @@ namespace tools return false; } + CHECK_MULTISIG_ENABLED(); + // validate the transfer requested and populate dsts & extra std::list<wallet_rpc::transfer_destination> destination; destination.push_back(wallet_rpc::transfer_destination()); @@ -1604,6 +1625,8 @@ namespace tools return false; } + CHECK_MULTISIG_ENABLED(); + // validate the transfer requested and populate dsts & extra std::list<wallet_rpc::transfer_destination> destination; destination.push_back(wallet_rpc::transfer_destination()); @@ -3933,6 +3956,9 @@ namespace tools er.message = "This wallet is already multisig"; return false; } + if (req.enable_multisig_experimental) + m_wallet->enable_multisig(true); + CHECK_MULTISIG_ENABLED(); if (m_wallet->watch_only()) { er.code = WALLET_RPC_ERROR_CODE_WATCH_ONLY; @@ -3959,6 +3985,7 @@ namespace tools er.message = "This wallet is already multisig"; return false; } + CHECK_MULTISIG_ENABLED(); if (m_wallet->watch_only()) { er.code = WALLET_RPC_ERROR_CODE_WATCH_ONLY; @@ -4003,6 +4030,7 @@ namespace tools er.message = "This wallet is multisig, but not yet finalized"; return false; } + CHECK_MULTISIG_ENABLED(); cryptonote::blobdata info; try @@ -4044,6 +4072,7 @@ namespace tools er.message = "This wallet is multisig, but not yet finalized"; return false; } + CHECK_MULTISIG_ENABLED(); if (req.info.size() < threshold - 1) { @@ -4096,6 +4125,7 @@ namespace tools //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_finalize_multisig(const wallet_rpc::COMMAND_RPC_FINALIZE_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_FINALIZE_MULTISIG::response& res, epee::json_rpc::error& er, const connection_context *ctx) { + CHECK_MULTISIG_ENABLED(); return false; } //------------------------------------------------------------------------------------------------------------------------------ @@ -4123,6 +4153,7 @@ namespace tools er.message = "This wallet is multisig, and already finalized"; return false; } + CHECK_MULTISIG_ENABLED(); if (req.multisig_info.size() + 1 < total) { @@ -4172,6 +4203,7 @@ namespace tools er.message = "This wallet is multisig, but not yet finalized"; return false; } + CHECK_MULTISIG_ENABLED(); cryptonote::blobdata blob; if (!epee::string_tools::parse_hexstr_to_binbuff(req.tx_data_hex, blob)) @@ -4241,6 +4273,7 @@ namespace tools er.message = "This wallet is multisig, but not yet finalized"; return false; } + CHECK_MULTISIG_ENABLED(); cryptonote::blobdata blob; if (!epee::string_tools::parse_hexstr_to_binbuff(req.tx_data_hex, blob)) diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h index fe53e293f..ecfc8e673 100644 --- a/src/wallet/wallet_rpc_server_commands_defs.h +++ b/src/wallet/wallet_rpc_server_commands_defs.h @@ -2416,7 +2416,10 @@ namespace wallet_rpc { struct request_t { + bool enable_multisig_experimental; + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_OPT(enable_multisig_experimental, false) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; diff --git a/src/wallet/wallet_rpc_server_error_codes.h b/src/wallet/wallet_rpc_server_error_codes.h index 914939573..734229380 100644 --- a/src/wallet/wallet_rpc_server_error_codes.h +++ b/src/wallet/wallet_rpc_server_error_codes.h @@ -78,3 +78,4 @@ #define WALLET_RPC_ERROR_CODE_ATTRIBUTE_NOT_FOUND -45 #define WALLET_RPC_ERROR_CODE_ZERO_AMOUNT -46 #define WALLET_RPC_ERROR_CODE_INVALID_SIGNATURE_TYPE -47 +#define WALLET_RPC_ERROR_CODE_DISABLED -48 diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 5a3c49560..2cabb1ba5 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -78,6 +78,7 @@ file(COPY data/outputs data/unsigned_monero_tx data/signed_monero_tx + data/sha256sum DESTINATION data) if (CMAKE_BUILD_TYPE STREQUAL "fuzz" OR OSSFUZZ) diff --git a/tests/core_tests/chaingen.cpp b/tests/core_tests/chaingen.cpp index 61195c7b0..9bcae19d0 100644 --- a/tests/core_tests/chaingen.cpp +++ b/tests/core_tests/chaingen.cpp @@ -661,7 +661,7 @@ void block_tracker::process(const block* blk, const transaction * tx, size_t i) for (size_t j = 0; j < tx->vout.size(); ++j) { const tx_out &out = tx->vout[j]; - if (typeid(cryptonote::txout_to_key) != out.target.type()) { // out_to_key + if (typeid(cryptonote::txout_to_key) != out.target.type() && typeid(cryptonote::txout_to_tagged_key) != out.target.type()) { continue; } @@ -1076,7 +1076,7 @@ bool construct_tx_rct(const cryptonote::account_keys& sender_account_keys, std:: std::vector<crypto::secret_key> additional_tx_keys; std::vector<tx_destination_entry> destinations_copy = destinations; rct::RCTConfig rct_config = {range_proof_type, bp_version}; - return construct_tx_and_get_tx_key(sender_account_keys, subaddresses, sources, destinations_copy, change_addr, extra, tx, unlock_time, tx_key, additional_tx_keys, rct, rct_config, nullptr); + return construct_tx_and_get_tx_key(sender_account_keys, subaddresses, sources, destinations_copy, change_addr, extra, tx, unlock_time, tx_key, additional_tx_keys, rct, rct_config); } transaction construct_tx_with_fee(std::vector<test_event_entry>& events, const block& blk_head, diff --git a/tests/core_tests/multisig.cpp b/tests/core_tests/multisig.cpp index 3db3d4059..28b44d293 100644 --- a/tests/core_tests/multisig.cpp +++ b/tests/core_tests/multisig.cpp @@ -28,6 +28,11 @@ // // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers +#include "ringct/rctSigs.h" +#include "cryptonote_basic/cryptonote_basic.h" +#include "multisig/multisig.h" +#include "multisig/multisig_tx_builder_ringct.h" +#include "common/apply_permutation.h" #include "chaingen.h" #include "multisig.h" @@ -113,7 +118,7 @@ static bool make_multisig_accounts(std::vector<cryptonote::account_base> &accoun bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry>& events, size_t inputs, size_t mixin, uint64_t amount_paid, bool valid, - size_t threshold, size_t total, size_t creator, std::vector<size_t> signers, + size_t threshold, size_t total, size_t creator, std::vector<size_t> other_signers, const std::function<void(std::vector<tx_source_entry> &sources, std::vector<tx_destination_entry> &destinations)> &pre_tx, const std::function<void(transaction &tx)> &post_tx) const { @@ -122,30 +127,18 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry CHECK_AND_ASSERT_MES(total >= 2, false, "Bad scheme"); CHECK_AND_ASSERT_MES(threshold <= total, false, "Bad scheme"); -#ifdef NO_MULTISIG - CHECK_AND_ASSERT_MES(total <= 5, false, "Unsupported scheme"); -#endif CHECK_AND_ASSERT_MES(inputs >= 1 && inputs <= 8, false, "Inputs should between 1 and 8"); // given as 1 based for clarity --creator; - for (size_t &signer: signers) + for (size_t &signer: other_signers) --signer; CHECK_AND_ASSERT_MES(creator < total, false, "invalid creator"); - for (size_t signer: signers) + for (size_t signer: other_signers) CHECK_AND_ASSERT_MES(signer < total, false, "invalid signer"); -#ifdef NO_MULTISIG - GENERATE_ACCOUNT(acc0); - GENERATE_ACCOUNT(acc1); - GENERATE_ACCOUNT(acc2); - GENERATE_ACCOUNT(acc3); - GENERATE_ACCOUNT(acc4); - account_base miner_account[5] = {acc0, acc1, acc2, acc3, acc4}; -#else GENERATE_MULTISIG_ACCOUNT(miner_account, threshold, total); -#endif MAKE_GENESIS_BLOCK(events, blk_0, miner_account[creator], ts_start); @@ -193,14 +186,13 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry { tx_pub_key[n] = get_tx_pub_key_from_extra(blocks[n].miner_tx); MDEBUG("tx_pub_key: " << tx_pub_key); - output_pub_key[n] = boost::get<txout_to_key>(blocks[n].miner_tx.vout[0].target).key; + cryptonote::get_output_public_key(blocks[n].miner_tx.vout[0], output_pub_key[n]); MDEBUG("output_pub_key: " << output_pub_key); } std::unordered_map<crypto::public_key, cryptonote::subaddress_index> subaddresses; subaddresses[miner_account[0].get_keys().m_account_address.m_spend_public_key] = {0,0}; -#ifndef NO_MULTISIG // create k/L/R/ki for that output we're going to spend std::vector<std::vector<std::vector<crypto::secret_key>>> account_k(total); std::vector<std::vector<std::vector<crypto::public_key>>> account_L(total); @@ -213,6 +205,7 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry false, "Mismatched spend public keys"); size_t nlr = threshold < total ? threshold - 1 : 1; + nlr *= multisig::signing::kAlphaComponents; account_k[msidx].resize(inputs); account_L[msidx].resize(inputs); account_R[msidx].resize(inputs); @@ -226,9 +219,9 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry account_k[msidx][tdidx].push_back(rct::rct2sk(rct::skGen())); multisig::generate_multisig_LR(output_pub_key[tdidx], account_k[msidx][tdidx][n], account_L[msidx][tdidx][n], account_R[msidx][tdidx][n]); } - size_t numki = miner_account[msidx].get_multisig_keys().size(); - account_ki[msidx][tdidx].resize(numki); - for (size_t kiidx = 0; kiidx < numki; ++kiidx) + size_t num_account_partial_ki = miner_account[msidx].get_multisig_keys().size(); + account_ki[msidx][tdidx].resize(num_account_partial_ki); + for (size_t kiidx = 0; kiidx < num_account_partial_ki; ++kiidx) { r = multisig::generate_multisig_key_image(miner_account[msidx].get_keys(), kiidx, output_pub_key[tdidx], account_ki[msidx][tdidx][kiidx]); CHECK_AND_ASSERT_MES(r, false, "Failed to generate multisig export key image"); @@ -248,7 +241,6 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry MDEBUG("ki: " << ki); } } -#endif // create kLRki std::vector<rct::multisig_kLRki> kLRkis; @@ -257,34 +249,6 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry { kLRkis.push_back(rct::multisig_kLRki()); rct::multisig_kLRki &kLRki = kLRkis.back(); -#ifdef NO_MULTISIG - kLRki = {rct::zero(), rct::zero(), rct::zero(), rct::zero()}; -#else - kLRki.k = rct::sk2rct(account_k[creator][tdidx][0]); - kLRki.L = rct::pk2rct(account_L[creator][tdidx][0]); - kLRki.R = rct::pk2rct(account_R[creator][tdidx][0]); - MDEBUG("Starting with k " << kLRki.k); - MDEBUG("Starting with L " << kLRki.L); - MDEBUG("Starting with R " << kLRki.R); - for (size_t msidx = 0; msidx < total; ++msidx) - { - if (msidx == creator) - continue; - if (std::find(signers.begin(), signers.end(), msidx) == signers.end()) - continue; - for (size_t lr = 0; lr < account_L[msidx][tdidx].size(); ++lr) - { - if (used_L.find(account_L[msidx][tdidx][lr]) == used_L.end()) - { - used_L.insert(account_L[msidx][tdidx][lr]); - MDEBUG("Adding L " << account_L[msidx][tdidx][lr] << " (for k " << account_k[msidx][tdidx][lr] << ")"); - MDEBUG("Adding R " << account_R[msidx][tdidx][lr]); - rct::addKeys((rct::key&)kLRki.L, kLRki.L, rct::pk2rct(account_L[msidx][tdidx][lr])); - rct::addKeys((rct::key&)kLRki.R, kLRki.R, rct::pk2rct(account_R[msidx][tdidx][lr])); - break; - } - } - } std::vector<crypto::key_image> pkis; for (size_t msidx = 0; msidx < total; ++msidx) for (size_t n = 0; n < account_ki[msidx][tdidx].size(); ++n) @@ -292,8 +256,6 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry r = multisig::generate_multisig_composite_key_image(miner_account[0].get_keys(), subaddresses, output_pub_key[tdidx], tx_pub_key[tdidx], additional_tx_keys, 0, pkis, (crypto::key_image&)kLRki.ki); CHECK_AND_ASSERT_MES(r, false, "Failed to generate composite key image"); MDEBUG("composite ki: " << kLRki.ki); - MDEBUG("L: " << kLRki.L); - MDEBUG("R: " << kLRki.R); for (size_t n = 1; n < total; ++n) { rct::key ki; @@ -302,9 +264,8 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry CHECK_AND_ASSERT_MES(kLRki.ki == ki, false, "Composite key images do not match"); } } -#endif - // create a tx: we have 8 outputs, all from coinbase, so "fake" rct - use 2 + // prepare a tx: we have 8 outputs, all from coinbase, so "fake" rct - use 2 std::vector<tx_source_entry> sources; for (size_t n = 0; n < inputs; ++n) { @@ -322,7 +283,9 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry for (size_t m = 0; m <= mixin; ++m) { rct::ctkey ctkey; - ctkey.dest = rct::pk2rct(boost::get<txout_to_key>(blocks[m].miner_tx.vout[0].target).key); + crypto::public_key output_public_key; + cryptonote::get_output_public_key(blocks[m].miner_tx.vout[0], output_public_key); + ctkey.dest = rct::pk2rct(output_public_key); MDEBUG("using " << (m == n ? "real" : "fake") << " input " << ctkey.dest); ctkey.mask = rct::commit(blocks[m].miner_tx.vout[0].amount, rct::identity()); // since those are coinbases, the masks are known src.outputs.push_back(std::make_pair(m, ctkey)); @@ -333,11 +296,8 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry tx_destination_entry td; td.addr = miner_account[creator].get_keys().m_account_address; td.amount = amount_paid; - std::vector<tx_destination_entry> destinations; + std::vector<tx_destination_entry> destinations; //tx need two outputs since HF_VERSION_MIN_2_OUTPUTS destinations.push_back(td); - cryptonote::account_base dummy; - dummy.generate(); - td.addr = dummy.get_keys().m_account_address; td.amount = 0; destinations.push_back(td); @@ -346,18 +306,12 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry transaction tx; crypto::secret_key tx_key; -#ifdef NO_MULTISIG - rct::multisig_out *msoutp = NULL; -#else - rct::multisig_out msout; - rct::multisig_out *msoutp = &msout; -#endif std::vector<crypto::secret_key> additional_tx_secret_keys; + crypto::secret_key multisig_tx_key_entropy; auto sources_copy = sources; - r = construct_tx_and_get_tx_key(miner_account[creator].get_keys(), subaddresses, sources, destinations, boost::none, std::vector<uint8_t>(), tx, 0, tx_key, additional_tx_secret_keys, true, { rct::RangeProofPaddedBulletproof, 0 }, msoutp); - CHECK_AND_ASSERT_MES(r, false, "failed to construct transaction"); + multisig::signing::tx_builder_ringct_t tx_builder; + CHECK_AND_ASSERT_MES(tx_builder.init(miner_account[creator].get_keys(), {}, 0, 0, {0}, sources, destinations, {}, {rct::RangeProofPaddedBulletproof, 4}, true, false, tx_key, additional_tx_secret_keys, multisig_tx_key_entropy, tx), false, "error: multisig::signing::tx_builder_ringct_t::init"); -#ifndef NO_MULTISIG // work out the permutation done on sources std::vector<size_t> ins_order; for (size_t n = 0; n < sources.size(); ++n) @@ -371,15 +325,50 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry } } CHECK_AND_ASSERT_MES(ins_order.size() == sources.size(), false, "Failed to work out sources permutation"); -#endif -#ifndef NO_MULTISIG + struct { + rct::keyM total_alpha_G; + rct::keyM total_alpha_H; + rct::keyV c_0; + rct::keyV s; + } sig; + { + used_L.clear(); + sig.total_alpha_G.resize(sources.size(), rct::keyV(multisig::signing::kAlphaComponents, rct::identity())); + sig.total_alpha_H.resize(sources.size(), rct::keyV(multisig::signing::kAlphaComponents, rct::identity())); + sig.c_0.resize(sources.size()); + sig.s.resize(sources.size()); + for (std::size_t i = 0; i < sources.size(); ++i) { + rct::keyV alpha(multisig::signing::kAlphaComponents); + for (std::size_t m = 0; m < multisig::signing::kAlphaComponents; ++m) { + alpha[m] = rct::sk2rct(account_k[creator][ins_order[i]][m]); + sig.total_alpha_G[i][m] = rct::pk2rct(account_L[creator][ins_order[i]][m]); + sig.total_alpha_H[i][m] = rct::pk2rct(account_R[creator][ins_order[i]][m]); + for (size_t j = 0; j < total; ++j) { + if (j == creator) + continue; + if (std::find(other_signers.begin(), other_signers.end(), j) == other_signers.end()) + continue; + for (std::size_t n = 0; n < account_L[j][ins_order[i]].size(); ++n) { + if (used_L.find(account_L[j][ins_order[i]][n]) == used_L.end()) { + used_L.insert(account_L[j][ins_order[i]][n]); + rct::addKeys(sig.total_alpha_G[i][m], sig.total_alpha_G[i][m], rct::pk2rct(account_L[j][ins_order[i]][n])); + rct::addKeys(sig.total_alpha_H[i][m], sig.total_alpha_H[i][m], rct::pk2rct(account_R[j][ins_order[i]][n])); + break; + } + } + } + } + CHECK_AND_ASSERT_MES(tx_builder.first_partial_sign(i, sig.total_alpha_G[i], sig.total_alpha_H[i], alpha, sig.c_0[i], sig.s[i]), false, "error: multisig::signing::tx_builder_ringct_t::first_partial_sign"); + } + } + // sign std::unordered_set<crypto::secret_key> used_keys; const std::vector<crypto::secret_key> &msk0 = miner_account[creator].get_multisig_keys(); for (const auto &sk: msk0) - used_keys.insert(sk); - for (size_t signer: signers) + used_keys.insert(sk); //these were used in 'tx_builder.init() -> tx_builder.first_partial_sign()' + for (size_t signer: other_signers) { rct::key skey = rct::zero(); const std::vector<crypto::secret_key> &msk1 = miner_account[signer].get_multisig_keys(); @@ -393,38 +382,37 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry } } CHECK_AND_ASSERT_MES(!(skey == rct::zero()), false, "failed to find secret multisig key to sign transaction"); - std::vector<unsigned int> indices; - for (const auto &src: sources_copy) - indices.push_back(src.real_output); - rct::keyV k; - for (size_t tdidx = 0; tdidx < inputs; ++tdidx) - { - k.push_back(rct::zero()); - for (size_t n = 0; n < account_k[signer][tdidx].size(); ++n) - { - crypto::public_key L; - rct::scalarmultBase((rct::key&)L, rct::sk2rct(account_k[signer][tdidx][n])); - if (used_L.find(L) != used_L.end()) - { - sc_add(k.back().bytes, k.back().bytes, rct::sk2rct(account_k[signer][tdidx][n]).bytes); + rct::keyM k(sources.size(), rct::keyV(multisig::signing::kAlphaComponents)); + for (std::size_t i = 0; i < sources.size(); ++i) { + for (std::size_t j = 0; j < multisig::signing::kAlphaComponents; ++j) { + for (std::size_t n = 0; n < account_k[signer][i].size(); ++n) { + crypto::public_key L; + rct::scalarmultBase((rct::key&)L, rct::sk2rct(account_k[signer][i][n])); + if (used_L.find(L) != used_L.end()) { + k[i][j] = rct::sk2rct(account_k[signer][i][n]); + account_k[signer][i][n] = rct::rct2sk(rct::zero()); //demo: always clear nonces from long-term storage after use + break; + } } + CHECK_AND_ASSERT_MES(!(k[i][j] == rct::zero()), false, "failed to find k to sign transaction"); } - CHECK_AND_ASSERT_MES(!(k.back() == rct::zero()), false, "failed to find k to sign transaction"); } - tools::apply_permutation(ins_order, indices); tools::apply_permutation(ins_order, k); + multisig::signing::tx_builder_ringct_t signer_tx_builder; + CHECK_AND_ASSERT_MES(signer_tx_builder.init(miner_account[signer].get_keys(), {}, 0, 0, {0}, sources, destinations, {}, {rct::RangeProofPaddedBulletproof, 4}, true, true, tx_key, additional_tx_secret_keys, multisig_tx_key_entropy, tx), false, "error: multisig::signing::tx_builder_ringct_t::init"); MDEBUG("signing with k size " << k.size()); - MDEBUG("signing with k " << k.back()); + for (size_t n = 0; n < multisig::signing::kAlphaComponents; ++n) + MDEBUG("signing with k " << k.back()[n]); MDEBUG("signing with sk " << skey); for (const auto &sk: used_keys) MDEBUG(" created with sk " << sk); - MDEBUG("signing with c size " << msout.c.size()); - MDEBUG("signing with c " << msout.c.back()); - r = rct::signMultisig(tx.rct_signatures, indices, k, msout, skey); - CHECK_AND_ASSERT_MES(r, false, "failed to sign transaction"); + CHECK_AND_ASSERT_MES(signer_tx_builder.next_partial_sign(sig.total_alpha_G, sig.total_alpha_H, k, skey, sig.c_0, sig.s), false, "error: multisig::signing::tx_builder_ringct_t::next_partial_sign"); + + // in round-robin signing, the last signer finalizes the tx + if (signer == other_signers.back()) + CHECK_AND_ASSERT_MES(signer_tx_builder.finalize_tx(sources, sig.c_0, sig.s, tx), false, "error: multisig::signing::tx_builder_ringct_t::finalize_tx"); } -#endif // verify this tx is really to the expected address const crypto::public_key tx_pub_key2 = get_tx_pub_key_from_extra(tx, 0); @@ -433,10 +421,12 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry CHECK_AND_ASSERT_MES(r, false, "Failed to generate derivation"); uint64_t n_outs = 0, amount = 0; std::vector<crypto::key_derivation> additional_derivations; + crypto::public_key output_public_key; for (size_t n = 0; n < tx.vout.size(); ++n) { - CHECK_AND_ASSERT_MES(typeid(txout_to_key) == tx.vout[n].target.type(), false, "Unexpected tx out type"); - if (is_out_to_acc_precomp(subaddresses, boost::get<txout_to_key>(tx.vout[n].target).key, derivation, additional_derivations, n, hw::get_device(("default")))) + CHECK_AND_ASSERT_MES(typeid(txout_to_tagged_key) == tx.vout[n].target.type(), false, "Unexpected tx out type"); + cryptonote::get_output_public_key(tx.vout[n], output_public_key); + if (is_out_to_acc_precomp(subaddresses, output_public_key, derivation, additional_derivations, n, hw::get_device(("default")))) { ++n_outs; CHECK_AND_ASSERT_MES(tx.vout[n].amount == 0, false, "Destination amount is not zero"); @@ -451,7 +441,7 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry amount += rct::h2d(ecdh_info.amount); } } - CHECK_AND_ASSERT_MES(n_outs == 1, false, "Not exactly 1 output was received"); + CHECK_AND_ASSERT_MES(n_outs == 2, false, "Not exactly 2 outputs were received"); CHECK_AND_ASSERT_MES(amount == amount_paid, false, "Amount paid was not the expected amount"); if (post_tx) diff --git a/tests/core_tests/multisig.h b/tests/core_tests/multisig.h index e9a2cf5f3..948c19458 100644 --- a/tests/core_tests/multisig.h +++ b/tests/core_tests/multisig.h @@ -71,7 +71,7 @@ struct gen_multisig_tx_validation_base : public test_chain_unit_base bool generate_with(std::vector<test_event_entry>& events, size_t inputs, size_t mixin, uint64_t amount_paid, bool valid, - size_t threshold, size_t total, size_t creator, std::vector<size_t> signers, + size_t threshold, size_t total, size_t creator, std::vector<size_t> other_signers, const std::function<void(std::vector<cryptonote::tx_source_entry> &sources, std::vector<cryptonote::tx_destination_entry> &destinations)> &pre_tx, const std::function<void(cryptonote::transaction &tx)> &post_tx) const; diff --git a/tests/core_tests/rct.cpp b/tests/core_tests/rct.cpp index 0926483fe..4e51ed713 100644 --- a/tests/core_tests/rct.cpp +++ b/tests/core_tests/rct.cpp @@ -229,7 +229,7 @@ bool gen_rct_tx_validation_base::generate_with_full(std::vector<test_event_entry std::vector<crypto::secret_key> additional_tx_keys; std::unordered_map<crypto::public_key, cryptonote::subaddress_index> subaddresses; subaddresses[miner_accounts[0].get_keys().m_account_address.m_spend_public_key] = {0,0}; - bool r = construct_tx_and_get_tx_key(miner_accounts[0].get_keys(), subaddresses, sources, destinations, cryptonote::account_public_address{}, std::vector<uint8_t>(), tx, 0, tx_key, additional_tx_keys, true, rct_config, NULL, use_view_tags); + bool r = construct_tx_and_get_tx_key(miner_accounts[0].get_keys(), subaddresses, sources, destinations, cryptonote::account_public_address{}, std::vector<uint8_t>(), tx, 0, tx_key, additional_tx_keys, true, rct_config, use_view_tags); CHECK_AND_ASSERT_MES(r, false, "failed to construct transaction"); if (post_tx) diff --git a/tests/core_tests/wallet_tools.cpp b/tests/core_tests/wallet_tools.cpp index fdc4753f9..a3b66e835 100644 --- a/tests/core_tests/wallet_tools.cpp +++ b/tests/core_tests/wallet_tools.cpp @@ -280,5 +280,5 @@ bool construct_tx_rct(tools::wallet2 * sender_wallet, std::vector<cryptonote::tx std::vector<crypto::secret_key> additional_tx_keys; std::vector<tx_destination_entry> destinations_copy = destinations; rct::RCTConfig rct_config = {range_proof_type, bp_version}; - return construct_tx_and_get_tx_key(sender_wallet->get_account().get_keys(), subaddresses, sources, destinations_copy, change_addr, extra, tx, unlock_time, tx_key, additional_tx_keys, rct, rct_config, nullptr); + return construct_tx_and_get_tx_key(sender_wallet->get_account().get_keys(), subaddresses, sources, destinations_copy, change_addr, extra, tx, unlock_time, tx_key, additional_tx_keys, rct, rct_config); } diff --git a/tests/data/sha256sum/CLSAG.pdf b/tests/data/sha256sum/CLSAG.pdf Binary files differnew file mode 100644 index 000000000..6d7bb79ae --- /dev/null +++ b/tests/data/sha256sum/CLSAG.pdf diff --git a/tests/data/sha256sum/empty.txt b/tests/data/sha256sum/empty.txt new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/tests/data/sha256sum/empty.txt diff --git a/tests/data/sha256sum/small_file.txt b/tests/data/sha256sum/small_file.txt new file mode 100644 index 000000000..74ca63e8d --- /dev/null +++ b/tests/data/sha256sum/small_file.txt @@ -0,0 +1,19 @@ +From: https://localmonero.co/knowledge/monero-circular-economies + +How does Monero uniquely enable these circular economies? + +While Monero shares some of the core attributes of Bitcoin that enable circular economies in a new way (censorship-resistant payments, p2p transactions, etc.), it brings an absolutely unique empowerment to those wishing to build and engage in circular economies. + +1. Monero enables global p2p transactions without fear of surveillance or censorship +Monero users do not need to worry about mass surveillance or even targeted censorship of their transactions, enabling unique peace of mind and preventing any burdens on commerce. You can transact with anyone in the world, at any time, without any surveillance using the Monero wallet of your choice. + +2. Fungibility removes the risk of tainted coins and ensures trust +As Monero is fungible (1 XMR equals 1 XMR, no matter what), participants in the circular economy don’t need to worry about the funds they are sending or receiving. Any Monero they send cannot be traced back to their other transactions and has no history and thus cannot be censored based on history, and Monero received will always be able to be spent freely at full market value. This fungibility adds to the peace of mind of participants, ensures that chain analysis firms cannot force their way into circular economies, and prevents a breakdown of trust in Monero as a method of exchange. +The current breakdown of trust in Bitcoin as a method of exchange is leading to it rapidly losing traction in circular economies where Monero is present. People don’t want to have to check funds for taint, worry about if they will be able to spend them freely, or feel the need to use any chain analysis tools to protect themselves from legal or regulatory issues. + +3. Monero’s low fees ensure a free flow of commerce +One of the simplest points to grasp about Monero transactions is that transaction fees are incredibly low and will remain reasonable in the long-term thanks to the tail emission and dynamic block size. +These low fees make sure that commerce can flow freely no matter the amount of blockchain congestion, further reducing the mental burden and stress on participants to try and time their transactions or wait hours/days to confirm low-fee transactions. With fees around 1c today, you can transact freely with any size of transaction without worry about fees down the line. + +Conclusion +Ultimately, Monero is digital cash as it should be. The peace of mind, fungibility, and privacy of transacting in cash but with all of the advantages of digital, global, and p2p transactions detached from the states control or surveillance. This ability to act as digital cash is uniquely enabling circular economies today and helping them to grow and prosper over time in ways that other cryptocurrencies like Bitcoin simply can’t. diff --git a/tests/functional_tests/multisig.py b/tests/functional_tests/multisig.py index 89cb2fdc7..1c5894f47 100755 --- a/tests/functional_tests/multisig.py +++ b/tests/functional_tests/multisig.py @@ -107,7 +107,7 @@ class MultisigTest(): try: self.wallet[i].close_wallet() except: pass res = self.wallet[i].restore_deterministic_wallet(seed = seeds[i]) - res = self.wallet[i].prepare_multisig() + res = self.wallet[i].prepare_multisig(enable_multisig_experimental = True) assert len(res.multisig_info) > 0 info.append(res.multisig_info) @@ -172,7 +172,7 @@ class MultisigTest(): res = wallet2of2[i].restore_deterministic_wallet(seed = seeds[i]) res = wallet2of2[i].is_multisig() assert not res.multisig - res = wallet2of2[i].prepare_multisig() + res = wallet2of2[i].prepare_multisig(enable_multisig_experimental = True) assert len(res.multisig_info) > 0 info2of2.append(res.multisig_info) @@ -187,7 +187,7 @@ class MultisigTest(): assert res.ready ok = False - try: res = wallet2of2[0].prepare_multisig() + try: res = wallet2of2[0].prepare_multisig(enable_multisig_experimental = True) except: ok = True assert ok @@ -205,7 +205,7 @@ class MultisigTest(): res = wallet2of3[i].restore_deterministic_wallet(seed = seeds[i]) res = wallet2of3[i].is_multisig() assert not res.multisig - res = wallet2of3[i].prepare_multisig() + res = wallet2of3[i].prepare_multisig(enable_multisig_experimental = True) assert len(res.multisig_info) > 0 info2of3.append(res.multisig_info) @@ -223,7 +223,7 @@ class MultisigTest(): assert not res.ready ok = False - try: res = wallet2of3[1].prepare_multisig() + try: res = wallet2of3[1].prepare_multisig(enable_multisig_experimental = True) except: ok = True assert ok diff --git a/tests/performance_tests/rct_mlsag.h b/tests/performance_tests/rct_mlsag.h index 4cdbcd601..2163431fe 100644 --- a/tests/performance_tests/rct_mlsag.h +++ b/tests/performance_tests/rct_mlsag.h @@ -65,7 +65,7 @@ public: { sk[j] = xm[ind][j]; } - IIccss = MLSAG_Gen(rct::identity(), P, sk, NULL, NULL, ind, rows-1, hw::get_device("default")); + IIccss = MLSAG_Gen(rct::identity(), P, sk, ind, rows-1, hw::get_device("default")); return true; } @@ -75,7 +75,7 @@ public: if (ver) MLSAG_Ver(rct::identity(), P, IIccss, rows-1); else - MLSAG_Gen(rct::identity(), P, sk, NULL, NULL, ind, rows-1, hw::get_device("default")); + MLSAG_Gen(rct::identity(), P, sk, ind, rows-1, hw::get_device("default")); return true; } diff --git a/tests/performance_tests/sig_clsag.h b/tests/performance_tests/sig_clsag.h index 53c38c56b..043830ee6 100644 --- a/tests/performance_tests/sig_clsag.h +++ b/tests/performance_tests/sig_clsag.h @@ -117,7 +117,7 @@ class test_sig_clsag sk.dest = r[u]; sk.mask = s[u]; - sigs.push_back(proveRctCLSAGSimple(messages[u],pubs,sk,s1[u],C_offsets[u],NULL,NULL,NULL,u,hw::get_device("default"))); + sigs.push_back(proveRctCLSAGSimple(messages[u],pubs,sk,s1[u],C_offsets[u],u,hw::get_device("default"))); } return true; diff --git a/tests/performance_tests/sig_mlsag.h b/tests/performance_tests/sig_mlsag.h index 60a306753..46bdcde63 100644 --- a/tests/performance_tests/sig_mlsag.h +++ b/tests/performance_tests/sig_mlsag.h @@ -117,7 +117,7 @@ class test_sig_mlsag sk.dest = r[u]; sk.mask = s[u]; - sigs.push_back(proveRctMGSimple(messages[u],pubs,sk,s1[u],C_offsets[u],NULL,NULL,u,hw::get_device("default"))); + sigs.push_back(proveRctMGSimple(messages[u],pubs,sk,s1[u],C_offsets[u],u,hw::get_device("default"))); } return true; diff --git a/tests/trezor/trezor_tests.cpp b/tests/trezor/trezor_tests.cpp index 0db05760a..0d61610be 100644 --- a/tests/trezor/trezor_tests.cpp +++ b/tests/trezor/trezor_tests.cpp @@ -52,7 +52,6 @@ namespace po = boost::program_options; namespace { const command_line::arg_descriptor<std::string> arg_filter = { "filter", "Regular expression filter for which tests to run" }; - const command_line::arg_descriptor<bool> arg_generate_and_play_test_data = {"generate_and_play_test_data", ""}; const command_line::arg_descriptor<std::string> arg_trezor_path = {"trezor_path", "Path to the trezor device to use, has to support debug link", ""}; const command_line::arg_descriptor<bool> arg_heavy_tests = {"heavy_tests", "Runs expensive tests (volume tests with real device)", false}; const command_line::arg_descriptor<std::string> arg_chain_path = {"chain_path", "Path to the serialized blockchain, speeds up testing", ""}; @@ -138,7 +137,7 @@ int main(int argc, char* argv[]) hw::register_device(HW_TREZOR_NAME, ensure_trezor_test_device()); // shim device for call tracking // Bootstrapping common chain & accounts - const uint8_t initial_hf = (uint8_t)get_env_long("TEST_MIN_HF", 12); + const uint8_t initial_hf = (uint8_t)get_env_long("TEST_MIN_HF", HF_VERSION_CLSAG); const uint8_t max_hf = (uint8_t)get_env_long("TEST_MAX_HF", HF_VERSION_CLSAG); auto sync_test = get_env_long("TEST_KI_SYNC", 1); MINFO("Test versions " << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")"); @@ -162,6 +161,10 @@ int main(int argc, char* argv[]) // Transaction tests for(uint8_t hf=initial_hf; hf <= max_hf + 1; ++hf) { + if (hf == 14) { // HF 14 is skipped. + continue; + } + if (hf > initial_hf || hf > max_hf) { daemon->stop_and_deinit(); @@ -201,12 +204,14 @@ int main(int argc, char* argv[]) TREZOR_COMMON_TEST_CASE(gen_trezor_4utxo_to_1norm_2sub, core, trezor_base); TREZOR_COMMON_TEST_CASE(gen_trezor_2utxo_sub_acc_to_1norm_2sub, core, trezor_base); TREZOR_COMMON_TEST_CASE(gen_trezor_4utxo_to_7outs, core, trezor_base); + TREZOR_COMMON_TEST_CASE(gen_trezor_4utxo_to_15outs, core, trezor_base); TREZOR_COMMON_TEST_CASE(wallet_api_tests, core, trezor_base); } if (trezor_base.heavy_tests()) { TREZOR_COMMON_TEST_CASE(gen_trezor_many_utxo, core, trezor_base); + TREZOR_COMMON_TEST_CASE(gen_trezor_many_utxo_many_txo, core, trezor_base); } core->deinit(); @@ -555,7 +560,7 @@ static void expand_tsx(cryptonote::transaction &tx) rv.p.MGs[n].II[0] = rct::ki2rct(boost::get<txin_to_key>(tx.vin[n]).k_image); } } - else if (rv.type == rct::RCTTypeCLSAG) + else if (rv.type == rct::RCTTypeCLSAG || rv.type == rct::RCTTypeBulletproofPlus) { if (!tx.pruned) { @@ -1269,6 +1274,8 @@ void gen_trezor_base::set_hard_fork(uint8_t hf) rct_config({rct::RangeProofPaddedBulletproof, 2}); } else if (hf == HF_VERSION_CLSAG){ rct_config({rct::RangeProofPaddedBulletproof, 3}); + } else if (hf == HF_VERSION_BULLETPROOF_PLUS){ + rct_config({rct::RangeProofPaddedBulletproof, 4}); } else { throw std::runtime_error("Unsupported HF"); } @@ -1655,7 +1662,7 @@ bool gen_trezor_1utxo::generate(std::vector<test_event_entry>& events) { TREZOR_TEST_PREFIX(); t_builder->cur_height(num_blocks(events) - 1) - ->mixin(TREZOR_TEST_MIXIN) + ->mixin(num_mixin()) ->fee(TREZOR_TEST_FEE) ->from(m_wl_alice.get(), 0) ->compute_sources(boost::none, MK_COINS(1), -1, -1) @@ -1671,7 +1678,7 @@ bool gen_trezor_1utxo_paymentid_short::generate(std::vector<test_event_entry>& e TREZOR_TEST_PREFIX(); TREZOR_SKIP_IF_VERSION_LEQ(hw::trezor::pack_version(2, 0, 9)); t_builder->cur_height(num_blocks(events) - 1) - ->mixin(TREZOR_TEST_MIXIN) + ->mixin(num_mixin()) ->fee(TREZOR_TEST_FEE) ->from(m_wl_alice.get(), 0) ->compute_sources(boost::none, MK_COINS(1), -1, -1) @@ -1688,7 +1695,7 @@ bool gen_trezor_1utxo_paymentid_short_integrated::generate(std::vector<test_even TREZOR_TEST_PREFIX(); TREZOR_SKIP_IF_VERSION_LEQ(hw::trezor::pack_version(2, 0, 9)); t_builder->cur_height(num_blocks(events) - 1) - ->mixin(TREZOR_TEST_MIXIN) + ->mixin(num_mixin()) ->fee(TREZOR_TEST_FEE) ->from(m_wl_alice.get(), 0) ->compute_sources(boost::none, MK_COINS(1), -1, -1) @@ -1705,7 +1712,7 @@ bool gen_trezor_4utxo::generate(std::vector<test_event_entry>& events) { TREZOR_TEST_PREFIX(); t_builder->cur_height(num_blocks(events) - 1) - ->mixin(TREZOR_TEST_MIXIN) + ->mixin(num_mixin()) ->fee(TREZOR_TEST_FEE) ->from(m_wl_alice.get(), 0) ->compute_sources(4, MK_COINS(1), -1, -1) @@ -1720,7 +1727,7 @@ bool gen_trezor_4utxo_acc1::generate(std::vector<test_event_entry>& events) { TREZOR_TEST_PREFIX(); t_builder->cur_height(num_blocks(events) - 1) - ->mixin(TREZOR_TEST_MIXIN) + ->mixin(num_mixin()) ->fee(TREZOR_TEST_FEE) ->from(m_wl_alice.get(), 1) ->compute_sources(4, MK_COINS(1), -1, -1) @@ -1735,7 +1742,7 @@ bool gen_trezor_4utxo_to_sub::generate(std::vector<test_event_entry>& events) { TREZOR_TEST_PREFIX(); t_builder->cur_height(num_blocks(events) - 1) - ->mixin(TREZOR_TEST_MIXIN) + ->mixin(num_mixin()) ->fee(TREZOR_TEST_FEE) ->from(m_wl_alice.get(), 0) ->compute_sources(4, MK_COINS(1), -1, -1) @@ -1750,7 +1757,7 @@ bool gen_trezor_4utxo_to_2sub::generate(std::vector<test_event_entry>& events) { TREZOR_TEST_PREFIX(); t_builder->cur_height(num_blocks(events) - 1) - ->mixin(TREZOR_TEST_MIXIN) + ->mixin(num_mixin()) ->fee(TREZOR_TEST_FEE) ->from(m_wl_alice.get(), 0) ->compute_sources(4, MK_COINS(1), -1, -1) @@ -1766,7 +1773,7 @@ bool gen_trezor_4utxo_to_1norm_2sub::generate(std::vector<test_event_entry>& eve { TREZOR_TEST_PREFIX(); t_builder->cur_height(num_blocks(events) - 1) - ->mixin(TREZOR_TEST_MIXIN) + ->mixin(num_mixin()) ->fee(TREZOR_TEST_FEE) ->from(m_wl_alice.get(), 0) ->compute_sources(4, MK_COINS(1), -1, -1) @@ -1783,7 +1790,7 @@ bool gen_trezor_2utxo_sub_acc_to_1norm_2sub::generate(std::vector<test_event_ent { TREZOR_TEST_PREFIX(); t_builder->cur_height(num_blocks(events) - 1) - ->mixin(TREZOR_TEST_MIXIN) + ->mixin(num_mixin()) ->fee(TREZOR_TEST_FEE) ->from(m_wl_alice.get(), 0) ->compute_sources_to_sub_acc(2, MK_COINS(1) >> 2, -1, -1) @@ -1800,7 +1807,7 @@ bool gen_trezor_4utxo_to_7outs::generate(std::vector<test_event_entry>& events) { TREZOR_TEST_PREFIX(); t_builder->cur_height(num_blocks(events) - 1) - ->mixin(TREZOR_TEST_MIXIN) + ->mixin(num_mixin()) ->fee(TREZOR_TEST_FEE) ->from(m_wl_alice.get(), 0) ->compute_sources(4, MK_COINS(1), -1, -1) @@ -1817,11 +1824,39 @@ bool gen_trezor_4utxo_to_7outs::generate(std::vector<test_event_entry>& events) TREZOR_TEST_SUFFIX(); } +bool gen_trezor_4utxo_to_15outs::generate(std::vector<test_event_entry>& events) +{ + TREZOR_TEST_PREFIX(); + t_builder->cur_height(num_blocks(events) - 1) + ->mixin(num_mixin()) + ->fee(TREZOR_TEST_FEE) + ->from(m_wl_alice.get(), 0) + ->compute_sources(4, MK_COINS(1), -1, -1) + ->add_destination(m_wl_eve->get_subaddress({1, 1}), true, 1000) + ->add_destination(m_wl_eve->get_subaddress({2, 1}), true, 1000) + ->add_destination(m_wl_eve->get_subaddress({0, 1}), true, 1000) + ->add_destination(m_wl_eve->get_subaddress({0, 2}), true, 1000) + ->add_destination(m_wl_eve->get_subaddress({0, 3}), true, 1000) + ->add_destination(m_wl_eve->get_subaddress({0, 4}), true, 1000) + ->add_destination(m_wl_eve->get_subaddress({1, 1}), true, 1000) + ->add_destination(m_wl_eve->get_subaddress({2, 1}), true, 1000) + ->add_destination(m_wl_eve->get_subaddress({0, 1}), true, 1000) + ->add_destination(m_wl_eve->get_subaddress({0, 2}), true, 1000) + ->add_destination(m_wl_eve->get_subaddress({0, 3}), true, 1000) + ->add_destination(m_wl_eve->get_subaddress({0, 4}), true, 1000) + ->add_destination(m_wl_eve->get_subaddress({0, 4}), true, 1000) + ->add_destination(m_wl_eve.get(), false, 1000) + ->rct_config(m_rct_config) + ->build_tx(); + + TREZOR_TEST_SUFFIX(); +} + bool gen_trezor_many_utxo::generate(std::vector<test_event_entry>& events) { TREZOR_TEST_PREFIX(); t_builder->cur_height(num_blocks(events) - 1) - ->mixin(TREZOR_TEST_MIXIN) + ->mixin(num_mixin()) ->fee(TREZOR_TEST_FEE) ->from(m_wl_alice.get(), 0) ->compute_sources(110, MK_COINS(1), -1, -1) @@ -1832,6 +1867,35 @@ bool gen_trezor_many_utxo::generate(std::vector<test_event_entry>& events) TREZOR_TEST_SUFFIX(); } +bool gen_trezor_many_utxo_many_txo::generate(std::vector<test_event_entry>& events) +{ + TREZOR_TEST_PREFIX(); + t_builder->cur_height(num_blocks(events) - 1) + ->mixin(num_mixin()) + ->fee(TREZOR_TEST_FEE) + ->from(m_wl_alice.get(), 0) + ->compute_sources(40, MK_COINS(1), -1, -1) + ->add_destination(m_eve_account, false, 1000) + ->add_destination(m_wl_eve->get_subaddress({1, 1}), true, 1000) + ->add_destination(m_wl_eve->get_subaddress({2, 1}), true, 1000) + ->add_destination(m_wl_eve->get_subaddress({0, 1}), true, 1000) + ->add_destination(m_wl_eve->get_subaddress({0, 2}), true, 1000) + ->add_destination(m_wl_eve->get_subaddress({0, 3}), true, 1000) + ->add_destination(m_wl_eve->get_subaddress({0, 4}), true, 1000) + ->add_destination(m_wl_eve->get_subaddress({1, 1}), true, 1000) + ->add_destination(m_wl_eve->get_subaddress({2, 1}), true, 1000) + ->add_destination(m_wl_eve->get_subaddress({0, 1}), true, 1000) + ->add_destination(m_wl_eve->get_subaddress({0, 2}), true, 1000) + ->add_destination(m_wl_eve->get_subaddress({0, 3}), true, 1000) + ->add_destination(m_wl_eve->get_subaddress({1, 4}), true, 1000) + ->add_destination(m_wl_eve->get_subaddress({2, 4}), true, 1000) + ->add_destination(m_wl_eve->get_subaddress({3, 4}), true, 1000) + ->rct_config(m_rct_config) + ->build_tx(); + + TREZOR_TEST_SUFFIX(); +} + void wallet_api_tests::init() { m_wallet_dir = boost::filesystem::unique_path(); @@ -1873,7 +1937,7 @@ bool wallet_api_tests::generate(std::vector<test_event_entry>& events) Monero::PendingTransaction * transaction = w->createTransaction(recepient_address, "", MK_COINS(10), - TREZOR_TEST_MIXIN, + num_mixin(), Monero::PendingTransaction::Priority_Medium, 0, std::set<uint32_t>{}); diff --git a/tests/trezor/trezor_tests.h b/tests/trezor/trezor_tests.h index 2953cc2bd..f7684da12 100644 --- a/tests/trezor/trezor_tests.h +++ b/tests/trezor/trezor_tests.h @@ -37,7 +37,9 @@ #include "../core_tests/wallet_tools.h" #define TREZOR_TEST_FEE 90000000000 -#define TREZOR_TEST_MIXIN 11 +#define TREZOR_TEST_CLSAG_MIXIN 11 +#define TREZOR_TEST_HF15_MIXIN 16 +#define TREZOR_TEST_MIXIN TREZOR_TEST_CLSAG_MIXIN /************************************************************************/ /* */ @@ -93,6 +95,7 @@ public: bool heavy_tests() const { return m_heavy_tests; } void rct_config(rct::RCTConfig rct_config) { m_rct_config = rct_config; } uint8_t cur_hf() const { return m_hard_forks.size() > 0 ? m_hard_forks.back().first : 0; } + size_t num_mixin() const { return m_top_hard_fork >= HF_VERSION_BULLETPROOF_PLUS ? TREZOR_TEST_HF15_MIXIN : TREZOR_TEST_CLSAG_MIXIN; } cryptonote::network_type nettype() const { return m_network_type; } std::shared_ptr<mock_daemon> daemon() const { return m_daemon; } void daemon(std::shared_ptr<mock_daemon> daemon){ m_daemon = std::move(daemon); } @@ -306,12 +309,24 @@ public: bool generate(std::vector<test_event_entry>& events) override; }; +class gen_trezor_4utxo_to_15outs : public gen_trezor_base +{ +public: + bool generate(std::vector<test_event_entry>& events) override; +}; + class gen_trezor_many_utxo : public gen_trezor_base { public: bool generate(std::vector<test_event_entry>& events) override; }; +class gen_trezor_many_utxo_many_txo : public gen_trezor_base +{ +public: + bool generate(std::vector<test_event_entry>& events) override; +}; + // Wallet::API tests class wallet_api_tests : public gen_trezor_base { diff --git a/tests/unit_tests/bulletproofs.cpp b/tests/unit_tests/bulletproofs.cpp index 493eb9426..65f0d85f8 100644 --- a/tests/unit_tests/bulletproofs.cpp +++ b/tests/unit_tests/bulletproofs.cpp @@ -132,7 +132,8 @@ TEST(bulletproofs, multi_splitting) rct::ctkeyV outSk; rct::RCTConfig rct_config { rct::RangeProofPaddedBulletproof, 4 }; - rct::rctSig s = rct::genRctSimple(rct::zero(), sc, destinations, inamounts, outamounts, available, mixRing, amount_keys, NULL, NULL, index, outSk, rct_config, hw::get_device("default")); + + rct::rctSig s = rct::genRctSimple(rct::zero(), sc, destinations, inamounts, outamounts, available, mixRing, amount_keys, index, outSk, rct_config, hw::get_device("default")); ASSERT_TRUE(rct::verRctSimple(s)); for (size_t i = 0; i < n_outputs; ++i) { diff --git a/tests/unit_tests/epee_boosted_tcp_server.cpp b/tests/unit_tests/epee_boosted_tcp_server.cpp index 54d27be1b..c08a86a5e 100644 --- a/tests/unit_tests/epee_boosted_tcp_server.cpp +++ b/tests/unit_tests/epee_boosted_tcp_server.cpp @@ -31,6 +31,8 @@ #include <boost/chrono/chrono.hpp> #include <boost/thread/condition_variable.hpp> #include <boost/thread/mutex.hpp> +#include <condition_variable> +#include <mutex> #include "gtest/gtest.h" @@ -276,6 +278,11 @@ TEST(test_epee_connection, test_lifetime) ASSERT_TRUE(shared_state->get_connections_count() == 0); constexpr auto DELAY = 30; constexpr auto TIMEOUT = 1; + while (server.get_connections_count()) { + server.get_config_shared()->del_in_connections( + server.get_config_shared()->get_in_connections_count() + ); + } server.get_config_shared()->set_handler(new command_handler_t(DELAY), &command_handler_t::destroy); for (auto i = 0; i < N; ++i) { tag = create_connection(); @@ -332,7 +339,7 @@ TEST(test_epee_connection, test_lifetime) ), &command_handler_t::destroy ); - for (auto i = 0; i < N; ++i) { + for (auto i = 0; i < N * N * N; ++i) { { connection_ptr conn(new connection_t(io_context, shared_state, {}, {})); conn->socket().connect(endpoint); @@ -342,6 +349,7 @@ TEST(test_epee_connection, test_lifetime) } ASSERT_TRUE(shared_state->get_connections_count() == 1); shared_state->del_out_connections(1); + while (shared_state->sock_count); ASSERT_TRUE(shared_state->get_connections_count() == 0); } @@ -452,7 +460,11 @@ TEST(test_epee_connection, test_lifetime) } for (;workers.size(); workers.pop_back()) workers.back().join(); - + while (server.get_connections_count()) { + server.get_config_shared()->del_in_connections( + server.get_config_shared()->get_in_connections_count() + ); + } }); for (auto& w: workers) { @@ -462,3 +474,241 @@ TEST(test_epee_connection, test_lifetime) server.timed_wait_server_stop(5 * 1000); server.deinit_server(); } + +TEST(test_epee_connection, ssl_shutdown) +{ + struct context_t: epee::net_utils::connection_context_base { + static constexpr size_t get_max_bytes(int) noexcept { return -1; } + static constexpr int handshake_command() noexcept { return 1001; } + static constexpr bool handshake_complete() noexcept { return true; } + }; + + struct command_handler_t: epee::levin::levin_commands_handler<context_t> { + virtual int invoke(int, const epee::span<const uint8_t>, epee::byte_stream&, context_t&) override { return {}; } + virtual int notify(int, const epee::span<const uint8_t>, context_t&) override { return {}; } + virtual void callback(context_t&) override {} + virtual void on_connection_new(context_t&) override {} + virtual void on_connection_close(context_t&) override { } + virtual ~command_handler_t() override {} + static void destroy(epee::levin::levin_commands_handler<context_t>* ptr) { delete ptr; } + }; + + using handler_t = epee::levin::async_protocol_handler<context_t>; + using io_context_t = boost::asio::io_service; + using endpoint_t = boost::asio::ip::tcp::endpoint; + using server_t = epee::net_utils::boosted_tcp_server<handler_t>; + using socket_t = boost::asio::ip::tcp::socket; + using ssl_socket_t = boost::asio::ssl::stream<socket_t>; + using ssl_context_t = boost::asio::ssl::context; + using ec_t = boost::system::error_code; + + endpoint_t endpoint(boost::asio::ip::address::from_string("127.0.0.1"), 5263); + server_t server(epee::net_utils::e_connection_type_P2P); + server.init_server(endpoint.port(), + endpoint.address().to_string(), + 0, + "", + false, + true, + epee::net_utils::ssl_support_t::e_ssl_support_enabled + ); + server.get_config_shared()->set_handler(new command_handler_t, &command_handler_t::destroy); + server.run_server(2, false); + + ssl_context_t ssl_context{boost::asio::ssl::context::sslv23}; + io_context_t io_context; + ssl_socket_t socket(io_context, ssl_context); + ec_t ec; + socket.next_layer().connect(endpoint, ec); + EXPECT_EQ(ec.value(), 0); + socket.handshake(boost::asio::ssl::stream_base::client, ec); + EXPECT_EQ(ec.value(), 0); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + while (server.get_config_shared()->get_connections_count() < 1); + server.get_config_shared()->del_in_connections(1); + while (server.get_config_shared()->get_connections_count() > 0); + server.send_stop_signal(); + EXPECT_TRUE(server.timed_wait_server_stop(5 * 1000)); + server.deinit_server(); + socket.next_layer().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec); + socket.next_layer().close(ec); + socket.shutdown(ec); +} + +TEST(test_epee_connection, ssl_handshake) +{ + using io_context_t = boost::asio::io_service; + using work_t = boost::asio::io_service::work; + using work_ptr = std::shared_ptr<work_t>; + using workers_t = std::vector<std::thread>; + using socket_t = boost::asio::ip::tcp::socket; + using ssl_socket_t = boost::asio::ssl::stream<socket_t>; + using ssl_socket_ptr = std::unique_ptr<ssl_socket_t>; + using ssl_options_t = epee::net_utils::ssl_options_t; + io_context_t io_context; + work_ptr work(std::make_shared<work_t>(io_context)); + workers_t workers; + auto constexpr N = 2; + while (workers.size() < N) { + workers.emplace_back([&io_context]{ + io_context.run(); + }); + } + ssl_options_t ssl_options{{}}; + auto ssl_context = ssl_options.create_context(); + for (size_t i = 0; i < N * N * N; ++i) { + ssl_socket_ptr ssl_socket(new ssl_socket_t(io_context, ssl_context)); + ssl_socket->next_layer().open(boost::asio::ip::tcp::v4()); + for (size_t i = 0; i < N; ++i) { + io_context.post([]{ + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + }); + } + EXPECT_EQ( + ssl_options.handshake( + *ssl_socket, + ssl_socket_t::server, + {}, + {}, + std::chrono::milliseconds(0) + ), + false + ); + ssl_socket->next_layer().close(); + ssl_socket.reset(); + } + work.reset(); + for (;workers.size(); workers.pop_back()) + workers.back().join(); +} + + +TEST(boosted_tcp_server, strand_deadlock) +{ + using context_t = epee::net_utils::connection_context_base; + using lock_t = std::mutex; + using unique_lock_t = std::unique_lock<lock_t>; + + struct config_t { + using condition_t = std::condition_variable_any; + using lock_guard_t = std::lock_guard<lock_t>; + void notify_success() + { + lock_guard_t guard(lock); + success = true; + condition.notify_all(); + } + lock_t lock; + condition_t condition; + bool success; + }; + + struct handler_t { + using config_type = config_t; + using connection_context = context_t; + using byte_slice_t = epee::byte_slice; + using socket_t = epee::net_utils::i_service_endpoint; + + handler_t(socket_t *socket, config_t &config, context_t &context): + socket(socket), + config(config), + context(context) + {} + void after_init_connection() + { + unique_lock_t guard(lock); + if (!context.m_is_income) { + guard.unlock(); + socket->do_send(byte_slice_t{"."}); + } + } + void handle_qued_callback() + { + } + bool handle_recv(const char *data, size_t bytes_transferred) + { + unique_lock_t guard(lock); + if (!context.m_is_income) { + if (context.m_recv_cnt == 1024) { + guard.unlock(); + socket->do_send(byte_slice_t{"."}); + } + } + else { + if (context.m_recv_cnt == 1) { + for(size_t i = 0; i < 1024; ++i) { + guard.unlock(); + socket->do_send(byte_slice_t{"."}); + guard.lock(); + } + } + else if(context.m_recv_cnt == 2) { + guard.unlock(); + socket->close(); + } + } + return true; + } + void release_protocol() + { + unique_lock_t guard(lock); + if(!context.m_is_income + && context.m_recv_cnt == 1024 + && context.m_send_cnt == 2 + ) { + guard.unlock(); + config.notify_success(); + } + } + + lock_t lock; + socket_t *socket; + config_t &config; + context_t &context; + }; + + using server_t = epee::net_utils::boosted_tcp_server<handler_t>; + using endpoint_t = boost::asio::ip::tcp::endpoint; + + endpoint_t endpoint(boost::asio::ip::address::from_string("127.0.0.1"), 5262); + server_t server(epee::net_utils::e_connection_type_P2P); + server.init_server( + endpoint.port(), + endpoint.address().to_string(), + {}, + {}, + {}, + true, + epee::net_utils::ssl_support_t::e_ssl_support_disabled + ); + server.run_server(2, {}); + server.async_call( + [&]{ + context_t context; + ASSERT_TRUE( + server.connect( + endpoint.address().to_string(), + std::to_string(endpoint.port()), + 5, + context, + "0.0.0.0", + epee::net_utils::ssl_support_t::e_ssl_support_disabled + ) + ); + } + ); + { + unique_lock_t guard(server.get_config_object().lock); + EXPECT_TRUE( + server.get_config_object().condition.wait_for( + guard, + std::chrono::seconds(5), + [&] { return server.get_config_object().success; } + ) + ); + } + + server.send_stop_signal(); + server.timed_wait_server_stop(5 * 1000); + server.deinit_server(); +} diff --git a/tests/unit_tests/node_server.cpp b/tests/unit_tests/node_server.cpp index 134fa6ece..6c8cd9f8d 100644 --- a/tests/unit_tests/node_server.cpp +++ b/tests/unit_tests/node_server.cpp @@ -1026,12 +1026,12 @@ TEST(node_server, race_condition) } void stop() {} void on_connection_close(context_t &context) {} - void set_max_out_peers(unsigned int max) {} + void set_max_out_peers(epee::net_utils::zone zone, unsigned int max) {} bool no_sync() const { return {}; } void set_no_sync(bool value) {} string_t get_peers_overview() const { return {}; } stripes_t get_next_needed_pruning_stripe() const { return {}; } - bool needs_new_sync_connections() const { return {}; } + bool needs_new_sync_connections(epee::net_utils::zone zone) const { return {}; } bool is_busy_syncing() { return {}; } }; using node_server_t = nodetool::node_server<protocol_t>; diff --git a/tests/unit_tests/ringct.cpp b/tests/unit_tests/ringct.cpp index f3ca2b2b4..920ec7c5e 100644 --- a/tests/unit_tests/ringct.cpp +++ b/tests/unit_tests/ringct.cpp @@ -113,7 +113,7 @@ TEST(ringct, MG_sigs) sk[j] = xm[ind][j]; } key message = identity(); - mgSig IIccss = MLSAG_Gen(message, P, sk, NULL, NULL, ind, R, hw::get_device("default")); + mgSig IIccss = MLSAG_Gen(message, P, sk, ind, R, hw::get_device("default")); ASSERT_TRUE(MLSAG_Ver(message, P, IIccss, R)); //#MG sig: false one @@ -134,7 +134,7 @@ TEST(ringct, MG_sigs) sk[j] = xx[ind][j]; } sk[2] = skGen();//assume we don't know one of the private keys.. - IIccss = MLSAG_Gen(message, P, sk, NULL, NULL, ind, R, hw::get_device("default")); + IIccss = MLSAG_Gen(message, P, sk, ind, R, hw::get_device("default")); ASSERT_FALSE(MLSAG_Ver(message, P, IIccss, R)); } @@ -178,13 +178,13 @@ TEST(ringct, CLSAG) insk.mask = t; // bad message - clsag = rct::proveRctCLSAGSimple(zero(),pubs,insk,t2,Cout,NULL,NULL,NULL,idx,hw::get_device("default")); + clsag = rct::proveRctCLSAGSimple(zero(),pubs,insk,t2,Cout,idx,hw::get_device("default")); ASSERT_FALSE(rct::verRctCLSAGSimple(message,clsag,pubs,Cout)); // bad index at creation try { - clsag = rct::proveRctCLSAGSimple(message,pubs,insk,t2,Cout,NULL,NULL,NULL,(idx + 1) % N,hw::get_device("default")); + clsag = rct::proveRctCLSAGSimple(message,pubs,insk,t2,Cout,(idx + 1) % N,hw::get_device("default")); ASSERT_FALSE(rct::verRctCLSAGSimple(message,clsag,pubs,Cout)); } catch (...) { /* either exception, or failure to verify above */ } @@ -195,7 +195,7 @@ TEST(ringct, CLSAG) ctkey insk2; insk2.dest = insk.dest; insk2.mask = skGen(); - clsag = rct::proveRctCLSAGSimple(message,pubs,insk2,t2,Cout,NULL,NULL,NULL,idx,hw::get_device("default")); + clsag = rct::proveRctCLSAGSimple(message,pubs,insk2,t2,Cout,idx,hw::get_device("default")); ASSERT_FALSE(rct::verRctCLSAGSimple(message,clsag,pubs,Cout)); } catch (...) { /* either exception, or failure to verify above */ } @@ -205,7 +205,7 @@ TEST(ringct, CLSAG) pubs[idx].mask = scalarmultBase(skGen()); try { - clsag = rct::proveRctCLSAGSimple(message,pubs,insk,t2,Cout,NULL,NULL,NULL,idx,hw::get_device("default")); + clsag = rct::proveRctCLSAGSimple(message,pubs,insk,t2,Cout,idx,hw::get_device("default")); ASSERT_FALSE(rct::verRctCLSAGSimple(message,clsag,pubs,Cout)); } catch (...) { /* either exception, or failure to verify above */ } @@ -217,7 +217,7 @@ TEST(ringct, CLSAG) ctkey insk2; insk2.dest = skGen(); insk2.mask = insk.mask; - clsag = rct::proveRctCLSAGSimple(message,pubs,insk2,t2,Cout,NULL,NULL,NULL,idx,hw::get_device("default")); + clsag = rct::proveRctCLSAGSimple(message,pubs,insk2,t2,Cout,idx,hw::get_device("default")); ASSERT_FALSE(rct::verRctCLSAGSimple(message,clsag,pubs,Cout)); } catch (...) { /* either exception, or failure to verify above */ } @@ -227,14 +227,14 @@ TEST(ringct, CLSAG) pubs[idx].dest = scalarmultBase(skGen()); try { - clsag = rct::proveRctCLSAGSimple(message,pubs,insk,t2,Cout,NULL,NULL,NULL,idx,hw::get_device("default")); + clsag = rct::proveRctCLSAGSimple(message,pubs,insk,t2,Cout,idx,hw::get_device("default")); ASSERT_FALSE(rct::verRctCLSAGSimple(message,clsag,pubs,Cout)); } catch (...) { /* either exception, or failure to verify above */ } pubs[idx] = backup; // Test correct signature - clsag = rct::proveRctCLSAGSimple(message,pubs,insk,t2,Cout,NULL,NULL,NULL,idx,hw::get_device("default")); + clsag = rct::proveRctCLSAGSimple(message,pubs,insk,t2,Cout,idx,hw::get_device("default")); ASSERT_TRUE(rct::verRctCLSAGSimple(message,clsag,pubs,Cout)); // empty s @@ -340,12 +340,12 @@ TEST(ringct, range_proofs) //compute rct data with mixin 3 - should fail since full type with > 1 input bool ok = false; - try { genRct(rct::zero(), sc, pc, destinations, amounts, amount_keys, NULL, NULL, 3, rct_config, hw::get_device("default")); } + try { genRct(rct::zero(), sc, pc, destinations, amounts, amount_keys, 3, rct_config, hw::get_device("default")); } catch(...) { ok = true; } ASSERT_TRUE(ok); //compute rct data with mixin 3 - rctSig s = genRctSimple(rct::zero(), sc, pc, destinations, inamounts, amounts, amount_keys, NULL, NULL, 0, 3, rct_config, hw::get_device("default")); + rctSig s = genRctSimple(rct::zero(), sc, pc, destinations, inamounts, amounts, amount_keys, 0, 3, rct_config, hw::get_device("default")); //verify rct data ASSERT_TRUE(verRctSimple(s)); @@ -362,7 +362,7 @@ TEST(ringct, range_proofs) //compute rct data with mixin 3 - s = genRctSimple(rct::zero(), sc, pc, destinations, inamounts, amounts, amount_keys, NULL, NULL, 0, 3, rct_config, hw::get_device("default")); + s = genRctSimple(rct::zero(), sc, pc, destinations, inamounts, amounts, amount_keys, 0, 3, rct_config, hw::get_device("default")); //verify rct data ASSERT_FALSE(verRctSimple(s)); @@ -410,7 +410,7 @@ TEST(ringct, range_proofs_with_fee) const rct::RCTConfig rct_config { RangeProofBorromean, 0 }; //compute rct data with mixin 3 - rctSig s = genRctSimple(rct::zero(), sc, pc, destinations, inamounts, amounts, amount_keys, NULL, NULL, 1, 3, rct_config, hw::get_device("default")); + rctSig s = genRctSimple(rct::zero(), sc, pc, destinations, inamounts, amounts, amount_keys, 1, 3, rct_config, hw::get_device("default")); //verify rct data ASSERT_TRUE(verRctSimple(s)); @@ -427,7 +427,7 @@ TEST(ringct, range_proofs_with_fee) //compute rct data with mixin 3 - s = genRctSimple(rct::zero(), sc, pc, destinations, inamounts, amounts, amount_keys, NULL, NULL, 500, 3, rct_config, hw::get_device("default")); + s = genRctSimple(rct::zero(), sc, pc, destinations, inamounts, amounts, amount_keys, 500, 3, rct_config, hw::get_device("default")); //verify rct data ASSERT_FALSE(verRctSimple(s)); @@ -486,7 +486,7 @@ TEST(ringct, simple) xmr_amount txnfee = 1; const rct::RCTConfig rct_config { RangeProofBorromean, 0 }; - rctSig s = genRctSimple(message, sc, pc, destinations,inamounts, outamounts, amount_keys, NULL, NULL, txnfee, 2, rct_config, hw::get_device("default")); + rctSig s = genRctSimple(message, sc, pc, destinations,inamounts, outamounts, amount_keys, txnfee, 2, rct_config, hw::get_device("default")); //verify ring ct signature ASSERT_TRUE(verRctSimple(s)); @@ -521,7 +521,7 @@ static rct::rctSig make_sample_rct_sig(int n_inputs, const uint64_t input_amount } const rct::RCTConfig rct_config { RangeProofBorromean, 0 }; - return genRct(rct::zero(), sc, pc, destinations, amounts, amount_keys, NULL, NULL, 3, rct_config, hw::get_device("default")); + return genRct(rct::zero(), sc, pc, destinations, amounts, amount_keys, 3, rct_config, hw::get_device("default")); } static rct::rctSig make_sample_simple_rct_sig(int n_inputs, const uint64_t input_amounts[], int n_outputs, const uint64_t output_amounts[], uint64_t fee) @@ -548,7 +548,7 @@ static rct::rctSig make_sample_simple_rct_sig(int n_inputs, const uint64_t input } const rct::RCTConfig rct_config { RangeProofBorromean, 0 }; - return genRctSimple(rct::zero(), sc, pc, destinations, inamounts, outamounts, amount_keys, NULL, NULL, fee, 3, rct_config, hw::get_device("default")); + return genRctSimple(rct::zero(), sc, pc, destinations, inamounts, outamounts, amount_keys, fee, 3, rct_config, hw::get_device("default")); } static bool range_proof_test(bool expected_valid, diff --git a/tests/unit_tests/serialization.cpp b/tests/unit_tests/serialization.cpp index 9e8a28f7c..87571e5b5 100644 --- a/tests/unit_tests/serialization.cpp +++ b/tests/unit_tests/serialization.cpp @@ -594,7 +594,7 @@ TEST(Serialization, serializes_ringct_types) destinations.push_back(Pk); //compute rct data with mixin 3 const rct::RCTConfig rct_config{ rct::RangeProofPaddedBulletproof, 2 }; - s0 = rct::genRctSimple(rct::zero(), sc, pc, destinations, inamounts, amounts, amount_keys, NULL, NULL, 0, 3, rct_config, hw::get_device("default")); + s0 = rct::genRctSimple(rct::zero(), sc, pc, destinations, inamounts, amounts, amount_keys, 0, 3, rct_config, hw::get_device("default")); ASSERT_FALSE(s0.p.MGs.empty()); ASSERT_TRUE(s0.p.CLSAGs.empty()); @@ -619,7 +619,7 @@ TEST(Serialization, serializes_ringct_types) ASSERT_EQ(bp0, bp1); const rct::RCTConfig rct_config_clsag{ rct::RangeProofPaddedBulletproof, 3 }; - s0 = rct::genRctSimple(rct::zero(), sc, pc, destinations, inamounts, amounts, amount_keys, NULL, NULL, 0, 3, rct_config_clsag, hw::get_device("default")); + s0 = rct::genRctSimple(rct::zero(), sc, pc, destinations, inamounts, amounts, amount_keys, 0, 3, rct_config_clsag, hw::get_device("default")); ASSERT_FALSE(s0.p.CLSAGs.empty()); ASSERT_TRUE(s0.p.MGs.empty()); diff --git a/tests/unit_tests/sha256.cpp b/tests/unit_tests/sha256.cpp index 486f31c5b..7a96a783b 100644 --- a/tests/unit_tests/sha256.cpp +++ b/tests/unit_tests/sha256.cpp @@ -26,10 +26,13 @@ // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#include <boost/filesystem.hpp> + #include "gtest/gtest.h" #include "common/util.h" #include "string_tools.h" +#include "unit_tests_utils.h" static bool check(const std::string &data, const char *expected_hash_hex) { @@ -39,7 +42,26 @@ static bool check(const std::string &data, const char *expected_hash_hex) return tools::sha256sum((const uint8_t*)data.data(), data.size(), hash) && hash == expected_hash; } +static std::string file_to_hex_hash(const std::string& filename) + { + const boost::filesystem::path full_path = unit_test::data_dir / "sha256sum" / filename; + + crypto::hash hash; + if (!tools::sha256sum(full_path.string(), hash)) { + throw std::runtime_error("sha256sum failed"); + } + + const std::string data_cstr(hash.data, sizeof(hash.data)); + const std::string hex_hash = epee::string_tools::buff_to_hex_nodelimer(data_cstr); + + return hex_hash; + } + TEST(sha256, empty) { ASSERT_TRUE(check(std::string(), "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")); } TEST(sha256, small) { ASSERT_TRUE(check("0123456789", "84d89877f0d4041efb6bf91a16f0248f2fd573e6af05c19f96bedb9f882f7882")); } TEST(sha256, large) { ASSERT_TRUE(check(std::string(65536*256, 0), "080acf35a507ac9849cfcba47dc2ad83e01b75663a516279c8b9d243b719643e")); } +TEST(sha256, emptyfile) { EXPECT_EQ(file_to_hex_hash("empty.txt"), "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); } +TEST(sha256, smallfile) { EXPECT_EQ(file_to_hex_hash("small_file.txt"), "91c60f6d9ad0235306115913febccb93a5014bf4cea1ecd1fa33f3cf07ad9e8d"); } +TEST(sha256, largefile) { EXPECT_EQ(file_to_hex_hash("CLSAG.pdf"), "c38699c9a235a70285165ff8cce0bf3e48989de8092c15514116ca4c95d41e3f"); } +TEST(sha256, noexist) { crypto::hash hash; EXPECT_FALSE(tools::sha256sum("this_file_does_not_exist.exe", hash)); } diff --git a/utils/build_scripts/windows.bat b/utils/build_scripts/windows.bat deleted file mode 100644 index 77ffd1c96..000000000 --- a/utils/build_scripts/windows.bat +++ /dev/null @@ -1,45 +0,0 @@ -:: Copyright (c) 2014-2022, The Monero Project -:: -:: All rights reserved. -:: -:: Redistribution and use in source and binary forms, with or without modification, are -:: permitted provided that the following conditions are met: -:: -:: 1. Redistributions of source code must retain the above copyright notice, this list of -:: conditions and the following disclaimer. -:: -:: 2. Redistributions in binary form must reproduce the above copyright notice, this list -:: of conditions and the following disclaimer in the documentation and/or other -:: materials provided with the distribution. -:: -:: 3. Neither the name of the copyright holder nor the names of its contributors may be -:: used to endorse or promote products derived from this software without specific -:: prior written permission. -:: -:: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -:: EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -:: MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -:: THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -:: SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -:: PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -:: INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -:: STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -:: THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -:: Set the following variables according to your environment -set BuildProcessorArchitecture=64 -set LocationDependencyBoostRoot=D:\Development\boost_1_55_0 -set LocationEnvironmentVariableSetterMsbuild=C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\vcvarsall.bat - -call "%LocationEnvironmentVariableSetterMsbuild%" -set LocationDependencyBoostLibrary=%LocationDependencyBoostRoot%\lib%BuildProcessorArchitecture%-msvc-%VisualStudioVersion% - -cd ..\.. -set LocationBuildSource=%CD% -mkdir build\win%BuildProcessorArchitecture% -cd build\win%BuildProcessorArchitecture% - -cmake -G "Visual Studio %VisualStudioVersion:.0=% Win%BuildProcessorArchitecture%" -DBOOST_ROOT="%LocationDependencyBoostRoot%" -DBOOST_LIBRARYDIR="%LocationDependencyBoostLibrary%" "%LocationBuildSource%" -msbuild Project.sln /p:Configuration=Release - -pause diff --git a/utils/fish/README.md b/utils/fish/README.md new file mode 100644 index 000000000..7d7c58c28 --- /dev/null +++ b/utils/fish/README.md @@ -0,0 +1,2 @@ +## Fish shell completions for Monero +This folder has basic Fish completions for `monerod`, `monero-wallet-cli`, and `monero-wallet-rpc`. To use them, put those files (or symlink them) inside `~/.config/fish/completions/` or wherever your Fish completion files are (see [https://fishshell.com/docs/current/completions.html#where-to-put-completions](https://fishshell.com/docs/current/completions.html#where-to-put-completions)) diff --git a/utils/fish/monero-wallet-cli.fish b/utils/fish/monero-wallet-cli.fish new file mode 100644 index 000000000..c9c878dba --- /dev/null +++ b/utils/fish/monero-wallet-cli.fish @@ -0,0 +1,61 @@ +complete -c monero-wallet-cli -f + +complete -c monero-wallet-cli -l help -d "Produce help message" +complete -c monero-wallet-cli -l version -d "Output version information" +complete -c monero-wallet-cli -l daemon-address -r -d "Use daemon instance at <host>:<port>" +complete -c monero-wallet-cli -l daemon-host -r -d "Use daemon instance at host <arg> instead of localhost" +complete -c monero-wallet-cli -l proxy -r -d "[<ip>:]<port> socks proxy to use for daemon connections" +complete -c monero-wallet-cli -l trusted-daemon -d "Enable commands which rely on a trusted" +complete -c monero-wallet-cli -l untrusted-daemon -d "Disable⋅commands⋅which⋅rely⋅on⋅a trusted daemon" +complete -c monero-wallet-cli -l password -r -d "Wallet⋅password⋅(escape/quote⋅as needed)" +complete -c monero-wallet-cli -l password-file -r -F -d "Wallet⋅password⋅file" +complete -c monero-wallet-cli -l daemon-port -r -d "Use daemon instance at port <arg> instead of 18081" +complete -c monero-wallet-cli -l daemon-login -r -d "Specify username[:password] for daemon RPC client" +complete -c monero-wallet-cli -l daemon-ssl -x -a "enabled disabled autodetect" -d "Enable SSL on daemon RPC connections. Default: autodetect" +complete -c monero-wallet-cli -l daemon-ssl-private-key -r -k -a "(__fish_complete_suffix .pem)" -d "Path to a PEM format private key" +complete -c monero-wallet-cli -l daemon-ssl-certificate -r -k -a "(__fish_complete_suffix .pem)" -d "Path to a PEM format certificate" +complete -c monero-wallet-cli -l daemon-ssl-ca-certificates -r -F -d "Path to file containing concatenated PEM format certificate(s) to replace system CA(s)." +complete -c monero-wallet-cli -l daemon-ssl-allowed-fingerprints -r -d "List of valid fingerprints of allowed RPC servers" +complete -c monero-wallet-cli -l daemon-ssl-allow-any-cert -d "Allow any SSL certificate from the daemon" +complete -c monero-wallet-cli -l daemon-ssl-allow-chained -d "Allow user (via --daemon-ssl-ca-certifi cates) chain certificates" +complete -c monero-wallet-cli -l testnet -d "For testnet. Daemon must also be launched with --testnet flag" +complete -c monero-wallet-cli -l stagenet -d "For stagenet. Daemon must also be launched with --stagenet flag" +complete -c monero-wallet-cli -l shared-ringdb-dir -r -F -d "Set shared ring database path" +complete -c monero-wallet-cli -l kdf-rounds -r -d "Number of rounds for the key derivation function. Default: 1" +complete -c monero-wallet-cli -l bitmessage-address -r -d "Use PyBitmessage instance at URL <arg>. Default: http://localhost:8442/" +complete -c monero-wallet-cli -l bitmessage-login -r -d "Specify <arg> as username:password for PyBitmessage API. Default: username:password" +complete -c monero-wallet-cli -l hw-device -r -d "HW device to use" +complete -c monero-wallet-cli -l hw-device-deriv-path -r -d "HW device wallet derivation path (e.g., SLIP-10)" +complete -c monero-wallet-cli -l tx-notify -r -d "Run a program for each new incoming transaction, '%s' will be replaced by the transaction hash" +complete -c monero-wallet-cli -l no-dns -d "Do not use DNS" +complete -c monero-wallet-cli -l offline -d "Do not connect to a daemon, nor use DNS" +complete -c monero-wallet-cli -l extra-entropy -r -F -d "File containing extra entropy to initialize the PRNG (any data, aim for 256 bits of entropy to be useful, which typically means more than 256 bits of data)" +complete -c monero-wallet-cli -l wallet-file -r -F -d "Use wallet <arg>" +complete -c monero-wallet-cli -l generate-new-wallet -r -F -d "Generate new wallet and save it to <arg>" +complete -c monero-wallet-cli -l generate-from-device -r -F -d "Generate new wallet from device and save it to <arg>" +complete -c monero-wallet-cli -l generate-from-view-key -r -d "Generate incoming-only wallet from view key" +complete -c monero-wallet-cli -l generate-from-spend-key -r -d "Generate deterministic wallet from spend key" +complete -c monero-wallet-cli -l generate-from-keys -r -d "Generate wallet from private keys" +complete -c monero-wallet-cli -l generate-from-multisig-keys -r -d "Generate a master wallet from multisig wallet keys" +complete -c monero-wallet-cli -l generate-from-json -r -k -a "(__fish_complete_suffix .json)" -d "Generate wallet from JSON format file" +complete -c monero-wallet-cli -l mnemonic-language -r -d "Language for mnemonic" +complete -c monero-wallet-cli -l command -d "" +complete -c monero-wallet-cli -l restore-deterministic-wallet -d "Recover wallet using Electrum-style mnemonic seed" +complete -c monero-wallet-cli -l restore-from-seed -d "alias for --restore-deterministic-wallet" +complete -c monero-wallet-cli -l restore-multisig-wallet -d "Recover multisig wallet using Electrum-style mnemonic seed" +complete -c monero-wallet-cli -l non-deterministic -d "Generate non-deterministic view and spend keys" +complete -c monero-wallet-cli -l electrum-seed -r -d "Specify Electrum seed for wallet recovery/creation" +complete -c monero-wallet-cli -l allow-mismatched-daemon-version -d "Allow communicating with a daemon that uses a different RPC version" +complete -c monero-wallet-cli -l restore-height -r -d "Restore from specific blockchain height. Default: 0" +complete -c monero-wallet-cli -l restore-date -r -d "Restore from estimated blockchain height on specified date" +complete -c monero-wallet-cli -l do-not-relay -d "The newly created transaction will not be relayed to the monero network" +complete -c monero-wallet-cli -l create-address-file -d "Create an address file for new wallets" +complete -c monero-wallet-cli -l subaddress-lookahead -r -d "Set subaddress lookahead sizes to <major>:<minor>" +complete -c monero-wallet-cli -l use-english-language-names -d "Display English language names" +complete -c monero-wallet-cli -l rpc-client-secret-key -r -d "Set RPC client secret key for RPC payments" +complete -c monero-wallet-cli -l log-file -r -F -d "Specify log file" +complete -c monero-wallet-cli -l log-level -r -a "0 1 2 3 4" -d "0-4 or categories" +complete -c monero-wallet-cli -l max-log-file-size -r -d "Specify maximum log file size [B]. Default: 104850000" +complete -c monero-wallet-cli -l max-log-files -r -d "Specify maximum number of rotated log files to be saved (no limit by setting to 0). Default: 50" +complete -c monero-wallet-cli -l max-concurrency -d "Max number of threads to use for a parallel job. Default: 1" +complete -c monero-wallet-cli -l config-file -r -F -d "Config file" diff --git a/utils/fish/monero-wallet-rpc.fish b/utils/fish/monero-wallet-rpc.fish new file mode 100644 index 000000000..a64e112ef --- /dev/null +++ b/utils/fish/monero-wallet-rpc.fish @@ -0,0 +1,65 @@ +complete -c monero-wallet-rpc -f + +complete -c monero-wallet-rpc -l help -d "Produce help message" +complete -c monero-wallet-rpc -l version -d "Output version information " +complete -c monero-wallet-rpc -l daemon-address -r -d "Use daemon instance at <host>:<port>" +complete -c monero-wallet-rpc -l daemon-host -r -d "Use daemon instance at host <arg> instead of localhost" +complete -c monero-wallet-rpc -l proxy -r -d "[<ip>:]<port> socks proxy to use for daemon connections" +complete -c monero-wallet-rpc -l trusted-daemon -d "Enable commands which rely on a trusted daemon" +complete -c monero-wallet-rpc -l untrusted-daemon -d "Disable commands which rely on a trusted daemon" +complete -c monero-wallet-rpc -l password -r -d "Wallet password (escape/quote as needed)" +complete -c monero-wallet-rpc -l password-file -r -F -d "Wallet password file" +complete -c monero-wallet-rpc -l daemon-port -r -d "Use daemon instance at port <arg> instead of 18081. Default: 0" +complete -c monero-wallet-rpc -l daemon-login -r -d "Specify username[:password] for daemon RPC client" +complete -c monero-wallet-rpc -l daemon-ssl -x -a "enabled disabled autodetect" -d "Enable SSL on daemon RPC connections. Default: autodetect" +complete -c monero-wallet-rpc -l daemon-ssl-private-key -r -k -a "(__fish_complete_suffix .pem)" -d "Path to a PEM format private key" +complete -c monero-wallet-rpc -l daemon-ssl-certificate -r -k -a "(__fish_complete_suffix .pem)" -d "Path to a PEM format certificate" +complete -c monero-wallet-rpc -l daemon-ssl-ca-certificates -r -F -d "Path to file containing concatenated PEM format certificate(s) to replace system CA(s)." +complete -c monero-wallet-rpc -l daemon-ssl-allowed-fingerprints -r -d "List of valid fingerprints of allowed RPC servers" +complete -c monero-wallet-rpc -l daemon-ssl-allow-any-cert -d "Allow any SSL certificate from the daemon" +complete -c monero-wallet-rpc -l daemon-ssl-allow-chained -d "Allow user (via --daemon-ssl-ca-certifi cates) chain certificates" +complete -c monero-wallet-rpc -l testnet -d "For testnet. Daemon must also be launched with --testnet flag" +complete -c monero-wallet-rpc -l stagenet -d "For stagenet. Daemon must also be launched with --stagenet flag" +complete -c monero-wallet-rpc -l shared-ringdb-dir -r -F -d "Set shared ring database path" +complete -c monero-wallet-rpc -l kdf-rounds -r -d "Number of rounds for the key derivation function. Default: 1" +complete -c monero-wallet-rpc -l bitmessage-address -r -d "Use PyBitmessage instance at URL <arg>. Default: http://localhost:8442/" +complete -c monero-wallet-rpc -l bitmessage-login -r -d "Specify <arg> as username:password for PyBitmessage API. Default: username:password" +complete -c monero-wallet-rpc -l hw-device -r -d "HW device to use" +complete -c monero-wallet-rpc -l hw-device-deriv-path -r -d "HW device wallet derivation path (e.g., SLIP-10)" +complete -c monero-wallet-rpc -l tx-notify -r -d "Run a program for each new incoming transaction, '%s' will be replaced by the transaction hash" +complete -c monero-wallet-rpc -l no-dns -d "Do not use DNS" +complete -c monero-wallet-rpc -l offline -d "Do not connect to a daemon, nor use DNS" +complete -c monero-wallet-rpc -l extra-entropy -r -F -d "File containing extra entropy to initialize the PRNG (any data, aim for 256 bits of entropy to be useful, which typically means more than 256 bits of data)" +complete -c monero-wallet-rpc -l rpc-bind-port -r -d "Sets bind port for server" +complete -c monero-wallet-rpc -l disable-rpc-login -d "Disable HTTP authentication for RPC connections served by this process" +complete -c monero-wallet-rpc -l restricted-rpc -d "Restricts to view-only commands" +complete -c monero-wallet-rpc -l rpc-bind-ip -r -d "Specify IP to bind RPC server. Default: 127.0.0.1" +complete -c monero-wallet-rpc -l rpc-bind-ipv6-address -r -d "Specify IPv6 address to bind RPC server. Default: ::1" +complete -c monero-wallet-rpc -l rpc-restricted-bind-ip -r -d "Specify IP to bind restricted RPC server. Default: 127.0.0.1" +complete -c monero-wallet-rpc -l rpc-restricted-bind-ipv6-address -r -d "Specify IPv6 address to bind restricted RPC server. Default: ::1" +complete -c monero-wallet-rpc -l rpc-use-ipv6 -d "Allow IPv6 for RPC" +complete -c monero-wallet-rpc -l rpc-ignore-ipv4 -d "Ignore unsuccessful IPv4 bind for RPC" +complete -c monero-wallet-rpc -l rpc-login -r -d "Specify username[:password] required for RPC server" +complete -c monero-wallet-rpc -l confirm-external-bind -d "Confirm rpc-bind-ip value is NOT a loopback (local) IP" +complete -c monero-wallet-rpc -l rpc-access-control-origins -r -d "Specify a comma separated list of origins to allow cross origin resource sharing" +complete -c monero-wallet-rpc -l rpc-ssl -x -a "enabled disabled autodetect" -d "Enable SSL on RPC connections. Default: autodetect" +complete -c monero-wallet-rpc -l rpc-ssl-private-key -r -k -a "(__fish_complete_suffix .pem)" -d "Path to a PEM format private key" +complete -c monero-wallet-rpc -l rpc-ssl-certificate -r -k -a "(__fish_complete_suffix .pem)" -d "Path to a PEM format certificate" +complete -c monero-wallet-rpc -l rpc-ssl-ca-certificates -r -F -d "Path to file containing concatenated PEM format certificate(s) to replace system CA(s)." +complete -c monero-wallet-rpc -l rpc-ssl-allowed-fingerprints -r -d "List of certificate fingerprints to allow" +complete -c monero-wallet-rpc -l rpc-ssl-allow-chained -d "Allow user (via --rpc-ssl-certificates) chain certificates" +complete -c monero-wallet-rpc -l disable-rpc-ban -d "Do not ban hosts on RPC errors" +complete -c monero-wallet-rpc -l wallet-file -r -F -d "Use wallet <arg>" +complete -c monero-wallet-rpc -l generate-from-json -r -k -a "(__fish_complete_suffix .json)" -d "Generate wallet from JSON format file" +complete -c monero-wallet-rpc -l wallet-dir -r -F -d "Directory for newly created wallets" +complete -c monero-wallet-rpc -l prompt-for-password -d "Prompts for password when not provided" +complete -c monero-wallet-rpc -l rpc-client-secret-key -r -d "Set RPC client secret key for RPC payments" +complete -c monero-wallet-rpc -l detach -d "Run as daemon" +complete -c monero-wallet-rpc -l pidfile -r -F -d "File path to write the daemon's PID to (optional, requires --detach)" +complete -c monero-wallet-rpc -l non-interactive -d "Run non-interactive" +complete -c monero-wallet-rpc -l log-file -r -F -d "Specify log file" +complete -c monero-wallet-rpc -l log-level -r -a "0 1 2 3 4" -d "0-4 or categories" +complete -c monero-wallet-rpc -l max-log-file-size -r -d "Specify maximum log file size [B]. Default: 104850000" +complete -c monero-wallet-rpc -l max-log-files -r -d "Specify maximum number of rotated log files to be saved (no limit by setting to 0). Default: 50" +complete -c monero-wallet-rpc -l max-concurrency -d "Max number of threads to use for a parallel job. Default: 1" +complete -c monero-wallet-rpc -l config-file -r -F -d "Config file" diff --git a/utils/fish/monerod.fish b/utils/fish/monerod.fish new file mode 100644 index 000000000..d2836a6b2 --- /dev/null +++ b/utils/fish/monerod.fish @@ -0,0 +1,113 @@ +complete -c monerod -f + +complete -c monerod -l help -d "Produce help message" +complete -c monerod -l version -d "Output version information" +complete -c monerod -l os-version -d "OS for which this executable was compiled" +complete -c monerod -l config-file -r -d "Specify configuration file" +complete -c monerod -l detach -d "Run as daemon" +complete -c monerod -l pidfile -r -F -d "File path to write the daemon's PID to (optional, requires --detach)" +complete -c monerod -l non-interactive -d "Run non-interactive" +complete -c monerod -l log-file -r -F -d "Specify log file" +complete -c monerod -l log-level -r -d "" +complete -c monerod -l max-log-file-size -r -d "Specify maximum log file size [B]. Default: 104850000" +complete -c monerod -l max-log-files -r -d "Specify maximum number of rotated log files to be saved (no limit by setting to 0). Default: 50" +complete -c monerod -l max-concurrency -r -d "Max number of threads to use for a parallel job. Default: 0" +complete -c monerod -l proxy -r -d "Network communication through proxy: <socks-ip:port> i.e. \"127.0.0.1:9050\"" +complete -c monerod -l proxy-allow-dns-leaks -d "Allow DNS leaks outside of proxy" +complete -c monerod -l public-node -d "Allow other users to use the node as a remote (restricted RPC mode, view-only commands) and advertise it over P2P" +complete -c monerod -l zmq-rpc-bind-ip -r -d "IP for ZMQ RPC server to listen on. Default: 127.0.0.1" +complete -c monerod -l zmq-rpc-bind-port -r -d "Port for ZMQ RPC server to listen on. Default: 18082, 28082 if 'testnet', 38082 if 'stagenet'" +complete -c monerod -l zmq-pub -r -d "Address for ZMQ pub - tcp://ip:port or ipc://path " +complete -c monerod -l no-zmq -d "Disable ZMQ RPC server [114/349]" +complete -c monerod -l data-dir -x -a "(__fish_complete_directories)" -d "Specify data directory" +complete -c monerod -l test-drop-download -d "For net tests: in download, discard ALL blocks instead checking/saving them (very fast)" +complete -c monerod -l test-drop-download-height -r -d "Like test-drop-download but discards only after around certain height. Default: 0" +complete -c monerod -l testnet -d "Run on testnet. The wallet must be launched with --testnet flag." +complete -c monerod -l stagenet -d "Run on stagenet. The wallet must be launched with --stagenet flag." +complete -c monerod -l regtest -d "Run in a regression testing mode." +complete -c monerod -l keep-fakechain -d "Don't delete any existing database when in fakechain mode." +complete -c monerod -l fixed-difficulty -r -d "Fixed difficulty used for testing. Default: 0" +complete -c monerod -l enforce-dns-checkpointing -d "checkpoints from DNS server will be enforced" +complete -c monerod -l prep-blocks-threads -r -d "Max number of threads to use when preparing block hashes in groups. Default: 4" +complete -c monerod -l fast-block-sync -r -d "Sync up most of the way by using embedded, known block hashes. Default: 1" +complete -c monerod -l show-time-stats -r -d "(=0) Show time-stats when processing blocks/txs and disk synchronization. Default: 0" +complete -c monerod -l block-sync-size -r -d "(=0) How many blocks to sync at once during chain synchronization (0 = adaptive). Default: 0" +complete -c monerod -l check-updates -x -a "disabled notify download update" -d "Check for new versions of monero. Default: notify" +complete -c monerod -l fluffy-blocks -d "Relay blocks as fluffy blocks (obsolete, now default)" +complete -c monerod -l no-fluffy-blocks -d "Relay blocks as normal blocks" +complete -c monerod -l test-dbg-lock-sleep -r -d "Sleep time in ms, defaults to 0 (off), used to debug before/after locking mutex. Values 100 to 1000 are good for tests." +complete -c monerod -l offline -d "Do not listen for peers, nor connect to any" +complete -c monerod -l disable-dns-checkpoints -d "Do not retrieve checkpoints from DNS" +complete -c monerod -l block-download-max-size -r -d "Set maximum size of block download queue in bytes (0 for default)" +complete -c monerod -l sync-pruned-blocks -d "Allow syncing from nodes with only pruned blocks" +complete -c monerod -l max-txpool-weight -r -d "Set maximum txpool weight in bytes. Default: 648000000" +complete -c monerod -l block-notify -r -d "Run a program for each new block, '%s' will be replaced by the block hash" +complete -c monerod -l prune-blockchain -d "Prune blockchain" +complete -c monerod -l reorg-notify -r -d "Run a program for each reorg, '%s' will be replaced by the split height, '%h' will be replaced by the new blockchain height, '%n' will be replaced by the number of new blocks in the new chain, and '%d' will be replaced by the number of blocks discarded from the old chain" +complete -c monerod -l block-rate-notify -r -d "Run a program when the block rate undergoes large fluctuations. This might be a sign of large amounts of hash rate going on and off the Monero network, and thus be of potential interest in predicting attacks. %t will be replaced by the number of minutes for the observation window, %b by the number of blocks observed within that window, and %e by the number of blocks that was expected in that window. It is suggested that this notification is used to automatically increase the number of confirmations required before a payment is acted upon." +complete -c monerod -l keep-alt-blocks -d "Keep alternative blocks on restart" +complete -c monerod -l extra-messages-file -r -F -d "Specify file for extra messages to include into coinbase transactions" +complete -c monerod -l start-mining -r -d "Specify wallet address to mining for" +complete -c monerod -l mining-threads -r -d "Specify mining threads count" +complete -c monerod -l bg-mining-enable -d "Enable background mining" +complete -c monerod -l bg-mining-ignore-battery -d "If true, assumes plugged in when unable to query system power status" +complete -c monerod -l bg-mining-min-idle-interval -r -d "Specify min lookback interval in seconds for determining idle state" +complete -c monerod -l bg-mining-idle-threshold -r -d "Specify minimum avg idle percentage over lookback interval" +complete -c monerod -l bg-mining-miner-target -r -d "Specify maximum percentage cpu use by miner(s)" +complete -c monerod -l db-sync-mode -r -d "Specify sync option, using format [safe|fast|fastest]:[sync|async]:[<nblo cks_per_sync>[blocks]|<nbytes_per_sync> [bytes]]. Default: fast:async:250000000bytes" +complete -c monerod -l db-salvage -d "Try to salvage a blockchain database if it seems corrupted" +complete -c monerod -l p2p-bind-ip -r -d "Interface for p2p network protocol (IPv4). Default: 0.0.0.0" +complete -c monerod -l p2p-bind-ipv6-address -r -d "Interface for p2p network protocol (IPv6). Default: ::" +complete -c monerod -l p2p-bind-port -r -d "Port for p2p network protocol (IPv4). Default: 18080, 28080 if 'testnet', 38080 if 'stagenet'" +complete -c monerod -l p2p-bind-port-ipv6 -d "Port for p2p network protocol (IPv6). Default: 18080, 28080 if 'testnet', 38080 if 'stagenet'" +complete -c monerod -l p2p-use-ipv6 -d "Enable IPv6 for p2p" +complete -c monerod -l p2p-ignore-ipv4 -d "Ignore unsuccessful IPv4 bind for p2p" +complete -c monerod -l p2p-external-port -r -d "External port for p2p network protocol (if port forwarding used with NAT). Default: 0" +complete -c monerod -l allow-local-ip -d "Allow local ip add to peer list, mostly in debug purposes" +complete -c monerod -l add-peer -r -d "Manually add peer to local peerlist" +complete -c monerod -l add-priority-node -r -d "Specify list of peers to connect to and attempt to keep the connection open" +complete -c monerod -l add-exclusive-node -r -d "Specify list of peers to connect to only. If this option is given the options add-priority-node and seed-node are ignored" +complete -c monerod -l seed-node -r -d "Connect to a node to retrieve peer addresses, and disconnect" +complete -c monerod -l tx-proxy -r -d "Send local txes through proxy: <network-type>,<socks-ip:port>[,max_con nections][,disable_noise] i.e. \"tor,127.0.0.1:9050,100,disable_noise\"" +complete -c monerod -l anonymous-inbound -r -d "<hidden-service-address>,<[bind-ip:]port>[,max_connections] i.e. \"x.onion,127.0.0.1:18083,100\"" +complete -c monerod -l ban-list -r -F -d "Specify ban list file, one IP address per line" +complete -c monerod -l hide-my-port -d "Do not announce yourself as peerlist candidate" +complete -c monerod -l no-sync -d "Don't synchronize the blockchain with other peers" +complete -c monerod -l enable-dns-blocklist -d "Apply realtime blocklist from DNS" +complete -c monerod -l no-igd -d "Disable UPnP port mapping" +complete -c monerod -l igd -r -a "Enabled disabled enabled" -d "UPnP port mapping. Default: delayed" +complete -c monerod -l out-peers -r -d "Set max number of out peers. Default: -1" +complete -c monerod -l in-peers -r -d "Set max number of in peers. Default: -1" +complete -c monerod -l tos-flag -r -d "Set TOS flag. Default: -1" +complete -c monerod -l limit-rate-up -r -d "Set limit-rate-up [kB/s]. Default: 2048" +complete -c monerod -l limit-rate-down -r -d "Set limit-rate-down [kB/s]. Default: 8192" +complete -c monerod -l limit-rate -r -d "Set limit-rate [kB/s]. Default: -1" +complete -c monerod -l pad-transactions -d "Pad relayed transactions to help defend against traffic volume analysis" +complete -c monerod -l max-connections-per-ip -r -d "Maximum number of connections allowed from the same IP address. Default: 1" +complete -c monerod -l rpc-bind-port -r -d "Port for RPC server. Default: 18081, 28081 if 'testnet', 38081 if 'stagenet'" +complete -c monerod -l rpc-restricted-bind-port -r -d "Port for restricted RPC server" +complete -c monerod -l restricted-rpc -d "Restrict RPC to view only commands and do not return privacy sensitive data in RPC calls" +complete -c monerod -l bootstrap-daemon-address -r -d "URL of a 'bootstrap' remote daemon that the connected wallets can use while this daemon is still not fully synced. Use 'auto' to enable automatic public nodes discovering and bootstrap daemon switching" +complete -c monerod -l bootstrap-daemon-login -r -d "Specify username:password for the bootstrap daemon login" +complete -c monerod -l bootstrap-daemon-proxy -r -d "<ip>:<port> socks proxy to use for bootstrap daemon connections" +complete -c monerod -l rpc-bind-ip -r -d "Specify IP to bind RPC server. Default: 127.0.0.1" +complete -c monerod -l rpc-bind-ipv6-address -r -d "Specify IPv6 address to bind RPC server. Default: ::1" +complete -c monerod -l rpc-restricted-bind-ip -r -d "Specify IP to bind restricted RPC server. Default: 127.0.0.1" +complete -c monerod -l rpc-restricted-bind-ipv6-address -r -d "Specify IPv6 address to bind restricted RPC server. Default: ::1" +complete -c monerod -l rpc-use-ipv6 -d "Allow IPv6 for RPC" +complete -c monerod -l rpc-ignore-ipv4 -d "Ignore unsuccessful IPv4 bind for RPC" +complete -c monerod -l rpc-login -d "Specify username[:password] required for RPC server" +complete -c monerod -l confirm-external-bind -d "Confirm rpc-bind-ip value is NOT a loopback (local) IP" +complete -c monerod -l rpc-access-control-origins -r -d "Specify a comma separated list of origins to allow cross origin resource sharing" +complete -c monerod -l rpc-ssl -x -a "enabled disabled autodetect" -d "Enable SSL on RPC connections. Default: autodetect" +complete -c monerod -l rpc-ssl-private-key -r -k -a "(__fish_complete_suffix .pem)" -d "Path to a PEM format private key" +complete -c monerod -l rpc-ssl-certificate -r -k -a "(__fish_complete_suffix .pem)" -d "Path to a PEM format certificate" +complete -c monerod -l rpc-ssl-ca-certificates -r -F -d "Path to file containing concatenated PEM format certificate(s) to replace system CA(s)." +complete -c monerod -l rpc-ssl-allowed-fingerprints -r -d "List of certificate fingerprints to allow" +complete -c monerod -l rpc-ssl-allow-chained -d "Allow user (via --rpc-ssl-certificates) chain certificates" +complete -c monerod -l disable-rpc-ban -d "Do not ban hosts on RPC errors" +complete -c monerod -l rpc-ssl-allow-any-cert -d "Allow any peer certificate" +complete -c monerod -l rpc-payment-address -r -d "Restrict RPC to clients sending micropayment to this address" +complete -c monerod -l rpc-payment-difficulty -r -d "Restrict RPC to clients sending micropayment at this difficulty. Default: 1000" +complete -c monerod -l rpc-payment-credits -r -d "Restrict RPC to clients sending micropayment, yields that many credits per payment. Default: 100" +complete -c monerod -l rpc-payment-allow-free-loopback -d "Allow free access from the loopback address (ie, the local host)" diff --git a/utils/python-rpc/framework/wallet.py b/utils/python-rpc/framework/wallet.py index 037beee84..01e937627 100644 --- a/utils/python-rpc/framework/wallet.py +++ b/utils/python-rpc/framework/wallet.py @@ -490,10 +490,11 @@ class Wallet(object): } return self.rpc.send_json_rpc_request(is_multisig) - def prepare_multisig(self): + def prepare_multisig(self, enable_multisig_experimental = False): prepare_multisig = { 'method': 'prepare_multisig', 'params' : { + 'enable_multisig_experimental': enable_multisig_experimental, }, 'jsonrpc': '2.0', 'id': '0' |