diff options
Diffstat (limited to 'src')
63 files changed, 1793 insertions, 616 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/common/dns_utils.cpp b/src/common/dns_utils.cpp index 9c10c420c..6fc784fb2 100644 --- a/src/common/dns_utils.cpp +++ b/src/common/dns_utils.cpp @@ -28,6 +28,7 @@ #include "common/dns_utils.h" // check local first (in the event of static or in-source compilation of libunbound) +#include "misc_language.h" #include "unbound.h" #include <deque> @@ -195,36 +196,7 @@ boost::optional<std::string> tlsa_to_string(const char* src, size_t len) return boost::none; return std::string(src, len); } - -// custom smart pointer. -// TODO: see if std::auto_ptr and the like support custom destructors -template<typename type, void (*freefunc)(type*)> -class scoped_ptr -{ -public: - scoped_ptr(): - ptr(nullptr) - { - } - scoped_ptr(type *p): - ptr(p) - { - } - ~scoped_ptr() - { - freefunc(ptr); - } - operator type *() { return ptr; } - type **operator &() { return &ptr; } - type *operator->() { return ptr; } - operator const type*() const { return &ptr; } - -private: - type* ptr; -}; - -typedef class scoped_ptr<ub_result,ub_resolve_free> ub_result_ptr; - + struct DNSResolverData { ub_ctx* m_ub_context; @@ -327,10 +299,14 @@ std::vector<std::string> DNSResolver::get_record(const std::string& url, int rec std::vector<std::string> addresses; dnssec_available = false; dnssec_valid = false; - - // destructor takes care of cleanup - ub_result_ptr result; - + + ub_result *result; + // Make sure we are cleaning after result. + epee::misc_utils::auto_scope_leave_caller scope_exit_handler = + epee::misc_utils::create_scope_leave_handler([&](){ + ub_resolve_free(result); + }); + MDEBUG("Performing DNSSEC " << get_record_name(record_type) << " record query for " << url); // call DNS resolver, blocking. if return value not zero, something went wrong 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/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp index 9aeee3d55..c86e7d848 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.cpp +++ b/src/cryptonote_basic/cryptonote_format_utils.cpp @@ -739,22 +739,28 @@ namespace cryptonote return true; } //--------------------------------------------------------------- - bool add_mm_merkle_root_to_tx_extra(std::vector<uint8_t>& tx_extra, const crypto::hash& mm_merkle_root, size_t mm_merkle_tree_depth) + bool add_mm_merkle_root_to_tx_extra(std::vector<uint8_t>& tx_extra, const crypto::hash& mm_merkle_root, uint64_t mm_merkle_tree_depth) { - CHECK_AND_ASSERT_MES(mm_merkle_tree_depth < 32, false, "merge mining merkle tree depth should be less than 32"); size_t start_pos = tx_extra.size(); - tx_extra.resize(tx_extra.size() + 3 + 32); + static const size_t max_varint_size = 16; + tx_extra.resize(tx_extra.size() + 2 + 32 + max_varint_size); //write tag tx_extra[start_pos] = TX_EXTRA_MERGE_MINING_TAG; //write data size ++start_pos; - tx_extra[start_pos] = 33; - //write depth varint (always one byte here) + const off_t len_bytes = start_pos; + // one byte placeholder for length since we'll only know the size later after writing a varint + tx_extra[start_pos] = 0; + //write depth varint ++start_pos; - tx_extra[start_pos] = mm_merkle_tree_depth; + uint8_t *ptr = &tx_extra[start_pos], *start = ptr; + tools::write_varint(ptr, mm_merkle_tree_depth); //write data - ++start_pos; + const size_t varint_size = ptr - start; + start_pos += varint_size; memcpy(&tx_extra[start_pos], &mm_merkle_root, 32); + tx_extra.resize(tx_extra.size() - (max_varint_size - varint_size)); + tx_extra[len_bytes] = 32 + varint_size; return true; } //--------------------------------------------------------------- diff --git a/src/cryptonote_basic/cryptonote_format_utils.h b/src/cryptonote_basic/cryptonote_format_utils.h index 33fec238e..bd4dcd83d 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.h +++ b/src/cryptonote_basic/cryptonote_format_utils.h @@ -83,7 +83,7 @@ namespace cryptonote std::vector<crypto::public_key> get_additional_tx_pub_keys_from_extra(const transaction_prefix& tx); bool add_additional_tx_pub_keys_to_extra(std::vector<uint8_t>& tx_extra, const std::vector<crypto::public_key>& additional_pub_keys); bool add_extra_nonce_to_tx_extra(std::vector<uint8_t>& tx_extra, const blobdata& extra_nonce); - bool add_mm_merkle_root_to_tx_extra(std::vector<uint8_t>& tx_extra, const crypto::hash& mm_merkle_root, size_t mm_merkle_tree_depth); + bool add_mm_merkle_root_to_tx_extra(std::vector<uint8_t>& tx_extra, const crypto::hash& mm_merkle_root, uint64_t mm_merkle_tree_depth); bool remove_field_from_tx_extra(std::vector<uint8_t>& tx_extra, const std::type_info &type); void set_payment_id_to_tx_extra_nonce(blobdata& extra_nonce, const crypto::hash& payment_id); void set_encrypted_payment_id_to_tx_extra_nonce(blobdata& extra_nonce, const crypto::hash8& payment_id); diff --git a/src/cryptonote_basic/merge_mining.cpp b/src/cryptonote_basic/merge_mining.cpp index 0e649e095..3e26c00e3 100644 --- a/src/cryptonote_basic/merge_mining.cpp +++ b/src/cryptonote_basic/merge_mining.cpp @@ -71,21 +71,21 @@ uint32_t get_path_from_aux_slot(uint32_t slot, uint32_t n_aux_chains) return path; } //--------------------------------------------------------------- -uint32_t encode_mm_depth(uint32_t n_aux_chains, uint32_t nonce) +uint64_t encode_mm_depth(uint32_t n_aux_chains, uint32_t nonce) { CHECK_AND_ASSERT_THROW_MES(n_aux_chains > 0, "n_aux_chains is 0"); + CHECK_AND_ASSERT_THROW_MES(n_aux_chains <= 256, "n_aux_chains is too large"); // how many bits to we need to representing n_aux_chains - 1 uint32_t n_bits = 1; - while ((1u << n_bits) < n_aux_chains && n_bits < 16) + while ((1u << n_bits) < n_aux_chains) ++n_bits; - CHECK_AND_ASSERT_THROW_MES(n_bits <= 16, "Way too many bits required"); - const uint32_t depth = (n_bits - 1) | ((n_aux_chains - 1) << 3) | (nonce << (3 + n_bits)); + const uint64_t depth = (n_bits - 1) | ((n_aux_chains - 1) << 3) | (((uint64_t)nonce) << (3 + n_bits)); return depth; } //--------------------------------------------------------------- -bool decode_mm_depth(uint32_t depth, uint32_t &n_aux_chains, uint32_t &nonce) +bool decode_mm_depth(uint64_t depth, uint32_t &n_aux_chains, uint32_t &nonce) { const uint32_t n_bits = 1 + (depth & 7); n_aux_chains = 1 + (depth >> 3 & ((1 << n_bits) - 1)); diff --git a/src/cryptonote_basic/merge_mining.h b/src/cryptonote_basic/merge_mining.h index acb70ebf0..ef4c92d67 100644 --- a/src/cryptonote_basic/merge_mining.h +++ b/src/cryptonote_basic/merge_mining.h @@ -36,6 +36,6 @@ namespace cryptonote { uint32_t get_aux_slot(const crypto::hash &id, uint32_t nonce, uint32_t n_aux_chains); uint32_t get_path_from_aux_slot(uint32_t slot, uint32_t n_aux_chains); - uint32_t encode_mm_depth(uint32_t n_aux_chains, uint32_t nonce); - bool decode_mm_depth(uint32_t depth, uint32_t &n_aux_chains, uint32_t &nonce); + uint64_t encode_mm_depth(uint32_t n_aux_chains, uint32_t nonce); + bool decode_mm_depth(uint64_t depth, uint32_t &n_aux_chains, uint32_t &nonce); } 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_basic/tx_extra.h b/src/cryptonote_basic/tx_extra.h index af6ee5197..cbf942d4c 100644 --- a/src/cryptonote_basic/tx_extra.h +++ b/src/cryptonote_basic/tx_extra.h @@ -52,7 +52,7 @@ namespace cryptonote // load template <template <bool> class Archive> - bool do_serialize(Archive<false>& ar) + bool member_do_serialize(Archive<false>& ar) { // size - 1 - because of variant tag for (size = 1; size <= TX_EXTRA_PADDING_MAX_COUNT; ++size) @@ -73,7 +73,7 @@ namespace cryptonote // store template <template <bool> class Archive> - bool do_serialize(Archive<true>& ar) + bool member_do_serialize(Archive<true>& ar) { if(TX_EXTRA_PADDING_MAX_COUNT < size) return false; @@ -124,12 +124,12 @@ namespace cryptonote END_SERIALIZE() }; - size_t depth; + uint64_t depth; crypto::hash merkle_root; // load template <template <bool> class Archive> - bool do_serialize(Archive<false>& ar) + bool member_do_serialize(Archive<false>& ar) { std::string field; if(!::do_serialize(ar, field)) @@ -142,7 +142,7 @@ namespace cryptonote // store template <template <bool> class Archive> - bool do_serialize(Archive<true>& ar) + bool member_do_serialize(Archive<true>& ar) { std::ostringstream oss; binary_archive<true> oar(oss); 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..9a0b02f70 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -2082,11 +2082,12 @@ namespace cryptonote } crypto::hash merkle_root; - size_t merkle_tree_depth = 0; std::vector<std::pair<crypto::hash, crypto::hash>> aux_pow; std::vector<crypto::hash> aux_pow_raw; + std::vector<crypto::hash> aux_pow_id_raw; aux_pow.reserve(req.aux_pow.size()); - aux_pow_raw.reserve(req.aux_pow.size()); + aux_pow_raw.resize(req.aux_pow.size()); + aux_pow_id_raw.resize(req.aux_pow.size()); for (const auto &s: req.aux_pow) { aux_pow.push_back({}); @@ -2102,7 +2103,6 @@ namespace cryptonote error_resp.message = "Invalid aux pow hash"; return false; } - aux_pow_raw.push_back(aux_pow.back().second); } size_t path_domain = 1; @@ -2111,11 +2111,14 @@ namespace cryptonote uint32_t nonce; const uint32_t max_nonce = 65535; bool collision = true; + std::vector<uint32_t> slots(aux_pow.size()); for (nonce = 0; nonce <= max_nonce; ++nonce) { - std::vector<bool> slots(aux_pow.size(), false); + std::vector<bool> slot_seen(aux_pow.size(), false); collision = false; for (size_t idx = 0; idx < aux_pow.size(); ++idx) + slots[idx] = 0xffffffff; + for (size_t idx = 0; idx < aux_pow.size(); ++idx) { const uint32_t slot = cryptonote::get_aux_slot(aux_pow[idx].first, nonce, aux_pow.size()); if (slot >= aux_pow.size()) @@ -2124,12 +2127,13 @@ namespace cryptonote error_resp.message = "Computed slot is out of range"; return false; } - if (slots[slot]) + if (slot_seen[slot]) { collision = true; break; } - slots[slot] = true; + slot_seen[slot] = true; + slots[idx] = slot; } if (!collision) break; @@ -2141,6 +2145,19 @@ namespace cryptonote return false; } + // set the order determined above + for (size_t i = 0; i < aux_pow.size(); ++i) + { + if (slots[i] >= aux_pow.size()) + { + error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; + error_resp.message = "Slot value out of range"; + return false; + } + aux_pow_raw[slots[i]] = aux_pow[i].second; + aux_pow_id_raw[slots[i]] = aux_pow[i].first; + } + crypto::tree_hash((const char(*)[crypto::HASH_SIZE])aux_pow_raw.data(), aux_pow_raw.size(), merkle_root.data); res.merkle_root = epee::string_tools::pod_to_hex(merkle_root); res.merkle_tree_depth = cryptonote::encode_mm_depth(aux_pow.size(), nonce); @@ -2167,7 +2184,7 @@ namespace cryptonote error_resp.message = "Error removing existing merkle root"; return false; } - if (!add_mm_merkle_root_to_tx_extra(b.miner_tx.extra, merkle_root, merkle_tree_depth)) + if (!add_mm_merkle_root_to_tx_extra(b.miner_tx.extra, merkle_root, res.merkle_tree_depth)) { error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; error_resp.message = "Error adding merkle root"; @@ -2181,7 +2198,8 @@ namespace cryptonote res.blocktemplate_blob = string_tools::buff_to_hex_nodelimer(block_blob); res.blockhashing_blob = string_tools::buff_to_hex_nodelimer(hashing_blob); - res.aux_pow = req.aux_pow; + for (size_t i = 0; i < aux_pow_raw.size(); ++i) + res.aux_pow.push_back({epee::string_tools::pod_to_hex(aux_pow_id_raw[i]), epee::string_tools::pod_to_hex(aux_pow_raw[i])}); res.status = CORE_RPC_STATUS_OK; return true; } @@ -2358,6 +2376,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 +3554,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..2a0a6201d 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; @@ -1094,7 +1094,7 @@ namespace cryptonote blobdata blocktemplate_blob; blobdata blockhashing_blob; std::string merkle_root; - uint32_t merkle_tree_depth; + uint64_t merkle_tree_depth; std::vector<aux_pow_t> aux_pow; BEGIN_KV_SERIALIZE_MAP() @@ -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/rpc/daemon_messages.cpp b/src/rpc/daemon_messages.cpp index 0da22f15f..36528fcea 100644 --- a/src/rpc/daemon_messages.cpp +++ b/src/rpc/daemon_messages.cpp @@ -48,6 +48,11 @@ void GetHeight::Response::doToJson(rapidjson::Writer<epee::byte_stream>& dest) c void GetHeight::Response::fromJson(const rapidjson::Value& val) { + if (!val.IsObject()) + { + throw json::WRONG_TYPE("json object"); + } + GET_FROM_JSON_OBJECT(val, height, height); } @@ -61,6 +66,11 @@ void GetBlocksFast::Request::doToJson(rapidjson::Writer<epee::byte_stream>& dest void GetBlocksFast::Request::fromJson(const rapidjson::Value& val) { + if (!val.IsObject()) + { + throw json::WRONG_TYPE("json object"); + } + GET_FROM_JSON_OBJECT(val, block_ids, block_ids); GET_FROM_JSON_OBJECT(val, start_height, start_height); GET_FROM_JSON_OBJECT(val, prune, prune); @@ -76,6 +86,11 @@ void GetBlocksFast::Response::doToJson(rapidjson::Writer<epee::byte_stream>& des void GetBlocksFast::Response::fromJson(const rapidjson::Value& val) { + if (!val.IsObject()) + { + throw json::WRONG_TYPE("json object"); + } + GET_FROM_JSON_OBJECT(val, blocks, blocks); GET_FROM_JSON_OBJECT(val, start_height, start_height); GET_FROM_JSON_OBJECT(val, current_height, current_height); @@ -91,6 +106,11 @@ void GetHashesFast::Request::doToJson(rapidjson::Writer<epee::byte_stream>& dest void GetHashesFast::Request::fromJson(const rapidjson::Value& val) { + if (!val.IsObject()) + { + throw json::WRONG_TYPE("json object"); + } + GET_FROM_JSON_OBJECT(val, known_hashes, known_hashes); GET_FROM_JSON_OBJECT(val, start_height, start_height); } @@ -100,10 +120,16 @@ void GetHashesFast::Response::doToJson(rapidjson::Writer<epee::byte_stream>& des INSERT_INTO_JSON_OBJECT(dest, hashes, hashes); INSERT_INTO_JSON_OBJECT(dest, start_height, start_height); INSERT_INTO_JSON_OBJECT(dest, current_height, current_height); + } void GetHashesFast::Response::fromJson(const rapidjson::Value& val) { + if (!val.IsObject()) + { + throw json::WRONG_TYPE("json object"); + } + GET_FROM_JSON_OBJECT(val, hashes, hashes); GET_FROM_JSON_OBJECT(val, start_height, start_height); GET_FROM_JSON_OBJECT(val, current_height, current_height); @@ -117,6 +143,11 @@ void GetTransactions::Request::doToJson(rapidjson::Writer<epee::byte_stream>& de void GetTransactions::Request::fromJson(const rapidjson::Value& val) { + if (!val.IsObject()) + { + throw json::WRONG_TYPE("json object"); + } + GET_FROM_JSON_OBJECT(val, tx_hashes, tx_hashes); } @@ -128,6 +159,11 @@ void GetTransactions::Response::doToJson(rapidjson::Writer<epee::byte_stream>& d void GetTransactions::Response::fromJson(const rapidjson::Value& val) { + if (!val.IsObject()) + { + throw json::WRONG_TYPE("json object"); + } + GET_FROM_JSON_OBJECT(val, txs, txs); GET_FROM_JSON_OBJECT(val, missed_hashes, missed_hashes); } @@ -140,6 +176,11 @@ void KeyImagesSpent::Request::doToJson(rapidjson::Writer<epee::byte_stream>& des void KeyImagesSpent::Request::fromJson(const rapidjson::Value& val) { + if (!val.IsObject()) + { + throw json::WRONG_TYPE("json object"); + } + GET_FROM_JSON_OBJECT(val, key_images, key_images); } @@ -150,6 +191,11 @@ void KeyImagesSpent::Response::doToJson(rapidjson::Writer<epee::byte_stream>& de void KeyImagesSpent::Response::fromJson(const rapidjson::Value& val) { + if (!val.IsObject()) + { + throw json::WRONG_TYPE("json object"); + } + GET_FROM_JSON_OBJECT(val, spent_status, spent_status); } @@ -161,6 +207,11 @@ void GetTxGlobalOutputIndices::Request::doToJson(rapidjson::Writer<epee::byte_st void GetTxGlobalOutputIndices::Request::fromJson(const rapidjson::Value& val) { + if (!val.IsObject()) + { + throw json::WRONG_TYPE("json object"); + } + GET_FROM_JSON_OBJECT(val, tx_hash, tx_hash); } @@ -171,6 +222,11 @@ void GetTxGlobalOutputIndices::Response::doToJson(rapidjson::Writer<epee::byte_s void GetTxGlobalOutputIndices::Response::fromJson(const rapidjson::Value& val) { + if (!val.IsObject()) + { + throw json::WRONG_TYPE("json object"); + } + GET_FROM_JSON_OBJECT(val, output_indices, output_indices); } @@ -182,6 +238,11 @@ void SendRawTx::Request::doToJson(rapidjson::Writer<epee::byte_stream>& dest) co void SendRawTx::Request::fromJson(const rapidjson::Value& val) { + if (!val.IsObject()) + { + throw json::WRONG_TYPE("json object"); + } + GET_FROM_JSON_OBJECT(val, tx, tx); GET_FROM_JSON_OBJECT(val, relay, relay); } @@ -194,6 +255,11 @@ void SendRawTx::Response::doToJson(rapidjson::Writer<epee::byte_stream>& dest) c void SendRawTx::Response::fromJson(const rapidjson::Value& val) { + if (!val.IsObject()) + { + throw json::WRONG_TYPE("json object"); + } + GET_FROM_JSON_OBJECT(val, relayed, relayed); } @@ -205,6 +271,11 @@ void SendRawTxHex::Request::doToJson(rapidjson::Writer<epee::byte_stream>& dest) void SendRawTxHex::Request::fromJson(const rapidjson::Value& val) { + if (!val.IsObject()) + { + throw json::WRONG_TYPE("json object"); + } + GET_FROM_JSON_OBJECT(val, tx_as_hex, tx_as_hex); GET_FROM_JSON_OBJECT(val, relay, relay); } @@ -219,6 +290,11 @@ void StartMining::Request::doToJson(rapidjson::Writer<epee::byte_stream>& dest) void StartMining::Request::fromJson(const rapidjson::Value& val) { + if (!val.IsObject()) + { + throw json::WRONG_TYPE("json object"); + } + GET_FROM_JSON_OBJECT(val, miner_address, miner_address); GET_FROM_JSON_OBJECT(val, threads_count, threads_count); GET_FROM_JSON_OBJECT(val, do_background_mining, do_background_mining); @@ -266,6 +342,11 @@ void MiningStatus::Response::doToJson(rapidjson::Writer<epee::byte_stream>& dest void MiningStatus::Response::fromJson(const rapidjson::Value& val) { + if (!val.IsObject()) + { + throw json::WRONG_TYPE("json object"); + } + GET_FROM_JSON_OBJECT(val, active, active); GET_FROM_JSON_OBJECT(val, speed, speed); GET_FROM_JSON_OBJECT(val, threads_count, threads_count); @@ -288,6 +369,11 @@ void GetInfo::Response::doToJson(rapidjson::Writer<epee::byte_stream>& dest) con void GetInfo::Response::fromJson(const rapidjson::Value& val) { + if (!val.IsObject()) + { + throw json::WRONG_TYPE("json object"); + } + GET_FROM_JSON_OBJECT(val, info, info); } @@ -314,6 +400,11 @@ void GetBlockHash::Request::doToJson(rapidjson::Writer<epee::byte_stream>& dest) void GetBlockHash::Request::fromJson(const rapidjson::Value& val) { + if (!val.IsObject()) + { + throw json::WRONG_TYPE("json object"); + } + GET_FROM_JSON_OBJECT(val, height, height); } @@ -324,6 +415,11 @@ void GetBlockHash::Response::doToJson(rapidjson::Writer<epee::byte_stream>& dest void GetBlockHash::Response::fromJson(const rapidjson::Value& val) { + if (!val.IsObject()) + { + throw json::WRONG_TYPE("json object"); + } + GET_FROM_JSON_OBJECT(val, hash, hash); } @@ -342,6 +438,11 @@ void GetLastBlockHeader::Response::doToJson(rapidjson::Writer<epee::byte_stream> void GetLastBlockHeader::Response::fromJson(const rapidjson::Value& val) { + if (!val.IsObject()) + { + throw json::WRONG_TYPE("json object"); + } + GET_FROM_JSON_OBJECT(val, header, header); } @@ -353,6 +454,11 @@ void GetBlockHeaderByHash::Request::doToJson(rapidjson::Writer<epee::byte_stream void GetBlockHeaderByHash::Request::fromJson(const rapidjson::Value& val) { + if (!val.IsObject()) + { + throw json::WRONG_TYPE("json object"); + } + GET_FROM_JSON_OBJECT(val, hash, hash); } @@ -363,6 +469,11 @@ void GetBlockHeaderByHash::Response::doToJson(rapidjson::Writer<epee::byte_strea void GetBlockHeaderByHash::Response::fromJson(const rapidjson::Value& val) { + if (!val.IsObject()) + { + throw json::WRONG_TYPE("json object"); + } + GET_FROM_JSON_OBJECT(val, header, header); } @@ -374,6 +485,11 @@ void GetBlockHeaderByHeight::Request::doToJson(rapidjson::Writer<epee::byte_stre void GetBlockHeaderByHeight::Request::fromJson(const rapidjson::Value& val) { + if (!val.IsObject()) + { + throw json::WRONG_TYPE("json object"); + } + GET_FROM_JSON_OBJECT(val, height, height); } @@ -384,6 +500,11 @@ void GetBlockHeaderByHeight::Response::doToJson(rapidjson::Writer<epee::byte_str void GetBlockHeaderByHeight::Response::fromJson(const rapidjson::Value& val) { + if (!val.IsObject()) + { + throw json::WRONG_TYPE("json object"); + } + GET_FROM_JSON_OBJECT(val, header, header); } @@ -395,6 +516,11 @@ void GetBlockHeadersByHeight::Request::doToJson(rapidjson::Writer<epee::byte_str void GetBlockHeadersByHeight::Request::fromJson(const rapidjson::Value& val) { + if (!val.IsObject()) + { + throw json::WRONG_TYPE("json object"); + } + GET_FROM_JSON_OBJECT(val, heights, heights); } @@ -405,6 +531,11 @@ void GetBlockHeadersByHeight::Response::doToJson(rapidjson::Writer<epee::byte_st void GetBlockHeadersByHeight::Response::fromJson(const rapidjson::Value& val) { + if (!val.IsObject()) + { + throw json::WRONG_TYPE("json object"); + } + GET_FROM_JSON_OBJECT(val, headers, headers); } @@ -424,6 +555,11 @@ void GetPeerList::Response::doToJson(rapidjson::Writer<epee::byte_stream>& dest) void GetPeerList::Response::fromJson(const rapidjson::Value& val) { + if (!val.IsObject()) + { + throw json::WRONG_TYPE("json object"); + } + GET_FROM_JSON_OBJECT(val, white_list, white_list); GET_FROM_JSON_OBJECT(val, gray_list, gray_list); } @@ -436,6 +572,11 @@ void SetLogLevel::Request::doToJson(rapidjson::Writer<epee::byte_stream>& dest) void SetLogLevel::Request::fromJson(const rapidjson::Value& val) { + if (!val.IsObject()) + { + throw json::WRONG_TYPE("json object"); + } + GET_FROM_JSON_OBJECT(val, level, level); } @@ -462,6 +603,11 @@ void GetTransactionPool::Response::doToJson(rapidjson::Writer<epee::byte_stream> void GetTransactionPool::Response::fromJson(const rapidjson::Value& val) { + if (!val.IsObject()) + { + throw json::WRONG_TYPE("json object"); + } + GET_FROM_JSON_OBJECT(val, transactions, transactions); GET_FROM_JSON_OBJECT(val, key_images, key_images); } @@ -474,6 +620,11 @@ void HardForkInfo::Request::doToJson(rapidjson::Writer<epee::byte_stream>& dest) void HardForkInfo::Request::fromJson(const rapidjson::Value& val) { + if (!val.IsObject()) + { + throw json::WRONG_TYPE("json object"); + } + GET_FROM_JSON_OBJECT(val, version, version); } @@ -484,6 +635,11 @@ void HardForkInfo::Response::doToJson(rapidjson::Writer<epee::byte_stream>& dest void HardForkInfo::Response::fromJson(const rapidjson::Value& val) { + if (!val.IsObject()) + { + throw json::WRONG_TYPE("json object"); + } + GET_FROM_JSON_OBJECT(val, info, info); } @@ -499,6 +655,11 @@ void GetOutputHistogram::Request::doToJson(rapidjson::Writer<epee::byte_stream>& void GetOutputHistogram::Request::fromJson(const rapidjson::Value& val) { + if (!val.IsObject()) + { + throw json::WRONG_TYPE("json object"); + } + GET_FROM_JSON_OBJECT(val, amounts, amounts); GET_FROM_JSON_OBJECT(val, min_count, min_count); GET_FROM_JSON_OBJECT(val, max_count, max_count); @@ -513,6 +674,11 @@ void GetOutputHistogram::Response::doToJson(rapidjson::Writer<epee::byte_stream> void GetOutputHistogram::Response::fromJson(const rapidjson::Value& val) { + if (!val.IsObject()) + { + throw json::WRONG_TYPE("json object"); + } + GET_FROM_JSON_OBJECT(val, histogram, histogram); } @@ -524,6 +690,11 @@ void GetOutputKeys::Request::doToJson(rapidjson::Writer<epee::byte_stream>& dest void GetOutputKeys::Request::fromJson(const rapidjson::Value& val) { + if (!val.IsObject()) + { + throw json::WRONG_TYPE("json object"); + } + GET_FROM_JSON_OBJECT(val, outputs, outputs); } @@ -534,6 +705,11 @@ void GetOutputKeys::Response::doToJson(rapidjson::Writer<epee::byte_stream>& des void GetOutputKeys::Response::fromJson(const rapidjson::Value& val) { + if (!val.IsObject()) + { + throw json::WRONG_TYPE("json object"); + } + GET_FROM_JSON_OBJECT(val, keys, keys); } @@ -552,6 +728,11 @@ void GetRPCVersion::Response::doToJson(rapidjson::Writer<epee::byte_stream>& des void GetRPCVersion::Response::fromJson(const rapidjson::Value& val) { + if (!val.IsObject()) + { + throw json::WRONG_TYPE("json object"); + } + GET_FROM_JSON_OBJECT(val, version, version); } @@ -562,6 +743,11 @@ void GetFeeEstimate::Request::doToJson(rapidjson::Writer<epee::byte_stream>& des void GetFeeEstimate::Request::fromJson(const rapidjson::Value& val) { + if (!val.IsObject()) + { + throw json::WRONG_TYPE("json object"); + } + GET_FROM_JSON_OBJECT(val, num_grace_blocks, num_grace_blocks); } @@ -575,6 +761,11 @@ void GetFeeEstimate::Response::doToJson(rapidjson::Writer<epee::byte_stream>& de void GetFeeEstimate::Response::fromJson(const rapidjson::Value& val) { + if (!val.IsObject()) + { + throw json::WRONG_TYPE("json object"); + } + GET_FROM_JSON_OBJECT(val, estimated_base_fee, estimated_base_fee); GET_FROM_JSON_OBJECT(val, fee_mask, fee_mask); GET_FROM_JSON_OBJECT(val, size_scale, size_scale); @@ -591,6 +782,11 @@ void GetOutputDistribution::Request::doToJson(rapidjson::Writer<epee::byte_strea void GetOutputDistribution::Request::fromJson(const rapidjson::Value& val) { + if (!val.IsObject()) + { + throw json::WRONG_TYPE("json object"); + } + GET_FROM_JSON_OBJECT(val, amounts, amounts); GET_FROM_JSON_OBJECT(val, from_height, from_height); GET_FROM_JSON_OBJECT(val, to_height, to_height); @@ -605,6 +801,11 @@ void GetOutputDistribution::Response::doToJson(rapidjson::Writer<epee::byte_stre void GetOutputDistribution::Response::fromJson(const rapidjson::Value& val) { + if (!val.IsObject()) + { + throw json::WRONG_TYPE("json object"); + } + GET_FROM_JSON_OBJECT(val, status, status); GET_FROM_JSON_OBJECT(val, distributions, distributions); } diff --git a/src/rpc/rpc_payment.h b/src/rpc/rpc_payment.h index 4ead5a344..a4cc6db57 100644 --- a/src/rpc/rpc_payment.h +++ b/src/rpc/rpc_payment.h @@ -148,8 +148,8 @@ namespace cryptonote template <class t_archive> inline void serialize(t_archive &a, const unsigned int ver) { - a & m_client_info.parent(); - a & m_hashrate.parent(); + a & m_client_info; + a & m_hashrate; a & m_credits_total; a & m_credits_used; a & m_nonces_good; @@ -177,9 +177,9 @@ namespace cryptonote cryptonote::account_public_address m_address; uint64_t m_diff; uint64_t m_credits_per_hash_found; - serializable_unordered_map<crypto::public_key, client_info> m_client_info; + std::unordered_map<crypto::public_key, client_info> m_client_info; std::string m_directory; - serializable_map<uint64_t, uint64_t> m_hashrate; + std::map<uint64_t, uint64_t> m_hashrate; uint64_t m_credits_total; uint64_t m_credits_used; uint64_t m_nonces_good; diff --git a/src/rpc/zmq_server.cpp b/src/rpc/zmq_server.cpp index cb8a8bea4..d73ea3bc9 100644 --- a/src/rpc/zmq_server.cpp +++ b/src/rpc/zmq_server.cpp @@ -158,13 +158,22 @@ void ZmqServer::serve() if (!pub || sockets[2].revents) { - std::string message = MONERO_UNWRAP(net::zmq::receive(rep.get(), read_flags)); - MDEBUG("Received RPC request: \"" << message << "\""); - epee::byte_slice response = handler.handle(std::move(message)); - - const boost::string_ref response_view{reinterpret_cast<const char*>(response.data()), response.size()}; - MDEBUG("Sending RPC reply: \"" << response_view << "\""); - MONERO_UNWRAP(net::zmq::send(std::move(response), rep.get())); + expect<std::string> message = net::zmq::receive(rep.get(), read_flags); + if (!message) + { + // EAGAIN can occur when using `zmq_poll`, which doesn't inspect for message validity + if (message != net::zmq::make_error_code(EAGAIN)) + MONERO_THROW(message.error(), "Read failure on ZMQ-RPC"); + } + else // no errors + { + MDEBUG("Received RPC request: \"" << *message << "\""); + epee::byte_slice response = handler.handle(std::move(*message)); + + const boost::string_ref response_view{reinterpret_cast<const char*>(response.data()), response.size()}; + MDEBUG("Sending RPC reply: \"" << response_view << "\""); + MONERO_UNWRAP(net::zmq::send(std::move(response), rep.get())); + } } } } 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/serialization/container.h b/src/serialization/container.h index 80e1892f2..6f0c48069 100644 --- a/src/serialization/container.h +++ b/src/serialization/container.h @@ -42,7 +42,7 @@ namespace serialization typename std::enable_if<!use_container_varint<T>(), bool>::type serialize_container_element(Archive& ar, T& e) { - return ::do_serialize(ar, e); + return do_serialize(ar, e); } template<typename Archive, typename T> @@ -52,13 +52,33 @@ namespace serialization static constexpr const bool previously_varint = std::is_same<uint64_t, T>() || std::is_same<uint32_t, T>(); if (!previously_varint && ar.varint_bug_backward_compatibility_enabled() && !typename Archive::is_saving()) - return ::do_serialize(ar, e); + return do_serialize(ar, e); ar.serialize_varint(e); return true; } - template <typename C> - void do_reserve(C &c, size_t N) {} + //! @brief Add an element to a container, inserting at the back if applicable. + template <class Container> + auto do_add(Container &c, typename Container::value_type &&e) -> decltype(c.emplace_back(e)) + { return c.emplace_back(e); } + template <class Container> + auto do_add(Container &c, typename Container::value_type &&e) -> decltype(c.emplace(e)) + { return c.emplace(e); } + + //! @brief Reserve space for N elements if applicable for container. + template<typename... C> + void do_reserve(const C&...) {} + template<typename C> + auto do_reserve(C &c, std::size_t N) -> decltype(c.reserve(N)) { return c.reserve(N); } + + // The value_type of STL map-like containers come in the form std::pair<const K, V>. + // Since we can't {de}serialize const types in this lib, we must convert this to std::pair<K, V> + template <class Container, typename = void> + struct serializable_value_type + { using type = typename Container::value_type; }; + template <class Container> + struct serializable_value_type<Container, std::conditional_t<false, typename Container::mapped_type, void>> + { using type = std::pair<typename Container::key_type, typename Container::mapped_type>; }; } } @@ -82,7 +102,7 @@ bool do_serialize_container(Archive<false> &ar, C &v) for (size_t i = 0; i < cnt; i++) { if (i > 0) ar.delimit_array(); - typename C::value_type e; + typename ::serialization::detail::serializable_value_type<C>::type e; if (!::serialization::detail::serialize_container_element(ar, e)) return false; ::serialization::detail::do_add(v, std::move(e)); @@ -104,7 +124,8 @@ bool do_serialize_container(Archive<true> &ar, C &v) return false; if (i != v.begin()) ar.delimit_array(); - if(!::serialization::detail::serialize_container_element(ar, (typename C::value_type&)*i)) + using serializable_value_type = typename ::serialization::detail::serializable_value_type<C>::type; + if(!::serialization::detail::serialize_container_element(ar, (serializable_value_type&)*i)) return false; if (!ar.good()) return false; diff --git a/src/serialization/containers.h b/src/serialization/containers.h index 65e6359b2..28eaa50fb 100644 --- a/src/serialization/containers.h +++ b/src/serialization/containers.h @@ -38,91 +38,26 @@ #include <set> #include "serialization.h" -template <template <bool> class Archive, class T> bool do_serialize(Archive<false> &ar, std::vector<T> &v); -template <template <bool> class Archive, class T> bool do_serialize(Archive<true> &ar, std::vector<T> &v); - -template <template <bool> class Archive, class T> bool do_serialize(Archive<false> &ar, std::deque<T> &v); -template <template <bool> class Archive, class T> bool do_serialize(Archive<true> &ar, std::deque<T> &v); - -template<typename K, typename V> -class serializable_unordered_map: public std::unordered_map<K, V> -{ -public: - typedef typename std::pair<K, V> value_type; - typename std::unordered_map<K, V> &parent() { return *this; } -}; - -template <template <bool> class Archive, typename K, typename V> bool do_serialize(Archive<false> &ar, serializable_unordered_map<K, V> &v); -template <template <bool> class Archive, typename K, typename V> bool do_serialize(Archive<true> &ar, serializable_unordered_map<K, V> &v); - -template<typename K, typename V> -class serializable_map: public std::map<K, V> -{ -public: - typedef typename std::pair<K, V> value_type; - typename std::map<K, V> &parent() { return *this; } -}; - -template <template <bool> class Archive, typename K, typename V> bool do_serialize(Archive<false> &ar, serializable_map<K, V> &v); -template <template <bool> class Archive, typename K, typename V> bool do_serialize(Archive<true> &ar, serializable_map<K, V> &v); - -template<typename K, typename V> -class serializable_unordered_multimap: public std::unordered_multimap<K, V> -{ -public: - typedef typename std::pair<K, V> value_type; - typename std::unordered_multimap<K, V> &parent() { return *this; } -}; - -template <template <bool> class Archive, typename K, typename V> bool do_serialize(Archive<false> &ar, serializable_unordered_multimap<K, V> &v); -template <template <bool> class Archive, typename K, typename V> bool do_serialize(Archive<true> &ar, serializable_unordered_multimap<K, V> &v); - -template <template <bool> class Archive, class T> bool do_serialize(Archive<false> &ar, std::unordered_set<T> &v); -template <template <bool> class Archive, class T> bool do_serialize(Archive<true> &ar, std::unordered_set<T> &v); - -template <template <bool> class Archive, class T> bool do_serialize(Archive<false> &ar, std::set<T> &v); -template <template <bool> class Archive, class T> bool do_serialize(Archive<true> &ar, std::set<T> &v); - namespace serialization { - namespace detail - { - template <typename T> void do_reserve(std::vector<T> &c, size_t N) { c.reserve(N); } - template <typename T> void do_add(std::vector<T> &c, T &&e) { c.emplace_back(std::forward<T>(e)); } - - template <typename T> void do_add(std::deque<T> &c, T &&e) { c.emplace_back(std::forward<T>(e)); } - - template <typename K, typename V> void do_add(serializable_unordered_map<K, V> &c, std::pair<K, V> &&e) { c.insert(std::forward<std::pair<K, V>>(e)); } - - template <typename K, typename V> void do_add(serializable_map<K, V> &c, std::pair<K, V> &&e) { c.insert(std::forward<std::pair<K, V>>(e)); } - - template <typename K, typename V> void do_add(serializable_unordered_multimap<K, V> &c, std::pair<K, V> &&e) { c.insert(std::forward<std::pair<K, V>>(e)); } - - template <typename T> void do_add(std::unordered_set<T> &c, T &&e) { c.insert(std::forward<T>(e)); } - - template <typename T> void do_add(std::set<T> &c, T &&e) { c.insert(std::forward<T>(e)); } - } + //! @brief Is this type a STL-like container? + //! To add a new container to be serialized, partially specialize the template is_container like so: + template <typename T> struct is_container: std::false_type {}; + template <typename... TA> struct is_container<std::deque<TA...>>: std::true_type {}; + template <typename... TA> struct is_container<std::map<TA...>>: std::true_type {}; + template <typename... TA> struct is_container<std::multimap<TA...>>: std::true_type {}; + template <typename... TA> struct is_container<std::set<TA...>>: std::true_type {}; + template <typename... TA> struct is_container<std::unordered_map<TA...>>: std::true_type {}; + template <typename... TA> struct is_container<std::unordered_multimap<TA...>>: std::true_type {}; + template <typename... TA> struct is_container<std::unordered_set<TA...>>: std::true_type {}; + template <typename... TA> struct is_container<std::vector<TA...>>: std::true_type {}; } #include "container.h" -template <template <bool> class Archive, class T> bool do_serialize(Archive<false> &ar, std::vector<T> &v) { return do_serialize_container(ar, v); } -template <template <bool> class Archive, class T> bool do_serialize(Archive<true> &ar, std::vector<T> &v) { return do_serialize_container(ar, v); } - -template <template <bool> class Archive, class T> bool do_serialize(Archive<false> &ar, std::deque<T> &v) { return do_serialize_container(ar, v); } -template <template <bool> class Archive, class T> bool do_serialize(Archive<true> &ar, std::deque<T> &v) { return do_serialize_container(ar, v); } - -template <template <bool> class Archive, typename K, typename V> bool do_serialize(Archive<false> &ar, serializable_unordered_map<K, V> &v) { return do_serialize_container(ar, v); } -template <template <bool> class Archive, typename K, typename V> bool do_serialize(Archive<true> &ar, serializable_unordered_map<K, V> &v) { return do_serialize_container(ar, v); } - -template <template <bool> class Archive, typename K, typename V> bool do_serialize(Archive<false> &ar, serializable_map<K, V> &v) { return do_serialize_container(ar, v); } -template <template <bool> class Archive, typename K, typename V> bool do_serialize(Archive<true> &ar, serializable_map<K, V> &v) { return do_serialize_container(ar, v); } - -template <template <bool> class Archive, typename K, typename V> bool do_serialize(Archive<false> &ar, serializable_unordered_multimap<K, V> &v) { return do_serialize_container(ar, v); } -template <template <bool> class Archive, typename K, typename V> bool do_serialize(Archive<true> &ar, serializable_unordered_multimap<K, V> &v) { return do_serialize_container(ar, v); } - -template <template <bool> class Archive, class T> bool do_serialize(Archive<false> &ar, std::unordered_set<T> &v) { return do_serialize_container(ar, v); } -template <template <bool> class Archive, class T> bool do_serialize(Archive<true> &ar, std::unordered_set<T> &v) { return do_serialize_container(ar, v); } - -template <template <bool> class Archive, class T> bool do_serialize(Archive<false> &ar, std::set<T> &v) { return do_serialize_container(ar, v); } -template <template <bool> class Archive, class T> bool do_serialize(Archive<true> &ar, std::set<T> &v) { return do_serialize_container(ar, v); } +template <class Archive, class Container> +std::enable_if_t<::serialization::is_container<Container>::value, bool> +do_serialize(Archive &ar, Container &c) +{ + return ::do_serialize_container(ar, c); +} diff --git a/src/serialization/debug_archive.h b/src/serialization/debug_archive.h index ef3bb4345..f4df963db 100644 --- a/src/serialization/debug_archive.h +++ b/src/serialization/debug_archive.h @@ -42,14 +42,12 @@ struct debug_archive : public json_archive<W> { }; template <class T> -struct serializer<debug_archive<true>, T> +static inline bool do_serialize(debug_archive<true> &ar, T &v) { - static void serialize(debug_archive<true> &ar, T &v) - { ar.begin_object(); ar.tag(variant_serialization_traits<debug_archive<true>, T>::get_tag()); - serializer<json_archive<true>, T>::serialize(ar, v); + do_serialize(static_cast<json_archive<true>&>(ar), v); ar.end_object(); ar.stream() << std::endl; - } -}; + return true; +} diff --git a/src/serialization/difficulty_type.h b/src/serialization/difficulty_type.h index 37761839b..c071f287b 100644 --- a/src/serialization/difficulty_type.h +++ b/src/serialization/difficulty_type.h @@ -31,8 +31,6 @@ #include "cryptonote_basic/difficulty.h" #include "serialization.h" -template<> struct is_basic_type<cryptonote::difficulty_type> { typedef boost::true_type type; }; - template <template <bool> class Archive> inline bool do_serialize(Archive<false>& ar, cryptonote::difficulty_type &diff) { diff --git a/src/serialization/pair.h b/src/serialization/pair.h index f21041fe8..888033fb9 100644 --- a/src/serialization/pair.h +++ b/src/serialization/pair.h @@ -47,7 +47,7 @@ namespace serialization typename std::enable_if<!use_pair_varint<T>(), bool>::type serialize_pair_element(Archive& ar, T& e) { - return ::do_serialize(ar, e); + return do_serialize(ar, e); } template<typename Archive, typename T> @@ -57,7 +57,7 @@ namespace serialization static constexpr const bool previously_varint = std::is_same<uint64_t, T>(); if (!previously_varint && ar.varint_bug_backward_compatibility_enabled() && !typename Archive::is_saving()) - return ::do_serialize(ar, e); + return do_serialize(ar, e); ar.serialize_varint(e); return true; } diff --git a/src/serialization/serialization.h b/src/serialization/serialization.h index efe1270a4..2911aecb5 100644 --- a/src/serialization/serialization.h +++ b/src/serialization/serialization.h @@ -57,73 +57,30 @@ template <class T> struct is_blob_type { typedef boost::false_type type; }; -/*! \struct has_free_serializer - * - * \brief a descriptor for dispatching serialize - */ -template <class T> -struct has_free_serializer { typedef boost::true_type type; }; - -/*! \struct is_basic_type - * - * \brief a descriptor for dispatching serialize - */ -template <class T> -struct is_basic_type { typedef boost::false_type type; }; - -template<typename F, typename S> -struct is_basic_type<std::pair<F,S>> { typedef boost::true_type type; }; -template<> -struct is_basic_type<std::string> { typedef boost::true_type type; }; - -/*! \struct serializer +/*! \fn do_serialize(Archive &ar, T &v) * - * \brief ... wouldn't a class be better? + * \brief main function for dispatching serialization for a given pair of archive and value types * - * \detailed The logic behind serializing data. Places the archive - * data into the supplied parameter. This dispatches based on the - * supplied \a T template parameter's traits of is_blob_type or it is - * an integral (as defined by the is_integral trait). Depends on the - * \a Archive parameter to have overloaded the serialize_blob(T v, - * size_t size) and serialize_int(T v) base on which trait it - * applied. When the class has neither types, it falls to the - * overloaded method do_serialize(Archive ar) in T to do the work. + * Types marked true with is_blob_type<T> will be serialized as a blob, integral types will be + * serialized as integers, and types who have a `member_do_serialize` method will be serialized + * using that method. Booleans are serialized like blobs. */ template <class Archive, class T> -struct serializer{ - static bool serialize(Archive &ar, T &v) { - return serialize(ar, v, typename boost::is_integral<T>::type(), typename is_blob_type<T>::type(), typename is_basic_type<T>::type()); - } - template<typename A> - static bool serialize(Archive &ar, T &v, boost::false_type, boost::true_type, A a) { - ar.serialize_blob(&v, sizeof(v)); - return true; - } - template<typename A> - static bool serialize(Archive &ar, T &v, boost::true_type, boost::false_type, A a) { - ar.serialize_int(v); - return true; - } - static bool serialize(Archive &ar, T &v, boost::false_type, boost::false_type, boost::false_type) { - //serialize_custom(ar, v, typename has_free_serializer<T>::type()); - return v.do_serialize(ar); - } - static bool serialize(Archive &ar, T &v, boost::false_type, boost::false_type, boost::true_type) { - //serialize_custom(ar, v, typename has_free_serializer<T>::type()); - return do_serialize(ar, v); - } - static void serialize_custom(Archive &ar, T &v, boost::true_type) { - } -}; - -/*! \fn do_serialize(Archive &ar, T &v) - * - * \brief just calls the serialize function defined for ar and v... - */ +inline std::enable_if_t<is_blob_type<T>::type::value, bool> do_serialize(Archive &ar, T &v) +{ + ar.serialize_blob(&v, sizeof(v)); + return true; +} template <class Archive, class T> -inline bool do_serialize(Archive &ar, T &v) +inline std::enable_if_t<boost::is_integral<T>::value, bool> do_serialize(Archive &ar, T &v) { - return ::serializer<Archive, T>::serialize(ar, v); + ar.serialize_int(v); + return true; +} +template <class Archive, class T> +inline auto do_serialize(Archive &ar, T &v) -> decltype(v.member_do_serialize(ar), true) +{ + return v.member_do_serialize(ar); } template <class Archive> inline bool do_serialize(Archive &ar, bool &v) @@ -144,16 +101,6 @@ inline bool do_serialize(Archive &ar, bool &v) typedef boost::true_type type; \ } -/*! \macro FREE_SERIALIZER - * - * \brief adds the has_free_serializer to the type - */ -#define FREE_SERIALIZER(T) \ - template<> \ - struct has_free_serializer<T> { \ - typedef boost::true_type type; \ - } - /*! \macro VARIANT_TAG * * \brief Adds the tag \tag to the \a Archive of \a Type @@ -174,7 +121,7 @@ inline bool do_serialize(Archive &ar, bool &v) */ #define BEGIN_SERIALIZE() \ template <bool W, template <bool> class Archive> \ - bool do_serialize(Archive<W> &ar) { + bool member_do_serialize(Archive<W> &ar) { /*! \macro BEGIN_SERIALIZE_OBJECT * @@ -183,7 +130,7 @@ inline bool do_serialize(Archive &ar, bool &v) */ #define BEGIN_SERIALIZE_OBJECT() \ template <bool W, template <bool> class Archive> \ - bool do_serialize(Archive<W> &ar) { \ + bool member_do_serialize(Archive<W> &ar) { \ ar.begin_object(); \ bool r = do_serialize_object(ar); \ ar.end_object(); \ @@ -197,11 +144,6 @@ inline bool do_serialize(Archive &ar, bool &v) #define PREPARE_CUSTOM_VECTOR_SERIALIZATION(size, vec) \ ::serialization::detail::prepare_custom_vector_serialization(size, vec, typename Archive<W>::is_saving()) -/*! \macro PREPARE_CUSTOM_DEQUE_SERIALIZATION - */ -#define PREPARE_CUSTOM_DEQUE_SERIALIZATION(size, vec) \ - ::serialization::detail::prepare_custom_deque_serialization(size, vec, typename Archive<W>::is_saving()) - /*! \macro END_SERIALIZE * \brief self-explanatory */ @@ -209,16 +151,6 @@ inline bool do_serialize(Archive &ar, bool &v) return ar.good(); \ } -/*! \macro VALUE(f) - * \brief the same as FIELD(f) - */ -#define VALUE(f) \ - do { \ - ar.tag(#f); \ - bool r = ::do_serialize(ar, f); \ - if (!r || !ar.good()) return false; \ - } while(0); - /*! \macro FIELD_N(t,f) * * \brief serializes a field \a f tagged \a t @@ -226,7 +158,7 @@ inline bool do_serialize(Archive &ar, bool &v) #define FIELD_N(t, f) \ do { \ ar.tag(t); \ - bool r = ::do_serialize(ar, f); \ + bool r = do_serialize(ar, f); \ if (!r || !ar.good()) return false; \ } while(0); @@ -237,7 +169,7 @@ inline bool do_serialize(Archive &ar, bool &v) #define FIELD(f) \ do { \ ar.tag(#f); \ - bool r = ::do_serialize(ar, f); \ + bool r = do_serialize(ar, f); \ if (!r || !ar.good()) return false; \ } while(0); @@ -247,7 +179,7 @@ inline bool do_serialize(Archive &ar, bool &v) */ #define FIELDS(f) \ do { \ - bool r = ::do_serialize(ar, f); \ + bool r = do_serialize(ar, f); \ if (!r || !ar.good()) return false; \ } while(0); @@ -317,17 +249,6 @@ namespace serialization { vec.resize(size); } - template <typename T> - void prepare_custom_deque_serialization(size_t size, std::deque<T>& vec, const boost::mpl::bool_<true>& /*is_saving*/) - { - } - - template <typename T> - void prepare_custom_deque_serialization(size_t size, std::deque<T>& vec, const boost::mpl::bool_<false>& /*is_saving*/) - { - vec.resize(size); - } - /*! \fn do_check_stream_state * * \brief self explanatory diff --git a/src/serialization/tuple.h b/src/serialization/tuple.h index e76ca0b99..d9592bc96 100644 --- a/src/serialization/tuple.h +++ b/src/serialization/tuple.h @@ -39,7 +39,7 @@ namespace serialization template <typename Archive, class T> bool serialize_tuple_element(Archive& ar, T& e) { - return ::do_serialize(ar, e); + return do_serialize(ar, e); } template <typename Archive> diff --git a/src/serialization/variant.h b/src/serialization/variant.h index 77d767a1c..1cf93c8cf 100644 --- a/src/serialization/variant.h +++ b/src/serialization/variant.h @@ -72,7 +72,7 @@ struct variant_reader { if(variant_serialization_traits<Archive, current_type>::get_tag() == t) { current_type x; - if(!::do_serialize(ar, x)) + if(!do_serialize(ar, x)) { ar.set_fail(); return false; @@ -100,19 +100,13 @@ struct variant_reader<Archive, Variant, TBegin, TBegin> } }; - -template <template <bool> class Archive, BOOST_VARIANT_ENUM_PARAMS(typename T)> -struct serializer<Archive<false>, boost::variant<BOOST_VARIANT_ENUM_PARAMS(T)>> -{ - typedef boost::variant<BOOST_VARIANT_ENUM_PARAMS(T)> variant_type; - typedef typename Archive<false>::variant_tag_type variant_tag_type; - typedef typename variant_type::types types; - - static bool serialize(Archive<false> &ar, variant_type &v) { - variant_tag_type t; +template <template <bool> class Archive, typename... T> +static bool do_serialize(Archive<false> &ar, boost::variant<T...> &v) { + using types = typename boost::variant<T...>::types; + typename Archive<false>::variant_tag_type t; ar.begin_variant(); ar.read_variant_tag(t); - if(!variant_reader<Archive<false>, variant_type, + if(!variant_reader<Archive<false>, boost::variant<T...>, typename boost::mpl::begin<types>::type, typename boost::mpl::end<types>::type>::read(ar, v, t)) { @@ -121,27 +115,21 @@ struct serializer<Archive<false>, boost::variant<BOOST_VARIANT_ENUM_PARAMS(T)>> } ar.end_variant(); return true; - } -}; +} -template <template <bool> class Archive, BOOST_VARIANT_ENUM_PARAMS(typename T)> -struct serializer<Archive<true>, boost::variant<BOOST_VARIANT_ENUM_PARAMS(T)>> +template <template <bool> class Archive> +struct variant_write_visitor : public boost::static_visitor<bool> { - typedef boost::variant<BOOST_VARIANT_ENUM_PARAMS(T)> variant_type; - //typedef typename Archive<true>::variant_tag_type variant_tag_type; - - struct visitor : public boost::static_visitor<bool> - { Archive<true> &ar; - visitor(Archive<true> &a) : ar(a) { } + variant_write_visitor(Archive<true> &a) : ar(a) { } template <class T> bool operator ()(T &rv) const { ar.begin_variant(); ar.write_variant_tag(variant_serialization_traits<Archive<true>, T>::get_tag()); - if(!::do_serialize(ar, rv)) + if(!do_serialize(ar, rv)) { ar.set_fail(); return false; @@ -149,9 +137,10 @@ struct serializer<Archive<true>, boost::variant<BOOST_VARIANT_ENUM_PARAMS(T)>> ar.end_variant(); return true; } - }; - - static bool serialize(Archive<true> &ar, variant_type &v) { - return boost::apply_visitor(visitor(ar), v); - } }; + +template <template <bool> class Archive, typename... T> +static bool do_serialize(Archive<true> &ar, boost::variant<T...> &v) +{ + return boost::apply_visitor(variant_write_visitor<Archive>(ar), v); +} diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 6e5d0b1ec..bff62f1a8 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -188,7 +188,7 @@ namespace const char* USAGE_INCOMING_TRANSFERS("incoming_transfers [available|unavailable] [verbose] [uses] [index=<N1>[,<N2>[,...]]]"); const char* USAGE_PAYMENTS("payments <PID_1> [<PID_2> ... <PID_N>]"); const char* USAGE_PAYMENT_ID("payment_id"); - const char* USAGE_TRANSFER("transfer [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] (<URI> | <address> <amount>) [<payment_id>]"); + const char* USAGE_TRANSFER("transfer [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] (<URI> | <address> <amount>) [subtractfeefrom=<D0>[,<D1>,all,...]] [<payment_id>]"); const char* USAGE_LOCKED_TRANSFER("locked_transfer [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] (<URI> | <addr> <amount>) <lockblocks> [<payment_id (obsolete)>]"); const char* USAGE_LOCKED_SWEEP_ALL("locked_sweep_all [index=<N1>[,<N2>,...] | index=all] [<priority>] [<ring_size>] <address> <lockblocks> [<payment_id (obsolete)>]"); const char* USAGE_SWEEP_ALL("sweep_all [index=<N1>[,<N2>,...] | index=all] [<priority>] [<ring_size>] [outputs=<N>] <address> [<payment_id (obsolete)>]"); @@ -520,7 +520,52 @@ namespace fail_msg_writer() << sw::tr("invalid format for subaddress lookahead; must be <major>:<minor>"); return r; } -} + + static constexpr std::string_view SFFD_ARG_NAME{"subtractfeefrom="}; + + bool parse_subtract_fee_from_outputs + ( + const std::string& arg, + tools::wallet2::unique_index_container& subtract_fee_from_outputs, + bool& subtract_fee_from_all, + bool& matches + ) + { + matches = false; + if (!boost::string_ref{arg}.starts_with(SFFD_ARG_NAME.data())) // if arg doesn't match + return true; + matches = true; + + const char* arg_end = arg.c_str() + arg.size(); + for (const char* p = arg.c_str() + SFFD_ARG_NAME.size(); p < arg_end;) + { + const char* new_p = nullptr; + const unsigned long dest_index = strtoul(p, const_cast<char**>(&new_p), 10); + if (dest_index == 0 && new_p == p) // numerical conversion failed + { + if (0 != strncmp(p, "all", 3)) + { + fail_msg_writer() << tr("Failed to parse subtractfeefrom list"); + return false; + } + subtract_fee_from_all = true; + break; + } + else if (dest_index > std::numeric_limits<uint32_t>::max()) + { + fail_msg_writer() << tr("Destination index is too large") << ": " << dest_index; + return false; + } + else + { + subtract_fee_from_outputs.insert(dest_index); + p = new_p + 1; // skip the comma + } + } + + return true; + } +} // anonymous namespace void simple_wallet::handle_transfer_exception(const std::exception_ptr &e, bool trusted_daemon) { @@ -3080,7 +3125,7 @@ simple_wallet::simple_wallet() tr("Show the blockchain height.")); m_cmd_binder.set_handler("transfer", boost::bind(&simple_wallet::on_command, this, &simple_wallet::transfer, _1), tr(USAGE_TRANSFER), - tr("Transfer <amount> to <address>. If the parameter \"index=<N1>[,<N2>,...]\" is specified, the wallet uses outputs received by addresses of those indices. If omitted, the wallet randomly chooses address indices to be used. In any case, it tries its best not to combine outputs across multiple addresses. <priority> is the priority of the transaction. The higher the priority, the higher the transaction fee. Valid values in priority order (from lowest to highest) are: unimportant, normal, elevated, priority. If omitted, the default value (see the command \"set priority\") is used. <ring_size> is the number of inputs to include for untraceability. Multiple payments can be made at once by adding URI_2 or <address_2> <amount_2> etcetera (before the payment ID, if it's included)")); + tr("Transfer <amount> to <address>. If the parameter \"index=<N1>[,<N2>,...]\" is specified, the wallet uses outputs received by addresses of those indices. If omitted, the wallet randomly chooses address indices to be used. In any case, it tries its best not to combine outputs across multiple addresses. <priority> is the priority of the transaction. The higher the priority, the higher the transaction fee. Valid values in priority order (from lowest to highest) are: unimportant, normal, elevated, priority. If omitted, the default value (see the command \"set priority\") is used. <ring_size> is the number of inputs to include for untraceability. Multiple payments can be made at once by adding URI_2 or <address_2> <amount_2> etcetera (before the payment ID, if it's included). The \"subtractfeefrom=\" list allows you to choose which destinations to fund the tx fee from instead of the change output. The fee will be split across the chosen destinations proportionally equally. For example, to make 3 transfers where the fee is taken from the first and third destinations, one could do: \"transfer <addr1> 3 <addr2> 0.5 <addr3> 1 subtractfeefrom=0,2\". Let's say the tx fee is 0.1. The balance would drop by exactly 4.5 XMR including fees, and addr1 & addr3 would receive 2.925 & 0.975 XMR, respectively. Use \"subtractfeefrom=all\" to spread the fee across all destinations.")); m_cmd_binder.set_handler("locked_transfer", boost::bind(&simple_wallet::on_command, this, &simple_wallet::locked_transfer,_1), tr(USAGE_LOCKED_TRANSFER), @@ -6349,6 +6394,27 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri local_args.pop_back(); } + // Parse subtractfeefrom destination list + tools::wallet2::unique_index_container subtract_fee_from_outputs; + bool subtract_fee_from_all = false; + for (auto it = local_args.begin(); it < local_args.end();) + { + bool matches = false; + if (!parse_subtract_fee_from_outputs(*it, subtract_fee_from_outputs, subtract_fee_from_all, matches)) + { + return false; + } + else if (matches) + { + it = local_args.erase(it); + break; + } + else + { + ++it; + } + } + vector<cryptonote::address_parse_info> dsts_info; vector<cryptonote::tx_destination_entry> dsts; for (size_t i = 0; i < local_args.size(); ) @@ -6445,6 +6511,13 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri dsts.push_back(de); } + if (subtract_fee_from_all) + { + subtract_fee_from_outputs.clear(); + for (decltype(subtract_fee_from_outputs)::value_type i = 0; i < dsts.size(); ++i) + subtract_fee_from_outputs.insert(i); + } + SCOPED_WALLET_UNLOCK_ON_BAD_PASSWORD(return false;); try @@ -6463,13 +6536,13 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri return false; } unlock_block = bc_height + locked_blocks; - ptx_vector = m_wallet->create_transactions_2(dsts, fake_outs_count, unlock_block /* unlock_time */, priority, extra, m_current_subaddress_account, subaddr_indices); + ptx_vector = m_wallet->create_transactions_2(dsts, fake_outs_count, unlock_block /* unlock_time */, priority, extra, m_current_subaddress_account, subaddr_indices, subtract_fee_from_outputs); break; default: LOG_ERROR("Unknown transfer method, using default"); /* FALLTHRU */ case Transfer: - ptx_vector = m_wallet->create_transactions_2(dsts, fake_outs_count, 0 /* unlock_time */, priority, extra, m_current_subaddress_account, subaddr_indices); + ptx_vector = m_wallet->create_transactions_2(dsts, fake_outs_count, 0 /* unlock_time */, priority, extra, m_current_subaddress_account, subaddr_indices, subtract_fee_from_outputs); break; } 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..e6c26cba0 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) @@ -273,16 +273,29 @@ bool message_transporter::post_request(const std::string &request, std::string & { if ((string_value.find("API Error 0021") == 0) && (request.find("joinChan") != std::string::npos)) { + // "API Error 0021: Unexpected API Failure" // Error that occurs if one tries to join an already joined chan, which can happen // if several auto-config participants share one PyBitmessage instance: As a little // hack simply ignore the error. (A clean solution would be to check for the chan // with 'listAddresses2', but parsing the returned array is much more complicated.) } + else if ((string_value.find("API Error 0024") == 0) && (request.find("joinChan") != std::string::npos)) + { + // "API Error 0024: Chan address is already present." + // Maybe a result of creating the chan in a slightly different way i.e. not with + // 'createChan'; everything works by just ignoring this error + } else if ((string_value.find("API Error 0013") == 0) && (request.find("leaveChan") != std::string::npos)) { + // "API Error 0013: Could not find your fromAddress in the keys.dat file." // Error that occurs if one tries to leave an already left / deleted chan, which can happen // if several auto-config participants share one PyBitmessage instance: Also ignore. } + else if ((string_value.find("API Error 0025") == 0) && (request.find("leaveChan") != std::string::npos)) + { + // "API Error 0025: Specified address is not a chan address. Use deleteAddress API call instead." + // Error does not really make sense, but everything works by just ignoring + } else { THROW_WALLET_EXCEPTION(tools::error::bitmessage_api_error, string_value); diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 49f95b1a1..e9c48f9a8 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -1821,7 +1821,7 @@ void reattach_blockchain(hashchain &blockchain, wallet2::detached_blockchain_dat } //---------------------------------------------------------------------------------------------------- bool has_nonrequested_tx_at_height_or_above_requested(uint64_t height, const std::unordered_set<crypto::hash> &requested_txids, const wallet2::transfer_container &transfers, - const wallet2::payment_container &payments, const serializable_unordered_map<crypto::hash, wallet2::confirmed_transfer_details> &confirmed_txs) + const wallet2::payment_container &payments, const std::unordered_map<crypto::hash, wallet2::confirmed_transfer_details> &confirmed_txs) { for (const auto &td : transfers) if (td.m_block_height >= height && requested_txids.find(td.m_txid) == requested_txids.end()) @@ -3317,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; @@ -4402,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); @@ -6163,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); @@ -6314,7 +6328,7 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas if (!same_file || force_rewrite_keys) { - bool r = store_keys(m_keys_file, password, false); + bool r = store_keys(m_keys_file, password, m_watch_only); THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); } @@ -7036,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 @@ -7287,9 +7304,7 @@ bool wallet2::sign_tx(unsigned_tx_set &exported_txs, std::vector<wallet2::pendin crypto::key_derivation derivation; std::vector<crypto::key_derivation> additional_derivations; - // compute public keys from out secret keys - crypto::public_key tx_pub_key; - crypto::secret_key_to_public_key(txs[n].tx_key, tx_pub_key); + crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(tx); std::vector<crypto::public_key> additional_tx_pub_keys; for (const crypto::secret_key &skey: txs[n].additional_tx_keys) { @@ -7540,7 +7555,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) @@ -7864,7 +7882,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()); @@ -8662,6 +8683,26 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> COMMAND_RPC_GET_OUTPUTS_BIN::request req = AUTO_VAL_INIT(req); COMMAND_RPC_GET_OUTPUTS_BIN::response daemon_resp = AUTO_VAL_INIT(daemon_resp); + // The secret picking order contains outputs in the order that we selected them. + // + // We will later sort the output request entries in a pre-determined order so that the daemon + // that we're requesting information from doesn't learn any information about the true spend + // for each ring. However, internally, we want to prefer to construct our rings using the + // outputs that we picked first versus outputs picked later. + // + // The reason why is because each consecutive output pick within a ring becomes increasing less + // statistically independent from other picks, since we pick outputs from a finite set + // *without replacement*, due to the protocol not allowing duplicate ring members. This effect + // is exacerbated by the fact that we pick 1.5x + 75 as many outputs as we need per RPC + // request to account for unusable outputs. This effect is small, but non-neglibile and gets + // worse with larger ring sizes. + std::vector<get_outputs_out> secret_picking_order; + + // Convenience/safety lambda to make sure that both output lists req.outputs and secret_picking_order are updated together + // Each ring section of req.outputs gets sorted later after selecting all outputs for that ring + const auto add_output_to_lists = [&req, &secret_picking_order](const get_outputs_out &goo) + { req.outputs.push_back(goo); secret_picking_order.push_back(goo); }; + std::unique_ptr<gamma_picker> gamma; if (has_rct) gamma.reset(new gamma_picker(rct_offsets)); @@ -8796,7 +8837,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> if (out < num_outs) { MINFO("Using it"); - req.outputs.push_back({amount, out}); + add_output_to_lists({amount, out}); ++num_found; seen_indices.emplace(out); if (out == td.m_global_output_index) @@ -8818,12 +8859,12 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> if (num_outs <= requested_outputs_count) { for (uint64_t i = 0; i < num_outs; i++) - req.outputs.push_back({amount, i}); + add_output_to_lists({amount, i}); // duplicate to make up shortfall: this will be caught after the RPC call, // so we can also output the amounts for which we can't reach the required // mixin after checking the actual unlockedness for (uint64_t i = num_outs; i < requested_outputs_count; ++i) - req.outputs.push_back({amount, num_outs - 1}); + add_output_to_lists({amount, num_outs - 1}); } else { @@ -8832,7 +8873,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> { num_found = 1; seen_indices.emplace(td.m_global_output_index); - req.outputs.push_back({amount, td.m_global_output_index}); + add_output_to_lists({amount, td.m_global_output_index}); LOG_PRINT_L1("Selecting real output: " << td.m_global_output_index << " for " << print_money(amount)); } @@ -8940,7 +8981,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> seen_indices.emplace(i); picks[type].insert(i); - req.outputs.push_back({amount, i}); + add_output_to_lists({amount, i}); ++num_found; MDEBUG("picked " << i << ", " << num_found << " now picked"); } @@ -8954,7 +8995,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> // we'll error out later while (num_found < requested_outputs_count) { - req.outputs.push_back({amount, 0}); + add_output_to_lists({amount, 0}); ++num_found; } } @@ -8964,6 +9005,10 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> [](const get_outputs_out &a, const get_outputs_out &b) { return a.index < b.index; }); } + THROW_WALLET_EXCEPTION_IF(req.outputs.size() != secret_picking_order.size(), error::wallet_internal_error, + "bug: we did not update req.outputs/secret_picking_order in tandem"); + + // List all requested outputs to debug log if (ELPP->vRegistry()->allowed(el::Level::Debug, MONERO_DEFAULT_LOG_CATEGORY)) { std::map<uint64_t, std::set<uint64_t>> outs; @@ -9081,18 +9126,21 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> } } - // then pick others in random order till we reach the required number - // since we use an equiprobable pick here, we don't upset the triangular distribution - std::vector<size_t> order; - order.resize(requested_outputs_count); - for (size_t n = 0; n < order.size(); ++n) - order[n] = n; - std::shuffle(order.begin(), order.end(), crypto::random_device{}); - + // While we are still lacking outputs in this result ring, in our secret pick order... LOG_PRINT_L2("Looking for " << (fake_outputs_count+1) << " outputs of size " << print_money(td.is_rct() ? 0 : td.amount())); - for (size_t o = 0; o < requested_outputs_count && outs.back().size() < fake_outputs_count + 1; ++o) + for (size_t ring_pick_idx = base; ring_pick_idx < base + requested_outputs_count && outs.back().size() < fake_outputs_count + 1; ++ring_pick_idx) { - size_t i = base + order[o]; + const get_outputs_out attempted_output = secret_picking_order[ring_pick_idx]; + + // Find the index i of our pick in the request/response arrays + size_t i; + for (i = base; i < base + requested_outputs_count; ++i) + if (req.outputs[i].index == attempted_output.index) + break; + THROW_WALLET_EXCEPTION_IF(i == base + requested_outputs_count, error::wallet_internal_error, + "Could not find index of picked output in requested outputs"); + + // Try adding this output's information to result ring if output isn't invalid LOG_PRINT_L2("Index " << i << "/" << requested_outputs_count << ": idx " << req.outputs[i].index << " (real " << td.m_global_output_index << "), unlocked " << daemon_resp.outs[i].unlocked << ", key " << daemon_resp.outs[i].key); tx_add_fake_output(outs, req.outputs[i].index, daemon_resp.outs[i].key, daemon_resp.outs[i].mask, td.m_global_output_index, daemon_resp.outs[i].unlocked, valid_public_keys_cache); } @@ -9803,7 +9851,7 @@ static uint32_t get_count_above(const std::vector<wallet2::transfer_details> &tr // This system allows for sending (almost) the entire balance, since it does // not generate spurious change in all txes, thus decreasing the instantaneous // usable balance. -std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices) +std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices, const unique_index_container& subtract_fee_from_outputs) { //ensure device is let in NONE mode in any case hw::device &hwdev = m_account.get_device(); @@ -9814,11 +9862,12 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp std::vector<std::pair<uint32_t, std::vector<size_t>>> unused_transfers_indices_per_subaddr; std::vector<std::pair<uint32_t, std::vector<size_t>>> unused_dust_indices_per_subaddr; - uint64_t needed_money; + uint64_t needed_money, total_needed_money; // 'needed_money' is the sum of the destination amounts, while 'total_needed_money' includes 'needed_money' plus the fee if not 'subtract_fee_from_outputs' uint64_t accumulated_fee, accumulated_change; struct TX { std::vector<size_t> selected_transfers; std::vector<cryptonote::tx_destination_entry> dsts; + std::vector<bool> dsts_are_fee_subtractable; cryptonote::transaction tx; pending_tx ptx; size_t weight; @@ -9828,9 +9877,11 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp TX() : weight(0), needed_fee(0) {} /* Add an output to the transaction. + * If merge_destinations is true, when adding a destination with an existing address, to increment the amount of the existing tx output instead of creating a new one + * If subtracting_fee is true, when we generate a final list of destinations for transfer_selected[_rct], this destination will be used to fund the tx fee * Returns True if the output was added, False if there are no more available output slots. */ - bool add(const cryptonote::tx_destination_entry &de, uint64_t amount, unsigned int original_output_index, bool merge_destinations, size_t max_dsts) { + bool add(const cryptonote::tx_destination_entry &de, uint64_t amount, unsigned int original_output_index, bool merge_destinations, size_t max_dsts, bool subtracting_fee) { if (merge_destinations) { std::vector<cryptonote::tx_destination_entry>::iterator i; @@ -9840,6 +9891,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp if (dsts.size() >= max_dsts) return false; dsts.push_back(de); + dsts_are_fee_subtractable.push_back(subtracting_fee); i = dsts.end() - 1; i->amount = 0; } @@ -9855,13 +9907,67 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp return false; dsts.push_back(de); dsts.back().amount = 0; + dsts_are_fee_subtractable.push_back(subtracting_fee); } THROW_WALLET_EXCEPTION_IF(memcmp(&dsts[original_output_index].addr, &de.addr, sizeof(de.addr)), error::wallet_internal_error, "Mismatched destination address"); dsts[original_output_index].amount += amount; } return true; } + + // Returns destinations adjusted for given fee if subtract_fee_from_outputs is enabled + std::vector<cryptonote::tx_destination_entry> get_adjusted_dsts(uint64_t needed_fee) const + { + uint64_t dest_total = 0; + uint64_t subtractable_dest_total = 0; + std::vector<size_t> subtractable_indices; + subtractable_indices.reserve(dsts.size()); + for (size_t i = 0; i < dsts.size(); ++i) + { + dest_total += dsts[i].amount; + if (dsts_are_fee_subtractable[i]) + { + subtractable_dest_total += dsts[i].amount; + subtractable_indices.push_back(i); + } + } + + if (subtractable_indices.empty()) // if subtract_fee_from_outputs is not enabled for this tx + return dsts; + + THROW_WALLET_EXCEPTION_IF(subtractable_dest_total < needed_fee, error::tx_not_possible, + subtractable_dest_total, dest_total, needed_fee); + + std::vector<cryptonote::tx_destination_entry> res = dsts; + + // subtract fees from destinations equally, rounded down, until dust is left where we subtract 1 + uint64_t subtractable_remaining = needed_fee; + auto si_it = subtractable_indices.cbegin(); + uint64_t amount_to_subtract = 0; + while (subtractable_remaining) + { + // Set the amount to subtract iterating at the beginning of the list so equal amounts are + // subtracted throughout the list of destinations. We use max(x, 1) so that we we still step + // forwards even when the amount remaining is less than the number of subtractable indices + if (si_it == subtractable_indices.cbegin()) + amount_to_subtract = std::max<uint64_t>(subtractable_remaining / subtractable_indices.size(), 1); + + cryptonote::tx_destination_entry& d = res[*si_it]; + THROW_WALLET_EXCEPTION_IF(d.amount <= amount_to_subtract, error::zero_amount); + + subtractable_remaining -= amount_to_subtract; + d.amount -= amount_to_subtract; + ++si_it; + + // Wrap around to first subtractable index once we hit the end of the list + if (si_it == subtractable_indices.cend()) + si_it = subtractable_indices.cbegin(); + } + + return res; + } }; + std::vector<TX> txes; bool adding_fee; // true if new outputs go towards fee, rather than destinations uint64_t needed_fee, available_for_fee = 0; @@ -9884,6 +9990,14 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp // throw if attempting a transaction with no destinations THROW_WALLET_EXCEPTION_IF(dsts.empty(), error::zero_destination); + // throw if subtract_fee_from_outputs has a bad index + THROW_WALLET_EXCEPTION_IF(subtract_fee_from_outputs.size() && *subtract_fee_from_outputs.crbegin() >= dsts.size(), + error::subtract_fee_from_bad_index, *subtract_fee_from_outputs.crbegin()); + + // throw if subtract_fee_from_outputs is enabled and we have too many outputs to fit into one tx + THROW_WALLET_EXCEPTION_IF(subtract_fee_from_outputs.size() && dsts.size() > BULLETPROOF_MAX_OUTPUTS - 1, + error::wallet_internal_error, "subtractfeefrom transfers cannot be split over multiple transactions yet"); + // calculate total amount being sent to all destinations // throw if total amount overflows uint64_t needed_money = 0; @@ -9911,6 +10025,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp // we could also check for being within FEE_PER_KB, but if the fee calculation // ever changes, this might be missed, so let this go through const uint64_t min_fee = (base_fee * estimate_tx_size(use_rct, 1, fake_outs_count, 2, extra.size(), bulletproof, clsag, bulletproof_plus, use_view_tags)); + total_needed_money = needed_money + (subtract_fee_from_outputs.size() ? 0 : min_fee); uint64_t balance_subtotal = 0; uint64_t unlocked_balance_subtotal = 0; for (uint32_t index_minor : subaddr_indices) @@ -9918,10 +10033,10 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp balance_subtotal += balance_per_subaddr[index_minor]; unlocked_balance_subtotal += unlocked_balance_per_subaddr[index_minor].first; } - THROW_WALLET_EXCEPTION_IF(needed_money + min_fee > balance_subtotal, error::not_enough_money, + THROW_WALLET_EXCEPTION_IF(total_needed_money > balance_subtotal || min_fee > balance_subtotal, error::not_enough_money, balance_subtotal, needed_money, 0); // first check overall balance is enough, then unlocked one, so we throw distinct exceptions - THROW_WALLET_EXCEPTION_IF(needed_money + min_fee > unlocked_balance_subtotal, error::not_enough_unlocked_money, + THROW_WALLET_EXCEPTION_IF(total_needed_money > unlocked_balance_subtotal || min_fee > unlocked_balance_subtotal, error::not_enough_unlocked_money, unlocked_balance_subtotal, needed_money, 0); for (uint32_t i : subaddr_indices) @@ -10024,7 +10139,8 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp // this is used to build a tx that's 1 or 2 inputs, and 2 outputs, which // will get us a known fee. uint64_t estimated_fee = estimate_fee(use_per_byte_fee, use_rct, 2, fake_outs_count, 2, extra.size(), bulletproof, clsag, bulletproof_plus, use_view_tags, base_fee, fee_quantization_mask); - preferred_inputs = pick_preferred_rct_inputs(needed_money + estimated_fee, subaddr_account, subaddr_indices); + total_needed_money = needed_money + (subtract_fee_from_outputs.size() ? 0 : estimated_fee); + preferred_inputs = pick_preferred_rct_inputs(total_needed_money, subaddr_account, subaddr_indices); if (!preferred_inputs.empty()) { string s; @@ -10057,7 +10173,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp // - we have something to send // - or we need to gather more fee // - or we have just one input in that tx, which is rct (to try and make all/most rct txes 2/2) - unsigned int original_output_index = 0; + unsigned int original_output_index = 0, destination_index = 0; std::vector<size_t>* unused_transfers_indices = &unused_transfers_indices_per_subaddr[0].second; std::vector<size_t>* unused_dust_indices = &unused_dust_indices_per_subaddr[0].second; @@ -10140,7 +10256,8 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp // we can fully pay that destination LOG_PRINT_L2("We can fully pay " << get_account_address_as_str(m_nettype, dsts[0].is_subaddress, dsts[0].addr) << " for " << print_money(dsts[0].amount)); - if (!tx.add(dsts[0], dsts[0].amount, original_output_index, m_merge_destinations, BULLETPROOF_MAX_OUTPUTS-1)) + const bool subtract_fee_from_this_dest = subtract_fee_from_outputs.count(destination_index); + if (!tx.add(dsts[0], dsts[0].amount, original_output_index, m_merge_destinations, BULLETPROOF_MAX_OUTPUTS-1, subtract_fee_from_this_dest)) { LOG_PRINT_L2("Didn't pay: ran out of output slots"); out_slots_exhausted = true; @@ -10150,6 +10267,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp dsts[0].amount = 0; pop_index(dsts, 0); ++original_output_index; + ++destination_index; } if (!out_slots_exhausted && available_amount > 0 && !dsts.empty() && @@ -10157,7 +10275,8 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp // we can partially fill that destination LOG_PRINT_L2("We can partially pay " << get_account_address_as_str(m_nettype, dsts[0].is_subaddress, dsts[0].addr) << " for " << print_money(available_amount) << "/" << print_money(dsts[0].amount)); - if (tx.add(dsts[0], available_amount, original_output_index, m_merge_destinations, BULLETPROOF_MAX_OUTPUTS-1)) + const bool subtract_fee_from_this_dest = subtract_fee_from_outputs.count(destination_index); + if (tx.add(dsts[0], available_amount, original_output_index, m_merge_destinations, BULLETPROOF_MAX_OUTPUTS-1, subtract_fee_from_this_dest)) { dsts[0].amount -= available_amount; available_amount = 0; @@ -10236,9 +10355,13 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp // Try to carve the estimated fee from the partial payment (if there is one) available_for_fee = try_carving_from_partial_payment(needed_fee, available_for_fee); - uint64_t inputs = 0, outputs = needed_fee; + uint64_t inputs = 0, outputs = 0; for (size_t idx: tx.selected_transfers) inputs += m_transfers[idx].amount(); for (const auto &o: tx.dsts) outputs += o.amount; + if (subtract_fee_from_outputs.empty()) // if normal tx that doesn't subtract fees + { + outputs += needed_fee; + } if (inputs < outputs) { @@ -10249,15 +10372,32 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp LOG_PRINT_L2("Trying to create a tx now, with " << tx.dsts.size() << " outputs and " << tx.selected_transfers.size() << " inputs"); + auto tx_dsts = tx.get_adjusted_dsts(needed_fee); if (use_rct) - transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, unlock_time, needed_fee, extra, + transfer_selected_rct(tx_dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, unlock_time, needed_fee, extra, test_tx, test_ptx, rct_config, use_view_tags); else - transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, unlock_time, needed_fee, extra, + transfer_selected(tx_dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, unlock_time, needed_fee, extra, detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx, use_view_tags); auto txBlob = t_serializable_object_to_blob(test_ptx.tx); needed_fee = calculate_fee(use_per_byte_fee, test_ptx.tx, txBlob.size(), base_fee, fee_quantization_mask); - available_for_fee = test_ptx.fee + test_ptx.change_dts.amount + (!test_ptx.dust_added_to_fee ? test_ptx.dust : 0); + + // Depending on the mode, we take extra fees from either our change output or the destination outputs for which subtract_fee_from_outputs is true + uint64_t output_available_for_fee = 0; + bool tx_has_subtractable_output = false; + for (size_t di = 0; di < tx.dsts.size(); ++di) + { + if (tx.dsts_are_fee_subtractable[di]) + { + output_available_for_fee += tx.dsts[di].amount; + tx_has_subtractable_output = true; + } + } + if (!tx_has_subtractable_output) + { + output_available_for_fee = test_ptx.change_dts.amount; + } + available_for_fee = test_ptx.fee + output_available_for_fee + (!test_ptx.dust_added_to_fee ? test_ptx.dust : 0); LOG_PRINT_L2("Made a " << get_weight_string(test_ptx.tx, txBlob.size()) << " tx, with " << print_money(available_for_fee) << " available for fee (" << print_money(needed_fee) << " needed)"); @@ -10273,18 +10413,24 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp else { LOG_PRINT_L2("We made a tx, adjusting fee and saving it, we need " << print_money(needed_fee) << " and we have " << print_money(test_ptx.fee)); - do { + size_t fee_tries; + for (fee_tries = 0; fee_tries < 10 && needed_fee > test_ptx.fee; ++fee_tries) { + tx_dsts = tx.get_adjusted_dsts(needed_fee); + if (use_rct) - transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, unlock_time, needed_fee, extra, + transfer_selected_rct(tx_dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, unlock_time, needed_fee, extra, test_tx, test_ptx, rct_config, use_view_tags); else - transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, unlock_time, needed_fee, extra, + transfer_selected(tx_dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, unlock_time, needed_fee, extra, detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx, use_view_tags); txBlob = t_serializable_object_to_blob(test_ptx.tx); needed_fee = calculate_fee(use_per_byte_fee, test_ptx.tx, txBlob.size(), base_fee, fee_quantization_mask); LOG_PRINT_L2("Made an attempt at a final " << get_weight_string(test_ptx.tx, txBlob.size()) << " tx, with " << print_money(test_ptx.fee) << " fee and " << print_money(test_ptx.change_dts.amount) << " change"); - } while (needed_fee > test_ptx.fee); + }; + + THROW_WALLET_EXCEPTION_IF(fee_tries == 10, error::wallet_internal_error, + "Too many attempts to raise pending tx fee to level of needed fee"); LOG_PRINT_L2("Made a final " << get_weight_string(test_ptx.tx, txBlob.size()) << " tx, with " << print_money(test_ptx.fee) << " fee and " << print_money(test_ptx.change_dts.amount) << " change"); @@ -10337,10 +10483,13 @@ skip_tx: for (std::vector<TX>::iterator i = txes.begin(); i != txes.end(); ++i) { TX &tx = *i; + + const auto tx_dsts = tx.get_adjusted_dsts(tx.needed_fee); + cryptonote::transaction test_tx; pending_tx test_ptx; if (use_rct) { - transfer_selected_rct(tx.dsts, /* NOMOD std::vector<cryptonote::tx_destination_entry> dsts,*/ + transfer_selected_rct(tx_dsts, /* NOMOD std::vector<cryptonote::tx_destination_entry> dsts,*/ tx.selected_transfers, /* const std::list<size_t> selected_transfers */ fake_outs_count, /* CONST size_t fake_outputs_count, */ tx.outs, /* MOD std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, */ @@ -10353,7 +10502,7 @@ skip_tx: rct_config, use_view_tags); /* const bool use_view_tags */ } else { - transfer_selected(tx.dsts, + transfer_selected(tx_dsts, tx.selected_transfers, fake_outs_count, tx.outs, @@ -10387,23 +10536,38 @@ skip_tx: ptx_vector.push_back(tx.ptx); } - THROW_WALLET_EXCEPTION_IF(!sanity_check(ptx_vector, original_dsts), error::wallet_internal_error, "Created transaction(s) failed sanity check"); + THROW_WALLET_EXCEPTION_IF(!sanity_check(ptx_vector, original_dsts, subtract_fee_from_outputs), error::wallet_internal_error, "Created transaction(s) failed sanity check"); // if we made it this far, we're OK to actually send the transactions return ptx_vector; } -bool wallet2::sanity_check(const std::vector<wallet2::pending_tx> &ptx_vector, std::vector<cryptonote::tx_destination_entry> dsts) const +bool wallet2::sanity_check(const std::vector<wallet2::pending_tx> &ptx_vector, const std::vector<cryptonote::tx_destination_entry>& dsts, const unique_index_container& subtract_fee_from_outputs) const { - MDEBUG("sanity_check: " << ptx_vector.size() << " txes, " << dsts.size() << " destinations"); + MDEBUG("sanity_check: " << ptx_vector.size() << " txes, " << dsts.size() << " destinations, subtract_fee_from_outputs " << + (subtract_fee_from_outputs.size() ? "enabled" : "disabled")); THROW_WALLET_EXCEPTION_IF(ptx_vector.empty(), error::wallet_internal_error, "No transactions"); + THROW_WALLET_EXCEPTION_IF(!subtract_fee_from_outputs.empty() && ptx_vector.size() != 1, + error::wallet_internal_error, "feature subtractfeefrom not supported for split transactions"); + + // For destinations from where the fee is subtracted, the required amount has to be at least + // target amount - (tx fee / num_subtractable + 1). +1 since fee might not be evenly divisble by + // the number of subtractble destinations. For non-subtractable destinations, we need at least + // the target amount. + const size_t num_subtractable_dests = subtract_fee_from_outputs.size(); + const uint64_t fee0 = ptx_vector[0].fee; + const uint64_t subtractable_fee_deduction = fee0 / std::max<size_t>(num_subtractable_dests, 1) + 1; // check every party in there does receive at least the required amount std::unordered_map<account_public_address, std::pair<uint64_t, bool>> required; - for (const auto &d: dsts) + for (size_t i = 0; i < dsts.size(); ++i) { - required[d.addr].first += d.amount; + const cryptonote::tx_destination_entry& d = dsts[i]; + const bool dest_is_subtractable = subtract_fee_from_outputs.count(i); + const uint64_t fee_deduction = dest_is_subtractable ? subtractable_fee_deduction : 0; + const uint64_t required_amount = d.amount - std::min(fee_deduction, d.amount); + required[d.addr].first += required_amount; required[d.addr].second = d.is_subaddress; } @@ -11985,7 +12149,7 @@ std::string wallet2::get_reserve_proof(const boost::optional<std::pair<uint32_t, } // collect all subaddress spend keys that received those outputs and generate their signatures - serializable_unordered_map<crypto::public_key, crypto::signature> subaddr_spendkeys; + std::unordered_map<crypto::public_key, crypto::signature> subaddr_spendkeys; for (const cryptonote::subaddress_index &index : subaddr_indices) { crypto::secret_key subaddr_spend_skey = m_account.get_keys().m_spend_secret_key; @@ -12030,7 +12194,7 @@ bool wallet2::check_reserve_proof(const cryptonote::account_public_address &addr bool loaded = false; std::vector<reserve_proof_entry> proofs; - serializable_unordered_map<crypto::public_key, crypto::signature> subaddr_spendkeys; + std::unordered_map<crypto::public_key, crypto::signature> subaddr_spendkeys; try { binary_archive<false> ar{epee::strspan<std::uint8_t>(sig_decoded)}; @@ -12044,7 +12208,7 @@ bool wallet2::check_reserve_proof(const cryptonote::account_public_address &addr { std::istringstream iss(sig_decoded); boost::archive::portable_binary_iarchive ar(iss); - ar >> proofs >> subaddr_spendkeys.parent(); + ar >> proofs >> subaddr_spendkeys; } THROW_WALLET_EXCEPTION_IF(subaddr_spendkeys.count(address.m_spend_public_key) == 0, error::wallet_internal_error, @@ -12288,7 +12452,7 @@ std::string wallet2::get_description() const return ""; } -const std::pair<serializable_map<std::string, std::string>, std::vector<std::string>>& wallet2::get_account_tags() +const std::pair<std::map<std::string, std::string>, std::vector<std::string>>& wallet2::get_account_tags() { // ensure consistency if (m_account_tags.second.size() != get_num_subaddress_accounts()) @@ -13511,7 +13675,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 d93b9b9fb..c5d2dc90c 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -590,7 +590,8 @@ private: }; typedef std::vector<transfer_details> transfer_container; - typedef serializable_unordered_multimap<crypto::hash, payment_details> payment_container; + typedef std::unordered_multimap<crypto::hash, payment_details> payment_container; + typedef std::set<uint32_t> unique_index_container; struct multisig_sig { @@ -701,7 +702,7 @@ private: { std::vector<pending_tx> ptx; std::vector<crypto::key_image> key_images; - serializable_unordered_map<crypto::public_key, crypto::key_image> tx_key_images; + std::unordered_map<crypto::public_key, crypto::key_image> tx_key_images; BEGIN_SERIALIZE_OBJECT() VERSION_FIELD(0) @@ -1096,11 +1097,11 @@ private: bool parse_unsigned_tx_from_str(const std::string &unsigned_tx_st, unsigned_tx_set &exported_txs) const; bool load_tx(const std::string &signed_filename, std::vector<tools::wallet2::pending_tx> &ptx, std::function<bool(const signed_tx_set&)> accept_func = NULL); bool parse_tx_from_str(const std::string &signed_tx_st, std::vector<tools::wallet2::pending_tx> &ptx, std::function<bool(const signed_tx_set &)> accept_func); - std::vector<wallet2::pending_tx> create_transactions_2(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices); // pass subaddr_indices by value on purpose + std::vector<wallet2::pending_tx> create_transactions_2(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices, const unique_index_container& subtract_fee_from_outputs = {}); // pass subaddr_indices by value on purpose std::vector<wallet2::pending_tx> create_transactions_all(uint64_t below, const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices); std::vector<wallet2::pending_tx> create_transactions_single(const crypto::key_image &ki, const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra); std::vector<wallet2::pending_tx> create_transactions_from(const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, std::vector<size_t> unused_transfers_indices, std::vector<size_t> unused_dust_indices, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra); - bool sanity_check(const std::vector<wallet2::pending_tx> &ptx_vector, std::vector<cryptonote::tx_destination_entry> dsts) const; + bool sanity_check(const std::vector<wallet2::pending_tx> &ptx_vector, const std::vector<cryptonote::tx_destination_entry>& dsts, const unique_index_container& subtract_fee_from_outputs = {}) const; void cold_tx_aux_import(const std::vector<pending_tx>& ptx, const std::vector<std::string>& tx_device_aux); void cold_sign_tx(const std::vector<pending_tx>& ptx_vector, signed_tx_set &exported_txs, std::vector<cryptonote::address_parse_info> &dsts_info, std::vector<std::string> & tx_device_aux); uint64_t cold_key_image_sync(uint64_t &spent, uint64_t &unspent); @@ -1157,25 +1158,25 @@ private: } a & m_transfers; a & m_account_public_address; - a & m_key_images.parent(); + a & m_key_images; if(ver < 6) return; - a & m_unconfirmed_txs.parent(); + a & m_unconfirmed_txs; if(ver < 7) return; - a & m_payments.parent(); + a & m_payments; if(ver < 8) return; - a & m_tx_keys.parent(); + a & m_tx_keys; if(ver < 9) return; - a & m_confirmed_txs.parent(); + a & m_confirmed_txs; if(ver < 11) return; a & dummy_refresh_height; if(ver < 12) return; - a & m_tx_notes.parent(); + a & m_tx_notes; if(ver < 13) return; if (ver < 17) @@ -1200,7 +1201,7 @@ private: } return; } - a & m_pub_keys.parent(); + a & m_pub_keys; if(ver < 16) return; a & m_address_book; @@ -1221,17 +1222,17 @@ private: a & m_scanned_pool_txs[1]; if (ver < 20) return; - a & m_subaddresses.parent(); + a & m_subaddresses; std::unordered_map<cryptonote::subaddress_index, crypto::public_key> dummy_subaddresses_inv; a & dummy_subaddresses_inv; a & m_subaddress_labels; - a & m_additional_tx_keys.parent(); + a & m_additional_tx_keys; if(ver < 21) return; - a & m_attributes.parent(); + a & m_attributes; if(ver < 22) return; - a & m_unconfirmed_payments.parent(); + a & m_unconfirmed_payments; if(ver < 23) return; a & (std::pair<std::map<std::string, std::string>, std::vector<std::string>>&)m_account_tags; @@ -1243,13 +1244,13 @@ private: a & m_last_block_reward; if(ver < 26) return; - a & m_tx_device.parent(); + a & m_tx_device; if(ver < 27) return; a & m_device_last_key_image_sync; if(ver < 28) return; - a & m_cold_key_images.parent(); + a & m_cold_key_images; if(ver < 29) return; crypto::secret_key dummy_rpc_client_secret_key; // Compatibility for old RPC payment system @@ -1464,7 +1465,7 @@ private: * \brief Get the list of registered account tags. * \return first.Key=(tag's name), first.Value=(tag's label), second[i]=(i-th account's tag) */ - const std::pair<serializable_map<std::string, std::string>, std::vector<std::string>>& get_account_tags(); + const std::pair<std::map<std::string, std::string>, std::vector<std::string>>& get_account_tags(); /*! * \brief Set a tag to the given accounts. * \param account_indices Indices of accounts. @@ -1776,28 +1777,28 @@ private: std::string m_mms_file; const std::unique_ptr<epee::net_utils::http::abstract_http_client> m_http_client; hashchain m_blockchain; - serializable_unordered_map<crypto::hash, unconfirmed_transfer_details> m_unconfirmed_txs; - serializable_unordered_map<crypto::hash, confirmed_transfer_details> m_confirmed_txs; - serializable_unordered_multimap<crypto::hash, pool_payment_details> m_unconfirmed_payments; - serializable_unordered_map<crypto::hash, crypto::secret_key> m_tx_keys; + std::unordered_map<crypto::hash, unconfirmed_transfer_details> m_unconfirmed_txs; + std::unordered_map<crypto::hash, confirmed_transfer_details> m_confirmed_txs; + std::unordered_multimap<crypto::hash, pool_payment_details> m_unconfirmed_payments; + std::unordered_map<crypto::hash, crypto::secret_key> m_tx_keys; cryptonote::checkpoints m_checkpoints; - serializable_unordered_map<crypto::hash, std::vector<crypto::secret_key>> m_additional_tx_keys; + std::unordered_map<crypto::hash, std::vector<crypto::secret_key>> m_additional_tx_keys; transfer_container m_transfers; payment_container m_payments; - serializable_unordered_map<crypto::key_image, size_t> m_key_images; - serializable_unordered_map<crypto::public_key, size_t> m_pub_keys; + std::unordered_map<crypto::key_image, size_t> m_key_images; + std::unordered_map<crypto::public_key, size_t> m_pub_keys; cryptonote::account_public_address m_account_public_address; - serializable_unordered_map<crypto::public_key, cryptonote::subaddress_index> m_subaddresses; + std::unordered_map<crypto::public_key, cryptonote::subaddress_index> m_subaddresses; std::vector<std::vector<std::string>> m_subaddress_labels; - serializable_unordered_map<crypto::hash, std::string> m_tx_notes; - serializable_unordered_map<std::string, std::string> m_attributes; + std::unordered_map<crypto::hash, std::string> m_tx_notes; + std::unordered_map<std::string, std::string> m_attributes; std::vector<tools::wallet2::address_book_row> m_address_book; - std::pair<serializable_map<std::string, std::string>, std::vector<std::string>> m_account_tags; + std::pair<std::map<std::string, std::string>, std::vector<std::string>> m_account_tags; uint64_t m_upper_transaction_weight_limit; //TODO: auto-calc this value or request from daemon, now use some fixed value const std::vector<std::vector<tools::wallet2::multisig_info>> *m_multisig_rescan_info; const std::vector<std::vector<rct::key>> *m_multisig_rescan_k; - serializable_unordered_map<crypto::public_key, crypto::key_image> m_cold_key_images; + std::unordered_map<crypto::public_key, crypto::key_image> m_cold_key_images; std::atomic<bool> m_run; @@ -1868,7 +1869,7 @@ private: bool m_allow_mismatched_daemon_version; // Aux transaction data from device - serializable_unordered_map<crypto::hash, std::string> m_tx_device; + std::unordered_map<crypto::hash, std::string> m_tx_device; std::string m_ring_database; bool m_ring_history_saved; @@ -2313,7 +2314,7 @@ namespace boost a & x.key_images; if (ver < 1) return; - a & x.tx_key_images.parent(); + a & x.tx_key_images; } template <class Archive> diff --git a/src/wallet/wallet_errors.h b/src/wallet/wallet_errors.h index 6706e77ff..5166f868f 100644 --- a/src/wallet/wallet_errors.h +++ b/src/wallet/wallet_errors.h @@ -85,6 +85,7 @@ namespace tools // tx_too_big // zero_amount // zero_destination + // subtract_fee_from_bad_index // wallet_rpc_error * // daemon_busy // no_connection_to_daemon @@ -779,6 +780,15 @@ namespace tools } }; //---------------------------------------------------------------------------------------------------- + struct subtract_fee_from_bad_index : public transfer_error + { + explicit subtract_fee_from_bad_index(std::string&& loc, long bad_index) + : transfer_error(std::move(loc), + "subtractfeefrom: bad index: " + std::to_string(bad_index) + " (indexes are 0-based)") + { + } + }; + //---------------------------------------------------------------------------------------------------- struct wallet_rpc_error : public wallet_logic_error { const std::string& request() const { return m_request; } diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index 0cc42ae3f..c43745dc6 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -987,9 +987,9 @@ namespace tools return amount; } //------------------------------------------------------------------------------------------------------------------------------ - template<typename Ts, typename Tu, typename Tk> + template<typename Ts, typename Tu, typename Tk, typename Ta> bool wallet_rpc_server::fill_response(std::vector<tools::wallet2::pending_tx> &ptx_vector, - bool get_tx_key, Ts& tx_key, Tu &amount, Tu &fee, Tu &weight, std::string &multisig_txset, std::string &unsigned_txset, bool do_not_relay, + bool get_tx_key, Ts& tx_key, Tu &amount, Ta &amounts_by_dest, Tu &fee, Tu &weight, std::string &multisig_txset, std::string &unsigned_txset, bool do_not_relay, Ts &tx_hash, bool get_tx_hex, Ts &tx_blob, bool get_tx_metadata, Ts &tx_metadata, Tk &spent_key_images, epee::json_rpc::error &er) { for (const auto & ptx : ptx_vector) @@ -1006,6 +1006,12 @@ namespace tools fill(fee, ptx.fee); fill(weight, cryptonote::get_transaction_weight(ptx.tx)); + // add amounts by destination + tools::wallet_rpc::amounts_list abd; + for (const auto& dst : ptx.dests) + abd.amounts.push_back(dst.amount); + fill(amounts_by_dest, abd); + // add spent key images tools::wallet_rpc::key_image_list key_image_list; bool all_are_txin_to_key = std::all_of(ptx.tx.vin.begin(), ptx.tx.vin.end(), [&](const cryptonote::txin_v& s_e) -> bool @@ -1086,7 +1092,7 @@ namespace tools { uint64_t mixin = m_wallet->adjust_mixin(req.ring_size ? req.ring_size - 1 : 0); uint32_t priority = m_wallet->adjust_priority(req.priority); - std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_2(dsts, mixin, req.unlock_time, priority, extra, req.account_index, req.subaddr_indices); + std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_2(dsts, mixin, req.unlock_time, priority, extra, req.account_index, req.subaddr_indices, req.subtract_fee_from_outputs); if (ptx_vector.empty()) { @@ -1103,7 +1109,7 @@ namespace tools return false; } - return fill_response(ptx_vector, req.get_tx_key, res.tx_key, res.amount, res.fee, res.weight, res.multisig_txset, res.unsigned_txset, req.do_not_relay, + return fill_response(ptx_vector, req.get_tx_key, res.tx_key, res.amount, res.amounts_by_dest, res.fee, res.weight, res.multisig_txset, res.unsigned_txset, req.do_not_relay, res.tx_hash, req.get_tx_hex, res.tx_blob, req.get_tx_metadata, res.tx_metadata, res.spent_key_images, er); } catch (const std::exception& e) @@ -1151,7 +1157,7 @@ namespace tools return false; } - return fill_response(ptx_vector, req.get_tx_keys, res.tx_key_list, res.amount_list, res.fee_list, res.weight_list, res.multisig_txset, res.unsigned_txset, req.do_not_relay, + return fill_response(ptx_vector, req.get_tx_keys, res.tx_key_list, res.amount_list, res.amounts_by_dest_list, res.fee_list, res.weight_list, res.multisig_txset, res.unsigned_txset, req.do_not_relay, res.tx_hash_list, req.get_tx_hex, res.tx_blob_list, req.get_tx_metadata, res.tx_metadata_list, res.spent_key_images_list, er); } catch (const std::exception& e) @@ -1540,7 +1546,7 @@ namespace tools { std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_unmixable_sweep_transactions(); - return fill_response(ptx_vector, req.get_tx_keys, res.tx_key_list, res.amount_list, res.fee_list, res.weight_list, res.multisig_txset, res.unsigned_txset, req.do_not_relay, + return fill_response(ptx_vector, req.get_tx_keys, res.tx_key_list, res.amount_list, res.amounts_by_dest_list, res.fee_list, res.weight_list, res.multisig_txset, res.unsigned_txset, req.do_not_relay, res.tx_hash_list, req.get_tx_hex, res.tx_blob_list, req.get_tx_metadata, res.tx_metadata_list, res.spent_key_images_list, er); } catch (const std::exception& e) @@ -1600,7 +1606,7 @@ namespace tools uint32_t priority = m_wallet->adjust_priority(req.priority); std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_all(req.below_amount, dsts[0].addr, dsts[0].is_subaddress, req.outputs, mixin, req.unlock_time, priority, extra, req.account_index, subaddr_indices); - return fill_response(ptx_vector, req.get_tx_keys, res.tx_key_list, res.amount_list, res.fee_list, res.weight_list, res.multisig_txset, res.unsigned_txset, req.do_not_relay, + return fill_response(ptx_vector, req.get_tx_keys, res.tx_key_list, res.amount_list, res.amounts_by_dest_list, res.fee_list, res.weight_list, res.multisig_txset, res.unsigned_txset, req.do_not_relay, res.tx_hash_list, req.get_tx_hex, res.tx_blob_list, req.get_tx_metadata, res.tx_metadata_list, res.spent_key_images_list, er); } catch (const std::exception& e) @@ -1677,7 +1683,7 @@ namespace tools return false; } - return fill_response(ptx_vector, req.get_tx_key, res.tx_key, res.amount, res.fee, res.weight, res.multisig_txset, res.unsigned_txset, req.do_not_relay, + return fill_response(ptx_vector, req.get_tx_key, res.tx_key, res.amount, res.amounts_by_dest, res.fee, res.weight, res.multisig_txset, res.unsigned_txset, req.do_not_relay, res.tx_hash, req.get_tx_hex, res.tx_blob, req.get_tx_metadata, res.tx_metadata, res.spent_key_images, er); } catch (const std::exception& e) diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h index 282035052..bfb7013e2 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" @@ -263,9 +262,9 @@ namespace tools bool not_open(epee::json_rpc::error& er); void handle_rpc_exception(const std::exception_ptr& e, epee::json_rpc::error& er, int default_error_code); - template<typename Ts, typename Tu, typename Tk> + template<typename Ts, typename Tu, typename Tk, typename Ta> bool fill_response(std::vector<tools::wallet2::pending_tx> &ptx_vector, - bool get_tx_key, Ts& tx_key, Tu &amount, Tu &fee, Tu &weight, std::string &multisig_txset, std::string &unsigned_txset, bool do_not_relay, + bool get_tx_key, Ts& tx_key, Tu &amount, Ta &amounts_by_dest, Tu &fee, Tu &weight, std::string &multisig_txset, std::string &unsigned_txset, bool do_not_relay, Ts &tx_hash, bool get_tx_hex, Ts &tx_blob, bool get_tx_metadata, Ts &tx_metadata, Tk &spent_key_images, epee::json_rpc::error &er); bool validate_transfer(const std::list<wallet_rpc::transfer_destination>& destinations, const std::string& payment_id, std::vector<cryptonote::tx_destination_entry>& dsts, std::vector<uint8_t>& extra, bool at_least_one_destination, epee::json_rpc::error& er); diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h index c00271030..5d5579ced 100644 --- a/src/wallet/wallet_rpc_server_commands_defs.h +++ b/src/wallet/wallet_rpc_server_commands_defs.h @@ -47,7 +47,7 @@ // advance which version they will stop working with // Don't go over 32767 for any of these #define WALLET_RPC_VERSION_MAJOR 1 -#define WALLET_RPC_VERSION_MINOR 26 +#define WALLET_RPC_VERSION_MINOR 27 #define MAKE_WALLET_RPC_VERSION(major,minor) (((major)<<16)|(minor)) #define WALLET_RPC_VERSION MAKE_WALLET_RPC_VERSION(WALLET_RPC_VERSION_MAJOR, WALLET_RPC_VERSION_MINOR) namespace tools @@ -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() }; @@ -530,11 +530,23 @@ namespace wallet_rpc END_KV_SERIALIZE_MAP() }; + struct amounts_list + { + std::list<uint64_t> amounts; + + bool operator==(const amounts_list& other) const { return amounts == other.amounts; } + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(amounts) + END_KV_SERIALIZE_MAP() + }; + struct single_transfer_response { std::string tx_hash; std::string tx_key; uint64_t amount; + amounts_list amounts_by_dest; uint64_t fee; uint64_t weight; std::string tx_blob; @@ -547,6 +559,7 @@ namespace wallet_rpc KV_SERIALIZE(tx_hash) KV_SERIALIZE(tx_key) KV_SERIALIZE(amount) + KV_SERIALIZE_OPT(amounts_by_dest, decltype(amounts_by_dest)()) KV_SERIALIZE(fee) KV_SERIALIZE(weight) KV_SERIALIZE(tx_blob) @@ -564,6 +577,7 @@ namespace wallet_rpc std::list<transfer_destination> destinations; uint32_t account_index; std::set<uint32_t> subaddr_indices; + std::set<uint32_t> subtract_fee_from_outputs; uint32_t priority; uint64_t ring_size; uint64_t unlock_time; @@ -577,6 +591,7 @@ namespace wallet_rpc KV_SERIALIZE(destinations) KV_SERIALIZE(account_index) KV_SERIALIZE(subaddr_indices) + KV_SERIALIZE_OPT(subtract_fee_from_outputs, decltype(subtract_fee_from_outputs)()) KV_SERIALIZE(priority) KV_SERIALIZE_OPT(ring_size, (uint64_t)0) KV_SERIALIZE(unlock_time) @@ -598,6 +613,7 @@ namespace wallet_rpc std::list<std::string> tx_hash_list; std::list<std::string> tx_key_list; std::list<uint64_t> amount_list; + std::list<amounts_list> amounts_by_dest_list; std::list<uint64_t> fee_list; std::list<uint64_t> weight_list; std::list<std::string> tx_blob_list; @@ -610,6 +626,7 @@ namespace wallet_rpc KV_SERIALIZE(tx_hash_list) KV_SERIALIZE(tx_key_list) KV_SERIALIZE(amount_list) + KV_SERIALIZE_OPT(amounts_by_dest_list, decltype(amounts_by_dest_list)()) KV_SERIALIZE(fee_list) KV_SERIALIZE(weight_list) KV_SERIALIZE(tx_blob_list) @@ -1029,7 +1046,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 +1182,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 +1425,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 +1576,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 +1600,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 +1618,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 +1630,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 +1660,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 +1675,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 +1690,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 +1720,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 +1733,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 +1743,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 +1756,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 +1767,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 +1778,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 +1793,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 +1804,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 +1834,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 +1878,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 +1904,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 +1981,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 +2029,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; |