diff options
Diffstat (limited to 'src')
43 files changed, 1151 insertions, 251 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fed042ce2..93643af24 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -96,6 +96,7 @@ add_subdirectory(hardforks) add_subdirectory(blockchain_db) add_subdirectory(mnemonics) add_subdirectory(rpc) +add_subdirectory(seraphis_crypto) if(NOT IOS) add_subdirectory(serialization) endif() diff --git a/src/blockchain_db/blockchain_db.h b/src/blockchain_db/blockchain_db.h index b38ec9e05..9628a5c4d 100644 --- a/src/blockchain_db/blockchain_db.h +++ b/src/blockchain_db/blockchain_db.h @@ -1306,6 +1306,21 @@ public: virtual bool get_pruned_tx_blobs_from(const crypto::hash& h, size_t count, std::vector<cryptonote::blobdata> &bd) const = 0; /** + * @brief Get all txids in the database (chain and pool) that match a certain nbits txid template + * + * To be more specific, for all `dbtxid` txids in the database, return `dbtxid` if + * `0 == cryptonote::compare_hash32_reversed_nbits(txid_template, dbtxid, nbits)`. + * + * @param txid_template the transaction id template + * @param nbits number of bits to compare against in the template + * @param max_num_txs The maximum number of txids to match, if we hit this limit, throw early + * @return std::vector<crypto::hash> the list of all matching txids + * + * @throw TX_EXISTS if the number of txids that match exceed `max_num_txs` + */ + virtual std::vector<crypto::hash> get_txids_loose(const crypto::hash& txid_template, std::uint32_t nbits, uint64_t max_num_txs = 0) = 0; + + /** * @brief fetches a variable number of blocks and transactions from the given height, in canonical blockchain order * * The subclass should return the blocks and transactions stored from the one with the given diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp index 4178c862b..2c015faee 100644 --- a/src/blockchain_db/lmdb/db_lmdb.cpp +++ b/src/blockchain_db/lmdb/db_lmdb.cpp @@ -3144,6 +3144,58 @@ bool BlockchainLMDB::get_pruned_tx_blobs_from(const crypto::hash& h, size_t coun return true; } +std::vector<crypto::hash> BlockchainLMDB::get_txids_loose(const crypto::hash& txid_template, std::uint32_t bits, uint64_t max_num_txs) +{ + LOG_PRINT_L3("BlockchainLMDB::" << __func__); + check_open(); + + std::vector<crypto::hash> matching_hashes; + + TXN_PREFIX_RDONLY(); // Start a read-only transaction + RCURSOR(tx_indices); // Open cursors to the tx_indices and txpool_meta databases + RCURSOR(txpool_meta); + + // Search on-chain and pool transactions together, starting with on-chain txs + MDB_cursor* cursor = m_cur_tx_indices; + MDB_val k = zerokval; // tx_indicies DB uses a dummy key + MDB_val_set(v, txid_template); // tx_indicies DB indexes data values by crypto::hash value on front + MDB_cursor_op op = MDB_GET_BOTH_RANGE; // Set the cursor to the first key/value pair >= the given key + bool doing_chain = true; // this variable tells us whether we are processing chain or pool txs + while (1) + { + const int get_result = mdb_cursor_get(cursor, &k, &v, op); + op = doing_chain ? MDB_NEXT_DUP : MDB_NEXT; // Set the cursor to the next key/value pair + if (get_result && get_result != MDB_NOTFOUND) + throw0(DB_ERROR(lmdb_error("DB error attempting to fetch txid range", get_result).c_str())); + + // In tx_indicies, the hash is stored at the data, in txpool_meta at the key + const crypto::hash* const p_dbtxid = (const crypto::hash*)(doing_chain ? v.mv_data : k.mv_data); + + // Check if we reached the end of a DB or the hashes no longer match the template + if (get_result == MDB_NOTFOUND || compare_hash32_reversed_nbits(txid_template, *p_dbtxid, bits)) + { + if (doing_chain) // done with chain processing, switch to pool processing + { + k.mv_size = sizeof(crypto::hash); // txpool_meta DB is indexed using crypto::hash as keys + k.mv_data = (void*) txid_template.data; + cursor = m_cur_txpool_meta; // switch databases + op = MDB_SET_RANGE; // Set the cursor to the first key >= the given key + doing_chain = false; + continue; + } + break; // if we get to this point, then we finished pool processing and we are done + } + else if (matching_hashes.size() >= max_num_txs && max_num_txs != 0) + throw0(TX_EXISTS("number of tx hashes in template range exceeds maximum")); + + matching_hashes.push_back(*p_dbtxid); + } + + TXN_POSTFIX_RDONLY(); // End the read-only transaction + + return matching_hashes; +} + bool BlockchainLMDB::get_blocks_from(uint64_t start_height, size_t min_block_count, size_t max_block_count, size_t max_tx_count, size_t max_size, std::vector<std::pair<std::pair<cryptonote::blobdata, crypto::hash>, std::vector<std::pair<crypto::hash, cryptonote::blobdata>>>>& blocks, bool pruned, bool skip_coinbase, bool get_miner_tx_hash) const { LOG_PRINT_L3("BlockchainLMDB::" << __func__); diff --git a/src/blockchain_db/lmdb/db_lmdb.h b/src/blockchain_db/lmdb/db_lmdb.h index c352458b4..95e7b2aa4 100644 --- a/src/blockchain_db/lmdb/db_lmdb.h +++ b/src/blockchain_db/lmdb/db_lmdb.h @@ -262,6 +262,8 @@ public: virtual bool get_prunable_tx_blob(const crypto::hash& h, cryptonote::blobdata &tx) const; virtual bool get_prunable_tx_hash(const crypto::hash& tx_hash, crypto::hash &prunable_hash) const; + virtual std::vector<crypto::hash> get_txids_loose(const crypto::hash& h, std::uint32_t bits, uint64_t max_num_txs = 0); + virtual uint64_t get_tx_count() const; virtual std::vector<transaction> get_tx_list(const std::vector<crypto::hash>& hlist) const; diff --git a/src/blockchain_db/testdb.h b/src/blockchain_db/testdb.h index 946f26270..a27183b2c 100644 --- a/src/blockchain_db/testdb.h +++ b/src/blockchain_db/testdb.h @@ -71,6 +71,7 @@ public: virtual bool get_pruned_tx_blob(const crypto::hash& h, cryptonote::blobdata &tx) const override { return false; } virtual bool get_pruned_tx_blobs_from(const crypto::hash& h, size_t count, std::vector<cryptonote::blobdata> &bd) const override { return false; } virtual bool get_blocks_from(uint64_t start_height, size_t min_block_count, size_t max_block_count, size_t max_tx_count, size_t max_size, std::vector<std::pair<std::pair<cryptonote::blobdata, crypto::hash>, std::vector<std::pair<crypto::hash, cryptonote::blobdata>>>>& blocks, bool pruned, bool skip_coinbase, bool get_miner_tx_hash) const override { return false; } + virtual std::vector<crypto::hash> get_txids_loose(const crypto::hash& h, std::uint32_t bits, uint64_t max_num_txs = 0) override { return {}; } virtual bool get_prunable_tx_blob(const crypto::hash& h, cryptonote::blobdata &tx) const override { return false; } virtual bool get_prunable_tx_hash(const crypto::hash& tx_hash, crypto::hash &prunable_hash) const override { return false; } virtual uint64_t get_block_height(const crypto::hash& h) const override { return 0; } diff --git a/src/blockchain_utilities/blockchain_stats.cpp b/src/blockchain_utilities/blockchain_stats.cpp index f65054fc5..f0a8e5adc 100644 --- a/src/blockchain_utilities/blockchain_stats.cpp +++ b/src/blockchain_utilities/blockchain_stats.cpp @@ -33,6 +33,7 @@ #include "cryptonote_basic/cryptonote_boost_serialization.h" #include "cryptonote_core/cryptonote_core.h" #include "blockchain_db/blockchain_db.h" +#include "time_helper.h" #include "version.h" #undef MONERO_DEFAULT_LOG_CATEGORY diff --git a/src/crypto/jh.c b/src/crypto/jh.c index 12d536375..738c681f8 100644 --- a/src/crypto/jh.c +++ b/src/crypto/jh.c @@ -34,7 +34,7 @@ typedef struct { unsigned long long databitlen; /*the message size in bits*/ unsigned long long datasize_in_buffer; /*the size of the message remained in buffer; assumed to be multiple of 8bits except for the last partial block at the end of the message*/ DATA_ALIGN16(uint64 x[8][2]); /*the 1024-bit state, ( x[i][0] || x[i][1] ) is the ith row of the state in the pseudocode*/ - unsigned char buffer[64]; /*the 512-bit message block to be hashed;*/ + DATA_ALIGN16(unsigned char buffer[64]); /*the 512-bit message block to be hashed;*/ } hashState; @@ -213,16 +213,24 @@ static void E8(hashState *state) /*The compression function F8 */ static void F8(hashState *state) { - uint64 i; + uint64_t* x = (uint64_t*)state->x; /*xor the 512-bit message with the fist half of the 1024-bit hash state*/ - for (i = 0; i < 8; i++) state->x[i >> 1][i & 1] ^= ((uint64*)state->buffer)[i]; + for (int i = 0; i < 8; ++i) { + uint64 b; + memcpy(&b, &state->buffer[i << 3], sizeof(b)); + x[i] ^= b; + } /*the bijective function E8 */ E8(state); /*xor the 512-bit message with the second half of the 1024-bit hash state*/ - for (i = 0; i < 8; i++) state->x[(8+i) >> 1][(8+i) & 1] ^= ((uint64*)state->buffer)[i]; + for (int i = 0; i < 8; ++i) { + uint64 b; + memcpy(&b, &state->buffer[i << 3], sizeof(b)); + x[i + 8] ^= b; + } } /*before hashing a message, initialize the hash state as H0 */ @@ -240,6 +248,7 @@ static HashReturn Init(hashState *state, int hashbitlen) case 224: memcpy(state->x,JH224_H0,128); break; case 256: memcpy(state->x,JH256_H0,128); break; case 384: memcpy(state->x,JH384_H0,128); break; + default: case 512: memcpy(state->x,JH512_H0,128); break; } diff --git a/src/cryptonote_basic/account_generators.h b/src/cryptonote_basic/account_generators.h new file mode 100644 index 000000000..c1102db60 --- /dev/null +++ b/src/cryptonote_basic/account_generators.h @@ -0,0 +1,71 @@ +// Copyright (c) 2021, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include "crypto/crypto.h" +#include "crypto/generators.h" + + +namespace cryptonote +{ + +enum class account_generator_era : unsigned char +{ + unknown = 0, + cryptonote = 1 //and ringct +}; + +struct account_generators +{ + crypto::public_key m_primary; //e.g. for spend key + crypto::public_key m_secondary; //e.g. for view key +}; + +inline crypto::public_key get_primary_generator(const account_generator_era era) +{ + if (era == account_generator_era::cryptonote) + return crypto::get_G(); + else + return crypto::null_pkey; //error +} + +inline crypto::public_key get_secondary_generator(const account_generator_era era) +{ + if (era == account_generator_era::cryptonote) + return crypto::get_G(); + else + return crypto::null_pkey; //error +} + +inline account_generators get_account_generators(const account_generator_era era) +{ + return account_generators{get_primary_generator(era), get_secondary_generator(era)}; +} + +} //namespace cryptonote diff --git a/src/cryptonote_basic/cryptonote_basic.h b/src/cryptonote_basic/cryptonote_basic.h index ae112c31f..0531439d6 100644 --- a/src/cryptonote_basic/cryptonote_basic.h +++ b/src/cryptonote_basic/cryptonote_basic.h @@ -77,7 +77,7 @@ namespace cryptonote // outputs <= HF_VERSION_VIEW_TAGS struct txout_to_key { - txout_to_key() { } + txout_to_key(): key() { } txout_to_key(const crypto::public_key &_key) : key(_key) { } crypto::public_key key; }; @@ -85,7 +85,7 @@ namespace cryptonote // outputs >= HF_VERSION_VIEW_TAGS struct txout_to_tagged_key { - txout_to_tagged_key() { } + txout_to_tagged_key(): key(), view_tag() { } txout_to_tagged_key(const crypto::public_key &_key, const crypto::view_tag &_view_tag) : key(_key), view_tag(_view_tag) { } crypto::public_key key; crypto::view_tag view_tag; // optimization to reduce scanning time diff --git a/src/cryptonote_basic/cryptonote_basic_impl.cpp b/src/cryptonote_basic/cryptonote_basic_impl.cpp index 9bde20609..7fe398283 100644 --- a/src/cryptonote_basic/cryptonote_basic_impl.cpp +++ b/src/cryptonote_basic/cryptonote_basic_impl.cpp @@ -310,6 +310,53 @@ namespace cryptonote { bool operator ==(const cryptonote::block& a, const cryptonote::block& b) { return cryptonote::get_block_hash(a) == cryptonote::get_block_hash(b); } + //-------------------------------------------------------------------------------- + int compare_hash32_reversed_nbits(const crypto::hash& ha, const crypto::hash& hb, unsigned int nbits) + { + static_assert(sizeof(uint64_t) * 4 == sizeof(crypto::hash), "hash is wrong size"); + + // We have to copy these buffers b/c of the strict aliasing rule + uint64_t va[4]; + memcpy(va, &ha, sizeof(crypto::hash)); + uint64_t vb[4]; + memcpy(vb, &hb, sizeof(crypto::hash)); + + for (int n = 3; n >= 0 && nbits; --n) + { + const unsigned int msb_nbits = std::min<unsigned int>(64, nbits); + const uint64_t lsb_nbits_dropped = static_cast<uint64_t>(64 - msb_nbits); + const uint64_t van = SWAP64LE(va[n]) >> lsb_nbits_dropped; + const uint64_t vbn = SWAP64LE(vb[n]) >> lsb_nbits_dropped; + nbits -= msb_nbits; + + if (van < vbn) return -1; else if (van > vbn) return 1; + } + + return 0; + } + + crypto::hash make_hash32_loose_template(unsigned int nbits, const crypto::hash& h) + { + static_assert(sizeof(uint64_t) * 4 == sizeof(crypto::hash), "hash is wrong size"); + + // We have to copy this buffer b/c of the strict aliasing rule + uint64_t vh[4]; + memcpy(vh, &h, sizeof(crypto::hash)); + + for (int n = 3; n >= 0; --n) + { + const unsigned int msb_nbits = std::min<unsigned int>(64, nbits); + const uint64_t mask = msb_nbits ? (~((std::uint64_t(1) << (64 - msb_nbits)) - 1)) : 0; + nbits -= msb_nbits; + + vh[n] &= SWAP64LE(mask); + } + + crypto::hash res; + memcpy(&res, vh, sizeof(crypto::hash)); + return res; + } + //-------------------------------------------------------------------------------- } //-------------------------------------------------------------------------------- diff --git a/src/cryptonote_basic/cryptonote_basic_impl.h b/src/cryptonote_basic/cryptonote_basic_impl.h index 984bee19f..53dbc47d7 100644 --- a/src/cryptonote_basic/cryptonote_basic_impl.h +++ b/src/cryptonote_basic/cryptonote_basic_impl.h @@ -112,6 +112,41 @@ namespace cryptonote { bool operator ==(const cryptonote::transaction& a, const cryptonote::transaction& b); bool operator ==(const cryptonote::block& a, const cryptonote::block& b); + + /************************************************************************/ + /* K-anonymity helper functions */ + /************************************************************************/ + + /** + * @brief Compares two hashes up to `nbits` bits in reverse byte order ("LMDB key order") + * + * The comparison essentially goes from the 31th, 30th, 29th, ..., 0th byte and compares the MSBs + * to the LSBs in each byte, up to `nbits` bits. If we use up `nbits` bits before finding a + * difference in the bits between the two hashes, we return 0. If we encounter a zero bit in `ha` + * where `hb` has a one in that bit place, then we reutrn -1. If the converse scenario happens, + * we return a 1. When `nbits` == 256 (there are 256 bits in `crypto::hash`), calling this is + * functionally identical to `BlockchainLMDB::compare_hash32`. + * + * @param ha left hash + * @param hb right hash + * @param nbits the number of bits to consider, a higher value means a finer comparison + * @return int 0 if ha == hb, -1 if ha < hb, 1 if ha > hb + */ + int compare_hash32_reversed_nbits(const crypto::hash& ha, const crypto::hash& hb, unsigned int nbits); + + /** + * @brief Make a template which matches `h` in LMDB order up to `nbits` bits, safe for k-anonymous fetching + * + * To be more technical, this function creates a hash which satifies the following property: + * For all `H_prime` s.t. `0 == compare_hash32_reversed_nbits(real_hash, H_prime, nbits)`, + * `1 > compare_hash32_reversed_nbits(real_hash, H_prime, 256)`. + * In other words, we return the "least" hash nbit-equal to `real_hash`. + * + * @param nbits The number of "MSB" bits to include in the template + * @param real_hash The original hash which contains more information than we want to disclose + * @return crypto::hash hash template that contains `nbits` bits matching real_hash and no more + */ + crypto::hash make_hash32_loose_template(unsigned int nbits, const crypto::hash& real_hash); } bool parse_hash256(const std::string &str_hash, crypto::hash& hash); diff --git a/src/cryptonote_basic/miner.cpp b/src/cryptonote_basic/miner.cpp index 91ee86d60..4b0e43213 100644 --- a/src/cryptonote_basic/miner.cpp +++ b/src/cryptonote_basic/miner.cpp @@ -42,6 +42,7 @@ #include "string_coding.h" #include "string_tools.h" #include "storages/portable_storage_template_helper.h" +#include "time_helper.h" #include "boost/logic/tribool.hpp" #include <boost/filesystem.hpp> diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index 45da75b66..fec905928 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -30,6 +30,7 @@ #pragma once +#include <cstdint> #include <stdexcept> #include <string> #include <boost/uuid/uuid.hpp> diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 8036c84cd..7c9bd9163 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -4615,40 +4615,9 @@ bool Blockchain::update_next_cumulative_weight_limit(uint64_t *long_term_effecti } else { - const uint64_t block_weight = m_db->get_block_weight(db_height - 1); + const uint64_t nblocks = std::min<uint64_t>(m_long_term_block_weights_window, db_height); + const uint64_t long_term_median = get_long_term_block_weight_median(db_height - nblocks, nblocks); - uint64_t long_term_median; - if (db_height == 1) - { - long_term_median = CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5; - } - else - { - uint64_t nblocks = std::min<uint64_t>(m_long_term_block_weights_window, db_height); - if (nblocks == db_height) - --nblocks; - long_term_median = get_long_term_block_weight_median(db_height - nblocks - 1, nblocks); - } - - m_long_term_effective_median_block_weight = std::max<uint64_t>(CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5, long_term_median); - - uint64_t short_term_constraint = m_long_term_effective_median_block_weight; - if (hf_version >= HF_VERSION_2021_SCALING) - short_term_constraint += m_long_term_effective_median_block_weight * 7 / 10; - else - short_term_constraint += m_long_term_effective_median_block_weight * 2 / 5; - uint64_t long_term_block_weight = std::min<uint64_t>(block_weight, short_term_constraint); - - if (db_height == 1) - { - long_term_median = long_term_block_weight; - } - else - { - m_long_term_block_weights_cache_tip_hash = m_db->get_block_hash_from_height(db_height - 1); - m_long_term_block_weights_cache_rolling_median.insert(long_term_block_weight); - long_term_median = m_long_term_block_weights_cache_rolling_median.median(); - } m_long_term_effective_median_block_weight = std::max<uint64_t>(CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5, long_term_median); std::vector<uint64_t> weights; diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index 3ad051fc3..be2848d09 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -1609,6 +1609,6 @@ namespace cryptonote */ void send_miner_notifications(uint64_t height, const crypto::hash &seed_hash, const crypto::hash &prev_id, uint64_t already_generated_coins); - friend class BlockchainAndPool; + friend struct BlockchainAndPool; }; } // namespace cryptonote diff --git a/src/cryptonote_core/blockchain_and_pool.h b/src/cryptonote_core/blockchain_and_pool.h index c0f607f64..497342aea 100644 --- a/src/cryptonote_core/blockchain_and_pool.h +++ b/src/cryptonote_core/blockchain_and_pool.h @@ -37,6 +37,7 @@ #include "blockchain.h" #include "tx_pool.h" +#include "warnings.h" namespace cryptonote { @@ -52,7 +53,10 @@ struct BlockchainAndPool { Blockchain blockchain; tx_memory_pool tx_pool; - + +PUSH_WARNINGS +DISABLE_GCC_WARNING(uninitialized) BlockchainAndPool(): blockchain(tx_pool), tx_pool(blockchain) {} +POP_WARNINGS }; } diff --git a/src/cryptonote_core/tx_pool.h b/src/cryptonote_core/tx_pool.h index 47268efb6..3bb96d3a8 100644 --- a/src/cryptonote_core/tx_pool.h +++ b/src/cryptonote_core/tx_pool.h @@ -676,7 +676,7 @@ private: //! Next timestamp that a DB check for relayable txes is allowed std::atomic<time_t> m_next_check; - friend class BlockchainAndPool; + friend struct BlockchainAndPool; }; } diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.h b/src/cryptonote_protocol/cryptonote_protocol_handler.h index a29ee8287..80582fad2 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.h +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.h @@ -39,6 +39,7 @@ #include "byte_slice.h" #include "math_helper.h" +#include "syncobj.h" #include "storages/levin_abstract_invoke2.h" #include "warnings.h" #include "cryptonote_protocol_defs.h" diff --git a/src/device/device_ledger.cpp b/src/device/device_ledger.cpp index 27d080a1c..009ee16ca 100644 --- a/src/device/device_ledger.cpp +++ b/src/device/device_ledger.cpp @@ -34,9 +34,6 @@ #include "cryptonote_basic/subaddress_index.h" #include "cryptonote_core/cryptonote_tx_utils.h" -#include <boost/thread/locks.hpp> -#include <boost/thread/lock_guard.hpp> - namespace hw { namespace ledger { @@ -322,10 +319,10 @@ namespace hw { //automatic lock one more level on device ensuring the current thread is allowed to use it #define AUTO_LOCK_CMD() \ /* lock both mutexes without deadlock*/ \ - boost::lock(device_locker, command_locker); \ + std::lock(device_locker, command_locker); \ /* make sure both already-locked mutexes are unlocked at the end of scope */ \ - boost::lock_guard<boost::recursive_mutex> lock1(device_locker, boost::adopt_lock); \ - boost::lock_guard<boost::mutex> lock2(command_locker, boost::adopt_lock) + std::lock_guard<std::recursive_mutex> lock1(device_locker, std::adopt_lock); \ + std::lock_guard<std::mutex> lock2(command_locker, std::adopt_lock) //lock the device for a long sequence void device_ledger::lock(void) { diff --git a/src/device/device_ledger.hpp b/src/device/device_ledger.hpp index 071274160..b65943f0c 100644 --- a/src/device/device_ledger.hpp +++ b/src/device/device_ledger.hpp @@ -35,8 +35,7 @@ #include "device.hpp" #include "log.hpp" #include "device_io_hid.hpp" -#include <boost/thread/mutex.hpp> -#include <boost/thread/recursive_mutex.hpp> +#include <mutex> namespace hw { @@ -140,8 +139,8 @@ namespace hw { class device_ledger : public hw::device { private: // Locker for concurrent access - mutable boost::recursive_mutex device_locker; - mutable boost::mutex command_locker; + mutable std::recursive_mutex device_locker; + mutable std::mutex command_locker; //IO hw::io::device_io_hid hw_device; diff --git a/src/device_trezor/CMakeLists.txt b/src/device_trezor/CMakeLists.txt index c30fb2b16..2d5614507 100644 --- a/src/device_trezor/CMakeLists.txt +++ b/src/device_trezor/CMakeLists.txt @@ -65,12 +65,16 @@ set(trezor_private_headers) # Protobuf and LibUSB processed by CheckTrezor if(DEVICE_TREZOR_READY) - message(STATUS "Trezor support enabled") + message(STATUS "Trezor: support enabled") if(USE_DEVICE_TREZOR_DEBUG) list(APPEND trezor_headers trezor/debug_link.hpp trezor/messages/messages-debug.pb.h) list(APPEND trezor_sources trezor/debug_link.cpp trezor/messages/messages-debug.pb.cc) - message(STATUS "Trezor debugging enabled") + message(STATUS "Trezor: debugging enabled") + endif() + + if(ANDROID) + set(TREZOR_EXTRA_LIBRARIES "log") endif() monero_private_headers(device_trezor @@ -93,10 +97,11 @@ if(DEVICE_TREZOR_READY) ${Protobuf_LIBRARY} ${TREZOR_LIBUSB_LIBRARIES} PRIVATE - ${EXTRA_LIBRARIES}) + ${EXTRA_LIBRARIES} + ${TREZOR_EXTRA_LIBRARIES}) else() - message(STATUS "Trezor support disabled") + message(STATUS "Trezor: support disabled") monero_private_headers(device_trezor) monero_add_library(device_trezor device_trezor.cpp) target_link_libraries(device_trezor PUBLIC cncrypto) diff --git a/src/device_trezor/README.md b/src/device_trezor/README.md new file mode 100644 index 000000000..ce08c0009 --- /dev/null +++ b/src/device_trezor/README.md @@ -0,0 +1,74 @@ +# Trezor hardware wallet support + +This module adds [Trezor] hardware support to Monero. + + +## Basic information + +Trezor integration is based on the following original proposal: https://github.com/ph4r05/monero-trezor-doc + +A custom high-level transaction signing protocol uses Trezor in a similar way a cold wallet is used. +Transaction is build incrementally on the device. + +Trezor implements the signing protocol in [trezor-firmware] repository, in the [monero](https://github.com/trezor/trezor-firmware/tree/master/core/src/apps/monero) application. +Please, refer to [monero readme](https://github.com/trezor/trezor-firmware/blob/master/core/src/apps/monero/README.md) for more information. + +## Dependencies + +Trezor uses [Protobuf](https://protobuf.dev/) library. As Monero is compiled with C++14, the newest Protobuf library version cannot be compiled because it requires C++17 (through its dependency Abseil library). +This can result in a compilation failure. + +Protobuf v21 is the latest compatible protobuf version. + +If you want to compile Monero with Trezor support, please make sure the Protobuf v21 is installed. + +More about this limitation: [PR #8752](https://github.com/monero-project/monero/pull/8752), +[1](https://github.com/monero-project/monero/pull/8752#discussion_r1246174755), [2](https://github.com/monero-project/monero/pull/8752#discussion_r1246480393) + +### OSX + +To build with installed, but not linked protobuf: + +```bash +CMAKE_PREFIX_PATH=$(find /opt/homebrew/Cellar/protobuf@21 -maxdepth 1 -type d -name "21.*" -print -quit) \ +make release +``` + +or to install and link as a default protobuf version: +```bash +# Either install all requirements as +brew update && brew bundle --file=contrib/brew/Brewfile + +# or install protobufv21 specifically +brew install protobuf@21 && brew link protobuf@21 +``` + +### MSYS32 + +```bash +curl -O https://repo.msys2.org/mingw/mingw64/mingw-w64-x86_64-protobuf-c-1.4.1-1-any.pkg.tar.zst +curl -O https://repo.msys2.org/mingw/mingw64/mingw-w64-x86_64-protobuf-21.9-1-any.pkg.tar.zst +pacman --noconfirm -U mingw-w64-x86_64-protobuf-c-1.4.1-1-any.pkg.tar.zst mingw-w64-x86_64-protobuf-21.9-1-any.pkg.tar.zst +``` + +### Other systems + +- install protobufv21 +- point `CMAKE_PREFIX_PATH` environment variable to Protobuf v21 installation. + +## Troubleshooting + +To disable Trezor support, set `USE_DEVICE_TREZOR=OFF`, e.g.: + +```shell +USE_DEVICE_TREZOR=OFF make release +``` + +## Resources: + +- First pull request https://github.com/monero-project/monero/pull/4241 +- Integration proposal https://github.com/ph4r05/monero-trezor-doc +- Integration readme in trezor-firmware https://github.com/trezor/trezor-firmware/blob/master/core/src/apps/monero/README.md + +[Trezor]: https://trezor.io/ +[trezor-firmware]: https://github.com/trezor/trezor-firmware/
\ No newline at end of file diff --git a/src/device_trezor/device_trezor.cpp b/src/device_trezor/device_trezor.cpp index 9c8148ed6..fa1e7c088 100644 --- a/src/device_trezor/device_trezor.cpp +++ b/src/device_trezor/device_trezor.cpp @@ -165,7 +165,7 @@ namespace trezor { auto res = get_address(); cryptonote::address_parse_info info{}; - bool r = cryptonote::get_account_address_from_str(info, this->network_type, res->address()); + bool r = cryptonote::get_account_address_from_str(info, this->m_network_type, res->address()); CHECK_AND_ASSERT_MES(r, false, "Could not parse returned address. Address parse failed: " + res->address()); CHECK_AND_ASSERT_MES(!info.is_subaddress, false, "Trezor returned a sub address"); @@ -693,14 +693,11 @@ namespace trezor { unsigned device_trezor::client_version() { auto trezor_version = get_version(); - if (trezor_version < pack_version(2, 4, 3)){ - throw exc::TrezorException("Minimal Trezor firmware version is 2.4.3. Please update."); + if (trezor_version < pack_version(2, 5, 2)){ + throw exc::TrezorException("Minimal Trezor firmware version is 2.5.2. Please update."); } - unsigned client_version = 3; - if (trezor_version >= pack_version(2, 5, 2)){ - client_version = 4; - } + unsigned client_version = 4; // since 2.5.2 #ifdef WITH_TREZOR_DEBUGGING // Override client version for tests diff --git a/src/device_trezor/device_trezor.hpp b/src/device_trezor/device_trezor.hpp index 35bb78927..804ed62c8 100644 --- a/src/device_trezor/device_trezor.hpp +++ b/src/device_trezor/device_trezor.hpp @@ -102,7 +102,7 @@ namespace trezor { bool has_ki_cold_sync() const override { return true; } bool has_tx_cold_sign() const override { return true; } - void set_network_type(cryptonote::network_type network_type) override { this->network_type = network_type; } + void set_network_type(cryptonote::network_type network_type) override { this->m_network_type = network_type; } void set_live_refresh_enabled(bool enabled) { m_live_refresh_enabled = enabled; } bool live_refresh_enabled() const { return m_live_refresh_enabled; } diff --git a/src/device_trezor/device_trezor_base.cpp b/src/device_trezor/device_trezor_base.cpp index a8a3d9f67..f65870be5 100644 --- a/src/device_trezor/device_trezor_base.cpp +++ b/src/device_trezor/device_trezor_base.cpp @@ -300,9 +300,6 @@ namespace trezor { case messages::MessageType_PassphraseRequest: on_passphrase_request(input, dynamic_cast<const messages::common::PassphraseRequest*>(input.m_msg.get())); return true; - case messages::MessageType_Deprecated_PassphraseStateRequest: - on_passphrase_state_request(input, dynamic_cast<const messages::common::Deprecated_PassphraseStateRequest*>(input.m_msg.get())); - return true; case messages::MessageType_PinMatrixRequest: on_pin_request(input, dynamic_cast<const messages::common::PinMatrixRequest*>(input.m_msg.get())); return true; @@ -475,21 +472,9 @@ namespace trezor { CHECK_AND_ASSERT_THROW_MES(msg, "Empty message"); MDEBUG("on_passhprase_request"); - // Backward compatibility, migration clause. - if (msg->has__on_device() && msg->_on_device()){ - messages::common::PassphraseAck m; - resp = call_raw(&m); - return; - } - m_seen_passphrase_entry_message = true; - bool on_device = true; - if (msg->has__on_device() && !msg->_on_device()){ - on_device = false; // do not enter on device, old devices. - } - - if (on_device && m_features && m_features->capabilities_size() > 0){ - on_device = false; + bool on_device = false; + if (m_features){ for (auto it = m_features->capabilities().begin(); it != m_features->capabilities().end(); it++) { if (*it == messages::management::Features::Capability_PassphraseEntry){ on_device = true; @@ -526,18 +511,6 @@ namespace trezor { resp = call_raw(&m); } - void device_trezor_base::on_passphrase_state_request(GenericMessage & resp, const messages::common::Deprecated_PassphraseStateRequest * msg) - { - MDEBUG("on_passhprase_state_request"); - CHECK_AND_ASSERT_THROW_MES(msg, "Empty message"); - - if (msg->has_state()) { - m_device_session_id = msg->state(); - } - messages::common::Deprecated_PassphraseStateAck m; - resp = call_raw(&m); - } - #ifdef WITH_TREZOR_DEBUGGING void device_trezor_base::wipe_device() { diff --git a/src/device_trezor/device_trezor_base.hpp b/src/device_trezor/device_trezor_base.hpp index 3ec21e157..e0fc2aafb 100644 --- a/src/device_trezor/device_trezor_base.hpp +++ b/src/device_trezor/device_trezor_base.hpp @@ -32,13 +32,12 @@ #include <cstddef> +#include <mutex> #include <string> #include "device/device.hpp" #include "device/device_default.hpp" #include "device/device_cold.hpp" #include <boost/scope_exit.hpp> -#include <boost/thread/mutex.hpp> -#include <boost/thread/recursive_mutex.hpp> #include "cryptonote_config.h" #include "trezor.hpp" @@ -49,12 +48,12 @@ //automatic lock one more level on device ensuring the current thread is allowed to use it #define TREZOR_AUTO_LOCK_CMD() \ /* lock both mutexes without deadlock*/ \ - boost::lock(device_locker, command_locker); \ + std::lock(device_locker, command_locker); \ /* make sure both already-locked mutexes are unlocked at the end of scope */ \ - boost::lock_guard<boost::recursive_mutex> lock1(device_locker, boost::adopt_lock); \ - boost::lock_guard<boost::mutex> lock2(command_locker, boost::adopt_lock) + std::lock_guard<std::recursive_mutex> lock1(device_locker, std::adopt_lock); \ + std::lock_guard<std::mutex> lock2(command_locker, std::adopt_lock) -#define TREZOR_AUTO_LOCK_DEVICE() boost::lock_guard<boost::recursive_mutex> lock1_device(device_locker) +#define TREZOR_AUTO_LOCK_DEVICE() std::lock_guard<std::recursive_mutex> lock1_device(device_locker) namespace hw { namespace trezor { @@ -86,8 +85,8 @@ namespace trezor { protected: // Locker for concurrent access - mutable boost::recursive_mutex device_locker; - mutable boost::mutex command_locker; + mutable std::recursive_mutex device_locker; + mutable std::mutex command_locker; std::shared_ptr<Transport> m_transport; i_device_callback * m_callback; @@ -100,7 +99,7 @@ namespace trezor { boost::optional<epee::wipeable_string> m_passphrase; messages::MessageType m_last_msg_type; - cryptonote::network_type network_type; + cryptonote::network_type m_network_type; bool m_reply_with_empty_passphrase; bool m_always_use_empty_passphrase; bool m_seen_passphrase_entry_message; @@ -227,9 +226,9 @@ namespace trezor { } if (network_type){ - msg->set_network_type(static_cast<uint32_t>(network_type.get())); + msg->set_network_type(static_cast<messages::monero::MoneroNetworkType>(network_type.get())); } else { - msg->set_network_type(static_cast<uint32_t>(this->network_type)); + msg->set_network_type(static_cast<messages::monero::MoneroNetworkType>(this->m_network_type)); } } @@ -318,7 +317,6 @@ namespace trezor { void on_button_pressed(); void on_pin_request(GenericMessage & resp, const messages::common::PinMatrixRequest * msg); void on_passphrase_request(GenericMessage & resp, const messages::common::PassphraseRequest * msg); - void on_passphrase_state_request(GenericMessage & resp, const messages::common::Deprecated_PassphraseStateRequest * msg); #ifdef WITH_TREZOR_DEBUGGING void set_debug(bool debug){ diff --git a/src/device_trezor/trezor/debug_link.cpp b/src/device_trezor/trezor/debug_link.cpp index ff7cf0ad6..76adcb164 100644 --- a/src/device_trezor/trezor/debug_link.cpp +++ b/src/device_trezor/trezor/debug_link.cpp @@ -67,7 +67,7 @@ namespace trezor{ void DebugLink::input_button(bool button){ messages::debug::DebugLinkDecision decision; - decision.set_yes_no(button); + decision.set_button(button ? messages::debug::DebugLinkDecision_DebugButton_YES : messages::debug::DebugLinkDecision_DebugButton_NO); call(decision, boost::none, true); } diff --git a/src/device_trezor/trezor/transport.cpp b/src/device_trezor/trezor/transport.cpp index 8c6b30787..23a04f9c7 100644 --- a/src/device_trezor/trezor/transport.cpp +++ b/src/device_trezor/trezor/transport.cpp @@ -154,6 +154,7 @@ namespace trezor{ // Helpers // +#define PROTO_MAGIC_SIZE 3 #define PROTO_HEADER_SIZE 6 static size_t message_size(const google::protobuf::Message &req){ @@ -193,7 +194,7 @@ namespace trezor{ } serialize_message_header(buff, msg_wire_num, msg_size); - if (!req.SerializeToArray(buff + 6, msg_size)){ + if (!req.SerializeToArray(buff + PROTO_HEADER_SIZE, msg_size)){ throw exc::EncodingException("Message serialization error"); } } @@ -252,16 +253,16 @@ namespace trezor{ throw exc::CommunicationException("Read chunk has invalid size"); } - if (memcmp(chunk_buff_raw, "?##", 3) != 0){ + if (memcmp(chunk_buff_raw, "?##", PROTO_MAGIC_SIZE) != 0){ throw exc::CommunicationException("Malformed chunk"); } uint16_t tag; uint32_t len; - nread -= 3 + 6; - deserialize_message_header(chunk_buff_raw + 3, tag, len); + nread -= PROTO_MAGIC_SIZE + PROTO_HEADER_SIZE; + deserialize_message_header(chunk_buff_raw + PROTO_MAGIC_SIZE, tag, len); - epee::wipeable_string data_acc(chunk_buff_raw + 3 + 6, nread); + epee::wipeable_string data_acc(chunk_buff_raw + PROTO_MAGIC_SIZE + PROTO_HEADER_SIZE, nread); data_acc.reserve(len); while(nread < len){ @@ -482,7 +483,7 @@ namespace trezor{ uint16_t msg_tag; uint32_t msg_len; deserialize_message_header(bin_data->data(), msg_tag, msg_len); - if (bin_data->size() != msg_len + 6){ + if (bin_data->size() != msg_len + PROTO_HEADER_SIZE){ throw exc::CommunicationException("Response is not well hexcoded"); } @@ -491,7 +492,7 @@ namespace trezor{ } std::shared_ptr<google::protobuf::Message> msg_wrap(MessageMapper::get_message(msg_tag)); - if (!msg_wrap->ParseFromArray(bin_data->data() + 6, msg_len)){ + if (!msg_wrap->ParseFromArray(bin_data->data() + PROTO_HEADER_SIZE, msg_len)){ throw exc::EncodingException("Response is not well hexcoded"); } msg = msg_wrap; diff --git a/src/multisig/multisig_kex_msg.cpp b/src/multisig/multisig_kex_msg.cpp index 49350948a..321305283 100644 --- a/src/multisig/multisig_kex_msg.cpp +++ b/src/multisig/multisig_kex_msg.cpp @@ -206,8 +206,13 @@ namespace multisig //---------------------------------------------------------------------------------------------------------------------- void multisig_kex_msg::parse_and_validate_msg() { + CHECK_AND_ASSERT_THROW_MES(MULTISIG_KEX_MSG_V2_MAGIC_1.size() == MULTISIG_KEX_MSG_V2_MAGIC_N.size(), + "Multisig kex msg magic inconsistency."); + CHECK_AND_ASSERT_THROW_MES(MULTISIG_KEX_MSG_V2_MAGIC_1.size() >= MULTISIG_KEX_V1_MAGIC.size(), + "Multisig kex msg magic inconsistency."); + // check message type - CHECK_AND_ASSERT_THROW_MES(m_msg.size() > 0, "Kex message unexpectedly empty."); + CHECK_AND_ASSERT_THROW_MES(m_msg.size() >= MULTISIG_KEX_MSG_V2_MAGIC_1.size(), "Kex message unexpectedly small."); CHECK_AND_ASSERT_THROW_MES(m_msg.substr(0, MULTISIG_KEX_V1_MAGIC.size()) != MULTISIG_KEX_V1_MAGIC, "V1 multisig kex messages are deprecated (unsafe)."); CHECK_AND_ASSERT_THROW_MES(m_msg.substr(0, MULTISIG_KEX_MSG_V1_MAGIC.size()) != MULTISIG_KEX_MSG_V1_MAGIC, @@ -215,8 +220,6 @@ namespace multisig // deserialize the message std::string msg_no_magic; - CHECK_AND_ASSERT_THROW_MES(MULTISIG_KEX_MSG_V2_MAGIC_1.size() == MULTISIG_KEX_MSG_V2_MAGIC_N.size(), - "Multisig kex msg magic inconsistency."); CHECK_AND_ASSERT_THROW_MES(tools::base58::decode(m_msg.substr(MULTISIG_KEX_MSG_V2_MAGIC_1.size()), msg_no_magic), "Multisig kex msg decoding error."); binary_archive<false> b_archive{epee::strspan<std::uint8_t>(msg_no_magic)}; diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl index 4f77ce834..815c1b354 100644 --- a/src/p2p/net_node.inl +++ b/src/p2p/net_node.inl @@ -51,7 +51,6 @@ #include "common/dns_utils.h" #include "common/pruning.h" #include "net/error.h" -#include "math_helper.h" #include "misc_log_ex.h" #include "p2p_protocol_defs.h" #include "crypto/crypto.h" diff --git a/src/ringct/rctTypes.h b/src/ringct/rctTypes.h index 380ca2f53..585d5fb49 100644 --- a/src/ringct/rctTypes.h +++ b/src/ringct/rctTypes.h @@ -218,7 +218,7 @@ namespace rct { rct::key a, b, t; Bulletproof(): - A({}), S({}), T1({}), T2({}), taux({}), mu({}), a({}), b({}), t({}) {} + A({}), S({}), T1({}), T2({}), taux({}), mu({}), a({}), b({}), t({}), V({}), L({}), R({}) {} Bulletproof(const rct::key &V, const rct::key &A, const rct::key &S, const rct::key &T1, const rct::key &T2, const rct::key &taux, const rct::key &mu, const rct::keyV &L, const rct::keyV &R, const rct::key &a, const rct::key &b, const rct::key &t): V({V}), A(A), S(S), T1(T1), T2(T2), taux(taux), mu(mu), L(L), R(R), a(a), b(b), t(t) {} Bulletproof(const rct::keyV &V, const rct::key &A, const rct::key &S, const rct::key &T1, const rct::key &T2, const rct::key &taux, const rct::key &mu, const rct::keyV &L, const rct::keyV &R, const rct::key &a, const rct::key &b, const rct::key &t): @@ -253,7 +253,7 @@ namespace rct { rct::key r1, s1, d1; rct::keyV L, R; - BulletproofPlus() {} + BulletproofPlus(): V(), A(), A1(), B(), r1(), s1(), d1(), L(), R() {} BulletproofPlus(const rct::key &V, const rct::key &A, const rct::key &A1, const rct::key &B, const rct::key &r1, const rct::key &s1, const rct::key &d1, const rct::keyV &L, const rct::keyV &R): V({V}), A(A), A1(A1), B(B), r1(r1), s1(s1), d1(d1), L(L), R(R) {} BulletproofPlus(const rct::keyV &V, const rct::key &A, const rct::key &A1, const rct::key &B, const rct::key &r1, const rct::key &s1, const rct::key &d1, const rct::keyV &L, const rct::keyV &R): @@ -362,11 +362,17 @@ namespace rct { { if (type == RCTTypeBulletproof2 || type == RCTTypeCLSAG || type == RCTTypeBulletproofPlus) { + // Since RCTTypeBulletproof2 enote types, we don't serialize the blinding factor, and only serialize the + // first 8 bytes of ecdhInfo[i].amount ar.begin_object(); - if (!typename Archive<W>::is_saving()) + crypto::hash8 trunc_amount; // placeholder variable needed to maintain "strict aliasing" + if (!typename Archive<W>::is_saving()) // loading memset(ecdhInfo[i].amount.bytes, 0, sizeof(ecdhInfo[i].amount.bytes)); - crypto::hash8 &amount = (crypto::hash8&)ecdhInfo[i].amount; - FIELD(amount); + else // saving + memcpy(trunc_amount.data, ecdhInfo[i].amount.bytes, sizeof(trunc_amount)); + FIELD(trunc_amount); + if (!typename Archive<W>::is_saving()) // loading + memcpy(ecdhInfo[i].amount.bytes, trunc_amount.data, sizeof(trunc_amount)); ar.end_object(); } else diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 03e9ec494..fbca32f80 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -2358,6 +2358,7 @@ namespace cryptonote bool core_rpc_server::use_bootstrap_daemon_if_necessary(const invoke_http_mode &mode, const std::string &command_name, const typename COMMAND_TYPE::request& req, typename COMMAND_TYPE::response& res, bool &r) { res.untrusted = false; + r = false; boost::upgrade_lock<boost::shared_mutex> upgrade_lock(m_bootstrap_daemon_mutex); @@ -3535,6 +3536,82 @@ namespace cryptonote return true; } //------------------------------------------------------------------------------------------------------------------------------ + bool core_rpc_server::on_get_txids_loose(const COMMAND_RPC_GET_TXIDS_LOOSE::request& req, COMMAND_RPC_GET_TXIDS_LOOSE::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) + { + RPC_TRACKER(get_txids_loose); + + // Maybe don't use bootstrap since this endpoint is meant to retreive TXIDs w/ k-anonymity, + // so shunting this request to a random node seems counterproductive. + +#if BYTE_ORDER == LITTLE_ENDIAN + const uint64_t max_num_txids = RESTRICTED_SPENT_KEY_IMAGES_COUNT * (m_restricted ? 1 : 10); + + // Sanity check parameters + if (req.num_matching_bits > 256) + { + error_resp.code = CORE_RPC_ERROR_CODE_WRONG_PARAM; + error_resp.message = "There are only 256 bits in a hash, you gave too many"; + return false; + } + + // Attempt to guess when bit count is too low before fetching, within a certain margin of error + const uint64_t num_txs_ever = m_core.get_blockchain_storage().get_db().get_tx_count(); + const uint64_t num_expected_fetch = (num_txs_ever >> std::min((int) req.num_matching_bits, 63)); + const uint64_t max_num_txids_with_margin = 2 * max_num_txids; + if (num_expected_fetch > max_num_txids_with_margin) + { + error_resp.code = CORE_RPC_ERROR_CODE_WRONG_PARAM; + error_resp.message = "Trying to search with too few matching bits, detected before fetching"; + return false; + } + + // Convert txid template to a crypto::hash + crypto::hash search_hash; + if (!epee::string_tools::hex_to_pod(req.txid_template, search_hash)) + { + error_resp.code = CORE_RPC_ERROR_CODE_WRONG_PARAM; + error_resp.message = "Could not decode hex txid"; + return false; + } + // Check that txid template is zeroed correctly for number of given matchign bits + else if (search_hash != make_hash32_loose_template(req.num_matching_bits, search_hash)) + { + error_resp.code = CORE_RPC_ERROR_CODE_WRONG_PARAM; + error_resp.message = "Txid template is not zeroed correctly for number of bits. You could be leaking true txid!"; + return false; + } + + try + { + // Do the DB fetch + const auto txids = m_core.get_blockchain_storage().get_db().get_txids_loose(search_hash, req.num_matching_bits, max_num_txids); + // Fill out response form + for (const auto& txid : txids) + res.txids.emplace_back(epee::string_tools::pod_to_hex(txid)); + } + catch (const TX_EXISTS&) + { + error_resp.code = CORE_RPC_ERROR_CODE_WRONG_PARAM; + error_resp.message = "Trying to search with too few matching bits"; + return false; + } + catch (const std::exception& e) + { + error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; + error_resp.message = std::string("Error during get_txids_loose: ") + e.what(); + return false; + } + + res.status = CORE_RPC_STATUS_OK; + return true; +#else // BYTE_ORDER == BIG_ENDIAN + // BlockchainLMDB::compare_hash32 has different key ordering (thus different txid templates) on BE systems + error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; + error_resp.message = "Due to implementation details, this feature is not available on big-endian daemons"; + return false; +#endif + } + //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_rpc_access_submit_nonce(const COMMAND_RPC_ACCESS_SUBMIT_NONCE::request& req, COMMAND_RPC_ACCESS_SUBMIT_NONCE::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) { RPC_TRACKER(rpc_access_submit_nonce); diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 790d5eb23..7c31d2539 100644 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -178,6 +178,7 @@ namespace cryptonote MAP_JON_RPC_WE("get_output_distribution", on_get_output_distribution, COMMAND_RPC_GET_OUTPUT_DISTRIBUTION) MAP_JON_RPC_WE_IF("prune_blockchain", on_prune_blockchain, COMMAND_RPC_PRUNE_BLOCKCHAIN, !m_restricted) MAP_JON_RPC_WE_IF("flush_cache", on_flush_cache, COMMAND_RPC_FLUSH_CACHE, !m_restricted) + MAP_JON_RPC_WE("get_txids_loose", on_get_txids_loose, COMMAND_RPC_GET_TXIDS_LOOSE) MAP_JON_RPC_WE("rpc_access_info", on_rpc_access_info, COMMAND_RPC_ACCESS_INFO) MAP_JON_RPC_WE("rpc_access_submit_nonce",on_rpc_access_submit_nonce, COMMAND_RPC_ACCESS_SUBMIT_NONCE) MAP_JON_RPC_WE("rpc_access_pay", on_rpc_access_pay, COMMAND_RPC_ACCESS_PAY) @@ -255,6 +256,7 @@ namespace cryptonote bool on_get_output_distribution(const COMMAND_RPC_GET_OUTPUT_DISTRIBUTION::request& req, COMMAND_RPC_GET_OUTPUT_DISTRIBUTION::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL); bool on_prune_blockchain(const COMMAND_RPC_PRUNE_BLOCKCHAIN::request& req, COMMAND_RPC_PRUNE_BLOCKCHAIN::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL); bool on_flush_cache(const COMMAND_RPC_FLUSH_CACHE::request& req, COMMAND_RPC_FLUSH_CACHE::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL); + bool on_get_txids_loose(const COMMAND_RPC_GET_TXIDS_LOOSE::request& req, COMMAND_RPC_GET_TXIDS_LOOSE::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL); bool on_rpc_access_info(const COMMAND_RPC_ACCESS_INFO::request& req, COMMAND_RPC_ACCESS_INFO::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL); bool on_rpc_access_submit_nonce(const COMMAND_RPC_ACCESS_SUBMIT_NONCE::request& req, COMMAND_RPC_ACCESS_SUBMIT_NONCE::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL); bool on_rpc_access_pay(const COMMAND_RPC_ACCESS_PAY::request& req, COMMAND_RPC_ACCESS_PAY::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL); diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 819d77c1f..c0330eed9 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -617,7 +617,7 @@ namespace cryptonote bool do_sanity_checks; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE_PARENT(rpc_access_request_base); + KV_SERIALIZE_PARENT(rpc_access_request_base) KV_SERIALIZE(tx_as_hex) KV_SERIALIZE_OPT(do_not_relay, false) KV_SERIALIZE_OPT(do_sanity_checks, true) @@ -693,7 +693,7 @@ namespace cryptonote struct request_t: public rpc_access_request_base { BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE_PARENT(rpc_access_request_base); + KV_SERIALIZE_PARENT(rpc_access_request_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; @@ -1217,7 +1217,7 @@ namespace cryptonote BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE_PARENT(rpc_access_request_base) - KV_SERIALIZE_OPT(fill_pow_hash, false); + KV_SERIALIZE_OPT(fill_pow_hash, false) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; @@ -1247,7 +1247,7 @@ namespace cryptonote KV_SERIALIZE_PARENT(rpc_access_request_base) KV_SERIALIZE(hash) KV_SERIALIZE(hashes) - KV_SERIALIZE_OPT(fill_pow_hash, false); + KV_SERIALIZE_OPT(fill_pow_hash, false) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; @@ -1276,7 +1276,7 @@ namespace cryptonote BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE_PARENT(rpc_access_request_base) KV_SERIALIZE(height) - KV_SERIALIZE_OPT(fill_pow_hash, false); + KV_SERIALIZE_OPT(fill_pow_hash, false) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; @@ -1305,7 +1305,7 @@ namespace cryptonote KV_SERIALIZE_PARENT(rpc_access_request_base) KV_SERIALIZE(hash) KV_SERIALIZE(height) - KV_SERIALIZE_OPT(fill_pow_hash, false); + KV_SERIALIZE_OPT(fill_pow_hash, false) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; @@ -1763,7 +1763,7 @@ namespace cryptonote KV_SERIALIZE_PARENT(rpc_access_request_base) KV_SERIALIZE(start_height) KV_SERIALIZE(end_height) - KV_SERIALIZE_OPT(fill_pow_hash, false); + KV_SERIALIZE_OPT(fill_pow_hash, false) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; @@ -2124,12 +2124,12 @@ namespace cryptonote uint64_t recent_cutoff; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE_PARENT(rpc_access_request_base); - KV_SERIALIZE(amounts); - KV_SERIALIZE(min_count); - KV_SERIALIZE(max_count); - KV_SERIALIZE(unlocked); - KV_SERIALIZE(recent_cutoff); + KV_SERIALIZE_PARENT(rpc_access_request_base) + KV_SERIALIZE(amounts) + KV_SERIALIZE(min_count) + KV_SERIALIZE(max_count) + KV_SERIALIZE(unlocked) + KV_SERIALIZE(recent_cutoff) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; @@ -2142,10 +2142,10 @@ namespace cryptonote uint64_t recent_instances; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(amount); - KV_SERIALIZE(total_instances); - KV_SERIALIZE(unlocked_instances); - KV_SERIALIZE(recent_instances); + KV_SERIALIZE(amount) + KV_SERIALIZE(total_instances) + KV_SERIALIZE(unlocked_instances) + KV_SERIALIZE(recent_instances) END_KV_SERIALIZE_MAP() entry(uint64_t amount, uint64_t total_instances, uint64_t unlocked_instances, uint64_t recent_instances): @@ -2216,9 +2216,9 @@ namespace cryptonote uint64_t count; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE_PARENT(rpc_access_request_base); - KV_SERIALIZE(height); - KV_SERIALIZE(count); + KV_SERIALIZE_PARENT(rpc_access_request_base) + KV_SERIALIZE(height) + KV_SERIALIZE(count) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; @@ -2793,4 +2793,31 @@ namespace cryptonote typedef epee::misc_utils::struct_init<response_t> response; }; + struct COMMAND_RPC_GET_TXIDS_LOOSE + { + struct request_t: public rpc_request_base + { + std::string txid_template; + std::uint32_t num_matching_bits; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) + KV_SERIALIZE(txid_template) + KV_SERIALIZE(num_matching_bits) + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<request_t> request; + + struct response_t: public rpc_response_base + { + std::vector<std::string> txids; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_response_base) + KV_SERIALIZE(txids) + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<response_t> response; + }; + } diff --git a/src/seraphis_crypto/CMakeLists.txt b/src/seraphis_crypto/CMakeLists.txt new file mode 100644 index 000000000..aeae61ad1 --- /dev/null +++ b/src/seraphis_crypto/CMakeLists.txt @@ -0,0 +1,51 @@ +# Copyright (c) 2021, The Monero Project +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, are +# permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of +# conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this list +# of conditions and the following disclaimer in the documentation and/or other +# materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be +# used to endorse or promote products derived from this software without specific +# prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +set(seraphis_crypto_sources + dummy.cpp) + +monero_find_all_headers(seraphis_crypto_headers, "${CMAKE_CURRENT_SOURCE_DIR}") + +monero_add_library(seraphis_crypto + ${seraphis_crypto_sources} + ${seraphis_crypto_headers}) + +target_link_libraries(seraphis_crypto + PUBLIC + cncrypto + common + epee + ringct + PRIVATE + ${EXTRA_LIBRARIES}) + +target_include_directories(seraphis_crypto + PUBLIC + "${CMAKE_CURRENT_SOURCE_DIR}" + PRIVATE + ${Boost_INCLUDE_DIRS}) diff --git a/src/seraphis_crypto/dummy.cpp b/src/seraphis_crypto/dummy.cpp new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/src/seraphis_crypto/dummy.cpp diff --git a/src/seraphis_crypto/sp_transcript.h b/src/seraphis_crypto/sp_transcript.h new file mode 100644 index 000000000..c2fbd88c2 --- /dev/null +++ b/src/seraphis_crypto/sp_transcript.h @@ -0,0 +1,397 @@ +// Copyright (c) 2022, 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. + +// Transcript class for assembling data that needs to be hashed. + +#pragma once + +//local headers +#include "crypto/crypto.h" +#include "cryptonote_config.h" +#include "ringct/rctTypes.h" +#include "wipeable_string.h" + +//third party headers +#include <boost/utility/string_ref.hpp> + +//standard headers +#include <list> +#include <string> +#include <type_traits> +#include <vector> + +//forward declarations + + +namespace sp +{ + +//// +// SpTranscriptBuilder +// - build a transcript +// - the user must provide a label when trying to append something to the transcript; labels are prepended to objects in +// the transcript +// - data types +// - unsigned int: uint_flag || varint(uint_variable) +// - signed int: int_flag || uchar{int_variable < 0 ? 1 : 0} || varint(abs(int_variable)) +// - byte buffer (assumed little-endian): buffer_flag || buffer_length || buffer +// - all labels are treated as byte buffers +// - named container: container_flag || container_name || data_member1 || ... || container_terminator_flag +// - list-type container (same-type elements only): list_flag || list_length || element1 || element2 || ... +// - the transcript can be used like a string via the .data() and .size() member functions +// - simple mode: exclude all labels, flags, and lengths +/// +class SpTranscriptBuilder final +{ +public: +//public member types + /// transcript builder mode + enum class Mode + { + FULL, + SIMPLE + }; + +//constructors + /// normal constructor + SpTranscriptBuilder(const std::size_t estimated_data_size, const Mode mode) : + m_mode{mode} + { + m_transcript.reserve(2 * estimated_data_size + 20); + } + +//overloaded operators + /// disable copy/move (this is a scoped manager [of the 'transcript' concept]) + SpTranscriptBuilder& operator=(SpTranscriptBuilder&&) = delete; + +//member functions + /// append a value to the transcript + template <typename T> + void append(const boost::string_ref label, const T &value) + { + this->append_impl(label, value); + } + + /// access the transcript data + const void* data() const { return m_transcript.data(); } + std::size_t size() const { return m_transcript.size(); } + +private: +//member variables + /// in simple mode, exclude labels, flags, and lengths + Mode m_mode; + /// the transcript buffer (wipeable in case it contains sensitive data) + epee::wipeable_string m_transcript; + +//private member types + /// flags for separating items added to the transcript + enum SpTranscriptBuilderFlag : unsigned char + { + UNSIGNED_INTEGER = 0, + SIGNED_INTEGER = 1, + BYTE_BUFFER = 2, + NAMED_CONTAINER = 3, + NAMED_CONTAINER_TERMINATOR = 4, + LIST_TYPE_CONTAINER = 5 + }; + +//transcript builders + void append_character(const unsigned char character) + { + m_transcript += static_cast<char>(character); + } + void append_uint(const std::uint64_t unsigned_integer) + { + unsigned char v_variable[(sizeof(std::uint64_t) * 8 + 6) / 7]; + unsigned char *v_variable_end = v_variable; + + // append uint to string as a varint + tools::write_varint(v_variable_end, unsigned_integer); + assert(v_variable_end <= v_variable + sizeof(v_variable)); + m_transcript.append(reinterpret_cast<const char*>(v_variable), v_variable_end - v_variable); + } + void append_flag(const SpTranscriptBuilderFlag flag) + { + if (m_mode == Mode::SIMPLE) + return; + + static_assert(sizeof(SpTranscriptBuilderFlag) == sizeof(unsigned char), ""); + this->append_character(static_cast<unsigned char>(flag)); + } + void append_length(const std::size_t length) + { + if (m_mode == Mode::SIMPLE) + return; + + static_assert(sizeof(std::size_t) <= sizeof(std::uint64_t), ""); + this->append_uint(static_cast<std::uint64_t>(length)); + } + void append_buffer(const void *data, const std::size_t length) + { + this->append_flag(SpTranscriptBuilderFlag::BYTE_BUFFER); + this->append_length(length); + m_transcript.append(reinterpret_cast<const char*>(data), length); + } + void append_label(const boost::string_ref label) + { + if (m_mode == Mode::SIMPLE || + label.size() == 0) + return; + + this->append_buffer(label.data(), label.size()); + } + void append_labeled_buffer(const boost::string_ref label, const void *data, const std::size_t length) + { + this->append_label(label); + this->append_buffer(data, length); + } + void begin_named_container(const boost::string_ref container_name) + { + this->append_flag(SpTranscriptBuilderFlag::NAMED_CONTAINER); + this->append_label(container_name); + } + void end_named_container() + { + this->append_flag(SpTranscriptBuilderFlag::NAMED_CONTAINER_TERMINATOR); + } + void begin_list_type_container(const std::size_t list_length) + { + this->append_flag(SpTranscriptBuilderFlag::LIST_TYPE_CONTAINER); + this->append_length(list_length); + } + +//append overloads + void append_impl(const boost::string_ref label, const rct::key &key_buffer) + { + this->append_labeled_buffer(label, key_buffer.bytes, sizeof(key_buffer)); + } + void append_impl(const boost::string_ref label, const rct::ctkey &ctkey) + { + this->append_label(label); + this->append_impl("ctmask", ctkey.mask); + this->append_impl("ctdest", ctkey.dest); + } + void append_impl(const boost::string_ref label, const crypto::secret_key &point_buffer) + { + this->append_labeled_buffer(label, point_buffer.data, sizeof(point_buffer)); + } + void append_impl(const boost::string_ref label, const crypto::public_key &scalar_buffer) + { + this->append_labeled_buffer(label, scalar_buffer.data, sizeof(scalar_buffer)); + } + void append_impl(const boost::string_ref label, const crypto::key_derivation &derivation_buffer) + { + this->append_labeled_buffer(label, derivation_buffer.data, sizeof(derivation_buffer)); + } + void append_impl(const boost::string_ref label, const crypto::key_image &key_image_buffer) + { + this->append_labeled_buffer(label, key_image_buffer.data, sizeof(key_image_buffer)); + } + void append_impl(const boost::string_ref label, const std::string &string_buffer) + { + this->append_labeled_buffer(label, string_buffer.data(), string_buffer.size()); + } + void append_impl(const boost::string_ref label, const epee::wipeable_string &string_buffer) + { + this->append_labeled_buffer(label, string_buffer.data(), string_buffer.size()); + } + void append_impl(const boost::string_ref label, const boost::string_ref string_buffer) + { + this->append_labeled_buffer(label, string_buffer.data(), string_buffer.size()); + } + template<std::size_t Sz> + void append_impl(const boost::string_ref label, const unsigned char(&uchar_buffer)[Sz]) + { + this->append_labeled_buffer(label, uchar_buffer, Sz); + } + template<std::size_t Sz> + void append_impl(const boost::string_ref label, const char(&char_buffer)[Sz]) + { + this->append_labeled_buffer(label, char_buffer, Sz); + } + void append_impl(const boost::string_ref label, const std::vector<unsigned char> &vector_buffer) + { + this->append_labeled_buffer(label, vector_buffer.data(), vector_buffer.size()); + } + void append_impl(const boost::string_ref label, const std::vector<char> &vector_buffer) + { + this->append_labeled_buffer(label, vector_buffer.data(), vector_buffer.size()); + } + void append_impl(const boost::string_ref label, const char single_character) + { + this->append_label(label); + this->append_character(static_cast<unsigned char>(single_character)); + } + void append_impl(const boost::string_ref label, const unsigned char single_character) + { + this->append_label(label); + this->append_character(single_character); + } + template<typename T, + std::enable_if_t<std::is_unsigned<T>::value, bool> = true> + void append_impl(const boost::string_ref label, const T unsigned_integer) + { + static_assert(sizeof(T) <= sizeof(std::uint64_t), "SpTranscriptBuilder: unsupported unsigned integer type."); + this->append_label(label); + this->append_flag(SpTranscriptBuilderFlag::UNSIGNED_INTEGER); + this->append_uint(unsigned_integer); + } + template<typename T, + std::enable_if_t<std::is_integral<T>::value, bool> = true, + std::enable_if_t<!std::is_unsigned<T>::value, bool> = true> + void append_impl(const boost::string_ref label, const T signed_integer) + { + using unsigned_type = std::make_unsigned<T>::type; + static_assert(sizeof(unsigned_type) <= sizeof(std::uint64_t), + "SpTranscriptBuilder: unsupported signed integer type."); + this->append_label(label); + this->append_flag(SpTranscriptBuilderFlag::SIGNED_INTEGER); + this->append_uint(static_cast<std::uint64_t>(static_cast<unsigned_type>(signed_integer))); + } + template<typename T, + std::enable_if_t<!std::is_integral<T>::value, bool> = true> + void append_impl(const boost::string_ref label, const T &named_container) + { + // named containers must satisfy two concepts: + // const boost::string_ref container_name(const T &container); + // void append_to_transcript(const T &container, SpTranscriptBuilder &transcript_inout); + //todo: container_name() and append_to_transcript() must be defined in the sp namespace, but that is not generic + this->append_label(label); + this->begin_named_container(container_name(named_container)); + append_to_transcript(named_container, *this); //non-member function assumed to be implemented elsewhere + this->end_named_container(); + } + template<typename T> + void append_impl(const boost::string_ref label, const std::vector<T> &list_container) + { + this->append_label(label); + this->begin_list_type_container(list_container.size()); + for (const T &element : list_container) + this->append_impl("", element); + } + template<typename T> + void append_impl(const boost::string_ref label, const std::list<T> &list_container) + { + this->append_label(label); + this->begin_list_type_container(list_container.size()); + for (const T &element : list_container) + this->append_impl("", element); + } +}; + +//// +// SpFSTranscript +// - build a Fiat-Shamir transcript +// - main format: prefix || domain_separator || object1_label || object1 || object2_label || object2 || ... +// note: prefix defaults to "monero" +/// +class SpFSTranscript final +{ +public: +//constructors + /// normal constructor: start building a transcript with the domain separator + SpFSTranscript(const boost::string_ref domain_separator, + const std::size_t estimated_data_size, + const boost::string_ref prefix = config::TRANSCRIPT_PREFIX) : + m_transcript_builder{15 + domain_separator.size() + estimated_data_size + prefix.size(), + SpTranscriptBuilder::Mode::FULL} + { + // transcript = prefix || domain_separator + m_transcript_builder.append("FSp", prefix); + m_transcript_builder.append("ds", domain_separator); + } + +//overloaded operators + /// disable copy/move (this is a scoped manager [of the 'transcript' concept]) + SpFSTranscript& operator=(SpFSTranscript&&) = delete; + +//member functions + /// build the transcript + template<typename T> + void append(const boost::string_ref label, const T &value) + { + m_transcript_builder.append(label, value); + } + + /// access the transcript data + const void* data() const { return m_transcript_builder.data(); } + std::size_t size() const { return m_transcript_builder.size(); } + +//member variables +private: + /// underlying transcript builder + SpTranscriptBuilder m_transcript_builder; +}; + +//// +// SpKDFTranscript +// - build a data string for a key-derivation function +// - mainly intended for short transcripts (~128 bytes or less) with fixed-length and statically ordered components +// - main format: prefix || domain_separator || object1 || object2 || ... +// - simple transcript mode: no labels, flags, or lengths +// note: prefix defaults to "monero" +/// +class SpKDFTranscript final +{ +public: +//constructors + /// normal constructor: start building a transcript with the domain separator + SpKDFTranscript(const boost::string_ref domain_separator, + const std::size_t estimated_data_size, + const boost::string_ref prefix = config::TRANSCRIPT_PREFIX) : + m_transcript_builder{10 + domain_separator.size() + estimated_data_size + prefix.size(), + SpTranscriptBuilder::Mode::SIMPLE} + { + // transcript = prefix || domain_separator + m_transcript_builder.append("", prefix); + m_transcript_builder.append("", domain_separator); + } + +//overloaded operators + /// disable copy/move (this is a scoped manager [of the 'transcript' concept]) + SpKDFTranscript& operator=(SpKDFTranscript&&) = delete; + +//member functions + /// build the transcript + template<typename T> + void append(const boost::string_ref, const T &value) + { + m_transcript_builder.append("", value); + } + + /// access the transcript data + const void* data() const { return m_transcript_builder.data(); } + std::size_t size() const { return m_transcript_builder.size(); } + +//member variables +private: + /// underlying transcript builder + SpTranscriptBuilder m_transcript_builder; +}; + +} //namespace sp diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index 0c3aaf853..7f4dbbc79 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -45,7 +45,10 @@ #include <sstream> #include <unordered_map> +#ifdef WIN32 #include <boost/locale.hpp> +#endif + #include <boost/filesystem.hpp> using namespace std; diff --git a/src/wallet/message_transporter.cpp b/src/wallet/message_transporter.cpp index 7aa765c87..b4f6ce7bd 100644 --- a/src/wallet/message_transporter.cpp +++ b/src/wallet/message_transporter.cpp @@ -61,7 +61,7 @@ namespace bitmessage_rpc KV_SERIALIZE(toAddress) KV_SERIALIZE(read) KV_SERIALIZE(msgid) - KV_SERIALIZE(message); + KV_SERIALIZE(message) KV_SERIALIZE(fromAddress) KV_SERIALIZE(receivedTime) KV_SERIALIZE(subject) diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 2c8bee2cb..76f62ce96 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -1001,6 +1001,24 @@ uint64_t num_priv_multisig_keys_post_setup(uint64_t threshold, uint64_t total) return n_multisig_keys; } +/** + * @brief Derives the chacha key to encrypt wallet cache files given the chacha key to encrypt the wallet keys files + * + * @param keys_data_key the chacha key that encrypts wallet keys files + * @return crypto::chacha_key the chacha key that encrypts the wallet cache files + */ +crypto::chacha_key derive_cache_key(const crypto::chacha_key& keys_data_key) +{ + static_assert(HASH_SIZE == sizeof(crypto::chacha_key), "Mismatched sizes of hash and chacha key"); + + crypto::chacha_key cache_key; + epee::mlocked<tools::scrubbed_arr<char, HASH_SIZE+1>> cache_key_data; + memcpy(cache_key_data.data(), &keys_data_key, HASH_SIZE); + cache_key_data[HASH_SIZE] = config::HASH_KEY_WALLET_CACHE; + cn_fast_hash(cache_key_data.data(), HASH_SIZE+1, (crypto::hash&) cache_key); + + return cache_key; +} //----------------------------------------------------------------- } //namespace @@ -3299,7 +3317,7 @@ void check_block_hard_fork_version(cryptonote::network_type nettype, uint8_t hf_ if (wallet_hard_forks[fork_index].version == hf_version) break; THROW_WALLET_EXCEPTION_IF(fork_index == wallet_num_hard_forks, error::wallet_internal_error, "Fork not found in table"); - uint64_t start_height = wallet_hard_forks[fork_index].height; + uint64_t start_height = hf_version == 1 ? 0 : wallet_hard_forks[fork_index].height; uint64_t end_height = fork_index == wallet_num_hard_forks - 1 ? std::numeric_limits<uint64_t>::max() : wallet_hard_forks[fork_index + 1].height; @@ -4348,6 +4366,10 @@ boost::optional<wallet2::keys_file_data> wallet2::get_keys_file_data(const epee: crypto::chacha_key key; crypto::generate_chacha_key(password.data(), password.size(), key, m_kdf_rounds); + // We use m_cache_key as a deterministic test to see if given key corresponds to original password + const crypto::chacha_key cache_key = derive_cache_key(key); + THROW_WALLET_EXCEPTION_IF(cache_key != m_cache_key, error::invalid_password); + if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only) { account.encrypt_viewkey(key); @@ -4380,7 +4402,7 @@ boost::optional<wallet2::keys_file_data> wallet2::get_keys_file_data(const epee: value2.SetInt(m_key_device_type); json.AddMember("key_on_device", value2, json.GetAllocator()); - value2.SetInt(watch_only ? 1 :0); // WTF ? JSON has different true and false types, and not boolean ?? + value2.SetInt((watch_only || m_watch_only) ? 1 :0); // WTF ? JSON has different true and false types, and not boolean ?? json.AddMember("watch_only", value2, json.GetAllocator()); value2.SetInt(m_multisig ? 1 :0); @@ -4575,11 +4597,8 @@ void wallet2::setup_keys(const epee::wipeable_string &password) m_account.decrypt_viewkey(key); } - static_assert(HASH_SIZE == sizeof(crypto::chacha_key), "Mismatched sizes of hash and chacha key"); - epee::mlocked<tools::scrubbed_arr<char, HASH_SIZE+1>> cache_key_data; - memcpy(cache_key_data.data(), &key, HASH_SIZE); - cache_key_data[HASH_SIZE] = config::HASH_KEY_WALLET_CACHE; - cn_fast_hash(cache_key_data.data(), HASH_SIZE+1, (crypto::hash&)m_cache_key); + m_cache_key = derive_cache_key(key); + get_ringdb_key(); } //---------------------------------------------------------------------------------------------------- @@ -4588,9 +4607,8 @@ void wallet2::change_password(const std::string &filename, const epee::wipeable_ if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only) decrypt_keys(original_password); setup_keys(new_password); - rewrite(filename, new_password); if (!filename.empty()) - store(); + store_to(filename, new_password, true); // force rewrite keys file to possible new location } //---------------------------------------------------------------------------------------------------- /*! @@ -5083,6 +5101,10 @@ void wallet2::encrypt_keys(const crypto::chacha_key &key) void wallet2::decrypt_keys(const crypto::chacha_key &key) { + // We use m_cache_key as a deterministic test to see if given key corresponds to original password + const crypto::chacha_key cache_key = derive_cache_key(key); + THROW_WALLET_EXCEPTION_IF(cache_key != m_cache_key, error::invalid_password); + m_account.encrypt_viewkey(key); m_account.decrypt_keys(key); } @@ -6141,6 +6163,20 @@ void wallet2::load(const std::string& wallet_, const epee::wipeable_string& pass error::wallet_files_doesnt_correspond, m_keys_file, m_wallet_file); } + // Wallets used to wipe, but not erase, old unused multisig key info, which lead to huge memory leaks. + // Here we erase these multisig keys if they're zero'd out to free up space. + for (auto &td : m_transfers) + { + auto mk_it = td.m_multisig_k.begin(); + while (mk_it != td.m_multisig_k.end()) + { + if (*mk_it == rct::zero()) + mk_it = td.m_multisig_k.erase(mk_it); + else + ++mk_it; + } + } + cryptonote::block genesis; generate_genesis(genesis); crypto::hash genesis_hash = get_block_hash(genesis); @@ -6230,22 +6266,32 @@ void wallet2::store() store_to("", epee::wipeable_string()); } //---------------------------------------------------------------------------------------------------- -void wallet2::store_to(const std::string &path, const epee::wipeable_string &password) +void wallet2::store_to(const std::string &path, const epee::wipeable_string &password, bool force_rewrite_keys) { trim_hashchain(); + const bool had_old_wallet_files = !m_wallet_file.empty(); + THROW_WALLET_EXCEPTION_IF(!had_old_wallet_files && path.empty(), error::wallet_internal_error, + "Cannot resave wallet to current file since wallet was not loaded from file to begin with"); + // if file is the same, we do: - // 1. save wallet to the *.new file - // 2. remove old wallet file - // 3. rename *.new to wallet_name + // 1. overwrite the keys file iff force_rewrite_keys is specified + // 2. save cache to the *.new file + // 3. rename *.new to wallet_name, replacing old cache file + // else we do: + // 1. prepare new file names with "path" variable + // 2. store new keys files + // 3. remove old keys file + // 4. store new cache file + // 5. remove old cache file // handle if we want just store wallet state to current files (ex store() replacement); - bool same_file = true; - if (!path.empty()) + bool same_file = had_old_wallet_files && path.empty(); + if (had_old_wallet_files && !path.empty()) { - std::string canonical_path = boost::filesystem::canonical(m_wallet_file).string(); - size_t pos = canonical_path.find(path); - same_file = pos != std::string::npos; + const std::string canonical_old_path = boost::filesystem::canonical(m_wallet_file).string(); + const std::string canonical_new_path = boost::filesystem::weakly_canonical(path).string(); + same_file = canonical_old_path == canonical_new_path; } @@ -6266,7 +6312,7 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas } // get wallet cache data - boost::optional<wallet2::cache_file_data> cache_file_data = get_cache_file_data(password); + boost::optional<wallet2::cache_file_data> cache_file_data = get_cache_file_data(); THROW_WALLET_EXCEPTION_IF(cache_file_data == boost::none, error::wallet_internal_error, "failed to generate wallet cache data"); const std::string new_file = same_file ? m_wallet_file + ".new" : path; @@ -6275,12 +6321,20 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas const std::string old_address_file = m_wallet_file + ".address.txt"; const std::string old_mms_file = m_mms_file; - // save keys to the new file - // if we here, main wallet file is saved and we only need to save keys and address files - if (!same_file) { + if (!same_file) + { prepare_file_names(path); - bool r = store_keys(m_keys_file, password, false); + } + + if (!same_file || force_rewrite_keys) + { + bool r = store_keys(m_keys_file, password, m_watch_only); THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); + } + + if (!same_file && had_old_wallet_files) + { + bool r = false; if (boost::filesystem::exists(old_address_file)) { // save address to the new file @@ -6293,11 +6347,6 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas LOG_ERROR("error removing file: " << old_address_file); } } - // remove old wallet file - r = boost::filesystem::remove(old_file); - if (!r) { - LOG_ERROR("error removing file: " << old_file); - } // remove old keys file r = boost::filesystem::remove(old_keys_file); if (!r) { @@ -6311,8 +6360,9 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas LOG_ERROR("error removing file: " << old_mms_file); } } - } else { - // save to new file + } + + // Save cache to new file. If storing to the same file, the temp path has the ".new" extension #ifdef WIN32 // On Windows avoid using std::ofstream which does not work with UTF-8 filenames // The price to pay is temporary higher memory consumption for string stream + binary archive @@ -6332,10 +6382,20 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas THROW_WALLET_EXCEPTION_IF(!success || !ostr.good(), error::file_save_error, new_file); #endif + if (same_file) + { // here we have "*.new" file, we need to rename it to be without ".new" std::error_code e = tools::replace_file(new_file, m_wallet_file); THROW_WALLET_EXCEPTION_IF(e, error::file_save_error, m_wallet_file, e); } + else if (!same_file && had_old_wallet_files) + { + // remove old wallet file + bool r = boost::filesystem::remove(old_file); + if (!r) { + LOG_ERROR("error removing file: " << old_file); + } + } if (m_message_store.get_active()) { @@ -6345,7 +6405,7 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas } } //---------------------------------------------------------------------------------------------------- -boost::optional<wallet2::cache_file_data> wallet2::get_cache_file_data(const epee::wipeable_string &passwords) +boost::optional<wallet2::cache_file_data> wallet2::get_cache_file_data() { trim_hashchain(); try @@ -6990,7 +7050,10 @@ void wallet2::commit_tx(pending_tx& ptx) // tx generated, get rid of used k values for (size_t idx: ptx.selected_transfers) + { memwipe(m_transfers[idx].m_multisig_k.data(), m_transfers[idx].m_multisig_k.size() * sizeof(m_transfers[idx].m_multisig_k[0])); + m_transfers[idx].m_multisig_k.clear(); + } //fee includes dust if dust policy specified it. LOG_PRINT_L1("Transaction successfully sent. <" << txid << ">" << ENDL @@ -7494,7 +7557,10 @@ std::string wallet2::save_multisig_tx(multisig_tx_set txs) // txes generated, get rid of used k values for (size_t n = 0; n < txs.m_ptx.size(); ++n) for (size_t idx: txs.m_ptx[n].construction_data.selected_transfers) + { memwipe(m_transfers[idx].m_multisig_k.data(), m_transfers[idx].m_multisig_k.size() * sizeof(m_transfers[idx].m_multisig_k[0])); + m_transfers[idx].m_multisig_k.clear(); + } // zero out some data we don't want to share for (auto &ptx: txs.m_ptx) @@ -7818,7 +7884,10 @@ bool wallet2::sign_multisig_tx(multisig_tx_set &exported_txs, std::vector<crypto // inputs in the transactions worked on here) for (size_t n = 0; n < exported_txs.m_ptx.size(); ++n) for (size_t idx: exported_txs.m_ptx[n].construction_data.selected_transfers) + { memwipe(m_transfers[idx].m_multisig_k.data(), m_transfers[idx].m_multisig_k.size() * sizeof(m_transfers[idx].m_multisig_k[0])); + m_transfers[idx].m_multisig_k.clear(); + } exported_txs.m_signers.insert(get_multisig_signer_public_key()); @@ -13492,7 +13561,10 @@ cryptonote::blobdata wallet2::export_multisig() transfer_details &td = m_transfers[n]; crypto::key_image ki; if (td.m_multisig_k.size()) + { memwipe(td.m_multisig_k.data(), td.m_multisig_k.size() * sizeof(td.m_multisig_k[0])); + td.m_multisig_k.clear(); + } info[n].m_LR.clear(); info[n].m_partial_key_images.clear(); diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 684bbd1ec..d93b9b9fb 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -933,22 +933,32 @@ private: /*! * \brief store_to Stores wallet to another file(s), deleting old ones * \param path Path to the wallet file (keys and address filenames will be generated based on this filename) - * \param password Password to protect new wallet (TODO: probably better save the password in the wallet object?) + * \param password Password that currently locks the wallet + * \param force_rewrite_keys if true, always rewrite keys file + * + * Leave both "path" and "password" blank to restore the cache file to the current position in the disk + * (which is the same as calling `store()`). If you want to store the wallet with a new password, + * use the method `change_password()`. + * + * Normally the keys file is not overwritten when storing, except when force_rewrite_keys is true + * or when `path` is a new wallet file. + * + * \throw error::invalid_password If storing keys file and old password is incorrect */ - void store_to(const std::string &path, const epee::wipeable_string &password); + void store_to(const std::string &path, const epee::wipeable_string &password, bool force_rewrite_keys = false); /*! * \brief get_keys_file_data Get wallet keys data which can be stored to a wallet file. - * \param password Password of the encrypted wallet buffer (TODO: probably better save the password in the wallet object?) + * \param password Password that currently locks the wallet * \param watch_only true to include only view key, false to include both spend and view keys * \return Encrypted wallet keys data which can be stored to a wallet file + * \throw error::invalid_password if password does not match current wallet */ boost::optional<wallet2::keys_file_data> get_keys_file_data(const epee::wipeable_string& password, bool watch_only); /*! * \brief get_cache_file_data Get wallet cache data which can be stored to a wallet file. - * \param password Password to protect the wallet cache data (TODO: probably better save the password in the wallet object?) - * \return Encrypted wallet cache data which can be stored to a wallet file + * \return Encrypted wallet cache data which can be stored to a wallet file (using current password) */ - boost::optional<wallet2::cache_file_data> get_cache_file_data(const epee::wipeable_string& password); + boost::optional<wallet2::cache_file_data> get_cache_file_data(); std::string path() const; diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h index 282035052..97fc0a278 100644 --- a/src/wallet/wallet_rpc_server.h +++ b/src/wallet/wallet_rpc_server.h @@ -35,7 +35,6 @@ #include <string> #include "common/util.h" #include "net/http_server_impl_base.h" -#include "math_helper.h" #include "wallet_rpc_server_commands_defs.h" #include "wallet2.h" diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h index c00271030..33afefb80 100644 --- a/src/wallet/wallet_rpc_server_commands_defs.h +++ b/src/wallet/wallet_rpc_server_commands_defs.h @@ -68,8 +68,8 @@ namespace wallet_rpc BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(account_index) KV_SERIALIZE(address_indices) - KV_SERIALIZE_OPT(all_accounts, false); - KV_SERIALIZE_OPT(strict, false); + KV_SERIALIZE_OPT(all_accounts, false) + KV_SERIALIZE_OPT(strict, false) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; @@ -348,9 +348,9 @@ namespace wallet_rpc std::vector<uint32_t> accounts; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(tag); - KV_SERIALIZE(label); - KV_SERIALIZE(accounts); + KV_SERIALIZE(tag) + KV_SERIALIZE(label) + KV_SERIALIZE(accounts) END_KV_SERIALIZE_MAP() }; @@ -1029,7 +1029,7 @@ namespace wallet_rpc KV_SERIALIZE(tx_hash) KV_SERIALIZE(subaddr_index) KV_SERIALIZE(key_image) - KV_SERIALIZE(pubkey); + KV_SERIALIZE(pubkey) KV_SERIALIZE(block_height) KV_SERIALIZE(frozen) KV_SERIALIZE(unlocked) @@ -1165,7 +1165,7 @@ namespace wallet_rpc bool hard; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE_OPT(hard, false); + KV_SERIALIZE_OPT(hard, false) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; @@ -1408,21 +1408,21 @@ namespace wallet_rpc uint64_t suggested_confirmations_threshold; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(txid); - KV_SERIALIZE(payment_id); - KV_SERIALIZE(height); - KV_SERIALIZE(timestamp); - KV_SERIALIZE(amount); - KV_SERIALIZE(amounts); - KV_SERIALIZE(fee); - KV_SERIALIZE(note); - KV_SERIALIZE(destinations); - KV_SERIALIZE(type); + KV_SERIALIZE(txid) + KV_SERIALIZE(payment_id) + KV_SERIALIZE(height) + KV_SERIALIZE(timestamp) + KV_SERIALIZE(amount) + KV_SERIALIZE(amounts) + KV_SERIALIZE(fee) + KV_SERIALIZE(note) + KV_SERIALIZE(destinations) + KV_SERIALIZE(type) KV_SERIALIZE(unlock_time) KV_SERIALIZE(locked) - KV_SERIALIZE(subaddr_index); - KV_SERIALIZE(subaddr_indices); - KV_SERIALIZE(address); + KV_SERIALIZE(subaddr_index) + KV_SERIALIZE(subaddr_indices) + KV_SERIALIZE(address) KV_SERIALIZE(double_spend_seen) KV_SERIALIZE_OPT(confirmations, (uint64_t)0) KV_SERIALIZE_OPT(suggested_confirmations_threshold, (uint64_t)0) @@ -1559,17 +1559,17 @@ namespace wallet_rpc bool all_accounts; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(in); - KV_SERIALIZE(out); - KV_SERIALIZE(pending); - KV_SERIALIZE(failed); - KV_SERIALIZE(pool); - KV_SERIALIZE(filter_by_height); - KV_SERIALIZE(min_height); - KV_SERIALIZE_OPT(max_height, (uint64_t)CRYPTONOTE_MAX_BLOCK_NUMBER); - KV_SERIALIZE(account_index); - KV_SERIALIZE(subaddr_indices); - KV_SERIALIZE_OPT(all_accounts, false); + KV_SERIALIZE(in) + KV_SERIALIZE(out) + KV_SERIALIZE(pending) + KV_SERIALIZE(failed) + KV_SERIALIZE(pool) + KV_SERIALIZE(filter_by_height) + KV_SERIALIZE(min_height) + KV_SERIALIZE_OPT(max_height, (uint64_t)CRYPTONOTE_MAX_BLOCK_NUMBER) + KV_SERIALIZE(account_index) + KV_SERIALIZE(subaddr_indices) + KV_SERIALIZE_OPT(all_accounts, false) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; @@ -1583,11 +1583,11 @@ namespace wallet_rpc std::list<transfer_entry> pool; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(in); - KV_SERIALIZE(out); - KV_SERIALIZE(pending); - KV_SERIALIZE(failed); - KV_SERIALIZE(pool); + KV_SERIALIZE(in) + KV_SERIALIZE(out) + KV_SERIALIZE(pending) + KV_SERIALIZE(failed) + KV_SERIALIZE(pool) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -1601,7 +1601,7 @@ namespace wallet_rpc uint32_t account_index; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(txid); + KV_SERIALIZE(txid) KV_SERIALIZE_OPT(account_index, (uint32_t)0) END_KV_SERIALIZE_MAP() }; @@ -1613,8 +1613,8 @@ namespace wallet_rpc std::list<transfer_entry> transfers; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(transfer); - KV_SERIALIZE(transfers); + KV_SERIALIZE(transfer) + KV_SERIALIZE(transfers) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -1643,7 +1643,7 @@ namespace wallet_rpc std::string signature; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(signature); + KV_SERIALIZE(signature) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -1658,9 +1658,9 @@ namespace wallet_rpc std::string signature; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(data); - KV_SERIALIZE(address); - KV_SERIALIZE(signature); + KV_SERIALIZE(data) + KV_SERIALIZE(address) + KV_SERIALIZE(signature) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; @@ -1673,10 +1673,10 @@ namespace wallet_rpc std::string signature_type; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(good); - KV_SERIALIZE(version); - KV_SERIALIZE(old); - KV_SERIALIZE(signature_type); + KV_SERIALIZE(good) + KV_SERIALIZE(version) + KV_SERIALIZE(old) + KV_SERIALIZE(signature_type) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -1703,7 +1703,7 @@ namespace wallet_rpc std::string outputs_data_hex; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(outputs_data_hex); + KV_SERIALIZE(outputs_data_hex) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -1716,7 +1716,7 @@ namespace wallet_rpc std::string outputs_data_hex; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(outputs_data_hex); + KV_SERIALIZE(outputs_data_hex) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; @@ -1726,7 +1726,7 @@ namespace wallet_rpc uint64_t num_imported; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(num_imported); + KV_SERIALIZE(num_imported) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -1739,7 +1739,7 @@ namespace wallet_rpc bool all; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE_OPT(all, false); + KV_SERIALIZE_OPT(all, false) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; @@ -1750,8 +1750,8 @@ namespace wallet_rpc std::string signature; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(key_image); - KV_SERIALIZE(signature); + KV_SERIALIZE(key_image) + KV_SERIALIZE(signature) END_KV_SERIALIZE_MAP() }; @@ -1761,8 +1761,8 @@ namespace wallet_rpc std::vector<signed_key_image> signed_key_images; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(offset); - KV_SERIALIZE(signed_key_images); + KV_SERIALIZE(offset) + KV_SERIALIZE(signed_key_images) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -1776,8 +1776,8 @@ namespace wallet_rpc std::string signature; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(key_image); - KV_SERIALIZE(signature); + KV_SERIALIZE(key_image) + KV_SERIALIZE(signature) END_KV_SERIALIZE_MAP() }; @@ -1787,8 +1787,8 @@ namespace wallet_rpc std::vector<signed_key_image> signed_key_images; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE_OPT(offset, (uint32_t)0); - KV_SERIALIZE(signed_key_images); + KV_SERIALIZE_OPT(offset, (uint32_t)0) + KV_SERIALIZE(signed_key_images) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; @@ -1817,11 +1817,11 @@ namespace wallet_rpc std::string recipient_name; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(address); - KV_SERIALIZE(payment_id); - KV_SERIALIZE(amount); - KV_SERIALIZE(tx_description); - KV_SERIALIZE(recipient_name); + KV_SERIALIZE(address) + KV_SERIALIZE(payment_id) + KV_SERIALIZE(amount) + KV_SERIALIZE(tx_description) + KV_SERIALIZE(recipient_name) END_KV_SERIALIZE_MAP() }; @@ -1861,8 +1861,8 @@ namespace wallet_rpc std::vector<std::string> unknown_parameters; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(uri); - KV_SERIALIZE(unknown_parameters); + KV_SERIALIZE(uri) + KV_SERIALIZE(unknown_parameters) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -1887,7 +1887,7 @@ namespace wallet_rpc uint64_t index; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(index); + KV_SERIALIZE(index) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -1964,7 +1964,7 @@ namespace wallet_rpc uint64_t index; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(index); + KV_SERIALIZE(index) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; @@ -2012,8 +2012,8 @@ namespace wallet_rpc bool received_money; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(blocks_fetched); - KV_SERIALIZE(received_money); + KV_SERIALIZE(blocks_fetched) + KV_SERIALIZE(received_money) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; |