diff options
58 files changed, 2589 insertions, 909 deletions
diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index 72e8ffc0d..000000000 --- a/.dockerignore +++ /dev/null @@ -1 +0,0 @@ -* diff --git a/CMakeLists.txt b/CMakeLists.txt index 357719ffd..d63b50510 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -704,7 +704,7 @@ endif() if(ANDROID) set(ATOMIC libatomic.a) endif() -if(CMAKE_C_COMPILER_ID STREQUAL "Clang" AND ARCH_WIDTH EQUAL "32" AND NOT IOS) +if(CMAKE_C_COMPILER_ID STREQUAL "Clang" AND ARCH_WIDTH EQUAL "32" AND NOT IOS AND NOT FREEBSD) find_library(ATOMIC atomic) list(APPEND EXTRA_LIBRARIES ${ATOMIC}) endif() diff --git a/Dockerfile b/Dockerfile index 37f212b73..71b0658a0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,31 +1,38 @@ +# Multistage docker build, requires docker 17.05 + +# builder stage +FROM ubuntu:16.04 as builder + +RUN apt-get update && \ + apt-get --no-install-recommends --yes install \ + ca-certificates \ + cmake \ + g++ \ + libboost1.58-all-dev \ + libssl-dev \ + libzmq3-dev \ + libreadline-dev \ + libsodium-dev \ + make \ + pkg-config \ + graphviz \ + doxygen \ + git + +WORKDIR /src +COPY . . +RUN rm -rf build && \ + make -j$(nproc) release-static + +# runtime stage FROM ubuntu:16.04 -ENV SRC_DIR /usr/local/src/monero - -RUN set -x \ - && buildDeps=' \ - ca-certificates \ - cmake \ - g++ \ - git \ - libboost1.58-all-dev \ - libssl-dev \ - libzmq3-dev \ - libsodium-dev \ - make \ - pkg-config \ - ' \ - && apt-get -qq update \ - && apt-get -qq --no-install-recommends install $buildDeps - -RUN git clone https://github.com/monero-project/monero.git $SRC_DIR -WORKDIR $SRC_DIR -RUN make -j$(nproc) release-static - -RUN cp build/release/bin/* /usr/local/bin/ \ - \ - && rm -r $SRC_DIR \ - && apt-get -qq --auto-remove purge $buildDeps +RUN apt-get update && \ + apt-get --no-install-recommends --yes install ca-certificates && \ + apt-get clean && \ + rm -rf /var/lib/apt + +COPY --from=builder /src/build/release/bin/* /usr/local/bin/ # Contains the blockchain VOLUME /root/.bitmonero @@ -35,13 +42,7 @@ VOLUME /root/.bitmonero # monero-wallet-cli VOLUME /wallet -ENV LOG_LEVEL 0 -ENV P2P_BIND_IP 0.0.0.0 -ENV P2P_BIND_PORT 18080 -ENV RPC_BIND_IP 127.0.0.1 -ENV RPC_BIND_PORT 18081 - EXPOSE 18080 EXPOSE 18081 -CMD monerod --log-level=$LOG_LEVEL --p2p-bind-ip=$P2P_BIND_IP --p2p-bind-port=$P2P_BIND_PORT --rpc-bind-ip=$RPC_BIND_IP --rpc-bind-port=$RPC_BIND_PORT +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"] diff --git a/cmake/FindReadline.cmake b/cmake/FindReadline.cmake index 9ccef7ad8..5a8aa7b0a 100644 --- a/cmake/FindReadline.cmake +++ b/cmake/FindReadline.cmake @@ -64,3 +64,7 @@ main() char * s = rl_copy_text(0, 0); } " GNU_READLINE_FOUND) + +if(NOT Readline_LIBRARY) + set(Readline_LIBRARY "") +endif() diff --git a/contrib/epee/include/console_handler.h b/contrib/epee/include/console_handler.h index b8336b270..6d369d4d8 100644 --- a/contrib/epee/include/console_handler.h +++ b/contrib/epee/include/console_handler.h @@ -456,29 +456,35 @@ eof: class command_handler { public: typedef boost::function<bool (const std::vector<std::string> &)> callback; - typedef std::map<std::string, std::pair<callback, std::string> > lookup; + typedef std::map<std::string, std::pair<callback, std::pair<std::string, std::string>>> lookup; std::string get_usage() { std::stringstream ss; - size_t max_command_len = 0; - for(auto& x:m_command_handlers) - if(x.first.size() > max_command_len) - max_command_len = x.first.size(); for(auto& x:m_command_handlers) { - ss.width(max_command_len + 3); - ss << std::left << x.first << x.second.second << ENDL; + ss << x.second.second.first << ENDL; } return ss.str(); } - void set_handler(const std::string& cmd, const callback& hndlr, const std::string& usage = "") + std::pair<std::string, std::string> get_documentation(const std::vector<std::string>& cmd) + { + if(cmd.empty()) + return std::make_pair("", ""); + auto it = m_command_handlers.find(cmd.front()); + if(it == m_command_handlers.end()) + return std::make_pair("", ""); + return it->second.second; + } + + void set_handler(const std::string& cmd, const callback& hndlr, const std::string& usage = "", const std::string& description = "") { lookup::mapped_type & vt = m_command_handlers[cmd]; vt.first = hndlr; - vt.second = usage; + vt.second.first = description.empty() ? cmd : usage; + vt.second.second = description.empty() ? usage : description; #ifdef HAVE_READLINE rdln::readline_buffer::add_completion(cmd); #endif diff --git a/contrib/epee/include/net/http_protocol_handler.inl b/contrib/epee/include/net/http_protocol_handler.inl index c3350bf73..c555707ac 100644 --- a/contrib/epee/include/net/http_protocol_handler.inl +++ b/contrib/epee/include/net/http_protocol_handler.inl @@ -639,6 +639,9 @@ namespace net_utils buf += "Access-Control-Allow-Origin: "; buf += m_query_info.m_header_info.m_origin; buf += "\r\n"; + buf += "Access-Control-Expose-Headers: www-authenticate\r\n"; + if (m_query_info.m_http_method == http::http_method_options) + buf += "Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With\r\n"; buf += "Access-Control-Allow-Methods: POST, PUT, GET, OPTIONS\r\n"; } } diff --git a/external/easylogging++/easylogging++.h b/external/easylogging++/easylogging++.h index c55cce755..469cf9eec 100644 --- a/external/easylogging++/easylogging++.h +++ b/external/easylogging++/easylogging++.h @@ -99,6 +99,11 @@ #else # define ELPP_OS_FREEBSD 0 #endif +#if (defined(__OpenBSD__)) +# define ELPP_OS_OPENBSD 1 +#else +# define ELPP_OS_OPENBSD 0 +#endif #if (defined(__sun)) # define ELPP_OS_SOLARIS 1 #else @@ -110,7 +115,7 @@ # define ELPP_OS_DRAGONFLY 0 #endif // Unix -#if ((ELPP_OS_LINUX || ELPP_OS_MAC || ELPP_OS_FREEBSD || ELPP_OS_SOLARIS || ELPP_OS_DRAGONFLY) && (!ELPP_OS_WINDOWS)) +#if ((ELPP_OS_LINUX || ELPP_OS_MAC || ELPP_OS_FREEBSD || ELPP_OS_SOLARIS || ELPP_OS_DRAGONFLY || ELPP_OS_OPENBSD) && (!ELPP_OS_WINDOWS)) # define ELPP_OS_UNIX 1 #else # define ELPP_OS_UNIX 0 @@ -195,7 +200,7 @@ ELPP_INTERNAL_DEBUGGING_OUT_INFO << ELPP_INTERNAL_DEBUGGING_MSG(internalInfoStre # define ELPP_INTERNAL_INFO(lvl, msg) #endif // (defined(ELPP_DEBUG_INFO)) #if (defined(ELPP_FEATURE_ALL)) || (defined(ELPP_FEATURE_CRASH_LOG)) -# if (ELPP_COMPILER_GCC && !ELPP_MINGW) +# if (ELPP_COMPILER_GCC && !ELPP_MINGW && !ELPP_OS_OPENBSD) # define ELPP_STACKTRACE 1 # else # define ELPP_STACKTRACE 0 diff --git a/src/blockchain_utilities/bootstrap_file.cpp b/src/blockchain_utilities/bootstrap_file.cpp index 2c993460a..50b56eab9 100644 --- a/src/blockchain_utilities/bootstrap_file.cpp +++ b/src/blockchain_utilities/bootstrap_file.cpp @@ -134,8 +134,7 @@ bool BootstrapFile::initialize_file() bbi.block_last_pos = 0; buffer_type buffer2; - boost::iostreams::stream<boost::iostreams::back_insert_device<buffer_type>>* output_stream_header; - output_stream_header = new boost::iostreams::stream<boost::iostreams::back_insert_device<buffer_type>>(buffer2); + boost::iostreams::stream<boost::iostreams::back_insert_device<buffer_type>> output_stream_header(buffer2); uint32_t bd_size = 0; @@ -147,8 +146,8 @@ bool BootstrapFile::initialize_file() { throw std::runtime_error("Error in serialization of bootstrap::file_info size"); } - *output_stream_header << blob; - *output_stream_header << bd; + output_stream_header << blob; + output_stream_header << bd; bd = t_serializable_object_to_blob(bbi); MDEBUG("bootstrap::blocks_info size: " << bd.size()); @@ -158,12 +157,12 @@ bool BootstrapFile::initialize_file() { throw std::runtime_error("Error in serialization of bootstrap::blocks_info size"); } - *output_stream_header << blob; - *output_stream_header << bd; + output_stream_header << blob; + output_stream_header << bd; - output_stream_header->flush(); - *output_stream_header << std::string(header_size-buffer2.size(), 0); // fill in rest with null bytes - output_stream_header->flush(); + output_stream_header.flush(); + output_stream_header << std::string(header_size-buffer2.size(), 0); // fill in rest with null bytes + output_stream_header.flush(); std::copy(buffer2.begin(), buffer2.end(), std::ostreambuf_iterator<char>(*m_raw_data_file)); return true; diff --git a/src/common/util.cpp b/src/common/util.cpp index a13ac6e50..e8ac61815 100644 --- a/src/common/util.cpp +++ b/src/common/util.cpp @@ -550,7 +550,7 @@ std::string get_nix_version_display_string() MCLOG_RED(el::Level::Warning, "global", "Running with glibc " << ver << ", hangs may occur - change glibc version if possible"); #endif -#if OPENSSL_VERSION_NUMBER < 0x10100000 +#if OPENSSL_VERSION_NUMBER < 0x10100000 || defined(LIBRESSL_VERSION_TEXT) SSL_library_init(); #else OPENSSL_init_ssl(0, NULL); diff --git a/src/common/util.h b/src/common/util.h index 1aac026c1..53ff78af8 100644 --- a/src/common/util.h +++ b/src/common/util.h @@ -39,6 +39,11 @@ #include <memory> #include <string> +#ifdef _WIN32 +#include "windows.h" +#include "misc_log_ex.h" +#endif + #include "crypto/hash.h" /*! \brief Various Tools diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp index 1c28ca4d8..3c760493f 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.cpp +++ b/src/cryptonote_basic/cryptonote_format_utils.cpp @@ -994,7 +994,7 @@ namespace cryptonote block_hashes_cached = block_hashes_cached_count; } //--------------------------------------------------------------- - crypto::secret_key encrypt_key(const crypto::secret_key &key, const std::string &passphrase) + crypto::secret_key encrypt_key(crypto::secret_key key, const std::string &passphrase) { crypto::hash hash; crypto::cn_slow_hash(passphrase.data(), passphrase.size(), hash); @@ -1002,7 +1002,7 @@ namespace cryptonote return key; } //--------------------------------------------------------------- - crypto::secret_key decrypt_key(const crypto::secret_key &key, const std::string &passphrase) + crypto::secret_key decrypt_key(crypto::secret_key key, const std::string &passphrase) { crypto::hash hash; crypto::cn_slow_hash(passphrase.data(), passphrase.size(), hash); diff --git a/src/cryptonote_basic/cryptonote_format_utils.h b/src/cryptonote_basic/cryptonote_format_utils.h index f88310c4c..aebeaa6f4 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.h +++ b/src/cryptonote_basic/cryptonote_format_utils.h @@ -226,8 +226,8 @@ namespace cryptonote bool is_valid_decomposed_amount(uint64_t amount); void get_hash_stats(uint64_t &tx_hashes_calculated, uint64_t &tx_hashes_cached, uint64_t &block_hashes_calculated, uint64_t & block_hashes_cached); - crypto::secret_key encrypt_key(const crypto::secret_key &key, const std::string &passphrase); - crypto::secret_key decrypt_key(const crypto::secret_key &key, const std::string &passphrase); + crypto::secret_key encrypt_key(crypto::secret_key key, const std::string &passphrase); + crypto::secret_key decrypt_key(crypto::secret_key key, const std::string &passphrase); #define CHECKED_GET_SPECIFIC_VARIANT(variant_var, specific_type, variable_name, fail_return_val) \ CHECK_AND_ASSERT_MES(variant_var.type() == typeid(specific_type), fail_return_val, "wrong variant type: " << variant_var.type().name() << ", expected " << typeid(specific_type).name()); \ specific_type& variable_name = boost::get<specific_type>(variant_var); diff --git a/src/cryptonote_basic/miner.cpp b/src/cryptonote_basic/miner.cpp index 0aafe24e1..c90ab0f03 100644 --- a/src/cryptonote_basic/miner.cpp +++ b/src/cryptonote_basic/miner.cpp @@ -53,6 +53,19 @@ #include <TargetConditionals.h> #endif +#ifdef __FreeBSD__ +#include <devstat.h> +#include <errno.h> +#include <fcntl.h> +#include <machine/apm_bios.h> +#include <stdio.h> +#include <sys/resource.h> +#include <sys/sysctl.h> +#include <sys/times.h> +#include <sys/types.h> +#include <unistd.h> +#endif + #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "miner" @@ -735,8 +748,6 @@ namespace cryptonote #elif defined(__linux__) - const std::string STR_CPU("cpu"); - const std::size_t STR_CPU_LEN = STR_CPU.size(); const std::string STAT_FILE_PATH = "/proc/stat"; if( !epee::file_io_utils::is_file_exist(STAT_FILE_PATH) ) @@ -785,9 +796,36 @@ namespace cryptonote return true; + #elif defined(__FreeBSD__) + + struct statinfo s; + size_t n = sizeof(s.cp_time); + if( sysctlbyname("kern.cp_time", s.cp_time, &n, NULL, 0) == -1 ) + { + LOG_ERROR("sysctlbyname(\"kern.cp_time\"): " << strerror(errno)); + return false; + } + if( n != sizeof(s.cp_time) ) + { + LOG_ERROR("sysctlbyname(\"kern.cp_time\") output is unexpectedly " + << n << " bytes instead of the expected " << sizeof(s.cp_time) + << " bytes."); + return false; + } + + idle_time = s.cp_time[CP_IDLE]; + total_time = + s.cp_time[CP_USER] + + s.cp_time[CP_NICE] + + s.cp_time[CP_SYS] + + s.cp_time[CP_INTR] + + s.cp_time[CP_IDLE]; + + return true; + #endif - return false; // unsupported systemm.. + return false; // unsupported system } //----------------------------------------------------------------------------------------------------- bool miner::get_process_time(uint64_t& total_time) @@ -807,7 +845,7 @@ namespace cryptonote return true; } - #elif (defined(__linux__) && defined(_SC_CLK_TCK)) || defined(__APPLE__) + #elif (defined(__linux__) && defined(_SC_CLK_TCK)) || defined(__APPLE__) || defined(__FreeBSD__) struct tms tms; if ( times(&tms) != (clock_t)-1 ) @@ -818,7 +856,7 @@ namespace cryptonote #endif - return false; // unsupported system.. + return false; // unsupported system } //----------------------------------------------------------------------------------------------------- uint8_t miner::get_percent_of_total(uint64_t other, uint64_t total) @@ -929,6 +967,70 @@ namespace cryptonote } return on_battery; + #elif defined(__FreeBSD__) + int ac; + size_t n = sizeof(ac); + if( sysctlbyname("hw.acpi.acline", &ac, &n, NULL, 0) == -1 ) + { + if( errno != ENOENT ) + { + LOG_ERROR("Cannot query battery status: " + << "sysctlbyname(\"hw.acpi.acline\"): " << strerror(errno)); + return boost::logic::tribool(boost::logic::indeterminate); + } + + // If sysctl fails with ENOENT, then try querying /dev/apm. + + static const char* dev_apm = "/dev/apm"; + const int fd = open(dev_apm, O_RDONLY); + if( fd == -1 ) { + LOG_ERROR("Cannot query battery status: " + << "open(): " << dev_apm << ": " << strerror(errno)); + return boost::logic::tribool(boost::logic::indeterminate); + } + + apm_info info; + if( ioctl(fd, APMIO_GETINFO, &info) == -1 ) { + close(fd); + LOG_ERROR("Cannot query battery status: " + << "ioctl(" << dev_apm << ", APMIO_GETINFO): " << strerror(errno)); + return boost::logic::tribool(boost::logic::indeterminate); + } + + close(fd); + + // See apm(8). + switch( info.ai_acline ) + { + case 0: // off-line + case 2: // backup power + return boost::logic::tribool(true); + case 1: // on-line + return boost::logic::tribool(false); + } + switch( info.ai_batt_stat ) + { + case 0: // high + case 1: // low + case 2: // critical + return boost::logic::tribool(true); + case 3: // charging + return boost::logic::tribool(false); + } + + LOG_ERROR("Cannot query battery status: " + << "sysctl hw.acpi.acline is not available and /dev/apm returns " + << "unexpected ac-line status (" << info.ai_acline << ") and " + << "battery status (" << info.ai_batt_stat << ")."); + return boost::logic::tribool(boost::logic::indeterminate); + } + if( n != sizeof(ac) ) + { + LOG_ERROR("sysctlbyname(\"hw.acpi.acline\") output is unexpectedly " + << n << " bytes instead of the expected " << sizeof(ac) << " bytes."); + return boost::logic::tribool(boost::logic::indeterminate); + } + return boost::logic::tribool(ac == 0); #endif LOG_ERROR("couldn't query power status"); diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index acc76a8d6..edc2dfdaa 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -1329,6 +1329,7 @@ namespace cryptonote << "where <level> is between 0 (no details) and 4 (very verbose), or custom category based levels (eg, *:WARNING)" << ENDL << ENDL << "Use the \"help\" command to see the list of available commands." << ENDL + << "Use \"help <command>\" to see a command's documentation." << ENDL << "**********************************************************************" << ENDL); m_starter_message_showed = true; } @@ -1336,6 +1337,7 @@ namespace cryptonote m_fork_moaner.do_call(boost::bind(&core::check_fork_time, this)); m_txpool_auto_relayer.do_call(boost::bind(&core::relay_txpool_transactions, this)); m_check_updates_interval.do_call(boost::bind(&core::check_updates, this)); + m_check_disk_space_interval.do_call(boost::bind(&core::check_disk_space, this)); m_miner.on_idle(); m_mempool.on_idle(); return true; @@ -1467,6 +1469,17 @@ namespace cryptonote return true; } //----------------------------------------------------------------------------------------------- + bool core::check_disk_space() + { + uint64_t free_space = get_free_space(); + if (free_space < 1ull * 1024 * 1024 * 1024) // 1 GB + { + const el::Level level = el::Level::Warning; + MCLOG_RED(level, "global", "Free space is below 1 GB on " << m_config_folder); + } + return true; + } + //----------------------------------------------------------------------------------------------- void core::set_target_blockchain_height(uint64_t target_blockchain_height) { m_target_blockchain_height = target_blockchain_height; @@ -1482,6 +1495,13 @@ namespace cryptonote return get_blockchain_storage().prevalidate_block_hashes(height, hashes); } //----------------------------------------------------------------------------------------------- + uint64_t core::get_free_space() const + { + boost::filesystem::path path(m_config_folder); + boost::filesystem::space_info si = boost::filesystem::space(path); + return si.available; + } + //----------------------------------------------------------------------------------------------- std::time_t core::get_start_time() const { return start_time; diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index dc014206d..905e67f6d 100644 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -766,6 +766,13 @@ namespace cryptonote */ uint64_t prevalidate_block_hashes(uint64_t height, const std::list<crypto::hash> &hashes); + /** + * @brief get free disk space on the blockchain partition + * + * @return free space in bytes + */ + uint64_t get_free_space() const; + private: /** @@ -925,6 +932,13 @@ namespace cryptonote */ bool check_updates(); + /** + * @brief checks free disk space + * + * @return true on success, false otherwise + */ + bool check_disk_space(); + bool m_test_drop_download = true; //!< whether or not to drop incoming blocks (for testing) uint64_t m_test_drop_download_height = 0; //!< height under which to drop incoming blocks, if doing so @@ -948,6 +962,7 @@ namespace cryptonote epee::math_helper::once_a_time_seconds<60*60*2, true> m_fork_moaner; //!< interval for checking HardFork status epee::math_helper::once_a_time_seconds<60*2, false> m_txpool_auto_relayer; //!< interval for checking re-relaying txpool transactions epee::math_helper::once_a_time_seconds<60*60*12, true> m_check_updates_interval; //!< interval for checking for new versions + epee::math_helper::once_a_time_seconds<60*10, true> m_check_disk_space_interval; //!< interval for checking for disk space std::atomic<bool> m_starter_message_showed; //!< has the "daemon will sync now" message been shown? diff --git a/src/cryptonote_core/cryptonote_tx_utils.h b/src/cryptonote_core/cryptonote_tx_utils.h index 9a3b2484d..8d9a1e332 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.h +++ b/src/cryptonote_core/cryptonote_tx_utils.h @@ -53,6 +53,20 @@ namespace cryptonote rct::key mask; //ringct amount mask 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)}))); } + + BEGIN_SERIALIZE_OBJECT() + FIELD(outputs) + FIELD(real_output) + FIELD(real_out_tx_key) + FIELD(real_out_additional_tx_keys) + FIELD(real_output_in_tx_index) + FIELD(amount) + FIELD(rct) + FIELD(mask) + + if (real_output >= outputs.size()) + return false; + END_SERIALIZE() }; struct tx_destination_entry diff --git a/src/cryptonote_protocol/cryptonote_protocol_defs.h b/src/cryptonote_protocol/cryptonote_protocol_defs.h index 1804cc101..7cf7e4a4d 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_defs.h +++ b/src/cryptonote_protocol/cryptonote_protocol_defs.h @@ -74,7 +74,7 @@ namespace cryptonote uint32_t support_flags; - boost::uuids::uuid connection_id; + std::string connection_id; uint64_t height; @@ -98,7 +98,7 @@ namespace cryptonote KV_SERIALIZE(avg_upload) KV_SERIALIZE(current_upload) KV_SERIALIZE(support_flags) - KV_SERIALIZE_VAL_POD_AS_BLOB(connection_id) + KV_SERIALIZE(connection_id) KV_SERIALIZE(height) END_KV_SERIALIZE_MAP() }; diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.inl b/src/cryptonote_protocol/cryptonote_protocol_handler.inl index 73433a8d8..9ae24551c 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.inl +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.inl @@ -244,7 +244,7 @@ namespace cryptonote cnx.current_download = cntxt.m_current_speed_down / 1024; cnx.current_upload = cntxt.m_current_speed_up / 1024; - cnx.connection_id = cntxt.m_connection_id; + cnx.connection_id = epee::string_tools::pod_to_hex(cntxt.m_connection_id); cnx.height = cntxt.m_remote_blockchain_height; diff --git a/src/daemon/command_server.cpp b/src/daemon/command_server.cpp index b134aee61..7ff6b2bf3 100644 --- a/src/daemon/command_server.cpp +++ b/src/daemon/command_server.cpp @@ -49,214 +49,228 @@ t_command_server::t_command_server( , m_is_rpc(is_rpc) { m_command_lookup.set_handler( - "q" - , [] (const std::vector<std::string>& args) {return true;} - , "ignored" - ); - m_command_lookup.set_handler( "help" , std::bind(&t_command_server::help, this, p::_1) - , "Show this help" + , "help [<command>]" + , "Show the help section or the documentation about a <command>." ); m_command_lookup.set_handler( "print_height" , std::bind(&t_command_parser_executor::print_height, &m_parser, p::_1) - , "Print local blockchain height" + , "Print the local blockchain height." ); m_command_lookup.set_handler( "print_pl" , std::bind(&t_command_parser_executor::print_peer_list, &m_parser, p::_1) - , "Print peer list" + , "Print the current peer list." ); m_command_lookup.set_handler( "print_pl_stats" , std::bind(&t_command_parser_executor::print_peer_list_stats, &m_parser, p::_1) - , "Print peer list stats" + , "Print the peer list statistics." ); m_command_lookup.set_handler( "print_cn" , std::bind(&t_command_parser_executor::print_connections, &m_parser, p::_1) - , "Print connections" + , "Print the current connections." ); m_command_lookup.set_handler( "print_bc" , std::bind(&t_command_parser_executor::print_blockchain_info, &m_parser, p::_1) - , "Print blockchain info in a given blocks range, print_bc <begin_height> [<end_height>]" + , "print_bc <begin_height> [<end_height>]" + , "Print the blockchain info in a given blocks range." ); m_command_lookup.set_handler( "print_block" , std::bind(&t_command_parser_executor::print_block, &m_parser, p::_1) - , "Print block, print_block <block_hash> | <block_height>" + , "print_block <block_hash> | <block_height>" + , "Print a given block." ); m_command_lookup.set_handler( "print_tx" , std::bind(&t_command_parser_executor::print_transaction, &m_parser, p::_1) - , "Print transaction, print_tx <transaction_hash> [+hex] [+json]" + , "print_tx <transaction_hash> [+hex] [+json]" + , "Print a given transaction." ); m_command_lookup.set_handler( "is_key_image_spent" , std::bind(&t_command_parser_executor::is_key_image_spent, &m_parser, p::_1) - , "Prints whether a given key image is in the spent key images set, is_key_image_spent <key_image>" + , "is_key_image_spent <key_image>" + , "Print whether a given key image is in the spent key images set." ); m_command_lookup.set_handler( "start_mining" , std::bind(&t_command_parser_executor::start_mining, &m_parser, p::_1) - , "Start mining for specified address, start_mining <addr> [<threads>] [do_background_mining] [ignore_battery], default 1 thread, no background mining" + , "start_mining <addr> [<threads>] [do_background_mining] [ignore_battery]" + , "Start mining for specified address. Defaults to 1 thread and no background mining." ); m_command_lookup.set_handler( "stop_mining" , std::bind(&t_command_parser_executor::stop_mining, &m_parser, p::_1) - , "Stop mining" + , "Stop mining." ); m_command_lookup.set_handler( "print_pool" , std::bind(&t_command_parser_executor::print_transaction_pool_long, &m_parser, p::_1) - , "Print transaction pool (long format)" + , "Print the transaction pool using a long format." ); m_command_lookup.set_handler( "print_pool_sh" , std::bind(&t_command_parser_executor::print_transaction_pool_short, &m_parser, p::_1) - , "Print transaction pool (short format)" + , "Print transaction pool using a short format." ); m_command_lookup.set_handler( "print_pool_stats" , std::bind(&t_command_parser_executor::print_transaction_pool_stats, &m_parser, p::_1) - , "Print transaction pool statistics" + , "Print the transaction pool's statistics." ); m_command_lookup.set_handler( "show_hr" , std::bind(&t_command_parser_executor::show_hash_rate, &m_parser, p::_1) - , "Start showing hash rate" + , "Start showing the current hash rate." ); m_command_lookup.set_handler( "hide_hr" , std::bind(&t_command_parser_executor::hide_hash_rate, &m_parser, p::_1) - , "Stop showing hash rate" + , "Stop showing the hash rate." ); m_command_lookup.set_handler( "save" , std::bind(&t_command_parser_executor::save_blockchain, &m_parser, p::_1) - , "Save blockchain" + , "Save the blockchain." ); m_command_lookup.set_handler( "set_log" , std::bind(&t_command_parser_executor::set_log_level, &m_parser, p::_1) - , "set_log <level>|<{+,-,}categories> - Change current log level/categories, <level> is a number 0-4" + , "set_log <level>|<{+,-,}categories>" + , "Change the current log level/categories where <level> is a number 0-4." ); m_command_lookup.set_handler( "diff" , std::bind(&t_command_parser_executor::show_difficulty, &m_parser, p::_1) - , "Show difficulty" + , "Show the current difficulty." ); m_command_lookup.set_handler( "status" , std::bind(&t_command_parser_executor::show_status, &m_parser, p::_1) - , "Show status" + , "Show the current status." ); m_command_lookup.set_handler( "stop_daemon" , std::bind(&t_command_parser_executor::stop_daemon, &m_parser, p::_1) - , "Stop the daemon" + , "Stop the daemon." ); m_command_lookup.set_handler( "exit" , std::bind(&t_command_parser_executor::stop_daemon, &m_parser, p::_1) - , "Stop the daemon" + , "Stop the daemon." ); m_command_lookup.set_handler( "print_status" , std::bind(&t_command_parser_executor::print_status, &m_parser, p::_1) - , "Prints daemon status" + , "Print the current daemon status." ); m_command_lookup.set_handler( "limit" , std::bind(&t_command_parser_executor::set_limit, &m_parser, p::_1) - , "limit <kB/s> - Set download and upload limit" + , "limit [<kB/s>]" + , "Get or set the download and upload limit." ); m_command_lookup.set_handler( "limit_up" , std::bind(&t_command_parser_executor::set_limit_up, &m_parser, p::_1) - , "limit <kB/s> - Set upload limit" + , "limit_up [<kB/s>]" + , "Get or set the upload limit." ); m_command_lookup.set_handler( "limit_down" , std::bind(&t_command_parser_executor::set_limit_down, &m_parser, p::_1) - , "limit <kB/s> - Set download limit" + , "limit_down [<kB/s>]" + , "Get or set the download limit." ); m_command_lookup.set_handler( "out_peers" , std::bind(&t_command_parser_executor::out_peers, &m_parser, p::_1) - , "Set max number of out peers" + , "out_peers <max_number>" + , "Set the <max_number> of out peers." ); m_command_lookup.set_handler( "start_save_graph" , std::bind(&t_command_parser_executor::start_save_graph, &m_parser, p::_1) - , "Start save data for dr monero" + , "Start saving data for dr monero." ); m_command_lookup.set_handler( "stop_save_graph" , std::bind(&t_command_parser_executor::stop_save_graph, &m_parser, p::_1) - , "Stop save data for dr monero" + , "Stop saving data for dr monero." ); m_command_lookup.set_handler( "hard_fork_info" , std::bind(&t_command_parser_executor::hard_fork_info, &m_parser, p::_1) - , "Print hard fork voting information" + , "Print the hard fork voting information." ); m_command_lookup.set_handler( "bans" , std::bind(&t_command_parser_executor::show_bans, &m_parser, p::_1) - , "Show the currently banned IPs" + , "Show the currently banned IPs." ); m_command_lookup.set_handler( "ban" , std::bind(&t_command_parser_executor::ban, &m_parser, p::_1) - , "Ban a given IP for a time" + , "ban <IP> [<seconds>]" + , "Ban a given <IP> for a given amount of <seconds>." ); m_command_lookup.set_handler( "unban" , std::bind(&t_command_parser_executor::unban, &m_parser, p::_1) - , "Unban a given IP" + , "unban <IP>" + , "Unban a given <IP>." ); m_command_lookup.set_handler( "flush_txpool" , std::bind(&t_command_parser_executor::flush_txpool, &m_parser, p::_1) - , "Flush a transaction from the tx pool by its txid, or the whole tx pool" + , "flush_txpool [<txid>]" + , "Flush a transaction from the tx pool by its <txid>, or the whole tx pool." ); m_command_lookup.set_handler( "output_histogram" , std::bind(&t_command_parser_executor::output_histogram, &m_parser, p::_1) - , "Print output histogram (amount, instances)" + , "output_histogram [@<amount>] <min_count> [<max_count>]" + , "Print the output histogram of outputs." ); m_command_lookup.set_handler( "print_coinbase_tx_sum" , std::bind(&t_command_parser_executor::print_coinbase_tx_sum, &m_parser, p::_1) - , "Print sum of coinbase transactions <start height> [block count]" + , "print_coinbase_tx_sum <start_height> [<block_count>]" + , "Print the sum of coinbase transactions." ); m_command_lookup.set_handler( "alt_chain_info" , std::bind(&t_command_parser_executor::alt_chain_info, &m_parser, p::_1) - , "Print information about alternative chains" + , "Print the information about alternative chains." ); m_command_lookup.set_handler( "bc_dyn_stats" , std::bind(&t_command_parser_executor::print_blockchain_dynamic_stats, &m_parser, p::_1) - , "Print information about current blockchain dynamic state, bc_dyn_stats <last n blocks>" + , "bc_dyn_stats <last_block_count>" + , "Print the information about current blockchain dynamic state." ); m_command_lookup.set_handler( "update" , std::bind(&t_command_parser_executor::update, &m_parser, p::_1) - , "subcommands: check (check if an update is available), download (download it if there is), update (not implemented)" + , "update (check|download)" + , "Check if an update is available, optionally downloads it if there is. Updating is not yet implemented." ); m_command_lookup.set_handler( "relay_tx" , std::bind(&t_command_parser_executor::relay_tx, &m_parser, p::_1) - , "Relay a given transaction by its txid" + , "relay_tx <txid>" + , "Relay a given transaction by its <txid>." ); m_command_lookup.set_handler( "sync_info" , std::bind(&t_command_parser_executor::sync_info, &m_parser, p::_1) - , "Print information about blockchain sync state" + , "Print information about the blockchain sync state." ); } @@ -293,7 +307,14 @@ void t_command_server::stop_handling() bool t_command_server::help(const std::vector<std::string>& args) { - std::cout << get_commands_str() << std::endl; + if(args.empty()) + { + std::cout << get_commands_str() << std::endl; + } + else + { + std::cout << get_command_usage(args) << std::endl; + } return true; } @@ -309,4 +330,25 @@ std::string t_command_server::get_commands_str() return ss.str(); } + std::string t_command_server::get_command_usage(const std::vector<std::string> &args) + { + std::pair<std::string, std::string> documentation = m_command_lookup.get_documentation(args); + std::stringstream ss; + if(documentation.first.empty()) + { + ss << "Unknown command: " << args.front() << std::endl; + } + else + { + std::string usage = documentation.second.empty() ? args.front() : documentation.first; + std::string description = documentation.second.empty() ? documentation.first : documentation.second; + usage.insert(0, " "); + ss << "Command usage: " << std::endl << usage << std::endl << std::endl; + boost::replace_all(description, "\n", "\n "); + description.insert(0, " "); + ss << "Command description: " << std::endl << description << std::endl; + } + return ss.str(); + } + } // namespace daemonize diff --git a/src/daemon/command_server.h b/src/daemon/command_server.h index 476b75141..2ad277f4a 100644 --- a/src/daemon/command_server.h +++ b/src/daemon/command_server.h @@ -73,6 +73,7 @@ private: bool help(const std::vector<std::string>& args); std::string get_commands_str(); + std::string get_command_usage(const std::vector<std::string> &args); }; } // namespace daemonize diff --git a/src/daemon/main.cpp b/src/daemon/main.cpp index ae83943b6..d038cc825 100644 --- a/src/daemon/main.cpp +++ b/src/daemon/main.cpp @@ -278,7 +278,7 @@ int main(int argc, char const * argv[]) tools::set_stack_trace_log(log_file_path.filename().string()); #endif // STACK_TRACE - if (command_line::has_arg(vm, daemon_args::arg_max_concurrency)) + if (!command_line::is_arg_defaulted(vm, daemon_args::arg_max_concurrency)) tools::set_max_concurrency(command_line::get_arg(vm, daemon_args::arg_max_concurrency)); // logging is now set up diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 230b9f090..d7ee28baa 100644 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -1304,6 +1304,7 @@ bool t_rpc_command_executor::start_save_graph() } } + tools::success_msg_writer() << "Saving graph is now on"; return true; } @@ -1329,6 +1330,7 @@ bool t_rpc_command_executor::stop_save_graph() return true; } } + tools::success_msg_writer() << "Saving graph is now off"; return true; } @@ -1495,6 +1497,7 @@ bool t_rpc_command_executor::flush_txpool(const std::string &txid) } } + tools::success_msg_writer() << "Pool successfully flushed"; return true; } @@ -1797,6 +1800,7 @@ bool t_rpc_command_executor::relay_tx(const std::string &txid) } } + tools::success_msg_writer() << "Transaction successfully relayed"; return true; } diff --git a/src/daemonizer/windows_daemonizer.inl b/src/daemonizer/windows_daemonizer.inl index 012cd1e67..e02468125 100644 --- a/src/daemonizer/windows_daemonizer.inl +++ b/src/daemonizer/windows_daemonizer.inl @@ -111,9 +111,9 @@ namespace daemonizer { if (command_line::has_arg(vm, arg_is_service)) { - if (command_line::has_arg(vm, command_line::arg_data_dir)) + if (command_line::has_arg(vm, cryptonote::arg_data_dir)) { - return command_line::get_arg(vm, command_line::arg_data_dir); + return command_line::get_arg(vm, cryptonote::arg_data_dir); } else { diff --git a/src/mnemonics/electrum-words.cpp b/src/mnemonics/electrum-words.cpp index 6ae33a743..1b14905f6 100644 --- a/src/mnemonics/electrum-words.cpp +++ b/src/mnemonics/electrum-words.cpp @@ -236,11 +236,13 @@ namespace crypto /*! * \brief Converts seed words to bytes (secret key). * \param words String containing the words separated by spaces. - * \param dst To put the secret key restored from the words. + * \param dst To put the secret data restored from the words. + * \param len The number of bytes to expect, 0 if unknown + * \param duplicate If true and len is not zero, we accept half the data, and duplicate it * \param language_name Language of the seed as found gets written here. * \return false if not a multiple of 3 words, or if word is not in the words list */ - bool words_to_bytes(std::string words, crypto::secret_key& dst, + bool words_to_bytes(std::string words, std::string& dst, size_t len, bool duplicate, std::string &language_name) { std::vector<std::string> seed; @@ -248,15 +250,23 @@ namespace crypto boost::algorithm::trim(words); boost::split(seed, words, boost::is_any_of(" "), boost::token_compress_on); - // error on non-compliant word list - if (seed.size() != seed_length/2 && seed.size() != seed_length && - seed.size() != seed_length + 1) - { + if (len % 4) return false; - } - // If it is seed with a checksum. - bool has_checksum = seed.size() == (seed_length + 1); + bool has_checksum = true; + if (len) + { + // error on non-compliant word list + const size_t expected = len * 8 * 3 / 32; + if (seed.size() != expected/2 && seed.size() != expected && + seed.size() != expected + 1) + { + return false; + } + + // If it is seed with a checksum. + has_checksum = seed.size() == (expected + 1); + } std::vector<uint32_t> matched_indices; Language::Base *language; @@ -290,32 +300,55 @@ namespace crypto if (!(val % word_list_length == w1)) return false; - memcpy(dst.data + i * 4, &val, 4); // copy 4 bytes to position + dst.append((const char*)&val, 4); // copy 4 bytes to position } - std::string wlist_copy = words; - if (seed.size() == seed_length/2) + if (len > 0 && duplicate) { - memcpy(dst.data+16, dst.data, 16); // if electrum 12-word seed, duplicate - wlist_copy += ' '; - wlist_copy += words; + const size_t expected = len * 3 / 32; + std::string wlist_copy = words; + if (seed.size() == expected/2) + { + dst.append(dst); // if electrum 12-word seed, duplicate + wlist_copy += ' '; + wlist_copy += words; + } } return true; } /*! + * \brief Converts seed words to bytes (secret key). + * \param words String containing the words separated by spaces. + * \param dst To put the secret key restored from the words. + * \param language_name Language of the seed as found gets written here. + * \return false if not a multiple of 3 words, or if word is not in the words list + */ + bool words_to_bytes(std::string words, crypto::secret_key& dst, + std::string &language_name) + { + std::string s; + if (!words_to_bytes(words, s, sizeof(dst), true, language_name)) + return false; + if (s.size() != sizeof(dst)) + return false; + dst = *(const crypto::secret_key*)s.data(); + return true; + } + + /*! * \brief Converts bytes (secret key) to seed words. * \param src Secret key * \param words Space delimited concatenated words get written here. * \param language_name Seed language name * \return true if successful false if not. Unsuccessful if wrong key size. */ - bool bytes_to_words(const crypto::secret_key& src, std::string& words, + bool bytes_to_words(const char *src, size_t len, std::string& words, const std::string &language_name) { - if (sizeof(src.data) % 4 != 0 || sizeof(src.data) == 0) return false; + if (len % 4 != 0 || len == 0) return false; Language::Base *language; if (language_name == "English") @@ -376,13 +409,13 @@ namespace crypto uint32_t word_list_length = word_list.size(); // 8 bytes -> 3 words. 8 digits base 16 -> 3 digits base 1626 - for (unsigned int i=0; i < sizeof(src.data)/4; i++, words += ' ') + for (unsigned int i=0; i < len/4; i++, words += ' ') { uint32_t w1, w2, w3; uint32_t val; - memcpy(&val, (src.data) + (i * 4), 4); + memcpy(&val, src + (i * 4), 4); w1 = val % word_list_length; w2 = ((val / word_list_length) + w1) % word_list_length; @@ -404,6 +437,12 @@ namespace crypto return true; } + bool bytes_to_words(const crypto::secret_key& src, std::string& words, + const std::string &language_name) + { + return bytes_to_words(src.data, sizeof(src), words, language_name); + } + /*! * \brief Gets a list of seed languages that are supported. * \param languages The vector is set to the list of languages. diff --git a/src/mnemonics/electrum-words.h b/src/mnemonics/electrum-words.h index 94ce9c200..941768352 100644 --- a/src/mnemonics/electrum-words.h +++ b/src/mnemonics/electrum-words.h @@ -64,6 +64,17 @@ namespace crypto /*! * \brief Converts seed words to bytes (secret key). * \param words String containing the words separated by spaces. + * \param dst To put the secret data restored from the words. + * \param len The number of bytes to expect, 0 if unknown + * \param duplicate If true and len is not zero, we accept half the data, and duplicate it + * \param language_name Language of the seed as found gets written here. + * \return false if not a multiple of 3 words, or if word is not in the words list + */ + bool words_to_bytes(std::string words, std::string& dst, size_t len, bool duplicate, + std::string &language_name); + /*! + * \brief Converts seed words to bytes (secret key). + * \param words String containing the words separated by spaces. * \param dst To put the secret key restored from the words. * \param language_name Language of the seed as found gets written here. * \return false if not a multiple of 3 words, or if word is not in the words list @@ -72,6 +83,17 @@ namespace crypto std::string &language_name); /*! + * \brief Converts bytes to seed words. + * \param src Secret data + * \param len Secret data length in bytes (positive multiples of 4 only) + * \param words Space delimited concatenated words get written here. + * \param language_name Seed language name + * \return true if successful false if not. Unsuccessful if wrong key size. + */ + bool bytes_to_words(const char *src, size_t len, std::string& words, + const std::string &language_name); + + /*! * \brief Converts bytes (secret key) to seed words. * \param src Secret key * \param words Space delimited concatenated words get written here. diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 6e7fb7e8e..f146dae3e 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -149,6 +149,7 @@ namespace cryptonote res.block_size_limit = m_core.get_blockchain_storage().get_current_cumulative_blocksize_limit(); res.status = CORE_RPC_STATUS_OK; res.start_time = (uint64_t)m_core.get_start_time(); + res.free_space = m_restricted ? std::numeric_limits<uint64_t>::max() : m_core.get_free_space(); return true; } //------------------------------------------------------------------------------------------------------------------------------ @@ -1328,6 +1329,7 @@ namespace cryptonote res.block_size_limit = m_core.get_blockchain_storage().get_current_cumulative_blocksize_limit(); res.status = CORE_RPC_STATUS_OK; res.start_time = (uint64_t)m_core.get_start_time(); + res.free_space = m_restricted ? std::numeric_limits<uint64_t>::max() : m_core.get_free_space(); return true; } //------------------------------------------------------------------------------------------------------------------------------ @@ -1745,12 +1747,13 @@ namespace cryptonote res.peers.push_back({c}); const cryptonote::block_queue &block_queue = m_p2p.get_payload_object().get_block_queue(); block_queue.foreach([&](const cryptonote::block_queue::span &span) { + const std::string span_connection_id = epee::string_tools::pod_to_hex(span.connection_id); uint32_t speed = (uint32_t)(100.0f * block_queue.get_speed(span.connection_id) + 0.5f); std::string address = ""; for (const auto &c: m_p2p.get_payload_object().get_connections()) - if (c.connection_id == span.connection_id) + if (c.connection_id == span_connection_id) address = c.address; - res.spans.push_back({span.start_block_height, span.nblocks, span.connection_id, (uint32_t)(span.rate + 0.5f), speed, span.size, address}); + res.spans.push_back({span.start_block_height, span.nblocks, span_connection_id, (uint32_t)(span.rate + 0.5f), speed, span.size, address}); return true; }); diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 15b4b503a..58a6ce9e1 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -927,6 +927,7 @@ namespace cryptonote uint64_t cumulative_difficulty; uint64_t block_size_limit; uint64_t start_time; + uint64_t free_space; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(status) @@ -947,6 +948,7 @@ namespace cryptonote KV_SERIALIZE(cumulative_difficulty) KV_SERIALIZE(block_size_limit) KV_SERIALIZE(start_time) + KV_SERIALIZE(free_space) END_KV_SERIALIZE_MAP() }; }; @@ -2075,7 +2077,7 @@ namespace cryptonote { uint64_t start_block_height; uint64_t nblocks; - boost::uuids::uuid connection_id; + std::string connection_id; uint32_t rate; uint32_t speed; uint64_t size; @@ -2084,7 +2086,7 @@ namespace cryptonote BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(start_block_height) KV_SERIALIZE(nblocks) - KV_SERIALIZE_VAL_POD_AS_BLOB(connection_id) + KV_SERIALIZE(connection_id) KV_SERIALIZE(rate) KV_SERIALIZE(speed) KV_SERIALIZE(size) diff --git a/src/serialization/serialization.h b/src/serialization/serialization.h index 869f5d10e..9e23f0791 100644 --- a/src/serialization/serialization.h +++ b/src/serialization/serialization.h @@ -43,6 +43,8 @@ #include <vector> #include <deque> #include <list> +#include <set> +#include <unordered_set> #include <string> #include <boost/type_traits/is_integral.hpp> #include <boost/type_traits/integral_constant.hpp> @@ -364,3 +366,4 @@ namespace serialization { #include "vector.h" #include "list.h" #include "pair.h" +#include "set.h" diff --git a/src/serialization/set.h b/src/serialization/set.h new file mode 100644 index 000000000..54b4eb3ab --- /dev/null +++ b/src/serialization/set.h @@ -0,0 +1,127 @@ +// Copyright (c) 2014-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 "serialization.h" + +template <template <bool> class Archive, class T> +bool do_serialize(Archive<false> &ar, std::set<T> &v); +template <template <bool> class Archive, class T> +bool do_serialize(Archive<true> &ar, std::set<T> &v); +template <template <bool> class Archive, class T> +bool do_serialize(Archive<false> &ar, std::unordered_set<T> &v); +template <template <bool> class Archive, class T> +bool do_serialize(Archive<true> &ar, std::unordered_set<T> &v); + +namespace serialization +{ + namespace detail + { + template <typename Archive, class T> + bool serialize_set_element(Archive& ar, T& e) + { + return ::do_serialize(ar, e); + } + + template <typename Archive> + bool serialize_set_element(Archive& ar, uint32_t& e) + { + ar.serialize_varint(e); + return true; + } + + template <typename Archive> + bool serialize_set_element(Archive& ar, uint64_t& e) + { + ar.serialize_varint(e); + return true; + } + } +} + +template <template <bool> class Archive, class T> +bool do_serialize_set(Archive<false> &ar, T &v) +{ + size_t cnt; + ar.begin_array(cnt); + if (!ar.stream().good()) + return false; + v.clear(); + + // very basic sanity check + if (ar.remaining_bytes() < cnt) { + ar.stream().setstate(std::ios::failbit); + return false; + } + + for (size_t i = 0; i < cnt; i++) { + if (i > 0) + ar.delimit_array(); + typename T::key_type k; + if (!::serialization::detail::serialize_set_element(ar, k)) + return false; + v.insert(std::move(k)); + if (!ar.stream().good()) + return false; + } + ar.end_array(); + return true; +} + +template <template <bool> class Archive, class T> +bool do_serialize_set(Archive<true> &ar, T &v) +{ + size_t cnt = v.size(); + ar.begin_array(cnt); + bool first = true; + for (const typename T::key_type &k: v) { + if (!ar.stream().good()) + return false; + if (!first) + ar.delimit_array(); + if(!::serialization::detail::serialize_set_element(ar, const_cast<typename T::key_type&>(k))) + return false; + if (!ar.stream().good()) + return false; + first = false; + } + ar.end_array(); + return true; +} + +template <template <bool> class Archive, class T> +bool do_serialize(Archive<false> &ar, std::set<T> &v) { return do_serialize_set(ar, v); } +template <template <bool> class Archive, class T> +bool do_serialize(Archive<true> &ar, std::set<T> &v) { return do_serialize_set(ar, v); } +template <template <bool> class Archive, class T> +bool do_serialize(Archive<false> &ar, std::unordered_set<T> &v) { return do_serialize_set(ar, v); } +template <template <bool> class Archive, class T> +bool do_serialize(Archive<true> &ar, std::unordered_set<T> &v) { return do_serialize_set(ar, v); } diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 9b5752b19..489733120 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -80,6 +80,8 @@ typedef cryptonote::simple_wallet sw; #define DEFAULT_MIX 4 +#define MIN_RING_SIZE 5 // Used to inform user about min ring size -- does not track actual protocol + #define OUTPUT_EXPORT_FILE_MAGIC "Monero output export\003" #define LOCK_IDLE_SCOPE() \ @@ -363,6 +365,27 @@ std::string simple_wallet::get_commands_str() return ss.str(); } +std::string simple_wallet::get_command_usage(const std::vector<std::string> &args) +{ + std::pair<std::string, std::string> documentation = m_cmd_binder.get_documentation(args); + std::stringstream ss; + if(documentation.first.empty()) + { + ss << tr("Unknown command: ") << args.front(); + } + else + { + std::string usage = documentation.second.empty() ? args.front() : documentation.first; + std::string description = documentation.second.empty() ? documentation.first : documentation.second; + usage.insert(0, " "); + ss << tr("Command usage: ") << ENDL << usage << ENDL << ENDL; + boost::replace_all(description, "\n", "\n "); + description.insert(0, " "); + ss << tr("Command description: ") << ENDL << description << ENDL; + } + return ss.str(); +} + bool simple_wallet::viewkey(const std::vector<std::string> &args/* = std::vector<std::string>()*/) { if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } @@ -619,27 +642,27 @@ bool simple_wallet::set_default_ring_size(const std::vector<std::string> &args/* { if (strchr(args[1].c_str(), '-')) { - fail_msg_writer() << tr("ring size must be an integer >= 3"); + fail_msg_writer() << tr("ring size must be an integer >= ") << MIN_RING_SIZE; return true; } uint32_t ring_size = boost::lexical_cast<uint32_t>(args[1]); - if (ring_size < 3 && ring_size != 0) + if (ring_size < MIN_RING_SIZE && ring_size != 0) { - fail_msg_writer() << tr("ring size must be an integer >= 3"); + fail_msg_writer() << tr("ring size must be an integer >= ") << MIN_RING_SIZE; return true; } const auto pwd_container = get_and_verify_password(); if (pwd_container) { - m_wallet->default_mixin(ring_size - 1); + m_wallet->default_mixin(ring_size > 0 ? ring_size - 1 : 0); m_wallet->rewrite(m_wallet_file, pwd_container->password()); } return true; } catch(const boost::bad_lexical_cast &) { - fail_msg_writer() << tr("ring size must be an integer >= 3"); + fail_msg_writer() << tr("ring size must be an integer >= ") << MIN_RING_SIZE; return true; } catch(...) @@ -883,7 +906,14 @@ bool simple_wallet::set_refresh_from_block_height(const std::vector<std::string> bool simple_wallet::help(const std::vector<std::string> &args/* = std::vector<std::string>()*/) { - success_msg_writer() << get_commands_str(); + if(args.empty()) + { + success_msg_writer() << get_commands_str(); + } + else + { + success_msg_writer() << get_command_usage(args); + } return true; } @@ -896,61 +926,244 @@ simple_wallet::simple_wallet() , m_in_manual_refresh(false) , m_current_subaddress_account(0) { - m_cmd_binder.set_handler("start_mining", boost::bind(&simple_wallet::start_mining, this, _1), tr("start_mining [<number_of_threads>] [bg_mining] [ignore_battery] - Start mining in daemon (bg_mining and ignore_battery are optional booleans)")); - m_cmd_binder.set_handler("stop_mining", boost::bind(&simple_wallet::stop_mining, this, _1), tr("Stop mining in daemon")); - m_cmd_binder.set_handler("save_bc", boost::bind(&simple_wallet::save_bc, this, _1), tr("Save current blockchain data")); - m_cmd_binder.set_handler("refresh", boost::bind(&simple_wallet::refresh, this, _1), tr("Synchronize transactions and balance")); - m_cmd_binder.set_handler("balance", boost::bind(&simple_wallet::show_balance, this, _1), tr("balance [detail] - Show wallet balance of currently selected account")); - m_cmd_binder.set_handler("incoming_transfers", boost::bind(&simple_wallet::show_incoming_transfers, this, _1), tr("incoming_transfers [available|unavailable] [verbose] [index=<N1>[,<N2>,...]] - Show incoming transfers, all or filtered by availability and address index")); - m_cmd_binder.set_handler("payments", boost::bind(&simple_wallet::show_payments, this, _1), tr("payments <PID_1> [<PID_2> ... <PID_N>] - Show payments for given payment ID[s]")); - m_cmd_binder.set_handler("bc_height", boost::bind(&simple_wallet::show_blockchain_height, this, _1), tr("Show blockchain height")); - m_cmd_binder.set_handler("transfer_original", boost::bind(&simple_wallet::transfer, this, _1), tr("Same as transfer, but using an older transaction building algorithm")); - m_cmd_binder.set_handler("transfer", boost::bind(&simple_wallet::transfer_new, this, _1), tr("transfer [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <address> <amount> [<payment_id>] - Transfer <amount> to <address>. If the parameter \"index=<N1>[,<N2>,...]\" is specified, the wallet uses outputs received by addresses of those indices. If omitted, the wallet randomly chooses address indices to be used. In any case, it tries its best not to combine outputs across multiple addresses. <priority> is the priority of the transaction. The higher the priority, the higher the fee of the transaction. Valid values in priority order (from lowest to highest) are: unimportant, normal, elevated, priority. If omitted, the default value (see the command \"set priority\") is used. <ring_size> is the number of inputs to include for untraceability. Multiple payments can be made at once by adding <address_2> <amount_2> etcetera (before the payment ID, if it's included)")); - m_cmd_binder.set_handler("locked_transfer", boost::bind(&simple_wallet::locked_transfer, this, _1), tr("locked_transfer [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <addr> <amount> <lockblocks> [<payment_id>] - Same as transfer, but with number of blocks to lock the transaction for, max 1000000")); - m_cmd_binder.set_handler("sweep_unmixable", boost::bind(&simple_wallet::sweep_unmixable, this, _1), tr("Send all unmixable outputs to yourself with ring_size 1")); - m_cmd_binder.set_handler("sweep_all", boost::bind(&simple_wallet::sweep_all, this, _1), tr("sweep_all [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <address> [<payment_id>] - Send all unlocked balance to an address. If the parameter \"index<N1>[,<N2>,...]\" is specified, the wallet sweeps outputs received by those address indices. If omitted, the wallet randomly chooses an address index to be used.")); - m_cmd_binder.set_handler("sweep_below", boost::bind(&simple_wallet::sweep_below, this, _1), tr("sweep_below <amount_threshold> [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <address> [<payment_id>] - Send all unlocked outputs below the threshold to an address")); - m_cmd_binder.set_handler("sweep_single", boost::bind(&simple_wallet::sweep_single, this, _1), tr("sweep_single [<priority>] [<ring_size>] <key_image> <address> [<payment_id>] - Send a single output of the given key image to an address without change")); - m_cmd_binder.set_handler("donate", boost::bind(&simple_wallet::donate, this, _1), tr("donate [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <amount> [<payment_id>] - Donate <amount> to the development team (donate.getmonero.org)")); - m_cmd_binder.set_handler("sign_transfer", boost::bind(&simple_wallet::sign_transfer, this, _1), tr("Sign a transaction from a file")); - m_cmd_binder.set_handler("submit_transfer", boost::bind(&simple_wallet::submit_transfer, this, _1), tr("Submit a signed transaction from a file")); - m_cmd_binder.set_handler("set_log", boost::bind(&simple_wallet::set_log, this, _1), tr("set_log <level>|{+,-,}<categories> - Change 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>] - If no argments 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.")); - 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> ] - If no arguments are specified or <index> is specified, the wallet shows the default or specified address. If \"all\" is specified, the walllet shows all the existing addresses in the currently selected account. If \"new \" is specified, the wallet creates a new address with the provided label text (which can be empty). If \"label\" is specified, the wallet sets the label of the address specified by <index> to the provided label text.")); - m_cmd_binder.set_handler("integrated_address", boost::bind(&simple_wallet::print_integrated_address, this, _1), tr("integrated_address [PID] - Encode a payment ID into an integrated address for the current wallet public address (no argument uses a random payment ID), or decode an integrated address to standard address and payment ID")); - m_cmd_binder.set_handler("address_book", boost::bind(&simple_wallet::address_book, this, _1), tr("address_book [(add (<address> [pid <long or short payment id>])|<integrated address> [<description possibly with whitespaces>])|(delete <index>)] - Print all entries in the address book, optionally adding/deleting an entry to/from it")); - m_cmd_binder.set_handler("save", boost::bind(&simple_wallet::save, this, _1), tr("Save wallet data")); - m_cmd_binder.set_handler("save_watch_only", boost::bind(&simple_wallet::save_watch_only, this, _1), tr("Save a watch-only keys file")); - m_cmd_binder.set_handler("viewkey", boost::bind(&simple_wallet::viewkey, this, _1), tr("Display private view key")); - m_cmd_binder.set_handler("spendkey", boost::bind(&simple_wallet::spendkey, this, _1), tr("Display private spend key")); - m_cmd_binder.set_handler("seed", boost::bind(&simple_wallet::seed, this, _1), tr("Display Electrum-style mnemonic seed")); - m_cmd_binder.set_handler("set", boost::bind(&simple_wallet::set_variable, this, _1), tr("Available options: seed language - set wallet seed language; always-confirm-transfers <1|0> - whether to confirm unsplit txes; print-ring-members <1|0> - whether to print detailed information about ring members during confirmation; store-tx-info <1|0> - whether to store outgoing tx info (destination address, payment ID, tx secret key) for future reference; default-ring-size <n> - set default ring size (default is 5); auto-refresh <1|0> - whether to automatically sync new blocks from the daemon; refresh-type <full|optimize-coinbase|no-coinbase|default> - set wallet refresh behaviour; priority [0|1|2|3|4] - default/unimportant/normal/elevated/priority fee; confirm-missing-payment-id <1|0>; ask-password <1|0>; unit <monero|millinero|micronero|nanonero|piconero> - set default monero (sub-)unit; min-outputs-count [n] - try to keep at least that many outputs of value at least min-outputs-value; min-outputs-value [n] - try to keep at least min-outputs-count outputs of at least that value; merge-destinations <1|0> - whether to merge multiple payments to the same destination address; confirm-backlog <1|0> - whether to warn if there is transaction backlog; confirm-backlog-threshold [n] - sets a threshold for confirm-backlog to only warn if the transaction backlog is greater than n blocks; refresh-from-block-height [n] - set height before which to ignore blocks")); - m_cmd_binder.set_handler("encrypted_seed", boost::bind(&simple_wallet::encrypted_seed, this, _1), tr("Display encrypted Electrum-style mnemonic seed")); - m_cmd_binder.set_handler("rescan_spent", boost::bind(&simple_wallet::rescan_spent, this, _1), tr("Rescan blockchain for spent outputs")); - m_cmd_binder.set_handler("get_tx_key", boost::bind(&simple_wallet::get_tx_key, this, _1), tr("Get transaction key (r) for a given <txid>")); - m_cmd_binder.set_handler("check_tx_key", boost::bind(&simple_wallet::check_tx_key, this, _1), tr("Check amount going to <address> in <txid>")); - m_cmd_binder.set_handler("get_tx_proof", boost::bind(&simple_wallet::get_tx_proof, this, _1), tr("Generate a signature to prove payment to <address> in <txid> using the transaction secret key (r) without revealing it")); - m_cmd_binder.set_handler("check_tx_proof", boost::bind(&simple_wallet::check_tx_proof, this, _1), tr("Check tx proof for payment going to <address> in <txid>")); - m_cmd_binder.set_handler("show_transfers", boost::bind(&simple_wallet::show_transfers, this, _1), tr("show_transfers [in|out|pending|failed|pool] [index=<N1>[,<N2>,...]] [<min_height> [<max_height>]] - Show incoming/outgoing transfers within an optional height range")); - m_cmd_binder.set_handler("unspent_outputs", boost::bind(&simple_wallet::unspent_outputs, this, _1), tr("unspent_outputs [index=<N1>[,<N2>,...]] [<min_amount> [<max_amount>]] - Show unspent outputs of a specified address within an optional amount range")); - m_cmd_binder.set_handler("rescan_bc", boost::bind(&simple_wallet::rescan_blockchain, this, _1), tr("Rescan blockchain from scratch")); - m_cmd_binder.set_handler("set_tx_note", boost::bind(&simple_wallet::set_tx_note, this, _1), tr("Set an arbitrary string note for a txid")); - m_cmd_binder.set_handler("get_tx_note", boost::bind(&simple_wallet::get_tx_note, this, _1), tr("Get a string note for a txid")); - m_cmd_binder.set_handler("set_description", boost::bind(&simple_wallet::set_description, this, _1), tr("Set an arbitrary description for the wallet")); - m_cmd_binder.set_handler("get_description", boost::bind(&simple_wallet::get_description, this, _1), tr("Get the description of the wallet ")); - m_cmd_binder.set_handler("status", boost::bind(&simple_wallet::status, this, _1), tr("Show wallet status information")); - m_cmd_binder.set_handler("wallet_info", boost::bind(&simple_wallet::wallet_info, this, _1), tr("Show wallet information")); - m_cmd_binder.set_handler("sign", boost::bind(&simple_wallet::sign, this, _1), tr("Sign the contents of a file")); - m_cmd_binder.set_handler("verify", boost::bind(&simple_wallet::verify, this, _1), tr("Verify a signature on the contents of a file")); - m_cmd_binder.set_handler("export_key_images", boost::bind(&simple_wallet::export_key_images, this, _1), tr("Export a signed set of key images")); - m_cmd_binder.set_handler("import_key_images", boost::bind(&simple_wallet::import_key_images, this, _1), tr("Import signed key images list and verify their spent status")); - m_cmd_binder.set_handler("export_outputs", boost::bind(&simple_wallet::export_outputs, this, _1), tr("Export a set of outputs owned by this wallet")); - m_cmd_binder.set_handler("import_outputs", boost::bind(&simple_wallet::import_outputs, this, _1), tr("Import set of outputs owned by this wallet")); - m_cmd_binder.set_handler("show_transfer", boost::bind(&simple_wallet::show_transfer, this, _1), tr("Show information about a transfer to/from this address")); - m_cmd_binder.set_handler("password", boost::bind(&simple_wallet::change_password, this, _1), tr("Change wallet password")); - m_cmd_binder.set_handler("payment_id", boost::bind(&simple_wallet::payment_id, this, _1), tr("Generate a new random full size payment id - these will be unencrypted on the blockchain, see integrated_address for encrypted short payment ids")); - m_cmd_binder.set_handler("fee", boost::bind(&simple_wallet::print_fee_info, this, _1), tr("Print information about fee and current transaction backlog")); - m_cmd_binder.set_handler("help", boost::bind(&simple_wallet::help, this, _1), tr("Show this help")); + m_cmd_binder.set_handler("start_mining", + boost::bind(&simple_wallet::start_mining, this, _1), + tr("start_mining [<number_of_threads>] [bg_mining] [ignore_battery]"), + tr("Start mining in the daemon (bg_mining and ignore_battery are optional booleans).")); + m_cmd_binder.set_handler("stop_mining", + boost::bind(&simple_wallet::stop_mining, this, _1), + tr("Stop mining in the daemon.")); + m_cmd_binder.set_handler("save_bc", + boost::bind(&simple_wallet::save_bc, this, _1), + tr("Save the current blockchain data.")); + m_cmd_binder.set_handler("refresh", + boost::bind(&simple_wallet::refresh, this, _1), + tr("Synchronize the transactions and balance.")); + m_cmd_binder.set_handler("balance", + boost::bind(&simple_wallet::show_balance, this, _1), + tr("balance [detail]"), + tr("Show the wallet's balance of the currently selected account.")); + m_cmd_binder.set_handler("incoming_transfers", + boost::bind(&simple_wallet::show_incoming_transfers, this, _1), + tr("incoming_transfers [available|unavailable] [verbose] [index=<N1>[,<N2>[,...]]]"), + tr("Show the incoming transfers, all or filtered by availability and address index.")); + m_cmd_binder.set_handler("payments", + boost::bind(&simple_wallet::show_payments, this, _1), + tr("payments <PID_1> [<PID_2> ... <PID_N>]"), + tr("Show the payments for the given payment IDs.")); + m_cmd_binder.set_handler("bc_height", + boost::bind(&simple_wallet::show_blockchain_height, this, _1), + tr("Show the blockchain height.")); + m_cmd_binder.set_handler("transfer_original", + boost::bind(&simple_wallet::transfer, this, _1), + tr("transfer_original [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <address> <amount> [<payment_id>]"), + tr("Transfer <amount> to <address> using an older transaction building algorithm. If the parameter \"index=<N1>[,<N2>,...]\" is specified, the wallet uses outputs received by addresses of those indices. If omitted, the wallet randomly chooses address indices to be used. In any case, it tries its best not to combine outputs across multiple addresses. <priority> is the priority of the transaction. The higher the priority, the higher the fee of the transaction. Valid values in priority order (from lowest to highest) are: unimportant, normal, elevated, priority. If omitted, the default value (see the command \"set priority\") is used. <ring_size> is the number of inputs to include for untraceability. Multiple payments can be made at once by adding <address_2> <amount_2> etcetera (before the payment ID, if it's included)")); + m_cmd_binder.set_handler("transfer", boost::bind(&simple_wallet::transfer_new, this, _1), + tr("transfer [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <address> <amount> [<payment_id>]"), + tr("Transfer <amount> to <address>. If the parameter \"index=<N1>[,<N2>,...]\" is specified, the wallet uses outputs received by addresses of those indices. If omitted, the wallet randomly chooses address indices to be used. In any case, it tries its best not to combine outputs across multiple addresses. <priority> is the priority of the transaction. The higher the priority, the higher the fee of the transaction. Valid values in priority order (from lowest to highest) are: unimportant, normal, elevated, priority. If omitted, the default value (see the command \"set priority\") is used. <ring_size> is the number of inputs to include for untraceability. Multiple payments can be made at once by adding <address_2> <amount_2> etcetera (before the payment ID, if it's included)")); + m_cmd_binder.set_handler("locked_transfer", + boost::bind(&simple_wallet::locked_transfer, this, _1), + tr("locked_transfer [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <addr> <amount> <lockblocks> [<payment_id>]"), + tr("Transfer <amount> to <address> and lock it for <lockblocks> (max. 1000000). If the parameter \"index=<N1>[,<N2>,...]\" is specified, the wallet uses outputs received by addresses of those indices. If omitted, the wallet randomly chooses address indices to be used. In any case, it tries its best not to combine outputs across multiple addresses. <priority> is the priority of the transaction. The higher the priority, the higher the fee of the transaction. Valid values in priority order (from lowest to highest) are: unimportant, normal, elevated, priority. If omitted, the default value (see the command \"set priority\") is used. <ring_size> is the number of inputs to include for untraceability. Multiple payments can be made at once by adding <address_2> <amount_2> etcetera (before the payment ID, if it's included)")); + m_cmd_binder.set_handler("sweep_unmixable", + boost::bind(&simple_wallet::sweep_unmixable, this, _1), + tr("Send all unmixable outputs to yourself with ring_size 1")); + m_cmd_binder.set_handler("sweep_all", boost::bind(&simple_wallet::sweep_all, this, _1), + tr("sweep_all [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <address> [<payment_id>]"), + tr("Send all unlocked balance to an address. If the parameter \"index<N1>[,<N2>,...]\" is specified, the wallet sweeps outputs received by those address indices. If omitted, the wallet randomly chooses an address index to be used.")); + m_cmd_binder.set_handler("sweep_below", + boost::bind(&simple_wallet::sweep_below, this, _1), + tr("sweep_below <amount_threshold> [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <address> [<payment_id>]"), + tr("Send all unlocked outputs below the threshold to an address.")); + m_cmd_binder.set_handler("sweep_single", + boost::bind(&simple_wallet::sweep_single, this, _1), + tr("sweep_single [<priority>] [<ring_size>] <key_image> <address> [<payment_id>]"), + tr("Send a single output of the given key image to an address without change.")); + m_cmd_binder.set_handler("donate", + boost::bind(&simple_wallet::donate, this, _1), + tr("donate [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <amount> [<payment_id>]"), + tr("Donate <amount> to the development team (donate.getmonero.org).")); + m_cmd_binder.set_handler("sign_transfer", + boost::bind(&simple_wallet::sign_transfer, this, _1), + tr("sign_transfer <file>"), + tr("Sign a transaction from a <file>.")); + m_cmd_binder.set_handler("submit_transfer", + boost::bind(&simple_wallet::submit_transfer, this, _1), + tr("Submit a signed transaction from a file.")); + m_cmd_binder.set_handler("set_log", + boost::bind(&simple_wallet::set_log, this, _1), + tr("set_log <level>|{+,-,}<categories>"), + 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.")); + 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>]"), + tr("If no arguments are specified or <index> is specified, the wallet shows the default or specified address. If \"all\" is specified, the walllet shows all the existing addresses in the currently selected account. If \"new \" is specified, the wallet creates a new address with the provided label text (which can be empty). If \"label\" is specified, the wallet sets the label of the address specified by <index> to the provided label text.")); + m_cmd_binder.set_handler("integrated_address", + boost::bind(&simple_wallet::print_integrated_address, this, _1), + tr("integrated_address [<payment_id> | <address>]"), + tr("Encode a payment ID into an integrated address for the current wallet public address (no argument uses a random payment ID), or decode an integrated address to standard address and payment ID")); + m_cmd_binder.set_handler("address_book", + boost::bind(&simple_wallet::address_book, this, _1), + tr("address_book [(add ((<address> [pid <id>])|<integrated address>) [<description possibly with whitespaces>])|(delete <index>)]"), + tr("Print all entries in the address book, optionally adding/deleting an entry to/from it.")); + m_cmd_binder.set_handler("save", + boost::bind(&simple_wallet::save, this, _1), + tr("Save the wallet data.")); + m_cmd_binder.set_handler("save_watch_only", + boost::bind(&simple_wallet::save_watch_only, this, _1), + tr("Save a watch-only keys file.")); + m_cmd_binder.set_handler("viewkey", + boost::bind(&simple_wallet::viewkey, this, _1), + tr("Display the private view key.")); + m_cmd_binder.set_handler("spendkey", + boost::bind(&simple_wallet::spendkey, this, _1), + tr("Display the private spend key.")); + m_cmd_binder.set_handler("seed", + boost::bind(&simple_wallet::seed, this, _1), + tr("Display the Electrum-style mnemonic seed")); + m_cmd_binder.set_handler("set", + boost::bind(&simple_wallet::set_variable, this, _1), + tr("set <option> [<value>]"), + tr("Available options:\n " + "seed language\n " + " Set the wallet's seed language.\n " + "always-confirm-transfers <1|0>\n " + " Whether to confirm unsplit txes.\n " + "print-ring-members <1|0>\n " + " Whether to print detailed information about ring members during confirmation.\n " + "store-tx-info <1|0>\n " + " Whether to store outgoing tx info (destination address, payment ID, tx secret key) for future reference.\n " + "default-ring-size <n>\n " + " Set the default ring size (default and minimum is 5).\n " + "auto-refresh <1|0>\n " + " Whether to automatically synchronize new blocks from the daemon.\n " + "refresh-type <full|optimize-coinbase|no-coinbase|default>\n " + " Set the wallet's refresh behaviour.\n " + "priority [0|1|2|3|4]\n " + " Set the fee too default/unimportant/normal/elevated/priority.\n " + "confirm-missing-payment-id <1|0>\n " + "ask-password <1|0>\n " + "unit <monero|millinero|micronero|nanonero|piconero>\n " + " Set the default monero (sub-)unit.\n " + "min-outputs-count [n]\n " + " Try to keep at least that many outputs of value at least min-outputs-value.\n " + "min-outputs-value [n]\n " + " Try to keep at least min-outputs-count outputs of at least that value.\n " + "merge-destinations <1|0>\n " + " Whether to merge multiple payments to the same destination address.\n " + "confirm-backlog <1|0>\n " + " Whether to warn if there is transaction backlog.\n " + "confirm-backlog-threshold [n]\n " + " Set a threshold for confirm-backlog to only warn if the transaction backlog is greater than n blocks.\n " + "refresh-from-block-height [n]\n " + " Set the height before which to ignore blocks.")); + m_cmd_binder.set_handler("encrypted_seed", + boost::bind(&simple_wallet::encrypted_seed, this, _1), + tr("Display the encrypted Electrum-style mnemonic seed.")); + m_cmd_binder.set_handler("rescan_spent", + boost::bind(&simple_wallet::rescan_spent, this, _1), + tr("Rescan the blockchain for spent outputs.")); + m_cmd_binder.set_handler("get_tx_key", + boost::bind(&simple_wallet::get_tx_key, this, _1), + tr("get_tx_key <txid>"), + tr("Get the transaction key (r) for a given <txid>.")); + m_cmd_binder.set_handler("check_tx_key", + boost::bind(&simple_wallet::check_tx_key, this, _1), + tr("check_tx_key <txid> <txkey> <address>"), + tr("Check the amount going to <address> in <txid>.")); + m_cmd_binder.set_handler("get_tx_proof_out", + boost::bind(&simple_wallet::get_tx_proof, this, _1), + tr("get_tx_proof_out <txid> <address> [<message>]"), + tr("Generate a signature proving funds sent to <address> in <txid>, optionally with a challenge string <message>, using either the transaction secret key (when <address> is not your wallet's address) or the view secret key (otherwise), which does not disclose the secret key.")); + m_cmd_binder.set_handler("check_tx_proof", + boost::bind(&simple_wallet::check_tx_proof, this, _1), + tr("check_tx_proof <txid> <address> <signature_file> [<message>]"), + tr("Check the proof for funds going to <address> in <txid> with the challenge string <message> if any.")); + m_cmd_binder.set_handler("get_spend_proof", + boost::bind(&simple_wallet::get_spend_proof, this, _1), + tr("get_spend_proof <txid> [<message>]"), + tr("Generate a signature proving that you generated <txid> using the spend secret key, optionally with a challenge string <message>.")); + m_cmd_binder.set_handler("check_spend_proof", + boost::bind(&simple_wallet::check_spend_proof, this, _1), + tr("check_spend_proof <txid> <signature_file> [<message>]"), + tr("Check a signature proving that the signer generated <txid>, optionally with a challenge string <message>.")); + m_cmd_binder.set_handler("show_transfers", + boost::bind(&simple_wallet::show_transfers, this, _1), + tr("show_transfers [in|out|pending|failed|pool] [index=<N1>[,<N2>,...]] [<min_height> [<max_height>]]"), + tr("Show the incoming/outgoing transfers within an optional height range.")); + m_cmd_binder.set_handler("unspent_outputs", + boost::bind(&simple_wallet::unspent_outputs, this, _1), + tr("unspent_outputs [index=<N1>[,<N2>,...]] [<min_amount> [<max_amount>]]"), + tr("Show the unspent outputs of a specified address within an optional amount range.")); + m_cmd_binder.set_handler("rescan_bc", + boost::bind(&simple_wallet::rescan_blockchain, this, _1), + tr("Rescan the blockchain from scratch.")); + m_cmd_binder.set_handler("set_tx_note", + boost::bind(&simple_wallet::set_tx_note, this, _1), + tr("set_tx_note <txid> [free text note]"), + tr("Set an arbitrary string note for a <txid>.")); + m_cmd_binder.set_handler("get_tx_note", + boost::bind(&simple_wallet::get_tx_note, this, _1), + tr("get_tx_note <txid>"), + tr("Get a string note for a txid.")); + m_cmd_binder.set_handler("set_description", + boost::bind(&simple_wallet::set_description, this, _1), + tr("set_description [free text note]"), + tr("Set an arbitrary description for the wallet.")); + m_cmd_binder.set_handler("get_description", + boost::bind(&simple_wallet::get_description, this, _1), + tr("Get the description of the wallet.")); + m_cmd_binder.set_handler("status", + boost::bind(&simple_wallet::status, this, _1), + tr("Show the wallet's status.")); + m_cmd_binder.set_handler("wallet_info", + boost::bind(&simple_wallet::wallet_info, this, _1), + tr("Show the wallet's information.")); + m_cmd_binder.set_handler("sign", + boost::bind(&simple_wallet::sign, this, _1), + tr("sign <file>"), + tr("Sign the contents of a file.")); + m_cmd_binder.set_handler("verify", + boost::bind(&simple_wallet::verify, this, _1), + tr("verify <filename> <address> <signature>"), + tr("Verify a signature on the contents of a file.")); + m_cmd_binder.set_handler("export_key_images", + boost::bind(&simple_wallet::export_key_images, this, _1), + tr("export_key_images <file>"), + tr("Export a signed set of key images to a <file>.")); + m_cmd_binder.set_handler("import_key_images", + boost::bind(&simple_wallet::import_key_images, this, _1), + tr("import_key_images <file>"), + tr("Import a signed key images list and verify their spent status.")); + m_cmd_binder.set_handler("export_outputs", + boost::bind(&simple_wallet::export_outputs, this, _1), + tr("export_outputs <file>"), + tr("Export a set of outputs owned by this wallet.")); + m_cmd_binder.set_handler("import_outputs", + boost::bind(&simple_wallet::import_outputs, this, _1), + tr("import_outputs <file>"), + tr("Import a set of outputs owned by this wallet.")); + m_cmd_binder.set_handler("show_transfer", + boost::bind(&simple_wallet::show_transfer, this, _1), + tr("show_transfer <txid>"), + tr("Show information about a transfer to/from this address.")); + m_cmd_binder.set_handler("password", + boost::bind(&simple_wallet::change_password, this, _1), + tr("Change the wallet's password.")); + m_cmd_binder.set_handler("payment_id", + boost::bind(&simple_wallet::payment_id, this, _1), + tr("Generate a new random full size payment id. These will be unencrypted on the blockchain, see integrated_address for encrypted short payment ids.")); + 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("help", + boost::bind(&simple_wallet::help, this, _1), + tr("help [<command>]"), + tr("Show the help section or the documentation about a <command>.")); } //---------------------------------------------------------------------------------------------------- bool simple_wallet::set_variable(const std::vector<std::string> &args) @@ -1009,7 +1222,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args) CHECK_SIMPLE_VARIABLE("always-confirm-transfers", set_always_confirm_transfers, tr("0 or 1")); CHECK_SIMPLE_VARIABLE("print-ring-members", set_print_ring_members, tr("0 or 1")); CHECK_SIMPLE_VARIABLE("store-tx-info", set_store_tx_info, tr("0 or 1")); - CHECK_SIMPLE_VARIABLE("default-ring-size", set_default_ring_size, tr("integer >= 3")); + CHECK_SIMPLE_VARIABLE("default-ring-size", set_default_ring_size, tr("integer >= ") << MIN_RING_SIZE); CHECK_SIMPLE_VARIABLE("auto-refresh", set_auto_refresh, tr("0 or 1")); CHECK_SIMPLE_VARIABLE("refresh-type", set_refresh_type, tr("full (slowest, no assumptions); optimize-coinbase (fast, assumes the whole coinbase is paid to a single address); no-coinbase (fastest, assumes we receive no coinbase transaction), default (same as optimize-coinbase)")); CHECK_SIMPLE_VARIABLE("priority", set_default_priority, tr("0, 1, 2, 3, or 4")); @@ -1801,6 +2014,7 @@ bool simple_wallet::new_wallet(const boost::program_options::variables_map& vm, tr("Your wallet has been generated!\n" "To start synchronizing with the daemon, use \"refresh\" command.\n" "Use \"help\" command to see the list of available commands.\n" + "Use \"help <command>\" to see a command's documentation.\n" "Always use \"exit\" command when closing monero-wallet-cli to save your\n" "current session's state. Otherwise, you might need to synchronize \n" "your wallet again (your wallet keys are NOT at risk in any case).\n") @@ -1920,6 +2134,7 @@ bool simple_wallet::open_wallet(const boost::program_options::variables_map& vm) success_msg_writer() << "**********************************************************************\n" << tr("Use \"help\" command to see the list of available commands.\n") << + tr("Use \"help <command>\" to see a command's documentation.\n") << "**********************************************************************"; return true; } @@ -2102,7 +2317,7 @@ void simple_wallet::on_money_received(uint64_t height, const crypto::hash &txid, message_writer(console_color_green, false) << "\r" << tr("Height ") << height << ", " << tr("txid ") << txid << ", " << - print_money(amount) << + print_money(amount) << ", " << tr("idx ") << subaddr_index; if (m_auto_refresh_refreshing) m_cmd_binder.print_prompt(); @@ -3466,7 +3681,7 @@ bool simple_wallet::sweep_single(const std::vector<std::string> &args_) // prompt if there is no payment id and confirmation is required if (!payment_id_seen && m_wallet->confirm_missing_payment_id()) { - std::string accepted = command_line::input_line(tr("No payment id is included with this transaction. Is this okay? (Y/Yes/N/No): ")); + std::string accepted = input_line(tr("No payment id is included with this transaction. Is this okay? (Y/Yes/N/No): ")); if (std::cin.eof()) return true; if (!command_line::is_yes(accepted)) @@ -3509,7 +3724,7 @@ bool simple_wallet::sweep_single(const std::vector<std::string> &args_) prompt << boost::format(tr("Sweeping %s for a total fee of %s. Is this okay? (Y/Yes/N/No)")) % print_money(total_sent) % print_money(total_fee); - std::string accepted = command_line::input_line(prompt.str()); + std::string accepted = input_line(prompt.str()); if (std::cin.eof()) return true; if (!command_line::is_yes(accepted)) @@ -3911,22 +4126,24 @@ bool simple_wallet::get_tx_key(const std::vector<std::string> &args_) } if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } - cryptonote::blobdata txid_data; - if(!epee::string_tools::parse_hexstr_to_binbuff(local_args.front(), txid_data) || txid_data.size() != sizeof(crypto::hash)) + crypto::hash txid; + if (!epee::string_tools::hex_to_pod(local_args[0], txid)) { fail_msg_writer() << tr("failed to parse txid"); return true; } - crypto::hash txid = *reinterpret_cast<const crypto::hash*>(txid_data.data()); LOCK_IDLE_SCOPE(); crypto::secret_key tx_key; std::vector<crypto::secret_key> additional_tx_keys; - std::vector<crypto::secret_key> amount_keys; if (m_wallet->get_tx_key(txid, tx_key, additional_tx_keys)) { - success_msg_writer() << tr("Tx key: ") << epee::string_tools::pod_to_hex(tx_key); + ostringstream oss; + oss << epee::string_tools::pod_to_hex(tx_key); + for (size_t i = 0; i < additional_tx_keys.size(); ++i) + oss << epee::string_tools::pod_to_hex(additional_tx_keys[i]); + success_msg_writer() << tr("Tx key: ") << oss.str(); return true; } else @@ -3938,19 +4155,18 @@ bool simple_wallet::get_tx_key(const std::vector<std::string> &args_) //---------------------------------------------------------------------------------------------------- bool simple_wallet::get_tx_proof(const std::vector<std::string> &args) { - if(args.size() != 2) { - fail_msg_writer() << tr("usage: get_tx_proof <txid> <dest_address>"); + if (args.size() != 2 && args.size() != 3) + { + fail_msg_writer() << tr("usage: get_tx_proof_out <txid> <address> [<message>]"); return true; } - if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } - cryptonote::blobdata txid_data; - if(!epee::string_tools::parse_hexstr_to_binbuff(args[0], txid_data) || txid_data.size() != sizeof(crypto::hash)) + crypto::hash txid; + if(!epee::string_tools::hex_to_pod(args[0], txid)) { fail_msg_writer() << tr("failed to parse txid"); return true; } - crypto::hash txid = *reinterpret_cast<const crypto::hash*>(txid_data.data()); cryptonote::address_parse_info info; if(!cryptonote::get_account_address_from_str_or_url(info, m_wallet->testnet(), args[1], oa_prompter)) @@ -3959,140 +4175,21 @@ bool simple_wallet::get_tx_proof(const std::vector<std::string> &args) return true; } - LOCK_IDLE_SCOPE(); - - crypto::secret_key tx_key; - std::vector<crypto::secret_key> additional_tx_keys; - if (!m_wallet->get_tx_key(txid, tx_key, additional_tx_keys)) - { - fail_msg_writer() << tr("Tx secret key wasn't found in the wallet file."); - return true; - } - - // fetch tx prefix either from the daemon or from the wallet cache if it's still pending - cryptonote::transaction_prefix tx; - // first, look up the wallet cache - std::list<std::pair<crypto::hash, tools::wallet2::unconfirmed_transfer_details>> unconfirmed_payments_out; - m_wallet->get_unconfirmed_payments_out(unconfirmed_payments_out, m_current_subaddress_account); - auto found = std::find_if(unconfirmed_payments_out.begin(), unconfirmed_payments_out.end(), [&](const std::pair<crypto::hash, tools::wallet2::unconfirmed_transfer_details>& p) - { - return p.first == txid; - }); - // if not found, query the daemon - if (found == unconfirmed_payments_out.end()) - { - COMMAND_RPC_GET_TRANSACTIONS::request req; - COMMAND_RPC_GET_TRANSACTIONS::response res; - req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); - if (!net_utils::invoke_http_json("/gettransactions", req, res, m_http_client) || - (res.txs.size() != 1 && res.txs_as_hex.size() != 1)) - { - fail_msg_writer() << tr("failed to get transaction from daemon"); - return true; - } - cryptonote::blobdata tx_data; - bool ok; - if (res.txs.size() == 1) - ok = string_tools::parse_hexstr_to_binbuff(res.txs.front().as_hex, tx_data); - else - ok = string_tools::parse_hexstr_to_binbuff(res.txs_as_hex.front(), tx_data); - if (!ok) - { - fail_msg_writer() << tr("failed to parse transaction from daemon"); - return true; - } - crypto::hash tx_hash, tx_prefix_hash; - cryptonote::transaction tx_full; - if (!cryptonote::parse_and_validate_tx_from_blob(tx_data, tx_full, tx_hash, tx_prefix_hash)) - { - fail_msg_writer() << tr("failed to validate transaction from daemon"); - return true; - } - if (tx_hash != txid) - { - fail_msg_writer() << tr("failed to get the right transaction from daemon"); - return true; - } - tx = tx_full; - } - else - { - tx = found->second.m_tx; - } - - // fetch tx pubkey - crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(tx); - if (tx_pub_key == crypto::null_pkey) - { - fail_msg_writer() << tr("Tx pubkey was not found"); - return true; - } - const std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(tx); - if (additional_tx_keys.size() != additional_tx_pub_keys.size()) - { - fail_msg_writer() << tr("The set of additional tx secret/public keys doesn't match"); - return true; - } - - // find the correct R: - // R = r*G for standard addresses - // R = r*B for subaddresses (where B is the recipient's spend pubkey) - crypto::public_key R; - crypto::secret_key r; - if (info.is_subaddress) - { - auto i = additional_tx_keys.begin(); - auto j = additional_tx_pub_keys.begin(); - for (; ; ++i, ++j) { - if (i == additional_tx_keys.end()) - { - r = tx_key; - R = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(info.address.m_spend_public_key), rct::sk2rct(r))); - if (R == tx_pub_key) - break; - fail_msg_writer() << tr("The matching tx secret/pubkey pair wasn't found for this subaddress"); - return true; - } - else - { - r = *i; - R = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(info.address.m_spend_public_key), rct::sk2rct(r))); - if (R == *j) - break; - } - } - } - else - { - r = tx_key; - crypto::secret_key_to_public_key(r, R); - if (R != tx_pub_key) - { - fail_msg_writer() << tr("The destinations of this tx don't include a standard address"); - return true; - } - } + if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } - crypto::public_key rA = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(info.address.m_view_public_key), rct::sk2rct(r))); - crypto::signature sig; try { - if (info.is_subaddress) - crypto::generate_tx_proof(txid, R, info.address.m_view_public_key, info.address.m_spend_public_key, rA, r, sig); + std::string sig_str = m_wallet->get_tx_proof(txid, info.address, info.is_subaddress, args.size() == 3 ? args[2] : ""); + const std::string filename = "monero_tx_proof"; + if (epee::file_io_utils::save_string_to_file(filename, sig_str)) + success_msg_writer() << tr("signature file saved to: ") << filename; else - crypto::generate_tx_proof(txid, R, info.address.m_view_public_key, boost::none, rA, r, sig); + fail_msg_writer() << tr("failed to save signature file"); } - catch (const std::runtime_error &e) + catch (const std::exception &e) { - fail_msg_writer() << e.what(); - return true; + fail_msg_writer() << tr("error: ") << e.what(); } - - std::string sig_str = std::string("ProofV1") + - tools::base58::encode(std::string((const char *)&rA, sizeof(crypto::public_key))) + - tools::base58::encode(std::string((const char *)&sig, sizeof(crypto::signature))); - - success_msg_writer() << tr("Signature: ") << sig_str; return true; } //---------------------------------------------------------------------------------------------------- @@ -4113,27 +4210,31 @@ bool simple_wallet::check_tx_key(const std::vector<std::string> &args_) fail_msg_writer() << tr("wallet is null"); return true; } - cryptonote::blobdata txid_data; - if(!epee::string_tools::parse_hexstr_to_binbuff(local_args[0], txid_data) || txid_data.size() != sizeof(crypto::hash)) + crypto::hash txid; + if(!epee::string_tools::hex_to_pod(local_args[0], txid)) { fail_msg_writer() << tr("failed to parse txid"); return true; } - crypto::hash txid = *reinterpret_cast<const crypto::hash*>(txid_data.data()); - if (local_args[1].size() < 64 || local_args[1].size() % 64) + crypto::secret_key tx_key; + std::vector<crypto::secret_key> additional_tx_keys; + if(!epee::string_tools::hex_to_pod(local_args[1].substr(0, 64), tx_key)) { fail_msg_writer() << tr("failed to parse tx key"); return true; } - crypto::secret_key tx_key; - cryptonote::blobdata tx_key_data; - if(!epee::string_tools::parse_hexstr_to_binbuff(local_args[1], tx_key_data) || tx_key_data.size() != sizeof(crypto::secret_key)) + local_args[1] = local_args[1].substr(64); + while (!local_args[1].empty()) { - fail_msg_writer() << tr("failed to parse tx key"); - return true; + additional_tx_keys.resize(additional_tx_keys.size() + 1); + if(!epee::string_tools::hex_to_pod(local_args[1].substr(0, 64), additional_tx_keys.back())) + { + fail_msg_writer() << tr("failed to parse tx key"); + return true; + } + local_args[1] = local_args[1].substr(64); } - tx_key = *reinterpret_cast<const crypto::secret_key*>(tx_key_data.data()); cryptonote::address_parse_info info; if(!cryptonote::get_account_address_from_str_or_url(info, m_wallet->testnet(), local_args[2], oa_prompter)) @@ -4142,134 +4243,48 @@ bool simple_wallet::check_tx_key(const std::vector<std::string> &args_) return true; } - crypto::key_derivation derivation; - if (!crypto::generate_key_derivation(info.address.m_view_public_key, tx_key, derivation)) - { - fail_msg_writer() << tr("failed to generate key derivation from supplied parameters"); - return true; - } - - return check_tx_key_helper(txid, info.address, info.is_subaddress, derivation); -} -//---------------------------------------------------------------------------------------------------- -bool simple_wallet::check_tx_key_helper(const crypto::hash &txid, const cryptonote::account_public_address &address, bool is_subaddress, const crypto::key_derivation &derivation) -{ - COMMAND_RPC_GET_TRANSACTIONS::request req; - COMMAND_RPC_GET_TRANSACTIONS::response res; - req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); - if (!net_utils::invoke_http_json("/gettransactions", req, res, m_http_client) || - (res.txs.size() != 1 && res.txs_as_hex.size() != 1)) - { - fail_msg_writer() << tr("failed to get transaction from daemon"); - return true; - } - cryptonote::blobdata tx_data; - bool ok; - if (res.txs.size() == 1) - ok = string_tools::parse_hexstr_to_binbuff(res.txs.front().as_hex, tx_data); - else - ok = string_tools::parse_hexstr_to_binbuff(res.txs_as_hex.front(), tx_data); - if (!ok) - { - fail_msg_writer() << tr("failed to parse transaction from daemon"); - return true; - } - crypto::hash tx_hash, tx_prefix_hash; - cryptonote::transaction tx; - if (!cryptonote::parse_and_validate_tx_from_blob(tx_data, tx, tx_hash, tx_prefix_hash)) - { - fail_msg_writer() << tr("failed to validate transaction from daemon"); - return true; - } - if (tx_hash != txid) + try { - fail_msg_writer() << tr("failed to get the right transaction from daemon"); - return true; - } + uint64_t received; + bool in_pool; + uint64_t confirmations; + m_wallet->check_tx_key(txid, tx_key, additional_tx_keys, info.address, received, in_pool, confirmations); - uint64_t received = 0; - try { - for (size_t n = 0; n < tx.vout.size(); ++n) + if (received > 0) { - if (typeid(txout_to_key) != tx.vout[n].target.type()) - continue; - const txout_to_key tx_out_to_key = boost::get<txout_to_key>(tx.vout[n].target); - crypto::public_key pubkey; - derive_public_key(derivation, n, address.m_spend_public_key, pubkey); - if (pubkey == tx_out_to_key.key) + success_msg_writer() << get_account_address_as_str(m_wallet->testnet(), info.is_subaddress, info.address) << " " << tr("received") << " " << print_money(received) << " " << tr("in txid") << " " << txid; + if (in_pool) + { + success_msg_writer() << tr("WARNING: this transaction is not yet included in the blockchain!"); + } + else { - uint64_t amount; - if (tx.version == 1) + if (confirmations != (uint64_t)-1) { - amount = tx.vout[n].amount; + success_msg_writer() << boost::format(tr("This transaction has %u confirmations")) % confirmations; } else { - try - { - rct::key Ctmp; - //rct::key amount_key = rct::hash_to_scalar(rct::scalarmultKey(rct::pk2rct(address.m_view_public_key), rct::sk2rct(tx_key))); - { - 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); - if (rct::equalKeys(C, Ctmp)) - amount = rct::h2d(ecdh_info.amount); - else - amount = 0; - } - } - catch (...) { amount = 0; } + success_msg_writer() << tr("WARNING: failed to determine number of confirmations!"); } - received += amount; } } - } - catch(const std::exception &e) - { - LOG_ERROR("error: " << e.what()); - fail_msg_writer() << tr("error: ") << e.what(); - return true; - } - - std::string address_str = get_account_address_as_str(m_wallet->testnet(), is_subaddress, address); - if (received > 0) - { - success_msg_writer() << address_str << " " << tr("received") << " " << print_money(received) << " " << tr("in txid") << " " << txid; - } - else - { - fail_msg_writer() << address_str << " " << tr("received nothing in txid") << " " << txid; - } - if (res.txs.front().in_pool) - { - success_msg_writer() << tr("WARNING: this transaction is not yet included in the blockchain!"); - } - else - { - std::string err; - uint64_t bc_height = get_daemon_blockchain_height(err); - if (err.empty()) - { - uint64_t confirmations = bc_height - (res.txs.front().block_height + 1); - success_msg_writer() << boost::format(tr("This transaction has %u confirmations")) % confirmations; - } else { - success_msg_writer() << tr("WARNING: failed to determine number of confirmations!"); + fail_msg_writer() << get_account_address_as_str(m_wallet->testnet(), info.is_subaddress, info.address) << " " << tr("received nothing in txid") << " " << txid; } } - + catch (const std::exception &e) + { + fail_msg_writer() << tr("error: ") << e.what(); + } return true; } //---------------------------------------------------------------------------------------------------- bool simple_wallet::check_tx_proof(const std::vector<std::string> &args) { - if(args.size() != 3) { - fail_msg_writer() << tr("usage: check_tx_proof <txid> <address> <signature>"); + if(args.size() != 3 && args.size() != 4) { + fail_msg_writer() << tr("usage: check_tx_proof <txid> <address> <signature_file> [<message>]"); return true; } @@ -4277,13 +4292,12 @@ bool simple_wallet::check_tx_proof(const std::vector<std::string> &args) return true; // parse txid - cryptonote::blobdata txid_data; - if(!epee::string_tools::parse_hexstr_to_binbuff(args[0], txid_data) || txid_data.size() != sizeof(crypto::hash)) + crypto::hash txid; + if(!epee::string_tools::hex_to_pod(args[0], txid)) { fail_msg_writer() << tr("failed to parse txid"); return true; } - crypto::hash txid = *reinterpret_cast<const crypto::hash*>(txid_data.data()); // parse address cryptonote::address_parse_info info; @@ -4293,117 +4307,141 @@ bool simple_wallet::check_tx_proof(const std::vector<std::string> &args) return true; } - // parse pubkey r*A & signature - std::string sig_str = args[2]; - const size_t header_len = strlen("ProofV1"); - if (sig_str.size() < header_len || sig_str.substr(0, header_len) != "ProofV1") + // read signature file + std::string sig_str; + if (!epee::file_io_utils::load_file_to_string(args[2], sig_str)) { - fail_msg_writer() << tr("Signature header check error"); + fail_msg_writer() << tr("failed to load signature file"); return true; } - crypto::public_key rA; - crypto::signature sig; - const size_t rA_len = tools::base58::encode(std::string((const char *)&rA, sizeof(crypto::public_key))).size(); - const size_t sig_len = tools::base58::encode(std::string((const char *)&sig, sizeof(crypto::signature))).size(); - std::string rA_decoded; - std::string sig_decoded; - if (!tools::base58::decode(sig_str.substr(header_len, rA_len), rA_decoded)) + + try { - fail_msg_writer() << tr("Signature decoding error"); - return true; + uint64_t received; + bool in_pool; + uint64_t confirmations; + if (m_wallet->check_tx_proof(txid, info.address, info.is_subaddress, args.size() == 4 ? args[3] : "", sig_str, received, in_pool, confirmations)) + { + success_msg_writer() << tr("Good signature"); + if (received > 0) + { + success_msg_writer() << get_account_address_as_str(m_wallet->testnet(), info.is_subaddress, info.address) << " " << tr("received") << " " << print_money(received) << " " << tr("in txid") << " " << txid; + if (in_pool) + { + success_msg_writer() << tr("WARNING: this transaction is not yet included in the blockchain!"); + } + else + { + if (confirmations != (uint64_t)-1) + { + success_msg_writer() << boost::format(tr("This transaction has %u confirmations")) % confirmations; + } + else + { + success_msg_writer() << tr("WARNING: failed to determine number of confirmations!"); + } + } + } + else + { + fail_msg_writer() << get_account_address_as_str(m_wallet->testnet(), info.is_subaddress, info.address) << " " << tr("received nothing in txid") << " " << txid; + } + } + else + { + fail_msg_writer() << tr("Bad signature"); + } } - if (!tools::base58::decode(sig_str.substr(header_len + rA_len, sig_len), sig_decoded)) + catch (const std::exception &e) { - fail_msg_writer() << tr("Signature decoding error"); - return true; + fail_msg_writer() << tr("error: ") << e.what(); } - if (sizeof(crypto::public_key) != rA_decoded.size() || sizeof(crypto::signature) != sig_decoded.size()) - { - fail_msg_writer() << tr("Signature decoding error"); + return true; +} +//---------------------------------------------------------------------------------------------------- +bool simple_wallet::get_spend_proof(const std::vector<std::string> &args) +{ + if(args.size() != 1 && args.size() != 2) { + fail_msg_writer() << tr("usage: get_spend_proof <txid> [<message>]"); return true; } - memcpy(&rA, rA_decoded.data(), sizeof(crypto::public_key)); - memcpy(&sig, sig_decoded.data(), sizeof(crypto::signature)); - // fetch tx pubkey from the daemon - COMMAND_RPC_GET_TRANSACTIONS::request req; - COMMAND_RPC_GET_TRANSACTIONS::response res; - req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); - if (!net_utils::invoke_http_json("/gettransactions", req, res, m_http_client) || - (res.txs.size() != 1 && res.txs_as_hex.size() != 1)) + if (m_wallet->watch_only()) { - fail_msg_writer() << tr("failed to get transaction from daemon"); + fail_msg_writer() << tr("wallet is watch-only and cannot generate the proof"); return true; } - cryptonote::blobdata tx_data; - bool ok; - if (res.txs.size() == 1) - ok = string_tools::parse_hexstr_to_binbuff(res.txs.front().as_hex, tx_data); - else - ok = string_tools::parse_hexstr_to_binbuff(res.txs_as_hex.front(), tx_data); - if (!ok) + + crypto::hash txid; + if (!epee::string_tools::hex_to_pod(args[0], txid)) { - fail_msg_writer() << tr("failed to parse transaction from daemon"); + fail_msg_writer() << tr("failed to parse txid"); return true; } - crypto::hash tx_hash, tx_prefix_hash; - cryptonote::transaction tx; - if (!cryptonote::parse_and_validate_tx_from_blob(tx_data, tx, tx_hash, tx_prefix_hash)) + + if (!try_connect_to_daemon()) { - fail_msg_writer() << tr("failed to validate transaction from daemon"); + fail_msg_writer() << tr("failed to connect to the daemon"); return true; } - if (tx_hash != txid) + + if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } + + try { - fail_msg_writer() << tr("failed to get the right transaction from daemon"); - return true; + const std::string sig_str = m_wallet->get_spend_proof(txid, args.size() == 2 ? args[1] : ""); + const std::string filename = "monero_spend_proof"; + if (epee::file_io_utils::save_string_to_file(filename, sig_str)) + success_msg_writer() << tr("signature file saved to: ") << filename; + else + fail_msg_writer() << tr("failed to save signature file"); } - crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(tx); - if (tx_pub_key == crypto::null_pkey) + catch (const std::exception &e) { - fail_msg_writer() << tr("Tx pubkey was not found"); + fail_msg_writer() << e.what(); + } + return true; +} +//---------------------------------------------------------------------------------------------------- +bool simple_wallet::check_spend_proof(const std::vector<std::string> &args) +{ + if(args.size() != 2 && args.size() != 3) { + fail_msg_writer() << tr("usage: check_spend_proof <txid> <signature_file> [<message>]"); return true; } - const std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(tx); - // check signature - bool good_signature = false; - if (info.is_subaddress) - { - good_signature = crypto::check_tx_proof(txid, tx_pub_key, info.address.m_view_public_key, info.address.m_spend_public_key, rA, sig); - if (!good_signature) - { - for (size_t i = 0; i < additional_tx_pub_keys.size(); ++i) - { - good_signature = crypto::check_tx_proof(txid, additional_tx_pub_keys[i], info.address.m_view_public_key, info.address.m_spend_public_key, rA, sig); - if (good_signature) - break; - } - } - } - else + crypto::hash txid; + if (!epee::string_tools::hex_to_pod(args[0], txid)) { - good_signature = crypto::check_tx_proof(txid, tx_pub_key, info.address.m_view_public_key, boost::none, rA, sig); - } - if (good_signature) - { - success_msg_writer() << tr("Good signature"); + fail_msg_writer() << tr("failed to parse txid"); + return true; } - else + + if (!try_connect_to_daemon()) { - fail_msg_writer() << tr("Bad signature"); + fail_msg_writer() << tr("failed to connect to the daemon"); return true; } - // obtain key derivation by multiplying scalar 1 to the pubkey r*A included in the signature - crypto::key_derivation derivation; - if (!crypto::generate_key_derivation(rA, rct::rct2sk(rct::I), derivation)) + std::string sig_str; + if (!epee::file_io_utils::load_file_to_string(args[1], sig_str)) { - fail_msg_writer() << tr("failed to generate key derivation"); + fail_msg_writer() << tr("failed to load signature file"); return true; } - return check_tx_key_helper(txid, info.address, info.is_subaddress, derivation); + try + { + if (m_wallet->check_spend_proof(txid, args.size() == 3 ? args[2] : "", sig_str)) + success_msg_writer() << tr("Good signature"); + else + fail_msg_writer() << tr("Bad signature"); + } + catch (const std::exception& e) + { + fail_msg_writer() << e.what(); + } + return true; } //---------------------------------------------------------------------------------------------------- static std::string get_human_readable_timestamp(uint64_t ts) diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index d65784828..f6405426b 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -77,6 +77,7 @@ namespace cryptonote //wallet *create_wallet(); bool process_command(const std::vector<std::string> &args); std::string get_commands_str(); + std::string get_command_usage(const std::vector<std::string> &args); private: bool handle_command_line(const boost::program_options::variables_map& vm); @@ -161,9 +162,10 @@ namespace cryptonote bool set_log(const std::vector<std::string> &args); bool get_tx_key(const std::vector<std::string> &args); bool check_tx_key(const std::vector<std::string> &args); - bool check_tx_key_helper(const crypto::hash &txid, const cryptonote::account_public_address &address, bool is_subaddress, const crypto::key_derivation &derivation); bool get_tx_proof(const std::vector<std::string> &args); bool check_tx_proof(const std::vector<std::string> &args); + bool get_spend_proof(const std::vector<std::string> &args); + bool check_spend_proof(const std::vector<std::string> &args); bool show_transfers(const std::vector<std::string> &args); bool unspent_outputs(const std::vector<std::string> &args); bool rescan_blockchain(const std::vector<std::string> &args); diff --git a/src/wallet/CMakeLists.txt b/src/wallet/CMakeLists.txt index e5c79a447..74992139d 100644 --- a/src/wallet/CMakeLists.txt +++ b/src/wallet/CMakeLists.txt @@ -33,21 +33,7 @@ set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) set(wallet_sources wallet2.cpp wallet_args.cpp - node_rpc_proxy.cpp - api/wallet.cpp - api/wallet_manager.cpp - api/transaction_info.cpp - api/transaction_history.cpp - api/pending_transaction.cpp - api/utils.cpp - api/address_book.cpp - api/subaddress.cpp - api/subaddress_account.cpp - api/unsigned_transaction.cpp) - -set(wallet_api_headers - wallet2_api.h) - + node_rpc_proxy.cpp) set(wallet_private_headers wallet2.h @@ -56,23 +42,12 @@ set(wallet_private_headers wallet_rpc_server.h wallet_rpc_server_commands_defs.h wallet_rpc_server_error_codes.h - node_rpc_proxy.h - api/wallet.h - api/wallet_manager.h - api/transaction_info.h - api/transaction_history.h - api/pending_transaction.h - api/common_defines.h - api/address_book.h - api/subaddress.h - api/subaddress_account.h - api/unsigned_transaction.h) + node_rpc_proxy.h) monero_private_headers(wallet ${wallet_private_headers}) monero_add_library(wallet ${wallet_sources} - ${wallet_api_headers} ${wallet_private_headers}) target_link_libraries(wallet PUBLIC @@ -127,6 +102,7 @@ install(TARGETS wallet_rpc_server DESTINATION bin) # build and install libwallet_merged only if we building for GUI if (BUILD_GUI_DEPS) set(libs_to_merge + wallet_api wallet cryptonote_core cryptonote_basic @@ -149,6 +125,5 @@ if (BUILD_GUI_DEPS) install(TARGETS wallet_merged ARCHIVE DESTINATION ${lib_folder}) - install(FILES ${wallet_api_headers} - DESTINATION include/wallet) + add_subdirectory(api) endif() diff --git a/src/wallet/api/CMakeLists.txt b/src/wallet/api/CMakeLists.txt new file mode 100644 index 000000000..26127b75c --- /dev/null +++ b/src/wallet/api/CMakeLists.txt @@ -0,0 +1,89 @@ +# Copyright (c) 2014-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 (${PROJECT_SOURCE_DIR}/cmake/libutils.cmake) + +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + +set(wallet_api_sources + wallet.cpp + wallet_manager.cpp + transaction_info.cpp + transaction_history.cpp + pending_transaction.cpp + utils.cpp + address_book.cpp + subaddress.cpp + subaddress_account.cpp + unsigned_transaction.cpp) + +set(wallet_api_headers + wallet2_api.h) + +set(wallet_api_private_headers + wallet.h + wallet_manager.h + transaction_info.h + transaction_history.h + pending_transaction.h + common_defines.h + address_book.h + subaddress.h + subaddress_account.h + unsigned_transaction.h) + +monero_private_headers(wallet_api + ${wallet_api_private_headers}) +monero_add_library(wallet_api + ${wallet_api_sources} + ${wallet_api_headers} + ${wallet_api_private_headers}) +target_link_libraries(wallet_api + PUBLIC + wallet + common + cryptonote_core + mnemonics + ${Boost_CHRONO_LIBRARY} + ${Boost_SERIALIZATION_LIBRARY} + ${Boost_FILESYSTEM_LIBRARY} + ${Boost_SYSTEM_LIBRARY} + ${Boost_THREAD_LIBRARY} + ${Boost_REGEX_LIBRARY} + PRIVATE + ${EXTRA_LIBRARIES}) + + +if(IOS) + set(lib_folder lib-${ARCH}) +else() + set(lib_folder lib) +endif() + +install(FILES ${wallet_api_headers} + DESTINATION include/wallet) diff --git a/src/wallet/api/address_book.h b/src/wallet/api/address_book.h index 25f59128b..b92743fe6 100644 --- a/src/wallet/api/address_book.h +++ b/src/wallet/api/address_book.h @@ -28,7 +28,7 @@ // // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers -#include "wallet/wallet2_api.h" +#include "wallet/api/wallet2_api.h" #include "wallet/wallet2.h" namespace Monero { diff --git a/src/wallet/api/pending_transaction.h b/src/wallet/api/pending_transaction.h index e5b33ac01..c98da59da 100644 --- a/src/wallet/api/pending_transaction.h +++ b/src/wallet/api/pending_transaction.h @@ -28,7 +28,7 @@ // // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers -#include "wallet/wallet2_api.h" +#include "wallet/api/wallet2_api.h" #include "wallet/wallet2.h" #include <string> diff --git a/src/wallet/api/subaddress.h b/src/wallet/api/subaddress.h index e3e28eba1..45fef6e67 100644 --- a/src/wallet/api/subaddress.h +++ b/src/wallet/api/subaddress.h @@ -26,7 +26,7 @@ // 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 "wallet/wallet2_api.h" +#include "wallet/api/wallet2_api.h" #include "wallet/wallet2.h" namespace Monero { diff --git a/src/wallet/api/subaddress_account.h b/src/wallet/api/subaddress_account.h index 107d7f87f..0a4db9671 100644 --- a/src/wallet/api/subaddress_account.h +++ b/src/wallet/api/subaddress_account.h @@ -26,7 +26,7 @@ // 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 "wallet/wallet2_api.h" +#include "wallet/api/wallet2_api.h" #include "wallet/wallet2.h" namespace Monero { diff --git a/src/wallet/api/transaction_history.h b/src/wallet/api/transaction_history.h index 4987bdab2..e5207e53a 100644 --- a/src/wallet/api/transaction_history.h +++ b/src/wallet/api/transaction_history.h @@ -28,7 +28,7 @@ // // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers -#include "wallet/wallet2_api.h" +#include "wallet/api/wallet2_api.h" #include <boost/thread/shared_mutex.hpp> namespace Monero { diff --git a/src/wallet/api/transaction_info.h b/src/wallet/api/transaction_info.h index d19ef8899..c961c0a9e 100644 --- a/src/wallet/api/transaction_info.h +++ b/src/wallet/api/transaction_info.h @@ -28,7 +28,7 @@ // // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers -#include "wallet/wallet2_api.h" +#include "wallet/api/wallet2_api.h" #include <string> #include <ctime> diff --git a/src/wallet/api/unsigned_transaction.h b/src/wallet/api/unsigned_transaction.h index 9c442f503..33c994d5f 100644 --- a/src/wallet/api/unsigned_transaction.h +++ b/src/wallet/api/unsigned_transaction.h @@ -28,7 +28,7 @@ // // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers -#include "wallet/wallet2_api.h" +#include "wallet/api/wallet2_api.h" #include "wallet/wallet2.h" #include <string> diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index 8e747d16b..fd0b65866 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -1385,24 +1385,194 @@ std::string WalletImpl::getUserNote(const std::string &txid) const return m_wallet->get_tx_note(htxid); } -std::string WalletImpl::getTxKey(const std::string &txid) const +std::string WalletImpl::getTxKey(const std::string &txid_str) const { - cryptonote::blobdata txid_data; - if(!epee::string_tools::parse_hexstr_to_binbuff(txid, txid_data) || txid_data.size() != sizeof(crypto::hash)) + crypto::hash txid; + if(!epee::string_tools::hex_to_pod(txid_str, txid)) { - return ""; + m_status = Status_Error; + m_errorString = tr("Failed to parse txid"); + return ""; } - const crypto::hash htxid = *reinterpret_cast<const crypto::hash*>(txid_data.data()); crypto::secret_key tx_key; std::vector<crypto::secret_key> additional_tx_keys; - if (m_wallet->get_tx_key(htxid, tx_key, additional_tx_keys)) + if (m_wallet->get_tx_key(txid, tx_key, additional_tx_keys)) { - return epee::string_tools::pod_to_hex(tx_key); + m_status = Status_Ok; + std::ostringstream oss; + oss << epee::string_tools::pod_to_hex(tx_key); + for (size_t i = 0; i < additional_tx_keys.size(); ++i) + oss << epee::string_tools::pod_to_hex(additional_tx_keys[i]); + return oss.str(); } else { - return ""; + m_status = Status_Error; + m_errorString = tr("no tx keys found for this txid"); + return ""; + } +} + +bool WalletImpl::checkTxKey(const std::string &txid_str, std::string tx_key_str, const std::string &address_str, uint64_t &received, bool &in_pool, uint64_t &confirmations) +{ + crypto::hash txid; + if (!epee::string_tools::hex_to_pod(txid_str, txid)) + { + m_status = Status_Error; + m_errorString = tr("Failed to parse txid"); + return false; + } + + crypto::secret_key tx_key; + std::vector<crypto::secret_key> additional_tx_keys; + if (!epee::string_tools::hex_to_pod(tx_key_str.substr(0, 64), tx_key)) + { + m_status = Status_Error; + m_errorString = tr("Failed to parse tx key"); + return false; + } + tx_key_str = tx_key_str.substr(64); + while (!tx_key_str.empty()) + { + additional_tx_keys.resize(additional_tx_keys.size() + 1); + if (!epee::string_tools::hex_to_pod(tx_key_str.substr(0, 64), additional_tx_keys.back())) + { + m_status = Status_Error; + m_errorString = tr("Failed to parse tx key"); + return false; + } + tx_key_str = tx_key_str.substr(64); + } + + cryptonote::address_parse_info info; + if (!cryptonote::get_account_address_from_str(info, m_wallet->testnet(), address_str)) + { + m_status = Status_Error; + m_errorString = tr("Failed to parse address"); + return false; + } + + try + { + m_wallet->check_tx_key(txid, tx_key, additional_tx_keys, info.address, received, in_pool, confirmations); + m_status = Status_Ok; + return true; + } + catch (const std::exception &e) + { + m_status = Status_Error; + m_errorString = e.what(); + return false; + } +} + +std::string WalletImpl::getTxProof(const std::string &txid_str, const std::string &address_str, const std::string &message) const +{ + crypto::hash txid; + if (!epee::string_tools::hex_to_pod(txid_str, txid)) + { + m_status = Status_Error; + m_errorString = tr("Failed to parse txid"); + return ""; + } + + cryptonote::address_parse_info info; + if (!cryptonote::get_account_address_from_str(info, m_wallet->testnet(), address_str)) + { + m_status = Status_Error; + m_errorString = tr("Failed to parse address"); + return ""; + } + + try + { + m_status = Status_Ok; + return m_wallet->get_tx_proof(txid, info.address, info.is_subaddress, message); + } + catch (const std::exception &e) + { + m_status = Status_Error; + m_errorString = e.what(); + return ""; + } +} + +bool WalletImpl::checkTxProof(const std::string &txid_str, const std::string &address_str, const std::string &message, const std::string &signature, bool &good, uint64_t &received, bool &in_pool, uint64_t &confirmations) +{ + crypto::hash txid; + if (!epee::string_tools::hex_to_pod(txid_str, txid)) + { + m_status = Status_Error; + m_errorString = tr("Failed to parse txid"); + return false; + } + + cryptonote::address_parse_info info; + if (!cryptonote::get_account_address_from_str(info, m_wallet->testnet(), address_str)) + { + m_status = Status_Error; + m_errorString = tr("Failed to parse address"); + return false; + } + + try + { + good = m_wallet->check_tx_proof(txid, info.address, info.is_subaddress, message, signature, received, in_pool, confirmations); + m_status = Status_Ok; + return true; + } + catch (const std::exception &e) + { + m_status = Status_Error; + m_errorString = e.what(); + return false; + } +} + +std::string WalletImpl::getSpendProof(const std::string &txid_str, const std::string &message) const { + crypto::hash txid; + if(!epee::string_tools::hex_to_pod(txid_str, txid)) + { + m_status = Status_Error; + m_errorString = tr("Failed to parse txid"); + return ""; + } + + try + { + m_status = Status_Ok; + return m_wallet->get_spend_proof(txid, message); + } + catch (const std::exception &e) + { + m_status = Status_Error; + m_errorString = e.what(); + return ""; + } +} + +bool WalletImpl::checkSpendProof(const std::string &txid_str, const std::string &message, const std::string &signature, bool &good) const { + good = false; + crypto::hash txid; + if(!epee::string_tools::hex_to_pod(txid_str, txid)) + { + m_status = Status_Error; + m_errorString = tr("Failed to parse txid"); + return false; + } + + try + { + m_status = Status_Ok; + good = m_wallet->check_spend_proof(txid, message, signature); + return true; + } + catch (const std::exception &e) + { + m_status = Status_Error; + m_errorString = e.what(); + return false; } } diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h index 051eda3ba..01359ffc6 100644 --- a/src/wallet/api/wallet.h +++ b/src/wallet/api/wallet.h @@ -31,7 +31,7 @@ #ifndef WALLET_IMPL_H #define WALLET_IMPL_H -#include "wallet/wallet2_api.h" +#include "wallet/api/wallet2_api.h" #include "wallet/wallet2.h" #include <string> @@ -137,6 +137,11 @@ public: virtual bool setUserNote(const std::string &txid, const std::string ¬e); virtual std::string getUserNote(const std::string &txid) const; virtual std::string getTxKey(const std::string &txid) const; + virtual bool checkTxKey(const std::string &txid, std::string tx_key, const std::string &address, uint64_t &received, bool &in_pool, uint64_t &confirmations); + virtual std::string getTxProof(const std::string &txid, const std::string &address, const std::string &message) const; + virtual bool checkTxProof(const std::string &txid, const std::string &address, const std::string &message, const std::string &signature, bool &good, uint64_t &received, bool &in_pool, uint64_t &confirmations); + virtual std::string getSpendProof(const std::string &txid, const std::string &message) const; + virtual bool checkSpendProof(const std::string &txid, const std::string &message, const std::string &signature, bool &good) const; virtual std::string signMessage(const std::string &message); virtual bool verifySignedMessage(const std::string &message, const std::string &address, const std::string &signature) const; virtual void startRefresh(); diff --git a/src/wallet/wallet2_api.h b/src/wallet/api/wallet2_api.h index 432c820cb..b1f8369a3 100644 --- a/src/wallet/wallet2_api.h +++ b/src/wallet/api/wallet2_api.h @@ -700,6 +700,11 @@ struct Wallet */ virtual std::string getUserNote(const std::string &txid) const = 0; virtual std::string getTxKey(const std::string &txid) const = 0; + virtual bool checkTxKey(const std::string &txid, std::string tx_key, const std::string &address, uint64_t &received, bool &in_pool, uint64_t &confirmations) = 0; + virtual std::string getTxProof(const std::string &txid, const std::string &address, const std::string &message) const = 0; + virtual bool checkTxProof(const std::string &txid, const std::string &address, const std::string &message, const std::string &signature, bool &good, uint64_t &received, bool &in_pool, uint64_t &confirmations) = 0; + virtual std::string getSpendProof(const std::string &txid, const std::string &message) const = 0; + virtual bool checkSpendProof(const std::string &txid, const std::string &message, const std::string &signature, bool &good) const = 0; /* * \brief signMessage - sign a message with the spend private key @@ -819,19 +824,6 @@ struct WalletManager */ virtual std::vector<std::string> findWallets(const std::string &path) = 0; - /*! - * \brief checkPayment - checks a payment was made using a txkey - * \param address - the address the payment was sent to - * \param txid - the transaction id for that payment - * \param txkey - the transaction's secret key - * \param daemon_address - the address (host and port) to the daemon to request transaction data - * \param received - if succesful, will hold the amount of monero received - * \param height - if succesful, will hold the height of the transaction (0 if only in the pool) - * \param error - if unsuccesful, will hold an error string with more information about the error - * \return - true is succesful, false otherwise - */ - virtual bool checkPayment(const std::string &address, const std::string &txid, const std::string &txkey, const std::string &daemon_address, uint64_t &received, uint64_t &height, std::string &error) const = 0; - //! returns verbose error string regarding last error; virtual std::string errorString() const = 0; diff --git a/src/wallet/api/wallet_manager.cpp b/src/wallet/api/wallet_manager.cpp index 2326f54d3..ee69ec028 100644 --- a/src/wallet/api/wallet_manager.cpp +++ b/src/wallet/api/wallet_manager.cpp @@ -189,155 +189,6 @@ bool WalletManagerImpl::connected(uint32_t *version) const return true; } -bool WalletManagerImpl::checkPayment(const std::string &address_text, const std::string &txid_text, const std::string &txkey_text, const std::string &daemon_address, uint64_t &received, uint64_t &height, std::string &error) const -{ - error = ""; - cryptonote::blobdata txid_data; - if(!epee::string_tools::parse_hexstr_to_binbuff(txid_text, txid_data) || txid_data.size() != sizeof(crypto::hash)) - { - error = tr("failed to parse txid"); - return false; - } - crypto::hash txid = *reinterpret_cast<const crypto::hash*>(txid_data.data()); - - if (txkey_text.size() < 64 || txkey_text.size() % 64) - { - error = tr("failed to parse tx key"); - return false; - } - crypto::secret_key tx_key; - cryptonote::blobdata tx_key_data; - if(!epee::string_tools::parse_hexstr_to_binbuff(txkey_text, tx_key_data) || tx_key_data.size() != sizeof(crypto::hash)) - { - error = tr("failed to parse tx key"); - return false; - } - tx_key = *reinterpret_cast<const crypto::secret_key*>(tx_key_data.data()); - - bool testnet = address_text[0] != '4'; - cryptonote::address_parse_info info; - if(!cryptonote::get_account_address_from_str(info, testnet, address_text)) - { - error = tr("failed to parse address"); - return false; - } - - cryptonote::COMMAND_RPC_GET_TRANSACTIONS::request req; - cryptonote::COMMAND_RPC_GET_TRANSACTIONS::response res; - req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); - if (!connect_and_invoke(m_daemonAddress, "/gettransactions", req, res) || - (res.txs.size() != 1 && res.txs_as_hex.size() != 1)) - { - error = tr("failed to get transaction from daemon"); - return false; - } - cryptonote::blobdata tx_data; - bool ok; - if (res.txs.size() == 1) - ok = epee::string_tools::parse_hexstr_to_binbuff(res.txs.front().as_hex, tx_data); - else - ok = epee::string_tools::parse_hexstr_to_binbuff(res.txs_as_hex.front(), tx_data); - if (!ok) - { - error = tr("failed to parse transaction from daemon"); - return false; - } - crypto::hash tx_hash, tx_prefix_hash; - cryptonote::transaction tx; - if (!cryptonote::parse_and_validate_tx_from_blob(tx_data, tx, tx_hash, tx_prefix_hash)) - { - error = tr("failed to validate transaction from daemon"); - return false; - } - if (tx_hash != txid) - { - error = tr("failed to get the right transaction from daemon"); - return false; - } - - crypto::key_derivation derivation; - if (!crypto::generate_key_derivation(info.address.m_view_public_key, tx_key, derivation)) - { - error = tr("failed to generate key derivation from supplied parameters"); - return false; - } - - received = 0; - try { - for (size_t n = 0; n < tx.vout.size(); ++n) - { - if (typeid(cryptonote::txout_to_key) != tx.vout[n].target.type()) - continue; - const cryptonote::txout_to_key tx_out_to_key = boost::get<cryptonote::txout_to_key>(tx.vout[n].target); - crypto::public_key pubkey; - derive_public_key(derivation, n, info.address.m_spend_public_key, pubkey); - if (pubkey == tx_out_to_key.key) - { - uint64_t amount; - if (tx.version == 1) - { - amount = tx.vout[n].amount; - } - else - { - try - { - rct::key Ctmp; - //rct::key amount_key = rct::hash_to_scalar(rct::scalarmultKey(rct::pk2rct(address.m_view_public_key), rct::sk2rct(tx_key))); - crypto::key_derivation derivation; - bool r = crypto::generate_key_derivation(info.address.m_view_public_key, tx_key, derivation); - if (!r) - { - LOG_ERROR("Failed to generate key derivation to decode rct output " << n); - amount = 0; - } - else - { - 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); - if (rct::equalKeys(C, Ctmp)) - amount = rct::h2d(ecdh_info.amount); - else - amount = 0; - } - } - catch (...) { amount = 0; } - } - received += amount; - } - } - } - catch(const std::exception &e) - { - LOG_ERROR("error: " << e.what()); - error = std::string(tr("error: ")) + e.what(); - return false; - } - - if (received > 0) - { - LOG_PRINT_L1(get_account_address_as_str(testnet, info.is_subaddress, info.address) << " " << tr("received") << " " << cryptonote::print_money(received) << " " << tr("in txid") << " " << txid); - } - else - { - LOG_PRINT_L1(get_account_address_as_str(testnet, info.is_subaddress, info.address) << " " << tr("received nothing in txid") << " " << txid); - } - if (res.txs.front().in_pool) - { - height = 0; - } - else - { - height = res.txs.front().block_height; - } - - return true; -} - uint64_t WalletManagerImpl::blockchainHeight() const { cryptonote::COMMAND_RPC_GET_INFO::request ireq; diff --git a/src/wallet/api/wallet_manager.h b/src/wallet/api/wallet_manager.h index 8455f0f16..978a2d411 100644 --- a/src/wallet/api/wallet_manager.h +++ b/src/wallet/api/wallet_manager.h @@ -29,7 +29,7 @@ // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers -#include "wallet/wallet2_api.h" +#include "wallet/api/wallet2_api.h" #include <string> namespace Monero { @@ -55,7 +55,6 @@ public: std::string errorString() const; void setDaemonAddress(const std::string &address); bool connected(uint32_t *version = NULL) const; - bool checkPayment(const std::string &address, const std::string &txid, const std::string &txkey, const std::string &daemon_address, uint64_t &received, uint64_t &height, std::string &error) const; uint64_t blockchainHeight() const; uint64_t blockchainTargetHeight() const; uint64_t networkDifficulty() const; diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 00b096b88..4ad7ede9c 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -39,7 +39,6 @@ using namespace epee; #include "cryptonote_config.h" #include "wallet2.h" -#include "wallet2_api.h" #include "cryptonote_basic/cryptonote_format_utils.h" #include "rpc/core_rpc_server_commands_defs.h" #include "misc_language.h" @@ -202,6 +201,8 @@ boost::optional<tools::password_container> get_password(const boost::program_opt return {tools::password_container{std::move(password)}}; } + THROW_WALLET_EXCEPTION_IF(!password_prompter, tools::error::wallet_internal_error, tools::wallet2::tr("no password specified; use --prompt-for-password to prompt for a password")); + return password_prompter(verify ? tr("Enter new wallet password") : tr("Wallet password"), verify); } @@ -455,6 +456,59 @@ void drop_from_short_history(std::list<crypto::hash> &short_chain_history, size_ } } +size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs, size_t extra_size) +{ + size_t size = 0; + + // tx prefix + + // first few bytes + size += 1 + 6; + + // vin + size += n_inputs * (1+6+(mixin+1)*2+32); + + // vout + size += n_outputs * (6+32); + + // extra + size += extra_size; + + // rct signatures + + // type + size += 1; + + // rangeSigs + size += (2*64*32+32+64*32) * n_outputs; + + // MGs + size += n_inputs * (64 * (mixin+1) + 32); + + // mixRing - not serialized, can be reconstructed + /* size += 2 * 32 * (mixin+1) * n_inputs; */ + + // pseudoOuts + size += 32 * n_inputs; + // ecdhInfo + size += 2 * 32 * n_outputs; + // outPk - only commitment is saved + size += 32 * n_outputs; + // txnFee + size += 4; + + LOG_PRINT_L2("estimated rct tx size for " << n_inputs << " with ring size " << (mixin+1) << " and " << n_outputs << ": " << size << " (" << ((32 * n_inputs/*+1*/) + 2 * 32 * (mixin+1) * n_inputs + 32 * n_outputs) << " saved)"); + return size; +} + +size_t estimate_tx_size(bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size) +{ + if (use_rct) + return estimate_rct_tx_size(n_inputs, mixin, n_outputs + 1, extra_size); + else + return n_inputs * (mixin+1) * APPROXIMATE_INPUT_BYTES + extra_size; +} + } //namespace namespace tools @@ -644,8 +698,7 @@ void wallet2::add_subaddress_account(const std::string& label) //---------------------------------------------------------------------------------------------------- void wallet2::add_subaddress(uint32_t index_major, const std::string& label) { - if (index_major >= m_subaddress_labels.size()) - throw std::runtime_error("index_major is out of bound"); + THROW_WALLET_EXCEPTION_IF(index_major >= m_subaddress_labels.size(), error::account_index_outofbound); uint32_t index_minor = (uint32_t)get_num_subaddresses(index_major); expand_subaddresses({index_major, index_minor}); m_subaddress_labels[index_major][index_minor] = label; @@ -701,10 +754,9 @@ std::string wallet2::get_subaddress_label(const cryptonote::subaddress_index& in //---------------------------------------------------------------------------------------------------- void wallet2::set_subaddress_label(const cryptonote::subaddress_index& index, const std::string &label) { - if (index.major >= m_subaddress_labels.size() || index.minor >= m_subaddress_labels[index.major].size()) - MERROR("Subaddress index is out of bounds. Failed to set subaddress label."); - else - m_subaddress_labels[index.major][index.minor] = label; + THROW_WALLET_EXCEPTION_IF(index.major >= m_subaddress_labels.size(), error::account_index_outofbound); + THROW_WALLET_EXCEPTION_IF(index.minor >= m_subaddress_labels[index.major].size(), error::address_index_outofbound); + m_subaddress_labels[index.major][index.minor] = label; } //---------------------------------------------------------------------------------------------------- /*! @@ -834,7 +886,12 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote tools::threadpool::waiter waiter; const cryptonote::account_keys& keys = m_account.get_keys(); crypto::key_derivation derivation; - generate_key_derivation(tx_pub_key, keys.m_view_secret_key, derivation); + if (!generate_key_derivation(tx_pub_key, keys.m_view_secret_key, derivation)) + { + MWARNING("Failed to generate key derivation from tx pubkey, skipping"); + static_assert(sizeof(derivation) == sizeof(rct::key), "Mismatched sizes of key_derivation and rct::key"); + memcpy(&derivation, rct::identity().bytes, sizeof(derivation)); + } // additional tx pubkeys and derivations for multi-destination transfers involving one or more subaddresses std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(tx); @@ -842,7 +899,11 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote 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()); + if (!generate_key_derivation(additional_tx_pub_keys[i], keys.m_view_secret_key, additional_derivations.back())) + { + MWARNING("Failed to generate key derivation from tx pubkey, skipping"); + additional_derivations.pop_back(); + } } if (miner_tx && m_refresh_type == RefreshNoCoinbase) @@ -1025,7 +1086,9 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote } uint64_t tx_money_spent_in_ins = 0; - boost::optional<uint32_t> subaddr_account; + // The line below is equivalent to "boost::optional<uint32_t> subaddr_account;", but avoids the GCC warning: ‘*((void*)& subaddr_account +4)’ may be used uninitialized in this function + // It's a GCC bug with boost::optional, see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47679 + auto subaddr_account ([]()->boost::optional<uint32_t> {return boost::none;}()); std::set<uint32_t> subaddr_indices; // check all outputs for spending (compare key images) for(auto& in: tx.vin) @@ -3990,7 +4053,8 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions(std::vector<crypto pending_tx ptx; // loop until fee is met without increasing tx size to next KB boundary. - uint64_t needed_fee = 0; + const size_t estimated_tx_size = estimate_tx_size(false, unused_transfers_indices.size(), fake_outs_count, dst_vector.size(), extra.size()); + uint64_t needed_fee = calculate_fee(fee_per_kb, estimated_tx_size, fee_multiplier); do { transfer(dst_vector, fake_outs_count, unused_transfers_indices, unlock_time, needed_fee, extra, tx, ptx, trusted_daemon); @@ -4580,9 +4644,7 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry uint64_t upper_transaction_size_limit = get_upper_transaction_size_limit(); uint64_t needed_money = fee; LOG_PRINT_L2("transfer_selected_rct: starting with fee " << print_money (needed_money)); - LOG_PRINT_L0("selected transfers: "); - for (auto t: selected_transfers) - LOG_PRINT_L2(" " << t); + LOG_PRINT_L2("selected transfers: " << strjoin(selected_transfers, " ")); // calculate total amount being sent to all destinations // throw if total amount overflows uint64_t @@ -4666,21 +4728,25 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry change_dts.amount = found_money - needed_money; if (change_dts.amount == 0) { - // If the change is 0, send it to a random address, to avoid confusing - // the sender with a 0 amount output. We send a 0 amount in order to avoid - // letting the destination be able to work out which of the inputs is the - // real one in our rings - LOG_PRINT_L2("generating dummy address for 0 change"); - cryptonote::account_base dummy; - dummy.generate(); - change_dts.addr = dummy.get_keys().m_account_address; - LOG_PRINT_L2("generated dummy address for 0 change"); + if (splitted_dsts.size() == 1) + { + // If the change is 0, send it to a random address, to avoid confusing + // the sender with a 0 amount output. We send a 0 amount in order to avoid + // letting the destination be able to work out which of the inputs is the + // real one in our rings + LOG_PRINT_L2("generating dummy address for 0 change"); + cryptonote::account_base dummy; + dummy.generate(); + change_dts.addr = dummy.get_keys().m_account_address; + LOG_PRINT_L2("generated dummy address for 0 change"); + splitted_dsts.push_back(change_dts); + } } else { change_dts.addr = get_subaddress({subaddr_account, 0}); + splitted_dsts.push_back(change_dts); } - splitted_dsts.push_back(change_dts); crypto::secret_key tx_key; std::vector<crypto::secret_key> additional_tx_keys; @@ -4727,59 +4793,6 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry LOG_PRINT_L2("transfer_selected_rct done"); } -static size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs) -{ - size_t size = 0; - - // tx prefix - - // first few bytes - size += 1 + 6; - - // vin - size += n_inputs * (1+6+(mixin+1)*2+32); - - // vout - size += n_outputs * (6+32); - - // extra - size += 40; - - // rct signatures - - // type - size += 1; - - // rangeSigs - size += (2*64*32+32+64*32) * n_outputs; - - // MGs - size += n_inputs * (64 * (mixin+1) + 32); - - // mixRing - not serialized, can be reconstructed - /* size += 2 * 32 * (mixin+1) * n_inputs; */ - - // pseudoOuts - size += 32 * n_inputs; - // ecdhInfo - size += 2 * 32 * n_outputs; - // outPk - only commitment is saved - size += 32 * n_outputs; - // txnFee - size += 4; - - LOG_PRINT_L2("estimated rct tx size for " << n_inputs << " with ring size " << (mixin+1) << " and " << n_outputs << ": " << size << " (" << ((32 * n_inputs/*+1*/) + 2 * 32 * (mixin+1) * n_inputs + 32 * n_outputs) << " saved)"); - return size; -} - -static size_t estimate_tx_size(bool use_rct, int n_inputs, int mixin, int n_outputs) -{ - if (use_rct) - return estimate_rct_tx_size(n_inputs, mixin, n_outputs + 1); - else - return n_inputs * (mixin+1) * APPROXIMATE_INPUT_BYTES; -} - std::vector<size_t> wallet2::pick_preferred_rct_inputs(uint64_t needed_money, uint32_t subaddr_account, const std::set<uint32_t> &subaddr_indices) const { std::vector<size_t> picks; @@ -4787,19 +4800,7 @@ 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 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 + // 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 @@ -5553,12 +5554,12 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp { // this is used to build a tx that's 1 or 2 inputs, and 2 outputs, which // will get us a known fee. - uint64_t estimated_fee = calculate_fee(fee_per_kb, estimate_rct_tx_size(2, fake_outs_count + 1, 2), fee_multiplier); + uint64_t estimated_fee = calculate_fee(fee_per_kb, estimate_rct_tx_size(2, fake_outs_count, 2, extra.size()), fee_multiplier); preferred_inputs = pick_preferred_rct_inputs(needed_money + estimated_fee, subaddr_account, subaddr_indices); if (!preferred_inputs.empty()) { string s; - for (auto i: preferred_inputs) s += boost::lexical_cast<std::string>(i) + "(" + print_money(m_transfers[i].amount()) + ") "; + for (auto i: preferred_inputs) s += boost::lexical_cast<std::string>(i) + " (" + print_money(m_transfers[i].amount()) + ") "; LOG_PRINT_L1("Found prefered rct inputs for rct tx: " << s); // bring the list of available outputs stored by the same subaddress index to the front of the list @@ -5590,14 +5591,13 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp unsigned int original_output_index = 0; std::vector<size_t>* unused_transfers_indices = &unused_transfers_indices_per_subaddr[0].second; std::vector<size_t>* unused_dust_indices = &unused_dust_indices_per_subaddr[0].second; - while ((!dsts.empty() && dsts[0].amount > 0) || adding_fee || should_pick_a_second_output(use_rct, txes.back().selected_transfers.size(), *unused_transfers_indices, *unused_dust_indices)) { + while ((!dsts.empty() && dsts[0].amount > 0) || adding_fee || !preferred_inputs.empty() || should_pick_a_second_output(use_rct, txes.back().selected_transfers.size(), *unused_transfers_indices, *unused_dust_indices)) { TX &tx = txes.back(); LOG_PRINT_L2("Start of loop with " << unused_transfers_indices->size() << " " << unused_dust_indices->size()); - LOG_PRINT_L2("Start of loop with " << unused_transfers_indices->size() << " " << unused_dust_indices->size()); LOG_PRINT_L2("unused_transfers_indices: " << strjoin(*unused_transfers_indices, " ")); - LOG_PRINT_L2("unused_dust_indices:" << strjoin(*unused_dust_indices, " ")); - LOG_PRINT_L2("dsts size " << dsts.size() << ", first " << (dsts.empty() ? -1 : dsts[0].amount)); + LOG_PRINT_L2("unused_dust_indices: " << strjoin(*unused_dust_indices, " ")); + LOG_PRINT_L2("dsts size " << dsts.size() << ", first " << (dsts.empty() ? "-" : cryptonote::print_money(dsts[0].amount))); LOG_PRINT_L2("adding_fee " << adding_fee << ", use_rct " << use_rct); // if we need to spend money and don't have any left, we fail @@ -5609,7 +5609,11 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp // get a random unspent output and use it to pay part (or all) of the current destination (and maybe next one, etc) // This could be more clever, but maybe at the cost of making probabilistic inferences easier size_t idx; - if ((dsts.empty() || dsts[0].amount == 0) && !adding_fee) { + if (!preferred_inputs.empty()) { + idx = pop_back(preferred_inputs); + pop_if_present(*unused_transfers_indices, idx); + pop_if_present(*unused_dust_indices, idx); + } else if ((dsts.empty() || dsts[0].amount == 0) && !adding_fee) { // the "make rct txes 2/2" case - we pick a small value output to "clean up" the wallet too std::vector<size_t> indices = get_only_rct(*unused_dust_indices, *unused_transfers_indices); idx = pop_best_value(indices, tx.selected_transfers, true); @@ -5632,10 +5636,6 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp } pop_if_present(*unused_transfers_indices, idx); pop_if_present(*unused_dust_indices, idx); - } else if (!preferred_inputs.empty()) { - idx = pop_back(preferred_inputs); - pop_if_present(*unused_transfers_indices, idx); - pop_if_present(*unused_dust_indices, idx); } else idx = pop_best_value(unused_transfers_indices->empty() ? *unused_dust_indices : *unused_transfers_indices, tx.selected_transfers); @@ -5657,7 +5657,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp } else { - while (!dsts.empty() && dsts[0].amount <= available_amount && estimate_tx_size(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()) < TX_SIZE_TARGET(upper_transaction_size_limit)) + while (!dsts.empty() && dsts[0].amount <= available_amount && estimate_tx_size(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size(), extra.size()) < TX_SIZE_TARGET(upper_transaction_size_limit)) { // we can fully pay that destination LOG_PRINT_L2("We can fully pay " << get_account_address_as_str(m_testnet, dsts[0].is_subaddress, dsts[0].addr) << @@ -5669,7 +5669,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp ++original_output_index; } - if (available_amount > 0 && !dsts.empty() && estimate_tx_size(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()) < TX_SIZE_TARGET(upper_transaction_size_limit)) { + if (available_amount > 0 && !dsts.empty() && estimate_tx_size(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size(), extra.size()) < TX_SIZE_TARGET(upper_transaction_size_limit)) { // we can partially fill that destination LOG_PRINT_L2("We can partially pay " << get_account_address_as_str(m_testnet, dsts[0].is_subaddress, dsts[0].addr) << " for " << print_money(available_amount) << "/" << print_money(dsts[0].amount)); @@ -5682,26 +5682,31 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp // here, check if we need to sent tx and start a new one LOG_PRINT_L2("Considering whether to create a tx now, " << tx.selected_transfers.size() << " inputs, tx limit " << upper_transaction_size_limit); - bool try_tx; - if (adding_fee) - { - /* might not actually be enough if adding this output bumps size to next kB, but we need to try */ - try_tx = available_for_fee >= needed_fee; - } - else + bool try_tx = false; + // if we have preferred picks, but haven't yet used all of them, continue + if (preferred_inputs.empty()) { - const size_t estimated_rct_tx_size = estimate_tx_size(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()); - try_tx = dsts.empty() || (estimated_rct_tx_size >= TX_SIZE_TARGET(upper_transaction_size_limit)); + if (adding_fee) + { + /* might not actually be enough if adding this output bumps size to next kB, but we need to try */ + try_tx = available_for_fee >= needed_fee; + } + else + { + const size_t estimated_rct_tx_size = estimate_tx_size(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size(), extra.size()); + try_tx = dsts.empty() || (estimated_rct_tx_size >= TX_SIZE_TARGET(upper_transaction_size_limit)); + } } if (try_tx) { cryptonote::transaction test_tx; pending_tx test_ptx; - needed_fee = 0; + const size_t estimated_tx_size = estimate_tx_size(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size(), extra.size()); + needed_fee = calculate_fee(fee_per_kb, estimated_tx_size, fee_multiplier); - LOG_PRINT_L2("Trying to create a tx now, with " << tx.dsts.size() << " destinations and " << - tx.selected_transfers.size() << " outputs"); + LOG_PRINT_L2("Trying to create a tx now, with " << tx.dsts.size() << " outputs and " << + tx.selected_transfers.size() << " inputs"); if (use_rct) transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra, test_tx, test_ptx); @@ -5743,8 +5748,8 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp } else { - LOG_PRINT_L2("We made a tx, adjusting fee and saving it"); - do { + LOG_PRINT_L2("We made a tx, adjusting fee and saving it, we need " << print_money(needed_fee) << " and we have " << print_money(test_ptx.fee)); + while (needed_fee > test_ptx.fee) { if (use_rct) transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra, test_tx, test_ptx); @@ -5755,7 +5760,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp 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) << " fee and " << print_money(test_ptx.change_dts.amount) << " change"); - } while (needed_fee > test_ptx.fee); + } LOG_PRINT_L2("Made a final " << ((txBlob.size() + 1023)/1024) << " kB tx, with " << print_money(test_ptx.fee) << " fee and " << print_money(test_ptx.change_dts.amount) << " change"); @@ -5937,14 +5942,15 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton // here, check if we need to sent tx and start a new one LOG_PRINT_L2("Considering whether to create a tx now, " << tx.selected_transfers.size() << " inputs, tx limit " << upper_transaction_size_limit); - const size_t estimated_rct_tx_size = estimate_tx_size(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size() + 1); + const size_t estimated_rct_tx_size = estimate_tx_size(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size() + 1, extra.size()); bool try_tx = (unused_dust_indices.empty() && unused_transfers_indices.empty()) || ( estimated_rct_tx_size >= TX_SIZE_TARGET(upper_transaction_size_limit)); if (try_tx) { cryptonote::transaction test_tx; pending_tx test_ptx; - needed_fee = 0; + const size_t estimated_tx_size = estimate_tx_size(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size(), extra.size()); + needed_fee = calculate_fee(fee_per_kb, estimated_tx_size, fee_multiplier); tx.dsts.push_back(tx_destination_entry(1, address, is_subaddress)); @@ -5964,7 +5970,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton THROW_WALLET_EXCEPTION_IF(needed_fee > available_for_fee, error::wallet_internal_error, "Transaction cannot pay for itself"); - do { + while (needed_fee > test_ptx.fee) { LOG_PRINT_L2("We made a tx, adjusting fee and saving it"); tx.dsts[0].amount = available_for_fee - needed_fee; if (use_rct) @@ -5977,7 +5983,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton 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) << " fee and " << print_money(test_ptx.change_dts.amount) << " change"); - } while (needed_fee > test_ptx.fee); + } LOG_PRINT_L2("Made a final " << ((txBlob.size() + 1023)/1024) << " kB tx, with " << print_money(test_ptx.fee) << " fee and " << print_money(test_ptx.change_dts.amount) << " change"); @@ -6207,6 +6213,576 @@ bool wallet2::get_tx_key(const crypto::hash &txid, crypto::secret_key &tx_key, s additional_tx_keys = j->second; return true; } +//---------------------------------------------------------------------------------------------------- +std::string wallet2::get_spend_proof(const crypto::hash &txid, const std::string &message) +{ + THROW_WALLET_EXCEPTION_IF(m_watch_only, error::wallet_internal_error, + "get_spend_proof requires spend secret key and is not available for a watch-only wallet"); + + // fetch tx from daemon + COMMAND_RPC_GET_TRANSACTIONS::request req = AUTO_VAL_INIT(req); + req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); + req.decode_as_json = false; + COMMAND_RPC_GET_TRANSACTIONS::response res = AUTO_VAL_INIT(res); + bool r; + { + const boost::lock_guard<boost::mutex> lock{m_daemon_rpc_mutex}; + r = epee::net_utils::invoke_http_json("/gettransactions", req, res, m_http_client, rpc_timeout); + } + THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "gettransactions"); + THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "gettransactions"); + THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::wallet_internal_error, "gettransactions"); + THROW_WALLET_EXCEPTION_IF(res.txs.size() != 1, error::wallet_internal_error, + "daemon returned wrong response for gettransactions, wrong txs count = " + + std::to_string(res.txs.size()) + ", expected 1"); + cryptonote::blobdata bd; + THROW_WALLET_EXCEPTION_IF(!epee::string_tools::parse_hexstr_to_binbuff(res.txs[0].as_hex, bd), error::wallet_internal_error, "failed to parse tx from hexstr"); + cryptonote::transaction tx; + crypto::hash tx_hash, tx_prefix_hash; + THROW_WALLET_EXCEPTION_IF(!cryptonote::parse_and_validate_tx_from_blob(bd, tx, tx_hash, tx_prefix_hash), error::wallet_internal_error, "failed to parse tx from blob"); + THROW_WALLET_EXCEPTION_IF(tx_hash != txid, error::wallet_internal_error, "txid mismatch"); + + std::vector<std::vector<crypto::signature>> signatures; + + // get signature prefix hash + std::string sig_prefix_data((const char*)&txid, sizeof(crypto::hash)); + sig_prefix_data += message; + crypto::hash sig_prefix_hash; + crypto::cn_fast_hash(sig_prefix_data.data(), sig_prefix_data.size(), sig_prefix_hash); + + for(size_t i = 0; i < tx.vin.size(); ++i) + { + const txin_to_key* const in_key = boost::get<txin_to_key>(std::addressof(tx.vin[i])); + if (in_key == nullptr) + continue; + + // check if the key image belongs to us + const auto found = m_key_images.find(in_key->k_image); + if(found == m_key_images.end()) + { + THROW_WALLET_EXCEPTION_IF(i > 0, error::wallet_internal_error, "subset of key images belong to us, very weird!"); + THROW_WALLET_EXCEPTION_IF(true, error::wallet_internal_error, "This tx wasn't generated by this wallet!"); + } + + // derive the real output keypair + const transfer_details& in_td = m_transfers[found->second]; + const txout_to_key* const in_tx_out_pkey = boost::get<txout_to_key>(std::addressof(in_td.m_tx.vout[in_td.m_internal_output_index].target)); + THROW_WALLET_EXCEPTION_IF(in_tx_out_pkey == nullptr, error::wallet_internal_error, "Output is not txout_to_key"); + const crypto::public_key in_tx_pub_key = get_tx_pub_key_from_extra(in_td.m_tx, in_td.m_pk_index); + const std::vector<crypto::public_key> in_additionakl_tx_pub_keys = get_additional_tx_pub_keys_from_extra(in_td.m_tx); + keypair in_ephemeral; + crypto::key_image in_img; + THROW_WALLET_EXCEPTION_IF(!generate_key_image_helper(m_account.get_keys(), m_subaddresses, in_tx_out_pkey->key, in_tx_pub_key, in_additionakl_tx_pub_keys, in_td.m_internal_output_index, in_ephemeral, in_img), + error::wallet_internal_error, "failed to generate key image"); + THROW_WALLET_EXCEPTION_IF(in_key->k_image != in_img, error::wallet_internal_error, "key image mismatch"); + + // get output pubkeys in the ring + const std::vector<uint64_t> absolute_offsets = cryptonote::relative_output_offsets_to_absolute(in_key->key_offsets); + const size_t ring_size = in_key->key_offsets.size(); + THROW_WALLET_EXCEPTION_IF(absolute_offsets.size() != ring_size, error::wallet_internal_error, "absolute offsets size is wrong"); + COMMAND_RPC_GET_OUTPUTS_BIN::request req = AUTO_VAL_INIT(req); + req.outputs.resize(ring_size); + for (size_t j = 0; j < ring_size; ++j) + { + req.outputs[j].amount = in_key->amount; + req.outputs[j].index = absolute_offsets[j]; + } + COMMAND_RPC_GET_OUTPUTS_BIN::response res = AUTO_VAL_INIT(res); + bool r; + { + const boost::lock_guard<boost::mutex> lock{m_daemon_rpc_mutex}; + r = epee::net_utils::invoke_http_bin("/get_outs.bin", req, res, m_http_client, rpc_timeout); + } + THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_outs.bin"); + THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_outs.bin"); + THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::wallet_internal_error, "get_outs.bin"); + THROW_WALLET_EXCEPTION_IF(res.outs.size() != ring_size, error::wallet_internal_error, + "daemon returned wrong response for get_outs.bin, wrong amounts count = " + + std::to_string(res.outs.size()) + ", expected " + std::to_string(ring_size)); + + // copy pubkey pointers + std::vector<const crypto::public_key *> p_output_keys; + for (const COMMAND_RPC_GET_OUTPUTS_BIN::outkey &out : res.outs) + p_output_keys.push_back(&out.key); + + // figure out real output index and secret key + size_t sec_index = -1; + for (size_t j = 0; j < ring_size; ++j) + { + if (res.outs[j].key == in_ephemeral.pub) + { + sec_index = j; + break; + } + } + THROW_WALLET_EXCEPTION_IF(sec_index >= ring_size, error::wallet_internal_error, "secret index not found"); + + // generate ring sig for this input + signatures.push_back(std::vector<crypto::signature>()); + std::vector<crypto::signature>& sigs = signatures.back(); + sigs.resize(in_key->key_offsets.size()); + crypto::generate_ring_signature(sig_prefix_hash, in_key->k_image, p_output_keys, in_ephemeral.sec, sec_index, sigs.data()); + } + + std::string sig_str = "SpendProofV1"; + for (const std::vector<crypto::signature>& ring_sig : signatures) + for (const crypto::signature& sig : ring_sig) + sig_str += tools::base58::encode(std::string((const char *)&sig, sizeof(crypto::signature))); + return sig_str; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::check_spend_proof(const crypto::hash &txid, const std::string &message, const std::string &sig_str) +{ + const std::string header = "SpendProofV1"; + const size_t header_len = header.size(); + THROW_WALLET_EXCEPTION_IF(sig_str.size() < header_len || sig_str.substr(0, header_len) != header, error::wallet_internal_error, + "Signature header check error"); + + // fetch tx from daemon + COMMAND_RPC_GET_TRANSACTIONS::request req = AUTO_VAL_INIT(req); + req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); + req.decode_as_json = false; + COMMAND_RPC_GET_TRANSACTIONS::response res = AUTO_VAL_INIT(res); + bool r; + { + const boost::lock_guard<boost::mutex> lock{m_daemon_rpc_mutex}; + r = epee::net_utils::invoke_http_json("/gettransactions", req, res, m_http_client, rpc_timeout); + } + THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "gettransactions"); + THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "gettransactions"); + THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::wallet_internal_error, "gettransactions"); + THROW_WALLET_EXCEPTION_IF(res.txs.size() != 1, error::wallet_internal_error, + "daemon returned wrong response for gettransactions, wrong txs count = " + + std::to_string(res.txs.size()) + ", expected 1"); + cryptonote::blobdata bd; + THROW_WALLET_EXCEPTION_IF(!epee::string_tools::parse_hexstr_to_binbuff(res.txs[0].as_hex, bd), error::wallet_internal_error, "failed to parse tx from hexstr"); + cryptonote::transaction tx; + crypto::hash tx_hash, tx_prefix_hash; + THROW_WALLET_EXCEPTION_IF(!cryptonote::parse_and_validate_tx_from_blob(bd, tx, tx_hash, tx_prefix_hash), error::wallet_internal_error, "failed to parse tx from blob"); + THROW_WALLET_EXCEPTION_IF(tx_hash != txid, error::wallet_internal_error, "txid mismatch"); + + // check signature size + size_t num_sigs = 0; + for(size_t i = 0; i < tx.vin.size(); ++i) + { + const txin_to_key* const in_key = boost::get<txin_to_key>(std::addressof(tx.vin[i])); + if (in_key != nullptr) + num_sigs += in_key->key_offsets.size(); + } + std::vector<std::vector<crypto::signature>> signatures = { std::vector<crypto::signature>(1) }; + const size_t sig_len = tools::base58::encode(std::string((const char *)&signatures[0][0], sizeof(crypto::signature))).size(); + THROW_WALLET_EXCEPTION_IF(sig_str.size() != header_len + num_sigs * sig_len, + error::wallet_internal_error, "incorrect signature size"); + + // decode base58 + signatures.clear(); + size_t offset = header_len; + for(size_t i = 0; i < tx.vin.size(); ++i) + { + const txin_to_key* const in_key = boost::get<txin_to_key>(std::addressof(tx.vin[i])); + if (in_key == nullptr) + continue; + signatures.resize(signatures.size() + 1); + signatures.back().resize(in_key->key_offsets.size()); + for (size_t j = 0; j < in_key->key_offsets.size(); ++j) + { + std::string sig_decoded; + THROW_WALLET_EXCEPTION_IF(!tools::base58::decode(sig_str.substr(offset, sig_len), sig_decoded), error::wallet_internal_error, "Signature decoding error"); + THROW_WALLET_EXCEPTION_IF(sizeof(crypto::signature) != sig_decoded.size(), error::wallet_internal_error, "Signature decoding error"); + memcpy(&signatures.back()[j], sig_decoded.data(), sizeof(crypto::signature)); + offset += sig_len; + } + } + + // get signature prefix hash + std::string sig_prefix_data((const char*)&txid, sizeof(crypto::hash)); + sig_prefix_data += message; + crypto::hash sig_prefix_hash; + crypto::cn_fast_hash(sig_prefix_data.data(), sig_prefix_data.size(), sig_prefix_hash); + + std::vector<std::vector<crypto::signature>>::const_iterator sig_iter = signatures.cbegin(); + for(size_t i = 0; i < tx.vin.size(); ++i) + { + const txin_to_key* const in_key = boost::get<txin_to_key>(std::addressof(tx.vin[i])); + if (in_key == nullptr) + continue; + + // get output pubkeys in the ring + COMMAND_RPC_GET_OUTPUTS_BIN::request req = AUTO_VAL_INIT(req); + const std::vector<uint64_t> absolute_offsets = cryptonote::relative_output_offsets_to_absolute(in_key->key_offsets); + req.outputs.resize(absolute_offsets.size()); + for (size_t j = 0; j < absolute_offsets.size(); ++j) + { + req.outputs[j].amount = in_key->amount; + req.outputs[j].index = absolute_offsets[j]; + } + COMMAND_RPC_GET_OUTPUTS_BIN::response res = AUTO_VAL_INIT(res); + bool r; + { + const boost::lock_guard<boost::mutex> lock{m_daemon_rpc_mutex}; + r = epee::net_utils::invoke_http_bin("/get_outs.bin", req, res, m_http_client, rpc_timeout); + } + THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_outs.bin"); + THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_outs.bin"); + THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::wallet_internal_error, "get_outs.bin"); + THROW_WALLET_EXCEPTION_IF(res.outs.size() != req.outputs.size(), error::wallet_internal_error, + "daemon returned wrong response for get_outs.bin, wrong amounts count = " + + std::to_string(res.outs.size()) + ", expected " + std::to_string(req.outputs.size())); + + // copy pointers + std::vector<const crypto::public_key *> p_output_keys; + for (const COMMAND_RPC_GET_OUTPUTS_BIN::outkey &out : res.outs) + p_output_keys.push_back(&out.key); + + // check this ring + if (!crypto::check_ring_signature(sig_prefix_hash, in_key->k_image, p_output_keys, sig_iter->data())) + return false; + ++sig_iter; + } + THROW_WALLET_EXCEPTION_IF(sig_iter != signatures.cend(), error::wallet_internal_error, "Signature iterator didn't reach the end"); + return true; +} +//---------------------------------------------------------------------------------------------------- + +void wallet2::check_tx_key(const crypto::hash &txid, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, const cryptonote::account_public_address &address, uint64_t &received, bool &in_pool, uint64_t &confirmations) +{ + crypto::key_derivation derivation; + THROW_WALLET_EXCEPTION_IF(!crypto::generate_key_derivation(address.m_view_public_key, tx_key, derivation), error::wallet_internal_error, + "Failed to generate key derivation from supplied parameters"); + + std::vector<crypto::key_derivation> additional_derivations; + additional_derivations.resize(additional_tx_keys.size()); + for (size_t i = 0; i < additional_tx_keys.size(); ++i) + THROW_WALLET_EXCEPTION_IF(!crypto::generate_key_derivation(address.m_view_public_key, additional_tx_keys[i], additional_derivations[i]), error::wallet_internal_error, + "Failed to generate key derivation from supplied parameters"); + + check_tx_key_helper(txid, derivation, additional_derivations, address, received, in_pool, confirmations); +} + +void wallet2::check_tx_key_helper(const crypto::hash &txid, const crypto::key_derivation &derivation, const std::vector<crypto::key_derivation> &additional_derivations, const cryptonote::account_public_address &address, uint64_t &received, bool &in_pool, uint64_t &confirmations) +{ + COMMAND_RPC_GET_TRANSACTIONS::request req; + COMMAND_RPC_GET_TRANSACTIONS::response res; + req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); + m_daemon_rpc_mutex.lock(); + bool ok = epee::net_utils::invoke_http_json("/gettransactions", req, res, m_http_client); + m_daemon_rpc_mutex.unlock(); + THROW_WALLET_EXCEPTION_IF(!ok || (res.txs.size() != 1 && res.txs_as_hex.size() != 1), + error::wallet_internal_error, "Failed to get transaction from daemon"); + + cryptonote::blobdata tx_data; + if (res.txs.size() == 1) + ok = string_tools::parse_hexstr_to_binbuff(res.txs.front().as_hex, tx_data); + else + ok = string_tools::parse_hexstr_to_binbuff(res.txs_as_hex.front(), tx_data); + THROW_WALLET_EXCEPTION_IF(!ok, error::wallet_internal_error, "Failed to parse transaction from daemon"); + + crypto::hash tx_hash, tx_prefix_hash; + cryptonote::transaction tx; + THROW_WALLET_EXCEPTION_IF(!cryptonote::parse_and_validate_tx_from_blob(tx_data, tx, tx_hash, tx_prefix_hash), error::wallet_internal_error, + "Failed to validate transaction from daemon"); + THROW_WALLET_EXCEPTION_IF(tx_hash != txid, error::wallet_internal_error, + "Failed to get the right transaction from daemon"); + THROW_WALLET_EXCEPTION_IF(!additional_derivations.empty() && additional_derivations.size() != tx.vout.size(), error::wallet_internal_error, + "The size of additional derivations is wrong"); + + received = 0; + for (size_t n = 0; n < tx.vout.size(); ++n) + { + const cryptonote::txout_to_key* const out_key = boost::get<cryptonote::txout_to_key>(std::addressof(tx.vout[n].target)); + if (!out_key) + continue; + + crypto::public_key derived_out_key; + derive_public_key(derivation, n, address.m_spend_public_key, derived_out_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); + found = out_key->key == derived_out_key; + found_derivation = additional_derivations[n]; + } + + if (found) + { + uint64_t amount; + if (tx.version == 1 || tx.rct_signatures.type == rct::RCTTypeNull) + { + amount = tx.vout[n].amount; + } + else + { + crypto::secret_key scalar1; + crypto::derivation_to_scalar(found_derivation, n, scalar1); + rct::ecdhTuple ecdh_info = tx.rct_signatures.ecdhInfo[n]; + rct::ecdhDecode(ecdh_info, rct::sk2rct(scalar1)); + const rct::key C = tx.rct_signatures.outPk[n].mask; + rct::key Ctmp; + rct::addKeys2(Ctmp, ecdh_info.mask, ecdh_info.amount, rct::H); + if (rct::equalKeys(C, Ctmp)) + amount = rct::h2d(ecdh_info.amount); + else + amount = 0; + } + received += amount; + } + } + + in_pool = res.txs.front().in_pool; + confirmations = (uint64_t)-1; + if (!in_pool) + { + std::string err; + uint64_t bc_height = get_daemon_blockchain_height(err); + if (err.empty()) + confirmations = bc_height - (res.txs.front().block_height + 1); + } +} + +std::string wallet2::get_tx_proof(const crypto::hash &txid, const cryptonote::account_public_address &address, bool is_subaddress, const std::string &message) +{ + // determine if the address is found in the subaddress hash table (i.e. whether the proof is outbound or inbound) + const bool is_out = m_subaddresses.count(address.m_spend_public_key) == 0; + + std::string prefix_data((const char*)&txid, sizeof(crypto::hash)); + prefix_data += message; + crypto::hash prefix_hash; + crypto::cn_fast_hash(prefix_data.data(), prefix_data.size(), prefix_hash); + + std::vector<crypto::public_key> shared_secret; + std::vector<crypto::signature> sig; + std::string sig_str; + if (is_out) + { + crypto::secret_key tx_key; + std::vector<crypto::secret_key> additional_tx_keys; + THROW_WALLET_EXCEPTION_IF(!get_tx_key(txid, tx_key, additional_tx_keys), error::wallet_internal_error, "Tx secret key wasn't found in the wallet file."); + + const size_t num_sigs = 1 + additional_tx_keys.size(); + shared_secret.resize(num_sigs); + sig.resize(num_sigs); + + shared_secret[0] = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(address.m_view_public_key), rct::sk2rct(tx_key))); + crypto::public_key tx_pub_key; + if (is_subaddress) + { + tx_pub_key = rct2pk(rct::scalarmultKey(rct::pk2rct(address.m_spend_public_key), rct::sk2rct(tx_key))); + crypto::generate_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, address.m_spend_public_key, shared_secret[0], tx_key, sig[0]); + } + else + { + crypto::secret_key_to_public_key(tx_key, tx_pub_key); + crypto::generate_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, boost::none, shared_secret[0], tx_key, sig[0]); + } + for (size_t i = 1; i < num_sigs; ++i) + { + shared_secret[i] = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(address.m_view_public_key), rct::sk2rct(additional_tx_keys[i - 1]))); + if (is_subaddress) + { + tx_pub_key = rct2pk(rct::scalarmultKey(rct::pk2rct(address.m_spend_public_key), rct::sk2rct(additional_tx_keys[i - 1]))); + crypto::generate_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, address.m_spend_public_key, shared_secret[i], additional_tx_keys[i - 1], sig[i]); + } + else + { + crypto::secret_key_to_public_key(additional_tx_keys[i - 1], tx_pub_key); + crypto::generate_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, boost::none, shared_secret[i], additional_tx_keys[i - 1], sig[i]); + } + } + sig_str = std::string("OutProofV1"); + } + else + { + // fetch tx pubkey from the daemon + COMMAND_RPC_GET_TRANSACTIONS::request req; + COMMAND_RPC_GET_TRANSACTIONS::response res; + req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); + m_daemon_rpc_mutex.lock(); + bool ok = net_utils::invoke_http_json("/gettransactions", req, res, m_http_client); + m_daemon_rpc_mutex.unlock(); + THROW_WALLET_EXCEPTION_IF(!ok || (res.txs.size() != 1 && res.txs_as_hex.size() != 1), + error::wallet_internal_error, "Failed to get transaction from daemon"); + + cryptonote::blobdata tx_data; + if (res.txs.size() == 1) + ok = string_tools::parse_hexstr_to_binbuff(res.txs.front().as_hex, tx_data); + else + ok = string_tools::parse_hexstr_to_binbuff(res.txs_as_hex.front(), tx_data); + THROW_WALLET_EXCEPTION_IF(!ok, error::wallet_internal_error, "Failed to parse transaction from daemon"); + + crypto::hash tx_hash, tx_prefix_hash; + cryptonote::transaction tx; + THROW_WALLET_EXCEPTION_IF(!cryptonote::parse_and_validate_tx_from_blob(tx_data, tx, tx_hash, tx_prefix_hash), error::wallet_internal_error, + "Failed to validate transaction from daemon"); + THROW_WALLET_EXCEPTION_IF(tx_hash != txid, error::wallet_internal_error, "Failed to get the right transaction from daemon"); + + crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(tx); + THROW_WALLET_EXCEPTION_IF(tx_pub_key == null_pkey, error::wallet_internal_error, "Tx pubkey was not found"); + + std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(tx); + const size_t num_sigs = 1 + additional_tx_pub_keys.size(); + shared_secret.resize(num_sigs); + sig.resize(num_sigs); + + const crypto::secret_key& a = m_account.get_keys().m_view_secret_key; + shared_secret[0] = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(tx_pub_key), rct::sk2rct(a))); + if (is_subaddress) + { + crypto::generate_tx_proof(prefix_hash, address.m_view_public_key, tx_pub_key, address.m_spend_public_key, shared_secret[0], a, sig[0]); + } + else + { + crypto::generate_tx_proof(prefix_hash, address.m_view_public_key, tx_pub_key, boost::none, shared_secret[0], a, sig[0]); + } + for (size_t i = 1; i < num_sigs; ++i) + { + shared_secret[i] = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(additional_tx_pub_keys[i - 1]), rct::sk2rct(a))); + if (is_subaddress) + { + crypto::generate_tx_proof(prefix_hash, address.m_view_public_key, additional_tx_pub_keys[i - 1], address.m_spend_public_key, shared_secret[i], a, sig[i]); + } + else + { + crypto::generate_tx_proof(prefix_hash, address.m_view_public_key, additional_tx_pub_keys[i - 1], boost::none, shared_secret[i], a, sig[i]); + } + } + sig_str = std::string("InProofV1"); + } + const size_t num_sigs = shared_secret.size(); + + // check if this address actually received any funds + crypto::key_derivation derivation; + THROW_WALLET_EXCEPTION_IF(!crypto::generate_key_derivation(shared_secret[0], rct::rct2sk(rct::I), derivation), error::wallet_internal_error, "Failed to generate key derivation"); + std::vector<crypto::key_derivation> additional_derivations(num_sigs - 1); + for (size_t i = 1; i < num_sigs; ++i) + THROW_WALLET_EXCEPTION_IF(!crypto::generate_key_derivation(shared_secret[i], rct::rct2sk(rct::I), additional_derivations[i - 1]), error::wallet_internal_error, "Failed to generate key derivation"); + uint64_t received; + bool in_pool; + uint64_t confirmations; + check_tx_key_helper(txid, derivation, additional_derivations, address, received, in_pool, confirmations); + THROW_WALLET_EXCEPTION_IF(!received, error::wallet_internal_error, tr("No funds received in this tx.")); + + // concatenate all signature strings + for (size_t i = 0; i < num_sigs; ++i) + sig_str += + tools::base58::encode(std::string((const char *)&shared_secret[i], sizeof(crypto::public_key))) + + tools::base58::encode(std::string((const char *)&sig[i], sizeof(crypto::signature))); + return sig_str; +} + +bool wallet2::check_tx_proof(const crypto::hash &txid, const cryptonote::account_public_address &address, bool is_subaddress, const std::string &message, const std::string &sig_str, uint64_t &received, bool &in_pool, uint64_t &confirmations) +{ + const bool is_out = sig_str.substr(0, 3) == "Out"; + const std::string header = is_out ? "OutProofV1" : "InProofV1"; + const size_t header_len = header.size(); + THROW_WALLET_EXCEPTION_IF(sig_str.size() < header_len || sig_str.substr(0, header_len) != header, error::wallet_internal_error, + "Signature header check error"); + + // decode base58 + std::vector<crypto::public_key> shared_secret(1); + std::vector<crypto::signature> sig(1); + const size_t pk_len = tools::base58::encode(std::string((const char *)&shared_secret[0], sizeof(crypto::public_key))).size(); + const size_t sig_len = tools::base58::encode(std::string((const char *)&sig[0], sizeof(crypto::signature))).size(); + const size_t num_sigs = (sig_str.size() - header_len) / (pk_len + sig_len); + THROW_WALLET_EXCEPTION_IF(sig_str.size() != header_len + num_sigs * (pk_len + sig_len), error::wallet_internal_error, + "Wrong signature size"); + shared_secret.resize(num_sigs); + sig.resize(num_sigs); + for (size_t i = 0; i < num_sigs; ++i) + { + std::string pk_decoded; + std::string sig_decoded; + const size_t offset = header_len + i * (pk_len + sig_len); + THROW_WALLET_EXCEPTION_IF(!tools::base58::decode(sig_str.substr(offset, pk_len), pk_decoded), error::wallet_internal_error, + "Signature decoding error"); + THROW_WALLET_EXCEPTION_IF(!tools::base58::decode(sig_str.substr(offset + pk_len, sig_len), sig_decoded), error::wallet_internal_error, + "Signature decoding error"); + THROW_WALLET_EXCEPTION_IF(sizeof(crypto::public_key) != pk_decoded.size() || sizeof(crypto::signature) != sig_decoded.size(), error::wallet_internal_error, + "Signature decoding error"); + memcpy(&shared_secret[i], pk_decoded.data(), sizeof(crypto::public_key)); + memcpy(&sig[i], sig_decoded.data(), sizeof(crypto::signature)); + } + + // fetch tx pubkey from the daemon + COMMAND_RPC_GET_TRANSACTIONS::request req; + COMMAND_RPC_GET_TRANSACTIONS::response res; + req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); + m_daemon_rpc_mutex.lock(); + bool ok = net_utils::invoke_http_json("/gettransactions", req, res, m_http_client); + m_daemon_rpc_mutex.unlock(); + THROW_WALLET_EXCEPTION_IF(!ok || (res.txs.size() != 1 && res.txs_as_hex.size() != 1), + error::wallet_internal_error, "Failed to get transaction from daemon"); + + cryptonote::blobdata tx_data; + if (res.txs.size() == 1) + ok = string_tools::parse_hexstr_to_binbuff(res.txs.front().as_hex, tx_data); + else + ok = string_tools::parse_hexstr_to_binbuff(res.txs_as_hex.front(), tx_data); + THROW_WALLET_EXCEPTION_IF(!ok, error::wallet_internal_error, "Failed to parse transaction from daemon"); + + crypto::hash tx_hash, tx_prefix_hash; + cryptonote::transaction tx; + THROW_WALLET_EXCEPTION_IF(!cryptonote::parse_and_validate_tx_from_blob(tx_data, tx, tx_hash, tx_prefix_hash), error::wallet_internal_error, + "Failed to validate transaction from daemon"); + THROW_WALLET_EXCEPTION_IF(tx_hash != txid, error::wallet_internal_error, "Failed to get the right transaction from daemon"); + + crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(tx); + THROW_WALLET_EXCEPTION_IF(tx_pub_key == null_pkey, error::wallet_internal_error, "Tx pubkey was not found"); + + std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(tx); + THROW_WALLET_EXCEPTION_IF(additional_tx_pub_keys.size() + 1 != num_sigs, error::wallet_internal_error, "Signature size mismatch with additional tx pubkeys"); + + std::string prefix_data((const char*)&txid, sizeof(crypto::hash)); + prefix_data += message; + crypto::hash prefix_hash; + crypto::cn_fast_hash(prefix_data.data(), prefix_data.size(), prefix_hash); + + // check signature + std::vector<int> good_signature(num_sigs, 0); + if (is_out) + { + good_signature[0] = is_subaddress ? + crypto::check_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, address.m_spend_public_key, shared_secret[0], sig[0]) : + crypto::check_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, boost::none, shared_secret[0], sig[0]); + + for (size_t i = 0; i < additional_tx_pub_keys.size(); ++i) + { + good_signature[i + 1] = is_subaddress ? + crypto::check_tx_proof(prefix_hash, additional_tx_pub_keys[i], address.m_view_public_key, address.m_spend_public_key, shared_secret[i + 1], sig[i + 1]) : + crypto::check_tx_proof(prefix_hash, additional_tx_pub_keys[i], address.m_view_public_key, boost::none, shared_secret[i + 1], sig[i + 1]); + } + } + else + { + good_signature[0] = is_subaddress ? + crypto::check_tx_proof(prefix_hash, address.m_view_public_key, tx_pub_key, address.m_spend_public_key, shared_secret[0], sig[0]) : + crypto::check_tx_proof(prefix_hash, address.m_view_public_key, tx_pub_key, boost::none, shared_secret[0], sig[0]); + + for (size_t i = 0; i < additional_tx_pub_keys.size(); ++i) + { + good_signature[i + 1] = is_subaddress ? + crypto::check_tx_proof(prefix_hash, address.m_view_public_key, additional_tx_pub_keys[i], address.m_spend_public_key, shared_secret[i + 1], sig[i + 1]) : + crypto::check_tx_proof(prefix_hash, address.m_view_public_key, additional_tx_pub_keys[i], boost::none, shared_secret[i + 1], sig[i + 1]); + } + } + + if (std::any_of(good_signature.begin(), good_signature.end(), [](int i) { return i > 0; })) + { + // obtain key derivation by multiplying scalar 1 to the shared secret + crypto::key_derivation derivation; + if (good_signature[0]) + THROW_WALLET_EXCEPTION_IF(!crypto::generate_key_derivation(shared_secret[0], rct::rct2sk(rct::I), derivation), error::wallet_internal_error, "Failed to generate key derivation"); + + std::vector<crypto::key_derivation> additional_derivations(num_sigs - 1); + for (size_t i = 1; i < num_sigs; ++i) + if (good_signature[i]) + THROW_WALLET_EXCEPTION_IF(!crypto::generate_key_derivation(shared_secret[i], rct::rct2sk(rct::I), additional_derivations[i - 1]), error::wallet_internal_error, "Failed to generate key derivation"); + + check_tx_key_helper(txid, derivation, additional_derivations, address, received, in_pool, confirmations); + return true; + } + return false; +} std::string wallet2::get_wallet_file() const { diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 6e01d4e28..ce0c67fc3 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -292,6 +292,19 @@ namespace tools std::vector<cryptonote::tx_destination_entry> dests; // original setup, does not include change uint32_t subaddr_account; // subaddress account of your wallet to be used in this transfer std::set<uint32_t> subaddr_indices; // set of address indices used as inputs in this transfer + + BEGIN_SERIALIZE_OBJECT() + FIELD(sources) + FIELD(change_dts) + FIELD(splitted_dsts) + FIELD(selected_transfers) + FIELD(extra) + FIELD(unlock_time) + FIELD(use_rct) + FIELD(dests) + FIELD(subaddr_account) + FIELD(subaddr_indices) + END_SERIALIZE() }; typedef std::vector<transfer_details> transfer_container; @@ -313,6 +326,20 @@ namespace tools std::vector<cryptonote::tx_destination_entry> dests; tx_construction_data construction_data; + + BEGIN_SERIALIZE_OBJECT() + FIELD(tx) + FIELD(dust) + FIELD(fee) + FIELD(dust_added_to_fee) + FIELD(change_dts) + FIELD(selected_transfers) + FIELD(key_images) + FIELD(tx_key) + FIELD(additional_tx_keys) + FIELD(dests) + FIELD(construction_data) + END_SERIALIZE() }; // The term "Unsigned tx" is not really a tx since it's not signed yet. @@ -684,7 +711,13 @@ namespace tools uint32_t get_confirm_backlog_threshold() const { return m_confirm_backlog_threshold; }; bool get_tx_key(const crypto::hash &txid, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys) const; + void check_tx_key(const crypto::hash &txid, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, const cryptonote::account_public_address &address, uint64_t &received, bool &in_pool, uint64_t &confirmations); + void check_tx_key_helper(const crypto::hash &txid, const crypto::key_derivation &derivation, const std::vector<crypto::key_derivation> &additional_derivations, const cryptonote::account_public_address &address, uint64_t &received, bool &in_pool, uint64_t &confirmations); + std::string get_tx_proof(const crypto::hash &txid, const cryptonote::account_public_address &address, bool is_subaddress, const std::string &message); + bool check_tx_proof(const crypto::hash &txid, const cryptonote::account_public_address &address, bool is_subaddress, const std::string &message, const std::string &sig_str, uint64_t &received, bool &in_pool, uint64_t &confirmations); + std::string get_spend_proof(const crypto::hash &txid, const std::string &message); + bool check_spend_proof(const crypto::hash &txid, const std::string &message, const std::string &sig_str); /*! * \brief GUI Address book get/store */ diff --git a/src/wallet/wallet_args.cpp b/src/wallet/wallet_args.cpp index cc6bb1de2..e665042d4 100644 --- a/src/wallet/wallet_args.cpp +++ b/src/wallet/wallet_args.cpp @@ -178,7 +178,7 @@ namespace wallet_args mlog_set_log(command_line::get_arg(vm, arg_log_level).c_str()); } - if(command_line::has_arg(vm, arg_max_concurrency)) + if (!command_line::is_arg_defaulted(vm, arg_max_concurrency)) tools::set_max_concurrency(command_line::get_arg(vm, arg_max_concurrency)); Print(print) << "Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")"; diff --git a/src/wallet/wallet_errors.h b/src/wallet/wallet_errors.h index 41eb77451..48fce40dd 100644 --- a/src/wallet/wallet_errors.h +++ b/src/wallet/wallet_errors.h @@ -269,6 +269,28 @@ namespace tools } }; //---------------------------------------------------------------------------------------------------- + struct index_outofbound : public wallet_logic_error + { + explicit index_outofbound(std::string&& loc, const std::string& message) + : wallet_logic_error(std::move(loc), message) + { + } + }; + struct account_index_outofbound : public index_outofbound + { + explicit account_index_outofbound(std::string&& loc) + : index_outofbound(std::move(loc), "account index is out of bound") + { + } + }; + struct address_index_outofbound: public index_outofbound + { + explicit address_index_outofbound(std::string&& loc) + : index_outofbound(std::move(loc), "address index is out of bound") + { + } + }; + //---------------------------------------------------------------------------------------------------- struct acc_outs_lookup_error : public refresh_error { explicit acc_outs_lookup_error(std::string&& loc, const cryptonote::transaction& tx, diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index 09ed205c6..c315684de 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -30,6 +30,7 @@ #include <boost/format.hpp> #include <boost/asio/ip/address.hpp> #include <boost/filesystem/operations.hpp> +#include <boost/algorithm/string.hpp> #include <cstdint> #include "include_base_utils.h" using namespace epee; @@ -58,6 +59,7 @@ namespace const command_line::arg_descriptor<bool> arg_disable_rpc_login = {"disable-rpc-login", "Disable HTTP authentication for RPC connections served by this process"}; const command_line::arg_descriptor<bool> arg_trusted_daemon = {"trusted-daemon", "Enable commands which rely on a trusted daemon", false}; const command_line::arg_descriptor<std::string> arg_wallet_dir = {"wallet-dir", "Directory for newly created wallets"}; + const command_line::arg_descriptor<bool> arg_prompt_for_password = {"prompt-for-password", "Prompts for password when not provided", false}; constexpr const char default_rpc_username[] = "monero"; @@ -376,27 +378,23 @@ namespace tools bool wallet_rpc_server::on_create_address(const wallet_rpc::COMMAND_RPC_CREATE_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_CREATE_ADDRESS::response& res, epee::json_rpc::error& er) { if (!m_wallet) return not_open(er); - m_wallet->add_subaddress(req.account_index, req.label); - res.address_index = m_wallet->get_num_subaddresses(req.account_index) - 1; - res.address = m_wallet->get_subaddress_as_str({req.account_index, res.address_index}); + try + { + m_wallet->add_subaddress(req.account_index, req.label); + res.address_index = m_wallet->get_num_subaddresses(req.account_index) - 1; + res.address = m_wallet->get_subaddress_as_str({req.account_index, res.address_index}); + } + 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_label_address(const wallet_rpc::COMMAND_RPC_LABEL_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_LABEL_ADDRESS::response& res, epee::json_rpc::error& er) { if (!m_wallet) return not_open(er); - if (req.index.major >= m_wallet->get_num_subaddress_accounts()) - { - er.code = WALLET_RPC_ERROR_CODE_ACCOUNT_INDEX_OUTOFBOUND; - er.message = "Account index is out of bound"; - return false; - } - if (req.index.minor >= m_wallet->get_num_subaddresses(req.index.major)) - { - er.code = WALLET_RPC_ERROR_CODE_ADDRESS_INDEX_OUTOFBOUND; - er.message = "Address index is out of bound"; - return false; - } try { m_wallet->set_subaddress_label(req.index, req.label); @@ -458,12 +456,6 @@ namespace tools bool wallet_rpc_server::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) { if (!m_wallet) return not_open(er); - if (req.account_index >= m_wallet->get_num_subaddress_accounts()) - { - er.code = WALLET_RPC_ERROR_CODE_ACCOUNT_INDEX_OUTOFBOUND; - er.message = "Account index is out of bound"; - return false; - } try { m_wallet->set_subaddress_label({req.account_index, 0}, req.label); @@ -623,6 +615,8 @@ namespace tools if (req.get_tx_key) { res.tx_key = epee::string_tools::pod_to_hex(ptx_vector.back().tx_key); + for (const crypto::secret_key& additional_tx_key : ptx_vector.back().additional_tx_keys) + res.tx_key += epee::string_tools::pod_to_hex(additional_tx_key); } res.fee = ptx_vector.back().fee; @@ -632,6 +626,13 @@ namespace tools 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; + binary_archive<true> ar(oss); + ::serialization::serialize(ar, ptx_vector.back()); + res.tx_metadata = epee::string_tools::buff_to_hex_nodelimer(oss.str()); + } return true; } catch (const std::exception& e) @@ -679,12 +680,14 @@ namespace tools } // populate response with tx hashes - for (auto & ptx : ptx_vector) + 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)); + for (const crypto::secret_key& additional_tx_key : ptx.additional_tx_keys) + res.tx_key_list.back() += epee::string_tools::pod_to_hex(additional_tx_key); } // Compute amount leaving wallet in tx. By convention dests does not include change outputs ptx_amount = 0; @@ -700,6 +703,13 @@ namespace tools 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; + 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())); + } } return true; @@ -730,7 +740,7 @@ namespace tools m_wallet->commit_tx(ptx_vector); // populate response with tx hashes - for (auto & ptx : ptx_vector) + 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) @@ -744,6 +754,13 @@ namespace tools 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; + 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())); + } } return true; @@ -788,7 +805,7 @@ namespace tools m_wallet->commit_tx(ptx_vector); // populate response with tx hashes - for (auto & ptx : ptx_vector) + 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) @@ -801,6 +818,13 @@ namespace tools 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; + 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())); + } } return true; @@ -846,7 +870,7 @@ namespace tools try { - uint64_t mixin = adjust_mixin(req.mixin); + uint64_t mixin = m_wallet->adjust_mixin(req.mixin); std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_single(ki, dsts[0].addr, dsts[0].is_subaddress, mixin, req.unlock_time, req.priority, extra, m_trusted_daemon); if (ptx_vector.empty()) @@ -884,6 +908,13 @@ namespace tools 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; + 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()); + } return true; } @@ -908,6 +939,47 @@ namespace tools return true; } //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_relay_tx(const wallet_rpc::COMMAND_RPC_RELAY_TX::request& req, wallet_rpc::COMMAND_RPC_RELAY_TX::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + + cryptonote::blobdata blob; + if (!epee::string_tools::parse_hexstr_to_binbuff(req.hex, blob)) + { + er.code = WALLET_RPC_ERROR_CODE_BAD_HEX; + er.message = "Failed to parse hex."; + 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) + { + er.code = WALLET_RPC_ERROR_CODE_BAD_TX_METADATA; + er.message = "Failed to parse tx metadata."; + return false; + } + + try + { + m_wallet->commit_tx(ptx); + } + catch(const std::exception &e) + { + er.code = WALLET_RPC_ERROR_CODE_GENERIC_TRANSFER_ERROR; + er.message = "Failed to commit tx."; + return false; + } + + res.tx_hash = epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx)); + + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_make_integrated_address(const wallet_rpc::COMMAND_RPC_MAKE_INTEGRATED_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_MAKE_INTEGRATED_ADDRESS::response& res, epee::json_rpc::error& er) { if (!m_wallet) return not_open(er); @@ -1396,6 +1468,208 @@ namespace tools res.value = m_wallet->get_attribute(req.key); return true; } + bool wallet_rpc_server::on_get_tx_key(const wallet_rpc::COMMAND_RPC_GET_TX_KEY::request& req, wallet_rpc::COMMAND_RPC_GET_TX_KEY::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + + crypto::hash txid; + if (!epee::string_tools::hex_to_pod(req.txid, txid)) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_TXID; + er.message = "TX ID has invalid format"; + return false; + } + + crypto::secret_key tx_key; + std::vector<crypto::secret_key> additional_tx_keys; + if (!m_wallet->get_tx_key(txid, tx_key, additional_tx_keys)) + { + er.code = WALLET_RPC_ERROR_CODE_NO_TXKEY; + er.message = "No tx secret key is stored for this tx"; + return false; + } + + std::ostringstream oss; + oss << epee::string_tools::pod_to_hex(tx_key); + for (size_t i = 0; i < additional_tx_keys.size(); ++i) + oss << epee::string_tools::pod_to_hex(additional_tx_keys[i]); + res.tx_key = oss.str(); + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_check_tx_key(const wallet_rpc::COMMAND_RPC_CHECK_TX_KEY::request& req, wallet_rpc::COMMAND_RPC_CHECK_TX_KEY::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + + crypto::hash txid; + if (!epee::string_tools::hex_to_pod(req.txid, txid)) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_TXID; + er.message = "TX ID has invalid format"; + return false; + } + + std::string tx_key_str = req.tx_key; + crypto::secret_key tx_key; + if (!epee::string_tools::hex_to_pod(tx_key_str.substr(0, 64), tx_key)) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_KEY; + er.message = "Tx key has invalid format"; + return false; + } + tx_key_str = tx_key_str.substr(64); + std::vector<crypto::secret_key> additional_tx_keys; + while (!tx_key_str.empty()) + { + additional_tx_keys.resize(additional_tx_keys.size() + 1); + if (!epee::string_tools::hex_to_pod(tx_key_str.substr(0, 64), additional_tx_keys.back())) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_KEY; + er.message = "Tx key has invalid format"; + return false; + } + tx_key_str = tx_key_str.substr(64); + } + + cryptonote::address_parse_info info; + if(!get_account_address_from_str(info, m_wallet->testnet(), req.address)) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_ADDRESS; + er.message = "Invalid address"; + return false; + } + + try + { + m_wallet->check_tx_key(txid, tx_key, additional_tx_keys, info.address, res.received, res.in_pool, res.confirmations); + } + 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_get_tx_proof(const wallet_rpc::COMMAND_RPC_GET_TX_PROOF::request& req, wallet_rpc::COMMAND_RPC_GET_TX_PROOF::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + + crypto::hash txid; + if (!epee::string_tools::hex_to_pod(req.txid, txid)) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_TXID; + er.message = "TX ID has invalid format"; + return false; + } + + cryptonote::address_parse_info info; + if(!get_account_address_from_str(info, m_wallet->testnet(), req.address)) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_ADDRESS; + er.message = "Invalid address"; + return false; + } + + try + { + res.signature = m_wallet->get_tx_proof(txid, info.address, info.is_subaddress, req.message); + } + 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_check_tx_proof(const wallet_rpc::COMMAND_RPC_CHECK_TX_PROOF::request& req, wallet_rpc::COMMAND_RPC_CHECK_TX_PROOF::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + + crypto::hash txid; + if (!epee::string_tools::hex_to_pod(req.txid, txid)) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_TXID; + er.message = "TX ID has invalid format"; + return false; + } + + cryptonote::address_parse_info info; + if(!get_account_address_from_str(info, m_wallet->testnet(), req.address)) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_ADDRESS; + er.message = "Invalid address"; + return false; + } + + try + { + uint64_t received; + bool in_pool; + uint64_t confirmations; + res.good = m_wallet->check_tx_proof(txid, info.address, info.is_subaddress, req.message, req.signature, res.received, res.in_pool, res.confirmations); + } + 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_get_spend_proof(const wallet_rpc::COMMAND_RPC_GET_SPEND_PROOF::request& req, wallet_rpc::COMMAND_RPC_GET_SPEND_PROOF::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + + crypto::hash txid; + if (!epee::string_tools::hex_to_pod(req.txid, txid)) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_TXID; + er.message = "TX ID has invalid format"; + return false; + } + + try + { + res.signature = m_wallet->get_spend_proof(txid, req.message); + } + 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_check_spend_proof(const wallet_rpc::COMMAND_RPC_CHECK_SPEND_PROOF::request& req, wallet_rpc::COMMAND_RPC_CHECK_SPEND_PROOF::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + + crypto::hash txid; + if (!epee::string_tools::hex_to_pod(req.txid, txid)) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_TXID; + er.message = "TX ID has invalid format"; + return false; + } + + try + { + res.good = m_wallet->check_spend_proof(txid, req.message, req.signature); + } + 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_get_transfers(const wallet_rpc::COMMAND_RPC_GET_TRANSFERS::request& req, wallet_rpc::COMMAND_RPC_GET_TRANSFERS::response& res, epee::json_rpc::error& er) { @@ -1904,7 +2178,7 @@ namespace tools command_line::add_arg(desc, arg_password); po::store(po::parse_command_line(argc, argv, desc), vm2); } - std::unique_ptr<tools::wallet2> wal = tools::wallet2::make_new(vm2, password_prompter).first; + std::unique_ptr<tools::wallet2> wal = tools::wallet2::make_new(vm2, nullptr).first; if (!wal) { er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; @@ -1978,7 +2252,7 @@ namespace tools } std::unique_ptr<tools::wallet2> wal = nullptr; try { - wal = tools::wallet2::make_from_file(vm2, wallet_file, password_prompter).first; + wal = tools::wallet2::make_from_file(vm2, wallet_file, nullptr).first; } catch (const std::exception& e) { @@ -2041,6 +2315,16 @@ namespace tools er.code = WALLET_RPC_ERROR_CODE_INVALID_PASSWORD; er.message = "Invalid password."; } + catch (const error::account_index_outofbound& e) + { + er.code = WALLET_RPC_ERROR_CODE_ACCOUNT_INDEX_OUTOFBOUND; + er.message = e.what(); + } + catch (const error::address_index_outofbound& e) + { + er.code = WALLET_RPC_ERROR_CODE_ADDRESS_INDEX_OUTOFBOUND; + er.message = e.what(); + } catch (const std::exception& e) { er.code = default_error_code; @@ -2070,7 +2354,7 @@ int main(int argc, char** argv) { command_line::add_arg(desc_params, arg_wallet_file); command_line::add_arg(desc_params, arg_from_json); command_line::add_arg(desc_params, arg_wallet_dir); - + command_line::add_arg(desc_params, arg_prompt_for_password); const auto vm = wallet_args::main( argc, argv, @@ -2092,6 +2376,8 @@ int main(int argc, char** argv) { const auto wallet_file = command_line::get_arg(*vm, arg_wallet_file); const auto from_json = command_line::get_arg(*vm, arg_from_json); const auto wallet_dir = command_line::get_arg(*vm, arg_wallet_dir); + const auto prompt_for_password = command_line::get_arg(*vm, arg_prompt_for_password); + const auto password_prompt = prompt_for_password ? password_prompter : nullptr; if(!wallet_file.empty() && !from_json.empty()) { @@ -2114,13 +2400,13 @@ int main(int argc, char** argv) { LOG_PRINT_L0(tools::wallet_rpc_server::tr("Loading wallet...")); if(!wallet_file.empty()) { - wal = tools::wallet2::make_from_file(*vm, wallet_file, password_prompter).first; + wal = tools::wallet2::make_from_file(*vm, wallet_file, password_prompt).first; } else { try { - wal = tools::wallet2::make_from_json(*vm, from_json, password_prompter); + wal = tools::wallet2::make_from_json(*vm, from_json, password_prompt); } catch (const std::exception &e) { diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h index 2a51ddf46..9455c4769 100644 --- a/src/wallet/wallet_rpc_server.h +++ b/src/wallet/wallet_rpc_server.h @@ -80,6 +80,7 @@ namespace tools MAP_JON_RPC_WE("sweep_dust", on_sweep_dust, wallet_rpc::COMMAND_RPC_SWEEP_DUST) MAP_JON_RPC_WE("sweep_all", on_sweep_all, wallet_rpc::COMMAND_RPC_SWEEP_ALL) MAP_JON_RPC_WE("sweep_single", on_sweep_single, wallet_rpc::COMMAND_RPC_SWEEP_SINGLE) + MAP_JON_RPC_WE("relay_tx", on_relay_tx, wallet_rpc::COMMAND_RPC_RELAY_TX) MAP_JON_RPC_WE("store", on_store, wallet_rpc::COMMAND_RPC_STORE) MAP_JON_RPC_WE("get_payments", on_get_payments, wallet_rpc::COMMAND_RPC_GET_PAYMENTS) MAP_JON_RPC_WE("get_bulk_payments", on_get_bulk_payments, wallet_rpc::COMMAND_RPC_GET_BULK_PAYMENTS) @@ -93,6 +94,12 @@ namespace tools MAP_JON_RPC_WE("get_tx_notes", on_get_tx_notes, wallet_rpc::COMMAND_RPC_GET_TX_NOTES) MAP_JON_RPC_WE("set_attribute", on_set_attribute, wallet_rpc::COMMAND_RPC_SET_ATTRIBUTE) MAP_JON_RPC_WE("get_attribute", on_get_attribute, wallet_rpc::COMMAND_RPC_GET_ATTRIBUTE) + MAP_JON_RPC_WE("get_tx_key", on_get_tx_key, wallet_rpc::COMMAND_RPC_GET_TX_KEY) + MAP_JON_RPC_WE("check_tx_key", on_check_tx_key, wallet_rpc::COMMAND_RPC_CHECK_TX_KEY) + MAP_JON_RPC_WE("get_tx_proof", on_get_tx_proof, wallet_rpc::COMMAND_RPC_GET_TX_PROOF) + MAP_JON_RPC_WE("check_tx_proof", on_check_tx_proof, wallet_rpc::COMMAND_RPC_CHECK_TX_PROOF) + MAP_JON_RPC_WE("get_spend_proof", on_get_spend_proof, wallet_rpc::COMMAND_RPC_GET_SPEND_PROOF) + MAP_JON_RPC_WE("check_spend_proof", on_check_spend_proof, wallet_rpc::COMMAND_RPC_CHECK_SPEND_PROOF) MAP_JON_RPC_WE("get_transfers", on_get_transfers, wallet_rpc::COMMAND_RPC_GET_TRANSFERS) MAP_JON_RPC_WE("get_transfer_by_txid", on_get_transfer_by_txid, wallet_rpc::COMMAND_RPC_GET_TRANSFER_BY_TXID) MAP_JON_RPC_WE("sign", on_sign, wallet_rpc::COMMAND_RPC_SIGN) @@ -128,6 +135,7 @@ namespace tools 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); bool on_sweep_all(const wallet_rpc::COMMAND_RPC_SWEEP_ALL::request& req, wallet_rpc::COMMAND_RPC_SWEEP_ALL::response& res, epee::json_rpc::error& er); bool on_sweep_single(const wallet_rpc::COMMAND_RPC_SWEEP_SINGLE::request& req, wallet_rpc::COMMAND_RPC_SWEEP_SINGLE::response& res, epee::json_rpc::error& er); + bool on_relay_tx(const wallet_rpc::COMMAND_RPC_RELAY_TX::request& req, wallet_rpc::COMMAND_RPC_RELAY_TX::response& res, epee::json_rpc::error& er); bool on_make_integrated_address(const wallet_rpc::COMMAND_RPC_MAKE_INTEGRATED_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_MAKE_INTEGRATED_ADDRESS::response& res, epee::json_rpc::error& er); bool on_split_integrated_address(const wallet_rpc::COMMAND_RPC_SPLIT_INTEGRATED_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_SPLIT_INTEGRATED_ADDRESS::response& res, epee::json_rpc::error& er); bool on_store(const wallet_rpc::COMMAND_RPC_STORE::request& req, wallet_rpc::COMMAND_RPC_STORE::response& res, epee::json_rpc::error& er); @@ -140,6 +148,12 @@ namespace tools bool on_get_tx_notes(const wallet_rpc::COMMAND_RPC_GET_TX_NOTES::request& req, wallet_rpc::COMMAND_RPC_GET_TX_NOTES::response& res, epee::json_rpc::error& er); bool on_set_attribute(const wallet_rpc::COMMAND_RPC_SET_ATTRIBUTE::request& req, wallet_rpc::COMMAND_RPC_SET_ATTRIBUTE::response& res, epee::json_rpc::error& er); bool on_get_attribute(const wallet_rpc::COMMAND_RPC_GET_ATTRIBUTE::request& req, wallet_rpc::COMMAND_RPC_GET_ATTRIBUTE::response& res, epee::json_rpc::error& er); + bool on_get_tx_key(const wallet_rpc::COMMAND_RPC_GET_TX_KEY::request& req, wallet_rpc::COMMAND_RPC_GET_TX_KEY::response& res, epee::json_rpc::error& er); + bool on_check_tx_key(const wallet_rpc::COMMAND_RPC_CHECK_TX_KEY::request& req, wallet_rpc::COMMAND_RPC_CHECK_TX_KEY::response& res, epee::json_rpc::error& er); + bool on_get_tx_proof(const wallet_rpc::COMMAND_RPC_GET_TX_PROOF::request& req, wallet_rpc::COMMAND_RPC_GET_TX_PROOF::response& res, epee::json_rpc::error& er); + bool on_check_tx_proof(const wallet_rpc::COMMAND_RPC_CHECK_TX_PROOF::request& req, wallet_rpc::COMMAND_RPC_CHECK_TX_PROOF::response& res, epee::json_rpc::error& er); + bool on_get_spend_proof(const wallet_rpc::COMMAND_RPC_GET_SPEND_PROOF::request& req, wallet_rpc::COMMAND_RPC_GET_SPEND_PROOF::response& res, epee::json_rpc::error& er); + bool on_check_spend_proof(const wallet_rpc::COMMAND_RPC_CHECK_SPEND_PROOF::request& req, wallet_rpc::COMMAND_RPC_CHECK_SPEND_PROOF::response& res, epee::json_rpc::error& er); bool on_get_transfers(const wallet_rpc::COMMAND_RPC_GET_TRANSFERS::request& req, wallet_rpc::COMMAND_RPC_GET_TRANSFERS::response& res, epee::json_rpc::error& er); bool on_get_transfer_by_txid(const wallet_rpc::COMMAND_RPC_GET_TRANSFER_BY_TXID::request& req, wallet_rpc::COMMAND_RPC_GET_TRANSFER_BY_TXID::response& res, epee::json_rpc::error& er); bool on_sign(const wallet_rpc::COMMAND_RPC_SIGN::request& req, wallet_rpc::COMMAND_RPC_SIGN::response& res, epee::json_rpc::error& er); @@ -167,7 +181,6 @@ namespace tools void fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &txid, const tools::wallet2::unconfirmed_transfer_details &pd); void fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &payment_id, const tools::wallet2::pool_payment_details &pd); bool not_open(epee::json_rpc::error& er); - uint64_t adjust_mixin(uint64_t mixin); void handle_rpc_exception(const std::exception_ptr& e, epee::json_rpc::error& er, int default_error_code); wallet2 *m_wallet; diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h index 9bcc5138a..e084d9e6d 100644 --- a/src/wallet/wallet_rpc_server_commands_defs.h +++ b/src/wallet/wallet_rpc_server_commands_defs.h @@ -291,6 +291,7 @@ namespace wallet_rpc bool get_tx_key; bool do_not_relay; bool get_tx_hex; + bool get_tx_metadata; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(destinations) @@ -303,6 +304,7 @@ namespace wallet_rpc KV_SERIALIZE(get_tx_key) KV_SERIALIZE_OPT(do_not_relay, false) KV_SERIALIZE_OPT(get_tx_hex, false) + KV_SERIALIZE_OPT(get_tx_metadata, false) END_KV_SERIALIZE_MAP() }; @@ -313,6 +315,7 @@ namespace wallet_rpc std::list<std::string> amount_keys; uint64_t fee; std::string tx_blob; + std::string tx_metadata; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(tx_hash) @@ -320,6 +323,7 @@ namespace wallet_rpc KV_SERIALIZE(amount_keys) KV_SERIALIZE(fee) KV_SERIALIZE(tx_blob) + KV_SERIALIZE(tx_metadata) END_KV_SERIALIZE_MAP() }; }; @@ -338,6 +342,7 @@ namespace wallet_rpc bool get_tx_keys; bool do_not_relay; bool get_tx_hex; + bool get_tx_metadata; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(destinations) @@ -350,6 +355,7 @@ namespace wallet_rpc KV_SERIALIZE(get_tx_keys) KV_SERIALIZE_OPT(do_not_relay, false) KV_SERIALIZE_OPT(get_tx_hex, false) + KV_SERIALIZE_OPT(get_tx_metadata, false) END_KV_SERIALIZE_MAP() }; @@ -369,6 +375,7 @@ namespace wallet_rpc std::list<uint64_t> amount_list; std::list<uint64_t> fee_list; std::list<std::string> tx_blob_list; + std::list<std::string> tx_metadata_list; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(tx_hash_list) @@ -376,6 +383,7 @@ namespace wallet_rpc KV_SERIALIZE(amount_list) KV_SERIALIZE(fee_list) KV_SERIALIZE(tx_blob_list) + KV_SERIALIZE(tx_metadata_list) END_KV_SERIALIZE_MAP() }; }; @@ -387,11 +395,13 @@ namespace wallet_rpc bool get_tx_keys; bool do_not_relay; bool get_tx_hex; + bool get_tx_metadata; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(get_tx_keys) KV_SERIALIZE_OPT(do_not_relay, false) KV_SERIALIZE_OPT(get_tx_hex, false) + KV_SERIALIZE_OPT(get_tx_metadata, false) END_KV_SERIALIZE_MAP() }; @@ -410,12 +420,14 @@ namespace wallet_rpc std::list<std::string> tx_key_list; std::list<uint64_t> fee_list; std::list<std::string> tx_blob_list; + std::list<std::string> tx_metadata_list; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(tx_hash_list) KV_SERIALIZE(tx_key_list) KV_SERIALIZE(fee_list) KV_SERIALIZE(tx_blob_list) + KV_SERIALIZE(tx_metadata_list) END_KV_SERIALIZE_MAP() }; }; @@ -435,6 +447,7 @@ namespace wallet_rpc uint64_t below_amount; bool do_not_relay; bool get_tx_hex; + bool get_tx_metadata; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(address) @@ -448,6 +461,7 @@ namespace wallet_rpc KV_SERIALIZE(below_amount) KV_SERIALIZE_OPT(do_not_relay, false) KV_SERIALIZE_OPT(get_tx_hex, false) + KV_SERIALIZE_OPT(get_tx_metadata, false) END_KV_SERIALIZE_MAP() }; @@ -466,12 +480,14 @@ namespace wallet_rpc std::list<std::string> tx_key_list; std::list<uint64_t> fee_list; std::list<std::string> tx_blob_list; + std::list<std::string> tx_metadata_list; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(tx_hash_list) KV_SERIALIZE(tx_key_list) KV_SERIALIZE(fee_list) KV_SERIALIZE(tx_blob_list) + KV_SERIALIZE(tx_metadata_list) END_KV_SERIALIZE_MAP() }; }; @@ -489,6 +505,7 @@ namespace wallet_rpc std::string key_image; bool do_not_relay; bool get_tx_hex; + bool get_tx_metadata; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(address) @@ -500,6 +517,36 @@ namespace wallet_rpc KV_SERIALIZE(key_image) KV_SERIALIZE_OPT(do_not_relay, false) KV_SERIALIZE_OPT(get_tx_hex, false) + KV_SERIALIZE_OPT(get_tx_metadata, false) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::string tx_hash; + std::string tx_key; + uint64_t fee; + std::string tx_blob; + std::string tx_metadata; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(tx_hash) + KV_SERIALIZE(tx_key) + KV_SERIALIZE(fee) + KV_SERIALIZE(tx_blob) + KV_SERIALIZE(tx_metadata) + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_RELAY_TX + { + struct request + { + std::string hex; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(hex) END_KV_SERIALIZE_MAP() }; @@ -828,6 +875,114 @@ namespace wallet_rpc }; }; + struct COMMAND_RPC_GET_TX_KEY + { + struct request + { + std::string txid; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(txid) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::string tx_key; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(tx_key) + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_CHECK_TX_KEY + { + struct request + { + std::string txid; + std::string tx_key; + std::string address; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(txid) + KV_SERIALIZE(tx_key) + KV_SERIALIZE(address) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + uint64_t received; + bool in_pool; + uint64_t confirmations; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(received) + KV_SERIALIZE(in_pool) + KV_SERIALIZE(confirmations) + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_GET_TX_PROOF + { + struct request + { + std::string txid; + std::string address; + std::string message; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(txid) + KV_SERIALIZE(address) + KV_SERIALIZE(message) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::string signature; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(signature) + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_CHECK_TX_PROOF + { + struct request + { + std::string txid; + std::string address; + std::string message; + std::string signature; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(txid) + KV_SERIALIZE(address) + KV_SERIALIZE(message) + KV_SERIALIZE(signature) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + bool good; + uint64_t received; + bool in_pool; + uint64_t confirmations; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(good) + KV_SERIALIZE(received) + KV_SERIALIZE(in_pool) + KV_SERIALIZE(confirmations) + END_KV_SERIALIZE_MAP() + }; + }; + struct transfer_entry { std::string txid; @@ -859,6 +1014,54 @@ namespace wallet_rpc END_KV_SERIALIZE_MAP() }; + struct COMMAND_RPC_GET_SPEND_PROOF + { + struct request + { + std::string txid; + std::string message; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(txid) + KV_SERIALIZE(message) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::string signature; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(signature) + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_CHECK_SPEND_PROOF + { + struct request + { + std::string txid; + std::string message; + std::string signature; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(txid) + KV_SERIALIZE(message) + KV_SERIALIZE(signature) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + bool good; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(good) + END_KV_SERIALIZE_MAP() + }; + }; + struct COMMAND_RPC_GET_TRANSFERS { struct request diff --git a/src/wallet/wallet_rpc_server_error_codes.h b/src/wallet/wallet_rpc_server_error_codes.h index e74e9110b..c3f3e20d1 100644 --- a/src/wallet/wallet_rpc_server_error_codes.h +++ b/src/wallet/wallet_rpc_server_error_codes.h @@ -54,3 +54,7 @@ #define WALLET_RPC_ERROR_CODE_WALLET_ALREADY_EXISTS -21 #define WALLET_RPC_ERROR_CODE_INVALID_PASSWORD -22 #define WALLET_RPC_ERROR_CODE_NO_WALLET_DIR -23 +#define WALLET_RPC_ERROR_CODE_NO_TXKEY -24 +#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 diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 762eee776..d0c89db46 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -87,7 +87,9 @@ add_subdirectory(unit_tests) add_subdirectory(difficulty) add_subdirectory(hash) add_subdirectory(net_load_tests) -add_subdirectory(libwallet_api_tests) +if (BUILD_GUI_DEPS) + add_subdirectory(libwallet_api_tests) +endif() # add_subdirectory(daemon_tests) diff --git a/tests/libwallet_api_tests/CMakeLists.txt b/tests/libwallet_api_tests/CMakeLists.txt index 4c5542d91..7da0d113e 100644 --- a/tests/libwallet_api_tests/CMakeLists.txt +++ b/tests/libwallet_api_tests/CMakeLists.txt @@ -39,6 +39,7 @@ add_executable(libwallet_api_tests target_link_libraries(libwallet_api_tests PRIVATE + wallet_api wallet version epee diff --git a/tests/libwallet_api_tests/main.cpp b/tests/libwallet_api_tests/main.cpp index 3434cd530..25fa547f8 100644 --- a/tests/libwallet_api_tests/main.cpp +++ b/tests/libwallet_api_tests/main.cpp @@ -30,7 +30,7 @@ #include "gtest/gtest.h" -#include "wallet/wallet2_api.h" +#include "wallet/api/wallet2_api.h" #include "wallet/wallet2.h" #include "include_base_utils.h" #include "common/util.h" diff --git a/tests/unit_tests/main.cpp b/tests/unit_tests/main.cpp index 86e7bcef3..95ea67410 100644 --- a/tests/unit_tests/main.cpp +++ b/tests/unit_tests/main.cpp @@ -52,7 +52,7 @@ int main(int argc, char** argv) ::testing::InitGoogleTest(&argc, argv); po::options_description desc_options("Command line options"); - const command_line::arg_descriptor<std::string> arg_data_dir = {"data-dir", "Data files directory", "", true}; + const command_line::arg_descriptor<std::string> arg_data_dir = { "data-dir", "Data files directory" }; command_line::add_arg(desc_options, arg_data_dir, ""); po::variables_map vm; @@ -65,7 +65,7 @@ int main(int argc, char** argv) if (! r) return 1; - if (vm["data-dir"].defaulted()) + if (command_line::is_arg_defaulted(vm, arg_data_dir)) unit_test::data_dir = boost::filesystem::canonical(boost::filesystem::path(epee::string_tools::get_current_module_folder())) .parent_path().parent_path().parent_path().parent_path() .append("tests").append("data"); diff --git a/utils/systemd/monerod.service b/utils/systemd/monerod.service index 96e88a2d3..b6b6b6ce6 100644 --- a/utils/systemd/monerod.service +++ b/utils/systemd/monerod.service @@ -14,5 +14,8 @@ PIDFile=/run/monero/monerod.pid ExecStart=/usr/bin/monerod --config-file /etc/monerod.conf \ --detach --pidfile /run/monero/monerod.pid +Restart=always +PrivateTmp=true + [Install] WantedBy=multi-user.target |