diff options
Diffstat (limited to 'src')
54 files changed, 1023 insertions, 464 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 931bbec4b..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 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/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/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..700eceff3 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.inl +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.inl @@ -273,7 +273,7 @@ namespace cryptonote { if (version < hshd.top_version) MCLOG_RED(el::Level::Warning, "global", context << " peer claims higher version that we think (" << - (unsigned)hshd.top_version << " for " << (hshd.current_height - 1) << "instead of " << (unsigned)version << + (unsigned)hshd.top_version << " for " << (hshd.current_height - 1) << " instead of " << (unsigned)version << ") - we may be forked from the network and a software upgrade may be needed"); return false; } @@ -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/core_rpc_server.h b/src/rpc/core_rpc_server.h index bf4371a4e..82d73728c 100644 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -77,17 +77,25 @@ namespace cryptonote CHAIN_HTTP_TO_MAP2(connection_context); //forward http requests to uri map BEGIN_URI_MAP2() + MAP_URI_AUTO_JON2("/get_height", on_get_height, COMMAND_RPC_GET_HEIGHT) MAP_URI_AUTO_JON2("/getheight", on_get_height, COMMAND_RPC_GET_HEIGHT) + MAP_URI_AUTO_BIN2("/get_blocks.bin", on_get_blocks, COMMAND_RPC_GET_BLOCKS_FAST) MAP_URI_AUTO_BIN2("/getblocks.bin", on_get_blocks, COMMAND_RPC_GET_BLOCKS_FAST) + MAP_URI_AUTO_BIN2("/get_blocks_by_height.bin", on_get_blocks_by_height, COMMAND_RPC_GET_BLOCKS_BY_HEIGHT) MAP_URI_AUTO_BIN2("/getblocks_by_height.bin", on_get_blocks_by_height, COMMAND_RPC_GET_BLOCKS_BY_HEIGHT) + MAP_URI_AUTO_BIN2("/get_hashes.bin", on_get_hashes, COMMAND_RPC_GET_HASHES_FAST) MAP_URI_AUTO_BIN2("/gethashes.bin", on_get_hashes, COMMAND_RPC_GET_HASHES_FAST) MAP_URI_AUTO_BIN2("/get_o_indexes.bin", on_get_indexes, COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES) + MAP_URI_AUTO_BIN2("/get_random_outs.bin", on_get_random_outs, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS) MAP_URI_AUTO_BIN2("/getrandom_outs.bin", on_get_random_outs, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS) MAP_URI_AUTO_BIN2("/get_outs.bin", on_get_outs_bin, COMMAND_RPC_GET_OUTPUTS_BIN) + MAP_URI_AUTO_BIN2("/get_random_rctouts.bin", on_get_random_rct_outs, COMMAND_RPC_GET_RANDOM_RCT_OUTPUTS) MAP_URI_AUTO_BIN2("/getrandom_rctouts.bin", on_get_random_rct_outs, COMMAND_RPC_GET_RANDOM_RCT_OUTPUTS) + MAP_URI_AUTO_JON2("/get_transactions", on_get_transactions, COMMAND_RPC_GET_TRANSACTIONS) MAP_URI_AUTO_JON2("/gettransactions", on_get_transactions, COMMAND_RPC_GET_TRANSACTIONS) MAP_URI_AUTO_JON2("/get_alt_blocks_hashes", on_get_alt_blocks_hashes, COMMAND_RPC_GET_ALT_BLOCKS_HASHES) MAP_URI_AUTO_JON2("/is_key_image_spent", on_is_key_image_spent, COMMAND_RPC_IS_KEY_IMAGE_SPENT) + MAP_URI_AUTO_JON2("/send_raw_transaction", on_send_raw_tx, COMMAND_RPC_SEND_RAW_TX) MAP_URI_AUTO_JON2("/sendrawtransaction", on_send_raw_tx, COMMAND_RPC_SEND_RAW_TX) MAP_URI_AUTO_JON2_IF("/start_mining", on_start_mining, COMMAND_RPC_START_MINING, !m_restricted) MAP_URI_AUTO_JON2_IF("/stop_mining", on_stop_mining, COMMAND_RPC_STOP_MINING, !m_restricted) @@ -101,6 +109,7 @@ namespace cryptonote MAP_URI_AUTO_JON2("/get_transaction_pool_hashes.bin", on_get_transaction_pool_hashes, COMMAND_RPC_GET_TRANSACTION_POOL_HASHES) MAP_URI_AUTO_JON2("/get_transaction_pool_stats", on_get_transaction_pool_stats, COMMAND_RPC_GET_TRANSACTION_POOL_STATS) MAP_URI_AUTO_JON2_IF("/stop_daemon", on_stop_daemon, COMMAND_RPC_STOP_DAEMON, !m_restricted) + MAP_URI_AUTO_JON2("/get_info", on_get_info, COMMAND_RPC_GET_INFO) MAP_URI_AUTO_JON2("/getinfo", on_get_info, COMMAND_RPC_GET_INFO) MAP_URI_AUTO_JON2("/get_limit", on_get_limit, COMMAND_RPC_GET_LIMIT) MAP_URI_AUTO_JON2_IF("/set_limit", on_set_limit, COMMAND_RPC_SET_LIMIT, !m_restricted) @@ -110,14 +119,23 @@ namespace cryptonote MAP_URI_AUTO_JON2("/get_outs", on_get_outs, COMMAND_RPC_GET_OUTPUTS) MAP_URI_AUTO_JON2_IF("/update", on_update, COMMAND_RPC_UPDATE, !m_restricted) BEGIN_JSON_RPC_MAP("/json_rpc") + MAP_JON_RPC("get_block_count", on_getblockcount, COMMAND_RPC_GETBLOCKCOUNT) MAP_JON_RPC("getblockcount", on_getblockcount, COMMAND_RPC_GETBLOCKCOUNT) + MAP_JON_RPC_WE("on_get_block_hash", on_getblockhash, COMMAND_RPC_GETBLOCKHASH) MAP_JON_RPC_WE("on_getblockhash", on_getblockhash, COMMAND_RPC_GETBLOCKHASH) + MAP_JON_RPC_WE("get_block_template", on_getblocktemplate, COMMAND_RPC_GETBLOCKTEMPLATE) MAP_JON_RPC_WE("getblocktemplate", on_getblocktemplate, COMMAND_RPC_GETBLOCKTEMPLATE) + MAP_JON_RPC_WE("submit_block", on_submitblock, COMMAND_RPC_SUBMITBLOCK) MAP_JON_RPC_WE("submitblock", on_submitblock, COMMAND_RPC_SUBMITBLOCK) + MAP_JON_RPC_WE("get_last_block_header", on_get_last_block_header, COMMAND_RPC_GET_LAST_BLOCK_HEADER) MAP_JON_RPC_WE("getlastblockheader", on_get_last_block_header, COMMAND_RPC_GET_LAST_BLOCK_HEADER) + MAP_JON_RPC_WE("get_block_header_by_hash", on_get_block_header_by_hash, COMMAND_RPC_GET_BLOCK_HEADER_BY_HASH) MAP_JON_RPC_WE("getblockheaderbyhash", on_get_block_header_by_hash, COMMAND_RPC_GET_BLOCK_HEADER_BY_HASH) + MAP_JON_RPC_WE("get_block_header_by_height", on_get_block_header_by_height, COMMAND_RPC_GET_BLOCK_HEADER_BY_HEIGHT) MAP_JON_RPC_WE("getblockheaderbyheight", on_get_block_header_by_height, COMMAND_RPC_GET_BLOCK_HEADER_BY_HEIGHT) + MAP_JON_RPC_WE("get_block_headers_range", on_get_block_headers_range, COMMAND_RPC_GET_BLOCK_HEADERS_RANGE) MAP_JON_RPC_WE("getblockheadersrange", on_get_block_headers_range, COMMAND_RPC_GET_BLOCK_HEADERS_RANGE) + MAP_JON_RPC_WE("get_block", on_get_block, COMMAND_RPC_GET_BLOCK) MAP_JON_RPC_WE("getblock", on_get_block, COMMAND_RPC_GET_BLOCK) MAP_JON_RPC_WE_IF("get_connections", on_get_connections, COMMAND_RPC_GET_CONNECTIONS, !m_restricted) MAP_JON_RPC_WE("get_info", on_get_info_json, COMMAND_RPC_GET_INFO) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index ad0bff077..00b553bad 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -1653,8 +1653,8 @@ namespace cryptonote struct response { std::string status; - uint64_t limit_up; - uint64_t limit_down; + int64_t limit_up; + int64_t limit_down; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(status) 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 fd62ebd2a..4bbf3226b 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -1690,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>]]"), @@ -1770,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), @@ -1786,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), @@ -3307,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; @@ -4528,14 +4545,18 @@ 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) { fail_msg_writer() << tr("usage: donate [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <amount> [<payment_id>]"); return true; } - // Hardcode Monero's donation address (see #1447) - const std::string address_str = "44AFFq5kSiGBoZ4NMDwYtN18obc8AemS33DBLWs3H7otXft3XjrpDtQGv7SqSsaBYBb98uNbr2VBBEt7f2wfn3RVGQBEP3A"; std::string amount_str; std::string payment_id_str; // get payment id and pop @@ -4551,11 +4572,11 @@ bool simple_wallet::donate(const std::vector<std::string> &args_) amount_str = local_args.back(); local_args.pop_back(); // push back address, amount, payment id - local_args.push_back(address_str); + local_args.push_back(MONERO_DONATION_ADDR); local_args.push_back(amount_str); if (!payment_id_str.empty()) local_args.push_back(payment_id_str); - message_writer() << tr("Donating ") << amount_str << " to The Monero Project (donate.getmonero.org/44AFFq5kSiGBoZ4NMDwYtN18obc8AemS33DBLWs3H7otXft3XjrpDtQGv7SqSsaBYBb98uNbr2VBBEt7f2wfn3RVGQBEP3A)."; + message_writer() << tr("Donating ") << amount_str << " to The Monero Project (donate.getmonero.org/"<< MONERO_DONATION_ADDR <<")."; transfer_new(local_args); return true; } @@ -5112,6 +5133,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]; @@ -6195,6 +6320,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; @@ -6227,6 +6353,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; @@ -6258,6 +6385,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(); @@ -6356,6 +6484,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"; } @@ -6607,7 +6736,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 024077ca1..086076be0 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -49,6 +49,8 @@ #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "wallet.simplewallet" +// Hardcode Monero's donation address (see #1447) +constexpr const char MONERO_DONATION_ADDR[] = "44AFFq5kSiGBoZ4NMDwYtN18obc8AemS33DBLWs3H7otXft3XjrpDtQGv7SqSsaBYBb98uNbr2VBBEt7f2wfn3RVGQBEP3A"; /*! * \namespace cryptonote @@ -170,6 +172,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); diff --git a/src/wallet/CMakeLists.txt b/src/wallet/CMakeLists.txt index 2d664ba15..abecabbc3 100644 --- a/src/wallet/CMakeLists.txt +++ b/src/wallet/CMakeLists.txt @@ -126,6 +126,6 @@ if (BUILD_GUI_DEPS) endif() install(TARGETS wallet_merged ARCHIVE DESTINATION ${lib_folder}) - - add_subdirectory(api) endif() + +add_subdirectory(api) diff --git a/src/wallet/api/CMakeLists.txt b/src/wallet/api/CMakeLists.txt index 26127b75c..60d6970bd 100644 --- a/src/wallet/api/CMakeLists.txt +++ b/src/wallet/api/CMakeLists.txt @@ -78,6 +78,8 @@ target_link_libraries(wallet_api PRIVATE ${EXTRA_LIBRARIES}) +set_property(TARGET wallet_api PROPERTY EXCLUDE_FROM_ALL TRUE) +set_property(TARGET obj_wallet_api PROPERTY EXCLUDE_FROM_ALL TRUE) if(IOS) set(lib_folder lib-${ARCH}) 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 c4f8f3457..81c814db9 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" @@ -543,8 +544,7 @@ crypto::hash8 get_short_payment_id(const tools::wallet2::pending_tx &ptx) { crypto::hash8 payment_id8 = null_hash8; std::vector<tx_extra_field> tx_extra_fields; - if(!parse_tx_extra(ptx.tx.extra, tx_extra_fields)) - return payment_id8; + parse_tx_extra(ptx.tx.extra, tx_extra_fields); // ok if partially parsed cryptonote::tx_extra_nonce extra_nonce; if (find_tx_extra_field_by_type(tx_extra_fields, extra_nonce)) { @@ -1459,14 +1459,12 @@ void wallet2::process_outgoing(const crypto::hash &txid, const cryptonote::trans entry.first->second.m_change = received; std::vector<tx_extra_field> tx_extra_fields; - if(parse_tx_extra(tx.extra, tx_extra_fields)) + parse_tx_extra(tx.extra, tx_extra_fields); // ok if partially parsed + tx_extra_nonce extra_nonce; + if (find_tx_extra_field_by_type(tx_extra_fields, extra_nonce)) { - tx_extra_nonce extra_nonce; - if (find_tx_extra_field_by_type(tx_extra_fields, extra_nonce)) - { - // we do not care about failure here - get_payment_id_from_tx_extra_nonce(extra_nonce.nonce, entry.first->second.m_payment_id); - } + // we do not care about failure here + get_payment_id_from_tx_extra_nonce(extra_nonce.nonce, entry.first->second.m_payment_id); } entry.first->second.m_subaddr_account = subaddr_account; entry.first->second.m_subaddr_indices = subaddr_indices; @@ -2035,6 +2033,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(); @@ -2977,6 +2980,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(); @@ -4233,8 +4237,7 @@ std::vector<std::vector<cryptonote::tx_destination_entry>> split_amounts( crypto::hash wallet2::get_payment_id(const pending_tx &ptx) const { std::vector<tx_extra_field> tx_extra_fields; - if(!parse_tx_extra(ptx.tx.extra, tx_extra_fields)) - return crypto::null_hash; + parse_tx_extra(ptx.tx.extra, tx_extra_fields); // ok if partially parsed tx_extra_nonce extra_nonce; crypto::hash payment_id = null_hash; if (find_tx_extra_field_by_type(tx_extra_fields, extra_nonce)) @@ -5833,7 +5836,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()) { @@ -5860,7 +5863,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"); @@ -6159,7 +6162,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; @@ -6833,6 +6837,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) @@ -6908,6 +6923,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) @@ -6960,37 +6976,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); + std::map<uint32_t, std::pair<std::vector<size_t>, std::vector<size_t>>> unused_transfer_dust_indices_per_subaddr; - 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); - - // 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); } @@ -7917,6 +7944,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; @@ -8607,11 +8879,8 @@ size_t wallet2::import_outputs(const std::vector<tools::wallet2::transfer_detail // the hot wallet wouldn't have known about key images (except if we already exported them) cryptonote::keypair in_ephemeral; - std::vector<tx_extra_field> tx_extra_fields; THROW_WALLET_EXCEPTION_IF(td.m_tx.vout.empty(), error::wallet_internal_error, "tx with no outputs at index " + boost::lexical_cast<std::string>(i)); - THROW_WALLET_EXCEPTION_IF(!parse_tx_extra(td.m_tx.extra, tx_extra_fields), error::wallet_internal_error, - "Transaction extra has unsupported format at index " + boost::lexical_cast<std::string>(i)); crypto::public_key tx_pub_key = get_tx_pub_key_from_received_outs(td); const std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(td.m_tx); diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 2fbe05f89..04d789f18 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -433,6 +433,16 @@ 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; /*! @@ -836,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 */ @@ -1119,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) @@ -1402,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_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index 3aba76da0..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) @@ -1718,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 88f2a85a4..a70d8626e 100644 --- a/src/wallet/wallet_rpc_server.h +++ b/src/wallet/wallet_rpc_server.h @@ -67,6 +67,8 @@ namespace tools BEGIN_URI_MAP2() BEGIN_JSON_RPC_MAP("/json_rpc") + MAP_JON_RPC_WE("get_balance", on_getbalance, wallet_rpc::COMMAND_RPC_GET_BALANCE) + MAP_JON_RPC_WE("get_address", on_getaddress, wallet_rpc::COMMAND_RPC_GET_ADDRESS) MAP_JON_RPC_WE("getbalance", on_getbalance, wallet_rpc::COMMAND_RPC_GET_BALANCE) MAP_JON_RPC_WE("getaddress", on_getaddress, wallet_rpc::COMMAND_RPC_GET_ADDRESS) MAP_JON_RPC_WE("create_address", on_create_address, wallet_rpc::COMMAND_RPC_CREATE_ADDRESS) @@ -78,6 +80,7 @@ namespace tools MAP_JON_RPC_WE("tag_accounts", on_tag_accounts, wallet_rpc::COMMAND_RPC_TAG_ACCOUNTS) MAP_JON_RPC_WE("untag_accounts", on_untag_accounts, wallet_rpc::COMMAND_RPC_UNTAG_ACCOUNTS) MAP_JON_RPC_WE("set_account_tag_description", on_set_account_tag_description, wallet_rpc::COMMAND_RPC_SET_ACCOUNT_TAG_DESCRIPTION) + MAP_JON_RPC_WE("get_height", on_getheight, wallet_rpc::COMMAND_RPC_GET_HEIGHT) MAP_JON_RPC_WE("getheight", on_getheight, wallet_rpc::COMMAND_RPC_GET_HEIGHT) MAP_JON_RPC_WE("transfer", on_transfer, wallet_rpc::COMMAND_RPC_TRANSFER) MAP_JON_RPC_WE("transfer_split", on_transfer_split, wallet_rpc::COMMAND_RPC_TRANSFER_SPLIT) @@ -104,6 +107,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 +175,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); diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h index d797af5c1..ac25e8e84 100644 --- a/src/wallet/wallet_rpc_server_commands_defs.h +++ b/src/wallet/wallet_rpc_server_commands_defs.h @@ -1114,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() @@ -1128,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() }; @@ -1180,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 |