aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/depends.yml2
-rw-r--r--CMakeLists.txt1
-rw-r--r--Doxyfile4
-rw-r--r--README.md12
-rw-r--r--contrib/depends/README.md8
-rw-r--r--contrib/depends/packages/unbound.mk9
-rw-r--r--contrib/depends/patches/unbound/disable-glibc-reallocarray.patch14
-rw-r--r--contrib/epee/include/net/abstract_tcp_server2.h223
-rw-r--r--contrib/epee/include/net/abstract_tcp_server2.inl1708
-rw-r--r--contrib/epee/include/net/enums.h10
-rw-r--r--contrib/epee/include/net/http_server_handlers_map2.h26
-rw-r--r--contrib/epee/include/net/net_helper.h113
-rw-r--r--contrib/epee/include/net/net_ssl.h5
-rw-r--r--contrib/epee/include/string_coding.h94
-rw-r--r--contrib/epee/src/net_ssl.cpp118
-rw-r--r--contrib/gitian/README.md3
-rw-r--r--contrib/gitian/gitian-android.yml2
-rwxr-xr-xcontrib/gitian/gitian-build.py6
-rw-r--r--contrib/gitian/gitian-freebsd.yml2
-rw-r--r--contrib/gitian/gitian-linux.yml2
-rw-r--r--contrib/gitian/gitian-osx.yml2
-rw-r--r--contrib/gitian/gitian-win.yml2
-rw-r--r--docs/PORTABLE_STORAGE.md48
-rw-r--r--docs/images/storage_binary_example.pngbin538682 -> 0 bytes
-rw-r--r--external/boost/archive/portable_binary_archive.hpp4
-rw-r--r--external/db_drivers/liblmdb/mdb.c58
-rw-r--r--src/blockchain_db/blockchain_db.h16
-rw-r--r--src/blockchain_db/lmdb/db_lmdb.cpp126
-rw-r--r--src/blockchain_db/lmdb/db_lmdb.h1
-rw-r--r--src/blocks/checkpoints.datbin272772 -> 332676 bytes
-rw-r--r--src/checkpoints/checkpoints.cpp1
-rw-r--r--src/crypto/hash-extra-jh.c6
-rw-r--r--src/crypto/hash-extra-skein.c6
-rw-r--r--src/cryptonote_basic/cryptonote_format_utils.cpp4
-rw-r--r--src/cryptonote_config.h3
-rw-r--r--src/cryptonote_core/CMakeLists.txt1
-rw-r--r--src/cryptonote_core/blockchain.cpp2
-rw-r--r--src/cryptonote_core/cryptonote_tx_utils.cpp27
-rw-r--r--src/cryptonote_core/cryptonote_tx_utils.h4
-rw-r--r--src/cryptonote_core/tx_pool.cpp96
-rw-r--r--src/cryptonote_core/tx_pool.h13
-rw-r--r--src/device/device_ledger.cpp16
-rw-r--r--src/device/device_ledger.hpp4
-rw-r--r--src/device_trezor/device_trezor.cpp28
-rw-r--r--src/device_trezor/trezor/protocol.cpp225
-rw-r--r--src/device_trezor/trezor/protocol.hpp45
-rw-r--r--src/hardforks/hardforks.cpp6
-rw-r--r--src/multisig/CMakeLists.txt5
-rw-r--r--src/multisig/multisig_clsag_context.cpp257
-rw-r--r--src/multisig/multisig_clsag_context.h137
-rw-r--r--src/multisig/multisig_tx_builder_ringct.cpp1059
-rw-r--r--src/multisig/multisig_tx_builder_ringct.h120
-rw-r--r--src/ringct/rctSigs.cpp164
-rw-r--r--src/ringct/rctSigs.h19
-rw-r--r--src/rpc/core_rpc_server.cpp50
-rw-r--r--src/simplewallet/simplewallet.cpp24
-rw-r--r--src/simplewallet/simplewallet.h2
-rw-r--r--src/version.cpp.in4
-rw-r--r--src/wallet/api/wallet.cpp44
-rw-r--r--src/wallet/api/wallet.h1
-rw-r--r--src/wallet/api/wallet2_api.h7
-rw-r--r--src/wallet/wallet2.cpp429
-rw-r--r--src/wallet/wallet2.h42
-rw-r--r--tests/core_tests/chaingen.cpp4
-rw-r--r--tests/core_tests/multisig.cpp182
-rw-r--r--tests/core_tests/multisig.h2
-rw-r--r--tests/core_tests/rct.cpp2
-rw-r--r--tests/core_tests/wallet_tools.cpp2
-rw-r--r--tests/performance_tests/rct_mlsag.h4
-rw-r--r--tests/performance_tests/sig_clsag.h2
-rw-r--r--tests/performance_tests/sig_mlsag.h2
-rw-r--r--tests/trezor/trezor_tests.cpp94
-rw-r--r--tests/trezor/trezor_tests.h17
-rw-r--r--tests/unit_tests/bulletproofs.cpp3
-rw-r--r--tests/unit_tests/epee_boosted_tcp_server.cpp254
-rw-r--r--tests/unit_tests/ringct.cpp34
-rw-r--r--tests/unit_tests/serialization.cpp4
-rw-r--r--utils/fish/README.md2
-rw-r--r--utils/fish/monero-wallet-cli.fish61
-rw-r--r--utils/fish/monero-wallet-rpc.fish65
-rw-r--r--utils/fish/monerod.fish113
81 files changed, 4402 insertions, 1915 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/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)
diff --git a/Doxyfile b/Doxyfile
index 81b200986..9ce0d2247 100644
--- a/Doxyfile
+++ b/Doxyfile
@@ -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
diff --git a/README.md b/README.md
index b3422280d..5609e0f03 100644
--- a/README.md
+++ b/README.md
@@ -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/README.md b/contrib/depends/README.md
index 1aa5b276f..2f1d8424a 100644
--- a/contrib/depends/README.md
+++ b/contrib/depends/README.md
@@ -1,6 +1,6 @@
### Usage
-To build dependencies for the current arch+OS:
+To build dependencies for the current arch+OS, from this working directory:
```bash
make
@@ -20,10 +20,12 @@ make HOST=x86_64-w64-mingw32 -j4
A toolchain will be generated that's suitable for plugging into Monero's
cmake. In the above example, a dir named x86_64-w64-mingw32 will be
-created. To use it for Monero:
+created. To use it for Monero, from the top of the Monero source tree:
```bash
-cmake -DCMAKE_TOOLCHAIN=`pwd`/contrib/depends/x86_64-w64-mingw32
+mkdir build
+cd build
+cmake -DCMAKE_TOOLCHAIN_FILE=$PWD/../contrib/depends/x86_64-w64-mingw32/share/toolchain.cmake ..
```
Common `host-platform-triplets` for cross compilation are:
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/http_server_handlers_map2.h b/contrib/epee/include/net/http_server_handlers_map2.h
index ffb3f3b7e..be8787669 100644
--- a/contrib/epee/include/net/http_server_handlers_map2.h
+++ b/contrib/epee/include/net/http_server_handlers_map2.h
@@ -63,10 +63,6 @@
bool handled = false; \
if(false) return true; //just a stub to have "else if"
-#define MAP_URI2(pattern, callback) else if(std::string::npos != query_info.m_URI.find(pattern)) return callback(query_info, response_info, &m_conn_context);
-
-#define MAP_URI_AUTO_XML2(s_pattern, callback_f, command_type) //TODO: don't think i ever again will use xml - ambiguous and "overtagged" format
-
#define MAP_URI_AUTO_JON2_IF(s_pattern, callback_f, command_type, cond) \
else if((query_info.m_URI == s_pattern) && (cond)) \
{ \
@@ -139,8 +135,6 @@
MDEBUG( s_pattern << "() processed with " << ticks1-ticks << "/"<< ticks2-ticks1 << "/" << ticks3-ticks2 << "ms"); \
}
-#define CHAIN_URI_MAP2(callback) else {callback(query_info, response_info, m_conn_context);handled = true;}
-
#define END_URI_MAP2() return handled;}
@@ -225,26 +219,6 @@
#define MAP_JON_RPC_WE(method_name, callback_f, command_type) MAP_JON_RPC_WE_IF(method_name, callback_f, command_type, true)
-#define MAP_JON_RPC_WERI(method_name, callback_f, command_type) \
- else if(callback_name == method_name) \
-{ \
- PREPARE_OBJECTS_FROM_JSON(command_type) \
- epee::json_rpc::error_response fail_resp = AUTO_VAL_INIT(fail_resp); \
- fail_resp.jsonrpc = "2.0"; \
- fail_resp.id = req.id; \
- MINFO(m_conn_context << "calling RPC method " << method_name); \
- bool res = false; \
- try { res = callback_f(req.params, resp.result, fail_resp.error, response_info, &m_conn_context); } \
- catch (const std::exception &e) { MERROR(m_conn_context << "Failed to " << #callback_f << "(): " << e.what()); } \
- if (!res) \
- { \
- epee::serialization::store_t_to_json(static_cast<epee::json_rpc::error_response&>(fail_resp), response_info.m_body); \
- return true; \
- } \
- FINALIZE_OBJECTS_TO_JSON(method_name) \
- return true;\
-}
-
#define MAP_JON_RPC(method_name, callback_f, command_type) \
else if(callback_name == method_name) \
{ \
diff --git a/contrib/epee/include/net/net_helper.h b/contrib/epee/include/net/net_helper.h
index 0a35797fd..ee344561d 100644
--- a/contrib/epee/include/net/net_helper.h
+++ b/contrib/epee/include/net/net_helper.h
@@ -688,119 +688,6 @@ namespace net_utils
std::atomic<uint64_t> m_bytes_sent;
std::atomic<uint64_t> m_bytes_received;
};
-
-
- /************************************************************************/
- /* */
- /************************************************************************/
- class async_blocked_mode_client: public blocked_mode_client
- {
- public:
- async_blocked_mode_client():m_send_deadline(blocked_mode_client::m_io_service)
- {
-
- // No deadline is required until the first socket operation is started. We
- // set the deadline to positive infinity so that the actor takes no action
- // until a specific deadline is set.
- m_send_deadline.expires_at(boost::posix_time::pos_infin);
-
- // Start the persistent actor that checks for deadline expiry.
- check_send_deadline();
- }
- ~async_blocked_mode_client()
- {
- m_send_deadline.cancel();
- }
-
- bool shutdown()
- {
- blocked_mode_client::shutdown();
- m_send_deadline.cancel();
- return true;
- }
-
- inline
- bool send(const void* data, size_t sz)
- {
- try
- {
- /*
- m_send_deadline.expires_from_now(boost::posix_time::milliseconds(m_reciev_timeout));
-
- // Set up the variable that receives the result of the asynchronous
- // operation. The error code is set to would_block to signal that the
- // operation is incomplete. Asio guarantees that its asynchronous
- // operations will never fail with would_block, so any other value in
- // ec indicates completion.
- boost::system::error_code ec = boost::asio::error::would_block;
-
- // Start the asynchronous operation itself. The boost::lambda function
- // object is used as a callback and will update the ec variable when the
- // operation completes. The blocking_udp_client.cpp example shows how you
- // can use boost::bind rather than boost::lambda.
- boost::asio::async_write(m_socket, boost::asio::buffer(data, sz), boost::lambda::var(ec) = boost::lambda::_1);
-
- // Block until the asynchronous operation has completed.
- while(ec == boost::asio::error::would_block)
- {
- m_io_service.run_one();
- }*/
-
- boost::system::error_code ec;
-
- size_t writen = write(data, sz, ec);
-
- if (!writen || ec)
- {
- LOG_PRINT_L3("Problems at write: " << ec.message());
- return false;
- }else
- {
- m_send_deadline.expires_at(boost::posix_time::pos_infin);
- }
- }
-
- catch(const boost::system::system_error& er)
- {
- LOG_ERROR("Some problems at connect, message: " << er.what());
- return false;
- }
- catch(...)
- {
- LOG_ERROR("Some fatal problems.");
- return false;
- }
-
- return true;
- }
-
-
- private:
-
- boost::asio::deadline_timer m_send_deadline;
-
- void check_send_deadline()
- {
- // Check whether the deadline has passed. We compare the deadline against
- // the current time since a new asynchronous operation may have moved the
- // deadline before this actor had a chance to run.
- if (m_send_deadline.expires_at() <= boost::asio::deadline_timer::traits_type::now())
- {
- // The deadline has passed. The socket is closed so that any outstanding
- // asynchronous operations are cancelled. This allows the blocked
- // connect(), read_line() or write_line() functions to return.
- LOG_PRINT_L3("Timed out socket");
- m_ssl_socket->next_layer().close();
-
- // There is no longer an active deadline. The expiry is set to positive
- // infinity so that the actor takes no action until a new deadline is set.
- m_send_deadline.expires_at(boost::posix_time::pos_infin);
- }
-
- // Put the actor back to sleep.
- m_send_deadline.async_wait(boost::bind(&async_blocked_mode_client::check_send_deadline, this));
- }
- };
}
}
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/string_coding.h b/contrib/epee/include/string_coding.h
index 82050ef96..0d9c6c244 100644
--- a/contrib/epee/include/string_coding.h
+++ b/contrib/epee/include/string_coding.h
@@ -34,100 +34,6 @@ namespace epee
{
namespace string_encoding
{
- inline std::string convert_to_ansii(const std::wstring& str_from)
- {
-
- std::string res(str_from.begin(), str_from.end());
- return res;
- /*
- std::string result;
- std::locale loc;
- for(unsigned int i= 0; i < str_from.size(); ++i)
- {
- result += std::use_facet<std::ctype<wchar_t> >(loc).narrow(str_from[i]);
- }
- return result;
- */
-
- //return boost::lexical_cast<std::string>(str_from);
- /*
- std::string str_trgt;
- if(!str_from.size())
- return str_trgt;
- int cb = ::WideCharToMultiByte( code_page, 0, str_from.data(), (__int32)str_from.size(), 0, 0, 0, 0 );
- if(!cb)
- return str_trgt;
- str_trgt.resize(cb);
- ::WideCharToMultiByte( code_page, 0, str_from.data(), (int)str_from.size(),
- (char*)str_trgt.data(), (int)str_trgt.size(), 0, 0);
- return str_trgt;*/
- }
-
- inline std::string convert_to_ansii(const std::string& str_from)
- {
- return str_from;
- }
-
- inline std::wstring convert_to_unicode(const std::string& str_from)
- {
- std::wstring result;
- std::locale loc;
- for(unsigned int i= 0; i < str_from.size(); ++i)
- {
- result += std::use_facet<std::ctype<wchar_t> >(loc).widen(str_from[i]);
- }
- return result;
-
- //return boost::lexical_cast<std::wstring>(str_from);
- /*
- std::wstring str_trgt;
- if(!str_from.size())
- return str_trgt;
-
- int cb = ::MultiByteToWideChar( code_page, 0, str_from.data(), (int)str_from.size(), 0, 0 );
- if(!cb)
- return str_trgt;
-
- str_trgt.resize(cb);
- ::MultiByteToWideChar( code_page, 0, str_from.data(),(int)str_from.size(),
- (wchar_t*)str_trgt.data(),(int)str_trgt.size());
- return str_trgt;*/
- }
- inline std::wstring convert_to_unicode(const std::wstring& str_from)
- {
- return str_from;
- }
-
- template<class target_string>
- inline target_string convert_to_t(const std::wstring& str_from);
-
- template<>
- inline std::string convert_to_t<std::string>(const std::wstring& str_from)
- {
- return convert_to_ansii(str_from);
- }
-
- template<>
- inline std::wstring convert_to_t<std::wstring>(const std::wstring& str_from)
- {
- return str_from;
- }
-
- template<class target_string>
- inline target_string convert_to_t(const std::string& str_from);
-
- template<>
- inline std::string convert_to_t<std::string>(const std::string& str_from)
- {
- return str_from;
- }
-
- template<>
- inline std::wstring convert_to_t<std::wstring>(const std::string& str_from)
- {
- return convert_to_unicode(str_from);
- }
-
inline
std::string& base64_chars()
{
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
deleted file mode 100644
index 1baa20cc1..000000000
--- a/docs/images/storage_binary_example.png
+++ /dev/null
Binary files differ
diff --git a/external/boost/archive/portable_binary_archive.hpp b/external/boost/archive/portable_binary_archive.hpp
index cdbd38aad..b1d6ae73e 100644
--- a/external/boost/archive/portable_binary_archive.hpp
+++ b/external/boost/archive/portable_binary_archive.hpp
@@ -44,12 +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/blockchain_db.h b/src/blockchain_db/blockchain_db.h
index 263948fa2..a163ef98c 100644
--- a/src/blockchain_db/blockchain_db.h
+++ b/src/blockchain_db/blockchain_db.h
@@ -1883,16 +1883,18 @@ public:
}
virtual ~db_txn_guard()
{
- if (active)
- stop();
+ stop();
}
void stop()
{
- if (readonly)
- db->block_rtxn_stop();
- else
- db->block_wtxn_stop();
- active = false;
+ if (active)
+ {
+ if (readonly)
+ db->block_rtxn_stop();
+ else
+ db->block_wtxn_stop();
+ active = false;
+ }
}
void abort()
{
diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp
index e2ac9df0b..f80013d02 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>
@@ -472,6 +465,32 @@ void mdb_txn_safe::increment_txns(int i)
num_active_txns += i;
}
+#define TXN_PREFIX(flags); \
+ mdb_txn_safe auto_txn; \
+ mdb_txn_safe* txn_ptr = &auto_txn; \
+ if (m_batch_active) \
+ txn_ptr = m_write_txn; \
+ else \
+ { \
+ if (auto mdb_res = lmdb_txn_begin(m_env, NULL, flags, auto_txn)) \
+ throw0(DB_ERROR(lmdb_error(std::string("Failed to create a transaction for the db in ")+__FUNCTION__+": ", mdb_res).c_str())); \
+ } \
+
+#define TXN_PREFIX_RDONLY() \
+ MDB_txn *m_txn; \
+ mdb_txn_cursors *m_cursors; \
+ mdb_txn_safe auto_txn; \
+ bool my_rtxn = block_rtxn_start(&m_txn, &m_cursors); \
+ if (my_rtxn) auto_txn.m_tinfo = m_tinfo.get(); \
+ else auto_txn.uncheck()
+#define TXN_POSTFIX_RDONLY()
+
+#define TXN_POSTFIX_SUCCESS() \
+ do { \
+ if (! m_batch_active) \
+ auto_txn.commit(); \
+ } while(0)
+
void lmdb_resized(MDB_env *env, int isactive)
{
mdb_txn_safe::prevent_new_txns();
@@ -720,21 +739,20 @@ uint64_t BlockchainLMDB::get_estimated_batch_size(uint64_t batch_num_blocks, uin
}
else
{
- MDB_txn *rtxn;
- mdb_txn_cursors *rcurs;
- bool my_rtxn = block_rtxn_start(&rtxn, &rcurs);
- for (uint64_t block_num = block_start; block_num <= block_stop; ++block_num)
{
- // we have access to block weight, which will be greater or equal to block size,
- // so use this as a proxy. If it's too much off, we might have to check actual size,
- // which involves reading more data, so is not really wanted
- size_t block_weight = get_block_weight(block_num);
- total_block_size += block_weight;
- // Track number of blocks being totalled here instead of assuming, in case
- // some blocks were to be skipped for being outliers.
- ++num_blocks_used;
+ TXN_PREFIX_RDONLY();
+ for (uint64_t block_num = block_start; block_num <= block_stop; ++block_num)
+ {
+ // we have access to block weight, which will be greater or equal to block size,
+ // so use this as a proxy. If it's too much off, we might have to check actual size,
+ // which involves reading more data, so is not really wanted
+ size_t block_weight = get_block_weight(block_num);
+ total_block_size += block_weight;
+ // Track number of blocks being totalled here instead of assuming, in case
+ // some blocks were to be skipped for being outliers.
+ ++num_blocks_used;
+ }
}
- if (my_rtxn) block_rtxn_stop();
avg_block_size = total_block_size / (num_blocks_used ? num_blocks_used : 1);
MDEBUG("average block size across recent " << num_blocks_used << " blocks: " << avg_block_size);
}
@@ -1303,26 +1321,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 +1332,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 +1356,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");
@@ -1714,32 +1703,6 @@ void BlockchainLMDB::unlock()
check_open();
}
-#define TXN_PREFIX(flags); \
- mdb_txn_safe auto_txn; \
- mdb_txn_safe* txn_ptr = &auto_txn; \
- if (m_batch_active) \
- txn_ptr = m_write_txn; \
- else \
- { \
- if (auto mdb_res = lmdb_txn_begin(m_env, NULL, flags, auto_txn)) \
- throw0(DB_ERROR(lmdb_error(std::string("Failed to create a transaction for the db in ")+__FUNCTION__+": ", mdb_res).c_str())); \
- } \
-
-#define TXN_PREFIX_RDONLY() \
- MDB_txn *m_txn; \
- mdb_txn_cursors *m_cursors; \
- mdb_txn_safe auto_txn; \
- bool my_rtxn = block_rtxn_start(&m_txn, &m_cursors); \
- if (my_rtxn) auto_txn.m_tinfo = m_tinfo.get(); \
- else auto_txn.uncheck()
-#define TXN_POSTFIX_RDONLY()
-
-#define TXN_POSTFIX_SUCCESS() \
- do { \
- if (! m_batch_active) \
- auto_txn.commit(); \
- } while(0)
-
// The below two macros are for DB access within block add/remove, whether
// regular batch txn is in use or not. m_write_txn is used as a batch txn, even
@@ -3959,13 +3922,20 @@ void BlockchainLMDB::block_rtxn_stop() const
LOG_PRINT_L3("BlockchainLMDB::" << __func__);
mdb_txn_reset(m_tinfo->m_ti_rtxn);
memset(&m_tinfo->m_ti_rflags, 0, sizeof(m_tinfo->m_ti_rflags));
+ /* cancel out the increment from rtxn_start */
+ mdb_txn_safe::increment_txns(-1);
}
bool BlockchainLMDB::block_rtxn_start() const
{
MDB_txn *mtxn;
mdb_txn_cursors *mcur;
- return block_rtxn_start(&mtxn, &mcur);
+ /* auto_txn is only used for the create gate */
+ mdb_txn_safe auto_txn;
+ bool ret = block_rtxn_start(&mtxn, &mcur);
+ if (ret)
+ auto_txn.increment_txns(1); /* remember there is an active readtxn */
+ return ret;
}
void BlockchainLMDB::block_wtxn_start()
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
index e75e379f2..2ed1d630f 100644
--- a/src/blocks/checkpoints.dat
+++ b/src/blocks/checkpoints.dat
Binary files differ
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/crypto/hash-extra-jh.c b/src/crypto/hash-extra-jh.c
index 4d7481c07..52efd4ae3 100644
--- a/src/crypto/hash-extra-jh.c
+++ b/src/crypto/hash-extra-jh.c
@@ -36,7 +36,9 @@
#include "jh.h"
#include "hash-ops.h"
+#define JH_HASH_BITLEN HASH_SIZE * 8
+
void hash_extra_jh(const void *data, size_t length, char *hash) {
- int r = jh_hash(HASH_SIZE * 8, data, 8 * length, (uint8_t*)hash);
- assert(SUCCESS == r);
+ // No need to check for failure b/c jh_hash only fails for invalid hash size
+ jh_hash(JH_HASH_BITLEN, data, 8 * length, (uint8_t*)hash);
}
diff --git a/src/crypto/hash-extra-skein.c b/src/crypto/hash-extra-skein.c
index 9ea9c4faa..3eacaba58 100644
--- a/src/crypto/hash-extra-skein.c
+++ b/src/crypto/hash-extra-skein.c
@@ -34,7 +34,9 @@
#include "hash-ops.h"
#include "skein.h"
+#define SKEIN_HASH_BITLEN HASH_SIZE * 8
+
void hash_extra_skein(const void *data, size_t length, char *hash) {
- int r = skein_hash(8 * HASH_SIZE, data, 8 * length, (uint8_t*)hash);
- assert(SKEIN_SUCCESS == r);
+ // No need to check for failure b/c skein_hash only fails for invalid hash size
+ skein_hash(SKEIN_HASH_BITLEN, data, 8 * length, (uint8_t*)hash);
}
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 0c18b2a34..2a514ceae 100644
--- a/src/cryptonote_core/tx_pool.cpp
+++ b/src/cryptonote_core/tx_pool.cpp
@@ -402,6 +402,19 @@ namespace cryptonote
m_txpool_max_weight = bytes;
}
//---------------------------------------------------------------------------------
+ void tx_memory_pool::reduce_txpool_weight(size_t weight)
+ {
+ if (weight > m_txpool_weight)
+ {
+ MERROR("Underflow in txpool weight");
+ m_txpool_weight = 0;
+ }
+ else
+ {
+ m_txpool_weight -= weight;
+ }
+ }
+ //---------------------------------------------------------------------------------
void tx_memory_pool::prune(size_t bytes)
{
CRITICAL_REGION_LOCAL(m_transactions_lock);
@@ -423,8 +436,14 @@ namespace cryptonote
txpool_tx_meta_t meta;
if (!m_blockchain.get_txpool_tx_meta(txid, meta))
{
- MERROR("Failed to find tx_meta in txpool");
- return;
+ static bool warned = false;
+ if (!warned)
+ {
+ MERROR("Failed to find tx_meta in txpool (will only print once)");
+ warned = true;
+ }
+ --it;
+ continue;
}
// don't prune the kept_by_block ones, they're likely added because we're adding a block with those
if (meta.kept_by_block)
@@ -442,7 +461,7 @@ namespace cryptonote
// remove first, in case this throws, so key images aren't removed
MINFO("Pruning tx " << txid << " from txpool: weight: " << meta.weight << ", fee/byte: " << it->first.first);
m_blockchain.remove_txpool_tx(txid);
- m_txpool_weight -= meta.weight;
+ reduce_txpool_weight(meta.weight);
remove_transaction_keyimages(tx, txid);
MINFO("Pruned tx " << txid << " from txpool: weight: " << meta.weight << ", fee/byte: " << it->first.first);
m_txs_by_fee_and_receive_time.erase(it--);
@@ -562,7 +581,7 @@ namespace cryptonote
// remove first, in case this throws, so key images aren't removed
m_blockchain.remove_txpool_tx(id);
- m_txpool_weight -= tx_weight;
+ reduce_txpool_weight(tx_weight);
remove_transaction_keyimages(tx, id);
lock.commit();
}
@@ -725,7 +744,7 @@ namespace cryptonote
{
// remove first, so we only remove key images if the tx removal succeeds
m_blockchain.remove_txpool_tx(txid);
- m_txpool_weight -= entry.second;
+ reduce_txpool_weight(entry.second);
remove_transaction_keyimages(tx, txid);
}
}
@@ -925,26 +944,61 @@ namespace cryptonote
{
CRITICAL_REGION_LOCAL(m_transactions_lock);
CRITICAL_REGION_LOCAL1(m_blockchain);
- const relay_category category = include_sensitive ? relay_category::all : relay_category::broadcasted;
- backlog.reserve(m_blockchain.get_txpool_tx_count(include_sensitive));
- txpool_tx_meta_t tmp_meta;
- m_blockchain.for_all_txpool_txes([this, &backlog, &tmp_meta](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata_ref *bd){
- transaction tx;
- if (!(meta.pruned ? parse_and_validate_tx_base_from_blob(*bd, tx) : parse_and_validate_tx_from_blob(*bd, tx)))
+
+ std::vector<tx_block_template_backlog_entry> tmp;
+ uint64_t total_weight = 0;
+
+ // First get everything from the mempool, filter it later
+ m_blockchain.for_all_txpool_txes([&tmp, &total_weight](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata_ref*){
+ tmp.emplace_back(tx_block_template_backlog_entry{txid, meta.weight, meta.fee});
+ total_weight += meta.weight;
+ return true;
+ }, false, include_sensitive ? relay_category::all : relay_category::broadcasted);
+
+ // Limit backlog to 112.5% of current median weight. This is enough to mine a full block with the optimal block reward
+ const uint64_t median_weight = m_blockchain.get_current_cumulative_block_weight_median();
+ const uint64_t max_backlog_weight = median_weight + (median_weight / 8);
+
+ // If the total weight is too high, choose the best paying transactions
+ if (total_weight > max_backlog_weight)
+ std::sort(tmp.begin(), tmp.end(), [](const auto& a, const auto& b){ return a.fee * b.weight > b.fee * a.weight; });
+
+ backlog.clear();
+ uint64_t w = 0;
+
+ std::unordered_set<crypto::key_image> k_images;
+
+ for (const tx_block_template_backlog_entry& e : tmp)
+ {
+ try
{
- MERROR("Failed to parse tx from txpool");
- // continue
- return true;
- }
- tx.set_hash(txid);
+ txpool_tx_meta_t meta;
+ if (!m_blockchain.get_txpool_tx_meta(e.id, meta))
+ continue;
- tmp_meta = meta;
+ cryptonote::blobdata txblob;
+ if (!m_blockchain.get_txpool_tx_blob(e.id, txblob, relay_category::all))
+ continue;
- if (is_transaction_ready_to_go(tmp_meta, txid, *bd, tx))
- backlog.push_back({txid, meta.weight, meta.fee});
+ cryptonote::transaction tx;
+ if (is_transaction_ready_to_go(meta, e.id, txblob, tx))
+ {
+ if (have_key_images(k_images, tx))
+ continue;
+ append_key_images(k_images, tx);
- return true;
- }, true, category);
+ backlog.push_back(e);
+ w += e.weight;
+ if (w > max_backlog_weight)
+ break;
+ }
+ }
+ catch (const std::exception &e)
+ {
+ MERROR("Failed to check transaction readiness: " << e.what());
+ // continue, not fatal
+ }
+ }
}
//------------------------------------------------------------------
void tx_memory_pool::get_transaction_stats(struct txpool_stats& stats, bool include_sensitive) const
diff --git a/src/cryptonote_core/tx_pool.h b/src/cryptonote_core/tx_pool.h
index 65c39f87c..45623fd14 100644
--- a/src/cryptonote_core/tx_pool.h
+++ b/src/cryptonote_core/tx_pool.h
@@ -266,7 +266,11 @@ namespace cryptonote
void get_transaction_backlog(std::vector<tx_backlog_entry>& backlog, bool include_sensitive = false) const;
/**
- * @brief get (hash, weight, fee) for all transactions in the pool - the minimum required information to create a block template
+ * @brief get (hash, weight, fee) for transactions in the pool - the minimum required information to create a block template
+ *
+ * Not all transactions in the pool will be returned for performance reasons
+ * If there are too many transactions in the pool, only the highest-paying transactions
+ * will be returned - but enough for the miner to create a full block
*
* @param backlog return-by-reference that data
* @param include_sensitive return stempool, anonymity-pool, and unrelayed txes
@@ -408,6 +412,13 @@ namespace cryptonote
*/
void set_txpool_max_weight(size_t bytes);
+ /**
+ * @brief reduce the cumulative txpool weight by the weight provided
+ *
+ * @param weight the weight to reduce the total txpool weight by
+ */
+ void reduce_txpool_weight(size_t weight);
+
#define CURRENT_MEMPOOL_ARCHIVE_VER 11
#define CURRENT_MEMPOOL_TX_DETAILS_ARCHIVE_VER 13
diff --git a/src/device/device_ledger.cpp b/src/device/device_ledger.cpp
index 51e65dfa5..7a6c6c62b 100644
--- a/src/device/device_ledger.cpp
+++ b/src/device/device_ledger.cpp
@@ -43,6 +43,10 @@ namespace hw {
#ifdef WITH_DEVICE_LEDGER
+ namespace {
+ bool apdu_verbose =true;
+ }
+
#undef MONERO_DEFAULT_LOG_CATEGORY
#define MONERO_DEFAULT_LOG_CATEGORY "device.ledger"
@@ -694,7 +698,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 +707,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 +1058,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 +1214,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/device_ledger.hpp b/src/device/device_ledger.hpp
index 074bfaa8d..ce0820927 100644
--- a/src/device/device_ledger.hpp
+++ b/src/device/device_ledger.hpp
@@ -87,10 +87,6 @@ namespace hw {
#define SW_PROTOCOL_NOT_SUPPORTED 0x6e00
#define SW_UNKNOWN 0x6f00
- namespace {
- bool apdu_verbose =true;
- }
-
void set_apdu_verbose(bool verbose);
class ABPkeys {
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/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 f6e313089..a8f4e5a07 100644
--- a/src/simplewallet/simplewallet.cpp
+++ b/src/simplewallet/simplewallet.cpp
@@ -5673,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;
@@ -6960,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;
}
@@ -7670,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]))
diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h
index 6c4ddd4e7..6a9fa149d 100644
--- a/src/simplewallet/simplewallet.h
+++ b/src/simplewallet/simplewallet.h
@@ -346,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 0b2a6c0f5..2de624ace 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"
@@ -2208,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;
@@ -2242,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];
@@ -2285,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;
@@ -3159,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;
@@ -3183,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)
{
@@ -3219,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
@@ -3418,6 +3424,10 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo
uint64_t blocks_start_height;
std::vector<cryptonote::block_complete_entry> blocks;
std::vector<parsed_block> parsed_blocks;
+ // TODO moneromooo-monero says this about the "refreshed" variable:
+ // "I had to reorder some code to fix... a timing info leak IIRC. In turn, this undid something I had fixed before, ... a subtle race condition with the txpool.
+ // It was pretty subtle IIRC, and so I needed time to think about how to refix it after the move, and I never got to it."
+ // https://github.com/monero-project/monero/pull/6097
bool refreshed = false;
std::shared_ptr<std::map<std::pair<uint64_t, uint64_t>, size_t>> output_tracker_cache;
hw::device &hwdev = m_account.get_device();
@@ -3622,32 +3632,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);
@@ -5070,7 +5055,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());
@@ -5127,7 +5111,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)
@@ -5163,7 +5146,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());
@@ -6642,8 +6624,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,
@@ -7151,77 +7132,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())
{
@@ -7232,7 +7250,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]));
@@ -8049,8 +8068,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,
@@ -8062,11 +8086,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());
@@ -8166,7 +8190,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;
@@ -8181,7 +8205,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;
@@ -8368,7 +8392,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
@@ -8530,7 +8554,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];
@@ -8758,9 +8782,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);
@@ -8842,6 +8865,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)
@@ -8909,7 +8936,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);
@@ -8952,10 +8978,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);
@@ -8992,12 +9016,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
@@ -9015,42 +9070,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"
+ );
}
}
@@ -9077,6 +9167,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;
@@ -12090,7 +12181,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");
@@ -13336,19 +13428,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
@@ -13414,15 +13513,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
@@ -13435,6 +13542,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());
@@ -14022,43 +14138,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 e051946ad..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()
};
@@ -1679,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);
@@ -1878,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
{
@@ -2316,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/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/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/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/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)"