diff options
122 files changed, 6720 insertions, 919 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index b11d6ba6f..11c549d7c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -238,6 +238,16 @@ if(STATIC) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DZMQ_STATIC") endif() +if(SANITIZE) + if (MSVC) + message(FATAL_ERROR "Cannot sanitize with MSVC") + else() + message(STATUS "Using ASAN") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address") + endif() +endif() + # Set default blockchain storage location: # memory was the default in Cryptonote before Monero implimented LMDB, it still works but is unneccessary. # set(DATABASE memory) diff --git a/Dockerfile b/Dockerfile index 71b0658a0..138dec4c7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,8 +21,10 @@ RUN apt-get update && \ WORKDIR /src COPY . . + +ARG NPROC RUN rm -rf build && \ - make -j$(nproc) release-static + if [ -z "$NPROC" ];then make -j$(nproc) release-static;else make -j$NPROC release-static;fi # runtime stage FROM ubuntu:16.04 @@ -45,4 +47,4 @@ VOLUME /wallet EXPOSE 18080 EXPOSE 18081 -ENTRYPOINT ["monerod", "--p2p-bind-ip=0.0.0.0", "--p2p-bind-port=18080", "--rpc-bind-ip=127.0.0.1", "--rpc-bind-port=18081", "--non-interactive"] +ENTRYPOINT ["monerod", "--p2p-bind-ip=0.0.0.0", "--p2p-bind-port=18080", "--rpc-bind-ip=0.0.0.0", "--rpc-bind-port=18081", "--non-interactive", "--confirm-external-bind"] @@ -35,9 +35,11 @@ cmake-debug: debug: cmake-debug cd build/debug && $(MAKE) +# Temporarily disable some tests: +# * libwallet_api_tests fail (Issue #895) debug-test: mkdir -p build/debug - cd build/debug && cmake -D BUILD_TESTS=ON -D CMAKE_BUILD_TYPE=Debug ../.. && $(MAKE) && $(MAKE) test + cd build/debug && cmake -D BUILD_TESTS=ON -D CMAKE_BUILD_TYPE=Debug ../.. && $(MAKE) && $(MAKE) ARGS="-E libwallet_api_tests" test debug-all: mkdir -p build/debug @@ -114,7 +116,7 @@ release-static-win32: fuzz: mkdir -p build/fuzz - cd build/fuzz && cmake -D BUILD_TESTS=ON -D USE_LTO=OFF -D CMAKE_C_COMPILER=afl-gcc -D CMAKE_CXX_COMPILER=afl-g++ -D ARCH="x86-64" -D CMAKE_BUILD_TYPE=fuzz -D BUILD_TAG="linux-x64" ../.. && $(MAKE) + cd build/fuzz && cmake -D STATIC=ON -D SANITIZE=ON -D BUILD_TESTS=ON -D USE_LTO=OFF -D CMAKE_C_COMPILER=afl-gcc -D CMAKE_CXX_COMPILER=afl-g++ -D ARCH="x86-64" -D CMAKE_BUILD_TYPE=fuzz -D BUILD_TAG="linux-x64" ../.. && $(MAKE) clean: @echo "WARNING: Back-up your wallet if it exists within ./build!" ; \ @@ -129,6 +129,10 @@ Installing a snap is very quick. Snaps are secure. They are isolated with all of xbps-install -S monero +* GuixSD + + guix package -i monero + * OS X via [Homebrew](http://brew.sh) brew tap sammy007/cryptonight @@ -136,7 +140,11 @@ Installing a snap is very quick. Snaps are secure. They are isolated with all of * Docker + # Build using all available cores docker build -t monero . + + # or build using a specific number of cores (reduce RAM requirement) + docker build --build-arg NPROC=1 -t monero . # either run in foreground docker run -it -v /monero/chain:/root/.bitmonero -v /monero/wallet:/wallet -p 18080:18080 monero @@ -168,6 +176,7 @@ library archives (`.a`). | OpenSSL | basically any | NO | `libssl-dev` | `openssl` | NO | sha256 sum | | libzmq | 3.0.0 | NO | `libzmq3-dev` | `zeromq` | NO | ZeroMQ library | | libunbound | 1.4.16 | YES | `libunbound-dev` | `unbound` | NO | DNS resolver | +| libsodium | ? | NO | `libsodium-dev` | ? | NO | libsodium | | libminiupnpc | 2.0 | YES | `libminiupnpc-dev` | `miniupnpc` | YES | NAT punching | | libunwind | any | NO | `libunwind8-dev` | `libunwind` | YES | Stack traces | | liblzma | any | NO | `liblzma-dev` | `xz` | YES | For libunwind | @@ -178,6 +187,7 @@ library archives (`.a`). | Doxygen | any | NO | `doxygen` | `doxygen` | YES | Documentation | | Graphviz | any | NO | `graphviz` | `graphviz` | YES | Documentation | + [^] On Debian/Ubuntu `libgtest-dev` only includes sources and headers. You must build the library binary manually. This can be done with the following command ```sudo apt-get install libgtest-dev && cd /usr/src/gtest && sudo cmake . && sudo make && sudo mv libg* /usr/lib/ ``` diff --git a/contrib/epee/include/file_io_utils.h b/contrib/epee/include/file_io_utils.h index f037b4dd5..989d16fba 100644 --- a/contrib/epee/include/file_io_utils.h +++ b/contrib/epee/include/file_io_utils.h @@ -133,6 +133,26 @@ namespace file_io_utils return false; } } + + inline + bool get_file_size(const std::string& path_to_file, uint64_t &size) + { + try + { + std::ifstream fstream; + fstream.exceptions(std::ifstream::failbit | std::ifstream::badbit); + fstream.open(path_to_file, std::ios_base::binary | std::ios_base::in | std::ios::ate); + size = fstream.tellg(); + fstream.close(); + return true; + } + + catch(...) + { + return false; + } + } + } } diff --git a/contrib/epee/include/misc_os_dependent.h b/contrib/epee/include/misc_os_dependent.h index 81cecf714..99690b301 100644 --- a/contrib/epee/include/misc_os_dependent.h +++ b/contrib/epee/include/misc_os_dependent.h @@ -23,6 +23,10 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // +#ifdef _WIN32 +#include <Winsock2.h> +#endif + #ifdef WIN32 #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN diff --git a/contrib/epee/include/net/abstract_tcp_server.h b/contrib/epee/include/net/abstract_tcp_server.h index 000305cfa..cbad1717c 100644 --- a/contrib/epee/include/net/abstract_tcp_server.h +++ b/contrib/epee/include/net/abstract_tcp_server.h @@ -305,7 +305,7 @@ namespace net_utils m_connections.back().powner = this; m_connections.back().m_self_it = --m_connections.end(); m_connections.back().m_context.m_remote_address = remote_address; - m_connections.back().m_htread = threads_helper::create_thread(ConnectionHandlerProc, &m_connections.back()); + m_connections.back().m_htread = threads_helper::create_thread(ConnectionHandlerProc, &m_connections.back()); // ugh, seems very risky return true; } diff --git a/contrib/epee/include/net/abstract_tcp_server2.inl b/contrib/epee/include/net/abstract_tcp_server2.inl index 04d884af2..870f6c2b2 100644 --- a/contrib/epee/include/net/abstract_tcp_server2.inl +++ b/contrib/epee/include/net/abstract_tcp_server2.inl @@ -735,7 +735,17 @@ PRAGMA_WARNING_DISABLE_VS(4355) boost::asio::placeholders::error)); return true; - CATCH_ENTRY_L0("boosted_tcp_server<t_protocol_handler>::init_server", false); + } + catch (const std::exception &e) + { + MFATAL("Error starting server: " << e.what()); + return false; + } + catch (...) + { + MFATAL("Error starting server"); + return false; + } } //----------------------------------------------------------------------------- PUSH_WARNINGS diff --git a/contrib/epee/include/net/http_client.h b/contrib/epee/include/net/http_client.h index 1a9d5d064..1edf65928 100644 --- a/contrib/epee/include/net/http_client.h +++ b/contrib/epee/include/net/http_client.h @@ -27,6 +27,7 @@ #pragma once +#include <ctype.h> #include <boost/shared_ptr.hpp> #include <boost/regex.hpp> #include <boost/lexical_cast.hpp> @@ -236,7 +237,8 @@ namespace net_utils namespace http { - class http_simple_client: public i_target_handler + template<typename net_client_type> + class http_simple_client_template: public i_target_handler { private: enum reciev_machine_state @@ -259,7 +261,7 @@ namespace net_utils }; - blocked_mode_client m_net_client; + net_client_type m_net_client; std::string m_host_buff; std::string m_port; http_client_auth m_auth; @@ -276,7 +278,7 @@ namespace net_utils bool m_ssl; public: - explicit http_simple_client() + explicit http_simple_client_template() : i_target_handler() , m_net_client() , m_host_buff() @@ -428,6 +430,15 @@ namespace net_utils CRITICAL_REGION_LOCAL(m_lock); return invoke(uri, "POST", body, timeout, ppresponse_info, additional_params); } + //--------------------------------------------------------------------------- + bool test(const std::string &s, std::chrono::milliseconds timeout) // TEST FUNC ONLY + { + CRITICAL_REGION_LOCAL(m_lock); + m_net_client.set_test_data(s); + m_state = reciev_machine_state_header; + return handle_reciev(timeout); + } + //--------------------------------------------------------------------------- private: //--------------------------------------------------------------------------- inline bool handle_reciev(std::chrono::milliseconds timeout) @@ -742,87 +753,107 @@ namespace net_utils return true; } //--------------------------------------------------------------------------- - inline - bool parse_header(http_header_info& body_info, const std::string& m_cache_to_process) - { + inline bool parse_header(http_header_info& body_info, const std::string& m_cache_to_process) + { MTRACE("http_stream_filter::parse_cached_header(*)"); - - STATIC_REGEXP_EXPR_1(rexp_mach_field, - "\n?((Connection)|(Referer)|(Content-Length)|(Content-Type)|(Transfer-Encoding)|(Content-Encoding)|(Host)|(Cookie)|(User-Agent)|(Origin)" - // 12 3 4 5 6 7 8 9 10 11 - "|([\\w-]+?)) ?: ?((.*?)(\r?\n))[^\t ]", - //12 13 14 15 - boost::regex::icase | boost::regex::normal); - boost::smatch result; - std::string::const_iterator it_current_bound = m_cache_to_process.begin(); - std::string::const_iterator it_end_bound = m_cache_to_process.end(); - - - - //lookup all fields and fill well-known fields - while( boost::regex_search( it_current_bound, it_end_bound, result, rexp_mach_field, boost::match_default) && result[0].matched) + const char *ptr = m_cache_to_process.c_str(); + while (ptr[0] != '\r' || ptr[1] != '\n') { - const size_t field_val = 14; - //const size_t field_etc_name = 11; - - int i = 2; //start position = 2 - if(result[i++].matched)//"Connection" - body_info.m_connection = result[field_val]; - else if(result[i++].matched)//"Referrer" - body_info.m_referer = result[field_val]; - else if(result[i++].matched)//"Content-Length" - body_info.m_content_length = result[field_val]; - else if(result[i++].matched)//"Content-Type" - body_info.m_content_type = result[field_val]; - else if(result[i++].matched)//"Transfer-Encoding" - body_info.m_transfer_encoding = result[field_val]; - else if(result[i++].matched)//"Content-Encoding" - body_info.m_content_encoding = result[field_val]; - else if(result[i++].matched)//"Host" - { body_info.m_host = result[field_val]; - string_tools::trim(body_info.m_host); + // optional \n + if (*ptr == '\n') + ++ptr; + // an identifier composed of letters or - + const char *key_pos = ptr; + while (isalnum(*ptr) || *ptr == '_' || *ptr == '-') + ++ptr; + const char *key_end = ptr; + // optional space (not in RFC, but in previous code) + if (*ptr == ' ') + ++ptr; + CHECK_AND_ASSERT_MES(*ptr == ':', true, "http_stream_filter::parse_cached_header() invalid header in: " << m_cache_to_process); + ++ptr; + // optional whitespace, but not newlines - line folding is obsolete, let's ignore it + while (isblank(*ptr)) + ++ptr; + const char *value_pos = ptr; + while (*ptr != '\r' && *ptr != '\n') + ++ptr; + const char *value_end = ptr; + // optional trailing whitespace + while (value_end > value_pos && isblank(*(value_end-1))) + --value_end; + if (*ptr == '\r') + ++ptr; + CHECK_AND_ASSERT_MES(*ptr == '\n', true, "http_stream_filter::parse_cached_header() invalid header in: " << m_cache_to_process); + ++ptr; + + const std::string key = std::string(key_pos, key_end - key_pos); + const std::string value = std::string(value_pos, value_end - value_pos); + if (!key.empty()) + { + if (!string_tools::compare_no_case(key, "Connection")) + body_info.m_connection = value; + else if(!string_tools::compare_no_case(key, "Referrer")) + body_info.m_referer = value; + else if(!string_tools::compare_no_case(key, "Content-Length")) + body_info.m_content_length = value; + else if(!string_tools::compare_no_case(key, "Content-Type")) + body_info.m_content_type = value; + else if(!string_tools::compare_no_case(key, "Transfer-Encoding")) + body_info.m_transfer_encoding = value; + else if(!string_tools::compare_no_case(key, "Content-Encoding")) + body_info.m_content_encoding = value; + else if(!string_tools::compare_no_case(key, "Host")) + body_info.m_host = value; + else if(!string_tools::compare_no_case(key, "Cookie")) + body_info.m_cookie = value; + else if(!string_tools::compare_no_case(key, "User-Agent")) + body_info.m_user_agent = value; + else if(!string_tools::compare_no_case(key, "Origin")) + body_info.m_origin = value; + else + body_info.m_etc_fields.emplace_back(key, value); } - else if(result[i++].matched)//"Cookie" - body_info.m_cookie = result[field_val]; - else if(result[i++].matched)//"User-Agent" - body_info.m_user_agent = result[field_val]; - else if(result[i++].matched)//"Origin" - body_info.m_origin = result[field_val]; - else if(result[i++].matched)//e.t.c (HAVE TO BE MATCHED!) - body_info.m_etc_fields.emplace_back(result[12], result[field_val]); - else - {CHECK_AND_ASSERT_MES(false, false, "http_stream_filter::parse_cached_header() not matched last entry in:"<<m_cache_to_process);} - - it_current_bound = result[(int)result.size()-1]. first; } - return true; - + return true; } - inline - bool analize_first_response_line() + //--------------------------------------------------------------------------- + inline bool analize_first_response_line() { - - //First line response, look like this: "HTTP/1.1 200 OK" - STATIC_REGEXP_EXPR_1(rexp_match_first_response_line, "^HTTP/(\\d+).(\\d+) ((\\d)\\d{2})( [^\n]*)?\r?\n", boost::regex::icase | boost::regex::normal); - // 1 2 34 5 - //size_t match_len = 0; - boost::smatch result; - if(boost::regex_search( m_header_cache, result, rexp_match_first_response_line, boost::match_default) && result[0].matched) - { - CHECK_AND_ASSERT_MES(result[1].matched&&result[2].matched, false, "http_stream_filter::handle_invoke_reply_line() assert failed..."); - m_response_info.m_http_ver_hi = boost::lexical_cast<int>(result[1]); - m_response_info.m_http_ver_lo = boost::lexical_cast<int>(result[2]); - m_response_info.m_response_code = boost::lexical_cast<int>(result[3]); - - m_header_cache.erase(to_nonsonst_iterator(m_header_cache, result[0].first), to_nonsonst_iterator(m_header_cache, result[0].second)); - return true; - }else - { - LOG_ERROR("http_stream_filter::handle_invoke_reply_line(): Failed to match first response line:" << m_header_cache); - return false; - } - + //First line response, look like this: "HTTP/1.1 200 OK" + const char *ptr = m_header_cache.c_str(); + CHECK_AND_ASSERT_MES(!memcmp(ptr, "HTTP/", 5), false, "Invalid first response line: " + m_header_cache); + ptr += 5; + CHECK_AND_ASSERT_MES(isdigit(*ptr), false, "Invalid first response line: " + m_header_cache); + unsigned long ul; + char *end; + ul = strtoul(ptr, &end, 10); + CHECK_AND_ASSERT_MES(ul <= INT_MAX && *end =='.', false, "Invalid first response line: " + m_header_cache); + m_response_info.m_http_ver_hi = ul; + ptr = end + 1; + CHECK_AND_ASSERT_MES(isdigit(*ptr), false, "Invalid first response line: " + m_header_cache + ", ptr: " << ptr); + ul = strtoul(ptr, &end, 10); + CHECK_AND_ASSERT_MES(ul <= INT_MAX && isblank(*end), false, "Invalid first response line: " + m_header_cache + ", ptr: " << ptr); + m_response_info.m_http_ver_lo = ul; + ptr = end + 1; + while (isblank(*ptr)) + ++ptr; + CHECK_AND_ASSERT_MES(isdigit(*ptr), false, "Invalid first response line: " + m_header_cache); + ul = strtoul(ptr, &end, 10); + CHECK_AND_ASSERT_MES(ul >= 100 && ul <= 999 && isspace(*end), false, "Invalid first response line: " + m_header_cache); + m_response_info.m_response_code = ul; + ptr = end; + // ignore the optional text, till the end + while (*ptr != '\r' && *ptr != '\n') + ++ptr; + if (*ptr == '\r') + ++ptr; + CHECK_AND_ASSERT_MES(*ptr == '\n', false, "Invalid first response line: " << m_header_cache); + ++ptr; + + m_header_cache.erase(0, ptr - m_header_cache.c_str()); + return true; } inline bool set_reply_content_encoder() @@ -957,6 +988,7 @@ namespace net_utils return true; } }; + typedef http_simple_client_template<blocked_mode_client> http_simple_client; } } } diff --git a/contrib/epee/include/net/http_protocol_handler.inl b/contrib/epee/include/net/http_protocol_handler.inl index c555707ac..c18f7f706 100644 --- a/contrib/epee/include/net/http_protocol_handler.inl +++ b/contrib/epee/include/net/http_protocol_handler.inl @@ -345,7 +345,12 @@ namespace net_utils { analize_http_method(result, m_query_info.m_http_method, m_query_info.m_http_ver_hi, m_query_info.m_http_ver_hi); m_query_info.m_URI = result[10]; - parse_uri(m_query_info.m_URI, m_query_info.m_uri_content); + if (!parse_uri(m_query_info.m_URI, m_query_info.m_uri_content)) + { + m_state = http_state_error; + MERROR("Failed to parse URI: m_query_info.m_URI"); + return false; + } m_query_info.m_http_method_str = result[2]; m_query_info.m_full_request_str = result[0]; diff --git a/contrib/epee/include/net/levin_protocol_handler_async.h b/contrib/epee/include/net/levin_protocol_handler_async.h index 7b526e4e7..5b825cef9 100644 --- a/contrib/epee/include/net/levin_protocol_handler_async.h +++ b/contrib/epee/include/net/levin_protocol_handler_async.h @@ -99,6 +99,7 @@ public: async_protocol_handler_config():m_pcommands_handler(NULL), m_pcommands_handler_destroy(NULL), m_max_packet_size(LEVIN_DEFAULT_MAX_PACKET_SIZE) {} + ~async_protocol_handler_config() { set_handler(NULL, NULL); } void del_out_connections(size_t count); }; @@ -378,12 +379,16 @@ public: if(m_cache_in_buffer.size() < m_current_head.m_cb) { is_continue = false; - if(cb >= MIN_BYTES_WANTED && !m_invoke_response_handlers.empty()) + if(cb >= MIN_BYTES_WANTED) { - //async call scenario - boost::shared_ptr<invoke_response_handler_base> response_handler = m_invoke_response_handlers.front(); - response_handler->reset_timer(); - MDEBUG(m_connection_context << "LEVIN_PACKET partial msg received. len=" << cb); + CRITICAL_REGION_LOCAL(m_invoke_response_handlers_lock); + if (!m_invoke_response_handlers.empty()) + { + //async call scenario + boost::shared_ptr<invoke_response_handler_base> response_handler = m_invoke_response_handlers.front(); + response_handler->reset_timer(); + MDEBUG(m_connection_context << "LEVIN_PACKET partial msg received. len=" << cb); + } } break; } @@ -745,15 +750,18 @@ void async_protocol_handler_config<t_connection_context>::del_out_connections(si shuffle(out_connections.begin(), out_connections.end(), std::default_random_engine(seed)); while (count > 0 && out_connections.size() > 0) { - boost::uuids::uuid connection_id = *out_connections.begin(); - async_protocol_handler<t_connection_context> *connection = find_connection(connection_id); - // we temporarily ref the connection so it doesn't drop from the m_connects table - // when we close it - connection->start_outer_call(); - close(connection_id); - del_connection(m_connects.at(connection_id)); - out_connections.erase(out_connections.begin()); - connection->finish_outer_call(); + try + { + auto i = out_connections.begin(); + async_protocol_handler<t_connection_context> *conn = m_connects.at(*i); + del_connection(conn); + close(*i); + out_connections.erase(i); + } + catch (const std::out_of_range &e) + { + MWARNING("Connection not found in m_connects, continuing"); + } --count; } diff --git a/contrib/epee/include/net/net_parse_helpers.h b/contrib/epee/include/net/net_parse_helpers.h index 08d2a2000..708cce0ff 100644 --- a/contrib/epee/include/net/net_parse_helpers.h +++ b/contrib/epee/include/net/net_parse_helpers.h @@ -103,7 +103,7 @@ namespace net_utils STATIC_REGEXP_EXPR_1(rexp_match_uri, "^([^?#]*)(\\?([^#]*))?(#(.*))?", boost::regex::icase | boost::regex::normal); boost::smatch result; - if(!boost::regex_search(uri, result, rexp_match_uri, boost::match_default) && result[0].matched) + if(!(boost::regex_search(uri, result, rexp_match_uri, boost::match_default) && result[0].matched)) { LOG_PRINT_L1("[PARSE URI] regex not matched for uri: " << uri); content.m_path = uri; @@ -139,7 +139,7 @@ namespace net_utils // 12 34 5 6 7 content.port = 0; boost::smatch result; - if(!boost::regex_search(url_str, result, rexp_match_uri, boost::match_default) && result[0].matched) + if(!(boost::regex_search(url_str, result, rexp_match_uri, boost::match_default) && result[0].matched)) { LOG_PRINT_L1("[PARSE URI] regex not matched for uri: " << rexp_match_uri); //content.m_path = uri; diff --git a/contrib/epee/include/net/network_throttle.hpp b/contrib/epee/include/net/network_throttle.hpp index 464b34726..fffd22a6a 100644 --- a/contrib/epee/include/net/network_throttle.hpp +++ b/contrib/epee/include/net/network_throttle.hpp @@ -121,8 +121,6 @@ class network_throttle_manager { friend class connection_basic; // FRIEND - to directly access global throttle-s. !! REMEMBER TO USE LOCKS! friend class connection_basic_pimpl; // ditto - static int xxx; - public: static i_network_throttle & get_global_throttle_in(); ///< singleton ; for friend class ; caller MUST use proper locks! like m_lock_get_global_throttle_in static i_network_throttle & get_global_throttle_inreq(); ///< ditto ; use lock ... use m_lock_get_global_throttle_inreq obviously diff --git a/contrib/epee/include/string_tools.h b/contrib/epee/include/string_tools.h index 307323aa1..5a1ef0743 100644 --- a/contrib/epee/include/string_tools.h +++ b/contrib/epee/include/string_tools.h @@ -161,7 +161,7 @@ DISABLE_GCC_WARNING(maybe-uninitialized) val = boost::lexical_cast<XType>(str_id); return true; } - catch(std::exception& /*e*/) + catch(const std::exception& /*e*/) { //const char* pmsg = e.what(); return false; diff --git a/contrib/epee/src/mlog.cpp b/contrib/epee/src/mlog.cpp index a30efbc6a..5b9472006 100644 --- a/contrib/epee/src/mlog.cpp +++ b/contrib/epee/src/mlog.cpp @@ -59,6 +59,7 @@ static std::string generate_log_filename(const char *base) strcpy(tmp, "unknown"); else strftime(tmp, sizeof(tmp), "%Y-%m-%d-%H-%M-%S", &tm); + tmp[sizeof(tmp) - 1] = 0; filename += "-"; filename += tmp; return filename; diff --git a/contrib/epee/src/network_throttle-detail.cpp b/contrib/epee/src/network_throttle-detail.cpp index 317dde8e0..ed6bc07ed 100644 --- a/contrib/epee/src/network_throttle-detail.cpp +++ b/contrib/epee/src/network_throttle-detail.cpp @@ -231,8 +231,10 @@ network_time_seconds network_throttle::get_sleep_time_after_tick(size_t packet_s } void network_throttle::logger_handle_net(const std::string &filename, double time, size_t size) { - boost::mutex mutex; - mutex.lock(); { + static boost::mutex mutex; + + boost::lock_guard<boost::mutex> lock(mutex); + { std::fstream file; file.open(filename.c_str(), std::ios::app | std::ios::out ); file.precision(6); @@ -240,7 +242,7 @@ void network_throttle::logger_handle_net(const std::string &filename, double tim _warn("Can't open file " << filename); file << static_cast<int>(time) << " " << static_cast<double>(size/1024) << "\n"; file.close(); - } mutex.unlock(); + } } // fine tune this to decide about sending speed: diff --git a/contrib/epee/src/network_throttle.cpp b/contrib/epee/src/network_throttle.cpp index afacc3e96..dd1640a2e 100644 --- a/contrib/epee/src/network_throttle.cpp +++ b/contrib/epee/src/network_throttle.cpp @@ -71,9 +71,6 @@ boost::mutex network_throttle_manager::m_lock_get_global_throttle_in; boost::mutex network_throttle_manager::m_lock_get_global_throttle_inreq; boost::mutex network_throttle_manager::m_lock_get_global_throttle_out; -int network_throttle_manager::xxx; - - // ================================================================================================ // methods: i_network_throttle & network_throttle_manager::get_global_throttle_in() { diff --git a/contrib/epee/src/wipeable_string.cpp b/contrib/epee/src/wipeable_string.cpp index 75191df71..894c47bbd 100644 --- a/contrib/epee/src/wipeable_string.cpp +++ b/contrib/epee/src/wipeable_string.cpp @@ -85,11 +85,15 @@ void wipeable_string::wipe() void wipeable_string::grow(size_t sz, size_t reserved) { CHECK_AND_ASSERT_THROW_MES(wipefunc, "wipefunc is not set"); - if (reserved == 0) + if (reserved < sz) reserved = sz; - CHECK_AND_ASSERT_THROW_MES(reserved >= sz, "reserved < sz"); if (reserved <= buffer.capacity()) + { + if (sz < buffer.size()) + wipefunc(buffer.data() + sz, buffer.size() - sz); + buffer.resize(sz); return; + } size_t old_sz = buffer.size(); std::unique_ptr<char[]> tmp{new char[old_sz]}; memcpy(tmp.get(), buffer.data(), old_sz * sizeof(char)); @@ -103,7 +107,7 @@ void wipeable_string::grow(size_t sz, size_t reserved) void wipeable_string::push_back(char c) { grow(size() + 1); - buffer.push_back(c); + buffer.back() = c; } void wipeable_string::pop_back() @@ -113,9 +117,6 @@ void wipeable_string::pop_back() void wipeable_string::resize(size_t sz) { - CHECK_AND_ASSERT_THROW_MES(wipefunc, "wipefunc is not set"); - if (sz < buffer.size()) - wipefunc(buffer.data() + sz, buffer.size() - sz); grow(sz); } diff --git a/contrib/fuzz_testing/fuzz.sh b/contrib/fuzz_testing/fuzz.sh index 35b74f7e4..f1c4ff202 100755 --- a/contrib/fuzz_testing/fuzz.sh +++ b/contrib/fuzz_testing/fuzz.sh @@ -10,12 +10,20 @@ fi type="$1" if test -z "$type" then - echo "usage: $0 block|transaction|signature|cold-outputs|cold-transaction" + echo "usage: $0 block|transaction|signature|cold-outputs|cold-transaction|load-from-binary|load-from-json|base58|parse-url|http-client|levin" exit 1 fi case "$type" in - block|transaction|signature|cold-outputs|cold-transaction) ;; - *) echo "usage: $0 block|transaction|signature|cold-outputs|cold-transaction"; exit 1 ;; + block|transaction|signature|cold-outputs|cold-transaction|load-from-binary|load-from-json|base58|parse-url|http-client|levin) ;; + *) echo "usage: $0 block|transaction|signature|cold-outputs|cold-transaction|load-from-binary|load-from-json|base58|parse-url|http-client|levin"; exit 1 ;; esac -afl-fuzz -i tests/data/fuzz/$type -m 150 -t 250 -o fuzz-out/$type build/fuzz/tests/fuzz/${type}_fuzz_tests +if test -d "fuzz-out/$type" +then + dir="-" +else + dir="tests/data/fuzz/$type" +fi + +mkdir -p fuzz-out +afl-fuzz -i "$dir" -m none -t 250 -o fuzz-out/$type build/fuzz/tests/fuzz/${type}_fuzz_tests @@ diff --git a/external/easylogging++/ea_config.h b/external/easylogging++/ea_config.h index 2524d3477..c97858f30 100644 --- a/external/easylogging++/ea_config.h +++ b/external/easylogging++/ea_config.h @@ -2,9 +2,11 @@ #define ELPP_THREAD_SAFE #define ELPP_DEFAULT_LOG_FILE "" -#if !defined __GNUC__ || defined __MINGW32__ || defined __MINGW64__ || defined __ANDROID__ -#else -#define ELPP_FEATURE_CRASH_LOG 1 -#endif #define ELPP_DISABLE_DEFAULT_CRASH_HANDLING #define ELPP_NO_CHECK_MACROS +#define ELPP_WINSOCK2 +#define ELPP_NO_DEBUG_MACROS + +#ifdef EASYLOGGING_CC +#define ELPP_FEATURE_CRASH_LOG +#endif diff --git a/external/easylogging++/easylogging++.cc b/external/easylogging++/easylogging++.cc index 31b201897..57742b2e5 100644 --- a/external/easylogging++/easylogging++.cc +++ b/external/easylogging++/easylogging++.cc @@ -1016,8 +1016,9 @@ const std::string OS::getBashOutput(const char* command) { char hBuff[4096]; if (fgets(hBuff, sizeof(hBuff), proc) != nullptr) { pclose(proc); - if (hBuff[strlen(hBuff) - 1] == '\n') { - hBuff[strlen(hBuff) - 1] = '\0'; + const size_t len = strlen(hBuff); + if (len > 0 && hBuff[len - 1] == '\n') { + hBuff[len- 1] = '\0'; } return std::string(hBuff); } diff --git a/external/unbound/CMakeLists.txt b/external/unbound/CMakeLists.txt index a2e475232..99ef62c5a 100644 --- a/external/unbound/CMakeLists.txt +++ b/external/unbound/CMakeLists.txt @@ -53,6 +53,7 @@ add_definitions(-std=c99) option(USE_ECDSA "Use ECDSA algorithms" ON) option(USE_SHA2 "Enable SHA2 support" ON) +option(USE_SHA1 "Enable SHA1 support" ON) set(ENABLE_DNSTAP 0) set(HAVE_SSL 1) if (CMAKE_USE_PTHREADS_INIT AND NOT CMAKE_USE_WIN32_THREADS_INIT) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0281b1df6..79d2a232d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -114,6 +114,7 @@ add_subdirectory(ringct) add_subdirectory(checkpoints) add_subdirectory(cryptonote_basic) add_subdirectory(cryptonote_core) +add_subdirectory(multisig) if(NOT IOS) add_subdirectory(blockchain_db) endif() @@ -129,6 +130,7 @@ endif() add_subdirectory(cryptonote_protocol) if(NOT IOS) add_subdirectory(simplewallet) + add_subdirectory(gen_multisig) add_subdirectory(daemonizer) add_subdirectory(daemon) add_subdirectory(blockchain_utilities) diff --git a/src/blockchain_db/blockchain_db.cpp b/src/blockchain_db/blockchain_db.cpp index 07b2451b0..7e77953c8 100644 --- a/src/blockchain_db/blockchain_db.cpp +++ b/src/blockchain_db/blockchain_db.cpp @@ -197,7 +197,7 @@ uint64_t BlockchainDB::add_block( const block& blk { // sanity if (blk.tx_hashes.size() != txs.size()) - throw new std::runtime_error("Inconsistent tx/hashes sizes"); + throw std::runtime_error("Inconsistent tx/hashes sizes"); block_txn_start(false); @@ -283,7 +283,7 @@ block BlockchainDB::get_block_from_height(const uint64_t& height) const blobdata bd = get_block_blob_from_height(height); block b; if (!parse_and_validate_block_from_blob(bd, b)) - throw new DB_ERROR("Failed to parse block from blob retrieved from the db"); + throw DB_ERROR("Failed to parse block from blob retrieved from the db"); return b; } @@ -293,7 +293,7 @@ block BlockchainDB::get_block(const crypto::hash& h) const blobdata bd = get_block_blob(h); block b; if (!parse_and_validate_block_from_blob(bd, b)) - throw new DB_ERROR("Failed to parse block from blob retrieved from the db"); + throw DB_ERROR("Failed to parse block from blob retrieved from the db"); return b; } @@ -304,7 +304,7 @@ bool BlockchainDB::get_tx(const crypto::hash& h, cryptonote::transaction &tx) co if (!get_tx_blob(h, bd)) return false; if (!parse_and_validate_tx_from_blob(bd, tx)) - throw new DB_ERROR("Failed to parse transaction from blob retrieved from the db"); + throw DB_ERROR("Failed to parse transaction from blob retrieved from the db"); return true; } @@ -313,7 +313,7 @@ transaction BlockchainDB::get_tx(const crypto::hash& h) const { transaction tx; if (!get_tx(h, tx)) - throw new TX_DNE(std::string("tx with hash ").append(epee::string_tools::pod_to_hex(h)).append(" not found in db").c_str()); + throw TX_DNE(std::string("tx with hash ").append(epee::string_tools::pod_to_hex(h)).append(" not found in db").c_str()); return tx; } diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp index 07a0e67b1..ee4368e86 100644 --- a/src/blockchain_db/lmdb/db_lmdb.cpp +++ b/src/blockchain_db/lmdb/db_lmdb.cpp @@ -2894,7 +2894,7 @@ uint64_t BlockchainLMDB::add_block(const block& blk, const size_t& block_size, c { BlockchainDB::add_block(blk, block_size, cumulative_difficulty, coins_generated, txs); } - catch (DB_ERROR_TXN_START& e) + catch (const DB_ERROR_TXN_START& e) { throw; } diff --git a/src/blockchain_utilities/blockchain_import.cpp b/src/blockchain_utilities/blockchain_import.cpp index 70d1dd696..758deb7e4 100644 --- a/src/blockchain_utilities/blockchain_import.cpp +++ b/src/blockchain_utilities/blockchain_import.cpp @@ -459,7 +459,7 @@ int import_from_file(cryptonote::core& core, const std::string& import_file_path // tx number 1: coinbase tx // tx number 2 onwards: archived_txs - for (transaction tx : archived_txs) + for (const transaction &tx : archived_txs) { // add blocks with verification. // for Blockchain and blockchain_storage add_new_block(). diff --git a/src/checkpoints/checkpoints.cpp b/src/checkpoints/checkpoints.cpp index c66c4f5d6..67a313bc2 100644 --- a/src/checkpoints/checkpoints.cpp +++ b/src/checkpoints/checkpoints.cpp @@ -204,7 +204,7 @@ namespace cryptonote return true; } - bool checkpoints::load_checkpoints_from_json(const std::string json_hashfile_fullpath) + bool checkpoints::load_checkpoints_from_json(const std::string &json_hashfile_fullpath) { boost::system::error_code errcode; if (! (boost::filesystem::exists(json_hashfile_fullpath, errcode))) @@ -218,7 +218,11 @@ namespace cryptonote uint64_t prev_max_height = get_max_height(); LOG_PRINT_L1("Hard-coded max checkpoint height is " << prev_max_height); t_hash_json hashes; - epee::serialization::load_t_from_json_file(hashes, json_hashfile_fullpath); + if (!epee::serialization::load_t_from_json_file(hashes, json_hashfile_fullpath)) + { + MERROR("Error loading checkpoints from " << json_hashfile_fullpath); + return false; + } for (std::vector<t_hashline>::const_iterator it = hashes.hashlines.begin(); it != hashes.hashlines.end(); ) { uint64_t height; @@ -286,7 +290,7 @@ namespace cryptonote return true; } - bool checkpoints::load_new_checkpoints(const std::string json_hashfile_fullpath, bool testnet, bool dns) + bool checkpoints::load_new_checkpoints(const std::string &json_hashfile_fullpath, bool testnet, bool dns) { bool result; diff --git a/src/checkpoints/checkpoints.h b/src/checkpoints/checkpoints.h index 3e034f6f0..83969f7b8 100644 --- a/src/checkpoints/checkpoints.h +++ b/src/checkpoints/checkpoints.h @@ -165,7 +165,7 @@ namespace cryptonote * * @return true if loading successful and no conflicts */ - bool load_new_checkpoints(const std::string json_hashfile_fullpath, bool testnet=false, bool dns=true); + bool load_new_checkpoints(const std::string &json_hashfile_fullpath, bool testnet=false, bool dns=true); /** * @brief load new checkpoints from json @@ -174,7 +174,7 @@ namespace cryptonote * * @return true if loading successful and no conflicts */ - bool load_checkpoints_from_json(const std::string json_hashfile_fullpath); + bool load_checkpoints_from_json(const std::string &json_hashfile_fullpath); /** * @brief load new checkpoints from DNS diff --git a/src/common/download.cpp b/src/common/download.cpp index 28aac5a59..87814fa5e 100644 --- a/src/common/download.cpp +++ b/src/common/download.cpp @@ -33,6 +33,7 @@ #include <boost/thread/thread.hpp> #include "cryptonote_config.h" #include "include_base_utils.h" +#include "file_io_utils.h" #include "net/http_client.h" #include "download.h" @@ -74,9 +75,20 @@ namespace tools try { boost::unique_lock<boost::mutex> lock(control->mutex); - MINFO("Downloading " << control->uri << " to " << control->path); + std::ios_base::openmode mode = std::ios_base::out | std::ios_base::binary; + uint64_t existing_size = 0; + if (epee::file_io_utils::get_file_size(control->path, existing_size) && existing_size > 0) + { + MINFO("Resuming downloading " << control->uri << " to " << control->path << " from " << existing_size); + mode |= std::ios_base::app; + } + else + { + MINFO("Downloading " << control->uri << " to " << control->path); + mode |= std::ios_base::trunc; + } std::ofstream f; - f.open(control->path, std::ios_base::binary | std::ios_base::out | std::ios_base::trunc); + f.open(control->path, mode); if (!f.good()) { MERROR("Failed to open file " << control->path); control->result_cb(control->path, control->uri, control->success); @@ -85,11 +97,13 @@ namespace tools class download_client: public epee::net_utils::http::http_simple_client { public: - download_client(download_async_handle control, std::ofstream &f): - control(control), f(f), content_length(-1), total(0) {} + download_client(download_async_handle control, std::ofstream &f, uint64_t offset = 0): + control(control), f(f), content_length(-1), total(0), offset(offset) {} virtual ~download_client() { f.close(); } virtual bool on_header(const epee::net_utils::http::http_response_info &headers) { + for (const auto &kv: headers.m_header_info.m_etc_fields) + MDEBUG("Header: " << kv.first << ": " << kv.second); ssize_t length; if (epee::string_tools::get_xtype_from_string(length, headers.m_header_info.m_content_length) && length >= 0) { @@ -104,6 +118,26 @@ namespace tools return false; } } + if (offset > 0) + { + // we requested a range, so check if we're getting it, otherwise truncate + bool got_range = false; + const std::string prefix = "bytes=" + std::to_string(offset) + "-"; + for (const auto &kv: headers.m_header_info.m_etc_fields) + { + if (kv.first == "Content-Range" && strncmp(kv.second.c_str(), prefix.c_str(), prefix.size())) + { + got_range = true; + break; + } + } + if (!got_range) + { + MWARNING("We did not get the requested range, downloading from start"); + f.close(); + f.open(control->path, std::ios_base::out | std::ios_base::binary | std::ios_base::trunc); + } + } return true; } virtual bool handle_target_data(std::string &piece_of_transfer) @@ -130,7 +164,8 @@ namespace tools std::ofstream &f; ssize_t content_length; size_t total; - } client(control, f); + uint64_t offset; + } client(control, f, existing_size); epee::net_utils::http::url_content u_c; if (!epee::net_utils::parse_url(control->uri, u_c)) { @@ -147,9 +182,10 @@ namespace tools lock.unlock(); - uint16_t port = u_c.port ? u_c.port : 80; + bool ssl = u_c.schema == "https"; + uint16_t port = u_c.port ? u_c.port : ssl ? 443 : 80; MDEBUG("Connecting to " << u_c.host << ":" << port); - client.set_server(u_c.host, std::to_string(port), boost::none); + client.set_server(u_c.host, std::to_string(port), boost::none, ssl); if (!client.connect(std::chrono::seconds(30))) { boost::lock_guard<boost::mutex> lock(control->mutex); @@ -159,7 +195,14 @@ namespace tools } MDEBUG("GETting " << u_c.uri); const epee::net_utils::http::http_response_info *info = NULL; - if (!client.invoke_get(u_c.uri, std::chrono::seconds(30), "", &info)) + epee::net_utils::http::fields_list fields; + if (existing_size > 0) + { + const std::string range = "bytes=" + std::to_string(existing_size) + "-"; + MDEBUG("Asking for range: " << range); + fields.push_back(std::make_pair("Range", range)); + } + if (!client.invoke_get(u_c.uri, std::chrono::seconds(30), "", &info, fields)) { boost::lock_guard<boost::mutex> lock(control->mutex); MERROR("Failed to connect to " << control->uri); @@ -189,7 +232,7 @@ namespace tools MDEBUG("response body: " << info->m_body); for (const auto &f: info->m_additional_fields) MDEBUG("additional field: " << f.first << ": " << f.second); - if (info->m_response_code != 200) + if (info->m_response_code != 200 && info->m_response_code != 206) { boost::lock_guard<boost::mutex> lock(control->mutex); MERROR("Status code " << info->m_response_code); diff --git a/src/common/stack_trace.cpp b/src/common/stack_trace.cpp index bcdf72b60..ed1093309 100644 --- a/src/common/stack_trace.cpp +++ b/src/common/stack_trace.cpp @@ -28,7 +28,10 @@ #if !defined __GNUC__ || defined __MINGW32__ || defined __MINGW64__ || defined __ANDROID__ #define USE_UNWIND +#else +#define ELPP_FEATURE_CRASH_LOG 1 #endif +#include "easylogging++/easylogging++.h" #include <stdexcept> #ifdef USE_UNWIND diff --git a/src/common/updates.cpp b/src/common/updates.cpp index eff6754af..2d9c2d89c 100644 --- a/src/common/updates.cpp +++ b/src/common/updates.cpp @@ -99,7 +99,7 @@ namespace tools std::string get_update_url(const std::string &software, const std::string &subdir, const std::string &buildtag, const std::string &version, bool user) { - const char *base = user ? "https://downloads.getmonero.org/" : "http://updates.getmonero.org/"; + const char *base = user ? "https://downloads.getmonero.org/" : "https://updates.getmonero.org/"; #ifdef _WIN32 static const char *extension = strncmp(buildtag.c_str(), "install-", 8) ? ".zip" : ".exe"; #else diff --git a/src/common/util.cpp b/src/common/util.cpp index d9e12ffe4..2a2f50c4f 100644 --- a/src/common/util.cpp +++ b/src/common/util.cpp @@ -635,13 +635,13 @@ std::string get_nix_version_display_string() int vercmp(const char *v0, const char *v1) { std::vector<std::string> f0, f1; - boost::split(f0, v0, boost::is_any_of(".")); - boost::split(f1, v1, boost::is_any_of(".")); - while (f0.size() < f1.size()) - f0.push_back("0"); - while (f1.size() < f0.size()) - f1.push_back("0"); - for (size_t i = 0; i < f0.size(); ++i) { + boost::split(f0, v0, boost::is_any_of(".-")); + boost::split(f1, v1, boost::is_any_of(".-")); + for (size_t i = 0; i < std::max(f0.size(), f1.size()); ++i) { + if (i >= f0.size()) + return -1; + if (i >= f1.size()) + return 1; int f0i = atoi(f0[i].c_str()), f1i = atoi(f1[i].c_str()); int n = f0i - f1i; if (n) diff --git a/src/crypto/CMakeLists.txt b/src/crypto/CMakeLists.txt index 1e06a0dfd..fd71a87e7 100644 --- a/src/crypto/CMakeLists.txt +++ b/src/crypto/CMakeLists.txt @@ -29,7 +29,7 @@ set(crypto_sources aesb.c blake256.c - chacha8.c + chacha.c crypto-ops-data.c crypto-ops.c crypto.cpp @@ -51,7 +51,7 @@ set(crypto_headers) set(crypto_private_headers blake256.h - chacha8.h + chacha.h crypto-ops.h crypto.h generic-ops.h diff --git a/src/crypto/chacha8.c b/src/crypto/chacha.c index df135af59..f573083be 100644 --- a/src/crypto/chacha8.c +++ b/src/crypto/chacha.c @@ -8,7 +8,7 @@ Public domain. #include <stdio.h> #include <sys/param.h> -#include "chacha8.h" +#include "chacha.h" #include "common/int-util.h" #include "warnings.h" @@ -40,7 +40,7 @@ static const char sigma[] = "expand 32-byte k"; DISABLE_GCC_AND_CLANG_WARNING(strict-aliasing) -void chacha8(const void* data, size_t length, const uint8_t* key, const uint8_t* iv, char* cipher) { +static void chacha(unsigned rounds, const void* data, size_t length, const uint8_t* key, const uint8_t* iv, char* cipher) { uint32_t x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15; uint32_t j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15; char* ctarget = 0; @@ -89,7 +89,7 @@ void chacha8(const void* data, size_t length, const uint8_t* key, const uint8_t* x13 = j13; x14 = j14; x15 = j15; - for (i = 8;i > 0;i -= 2) { + for (i = rounds;i > 0;i -= 2) { QUARTERROUND( x0, x4, x8,x12) QUARTERROUND( x1, x5, x9,x13) QUARTERROUND( x2, x6,x10,x14) @@ -168,3 +168,13 @@ void chacha8(const void* data, size_t length, const uint8_t* key, const uint8_t* data = (uint8_t*)data + 64; } } + +void chacha8(const void* data, size_t length, const uint8_t* key, const uint8_t* iv, char* cipher) +{ + chacha(8, data, length, key, iv, cipher); +} + +void chacha20(const void* data, size_t length, const uint8_t* key, const uint8_t* iv, char* cipher) +{ + chacha(20, data, length, key, iv, cipher); +} diff --git a/src/crypto/chacha8.h b/src/crypto/chacha.h index dcbe6a933..a9665030d 100644 --- a/src/crypto/chacha8.h +++ b/src/crypto/chacha.h @@ -33,8 +33,8 @@ #include <stdint.h> #include <stddef.h> -#define CHACHA8_KEY_SIZE 32 -#define CHACHA8_IV_SIZE 8 +#define CHACHA_KEY_SIZE 32 +#define CHACHA_IV_SIZE 8 #if defined(__cplusplus) #include <memory.h> @@ -46,33 +46,38 @@ namespace crypto { extern "C" { #endif void chacha8(const void* data, size_t length, const uint8_t* key, const uint8_t* iv, char* cipher); + void chacha20(const void* data, size_t length, const uint8_t* key, const uint8_t* iv, char* cipher); #if defined(__cplusplus) } - using chacha8_key = tools::scrubbed_arr<uint8_t, CHACHA8_KEY_SIZE>; + using chacha_key = tools::scrubbed_arr<uint8_t, CHACHA_KEY_SIZE>; #pragma pack(push, 1) - // MS VC 2012 doesn't interpret `class chacha8_iv` as POD in spite of [9.0.10], so it is a struct - struct chacha8_iv { - uint8_t data[CHACHA8_IV_SIZE]; + // MS VC 2012 doesn't interpret `class chacha_iv` as POD in spite of [9.0.10], so it is a struct + struct chacha_iv { + uint8_t data[CHACHA_IV_SIZE]; }; #pragma pack(pop) - static_assert(sizeof(chacha8_key) == CHACHA8_KEY_SIZE && sizeof(chacha8_iv) == CHACHA8_IV_SIZE, "Invalid structure size"); + static_assert(sizeof(chacha_key) == CHACHA_KEY_SIZE && sizeof(chacha_iv) == CHACHA_IV_SIZE, "Invalid structure size"); - inline void chacha8(const void* data, std::size_t length, const chacha8_key& key, const chacha8_iv& iv, char* cipher) { + inline void chacha8(const void* data, std::size_t length, const chacha_key& key, const chacha_iv& iv, char* cipher) { chacha8(data, length, key.data(), reinterpret_cast<const uint8_t*>(&iv), cipher); } - inline void generate_chacha8_key(const void *data, size_t size, chacha8_key& key) { - static_assert(sizeof(chacha8_key) <= sizeof(hash), "Size of hash must be at least that of chacha8_key"); + inline void chacha20(const void* data, std::size_t length, const chacha_key& key, const chacha_iv& iv, char* cipher) { + chacha20(data, length, key.data(), reinterpret_cast<const uint8_t*>(&iv), cipher); + } + + inline void generate_chacha_key(const void *data, size_t size, chacha_key& key) { + static_assert(sizeof(chacha_key) <= sizeof(hash), "Size of hash must be at least that of chacha_key"); tools::scrubbed_arr<char, HASH_SIZE> pwd_hash; crypto::cn_slow_hash(data, size, pwd_hash.data()); memcpy(&key, pwd_hash.data(), sizeof(key)); } - inline void generate_chacha8_key(std::string password, chacha8_key& key) { - return generate_chacha8_key(password.data(), password.size(), key); + inline void generate_chacha_key(std::string password, chacha_key& key) { + return generate_chacha_key(password.data(), password.size(), key); } } diff --git a/src/cryptonote_basic/account.cpp b/src/cryptonote_basic/account.cpp index fb832d88e..ddc1fc7fc 100644 --- a/src/cryptonote_basic/account.cpp +++ b/src/cryptonote_basic/account.cpp @@ -64,6 +64,7 @@ DISABLE_VS_WARNINGS(4244 4345) void account_base::forget_spend_key() { m_keys.m_spend_secret_key = crypto::secret_key(); + m_keys.m_multisig_keys.clear(); } //----------------------------------------------------------------- crypto::secret_key account_base::generate(const crypto::secret_key& recovery_key, bool recover, bool two_random) @@ -123,6 +124,20 @@ DISABLE_VS_WARNINGS(4244 4345) create_from_keys(address, fake, viewkey); } //----------------------------------------------------------------- + bool account_base::make_multisig(const crypto::secret_key &view_secret_key, const crypto::secret_key &spend_secret_key, const crypto::public_key &spend_public_key, const std::vector<crypto::secret_key> &multisig_keys) + { + m_keys.m_account_address.m_spend_public_key = spend_public_key; + m_keys.m_view_secret_key = view_secret_key; + m_keys.m_spend_secret_key = spend_secret_key; + m_keys.m_multisig_keys = multisig_keys; + return crypto::secret_key_to_public_key(view_secret_key, m_keys.m_account_address.m_view_public_key); + } + //----------------------------------------------------------------- + void account_base::finalize_multisig(const crypto::public_key &spend_public_key) + { + m_keys.m_account_address.m_spend_public_key = spend_public_key; + } + //----------------------------------------------------------------- const account_keys& account_base::get_keys() const { return m_keys; diff --git a/src/cryptonote_basic/account.h b/src/cryptonote_basic/account.h index e0d5447a2..50af36a9d 100644 --- a/src/cryptonote_basic/account.h +++ b/src/cryptonote_basic/account.h @@ -42,11 +42,13 @@ namespace cryptonote account_public_address m_account_address; crypto::secret_key m_spend_secret_key; crypto::secret_key m_view_secret_key; + std::vector<crypto::secret_key> m_multisig_keys; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(m_account_address) KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE(m_spend_secret_key) KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE(m_view_secret_key) + KV_SERIALIZE_CONTAINER_POD_AS_BLOB(m_multisig_keys) END_KV_SERIALIZE_MAP() }; @@ -60,6 +62,8 @@ namespace cryptonote crypto::secret_key generate(const crypto::secret_key& recovery_key = crypto::secret_key(), bool recover = false, bool two_random = false); void create_from_keys(const cryptonote::account_public_address& address, const crypto::secret_key& spendkey, const crypto::secret_key& viewkey); void create_from_viewkey(const cryptonote::account_public_address& address, const crypto::secret_key& viewkey); + bool make_multisig(const crypto::secret_key &view_secret_key, const crypto::secret_key &spend_secret_key, const crypto::public_key &spend_public_key, const std::vector<crypto::secret_key> &multisig_keys); + void finalize_multisig(const crypto::public_key &spend_public_key); const account_keys& get_keys() const; std::string get_public_address_str(bool testnet) const; std::string get_public_integrated_address_str(const crypto::hash8 &payment_id, bool testnet) const; @@ -71,6 +75,7 @@ namespace cryptonote bool store(const std::string& file_path); void forget_spend_key(); + const std::vector<crypto::secret_key> &get_multisig_keys() const { return m_keys.m_multisig_keys; } template <class t_archive> inline void serialize(t_archive &a, const unsigned int /*ver*/) diff --git a/src/cryptonote_basic/cryptonote_basic.h b/src/cryptonote_basic/cryptonote_basic.h index 89dda8c3d..821c21d84 100644 --- a/src/cryptonote_basic/cryptonote_basic.h +++ b/src/cryptonote_basic/cryptonote_basic.h @@ -259,7 +259,7 @@ namespace cryptonote ar.tag("rctsig_prunable"); ar.begin_object(); r = rct_signatures.p.serialize_rctsig_prunable(ar, rct_signatures.type, vin.size(), vout.size(), - vin[0].type() == typeid(txin_to_key) ? boost::get<txin_to_key>(vin[0]).key_offsets.size() - 1 : 0); + vin.size() > 0 && vin[0].type() == typeid(txin_to_key) ? boost::get<txin_to_key>(vin[0]).key_offsets.size() - 1 : 0); if (!r || !ar.stream().good()) return false; ar.end_object(); } diff --git a/src/cryptonote_basic/cryptonote_boost_serialization.h b/src/cryptonote_basic/cryptonote_boost_serialization.h index 760edf9b9..ed8239176 100644 --- a/src/cryptonote_basic/cryptonote_boost_serialization.h +++ b/src/cryptonote_basic/cryptonote_boost_serialization.h @@ -253,6 +253,21 @@ namespace boost } template <class Archive> + inline void serialize(Archive &a, rct::multisig_kLRki &x, const boost::serialization::version_type ver) + { + a & x.k; + a & x.L; + a & x.R; + a & x.ki; + } + + template <class Archive> + inline void serialize(Archive &a, rct::multisig_out &x, const boost::serialization::version_type ver) + { + a & x.c; + } + + template <class Archive> inline typename std::enable_if<Archive::is_loading::value, void>::type serializeOutPk(Archive &a, rct::ctkeyV &outPk_, const boost::serialization::version_type ver) { rct::keyV outPk; diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp index a22c3bdea..21fa63842 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.cpp +++ b/src/cryptonote_basic/cryptonote_format_utils.cpp @@ -80,6 +80,31 @@ static std::atomic<uint64_t> tx_hashes_cached_count(0); static std::atomic<uint64_t> block_hashes_calculated_count(0); static std::atomic<uint64_t> block_hashes_cached_count(0); +#define CHECK_AND_ASSERT_THROW_MES_L1(expr, message) {if(!(expr)) {MWARNING(message); throw std::runtime_error(message);}} + +namespace cryptonote +{ + static inline unsigned char *operator &(ec_point &point) { + return &reinterpret_cast<unsigned char &>(point); + } + static inline const unsigned char *operator &(const ec_point &point) { + return &reinterpret_cast<const unsigned char &>(point); + } + + // a copy of rct::addKeys, since we can't link to libringct to avoid circular dependencies + static void add_public_key(crypto::public_key &AB, const crypto::public_key &A, const crypto::public_key &B) { + ge_p3 B2, A2; + CHECK_AND_ASSERT_THROW_MES_L1(ge_frombytes_vartime(&B2, &B) == 0, "ge_frombytes_vartime failed at "+boost::lexical_cast<std::string>(__LINE__)); + CHECK_AND_ASSERT_THROW_MES_L1(ge_frombytes_vartime(&A2, &A) == 0, "ge_frombytes_vartime failed at "+boost::lexical_cast<std::string>(__LINE__)); + ge_cached tmp2; + ge_p3_to_cached(&tmp2, &B2); + ge_p1p1 tmp3; + ge_add(&tmp3, &A2, &tmp2); + ge_p1p1_to_p3(&A2, &tmp3); + ge_p3_tobytes(&AB, &A2); + } +} + namespace cryptonote { //--------------------------------------------------------------- @@ -182,6 +207,7 @@ namespace cryptonote crypto::derive_secret_key(recv_derivation, real_output_index, ack.m_spend_secret_key, scalar_step1); // computes Hs(a*R || idx) + b // step 2: add Hs(a || index_major || index_minor) + crypto::secret_key subaddr_sk; crypto::secret_key scalar_step2; if (received_index.is_zero()) { @@ -189,13 +215,32 @@ namespace cryptonote } else { - crypto::secret_key m = get_subaddress_secret_key(ack.m_view_secret_key, received_index); - sc_add((unsigned char*)&scalar_step2, (unsigned char*)&scalar_step1, (unsigned char*)&m); + subaddr_sk = get_subaddress_secret_key(ack.m_view_secret_key, received_index); + sc_add((unsigned char*)&scalar_step2, (unsigned char*)&scalar_step1, (unsigned char*)&subaddr_sk); } in_ephemeral.sec = scalar_step2; - crypto::secret_key_to_public_key(in_ephemeral.sec, in_ephemeral.pub); - CHECK_AND_ASSERT_MES(in_ephemeral.pub == out_key, false, "key image helper precomp: given output pubkey doesn't match the derived one"); + + if (ack.m_multisig_keys.empty()) + { + // when not in multisig, we know the full spend secret key, so the output pubkey can be obtained by scalarmultBase + CHECK_AND_ASSERT_MES(crypto::secret_key_to_public_key(in_ephemeral.sec, in_ephemeral.pub), false, "Failed to derive public key"); + } + else + { + // when in multisig, we only know the partial spend secret key. but we do know the full spend public key, so the output pubkey can be obtained by using the standard CN key derivation + CHECK_AND_ASSERT_MES(crypto::derive_public_key(recv_derivation, real_output_index, ack.m_account_address.m_spend_public_key, in_ephemeral.pub), false, "Failed to derive public key"); + // and don't forget to add the contribution from the subaddress part + if (!received_index.is_zero()) + { + crypto::public_key subaddr_pk; + CHECK_AND_ASSERT_MES(crypto::secret_key_to_public_key(subaddr_sk, subaddr_pk), false, "Failed to derive public key"); + add_public_key(in_ephemeral.pub, in_ephemeral.pub, subaddr_pk); + } + } + + CHECK_AND_ASSERT_MES(in_ephemeral.pub == out_key, + false, "key image helper precomp: given output pubkey doesn't match the derived one"); } crypto::generate_key_image(in_ephemeral.pub, in_ephemeral.sec, ki); @@ -585,17 +630,21 @@ namespace cryptonote bool is_out_to_acc(const account_keys& acc, const txout_to_key& out_key, const crypto::public_key& tx_pub_key, const std::vector<crypto::public_key>& additional_tx_pub_keys, size_t output_index) { crypto::key_derivation derivation; - generate_key_derivation(tx_pub_key, acc.m_view_secret_key, derivation); + bool r = generate_key_derivation(tx_pub_key, acc.m_view_secret_key, derivation); + CHECK_AND_ASSERT_MES(r, false, "Failed to generate key derivation"); crypto::public_key pk; - derive_public_key(derivation, output_index, acc.m_account_address.m_spend_public_key, pk); + r = derive_public_key(derivation, output_index, acc.m_account_address.m_spend_public_key, pk); + CHECK_AND_ASSERT_MES(r, false, "Failed to derive public key"); if (pk == out_key.key) return true; // try additional tx pubkeys if available if (!additional_tx_pub_keys.empty()) { CHECK_AND_ASSERT_MES(output_index < additional_tx_pub_keys.size(), false, "wrong number of additional tx pubkeys"); - generate_key_derivation(additional_tx_pub_keys[output_index], acc.m_view_secret_key, derivation); - derive_public_key(derivation, output_index, acc.m_account_address.m_spend_public_key, pk); + r = generate_key_derivation(additional_tx_pub_keys[output_index], acc.m_view_secret_key, derivation); + CHECK_AND_ASSERT_MES(r, false, "Failed to generate key derivation"); + r = derive_public_key(derivation, output_index, acc.m_account_address.m_spend_public_key, pk); + CHECK_AND_ASSERT_MES(r, false, "Failed to derive public key"); return pk == out_key.key; } return false; diff --git a/src/cryptonote_core/CMakeLists.txt b/src/cryptonote_core/CMakeLists.txt index 169a38f0a..eeed881da 100644 --- a/src/cryptonote_core/CMakeLists.txt +++ b/src/cryptonote_core/CMakeLists.txt @@ -59,6 +59,7 @@ target_link_libraries(cryptonote_core common cncrypto blockchain_db + multisig ringct ${Boost_DATE_TIME_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 123bd194b..709c5e852 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -321,6 +321,7 @@ bool Blockchain::init(BlockchainDB* db, const bool testnet, bool offline, const if (!db->is_open()) { LOG_ERROR("Attempted to init Blockchain with unopened DB"); + delete db; return false; } @@ -471,7 +472,7 @@ bool Blockchain::deinit() // memory operation), otherwise we may cause a loop. if (m_db == NULL) { - throw new DB_ERROR("The db pointer is null in Blockchain, the blockchain may be corrupt!"); + throw DB_ERROR("The db pointer is null in Blockchain, the blockchain may be corrupt!"); } try @@ -489,7 +490,9 @@ bool Blockchain::deinit() } delete m_hardfork; + m_hardfork = NULL; delete m_db; + m_db = NULL; return true; } //------------------------------------------------------------------ @@ -2050,49 +2053,6 @@ bool Blockchain::get_transactions(const t_ids_container& txs_ids, t_tx_container return true; } //------------------------------------------------------------------ -void Blockchain::print_blockchain(uint64_t start_index, uint64_t end_index) const -{ - LOG_PRINT_L3("Blockchain::" << __func__); - std::stringstream ss; - CRITICAL_REGION_LOCAL(m_blockchain_lock); - auto h = m_db->height(); - if(start_index > h) - { - MERROR("Wrong starter index set: " << start_index << ", expected max index " << h); - return; - } - - for(size_t i = start_index; i <= h && i != end_index; i++) - { - ss << "height " << i << ", timestamp " << m_db->get_block_timestamp(i) << ", cumul_dif " << m_db->get_block_cumulative_difficulty(i) << ", size " << m_db->get_block_size(i) << "\nid\t\t" << m_db->get_block_hash_from_height(i) << "\ndifficulty\t\t" << m_db->get_block_difficulty(i) << ", nonce " << m_db->get_block_from_height(i).nonce << ", tx_count " << m_db->get_block_from_height(i).tx_hashes.size() << std::endl; - } - MCINFO("globlal", "Current blockchain:" << std::endl << ss.str()); -} -//------------------------------------------------------------------ -void Blockchain::print_blockchain_index() const -{ - LOG_PRINT_L3("Blockchain::" << __func__); - std::stringstream ss; - CRITICAL_REGION_LOCAL(m_blockchain_lock); - auto height = m_db->height(); - if (height != 0) - { - for(uint64_t i = 0; i <= height; i++) - { - ss << "height: " << i << ", hash: " << m_db->get_block_hash_from_height(i); - } - } - - MINFO("Current blockchain index:" << std::endl << ss.str()); -} -//------------------------------------------------------------------ -//TODO: remove this function and references to it -void Blockchain::print_blockchain_outs(const std::string& file) const -{ - LOG_PRINT_L3("Blockchain::" << __func__); - return; -} -//------------------------------------------------------------------ // Find the split point between us and foreign blockchain and return // (by reference) the most recent common block hash along with up to // BLOCKS_IDS_SYNCHRONIZING_DEFAULT_COUNT additional (more recent) hashes. @@ -2338,7 +2298,7 @@ void Blockchain::on_new_tx_from_block(const cryptonote::transaction &tx) TIME_MEASURE_FINISH(a); if(m_show_time_stats) { - size_t ring_size = tx.vin[0].type() == typeid(txin_to_key) ? boost::get<txin_to_key>(tx.vin[0]).key_offsets.size() : 0; + size_t ring_size = !tx.vin.empty() && tx.vin[0].type() == typeid(txin_to_key) ? boost::get<txin_to_key>(tx.vin[0]).key_offsets.size() : 0; MINFO("HASH: " << "-" << " I/M/O: " << tx.vin.size() << "/" << ring_size << "/" << tx.vout.size() << " H: " << 0 << " chcktx: " << a); } } @@ -2373,7 +2333,7 @@ bool Blockchain::check_tx_inputs(transaction& tx, uint64_t& max_used_block_heigh TIME_MEASURE_FINISH(a); if(m_show_time_stats) { - size_t ring_size = tx.vin[0].type() == typeid(txin_to_key) ? boost::get<txin_to_key>(tx.vin[0]).key_offsets.size() : 0; + size_t ring_size = !tx.vin.empty() && tx.vin[0].type() == typeid(txin_to_key) ? boost::get<txin_to_key>(tx.vin[0]).key_offsets.size() : 0; MINFO("HASH: " << get_transaction_hash(tx) << " I/M/O: " << tx.vin.size() << "/" << ring_size << "/" << tx.vout.size() << " H: " << max_used_block_height << " ms: " << a + m_fake_scan_time << " B: " << get_object_blobsize(tx)); } if (!res) @@ -2466,6 +2426,7 @@ bool Blockchain::expand_transaction_2(transaction &tx, const crypto::hash &tx_pr // mixRing - full and simple store it in opposite ways if (rv.type == rct::RCTTypeFull || rv.type == rct::RCTTypeFullBulletproof) { + CHECK_AND_ASSERT_MES(!pubkeys.empty() && !pubkeys[0].empty(), false, "empty pubkeys"); rv.mixRing.resize(pubkeys[0].size()); for (size_t m = 0; m < pubkeys[0].size(); ++m) rv.mixRing[m].clear(); @@ -2480,6 +2441,7 @@ bool Blockchain::expand_transaction_2(transaction &tx, const crypto::hash &tx_pr } else if (rv.type == rct::RCTTypeSimple || rv.type == rct::RCTTypeSimpleBulletproof) { + CHECK_AND_ASSERT_MES(!pubkeys.empty() && !pubkeys[0].empty(), false, "empty pubkeys"); rv.mixRing.resize(pubkeys.size()); for (size_t n = 0; n < pubkeys.size(); ++n) { @@ -2811,7 +2773,7 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, } for (size_t n = 0; n < tx.vin.size(); ++n) { - if (memcmp(&boost::get<txin_to_key>(tx.vin[n]).k_image, &rv.p.MGs[n].II[0], 32)) + if (rv.p.MGs[n].II.empty() || memcmp(&boost::get<txin_to_key>(tx.vin[n]).k_image, &rv.p.MGs[n].II[0], 32)) { MERROR_VER("Failed to check ringct signatures: mismatched key image"); return false; @@ -2864,7 +2826,7 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, MERROR_VER("Failed to check ringct signatures: Bad MGs size"); return false; } - if (rv.p.MGs[0].II.size() != tx.vin.size()) + if (rv.p.MGs.empty() || rv.p.MGs[0].II.size() != tx.vin.size()) { MERROR_VER("Failed to check ringct signatures: mismatched II/vin sizes"); return false; diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index b76d0555f..2d5307ac0 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -680,32 +680,6 @@ namespace cryptonote //debug functions /** - * @brief prints data about a snippet of the blockchain - * - * if start_index is greater than the blockchain height, do nothing - * - * @param start_index height on chain to start at - * @param end_index height on chain to end at - */ - void print_blockchain(uint64_t start_index, uint64_t end_index) const; - - /** - * @brief prints every block's hash - * - * WARNING: This function will absolutely crush a terminal in prints, so - * it is recommended to redirect this output to a log file (or null sink - * if a log file is already set up, as should be the default) - */ - void print_blockchain_index() const; - - /** - * @brief currently does nothing, candidate for removal - * - * @param file - */ - void print_blockchain_outs(const std::string& file) const; - - /** * @brief check the blockchain against a set of checkpoints * * If a block fails a checkpoint and enforce is enabled, the blockchain diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 960a07832..adbc727b0 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -47,6 +47,7 @@ using namespace epee; #include "cryptonote_config.h" #include "cryptonote_tx_utils.h" #include "misc_language.h" +#include "file_io_utils.h" #include <csignal> #include "checkpoints/checkpoints.h" #include "ringct/rctTypes.h" @@ -377,7 +378,7 @@ namespace cryptonote // folder might not be a directory, etc, etc catch (...) { } - BlockchainDB* db = new_db(db_type); + std::unique_ptr<BlockchainDB> db(new_db(db_type)); if (db == NULL) { LOG_ERROR("Attempted to use non-existent database type"); @@ -468,7 +469,7 @@ namespace cryptonote m_blockchain_storage.set_user_options(blocks_threads, blocks_per_sync, sync_mode, fast_sync); - r = m_blockchain_storage.init(db, m_testnet, test_options); + r = m_blockchain_storage.init(db.release(), m_testnet, m_offline, test_options); r = m_mempool.init(); CHECK_AND_ASSERT_MES(r, false, "Failed to initialize memory pool"); @@ -1052,21 +1053,6 @@ namespace cryptonote return m_blockchain_storage.find_blockchain_supplement(req_start_block, qblock_ids, blocks, total_height, start_height, max_count); } //----------------------------------------------------------------------------------------------- - void core::print_blockchain(uint64_t start_index, uint64_t end_index) const - { - m_blockchain_storage.print_blockchain(start_index, end_index); - } - //----------------------------------------------------------------------------------------------- - void core::print_blockchain_index() const - { - m_blockchain_storage.print_blockchain_index(); - } - //----------------------------------------------------------------------------------------------- - void core::print_blockchain_outs(const std::string& file) - { - m_blockchain_storage.print_blockchain_outs(file); - } - //----------------------------------------------------------------------------------------------- bool core::get_random_outs_for_amounts(const COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::request& req, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response& res) const { return m_blockchain_storage.get_random_outs_for_amounts(req, res); @@ -1452,27 +1438,56 @@ namespace cryptonote if (!tools::sha256sum(path.string(), file_hash) || (hash != epee::string_tools::pod_to_hex(file_hash))) { MCDEBUG("updates", "We don't have that file already, downloading"); + const std::string tmppath = path.string() + ".tmp"; + if (epee::file_io_utils::is_file_exist(tmppath)) + { + MCDEBUG("updates", "We have part of the file already, resuming download"); + } m_last_update_length = 0; - m_update_download = tools::download_async(path.string(), url, [this, hash](const std::string &path, const std::string &uri, bool success) { + m_update_download = tools::download_async(tmppath, url, [this, hash, path](const std::string &tmppath, const std::string &uri, bool success) { + bool remove = false, good = true; if (success) { crypto::hash file_hash; - if (!tools::sha256sum(path, file_hash)) + if (!tools::sha256sum(tmppath, file_hash)) { - MCERROR("updates", "Failed to hash " << path); + MCERROR("updates", "Failed to hash " << tmppath); + remove = true; + good = false; } - if (hash != epee::string_tools::pod_to_hex(file_hash)) + else if (hash != epee::string_tools::pod_to_hex(file_hash)) { MCERROR("updates", "Download from " << uri << " does not match the expected hash"); + remove = true; + good = false; } - MCLOG_CYAN(el::Level::Info, "updates", "New version downloaded to " << path); } else { MCERROR("updates", "Failed to download " << uri); + good = false; } boost::unique_lock<boost::mutex> lock(m_update_mutex); m_update_download = 0; + if (success && !remove) + { + std::error_code e = tools::replace_file(tmppath, path.string()); + if (e) + { + MCERROR("updates", "Failed to rename downloaded file"); + good = false; + } + } + else if (remove) + { + if (!boost::filesystem::remove(tmppath)) + { + MCERROR("updates", "Failed to remove invalid downloaded file"); + good = false; + } + } + if (good) + MCLOG_CYAN(el::Level::Info, "updates", "New version downloaded to " << path.string()); }, [this](const std::string &path, const std::string &uri, size_t length, ssize_t content_length) { if (length >= m_last_update_length + 1024 * 1024 * 10) { diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index 9f84ed303..adc201fb5 100644 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -601,20 +601,6 @@ namespace cryptonote const Blockchain& get_blockchain_storage()const{return m_blockchain_storage;} /** - * @copydoc Blockchain::print_blockchain - * - * @note see Blockchain::print_blockchain - */ - void print_blockchain(uint64_t start_index, uint64_t end_index) const; - - /** - * @copydoc Blockchain::print_blockchain_index - * - * @note see Blockchain::print_blockchain_index - */ - void print_blockchain_index() const; - - /** * @copydoc tx_memory_pool::print_pool * * @note see tx_memory_pool::print_pool @@ -622,13 +608,6 @@ namespace cryptonote std::string print_pool(bool short_format) const; /** - * @copydoc Blockchain::print_blockchain_outs - * - * @note see Blockchain::print_blockchain_outs - */ - void print_blockchain_outs(const std::string& file); - - /** * @copydoc miner::on_synchronized * * @note see miner::on_synchronized diff --git a/src/cryptonote_core/cryptonote_tx_utils.cpp b/src/cryptonote_core/cryptonote_tx_utils.cpp index 662420bef..916b1e05a 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.cpp +++ b/src/cryptonote_core/cryptonote_tx_utils.cpp @@ -40,12 +40,39 @@ using namespace epee; #include "crypto/crypto.h" #include "crypto/hash.h" #include "ringct/rctSigs.h" +#include "multisig/multisig.h" using namespace crypto; namespace cryptonote { //--------------------------------------------------------------- + void classify_addresses(const std::vector<tx_destination_entry> &destinations, const boost::optional<cryptonote::account_public_address>& change_addr, size_t &num_stdaddresses, size_t &num_subaddresses, account_public_address &single_dest_subaddress) + { + num_stdaddresses = 0; + num_subaddresses = 0; + std::unordered_set<cryptonote::account_public_address> unique_dst_addresses; + for(const tx_destination_entry& dst_entr: destinations) + { + if (change_addr && dst_entr.addr == change_addr) + continue; + if (unique_dst_addresses.count(dst_entr.addr) == 0) + { + unique_dst_addresses.insert(dst_entr.addr); + if (dst_entr.is_subaddress) + { + ++num_subaddresses; + single_dest_subaddress = dst_entr.addr; + } + else + { + ++num_stdaddresses; + } + } + } + LOG_PRINT_L2("destinations include " << num_stdaddresses << " standard addresses and " << num_subaddresses << " subaddresses"); + } + //--------------------------------------------------------------- bool construct_miner_tx(size_t height, size_t median_size, uint64_t already_generated_coins, size_t current_block_size, uint64_t fee, const account_public_address &miner_address, transaction& tx, const blobdata& extra_nonce, size_t max_outs, uint8_t hard_fork_version) { tx.vin.clear(); tx.vout.clear(); @@ -161,19 +188,27 @@ namespace cryptonote return destinations[0].addr.m_view_public_key; } //--------------------------------------------------------------- - bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, 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, bool bulletproof) + 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, const std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, 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, bool bulletproof, rct::multisig_out *msout) { + if (sources.empty()) + { + LOG_ERROR("Empty sources"); + return false; + } + 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; tx.extra = extra; - keypair txkey; - txkey.sec = rct::rct2sk(rct::skGen()); - tx_key = txkey.sec; + crypto::public_key txkey_pub; // if we have a stealth payment id, find it and encrypt it with the tx key now std::vector<tx_extra_field> tx_extra_fields; @@ -193,7 +228,7 @@ namespace cryptonote return false; } - if (!encrypt_payment_id(payment_id, view_key_pub, txkey.sec)) + if (!encrypt_payment_id(payment_id, view_key_pub, tx_key)) { LOG_ERROR("Failed to encrypt payment id"); return false; @@ -247,8 +282,8 @@ namespace cryptonote return false; } - //check that derivated key is equal with real output key - if( !(in_ephemeral.pub == src_entr.outputs[src_entr.real_output].second.dest) ) + //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) ) { 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:" @@ -261,7 +296,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 = img; + input_to_key.k_image = msout ? rct::rct2ki(src_entr.multisig_kLRki.ki) : img; //fill outputs array and use relative offsets for(const tx_source_entry::output_entry& out_entry: src_entr.outputs) @@ -293,47 +328,29 @@ namespace cryptonote // figure out if we need to make additional tx pubkeys size_t num_stdaddresses = 0; size_t num_subaddresses = 0; - std::unordered_set<cryptonote::account_public_address> unique_dst_addresses; account_public_address single_dest_subaddress; - for(const tx_destination_entry& dst_entr: destinations) - { - if (change_addr && dst_entr.addr == *change_addr) - continue; - if (unique_dst_addresses.count(dst_entr.addr) == 0) - { - unique_dst_addresses.insert(dst_entr.addr); - if (dst_entr.is_subaddress) - { - ++num_subaddresses; - single_dest_subaddress = dst_entr.addr; - } - else - { - ++num_stdaddresses; - } - } - } - LOG_PRINT_L2("destinations include " << num_stdaddresses << " standard addresses and " << num_subaddresses << "subaddresses"); + classify_addresses(destinations, change_addr, num_stdaddresses, num_subaddresses, single_dest_subaddress); // if this is a single-destination transfer to a subaddress, we set the tx pubkey to R=s*D if (num_stdaddresses == 0 && num_subaddresses == 1) { - txkey.pub = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(single_dest_subaddress.m_spend_public_key), rct::sk2rct(txkey.sec))); + txkey_pub = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(single_dest_subaddress.m_spend_public_key), rct::sk2rct(tx_key))); } else { - txkey.pub = rct::rct2pk(rct::scalarmultBase(rct::sk2rct(txkey.sec))); + txkey_pub = rct::rct2pk(rct::scalarmultBase(rct::sk2rct(tx_key))); } remove_field_from_tx_extra(tx.extra, typeid(tx_extra_pub_key)); - add_tx_pub_key_to_extra(tx, txkey.pub); + add_tx_pub_key_to_extra(tx, txkey_pub); std::vector<crypto::public_key> additional_tx_public_keys; - additional_tx_keys.clear(); // we don't need to include additional tx keys if: // - all the destinations are standard addresses // - there's only one destination which is a subaddress bool need_additional_txkeys = num_subaddresses > 0 && (num_stdaddresses > 0 || num_subaddresses > 1); + if (need_additional_txkeys) + CHECK_AND_ASSERT_MES(destinations.size() == additional_tx_keys.size(), false, "Wrong amount of additional tx keys"); uint64_t summary_outs_money = 0; //fill outputs @@ -348,7 +365,7 @@ namespace cryptonote keypair additional_txkey; if (need_additional_txkeys) { - additional_txkey.sec = rct::rct2sk(rct::skGen()); + additional_txkey.sec = additional_tx_keys[output_index]; if (dst_entr.is_subaddress) additional_txkey.pub = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(dst_entr.addr.m_spend_public_key), rct::sk2rct(additional_txkey.sec))); else @@ -359,20 +376,19 @@ namespace cryptonote if (change_addr && dst_entr.addr == *change_addr) { // sending change to yourself; derivation = a*R - r = crypto::generate_key_derivation(txkey.pub, sender_account_keys.m_view_secret_key, derivation); - CHECK_AND_ASSERT_MES(r, false, "at creation outs: failed to generate_key_derivation(" << txkey.pub << ", " << sender_account_keys.m_view_secret_key << ")"); + r = crypto::generate_key_derivation(txkey_pub, sender_account_keys.m_view_secret_key, derivation); + CHECK_AND_ASSERT_MES(r, false, "at creation outs: failed to generate_key_derivation(" << txkey_pub << ", " << sender_account_keys.m_view_secret_key << ")"); } else { // sending to the recipient; derivation = r*A (or s*C in the subaddress scheme) - r = crypto::generate_key_derivation(dst_entr.addr.m_view_public_key, dst_entr.is_subaddress && need_additional_txkeys ? additional_txkey.sec : txkey.sec, derivation); - CHECK_AND_ASSERT_MES(r, false, "at creation outs: failed to generate_key_derivation(" << dst_entr.addr.m_view_public_key << ", " << (dst_entr.is_subaddress && need_additional_txkeys ? additional_txkey.sec : txkey.sec) << ")"); + r = crypto::generate_key_derivation(dst_entr.addr.m_view_public_key, dst_entr.is_subaddress && need_additional_txkeys ? additional_txkey.sec : tx_key, derivation); + CHECK_AND_ASSERT_MES(r, false, "at creation outs: failed to generate_key_derivation(" << dst_entr.addr.m_view_public_key << ", " << (dst_entr.is_subaddress && need_additional_txkeys ? additional_txkey.sec : tx_key) << ")"); } if (need_additional_txkeys) { additional_tx_public_keys.push_back(additional_txkey.pub); - additional_tx_keys.push_back(additional_txkey.sec); } if (tx.version > 1) @@ -393,10 +409,11 @@ namespace cryptonote output_index++; summary_outs_money += dst_entr.amount; } + CHECK_AND_ASSERT_MES(additional_tx_public_keys.size() == additional_tx_keys.size(), false, "Internal error creating additional public keys"); remove_field_from_tx_extra(tx.extra, typeid(tx_extra_additional_pub_keys)); - LOG_PRINT_L2("tx pubkey: " << txkey.pub); + LOG_PRINT_L2("tx pubkey: " << txkey_pub); if (need_additional_txkeys) { LOG_PRINT_L2("additional tx pubkeys: "); @@ -492,6 +509,7 @@ 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; @@ -504,6 +522,10 @@ namespace cryptonote inSk.push_back(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) { @@ -553,9 +575,9 @@ namespace cryptonote get_transaction_prefix_hash(tx, tx_prefix_hash); 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, index, outSk, bulletproof); + tx.rct_signatures = rct::genRctSimple(rct::hash2rct(tx_prefix_hash), inSk, destinations, inamounts, outamounts, amount_in - amount_out, mixRing, amount_keys, msout ? &kLRki : NULL, msout, index, outSk, bulletproof); else - tx.rct_signatures = rct::genRct(rct::hash2rct(tx_prefix_hash), inSk, destinations, outamounts, mixRing, amount_keys, sources[0].real_output, outSk, bulletproof); // same index assumption + tx.rct_signatures = rct::genRct(rct::hash2rct(tx_prefix_hash), inSk, destinations, outamounts, mixRing, amount_keys, msout ? &kLRki[0] : NULL, msout, sources[0].real_output, outSk, bulletproof); // same index assumption CHECK_AND_ASSERT_MES(tx.vout.size() == outSk.size(), false, "outSk size does not match vout"); @@ -567,13 +589,34 @@ 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, const std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, 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, bool bulletproof, rct::multisig_out *msout) + { + keypair txkey = keypair::generate(); + tx_key = txkey.sec; + + // figure out if we need to make additional tx pubkeys + size_t num_stdaddresses = 0; + size_t num_subaddresses = 0; + account_public_address single_dest_subaddress; + classify_addresses(destinations, change_addr, num_stdaddresses, num_subaddresses, single_dest_subaddress); + bool need_additional_txkeys = num_subaddresses > 0 && (num_stdaddresses > 0 || num_subaddresses > 1); + if (need_additional_txkeys) + { + additional_tx_keys.clear(); + for (const auto &d: destinations) + additional_tx_keys.push_back(keypair::generate().sec); + } + + return construct_tx_with_tx_key(sender_account_keys, subaddresses, sources, destinations, change_addr, extra, tx, unlock_time, tx_key, additional_tx_keys, rct, bulletproof, msout); + } + //--------------------------------------------------------------- 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, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time) { std::unordered_map<crypto::public_key, cryptonote::subaddress_index> subaddresses; subaddresses[sender_account_keys.m_account_address.m_spend_public_key] = {0,0}; crypto::secret_key tx_key; std::vector<crypto::secret_key> additional_tx_keys; - return construct_tx_and_get_tx_key(sender_account_keys, subaddresses, sources, destinations, change_addr, extra, tx, unlock_time, tx_key, additional_tx_keys); + return construct_tx_and_get_tx_key(sender_account_keys, subaddresses, sources, destinations, change_addr, extra, tx, unlock_time, tx_key, additional_tx_keys, false, false, NULL); } //--------------------------------------------------------------- bool generate_genesis_block( diff --git a/src/cryptonote_core/cryptonote_tx_utils.h b/src/cryptonote_core/cryptonote_tx_utils.h index d72f5d13b..5947522e2 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.h +++ b/src/cryptonote_core/cryptonote_tx_utils.h @@ -51,6 +51,7 @@ namespace cryptonote uint64_t amount; //money bool rct; //true if the output is rct rct::key mask; //ringct amount mask + rct::multisig_kLRki multisig_kLRki; //multisig info void push_output(uint64_t idx, const crypto::public_key &k, uint64_t amount) { outputs.push_back(std::make_pair(idx, rct::ctkey({rct::pk2rct(k), rct::zeroCommit(amount)}))); } @@ -63,6 +64,7 @@ namespace cryptonote FIELD(amount) FIELD(rct) FIELD(mask) + FIELD(multisig_kLRki) if (real_output >= outputs.size()) return false; @@ -87,8 +89,9 @@ namespace cryptonote //--------------------------------------------------------------- crypto::public_key get_destination_view_key_pub(const std::vector<tx_destination_entry> &destinations, const account_keys &sender_keys); - bool construct_tx(const account_keys& sender_account_keys, std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time); - bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, 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, bool bulletproof = false); + 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, 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, const std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, 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, bool bulletproof = false, rct::multisig_out *msout = NULL); + 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, const std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, 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, bool bulletproof = false, rct::multisig_out *msout = NULL); bool generate_genesis_block( block& bl @@ -98,7 +101,7 @@ namespace cryptonote } -BOOST_CLASS_VERSION(cryptonote::tx_source_entry, 0) +BOOST_CLASS_VERSION(cryptonote::tx_source_entry, 1) BOOST_CLASS_VERSION(cryptonote::tx_destination_entry, 1) namespace boost @@ -115,6 +118,10 @@ namespace boost a & x.amount; a & x.rct; a & x.mask; + if (ver < 1) + return; + a & x.multisig_kLRki; + a & x.real_out_additional_tx_keys; } template <class Archive> diff --git a/src/cryptonote_protocol/block_queue.cpp b/src/cryptonote_protocol/block_queue.cpp index bfff35456..3844d3751 100644 --- a/src/cryptonote_protocol/block_queue.cpp +++ b/src/cryptonote_protocol/block_queue.cpp @@ -62,6 +62,7 @@ void block_queue::add_blocks(uint64_t height, std::list<cryptonote::block_comple void block_queue::add_blocks(uint64_t height, uint64_t nblocks, const boost::uuids::uuid &connection_id, boost::posix_time::ptime time) { + CHECK_AND_ASSERT_THROW_MES(nblocks > 0, "Empty span"); boost::unique_lock<boost::recursive_mutex> lock(mutex); blocks.insert(span(height, nblocks, connection_id, time)); } @@ -384,7 +385,7 @@ float block_queue::get_speed(const boost::uuids::uuid &connection_id) const i->second = (i->second + span.rate) / 2; } float conn_rate = -1, best_rate = 0; - for (auto i: speeds) + for (const auto &i: speeds) { if (i.first == connection_id) conn_rate = i.second; diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.inl b/src/cryptonote_protocol/cryptonote_protocol_handler.inl index 389e8ba84..8aef31a5a 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.inl +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.inl @@ -266,13 +266,17 @@ namespace cryptonote return true; // from v6, if the peer advertises a top block version, reject if it's not what it should be (will only work if no voting) - const uint8_t version = m_core.get_ideal_hard_fork_version(hshd.current_height - 1); - if (version >= 6 && version != hshd.top_version) + if (hshd.current_height > 0) { - if (version < hshd.top_version) - MCLOG_RED(el::Level::Warning, "global", context << " peer claims higher version that we think - we may be forked from the network and a software upgrade may be needed"); - LOG_DEBUG_CC(context, "Ignoring due to wrong top version for block " << (hshd.current_height - 1) << ": " << (unsigned)hshd.top_version << ", expected " << (unsigned)version); - return false; + const uint8_t version = m_core.get_ideal_hard_fork_version(hshd.current_height - 1); + if (version >= 6 && version != hshd.top_version) + { + if (version < hshd.top_version) + MCLOG_RED(el::Level::Warning, "global", context << " peer claims higher version that we think (" << + (unsigned)hshd.top_version << " for " << (hshd.current_height - 1) << "instead of " << (unsigned)version << + ") - we may be forked from the network and a software upgrade may be needed"); + return false; + } } context.m_remote_blockchain_height = hshd.current_height; @@ -999,6 +1003,11 @@ skip: MDEBUG(context << " next span in the queue has blocks " << start_height << "-" << (start_height + blocks.size() - 1) << ", we need " << previous_height); + if (blocks.empty()) + { + MERROR("Next span has no blocks"); + break; + } block new_block; if (!parse_and_validate_block_from_blob(blocks.front().block, new_block)) @@ -1494,6 +1503,7 @@ skip: NOTIFY_REQUEST_CHAIN::request r = boost::value_initialized<NOTIFY_REQUEST_CHAIN::request>(); m_core.get_short_chain_history(r.block_ids); + CHECK_AND_ASSERT_MES(!r.block_ids.empty(), false, "Short chain history is empty"); if (!start_from_current_chain) { @@ -1581,6 +1591,12 @@ skip: drop_connection(context, true, false); return 1; } + if (arg.total_height < arg.m_block_ids.size() || arg.start_height > arg.total_height - arg.m_block_ids.size()) + { + LOG_ERROR_CCONTEXT("sent invalid start/nblocks/height, dropping connection"); + drop_connection(context, true, false); + return 1; + } context.m_remote_blockchain_height = arg.total_height; context.m_last_response_height = arg.start_height + arg.m_block_ids.size()-1; diff --git a/src/daemon/command_parser_executor.cpp b/src/daemon/command_parser_executor.cpp index 5307b2472..8970f9407 100644 --- a/src/daemon/command_parser_executor.cpp +++ b/src/daemon/command_parser_executor.cpp @@ -173,7 +173,7 @@ bool t_command_parser_executor::print_block(const std::vector<std::string>& args uint64_t height = boost::lexical_cast<uint64_t>(arg); return m_executor.print_block_by_height(height); } - catch (boost::bad_lexical_cast&) + catch (const boost::bad_lexical_cast&) { crypto::hash block_hash; if (parse_hash256(arg, block_hash)) @@ -420,7 +420,7 @@ bool t_command_parser_executor::out_peers(const std::vector<std::string>& args) limit = std::stoi(args[0]); } - catch(std::exception& ex) { + catch(const std::exception& ex) { _erro("stoi exception"); return false; } @@ -450,7 +450,7 @@ bool t_command_parser_executor::hard_fork_info(const std::vector<std::string>& a try { version = std::stoi(args[0]); } - catch(std::exception& ex) { + catch(const std::exception& ex) { return false; } if (version <= 0 || version > 255) diff --git a/src/daemon/core.h b/src/daemon/core.h index 97f1cb8c1..bf4f9bb3d 100644 --- a/src/daemon/core.h +++ b/src/daemon/core.h @@ -31,7 +31,6 @@ #include "cryptonote_core/cryptonote_core.h" #include "cryptonote_protocol/cryptonote_protocol_handler.h" #include "misc_log_ex.h" -#include "daemon/command_line_args.h" #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "daemon" diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 3bc6ea392..55d868a3c 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -43,8 +43,9 @@ #include "daemon/protocol.h" #include "daemon/rpc.h" #include "daemon/command_server.h" +#include "daemon/command_server.h" +#include "daemon/command_line_args.h" #include "version.h" -#include "syncobj.h" using namespace epee; diff --git a/src/daemon/executor.cpp b/src/daemon/executor.cpp index 6130bfa28..3379eeca4 100644 --- a/src/daemon/executor.cpp +++ b/src/daemon/executor.cpp @@ -30,7 +30,6 @@ #include "daemon/executor.h" -#include "common/command_line.h" #include "cryptonote_config.h" #include "version.h" diff --git a/src/daemon/protocol.h b/src/daemon/protocol.h index fc5edbcaa..01de535d5 100644 --- a/src/daemon/protocol.h +++ b/src/daemon/protocol.h @@ -33,8 +33,6 @@ #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "daemon" -#include "common/scoped_message_writer.h" - namespace daemonize { diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index aeb2ef55f..2e2411708 100644 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -1611,7 +1611,7 @@ bool t_rpc_command_executor::alt_chain_info() } tools::msg_writer() << boost::lexical_cast<std::string>(res.chains.size()) << " alternate chains found:"; - for (const auto chain: res.chains) + for (const auto &chain: res.chains) { uint64_t start_height = (chain.height - chain.length + 1); tools::msg_writer() << chain.length << " blocks long, from height " << start_height << " (" << (ires.height - start_height - 1) diff --git a/src/daemonizer/windows_service.cpp b/src/daemonizer/windows_service.cpp index d540f5bf8..9b8e46615 100644 --- a/src/daemonizer/windows_service.cpp +++ b/src/daemonizer/windows_service.cpp @@ -26,6 +26,9 @@ // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#include <boost/chrono/chrono.hpp> +#include <boost/thread/thread.hpp> + #undef UNICODE #undef _UNICODE diff --git a/src/debug_utilities/cn_deserialize.cpp b/src/debug_utilities/cn_deserialize.cpp index a59c04dc6..04c0935c8 100644 --- a/src/debug_utilities/cn_deserialize.cpp +++ b/src/debug_utilities/cn_deserialize.cpp @@ -154,7 +154,11 @@ int main(int argc, char* argv[]) std::cout << "Parsed transaction:" << std::endl; std::cout << cryptonote::obj_to_json_str(tx) << std::endl; - if (cryptonote::parse_tx_extra(tx.extra, fields)) + bool parsed = cryptonote::parse_tx_extra(tx.extra, fields); + if (!parsed) + std::cout << "Failed to parse tx_extra" << std::endl; + + if (!fields.empty()) { std::cout << "tx_extra has " << fields.size() << " field(s)" << std::endl; for (size_t n = 0; n < fields.size(); ++n) @@ -171,7 +175,7 @@ int main(int argc, char* argv[]) } else { - std::cout << "Failed to parse tx_extra" << std::endl; + std::cout << "No fields were found in tx_extra" << std::endl; } } else diff --git a/src/debug_utilities/object_sizes.cpp b/src/debug_utilities/object_sizes.cpp index 82d8a4add..967742229 100644 --- a/src/debug_utilities/object_sizes.cpp +++ b/src/debug_utilities/object_sizes.cpp @@ -51,7 +51,7 @@ class size_logger public: ~size_logger() { - for (const auto i: types) + for (const auto &i: types) std::cout << std::to_string(i.first) << "\t" << i.second << std::endl; } void add(const char *type, size_t size) { types.insert(std::make_pair(size, type)); } diff --git a/src/gen_multisig/CMakeLists.txt b/src/gen_multisig/CMakeLists.txt new file mode 100644 index 000000000..ff3c46862 --- /dev/null +++ b/src/gen_multisig/CMakeLists.txt @@ -0,0 +1,54 @@ +# Copyright (c) 2017, The Monero Project +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, are +# permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of +# conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this list +# of conditions and the following disclaimer in the documentation and/or other +# materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be +# used to endorse or promote products derived from this software without specific +# prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +set(gen_multisig_sources + gen_multisig.cpp) + +monero_add_executable(gen_multisig + ${gen_multisig_sources}) +target_link_libraries(gen_multisig + PRIVATE + wallet + cryptonote_core + cncrypto + common + epee + ${EPEE_READLINE} + ${Boost_CHRONO_LIBRARY} + ${Boost_PROGRAM_OPTIONS_LIBRARY} + ${Boost_FILESYSTEM_LIBRARY} + ${Boost_THREAD_LIBRARY} + ${Readline_LIBRARY} + ${CMAKE_THREAD_LIBS_INIT} + ${EXTRA_LIBRARIES}) +add_dependencies(gen_multisig + version) +set_property(TARGET gen_multisig + PROPERTY + OUTPUT_NAME "monero-gen-trusted-multisig") +install(TARGETS gen_multisig DESTINATION bin) diff --git a/src/gen_multisig/gen_multisig.cpp b/src/gen_multisig/gen_multisig.cpp new file mode 100644 index 000000000..a9bc7b8fd --- /dev/null +++ b/src/gen_multisig/gen_multisig.cpp @@ -0,0 +1,241 @@ +// Copyright (c) 2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +/*! + * \file gen_multisig.cpp + * + * \brief Generates a set of multisig wallets + */ +#include <iostream> +#include <sstream> +#include <boost/program_options.hpp> +#include <boost/algorithm/string.hpp> +#include <boost/format.hpp> +#include "include_base_utils.h" +#include "crypto/crypto.h" // for crypto::secret_key definition +#include "common/i18n.h" +#include "common/command_line.h" +#include "common/util.h" +#include "common/scoped_message_writer.h" +#include "wallet/wallet_args.h" +#include "wallet/wallet2.h" + +using namespace std; +using namespace epee; +using namespace cryptonote; +using boost::lexical_cast; +namespace po = boost::program_options; + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "wallet.gen_multisig" + +namespace genms +{ + const char* tr(const char* str) + { + return i18n_translate(str, "tools::gen_multisig"); + } + +} + +namespace +{ + const command_line::arg_descriptor<std::string> arg_filename_base = {"filename-base", genms::tr("Base filename (-1, -2, etc suffixes will be appended as needed)"), ""}; + const command_line::arg_descriptor<std::string> arg_scheme = {"scheme", genms::tr("Give threshold and participants at once as M/N"), ""}; + const command_line::arg_descriptor<uint32_t> arg_participants = {"participants", genms::tr("How many participants wil share parts of the multisig wallet"), 0}; + const command_line::arg_descriptor<uint32_t> arg_threshold = {"threshold", genms::tr("How many signers are required to sign a valid transaction"), 0}; + const command_line::arg_descriptor<bool, false> arg_testnet = {"testnet", genms::tr("Create testnet multisig wallets"), false}; + + const command_line::arg_descriptor< std::vector<std::string> > arg_command = {"command", ""}; +} + +static bool generate_multisig(uint32_t threshold, uint32_t total, const std::string &basename, bool testnet) +{ + tools::msg_writer() << (boost::format(genms::tr("Generating %u %u/%u multisig wallets")) % total % threshold % total).str(); + + const auto pwd_container = tools::password_container::prompt(true, "Enter password for new multisig wallets"); + + try + { + // create M wallets first + std::vector<boost::shared_ptr<tools::wallet2>> wallets(total); + for (size_t n = 0; n < total; ++n) + { + std::string name = basename + "-" + std::to_string(n + 1); + wallets[n].reset(new tools::wallet2(testnet)); + wallets[n]->init(""); + wallets[n]->generate(name, pwd_container->password(), rct::rct2sk(rct::skGen()), false, false); + } + + // gather the keys + std::vector<crypto::secret_key> sk(total); + std::vector<crypto::public_key> pk(total); + for (size_t n = 0; n < total; ++n) + { + if (!tools::wallet2::verify_multisig_info(wallets[n]->get_multisig_info(), sk[n], pk[n])) + { + tools::fail_msg_writer() << tr("Failed to verify multisig info"); + return false; + } + } + + // make the wallets multisig + std::vector<std::string> extra_info(total); + std::stringstream ss; + for (size_t n = 0; n < total; ++n) + { + std::string name = basename + "-" + std::to_string(n + 1); + std::vector<crypto::secret_key> skn; + std::vector<crypto::public_key> pkn; + for (size_t k = 0; k < total; ++k) + { + if (k != n) + { + skn.push_back(sk[k]); + pkn.push_back(pk[k]); + } + } + extra_info[n] = wallets[n]->make_multisig(pwd_container->password(), skn, pkn, threshold); + ss << " " << name << std::endl; + } + + // finalize step if needed + if (!extra_info[0].empty()) + { + std::unordered_set<crypto::public_key> pkeys; + std::vector<crypto::public_key> signers(total); + for (size_t n = 0; n < total; ++n) + { + if (!tools::wallet2::verify_extra_multisig_info(extra_info[n], pkeys, signers[n])) + { + tools::fail_msg_writer() << genms::tr("Error verifying multisig extra info"); + return false; + } + } + for (size_t n = 0; n < total; ++n) + { + if (!wallets[n]->finalize_multisig(pwd_container->password(), pkeys, signers)) + { + tools::fail_msg_writer() << genms::tr("Error finalizing multisig"); + return false; + } + } + } + + std::string address = wallets[0]->get_account().get_public_address_str(wallets[0]->testnet()); + tools::success_msg_writer() << genms::tr("Generated multisig wallets for address ") << address << std::endl << ss.str(); + } + catch (const std::exception &e) + { + tools::fail_msg_writer() << genms::tr("Error creating multisig wallets: ") << e.what(); + return false; + } + + return true; +} + +int main(int argc, char* argv[]) +{ + po::options_description desc_params(wallet_args::tr("Wallet options")); + command_line::add_arg(desc_params, arg_filename_base); + command_line::add_arg(desc_params, arg_scheme); + command_line::add_arg(desc_params, arg_threshold); + command_line::add_arg(desc_params, arg_participants); + command_line::add_arg(desc_params, arg_testnet); + + const auto vm = wallet_args::main( + argc, argv, + "monero-gen-multisig [--testnet] [--filename-base=<filename>] [--scheme=M/N] [--threshold=M] [--participants=N]", + genms::tr("This program generates a set of multisig wallets - use this simpler scheme only if all the participants trust each other"), + desc_params, + boost::program_options::positional_options_description(), + [](const std::string &s, bool emphasis){ tools::scoped_message_writer(emphasis ? epee::console_color_white : epee::console_color_default, true) << s; }, + "monero-gen-multisig.log" + ); + if (!vm) + return 1; + + bool testnet; + uint32_t threshold = 0, total = 0; + std::string basename; + + testnet = command_line::get_arg(*vm, arg_testnet); + if (command_line::has_arg(*vm, arg_scheme)) + { + if (sscanf(command_line::get_arg(*vm, arg_scheme).c_str(), "%u/%u", &threshold, &total) != 2) + { + tools::fail_msg_writer() << genms::tr("Error: expected N/M, but got: ") << command_line::get_arg(*vm, arg_scheme); + return 1; + } + } + if (!(*vm)["threshold"].defaulted()) + { + if (threshold) + { + tools::fail_msg_writer() << genms::tr("Error: either --scheme or both of --threshold and --participants may be given"); + return 1; + } + threshold = command_line::get_arg(*vm, arg_threshold); + } + if (!(*vm)["participants"].defaulted()) + { + if (total) + { + tools::fail_msg_writer() << genms::tr("Error: either --scheme or both of --threshold and --participants may be given"); + return 1; + } + total = command_line::get_arg(*vm, arg_participants); + } + if (threshold <= 1 || threshold > total) + { + tools::fail_msg_writer() << (boost::format(genms::tr("Error: expected N > 1 and N <= M, but got N==%u and M==%d")) % threshold % total).str(); + return 1; + } + if (!(*vm)["filename-base"].defaulted() && !command_line::get_arg(*vm, arg_filename_base).empty()) + { + basename = command_line::get_arg(*vm, arg_filename_base); + } + else + { + tools::fail_msg_writer() << genms::tr("Error: --filename-base is required"); + return 1; + } + + if (threshold != total-1 && threshold != total) + { + tools::fail_msg_writer() << genms::tr("Error: unsupported scheme: only N/N and N-1/N are supported"); + return 1; + } + if (!generate_multisig(threshold, total, basename, testnet)) + return 1; + + return 0; + //CATCH_ENTRY_L0("main", 1); +} diff --git a/src/mnemonics/electrum-words.cpp b/src/mnemonics/electrum-words.cpp index 1b14905f6..ba67952aa 100644 --- a/src/mnemonics/electrum-words.cpp +++ b/src/mnemonics/electrum-words.cpp @@ -205,6 +205,8 @@ namespace */ bool checksum_test(std::vector<std::string> seed, uint32_t unique_prefix_length) { + if (seed.empty()) + return false; // The last word is the checksum. std::string last_word = seed.back(); seed.pop_back(); diff --git a/src/multisig/CMakeLists.txt b/src/multisig/CMakeLists.txt new file mode 100644 index 000000000..432865ad3 --- /dev/null +++ b/src/multisig/CMakeLists.txt @@ -0,0 +1,52 @@ +# Copyright (c) 2017, The Monero Project +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, are +# permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of +# conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this list +# of conditions and the following disclaimer in the documentation and/or other +# materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be +# used to endorse or promote products derived from this software without specific +# prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +set(multisig_sources + multisig.cpp) + +set(multisig_headers) + +set(multisig_private_headers + multisig.h) + +monero_private_headers(multisig + ${multisig_private_headers}) + +monero_add_library(multisig + ${multisig_sources} + ${multisig_headers} + ${multisig_private_headers}) + +target_link_libraries(multisig + PUBLIC + ringct + cryptonote_basic + common + cncrypto + PRIVATE + ${EXTRA_LIBRARIES}) diff --git a/src/multisig/multisig.cpp b/src/multisig/multisig.cpp new file mode 100644 index 000000000..39d0e1c4b --- /dev/null +++ b/src/multisig/multisig.cpp @@ -0,0 +1,141 @@ +// Copyright (c) 2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <unordered_set> +#include "include_base_utils.h" +#include "crypto/crypto.h" +#include "ringct/rctOps.h" +#include "cryptonote_basic/account.h" +#include "cryptonote_basic/cryptonote_format_utils.h" +#include "multisig.h" + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "multisig" + +using namespace std; + +static const rct::key multisig_salt = { {'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 } }; + +namespace cryptonote +{ + //----------------------------------------------------------------- + crypto::secret_key get_multisig_blinded_secret_key(const crypto::secret_key &key) + { + rct::keyV data; + data.push_back(rct::sk2rct(key)); + data.push_back(multisig_salt); + return rct::rct2sk(rct::hash_to_scalar(data)); + } + //----------------------------------------------------------------- + void generate_multisig_N_N(const account_keys &keys, const std::vector<crypto::public_key> &spend_keys, std::vector<crypto::secret_key> &multisig_keys, rct::key &spend_skey, rct::key &spend_pkey) + { + // the multisig spend public key is the sum of all spend public keys + multisig_keys.clear(); + const crypto::secret_key spend_secret_key = get_multisig_blinded_secret_key(keys.m_spend_secret_key); + CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(spend_secret_key, (crypto::public_key&)spend_pkey), "Failed to derive public key"); + for (const auto &k: spend_keys) + rct::addKeys(spend_pkey, spend_pkey, rct::pk2rct(k)); + multisig_keys.push_back(spend_secret_key); + spend_skey = rct::sk2rct(spend_secret_key); + } + //----------------------------------------------------------------- + void generate_multisig_N1_N(const account_keys &keys, const std::vector<crypto::public_key> &spend_keys, std::vector<crypto::secret_key> &multisig_keys, rct::key &spend_skey, rct::key &spend_pkey) + { + multisig_keys.clear(); + spend_pkey = rct::identity(); + spend_skey = rct::zero(); + + // create all our composite private keys + crypto::secret_key blinded_skey = get_multisig_blinded_secret_key(keys.m_spend_secret_key); + for (const auto &k: spend_keys) + { + rct::key sk = rct::scalarmultKey(rct::pk2rct(k), rct::sk2rct(blinded_skey)); + crypto::secret_key msk = get_multisig_blinded_secret_key(rct::rct2sk(sk)); + multisig_keys.push_back(msk); + sc_add(spend_skey.bytes, spend_skey.bytes, (const unsigned char*)msk.data); + } + } + //----------------------------------------------------------------- + crypto::secret_key generate_multisig_view_secret_key(const crypto::secret_key &skey, const std::vector<crypto::secret_key> &skeys) + { + rct::key view_skey = rct::sk2rct(get_multisig_blinded_secret_key(skey)); + for (const auto &k: skeys) + sc_add(view_skey.bytes, view_skey.bytes, rct::sk2rct(k).bytes); + return rct::rct2sk(view_skey); + } + //----------------------------------------------------------------- + crypto::public_key generate_multisig_N1_N_spend_public_key(const std::vector<crypto::public_key> &pkeys) + { + rct::key spend_public_key = rct::identity(); + for (const auto &pk: pkeys) + { + rct::addKeys(spend_public_key, spend_public_key, rct::pk2rct(pk)); + } + return rct::rct2pk(spend_public_key); + } + //----------------------------------------------------------------- + bool generate_multisig_key_image(const account_keys &keys, size_t multisig_key_index, const crypto::public_key& out_key, crypto::key_image& ki) + { + if (multisig_key_index >= keys.m_multisig_keys.size()) + return false; + crypto::generate_key_image(out_key, keys.m_multisig_keys[multisig_key_index], ki); + return true; + } + //----------------------------------------------------------------- + void generate_multisig_LR(const crypto::public_key pkey, const crypto::secret_key &k, crypto::public_key &L, crypto::public_key &R) + { + rct::scalarmultBase((rct::key&)L, rct::sk2rct(k)); + crypto::generate_key_image(pkey, k, (crypto::key_image&)R); + } + //----------------------------------------------------------------- + bool generate_multisig_composite_key_image(const account_keys &keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, const crypto::public_key& out_key, const crypto::public_key &tx_public_key, const std::vector<crypto::public_key>& additional_tx_public_keys, size_t real_output_index, const std::vector<crypto::key_image> &pkis, crypto::key_image &ki) + { + cryptonote::keypair in_ephemeral; + if (!cryptonote::generate_key_image_helper(keys, subaddresses, out_key, tx_public_key, additional_tx_public_keys, real_output_index, in_ephemeral, ki)) + return false; + std::unordered_set<crypto::key_image> used; + for (size_t m = 0; m < keys.m_multisig_keys.size(); ++m) + { + crypto::key_image pki; + bool r = cryptonote::generate_multisig_key_image(keys, m, out_key, pki); + if (!r) + return false; + used.insert(pki); + } + for (const auto &pki: pkis) + { + if (used.find(pki) == used.end()) + { + used.insert(pki); + rct::addKeys((rct::key&)ki, rct::ki2rct(ki), rct::ki2rct(pki)); + } + } + return true; + } + //----------------------------------------------------------------- +} diff --git a/src/multisig/multisig.h b/src/multisig/multisig.h new file mode 100644 index 000000000..f29b47987 --- /dev/null +++ b/src/multisig/multisig.h @@ -0,0 +1,49 @@ +// Copyright (c) 2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include <vector> +#include <unordered_map> +#include "crypto/crypto.h" +#include "cryptonote_basic/cryptonote_format_utils.h" +#include "ringct/rctTypes.h" + +namespace cryptonote +{ + struct account_keys; + + crypto::secret_key get_multisig_blinded_secret_key(const crypto::secret_key &key); + void generate_multisig_N_N(const account_keys &keys, const std::vector<crypto::public_key> &spend_keys, std::vector<crypto::secret_key> &multisig_keys, rct::key &spend_skey, rct::key &spend_pkey); + void generate_multisig_N1_N(const account_keys &keys, const std::vector<crypto::public_key> &spend_keys, std::vector<crypto::secret_key> &multisig_keys, rct::key &spend_skey, rct::key &spend_pkey); + crypto::secret_key generate_multisig_view_secret_key(const crypto::secret_key &skey, const std::vector<crypto::secret_key> &skeys); + crypto::public_key generate_multisig_N1_N_spend_public_key(const std::vector<crypto::public_key> &pkeys); + bool generate_multisig_key_image(const account_keys &keys, size_t multisig_key_index, const crypto::public_key& out_key, crypto::key_image& ki); + void generate_multisig_LR(const crypto::public_key pkey, const crypto::secret_key &k, crypto::public_key &L, crypto::public_key &R); + bool generate_multisig_composite_key_image(const account_keys &keys, const std::unordered_map<crypto::public_key, cryptonote::subaddress_index>& subaddresses, const crypto::public_key& out_key, const crypto::public_key &tx_public_key, const std::vector<crypto::public_key>& additional_tx_public_keys, size_t real_output_index, const std::vector<crypto::key_image> &pkis, crypto::key_image &ki); +} diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl index 55be7c2cb..269a9ba87 100644 --- a/src/p2p/net_node.inl +++ b/src/p2p/net_node.inl @@ -649,6 +649,10 @@ namespace nodetool template<class t_payload_net_handler> bool node_server<t_payload_net_handler>::send_stop_signal() { + MDEBUG("[node] sending stop signal"); + m_net_server.send_stop_signal(); + MDEBUG("[node] Stop signal sent"); + std::list<boost::uuids::uuid> connection_ids; m_net_server.get_config_object().foreach_connection([&](const p2p_connection_context& cntxt) { connection_ids.push_back(cntxt.m_connection_id); @@ -658,8 +662,7 @@ namespace nodetool m_net_server.get_config_object().close(connection_id); m_payload_handler.stop(); - m_net_server.send_stop_signal(); - MDEBUG("[node] Stop signal sent"); + return true; } //----------------------------------------------------------------------------------- @@ -1856,8 +1859,8 @@ namespace nodetool template<class t_payload_net_handler> bool node_server<t_payload_net_handler>::has_too_many_connections(const epee::net_utils::network_address &address) { - const uint8_t max_connections = 1; - uint8_t count = 0; + const size_t max_connections = 1; + size_t count = 0; m_net_server.get_config_object().foreach_connection([&](const p2p_connection_context& cntxt) { diff --git a/src/ringct/rctOps.cpp b/src/ringct/rctOps.cpp index a7311482c..cc46d0aa7 100644 --- a/src/ringct/rctOps.cpp +++ b/src/ringct/rctOps.cpp @@ -76,6 +76,7 @@ namespace rct { //Generates a vector of secret key //Mainly used in testing keyV skvGen(size_t rows ) { + CHECK_AND_ASSERT_THROW_MES(rows > 0, "0 keys requested"); keyV rv(rows); size_t i = 0; crypto::rand(rows * sizeof(key), (uint8_t*)&rv[0]); @@ -351,6 +352,7 @@ namespace rct { //This takes the outputs and commitments //and hashes them into a 32 byte sized key key cn_fast_hash(const ctkeyV &PC) { + if (PC.empty()) return rct::hash2rct(crypto::cn_fast_hash("", 0)); key rv; cn_fast_hash(rv, &PC[0], 64*PC.size()); return rv; @@ -367,6 +369,7 @@ namespace rct { //put them in the key vector and it concatenates them //and then hashes them key cn_fast_hash(const keyV &keys) { + if (keys.empty()) return rct::hash2rct(crypto::cn_fast_hash("", 0)); key rv; cn_fast_hash(rv, &keys[0], keys.size() * sizeof(keys[0])); //dp(rv); diff --git a/src/ringct/rctSigs.cpp b/src/ringct/rctSigs.cpp index 38b213e8b..24ab08778 100644 --- a/src/ringct/rctSigs.cpp +++ b/src/ringct/rctSigs.cpp @@ -122,7 +122,7 @@ 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 unsigned int index, size_t dsRows) { + 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) { mgSig rv; size_t cols = pk.size(); CHECK_AND_ASSERT_THROW_MES(cols >= 2, "Error! What is c if cols = 1!"); @@ -134,6 +134,8 @@ 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; @@ -148,13 +150,22 @@ namespace rct { toHash[0] = message; DP("here1"); for (i = 0; i < dsRows; i++) { - skpkGen(alpha[i], aG[i]); //need to save alphas for later.. - Hi = hashToPoint(pk[index][i]); - aHP[i] = scalarmultKey(Hi, alpha[i]); toHash[3 * i + 1] = pk[index][i]; - toHash[3 * i + 2] = aG[i]; - toHash[3 * i + 3] = aHP[i]; - rv.II[i] = scalarmultKey(Hi, xx[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 { + Hi = hashToPoint(pk[index][i]); + skpkGen(alpha[i], aG[i]); //need to save alphas for later.. + aHP[i] = scalarmultKey(Hi, alpha[i]); + toHash[3 * i + 2] = aG[i]; + toHash[3 * i + 3] = aHP[i]; + rv.II[i] = scalarmultKey(Hi, xx[i]); + } precomp(Ip[i].k, rv.II[i]); } size_t ndsRows = 3 * dsRows; //non Double Spendable Rows (see identity chains paper) @@ -198,7 +209,9 @@ namespace rct { } for (j = 0; j < rows; j++) { sc_mulsub(rv.ss[index][j].bytes, c.bytes, xx[j].bytes, alpha[j].bytes); - } + } + if (mscout) + *mscout = c; return rv; } @@ -393,7 +406,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, unsigned int index, key txnFeeKey) { + 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, key txnFeeKey) { mgSig mg; //setup vars size_t cols = pubs.size(); @@ -405,6 +418,7 @@ 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); @@ -437,7 +451,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.. } - return MLSAG_Gen(message, M, sk, index, rows); + return MLSAG_Gen(message, M, sk, kLRki, mscout, index, rows); } @@ -448,12 +462,13 @@ 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, unsigned int 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) { mgSig mg; //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; @@ -464,7 +479,7 @@ namespace rct { sk[0] = copy(inSk.dest); sc_sub(sk[1].bytes, inSk.mask.bytes, a.bytes); } - return MLSAG_Gen(message, M, sk, index, rows); + return MLSAG_Gen(message, M, sk, kLRki, mscout, index, rows); } @@ -598,13 +613,14 @@ 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, unsigned int index, ctkeyV &outSk, bool bulletproof) { + 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, bool bulletproof) { 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"); rctSig rv; rv.type = bulletproof ? RCTTypeFullBulletproof : RCTTypeFull; @@ -653,21 +669,23 @@ namespace rct { key txnFeeKey = scalarmultH(d2h(rv.txnFee)); rv.mixRing = mixRing; - rv.p.MGs.push_back(proveRctMG(get_pre_mlsag_hash(rv), rv.mixRing, inSk, outSk, rv.outPk, index, txnFeeKey)); + if (msout) + msout->c.resize(1); + rv.p.MGs.push_back(proveRctMG(get_pre_mlsag_hash(rv), rv.mixRing, inSk, outSk, rv.outPk, kLRki, msout ? &msout->c[0] : NULL, index, txnFeeKey)); 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 int mixin) { + 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) { unsigned int index; ctkeyM mixRing; ctkeyV outSk; tie(mixRing, index) = populateFromBlockchain(inPk, mixin); - return genRct(message, inSk, destinations, amounts, mixRing, amount_keys, index, outSk, false); + return genRct(message, inSk, destinations, amounts, mixRing, amount_keys, kLRki, msout, index, outSk, false); } //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<unsigned int> & index, ctkeyV &outSk, bool bulletproof) { + 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, bool bulletproof) { CHECK_AND_ASSERT_THROW_MES(inamounts.size() > 0, "Empty inamounts"); CHECK_AND_ASSERT_THROW_MES(inamounts.size() == inSk.size(), "Different number of inamounts/inSk"); CHECK_AND_ASSERT_THROW_MES(outamounts.size() == destinations.size(), "Different number of amounts/destinations"); @@ -677,6 +695,10 @@ 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; rv.type = bulletproof ? RCTTypeSimpleBulletproof : RCTTypeSimple; @@ -736,13 +758,15 @@ namespace rct { DP(rv.pseudoOuts[i]); key full_message = get_pre_mlsag_hash(rv); + if (msout) + msout->c.resize(inamounts.size()); for (i = 0 ; i < inamounts.size(); i++) { - rv.p.MGs[i] = proveRctMGSimple(full_message, rv.mixRing[i], inSk[i], a[i], rv.pseudoOuts[i], index[i]); + rv.p.MGs[i] = proveRctMGSimple(full_message, rv.mixRing[i], inSk[i], a[i], rv.pseudoOuts[i], kLRki ? &(*kLRki)[i]: NULL, msout ? &msout->c[i] : NULL, index[i]); } 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, xmr_amount txnFee, unsigned int mixin) { + 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) { std::vector<unsigned int> index; index.resize(inPk.size()); ctkeyM mixRing; @@ -752,7 +776,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, index, outSk, false); + return genRctSimple(message, inSk, destinations, inamounts, outamounts, txnFee, mixRing, amount_keys, kLRki, msout, index, outSk, false); } //RingCT protocol @@ -822,8 +846,14 @@ namespace rct { return true; } - catch(...) + catch (const std::exception &e) { + LOG_PRINT_L1("Error in verRct: " << e.what()); + return false; + } + catch (...) + { + LOG_PRINT_L1("Error in verRct, but not an actual exception"); return false; } } @@ -920,7 +950,16 @@ namespace rct { return true; } // we can get deep throws from ge_frombytes_vartime if input isn't valid - catch (...) { return false; } + catch (const std::exception &e) + { + LOG_PRINT_L1("Error in verRct: " << e.what()); + return false; + } + catch (...) + { + LOG_PRINT_L1("Error in verRct, but not an actual exception"); + return false; + } } //RingCT protocol @@ -988,4 +1027,27 @@ namespace rct { key mask; return decodeRctSimple(rv, sk, i, mask); } + + bool signMultisig(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 == RCTTypeFullBulletproof || rv.type == RCTTypeSimpleBulletproof, + 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.MGs.size(), false, "Mismatched k/MGs size"); + CHECK_AND_ASSERT_MES(k.size() == msout.c.size(), false, "Mismatched k/msout.c size"); + if (rv.type == RCTTypeFull || rv.type == RCTTypeFullBulletproof) + { + 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"); + } + + 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; + } } diff --git a/src/ringct/rctSigs.h b/src/ringct/rctSigs.h index 46c9cb2df..e83083a98 100644 --- a/src/ringct/rctSigs.h +++ b/src/ringct/rctSigs.h @@ -73,7 +73,7 @@ namespace rct { // the signer knows a secret key for each row in that column // Ver verifies that the MG sig was created correctly keyV keyImageV(const keyV &xx); - mgSig MLSAG_Gen(const key &message, const keyM & pk, const keyV & xx, const unsigned int index, size_t dsRows); + 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); bool MLSAG_Ver(const key &message, const keyM &pk, const mgSig &sig, size_t dsRows); //mgSig MLSAG_Gen_Old(const keyM & pk, const keyV & xx, const int index); @@ -95,8 +95,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, unsigned int index, key txnFee, const key &message); - mgSig proveRctMGSimple(const key & message, const ctkeyV & pubs, const ctkey & inSk, const key &a , const key &Cout, unsigned int index); + mgSig proveRctMG(const ctkeyM & pubs, const ctkeyV & inSk, const keyV &outMasks, const ctkeyV & outPk, const multisig_kLRki *kLRki, key *mscout, unsigned int index, key txnFee, const key &message); + 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); bool verRctMG(const mgSig &mg, const ctkeyM & pubs, const ctkeyV & outPk, key txnFee, const key &message); bool verRctMGSimple(const key &message, const mgSig &mg, const ctkeyV & pubs, const key & C); @@ -118,10 +118,10 @@ namespace rct { //decodeRct: (c.f. http://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, unsigned int index, ctkeyV &outSk, bool bulletproof); - 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); - 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); - 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, bool bulletproof); + 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, bool bulletproof); + 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); + 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); + 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, bool bulletproof); bool verRct(const rctSig & rv, bool semantics); static inline bool verRct(const rctSig & rv) { return verRct(rv, true) && verRct(rv, false); } bool verRctSimple(const rctSig & rv, bool semantics); @@ -130,6 +130,8 @@ namespace rct { xmr_amount decodeRct(const rctSig & rv, const key & sk, unsigned int i); xmr_amount decodeRctSimple(const rctSig & rv, const key & sk, unsigned int i, key & mask); xmr_amount decodeRctSimple(const rctSig & rv, const key & sk, unsigned int i); + + 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/ringct/rctTypes.h b/src/ringct/rctTypes.h index 50dfdb432..5ea2dcc7c 100644 --- a/src/ringct/rctTypes.h +++ b/src/ringct/rctTypes.h @@ -99,6 +99,22 @@ namespace rct { typedef std::vector<ctkey> ctkeyV; typedef std::vector<ctkeyV> ctkeyM; + //used for multisig data + struct multisig_kLRki { + key k; + key L; + key R; + key ki; + }; + + struct multisig_out { + std::vector<key> c; // for all inputs + + BEGIN_SERIALIZE_OBJECT() + FIELD(c) + END_SERIALIZE() + }; + //data for passing the amount to the receiver secretly // If the pedersen commitment to an amount is C = aG + bH, // "mask" contains a 32 byte key a @@ -501,9 +517,15 @@ inline std::ostream &operator <<(std::ostream &o, const rct::key &v) { } +namespace std +{ + template<> struct hash<rct::key> { std::size_t operator()(const rct::key &k) const { return reinterpret_cast<const std::size_t&>(k); } }; +} + BLOB_SERIALIZER(rct::key); BLOB_SERIALIZER(rct::key64); BLOB_SERIALIZER(rct::ctkey); +BLOB_SERIALIZER(rct::multisig_kLRki); BLOB_SERIALIZER(rct::boroSig); VARIANT_TAG(debug_archive, rct::key, "rct::key"); @@ -519,6 +541,8 @@ VARIANT_TAG(debug_archive, rct::rangeSig, "rct::rangeSig"); VARIANT_TAG(debug_archive, rct::boroSig, "rct::boroSig"); VARIANT_TAG(debug_archive, rct::rctSig, "rct::rctSig"); VARIANT_TAG(debug_archive, rct::Bulletproof, "rct::bulletproof"); +VARIANT_TAG(debug_archive, rct::multisig_kLRki, "rct::multisig_kLRki"); +VARIANT_TAG(debug_archive, rct::multisig_out, "rct::multisig_out"); VARIANT_TAG(binary_archive, rct::key, 0x90); VARIANT_TAG(binary_archive, rct::key64, 0x91); @@ -533,6 +557,8 @@ VARIANT_TAG(binary_archive, rct::rangeSig, 0x99); VARIANT_TAG(binary_archive, rct::boroSig, 0x9a); VARIANT_TAG(binary_archive, rct::rctSig, 0x9b); VARIANT_TAG(binary_archive, rct::Bulletproof, 0x9c); +VARIANT_TAG(binary_archive, rct::multisig_kLRki, 0x9d); +VARIANT_TAG(binary_archive, rct::multisig_out, 0x9e); VARIANT_TAG(json_archive, rct::key, "rct_key"); VARIANT_TAG(json_archive, rct::key64, "rct_key64"); @@ -547,5 +573,7 @@ VARIANT_TAG(json_archive, rct::rangeSig, "rct_rangeSig"); VARIANT_TAG(json_archive, rct::boroSig, "rct_boroSig"); VARIANT_TAG(json_archive, rct::rctSig, "rct_rctSig"); VARIANT_TAG(json_archive, rct::Bulletproof, "rct_bulletproof"); +VARIANT_TAG(json_archive, rct::multisig_kLRki, "rct_multisig_kLR"); +VARIANT_TAG(json_archive, rct::multisig_out, "rct_multisig_out"); #endif /* RCTTYPES_H */ diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 803588cbd..a6109cb89 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -492,6 +492,11 @@ namespace cryptonote { if (std::find(missed_txs.begin(), missed_txs.end(), h) == missed_txs.end()) { + if (txs.empty()) + { + res.status = "Failed: internal error - txs is empty"; + return true; + } // core returns the ones it finds in the right order if (get_transaction_hash(txs.front()) != h) { @@ -1150,7 +1155,7 @@ namespace cryptonote error_resp.message = "Internal error: can't get block by hash. Hash = " + req.hash + '.'; return false; } - if (blk.miner_tx.vin.front().type() != typeid(txin_gen)) + if (blk.miner_tx.vin.size() != 1 || blk.miner_tx.vin.front().type() != typeid(txin_gen)) { error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; error_resp.message = "Internal error: coinbase transaction in the block has the wrong type"; @@ -1188,7 +1193,7 @@ namespace cryptonote error_resp.message = "Internal error: can't get block by height. Height = " + boost::lexical_cast<std::string>(h) + ". Hash = " + epee::string_tools::pod_to_hex(block_hash) + '.'; return false; } - if (blk.miner_tx.vin.front().type() != typeid(txin_gen)) + if (blk.miner_tx.vin.size() != 1 || blk.miner_tx.vin.front().type() != typeid(txin_gen)) { error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; error_resp.message = "Internal error: coinbase transaction in the block has the wrong type"; @@ -1274,7 +1279,7 @@ namespace cryptonote error_resp.message = "Internal error: can't get block by hash. Hash = " + req.hash + '.'; return false; } - if (blk.miner_tx.vin.front().type() != typeid(txin_gen)) + if (blk.miner_tx.vin.size() != 1 || blk.miner_tx.vin.front().type() != typeid(txin_gen)) { error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; error_resp.message = "Internal error: coinbase transaction in the block has the wrong type"; @@ -1436,19 +1441,25 @@ namespace cryptonote { failed = true; } - crypto::hash txid = *reinterpret_cast<const crypto::hash*>(txid_data.data()); - txids.push_back(txid); + else + { + crypto::hash txid = *reinterpret_cast<const crypto::hash*>(txid_data.data()); + txids.push_back(txid); + } } } if (!m_core.get_blockchain_storage().flush_txes_from_pool(txids)) { - res.status = "Failed to remove one more tx"; + res.status = "Failed to remove one or more tx(es)"; return false; } if (failed) { - res.status = "Failed to parse txid"; + if (txids.empty()) + res.status = "Failed to parse txid"; + else + res.status = "Failed to parse some of the txids"; return false; } @@ -1705,13 +1716,16 @@ namespace cryptonote PERF_TIMER(on_relay_tx); bool failed = false; + res.status = ""; for (const auto &str: req.txids) { cryptonote::blobdata txid_data; if(!epee::string_tools::parse_hexstr_to_binbuff(str, txid_data)) { - res.status = std::string("Invalid transaction id: ") + str; + if (!res.status.empty()) res.status += ", "; + res.status += std::string("invalid transaction id: ") + str; failed = true; + continue; } crypto::hash txid = *reinterpret_cast<const crypto::hash*>(txid_data.data()); @@ -1727,8 +1741,10 @@ namespace cryptonote } else { - res.status = std::string("Transaction not found in pool: ") + str; + if (!res.status.empty()) res.status += ", "; + res.status += std::string("transaction not found in pool: ") + str; failed = true; + continue; } } diff --git a/src/rpc/daemon_handler.cpp b/src/rpc/daemon_handler.cpp index 6643ce4e4..908f9e187 100644 --- a/src/rpc/daemon_handler.cpp +++ b/src/rpc/daemon_handler.cpp @@ -799,6 +799,10 @@ namespace rpc } header.hash = hash_in; + if (b.miner_tx.vin.size() != 1 || b.miner_tx.vin.front().type() != typeid(txin_gen)) + { + return false; + } header.height = boost::get<txin_gen>(b.miner_tx.vin.front()).height; header.major_version = b.major_version; diff --git a/src/rpc/message.cpp b/src/rpc/message.cpp index d6da124d1..98b40e667 100644 --- a/src/rpc/message.cpp +++ b/src/rpc/message.cpp @@ -111,7 +111,7 @@ FullMessage::FullMessage(Message* message) FullMessage::FullMessage(const std::string& json_string, bool request) { doc.Parse(json_string.c_str()); - if (doc.HasParseError()) + if (doc.HasParseError() || !doc.IsObject()) { throw cryptonote::json::PARSE_FAIL(); } diff --git a/src/serialization/crypto.h b/src/serialization/crypto.h index 4213f2e58..8083bdeb1 100644 --- a/src/serialization/crypto.h +++ b/src/serialization/crypto.h @@ -34,7 +34,7 @@ #include "serialization.h" #include "debug_archive.h" -#include "crypto/chacha8.h" +#include "crypto/chacha.h" #include "crypto/crypto.h" #include "crypto/hash.h" @@ -77,7 +77,7 @@ bool do_serialize(Archive<true> &ar, std::vector<crypto::signature> &v) return true; } -BLOB_SERIALIZER(crypto::chacha8_iv); +BLOB_SERIALIZER(crypto::chacha_iv); BLOB_SERIALIZER(crypto::hash); BLOB_SERIALIZER(crypto::hash8); BLOB_SERIALIZER(crypto::public_key); diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index bbf794c05..608844b80 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -60,6 +60,7 @@ #include "rapidjson/document.h" #include "common/json_util.h" #include "ringct/rctSigs.h" +#include "multisig/multisig.h" #include "wallet/wallet_args.h" #include <stdexcept> @@ -340,6 +341,106 @@ namespace } return true; } + + void handle_transfer_exception(const std::exception_ptr &e) + { + try + { + std::rethrow_exception(e); + } + catch (const tools::error::daemon_busy&) + { + fail_msg_writer() << tr("daemon is busy. Please try again later."); + } + catch (const tools::error::no_connection_to_daemon&) + { + fail_msg_writer() << tr("no connection to daemon. Please make sure daemon is running."); + } + catch (const tools::error::wallet_rpc_error& e) + { + LOG_ERROR("RPC error: " << e.to_string()); + fail_msg_writer() << tr("RPC error: ") << e.what(); + } + catch (const tools::error::get_random_outs_error &e) + { + fail_msg_writer() << tr("failed to get random outputs to mix: ") << e.what(); + } + catch (const tools::error::not_enough_unlocked_money& e) + { + LOG_PRINT_L0(boost::format("not enough money to transfer, available only %s, sent amount %s") % + print_money(e.available()) % + print_money(e.tx_amount())); + fail_msg_writer() << tr("Not enough money in unlocked balance"); + } + catch (const tools::error::not_enough_money& e) + { + LOG_PRINT_L0(boost::format("not enough money to transfer, available only %s, sent amount %s") % + print_money(e.available()) % + print_money(e.tx_amount())); + fail_msg_writer() << tr("Not enough money in unlocked balance"); + } + catch (const tools::error::tx_not_possible& e) + { + LOG_PRINT_L0(boost::format("not enough money to transfer, available only %s, transaction amount %s = %s + %s (fee)") % + print_money(e.available()) % + print_money(e.tx_amount() + e.fee()) % + print_money(e.tx_amount()) % + print_money(e.fee())); + fail_msg_writer() << tr("Failed to find a way to create transactions. This is usually due to dust which is so small it cannot pay for itself in fees, or trying to send more money than the unlocked balance, or not leaving enough for fees"); + } + catch (const tools::error::not_enough_outs_to_mix& e) + { + auto writer = fail_msg_writer(); + writer << tr("not enough outputs for specified ring size") << " = " << (e.mixin_count() + 1) << ":"; + for (std::pair<uint64_t, uint64_t> outs_for_amount : e.scanty_outs()) + { + writer << "\n" << tr("output amount") << " = " << print_money(outs_for_amount.first) << ", " << tr("found outputs to use") << " = " << outs_for_amount.second; + } + } + catch (const tools::error::tx_not_constructed&) + { + fail_msg_writer() << tr("transaction was not constructed"); + } + catch (const tools::error::tx_rejected& e) + { + fail_msg_writer() << (boost::format(tr("transaction %s was rejected by daemon with status: ")) % get_transaction_hash(e.tx())) << e.status(); + std::string reason = e.reason(); + if (!reason.empty()) + fail_msg_writer() << tr("Reason: ") << reason; + } + catch (const tools::error::tx_sum_overflow& e) + { + fail_msg_writer() << e.what(); + } + catch (const tools::error::zero_destination&) + { + fail_msg_writer() << tr("one of destinations is zero"); + } + catch (const tools::error::tx_too_big& e) + { + fail_msg_writer() << tr("failed to find a suitable way to split transactions"); + } + catch (const tools::error::transfer_error& e) + { + LOG_ERROR("unknown transfer error: " << e.to_string()); + fail_msg_writer() << tr("unknown transfer error: ") << e.what(); + } + catch (const tools::error::multisig_export_needed& e) + { + LOG_ERROR("Multisig error: " << e.to_string()); + fail_msg_writer() << tr("Multisig error: ") << e.what(); + } + catch (const tools::error::wallet_internal_error& e) + { + LOG_ERROR("internal error: " << e.to_string()); + fail_msg_writer() << tr("internal error: ") << e.what(); + } + catch (const std::exception& e) + { + LOG_ERROR("unexpected error: " << e.what()); + fail_msg_writer() << tr("unexpected error: ") << e.what(); + } + } } bool parse_priority(const std::string& arg, uint32_t& priority) @@ -417,6 +518,11 @@ bool simple_wallet::print_seed(bool encrypted) bool success = false; std::string electrum_words; + if (m_wallet->multisig()) + { + fail_msg_writer() << tr("wallet is multisig and has no seed"); + return true; + } if (m_wallet->watch_only()) { fail_msg_writer() << tr("wallet is watch-only and has no seed"); @@ -468,6 +574,11 @@ bool simple_wallet::encrypted_seed(const std::vector<std::string> &args/* = std: bool simple_wallet::seed_set_language(const std::vector<std::string> &args/* = std::vector<std::string>()*/) { + if (m_wallet->multisig()) + { + fail_msg_writer() << tr("wallet is multisig and has no seed"); + return true; + } if (m_wallet->watch_only()) { fail_msg_writer() << tr("wallet is watch-only and has no seed"); @@ -587,6 +698,469 @@ bool simple_wallet::print_fee_info(const std::vector<std::string> &args/* = std: return true; } +bool simple_wallet::prepare_multisig(const std::vector<std::string> &args) +{ + if (m_wallet->multisig()) + { + fail_msg_writer() << tr("This wallet is already multisig"); + return true; + } + if (m_wallet->watch_only()) + { + fail_msg_writer() << tr("wallet is watch-only and cannot be made multisig"); + return true; + } + + if(m_wallet->get_num_transfer_details()) + { + fail_msg_writer() << tr("This wallet has been used before, please use a new wallet to create a multisig wallet"); + return true; + } + + const auto orig_pwd_container = get_and_verify_password(); + if(orig_pwd_container == boost::none) + { + fail_msg_writer() << tr("Your password is incorrect."); + return true; + } + + std::string multisig_info = m_wallet->get_multisig_info(); + success_msg_writer() << multisig_info; + success_msg_writer() << tr("Send this multisig info to all other participants, then use make_multisig <threshold> <info1> [<info2>...] with others' multisig info"); + success_msg_writer() << tr("This includes the PRIVATE view key, so needs to be disclosed only to that multisig wallet's participants "); + return true; +} + +bool simple_wallet::make_multisig(const std::vector<std::string> &args) +{ + if (m_wallet->multisig()) + { + fail_msg_writer() << tr("This wallet is already multisig"); + return true; + } + if (m_wallet->watch_only()) + { + fail_msg_writer() << tr("wallet is watch-only and cannot be made multisig"); + return true; + } + + if(m_wallet->get_num_transfer_details()) + { + fail_msg_writer() << tr("This wallet has been used before, please use a new wallet to create a multisig wallet"); + return true; + } + + const auto orig_pwd_container = get_and_verify_password(); + if(orig_pwd_container == boost::none) + { + fail_msg_writer() << tr("Your original password was incorrect."); + return true; + } + + if (args.size() < 2) + { + fail_msg_writer() << tr("usage: make_multisig <threshold> <multisiginfo1> [<multisiginfo2>...]"); + return true; + } + + // parse threshold + uint32_t threshold; + if (!string_tools::get_xtype_from_string(threshold, args[0])) + { + fail_msg_writer() << tr("Invalid threshold"); + return true; + } + + LOCK_IDLE_SCOPE(); + + try + { + auto local_args = args; + local_args.erase(local_args.begin()); + std::string multisig_extra_info = m_wallet->make_multisig(orig_pwd_container->password(), local_args, threshold); + if (!multisig_extra_info.empty()) + { + success_msg_writer() << tr("Another step is needed"); + success_msg_writer() << multisig_extra_info; + success_msg_writer() << tr("Send this multisig info to all other participants, then use finalize_multisig <info1> [<info2>...] with others' multisig info"); + return true; + } + } + catch (const std::exception &e) + { + fail_msg_writer() << tr("Error creating multisig: ") << e.what(); + return true; + } + + uint32_t total; + m_wallet->multisig(NULL, &threshold, &total); + success_msg_writer() << std::to_string(threshold) << "/" << total << tr(" multisig address: ") + << m_wallet->get_account().get_public_address_str(m_wallet->testnet()); + + return true; +} + +bool simple_wallet::finalize_multisig(const std::vector<std::string> &args) +{ + bool ready; + if (!m_wallet->multisig(&ready)) + { + fail_msg_writer() << tr("This wallet is not multisig"); + return true; + } + if (ready) + { + fail_msg_writer() << tr("This wallet is already finalized"); + return true; + } + + const auto orig_pwd_container = get_and_verify_password(); + if(orig_pwd_container == boost::none) + { + fail_msg_writer() << tr("Your original password was incorrect."); + return true; + } + + if (args.size() < 2) + { + fail_msg_writer() << tr("usage: finalize_multisig <multisiginfo1> [<multisiginfo2>...]"); + return true; + } + + try + { + if (!m_wallet->finalize_multisig(orig_pwd_container->password(), args)) + { + fail_msg_writer() << tr("Failed to finalize multisig"); + return true; + } + } + catch (const std::exception &e) + { + fail_msg_writer() << tr("Failed to finalize multisig: ") << e.what(); + return true; + } + + return true; +} + +bool simple_wallet::export_multisig(const std::vector<std::string> &args) +{ + bool ready; + if (!m_wallet->multisig(&ready)) + { + fail_msg_writer() << tr("This wallet is not multisig"); + return true; + } + if (!ready) + { + fail_msg_writer() << tr("This multisig wallet is not yet finalized"); + return true; + } + if (args.size() != 1) + { + fail_msg_writer() << tr("usage: export_multisig_info <filename>"); + return true; + } + if (m_wallet->ask_password() && !get_and_verify_password()) + return true; + + const std::string filename = args[0]; + try + { + cryptonote::blobdata ciphertext = m_wallet->export_multisig(); + + bool r = epee::file_io_utils::save_string_to_file(filename, ciphertext); + if (!r) + { + fail_msg_writer() << tr("failed to save file ") << filename; + return true; + } + } + catch (const std::exception &e) + { + LOG_ERROR("Error exporting multisig info: " << e.what()); + fail_msg_writer() << tr("Error exporting multisig info: ") << e.what(); + return true; + } + + success_msg_writer() << tr("Multisig info exported to ") << filename; + return true; +} + +bool simple_wallet::import_multisig(const std::vector<std::string> &args) +{ + bool ready; + uint32_t threshold, total; + if (!m_wallet->multisig(&ready, &threshold, &total)) + { + fail_msg_writer() << tr("This wallet is not multisig"); + return true; + } + if (!ready) + { + fail_msg_writer() << tr("This multisig wallet is not yet finalized"); + return true; + } + if (args.size() < threshold - 1) + { + fail_msg_writer() << tr("usage: import_multisig_info <filename1> [<filename2>...] - one for each other participant"); + return true; + } + if (m_wallet->ask_password() && !get_and_verify_password()) + return true; + + std::vector<cryptonote::blobdata> info; + for (size_t n = 0; n < args.size(); ++n) + { + const std::string filename = args[n]; + std::string data; + bool r = epee::file_io_utils::load_file_to_string(filename, data); + if (!r) + { + fail_msg_writer() << tr("failed to read file ") << filename; + return true; + } + info.push_back(std::move(data)); + } + + LOCK_IDLE_SCOPE(); + + // all read and parsed, actually import + try + { + size_t n_outputs = m_wallet->import_multisig(info); + // Clear line "Height xxx of xxx" + std::cout << "\r \r"; + success_msg_writer() << tr("Multisig info imported"); + } + catch (const std::exception &e) + { + fail_msg_writer() << tr("Failed to import multisig info: ") << e.what(); + return true; + } + if (m_trusted_daemon) + { + try + { + m_wallet->rescan_spent(); + } + catch (const std::exception &e) + { + message_writer() << tr("Failed to update spent status after importing multisig info: ") << e.what(); + } + } + else + { + message_writer() << tr("Untrusted daemon, spent status may be incorrect. Use a trusted daemon and run \"rescan_spent\""); + } + return true; +} + +bool simple_wallet::accept_loaded_tx(const tools::wallet2::multisig_tx_set &txs) +{ + std::string extra_message; + return accept_loaded_tx([&txs](){return txs.m_ptx.size();}, [&txs](size_t n)->const tools::wallet2::tx_construction_data&{return txs.m_ptx[n].construction_data;}, extra_message); +} + +bool simple_wallet::sign_multisig(const std::vector<std::string> &args) +{ + bool ready; + if(!m_wallet->multisig(&ready)) + { + fail_msg_writer() << tr("This is not a multisig wallet"); + return true; + } + if (!ready) + { + fail_msg_writer() << tr("This multisig wallet is not yet finalized"); + return true; + } + if (args.size() != 1) + { + fail_msg_writer() << tr("usage: sign_multisig <filename>"); + return true; + } + if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } + + std::string filename = args[0]; + std::vector<crypto::hash> txids; + uint32_t signers = 0; + try + { + bool r = m_wallet->sign_multisig_tx_from_file(filename, txids, [&](const tools::wallet2::multisig_tx_set &tx){ signers = tx.m_signers.size(); return accept_loaded_tx(tx); }); + if (!r) + { + fail_msg_writer() << tr("Failed to sign multisig transaction"); + return true; + } + } + catch (const tools::error::multisig_export_needed& e) + { + fail_msg_writer() << tr("Multisig error: ") << e.what(); + return true; + } + catch (const std::exception &e) + { + fail_msg_writer() << tr("Failed to sign multisig transaction: ") << e.what(); + return true; + } + + if (txids.empty()) + { + uint32_t threshold; + m_wallet->multisig(NULL, &threshold); + uint32_t signers_needed = threshold - signers - 1; + success_msg_writer(true) << tr("Transaction successfully signed to file ") << filename << ", " + << signers_needed << " more signer(s) needed"; + return true; + } + else + { + std::string txids_as_text; + for (const auto &txid: txids) + { + if (!txids_as_text.empty()) + txids_as_text += (", "); + txids_as_text += epee::string_tools::pod_to_hex(txid); + } + success_msg_writer(true) << tr("Transaction successfully signed to file ") << filename << ", txid " << txids_as_text; + success_msg_writer(true) << tr("It may be relayed to the network with submit_multisig"); + } + return true; +} + +bool simple_wallet::submit_multisig(const std::vector<std::string> &args) +{ + bool ready; + uint32_t threshold; + if (!m_wallet->multisig(&ready, &threshold)) + { + fail_msg_writer() << tr("This is not a multisig wallet"); + return true; + } + if (!ready) + { + fail_msg_writer() << tr("This multisig wallet is not yet finalized"); + return true; + } + if (args.size() != 1) + { + fail_msg_writer() << tr("usage: submit_multisig <filename>"); + return true; + } + if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } + + if (!try_connect_to_daemon()) + return true; + + std::string filename = args[0]; + try + { + tools::wallet2::multisig_tx_set txs; + bool r = m_wallet->load_multisig_tx_from_file(filename, txs, [&](const tools::wallet2::multisig_tx_set &tx){ return accept_loaded_tx(tx); }); + if (!r) + { + fail_msg_writer() << tr("Failed to load multisig transaction from file"); + return true; + } + if (txs.m_signers.size() < threshold) + { + fail_msg_writer() << (boost::format(tr("Multisig transaction signed by only %u signers, needs %u more signatures")) + % txs.m_signers.size() % (threshold - txs.m_signers.size())).str(); + return true; + } + + // actually commit the transactions + for (auto &ptx: txs.m_ptx) + { + m_wallet->commit_tx(ptx); + success_msg_writer(true) << tr("Transaction successfully submitted, transaction ") << get_transaction_hash(ptx.tx) << ENDL + << tr("You can check its status by using the `show_transfers` command."); + } + } + catch (const std::exception &e) + { + handle_transfer_exception(std::current_exception()); + } + catch (...) + { + LOG_ERROR("unknown error"); + fail_msg_writer() << tr("unknown error"); + } + + return true; +} + +bool simple_wallet::export_raw_multisig(const std::vector<std::string> &args) +{ + bool ready; + uint32_t threshold; + if (!m_wallet->multisig(&ready, &threshold)) + { + fail_msg_writer() << tr("This is not a multisig wallet"); + return true; + } + if (!ready) + { + fail_msg_writer() << tr("This multisig wallet is not yet finalized"); + return true; + } + if (args.size() != 1) + { + fail_msg_writer() << tr("usage: export_raw_multisig <filename>"); + return true; + } + if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } + + std::string filename = args[0]; + try + { + tools::wallet2::multisig_tx_set txs; + bool r = m_wallet->load_multisig_tx_from_file(filename, txs, [&](const tools::wallet2::multisig_tx_set &tx){ return accept_loaded_tx(tx); }); + if (!r) + { + fail_msg_writer() << tr("Failed to load multisig transaction from file"); + return true; + } + if (txs.m_signers.size() < threshold) + { + fail_msg_writer() << (boost::format(tr("Multisig transaction signed by only %u signers, needs %u more signatures")) + % txs.m_signers.size() % (threshold - txs.m_signers.size())).str(); + return true; + } + + // save the transactions + std::string filenames; + for (auto &ptx: txs.m_ptx) + { + const crypto::hash txid = cryptonote::get_transaction_hash(ptx.tx); + const std::string filename = std::string("raw_multisig_monero_tx_") + epee::string_tools::pod_to_hex(txid); + if (!filenames.empty()) + filenames += ", "; + filenames += filename; + if (!epee::file_io_utils::save_string_to_file(filename, cryptonote::tx_to_blob(ptx.tx))) + { + fail_msg_writer() << tr("Failed to export multisig transaction to file ") << filename; + return true; + } + } + success_msg_writer() << tr("Saved exported multisig transaction file(s): ") << filenames; + } + catch (const std::exception& e) + { + LOG_ERROR("unexpected error: " << e.what()); + fail_msg_writer() << tr("unexpected error: ") << e.what(); + } + catch (...) + { + LOG_ERROR("Unknown error"); + fail_msg_writer() << tr("unknown error"); + } + + return true; +} + bool simple_wallet::set_always_confirm_transfers(const std::vector<std::string> &args/* = std::vector<std::string>()*/) { const auto pwd_container = get_and_verify_password(); @@ -1001,8 +1575,20 @@ simple_wallet::simple_wallet() tr("Change the current log detail (level must be <0-4>).")); m_cmd_binder.set_handler("account", boost::bind(&simple_wallet::account, this, _1), - tr("account [new <label text with white spaces allowed> | switch <index> | label <index> <label text with white spaces allowed>]"), - tr("If no arguments are specified, the wallet shows all the existing accounts along with their balances. If the \"new\" argument is specified, the wallet creates a new account with its label initialized by the provided label text (which can be empty). If the \"switch\" argument is specified, the wallet switches to the account specified by <index>. If the \"label\" argument is specified, the wallet sets the label of the account specified by <index> to the provided label text.")); + tr("account\n" + " account new <label text with white spaces allowed>\n" + " account switch <index> \n" + " account label <index> <label text with white spaces allowed>\n" + " account tag <tag_name> <account_index_1> [<account_index_2> ...]\n" + " account untag <account_index_1> [<account_index_2> ...]\n" + " account tag_description <tag_name> <description>"), + tr("If no arguments are specified, the wallet shows all the existing accounts along with their balances.\n" + "If the \"new\" argument is specified, the wallet creates a new account with its label initialized by the provided label text (which can be empty).\n" + "If the \"switch\" argument is specified, the wallet switches to the account specified by <index>.\n" + "If the \"label\" argument is specified, the wallet sets the label of the account specified by <index> to the provided label text.\n" + "If the \"tag\" argument is specified, a tag <tag_name> is assigned to the specified accounts <account_index_1>, <account_index_2>, ....\n" + "If the \"untag\" argument is specified, the tags assigned to the specified accounts <account_index_1>, <account_index_2> ..., are removed.\n" + "If the \"tag_description\" argument is specified, the tag <tag_name> is assigned an arbitrary text <description>.")); m_cmd_binder.set_handler("address", boost::bind(&simple_wallet::print_address, this, _1), tr("address [ new <label text with white spaces allowed> | all | <index_min> [<index_max>] | label <index> <label text with white spaces allowed>]"), @@ -1165,6 +1751,35 @@ simple_wallet::simple_wallet() m_cmd_binder.set_handler("fee", boost::bind(&simple_wallet::print_fee_info, this, _1), tr("Print the information about the current fee and transaction backlog.")); + m_cmd_binder.set_handler("prepare_multisig", boost::bind(&simple_wallet::prepare_multisig, this, _1), + tr("Export data needed to create a multisig wallet")); + m_cmd_binder.set_handler("make_multisig", boost::bind(&simple_wallet::make_multisig, this, _1), + tr("make_multisig <threshold> <string1> [<string>...]"), + tr("Turn this wallet into a multisig wallet")); + m_cmd_binder.set_handler("finalize_multisig", + boost::bind(&simple_wallet::finalize_multisig, this, _1), + tr("finalize_multisig <string> [<string>...]"), + tr("Turn this wallet into a multisig wallet, extra step for N-1/N wallets")); + m_cmd_binder.set_handler("export_multisig_info", + boost::bind(&simple_wallet::export_multisig, this, _1), + tr("export_multisig <filename>"), + tr("Export multisig info for other participants")); + m_cmd_binder.set_handler("import_multisig_info", + boost::bind(&simple_wallet::import_multisig, this, _1), + tr("import_multisig <filename> [<filename>...]"), + tr("Import multisig info from other participants")); + m_cmd_binder.set_handler("sign_multisig", + boost::bind(&simple_wallet::sign_multisig, this, _1), + tr("sign_multisig <filename>"), + tr("Sign a multisig transaction from a file")); + m_cmd_binder.set_handler("submit_multisig", + boost::bind(&simple_wallet::submit_multisig, this, _1), + tr("submit_multisig <filename>"), + tr("Submit a signed multisig transaction from a file")); + m_cmd_binder.set_handler("export_raw_multisig_tx", + boost::bind(&simple_wallet::export_raw_multisig, this, _1), + tr("export_raw_multisig <filename>"), + tr("Export a signed multisig transaction to a file")); m_cmd_binder.set_handler("help", boost::bind(&simple_wallet::help, this, _1), tr("help [<command>]"), @@ -2098,9 +2713,17 @@ bool simple_wallet::open_wallet(const boost::program_options::variables_map& vm) return false; } + std::string prefix; + bool ready; + uint32_t threshold, total; + if (m_wallet->watch_only()) + prefix = tr("Opened watch-only wallet"); + else if (m_wallet->multisig(&ready, &threshold, &total)) + prefix = (boost::format(tr("Opened %u/%u multisig wallet%s")) % threshold % total % (ready ? "" : " (not yet finalized)")).str(); + else + prefix = tr("Opened wallet"); message_writer(console_color_white, true) << - (m_wallet->watch_only() ? tr("Opened watch-only wallet") : tr("Opened wallet")) << ": " - << m_wallet->get_account().get_public_address_str(m_wallet->testnet()); + prefix << ": " << m_wallet->get_account().get_public_address_str(m_wallet->testnet()); // If the wallet file is deprecated, we should ask for mnemonic language again and store // everything in the new format. // NOTE: this is_deprecated() refers to the wallet file format before becoming JSON. It does not refer to the "old english" seed words form of "deprecated" used elsewhere. @@ -2205,6 +2828,12 @@ bool simple_wallet::save(const std::vector<std::string> &args) //---------------------------------------------------------------------------------------------------- bool simple_wallet::save_watch_only(const std::vector<std::string> &args/* = std::vector<std::string>()*/) { + if (m_wallet->multisig()) + { + fail_msg_writer() << tr("wallet is multisig and cannot save a watch-only version"); + return true; + } + const auto pwd_container = tools::password_container::prompt(true, tr("Password for new watch-only wallet")); if (!pwd_container) @@ -2498,9 +3127,14 @@ bool simple_wallet::refresh(const std::vector<std::string>& args) //---------------------------------------------------------------------------------------------------- bool simple_wallet::show_balance_unlocked(bool detailed) { + std::string extra; + if (m_wallet->has_multisig_partial_key_images()) + extra = tr(" (Some owned outputs have partial key images - import_multisig_info needed)"); success_msg_writer() << tr("Currently selected account: [") << m_current_subaddress_account << tr("] ") << m_wallet->get_subaddress_label({m_current_subaddress_account, 0}); + const std::string tag = m_wallet->get_account_tags().second[m_current_subaddress_account]; + success_msg_writer() << tr("Tag: ") << (tag.empty() ? std::string{tr("(No tag assigned)")} : tag); success_msg_writer() << tr("Balance: ") << print_money(m_wallet->balance(m_current_subaddress_account)) << ", " - << tr("unlocked balance: ") << print_money(m_wallet->unlocked_balance(m_current_subaddress_account)); + << tr("unlocked balance: ") << print_money(m_wallet->unlocked_balance(m_current_subaddress_account)) << extra; std::map<uint32_t, uint64_t> balance_per_subaddress = m_wallet->balance_per_subaddress(m_current_subaddress_account); std::map<uint32_t, uint64_t> unlocked_balance_per_subaddress = m_wallet->unlocked_balance_per_subaddress(m_current_subaddress_account); if (!detailed || balance_per_subaddress.empty()) @@ -2594,7 +3228,7 @@ bool simple_wallet::show_incoming_transfers(const std::vector<std::string>& args } std::string verbose_string; if (verbose) - verbose_string = (boost::format("%68s%68s") % td.get_public_key() % (td.m_key_image_known ? epee::string_tools::pod_to_hex(td.m_key_image) : std::string('?', 64))).str(); + verbose_string = (boost::format("%68s%68s") % td.get_public_key() % (td.m_key_image_known ? epee::string_tools::pod_to_hex(td.m_key_image) : td.m_key_image_partial ? (epee::string_tools::pod_to_hex(td.m_key_image) + "/p") : std::string(64, '?'))).str(); message_writer(td.m_spent ? console_color_magenta : console_color_green, false) << boost::format("%21s%8s%12s%8s%16u%68s%16u%s") % print_money(td.amount()) % @@ -2858,101 +3492,6 @@ bool simple_wallet::print_ring_members(const std::vector<tools::wallet2::pending return true; } //---------------------------------------------------------------------------------------------------- -static void handle_transfer_exception(const std::exception_ptr &e) -{ - try - { - std::rethrow_exception(e); - } - catch (const tools::error::daemon_busy&) - { - fail_msg_writer() << tr("daemon is busy. Please try again later."); - } - catch (const tools::error::no_connection_to_daemon&) - { - fail_msg_writer() << tr("no connection to daemon. Please make sure daemon is running."); - } - catch (const tools::error::wallet_rpc_error& e) - { - LOG_ERROR("RPC error: " << e.to_string()); - fail_msg_writer() << tr("RPC error: ") << e.what(); - } - catch (const tools::error::get_random_outs_error &e) - { - fail_msg_writer() << tr("failed to get random outputs to mix: ") << e.what(); - } - catch (const tools::error::not_enough_unlocked_money& e) - { - LOG_PRINT_L0(boost::format("not enough money to transfer, available only %s, sent amount %s") % - print_money(e.available()) % - print_money(e.tx_amount())); - fail_msg_writer() << tr("Not enough money in unlocked balance"); - } - catch (const tools::error::not_enough_money& e) - { - LOG_PRINT_L0(boost::format("not enough money to transfer, available only %s, sent amount %s") % - print_money(e.available()) % - print_money(e.tx_amount())); - fail_msg_writer() << tr("Not enough money in unlocked balance"); - } - catch (const tools::error::tx_not_possible& e) - { - LOG_PRINT_L0(boost::format("not enough money to transfer, available only %s, transaction amount %s = %s + %s (fee)") % - print_money(e.available()) % - print_money(e.tx_amount() + e.fee()) % - print_money(e.tx_amount()) % - print_money(e.fee())); - fail_msg_writer() << tr("Failed to find a way to create transactions. This is usually due to dust which is so small it cannot pay for itself in fees, or trying to send more money than the unlocked balance, or not leaving enough for fees"); - } - catch (const tools::error::not_enough_outs_to_mix& e) - { - auto writer = fail_msg_writer(); - writer << tr("not enough outputs for specified ring size") << " = " << (e.mixin_count() + 1) << ":"; - for (std::pair<uint64_t, uint64_t> outs_for_amount : e.scanty_outs()) - { - writer << "\n" << tr("output amount") << " = " << print_money(outs_for_amount.first) << ", " << tr("found outputs to use") << " = " << outs_for_amount.second; - } - } - catch (const tools::error::tx_not_constructed&) - { - fail_msg_writer() << tr("transaction was not constructed"); - } - catch (const tools::error::tx_rejected& e) - { - fail_msg_writer() << (boost::format(tr("transaction %s was rejected by daemon with status: ")) % get_transaction_hash(e.tx())) << e.status(); - std::string reason = e.reason(); - if (!reason.empty()) - fail_msg_writer() << tr("Reason: ") << reason; - } - catch (const tools::error::tx_sum_overflow& e) - { - fail_msg_writer() << e.what(); - } - catch (const tools::error::zero_destination&) - { - fail_msg_writer() << tr("one of destinations is zero"); - } - catch (const tools::error::tx_too_big& e) - { - fail_msg_writer() << tr("failed to find a suitable way to split transactions"); - } - catch (const tools::error::transfer_error& e) - { - LOG_ERROR("unknown transfer error: " << e.to_string()); - fail_msg_writer() << tr("unknown transfer error: ") << e.what(); - } - catch (const tools::error::wallet_internal_error& e) - { - LOG_ERROR("internal error: " << e.to_string()); - fail_msg_writer() << tr("internal error: ") << e.what(); - } - catch (const std::exception& e) - { - LOG_ERROR("unexpected error: " << e.what()); - fail_msg_writer() << tr("unexpected error: ") << e.what(); - } -} -//---------------------------------------------------------------------------------------------------- bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::string> &args_) { // "transfer [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <address> <amount> [<payment_id>]" @@ -3279,7 +3818,19 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri } // actually commit the transactions - if (m_wallet->watch_only()) + if (m_wallet->multisig()) + { + bool r = m_wallet->save_multisig_tx(ptx_vector, "multisig_monero_tx"); + if (!r) + { + fail_msg_writer() << tr("Failed to write transaction(s) to file"); + } + else + { + success_msg_writer(true) << tr("Unsigned transaction(s) successfully written to file: ") << "multisig_monero_tx"; + } + } + else if (m_wallet->watch_only()) { bool r = m_wallet->save_tx(ptx_vector, "unsigned_monero_tx"); if (!r) @@ -3375,7 +3926,19 @@ bool simple_wallet::sweep_unmixable(const std::vector<std::string> &args_) } // actually commit the transactions - if (m_wallet->watch_only()) + if (m_wallet->multisig()) + { + bool r = m_wallet->save_multisig_tx(ptx_vector, "multisig_monero_tx"); + if (!r) + { + fail_msg_writer() << tr("Failed to write transaction(s) to file"); + } + else + { + success_msg_writer(true) << tr("Unsigned transaction(s) successfully written to file: ") << "multisig_monero_tx"; + } + } + else if (m_wallet->watch_only()) { bool r = m_wallet->save_tx(ptx_vector, "unsigned_monero_tx"); if (!r) @@ -3594,7 +4157,19 @@ bool simple_wallet::sweep_main(uint64_t below, const std::vector<std::string> &a } // actually commit the transactions - if (m_wallet->watch_only()) + if (m_wallet->multisig()) + { + bool r = m_wallet->save_multisig_tx(ptx_vector, "multisig_monero_tx"); + if (!r) + { + fail_msg_writer() << tr("Failed to write transaction(s) to file"); + } + else + { + success_msg_writer(true) << tr("Unsigned transaction(s) successfully written to file: ") << "multisig_monero_tx"; + } + } + else if (m_wallet->watch_only()) { bool r = m_wallet->save_tx(ptx_vector, "unsigned_monero_tx"); if (!r) @@ -3760,9 +4335,9 @@ bool simple_wallet::sweep_single(const std::vector<std::string> &args_) fail_msg_writer() << tr("Multiple transactions are created, which is not supposed to happen"); return true; } - if (ptx_vector[0].selected_transfers.size() > 1) + if (ptx_vector[0].selected_transfers.size() != 1) { - fail_msg_writer() << tr("The transaction uses multiple inputs, which is not supposed to happen"); + fail_msg_writer() << tr("The transaction uses multiple or no inputs, which is not supposed to happen"); return true; } @@ -3785,7 +4360,19 @@ bool simple_wallet::sweep_single(const std::vector<std::string> &args_) } // actually commit the transactions - if (m_wallet->watch_only()) + if (m_wallet->multisig()) + { + bool r = m_wallet->save_multisig_tx(ptx_vector, "multisig_monero_tx"); + if (!r) + { + fail_msg_writer() << tr("Failed to write transaction(s) to file"); + } + else + { + success_msg_writer(true) << tr("Unsigned transaction(s) successfully written to file: ") << "multisig_monero_tx"; + } + } + else if (m_wallet->watch_only()) { bool r = m_wallet->save_tx(ptx_vector, "unsigned_monero_tx"); if (!r) @@ -3804,85 +4391,9 @@ bool simple_wallet::sweep_single(const std::vector<std::string> &args_) } } - catch (const tools::error::daemon_busy&) - { - fail_msg_writer() << tr("daemon is busy. Please try again later."); - } - catch (const tools::error::no_connection_to_daemon&) - { - fail_msg_writer() << tr("no connection to daemon. Please make sure daemon is running."); - } - catch (const tools::error::wallet_rpc_error& e) - { - LOG_ERROR("RPC error: " << e.to_string()); - fail_msg_writer() << tr("RPC error: ") << e.what(); - } - catch (const tools::error::get_random_outs_error &e) - { - fail_msg_writer() << tr("failed to get random outputs to mix: ") << e.what(); - } - catch (const tools::error::not_enough_money& e) - { - LOG_PRINT_L0(boost::format("not enough money to transfer, available only %s, sent amount %s") % - print_money(e.available()) % - print_money(e.tx_amount())); - fail_msg_writer() << tr("Not enough money in unlocked balance"); - } - catch (const tools::error::tx_not_possible& e) - { - LOG_PRINT_L0(boost::format("not enough money to transfer, available only %s, transaction amount %s = %s + %s (fee)") % - print_money(e.available()) % - print_money(e.tx_amount() + e.fee()) % - print_money(e.tx_amount()) % - print_money(e.fee())); - fail_msg_writer() << tr("Failed to find a way to create transactions. This is usually due to dust which is so small it cannot pay for itself in fees, or trying to send more money than the unlocked balance, or not leaving enough for fees"); - } - catch (const tools::error::not_enough_outs_to_mix& e) - { - auto writer = fail_msg_writer(); - writer << tr("not enough outputs for specified mixin_count") << " = " << e.mixin_count() << ":"; - for (std::pair<uint64_t, uint64_t> outs_for_amount : e.scanty_outs()) - { - writer << "\n" << tr("output amount") << " = " << print_money(outs_for_amount.first) << ", " << tr("found outputs to mix") << " = " << outs_for_amount.second; - } - } - catch (const tools::error::tx_not_constructed&) - { - fail_msg_writer() << tr("transaction was not constructed"); - } - catch (const tools::error::tx_rejected& e) - { - fail_msg_writer() << (boost::format(tr("transaction %s was rejected by daemon with status: ")) % get_transaction_hash(e.tx())) << e.status(); - std::string reason = e.reason(); - if (!reason.empty()) - fail_msg_writer() << tr("Reason: ") << reason; - } - catch (const tools::error::tx_sum_overflow& e) - { - fail_msg_writer() << e.what(); - } - catch (const tools::error::zero_destination&) - { - fail_msg_writer() << tr("one of destinations is zero"); - } - catch (const tools::error::tx_too_big& e) - { - fail_msg_writer() << tr("failed to find a suitable way to split transactions"); - } - catch (const tools::error::transfer_error& e) - { - LOG_ERROR("unknown transfer error: " << e.to_string()); - fail_msg_writer() << tr("unknown transfer error: ") << e.what(); - } - catch (const tools::error::wallet_internal_error& e) - { - LOG_ERROR("internal error: " << e.to_string()); - fail_msg_writer() << tr("internal error: ") << e.what(); - } catch (const std::exception& e) { - LOG_ERROR("unexpected error: " << e.what()); - fail_msg_writer() << tr("unexpected error: ") << e.what(); + handle_transfer_exception(std::current_exception()); } catch (...) { @@ -4086,6 +4597,11 @@ bool simple_wallet::accept_loaded_tx(const tools::wallet2::signed_tx_set &txs) //---------------------------------------------------------------------------------------------------- bool simple_wallet::sign_transfer(const std::vector<std::string> &args_) { + if(m_wallet->multisig()) + { + fail_msg_writer() << tr("This is a multisig wallet, it can only sign with sign_multisig"); + return true; + } if(m_wallet->watch_only()) { fail_msg_writer() << tr("This is a watch only wallet"); @@ -4592,7 +5108,7 @@ bool simple_wallet::show_transfers(const std::vector<std::string> &args_) try { min_height = boost::lexical_cast<uint64_t>(local_args[0]); } - catch (boost::bad_lexical_cast &) { + catch (const boost::bad_lexical_cast &) { fail_msg_writer() << tr("bad min_height parameter:") << " " << local_args[0]; return true; } @@ -4604,7 +5120,7 @@ bool simple_wallet::show_transfers(const std::vector<std::string> &args_) try { max_height = boost::lexical_cast<uint64_t>(local_args[0]); } - catch (boost::bad_lexical_cast &) { + catch (const boost::bad_lexical_cast &) { fail_msg_writer() << tr("bad max_height parameter:") << " " << local_args[0]; return true; } @@ -4767,7 +5283,7 @@ bool simple_wallet::unspent_outputs(const std::vector<std::string> &args_) for (const auto& td : transfers) { uint64_t amount = td.amount(); - if (td.m_spent || amount < min_amount || amount > max_amount || td.m_subaddr_index.major != m_current_subaddress_account || subaddr_indices.count(td.m_subaddr_index.minor) == 0) + if (td.m_spent || amount < min_amount || amount > max_amount || td.m_subaddr_index.major != m_current_subaddress_account || (subaddr_indices.count(td.m_subaddr_index.minor) == 0 && !subaddr_indices.empty())) continue; amount_to_tds[amount].push_back(td); if (min_height > td.m_block_height) min_height = td.m_block_height; @@ -4930,6 +5446,9 @@ bool simple_wallet::account(const std::vector<std::string> &args/* = std::vector // account new <label text with white spaces allowed> // account switch <index> // account label <index> <label text with white spaces allowed> + // account tag <tag_name> <account_index_1> [<account_index_2> ...] + // account untag <account_index_1> [<account_index_2> ...] + // account tag_description <tag_name> <description> if (args.empty()) { @@ -4994,18 +5513,128 @@ bool simple_wallet::account(const std::vector<std::string> &args/* = std::vector fail_msg_writer() << e.what(); } } + else if (command == "tag" && local_args.size() >= 2) + { + const std::string tag = local_args[0]; + std::set<uint32_t> account_indices; + for (size_t i = 1; i < local_args.size(); ++i) + { + uint32_t account_index; + if (!epee::string_tools::get_xtype_from_string(account_index, local_args[i])) + { + fail_msg_writer() << tr("failed to parse index: ") << local_args[i]; + return true; + } + account_indices.insert(account_index); + } + try + { + m_wallet->set_account_tag(account_indices, tag); + print_accounts(tag); + } + catch (const std::exception& e) + { + fail_msg_writer() << e.what(); + } + } + else if (command == "untag" && local_args.size() >= 1) + { + std::set<uint32_t> account_indices; + for (size_t i = 0; i < local_args.size(); ++i) + { + uint32_t account_index; + if (!epee::string_tools::get_xtype_from_string(account_index, local_args[i])) + { + fail_msg_writer() << tr("failed to parse index: ") << local_args[i]; + return true; + } + account_indices.insert(account_index); + } + try + { + m_wallet->set_account_tag(account_indices, ""); + print_accounts(); + } + catch (const std::exception& e) + { + fail_msg_writer() << e.what(); + } + } + else if (command == "tag_description" && local_args.size() >= 1) + { + const std::string tag = local_args[0]; + std::string description; + if (local_args.size() > 1) + { + local_args.erase(local_args.begin()); + description = boost::join(local_args, " "); + } + try + { + m_wallet->set_account_tag_description(tag, description); + print_accounts(tag); + } + catch (const std::exception& e) + { + fail_msg_writer() << e.what(); + } + } else { - fail_msg_writer() << tr("usage: account [new <label text with white spaces allowed> | switch <index> | label <index> <label text with white spaces allowed>]"); + fail_msg_writer() << tr("usage:\n" + " account\n" + " account new <label text with white spaces allowed>\n" + " account switch <index>\n" + " account label <index> <label text with white spaces allowed>\n" + " account tag <tag_name> <account_index_1> [<account_index_2> ...]\n" + " account untag <account_index_1> [<account_index_2> ...]\n" + " account tag_description <tag_name> <description>"); } return true; } //---------------------------------------------------------------------------------------------------- void simple_wallet::print_accounts() { + const std::pair<std::map<std::string, std::string>, std::vector<std::string>>& account_tags = m_wallet->get_account_tags(); + size_t num_untagged_accounts = m_wallet->get_num_subaddress_accounts(); + for (const std::pair<std::string, std::string>& p : account_tags.first) + { + const std::string& tag = p.first; + print_accounts(tag); + num_untagged_accounts -= std::count(account_tags.second.begin(), account_tags.second.end(), tag); + success_msg_writer() << ""; + } + + if (num_untagged_accounts > 0) + print_accounts(""); + + if (num_untagged_accounts < m_wallet->get_num_subaddress_accounts()) + success_msg_writer() << tr("\nGrand total:\n Balance: ") << print_money(m_wallet->balance_all()) << tr(", unlocked balance: ") << print_money(m_wallet->unlocked_balance_all()); +} +//---------------------------------------------------------------------------------------------------- +void simple_wallet::print_accounts(const std::string& tag) +{ + const std::pair<std::map<std::string, std::string>, std::vector<std::string>>& account_tags = m_wallet->get_account_tags(); + if (tag.empty()) + { + success_msg_writer() << tr("Untagged accounts:"); + } + else + { + if (account_tags.first.count(tag) == 0) + { + fail_msg_writer() << boost::format(tr("Tag %s is unregistered.")) % tag; + return; + } + success_msg_writer() << tr("Accounts with tag: ") << tag; + success_msg_writer() << tr("Tag's description: ") << account_tags.first.find(tag)->second; + } success_msg_writer() << boost::format(" %15s %21s %21s %21s") % tr("Account") % tr("Balance") % tr("Unlocked balance") % tr("Label"); + uint64_t total_balance = 0, total_unlocked_balance = 0; for (uint32_t account_index = 0; account_index < m_wallet->get_num_subaddress_accounts(); ++account_index) { + if (account_tags.second[account_index] != tag) + continue; success_msg_writer() << boost::format(tr(" %c%8u %6s %21s %21s %21s")) % (m_current_subaddress_account == account_index ? '*' : ' ') % account_index @@ -5013,9 +5642,11 @@ void simple_wallet::print_accounts() % print_money(m_wallet->balance(account_index)) % print_money(m_wallet->unlocked_balance(account_index)) % m_wallet->get_subaddress_label({account_index, 0}); + total_balance += m_wallet->balance(account_index); + total_unlocked_balance += m_wallet->unlocked_balance(account_index); } success_msg_writer() << tr("----------------------------------------------------------------------------------"); - success_msg_writer() << boost::format(tr("%15s %21s %21s")) % "Total" % print_money(m_wallet->balance_all()) % print_money(m_wallet->unlocked_balance_all()); + success_msg_writer() << boost::format(tr("%15s %21s %21s")) % "Total" % print_money(total_balance) % print_money(total_unlocked_balance); } //---------------------------------------------------------------------------------------------------- bool simple_wallet::print_address(const std::vector<std::string> &args/* = std::vector<std::string>()*/) @@ -5359,10 +5990,20 @@ bool simple_wallet::status(const std::vector<std::string> &args) //---------------------------------------------------------------------------------------------------- bool simple_wallet::wallet_info(const std::vector<std::string> &args) { + bool ready; + uint32_t threshold, total; + message_writer() << tr("Filename: ") << m_wallet->get_wallet_file(); message_writer() << tr("Description: ") << m_wallet->get_description(); message_writer() << tr("Address: ") << m_wallet->get_account().get_public_address_str(m_wallet->testnet()); - message_writer() << tr("Watch only: ") << (m_wallet->watch_only() ? tr("Yes") : tr("No")); + std::string type; + if (m_wallet->watch_only()) + type = tr("Watch only"); + else if (m_wallet->multisig(&ready, &threshold, &total)) + type = (boost::format(tr("%u/%u multisig%s")) % threshold % total % (ready ? "" : " (not yet finalized)")).str(); + else + type = tr("Normal"); + message_writer() << tr("Type: ") << type; message_writer() << tr("Testnet: ") << (m_wallet->testnet() ? tr("Yes") : tr("No")); return true; } @@ -5379,6 +6020,11 @@ bool simple_wallet::sign(const std::vector<std::string> &args) fail_msg_writer() << tr("wallet is watch-only and cannot sign"); return true; } + if (m_wallet->multisig()) + { + fail_msg_writer() << tr("This wallet is multisig and cannot sign"); + return true; + } if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } std::string filename = args[0]; std::string data; @@ -5840,6 +6486,7 @@ int main(int argc, char* argv[]) const auto vm = wallet_args::main( argc, argv, "monero-wallet-cli [--wallet-file=<file>|--generate-new-wallet=<file>] [<COMMAND>]", + sw::tr("This is the command line monero wallet. It needs to connect to a monero\ndaemon to work correctly."), desc_params, positional_options, [](const std::string &s, bool emphasis){ tools::scoped_message_writer(emphasis ? epee::console_color_white : epee::console_color_default, true) << s; }, diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index ad174a636..e5c00e542 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -153,6 +153,7 @@ namespace cryptonote ); bool account(const std::vector<std::string> &args = std::vector<std::string>()); void print_accounts(); + void print_accounts(const std::string& tag); bool print_address(const std::vector<std::string> &args = std::vector<std::string>()); bool print_integrated_address(const std::vector<std::string> &args = std::vector<std::string>()); bool address_book(const std::vector<std::string> &args = std::vector<std::string>()); @@ -188,6 +189,15 @@ namespace cryptonote bool change_password(const std::vector<std::string>& args); bool payment_id(const std::vector<std::string> &args); bool print_fee_info(const std::vector<std::string> &args); + bool prepare_multisig(const std::vector<std::string>& args); + bool make_multisig(const std::vector<std::string>& args); + bool finalize_multisig(const std::vector<std::string> &args); + bool export_multisig(const std::vector<std::string>& args); + bool import_multisig(const std::vector<std::string>& args); + bool accept_loaded_tx(const tools::wallet2::multisig_tx_set &txs); + bool sign_multisig(const std::vector<std::string>& args); + bool submit_multisig(const std::vector<std::string>& args); + bool export_raw_multisig(const std::vector<std::string>& args); uint64_t get_daemon_blockchain_height(std::string& err); bool try_connect_to_daemon(bool silent = false, uint32_t* version = nullptr); diff --git a/src/version.cpp.in b/src/version.cpp.in index d1444f867..18d62db6b 100644 --- a/src/version.cpp.in +++ b/src/version.cpp.in @@ -1,5 +1,5 @@ #define DEF_MONERO_VERSION_TAG "@VERSIONTAG@" -#define DEF_MONERO_VERSION "0.11.0.0" +#define DEF_MONERO_VERSION "0.11.1.0-master" #define DEF_MONERO_RELEASE_NAME "Helium Hydra" #define DEF_MONERO_VERSION_FULL DEF_MONERO_VERSION "-" DEF_MONERO_VERSION_TAG diff --git a/src/wallet/CMakeLists.txt b/src/wallet/CMakeLists.txt index ab48bd7a2..2d664ba15 100644 --- a/src/wallet/CMakeLists.txt +++ b/src/wallet/CMakeLists.txt @@ -51,6 +51,7 @@ monero_add_library(wallet ${wallet_private_headers}) target_link_libraries(wallet PUBLIC + multisig common cryptonote_core mnemonics @@ -104,6 +105,7 @@ if (BUILD_GUI_DEPS) set(libs_to_merge wallet_api wallet + multisig cryptonote_core cryptonote_basic mnemonics diff --git a/src/wallet/api/unsigned_transaction.cpp b/src/wallet/api/unsigned_transaction.cpp index 4c8c5ade2..d22719189 100644 --- a/src/wallet/api/unsigned_transaction.cpp +++ b/src/wallet/api/unsigned_transaction.cpp @@ -293,6 +293,10 @@ std::vector<std::string> UnsignedTransactionImpl::recipientAddress() const // TODO: return integrated address if short payment ID exists std::vector<string> result; for (const auto &utx: m_unsigned_tx_set.txes) { + if (utx.dests.empty()) { + MERROR("empty destinations, skipped"); + continue; + } result.push_back(cryptonote::get_account_address_as_str(m_wallet.m_wallet->testnet(), utx.dests[0].is_subaddress, utx.dests[0].addr)); } return result; diff --git a/src/wallet/api/utils.cpp b/src/wallet/api/utils.cpp index a9646e038..e54dd9f1c 100644 --- a/src/wallet/api/utils.cpp +++ b/src/wallet/api/utils.cpp @@ -48,6 +48,11 @@ bool isAddressLocal(const std::string &address) } } +void onStartup() +{ + tools::on_startup(); +} + } diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h index b1f8369a3..ab1a48d6e 100644 --- a/src/wallet/api/wallet2_api.h +++ b/src/wallet/api/wallet2_api.h @@ -42,6 +42,7 @@ namespace Monero { namespace Utils { bool isAddressLocal(const std::string &hostaddr); + void onStartup(); } template<typename T> @@ -812,10 +813,10 @@ struct WalletManager * @brief verifyWalletPassword - check if the given filename is the wallet * @param keys_file_name - location of keys file * @param password - password to verify - * @param watch_only - verify only view keys? + * @param no_spend_key - verify only view keys? * @return - true if password is correct */ - virtual bool verifyWalletPassword(const std::string &keys_file_name, const std::string &password, bool watch_only) const = 0; + virtual bool verifyWalletPassword(const std::string &keys_file_name, const std::string &password, bool no_spend_key) const = 0; /*! * \brief findWallets - searches for the wallet files by given path name recursively diff --git a/src/wallet/api/wallet_manager.cpp b/src/wallet/api/wallet_manager.cpp index ee69ec028..a6e5e551e 100644 --- a/src/wallet/api/wallet_manager.cpp +++ b/src/wallet/api/wallet_manager.cpp @@ -127,9 +127,9 @@ bool WalletManagerImpl::walletExists(const std::string &path) return false; } -bool WalletManagerImpl::verifyWalletPassword(const std::string &keys_file_name, const std::string &password, bool watch_only) const +bool WalletManagerImpl::verifyWalletPassword(const std::string &keys_file_name, const std::string &password, bool no_spend_key) const { - return tools::wallet2::verify_password(keys_file_name, password, watch_only); + return tools::wallet2::verify_password(keys_file_name, password, no_spend_key); } std::vector<std::string> WalletManagerImpl::findWallets(const std::string &path) diff --git a/src/wallet/api/wallet_manager.h b/src/wallet/api/wallet_manager.h index 978a2d411..ef5b8f91b 100644 --- a/src/wallet/api/wallet_manager.h +++ b/src/wallet/api/wallet_manager.h @@ -50,7 +50,7 @@ public: const std::string &spendKeyString = ""); virtual bool closeWallet(Wallet *wallet, bool store = true); bool walletExists(const std::string &path); - bool verifyWalletPassword(const std::string &keys_file_name, const std::string &password, bool watch_only) const; + bool verifyWalletPassword(const std::string &keys_file_name, const std::string &password, bool no_spend_key) const; std::vector<std::string> findWallets(const std::string &path); std::string errorString() const; void setDaemonAddress(const std::string &address); diff --git a/src/wallet/node_rpc_proxy.cpp b/src/wallet/node_rpc_proxy.cpp index 9f30e4ac5..07185d12c 100644 --- a/src/wallet/node_rpc_proxy.cpp +++ b/src/wallet/node_rpc_proxy.cpp @@ -68,7 +68,6 @@ void NodeRPCProxy::invalidate() boost::optional<std::string> NodeRPCProxy::get_rpc_version(uint32_t &rpc_version) const { - const time_t now = time(NULL); if (m_rpc_version == 0) { epee::json_rpc::request<cryptonote::COMMAND_RPC_GET_VERSION::request> req_t = AUTO_VAL_INIT(req_t); diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index abd24295a..d6f4b9b98 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -46,6 +46,7 @@ using namespace epee; #include "rpc/core_rpc_server_commands_defs.h" #include "misc_language.h" #include "cryptonote_basic/cryptonote_basic_impl.h" +#include "multisig/multisig.h" #include "common/boost_serialization_helper.h" #include "common/command_line.h" #include "common/threadpool.h" @@ -56,6 +57,7 @@ using namespace epee; #include "mnemonics/electrum-words.h" #include "common/i18n.h" #include "common/util.h" +#include "common/apply_permutation.h" #include "rapidjson/document.h" #include "rapidjson/writer.h" #include "rapidjson/stringbuffer.h" @@ -87,6 +89,7 @@ using namespace cryptonote; #define UNSIGNED_TX_PREFIX "Monero unsigned tx set\004" #define SIGNED_TX_PREFIX "Monero signed tx set\004" +#define MULTISIG_UNSIGNED_TX_PREFIX "Monero multisig unsigned tx set\001" #define RECENT_OUTPUT_RATIO (0.5) // 50% of outputs are from the recent zone #define RECENT_OUTPUT_ZONE ((time_t)(1.8 * 86400)) // last 1.8 day makes up the recent zone (taken from monerolink.pdf, Miller et al) @@ -100,6 +103,8 @@ using namespace cryptonote; #define KEY_IMAGE_EXPORT_FILE_MAGIC "Monero key image export\002" +#define MULTISIG_EXPORT_FILE_MAGIC "Monero multisig export\001" + namespace { // Create on-demand to prevent static initialization order fiasco issues. @@ -139,6 +144,16 @@ uint64_t calculate_fee(uint64_t fee_per_kb, const cryptonote::blobdata &blob, ui return calculate_fee(fee_per_kb, blob.size(), fee_multiplier); } +std::string get_size_string(size_t sz) +{ + return std::to_string(sz) + " bytes (" + std::to_string((sz + 1023) / 1024) + " kB)"; +} + +std::string get_size_string(const cryptonote::blobdata &tx) +{ + return get_size_string(tx.size()); +} + std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variables_map& vm, const options& opts, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter) { const bool testnet = command_line::get_arg(vm, opts.testnet); @@ -524,6 +539,47 @@ uint8_t get_bulletproof_fork(bool testnet) return 255; // TODO } +crypto::hash8 get_short_payment_id(const tools::wallet2::pending_tx &ptx) +{ + crypto::hash8 payment_id8 = null_hash8; + std::vector<tx_extra_field> tx_extra_fields; + if(!parse_tx_extra(ptx.tx.extra, tx_extra_fields)) + return payment_id8; + cryptonote::tx_extra_nonce extra_nonce; + if (find_tx_extra_field_by_type(tx_extra_fields, extra_nonce)) + { + if(get_encrypted_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id8)) + { + if (ptx.dests.empty()) + { + MWARNING("Encrypted payment id found, but no destinations public key, cannot decrypt"); + return crypto::null_hash8; + } + decrypt_payment_id(payment_id8, ptx.dests[0].addr.m_view_public_key, ptx.tx_key); + } + } + return payment_id8; +} + +tools::wallet2::tx_construction_data get_construction_data_with_decrypted_short_payment_id(const tools::wallet2::pending_tx &ptx) +{ + tools::wallet2::tx_construction_data construction_data = ptx.construction_data; + crypto::hash8 payment_id = get_short_payment_id(ptx); + if (payment_id != null_hash8) + { + // Remove encrypted + remove_field_from_tx_extra(construction_data.extra, typeid(cryptonote::tx_extra_nonce)); + // Add decrypted + std::string extra_nonce; + set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, payment_id); + THROW_WALLET_EXCEPTION_IF(!add_extra_nonce_to_tx_extra(construction_data.extra, extra_nonce), + tools::error::wallet_internal_error, "Failed to add decrypted payment id to tx extra"); + LOG_PRINT_L1("Decrypted payment ID: " << payment_id); + } + return construction_data; +} + + //----------------------------------------------------------------- } //namespace namespace tools @@ -534,6 +590,41 @@ const size_t MAX_SPLIT_ATTEMPTS = 30; constexpr const std::chrono::seconds wallet2::rpc_timeout; const char* wallet2::tr(const char* str) { return i18n_translate(str, "tools::wallet2"); } +wallet2::wallet2(bool testnet, bool restricted): + m_multisig_rescan_info(NULL), + m_multisig_rescan_k(NULL), + m_run(true), + m_callback(0), + m_testnet(testnet), + m_always_confirm_transfers(true), + m_print_ring_members(false), + m_store_tx_info(true), + m_default_mixin(0), + m_default_priority(0), + m_refresh_type(RefreshOptimizeCoinbase), + m_auto_refresh(true), + m_refresh_from_block_height(0), + m_confirm_missing_payment_id(true), + m_ask_password(true), + m_min_output_count(0), + m_min_output_value(0), + m_merge_destinations(false), + m_confirm_backlog(true), + m_is_initialized(false), + m_restricted(restricted), + is_old_file_format(false), + m_node_rpc_proxy(m_http_client, m_daemon_rpc_mutex), + m_subaddress_lookahead_major(SUBADDRESS_LOOKAHEAD_MAJOR), + m_subaddress_lookahead_minor(SUBADDRESS_LOOKAHEAD_MINOR), + m_light_wallet(false), + m_light_wallet_scanned_block_height(0), + m_light_wallet_blockchain_height(0), + m_light_wallet_connected(false), + m_light_wallet_balance(0), + m_light_wallet_unlocked_balance(0) +{ +} + bool wallet2::has_testnet_option(const boost::program_options::variables_map& vm) { return command_line::get_arg(vm, options().testnet); @@ -634,7 +725,11 @@ bool wallet2::get_seed(std::string& electrum_words, const epee::wipeable_string crypto::secret_key key = get_account().get_keys().m_spend_secret_key; if (!passphrase.empty()) key = cryptonote::encrypt_key(key, passphrase); - crypto::ElectrumWords::bytes_to_words(key, electrum_words, seed_language); + if (!crypto::ElectrumWords::bytes_to_words(key, electrum_words, seed_language)) + { + std::cout << "Failed to create seed from key for language: " << seed_language << std::endl; + return false; + } return true; } @@ -725,9 +820,9 @@ void wallet2::expand_subaddresses(const cryptonote::subaddress_index& index) { // add new accounts cryptonote::subaddress_index index2; - for (index2.major = m_subaddress_labels.size(); index2.major < index.major + SUBADDRESS_LOOKAHEAD_MAJOR; ++index2.major) + for (index2.major = m_subaddress_labels.size(); index2.major < index.major + m_subaddress_lookahead_major; ++index2.major) { - for (index2.minor = 0; index2.minor < (index2.major == index.major ? index.minor : 0) + SUBADDRESS_LOOKAHEAD_MINOR; ++index2.minor) + for (index2.minor = 0; index2.minor < (index2.major == index.major ? index.minor : 0) + m_subaddress_lookahead_minor; ++index2.minor) { if (m_subaddresses_inv.count(index2) == 0) { @@ -744,7 +839,7 @@ void wallet2::expand_subaddresses(const cryptonote::subaddress_index& index) { // add new subaddresses cryptonote::subaddress_index index2 = index; - for (index2.minor = m_subaddress_labels[index.major].size(); index2.minor < index.minor + SUBADDRESS_LOOKAHEAD_MINOR; ++index2.minor) + for (index2.minor = m_subaddress_labels[index.major].size(); index2.minor < index.minor + m_subaddress_lookahead_minor; ++index2.minor) { if (m_subaddresses_inv.count(index2) == 0) { @@ -774,6 +869,12 @@ void wallet2::set_subaddress_label(const cryptonote::subaddress_index& index, co m_subaddress_labels[index.major][index.minor] = label; } //---------------------------------------------------------------------------------------------------- +void wallet2::set_subaddress_lookahead(size_t major, size_t minor) +{ + m_subaddress_lookahead_major = major; + m_subaddress_lookahead_minor = minor; +} +//---------------------------------------------------------------------------------------------------- /*! * \brief Tells if the wallet file is deprecated. */ @@ -846,10 +947,20 @@ static uint64_t decodeRct(const rct::rctSig & rv, const crypto::key_derivation & //---------------------------------------------------------------------------------------------------- void wallet2::scan_output(const cryptonote::account_keys &keys, const cryptonote::transaction &tx, const crypto::public_key &tx_pub_key, size_t i, tx_scan_info_t &tx_scan_info, int &num_vouts_received, std::unordered_map<cryptonote::subaddress_index, uint64_t> &tx_money_got_in_outs, std::vector<size_t> &outs) { - bool r = cryptonote::generate_key_image_helper_precomp(keys, boost::get<cryptonote::txout_to_key>(tx.vout[i].target).key, tx_scan_info.received->derivation, i, tx_scan_info.received->index, tx_scan_info.in_ephemeral, tx_scan_info.ki); - THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key image"); - THROW_WALLET_EXCEPTION_IF(tx_scan_info.in_ephemeral.pub != boost::get<cryptonote::txout_to_key>(tx.vout[i].target).key, - error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key"); + THROW_WALLET_EXCEPTION_IF(i >= tx.vout.size(), error::wallet_internal_error, "Invalid vout index"); + if (m_multisig) + { + tx_scan_info.in_ephemeral.pub = boost::get<cryptonote::txout_to_key>(tx.vout[i].target).key; + tx_scan_info.in_ephemeral.sec = crypto::null_skey; + tx_scan_info.ki = rct::rct2ki(rct::zero()); + } + else + { + bool r = cryptonote::generate_key_image_helper_precomp(keys, boost::get<cryptonote::txout_to_key>(tx.vout[i].target).key, tx_scan_info.received->derivation, i, tx_scan_info.received->index, tx_scan_info.in_ephemeral, tx_scan_info.ki); + THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key image"); + THROW_WALLET_EXCEPTION_IF(tx_scan_info.in_ephemeral.pub != boost::get<cryptonote::txout_to_key>(tx.vout[i].target).key, + error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key"); + } outs.push_back(i); if (tx_scan_info.money_transfered == 0) @@ -894,7 +1005,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote LOG_PRINT_L0("Public key wasn't found in the transaction extra. Skipping transaction " << txid); if(0 != m_callback) m_callback->on_skip_transaction(height, txid, tx); - return; + break; } int num_vouts_received = 0; @@ -1018,7 +1129,8 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote td.m_tx = (const cryptonote::transaction_prefix&)tx; td.m_txid = txid; td.m_key_image = tx_scan_info[o].ki; - td.m_key_image_known = !m_watch_only; + td.m_key_image_known = !m_watch_only && !m_multisig; + td.m_key_image_partial = m_multisig; td.m_amount = tx.vout[o].amount; td.m_pk_index = pk_index - 1; td.m_subaddr_index = tx_scan_info[o].received->index; @@ -1040,8 +1152,16 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote td.m_rct = false; } set_unspent(m_transfers.size()-1); - m_key_images[td.m_key_image] = m_transfers.size()-1; + if (!m_multisig) + m_key_images[td.m_key_image] = m_transfers.size()-1; m_pub_keys[tx_scan_info[o].in_ephemeral.pub] = m_transfers.size()-1; + if (m_multisig) + { + THROW_WALLET_EXCEPTION_IF(!m_multisig_rescan_k && m_multisig_rescan_info, + error::wallet_internal_error, "NULL m_multisig_rescan_k"); + if (m_multisig_rescan_info && m_multisig_rescan_info->front().size() >= m_transfers.size()) + update_multisig_rescan_info(*m_multisig_rescan_k, *m_multisig_rescan_info, m_transfers.size() - 1); + } 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); @@ -1090,6 +1210,13 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote td.m_mask = rct::identity(); td.m_rct = false; } + if (m_multisig) + { + THROW_WALLET_EXCEPTION_IF(!m_multisig_rescan_k && m_multisig_rescan_info, + error::wallet_internal_error, "NULL m_multisig_rescan_k"); + if (m_multisig_rescan_info && m_multisig_rescan_info->front().size() >= m_transfers.size()) + update_multisig_rescan_info(*m_multisig_rescan_k, *m_multisig_rescan_info, m_transfers.size() - 1); + } THROW_WALLET_EXCEPTION_IF(td.get_public_key() != tx_scan_info[o].in_ephemeral.pub, error::wallet_internal_error, "Inconsistent public keys"); THROW_WALLET_EXCEPTION_IF(td.m_spent, error::wallet_internal_error, "Inconsistent spent status"); @@ -2083,8 +2210,10 @@ void wallet2::detach_blockchain(uint64_t height) for(size_t i = i_start; i!= m_transfers.size();i++) { + if (!m_transfers[i].m_key_image_known || m_transfers[i].m_key_image_partial) + continue; auto it_ki = m_key_images.find(m_transfers[i].m_key_image); - THROW_WALLET_EXCEPTION_IF(it_ki == m_key_images.end(), error::wallet_internal_error, "key image not found"); + THROW_WALLET_EXCEPTION_IF(it_ki == m_key_images.end(), error::wallet_internal_error, "key image not found: index " + std::to_string(i) + ", ki " + epee::string_tools::pod_to_hex(m_transfers[i].m_key_image) + ", " + std::to_string(m_key_images.size()) + " key images known"); m_key_images.erase(it_ki); } @@ -2157,6 +2286,7 @@ bool wallet2::clear() bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable_string& password, bool watch_only) { std::string account_data; + std::string multisig_signers; cryptonote::account_base account = m_account; if (watch_only) @@ -2181,6 +2311,20 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable value2.SetInt(watch_only ? 1 :0); // WTF ? JSON has different true and false types, and not boolean ?? json.AddMember("watch_only", value2, json.GetAllocator()); + value2.SetInt(m_multisig ? 1 :0); + json.AddMember("multisig", value2, json.GetAllocator()); + + value2.SetUint(m_multisig_threshold); + json.AddMember("multisig_threshold", value2, json.GetAllocator()); + + if (m_multisig) + { + bool r = ::serialization::dump_binary(m_multisig_signers, multisig_signers); + CHECK_AND_ASSERT_MES(r, false, "failed to serialize wallet multisig signers"); + value.SetString(multisig_signers.c_str(), multisig_signers.length()); + json.AddMember("multisig_signers", value, json.GetAllocator()); + } + value2.SetInt(m_always_confirm_transfers ? 1 :0); json.AddMember("always_confirm_transfers", value2, json.GetAllocator()); @@ -2239,12 +2383,12 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable account_data = buffer.GetString(); // Encrypt the entire JSON object. - crypto::chacha8_key key; - crypto::generate_chacha8_key(password.data(), password.size(), key); + crypto::chacha_key key; + crypto::generate_chacha_key(password.data(), password.size(), key); std::string cipher; cipher.resize(account_data.size()); - keys_file_data.iv = crypto::rand<crypto::chacha8_iv>(); - crypto::chacha8(account_data.data(), account_data.size(), key, keys_file_data.iv, &cipher[0]); + keys_file_data.iv = crypto::rand<crypto::chacha_iv>(); + crypto::chacha20(account_data.data(), account_data.size(), key, keys_file_data.iv, &cipher[0]); keys_file_data.account_data = cipher; std::string buf; @@ -2272,6 +2416,7 @@ namespace */ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_string& password) { + rapidjson::Document json; wallet2::keys_file_data keys_file_data; std::string buf; bool r = epee::file_io_utils::load_file_to_string(keys_file_name, buf); @@ -2280,18 +2425,22 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ // Decrypt the contents r = ::serialization::parse_binary(buf, keys_file_data); THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "internal error: failed to deserialize \"" + keys_file_name + '\"'); - crypto::chacha8_key key; - crypto::generate_chacha8_key(password.data(), password.size(), key); + crypto::chacha_key key; + crypto::generate_chacha_key(password.data(), password.size(), key); std::string account_data; account_data.resize(keys_file_data.account_data.size()); - crypto::chacha8(keys_file_data.account_data.data(), keys_file_data.account_data.size(), key, keys_file_data.iv, &account_data[0]); + crypto::chacha20(keys_file_data.account_data.data(), keys_file_data.account_data.size(), key, keys_file_data.iv, &account_data[0]); + if (json.Parse(account_data.c_str()).HasParseError() || !json.IsObject()) + crypto::chacha8(keys_file_data.account_data.data(), keys_file_data.account_data.size(), key, keys_file_data.iv, &account_data[0]); // The contents should be JSON if the wallet follows the new format. - rapidjson::Document json; if (json.Parse(account_data.c_str()).HasParseError()) { is_old_file_format = true; m_watch_only = false; + m_multisig = false; + m_multisig_threshold = 0; + m_multisig_signers.clear(); m_always_confirm_transfers = false; m_print_ring_members = false; m_default_mixin = 0; @@ -2328,6 +2477,31 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ } GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, watch_only, int, Int, false, false); m_watch_only = field_watch_only; + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, multisig, int, Int, false, false); + m_multisig = field_multisig; + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, multisig_threshold, unsigned int, Uint, m_multisig, 0); + m_multisig_threshold = field_multisig_threshold; + if (m_multisig) + { + if (!json.HasMember("multisig_signers")) + { + LOG_ERROR("Field multisig_signers not found in JSON"); + return false; + } + if (!json["multisig_signers"].IsString()) + { + LOG_ERROR("Field multisig_signers found in JSON, but not String"); + return false; + } + const char *field_multisig_signers = json["multisig_signers"].GetString(); + std::string multisig_signers = std::string(field_multisig_signers, field_multisig_signers + json["multisig_signers"].GetStringLength()); + r = ::serialization::parse_binary(multisig_signers, m_multisig_signers); + if (!r) + { + LOG_ERROR("Field multisig_signers found in JSON, but failed to parse"); + return false; + } + } GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, always_confirm_transfers, int, Int, false, true); m_always_confirm_transfers = field_always_confirm_transfers; GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, print_ring_members, int, Int, false, true); @@ -2394,7 +2568,7 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ const cryptonote::account_keys& keys = m_account.get_keys(); r = epee::serialization::load_t_from_binary(m_account, account_data); r = r && verify_keys(keys.m_view_secret_key, keys.m_account_address.m_view_public_key); - if(!m_watch_only) + if(!m_watch_only && !m_multisig) r = r && verify_keys(keys.m_spend_secret_key, keys.m_account_address.m_spend_public_key); THROW_WALLET_EXCEPTION_IF(!r, error::invalid_password); return true; @@ -2412,14 +2586,14 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ */ bool wallet2::verify_password(const epee::wipeable_string& password) const { - return verify_password(m_keys_file, password, m_watch_only); + return verify_password(m_keys_file, password, m_watch_only || m_multisig); } /*! * \brief verify password for specified wallet keys file. * \param keys_file_name Keys file to verify password for * \param password Password to verify - * \param watch_only If set = only verify view keys, otherwise also spend keys + * \param no_spend_key If set = only verify view keys, otherwise also spend keys * \return true if password is correct * * for verification only @@ -2427,8 +2601,9 @@ bool wallet2::verify_password(const epee::wipeable_string& password) const * can be used prior to rewriting wallet keys file, to ensure user has entered the correct password * */ -bool wallet2::verify_password(const std::string& keys_file_name, const epee::wipeable_string& password, bool watch_only) +bool wallet2::verify_password(const std::string& keys_file_name, const epee::wipeable_string& password, bool no_spend_key) { + rapidjson::Document json; wallet2::keys_file_data keys_file_data; std::string buf; bool r = epee::file_io_utils::load_file_to_string(keys_file_name, buf); @@ -2437,14 +2612,15 @@ bool wallet2::verify_password(const std::string& keys_file_name, const epee::wip // Decrypt the contents r = ::serialization::parse_binary(buf, keys_file_data); THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "internal error: failed to deserialize \"" + keys_file_name + '\"'); - crypto::chacha8_key key; - crypto::generate_chacha8_key(password.data(), password.size(), key); + crypto::chacha_key key; + crypto::generate_chacha_key(password.data(), password.size(), key); std::string account_data; account_data.resize(keys_file_data.account_data.size()); - crypto::chacha8(keys_file_data.account_data.data(), keys_file_data.account_data.size(), key, keys_file_data.iv, &account_data[0]); + crypto::chacha20(keys_file_data.account_data.data(), keys_file_data.account_data.size(), key, keys_file_data.iv, &account_data[0]); + if (json.Parse(account_data.c_str()).HasParseError() || !json.IsObject()) + crypto::chacha8(keys_file_data.account_data.data(), keys_file_data.account_data.size(), key, keys_file_data.iv, &account_data[0]); // The contents should be JSON if the wallet follows the new format. - rapidjson::Document json; if (json.Parse(account_data.c_str()).HasParseError()) { // old format before JSON wallet key file format @@ -2461,7 +2637,7 @@ bool wallet2::verify_password(const std::string& keys_file_name, const epee::wip const cryptonote::account_keys& keys = account_data_check.get_keys(); r = r && verify_keys(keys.m_view_secret_key, keys.m_account_address.m_view_public_key); - if(!watch_only) + if(!no_spend_key) r = r && verify_keys(keys.m_spend_secret_key, keys.m_account_address.m_spend_public_key); return r; } @@ -2481,14 +2657,20 @@ crypto::secret_key wallet2::generate(const std::string& wallet_, const epee::wip clear(); prepare_file_names(wallet_); - boost::system::error_code ignored_ec; - THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_wallet_file, ignored_ec), error::file_exists, m_wallet_file); - THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_keys_file, ignored_ec), error::file_exists, m_keys_file); + if (!wallet_.empty()) + { + boost::system::error_code ignored_ec; + THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_wallet_file, ignored_ec), error::file_exists, m_wallet_file); + THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_keys_file, ignored_ec), error::file_exists, m_keys_file); + } crypto::secret_key retval = m_account.generate(recovery_param, recover, two_random); m_account_public_address = m_account.get_keys().m_account_address; m_watch_only = false; + m_multisig = false; + m_multisig_threshold = 0; + m_multisig_signers.clear(); // -1 month for fluctuations in block time and machine date/time setup. // avg seconds per block @@ -2502,18 +2684,23 @@ crypto::secret_key wallet2::generate(const std::string& wallet_, const epee::wip m_refresh_from_block_height = height >= blocks_per_month ? height - blocks_per_month : 0; } - bool r = store_keys(m_keys_file, password, false); - THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); + if (!wallet_.empty()) + { + bool r = store_keys(m_keys_file, password, false); + THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); - r = file_io_utils::save_string_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_testnet)); - if(!r) MERROR("String with address text not saved"); + r = file_io_utils::save_string_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_testnet)); + if(!r) MERROR("String with address text not saved"); + } cryptonote::block b; generate_genesis(b); m_blockchain.push_back(get_block_hash(b)); add_subaddress_account(tr("Primary account")); - store(); + if (!wallet_.empty()) + store(); + return retval; } @@ -2562,26 +2749,36 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& clear(); prepare_file_names(wallet_); - boost::system::error_code ignored_ec; - THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_wallet_file, ignored_ec), error::file_exists, m_wallet_file); - THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_keys_file, ignored_ec), error::file_exists, m_keys_file); + if (!wallet_.empty()) + { + boost::system::error_code ignored_ec; + THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_wallet_file, ignored_ec), error::file_exists, m_wallet_file); + THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_keys_file, ignored_ec), error::file_exists, m_keys_file); + } m_account.create_from_viewkey(account_public_address, viewkey); m_account_public_address = account_public_address; m_watch_only = true; + m_multisig = false; + m_multisig_threshold = 0; + m_multisig_signers.clear(); - bool r = store_keys(m_keys_file, password, true); - THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); + if (!wallet_.empty()) + { + bool r = store_keys(m_keys_file, password, true); + THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); - r = file_io_utils::save_string_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_testnet)); - if(!r) MERROR("String with address text not saved"); + r = file_io_utils::save_string_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_testnet)); + if(!r) MERROR("String with address text not saved"); + } cryptonote::block b; generate_genesis(b); m_blockchain.push_back(get_block_hash(b)); add_subaddress_account(tr("Primary account")); - store(); + if (!wallet_.empty()) + store(); } /*! @@ -2598,26 +2795,373 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& clear(); prepare_file_names(wallet_); - boost::system::error_code ignored_ec; - THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_wallet_file, ignored_ec), error::file_exists, m_wallet_file); - THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_keys_file, ignored_ec), error::file_exists, m_keys_file); + if (!wallet_.empty()) + { + boost::system::error_code ignored_ec; + THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_wallet_file, ignored_ec), error::file_exists, m_wallet_file); + THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_keys_file, ignored_ec), error::file_exists, m_keys_file); + } m_account.create_from_keys(account_public_address, spendkey, viewkey); m_account_public_address = account_public_address; m_watch_only = false; + m_multisig = false; + m_multisig_threshold = 0; + m_multisig_signers.clear(); - bool r = store_keys(m_keys_file, password, false); - THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); + if (!wallet_.empty()) + { + bool r = store_keys(m_keys_file, password, false); + THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); + + r = file_io_utils::save_string_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_testnet)); + if(!r) MERROR("String with address text not saved"); + } + + cryptonote::block b; + generate_genesis(b); + m_blockchain.push_back(get_block_hash(b)); + + if (!wallet_.empty()) + store(); +} + +std::string wallet2::make_multisig(const epee::wipeable_string &password, + const std::vector<crypto::secret_key> &view_keys, + const std::vector<crypto::public_key> &spend_keys, + uint32_t threshold) +{ + CHECK_AND_ASSERT_THROW_MES(!view_keys.empty(), "empty view keys"); + CHECK_AND_ASSERT_THROW_MES(view_keys.size() == spend_keys.size(), "Mismatched view/spend key sizes"); + CHECK_AND_ASSERT_THROW_MES(threshold > 1 && threshold <= spend_keys.size() + 1, "Invalid threshold"); + CHECK_AND_ASSERT_THROW_MES(threshold == spend_keys.size() || threshold == spend_keys.size() + 1, "Unsupported threshold case"); + + std::string extra_multisig_info; + crypto::hash hash; + + clear(); + + MINFO("Creating spend key..."); + std::vector<crypto::secret_key> multisig_keys; + rct::key spend_pkey, spend_skey; + if (threshold == spend_keys.size() + 1) + { + cryptonote::generate_multisig_N_N(get_account().get_keys(), spend_keys, multisig_keys, spend_skey, spend_pkey); + } + else if (threshold == spend_keys.size()) + { + cryptonote::generate_multisig_N1_N(get_account().get_keys(), spend_keys, multisig_keys, spend_skey, spend_pkey); + + // We need an extra step, so we package all the composite public keys + // we know about, and make a signed string out of them + std::string data; + crypto::public_key signer; + CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(rct::rct2sk(spend_skey), signer), "Failed to derive public spend key"); + data += std::string((const char *)&signer, sizeof(crypto::public_key)); + + for (const auto &msk: multisig_keys) + { + rct::key pmsk = rct::scalarmultBase(rct::sk2rct(msk)); + data += std::string((const char *)&pmsk, sizeof(crypto::public_key)); + } + + data.resize(data.size() + sizeof(crypto::signature)); + crypto::cn_fast_hash(data.data(), data.size() - sizeof(signature), hash); + crypto::signature &signature = *(crypto::signature*)&data[data.size() - sizeof(crypto::signature)]; + crypto::generate_signature(hash, signer, rct::rct2sk(spend_skey), signature); + + extra_multisig_info = std::string("MultisigxV1") + tools::base58::encode(data); + } + else + { + CHECK_AND_ASSERT_THROW_MES(false, "Unsupported threshold case"); + } + + // the multisig view key is shared by all, make one all can derive + MINFO("Creating view key..."); + crypto::secret_key view_skey = cryptonote::generate_multisig_view_secret_key(get_account().get_keys().m_view_secret_key, view_keys); + + MINFO("Creating multisig address..."); + CHECK_AND_ASSERT_THROW_MES(m_account.make_multisig(view_skey, rct::rct2sk(spend_skey), rct::rct2pk(spend_pkey), multisig_keys), + "Failed to create multisig wallet due to bad keys"); - r = file_io_utils::save_string_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_testnet)); - if(!r) MERROR("String with address text not saved"); + m_account_public_address = m_account.get_keys().m_account_address; + m_watch_only = false; + m_multisig = true; + m_multisig_threshold = threshold; + if (threshold == spend_keys.size() + 1) + { + m_multisig_signers = spend_keys; + m_multisig_signers.push_back(get_multisig_signer_public_key()); + } + else + { + m_multisig_signers = std::vector<crypto::public_key>(spend_keys.size() + 1, crypto::null_pkey); + } + + if (!m_wallet_file.empty()) + { + bool r = store_keys(m_keys_file, password, false); + THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); + + r = file_io_utils::save_string_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_testnet)); + if(!r) MERROR("String with address text not saved"); + } cryptonote::block b; generate_genesis(b); m_blockchain.push_back(get_block_hash(b)); add_subaddress_account(tr("Primary account")); - store(); + if (!m_wallet_file.empty()) + store(); + + return extra_multisig_info; +} + +std::string wallet2::make_multisig(const epee::wipeable_string &password, + const std::vector<std::string> &info, + uint32_t threshold) +{ + // parse all multisig info + std::vector<crypto::secret_key> secret_keys(info.size()); + std::vector<crypto::public_key> public_keys(info.size()); + for (size_t i = 0; i < info.size(); ++i) + { + THROW_WALLET_EXCEPTION_IF(!verify_multisig_info(info[i], secret_keys[i], public_keys[i]), + error::wallet_internal_error, "Bad multisig info: " + info[i]); + } + + // remove duplicates + for (size_t i = 0; i < secret_keys.size(); ++i) + { + for (size_t j = i + 1; j < secret_keys.size(); ++j) + { + if (rct::sk2rct(secret_keys[i]) == rct::sk2rct(secret_keys[j])) + { + MDEBUG("Duplicate key found, ignoring"); + secret_keys[j] = secret_keys.back(); + public_keys[j] = public_keys.back(); + secret_keys.pop_back(); + public_keys.pop_back(); + --j; + } + } + } + + // people may include their own, weed it out + const crypto::secret_key local_skey = cryptonote::get_multisig_blinded_secret_key(get_account().get_keys().m_view_secret_key); + const crypto::public_key local_pkey = get_multisig_signer_public_key(get_account().get_keys().m_spend_secret_key); + for (size_t i = 0; i < secret_keys.size(); ++i) + { + if (secret_keys[i] == local_skey) + { + MDEBUG("Local key is present, ignoring"); + secret_keys[i] = secret_keys.back(); + public_keys[i] = public_keys.back(); + secret_keys.pop_back(); + public_keys.pop_back(); + --i; + } + else + { + THROW_WALLET_EXCEPTION_IF(public_keys[i] == local_pkey, error::wallet_internal_error, + "Found local spend public key, but not local view secret key - something very weird"); + } + } + + return make_multisig(password, secret_keys, public_keys, threshold); +} + +bool wallet2::finalize_multisig(const epee::wipeable_string &password, std::unordered_set<crypto::public_key> pkeys, std::vector<crypto::public_key> signers) +{ + CHECK_AND_ASSERT_THROW_MES(!pkeys.empty(), "empty pkeys"); + + // add ours if not included + crypto::public_key local_signer; + CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(get_account().get_keys().m_spend_secret_key, local_signer), + "Failed to derive public spend key"); + if (std::find(signers.begin(), signers.end(), local_signer) == signers.end()) + { + signers.push_back(local_signer); + for (const auto &msk: get_account().get_multisig_keys()) + { + pkeys.insert(rct::rct2pk(rct::scalarmultBase(rct::sk2rct(msk)))); + } + } + + CHECK_AND_ASSERT_THROW_MES(signers.size() == m_multisig_signers.size(), "Bad signers size"); + + crypto::public_key spend_public_key = cryptonote::generate_multisig_N1_N_spend_public_key(std::vector<crypto::public_key>(pkeys.begin(), pkeys.end())); + m_account_public_address.m_spend_public_key = spend_public_key; + m_account.finalize_multisig(spend_public_key); + + m_multisig_signers = signers; + std::sort(m_multisig_signers.begin(), m_multisig_signers.end(), [](const crypto::public_key &e0, const crypto::public_key &e1){ return memcmp(&e0, &e1, sizeof(e0)); }); + + if (!m_wallet_file.empty()) + { + bool r = store_keys(m_keys_file, password, false); + THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); + + r = file_io_utils::save_string_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_testnet)); + if(!r) MERROR("String with address text not saved"); + } + + m_subaddresses.clear(); + m_subaddresses_inv.clear(); + m_subaddress_labels.clear(); + add_subaddress_account(tr("Primary account")); + + if (!m_wallet_file.empty()) + store(); + + return true; +} + +bool wallet2::finalize_multisig(const epee::wipeable_string &password, const std::vector<std::string> &info) +{ + // parse all multisig info + std::unordered_set<crypto::public_key> public_keys; + std::vector<crypto::public_key> signers(info.size(), crypto::null_pkey); + for (size_t i = 0; i < info.size(); ++i) + { + if (!verify_extra_multisig_info(info[i], public_keys, signers[i])) + { + MERROR("Bad multisig info"); + return false; + } + } + return finalize_multisig(password, public_keys, signers); +} + +std::string wallet2::get_multisig_info() const +{ + // It's a signed package of private view key and public spend key + const crypto::secret_key skey = cryptonote::get_multisig_blinded_secret_key(get_account().get_keys().m_view_secret_key); + const crypto::public_key pkey = get_multisig_signer_public_key(get_account().get_keys().m_spend_secret_key); + crypto::hash hash; + + std::string data; + data += std::string((const char *)&skey, sizeof(crypto::secret_key)); + data += std::string((const char *)&pkey, sizeof(crypto::public_key)); + + data.resize(data.size() + sizeof(crypto::signature)); + crypto::cn_fast_hash(data.data(), data.size() - sizeof(signature), hash); + crypto::signature &signature = *(crypto::signature*)&data[data.size() - sizeof(crypto::signature)]; + crypto::generate_signature(hash, pkey, get_multisig_blinded_secret_key(get_account().get_keys().m_spend_secret_key), signature); + + return std::string("MultisigV1") + tools::base58::encode(data); +} + +bool wallet2::verify_multisig_info(const std::string &data, crypto::secret_key &skey, crypto::public_key &pkey) +{ + const size_t header_len = strlen("MultisigV1"); + if (data.size() < header_len || data.substr(0, header_len) != "MultisigV1") + { + MERROR("Multisig info header check error"); + return false; + } + std::string decoded; + if (!tools::base58::decode(data.substr(header_len), decoded)) + { + MERROR("Multisig info decoding error"); + return false; + } + if (decoded.size() != sizeof(crypto::secret_key) + sizeof(crypto::public_key) + sizeof(crypto::signature)) + { + MERROR("Multisig info is corrupt"); + return false; + } + + size_t offset = 0; + skey = *(const crypto::secret_key*)(decoded.data() + offset); + offset += sizeof(skey); + pkey = *(const crypto::public_key*)(decoded.data() + offset); + offset += sizeof(pkey); + const crypto::signature &signature = *(const crypto::signature*)(decoded.data() + offset); + + crypto::hash hash; + crypto::cn_fast_hash(decoded.data(), decoded.size() - sizeof(signature), hash); + if (!crypto::check_signature(hash, pkey, signature)) + { + MERROR("Multisig info signature is invalid"); + return false; + } + + return true; +} + +bool wallet2::verify_extra_multisig_info(const std::string &data, std::unordered_set<crypto::public_key> &pkeys, crypto::public_key &signer) +{ + const size_t header_len = strlen("MultisigxV1"); + if (data.size() < header_len || data.substr(0, header_len) != "MultisigxV1") + { + MERROR("Multisig info header check error"); + return false; + } + std::string decoded; + if (!tools::base58::decode(data.substr(header_len), decoded)) + { + MERROR("Multisig info decoding error"); + return false; + } + if (decoded.size() < sizeof(crypto::public_key) + sizeof(crypto::signature)) + { + MERROR("Multisig info is corrupt"); + return false; + } + if ((decoded.size() - (sizeof(crypto::public_key) + sizeof(crypto::signature))) % sizeof(crypto::public_key)) + { + MERROR("Multisig info is corrupt"); + return false; + } + + const size_t n_keys = (decoded.size() - (sizeof(crypto::public_key) + sizeof(crypto::signature))) / sizeof(crypto::public_key); + size_t offset = 0; + signer = *(const crypto::public_key*)(decoded.data() + offset); + offset += sizeof(signer); + const crypto::signature &signature = *(const crypto::signature*)(decoded.data() + offset + n_keys * sizeof(crypto::public_key)); + + crypto::hash hash; + crypto::cn_fast_hash(decoded.data(), decoded.size() - sizeof(signature), hash); + if (!crypto::check_signature(hash, signer, signature)) + { + MERROR("Multisig info signature is invalid"); + return false; + } + + for (size_t n = 0; n < n_keys; ++n) + { + crypto::public_key mspk = *(const crypto::public_key*)(decoded.data() + offset); + pkeys.insert(mspk); + offset += sizeof(mspk); + } + + return true; +} + +bool wallet2::multisig(bool *ready, uint32_t *threshold, uint32_t *total) const +{ + if (!m_multisig) + return false; + if (threshold) + *threshold = m_multisig_threshold; + if (total) + *total = m_multisig_signers.size(); + if (ready) + *ready = !(get_account().get_keys().m_account_address.m_spend_public_key == rct::rct2pk(rct::identity())); + return true; +} + +bool wallet2::has_multisig_partial_key_images() const +{ + if (!m_multisig) + return false; + for (const auto &td: m_transfers) + if (td.m_key_image_partial) + return true; + return false; } /*! @@ -2627,6 +3171,8 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& */ void wallet2::rewrite(const std::string& wallet_name, const epee::wipeable_string& password) { + if (wallet_name.empty()) + return; prepare_file_names(wallet_name); boost::system::error_code ignored_ec; THROW_WALLET_EXCEPTION_IF(!boost::filesystem::exists(m_keys_file, ignored_ec), error::file_not_found, m_keys_file); @@ -2760,7 +3306,7 @@ bool wallet2::check_connection(uint32_t *version, uint32_t timeout) return true; } //---------------------------------------------------------------------------------------------------- -bool wallet2::generate_chacha8_key_from_secret_keys(crypto::chacha8_key &key) const +bool wallet2::generate_chacha_key_from_secret_keys(crypto::chacha_key &key) const { const account_keys &keys = m_account.get_keys(); const crypto::secret_key &view_key = keys.m_view_secret_key; @@ -2769,7 +3315,7 @@ bool wallet2::generate_chacha8_key_from_secret_keys(crypto::chacha8_key &key) co memcpy(data.data(), &view_key, sizeof(view_key)); memcpy(data.data() + sizeof(view_key), &spend_key, sizeof(spend_key)); data[sizeof(data) - 1] = CHACHA8_KEY_TAIL; - crypto::generate_chacha8_key(data.data(), sizeof(data), key); + crypto::generate_chacha_key(data.data(), sizeof(data), key); return true; } //---------------------------------------------------------------------------------------------------- @@ -2809,34 +3355,46 @@ void wallet2::load(const std::string& wallet_, const epee::wipeable_string& pass r = ::serialization::parse_binary(buf, cache_file_data); THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "internal error: failed to deserialize \"" + m_wallet_file + '\"'); - crypto::chacha8_key key; - generate_chacha8_key_from_secret_keys(key); + crypto::chacha_key key; + generate_chacha_key_from_secret_keys(key); std::string cache_data; cache_data.resize(cache_file_data.cache_data.size()); - crypto::chacha8(cache_file_data.cache_data.data(), cache_file_data.cache_data.size(), key, cache_file_data.iv, &cache_data[0]); + crypto::chacha20(cache_file_data.cache_data.data(), cache_file_data.cache_data.size(), key, cache_file_data.iv, &cache_data[0]); - std::stringstream iss; - iss << cache_data; try { + std::stringstream iss; + iss << cache_data; boost::archive::portable_binary_iarchive ar(iss); ar >> *this; } catch (...) { - LOG_PRINT_L0("Failed to open portable binary, trying unportable"); - boost::filesystem::copy_file(m_wallet_file, m_wallet_file + ".unportable", boost::filesystem::copy_option::overwrite_if_exists); - iss.str(""); - iss << cache_data; - boost::archive::binary_iarchive ar(iss); - ar >> *this; + crypto::chacha8(cache_file_data.cache_data.data(), cache_file_data.cache_data.size(), key, cache_file_data.iv, &cache_data[0]); + try + { + std::stringstream iss; + iss << cache_data; + boost::archive::portable_binary_iarchive ar(iss); + ar >> *this; + } + catch (...) + { + LOG_PRINT_L0("Failed to open portable binary, trying unportable"); + boost::filesystem::copy_file(m_wallet_file, m_wallet_file + ".unportable", boost::filesystem::copy_option::overwrite_if_exists); + std::stringstream iss; + iss.str(""); + iss << cache_data; + boost::archive::binary_iarchive ar(iss); + ar >> *this; + } } } catch (...) { LOG_PRINT_L1("Failed to load encrypted cache, trying unencrypted"); - std::stringstream iss; - iss << buf; try { + std::stringstream iss; + iss << buf; boost::archive::portable_binary_iarchive ar(iss); ar >> *this; } @@ -2844,6 +3402,7 @@ void wallet2::load(const std::string& wallet_, const epee::wipeable_string& pass { LOG_PRINT_L0("Failed to open portable binary, trying unportable"); boost::filesystem::copy_file(m_wallet_file, m_wallet_file + ".unportable", boost::filesystem::copy_option::overwrite_if_exists); + std::stringstream iss; iss.str(""); iss << buf; boost::archive::binary_iarchive ar(iss); @@ -2968,12 +3527,12 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas wallet2::cache_file_data cache_file_data = boost::value_initialized<wallet2::cache_file_data>(); cache_file_data.cache_data = oss.str(); - crypto::chacha8_key key; - generate_chacha8_key_from_secret_keys(key); + crypto::chacha_key key; + generate_chacha_key_from_secret_keys(key); std::string cipher; cipher.resize(cache_file_data.cache_data.size()); - cache_file_data.iv = crypto::rand<crypto::chacha8_iv>(); - crypto::chacha8(cache_file_data.cache_data.data(), cache_file_data.cache_data.size(), key, cache_file_data.iv, &cipher[0]); + cache_file_data.iv = crypto::rand<crypto::chacha_iv>(); + crypto::chacha20(cache_file_data.cache_data.data(), cache_file_data.cache_data.size(), key, cache_file_data.iv, &cipher[0]); cache_file_data.cache_data = cipher; const std::string new_file = same_file ? m_wallet_file + ".new" : path; @@ -2981,14 +3540,6 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas const std::string old_keys_file = m_keys_file; const std::string old_address_file = m_wallet_file + ".address.txt"; - // save to new file - std::ofstream ostr; - ostr.open(new_file, std::ios_base::binary | std::ios_base::out | std::ios_base::trunc); - binary_archive<true> oar(ostr); - bool success = ::serialization::serialize(oar, cache_file_data); - ostr.close(); - THROW_WALLET_EXCEPTION_IF(!success || !ostr.good(), error::file_save_error, new_file); - // save keys to the new file // if we here, main wallet file is saved and we only need to save keys and address files if (!same_file) { @@ -3015,6 +3566,14 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas LOG_ERROR("error removing file: " << old_address_file); } } else { + // save to new file + std::ofstream ostr; + ostr.open(new_file, std::ios_base::binary | std::ios_base::out | std::ios_base::trunc); + binary_archive<true> oar(ostr); + bool success = ::serialization::serialize(oar, cache_file_data); + ostr.close(); + THROW_WALLET_EXCEPTION_IF(!success || !ostr.good(), error::file_save_error, new_file); + // here we have "*.new" file, we need to rename it to be without ".new" std::error_code e = tools::replace_file(new_file, m_wallet_file); THROW_WALLET_EXCEPTION_IF(e, error::file_save_error, m_wallet_file, e); @@ -3200,7 +3759,7 @@ void wallet2::rescan_spent() { transfer_details& td = m_transfers[i]; // a view wallet may not know about key images - if (!td.m_key_image_known) + if (!td.m_key_image_known || td.m_key_image_partial) continue; if (td.m_spent != (spent_status[i] != COMMAND_RPC_IS_KEY_IMAGE_SPENT::UNSPENT)) { @@ -3527,6 +4086,11 @@ crypto::hash wallet2::get_payment_id(const pending_tx &ptx) const crypto::hash8 payment_id8 = null_hash8; if(get_encrypted_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id8)) { + if (ptx.dests.empty()) + { + MWARNING("Encrypted payment id found, but no destinations public key, cannot decrypt"); + return crypto::null_hash; + } if (decrypt_payment_id(payment_id8, ptx.dests[0].addr.m_view_public_key, ptx.tx_key)) { memcpy(payment_id.data, payment_id8.data, 8); @@ -3540,23 +4104,6 @@ crypto::hash wallet2::get_payment_id(const pending_tx &ptx) const return payment_id; } -crypto::hash8 wallet2::get_short_payment_id(const pending_tx &ptx) const -{ - crypto::hash8 payment_id8 = null_hash8; - std::vector<tx_extra_field> tx_extra_fields; - if(!parse_tx_extra(ptx.tx.extra, tx_extra_fields)) - return payment_id8; - cryptonote::tx_extra_nonce extra_nonce; - if (find_tx_extra_field_by_type(tx_extra_fields, extra_nonce)) - { - if(get_encrypted_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id8)) - { - decrypt_payment_id(payment_id8, ptx.dests[0].addr.m_view_public_key, ptx.tx_key); - } - } - return payment_id8; -} - //---------------------------------------------------------------------------------------------------- // take a pending tx and actually send it to the daemon void wallet2::commit_tx(pending_tx& ptx) @@ -3624,6 +4171,10 @@ void wallet2::commit_tx(pending_tx& ptx) set_spent(idx, 0); } + // tx generated, get rid of used k values + for (size_t idx: ptx.selected_transfers) + m_transfers[idx].m_multisig_k.clear(); + //fee includes dust if dust policy specified it. LOG_PRINT_L1("Transaction successfully sent. <" << txid << ">" << ENDL << "Commission: " << print_money(ptx.fee) << " (dust sent to dust addr: " << print_money((ptx.dust_added_to_fee ? 0 : ptx.dust)) << ")" << ENDL @@ -3646,27 +4197,10 @@ bool wallet2::save_tx(const std::vector<pending_tx>& ptx_vector, const std::stri unsigned_tx_set txs; for (auto &tx: ptx_vector) { - tx_construction_data construction_data = tx.construction_data; // Short payment id is encrypted with tx_key. // Since sign_tx() generates new tx_keys and encrypts the payment id, we need to save the decrypted payment ID - // Get decrypted payment id from pending_tx - crypto::hash8 payment_id = get_short_payment_id(tx); - if (payment_id != null_hash8) - { - // Remove encrypted - remove_field_from_tx_extra(construction_data.extra, typeid(cryptonote::tx_extra_nonce)); - // Add decrypted - std::string extra_nonce; - set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, payment_id); - if (!add_extra_nonce_to_tx_extra(construction_data.extra, extra_nonce)) - { - LOG_ERROR("Failed to add decrypted payment id to tx extra"); - return false; - } - LOG_PRINT_L1("Decrypted payment ID: " << payment_id); - } // Save tx construction_data to unsigned_tx_set - txs.txes.push_back(construction_data); + txs.txes.push_back(get_construction_data_with_decrypted_short_payment_id(tx)); } txs.transfers = m_transfers; @@ -3781,13 +4315,15 @@ bool wallet2::sign_tx(unsigned_tx_set &exported_txs, const std::string &signed_f for (size_t n = 0; n < exported_txs.txes.size(); ++n) { tools::wallet2::tx_construction_data &sd = exported_txs.txes[n]; + THROW_WALLET_EXCEPTION_IF(sd.sources.empty(), error::wallet_internal_error, "Empty sources"); LOG_PRINT_L1(" " << (n+1) << ": " << sd.sources.size() << " inputs, ring size " << sd.sources[0].outputs.size()); signed_txes.ptx.push_back(pending_tx()); tools::wallet2::pending_tx &ptx = signed_txes.ptx.back(); bool bulletproof = sd.use_rct && !ptx.tx.rct_signatures.p.bulletproofs.empty(); crypto::secret_key tx_key; std::vector<crypto::secret_key> additional_tx_keys; - 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, bulletproof); + 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, bulletproof, m_multisig ? &msout : NULL); THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sd.sources, sd.splitted_dsts, sd.unlock_time, m_testnet); // 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, @@ -3832,7 +4368,7 @@ bool wallet2::sign_tx(unsigned_tx_set &exported_txs, const std::string &signed_f signed_txes.key_images.resize(m_transfers.size()); for (size_t i = 0; i < m_transfers.size(); ++i) { - if (!m_transfers[i].m_key_image_known) + if (!m_transfers[i].m_key_image_known || m_transfers[i].m_key_image_partial) LOG_PRINT_L0("WARNING: key image not known in signing wallet at index " << i); signed_txes.key_images[i] = m_transfers[i].m_key_image; } @@ -3958,11 +4494,12 @@ bool wallet2::load_tx(const std::string &signed_filename, std::vector<tools::wal for (size_t i = 0; i < signed_txs.key_images.size(); ++i) { transfer_details &td = m_transfers[i]; - if (td.m_key_image_known && td.m_key_image != signed_txs.key_images[i]) + if (td.m_key_image_known && !td.m_key_image_partial && td.m_key_image != signed_txs.key_images[i]) LOG_PRINT_L0("WARNING: imported key image differs from previously known key image at index " << i << ": trusting imported one"); td.m_key_image = signed_txs.key_images[i]; m_key_images[m_transfers[i].m_key_image] = i; td.m_key_image_known = true; + td.m_key_image_partial = false; m_pub_keys[m_transfers[i].get_public_key()] = i; } @@ -3971,6 +4508,292 @@ bool wallet2::load_tx(const std::string &signed_filename, std::vector<tools::wal return true; } //---------------------------------------------------------------------------------------------------- +std::string wallet2::save_multisig_tx(multisig_tx_set txs) +{ + LOG_PRINT_L0("saving " << txs.m_ptx.size() << " multisig transactions"); + + // txes generated, get rid of used k values + for (size_t n = 0; n < txs.m_ptx.size(); ++n) + for (size_t idx: txs.m_ptx[n].construction_data.selected_transfers) + m_transfers[idx].m_multisig_k.clear(); + + // zero out some data we don't want to share + for (auto &ptx: txs.m_ptx) + { + for (auto &e: ptx.construction_data.sources) + e.multisig_kLRki.k = rct::zero(); + } + + for (auto &ptx: txs.m_ptx) + { + // Get decrypted payment id from pending_tx + ptx.construction_data = get_construction_data_with_decrypted_short_payment_id(ptx); + } + + // save as binary + std::ostringstream oss; + boost::archive::portable_binary_oarchive ar(oss); + try + { + ar << txs; + } + catch (...) + { + return std::string(); + } + LOG_PRINT_L2("Saving multisig unsigned tx data: " << oss.str()); + std::string ciphertext = encrypt_with_view_secret_key(oss.str()); + return std::string(MULTISIG_UNSIGNED_TX_PREFIX) + ciphertext; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::save_multisig_tx(const multisig_tx_set &txs, const std::string &filename) +{ + std::string ciphertext = save_multisig_tx(txs); + if (ciphertext.empty()) + return false; + return epee::file_io_utils::save_string_to_file(filename, ciphertext); +} +//---------------------------------------------------------------------------------------------------- +std::string wallet2::save_multisig_tx(const std::vector<pending_tx>& ptx_vector) +{ + multisig_tx_set txs; + txs.m_ptx = ptx_vector; + + for (const auto &msk: get_account().get_multisig_keys()) + { + crypto::public_key pkey = get_multisig_signing_public_key(msk); + for (auto &ptx: txs.m_ptx) for (auto &sig: ptx.multisig_sigs) sig.signing_keys.insert(pkey); + } + + txs.m_signers.insert(get_multisig_signer_public_key()); + + return save_multisig_tx(txs); +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::save_multisig_tx(const std::vector<pending_tx>& ptx_vector, const std::string &filename) +{ + std::string ciphertext = save_multisig_tx(ptx_vector); + if (ciphertext.empty()) + return false; + return epee::file_io_utils::save_string_to_file(filename, ciphertext); +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::load_multisig_tx(cryptonote::blobdata s, multisig_tx_set &exported_txs, std::function<bool(const multisig_tx_set&)> accept_func) +{ + const size_t magiclen = strlen(MULTISIG_UNSIGNED_TX_PREFIX); + if (strncmp(s.c_str(), MULTISIG_UNSIGNED_TX_PREFIX, magiclen)) + { + LOG_PRINT_L0("Bad magic from multisig tx data"); + return false; + } + try + { + s = decrypt_with_view_secret_key(std::string(s, magiclen)); + } + catch (const std::exception &e) + { + LOG_PRINT_L0("Failed to decrypt multisig tx data: " << e.what()); + return false; + } + try + { + std::istringstream iss(s); + boost::archive::portable_binary_iarchive ar(iss); + ar >> exported_txs; + } + catch (...) + { + LOG_PRINT_L0("Failed to parse multisig tx data"); + return false; + } + + // sanity checks + for (const auto &ptx: exported_txs.m_ptx) + { + CHECK_AND_ASSERT_MES(ptx.selected_transfers.size() == ptx.tx.vin.size(), false, "Mismatched selected_transfers/vin sizes"); + for (size_t idx: ptx.selected_transfers) + CHECK_AND_ASSERT_MES(idx < m_transfers.size(), false, "Transfer index out of range"); + CHECK_AND_ASSERT_MES(ptx.construction_data.selected_transfers.size() == ptx.tx.vin.size(), false, "Mismatched cd selected_transfers/vin sizes"); + for (size_t idx: ptx.construction_data.selected_transfers) + CHECK_AND_ASSERT_MES(idx < m_transfers.size(), false, "Transfer index out of range"); + CHECK_AND_ASSERT_MES(ptx.construction_data.sources.size() == ptx.tx.vin.size(), false, "Mismatched sources/vin sizes"); + } + + LOG_PRINT_L1("Loaded multisig tx unsigned data from binary: " << exported_txs.m_ptx.size() << " transactions"); + for (auto &ptx: exported_txs.m_ptx) LOG_PRINT_L0(cryptonote::obj_to_json_str(ptx.tx)); + + if (accept_func && !accept_func(exported_txs)) + { + LOG_PRINT_L1("Transactions rejected by callback"); + return false; + } + + const bool is_signed = exported_txs.m_signers.size() >= m_multisig_threshold; + if (is_signed) + { + for (const auto &ptx: exported_txs.m_ptx) + { + const crypto::hash txid = get_transaction_hash(ptx.tx); + if (store_tx_info()) + { + m_tx_keys.insert(std::make_pair(txid, ptx.tx_key)); + m_additional_tx_keys.insert(std::make_pair(txid, ptx.additional_tx_keys)); + } + } + } + + return true; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::load_multisig_tx_from_file(const std::string &filename, multisig_tx_set &exported_txs, std::function<bool(const multisig_tx_set&)> accept_func) +{ + std::string s; + boost::system::error_code errcode; + + if (!boost::filesystem::exists(filename, errcode)) + { + LOG_PRINT_L0("File " << filename << " does not exist: " << errcode); + return false; + } + if (!epee::file_io_utils::load_file_to_string(filename.c_str(), s)) + { + LOG_PRINT_L0("Failed to load from " << filename); + return false; + } + + if (!load_multisig_tx(s, exported_txs, accept_func)) + { + LOG_PRINT_L0("Failed to parse multisig tx data from " << filename); + return false; + } + return true; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::sign_multisig_tx(multisig_tx_set &exported_txs, std::vector<crypto::hash> &txids) +{ + THROW_WALLET_EXCEPTION_IF(exported_txs.m_ptx.empty(), error::wallet_internal_error, "No tx found"); + + const crypto::public_key local_signer = get_multisig_signer_public_key(); + + THROW_WALLET_EXCEPTION_IF(exported_txs.m_signers.find(local_signer) != exported_txs.m_signers.end(), + error::wallet_internal_error, "Transaction already signed by this private key"); + THROW_WALLET_EXCEPTION_IF(exported_txs.m_signers.size() > m_multisig_threshold, + error::wallet_internal_error, "Transaction was signed by too many signers"); + THROW_WALLET_EXCEPTION_IF(exported_txs.m_signers.size() == m_multisig_threshold, + error::wallet_internal_error, "Transaction is already fully signed"); + + txids.clear(); + + // sign the transactions + 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) << + ", 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; + const bool bulletproof = sd.use_rct && (ptx.tx.rct_signatures.type == rct::RCTTypeFullBulletproof || ptx.tx.rct_signatures.type == rct::RCTTypeSimpleBulletproof); + 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, bulletproof, &msout); + THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sd.sources, sd.splitted_dsts, sd.unlock_time, m_testnet); + + 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); + + for (auto &sig: ptx.multisig_sigs) + { + if (sig.ignore != local_signer) + { + ptx.tx.rct_signatures = sig.sigs; + + rct::keyV k; + for (size_t idx: sd.selected_transfers) + k.push_back(get_multisig_k(idx, sig.used_L)); + + rct::key skey = rct::zero(); + for (const auto &msk: get_account().get_multisig_keys()) + { + crypto::public_key pmsk = get_multisig_signing_public_key(msk); + + if (sig.signing_keys.find(pmsk) == sig.signing_keys.end()) + { + sc_add(skey.bytes, skey.bytes, rct::sk2rct(msk).bytes); + sig.signing_keys.insert(pmsk); + } + } + 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; + } + } + + 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 + bool found = false; + for (const auto &sig: ptx.multisig_sigs) + { + if (sig.ignore != local_signer && exported_txs.m_signers.find(sig.ignore) == exported_txs.m_signers.end()) + { + THROW_WALLET_EXCEPTION_IF(found, error::wallet_internal_error, "More than one transaction is final"); + ptx.tx.rct_signatures = sig.sigs; + 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"); + const crypto::hash txid = get_transaction_hash(ptx.tx); + if (store_tx_info()) + { + m_tx_keys.insert(std::make_pair(txid, ptx.tx_key)); + m_additional_tx_keys.insert(std::make_pair(txid, ptx.additional_tx_keys)); + } + txids.push_back(txid); + } + } + + // txes generated, get rid of used k values + 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) + m_transfers[idx].m_multisig_k.clear(); + + exported_txs.m_signers.insert(get_multisig_signer_public_key()); + + return true; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::sign_multisig_tx_to_file(multisig_tx_set &exported_txs, const std::string &filename, std::vector<crypto::hash> &txids) +{ + bool r = sign_multisig_tx(exported_txs, txids); + if (!r) + return false; + return save_multisig_tx(exported_txs, filename); +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::sign_multisig_tx_from_file(const std::string &filename, std::vector<crypto::hash> &txids, std::function<bool(const multisig_tx_set&)> accept_func) +{ + multisig_tx_set exported_txs; + if(!load_multisig_tx_from_file(filename, exported_txs)) + return false; + + if (accept_func && !accept_func(exported_txs)) + { + LOG_PRINT_L1("Transactions rejected by callback"); + return false; + } + return sign_multisig_tx_to_file(exported_txs, filename, txids); +} +//---------------------------------------------------------------------------------------------------- uint64_t wallet2::get_fee_multiplier(uint32_t priority, int fee_algorithm) { static const uint64_t old_multipliers[3] = {1, 2, 3}; @@ -4169,6 +4992,7 @@ bool wallet2::tx_add_fake_output(std::vector<std::vector<tools::wallet2::get_out if (global_index == real_index) // don't re-add real one return false; auto item = std::make_tuple(global_index, tx_public_key, mask); + CHECK_AND_ASSERT_MES(!outs.empty(), false, "internal error: outs is empty"); if (std::find(outs.back().begin(), outs.back().end(), item) != outs.back().end()) // don't add duplicates return false; outs.back().push_back(item); @@ -4333,7 +5157,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> // if there are just enough outputs to mix with, use all of them. // Eventually this should become impossible. uint64_t num_outs = 0, num_recent_outs = 0; - for (auto he: resp_t.result.histogram) + for (const auto &he: resp_t.result.histogram) { if (he.amount == amount) { @@ -4522,6 +5346,8 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent // throw if attempting a transaction with no destinations THROW_WALLET_EXCEPTION_IF(dsts.empty(), error::zero_destination); + THROW_WALLET_EXCEPTION_IF(m_multisig, error::wallet_internal_error, "Multisig wallets cannot spend non rct outputs"); + uint64_t upper_transaction_size_limit = get_upper_transaction_size_limit(); uint64_t needed_money = fee; LOG_PRINT_L2("transfer: starting with fee " << print_money (needed_money)); @@ -4594,6 +5420,7 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent src.real_out_additional_tx_keys = get_additional_tx_pub_keys_from_extra(td.m_tx); src.real_output = it_to_replace - src.outputs.begin(); src.real_output_in_tx_index = td.m_internal_output_index; + src.multisig_kLRki = rct::multisig_kLRki({rct::zero(), rct::zero(), rct::zero(), rct::zero()}); detail::print_source_entry(src); ++out_index; } @@ -4621,8 +5448,9 @@ 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); + 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, false, m_multisig ? &msout : NULL); LOG_PRINT_L2("constructed tx, r="<<r); THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, splitted_dsts, unlock_time, m_testnet); THROW_WALLET_EXCEPTION_IF(upper_transaction_size_limit <= get_object_blobsize(tx), error::tx_too_big, tx, upper_transaction_size_limit); @@ -4691,6 +5519,36 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry THROW_WALLET_EXCEPTION_IF(needed_money < dt.amount, error::tx_sum_overflow, dsts, fee, m_testnet); } + // if this is a multisig wallet, create a list of multisig signers we can use + std::deque<crypto::public_key> multisig_signers; + size_t n_multisig_txes = 0; + if (m_multisig && !m_transfers.empty()) + { + const crypto::public_key local_signer = get_multisig_signer_public_key(); + size_t n_available_signers = 1; + for (const crypto::public_key &signer: m_multisig_signers) + { + if (signer == local_signer) + continue; + multisig_signers.push_front(signer); + for (const auto &i: m_transfers[0].m_multisig_info) + { + if (i.m_signer == signer) + { + multisig_signers.pop_front(); + multisig_signers.push_back(signer); + ++n_available_signers; + break; + } + } + } + multisig_signers.push_back(local_signer); + MDEBUG("We can use " << n_available_signers << "/" << m_multisig_signers.size() << " other signers"); + THROW_WALLET_EXCEPTION_IF(n_available_signers+1 < m_multisig_threshold, error::multisig_import_needed); + n_multisig_txes = n_available_signers == m_multisig_signers.size() ? m_multisig_threshold : 1; + MDEBUG("We will create " << n_multisig_txes << " txes"); + } + uint64_t found_money = 0; for(size_t idx: selected_transfers) { @@ -4711,6 +5569,7 @@ 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); @@ -4752,6 +5611,13 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry src.real_output = it_to_replace - src.outputs.begin(); src.real_output_in_tx_index = td.m_internal_output_index; src.mask = td.m_mask; + if (m_multisig) + { + crypto::public_key ignore = m_multisig_threshold == m_multisig_signers.size() ? crypto::null_pkey : multisig_signers.front(); + src.multisig_kLRki = get_multisig_composite_kLRki(idx, ignore, used_L, used_L); + } + else + src.multisig_kLRki = rct::multisig_kLRki({rct::zero(), rct::zero(), rct::zero(), rct::zero()}); detail::print_source_entry(src); ++out_index; } @@ -4785,12 +5651,67 @@ 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; 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, true, bulletproof); + 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, bulletproof, m_multisig ? &msout : NULL); LOG_PRINT_L2("constructed tx, r="<<r); THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, dsts, unlock_time, m_testnet); THROW_WALLET_EXCEPTION_IF(upper_transaction_size_limit <= get_object_blobsize(tx), error::tx_too_big, tx, upper_transaction_size_limit); + // work out the permutation done on sources + std::vector<size_t> ins_order; + for (size_t n = 0; n < sources.size(); ++n) + { + for (size_t idx = 0; idx < sources_copy.size(); ++idx) + { + THROW_WALLET_EXCEPTION_IF((size_t)sources_copy[idx].real_output >= sources_copy[idx].outputs.size(), + error::wallet_internal_error, "Invalid real_output"); + if (sources_copy[idx].outputs[sources_copy[idx].real_output].second.dest == sources[n].outputs[sources[n].real_output].second.dest) + ins_order.push_back(idx); + } + } + 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) + { + crypto::public_key ignore = m_multisig_threshold == m_multisig_signers.size() ? crypto::null_pkey : multisig_signers.front(); + multisig_sigs.push_back({tx.rct_signatures, ignore, used_L, {}, 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 signer_index = 1; signer_index < n_multisig_txes; ++signer_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[src_idx]; + src.multisig_kLRki = get_multisig_composite_kLRki(idx, multisig_signers[signer_index], used_L, new_used_L); + ++src_idx; + } + + LOG_PRINT_L2("Creating supplementary multisig transaction"); + cryptonote::transaction ms_tx; + auto sources_copy_copy = sources_copy; + 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, bulletproof, &msout); + LOG_PRINT_L2("constructed tx, r="<<r); + THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, splitted_dsts, unlock_time, m_testnet); + THROW_WALLET_EXCEPTION_IF(upper_transaction_size_limit <= get_object_blobsize(tx), error::tx_too_big, tx, upper_transaction_size_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, multisig_signers[signer_index], new_used_L, {}, 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"); + } + } + } + LOG_PRINT_L2("gathering key images"); std::string key_images; bool all_are_txin_to_key = std::all_of(tx.vin.begin(), tx.vin.end(), [&](const txin_v& s_e) -> bool @@ -4809,13 +5730,15 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry ptx.tx = tx; ptx.change_dts = change_dts; ptx.selected_transfers = selected_transfers; + tools::apply_permutation(ins_order, ptx.selected_transfers); ptx.tx_key = tx_key; ptx.additional_tx_keys = additional_tx_keys; ptx.dests = dsts; - ptx.construction_data.sources = sources; + ptx.multisig_sigs = multisig_sigs; + ptx.construction_data.sources = sources_copy; ptx.construction_data.change_dts = change_dts; ptx.construction_data.splitted_dsts = splitted_dsts; - ptx.construction_data.selected_transfers = selected_transfers; + ptx.construction_data.selected_transfers = ptx.selected_transfers; ptx.construction_data.extra = tx.extra; ptx.construction_data.unlock_time = unlock_time; ptx.construction_data.use_rct = true; @@ -4835,20 +5758,32 @@ std::vector<size_t> wallet2::pick_preferred_rct_inputs(uint64_t needed_money, ui LOG_PRINT_L2("pick_preferred_rct_inputs: needed_money " << print_money(needed_money)); - // try to find two outputs + // try to find a rct input of enough size + for (size_t i = 0; i < m_transfers.size(); ++i) + { + const transfer_details& td = m_transfers[i]; + if (!td.m_spent && td.is_rct() && td.amount() >= needed_money && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1) + { + LOG_PRINT_L2("We can use " << i << " alone: " << print_money(td.amount())); + picks.push_back(i); + return picks; + } + } + + // then try to find two outputs // this could be made better by picking one of the outputs to be a small one, since those // are less useful since often below the needed money, so if one can be used in a pair, // it gets rid of it for the future for (size_t i = 0; i < m_transfers.size(); ++i) { const transfer_details& td = m_transfers[i]; - if (!td.m_spent && td.is_rct() && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1) + if (!td.m_spent && !td.m_key_image_partial && td.is_rct() && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1) { LOG_PRINT_L2("Considering input " << i << ", " << print_money(td.amount())); for (size_t j = i + 1; j < m_transfers.size(); ++j) { const transfer_details& td2 = m_transfers[j]; - if (!td2.m_spent && td2.is_rct() && td.amount() + td2.amount() >= needed_money && is_transfer_unlocked(td2) && td2.m_subaddr_index == td.m_subaddr_index) + if (!td2.m_spent && !td.m_key_image_partial && td2.is_rct() && td.amount() + td2.amount() >= needed_money && is_transfer_unlocked(td2) && td2.m_subaddr_index == td.m_subaddr_index) { // update our picks if those outputs are less related than any we // already found. If the same, don't update, and oldest suitable outputs @@ -5343,7 +6278,8 @@ bool wallet2::light_wallet_parse_rct_str(const std::string& rct_string, const cr if (decrypt) { // Decrypt the mask crypto::key_derivation derivation; - generate_key_derivation(tx_pub_key, get_account().get_keys().m_view_secret_key, derivation); + bool r = generate_key_derivation(tx_pub_key, get_account().get_keys().m_view_secret_key, derivation); + THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key derivation"); crypto::secret_key scalar; crypto::derivation_to_scalar(derivation, internal_output_index, scalar); sc_sub(decrypted_mask.bytes,encrypted_mask.bytes,rct::hash_to_scalar(rct::sk2rct(scalar)).bytes); @@ -5510,7 +6446,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp for (size_t i = 0; i < m_transfers.size(); ++i) { const transfer_details& td = m_transfers[i]; - if (!td.m_spent && (use_rct ? true : !td.is_rct()) && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1) + if (!td.m_spent && !td.m_key_image_partial && (use_rct ? true : !td.is_rct()) && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1) { const uint32_t index_minor = td.m_subaddr_index.minor; auto find_predicate = [&index_minor](const std::pair<uint32_t, std::vector<size_t>>& x) { return x.first == index_minor; }; @@ -5752,10 +6688,10 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp auto txBlob = t_serializable_object_to_blob(test_ptx.tx); needed_fee = calculate_fee(fee_per_kb, txBlob, fee_multiplier); available_for_fee = test_ptx.fee + test_ptx.change_dts.amount + (!test_ptx.dust_added_to_fee ? test_ptx.dust : 0); - LOG_PRINT_L2("Made a " << ((txBlob.size() + 1023) / 1024) << " kB tx, with " << print_money(available_for_fee) << " available for fee (" << + LOG_PRINT_L2("Made a " << get_size_string(txBlob) << " tx, with " << print_money(available_for_fee) << " available for fee (" << print_money(needed_fee) << " needed)"); - if (needed_fee > available_for_fee && dsts[0].amount > 0) + if (needed_fee > available_for_fee && !dsts.empty() && dsts[0].amount > 0) { // we don't have enough for the fee, but we've only partially paid the current address, // so we can take the fee from the paid amount, since we'll have to make another tx anyway @@ -5794,11 +6730,11 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx); txBlob = t_serializable_object_to_blob(test_ptx.tx); needed_fee = calculate_fee(fee_per_kb, txBlob, fee_multiplier); - LOG_PRINT_L2("Made an attempt at a final " << ((txBlob.size() + 1023)/1024) << " kB tx, with " << print_money(test_ptx.fee) << + LOG_PRINT_L2("Made an attempt at a final " << get_size_string(txBlob) << " tx, with " << print_money(test_ptx.fee) << " fee and " << print_money(test_ptx.change_dts.amount) << " change"); } - LOG_PRINT_L2("Made a final " << ((txBlob.size() + 1023)/1024) << " kB tx, with " << print_money(test_ptx.fee) << + LOG_PRINT_L2("Made a final " << get_size_string(txBlob) << " tx, with " << print_money(test_ptx.fee) << " fee and " << print_money(test_ptx.change_dts.amount) << " change"); tx.tx = test_tx; @@ -5850,7 +6786,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp for (size_t idx: tx.selected_transfers) tx_money += m_transfers[idx].amount(); LOG_PRINT_L1(" Transaction " << (1+std::distance(txes.begin(), i)) << "/" << txes.size() << - ": " << (tx.bytes+1023)/1024 << " kB, sending " << print_money(tx_money) << " in " << tx.selected_transfers.size() << + ": " << get_size_string(tx.bytes) << ", sending " << print_money(tx_money) << " in " << tx.selected_transfers.size() << " outputs to " << tx.dsts.size() << " destination(s), including " << print_money(tx.ptx.fee) << " fee, " << print_money(tx.ptx.change_dts.amount) << " change"); ptx_vector.push_back(tx.ptx); @@ -5886,7 +6822,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_all(uint64_t below for (size_t i = 0; i < m_transfers.size(); ++i) { const transfer_details& td = m_transfers[i]; - if (!td.m_spent && (use_rct ? true : !td.is_rct()) && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1) + if (!td.m_spent && !td.m_key_image_partial && (use_rct ? true : !td.is_rct()) && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1) { if (below == 0 || td.amount() < below) { @@ -6002,7 +6938,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton auto txBlob = t_serializable_object_to_blob(test_ptx.tx); needed_fee = calculate_fee(fee_per_kb, txBlob, fee_multiplier); available_for_fee = test_ptx.fee + test_ptx.dests[0].amount + test_ptx.change_dts.amount; - LOG_PRINT_L2("Made a " << ((txBlob.size() + 1023) / 1024) << " kB tx, with " << print_money(available_for_fee) << " available for fee (" << + LOG_PRINT_L2("Made a " << get_size_string(txBlob) << " tx, with " << print_money(available_for_fee) << " available for fee (" << print_money(needed_fee) << " needed)"); THROW_WALLET_EXCEPTION_IF(needed_fee > available_for_fee, error::wallet_internal_error, "Transaction cannot pay for itself"); @@ -6018,11 +6954,11 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx); txBlob = t_serializable_object_to_blob(test_ptx.tx); needed_fee = calculate_fee(fee_per_kb, txBlob, fee_multiplier); - LOG_PRINT_L2("Made an attempt at a final " << ((txBlob.size() + 1023)/1024) << " kB tx, with " << print_money(test_ptx.fee) << + LOG_PRINT_L2("Made an attempt at a final " << get_size_string(txBlob) << " tx, with " << print_money(test_ptx.fee) << " fee and " << print_money(test_ptx.change_dts.amount) << " change"); } - LOG_PRINT_L2("Made a final " << ((txBlob.size() + 1023)/1024) << " kB tx, with " << print_money(test_ptx.fee) << + LOG_PRINT_L2("Made a final " << get_size_string(txBlob) << " tx, with " << print_money(test_ptx.fee) << " fee and " << print_money(test_ptx.change_dts.amount) << " change"); tx.tx = test_tx; @@ -6049,7 +6985,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton for (size_t idx: tx.selected_transfers) tx_money += m_transfers[idx].amount(); LOG_PRINT_L1(" Transaction " << (1+std::distance(txes.begin(), i)) << "/" << txes.size() << - ": " << (tx.bytes+1023)/1024 << " kB, sending " << print_money(tx_money) << " in " << tx.selected_transfers.size() << + ": " << get_size_string(tx.bytes) << ", sending " << print_money(tx_money) << " in " << tx.selected_transfers.size() << " outputs to " << tx.dsts.size() << " destination(s), including " << print_money(tx.ptx.fee) << " fee, " << print_money(tx.ptx.change_dts.amount) << " change"); ptx_vector.push_back(tx.ptx); @@ -6100,6 +7036,8 @@ std::vector<size_t> wallet2::select_available_outputs(const std::function<bool(c { if (i->m_spent) continue; + if (i->m_key_image_partial) + continue; if (!is_transfer_unlocked(*i)) continue; if (f(*i)) @@ -6531,12 +7469,14 @@ void wallet2::check_tx_key_helper(const crypto::hash &txid, const crypto::key_de continue; crypto::public_key derived_out_key; - derive_public_key(derivation, n, address.m_spend_public_key, derived_out_key); + bool r = derive_public_key(derivation, n, address.m_spend_public_key, derived_out_key); + THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to derive public key"); bool found = out_key->key == derived_out_key; crypto::key_derivation found_derivation = derivation; if (!found && !additional_derivations.empty()) { - derive_public_key(additional_derivations[n], n, address.m_spend_public_key, derived_out_key); + r = derive_public_key(additional_derivations[n], n, address.m_spend_public_key, derived_out_key); + THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to derive public key"); found = out_key->key == derived_out_key; found_derivation = additional_derivations[n]; } @@ -6936,6 +7876,46 @@ std::string wallet2::get_description() const return get_attribute(ATTRIBUTE_DESCRIPTION); } +const std::pair<std::map<std::string, std::string>, std::vector<std::string>>& wallet2::get_account_tags() +{ + // ensure consistency + if (m_account_tags.second.size() != get_num_subaddress_accounts()) + m_account_tags.second.resize(get_num_subaddress_accounts(), ""); + for (const std::string& tag : m_account_tags.second) + { + if (!tag.empty() && m_account_tags.first.count(tag) == 0) + m_account_tags.first.insert({tag, ""}); + } + for (auto i = m_account_tags.first.begin(); i != m_account_tags.first.end(); ) + { + if (std::find(m_account_tags.second.begin(), m_account_tags.second.end(), i->first) == m_account_tags.second.end()) + i = m_account_tags.first.erase(i); + else + ++i; + } + return m_account_tags; +} + +void wallet2::set_account_tag(const std::set<uint32_t> account_indices, const std::string& tag) +{ + for (uint32_t account_index : account_indices) + { + THROW_WALLET_EXCEPTION_IF(account_index >= get_num_subaddress_accounts(), error::wallet_internal_error, "Account index out of bound"); + if (m_account_tags.second[account_index] == tag) + MDEBUG("This tag is already assigned to this account"); + else + m_account_tags.second[account_index] = tag; + } + get_account_tags(); +} + +void wallet2::set_account_tag_description(const std::string& tag, const std::string& description) +{ + THROW_WALLET_EXCEPTION_IF(tag.empty(), error::wallet_internal_error, "Tag must not be empty"); + THROW_WALLET_EXCEPTION_IF(m_account_tags.first.count(tag) == 0, error::wallet_internal_error, "Tag is unregistered"); + m_account_tags.first[tag] = description; +} + std::string wallet2::sign(const std::string &data) const { crypto::hash hash; @@ -7001,13 +7981,15 @@ crypto::public_key wallet2::get_tx_pub_key_from_received_outs(const tools::walle for (size_t i = 0; i < additional_tx_pub_keys.size(); ++i) { additional_derivations.push_back({}); - generate_key_derivation(additional_tx_pub_keys[i], keys.m_view_secret_key, additional_derivations.back()); + bool r = generate_key_derivation(additional_tx_pub_keys[i], keys.m_view_secret_key, additional_derivations.back()); + THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key derivation"); } while (find_tx_extra_field_by_type(tx_extra_fields, pub_key_field, pk_index++)) { const crypto::public_key tx_pub_key = pub_key_field.pub_key; crypto::key_derivation derivation; - generate_key_derivation(tx_pub_key, keys.m_view_secret_key, derivation); + bool r = generate_key_derivation(tx_pub_key, keys.m_view_secret_key, derivation); + THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key derivation"); for (size_t i = 0; i < td.m_tx.vout.size(); ++i) { @@ -7080,7 +8062,7 @@ std::vector<std::pair<crypto::key_image, crypto::signature>> wallet2::export_key bool r = cryptonote::generate_key_image_helper(m_account.get_keys(), m_subaddresses, pkey, tx_pub_key, additional_tx_pub_keys, td.m_internal_output_index, in_ephemeral, ki); THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key image"); - THROW_WALLET_EXCEPTION_IF(td.m_key_image_known && ki != td.m_key_image, + THROW_WALLET_EXCEPTION_IF(td.m_key_image_known && !td.m_key_image_partial && ki != td.m_key_image, error::wallet_internal_error, "key_image generated not matched with cached key image"); THROW_WALLET_EXCEPTION_IF(in_ephemeral.pub != pkey, error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key"); @@ -7195,6 +8177,7 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag m_transfers[n].m_key_image = signed_key_images[n].first; m_key_images[m_transfers[n].m_key_image] = n; m_transfers[n].m_key_image_known = true; + m_transfers[n].m_key_image_partial = false; } if(check_spent) @@ -7293,13 +8276,15 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag const cryptonote::account_keys& keys = m_account.get_keys(); const crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(spent_tx); crypto::key_derivation derivation; - generate_key_derivation(tx_pub_key, keys.m_view_secret_key, derivation); + bool r = generate_key_derivation(tx_pub_key, keys.m_view_secret_key, derivation); + THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key derivation"); const std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(spent_tx); std::vector<crypto::key_derivation> additional_derivations; for (size_t i = 0; i < additional_tx_pub_keys.size(); ++i) { additional_derivations.push_back({}); - generate_key_derivation(additional_tx_pub_keys[i], keys.m_view_secret_key, additional_derivations.back()); + r = generate_key_derivation(additional_tx_pub_keys[i], keys.m_view_secret_key, additional_derivations.back()); + THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key derivation"); } size_t output_index = 0; for (const cryptonote::tx_out& out : spent_tx.vout) @@ -7479,6 +8464,7 @@ size_t wallet2::import_outputs(const std::vector<tools::wallet2::transfer_detail THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key image"); expand_subaddresses(td.m_subaddr_index); td.m_key_image_known = true; + td.m_key_image_partial = false; THROW_WALLET_EXCEPTION_IF(in_ephemeral.pub != boost::get<cryptonote::txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key, error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key at index " + boost::lexical_cast<std::string>(i)); @@ -7490,14 +8476,295 @@ size_t wallet2::import_outputs(const std::vector<tools::wallet2::transfer_detail return m_transfers.size(); } //---------------------------------------------------------------------------------------------------- +crypto::public_key wallet2::get_multisig_signer_public_key(const crypto::secret_key &spend_skey) const +{ + crypto::public_key pkey; + crypto::secret_key_to_public_key(get_multisig_blinded_secret_key(spend_skey), pkey); + return pkey; +} +//---------------------------------------------------------------------------------------------------- +crypto::public_key wallet2::get_multisig_signer_public_key() const +{ + CHECK_AND_ASSERT_THROW_MES(m_multisig, "Wallet is not multisig"); + crypto::public_key signer; + CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(get_account().get_keys().m_spend_secret_key, signer), "Failed to generate signer public key"); + return signer; +} +//---------------------------------------------------------------------------------------------------- +crypto::public_key wallet2::get_multisig_signing_public_key(const crypto::secret_key &msk) const +{ + CHECK_AND_ASSERT_THROW_MES(m_multisig, "Wallet is not multisig"); + crypto::public_key pkey; + CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(msk, pkey), "Failed to derive public key"); + return pkey; +} +//---------------------------------------------------------------------------------------------------- +crypto::public_key wallet2::get_multisig_signing_public_key(size_t idx) const +{ + CHECK_AND_ASSERT_THROW_MES(m_multisig, "Wallet is not multisig"); + CHECK_AND_ASSERT_THROW_MES(idx < get_account().get_multisig_keys().size(), "Multisig signing key index out of range"); + 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 +{ + 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) + { + rct::key L; + rct::scalarmultBase(L, k); + if (used_L.find(L) != used_L.end()) + return k; + } + 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 +{ + CHECK_AND_ASSERT_THROW_MES(n < m_transfers.size(), "Bad m_transfers index"); + rct::multisig_kLRki kLRki; + kLRki.k = k; + cryptonote::generate_multisig_LR(m_transfers[n].get_public_key(), rct::rct2sk(kLRki.k), (crypto::public_key&)kLRki.L, (crypto::public_key&)kLRki.R); + kLRki.ki = rct::ki2rct(m_transfers[n].m_key_image); + return kLRki; +} +//---------------------------------------------------------------------------------------------------- +rct::multisig_kLRki wallet2::get_multisig_composite_kLRki(size_t n, const crypto::public_key &ignore, std::unordered_set<rct::key> &used_L, std::unordered_set<rct::key> &new_used_L) const +{ + CHECK_AND_ASSERT_THROW_MES(n < m_transfers.size(), "Bad transfer index"); + + const transfer_details &td = m_transfers[n]; + rct::multisig_kLRki kLRki = get_multisig_kLRki(n, rct::skGen()); + + // pick a L/R pair from every other participant but one + size_t n_signers_used = 1; + for (const auto &p: m_transfers[n].m_multisig_info) + { + if (p.m_signer == ignore) + continue; + for (const auto &lr: p.m_LR) + { + if (used_L.find(lr.m_L) != used_L.end()) + continue; + used_L.insert(lr.m_L); + new_used_L.insert(lr.m_L); + rct::addKeys(kLRki.L, kLRki.L, lr.m_L); + rct::addKeys(kLRki.R, kLRki.R, lr.m_R); + ++n_signers_used; + break; + } + } + CHECK_AND_ASSERT_THROW_MES(n_signers_used >= m_multisig_threshold, "LR not found for enough participants"); + + return kLRki; +} +//---------------------------------------------------------------------------------------------------- +crypto::key_image wallet2::get_multisig_composite_key_image(size_t n) const +{ + CHECK_AND_ASSERT_THROW_MES(n < m_transfers.size(), "Bad output index"); + + const transfer_details &td = m_transfers[n]; + const crypto::public_key tx_key = get_tx_pub_key_from_received_outs(td); + const std::vector<crypto::public_key> additional_tx_keys = cryptonote::get_additional_tx_pub_keys_from_extra(td.m_tx); + crypto::key_image ki; + std::vector<crypto::key_image> pkis; + for (const auto &info: td.m_multisig_info) + for (const auto &pki: info.m_partial_key_images) + pkis.push_back(pki); + bool r = cryptonote::generate_multisig_composite_key_image(get_account().get_keys(), m_subaddresses, td.get_public_key(), tx_key, additional_tx_keys, td.m_internal_output_index, pkis, ki); + THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key image"); + return ki; +} +//---------------------------------------------------------------------------------------------------- +cryptonote::blobdata wallet2::export_multisig() +{ + std::vector<tools::wallet2::multisig_info> info; + + const crypto::public_key signer = get_multisig_signer_public_key(); + + info.resize(m_transfers.size()); + for (size_t n = 0; n < m_transfers.size(); ++n) + { + transfer_details &td = m_transfers[n]; + const std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(td.m_tx); + crypto::key_image ki; + td.m_multisig_k.clear(); + info[n].m_LR.clear(); + info[n].m_partial_key_images.clear(); + + 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 + bool r = generate_multisig_key_image(get_account().get_keys(), m, td.get_public_key(), ki); + CHECK_AND_ASSERT_THROW_MES(r, "Failed to generate key image"); + info[n].m_partial_key_images.push_back(ki); + } + + size_t nlr = m_multisig_threshold < m_multisig_signers.size() ? m_multisig_threshold - 1 : 1; + for (size_t m = 0; m < nlr; ++m) + { + td.m_multisig_k.push_back(rct::skGen()); + const rct::multisig_kLRki kLRki = get_multisig_kLRki(n, td.m_multisig_k.back()); + info[n].m_LR.push_back({kLRki.L, kLRki.R}); + } + + info[n].m_signer = signer; + } + + std::stringstream oss; + boost::archive::portable_binary_oarchive ar(oss); + ar << info; + + std::string magic(MULTISIG_EXPORT_FILE_MAGIC, strlen(MULTISIG_EXPORT_FILE_MAGIC)); + const cryptonote::account_public_address &keys = get_account().get_keys().m_account_address; + std::string header; + header += std::string((const char *)&keys.m_spend_public_key, sizeof(crypto::public_key)); + header += std::string((const char *)&keys.m_view_public_key, sizeof(crypto::public_key)); + header += std::string((const char *)&signer, sizeof(crypto::public_key)); + std::string ciphertext = encrypt_with_view_secret_key(header + oss.str()); + + return MULTISIG_EXPORT_FILE_MAGIC + ciphertext; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::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) +{ + CHECK_AND_ASSERT_THROW_MES(n < m_transfers.size(), "Bad index in update_multisig_info"); + CHECK_AND_ASSERT_THROW_MES(multisig_k.size() >= m_transfers.size(), "Mismatched sizes of multisig_k and info"); + + MDEBUG("update_multisig_rescan_info: updating index " << n); + transfer_details &td = m_transfers[n]; + td.m_multisig_info.clear(); + for (const auto &pi: info) + { + CHECK_AND_ASSERT_THROW_MES(n < pi.size(), "Bad pi size"); + td.m_multisig_info.push_back(pi[n]); + } + m_key_images.erase(td.m_key_image); + td.m_key_image = get_multisig_composite_key_image(n); + td.m_key_image_known = true; + td.m_key_image_partial = false; + td.m_multisig_k = multisig_k[n]; + m_key_images[td.m_key_image] = n; +} +//---------------------------------------------------------------------------------------------------- +size_t wallet2::import_multisig(std::vector<cryptonote::blobdata> blobs) +{ + CHECK_AND_ASSERT_THROW_MES(m_multisig, "Wallet is not multisig"); + + std::vector<std::vector<tools::wallet2::multisig_info>> info; + std::unordered_set<crypto::public_key> seen; + for (cryptonote::blobdata &data: blobs) + { + const size_t magiclen = strlen(MULTISIG_EXPORT_FILE_MAGIC); + THROW_WALLET_EXCEPTION_IF(data.size() < magiclen || memcmp(data.data(), MULTISIG_EXPORT_FILE_MAGIC, magiclen), + error::wallet_internal_error, "Bad multisig info file magic in "); + + data = decrypt_with_view_secret_key(std::string(data, magiclen)); + + const size_t headerlen = 3 * sizeof(crypto::public_key); + THROW_WALLET_EXCEPTION_IF(data.size() < headerlen, error::wallet_internal_error, "Bad data size"); + + const crypto::public_key &public_spend_key = *(const crypto::public_key*)&data[0]; + const crypto::public_key &public_view_key = *(const crypto::public_key*)&data[sizeof(crypto::public_key)]; + const crypto::public_key &signer = *(const crypto::public_key*)&data[2*sizeof(crypto::public_key)]; + const cryptonote::account_public_address &keys = get_account().get_keys().m_account_address; + THROW_WALLET_EXCEPTION_IF(public_spend_key != keys.m_spend_public_key || public_view_key != keys.m_view_public_key, + error::wallet_internal_error, "Multisig info is for a different account"); + if (get_multisig_signer_public_key() == signer) + { + MINFO("Multisig info from this wallet ignored"); + continue; + } + if (seen.find(signer) != seen.end()) + { + MINFO("Duplicate multisig info ignored"); + continue; + } + seen.insert(signer); + + std::string body(data, headerlen); + std::istringstream iss(body); + std::vector<tools::wallet2::multisig_info> i; + boost::archive::portable_binary_iarchive ar(iss); + ar >> i; + MINFO(boost::format("%u outputs found") % boost::lexical_cast<std::string>(i.size())); + info.push_back(std::move(i)); + } + + CHECK_AND_ASSERT_THROW_MES(info.size() + 1 <= m_multisig_signers.size() && info.size() + 1 >= m_multisig_threshold, "Wrong number of multisig sources"); + + std::vector<std::vector<rct::key>> k; + k.reserve(m_transfers.size()); + for (const auto &td: m_transfers) + k.push_back(td.m_multisig_k); + + // how many outputs we're going to update + size_t n_outputs = m_transfers.size(); + for (const auto &pi: info) + if (pi.size() < n_outputs) + n_outputs = pi.size(); + + if (n_outputs == 0) + return 0; + + // check signers are consistent + for (const auto &pi: info) + { + CHECK_AND_ASSERT_THROW_MES(std::find(m_multisig_signers.begin(), m_multisig_signers.end(), pi[0].m_signer) != m_multisig_signers.end(), + "Signer is not a member of this multisig wallet"); + for (size_t n = 1; n < n_outputs; ++n) + CHECK_AND_ASSERT_THROW_MES(pi[n].m_signer == pi[0].m_signer, "Mismatched signers in imported multisig info"); + } + + // trim data we don't have info for from all participants + for (auto &pi: info) + pi.resize(n_outputs); + + // sort by signer + if (!info.empty() && !info.front().empty()) + { + std::sort(info.begin(), info.end(), [](const std::vector<tools::wallet2::multisig_info> &i0, const std::vector<tools::wallet2::multisig_info> &i1){ return memcmp(&i0[0].m_signer, &i1[0].m_signer, sizeof(i0[0].m_signer)); }); + } + + // first pass to determine where to detach the blockchain + for (size_t n = 0; n < n_outputs; ++n) + { + const transfer_details &td = m_transfers[n]; + if (!td.m_key_image_partial) + continue; + MINFO("Multisig info importing from block height " << td.m_block_height); + detach_blockchain(td.m_block_height); + break; + } + + for (size_t n = 0; n < n_outputs && n < m_transfers.size(); ++n) + { + update_multisig_rescan_info(k, info, n); + } + + m_multisig_rescan_k = &k; + m_multisig_rescan_info = &info; + try + { + refresh(); + } + catch (...) {} + m_multisig_rescan_info = NULL; + m_multisig_rescan_k = NULL; + + return n_outputs; +} +//---------------------------------------------------------------------------------------------------- std::string wallet2::encrypt(const std::string &plaintext, const crypto::secret_key &skey, bool authenticated) const { - crypto::chacha8_key key; - crypto::generate_chacha8_key(&skey, sizeof(skey), key); + crypto::chacha_key key; + crypto::generate_chacha_key(&skey, sizeof(skey), key); std::string ciphertext; - crypto::chacha8_iv iv = crypto::rand<crypto::chacha8_iv>(); + crypto::chacha_iv iv = crypto::rand<crypto::chacha_iv>(); ciphertext.resize(plaintext.size() + sizeof(iv) + (authenticated ? sizeof(crypto::signature) : 0)); - crypto::chacha8(plaintext.data(), plaintext.size(), key, iv, &ciphertext[sizeof(iv)]); + crypto::chacha20(plaintext.data(), plaintext.size(), key, iv, &ciphertext[sizeof(iv)]); memcpy(&ciphertext[0], &iv, sizeof(iv)); if (authenticated) { @@ -7518,13 +8785,13 @@ std::string wallet2::encrypt_with_view_secret_key(const std::string &plaintext, //---------------------------------------------------------------------------------------------------- std::string wallet2::decrypt(const std::string &ciphertext, const crypto::secret_key &skey, bool authenticated) const { - const size_t prefix_size = sizeof(chacha8_iv) + (authenticated ? sizeof(crypto::signature) : 0); + const size_t prefix_size = sizeof(chacha_iv) + (authenticated ? sizeof(crypto::signature) : 0); THROW_WALLET_EXCEPTION_IF(ciphertext.size() < prefix_size, error::wallet_internal_error, "Unexpected ciphertext size"); - crypto::chacha8_key key; - crypto::generate_chacha8_key(&skey, sizeof(skey), key); - const crypto::chacha8_iv &iv = *(const crypto::chacha8_iv*)&ciphertext[0]; + crypto::chacha_key key; + crypto::generate_chacha_key(&skey, sizeof(skey), key); + const crypto::chacha_iv &iv = *(const crypto::chacha_iv*)&ciphertext[0]; std::string plaintext; plaintext.resize(ciphertext.size() - prefix_size); if (authenticated) @@ -7537,7 +8804,7 @@ std::string wallet2::decrypt(const std::string &ciphertext, const crypto::secret THROW_WALLET_EXCEPTION_IF(!crypto::check_signature(hash, pkey, signature), error::wallet_internal_error, "Failed to authenticate ciphertext"); } - crypto::chacha8(ciphertext.data() + sizeof(iv), ciphertext.size() - prefix_size, key, iv, &plaintext[0]); + crypto::chacha20(ciphertext.data() + sizeof(iv), ciphertext.size() - prefix_size, key, iv, &plaintext[0]); return plaintext; } //---------------------------------------------------------------------------------------------------- @@ -7739,6 +9006,7 @@ uint64_t wallet2::get_blockchain_height_by_date(uint16_t year, uint8_t month, ui throw std::runtime_error(oss.str()); } cryptonote::block blk_min, blk_mid, blk_max; + if (res.blocks.size() < 3) throw std::runtime_error("Not enough blocks returned from daemon"); if (!parse_and_validate_block_from_blob(res.blocks[0].block, blk_min)) throw std::runtime_error("failed to parse blob at height " + std::to_string(height_min)); if (!parse_and_validate_block_from_blob(res.blocks[1].block, blk_mid)) throw std::runtime_error("failed to parse blob at height " + std::to_string(height_mid)); if (!parse_and_validate_block_from_blob(res.blocks[2].block, blk_max)) throw std::runtime_error("failed to parse blob at height " + std::to_string(height_max)); diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index be8315468..b1115f67b 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -49,7 +49,7 @@ #include "cryptonote_basic/cryptonote_format_utils.h" #include "cryptonote_core/cryptonote_tx_utils.h" #include "common/unordered_containers_boost_serialization.h" -#include "crypto/chacha8.h" +#include "crypto/chacha.h" #include "crypto/hash.h" #include "ringct/rctTypes.h" #include "ringct/rctOps.h" @@ -144,10 +144,6 @@ namespace tools RefreshDefault = RefreshOptimizeCoinbase, }; - private: - wallet2(const wallet2&) : m_run(true), m_callback(0), m_testnet(false), m_always_confirm_transfers(true), m_print_ring_members(false), m_store_tx_info(true), m_default_mixin(0), m_default_priority(0), m_refresh_type(RefreshDefault), m_auto_refresh(true), m_refresh_from_block_height(0), m_confirm_missing_payment_id(true), m_ask_password(true), m_min_output_count(0), m_min_output_value(0), m_merge_destinations(false), m_confirm_backlog(true), m_is_initialized(false),m_node_rpc_proxy(m_http_client, m_daemon_rpc_mutex) {} - - public: static const char* tr(const char* str); static bool has_testnet_option(const boost::program_options::variables_map& vm); @@ -166,9 +162,33 @@ namespace tools //! Just parses variables. static std::unique_ptr<wallet2> make_dummy(const boost::program_options::variables_map& vm, const std::function<boost::optional<password_container>(const char *, bool)> &password_prompter); - static bool verify_password(const std::string& keys_file_name, const epee::wipeable_string& password, bool watch_only); + static bool verify_password(const std::string& keys_file_name, const epee::wipeable_string& password, bool no_spend_key); + + wallet2(bool testnet = false, bool restricted = false); + + struct multisig_info + { + struct LR + { + rct::key m_L; + rct::key m_R; + + BEGIN_SERIALIZE_OBJECT() + FIELD(m_L) + FIELD(m_R) + END_SERIALIZE() + }; - wallet2(bool testnet = false, bool restricted = false) : m_run(true), m_callback(0), m_testnet(testnet), m_always_confirm_transfers(true), m_print_ring_members(false), m_store_tx_info(true), m_default_mixin(0), m_default_priority(0), m_refresh_type(RefreshDefault), m_auto_refresh(true), m_refresh_from_block_height(0), m_confirm_missing_payment_id(true), m_ask_password(true), m_min_output_count(0), m_min_output_value(0), m_merge_destinations(false), m_confirm_backlog(true), m_is_initialized(false), m_restricted(restricted), is_old_file_format(false), m_node_rpc_proxy(m_http_client, m_daemon_rpc_mutex), m_light_wallet(false), m_light_wallet_scanned_block_height(0), m_light_wallet_blockchain_height(0), m_light_wallet_connected(false), m_light_wallet_balance(0), m_light_wallet_unlocked_balance(0) {} + crypto::public_key m_signer; + std::vector<LR> m_LR; + std::vector<crypto::key_image> m_partial_key_images; // one per key the participant has + + BEGIN_SERIALIZE_OBJECT() + FIELD(m_signer) + FIELD(m_LR) + FIELD(m_partial_key_images) + END_SERIALIZE() + }; struct tx_scan_info_t { @@ -199,6 +219,9 @@ namespace tools bool m_key_image_known; size_t m_pk_index; cryptonote::subaddress_index m_subaddr_index; + bool m_key_image_partial; + std::vector<rct::key> m_multisig_k; + std::vector<multisig_info> m_multisig_info; // one per other participant bool is_rct() const { return m_rct; } uint64_t amount() const { return m_amount; } @@ -219,6 +242,9 @@ namespace tools FIELD(m_key_image_known) FIELD(m_pk_index) FIELD(m_subaddr_index) + FIELD(m_key_image_partial) + FIELD(m_multisig_k) + FIELD(m_multisig_info) END_SERIALIZE() }; @@ -308,6 +334,15 @@ namespace tools typedef std::vector<transfer_details> transfer_container; typedef std::unordered_multimap<crypto::hash, payment_details> payment_container; + struct multisig_sig + { + rct::rctSig sigs; + crypto::public_key ignore; + std::unordered_set<rct::key> used_L; + std::unordered_set<crypto::public_key> signing_keys; + rct::multisig_out msout; + }; + // The convention for destinations is: // dests does not include change // splitted_dsts (in construction_data) does @@ -322,6 +357,7 @@ namespace tools crypto::secret_key tx_key; std::vector<crypto::secret_key> additional_tx_keys; std::vector<cryptonote::tx_destination_entry> dests; + std::vector<multisig_sig> multisig_sigs; tx_construction_data construction_data; @@ -337,6 +373,7 @@ namespace tools FIELD(additional_tx_keys) FIELD(dests) FIELD(construction_data) + FIELD(multisig_sigs) END_SERIALIZE() }; @@ -354,9 +391,20 @@ namespace tools std::vector<crypto::key_image> key_images; }; + struct multisig_tx_set + { + std::vector<pending_tx> m_ptx; + std::unordered_set<crypto::public_key> m_signers; + + BEGIN_SERIALIZE_OBJECT() + FIELD(m_ptx) + FIELD(m_signers) + END_SERIALIZE() + }; + struct keys_file_data { - crypto::chacha8_iv iv; + crypto::chacha_iv iv; std::string account_data; BEGIN_SERIALIZE_OBJECT() @@ -367,7 +415,7 @@ namespace tools struct cache_file_data { - crypto::chacha8_iv iv; + crypto::chacha_iv iv; std::string cache_data; BEGIN_SERIALIZE_OBJECT() @@ -419,6 +467,53 @@ namespace tools const cryptonote::account_public_address &account_public_address, const crypto::secret_key& viewkey = crypto::secret_key()); /*! + * \brief Creates a multisig wallet + * \return empty if done, non empty if we need to send another string + * to other participants + */ + std::string make_multisig(const epee::wipeable_string &password, + const std::vector<std::string> &info, + uint32_t threshold); + /*! + * \brief Creates a multisig wallet + * \return empty if done, non empty if we need to send another string + * to other participants + */ + std::string make_multisig(const epee::wipeable_string &password, + const std::vector<crypto::secret_key> &view_keys, + const std::vector<crypto::public_key> &spend_keys, + uint32_t threshold); + /*! + * \brief Finalizes creation of a multisig wallet + */ + bool finalize_multisig(const epee::wipeable_string &password, const std::vector<std::string> &info); + /*! + * \brief Finalizes creation of a multisig wallet + */ + bool finalize_multisig(const epee::wipeable_string &password, std::unordered_set<crypto::public_key> pkeys, std::vector<crypto::public_key> signers); + /*! + * Get a packaged multisig information string + */ + std::string get_multisig_info() const; + /*! + * Verifies and extracts keys from a packaged multisig information string + */ + static bool verify_multisig_info(const std::string &data, crypto::secret_key &skey, crypto::public_key &pkey); + /*! + * Verifies and extracts keys from a packaged multisig information string + */ + static bool verify_extra_multisig_info(const std::string &data, std::unordered_set<crypto::public_key> &pkeys, crypto::public_key &signer); + /*! + * Export multisig info + * This will generate and remember new k values + */ + cryptonote::blobdata export_multisig(); + /*! + * Import a set of multisig info from multisig partners + * \return the number of inputs which were imported + */ + size_t import_multisig(std::vector<cryptonote::blobdata> info); + /*! * \brief Rewrites to the wallet file for wallet upgrade (doesn't generate key, assumes it's already there) * \param wallet_name Name of wallet file (should exist) * \param password Password for wallet file @@ -497,6 +592,7 @@ namespace tools void expand_subaddresses(const cryptonote::subaddress_index& index); std::string get_subaddress_label(const cryptonote::subaddress_index& index) const; void set_subaddress_label(const cryptonote::subaddress_index &index, const std::string &label); + void set_subaddress_lookahead(size_t major, size_t minor); /*! * \brief Tells if the wallet file is deprecated. */ @@ -512,6 +608,8 @@ namespace tools bool testnet() const { return m_testnet; } bool restricted() const { return m_restricted; } bool watch_only() const { return m_watch_only; } + bool multisig(bool *ready = NULL, uint32_t *threshold = NULL, uint32_t *total = NULL) const; + bool has_multisig_partial_key_images() const; // locked & unlocked balance of given or current subaddress account uint64_t balance(uint32_t subaddr_index_major) const; @@ -539,6 +637,10 @@ namespace tools void commit_tx(pending_tx& ptx_vector); void commit_tx(std::vector<pending_tx>& ptx_vector); bool save_tx(const std::vector<pending_tx>& ptx_vector, const std::string &filename); + std::string save_multisig_tx(multisig_tx_set txs); + bool save_multisig_tx(const multisig_tx_set &txs, const std::string &filename); + std::string save_multisig_tx(const std::vector<pending_tx>& ptx_vector); + bool save_multisig_tx(const std::vector<pending_tx>& ptx_vector, const std::string &filename); // load unsigned tx from file and sign it. Takes confirmation callback as argument. Used by the cli wallet bool sign_tx(const std::string &unsigned_filename, const std::string &signed_filename, std::vector<wallet2::pending_tx> &ptx, std::function<bool(const unsigned_tx_set&)> accept_func = NULL, bool export_raw = false); // sign unsigned tx. Takes unsigned_tx_set as argument. Used by GUI @@ -551,6 +653,11 @@ namespace tools std::vector<wallet2::pending_tx> create_transactions_all(uint64_t below, const cryptonote::account_public_address &address, bool is_subaddress, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices, bool trusted_daemon); std::vector<wallet2::pending_tx> create_transactions_single(const crypto::key_image &ki, const cryptonote::account_public_address &address, bool is_subaddress, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, bool trusted_daemon); std::vector<wallet2::pending_tx> create_transactions_from(const cryptonote::account_public_address &address, bool is_subaddress, std::vector<size_t> unused_transfers_indices, std::vector<size_t> unused_dust_indices, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, bool trusted_daemon); + bool load_multisig_tx(cryptonote::blobdata blob, multisig_tx_set &exported_txs, std::function<bool(const multisig_tx_set&)> accept_func = NULL); + bool load_multisig_tx_from_file(const std::string &filename, multisig_tx_set &exported_txs, std::function<bool(const multisig_tx_set&)> accept_func = NULL); + bool sign_multisig_tx_from_file(const std::string &filename, std::vector<crypto::hash> &txids, std::function<bool(const multisig_tx_set&)> accept_func); + bool sign_multisig_tx(multisig_tx_set &exported_txs, std::vector<crypto::hash> &txids); + bool sign_multisig_tx_to_file(multisig_tx_set &exported_txs, const std::string &filename, std::vector<crypto::hash> &txids); std::vector<pending_tx> create_unmixable_sweep_transactions(bool trusted_daemon); bool check_connection(uint32_t *version = NULL, uint32_t timeout = 200000); void get_transfers(wallet2::transfer_container& incoming_transfers) const; @@ -660,6 +767,9 @@ namespace tools if(ver < 22) return; a & m_unconfirmed_payments; + if(ver < 23) + return; + a & m_account_tags; } /*! @@ -724,6 +834,7 @@ namespace tools bool delete_address_book_row(std::size_t row_id); uint64_t get_num_rct_outputs(); + size_t get_num_transfer_details() const { return m_transfers.size(); } const transfer_details &get_transfer_details(size_t idx) const; void get_hard_fork_info(uint8_t version, uint64_t &earliest_height); @@ -755,6 +866,24 @@ namespace tools void set_description(const std::string &description); std::string get_description() const; + /*! + * \brief Get the list of registered account tags. + * \return first.Key=(tag's name), first.Value=(tag's label), second[i]=(i-th account's tag) + */ + const std::pair<std::map<std::string, std::string>, std::vector<std::string>>& get_account_tags(); + /*! + * \brief Set a tag to the given accounts. + * \param account_indices Indices of accounts. + * \param tag Tag's name. If empty, the accounts become untagged. + */ + void set_account_tag(const std::set<uint32_t> account_indices, const std::string& tag); + /*! + * \brief Set the label of the given tag. + * \param tag Tag's name (which must be non-empty). + * \param label Tag's description. + */ + void set_account_tag_description(const std::string& tag, const std::string& description); + std::string sign(const std::string &data) const; bool verify(const std::string &data, const cryptonote::account_public_address &address, const std::string &signature) const; @@ -829,6 +958,11 @@ namespace tools void set_attribute(const std::string &key, const std::string &value); std::string get_attribute(const std::string &key) const; + crypto::public_key get_multisig_signer_public_key(const crypto::secret_key &spend_skey) const; + crypto::public_key get_multisig_signer_public_key() const; + crypto::public_key get_multisig_signing_public_key(size_t idx) const; + crypto::public_key get_multisig_signing_public_key(const crypto::secret_key &skey) const; + private: /*! * \brief Stores wallet information to wallet file. @@ -862,9 +996,8 @@ namespace tools void add_unconfirmed_tx(const cryptonote::transaction& tx, uint64_t amount_in, const std::vector<cryptonote::tx_destination_entry> &dests, const crypto::hash &payment_id, uint64_t change_amount, uint32_t subaddr_account, const std::set<uint32_t>& subaddr_indices); void generate_genesis(cryptonote::block& b); void check_genesis(const crypto::hash& genesis_hash) const; //throws - bool generate_chacha8_key_from_secret_keys(crypto::chacha8_key &key) const; + bool generate_chacha_key_from_secret_keys(crypto::chacha_key &key) const; crypto::hash get_payment_id(const pending_tx &ptx) const; - crypto::hash8 get_short_payment_id(const pending_tx &ptx) const; void check_acc_out_precomp(const cryptonote::tx_out &o, const crypto::key_derivation &derivation, const std::vector<crypto::key_derivation> &additional_derivations, size_t i, tx_scan_info_t &tx_scan_info) const; void parse_block_round(const cryptonote::blobdata &blob, cryptonote::block &bl, crypto::hash &bl_id, bool &error) const; uint64_t get_upper_transaction_size_limit(); @@ -876,12 +1009,16 @@ namespace tools void set_unspent(size_t idx); void get_outs(std::vector<std::vector<get_outs_entry>> &outs, const std::vector<size_t> &selected_transfers, size_t fake_outputs_count); bool tx_add_fake_output(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, uint64_t global_index, const crypto::public_key& tx_public_key, const rct::key& mask, uint64_t real_index, bool unlocked) const; - bool wallet_generate_key_image_helper(const cryptonote::account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, cryptonote::keypair& in_ephemeral, crypto::key_image& ki); crypto::public_key get_tx_pub_key_from_received_outs(const tools::wallet2::transfer_details &td) const; bool should_pick_a_second_output(bool use_rct, size_t n_transfers, const std::vector<size_t> &unused_transfers_indices, const std::vector<size_t> &unused_dust_indices) const; std::vector<size_t> get_only_rct(const std::vector<size_t> &unused_dust_indices, const std::vector<size_t> &unused_transfers_indices) const; void scan_output(const cryptonote::account_keys &keys, const cryptonote::transaction &tx, const crypto::public_key &tx_pub_key, size_t i, tx_scan_info_t &tx_scan_info, int &num_vouts_received, std::unordered_map<cryptonote::subaddress_index, uint64_t> &tx_money_got_in_outs, std::vector<size_t> &outs); void trim_hashchain(); + crypto::key_image get_multisig_composite_key_image(size_t n) const; + rct::multisig_kLRki get_multisig_composite_kLRki(size_t n, const crypto::public_key &ignore, std::unordered_set<rct::key> &used_L, std::unordered_set<rct::key> &new_used_L) const; + 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 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); cryptonote::account_base m_account; boost::optional<epee::net_utils::http::login> m_daemon_login; @@ -909,7 +1046,10 @@ namespace tools std::unordered_map<crypto::hash, std::string> m_tx_notes; std::unordered_map<std::string, std::string> m_attributes; std::vector<tools::wallet2::address_book_row> m_address_book; + std::pair<std::map<std::string, std::string>, std::vector<std::string>> m_account_tags; uint64_t m_upper_transaction_size_limit; //TODO: auto-calc this value or request from daemon, now use some fixed value + const std::vector<std::vector<tools::wallet2::multisig_info>> *m_multisig_rescan_info; + const std::vector<std::vector<rct::key>> *m_multisig_rescan_k; std::atomic<bool> m_run; @@ -921,6 +1061,9 @@ namespace tools std::string seed_language; /*!< Language of the mnemonics (seed). */ bool is_old_file_format; /*!< Whether the wallet file is of an old file format */ bool m_watch_only; /*!< no spend key */ + bool m_multisig; /*!< if > 1 spend secret key will not match spend public key */ + uint32_t m_multisig_threshold; + std::vector<crypto::public_key> m_multisig_signers; bool m_always_confirm_transfers; bool m_print_ring_members; bool m_store_tx_info; /*!< request txkey to be returned in RPC, and store in the wallet cache file */ @@ -939,6 +1082,7 @@ namespace tools bool m_is_initialized; NodeRPCProxy m_node_rpc_proxy; std::unordered_set<crypto::hash> m_scanned_pool_txs[2]; + size_t m_subaddress_lookahead_major, m_subaddress_lookahead_minor; // Light wallet bool m_light_wallet; /* sends view key to daemon for scanning */ @@ -955,8 +1099,11 @@ namespace tools std::unordered_map<crypto::public_key, std::map<uint64_t, crypto::key_image> > m_key_image_cache; }; } -BOOST_CLASS_VERSION(tools::wallet2, 22) -BOOST_CLASS_VERSION(tools::wallet2::transfer_details, 8) +BOOST_CLASS_VERSION(tools::wallet2, 23) +BOOST_CLASS_VERSION(tools::wallet2::transfer_details, 9) +BOOST_CLASS_VERSION(tools::wallet2::multisig_info, 1) +BOOST_CLASS_VERSION(tools::wallet2::multisig_info::LR, 0) +BOOST_CLASS_VERSION(tools::wallet2::multisig_tx_set, 1) BOOST_CLASS_VERSION(tools::wallet2::payment_details, 2) BOOST_CLASS_VERSION(tools::wallet2::pool_payment_details, 1) BOOST_CLASS_VERSION(tools::wallet2::unconfirmed_transfer_details, 7) @@ -965,7 +1112,8 @@ BOOST_CLASS_VERSION(tools::wallet2::address_book_row, 17) BOOST_CLASS_VERSION(tools::wallet2::unsigned_tx_set, 0) BOOST_CLASS_VERSION(tools::wallet2::signed_tx_set, 0) BOOST_CLASS_VERSION(tools::wallet2::tx_construction_data, 2) -BOOST_CLASS_VERSION(tools::wallet2::pending_tx, 2) +BOOST_CLASS_VERSION(tools::wallet2::pending_tx, 3) +BOOST_CLASS_VERSION(tools::wallet2::multisig_sig, 0) namespace boost { @@ -1003,6 +1151,12 @@ namespace boost { x.m_subaddr_index = {}; } + if (ver < 9) + { + x.m_key_image_partial = false; + x.m_multisig_k.clear(); + x.m_multisig_info.clear(); + } } template <class Archive> @@ -1076,6 +1230,36 @@ namespace boost return; } a & x.m_subaddr_index; + if (ver < 9) + { + initialize_transfer_details(a, x, ver); + return; + } + a & x.m_multisig_info; + a & x.m_multisig_k; + a & x.m_key_image_partial; + } + + template <class Archive> + inline void serialize(Archive &a, tools::wallet2::multisig_info::LR &x, const boost::serialization::version_type ver) + { + a & x.m_L; + a & x.m_R; + } + + template <class Archive> + inline void serialize(Archive &a, tools::wallet2::multisig_info &x, const boost::serialization::version_type ver) + { + a & x.m_signer; + a & x.m_LR; + a & x.m_partial_key_images; + } + + template <class Archive> + inline void serialize(Archive &a, tools::wallet2::multisig_tx_set &x, const boost::serialization::version_type ver) + { + a & x.m_ptx; + a & x.m_signers; } template <class Archive> @@ -1254,6 +1438,16 @@ namespace boost } template <class Archive> + inline void serialize(Archive &a, tools::wallet2::multisig_sig &x, const boost::serialization::version_type ver) + { + a & x.sigs; + a & x.ignore; + a & x.used_L; + a & x.signing_keys; + a & x.msout; + } + + template <class Archive> inline void serialize(Archive &a, tools::wallet2::pending_tx &x, const boost::serialization::version_type ver) { a & x.tx; @@ -1281,6 +1475,9 @@ namespace boost if (ver < 2) return; a & x.selected_transfers; + if (ver < 3) + return; + a & x.multisig_sigs; } } } @@ -1356,6 +1553,8 @@ namespace tools // throw if attempting a transaction with no destinations THROW_WALLET_EXCEPTION_IF(dsts.empty(), error::zero_destination); + THROW_WALLET_EXCEPTION_IF(m_multisig, error::wallet_internal_error, "Multisig wallets cannot spend non rct outputs"); + uint64_t upper_transaction_size_limit = get_upper_transaction_size_limit(); uint64_t needed_money = fee; @@ -1458,6 +1657,7 @@ namespace tools src.real_out_tx_key = get_tx_pub_key_from_extra(td.m_tx); src.real_output = interted_it - src.outputs.begin(); src.real_output_in_tx_index = td.m_internal_output_index; + src.multisig_kLRki = rct::multisig_kLRki({rct::zero(), rct::zero(), rct::zero(), rct::zero()}); detail::print_source_entry(src); ++i; } @@ -1484,7 +1684,8 @@ namespace tools crypto::secret_key tx_key; std::vector<crypto::secret_key> additional_tx_keys; - 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); + rct::multisig_out msout; + 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, false, m_multisig ? &msout : NULL); THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, splitted_dsts, unlock_time, m_testnet); THROW_WALLET_EXCEPTION_IF(upper_transaction_size_limit <= get_object_blobsize(tx), error::tx_too_big, tx, upper_transaction_size_limit); diff --git a/src/wallet/wallet_args.cpp b/src/wallet/wallet_args.cpp index 91162c4bc..2273f14ad 100644 --- a/src/wallet/wallet_args.cpp +++ b/src/wallet/wallet_args.cpp @@ -85,6 +85,7 @@ namespace wallet_args boost::optional<boost::program_options::variables_map> main( int argc, char** argv, const char* const usage, + const char* const notice, boost::program_options::options_description desc_params, const boost::program_options::positional_options_description& positional_options, const std::function<void(const std::string&, bool)> &print, @@ -179,6 +180,9 @@ namespace wallet_args mlog_set_log(command_line::get_arg(vm, arg_log_level).c_str()); } + if (notice) + Print(print) << notice << ENDL; + if (!command_line::is_arg_defaulted(vm, arg_max_concurrency)) tools::set_max_concurrency(command_line::get_arg(vm, arg_max_concurrency)); diff --git a/src/wallet/wallet_args.h b/src/wallet/wallet_args.h index 8974098ad..212958988 100644 --- a/src/wallet/wallet_args.h +++ b/src/wallet/wallet_args.h @@ -48,6 +48,7 @@ namespace wallet_args boost::optional<boost::program_options::variables_map> main( int argc, char** argv, const char* const usage, + const char* const notice, boost::program_options::options_description desc_params, const boost::program_options::positional_options_description& positional_options, const std::function<void(const std::string&, bool)> &print, diff --git a/src/wallet/wallet_errors.h b/src/wallet/wallet_errors.h index 48fce40dd..234c22d85 100644 --- a/src/wallet/wallet_errors.h +++ b/src/wallet/wallet_errors.h @@ -50,6 +50,8 @@ namespace tools // wallet_internal_error // unexpected_txin_type // wallet_not_initialized + // multisig_export_needed + // multisig_import_needed // std::logic_error // wallet_logic_error * // file_exists @@ -186,7 +188,22 @@ namespace tools { } }; - + //---------------------------------------------------------------------------------------------------- + struct multisig_export_needed : public wallet_runtime_error + { + explicit multisig_export_needed(std::string&& loc) + : wallet_runtime_error(std::move(loc), "This signature was made with stale data: export fresh multisig data, which other participants must then use") + { + } + }; + //---------------------------------------------------------------------------------------------------- + struct multisig_import_needed : public wallet_runtime_error + { + explicit multisig_import_needed(std::string&& loc) + : wallet_runtime_error(std::move(loc), "Not enough multisig data was found to sign: import multisig data from more other participants") + { + } + }; //---------------------------------------------------------------------------------------------------- const char* const file_error_messages[] = { "file already exists", diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index e790b9954..f031b765d 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -41,6 +41,7 @@ using namespace epee; #include "common/i18n.h" #include "cryptonote_basic/cryptonote_format_utils.h" #include "cryptonote_basic/account.h" +#include "multisig/multisig.h" #include "wallet_rpc_server_commands_defs.h" #include "misc_language.h" #include "string_coding.h" @@ -324,6 +325,7 @@ namespace tools { res.balance = m_wallet->balance(req.account_index); res.unlocked_balance = m_wallet->unlocked_balance(req.account_index); + res.multisig_import_needed = m_wallet->multisig() && m_wallet->has_multisig_partial_key_images(); std::map<uint32_t, uint64_t> balance_per_subaddress = m_wallet->balance_per_subaddress(req.account_index); std::map<uint32_t, uint64_t> unlocked_balance_per_subaddress = m_wallet->unlocked_balance_per_subaddress(req.account_index); std::vector<tools::wallet2::transfer_details> transfers; @@ -354,13 +356,24 @@ namespace tools if (!m_wallet) return not_open(er); try { - res.addresses.resize(m_wallet->get_num_subaddresses(req.account_index)); + res.addresses.clear(); + std::vector<uint32_t> req_address_index; + if (req.address_index.empty()) + { + for (uint32_t i = 0; i < m_wallet->get_num_subaddresses(req.account_index); ++i) + req_address_index.push_back(i); + } + else + { + req_address_index = req.address_index; + } tools::wallet2::transfer_container transfers; m_wallet->get_transfers(transfers); - cryptonote::subaddress_index index = {req.account_index, 0}; - for (; index.minor < m_wallet->get_num_subaddresses(req.account_index); ++index.minor) + for (uint32_t i : req_address_index) { - auto& info = res.addresses[index.minor]; + res.addresses.resize(res.addresses.size() + 1); + auto& info = res.addresses.back(); + const cryptonote::subaddress_index index = {req.account_index, i}; info.address = m_wallet->get_subaddress_as_str(index); info.label = m_wallet->get_subaddress_label(index); info.address_index = index.minor; @@ -416,14 +429,24 @@ namespace tools res.total_balance = 0; res.total_unlocked_balance = 0; cryptonote::subaddress_index subaddr_index = {0,0}; + const std::pair<std::map<std::string, std::string>, std::vector<std::string>> account_tags = m_wallet->get_account_tags(); + if (!req.tag.empty() && account_tags.first.count(req.tag) == 0) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = (boost::format(tr("Tag %s is unregistered.")) % req.tag).str(); + return false; + } for (; subaddr_index.major < m_wallet->get_num_subaddress_accounts(); ++subaddr_index.major) { + if (!req.tag.empty() && req.tag != account_tags.second[subaddr_index.major]) + continue; wallet_rpc::COMMAND_RPC_GET_ACCOUNTS::subaddress_account_info info; info.account_index = subaddr_index.major; info.base_address = m_wallet->get_subaddress_as_str(subaddr_index); info.balance = m_wallet->balance(subaddr_index.major); info.unlocked_balance = m_wallet->unlocked_balance(subaddr_index.major); info.label = m_wallet->get_subaddress_label(subaddr_index); + info.tag = account_tags.second[subaddr_index.major]; res.subaddress_accounts.push_back(info); res.total_balance += info.balance; res.total_unlocked_balance += info.unlocked_balance; @@ -469,6 +492,66 @@ namespace tools return true; } //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_get_account_tags(const wallet_rpc::COMMAND_RPC_GET_ACCOUNT_TAGS::request& req, wallet_rpc::COMMAND_RPC_GET_ACCOUNT_TAGS::response& res, epee::json_rpc::error& er) + { + const std::pair<std::map<std::string, std::string>, std::vector<std::string>> account_tags = m_wallet->get_account_tags(); + for (const std::pair<std::string, std::string>& p : account_tags.first) + { + res.account_tags.resize(res.account_tags.size() + 1); + auto& info = res.account_tags.back(); + info.tag = p.first; + info.label = p.second; + for (size_t i = 0; i < account_tags.second.size(); ++i) + { + if (account_tags.second[i] == info.tag) + info.accounts.push_back(i); + } + } + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_tag_accounts(const wallet_rpc::COMMAND_RPC_TAG_ACCOUNTS::request& req, wallet_rpc::COMMAND_RPC_TAG_ACCOUNTS::response& res, epee::json_rpc::error& er) + { + try + { + m_wallet->set_account_tag(req.accounts, req.tag); + } + catch (const std::exception& e) + { + handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR); + return false; + } + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_untag_accounts(const wallet_rpc::COMMAND_RPC_UNTAG_ACCOUNTS::request& req, wallet_rpc::COMMAND_RPC_UNTAG_ACCOUNTS::response& res, epee::json_rpc::error& er) + { + try + { + m_wallet->set_account_tag(req.accounts, ""); + } + catch (const std::exception& e) + { + handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR); + return false; + } + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_set_account_tag_description(const wallet_rpc::COMMAND_RPC_SET_ACCOUNT_TAG_DESCRIPTION::request& req, wallet_rpc::COMMAND_RPC_SET_ACCOUNT_TAG_DESCRIPTION::response& res, epee::json_rpc::error& er) + { + try + { + m_wallet->set_account_tag_description(req.tag, req.description); + } + catch (const std::exception& e) + { + handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR); + return false; + } + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_getheight(const wallet_rpc::COMMAND_RPC_GET_HEIGHT::request& req, wallet_rpc::COMMAND_RPC_GET_HEIGHT::response& res, epee::json_rpc::error& er) { if (!m_wallet) return not_open(er); @@ -484,7 +567,7 @@ namespace tools return true; } //------------------------------------------------------------------------------------------------------------------------------ - bool wallet_rpc_server::validate_transfer(const std::list<wallet_rpc::transfer_destination>& destinations, const std::string& payment_id, std::vector<cryptonote::tx_destination_entry>& dsts, std::vector<uint8_t>& extra, epee::json_rpc::error& er) + bool wallet_rpc_server::validate_transfer(const std::list<wallet_rpc::transfer_destination>& destinations, const std::string& payment_id, std::vector<cryptonote::tx_destination_entry>& dsts, std::vector<uint8_t>& extra, bool at_least_one_destination, epee::json_rpc::error& er) { crypto::hash8 integrated_payment_id = crypto::null_hash8; std::string extra_nonce; @@ -539,6 +622,13 @@ namespace tools } } + if (at_least_one_destination && dsts.empty()) + { + er.code = WALLET_RPC_ERROR_CODE_ZERO_DESTINATION; + er.message = "No destinations for this transfer"; + return false; + } + if (!payment_id.empty()) { @@ -590,7 +680,7 @@ namespace tools } // validate the transfer requested and populate dsts & extra - if (!validate_transfer(req.destinations, req.payment_id, dsts, extra, er)) + if (!validate_transfer(req.destinations, req.payment_id, dsts, extra, true, er)) { return false; } @@ -600,6 +690,13 @@ namespace tools uint64_t mixin = m_wallet->adjust_mixin(req.mixin); std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_2(dsts, mixin, req.unlock_time, req.priority, extra, req.account_index, req.subaddr_indices, m_trusted_daemon); + if (ptx_vector.empty()) + { + er.code = WALLET_RPC_ERROR_CODE_TX_NOT_POSSIBLE; + er.message = "No transaction created"; + return false; + } + // reject proposed transactions if there are more than one. see on_transfer_split below. if (ptx_vector.size() != 1) { @@ -608,11 +705,6 @@ namespace tools return false; } - if (!req.do_not_relay) - m_wallet->commit_tx(ptx_vector); - - // populate response with tx hash - res.tx_hash = epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx_vector.back().tx)); if (req.get_tx_key) { res.tx_key = epee::string_tools::pod_to_hex(ptx_vector.back().tx_key); @@ -621,18 +713,45 @@ namespace tools } res.fee = ptx_vector.back().fee; - if (req.get_tx_hex) + if (m_wallet->multisig()) { - cryptonote::blobdata blob; - tx_to_blob(ptx_vector.back().tx, blob); - res.tx_blob = epee::string_tools::buff_to_hex_nodelimer(blob); + res.multisig_txset = epee::string_tools::buff_to_hex_nodelimer(m_wallet->save_multisig_tx(ptx_vector)); + if (res.multisig_txset.empty()) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "Failed to save multisig tx set after creation"; + return false; + } } - if (req.get_tx_metadata) + else { - std::ostringstream oss; - binary_archive<true> ar(oss); - ::serialization::serialize(ar, ptx_vector.back()); - res.tx_metadata = epee::string_tools::buff_to_hex_nodelimer(oss.str()); + if (!req.do_not_relay) + m_wallet->commit_tx(ptx_vector); + + // populate response with tx hash + res.tx_hash = epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx_vector.back().tx)); + if (req.get_tx_hex) + { + cryptonote::blobdata blob; + tx_to_blob(ptx_vector.back().tx, blob); + res.tx_blob = epee::string_tools::buff_to_hex_nodelimer(blob); + } + if (req.get_tx_metadata) + { + std::ostringstream oss; + boost::archive::portable_binary_oarchive ar(oss); + try + { + ar << ptx_vector.back(); + } + catch (...) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "Failed to save multisig tx set after creation"; + return false; + } + res.tx_metadata = epee::string_tools::buff_to_hex_nodelimer(oss.str()); + } } return true; } @@ -659,7 +778,7 @@ namespace tools } // validate the transfer requested and populate dsts & extra; RPC_TRANSFER::request and RPC_TRANSFER_SPLIT::request are identical types. - if (!validate_transfer(req.destinations, req.payment_id, dsts, extra, er)) + if (!validate_transfer(req.destinations, req.payment_id, dsts, extra, true, er)) { return false; } @@ -673,17 +792,9 @@ namespace tools ptx_vector = m_wallet->create_transactions_2(dsts, mixin, req.unlock_time, req.priority, extra, req.account_index, req.subaddr_indices, m_trusted_daemon); LOG_PRINT_L2("on_transfer_split called create_transactions_2"); - if (!req.do_not_relay) - { - LOG_PRINT_L2("on_transfer_split calling commit_tx"); - m_wallet->commit_tx(ptx_vector); - LOG_PRINT_L2("on_transfer_split called commit_tx"); - } - // populate response with tx hashes for (const auto & ptx : ptx_vector) { - res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx))); if (req.get_tx_keys) { res.tx_key_list.push_back(epee::string_tools::pod_to_hex(ptx.tx_key)); @@ -697,19 +808,56 @@ namespace tools res.amount_list.push_back(ptx_amount); res.fee_list.push_back(ptx.fee); + } - if (req.get_tx_hex) + if (m_wallet->multisig()) + { + res.multisig_txset = epee::string_tools::buff_to_hex_nodelimer(m_wallet->save_multisig_tx(ptx_vector)); + if (res.multisig_txset.empty()) { - cryptonote::blobdata blob; - tx_to_blob(ptx.tx, blob); - res.tx_blob_list.push_back(epee::string_tools::buff_to_hex_nodelimer(blob)); + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "Failed to save multisig tx set after creation"; + return false; } - if (req.get_tx_metadata) + } + + // populate response with tx hashes + for (const auto & ptx : ptx_vector) + { + if (!req.do_not_relay) { - std::ostringstream oss; - binary_archive<true> ar(oss); - ::serialization::serialize(ar, const_cast<tools::wallet2::pending_tx&>(ptx)); - res.tx_metadata_list.push_back(epee::string_tools::buff_to_hex_nodelimer(oss.str())); + LOG_PRINT_L2("on_transfer_split calling commit_tx"); + m_wallet->commit_tx(ptx_vector); + LOG_PRINT_L2("on_transfer_split called commit_tx"); + } + + // populate response with tx hashes + for (auto & ptx : ptx_vector) + { + res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx))); + + if (req.get_tx_hex) + { + cryptonote::blobdata blob; + tx_to_blob(ptx.tx, blob); + res.tx_blob_list.push_back(epee::string_tools::buff_to_hex_nodelimer(blob)); + } + if (req.get_tx_metadata) + { + std::ostringstream oss; + boost::archive::portable_binary_oarchive ar(oss); + try + { + ar << ptx; + } + catch (...) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "Failed to save multisig tx set after creation"; + return false; + } + res.tx_metadata_list.push_back(epee::string_tools::buff_to_hex_nodelimer(oss.str())); + } } } @@ -737,30 +885,65 @@ namespace tools { std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_unmixable_sweep_transactions(m_trusted_daemon); - if (!req.do_not_relay) - m_wallet->commit_tx(ptx_vector); - - // populate response with tx hashes for (const auto & ptx : ptx_vector) { - res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx))); if (req.get_tx_keys) { res.tx_key_list.push_back(epee::string_tools::pod_to_hex(ptx.tx_key)); } res.fee_list.push_back(ptx.fee); - if (req.get_tx_hex) + } + + if (m_wallet->multisig()) + { + for (tools::wallet2::pending_tx &ptx: ptx_vector) { - cryptonote::blobdata blob; - tx_to_blob(ptx.tx, blob); - res.tx_blob_list.push_back(epee::string_tools::buff_to_hex_nodelimer(blob)); + std::ostringstream oss; + boost::archive::portable_binary_oarchive ar(oss); + try + { + ar << ptx; + } + catch (...) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "Failed to save multisig tx set after creation"; + return false; + } + res.multisig_txset.push_back(epee::string_tools::buff_to_hex_nodelimer(oss.str())); } - if (req.get_tx_metadata) + } + else + { + if (!req.do_not_relay) + m_wallet->commit_tx(ptx_vector); + + // populate response with tx hashes + for (auto & ptx : ptx_vector) { - std::ostringstream oss; - binary_archive<true> ar(oss); - ::serialization::serialize(ar, const_cast<tools::wallet2::pending_tx&>(ptx)); - res.tx_metadata_list.push_back(epee::string_tools::buff_to_hex_nodelimer(oss.str())); + res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx))); + if (req.get_tx_hex) + { + cryptonote::blobdata blob; + tx_to_blob(ptx.tx, blob); + res.tx_blob_list.push_back(epee::string_tools::buff_to_hex_nodelimer(blob)); + } + if (req.get_tx_metadata) + { + std::ostringstream oss; + boost::archive::portable_binary_oarchive ar(oss); + try + { + ar << ptx; + } + catch (...) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "Failed to save multisig tx set after creation"; + return false; + } + res.tx_metadata_list.push_back(epee::string_tools::buff_to_hex_nodelimer(oss.str())); + } } } @@ -792,7 +975,7 @@ namespace tools destination.push_back(wallet_rpc::transfer_destination()); destination.back().amount = 0; destination.back().address = req.address; - if (!validate_transfer(destination, req.payment_id, dsts, extra, er)) + if (!validate_transfer(destination, req.payment_id, dsts, extra, true, er)) { return false; } @@ -802,29 +985,64 @@ namespace tools uint64_t mixin = m_wallet->adjust_mixin(req.mixin); std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_all(req.below_amount, dsts[0].addr, dsts[0].is_subaddress, mixin, req.unlock_time, req.priority, extra, req.account_index, req.subaddr_indices, m_trusted_daemon); - if (!req.do_not_relay) - m_wallet->commit_tx(ptx_vector); - - // populate response with tx hashes for (const auto & ptx : ptx_vector) { - res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx))); if (req.get_tx_keys) { res.tx_key_list.push_back(epee::string_tools::pod_to_hex(ptx.tx_key)); } - if (req.get_tx_hex) + } + + if (m_wallet->multisig()) + { + for (tools::wallet2::pending_tx &ptx: ptx_vector) { - cryptonote::blobdata blob; - tx_to_blob(ptx.tx, blob); - res.tx_blob_list.push_back(epee::string_tools::buff_to_hex_nodelimer(blob)); + std::ostringstream oss; + boost::archive::portable_binary_oarchive ar(oss); + try + { + ar << ptx; + } + catch (...) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "Failed to save multisig tx set after creation"; + return false; + } + res.multisig_txset.push_back(epee::string_tools::buff_to_hex_nodelimer(oss.str())); } - if (req.get_tx_metadata) + } + else + { + if (!req.do_not_relay) + m_wallet->commit_tx(ptx_vector); + + // populate response with tx hashes + for (auto & ptx : ptx_vector) { - std::ostringstream oss; - binary_archive<true> ar(oss); - ::serialization::serialize(ar, const_cast<tools::wallet2::pending_tx&>(ptx)); - res.tx_metadata_list.push_back(epee::string_tools::buff_to_hex_nodelimer(oss.str())); + res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx))); + if (req.get_tx_hex) + { + cryptonote::blobdata blob; + tx_to_blob(ptx.tx, blob); + res.tx_blob_list.push_back(epee::string_tools::buff_to_hex_nodelimer(blob)); + } + if (req.get_tx_metadata) + { + std::ostringstream oss; + boost::archive::portable_binary_oarchive ar(oss); + try + { + ar << ptx; + } + catch (...) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "Failed to save multisig tx set after creation"; + return false; + } + res.tx_metadata_list.push_back(epee::string_tools::buff_to_hex_nodelimer(oss.str())); + } } } @@ -856,7 +1074,7 @@ namespace tools destination.push_back(wallet_rpc::transfer_destination()); destination.back().amount = 0; destination.back().address = req.address; - if (!validate_transfer(destination, req.payment_id, dsts, extra, er)) + if (!validate_transfer(destination, req.payment_id, dsts, extra, true, er)) { return false; } @@ -886,37 +1104,59 @@ namespace tools er.message = "Multiple transactions are created, which is not supposed to happen"; return false; } - if (ptx_vector[0].selected_transfers.size() > 1) + const wallet2::pending_tx &ptx = ptx_vector[0]; + if (ptx.selected_transfers.size() > 1) { er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; er.message = "The transaction uses multiple inputs, which is not supposed to happen"; return false; } - if (!req.do_not_relay) - m_wallet->commit_tx(ptx_vector); - - // populate response with tx hashes - const wallet2::pending_tx &ptx = ptx_vector[0]; - res.tx_hash = epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx)); if (req.get_tx_key) { res.tx_key = epee::string_tools::pod_to_hex(ptx.tx_key); } - if (req.get_tx_hex) + + if (m_wallet->multisig()) { - cryptonote::blobdata blob; - tx_to_blob(ptx.tx, blob); - res.tx_blob = epee::string_tools::buff_to_hex_nodelimer(blob); + res.multisig_txset = epee::string_tools::buff_to_hex_nodelimer(m_wallet->save_multisig_tx(ptx_vector)); + if (res.multisig_txset.empty()) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "Failed to save multisig tx set after creation"; + return false; + } } - if (req.get_tx_metadata) + else { - std::ostringstream oss; - binary_archive<true> ar(oss); - ::serialization::serialize(ar, const_cast<tools::wallet2::pending_tx&>(ptx)); - res.tx_metadata = epee::string_tools::buff_to_hex_nodelimer(oss.str()); - } + if (!req.do_not_relay) + m_wallet->commit_tx(ptx_vector); + // populate response with tx hashes + res.tx_hash = epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx)); + if (req.get_tx_hex) + { + cryptonote::blobdata blob; + tx_to_blob(ptx.tx, blob); + res.tx_blob = epee::string_tools::buff_to_hex_nodelimer(blob); + } + if (req.get_tx_metadata) + { + std::ostringstream oss; + boost::archive::portable_binary_oarchive ar(oss); + try + { + ar << ptx; + } + catch (...) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "Failed to save multisig tx set after creation"; + return false; + } + res.tx_metadata = epee::string_tools::buff_to_hex_nodelimer(oss.str()); + } + } return true; } catch (const tools::error::daemon_busy& e) @@ -952,13 +1192,14 @@ namespace tools return false; } - std::stringstream ss; - ss << blob; - binary_archive<false> ba(ss); - tools::wallet2::pending_tx ptx; - bool r = ::serialization::serialize(ba, ptx); - if (!r) + try + { + std::istringstream iss(blob); + boost::archive::portable_binary_iarchive ar(iss); + ar >> ptx; + } + catch (...) { er.code = WALLET_RPC_ERROR_CODE_BAD_TX_METADATA; er.message = "Failed to parse tx metadata."; @@ -1108,6 +1349,7 @@ namespace tools rpc_payment.block_height = payment.m_block_height; rpc_payment.unlock_time = payment.m_unlock_time; rpc_payment.subaddr_index = payment.m_subaddr_index; + rpc_payment.address = m_wallet->get_subaddress_as_str(payment.m_subaddr_index); res.payments.push_back(rpc_payment); } @@ -1134,6 +1376,7 @@ namespace tools rpc_payment.block_height = payment.second.m_block_height; rpc_payment.unlock_time = payment.second.m_unlock_time; rpc_payment.subaddr_index = payment.second.m_subaddr_index; + rpc_payment.address = m_wallet->get_subaddress_as_str(payment.second.m_subaddr_index); res.payments.push_back(std::move(rpc_payment)); } @@ -1184,6 +1427,7 @@ namespace tools rpc_payment.block_height = payment.m_block_height; rpc_payment.unlock_time = payment.m_unlock_time; rpc_payment.subaddr_index = payment.m_subaddr_index; + rpc_payment.address = m_wallet->get_subaddress_as_str(payment.m_subaddr_index); res.payments.push_back(std::move(rpc_payment)); } } @@ -2338,6 +2582,375 @@ namespace tools } } //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_is_multisig(const wallet_rpc::COMMAND_RPC_IS_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_IS_MULTISIG::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + res.multisig = m_wallet->multisig(&res.ready, &res.threshold, &res.total); + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_prepare_multisig(const wallet_rpc::COMMAND_RPC_PREPARE_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_PREPARE_MULTISIG::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + if (m_wallet->restricted()) + { + er.code = WALLET_RPC_ERROR_CODE_DENIED; + er.message = "Command unavailable in restricted mode."; + return false; + } + if (m_wallet->multisig()) + { + er.code = WALLET_RPC_ERROR_CODE_ALREADY_MULTISIG; + er.message = "This wallet is already multisig"; + return false; + } + if (m_wallet->watch_only()) + { + er.code = WALLET_RPC_ERROR_CODE_WATCH_ONLY; + er.message = "wallet is watch-only and cannot be made multisig"; + return false; + } + + res.multisig_info = m_wallet->get_multisig_info(); + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_make_multisig(const wallet_rpc::COMMAND_RPC_MAKE_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_MAKE_MULTISIG::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + if (m_wallet->restricted()) + { + er.code = WALLET_RPC_ERROR_CODE_DENIED; + er.message = "Command unavailable in restricted mode."; + return false; + } + if (m_wallet->multisig()) + { + er.code = WALLET_RPC_ERROR_CODE_ALREADY_MULTISIG; + er.message = "This wallet is already multisig"; + return false; + } + if (m_wallet->watch_only()) + { + er.code = WALLET_RPC_ERROR_CODE_WATCH_ONLY; + er.message = "wallet is watch-only and cannot be made multisig"; + return false; + } + + try + { + res.multisig_info = m_wallet->make_multisig(req.password, req.multisig_info, req.threshold); + res.address = m_wallet->get_account().get_public_address_str(m_wallet->testnet()); + } + catch (const std::exception &e) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = e.what(); + return false; + } + + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_export_multisig(const wallet_rpc::COMMAND_RPC_EXPORT_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_EXPORT_MULTISIG::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + if (m_wallet->restricted()) + { + er.code = WALLET_RPC_ERROR_CODE_DENIED; + er.message = "Command unavailable in restricted mode."; + return false; + } + bool ready; + if (!m_wallet->multisig(&ready)) + { + er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG; + er.message = "This wallet is not multisig"; + return false; + } + if (!ready) + { + er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG; + er.message = "This wallet is multisig, but not yet finalized"; + return false; + } + + cryptonote::blobdata info; + try + { + info = m_wallet->export_multisig(); + } + catch (const std::exception &e) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = e.what(); + return false; + } + + res.info = epee::string_tools::buff_to_hex_nodelimer(info); + + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_import_multisig(const wallet_rpc::COMMAND_RPC_IMPORT_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_IMPORT_MULTISIG::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + if (m_wallet->restricted()) + { + er.code = WALLET_RPC_ERROR_CODE_DENIED; + er.message = "Command unavailable in restricted mode."; + return false; + } + bool ready; + uint32_t threshold, total; + if (!m_wallet->multisig(&ready, &threshold, &total)) + { + er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG; + er.message = "This wallet is not multisig"; + return false; + } + if (!ready) + { + er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG; + er.message = "This wallet is multisig, but not yet finalized"; + return false; + } + + if (req.info.size() < threshold - 1) + { + er.code = WALLET_RPC_ERROR_CODE_THRESHOLD_NOT_REACHED; + er.message = "Needs multisig export info from more participants"; + return false; + } + + std::vector<cryptonote::blobdata> info; + info.resize(req.info.size()); + for (size_t n = 0; n < info.size(); ++n) + { + if (!epee::string_tools::parse_hexstr_to_binbuff(req.info[n], info[n])) + { + er.code = WALLET_RPC_ERROR_CODE_BAD_HEX; + er.message = "Failed to parse hex."; + return false; + } + } + + try + { + res.n_outputs = m_wallet->import_multisig(info); + } + catch (const std::exception &e) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "Error calling import_multisig"; + return false; + } + + if (m_trusted_daemon) + { + try + { + m_wallet->rescan_spent(); + } + catch (const std::exception &e) + { + er.message = std::string("Success, but failed to update spent status after import multisig info: ") + e.what(); + } + } + else + { + er.message = "Success, but cannot update spent status after import multisig info as dameon is untrusted"; + } + + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_finalize_multisig(const wallet_rpc::COMMAND_RPC_FINALIZE_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_FINALIZE_MULTISIG::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + if (m_wallet->restricted()) + { + er.code = WALLET_RPC_ERROR_CODE_DENIED; + er.message = "Command unavailable in restricted mode."; + return false; + } + bool ready; + uint32_t threshold, total; + if (!m_wallet->multisig(&ready, &threshold, &total)) + { + er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG; + er.message = "This wallet is not multisig"; + return false; + } + if (ready) + { + er.code = WALLET_RPC_ERROR_CODE_ALREADY_MULTISIG; + er.message = "This wallet is multisig, and already finalized"; + return false; + } + + if (req.multisig_info.size() < threshold - 1) + { + er.code = WALLET_RPC_ERROR_CODE_THRESHOLD_NOT_REACHED; + er.message = "Needs multisig info from more participants"; + return false; + } + + try + { + if (!m_wallet->finalize_multisig(req.password, req.multisig_info)) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "Error calling finalize_multisig"; + return false; + } + } + catch (const std::exception &e) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = std::string("Error calling finalize_multisig: ") + e.what(); + return false; + } + res.address = m_wallet->get_account().get_public_address_str(m_wallet->testnet()); + + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_sign_multisig(const wallet_rpc::COMMAND_RPC_SIGN_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_SIGN_MULTISIG::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + if (m_wallet->restricted()) + { + er.code = WALLET_RPC_ERROR_CODE_DENIED; + er.message = "Command unavailable in restricted mode."; + return false; + } + bool ready; + uint32_t threshold, total; + if (!m_wallet->multisig(&ready, &threshold, &total)) + { + er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG; + er.message = "This wallet is not multisig"; + return false; + } + if (!ready) + { + er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG; + er.message = "This wallet is multisig, but not yet finalized"; + return false; + } + + cryptonote::blobdata blob; + if (!epee::string_tools::parse_hexstr_to_binbuff(req.tx_data_hex, blob)) + { + er.code = WALLET_RPC_ERROR_CODE_BAD_HEX; + er.message = "Failed to parse hex."; + return false; + } + + tools::wallet2::multisig_tx_set txs; + bool r = m_wallet->load_multisig_tx(blob, txs, NULL); + if (!r) + { + er.code = WALLET_RPC_ERROR_CODE_BAD_MULTISIG_TX_DATA; + er.message = "Failed to parse multisig tx data."; + return false; + } + + std::vector<crypto::hash> txids; + try + { + bool r = m_wallet->sign_multisig_tx(txs, txids); + if (!r) + { + er.code = WALLET_RPC_ERROR_CODE_MULTISIG_SIGNATURE; + er.message = "Failed to sign multisig tx"; + return false; + } + } + catch (const std::exception &e) + { + er.code = WALLET_RPC_ERROR_CODE_MULTISIG_SIGNATURE; + er.message = std::string("Failed to sign multisig tx: ") + e.what(); + return false; + } + + res.tx_data_hex = epee::string_tools::buff_to_hex_nodelimer(m_wallet->save_multisig_tx(txs)); + if (!txids.empty()) + { + for (const crypto::hash &txid: txids) + res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(txid)); + } + + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_submit_multisig(const wallet_rpc::COMMAND_RPC_SUBMIT_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_SUBMIT_MULTISIG::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + if (m_wallet->restricted()) + { + er.code = WALLET_RPC_ERROR_CODE_DENIED; + er.message = "Command unavailable in restricted mode."; + return false; + } + bool ready; + uint32_t threshold, total; + if (!m_wallet->multisig(&ready, &threshold, &total)) + { + er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG; + er.message = "This wallet is not multisig"; + return false; + } + if (!ready) + { + er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG; + er.message = "This wallet is multisig, but not yet finalized"; + return false; + } + + cryptonote::blobdata blob; + if (!epee::string_tools::parse_hexstr_to_binbuff(req.tx_data_hex, blob)) + { + er.code = WALLET_RPC_ERROR_CODE_BAD_HEX; + er.message = "Failed to parse hex."; + return false; + } + + tools::wallet2::multisig_tx_set txs; + bool r = m_wallet->load_multisig_tx(blob, txs, NULL); + if (!r) + { + er.code = WALLET_RPC_ERROR_CODE_BAD_MULTISIG_TX_DATA; + er.message = "Failed to parse multisig tx data."; + return false; + } + + if (txs.m_signers.size() < threshold) + { + er.code = WALLET_RPC_ERROR_CODE_THRESHOLD_NOT_REACHED; + er.message = "Not enough signers signed this transaction."; + return false; + } + + try + { + for (auto &ptx: txs.m_ptx) + { + m_wallet->commit_tx(ptx); + res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx))); + } + } + catch (const std::exception &e) + { + er.code = WALLET_RPC_ERROR_CODE_MULTISIG_SUBMISSION; + er.message = std::string("Failed to submit multisig tx: ") + e.what(); + return false; + } + + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ } int main(int argc, char** argv) { @@ -2360,6 +2973,7 @@ int main(int argc, char** argv) { const auto vm = wallet_args::main( argc, argv, "monero-wallet-rpc [--wallet-file=<file>|--generate-from-json=<file>|--wallet-dir=<directory>] [--rpc-bind-port=<port>]", + tools::wallet_rpc_server::tr("This is the RPC monero wallet. It needs to connect to a monero\ndaemon to work correctly."), desc_params, po::positional_options_description(), [](const std::string &s, bool emphasis){ epee::set_console_color(emphasis ? epee::console_color_white : epee::console_color_default, true); std::cout << s << std::endl; if (emphasis) epee::reset_console_color(); }, diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h index 9455c4769..b20198b78 100644 --- a/src/wallet/wallet_rpc_server.h +++ b/src/wallet/wallet_rpc_server.h @@ -74,6 +74,10 @@ namespace tools MAP_JON_RPC_WE("get_accounts", on_get_accounts, wallet_rpc::COMMAND_RPC_GET_ACCOUNTS) MAP_JON_RPC_WE("create_account", on_create_account, wallet_rpc::COMMAND_RPC_CREATE_ACCOUNT) MAP_JON_RPC_WE("label_account", on_label_account, wallet_rpc::COMMAND_RPC_LABEL_ACCOUNT) + MAP_JON_RPC_WE("get_account_tags", on_get_account_tags, wallet_rpc::COMMAND_RPC_GET_ACCOUNT_TAGS) + MAP_JON_RPC_WE("tag_accounts", on_tag_accounts, wallet_rpc::COMMAND_RPC_TAG_ACCOUNTS) + MAP_JON_RPC_WE("untag_accounts", on_untag_accounts, wallet_rpc::COMMAND_RPC_UNTAG_ACCOUNTS) + MAP_JON_RPC_WE("set_account_tag_description", on_set_account_tag_description, wallet_rpc::COMMAND_RPC_SET_ACCOUNT_TAG_DESCRIPTION) MAP_JON_RPC_WE("getheight", on_getheight, wallet_rpc::COMMAND_RPC_GET_HEIGHT) MAP_JON_RPC_WE("transfer", on_transfer, wallet_rpc::COMMAND_RPC_TRANSFER) MAP_JON_RPC_WE("transfer_split", on_transfer_split, wallet_rpc::COMMAND_RPC_TRANSFER_SPLIT) @@ -117,6 +121,14 @@ namespace tools MAP_JON_RPC_WE("get_languages", on_get_languages, wallet_rpc::COMMAND_RPC_GET_LANGUAGES) MAP_JON_RPC_WE("create_wallet", on_create_wallet, wallet_rpc::COMMAND_RPC_CREATE_WALLET) MAP_JON_RPC_WE("open_wallet", on_open_wallet, wallet_rpc::COMMAND_RPC_OPEN_WALLET) + MAP_JON_RPC_WE("is_multisig", on_is_multisig, wallet_rpc::COMMAND_RPC_IS_MULTISIG) + MAP_JON_RPC_WE("prepare_multisig", on_prepare_multisig, wallet_rpc::COMMAND_RPC_PREPARE_MULTISIG) + MAP_JON_RPC_WE("make_multisig", on_make_multisig, wallet_rpc::COMMAND_RPC_MAKE_MULTISIG) + MAP_JON_RPC_WE("export_multisig_info", on_export_multisig, wallet_rpc::COMMAND_RPC_EXPORT_MULTISIG) + MAP_JON_RPC_WE("import_multisig_info", on_import_multisig, wallet_rpc::COMMAND_RPC_IMPORT_MULTISIG) + MAP_JON_RPC_WE("finalize_multisig", on_finalize_multisig, wallet_rpc::COMMAND_RPC_FINALIZE_MULTISIG) + MAP_JON_RPC_WE("sign_multisig", on_sign_multisig, wallet_rpc::COMMAND_RPC_SIGN_MULTISIG) + MAP_JON_RPC_WE("submit_multisig", on_submit_multisig, wallet_rpc::COMMAND_RPC_SUBMIT_MULTISIG) END_JSON_RPC_MAP() END_URI_MAP2() @@ -128,8 +140,12 @@ namespace tools bool on_get_accounts(const wallet_rpc::COMMAND_RPC_GET_ACCOUNTS::request& req, wallet_rpc::COMMAND_RPC_GET_ACCOUNTS::response& res, epee::json_rpc::error& er); bool on_create_account(const wallet_rpc::COMMAND_RPC_CREATE_ACCOUNT::request& req, wallet_rpc::COMMAND_RPC_CREATE_ACCOUNT::response& res, epee::json_rpc::error& er); bool on_label_account(const wallet_rpc::COMMAND_RPC_LABEL_ACCOUNT::request& req, wallet_rpc::COMMAND_RPC_LABEL_ACCOUNT::response& res, epee::json_rpc::error& er); + bool on_get_account_tags(const wallet_rpc::COMMAND_RPC_GET_ACCOUNT_TAGS::request& req, wallet_rpc::COMMAND_RPC_GET_ACCOUNT_TAGS::response& res, epee::json_rpc::error& er); + bool on_tag_accounts(const wallet_rpc::COMMAND_RPC_TAG_ACCOUNTS::request& req, wallet_rpc::COMMAND_RPC_TAG_ACCOUNTS::response& res, epee::json_rpc::error& er); + bool on_untag_accounts(const wallet_rpc::COMMAND_RPC_UNTAG_ACCOUNTS::request& req, wallet_rpc::COMMAND_RPC_UNTAG_ACCOUNTS::response& res, epee::json_rpc::error& er); + bool on_set_account_tag_description(const wallet_rpc::COMMAND_RPC_SET_ACCOUNT_TAG_DESCRIPTION::request& req, wallet_rpc::COMMAND_RPC_SET_ACCOUNT_TAG_DESCRIPTION::response& res, epee::json_rpc::error& er); bool on_getheight(const wallet_rpc::COMMAND_RPC_GET_HEIGHT::request& req, wallet_rpc::COMMAND_RPC_GET_HEIGHT::response& res, epee::json_rpc::error& er); - bool validate_transfer(const std::list<wallet_rpc::transfer_destination>& destinations, const std::string& payment_id, std::vector<cryptonote::tx_destination_entry>& dsts, std::vector<uint8_t>& extra, epee::json_rpc::error& er); + bool validate_transfer(const std::list<wallet_rpc::transfer_destination>& destinations, const std::string& payment_id, std::vector<cryptonote::tx_destination_entry>& dsts, std::vector<uint8_t>& extra, bool at_least_one_destination, epee::json_rpc::error& er); bool on_transfer(const wallet_rpc::COMMAND_RPC_TRANSFER::request& req, wallet_rpc::COMMAND_RPC_TRANSFER::response& res, epee::json_rpc::error& er); bool on_transfer_split(const wallet_rpc::COMMAND_RPC_TRANSFER_SPLIT::request& req, wallet_rpc::COMMAND_RPC_TRANSFER_SPLIT::response& res, epee::json_rpc::error& er); bool on_sweep_dust(const wallet_rpc::COMMAND_RPC_SWEEP_DUST::request& req, wallet_rpc::COMMAND_RPC_SWEEP_DUST::response& res, epee::json_rpc::error& er); @@ -171,6 +187,14 @@ namespace tools bool on_get_languages(const wallet_rpc::COMMAND_RPC_GET_LANGUAGES::request& req, wallet_rpc::COMMAND_RPC_GET_LANGUAGES::response& res, epee::json_rpc::error& er); bool on_create_wallet(const wallet_rpc::COMMAND_RPC_CREATE_WALLET::request& req, wallet_rpc::COMMAND_RPC_CREATE_WALLET::response& res, epee::json_rpc::error& er); bool on_open_wallet(const wallet_rpc::COMMAND_RPC_OPEN_WALLET::request& req, wallet_rpc::COMMAND_RPC_OPEN_WALLET::response& res, epee::json_rpc::error& er); + bool on_is_multisig(const wallet_rpc::COMMAND_RPC_IS_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_IS_MULTISIG::response& res, epee::json_rpc::error& er); + bool on_prepare_multisig(const wallet_rpc::COMMAND_RPC_PREPARE_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_PREPARE_MULTISIG::response& res, epee::json_rpc::error& er); + bool on_make_multisig(const wallet_rpc::COMMAND_RPC_MAKE_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_MAKE_MULTISIG::response& res, epee::json_rpc::error& er); + bool on_export_multisig(const wallet_rpc::COMMAND_RPC_EXPORT_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_EXPORT_MULTISIG::response& res, epee::json_rpc::error& er); + bool on_import_multisig(const wallet_rpc::COMMAND_RPC_IMPORT_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_IMPORT_MULTISIG::response& res, epee::json_rpc::error& er); + bool on_finalize_multisig(const wallet_rpc::COMMAND_RPC_FINALIZE_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_FINALIZE_MULTISIG::response& res, epee::json_rpc::error& er); + bool on_sign_multisig(const wallet_rpc::COMMAND_RPC_SIGN_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_SIGN_MULTISIG::response& res, epee::json_rpc::error& er); + bool on_submit_multisig(const wallet_rpc::COMMAND_RPC_SUBMIT_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_SUBMIT_MULTISIG::response& res, epee::json_rpc::error& er); //json rpc v2 bool on_query_key(const wallet_rpc::COMMAND_RPC_QUERY_KEY::request& req, wallet_rpc::COMMAND_RPC_QUERY_KEY::response& res, epee::json_rpc::error& er); diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h index e084d9e6d..76c02039b 100644 --- a/src/wallet/wallet_rpc_server_commands_defs.h +++ b/src/wallet/wallet_rpc_server_commands_defs.h @@ -78,11 +78,13 @@ namespace wallet_rpc { uint64_t balance; uint64_t unlocked_balance; + bool multisig_import_needed; std::vector<per_subaddress_info> per_subaddress; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(balance) KV_SERIALIZE(unlocked_balance) + KV_SERIALIZE(multisig_import_needed) KV_SERIALIZE(per_subaddress) END_KV_SERIALIZE_MAP() }; @@ -93,8 +95,10 @@ namespace wallet_rpc struct request { uint32_t account_index; + std::vector<uint32_t> address_index; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(account_index) + KV_SERIALIZE(address_index) END_KV_SERIALIZE_MAP() }; @@ -174,7 +178,10 @@ namespace wallet_rpc { struct request { + std::string tag; // all accounts if empty, otherwise those accounts with this tag + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(tag) END_KV_SERIALIZE_MAP() }; @@ -185,6 +192,7 @@ namespace wallet_rpc uint64_t balance; uint64_t unlocked_balance; std::string label; + std::string tag; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(account_index) @@ -192,6 +200,7 @@ namespace wallet_rpc KV_SERIALIZE(balance) KV_SERIALIZE(unlocked_balance) KV_SERIALIZE(label) + KV_SERIALIZE(tag) END_KV_SERIALIZE_MAP() }; @@ -250,6 +259,95 @@ namespace wallet_rpc }; }; + struct COMMAND_RPC_GET_ACCOUNT_TAGS + { + struct request + { + BEGIN_KV_SERIALIZE_MAP() + END_KV_SERIALIZE_MAP() + }; + + struct account_tag_info + { + std::string tag; + std::string label; + std::vector<uint32_t> accounts; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(tag); + KV_SERIALIZE(label); + KV_SERIALIZE(accounts); + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::vector<account_tag_info> account_tags; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(account_tags) + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_TAG_ACCOUNTS + { + struct request + { + std::string tag; + std::set<uint32_t> accounts; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(tag) + KV_SERIALIZE(accounts) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + BEGIN_KV_SERIALIZE_MAP() + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_UNTAG_ACCOUNTS + { + struct request + { + std::set<uint32_t> accounts; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(accounts) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + BEGIN_KV_SERIALIZE_MAP() + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_SET_ACCOUNT_TAG_DESCRIPTION + { + struct request + { + std::string tag; + std::string description; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(tag) + KV_SERIALIZE(description) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + BEGIN_KV_SERIALIZE_MAP() + END_KV_SERIALIZE_MAP() + }; + }; + struct COMMAND_RPC_GET_HEIGHT { struct request @@ -316,6 +414,7 @@ namespace wallet_rpc uint64_t fee; std::string tx_blob; std::string tx_metadata; + std::string multisig_txset; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(tx_hash) @@ -324,6 +423,7 @@ namespace wallet_rpc KV_SERIALIZE(fee) KV_SERIALIZE(tx_blob) KV_SERIALIZE(tx_metadata) + KV_SERIALIZE(multisig_txset) END_KV_SERIALIZE_MAP() }; }; @@ -376,6 +476,7 @@ namespace wallet_rpc std::list<uint64_t> fee_list; std::list<std::string> tx_blob_list; std::list<std::string> tx_metadata_list; + std::string multisig_txset; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(tx_hash_list) @@ -384,6 +485,7 @@ namespace wallet_rpc KV_SERIALIZE(fee_list) KV_SERIALIZE(tx_blob_list) KV_SERIALIZE(tx_metadata_list) + KV_SERIALIZE(multisig_txset) END_KV_SERIALIZE_MAP() }; }; @@ -421,6 +523,7 @@ namespace wallet_rpc std::list<uint64_t> fee_list; std::list<std::string> tx_blob_list; std::list<std::string> tx_metadata_list; + std::list<std::string> multisig_txset; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(tx_hash_list) @@ -428,6 +531,7 @@ namespace wallet_rpc KV_SERIALIZE(fee_list) KV_SERIALIZE(tx_blob_list) KV_SERIALIZE(tx_metadata_list) + KV_SERIALIZE(multisig_txset) END_KV_SERIALIZE_MAP() }; }; @@ -481,6 +585,7 @@ namespace wallet_rpc std::list<uint64_t> fee_list; std::list<std::string> tx_blob_list; std::list<std::string> tx_metadata_list; + std::list<std::string> multisig_txset; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(tx_hash_list) @@ -488,6 +593,7 @@ namespace wallet_rpc KV_SERIALIZE(fee_list) KV_SERIALIZE(tx_blob_list) KV_SERIALIZE(tx_metadata_list) + KV_SERIALIZE(multisig_txset) END_KV_SERIALIZE_MAP() }; }; @@ -528,6 +634,7 @@ namespace wallet_rpc uint64_t fee; std::string tx_blob; std::string tx_metadata; + std::string multisig_txset; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(tx_hash) @@ -535,6 +642,7 @@ namespace wallet_rpc KV_SERIALIZE(fee) KV_SERIALIZE(tx_blob) KV_SERIALIZE(tx_metadata) + KV_SERIALIZE(multisig_txset) END_KV_SERIALIZE_MAP() }; }; @@ -589,6 +697,7 @@ namespace wallet_rpc uint64_t block_height; uint64_t unlock_time; cryptonote::subaddress_index subaddr_index; + std::string address; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(payment_id) @@ -597,6 +706,7 @@ namespace wallet_rpc KV_SERIALIZE(block_height) KV_SERIALIZE(unlock_time) KV_SERIALIZE(subaddr_index) + KV_SERIALIZE(address) END_KV_SERIALIZE_MAP() }; @@ -1485,5 +1595,181 @@ namespace wallet_rpc END_KV_SERIALIZE_MAP() }; }; + + struct COMMAND_RPC_IS_MULTISIG + { + struct request + { + BEGIN_KV_SERIALIZE_MAP() + END_KV_SERIALIZE_MAP() + }; + + struct response + { + bool multisig; + bool ready; + uint32_t threshold; + uint32_t total; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(multisig) + KV_SERIALIZE(ready) + KV_SERIALIZE(threshold) + KV_SERIALIZE(total) + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_PREPARE_MULTISIG + { + struct request + { + BEGIN_KV_SERIALIZE_MAP() + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::string multisig_info; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(multisig_info) + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_MAKE_MULTISIG + { + struct request + { + std::vector<std::string> multisig_info; + uint32_t threshold; + std::string password; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(multisig_info) + KV_SERIALIZE(threshold) + KV_SERIALIZE(password) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::string address; + std::string multisig_info; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(address) + KV_SERIALIZE(multisig_info) + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_EXPORT_MULTISIG + { + struct request + { + BEGIN_KV_SERIALIZE_MAP() + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::string info; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(info) + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_IMPORT_MULTISIG + { + struct request + { + std::vector<std::string> info; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(info) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + uint64_t n_outputs; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(n_outputs) + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_FINALIZE_MULTISIG + { + struct request + { + std::string password; + std::vector<std::string> multisig_info; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(password) + KV_SERIALIZE(multisig_info) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::string address; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(address) + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_SIGN_MULTISIG + { + struct request + { + std::string tx_data_hex; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(tx_data_hex) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::string tx_data_hex; + std::list<std::string> tx_hash_list; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(tx_data_hex) + KV_SERIALIZE(tx_hash_list) + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_SUBMIT_MULTISIG + { + struct request + { + std::string tx_data_hex; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(tx_data_hex) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::list<std::string> tx_hash_list; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(tx_hash_list) + END_KV_SERIALIZE_MAP() + }; + }; + } } diff --git a/src/wallet/wallet_rpc_server_error_codes.h b/src/wallet/wallet_rpc_server_error_codes.h index c3f3e20d1..578413e38 100644 --- a/src/wallet/wallet_rpc_server_error_codes.h +++ b/src/wallet/wallet_rpc_server_error_codes.h @@ -58,3 +58,12 @@ #define WALLET_RPC_ERROR_CODE_WRONG_KEY -25 #define WALLET_RPC_ERROR_CODE_BAD_HEX -26 #define WALLET_RPC_ERROR_CODE_BAD_TX_METADATA -27 +#define WALLET_RPC_ERROR_CODE_ALREADY_MULTISIG -28 +#define WALLET_RPC_ERROR_CODE_WATCH_ONLY -29 +#define WALLET_RPC_ERROR_CODE_BAD_MULTISIG_INFO -30 +#define WALLET_RPC_ERROR_CODE_NOT_MULTISIG -31 +#define WALLET_RPC_ERROR_CODE_WRONG_LR -32 +#define WALLET_RPC_ERROR_CODE_THRESHOLD_NOT_REACHED -33 +#define WALLET_RPC_ERROR_CODE_BAD_MULTISIG_TX_DATA -34 +#define WALLET_RPC_ERROR_CODE_MULTISIG_SIGNATURE -35 +#define WALLET_RPC_ERROR_CODE_MULTISIG_SUBMISSION -36 diff --git a/tests/core_tests/CMakeLists.txt b/tests/core_tests/CMakeLists.txt index 68f2e9816..d80d62135 100644 --- a/tests/core_tests/CMakeLists.txt +++ b/tests/core_tests/CMakeLists.txt @@ -36,6 +36,7 @@ set(core_tests_sources chaingen_main.cpp double_spend.cpp integer_overflow.cpp + multisig.cpp ring_signature_1.cpp transaction_tests.cpp tx_validation.cpp @@ -52,6 +53,7 @@ set(core_tests_headers double_spend.h double_spend.inl integer_overflow.h + multisig.h ring_signature_1.h transaction_tests.h tx_validation.h @@ -63,6 +65,7 @@ add_executable(core_tests ${core_tests_headers}) target_link_libraries(core_tests PRIVATE + multisig cryptonote_core p2p version diff --git a/tests/core_tests/chaingen.h b/tests/core_tests/chaingen.h index 0dcbc7f0c..9fed95183 100644 --- a/tests/core_tests/chaingen.h +++ b/tests/core_tests/chaingen.h @@ -505,6 +505,56 @@ inline bool do_replay_file(const std::string& filename) cryptonote::account_base account; \ account.generate(); +#define GENERATE_MULTISIG_ACCOUNT(account, threshold, total) \ + CHECK_AND_ASSERT_MES(threshold >= 2 && threshold <= total, false, "Invalid multisig scheme"); \ + std::vector<cryptonote::account_base> account(total); \ + do \ + { \ + for (size_t msidx = 0; msidx < total; ++msidx) \ + account[msidx].generate(); \ + std::unordered_set<crypto::public_key> all_multisig_keys; \ + std::vector<std::vector<crypto::secret_key>> view_keys(total); \ + std::vector<std::vector<crypto::public_key>> spend_keys(total); \ + for (size_t msidx = 0; msidx < total; ++msidx) \ + { \ + for (size_t msidx_inner = 0; msidx_inner < total; ++msidx_inner) \ + { \ + if (msidx_inner != msidx) \ + { \ + crypto::secret_key vkh = cryptonote::get_multisig_blinded_secret_key(account[msidx_inner].get_keys().m_view_secret_key); \ + view_keys[msidx].push_back(vkh); \ + crypto::secret_key skh = cryptonote::get_multisig_blinded_secret_key(account[msidx_inner].get_keys().m_spend_secret_key); \ + crypto::public_key pskh; \ + crypto::secret_key_to_public_key(skh, pskh); \ + spend_keys[msidx].push_back(pskh); \ + } \ + } \ + } \ + for (size_t msidx = 0; msidx < total; ++msidx) \ + { \ + std::vector<crypto::secret_key> multisig_keys; \ + crypto::secret_key spend_skey; \ + crypto::public_key spend_pkey; \ + if (threshold == total) \ + cryptonote::generate_multisig_N_N(account[msidx].get_keys(), spend_keys[msidx], multisig_keys, (rct::key&)spend_skey, (rct::key&)spend_pkey); \ + else \ + cryptonote::generate_multisig_N1_N(account[msidx].get_keys(), spend_keys[msidx], multisig_keys, (rct::key&)spend_skey, (rct::key&)spend_pkey); \ + crypto::secret_key view_skey = cryptonote::generate_multisig_view_secret_key(account[msidx].get_keys().m_view_secret_key, view_keys[msidx]); \ + account[msidx].make_multisig(view_skey, spend_skey, spend_pkey, multisig_keys); \ + for (const auto &k: multisig_keys) \ + all_multisig_keys.insert(rct::rct2pk(rct::scalarmultBase(rct::sk2rct(k)))); \ + } \ + if (threshold < total) \ + { \ + std::vector<crypto::public_key> spend_public_keys; \ + for (const auto &k: all_multisig_keys) \ + spend_public_keys.push_back(k); \ + crypto::public_key spend_pkey = cryptonote::generate_multisig_N1_N_spend_public_key(spend_public_keys); \ + for (size_t msidx = 0; msidx < total; ++msidx) \ + account[msidx].finalize_multisig(spend_pkey); \ + } \ + } while(0) + #define MAKE_ACCOUNT(VEC_EVENTS, account) \ cryptonote::account_base account; \ account.generate(); \ diff --git a/tests/core_tests/chaingen_main.cpp b/tests/core_tests/chaingen_main.cpp index 9eba347cd..95284c11a 100644 --- a/tests/core_tests/chaingen_main.cpp +++ b/tests/core_tests/chaingen_main.cpp @@ -199,6 +199,25 @@ int main(int argc, char* argv[]) GENERATE_AND_PLAY(gen_rct_tx_pre_rct_altered_extra); GENERATE_AND_PLAY(gen_rct_tx_rct_altered_extra); + GENERATE_AND_PLAY(gen_multisig_tx_valid_22_1_2); + GENERATE_AND_PLAY(gen_multisig_tx_valid_22_1_2_many_inputs); + GENERATE_AND_PLAY(gen_multisig_tx_valid_22_2_1); + GENERATE_AND_PLAY(gen_multisig_tx_valid_33_1_23); + GENERATE_AND_PLAY(gen_multisig_tx_valid_33_3_21); + GENERATE_AND_PLAY(gen_multisig_tx_valid_23_1_2); + GENERATE_AND_PLAY(gen_multisig_tx_valid_23_1_3); + GENERATE_AND_PLAY(gen_multisig_tx_valid_23_2_1); + GENERATE_AND_PLAY(gen_multisig_tx_valid_23_2_3); + GENERATE_AND_PLAY(gen_multisig_tx_valid_45_1_234); + GENERATE_AND_PLAY(gen_multisig_tx_valid_45_4_135_many_inputs); + GENERATE_AND_PLAY(gen_multisig_tx_valid_89_3_1245789); + GENERATE_AND_PLAY(gen_multisig_tx_invalid_23_1__no_threshold); + GENERATE_AND_PLAY(gen_multisig_tx_invalid_45_5_23_no_threshold); + GENERATE_AND_PLAY(gen_multisig_tx_invalid_22_1__no_threshold); + GENERATE_AND_PLAY(gen_multisig_tx_invalid_33_1__no_threshold); + GENERATE_AND_PLAY(gen_multisig_tx_invalid_33_1_2_no_threshold); + GENERATE_AND_PLAY(gen_multisig_tx_invalid_33_1_3_no_threshold); + el::Level level = (failed_tests.empty() ? el::Level::Info : el::Level::Error); MLOG(level, "\nREPORT:"); MLOG(level, " Test run: " << tests_count); diff --git a/tests/core_tests/chaingen_tests_list.h b/tests/core_tests/chaingen_tests_list.h index 26f88dddb..b9bbf03b4 100644 --- a/tests/core_tests/chaingen_tests_list.h +++ b/tests/core_tests/chaingen_tests_list.h @@ -41,6 +41,7 @@ #include "tx_validation.h" #include "v2_tests.h" #include "rct.h" +#include "multisig.h" /************************************************************************/ /* */ /************************************************************************/ diff --git a/tests/core_tests/multisig.cpp b/tests/core_tests/multisig.cpp new file mode 100644 index 000000000..79a3a7cf4 --- /dev/null +++ b/tests/core_tests/multisig.cpp @@ -0,0 +1,523 @@ +// Copyright (c) 2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// 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 "common/apply_permutation.h" +#include "chaingen.h" +#include "multisig.h" + +using namespace epee; +using namespace crypto; +using namespace cryptonote; + +//#define NO_MULTISIG + +//---------------------------------------------------------------------------------------------------------------------- +// Tests + +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, + 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 +{ + uint64_t ts_start = 1338224400; + bool r; + + CHECK_AND_ASSERT_MES(total >= 2, false, "Bad scheme"); + CHECK_AND_ASSERT_MES(threshold <= total, false, "Bad scheme"); + CHECK_AND_ASSERT_MES(threshold >= total - 1, false, "Unsupported 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) + --signer; + + CHECK_AND_ASSERT_MES(creator < total, false, "invalid creator"); + for (size_t signer: 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); + + // create 8 miner accounts, and have them mine the next 8 blocks + // they will have a coinbase with a single out that's pseudo rct + constexpr size_t n_coinbases = 8; + cryptonote::account_base miner_accounts[n_coinbases]; + const cryptonote::block *prev_block = &blk_0; + cryptonote::block blocks[n_coinbases]; + for (size_t n = 0; n < n_coinbases; ++n) { + // the first block goes to the multisig account + miner_accounts[n].generate(); + account_base &account = n < inputs ? miner_account[creator] : miner_accounts[n]; + CHECK_AND_ASSERT_MES(generator.construct_block_manually(blocks[n], *prev_block, account, + test_generator::bf_major_ver | test_generator::bf_minor_ver | test_generator::bf_timestamp | test_generator::bf_hf_version | test_generator::bf_max_outs, + 4, 4, prev_block->timestamp + DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN * 2, // v2 has blocks twice as long + crypto::hash(), 0, transaction(), std::vector<crypto::hash>(), 0, 1, 4), + false, "Failed to generate block"); + events.push_back(blocks[n]); + prev_block = blocks + n; + LOG_PRINT_L0("Initial miner tx " << n << ": " << obj_to_json_str(blocks[n].miner_tx)); + LOG_PRINT_L0("in block: " << obj_to_json_str(blocks[n])); + } + + // rewind + cryptonote::block blk_r, blk_last; + { + blk_last = blocks[n_coinbases - 1]; + for (size_t i = 0; i < CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW; ++i) + { + cryptonote::block blk; + CHECK_AND_ASSERT_MES(generator.construct_block_manually(blk, blk_last, miner_accounts[0], + test_generator::bf_major_ver | test_generator::bf_minor_ver | test_generator::bf_timestamp | test_generator::bf_hf_version | test_generator::bf_max_outs, + 4, 4, blk_last.timestamp + DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN * 2, // v2 has blocks twice as long + crypto::hash(), 0, transaction(), std::vector<crypto::hash>(), 0, 1, 4), + false, "Failed to generate block"); + events.push_back(blk); + blk_last = blk; + } + blk_r = blk_last; + } + + cryptonote::keypair in_ephemeral; + crypto::public_key tx_pub_key[n_coinbases]; + crypto::public_key output_pub_key[n_coinbases]; + for (size_t n = 0; n < n_coinbases; ++n) + { + 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; + 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); + std::vector<std::vector<std::vector<crypto::public_key>>> account_R(total); + std::vector<std::vector<std::vector<crypto::key_image>>> account_ki(total); + std::vector<crypto::public_key> additional_tx_keys; + for (size_t msidx = 0; msidx < total; ++msidx) + { + CHECK_AND_ASSERT_MES(miner_account[msidx].get_keys().m_account_address.m_spend_public_key == miner_account[0].get_keys().m_account_address.m_spend_public_key, + false, "Mismatched spend public keys"); + + size_t nlr = threshold < total ? threshold - 1 : 1; + account_k[msidx].resize(inputs); + account_L[msidx].resize(inputs); + account_R[msidx].resize(inputs); + account_ki[msidx].resize(inputs); + for (size_t tdidx = 0; tdidx < inputs; ++tdidx) + { + account_L[msidx][tdidx].resize(nlr); + account_R[msidx][tdidx].resize(nlr); + for (size_t n = 0; n < nlr; ++n) + { + account_k[msidx][tdidx].push_back(rct::rct2sk(rct::skGen())); + cryptonote::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) + { + r = cryptonote::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"); + } + MDEBUG("Party " << msidx << ":"); + MDEBUG("spend: sec " << miner_account[msidx].get_keys().m_spend_secret_key << ", pub " << miner_account[msidx].get_keys().m_account_address.m_spend_public_key); + MDEBUG("view: sec " << miner_account[msidx].get_keys().m_view_secret_key << ", pub " << miner_account[msidx].get_keys().m_account_address.m_view_public_key); + for (const auto &k: miner_account[msidx].get_multisig_keys()) + MDEBUG("msk: " << k); + for (size_t n = 0; n < account_k[msidx][tdidx].size(); ++n) + { + MDEBUG("k: " << account_k[msidx][tdidx][n]); + MDEBUG("L: " << account_L[msidx][tdidx][n]); + MDEBUG("R: " << account_R[msidx][tdidx][n]); + } + for (const auto &ki: account_ki[msidx][tdidx]) + MDEBUG("ki: " << ki); + } + } +#endif + + // create kLRki + std::vector<rct::multisig_kLRki> kLRkis; + std::unordered_set<crypto::public_key> used_L; + for (size_t tdidx = 0; tdidx < inputs; ++tdidx) + { + 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) + pkis.push_back(account_ki[msidx][tdidx][n]); + r = cryptonote::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; + r = cryptonote::generate_multisig_composite_key_image(miner_account[n].get_keys(), subaddresses, output_pub_key[tdidx], tx_pub_key[tdidx], additional_tx_keys, 0, pkis, (crypto::key_image&)ki); + CHECK_AND_ASSERT_MES(r, false, "Failed to generate composite key image"); + 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 + std::vector<tx_source_entry> sources; + for (size_t n = 0; n < inputs; ++n) + { + sources.resize(sources.size() + 1); + tx_source_entry& src = sources.back(); + + src.real_output = n; + src.amount = blocks[n].miner_tx.vout[0].amount; + src.real_out_tx_key = tx_pub_key[n]; + src.real_output_in_tx_index = 0; + src.mask = rct::identity(); + src.rct = true; + src.multisig_kLRki = kLRkis[n]; + + 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); + 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)); + } + } + + //fill outputs 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; + destinations.push_back(td); + + if (pre_tx) + pre_tx(sources, destinations); + + 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; + 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, false, msoutp); + CHECK_AND_ASSERT_MES(r, false, "failed to construct transaction"); + +#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) + { + for (size_t idx = 0; idx < sources_copy.size(); ++idx) + { + CHECK_AND_ASSERT_MES((size_t)sources_copy[idx].real_output < sources_copy[idx].outputs.size(), + false, "Invalid real_output"); + if (sources_copy[idx].outputs[sources_copy[idx].real_output].second.dest == sources[n].outputs[sources[n].real_output].second.dest) + ins_order.push_back(idx); + } + } + CHECK_AND_ASSERT_MES(ins_order.size() == sources.size(), false, "Failed to work out sources permutation"); +#endif + +#ifndef NO_MULTISIG + // 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) + { + rct::key skey = rct::zero(); + const std::vector<crypto::secret_key> &msk1 = miner_account[signer].get_multisig_keys(); + for (size_t n = 0; n < msk1.size(); ++n) + { + const crypto::secret_key &sk1 = msk1[n]; + if (used_keys.find(sk1) == used_keys.end()) + { + used_keys.insert(sk1); + sc_add(skey.bytes, skey.bytes, rct::sk2rct(sk1).bytes); + } + } + 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); + } + } + 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); + + MDEBUG("signing with k size " << k.size()); + MDEBUG("signing with k " << k.back()); + 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"); + } +#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); + crypto::key_derivation derivation; + r = crypto::generate_key_derivation(tx_pub_key2, miner_account[creator].get_keys().m_view_secret_key, derivation); + CHECK_AND_ASSERT_MES(r, false, "Failed to generate derivation"); + uint64_t n_outs = 0, amount = 0; + std::vector<crypto::key_derivation> additional_derivations; + 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)) + { + ++n_outs; + CHECK_AND_ASSERT_MES(tx.vout[n].amount == 0, false, "Destination amount is not zero"); + rct::key Ctmp; + crypto::secret_key scalar1; + crypto::derivation_to_scalar(derivation, n, scalar1); + rct::ecdhTuple ecdh_info = tx.rct_signatures.ecdhInfo[n]; + rct::ecdhDecode(ecdh_info, rct::sk2rct(scalar1)); + rct::key C = tx.rct_signatures.outPk[n].mask; + rct::addKeys2(Ctmp, ecdh_info.mask, ecdh_info.amount, rct::H); + CHECK_AND_ASSERT_MES(rct::equalKeys(C, Ctmp), false, "Failed to decode amount"); + amount += rct::h2d(ecdh_info.amount); + } + } + CHECK_AND_ASSERT_MES(n_outs == 1, false, "Not exactly 1 output was received"); + CHECK_AND_ASSERT_MES(amount == amount_paid, false, "Amount paid was not the expected amount"); + + if (post_tx) + post_tx(tx); + + if (!valid) + DO_CALLBACK(events, "mark_invalid_tx"); + events.push_back(tx); + LOG_PRINT_L0("Test tx: " << obj_to_json_str(tx)); + + return true; +} + +bool gen_multisig_tx_valid_22_1_2::generate(std::vector<test_event_entry>& events) const +{ + const size_t mixin = 4; + const uint64_t amount_paid = 10000; + return generate_with(events, 2, mixin, amount_paid, true, 2, 2, 1, {2}, NULL, NULL); +} + +bool gen_multisig_tx_valid_22_1_2_many_inputs::generate(std::vector<test_event_entry>& events) const +{ + const size_t mixin = 4; + const uint64_t amount_paid = 10000; + return generate_with(events, 4, mixin, amount_paid, true, 2, 2, 1, {2}, NULL, NULL); +} + +bool gen_multisig_tx_valid_22_2_1::generate(std::vector<test_event_entry>& events) const +{ + const size_t mixin = 4; + const uint64_t amount_paid = 10000; + return generate_with(events, 2, mixin, amount_paid, true, 2, 2, 2, {1}, NULL, NULL); +} + +bool gen_multisig_tx_valid_33_1_23::generate(std::vector<test_event_entry>& events) const +{ + const size_t mixin = 4; + const uint64_t amount_paid = 10000; + return generate_with(events, 2, mixin, amount_paid, true, 3, 3, 1, {2, 3}, NULL, NULL); +} + +bool gen_multisig_tx_valid_33_3_21::generate(std::vector<test_event_entry>& events) const +{ + const size_t mixin = 4; + const uint64_t amount_paid = 10000; + return generate_with(events, 2, mixin, amount_paid, true, 3, 3, 3, {2, 1}, NULL, NULL); +} + +bool gen_multisig_tx_valid_23_1_2::generate(std::vector<test_event_entry>& events) const +{ + const size_t mixin = 4; + const uint64_t amount_paid = 10000; + return generate_with(events, 2, mixin, amount_paid, true, 2, 3, 1, {2}, NULL, NULL); +} + +bool gen_multisig_tx_valid_23_1_3::generate(std::vector<test_event_entry>& events) const +{ + const size_t mixin = 4; + const uint64_t amount_paid = 10000; + return generate_with(events, 2, mixin, amount_paid, true, 2, 3, 1, {3}, NULL, NULL); +} + +bool gen_multisig_tx_valid_23_2_1::generate(std::vector<test_event_entry>& events) const +{ + const size_t mixin = 4; + const uint64_t amount_paid = 10000; + return generate_with(events, 2, mixin, amount_paid, true, 2, 3, 2, {1}, NULL, NULL); +} + +bool gen_multisig_tx_valid_23_2_3::generate(std::vector<test_event_entry>& events) const +{ + const size_t mixin = 4; + const uint64_t amount_paid = 10000; + return generate_with(events, 2, mixin, amount_paid, true, 2, 3, 2, {3}, NULL, NULL); +} + +bool gen_multisig_tx_valid_45_1_234::generate(std::vector<test_event_entry>& events) const +{ + const size_t mixin = 4; + const uint64_t amount_paid = 10000; + return generate_with(events, 2, mixin, amount_paid, true, 4, 5, 1, {2, 3, 4}, NULL, NULL); +} + +bool gen_multisig_tx_valid_45_4_135_many_inputs::generate(std::vector<test_event_entry>& events) const +{ + const size_t mixin = 4; + const uint64_t amount_paid = 10000; + return generate_with(events, 4, mixin, amount_paid, true, 4, 5, 4, {1, 3, 5}, NULL, NULL); +} + +bool gen_multisig_tx_valid_89_3_1245789::generate(std::vector<test_event_entry>& events) const +{ + const size_t mixin = 4; + const uint64_t amount_paid = 10000; + return generate_with(events, 2, mixin, amount_paid, true, 8, 9, 3, {1, 2, 4, 5, 7, 8, 9}, NULL, NULL); +} + +bool gen_multisig_tx_invalid_22_1__no_threshold::generate(std::vector<test_event_entry>& events) const +{ + const size_t mixin = 4; + const uint64_t amount_paid = 10000; + return generate_with(events, 2, mixin, amount_paid, false, 2, 2, 1, {}, NULL, NULL); +} + +bool gen_multisig_tx_invalid_33_1__no_threshold::generate(std::vector<test_event_entry>& events) const +{ + const size_t mixin = 4; + const uint64_t amount_paid = 10000; + return generate_with(events, 2, mixin, amount_paid, false, 3, 3, 1, {}, NULL, NULL); +} + +bool gen_multisig_tx_invalid_33_1_2_no_threshold::generate(std::vector<test_event_entry>& events) const +{ + const size_t mixin = 4; + const uint64_t amount_paid = 10000; + return generate_with(events, 2, mixin, amount_paid, false, 3, 3, 1, {2}, NULL, NULL); +} + +bool gen_multisig_tx_invalid_33_1_3_no_threshold::generate(std::vector<test_event_entry>& events) const +{ + const size_t mixin = 4; + const uint64_t amount_paid = 10000; + return generate_with(events, 2, mixin, amount_paid, false, 3, 3, 1, {3}, NULL, NULL); +} + +bool gen_multisig_tx_invalid_23_1__no_threshold::generate(std::vector<test_event_entry>& events) const +{ + const size_t mixin = 4; + const uint64_t amount_paid = 10000; + return generate_with(events, 2, mixin, amount_paid, false, 2, 3, 1, {}, NULL, NULL); +} + +bool gen_multisig_tx_invalid_45_5_23_no_threshold::generate(std::vector<test_event_entry>& events) const +{ + const size_t mixin = 4; + const uint64_t amount_paid = 10000; + return generate_with(events, 2, mixin, amount_paid, false, 4, 5, 5, {2, 3}, NULL, NULL); +} diff --git a/tests/core_tests/multisig.h b/tests/core_tests/multisig.h new file mode 100644 index 000000000..62a1c6a35 --- /dev/null +++ b/tests/core_tests/multisig.h @@ -0,0 +1,199 @@ +// Copyright (c) 2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#pragma once +#include "chaingen.h" + +struct gen_multisig_tx_validation_base : public test_chain_unit_base +{ + gen_multisig_tx_validation_base() + : m_invalid_tx_index(0) + , m_invalid_block_index(0) + { + REGISTER_CALLBACK_METHOD(gen_multisig_tx_validation_base, mark_invalid_tx); + REGISTER_CALLBACK_METHOD(gen_multisig_tx_validation_base, mark_invalid_block); + } + + bool check_tx_verification_context(const cryptonote::tx_verification_context& tvc, bool tx_added, size_t event_idx, const cryptonote::transaction& /*tx*/) + { + if (m_invalid_tx_index == event_idx) + return tvc.m_verifivation_failed; + else + return !tvc.m_verifivation_failed && tx_added; + } + + bool check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t event_idx, const cryptonote::block& /*block*/) + { + if (m_invalid_block_index == event_idx) + return bvc.m_verifivation_failed; + else + return !bvc.m_verifivation_failed; + } + + bool mark_invalid_block(cryptonote::core& /*c*/, size_t ev_index, const std::vector<test_event_entry>& /*events*/) + { + m_invalid_block_index = ev_index + 1; + return true; + } + + bool mark_invalid_tx(cryptonote::core& /*c*/, size_t ev_index, const std::vector<test_event_entry>& /*events*/) + { + m_invalid_tx_index = ev_index + 1; + return true; + } + + 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, + 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; + +private: + size_t m_invalid_tx_index; + size_t m_invalid_block_index; +}; + +template<> +struct get_test_options<gen_multisig_tx_validation_base> { + const std::pair<uint8_t, uint64_t> hard_forks[3] = {std::make_pair(1, 0), std::make_pair(4, 1), std::make_pair(0, 0)}; + const cryptonote::test_options test_options = { + hard_forks + }; +}; + +// valid +struct gen_multisig_tx_valid_22_1_2: public gen_multisig_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_multisig_tx_valid_22_1_2>: public get_test_options<gen_multisig_tx_validation_base> {}; + +struct gen_multisig_tx_valid_22_1_2_many_inputs: public gen_multisig_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_multisig_tx_valid_22_1_2_many_inputs>: public get_test_options<gen_multisig_tx_validation_base> {}; + +struct gen_multisig_tx_valid_22_2_1: public gen_multisig_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_multisig_tx_valid_22_2_1>: public get_test_options<gen_multisig_tx_validation_base> {}; + +struct gen_multisig_tx_valid_33_1_23: public gen_multisig_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_multisig_tx_valid_33_1_23>: public get_test_options<gen_multisig_tx_validation_base> {}; + +struct gen_multisig_tx_valid_33_3_21: public gen_multisig_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_multisig_tx_valid_33_3_21>: public get_test_options<gen_multisig_tx_validation_base> {}; + +struct gen_multisig_tx_valid_23_1_2: public gen_multisig_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_multisig_tx_valid_23_1_2>: public get_test_options<gen_multisig_tx_validation_base> {}; + +struct gen_multisig_tx_valid_23_1_3: public gen_multisig_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_multisig_tx_valid_23_1_3>: public get_test_options<gen_multisig_tx_validation_base> {}; + +struct gen_multisig_tx_valid_23_2_1: public gen_multisig_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_multisig_tx_valid_23_2_1>: public get_test_options<gen_multisig_tx_validation_base> {}; + +struct gen_multisig_tx_valid_23_2_3: public gen_multisig_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_multisig_tx_valid_23_2_3>: public get_test_options<gen_multisig_tx_validation_base> {}; + +struct gen_multisig_tx_valid_45_1_234: public gen_multisig_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_multisig_tx_valid_45_1_234>: public get_test_options<gen_multisig_tx_validation_base> {}; + +struct gen_multisig_tx_valid_45_4_135_many_inputs: public gen_multisig_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_multisig_tx_valid_45_4_135_many_inputs>: public get_test_options<gen_multisig_tx_validation_base> {}; + +struct gen_multisig_tx_valid_89_3_1245789: public gen_multisig_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_multisig_tx_valid_89_3_1245789>: public get_test_options<gen_multisig_tx_validation_base> {}; + +// invalid +struct gen_multisig_tx_invalid_22_1__no_threshold: public gen_multisig_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_multisig_tx_invalid_22_1__no_threshold>: public get_test_options<gen_multisig_tx_validation_base> {}; + +struct gen_multisig_tx_invalid_33_1__no_threshold: public gen_multisig_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_multisig_tx_invalid_33_1__no_threshold>: public get_test_options<gen_multisig_tx_validation_base> {}; + +struct gen_multisig_tx_invalid_33_1_2_no_threshold: public gen_multisig_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_multisig_tx_invalid_33_1_2_no_threshold>: public get_test_options<gen_multisig_tx_validation_base> {}; + +struct gen_multisig_tx_invalid_33_1_3_no_threshold: public gen_multisig_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_multisig_tx_invalid_33_1_3_no_threshold>: public get_test_options<gen_multisig_tx_validation_base> {}; + +struct gen_multisig_tx_invalid_23_1__no_threshold: public gen_multisig_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_multisig_tx_invalid_23_1__no_threshold>: public get_test_options<gen_multisig_tx_validation_base> {}; + +struct gen_multisig_tx_invalid_45_5_23_no_threshold: public gen_multisig_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_multisig_tx_invalid_45_5_23_no_threshold>: public get_test_options<gen_multisig_tx_validation_base> {}; diff --git a/tests/data/fuzz/base58/ENC1 b/tests/data/fuzz/base58/ENC1 new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/tests/data/fuzz/base58/ENC1 diff --git a/tests/data/fuzz/base58/ENC2 b/tests/data/fuzz/base58/ENC2 new file mode 100644 index 000000000..73da627e4 --- /dev/null +++ b/tests/data/fuzz/base58/ENC2 @@ -0,0 +1 @@ +9zZBkWRgMNPEnVofRFqWK9MKBwgXNyKELBJSttxb1t2UhDM114URntt5iYcXzXusoHZygfSojsbviXZhnP9pJ4p2SDcv81L
\ No newline at end of file diff --git a/tests/data/fuzz/http-client/RESP1 b/tests/data/fuzz/http-client/RESP1 new file mode 100644 index 000000000..3046dc886 --- /dev/null +++ b/tests/data/fuzz/http-client/RESP1 @@ -0,0 +1,8 @@ +HTTP/1.1 200 Ok
+Server: Epee-based
+Content-Length: 5
+Content-Type: text/plain
+Last-Modified: Mon, 11 Dec 2017 09:03:03 GMT
+Accept-Ranges: bytes
+
+foo
diff --git a/tests/data/fuzz/levin/LEVIN1 b/tests/data/fuzz/levin/LEVIN1 Binary files differnew file mode 100644 index 000000000..51a640c61 --- /dev/null +++ b/tests/data/fuzz/levin/LEVIN1 diff --git a/tests/data/fuzz/load-from-binary/BINARY1 b/tests/data/fuzz/load-from-binary/BINARY1 Binary files differnew file mode 100644 index 000000000..c99d7e7bc --- /dev/null +++ b/tests/data/fuzz/load-from-binary/BINARY1 diff --git a/tests/data/fuzz/load-from-json/JSON1 b/tests/data/fuzz/load-from-json/JSON1 new file mode 100644 index 000000000..9e26dfeeb --- /dev/null +++ b/tests/data/fuzz/load-from-json/JSON1 @@ -0,0 +1 @@ +{}
\ No newline at end of file diff --git a/tests/data/fuzz/parse-url/URL1 b/tests/data/fuzz/parse-url/URL1 new file mode 100644 index 000000000..e56ea71e3 --- /dev/null +++ b/tests/data/fuzz/parse-url/URL1 @@ -0,0 +1 @@ +127.0.0.1
\ No newline at end of file diff --git a/tests/data/fuzz/parse-url/URL2 b/tests/data/fuzz/parse-url/URL2 new file mode 100644 index 000000000..b66e7de9a --- /dev/null +++ b/tests/data/fuzz/parse-url/URL2 @@ -0,0 +1 @@ +iframe_test.html?api_url=http://api.vk.com/api.php&api_id=3289090&api_settings=1&viewer_id=562964060&viewer_type=0&sid=0aad8d1c5713130f9ca0076f2b7b47e532877424961367d81e7fa92455f069be7e21bc3193cbd0be11895&secret=368ebbc0ef&access_token=668bc03f43981d883f73876ffff4aa8564254b359cc745dfa1b3cde7bdab2e94105d8f6d8250717569c0a7&user_id=0&group_id=0&is_app_user=1&auth_key=d2f7a895ca5ff3fdb2a2a8ae23fe679a&language=0&parent_language=0&ad_info=ElsdCQBaQlxiAQRdFUVUXiN2AVBzBx5pU1BXIgZUJlIEAWcgAUoLQg==&referrer=unknown&lc_name=9834b6a3&hash=
\ No newline at end of file diff --git a/tests/fuzz/CMakeLists.txt b/tests/fuzz/CMakeLists.txt index 853d46a12..5d58f9a3c 100644 --- a/tests/fuzz/CMakeLists.txt +++ b/tests/fuzz/CMakeLists.txt @@ -89,3 +89,80 @@ set_property(TARGET cold-transaction_fuzz_tests PROPERTY FOLDER "tests") +add_executable(load-from-binary_fuzz_tests load_from_binary.cpp fuzzer.cpp) +target_link_libraries(load-from-binary_fuzz_tests + PRIVATE + common + epee + ${Boost_PROGRAM_OPTIONS_LIBRARY} + ${CMAKE_THREAD_LIBS_INIT} + ${EXTRA_LIBRARIES}) +set_property(TARGET load-from-binary_fuzz_tests + PROPERTY + FOLDER "tests") + +add_executable(load-from-json_fuzz_tests load_from_json.cpp fuzzer.cpp) +target_link_libraries(load-from-json_fuzz_tests + PRIVATE + common + epee + ${Boost_PROGRAM_OPTIONS_LIBRARY} + ${CMAKE_THREAD_LIBS_INIT} + ${EXTRA_LIBRARIES}) +set_property(TARGET load-from-json_fuzz_tests + PROPERTY + FOLDER "tests") + +add_executable(base58_fuzz_tests base58.cpp fuzzer.cpp) +target_link_libraries(base58_fuzz_tests + PRIVATE + common + epee + ${Boost_PROGRAM_OPTIONS_LIBRARY} + ${CMAKE_THREAD_LIBS_INIT} + ${EXTRA_LIBRARIES}) +set_property(TARGET base58_fuzz_tests + PROPERTY + FOLDER "tests") + +add_executable(parse-url_fuzz_tests parse_url.cpp fuzzer.cpp) +target_link_libraries(parse-url_fuzz_tests + PRIVATE + epee + ${Boost_REGEX_LIBRARY} + ${Boost_PROGRAM_OPTIONS_LIBRARY} + ${Boost_SYSTEM_LIBRARY} + ${CMAKE_THREAD_LIBS_INIT} + ${EXTRA_LIBRARIES}) +set_property(TARGET parse-url_fuzz_tests + PROPERTY + FOLDER "tests") + +add_executable(http-client_fuzz_tests http-client.cpp fuzzer.cpp) +target_link_libraries(http-client_fuzz_tests + PRIVATE + epee + ${Boost_THREAD_LIBRARY} + ${Boost_REGEX_LIBRARY} + ${Boost_PROGRAM_OPTIONS_LIBRARY} + ${Boost_SYSTEM_LIBRARY} + ${CMAKE_THREAD_LIBS_INIT} + ${EXTRA_LIBRARIES}) +set_property(TARGET http-client_fuzz_tests + PROPERTY + FOLDER "tests") + +add_executable(levin_fuzz_tests levin.cpp fuzzer.cpp) +target_link_libraries(levin_fuzz_tests + PRIVATE + common + epee + ${Boost_THREAD_LIBRARY} + ${Boost_REGEX_LIBRARY} + ${Boost_PROGRAM_OPTIONS_LIBRARY} + ${CMAKE_THREAD_LIBS_INIT} + ${EXTRA_LIBRARIES}) +set_property(TARGET levin_fuzz_tests + PROPERTY + FOLDER "tests") + diff --git a/tests/fuzz/base58.cpp b/tests/fuzz/base58.cpp new file mode 100644 index 000000000..aea62f721 --- /dev/null +++ b/tests/fuzz/base58.cpp @@ -0,0 +1,74 @@ +// Copyright (c) 2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "include_base_utils.h" +#include "file_io_utils.h" +#include "common/base58.h" +#include "fuzzer.h" + +class Base58Fuzzer: public Fuzzer +{ +public: + Base58Fuzzer() {} + virtual int init(); + virtual int run(const std::string &filename); +}; + +int Base58Fuzzer::init() +{ + return 0; +} + +int Base58Fuzzer::run(const std::string &filename) +{ + std::string s; + + if (!epee::file_io_utils::load_file_to_string(filename, s)) + { + std::cout << "Error: failed to load file " << filename << std::endl; + return 1; + } + try + { + std::string data; + tools::base58::decode(s, data); + } + catch (const std::exception &e) + { + std::cerr << "Failed to load from binary: " << e.what() << std::endl; + return 1; + } + return 0; +} + +int main(int argc, const char **argv) +{ + Base58Fuzzer fuzzer; + return run_fuzzer(argc, argv, fuzzer); +} + diff --git a/tests/fuzz/fuzzer.cpp b/tests/fuzz/fuzzer.cpp index ede3fcc40..686a5e5f0 100644 --- a/tests/fuzz/fuzzer.cpp +++ b/tests/fuzz/fuzzer.cpp @@ -44,41 +44,11 @@ static int __AFL_LOOP(int) } #endif -using namespace epee; -using namespace boost::program_options; - int run_fuzzer(int argc, const char **argv, Fuzzer &fuzzer) { - TRY_ENTRY(); - tools::on_startup(); - string_tools::set_module_name_and_folder(argv[0]); - - //set up logging options - mlog_configure(mlog_get_default_log_path("fuzztests.log"), true); - mlog_set_log("*:FATAL,logging:none"); - - options_description desc_options("Allowed options"); - command_line::add_arg(desc_options, command_line::arg_help); - - variables_map vm; - bool r = command_line::handle_error_helper(desc_options, [&]() - { - store(parse_command_line(argc, argv, desc_options), vm); - notify(vm); - return true; - }); - if (!r) - return 1; - - if (command_line::get_arg(vm, command_line::arg_help)) - { - std::cout << desc_options << std::endl; - return 0; - } - if (argc < 2) { - std::cout << desc_options << std::endl; + std::cout << "usage: " << argv[0] << " " << "<filename>" << std::endl; return 1; } @@ -94,6 +64,5 @@ int run_fuzzer(int argc, const char **argv, Fuzzer &fuzzer) return ret; } - CATCH_ENTRY_L0("fuzzer_main", 1); return 0; } diff --git a/tests/fuzz/http-client.cpp b/tests/fuzz/http-client.cpp new file mode 100644 index 000000000..21560df21 --- /dev/null +++ b/tests/fuzz/http-client.cpp @@ -0,0 +1,98 @@ +// Copyright (c) 2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "include_base_utils.h" +#include "file_io_utils.h" +#include "net/http_client.h" +#include "fuzzer.h" + +class dummy_client +{ +public: + bool connect(const std::string& addr, int port, std::chrono::milliseconds timeout, bool ssl = false, const std::string& bind_ip = "0.0.0.0") { return true; } + bool connect(const std::string& addr, const std::string& port, std::chrono::milliseconds timeout, bool ssl = false, const std::string& bind_ip = "0.0.0.0") { return true; } + bool disconnect() { return true; } + bool send(const std::string& buff, std::chrono::milliseconds timeout) { return true; } + bool send(const void* data, size_t sz) { return true; } + bool is_connected() { return true; } + bool recv(std::string& buff, std::chrono::milliseconds timeout) + { + buff = data; + data.clear(); + return true; + } + + void set_test_data(const std::string &s) { data = s; } + +private: + std::string data; +}; + +class HTTPClientFuzzer: public Fuzzer +{ +public: + HTTPClientFuzzer() {} + virtual int init(); + virtual int run(const std::string &filename); + +private: + epee::net_utils::http::http_simple_client_template<dummy_client> client; +}; + +int HTTPClientFuzzer::init() +{ + return 0; +} + +int HTTPClientFuzzer::run(const std::string &filename) +{ + std::string s; + + if (!epee::file_io_utils::load_file_to_string(filename, s)) + { + std::cout << "Error: failed to load file " << filename << std::endl; + return 1; + } + try + { + client.test(s, std::chrono::milliseconds(1000)); + } + catch (const std::exception &e) + { + std::cerr << "Failed to test http client: " << e.what() << std::endl; + return 1; + } + return 0; +} + +int main(int argc, const char **argv) +{ + HTTPClientFuzzer fuzzer; + return run_fuzzer(argc, argv, fuzzer); +} + diff --git a/tests/fuzz/levin.cpp b/tests/fuzz/levin.cpp new file mode 100644 index 000000000..2fd60ae50 --- /dev/null +++ b/tests/fuzz/levin.cpp @@ -0,0 +1,345 @@ +// Copyright (c) 2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "include_base_utils.h" +#include "file_io_utils.h" +#include "net/net_utils_base.h" +#include "net/abstract_tcp_server2.h" +#include "storages/levin_abstract_invoke2.h" +#include "net/levin_protocol_handler_async.h" +#include "fuzzer.h" + +namespace +{ + class call_counter + { + public: + call_counter() : m_counter(0) { } + + // memory_order_relaxed is enough for call counter + void inc() volatile { m_counter.fetch_add(1, std::memory_order_relaxed); } + size_t get() volatile const { return m_counter.load(std::memory_order_relaxed); } + void reset() volatile { m_counter.store(0, std::memory_order_relaxed); } + + private: + std::atomic<size_t> m_counter; + }; + + struct test_levin_connection_context : public epee::net_utils::connection_context_base + { + }; + + typedef epee::levin::async_protocol_handler_config<test_levin_connection_context> test_levin_protocol_handler_config; + typedef epee::levin::async_protocol_handler<test_levin_connection_context> test_levin_protocol_handler; + + struct test_levin_commands_handler : public epee::levin::levin_commands_handler<test_levin_connection_context> + { + test_levin_commands_handler() + : m_return_code(LEVIN_OK) + , m_last_command(-1) + { + } + + virtual int invoke(int command, const std::string& in_buff, std::string& buff_out, test_levin_connection_context& context) + { + m_invoke_counter.inc(); + boost::unique_lock<boost::mutex> lock(m_mutex); + m_last_command = command; + m_last_in_buf = in_buff; + buff_out = m_invoke_out_buf; + return m_return_code; + } + + virtual int notify(int command, const std::string& in_buff, test_levin_connection_context& context) + { + m_notify_counter.inc(); + boost::unique_lock<boost::mutex> lock(m_mutex); + m_last_command = command; + m_last_in_buf = in_buff; + return m_return_code; + } + + virtual void callback(test_levin_connection_context& context) + { + m_callback_counter.inc(); + //std::cout << "test_levin_commands_handler::callback()" << std::endl; + } + + virtual void on_connection_new(test_levin_connection_context& context) + { + m_new_connection_counter.inc(); + //std::cout << "test_levin_commands_handler::on_connection_new()" << std::endl; + } + + virtual void on_connection_close(test_levin_connection_context& context) + { + m_close_connection_counter.inc(); + //std::cout << "test_levin_commands_handler::on_connection_close()" << std::endl; + } + + size_t invoke_counter() const { return m_invoke_counter.get(); } + size_t notify_counter() const { return m_notify_counter.get(); } + size_t callback_counter() const { return m_callback_counter.get(); } + size_t new_connection_counter() const { return m_new_connection_counter.get(); } + size_t close_connection_counter() const { return m_close_connection_counter.get(); } + + int return_code() const { return m_return_code; } + void return_code(int v) { m_return_code = v; } + + const std::string& invoke_out_buf() const { return m_invoke_out_buf; } + void invoke_out_buf(const std::string& v) { m_invoke_out_buf = v; } + + int last_command() const { return m_last_command; } + const std::string& last_in_buf() const { return m_last_in_buf; } + + private: + call_counter m_invoke_counter; + call_counter m_notify_counter; + call_counter m_callback_counter; + call_counter m_new_connection_counter; + call_counter m_close_connection_counter; + + boost::mutex m_mutex; + + int m_return_code; + std::string m_invoke_out_buf; + + int m_last_command; + std::string m_last_in_buf; + }; + + class test_connection : public epee::net_utils::i_service_endpoint + { + public: + test_connection(boost::asio::io_service& io_service, test_levin_protocol_handler_config& protocol_config) + : m_io_service(io_service) + , m_protocol_handler(this, protocol_config, m_context) + , m_send_return(true) + { + } + + void start() + { + m_protocol_handler.after_init_connection(); + } + + // Implement epee::net_utils::i_service_endpoint interface + virtual bool do_send(const void* ptr, size_t cb) + { + m_send_counter.inc(); + boost::unique_lock<boost::mutex> lock(m_mutex); + m_last_send_data.append(reinterpret_cast<const char*>(ptr), cb); + return m_send_return; + } + + virtual bool close() { return true; } + virtual bool call_run_once_service_io() { return true; } + virtual bool request_callback() { return true; } + virtual boost::asio::io_service& get_io_service() { return m_io_service; } + virtual bool add_ref() { return true; } + virtual bool release() { return true; } + + size_t send_counter() const { return m_send_counter.get(); } + + const std::string& last_send_data() const { return m_last_send_data; } + void reset_last_send_data() { boost::unique_lock<boost::mutex> lock(m_mutex); m_last_send_data.clear(); } + + bool send_return() const { return m_send_return; } + void send_return(bool v) { m_send_return = v; } + + public: + test_levin_connection_context m_context; + test_levin_protocol_handler m_protocol_handler; + + private: + boost::asio::io_service& m_io_service; + + call_counter m_send_counter; + boost::mutex m_mutex; + + std::string m_last_send_data; + + bool m_send_return; + }; + +#if 0 + class async_protocol_handler_test : public ::testing::Test + { + public: + const static uint64_t invoke_timeout = 5 * 1000; + const static size_t max_packet_size = 10 * 1024 * 1024; + + typedef std::unique_ptr<test_connection> test_connection_ptr; + + async_protocol_handler_test(): + m_pcommands_handler(new test_levin_commands_handler()), + m_commands_handler(*m_pcommands_handler) + { + m_handler_config.set_handler(m_pcommands_handler, [](epee::levin::levin_commands_handler<test_levin_connection_context> *handler) { delete handler; }); + m_handler_config.m_invoke_timeout = invoke_timeout; + m_handler_config.m_max_packet_size = max_packet_size; + } + + virtual void SetUp() + { + } + + protected: + test_connection_ptr create_connection(bool start = true) + { + test_connection_ptr conn(new test_connection(m_io_service, m_handler_config)); + if (start) + { + conn->start(); + } + return conn; + } + + protected: + boost::asio::io_service m_io_service; + test_levin_protocol_handler_config m_handler_config; + test_levin_commands_handler *m_pcommands_handler, &m_commands_handler; + }; + + class positive_test_connection_to_levin_protocol_handler_calls : public async_protocol_handler_test + { + }; + + class test_levin_protocol_handler__hanle_recv_with_invalid_data : public async_protocol_handler_test + { + public: + static const int expected_command = 5615871; + static const int expected_return_code = 782546; + + test_levin_protocol_handler__hanle_recv_with_invalid_data() + : m_expected_invoke_out_buf(512, 'y') + { + } + + virtual void SetUp() + { + async_protocol_handler_test::SetUp(); + + m_conn = create_connection(); + + m_in_data.assign(256, 't'); + + m_req_head.m_signature = LEVIN_SIGNATURE; + m_req_head.m_cb = m_in_data.size(); + m_req_head.m_have_to_return_data = true; + m_req_head.m_command = expected_command; + m_req_head.m_return_code = LEVIN_OK; + m_req_head.m_flags = LEVIN_PACKET_REQUEST; + m_req_head.m_protocol_version = LEVIN_PROTOCOL_VER_1; + + m_commands_handler.return_code(expected_return_code); + m_commands_handler.invoke_out_buf(m_expected_invoke_out_buf); + } + + protected: + void prepare_buf() + { + m_buf.assign(reinterpret_cast<const char*>(&m_req_head), sizeof(m_req_head)); + m_buf += m_in_data; + } + + protected: + test_connection_ptr m_conn; + epee::levin::bucket_head2 m_req_head; + std::string m_in_data; + std::string m_buf; + std::string m_expected_invoke_out_buf; + }; +#endif +} + +class LevinFuzzer: public Fuzzer +{ +public: + LevinFuzzer() {} //: handler(endpoint, config, context) {} + virtual int init(); + virtual int run(const std::string &filename); + +private: + //epee::net_utils::connection_context_base context; + //epee::levin::async_protocol_handler<> handler; +}; + +int LevinFuzzer::init() +{ + return 0; +} + +int LevinFuzzer::run(const std::string &filename) +{ + std::string s; + +// + epee::levin::bucket_head2 req_head; + req_head.m_signature = LEVIN_SIGNATURE; + req_head.m_cb = 0; + req_head.m_have_to_return_data = true; + req_head.m_command = 2000; + req_head.m_flags = LEVIN_PACKET_REQUEST; + req_head.m_protocol_version = LEVIN_PROTOCOL_VER_1; + FILE *f=fopen("/tmp/out.levin", "w"); + fwrite(&req_head,sizeof(req_head),1, f); + fclose(f); +// + if (!epee::file_io_utils::load_file_to_string(filename, s)) + { + std::cout << "Error: failed to load file " << filename << std::endl; + return 1; + } + try + { + //std::unique_ptr<test_connection> conn = new test(); + boost::asio::io_service io_service; + test_levin_protocol_handler_config m_handler_config; + test_levin_commands_handler *m_pcommands_handler = new test_levin_commands_handler(); + m_handler_config.set_handler(m_pcommands_handler, [](epee::levin::levin_commands_handler<test_levin_connection_context> *handler) { delete handler; }); + std::unique_ptr<test_connection> conn(new test_connection(io_service, m_handler_config)); + conn->start(); + //m_commands_handler.invoke_out_buf(expected_out_data); + //m_commands_handler.return_code(expected_return_code); + conn->m_protocol_handler.handle_recv(s.data(), s.size()); + } + catch (const std::exception &e) + { + std::cerr << "Failed to test http client: " << e.what() << std::endl; + return 1; + } + return 0; +} + +int main(int argc, const char **argv) +{ + LevinFuzzer fuzzer; + return run_fuzzer(argc, argv, fuzzer); +} + diff --git a/tests/fuzz/load_from_binary.cpp b/tests/fuzz/load_from_binary.cpp new file mode 100644 index 000000000..3c8dd177b --- /dev/null +++ b/tests/fuzz/load_from_binary.cpp @@ -0,0 +1,76 @@ +// Copyright (c) 2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "include_base_utils.h" +#include "file_io_utils.h" +#include "serialization/keyvalue_serialization.h" +#include "storages/portable_storage_template_helper.h" +#include "storages/portable_storage_base.h" +#include "fuzzer.h" + +class PortableStorageFuzzer: public Fuzzer +{ +public: + PortableStorageFuzzer() {} + virtual int init(); + virtual int run(const std::string &filename); +}; + +int PortableStorageFuzzer::init() +{ + return 0; +} + +int PortableStorageFuzzer::run(const std::string &filename) +{ + std::string s; + + if (!epee::file_io_utils::load_file_to_string(filename, s)) + { + std::cout << "Error: failed to load file " << filename << std::endl; + return 1; + } + try + { + epee::serialization::portable_storage ps; + ps.load_from_binary(s); + } + catch (const std::exception &e) + { + std::cerr << "Failed to load from binary: " << e.what() << std::endl; + return 1; + } + return 0; +} + +int main(int argc, const char **argv) +{ + PortableStorageFuzzer fuzzer; + return run_fuzzer(argc, argv, fuzzer); +} + diff --git a/tests/fuzz/load_from_json.cpp b/tests/fuzz/load_from_json.cpp new file mode 100644 index 000000000..5d39c89a6 --- /dev/null +++ b/tests/fuzz/load_from_json.cpp @@ -0,0 +1,76 @@ +// Copyright (c) 2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "include_base_utils.h" +#include "file_io_utils.h" +#include "serialization/keyvalue_serialization.h" +#include "storages/portable_storage_template_helper.h" +#include "storages/portable_storage_base.h" +#include "fuzzer.h" + +class PortableStorageFuzzer: public Fuzzer +{ +public: + PortableStorageFuzzer() {} + virtual int init(); + virtual int run(const std::string &filename); +}; + +int PortableStorageFuzzer::init() +{ + return 0; +} + +int PortableStorageFuzzer::run(const std::string &filename) +{ + std::string s; + + if (!epee::file_io_utils::load_file_to_string(filename, s)) + { + std::cout << "Error: failed to load file " << filename << std::endl; + return 1; + } + try + { + epee::serialization::portable_storage ps; + ps.load_from_json(s); + } + catch (const std::exception &e) + { + std::cerr << "Failed to load from binary: " << e.what() << std::endl; + return 1; + } + return 0; +} + +int main(int argc, const char **argv) +{ + PortableStorageFuzzer fuzzer; + return run_fuzzer(argc, argv, fuzzer); +} + diff --git a/tests/fuzz/parse_url.cpp b/tests/fuzz/parse_url.cpp new file mode 100644 index 000000000..bf3a3bdd4 --- /dev/null +++ b/tests/fuzz/parse_url.cpp @@ -0,0 +1,74 @@ +// Copyright (c) 2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "include_base_utils.h" +#include "file_io_utils.h" +#include "net/net_parse_helpers.h" +#include "fuzzer.h" + +class ParseURLFuzzer: public Fuzzer +{ +public: + ParseURLFuzzer() {} + virtual int init(); + virtual int run(const std::string &filename); +}; + +int ParseURLFuzzer::init() +{ + return 0; +} + +int ParseURLFuzzer::run(const std::string &filename) +{ + std::string s; + + if (!epee::file_io_utils::load_file_to_string(filename, s)) + { + std::cout << "Error: failed to load file " << filename << std::endl; + return 1; + } + try + { + epee::net_utils::http::url_content url; + epee::net_utils::parse_url(s, url); + } + catch (const std::exception &e) + { + std::cerr << "Failed to load from binary: " << e.what() << std::endl; + return 1; + } + return 0; +} + +int main(int argc, const char **argv) +{ + ParseURLFuzzer fuzzer; + return run_fuzzer(argc, argv, fuzzer); +} + diff --git a/tests/unit_tests/CMakeLists.txt b/tests/unit_tests/CMakeLists.txt index e37d34063..ba3acef0c 100644 --- a/tests/unit_tests/CMakeLists.txt +++ b/tests/unit_tests/CMakeLists.txt @@ -36,7 +36,7 @@ set(unit_tests_sources block_reward.cpp bulletproofs.cpp canonical_amounts.cpp - chacha8.cpp + chacha.cpp checkpoints.cpp command_line.cpp crypto.cpp @@ -53,6 +53,7 @@ set(unit_tests_sources memwipe.cpp mnemonics.cpp mul_div.cpp + multisig.cpp parse_amount.cpp serialization.cpp sha256.cpp diff --git a/tests/unit_tests/chacha8.cpp b/tests/unit_tests/chacha.cpp index bf0699eba..eadebf9d8 100644 --- a/tests/unit_tests/chacha8.cpp +++ b/tests/unit_tests/chacha.cpp @@ -32,7 +32,7 @@ #include "gtest/gtest.h" -#include "crypto/chacha8.h" +#include "crypto/chacha.h" namespace { diff --git a/tests/unit_tests/memwipe.cpp b/tests/unit_tests/memwipe.cpp index b2b19fbf5..2d8980ef7 100644 --- a/tests/unit_tests/memwipe.cpp +++ b/tests/unit_tests/memwipe.cpp @@ -47,7 +47,7 @@ static void test(bool wipe) if ((intptr_t)quux == foop) { MDEBUG(std::hex << std::setw(8) << std::setfill('0') << *(uint32_t*)quux); - if (wipe) ASSERT_TRUE(!memcmp(quux, "\0\0\0", 3)); + if (wipe) ASSERT_TRUE(memcmp(quux, "bar", 3)); } else MWARNING("We did not get the same location, cannot check"); free(quux); diff --git a/tests/unit_tests/multisig.cpp b/tests/unit_tests/multisig.cpp new file mode 100644 index 000000000..8b2c7e5f8 --- /dev/null +++ b/tests/unit_tests/multisig.cpp @@ -0,0 +1,188 @@ +// Copyright (c) 2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "gtest/gtest.h" + +#include <cstdint> + +#include "wallet/wallet2.h" + +static const struct +{ + const char *address; + const char *spendkey; +} test_addresses[] = +{ + { + "9uvjbU54ZJb8j7Dcq1h3F1DnBRkxXdYUX4pbJ7mE3ghM8uF3fKzqRKRNAKYZXcNLqMg7MxjVVD2wKC2PALUwEveGSC3YSWD", + "2dd6e34a234c3e8b5d29a371789e4601e96dee4ea6f7ef79224d1a2d91164c01" + }, + { + "9ywDBAyDbb6QKFiZxDJ4hHZqZEQXXCR5EaYNcndUpqPDeE7rEgs6neQdZnhcDrWbURYK8xUjhuG2mVjJdmknrZbcG7NnbaB", + "fac47aecc948ce9d3531aa042abb18235b1df632087c55a361b632ffdd6ede0c" + }, + { + "9t6Hn946u3eah5cuncH1hB5hGzsTUoevtf4SY7MHN5NgJZh2SFWsyVt3vUhuHyRKyrCQvr71Lfc1AevG3BXE11PQFoXDtD8", + "bbd3175ef9fd9f5eefdc43035f882f74ad14c4cf1799d8b6f9001bc197175d02" + } +}; + +static void make_wallet(unsigned int idx, tools::wallet2 &wallet) +{ + ASSERT_TRUE(idx < sizeof(test_addresses) / sizeof(test_addresses[0])); + + crypto::secret_key spendkey; + epee::string_tools::hex_to_pod(test_addresses[idx].spendkey, spendkey); + + try + { + wallet.init(""); + wallet.set_subaddress_lookahead(1, 1); + wallet.generate("", "", spendkey, true, false); + ASSERT_TRUE(test_addresses[idx].address == wallet.get_account().get_public_address_str(true)); + } + catch (const std::exception &e) + { + MFATAL("Error creating test wallet: " << e.what()); + ASSERT_TRUE(0); + } +} + +static void make_M_2_wallet(tools::wallet2 &wallet0, tools::wallet2 &wallet1, unsigned int M) +{ + ASSERT_TRUE(M <= 2); + + make_wallet(0, wallet0); + make_wallet(1, wallet1); + + std::vector<crypto::secret_key> sk0(1), sk1(1); + std::vector<crypto::public_key> pk0(1), pk1(1); + + std::string mi0 = wallet0.get_multisig_info(); + std::string mi1 = wallet1.get_multisig_info(); + + ASSERT_TRUE(tools::wallet2::verify_multisig_info(mi1, sk0[0], pk0[0])); + ASSERT_TRUE(tools::wallet2::verify_multisig_info(mi0, sk1[0], pk1[0])); + + ASSERT_FALSE(wallet0.multisig() || wallet1.multisig()); + wallet0.make_multisig("", sk0, pk0, M); + wallet1.make_multisig("", sk1, pk1, M); + + ASSERT_TRUE(wallet0.get_account().get_public_address_str(true) == wallet1.get_account().get_public_address_str(true)); + + bool ready; + uint32_t threshold, total; + ASSERT_TRUE(wallet0.multisig(&ready, &threshold, &total)); + ASSERT_TRUE(ready); + ASSERT_TRUE(threshold == M); + ASSERT_TRUE(total == 2); + ASSERT_TRUE(wallet1.multisig(&ready, &threshold, &total)); + ASSERT_TRUE(ready); + ASSERT_TRUE(threshold == M); + ASSERT_TRUE(total == 2); +} + +static void make_M_3_wallet(tools::wallet2 &wallet0, tools::wallet2 &wallet1, tools::wallet2 &wallet2, unsigned int M) +{ + ASSERT_TRUE(M <= 3); + + make_wallet(0, wallet0); + make_wallet(1, wallet1); + make_wallet(2, wallet2); + + std::vector<crypto::secret_key> sk0(2), sk1(2), sk2(2); + std::vector<crypto::public_key> pk0(2), pk1(2), pk2(2); + + std::string mi0 = wallet0.get_multisig_info(); + std::string mi1 = wallet1.get_multisig_info(); + std::string mi2 = wallet2.get_multisig_info(); + + ASSERT_TRUE(tools::wallet2::verify_multisig_info(mi1, sk0[0], pk0[0])); + ASSERT_TRUE(tools::wallet2::verify_multisig_info(mi2, sk0[1], pk0[1])); + ASSERT_TRUE(tools::wallet2::verify_multisig_info(mi0, sk1[0], pk1[0])); + ASSERT_TRUE(tools::wallet2::verify_multisig_info(mi2, sk1[1], pk1[1])); + ASSERT_TRUE(tools::wallet2::verify_multisig_info(mi0, sk2[0], pk2[0])); + ASSERT_TRUE(tools::wallet2::verify_multisig_info(mi1, sk2[1], pk2[1])); + + ASSERT_FALSE(wallet0.multisig() || wallet1.multisig() || wallet2.multisig()); + std::string mxi0 = wallet0.make_multisig("", sk0, pk0, M); + std::string mxi1 = wallet1.make_multisig("", sk1, pk1, M); + std::string mxi2 = wallet2.make_multisig("", sk2, pk2, M); + + const size_t nset = !mxi0.empty() + !mxi1.empty() + !mxi2.empty(); + ASSERT_TRUE((M < 3 && nset == 3) || (M == 3 && nset == 0)); + + if (nset > 0) + { + std::unordered_set<crypto::public_key> pkeys; + std::vector<crypto::public_key> signers(3, crypto::null_pkey); + ASSERT_TRUE(tools::wallet2::verify_extra_multisig_info(mxi0, pkeys, signers[0])); + ASSERT_TRUE(tools::wallet2::verify_extra_multisig_info(mxi1, pkeys, signers[1])); + ASSERT_TRUE(tools::wallet2::verify_extra_multisig_info(mxi2, pkeys, signers[2])); + ASSERT_TRUE(pkeys.size() == 3); + ASSERT_TRUE(wallet0.finalize_multisig("", pkeys, signers)); + ASSERT_TRUE(wallet1.finalize_multisig("", pkeys, signers)); + ASSERT_TRUE(wallet2.finalize_multisig("", pkeys, signers)); + } + + ASSERT_TRUE(wallet0.get_account().get_public_address_str(true) == wallet1.get_account().get_public_address_str(true)); + ASSERT_TRUE(wallet0.get_account().get_public_address_str(true) == wallet2.get_account().get_public_address_str(true)); + + bool ready; + uint32_t threshold, total; + ASSERT_TRUE(wallet0.multisig(&ready, &threshold, &total)); + ASSERT_TRUE(ready); + ASSERT_TRUE(threshold == M); + ASSERT_TRUE(total == 3); + ASSERT_TRUE(wallet1.multisig(&ready, &threshold, &total)); + ASSERT_TRUE(ready); + ASSERT_TRUE(threshold == M); + ASSERT_TRUE(total == 3); + ASSERT_TRUE(wallet2.multisig(&ready, &threshold, &total)); + ASSERT_TRUE(ready); + ASSERT_TRUE(threshold == M); + ASSERT_TRUE(total == 3); +} + +TEST(multisig, make_2_2) +{ + tools::wallet2 wallet0, wallet1; + make_M_2_wallet(wallet0, wallet1, 2); +} + +TEST(multisig, make_3_3) +{ + tools::wallet2 wallet0, wallet1, wallet2; + make_M_3_wallet(wallet0, wallet1, wallet2, 3); +} + +TEST(multisig, make_2_3) +{ + tools::wallet2 wallet0, wallet1, wallet2; + make_M_3_wallet(wallet0, wallet1, wallet2, 2); +} diff --git a/tests/unit_tests/ringct.cpp b/tests/unit_tests/ringct.cpp index ef6151efb..6956179c1 100644 --- a/tests/unit_tests/ringct.cpp +++ b/tests/unit_tests/ringct.cpp @@ -111,7 +111,7 @@ TEST(ringct, MG_sigs) sk[j] = xm[ind][j]; } key message = identity(); - mgSig IIccss = MLSAG_Gen(message, P, sk, ind, R); + mgSig IIccss = MLSAG_Gen(message, P, sk, NULL, NULL, ind, R); ASSERT_TRUE(MLSAG_Ver(message, P, IIccss, R)); //#MG sig: false one @@ -132,7 +132,7 @@ TEST(ringct, MG_sigs) sk[j] = xx[ind][j]; } sk[2] = skGen();//asume we don't know one of the private keys.. - IIccss = MLSAG_Gen(message, P, sk, ind, R); + IIccss = MLSAG_Gen(message, P, sk, NULL, NULL, ind, R); ASSERT_FALSE(MLSAG_Ver(message, P, IIccss, R)); } @@ -171,7 +171,7 @@ TEST(ringct, range_proofs) destinations.push_back(Pk); //compute rct data with mixin 500 - rctSig s = genRct(rct::zero(), sc, pc, destinations, amounts, amount_keys, 3); + rctSig s = genRct(rct::zero(), sc, pc, destinations, amounts, amount_keys, NULL, NULL, 3); //verify rct data ASSERT_TRUE(verRct(s)); @@ -188,7 +188,7 @@ TEST(ringct, range_proofs) //compute rct data with mixin 500 - s = genRct(rct::zero(), sc, pc, destinations, amounts, amount_keys, 3); + s = genRct(rct::zero(), sc, pc, destinations, amounts, amount_keys, NULL, NULL, 3); //verify rct data ASSERT_FALSE(verRct(s)); @@ -235,7 +235,7 @@ TEST(ringct, range_proofs_with_fee) destinations.push_back(Pk); //compute rct data with mixin 500 - rctSig s = genRct(rct::zero(), sc, pc, destinations, amounts, amount_keys, 3); + rctSig s = genRct(rct::zero(), sc, pc, destinations, amounts, amount_keys, NULL, NULL, 3); //verify rct data ASSERT_TRUE(verRct(s)); @@ -252,7 +252,7 @@ TEST(ringct, range_proofs_with_fee) //compute rct data with mixin 500 - s = genRct(rct::zero(), sc, pc, destinations, amounts, amount_keys, 3); + s = genRct(rct::zero(), sc, pc, destinations, amounts, amount_keys, NULL, NULL, 3); //verify rct data ASSERT_FALSE(verRct(s)); @@ -310,7 +310,7 @@ TEST(ringct, simple) //compute sig with mixin 2 xmr_amount txnfee = 1; - rctSig s = genRctSimple(message, sc, pc, destinations,inamounts, outamounts, amount_keys, txnfee, 2); + rctSig s = genRctSimple(message, sc, pc, destinations,inamounts, outamounts, amount_keys, NULL, NULL, txnfee, 2); //verify ring ct signature ASSERT_TRUE(verRctSimple(s)); @@ -344,7 +344,7 @@ static rct::rctSig make_sample_rct_sig(int n_inputs, const uint64_t input_amount } } - return genRct(rct::zero(), sc, pc, destinations, amounts, amount_keys, 3);; + return genRct(rct::zero(), sc, pc, destinations, amounts, amount_keys, NULL, NULL, 3);; } 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) @@ -370,7 +370,7 @@ static rct::rctSig make_sample_simple_rct_sig(int n_inputs, const uint64_t input destinations.push_back(Pk); } - return genRctSimple(rct::zero(), sc, pc, destinations, inamounts, outamounts, amount_keys, fee, 3);; + return genRctSimple(rct::zero(), sc, pc, destinations, inamounts, outamounts, amount_keys, NULL, NULL, fee, 3);; } static bool range_proof_test(bool expected_valid, diff --git a/tests/unit_tests/serialization.cpp b/tests/unit_tests/serialization.cpp index 0750ab7d1..2ef1097da 100644 --- a/tests/unit_tests/serialization.cpp +++ b/tests/unit_tests/serialization.cpp @@ -591,7 +591,7 @@ TEST(Serialization, serializes_ringct_types) rct::skpkGen(Sk, Pk); destinations.push_back(Pk); //compute rct data with mixin 500 - s0 = rct::genRct(rct::zero(), sc, pc, destinations, amounts, amount_keys, 3); + s0 = rct::genRct(rct::zero(), sc, pc, destinations, amounts, amount_keys, NULL, NULL, 3); mg0 = s0.p.MGs[0]; ASSERT_TRUE(serialization::dump_binary(mg0, blob)); @@ -802,12 +802,12 @@ TEST(Serialization, portability_outputs) // decrypt (copied from wallet2::decrypt) auto decrypt = [] (const std::string &ciphertext, const crypto::secret_key &skey, bool authenticated) -> string { - const size_t prefix_size = sizeof(chacha8_iv) + (authenticated ? sizeof(crypto::signature) : 0); + const size_t prefix_size = sizeof(chacha_iv) + (authenticated ? sizeof(crypto::signature) : 0); if(ciphertext.size() < prefix_size) return {}; - crypto::chacha8_key key; - crypto::generate_chacha8_key(&skey, sizeof(skey), key); - const crypto::chacha8_iv &iv = *(const crypto::chacha8_iv*)&ciphertext[0]; + crypto::chacha_key key; + crypto::generate_chacha_key(&skey, sizeof(skey), key); + const crypto::chacha_iv &iv = *(const crypto::chacha_iv*)&ciphertext[0]; std::string plaintext; plaintext.resize(ciphertext.size() - prefix_size); if (authenticated) diff --git a/tests/unit_tests/slow_memmem.cpp b/tests/unit_tests/slow_memmem.cpp index 0312019be..6e1dcb85f 100644 --- a/tests/unit_tests/slow_memmem.cpp +++ b/tests/unit_tests/slow_memmem.cpp @@ -81,7 +81,6 @@ static const struct { {1,"x",1,"x",0}, {2,"x",1,"",1}, {1,"x",1,"",0}, - {1,"x",2,"",0}, {1,"x",2,"x",0}, {2,"ax",2,"x",0}, {1,"xx",2,"xx",0}, @@ -103,7 +102,7 @@ static const struct { {8,"xxxxxxab",3,"xyz",0}, {8,"xxxxxxab",6,"abcdef",0}, {9,"\0xxxxxab",3,"ab",6}, - {4,"\0\0a",3,"\0a",1}, + {4,"\0\0a",3,"\0a",1}, // }; TEST(slowmem,Success) @@ -122,7 +121,6 @@ TEST(slowmem,Success) free(pat); free(buf); ASSERT_EQ(res,T[n].res); -ASSERT_EQ(1,1); #ifdef VERBOSE if (res!=T[n].res) printf("failed (got %zu, expected %zu)",res,T[n].res); else printf("ok"); printf("\n"); diff --git a/tests/unit_tests/vercmp.cpp b/tests/unit_tests/vercmp.cpp index d48dfdf7c..8f359585d 100644 --- a/tests/unit_tests/vercmp.cpp +++ b/tests/unit_tests/vercmp.cpp @@ -40,4 +40,6 @@ TEST(vercmp, two_one) { ASSERT_TRUE(tools::vercmp("2", "1") > 0); } TEST(vercmp, ten_nine) { ASSERT_TRUE(tools::vercmp("10", "9") > 0); } TEST(vercmp, one_dot_ten_one_dot_nine) { ASSERT_TRUE(tools::vercmp("1.10", "1.9") > 0); } TEST(vercmp, one_one_dot_nine) { ASSERT_TRUE(tools::vercmp("1", "1.9") < 0); } +TEST(vercmp, to_master) { ASSERT_TRUE(tools::vercmp("1.0", "1.0-master") < 0); } +TEST(vercmp, from_master) { ASSERT_TRUE(tools::vercmp("1.0-master", "1.1") < 0); } |