diff options
Diffstat (limited to 'src')
53 files changed, 1496 insertions, 835 deletions
diff --git a/src/blockchain_db/blockchain_db.h b/src/blockchain_db/blockchain_db.h index 88034a927..33c3341fa 100644 --- a/src/blockchain_db/blockchain_db.h +++ b/src/blockchain_db/blockchain_db.h @@ -1333,10 +1333,11 @@ public: * @brief get a txpool transaction's metadata * * @param txid the transaction id of the transation to lookup + * @param meta the metadata to return * - * @return the metadata associated with that transaction + * @return true if the tx meta was found, false otherwise */ - virtual txpool_tx_meta_t get_txpool_tx_meta(const crypto::hash& txid) const = 0; + virtual bool get_txpool_tx_meta(const crypto::hash& txid, txpool_tx_meta_t &meta) const = 0; /** * @brief get a txpool transaction's blob diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp index ee4368e86..d19399bec 100644 --- a/src/blockchain_db/lmdb/db_lmdb.cpp +++ b/src/blockchain_db/lmdb/db_lmdb.cpp @@ -1621,7 +1621,7 @@ void BlockchainLMDB::remove_txpool_tx(const crypto::hash& txid) } } -txpool_tx_meta_t BlockchainLMDB::get_txpool_tx_meta(const crypto::hash& txid) const +bool BlockchainLMDB::get_txpool_tx_meta(const crypto::hash& txid, txpool_tx_meta_t &meta) const { LOG_PRINT_L3("BlockchainLMDB::" << __func__); check_open(); @@ -1632,12 +1632,14 @@ txpool_tx_meta_t BlockchainLMDB::get_txpool_tx_meta(const crypto::hash& txid) co MDB_val k = {sizeof(txid), (void *)&txid}; MDB_val v; auto result = mdb_cursor_get(m_cur_txpool_meta, &k, &v, MDB_SET); + if (result == MDB_NOTFOUND) + return false; if (result != 0) throw1(DB_ERROR(lmdb_error("Error finding txpool tx meta: ", result).c_str())); - const txpool_tx_meta_t meta = *(const txpool_tx_meta_t*)v.mv_data; + meta = *(const txpool_tx_meta_t*)v.mv_data; TXN_POSTFIX_RDONLY(); - return meta; + return true; } bool BlockchainLMDB::get_txpool_tx_blob(const crypto::hash& txid, cryptonote::blobdata &bd) const @@ -2623,6 +2625,12 @@ bool BlockchainLMDB::batch_start(uint64_t batch_num_blocks, uint64_t batch_bytes m_batch_active = true; memset(&m_wcursors, 0, sizeof(m_wcursors)); + if (m_tinfo.get()) + { + if (m_tinfo->m_ti_rflags.m_rf_txn) + mdb_txn_reset(m_tinfo->m_ti_rtxn); + memset(&m_tinfo->m_ti_rflags, 0, sizeof(m_tinfo->m_ti_rflags)); + } LOG_PRINT_L3("batch transaction: begin"); return true; @@ -2732,29 +2740,34 @@ void BlockchainLMDB::set_batch_transactions(bool batch_transactions) bool BlockchainLMDB::block_rtxn_start(MDB_txn **mtxn, mdb_txn_cursors **mcur) const { bool ret = false; + mdb_threadinfo *tinfo; if (m_write_txn && m_writer == boost::this_thread::get_id()) { *mtxn = m_write_txn->m_txn; *mcur = (mdb_txn_cursors *)&m_wcursors; return ret; } - if (!m_tinfo.get()) + /* Check for existing info and force reset if env doesn't match - + * only happens if env was opened/closed multiple times in same process + */ + if (!(tinfo = m_tinfo.get()) || mdb_txn_env(tinfo->m_ti_rtxn) != m_env) { - m_tinfo.reset(new mdb_threadinfo); - memset(&m_tinfo->m_ti_rcursors, 0, sizeof(m_tinfo->m_ti_rcursors)); - memset(&m_tinfo->m_ti_rflags, 0, sizeof(m_tinfo->m_ti_rflags)); - if (auto mdb_res = lmdb_txn_begin(m_env, NULL, MDB_RDONLY, &m_tinfo->m_ti_rtxn)) + tinfo = new mdb_threadinfo; + m_tinfo.reset(tinfo); + memset(&tinfo->m_ti_rcursors, 0, sizeof(tinfo->m_ti_rcursors)); + memset(&tinfo->m_ti_rflags, 0, sizeof(tinfo->m_ti_rflags)); + if (auto mdb_res = lmdb_txn_begin(m_env, NULL, MDB_RDONLY, &tinfo->m_ti_rtxn)) throw0(DB_ERROR_TXN_START(lmdb_error("Failed to create a read transaction for the db: ", mdb_res).c_str())); ret = true; - } else if (!m_tinfo->m_ti_rflags.m_rf_txn) + } else if (!tinfo->m_ti_rflags.m_rf_txn) { - if (auto mdb_res = lmdb_txn_renew(m_tinfo->m_ti_rtxn)) + if (auto mdb_res = lmdb_txn_renew(tinfo->m_ti_rtxn)) throw0(DB_ERROR_TXN_START(lmdb_error("Failed to renew a read transaction for the db: ", mdb_res).c_str())); ret = true; } if (ret) - m_tinfo->m_ti_rflags.m_rf_txn = true; - *mtxn = m_tinfo->m_ti_rtxn; - *mcur = &m_tinfo->m_ti_rcursors; + tinfo->m_ti_rflags.m_rf_txn = true; + *mtxn = tinfo->m_ti_rtxn; + *mcur = &tinfo->m_ti_rcursors; if (ret) LOG_PRINT_L3("BlockchainLMDB::" << __func__); @@ -2772,28 +2785,9 @@ void BlockchainLMDB::block_txn_start(bool readonly) { if (readonly) { - bool didit = false; - if (m_write_txn && m_writer == boost::this_thread::get_id()) - return; - if (!m_tinfo.get()) - { - m_tinfo.reset(new mdb_threadinfo); - memset(&m_tinfo->m_ti_rcursors, 0, sizeof(m_tinfo->m_ti_rcursors)); - memset(&m_tinfo->m_ti_rflags, 0, sizeof(m_tinfo->m_ti_rflags)); - if (auto mdb_res = lmdb_txn_begin(m_env, NULL, MDB_RDONLY, &m_tinfo->m_ti_rtxn)) - throw0(DB_ERROR_TXN_START(lmdb_error("Failed to create a read transaction for the db: ", mdb_res).c_str())); - didit = true; - } else if (!m_tinfo->m_ti_rflags.m_rf_txn) - { - if (auto mdb_res = lmdb_txn_renew(m_tinfo->m_ti_rtxn)) - throw0(DB_ERROR_TXN_START(lmdb_error("Failed to renew a read transaction for the db: ", mdb_res).c_str())); - didit = true; - } - if (didit) - { - m_tinfo->m_ti_rflags.m_rf_txn = true; - LOG_PRINT_L3("BlockchainLMDB::" << __func__ << " RO"); - } + MDB_txn *mtxn; + mdb_txn_cursors *mcur; + block_rtxn_start(&mtxn, &mcur); return; } @@ -2818,6 +2812,12 @@ void BlockchainLMDB::block_txn_start(bool readonly) throw0(DB_ERROR_TXN_START(lmdb_error("Failed to create a transaction for the db: ", mdb_res).c_str())); } memset(&m_wcursors, 0, sizeof(m_wcursors)); + if (m_tinfo.get()) + { + if (m_tinfo->m_ti_rflags.m_rf_txn) + mdb_txn_reset(m_tinfo->m_ti_rtxn); + memset(&m_tinfo->m_ti_rflags, 0, sizeof(m_tinfo->m_ti_rflags)); + } } else if (m_writer != boost::this_thread::get_id()) throw0(DB_ERROR_TXN_START((std::string("Attempted to start new write txn when batch txn already exists in ")+__FUNCTION__).c_str())); } diff --git a/src/blockchain_db/lmdb/db_lmdb.h b/src/blockchain_db/lmdb/db_lmdb.h index 85b62b5db..ecd14f11b 100644 --- a/src/blockchain_db/lmdb/db_lmdb.h +++ b/src/blockchain_db/lmdb/db_lmdb.h @@ -246,7 +246,7 @@ public: virtual uint64_t get_txpool_tx_count(bool include_unrelayed_txes = true) const; virtual bool txpool_has_tx(const crypto::hash &txid) const; virtual void remove_txpool_tx(const crypto::hash& txid); - virtual txpool_tx_meta_t get_txpool_tx_meta(const crypto::hash& txid) const; + virtual bool get_txpool_tx_meta(const crypto::hash& txid, txpool_tx_meta_t &meta) const; virtual bool get_txpool_tx_blob(const crypto::hash& txid, cryptonote::blobdata &bd) const; virtual cryptonote::blobdata get_txpool_tx_blob(const crypto::hash& txid) const; virtual bool for_all_txpool_txes(std::function<bool(const crypto::hash&, const txpool_tx_meta_t&, const cryptonote::blobdata*)> f, bool include_blob = false, bool include_unrelayed_txes = true) const; diff --git a/src/blockchain_utilities/CMakeLists.txt b/src/blockchain_utilities/CMakeLists.txt index bd32e0c55..6c55e8d2d 100644 --- a/src/blockchain_utilities/CMakeLists.txt +++ b/src/blockchain_utilities/CMakeLists.txt @@ -29,9 +29,9 @@ set(blocksdat "") if(PER_BLOCK_CHECKPOINT) if(APPLE) - add_custom_command(OUTPUT blocksdat.o MAIN_DEPENDENCY ../blocks/checkpoints.dat COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && touch stub.c && ${CMAKE_C_COMPILER} -o stub.o -c stub.c COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && ${CMAKE_LINKER} -r -sectcreate __DATA __blocks_dat ../blocks/checkpoints.dat -o ${CMAKE_CURRENT_BINARY_DIR}/blocksdat.o stub.o && rm -f stub.*) + add_custom_command(OUTPUT blocksdat.o MAIN_DEPENDENCY ../blocks/checkpoints.dat COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && touch stub.c && ${CMAKE_C_COMPILER} -o stub.o -c stub.c COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && ${CMAKE_LINKER} ${LD_RAW_FLAGS} -r -sectcreate __DATA __blocks_dat ../blocks/checkpoints.dat -o ${CMAKE_CURRENT_BINARY_DIR}/blocksdat.o stub.o && rm -f stub.*) else() - add_custom_command(OUTPUT blocksdat.o MAIN_DEPENDENCY ../blocks/checkpoints.dat COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && cp ../blocks/checkpoints.dat blocks.dat && ${CMAKE_LINKER} -r -b binary -o ${CMAKE_CURRENT_BINARY_DIR}/blocksdat.o blocks.dat && rm -f blocks.dat) + add_custom_command(OUTPUT blocksdat.o MAIN_DEPENDENCY ../blocks/checkpoints.dat COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && cp ../blocks/checkpoints.dat blocks.dat && ${CMAKE_LINKER} ${LD_RAW_FLAGS} -r -b binary -o ${CMAKE_CURRENT_BINARY_DIR}/blocksdat.o blocks.dat && rm -f blocks.dat) endif() set(blocksdat "blocksdat.o") endif() diff --git a/src/blockchain_utilities/blockchain_import.cpp b/src/blockchain_utilities/blockchain_import.cpp index 758deb7e4..edb8881e0 100644 --- a/src/blockchain_utilities/blockchain_import.cpp +++ b/src/blockchain_utilities/blockchain_import.cpp @@ -594,8 +594,8 @@ int main(int argc, char* argv[]) const command_line::arg_descriptor<std::string> arg_database = { "database", available_dbs.c_str(), default_db_type }; - const command_line::arg_descriptor<bool> arg_verify = {"verify", - "Verify blocks and transactions during import", true}; + const command_line::arg_descriptor<bool> arg_verify = {"guard-against-pwnage", + "Verify blocks and transactions during import (only disable if you exported the file yourself)", true}; const command_line::arg_descriptor<bool> arg_batch = {"batch", "Batch transactions for faster import", true}; const command_line::arg_descriptor<bool> arg_resume = {"resume", diff --git a/src/blocks/CMakeLists.txt b/src/blocks/CMakeLists.txt index 3a866af5b..9317d585b 100644 --- a/src/blocks/CMakeLists.txt +++ b/src/blocks/CMakeLists.txt @@ -30,8 +30,8 @@ if(APPLE) add_library(blocks STATIC blockexports.c) set_target_properties(blocks PROPERTIES LINKER_LANGUAGE C) else() - add_custom_command(OUTPUT blocks.o MAIN_DEPENDENCY blocks.dat COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && ${CMAKE_LINKER} -r -b binary -o ${CMAKE_CURRENT_BINARY_DIR}/blocks.o blocks.dat) - add_custom_command(OUTPUT testnet_blocks.o MAIN_DEPENDENCY testnet_blocks.dat COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && ${CMAKE_LINKER} -r -b binary -o ${CMAKE_CURRENT_BINARY_DIR}/testnet_blocks.o testnet_blocks.dat) + add_custom_command(OUTPUT blocks.o MAIN_DEPENDENCY blocks.dat COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && ${CMAKE_LINKER} ${LD_RAW_FLAGS} -r -b binary -o ${CMAKE_CURRENT_BINARY_DIR}/blocks.o blocks.dat) + add_custom_command(OUTPUT testnet_blocks.o MAIN_DEPENDENCY testnet_blocks.dat COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && ${CMAKE_LINKER} ${LD_RAW_FLAGS} -r -b binary -o ${CMAKE_CURRENT_BINARY_DIR}/testnet_blocks.o testnet_blocks.dat) add_library(blocks STATIC blocks.o testnet_blocks.o blockexports.c) set_target_properties(blocks PROPERTIES LINKER_LANGUAGE C) endif() diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 7ad08ea83..7ce0229da 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -35,7 +35,6 @@ set(common_sources download.cpp util.cpp i18n.cpp - memwipe.c password.cpp perf_timer.cpp threadpool.cpp @@ -64,7 +63,6 @@ set(common_private_headers util.h varint.h i18n.h - memwipe.h password.h perf_timer.h stack_trace.h @@ -76,7 +74,8 @@ monero_private_headers(common monero_add_library(common ${common_sources} ${common_headers} - ${common_private_headers}) + ${common_private_headers} + DEPENDS generate_translations_header) target_link_libraries(common PUBLIC epee @@ -92,9 +91,5 @@ target_link_libraries(common ${OPENSSL_LIBRARIES} ${EXTRA_LIBRARIES}) -if(HAVE_C11) -SET_PROPERTY(SOURCE memwipe.c PROPERTY COMPILE_FLAGS -std=c11) -endif() - #monero_install_headers(common # ${common_headers}) diff --git a/src/common/apply_permutation.h b/src/common/apply_permutation.h index 4fd952686..8684f14ce 100644 --- a/src/common/apply_permutation.h +++ b/src/common/apply_permutation.h @@ -30,6 +30,8 @@ // This algorithm is adapted from Raymond Chen's code: // https://blogs.msdn.microsoft.com/oldnewthing/20170109-00/?p=95145 +#pragma once + #include <vector> #include <functional> #include "misc_log_ex.h" diff --git a/src/common/i18n.cpp b/src/common/i18n.cpp index 4a76e76fc..28a186bf0 100644 --- a/src/common/i18n.cpp +++ b/src/common/i18n.cpp @@ -35,6 +35,7 @@ #include "file_io_utils.h" #include "common/util.h" #include "common/i18n.h" +#include "translation_files.h" #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "i18n" @@ -62,6 +63,7 @@ std::string i18n_get_language() e = "en"; std::string language = e; + language = language.substr(0, language.find(".")); std::transform(language.begin(), language.end(), language.begin(), tolower); return language; } @@ -137,25 +139,40 @@ int i18n_set_language(const char *directory, const char *base, std::string langu i18n_log("Loading translations for language " << language); boost::system::error_code ignored_ec; - if (!boost::filesystem::exists(filename, ignored_ec)) { + if (boost::filesystem::exists(filename, ignored_ec)) { + if (!epee::file_io_utils::load_file_to_string(filename, contents)) { + i18n_log("Failed to load translations file: " << filename); + return -1; + } + } else { i18n_log("Translations file not found: " << filename); - const char *underscore = strchr(language.c_str(), '_'); - if (underscore) { - std::string fallback_language = std::string(language, 0, underscore - language.c_str()); - filename = std::string(directory) + "/" + base + "_" + fallback_language + ".qm"; - i18n_log("Not found, loading translations for language " << fallback_language); - if (!boost::filesystem::exists(filename, ignored_ec)) { - i18n_log("Translations file not found: " << filename); + filename = std::string(base) + "_" + language + ".qm"; + if (!find_embedded_file(filename, contents)) { + i18n_log("Embedded translations file not found: " << filename); + const char *underscore = strchr(language.c_str(), '_'); + if (underscore) { + std::string fallback_language = std::string(language, 0, underscore - language.c_str()); + filename = std::string(directory) + "/" + base + "_" + fallback_language + ".qm"; + i18n_log("Loading translations for language " << fallback_language); + if (boost::filesystem::exists(filename, ignored_ec)) { + if (!epee::file_io_utils::load_file_to_string(filename, contents)) { + i18n_log("Failed to load translations file: " << filename); + return -1; + } + } else { + i18n_log("Translations file not found: " << filename); + filename = std::string(base) + "_" + fallback_language + ".qm"; + if (!find_embedded_file(filename, contents)) { + i18n_log("Embedded translations file not found: " << filename); + return -1; + } + } + } else { return -1; } } } - if (!epee::file_io_utils::load_file_to_string(filename, contents)) { - i18n_log("Failed to load translations file: " << filename); - return -1; - } - data = (const unsigned char*)contents.c_str(); datalen = contents.size(); idx = 0; diff --git a/src/common/memwipe.c b/src/common/memwipe.c deleted file mode 100644 index da7e9f346..000000000 --- a/src/common/memwipe.c +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright (c) 2017, The Monero Project -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are -// permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other -// materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific -// prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// Parts of this file Copyright (c) 2009-2015 The Bitcoin Core developers - -#define __STDC_WANT_LIB_EXT1__ 1 -#include <string.h> -#include <stdlib.h> -#ifdef HAVE_EXPLICIT_BZERO -#include <strings.h> -#endif -#include "memwipe.h" - -#if defined(_MSC_VER) -#define SCARECROW \ - __asm; -#else -#define SCARECROW \ - __asm__ __volatile__("" : : "r"(ptr) : "memory"); -#endif - -#ifdef HAVE_MEMSET_S - -void *memwipe(void *ptr, size_t n) -{ - if (memset_s(ptr, n, 0, n)) - { - abort(); - } - SCARECROW // might as well... - return ptr; -} - -#elif defined HAVE_EXPLICIT_BZERO - -void *memwipe(void *ptr, size_t n) -{ - explicit_bzero(ptr, n); - SCARECROW - return ptr; -} - -#else - -/* The memory_cleanse implementation is taken from Bitcoin */ - -/* Compilers have a bad habit of removing "superfluous" memset calls that - * are trying to zero memory. For example, when memset()ing a buffer and - * then free()ing it, the compiler might decide that the memset is - * unobservable and thus can be removed. - * - * Previously we used OpenSSL which tried to stop this by a) implementing - * memset in assembly on x86 and b) putting the function in its own file - * for other platforms. - * - * This change removes those tricks in favour of using asm directives to - * scare the compiler away. As best as our compiler folks can tell, this is - * sufficient and will continue to be so. - * - * Adam Langley <agl@google.com> - * Commit: ad1907fe73334d6c696c8539646c21b11178f20f - * BoringSSL (LICENSE: ISC) - */ -static void memory_cleanse(void *ptr, size_t len) -{ - memset(ptr, 0, len); - - /* As best as we can tell, this is sufficient to break any optimisations that - might try to eliminate "superfluous" memsets. If there's an easy way to - detect memset_s, it would be better to use that. */ - SCARECROW -} - -void *memwipe(void *ptr, size_t n) -{ - memory_cleanse(ptr, n); - SCARECROW - return ptr; -} - -#endif diff --git a/src/common/password.cpp b/src/common/password.cpp index dc0856160..011123300 100644 --- a/src/common/password.cpp +++ b/src/common/password.cpp @@ -46,7 +46,7 @@ #include "readline_buffer.h" #endif -#include "common/memwipe.h" +#include "memwipe.h" namespace { diff --git a/src/common/threadpool.cpp b/src/common/threadpool.cpp index 20c5765b0..5d749e08e 100644 --- a/src/common/threadpool.cpp +++ b/src/common/threadpool.cpp @@ -34,6 +34,8 @@ #include "cryptonote_config.h" #include "common/util.h" +static __thread int depth = 0; + namespace tools { threadpool::threadpool() : running(true), active(0) { @@ -60,11 +62,13 @@ threadpool::~threadpool() { void threadpool::submit(waiter *obj, std::function<void()> f) { entry e = {obj, f}; boost::unique_lock<boost::mutex> lock(mutex); - if (active == max && !queue.empty()) { + if ((active == max && !queue.empty()) || depth > 0) { // if all available threads are already running // and there's work waiting, just run in current thread lock.unlock(); + ++depth; f(); + --depth; } else { if (obj) obj->inc(); @@ -106,7 +110,9 @@ void threadpool::run() { e = queue.front(); queue.pop_front(); lock.unlock(); + ++depth; e.f(); + --depth; if (e.wo) e.wo->dec(); diff --git a/src/common/util.cpp b/src/common/util.cpp index 2a2f50c4f..a4a435104 100644 --- a/src/common/util.cpp +++ b/src/common/util.cpp @@ -39,6 +39,7 @@ #include "wipeable_string.h" using namespace epee; +#include "crypto/crypto.h" #include "util.h" #include "memwipe.h" #include "cryptonote_config.h" diff --git a/src/crypto/CMakeLists.txt b/src/crypto/CMakeLists.txt index fd71a87e7..764b30273 100644 --- a/src/crypto/CMakeLists.txt +++ b/src/crypto/CMakeLists.txt @@ -76,6 +76,7 @@ monero_add_library(cncrypto ${crypto_private_headers}) target_link_libraries(cncrypto PUBLIC + epee ${Boost_SYSTEM_LIBRARY} PRIVATE ${EXTRA_LIBRARIES}) diff --git a/src/crypto/blake256.c b/src/crypto/blake256.c index 1e43f9c4d..95b2a6927 100644 --- a/src/crypto/blake256.c +++ b/src/crypto/blake256.c @@ -157,7 +157,7 @@ void blake256_update(state *S, const uint8_t *data, uint64_t datalen) { int left = S->buflen >> 3; int fill = 64 - left; - if (left && (((datalen >> 3) & 0x3F) >= (unsigned) fill)) { + if (left && (((datalen >> 3)) >= (unsigned) fill)) { memcpy((void *) (S->buf + left), (void *) data, fill); S->t[0] += 512; if (S->t[0] == 0) S->t[1]++; diff --git a/src/crypto/chacha.h b/src/crypto/chacha.h index a9665030d..c11e4aa2f 100644 --- a/src/crypto/chacha.h +++ b/src/crypto/chacha.h @@ -39,7 +39,7 @@ #if defined(__cplusplus) #include <memory.h> -#include "common/memwipe.h" +#include "memwipe.h" #include "hash.h" namespace crypto { diff --git a/src/crypto/crypto.h b/src/crypto/crypto.h index 0ce5e6d7a..a929302c1 100644 --- a/src/crypto/crypto.h +++ b/src/crypto/crypto.h @@ -41,7 +41,7 @@ #include "common/pod-class.h" #include "common/util.h" -#include "common/memwipe.h" +#include "memwipe.h" #include "generic-ops.h" #include "hex.h" #include "span.h" diff --git a/src/cryptonote_basic/cryptonote_basic.h b/src/cryptonote_basic/cryptonote_basic.h index 821c21d84..c81901f4e 100644 --- a/src/cryptonote_basic/cryptonote_basic.h +++ b/src/cryptonote_basic/cryptonote_basic.h @@ -36,7 +36,6 @@ #include <cstring> // memcmp #include <sstream> #include <atomic> -#include "serialization/serialization.h" #include "serialization/variant.h" #include "serialization/vector.h" #include "serialization/binary_archive.h" diff --git a/src/cryptonote_basic/cryptonote_basic_impl.cpp b/src/cryptonote_basic/cryptonote_basic_impl.cpp index 1183fda06..929be0d5a 100644 --- a/src/cryptonote_basic/cryptonote_basic_impl.cpp +++ b/src/cryptonote_basic/cryptonote_basic_impl.cpp @@ -34,7 +34,7 @@ using namespace epee; #include "cryptonote_basic_impl.h" #include "string_tools.h" #include "serialization/binary_utils.h" -#include "serialization/vector.h" +#include "serialization/container.h" #include "cryptonote_format_utils.h" #include "cryptonote_config.h" #include "misc_language.h" diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp index 21fa63842..5f6dc3bd6 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.cpp +++ b/src/cryptonote_basic/cryptonote_format_utils.cpp @@ -35,6 +35,7 @@ using namespace epee; #include <boost/algorithm/string.hpp> #include "wipeable_string.h" #include "string_tools.h" +#include "serialization/string.h" #include "cryptonote_format_utils.h" #include "cryptonote_config.h" #include "crypto/crypto.h" diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 709c5e852..69288a2b7 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -2933,7 +2933,7 @@ bool Blockchain::check_fee(size_t blob_size, uint64_t fee) const needed_fee += (blob_size % 1024) ? 1 : 0; needed_fee *= fee_per_kb; - if (fee < needed_fee * 0.98) // keep a little buffer on acceptance + if (fee < needed_fee - needed_fee / 50) // keep a little 2% buffer on acceptance - no integer overflow { MERROR_VER("transaction fee is not enough: " << print_money(fee) << ", minimum fee: " << print_money(needed_fee)); return false; @@ -4198,9 +4198,9 @@ uint64_t Blockchain::get_txpool_tx_count(bool include_unrelayed_txes) const return m_db->get_txpool_tx_count(include_unrelayed_txes); } -txpool_tx_meta_t Blockchain::get_txpool_tx_meta(const crypto::hash& txid) const +bool Blockchain::get_txpool_tx_meta(const crypto::hash& txid, txpool_tx_meta_t &meta) const { - return m_db->get_txpool_tx_meta(txid); + return m_db->get_txpool_tx_meta(txid, meta); } bool Blockchain::get_txpool_tx_blob(const crypto::hash& txid, cryptonote::blobdata &bd) const diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index 2d5307ac0..25e573a2c 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -914,7 +914,7 @@ namespace cryptonote void update_txpool_tx(const crypto::hash &txid, const txpool_tx_meta_t &meta); void remove_txpool_tx(const crypto::hash &txid); uint64_t get_txpool_tx_count(bool include_unrelayed_txes = true) const; - txpool_tx_meta_t get_txpool_tx_meta(const crypto::hash& txid) const; + bool get_txpool_tx_meta(const crypto::hash& txid, txpool_tx_meta_t &meta) const; bool get_txpool_tx_blob(const crypto::hash& txid, cryptonote::blobdata &bd) const; cryptonote::blobdata get_txpool_tx_blob(const crypto::hash& txid) const; bool for_all_txpool_txes(std::function<bool(const crypto::hash&, const txpool_tx_meta_t&, const cryptonote::blobdata*)>, bool include_blob = false, bool include_unrelayed_txes = true) const; diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp index e6f217463..8773c1f74 100644 --- a/src/cryptonote_core/tx_pool.cpp +++ b/src/cryptonote_core/tx_pool.cpp @@ -371,7 +371,12 @@ namespace cryptonote try { LockedTXN lock(m_blockchain); - txpool_tx_meta_t meta = m_blockchain.get_txpool_tx_meta(id); + txpool_tx_meta_t meta; + if (!m_blockchain.get_txpool_tx_meta(id, meta)) + { + MERROR("Failed to find tx in txpool"); + return false; + } cryptonote::blobdata txblob = m_blockchain.get_txpool_tx_blob(id); if (!parse_and_validate_tx_from_blob(txblob, tx)) { @@ -514,10 +519,13 @@ namespace cryptonote { try { - txpool_tx_meta_t meta = m_blockchain.get_txpool_tx_meta(it->first); - meta.relayed = true; - meta.last_relayed_time = now; - m_blockchain.update_txpool_tx(it->first, meta); + txpool_tx_meta_t meta; + if (m_blockchain.get_txpool_tx_meta(it->first, meta)) + { + meta.relayed = true; + meta.last_relayed_time = now; + m_blockchain.update_txpool_tx(it->first, meta); + } } catch (const std::exception &e) { @@ -696,7 +704,11 @@ namespace cryptonote { try { - meta = m_blockchain.get_txpool_tx_meta(tx_id_hash); + if (!m_blockchain.get_txpool_tx_meta(tx_id_hash, meta)) + { + MERROR("Failed to get tx meta from txpool"); + return false; + } if (!meta.relayed) // Do not include that transaction if in restricted mode and it's not relayed continue; @@ -918,7 +930,13 @@ namespace cryptonote { for (const crypto::hash &txid: it->second) { - txpool_tx_meta_t meta = m_blockchain.get_txpool_tx_meta(txid); + txpool_tx_meta_t meta; + if (!m_blockchain.get_txpool_tx_meta(txid, meta)) + { + MERROR("Failed to find tx meta in txpool"); + // continue, not fatal + continue; + } if (!meta.double_spend_seen) { MDEBUG("Marking " << txid << " as double spending " << itk.k_image); @@ -998,7 +1016,12 @@ namespace cryptonote auto sorted_it = m_txs_by_fee_and_receive_time.begin(); while (sorted_it != m_txs_by_fee_and_receive_time.end()) { - txpool_tx_meta_t meta = m_blockchain.get_txpool_tx_meta(sorted_it->second); + txpool_tx_meta_t meta; + if (!m_blockchain.get_txpool_tx_meta(sorted_it->second, meta)) + { + MERROR(" failed to find tx meta"); + continue; + } LOG_PRINT_L2("Considering " << sorted_it->second << ", size " << meta.blob_size << ", current block size " << total_size << "/" << max_total_size << ", current coinbase " << print_money(best_coinbase)); // Can not exceed maximum block size diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.inl b/src/cryptonote_protocol/cryptonote_protocol_handler.inl index 8aef31a5a..6c350775d 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.inl +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.inl @@ -876,6 +876,8 @@ namespace cryptonote } context.m_remote_blockchain_height = arg.current_blockchain_height; + if (context.m_remote_blockchain_height > m_core.get_target_blockchain_height()) + m_core.set_target_blockchain_height(context.m_remote_blockchain_height); std::vector<crypto::hash> block_hashes; block_hashes.reserve(arg.blocks.size()); @@ -1059,6 +1061,11 @@ skip: num_txs += block_entry.txs.size(); std::vector<tx_verification_context> tvc; m_core.handle_incoming_txs(block_entry.txs, tvc, true, true, false); + if (tvc.size() != block_entry.txs.size()) + { + LOG_ERROR_CCONTEXT("Internal error: tvc.size() != block_entry.txs.size()"); + return true; + } std::list<blobdata>::const_iterator it = block_entry.txs.begin(); for (size_t i = 0; i < tvc.size(); ++i, ++it) { diff --git a/src/daemon/CMakeLists.txt b/src/daemon/CMakeLists.txt index ad84db450..237105d06 100644 --- a/src/daemon/CMakeLists.txt +++ b/src/daemon/CMakeLists.txt @@ -29,9 +29,9 @@ set(blocksdat "") if(PER_BLOCK_CHECKPOINT) if(APPLE) - add_custom_command(OUTPUT blocksdat.o MAIN_DEPENDENCY ../blocks/checkpoints.dat COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && touch stub.c && ${CMAKE_C_COMPILER} -o stub.o -c stub.c COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && ${CMAKE_LINKER} -r -sectcreate __DATA __blocks_dat ../blocks/checkpoints.dat -o ${CMAKE_CURRENT_BINARY_DIR}/blocksdat.o stub.o && rm -f stub.*) + add_custom_command(OUTPUT blocksdat.o MAIN_DEPENDENCY ../blocks/checkpoints.dat COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && touch stub.c && ${CMAKE_C_COMPILER} -o stub.o -c stub.c COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && ${CMAKE_LINKER} ${LD_RAW_FLAGS} -r -sectcreate __DATA __blocks_dat ../blocks/checkpoints.dat -o ${CMAKE_CURRENT_BINARY_DIR}/blocksdat.o stub.o && rm -f stub.*) else() - add_custom_command(OUTPUT blocksdat.o MAIN_DEPENDENCY ../blocks/checkpoints.dat COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && cp ../blocks/checkpoints.dat blocks.dat && ${CMAKE_LINKER} -r -b binary -o ${CMAKE_CURRENT_BINARY_DIR}/blocksdat.o blocks.dat && rm -f blocks.dat) + add_custom_command(OUTPUT blocksdat.o MAIN_DEPENDENCY ../blocks/checkpoints.dat COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && cp ../blocks/checkpoints.dat blocks.dat && ${CMAKE_LINKER} ${LD_RAW_FLAGS} -r -b binary -o ${CMAKE_CURRENT_BINARY_DIR}/blocksdat.o blocks.dat && rm -f blocks.dat) endif() set(blocksdat "blocksdat.o") endif() @@ -102,7 +102,7 @@ target_link_libraries(daemon ${Boost_SYSTEM_LIBRARY} ${CMAKE_THREAD_LIBS_INIT} ${ZMQ_LIB} - ${Readline_LIBRARY} + ${GNU_READLINE_LIBRARY} ${EXTRA_LIBRARIES}) set_property(TARGET daemon PROPERTY diff --git a/src/daemon/main.cpp b/src/daemon/main.cpp index 6ac47fcb2..a47e74c71 100644 --- a/src/daemon/main.cpp +++ b/src/daemon/main.cpp @@ -269,7 +269,7 @@ int main(int argc, char const * argv[]) } else { - std::cerr << "Unknown command" << std::endl; + std::cerr << "Unknown command: " << command.front() << std::endl; return 1; } } diff --git a/src/gen_multisig/CMakeLists.txt b/src/gen_multisig/CMakeLists.txt index ff3c46862..8c534d213 100644 --- a/src/gen_multisig/CMakeLists.txt +++ b/src/gen_multisig/CMakeLists.txt @@ -43,8 +43,8 @@ target_link_libraries(gen_multisig ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_THREAD_LIBRARY} - ${Readline_LIBRARY} ${CMAKE_THREAD_LIBS_INIT} + ${GNU_READLINE_LIBRARY} ${EXTRA_LIBRARIES}) add_dependencies(gen_multisig version) diff --git a/src/mnemonics/CMakeLists.txt b/src/mnemonics/CMakeLists.txt index 5ce2198ae..79964e873 100644 --- a/src/mnemonics/CMakeLists.txt +++ b/src/mnemonics/CMakeLists.txt @@ -57,6 +57,7 @@ monero_add_library(mnemonics ${mnemonics_private_headers}) target_link_libraries(mnemonics PUBLIC + epee easylogging ${Boost_SYSTEM_LIBRARY} PRIVATE diff --git a/src/mnemonics/electrum-words.cpp b/src/mnemonics/electrum-words.cpp index ba67952aa..f44ad40aa 100644 --- a/src/mnemonics/electrum-words.cpp +++ b/src/mnemonics/electrum-words.cpp @@ -410,7 +410,7 @@ namespace crypto std::vector<std::string> words_store; uint32_t word_list_length = word_list.size(); - // 8 bytes -> 3 words. 8 digits base 16 -> 3 digits base 1626 + // 4 bytes -> 3 words. 8 digits base 16 -> 3 digits base 1626 for (unsigned int i=0; i < len/4; i++, words += ' ') { uint32_t w1, w2, w3; diff --git a/src/p2p/CMakeLists.txt b/src/p2p/CMakeLists.txt index 123b0a272..3fc053dc7 100644 --- a/src/p2p/CMakeLists.txt +++ b/src/p2p/CMakeLists.txt @@ -46,5 +46,6 @@ target_link_libraries(p2p ${Boost_FILESYSTEM_LIBRARY} ${Boost_SYSTEM_LIBRARY} ${Boost_THREAD_LIBRARY} + ${Boost_SERIALIZATION_LIBRARY} PRIVATE ${EXTRA_LIBRARIES}) diff --git a/src/ringct/rctTypes.h b/src/ringct/rctTypes.h index 5ea2dcc7c..2df797360 100644 --- a/src/ringct/rctTypes.h +++ b/src/ringct/rctTypes.h @@ -47,7 +47,7 @@ extern "C" { #include "hex.h" #include "span.h" -#include "serialization/serialization.h" +#include "serialization/vector.h" #include "serialization/debug_archive.h" #include "serialization/binary_archive.h" #include "serialization/json_archive.h" diff --git a/src/rpc/CMakeLists.txt b/src/rpc/CMakeLists.txt index 748c6b8c1..19ea93902 100644 --- a/src/rpc/CMakeLists.txt +++ b/src/rpc/CMakeLists.txt @@ -101,6 +101,7 @@ target_link_libraries(rpc_base epee ${Boost_REGEX_LIBRARY} ${Boost_THREAD_LIBRARY} + ${Boost_PROGRAM_OPTIONS_LIBRARY} PRIVATE ${EXTRA_LIBRARIES}) @@ -125,6 +126,7 @@ target_link_libraries(daemon_messages target_link_libraries(daemon_rpc_server LINK_PRIVATE + rpc cryptonote_core cryptonote_protocol daemon_messages diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index a6109cb89..4966b107d 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -106,8 +106,9 @@ namespace cryptonote if (rpc_config->login) http_login.emplace(std::move(rpc_config->login->username), std::move(rpc_config->login->password).password()); + auto rng = [](size_t len, uint8_t *ptr){ return crypto::rand(len, ptr); }; return epee::http_server_impl_base<core_rpc_server, connection_context>::init( - std::move(port), std::move(rpc_config->bind_ip), std::move(rpc_config->access_control_origins), std::move(http_login) + rng, std::move(port), std::move(rpc_config->bind_ip), std::move(rpc_config->access_control_origins), std::move(http_login) ); } //------------------------------------------------------------------------------------------------------------------------------ diff --git a/src/rpc/zmq_server.cpp b/src/rpc/zmq_server.cpp index afdff2328..6f06f4497 100644 --- a/src/rpc/zmq_server.cpp +++ b/src/rpc/zmq_server.cpp @@ -102,7 +102,7 @@ bool ZmqServer::addTCPSocket(std::string address, std::string port) rep_socket.reset(new zmq::socket_t(context, ZMQ_REP)); - rep_socket->setsockopt(ZMQ_RCVTIMEO, DEFAULT_RPC_RECV_TIMEOUT_MS); + rep_socket->setsockopt(ZMQ_RCVTIMEO, &DEFAULT_RPC_RECV_TIMEOUT_MS, sizeof(DEFAULT_RPC_RECV_TIMEOUT_MS)); std::string bind_address = addr_prefix + address + std::string(":") + port; rep_socket->bind(bind_address.c_str()); diff --git a/src/serialization/container.h b/src/serialization/container.h new file mode 100644 index 000000000..978a59d2a --- /dev/null +++ b/src/serialization/container.h @@ -0,0 +1,113 @@ +// 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" + +namespace serialization +{ + namespace detail + { + template <typename Archive, class T> + bool serialize_container_element(Archive& ar, T& e) + { + return ::do_serialize(ar, e); + } + + template <typename Archive> + bool serialize_container_element(Archive& ar, uint32_t& e) + { + ar.serialize_varint(e); + return true; + } + + template <typename Archive> + bool serialize_container_element(Archive& ar, uint64_t& e) + { + ar.serialize_varint(e); + return true; + } + + template <typename C> + void do_reserve(C &c, size_t N) {} + } +} + +template <template <bool> class Archive, typename C> +bool do_serialize_container(Archive<false> &ar, C &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; + } + + ::serialization::detail::do_reserve(v, cnt); + + for (size_t i = 0; i < cnt; i++) { + if (i > 0) + ar.delimit_array(); + typename C::value_type e; + if (!::serialization::detail::serialize_container_element(ar, e)) + return false; + ::serialization::detail::do_add(v, std::move(e)); + if (!ar.stream().good()) + return false; + } + ar.end_array(); + return true; +} + +template <template <bool> class Archive, typename C> +bool do_serialize_container(Archive<true> &ar, C &v) +{ + size_t cnt = v.size(); + ar.begin_array(cnt); + for (auto i = v.begin(); i != v.end(); ++i) + { + if (!ar.stream().good()) + return false; + if (i != v.begin()) + ar.delimit_array(); + if(!::serialization::detail::serialize_container_element(ar, const_cast<typename C::value_type&>(*i))) + return false; + if (!ar.stream().good()) + return false; + } + ar.end_array(); + return true; +} diff --git a/src/common/memwipe.h b/src/serialization/deque.h index c3b4ce8ab..994d3f195 100644 --- a/src/common/memwipe.h +++ b/src/serialization/deque.h @@ -1,4 +1,4 @@ -// Copyright (c) 2017, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -30,55 +30,35 @@ #pragma once -#ifdef __cplusplus -#include <array> +#include <deque> -extern "C" { -#endif +template <template <bool> class Archive, class T> +bool do_serialize(Archive<false> &ar, std::deque<T> &v); +template <template <bool> class Archive, class T> +bool do_serialize(Archive<true> &ar, std::deque<T> &v); -void *memwipe(void *src, size_t n); - -#ifdef __cplusplus -} -#endif - -#ifdef __cplusplus -namespace tools { - - /// Scrubs data in the contained type upon destruction. - /// - /// Primarily useful for making sure that private keys don't stick around in - /// memory after the objects that held them have gone out of scope. - template <class T> - struct scrubbed : public T { - using type = T; - - ~scrubbed() { - scrub(); +namespace serialization +{ + namespace detail + { + template <typename T> + void do_reserve(std::deque<T> &c, size_t N) + { + c.reserve(N); } - /// Destroy the contents of the contained type. - void scrub() { - static_assert(std::is_pod<T>::value, - "T cannot be auto-scrubbed. T must be POD."); - static_assert(std::is_trivially_destructible<T>::value, - "T cannot be auto-scrubbed. T must be trivially destructable."); - memwipe(this, sizeof(T)); + template <typename T> + void do_add(std::deque<T> &c, T &&e) + { + c.emplace_back(std::move(e)); } - }; + } +} - template <class T, size_t N> - using scrubbed_arr = scrubbed<std::array<T, N>>; -} // namespace tools +#include "serialization.h" -// Partial specialization for std::is_pod<tools::scrubbed<T>> so that it can -// pretend to be the containted type in those contexts. -namespace std -{ - template<class t_scrubbee> - struct is_pod<tools::scrubbed<t_scrubbee>> { - static const bool value = is_pod<t_scrubbee>::value; - }; -} +template <template <bool> class Archive, class T> +bool do_serialize(Archive<false> &ar, std::deque<T> &v) { return do_serialize_container(ar, v); } +template <template <bool> class Archive, class T> +bool do_serialize(Archive<true> &ar, std::deque<T> &v) { return do_serialize_container(ar, v); } -#endif // __cplusplus diff --git a/src/serialization/list.h b/src/serialization/list.h index d0fb72163..d725458e7 100644 --- a/src/serialization/list.h +++ b/src/serialization/list.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2015, The Monero Project +// Copyright (c) 2014-2017, The Monero Project // // All rights reserved. // @@ -30,71 +30,29 @@ #pragma once -#include "serialization.h" +#include <list> + +template <template <bool> class Archive, class T> +bool do_serialize(Archive<false> &ar, std::list<T> &v); +template <template <bool> class Archive, class T> +bool do_serialize(Archive<true> &ar, std::list<T> &v); namespace serialization { namespace detail { - template <typename Archive, class T> - bool serialize_list_element(Archive& ar, T& e) - { - return ::do_serialize(ar, e); - } - - template <typename Archive> - bool serialize_list_element(Archive& ar, uint64_t& e) + template <typename T> + void do_add(std::list<T> &c, T &&e) { - ar.serialize_varint(e); - return true; + c.emplace_back(std::move(e)); } } } -template <template <bool> class Archive, class T> -bool do_serialize(Archive<false> &ar, std::list<T> &l) -{ - size_t cnt; - ar.begin_array(cnt); - if (!ar.stream().good()) - return false; - l.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(); - l.push_back(T()); - T &t = l.back(); - if (!::serialization::detail::serialize_list_element(ar, t)) - return false; - if (!ar.stream().good()) - return false; - } - ar.end_array(); - return true; -} +#include "serialization.h" template <template <bool> class Archive, class T> -bool do_serialize(Archive<true> &ar, std::list<T> &l) -{ - size_t cnt = l.size(); - ar.begin_array(cnt); - for (typename std::list<T>::iterator i = l.begin(); i != l.end(); ++i) { - if (!ar.stream().good()) - return false; - if (i != l.begin()) - ar.delimit_array(); - if(!::serialization::detail::serialize_list_element(ar, *i)) - return false; - if (!ar.stream().good()) - return false; - } - ar.end_array(); - return true; -} +bool do_serialize(Archive<false> &ar, std::list<T> &v) { return do_serialize_container(ar, v); } +template <template <bool> class Archive, class T> +bool do_serialize(Archive<true> &ar, std::list<T> &v) { return do_serialize_container(ar, v); } + diff --git a/src/serialization/serialization.h b/src/serialization/serialization.h index 9e23f0791..56496c790 100644 --- a/src/serialization/serialization.h +++ b/src/serialization/serialization.h @@ -63,15 +63,17 @@ struct is_blob_type { typedef boost::false_type type; }; template <class T> struct has_free_serializer { typedef boost::true_type type; }; -/*! \struct is_pair_type +/*! \struct is_basic_type * * \brief a descriptor for dispatching serialize */ template <class T> -struct is_pair_type { typedef boost::false_type type; }; +struct is_basic_type { typedef boost::false_type type; }; template<typename F, typename S> -struct is_pair_type<std::pair<F,S>> { typedef boost::true_type type; }; +struct is_basic_type<std::pair<F,S>> { typedef boost::true_type type; }; +template<> +struct is_basic_type<std::string> { typedef boost::true_type type; }; /*! \struct serializer * @@ -89,7 +91,7 @@ struct is_pair_type<std::pair<F,S>> { typedef boost::true_type type; }; template <class Archive, class T> struct serializer{ static bool serialize(Archive &ar, T &v) { - return serialize(ar, v, typename boost::is_integral<T>::type(), typename is_blob_type<T>::type(), typename is_pair_type<T>::type()); + return serialize(ar, v, typename boost::is_integral<T>::type(), typename is_blob_type<T>::type(), typename is_basic_type<T>::type()); } template<typename A> static bool serialize(Archive &ar, T &v, boost::false_type, boost::true_type, A a) { @@ -361,9 +363,3 @@ namespace serialization { return r && check_stream_state(ar); } } - -#include "string.h" -#include "vector.h" -#include "list.h" -#include "pair.h" -#include "set.h" diff --git a/src/serialization/set.h b/src/serialization/set.h index 54b4eb3ab..e6eff62a9 100644 --- a/src/serialization/set.h +++ b/src/serialization/set.h @@ -30,98 +30,29 @@ #pragma once -#include "serialization.h" +#include <set> 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) + template <typename T> + void do_add(std::set<T> &c, 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; + c.insert(std::move(e)); } } } -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; -} +#include "serialization.h" template <template <bool> class Archive, class T> -bool do_serialize(Archive<false> &ar, std::set<T> &v) { return do_serialize_set(ar, v); } +bool do_serialize(Archive<false> &ar, std::set<T> &v) { return do_serialize_container(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); } +bool do_serialize(Archive<true> &ar, std::set<T> &v) { return do_serialize_container(ar, v); } + diff --git a/src/serialization/unordered_set.h b/src/serialization/unordered_set.h new file mode 100644 index 000000000..b277f0c4a --- /dev/null +++ b/src/serialization/unordered_set.h @@ -0,0 +1,58 @@ +// 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 <set> + +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 T> + void do_add(std::unordered_set<T> &c, T &&e) + { + c.insert(std::move(e)); + } + } +} + +#include "serialization.h" + +template <template <bool> class Archive, class T> +bool do_serialize(Archive<false> &ar, std::unordered_set<T> &v) { return do_serialize_container(ar, v); } +template <template <bool> class Archive, class T> +bool do_serialize(Archive<true> &ar, std::unordered_set<T> &v) { return do_serialize_container(ar, v); } + diff --git a/src/serialization/vector.h b/src/serialization/vector.h index 12fd59558..9cf3d8272 100644 --- a/src/serialization/vector.h +++ b/src/serialization/vector.h @@ -30,6 +30,7 @@ #pragma once +#include <vector> #include "serialization.h" template <template <bool> class Archive, class T> @@ -37,91 +38,28 @@ bool do_serialize(Archive<false> &ar, std::vector<T> &v); template <template <bool> class Archive, class T> bool do_serialize(Archive<true> &ar, std::vector<T> &v); -template <template <bool> class Archive, class T> -bool do_serialize(Archive<false> &ar, std::deque<T> &v); -template <template <bool> class Archive, class T> -bool do_serialize(Archive<true> &ar, std::deque<T> &v); - namespace serialization { namespace detail { - template <typename Archive, class T> - bool serialize_vector_element(Archive& ar, T& e) - { - return ::do_serialize(ar, e); - } - - template <typename Archive> - bool serialize_vector_element(Archive& ar, uint32_t& e) + template <typename T> + void do_reserve(std::vector<T> &c, size_t N) { - ar.serialize_varint(e); - return true; + c.reserve(N); } - template <typename Archive> - bool serialize_vector_element(Archive& ar, uint64_t& e) + template <typename T> + void do_add(std::vector<T> &c, T &&e) { - ar.serialize_varint(e); - return true; + c.emplace_back(std::move(e)); } } } -template <template <bool> class Archive, class T> -bool do_serialize_vd(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; - } - - v.reserve(cnt); - for (size_t i = 0; i < cnt; i++) { - if (i > 0) - ar.delimit_array(); - v.resize(i+1); - if (!::serialization::detail::serialize_vector_element(ar, v[i])) - return false; - if (!ar.stream().good()) - return false; - } - ar.end_array(); - return true; -} - -template <template <bool> class Archive, class T> -bool do_serialize_vd(Archive<true> &ar, T &v) -{ - size_t cnt = v.size(); - ar.begin_array(cnt); - for (size_t i = 0; i < cnt; i++) { - if (!ar.stream().good()) - return false; - if (i > 0) - ar.delimit_array(); - if(!::serialization::detail::serialize_vector_element(ar, v[i])) - return false; - if (!ar.stream().good()) - return false; - } - ar.end_array(); - return true; -} +#include "container.h" template <template <bool> class Archive, class T> -bool do_serialize(Archive<false> &ar, std::vector<T> &v) { return do_serialize_vd(ar, v); } +bool do_serialize(Archive<false> &ar, std::vector<T> &v) { return do_serialize_container(ar, v); } template <template <bool> class Archive, class T> -bool do_serialize(Archive<true> &ar, std::vector<T> &v) { return do_serialize_vd(ar, v); } +bool do_serialize(Archive<true> &ar, std::vector<T> &v) { return do_serialize_container(ar, v); } -template <template <bool> class Archive, class T> -bool do_serialize(Archive<false> &ar, std::deque<T> &v) { return do_serialize_vd(ar, v); } -template <template <bool> class Archive, class T> -bool do_serialize(Archive<true> &ar, std::deque<T> &v) { return do_serialize_vd(ar, v); } diff --git a/src/simplewallet/CMakeLists.txt b/src/simplewallet/CMakeLists.txt index beaacf0e9..f190ada8d 100644 --- a/src/simplewallet/CMakeLists.txt +++ b/src/simplewallet/CMakeLists.txt @@ -55,8 +55,8 @@ target_link_libraries(simplewallet ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_THREAD_LIBRARY} - ${Readline_LIBRARY} ${CMAKE_THREAD_LIBS_INIT} + ${GNU_READLINE_LIBRARY} ${EXTRA_LIBRARIES}) set_property(TARGET simplewallet PROPERTY diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index bc3b3e926..79f619ab8 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -121,6 +121,7 @@ namespace const command_line::arg_descriptor<std::string> arg_mnemonic_language = {"mnemonic-language", sw::tr("Language for mnemonic"), ""}; const command_line::arg_descriptor<std::string> arg_electrum_seed = {"electrum-seed", sw::tr("Specify Electrum seed for wallet recovery/creation"), ""}; const command_line::arg_descriptor<bool> arg_restore_deterministic_wallet = {"restore-deterministic-wallet", sw::tr("Recover wallet using Electrum-style mnemonic seed"), false}; + const command_line::arg_descriptor<bool> arg_restore_multisig_wallet = {"restore-multisig-wallet", sw::tr("Recover multisig wallet using Electrum-style mnemonic seed"), false}; const command_line::arg_descriptor<bool> arg_non_deterministic = {"non-deterministic", sw::tr("Create non-deterministic view and spend keys"), false}; const command_line::arg_descriptor<bool> arg_trusted_daemon = {"trusted-daemon", sw::tr("Enable commands which rely on a trusted daemon"), false}; const command_line::arg_descriptor<bool> arg_allow_mismatched_daemon_version = {"allow-mismatched-daemon-version", sw::tr("Allow communicating with a daemon that uses a different RPC version"), false}; @@ -516,48 +517,55 @@ bool simple_wallet::spendkey(const std::vector<std::string> &args/* = std::vecto bool simple_wallet::print_seed(bool encrypted) { bool success = false; - std::string electrum_words; + std::string seed; + bool ready, multisig; - if (m_wallet->multisig()) - { - fail_msg_writer() << tr("wallet is multisig and has no seed"); - return true; - } if (m_wallet->watch_only()) { fail_msg_writer() << tr("wallet is watch-only and has no seed"); return true; } if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } - if (m_wallet->is_deterministic()) - { - if (m_wallet->get_seed_language().empty()) - { - std::string mnemonic_language = get_mnemonic_language(); - if (mnemonic_language.empty()) - return true; - m_wallet->set_seed_language(mnemonic_language); - } - epee::wipeable_string seed_pass; - if (encrypted) + multisig = m_wallet->multisig(&ready); + if (multisig) + { + if (!ready) { - auto pwd_container = tools::password_container::prompt(true, tr("Enter optional seed encryption passphrase, empty to see raw seed")); - if (std::cin.eof() || !pwd_container) - return true; - seed_pass = pwd_container->password(); + fail_msg_writer() << tr("wallet is multisig but not yet finalized"); + return true; } + } + else if (!m_wallet->is_deterministic()) + { + fail_msg_writer() << tr("wallet is non-deterministic and has no seed"); + return true; + } - success = m_wallet->get_seed(electrum_words, seed_pass); + epee::wipeable_string seed_pass; + if (encrypted) + { +#ifdef HAVE_READLINE + rdln::suspend_readline pause_readline; +#endif + auto pwd_container = tools::password_container::prompt(true, tr("Enter optional seed encryption passphrase, empty to see raw seed")); + if (std::cin.eof() || !pwd_container) + return true; + seed_pass = pwd_container->password(); } + if (multisig) + success = m_wallet->get_multisig_seed(seed, seed_pass); + else if (m_wallet->is_deterministic()) + success = m_wallet->get_seed(seed, seed_pass); + if (success) { - print_seed(electrum_words); + print_seed(seed); } else { - fail_msg_writer() << tr("wallet is non-deterministic and has no seed"); + fail_msg_writer() << tr("Failed to retrieve seed"); } return true; } @@ -1682,6 +1690,16 @@ simple_wallet::simple_wallet() 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("get_reserve_proof", + boost::bind(&simple_wallet::get_reserve_proof, this, _1), + tr("get_reserve_proof (all|<amount>) [<message>]"), + tr("Generate a signature proving that you own at least this much, optionally with a challenge string <message>.\n" + "If 'all' is specified, you prove the entire sum of all of your existing accounts' balances.\n" + "Otherwise, you prove the reserve of the smallest possible amount above <amount> available in your current account.")); + m_cmd_binder.set_handler("check_reserve_proof", + boost::bind(&simple_wallet::check_reserve_proof, this, _1), + tr("check_reserve_proof <address> <signature_file> [<message>]"), + tr("Check a signature proving that the owner of <address> holds at least this much, 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>]]"), @@ -1762,11 +1780,11 @@ simple_wallet::simple_wallet() tr("Turn this wallet into a multisig wallet, extra step for N-1/N wallets")); m_cmd_binder.set_handler("export_multisig_info", boost::bind(&simple_wallet::export_multisig, this, _1), - tr("export_multisig <filename>"), + tr("export_multisig_info <filename>"), tr("Export multisig info for other participants")); m_cmd_binder.set_handler("import_multisig_info", boost::bind(&simple_wallet::import_multisig, this, _1), - tr("import_multisig <filename> [<filename>...]"), + tr("import_multisig_info <filename> [<filename>...]"), tr("Import multisig info from other participants")); m_cmd_binder.set_handler("sign_multisig", boost::bind(&simple_wallet::sign_multisig, this, _1), @@ -1778,7 +1796,7 @@ simple_wallet::simple_wallet() tr("Submit a signed multisig transaction from a file")); m_cmd_binder.set_handler("export_raw_multisig_tx", boost::bind(&simple_wallet::export_raw_multisig, this, _1), - tr("export_raw_multisig <filename>"), + tr("export_raw_multisig_tx <filename>"), tr("Export a signed multisig transaction to a file")); m_cmd_binder.set_handler("help", boost::bind(&simple_wallet::help, this, _1), @@ -1984,6 +2002,8 @@ static bool might_be_partial_seed(std::string words) //---------------------------------------------------------------------------------------------------- bool simple_wallet::init(const boost::program_options::variables_map& vm) { + std::string multisig_keys; + if (!handle_command_line(vm)) return false; @@ -2001,49 +2021,91 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) { std::string old_language; // check for recover flag. if present, require electrum word list (only recovery option for now). - if (m_restore_deterministic_wallet) + if (m_restore_deterministic_wallet || m_restore_multisig_wallet) { if (m_non_deterministic) { - fail_msg_writer() << tr("can't specify both --restore-deterministic-wallet and --non-deterministic"); + fail_msg_writer() << tr("can't specify both --restore-deterministic-wallet or --restore-multisig-wallet and --non-deterministic"); return false; } if (!m_wallet_file.empty()) { - fail_msg_writer() << tr("--restore-deterministic-wallet uses --generate-new-wallet, not --wallet-file"); + if (m_restore_multisig_wallet) + fail_msg_writer() << tr("--restore-multisig-wallet uses --generate-new-wallet, not --wallet-file"); + else + fail_msg_writer() << tr("--restore-deterministic-wallet uses --generate-new-wallet, not --wallet-file"); return false; } if (m_electrum_seed.empty()) { - m_electrum_seed = ""; - do + if (m_restore_multisig_wallet) { - const char *prompt = m_electrum_seed.empty() ? "Specify Electrum seed: " : "Electrum seed continued: "; - std::string electrum_seed = input_line(prompt); - if (std::cin.eof()) - return false; - if (electrum_seed.empty()) + const char *prompt = "Specify multisig seed: "; + m_electrum_seed = input_line(prompt); + if (std::cin.eof()) + return false; + if (m_electrum_seed.empty()) + { + fail_msg_writer() << tr("specify a recovery parameter with the --electrum-seed=\"multisig seed here\""); + return false; + } + } + else + { + m_electrum_seed = ""; + do { - fail_msg_writer() << tr("specify a recovery parameter with the --electrum-seed=\"words list here\""); - return false; - } - m_electrum_seed += electrum_seed + " "; - } while (might_be_partial_seed(m_electrum_seed)); + const char *prompt = m_electrum_seed.empty() ? "Specify Electrum seed: " : "Electrum seed continued: "; + std::string electrum_seed = input_line(prompt); + if (std::cin.eof()) + return false; + if (electrum_seed.empty()) + { + fail_msg_writer() << tr("specify a recovery parameter with the --electrum-seed=\"words list here\""); + return false; + } + m_electrum_seed += electrum_seed + " "; + } while (might_be_partial_seed(m_electrum_seed)); + } } - if (!crypto::ElectrumWords::words_to_bytes(m_electrum_seed, m_recovery_key, old_language)) + if (m_restore_multisig_wallet) { - fail_msg_writer() << tr("Electrum-style word list failed verification"); - return false; + if (!epee::string_tools::parse_hexstr_to_binbuff(m_electrum_seed, multisig_keys)) + { + fail_msg_writer() << tr("Multisig seed failed verification"); + return false; + } + } + else + { + if (!crypto::ElectrumWords::words_to_bytes(m_electrum_seed, m_recovery_key, old_language)) + { + fail_msg_writer() << tr("Electrum-style word list failed verification"); + return false; + } } +#ifdef HAVE_READLINE + rdln::suspend_readline pause_readline; +#endif auto pwd_container = tools::password_container::prompt(false, tr("Enter seed encryption passphrase, empty if none")); if (std::cin.eof() || !pwd_container) return false; epee::wipeable_string seed_pass = pwd_container->password(); if (!seed_pass.empty()) - m_recovery_key = cryptonote::decrypt_key(m_recovery_key, seed_pass); + { + if (m_restore_multisig_wallet) + { + crypto::secret_key key; + crypto::cn_slow_hash(seed_pass.data(), seed_pass.size(), (crypto::hash&)key); + sc_reduce32((unsigned char*)key.data); + multisig_keys = m_wallet->decrypt(multisig_keys, key, true); + } + else + m_recovery_key = cryptonote::decrypt_key(m_recovery_key, seed_pass); + } } if (!m_generate_from_view_key.empty()) { @@ -2354,7 +2416,11 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) return false; } m_wallet_file = m_generate_new; - bool r = new_wallet(vm, m_recovery_key, m_restore_deterministic_wallet, m_non_deterministic, old_language); + bool r; + if (m_restore_multisig_wallet) + r = new_wallet(vm, multisig_keys, old_language); + else + r = new_wallet(vm, m_recovery_key, m_restore_deterministic_wallet, m_non_deterministic, old_language); CHECK_AND_ASSERT_MES(r, false, tr("account creation failed")); } if (!m_restore_height && m_restoring) @@ -2485,6 +2551,7 @@ bool simple_wallet::handle_command_line(const boost::program_options::variables_ m_mnemonic_language = command_line::get_arg(vm, arg_mnemonic_language); m_electrum_seed = command_line::get_arg(vm, arg_electrum_seed); m_restore_deterministic_wallet = command_line::get_arg(vm, arg_restore_deterministic_wallet); + m_restore_multisig_wallet = command_line::get_arg(vm, arg_restore_multisig_wallet); m_non_deterministic = command_line::get_arg(vm, arg_non_deterministic); m_trusted_daemon = command_line::get_arg(vm, arg_trusted_daemon); m_allow_mismatched_daemon_version = command_line::get_arg(vm, arg_allow_mismatched_daemon_version); @@ -2495,7 +2562,8 @@ bool simple_wallet::handle_command_line(const boost::program_options::variables_ !m_generate_from_keys.empty() || !m_generate_from_multisig_keys.empty() || !m_generate_from_json.empty() || - m_restore_deterministic_wallet; + m_restore_deterministic_wallet || + m_restore_multisig_wallet; return true; } @@ -2695,6 +2763,49 @@ bool simple_wallet::new_wallet(const boost::program_options::variables_map& vm, return true; } //---------------------------------------------------------------------------------------------------- +bool simple_wallet::new_wallet(const boost::program_options::variables_map& vm, + const std::string &multisig_keys, const std::string &old_language) +{ + auto rc = tools::wallet2::make_new(vm, password_prompter); + m_wallet = std::move(rc.first); + if (!m_wallet) + { + return false; + } + + std::string mnemonic_language = old_language; + + std::vector<std::string> language_list; + crypto::ElectrumWords::get_language_list(language_list); + if (mnemonic_language.empty() && std::find(language_list.begin(), language_list.end(), m_mnemonic_language) != language_list.end()) + { + mnemonic_language = m_mnemonic_language; + } + + m_wallet->set_seed_language(mnemonic_language); + + try + { + m_wallet->generate(m_wallet_file, std::move(rc.second).password(), multisig_keys); + bool ready; + uint32_t threshold, total; + if (!m_wallet->multisig(&ready, &threshold, &total) || !ready) + { + fail_msg_writer() << tr("failed to generate new mutlisig wallet"); + return false; + } + message_writer(console_color_white, true) << boost::format(tr("Generated new %u/%u multisig wallet: ")) % threshold % total + << m_wallet->get_account().get_public_address_str(m_wallet->testnet()); + } + catch (const std::exception& e) + { + fail_msg_writer() << tr("failed to generate new wallet: ") << e.what(); + return false; + } + + return true; +} +//---------------------------------------------------------------------------------------------------- bool simple_wallet::open_wallet(const boost::program_options::variables_map& vm) { if (!tools::wallet2::wallet_valid_path_format(m_wallet_file)) @@ -3206,6 +3317,13 @@ bool simple_wallet::show_incoming_transfers(const std::vector<std::string>& args { if (!parse_subaddress_indices(local_args[0], subaddr_indices)) return true; + local_args.erase(local_args.begin()); + } + + if (local_args.size() > 0) + { + fail_msg_writer() << tr("usage: incoming_transfers [available|unavailable] [verbose] [index=<N>]"); + return true; } tools::wallet2::transfer_container transfers; @@ -4427,6 +4545,12 @@ bool simple_wallet::sweep_below(const std::vector<std::string> &args_) //---------------------------------------------------------------------------------------------------- bool simple_wallet::donate(const std::vector<std::string> &args_) { + if(m_wallet->testnet()) + { + fail_msg_writer() << tr("donations are not enabled on the testnet"); + return true; + } + std::vector<std::string> local_args = args_; if(local_args.empty() || local_args.size() > 5) { @@ -5011,6 +5135,110 @@ bool simple_wallet::check_spend_proof(const std::vector<std::string> &args) return true; } //---------------------------------------------------------------------------------------------------- +bool simple_wallet::get_reserve_proof(const std::vector<std::string> &args) +{ + if(args.size() != 1 && args.size() != 2) { + fail_msg_writer() << tr("usage: get_reserve_proof (all|<amount>) [<message>]"); + return true; + } + + if (m_wallet->watch_only() || m_wallet->multisig()) + { + fail_msg_writer() << tr("The reserve proof can be generated only by a full wallet"); + return true; + } + + boost::optional<std::pair<uint32_t, uint64_t>> account_minreserve; + if (args[0] != "all") + { + account_minreserve = std::pair<uint32_t, uint64_t>(); + account_minreserve->first = m_current_subaddress_account; + if (!cryptonote::parse_amount(account_minreserve->second, args[0])) + { + fail_msg_writer() << tr("amount is wrong: ") << args[0]; + return true; + } + } + + if (!try_connect_to_daemon()) + { + fail_msg_writer() << tr("failed to connect to the daemon"); + return true; + } + + if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } + + LOCK_IDLE_SCOPE(); + + try + { + const std::string sig_str = m_wallet->get_reserve_proof(account_minreserve, args.size() == 2 ? args[1] : ""); + const std::string filename = "monero_reserve_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"); + } + catch (const std::exception &e) + { + fail_msg_writer() << e.what(); + } + return true; +} +//---------------------------------------------------------------------------------------------------- +bool simple_wallet::check_reserve_proof(const std::vector<std::string> &args) +{ + if(args.size() != 2 && args.size() != 3) { + fail_msg_writer() << tr("usage: check_reserve_proof <address> <signature_file> [<message>]"); + return true; + } + + if (!try_connect_to_daemon()) + { + fail_msg_writer() << tr("failed to connect to the daemon"); + return true; + } + + cryptonote::address_parse_info info; + if(!cryptonote::get_account_address_from_str_or_url(info, m_wallet->testnet(), args[0], oa_prompter)) + { + fail_msg_writer() << tr("failed to parse address"); + return true; + } + if (info.is_subaddress) + { + fail_msg_writer() << tr("Address must not be a subaddress"); + return true; + } + + std::string sig_str; + if (!epee::file_io_utils::load_file_to_string(args[1], sig_str)) + { + fail_msg_writer() << tr("failed to load signature file"); + return true; + } + + LOCK_IDLE_SCOPE(); + + try + { + uint64_t total, spent; + if (m_wallet->check_reserve_proof(info.address, args.size() == 3 ? args[2] : "", sig_str, total, spent)) + { + success_msg_writer() << boost::format(tr("Good signature -- total: %s, spent: %s, unspent: %s")) % print_money(total) % print_money(spent) % print_money(total - spent); + } + 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) { char buffer[64]; @@ -6094,6 +6322,7 @@ bool simple_wallet::export_key_images(const std::vector<std::string> &args) try { + LOCK_IDLE_SCOPE(); if (!m_wallet->export_key_images(filename)) { fail_msg_writer() << tr("failed to save file ") << filename; @@ -6126,6 +6355,7 @@ bool simple_wallet::import_key_images(const std::vector<std::string> &args) } std::string filename = args[0]; + LOCK_IDLE_SCOPE(); try { uint64_t spent = 0, unspent = 0; @@ -6157,6 +6387,7 @@ bool simple_wallet::export_outputs(const std::vector<std::string> &args) if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } std::string filename = args[0]; + LOCK_IDLE_SCOPE(); try { std::vector<tools::wallet2::transfer_details> outs = m_wallet->export_outputs(); @@ -6255,6 +6486,7 @@ bool simple_wallet::import_outputs(const std::vector<std::string> &args) boost::archive::binary_iarchive ar(iss); ar >> outputs; } + LOCK_IDLE_SCOPE(); size_t n_outputs = m_wallet->import_outputs(outputs); success_msg_writer() << boost::lexical_cast<std::string>(n_outputs) << " outputs imported"; } @@ -6473,6 +6705,7 @@ int main(int argc, char* argv[]) command_line::add_arg(desc_params, arg_command); command_line::add_arg(desc_params, arg_restore_deterministic_wallet ); + command_line::add_arg(desc_params, arg_restore_multisig_wallet ); command_line::add_arg(desc_params, arg_non_deterministic ); command_line::add_arg(desc_params, arg_electrum_seed ); command_line::add_arg(desc_params, arg_trusted_daemon); @@ -6505,7 +6738,8 @@ int main(int argc, char* argv[]) std::vector<std::string> command = command_line::get_arg(*vm, arg_command); if (!command.empty()) { - w.process_command(command); + if (!w.process_command(command)) + fail_msg_writer() << tr("Unknown command: ") << command.front(); w.stop(); w.deinit(); } diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index e5c00e542..45ed2c32c 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -92,6 +92,8 @@ namespace cryptonote bool recover, bool two_random, const std::string &old_language); bool new_wallet(const boost::program_options::variables_map& vm, const cryptonote::account_public_address& address, const boost::optional<crypto::secret_key>& spendkey, const crypto::secret_key& viewkey); + bool new_wallet(const boost::program_options::variables_map& vm, + const std::string &multisig_keys, const std::string &old_language); bool open_wallet(const boost::program_options::variables_map& vm); bool close_wallet(); @@ -168,6 +170,8 @@ namespace cryptonote 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 get_reserve_proof(const std::vector<std::string> &args); + bool check_reserve_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); @@ -306,6 +310,7 @@ namespace cryptonote crypto::secret_key m_recovery_key; // recovery key (used as random for wallet gen) bool m_restore_deterministic_wallet; // recover flag + bool m_restore_multisig_wallet; // recover flag bool m_non_deterministic; // old 2-random generation bool m_trusted_daemon; bool m_allow_mismatched_daemon_version; diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index fd0b65866..f96640d6e 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -1576,6 +1576,55 @@ bool WalletImpl::checkSpendProof(const std::string &txid_str, const std::string } } +std::string WalletImpl::getReserveProof(bool all, uint32_t account_index, uint64_t amount, const std::string &message) const { + try + { + m_status = Status_Ok; + boost::optional<std::pair<uint32_t, uint64_t>> account_minreserve; + if (!all) + { + account_minreserve = std::make_pair(account_index, amount); + } + return m_wallet->get_reserve_proof(account_minreserve, message); + } + catch (const std::exception &e) + { + m_status = Status_Error; + m_errorString = e.what(); + return ""; + } +} + +bool WalletImpl::checkReserveProof(const std::string &address, const std::string &message, const std::string &signature, bool &good, uint64_t &total, uint64_t &spent) const { + cryptonote::address_parse_info info; + if (!cryptonote::get_account_address_from_str(info, m_wallet->testnet(), address)) + { + m_status = Status_Error; + m_errorString = tr("Failed to parse address"); + return false; + } + if (info.is_subaddress) + { + m_status = Status_Error; + m_errorString = tr("Address must not be a subaddress"); + return false; + } + + good = false; + try + { + m_status = Status_Ok; + good = m_wallet->check_reserve_proof(info.address, message, signature, total, spent); + return true; + } + catch (const std::exception &e) + { + m_status = Status_Error; + m_errorString = e.what(); + return false; + } +} + std::string WalletImpl::signMessage(const std::string &message) { return m_wallet->sign(message); diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h index 01359ffc6..0b9fc851b 100644 --- a/src/wallet/api/wallet.h +++ b/src/wallet/api/wallet.h @@ -142,6 +142,8 @@ public: 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 getReserveProof(bool all, uint32_t account_index, uint64_t amount, const std::string &message) const; + virtual bool checkReserveProof(const std::string &address, const std::string &message, const std::string &signature, bool &good, uint64_t &total, uint64_t &spent) 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/api/wallet2_api.h b/src/wallet/api/wallet2_api.h index ab1a48d6e..acecbb9dd 100644 --- a/src/wallet/api/wallet2_api.h +++ b/src/wallet/api/wallet2_api.h @@ -706,6 +706,12 @@ struct Wallet 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 getReserveProof - Generates a proof that proves the reserve of unspent funds + * Parameters `account_index` and `amount` are ignored when `all` is true + */ + virtual std::string getReserveProof(bool all, uint32_t account_index, uint64_t amount, const std::string &message) const = 0; + virtual bool checkReserveProof(const std::string &address, const std::string &message, const std::string &signature, bool &good, uint64_t &total, uint64_t &spent) const = 0; /* * \brief signMessage - sign a message with the spend private key diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index d6f4b9b98..1549c73b1 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -53,6 +53,7 @@ using namespace epee; #include "profile_tools.h" #include "crypto/crypto.h" #include "serialization/binary_utils.h" +#include "serialization/string.h" #include "cryptonote_basic/blobdatatype.h" #include "mnemonics/electrum-words.h" #include "common/i18n.h" @@ -62,7 +63,7 @@ using namespace epee; #include "rapidjson/writer.h" #include "rapidjson/stringbuffer.h" #include "common/json_util.h" -#include "common/memwipe.h" +#include "memwipe.h" #include "common/base58.h" #include "ringct/rctSigs.h" @@ -610,6 +611,7 @@ wallet2::wallet2(bool testnet, bool restricted): m_min_output_value(0), m_merge_destinations(false), m_confirm_backlog(true), + m_confirm_backlog_threshold(0), m_is_initialized(false), m_restricted(restricted), is_old_file_format(false), @@ -733,6 +735,70 @@ bool wallet2::get_seed(std::string& electrum_words, const epee::wipeable_string return true; } +//---------------------------------------------------------------------------------------------------- +bool wallet2::get_multisig_seed(std::string& seed, const epee::wipeable_string &passphrase, bool raw) const +{ + bool ready; + uint32_t threshold, total; + if (!multisig(&ready, &threshold, &total)) + { + std::cout << "This is not a multisig wallet" << std::endl; + return false; + } + if (!ready) + { + std::cout << "This multisig wallet is not yet finalized" << std::endl; + return false; + } + if (!raw && seed_language.empty()) + { + std::cout << "seed_language not set" << std::endl; + return false; + } + + crypto::secret_key skey; + crypto::public_key pkey; + const account_keys &keys = get_account().get_keys(); + std::string data; + data.append((const char*)&threshold, sizeof(uint32_t)); + data.append((const char*)&total, sizeof(uint32_t)); + skey = keys.m_spend_secret_key; + data.append((const char*)&skey, sizeof(skey)); + pkey = keys.m_account_address.m_spend_public_key; + data.append((const char*)&pkey, sizeof(pkey)); + skey = keys.m_view_secret_key; + data.append((const char*)&skey, sizeof(skey)); + pkey = keys.m_account_address.m_view_public_key; + data.append((const char*)&pkey, sizeof(pkey)); + for (const auto &skey: keys.m_multisig_keys) + data.append((const char*)&skey, sizeof(skey)); + for (const auto &signer: m_multisig_signers) + data.append((const char*)&signer, sizeof(signer)); + + if (!passphrase.empty()) + { + crypto::secret_key key; + crypto::cn_slow_hash(passphrase.data(), passphrase.size(), (crypto::hash&)key); + sc_reduce32((unsigned char*)key.data); + data = encrypt(data, key, true); + } + + if (raw) + { + seed = epee::string_tools::buff_to_hex_nodelimer(data); + } + else + { + if (!crypto::ElectrumWords::bytes_to_words(data.data(), data.size(), seed, seed_language)) + { + std::cout << "Failed to encode seed"; + return false; + } + } + + return true; +} +//---------------------------------------------------------------------------------------------------- /*! * \brief Gets the seed language */ @@ -1970,6 +2036,11 @@ void wallet2::fast_refresh(uint64_t stop_height, uint64_t &blocks_start_height, pull_hashes(0, blocks_start_height, short_chain_history, hashes); if (hashes.size() <= 3) return; + if (blocks_start_height < m_blockchain.offset()) + { + MERROR("Blocks start before blockchain offset: " << blocks_start_height << " " << m_blockchain.offset()); + return; + } if (hashes.size() + current_index < stop_height) { drop_from_short_history(short_chain_history, 3); std::list<crypto::hash>::iterator right = hashes.end(); @@ -2646,6 +2717,97 @@ bool wallet2::verify_password(const std::string& keys_file_name, const epee::wip * \brief Generates a wallet or restores one. * \param wallet_ Name of wallet file * \param password Password of wallet file + * \param multisig_data The multisig restore info and keys + */ +void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& password, + const std::string& multisig_data) +{ + clear(); + prepare_file_names(wallet_); + + if (!wallet_.empty()) + { + boost::system::error_code ignored_ec; + THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_wallet_file, ignored_ec), error::file_exists, m_wallet_file); + THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_keys_file, ignored_ec), error::file_exists, m_keys_file); + } + + m_account.generate(rct::rct2sk(rct::zero()), true, false); + + THROW_WALLET_EXCEPTION_IF(multisig_data.size() < 32, error::invalid_multisig_seed); + size_t offset = 0; + uint32_t threshold = *(uint32_t*)(multisig_data.data() + offset); + offset += sizeof(uint32_t); + uint32_t total = *(uint32_t*)(multisig_data.data() + offset); + offset += sizeof(uint32_t); + THROW_WALLET_EXCEPTION_IF(threshold < 2, error::invalid_multisig_seed); + THROW_WALLET_EXCEPTION_IF(total != threshold && total != threshold + 1, error::invalid_multisig_seed); + const size_t n_multisig_keys = total == threshold ? 1 : threshold; + THROW_WALLET_EXCEPTION_IF(multisig_data.size() != 8 + 32 * (4 + n_multisig_keys + total), error::invalid_multisig_seed); + + std::vector<crypto::secret_key> multisig_keys; + std::vector<crypto::public_key> multisig_signers; + crypto::secret_key spend_secret_key = *(crypto::secret_key*)(multisig_data.data() + offset); + offset += sizeof(crypto::secret_key); + crypto::public_key spend_public_key = *(crypto::public_key*)(multisig_data.data() + offset); + offset += sizeof(crypto::public_key); + crypto::secret_key view_secret_key = *(crypto::secret_key*)(multisig_data.data() + offset); + offset += sizeof(crypto::secret_key); + crypto::public_key view_public_key = *(crypto::public_key*)(multisig_data.data() + offset); + offset += sizeof(crypto::public_key); + for (size_t n = 0; n < n_multisig_keys; ++n) + { + multisig_keys.push_back(*(crypto::secret_key*)(multisig_data.data() + offset)); + offset += sizeof(crypto::secret_key); + } + for (size_t n = 0; n < total; ++n) + { + multisig_signers.push_back(*(crypto::public_key*)(multisig_data.data() + offset)); + offset += sizeof(crypto::public_key); + } + + crypto::public_key calculated_view_public_key; + THROW_WALLET_EXCEPTION_IF(!crypto::secret_key_to_public_key(view_secret_key, calculated_view_public_key), error::invalid_multisig_seed); + THROW_WALLET_EXCEPTION_IF(view_public_key != calculated_view_public_key, error::invalid_multisig_seed); + crypto::public_key local_signer; + THROW_WALLET_EXCEPTION_IF(!crypto::secret_key_to_public_key(spend_secret_key, local_signer), error::invalid_multisig_seed); + THROW_WALLET_EXCEPTION_IF(std::find(multisig_signers.begin(), multisig_signers.end(), local_signer) == multisig_signers.end(), error::invalid_multisig_seed); + rct::key skey = rct::zero(); + for (const auto &msk: multisig_keys) + sc_add(skey.bytes, skey.bytes, rct::sk2rct(msk).bytes); + THROW_WALLET_EXCEPTION_IF(!(rct::rct2sk(skey) == spend_secret_key), error::invalid_multisig_seed); + + m_account.make_multisig(view_secret_key, spend_secret_key, spend_public_key, multisig_keys); + m_account.finalize_multisig(spend_public_key); + + m_account_public_address = m_account.get_keys().m_account_address; + m_watch_only = false; + m_multisig = true; + m_multisig_threshold = threshold; + m_multisig_signers = multisig_signers; + + if (!wallet_.empty()) + { + bool r = store_keys(m_keys_file, password, false); + THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); + + r = file_io_utils::save_string_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_testnet)); + if(!r) MERROR("String with address text not saved"); + } + + cryptonote::block b; + generate_genesis(b); + m_blockchain.push_back(get_block_hash(b)); + add_subaddress_account(tr("Primary account")); + + if (!wallet_.empty()) + store(); +} + +/*! + * \brief Generates a wallet or restores one. + * \param wallet_ Name of wallet file + * \param password Password of wallet file * \param recovery_param If it is a restore, the recovery key * \param recover Whether it is a restore * \param two_random Whether it is a non-deterministic wallet @@ -2821,6 +2983,7 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& cryptonote::block b; generate_genesis(b); m_blockchain.push_back(get_block_hash(b)); + add_subaddress_account(tr("Primary account")); if (!wallet_.empty()) store(); @@ -5677,7 +5840,7 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry if (m_multisig) { crypto::public_key ignore = m_multisig_threshold == m_multisig_signers.size() ? crypto::null_pkey : multisig_signers.front(); - multisig_sigs.push_back({tx.rct_signatures, ignore, used_L, {}, msout}); + multisig_sigs.push_back({tx.rct_signatures, ignore, used_L, std::unordered_set<crypto::public_key>(), msout}); if (m_multisig_threshold < m_multisig_signers.size()) { @@ -5704,7 +5867,7 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, splitted_dsts, unlock_time, m_testnet); THROW_WALLET_EXCEPTION_IF(upper_transaction_size_limit <= get_object_blobsize(tx), error::tx_too_big, tx, upper_transaction_size_limit); THROW_WALLET_EXCEPTION_IF(cryptonote::get_transaction_prefix_hash(ms_tx) != prefix_hash, error::wallet_internal_error, "Multisig txes do not share prefix"); - multisig_sigs.push_back({ms_tx.rct_signatures, multisig_signers[signer_index], new_used_L, {}, msout}); + multisig_sigs.push_back({ms_tx.rct_signatures, multisig_signers[signer_index], new_used_L, std::unordered_set<crypto::public_key>(), msout}); ms_tx.rct_signatures = tx.rct_signatures; THROW_WALLET_EXCEPTION_IF(cryptonote::get_transaction_hash(ms_tx) != cryptonote::get_transaction_hash(tx), error::wallet_internal_error, "Multisig txes differ by more than the signatures"); @@ -6003,7 +6166,8 @@ void wallet2::light_wallet_get_unspent_outs() add_tx_pub_key_to_extra(td.m_tx, tx_pub_key); td.m_key_image = unspent_key_image; - td.m_key_image_known = !m_watch_only; + td.m_key_image_known = !m_watch_only && !m_multisig; + td.m_key_image_partial = m_multisig; td.m_amount = o.amount; td.m_pk_index = 0; td.m_internal_output_index = o.index; @@ -6677,6 +6841,17 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp const size_t estimated_tx_size = estimate_tx_size(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size(), extra.size(), bulletproof); needed_fee = calculate_fee(fee_per_kb, estimated_tx_size, fee_multiplier); + uint64_t inputs = 0, outputs = needed_fee; + for (size_t idx: tx.selected_transfers) inputs += m_transfers[idx].amount(); + for (const auto &o: tx.dsts) outputs += o.amount; + + if (inputs < outputs) + { + LOG_PRINT_L2("We don't have enough for the basic fee, switching to adding_fee"); + adding_fee = true; + goto skip_tx; + } + LOG_PRINT_L2("Trying to create a tx now, with " << tx.dsts.size() << " outputs and " << tx.selected_transfers.size() << " inputs"); if (use_rct) @@ -6752,6 +6927,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp } } +skip_tx: // if unused_*_indices is empty while unused_*_indices_per_subaddr has multiple elements, and if we still have something to pay, // pop front of unused_*_indices_per_subaddr and have unused_*_indices point to the front of unused_*_indices_per_subaddr if ((!dsts.empty() && dsts[0].amount > 0) || adding_fee) @@ -6804,37 +6980,48 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_all(uint64_t below THROW_WALLET_EXCEPTION_IF(unlocked_balance(subaddr_account) == 0, error::wallet_internal_error, "No unlocked balance in the entire wallet"); - std::map<uint32_t, uint64_t> balance_per_subaddr = unlocked_balance_per_subaddress(subaddr_account); - - if (subaddr_indices.empty()) - { - // in case subaddress index wasn't specified, choose non-empty subaddress randomly (with index=0 being chosen last) - if (balance_per_subaddr.count(0) == 1 && balance_per_subaddr.size() > 1) - balance_per_subaddr.erase(0); - auto i = balance_per_subaddr.begin(); - std::advance(i, crypto::rand<size_t>() % balance_per_subaddr.size()); - subaddr_indices.insert(i->first); - } - for (uint32_t i : subaddr_indices) - LOG_PRINT_L2("Spending from subaddress index " << i); + std::map<uint32_t, std::pair<std::vector<size_t>, std::vector<size_t>>> unused_transfer_dust_indices_per_subaddr; - // gather all dust and non-dust outputs of specified subaddress + // gather all dust and non-dust outputs of specified subaddress (if any) and below specified threshold (if any) + bool fund_found = false; for (size_t i = 0; i < m_transfers.size(); ++i) { const transfer_details& td = m_transfers[i]; - if (!td.m_spent && !td.m_key_image_partial && (use_rct ? true : !td.is_rct()) && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1) + if (!td.m_spent && !td.m_key_image_partial && (use_rct ? true : !td.is_rct()) && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && (subaddr_indices.empty() || subaddr_indices.count(td.m_subaddr_index.minor) == 1)) { + fund_found = true; if (below == 0 || td.amount() < below) { if ((td.is_rct()) || is_valid_decomposed_amount(td.amount())) - unused_transfers_indices.push_back(i); + unused_transfer_dust_indices_per_subaddr[td.m_subaddr_index.minor].first.push_back(i); else - unused_dust_indices.push_back(i); + unused_transfer_dust_indices_per_subaddr[td.m_subaddr_index.minor].second.push_back(i); } } } + THROW_WALLET_EXCEPTION_IF(!fund_found, error::wallet_internal_error, "No unlocked balance in the specified subaddress(es)"); + THROW_WALLET_EXCEPTION_IF(unused_transfer_dust_indices_per_subaddr.empty(), error::wallet_internal_error, "The smallest amount found is not below the specified threshold"); - THROW_WALLET_EXCEPTION_IF(unused_transfers_indices.empty() && unused_dust_indices.empty(), error::not_enough_money, 0, 0, 0); // not sure if a new error class (something like 'cant_sweep_empty'?) should be introduced + if (subaddr_indices.empty()) + { + // in case subaddress index wasn't specified, choose non-empty subaddress randomly (with index=0 being chosen last) + if (unused_transfer_dust_indices_per_subaddr.count(0) == 1 && unused_transfer_dust_indices_per_subaddr.size() > 1) + unused_transfer_dust_indices_per_subaddr.erase(0); + auto i = unused_transfer_dust_indices_per_subaddr.begin(); + std::advance(i, crypto::rand<size_t>() % unused_transfer_dust_indices_per_subaddr.size()); + unused_transfers_indices = i->second.first; + unused_dust_indices = i->second.second; + LOG_PRINT_L2("Spending from subaddress index " << i->first); + } + else + { + for (const auto& p : unused_transfer_dust_indices_per_subaddr) + { + unused_transfers_indices.insert(unused_transfers_indices.end(), p.second.first.begin(), p.second.first.end()); + unused_dust_indices.insert(unused_dust_indices.end(), p.second.second.begin(), p.second.second.end()); + LOG_PRINT_L2("Spending from subaddress index " << p.first); + } + } return create_transactions_from(address, is_subaddress, unused_transfers_indices, unused_dust_indices, fake_outs_count, unlock_time, priority, extra, trusted_daemon); } @@ -7761,6 +7948,251 @@ bool wallet2::check_tx_proof(const crypto::hash &txid, const cryptonote::account return false; } +std::string wallet2::get_reserve_proof(const boost::optional<std::pair<uint32_t, uint64_t>> &account_minreserve, const std::string &message) +{ + THROW_WALLET_EXCEPTION_IF(m_watch_only || m_multisig, error::wallet_internal_error, "Reserve proof can only be generated by a full wallet"); + THROW_WALLET_EXCEPTION_IF(balance_all() == 0, error::wallet_internal_error, "Zero balance"); + THROW_WALLET_EXCEPTION_IF(account_minreserve && balance(account_minreserve->first) < account_minreserve->second, error::wallet_internal_error, + "Not enough balance in this account for the requested minimum reserve amount"); + + // determine which outputs to include in the proof + std::vector<size_t> selected_transfers; + for (size_t i = 0; i < m_transfers.size(); ++i) + { + const transfer_details &td = m_transfers[i]; + if (!td.m_spent && (!account_minreserve || account_minreserve->first == td.m_subaddr_index.major)) + selected_transfers.push_back(i); + } + + if (account_minreserve) + { + // minimize the number of outputs included in the proof, by only picking the N largest outputs that can cover the requested min reserve amount + std::sort(selected_transfers.begin(), selected_transfers.end(), [&](const size_t a, const size_t b) + { return m_transfers[a].amount() > m_transfers[b].amount(); }); + while (selected_transfers.size() >= 2 && m_transfers[selected_transfers[1]].amount() >= account_minreserve->second) + selected_transfers.erase(selected_transfers.begin()); + size_t sz = 0; + uint64_t total = 0; + while (total < account_minreserve->second) + { + total += m_transfers[selected_transfers[sz]].amount(); + ++sz; + } + selected_transfers.resize(sz); + } + + // compute signature prefix hash + std::string prefix_data = message; + prefix_data.append((const char*)&m_account.get_keys().m_account_address, sizeof(cryptonote::account_public_address)); + for (size_t i = 0; i < selected_transfers.size(); ++i) + { + prefix_data.append((const char*)&m_transfers[selected_transfers[i]].m_key_image, sizeof(crypto::key_image)); + } + crypto::hash prefix_hash; + crypto::cn_fast_hash(prefix_data.data(), prefix_data.size(), prefix_hash); + + // generate proof entries + std::vector<reserve_proof_entry> proofs(selected_transfers.size()); + std::unordered_set<cryptonote::subaddress_index> subaddr_indices = { {0,0} }; + for (size_t i = 0; i < selected_transfers.size(); ++i) + { + const transfer_details &td = m_transfers[selected_transfers[i]]; + reserve_proof_entry& proof = proofs[i]; + proof.txid = td.m_txid; + proof.index_in_tx = td.m_internal_output_index; + proof.key_image = td.m_key_image; + subaddr_indices.insert(td.m_subaddr_index); + + // get tx pub key + const crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(td.m_tx, td.m_pk_index); + THROW_WALLET_EXCEPTION_IF(tx_pub_key == crypto::null_pkey, error::wallet_internal_error, "The tx public key isn't found"); + const std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(td.m_tx); + + // determine which tx pub key was used for deriving the output key + const crypto::public_key *tx_pub_key_used = &tx_pub_key; + for (int i = 0; i < 2; ++i) + { + proof.shared_secret = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(*tx_pub_key_used), rct::sk2rct(m_account.get_keys().m_view_secret_key))); + crypto::key_derivation derivation; + THROW_WALLET_EXCEPTION_IF(!crypto::generate_key_derivation(proof.shared_secret, rct::rct2sk(rct::I), derivation), + error::wallet_internal_error, "Failed to generate key derivation"); + crypto::public_key subaddress_spendkey; + THROW_WALLET_EXCEPTION_IF(!derive_subaddress_public_key(td.get_public_key(), derivation, proof.index_in_tx, subaddress_spendkey), + error::wallet_internal_error, "Failed to derive subaddress public key"); + if (m_subaddresses.count(subaddress_spendkey) == 1) + break; + THROW_WALLET_EXCEPTION_IF(additional_tx_pub_keys.empty(), error::wallet_internal_error, + "Normal tx pub key doesn't derive the expected output, while the additional tx pub keys are empty"); + THROW_WALLET_EXCEPTION_IF(i == 1, error::wallet_internal_error, + "Neither normal tx pub key nor additional tx pub key derive the expected output key"); + tx_pub_key_used = &additional_tx_pub_keys[proof.index_in_tx]; + } + + // generate signature for shared secret + crypto::generate_tx_proof(prefix_hash, m_account.get_keys().m_account_address.m_view_public_key, *tx_pub_key_used, boost::none, proof.shared_secret, m_account.get_keys().m_view_secret_key, proof.shared_secret_sig); + + // derive ephemeral secret key + crypto::key_image ki; + cryptonote::keypair ephemeral; + const bool r = cryptonote::generate_key_image_helper(m_account.get_keys(), m_subaddresses, td.get_public_key(), tx_pub_key, additional_tx_pub_keys, td.m_internal_output_index, ephemeral, ki); + THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key image"); + THROW_WALLET_EXCEPTION_IF(ephemeral.pub != td.get_public_key(), error::wallet_internal_error, "Derived public key doesn't agree with the stored one"); + + // generate signature for key image + const std::vector<const crypto::public_key*> pubs = { &ephemeral.pub }; + crypto::generate_ring_signature(prefix_hash, td.m_key_image, &pubs[0], 1, ephemeral.sec, 0, &proof.key_image_sig); + } + + // collect all subaddress spend keys that received those outputs and generate their signatures + std::unordered_map<crypto::public_key, crypto::signature> subaddr_spendkeys; + for (const cryptonote::subaddress_index &index : subaddr_indices) + { + crypto::secret_key subaddr_spend_skey = m_account.get_keys().m_spend_secret_key; + if (!index.is_zero()) + { + crypto::secret_key m = cryptonote::get_subaddress_secret_key(m_account.get_keys().m_view_secret_key, index); + crypto::secret_key tmp = subaddr_spend_skey; + sc_add((unsigned char*)&subaddr_spend_skey, (unsigned char*)&m, (unsigned char*)&tmp); + } + crypto::public_key subaddr_spend_pkey; + secret_key_to_public_key(subaddr_spend_skey, subaddr_spend_pkey); + crypto::generate_signature(prefix_hash, subaddr_spend_pkey, subaddr_spend_skey, subaddr_spendkeys[subaddr_spend_pkey]); + } + + // serialize & encode + std::ostringstream oss; + boost::archive::portable_binary_oarchive ar(oss); + ar << proofs << subaddr_spendkeys; + return "ReserveProofV1" + tools::base58::encode(oss.str()); +} + +bool wallet2::check_reserve_proof(const cryptonote::account_public_address &address, const std::string &message, const std::string &sig_str, uint64_t &total, uint64_t &spent) +{ + uint32_t rpc_version; + THROW_WALLET_EXCEPTION_IF(!check_connection(&rpc_version), error::wallet_internal_error, "Failed to connect to daemon: " + get_daemon_address()); + THROW_WALLET_EXCEPTION_IF(rpc_version < MAKE_CORE_RPC_VERSION(1, 0), error::wallet_internal_error, "Daemon RPC version is too old"); + + static constexpr char header[] = "ReserveProofV1"; + THROW_WALLET_EXCEPTION_IF(!boost::string_ref{sig_str}.starts_with(header), error::wallet_internal_error, + "Signature header check error"); + + std::string sig_decoded; + THROW_WALLET_EXCEPTION_IF(!tools::base58::decode(sig_str.substr(std::strlen(header)), sig_decoded), error::wallet_internal_error, + "Signature decoding error"); + + std::istringstream iss(sig_decoded); + boost::archive::portable_binary_iarchive ar(iss); + std::vector<reserve_proof_entry> proofs; + std::unordered_map<crypto::public_key, crypto::signature> subaddr_spendkeys; + ar >> proofs >> subaddr_spendkeys; + + THROW_WALLET_EXCEPTION_IF(subaddr_spendkeys.count(address.m_spend_public_key) == 0, error::wallet_internal_error, + "The given address isn't found in the proof"); + + // compute signature prefix hash + std::string prefix_data = message; + prefix_data.append((const char*)&address, sizeof(cryptonote::account_public_address)); + for (size_t i = 0; i < proofs.size(); ++i) + { + prefix_data.append((const char*)&proofs[i].key_image, sizeof(crypto::key_image)); + } + crypto::hash prefix_hash; + crypto::cn_fast_hash(prefix_data.data(), prefix_data.size(), prefix_hash); + + // fetch txes from daemon + COMMAND_RPC_GET_TRANSACTIONS::request gettx_req; + COMMAND_RPC_GET_TRANSACTIONS::response gettx_res; + for (size_t i = 0; i < proofs.size(); ++i) + gettx_req.txs_hashes.push_back(epee::string_tools::pod_to_hex(proofs[i].txid)); + m_daemon_rpc_mutex.lock(); + bool ok = net_utils::invoke_http_json("/gettransactions", gettx_req, gettx_res, m_http_client); + m_daemon_rpc_mutex.unlock(); + THROW_WALLET_EXCEPTION_IF(!ok || gettx_res.txs.size() != proofs.size(), + error::wallet_internal_error, "Failed to get transaction from daemon"); + + // check spent status + COMMAND_RPC_IS_KEY_IMAGE_SPENT::request kispent_req; + COMMAND_RPC_IS_KEY_IMAGE_SPENT::response kispent_res; + for (size_t i = 0; i < proofs.size(); ++i) + kispent_req.key_images.push_back(epee::string_tools::pod_to_hex(proofs[i].key_image)); + m_daemon_rpc_mutex.lock(); + ok = epee::net_utils::invoke_http_json("/is_key_image_spent", kispent_req, kispent_res, m_http_client, rpc_timeout); + m_daemon_rpc_mutex.unlock(); + THROW_WALLET_EXCEPTION_IF(!ok || kispent_res.spent_status.size() != proofs.size(), + error::wallet_internal_error, "Failed to get key image spent status from daemon"); + + total = spent = 0; + for (size_t i = 0; i < proofs.size(); ++i) + { + const reserve_proof_entry& proof = proofs[i]; + THROW_WALLET_EXCEPTION_IF(gettx_res.txs[i].in_pool, error::wallet_internal_error, "Tx is unconfirmed"); + + cryptonote::blobdata tx_data; + ok = string_tools::parse_hexstr_to_binbuff(gettx_res.txs[i].as_hex, 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 != proof.txid, error::wallet_internal_error, "Failed to get the right transaction from daemon"); + + THROW_WALLET_EXCEPTION_IF(proof.index_in_tx >= tx.vout.size(), error::wallet_internal_error, "index_in_tx is out of bound"); + + const cryptonote::txout_to_key* const out_key = boost::get<cryptonote::txout_to_key>(std::addressof(tx.vout[proof.index_in_tx].target)); + THROW_WALLET_EXCEPTION_IF(!out_key, error::wallet_internal_error, "Output key wasn't found") + + // get tx pub key + const crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(tx); + THROW_WALLET_EXCEPTION_IF(tx_pub_key == crypto::null_pkey, error::wallet_internal_error, "The tx public key isn't found"); + const std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(tx); + + // check singature for shared secret + ok = crypto::check_tx_proof(prefix_hash, address.m_view_public_key, tx_pub_key, boost::none, proof.shared_secret, proof.shared_secret_sig); + if (!ok && additional_tx_pub_keys.size() == tx.vout.size()) + ok = crypto::check_tx_proof(prefix_hash, address.m_view_public_key, additional_tx_pub_keys[proof.index_in_tx], boost::none, proof.shared_secret, proof.shared_secret_sig); + if (!ok) + return false; + + // check signature for key image + const std::vector<const crypto::public_key*> pubs = { &out_key->key }; + ok = crypto::check_ring_signature(prefix_hash, proof.key_image, &pubs[0], 1, &proof.key_image_sig); + if (!ok) + return false; + + // check if the address really received the fund + crypto::key_derivation derivation; + THROW_WALLET_EXCEPTION_IF(!crypto::generate_key_derivation(proof.shared_secret, rct::rct2sk(rct::I), derivation), error::wallet_internal_error, "Failed to generate key derivation"); + crypto::public_key subaddr_spendkey; + crypto::derive_subaddress_public_key(out_key->key, derivation, proof.index_in_tx, subaddr_spendkey); + THROW_WALLET_EXCEPTION_IF(subaddr_spendkeys.count(subaddr_spendkey) == 0, error::wallet_internal_error, + "The address doesn't seem to have received the fund"); + + // check amount + uint64_t amount = tx.vout[proof.index_in_tx].amount; + if (amount == 0) + { + // decode rct + crypto::secret_key shared_secret; + crypto::derivation_to_scalar(derivation, proof.index_in_tx, shared_secret); + rct::ecdhTuple ecdh_info = tx.rct_signatures.ecdhInfo[proof.index_in_tx]; + rct::ecdhDecode(ecdh_info, rct::sk2rct(shared_secret)); + amount = rct::h2d(ecdh_info.amount); + } + total += amount; + if (kispent_res.spent_status[i]) + spent += amount; + } + + // check signatures for all subaddress spend keys + for (const auto &i : subaddr_spendkeys) + { + if (!crypto::check_signature(prefix_hash, i.first, i.second)) + return false; + } + return true; +} + std::string wallet2::get_wallet_file() const { return m_wallet_file; diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index b1115f67b..04d789f18 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -433,9 +433,28 @@ namespace tools bool m_is_subaddress; }; + struct reserve_proof_entry + { + crypto::hash txid; + uint64_t index_in_tx; + crypto::public_key shared_secret; + crypto::key_image key_image; + crypto::signature shared_secret_sig; + crypto::signature key_image_sig; + }; + typedef std::tuple<uint64_t, crypto::public_key, rct::key> get_outs_entry; /*! + * \brief Generates a wallet or restores one. + * \param wallet_ Name of wallet file + * \param password Password of wallet file + * \param multisig_data The multisig restore info and keys + */ + void generate(const std::string& wallet_, const epee::wipeable_string& password, + const std::string& multisig_data); + + /*! * \brief Generates a wallet or restores one. * \param wallet_ Name of wallet file * \param password Password of wallet file @@ -610,6 +629,7 @@ namespace tools bool watch_only() const { return m_watch_only; } bool multisig(bool *ready = NULL, uint32_t *threshold = NULL, uint32_t *total = NULL) const; bool has_multisig_partial_key_images() const; + bool get_multisig_seed(std::string& seed, const epee::wipeable_string &passphrase = std::string(), bool raw = true) const; // locked & unlocked balance of given or current subaddress account uint64_t balance(uint32_t subaddr_index_major) const; @@ -826,6 +846,26 @@ namespace tools 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 Generates a proof that proves the reserve of unspent funds + * \param account_minreserve When specified, collect outputs only belonging to the given account and prove the smallest reserve above the given amount + * When unspecified, proves for all unspent outputs across all accounts + * \param message Arbitrary challenge message to be signed together + * \return Signature string + */ + std::string get_reserve_proof(const boost::optional<std::pair<uint32_t, uint64_t>> &account_minreserve, const std::string &message); + /*! + * \brief Verifies a proof of reserve + * \param address The signer's address + * \param message Challenge message used for signing + * \param sig_str Signature string + * \param total [OUT] the sum of funds included in the signature + * \param spent [OUT] the sum of spent funds included in the signature + * \return true if the signature verifies correctly + */ + bool check_reserve_proof(const cryptonote::account_public_address &address, const std::string &message, const std::string &sig_str, uint64_t &total, uint64_t &spent); + /*! * \brief GUI Address book get/store */ @@ -1109,6 +1149,7 @@ BOOST_CLASS_VERSION(tools::wallet2::pool_payment_details, 1) BOOST_CLASS_VERSION(tools::wallet2::unconfirmed_transfer_details, 7) BOOST_CLASS_VERSION(tools::wallet2::confirmed_transfer_details, 5) BOOST_CLASS_VERSION(tools::wallet2::address_book_row, 17) +BOOST_CLASS_VERSION(tools::wallet2::reserve_proof_entry, 0) BOOST_CLASS_VERSION(tools::wallet2::unsigned_tx_set, 0) BOOST_CLASS_VERSION(tools::wallet2::signed_tx_set, 0) BOOST_CLASS_VERSION(tools::wallet2::tx_construction_data, 2) @@ -1392,6 +1433,17 @@ namespace boost } template <class Archive> + inline void serialize(Archive& a, tools::wallet2::reserve_proof_entry& x, const boost::serialization::version_type ver) + { + a & x.txid; + a & x.index_in_tx; + a & x.shared_secret; + a & x.key_image; + a & x.shared_secret_sig; + a & x.key_image_sig; + } + + template <class Archive> inline void serialize(Archive &a, tools::wallet2::unsigned_tx_set &x, const boost::serialization::version_type ver) { a & x.txes; diff --git a/src/wallet/wallet_errors.h b/src/wallet/wallet_errors.h index 234c22d85..023b53f28 100644 --- a/src/wallet/wallet_errors.h +++ b/src/wallet/wallet_errors.h @@ -60,6 +60,7 @@ namespace tools // file_save_error // invalid_password // invalid_priority + // invalid_multisig_seed // refresh_error * // acc_outs_lookup_error // block_parse_error @@ -266,6 +267,16 @@ namespace tools std::string to_string() const { return wallet_logic_error::to_string(); } }; + struct invalid_multisig_seed : public wallet_logic_error + { + explicit invalid_multisig_seed(std::string&& loc) + : wallet_logic_error(std::move(loc), "invalid multisig seed") + { + } + + std::string to_string() const { return wallet_logic_error::to_string(); } + }; + //---------------------------------------------------------------------------------------------------- struct invalid_pregenerated_random : public wallet_logic_error { diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index f031b765d..3bb69f2be 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -89,6 +89,8 @@ namespace tools //------------------------------------------------------------------------------------------------------------------------------ wallet_rpc_server::~wallet_rpc_server() { + if (m_wallet) + delete m_wallet; } //------------------------------------------------------------------------------------------------------------------------------ void wallet_rpc_server::set_wallet(wallet2 *cr) @@ -229,8 +231,9 @@ namespace tools m_http_client.set_server(walvars->get_daemon_address(), walvars->get_daemon_login()); m_net_server.set_threads_prefix("RPC"); + auto rng = [](size_t len, uint8_t *ptr) { return crypto::rand(len, ptr); }; return epee::http_server_impl_base<wallet_rpc_server, connection_context>::init( - std::move(bind_port), std::move(rpc_config->bind_ip), std::move(rpc_config->access_control_origins), std::move(http_login) + rng, std::move(bind_port), std::move(rpc_config->bind_ip), std::move(rpc_config->access_control_origins), std::move(http_login) ); } //------------------------------------------------------------------------------------------------------------------------------ @@ -255,6 +258,7 @@ namespace tools entry.note = m_wallet->get_tx_note(pd.m_tx_hash); entry.type = "in"; entry.subaddr_index = pd.m_subaddr_index; + entry.address = m_wallet->get_subaddress_as_str(pd.m_subaddr_index); } //------------------------------------------------------------------------------------------------------------------------------ void wallet_rpc_server::fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &txid, const tools::wallet2::confirmed_transfer_details &pd) @@ -280,6 +284,7 @@ namespace tools entry.type = "out"; entry.subaddr_index = { pd.m_subaddr_account, 0 }; + entry.address = m_wallet->get_subaddress_as_str({pd.m_subaddr_account, 0}); } //------------------------------------------------------------------------------------------------------------------------------ void wallet_rpc_server::fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &txid, const tools::wallet2::unconfirmed_transfer_details &pd) @@ -298,6 +303,7 @@ namespace tools entry.note = m_wallet->get_tx_note(txid); entry.type = is_failed ? "failed" : "pending"; entry.subaddr_index = { pd.m_subaddr_account, 0 }; + entry.address = m_wallet->get_subaddress_as_str({pd.m_subaddr_account, 0}); } //------------------------------------------------------------------------------------------------------------------------------ void wallet_rpc_server::fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &payment_id, const tools::wallet2::pool_payment_details &ppd) @@ -316,6 +322,7 @@ namespace tools entry.double_spend_seen = ppd.m_double_spend_seen; entry.type = "pool"; entry.subaddr_index = pd.m_subaddr_index; + entry.address = m_wallet->get_subaddress_as_str(pd.m_subaddr_index); } //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_getbalance(const wallet_rpc::COMMAND_RPC_GET_BALANCE::request& req, wallet_rpc::COMMAND_RPC_GET_BALANCE::response& res, epee::json_rpc::error& er) @@ -662,7 +669,98 @@ namespace tools } return true; } + //------------------------------------------------------------------------------------------------------------------------------ + static std::string ptx_to_string(const tools::wallet2::pending_tx &ptx) + { + std::ostringstream oss; + boost::archive::portable_binary_oarchive ar(oss); + try + { + ar << ptx; + } + catch (...) + { + return ""; + } + return epee::string_tools::buff_to_hex_nodelimer(oss.str()); + } + //------------------------------------------------------------------------------------------------------------------------------ + template<typename T> static bool is_error_value(const T &val) { return false; } + static bool is_error_value(const std::string &s) { return s.empty(); } + //------------------------------------------------------------------------------------------------------------------------------ + template<typename T, typename V> + static bool fill(T &where, V s) + { + if (is_error_value(s)) return false; + where = std::move(s); + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + template<typename T, typename V> + static bool fill(std::list<T> &where, V s) + { + if (is_error_value(s)) return false; + where.emplace_back(std::move(s)); + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + static uint64_t total_amount(const tools::wallet2::pending_tx &ptx) + { + uint64_t amount = 0; + for (const auto &dest: ptx.dests) amount += dest.amount; + return amount; + } + //------------------------------------------------------------------------------------------------------------------------------ + template<typename Ts, typename Tu> + bool wallet_rpc_server::fill_response(std::vector<tools::wallet2::pending_tx> &ptx_vector, + bool get_tx_key, Ts& tx_key, Tu &amount, Tu &fee, std::string &multisig_txset, bool do_not_relay, + Ts &tx_hash, bool get_tx_hex, Ts &tx_blob, bool get_tx_metadata, Ts &tx_metadata, epee::json_rpc::error &er) + { + for (const auto & ptx : ptx_vector) + { + if (get_tx_key) + { + std::string s = epee::string_tools::pod_to_hex(ptx.tx_key); + for (const crypto::secret_key& additional_tx_key : ptx.additional_tx_keys) + s += epee::string_tools::pod_to_hex(additional_tx_key); + fill(tx_key, s); + } + // Compute amount leaving wallet in tx. By convention dests does not include change outputs + fill(amount, total_amount(ptx)); + fill(fee, ptx.fee); + } + if (m_wallet->multisig()) + { + multisig_txset = epee::string_tools::buff_to_hex_nodelimer(m_wallet->save_multisig_tx(ptx_vector)); + if (multisig_txset.empty()) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "Failed to save multisig tx set after creation"; + return false; + } + } + else + { + if (!do_not_relay) + m_wallet->commit_tx(ptx_vector); + + // populate response with tx hashes + for (auto & ptx : ptx_vector) + { + bool r = fill(tx_hash, epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx))); + r = r && (!get_tx_hex || fill(tx_blob, epee::string_tools::buff_to_hex_nodelimer(tx_to_blob(ptx.tx)))); + r = r && (!get_tx_metadata || fill(tx_metadata, ptx_to_string(ptx))); + if (!r) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "Failed to save tx info"; + return false; + } + } + } + return true; + } //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_transfer(const wallet_rpc::COMMAND_RPC_TRANSFER::request& req, wallet_rpc::COMMAND_RPC_TRANSFER::response& res, epee::json_rpc::error& er) { @@ -705,55 +803,8 @@ namespace tools return false; } - 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; - - if (m_wallet->multisig()) - { - res.multisig_txset = epee::string_tools::buff_to_hex_nodelimer(m_wallet->save_multisig_tx(ptx_vector)); - if (res.multisig_txset.empty()) - { - er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; - er.message = "Failed to save multisig tx set after creation"; - return false; - } - } - else - { - if (!req.do_not_relay) - m_wallet->commit_tx(ptx_vector); - - // populate response with tx hash - res.tx_hash = epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx_vector.back().tx)); - if (req.get_tx_hex) - { - cryptonote::blobdata blob; - tx_to_blob(ptx_vector.back().tx, blob); - res.tx_blob = epee::string_tools::buff_to_hex_nodelimer(blob); - } - if (req.get_tx_metadata) - { - std::ostringstream oss; - boost::archive::portable_binary_oarchive ar(oss); - try - { - ar << ptx_vector.back(); - } - catch (...) - { - er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; - er.message = "Failed to save multisig tx set after creation"; - return false; - } - res.tx_metadata = epee::string_tools::buff_to_hex_nodelimer(oss.str()); - } - } - return true; + return fill_response(ptx_vector, req.get_tx_key, res.tx_key, res.amount, res.fee, res.multisig_txset, req.do_not_relay, + res.tx_hash, req.get_tx_hex, res.tx_blob, req.get_tx_metadata, res.tx_metadata, er); } catch (const std::exception& e) { @@ -786,82 +837,12 @@ namespace tools try { uint64_t mixin = m_wallet->adjust_mixin(req.mixin); - uint64_t ptx_amount; - std::vector<wallet2::pending_tx> ptx_vector; LOG_PRINT_L2("on_transfer_split calling create_transactions_2"); - ptx_vector = m_wallet->create_transactions_2(dsts, mixin, req.unlock_time, req.priority, extra, req.account_index, req.subaddr_indices, m_trusted_daemon); + std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_2(dsts, mixin, req.unlock_time, req.priority, extra, req.account_index, req.subaddr_indices, m_trusted_daemon); LOG_PRINT_L2("on_transfer_split called create_transactions_2"); - // populate response with tx hashes - for (const auto & ptx : ptx_vector) - { - 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; - for(auto & dt: ptx.dests) - ptx_amount += dt.amount; - res.amount_list.push_back(ptx_amount); - - res.fee_list.push_back(ptx.fee); - } - - if (m_wallet->multisig()) - { - res.multisig_txset = epee::string_tools::buff_to_hex_nodelimer(m_wallet->save_multisig_tx(ptx_vector)); - if (res.multisig_txset.empty()) - { - er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; - er.message = "Failed to save multisig tx set after creation"; - return false; - } - } - - // populate response with tx hashes - for (const auto & ptx : ptx_vector) - { - if (!req.do_not_relay) - { - LOG_PRINT_L2("on_transfer_split calling commit_tx"); - m_wallet->commit_tx(ptx_vector); - LOG_PRINT_L2("on_transfer_split called commit_tx"); - } - - // populate response with tx hashes - for (auto & ptx : ptx_vector) - { - res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx))); - - if (req.get_tx_hex) - { - cryptonote::blobdata blob; - tx_to_blob(ptx.tx, blob); - res.tx_blob_list.push_back(epee::string_tools::buff_to_hex_nodelimer(blob)); - } - if (req.get_tx_metadata) - { - std::ostringstream oss; - boost::archive::portable_binary_oarchive ar(oss); - try - { - ar << ptx; - } - catch (...) - { - er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; - er.message = "Failed to save multisig tx set after creation"; - return false; - } - res.tx_metadata_list.push_back(epee::string_tools::buff_to_hex_nodelimer(oss.str())); - } - } - } - - return true; + return fill_response(ptx_vector, req.get_tx_keys, res.tx_key_list, res.amount_list, res.fee_list, res.multisig_txset, req.do_not_relay, + res.tx_hash_list, req.get_tx_hex, res.tx_blob_list, req.get_tx_metadata, res.tx_metadata_list, er); } catch (const std::exception& e) { @@ -885,69 +866,8 @@ namespace tools { std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_unmixable_sweep_transactions(m_trusted_daemon); - for (const auto & ptx : ptx_vector) - { - if (req.get_tx_keys) - { - res.tx_key_list.push_back(epee::string_tools::pod_to_hex(ptx.tx_key)); - } - res.fee_list.push_back(ptx.fee); - } - - if (m_wallet->multisig()) - { - for (tools::wallet2::pending_tx &ptx: ptx_vector) - { - std::ostringstream oss; - boost::archive::portable_binary_oarchive ar(oss); - try - { - ar << ptx; - } - catch (...) - { - er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; - er.message = "Failed to save multisig tx set after creation"; - return false; - } - res.multisig_txset.push_back(epee::string_tools::buff_to_hex_nodelimer(oss.str())); - } - } - else - { - if (!req.do_not_relay) - m_wallet->commit_tx(ptx_vector); - - // populate response with tx hashes - for (auto & ptx : ptx_vector) - { - res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx))); - if (req.get_tx_hex) - { - cryptonote::blobdata blob; - tx_to_blob(ptx.tx, blob); - res.tx_blob_list.push_back(epee::string_tools::buff_to_hex_nodelimer(blob)); - } - if (req.get_tx_metadata) - { - std::ostringstream oss; - boost::archive::portable_binary_oarchive ar(oss); - try - { - ar << ptx; - } - catch (...) - { - er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; - er.message = "Failed to save multisig tx set after creation"; - return false; - } - res.tx_metadata_list.push_back(epee::string_tools::buff_to_hex_nodelimer(oss.str())); - } - } - } - - return true; + return fill_response(ptx_vector, req.get_tx_keys, res.tx_key_list, res.amount_list, res.fee_list, res.multisig_txset, req.do_not_relay, + res.tx_hash_list, req.get_tx_hex, res.tx_blob_list, req.get_tx_metadata, res.tx_metadata_list, er); } catch (const std::exception& e) { @@ -985,68 +905,8 @@ namespace tools uint64_t mixin = m_wallet->adjust_mixin(req.mixin); std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_all(req.below_amount, dsts[0].addr, dsts[0].is_subaddress, mixin, req.unlock_time, req.priority, extra, req.account_index, req.subaddr_indices, m_trusted_daemon); - for (const auto & ptx : ptx_vector) - { - if (req.get_tx_keys) - { - res.tx_key_list.push_back(epee::string_tools::pod_to_hex(ptx.tx_key)); - } - } - - if (m_wallet->multisig()) - { - for (tools::wallet2::pending_tx &ptx: ptx_vector) - { - std::ostringstream oss; - boost::archive::portable_binary_oarchive ar(oss); - try - { - ar << ptx; - } - catch (...) - { - er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; - er.message = "Failed to save multisig tx set after creation"; - return false; - } - res.multisig_txset.push_back(epee::string_tools::buff_to_hex_nodelimer(oss.str())); - } - } - else - { - if (!req.do_not_relay) - m_wallet->commit_tx(ptx_vector); - - // populate response with tx hashes - for (auto & ptx : ptx_vector) - { - res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx))); - if (req.get_tx_hex) - { - cryptonote::blobdata blob; - tx_to_blob(ptx.tx, blob); - res.tx_blob_list.push_back(epee::string_tools::buff_to_hex_nodelimer(blob)); - } - if (req.get_tx_metadata) - { - std::ostringstream oss; - boost::archive::portable_binary_oarchive ar(oss); - try - { - ar << ptx; - } - catch (...) - { - er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; - er.message = "Failed to save multisig tx set after creation"; - return false; - } - res.tx_metadata_list.push_back(epee::string_tools::buff_to_hex_nodelimer(oss.str())); - } - } - } - - return true; + return fill_response(ptx_vector, req.get_tx_keys, res.tx_key_list, res.amount_list, res.fee_list, res.multisig_txset, req.do_not_relay, + res.tx_hash_list, req.get_tx_hex, res.tx_blob_list, req.get_tx_metadata, res.tx_metadata_list, er); } catch (const std::exception& e) { @@ -1112,63 +972,12 @@ namespace tools return false; } - if (req.get_tx_key) - { - res.tx_key = epee::string_tools::pod_to_hex(ptx.tx_key); - } - - if (m_wallet->multisig()) - { - res.multisig_txset = epee::string_tools::buff_to_hex_nodelimer(m_wallet->save_multisig_tx(ptx_vector)); - if (res.multisig_txset.empty()) - { - er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; - er.message = "Failed to save multisig tx set after creation"; - return false; - } - } - else - { - if (!req.do_not_relay) - m_wallet->commit_tx(ptx_vector); - - // populate response with tx hashes - res.tx_hash = epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx)); - if (req.get_tx_hex) - { - cryptonote::blobdata blob; - tx_to_blob(ptx.tx, blob); - res.tx_blob = epee::string_tools::buff_to_hex_nodelimer(blob); - } - if (req.get_tx_metadata) - { - std::ostringstream oss; - boost::archive::portable_binary_oarchive ar(oss); - try - { - ar << ptx; - } - catch (...) - { - er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; - er.message = "Failed to save multisig tx set after creation"; - return false; - } - res.tx_metadata = epee::string_tools::buff_to_hex_nodelimer(oss.str()); - } - } - return true; - } - catch (const tools::error::daemon_busy& e) - { - er.code = WALLET_RPC_ERROR_CODE_DAEMON_IS_BUSY; - er.message = e.what(); - return false; + return fill_response(ptx_vector, req.get_tx_key, res.tx_key, res.amount, res.fee, res.multisig_txset, req.do_not_relay, + res.tx_hash, req.get_tx_hex, res.tx_blob, req.get_tx_metadata, res.tx_metadata, er); } catch (const std::exception& e) { - er.code = WALLET_RPC_ERROR_CODE_GENERIC_TRANSFER_ERROR; - er.message = e.what(); + handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_GENERIC_TRANSFER_ERROR); return false; } catch (...) @@ -1916,6 +1725,66 @@ namespace tools return true; } //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_get_reserve_proof(const wallet_rpc::COMMAND_RPC_GET_RESERVE_PROOF::request& req, wallet_rpc::COMMAND_RPC_GET_RESERVE_PROOF::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + + boost::optional<std::pair<uint32_t, uint64_t>> account_minreserve; + if (!req.all) + { + if (req.account_index >= m_wallet->get_num_subaddress_accounts()) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "Account index is out of bound"; + return false; + } + account_minreserve = std::make_pair(req.account_index, req.amount); + } + + try + { + res.signature = m_wallet->get_reserve_proof(account_minreserve, 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_reserve_proof(const wallet_rpc::COMMAND_RPC_CHECK_RESERVE_PROOF::request& req, wallet_rpc::COMMAND_RPC_CHECK_RESERVE_PROOF::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + + 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; + } + if (info.is_subaddress) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "Address must not be a subaddress"; + return false; + } + + try + { + res.good = m_wallet->check_reserve_proof(info.address, req.message, req.signature, res.total, res.spent); + } + 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) { if (!m_wallet) return not_open(er); diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h index b20198b78..9e2e9d216 100644 --- a/src/wallet/wallet_rpc_server.h +++ b/src/wallet/wallet_rpc_server.h @@ -104,6 +104,8 @@ namespace tools 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_reserve_proof", on_get_reserve_proof, wallet_rpc::COMMAND_RPC_GET_RESERVE_PROOF) + MAP_JON_RPC_WE("check_reserve_proof", on_check_reserve_proof, wallet_rpc::COMMAND_RPC_CHECK_RESERVE_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) @@ -170,6 +172,8 @@ namespace tools 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_reserve_proof(const wallet_rpc::COMMAND_RPC_GET_RESERVE_PROOF::request& req, wallet_rpc::COMMAND_RPC_GET_RESERVE_PROOF::response& res, epee::json_rpc::error& er); + bool on_check_reserve_proof(const wallet_rpc::COMMAND_RPC_CHECK_RESERVE_PROOF::request& req, wallet_rpc::COMMAND_RPC_CHECK_RESERVE_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); @@ -207,6 +211,11 @@ namespace tools bool not_open(epee::json_rpc::error& er); void handle_rpc_exception(const std::exception_ptr& e, epee::json_rpc::error& er, int default_error_code); + template<typename Ts, typename Tu> + bool fill_response(std::vector<tools::wallet2::pending_tx> &ptx_vector, + bool get_tx_key, Ts& tx_key, Tu &amount, Tu &fee, std::string &multisig_txset, bool do_not_relay, + Ts &tx_hash, bool get_tx_hex, Ts &tx_blob, bool get_tx_metadata, Ts &tx_metadata, epee::json_rpc::error &er); + wallet2 *m_wallet; std::string m_wallet_dir; tools::private_file rpc_login_file; diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h index 76c02039b..ac25e8e84 100644 --- a/src/wallet/wallet_rpc_server_commands_defs.h +++ b/src/wallet/wallet_rpc_server_commands_defs.h @@ -411,6 +411,7 @@ namespace wallet_rpc std::string tx_hash; std::string tx_key; std::list<std::string> amount_keys; + uint64_t amount; uint64_t fee; std::string tx_blob; std::string tx_metadata; @@ -420,6 +421,7 @@ namespace wallet_rpc KV_SERIALIZE(tx_hash) KV_SERIALIZE(tx_key) KV_SERIALIZE(amount_keys) + KV_SERIALIZE(amount) KV_SERIALIZE(fee) KV_SERIALIZE(tx_blob) KV_SERIALIZE(tx_metadata) @@ -520,14 +522,16 @@ namespace wallet_rpc { std::list<std::string> tx_hash_list; std::list<std::string> tx_key_list; + 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; - std::list<std::string> multisig_txset; + std::string multisig_txset; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(tx_hash_list) KV_SERIALIZE(tx_key_list) + KV_SERIALIZE(amount_list) KV_SERIALIZE(fee_list) KV_SERIALIZE(tx_blob_list) KV_SERIALIZE(tx_metadata_list) @@ -582,14 +586,16 @@ namespace wallet_rpc { std::list<std::string> tx_hash_list; std::list<std::string> tx_key_list; + 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; - std::list<std::string> multisig_txset; + std::string multisig_txset; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(tx_hash_list) KV_SERIALIZE(tx_key_list) + KV_SERIALIZE(amount_list) KV_SERIALIZE(fee_list) KV_SERIALIZE(tx_blob_list) KV_SERIALIZE(tx_metadata_list) @@ -631,6 +637,7 @@ namespace wallet_rpc { std::string tx_hash; std::string tx_key; + uint64_t amount; uint64_t fee; std::string tx_blob; std::string tx_metadata; @@ -639,6 +646,7 @@ namespace wallet_rpc BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(tx_hash) KV_SERIALIZE(tx_key) + KV_SERIALIZE(amount) KV_SERIALIZE(fee) KV_SERIALIZE(tx_blob) KV_SERIALIZE(tx_metadata) @@ -1106,6 +1114,7 @@ namespace wallet_rpc std::string type; uint64_t unlock_time; cryptonote::subaddress_index subaddr_index; + std::string address; bool double_spend_seen; BEGIN_KV_SERIALIZE_MAP() @@ -1120,6 +1129,7 @@ namespace wallet_rpc KV_SERIALIZE(type); KV_SERIALIZE(unlock_time) KV_SERIALIZE(subaddr_index); + KV_SERIALIZE(address); KV_SERIALIZE(double_spend_seen) END_KV_SERIALIZE_MAP() }; @@ -1172,6 +1182,62 @@ namespace wallet_rpc }; }; + struct COMMAND_RPC_GET_RESERVE_PROOF + { + struct request + { + bool all; + uint32_t account_index; // ignored when `all` is true + uint64_t amount; // ignored when `all` is true + std::string message; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(all) + KV_SERIALIZE(account_index) + KV_SERIALIZE(amount) + 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_RESERVE_PROOF + { + struct request + { + std::string address; + std::string message; + std::string signature; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(address) + KV_SERIALIZE(message) + KV_SERIALIZE(signature) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + bool good; + uint64_t total; + uint64_t spent; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(good) + KV_SERIALIZE(total) + KV_SERIALIZE(spent) + END_KV_SERIALIZE_MAP() + }; + }; + struct COMMAND_RPC_GET_TRANSFERS { struct request |